kitcn 0.24.0 → 0.25.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 (78) hide show
  1. package/dist/aggregate/index.d.ts +11 -1
  2. package/dist/aggregate/index.js +1 -1
  3. package/dist/auth/generated/index.d.ts +1 -1
  4. package/dist/auth/generated/index.js +1 -1
  5. package/dist/auth/http/index.d.ts +1 -1
  6. package/dist/auth/index.d.ts +4 -4
  7. package/dist/auth/index.js +10 -9
  8. package/dist/auth/nextjs/index.d.ts +2 -2
  9. package/dist/auth/nextjs/index.js +3 -3
  10. package/dist/auth/start/server/index.d.ts +1 -1
  11. package/dist/auth/start/server/index.js +1 -1
  12. package/dist/{builder-CsxVc5xC.js → builder-1Vx-tEnS.js} +3 -3
  13. package/dist/{caller-factory-CWa0ELLD.js → caller-factory-Dd3H7j3V.js} +3 -3
  14. package/dist/{where-clause-compiler-DW6jy2er.d.ts → capabilities-Bem7xvGK.d.ts} +2958 -4145
  15. package/dist/cli.mjs +59 -40
  16. package/dist/{convex-plugin-D8B0oCFq.js → convex-plugin-DfOhBU9g.js} +1 -1
  17. package/dist/{create-schema-orm-B3f2Kc8O.js → create-schema-orm-Bu2JCiBS.js} +1 -1
  18. package/dist/crpc/index.d.ts +2 -2
  19. package/dist/crpc/index.js +3 -3
  20. package/dist/{customFunctions-DxEEO4Dq.js → customFunctions-BbhgdRGl.js} +2 -2
  21. package/dist/definitions-D9vhJ6OV.js +215 -0
  22. package/dist/extensions-DzjCJXYl.js +55 -0
  23. package/dist/filter-expression-Dydt8wS0.js +402 -0
  24. package/dist/{generated-contract-disabled-B-nmd7Ne.d.ts → generated-contract-disabled-BEc4d98x.d.ts} +2 -2
  25. package/dist/{http-types-DXOgaerG.d.ts → http-types-90mapMP9.d.ts} +2 -2
  26. package/dist/id-Bibg34Yb.js +37 -0
  27. package/dist/{local-env-yFKub75x.mjs → local-env-Du24tdbp.mjs} +2243 -2113
  28. package/dist/{middleware-Br5zubu7.d.ts → middleware-CE_MC6Gi.d.ts} +1 -1
  29. package/dist/{middleware-DUd1Sj39.js → middleware-cJDRHTRY.js} +1 -1
  30. package/dist/orm/aggregate-index/index.d.ts +20 -0
  31. package/dist/orm/aggregate-index/index.js +2136 -0
  32. package/dist/orm/index.d.ts +5 -4
  33. package/dist/orm/index.js +126 -4292
  34. package/dist/orm/migrations/index.d.ts +3 -0
  35. package/dist/orm/migrations/index.js +506 -0
  36. package/dist/plugins/index.d.ts +1 -1
  37. package/dist/plugins/index.js +1 -1
  38. package/dist/{procedure-caller-DQxnLBS_.js → procedure-caller-DIzXfX2E.js} +2 -2
  39. package/dist/{procedure-name-C20pFZnk.d.ts → procedure-name-B09-cf1-.d.ts} +3 -3
  40. package/dist/{query-context-BzihIpnM.js → query-context-DOm5Xm3H.js} +1 -402
  41. package/dist/ratelimit/index.d.ts +2 -2
  42. package/dist/ratelimit/index.js +3 -3
  43. package/dist/rsc/index.d.ts +4 -4
  44. package/dist/rsc/index.js +4 -4
  45. package/dist/runtime-DfGsZtx3.js +1074 -0
  46. package/dist/schema-Besl-rpv.js +1411 -0
  47. package/dist/server/index.d.ts +4 -4
  48. package/dist/server/index.js +4 -4
  49. package/dist/{runtime-6DJxpDfb.js → table-rmJm5Qio.js} +3 -1095
  50. package/dist/{token-xlpENMVn.js → token-DcV_0fkF.js} +1 -1
  51. package/dist/{types-BHR7ZKKD.d.ts → types-In-ZZOsd.d.ts} +1 -1
  52. package/dist/{validators-C7LelqTN.js → validators-Dmbhsd3U.js} +1 -1
  53. package/dist/watcher.mjs +1 -1
  54. package/dist/where-clause-compiler-mohWFiG0.d.ts +2042 -0
  55. package/package.json +3 -2
  56. package/skills/kitcn/SKILL.md +19 -18
  57. package/skills/kitcn/references/features/aggregates.md +21 -0
  58. package/skills/kitcn/references/features/auth-organizations.md +3 -3
  59. package/skills/kitcn/references/features/auth-polar.md +2 -1
  60. package/skills/kitcn/references/features/auth.md +8 -1
  61. package/skills/kitcn/references/setup/auth.md +52 -42
  62. package/skills/kitcn/references/setup/index.md +1 -1
  63. package/skills/kitcn/references/setup/server.md +2 -1
  64. /package/dist/{api-entry-N3nBOlI2.js → api-entry-CkDpGYVg.js} +0 -0
  65. /package/dist/{context-utils-BBUtBqjN.d.ts → context-utils-DwZ3Cam1.d.ts} +0 -0
  66. /package/dist/{create-schema-BXrKE2YY.js → create-schema-BRFjdgRr.js} +0 -0
  67. /package/dist/{error-Bvo7YEhk.js → error-CMLeCadS.js} +0 -0
  68. /package/dist/{generated-contract-disabled-BXaz7JCE.js → generated-contract-disabled-CZa0iyV0.js} +0 -0
  69. /package/dist/{meta-utils-D9K4fICl.js → meta-utils-D2lqN2Lg.js} +0 -0
  70. /package/dist/{query-context-CNo9ffvI.d.ts → query-context-DJONf8X5.d.ts} +0 -0
  71. /package/dist/{query-options-CzRV4G4N.js → query-options-DUiOj01a.js} +0 -0
  72. /package/dist/{token-B9Bjcqug.d.ts → token-kQaqFby4.d.ts} +0 -0
  73. /package/dist/{transformer-yZuBWo8v.js → transformer-D8wO-kEj.js} +0 -0
  74. /package/dist/{types-BCl8gfGy.d.ts → types-Bf3XQex5.d.ts} +0 -0
  75. /package/dist/{types-DF2cg_w0.d.ts → types-MHYXboyy.d.ts} +0 -0
  76. /package/dist/{types-CnTpHR1F.d.ts → types-eNzQmbqM.d.ts} +0 -0
  77. /package/dist/{upstream-BR6sBLg3.js → upstream-BCgGZX6q.js} +0 -0
  78. /package/dist/{validators-BhsByJeg.d.ts → validators-V4eM2-Jg.d.ts} +0 -0
@@ -0,0 +1,1411 @@
1
+ import { A as integer, C as index, N as json, O as text, P as objectOf, _ as TableDeleteConfig, a as Columns, j as arrayOf, s as OrmContext, t as convexTable, v as TableName, y as TablePolymorphic } from "./table-rmJm5Qio.js";
2
+ import { t as id } from "./id-Bibg34Yb.js";
3
+ import { C as matchLikePattern, _ as isFieldReference, d as filterValueInList, f as filterValuesEqual, u as fieldRef } from "./filter-expression-Dydt8wS0.js";
4
+ import { t as defineSchemaExtension } from "./extensions-DzjCJXYl.js";
5
+
6
+ //#region src/orm/timestamp-mode.ts
7
+ const PUBLIC_CREATED_AT_FIELD = "createdAt";
8
+ const INTERNAL_CREATION_TIME_FIELD = "_creationTime";
9
+ const CREATED_AT_MIGRATION_MESSAGE = "`_creationTime` is no longer public. Use `createdAt` instead.";
10
+ const hasUserCreatedAtColumn = (table) => {
11
+ if (!table || typeof table !== "object") return false;
12
+ const columns = table[Columns];
13
+ if (!columns || typeof columns !== "object") return false;
14
+ return Object.hasOwn(columns, PUBLIC_CREATED_AT_FIELD);
15
+ };
16
+ const usesSystemCreatedAtAlias = (_table) => true;
17
+
18
+ //#endregion
19
+ //#region src/orm/index-utils.ts
20
+ function getIndexes(table) {
21
+ const indexes = table.getIndexes?.();
22
+ return Array.isArray(indexes) ? indexes : [];
23
+ }
24
+ function getAggregateIndexes(table) {
25
+ const indexes = table.getAggregateIndexes?.();
26
+ return Array.isArray(indexes) ? indexes : [];
27
+ }
28
+ function getRankIndexes(table) {
29
+ const indexes = table.getRankIndexes?.();
30
+ return Array.isArray(indexes) ? indexes : [];
31
+ }
32
+ function getSearchIndexes(table) {
33
+ const indexes = table.getSearchIndexes?.();
34
+ return Array.isArray(indexes) ? indexes : [];
35
+ }
36
+ function findSearchIndexByName(table, indexName) {
37
+ return getSearchIndexes(table).find((index) => index.name === indexName) ?? null;
38
+ }
39
+ function getVectorIndexes(table) {
40
+ const indexes = table.getVectorIndexes?.();
41
+ return Array.isArray(indexes) ? indexes : [];
42
+ }
43
+ function findVectorIndexByName(table, indexName) {
44
+ return getVectorIndexes(table).find((index) => index.name === indexName) ?? null;
45
+ }
46
+ function findIndexForColumns(indexes, columns) {
47
+ for (const index of indexes) if (hasColumnPrefix(index, columns)) return index.name;
48
+ return null;
49
+ }
50
+ const hasColumnPrefix = (index, columns) => {
51
+ if (index.fields.length < columns.length) return false;
52
+ for (let i = 0; i < columns.length; i += 1) if (index.fields[i] !== columns[i]) return false;
53
+ return true;
54
+ };
55
+ /**
56
+ * Answers "does scanning this index already produce the requested order?".
57
+ *
58
+ * Convex walks an index in full key order, so once a leading run of fields is
59
+ * pinned to a single value by `eq`, the remainder of the scan is already sorted
60
+ * by the next index field — and by `_creationTime` once every declared field is
61
+ * pinned, because Convex appends it as the implicit trailing key. When that
62
+ * field is the one the caller asked to sort by, `.order(dir).take(n)` returns
63
+ * the exact page and nothing has to be collected and sorted afterwards.
64
+ *
65
+ * This is the single owner of that decision. The top-level query planner and
66
+ * the relation loader both call it, so a bound one of them can push into the
67
+ * index can never be one the other silently drops.
68
+ *
69
+ * Returns the direction to hand to `.order()`, or null when the sort must run
70
+ * after the fetch.
71
+ */
72
+ function resolveIndexOrderPushdown(params) {
73
+ const { indexFields, pinnedEqCount, orderSpecs } = params;
74
+ if (!indexFields || orderSpecs.length !== 1) return null;
75
+ const primary = orderSpecs[0];
76
+ const eqCount = Math.min(Math.max(pinnedEqCount, 0), indexFields.length);
77
+ for (let i = 0; i < eqCount; i += 1) if (indexFields[i] === primary.field) return primary.direction;
78
+ const nativeField = eqCount >= indexFields.length ? INTERNAL_CREATION_TIME_FIELD : indexFields[eqCount];
79
+ return primary.field === nativeField ? primary.direction : null;
80
+ }
81
+ function findRelationIndex(table, columns, relationName, targetTableName, strict = true, allowFullScan = false, orderSpecs = []) {
82
+ const indexes = getIndexes(table);
83
+ const fallbackIndex = findIndexForColumns(indexes, columns);
84
+ const index = (orderSpecs.length === 0 ? void 0 : indexes.find((candidate) => hasColumnPrefix(candidate, columns) && resolveIndexOrderPushdown({
85
+ indexFields: candidate.fields,
86
+ pinnedEqCount: columns.length,
87
+ orderSpecs
88
+ }) !== null))?.name ?? fallbackIndex;
89
+ if (!index && !allowFullScan) throw new Error(`Relation ${relationName} requires index on '${targetTableName}(${columns.join(", ")})'. Set allowFullScan: true to override.`);
90
+ if (!index && strict) console.warn(`Relation ${relationName} running without index (allowFullScan: true).`);
91
+ return index;
92
+ }
93
+
94
+ //#endregion
95
+ //#region src/orm/write-fanout.ts
96
+ /**
97
+ * Shared primitives for fanning out independent writes.
98
+ *
99
+ * Deliberately dependency-free: `mutation-utils`, `lifecycle` and `query` all
100
+ * import it, and Convex bundles every static import of a function entry.
101
+ */
102
+ const ORMLIFECYCLE_HOOKED_TABLES = Symbol.for("kitcn:OrmLifecycleHookedTables");
103
+ /** Matches the ORM's relation-loading default. */
104
+ const DEFAULT_WRITE_FANOUT_CONCURRENCY = 25;
105
+ /**
106
+ * Runs `worker` over `items` with at most `limit` in flight. Results keep input
107
+ * order. Unbounded `Promise.all` is not an option here: a fan-out is bounded
108
+ * only by `mutationMaxRows` (10,000), and that many simultaneous in-flight
109
+ * syscalls is its own failure mode.
110
+ */
111
+ async function mapWithConcurrency(items, limit, worker) {
112
+ if (items.length === 0) return [];
113
+ const width = Math.max(1, Math.min(limit, items.length));
114
+ const results = new Array(items.length);
115
+ let nextIndex = 0;
116
+ const runWorker = async () => {
117
+ while (true) {
118
+ const index = nextIndex;
119
+ nextIndex += 1;
120
+ if (index >= items.length) return;
121
+ results[index] = await worker(items[index], index);
122
+ }
123
+ };
124
+ await Promise.all(Array.from({ length: width }, () => runWorker()));
125
+ return results;
126
+ }
127
+ /**
128
+ * Records which tables the lifecycle writer intercepts, so write fan-out can
129
+ * see it without importing `lifecycle` (which would close an import cycle
130
+ * through `aggregate-index/runtime`).
131
+ */
132
+ const markLifecycleHookedTables = (db, tableNames) => {
133
+ Object.defineProperty(db, ORMLIFECYCLE_HOOKED_TABLES, {
134
+ configurable: false,
135
+ enumerable: false,
136
+ value: tableNames,
137
+ writable: false
138
+ });
139
+ return db;
140
+ };
141
+ /**
142
+ * True when writes to `tableName` run through trigger / aggregate-index hooks.
143
+ * Those writes are serialized by the lifecycle write lock and their hooks fire
144
+ * in write order, so they must keep their sequential loop; everything else is
145
+ * free to fan out.
146
+ */
147
+ const hasLifecycleHooks = (db, tableName) => {
148
+ return (db?.[ORMLIFECYCLE_HOOKED_TABLES])?.has(tableName) ?? false;
149
+ };
150
+
151
+ //#endregion
152
+ //#region src/orm/mutation-utils.ts
153
+ const UNDEFINED_SENTINEL_KEY = "__kitcnUndefined";
154
+ const INTERNAL_ID_FIELD = "_id";
155
+ const PUBLIC_ID_FIELD = "id";
156
+ const DATE_COLUMN_TYPE = "ConvexDate";
157
+ const TIMESTAMP_COLUMN_TYPE = "ConvexTimestamp";
158
+ const isPlainObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
159
+ const temporalColumnDescriptorCache = /* @__PURE__ */ new WeakMap();
160
+ const DATE_ONLY_REGEX = /^\d{4}-\d{2}-\d{2}$/;
161
+ const toDateOnlyString = (value) => value.toISOString().slice(0, 10);
162
+ const toDateOnlyDate = (value) => DATE_ONLY_REGEX.test(value) ? /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`) : value;
163
+ const toTimestampMillis = (value) => {
164
+ if (value instanceof Date) return value.getTime();
165
+ if (typeof value === "string") {
166
+ const parsed = Date.parse(value);
167
+ if (!Number.isNaN(parsed)) return parsed;
168
+ }
169
+ return value;
170
+ };
171
+ const readTimestampValue = (value, mode) => {
172
+ if (mode === "date" && typeof value === "number") return new Date(value);
173
+ if (mode === "string" && typeof value === "number") return new Date(value).toISOString();
174
+ return value;
175
+ };
176
+ const getTemporalDescriptorFromColumn = (name, column) => {
177
+ const config = column?.config;
178
+ const columnType = config?.columnType;
179
+ if (columnType !== DATE_COLUMN_TYPE && columnType !== TIMESTAMP_COLUMN_TYPE) return;
180
+ if (columnType === DATE_COLUMN_TYPE) return {
181
+ name,
182
+ columnType,
183
+ mode: config?.mode === "date" ? "date" : "string"
184
+ };
185
+ return {
186
+ name,
187
+ columnType,
188
+ mode: config?.mode === "string" ? "string" : "date"
189
+ };
190
+ };
191
+ const getTemporalColumnDescriptors = (table) => {
192
+ const cacheKey = table;
193
+ const cached = temporalColumnDescriptorCache.get(cacheKey);
194
+ if (cached) return cached;
195
+ const descriptors = /* @__PURE__ */ new Map();
196
+ for (const [name, column] of Object.entries(getTableColumns(table))) {
197
+ const descriptor = getTemporalDescriptorFromColumn(name, column);
198
+ if (!descriptor) continue;
199
+ descriptors.set(name, descriptor);
200
+ }
201
+ temporalColumnDescriptorCache.set(cacheKey, descriptors);
202
+ return descriptors;
203
+ };
204
+ const getTemporalColumnDescriptor = (table, columnName) => {
205
+ const temporalColumns = getTemporalColumnDescriptors(table);
206
+ const direct = temporalColumns.get(columnName);
207
+ if (direct) return direct;
208
+ const columns = getTableColumns(table);
209
+ for (const descriptor of temporalColumns.values()) if (columns[descriptor.name]?.config?.name === columnName) return descriptor;
210
+ };
211
+ const normalizeTemporalWriteValue = (descriptor, value) => {
212
+ if (descriptor.columnType === DATE_COLUMN_TYPE) {
213
+ if (value instanceof Date) return toDateOnlyString(value);
214
+ return value;
215
+ }
216
+ return toTimestampMillis(value);
217
+ };
218
+ const hydrateTemporalReadValue = (descriptor, value) => {
219
+ if (descriptor.columnType === DATE_COLUMN_TYPE) {
220
+ if (descriptor.mode === "date" && typeof value === "string") return toDateOnlyDate(value);
221
+ return value;
222
+ }
223
+ return readTimestampValue(value, descriptor.mode);
224
+ };
225
+ const normalizeTemporalComparableValue = (table, fieldName, value) => {
226
+ const descriptor = getTemporalColumnDescriptor(table, fieldName);
227
+ if (!descriptor) return value;
228
+ if (Array.isArray(value)) return value.map((entry) => normalizeTemporalWriteValue(descriptor, entry));
229
+ return normalizeTemporalWriteValue(descriptor, value);
230
+ };
231
+ const normalizeDateFieldsForWrite = (table, value) => {
232
+ const useSystemCreatedAt = usesSystemCreatedAtAlias(table);
233
+ const hasUserCreatedAt = hasUserCreatedAtColumn(table);
234
+ const temporalColumns = getTemporalColumnDescriptors(table);
235
+ const result = { ...value };
236
+ if (Object.hasOwn(result, INTERNAL_CREATION_TIME_FIELD)) throw new Error(CREATED_AT_MIGRATION_MESSAGE);
237
+ if (useSystemCreatedAt && !hasUserCreatedAt && Object.hasOwn(result, PUBLIC_CREATED_AT_FIELD)) delete result[PUBLIC_CREATED_AT_FIELD];
238
+ for (const [name, descriptor] of temporalColumns.entries()) {
239
+ if (!Object.hasOwn(result, name)) continue;
240
+ result[name] = normalizeTemporalWriteValue(descriptor, result[name]);
241
+ }
242
+ return result;
243
+ };
244
+ /**
245
+ * Reshapes a stored document into its public form in a single pass: renames
246
+ * `_id` to `id`, aliases `_creationTime` to `createdAt`, and hydrates temporal
247
+ * columns. Deliberately allocates exactly one object and never calls `delete`
248
+ * on it — deleting an existing own property drops the object into V8
249
+ * dictionary mode, which then poisons every downstream spread.
250
+ */
251
+ const hydrateDateFieldsForRead = (table, value) => {
252
+ if (!isPlainObject(value)) return value;
253
+ const obj = value;
254
+ const hasId = Object.hasOwn(obj, INTERNAL_ID_FIELD);
255
+ const rawCreationTime = obj[INTERNAL_CREATION_TIME_FIELD];
256
+ const aliasCreatedAt = usesSystemCreatedAtAlias(table) && Object.hasOwn(obj, INTERNAL_CREATION_TIME_FIELD) && rawCreationTime !== void 0;
257
+ const temporalColumns = getTemporalColumnDescriptors(table);
258
+ const result = {};
259
+ for (const key of Object.keys(obj)) {
260
+ if (key === INTERNAL_ID_FIELD || key === INTERNAL_CREATION_TIME_FIELD) continue;
261
+ if (key === PUBLIC_CREATED_AT_FIELD && aliasCreatedAt) {
262
+ result[key] = void 0;
263
+ continue;
264
+ }
265
+ const descriptor = temporalColumns.get(key);
266
+ result[key] = descriptor ? hydrateTemporalReadValue(descriptor, obj[key]) : obj[key];
267
+ }
268
+ if (hasId) result[PUBLIC_ID_FIELD] = obj[INTERNAL_ID_FIELD];
269
+ if (aliasCreatedAt) {
270
+ const descriptor = temporalColumns.get(PUBLIC_CREATED_AT_FIELD);
271
+ result[PUBLIC_CREATED_AT_FIELD] = descriptor ? hydrateTemporalReadValue(descriptor, rawCreationTime) : rawCreationTime;
272
+ }
273
+ return result;
274
+ };
275
+ const selectReturningRowWithHydration = (table, row, fields) => {
276
+ const useSystemCreatedAt = usesSystemCreatedAtAlias(table);
277
+ const temporalColumns = getTemporalColumnDescriptors(table);
278
+ const selected = {};
279
+ for (const [selectedKey, column] of Object.entries(fields)) {
280
+ const columnName = getSelectionColumnName(column);
281
+ let value = row[columnName];
282
+ if (!(columnName === INTERNAL_CREATION_TIME_FIELD && useSystemCreatedAt)) {
283
+ const descriptor = temporalColumns.get(columnName);
284
+ if (descriptor) value = hydrateTemporalReadValue(descriptor, value);
285
+ }
286
+ selected[selectedKey] = value;
287
+ }
288
+ return { ...selected };
289
+ };
290
+ const encodeUndefinedDeep = (value) => {
291
+ if (value === void 0) return { [UNDEFINED_SENTINEL_KEY]: true };
292
+ if (Array.isArray(value)) return value.map((item) => encodeUndefinedDeep(item));
293
+ if (isPlainObject(value)) {
294
+ const result = {};
295
+ for (const [key, nested] of Object.entries(value)) result[key] = encodeUndefinedDeep(nested);
296
+ return result;
297
+ }
298
+ return value;
299
+ };
300
+ const decodeUndefinedDeep = (value) => {
301
+ if (Array.isArray(value)) return value.map((item) => decodeUndefinedDeep(item));
302
+ if (isPlainObject(value)) {
303
+ if (Object.keys(value).length === 1 && value[UNDEFINED_SENTINEL_KEY] === true) return;
304
+ const result = {};
305
+ for (const [key, nested] of Object.entries(value)) result[key] = decodeUndefinedDeep(nested);
306
+ return result;
307
+ }
308
+ return value;
309
+ };
310
+ const isSerializedFieldReference = (value) => isPlainObject(value) && typeof value.fieldName === "string";
311
+ const createBinaryExpression = (operator, fieldName, value) => {
312
+ return {
313
+ type: "binary",
314
+ operator,
315
+ operands: [fieldRef(fieldName), value],
316
+ accept(visitor) {
317
+ return visitor.visitBinary(this);
318
+ }
319
+ };
320
+ };
321
+ const createLogicalExpression = (operator, operands) => ({
322
+ type: "logical",
323
+ operator,
324
+ operands,
325
+ accept(visitor) {
326
+ return visitor.visitLogical(this);
327
+ }
328
+ });
329
+ const createUnaryExpression = (operator, operand) => ({
330
+ type: "unary",
331
+ operator,
332
+ operands: [operand],
333
+ accept(visitor) {
334
+ return visitor.visitUnary(this);
335
+ }
336
+ });
337
+ const serializeFilterExpression = (expression) => {
338
+ if (!expression) return;
339
+ if (expression.type === "binary") {
340
+ const binary = expression;
341
+ const [field, value] = binary.operands;
342
+ if (!isFieldReference(field)) throw new Error("Binary expression must have FieldReference as first operand");
343
+ return {
344
+ type: "binary",
345
+ operator: binary.operator,
346
+ field: { fieldName: field.fieldName },
347
+ value: encodeUndefinedDeep(value)
348
+ };
349
+ }
350
+ if (expression.type === "logical") {
351
+ const logical = expression;
352
+ return {
353
+ type: "logical",
354
+ operator: logical.operator,
355
+ operands: logical.operands.map((operand) => serializeFilterExpression(operand))
356
+ };
357
+ }
358
+ const unary = expression;
359
+ const [operand] = unary.operands;
360
+ return {
361
+ type: "unary",
362
+ operator: unary.operator,
363
+ operand: isFieldReference(operand) ? { fieldName: operand.fieldName } : serializeFilterExpression(operand)
364
+ };
365
+ };
366
+ const deserializeFilterExpression = (expression) => {
367
+ if (!expression) return;
368
+ if (expression.type === "binary") {
369
+ const binary = expression;
370
+ return createBinaryExpression(binary.operator, binary.field.fieldName, decodeUndefinedDeep(binary.value));
371
+ }
372
+ if (expression.type === "logical") {
373
+ const logical = expression;
374
+ return createLogicalExpression(logical.operator, logical.operands.map((operand) => deserializeFilterExpression(operand)).filter((operand) => !!operand));
375
+ }
376
+ const unary = expression;
377
+ const operand = unary.operand;
378
+ if (isSerializedFieldReference(operand)) return createUnaryExpression(unary.operator, fieldRef(operand.fieldName));
379
+ const nested = deserializeFilterExpression(operand);
380
+ if (!nested) throw new Error("Serialized unary operand is missing.");
381
+ return createUnaryExpression(unary.operator, nested);
382
+ };
383
+ const PRIMARY_ID_LOOKUP_CURSOR_PREFIX = "kitcn:primary-id:";
384
+ const dedupePrimaryIds = (values) => Array.from(new Map(values.map((value) => [String(value), value])).values());
385
+ const parsePrimaryIdLookupCursor = (cursor) => {
386
+ if (cursor === null) return 0;
387
+ if (!cursor.startsWith(PRIMARY_ID_LOOKUP_CURSOR_PREFIX)) throw new Error("Invalid primary id mutation cursor.");
388
+ const offset = Number(cursor.slice(17));
389
+ if (!Number.isInteger(offset) || offset < 0) throw new Error("Invalid primary id mutation cursor.");
390
+ return offset;
391
+ };
392
+ const canUsePrimaryIdLookupCursor = (cursor) => cursor === null || cursor === void 0 || cursor.startsWith(PRIMARY_ID_LOOKUP_CURSOR_PREFIX);
393
+ const extractPrimaryIdLookup = (expression) => {
394
+ if (!expression || expression.type !== "binary") return null;
395
+ const binary = expression;
396
+ const [left, right] = binary.operands;
397
+ if (binary.operator === "eq") {
398
+ if (isFieldReference(left) && left.fieldName === "_id") {
399
+ if (isFieldReference(right)) return null;
400
+ return {
401
+ kind: "eq",
402
+ values: [right]
403
+ };
404
+ }
405
+ if (isFieldReference(right) && right.fieldName === "_id") {
406
+ if (isFieldReference(left)) return null;
407
+ return {
408
+ kind: "eq",
409
+ values: [left]
410
+ };
411
+ }
412
+ }
413
+ if (binary.operator === "inArray" && isFieldReference(left) && left.fieldName === "_id" && Array.isArray(right)) return {
414
+ kind: "in",
415
+ values: dedupePrimaryIds(right)
416
+ };
417
+ return null;
418
+ };
419
+ const windowPrimaryIdLookup = (lookup, pagination) => {
420
+ if (!pagination) return {
421
+ continueCursor: null,
422
+ isDone: true,
423
+ values: lookup.values
424
+ };
425
+ const offset = parsePrimaryIdLookupCursor(pagination.cursor);
426
+ const nextOffset = offset + pagination.limit;
427
+ const values = lookup.values.slice(offset, nextOffset);
428
+ const isDone = nextOffset >= lookup.values.length;
429
+ return {
430
+ continueCursor: isDone ? null : `${PRIMARY_ID_LOOKUP_CURSOR_PREFIX}${nextOffset}`,
431
+ isDone,
432
+ values
433
+ };
434
+ };
435
+ const collectPrimaryIdLookupRows = async (db, tableName, lookup, options) => {
436
+ if (!options.pagination && lookup.values.length > options.maxRows) throw new Error(`${options.operation} exceeded mutationMaxRows (${options.maxRows}) on "${tableName}". Narrow the filter or increase defineSchema(..., { defaults: { mutationMaxRows } }).`);
437
+ if (!options.pagination && lookup.values.length > options.batchSize) throw new Error(`${options.operation} matched more than mutationBatchSize (${options.batchSize}) primary ids on "${tableName}". Use executeAsync({ batchSize }) or paginate() to split the mutation.`);
438
+ const primaryIdWindow = windowPrimaryIdLookup(lookup, options.pagination);
439
+ if (primaryIdWindow.values.length > options.maxRows) throw new Error(`${options.operation} exceeded mutationMaxRows (${options.maxRows}) on "${tableName}". Narrow the filter or increase defineSchema(..., { defaults: { mutationMaxRows } }).`);
440
+ const normalizedIds = primaryIdWindow.values.filter((value) => value !== null && value !== void 0).map((value) => db.normalizeId(tableName, value)).filter((value) => value !== null);
441
+ const fetchedRows = await Promise.all(normalizedIds.map((value) => db.get(value)));
442
+ return {
443
+ continueCursor: primaryIdWindow.continueCursor,
444
+ isDone: primaryIdWindow.isDone,
445
+ rows: fetchedRows.filter((row) => !!row)
446
+ };
447
+ };
448
+ const DEFAULT_MUTATION_BATCH_SIZE = 400;
449
+ const DEFAULT_MUTATION_LEAF_BATCH_SIZE = 1600;
450
+ const DEFAULT_MUTATION_MAX_ROWS = 1e4;
451
+ const DEFAULT_MUTATION_MAX_BYTES_PER_BATCH = 2097152;
452
+ const DEFAULT_MUTATION_SCHEDULE_CALL_CAP = 800;
453
+ const DEFAULT_MUTATION_ASYNC_DELAY_MS = 0;
454
+ const DEFAULT_COUNT_BACKFILL_BATCH_SIZE = 1e3;
455
+ const DEFAULT_RELATION_FAN_OUT_MAX_KEYS = 1e3;
456
+ const DEFAULT_AGGREGATE_CARTESIAN_MAX_KEYS = 4096;
457
+ const DEFAULT_AGGREGATE_WORK_BUDGET = 16384;
458
+ const MEASURED_BYTE_SAFETY_MULTIPLIER = 2;
459
+ const UTF8_LENGTH_THRESHOLD = 500;
460
+ const UTF8_ENCODER = new TextEncoder();
461
+ const getUtf8ByteLength = (value) => {
462
+ if (value.length > UTF8_LENGTH_THRESHOLD) return UTF8_ENCODER.encode(value).length;
463
+ let bytes = 0;
464
+ for (let i = 0; i < value.length; i++) {
465
+ const code = value.charCodeAt(i);
466
+ if (code < 128) bytes += 1;
467
+ else if (code < 2048) bytes += 2;
468
+ else if (code >= 55296 && code <= 56319) {
469
+ bytes += 4;
470
+ i += 1;
471
+ } else bytes += 3;
472
+ }
473
+ return bytes;
474
+ };
475
+ const resolveOrmRuntimeDefaults = (defaults, runtime = {}) => {
476
+ const inferredMode = runtime.scheduler && runtime.scheduledMutationBatch ? "async" : "sync";
477
+ return {
478
+ defaultLimit: defaults?.defaultLimit,
479
+ countBackfillBatchSize: defaults?.countBackfillBatchSize ?? DEFAULT_COUNT_BACKFILL_BATCH_SIZE,
480
+ relationFanOutMaxKeys: defaults?.relationFanOutMaxKeys ?? DEFAULT_RELATION_FAN_OUT_MAX_KEYS,
481
+ aggregateCartesianMaxKeys: defaults?.aggregateCartesianMaxKeys ?? DEFAULT_AGGREGATE_CARTESIAN_MAX_KEYS,
482
+ aggregateWorkBudget: defaults?.aggregateWorkBudget ?? DEFAULT_AGGREGATE_WORK_BUDGET,
483
+ mutationBatchSize: defaults?.mutationBatchSize ?? DEFAULT_MUTATION_BATCH_SIZE,
484
+ mutationLeafBatchSize: defaults?.mutationLeafBatchSize ?? DEFAULT_MUTATION_LEAF_BATCH_SIZE,
485
+ mutationMaxRows: defaults?.mutationMaxRows ?? DEFAULT_MUTATION_MAX_ROWS,
486
+ mutationMaxBytesPerBatch: defaults?.mutationMaxBytesPerBatch ?? DEFAULT_MUTATION_MAX_BYTES_PER_BATCH,
487
+ mutationScheduleCallCap: defaults?.mutationScheduleCallCap ?? DEFAULT_MUTATION_SCHEDULE_CALL_CAP,
488
+ mutationExecutionMode: defaults?.mutationExecutionMode ?? inferredMode,
489
+ mutationAsyncDelayMs: defaults?.mutationAsyncDelayMs ?? DEFAULT_MUTATION_ASYNC_DELAY_MS
490
+ };
491
+ };
492
+ const estimateMeasuredMutationRowBytes = (row) => {
493
+ return getUtf8ByteLength(JSON.stringify(row)) * MEASURED_BYTE_SAFETY_MULTIPLIER;
494
+ };
495
+ const takeRowsWithinByteBudget = (rows, maxBytesPerBatch) => {
496
+ if (!Number.isInteger(maxBytesPerBatch) || maxBytesPerBatch < 1) throw new Error("mutationMaxBytesPerBatch must be a positive integer.");
497
+ if (rows.length === 0) return {
498
+ rows,
499
+ hitLimit: false
500
+ };
501
+ let bytes = 0;
502
+ const selected = [];
503
+ for (const row of rows) {
504
+ const rowBytes = estimateMeasuredMutationRowBytes(row);
505
+ if (selected.length > 0 && bytes + rowBytes > maxBytesPerBatch) return {
506
+ rows: selected,
507
+ hitLimit: true
508
+ };
509
+ selected.push(row);
510
+ bytes += rowBytes;
511
+ }
512
+ return {
513
+ rows: selected,
514
+ hitLimit: false
515
+ };
516
+ };
517
+ const getMutationCollectionLimits = (context) => {
518
+ const defaults = context?.resolvedDefaults ?? context?.defaults;
519
+ const batchSize = defaults?.mutationBatchSize ?? DEFAULT_MUTATION_BATCH_SIZE;
520
+ const leafBatchSize = defaults?.mutationLeafBatchSize ?? DEFAULT_MUTATION_LEAF_BATCH_SIZE;
521
+ const maxRows = defaults?.mutationMaxRows ?? DEFAULT_MUTATION_MAX_ROWS;
522
+ const maxBytesPerBatch = defaults?.mutationMaxBytesPerBatch ?? DEFAULT_MUTATION_MAX_BYTES_PER_BATCH;
523
+ const scheduleCallCap = defaults?.mutationScheduleCallCap ?? DEFAULT_MUTATION_SCHEDULE_CALL_CAP;
524
+ if (!Number.isInteger(batchSize) || batchSize < 1) throw new Error("mutationBatchSize must be a positive integer.");
525
+ if (!Number.isInteger(leafBatchSize) || leafBatchSize < 1) throw new Error("mutationLeafBatchSize must be a positive integer.");
526
+ if (!Number.isInteger(maxRows) || maxRows < 1) throw new Error("mutationMaxRows must be a positive integer.");
527
+ if (!Number.isInteger(maxBytesPerBatch) || maxBytesPerBatch < 1) throw new Error("mutationMaxBytesPerBatch must be a positive integer.");
528
+ if (!Number.isInteger(scheduleCallCap) || scheduleCallCap < 1) throw new Error("mutationScheduleCallCap must be a positive integer.");
529
+ return {
530
+ batchSize,
531
+ leafBatchSize,
532
+ maxRows,
533
+ maxBytesPerBatch,
534
+ scheduleCallCap
535
+ };
536
+ };
537
+ const consumeScheduleCall = (state) => {
538
+ if (!state) return;
539
+ if (state.remainingCalls < 1) throw new Error(`Async cascade scheduling exceeded mutationScheduleCallCap (${state.callCap}). Increase defineSchema(..., { defaults: { mutationScheduleCallCap } }) or reduce fan-out per mutation.`);
540
+ state.remainingCalls -= 1;
541
+ };
542
+ const getMutationExecutionMode = (context, override) => {
543
+ const requestedMode = override ?? context?.resolvedDefaults?.mutationExecutionMode ?? context?.defaults?.mutationExecutionMode;
544
+ if (requestedMode === "sync") return "sync";
545
+ if (requestedMode === "async") {
546
+ if (override === "async") return "async";
547
+ if (context?.scheduler && context?.scheduledMutationBatch) return "async";
548
+ return "sync";
549
+ }
550
+ if (context?.scheduler && context?.scheduledMutationBatch) return "async";
551
+ return "sync";
552
+ };
553
+ const getMutationAsyncDelayMs = (context, override) => override ?? context?.resolvedDefaults?.mutationAsyncDelayMs ?? context?.defaults?.mutationAsyncDelayMs ?? DEFAULT_MUTATION_ASYNC_DELAY_MS;
554
+ const collectMutationRowsBounded = async (buildQuery, options) => {
555
+ const rows = await buildQuery().take(options.maxRows + 1);
556
+ if (rows.length > options.maxRows) throw new Error(`${options.operation} matched more than ${options.maxRows} rows on "${options.tableName}". Narrow the filter or increase defineSchema(..., { defaults: { mutationMaxRows } }).`);
557
+ return rows;
558
+ };
559
+ function getTableName(table) {
560
+ const name = table.tableName ?? table[TableName] ?? table?._?.name;
561
+ if (!name) throw new Error("Table is missing a name");
562
+ return name;
563
+ }
564
+ function getTableDeleteConfig(table) {
565
+ return table[TableDeleteConfig];
566
+ }
567
+ function getUniqueIndexes(table) {
568
+ const fromMethod = table.getUniqueIndexes?.();
569
+ if (Array.isArray(fromMethod)) return fromMethod;
570
+ const fromField = table.uniqueIndexes;
571
+ return Array.isArray(fromField) ? fromField : [];
572
+ }
573
+ function getChecks(table) {
574
+ const fromMethod = table.getChecks?.();
575
+ if (Array.isArray(fromMethod)) return fromMethod;
576
+ const fromField = table.checks;
577
+ return Array.isArray(fromField) ? fromField : [];
578
+ }
579
+ function buildForeignKeyGraph(schema) {
580
+ const tableByName = /* @__PURE__ */ new Map();
581
+ for (const tableConfig of Object.values(schema)) if (tableConfig?.name && tableConfig.table) tableByName.set(tableConfig.name, tableConfig.table);
582
+ const incomingByTable = /* @__PURE__ */ new Map();
583
+ for (const tableConfig of Object.values(schema)) {
584
+ const sourceTable = tableConfig.table;
585
+ const sourceTableName = tableConfig.name;
586
+ const foreignKeys = getForeignKeys(sourceTable);
587
+ for (const foreignKey of foreignKeys) {
588
+ const targetTableName = foreignKey.foreignTableName;
589
+ if (!tableByName.get(targetTableName)) throw new Error(`Foreign key from '${sourceTableName}' references missing table '${targetTableName}'.`);
590
+ const entry = {
591
+ sourceTable,
592
+ sourceTableName,
593
+ sourceColumns: foreignKey.columns,
594
+ targetTableName,
595
+ targetColumns: foreignKey.foreignColumns,
596
+ onDelete: foreignKey.onDelete,
597
+ onUpdate: foreignKey.onUpdate
598
+ };
599
+ const list = incomingByTable.get(targetTableName) ?? [];
600
+ list.push(entry);
601
+ incomingByTable.set(targetTableName, list);
602
+ }
603
+ }
604
+ return { incomingByTable };
605
+ }
606
+ function getOrmContext(db) {
607
+ return db[OrmContext];
608
+ }
609
+ function getForeignKeys(table) {
610
+ const fromMethod = table.getForeignKeys?.();
611
+ if (Array.isArray(fromMethod)) return fromMethod;
612
+ const fromField = table.foreignKeys;
613
+ return Array.isArray(fromField) ? fromField : [];
614
+ }
615
+ function getColumnName(column) {
616
+ const name = column.config?.name ?? column?._?.name;
617
+ if (!name) throw new Error("Column builder is missing a column name");
618
+ return name;
619
+ }
620
+ function getTableColumns(table) {
621
+ return table[Columns] ?? {};
622
+ }
623
+ function getTablePolymorphicConfigs(table) {
624
+ const fromMethod = table.getPolymorphicConfigs?.();
625
+ if (Array.isArray(fromMethod)) return fromMethod;
626
+ const fromSymbol = table[TablePolymorphic];
627
+ return Array.isArray(fromSymbol) ? fromSymbol : [];
628
+ }
629
+ function enforcePolymorphicWrite(table, candidate, options) {
630
+ const configs = getTablePolymorphicConfigs(table);
631
+ if (configs.length === 0) return;
632
+ for (const config of configs) {
633
+ const changedFields = options?.changedFields;
634
+ const discriminatorChanged = !changedFields || changedFields.has(config.discriminator);
635
+ const generatedFieldChanged = !changedFields || config.generatedFieldNames.some((fieldName) => changedFields.has(fieldName));
636
+ if (!discriminatorChanged && !generatedFieldChanged) continue;
637
+ const discriminatorValue = candidate[config.discriminator];
638
+ if (typeof discriminatorValue !== "string" || !Object.hasOwn(config.variants, discriminatorValue)) throw new Error(`Invalid discriminator '${config.discriminator}' on '${getTableName(table)}'. Expected one of: ${Object.keys(config.variants).join(", ")}.`);
639
+ const activeVariant = config.variants[discriminatorValue];
640
+ if (!activeVariant) throw new Error(`Invalid discriminator '${config.discriminator}' value '${discriminatorValue}' on '${getTableName(table)}'.`);
641
+ for (const requiredFieldName of activeVariant.requiredFieldNames) {
642
+ const value = candidate[requiredFieldName];
643
+ if (value === null || value === void 0) throw new Error(`discriminator branch '${discriminatorValue}' requires '${requiredFieldName}' on '${getTableName(table)}'.`);
644
+ }
645
+ const activeFieldSet = new Set(activeVariant.fieldNames);
646
+ for (const generatedFieldName of config.generatedFieldNames) {
647
+ if (activeFieldSet.has(generatedFieldName)) continue;
648
+ const value = candidate[generatedFieldName];
649
+ if (value !== null && value !== void 0) throw new Error(`discriminator branch '${discriminatorValue}' cannot set '${generatedFieldName}' on '${getTableName(table)}' because it belongs to another variant.`);
650
+ }
651
+ }
652
+ }
653
+ function getColumnConfig(table, columnName) {
654
+ const builder = getTableColumns(table)[columnName];
655
+ if (!builder) return null;
656
+ return builder.config ?? null;
657
+ }
658
+ function applyDefaults(table, value) {
659
+ const columns = table[Columns];
660
+ if (!columns) return value;
661
+ const result = { ...value };
662
+ const polymorphicConfigs = getTablePolymorphicConfigs(table);
663
+ const activeGeneratedFields = /* @__PURE__ */ new Set();
664
+ const generatedFields = /* @__PURE__ */ new Set();
665
+ for (const config of polymorphicConfigs) {
666
+ for (const fieldName of config.generatedFieldNames) generatedFields.add(fieldName);
667
+ const discriminatorValue = result[config.discriminator];
668
+ if (typeof discriminatorValue !== "string") continue;
669
+ const activeVariant = config.variants[discriminatorValue];
670
+ if (!activeVariant) continue;
671
+ for (const fieldName of activeVariant.fieldNames) activeGeneratedFields.add(fieldName);
672
+ }
673
+ for (const [columnName, builder] of Object.entries(columns)) {
674
+ if (result[columnName] !== void 0) continue;
675
+ if (generatedFields.has(columnName) && !activeGeneratedFields.has(columnName)) continue;
676
+ const config = builder.config;
677
+ if (!config) continue;
678
+ if (typeof config.defaultFn === "function") {
679
+ result[columnName] = config.defaultFn();
680
+ continue;
681
+ }
682
+ if (config.hasDefault) {
683
+ result[columnName] = config.default;
684
+ continue;
685
+ }
686
+ if (typeof config.onUpdateFn === "function") result[columnName] = config.onUpdateFn();
687
+ }
688
+ return result;
689
+ }
690
+ async function enforceUniqueIndexes(db, table, candidate, options) {
691
+ const uniqueIndexes = getUniqueIndexes(table);
692
+ if (uniqueIndexes.length === 0) return;
693
+ const tableName = getTableName(table);
694
+ const changedFields = options?.changedFields;
695
+ for (const index of uniqueIndexes) {
696
+ if (changedFields && !index.fields.some((field) => changedFields.has(field))) continue;
697
+ const entries = index.fields.map((field) => [field, candidate[field]]);
698
+ if (entries.some(([, value]) => value === void 0 || value === null) && !index.nullsNotDistinct) continue;
699
+ const existing = await db.query(tableName).withIndex(index.name, (q) => {
700
+ let builder = q.eq(entries[0][0], entries[0][1]);
701
+ for (let i = 1; i < entries.length; i++) builder = builder.eq(entries[i][0], entries[i][1]);
702
+ return builder;
703
+ }).unique();
704
+ if (existing !== null && (options?.currentId === void 0 || existing._id !== options.currentId)) throw new Error(`Unique index '${index.name}' violation on '${tableName}'.`);
705
+ }
706
+ }
707
+ async function enforceForeignKeys(db, table, candidate, options) {
708
+ const foreignKeys = getForeignKeys(table);
709
+ if (foreignKeys.length === 0) return;
710
+ const tableName = getTableName(table);
711
+ const changedFields = options?.changedFields;
712
+ for (const foreignKey of foreignKeys) {
713
+ if (changedFields && !foreignKey.columns.some((field) => changedFields.has(field))) continue;
714
+ const entries = foreignKey.columns.map((field) => [field, candidate[field]]);
715
+ if (entries.some(([, value]) => value === void 0 || value === null)) continue;
716
+ if (foreignKey.foreignColumns.length === 1 && foreignKey.foreignColumns[0] === "_id") {
717
+ const foreignId = entries[0]?.[1];
718
+ if (!await db.get(foreignId)) throw new Error(`Foreign key violation on '${tableName}': missing document in '${foreignKey.foreignTableName}'.`);
719
+ continue;
720
+ }
721
+ if (!foreignKey.foreignTable) throw new Error(`Foreign key on '${tableName}' requires indexed foreign columns on '${foreignKey.foreignTableName}'.`);
722
+ const indexName = findIndexForColumns(getIndexes(foreignKey.foreignTable), foreignKey.foreignColumns);
723
+ if (!indexName) throw new Error(`Foreign key on '${tableName}' requires index on '${foreignKey.foreignTableName}(${foreignKey.foreignColumns.join(", ")})'.`);
724
+ if (!await db.query(foreignKey.foreignTableName).withIndex(indexName, (q) => {
725
+ let builder = q.eq(foreignKey.foreignColumns[0], entries[0][1]);
726
+ for (let i = 1; i < entries.length; i++) builder = builder.eq(foreignKey.foreignColumns[i], entries[i][1]);
727
+ return builder;
728
+ }).first()) throw new Error(`Foreign key violation on '${tableName}': missing document in '${foreignKey.foreignTableName}'.`);
729
+ }
730
+ }
731
+ function getIndexForForeignKey(foreignKey) {
732
+ return findIndexForColumns(getIndexes(foreignKey.sourceTable), foreignKey.sourceColumns);
733
+ }
734
+ function foreignKeyIndexError(foreignKey) {
735
+ return /* @__PURE__ */ new Error(`Foreign key on '${foreignKey.sourceTableName}' requires index on '${foreignKey.sourceTableName}(${foreignKey.sourceColumns.join(", ")})' for cascading actions.`);
736
+ }
737
+ function buildIndexPredicate(q, columns, values) {
738
+ let builder = q.eq(columns[0], values[0]);
739
+ for (let i = 1; i < columns.length; i++) builder = builder.eq(columns[i], values[i]);
740
+ return builder;
741
+ }
742
+ function buildFilterPredicate(q, columns, values) {
743
+ let expr = q.eq(q.field(columns[0]), values[0]);
744
+ for (let i = 1; i < columns.length; i++) expr = q.and(expr, q.eq(q.field(columns[i]), values[i]));
745
+ return expr;
746
+ }
747
+ function ensureNullableColumns(table, columns, context) {
748
+ for (const columnName of columns) {
749
+ const config = getColumnConfig(table, columnName);
750
+ if (!config) throw new Error(`${context}: missing column '${columnName}' in table '${getTableName(table)}'.`);
751
+ if (config.notNull) throw new Error(`${context}: column '${columnName}' is not nullable in '${getTableName(table)}'.`);
752
+ }
753
+ }
754
+ function ensureDefaultColumns(table, columns, context) {
755
+ const defaults = {};
756
+ for (const columnName of columns) {
757
+ const config = getColumnConfig(table, columnName);
758
+ if (!config) throw new Error(`${context}: missing column '${columnName}' in table '${getTableName(table)}'.`);
759
+ if (!config.hasDefault) throw new Error(`${context}: column '${columnName}' has no default in '${getTableName(table)}'.`);
760
+ defaults[columnName] = config.default;
761
+ }
762
+ return defaults;
763
+ }
764
+ function ensureNonNullValues(table, values, context) {
765
+ for (const [columnName, value] of Object.entries(values)) if (getColumnConfig(table, columnName)?.notNull && (value === null || value === void 0)) throw new Error(`${context}: column '${columnName}' cannot be null in '${getTableName(table)}'.`);
766
+ }
767
+ async function collectReferencingRows(db, foreignKey, targetValues, indexName, options) {
768
+ return collectMutationRowsBounded(() => db.query(foreignKey.sourceTableName).withIndex(indexName, (q) => buildIndexPredicate(q, foreignKey.sourceColumns, targetValues)), {
769
+ operation: options.operation,
770
+ tableName: foreignKey.sourceTableName,
771
+ batchSize: options.batchSize,
772
+ maxRows: options.maxRows
773
+ });
774
+ }
775
+ async function collectAsyncCascadeRowsBounded(buildQuery, batchSize, maxBytesPerBatch) {
776
+ const rows = await buildQuery().take(batchSize + 1);
777
+ const hasMoreRows = rows.length > batchSize;
778
+ const bounded = takeRowsWithinByteBudget(hasMoreRows ? rows.slice(0, batchSize) : rows, maxBytesPerBatch);
779
+ return {
780
+ rows: bounded.rows,
781
+ needsContinuation: hasMoreRows || bounded.hitLimit
782
+ };
783
+ }
784
+ async function hasReferencingRow(db, foreignKey, targetValues, indexName) {
785
+ const query = db.query(foreignKey.sourceTableName);
786
+ return (indexName ? await query.withIndex(indexName, (q) => buildIndexPredicate(q, foreignKey.sourceColumns, targetValues)).first() : await query.filter((q) => buildFilterPredicate(q, foreignKey.sourceColumns, targetValues)).first()) !== null;
787
+ }
788
+ async function softDeleteRow(db, table, row) {
789
+ const tableName = getTableName(table);
790
+ if (!("deletionTime" in getTableColumns(table))) throw new Error(`Soft delete requires 'deletionTime' field on '${tableName}'.`);
791
+ const deletionTime = Date.now();
792
+ await db.patch(tableName, row._id, { deletionTime });
793
+ return deletionTime;
794
+ }
795
+ /**
796
+ * Applies one loop-invariant payload to every referencing row. The ids come
797
+ * from a single index scan so they are distinct, and the write set is
798
+ * order-independent — but a table whose writes run through lifecycle hooks
799
+ * keeps the sequential loop: those writes are serialized by the lifecycle write
800
+ * lock anyway, and their hooks would otherwise fire out of write order.
801
+ */
802
+ async function patchReferencingRows(db, tableName, rows, patch) {
803
+ if (hasLifecycleHooks(db, tableName)) {
804
+ for (const row of rows) await db.patch(tableName, row._id, patch);
805
+ return;
806
+ }
807
+ await mapWithConcurrency(rows, DEFAULT_WRITE_FANOUT_CONCURRENCY, async (row) => {
808
+ await db.patch(tableName, row._id, patch);
809
+ });
810
+ }
811
+ async function hardDeleteRow(db, tableName, row) {
812
+ await db.delete(tableName, row._id);
813
+ }
814
+ async function applyIncomingForeignKeyActionsOnDelete(db, table, row, options) {
815
+ const tableName = getTableName(table);
816
+ const incoming = options.graph.incomingByTable.get(tableName) ?? [];
817
+ if (incoming.length === 0) return;
818
+ for (const foreignKey of incoming) {
819
+ const action = foreignKey.onDelete ?? "no action";
820
+ const targetValues = foreignKey.targetColumns.map((column) => row[column]);
821
+ if (targetValues.some((value) => value === void 0 || value === null)) continue;
822
+ const indexName = getIndexForForeignKey(foreignKey);
823
+ if (action === "restrict" || action === "no action") {
824
+ if (!indexName && !options.allowFullScan) throw foreignKeyIndexError(foreignKey);
825
+ if (!indexName && options.strict) console.warn(`Foreign key check running without index (allowFullScan: true) on '${foreignKey.sourceTableName}'.`);
826
+ if (await hasReferencingRow(db, foreignKey, targetValues, indexName)) throw new Error(`Foreign key restrict violation on '${tableName}' from '${foreignKey.sourceTableName}'.`);
827
+ continue;
828
+ }
829
+ if (!indexName) {
830
+ if (!options.allowFullScan) throw foreignKeyIndexError(foreignKey);
831
+ if (options.strict) console.warn(`Foreign key cascade check running without index (allowFullScan: true) on '${foreignKey.sourceTableName}'.`);
832
+ if (await hasReferencingRow(db, foreignKey, targetValues, null)) throw foreignKeyIndexError(foreignKey);
833
+ continue;
834
+ }
835
+ let referencingRows;
836
+ if (options.executionMode === "async") {
837
+ const asyncBatchSize = action === "cascade" ? options.batchSize : options.leafBatchSize;
838
+ const { rows, needsContinuation } = await collectAsyncCascadeRowsBounded(() => db.query(foreignKey.sourceTableName).withIndex(indexName, (q) => buildIndexPredicate(q, foreignKey.sourceColumns, targetValues)), asyncBatchSize, options.maxBytesPerBatch);
839
+ referencingRows = rows;
840
+ if (needsContinuation) {
841
+ if (!options.scheduler || !options.scheduledMutationBatch) throw new Error("Async mutation execution requires orm.db(ctx) configured with scheduling (ormFunctions.scheduledMutationBatch).");
842
+ consumeScheduleCall(options.scheduleState);
843
+ await options.scheduler.runAfter(options.delayMs ?? 0, options.scheduledMutationBatch, {
844
+ workType: "cascade-delete",
845
+ mode: "async",
846
+ operation: "delete",
847
+ table: foreignKey.sourceTableName,
848
+ foreignIndexName: indexName,
849
+ foreignSourceColumns: foreignKey.sourceColumns,
850
+ targetValues: encodeUndefinedDeep(targetValues),
851
+ foreignAction: action,
852
+ deleteMode: options.deleteMode,
853
+ cascadeMode: options.cascadeMode,
854
+ cursor: null,
855
+ batchSize: asyncBatchSize,
856
+ maxBytesPerBatch: options.maxBytesPerBatch,
857
+ delayMs: options.delayMs ?? 0
858
+ });
859
+ }
860
+ } else referencingRows = await collectReferencingRows(db, foreignKey, targetValues, indexName, {
861
+ operation: "delete",
862
+ batchSize: options.batchSize,
863
+ maxRows: options.maxRows
864
+ });
865
+ if (referencingRows.length === 0) continue;
866
+ if (action === "set null") {
867
+ ensureNullableColumns(foreignKey.sourceTable, foreignKey.sourceColumns, `Foreign key set null on '${foreignKey.sourceTableName}'`);
868
+ const nullPatch = {};
869
+ for (const columnName of foreignKey.sourceColumns) nullPatch[columnName] = null;
870
+ await patchReferencingRows(db, foreignKey.sourceTableName, referencingRows, nullPatch);
871
+ continue;
872
+ }
873
+ if (action === "set default") {
874
+ const defaults = ensureDefaultColumns(foreignKey.sourceTable, foreignKey.sourceColumns, `Foreign key set default on '${foreignKey.sourceTableName}'`);
875
+ await patchReferencingRows(db, foreignKey.sourceTableName, referencingRows, defaults);
876
+ continue;
877
+ }
878
+ if (action === "cascade") for (const referencingRow of referencingRows) {
879
+ const key = `${foreignKey.sourceTableName}:${referencingRow._id}`;
880
+ if (options.visited.has(key)) continue;
881
+ options.visited.add(key);
882
+ await applyIncomingForeignKeyActionsOnDelete(db, foreignKey.sourceTable, referencingRow, options);
883
+ if (options.cascadeMode === "soft") await softDeleteRow(db, foreignKey.sourceTable, referencingRow);
884
+ else await hardDeleteRow(db, foreignKey.sourceTableName, referencingRow);
885
+ }
886
+ }
887
+ }
888
+ async function applyIncomingForeignKeyActionsOnUpdate(db, table, oldRow, newRow, options) {
889
+ const tableName = getTableName(table);
890
+ const incoming = options.graph.incomingByTable.get(tableName) ?? [];
891
+ if (incoming.length === 0) return;
892
+ for (const foreignKey of incoming) {
893
+ const action = foreignKey.onUpdate ?? "no action";
894
+ const oldValues = foreignKey.targetColumns.map((column) => oldRow[column]);
895
+ const newValues = foreignKey.targetColumns.map((column) => newRow[column]);
896
+ if (!oldValues.some((value, index) => !Object.is(value, newValues[index]))) continue;
897
+ if (oldValues.some((value) => value === void 0 || value === null)) continue;
898
+ const indexName = getIndexForForeignKey(foreignKey);
899
+ if (action === "restrict" || action === "no action") {
900
+ if (!indexName && !options.allowFullScan) throw foreignKeyIndexError(foreignKey);
901
+ if (!indexName && options.strict) console.warn(`Foreign key check running without index (allowFullScan: true) on '${foreignKey.sourceTableName}'.`);
902
+ if (await hasReferencingRow(db, foreignKey, oldValues, indexName)) throw new Error(`Foreign key restrict violation on '${tableName}' from '${foreignKey.sourceTableName}'.`);
903
+ continue;
904
+ }
905
+ if (!indexName) {
906
+ if (!options.allowFullScan) throw foreignKeyIndexError(foreignKey);
907
+ if (options.strict) console.warn(`Foreign key cascade check running without index (allowFullScan: true) on '${foreignKey.sourceTableName}'.`);
908
+ if (await hasReferencingRow(db, foreignKey, oldValues, null)) throw foreignKeyIndexError(foreignKey);
909
+ continue;
910
+ }
911
+ let referencingRows;
912
+ if (options.executionMode === "async") {
913
+ const asyncBatchSize = options.leafBatchSize;
914
+ const { rows, needsContinuation } = await collectAsyncCascadeRowsBounded(() => db.query(foreignKey.sourceTableName).withIndex(indexName, (q) => buildIndexPredicate(q, foreignKey.sourceColumns, oldValues)), asyncBatchSize, options.maxBytesPerBatch);
915
+ referencingRows = rows;
916
+ if (needsContinuation) {
917
+ if (!options.scheduler || !options.scheduledMutationBatch) throw new Error("Async mutation execution requires orm.db(ctx) configured with scheduling (ormFunctions.scheduledMutationBatch).");
918
+ consumeScheduleCall(options.scheduleState);
919
+ await options.scheduler.runAfter(options.delayMs ?? 0, options.scheduledMutationBatch, {
920
+ workType: "cascade-update",
921
+ mode: "async",
922
+ operation: "update",
923
+ table: foreignKey.sourceTableName,
924
+ foreignIndexName: indexName,
925
+ foreignSourceColumns: foreignKey.sourceColumns,
926
+ targetValues: encodeUndefinedDeep(oldValues),
927
+ newValues: encodeUndefinedDeep(newValues),
928
+ foreignAction: action,
929
+ cursor: null,
930
+ batchSize: asyncBatchSize,
931
+ maxBytesPerBatch: options.maxBytesPerBatch,
932
+ delayMs: options.delayMs ?? 0
933
+ });
934
+ }
935
+ } else referencingRows = await collectReferencingRows(db, foreignKey, oldValues, indexName, {
936
+ operation: "update",
937
+ batchSize: options.batchSize,
938
+ maxRows: options.maxRows
939
+ });
940
+ if (referencingRows.length === 0) continue;
941
+ if (action === "set null") {
942
+ ensureNullableColumns(foreignKey.sourceTable, foreignKey.sourceColumns, `Foreign key set null on '${foreignKey.sourceTableName}'`);
943
+ const nullPatch = {};
944
+ for (const columnName of foreignKey.sourceColumns) nullPatch[columnName] = null;
945
+ await patchReferencingRows(db, foreignKey.sourceTableName, referencingRows, nullPatch);
946
+ continue;
947
+ }
948
+ if (action === "set default") {
949
+ const defaults = ensureDefaultColumns(foreignKey.sourceTable, foreignKey.sourceColumns, `Foreign key set default on '${foreignKey.sourceTableName}'`);
950
+ await patchReferencingRows(db, foreignKey.sourceTableName, referencingRows, defaults);
951
+ continue;
952
+ }
953
+ if (action === "cascade") {
954
+ const patchValues = {};
955
+ for (let i = 0; i < foreignKey.sourceColumns.length; i++) patchValues[foreignKey.sourceColumns[i]] = newValues[i];
956
+ ensureNonNullValues(foreignKey.sourceTable, patchValues, `Foreign key cascade update on '${foreignKey.sourceTableName}'`);
957
+ await patchReferencingRows(db, foreignKey.sourceTableName, referencingRows, patchValues);
958
+ }
959
+ }
960
+ }
961
+ function getSelectionColumnName(value) {
962
+ if (value && typeof value === "object") {
963
+ if ("columnName" in value) return value.columnName;
964
+ if ("config" in value && value.config?.name) return value.config.name;
965
+ }
966
+ throw new Error("Returning selection must reference a column");
967
+ }
968
+ function splitReturningSelection(fields) {
969
+ const columnSelection = {};
970
+ let countSelection;
971
+ for (const [key, value] of Object.entries(fields)) {
972
+ if (key !== "_count") {
973
+ getSelectionColumnName(value);
974
+ columnSelection[key] = value;
975
+ continue;
976
+ }
977
+ if (value === void 0) continue;
978
+ if (!isPlainObject(value)) throw new Error("returning({ _count }) requires an object.");
979
+ const nextCountSelection = {};
980
+ for (const [relationName, relationSelection] of Object.entries(value)) {
981
+ if (relationSelection === void 0 || relationSelection === false) continue;
982
+ if (relationSelection === true) {
983
+ nextCountSelection[relationName] = true;
984
+ continue;
985
+ }
986
+ if (!isPlainObject(relationSelection)) throw new Error(`returning({ _count.${relationName} }) must be true or { where }`);
987
+ if ("select" in relationSelection) throw new Error(`returning({ _count.${relationName}.select }) is removed. Use returning({ _count: { ${relationName}: true } })`);
988
+ for (const [optionKey, optionValue] of Object.entries(relationSelection)) if (optionKey !== "where" && optionValue !== void 0) throw new Error(`returning({ _count.${relationName} }) does not support '${optionKey}'`);
989
+ if (typeof relationSelection.where === "function") throw new Error(`returning({ _count.${relationName}.where }) callback is unsupported in v1`);
990
+ nextCountSelection[relationName] = { where: relationSelection.where };
991
+ }
992
+ countSelection = nextCountSelection;
993
+ }
994
+ return {
995
+ columnSelection: Object.keys(columnSelection).length > 0 ? columnSelection : void 0,
996
+ countSelection
997
+ };
998
+ }
999
+ const matchLike = matchLikePattern;
1000
+ function evaluateFilter(row, filter) {
1001
+ if (filter.type === "binary") {
1002
+ const [field, value] = filter.operands;
1003
+ if (!isFieldReference(field)) throw new Error("Binary expression must have FieldReference as first operand");
1004
+ const fieldValue = row[field.fieldName];
1005
+ switch (filter.operator) {
1006
+ case "like": {
1007
+ const pattern = value;
1008
+ if (typeof fieldValue !== "string") return false;
1009
+ return matchLike(fieldValue, pattern, false);
1010
+ }
1011
+ case "ilike": {
1012
+ const pattern = value;
1013
+ if (typeof fieldValue !== "string") return false;
1014
+ return matchLike(fieldValue, pattern, true);
1015
+ }
1016
+ case "notLike": {
1017
+ const pattern = value;
1018
+ if (typeof fieldValue !== "string") return false;
1019
+ return !matchLike(fieldValue, pattern, false);
1020
+ }
1021
+ case "notIlike": {
1022
+ const pattern = value;
1023
+ if (typeof fieldValue !== "string") return false;
1024
+ return !matchLike(fieldValue, pattern, true);
1025
+ }
1026
+ case "startsWith":
1027
+ if (typeof fieldValue !== "string") return false;
1028
+ return fieldValue.startsWith(value);
1029
+ case "endsWith":
1030
+ if (typeof fieldValue !== "string") return false;
1031
+ return fieldValue.endsWith(value);
1032
+ case "contains":
1033
+ if (typeof fieldValue !== "string") return false;
1034
+ return fieldValue.includes(value);
1035
+ case "eq": return filterValuesEqual(fieldValue, value);
1036
+ case "ne": return !filterValuesEqual(fieldValue, value);
1037
+ case "gt": return fieldValue > value;
1038
+ case "gte": return fieldValue >= value;
1039
+ case "lt": return fieldValue < value;
1040
+ case "lte": return fieldValue <= value;
1041
+ case "inArray": return filterValueInList(fieldValue, value);
1042
+ case "notInArray": return !filterValueInList(fieldValue, value);
1043
+ case "arrayContains":
1044
+ if (!Array.isArray(fieldValue)) return false;
1045
+ return value.every((item) => fieldValue.includes(item));
1046
+ case "arrayContained": {
1047
+ if (!Array.isArray(fieldValue)) return false;
1048
+ const arr = value;
1049
+ return fieldValue.every((item) => arr.includes(item));
1050
+ }
1051
+ case "arrayOverlaps": {
1052
+ if (!Array.isArray(fieldValue)) return false;
1053
+ const arr = value;
1054
+ return fieldValue.some((item) => arr.includes(item));
1055
+ }
1056
+ default: throw new Error(`Unsupported post-fetch operator: ${filter.operator}`);
1057
+ }
1058
+ }
1059
+ if (filter.type === "unary") {
1060
+ const [operand] = filter.operands;
1061
+ if (isFieldReference(operand)) {
1062
+ const fieldValue = row[operand.fieldName];
1063
+ switch (filter.operator) {
1064
+ case "isNull": return fieldValue === null || fieldValue === void 0;
1065
+ case "isNotNull": return fieldValue !== null && fieldValue !== void 0;
1066
+ default: throw new Error(`Unsupported unary operator: ${filter.operator}`);
1067
+ }
1068
+ }
1069
+ if (filter.operator === "not") return !evaluateFilter(row, operand);
1070
+ throw new Error("Unary expression must have FieldReference or FilterExpression as operand");
1071
+ }
1072
+ if (filter.type === "logical") {
1073
+ if (filter.operator === "and") return filter.operands.every((f) => evaluateFilter(row, f));
1074
+ if (filter.operator === "or") return filter.operands.some((f) => evaluateFilter(row, f));
1075
+ }
1076
+ throw new Error(`Unsupported filter type for post-fetch: ${filter.type}`);
1077
+ }
1078
+ function triNot(value) {
1079
+ return value === "unknown" ? "unknown" : !value;
1080
+ }
1081
+ function triAnd(values) {
1082
+ if (values.some((value) => value === false)) return false;
1083
+ if (values.every((value) => value === true)) return true;
1084
+ return "unknown";
1085
+ }
1086
+ function triOr(values) {
1087
+ if (values.some((value) => value === true)) return true;
1088
+ if (values.every((value) => value === false)) return false;
1089
+ return "unknown";
1090
+ }
1091
+ function evaluateCheckConstraintTriState(row, filter) {
1092
+ if (filter.type === "binary") {
1093
+ const [field, value] = filter.operands;
1094
+ if (!isFieldReference(field)) throw new Error("Binary expression must have FieldReference as first operand");
1095
+ const fieldValue = row[field.fieldName];
1096
+ const compareValue = isFieldReference(value) ? row[value.fieldName] : value;
1097
+ const nullish = (entry) => entry === null || entry === void 0;
1098
+ if (nullish(fieldValue) || nullish(compareValue)) return "unknown";
1099
+ switch (filter.operator) {
1100
+ case "like":
1101
+ if (typeof fieldValue !== "string" || typeof compareValue !== "string") return false;
1102
+ return matchLike(fieldValue, compareValue, false);
1103
+ case "ilike":
1104
+ if (typeof fieldValue !== "string" || typeof compareValue !== "string") return false;
1105
+ return matchLike(fieldValue, compareValue, true);
1106
+ case "notLike":
1107
+ if (typeof fieldValue !== "string" || typeof compareValue !== "string") return false;
1108
+ return !matchLike(fieldValue, compareValue, false);
1109
+ case "notIlike":
1110
+ if (typeof fieldValue !== "string" || typeof compareValue !== "string") return false;
1111
+ return !matchLike(fieldValue, compareValue, true);
1112
+ case "startsWith":
1113
+ if (typeof fieldValue !== "string" || typeof compareValue !== "string") return false;
1114
+ return fieldValue.startsWith(compareValue);
1115
+ case "endsWith":
1116
+ if (typeof fieldValue !== "string" || typeof compareValue !== "string") return false;
1117
+ return fieldValue.endsWith(compareValue);
1118
+ case "contains":
1119
+ if (typeof fieldValue !== "string" || typeof compareValue !== "string") return false;
1120
+ return fieldValue.includes(compareValue);
1121
+ case "eq": return fieldValue === compareValue;
1122
+ case "ne": return fieldValue !== compareValue;
1123
+ case "gt": return fieldValue > compareValue;
1124
+ case "gte": return fieldValue >= compareValue;
1125
+ case "lt": return fieldValue < compareValue;
1126
+ case "lte": return fieldValue <= compareValue;
1127
+ case "inArray": {
1128
+ const arr = compareValue;
1129
+ if (!Array.isArray(arr)) return false;
1130
+ if (arr.includes(fieldValue)) return true;
1131
+ return arr.some(nullish) ? "unknown" : false;
1132
+ }
1133
+ case "notInArray": {
1134
+ const arr = compareValue;
1135
+ if (!Array.isArray(arr)) return false;
1136
+ if (arr.includes(fieldValue)) return false;
1137
+ return arr.some(nullish) ? "unknown" : true;
1138
+ }
1139
+ case "arrayContains": {
1140
+ if (!Array.isArray(fieldValue)) return false;
1141
+ const arr = compareValue;
1142
+ if (!Array.isArray(arr)) return false;
1143
+ return arr.every((item) => fieldValue.includes(item));
1144
+ }
1145
+ case "arrayContained": {
1146
+ if (!Array.isArray(fieldValue)) return false;
1147
+ const arr = compareValue;
1148
+ if (!Array.isArray(arr)) return false;
1149
+ return fieldValue.every((item) => arr.includes(item));
1150
+ }
1151
+ case "arrayOverlaps": {
1152
+ if (!Array.isArray(fieldValue)) return false;
1153
+ const arr = compareValue;
1154
+ if (!Array.isArray(arr)) return false;
1155
+ return fieldValue.some((item) => arr.includes(item));
1156
+ }
1157
+ default: throw new Error(`Unsupported operator: ${filter.operator}`);
1158
+ }
1159
+ }
1160
+ if (filter.type === "unary") {
1161
+ const [operand] = filter.operands;
1162
+ if (isFieldReference(operand)) {
1163
+ const fieldValue = row[operand.fieldName];
1164
+ switch (filter.operator) {
1165
+ case "isNull": return fieldValue === null || fieldValue === void 0;
1166
+ case "isNotNull": return fieldValue !== null && fieldValue !== void 0;
1167
+ default: throw new Error(`Unsupported unary operator: ${filter.operator}`);
1168
+ }
1169
+ }
1170
+ if (filter.operator === "not") return triNot(evaluateCheckConstraintTriState(row, operand));
1171
+ throw new Error("Unary expression must have FieldReference or FilterExpression as operand");
1172
+ }
1173
+ if (filter.type === "logical") {
1174
+ if (filter.operator === "and") return triAnd(filter.operands.map((operand) => evaluateCheckConstraintTriState(row, operand)));
1175
+ if (filter.operator === "or") return triOr(filter.operands.map((operand) => evaluateCheckConstraintTriState(row, operand)));
1176
+ }
1177
+ throw new Error(`Unsupported filter type for check: ${filter.type}`);
1178
+ }
1179
+ function enforceCheckConstraints(table, candidate) {
1180
+ const checks = getChecks(table);
1181
+ if (checks.length === 0) return;
1182
+ const tableName = getTableName(table);
1183
+ for (const check of checks) if (evaluateCheckConstraintTriState(candidate, check.expression) === false) throw new Error(`Check constraint '${check.name}' violation on '${tableName}'.`);
1184
+ }
1185
+ function toConvexFilter(expression) {
1186
+ const visitor = {
1187
+ visitBinary: (expr) => {
1188
+ const [field, value] = expr.operands;
1189
+ if (!isFieldReference(field)) throw new Error("Binary expression must have FieldReference as first operand");
1190
+ const fieldName = field.fieldName;
1191
+ switch (expr.operator) {
1192
+ case "eq": return (q) => q.eq(q.field(fieldName), value);
1193
+ case "ne": return (q) => q.neq(q.field(fieldName), value);
1194
+ case "gt": return (q) => q.gt(q.field(fieldName), value);
1195
+ case "gte": return (q) => q.gte(q.field(fieldName), value);
1196
+ case "lt": return (q) => q.lt(q.field(fieldName), value);
1197
+ case "lte": return (q) => q.lte(q.field(fieldName), value);
1198
+ case "inArray": {
1199
+ const values = value;
1200
+ return (q) => {
1201
+ if (values.length === 0) return q.eq(q.field("_id"), "__better_convex_never__");
1202
+ return values.map((v) => q.eq(q.field(fieldName), v)).reduce((acc, cond) => q.or(acc, cond));
1203
+ };
1204
+ }
1205
+ case "notInArray": {
1206
+ const values = value;
1207
+ return (q) => {
1208
+ return values.map((v) => q.neq(q.field(fieldName), v)).reduce((acc, cond) => q.and(acc, cond));
1209
+ };
1210
+ }
1211
+ case "like":
1212
+ case "ilike":
1213
+ case "notLike":
1214
+ case "notIlike":
1215
+ case "startsWith":
1216
+ case "endsWith":
1217
+ case "contains":
1218
+ case "arrayContains":
1219
+ case "arrayContained":
1220
+ case "arrayOverlaps": return () => true;
1221
+ default: throw new Error(`Unsupported binary operator: ${expr.operator}`);
1222
+ }
1223
+ },
1224
+ visitLogical: (expr) => {
1225
+ const operandFns = expr.operands.map((op) => op.accept(visitor));
1226
+ if (expr.operator === "and") return (q) => {
1227
+ let result = operandFns[0](q);
1228
+ for (let i = 1; i < operandFns.length; i++) result = q.and(result, operandFns[i](q));
1229
+ return result;
1230
+ };
1231
+ if (expr.operator === "or") return (q) => {
1232
+ let result = operandFns[0](q);
1233
+ for (let i = 1; i < operandFns.length; i++) result = q.or(result, operandFns[i](q));
1234
+ return result;
1235
+ };
1236
+ throw new Error(`Unsupported logical operator: ${expr.operator}`);
1237
+ },
1238
+ visitUnary: (expr) => {
1239
+ const operand = expr.operands[0];
1240
+ if (expr.operator === "not") {
1241
+ const operandFn = operand.accept(visitor);
1242
+ return (q) => q.not(operandFn(q));
1243
+ }
1244
+ if (expr.operator === "isNull") {
1245
+ if (!isFieldReference(operand)) throw new Error("isNull must operate on a field reference");
1246
+ const fieldName = operand.fieldName;
1247
+ return (q) => q.eq(q.field(fieldName), null);
1248
+ }
1249
+ if (expr.operator === "isNotNull") {
1250
+ if (!isFieldReference(operand)) throw new Error("isNotNull must operate on a field reference");
1251
+ const fieldName = operand.fieldName;
1252
+ return (q) => q.neq(q.field(fieldName), null);
1253
+ }
1254
+ throw new Error(`Unsupported unary operator: ${expr.operator}`);
1255
+ }
1256
+ };
1257
+ return expression.accept(visitor);
1258
+ }
1259
+
1260
+ //#endregion
1261
+ //#region src/orm/aggregate-index/errors.ts
1262
+ const COUNT_ERROR = {
1263
+ FILTER_UNSUPPORTED: "COUNT_FILTER_UNSUPPORTED",
1264
+ NOT_INDEXED: "COUNT_NOT_INDEXED",
1265
+ INDEX_BUILDING: "COUNT_INDEX_BUILDING",
1266
+ RLS_UNSUPPORTED: "COUNT_RLS_UNSUPPORTED"
1267
+ };
1268
+ const AGGREGATE_ERROR = {
1269
+ ARGS_UNSUPPORTED: "AGGREGATE_ARGS_UNSUPPORTED",
1270
+ FILTER_UNSUPPORTED: "AGGREGATE_FILTER_UNSUPPORTED",
1271
+ NOT_INDEXED: "AGGREGATE_NOT_INDEXED",
1272
+ INDEX_BUILDING: "AGGREGATE_INDEX_BUILDING",
1273
+ RLS_UNSUPPORTED: "AGGREGATE_RLS_UNSUPPORTED"
1274
+ };
1275
+ const createError = (code, message) => /* @__PURE__ */ new Error(`${code}: ${message}`);
1276
+ const createCountError = (code, message) => createError(code, message);
1277
+ const createAggregateError = (code, message) => createError(code, message);
1278
+ const assertAggregateAllowedForRls = (tableConfig, rlsMode, code, methodName) => {
1279
+ if ((typeof tableConfig.table.isRlsEnabled === "function" ? tableConfig.table.isRlsEnabled() : false) && rlsMode !== "skip") throw createError(code, `${methodName} is not available for table '${tableConfig.name}' in RLS-restricted contexts in v1.`);
1280
+ };
1281
+ const ensureCountAllowedForRls = (tableConfig, rlsMode) => {
1282
+ assertAggregateAllowedForRls(tableConfig, rlsMode, COUNT_ERROR.RLS_UNSUPPORTED, "count()");
1283
+ };
1284
+ const ensureAggregateAllowedForRls = (tableConfig, rlsMode, methodName) => {
1285
+ assertAggregateAllowedForRls(tableConfig, rlsMode, AGGREGATE_ERROR.RLS_UNSUPPORTED, methodName);
1286
+ };
1287
+
1288
+ //#endregion
1289
+ //#region src/orm/aggregate-index/definitions.ts
1290
+ const getAggregateIndexDefinitions = (tableConfig) => {
1291
+ const aggregateIndexes = tableConfig.table.getAggregateIndexes?.();
1292
+ if (!Array.isArray(aggregateIndexes)) return [];
1293
+ return aggregateIndexes.map((entry) => ({
1294
+ name: entry.name,
1295
+ fields: entry.fields ?? [],
1296
+ countFields: entry.countFields ?? [],
1297
+ sumFields: entry.sumFields ?? [],
1298
+ avgFields: entry.avgFields ?? [],
1299
+ minFields: entry.minFields ?? [],
1300
+ maxFields: entry.maxFields ?? []
1301
+ }));
1302
+ };
1303
+ const getRankIndexDefinitions = (tableConfig) => {
1304
+ const rankIndexes = tableConfig.table.getRankIndexes?.();
1305
+ if (!Array.isArray(rankIndexes)) return [];
1306
+ return rankIndexes.map((entry) => ({
1307
+ name: entry.name,
1308
+ partitionFields: entry.partitionFields ?? [],
1309
+ orderFields: entry.orderFields ?? [],
1310
+ sumField: entry.sumField
1311
+ }));
1312
+ };
1313
+
1314
+ //#endregion
1315
+ //#region src/orm/aggregate-index/schema.ts
1316
+ const AGGREGATE_BUCKET_TABLE = "aggregate_bucket";
1317
+ const AGGREGATE_MEMBER_TABLE = "aggregate_member";
1318
+ const AGGREGATE_EXTREMA_TABLE = "aggregate_extrema";
1319
+ const AGGREGATE_RANK_TREE_TABLE = "aggregate_rank_tree";
1320
+ const AGGREGATE_RANK_NODE_TABLE = "aggregate_rank_node";
1321
+ const AGGREGATE_STATE_TABLE = "aggregate_state";
1322
+ const countBucketTable = convexTable(AGGREGATE_BUCKET_TABLE, {
1323
+ tableKey: text().notNull(),
1324
+ indexName: text().notNull(),
1325
+ keyHash: text().notNull(),
1326
+ keyParts: arrayOf(json()).notNull(),
1327
+ count: integer().notNull(),
1328
+ sumValues: objectOf(integer().notNull()).notNull(),
1329
+ nonNullCountValues: objectOf(integer().notNull()).notNull(),
1330
+ updatedAt: integer().notNull()
1331
+ }, (t) => [index("by_table_index_hash").on(t.tableKey, t.indexName, t.keyHash), index("by_table_index").on(t.tableKey, t.indexName)]);
1332
+ const countMemberTable = convexTable(AGGREGATE_MEMBER_TABLE, {
1333
+ kind: text().notNull(),
1334
+ tableKey: text().notNull(),
1335
+ indexName: text().notNull(),
1336
+ docId: text().notNull(),
1337
+ keyHash: text().notNull(),
1338
+ keyParts: arrayOf(json()).notNull(),
1339
+ sumValues: objectOf(integer().notNull()).notNull(),
1340
+ nonNullCountValues: objectOf(integer().notNull()).notNull(),
1341
+ extremaValues: objectOf(json()).notNull(),
1342
+ rankNamespace: json(),
1343
+ rankKey: json(),
1344
+ rankSumValue: integer(),
1345
+ updatedAt: integer().notNull()
1346
+ }, (t) => [index("by_kind_table_index_doc").on(t.kind, t.tableKey, t.indexName, t.docId), index("by_kind_table_index").on(t.kind, t.tableKey, t.indexName)]);
1347
+ const countExtremaTable = convexTable(AGGREGATE_EXTREMA_TABLE, {
1348
+ tableKey: text().notNull(),
1349
+ indexName: text().notNull(),
1350
+ keyHash: text().notNull(),
1351
+ fieldName: text().notNull(),
1352
+ valueHash: text().notNull(),
1353
+ value: json().notNull(),
1354
+ sortKey: text().notNull(),
1355
+ count: integer().notNull(),
1356
+ updatedAt: integer().notNull()
1357
+ }, (t) => [
1358
+ index("by_table_index").on(t.tableKey, t.indexName),
1359
+ index("by_table_index_hash_field_value").on(t.tableKey, t.indexName, t.keyHash, t.fieldName, t.valueHash),
1360
+ index("by_table_index_hash_field_sort").on(t.tableKey, t.indexName, t.keyHash, t.fieldName, t.sortKey)
1361
+ ]);
1362
+ const countStateTable = convexTable(AGGREGATE_STATE_TABLE, {
1363
+ kind: text().notNull(),
1364
+ tableKey: text().notNull(),
1365
+ indexName: text().notNull(),
1366
+ keyDefinitionHash: text().notNull(),
1367
+ metricDefinitionHash: text().notNull(),
1368
+ status: text().notNull(),
1369
+ cursor: text(),
1370
+ processed: integer().notNull(),
1371
+ startedAt: integer().notNull(),
1372
+ updatedAt: integer().notNull(),
1373
+ completedAt: integer(),
1374
+ lastError: text()
1375
+ }, (t) => [
1376
+ index("by_kind_table_index").on(t.kind, t.tableKey, t.indexName),
1377
+ index("by_kind_status").on(t.kind, t.status),
1378
+ index("by_table_status").on(t.tableKey, t.status)
1379
+ ]);
1380
+ const rankTreeTable = convexTable(AGGREGATE_RANK_TREE_TABLE, {
1381
+ aggregateName: text().notNull(),
1382
+ maxNodeSize: integer().notNull(),
1383
+ namespace: json(),
1384
+ root: id(AGGREGATE_RANK_NODE_TABLE).notNull()
1385
+ }, (tree) => [index("by_namespace").on(tree.namespace), index("by_aggregate_name").on(tree.aggregateName)]);
1386
+ const rankNodeTable = convexTable(AGGREGATE_RANK_NODE_TABLE, {
1387
+ aggregate: objectOf({
1388
+ count: integer().notNull(),
1389
+ sum: integer().notNull()
1390
+ }),
1391
+ items: arrayOf(objectOf({
1392
+ k: json(),
1393
+ v: json(),
1394
+ s: integer().notNull()
1395
+ })).notNull(),
1396
+ subtrees: arrayOf(text().notNull()).notNull()
1397
+ });
1398
+ const aggregateStorageTables = {
1399
+ [AGGREGATE_BUCKET_TABLE]: countBucketTable,
1400
+ [AGGREGATE_MEMBER_TABLE]: countMemberTable,
1401
+ [AGGREGATE_EXTREMA_TABLE]: countExtremaTable,
1402
+ [AGGREGATE_RANK_TREE_TABLE]: rankTreeTable,
1403
+ [AGGREGATE_RANK_NODE_TABLE]: rankNodeTable,
1404
+ [AGGREGATE_STATE_TABLE]: countStateTable
1405
+ };
1406
+ function aggregateExtension() {
1407
+ return defineSchemaExtension("aggregate", aggregateStorageTables);
1408
+ }
1409
+
1410
+ //#endregion
1411
+ export { serializeFilterExpression as $, ensureNonNullValues as A, getMutationExecutionMode as B, deserializeFilterExpression as C, enforcePolymorphicWrite as D, enforceForeignKeys as E, getChecks as F, getUniqueIndexes as G, getTableColumns as H, getColumnName as I, normalizeDateFieldsForWrite as J, hardDeleteRow as K, getForeignKeys as L, evaluateCheckConstraintTriState as M, evaluateFilter as N, enforceUniqueIndexes as O, extractPrimaryIdLookup as P, selectReturningRowWithHydration as Q, getMutationAsyncDelayMs as R, decodeUndefinedDeep as S, enforceCheckConstraints as T, getTableDeleteConfig as U, getOrmContext as V, getTableName as W, patchReferencingRows as X, normalizeTemporalComparableValue as Y, resolveOrmRuntimeDefaults as Z, applyIncomingForeignKeyActionsOnUpdate as _, usesSystemCreatedAtAlias as _t, aggregateExtension as a, markLifecycleHookedTables as at, collectMutationRowsBounded as b, AGGREGATE_ERROR as c, findSearchIndexByName as ct, createCountError as d, getIndexes as dt, softDeleteRow as et, createError as f, getRankIndexes as ft, applyIncomingForeignKeyActionsOnDelete as g, PUBLIC_CREATED_AT_FIELD as gt, applyDefaults as h, INTERNAL_CREATION_TIME_FIELD as ht, AGGREGATE_STATE_TABLE as i, mapWithConcurrency as it, ensureNullableColumns as j, ensureDefaultColumns as k, COUNT_ERROR as l, findVectorIndexByName as lt, ensureCountAllowedForRls as m, CREATED_AT_MIGRATION_MESSAGE as mt, AGGREGATE_EXTREMA_TABLE as n, takeRowsWithinByteBudget as nt, getAggregateIndexDefinitions as o, findIndexForColumns as ot, ensureAggregateAllowedForRls as p, resolveIndexOrderPushdown as pt, hydrateDateFieldsForRead as q, AGGREGATE_MEMBER_TABLE as r, toConvexFilter as rt, getRankIndexDefinitions as s, findRelationIndex as st, AGGREGATE_BUCKET_TABLE as t, splitReturningSelection as tt, createAggregateError as u, getAggregateIndexes as ut, buildForeignKeyGraph as v, encodeUndefinedDeep as w, collectPrimaryIdLookupRows as x, canUsePrimaryIdLookupCursor as y, getMutationCollectionLimits as z };