@parcae/model
Advanced tools
| import { CHAINABLE_METHODS } from './chunk-F5LRP7SS.js'; | ||
| import { proxy } from 'valtio'; | ||
| function stripVersion(path) { | ||
| return path.replace(/^\/v\d+/, ""); | ||
| } | ||
| var FrontendAdapter = class { | ||
| transport; | ||
| constructor(transport) { | ||
| this.transport = transport; | ||
| } | ||
| p(modelPath) { | ||
| return stripVersion(modelPath); | ||
| } | ||
| // ── createStore ────────────────────────────────────────────────────── | ||
| createStore(data) { | ||
| return proxy(data); | ||
| } | ||
| // ── save ───────────────────────────────────────────────────────────── | ||
| async save(model, changes) { | ||
| const ModelClass = model.constructor; | ||
| const path = this.resolvePath(ModelClass); | ||
| if (changes.creating) { | ||
| const result = await this.transport.post(path, model.__data); | ||
| if (result?.id) model.id = result.id; | ||
| } else if (changes.ops.length > 0) { | ||
| await this.transport.patch(`${path}/${model.id}`, { ops: changes.ops }); | ||
| } else if (changes.updates.length > 0) { | ||
| const data = {}; | ||
| for (const key of changes.updates) { | ||
| data[key] = model.__data[key]; | ||
| } | ||
| await this.transport.put(`${path}/${model.id}`, data); | ||
| } | ||
| } | ||
| // ── remove ─────────────────────────────────────────────────────────── | ||
| async remove(model) { | ||
| const ModelClass = model.constructor; | ||
| const path = this.resolvePath(ModelClass); | ||
| await this.transport.delete(`${path}/${model.id}`); | ||
| } | ||
| // ── findById ───────────────────────────────────────────────────────── | ||
| async findById(modelClass, id) { | ||
| const path = this.resolvePath(modelClass); | ||
| try { | ||
| const data = await this.transport.get(`${path}/${id}`); | ||
| if (!data) return null; | ||
| return new modelClass(this, data); | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
| // ── query ──────────────────────────────────────────────────────────── | ||
| query(modelClass) { | ||
| return this._buildQuery(modelClass, []); | ||
| } | ||
| _buildQuery(modelClass, steps) { | ||
| const adapter = this; | ||
| const chain = {}; | ||
| for (const method of CHAINABLE_METHODS) { | ||
| chain[method] = (...args) => { | ||
| return adapter._buildQuery(modelClass, [ | ||
| ...steps, | ||
| { method, args: adapter._serializeArgs(args) } | ||
| ]); | ||
| }; | ||
| } | ||
| chain.find = async () => { | ||
| const path = adapter.resolvePath(modelClass); | ||
| const result = await adapter.transport.get(path, { __query: steps }); | ||
| const items = result?.[modelClass.type + "s"] ?? result?.items ?? []; | ||
| const models = items.map((row) => new modelClass(adapter, row)); | ||
| if (result?.__queryHash) { | ||
| Object.defineProperty(models, "__queryHash", { | ||
| value: result.__queryHash, | ||
| enumerable: false | ||
| }); | ||
| } | ||
| return models; | ||
| }; | ||
| chain.first = async () => { | ||
| const all = await chain.find(); | ||
| return all[0] ?? null; | ||
| }; | ||
| chain.count = async () => { | ||
| const path = adapter.resolvePath(modelClass); | ||
| const result = await adapter.transport.get(path, { | ||
| __query: steps, | ||
| __count: true | ||
| }); | ||
| return result?.total ?? 0; | ||
| }; | ||
| chain.__steps = steps; | ||
| chain.__modelType = modelClass.type; | ||
| chain.__modelClass = modelClass; | ||
| chain.__adapter = adapter; | ||
| return chain; | ||
| } | ||
| _serializeArgs(args) { | ||
| return args.map((arg) => { | ||
| if (typeof arg === "function") { | ||
| const nestedSteps = []; | ||
| const recorder = new Proxy( | ||
| {}, | ||
| { | ||
| get: (_target, method) => { | ||
| return (...innerArgs) => { | ||
| nestedSteps.push({ | ||
| method, | ||
| args: this._serializeArgs(innerArgs) | ||
| }); | ||
| return recorder; | ||
| }; | ||
| } | ||
| } | ||
| ); | ||
| arg(recorder); | ||
| return { __nested: nestedSteps }; | ||
| } | ||
| return arg; | ||
| }); | ||
| } | ||
| // ── patch ──────────────────────────────────────────────────────────── | ||
| async patch(model, ops) { | ||
| const ModelClass = model.constructor; | ||
| const path = this.resolvePath(ModelClass); | ||
| await this.transport.patch(`${path}/${model.id}`, { ops }); | ||
| } | ||
| // ── helpers ────────────────────────────────────────────────────────── | ||
| resolvePath(modelClass) { | ||
| if (!modelClass.path && !modelClass.type) { | ||
| throw new Error(`Model has no path or type`); | ||
| } | ||
| const fullPath = modelClass.path ?? `/v1/${modelClass.type}s`; | ||
| return this.p(fullPath); | ||
| } | ||
| }; | ||
| export { FrontendAdapter }; | ||
| //# sourceMappingURL=chunk-7RSAGEBY.js.map | ||
| //# sourceMappingURL=chunk-7RSAGEBY.js.map |
| {"version":3,"sources":["../src/adapters/client.ts"],"names":[],"mappings":";;;AA+FA,SAAS,aAAa,IAAA,EAAsB;AAC1C,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAA;AACnC;AAIO,IAAM,kBAAN,MAA8C;AAAA,EAC5C,SAAA;AAAA,EAEP,YAAY,SAAA,EAAsB;AAChC,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAAA,EACnB;AAAA,EAEQ,EAAE,SAAA,EAA2B;AACnC,IAAA,OAAO,aAAa,SAAS,CAAA;AAAA,EAC/B;AAAA;AAAA,EAIA,YAAY,IAAA,EAAgD;AAC1D,IAAA,OAAO,MAAM,IAAI,CAAA;AAAA,EACnB;AAAA;AAAA,EAIA,MAAM,IAAA,CAAK,KAAA,EAAY,OAAA,EAAmC;AACxD,IAAA,MAAM,aAAa,KAAA,CAAM,WAAA;AACzB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,WAAA,CAAY,UAAU,CAAA;AAExC,IAAA,IAAI,QAAQ,QAAA,EAAU;AACpB,MAAA,MAAM,SAAS,MAAM,IAAA,CAAK,UAAU,IAAA,CAAK,IAAA,EAAM,MAAM,MAAM,CAAA;AAC3D,MAAA,IAAI,MAAA,EAAQ,EAAA,EAAK,KAAA,CAAc,KAAK,MAAA,CAAO,EAAA;AAAA,IAC7C,CAAA,MAAA,IAAW,OAAA,CAAQ,GAAA,CAAI,MAAA,GAAS,CAAA,EAAG;AACjC,MAAA,MAAM,IAAA,CAAK,SAAA,CAAU,KAAA,CAAM,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,KAAA,CAAM,EAAE,CAAA,CAAA,EAAI,EAAE,GAAA,EAAK,OAAA,CAAQ,KAAK,CAAA;AAAA,IACxE,CAAA,MAAA,IAAW,OAAA,CAAQ,OAAA,CAAQ,MAAA,GAAS,CAAA,EAAG;AACrC,MAAA,MAAM,OAA4B,EAAC;AACnC,MAAA,KAAA,MAAW,GAAA,IAAO,QAAQ,OAAA,EAAS;AACjC,QAAA,IAAA,CAAK,GAAG,CAAA,GAAI,KAAA,CAAM,MAAA,CAAO,GAAG,CAAA;AAAA,MAC9B;AACA,MAAA,MAAM,IAAA,CAAK,UAAU,GAAA,CAAI,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,KAAA,CAAM,EAAE,CAAA,CAAA,EAAI,IAAI,CAAA;AAAA,IACtD;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,OAAO,KAAA,EAA2B;AACtC,IAAA,MAAM,aAAa,KAAA,CAAM,WAAA;AACzB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,WAAA,CAAY,UAAU,CAAA;AACxC,IAAA,MAAM,IAAA,CAAK,UAAU,MAAA,CAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,KAAA,CAAM,EAAE,CAAA,CAAE,CAAA;AAAA,EACnD;AAAA;AAAA,EAIA,MAAM,QAAA,CACJ,UAAA,EACA,EAAA,EACmB;AACnB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,WAAA,CAAY,UAAU,CAAA;AACxC,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,EAAE,CAAA,CAAE,CAAA;AACrD,MAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,MAAA,OAAO,IAAI,UAAA,CAAW,IAAA,EAAM,IAAI,CAAA;AAAA,IAClC,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA,EAIA,MAAS,UAAA,EAAgD;AACvD,IAAA,OAAO,IAAA,CAAK,WAAA,CAAY,UAAA,EAAY,EAAE,CAAA;AAAA,EACxC;AAAA,EAEQ,WAAA,CACN,YACA,KAAA,EACe;AACf,IAAA,MAAM,OAAA,GAAU,IAAA;AAEhB,IAAA,MAAM,QAAa,EAAC;AAEpB,IAAA,KAAA,MAAW,UAAU,iBAAA,EAAmB;AACtC,MAAA,KAAA,CAAM,MAAM,CAAA,GAAI,CAAA,GAAI,IAAA,KAAgB;AAClC,QAAA,OAAO,OAAA,CAAQ,YAAY,UAAA,EAAY;AAAA,UACrC,GAAG,KAAA;AAAA,UACH,EAAE,MAAA,EAAQ,IAAA,EAAM,OAAA,CAAQ,cAAA,CAAe,IAAI,CAAA;AAAE,SAC9C,CAAA;AAAA,MACH,CAAA;AAAA,IACF;AAEA,IAAA,KAAA,CAAM,OAAO,YAA0B;AACrC,MAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,WAAA,CAAY,UAAU,CAAA;AAC3C,MAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,SAAA,CAAU,IAAI,IAAA,EAAM,EAAE,OAAA,EAAS,KAAA,EAAO,CAAA;AACnE,MAAA,MAAM,KAAA,GAAQ,SAAS,UAAA,CAAW,IAAA,GAAO,GAAG,CAAA,IAAK,MAAA,EAAQ,SAAS,EAAC;AACnE,MAAA,MAAM,MAAA,GAAS,MAAM,GAAA,CAAI,CAAC,QAAa,IAAI,UAAA,CAAW,OAAA,EAAS,GAAG,CAAC,CAAA;AAEnE,MAAA,IAAI,QAAQ,WAAA,EAAa;AACvB,QAAA,MAAA,CAAO,cAAA,CAAe,QAAQ,aAAA,EAAe;AAAA,UAC3C,OAAO,MAAA,CAAO,WAAA;AAAA,UACd,UAAA,EAAY;AAAA,SACb,CAAA;AAAA,MACH;AACA,MAAA,OAAO,MAAA;AAAA,IACT,CAAA;AAEA,IAAA,KAAA,CAAM,QAAQ,YAA+B;AAC3C,MAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,IAAA,EAAK;AAC7B,MAAA,OAAO,GAAA,CAAI,CAAC,CAAA,IAAK,IAAA;AAAA,IACnB,CAAA;AAEA,IAAA,KAAA,CAAM,QAAQ,YAA6B;AACzC,MAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,WAAA,CAAY,UAAU,CAAA;AAC3C,MAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,SAAA,CAAU,IAAI,IAAA,EAAM;AAAA,QAC/C,OAAA,EAAS,KAAA;AAAA,QACT,OAAA,EAAS;AAAA,OACV,CAAA;AACD,MAAA,OAAO,QAAQ,KAAA,IAAS,CAAA;AAAA,IAC1B,CAAA;AAEA,IAAA,KAAA,CAAM,OAAA,GAAU,KAAA;AAChB,IAAA,KAAA,CAAM,cAAc,UAAA,CAAW,IAAA;AAC/B,IAAA,KAAA,CAAM,YAAA,GAAe,UAAA;AACrB,IAAA,KAAA,CAAM,SAAA,GAAY,OAAA;AAElB,IAAA,OAAO,KAAA;AAAA,EACT;AAAA,EAEQ,eAAe,IAAA,EAAoB;AACzC,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,KAAQ;AACvB,MAAA,IAAI,OAAO,QAAQ,UAAA,EAAY;AAE7B,QAAA,MAAM,cAA2B,EAAC;AAClC,QAAA,MAAM,WAAgB,IAAI,KAAA;AAAA,UACxB,EAAC;AAAA,UACD;AAAA,YACE,GAAA,EAAK,CAAC,OAAA,EAAS,MAAA,KAAmB;AAChC,cAAA,OAAO,IAAI,SAAA,KAAqB;AAC9B,gBAAA,WAAA,CAAY,IAAA,CAAK;AAAA,kBACf,MAAA;AAAA,kBACA,IAAA,EAAM,IAAA,CAAK,cAAA,CAAe,SAAS;AAAA,iBACpC,CAAA;AACD,gBAAA,OAAO,QAAA;AAAA,cACT,CAAA;AAAA,YACF;AAAA;AACF,SACF;AACA,QAAA,GAAA,CAAI,QAAQ,CAAA;AACZ,QAAA,OAAO,EAAE,UAAU,WAAA,EAAY;AAAA,MACjC;AACA,MAAA,OAAO,GAAA;AAAA,IACT,CAAC,CAAA;AAAA,EACH;AAAA;AAAA,EAIA,MAAM,KAAA,CAAM,KAAA,EAAY,GAAA,EAA+B;AACrD,IAAA,MAAM,aAAa,KAAA,CAAM,WAAA;AACzB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,WAAA,CAAY,UAAU,CAAA;AACxC,IAAA,MAAM,IAAA,CAAK,SAAA,CAAU,KAAA,CAAM,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,KAAA,CAAM,EAAE,CAAA,CAAA,EAAI,EAAE,GAAA,EAAK,CAAA;AAAA,EAC3D;AAAA;AAAA,EAIQ,YAAY,UAAA,EAAsC;AACxD,IAAA,IAAI,CAAC,UAAA,CAAW,IAAA,IAAQ,CAAC,WAAW,IAAA,EAAM;AACxC,MAAA,MAAM,IAAI,MAAM,CAAA,yBAAA,CAA2B,CAAA;AAAA,IAC7C;AACA,IAAA,MAAM,QAAA,GAAW,UAAA,CAAW,IAAA,IAAQ,CAAA,IAAA,EAAO,WAAW,IAAI,CAAA,CAAA,CAAA;AAC1D,IAAA,OAAO,IAAA,CAAK,EAAE,QAAQ,CAAA;AAAA,EACxB;AACF","file":"chunk-7RSAGEBY.js","sourcesContent":["/**\n * FrontendAdapter — Valtio + Transport for Parcae Model.\n *\n * Transport-agnostic. Works with any implementation of the Transport interface:\n * - SocketTransport (Socket.IO — bidirectional, realtime)\n * - SSETransport (Server-Sent Events — read-heavy, simpler)\n * - GRPCTransport (gRPC — service-to-service)\n * - Or any custom transport\n */\n\nimport { proxy } from \"valtio\";\nimport {\n CHAINABLE_METHODS,\n type ModelAdapter,\n type ModelConstructor,\n type ChangeSet,\n type QueryChain,\n type QueryStep,\n type PatchOp,\n} from \"./types\";\n\n// ─── Transport Interface ─────────────────────────────────────────────────────\n\n/** Per-request options that callers can pass to any RPC method. */\nexport interface RequestOptions {\n /** Override the default RPC timeout (in milliseconds). */\n timeout?: number;\n}\n\n/**\n * Abstract transport layer. Decouples the Model system from the wire protocol.\n *\n * Implementations:\n * - SocketTransport: Socket.IO (bidirectional, realtime subscriptions)\n * - SSETransport: HTTP + Server-Sent Events (read-heavy, simpler infra)\n * - GRPCTransport: gRPC (service-to-service, protobuf)\n */\nexport interface Transport {\n // ── Request/Response (RPC-style) ────────────────────────────────────\n get(path: string, data?: any, options?: RequestOptions): Promise<any>;\n post(path: string, data?: any, options?: RequestOptions): Promise<any>;\n put(path: string, data?: any, options?: RequestOptions): Promise<any>;\n patch(path: string, data?: any, options?: RequestOptions): Promise<any>;\n delete(path: string, data?: any, options?: RequestOptions): Promise<any>;\n\n // ── Subscriptions (realtime) ────────────────────────────────────────\n /**\n * Subscribe to a named event stream.\n * Returns a dispose function to unsubscribe.\n *\n * Socket.IO: socket.on(event, handler)\n * SSE: new EventSource(url) per channel\n * gRPC: server streaming RPC\n */\n subscribe?(event: string, handler: (...args: any[]) => void): () => void;\n\n /**\n * Unsubscribe from a named event stream.\n * Alternative to calling the dispose function returned by subscribe().\n */\n unsubscribe?(event: string, handler?: (...args: any[]) => void): void;\n\n // ── Control messages ────────────────────────────────────────────────\n /**\n * Send a control message to the server (fire-and-forget).\n *\n * Socket.IO: socket.emit(event, ...args)\n * SSE: POST /v1/__control { event, args }\n * gRPC: unary RPC\n */\n send?(event: string, ...args: any[]): void;\n\n // ── Connection state ────────────────────────────────────────────────\n /** Whether the transport is currently connected. */\n readonly isConnected?: boolean;\n /** Whether the transport is currently loading/connecting. */\n readonly isLoading?: boolean;\n\n /**\n * Listen for connection state changes.\n * Events: \"connected\", \"disconnected\", \"reconnecting\", \"error\"\n */\n on?(event: string, handler: (...args: any[]) => void): void;\n off?(event: string, handler?: (...args: any[]) => void): void;\n\n /** Disconnect the transport. */\n disconnect?(): void;\n /** Reconnect the transport. */\n reconnect?(): Promise<void>;\n}\n\n/**\n * Strip version prefix from a path if it starts with /v{number}.\n * The transport prepends the version automatically.\n */\nfunction stripVersion(path: string): string {\n return path.replace(/^\\/v\\d+/, \"\");\n}\n\n// ─── FrontendAdapter ─────────────────────────────────────────────────────────\n\nexport class FrontendAdapter implements ModelAdapter {\n public transport: Transport;\n\n constructor(transport: Transport) {\n this.transport = transport;\n }\n\n private p(modelPath: string): string {\n return stripVersion(modelPath);\n }\n\n // ── createStore ──────────────────────────────────────────────────────\n\n createStore(data: Record<string, any>): Record<string, any> {\n return proxy(data);\n }\n\n // ── save ─────────────────────────────────────────────────────────────\n\n async save(model: any, changes: ChangeSet): Promise<void> {\n const ModelClass = model.constructor as ModelConstructor;\n const path = this.resolvePath(ModelClass);\n\n if (changes.creating) {\n const result = await this.transport.post(path, model.__data);\n if (result?.id) (model as any).id = result.id;\n } else if (changes.ops.length > 0) {\n await this.transport.patch(`${path}/${model.id}`, { ops: changes.ops });\n } else if (changes.updates.length > 0) {\n const data: Record<string, any> = {};\n for (const key of changes.updates) {\n data[key] = model.__data[key];\n }\n await this.transport.put(`${path}/${model.id}`, data);\n }\n }\n\n // ── remove ───────────────────────────────────────────────────────────\n\n async remove(model: any): Promise<void> {\n const ModelClass = model.constructor as ModelConstructor;\n const path = this.resolvePath(ModelClass);\n await this.transport.delete(`${path}/${model.id}`);\n }\n\n // ── findById ─────────────────────────────────────────────────────────\n\n async findById<T>(\n modelClass: ModelConstructor<T>,\n id: string,\n ): Promise<T | null> {\n const path = this.resolvePath(modelClass);\n try {\n const data = await this.transport.get(`${path}/${id}`);\n if (!data) return null;\n return new modelClass(this, data) as T;\n } catch {\n return null;\n }\n }\n\n // ── query ────────────────────────────────────────────────────────────\n\n query<T>(modelClass: ModelConstructor<T>): QueryChain<T> {\n return this._buildQuery(modelClass, []);\n }\n\n private _buildQuery<T>(\n modelClass: ModelConstructor<T>,\n steps: QueryStep[],\n ): QueryChain<T> {\n const adapter = this;\n\n const chain: any = {};\n\n for (const method of CHAINABLE_METHODS) {\n chain[method] = (...args: any[]) => {\n return adapter._buildQuery(modelClass, [\n ...steps,\n { method, args: adapter._serializeArgs(args) },\n ]);\n };\n }\n\n chain.find = async (): Promise<T[]> => {\n const path = adapter.resolvePath(modelClass);\n const result = await adapter.transport.get(path, { __query: steps });\n const items = result?.[modelClass.type + \"s\"] ?? result?.items ?? [];\n const models = items.map((row: any) => new modelClass(adapter, row));\n // Attach query subscription hash if backend provided one\n if (result?.__queryHash) {\n Object.defineProperty(models, \"__queryHash\", {\n value: result.__queryHash,\n enumerable: false,\n });\n }\n return models;\n };\n\n chain.first = async (): Promise<T | null> => {\n const all = await chain.find();\n return all[0] ?? null;\n };\n\n chain.count = async (): Promise<number> => {\n const path = adapter.resolvePath(modelClass);\n const result = await adapter.transport.get(path, {\n __query: steps,\n __count: true,\n });\n return result?.total ?? 0;\n };\n\n chain.__steps = steps;\n chain.__modelType = modelClass.type;\n chain.__modelClass = modelClass;\n chain.__adapter = adapter;\n\n return chain as QueryChain<T>;\n }\n\n private _serializeArgs(args: any[]): any[] {\n return args.map((arg) => {\n if (typeof arg === \"function\") {\n // Capture nested builder calls: .where((b) => b.where(...).orWhere(...))\n const nestedSteps: QueryStep[] = [];\n const recorder: any = new Proxy(\n {},\n {\n get: (_target, method: string) => {\n return (...innerArgs: any[]) => {\n nestedSteps.push({\n method,\n args: this._serializeArgs(innerArgs),\n });\n return recorder;\n };\n },\n },\n );\n arg(recorder);\n return { __nested: nestedSteps };\n }\n return arg;\n });\n }\n\n // ── patch ────────────────────────────────────────────────────────────\n\n async patch(model: any, ops: PatchOp[]): Promise<void> {\n const ModelClass = model.constructor as ModelConstructor;\n const path = this.resolvePath(ModelClass);\n await this.transport.patch(`${path}/${model.id}`, { ops });\n }\n\n // ── helpers ──────────────────────────────────────────────────────────\n\n private resolvePath(modelClass: ModelConstructor): string {\n if (!modelClass.path && !modelClass.type) {\n throw new Error(`Model has no path or type`);\n }\n const fullPath = modelClass.path ?? `/v1/${modelClass.type}s`;\n return this.p(fullPath);\n }\n}\n"]} |
| var __defProp = Object.defineProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| // src/adapters/types.ts | ||
| var CHAINABLE_METHODS = [ | ||
| "select", | ||
| "where", | ||
| "andWhere", | ||
| "orWhere", | ||
| "whereIn", | ||
| "whereNot", | ||
| "whereNotIn", | ||
| "whereNull", | ||
| "whereNotNull", | ||
| "whereBetween", | ||
| "whereRaw", | ||
| "orWhereRaw", | ||
| "orWhereIn", | ||
| "orWhereNull", | ||
| "whereExists", | ||
| "search", | ||
| "orderBy", | ||
| "orderByRaw", | ||
| "groupBy", | ||
| "groupByRaw", | ||
| "having", | ||
| "havingRaw", | ||
| "limit", | ||
| "offset", | ||
| "distinct", | ||
| "distinctOn", | ||
| "join", | ||
| "innerJoin", | ||
| "leftJoin", | ||
| "rightJoin", | ||
| "clearOrder", | ||
| "clearSelect", | ||
| "from", | ||
| "sum", | ||
| "avg", | ||
| "min", | ||
| "max", | ||
| "increment", | ||
| "decrement" | ||
| ]; | ||
| export { CHAINABLE_METHODS, __export }; | ||
| //# sourceMappingURL=chunk-F5LRP7SS.js.map | ||
| //# sourceMappingURL=chunk-F5LRP7SS.js.map |
| {"version":3,"sources":["../src/adapters/types.ts"],"names":[],"mappings":";;;;;;;AAoRO,IAAM,iBAAA,GAAoB;AAAA,EAC/B,QAAA;AAAA,EACA,OAAA;AAAA,EACA,UAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,UAAA;AAAA,EACA,YAAA;AAAA,EACA,WAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,UAAA;AAAA,EACA,YAAA;AAAA,EACA,WAAA;AAAA,EACA,aAAA;AAAA,EACA,aAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,YAAA;AAAA,EACA,SAAA;AAAA,EACA,YAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA,YAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA;AAAA,EACA,UAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,aAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF","file":"chunk-F5LRP7SS.js","sourcesContent":["/**\n * @parcae/model — Adapter Types\n *\n * Defines the adapter interface that decouples Model from persistence.\n * Frontend: Valtio + Socket.IO transport\n * Backend: Knex + PostgreSQL\n */\n\nimport type { Operation as PatchOp } from \"fast-json-patch\";\n\n// ─── Column Types ────────────────────────────────────────────────────────────\n\n/**\n * Primitive column types that map to Postgres column types.\n * These are resolved from TypeScript types via RTTIST at startup.\n */\nexport type PrimitiveColumnType =\n | \"string\" // VARCHAR(2048)\n | \"text\" // TEXT\n | \"integer\" // INTEGER\n | \"number\" // DOUBLE PRECISION\n | \"boolean\" // BOOLEAN\n | \"datetime\" // TIMESTAMP\n | \"json\"; // JSONB\n\n/**\n * A column can be a primitive type or a reference to another Model class.\n */\nexport type ColumnType =\n | PrimitiveColumnType\n | { kind: \"ref\"; target: ModelConstructor };\n\n/**\n * Schema definition for a model, mapping property names to column types.\n * Generated by the RTTIST schema resolver at startup.\n */\nexport type SchemaDefinition = Record<string, ColumnType>;\n\n/**\n * Index definition — a single column name or a composite index.\n */\nexport type IndexDefinition = string | string[];\n\n// ─── Model Constructor ───────────────────────────────────────────────────────\n\n/**\n * The static side of a Model subclass.\n */\nexport interface ModelConstructor<T = any> {\n type: string;\n path?: string;\n scope?: ModelScope;\n indexes?: IndexDefinition[];\n managed?: boolean;\n /**\n * Fields to index for full-text and fuzzy search.\n * When present, `ensureTable()` creates:\n * - A generated `_search` tsvector column + GIN index\n * - Per-field trigram GIN indexes (pg_trgm)\n * - On AlloyDB: `_embedding` vector(768) column + ScaNN index\n *\n * Fields are weighted by order: first = 'A' (highest), second = 'B', etc.\n *\n * @example\n * ```typescript\n * class Project extends Model {\n * static searchFields = [\"title\", \"description\"];\n * }\n * // Then query with: Project.search(\"ghost town\").find()\n * ```\n */\n searchFields?: string[];\n __schema?: SchemaDefinition;\n new (adapter: ModelAdapter, data?: Record<string, any>): T;\n}\n\n// ─── Scope System ────────────────────────────────────────────────────────────\n\n/**\n * Context passed to scope functions. Contains the authenticated user\n * and request parameters.\n */\nexport interface ScopeContext {\n user?: { id: string; [key: string]: any } | null;\n params?: Record<string, string>;\n data?: Record<string, any>;\n}\n\n/**\n * The result of a scope function. Either:\n * - A query builder modifier function (for read/update/delete/patch)\n * - A data object to merge (for create — e.g. { user: ctx.user.id })\n * - null to deny access\n */\nexport type ScopeResult = ((qb: any) => any) | Record<string, any> | null;\n\n/**\n * A scope function receives the request context and returns a ScopeResult.\n */\nexport type ScopeFunction = (ctx: ScopeContext) => ScopeResult;\n\n/**\n * Per-operation scope definitions.\n */\nexport interface ModelScope {\n read?: ScopeFunction;\n create?: ScopeFunction;\n update?: ScopeFunction;\n delete?: ScopeFunction;\n patch?: ScopeFunction;\n}\n\n// ─── Change Tracking ─────────────────────────────────────────────────────────\n\n/**\n * A set of changes to flush to the adapter.\n */\nexport interface ChangeSet {\n /** Property names that were set via direct assignment */\n updates: string[];\n /** RFC 6902 JSON Patch operations */\n ops: PatchOp[];\n /** Whether this is a new model being created */\n creating: boolean;\n}\n\n// ─── Query Chain ─────────────────────────────────────────────────────────────\n\n/**\n * A serializable query step. On the frontend, query chains record steps\n * as these objects and send them to the backend for execution.\n */\nexport interface QueryStep {\n method: string;\n args: any[];\n}\n\n/**\n * Chainable query builder. The generic T flows through to terminal methods.\n *\n * On the backend, each method directly mutates a Knex query.\n * On the frontend, each method records a QueryStep for serialization.\n */\nexport interface QueryChain<T> {\n // Chainable — filtering\n where(...args: any[]): QueryChain<T>;\n andWhere(...args: any[]): QueryChain<T>;\n orWhere(...args: any[]): QueryChain<T>;\n whereIn(column: string, values: any[]): QueryChain<T>;\n whereNot(...args: any[]): QueryChain<T>;\n whereNotIn(column: string, values: any[]): QueryChain<T>;\n whereNull(column: string): QueryChain<T>;\n whereNotNull(column: string): QueryChain<T>;\n whereBetween(column: string, range: [any, any]): QueryChain<T>;\n whereRaw(query: string, ...bindings: any[]): QueryChain<T>;\n orWhereRaw(query: string, ...bindings: any[]): QueryChain<T>;\n orWhereIn(column: string, values: any[]): QueryChain<T>;\n orWhereNull(column: string): QueryChain<T>;\n whereExists(callback: any): QueryChain<T>;\n\n // Chainable — search\n /**\n * Full-text + fuzzy search across fields declared in `static search`.\n * On the backend, generates hybrid SQL (tsvector + trigram + optional vector).\n * On the frontend, records a QueryStep for serialization.\n */\n search(term: string): QueryChain<T>;\n\n // Chainable — ordering & pagination\n orderBy(column: string, direction?: \"asc\" | \"desc\"): QueryChain<T>;\n orderByRaw(query: string, ...bindings: any[]): QueryChain<T>;\n limit(count: number): QueryChain<T>;\n offset(count: number): QueryChain<T>;\n\n // Chainable — selection & grouping\n select(...columns: string[]): QueryChain<T>;\n distinct(...columns: string[]): QueryChain<T>;\n distinctOn(...columns: string[]): QueryChain<T>;\n groupBy(...columns: string[]): QueryChain<T>;\n groupByRaw(query: string, ...bindings: any[]): QueryChain<T>;\n having(...args: any[]): QueryChain<T>;\n havingRaw(query: string, ...bindings: any[]): QueryChain<T>;\n\n // Chainable — joins\n join(...args: any[]): QueryChain<T>;\n innerJoin(...args: any[]): QueryChain<T>;\n leftJoin(...args: any[]): QueryChain<T>;\n rightJoin(...args: any[]): QueryChain<T>;\n\n // Chainable — modifiers\n clearOrder(): QueryChain<T>;\n clearSelect(): QueryChain<T>;\n from(table: string): QueryChain<T>;\n\n // Chainable — aggregates (chainable versions)\n sum(column: string): QueryChain<T>;\n avg(column: string): QueryChain<T>;\n min(column: string): QueryChain<T>;\n max(column: string): QueryChain<T>;\n increment(column: string, amount?: number): QueryChain<T>;\n decrement(column: string, amount?: number): QueryChain<T>;\n\n // Convenience\n basic(\n limit?: number,\n sort?: string,\n direction?: \"asc\" | \"desc\",\n page?: number,\n ): QueryChain<T>;\n\n // Terminal — execute the query\n find(): Promise<T[]>;\n first(): Promise<T | null>;\n count(): Promise<number>;\n\n // Introspection — access underlying query builder\n /** @internal Return the underlying query builder (e.g. Knex QueryBuilder). */\n exec(): any;\n /** @internal Create an independent copy of this chain. */\n clone(): QueryChain<T>;\n\n // Internal — used by hooks and subscription manager\n /** @internal */ __steps: QueryStep[];\n /** @internal */ __modelType: string;\n /** @internal */ __modelClass: ModelConstructor<T>;\n /** @internal */ __adapter: ModelAdapter;\n /** @internal */ __debounceMs?: number;\n}\n\n// ─── Model Adapter ───────────────────────────────────────────────────────────\n\n/**\n * The adapter interface. Decouples Model from persistence.\n *\n * - FrontendAdapter: Valtio proxy + Socket.IO transport\n * - BackendAdapter: Knex + PostgreSQL + hooks + pub/sub\n */\nexport interface ModelAdapter {\n /** Create the reactive data store. Frontend: Valtio proxy. Backend: plain object. */\n createStore(data: Record<string, any>): Record<string, any>;\n\n /** Persist changes (create or update). */\n save(model: any, changes: ChangeSet): Promise<void>;\n\n /** Delete a model. */\n remove(model: any): Promise<void>;\n\n /** Fetch a single model by ID. */\n findById<T>(modelClass: ModelConstructor<T>, id: string): Promise<T | null>;\n\n /** Start a query chain. */\n query<T>(modelClass: ModelConstructor<T>): QueryChain<T>;\n\n /**\n * Build a scoped query from client-sent QueryStep[] (backend only).\n * Safe replay of __query steps with scope applied first.\n */\n queryFromClient?<T>(\n modelClass: ModelConstructor<T>,\n scope: Record<string, any>,\n rawSteps: QueryStep[] | string | undefined,\n ): QueryChain<T>;\n\n /** Apply atomic RFC 6902 JSON Patch operations. */\n patch(model: any, ops: PatchOp[]): Promise<void>;\n}\n\nexport type { PatchOp };\n\n// ─── Chainable Methods ───────────────────────────────────────────────────────\n\n/**\n * The set of query chain methods that are chainable (return a new QueryChain).\n * Shared across lazyQuery, FrontendAdapter, and BackendAdapter to avoid\n * the same 37-element array being duplicated three times.\n */\nexport const CHAINABLE_METHODS = [\n \"select\",\n \"where\",\n \"andWhere\",\n \"orWhere\",\n \"whereIn\",\n \"whereNot\",\n \"whereNotIn\",\n \"whereNull\",\n \"whereNotNull\",\n \"whereBetween\",\n \"whereRaw\",\n \"orWhereRaw\",\n \"orWhereIn\",\n \"orWhereNull\",\n \"whereExists\",\n \"search\",\n \"orderBy\",\n \"orderByRaw\",\n \"groupBy\",\n \"groupByRaw\",\n \"having\",\n \"havingRaw\",\n \"limit\",\n \"offset\",\n \"distinct\",\n \"distinctOn\",\n \"join\",\n \"innerJoin\",\n \"leftJoin\",\n \"rightJoin\",\n \"clearOrder\",\n \"clearSelect\",\n \"from\",\n \"sum\",\n \"avg\",\n \"min\",\n \"max\",\n \"increment\",\n \"decrement\",\n] as const;\n"]} |
@@ -14,2 +14,7 @@ import { ModelAdapter, ChangeSet, ModelConstructor, QueryChain } from './types.js'; | ||
| /** Per-request options that callers can pass to any RPC method. */ | ||
| interface RequestOptions { | ||
| /** Override the default RPC timeout (in milliseconds). */ | ||
| timeout?: number; | ||
| } | ||
| /** | ||
@@ -24,7 +29,7 @@ * Abstract transport layer. Decouples the Model system from the wire protocol. | ||
| interface Transport { | ||
| get(path: string, data?: any): Promise<any>; | ||
| post(path: string, data?: any): Promise<any>; | ||
| put(path: string, data?: any): Promise<any>; | ||
| patch(path: string, data?: any): Promise<any>; | ||
| delete(path: string, data?: any): Promise<any>; | ||
| get(path: string, data?: any, options?: RequestOptions): Promise<any>; | ||
| post(path: string, data?: any, options?: RequestOptions): Promise<any>; | ||
| put(path: string, data?: any, options?: RequestOptions): Promise<any>; | ||
| patch(path: string, data?: any, options?: RequestOptions): Promise<any>; | ||
| delete(path: string, data?: any, options?: RequestOptions): Promise<any>; | ||
| /** | ||
@@ -82,2 +87,2 @@ * Subscribe to a named event stream. | ||
| export { FrontendAdapter, type Transport, FrontendAdapter as default }; | ||
| export { FrontendAdapter, type RequestOptions, type Transport }; |
@@ -1,3 +0,4 @@ | ||
| export { FrontendAdapter, client_default as default } from '../chunk-UE7DW2FG.js'; | ||
| export { FrontendAdapter } from '../chunk-7RSAGEBY.js'; | ||
| import '../chunk-F5LRP7SS.js'; | ||
| //# sourceMappingURL=client.js.map | ||
| //# sourceMappingURL=client.js.map |
@@ -42,2 +42,20 @@ import { Operation } from 'fast-json-patch'; | ||
| managed?: boolean; | ||
| /** | ||
| * Fields to index for full-text and fuzzy search. | ||
| * When present, `ensureTable()` creates: | ||
| * - A generated `_search` tsvector column + GIN index | ||
| * - Per-field trigram GIN indexes (pg_trgm) | ||
| * - On AlloyDB: `_embedding` vector(768) column + ScaNN index | ||
| * | ||
| * Fields are weighted by order: first = 'A' (highest), second = 'B', etc. | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * class Project extends Model { | ||
| * static searchFields = ["title", "description"]; | ||
| * } | ||
| * // Then query with: Project.search("ghost town").find() | ||
| * ``` | ||
| */ | ||
| searchFields?: string[]; | ||
| __schema?: SchemaDefinition; | ||
@@ -119,2 +137,8 @@ new (adapter: ModelAdapter, data?: Record<string, any>): T; | ||
| whereExists(callback: any): QueryChain<T>; | ||
| /** | ||
| * Full-text + fuzzy search across fields declared in `static search`. | ||
| * On the backend, generates hybrid SQL (tsvector + trigram + optional vector). | ||
| * On the frontend, records a QueryStep for serialization. | ||
| */ | ||
| search(term: string): QueryChain<T>; | ||
| orderBy(column: string, direction?: "asc" | "desc"): QueryChain<T>; | ||
@@ -146,9 +170,8 @@ orderByRaw(query: string, ...bindings: any[]): QueryChain<T>; | ||
| find(): Promise<T[]>; | ||
| /** Like find(), but also subscribes to realtime updates via the QSM. */ | ||
| findAndSubscribe(): Promise<{ | ||
| items: T[]; | ||
| hash: string | null; | ||
| }>; | ||
| first(): Promise<T | null>; | ||
| count(): Promise<number>; | ||
| /** @internal Return the underlying query builder (e.g. Knex QueryBuilder). */ | ||
| exec(): any; | ||
| /** @internal Create an independent copy of this chain. */ | ||
| clone(): QueryChain<T>; | ||
| /** @internal */ __steps: QueryStep[]; | ||
@@ -177,2 +200,7 @@ /** @internal */ __modelType: string; | ||
| query<T>(modelClass: ModelConstructor<T>): QueryChain<T>; | ||
| /** | ||
| * Build a scoped query from client-sent QueryStep[] (backend only). | ||
| * Safe replay of __query steps with scope applied first. | ||
| */ | ||
| queryFromClient?<T>(modelClass: ModelConstructor<T>, scope: Record<string, any>, rawSteps: QueryStep[] | string | undefined): QueryChain<T>; | ||
| /** Apply atomic RFC 6902 JSON Patch operations. */ | ||
@@ -182,2 +210,9 @@ patch(model: any, ops: Operation[]): Promise<void>; | ||
| export type { ChangeSet, ColumnType, IndexDefinition, ModelAdapter, ModelConstructor, ModelScope, PrimitiveColumnType, QueryChain, QueryStep, SchemaDefinition, ScopeContext, ScopeFunction, ScopeResult }; | ||
| /** | ||
| * The set of query chain methods that are chainable (return a new QueryChain). | ||
| * Shared across lazyQuery, FrontendAdapter, and BackendAdapter to avoid | ||
| * the same 37-element array being duplicated three times. | ||
| */ | ||
| declare const CHAINABLE_METHODS: readonly ["select", "where", "andWhere", "orWhere", "whereIn", "whereNot", "whereNotIn", "whereNull", "whereNotNull", "whereBetween", "whereRaw", "orWhereRaw", "orWhereIn", "orWhereNull", "whereExists", "search", "orderBy", "orderByRaw", "groupBy", "groupByRaw", "having", "havingRaw", "limit", "offset", "distinct", "distinctOn", "join", "innerJoin", "leftJoin", "rightJoin", "clearOrder", "clearSelect", "from", "sum", "avg", "min", "max", "increment", "decrement"]; | ||
| export { CHAINABLE_METHODS, type ChangeSet, type ColumnType, type IndexDefinition, type ModelAdapter, type ModelConstructor, type ModelScope, type PrimitiveColumnType, type QueryChain, type QueryStep, type SchemaDefinition, type ScopeContext, type ScopeFunction, type ScopeResult }; |
@@ -1,3 +0,3 @@ | ||
| export { CHAINABLE_METHODS } from '../chunk-F5LRP7SS.js'; | ||
| //# sourceMappingURL=types.js.map | ||
| //# sourceMappingURL=types.js.map |
+32
-5
| import { EventEmitter } from 'eventemitter3'; | ||
| import { ModelAdapter, SchemaDefinition, ModelConstructor, QueryChain } from './adapters/types.js'; | ||
| export { ChangeSet, ColumnType, IndexDefinition, ModelScope, PrimitiveColumnType, QueryStep, ScopeContext, ScopeFunction, ScopeResult } from './adapters/types.js'; | ||
| export { CHAINABLE_METHODS, ChangeSet, ColumnType, QueryStep, ScopeContext } from './adapters/types.js'; | ||
| import { Operation } from 'fast-json-patch'; | ||
| export { Operation as PatchOp } from 'fast-json-patch'; | ||
| export { FrontendAdapter, Transport } from './adapters/client.js'; | ||
| export { FrontendAdapter, RequestOptions, Transport } from './adapters/client.js'; | ||
@@ -28,2 +28,12 @@ /** | ||
| declare const SYM_INIT_DATA: unique symbol; | ||
| /** | ||
| * Symbol used to atomically merge server-authoritative data onto a Model | ||
| * instance, bypassing the Proxy set trap so that SYM_UPDATES (local | ||
| * dirty-tracking) is not polluted. Skips any keys that have pending | ||
| * local changes (SYM_UPDATES or SYM_PATCHING). | ||
| * | ||
| * Accessed via the Proxy get trap, which returns a closure over the raw | ||
| * target so writes bypass the Proxy entirely. | ||
| */ | ||
| declare const SYM_SERVER_MERGE: unique symbol; | ||
| declare class Model extends EventEmitter { | ||
@@ -40,2 +50,3 @@ static type: string; | ||
| static indexes?: (string | string[])[]; | ||
| static searchFields?: string[]; | ||
| static managed: boolean; | ||
@@ -65,3 +76,3 @@ /** @internal */ | ||
| static count<T extends Model>(this: ModelConstructor<T>): Promise<number>; | ||
| static basic<T extends Model>(this: ModelConstructor<T>, limit?: number, sort?: string, direction?: "asc" | "desc", page?: number): QueryChain<T>; | ||
| static search<T extends Model>(this: ModelConstructor<T>, term: string): QueryChain<T>; | ||
| constructor(adapter: ModelAdapter, data?: Record<string, any>); | ||
@@ -95,3 +106,2 @@ /** | ||
| refresh(): Promise<void>; | ||
| load(): Promise<void>; | ||
| sanitize(_user?: { | ||
@@ -102,2 +112,3 @@ id: string; | ||
| private static __refCache; | ||
| private static REF_CACHE_TTL; | ||
| private _createRefProxy; | ||
@@ -118,2 +129,18 @@ } | ||
| export { Model, ModelAdapter, ModelConstructor, QueryChain, SchemaDefinition, generateId }; | ||
| /** | ||
| * @parcae/model | ||
| * | ||
| * The Parcae Model system — typed ORM base class with adapter pattern. | ||
| * Class properties ARE the schema. Direct property access. Full type safety. | ||
| */ | ||
| /** | ||
| * Branded type for unlimited TEXT columns (vs string which is VARCHAR 2048). | ||
| * Use `content: Text = ""` on a Model property to get a TEXT column in Postgres. | ||
| * At runtime it's just a string — the brand is erased. | ||
| */ | ||
| type Text = string & { | ||
| readonly __brand: "Text"; | ||
| }; | ||
| export { Model, ModelAdapter, ModelConstructor, QueryChain, SYM_SERVER_MERGE, SchemaDefinition, type Text, generateId }; |
+696
-64
@@ -1,6 +0,644 @@ | ||
| export { FrontendAdapter } from './chunk-UE7DW2FG.js'; | ||
| export { FrontendAdapter } from './chunk-7RSAGEBY.js'; | ||
| import { __export, CHAINABLE_METHODS } from './chunk-F5LRP7SS.js'; | ||
| export { CHAINABLE_METHODS } from './chunk-F5LRP7SS.js'; | ||
| import { EventEmitter } from 'eventemitter3'; | ||
| import ShortId from 'short-unique-id'; | ||
| import { applyPatch } from 'fast-json-patch'; | ||
| // ../../node_modules/.pnpm/fast-json-patch@3.1.1/node_modules/fast-json-patch/module/core.mjs | ||
| var core_exports = {}; | ||
| __export(core_exports, { | ||
| JsonPatchError: () => JsonPatchError, | ||
| _areEquals: () => _areEquals, | ||
| applyOperation: () => applyOperation, | ||
| applyPatch: () => applyPatch, | ||
| applyReducer: () => applyReducer, | ||
| deepClone: () => deepClone, | ||
| getValueByPointer: () => getValueByPointer, | ||
| validate: () => validate, | ||
| validator: () => validator | ||
| }); | ||
| // ../../node_modules/.pnpm/fast-json-patch@3.1.1/node_modules/fast-json-patch/module/helpers.mjs | ||
| var __extends = /* @__PURE__ */ (function() { | ||
| var extendStatics = function(d, b) { | ||
| extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(d2, b2) { | ||
| d2.__proto__ = b2; | ||
| } || function(d2, b2) { | ||
| for (var p in b2) if (b2.hasOwnProperty(p)) d2[p] = b2[p]; | ||
| }; | ||
| return extendStatics(d, b); | ||
| }; | ||
| return function(d, b) { | ||
| extendStatics(d, b); | ||
| function __() { | ||
| this.constructor = d; | ||
| } | ||
| d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
| }; | ||
| })(); | ||
| var _hasOwnProperty = Object.prototype.hasOwnProperty; | ||
| function hasOwnProperty(obj, key) { | ||
| return _hasOwnProperty.call(obj, key); | ||
| } | ||
| function _objectKeys(obj) { | ||
| if (Array.isArray(obj)) { | ||
| var keys_1 = new Array(obj.length); | ||
| for (var k = 0; k < keys_1.length; k++) { | ||
| keys_1[k] = "" + k; | ||
| } | ||
| return keys_1; | ||
| } | ||
| if (Object.keys) { | ||
| return Object.keys(obj); | ||
| } | ||
| var keys = []; | ||
| for (var i in obj) { | ||
| if (hasOwnProperty(obj, i)) { | ||
| keys.push(i); | ||
| } | ||
| } | ||
| return keys; | ||
| } | ||
| function _deepClone(obj) { | ||
| switch (typeof obj) { | ||
| case "object": | ||
| return JSON.parse(JSON.stringify(obj)); | ||
| //Faster than ES5 clone - http://jsperf.com/deep-cloning-of-objects/5 | ||
| case "undefined": | ||
| return null; | ||
| //this is how JSON.stringify behaves for array items | ||
| default: | ||
| return obj; | ||
| } | ||
| } | ||
| function isInteger(str) { | ||
| var i = 0; | ||
| var len = str.length; | ||
| var charCode; | ||
| while (i < len) { | ||
| charCode = str.charCodeAt(i); | ||
| if (charCode >= 48 && charCode <= 57) { | ||
| i++; | ||
| continue; | ||
| } | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| function escapePathComponent(path) { | ||
| if (path.indexOf("/") === -1 && path.indexOf("~") === -1) | ||
| return path; | ||
| return path.replace(/~/g, "~0").replace(/\//g, "~1"); | ||
| } | ||
| function unescapePathComponent(path) { | ||
| return path.replace(/~1/g, "/").replace(/~0/g, "~"); | ||
| } | ||
| function hasUndefined(obj) { | ||
| if (obj === void 0) { | ||
| return true; | ||
| } | ||
| if (obj) { | ||
| if (Array.isArray(obj)) { | ||
| for (var i_1 = 0, len = obj.length; i_1 < len; i_1++) { | ||
| if (hasUndefined(obj[i_1])) { | ||
| return true; | ||
| } | ||
| } | ||
| } else if (typeof obj === "object") { | ||
| var objKeys = _objectKeys(obj); | ||
| var objKeysLength = objKeys.length; | ||
| for (var i = 0; i < objKeysLength; i++) { | ||
| if (hasUndefined(obj[objKeys[i]])) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| function patchErrorMessageFormatter(message, args) { | ||
| var messageParts = [message]; | ||
| for (var key in args) { | ||
| var value = typeof args[key] === "object" ? JSON.stringify(args[key], null, 2) : args[key]; | ||
| if (typeof value !== "undefined") { | ||
| messageParts.push(key + ": " + value); | ||
| } | ||
| } | ||
| return messageParts.join("\n"); | ||
| } | ||
| var PatchError = ( | ||
| /** @class */ | ||
| (function(_super) { | ||
| __extends(PatchError2, _super); | ||
| function PatchError2(message, name, index, operation, tree) { | ||
| var _newTarget = this.constructor; | ||
| var _this = _super.call(this, patchErrorMessageFormatter(message, { name, index, operation, tree })) || this; | ||
| _this.name = name; | ||
| _this.index = index; | ||
| _this.operation = operation; | ||
| _this.tree = tree; | ||
| Object.setPrototypeOf(_this, _newTarget.prototype); | ||
| _this.message = patchErrorMessageFormatter(message, { name, index, operation, tree }); | ||
| return _this; | ||
| } | ||
| return PatchError2; | ||
| })(Error) | ||
| ); | ||
| // ../../node_modules/.pnpm/fast-json-patch@3.1.1/node_modules/fast-json-patch/module/core.mjs | ||
| var JsonPatchError = PatchError; | ||
| var deepClone = _deepClone; | ||
| var objOps = { | ||
| add: function(obj, key, document) { | ||
| obj[key] = this.value; | ||
| return { newDocument: document }; | ||
| }, | ||
| remove: function(obj, key, document) { | ||
| var removed = obj[key]; | ||
| delete obj[key]; | ||
| return { newDocument: document, removed }; | ||
| }, | ||
| replace: function(obj, key, document) { | ||
| var removed = obj[key]; | ||
| obj[key] = this.value; | ||
| return { newDocument: document, removed }; | ||
| }, | ||
| move: function(obj, key, document) { | ||
| var removed = getValueByPointer(document, this.path); | ||
| if (removed) { | ||
| removed = _deepClone(removed); | ||
| } | ||
| var originalValue = applyOperation(document, { op: "remove", path: this.from }).removed; | ||
| applyOperation(document, { op: "add", path: this.path, value: originalValue }); | ||
| return { newDocument: document, removed }; | ||
| }, | ||
| copy: function(obj, key, document) { | ||
| var valueToCopy = getValueByPointer(document, this.from); | ||
| applyOperation(document, { op: "add", path: this.path, value: _deepClone(valueToCopy) }); | ||
| return { newDocument: document }; | ||
| }, | ||
| test: function(obj, key, document) { | ||
| return { newDocument: document, test: _areEquals(obj[key], this.value) }; | ||
| }, | ||
| _get: function(obj, key, document) { | ||
| this.value = obj[key]; | ||
| return { newDocument: document }; | ||
| } | ||
| }; | ||
| var arrOps = { | ||
| add: function(arr, i, document) { | ||
| if (isInteger(i)) { | ||
| arr.splice(i, 0, this.value); | ||
| } else { | ||
| arr[i] = this.value; | ||
| } | ||
| return { newDocument: document, index: i }; | ||
| }, | ||
| remove: function(arr, i, document) { | ||
| var removedList = arr.splice(i, 1); | ||
| return { newDocument: document, removed: removedList[0] }; | ||
| }, | ||
| replace: function(arr, i, document) { | ||
| var removed = arr[i]; | ||
| arr[i] = this.value; | ||
| return { newDocument: document, removed }; | ||
| }, | ||
| move: objOps.move, | ||
| copy: objOps.copy, | ||
| test: objOps.test, | ||
| _get: objOps._get | ||
| }; | ||
| function getValueByPointer(document, pointer) { | ||
| if (pointer == "") { | ||
| return document; | ||
| } | ||
| var getOriginalDestination = { op: "_get", path: pointer }; | ||
| applyOperation(document, getOriginalDestination); | ||
| return getOriginalDestination.value; | ||
| } | ||
| function applyOperation(document, operation, validateOperation, mutateDocument, banPrototypeModifications, index) { | ||
| if (validateOperation === void 0) { | ||
| validateOperation = false; | ||
| } | ||
| if (mutateDocument === void 0) { | ||
| mutateDocument = true; | ||
| } | ||
| if (banPrototypeModifications === void 0) { | ||
| banPrototypeModifications = true; | ||
| } | ||
| if (index === void 0) { | ||
| index = 0; | ||
| } | ||
| if (validateOperation) { | ||
| if (typeof validateOperation == "function") { | ||
| validateOperation(operation, 0, document, operation.path); | ||
| } else { | ||
| validator(operation, 0); | ||
| } | ||
| } | ||
| if (operation.path === "") { | ||
| var returnValue = { newDocument: document }; | ||
| if (operation.op === "add") { | ||
| returnValue.newDocument = operation.value; | ||
| return returnValue; | ||
| } else if (operation.op === "replace") { | ||
| returnValue.newDocument = operation.value; | ||
| returnValue.removed = document; | ||
| return returnValue; | ||
| } else if (operation.op === "move" || operation.op === "copy") { | ||
| returnValue.newDocument = getValueByPointer(document, operation.from); | ||
| if (operation.op === "move") { | ||
| returnValue.removed = document; | ||
| } | ||
| return returnValue; | ||
| } else if (operation.op === "test") { | ||
| returnValue.test = _areEquals(document, operation.value); | ||
| if (returnValue.test === false) { | ||
| throw new JsonPatchError("Test operation failed", "TEST_OPERATION_FAILED", index, operation, document); | ||
| } | ||
| returnValue.newDocument = document; | ||
| return returnValue; | ||
| } else if (operation.op === "remove") { | ||
| returnValue.removed = document; | ||
| returnValue.newDocument = null; | ||
| return returnValue; | ||
| } else if (operation.op === "_get") { | ||
| operation.value = document; | ||
| return returnValue; | ||
| } else { | ||
| if (validateOperation) { | ||
| throw new JsonPatchError("Operation `op` property is not one of operations defined in RFC-6902", "OPERATION_OP_INVALID", index, operation, document); | ||
| } else { | ||
| return returnValue; | ||
| } | ||
| } | ||
| } else { | ||
| if (!mutateDocument) { | ||
| document = _deepClone(document); | ||
| } | ||
| var path = operation.path || ""; | ||
| var keys = path.split("/"); | ||
| var obj = document; | ||
| var t = 1; | ||
| var len = keys.length; | ||
| var existingPathFragment = void 0; | ||
| var key = void 0; | ||
| var validateFunction = void 0; | ||
| if (typeof validateOperation == "function") { | ||
| validateFunction = validateOperation; | ||
| } else { | ||
| validateFunction = validator; | ||
| } | ||
| while (true) { | ||
| key = keys[t]; | ||
| if (key && key.indexOf("~") != -1) { | ||
| key = unescapePathComponent(key); | ||
| } | ||
| if (banPrototypeModifications && (key == "__proto__" || key == "prototype" && t > 0 && keys[t - 1] == "constructor")) { | ||
| throw new TypeError("JSON-Patch: modifying `__proto__` or `constructor/prototype` prop is banned for security reasons, if this was on purpose, please set `banPrototypeModifications` flag false and pass it to this function. More info in fast-json-patch README"); | ||
| } | ||
| if (validateOperation) { | ||
| if (existingPathFragment === void 0) { | ||
| if (obj[key] === void 0) { | ||
| existingPathFragment = keys.slice(0, t).join("/"); | ||
| } else if (t == len - 1) { | ||
| existingPathFragment = operation.path; | ||
| } | ||
| if (existingPathFragment !== void 0) { | ||
| validateFunction(operation, 0, document, existingPathFragment); | ||
| } | ||
| } | ||
| } | ||
| t++; | ||
| if (Array.isArray(obj)) { | ||
| if (key === "-") { | ||
| key = obj.length; | ||
| } else { | ||
| if (validateOperation && !isInteger(key)) { | ||
| throw new JsonPatchError("Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index", "OPERATION_PATH_ILLEGAL_ARRAY_INDEX", index, operation, document); | ||
| } else if (isInteger(key)) { | ||
| key = ~~key; | ||
| } | ||
| } | ||
| if (t >= len) { | ||
| if (validateOperation && operation.op === "add" && key > obj.length) { | ||
| throw new JsonPatchError("The specified index MUST NOT be greater than the number of elements in the array", "OPERATION_VALUE_OUT_OF_BOUNDS", index, operation, document); | ||
| } | ||
| var returnValue = arrOps[operation.op].call(operation, obj, key, document); | ||
| if (returnValue.test === false) { | ||
| throw new JsonPatchError("Test operation failed", "TEST_OPERATION_FAILED", index, operation, document); | ||
| } | ||
| return returnValue; | ||
| } | ||
| } else { | ||
| if (t >= len) { | ||
| var returnValue = objOps[operation.op].call(operation, obj, key, document); | ||
| if (returnValue.test === false) { | ||
| throw new JsonPatchError("Test operation failed", "TEST_OPERATION_FAILED", index, operation, document); | ||
| } | ||
| return returnValue; | ||
| } | ||
| } | ||
| obj = obj[key]; | ||
| if (validateOperation && t < len && (!obj || typeof obj !== "object")) { | ||
| throw new JsonPatchError("Cannot perform operation at the desired path", "OPERATION_PATH_UNRESOLVABLE", index, operation, document); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| function applyPatch(document, patch, validateOperation, mutateDocument, banPrototypeModifications) { | ||
| if (mutateDocument === void 0) { | ||
| mutateDocument = true; | ||
| } | ||
| if (banPrototypeModifications === void 0) { | ||
| banPrototypeModifications = true; | ||
| } | ||
| if (validateOperation) { | ||
| if (!Array.isArray(patch)) { | ||
| throw new JsonPatchError("Patch sequence must be an array", "SEQUENCE_NOT_AN_ARRAY"); | ||
| } | ||
| } | ||
| if (!mutateDocument) { | ||
| document = _deepClone(document); | ||
| } | ||
| var results = new Array(patch.length); | ||
| for (var i = 0, length_1 = patch.length; i < length_1; i++) { | ||
| results[i] = applyOperation(document, patch[i], validateOperation, true, banPrototypeModifications, i); | ||
| document = results[i].newDocument; | ||
| } | ||
| results.newDocument = document; | ||
| return results; | ||
| } | ||
| function applyReducer(document, operation, index) { | ||
| var operationResult = applyOperation(document, operation); | ||
| if (operationResult.test === false) { | ||
| throw new JsonPatchError("Test operation failed", "TEST_OPERATION_FAILED", index, operation, document); | ||
| } | ||
| return operationResult.newDocument; | ||
| } | ||
| function validator(operation, index, document, existingPathFragment) { | ||
| if (typeof operation !== "object" || operation === null || Array.isArray(operation)) { | ||
| throw new JsonPatchError("Operation is not an object", "OPERATION_NOT_AN_OBJECT", index, operation, document); | ||
| } else if (!objOps[operation.op]) { | ||
| throw new JsonPatchError("Operation `op` property is not one of operations defined in RFC-6902", "OPERATION_OP_INVALID", index, operation, document); | ||
| } else if (typeof operation.path !== "string") { | ||
| throw new JsonPatchError("Operation `path` property is not a string", "OPERATION_PATH_INVALID", index, operation, document); | ||
| } else if (operation.path.indexOf("/") !== 0 && operation.path.length > 0) { | ||
| throw new JsonPatchError('Operation `path` property must start with "/"', "OPERATION_PATH_INVALID", index, operation, document); | ||
| } else if ((operation.op === "move" || operation.op === "copy") && typeof operation.from !== "string") { | ||
| throw new JsonPatchError("Operation `from` property is not present (applicable in `move` and `copy` operations)", "OPERATION_FROM_REQUIRED", index, operation, document); | ||
| } else if ((operation.op === "add" || operation.op === "replace" || operation.op === "test") && operation.value === void 0) { | ||
| throw new JsonPatchError("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)", "OPERATION_VALUE_REQUIRED", index, operation, document); | ||
| } else if ((operation.op === "add" || operation.op === "replace" || operation.op === "test") && hasUndefined(operation.value)) { | ||
| throw new JsonPatchError("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)", "OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED", index, operation, document); | ||
| } else if (document) { | ||
| if (operation.op == "add") { | ||
| var pathLen = operation.path.split("/").length; | ||
| var existingPathLen = existingPathFragment.split("/").length; | ||
| if (pathLen !== existingPathLen + 1 && pathLen !== existingPathLen) { | ||
| throw new JsonPatchError("Cannot perform an `add` operation at the desired path", "OPERATION_PATH_CANNOT_ADD", index, operation, document); | ||
| } | ||
| } else if (operation.op === "replace" || operation.op === "remove" || operation.op === "_get") { | ||
| if (operation.path !== existingPathFragment) { | ||
| throw new JsonPatchError("Cannot perform the operation at a path that does not exist", "OPERATION_PATH_UNRESOLVABLE", index, operation, document); | ||
| } | ||
| } else if (operation.op === "move" || operation.op === "copy") { | ||
| var existingValue = { op: "_get", path: operation.from, value: void 0 }; | ||
| var error = validate([existingValue], document); | ||
| if (error && error.name === "OPERATION_PATH_UNRESOLVABLE") { | ||
| throw new JsonPatchError("Cannot perform the operation from a path that does not exist", "OPERATION_FROM_UNRESOLVABLE", index, operation, document); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| function validate(sequence, document, externalValidator) { | ||
| try { | ||
| if (!Array.isArray(sequence)) { | ||
| throw new JsonPatchError("Patch sequence must be an array", "SEQUENCE_NOT_AN_ARRAY"); | ||
| } | ||
| if (document) { | ||
| applyPatch(_deepClone(document), _deepClone(sequence), externalValidator || true); | ||
| } else { | ||
| externalValidator = externalValidator || validator; | ||
| for (var i = 0; i < sequence.length; i++) { | ||
| externalValidator(sequence[i], i, document, void 0); | ||
| } | ||
| } | ||
| } catch (e) { | ||
| if (e instanceof JsonPatchError) { | ||
| return e; | ||
| } else { | ||
| throw e; | ||
| } | ||
| } | ||
| } | ||
| function _areEquals(a, b) { | ||
| if (a === b) | ||
| return true; | ||
| if (a && b && typeof a == "object" && typeof b == "object") { | ||
| var arrA = Array.isArray(a), arrB = Array.isArray(b), i, length, key; | ||
| if (arrA && arrB) { | ||
| length = a.length; | ||
| if (length != b.length) | ||
| return false; | ||
| for (i = length; i-- !== 0; ) | ||
| if (!_areEquals(a[i], b[i])) | ||
| return false; | ||
| return true; | ||
| } | ||
| if (arrA != arrB) | ||
| return false; | ||
| var keys = Object.keys(a); | ||
| length = keys.length; | ||
| if (length !== Object.keys(b).length) | ||
| return false; | ||
| for (i = length; i-- !== 0; ) | ||
| if (!b.hasOwnProperty(keys[i])) | ||
| return false; | ||
| for (i = length; i-- !== 0; ) { | ||
| key = keys[i]; | ||
| if (!_areEquals(a[key], b[key])) | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| return a !== a && b !== b; | ||
| } | ||
| // ../../node_modules/.pnpm/fast-json-patch@3.1.1/node_modules/fast-json-patch/module/duplex.mjs | ||
| var duplex_exports = {}; | ||
| __export(duplex_exports, { | ||
| compare: () => compare, | ||
| generate: () => generate, | ||
| observe: () => observe, | ||
| unobserve: () => unobserve | ||
| }); | ||
| var beforeDict = /* @__PURE__ */ new WeakMap(); | ||
| var Mirror = ( | ||
| /** @class */ | ||
| /* @__PURE__ */ (function() { | ||
| function Mirror2(obj) { | ||
| this.observers = /* @__PURE__ */ new Map(); | ||
| this.obj = obj; | ||
| } | ||
| return Mirror2; | ||
| })() | ||
| ); | ||
| var ObserverInfo = ( | ||
| /** @class */ | ||
| /* @__PURE__ */ (function() { | ||
| function ObserverInfo2(callback, observer) { | ||
| this.callback = callback; | ||
| this.observer = observer; | ||
| } | ||
| return ObserverInfo2; | ||
| })() | ||
| ); | ||
| function getMirror(obj) { | ||
| return beforeDict.get(obj); | ||
| } | ||
| function getObserverFromMirror(mirror, callback) { | ||
| return mirror.observers.get(callback); | ||
| } | ||
| function removeObserverFromMirror(mirror, observer) { | ||
| mirror.observers.delete(observer.callback); | ||
| } | ||
| function unobserve(root, observer) { | ||
| observer.unobserve(); | ||
| } | ||
| function observe(obj, callback) { | ||
| var patches = []; | ||
| var observer; | ||
| var mirror = getMirror(obj); | ||
| if (!mirror) { | ||
| mirror = new Mirror(obj); | ||
| beforeDict.set(obj, mirror); | ||
| } else { | ||
| var observerInfo = getObserverFromMirror(mirror, callback); | ||
| observer = observerInfo && observerInfo.observer; | ||
| } | ||
| if (observer) { | ||
| return observer; | ||
| } | ||
| observer = {}; | ||
| mirror.value = _deepClone(obj); | ||
| if (callback) { | ||
| observer.callback = callback; | ||
| observer.next = null; | ||
| var dirtyCheck = function() { | ||
| generate(observer); | ||
| }; | ||
| var fastCheck = function() { | ||
| clearTimeout(observer.next); | ||
| observer.next = setTimeout(dirtyCheck); | ||
| }; | ||
| if (typeof window !== "undefined") { | ||
| window.addEventListener("mouseup", fastCheck); | ||
| window.addEventListener("keyup", fastCheck); | ||
| window.addEventListener("mousedown", fastCheck); | ||
| window.addEventListener("keydown", fastCheck); | ||
| window.addEventListener("change", fastCheck); | ||
| } | ||
| } | ||
| observer.patches = patches; | ||
| observer.object = obj; | ||
| observer.unobserve = function() { | ||
| generate(observer); | ||
| clearTimeout(observer.next); | ||
| removeObserverFromMirror(mirror, observer); | ||
| if (typeof window !== "undefined") { | ||
| window.removeEventListener("mouseup", fastCheck); | ||
| window.removeEventListener("keyup", fastCheck); | ||
| window.removeEventListener("mousedown", fastCheck); | ||
| window.removeEventListener("keydown", fastCheck); | ||
| window.removeEventListener("change", fastCheck); | ||
| } | ||
| }; | ||
| mirror.observers.set(callback, new ObserverInfo(callback, observer)); | ||
| return observer; | ||
| } | ||
| function generate(observer, invertible) { | ||
| if (invertible === void 0) { | ||
| invertible = false; | ||
| } | ||
| var mirror = beforeDict.get(observer.object); | ||
| _generate(mirror.value, observer.object, observer.patches, "", invertible); | ||
| if (observer.patches.length) { | ||
| applyPatch(mirror.value, observer.patches); | ||
| } | ||
| var temp = observer.patches; | ||
| if (temp.length > 0) { | ||
| observer.patches = []; | ||
| if (observer.callback) { | ||
| observer.callback(temp); | ||
| } | ||
| } | ||
| return temp; | ||
| } | ||
| function _generate(mirror, obj, patches, path, invertible) { | ||
| if (obj === mirror) { | ||
| return; | ||
| } | ||
| if (typeof obj.toJSON === "function") { | ||
| obj = obj.toJSON(); | ||
| } | ||
| var newKeys = _objectKeys(obj); | ||
| var oldKeys = _objectKeys(mirror); | ||
| var deleted = false; | ||
| for (var t = oldKeys.length - 1; t >= 0; t--) { | ||
| var key = oldKeys[t]; | ||
| var oldVal = mirror[key]; | ||
| if (hasOwnProperty(obj, key) && !(obj[key] === void 0 && oldVal !== void 0 && Array.isArray(obj) === false)) { | ||
| var newVal = obj[key]; | ||
| if (typeof oldVal == "object" && oldVal != null && typeof newVal == "object" && newVal != null && Array.isArray(oldVal) === Array.isArray(newVal)) { | ||
| _generate(oldVal, newVal, patches, path + "/" + escapePathComponent(key), invertible); | ||
| } else { | ||
| if (oldVal !== newVal) { | ||
| if (invertible) { | ||
| patches.push({ op: "test", path: path + "/" + escapePathComponent(key), value: _deepClone(oldVal) }); | ||
| } | ||
| patches.push({ op: "replace", path: path + "/" + escapePathComponent(key), value: _deepClone(newVal) }); | ||
| } | ||
| } | ||
| } else if (Array.isArray(mirror) === Array.isArray(obj)) { | ||
| if (invertible) { | ||
| patches.push({ op: "test", path: path + "/" + escapePathComponent(key), value: _deepClone(oldVal) }); | ||
| } | ||
| patches.push({ op: "remove", path: path + "/" + escapePathComponent(key) }); | ||
| deleted = true; | ||
| } else { | ||
| if (invertible) { | ||
| patches.push({ op: "test", path, value: mirror }); | ||
| } | ||
| patches.push({ op: "replace", path, value: obj }); | ||
| } | ||
| } | ||
| if (!deleted && newKeys.length == oldKeys.length) { | ||
| return; | ||
| } | ||
| for (var t = 0; t < newKeys.length; t++) { | ||
| var key = newKeys[t]; | ||
| if (!hasOwnProperty(mirror, key) && obj[key] !== void 0) { | ||
| patches.push({ op: "add", path: path + "/" + escapePathComponent(key), value: _deepClone(obj[key]) }); | ||
| } | ||
| } | ||
| } | ||
| function compare(tree1, tree2, invertible) { | ||
| if (invertible === void 0) { | ||
| invertible = false; | ||
| } | ||
| var patches = []; | ||
| _generate(tree1, tree2, patches, "", invertible); | ||
| return patches; | ||
| } | ||
| // ../../node_modules/.pnpm/fast-json-patch@3.1.1/node_modules/fast-json-patch/index.mjs | ||
| Object.assign({}, core_exports, duplex_exports, { | ||
| JsonPatchError: PatchError, | ||
| deepClone: _deepClone, | ||
| escapePathComponent, | ||
| unescapePathComponent | ||
| }); | ||
| // src/Model.ts | ||
| var uid = new ShortId({ length: 20 }); | ||
@@ -18,2 +656,3 @@ function generateId() { | ||
| var SYM_INIT_DATA = /* @__PURE__ */ Symbol("parcae:initData"); | ||
| var SYM_SERVER_MERGE = /* @__PURE__ */ Symbol("parcae:serverMerge"); | ||
| var INSTANCE_METHODS = /* @__PURE__ */ new Set([ | ||
@@ -32,52 +671,6 @@ "save", | ||
| function lazyQuery(modelClass, steps = []) { | ||
| const CHAINABLE = [ | ||
| "select", | ||
| "where", | ||
| "andWhere", | ||
| "orWhere", | ||
| "whereIn", | ||
| "whereNot", | ||
| "whereNotIn", | ||
| "whereNull", | ||
| "whereNotNull", | ||
| "whereBetween", | ||
| "whereRaw", | ||
| "orWhereRaw", | ||
| "orWhereIn", | ||
| "orWhereNull", | ||
| "whereExists", | ||
| "orderBy", | ||
| "orderByRaw", | ||
| "groupBy", | ||
| "groupByRaw", | ||
| "having", | ||
| "havingRaw", | ||
| "limit", | ||
| "offset", | ||
| "distinct", | ||
| "distinctOn", | ||
| "join", | ||
| "innerJoin", | ||
| "leftJoin", | ||
| "rightJoin", | ||
| "clearOrder", | ||
| "clearSelect", | ||
| "from", | ||
| "sum", | ||
| "avg", | ||
| "min", | ||
| "max", | ||
| "increment", | ||
| "decrement" | ||
| ]; | ||
| const chain = {}; | ||
| for (const method of CHAINABLE) { | ||
| for (const method of CHAINABLE_METHODS) { | ||
| chain[method] = (...args) => lazyQuery(modelClass, [...steps, { method, args }]); | ||
| } | ||
| chain.basic = (limit = 25, sort = "createdAt", direction = "desc", page = 0) => lazyQuery(modelClass, [ | ||
| ...steps, | ||
| { method: "orderBy", args: [sort, direction] }, | ||
| { method: "limit", args: [limit] }, | ||
| { method: "offset", args: [page * limit] } | ||
| ]); | ||
| const resolve = async () => { | ||
@@ -92,3 +685,2 @@ const adapter = Model.hasAdapter() ? Model.getAdapter() : await Model.waitForAdapter(); | ||
| chain.find = async () => (await resolve()).find(); | ||
| chain.findAndSubscribe = async () => (await resolve()).findAndSubscribe(); | ||
| chain.first = async () => (await resolve()).first(); | ||
@@ -123,2 +715,3 @@ chain.count = async () => (await resolve()).count(); | ||
| static indexes; | ||
| static searchFields; | ||
| static managed = true; | ||
@@ -218,4 +811,4 @@ /** @internal */ | ||
| } | ||
| static basic(limit, sort, direction, page) { | ||
| return this._query().basic(limit, sort, direction, page); | ||
| static search(term) { | ||
| return this._query().search(term); | ||
| } | ||
@@ -280,2 +873,23 @@ // ── Constructor ──────────────────────────────────────────────────── | ||
| get(target2, prop) { | ||
| if (prop === SYM_SERVER_MERGE) { | ||
| return (serverData) => { | ||
| const pendingUpdates = new Set(target2[SYM_UPDATES]); | ||
| const pendingPatches = target2[SYM_PATCHING]; | ||
| const serverKeys = new Set(Object.keys(serverData)); | ||
| for (const key of serverKeys) { | ||
| if (pendingUpdates.has(key) || pendingPatches.has(key)) continue; | ||
| target2[key] = serverData[key]; | ||
| } | ||
| for (const key of Object.keys(target2)) { | ||
| if (SYSTEM_DATA_KEYS.has(key)) continue; | ||
| if (INSTANCE_METHODS.has(key)) continue; | ||
| if (EVENTEMITTER_KEYS.has(key)) continue; | ||
| if (key.startsWith("_")) continue; | ||
| if (serverKeys.has(key)) continue; | ||
| if (pendingUpdates.has(key) || pendingPatches.has(key)) continue; | ||
| delete target2[key]; | ||
| } | ||
| return new Proxy(target2, this); | ||
| }; | ||
| } | ||
| if (typeof prop === "symbol") { | ||
@@ -447,13 +1061,8 @@ return target2[prop]; | ||
| const freshData = fresh.__data; | ||
| const pendingKeys = this.__pendingKeys; | ||
| for (const key of Object.keys(freshData)) { | ||
| if (!pendingKeys.has(key)) { | ||
| this[key] = freshData[key]; | ||
| } | ||
| const merge = this[SYM_SERVER_MERGE]; | ||
| if (merge) { | ||
| merge(freshData); | ||
| } | ||
| } | ||
| } | ||
| async load() { | ||
| return this.refresh(); | ||
| } | ||
| async sanitize(_user) { | ||
@@ -467,9 +1076,12 @@ return { type: this.type, ...this.__data }; | ||
| static __refCache = /* @__PURE__ */ new Map(); | ||
| static REF_CACHE_TTL = 3e4; | ||
| // 30 seconds | ||
| _createRefProxy(targetClass, refId) { | ||
| const cacheKey = `${targetClass.type}:${refId}`; | ||
| const cached = _Model.__refCache.get(cacheKey); | ||
| if (cached) return cached; | ||
| if (cached && cached.expires > Date.now()) return cached.value; | ||
| if (cached) _Model.__refCache.delete(cacheKey); | ||
| let loaded = null; | ||
| let loading = null; | ||
| return new Proxy({}, { | ||
| const proxy = new Proxy({}, { | ||
| get(_target, prop) { | ||
@@ -486,3 +1098,6 @@ if (prop === "id") return refId; | ||
| loaded = result; | ||
| _Model.__refCache.set(cacheKey, loaded); | ||
| _Model.__refCache.set(cacheKey, { | ||
| value: proxy, | ||
| expires: Date.now() + _Model.REF_CACHE_TTL | ||
| }); | ||
| return result; | ||
@@ -494,7 +1109,24 @@ }); | ||
| }); | ||
| return proxy; | ||
| } | ||
| }; | ||
| /*! Bundled license information: | ||
| export { Model, generateId }; | ||
| fast-json-patch/module/helpers.mjs: | ||
| (*! | ||
| * https://github.com/Starcounter-Jack/JSON-Patch | ||
| * (c) 2017-2022 Joachim Wester | ||
| * MIT licensed | ||
| *) | ||
| fast-json-patch/module/duplex.mjs: | ||
| (*! | ||
| * https://github.com/Starcounter-Jack/JSON-Patch | ||
| * (c) 2017-2021 Joachim Wester | ||
| * MIT license | ||
| *) | ||
| */ | ||
| export { Model, SYM_SERVER_MERGE, generateId }; | ||
| //# sourceMappingURL=index.js.map | ||
| //# sourceMappingURL=index.js.map |
+1
-2
| { | ||
| "name": "@parcae/model", | ||
| "version": "0.7.0", | ||
| "version": "0.8.0", | ||
| "description": "Parcae Model — typed ORM base class with adapter pattern", | ||
@@ -44,3 +44,2 @@ "type": "module", | ||
| "build": "tsup", | ||
| "dev": "tsup --watch", | ||
| "typecheck": "tsc --noEmit", | ||
@@ -47,0 +46,0 @@ "clean": "rm -rf dist", |
+37
-37
@@ -19,8 +19,8 @@ # @parcae/model | ||
| user!: User; // reference -> VARCHAR storing ID | ||
| title: string = ""; // string -> VARCHAR | ||
| body: PostBody = {}; // object -> JSONB | ||
| tags: string[] = []; // array -> JSONB | ||
| user!: User; // reference -> VARCHAR storing ID | ||
| title: string = ""; // string -> VARCHAR | ||
| body: PostBody = {}; // object -> JSONB | ||
| tags: string[] = []; // array -> JSONB | ||
| published: boolean = false; // boolean -> BOOLEAN | ||
| views: number = 0; // number -> DOUBLE PRECISION | ||
| views: number = 0; // number -> DOUBLE PRECISION | ||
| } | ||
@@ -38,7 +38,7 @@ ``` | ||
| post.title; // "Hello" — reads from data store | ||
| post.title = "Updated"; // change tracked automatically | ||
| post.published; // false — typed as boolean | ||
| post.title; // "Hello" — reads from data store | ||
| post.title = "Updated"; // change tracked automatically | ||
| post.published; // false — typed as boolean | ||
| await post.save(); // flushes tracked changes | ||
| await post.save(); // flushes tracked changes | ||
| ``` | ||
@@ -51,8 +51,8 @@ | ||
| ```typescript | ||
| post.user; // User proxy — loads on property access, Suspense-compatible | ||
| post.$user; // "user_k8f2m9x" — raw string ID, no loading | ||
| post.user; // User proxy — loads on property access, Suspense-compatible | ||
| post.$user; // "user_k8f2m9x" — raw string ID, no loading | ||
| // Setting a reference accepts a Model instance or raw ID | ||
| post.user = someUser; // extracts someUser.id | ||
| post.$user = "user_abc123"; // sets raw ID directly | ||
| post.user = someUser; // extracts someUser.id | ||
| post.$user = "user_abc123"; // sets raw ID directly | ||
| ``` | ||
@@ -99,3 +99,3 @@ | ||
| const post = Post.create({ title: "New Post" }); | ||
| post.id; // auto-generated 20-char ID | ||
| post.id; // auto-generated 20-char ID | ||
@@ -108,3 +108,3 @@ // Save (insert or update) | ||
| post.title = "A"; | ||
| post.title = "AB"; // batched into single save | ||
| post.title = "AB"; // batched into single save | ||
| await post.save(); | ||
@@ -125,3 +125,3 @@ | ||
| // Serialize | ||
| post.toJSON(); // { type, id, title, ... } | ||
| post.toJSON(); // { type, id, title, ... } | ||
| await post.sanitize(user); // override in subclass to strip fields | ||
@@ -163,6 +163,6 @@ ``` | ||
| | Adapter | Store | Persistence | | ||
| | --- | --- | --- | | ||
| | Adapter | Store | Persistence | | ||
| | ----------------- | ----------------------- | ------------------------------- | | ||
| | `FrontendAdapter` | Valtio proxy (reactive) | Transport RPC (Socket.IO / SSE) | | ||
| | `BackendAdapter` | Plain object | Knex + PostgreSQL | | ||
| | `BackendAdapter` | Plain object | Knex + PostgreSQL | | ||
@@ -211,23 +211,23 @@ Set the adapter once at startup: | ||
| | Property | Type | Description | | ||
| | --- | --- | --- | | ||
| | `type` | `string` | Model identifier. Used for table naming and routing. | | ||
| | `path` | `string?` | Custom API path. Defaults to `/v1/{type}s`. | | ||
| | `scope` | `ModelScope?` | Row-level security rules. | | ||
| | `indexes` | `IndexDefinition[]?` | Database index definitions. | | ||
| | `managed` | `boolean` | `false` for externally managed tables (e.g. auth). Default: `true`. | | ||
| | `__schema` | `SchemaDefinition?` | Resolved at startup by RTTIST. Maps properties to column types. | | ||
| | Property | Type | Description | | ||
| | ---------- | -------------------- | ------------------------------------------------------------------- | | ||
| | `type` | `string` | Model identifier. Used for table naming and routing. | | ||
| | `path` | `string?` | Custom API path. Defaults to `/v1/{type}s`. | | ||
| | `scope` | `ModelScope?` | Row-level security rules. | | ||
| | `indexes` | `IndexDefinition[]?` | Database index definitions. | | ||
| | `managed` | `boolean` | `false` for externally managed tables (e.g. auth). Default: `true`. | | ||
| | `__schema` | `SchemaDefinition?` | Resolved at startup by RTTIST. Maps properties to column types. | | ||
| ## Type Mapping | ||
| | TypeScript | ColumnType | Postgres | | ||
| | --- | --- | --- | | ||
| | `string` | `"string"` | VARCHAR(2048) | | ||
| | `string` (long) | `"text"` | TEXT | | ||
| | `number` (int) | `"integer"` | INTEGER | | ||
| | `number` (float) | `"number"` | DOUBLE PRECISION | | ||
| | `boolean` | `"boolean"` | BOOLEAN | | ||
| | `Date` | `"datetime"` | TIMESTAMP | | ||
| | `SomeModel` | `{ kind: "ref" }` | VARCHAR (foreign key) | | ||
| | object / array | `"json"` | JSONB | | ||
| | TypeScript | ColumnType | Postgres | | ||
| | ---------------- | ----------------- | --------------------- | | ||
| | `string` | `"string"` | VARCHAR(2048) | | ||
| | `string` (long) | `"text"` | TEXT | | ||
| | `number` (int) | `"integer"` | INTEGER | | ||
| | `number` (float) | `"number"` | DOUBLE PRECISION | | ||
| | `boolean` | `"boolean"` | BOOLEAN | | ||
| | `Date` | `"datetime"` | TIMESTAMP | | ||
| | `SomeModel` | `{ kind: "ref" }` | VARCHAR (foreign key) | | ||
| | object / array | `"json"` | JSONB | | ||
@@ -234,0 +234,0 @@ ## Exports |
| import { proxy } from 'valtio'; | ||
| // src/adapters/client.ts | ||
| function stripVersion(path) { | ||
| return path.replace(/^\/v\d+/, ""); | ||
| } | ||
| var FrontendAdapter = class { | ||
| transport; | ||
| constructor(transport) { | ||
| this.transport = transport; | ||
| } | ||
| p(modelPath) { | ||
| return stripVersion(modelPath); | ||
| } | ||
| // ── createStore ────────────────────────────────────────────────────── | ||
| createStore(data) { | ||
| return proxy(data); | ||
| } | ||
| // ── save ───────────────────────────────────────────────────────────── | ||
| async save(model, changes) { | ||
| const ModelClass = model.constructor; | ||
| const path = this.resolvePath(ModelClass); | ||
| if (changes.creating) { | ||
| const result = await this.transport.post(path, model.__data); | ||
| if (result?.id) model.__data.id = result.id; | ||
| } else if (changes.ops.length > 0) { | ||
| await this.transport.patch(`${path}/${model.id}`, { ops: changes.ops }); | ||
| } else if (changes.updates.length > 0) { | ||
| const data = {}; | ||
| for (const key of changes.updates) { | ||
| data[key] = model.__data[key]; | ||
| } | ||
| await this.transport.put(`${path}/${model.id}`, data); | ||
| } | ||
| } | ||
| // ── remove ─────────────────────────────────────────────────────────── | ||
| async remove(model) { | ||
| const ModelClass = model.constructor; | ||
| const path = this.resolvePath(ModelClass); | ||
| await this.transport.delete(`${path}/${model.id}`); | ||
| } | ||
| // ── findById ───────────────────────────────────────────────────────── | ||
| async findById(modelClass, id) { | ||
| const path = this.resolvePath(modelClass); | ||
| try { | ||
| const data = await this.transport.get(`${path}/${id}`); | ||
| if (!data) return null; | ||
| return new modelClass(this, data); | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
| // ── query ──────────────────────────────────────────────────────────── | ||
| query(modelClass) { | ||
| return this._buildQuery(modelClass, []); | ||
| } | ||
| _buildQuery(modelClass, steps) { | ||
| const adapter = this; | ||
| const CHAINABLE = [ | ||
| "select", | ||
| "where", | ||
| "andWhere", | ||
| "orWhere", | ||
| "whereIn", | ||
| "whereNot", | ||
| "whereNotIn", | ||
| "whereNull", | ||
| "whereNotNull", | ||
| "whereBetween", | ||
| "whereRaw", | ||
| "orWhereRaw", | ||
| "orWhereIn", | ||
| "orWhereNull", | ||
| "whereExists", | ||
| "orderBy", | ||
| "orderByRaw", | ||
| "groupBy", | ||
| "groupByRaw", | ||
| "having", | ||
| "havingRaw", | ||
| "limit", | ||
| "offset", | ||
| "distinct", | ||
| "distinctOn", | ||
| "join", | ||
| "innerJoin", | ||
| "leftJoin", | ||
| "rightJoin", | ||
| "clearOrder", | ||
| "clearSelect", | ||
| "from", | ||
| "sum", | ||
| "avg", | ||
| "min", | ||
| "max", | ||
| "increment", | ||
| "decrement" | ||
| ]; | ||
| const chain = {}; | ||
| for (const method of CHAINABLE) { | ||
| chain[method] = (...args) => { | ||
| return adapter._buildQuery(modelClass, [ | ||
| ...steps, | ||
| { method, args: adapter._serializeArgs(args) } | ||
| ]); | ||
| }; | ||
| } | ||
| chain.basic = (limit = 25, sort = "createdAt", direction = "desc", page = 0) => { | ||
| return adapter._buildQuery(modelClass, [ | ||
| ...steps, | ||
| { method: "orderBy", args: [sort, direction] }, | ||
| { method: "limit", args: [limit] }, | ||
| { method: "offset", args: [page * limit] } | ||
| ]); | ||
| }; | ||
| let debounceMs = void 0; | ||
| chain.debounce = (ms) => { | ||
| debounceMs = ms; | ||
| const newChain = adapter._buildQuery(modelClass, steps); | ||
| newChain.__debounceMs = ms; | ||
| return newChain; | ||
| }; | ||
| chain.find = async () => { | ||
| const path = adapter.resolvePath(modelClass); | ||
| const result = await adapter.transport.get(path, { __query: steps }); | ||
| const items = result?.[modelClass.type + "s"] ?? result?.items ?? []; | ||
| return items.map((row) => new modelClass(adapter, row)); | ||
| }; | ||
| chain.findAndSubscribe = async () => { | ||
| const path = adapter.resolvePath(modelClass); | ||
| const result = await adapter.transport.get(path, { | ||
| __query: steps, | ||
| __subscribe: true | ||
| }); | ||
| const items = result?.[modelClass.type + "s"] ?? result?.items ?? []; | ||
| return { | ||
| items: items.map((row) => new modelClass(adapter, row)), | ||
| hash: result?.__hash ?? null | ||
| }; | ||
| }; | ||
| chain.first = async () => { | ||
| const all = await chain.find(); | ||
| return all[0] ?? null; | ||
| }; | ||
| chain.count = async () => { | ||
| const path = adapter.resolvePath(modelClass); | ||
| const result = await adapter.transport.get(path, { | ||
| __query: steps, | ||
| __count: true | ||
| }); | ||
| return result?.total ?? 0; | ||
| }; | ||
| chain.__steps = steps; | ||
| chain.__modelType = modelClass.type; | ||
| chain.__modelClass = modelClass; | ||
| chain.__adapter = adapter; | ||
| Object.defineProperty(chain, "__debounceMs", { | ||
| get: () => debounceMs, | ||
| enumerable: true | ||
| }); | ||
| return chain; | ||
| } | ||
| _serializeArgs(args) { | ||
| return args.map((arg) => { | ||
| if (typeof arg === "function") return "__fn__"; | ||
| return arg; | ||
| }); | ||
| } | ||
| // ── patch ──────────────────────────────────────────────────────────── | ||
| async patch(model, ops) { | ||
| const ModelClass = model.constructor; | ||
| const path = this.resolvePath(ModelClass); | ||
| await this.transport.patch(`${path}/${model.id}`, { ops }); | ||
| } | ||
| // ── helpers ────────────────────────────────────────────────────────── | ||
| resolvePath(modelClass) { | ||
| if (!modelClass.path && !modelClass.type) { | ||
| throw new Error(`Model has no path or type`); | ||
| } | ||
| const fullPath = modelClass.path ?? `/v1/${modelClass.type}s`; | ||
| return this.p(fullPath); | ||
| } | ||
| }; | ||
| var client_default = FrontendAdapter; | ||
| export { FrontendAdapter, client_default }; | ||
| //# sourceMappingURL=chunk-UE7DW2FG.js.map | ||
| //# sourceMappingURL=chunk-UE7DW2FG.js.map |
| {"version":3,"sources":["../src/adapters/client.ts"],"names":[],"mappings":"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","file":"chunk-UE7DW2FG.js","sourcesContent":["/**\n * FrontendAdapter — Valtio + Transport for Parcae Model.\n *\n * Transport-agnostic. Works with any implementation of the Transport interface:\n * - SocketTransport (Socket.IO — bidirectional, realtime)\n * - SSETransport (Server-Sent Events — read-heavy, simpler)\n * - GRPCTransport (gRPC — service-to-service)\n * - Or any custom transport\n */\n\nimport { proxy } from \"valtio\";\nimport type {\n ModelAdapter,\n ModelConstructor,\n ChangeSet,\n QueryChain,\n QueryStep,\n PatchOp,\n} from \"./types\";\n\n// ─── Transport Interface ─────────────────────────────────────────────────────\n\n/**\n * Abstract transport layer. Decouples the Model system from the wire protocol.\n *\n * Implementations:\n * - SocketTransport: Socket.IO (bidirectional, realtime subscriptions)\n * - SSETransport: HTTP + Server-Sent Events (read-heavy, simpler infra)\n * - GRPCTransport: gRPC (service-to-service, protobuf)\n */\nexport interface Transport {\n // ── Request/Response (RPC-style) ────────────────────────────────────\n get(path: string, data?: any): Promise<any>;\n post(path: string, data?: any): Promise<any>;\n put(path: string, data?: any): Promise<any>;\n patch(path: string, data?: any): Promise<any>;\n delete(path: string, data?: any): Promise<any>;\n\n // ── Subscriptions (realtime) ────────────────────────────────────────\n /**\n * Subscribe to a named event stream.\n * Returns a dispose function to unsubscribe.\n *\n * Socket.IO: socket.on(event, handler)\n * SSE: new EventSource(url) per channel\n * gRPC: server streaming RPC\n */\n subscribe?(event: string, handler: (...args: any[]) => void): () => void;\n\n /**\n * Unsubscribe from a named event stream.\n * Alternative to calling the dispose function returned by subscribe().\n */\n unsubscribe?(event: string, handler?: (...args: any[]) => void): void;\n\n // ── Control messages ────────────────────────────────────────────────\n /**\n * Send a control message to the server (fire-and-forget).\n *\n * Socket.IO: socket.emit(event, ...args)\n * SSE: POST /v1/__control { event, args }\n * gRPC: unary RPC\n */\n send?(event: string, ...args: any[]): void;\n\n // ── Connection state ────────────────────────────────────────────────\n /** Whether the transport is currently connected. */\n readonly isConnected?: boolean;\n /** Whether the transport is currently loading/connecting. */\n readonly isLoading?: boolean;\n\n /**\n * Listen for connection state changes.\n * Events: \"connected\", \"disconnected\", \"reconnecting\", \"error\"\n */\n on?(event: string, handler: (...args: any[]) => void): void;\n off?(event: string, handler?: (...args: any[]) => void): void;\n\n /** Disconnect the transport. */\n disconnect?(): void;\n /** Reconnect the transport. */\n reconnect?(): Promise<void>;\n}\n\n/**\n * Strip version prefix from a path if it starts with /v{number}.\n * The transport prepends the version automatically.\n */\nfunction stripVersion(path: string): string {\n return path.replace(/^\\/v\\d+/, \"\");\n}\n\n// ─── FrontendAdapter ─────────────────────────────────────────────────────────\n\nexport class FrontendAdapter implements ModelAdapter {\n public transport: Transport;\n\n constructor(transport: Transport) {\n this.transport = transport;\n }\n\n private p(modelPath: string): string {\n return stripVersion(modelPath);\n }\n\n // ── createStore ──────────────────────────────────────────────────────\n\n createStore(data: Record<string, any>): Record<string, any> {\n return proxy(data);\n }\n\n // ── save ─────────────────────────────────────────────────────────────\n\n async save(model: any, changes: ChangeSet): Promise<void> {\n const ModelClass = model.constructor as ModelConstructor;\n const path = this.resolvePath(ModelClass);\n\n if (changes.creating) {\n const result = await this.transport.post(path, model.__data);\n if (result?.id) model.__data.id = result.id;\n } else if (changes.ops.length > 0) {\n await this.transport.patch(`${path}/${model.id}`, { ops: changes.ops });\n } else if (changes.updates.length > 0) {\n const data: Record<string, any> = {};\n for (const key of changes.updates) {\n data[key] = model.__data[key];\n }\n await this.transport.put(`${path}/${model.id}`, data);\n }\n }\n\n // ── remove ───────────────────────────────────────────────────────────\n\n async remove(model: any): Promise<void> {\n const ModelClass = model.constructor as ModelConstructor;\n const path = this.resolvePath(ModelClass);\n await this.transport.delete(`${path}/${model.id}`);\n }\n\n // ── findById ─────────────────────────────────────────────────────────\n\n async findById<T>(\n modelClass: ModelConstructor<T>,\n id: string,\n ): Promise<T | null> {\n const path = this.resolvePath(modelClass);\n try {\n const data = await this.transport.get(`${path}/${id}`);\n if (!data) return null;\n return new modelClass(this, data) as T;\n } catch {\n return null;\n }\n }\n\n // ── query ────────────────────────────────────────────────────────────\n\n query<T>(modelClass: ModelConstructor<T>): QueryChain<T> {\n return this._buildQuery(modelClass, []);\n }\n\n private _buildQuery<T>(\n modelClass: ModelConstructor<T>,\n steps: QueryStep[],\n ): QueryChain<T> {\n const adapter = this;\n\n const CHAINABLE = [\n \"select\",\n \"where\",\n \"andWhere\",\n \"orWhere\",\n \"whereIn\",\n \"whereNot\",\n \"whereNotIn\",\n \"whereNull\",\n \"whereNotNull\",\n \"whereBetween\",\n \"whereRaw\",\n \"orWhereRaw\",\n \"orWhereIn\",\n \"orWhereNull\",\n \"whereExists\",\n \"orderBy\",\n \"orderByRaw\",\n \"groupBy\",\n \"groupByRaw\",\n \"having\",\n \"havingRaw\",\n \"limit\",\n \"offset\",\n \"distinct\",\n \"distinctOn\",\n \"join\",\n \"innerJoin\",\n \"leftJoin\",\n \"rightJoin\",\n \"clearOrder\",\n \"clearSelect\",\n \"from\",\n \"sum\",\n \"avg\",\n \"min\",\n \"max\",\n \"increment\",\n \"decrement\",\n ] as const;\n\n const chain: any = {};\n\n for (const method of CHAINABLE) {\n chain[method] = (...args: any[]) => {\n return adapter._buildQuery(modelClass, [\n ...steps,\n { method, args: adapter._serializeArgs(args) },\n ]);\n };\n }\n\n chain.basic = (\n limit: number = 25,\n sort: string = \"createdAt\",\n direction: \"asc\" | \"desc\" = \"desc\",\n page: number = 0,\n ) => {\n return adapter._buildQuery(modelClass, [\n ...steps,\n { method: \"orderBy\", args: [sort, direction] },\n { method: \"limit\", args: [limit] },\n { method: \"offset\", args: [page * limit] },\n ]);\n };\n\n let debounceMs: number | undefined = undefined;\n chain.debounce = (ms: number) => {\n debounceMs = ms;\n const newChain = adapter._buildQuery(modelClass, steps);\n (newChain as any).__debounceMs = ms;\n return newChain;\n };\n\n chain.find = async (): Promise<T[]> => {\n const path = adapter.resolvePath(modelClass);\n const result = await adapter.transport.get(path, { __query: steps });\n const items = result?.[modelClass.type + \"s\"] ?? result?.items ?? [];\n return items.map((row: any) => new modelClass(adapter, row));\n };\n\n /**\n * Like find(), but also subscribes to realtime updates.\n * Returns { items, hash } — the hash identifies the subscription.\n */\n chain.findAndSubscribe = async (): Promise<{\n items: T[];\n hash: string | null;\n }> => {\n const path = adapter.resolvePath(modelClass);\n const result = await adapter.transport.get(path, {\n __query: steps,\n __subscribe: true,\n });\n const items = result?.[modelClass.type + \"s\"] ?? result?.items ?? [];\n return {\n items: items.map((row: any) => new modelClass(adapter, row)),\n hash: result?.__hash ?? null,\n };\n };\n\n chain.first = async (): Promise<T | null> => {\n const all = await chain.find();\n return all[0] ?? null;\n };\n\n chain.count = async (): Promise<number> => {\n const path = adapter.resolvePath(modelClass);\n const result = await adapter.transport.get(path, {\n __query: steps,\n __count: true,\n });\n return result?.total ?? 0;\n };\n\n chain.__steps = steps;\n chain.__modelType = modelClass.type;\n chain.__modelClass = modelClass;\n chain.__adapter = adapter;\n Object.defineProperty(chain, \"__debounceMs\", {\n get: () => debounceMs,\n enumerable: true,\n });\n\n return chain as QueryChain<T>;\n }\n\n private _serializeArgs(args: any[]): any[] {\n return args.map((arg) => {\n if (typeof arg === \"function\") return \"__fn__\";\n return arg;\n });\n }\n\n // ── patch ────────────────────────────────────────────────────────────\n\n async patch(model: any, ops: PatchOp[]): Promise<void> {\n const ModelClass = model.constructor as ModelConstructor;\n const path = this.resolvePath(ModelClass);\n await this.transport.patch(`${path}/${model.id}`, { ops });\n }\n\n // ── helpers ──────────────────────────────────────────────────────────\n\n private resolvePath(modelClass: ModelConstructor): string {\n if (!modelClass.path && !modelClass.type) {\n throw new Error(`Model has no path or type`);\n }\n const fullPath = modelClass.path ?? `/v1/${modelClass.type}s`;\n return this.p(fullPath);\n }\n}\n\nexport default FrontendAdapter;\n"]} |
Sorry, the diff of this file is too big to display
195821
93.78%15
15.38%1731
66.92%