@supalive/core 1.13.1 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +1 -2
  2. package/dist/helper-CUSGbt2A.d.ts +15 -0
  3. package/dist/helper-CUSGbt2A.d.ts.map +1 -0
  4. package/dist/helper-CVcjwD3k.js +73 -0
  5. package/dist/helper-CVcjwD3k.js.map +1 -0
  6. package/dist/index-Ddr4NiPf.d.ts +2295 -0
  7. package/dist/index-Ddr4NiPf.d.ts.map +1 -0
  8. package/dist/index-f7mNbMhq.d.ts +2301 -0
  9. package/dist/index-f7mNbMhq.d.ts.map +1 -0
  10. package/dist/mysql-BX5hfgfE.d.ts +114 -0
  11. package/dist/mysql-BX5hfgfE.d.ts.map +1 -0
  12. package/dist/mysql-DWj2q3qR.d.ts +114 -0
  13. package/dist/mysql-DWj2q3qR.d.ts.map +1 -0
  14. package/dist/mysql-v_0U0NU4.js +623 -0
  15. package/dist/mysql-v_0U0NU4.js.map +1 -0
  16. package/dist/overlap-checker-DVbh0YC4.js +259 -0
  17. package/dist/overlap-checker-DVbh0YC4.js.map +1 -0
  18. package/dist/postgres-D0M1ri-C.d.ts +118 -0
  19. package/dist/postgres-D0M1ri-C.d.ts.map +1 -0
  20. package/dist/postgres-EUz8TYFn.d.ts +118 -0
  21. package/dist/postgres-EUz8TYFn.d.ts.map +1 -0
  22. package/dist/postgres-vYRyZJZd.js +868 -0
  23. package/dist/postgres-vYRyZJZd.js.map +1 -0
  24. package/dist/query-Bk2W-ynQ.js +730 -0
  25. package/dist/query-Bk2W-ynQ.js.map +1 -0
  26. package/dist/realtime_db-DnZKn_FZ.js +312 -0
  27. package/dist/realtime_db-DnZKn_FZ.js.map +1 -0
  28. package/dist/router-DP2ThAwh.js +274 -0
  29. package/dist/router-DP2ThAwh.js.map +1 -0
  30. package/dist/router-Dfnwka7K.js +337 -0
  31. package/dist/router-Dfnwka7K.js.map +1 -0
  32. package/dist/schema-DRtz5h5l.js +131 -0
  33. package/dist/schema-DRtz5h5l.js.map +1 -0
  34. package/dist/src/client/index.d.ts +1 -1
  35. package/dist/src/client/index.js +2 -2
  36. package/dist/src/exports/mysql.d.ts +1 -1
  37. package/dist/src/exports/postgres.d.ts +1 -1
  38. package/dist/src/exports/procedure.d.ts +12 -9
  39. package/dist/src/exports/procedure.d.ts.map +1 -1
  40. package/dist/src/exports/procedure.js +65 -6
  41. package/dist/src/exports/procedure.js.map +1 -1
  42. package/dist/src/exports/schema-sql.d.ts +2 -2
  43. package/dist/src/exports/schema-sql.js +3 -3
  44. package/dist/src/exports/schema-sql.js.map +1 -1
  45. package/dist/src/exports/server.d.ts +4 -4
  46. package/dist/src/exports/server.d.ts.map +1 -1
  47. package/dist/src/exports/server.js +4 -4
  48. package/dist/src/exports/server.js.map +1 -1
  49. package/dist/src/exports/sub-manager-worker-entry.js +1 -1
  50. package/dist/src/exports/types.d.ts +8 -3
  51. package/dist/src/exports/types.d.ts.map +1 -0
  52. package/dist/src/exports/types.js +20 -1
  53. package/dist/src/exports/types.js.map +1 -0
  54. package/dist/sub-worker-dispatch-CC0w_sx0.js +826 -0
  55. package/dist/sub-worker-dispatch-CC0w_sx0.js.map +1 -0
  56. package/dist/sub-worker-dispatch-UGC9vwQJ.js +826 -0
  57. package/dist/sub-worker-dispatch-UGC9vwQJ.js.map +1 -0
  58. package/dist/types_db-Dw2RHWrd.js +172 -0
  59. package/dist/types_db-Dw2RHWrd.js.map +1 -0
  60. package/dist/types_server-B7elBQyz.d.ts +523 -0
  61. package/dist/types_server-B7elBQyz.d.ts.map +1 -0
  62. package/dist/types_server-BSiGxbqb.d.ts +523 -0
  63. package/dist/types_server-BSiGxbqb.d.ts.map +1 -0
  64. package/package.json +1 -1
@@ -0,0 +1,274 @@
1
+ //#region src/router/procedure.ts
2
+ /**
3
+ * No-op connection routing for query paths that genuinely ALWAYS run on the
4
+ * primary — live subscription re-execution and mutation handlers. Spread into
5
+ * the ctx so `ctx.usePrimaryConn()`/`useReplicaConn()` are always callable but
6
+ * inert (there is only ever the primary connection to bind).
7
+ *
8
+ * For a nested `runQuery` that JOINS a parent transaction, use {@link parentConn}
9
+ * instead — there the connection isn't necessarily the primary.
10
+ */
11
+ const primaryOnlyConn = {
12
+ usePrimaryConn: () => {},
13
+ useReplicaConn: () => {}
14
+ };
15
+ /**
16
+ * No-op connection routing for a nested `runQuery` that JOINS a parent
17
+ * transaction (a `runQuery` called with a query/mutation `parentCtx`). The knobs
18
+ * always use the PARENT's connection because the read connection is fixed to the
19
+ * parent transaction's — which may be the PRIMARY or the REPLICA, depending on
20
+ * how the parent query was routed.
21
+ *
22
+ * They MUST be inert: a nested query shares the parent's snapshot + readSet, so
23
+ * calling `useConnection` here would either repoint the parent's connection
24
+ * mid-flight (before its first read) or throw (after it) — either way corrupting
25
+ * the parent transaction. A handler that needs to force primary therefore cannot
26
+ * do so while nested under a replica-routed parent (it inherits the replica,
27
+ * which is already safe w.r.t. the client's `minTs` floor); call it via an
28
+ * action-parent `runQuery` instead, which opens a fresh routed read.
29
+ */
30
+ const parentConn = {
31
+ usePrimaryConn: () => {},
32
+ useReplicaConn: () => {}
33
+ };
34
+ /**
35
+ * Create a query builder with a pre-defined context type.
36
+ * This allows you to define the context type once and have it inferred
37
+ * automatically in all your query handlers.
38
+ *
39
+ * @example
40
+ * // Define your server context
41
+ * interface ServerContext {
42
+ * auth: { userId: string };
43
+ * requestId: string;
44
+ * }
45
+ *
46
+ * // Create a typed query builder
47
+ * const query = createQueryBuilder<ServerContext>();
48
+ *
49
+ * // Use it - context type is automatically inferred!
50
+ * const getUser = query({
51
+ * args: z.object({ id: z.string() }),
52
+ * handler: async (ctx, { id }) => {
53
+ * // ctx.db for database queries
54
+ * const user = await ctx.db.query(UsersSchema).find(id);
55
+ * // ctx.context for server context
56
+ * console.log(ctx.context.requestId);
57
+ * return user;
58
+ * }
59
+ * });
60
+ */
61
+ function createQueryBuilder() {
62
+ return function query(config) {
63
+ return {
64
+ _type: "procedure",
65
+ procedureType: "query",
66
+ inputSchema: config.args,
67
+ outputSchema: config.returns,
68
+ fn: config.handler,
69
+ internal: config.internal ?? false,
70
+ queryIdentity: config.queryIdentity
71
+ };
72
+ };
73
+ }
74
+ /**
75
+ * Create a mutation builder with a pre-defined context type.
76
+ * This allows you to define the context type once and have it inferred
77
+ * automatically in all your mutation handlers.
78
+ *
79
+ * @example
80
+ * // Define your server context
81
+ * interface ServerContext {
82
+ * auth: { userId: string };
83
+ * requestId: string;
84
+ * }
85
+ *
86
+ * // Create a typed mutation builder
87
+ * const mutation = createMutationBuilder<ServerContext>();
88
+ *
89
+ * // Use it - context type is automatically inferred!
90
+ * const createUser = mutation({
91
+ * args: z.object({ name: z.string() }),
92
+ * handler: async (ctx, { name }) => {
93
+ * // ctx.db for mutations
94
+ * await ctx.db.insert(UsersSchema, id, { name });
95
+ * // ctx.context for server context
96
+ * console.log(ctx.context.requestId);
97
+ * return { success: true };
98
+ * }
99
+ * });
100
+ */
101
+ function createMutationBuilder() {
102
+ return function mutation(config) {
103
+ return {
104
+ _type: "procedure",
105
+ procedureType: "mutation",
106
+ inputSchema: config.args,
107
+ outputSchema: config.returns,
108
+ fn: config.handler,
109
+ internal: config.internal ?? false
110
+ };
111
+ };
112
+ }
113
+ /**
114
+ * Create an action builder with a pre-defined context type.
115
+ * Actions have access to the full SupaliveDb for both queries and mutations.
116
+ *
117
+ * @example
118
+ * // Define your server context
119
+ * interface ServerContext {
120
+ * auth: { userId: string };
121
+ * requestId: string;
122
+ * }
123
+ *
124
+ * // Create a typed action builder
125
+ * const action = createActionBuilder<ServerContext>();
126
+ *
127
+ * // Use it - context type is automatically inferred!
128
+ * const processOrder = action({
129
+ * args: z.object({ orderId: z.string() }),
130
+ * handler: async (ctx, { orderId }) => {
131
+ * // ctx.db for full database access
132
+ * const order = await ctx.db.query(async (db) => {
133
+ * return db.query(OrdersSchema).find(orderId);
134
+ * });
135
+ * await ctx.db.mutation(async (db) => {
136
+ * await db.update(OrdersSchema, orderId, { status: "processed" });
137
+ * });
138
+ * // ctx.context for server context
139
+ * console.log(ctx.context.requestId);
140
+ * return { success: true };
141
+ * }
142
+ * });
143
+ */
144
+ function createActionBuilder() {
145
+ return function action(config) {
146
+ return {
147
+ _type: "procedure",
148
+ procedureType: "action",
149
+ inputSchema: config.args,
150
+ outputSchema: config.returns,
151
+ fn: config.handler,
152
+ internal: config.internal ?? false
153
+ };
154
+ };
155
+ }
156
+ /**
157
+ * Create a job builder with a pre-defined context type. A job is a
158
+ * server-only procedure invoked by the scheduler over HTTP — either on its
159
+ * declared `cron` schedule or as a precise one-shot enqueued via the
160
+ * server's job API. Jobs run like actions (full db, non-transactional) and
161
+ * are always internal, so they are never reachable from a client.
162
+ *
163
+ * @example
164
+ * const job = createJobBuilder<ServerContext>();
165
+ *
166
+ * export const cleanupOtps = job({
167
+ * cron: "0 * * * *", // hourly
168
+ * args: z.object({}),
169
+ * handler: async (ctx) => {
170
+ * await ctx.db.mutation(async (db) => { ... });
171
+ * return { ok: true };
172
+ * },
173
+ * });
174
+ */
175
+ function createJobBuilder() {
176
+ return function job(config) {
177
+ return {
178
+ _type: "procedure",
179
+ procedureType: "job",
180
+ inputSchema: config.args,
181
+ fn: config.handler,
182
+ internal: true,
183
+ cron: config.cron
184
+ };
185
+ };
186
+ }
187
+ //#endregion
188
+ //#region src/router/router.ts
189
+ /**
190
+ * Create a router from procedure definitions.
191
+ * Procedure names are inferred from the object keys.
192
+ *
193
+ * @example
194
+ * const appRouter = router({
195
+ * procedures: {
196
+ * getUser,
197
+ * createUser,
198
+ * internalGetAll,
199
+ * },
200
+ * });
201
+ *
202
+ * export type AppRouter = typeof appRouter;
203
+ */
204
+ function router(config) {
205
+ const registry = getContextRegistry(config.contextName ?? "default");
206
+ for (const [name, proc] of Object.entries(config.procedures)) registry.registerProcedure(name, proc);
207
+ return {
208
+ _type: "router",
209
+ procedures: config.procedures,
210
+ contextName: config.contextName ?? "default"
211
+ };
212
+ }
213
+ const contextRegistry = /* @__PURE__ */ new Map();
214
+ function getContextRegistry(inContext) {
215
+ let registry = contextRegistry.get(inContext);
216
+ if (!registry) {
217
+ registry = new ContextRegistry();
218
+ contextRegistry.set(inContext, registry);
219
+ }
220
+ return registry;
221
+ }
222
+ var ContextRegistry = class {
223
+ procedureRegistry = /* @__PURE__ */ new Map();
224
+ internalProcedureNames = /* @__PURE__ */ new Set();
225
+ registerProcedure(name, proc) {
226
+ if (this.procedureRegistry.has(name)) throw new Error(`[router] Procedure "${name}" is already registered. Each procedure name must be unique across the application.`);
227
+ if (proc.internal) this.markInternalProcedure(name);
228
+ const queryIdentity = proc.procedureType === "query" ? proc.queryIdentity : void 0;
229
+ const cron = proc.procedureType === "job" ? proc.cron : void 0;
230
+ this.procedureRegistry.set(name, {
231
+ name,
232
+ type: proc.procedureType,
233
+ internal: proc.internal,
234
+ inputSchema: proc.inputSchema,
235
+ fn: proc.fn,
236
+ queryIdentity,
237
+ cron
238
+ });
239
+ }
240
+ /** Get a registered procedure by name (runtime lookup) */
241
+ getProcedure(name) {
242
+ return this.procedureRegistry.get(name);
243
+ }
244
+ /** Get all registered procedure names */
245
+ getProcedureNames() {
246
+ return [...this.procedureRegistry.keys()];
247
+ }
248
+ /** Get all registered procedures */
249
+ getAllProcedures() {
250
+ return new Map(this.procedureRegistry);
251
+ }
252
+ /** Get all registered job procedures (server-only, scheduler-triggered). */
253
+ getJobProcedures() {
254
+ return [...this.procedureRegistry.values()].filter((p) => p.type === "job");
255
+ }
256
+ /** Clear the registry (for testing only) */
257
+ clearRegistry() {
258
+ this.procedureRegistry.clear();
259
+ this.clearInternalProcedures();
260
+ }
261
+ markInternalProcedure(name) {
262
+ this.internalProcedureNames.add(name);
263
+ }
264
+ isInternalProcedure(name) {
265
+ return this.internalProcedureNames.has(name);
266
+ }
267
+ clearInternalProcedures() {
268
+ this.internalProcedureNames.clear();
269
+ }
270
+ };
271
+ //#endregion
272
+ export { createJobBuilder as a, parentConn as c, createActionBuilder as i, primaryOnlyConn as l, getContextRegistry as n, createMutationBuilder as o, router as r, createQueryBuilder as s, ContextRegistry as t };
273
+
274
+ //# sourceMappingURL=router-DP2ThAwh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router-DP2ThAwh.js","names":[],"sources":["../src/router/procedure.ts","../src/router/router.ts"],"sourcesContent":["import type { ZodType } from \"zod\";\nimport type { DbReader, DbWriter } from \"../db/context\";\nimport type { SupaliveDb } from \"../db/realtime_db\";\nimport type { ObjectStorage } from \"../storage\";\nimport type { JobClient } from \"../jobs/scheduler\";\n\n// ─── Context Types ───────────────────────────────────────────────────────────\n\n/**\n * Query context passed to query handlers.\n * Contains database reader and user-defined server context.\n */\n/** Options for the per-handler connection-routing methods (see {@link QueryCtx}). */\nexport interface ConnOpts {\n /**\n * Force the PRIMARY (writer) connection even inside a query/action —\n * read-your-writes. On `useReplicaConn` this overrides the replica choice.\n */\n readOwnWrite?: boolean;\n}\n\nexport interface QueryCtx<TContext = unknown> {\n /** Database reader for queries */\n db: DbReader;\n /** User-defined server context (auth, requestId, etc.) */\n serverCtx?: TContext;\n /**\n * Route this query's reads to the PRIMARY (writer) connection. Must be called\n * BEFORE any read (throws otherwise). A no-op when reads already run on primary\n * (no replica configured, or a primary-only path like a live query).\n */\n usePrimaryConn(opts?: ConnOpts): void;\n /**\n * Route this query's reads to the read REPLICA, if one is configured. Must be\n * called BEFORE any read. `{ readOwnWrite: true }` forces the PRIMARY instead.\n */\n useReplicaConn(opts?: ConnOpts): void;\n}\n\n/**\n * No-op connection routing for query paths that genuinely ALWAYS run on the\n * primary — live subscription re-execution and mutation handlers. Spread into\n * the ctx so `ctx.usePrimaryConn()`/`useReplicaConn()` are always callable but\n * inert (there is only ever the primary connection to bind).\n *\n * For a nested `runQuery` that JOINS a parent transaction, use {@link parentConn}\n * instead — there the connection isn't necessarily the primary.\n */\nexport const primaryOnlyConn: Pick<QueryCtx, \"usePrimaryConn\" | \"useReplicaConn\"> = {\n usePrimaryConn: () => { },\n useReplicaConn: () => { },\n};\n\n/**\n * No-op connection routing for a nested `runQuery` that JOINS a parent\n * transaction (a `runQuery` called with a query/mutation `parentCtx`). The knobs\n * always use the PARENT's connection because the read connection is fixed to the\n * parent transaction's — which may be the PRIMARY or the REPLICA, depending on\n * how the parent query was routed.\n *\n * They MUST be inert: a nested query shares the parent's snapshot + readSet, so\n * calling `useConnection` here would either repoint the parent's connection\n * mid-flight (before its first read) or throw (after it) — either way corrupting\n * the parent transaction. A handler that needs to force primary therefore cannot\n * do so while nested under a replica-routed parent (it inherits the replica,\n * which is already safe w.r.t. the client's `minTs` floor); call it via an\n * action-parent `runQuery` instead, which opens a fresh routed read.\n */\nexport const parentConn: Pick<QueryCtx, \"usePrimaryConn\" | \"useReplicaConn\"> = {\n usePrimaryConn: () => { },\n useReplicaConn: () => { },\n};\n\n/**\n * Mutation context passed to mutation handlers.\n * Contains database writer and user-defined server context.\n */\nexport interface MutationCtx<TContext = unknown> {\n /** Database writer for mutations (includes insert/update/delete) */\n db: DbWriter;\n /** User-defined server context (auth, requestId, etc.) */\n serverCtx?: TContext;\n /**\n * No-op on a mutation — writes (and their reads) always run on the primary.\n * Present so a `MutationCtx` stays structurally assignable to `QueryCtx`, i.e.\n * a mutation can reuse a `(ctx: QueryCtx) => …` read helper.\n */\n usePrimaryConn(opts?: ConnOpts): void;\n /** No-op on a mutation (always primary). See {@link usePrimaryConn}. */\n useReplicaConn(opts?: ConnOpts): void;\n}\n\n/**\n * Action context passed to action handlers.\n *\n * Actions are the \"external I/O\" tier: they hold the full {@link SupaliveDb}\n * (queries + mutations) and are the ONLY context that also carries side-effect\n * services — {@link ObjectStorage} for uploads/downloads and a {@link JobClient}\n * for scheduling precise one-shots. Queries/mutations stay pure (db only) so\n * they remain cacheable and transaction-scoped.\n */\nexport interface ActionCtx<TContext = unknown> {\n /** Full database interface for queries and mutations */\n db: SupaliveDb;\n /** Object storage, when the server was configured with one (see\n * `SupaliveServerConfig.storage`); `undefined` otherwise. The usual home\n * for upload/download presigning. */\n storage: ObjectStorage;\n /** Job scheduler client for enqueueing precise one-shots, when the server\n * was configured with a `scheduler`; `undefined` otherwise. Scheduling is\n * network I/O, so it belongs in actions, never inside a DB transaction. */\n scheduler: JobClient;\n /** User-defined server context (auth, requestId, etc.) */\n serverCtx?: TContext;\n}\n\n/**\n * Job context passed to job handlers. Structurally identical to\n * {@link ActionCtx}: a job is a server-only procedure that runs\n * non-transactionally against the full {@link SupaliveDb} and may perform\n * external work, but it is triggered by the scheduler over HTTP (a cron tick\n * or a precise one-shot) rather than by a connected client. `serverCtx` is\n * the system context built by the server's `jobContext` factory.\n */\nexport interface JobCtx<TContext = unknown> {\n /** Full database interface for queries and mutations */\n db: SupaliveDb;\n /** System server context built by the server for scheduler-triggered runs */\n serverCtx?: TContext;\n}\n\n/**\n * Context handed to `caller.<proc>.runQuery` / `.runMutation` when invoking one\n * procedure from inside another. Pass the caller handler's own `ctx` — its `db`\n * carries the parent's live transaction (a {@link DbReader}/{@link DbWriter}) or,\n * inside an action, the full {@link SupaliveDb}. The caller uses this to decide\n * whether the nested call joins the parent's snapshot (queries) or runs as an\n * independent sub-transaction (mutations).\n */\nexport interface ParentCtx<TContext = unknown> {\n db: DbReader | DbWriter | SupaliveDb;\n serverCtx?: TContext | undefined;\n}\n\n// ─── Procedure Types ─────────────────────────────────────────────────────────\n\nexport type QueryFn<TInput, TResult, TContext = unknown> = (\n ctx: QueryCtx<TContext>,\n input: TInput,\n) => Promise<TResult>;\n\nexport type MutationFn<TInput, TResult, TContext = unknown> = (\n ctx: MutationCtx<TContext>,\n input: TInput,\n) => Promise<TResult>;\n\nexport type ActionFn<TInput, TResult, TContext = unknown> = (\n ctx: ActionCtx<TContext>,\n input: TInput,\n) => Promise<TResult>;\n\nexport type JobFn<TInput, TResult, TContext = unknown> = (\n ctx: JobCtx<TContext>,\n input: TInput,\n) => Promise<TResult>;\n\n/**\n * Per-procedure override for the cache/subscription segmentation key.\n *\n * omitted | undefined → fall back to `config.getUserId(serverCtx)` (default)\n * false → no identity in hash; cache/sub shared across all users\n * string → a static identity literal (e.g. \"public\" or a tenant id)\n * function → compute from serverCtx + input (sync)\n *\n * When the result of a query is identical regardless of who calls it, set\n * this to a literal (or `false`) so a single cache entry serves everyone.\n *\n * The function form intentionally takes `serverCtx` (not the full `QueryCtx`)\n * because identity is resolved before any DB read is issued.\n */\nexport type QueryIdentitySpec<TInput, TContext> =\n | false\n | string\n | ((serverCtx: TContext, input: TInput) => string | null | undefined);\n\nexport interface BaseProcedure<\n TInput, TResult,\n TType extends \"query\" | \"mutation\" | \"action\" | \"job\",\n TContext = unknown,\n TInternal extends boolean = boolean\n> {\n readonly _type: \"procedure\";\n readonly procedureType: TType;\n readonly inputSchema: ZodType<TInput>;\n readonly outputSchema?: ZodType<any>;\n readonly fn: QueryFn<TInput, TResult, TContext> | MutationFn<TInput, TResult, TContext> | ActionFn<TInput, TResult, TContext> | JobFn<TInput, TResult, TContext>;\n readonly internal: TInternal;\n}\n\nexport interface QueryProcedure<\n TInput, TResult,\n TContext = unknown,\n TInternal extends boolean = boolean\n> extends BaseProcedure<TInput, TResult, \"query\", TContext, TInternal> {\n readonly procedureType: \"query\";\n readonly fn: QueryFn<TInput, TResult, TContext>;\n readonly queryIdentity?: QueryIdentitySpec<TInput, TContext>;\n}\n\nexport interface MutationProcedure<\n TInput, TResult,\n TContext = unknown,\n TInternal extends boolean = boolean\n> extends BaseProcedure<TInput, TResult, \"mutation\", TContext, TInternal> {\n readonly procedureType: \"mutation\";\n readonly fn: MutationFn<TInput, TResult, TContext>;\n}\n\nexport interface ActionProcedure<\n TInput, TResult,\n TContext = unknown,\n TInternal extends boolean = boolean\n> extends BaseProcedure<TInput, TResult, \"action\", TContext, TInternal> {\n readonly procedureType: \"action\";\n readonly fn: ActionFn<TInput, TResult, TContext>;\n}\n\n/**\n * A scheduler-triggered, server-only procedure. Runs like an action (full\n * db, non-transactional, may do external work) but is dispatched by the\n * server's HTTP job endpoint on a cron tick or a precise one-shot rather\n * than over the client WebSocket. Always {@link internal}: true, so it is\n * never reachable via `call`/`subscribe`.\n */\nexport interface JobProcedure<\n TInput, TResult,\n TContext = unknown,\n> extends BaseProcedure<TInput, TResult, \"job\", TContext, true> {\n readonly procedureType: \"job\";\n readonly fn: JobFn<TInput, TResult, TContext>;\n /**\n * Cron expression for a recurring job (e.g. `\"0 3 * * *\"`). Declared crons\n * are synced to the scheduler at server startup. Omit for a job that is\n * only ever invoked as a precise one-shot via the scheduler API.\n */\n readonly cron?: string;\n}\n\nexport type AnyProcedure<TContext = unknown> =\n | QueryProcedure<any, any, TContext>\n | MutationProcedure<any, any, TContext>\n | ActionProcedure<any, any, TContext>\n | JobProcedure<any, any, TContext>;\n\n// ─── Procedure Configuration Types ───────────────────────────────────────────\n\nexport interface QueryConfig<TInput, TResult, TContext = unknown> {\n /** Zod schema for input validation */\n args: ZodType<TInput>;\n /** The query handler function */\n handler: QueryFn<TInput, TResult, TContext>;\n /** Mark as internal (server-only). Default: false */\n internal?: boolean;\n /**\n * Optional Zod schema describing return-type overrides for codegen.\n * Only the fields you specify are overridden; everything else is inferred\n * from the handler's TypeScript return type. Use `.modelName(\"Name\")`\n * on a Zod object to rename the generated model class.\n */\n returns?: ZodType<any>;\n /**\n * Override the cache/subscription segmentation key for this procedure.\n * See {@link QueryIdentitySpec}. Omit to keep the default (per-user) behavior.\n */\n queryIdentity?: QueryIdentitySpec<TInput, TContext>;\n}\n\nexport interface MutationConfig<TInput, TResult, TContext = unknown> {\n /** Zod schema for input validation */\n args: ZodType<TInput>;\n /** The mutation handler function */\n handler: MutationFn<TInput, TResult, TContext>;\n /** Mark as internal (server-only). Default: false */\n internal?: boolean;\n /**\n * Optional Zod schema describing return-type overrides for codegen.\n * Only the fields you specify are overridden; everything else is inferred\n * from the handler's TypeScript return type.\n */\n returns?: ZodType<any>;\n}\n\nexport interface ActionConfig<TInput, TResult, TContext = unknown> {\n /** Zod schema for input validation */\n args: ZodType<TInput>;\n /** The action handler function */\n handler: ActionFn<TInput, TResult, TContext>;\n /** Mark as internal (server-only). Default: false */\n internal?: boolean;\n /**\n * Optional Zod schema describing return-type overrides for codegen.\n * Only the fields you specify are overridden; everything else is inferred\n * from the handler's TypeScript return type.\n */\n returns?: ZodType<any>;\n}\n\nexport interface JobConfig<TInput, TResult, TContext = unknown> {\n /** Zod schema for the job payload */\n args: ZodType<TInput>;\n /** The job handler function */\n handler: JobFn<TInput, TResult, TContext>;\n /**\n * Cron expression for a recurring schedule (e.g. `\"0 3 * * *\"`). Omit for a\n * job that is only invoked as a precise one-shot.\n */\n cron?: string;\n}\n\n/** Extract input type from a procedure */\nexport type InputOf<T> = T extends BaseProcedure<infer I, any, any, any> ? I : never;\n\n/** Extract output type from a procedure */\nexport type OutputOf<T> = T extends BaseProcedure<any, infer O, any, any> ? O : never;\n\n/** Extract procedure type (query/mutation) */\nexport type TypeOf<T> = T extends BaseProcedure<any, any, infer Type, any> ? Type : never;\n\n/** Extract server context type from a procedure */\nexport type ContextOf<T> = T extends BaseProcedure<any, any, any, infer C> ? C : never;\n\n/**\n * Create a query builder with a pre-defined context type.\n * This allows you to define the context type once and have it inferred\n * automatically in all your query handlers.\n * \n * @example\n * // Define your server context\n * interface ServerContext {\n * auth: { userId: string };\n * requestId: string;\n * }\n * \n * // Create a typed query builder\n * const query = createQueryBuilder<ServerContext>();\n * \n * // Use it - context type is automatically inferred!\n * const getUser = query({\n * args: z.object({ id: z.string() }),\n * handler: async (ctx, { id }) => {\n * // ctx.db for database queries\n * const user = await ctx.db.query(UsersSchema).find(id);\n * // ctx.context for server context\n * console.log(ctx.context.requestId);\n * return user;\n * }\n * });\n */\nexport function createQueryBuilder<TContext = unknown>() {\n return function query<TInput, TResult, const TInternal extends boolean = false>(\n config: QueryConfig<TInput, TResult, TContext> & { internal?: TInternal }\n ): QueryProcedure<TInput, TResult, TContext, TInternal> {\n return {\n _type: \"procedure\",\n procedureType: \"query\",\n inputSchema: config.args,\n outputSchema: config.returns,\n fn: config.handler,\n internal: (config.internal ?? false) as TInternal,\n queryIdentity: config.queryIdentity,\n };\n };\n}\n\n// ─── Mutation Builder Factory ────────────────────────────────────────────────\n\n/**\n * Create a mutation builder with a pre-defined context type.\n * This allows you to define the context type once and have it inferred\n * automatically in all your mutation handlers.\n * \n * @example\n * // Define your server context\n * interface ServerContext {\n * auth: { userId: string };\n * requestId: string;\n * }\n * \n * // Create a typed mutation builder\n * const mutation = createMutationBuilder<ServerContext>();\n * \n * // Use it - context type is automatically inferred!\n * const createUser = mutation({\n * args: z.object({ name: z.string() }),\n * handler: async (ctx, { name }) => {\n * // ctx.db for mutations\n * await ctx.db.insert(UsersSchema, id, { name });\n * // ctx.context for server context\n * console.log(ctx.context.requestId);\n * return { success: true };\n * }\n * });\n */\nexport function createMutationBuilder<TContext = unknown>() {\n return function mutation<TInput, TResult, const TInternal extends boolean = false>(\n config: MutationConfig<TInput, TResult, TContext> & { internal?: TInternal }\n ): MutationProcedure<TInput, TResult, TContext, TInternal> {\n return {\n _type: \"procedure\",\n procedureType: \"mutation\",\n inputSchema: config.args,\n outputSchema: config.returns,\n fn: config.handler,\n internal: (config.internal ?? false) as TInternal,\n };\n };\n}\n\n// ─── Action Builder Factory ──────────────────────────────────────────────────\n\n/**\n * Create an action builder with a pre-defined context type.\n * Actions have access to the full SupaliveDb for both queries and mutations.\n *\n * @example\n * // Define your server context\n * interface ServerContext {\n * auth: { userId: string };\n * requestId: string;\n * }\n *\n * // Create a typed action builder\n * const action = createActionBuilder<ServerContext>();\n *\n * // Use it - context type is automatically inferred!\n * const processOrder = action({\n * args: z.object({ orderId: z.string() }),\n * handler: async (ctx, { orderId }) => {\n * // ctx.db for full database access\n * const order = await ctx.db.query(async (db) => {\n * return db.query(OrdersSchema).find(orderId);\n * });\n * await ctx.db.mutation(async (db) => {\n * await db.update(OrdersSchema, orderId, { status: \"processed\" });\n * });\n * // ctx.context for server context\n * console.log(ctx.context.requestId);\n * return { success: true };\n * }\n * });\n */\nexport function createActionBuilder<TContext = unknown>() {\n return function action<TInput, TResult, const TInternal extends boolean = false>(\n config: ActionConfig<TInput, TResult, TContext> & { internal?: TInternal }\n ): ActionProcedure<TInput, TResult, TContext, TInternal> {\n return {\n _type: \"procedure\",\n procedureType: \"action\",\n inputSchema: config.args,\n outputSchema: config.returns,\n fn: config.handler,\n internal: (config.internal ?? false) as TInternal,\n };\n };\n}\n\n// ─── Job Builder Factory ─────────────────────────────────────────────────────\n\n/**\n * Create a job builder with a pre-defined context type. A job is a\n * server-only procedure invoked by the scheduler over HTTP — either on its\n * declared `cron` schedule or as a precise one-shot enqueued via the\n * server's job API. Jobs run like actions (full db, non-transactional) and\n * are always internal, so they are never reachable from a client.\n *\n * @example\n * const job = createJobBuilder<ServerContext>();\n *\n * export const cleanupOtps = job({\n * cron: \"0 * * * *\", // hourly\n * args: z.object({}),\n * handler: async (ctx) => {\n * await ctx.db.mutation(async (db) => { ... });\n * return { ok: true };\n * },\n * });\n */\nexport function createJobBuilder<TContext = unknown>() {\n return function job<TInput, TResult>(\n config: JobConfig<TInput, TResult, TContext>\n ): JobProcedure<TInput, TResult, TContext> {\n return {\n _type: \"procedure\",\n procedureType: \"job\",\n inputSchema: config.args,\n fn: config.handler,\n internal: true,\n cron: config.cron,\n };\n };\n}","import type { AnyProcedure } from \"./procedure\";\n\n// ─── Router Types ────────────────────────────────────────────────────────────\n\n/**\n * A router maps procedure names (keys) to their definitions.\n * This is the type that's exported from your app and used by the client.\n */\nexport type Router<TProcedures extends Record<string, AnyProcedure<TContext>>, TContext = unknown> = {\n _type: \"router\";\n procedures: TProcedures;\n contextName: string;\n};\n\n/**\n * Inferred AppRouter type from router() call.\n * Captures the full procedure map for client type inference.\n */\nexport type AppRouter<\n TProcedures extends Record<string, AnyProcedure<TContext>> = Record<string, AnyProcedure>,\n TContext = unknown\n> = Router<TProcedures, TContext>;\n\n// ─── Type Helpers for Client ─────────────────────────────────────────────────\n\n/** Get query procedures only from a router */\nexport type QueryProcedures<T extends Record<string, AnyProcedure>> = {\n [K in keyof T as T[K] extends { procedureType: \"query\" } ? K : never]: T[K];\n};\n\n/** Get mutation procedures only from a router */\nexport type MutationProcedures<T extends Record<string, AnyProcedure>> = {\n [K in keyof T as T[K] extends { procedureType: \"mutation\" } ? K : never]: T[K];\n};\n\n/** Get action procedures only from a router */\nexport type ActionProcedures<T extends Record<string, AnyProcedure>> = {\n [K in keyof T as T[K] extends { procedureType: \"action\" } ? K : never]: T[K];\n};\n\n/** Get job procedures only from a router */\nexport type JobProcedures<T extends Record<string, AnyProcedure>> = {\n [K in keyof T as T[K] extends { procedureType: \"job\" } ? K : never]: T[K];\n};\n\n/** Get public (non-internal) procedures - for client type safety */\nexport type PublicProcedures<T extends Record<string, AnyProcedure>> = Pick<\n T,\n { [K in keyof T]: T[K] extends { internal: true } ? never : K }[keyof T]\n>;\n\n/** Extract procedure names from a router */\nexport type ProcedureNames<T extends Router<any>> =\n T extends Router<infer P> ? keyof P : never;\n\nexport type RouterConfig<T> = {\n procedures: T;\n contextName?: string;\n};\n\n/**\n* Create a router from procedure definitions.\n* Procedure names are inferred from the object keys.\n* \n* @example\n* const appRouter = router({\n* procedures: {\n* getUser,\n* createUser,\n* internalGetAll,\n* },\n* });\n* \n* export type AppRouter = typeof appRouter;\n*/\nexport function router<TProcedures extends Record<string, AnyProcedure<any>>, TContext = unknown>(\n config: RouterConfig<TProcedures>,\n): Router<TProcedures, TContext> {\n const registry = getContextRegistry(config.contextName ?? \"default\");\n\n // Register all procedures at router creation time\n for (const [name, proc] of Object.entries(config.procedures)) {\n registry.registerProcedure(name, proc);\n }\n\n\n return {\n _type: \"router\",\n procedures: config.procedures,\n contextName: config.contextName ?? \"default\",\n };\n}\n\nexport interface RegisteredProcedure<TContext = unknown> {\n name: string;\n type: \"query\" | \"mutation\" | \"action\" | \"job\";\n internal: boolean;\n inputSchema: AnyProcedure<TContext>[\"inputSchema\"];\n fn: AnyProcedure<TContext>[\"fn\"];\n /** Only present for `type === \"query\"`. See `QueryIdentitySpec`. */\n queryIdentity?: false | string | ((serverCtx: TContext | undefined, input: unknown) => string | null | undefined);\n /** Only present for `type === \"job\"`. Cron expression for recurring jobs. */\n cron?: string;\n}\n\n\nconst contextRegistry = new Map<string, ContextRegistry>();\n\nexport function getContextRegistry(inContext: string): ContextRegistry {\n let registry = contextRegistry.get(inContext);\n if (!registry) {\n registry = new ContextRegistry();\n contextRegistry.set(inContext, registry);\n }\n return registry;\n}\n\nexport class ContextRegistry {\n procedureRegistry = new Map<string, RegisteredProcedure<any>>();\n internalProcedureNames = new Set<string>();\n\n registerProcedure<TContext>(name: string, proc: AnyProcedure<TContext>): void {\n if (this.procedureRegistry.has(name)) {\n throw new Error(\n `[router] Procedure \"${name}\" is already registered. ` +\n `Each procedure name must be unique across the application.`\n );\n }\n\n // Track internal procedures by name\n if (proc.internal) {\n this.markInternalProcedure(name);\n }\n\n const queryIdentity =\n proc.procedureType === \"query\"\n ? (proc as { queryIdentity?: RegisteredProcedure<TContext>[\"queryIdentity\"] }).queryIdentity\n : undefined;\n\n const cron =\n proc.procedureType === \"job\"\n ? (proc as { cron?: string }).cron\n : undefined;\n\n this.procedureRegistry.set(name, {\n name,\n type: proc.procedureType,\n internal: proc.internal,\n inputSchema: proc.inputSchema,\n fn: proc.fn,\n queryIdentity,\n cron,\n });\n }\n\n /** Get a registered procedure by name (runtime lookup) */\n getProcedure<TContext = unknown>(name: string): RegisteredProcedure<TContext> | undefined {\n return this.procedureRegistry.get(name);\n }\n\n /** Get all registered procedure names */\n getProcedureNames(): string[] {\n return [...this.procedureRegistry.keys()];\n }\n\n /** Get all registered procedures */\n getAllProcedures(): Map<string, RegisteredProcedure> {\n return new Map(this.procedureRegistry);\n }\n\n /** Get all registered job procedures (server-only, scheduler-triggered). */\n getJobProcedures(): RegisteredProcedure[] {\n return [...this.procedureRegistry.values()].filter((p) => p.type === \"job\");\n }\n\n /** Clear the registry (for testing only) */\n clearRegistry(): void {\n this.procedureRegistry.clear();\n this.clearInternalProcedures();\n }\n\n markInternalProcedure(name: string): void {\n this.internalProcedureNames.add(name);\n }\n\n isInternalProcedure(name: string): boolean {\n return this.internalProcedureNames.has(name);\n }\n\n clearInternalProcedures(): void {\n this.internalProcedureNames.clear();\n }\n}"],"mappings":"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"}
@@ -0,0 +1,337 @@
1
+ import { z as z$1 } from "zod";
2
+ //#region src/router/procedure.ts
3
+ /**
4
+ * Patch all Zod schema types on the given `z` namespace with `.modelName(name)`.
5
+ * Must be called with the same `z` instance the consumer uses so the prototypes
6
+ * match. Runs once per unique `z` instance (tracked via a WeakSet).
7
+ */
8
+ function patchZod(zInstance) {
9
+ const key = "__supalivePatchedZodInstances";
10
+ const patchedInstances = globalThis[key] ??= /* @__PURE__ */ new Set();
11
+ if (patchedInstances.has(zInstance)) return;
12
+ patchedInstances.add(zInstance);
13
+ const patched = /* @__PURE__ */ new Set();
14
+ const samples = [
15
+ zInstance.string(),
16
+ zInstance.number(),
17
+ zInstance.boolean(),
18
+ zInstance.bigint(),
19
+ zInstance.date(),
20
+ zInstance.object({}),
21
+ zInstance.array(zInstance.string()),
22
+ zInstance.enum(["_"]),
23
+ zInstance.literal("_"),
24
+ zInstance.null(),
25
+ zInstance.undefined(),
26
+ zInstance.any(),
27
+ zInstance.unknown(),
28
+ zInstance.void(),
29
+ zInstance.never(),
30
+ zInstance.nan(),
31
+ zInstance.record(zInstance.string(), zInstance.string()),
32
+ zInstance.union([zInstance.string(), zInstance.number()]),
33
+ zInstance.intersection(zInstance.string(), zInstance.string()),
34
+ zInstance.tuple([zInstance.string()]),
35
+ zInstance.promise(zInstance.string()),
36
+ zInstance.custom(),
37
+ zInstance.coerce.string(),
38
+ zInstance.coerce.number(),
39
+ zInstance.coerce.boolean(),
40
+ zInstance.coerce.bigint(),
41
+ zInstance.coerce.date(),
42
+ zInstance.string().optional(),
43
+ zInstance.string().nullable(),
44
+ zInstance.string().default(""),
45
+ zInstance.string().pipe(zInstance.string()),
46
+ zInstance.string().transform(() => "")
47
+ ];
48
+ for (const inst of samples) {
49
+ const proto = Object.getPrototypeOf(inst);
50
+ if (patched.has(proto)) continue;
51
+ patched.add(proto);
52
+ const metaDesc = Object.getOwnPropertyDescriptor(proto, "meta");
53
+ if (!metaDesc || !metaDesc.get) continue;
54
+ if (proto.modelName) continue;
55
+ Object.defineProperty(proto, "modelName", {
56
+ value: function modelName(name) {
57
+ return this.meta({ __genType: name });
58
+ },
59
+ writable: true,
60
+ configurable: true
61
+ });
62
+ }
63
+ }
64
+ patchZod(z$1);
65
+ /**
66
+ * No-op connection routing for query paths that genuinely ALWAYS run on the
67
+ * primary — live subscription re-execution and mutation handlers. Spread into
68
+ * the ctx so `ctx.usePrimaryConn()`/`useReplicaConn()` are always callable but
69
+ * inert (there is only ever the primary connection to bind).
70
+ *
71
+ * For a nested `runQuery` that JOINS a parent transaction, use {@link parentConn}
72
+ * instead — there the connection isn't necessarily the primary.
73
+ */
74
+ const primaryOnlyConn = {
75
+ usePrimaryConn: () => {},
76
+ useReplicaConn: () => {}
77
+ };
78
+ /**
79
+ * No-op connection routing for a nested `runQuery` that JOINS a parent
80
+ * transaction (a `runQuery` called with a query/mutation `parentCtx`). The knobs
81
+ * always use the PARENT's connection because the read connection is fixed to the
82
+ * parent transaction's — which may be the PRIMARY or the REPLICA, depending on
83
+ * how the parent query was routed.
84
+ *
85
+ * They MUST be inert: a nested query shares the parent's snapshot + readSet, so
86
+ * calling `useConnection` here would either repoint the parent's connection
87
+ * mid-flight (before its first read) or throw (after it) — either way corrupting
88
+ * the parent transaction. A handler that needs to force primary therefore cannot
89
+ * do so while nested under a replica-routed parent (it inherits the replica,
90
+ * which is already safe w.r.t. the client's `minTs` floor); call it via an
91
+ * action-parent `runQuery` instead, which opens a fresh routed read.
92
+ */
93
+ const parentConn = {
94
+ usePrimaryConn: () => {},
95
+ useReplicaConn: () => {}
96
+ };
97
+ /**
98
+ * Create a query builder with a pre-defined context type.
99
+ * This allows you to define the context type once and have it inferred
100
+ * automatically in all your query handlers.
101
+ *
102
+ * @example
103
+ * // Define your server context
104
+ * interface ServerContext {
105
+ * auth: { userId: string };
106
+ * requestId: string;
107
+ * }
108
+ *
109
+ * // Create a typed query builder
110
+ * const query = createQueryBuilder<ServerContext>();
111
+ *
112
+ * // Use it - context type is automatically inferred!
113
+ * const getUser = query({
114
+ * args: z.object({ id: z.string() }),
115
+ * handler: async (ctx, { id }) => {
116
+ * // ctx.db for database queries
117
+ * const user = await ctx.db.query(UsersSchema).find(id);
118
+ * // ctx.context for server context
119
+ * console.log(ctx.context.requestId);
120
+ * return user;
121
+ * }
122
+ * });
123
+ */
124
+ function createQueryBuilder() {
125
+ return function query(config) {
126
+ return {
127
+ _type: "procedure",
128
+ procedureType: "query",
129
+ inputSchema: config.args,
130
+ outputSchema: config.returns,
131
+ fn: config.handler,
132
+ internal: config.internal ?? false,
133
+ queryIdentity: config.queryIdentity
134
+ };
135
+ };
136
+ }
137
+ /**
138
+ * Create a mutation builder with a pre-defined context type.
139
+ * This allows you to define the context type once and have it inferred
140
+ * automatically in all your mutation handlers.
141
+ *
142
+ * @example
143
+ * // Define your server context
144
+ * interface ServerContext {
145
+ * auth: { userId: string };
146
+ * requestId: string;
147
+ * }
148
+ *
149
+ * // Create a typed mutation builder
150
+ * const mutation = createMutationBuilder<ServerContext>();
151
+ *
152
+ * // Use it - context type is automatically inferred!
153
+ * const createUser = mutation({
154
+ * args: z.object({ name: z.string() }),
155
+ * handler: async (ctx, { name }) => {
156
+ * // ctx.db for mutations
157
+ * await ctx.db.insert(UsersSchema, id, { name });
158
+ * // ctx.context for server context
159
+ * console.log(ctx.context.requestId);
160
+ * return { success: true };
161
+ * }
162
+ * });
163
+ */
164
+ function createMutationBuilder() {
165
+ return function mutation(config) {
166
+ return {
167
+ _type: "procedure",
168
+ procedureType: "mutation",
169
+ inputSchema: config.args,
170
+ outputSchema: config.returns,
171
+ fn: config.handler,
172
+ internal: config.internal ?? false
173
+ };
174
+ };
175
+ }
176
+ /**
177
+ * Create an action builder with a pre-defined context type.
178
+ * Actions have access to the full SupaliveDb for both queries and mutations.
179
+ *
180
+ * @example
181
+ * // Define your server context
182
+ * interface ServerContext {
183
+ * auth: { userId: string };
184
+ * requestId: string;
185
+ * }
186
+ *
187
+ * // Create a typed action builder
188
+ * const action = createActionBuilder<ServerContext>();
189
+ *
190
+ * // Use it - context type is automatically inferred!
191
+ * const processOrder = action({
192
+ * args: z.object({ orderId: z.string() }),
193
+ * handler: async (ctx, { orderId }) => {
194
+ * // ctx.db for full database access
195
+ * const order = await ctx.db.query(async (db) => {
196
+ * return db.query(OrdersSchema).find(orderId);
197
+ * });
198
+ * await ctx.db.mutation(async (db) => {
199
+ * await db.update(OrdersSchema, orderId, { status: "processed" });
200
+ * });
201
+ * // ctx.context for server context
202
+ * console.log(ctx.context.requestId);
203
+ * return { success: true };
204
+ * }
205
+ * });
206
+ */
207
+ function createActionBuilder() {
208
+ return function action(config) {
209
+ return {
210
+ _type: "procedure",
211
+ procedureType: "action",
212
+ inputSchema: config.args,
213
+ outputSchema: config.returns,
214
+ fn: config.handler,
215
+ internal: config.internal ?? false
216
+ };
217
+ };
218
+ }
219
+ /**
220
+ * Create a job builder with a pre-defined context type. A job is a
221
+ * server-only procedure invoked by the scheduler over HTTP — either on its
222
+ * declared `cron` schedule or as a precise one-shot enqueued via the
223
+ * server's job API. Jobs run like actions (full db, non-transactional) and
224
+ * are always internal, so they are never reachable from a client.
225
+ *
226
+ * @example
227
+ * const job = createJobBuilder<ServerContext>();
228
+ *
229
+ * export const cleanupOtps = job({
230
+ * cron: "0 * * * *", // hourly
231
+ * args: z.object({}),
232
+ * handler: async (ctx) => {
233
+ * await ctx.db.mutation(async (db) => { ... });
234
+ * return { ok: true };
235
+ * },
236
+ * });
237
+ */
238
+ function createJobBuilder() {
239
+ return function job(config) {
240
+ return {
241
+ _type: "procedure",
242
+ procedureType: "job",
243
+ inputSchema: config.args,
244
+ fn: config.handler,
245
+ internal: true,
246
+ cron: config.cron
247
+ };
248
+ };
249
+ }
250
+ //#endregion
251
+ //#region src/router/router.ts
252
+ /**
253
+ * Create a router from procedure definitions.
254
+ * Procedure names are inferred from the object keys.
255
+ *
256
+ * @example
257
+ * const appRouter = router({
258
+ * procedures: {
259
+ * getUser,
260
+ * createUser,
261
+ * internalGetAll,
262
+ * },
263
+ * });
264
+ *
265
+ * export type AppRouter = typeof appRouter;
266
+ */
267
+ function router(config) {
268
+ const registry = getContextRegistry(config.contextName ?? "default");
269
+ for (const [name, proc] of Object.entries(config.procedures)) registry.registerProcedure(name, proc);
270
+ return {
271
+ _type: "router",
272
+ procedures: config.procedures,
273
+ contextName: config.contextName ?? "default"
274
+ };
275
+ }
276
+ const contextRegistry = /* @__PURE__ */ new Map();
277
+ function getContextRegistry(inContext) {
278
+ let registry = contextRegistry.get(inContext);
279
+ if (!registry) {
280
+ registry = new ContextRegistry();
281
+ contextRegistry.set(inContext, registry);
282
+ }
283
+ return registry;
284
+ }
285
+ var ContextRegistry = class {
286
+ procedureRegistry = /* @__PURE__ */ new Map();
287
+ internalProcedureNames = /* @__PURE__ */ new Set();
288
+ registerProcedure(name, proc) {
289
+ if (this.procedureRegistry.has(name)) throw new Error(`[router] Procedure "${name}" is already registered. Each procedure name must be unique across the application.`);
290
+ if (proc.internal) this.markInternalProcedure(name);
291
+ const queryIdentity = proc.procedureType === "query" ? proc.queryIdentity : void 0;
292
+ const cron = proc.procedureType === "job" ? proc.cron : void 0;
293
+ this.procedureRegistry.set(name, {
294
+ name,
295
+ type: proc.procedureType,
296
+ internal: proc.internal,
297
+ inputSchema: proc.inputSchema,
298
+ fn: proc.fn,
299
+ queryIdentity,
300
+ cron
301
+ });
302
+ }
303
+ /** Get a registered procedure by name (runtime lookup) */
304
+ getProcedure(name) {
305
+ return this.procedureRegistry.get(name);
306
+ }
307
+ /** Get all registered procedure names */
308
+ getProcedureNames() {
309
+ return [...this.procedureRegistry.keys()];
310
+ }
311
+ /** Get all registered procedures */
312
+ getAllProcedures() {
313
+ return new Map(this.procedureRegistry);
314
+ }
315
+ /** Get all registered job procedures (server-only, scheduler-triggered). */
316
+ getJobProcedures() {
317
+ return [...this.procedureRegistry.values()].filter((p) => p.type === "job");
318
+ }
319
+ /** Clear the registry (for testing only) */
320
+ clearRegistry() {
321
+ this.procedureRegistry.clear();
322
+ this.clearInternalProcedures();
323
+ }
324
+ markInternalProcedure(name) {
325
+ this.internalProcedureNames.add(name);
326
+ }
327
+ isInternalProcedure(name) {
328
+ return this.internalProcedureNames.has(name);
329
+ }
330
+ clearInternalProcedures() {
331
+ this.internalProcedureNames.clear();
332
+ }
333
+ };
334
+ //#endregion
335
+ export { createJobBuilder as a, parentConn as c, createActionBuilder as i, primaryOnlyConn as l, getContextRegistry as n, createMutationBuilder as o, router as r, createQueryBuilder as s, ContextRegistry as t };
336
+
337
+ //# sourceMappingURL=router-Dfnwka7K.js.map