@supalive/core 1.15.0 → 1.17.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 (150) hide show
  1. package/dist/index-B2nE1y9E.d.ts +2384 -0
  2. package/dist/index-B2nE1y9E.d.ts.map +1 -0
  3. package/dist/index-B4UxN1ZL.d.ts +2365 -0
  4. package/dist/index-B4UxN1ZL.d.ts.map +1 -0
  5. package/dist/index-B5hAqW3O.d.ts +2402 -0
  6. package/dist/index-B5hAqW3O.d.ts.map +1 -0
  7. package/dist/index-B8JFAyw4.d.ts +2377 -0
  8. package/dist/index-B8JFAyw4.d.ts.map +1 -0
  9. package/dist/index-BI3MfAyI.d.ts +2399 -0
  10. package/dist/index-BI3MfAyI.d.ts.map +1 -0
  11. package/dist/index-BL90lDw7.d.ts +2400 -0
  12. package/dist/index-BL90lDw7.d.ts.map +1 -0
  13. package/dist/index-BvryqYUm.d.ts +2394 -0
  14. package/dist/index-BvryqYUm.d.ts.map +1 -0
  15. package/dist/index-CfcrWLLm.d.ts +2377 -0
  16. package/dist/index-CfcrWLLm.d.ts.map +1 -0
  17. package/dist/index-DYkOBnw_.d.ts +2378 -0
  18. package/dist/index-DYkOBnw_.d.ts.map +1 -0
  19. package/dist/index-DcM2uKpT.d.ts +2393 -0
  20. package/dist/index-DcM2uKpT.d.ts.map +1 -0
  21. package/dist/index-DsvdhOKL.d.ts +2400 -0
  22. package/dist/index-DsvdhOKL.d.ts.map +1 -0
  23. package/dist/index-ggi1DrSG.d.ts +2336 -0
  24. package/dist/index-ggi1DrSG.d.ts.map +1 -0
  25. package/dist/mysql-07ROfbBZ.d.ts +114 -0
  26. package/dist/mysql-07ROfbBZ.d.ts.map +1 -0
  27. package/dist/mysql-BJaQhVGE.d.ts +114 -0
  28. package/dist/mysql-BJaQhVGE.d.ts.map +1 -0
  29. package/dist/mysql-BKs5PTSR.d.ts +114 -0
  30. package/dist/mysql-BKs5PTSR.d.ts.map +1 -0
  31. package/dist/mysql-BTigT6mP.d.ts +114 -0
  32. package/dist/mysql-BTigT6mP.d.ts.map +1 -0
  33. package/dist/mysql-C7Km_lgc.d.ts +114 -0
  34. package/dist/mysql-C7Km_lgc.d.ts.map +1 -0
  35. package/dist/mysql-CXymz1Eu.d.ts +114 -0
  36. package/dist/mysql-CXymz1Eu.d.ts.map +1 -0
  37. package/dist/mysql-ClUK7S2j.js +623 -0
  38. package/dist/mysql-ClUK7S2j.js.map +1 -0
  39. package/dist/mysql-CtpWqOhO.d.ts +114 -0
  40. package/dist/mysql-CtpWqOhO.d.ts.map +1 -0
  41. package/dist/mysql-Cxro4f6T.d.ts +114 -0
  42. package/dist/mysql-Cxro4f6T.d.ts.map +1 -0
  43. package/dist/mysql-DNiYIZCK.d.ts +114 -0
  44. package/dist/mysql-DNiYIZCK.d.ts.map +1 -0
  45. package/dist/mysql-DS1s1c5M.d.ts +114 -0
  46. package/dist/mysql-DS1s1c5M.d.ts.map +1 -0
  47. package/dist/mysql-fTKZ1zaF.js +623 -0
  48. package/dist/mysql-fTKZ1zaF.js.map +1 -0
  49. package/dist/mysql-qTszEKAU.d.ts +114 -0
  50. package/dist/mysql-qTszEKAU.d.ts.map +1 -0
  51. package/dist/mysql-qW13LcGQ.d.ts +114 -0
  52. package/dist/mysql-qW13LcGQ.d.ts.map +1 -0
  53. package/dist/overlap-checker-1TwhkAwy.js +259 -0
  54. package/dist/overlap-checker-1TwhkAwy.js.map +1 -0
  55. package/dist/overlap-checker-CGlxWcyb.js +259 -0
  56. package/dist/overlap-checker-CGlxWcyb.js.map +1 -0
  57. package/dist/postgres-B0fqJe1X.d.ts +118 -0
  58. package/dist/postgres-B0fqJe1X.d.ts.map +1 -0
  59. package/dist/postgres-B5-5vgLJ.d.ts +118 -0
  60. package/dist/postgres-B5-5vgLJ.d.ts.map +1 -0
  61. package/dist/postgres-BATDLPml.d.ts +118 -0
  62. package/dist/postgres-BATDLPml.d.ts.map +1 -0
  63. package/dist/postgres-BUVYjnaT.d.ts +118 -0
  64. package/dist/postgres-BUVYjnaT.d.ts.map +1 -0
  65. package/dist/postgres-Bk_8PFRT.d.ts +118 -0
  66. package/dist/postgres-Bk_8PFRT.d.ts.map +1 -0
  67. package/dist/postgres-Bx79_JEy.d.ts +118 -0
  68. package/dist/postgres-Bx79_JEy.d.ts.map +1 -0
  69. package/dist/postgres-C3w741VH.d.ts +118 -0
  70. package/dist/postgres-C3w741VH.d.ts.map +1 -0
  71. package/dist/postgres-CAZYYgbZ.d.ts +118 -0
  72. package/dist/postgres-CAZYYgbZ.d.ts.map +1 -0
  73. package/dist/postgres-CN03Espg.js +868 -0
  74. package/dist/postgres-CN03Espg.js.map +1 -0
  75. package/dist/postgres-CRPJdZx2.js +868 -0
  76. package/dist/postgres-CRPJdZx2.js.map +1 -0
  77. package/dist/postgres-DCXnKaJr.d.ts +118 -0
  78. package/dist/postgres-DCXnKaJr.d.ts.map +1 -0
  79. package/dist/postgres-DgCYPf0j.d.ts +118 -0
  80. package/dist/postgres-DgCYPf0j.d.ts.map +1 -0
  81. package/dist/postgres-Dnshux6k.d.ts +118 -0
  82. package/dist/postgres-Dnshux6k.d.ts.map +1 -0
  83. package/dist/postgres-bhVzeRf2.d.ts +118 -0
  84. package/dist/postgres-bhVzeRf2.d.ts.map +1 -0
  85. package/dist/query-LJranz0c.js +795 -0
  86. package/dist/query-LJranz0c.js.map +1 -0
  87. package/dist/query-q1ZvKrZO.js +762 -0
  88. package/dist/query-q1ZvKrZO.js.map +1 -0
  89. package/dist/realtime_db-B0Tin87E.js +312 -0
  90. package/dist/realtime_db-B0Tin87E.js.map +1 -0
  91. package/dist/realtime_db-DVhhasJj.js +312 -0
  92. package/dist/realtime_db-DVhhasJj.js.map +1 -0
  93. package/dist/router-Bk0eLfmB.js +285 -0
  94. package/dist/router-Bk0eLfmB.js.map +1 -0
  95. package/dist/router-DP2ThAwh.js.map +1 -1
  96. package/dist/src/client/index.d.ts +2 -2
  97. package/dist/src/client/index.js +95 -20
  98. package/dist/src/client/index.js.map +1 -1
  99. package/dist/src/exports/mysql.d.ts +1 -1
  100. package/dist/src/exports/mysql.js +1 -1
  101. package/dist/src/exports/postgres.d.ts +1 -1
  102. package/dist/src/exports/postgres.js +1 -1
  103. package/dist/src/exports/procedure.d.ts +2 -2
  104. package/dist/src/exports/procedure.js +1 -1
  105. package/dist/src/exports/schema-sql.d.ts +1 -1
  106. package/dist/src/exports/server.d.ts +13 -6
  107. package/dist/src/exports/server.d.ts.map +1 -1
  108. package/dist/src/exports/server.js +802 -252
  109. package/dist/src/exports/server.js.map +1 -1
  110. package/dist/src/exports/sub-manager-worker-entry.js +1 -1
  111. package/dist/src/exports/types.d.ts +2 -2
  112. package/dist/src/exports/types.js +1 -1
  113. package/dist/sub-worker-dispatch-D7jfw6ys.js +826 -0
  114. package/dist/sub-worker-dispatch-D7jfw6ys.js.map +1 -0
  115. package/dist/sub-worker-dispatch-qiSs5Xkm.js +826 -0
  116. package/dist/sub-worker-dispatch-qiSs5Xkm.js.map +1 -0
  117. package/dist/types_client_rpc-CWZmlWmM.js +145 -0
  118. package/dist/types_client_rpc-CWZmlWmM.js.map +1 -0
  119. package/dist/types_client_rpc-D4td5W0Y.js +141 -0
  120. package/dist/types_client_rpc-D4td5W0Y.js.map +1 -0
  121. package/dist/types_server-AXVa_2mF.d.ts +684 -0
  122. package/dist/types_server-AXVa_2mF.d.ts.map +1 -0
  123. package/dist/types_server-BhC19R2a.js.map +1 -1
  124. package/dist/types_server-BmIgDd8d.d.ts +614 -0
  125. package/dist/types_server-BmIgDd8d.d.ts.map +1 -0
  126. package/dist/types_server-C6B9_b1R.d.ts +662 -0
  127. package/dist/types_server-C6B9_b1R.d.ts.map +1 -0
  128. package/dist/types_server-CUPM08l2.d.ts +684 -0
  129. package/dist/types_server-CUPM08l2.d.ts.map +1 -0
  130. package/dist/types_server-CtYRmHdO.d.ts +684 -0
  131. package/dist/types_server-CtYRmHdO.d.ts.map +1 -0
  132. package/dist/types_server-DHRi8bGO.d.ts +684 -0
  133. package/dist/types_server-DHRi8bGO.d.ts.map +1 -0
  134. package/dist/types_server-DRr5yKy1.d.ts +655 -0
  135. package/dist/types_server-DRr5yKy1.d.ts.map +1 -0
  136. package/dist/types_server-DmCbve3w.d.ts +684 -0
  137. package/dist/types_server-DmCbve3w.d.ts.map +1 -0
  138. package/dist/types_server-DoeybfmY.d.ts +684 -0
  139. package/dist/types_server-DoeybfmY.d.ts.map +1 -0
  140. package/dist/types_server-Gs_vSVaS.d.ts +655 -0
  141. package/dist/types_server-Gs_vSVaS.d.ts.map +1 -0
  142. package/dist/types_server-ILUmPFvk.d.ts +614 -0
  143. package/dist/types_server-ILUmPFvk.d.ts.map +1 -0
  144. package/dist/types_server-JK14NE0j.d.ts +684 -0
  145. package/dist/types_server-JK14NE0j.d.ts.map +1 -0
  146. package/dist/types_server-hYv2--1M.d.ts +684 -0
  147. package/dist/types_server-hYv2--1M.d.ts.map +1 -0
  148. package/dist/types_server-kkQJDSjA.d.ts +684 -0
  149. package/dist/types_server-kkQJDSjA.d.ts.map +1 -0
  150. package/package.json +2 -1
@@ -0,0 +1,312 @@
1
+ import { a as unwrapZodType, i as normalizeCodec } from "./table-id-DyrJBKAQ.js";
2
+ import { s as traceDbCall, t as QueryBuilder } from "./query-q1ZvKrZO.js";
3
+ import { r as LazyCommitTsParam } from "./db-ClCicssQ.js";
4
+ import { l as OccConflictError } from "./types_db-OUou3o2Z.js";
5
+ import { z as z$1 } from "zod";
6
+ import { ExponentialBackoff, handleWhen, retry } from "cockatiel";
7
+ //#region src/db/context.ts
8
+ /**
9
+ * Materialize a schema's runtime {@link Model}: column-name mapping, per-column
10
+ * coarse type tags, and the codec used to (de)serialize each column.
11
+ *
12
+ * Codecs are resolved via {@link normalizeCodec}, which supplies a sensible
13
+ * default per Zod type (e.g. a {@link dateCodec} for `z.date()`, so `Date`
14
+ * columns round-trip automatically) while letting a user-supplied codec in
15
+ * `schema.columnsOptions.codecs` override it — partially or fully.
16
+ */
17
+ function buildModel(schema) {
18
+ const shape = schema.schema.shape;
19
+ const customColumns = schema.columns;
20
+ const columns = {};
21
+ const types = {};
22
+ const codecs = {};
23
+ const schemaCodecs = schema.columnsOptions.codecs ?? {};
24
+ for (const key of Object.keys(shape)) {
25
+ columns[key] = customColumns?.[key] ?? key;
26
+ const field = shape[key];
27
+ const baseField = unwrapZodType(field);
28
+ if (baseField instanceof z$1.ZodNumber) types[key] = "integer";
29
+ else if (baseField instanceof z$1.ZodString) types[key] = "string";
30
+ else if (baseField instanceof z$1.ZodBoolean) types[key] = "boolean";
31
+ else if (baseField instanceof z$1.ZodDate) types[key] = "date";
32
+ else if (baseField instanceof z$1.ZodArray) types[key] = "array";
33
+ else types[key] = "json";
34
+ codecs[key] = normalizeCodec(schemaCodecs[key], baseField);
35
+ }
36
+ return {
37
+ table: schema.table,
38
+ tableId: schema.tableId,
39
+ columns,
40
+ reverseColumns: Object.fromEntries(Object.entries(columns).map(([k, v]) => [v, k])),
41
+ types,
42
+ codecs
43
+ };
44
+ }
45
+ var TxContext = class {
46
+ beginTs;
47
+ db;
48
+ readSet = [];
49
+ writeSet = [];
50
+ models = /* @__PURE__ */ new Map();
51
+ getOrCreateModel(schema) {
52
+ const existing = this.models.get(schema.table);
53
+ if (existing) return existing;
54
+ const model = buildModel(schema);
55
+ this.models.set(schema.table, model);
56
+ return model;
57
+ }
58
+ constructor(db, beginTs) {
59
+ this.db = db;
60
+ this.beginTs = beginTs;
61
+ }
62
+ /**
63
+ * Rebind the read connection (and its snapshot ts). Used by the replica
64
+ * router when a query handler calls `ctx.usePrimaryConn()`/`useReplicaConn()`.
65
+ * Must be called BEFORE any read — the readSet/beginTs would otherwise mix
66
+ * snapshots — so this throws if a read or write has already happened.
67
+ */
68
+ useConnection(db, beginTs) {
69
+ if (this.readSet.length > 0 || this.writeSet.length > 0) throw new Error("useConnection()/usePrimaryConn()/useReplicaConn() must be called before any read or write in the handler");
70
+ this.db = db;
71
+ this.beginTs = beginTs;
72
+ }
73
+ internalGetReadSet() {
74
+ return this.readSet;
75
+ }
76
+ internalGetWriteSet() {
77
+ return this.writeSet;
78
+ }
79
+ /**
80
+ * Merge a parent procedure's reads into this context's readSet. Used when a
81
+ * `runMutation` sub-transaction inherits the reads of the procedure that
82
+ * invoked it, so the sub-tx's OCC validation also fences on those rows.
83
+ * Entries are shallow-cloned so the two readSets don't alias.
84
+ */
85
+ appendInternalReadSet(parentEntries) {
86
+ this.readSet.push(...parentEntries.map((e) => ({ ...e })));
87
+ }
88
+ findPendingWrite(table, id) {
89
+ return this.writeSet.find((w) => w.table === table && w.id === id);
90
+ }
91
+ findPendingInsert(table, id) {
92
+ return this.writeSet.find((w) => w.table === table && w.id === id && w.op === "insert");
93
+ }
94
+ encodeWithCodec(model, property, value) {
95
+ const codec = model.codecs[property];
96
+ if (!codec?.encode || value === void 0 || value === null) return value;
97
+ return codec.encode(value);
98
+ }
99
+ };
100
+ var DbReader = class extends TxContext {
101
+ query(schema) {
102
+ const model = this.getOrCreateModel(schema);
103
+ return new QueryBuilder(this, model);
104
+ }
105
+ };
106
+ var DbWriter = class extends DbReader {
107
+ async insert(schema, id, data) {
108
+ const model = this.getOrCreateModel(schema);
109
+ const mappedData = { id };
110
+ const shape = schema.schema.shape;
111
+ for (const key of Object.keys(shape)) {
112
+ let value = data[key];
113
+ if (value === void 0) {
114
+ const field = shape[key];
115
+ if (field instanceof z$1.ZodDefault) {
116
+ const defaultVal = field.def.defaultValue;
117
+ value = typeof defaultVal === "function" ? defaultVal() : defaultVal;
118
+ if (value !== void 0) {
119
+ const columnName = model.columns[key] ?? key;
120
+ mappedData[columnName] = this.encodeWithCodec(model, key, value);
121
+ }
122
+ }
123
+ } else {
124
+ const columnName = model.columns[key] ?? key;
125
+ mappedData[columnName] = this.encodeWithCodec(model, key, value);
126
+ }
127
+ }
128
+ mappedData["id"] = id;
129
+ const existing = this.findPendingWrite(schema.table, id);
130
+ if (existing) if (existing.op === "delete") {
131
+ existing.op = "update";
132
+ existing.data = mappedData;
133
+ } else existing.data = {
134
+ ...existing.data,
135
+ ...mappedData
136
+ };
137
+ else this.writeSet.push({
138
+ table: schema.table,
139
+ tableId: schema.tableId,
140
+ id,
141
+ op: "insert",
142
+ data: mappedData
143
+ });
144
+ }
145
+ async update(schema, id, data) {
146
+ const model = this.getOrCreateModel(schema);
147
+ const mappedData = {};
148
+ for (const [key, value] of Object.entries(data)) {
149
+ const columnName = model.columns[key] ?? key;
150
+ mappedData[columnName] = this.encodeWithCodec(model, key, value);
151
+ }
152
+ const existing = this.findPendingWrite(schema.table, id);
153
+ if (existing) {
154
+ existing.op = existing.op === "insert" ? "insert" : "update";
155
+ existing.data = {
156
+ ...existing.data,
157
+ ...mappedData
158
+ };
159
+ } else this.writeSet.push({
160
+ table: schema.table,
161
+ tableId: schema.tableId,
162
+ id,
163
+ op: "update",
164
+ data: mappedData
165
+ });
166
+ }
167
+ async delete(schema, id) {
168
+ const existingInsert = this.findPendingInsert(schema.table, id);
169
+ if (existingInsert) {
170
+ this.writeSet = this.writeSet.filter((w) => w !== existingInsert);
171
+ return;
172
+ }
173
+ this.writeSet.push({
174
+ table: schema.table,
175
+ tableId: schema.tableId,
176
+ id,
177
+ op: "delete"
178
+ });
179
+ }
180
+ };
181
+ //#endregion
182
+ //#region src/db/realtime_db.ts
183
+ const occRetryPolicy = retry(handleWhen((err) => OccConflictError.isOccConflictError(err)), {
184
+ maxAttempts: 15,
185
+ backoff: new ExponentialBackoff({
186
+ initialDelay: 10,
187
+ maxDelay: 7e3,
188
+ exponent: 2
189
+ })
190
+ });
191
+ const getDbclientRetryPolicy = retry(handleWhen((err) => err.message.includes("timeout exceeded")), {
192
+ maxAttempts: 3,
193
+ backoff: new ExponentialBackoff({
194
+ initialDelay: 500,
195
+ maxDelay: 2e3,
196
+ exponent: 2
197
+ })
198
+ });
199
+ var SupaliveDb = class SupaliveDb {
200
+ impl;
201
+ replica;
202
+ /**
203
+ * @param impl the PRIMARY (writer) connection — all mutations, the OCC
204
+ * commit-ts source, and cache-freshness commit-log scans run here.
205
+ * @param replica optional read replica for query/action reads (see the
206
+ * routing in one-shot-query.ts). Undefined → reads use primary.
207
+ */
208
+ constructor(impl, replica) {
209
+ this.impl = impl;
210
+ this.replica = replica;
211
+ }
212
+ static create(config) {
213
+ return new SupaliveDb(config.db, config.replica ? new SupaliveDb(config.replica) : void 0);
214
+ }
215
+ /** True when a distinct read replica is configured. */
216
+ get hasReplica() {
217
+ return this.replica !== void 0;
218
+ }
219
+ onAfterCommit;
220
+ /**
221
+ * Register a hook fired once after every successful `mutation()` commit
222
+ * (never on an OCC conflict/rollback), with the committed writeSet and its
223
+ * commitTs. The server uses this to drive subscription invalidation for ALL
224
+ * mutation sources — client WS calls, actions, jobs, and the internal caller —
225
+ * so a server-side write pushes live updates just like a client write does.
226
+ * Non-server consumers (migration tooling, tests) leave it unset (no-op).
227
+ */
228
+ setOnAfterCommit(cb) {
229
+ this.onAfterCommit = cb;
230
+ }
231
+ /**
232
+ * One-shot read against an EXPLICIT connection + snapshot ts (rather than this
233
+ * db's own latest). Used by the replica router: the caller pre-resolves the
234
+ * default read connection (replica for queries) and hands the handler a
235
+ * `DbReader` it can rebind via `useConnection` (ctx.usePrimaryConn/…). The
236
+ * returned `ts` is the FINAL bound connection's snapshot, so the cache entry is
237
+ * stamped with the ts actually read at.
238
+ */
239
+ async queryInternalOn(read, fn) {
240
+ const ctx = new DbReader(read.impl, read.beginTs);
241
+ const data = await fn(ctx);
242
+ return {
243
+ ts: ctx.beginTs,
244
+ data,
245
+ readSet: ctx.internalGetReadSet()
246
+ };
247
+ }
248
+ async query(fn) {
249
+ const beginTs = await this.impl.getLatestSnapshotTimestamp();
250
+ return await fn(new DbReader(this.impl, beginTs));
251
+ }
252
+ /**
253
+ * Compute a query once at the latest snapshot, returning the result together
254
+ * with the snapshot `ts` and the tracked `readSet`. This is a plain one-shot
255
+ * evaluation — it registers NO live subscription; the `readSet`/`ts` are
256
+ * returned purely so the caller (e.g. the one-shot query cache) can store
257
+ * freshness metadata. Use this for cached one-shot query calls (WS `call`,
258
+ * HTTP RPC). `liveQuery` is the same computation under the subscription path's
259
+ * name and simply delegates here.
260
+ */
261
+ async queryInternal(fn) {
262
+ const beginTs = await this.impl.getLatestSnapshotTimestamp();
263
+ const ctx = new DbReader(this.impl, beginTs);
264
+ return {
265
+ ts: beginTs,
266
+ data: await fn(ctx),
267
+ readSet: ctx.internalGetReadSet()
268
+ };
269
+ }
270
+ async liveQuery(fn) {
271
+ return this.queryInternal(fn);
272
+ }
273
+ async mutation(fn, retryPolicy = occRetryPolicy) {
274
+ return await retryPolicy.execute(async (p) => {
275
+ let db;
276
+ try {
277
+ const beginTs = await this.impl.getLatestSnapshotTimestamp() + 1n;
278
+ const ctx = new DbWriter(this.impl, beginTs);
279
+ const result = await fn(ctx);
280
+ const readSet = ctx.internalGetReadSet();
281
+ const writeSet = ctx.internalGetWriteSet();
282
+ const commitTsLazy = new LazyCommitTsParam(void 0);
283
+ await this.impl.attachPrevDataToWriteSet(writeSet, readSet);
284
+ const queries = await this.impl.prepareCommitWrites(readSet, writeSet, commitTsLazy);
285
+ db = await getDbclientRetryPolicy.execute(() => this.impl.getTransactionClient());
286
+ await db.begin();
287
+ const commitTs = await db.getNextTimestamp();
288
+ commitTsLazy.value = commitTs;
289
+ const commitResult = await traceDbCall("commit", "", this.impl.dbType, () => db.commitWrites(beginTs, queries, commitTs));
290
+ if (!commitResult.success) throw new OccConflictError("Commit failed", { commitResult });
291
+ await db.updateLatestSnapshotTimestamp(commitTs);
292
+ await db.commit();
293
+ this.onAfterCommit?.(writeSet, commitTs);
294
+ return {
295
+ result,
296
+ writeSet,
297
+ commitTs
298
+ };
299
+ } catch (err) {
300
+ await db?.rollback();
301
+ throw err;
302
+ } finally {
303
+ db?.release();
304
+ }
305
+ });
306
+ }
307
+ };
308
+ const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
309
+ //#endregion
310
+ export { TxContext as a, DbWriter as i, sleep as n, DbReader as r, SupaliveDb as t };
311
+
312
+ //# sourceMappingURL=realtime_db-DVhhasJj.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime_db-DVhhasJj.js","names":["z"],"sources":["../src/db/context.ts","../src/db/realtime_db.ts"],"sourcesContent":["import { z, ZodRawShape } from \"zod\";\nimport { Database } from \"./db\";\nimport { ReadEntry, WriteEntry, } from \"./types_db\";\n\nimport { ColumnCodec, InferSchema, InsertData, Model, SchemaDefinition } from \"./schema\";\nimport { normalizeCodec, unwrapZodType } from \"./codec\";\nimport { QueryBuilder } from \"./query\";\n\n/**\n * Materialize a schema's runtime {@link Model}: column-name mapping, per-column\n * coarse type tags, and the codec used to (de)serialize each column.\n *\n * Codecs are resolved via {@link normalizeCodec}, which supplies a sensible\n * default per Zod type (e.g. a {@link dateCodec} for `z.date()`, so `Date`\n * columns round-trip automatically) while letting a user-supplied codec in\n * `schema.columnsOptions.codecs` override it — partially or fully.\n */\nexport function buildModel(schema: SchemaDefinition<string, ZodRawShape>): Model {\n const shape = schema.schema.shape;\n const customColumns = (schema as any).columns;\n const columns: Record<string, string> = {};\n const types: Record<string, string> = {};\n const codecs: Record<string, ColumnCodec<any, any>> = {};\n const schemaCodecs = schema.columnsOptions.codecs ?? {};\n\n for (const key of Object.keys(shape)) {\n columns[key] = customColumns?.[key] ?? key;\n const field = shape[key];\n const baseField = unwrapZodType(field);\n if (baseField instanceof z.ZodNumber) {\n types[key] = \"integer\";\n } else if (baseField instanceof z.ZodString) {\n types[key] = \"string\";\n } else if (baseField instanceof z.ZodBoolean) {\n types[key] = \"boolean\";\n } else if (baseField instanceof z.ZodDate) {\n types[key] = \"date\";\n } else if (baseField instanceof z.ZodArray) {\n types[key] = \"array\";\n } else {\n types[key] = \"json\";\n }\n codecs[key] = normalizeCodec(schemaCodecs[key], baseField);\n }\n\n return {\n table: schema.table,\n tableId: schema.tableId,\n columns,\n reverseColumns: Object.fromEntries(Object.entries(columns).map(([k, v]) => [v, k])),\n types,\n codecs,\n };\n}\n\nexport class TxContext {\n // Not `readonly`: a query's read connection can be rebound (primary↔replica)\n // via {@link useConnection} before the first read — see the replica routing in\n // realtime_db.ts / one-shot-query.ts. Mutations never rebind.\n beginTs: bigint;\n db: Database;\n protected readSet: ReadEntry[] = [];\n protected writeSet: WriteEntry[] = [];\n private models: Map<string, Model> = new Map();\n\n protected getOrCreateModel(schema: SchemaDefinition<string, ZodRawShape>): Model {\n const existing = this.models.get(schema.table);\n if (existing) return existing;\n\n const model = buildModel(schema);\n this.models.set(schema.table, model);\n return model;\n }\n\n constructor(db: Database, beginTs: bigint) {\n this.db = db;\n this.beginTs = beginTs;\n }\n\n /**\n * Rebind the read connection (and its snapshot ts). Used by the replica\n * router when a query handler calls `ctx.usePrimaryConn()`/`useReplicaConn()`.\n * Must be called BEFORE any read — the readSet/beginTs would otherwise mix\n * snapshots — so this throws if a read or write has already happened.\n */\n useConnection(db: Database, beginTs: bigint): void {\n if (this.readSet.length > 0 || this.writeSet.length > 0) {\n throw new Error(\n \"useConnection()/usePrimaryConn()/useReplicaConn() must be called before any read or write in the handler\",\n );\n }\n this.db = db;\n this.beginTs = beginTs;\n }\n\n internalGetReadSet(): ReadEntry[] { return this.readSet; }\n internalGetWriteSet(): WriteEntry[] { return this.writeSet; }\n\n /**\n * Merge a parent procedure's reads into this context's readSet. Used when a\n * `runMutation` sub-transaction inherits the reads of the procedure that\n * invoked it, so the sub-tx's OCC validation also fences on those rows.\n * Entries are shallow-cloned so the two readSets don't alias.\n */\n appendInternalReadSet(parentEntries: ReadEntry[]) {\n this.readSet.push(...parentEntries.map((e) => ({...e})));\n }\n\n protected findPendingWrite(table: string, id: string): WriteEntry | undefined {\n return this.writeSet.find(w => w.table === table && w.id === id);\n }\n\n protected findPendingInsert(table: string, id: string): WriteEntry | undefined {\n return this.writeSet.find(w => w.table === table && w.id === id && w.op === \"insert\");\n }\n\n protected encodeWithCodec(model: Model, property: string, value: unknown): unknown {\n const codec = model.codecs[property];\n if (!codec?.encode || value === undefined || value === null) return value;\n return codec.encode(value);\n }\n}\n\nexport class DbReader extends TxContext {\n query<S extends SchemaDefinition<string, ZodRawShape>>(schema: S): QueryBuilder<S> {\n const model = this.getOrCreateModel(schema);\n return new QueryBuilder<S>(this, model);\n }\n}\n\nexport class DbWriter extends DbReader {\n async insert<S extends SchemaDefinition<string, ZodRawShape>, ID extends string>(\n schema: S,\n id: ID,\n data: InsertData<S>\n ): Promise<void> {\n const model = this.getOrCreateModel(schema);\n const mappedData: Record<string, unknown> = { id };\n const shape = schema.schema.shape;\n\n for (const key of Object.keys(shape)) {\n let value = data[key];\n\n if (value === undefined) {\n const field = shape[key];\n if (field instanceof z.ZodDefault) {\n const defaultVal = field.def.defaultValue;\n value = (typeof defaultVal === \"function\" ? defaultVal() : defaultVal)\n\n if (value !== undefined) {\n const columnName = model.columns[key] ?? key;\n mappedData[columnName] = this.encodeWithCodec(model, key, value);\n }\n }\n } else {\n const columnName = model.columns[key] ?? key;\n mappedData[columnName] = this.encodeWithCodec(model, key, value);\n }\n }\n // ensure id same\n mappedData[\"id\"] = id;\n\n // Check for existing pending write for the same ID\n const existing = this.findPendingWrite(schema.table, id);\n if (existing) {\n if (existing.op === \"delete\") {\n // If there's a pending delete, change it to an update with the new data\n existing.op = \"update\";\n existing.data = mappedData;\n } else {\n // If there's a pending update or same insert with the same ID , merge the new data\n existing.data = { ...existing.data, ...mappedData };\n }\n } else {\n this.writeSet.push({ table: schema.table, tableId: schema.tableId, id: id, op: \"insert\", data: mappedData });\n }\n }\n\n async update<S extends SchemaDefinition<string, ZodRawShape>, ID extends string>(\n schema: S,\n id: ID,\n data: Partial<Omit<InferSchema<S>, \"id\" | \"commitTs\">>,\n ): Promise<void> {\n const model = this.getOrCreateModel(schema);\n const mappedData: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(data)) {\n const columnName = model.columns[key] ?? key;\n mappedData[columnName] = this.encodeWithCodec(model, key, value);\n }\n\n const existing = this.findPendingWrite(schema.table, id);\n if (existing) {\n existing.op = existing.op === \"insert\" ? \"insert\" : \"update\";\n existing.data = { ...existing.data, ...mappedData };\n } else {\n // expectedTs will be filled by the committer via batch fetch if not known yet\n this.writeSet.push({ table: schema.table, tableId: schema.tableId, id, op: \"update\", data: mappedData });\n }\n }\n\n async delete<S extends SchemaDefinition<string, ZodRawShape>, ID extends string>(\n schema: S,\n id: ID,\n ): Promise<void> {\n // If there's a pending insert for the same ID, remove it instead of adding a delete\n const existingInsert = this.findPendingInsert(schema.table, id);\n if (existingInsert) {\n this.writeSet = this.writeSet.filter(w => w !== existingInsert);\n return;\n }\n\n // expectedTs will be filled by the committer via batch fetch if not known yet\n this.writeSet.push({ table: schema.table, tableId: schema.tableId, id, op: \"delete\" });\n }\n}\n","import { Database, LazyCommitTsParam, TxDatabase } from \"./db\";\nimport {\n OccConflictError, WriteEntry, CommitTs, LiveResult, MutationResult\n} from \"./types_db\";\nimport { DbReader, DbWriter } from \"./context\";\nimport { traceDbCall } from \"./db-trace\";\nimport { ExponentialBackoff, handleWhen, retry, RetryPolicy } from \"cockatiel\";\n\nexport const occRetryPolicy = retry(\n handleWhen((err) => OccConflictError.isOccConflictError(err)),\n {\n maxAttempts: 15,\n backoff: new ExponentialBackoff({\n initialDelay: 10,\n maxDelay: 7000,\n exponent: 2,\n }),\n },\n);\n\nexport const getDbclientRetryPolicy = retry(\n handleWhen((err) => err.message.includes('timeout exceeded')),\n {\n maxAttempts: 3,\n backoff: new ExponentialBackoff({\n initialDelay: 500,\n maxDelay: 2000,\n exponent: 2,\n }),\n },\n);\n\nexport class SupaliveDb {\n /**\n * @param impl the PRIMARY (writer) connection — all mutations, the OCC\n * commit-ts source, and cache-freshness commit-log scans run here.\n * @param replica optional read replica for query/action reads (see the\n * routing in one-shot-query.ts). Undefined → reads use primary.\n */\n constructor(readonly impl: Database, readonly replica?: SupaliveDb) { }\n\n static create(config: { db: Database; replica?: Database }): SupaliveDb {\n return new SupaliveDb(config.db, config.replica ? new SupaliveDb(config.replica) : undefined);\n }\n\n /** True when a distinct read replica is configured. */\n get hasReplica(): boolean {\n return this.replica !== undefined;\n }\n\n protected onAfterCommit?: (writeSet: WriteEntry[], commitTs: CommitTs) => void;\n\n /**\n * Register a hook fired once after every successful `mutation()` commit\n * (never on an OCC conflict/rollback), with the committed writeSet and its\n * commitTs. The server uses this to drive subscription invalidation for ALL\n * mutation sources — client WS calls, actions, jobs, and the internal caller —\n * so a server-side write pushes live updates just like a client write does.\n * Non-server consumers (migration tooling, tests) leave it unset (no-op).\n */\n setOnAfterCommit(cb: (writeSet: WriteEntry[], commitTs: CommitTs) => void): void {\n this.onAfterCommit = cb;\n }\n\n // ── Read-only query ──────────────────────────────────────────────\n\n /**\n * One-shot read against an EXPLICIT connection + snapshot ts (rather than this\n * db's own latest). Used by the replica router: the caller pre-resolves the\n * default read connection (replica for queries) and hands the handler a\n * `DbReader` it can rebind via `useConnection` (ctx.usePrimaryConn/…). The\n * returned `ts` is the FINAL bound connection's snapshot, so the cache entry is\n * stamped with the ts actually read at.\n */\n async queryInternalOn<T>(\n read: { impl: Database; beginTs: bigint },\n fn: (ctx: DbReader) => Promise<T>,\n ): Promise<LiveResult<T>> {\n const ctx = new DbReader(read.impl, read.beginTs);\n const data = await fn(ctx);\n return { ts: ctx.beginTs, data, readSet: ctx.internalGetReadSet() };\n }\n\n async query<T>(fn: (ctx: DbReader) => Promise<T>): Promise<T> {\n const beginTs = await this.impl.getLatestSnapshotTimestamp();\n const ctx = new DbReader(this.impl, beginTs);\n return await fn(ctx);\n }\n\n /**\n * Compute a query once at the latest snapshot, returning the result together\n * with the snapshot `ts` and the tracked `readSet`. This is a plain one-shot\n * evaluation — it registers NO live subscription; the `readSet`/`ts` are\n * returned purely so the caller (e.g. the one-shot query cache) can store\n * freshness metadata. Use this for cached one-shot query calls (WS `call`,\n * HTTP RPC). `liveQuery` is the same computation under the subscription path's\n * name and simply delegates here.\n */\n async queryInternal<T>(fn: (ctx: DbReader) => Promise<T>): Promise<LiveResult<T>> {\n const beginTs = await this.impl.getLatestSnapshotTimestamp();\n const ctx = new DbReader(this.impl, beginTs);\n const data = await fn(ctx);\n\n return {\n ts: beginTs,\n data,\n readSet: ctx.internalGetReadSet(),\n };\n }\n\n async liveQuery<T>(fn: (ctx: DbReader) => Promise<T>): Promise<LiveResult<T>> {\n return this.queryInternal(fn);\n }\n\n // ── Mutation ───────────────────────────────────────────────────────\n\n async mutation<T>(\n fn: (ctx: DbWriter) => Promise<T>,\n retryPolicy: RetryPolicy = occRetryPolicy,\n ): Promise<MutationResult<T>> {\n return await retryPolicy.execute(async (p) => {\n let db: TxDatabase | undefined;\n try {\n // Step1: Get latest snapshot timestamp as beginTs\n const beginTs = await this.impl.getLatestSnapshotTimestamp() + 1n;\n\n const ctx = new DbWriter(this.impl, beginTs);\n const result = await fn(ctx);\n\n const readSet = ctx.internalGetReadSet();\n const writeSet = ctx.internalGetWriteSet();\n const commitTsLazy = new LazyCommitTsParam(undefined);\n\n // Step2: Capture prevData + expectedCommitTs per write. prevData\n // is consumed by the invalidator (range subs need it to detect\n // result-set transitions). expectedCommitTs becomes the CAS fence\n // for the apply step — preferring prior find()-captured ts so\n // read-then-update patterns get beginTs-level isolation.\n await this.impl.attachPrevDataToWriteSet(writeSet, readSet);\n\n // Step3: Prepare quieries before transaction\n const queries = await this.impl.prepareCommitWrites(\n readSet,\n writeSet,\n commitTsLazy,\n );\n\n // Step4: Starts new transaction\n db = await getDbclientRetryPolicy.execute(() => this.impl.getTransactionClient());\n await db.begin();\n\n // Step5: Get commitTs from DB sequence (bigger than all previous)\n const commitTs = await db.getNextTimestamp();\n commitTsLazy.value = commitTs;\n\n // Step6: Apply + validate. CAS UPDATE/DELETE catches concurrent\n // peer touches on the write set; the readSet is validated either\n // per-row (fast path, no range reads) or via commit-log scan\n // (slow path, when range reads are present so phantoms still\n // need detection).\n const commitResult = await traceDbCall(\n \"commit\",\n \"\",\n this.impl.dbType,\n () => db!.commitWrites(beginTs, queries, commitTs),\n );\n\n if (!commitResult.success) {\n throw new OccConflictError(\"Commit failed\", { commitResult });\n }\n\n await db.updateLatestSnapshotTimestamp(commitTs);\n await db.commit();\n\n // Notify after successful commit\n this.onAfterCommit?.(writeSet, commitTs);\n\n return { result, writeSet, commitTs };\n } catch (err) {\n await db?.rollback();\n throw err;\n } finally {\n db?.release();\n }\n });\n }\n}\n\nexport const sleep = (ms: number) => new Promise<void>((res) => setTimeout(res, ms));\n"],"mappings":"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"}
@@ -0,0 +1,285 @@
1
+ //#region src/router/procedure.ts
2
+ /**
3
+ * A {@link HandlerLogger} that discards everything. Used as `ctx.log` for direct
4
+ * internal-caller invocations (which run outside the server's request logger), so
5
+ * handler code can ALWAYS call `ctx.log.info(...)` without a null check.
6
+ */
7
+ const noopLogger = {
8
+ debug() {},
9
+ info() {},
10
+ warn() {},
11
+ error() {}
12
+ };
13
+ /**
14
+ * No-op connection routing for query paths that genuinely ALWAYS run on the
15
+ * primary — live subscription re-execution and mutation handlers. Spread into
16
+ * the ctx so `ctx.usePrimaryConn()`/`useReplicaConn()` are always callable but
17
+ * inert (there is only ever the primary connection to bind).
18
+ *
19
+ * For a nested `runQuery` that JOINS a parent transaction, use {@link parentConn}
20
+ * instead — there the connection isn't necessarily the primary.
21
+ */
22
+ const primaryOnlyConn = {
23
+ usePrimaryConn: () => {},
24
+ useReplicaConn: () => {}
25
+ };
26
+ /**
27
+ * No-op connection routing for a nested `runQuery` that JOINS a parent
28
+ * transaction (a `runQuery` called with a query/mutation `parentCtx`). The knobs
29
+ * always use the PARENT's connection because the read connection is fixed to the
30
+ * parent transaction's — which may be the PRIMARY or the REPLICA, depending on
31
+ * how the parent query was routed.
32
+ *
33
+ * They MUST be inert: a nested query shares the parent's snapshot + readSet, so
34
+ * calling `useConnection` here would either repoint the parent's connection
35
+ * mid-flight (before its first read) or throw (after it) — either way corrupting
36
+ * the parent transaction. A handler that needs to force primary therefore cannot
37
+ * do so while nested under a replica-routed parent (it inherits the replica,
38
+ * which is already safe w.r.t. the client's `minTs` floor); call it via an
39
+ * action-parent `runQuery` instead, which opens a fresh routed read.
40
+ */
41
+ const parentConn = {
42
+ usePrimaryConn: () => {},
43
+ useReplicaConn: () => {}
44
+ };
45
+ /**
46
+ * Create a query builder with a pre-defined context type.
47
+ * This allows you to define the context type once and have it inferred
48
+ * automatically in all your query handlers.
49
+ *
50
+ * @example
51
+ * // Define your server context
52
+ * interface ServerContext {
53
+ * auth: { userId: string };
54
+ * requestId: string;
55
+ * }
56
+ *
57
+ * // Create a typed query builder
58
+ * const query = createQueryBuilder<ServerContext>();
59
+ *
60
+ * // Use it - context type is automatically inferred!
61
+ * const getUser = query({
62
+ * args: z.object({ id: z.string() }),
63
+ * handler: async (ctx, { id }) => {
64
+ * // ctx.db for database queries
65
+ * const user = await ctx.db.query(UsersSchema).find(id);
66
+ * // ctx.context for server context
67
+ * console.log(ctx.context.requestId);
68
+ * return user;
69
+ * }
70
+ * });
71
+ */
72
+ function createQueryBuilder() {
73
+ return function query(config) {
74
+ return {
75
+ _type: "procedure",
76
+ procedureType: "query",
77
+ inputSchema: config.args,
78
+ outputSchema: config.returns,
79
+ fn: config.handler,
80
+ internal: config.internal ?? false,
81
+ queryIdentity: config.queryIdentity
82
+ };
83
+ };
84
+ }
85
+ /**
86
+ * Create a mutation builder with a pre-defined context type.
87
+ * This allows you to define the context type once and have it inferred
88
+ * automatically in all your mutation handlers.
89
+ *
90
+ * @example
91
+ * // Define your server context
92
+ * interface ServerContext {
93
+ * auth: { userId: string };
94
+ * requestId: string;
95
+ * }
96
+ *
97
+ * // Create a typed mutation builder
98
+ * const mutation = createMutationBuilder<ServerContext>();
99
+ *
100
+ * // Use it - context type is automatically inferred!
101
+ * const createUser = mutation({
102
+ * args: z.object({ name: z.string() }),
103
+ * handler: async (ctx, { name }) => {
104
+ * // ctx.db for mutations
105
+ * await ctx.db.insert(UsersSchema, id, { name });
106
+ * // ctx.context for server context
107
+ * console.log(ctx.context.requestId);
108
+ * return { success: true };
109
+ * }
110
+ * });
111
+ */
112
+ function createMutationBuilder() {
113
+ return function mutation(config) {
114
+ return {
115
+ _type: "procedure",
116
+ procedureType: "mutation",
117
+ inputSchema: config.args,
118
+ outputSchema: config.returns,
119
+ fn: config.handler,
120
+ internal: config.internal ?? false
121
+ };
122
+ };
123
+ }
124
+ /**
125
+ * Create an action builder with a pre-defined context type.
126
+ * Actions have access to the full SupaliveDb for both queries and mutations.
127
+ *
128
+ * @example
129
+ * // Define your server context
130
+ * interface ServerContext {
131
+ * auth: { userId: string };
132
+ * requestId: string;
133
+ * }
134
+ *
135
+ * // Create a typed action builder
136
+ * const action = createActionBuilder<ServerContext>();
137
+ *
138
+ * // Use it - context type is automatically inferred!
139
+ * const processOrder = action({
140
+ * args: z.object({ orderId: z.string() }),
141
+ * handler: async (ctx, { orderId }) => {
142
+ * // ctx.db for full database access
143
+ * const order = await ctx.db.query(async (db) => {
144
+ * return db.query(OrdersSchema).find(orderId);
145
+ * });
146
+ * await ctx.db.mutation(async (db) => {
147
+ * await db.update(OrdersSchema, orderId, { status: "processed" });
148
+ * });
149
+ * // ctx.context for server context
150
+ * console.log(ctx.context.requestId);
151
+ * return { success: true };
152
+ * }
153
+ * });
154
+ */
155
+ function createActionBuilder() {
156
+ return function action(config) {
157
+ return {
158
+ _type: "procedure",
159
+ procedureType: "action",
160
+ inputSchema: config.args,
161
+ outputSchema: config.returns,
162
+ fn: config.handler,
163
+ internal: config.internal ?? false
164
+ };
165
+ };
166
+ }
167
+ /**
168
+ * Create a job builder with a pre-defined context type. A job is a
169
+ * server-only procedure invoked by the scheduler over HTTP — either on its
170
+ * declared `cron` schedule or as a precise one-shot enqueued via the
171
+ * server's job API. Jobs run like actions (full db, non-transactional) and
172
+ * are always internal, so they are never reachable from a client.
173
+ *
174
+ * @example
175
+ * const job = createJobBuilder<ServerContext>();
176
+ *
177
+ * export const cleanupOtps = job({
178
+ * cron: "0 * * * *", // hourly
179
+ * args: z.object({}),
180
+ * handler: async (ctx) => {
181
+ * await ctx.db.mutation(async (db) => { ... });
182
+ * return { ok: true };
183
+ * },
184
+ * });
185
+ */
186
+ function createJobBuilder() {
187
+ return function job(config) {
188
+ return {
189
+ _type: "procedure",
190
+ procedureType: "job",
191
+ inputSchema: config.args,
192
+ fn: config.handler,
193
+ internal: true,
194
+ cron: config.cron
195
+ };
196
+ };
197
+ }
198
+ //#endregion
199
+ //#region src/router/router.ts
200
+ /**
201
+ * Create a router from procedure definitions.
202
+ * Procedure names are inferred from the object keys.
203
+ *
204
+ * @example
205
+ * const appRouter = router({
206
+ * procedures: {
207
+ * getUser,
208
+ * createUser,
209
+ * internalGetAll,
210
+ * },
211
+ * });
212
+ *
213
+ * export type AppRouter = typeof appRouter;
214
+ */
215
+ function router(config) {
216
+ const registry = getContextRegistry(config.contextName ?? "default");
217
+ for (const [name, proc] of Object.entries(config.procedures)) registry.registerProcedure(name, proc);
218
+ return {
219
+ _type: "router",
220
+ procedures: config.procedures,
221
+ contextName: config.contextName ?? "default"
222
+ };
223
+ }
224
+ const contextRegistry = /* @__PURE__ */ new Map();
225
+ function getContextRegistry(inContext) {
226
+ let registry = contextRegistry.get(inContext);
227
+ if (!registry) {
228
+ registry = new ContextRegistry();
229
+ contextRegistry.set(inContext, registry);
230
+ }
231
+ return registry;
232
+ }
233
+ var ContextRegistry = class {
234
+ procedureRegistry = /* @__PURE__ */ new Map();
235
+ internalProcedureNames = /* @__PURE__ */ new Set();
236
+ registerProcedure(name, proc) {
237
+ if (this.procedureRegistry.has(name)) throw new Error(`[router] Procedure "${name}" is already registered. Each procedure name must be unique across the application.`);
238
+ if (proc.internal) this.markInternalProcedure(name);
239
+ const queryIdentity = proc.procedureType === "query" ? proc.queryIdentity : void 0;
240
+ const cron = proc.procedureType === "job" ? proc.cron : void 0;
241
+ this.procedureRegistry.set(name, {
242
+ name,
243
+ type: proc.procedureType,
244
+ internal: proc.internal,
245
+ inputSchema: proc.inputSchema,
246
+ fn: proc.fn,
247
+ queryIdentity,
248
+ cron
249
+ });
250
+ }
251
+ /** Get a registered procedure by name (runtime lookup) */
252
+ getProcedure(name) {
253
+ return this.procedureRegistry.get(name);
254
+ }
255
+ /** Get all registered procedure names */
256
+ getProcedureNames() {
257
+ return [...this.procedureRegistry.keys()];
258
+ }
259
+ /** Get all registered procedures */
260
+ getAllProcedures() {
261
+ return new Map(this.procedureRegistry);
262
+ }
263
+ /** Get all registered job procedures (server-only, scheduler-triggered). */
264
+ getJobProcedures() {
265
+ return [...this.procedureRegistry.values()].filter((p) => p.type === "job");
266
+ }
267
+ /** Clear the registry (for testing only) */
268
+ clearRegistry() {
269
+ this.procedureRegistry.clear();
270
+ this.clearInternalProcedures();
271
+ }
272
+ markInternalProcedure(name) {
273
+ this.internalProcedureNames.add(name);
274
+ }
275
+ isInternalProcedure(name) {
276
+ return this.internalProcedureNames.has(name);
277
+ }
278
+ clearInternalProcedures() {
279
+ this.internalProcedureNames.clear();
280
+ }
281
+ };
282
+ //#endregion
283
+ export { createJobBuilder as a, noopLogger as c, createActionBuilder as i, parentConn as l, getContextRegistry as n, createMutationBuilder as o, router as r, createQueryBuilder as s, ContextRegistry as t, primaryOnlyConn as u };
284
+
285
+ //# sourceMappingURL=router-Bk0eLfmB.js.map