kitcn 0.24.0 → 0.25.1

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-C6JSHkRK.js} +9 -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 +94 -58
  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-ivbc6-_g.js} +2 -2
  21. package/dist/definitions-Dzk8mSTL.js +215 -0
  22. package/dist/extensions-Blzsyekm.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-CuSfWa5q.js +37 -0
  27. package/dist/{local-env-yFKub75x.mjs → local-env-Dkh4a_BK.mjs} +2244 -2113
  28. package/dist/{middleware-Br5zubu7.d.ts → middleware-CE_MC6Gi.d.ts} +1 -1
  29. package/dist/{middleware-DUd1Sj39.js → middleware-CdqNVyp8.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 +127 -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-CgLWu5F9.js} +2 -2
  39. package/dist/{procedure-name-C20pFZnk.d.ts → procedure-name-Bjw1JDIS.d.ts} +7 -4
  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-BdqTbgKh.js +1080 -0
  46. package/dist/schema-BQd2Xrfb.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-CX2lnX7e.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-CIoUYCqO.js} +2 -1
  53. package/dist/{validators-BhsByJeg.d.ts → validators-wOIjhkfN.d.ts} +7 -1
  54. package/dist/watcher.mjs +1 -1
  55. package/dist/where-clause-compiler-Dmq4HMrK.d.ts +2089 -0
  56. package/package.json +6 -4
  57. package/skills/kitcn/SKILL.md +19 -18
  58. package/skills/kitcn/references/features/aggregates.md +21 -0
  59. package/skills/kitcn/references/features/auth-organizations.md +3 -3
  60. package/skills/kitcn/references/features/auth-polar.md +2 -1
  61. package/skills/kitcn/references/features/auth.md +8 -1
  62. package/skills/kitcn/references/setup/auth.md +52 -42
  63. package/skills/kitcn/references/setup/index.md +1 -1
  64. package/skills/kitcn/references/setup/server.md +2 -1
  65. /package/dist/{api-entry-N3nBOlI2.js → api-entry-CkDpGYVg.js} +0 -0
  66. /package/dist/{context-utils-BBUtBqjN.d.ts → context-utils-DwZ3Cam1.d.ts} +0 -0
  67. /package/dist/{create-schema-BXrKE2YY.js → create-schema-BRFjdgRr.js} +0 -0
  68. /package/dist/{error-Bvo7YEhk.js → error-CMLeCadS.js} +0 -0
  69. /package/dist/{generated-contract-disabled-BXaz7JCE.js → generated-contract-disabled-CZa0iyV0.js} +0 -0
  70. /package/dist/{meta-utils-D9K4fICl.js → meta-utils-D2lqN2Lg.js} +0 -0
  71. /package/dist/{query-context-CNo9ffvI.d.ts → query-context-DJONf8X5.d.ts} +0 -0
  72. /package/dist/{query-options-CzRV4G4N.js → query-options-DUiOj01a.js} +0 -0
  73. /package/dist/{token-B9Bjcqug.d.ts → token-kQaqFby4.d.ts} +0 -0
  74. /package/dist/{transformer-yZuBWo8v.js → transformer-D8wO-kEj.js} +0 -0
  75. /package/dist/{types-BCl8gfGy.d.ts → types-Bf3XQex5.d.ts} +0 -0
  76. /package/dist/{types-DF2cg_w0.d.ts → types-MHYXboyy.d.ts} +0 -0
  77. /package/dist/{types-CnTpHR1F.d.ts → types-eNzQmbqM.d.ts} +0 -0
  78. /package/dist/{upstream-BR6sBLg3.js → upstream-BCgGZX6q.js} +0 -0
@@ -4,8 +4,8 @@ import fs from "node:fs";
4
4
  import path, { join } from "node:path";
5
5
  import { createHash } from "node:crypto";
6
6
  import { v } from "convex/values";
7
- import "convex/server";
8
7
  import { createColors } from "picocolors";
8
+ import "convex/server";
9
9
 
10
10
  //#region src/orm/builders/column-builder.ts
11
11
  /**
@@ -220,2213 +220,2229 @@ const OrmSchemaRelations = Symbol.for("kitcn:OrmSchemaRelations");
220
220
  const OrmSchemaTriggers = Symbol.for("kitcn:OrmSchemaTriggers");
221
221
 
222
222
  //#endregion
223
- //#region src/orm/builders/convex-column-builder.ts
224
- /**
225
- * Convex-specific column builder base class
226
- *
227
- * All Convex column builders (ConvexTextBuilder, ConvexIntegerBuilder, etc.)
228
- * inherit from this class.
229
- */
230
- var ConvexColumnBuilder = class extends ColumnBuilder {
231
- static [entityKind] = "ConvexColumnBuilder";
232
- };
223
+ //#region src/orm/index-utils.ts
224
+ function getIndexes(table) {
225
+ const indexes = table.getIndexes?.();
226
+ return Array.isArray(indexes) ? indexes : [];
227
+ }
228
+ function getAggregateIndexes(table) {
229
+ const indexes = table.getAggregateIndexes?.();
230
+ return Array.isArray(indexes) ? indexes : [];
231
+ }
232
+ function getRankIndexes(table) {
233
+ const indexes = table.getRankIndexes?.();
234
+ return Array.isArray(indexes) ? indexes : [];
235
+ }
233
236
 
234
237
  //#endregion
235
- //#region src/orm/builders/boolean.ts
236
- /**
237
- * Boolean column builder class
238
- * Compiles to v.boolean() or v.optional(v.boolean())
239
- */
240
- var ConvexBooleanBuilder = class extends ConvexColumnBuilder {
241
- static [entityKind] = "ConvexBooleanBuilder";
242
- constructor(name) {
243
- super(name, "boolean", "ConvexBoolean");
244
- }
245
- /**
246
- * Expose Convex validator for schema integration
247
- */
248
- get convexValidator() {
249
- if (this.config.notNull) return v.boolean();
250
- return v.optional(v.union(v.null(), v.boolean()));
251
- }
252
- /**
253
- * Compile to Convex validator
254
- * .notNull() → v.boolean()
255
- * nullable → v.optional(v.boolean())
256
- */
257
- build() {
258
- return this.convexValidator;
238
+ //#region src/cli/utils/highlighter.ts
239
+ const isTruthyEnvFlag = (value) => value !== void 0 && value !== "" && value !== "0";
240
+ const isColorEnabled = () => {
241
+ if (process.env.FORCE_COLOR === "0") return false;
242
+ if (isTruthyEnvFlag(process.env.FORCE_COLOR)) return true;
243
+ if (isTruthyEnvFlag(process.env.NO_COLOR)) return false;
244
+ return Boolean(process.stdout.isTTY && process.env.TERM !== "dumb");
245
+ };
246
+ const withColors = (callback) => callback(createColors(isColorEnabled()));
247
+ const highlighter = {
248
+ bold(value) {
249
+ return withColors((colors) => colors.bold(value));
250
+ },
251
+ dim(value) {
252
+ return withColors((colors) => colors.dim(value));
253
+ },
254
+ info(value) {
255
+ return withColors((colors) => colors.cyan(value));
256
+ },
257
+ success(value) {
258
+ return withColors((colors) => colors.green(value));
259
+ },
260
+ warn(value) {
261
+ return withColors((colors) => colors.yellow(value));
262
+ },
263
+ error(value) {
264
+ return withColors((colors) => colors.red(value));
265
+ },
266
+ path(value) {
267
+ return withColors((colors) => colors.bold(value));
259
268
  }
260
269
  };
261
- function boolean$1(name) {
262
- return new ConvexBooleanBuilder(name ?? "");
263
- }
264
270
 
265
271
  //#endregion
266
- //#region src/internal/upstream/validators.ts
267
- /** @deprecated Use `v.string()` instead. Any string value. */
268
- const string = v.string();
269
- /** @deprecated Use `v.float64()` instead. JavaScript number, represented as a float64 in the database. */
270
- const number = v.float64();
271
- /** @deprecated Use `v.float64()` instead. JavaScript number, represented as a float64 in the database. */
272
- const float64 = v.float64();
273
- /** @deprecated Use `v.boolean()` instead. boolean value. For typing it only as true, use `l(true)` */
274
- const boolean = v.boolean();
275
- /** @deprecated Use `v.int64()` instead. bigint, though stored as an int64 in the database. */
276
- const biging = v.int64();
277
- /** @deprecated Use `v.int64()` instead. bigint, though stored as an int64 in the database. */
278
- const int64 = v.int64();
279
- /** @deprecated Use `v.any()` instead. Any Convex value */
280
- const any = v.any();
281
- /** @deprecated Use `v.null()` instead. Null value. Underscore is so it doesn't shadow the null builtin */
282
- const null_ = v.null();
283
- /** @deprecated Use `v.*()` instead. */
284
- const { id: id$1, object, array, bytes, literal, optional, union } = v;
285
- /** @deprecated Use `v.bytes()` instead. ArrayBuffer validator. */
286
- const arrayBuffer = v.bytes();
287
- /** Mark fields as deprecated with this permissive validator typed as null */
288
- const deprecated = v.optional(v.any());
272
+ //#region src/cli/utils/lazy-deps.ts
289
273
  /**
290
- * Converts an optional validator to a required validator.
291
- *
292
- * This is the inverse of `v.optional()`. It takes a validator that may be optional
293
- * and returns the equivalent required validator.
294
- *
295
- * ```ts
296
- * const optionalString = v.optional(v.string());
297
- * const requiredString = vRequired(optionalString); // v.string()
274
+ * Heavy CLI-only dependencies, resolved on first use instead of at module init.
298
275
  *
299
- * // Already required validators are returned as-is
300
- * const alreadyRequired = v.string();
301
- * const stillRequired = vRequired(alreadyRequired); // v.string()
302
- * ```
276
+ * `kitcn --version`, `kitcn --help` and every `--json` call an agent makes must
277
+ * not pay to boot esbuild's native service, Babel's parser, jiti, dotenv or the
278
+ * interactive prompt stack — the CLI is non-interactive by default and most
279
+ * commands touch none of them.
303
280
  *
304
- * @param validator The validator to make required.
305
- * @returns A required version of the validator.
281
+ * Each accessor is memoized and stays synchronous so call sites keep their
282
+ * current signatures, and each dependency is loaded through exactly one
283
+ * mechanism: `@clack/prompts` compares against a module-private `Symbol` in
284
+ * `isCancel`, so a second copy of the module would silently break cancellation.
306
285
  */
307
- function vRequired(validator) {
308
- const { kind, isOptional } = validator;
309
- if (isOptional === "required") return validator;
310
- switch (kind) {
311
- case "id": return v.id(validator.tableName);
312
- case "string": return v.string();
313
- case "float64": return v.float64();
314
- case "int64": return v.int64();
315
- case "boolean": return v.boolean();
316
- case "null": return v.null();
317
- case "any": return v.any();
318
- case "literal": return v.literal(validator.value);
319
- case "bytes": return v.bytes();
320
- case "object": return v.object(validator.fields);
321
- case "array": return v.array(validator.element);
322
- case "record": return v.record(validator.key, validator.value);
323
- case "union": return v.union(...validator.members);
324
- default: throw new Error("Unknown Convex validator type: " + kind);
325
- }
326
- }
286
+ const require$1 = createRequire(import.meta.url);
287
+ let babelParser;
288
+ let clackPrompts;
289
+ let dotenv;
290
+ let esbuild;
291
+ let jiti;
292
+ const loadBabelParser = () => babelParser ??= require$1("@babel/parser");
293
+ const loadClackPrompts = () => clackPrompts ??= require$1("@clack/prompts");
294
+ const loadDotenv = () => dotenv ??= require$1("dotenv");
295
+ const loadEsbuild = () => esbuild ??= require$1("esbuild");
296
+ const loadJiti = () => jiti ??= require$1("jiti");
327
297
 
328
298
  //#endregion
329
- //#region src/orm/builders/custom.ts
330
- function isRecord$2(value) {
331
- return typeof value === "object" && value !== null && !Array.isArray(value);
332
- }
333
- function isValidator(value) {
334
- return isRecord$2(value) && typeof value.kind === "string" && typeof value.isOptional === "string";
335
- }
336
- function isColumnBuilder$1(value) {
337
- return isRecord$2(value) && value[entityKind] === "ColumnBuilder";
338
- }
339
- function toRequiredValidator(validator) {
340
- return validator.isOptional === "optional" ? vRequired(validator) : validator;
341
- }
342
- function toRequiredBuilderValidator(validator) {
343
- const requiredValidator = toRequiredValidator(validator);
344
- if (requiredValidator.kind !== "union") return requiredValidator;
345
- const nonNullMembers = requiredValidator.members.filter((member) => member.kind !== "null");
346
- if (nonNullMembers.length !== 1) return requiredValidator;
347
- const [member] = nonNullMembers;
348
- if (member.kind === "object" || member.kind === "array") return member;
349
- return requiredValidator;
350
- }
351
- function formatInvalidInput(path, value) {
352
- return `${path} expected a column builder, Convex validator, or nested object shape. Got ${Array.isArray(value) ? "array" : value === null ? "null" : typeof value}.`;
353
- }
354
- function objectShapeToValidator(shape, path) {
355
- const fields = {};
356
- for (const [key, value] of Object.entries(shape)) fields[key] = nestedInputToValidator(value, `${path}.${key}`);
357
- return v.object(fields);
358
- }
359
- function nestedInputToValidator(input, path) {
360
- if (isColumnBuilder$1(input)) return toRequiredBuilderValidator(input.convexValidator);
361
- if (isValidator(input)) return toRequiredValidator(input);
362
- if (isRecord$2(input)) return objectShapeToValidator(input, path);
363
- throw new Error(formatInvalidInput(path, input));
364
- }
365
- var ConvexCustomBuilder = class extends ConvexColumnBuilder {
366
- static [entityKind] = "ConvexCustomBuilder";
367
- constructor(name, validator) {
368
- super(name, "any", "ConvexCustom");
369
- this.config.validator = validator;
370
- }
371
- get convexValidator() {
372
- const validator = this.config.validator;
373
- if (this.config.notNull) return validator;
374
- return v.optional(v.union(v.null(), validator));
375
- }
376
- build() {
377
- return this.convexValidator;
378
- }
379
- };
380
- function custom(a, b) {
381
- if (b !== void 0) return new ConvexCustomBuilder(a, b);
382
- return new ConvexCustomBuilder("", a);
383
- }
384
- /**
385
- * Creates an array column from a nested validator or builder.
386
- *
387
- * Values in nested arrays are always compiled as required validators.
388
- */
389
- function arrayOf(element) {
390
- return custom(v.array(nestedInputToValidator(element, "arrayOf(element)"))).$type();
391
- }
392
- /**
393
- * Creates an object column from either:
394
- * - a nested shape of validators/builders, or
395
- * - a validator/builder describing homogeneous record values
396
- *
397
- * Fields in nested objects are always compiled as required validators.
398
- */
399
- function objectOf(input) {
400
- if (isColumnBuilder$1(input) || isValidator(input)) return custom(v.record(v.string(), nestedInputToValidator(input, "objectOf(value)"))).$type();
401
- if (!isRecord$2(input)) throw new Error(formatInvalidInput("objectOf(shape)", input));
402
- return custom(objectShapeToValidator(input, "objectOf(shape)")).$type();
403
- }
404
- /**
405
- * Convenience wrapper for Convex "JSON" values.
406
- *
407
- * Note: This is Convex JSON (runtime `v.any()`), not SQL JSON/JSONB.
408
- */
409
- function json() {
410
- return custom(v.any()).$type();
411
- }
299
+ //#region src/cli/utils/crpc-builder-stub.ts
300
+ const CRPC_BUILDER_STUB_SOURCE = `const createMiddleware = (handler = undefined) => ({
301
+ _handler: handler,
302
+ pipe(nextHandler = undefined) {
303
+ return createMiddleware(nextHandler);
304
+ },
305
+ });
412
306
 
413
- //#endregion
414
- //#region src/orm/builders/id.ts
415
- /**
416
- * ID column builder class
417
- * Compiles to v.id(tableName) or v.optional(v.id(tableName))
418
- */
419
- var ConvexIdBuilder = class extends ConvexColumnBuilder {
420
- static [entityKind] = "ConvexIdBuilder";
421
- constructor(name, tableName) {
422
- super(name, "string", "ConvexId");
423
- this.tableName = tableName;
424
- this.config.referenceTable = tableName;
425
- }
426
- /**
427
- * Expose Convex validator for schema integration
428
- */
429
- get convexValidator() {
430
- if (this.config.notNull) return v.id(this.tableName);
431
- return v.optional(v.union(v.null(), v.id(this.tableName)));
432
- }
433
- /**
434
- * Compile to Convex validator
435
- * .notNull() → v.id(tableName)
436
- * nullable → v.optional(v.id(tableName))
437
- */
438
- build() {
439
- return this.convexValidator;
440
- }
307
+ const toMetaObject = (value = undefined) =>
308
+ value && typeof value === "object" ? value : {};
309
+
310
+ const createProcedureExport = (type, state, handler) => ({
311
+ _crpcMeta: {
312
+ type,
313
+ internal: state.internal ?? false,
314
+ ...toMetaObject(state.meta),
315
+ },
316
+ ...(state.httpRoute ? { _crpcHttpRoute: state.httpRoute } : {}),
317
+ _handler: handler,
318
+ });
319
+
320
+ const createProcedureBuilder = (state = {}) => {
321
+ const builder = {
322
+ internal() {
323
+ return createProcedureBuilder({ ...state, internal: true });
324
+ },
325
+ use() {
326
+ return createProcedureBuilder(state);
327
+ },
328
+ meta(value = undefined) {
329
+ return createProcedureBuilder({
330
+ ...state,
331
+ meta: {
332
+ ...toMetaObject(state.meta),
333
+ ...toMetaObject(value),
334
+ },
335
+ });
336
+ },
337
+ input() {
338
+ return createProcedureBuilder(state);
339
+ },
340
+ params() {
341
+ return createProcedureBuilder(state);
342
+ },
343
+ searchParams() {
344
+ return createProcedureBuilder(state);
345
+ },
346
+ paginated(options = undefined) {
347
+ return createProcedureBuilder({
348
+ ...state,
349
+ meta:
350
+ typeof options?.limit === "number"
351
+ ? {
352
+ ...toMetaObject(state.meta),
353
+ limit: options.limit,
354
+ }
355
+ : state.meta,
356
+ });
357
+ },
358
+ output() {
359
+ return createProcedureBuilder(state);
360
+ },
361
+ form() {
362
+ return createProcedureBuilder(state);
363
+ },
364
+ route(path, method) {
365
+ return createProcedureBuilder({
366
+ ...state,
367
+ httpRoute:
368
+ typeof path === "string" && typeof method === "string"
369
+ ? { path, method: method.toUpperCase() }
370
+ : undefined,
371
+ });
372
+ },
373
+ get(path) {
374
+ return builder.route(path, "GET");
375
+ },
376
+ post(path) {
377
+ return builder.route(path, "POST");
378
+ },
379
+ put(path) {
380
+ return builder.route(path, "PUT");
381
+ },
382
+ patch(path) {
383
+ return builder.route(path, "PATCH");
384
+ },
385
+ delete(path) {
386
+ return builder.route(path, "DELETE");
387
+ },
388
+ query(handler = undefined) {
389
+ return createProcedureExport("query", state, handler);
390
+ },
391
+ mutation(handler = undefined) {
392
+ return createProcedureExport("mutation", state, handler);
393
+ },
394
+ action(handler = undefined) {
395
+ return createProcedureExport("action", state, handler);
396
+ },
397
+ middleware(handler = undefined) {
398
+ return createMiddleware(handler);
399
+ },
400
+ };
401
+
402
+ return builder;
441
403
  };
442
- function id(tableName) {
443
- return new ConvexIdBuilder("", tableName);
444
- }
445
404
 
446
- //#endregion
447
- //#region src/orm/builders/number.ts
448
- /**
449
- * Number column builder class
450
- * Compiles to v.number() or v.optional(v.number())
451
- */
452
- var ConvexNumberBuilder = class extends ConvexColumnBuilder {
453
- static [entityKind] = "ConvexNumberBuilder";
454
- constructor(name) {
455
- super(name, "number", "ConvexNumber");
456
- }
457
- /**
458
- * Expose Convex validator for schema integration
459
- */
460
- get convexValidator() {
461
- if (this.config.notNull) return v.number();
462
- return v.optional(v.union(v.null(), v.number()));
463
- }
464
- /**
465
- * Compile to Convex validator
466
- * .notNull() → v.number()
467
- * nullable → v.optional(v.number())
468
- */
469
- build() {
470
- return this.convexValidator;
471
- }
405
+ const flattenRouterRecord = (record = {}, prefix = "") => {
406
+ const procedures = {};
407
+ for (const [key, value] of Object.entries(record)) {
408
+ const procedurePath = prefix ? prefix + "." + key : key;
409
+ if (value?._def?.router === true) {
410
+ Object.assign(
411
+ procedures,
412
+ flattenRouterRecord(value._def.record ?? {}, procedurePath)
413
+ );
414
+ continue;
415
+ }
416
+ procedures[procedurePath] = value;
417
+ }
418
+ return procedures;
472
419
  };
473
- function integer(name) {
474
- return new ConvexNumberBuilder(name ?? "");
475
- }
476
420
 
477
- //#endregion
478
- //#region src/orm/builders/text.ts
479
- /**
480
- * Text column builder class
481
- * Compiles to v.string() or v.optional(v.string())
482
- */
483
- var ConvexTextBuilder = class extends ConvexColumnBuilder {
484
- static [entityKind] = "ConvexTextBuilder";
485
- constructor(name) {
486
- super(name, "string", "ConvexText");
487
- }
488
- /**
489
- * Expose Convex validator for schema integration
490
- */
491
- get convexValidator() {
492
- if (this.config.notNull) return v.string();
493
- return v.optional(v.union(v.null(), v.string()));
494
- }
495
- /**
496
- * Compile to Convex validator
497
- * .notNull() → v.string()
498
- * nullable → v.optional(v.string())
499
- */
500
- build() {
501
- return this.convexValidator;
502
- }
421
+ const createRouter = (record = {}) => ({
422
+ _def: {
423
+ router: true,
424
+ procedures: flattenRouterRecord(record),
425
+ record,
426
+ },
427
+ });
428
+
429
+ export const initCRPC = {
430
+ meta() {
431
+ return this;
432
+ },
433
+ dataModel() {
434
+ return this;
435
+ },
436
+ context() {
437
+ return this;
438
+ },
439
+ middleware(handler = undefined) {
440
+ return createMiddleware(handler);
441
+ },
442
+ create() {
443
+ return {
444
+ query: createProcedureBuilder(),
445
+ mutation: createProcedureBuilder(),
446
+ action: createProcedureBuilder(),
447
+ httpAction: createProcedureBuilder(),
448
+ middleware: createMiddleware,
449
+ router: createRouter,
450
+ };
451
+ },
503
452
  };
504
- function text(name) {
505
- return new ConvexTextBuilder(name ?? "");
506
- }
453
+
454
+ export const httpAction = createProcedureBuilder();
455
+ `;
507
456
 
508
457
  //#endregion
509
- //#region src/orm/extensions.ts
510
- function defineChainMethod(target, key, value) {
511
- Object.defineProperty(target, key, {
512
- value,
513
- enumerable: false,
514
- configurable: true
515
- });
516
- }
517
- function createSchemaExtensionChain(state, capabilities) {
518
- const extension = {
519
- key: state.key,
520
- tables: state.tables
521
- };
522
- Object.defineProperty(extension, OrmSchemaExtensionRelations, {
523
- value: state.relations,
524
- enumerable: false,
525
- configurable: true
526
- });
527
- Object.defineProperty(extension, OrmSchemaExtensionTriggers, {
528
- value: state.triggers,
529
- enumerable: false,
530
- configurable: true
531
- });
532
- if (capabilities.canRelations) defineChainMethod(extension, "relations", (relations) => createSchemaExtensionChain({
533
- ...state,
534
- relations
535
- }, {
536
- canRelations: false,
537
- canTriggers: true
538
- }));
539
- if (capabilities.canTriggers) defineChainMethod(extension, "triggers", (triggers) => createSchemaExtensionChain({
540
- ...state,
541
- triggers
542
- }, {
543
- canRelations: false,
544
- canTriggers: false
545
- }));
546
- return extension;
547
- }
548
- function defineSchemaExtension(key, tables) {
549
- return createSchemaExtensionChain({
550
- key,
551
- tables,
552
- relations: void 0,
553
- triggers: void 0
554
- }, {
555
- canRelations: true,
556
- canTriggers: true
557
- });
458
+ //#region src/cli/utils/project-jiti.ts
459
+ const require = createRequire(import.meta.url);
460
+ const JITI_EXPORT_CONDITION_PRIORITY = [
461
+ "bun",
462
+ "import",
463
+ "module",
464
+ "default",
465
+ "require"
466
+ ];
467
+ const SERVER_PARSER_SHIM_SOURCE = `${CRPC_BUILDER_STUB_SOURCE}
468
+
469
+ export class CRPCError extends Error {
470
+ constructor(options = {}) {
471
+ super(options.message ?? options.code ?? "CRPC error");
472
+ this.code = options.code;
473
+ }
558
474
  }
559
475
 
560
- //#endregion
561
- //#region src/orm/indexes.ts
562
- var ConvexIndexBuilderOn = class {
563
- static [entityKind] = "ConvexIndexBuilderOn";
564
- [entityKind] = "ConvexIndexBuilderOn";
565
- constructor(name, unique) {
566
- this.name = name;
567
- this.unique = unique;
568
- }
569
- on(...columns) {
570
- return new ConvexIndexBuilder(this.name, columns, this.unique);
571
- }
476
+ export const createEnv = ({ schema }) => () =>
477
+ typeof schema?.parse === "function" ? schema.parse(process.env) : process.env;
478
+ export const createHttpRouter = (_app, httpRouter) => httpRouter ?? {};
479
+ export const createCallerFactory = () => () => ({});
480
+ export const createApiLeaf = (fnOrRoot, pathOrMeta, maybeMeta) => {
481
+ const meta = maybeMeta ?? pathOrMeta;
482
+ const fn = Array.isArray(pathOrMeta)
483
+ ? pathOrMeta.reduce((current, segment) => current?.[segment], fnOrRoot)
484
+ : fnOrRoot;
485
+ return Object.assign(fn ?? {}, meta ?? {}, { functionRef: fn });
572
486
  };
573
- var ConvexIndexBuilder = class {
574
- static [entityKind] = "ConvexIndexBuilder";
575
- [entityKind] = "ConvexIndexBuilder";
576
- config;
577
- constructor(name, columns, unique) {
578
- this.config = {
579
- name,
580
- columns,
581
- unique,
582
- where: void 0
583
- };
584
- }
585
- /**
586
- * Partial index conditions are not supported in Convex.
587
- * This method is kept for Drizzle API parity.
588
- */
589
- where(condition) {
590
- this.config.where = condition;
591
- return this;
487
+ export const createGeneratedFunctionReference = (name) => ({
488
+ [Symbol.for("functionName")]: name,
489
+ });
490
+ export const registerProcedureNameLookup = () => {};
491
+ export const typedProcedureResolver = (_functionRef, resolver) => resolver;
492
+ export const createGeneratedRegistryRuntime = () => ({
493
+ getCallerFactory() {
494
+ return () => ({});
495
+ },
496
+ getHandlerFactory() {
497
+ return () => ({});
498
+ },
499
+ });
500
+ `;
501
+ const resolveJitiExportTarget = (target) => {
502
+ if (typeof target === "string") return target;
503
+ if (Array.isArray(target)) {
504
+ for (const entry of target) {
505
+ const resolved = resolveJitiExportTarget(entry);
506
+ if (resolved) return resolved;
507
+ }
508
+ return null;
592
509
  }
593
- };
594
- var ConvexSearchIndexBuilderOn = class {
595
- static [entityKind] = "ConvexSearchIndexBuilderOn";
596
- [entityKind] = "ConvexSearchIndexBuilderOn";
597
- constructor(name) {
598
- this.name = name;
510
+ if (!target || typeof target !== "object") return null;
511
+ const record = target;
512
+ for (const condition of JITI_EXPORT_CONDITION_PRIORITY) {
513
+ const resolved = resolveJitiExportTarget(record[condition]);
514
+ if (resolved) return resolved;
599
515
  }
600
- on(searchField) {
601
- return new ConvexSearchIndexBuilder(this.name, searchField);
516
+ for (const value of Object.values(record)) {
517
+ const resolved = resolveJitiExportTarget(value);
518
+ if (resolved) return resolved;
602
519
  }
520
+ return null;
603
521
  };
604
- var ConvexSearchIndexBuilder = class {
605
- static [entityKind] = "ConvexSearchIndexBuilder";
606
- [entityKind] = "ConvexSearchIndexBuilder";
607
- config;
608
- constructor(name, searchField) {
609
- this.config = {
610
- name,
611
- searchField,
612
- filterFields: [],
613
- staged: false
614
- };
615
- }
616
- filter(...fields) {
617
- this.config.filterFields = fields;
618
- return this;
522
+ const buildLocalPackageExportAliases = (cwd, packageName) => {
523
+ const packageDir = path.join(cwd, "node_modules", ...packageName.split("/"));
524
+ const packageJsonPath = path.join(packageDir, "package.json");
525
+ if (!fs.existsSync(packageJsonPath)) return {};
526
+ const exportsField = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")).exports;
527
+ if (!exportsField || typeof exportsField !== "object" || Array.isArray(exportsField)) return {};
528
+ const aliases = {};
529
+ for (const [exportKey, exportTarget] of Object.entries(exportsField)) {
530
+ const resolvedTarget = resolveJitiExportTarget(exportTarget);
531
+ if (!resolvedTarget || !resolvedTarget.startsWith("./")) continue;
532
+ const specifier = exportKey === "." ? packageName : exportKey.startsWith("./") ? `${packageName}${exportKey.slice(1)}` : null;
533
+ if (!specifier) continue;
534
+ aliases[specifier] = path.join(packageDir, resolvedTarget);
619
535
  }
620
- staged() {
621
- this.config.staged = true;
622
- return this;
536
+ return aliases;
537
+ };
538
+ const ensureServerParserShim = (cwd) => {
539
+ const shimDir = path.join(cwd, "node_modules", ".kitcn");
540
+ const shimPath = path.join(shimDir, "project-jiti-server-shim.mjs");
541
+ fs.mkdirSync(shimDir, { recursive: true });
542
+ if (!fs.existsSync(shimPath) || fs.readFileSync(shimPath, "utf8") !== SERVER_PARSER_SHIM_SOURCE) fs.writeFileSync(shimPath, SERVER_PARSER_SHIM_SOURCE, "utf8");
543
+ return shimPath;
544
+ };
545
+ const trimTsconfigWildcardSuffix = (value) => value.endsWith("/*") ? value.slice(0, -2) : value;
546
+ const loadTypeScript = () => {
547
+ try {
548
+ return require("typescript");
549
+ } catch {
550
+ return null;
623
551
  }
624
552
  };
625
- var ConvexVectorIndexBuilderOn = class {
626
- static [entityKind] = "ConvexVectorIndexBuilderOn";
627
- [entityKind] = "ConvexVectorIndexBuilderOn";
628
- constructor(name) {
629
- this.name = name;
553
+ const buildTsconfigPathAliases = (cwd) => {
554
+ const typescript = loadTypeScript();
555
+ if (!typescript) return {};
556
+ const configPath = typescript.findConfigFile(cwd, fs.existsSync, "tsconfig.json");
557
+ if (!configPath) return {};
558
+ const readResult = typescript.readConfigFile(configPath, typescript.sys.readFile);
559
+ if (readResult.error) return {};
560
+ const parsedConfig = typescript.parseJsonConfigFileContent(readResult.config, typescript.sys, path.dirname(configPath));
561
+ const baseUrl = typeof parsedConfig.options.baseUrl === "string" && parsedConfig.options.baseUrl.length > 0 ? parsedConfig.options.baseUrl : path.dirname(configPath);
562
+ const paths = parsedConfig.options.paths;
563
+ if (!paths) return {};
564
+ const aliases = {};
565
+ for (const [specifier, targets] of Object.entries(paths)) {
566
+ const firstTarget = targets[0];
567
+ if (!firstTarget) continue;
568
+ const aliasKey = trimTsconfigWildcardSuffix(specifier);
569
+ const aliasTarget = trimTsconfigWildcardSuffix(firstTarget);
570
+ if (!aliasKey || aliasKey === "*") continue;
571
+ aliases[aliasKey] = path.resolve(baseUrl, aliasTarget);
630
572
  }
631
- on(vectorField) {
632
- return new ConvexVectorIndexBuilder(this.name, vectorField);
573
+ return aliases;
574
+ };
575
+ const getProjectServerParserShimPath = (cwd = process.cwd()) => ensureServerParserShim(cwd);
576
+ const createProjectJiti = (cwd = process.cwd()) => loadJiti().createJiti(cwd, {
577
+ interopDefault: true,
578
+ jsx: { runtime: "automatic" },
579
+ moduleCache: false,
580
+ tryNative: false,
581
+ alias: {
582
+ ...buildTsconfigPathAliases(cwd),
583
+ ...buildLocalPackageExportAliases(cwd, "kitcn"),
584
+ ...buildLocalPackageExportAliases(cwd, "convex"),
585
+ "kitcn/server": getProjectServerParserShimPath(cwd)
633
586
  }
587
+ });
588
+
589
+ //#endregion
590
+ //#region src/orm/builders/convex-column-builder.ts
591
+ /**
592
+ * Convex-specific column builder base class
593
+ *
594
+ * All Convex column builders (ConvexTextBuilder, ConvexIntegerBuilder, etc.)
595
+ * inherit from this class.
596
+ */
597
+ var ConvexColumnBuilder = class extends ColumnBuilder {
598
+ static [entityKind] = "ConvexColumnBuilder";
634
599
  };
635
- var ConvexAggregateIndexBuilderOn = class {
636
- static [entityKind] = "ConvexAggregateIndexBuilderOn";
637
- [entityKind] = "ConvexAggregateIndexBuilderOn";
600
+
601
+ //#endregion
602
+ //#region src/orm/builders/boolean.ts
603
+ /**
604
+ * Boolean column builder class
605
+ * Compiles to v.boolean() or v.optional(v.boolean())
606
+ */
607
+ var ConvexBooleanBuilder = class extends ConvexColumnBuilder {
608
+ static [entityKind] = "ConvexBooleanBuilder";
638
609
  constructor(name) {
639
- this.name = name;
610
+ super(name, "boolean", "ConvexBoolean");
640
611
  }
641
- on(...columns) {
642
- return new ConvexAggregateIndexBuilder(this.name, columns);
612
+ /**
613
+ * Expose Convex validator for schema integration
614
+ */
615
+ get convexValidator() {
616
+ if (this.config.notNull) return v.boolean();
617
+ return v.optional(v.union(v.null(), v.boolean()));
643
618
  }
644
- all() {
645
- return new ConvexAggregateIndexBuilder(this.name, []);
619
+ /**
620
+ * Compile to Convex validator
621
+ * .notNull() → v.boolean()
622
+ * nullable → v.optional(v.boolean())
623
+ */
624
+ build() {
625
+ return this.convexValidator;
646
626
  }
647
627
  };
648
- var ConvexAggregateIndexBuilder = class {
649
- static [entityKind] = "ConvexAggregateIndexBuilder";
650
- [entityKind] = "ConvexAggregateIndexBuilder";
651
- config;
652
- constructor(name, columns) {
653
- this.config = {
654
- name,
655
- columns,
656
- countFields: [],
657
- sumFields: [],
658
- avgFields: [],
659
- minFields: [],
660
- maxFields: []
661
- };
662
- }
663
- count(...fields) {
664
- this.config.countFields = [...this.config.countFields, ...fields];
665
- return this;
666
- }
667
- sum(...fields) {
668
- this.config.sumFields = [...this.config.sumFields, ...fields];
669
- return this;
670
- }
671
- avg(...fields) {
672
- this.config.avgFields = [...this.config.avgFields, ...fields];
673
- return this;
674
- }
675
- min(...fields) {
676
- this.config.minFields = [...this.config.minFields, ...fields];
677
- return this;
678
- }
679
- max(...fields) {
680
- this.config.maxFields = [...this.config.maxFields, ...fields];
681
- return this;
682
- }
683
- };
684
- var ConvexRankIndexBuilderOn = class {
685
- static [entityKind] = "ConvexRankIndexBuilderOn";
686
- [entityKind] = "ConvexRankIndexBuilderOn";
687
- constructor(name) {
688
- this.name = name;
689
- }
690
- partitionBy(...columns) {
691
- return new ConvexRankIndexBuilder(this.name, columns, []);
692
- }
693
- all() {
694
- return new ConvexRankIndexBuilder(this.name, [], []);
695
- }
696
- };
697
- var ConvexRankIndexBuilder = class {
698
- static [entityKind] = "ConvexRankIndexBuilder";
699
- [entityKind] = "ConvexRankIndexBuilder";
700
- config;
701
- constructor(name, partitionColumns, orderColumns) {
702
- this.config = {
703
- name,
704
- partitionColumns,
705
- orderColumns,
706
- sumField: void 0
707
- };
708
- }
709
- orderBy(...columns) {
710
- const orderColumns = columns.map((entry) => {
711
- if (entry && typeof entry === "object" && "column" in entry && "direction" in entry) {
712
- const builder = entry.column;
713
- if (!builder) throw new Error("rankIndex orderBy() expected a column builder.");
714
- return {
715
- column: builder,
716
- direction: entry.direction
717
- };
718
- }
719
- return {
720
- column: entry,
721
- direction: "asc"
722
- };
723
- });
724
- this.config.orderColumns = [...this.config.orderColumns, ...orderColumns];
725
- return this;
726
- }
727
- sum(field) {
728
- this.config.sumField = field;
729
- return this;
730
- }
731
- };
732
- var ConvexVectorIndexBuilder = class {
733
- static [entityKind] = "ConvexVectorIndexBuilder";
734
- [entityKind] = "ConvexVectorIndexBuilder";
735
- config;
736
- constructor(name, vectorField) {
737
- this.config = {
738
- name,
739
- vectorField,
740
- dimensions: void 0,
741
- filterFields: [],
742
- staged: false
743
- };
744
- }
745
- dimensions(dimensions) {
746
- if (!Number.isInteger(dimensions)) throw new Error(`Vector index '${this.config.name}' dimensions must be an integer, got ${dimensions}`);
747
- if (dimensions <= 0) throw new Error(`Vector index '${this.config.name}' dimensions must be positive, got ${dimensions}`);
748
- if (dimensions > 1e4) console.warn(`Vector index '${this.config.name}' has unusually large dimensions (${dimensions}). Common values: 768, 1536, 3072`);
749
- this.config.dimensions = dimensions;
750
- return this;
751
- }
752
- filter(...fields) {
753
- this.config.filterFields = fields;
754
- return this;
755
- }
756
- staged() {
757
- this.config.staged = true;
758
- return this;
759
- }
760
- };
761
- function index(name) {
762
- return new ConvexIndexBuilderOn(name, false);
763
- }
764
-
765
- //#endregion
766
- //#region src/orm/rls/policies.ts
767
- var RlsPolicy = class {
768
- static [entityKind] = "RlsPolicy";
769
- [entityKind] = "RlsPolicy";
770
- as;
771
- for;
772
- to;
773
- using;
774
- withCheck;
775
- /** @internal */
776
- _linkedTable;
777
- constructor(name, config) {
778
- this.name = name;
779
- if (config) {
780
- this.as = config.as;
781
- this.for = config.for;
782
- this.to = config.to;
783
- this.using = config.using;
784
- this.withCheck = config.withCheck;
785
- }
786
- }
787
- link(table) {
788
- this._linkedTable = table;
789
- return this;
790
- }
791
- };
792
- function isRlsPolicy(value) {
793
- return !!value && typeof value === "object" && value[entityKind] === "RlsPolicy";
628
+ function boolean$1(name) {
629
+ return new ConvexBooleanBuilder(name ?? "");
794
630
  }
795
631
 
796
632
  //#endregion
797
- //#region src/orm/table.ts
798
- /**
799
- * Reserved Convex system table names that cannot be used
800
- */
801
- const RESERVED_TABLES = new Set(["_storage", "_scheduled_functions"]);
802
- const RESERVED_COLUMN_NAMES = new Set([
803
- "id",
804
- "_id",
805
- "_creationTime"
806
- ]);
807
- const DEFAULT_POLYMORPHIC_ALIAS = "details";
808
- const CONVEX_TABLE_FIELD_LIMIT = 1024;
809
- /**
810
- * Valid table name pattern: starts with letter/underscore, contains only alphanumeric and underscore
811
- */
812
- const TABLE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
813
- /**
814
- * Validate table name against Convex constraints
815
- */
816
- function validateTableName(name) {
817
- if (RESERVED_TABLES.has(name)) throw new Error(`Table name '${name}' is reserved. System tables cannot be redefined.`);
818
- if (!TABLE_NAME_REGEX.test(name)) throw new Error(`Invalid table name '${name}'. Must start with letter, contain only alphanumeric and underscore.`);
819
- }
633
+ //#region src/internal/upstream/validators.ts
634
+ /** @deprecated Use `v.string()` instead. Any string value. */
635
+ const string = v.string();
636
+ /** @deprecated Use `v.float64()` instead. JavaScript number, represented as a float64 in the database. */
637
+ const number = v.float64();
638
+ /** @deprecated Use `v.float64()` instead. JavaScript number, represented as a float64 in the database. */
639
+ const float64 = v.float64();
640
+ /** @deprecated Use `v.boolean()` instead. boolean value. For typing it only as true, use `l(true)` */
641
+ const boolean = v.boolean();
642
+ /** @deprecated Use `v.int64()` instead. bigint, though stored as an int64 in the database. */
643
+ const biging = v.int64();
644
+ /** @deprecated Use `v.int64()` instead. bigint, though stored as an int64 in the database. */
645
+ const int64 = v.int64();
646
+ /** @deprecated Use `v.any()` instead. Any Convex value */
647
+ const any = v.any();
648
+ /** @deprecated Use `v.null()` instead. Null value. Underscore is so it doesn't shadow the null builtin */
649
+ const null_ = v.null();
650
+ /** @deprecated Use `v.*()` instead. */
651
+ const { id: id$1, object, array, bytes, literal, optional, union } = v;
652
+ /** @deprecated Use `v.bytes()` instead. ArrayBuffer validator. */
653
+ const arrayBuffer = v.bytes();
654
+ /** Mark fields as deprecated with this permissive validator typed as null */
655
+ const deprecated = v.optional(v.any());
820
656
  /**
821
- * Create a Convex object validator from column builders
657
+ * Converts an optional validator to a required validator.
822
658
  *
823
- * Extracts .convexValidator from each column and creates v.object({...})
824
- * This is the core factory that bridges ORM columns to Convex validators.
659
+ * This is the inverse of `v.optional()`. It takes a validator that may be optional
660
+ * and returns the equivalent required validator.
825
661
  *
826
- * @param columns - Record of column name to column builder
827
- * @returns Convex object validator
662
+ * ```ts
663
+ * const optionalString = v.optional(v.string());
664
+ * const requiredString = vRequired(optionalString); // v.string()
665
+ *
666
+ * // Already required validators are returned as-is
667
+ * const alreadyRequired = v.string();
668
+ * const stillRequired = vRequired(alreadyRequired); // v.string()
669
+ * ```
670
+ *
671
+ * @param validator The validator to make required.
672
+ * @returns A required version of the validator.
828
673
  */
829
- function createValidatorFromColumns(columns) {
830
- const validatorFields = Object.fromEntries(Object.entries(columns).map(([key, builder]) => [key, builder.convexValidator]));
831
- return v.object(validatorFields);
832
- }
833
- var ConvexDeletionBuilder = class {
834
- static [entityKind] = "ConvexDeletionBuilder";
835
- [entityKind] = "ConvexDeletionBuilder";
836
- constructor(config) {
837
- this.config = config;
674
+ function vRequired(validator) {
675
+ const { kind, isOptional } = validator;
676
+ if (isOptional === "required") return validator;
677
+ switch (kind) {
678
+ case "id": return v.id(validator.tableName);
679
+ case "string": return v.string();
680
+ case "float64": return v.float64();
681
+ case "int64": return v.int64();
682
+ case "commitTs": return v.commitTs();
683
+ case "boolean": return v.boolean();
684
+ case "null": return v.null();
685
+ case "any": return v.any();
686
+ case "literal": return v.literal(validator.value);
687
+ case "bytes": return v.bytes();
688
+ case "object": return v.object(validator.fields);
689
+ case "array": return v.array(validator.element);
690
+ case "record": return v.record(validator.key, validator.value);
691
+ case "union": return v.union(...validator.members);
692
+ default: throw new Error("Unknown Convex validator type: " + kind);
838
693
  }
839
- };
840
- function isConvexIndexBuilder(value) {
841
- return typeof value === "object" && value !== null && value[entityKind] === "ConvexIndexBuilder";
842
694
  }
843
- function isConvexIndexBuilderOn(value) {
844
- return typeof value === "object" && value !== null && value[entityKind] === "ConvexIndexBuilderOn";
695
+
696
+ //#endregion
697
+ //#region src/orm/builders/custom.ts
698
+ function isRecord$2(value) {
699
+ return typeof value === "object" && value !== null && !Array.isArray(value);
845
700
  }
846
- function isConvexAggregateIndexBuilder(value) {
847
- return typeof value === "object" && value !== null && value[entityKind] === "ConvexAggregateIndexBuilder";
701
+ function isValidator(value) {
702
+ return isRecord$2(value) && typeof value.kind === "string" && typeof value.isOptional === "string";
848
703
  }
849
- function isConvexAggregateIndexBuilderOn(value) {
850
- return typeof value === "object" && value !== null && value[entityKind] === "ConvexAggregateIndexBuilderOn";
704
+ function isColumnBuilder$1(value) {
705
+ return isRecord$2(value) && value[entityKind] === "ColumnBuilder";
851
706
  }
852
- function isConvexRankIndexBuilderOn(value) {
853
- return typeof value === "object" && value !== null && value[entityKind] === "ConvexRankIndexBuilderOn";
707
+ function toRequiredValidator(validator) {
708
+ return validator.isOptional === "optional" ? vRequired(validator) : validator;
854
709
  }
855
- function isConvexRankIndexBuilder(value) {
856
- return typeof value === "object" && value !== null && value[entityKind] === "ConvexRankIndexBuilder";
710
+ function toRequiredBuilderValidator(validator) {
711
+ const requiredValidator = toRequiredValidator(validator);
712
+ if (requiredValidator.kind !== "union") return requiredValidator;
713
+ const nonNullMembers = requiredValidator.members.filter((member) => member.kind !== "null");
714
+ if (nonNullMembers.length !== 1) return requiredValidator;
715
+ const [member] = nonNullMembers;
716
+ if (member.kind === "object" || member.kind === "array") return member;
717
+ return requiredValidator;
857
718
  }
858
- function isConvexUniqueConstraintBuilderOn(value) {
859
- return typeof value === "object" && value !== null && value[entityKind] === "ConvexUniqueConstraintBuilderOn";
719
+ function formatInvalidInput(path, value) {
720
+ return `${path} expected a column builder, Convex validator, or nested object shape. Got ${Array.isArray(value) ? "array" : value === null ? "null" : typeof value}.`;
860
721
  }
861
- function isConvexForeignKeyBuilder(value) {
862
- return typeof value === "object" && value !== null && value[entityKind] === "ConvexForeignKeyBuilder";
863
- }
864
- function isConvexCheckBuilder(value) {
865
- return typeof value === "object" && value !== null && value[entityKind] === "ConvexCheckBuilder";
866
- }
867
- function isConvexSearchIndexBuilderOn(value) {
868
- return typeof value === "object" && value !== null && value[entityKind] === "ConvexSearchIndexBuilderOn";
869
- }
870
- function isConvexUniqueConstraintBuilder(value) {
871
- return typeof value === "object" && value !== null && value[entityKind] === "ConvexUniqueConstraintBuilder";
872
- }
873
- function isConvexSearchIndexBuilder(value) {
874
- return typeof value === "object" && value !== null && value[entityKind] === "ConvexSearchIndexBuilder";
875
- }
876
- function isConvexVectorIndexBuilderOn(value) {
877
- return typeof value === "object" && value !== null && value[entityKind] === "ConvexVectorIndexBuilderOn";
878
- }
879
- function isConvexVectorIndexBuilder(value) {
880
- return typeof value === "object" && value !== null && value[entityKind] === "ConvexVectorIndexBuilder";
881
- }
882
- function isConvexDeletionBuilder(value) {
883
- return typeof value === "object" && value !== null && value[entityKind] === "ConvexDeletionBuilder";
884
- }
885
- function isConvexLifecycleBuilder(value) {
886
- return typeof value === "object" && value !== null && value[entityKind] === "ConvexLifecycleBuilder";
887
- }
888
- function getColumnName(column) {
889
- const config = column.config;
890
- if (!config?.name) throw new Error("Invalid index column: expected a convexTable column builder.");
891
- return config.name;
722
+ function objectShapeToValidator(shape, path) {
723
+ const fields = {};
724
+ for (const [key, value] of Object.entries(shape)) fields[key] = nestedInputToValidator(value, `${path}.${key}`);
725
+ return v.object(fields);
892
726
  }
893
- function getColumnType(column) {
894
- return column.config?.columnType;
727
+ function nestedInputToValidator(input, path) {
728
+ if (isColumnBuilder$1(input)) return toRequiredBuilderValidator(input.convexValidator);
729
+ if (isValidator(input)) return toRequiredValidator(input);
730
+ if (isRecord$2(input)) return objectShapeToValidator(input, path);
731
+ throw new Error(formatInvalidInput(path, input));
895
732
  }
896
- function getColumnDimensions(column) {
897
- return column.config?.dimensions;
733
+ var ConvexCustomBuilder = class extends ConvexColumnBuilder {
734
+ static [entityKind] = "ConvexCustomBuilder";
735
+ constructor(name, validator) {
736
+ super(name, "any", "ConvexCustom");
737
+ this.config.validator = validator;
738
+ }
739
+ get convexValidator() {
740
+ const validator = this.config.validator;
741
+ if (this.config.notNull) return validator;
742
+ return v.optional(v.union(v.null(), validator));
743
+ }
744
+ build() {
745
+ return this.convexValidator;
746
+ }
747
+ };
748
+ function custom(a, b) {
749
+ if (b !== void 0) return new ConvexCustomBuilder(a, b);
750
+ return new ConvexCustomBuilder("", a);
898
751
  }
899
- function getColumnTableName(column) {
900
- const config = column.config;
901
- return config?.tableName ?? config?.referenceTable;
752
+ /**
753
+ * Creates an array column from a nested validator or builder.
754
+ *
755
+ * Values in nested arrays are always compiled as required validators.
756
+ */
757
+ function arrayOf(element) {
758
+ return custom(v.array(nestedInputToValidator(element, "arrayOf(element)"))).$type();
902
759
  }
903
- function getColumnTable(column) {
904
- return column.config?.table;
760
+ /**
761
+ * Creates an object column from either:
762
+ * - a nested shape of validators/builders, or
763
+ * - a validator/builder describing homogeneous record values
764
+ *
765
+ * Fields in nested objects are always compiled as required validators.
766
+ */
767
+ function objectOf(input) {
768
+ if (isColumnBuilder$1(input) || isValidator(input)) return custom(v.record(v.string(), nestedInputToValidator(input, "objectOf(value)"))).$type();
769
+ if (!isRecord$2(input)) throw new Error(formatInvalidInput("objectOf(shape)", input));
770
+ return custom(objectShapeToValidator(input, "objectOf(shape)")).$type();
905
771
  }
906
- function getUniqueIndexName(tableName, fields, explicitName) {
907
- if (explicitName) return explicitName;
908
- return `${tableName}_${fields.join("_")}_unique`;
772
+ /**
773
+ * Convenience wrapper for Convex "JSON" values.
774
+ *
775
+ * Note: This is Convex JSON (runtime `v.any()`), not SQL JSON/JSONB.
776
+ */
777
+ function json() {
778
+ return custom(v.any()).$type();
909
779
  }
910
- function assertColumnInTable(column, expectedTable, context) {
911
- const tableName = getColumnTableName(column);
912
- if (tableName && tableName !== expectedTable) throw new Error(`${context} references column from '${tableName}', but belongs to '${expectedTable}'.`);
913
- return getColumnName(column);
780
+
781
+ //#endregion
782
+ //#region src/orm/builders/id.ts
783
+ /**
784
+ * ID column builder class
785
+ * Compiles to v.id(tableName) or v.optional(v.id(tableName))
786
+ */
787
+ var ConvexIdBuilder = class extends ConvexColumnBuilder {
788
+ static [entityKind] = "ConvexIdBuilder";
789
+ constructor(name, tableName) {
790
+ super(name, "string", "ConvexId");
791
+ this.tableName = tableName;
792
+ this.config.referenceTable = tableName;
793
+ }
794
+ /**
795
+ * Expose Convex validator for schema integration
796
+ */
797
+ get convexValidator() {
798
+ if (this.config.notNull) return v.id(this.tableName);
799
+ return v.optional(v.union(v.null(), v.id(this.tableName)));
800
+ }
801
+ /**
802
+ * Compile to Convex validator
803
+ * .notNull() → v.id(tableName)
804
+ * nullable → v.optional(v.id(tableName))
805
+ */
806
+ build() {
807
+ return this.convexValidator;
808
+ }
809
+ };
810
+ function id(tableName) {
811
+ return new ConvexIdBuilder("", tableName);
914
812
  }
915
- function assertNoReservedCreatedAtIndexFields(fields, context) {
916
- if (fields.includes("createdAt")) throw new Error(`${context} cannot use 'createdAt'. 'createdAt' is reserved and maps to internal '_creationTime'.`);
813
+
814
+ //#endregion
815
+ //#region src/orm/builders/number.ts
816
+ /**
817
+ * Number column builder class
818
+ * Compiles to v.number() or v.optional(v.number())
819
+ */
820
+ var ConvexNumberBuilder = class extends ConvexColumnBuilder {
821
+ static [entityKind] = "ConvexNumberBuilder";
822
+ constructor(name) {
823
+ super(name, "number", "ConvexNumber");
824
+ }
825
+ /**
826
+ * Expose Convex validator for schema integration
827
+ */
828
+ get convexValidator() {
829
+ if (this.config.notNull) return v.number();
830
+ return v.optional(v.union(v.null(), v.number()));
831
+ }
832
+ /**
833
+ * Compile to Convex validator
834
+ * .notNull() → v.number()
835
+ * nullable → v.optional(v.number())
836
+ */
837
+ build() {
838
+ return this.convexValidator;
839
+ }
840
+ };
841
+ function integer(name) {
842
+ return new ConvexNumberBuilder(name ?? "");
917
843
  }
918
- function assertSearchFieldType(column, indexName) {
919
- const columnType = getColumnType(column) ?? "unknown";
920
- if (columnType !== "ConvexText") throw new Error(`Search index '${indexName}' only supports text() columns. Field '${getColumnName(column)}' is type '${columnType}'.`);
844
+
845
+ //#endregion
846
+ //#region src/orm/builders/text.ts
847
+ /**
848
+ * Text column builder class
849
+ * Compiles to v.string() or v.optional(v.string())
850
+ */
851
+ var ConvexTextBuilder = class extends ConvexColumnBuilder {
852
+ static [entityKind] = "ConvexTextBuilder";
853
+ constructor(name) {
854
+ super(name, "string", "ConvexText");
855
+ }
856
+ /**
857
+ * Expose Convex validator for schema integration
858
+ */
859
+ get convexValidator() {
860
+ if (this.config.notNull) return v.string();
861
+ return v.optional(v.union(v.null(), v.string()));
862
+ }
863
+ /**
864
+ * Compile to Convex validator
865
+ * .notNull() → v.string()
866
+ * nullable → v.optional(v.string())
867
+ */
868
+ build() {
869
+ return this.convexValidator;
870
+ }
871
+ };
872
+ function text(name) {
873
+ return new ConvexTextBuilder(name ?? "");
921
874
  }
922
- function assertVectorFieldType(column, indexName) {
923
- const columnType = getColumnType(column) ?? "unknown";
924
- if (columnType !== "ConvexVector") throw new Error(`Vector index '${indexName}' requires a vector() column. Field '${getColumnName(column)}' is type '${columnType}'.`);
875
+
876
+ //#endregion
877
+ //#region src/orm/extensions.ts
878
+ function defineChainMethod(target, key, value) {
879
+ Object.defineProperty(target, key, {
880
+ value,
881
+ enumerable: false,
882
+ configurable: true
883
+ });
925
884
  }
926
- function assertAggregateSumFieldType(column, indexName) {
927
- const columnType = getColumnType(column) ?? "unknown";
928
- if (!["ConvexNumber", "ConvexTimestamp"].includes(columnType)) throw new Error(`aggregateIndex '${indexName}' sum() supports integer()/timestamp() columns only. Field '${getColumnName(column)}' is type '${columnType}'.`);
885
+ function createSchemaExtensionChain(state, capabilities) {
886
+ const extension = {
887
+ key: state.key,
888
+ tables: state.tables
889
+ };
890
+ Object.defineProperty(extension, OrmSchemaExtensionRelations, {
891
+ value: state.relations,
892
+ enumerable: false,
893
+ configurable: true
894
+ });
895
+ Object.defineProperty(extension, OrmSchemaExtensionTriggers, {
896
+ value: state.triggers,
897
+ enumerable: false,
898
+ configurable: true
899
+ });
900
+ if (capabilities.canRelations) defineChainMethod(extension, "relations", (relations) => createSchemaExtensionChain({
901
+ ...state,
902
+ relations
903
+ }, {
904
+ canRelations: false,
905
+ canTriggers: true
906
+ }));
907
+ if (capabilities.canTriggers) defineChainMethod(extension, "triggers", (triggers) => createSchemaExtensionChain({
908
+ ...state,
909
+ triggers
910
+ }, {
911
+ canRelations: false,
912
+ canTriggers: false
913
+ }));
914
+ return extension;
929
915
  }
930
- function assertAggregateAvgFieldType(column, indexName) {
931
- const columnType = getColumnType(column) ?? "unknown";
932
- if (!["ConvexNumber", "ConvexTimestamp"].includes(columnType)) throw new Error(`aggregateIndex '${indexName}' avg() supports integer()/timestamp() columns only. Field '${getColumnName(column)}' is type '${columnType}'.`);
916
+ function defineSchemaExtension(key, tables) {
917
+ return createSchemaExtensionChain({
918
+ key,
919
+ tables,
920
+ relations: void 0,
921
+ triggers: void 0
922
+ }, {
923
+ canRelations: true,
924
+ canTriggers: true
925
+ });
933
926
  }
934
- function assertAggregateComparableFieldType(column, indexName, method) {
935
- const columnType = getColumnType(column) ?? "unknown";
936
- if (![
937
- "ConvexNumber",
938
- "ConvexTimestamp",
939
- "ConvexDate",
940
- "ConvexText",
941
- "ConvexBoolean",
942
- "ConvexId"
943
- ].includes(columnType)) throw new Error(`aggregateIndex '${indexName}' ${method}() does not support column type '${columnType}' on '${getColumnName(column)}'.`);
944
- }
945
- function assertRankOrderFieldType(column, indexName) {
946
- const columnType = getColumnType(column) ?? "unknown";
947
- if (![
948
- "ConvexNumber",
949
- "ConvexTimestamp",
950
- "ConvexDate"
951
- ].includes(columnType)) throw new Error(`rankIndex '${indexName}' orderBy() supports integer()/timestamp()/date() columns only. Field '${getColumnName(column)}' is type '${columnType}'.`);
952
- }
953
- const dedupeFieldNames = (fields) => [...new Set(fields)];
954
- const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
955
- const isColumnBuilder = (value) => isRecord$1(value) && typeof value.build === "function";
956
- const getDiscriminatorConfig = (value) => {
957
- if (!isColumnBuilder(value)) return;
958
- const discriminator = value.config?.discriminator;
959
- if (!discriminator) return;
960
- return discriminator;
927
+
928
+ //#endregion
929
+ //#region src/orm/indexes.ts
930
+ var ConvexIndexBuilderOn = class {
931
+ static [entityKind] = "ConvexIndexBuilderOn";
932
+ [entityKind] = "ConvexIndexBuilderOn";
933
+ constructor(name, unique) {
934
+ this.name = name;
935
+ this.unique = unique;
936
+ }
937
+ on(...columns) {
938
+ return new ConvexIndexBuilder(this.name, columns, this.unique);
939
+ }
961
940
  };
962
- const getPolymorphicFieldSignature = (column) => {
963
- const validator = column.convexValidator ?? column.build();
964
- return JSON.stringify({
965
- columnType: column.config?.columnType,
966
- validator: validator?.json
967
- });
941
+ var ConvexIndexBuilder = class {
942
+ static [entityKind] = "ConvexIndexBuilder";
943
+ [entityKind] = "ConvexIndexBuilder";
944
+ config;
945
+ constructor(name, columns, unique) {
946
+ this.config = {
947
+ name,
948
+ columns,
949
+ unique,
950
+ where: void 0
951
+ };
952
+ }
953
+ /**
954
+ * Partial index conditions are not supported in Convex.
955
+ * This method is kept for Drizzle API parity.
956
+ */
957
+ where(condition) {
958
+ this.config.where = condition;
959
+ return this;
960
+ }
968
961
  };
969
- function resolveTableColumns(tableName, columns) {
970
- const resolvedColumns = {};
971
- const pendingPolymorphic = [];
972
- for (const [columnName, rawBuilder] of Object.entries(columns)) {
973
- if (!isColumnBuilder(rawBuilder)) throw new Error(`Column '${columnName}' on '${tableName}' must be a column builder.`);
974
- resolvedColumns[columnName] = rawBuilder;
975
- const discriminatorConfig = getDiscriminatorConfig(rawBuilder);
976
- if (!discriminatorConfig) continue;
977
- if (!isRecord$1(discriminatorConfig.variants) || Object.keys(discriminatorConfig.variants).length === 0) throw new Error(`discriminator('${tableName}.${columnName}') requires at least one variant.`);
978
- const alias = discriminatorConfig.as === void 0 ? DEFAULT_POLYMORPHIC_ALIAS : discriminatorConfig.as;
979
- if (typeof alias !== "string" || alias.length === 0) throw new Error(`discriminator('${tableName}.${columnName}').as must be a non-empty string.`);
980
- pendingPolymorphic.push({
981
- discriminator: columnName,
982
- alias,
983
- variants: discriminatorConfig.variants
984
- });
962
+ var ConvexSearchIndexBuilderOn = class {
963
+ static [entityKind] = "ConvexSearchIndexBuilderOn";
964
+ [entityKind] = "ConvexSearchIndexBuilderOn";
965
+ constructor(name) {
966
+ this.name = name;
985
967
  }
986
- if (pendingPolymorphic.length > 1) throw new Error(`Only one discriminator(...) column is currently supported on '${tableName}'.`);
987
- const polymorphicConfigs = [];
988
- for (const pending of pendingPolymorphic) {
989
- if (pending.alias in resolvedColumns) throw new Error(`discriminator('${tableName}.${pending.discriminator}') alias '${pending.alias}' collides with an existing column.`);
990
- const generatedFieldMap = /* @__PURE__ */ new Map();
991
- const variantRuntime = {};
992
- for (const [variantKey, rawVariantColumns] of Object.entries(pending.variants)) {
993
- if (!isRecord$1(rawVariantColumns)) throw new Error(`discriminator('${tableName}.${pending.discriminator}') variant '${variantKey}' must be an object.`);
994
- const fieldNames = [];
995
- const requiredFieldNames = [];
996
- for (const [fieldName, rawFieldBuilder] of Object.entries(rawVariantColumns)) {
997
- if (!isColumnBuilder(rawFieldBuilder)) throw new Error(`discriminator('${tableName}.${pending.discriminator}').variants.${variantKey}.${fieldName} must be a column builder.`);
998
- if (fieldName in resolvedColumns) throw new Error(`discriminator('${tableName}.${pending.discriminator}').variants.${variantKey}.${fieldName} collides with an existing table column.`);
999
- const fieldBuilder = rawFieldBuilder;
1000
- const fieldConfig = fieldBuilder.config;
1001
- const isRequiredForVariant = fieldConfig?.notNull === true && fieldConfig.hasDefault !== true && typeof fieldConfig.defaultFn !== "function";
1002
- const signature = getPolymorphicFieldSignature(fieldBuilder);
1003
- const existing = generatedFieldMap.get(fieldName);
1004
- if (existing && existing.signature !== signature) throw new Error(`discriminator('${tableName}.${pending.discriminator}') field '${fieldName}' has conflicting builder signatures across variants.`);
1005
- if (!existing) {
1006
- if (fieldConfig) fieldConfig.notNull = false;
1007
- generatedFieldMap.set(fieldName, {
1008
- builder: fieldBuilder,
1009
- signature
1010
- });
1011
- }
1012
- fieldNames.push(fieldName);
1013
- if (isRequiredForVariant) requiredFieldNames.push(fieldName);
968
+ on(searchField) {
969
+ return new ConvexSearchIndexBuilder(this.name, searchField);
970
+ }
971
+ };
972
+ var ConvexSearchIndexBuilder = class {
973
+ static [entityKind] = "ConvexSearchIndexBuilder";
974
+ [entityKind] = "ConvexSearchIndexBuilder";
975
+ config;
976
+ constructor(name, searchField) {
977
+ this.config = {
978
+ name,
979
+ searchField,
980
+ filterFields: [],
981
+ staged: false
982
+ };
983
+ }
984
+ filter(...fields) {
985
+ this.config.filterFields = fields;
986
+ return this;
987
+ }
988
+ staged() {
989
+ this.config.staged = true;
990
+ return this;
991
+ }
992
+ };
993
+ var ConvexVectorIndexBuilderOn = class {
994
+ static [entityKind] = "ConvexVectorIndexBuilderOn";
995
+ [entityKind] = "ConvexVectorIndexBuilderOn";
996
+ constructor(name) {
997
+ this.name = name;
998
+ }
999
+ on(vectorField) {
1000
+ return new ConvexVectorIndexBuilder(this.name, vectorField);
1001
+ }
1002
+ };
1003
+ var ConvexAggregateIndexBuilderOn = class {
1004
+ static [entityKind] = "ConvexAggregateIndexBuilderOn";
1005
+ [entityKind] = "ConvexAggregateIndexBuilderOn";
1006
+ constructor(name) {
1007
+ this.name = name;
1008
+ }
1009
+ on(...columns) {
1010
+ return new ConvexAggregateIndexBuilder(this.name, columns);
1011
+ }
1012
+ all() {
1013
+ return new ConvexAggregateIndexBuilder(this.name, []);
1014
+ }
1015
+ };
1016
+ var ConvexAggregateIndexBuilder = class {
1017
+ static [entityKind] = "ConvexAggregateIndexBuilder";
1018
+ [entityKind] = "ConvexAggregateIndexBuilder";
1019
+ config;
1020
+ constructor(name, columns) {
1021
+ this.config = {
1022
+ name,
1023
+ columns,
1024
+ countFields: [],
1025
+ sumFields: [],
1026
+ avgFields: [],
1027
+ minFields: [],
1028
+ maxFields: []
1029
+ };
1030
+ }
1031
+ count(...fields) {
1032
+ this.config.countFields = [...this.config.countFields, ...fields];
1033
+ return this;
1034
+ }
1035
+ sum(...fields) {
1036
+ this.config.sumFields = [...this.config.sumFields, ...fields];
1037
+ return this;
1038
+ }
1039
+ avg(...fields) {
1040
+ this.config.avgFields = [...this.config.avgFields, ...fields];
1041
+ return this;
1042
+ }
1043
+ min(...fields) {
1044
+ this.config.minFields = [...this.config.minFields, ...fields];
1045
+ return this;
1046
+ }
1047
+ max(...fields) {
1048
+ this.config.maxFields = [...this.config.maxFields, ...fields];
1049
+ return this;
1050
+ }
1051
+ };
1052
+ var ConvexRankIndexBuilderOn = class {
1053
+ static [entityKind] = "ConvexRankIndexBuilderOn";
1054
+ [entityKind] = "ConvexRankIndexBuilderOn";
1055
+ constructor(name) {
1056
+ this.name = name;
1057
+ }
1058
+ partitionBy(...columns) {
1059
+ return new ConvexRankIndexBuilder(this.name, columns, []);
1060
+ }
1061
+ all() {
1062
+ return new ConvexRankIndexBuilder(this.name, [], []);
1063
+ }
1064
+ };
1065
+ var ConvexRankIndexBuilder = class {
1066
+ static [entityKind] = "ConvexRankIndexBuilder";
1067
+ [entityKind] = "ConvexRankIndexBuilder";
1068
+ config;
1069
+ constructor(name, partitionColumns, orderColumns) {
1070
+ this.config = {
1071
+ name,
1072
+ partitionColumns,
1073
+ orderColumns,
1074
+ sumField: void 0
1075
+ };
1076
+ }
1077
+ orderBy(...columns) {
1078
+ const orderColumns = columns.map((entry) => {
1079
+ if (entry && typeof entry === "object" && "column" in entry && "direction" in entry) {
1080
+ const builder = entry.column;
1081
+ if (!builder) throw new Error("rankIndex orderBy() expected a column builder.");
1082
+ return {
1083
+ column: builder,
1084
+ direction: entry.direction
1085
+ };
1014
1086
  }
1015
- variantRuntime[variantKey] = {
1016
- fieldNames,
1017
- requiredFieldNames
1087
+ return {
1088
+ column: entry,
1089
+ direction: "asc"
1018
1090
  };
1019
- }
1020
- for (const [fieldName, { builder }] of generatedFieldMap.entries()) resolvedColumns[fieldName] = builder;
1021
- polymorphicConfigs.push({
1022
- discriminator: pending.discriminator,
1023
- alias: pending.alias,
1024
- generatedFieldNames: Object.freeze([...generatedFieldMap.keys()]),
1025
- variants: Object.freeze(variantRuntime)
1026
1091
  });
1092
+ this.config.orderColumns = [...this.config.orderColumns, ...orderColumns];
1093
+ return this;
1027
1094
  }
1028
- if (Object.keys(resolvedColumns).length > CONVEX_TABLE_FIELD_LIMIT) throw new Error(`Table '${tableName}' exceeds Convex field count limit (${CONVEX_TABLE_FIELD_LIMIT}) after discriminator expansion.`);
1029
- return {
1030
- columns: resolvedColumns,
1031
- polymorphicConfigs
1032
- };
1033
- }
1034
- function applyExtraConfig(table, config) {
1035
- if (!config) return;
1036
- const entries = Array.isArray(config) ? config : Object.values(config);
1037
- for (const entry of entries) {
1038
- if (isConvexIndexBuilderOn(entry)) throw new Error(`Invalid index definition on '${table.tableName}'. Did you forget to call .on(...)?`);
1039
- if (isConvexUniqueConstraintBuilderOn(entry)) throw new Error(`Invalid unique constraint definition on '${table.tableName}'. Did you forget to call .on(...)?`);
1040
- if (isConvexAggregateIndexBuilderOn(entry)) throw new Error(`Invalid aggregate index definition on '${table.tableName}'. Did you forget to call .on(...) or .all()?`);
1041
- if (isConvexRankIndexBuilderOn(entry)) throw new Error(`Invalid rank index definition on '${table.tableName}'. Did you forget to call .partitionBy(...) or .all()?`);
1042
- if (isConvexSearchIndexBuilderOn(entry)) throw new Error(`Invalid search index definition on '${table.tableName}'. Did you forget to call .on(...)?`);
1043
- if (isConvexVectorIndexBuilderOn(entry)) throw new Error(`Invalid vector index definition on '${table.tableName}'. Did you forget to call .on(...)?`);
1044
- if (isRlsPolicy(entry)) {
1045
- const target = entry._linkedTable ?? table;
1046
- if (typeof target.addRlsPolicy === "function") target.addRlsPolicy(entry);
1047
- else {
1048
- const policies = target[RlsPolicies] ?? [];
1049
- policies.push(entry);
1050
- target[RlsPolicies] = policies;
1051
- target[EnableRLS] = true;
1052
- }
1053
- continue;
1054
- }
1055
- if (isConvexDeletionBuilder(entry)) {
1056
- if (table[TableDeleteConfig]) throw new Error(`Only one deletion(...) config can be defined for '${table.tableName}'.`);
1057
- table[TableDeleteConfig] = {
1058
- mode: entry.config.mode,
1059
- delayMs: entry.config.delayMs
1060
- };
1061
- continue;
1062
- }
1063
- if (isConvexLifecycleBuilder(entry)) throw new Error(`Lifecycle hooks are no longer supported inside convexTable('${table.tableName}', ..., extraConfig). Export schema triggers with defineTriggers(relations, { ... }) from schema.ts.`);
1064
- if (isConvexIndexBuilder(entry)) {
1065
- const { name, columns, unique, where } = entry.config;
1066
- if (where) throw new Error(`Convex does not support partial indexes. Remove .where(...) from index '${name}'.`);
1067
- if (unique) {}
1068
- const fields = columns.map((column) => assertColumnInTable(column, table.tableName, `Index '${name}'`));
1069
- assertNoReservedCreatedAtIndexFields(fields, `Index '${name}'`);
1070
- table.addIndex(name, fields);
1071
- if (unique) table.addUniqueIndex(name, fields, false);
1072
- continue;
1073
- }
1074
- if (isConvexAggregateIndexBuilder(entry)) {
1075
- const { name, columns, countFields, sumFields, avgFields, minFields, maxFields } = entry.config;
1076
- const fields = columns.map((column) => assertColumnInTable(column, table.tableName, `Aggregate index '${name}'`));
1077
- assertNoReservedCreatedAtIndexFields(fields, `Aggregate index '${name}'`);
1078
- const resolvedCountFields = dedupeFieldNames(countFields.map((column) => assertColumnInTable(column, table.tableName, `Aggregate index '${name}' count`)));
1079
- assertNoReservedCreatedAtIndexFields(resolvedCountFields, `Aggregate index '${name}' count`);
1080
- const resolvedSumFields = dedupeFieldNames(sumFields.map((column) => {
1081
- const field = assertColumnInTable(column, table.tableName, `Aggregate index '${name}' sum`);
1082
- assertAggregateSumFieldType(column, name);
1083
- return field;
1084
- }));
1085
- assertNoReservedCreatedAtIndexFields(resolvedSumFields, `Aggregate index '${name}' sum`);
1086
- const resolvedAvgFields = dedupeFieldNames(avgFields.map((column) => {
1087
- const field = assertColumnInTable(column, table.tableName, `Aggregate index '${name}' avg`);
1088
- assertAggregateAvgFieldType(column, name);
1089
- return field;
1090
- }));
1091
- assertNoReservedCreatedAtIndexFields(resolvedAvgFields, `Aggregate index '${name}' avg`);
1092
- const resolvedMinFields = dedupeFieldNames(minFields.map((column) => {
1093
- const field = assertColumnInTable(column, table.tableName, `Aggregate index '${name}' min`);
1094
- assertAggregateComparableFieldType(column, name, "min");
1095
- return field;
1096
- }));
1097
- assertNoReservedCreatedAtIndexFields(resolvedMinFields, `Aggregate index '${name}' min`);
1098
- const resolvedMaxFields = dedupeFieldNames(maxFields.map((column) => {
1099
- const field = assertColumnInTable(column, table.tableName, `Aggregate index '${name}' max`);
1100
- assertAggregateComparableFieldType(column, name, "max");
1101
- return field;
1102
- }));
1103
- assertNoReservedCreatedAtIndexFields(resolvedMaxFields, `Aggregate index '${name}' max`);
1104
- table.addAggregateIndex(name, {
1105
- fields,
1106
- countFields: resolvedCountFields,
1107
- sumFields: resolvedSumFields,
1108
- avgFields: resolvedAvgFields,
1109
- minFields: resolvedMinFields,
1110
- maxFields: resolvedMaxFields
1111
- });
1112
- continue;
1113
- }
1114
- if (isConvexRankIndexBuilder(entry)) {
1115
- const { name, partitionColumns, orderColumns, sumField } = entry.config;
1116
- if (!orderColumns.length) throw new Error(`rankIndex '${name}' on '${table.tableName}' must declare at least one orderBy(...) column.`);
1117
- const resolvedPartitionFields = dedupeFieldNames(partitionColumns.map((column) => assertColumnInTable(column, table.tableName, `rankIndex '${name}'`)));
1118
- assertNoReservedCreatedAtIndexFields(resolvedPartitionFields, `rankIndex '${name}' partitionBy`);
1119
- const resolvedOrderFields = orderColumns.map((entry) => {
1120
- const field = assertColumnInTable(entry.column, table.tableName, `rankIndex '${name}' orderBy`);
1121
- assertNoReservedCreatedAtIndexFields([field], `rankIndex '${name}'`);
1122
- assertRankOrderFieldType(entry.column, name);
1123
- return {
1124
- field,
1125
- direction: entry.direction
1126
- };
1127
- });
1128
- const resolvedSumField = sumField ? (() => {
1129
- const field = assertColumnInTable(sumField, table.tableName, `rankIndex '${name}' sum`);
1130
- assertNoReservedCreatedAtIndexFields([field], `rankIndex '${name}'`);
1131
- assertAggregateSumFieldType(sumField, name);
1132
- return field;
1133
- })() : void 0;
1134
- table.addRankIndex(name, {
1135
- partitionFields: resolvedPartitionFields,
1136
- orderFields: resolvedOrderFields,
1137
- sumField: resolvedSumField
1138
- });
1139
- continue;
1140
- }
1141
- if (isConvexUniqueConstraintBuilder(entry)) {
1142
- const { name, columns, nullsNotDistinct } = entry.config;
1143
- const fields = columns.map((column) => assertColumnInTable(column, table.tableName, "Unique constraint"));
1144
- assertNoReservedCreatedAtIndexFields(fields, "Unique constraint");
1145
- const indexName = getUniqueIndexName(table.tableName, fields, name);
1146
- table.addIndex(indexName, fields);
1147
- table.addUniqueIndex(indexName, fields, nullsNotDistinct);
1148
- continue;
1149
- }
1150
- if (isConvexForeignKeyBuilder(entry)) {
1151
- const { name, columns, foreignColumns, onDelete, onUpdate } = entry.config;
1152
- if (columns.length === 0 || foreignColumns.length === 0) throw new Error(`Foreign key on '${table.tableName}' requires at least one column.`);
1153
- if (columns.length !== foreignColumns.length) throw new Error(`Foreign key on '${table.tableName}' must specify matching columns and foreignColumns.`);
1154
- const localFields = columns.map((column) => assertColumnInTable(column, table.tableName, "Foreign key"));
1155
- const foreignTableName = getColumnTableName(foreignColumns[0]);
1156
- if (!foreignTableName) throw new Error(`Foreign key on '${table.tableName}' references a column without a table.`);
1157
- const foreignTable = getColumnTable(foreignColumns[0]);
1158
- const foreignFields = foreignColumns.map((column) => {
1159
- const tableName = getColumnTableName(column);
1160
- if (tableName && tableName !== foreignTableName) throw new Error(`Foreign key on '${table.tableName}' mixes foreign columns from '${foreignTableName}' and '${tableName}'.`);
1161
- return getColumnName(column);
1162
- });
1163
- table.addForeignKey({
1164
- name,
1165
- columns: localFields,
1166
- foreignTableName,
1167
- foreignTable,
1168
- foreignColumns: foreignFields,
1169
- onDelete,
1170
- onUpdate
1171
- });
1172
- continue;
1173
- }
1174
- if (isConvexCheckBuilder(entry)) {
1175
- const { name, expression } = entry.config;
1176
- table.addCheck(name, expression);
1177
- continue;
1178
- }
1179
- if (isConvexSearchIndexBuilder(entry)) {
1180
- const { name, searchField, filterFields, staged } = entry.config;
1181
- const searchFieldName = assertColumnInTable(searchField, table.tableName, `Search index '${name}'`);
1182
- assertNoReservedCreatedAtIndexFields([searchFieldName], `Search index '${name}'`);
1183
- assertSearchFieldType(searchField, name);
1184
- const filterFieldNames = filterFields.map((field) => assertColumnInTable(field, table.tableName, `Search index '${name}'`));
1185
- assertNoReservedCreatedAtIndexFields(filterFieldNames, `Search index '${name}'`);
1186
- table.addSearchIndex(name, {
1187
- searchField: searchFieldName,
1188
- filterFields: filterFieldNames,
1189
- staged
1190
- });
1191
- continue;
1192
- }
1193
- if (isConvexVectorIndexBuilder(entry)) {
1194
- const { name, vectorField, dimensions, filterFields, staged } = entry.config;
1195
- if (dimensions === void 0) throw new Error(`Vector index '${name}' is missing dimensions. Call .dimensions(n) before using.`);
1196
- const vectorFieldName = assertColumnInTable(vectorField, table.tableName, `Vector index '${name}'`);
1197
- assertNoReservedCreatedAtIndexFields([vectorFieldName], `Vector index '${name}'`);
1198
- assertVectorFieldType(vectorField, name);
1199
- const columnDimensions = getColumnDimensions(vectorField);
1200
- if (columnDimensions !== void 0 && columnDimensions !== dimensions) throw new Error(`Vector index '${name}' dimensions (${dimensions}) do not match vector column '${vectorFieldName}' dimensions (${columnDimensions}).`);
1201
- const filterFieldNames = filterFields.map((field) => assertColumnInTable(field, table.tableName, `Vector index '${name}'`));
1202
- assertNoReservedCreatedAtIndexFields(filterFieldNames, `Vector index '${name}'`);
1203
- table.addVectorIndex(name, {
1204
- vectorField: vectorFieldName,
1205
- dimensions,
1206
- filterFields: filterFieldNames,
1207
- staged
1208
- });
1209
- continue;
1095
+ sum(field) {
1096
+ this.config.sumField = field;
1097
+ return this;
1098
+ }
1099
+ };
1100
+ var ConvexVectorIndexBuilder = class {
1101
+ static [entityKind] = "ConvexVectorIndexBuilder";
1102
+ [entityKind] = "ConvexVectorIndexBuilder";
1103
+ config;
1104
+ constructor(name, vectorField) {
1105
+ this.config = {
1106
+ name,
1107
+ vectorField,
1108
+ dimensions: void 0,
1109
+ filterFields: [],
1110
+ staged: false
1111
+ };
1112
+ }
1113
+ dimensions(dimensions) {
1114
+ if (!Number.isInteger(dimensions)) throw new Error(`Vector index '${this.config.name}' dimensions must be an integer, got ${dimensions}`);
1115
+ if (dimensions <= 0) throw new Error(`Vector index '${this.config.name}' dimensions must be positive, got ${dimensions}`);
1116
+ if (dimensions > 1e4) console.warn(`Vector index '${this.config.name}' has unusually large dimensions (${dimensions}). Common values: 768, 1536, 3072`);
1117
+ this.config.dimensions = dimensions;
1118
+ return this;
1119
+ }
1120
+ filter(...fields) {
1121
+ this.config.filterFields = fields;
1122
+ return this;
1123
+ }
1124
+ staged() {
1125
+ this.config.staged = true;
1126
+ return this;
1127
+ }
1128
+ };
1129
+ function index(name) {
1130
+ return new ConvexIndexBuilderOn(name, false);
1131
+ }
1132
+
1133
+ //#endregion
1134
+ //#region src/orm/rls/policies.ts
1135
+ var RlsPolicy = class {
1136
+ static [entityKind] = "RlsPolicy";
1137
+ [entityKind] = "RlsPolicy";
1138
+ as;
1139
+ for;
1140
+ to;
1141
+ using;
1142
+ withCheck;
1143
+ /** @internal */
1144
+ _linkedTable;
1145
+ constructor(name, config) {
1146
+ this.name = name;
1147
+ if (config) {
1148
+ this.as = config.as;
1149
+ this.for = config.for;
1150
+ this.to = config.to;
1151
+ this.using = config.using;
1152
+ this.withCheck = config.withCheck;
1210
1153
  }
1211
- throw new Error(`Unsupported extra config value in convexTable('${table.tableName}').`);
1212
1154
  }
1155
+ link(table) {
1156
+ this._linkedTable = table;
1157
+ return this;
1158
+ }
1159
+ };
1160
+ function isRlsPolicy(value) {
1161
+ return !!value && typeof value === "object" && value[entityKind] === "RlsPolicy";
1213
1162
  }
1163
+
1164
+ //#endregion
1165
+ //#region src/orm/table.ts
1214
1166
  /**
1215
- * ConvexTable implementation class
1216
- * Provides all properties required by Convex's TableDefinition
1217
- *
1218
- * Following convex-ents pattern:
1219
- * - Private fields for indexes (matches TableDefinition structure)
1220
- * - Duck typing (defineSchema only checks object shape)
1221
- * - Direct validator storage (no re-wrapping)
1167
+ * Reserved Convex system table names that cannot be used
1222
1168
  */
1223
- var ConvexTableImpl = class {
1224
- /**
1225
- * Required by TableDefinition
1226
- * Public validator property containing v.object({...}) with all column validators
1227
- */
1228
- validator;
1229
- /**
1230
- * TableDefinition private fields
1231
- * These satisfy structural typing requirements for defineSchema()
1232
- */
1233
- indexes = [];
1234
- uniqueIndexes = [];
1235
- aggregateIndexes = [];
1236
- rankIndexes = [];
1237
- foreignKeys = [];
1238
- deferredForeignKeys = [];
1239
- deferredForeignKeysResolved = false;
1240
- stagedDbIndexes = [];
1241
- searchIndexes = [];
1242
- stagedSearchIndexes = [];
1243
- vectorIndexes = [];
1244
- stagedVectorIndexes = [];
1245
- checks = [];
1246
- /**
1247
- * Symbol-based metadata storage
1248
- */
1249
- [TableName];
1250
- [Columns];
1251
- [Brand] = "ConvexTable";
1252
- [EnableRLS] = false;
1253
- [RlsPolicies] = [];
1254
- [TableDeleteConfig];
1255
- [TablePolymorphic];
1256
- /**
1257
- * Public tableName for convenience
1258
- */
1259
- tableName;
1260
- constructor(name, columns, polymorphicConfigs) {
1261
- validateTableName(name);
1262
- for (const columnName of Object.keys(columns)) if (RESERVED_COLUMN_NAMES.has(columnName)) throw new Error(`Column name '${columnName}' is reserved. System fields are managed by Convex ORM.`);
1263
- this[TableName] = name;
1264
- const namedColumns = Object.fromEntries(Object.entries(columns).map(([columnName, builder]) => {
1265
- builder.config.name = columnName;
1266
- builder.config.tableName = name;
1267
- builder.config.table = this;
1268
- return [columnName, builder];
1269
- }));
1270
- this[Columns] = namedColumns;
1271
- this.tableName = name;
1272
- if (polymorphicConfigs && polymorphicConfigs.length > 0) this[TablePolymorphic] = polymorphicConfigs;
1273
- this.validator = createValidatorFromColumns(namedColumns);
1274
- for (const [columnName, builder] of Object.entries(namedColumns)) {
1275
- const config = builder.config;
1276
- if (config?.isUnique) {
1277
- const indexName = getUniqueIndexName(name, [columnName], config.uniqueName);
1278
- const nullsNotDistinct = config.uniqueNulls === "not distinct";
1279
- this.addIndex(indexName, [columnName]);
1280
- this.addUniqueIndex(indexName, [columnName], nullsNotDistinct);
1281
- }
1282
- if (config?.referenceTable && (!config.foreignKeyConfigs || config.foreignKeyConfigs.length === 0)) this.addForeignKey({
1283
- name: void 0,
1284
- columns: [columnName],
1285
- foreignTableName: config.referenceTable,
1286
- foreignColumns: ["_id"]
1287
- });
1288
- if (config?.foreignKeyConfigs?.length) for (const foreignConfig of config.foreignKeyConfigs) this.deferredForeignKeys.push({
1289
- localColumnName: columnName,
1290
- ref: foreignConfig.ref,
1291
- config: foreignConfig.config
1292
- });
1293
- }
1294
- }
1295
- getPolymorphicConfigs() {
1296
- return this[TablePolymorphic];
1297
- }
1298
- /**
1299
- * Internal: add index to table from builder extraConfig
1300
- *
1301
- */
1302
- addIndex(name, fields) {
1303
- this.indexes.push({
1304
- indexDescriptor: name,
1305
- fields
1306
- });
1307
- }
1308
- /**
1309
- * Internal: add unique index metadata for runtime enforcement
1310
- */
1311
- addUniqueIndex(name, fields, nullsNotDistinct) {
1312
- this.uniqueIndexes.push({
1313
- name,
1314
- fields,
1315
- nullsNotDistinct
1316
- });
1317
- }
1318
- addAggregateIndex(name, config) {
1319
- if (this.aggregateIndexes.some((index) => index.name === name) || this.rankIndexes.some((index) => index.name === name)) throw new Error(`Duplicate aggregate index '${name}' on '${this.tableName}'.`);
1320
- this.aggregateIndexes.push({
1321
- name,
1322
- fields: config.fields,
1323
- countFields: config.countFields,
1324
- sumFields: config.sumFields,
1325
- avgFields: config.avgFields,
1326
- minFields: config.minFields,
1327
- maxFields: config.maxFields
1328
- });
1169
+ const RESERVED_TABLES = new Set(["_storage", "_scheduled_functions"]);
1170
+ const RESERVED_COLUMN_NAMES = new Set([
1171
+ "id",
1172
+ "_id",
1173
+ "_creationTime"
1174
+ ]);
1175
+ const DEFAULT_POLYMORPHIC_ALIAS = "details";
1176
+ const CONVEX_TABLE_FIELD_LIMIT = 1024;
1177
+ /**
1178
+ * Valid table name pattern: starts with letter/underscore, contains only alphanumeric and underscore
1179
+ */
1180
+ const TABLE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
1181
+ /**
1182
+ * Validate table name against Convex constraints
1183
+ */
1184
+ function validateTableName(name) {
1185
+ if (RESERVED_TABLES.has(name)) throw new Error(`Table name '${name}' is reserved. System tables cannot be redefined.`);
1186
+ if (!TABLE_NAME_REGEX.test(name)) throw new Error(`Invalid table name '${name}'. Must start with letter, contain only alphanumeric and underscore.`);
1187
+ }
1188
+ /**
1189
+ * Create a Convex object validator from column builders
1190
+ *
1191
+ * Extracts .convexValidator from each column and creates v.object({...})
1192
+ * This is the core factory that bridges ORM columns to Convex validators.
1193
+ *
1194
+ * @param columns - Record of column name to column builder
1195
+ * @returns Convex object validator
1196
+ */
1197
+ function createValidatorFromColumns(columns) {
1198
+ const validatorFields = Object.fromEntries(Object.entries(columns).map(([key, builder]) => [key, builder.convexValidator]));
1199
+ return v.object(validatorFields);
1200
+ }
1201
+ var ConvexDeletionBuilder = class {
1202
+ static [entityKind] = "ConvexDeletionBuilder";
1203
+ [entityKind] = "ConvexDeletionBuilder";
1204
+ constructor(config) {
1205
+ this.config = config;
1329
1206
  }
1330
- addRankIndex(name, config) {
1331
- if (this.rankIndexes.some((index) => index.name === name) || this.aggregateIndexes.some((index) => index.name === name)) throw new Error(`Duplicate aggregate index '${name}' on '${this.tableName}'.`);
1332
- this.rankIndexes.push({
1333
- name,
1334
- partitionFields: config.partitionFields,
1335
- orderFields: config.orderFields,
1336
- sumField: config.sumField
1207
+ };
1208
+ function isConvexIndexBuilder(value) {
1209
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexIndexBuilder";
1210
+ }
1211
+ function isConvexIndexBuilderOn(value) {
1212
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexIndexBuilderOn";
1213
+ }
1214
+ function isConvexAggregateIndexBuilder(value) {
1215
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexAggregateIndexBuilder";
1216
+ }
1217
+ function isConvexAggregateIndexBuilderOn(value) {
1218
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexAggregateIndexBuilderOn";
1219
+ }
1220
+ function isConvexRankIndexBuilderOn(value) {
1221
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexRankIndexBuilderOn";
1222
+ }
1223
+ function isConvexRankIndexBuilder(value) {
1224
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexRankIndexBuilder";
1225
+ }
1226
+ function isConvexUniqueConstraintBuilderOn(value) {
1227
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexUniqueConstraintBuilderOn";
1228
+ }
1229
+ function isConvexForeignKeyBuilder(value) {
1230
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexForeignKeyBuilder";
1231
+ }
1232
+ function isConvexCheckBuilder(value) {
1233
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexCheckBuilder";
1234
+ }
1235
+ function isConvexSearchIndexBuilderOn(value) {
1236
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexSearchIndexBuilderOn";
1237
+ }
1238
+ function isConvexUniqueConstraintBuilder(value) {
1239
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexUniqueConstraintBuilder";
1240
+ }
1241
+ function isConvexSearchIndexBuilder(value) {
1242
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexSearchIndexBuilder";
1243
+ }
1244
+ function isConvexVectorIndexBuilderOn(value) {
1245
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexVectorIndexBuilderOn";
1246
+ }
1247
+ function isConvexVectorIndexBuilder(value) {
1248
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexVectorIndexBuilder";
1249
+ }
1250
+ function isConvexDeletionBuilder(value) {
1251
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexDeletionBuilder";
1252
+ }
1253
+ function isConvexLifecycleBuilder(value) {
1254
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexLifecycleBuilder";
1255
+ }
1256
+ function getColumnName(column) {
1257
+ const config = column.config;
1258
+ if (!config?.name) throw new Error("Invalid index column: expected a convexTable column builder.");
1259
+ return config.name;
1260
+ }
1261
+ function getColumnType(column) {
1262
+ return column.config?.columnType;
1263
+ }
1264
+ function getColumnDimensions(column) {
1265
+ return column.config?.dimensions;
1266
+ }
1267
+ function getColumnTableName(column) {
1268
+ const config = column.config;
1269
+ return config?.tableName ?? config?.referenceTable;
1270
+ }
1271
+ function getColumnTable(column) {
1272
+ return column.config?.table;
1273
+ }
1274
+ function getUniqueIndexName(tableName, fields, explicitName) {
1275
+ if (explicitName) return explicitName;
1276
+ return `${tableName}_${fields.join("_")}_unique`;
1277
+ }
1278
+ function assertColumnInTable(column, expectedTable, context) {
1279
+ const tableName = getColumnTableName(column);
1280
+ if (tableName && tableName !== expectedTable) throw new Error(`${context} references column from '${tableName}', but belongs to '${expectedTable}'.`);
1281
+ return getColumnName(column);
1282
+ }
1283
+ function assertNoReservedCreatedAtIndexFields(fields, context) {
1284
+ if (fields.includes("createdAt")) throw new Error(`${context} cannot use 'createdAt'. 'createdAt' is reserved and maps to internal '_creationTime'.`);
1285
+ }
1286
+ function assertSearchFieldType(column, indexName) {
1287
+ const columnType = getColumnType(column) ?? "unknown";
1288
+ if (columnType !== "ConvexText") throw new Error(`Search index '${indexName}' only supports text() columns. Field '${getColumnName(column)}' is type '${columnType}'.`);
1289
+ }
1290
+ function assertVectorFieldType(column, indexName) {
1291
+ const columnType = getColumnType(column) ?? "unknown";
1292
+ if (columnType !== "ConvexVector") throw new Error(`Vector index '${indexName}' requires a vector() column. Field '${getColumnName(column)}' is type '${columnType}'.`);
1293
+ }
1294
+ function assertAggregateSumFieldType(column, indexName) {
1295
+ const columnType = getColumnType(column) ?? "unknown";
1296
+ if (!["ConvexNumber", "ConvexTimestamp"].includes(columnType)) throw new Error(`aggregateIndex '${indexName}' sum() supports integer()/timestamp() columns only. Field '${getColumnName(column)}' is type '${columnType}'.`);
1297
+ }
1298
+ function assertAggregateAvgFieldType(column, indexName) {
1299
+ const columnType = getColumnType(column) ?? "unknown";
1300
+ if (!["ConvexNumber", "ConvexTimestamp"].includes(columnType)) throw new Error(`aggregateIndex '${indexName}' avg() supports integer()/timestamp() columns only. Field '${getColumnName(column)}' is type '${columnType}'.`);
1301
+ }
1302
+ function assertAggregateComparableFieldType(column, indexName, method) {
1303
+ const columnType = getColumnType(column) ?? "unknown";
1304
+ if (![
1305
+ "ConvexNumber",
1306
+ "ConvexTimestamp",
1307
+ "ConvexDate",
1308
+ "ConvexText",
1309
+ "ConvexBoolean",
1310
+ "ConvexId"
1311
+ ].includes(columnType)) throw new Error(`aggregateIndex '${indexName}' ${method}() does not support column type '${columnType}' on '${getColumnName(column)}'.`);
1312
+ }
1313
+ function assertRankOrderFieldType(column, indexName) {
1314
+ const columnType = getColumnType(column) ?? "unknown";
1315
+ if (![
1316
+ "ConvexNumber",
1317
+ "ConvexTimestamp",
1318
+ "ConvexDate"
1319
+ ].includes(columnType)) throw new Error(`rankIndex '${indexName}' orderBy() supports integer()/timestamp()/date() columns only. Field '${getColumnName(column)}' is type '${columnType}'.`);
1320
+ }
1321
+ const dedupeFieldNames = (fields) => [...new Set(fields)];
1322
+ const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1323
+ const isColumnBuilder = (value) => isRecord$1(value) && typeof value.build === "function";
1324
+ const getDiscriminatorConfig = (value) => {
1325
+ if (!isColumnBuilder(value)) return;
1326
+ const discriminator = value.config?.discriminator;
1327
+ if (!discriminator) return;
1328
+ return discriminator;
1329
+ };
1330
+ const getPolymorphicFieldSignature = (column) => {
1331
+ const validator = column.convexValidator ?? column.build();
1332
+ return JSON.stringify({
1333
+ columnType: column.config?.columnType,
1334
+ validator: validator?.json
1335
+ });
1336
+ };
1337
+ function resolveTableColumns(tableName, columns) {
1338
+ const resolvedColumns = {};
1339
+ const pendingPolymorphic = [];
1340
+ for (const [columnName, rawBuilder] of Object.entries(columns)) {
1341
+ if (!isColumnBuilder(rawBuilder)) throw new Error(`Column '${columnName}' on '${tableName}' must be a column builder.`);
1342
+ resolvedColumns[columnName] = rawBuilder;
1343
+ const discriminatorConfig = getDiscriminatorConfig(rawBuilder);
1344
+ if (!discriminatorConfig) continue;
1345
+ if (!isRecord$1(discriminatorConfig.variants) || Object.keys(discriminatorConfig.variants).length === 0) throw new Error(`discriminator('${tableName}.${columnName}') requires at least one variant.`);
1346
+ const alias = discriminatorConfig.as === void 0 ? DEFAULT_POLYMORPHIC_ALIAS : discriminatorConfig.as;
1347
+ if (typeof alias !== "string" || alias.length === 0) throw new Error(`discriminator('${tableName}.${columnName}').as must be a non-empty string.`);
1348
+ pendingPolymorphic.push({
1349
+ discriminator: columnName,
1350
+ alias,
1351
+ variants: discriminatorConfig.variants
1337
1352
  });
1338
1353
  }
1339
- getAggregateIndexes() {
1340
- return [...this.aggregateIndexes];
1341
- }
1342
- getRankIndexes() {
1343
- return [...this.rankIndexes];
1344
- }
1345
- /**
1346
- * Internal: expose unique index metadata for mutation enforcement
1347
- */
1348
- getUniqueIndexes() {
1349
- return this.uniqueIndexes;
1350
- }
1351
- /**
1352
- * Internal: expose index metadata for runtime enforcement
1353
- */
1354
- getIndexes() {
1355
- return this.indexes.map((entry) => ({
1356
- name: entry.indexDescriptor,
1357
- fields: entry.fields
1358
- }));
1359
- }
1360
- /**
1361
- * Internal: expose search index metadata for runtime query execution
1362
- */
1363
- getSearchIndexes() {
1364
- return this.searchIndexes.map((entry) => ({
1365
- name: entry.indexDescriptor,
1366
- searchField: entry.searchField,
1367
- filterFields: entry.filterFields
1368
- }));
1369
- }
1370
- /**
1371
- * Internal: expose vector index metadata for runtime query execution
1372
- */
1373
- getVectorIndexes() {
1374
- return this.vectorIndexes.map((entry) => ({
1375
- name: entry.indexDescriptor,
1376
- vectorField: entry.vectorField,
1377
- dimensions: entry.dimensions,
1378
- filterFields: entry.filterFields
1379
- }));
1380
- }
1381
- /**
1382
- * Internal: attach an RLS policy to this table
1383
- */
1384
- addRlsPolicy(policy) {
1385
- this[RlsPolicies].push(policy);
1386
- this[EnableRLS] = true;
1387
- }
1388
- /**
1389
- * Internal: return attached RLS policies
1390
- */
1391
- getRlsPolicies() {
1392
- return this[RlsPolicies];
1393
- }
1394
- /**
1395
- * Internal: check if RLS is enabled on this table
1396
- */
1397
- isRlsEnabled() {
1398
- return this[EnableRLS];
1399
- }
1400
- /**
1401
- * Internal: add foreign key metadata for runtime enforcement
1402
- */
1403
- addForeignKey(definition) {
1404
- const matches = (existing) => {
1405
- if (existing.foreignTableName !== definition.foreignTableName) return false;
1406
- if (existing.columns.length !== definition.columns.length) return false;
1407
- if (existing.foreignColumns.length !== definition.foreignColumns.length) return false;
1408
- for (let i = 0; i < existing.columns.length; i++) if (existing.columns[i] !== definition.columns[i]) return false;
1409
- for (let i = 0; i < existing.foreignColumns.length; i++) if (existing.foreignColumns[i] !== definition.foreignColumns[i]) return false;
1410
- return true;
1411
- };
1412
- this.foreignKeys = this.foreignKeys.filter((existing) => !matches(existing));
1413
- this.foreignKeys.push(definition);
1354
+ if (pendingPolymorphic.length > 1) throw new Error(`Only one discriminator(...) column is currently supported on '${tableName}'.`);
1355
+ const polymorphicConfigs = [];
1356
+ for (const pending of pendingPolymorphic) {
1357
+ if (pending.alias in resolvedColumns) throw new Error(`discriminator('${tableName}.${pending.discriminator}') alias '${pending.alias}' collides with an existing column.`);
1358
+ const generatedFieldMap = /* @__PURE__ */ new Map();
1359
+ const variantRuntime = {};
1360
+ for (const [variantKey, rawVariantColumns] of Object.entries(pending.variants)) {
1361
+ if (!isRecord$1(rawVariantColumns)) throw new Error(`discriminator('${tableName}.${pending.discriminator}') variant '${variantKey}' must be an object.`);
1362
+ const fieldNames = [];
1363
+ const requiredFieldNames = [];
1364
+ for (const [fieldName, rawFieldBuilder] of Object.entries(rawVariantColumns)) {
1365
+ if (!isColumnBuilder(rawFieldBuilder)) throw new Error(`discriminator('${tableName}.${pending.discriminator}').variants.${variantKey}.${fieldName} must be a column builder.`);
1366
+ if (fieldName in resolvedColumns) throw new Error(`discriminator('${tableName}.${pending.discriminator}').variants.${variantKey}.${fieldName} collides with an existing table column.`);
1367
+ const fieldBuilder = rawFieldBuilder;
1368
+ const fieldConfig = fieldBuilder.config;
1369
+ const isRequiredForVariant = fieldConfig?.notNull === true && fieldConfig.hasDefault !== true && typeof fieldConfig.defaultFn !== "function";
1370
+ const signature = getPolymorphicFieldSignature(fieldBuilder);
1371
+ const existing = generatedFieldMap.get(fieldName);
1372
+ if (existing && existing.signature !== signature) throw new Error(`discriminator('${tableName}.${pending.discriminator}') field '${fieldName}' has conflicting builder signatures across variants.`);
1373
+ if (!existing) {
1374
+ if (fieldConfig) fieldConfig.notNull = false;
1375
+ generatedFieldMap.set(fieldName, {
1376
+ builder: fieldBuilder,
1377
+ signature
1378
+ });
1379
+ }
1380
+ fieldNames.push(fieldName);
1381
+ if (isRequiredForVariant) requiredFieldNames.push(fieldName);
1382
+ }
1383
+ variantRuntime[variantKey] = {
1384
+ fieldNames,
1385
+ requiredFieldNames
1386
+ };
1387
+ }
1388
+ for (const [fieldName, { builder }] of generatedFieldMap.entries()) resolvedColumns[fieldName] = builder;
1389
+ polymorphicConfigs.push({
1390
+ discriminator: pending.discriminator,
1391
+ alias: pending.alias,
1392
+ generatedFieldNames: Object.freeze([...generatedFieldMap.keys()]),
1393
+ variants: Object.freeze(variantRuntime)
1394
+ });
1414
1395
  }
1415
- resolveDeferredForeignKeys() {
1416
- if (this.deferredForeignKeysResolved) return;
1417
- this.deferredForeignKeysResolved = true;
1418
- for (const deferred of this.deferredForeignKeys) {
1419
- let foreignColumn;
1420
- try {
1421
- foreignColumn = deferred.ref();
1422
- } catch (error) {
1423
- const reason = error instanceof Error ? ` ${error.message}` : "";
1424
- throw new Error(`Failed to resolve foreign key reference for '${this.tableName}.${deferred.localColumnName}'. Use references(() => targetTable.column) after both tables are declared.${reason}`);
1396
+ if (Object.keys(resolvedColumns).length > CONVEX_TABLE_FIELD_LIMIT) throw new Error(`Table '${tableName}' exceeds Convex field count limit (${CONVEX_TABLE_FIELD_LIMIT}) after discriminator expansion.`);
1397
+ return {
1398
+ columns: resolvedColumns,
1399
+ polymorphicConfigs
1400
+ };
1401
+ }
1402
+ function applyExtraConfig(table, config) {
1403
+ if (!config) return;
1404
+ const entries = Array.isArray(config) ? config : Object.values(config);
1405
+ for (const entry of entries) {
1406
+ if (isConvexIndexBuilderOn(entry)) throw new Error(`Invalid index definition on '${table.tableName}'. Did you forget to call .on(...)?`);
1407
+ if (isConvexUniqueConstraintBuilderOn(entry)) throw new Error(`Invalid unique constraint definition on '${table.tableName}'. Did you forget to call .on(...)?`);
1408
+ if (isConvexAggregateIndexBuilderOn(entry)) throw new Error(`Invalid aggregate index definition on '${table.tableName}'. Did you forget to call .on(...) or .all()?`);
1409
+ if (isConvexRankIndexBuilderOn(entry)) throw new Error(`Invalid rank index definition on '${table.tableName}'. Did you forget to call .partitionBy(...) or .all()?`);
1410
+ if (isConvexSearchIndexBuilderOn(entry)) throw new Error(`Invalid search index definition on '${table.tableName}'. Did you forget to call .on(...)?`);
1411
+ if (isConvexVectorIndexBuilderOn(entry)) throw new Error(`Invalid vector index definition on '${table.tableName}'. Did you forget to call .on(...)?`);
1412
+ if (isRlsPolicy(entry)) {
1413
+ const target = entry._linkedTable ?? table;
1414
+ if (typeof target.addRlsPolicy === "function") target.addRlsPolicy(entry);
1415
+ else {
1416
+ const policies = target[RlsPolicies] ?? [];
1417
+ policies.push(entry);
1418
+ target[RlsPolicies] = policies;
1419
+ target[EnableRLS] = true;
1425
1420
  }
1426
- const foreignTableName = getColumnTableName(foreignColumn);
1427
- if (!foreignTableName) throw new Error(`Foreign key on '${this.tableName}.${deferred.localColumnName}' references a column without a table. Use references(() => targetTable.column).`);
1428
- const foreignTable = getColumnTable(foreignColumn);
1429
- if (!foreignTable) throw new Error(`Foreign key on '${this.tableName}.${deferred.localColumnName}' references a column without table metadata. Replace references(() => id('tableName')) with references(() => table.id).`);
1430
- const foreignColumnName = getColumnName(foreignColumn);
1431
- this.addForeignKey({
1432
- name: deferred.config.name,
1433
- columns: [deferred.localColumnName],
1421
+ continue;
1422
+ }
1423
+ if (isConvexDeletionBuilder(entry)) {
1424
+ if (table[TableDeleteConfig]) throw new Error(`Only one deletion(...) config can be defined for '${table.tableName}'.`);
1425
+ table[TableDeleteConfig] = {
1426
+ mode: entry.config.mode,
1427
+ delayMs: entry.config.delayMs
1428
+ };
1429
+ continue;
1430
+ }
1431
+ if (isConvexLifecycleBuilder(entry)) throw new Error(`Lifecycle hooks are no longer supported inside convexTable('${table.tableName}', ..., extraConfig). Export schema triggers with defineTriggers(relations, { ... }) from schema.ts.`);
1432
+ if (isConvexIndexBuilder(entry)) {
1433
+ const { name, columns, unique, where } = entry.config;
1434
+ if (where) throw new Error(`Convex does not support partial indexes. Remove .where(...) from index '${name}'.`);
1435
+ if (unique) {}
1436
+ const fields = columns.map((column) => assertColumnInTable(column, table.tableName, `Index '${name}'`));
1437
+ assertNoReservedCreatedAtIndexFields(fields, `Index '${name}'`);
1438
+ table.addIndex(name, fields);
1439
+ if (unique) table.addUniqueIndex(name, fields, false);
1440
+ continue;
1441
+ }
1442
+ if (isConvexAggregateIndexBuilder(entry)) {
1443
+ const { name, columns, countFields, sumFields, avgFields, minFields, maxFields } = entry.config;
1444
+ const fields = columns.map((column) => assertColumnInTable(column, table.tableName, `Aggregate index '${name}'`));
1445
+ assertNoReservedCreatedAtIndexFields(fields, `Aggregate index '${name}'`);
1446
+ const resolvedCountFields = dedupeFieldNames(countFields.map((column) => assertColumnInTable(column, table.tableName, `Aggregate index '${name}' count`)));
1447
+ assertNoReservedCreatedAtIndexFields(resolvedCountFields, `Aggregate index '${name}' count`);
1448
+ const resolvedSumFields = dedupeFieldNames(sumFields.map((column) => {
1449
+ const field = assertColumnInTable(column, table.tableName, `Aggregate index '${name}' sum`);
1450
+ assertAggregateSumFieldType(column, name);
1451
+ return field;
1452
+ }));
1453
+ assertNoReservedCreatedAtIndexFields(resolvedSumFields, `Aggregate index '${name}' sum`);
1454
+ const resolvedAvgFields = dedupeFieldNames(avgFields.map((column) => {
1455
+ const field = assertColumnInTable(column, table.tableName, `Aggregate index '${name}' avg`);
1456
+ assertAggregateAvgFieldType(column, name);
1457
+ return field;
1458
+ }));
1459
+ assertNoReservedCreatedAtIndexFields(resolvedAvgFields, `Aggregate index '${name}' avg`);
1460
+ const resolvedMinFields = dedupeFieldNames(minFields.map((column) => {
1461
+ const field = assertColumnInTable(column, table.tableName, `Aggregate index '${name}' min`);
1462
+ assertAggregateComparableFieldType(column, name, "min");
1463
+ return field;
1464
+ }));
1465
+ assertNoReservedCreatedAtIndexFields(resolvedMinFields, `Aggregate index '${name}' min`);
1466
+ const resolvedMaxFields = dedupeFieldNames(maxFields.map((column) => {
1467
+ const field = assertColumnInTable(column, table.tableName, `Aggregate index '${name}' max`);
1468
+ assertAggregateComparableFieldType(column, name, "max");
1469
+ return field;
1470
+ }));
1471
+ assertNoReservedCreatedAtIndexFields(resolvedMaxFields, `Aggregate index '${name}' max`);
1472
+ table.addAggregateIndex(name, {
1473
+ fields,
1474
+ countFields: resolvedCountFields,
1475
+ sumFields: resolvedSumFields,
1476
+ avgFields: resolvedAvgFields,
1477
+ minFields: resolvedMinFields,
1478
+ maxFields: resolvedMaxFields
1479
+ });
1480
+ continue;
1481
+ }
1482
+ if (isConvexRankIndexBuilder(entry)) {
1483
+ const { name, partitionColumns, orderColumns, sumField } = entry.config;
1484
+ if (!orderColumns.length) throw new Error(`rankIndex '${name}' on '${table.tableName}' must declare at least one orderBy(...) column.`);
1485
+ const resolvedPartitionFields = dedupeFieldNames(partitionColumns.map((column) => assertColumnInTable(column, table.tableName, `rankIndex '${name}'`)));
1486
+ assertNoReservedCreatedAtIndexFields(resolvedPartitionFields, `rankIndex '${name}' partitionBy`);
1487
+ const resolvedOrderFields = orderColumns.map((entry) => {
1488
+ const field = assertColumnInTable(entry.column, table.tableName, `rankIndex '${name}' orderBy`);
1489
+ assertNoReservedCreatedAtIndexFields([field], `rankIndex '${name}'`);
1490
+ assertRankOrderFieldType(entry.column, name);
1491
+ return {
1492
+ field,
1493
+ direction: entry.direction
1494
+ };
1495
+ });
1496
+ const resolvedSumField = sumField ? (() => {
1497
+ const field = assertColumnInTable(sumField, table.tableName, `rankIndex '${name}' sum`);
1498
+ assertNoReservedCreatedAtIndexFields([field], `rankIndex '${name}'`);
1499
+ assertAggregateSumFieldType(sumField, name);
1500
+ return field;
1501
+ })() : void 0;
1502
+ table.addRankIndex(name, {
1503
+ partitionFields: resolvedPartitionFields,
1504
+ orderFields: resolvedOrderFields,
1505
+ sumField: resolvedSumField
1506
+ });
1507
+ continue;
1508
+ }
1509
+ if (isConvexUniqueConstraintBuilder(entry)) {
1510
+ const { name, columns, nullsNotDistinct } = entry.config;
1511
+ const fields = columns.map((column) => assertColumnInTable(column, table.tableName, "Unique constraint"));
1512
+ assertNoReservedCreatedAtIndexFields(fields, "Unique constraint");
1513
+ const indexName = getUniqueIndexName(table.tableName, fields, name);
1514
+ table.addIndex(indexName, fields);
1515
+ table.addUniqueIndex(indexName, fields, nullsNotDistinct);
1516
+ continue;
1517
+ }
1518
+ if (isConvexForeignKeyBuilder(entry)) {
1519
+ const { name, columns, foreignColumns, onDelete, onUpdate } = entry.config;
1520
+ if (columns.length === 0 || foreignColumns.length === 0) throw new Error(`Foreign key on '${table.tableName}' requires at least one column.`);
1521
+ if (columns.length !== foreignColumns.length) throw new Error(`Foreign key on '${table.tableName}' must specify matching columns and foreignColumns.`);
1522
+ const localFields = columns.map((column) => assertColumnInTable(column, table.tableName, "Foreign key"));
1523
+ const foreignTableName = getColumnTableName(foreignColumns[0]);
1524
+ if (!foreignTableName) throw new Error(`Foreign key on '${table.tableName}' references a column without a table.`);
1525
+ const foreignTable = getColumnTable(foreignColumns[0]);
1526
+ const foreignFields = foreignColumns.map((column) => {
1527
+ const tableName = getColumnTableName(column);
1528
+ if (tableName && tableName !== foreignTableName) throw new Error(`Foreign key on '${table.tableName}' mixes foreign columns from '${foreignTableName}' and '${tableName}'.`);
1529
+ return getColumnName(column);
1530
+ });
1531
+ table.addForeignKey({
1532
+ name,
1533
+ columns: localFields,
1434
1534
  foreignTableName,
1435
1535
  foreignTable,
1436
- foreignColumns: [foreignColumnName],
1437
- onDelete: deferred.config.onDelete,
1438
- onUpdate: deferred.config.onUpdate
1536
+ foreignColumns: foreignFields,
1537
+ onDelete,
1538
+ onUpdate
1439
1539
  });
1540
+ continue;
1440
1541
  }
1441
- this.deferredForeignKeys = [];
1542
+ if (isConvexCheckBuilder(entry)) {
1543
+ const { name, expression } = entry.config;
1544
+ table.addCheck(name, expression);
1545
+ continue;
1546
+ }
1547
+ if (isConvexSearchIndexBuilder(entry)) {
1548
+ const { name, searchField, filterFields, staged } = entry.config;
1549
+ const searchFieldName = assertColumnInTable(searchField, table.tableName, `Search index '${name}'`);
1550
+ assertNoReservedCreatedAtIndexFields([searchFieldName], `Search index '${name}'`);
1551
+ assertSearchFieldType(searchField, name);
1552
+ const filterFieldNames = filterFields.map((field) => assertColumnInTable(field, table.tableName, `Search index '${name}'`));
1553
+ assertNoReservedCreatedAtIndexFields(filterFieldNames, `Search index '${name}'`);
1554
+ table.addSearchIndex(name, {
1555
+ searchField: searchFieldName,
1556
+ filterFields: filterFieldNames,
1557
+ staged
1558
+ });
1559
+ continue;
1560
+ }
1561
+ if (isConvexVectorIndexBuilder(entry)) {
1562
+ const { name, vectorField, dimensions, filterFields, staged } = entry.config;
1563
+ if (dimensions === void 0) throw new Error(`Vector index '${name}' is missing dimensions. Call .dimensions(n) before using.`);
1564
+ const vectorFieldName = assertColumnInTable(vectorField, table.tableName, `Vector index '${name}'`);
1565
+ assertNoReservedCreatedAtIndexFields([vectorFieldName], `Vector index '${name}'`);
1566
+ assertVectorFieldType(vectorField, name);
1567
+ const columnDimensions = getColumnDimensions(vectorField);
1568
+ if (columnDimensions !== void 0 && columnDimensions !== dimensions) throw new Error(`Vector index '${name}' dimensions (${dimensions}) do not match vector column '${vectorFieldName}' dimensions (${columnDimensions}).`);
1569
+ const filterFieldNames = filterFields.map((field) => assertColumnInTable(field, table.tableName, `Vector index '${name}'`));
1570
+ assertNoReservedCreatedAtIndexFields(filterFieldNames, `Vector index '${name}'`);
1571
+ table.addVectorIndex(name, {
1572
+ vectorField: vectorFieldName,
1573
+ dimensions,
1574
+ filterFields: filterFieldNames,
1575
+ staged
1576
+ });
1577
+ continue;
1578
+ }
1579
+ throw new Error(`Unsupported extra config value in convexTable('${table.tableName}').`);
1442
1580
  }
1581
+ }
1582
+ /**
1583
+ * ConvexTable implementation class
1584
+ * Provides all properties required by Convex's TableDefinition
1585
+ *
1586
+ * Following convex-ents pattern:
1587
+ * - Private fields for indexes (matches TableDefinition structure)
1588
+ * - Duck typing (defineSchema only checks object shape)
1589
+ * - Direct validator storage (no re-wrapping)
1590
+ */
1591
+ var ConvexTableImpl = class {
1443
1592
  /**
1444
- * Internal: expose foreign key metadata for mutation enforcement
1593
+ * Required by TableDefinition
1594
+ * Public validator property containing v.object({...}) with all column validators
1445
1595
  */
1446
- getForeignKeys() {
1447
- this.resolveDeferredForeignKeys();
1448
- return this.foreignKeys;
1449
- }
1450
- addCheck(name, expression) {
1451
- this.checks.push({
1452
- name,
1453
- expression
1454
- });
1455
- }
1456
- getChecks() {
1457
- return this.checks;
1458
- }
1596
+ validator;
1459
1597
  /**
1460
- * Internal: add search index to table from builder extraConfig
1598
+ * TableDefinition private fields
1599
+ * These satisfy structural typing requirements for defineSchema()
1461
1600
  */
1462
- addSearchIndex(name, config) {
1463
- const entry = {
1464
- indexDescriptor: name,
1465
- searchField: config.searchField,
1466
- filterFields: config.filterFields ?? []
1467
- };
1468
- if (config.staged) this.stagedSearchIndexes.push(entry);
1469
- else this.searchIndexes.push(entry);
1470
- }
1601
+ indexes = [];
1602
+ uniqueIndexes = [];
1603
+ aggregateIndexes = [];
1604
+ rankIndexes = [];
1605
+ foreignKeys = [];
1606
+ deferredForeignKeys = [];
1607
+ deferredForeignKeysResolved = false;
1608
+ stagedDbIndexes = [];
1609
+ searchIndexes = [];
1610
+ stagedSearchIndexes = [];
1611
+ vectorIndexes = [];
1612
+ stagedVectorIndexes = [];
1613
+ checks = [];
1471
1614
  /**
1472
- * Internal: add vector index to table from builder extraConfig
1615
+ * Symbol-based metadata storage
1473
1616
  */
1474
- addVectorIndex(name, config) {
1475
- const entry = {
1476
- indexDescriptor: name,
1477
- vectorField: config.vectorField,
1478
- dimensions: config.dimensions,
1479
- filterFields: config.filterFields ?? []
1480
- };
1481
- if (config.staged) this.stagedVectorIndexes.push(entry);
1482
- else this.vectorIndexes.push(entry);
1483
- }
1617
+ [TableName];
1618
+ [Columns];
1619
+ [Brand] = "ConvexTable";
1620
+ [EnableRLS] = false;
1621
+ [RlsPolicies] = [];
1622
+ [TableDeleteConfig];
1623
+ [TablePolymorphic];
1484
1624
  /**
1485
- * Export the contents of this definition for Convex schema tooling.
1486
- * Mirrors convex/server TableDefinition.export().
1625
+ * Public tableName for convenience
1487
1626
  */
1488
- export() {
1489
- const documentType = this.validator.json;
1490
- if (typeof documentType !== "object") throw new Error("Invalid validator: please make sure that the parameter of `defineTable` is valid (see https://docs.convex.dev/database/schemas)");
1491
- return {
1492
- indexes: this.indexes,
1493
- stagedDbIndexes: this.stagedDbIndexes,
1494
- searchIndexes: this.searchIndexes,
1495
- stagedSearchIndexes: this.stagedSearchIndexes,
1496
- vectorIndexes: this.vectorIndexes,
1497
- stagedVectorIndexes: this.stagedVectorIndexes,
1498
- documentType
1499
- };
1500
- }
1501
- };
1502
- const convexTableInternal = (name, columns, extraConfig) => {
1503
- const expanded = resolveTableColumns(name, columns);
1504
- const rawTable = new ConvexTableImpl(name, expanded.columns, expanded.polymorphicConfigs);
1505
- const systemFields = createSystemFields(name);
1506
- for (const builder of Object.values(systemFields)) builder.config.table = rawTable;
1507
- const table = Object.assign(rawTable, systemFields, rawTable[Columns]);
1508
- const internalCreationTime = systemFields._creationTime;
1509
- if (Object.hasOwn(table, "_creationTime")) table._creationTime = void 0;
1510
- Object.defineProperty(table, "_creationTime", {
1511
- value: internalCreationTime,
1512
- enumerable: false,
1513
- configurable: true,
1514
- writable: false
1515
- });
1516
- Object.defineProperty(table, "_id", {
1517
- value: systemFields.id,
1518
- enumerable: false,
1519
- configurable: true,
1520
- writable: false
1521
- });
1522
- applyExtraConfig(rawTable, extraConfig?.(table));
1523
- return table;
1524
- };
1525
- const convexTableWithRLS = (name, columns, extraConfig) => {
1526
- const table = convexTableInternal(name, columns, extraConfig);
1527
- table[EnableRLS] = true;
1528
- return table;
1529
- };
1530
- const convexTable = Object.assign(convexTableInternal, { withRLS: convexTableWithRLS });
1531
-
1532
- //#endregion
1533
- //#region src/orm/aggregate-index/schema.ts
1534
- const AGGREGATE_BUCKET_TABLE = "aggregate_bucket";
1535
- const AGGREGATE_MEMBER_TABLE = "aggregate_member";
1536
- const AGGREGATE_EXTREMA_TABLE = "aggregate_extrema";
1537
- const AGGREGATE_RANK_TREE_TABLE = "aggregate_rank_tree";
1538
- const AGGREGATE_RANK_NODE_TABLE = "aggregate_rank_node";
1539
- const AGGREGATE_STATE_TABLE = "aggregate_state";
1540
- const countBucketTable = convexTable(AGGREGATE_BUCKET_TABLE, {
1541
- tableKey: text().notNull(),
1542
- indexName: text().notNull(),
1543
- keyHash: text().notNull(),
1544
- keyParts: arrayOf(json()).notNull(),
1545
- count: integer().notNull(),
1546
- sumValues: objectOf(integer().notNull()).notNull(),
1547
- nonNullCountValues: objectOf(integer().notNull()).notNull(),
1548
- updatedAt: integer().notNull()
1549
- }, (t) => [index("by_table_index_hash").on(t.tableKey, t.indexName, t.keyHash), index("by_table_index").on(t.tableKey, t.indexName)]);
1550
- const countMemberTable = convexTable(AGGREGATE_MEMBER_TABLE, {
1551
- kind: text().notNull(),
1552
- tableKey: text().notNull(),
1553
- indexName: text().notNull(),
1554
- docId: text().notNull(),
1555
- keyHash: text().notNull(),
1556
- keyParts: arrayOf(json()).notNull(),
1557
- sumValues: objectOf(integer().notNull()).notNull(),
1558
- nonNullCountValues: objectOf(integer().notNull()).notNull(),
1559
- extremaValues: objectOf(json()).notNull(),
1560
- rankNamespace: json(),
1561
- rankKey: json(),
1562
- rankSumValue: integer(),
1563
- updatedAt: integer().notNull()
1564
- }, (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)]);
1565
- const countExtremaTable = convexTable(AGGREGATE_EXTREMA_TABLE, {
1566
- tableKey: text().notNull(),
1567
- indexName: text().notNull(),
1568
- keyHash: text().notNull(),
1569
- fieldName: text().notNull(),
1570
- valueHash: text().notNull(),
1571
- value: json().notNull(),
1572
- sortKey: text().notNull(),
1573
- count: integer().notNull(),
1574
- updatedAt: integer().notNull()
1575
- }, (t) => [
1576
- index("by_table_index").on(t.tableKey, t.indexName),
1577
- index("by_table_index_hash_field_value").on(t.tableKey, t.indexName, t.keyHash, t.fieldName, t.valueHash),
1578
- index("by_table_index_hash_field_sort").on(t.tableKey, t.indexName, t.keyHash, t.fieldName, t.sortKey)
1579
- ]);
1580
- const countStateTable = convexTable(AGGREGATE_STATE_TABLE, {
1581
- kind: text().notNull(),
1582
- tableKey: text().notNull(),
1583
- indexName: text().notNull(),
1584
- keyDefinitionHash: text().notNull(),
1585
- metricDefinitionHash: text().notNull(),
1586
- status: text().notNull(),
1587
- cursor: text(),
1588
- processed: integer().notNull(),
1589
- startedAt: integer().notNull(),
1590
- updatedAt: integer().notNull(),
1591
- completedAt: integer(),
1592
- lastError: text()
1593
- }, (t) => [
1594
- index("by_kind_table_index").on(t.kind, t.tableKey, t.indexName),
1595
- index("by_kind_status").on(t.kind, t.status),
1596
- index("by_table_status").on(t.tableKey, t.status)
1597
- ]);
1598
- const rankTreeTable = convexTable(AGGREGATE_RANK_TREE_TABLE, {
1599
- aggregateName: text().notNull(),
1600
- maxNodeSize: integer().notNull(),
1601
- namespace: json(),
1602
- root: id(AGGREGATE_RANK_NODE_TABLE).notNull()
1603
- }, (tree) => [index("by_namespace").on(tree.namespace), index("by_aggregate_name").on(tree.aggregateName)]);
1604
- const rankNodeTable = convexTable(AGGREGATE_RANK_NODE_TABLE, {
1605
- aggregate: objectOf({
1606
- count: integer().notNull(),
1607
- sum: integer().notNull()
1608
- }),
1609
- items: arrayOf(objectOf({
1610
- k: json(),
1611
- v: json(),
1612
- s: integer().notNull()
1613
- })).notNull(),
1614
- subtrees: arrayOf(text().notNull()).notNull()
1615
- });
1616
- const aggregateStorageTables = {
1617
- [AGGREGATE_BUCKET_TABLE]: countBucketTable,
1618
- [AGGREGATE_MEMBER_TABLE]: countMemberTable,
1619
- [AGGREGATE_EXTREMA_TABLE]: countExtremaTable,
1620
- [AGGREGATE_RANK_TREE_TABLE]: rankTreeTable,
1621
- [AGGREGATE_RANK_NODE_TABLE]: rankNodeTable,
1622
- [AGGREGATE_STATE_TABLE]: countStateTable
1623
- };
1624
- function aggregateExtension() {
1625
- return defineSchemaExtension("aggregate", aggregateStorageTables);
1626
- }
1627
-
1628
- //#endregion
1629
- //#region src/orm/migrations/schema.ts
1630
- const MIGRATION_STATE_TABLE = "migration_state";
1631
- const MIGRATION_RUN_TABLE = "migration_run";
1632
- const migrationStateTable = convexTable(MIGRATION_STATE_TABLE, {
1633
- migrationId: text().notNull(),
1634
- checksum: text().notNull(),
1635
- applied: boolean$1().notNull(),
1636
- status: text().notNull(),
1637
- direction: text(),
1638
- runId: text(),
1639
- cursor: text(),
1640
- processed: integer().notNull(),
1641
- startedAt: integer(),
1642
- updatedAt: integer().notNull(),
1643
- completedAt: integer(),
1644
- lastError: text(),
1645
- writeMode: text().notNull()
1646
- }, (t) => [index("by_migration_id").on(t.migrationId), index("by_status").on(t.status)]);
1647
- const migrationRunTable = convexTable(MIGRATION_RUN_TABLE, {
1648
- runId: text().notNull(),
1649
- direction: text().notNull(),
1650
- status: text().notNull(),
1651
- dryRun: boolean$1().notNull(),
1652
- allowDrift: boolean$1().notNull(),
1653
- migrationIds: custom(v.array(v.string())).notNull(),
1654
- currentIndex: integer().notNull(),
1655
- startedAt: integer().notNull(),
1656
- updatedAt: integer().notNull(),
1657
- completedAt: integer(),
1658
- cancelRequested: boolean$1().notNull(),
1659
- lastError: text()
1660
- }, (t) => [index("by_run_id").on(t.runId), index("by_status").on(t.status)]);
1661
- const migrationStorageTables = {
1662
- [MIGRATION_STATE_TABLE]: migrationStateTable,
1663
- [MIGRATION_RUN_TABLE]: migrationRunTable
1664
- };
1665
- function migrationExtension() {
1666
- return defineSchemaExtension("migration", migrationStorageTables);
1667
- }
1668
-
1669
- //#endregion
1670
- //#region src/orm/relations.ts
1671
- var RelationsBuilderTable = class {
1672
- static [entityKind] = "RelationsBuilderTable";
1673
- _;
1674
- constructor(table, name) {
1675
- this._ = {
1676
- name,
1677
- table
1678
- };
1679
- }
1680
- };
1681
- var RelationsBuilderColumn = class {
1682
- static [entityKind] = "RelationsBuilderColumn";
1683
- _;
1684
- constructor(column, tableName, key) {
1685
- this._ = {
1686
- tableName,
1687
- column,
1688
- key
1689
- };
1690
- }
1691
- through(column) {
1692
- return new RelationsBuilderJunctionColumn(this._.column, this._.tableName, this._.key, column);
1693
- }
1694
- };
1695
- var RelationsBuilderJunctionColumn = class {
1696
- static [entityKind] = "RelationsBuilderColumn";
1697
- _;
1698
- constructor(column, tableName, key, through) {
1699
- this._ = {
1700
- tableName,
1701
- column,
1702
- through,
1703
- key
1704
- };
1705
- }
1706
- };
1707
- var RelationsHelperStatic = class {
1708
- static [entityKind] = "RelationsHelperStatic";
1709
- constructor(tables) {
1710
- const one = {};
1711
- const many = {};
1712
- for (const [tableName, table] of Object.entries(tables)) {
1713
- one[tableName] = (config) => new One(tables, table, tableName, config);
1714
- many[tableName] = (config) => new Many(tables, table, tableName, config);
1627
+ tableName;
1628
+ constructor(name, columns, polymorphicConfigs) {
1629
+ validateTableName(name);
1630
+ for (const columnName of Object.keys(columns)) if (RESERVED_COLUMN_NAMES.has(columnName)) throw new Error(`Column name '${columnName}' is reserved. System fields are managed by Convex ORM.`);
1631
+ this[TableName] = name;
1632
+ const namedColumns = Object.fromEntries(Object.entries(columns).map(([columnName, builder]) => {
1633
+ builder.config.name = columnName;
1634
+ builder.config.tableName = name;
1635
+ builder.config.table = this;
1636
+ return [columnName, builder];
1637
+ }));
1638
+ this[Columns] = namedColumns;
1639
+ this.tableName = name;
1640
+ if (polymorphicConfigs && polymorphicConfigs.length > 0) this[TablePolymorphic] = polymorphicConfigs;
1641
+ this.validator = createValidatorFromColumns(namedColumns);
1642
+ for (const [columnName, builder] of Object.entries(namedColumns)) {
1643
+ const config = builder.config;
1644
+ if (config?.isUnique) {
1645
+ const indexName = getUniqueIndexName(name, [columnName], config.uniqueName);
1646
+ const nullsNotDistinct = config.uniqueNulls === "not distinct";
1647
+ this.addIndex(indexName, [columnName]);
1648
+ this.addUniqueIndex(indexName, [columnName], nullsNotDistinct);
1649
+ }
1650
+ if (config?.referenceTable && (!config.foreignKeyConfigs || config.foreignKeyConfigs.length === 0)) this.addForeignKey({
1651
+ name: void 0,
1652
+ columns: [columnName],
1653
+ foreignTableName: config.referenceTable,
1654
+ foreignColumns: ["_id"]
1655
+ });
1656
+ if (config?.foreignKeyConfigs?.length) for (const foreignConfig of config.foreignKeyConfigs) this.deferredForeignKeys.push({
1657
+ localColumnName: columnName,
1658
+ ref: foreignConfig.ref,
1659
+ config: foreignConfig.config
1660
+ });
1715
1661
  }
1716
- this.one = one;
1717
- this.many = many;
1718
1662
  }
1719
- one;
1720
- many;
1721
- };
1722
- function createRelationsHelper(tables) {
1723
- const helperStatic = new RelationsHelperStatic(tables);
1724
- const relationsTables = Object.entries(tables).reduce((acc, [tKey, value]) => {
1725
- const rTable = new RelationsBuilderTable(value, tKey);
1726
- const columns = Object.entries(getTableColumns(value)).reduce((colsAcc, [cKey, column]) => {
1727
- colsAcc[cKey] = new RelationsBuilderColumn(column, tKey, cKey);
1728
- return colsAcc;
1729
- }, {});
1730
- const relationsTable = Object.assign(rTable, columns);
1731
- Object.defineProperty(relationsTable, "_id", {
1732
- get() {
1733
- throw new Error("`_id` is no longer public in relations. Use `id`.");
1734
- },
1735
- enumerable: false,
1736
- configurable: false
1737
- });
1738
- acc[tKey] = relationsTable;
1739
- return acc;
1740
- }, {});
1741
- return Object.assign(helperStatic, relationsTables);
1742
- }
1743
- function extractTablesFromSchema(schema) {
1744
- const schemaTables = schema && typeof schema === "object" && !Array.isArray(schema) && "tables" in schema && schema.tables && typeof schema.tables === "object" && !Array.isArray(schema.tables) ? schema.tables : schema;
1745
- return Object.fromEntries(Object.entries(schemaTables).filter(([_, e]) => isConvexTable(e)));
1746
- }
1747
- var Relation = class {
1748
- static [entityKind] = "RelationV2";
1749
- fieldName;
1750
- sourceColumns;
1751
- targetColumns;
1752
- alias;
1753
- where;
1754
- sourceTable;
1755
- targetTable;
1756
- through;
1757
- throughTable;
1758
- isReversed;
1759
- /** @internal */
1760
- sourceColumnTableNames = [];
1761
- /** @internal */
1762
- targetColumnTableNames = [];
1763
- constructor(targetTable, targetTableName) {
1764
- this.targetTableName = targetTableName;
1765
- this.targetTable = targetTable;
1663
+ getPolymorphicConfigs() {
1664
+ return this[TablePolymorphic];
1766
1665
  }
1767
- };
1768
- var One = class extends Relation {
1769
- static [entityKind] = "OneV2";
1770
- relationType = "one";
1771
- optional;
1772
- constructor(tables, targetTable, targetTableName, config) {
1773
- super(targetTable, targetTableName);
1774
- this.alias = config?.alias;
1775
- this.where = config?.where;
1776
- if (config?.from) this.sourceColumns = (Array.isArray(config.from) ? config.from : [config.from]).map((it) => {
1777
- this.throughTable ??= it._.through ? tables[it._.through._.tableName] : void 0;
1778
- this.sourceColumnTableNames.push(it._.tableName);
1779
- return it._.column;
1666
+ /**
1667
+ * Internal: add index to table from builder extraConfig
1668
+ *
1669
+ */
1670
+ addIndex(name, fields) {
1671
+ this.indexes.push({
1672
+ indexDescriptor: name,
1673
+ fields
1780
1674
  });
1781
- if (config?.to) this.targetColumns = (Array.isArray(config.to) ? config.to : [config.to]).map((it) => {
1782
- this.throughTable ??= it._.through ? tables[it._.through._.tableName] : void 0;
1783
- this.targetColumnTableNames.push(it._.tableName);
1784
- return it._.column;
1675
+ }
1676
+ /**
1677
+ * Internal: add unique index metadata for runtime enforcement
1678
+ */
1679
+ addUniqueIndex(name, fields, nullsNotDistinct) {
1680
+ this.uniqueIndexes.push({
1681
+ name,
1682
+ fields,
1683
+ nullsNotDistinct
1785
1684
  });
1786
- if (this.throughTable) this.through = {
1787
- source: (Array.isArray(config?.from) ? config.from : config?.from ? [config.from] : []).map((c) => c._.through),
1788
- target: (Array.isArray(config?.to) ? config.to : config?.to ? [config.to] : []).map((c) => c._.through)
1789
- };
1790
- this.optional = config?.optional ?? true;
1791
1685
  }
1792
- };
1793
- var Many = class extends Relation {
1794
- static [entityKind] = "ManyV2";
1795
- relationType = "many";
1796
- constructor(tables, targetTable, targetTableName, config) {
1797
- super(targetTable, targetTableName);
1798
- this.config = config;
1799
- this.alias = config?.alias;
1800
- this.where = config?.where;
1801
- if (config?.from) this.sourceColumns = (Array.isArray(config.from) ? config.from : [config.from]).map((it) => {
1802
- this.throughTable ??= it._.through ? tables[it._.through._.tableName] : void 0;
1803
- this.sourceColumnTableNames.push(it._.tableName);
1804
- return it._.column;
1686
+ addAggregateIndex(name, config) {
1687
+ if (this.aggregateIndexes.some((index) => index.name === name) || this.rankIndexes.some((index) => index.name === name)) throw new Error(`Duplicate aggregate index '${name}' on '${this.tableName}'.`);
1688
+ this.aggregateIndexes.push({
1689
+ name,
1690
+ fields: config.fields,
1691
+ countFields: config.countFields,
1692
+ sumFields: config.sumFields,
1693
+ avgFields: config.avgFields,
1694
+ minFields: config.minFields,
1695
+ maxFields: config.maxFields
1805
1696
  });
1806
- if (config?.to) this.targetColumns = (Array.isArray(config.to) ? config.to : [config.to]).map((it) => {
1807
- this.throughTable ??= it._.through ? tables[it._.through._.tableName] : void 0;
1808
- this.targetColumnTableNames.push(it._.tableName);
1809
- return it._.column;
1697
+ }
1698
+ addRankIndex(name, config) {
1699
+ if (this.rankIndexes.some((index) => index.name === name) || this.aggregateIndexes.some((index) => index.name === name)) throw new Error(`Duplicate aggregate index '${name}' on '${this.tableName}'.`);
1700
+ this.rankIndexes.push({
1701
+ name,
1702
+ partitionFields: config.partitionFields,
1703
+ orderFields: config.orderFields,
1704
+ sumField: config.sumField
1810
1705
  });
1811
- if (this.throughTable) this.through = {
1812
- source: (Array.isArray(config?.from) ? config.from : config?.from ? [config.from] : []).map((c) => c._.through),
1813
- target: (Array.isArray(config?.to) ? config.to : config?.to ? [config.to] : []).map((c) => c._.through)
1814
- };
1815
1706
  }
1816
- };
1817
- function buildRelations(tables, config, strict, defaults) {
1818
- const tablesConfig = {};
1819
- for (const [tsName, table] of Object.entries(tables)) tablesConfig[tsName] = {
1820
- table,
1821
- name: tsName,
1822
- polymorphic: table[TablePolymorphic],
1823
- relations: config[tsName] ?? {},
1824
- strict,
1825
- defaults
1826
- };
1827
- return processRelations(tablesConfig, tables);
1828
- }
1829
- function defineRelations(schema, relations) {
1830
- const tables = extractTablesFromSchema(schema);
1831
- const schemaOptions = schema[OrmSchemaOptions];
1832
- const strict = schemaOptions?.strict ?? true;
1833
- const defaults = schemaOptions?.defaults;
1834
- const schemaDefinition = schema[OrmSchemaDefinition];
1835
- const pluginTableNames = schema[OrmSchemaExtensionTables];
1836
- const plugins = schema[OrmSchemaExtensions];
1837
- const tablesConfig = buildRelations(tables, relations ? relations(createRelationsHelper(tables)) : {}, strict, defaults);
1838
- Object.defineProperty(tablesConfig, OrmSchemaOptions, {
1839
- value: {
1840
- strict,
1841
- defaults
1842
- },
1843
- enumerable: false
1844
- });
1845
- if (schemaDefinition) Object.defineProperty(tablesConfig, OrmSchemaDefinition, {
1846
- value: schemaDefinition,
1847
- enumerable: false
1848
- });
1849
- if (pluginTableNames) Object.defineProperty(tablesConfig, OrmSchemaExtensionTables, {
1850
- value: pluginTableNames,
1851
- enumerable: false
1852
- });
1853
- if (plugins) Object.defineProperty(tablesConfig, OrmSchemaExtensions, {
1854
- value: plugins,
1855
- enumerable: false
1856
- });
1857
- return tablesConfig;
1858
- }
1859
- function processRelations(tablesConfig, tables) {
1860
- for (const tableConfig of Object.values(tablesConfig)) for (const [relationFieldName, relation] of Object.entries(tableConfig.relations)) {
1861
- if (!isRelation(relation)) continue;
1862
- relation.sourceTable = tableConfig.table;
1863
- relation.fieldName = relationFieldName;
1707
+ getAggregateIndexes() {
1708
+ return [...this.aggregateIndexes];
1709
+ }
1710
+ getRankIndexes() {
1711
+ return [...this.rankIndexes];
1712
+ }
1713
+ /**
1714
+ * Internal: expose unique index metadata for mutation enforcement
1715
+ */
1716
+ getUniqueIndexes() {
1717
+ return this.uniqueIndexes;
1718
+ }
1719
+ /**
1720
+ * Internal: expose index metadata for runtime enforcement
1721
+ */
1722
+ getIndexes() {
1723
+ return this.indexes.map((entry) => ({
1724
+ name: entry.indexDescriptor,
1725
+ fields: entry.fields
1726
+ }));
1727
+ }
1728
+ /**
1729
+ * Internal: expose search index metadata for runtime query execution
1730
+ */
1731
+ getSearchIndexes() {
1732
+ return this.searchIndexes.map((entry) => ({
1733
+ name: entry.indexDescriptor,
1734
+ searchField: entry.searchField,
1735
+ filterFields: entry.filterFields
1736
+ }));
1737
+ }
1738
+ /**
1739
+ * Internal: expose vector index metadata for runtime query execution
1740
+ */
1741
+ getVectorIndexes() {
1742
+ return this.vectorIndexes.map((entry) => ({
1743
+ name: entry.indexDescriptor,
1744
+ vectorField: entry.vectorField,
1745
+ dimensions: entry.dimensions,
1746
+ filterFields: entry.filterFields
1747
+ }));
1748
+ }
1749
+ /**
1750
+ * Internal: attach an RLS policy to this table
1751
+ */
1752
+ addRlsPolicy(policy) {
1753
+ this[RlsPolicies].push(policy);
1754
+ this[EnableRLS] = true;
1755
+ }
1756
+ /**
1757
+ * Internal: return attached RLS policies
1758
+ */
1759
+ getRlsPolicies() {
1760
+ return this[RlsPolicies];
1864
1761
  }
1865
- for (const [sourceTableName, tableConfig] of Object.entries(tablesConfig)) for (const [relationFieldName, relation] of Object.entries(tableConfig.relations)) {
1866
- if (!isRelation(relation)) continue;
1867
- let reverseRelation;
1868
- const { targetTableName, alias, sourceColumns, targetColumns, throughTable, sourceTable, through, where, sourceColumnTableNames, targetColumnTableNames } = relation;
1869
- const relationPrintName = `relations -> ${tableConfig.name}: { ${relationFieldName}: r.${relation.relationType === "one" ? "one" : "many"}.${targetTableName}(...) }`;
1870
- if (relationFieldName in getTableColumns(tableConfig.table)) throw new Error(`${relationPrintName}: relation name collides with column "${relationFieldName}" of table "${tableConfig.name}"`);
1871
- if (typeof alias === "string" && !alias) throw new Error(`${relationPrintName}: "alias" cannot be empty`);
1872
- if (sourceColumns?.length === 0) throw new Error(`${relationPrintName}: "from" cannot be empty`);
1873
- if (targetColumns?.length === 0) throw new Error(`${relationPrintName}: "to" cannot be empty`);
1874
- if (sourceColumns && targetColumns) {
1875
- if (sourceColumns.length !== targetColumns.length && !throughTable) throw new Error(`${relationPrintName}: "from" and "to" must have same length`);
1876
- for (const sName of sourceColumnTableNames) if (sName !== sourceTableName) throw new Error(`${relationPrintName}: all "from" columns must belong to table "${sourceTableName}", found "${sName}"`);
1877
- for (const tName of targetColumnTableNames) if (tName !== targetTableName) throw new Error(`${relationPrintName}: all "to" columns must belong to table "${targetTableName}", found "${tName}"`);
1878
- if (through) {
1879
- if (through.source.length !== sourceColumns.length || through.target.length !== targetColumns.length) throw new Error(`${relationPrintName}: .through() must be used on all columns in "from" and "to" or none`);
1880
- for (const column of through.source) if (tables[column._.tableName] !== throughTable) throw new Error(`${relationPrintName}: .through() must use same table for all columns`);
1881
- for (const column of through.target) if (tables[column._.tableName] !== throughTable) throw new Error(`${relationPrintName}: .through() must use same table for all columns`);
1762
+ /**
1763
+ * Internal: check if RLS is enabled on this table
1764
+ */
1765
+ isRlsEnabled() {
1766
+ return this[EnableRLS];
1767
+ }
1768
+ /**
1769
+ * Internal: add foreign key metadata for runtime enforcement
1770
+ */
1771
+ addForeignKey(definition) {
1772
+ const matches = (existing) => {
1773
+ if (existing.foreignTableName !== definition.foreignTableName) return false;
1774
+ if (existing.columns.length !== definition.columns.length) return false;
1775
+ if (existing.foreignColumns.length !== definition.foreignColumns.length) return false;
1776
+ for (let i = 0; i < existing.columns.length; i++) if (existing.columns[i] !== definition.columns[i]) return false;
1777
+ for (let i = 0; i < existing.foreignColumns.length; i++) if (existing.foreignColumns[i] !== definition.foreignColumns[i]) return false;
1778
+ return true;
1779
+ };
1780
+ this.foreignKeys = this.foreignKeys.filter((existing) => !matches(existing));
1781
+ this.foreignKeys.push(definition);
1782
+ }
1783
+ resolveDeferredForeignKeys() {
1784
+ if (this.deferredForeignKeysResolved) return;
1785
+ this.deferredForeignKeysResolved = true;
1786
+ for (const deferred of this.deferredForeignKeys) {
1787
+ let foreignColumn;
1788
+ try {
1789
+ foreignColumn = deferred.ref();
1790
+ } catch (error) {
1791
+ const reason = error instanceof Error ? ` ${error.message}` : "";
1792
+ throw new Error(`Failed to resolve foreign key reference for '${this.tableName}.${deferred.localColumnName}'. Use references(() => targetTable.column) after both tables are declared.${reason}`);
1882
1793
  }
1883
- continue;
1884
- }
1885
- if (sourceColumns || targetColumns) throw new Error(`${relationPrintName}: relation must have both "from" and "to" or none`);
1886
- const reverseTableConfig = tablesConfig[targetTableName];
1887
- if (!reverseTableConfig) throw new Error(`${relationPrintName}: missing "from"/"to" and no reverse relation found for "${targetTableName}"`);
1888
- if (alias) {
1889
- const reverseRelations = Object.values(reverseTableConfig.relations).filter((it) => isRelation(it) && it.alias === alias && it !== relation);
1890
- if (reverseRelations.length > 1) throw new Error(`${relationPrintName}: multiple reverse relations with alias "${alias}" found`);
1891
- reverseRelation = reverseRelations[0];
1892
- if (!reverseRelation) throw new Error(`${relationPrintName}: no reverse relation with alias "${alias}" found in "${targetTableName}"`);
1893
- } else {
1894
- const reverseRelations = Object.values(reverseTableConfig.relations).filter((it) => isRelation(it) && it.targetTable === sourceTable && !it.alias && it !== relation);
1895
- if (reverseRelations.length > 1) throw new Error(`${relationPrintName}: multiple relations between "${targetTableName}" and "${sourceTableName}"; use alias`);
1896
- reverseRelation = reverseRelations[0];
1897
- if (!reverseRelation) throw new Error(`${relationPrintName}: no reverse relation between "${targetTableName}" and "${sourceTableName}"`);
1794
+ const foreignTableName = getColumnTableName(foreignColumn);
1795
+ if (!foreignTableName) throw new Error(`Foreign key on '${this.tableName}.${deferred.localColumnName}' references a column without a table. Use references(() => targetTable.column).`);
1796
+ const foreignTable = getColumnTable(foreignColumn);
1797
+ if (!foreignTable) throw new Error(`Foreign key on '${this.tableName}.${deferred.localColumnName}' references a column without table metadata. Replace references(() => id('tableName')) with references(() => table.id).`);
1798
+ const foreignColumnName = getColumnName(foreignColumn);
1799
+ this.addForeignKey({
1800
+ name: deferred.config.name,
1801
+ columns: [deferred.localColumnName],
1802
+ foreignTableName,
1803
+ foreignTable,
1804
+ foreignColumns: [foreignColumnName],
1805
+ onDelete: deferred.config.onDelete,
1806
+ onUpdate: deferred.config.onUpdate
1807
+ });
1898
1808
  }
1899
- if (!reverseRelation.sourceColumns || !reverseRelation.targetColumns) throw new Error(`${relationPrintName}: reverse relation "${targetTableName}.${reverseRelation.fieldName}" missing "from"/"to"`);
1900
- relation.sourceColumns = reverseRelation.targetColumns;
1901
- relation.targetColumns = reverseRelation.sourceColumns;
1902
- relation.through = reverseRelation.through ? {
1903
- source: reverseRelation.through.target,
1904
- target: reverseRelation.through.source
1905
- } : void 0;
1906
- relation.throughTable = reverseRelation.throughTable;
1907
- relation.isReversed = !where;
1908
- relation.where = where ?? reverseRelation.where;
1809
+ this.deferredForeignKeys = [];
1909
1810
  }
1910
- return tablesConfig;
1911
- }
1912
- function isConvexTable(value) {
1913
- return typeof value === "object" && value !== null && "tableName" in value && Columns in value;
1914
- }
1915
- function isRelation(value) {
1916
- return value instanceof Relation;
1917
- }
1918
- function getTableColumns(table) {
1919
- const columns = table[Columns];
1920
- const system = {};
1921
- if (table.id) system.id = table.id;
1922
- if (table.createdAt || table._creationTime) system.createdAt = table._creationTime ?? table.createdAt;
1923
- return {
1924
- ...columns,
1925
- ...system
1926
- };
1927
- }
1928
-
1929
- //#endregion
1930
- //#region src/orm/triggers.ts
1931
- function defineTriggers(schema, triggers) {
1932
- return triggers;
1933
- }
1934
-
1935
- //#endregion
1936
- //#region src/orm/schema.ts
1937
- const BUILTIN_SCHEMA_EXTENSIONS = [aggregateExtension(), migrationExtension()];
1938
- function resolveSchemaExtensions(extensions) {
1939
- const resolved = [...BUILTIN_SCHEMA_EXTENSIONS, ...extensions ?? []];
1940
- const seen = /* @__PURE__ */ new Set();
1941
- for (const extension of resolved) {
1942
- if (seen.has(extension.key)) throw new Error(`defineSchema received duplicate extension '${extension.key}'. Remove duplicate extension registrations.`);
1943
- seen.add(extension.key);
1811
+ /**
1812
+ * Internal: expose foreign key metadata for mutation enforcement
1813
+ */
1814
+ getForeignKeys() {
1815
+ this.resolveDeferredForeignKeys();
1816
+ return this.foreignKeys;
1944
1817
  }
1945
- return resolved;
1946
- }
1947
- function mergeRelationConfigs(sources) {
1948
- const merged = {};
1949
- const relationOrigins = /* @__PURE__ */ new Map();
1950
- for (const { source, config } of sources) for (const [tableName, relationConfig] of Object.entries(config)) {
1951
- if (!relationConfig) continue;
1952
- let tableRelations = merged[tableName];
1953
- if (!tableRelations) {
1954
- tableRelations = Object.create(null);
1955
- merged[tableName] = tableRelations;
1956
- }
1957
- for (const [fieldName, relation] of Object.entries(relationConfig)) {
1958
- const relationKey = `${tableName}.${fieldName}`;
1959
- const existingSource = relationOrigins.get(relationKey);
1960
- if (existingSource) throw new Error(`defineSchema relation field '${relationKey}' is defined more than once (${existingSource} and ${source}).`);
1961
- tableRelations[fieldName] = relation;
1962
- relationOrigins.set(relationKey, source);
1963
- }
1818
+ addCheck(name, expression) {
1819
+ this.checks.push({
1820
+ name,
1821
+ expression
1822
+ });
1964
1823
  }
1965
- return merged;
1966
- }
1967
- function mergeTriggerConfigs(sources) {
1968
- const merged = {};
1969
- const triggerOrigins = /* @__PURE__ */ new Map();
1970
- for (const { source, config } of sources) for (const [tableName, tableConfig] of Object.entries(config)) {
1971
- if (!tableConfig || typeof tableConfig !== "object" || Array.isArray(tableConfig)) {
1972
- const triggerKey = `${tableName}`;
1973
- const existingSource = triggerOrigins.get(triggerKey);
1974
- if (existingSource) throw new Error(`defineSchema trigger '${triggerKey}' is defined more than once (${existingSource} and ${source}).`);
1975
- merged[tableName] = tableConfig;
1976
- triggerOrigins.set(triggerKey, source);
1977
- continue;
1978
- }
1979
- let mergedTable = merged[tableName];
1980
- if (!mergedTable) {
1981
- mergedTable = Object.create(null);
1982
- merged[tableName] = mergedTable;
1983
- }
1984
- for (const [hookKey, hookValue] of Object.entries(tableConfig)) {
1985
- if ((hookKey === "create" || hookKey === "update" || hookKey === "delete") && hookValue && typeof hookValue === "object" && !Array.isArray(hookValue)) {
1986
- let mergedOperation = mergedTable[hookKey];
1987
- if (!mergedOperation || typeof mergedOperation !== "object") {
1988
- mergedOperation = Object.create(null);
1989
- mergedTable[hookKey] = mergedOperation;
1990
- }
1991
- for (const [operationHookKey, operationHookValue] of Object.entries(hookValue)) {
1992
- const triggerKey = `${tableName}.${hookKey}.${operationHookKey}`;
1993
- const existingSource = triggerOrigins.get(triggerKey);
1994
- if (existingSource) throw new Error(`defineSchema trigger '${triggerKey}' is defined more than once (${existingSource} and ${source}).`);
1995
- mergedOperation[operationHookKey] = operationHookValue;
1996
- triggerOrigins.set(triggerKey, source);
1997
- }
1998
- continue;
1999
- }
2000
- const triggerKey = `${tableName}.${hookKey}`;
2001
- const existingSource = triggerOrigins.get(triggerKey);
2002
- if (existingSource) throw new Error(`defineSchema trigger '${triggerKey}' is defined more than once (${existingSource} and ${source}).`);
2003
- mergedTable[hookKey] = hookValue;
2004
- triggerOrigins.set(triggerKey, source);
2005
- }
1824
+ getChecks() {
1825
+ return this.checks;
2006
1826
  }
2007
- return merged;
2008
- }
2009
- function defineMetadata(target, key, value) {
2010
- const existing = Object.getOwnPropertyDescriptor(target, key);
2011
- if (existing && !existing.configurable) return;
2012
- Object.defineProperty(target, key, {
2013
- value,
2014
- enumerable: false,
2015
- configurable: true
2016
- });
2017
- }
2018
- const OrmSchemaComposerState = Symbol("kitcn:OrmSchemaComposerState");
2019
- function getSchemaComposerState(schema) {
2020
- if (!schema || typeof schema !== "object") return;
2021
- return schema[OrmSchemaComposerState];
2022
- }
2023
- function finalizeSchemaMetadata(schema) {
2024
- if (!schema || typeof schema !== "object") return;
2025
- const composerState = getSchemaComposerState(schema);
2026
- if (!composerState) return;
2027
- const schemaObject = schema;
2028
- const resolvedExtensions = schemaObject[OrmSchemaExtensions] ?? resolveSchemaExtensions(composerState.extensions);
2029
- const extensionTableNames = schemaObject[OrmSchemaExtensionTables] ?? Object.freeze([]);
2030
- const options = schemaObject[OrmSchemaOptions];
2031
- const extensionRelationFactories = resolvedExtensions.map((extension) => ({
2032
- key: extension.key,
2033
- relations: extension[OrmSchemaExtensionRelations]
2034
- })).filter((entry) => entry.relations !== void 0);
2035
- const extensionTriggerFactories = resolvedExtensions.map((extension) => ({
2036
- key: extension.key,
2037
- triggers: extension[OrmSchemaExtensionTriggers]
2038
- })).filter((entry) => entry.triggers !== void 0);
2039
- const hasExtensionRelations = extensionRelationFactories.length > 0;
2040
- const hasExtensionTriggers = extensionTriggerFactories.length > 0;
2041
- const shouldBuildRelations = hasExtensionRelations || hasExtensionTriggers || Boolean(composerState.relations) || Boolean(composerState.triggers);
2042
- let relations = schemaObject[OrmSchemaRelations];
2043
- if (!relations && shouldBuildRelations) {
2044
- relations = hasExtensionRelations || composerState.relations ? defineRelations(schema, (helpers) => mergeRelationConfigs([...extensionRelationFactories.map((extension) => ({
2045
- source: `extension '${extension.key}'`,
2046
- config: extension.relations(helpers)
2047
- })), ...composerState.relations ? [{
2048
- source: "schema.relations()",
2049
- config: composerState.relations(helpers)
2050
- }] : []])) : defineRelations(schema);
2051
- defineMetadata(relations, OrmSchemaDefinition, schema);
2052
- if (options) defineMetadata(relations, OrmSchemaOptions, options);
2053
- defineMetadata(relations, OrmSchemaExtensionTables, extensionTableNames);
2054
- defineMetadata(relations, OrmSchemaExtensions, resolvedExtensions);
2055
- defineMetadata(schema, OrmSchemaRelations, relations);
1827
+ /**
1828
+ * Internal: add search index to table from builder extraConfig
1829
+ */
1830
+ addSearchIndex(name, config) {
1831
+ const entry = {
1832
+ indexDescriptor: name,
1833
+ searchField: config.searchField,
1834
+ filterFields: config.filterFields ?? []
1835
+ };
1836
+ if (config.staged) this.stagedSearchIndexes.push(entry);
1837
+ else this.searchIndexes.push(entry);
2056
1838
  }
2057
- if (!relations || schemaObject[OrmSchemaTriggers]) return;
2058
- const triggerSources = hasExtensionTriggers || composerState.triggers ? [...extensionTriggerFactories.map((extension) => ({
2059
- source: `extension '${extension.key}'`,
2060
- config: typeof extension.triggers === "function" ? extension.triggers(relations) : extension.triggers
2061
- })), ...composerState.triggers ? [{
2062
- source: "schema.triggers()",
2063
- config: composerState.triggers
2064
- }] : []] : [];
2065
- if (triggerSources.length === 0) return;
2066
- defineMetadata(schema, OrmSchemaTriggers, defineTriggers(relations, mergeTriggerConfigs(triggerSources)));
2067
- }
2068
- function getSchemaRelations(schema) {
2069
- if (!schema || typeof schema !== "object") return;
2070
- finalizeSchemaMetadata(schema);
2071
- return schema[OrmSchemaRelations];
2072
- }
2073
- function getSchemaTriggers(schema) {
2074
- if (!schema || typeof schema !== "object") return;
2075
- finalizeSchemaMetadata(schema);
2076
- return schema[OrmSchemaTriggers];
2077
- }
2078
-
2079
- //#endregion
2080
- //#region src/cli/utils/highlighter.ts
2081
- const isTruthyEnvFlag = (value) => value !== void 0 && value !== "" && value !== "0";
2082
- const isColorEnabled = () => {
2083
- if (process.env.FORCE_COLOR === "0") return false;
2084
- if (isTruthyEnvFlag(process.env.FORCE_COLOR)) return true;
2085
- if (isTruthyEnvFlag(process.env.NO_COLOR)) return false;
2086
- return Boolean(process.stdout.isTTY && process.env.TERM !== "dumb");
2087
- };
2088
- const withColors = (callback) => callback(createColors(isColorEnabled()));
2089
- const highlighter = {
2090
- bold(value) {
2091
- return withColors((colors) => colors.bold(value));
2092
- },
2093
- dim(value) {
2094
- return withColors((colors) => colors.dim(value));
2095
- },
2096
- info(value) {
2097
- return withColors((colors) => colors.cyan(value));
2098
- },
2099
- success(value) {
2100
- return withColors((colors) => colors.green(value));
2101
- },
2102
- warn(value) {
2103
- return withColors((colors) => colors.yellow(value));
2104
- },
2105
- error(value) {
2106
- return withColors((colors) => colors.red(value));
2107
- },
2108
- path(value) {
2109
- return withColors((colors) => colors.bold(value));
1839
+ /**
1840
+ * Internal: add vector index to table from builder extraConfig
1841
+ */
1842
+ addVectorIndex(name, config) {
1843
+ const entry = {
1844
+ indexDescriptor: name,
1845
+ vectorField: config.vectorField,
1846
+ dimensions: config.dimensions,
1847
+ filterFields: config.filterFields ?? []
1848
+ };
1849
+ if (config.staged) this.stagedVectorIndexes.push(entry);
1850
+ else this.vectorIndexes.push(entry);
1851
+ }
1852
+ /**
1853
+ * Export the contents of this definition for Convex schema tooling.
1854
+ * Mirrors convex/server TableDefinition.export().
1855
+ */
1856
+ export() {
1857
+ const documentType = this.validator.json;
1858
+ if (typeof documentType !== "object") throw new Error("Invalid validator: please make sure that the parameter of `defineTable` is valid (see https://docs.convex.dev/database/schemas)");
1859
+ return {
1860
+ indexes: this.indexes,
1861
+ stagedDbIndexes: this.stagedDbIndexes,
1862
+ searchIndexes: this.searchIndexes,
1863
+ stagedSearchIndexes: this.stagedSearchIndexes,
1864
+ vectorIndexes: this.vectorIndexes,
1865
+ stagedVectorIndexes: this.stagedVectorIndexes,
1866
+ documentType
1867
+ };
2110
1868
  }
2111
1869
  };
2112
-
2113
- //#endregion
2114
- //#region src/cli/utils/lazy-deps.ts
2115
- /**
2116
- * Heavy CLI-only dependencies, resolved on first use instead of at module init.
2117
- *
2118
- * `kitcn --version`, `kitcn --help` and every `--json` call an agent makes must
2119
- * not pay to boot esbuild's native service, Babel's parser, jiti, dotenv or the
2120
- * interactive prompt stack — the CLI is non-interactive by default and most
2121
- * commands touch none of them.
2122
- *
2123
- * Each accessor is memoized and stays synchronous so call sites keep their
2124
- * current signatures, and each dependency is loaded through exactly one
2125
- * mechanism: `@clack/prompts` compares against a module-private `Symbol` in
2126
- * `isCancel`, so a second copy of the module would silently break cancellation.
2127
- */
2128
- const require$1 = createRequire(import.meta.url);
2129
- let babelParser;
2130
- let clackPrompts;
2131
- let dotenv;
2132
- let esbuild;
2133
- let jiti;
2134
- const loadBabelParser = () => babelParser ??= require$1("@babel/parser");
2135
- const loadClackPrompts = () => clackPrompts ??= require$1("@clack/prompts");
2136
- const loadDotenv = () => dotenv ??= require$1("dotenv");
2137
- const loadEsbuild = () => esbuild ??= require$1("esbuild");
2138
- const loadJiti = () => jiti ??= require$1("jiti");
2139
-
2140
- //#endregion
2141
- //#region src/cli/utils/crpc-builder-stub.ts
2142
- const CRPC_BUILDER_STUB_SOURCE = `const createMiddleware = (handler = undefined) => ({
2143
- _handler: handler,
2144
- pipe(nextHandler = undefined) {
2145
- return createMiddleware(nextHandler);
2146
- },
2147
- });
2148
-
2149
- const toMetaObject = (value = undefined) =>
2150
- value && typeof value === "object" ? value : {};
2151
-
2152
- const createProcedureExport = (type, state, handler) => ({
2153
- _crpcMeta: {
2154
- type,
2155
- internal: state.internal ?? false,
2156
- ...toMetaObject(state.meta),
2157
- },
2158
- ...(state.httpRoute ? { _crpcHttpRoute: state.httpRoute } : {}),
2159
- _handler: handler,
2160
- });
2161
-
2162
- const createProcedureBuilder = (state = {}) => {
2163
- const builder = {
2164
- internal() {
2165
- return createProcedureBuilder({ ...state, internal: true });
2166
- },
2167
- use() {
2168
- return createProcedureBuilder(state);
2169
- },
2170
- meta(value = undefined) {
2171
- return createProcedureBuilder({
2172
- ...state,
2173
- meta: {
2174
- ...toMetaObject(state.meta),
2175
- ...toMetaObject(value),
2176
- },
2177
- });
2178
- },
2179
- input() {
2180
- return createProcedureBuilder(state);
2181
- },
2182
- params() {
2183
- return createProcedureBuilder(state);
2184
- },
2185
- searchParams() {
2186
- return createProcedureBuilder(state);
2187
- },
2188
- paginated(options = undefined) {
2189
- return createProcedureBuilder({
2190
- ...state,
2191
- meta:
2192
- typeof options?.limit === "number"
2193
- ? {
2194
- ...toMetaObject(state.meta),
2195
- limit: options.limit,
2196
- }
2197
- : state.meta,
2198
- });
2199
- },
2200
- output() {
2201
- return createProcedureBuilder(state);
2202
- },
2203
- form() {
2204
- return createProcedureBuilder(state);
2205
- },
2206
- route(path, method) {
2207
- return createProcedureBuilder({
2208
- ...state,
2209
- httpRoute:
2210
- typeof path === "string" && typeof method === "string"
2211
- ? { path, method: method.toUpperCase() }
2212
- : undefined,
2213
- });
2214
- },
2215
- get(path) {
2216
- return builder.route(path, "GET");
2217
- },
2218
- post(path) {
2219
- return builder.route(path, "POST");
2220
- },
2221
- put(path) {
2222
- return builder.route(path, "PUT");
2223
- },
2224
- patch(path) {
2225
- return builder.route(path, "PATCH");
2226
- },
2227
- delete(path) {
2228
- return builder.route(path, "DELETE");
2229
- },
2230
- query(handler = undefined) {
2231
- return createProcedureExport("query", state, handler);
2232
- },
2233
- mutation(handler = undefined) {
2234
- return createProcedureExport("mutation", state, handler);
2235
- },
2236
- action(handler = undefined) {
2237
- return createProcedureExport("action", state, handler);
2238
- },
2239
- middleware(handler = undefined) {
2240
- return createMiddleware(handler);
2241
- },
2242
- };
2243
-
2244
- return builder;
1870
+ const convexTableInternal = (name, columns, extraConfig) => {
1871
+ const expanded = resolveTableColumns(name, columns);
1872
+ const rawTable = new ConvexTableImpl(name, expanded.columns, expanded.polymorphicConfigs);
1873
+ const systemFields = createSystemFields(name);
1874
+ for (const builder of Object.values(systemFields)) builder.config.table = rawTable;
1875
+ const table = Object.assign(rawTable, systemFields, rawTable[Columns]);
1876
+ const internalCreationTime = systemFields._creationTime;
1877
+ if (Object.hasOwn(table, "_creationTime")) table._creationTime = void 0;
1878
+ Object.defineProperty(table, "_creationTime", {
1879
+ value: internalCreationTime,
1880
+ enumerable: false,
1881
+ configurable: true,
1882
+ writable: false
1883
+ });
1884
+ Object.defineProperty(table, "_id", {
1885
+ value: systemFields.id,
1886
+ enumerable: false,
1887
+ configurable: true,
1888
+ writable: false
1889
+ });
1890
+ applyExtraConfig(rawTable, extraConfig?.(table));
1891
+ return table;
2245
1892
  };
2246
-
2247
- const flattenRouterRecord = (record = {}, prefix = "") => {
2248
- const procedures = {};
2249
- for (const [key, value] of Object.entries(record)) {
2250
- const procedurePath = prefix ? prefix + "." + key : key;
2251
- if (value?._def?.router === true) {
2252
- Object.assign(
2253
- procedures,
2254
- flattenRouterRecord(value._def.record ?? {}, procedurePath)
2255
- );
2256
- continue;
2257
- }
2258
- procedures[procedurePath] = value;
2259
- }
2260
- return procedures;
1893
+ const convexTableWithRLS = (name, columns, extraConfig) => {
1894
+ const table = convexTableInternal(name, columns, extraConfig);
1895
+ table[EnableRLS] = true;
1896
+ return table;
2261
1897
  };
1898
+ const convexTable = Object.assign(convexTableInternal, { withRLS: convexTableWithRLS });
2262
1899
 
2263
- const createRouter = (record = {}) => ({
2264
- _def: {
2265
- router: true,
2266
- procedures: flattenRouterRecord(record),
2267
- record,
2268
- },
1900
+ //#endregion
1901
+ //#region src/orm/aggregate-index/schema.ts
1902
+ const AGGREGATE_BUCKET_TABLE = "aggregate_bucket";
1903
+ const AGGREGATE_MEMBER_TABLE = "aggregate_member";
1904
+ const AGGREGATE_EXTREMA_TABLE = "aggregate_extrema";
1905
+ const AGGREGATE_RANK_TREE_TABLE = "aggregate_rank_tree";
1906
+ const AGGREGATE_RANK_NODE_TABLE = "aggregate_rank_node";
1907
+ const AGGREGATE_STATE_TABLE = "aggregate_state";
1908
+ const countBucketTable = convexTable(AGGREGATE_BUCKET_TABLE, {
1909
+ tableKey: text().notNull(),
1910
+ indexName: text().notNull(),
1911
+ keyHash: text().notNull(),
1912
+ keyParts: arrayOf(json()).notNull(),
1913
+ count: integer().notNull(),
1914
+ sumValues: objectOf(integer().notNull()).notNull(),
1915
+ nonNullCountValues: objectOf(integer().notNull()).notNull(),
1916
+ updatedAt: integer().notNull()
1917
+ }, (t) => [index("by_table_index_hash").on(t.tableKey, t.indexName, t.keyHash), index("by_table_index").on(t.tableKey, t.indexName)]);
1918
+ const countMemberTable = convexTable(AGGREGATE_MEMBER_TABLE, {
1919
+ kind: text().notNull(),
1920
+ tableKey: text().notNull(),
1921
+ indexName: text().notNull(),
1922
+ docId: text().notNull(),
1923
+ keyHash: text().notNull(),
1924
+ keyParts: arrayOf(json()).notNull(),
1925
+ sumValues: objectOf(integer().notNull()).notNull(),
1926
+ nonNullCountValues: objectOf(integer().notNull()).notNull(),
1927
+ extremaValues: objectOf(json()).notNull(),
1928
+ rankNamespace: json(),
1929
+ rankKey: json(),
1930
+ rankSumValue: integer(),
1931
+ updatedAt: integer().notNull()
1932
+ }, (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)]);
1933
+ const countExtremaTable = convexTable(AGGREGATE_EXTREMA_TABLE, {
1934
+ tableKey: text().notNull(),
1935
+ indexName: text().notNull(),
1936
+ keyHash: text().notNull(),
1937
+ fieldName: text().notNull(),
1938
+ valueHash: text().notNull(),
1939
+ value: json().notNull(),
1940
+ sortKey: text().notNull(),
1941
+ count: integer().notNull(),
1942
+ updatedAt: integer().notNull()
1943
+ }, (t) => [
1944
+ index("by_table_index").on(t.tableKey, t.indexName),
1945
+ index("by_table_index_hash_field_value").on(t.tableKey, t.indexName, t.keyHash, t.fieldName, t.valueHash),
1946
+ index("by_table_index_hash_field_sort").on(t.tableKey, t.indexName, t.keyHash, t.fieldName, t.sortKey)
1947
+ ]);
1948
+ const countStateTable = convexTable(AGGREGATE_STATE_TABLE, {
1949
+ kind: text().notNull(),
1950
+ tableKey: text().notNull(),
1951
+ indexName: text().notNull(),
1952
+ keyDefinitionHash: text().notNull(),
1953
+ metricDefinitionHash: text().notNull(),
1954
+ status: text().notNull(),
1955
+ cursor: text(),
1956
+ processed: integer().notNull(),
1957
+ startedAt: integer().notNull(),
1958
+ updatedAt: integer().notNull(),
1959
+ completedAt: integer(),
1960
+ lastError: text()
1961
+ }, (t) => [
1962
+ index("by_kind_table_index").on(t.kind, t.tableKey, t.indexName),
1963
+ index("by_kind_status").on(t.kind, t.status),
1964
+ index("by_table_status").on(t.tableKey, t.status)
1965
+ ]);
1966
+ const rankTreeTable = convexTable(AGGREGATE_RANK_TREE_TABLE, {
1967
+ aggregateName: text().notNull(),
1968
+ maxNodeSize: integer().notNull(),
1969
+ namespace: json(),
1970
+ root: id(AGGREGATE_RANK_NODE_TABLE).notNull()
1971
+ }, (tree) => [index("by_namespace").on(tree.namespace), index("by_aggregate_name").on(tree.aggregateName)]);
1972
+ const rankNodeTable = convexTable(AGGREGATE_RANK_NODE_TABLE, {
1973
+ aggregate: objectOf({
1974
+ count: integer().notNull(),
1975
+ sum: integer().notNull()
1976
+ }),
1977
+ items: arrayOf(objectOf({
1978
+ k: json(),
1979
+ v: json(),
1980
+ s: integer().notNull()
1981
+ })).notNull(),
1982
+ subtrees: arrayOf(text().notNull()).notNull()
2269
1983
  });
2270
-
2271
- export const initCRPC = {
2272
- meta() {
2273
- return this;
2274
- },
2275
- dataModel() {
2276
- return this;
2277
- },
2278
- context() {
2279
- return this;
2280
- },
2281
- middleware(handler = undefined) {
2282
- return createMiddleware(handler);
2283
- },
2284
- create() {
2285
- return {
2286
- query: createProcedureBuilder(),
2287
- mutation: createProcedureBuilder(),
2288
- action: createProcedureBuilder(),
2289
- httpAction: createProcedureBuilder(),
2290
- middleware: createMiddleware,
2291
- router: createRouter,
2292
- };
2293
- },
1984
+ const aggregateStorageTables = {
1985
+ [AGGREGATE_BUCKET_TABLE]: countBucketTable,
1986
+ [AGGREGATE_MEMBER_TABLE]: countMemberTable,
1987
+ [AGGREGATE_EXTREMA_TABLE]: countExtremaTable,
1988
+ [AGGREGATE_RANK_TREE_TABLE]: rankTreeTable,
1989
+ [AGGREGATE_RANK_NODE_TABLE]: rankNodeTable,
1990
+ [AGGREGATE_STATE_TABLE]: countStateTable
2294
1991
  };
2295
-
2296
- export const httpAction = createProcedureBuilder();
2297
- `;
1992
+ function aggregateExtension() {
1993
+ return defineSchemaExtension("aggregate", aggregateStorageTables);
1994
+ }
2298
1995
 
2299
1996
  //#endregion
2300
- //#region src/cli/utils/project-jiti.ts
2301
- const require = createRequire(import.meta.url);
2302
- const JITI_EXPORT_CONDITION_PRIORITY = [
2303
- "bun",
2304
- "import",
2305
- "module",
2306
- "default",
2307
- "require"
2308
- ];
2309
- const SERVER_PARSER_SHIM_SOURCE = `${CRPC_BUILDER_STUB_SOURCE}
2310
-
2311
- export class CRPCError extends Error {
2312
- constructor(options = {}) {
2313
- super(options.message ?? options.code ?? "CRPC error");
2314
- this.code = options.code;
2315
- }
1997
+ //#region src/orm/migrations/schema.ts
1998
+ const MIGRATION_STATE_TABLE = "migration_state";
1999
+ const MIGRATION_RUN_TABLE = "migration_run";
2000
+ const migrationStateTable = convexTable(MIGRATION_STATE_TABLE, {
2001
+ migrationId: text().notNull(),
2002
+ checksum: text().notNull(),
2003
+ applied: boolean$1().notNull(),
2004
+ status: text().notNull(),
2005
+ direction: text(),
2006
+ runId: text(),
2007
+ cursor: text(),
2008
+ processed: integer().notNull(),
2009
+ startedAt: integer(),
2010
+ updatedAt: integer().notNull(),
2011
+ completedAt: integer(),
2012
+ lastError: text(),
2013
+ writeMode: text().notNull()
2014
+ }, (t) => [index("by_migration_id").on(t.migrationId), index("by_status").on(t.status)]);
2015
+ const migrationRunTable = convexTable(MIGRATION_RUN_TABLE, {
2016
+ runId: text().notNull(),
2017
+ direction: text().notNull(),
2018
+ status: text().notNull(),
2019
+ dryRun: boolean$1().notNull(),
2020
+ allowDrift: boolean$1().notNull(),
2021
+ migrationIds: custom(v.array(v.string())).notNull(),
2022
+ currentIndex: integer().notNull(),
2023
+ startedAt: integer().notNull(),
2024
+ updatedAt: integer().notNull(),
2025
+ completedAt: integer(),
2026
+ cancelRequested: boolean$1().notNull(),
2027
+ lastError: text()
2028
+ }, (t) => [index("by_run_id").on(t.runId), index("by_status").on(t.status)]);
2029
+ const migrationStorageTables = {
2030
+ [MIGRATION_STATE_TABLE]: migrationStateTable,
2031
+ [MIGRATION_RUN_TABLE]: migrationRunTable
2032
+ };
2033
+ function migrationExtension() {
2034
+ return defineSchemaExtension("migration", migrationStorageTables);
2316
2035
  }
2317
2036
 
2318
- export const createEnv = ({ schema }) => () =>
2319
- typeof schema?.parse === "function" ? schema.parse(process.env) : process.env;
2320
- export const createHttpRouter = (_app, httpRouter) => httpRouter ?? {};
2321
- export const createCallerFactory = () => () => ({});
2322
- export const createApiLeaf = (fnOrRoot, pathOrMeta, maybeMeta) => {
2323
- const meta = maybeMeta ?? pathOrMeta;
2324
- const fn = Array.isArray(pathOrMeta)
2325
- ? pathOrMeta.reduce((current, segment) => current?.[segment], fnOrRoot)
2326
- : fnOrRoot;
2327
- return Object.assign(fn ?? {}, meta ?? {}, { functionRef: fn });
2037
+ //#endregion
2038
+ //#region src/orm/relations.ts
2039
+ var RelationsBuilderTable = class {
2040
+ static [entityKind] = "RelationsBuilderTable";
2041
+ _;
2042
+ constructor(table, name) {
2043
+ this._ = {
2044
+ name,
2045
+ table
2046
+ };
2047
+ }
2328
2048
  };
2329
- export const createGeneratedFunctionReference = (name) => ({
2330
- [Symbol.for("functionName")]: name,
2331
- });
2332
- export const registerProcedureNameLookup = () => {};
2333
- export const typedProcedureResolver = (_functionRef, resolver) => resolver;
2334
- export const createGeneratedRegistryRuntime = () => ({
2335
- getCallerFactory() {
2336
- return () => ({});
2337
- },
2338
- getHandlerFactory() {
2339
- return () => ({});
2340
- },
2341
- });
2342
- `;
2343
- const resolveJitiExportTarget = (target) => {
2344
- if (typeof target === "string") return target;
2345
- if (Array.isArray(target)) {
2346
- for (const entry of target) {
2347
- const resolved = resolveJitiExportTarget(entry);
2348
- if (resolved) return resolved;
2049
+ var RelationsBuilderColumn = class {
2050
+ static [entityKind] = "RelationsBuilderColumn";
2051
+ _;
2052
+ constructor(column, tableName, key) {
2053
+ this._ = {
2054
+ tableName,
2055
+ column,
2056
+ key
2057
+ };
2058
+ }
2059
+ through(column) {
2060
+ return new RelationsBuilderJunctionColumn(this._.column, this._.tableName, this._.key, column);
2061
+ }
2062
+ };
2063
+ var RelationsBuilderJunctionColumn = class {
2064
+ static [entityKind] = "RelationsBuilderColumn";
2065
+ _;
2066
+ constructor(column, tableName, key, through) {
2067
+ this._ = {
2068
+ tableName,
2069
+ column,
2070
+ through,
2071
+ key
2072
+ };
2073
+ }
2074
+ };
2075
+ var RelationsHelperStatic = class {
2076
+ static [entityKind] = "RelationsHelperStatic";
2077
+ constructor(tables) {
2078
+ const one = {};
2079
+ const many = {};
2080
+ for (const [tableName, table] of Object.entries(tables)) {
2081
+ one[tableName] = (config) => new One(tables, table, tableName, config);
2082
+ many[tableName] = (config) => new Many(tables, table, tableName, config);
2349
2083
  }
2350
- return null;
2351
- }
2352
- if (!target || typeof target !== "object") return null;
2353
- const record = target;
2354
- for (const condition of JITI_EXPORT_CONDITION_PRIORITY) {
2355
- const resolved = resolveJitiExportTarget(record[condition]);
2356
- if (resolved) return resolved;
2084
+ this.one = one;
2085
+ this.many = many;
2357
2086
  }
2358
- for (const value of Object.values(record)) {
2359
- const resolved = resolveJitiExportTarget(value);
2360
- if (resolved) return resolved;
2087
+ one;
2088
+ many;
2089
+ };
2090
+ function createRelationsHelper(tables) {
2091
+ const helperStatic = new RelationsHelperStatic(tables);
2092
+ const relationsTables = Object.entries(tables).reduce((acc, [tKey, value]) => {
2093
+ const rTable = new RelationsBuilderTable(value, tKey);
2094
+ const columns = Object.entries(getTableColumns(value)).reduce((colsAcc, [cKey, column]) => {
2095
+ colsAcc[cKey] = new RelationsBuilderColumn(column, tKey, cKey);
2096
+ return colsAcc;
2097
+ }, {});
2098
+ const relationsTable = Object.assign(rTable, columns);
2099
+ Object.defineProperty(relationsTable, "_id", {
2100
+ get() {
2101
+ throw new Error("`_id` is no longer public in relations. Use `id`.");
2102
+ },
2103
+ enumerable: false,
2104
+ configurable: false
2105
+ });
2106
+ acc[tKey] = relationsTable;
2107
+ return acc;
2108
+ }, {});
2109
+ return Object.assign(helperStatic, relationsTables);
2110
+ }
2111
+ function extractTablesFromSchema(schema) {
2112
+ const schemaTables = schema && typeof schema === "object" && !Array.isArray(schema) && "tables" in schema && schema.tables && typeof schema.tables === "object" && !Array.isArray(schema.tables) ? schema.tables : schema;
2113
+ return Object.fromEntries(Object.entries(schemaTables).filter(([_, e]) => isConvexTable(e)));
2114
+ }
2115
+ var Relation = class {
2116
+ static [entityKind] = "RelationV2";
2117
+ fieldName;
2118
+ sourceColumns;
2119
+ targetColumns;
2120
+ alias;
2121
+ where;
2122
+ sourceTable;
2123
+ targetTable;
2124
+ through;
2125
+ throughTable;
2126
+ isReversed;
2127
+ /** @internal */
2128
+ sourceColumnTableNames = [];
2129
+ /** @internal */
2130
+ targetColumnTableNames = [];
2131
+ constructor(targetTable, targetTableName) {
2132
+ this.targetTableName = targetTableName;
2133
+ this.targetTable = targetTable;
2361
2134
  }
2362
- return null;
2363
2135
  };
2364
- const buildLocalPackageExportAliases = (cwd, packageName) => {
2365
- const packageDir = path.join(cwd, "node_modules", ...packageName.split("/"));
2366
- const packageJsonPath = path.join(packageDir, "package.json");
2367
- if (!fs.existsSync(packageJsonPath)) return {};
2368
- const exportsField = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")).exports;
2369
- if (!exportsField || typeof exportsField !== "object" || Array.isArray(exportsField)) return {};
2370
- const aliases = {};
2371
- for (const [exportKey, exportTarget] of Object.entries(exportsField)) {
2372
- const resolvedTarget = resolveJitiExportTarget(exportTarget);
2373
- if (!resolvedTarget || !resolvedTarget.startsWith("./")) continue;
2374
- const specifier = exportKey === "." ? packageName : exportKey.startsWith("./") ? `${packageName}${exportKey.slice(1)}` : null;
2375
- if (!specifier) continue;
2376
- aliases[specifier] = path.join(packageDir, resolvedTarget);
2136
+ var One = class extends Relation {
2137
+ static [entityKind] = "OneV2";
2138
+ relationType = "one";
2139
+ optional;
2140
+ constructor(tables, targetTable, targetTableName, config) {
2141
+ super(targetTable, targetTableName);
2142
+ this.alias = config?.alias;
2143
+ this.where = config?.where;
2144
+ if (config?.from) this.sourceColumns = (Array.isArray(config.from) ? config.from : [config.from]).map((it) => {
2145
+ this.throughTable ??= it._.through ? tables[it._.through._.tableName] : void 0;
2146
+ this.sourceColumnTableNames.push(it._.tableName);
2147
+ return it._.column;
2148
+ });
2149
+ if (config?.to) this.targetColumns = (Array.isArray(config.to) ? config.to : [config.to]).map((it) => {
2150
+ this.throughTable ??= it._.through ? tables[it._.through._.tableName] : void 0;
2151
+ this.targetColumnTableNames.push(it._.tableName);
2152
+ return it._.column;
2153
+ });
2154
+ if (this.throughTable) this.through = {
2155
+ source: (Array.isArray(config?.from) ? config.from : config?.from ? [config.from] : []).map((c) => c._.through),
2156
+ target: (Array.isArray(config?.to) ? config.to : config?.to ? [config.to] : []).map((c) => c._.through)
2157
+ };
2158
+ this.optional = config?.optional ?? true;
2377
2159
  }
2378
- return aliases;
2379
2160
  };
2380
- const ensureServerParserShim = (cwd) => {
2381
- const shimDir = path.join(cwd, "node_modules", ".kitcn");
2382
- const shimPath = path.join(shimDir, "project-jiti-server-shim.mjs");
2383
- fs.mkdirSync(shimDir, { recursive: true });
2384
- if (!fs.existsSync(shimPath) || fs.readFileSync(shimPath, "utf8") !== SERVER_PARSER_SHIM_SOURCE) fs.writeFileSync(shimPath, SERVER_PARSER_SHIM_SOURCE, "utf8");
2385
- return shimPath;
2161
+ var Many = class extends Relation {
2162
+ static [entityKind] = "ManyV2";
2163
+ relationType = "many";
2164
+ constructor(tables, targetTable, targetTableName, config) {
2165
+ super(targetTable, targetTableName);
2166
+ this.config = config;
2167
+ this.alias = config?.alias;
2168
+ this.where = config?.where;
2169
+ if (config?.from) this.sourceColumns = (Array.isArray(config.from) ? config.from : [config.from]).map((it) => {
2170
+ this.throughTable ??= it._.through ? tables[it._.through._.tableName] : void 0;
2171
+ this.sourceColumnTableNames.push(it._.tableName);
2172
+ return it._.column;
2173
+ });
2174
+ if (config?.to) this.targetColumns = (Array.isArray(config.to) ? config.to : [config.to]).map((it) => {
2175
+ this.throughTable ??= it._.through ? tables[it._.through._.tableName] : void 0;
2176
+ this.targetColumnTableNames.push(it._.tableName);
2177
+ return it._.column;
2178
+ });
2179
+ if (this.throughTable) this.through = {
2180
+ source: (Array.isArray(config?.from) ? config.from : config?.from ? [config.from] : []).map((c) => c._.through),
2181
+ target: (Array.isArray(config?.to) ? config.to : config?.to ? [config.to] : []).map((c) => c._.through)
2182
+ };
2183
+ }
2386
2184
  };
2387
- const trimTsconfigWildcardSuffix = (value) => value.endsWith("/*") ? value.slice(0, -2) : value;
2388
- const loadTypeScript = () => {
2389
- try {
2390
- return require("typescript");
2391
- } catch {
2392
- return null;
2185
+ function buildRelations(tables, config, strict, defaults) {
2186
+ const tablesConfig = {};
2187
+ for (const [tsName, table] of Object.entries(tables)) tablesConfig[tsName] = {
2188
+ table,
2189
+ name: tsName,
2190
+ polymorphic: table[TablePolymorphic],
2191
+ relations: config[tsName] ?? {},
2192
+ strict,
2193
+ defaults
2194
+ };
2195
+ return processRelations(tablesConfig, tables);
2196
+ }
2197
+ function defineRelations(schema, relations) {
2198
+ const tables = extractTablesFromSchema(schema);
2199
+ const schemaOptions = schema[OrmSchemaOptions];
2200
+ const strict = schemaOptions?.strict ?? true;
2201
+ const defaults = schemaOptions?.defaults;
2202
+ const schemaDefinition = schema[OrmSchemaDefinition];
2203
+ const pluginTableNames = schema[OrmSchemaExtensionTables];
2204
+ const plugins = schema[OrmSchemaExtensions];
2205
+ const tablesConfig = buildRelations(tables, relations ? relations(createRelationsHelper(tables)) : {}, strict, defaults);
2206
+ Object.defineProperty(tablesConfig, OrmSchemaOptions, {
2207
+ value: {
2208
+ strict,
2209
+ defaults
2210
+ },
2211
+ enumerable: false
2212
+ });
2213
+ if (schemaDefinition) Object.defineProperty(tablesConfig, OrmSchemaDefinition, {
2214
+ value: schemaDefinition,
2215
+ enumerable: false
2216
+ });
2217
+ if (pluginTableNames) Object.defineProperty(tablesConfig, OrmSchemaExtensionTables, {
2218
+ value: pluginTableNames,
2219
+ enumerable: false
2220
+ });
2221
+ if (plugins) Object.defineProperty(tablesConfig, OrmSchemaExtensions, {
2222
+ value: plugins,
2223
+ enumerable: false
2224
+ });
2225
+ return tablesConfig;
2226
+ }
2227
+ function processRelations(tablesConfig, tables) {
2228
+ for (const tableConfig of Object.values(tablesConfig)) for (const [relationFieldName, relation] of Object.entries(tableConfig.relations)) {
2229
+ if (!isRelation(relation)) continue;
2230
+ relation.sourceTable = tableConfig.table;
2231
+ relation.fieldName = relationFieldName;
2232
+ }
2233
+ for (const [sourceTableName, tableConfig] of Object.entries(tablesConfig)) for (const [relationFieldName, relation] of Object.entries(tableConfig.relations)) {
2234
+ if (!isRelation(relation)) continue;
2235
+ let reverseRelation;
2236
+ const { targetTableName, alias, sourceColumns, targetColumns, throughTable, sourceTable, through, where, sourceColumnTableNames, targetColumnTableNames } = relation;
2237
+ const relationPrintName = `relations -> ${tableConfig.name}: { ${relationFieldName}: r.${relation.relationType === "one" ? "one" : "many"}.${targetTableName}(...) }`;
2238
+ if (relationFieldName in getTableColumns(tableConfig.table)) throw new Error(`${relationPrintName}: relation name collides with column "${relationFieldName}" of table "${tableConfig.name}"`);
2239
+ if (typeof alias === "string" && !alias) throw new Error(`${relationPrintName}: "alias" cannot be empty`);
2240
+ if (sourceColumns?.length === 0) throw new Error(`${relationPrintName}: "from" cannot be empty`);
2241
+ if (targetColumns?.length === 0) throw new Error(`${relationPrintName}: "to" cannot be empty`);
2242
+ if (sourceColumns && targetColumns) {
2243
+ if (sourceColumns.length !== targetColumns.length && !throughTable) throw new Error(`${relationPrintName}: "from" and "to" must have same length`);
2244
+ for (const sName of sourceColumnTableNames) if (sName !== sourceTableName) throw new Error(`${relationPrintName}: all "from" columns must belong to table "${sourceTableName}", found "${sName}"`);
2245
+ for (const tName of targetColumnTableNames) if (tName !== targetTableName) throw new Error(`${relationPrintName}: all "to" columns must belong to table "${targetTableName}", found "${tName}"`);
2246
+ if (through) {
2247
+ if (through.source.length !== sourceColumns.length || through.target.length !== targetColumns.length) throw new Error(`${relationPrintName}: .through() must be used on all columns in "from" and "to" or none`);
2248
+ for (const column of through.source) if (tables[column._.tableName] !== throughTable) throw new Error(`${relationPrintName}: .through() must use same table for all columns`);
2249
+ for (const column of through.target) if (tables[column._.tableName] !== throughTable) throw new Error(`${relationPrintName}: .through() must use same table for all columns`);
2250
+ }
2251
+ continue;
2252
+ }
2253
+ if (sourceColumns || targetColumns) throw new Error(`${relationPrintName}: relation must have both "from" and "to" or none`);
2254
+ const reverseTableConfig = tablesConfig[targetTableName];
2255
+ if (!reverseTableConfig) throw new Error(`${relationPrintName}: missing "from"/"to" and no reverse relation found for "${targetTableName}"`);
2256
+ if (alias) {
2257
+ const reverseRelations = Object.values(reverseTableConfig.relations).filter((it) => isRelation(it) && it.alias === alias && it !== relation);
2258
+ if (reverseRelations.length > 1) throw new Error(`${relationPrintName}: multiple reverse relations with alias "${alias}" found`);
2259
+ reverseRelation = reverseRelations[0];
2260
+ if (!reverseRelation) throw new Error(`${relationPrintName}: no reverse relation with alias "${alias}" found in "${targetTableName}"`);
2261
+ } else {
2262
+ const reverseRelations = Object.values(reverseTableConfig.relations).filter((it) => isRelation(it) && it.targetTable === sourceTable && !it.alias && it !== relation);
2263
+ if (reverseRelations.length > 1) throw new Error(`${relationPrintName}: multiple relations between "${targetTableName}" and "${sourceTableName}"; use alias`);
2264
+ reverseRelation = reverseRelations[0];
2265
+ if (!reverseRelation) throw new Error(`${relationPrintName}: no reverse relation between "${targetTableName}" and "${sourceTableName}"`);
2266
+ }
2267
+ if (!reverseRelation.sourceColumns || !reverseRelation.targetColumns) throw new Error(`${relationPrintName}: reverse relation "${targetTableName}.${reverseRelation.fieldName}" missing "from"/"to"`);
2268
+ relation.sourceColumns = reverseRelation.targetColumns;
2269
+ relation.targetColumns = reverseRelation.sourceColumns;
2270
+ relation.through = reverseRelation.through ? {
2271
+ source: reverseRelation.through.target,
2272
+ target: reverseRelation.through.source
2273
+ } : void 0;
2274
+ relation.throughTable = reverseRelation.throughTable;
2275
+ relation.isReversed = !where;
2276
+ relation.where = where ?? reverseRelation.where;
2277
+ }
2278
+ return tablesConfig;
2279
+ }
2280
+ function isConvexTable(value) {
2281
+ return typeof value === "object" && value !== null && "tableName" in value && Columns in value;
2282
+ }
2283
+ function isRelation(value) {
2284
+ return value instanceof Relation;
2285
+ }
2286
+ function getTableColumns(table) {
2287
+ const columns = table[Columns];
2288
+ const system = {};
2289
+ if (table.id) system.id = table.id;
2290
+ if (table.createdAt || table._creationTime) system.createdAt = table._creationTime ?? table.createdAt;
2291
+ return {
2292
+ ...columns,
2293
+ ...system
2294
+ };
2295
+ }
2296
+
2297
+ //#endregion
2298
+ //#region src/orm/triggers.ts
2299
+ function defineTriggers(schema, triggers) {
2300
+ return triggers;
2301
+ }
2302
+
2303
+ //#endregion
2304
+ //#region src/orm/schema.ts
2305
+ const BUILTIN_SCHEMA_EXTENSIONS = [aggregateExtension(), migrationExtension()];
2306
+ function resolveSchemaExtensions(extensions) {
2307
+ const resolved = [...BUILTIN_SCHEMA_EXTENSIONS, ...extensions ?? []];
2308
+ const seen = /* @__PURE__ */ new Set();
2309
+ for (const extension of resolved) {
2310
+ if (seen.has(extension.key)) throw new Error(`defineSchema received duplicate extension '${extension.key}'. Remove duplicate extension registrations.`);
2311
+ seen.add(extension.key);
2393
2312
  }
2394
- };
2395
- const buildTsconfigPathAliases = (cwd) => {
2396
- const typescript = loadTypeScript();
2397
- if (!typescript) return {};
2398
- const configPath = typescript.findConfigFile(cwd, fs.existsSync, "tsconfig.json");
2399
- if (!configPath) return {};
2400
- const readResult = typescript.readConfigFile(configPath, typescript.sys.readFile);
2401
- if (readResult.error) return {};
2402
- const parsedConfig = typescript.parseJsonConfigFileContent(readResult.config, typescript.sys, path.dirname(configPath));
2403
- const baseUrl = typeof parsedConfig.options.baseUrl === "string" && parsedConfig.options.baseUrl.length > 0 ? parsedConfig.options.baseUrl : path.dirname(configPath);
2404
- const paths = parsedConfig.options.paths;
2405
- if (!paths) return {};
2406
- const aliases = {};
2407
- for (const [specifier, targets] of Object.entries(paths)) {
2408
- const firstTarget = targets[0];
2409
- if (!firstTarget) continue;
2410
- const aliasKey = trimTsconfigWildcardSuffix(specifier);
2411
- const aliasTarget = trimTsconfigWildcardSuffix(firstTarget);
2412
- if (!aliasKey || aliasKey === "*") continue;
2413
- aliases[aliasKey] = path.resolve(baseUrl, aliasTarget);
2313
+ return resolved;
2314
+ }
2315
+ function mergeRelationConfigs(sources) {
2316
+ const merged = {};
2317
+ const relationOrigins = /* @__PURE__ */ new Map();
2318
+ for (const { source, config } of sources) for (const [tableName, relationConfig] of Object.entries(config)) {
2319
+ if (!relationConfig) continue;
2320
+ let tableRelations = merged[tableName];
2321
+ if (!tableRelations) {
2322
+ tableRelations = Object.create(null);
2323
+ merged[tableName] = tableRelations;
2324
+ }
2325
+ for (const [fieldName, relation] of Object.entries(relationConfig)) {
2326
+ const relationKey = `${tableName}.${fieldName}`;
2327
+ const existingSource = relationOrigins.get(relationKey);
2328
+ if (existingSource) throw new Error(`defineSchema relation field '${relationKey}' is defined more than once (${existingSource} and ${source}).`);
2329
+ tableRelations[fieldName] = relation;
2330
+ relationOrigins.set(relationKey, source);
2331
+ }
2414
2332
  }
2415
- return aliases;
2416
- };
2417
- const getProjectServerParserShimPath = (cwd = process.cwd()) => ensureServerParserShim(cwd);
2418
- const createProjectJiti = (cwd = process.cwd()) => loadJiti().createJiti(cwd, {
2419
- interopDefault: true,
2420
- jsx: { runtime: "automatic" },
2421
- moduleCache: false,
2422
- tryNative: false,
2423
- alias: {
2424
- ...buildTsconfigPathAliases(cwd),
2425
- ...buildLocalPackageExportAliases(cwd, "kitcn"),
2426
- ...buildLocalPackageExportAliases(cwd, "convex"),
2427
- "kitcn/server": getProjectServerParserShimPath(cwd)
2333
+ return merged;
2334
+ }
2335
+ function mergeTriggerConfigs(sources) {
2336
+ const merged = {};
2337
+ const triggerOrigins = /* @__PURE__ */ new Map();
2338
+ for (const { source, config } of sources) for (const [tableName, tableConfig] of Object.entries(config)) {
2339
+ if (!tableConfig || typeof tableConfig !== "object" || Array.isArray(tableConfig)) {
2340
+ const triggerKey = `${tableName}`;
2341
+ const existingSource = triggerOrigins.get(triggerKey);
2342
+ if (existingSource) throw new Error(`defineSchema trigger '${triggerKey}' is defined more than once (${existingSource} and ${source}).`);
2343
+ merged[tableName] = tableConfig;
2344
+ triggerOrigins.set(triggerKey, source);
2345
+ continue;
2346
+ }
2347
+ let mergedTable = merged[tableName];
2348
+ if (!mergedTable) {
2349
+ mergedTable = Object.create(null);
2350
+ merged[tableName] = mergedTable;
2351
+ }
2352
+ for (const [hookKey, hookValue] of Object.entries(tableConfig)) {
2353
+ if ((hookKey === "create" || hookKey === "update" || hookKey === "delete") && hookValue && typeof hookValue === "object" && !Array.isArray(hookValue)) {
2354
+ let mergedOperation = mergedTable[hookKey];
2355
+ if (!mergedOperation || typeof mergedOperation !== "object") {
2356
+ mergedOperation = Object.create(null);
2357
+ mergedTable[hookKey] = mergedOperation;
2358
+ }
2359
+ for (const [operationHookKey, operationHookValue] of Object.entries(hookValue)) {
2360
+ const triggerKey = `${tableName}.${hookKey}.${operationHookKey}`;
2361
+ const existingSource = triggerOrigins.get(triggerKey);
2362
+ if (existingSource) throw new Error(`defineSchema trigger '${triggerKey}' is defined more than once (${existingSource} and ${source}).`);
2363
+ mergedOperation[operationHookKey] = operationHookValue;
2364
+ triggerOrigins.set(triggerKey, source);
2365
+ }
2366
+ continue;
2367
+ }
2368
+ const triggerKey = `${tableName}.${hookKey}`;
2369
+ const existingSource = triggerOrigins.get(triggerKey);
2370
+ if (existingSource) throw new Error(`defineSchema trigger '${triggerKey}' is defined more than once (${existingSource} and ${source}).`);
2371
+ mergedTable[hookKey] = hookValue;
2372
+ triggerOrigins.set(triggerKey, source);
2373
+ }
2428
2374
  }
2429
- });
2375
+ return merged;
2376
+ }
2377
+ function defineMetadata(target, key, value) {
2378
+ const existing = Object.getOwnPropertyDescriptor(target, key);
2379
+ if (existing && !existing.configurable) return;
2380
+ Object.defineProperty(target, key, {
2381
+ value,
2382
+ enumerable: false,
2383
+ configurable: true
2384
+ });
2385
+ }
2386
+ const OrmSchemaComposerState = Symbol("kitcn:OrmSchemaComposerState");
2387
+ function getSchemaComposerState(schema) {
2388
+ if (!schema || typeof schema !== "object") return;
2389
+ return schema[OrmSchemaComposerState];
2390
+ }
2391
+ function finalizeSchemaMetadata(schema) {
2392
+ if (!schema || typeof schema !== "object") return;
2393
+ const composerState = getSchemaComposerState(schema);
2394
+ if (!composerState) return;
2395
+ const schemaObject = schema;
2396
+ const resolvedExtensions = schemaObject[OrmSchemaExtensions] ?? resolveSchemaExtensions(composerState.extensions);
2397
+ const extensionTableNames = schemaObject[OrmSchemaExtensionTables] ?? Object.freeze([]);
2398
+ const options = schemaObject[OrmSchemaOptions];
2399
+ const extensionRelationFactories = resolvedExtensions.map((extension) => ({
2400
+ key: extension.key,
2401
+ relations: extension[OrmSchemaExtensionRelations]
2402
+ })).filter((entry) => entry.relations !== void 0);
2403
+ const extensionTriggerFactories = resolvedExtensions.map((extension) => ({
2404
+ key: extension.key,
2405
+ triggers: extension[OrmSchemaExtensionTriggers]
2406
+ })).filter((entry) => entry.triggers !== void 0);
2407
+ const hasExtensionRelations = extensionRelationFactories.length > 0;
2408
+ const hasExtensionTriggers = extensionTriggerFactories.length > 0;
2409
+ const shouldBuildRelations = hasExtensionRelations || hasExtensionTriggers || Boolean(composerState.relations) || Boolean(composerState.triggers);
2410
+ let relations = schemaObject[OrmSchemaRelations];
2411
+ if (!relations && shouldBuildRelations) {
2412
+ relations = hasExtensionRelations || composerState.relations ? defineRelations(schema, (helpers) => mergeRelationConfigs([...extensionRelationFactories.map((extension) => ({
2413
+ source: `extension '${extension.key}'`,
2414
+ config: extension.relations(helpers)
2415
+ })), ...composerState.relations ? [{
2416
+ source: "schema.relations()",
2417
+ config: composerState.relations(helpers)
2418
+ }] : []])) : defineRelations(schema);
2419
+ defineMetadata(relations, OrmSchemaDefinition, schema);
2420
+ if (options) defineMetadata(relations, OrmSchemaOptions, options);
2421
+ defineMetadata(relations, OrmSchemaExtensionTables, extensionTableNames);
2422
+ defineMetadata(relations, OrmSchemaExtensions, resolvedExtensions);
2423
+ defineMetadata(schema, OrmSchemaRelations, relations);
2424
+ }
2425
+ if (!relations || schemaObject[OrmSchemaTriggers]) return;
2426
+ const triggerSources = hasExtensionTriggers || composerState.triggers ? [...extensionTriggerFactories.map((extension) => ({
2427
+ source: `extension '${extension.key}'`,
2428
+ config: typeof extension.triggers === "function" ? extension.triggers(relations) : extension.triggers
2429
+ })), ...composerState.triggers ? [{
2430
+ source: "schema.triggers()",
2431
+ config: composerState.triggers
2432
+ }] : []] : [];
2433
+ if (triggerSources.length === 0) return;
2434
+ defineMetadata(schema, OrmSchemaTriggers, defineTriggers(relations, mergeTriggerConfigs(triggerSources)));
2435
+ }
2436
+ function getSchemaRelations(schema) {
2437
+ if (!schema || typeof schema !== "object") return;
2438
+ finalizeSchemaMetadata(schema);
2439
+ return schema[OrmSchemaRelations];
2440
+ }
2441
+ function getSchemaTriggers(schema) {
2442
+ if (!schema || typeof schema !== "object") return;
2443
+ finalizeSchemaMetadata(schema);
2444
+ return schema[OrmSchemaTriggers];
2445
+ }
2430
2446
 
2431
2447
  //#endregion
2432
2448
  //#region src/shared/meta-utils.ts
@@ -2510,6 +2526,56 @@ const logger = {
2510
2526
  }
2511
2527
  };
2512
2528
 
2529
+ //#endregion
2530
+ //#region src/cli/utils/schema-tables.ts
2531
+ /**
2532
+ * Schema introspection shared by codegen and the backend flows.
2533
+ *
2534
+ * Both need the same answer to "does this app use aggregate/rank indexes?":
2535
+ * codegen registers `aggregateCapability()` on it, and the CLI skips the
2536
+ * backfill flows on it. Two implementations would drift into a state where the
2537
+ * generated ORM refuses a call the CLI still makes.
2538
+ */
2539
+ /** The `defineSchema(...)` value behind a loaded `schema.ts` module. */
2540
+ function resolveSchemaDefaultExport(schemaModule) {
2541
+ const schemaValue = schemaModule.default !== void 0 ? schemaModule.default : schemaModule;
2542
+ if (!schemaValue || typeof schemaValue !== "object") return null;
2543
+ return schemaValue;
2544
+ }
2545
+ /**
2546
+ * Every live table behind a schema value.
2547
+ *
2548
+ * `SchemaDefinition.tables` is the merged base + plugin-extension map, so auth,
2549
+ * ratelimit and the built-in aggregate/migration bookkeeping tables are all
2550
+ * covered. The relations config is unioned in because a `.relations()` chain
2551
+ * re-materializes the definition, and it is absent for schemas that declare
2552
+ * neither relations nor triggers.
2553
+ */
2554
+ function collectSchemaTables(schemaValue) {
2555
+ const allTables = /* @__PURE__ */ new Set();
2556
+ const relations = getSchemaRelations(schemaValue);
2557
+ if (relations && typeof relations === "object" && !Array.isArray(relations)) for (const relationConfig of Object.values(relations)) {
2558
+ const table = relationConfig?.table;
2559
+ if (table) allTables.add(table);
2560
+ }
2561
+ const tables = schemaValue.tables;
2562
+ if (tables && typeof tables === "object" && !Array.isArray(tables)) {
2563
+ for (const table of Object.values(tables)) if (table) allTables.add(table);
2564
+ }
2565
+ return [...allTables];
2566
+ }
2567
+ /**
2568
+ * Whether the schema needs the aggregate-index runtime.
2569
+ *
2570
+ * `aggregateIndex(...)` / `rankIndex(...)` register synchronously inside
2571
+ * `convexTable(...)`, so the answer is final once `schema.ts` has evaluated.
2572
+ * Duck-typed rather than routed through `getTableConfig`, which throws on a
2573
+ * raw Convex `defineTable()` in a mixed schema.
2574
+ */
2575
+ function schemaDeclaresAggregateIndexes(schemaValue) {
2576
+ return collectSchemaTables(schemaValue).some((table) => getAggregateIndexes(table).length > 0 || getRankIndexes(table).length > 0);
2577
+ }
2578
+
2513
2579
  //#endregion
2514
2580
  //#region src/cli/codegen.ts
2515
2581
  /** Valid JS identifier pattern for object keys */
@@ -2606,49 +2672,51 @@ const GENERATED_ORM_RUNTIME_PROCEDURES = [
2606
2672
  type: "mutation"
2607
2673
  },
2608
2674
  {
2609
- exportName: "aggregateBackfill",
2675
+ exportName: "migrationRun",
2610
2676
  internal: true,
2611
2677
  type: "mutation"
2612
2678
  },
2613
2679
  {
2614
- exportName: "aggregateBackfillChunk",
2680
+ exportName: "migrationRunChunk",
2615
2681
  internal: true,
2616
2682
  type: "mutation"
2617
2683
  },
2618
2684
  {
2619
- exportName: "aggregateBackfillStatus",
2685
+ exportName: "migrationStatus",
2620
2686
  internal: true,
2621
2687
  type: "mutation"
2622
2688
  },
2623
2689
  {
2624
- exportName: "migrationRun",
2690
+ exportName: "migrationCancel",
2625
2691
  internal: true,
2626
2692
  type: "mutation"
2627
2693
  },
2628
2694
  {
2629
- exportName: "migrationRunChunk",
2695
+ exportName: "resetChunk",
2630
2696
  internal: true,
2631
2697
  type: "mutation"
2632
2698
  },
2633
2699
  {
2634
- exportName: "migrationStatus",
2700
+ exportName: "reset",
2635
2701
  internal: true,
2636
- type: "mutation"
2637
- },
2702
+ type: "action"
2703
+ }
2704
+ ];
2705
+ const GENERATED_AGGREGATE_RUNTIME_PROCEDURES = [
2638
2706
  {
2639
- exportName: "migrationCancel",
2707
+ exportName: "aggregateBackfill",
2640
2708
  internal: true,
2641
2709
  type: "mutation"
2642
2710
  },
2643
2711
  {
2644
- exportName: "resetChunk",
2712
+ exportName: "aggregateBackfillChunk",
2645
2713
  internal: true,
2646
2714
  type: "mutation"
2647
2715
  },
2648
2716
  {
2649
- exportName: "reset",
2717
+ exportName: "aggregateBackfillStatus",
2650
2718
  internal: true,
2651
- type: "action"
2719
+ type: "mutation"
2652
2720
  }
2653
2721
  ];
2654
2722
  function listFilesRecursive(cwd, relDir = "") {
@@ -2848,6 +2916,9 @@ const GENERATED_DIR = "generated";
2848
2916
  function getGeneratedServerOutputFile(functionsDir) {
2849
2917
  return path.join(functionsDir, GENERATED_DIR, "server.ts");
2850
2918
  }
2919
+ function getGeneratedAggregateOutputFile(functionsDir) {
2920
+ return path.join(functionsDir, GENERATED_DIR, "aggregate.ts");
2921
+ }
2851
2922
  function getGeneratedOrmOutputFile(functionsDir) {
2852
2923
  return path.join(functionsDir, GENERATED_DIR, "orm.ts");
2853
2924
  }
@@ -2991,34 +3062,44 @@ function listGeneratedRuntimeFiles(functionsDir) {
2991
3062
  if (!fs.existsSync(generatedDir)) return [];
2992
3063
  return listFilesRecursive(generatedDir).filter((file) => file.endsWith(".runtime.ts")).map((file) => path.join(generatedDir, file));
2993
3064
  }
3065
+ const EMPTY_SCHEMA_METADATA = {
3066
+ hasOrmSchema: false,
3067
+ hasAggregateIndexes: false,
3068
+ hasRelations: false,
3069
+ hasTriggers: false
3070
+ };
2994
3071
  async function resolveSchemaMetadataForCodegen(functionsDir, debug, createJitiInstance = createProjectJiti) {
2995
3072
  const schemaPath = path.join(functionsDir, "schema.ts");
2996
- if (!fs.existsSync(schemaPath)) return {
2997
- hasOrmSchema: false,
2998
- hasRelations: false,
2999
- hasTriggers: false
3000
- };
3073
+ if (!fs.existsSync(schemaPath)) return EMPTY_SCHEMA_METADATA;
3001
3074
  const jitiInstance = createJitiInstance();
3002
3075
  try {
3003
3076
  const schemaModule = await jitiInstance.import(schemaPath);
3004
3077
  const schemaValue = schemaModule && typeof schemaModule === "object" ? schemaModule.default ?? schemaModule : null;
3005
- if (!schemaValue || typeof schemaValue !== "object") return {
3006
- hasOrmSchema: false,
3007
- hasRelations: false,
3008
- hasTriggers: false
3009
- };
3078
+ if (!schemaValue || typeof schemaValue !== "object") return EMPTY_SCHEMA_METADATA;
3079
+ const hasOrmSchema = OrmSchemaOptions in schemaValue;
3080
+ const hasRelations = Boolean(getSchemaRelations(schemaValue));
3081
+ const hasTriggers = Boolean(getSchemaTriggers(schemaValue));
3010
3082
  return {
3011
- hasOrmSchema: OrmSchemaOptions in schemaValue,
3012
- hasRelations: Boolean(getSchemaRelations(schemaValue)),
3013
- hasTriggers: Boolean(getSchemaTriggers(schemaValue))
3083
+ hasOrmSchema,
3084
+ hasAggregateIndexes: hasOrmSchema && resolveHasAggregateIndexes(schemaValue, schemaPath, debug),
3085
+ hasRelations,
3086
+ hasTriggers
3014
3087
  };
3015
3088
  } catch (error) {
3016
3089
  if (debug) logger.warn(`⚠️ Failed to load schema extensions from ${schemaPath}: ${error.message}`);
3017
- return {
3018
- hasOrmSchema: false,
3019
- hasRelations: false,
3020
- hasTriggers: false
3021
- };
3090
+ return EMPTY_SCHEMA_METADATA;
3091
+ }
3092
+ }
3093
+ /**
3094
+ * Fails open: registering an unused capability only costs bundle size, while
3095
+ * omitting a needed one breaks `createOrm()` for every function in the app.
3096
+ */
3097
+ function resolveHasAggregateIndexes(schemaValue, schemaPath, debug) {
3098
+ try {
3099
+ return schemaDeclaresAggregateIndexes(schemaValue);
3100
+ } catch (error) {
3101
+ if (debug) logger.warn(`⚠️ Failed to inspect aggregate indexes in ${schemaPath}: ${error.message}`);
3102
+ return true;
3022
3103
  }
3023
3104
  }
3024
3105
  function cleanupGeneratedPluginArtifacts(functionsDir) {
@@ -3027,7 +3108,7 @@ function cleanupGeneratedPluginArtifacts(functionsDir) {
3027
3108
  force: true
3028
3109
  });
3029
3110
  }
3030
- function emitGeneratedServerFile(outputFile, functionsDir, hasOrmSchema, hasMigrationsManifest, procedureNameLookup) {
3111
+ function emitGeneratedServerFile(outputFile, functionsDir, hasOrmSchema, hasAggregateIndexes, hasMigrationsManifest, procedureNameLookup) {
3031
3112
  const asSingleQuotedImport = (importPath) => `'${importPath.replaceAll("'", "\\'")}'`;
3032
3113
  const serverTypesImportPath = getServerTypesImportPath(outputFile, functionsDir);
3033
3114
  const dataModelImportPath = getDataModelImportPath(outputFile, functionsDir);
@@ -3039,6 +3120,9 @@ function emitGeneratedServerFile(outputFile, functionsDir, hasOrmSchema, hasMigr
3039
3120
  const migrationsManifestImportLiteral = asSingleQuotedImport(migrationsManifestImportPath);
3040
3121
  const migrationsImportLine = hasMigrationsManifest ? `import { migrations } from ${migrationsManifestImportLiteral};\n` : "";
3041
3122
  const migrationsConfigLine = hasMigrationsManifest ? " migrations,\n" : "";
3123
+ const capabilityImportLines = [hasAggregateIndexes ? "import { aggregateCapability } from 'kitcn/orm/aggregate-index';\n" : "", hasMigrationsManifest ? "import { migrationCapability } from 'kitcn/orm/migrations';\n" : ""].join("");
3124
+ const capabilityEntries = [...hasAggregateIndexes ? ["aggregateCapability()"] : [], ...hasMigrationsManifest ? ["migrationCapability()"] : []];
3125
+ const capabilitiesConfigLine = capabilityEntries.length > 0 ? ` capabilities: [${capabilityEntries.join(", ")}],\n` : "";
3042
3126
  const functionsDirHint = normalizeImportPath(path.relative(process.cwd(), functionsDir)) || "convex";
3043
3127
  const procedureNameLookupLiteral = emitProcedureNameLookupLiteral(procedureNameLookup);
3044
3128
  if (!hasOrmSchema) return `// biome-ignore-all format: generated
@@ -3080,6 +3164,7 @@ export const initCRPC = baseInitCRPC.dataModel<DataModel>().context({
3080
3164
  export { httpAction, internalMutation };
3081
3165
  `;
3082
3166
  const moduleNamespace = getModuleNameFromOutputFile(outputFile, functionsDir);
3167
+ const aggregateModuleNamespace = getModuleNameFromOutputFile(getGeneratedAggregateOutputFile(functionsDir), functionsDir);
3083
3168
  return `// biome-ignore-all format: generated
3084
3169
  // This file is auto-generated by kitcn
3085
3170
  // Do not edit manually. Run \`kitcn codegen\` to regenerate.
@@ -3089,7 +3174,7 @@ import {
3089
3174
  type GenericOrmCtx,
3090
3175
  type OrmFunctions,
3091
3176
  } from 'kitcn/orm';
3092
- import {
3177
+ ${capabilityImportLines}import {
3093
3178
  createGeneratedFunctionReference,
3094
3179
  initCRPC as baseInitCRPC,
3095
3180
  registerProcedureNameLookup,
@@ -3107,7 +3192,7 @@ ${migrationsImportLine}
3107
3192
  ${`const ormFunctions: OrmFunctions = {
3108
3193
  scheduledMutationBatch: createGeneratedFunctionReference<"mutation", "internal", unknown>(${JSON.stringify(`${moduleNamespace}:scheduledMutationBatch`)}),
3109
3194
  scheduledDelete: createGeneratedFunctionReference<"mutation", "internal", unknown>(${JSON.stringify(`${moduleNamespace}:scheduledDelete`)}),
3110
- aggregateBackfillChunk: createGeneratedFunctionReference<"mutation", "internal", unknown>(${JSON.stringify(`${moduleNamespace}:aggregateBackfillChunk`)}),
3195
+ aggregateBackfillChunk: createGeneratedFunctionReference<"mutation", "internal", unknown>(${JSON.stringify(`${aggregateModuleNamespace}:aggregateBackfillChunk`)}),
3111
3196
  migrationRunChunk: createGeneratedFunctionReference<"mutation", "internal", unknown>(${JSON.stringify(`${moduleNamespace}:migrationRunChunk`)}),
3112
3197
  resetChunk: createGeneratedFunctionReference<"mutation", "internal", unknown>(${JSON.stringify(`${moduleNamespace}:resetChunk`)}),
3113
3198
  };`}
@@ -3121,7 +3206,7 @@ registerProcedureNameLookup(
3121
3206
  export const orm = createOrm({
3122
3207
  schema: ormSchema,
3123
3208
  ormFunctions,
3124
- ${migrationsConfigLine} internalMutation,
3209
+ ${capabilitiesConfigLine}${migrationsConfigLine} internalMutation,
3125
3210
  });
3126
3211
 
3127
3212
  export type OrmCtx<Ctx extends ServerQueryCtx | ServerMutationCtx = ServerQueryCtx> = GenericOrmCtx<Ctx, typeof ormSchema>;
@@ -3144,9 +3229,6 @@ export { httpAction, internalMutation };
3144
3229
  export const {
3145
3230
  scheduledMutationBatch,
3146
3231
  scheduledDelete,
3147
- aggregateBackfill,
3148
- aggregateBackfillChunk,
3149
- aggregateBackfillStatus,
3150
3232
  migrationRun,
3151
3233
  migrationRunChunk,
3152
3234
  migrationStatus,
@@ -3156,6 +3238,41 @@ export const {
3156
3238
  } = orm.api();
3157
3239
  `;
3158
3240
  }
3241
+ function emitGeneratedAggregateFile(outputFile, functionsDir) {
3242
+ const schemaImportLiteral = JSON.stringify(getSchemaImportPath(outputFile, functionsDir));
3243
+ const serverTypesImportLiteral = JSON.stringify(getServerTypesImportPath(outputFile, functionsDir));
3244
+ const moduleNamespace = getModuleNameFromOutputFile(outputFile, functionsDir);
3245
+ const serverModuleNamespace = getModuleNameFromOutputFile(getGeneratedServerOutputFile(functionsDir), functionsDir);
3246
+ return `// biome-ignore-all format: generated
3247
+ // This file is auto-generated by kitcn
3248
+ // Do not edit manually. Run \`kitcn codegen\` to regenerate.
3249
+
3250
+ import { createOrm, type OrmFunctions } from 'kitcn/orm';
3251
+ import { aggregateCapability } from 'kitcn/orm/aggregate-index';
3252
+ import { createGeneratedFunctionReference } from 'kitcn/server';
3253
+ import { internalMutation } from ${serverTypesImportLiteral};
3254
+ import schema from ${schemaImportLiteral};
3255
+
3256
+ const ormFunctions: OrmFunctions = {
3257
+ scheduledMutationBatch: createGeneratedFunctionReference<"mutation", "internal", unknown>(${JSON.stringify(`${serverModuleNamespace}:scheduledMutationBatch`)}),
3258
+ scheduledDelete: createGeneratedFunctionReference<"mutation", "internal", unknown>(${JSON.stringify(`${serverModuleNamespace}:scheduledDelete`)}),
3259
+ aggregateBackfillChunk: createGeneratedFunctionReference<"mutation", "internal", unknown>(${JSON.stringify(`${moduleNamespace}:aggregateBackfillChunk`)}),
3260
+ };
3261
+
3262
+ const orm = createOrm({
3263
+ schema,
3264
+ ormFunctions,
3265
+ capabilities: [aggregateCapability()],
3266
+ internalMutation,
3267
+ });
3268
+
3269
+ export const {
3270
+ aggregateBackfill,
3271
+ aggregateBackfillChunk,
3272
+ aggregateBackfillStatus,
3273
+ } = orm.api();
3274
+ `;
3275
+ }
3159
3276
  function emitGeneratedAuthFile(outputFile, functionsDir, hasOrmSchema, authContract) {
3160
3277
  const asSingleQuotedImport = (importPath) => `'${importPath.replaceAll("'", "\\'")}'`;
3161
3278
  const runtimeApiImportPath = getRuntimeApiImportPath(outputFile, functionsDir);
@@ -3521,6 +3638,13 @@ function buildGeneratedOrmRuntimeProcedureEntries(moduleName) {
3521
3638
  kind: "dispatch"
3522
3639
  }));
3523
3640
  }
3641
+ function buildGeneratedAggregateRuntimeProcedureEntries(moduleName) {
3642
+ return GENERATED_AGGREGATE_RUNTIME_PROCEDURES.map((entry) => ({
3643
+ ...entry,
3644
+ moduleName,
3645
+ kind: "dispatch"
3646
+ }));
3647
+ }
3524
3648
  function partitionRuntimeEntriesForEmission(entries) {
3525
3649
  return {
3526
3650
  callerEntries: entries,
@@ -3616,6 +3740,7 @@ async function generateMeta(sharedDir, options) {
3616
3740
  const startTime = Date.now();
3617
3741
  const { functionsDir, outputFile } = getConvexConfig(sharedDir);
3618
3742
  const serverOutputFile = getGeneratedServerOutputFile(functionsDir);
3743
+ const aggregateOutputFile = getGeneratedAggregateOutputFile(functionsDir);
3619
3744
  const ormOutputFile = getGeneratedOrmOutputFile(functionsDir);
3620
3745
  const crpcOutputFile = getGeneratedCrpcOutputFile(functionsDir);
3621
3746
  const authOutputFile = getGeneratedAuthOutputFile(functionsDir);
@@ -3670,6 +3795,7 @@ async function generateMeta(sharedDir, options) {
3670
3795
  const runtimePlaceholderModules = [...new Set([
3671
3796
  ...files.map((file) => file.replace(TS_EXTENSION_RE, "")),
3672
3797
  ...hasOrmSchema ? ["generated/server"] : [],
3798
+ ...hasOrmSchema ? ["generated/aggregate"] : [],
3673
3799
  ...generateAuth ? [generatedAuthModuleName] : []
3674
3800
  ])];
3675
3801
  createdRuntimePlaceholders = ensureGeneratedRuntimePlaceholders(functionsDir, runtimePlaceholderModules, resolveModuleRuntimeExportNames(runtimePlaceholderModules, normalizedTrimSegments));
@@ -3780,10 +3906,12 @@ ${optionalTypeExports}
3780
3906
  if (!fs.existsSync(outputDirname)) fs.mkdirSync(outputDirname, { recursive: true });
3781
3907
  writeFileIfChanged(outputFile, output);
3782
3908
  } else fs.rmSync(outputFile, { force: true });
3783
- const serverOutput = emitGeneratedServerFile(serverOutputFile, functionsDir, hasOrmSchema, hasMigrationsManifest, procedureNameLookup);
3909
+ const serverOutput = emitGeneratedServerFile(serverOutputFile, functionsDir, hasOrmSchema, schemaMetadata.hasAggregateIndexes, hasMigrationsManifest, procedureNameLookup);
3784
3910
  const generatedOutputDirname = path.dirname(serverOutputFile);
3785
3911
  if (!fs.existsSync(generatedOutputDirname)) fs.mkdirSync(generatedOutputDirname, { recursive: true });
3786
3912
  writeFileIfChanged(serverOutputFile, serverOutput);
3913
+ if (hasOrmSchema) writeFileIfChanged(aggregateOutputFile, emitGeneratedAggregateFile(aggregateOutputFile, functionsDir));
3914
+ else fs.rmSync(aggregateOutputFile, { force: true });
3787
3915
  fs.rmSync(ormOutputFile, { force: true });
3788
3916
  fs.rmSync(crpcOutputFile, { force: true });
3789
3917
  writeFileIfChanged(migrationsHelperOutputFile, emitGeneratedMigrationsFile(migrationsHelperOutputFile, functionsDir, hasRelationsMetadata));
@@ -3795,6 +3923,7 @@ ${optionalTypeExports}
3795
3923
  fs.rmSync(getLegacyGeneratedOutputFile(functionsDir), { force: true });
3796
3924
  const mergedProcedureEntries = dedupeProcedureEntries([
3797
3925
  ...hasOrmSchema ? buildGeneratedOrmRuntimeProcedureEntries("generated/server") : [],
3926
+ ...hasOrmSchema ? buildGeneratedAggregateRuntimeProcedureEntries("generated/aggregate") : [],
3798
3927
  ...generateApi ? procedureEntries : [],
3799
3928
  ...generateAuth && hasAuthDefaultExport ? buildAuthRuntimeProcedureEntries(generatedAuthModuleName) : []
3800
3929
  ]);
@@ -3838,6 +3967,8 @@ ${optionalTypeExports}
3838
3967
  if (generateApi) logger.success(`\nGenerated ${outputFile}`);
3839
3968
  else logger.info(`\nRemoved ${outputFile}`);
3840
3969
  logger.success(`Generated ${serverOutputFile}`);
3970
+ if (hasOrmSchema) logger.success(`Generated ${aggregateOutputFile}`);
3971
+ else logger.info(`Removed ${aggregateOutputFile}`);
3841
3972
  logger.success(`Generated ${migrationsHelperOutputFile}`);
3842
3973
  if (generateAuth) logger.success(`Generated ${authOutputFile}`);
3843
3974
  else logger.info(`Removed ${authOutputFile}`);
@@ -4220,4 +4351,4 @@ async function withLocalCodegenEnv(sharedDir, backend, fn) {
4220
4351
  }
4221
4352
 
4222
4353
  //#endregion
4223
- export { createSystemFields as C, TableName as S, getSchemaRelations as _, generateMeta as a, OrmSchemaExtensions as b, PARSE_SNAPSHOT_SUFFIX as c, loadBabelParser as d, loadClackPrompts as f, isColorEnabled as g, highlighter as h, resolveConfiguredBackend as i, createProjectJiti as l, loadEsbuild as m, withLocalCodegenEnv as n, getConvexConfig as o, loadDotenv as p, loadCliConfig as r, logger as s, getLocalBackendEnvVars as t, CRPC_BUILDER_STUB_SOURCE as u, Columns as v, RlsPolicies as x, EnableRLS as y };
4354
+ export { Columns as C, TableName as D, RlsPolicies as E, createSystemFields as O, getRankIndexes as S, OrmSchemaExtensions as T, loadEsbuild as _, generateMeta as a, getAggregateIndexes as b, resolveSchemaDefaultExport as c, PARSE_SNAPSHOT_SUFFIX as d, createProjectJiti as f, loadDotenv as g, loadClackPrompts as h, resolveConfiguredBackend as i, schemaDeclaresAggregateIndexes as l, loadBabelParser as m, withLocalCodegenEnv as n, getConvexConfig as o, CRPC_BUILDER_STUB_SOURCE as p, loadCliConfig as r, collectSchemaTables as s, getLocalBackendEnvVars as t, logger as u, highlighter as v, EnableRLS as w, getIndexes as x, isColorEnabled as y };