Connor McCutcheon
/ Music
rpc.ts
ts
/* eslint-disable no-restricted-globals */
export type WorkerModule = Record<string, (...args: any) => any>;
export type Remote<T extends WorkerModule> = {
  [K in keyof T]: (
    T[K] extends (...args: infer Args) => infer R
    ? (...args: Args) => (R extends Promise<any> ? R : Promise<R>)
    : never
  );
};
type TransferableFactory<T extends any[]> = (...args: T) => Transferable[];
export type LocalTransferableFactoryMap<T extends WorkerModule> = Partial<{
  [K in keyof T]: TransferableFactory<Parameters<T[K]>>;
}>;
const enum Completion {
  resolve,
  reject,
};
const noTransferable: Transferable[] = [];
const noTransferableFactory = () => noTransferable;
const noFactoryMap = {};
const wrap = <T extends WorkerModule>(
  worker: Worker, factoryMap: LocalTransferableFactoryMap<T> = noFactoryMap,
) => {
  let id = 0;
  const completions: Map<number, Parameters<ConstructorParameters<PromiseConstructor>[0]>> = new Map();
  const listener = <K extends keyof T>(event: MessageEvent<[id: number, completion: Completion, result: ReturnType<T[K]>]>) => {
    const { 0: id, 1: completion, 2: result } = event.data;
    const settle = completions.get(id)![completion];
    completions.delete(id);
    settle(result);
  };
  const { proxy, revoke } = Proxy.revocable<Remote<T>>(Object.create(null), {
    get: (target, key) => {
      if (typeof key === 'string' && !(key in target)) {
        const method = (...args: Parameters<T[keyof T]>) => new Promise((resolve, reject) => {
          const { [key as keyof T]: factory = noTransferableFactory } = factoryMap;
          completions.set(id, [resolve, reject]);
          worker.postMessage([key, id, args], factory.apply(undefined, args));
          id = (id + 1) % Number.MAX_SAFE_INTEGER;
        });
        (target[key as keyof T] as any) = method;
      }
      return target[key as keyof T];
    } 
  });
  worker.addEventListener('message', listener);
  return {
    proxy,
    revoke: () => {
      worker.removeEventListener('message', listener);
      revoke();
    }
  };
};
export type RemoteTransferableFactoryMap<T extends WorkerModule> = Partial<{
  [K in keyof T]: TransferableFactory<[ReturnType<T[K]>]>;
}>;
const expose = <T extends WorkerModule>(
  workerModule: T, factoryMap: RemoteTransferableFactoryMap<T> = noFactoryMap,
) => {
  const listener = async <K extends keyof T>(event: MessageEvent<[key: K, id: number, args: Parameters<T[K]>]>) => {
    const { 0: key, 1: id, 2: args } = event.data;
    const method = workerModule[key];
    const { [key]: factory = noTransferableFactory } = factoryMap;
    try {
      const value = await method.apply(undefined, args);
      postMessage([id, Completion.resolve, value], factory(value));
    } catch (reason) {
      postMessage([id, Completion.reject, reason]);
    }
  };
  addEventListener('message', listener);
};
export { wrap, expose };
No comments yet.