import type { AbstractCrdtDocFactory } from "@pluv/crdt"; import type { IOLike, IORouterLike, JsonObject } from "@pluv/types"; import type { PluvProcedure } from "./PluvProcedure"; export type PluvRouterEventConfig< TIO extends IOLike, TPresence extends Record, TCrdt extends AbstractCrdtDocFactory, > = { [P: string]: Pick, "config"> }; export type MergedRouter< TRouters extends PluvRouter[], TIO extends IOLike, TPresence extends Record, TCrdt extends AbstractCrdtDocFactory, TRoot extends TRouters[0]["_defs"]["events"], > = TRouters extends [ infer IHead extends PluvRouter, ...infer ITail extends PluvRouter[], ] ? MergedRouter : PluvRouter; export class PluvRouter< TIO extends IOLike, TPresence extends Record, TCrdt extends AbstractCrdtDocFactory, TEvents extends PluvRouterEventConfig = {}, > implements IORouterLike { readonly _defs: { events: TEvents } = { events: {} as TEvents }; constructor(events: TEvents) { const invalidName = Object.keys(events).find((name) => !this._isValidEventName(name)); if (typeof invalidName === "string") { throw new Error( `Invalid event name. Event names must be formatted as valid JavaScript variable names: "${invalidName}"`, ); } this._defs = { events }; } public static merge< TRouters extends PluvRouter[], TIO extends IOLike, TPresence extends Record, TCrdt extends AbstractCrdtDocFactory, >(...routers: TRouters): MergedRouter { const events = Object.assign( Object.create(null), ...routers.map((router) => router._defs.events), ); if (Object.keys(events).some((key) => key.startsWith("$"))) { throw new Error('Procedures may not start with "$"'); } return new PluvRouter(events) as MergedRouter< TRouters, TIO, TPresence, TCrdt, {} >; } private _isValidEventName(name: string): boolean { return /^[a-z_$][a-z0-9_$]*$/gi.test(name); } }