typescriptadvanced

JavaScript Proxy Handler Pattern

Use Proxy and Reflect to create observable objects, validation layers, and dynamic API clients.

typescript
// Observable object with change tracking
function createObservable<T extends object>(
  target: T,
  onChange: (prop: string, oldVal: unknown, newVal: unknown) => void,
): T {
  return new Proxy(target, {
    set(obj, prop, value) {
      const old = Reflect.get(obj, prop);
      const result = Reflect.set(obj, prop, value);
      if (old !== value) onChange(String(prop), old, value);
      return result;
    },
  });
}

// Validated object
function createValidated<T extends object>(
  target: T,
  validators: Partial<Record<keyof T, (v: unknown) => boolean>>,
): T {
  return new Proxy(target, {
    set(obj, prop, value) {
      const validator = validators[prop as keyof T];
      if (validator && !validator(value)) {
        throw new TypeError(`Invalid value for ${String(prop)}: ${value}`);
      }
      return Reflect.set(obj, prop, value);
    },
  });
}

// Dynamic API client (generates methods from URL paths)
function createApiClient(baseUrl: string): Record<string, Function> {
  return new Proxy({} as Record<string, Function>, {
    get(_, prop) {
      return async (params?: Record<string, string>) => {
        const url = new URL(`/${String(prop)}`, baseUrl);
        if (params) {
          Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
        }
        console.log(`GET ${url.toString()}`);
        // const res = await fetch(url); return res.json();
        return { endpoint: String(prop), params };
      };
    },
  });
}

// Usage
const user = createObservable({ name: 'Alice', age: 30 }, (prop, old, val) => {
  console.log(`${prop}: ${old} -> ${val}`);
});
user.name = 'Bob';   // name: Alice -> Bob
user.age = 31;       // age: 30 -> 31

const config = createValidated(
  { port: 3000, host: 'localhost' },
  { port: (v) => typeof v === 'number' && (v as number) > 0 && (v as number) < 65536 },
);
config.port = 8080; // OK
try { config.port = -1; } catch (e: any) { console.log(e.message); }

const api = createApiClient('https://api.example.com');
api.users({ page: '1' }).then(console.log);
api.products({ category: 'tools' }).then(console.log);

Use Cases

  • Observable state management
  • Runtime input validation
  • Dynamic REST client generation

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.