kitcn 0.17.2 → 0.17.4

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.
@@ -0,0 +1,4219 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import fs from "node:fs";
4
+ import path, { join } from "node:path";
5
+ import { createHash } from "node:crypto";
6
+ import { v } from "convex/values";
7
+ import "convex/server";
8
+ import { createColors } from "picocolors";
9
+
10
+ //#region src/orm/builders/column-builder.ts
11
+ /**
12
+ * entityKind symbol for runtime type checking
13
+ * Following Drizzle's pattern for type guards
14
+ */
15
+ const entityKind = Symbol.for("kitcn:entityKind");
16
+ /**
17
+ * Base ColumnBuilder abstract class
18
+ *
19
+ * All column builders inherit from this class.
20
+ * Implements chaining methods and stores runtime config.
21
+ */
22
+ var ColumnBuilder = class {
23
+ static [entityKind] = "ColumnBuilder";
24
+ [entityKind] = "ColumnBuilder";
25
+ /**
26
+ * Runtime configuration - actual mutable state
27
+ */
28
+ config;
29
+ constructor(name, dataType, columnType) {
30
+ this.config = {
31
+ name,
32
+ notNull: false,
33
+ default: void 0,
34
+ hasDefault: false,
35
+ primaryKey: false,
36
+ isUnique: false,
37
+ uniqueName: void 0,
38
+ uniqueNulls: void 0,
39
+ foreignKeyConfigs: [],
40
+ dataType,
41
+ columnType
42
+ };
43
+ }
44
+ /**
45
+ * Mark column as NOT NULL
46
+ * Returns type-branded instance with notNull: true
47
+ */
48
+ notNull() {
49
+ this.config.notNull = true;
50
+ return this;
51
+ }
52
+ /**
53
+ * Override the TypeScript type for this column.
54
+ * Mirrors Drizzle's $type() (type-only, no runtime validation changes).
55
+ */
56
+ $type() {
57
+ return this;
58
+ }
59
+ /**
60
+ * Set default value for column
61
+ * Makes field optional on insert
62
+ */
63
+ default(value) {
64
+ this.config.default = value;
65
+ this.config.hasDefault = true;
66
+ return this;
67
+ }
68
+ /**
69
+ * Set default function for column (runtime evaluated on insert).
70
+ * Mirrors Drizzle's $defaultFn() / $default().
71
+ */
72
+ $defaultFn(fn) {
73
+ this.config.defaultFn = fn;
74
+ return this;
75
+ }
76
+ /**
77
+ * Alias of $defaultFn for Drizzle parity.
78
+ */
79
+ $default(fn) {
80
+ return this.$defaultFn(fn);
81
+ }
82
+ /**
83
+ * Set on-update function for column (runtime evaluated on update).
84
+ * Mirrors Drizzle's $onUpdateFn() / $onUpdate().
85
+ */
86
+ $onUpdateFn(fn) {
87
+ this.config.onUpdateFn = fn;
88
+ return this;
89
+ }
90
+ /**
91
+ * Alias of $onUpdateFn for Drizzle parity.
92
+ */
93
+ $onUpdate(fn) {
94
+ return this.$onUpdateFn(fn);
95
+ }
96
+ /**
97
+ * Mark column as primary key
98
+ * Implies NOT NULL
99
+ */
100
+ primaryKey() {
101
+ this.config.primaryKey = true;
102
+ this.config.notNull = true;
103
+ return this;
104
+ }
105
+ /**
106
+ * Mark column as UNIQUE
107
+ * Mirrors Drizzle column unique API
108
+ */
109
+ unique(name, config) {
110
+ this.config.isUnique = true;
111
+ this.config.uniqueName = name;
112
+ this.config.uniqueNulls = config?.nulls;
113
+ return this;
114
+ }
115
+ /**
116
+ * Define a foreign key reference
117
+ * Mirrors Drizzle column references() API
118
+ */
119
+ references(ref, config = {}) {
120
+ this.config.foreignKeyConfigs.push({
121
+ ref,
122
+ config
123
+ });
124
+ return this;
125
+ }
126
+ };
127
+
128
+ //#endregion
129
+ //#region src/orm/builders/system-fields.ts
130
+ /**
131
+ * System Fields - Convex-provided fields available on all documents
132
+ *
133
+ * id: Document ID (string, backed by internal Convex _id)
134
+ * createdAt: Creation timestamp alias (backed by internal Convex _creationTime)
135
+ *
136
+ * These are automatically added to every Convex table.
137
+ */
138
+ var ConvexSystemIdBuilder = class extends ColumnBuilder {
139
+ static [entityKind] = "ConvexSystemIdBuilder";
140
+ [entityKind] = "ConvexSystemIdBuilder";
141
+ constructor() {
142
+ super("_id", "string", "ConvexSystemId");
143
+ this.config.notNull = true;
144
+ }
145
+ build() {
146
+ return v.string();
147
+ }
148
+ /**
149
+ * Convex validator - runtime access
150
+ * System fields use v.string() for _id
151
+ */
152
+ get convexValidator() {
153
+ return this.build();
154
+ }
155
+ };
156
+ var ConvexSystemCreationTimeBuilder = class extends ColumnBuilder {
157
+ static [entityKind] = "ConvexSystemCreationTimeBuilder";
158
+ [entityKind] = "ConvexSystemCreationTimeBuilder";
159
+ constructor() {
160
+ super("_creationTime", "number", "ConvexSystemCreationTime");
161
+ this.config.notNull = true;
162
+ }
163
+ build() {
164
+ return v.number();
165
+ }
166
+ /**
167
+ * Convex validator - runtime access
168
+ * System fields use v.number() for _creationTime
169
+ */
170
+ get convexValidator() {
171
+ return this.build();
172
+ }
173
+ };
174
+ var ConvexSystemCreatedAtBuilder = class extends ColumnBuilder {
175
+ static [entityKind] = "ConvexSystemCreatedAtBuilder";
176
+ [entityKind] = "ConvexSystemCreatedAtBuilder";
177
+ constructor() {
178
+ super("_creationTime", "number", "ConvexSystemCreatedAt");
179
+ this.config.notNull = true;
180
+ }
181
+ build() {
182
+ return v.number();
183
+ }
184
+ get convexValidator() {
185
+ return this.build();
186
+ }
187
+ };
188
+ function createSystemFields(tableName) {
189
+ const id = new ConvexSystemIdBuilder();
190
+ const creationTime = new ConvexSystemCreationTimeBuilder();
191
+ const createdAt = new ConvexSystemCreatedAtBuilder();
192
+ id.config.tableName = tableName;
193
+ creationTime.config.tableName = tableName;
194
+ createdAt.config.tableName = tableName;
195
+ return {
196
+ id,
197
+ _creationTime: creationTime,
198
+ createdAt
199
+ };
200
+ }
201
+
202
+ //#endregion
203
+ //#region src/orm/symbols.ts
204
+ const TableName = Symbol.for("kitcn:TableName");
205
+ const Columns = Symbol.for("kitcn:Columns");
206
+ const Brand = Symbol.for("kitcn:Brand");
207
+ const Relations = Symbol.for("kitcn:Relations");
208
+ const OrmContext = Symbol.for("kitcn:OrmContext");
209
+ const RlsPolicies = Symbol.for("kitcn:RlsPolicies");
210
+ const EnableRLS = Symbol.for("kitcn:EnableRLS");
211
+ const TableDeleteConfig = Symbol.for("kitcn:TableDeleteConfig");
212
+ const TablePolymorphic = Symbol.for("kitcn:TablePolymorphic");
213
+ const OrmSchemaOptions = Symbol.for("kitcn:OrmSchemaOptions");
214
+ const OrmSchemaDefinition = Symbol.for("kitcn:OrmSchemaDefinition");
215
+ const OrmSchemaExtensionTables = Symbol.for("kitcn:OrmSchemaExtensionTables");
216
+ const OrmSchemaExtensions = Symbol.for("kitcn:OrmSchemaExtensions");
217
+ const OrmSchemaExtensionRelations = Symbol.for("kitcn:OrmSchemaExtensionRelations");
218
+ const OrmSchemaExtensionTriggers = Symbol.for("kitcn:OrmSchemaExtensionTriggers");
219
+ const OrmSchemaRelations = Symbol.for("kitcn:OrmSchemaRelations");
220
+ const OrmSchemaTriggers = Symbol.for("kitcn:OrmSchemaTriggers");
221
+
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
+ };
233
+
234
+ //#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;
259
+ }
260
+ };
261
+ function boolean$1(name) {
262
+ return new ConvexBooleanBuilder(name ?? "");
263
+ }
264
+
265
+ //#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());
289
+ /**
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()
298
+ *
299
+ * // Already required validators are returned as-is
300
+ * const alreadyRequired = v.string();
301
+ * const stillRequired = vRequired(alreadyRequired); // v.string()
302
+ * ```
303
+ *
304
+ * @param validator The validator to make required.
305
+ * @returns A required version of the validator.
306
+ */
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
+ }
327
+
328
+ //#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
+ }
412
+
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
+ }
441
+ };
442
+ function id(tableName) {
443
+ return new ConvexIdBuilder("", tableName);
444
+ }
445
+
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
+ }
472
+ };
473
+ function integer(name) {
474
+ return new ConvexNumberBuilder(name ?? "");
475
+ }
476
+
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
+ }
503
+ };
504
+ function text(name) {
505
+ return new ConvexTextBuilder(name ?? "");
506
+ }
507
+
508
+ //#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
+ });
558
+ }
559
+
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
+ }
572
+ };
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;
592
+ }
593
+ };
594
+ var ConvexSearchIndexBuilderOn = class {
595
+ static [entityKind] = "ConvexSearchIndexBuilderOn";
596
+ [entityKind] = "ConvexSearchIndexBuilderOn";
597
+ constructor(name) {
598
+ this.name = name;
599
+ }
600
+ on(searchField) {
601
+ return new ConvexSearchIndexBuilder(this.name, searchField);
602
+ }
603
+ };
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;
619
+ }
620
+ staged() {
621
+ this.config.staged = true;
622
+ return this;
623
+ }
624
+ };
625
+ var ConvexVectorIndexBuilderOn = class {
626
+ static [entityKind] = "ConvexVectorIndexBuilderOn";
627
+ [entityKind] = "ConvexVectorIndexBuilderOn";
628
+ constructor(name) {
629
+ this.name = name;
630
+ }
631
+ on(vectorField) {
632
+ return new ConvexVectorIndexBuilder(this.name, vectorField);
633
+ }
634
+ };
635
+ var ConvexAggregateIndexBuilderOn = class {
636
+ static [entityKind] = "ConvexAggregateIndexBuilderOn";
637
+ [entityKind] = "ConvexAggregateIndexBuilderOn";
638
+ constructor(name) {
639
+ this.name = name;
640
+ }
641
+ on(...columns) {
642
+ return new ConvexAggregateIndexBuilder(this.name, columns);
643
+ }
644
+ all() {
645
+ return new ConvexAggregateIndexBuilder(this.name, []);
646
+ }
647
+ };
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";
794
+ }
795
+
796
+ //#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
+ }
820
+ /**
821
+ * Create a Convex object validator from column builders
822
+ *
823
+ * Extracts .convexValidator from each column and creates v.object({...})
824
+ * This is the core factory that bridges ORM columns to Convex validators.
825
+ *
826
+ * @param columns - Record of column name to column builder
827
+ * @returns Convex object validator
828
+ */
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;
838
+ }
839
+ };
840
+ function isConvexIndexBuilder(value) {
841
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexIndexBuilder";
842
+ }
843
+ function isConvexIndexBuilderOn(value) {
844
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexIndexBuilderOn";
845
+ }
846
+ function isConvexAggregateIndexBuilder(value) {
847
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexAggregateIndexBuilder";
848
+ }
849
+ function isConvexAggregateIndexBuilderOn(value) {
850
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexAggregateIndexBuilderOn";
851
+ }
852
+ function isConvexRankIndexBuilderOn(value) {
853
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexRankIndexBuilderOn";
854
+ }
855
+ function isConvexRankIndexBuilder(value) {
856
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexRankIndexBuilder";
857
+ }
858
+ function isConvexUniqueConstraintBuilderOn(value) {
859
+ return typeof value === "object" && value !== null && value[entityKind] === "ConvexUniqueConstraintBuilderOn";
860
+ }
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;
892
+ }
893
+ function getColumnType(column) {
894
+ return column.config?.columnType;
895
+ }
896
+ function getColumnDimensions(column) {
897
+ return column.config?.dimensions;
898
+ }
899
+ function getColumnTableName(column) {
900
+ const config = column.config;
901
+ return config?.tableName ?? config?.referenceTable;
902
+ }
903
+ function getColumnTable(column) {
904
+ return column.config?.table;
905
+ }
906
+ function getUniqueIndexName(tableName, fields, explicitName) {
907
+ if (explicitName) return explicitName;
908
+ return `${tableName}_${fields.join("_")}_unique`;
909
+ }
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);
914
+ }
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'.`);
917
+ }
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}'.`);
921
+ }
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}'.`);
925
+ }
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}'.`);
929
+ }
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}'.`);
933
+ }
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;
961
+ };
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
+ });
968
+ };
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
+ });
985
+ }
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);
1014
+ }
1015
+ variantRuntime[variantKey] = {
1016
+ fieldNames,
1017
+ requiredFieldNames
1018
+ };
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
+ });
1027
+ }
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;
1210
+ }
1211
+ throw new Error(`Unsupported extra config value in convexTable('${table.tableName}').`);
1212
+ }
1213
+ }
1214
+ /**
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)
1222
+ */
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
+ });
1329
+ }
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
1337
+ });
1338
+ }
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);
1414
+ }
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}`);
1425
+ }
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],
1434
+ foreignTableName,
1435
+ foreignTable,
1436
+ foreignColumns: [foreignColumnName],
1437
+ onDelete: deferred.config.onDelete,
1438
+ onUpdate: deferred.config.onUpdate
1439
+ });
1440
+ }
1441
+ this.deferredForeignKeys = [];
1442
+ }
1443
+ /**
1444
+ * Internal: expose foreign key metadata for mutation enforcement
1445
+ */
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
+ }
1459
+ /**
1460
+ * Internal: add search index to table from builder extraConfig
1461
+ */
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
+ }
1471
+ /**
1472
+ * Internal: add vector index to table from builder extraConfig
1473
+ */
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
+ }
1484
+ /**
1485
+ * Export the contents of this definition for Convex schema tooling.
1486
+ * Mirrors convex/server TableDefinition.export().
1487
+ */
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) => [index("by_kind_table_index").on(t.kind, t.tableKey, t.indexName), index("by_kind_status").on(t.kind, t.status)]);
1594
+ const rankTreeTable = convexTable(AGGREGATE_RANK_TREE_TABLE, {
1595
+ aggregateName: text().notNull(),
1596
+ maxNodeSize: integer().notNull(),
1597
+ namespace: json(),
1598
+ root: id(AGGREGATE_RANK_NODE_TABLE).notNull()
1599
+ }, (tree) => [index("by_namespace").on(tree.namespace), index("by_aggregate_name").on(tree.aggregateName)]);
1600
+ const rankNodeTable = convexTable(AGGREGATE_RANK_NODE_TABLE, {
1601
+ aggregate: objectOf({
1602
+ count: integer().notNull(),
1603
+ sum: integer().notNull()
1604
+ }),
1605
+ items: arrayOf(objectOf({
1606
+ k: json(),
1607
+ v: json(),
1608
+ s: integer().notNull()
1609
+ })).notNull(),
1610
+ subtrees: arrayOf(text().notNull()).notNull()
1611
+ });
1612
+ const aggregateStorageTables = {
1613
+ [AGGREGATE_BUCKET_TABLE]: countBucketTable,
1614
+ [AGGREGATE_MEMBER_TABLE]: countMemberTable,
1615
+ [AGGREGATE_EXTREMA_TABLE]: countExtremaTable,
1616
+ [AGGREGATE_RANK_TREE_TABLE]: rankTreeTable,
1617
+ [AGGREGATE_RANK_NODE_TABLE]: rankNodeTable,
1618
+ [AGGREGATE_STATE_TABLE]: countStateTable
1619
+ };
1620
+ function aggregateExtension() {
1621
+ return defineSchemaExtension("aggregate", aggregateStorageTables);
1622
+ }
1623
+
1624
+ //#endregion
1625
+ //#region src/orm/migrations/schema.ts
1626
+ const MIGRATION_STATE_TABLE = "migration_state";
1627
+ const MIGRATION_RUN_TABLE = "migration_run";
1628
+ const migrationStateTable = convexTable(MIGRATION_STATE_TABLE, {
1629
+ migrationId: text().notNull(),
1630
+ checksum: text().notNull(),
1631
+ applied: boolean$1().notNull(),
1632
+ status: text().notNull(),
1633
+ direction: text(),
1634
+ runId: text(),
1635
+ cursor: text(),
1636
+ processed: integer().notNull(),
1637
+ startedAt: integer(),
1638
+ updatedAt: integer().notNull(),
1639
+ completedAt: integer(),
1640
+ lastError: text(),
1641
+ writeMode: text().notNull()
1642
+ }, (t) => [index("by_migration_id").on(t.migrationId), index("by_status").on(t.status)]);
1643
+ const migrationRunTable = convexTable(MIGRATION_RUN_TABLE, {
1644
+ runId: text().notNull(),
1645
+ direction: text().notNull(),
1646
+ status: text().notNull(),
1647
+ dryRun: boolean$1().notNull(),
1648
+ allowDrift: boolean$1().notNull(),
1649
+ migrationIds: custom(v.array(v.string())).notNull(),
1650
+ currentIndex: integer().notNull(),
1651
+ startedAt: integer().notNull(),
1652
+ updatedAt: integer().notNull(),
1653
+ completedAt: integer(),
1654
+ cancelRequested: boolean$1().notNull(),
1655
+ lastError: text()
1656
+ }, (t) => [index("by_run_id").on(t.runId), index("by_status").on(t.status)]);
1657
+ const migrationStorageTables = {
1658
+ [MIGRATION_STATE_TABLE]: migrationStateTable,
1659
+ [MIGRATION_RUN_TABLE]: migrationRunTable
1660
+ };
1661
+ function migrationExtension() {
1662
+ return defineSchemaExtension("migration", migrationStorageTables);
1663
+ }
1664
+
1665
+ //#endregion
1666
+ //#region src/orm/relations.ts
1667
+ var RelationsBuilderTable = class {
1668
+ static [entityKind] = "RelationsBuilderTable";
1669
+ _;
1670
+ constructor(table, name) {
1671
+ this._ = {
1672
+ name,
1673
+ table
1674
+ };
1675
+ }
1676
+ };
1677
+ var RelationsBuilderColumn = class {
1678
+ static [entityKind] = "RelationsBuilderColumn";
1679
+ _;
1680
+ constructor(column, tableName, key) {
1681
+ this._ = {
1682
+ tableName,
1683
+ column,
1684
+ key
1685
+ };
1686
+ }
1687
+ through(column) {
1688
+ return new RelationsBuilderJunctionColumn(this._.column, this._.tableName, this._.key, column);
1689
+ }
1690
+ };
1691
+ var RelationsBuilderJunctionColumn = class {
1692
+ static [entityKind] = "RelationsBuilderColumn";
1693
+ _;
1694
+ constructor(column, tableName, key, through) {
1695
+ this._ = {
1696
+ tableName,
1697
+ column,
1698
+ through,
1699
+ key
1700
+ };
1701
+ }
1702
+ };
1703
+ var RelationsHelperStatic = class {
1704
+ static [entityKind] = "RelationsHelperStatic";
1705
+ constructor(tables) {
1706
+ const one = {};
1707
+ const many = {};
1708
+ for (const [tableName, table] of Object.entries(tables)) {
1709
+ one[tableName] = (config) => new One(tables, table, tableName, config);
1710
+ many[tableName] = (config) => new Many(tables, table, tableName, config);
1711
+ }
1712
+ this.one = one;
1713
+ this.many = many;
1714
+ }
1715
+ one;
1716
+ many;
1717
+ };
1718
+ function createRelationsHelper(tables) {
1719
+ const helperStatic = new RelationsHelperStatic(tables);
1720
+ const relationsTables = Object.entries(tables).reduce((acc, [tKey, value]) => {
1721
+ const rTable = new RelationsBuilderTable(value, tKey);
1722
+ const columns = Object.entries(getTableColumns(value)).reduce((colsAcc, [cKey, column]) => {
1723
+ colsAcc[cKey] = new RelationsBuilderColumn(column, tKey, cKey);
1724
+ return colsAcc;
1725
+ }, {});
1726
+ const relationsTable = Object.assign(rTable, columns);
1727
+ Object.defineProperty(relationsTable, "_id", {
1728
+ get() {
1729
+ throw new Error("`_id` is no longer public in relations. Use `id`.");
1730
+ },
1731
+ enumerable: false,
1732
+ configurable: false
1733
+ });
1734
+ acc[tKey] = relationsTable;
1735
+ return acc;
1736
+ }, {});
1737
+ return Object.assign(helperStatic, relationsTables);
1738
+ }
1739
+ function extractTablesFromSchema(schema) {
1740
+ 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;
1741
+ return Object.fromEntries(Object.entries(schemaTables).filter(([_, e]) => isConvexTable(e)));
1742
+ }
1743
+ var Relation = class {
1744
+ static [entityKind] = "RelationV2";
1745
+ fieldName;
1746
+ sourceColumns;
1747
+ targetColumns;
1748
+ alias;
1749
+ where;
1750
+ sourceTable;
1751
+ targetTable;
1752
+ through;
1753
+ throughTable;
1754
+ isReversed;
1755
+ /** @internal */
1756
+ sourceColumnTableNames = [];
1757
+ /** @internal */
1758
+ targetColumnTableNames = [];
1759
+ constructor(targetTable, targetTableName) {
1760
+ this.targetTableName = targetTableName;
1761
+ this.targetTable = targetTable;
1762
+ }
1763
+ };
1764
+ var One = class extends Relation {
1765
+ static [entityKind] = "OneV2";
1766
+ relationType = "one";
1767
+ optional;
1768
+ constructor(tables, targetTable, targetTableName, config) {
1769
+ super(targetTable, targetTableName);
1770
+ this.alias = config?.alias;
1771
+ this.where = config?.where;
1772
+ if (config?.from) this.sourceColumns = (Array.isArray(config.from) ? config.from : [config.from]).map((it) => {
1773
+ this.throughTable ??= it._.through ? tables[it._.through._.tableName] : void 0;
1774
+ this.sourceColumnTableNames.push(it._.tableName);
1775
+ return it._.column;
1776
+ });
1777
+ if (config?.to) this.targetColumns = (Array.isArray(config.to) ? config.to : [config.to]).map((it) => {
1778
+ this.throughTable ??= it._.through ? tables[it._.through._.tableName] : void 0;
1779
+ this.targetColumnTableNames.push(it._.tableName);
1780
+ return it._.column;
1781
+ });
1782
+ if (this.throughTable) this.through = {
1783
+ source: (Array.isArray(config?.from) ? config.from : config?.from ? [config.from] : []).map((c) => c._.through),
1784
+ target: (Array.isArray(config?.to) ? config.to : config?.to ? [config.to] : []).map((c) => c._.through)
1785
+ };
1786
+ this.optional = config?.optional ?? true;
1787
+ }
1788
+ };
1789
+ var Many = class extends Relation {
1790
+ static [entityKind] = "ManyV2";
1791
+ relationType = "many";
1792
+ constructor(tables, targetTable, targetTableName, config) {
1793
+ super(targetTable, targetTableName);
1794
+ this.config = config;
1795
+ this.alias = config?.alias;
1796
+ this.where = config?.where;
1797
+ if (config?.from) this.sourceColumns = (Array.isArray(config.from) ? config.from : [config.from]).map((it) => {
1798
+ this.throughTable ??= it._.through ? tables[it._.through._.tableName] : void 0;
1799
+ this.sourceColumnTableNames.push(it._.tableName);
1800
+ return it._.column;
1801
+ });
1802
+ if (config?.to) this.targetColumns = (Array.isArray(config.to) ? config.to : [config.to]).map((it) => {
1803
+ this.throughTable ??= it._.through ? tables[it._.through._.tableName] : void 0;
1804
+ this.targetColumnTableNames.push(it._.tableName);
1805
+ return it._.column;
1806
+ });
1807
+ if (this.throughTable) this.through = {
1808
+ source: (Array.isArray(config?.from) ? config.from : config?.from ? [config.from] : []).map((c) => c._.through),
1809
+ target: (Array.isArray(config?.to) ? config.to : config?.to ? [config.to] : []).map((c) => c._.through)
1810
+ };
1811
+ }
1812
+ };
1813
+ function buildRelations(tables, config, strict, defaults) {
1814
+ const tablesConfig = {};
1815
+ for (const [tsName, table] of Object.entries(tables)) tablesConfig[tsName] = {
1816
+ table,
1817
+ name: tsName,
1818
+ polymorphic: table[TablePolymorphic],
1819
+ relations: config[tsName] ?? {},
1820
+ strict,
1821
+ defaults
1822
+ };
1823
+ return processRelations(tablesConfig, tables);
1824
+ }
1825
+ function defineRelations(schema, relations) {
1826
+ const tables = extractTablesFromSchema(schema);
1827
+ const schemaOptions = schema[OrmSchemaOptions];
1828
+ const strict = schemaOptions?.strict ?? true;
1829
+ const defaults = schemaOptions?.defaults;
1830
+ const schemaDefinition = schema[OrmSchemaDefinition];
1831
+ const pluginTableNames = schema[OrmSchemaExtensionTables];
1832
+ const plugins = schema[OrmSchemaExtensions];
1833
+ const tablesConfig = buildRelations(tables, relations ? relations(createRelationsHelper(tables)) : {}, strict, defaults);
1834
+ Object.defineProperty(tablesConfig, OrmSchemaOptions, {
1835
+ value: {
1836
+ strict,
1837
+ defaults
1838
+ },
1839
+ enumerable: false
1840
+ });
1841
+ if (schemaDefinition) Object.defineProperty(tablesConfig, OrmSchemaDefinition, {
1842
+ value: schemaDefinition,
1843
+ enumerable: false
1844
+ });
1845
+ if (pluginTableNames) Object.defineProperty(tablesConfig, OrmSchemaExtensionTables, {
1846
+ value: pluginTableNames,
1847
+ enumerable: false
1848
+ });
1849
+ if (plugins) Object.defineProperty(tablesConfig, OrmSchemaExtensions, {
1850
+ value: plugins,
1851
+ enumerable: false
1852
+ });
1853
+ return tablesConfig;
1854
+ }
1855
+ function processRelations(tablesConfig, tables) {
1856
+ for (const tableConfig of Object.values(tablesConfig)) for (const [relationFieldName, relation] of Object.entries(tableConfig.relations)) {
1857
+ if (!isRelation(relation)) continue;
1858
+ relation.sourceTable = tableConfig.table;
1859
+ relation.fieldName = relationFieldName;
1860
+ }
1861
+ for (const [sourceTableName, tableConfig] of Object.entries(tablesConfig)) for (const [relationFieldName, relation] of Object.entries(tableConfig.relations)) {
1862
+ if (!isRelation(relation)) continue;
1863
+ let reverseRelation;
1864
+ const { targetTableName, alias, sourceColumns, targetColumns, throughTable, sourceTable, through, where, sourceColumnTableNames, targetColumnTableNames } = relation;
1865
+ const relationPrintName = `relations -> ${tableConfig.name}: { ${relationFieldName}: r.${relation.relationType === "one" ? "one" : "many"}.${targetTableName}(...) }`;
1866
+ if (relationFieldName in getTableColumns(tableConfig.table)) throw new Error(`${relationPrintName}: relation name collides with column "${relationFieldName}" of table "${tableConfig.name}"`);
1867
+ if (typeof alias === "string" && !alias) throw new Error(`${relationPrintName}: "alias" cannot be empty`);
1868
+ if (sourceColumns?.length === 0) throw new Error(`${relationPrintName}: "from" cannot be empty`);
1869
+ if (targetColumns?.length === 0) throw new Error(`${relationPrintName}: "to" cannot be empty`);
1870
+ if (sourceColumns && targetColumns) {
1871
+ if (sourceColumns.length !== targetColumns.length && !throughTable) throw new Error(`${relationPrintName}: "from" and "to" must have same length`);
1872
+ for (const sName of sourceColumnTableNames) if (sName !== sourceTableName) throw new Error(`${relationPrintName}: all "from" columns must belong to table "${sourceTableName}", found "${sName}"`);
1873
+ for (const tName of targetColumnTableNames) if (tName !== targetTableName) throw new Error(`${relationPrintName}: all "to" columns must belong to table "${targetTableName}", found "${tName}"`);
1874
+ if (through) {
1875
+ 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`);
1876
+ for (const column of through.source) if (tables[column._.tableName] !== throughTable) throw new Error(`${relationPrintName}: .through() must use same table for all columns`);
1877
+ for (const column of through.target) if (tables[column._.tableName] !== throughTable) throw new Error(`${relationPrintName}: .through() must use same table for all columns`);
1878
+ }
1879
+ continue;
1880
+ }
1881
+ if (sourceColumns || targetColumns) throw new Error(`${relationPrintName}: relation must have both "from" and "to" or none`);
1882
+ const reverseTableConfig = tablesConfig[targetTableName];
1883
+ if (!reverseTableConfig) throw new Error(`${relationPrintName}: missing "from"/"to" and no reverse relation found for "${targetTableName}"`);
1884
+ if (alias) {
1885
+ const reverseRelations = Object.values(reverseTableConfig.relations).filter((it) => isRelation(it) && it.alias === alias && it !== relation);
1886
+ if (reverseRelations.length > 1) throw new Error(`${relationPrintName}: multiple reverse relations with alias "${alias}" found`);
1887
+ reverseRelation = reverseRelations[0];
1888
+ if (!reverseRelation) throw new Error(`${relationPrintName}: no reverse relation with alias "${alias}" found in "${targetTableName}"`);
1889
+ } else {
1890
+ const reverseRelations = Object.values(reverseTableConfig.relations).filter((it) => isRelation(it) && it.targetTable === sourceTable && !it.alias && it !== relation);
1891
+ if (reverseRelations.length > 1) throw new Error(`${relationPrintName}: multiple relations between "${targetTableName}" and "${sourceTableName}"; use alias`);
1892
+ reverseRelation = reverseRelations[0];
1893
+ if (!reverseRelation) throw new Error(`${relationPrintName}: no reverse relation between "${targetTableName}" and "${sourceTableName}"`);
1894
+ }
1895
+ if (!reverseRelation.sourceColumns || !reverseRelation.targetColumns) throw new Error(`${relationPrintName}: reverse relation "${targetTableName}.${reverseRelation.fieldName}" missing "from"/"to"`);
1896
+ relation.sourceColumns = reverseRelation.targetColumns;
1897
+ relation.targetColumns = reverseRelation.sourceColumns;
1898
+ relation.through = reverseRelation.through ? {
1899
+ source: reverseRelation.through.target,
1900
+ target: reverseRelation.through.source
1901
+ } : void 0;
1902
+ relation.throughTable = reverseRelation.throughTable;
1903
+ relation.isReversed = !where;
1904
+ relation.where = where ?? reverseRelation.where;
1905
+ }
1906
+ return tablesConfig;
1907
+ }
1908
+ function isConvexTable(value) {
1909
+ return typeof value === "object" && value !== null && "tableName" in value && Columns in value;
1910
+ }
1911
+ function isRelation(value) {
1912
+ return value instanceof Relation;
1913
+ }
1914
+ function getTableColumns(table) {
1915
+ const columns = table[Columns];
1916
+ const system = {};
1917
+ if (table.id) system.id = table.id;
1918
+ if (table.createdAt || table._creationTime) system.createdAt = table._creationTime ?? table.createdAt;
1919
+ return {
1920
+ ...columns,
1921
+ ...system
1922
+ };
1923
+ }
1924
+
1925
+ //#endregion
1926
+ //#region src/orm/triggers.ts
1927
+ function defineTriggers(schema, triggers) {
1928
+ return triggers;
1929
+ }
1930
+
1931
+ //#endregion
1932
+ //#region src/orm/schema.ts
1933
+ const BUILTIN_SCHEMA_EXTENSIONS = [aggregateExtension(), migrationExtension()];
1934
+ function resolveSchemaExtensions(extensions) {
1935
+ const resolved = [...BUILTIN_SCHEMA_EXTENSIONS, ...extensions ?? []];
1936
+ const seen = /* @__PURE__ */ new Set();
1937
+ for (const extension of resolved) {
1938
+ if (seen.has(extension.key)) throw new Error(`defineSchema received duplicate extension '${extension.key}'. Remove duplicate extension registrations.`);
1939
+ seen.add(extension.key);
1940
+ }
1941
+ return resolved;
1942
+ }
1943
+ function mergeRelationConfigs(sources) {
1944
+ const merged = {};
1945
+ const relationOrigins = /* @__PURE__ */ new Map();
1946
+ for (const { source, config } of sources) for (const [tableName, relationConfig] of Object.entries(config)) {
1947
+ if (!relationConfig) continue;
1948
+ let tableRelations = merged[tableName];
1949
+ if (!tableRelations) {
1950
+ tableRelations = Object.create(null);
1951
+ merged[tableName] = tableRelations;
1952
+ }
1953
+ for (const [fieldName, relation] of Object.entries(relationConfig)) {
1954
+ const relationKey = `${tableName}.${fieldName}`;
1955
+ const existingSource = relationOrigins.get(relationKey);
1956
+ if (existingSource) throw new Error(`defineSchema relation field '${relationKey}' is defined more than once (${existingSource} and ${source}).`);
1957
+ tableRelations[fieldName] = relation;
1958
+ relationOrigins.set(relationKey, source);
1959
+ }
1960
+ }
1961
+ return merged;
1962
+ }
1963
+ function mergeTriggerConfigs(sources) {
1964
+ const merged = {};
1965
+ const triggerOrigins = /* @__PURE__ */ new Map();
1966
+ for (const { source, config } of sources) for (const [tableName, tableConfig] of Object.entries(config)) {
1967
+ if (!tableConfig || typeof tableConfig !== "object" || Array.isArray(tableConfig)) {
1968
+ const triggerKey = `${tableName}`;
1969
+ const existingSource = triggerOrigins.get(triggerKey);
1970
+ if (existingSource) throw new Error(`defineSchema trigger '${triggerKey}' is defined more than once (${existingSource} and ${source}).`);
1971
+ merged[tableName] = tableConfig;
1972
+ triggerOrigins.set(triggerKey, source);
1973
+ continue;
1974
+ }
1975
+ let mergedTable = merged[tableName];
1976
+ if (!mergedTable) {
1977
+ mergedTable = Object.create(null);
1978
+ merged[tableName] = mergedTable;
1979
+ }
1980
+ for (const [hookKey, hookValue] of Object.entries(tableConfig)) {
1981
+ if ((hookKey === "create" || hookKey === "update" || hookKey === "delete") && hookValue && typeof hookValue === "object" && !Array.isArray(hookValue)) {
1982
+ let mergedOperation = mergedTable[hookKey];
1983
+ if (!mergedOperation || typeof mergedOperation !== "object") {
1984
+ mergedOperation = Object.create(null);
1985
+ mergedTable[hookKey] = mergedOperation;
1986
+ }
1987
+ for (const [operationHookKey, operationHookValue] of Object.entries(hookValue)) {
1988
+ const triggerKey = `${tableName}.${hookKey}.${operationHookKey}`;
1989
+ const existingSource = triggerOrigins.get(triggerKey);
1990
+ if (existingSource) throw new Error(`defineSchema trigger '${triggerKey}' is defined more than once (${existingSource} and ${source}).`);
1991
+ mergedOperation[operationHookKey] = operationHookValue;
1992
+ triggerOrigins.set(triggerKey, source);
1993
+ }
1994
+ continue;
1995
+ }
1996
+ const triggerKey = `${tableName}.${hookKey}`;
1997
+ const existingSource = triggerOrigins.get(triggerKey);
1998
+ if (existingSource) throw new Error(`defineSchema trigger '${triggerKey}' is defined more than once (${existingSource} and ${source}).`);
1999
+ mergedTable[hookKey] = hookValue;
2000
+ triggerOrigins.set(triggerKey, source);
2001
+ }
2002
+ }
2003
+ return merged;
2004
+ }
2005
+ function defineMetadata(target, key, value) {
2006
+ const existing = Object.getOwnPropertyDescriptor(target, key);
2007
+ if (existing && !existing.configurable) return;
2008
+ Object.defineProperty(target, key, {
2009
+ value,
2010
+ enumerable: false,
2011
+ configurable: true
2012
+ });
2013
+ }
2014
+ const OrmSchemaComposerState = Symbol("kitcn:OrmSchemaComposerState");
2015
+ function getSchemaComposerState(schema) {
2016
+ if (!schema || typeof schema !== "object") return;
2017
+ return schema[OrmSchemaComposerState];
2018
+ }
2019
+ function finalizeSchemaMetadata(schema) {
2020
+ if (!schema || typeof schema !== "object") return;
2021
+ const composerState = getSchemaComposerState(schema);
2022
+ if (!composerState) return;
2023
+ const schemaObject = schema;
2024
+ const resolvedExtensions = schemaObject[OrmSchemaExtensions] ?? resolveSchemaExtensions(composerState.extensions);
2025
+ const extensionTableNames = schemaObject[OrmSchemaExtensionTables] ?? Object.freeze([]);
2026
+ const options = schemaObject[OrmSchemaOptions];
2027
+ const extensionRelationFactories = resolvedExtensions.map((extension) => ({
2028
+ key: extension.key,
2029
+ relations: extension[OrmSchemaExtensionRelations]
2030
+ })).filter((entry) => entry.relations !== void 0);
2031
+ const extensionTriggerFactories = resolvedExtensions.map((extension) => ({
2032
+ key: extension.key,
2033
+ triggers: extension[OrmSchemaExtensionTriggers]
2034
+ })).filter((entry) => entry.triggers !== void 0);
2035
+ const hasExtensionRelations = extensionRelationFactories.length > 0;
2036
+ const hasExtensionTriggers = extensionTriggerFactories.length > 0;
2037
+ const shouldBuildRelations = hasExtensionRelations || hasExtensionTriggers || Boolean(composerState.relations) || Boolean(composerState.triggers);
2038
+ let relations = schemaObject[OrmSchemaRelations];
2039
+ if (!relations && shouldBuildRelations) {
2040
+ relations = hasExtensionRelations || composerState.relations ? defineRelations(schema, (helpers) => mergeRelationConfigs([...extensionRelationFactories.map((extension) => ({
2041
+ source: `extension '${extension.key}'`,
2042
+ config: extension.relations(helpers)
2043
+ })), ...composerState.relations ? [{
2044
+ source: "schema.relations()",
2045
+ config: composerState.relations(helpers)
2046
+ }] : []])) : defineRelations(schema);
2047
+ defineMetadata(relations, OrmSchemaDefinition, schema);
2048
+ if (options) defineMetadata(relations, OrmSchemaOptions, options);
2049
+ defineMetadata(relations, OrmSchemaExtensionTables, extensionTableNames);
2050
+ defineMetadata(relations, OrmSchemaExtensions, resolvedExtensions);
2051
+ defineMetadata(schema, OrmSchemaRelations, relations);
2052
+ }
2053
+ if (!relations || schemaObject[OrmSchemaTriggers]) return;
2054
+ const triggerSources = hasExtensionTriggers || composerState.triggers ? [...extensionTriggerFactories.map((extension) => ({
2055
+ source: `extension '${extension.key}'`,
2056
+ config: typeof extension.triggers === "function" ? extension.triggers(relations) : extension.triggers
2057
+ })), ...composerState.triggers ? [{
2058
+ source: "schema.triggers()",
2059
+ config: composerState.triggers
2060
+ }] : []] : [];
2061
+ if (triggerSources.length === 0) return;
2062
+ defineMetadata(schema, OrmSchemaTriggers, defineTriggers(relations, mergeTriggerConfigs(triggerSources)));
2063
+ }
2064
+ function getSchemaRelations(schema) {
2065
+ if (!schema || typeof schema !== "object") return;
2066
+ finalizeSchemaMetadata(schema);
2067
+ return schema[OrmSchemaRelations];
2068
+ }
2069
+ function getSchemaTriggers(schema) {
2070
+ if (!schema || typeof schema !== "object") return;
2071
+ finalizeSchemaMetadata(schema);
2072
+ return schema[OrmSchemaTriggers];
2073
+ }
2074
+
2075
+ //#endregion
2076
+ //#region src/cli/utils/highlighter.ts
2077
+ const isTruthyEnvFlag = (value) => value !== void 0 && value !== "" && value !== "0";
2078
+ const isColorEnabled = () => {
2079
+ if (process.env.FORCE_COLOR === "0") return false;
2080
+ if (isTruthyEnvFlag(process.env.FORCE_COLOR)) return true;
2081
+ if (isTruthyEnvFlag(process.env.NO_COLOR)) return false;
2082
+ return Boolean(process.stdout.isTTY && process.env.TERM !== "dumb");
2083
+ };
2084
+ const withColors = (callback) => callback(createColors(isColorEnabled()));
2085
+ const highlighter = {
2086
+ bold(value) {
2087
+ return withColors((colors) => colors.bold(value));
2088
+ },
2089
+ dim(value) {
2090
+ return withColors((colors) => colors.dim(value));
2091
+ },
2092
+ info(value) {
2093
+ return withColors((colors) => colors.cyan(value));
2094
+ },
2095
+ success(value) {
2096
+ return withColors((colors) => colors.green(value));
2097
+ },
2098
+ warn(value) {
2099
+ return withColors((colors) => colors.yellow(value));
2100
+ },
2101
+ error(value) {
2102
+ return withColors((colors) => colors.red(value));
2103
+ },
2104
+ path(value) {
2105
+ return withColors((colors) => colors.bold(value));
2106
+ }
2107
+ };
2108
+
2109
+ //#endregion
2110
+ //#region src/cli/utils/lazy-deps.ts
2111
+ /**
2112
+ * Heavy CLI-only dependencies, resolved on first use instead of at module init.
2113
+ *
2114
+ * `kitcn --version`, `kitcn --help` and every `--json` call an agent makes must
2115
+ * not pay to boot esbuild's native service, Babel's parser, jiti, dotenv or the
2116
+ * interactive prompt stack — the CLI is non-interactive by default and most
2117
+ * commands touch none of them.
2118
+ *
2119
+ * Each accessor is memoized and stays synchronous so call sites keep their
2120
+ * current signatures, and each dependency is loaded through exactly one
2121
+ * mechanism: `@clack/prompts` compares against a module-private `Symbol` in
2122
+ * `isCancel`, so a second copy of the module would silently break cancellation.
2123
+ */
2124
+ const require$1 = createRequire(import.meta.url);
2125
+ let babelParser;
2126
+ let clackPrompts;
2127
+ let dotenv;
2128
+ let esbuild;
2129
+ let jiti;
2130
+ const loadBabelParser = () => babelParser ??= require$1("@babel/parser");
2131
+ const loadClackPrompts = () => clackPrompts ??= require$1("@clack/prompts");
2132
+ const loadDotenv = () => dotenv ??= require$1("dotenv");
2133
+ const loadEsbuild = () => esbuild ??= require$1("esbuild");
2134
+ const loadJiti = () => jiti ??= require$1("jiti");
2135
+
2136
+ //#endregion
2137
+ //#region src/cli/utils/crpc-builder-stub.ts
2138
+ const CRPC_BUILDER_STUB_SOURCE = `const createMiddleware = (handler = undefined) => ({
2139
+ _handler: handler,
2140
+ pipe(nextHandler = undefined) {
2141
+ return createMiddleware(nextHandler);
2142
+ },
2143
+ });
2144
+
2145
+ const toMetaObject = (value = undefined) =>
2146
+ value && typeof value === "object" ? value : {};
2147
+
2148
+ const createProcedureExport = (type, state, handler) => ({
2149
+ _crpcMeta: {
2150
+ type,
2151
+ internal: state.internal ?? false,
2152
+ ...toMetaObject(state.meta),
2153
+ },
2154
+ ...(state.httpRoute ? { _crpcHttpRoute: state.httpRoute } : {}),
2155
+ _handler: handler,
2156
+ });
2157
+
2158
+ const createProcedureBuilder = (state = {}) => {
2159
+ const builder = {
2160
+ internal() {
2161
+ return createProcedureBuilder({ ...state, internal: true });
2162
+ },
2163
+ use() {
2164
+ return createProcedureBuilder(state);
2165
+ },
2166
+ meta(value = undefined) {
2167
+ return createProcedureBuilder({
2168
+ ...state,
2169
+ meta: {
2170
+ ...toMetaObject(state.meta),
2171
+ ...toMetaObject(value),
2172
+ },
2173
+ });
2174
+ },
2175
+ input() {
2176
+ return createProcedureBuilder(state);
2177
+ },
2178
+ params() {
2179
+ return createProcedureBuilder(state);
2180
+ },
2181
+ searchParams() {
2182
+ return createProcedureBuilder(state);
2183
+ },
2184
+ paginated(options = undefined) {
2185
+ return createProcedureBuilder({
2186
+ ...state,
2187
+ meta:
2188
+ typeof options?.limit === "number"
2189
+ ? {
2190
+ ...toMetaObject(state.meta),
2191
+ limit: options.limit,
2192
+ }
2193
+ : state.meta,
2194
+ });
2195
+ },
2196
+ output() {
2197
+ return createProcedureBuilder(state);
2198
+ },
2199
+ form() {
2200
+ return createProcedureBuilder(state);
2201
+ },
2202
+ route(path, method) {
2203
+ return createProcedureBuilder({
2204
+ ...state,
2205
+ httpRoute:
2206
+ typeof path === "string" && typeof method === "string"
2207
+ ? { path, method: method.toUpperCase() }
2208
+ : undefined,
2209
+ });
2210
+ },
2211
+ get(path) {
2212
+ return builder.route(path, "GET");
2213
+ },
2214
+ post(path) {
2215
+ return builder.route(path, "POST");
2216
+ },
2217
+ put(path) {
2218
+ return builder.route(path, "PUT");
2219
+ },
2220
+ patch(path) {
2221
+ return builder.route(path, "PATCH");
2222
+ },
2223
+ delete(path) {
2224
+ return builder.route(path, "DELETE");
2225
+ },
2226
+ query(handler = undefined) {
2227
+ return createProcedureExport("query", state, handler);
2228
+ },
2229
+ mutation(handler = undefined) {
2230
+ return createProcedureExport("mutation", state, handler);
2231
+ },
2232
+ action(handler = undefined) {
2233
+ return createProcedureExport("action", state, handler);
2234
+ },
2235
+ middleware(handler = undefined) {
2236
+ return createMiddleware(handler);
2237
+ },
2238
+ };
2239
+
2240
+ return builder;
2241
+ };
2242
+
2243
+ const flattenRouterRecord = (record = {}, prefix = "") => {
2244
+ const procedures = {};
2245
+ for (const [key, value] of Object.entries(record)) {
2246
+ const procedurePath = prefix ? prefix + "." + key : key;
2247
+ if (value?._def?.router === true) {
2248
+ Object.assign(
2249
+ procedures,
2250
+ flattenRouterRecord(value._def.record ?? {}, procedurePath)
2251
+ );
2252
+ continue;
2253
+ }
2254
+ procedures[procedurePath] = value;
2255
+ }
2256
+ return procedures;
2257
+ };
2258
+
2259
+ const createRouter = (record = {}) => ({
2260
+ _def: {
2261
+ router: true,
2262
+ procedures: flattenRouterRecord(record),
2263
+ record,
2264
+ },
2265
+ });
2266
+
2267
+ export const initCRPC = {
2268
+ meta() {
2269
+ return this;
2270
+ },
2271
+ dataModel() {
2272
+ return this;
2273
+ },
2274
+ context() {
2275
+ return this;
2276
+ },
2277
+ middleware(handler = undefined) {
2278
+ return createMiddleware(handler);
2279
+ },
2280
+ create() {
2281
+ return {
2282
+ query: createProcedureBuilder(),
2283
+ mutation: createProcedureBuilder(),
2284
+ action: createProcedureBuilder(),
2285
+ httpAction: createProcedureBuilder(),
2286
+ middleware: createMiddleware,
2287
+ router: createRouter,
2288
+ };
2289
+ },
2290
+ };
2291
+
2292
+ export const httpAction = createProcedureBuilder();
2293
+ `;
2294
+
2295
+ //#endregion
2296
+ //#region src/cli/utils/project-jiti.ts
2297
+ const require = createRequire(import.meta.url);
2298
+ const JITI_EXPORT_CONDITION_PRIORITY = [
2299
+ "bun",
2300
+ "import",
2301
+ "module",
2302
+ "default",
2303
+ "require"
2304
+ ];
2305
+ const SERVER_PARSER_SHIM_SOURCE = `${CRPC_BUILDER_STUB_SOURCE}
2306
+
2307
+ export class CRPCError extends Error {
2308
+ constructor(options = {}) {
2309
+ super(options.message ?? options.code ?? "CRPC error");
2310
+ this.code = options.code;
2311
+ }
2312
+ }
2313
+
2314
+ export const createEnv = ({ schema }) => () =>
2315
+ typeof schema?.parse === "function" ? schema.parse(process.env) : process.env;
2316
+ export const createHttpRouter = (_app, httpRouter) => httpRouter ?? {};
2317
+ export const createCallerFactory = () => () => ({});
2318
+ export const createApiLeaf = (fnOrRoot, pathOrMeta, maybeMeta) => {
2319
+ const meta = maybeMeta ?? pathOrMeta;
2320
+ const fn = Array.isArray(pathOrMeta)
2321
+ ? pathOrMeta.reduce((current, segment) => current?.[segment], fnOrRoot)
2322
+ : fnOrRoot;
2323
+ return Object.assign(fn ?? {}, meta ?? {}, { functionRef: fn });
2324
+ };
2325
+ export const createGeneratedFunctionReference = (name) => ({
2326
+ [Symbol.for("functionName")]: name,
2327
+ });
2328
+ export const registerProcedureNameLookup = () => {};
2329
+ export const typedProcedureResolver = (_functionRef, resolver) => resolver;
2330
+ export const createGeneratedRegistryRuntime = () => ({
2331
+ getCallerFactory() {
2332
+ return () => ({});
2333
+ },
2334
+ getHandlerFactory() {
2335
+ return () => ({});
2336
+ },
2337
+ });
2338
+ `;
2339
+ const resolveJitiExportTarget = (target) => {
2340
+ if (typeof target === "string") return target;
2341
+ if (Array.isArray(target)) {
2342
+ for (const entry of target) {
2343
+ const resolved = resolveJitiExportTarget(entry);
2344
+ if (resolved) return resolved;
2345
+ }
2346
+ return null;
2347
+ }
2348
+ if (!target || typeof target !== "object") return null;
2349
+ const record = target;
2350
+ for (const condition of JITI_EXPORT_CONDITION_PRIORITY) {
2351
+ const resolved = resolveJitiExportTarget(record[condition]);
2352
+ if (resolved) return resolved;
2353
+ }
2354
+ for (const value of Object.values(record)) {
2355
+ const resolved = resolveJitiExportTarget(value);
2356
+ if (resolved) return resolved;
2357
+ }
2358
+ return null;
2359
+ };
2360
+ const buildLocalPackageExportAliases = (cwd, packageName) => {
2361
+ const packageDir = path.join(cwd, "node_modules", ...packageName.split("/"));
2362
+ const packageJsonPath = path.join(packageDir, "package.json");
2363
+ if (!fs.existsSync(packageJsonPath)) return {};
2364
+ const exportsField = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")).exports;
2365
+ if (!exportsField || typeof exportsField !== "object" || Array.isArray(exportsField)) return {};
2366
+ const aliases = {};
2367
+ for (const [exportKey, exportTarget] of Object.entries(exportsField)) {
2368
+ const resolvedTarget = resolveJitiExportTarget(exportTarget);
2369
+ if (!resolvedTarget || !resolvedTarget.startsWith("./")) continue;
2370
+ const specifier = exportKey === "." ? packageName : exportKey.startsWith("./") ? `${packageName}${exportKey.slice(1)}` : null;
2371
+ if (!specifier) continue;
2372
+ aliases[specifier] = path.join(packageDir, resolvedTarget);
2373
+ }
2374
+ return aliases;
2375
+ };
2376
+ const ensureServerParserShim = (cwd) => {
2377
+ const shimDir = path.join(cwd, "node_modules", ".kitcn");
2378
+ const shimPath = path.join(shimDir, "project-jiti-server-shim.mjs");
2379
+ fs.mkdirSync(shimDir, { recursive: true });
2380
+ if (!fs.existsSync(shimPath) || fs.readFileSync(shimPath, "utf8") !== SERVER_PARSER_SHIM_SOURCE) fs.writeFileSync(shimPath, SERVER_PARSER_SHIM_SOURCE, "utf8");
2381
+ return shimPath;
2382
+ };
2383
+ const trimTsconfigWildcardSuffix = (value) => value.endsWith("/*") ? value.slice(0, -2) : value;
2384
+ const loadTypeScript = () => {
2385
+ try {
2386
+ return require("typescript");
2387
+ } catch {
2388
+ return null;
2389
+ }
2390
+ };
2391
+ const buildTsconfigPathAliases = (cwd) => {
2392
+ const typescript = loadTypeScript();
2393
+ if (!typescript) return {};
2394
+ const configPath = typescript.findConfigFile(cwd, fs.existsSync, "tsconfig.json");
2395
+ if (!configPath) return {};
2396
+ const readResult = typescript.readConfigFile(configPath, typescript.sys.readFile);
2397
+ if (readResult.error) return {};
2398
+ const parsedConfig = typescript.parseJsonConfigFileContent(readResult.config, typescript.sys, path.dirname(configPath));
2399
+ const baseUrl = typeof parsedConfig.options.baseUrl === "string" && parsedConfig.options.baseUrl.length > 0 ? parsedConfig.options.baseUrl : path.dirname(configPath);
2400
+ const paths = parsedConfig.options.paths;
2401
+ if (!paths) return {};
2402
+ const aliases = {};
2403
+ for (const [specifier, targets] of Object.entries(paths)) {
2404
+ const firstTarget = targets[0];
2405
+ if (!firstTarget) continue;
2406
+ const aliasKey = trimTsconfigWildcardSuffix(specifier);
2407
+ const aliasTarget = trimTsconfigWildcardSuffix(firstTarget);
2408
+ if (!aliasKey || aliasKey === "*") continue;
2409
+ aliases[aliasKey] = path.resolve(baseUrl, aliasTarget);
2410
+ }
2411
+ return aliases;
2412
+ };
2413
+ const getProjectServerParserShimPath = (cwd = process.cwd()) => ensureServerParserShim(cwd);
2414
+ const createProjectJiti = (cwd = process.cwd()) => loadJiti().createJiti(cwd, {
2415
+ interopDefault: true,
2416
+ jsx: { runtime: "automatic" },
2417
+ moduleCache: false,
2418
+ tryNative: false,
2419
+ alias: {
2420
+ ...buildTsconfigPathAliases(cwd),
2421
+ ...buildLocalPackageExportAliases(cwd, "kitcn"),
2422
+ ...buildLocalPackageExportAliases(cwd, "convex"),
2423
+ "kitcn/server": getProjectServerParserShimPath(cwd)
2424
+ }
2425
+ });
2426
+
2427
+ //#endregion
2428
+ //#region src/shared/meta-utils.ts
2429
+ /**
2430
+ * Suffix of a parse snapshot — a mirror of a Convex module that kitcn builds
2431
+ * predating in-memory evaluation could strand in a project. Codegen writes no
2432
+ * snapshot, so this exists only so readers of the Convex functions directory
2433
+ * skip a stranded file rather than parse it as a real module. Nothing deletes
2434
+ * one: kitcn cannot prove it owns a file it did not write.
2435
+ */
2436
+ const PARSE_SNAPSHOT_SUFFIX = ".kitcn-parse.ts";
2437
+ /** Files to exclude from meta generation */
2438
+ const EXCLUDED_FILES = new Set([
2439
+ "schema.ts",
2440
+ "auth.ts",
2441
+ "generated.ts",
2442
+ "convex.config.ts",
2443
+ "auth.config.ts"
2444
+ ]);
2445
+ const EXPORTED_CONST_CAPTURE_REGEX = /export\s+const\s+([a-zA-Z_$][\w$]*)\s*=/g;
2446
+ const CHAINED_PROCEDURE_CAPTURE_REGEX = /\.\s*(?:query|mutation|action)\s*\(/;
2447
+ const EXPORTED_NATIVE_HANDLER_CAPTURE_REGEX = /export\s+const\s+([a-zA-Z_$][\w$]*)\s*=\s*(?:[\w$]+\.)?(?:query|mutation|action|internalQuery|internalMutation|internalAction)\s*\(/g;
2448
+ const EXPORTED_ORM_API_DESTRUCTURE_CAPTURE_REGEX = /export\s+const\s*\{([^}]+)\}\s*=\s*orm\.api\s*\(\s*\)\s*;?/g;
2449
+ const DIRECT_CODEGEN_META_CAPTURE_REGEX = /\b_crpc(?:Meta|HttpRoute)\b/;
2450
+ /**
2451
+ * Check if a file path should be included in meta generation.
2452
+ * Filters out private files/directories (prefixed with _) and config files.
2453
+ */
2454
+ function isValidConvexFile(file) {
2455
+ if (file.endsWith(PARSE_SNAPSHOT_SUFFIX)) return false;
2456
+ if (file.endsWith(".runtime.ts")) return false;
2457
+ if (file.endsWith(".test.ts")) return false;
2458
+ if (file.endsWith(".spec.ts")) return false;
2459
+ if (file.endsWith(".testing.ts")) return false;
2460
+ if (file.endsWith(".typecheck.ts")) return false;
2461
+ if (file.startsWith("generated/")) return false;
2462
+ if (file.startsWith("_") || file.includes("/_")) return false;
2463
+ const basename = file.split("/").pop() ?? "";
2464
+ if (EXCLUDED_FILES.has(basename)) return false;
2465
+ return true;
2466
+ }
2467
+ function hasPotentialCodegenExports(source, filePath) {
2468
+ const normalizedFilePath = filePath?.replace(/\\/g, "/");
2469
+ if (normalizedFilePath === "http.ts" || normalizedFilePath?.endsWith("/http.ts")) return true;
2470
+ if (DIRECT_CODEGEN_META_CAPTURE_REGEX.test(source)) return true;
2471
+ if (Array.from(source.matchAll(EXPORTED_NATIVE_HANDLER_CAPTURE_REGEX)).length) return true;
2472
+ if (Array.from(source.matchAll(EXPORTED_ORM_API_DESTRUCTURE_CAPTURE_REGEX)).length) return true;
2473
+ const exportConstMatches = Array.from(source.matchAll(EXPORTED_CONST_CAPTURE_REGEX));
2474
+ for (const [index, match] of exportConstMatches.entries()) {
2475
+ const start = (match.index ?? 0) + match[0].length;
2476
+ const end = exportConstMatches[index + 1]?.index ?? source.length;
2477
+ if (CHAINED_PROCEDURE_CAPTURE_REGEX.test(source.slice(start, end))) return true;
2478
+ }
2479
+ return false;
2480
+ }
2481
+
2482
+ //#endregion
2483
+ //#region src/cli/utils/logger.ts
2484
+ const joinArgs = (args) => args.map(String).join(" ");
2485
+ const logger = {
2486
+ error(...args) {
2487
+ console.error(highlighter.error(joinArgs(args)));
2488
+ },
2489
+ warn(...args) {
2490
+ console.warn(highlighter.warn(joinArgs(args)));
2491
+ },
2492
+ info(...args) {
2493
+ console.info(highlighter.info(joinArgs(args)));
2494
+ },
2495
+ success(...args) {
2496
+ console.info(highlighter.success(joinArgs(args)));
2497
+ },
2498
+ log(...args) {
2499
+ console.info(joinArgs(args));
2500
+ },
2501
+ write(value) {
2502
+ console.info(value);
2503
+ },
2504
+ break() {
2505
+ console.info("");
2506
+ }
2507
+ };
2508
+
2509
+ //#endregion
2510
+ //#region src/cli/codegen.ts
2511
+ /** Valid JS identifier pattern for object keys */
2512
+ const VALID_IDENTIFIER_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
2513
+ /** Valid JS identifier start pattern */
2514
+ const IDENTIFIER_START_RE = /^[a-zA-Z_$]/;
2515
+ /** Pattern to strip .ts extension */
2516
+ const TS_EXTENSION_RE = /\.ts$/;
2517
+ /** Pattern to detect default exports in auth contract files. */
2518
+ const DEFAULT_EXPORT_RE = /\bexport\s+default\b/;
2519
+ const MISSING_KITCN_IMPORT_RE = /Cannot find (?:module|package) ['"]kitcn(?:\/[^'"]+)?['"]/;
2520
+ const RUNTIME_CALLER_RESERVED_EXPORTS = new Set(["actions", "schedule"]);
2521
+ const DEFAULT_TRIM_SEGMENTS = ["plugins", "generated"];
2522
+ const CODEGEN_SCOPES$1 = new Set([
2523
+ "all",
2524
+ "auth",
2525
+ "orm"
2526
+ ]);
2527
+ function normalizeCodegenScope(scope) {
2528
+ const normalized = scope ?? "all";
2529
+ if (CODEGEN_SCOPES$1.has(normalized)) return normalized;
2530
+ throw new Error(`Invalid codegen scope "${normalized}". Expected one of: all, auth, orm.`);
2531
+ }
2532
+ function shouldGenerateApi(scope) {
2533
+ return scope === "all";
2534
+ }
2535
+ function shouldGenerateAuth(scope) {
2536
+ return scope !== "orm";
2537
+ }
2538
+ function resolveGenerationMode(options) {
2539
+ const scope = normalizeCodegenScope(options?.scope);
2540
+ return {
2541
+ generateApi: shouldGenerateApi(scope),
2542
+ generateAuth: shouldGenerateAuth(scope),
2543
+ modeLabel: scope
2544
+ };
2545
+ }
2546
+ const AUTH_RUNTIME_PROCEDURES = [
2547
+ {
2548
+ exportName: "create",
2549
+ internal: true,
2550
+ type: "mutation"
2551
+ },
2552
+ {
2553
+ exportName: "deleteMany",
2554
+ internal: true,
2555
+ type: "mutation"
2556
+ },
2557
+ {
2558
+ exportName: "deleteOne",
2559
+ internal: true,
2560
+ type: "mutation"
2561
+ },
2562
+ {
2563
+ exportName: "findMany",
2564
+ internal: true,
2565
+ type: "query"
2566
+ },
2567
+ {
2568
+ exportName: "findOne",
2569
+ internal: true,
2570
+ type: "query"
2571
+ },
2572
+ {
2573
+ exportName: "getLatestJwks",
2574
+ internal: true,
2575
+ type: "action"
2576
+ },
2577
+ {
2578
+ exportName: "rotateKeys",
2579
+ internal: true,
2580
+ type: "action"
2581
+ },
2582
+ {
2583
+ exportName: "updateMany",
2584
+ internal: true,
2585
+ type: "mutation"
2586
+ },
2587
+ {
2588
+ exportName: "updateOne",
2589
+ internal: true,
2590
+ type: "mutation"
2591
+ }
2592
+ ];
2593
+ const GENERATED_ORM_RUNTIME_PROCEDURES = [
2594
+ {
2595
+ exportName: "scheduledMutationBatch",
2596
+ internal: true,
2597
+ type: "mutation"
2598
+ },
2599
+ {
2600
+ exportName: "scheduledDelete",
2601
+ internal: true,
2602
+ type: "mutation"
2603
+ },
2604
+ {
2605
+ exportName: "aggregateBackfill",
2606
+ internal: true,
2607
+ type: "mutation"
2608
+ },
2609
+ {
2610
+ exportName: "aggregateBackfillChunk",
2611
+ internal: true,
2612
+ type: "mutation"
2613
+ },
2614
+ {
2615
+ exportName: "aggregateBackfillStatus",
2616
+ internal: true,
2617
+ type: "mutation"
2618
+ },
2619
+ {
2620
+ exportName: "migrationRun",
2621
+ internal: true,
2622
+ type: "mutation"
2623
+ },
2624
+ {
2625
+ exportName: "migrationRunChunk",
2626
+ internal: true,
2627
+ type: "mutation"
2628
+ },
2629
+ {
2630
+ exportName: "migrationStatus",
2631
+ internal: true,
2632
+ type: "mutation"
2633
+ },
2634
+ {
2635
+ exportName: "migrationCancel",
2636
+ internal: true,
2637
+ type: "mutation"
2638
+ },
2639
+ {
2640
+ exportName: "resetChunk",
2641
+ internal: true,
2642
+ type: "mutation"
2643
+ },
2644
+ {
2645
+ exportName: "reset",
2646
+ internal: true,
2647
+ type: "action"
2648
+ }
2649
+ ];
2650
+ function listFilesRecursive(cwd, relDir = "") {
2651
+ const absDir = path.join(cwd, relDir);
2652
+ const entries = fs.readdirSync(absDir, { withFileTypes: true }).sort((a, b) => a.name > b.name ? 1 : -1);
2653
+ const files = [];
2654
+ for (const entry of entries) {
2655
+ const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
2656
+ if (entry.isDirectory()) {
2657
+ files.push(...listFilesRecursive(cwd, relPath));
2658
+ continue;
2659
+ }
2660
+ if (entry.isFile()) files.push(relPath);
2661
+ }
2662
+ return files;
2663
+ }
2664
+ function ensureRelativeImportPath(value) {
2665
+ if (value.startsWith(".") || value.startsWith("/")) return value;
2666
+ return `./${value}`;
2667
+ }
2668
+ function normalizeImportPath(value) {
2669
+ return value.replace(/\\/g, "/");
2670
+ }
2671
+ function escapeRegex(value) {
2672
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2673
+ }
2674
+ function getIndexLineColumn(source, index) {
2675
+ const lines = source.slice(0, index).split("\n");
2676
+ return {
2677
+ column: (lines.at(-1)?.length ?? 0) + 1,
2678
+ line: lines.length
2679
+ };
2680
+ }
2681
+ function findProcedureCallIndex(params) {
2682
+ const exportMatch = (params.exportName === "default" ? /\bexport\s+default\b/g : new RegExp(`\\bexport\\s+const\\s+${escapeRegex(params.exportName)}\\s*=`, "g")).exec(params.source);
2683
+ if (!exportMatch) return;
2684
+ const tail = params.source.slice(exportMatch.index);
2685
+ const callMatch = new RegExp(`\\.\\s*${params.type}\\s*\\(`, "g").exec(tail);
2686
+ if (!callMatch || callMatch.index < 0) return;
2687
+ return exportMatch.index + callMatch.index;
2688
+ }
2689
+ function buildProcedureNameLookupEntries(params) {
2690
+ if (params.procedures.length === 0) return [];
2691
+ const source = fs.readFileSync(params.filePath, "utf8");
2692
+ return params.procedures.flatMap((procedure) => {
2693
+ const callIndex = findProcedureCallIndex({
2694
+ exportName: procedure.exportName,
2695
+ source,
2696
+ type: procedure.type
2697
+ });
2698
+ if (callIndex === void 0) return [];
2699
+ const location = getIndexLineColumn(source, callIndex);
2700
+ return [{
2701
+ column: location.column,
2702
+ line: location.line,
2703
+ name: `${params.moduleName}:${procedure.exportName}`
2704
+ }];
2705
+ });
2706
+ }
2707
+ function emitProcedureNameLookupLiteral(lookup) {
2708
+ const entries = Object.entries(lookup);
2709
+ if (entries.length === 0) return "{}";
2710
+ return `{\n${entries.map(([file, locations]) => {
2711
+ const items = locations.map((location) => `{ column: ${location.column}, line: ${location.line}, name: ${JSON.stringify(location.name)} }`).join(", ");
2712
+ return ` ${JSON.stringify(file)}: [${items}],`;
2713
+ }).join("\n")}\n}`;
2714
+ }
2715
+ function formatKey(key) {
2716
+ return VALID_IDENTIFIER_RE.test(key) ? key : `'${key}'`;
2717
+ }
2718
+ function toPascalCaseToken(token) {
2719
+ if (token.length === 0) return "";
2720
+ return `${token[0]?.toUpperCase() ?? ""}${token.slice(1)}`;
2721
+ }
2722
+ function getRuntimeNameHash(moduleName) {
2723
+ return createHash("sha1").update(moduleName).digest("hex").slice(0, 6);
2724
+ }
2725
+ function normalizeTrimSegments(trimSegments) {
2726
+ const source = [...DEFAULT_TRIM_SEGMENTS, ...trimSegments ?? []];
2727
+ return [...new Set(source.map((segment) => segment.trim()).filter(Boolean))];
2728
+ }
2729
+ function toRuntimeExportBase(moduleSegments, fallbackSegments) {
2730
+ const base = moduleSegments.filter((segment) => segment.length > 0).flatMap((segment) => segment.split(/[^a-zA-Z0-9]+/g).filter(Boolean).map((token) => toPascalCaseToken(token))).join("");
2731
+ if (base.length === 0) {
2732
+ if (fallbackSegments && fallbackSegments.length > 0) return toRuntimeExportBase(fallbackSegments);
2733
+ return "Module";
2734
+ }
2735
+ return IDENTIFIER_START_RE.test(base) ? base : `M${base}`;
2736
+ }
2737
+ function getModuleRuntimeExportBase(moduleName, trimSegments) {
2738
+ const moduleSegments = moduleName.split("/").filter(Boolean);
2739
+ const trimSet = new Set(trimSegments);
2740
+ const keptSegments = moduleSegments.filter((segment) => !trimSet.has(segment));
2741
+ const removedSegments = moduleSegments.filter((segment) => trimSet.has(segment));
2742
+ const primaryBase = toRuntimeExportBase(keptSegments, moduleSegments);
2743
+ const removedBase = toRuntimeExportBase(removedSegments);
2744
+ return {
2745
+ primaryBase,
2746
+ collisionBase: removedSegments.length > 0 ? `${primaryBase}${removedBase}` : primaryBase
2747
+ };
2748
+ }
2749
+ function resolveModuleRuntimeExportNames(moduleNames, trimSegments) {
2750
+ const resolvedNames = /* @__PURE__ */ new Map();
2751
+ const byPrimaryBase = /* @__PURE__ */ new Map();
2752
+ for (const moduleName of [...new Set(moduleNames)].sort((a, b) => a.localeCompare(b))) {
2753
+ if (moduleName === "generated/server") {
2754
+ resolvedNames.set(moduleName, {
2755
+ callerExportName: "createServerCaller",
2756
+ handlerExportName: "createServerHandler"
2757
+ });
2758
+ continue;
2759
+ }
2760
+ const { primaryBase, collisionBase } = getModuleRuntimeExportBase(moduleName, trimSegments);
2761
+ const entries = byPrimaryBase.get(primaryBase);
2762
+ if (entries) {
2763
+ entries.push({
2764
+ moduleName,
2765
+ collisionBase
2766
+ });
2767
+ continue;
2768
+ }
2769
+ byPrimaryBase.set(primaryBase, [{
2770
+ moduleName,
2771
+ collisionBase
2772
+ }]);
2773
+ }
2774
+ for (const [primaryBase, entries] of byPrimaryBase) {
2775
+ if (entries.length === 1) {
2776
+ const entry = entries[0];
2777
+ if (!entry) continue;
2778
+ resolvedNames.set(entry.moduleName, {
2779
+ callerExportName: `create${primaryBase}Caller`,
2780
+ handlerExportName: `create${primaryBase}Handler`
2781
+ });
2782
+ continue;
2783
+ }
2784
+ const usedBases = /* @__PURE__ */ new Set();
2785
+ for (const entry of entries) {
2786
+ let exportBase = entry.collisionBase;
2787
+ if (usedBases.has(exportBase)) exportBase = `${exportBase}_${getRuntimeNameHash(entry.moduleName)}`;
2788
+ usedBases.add(exportBase);
2789
+ resolvedNames.set(entry.moduleName, {
2790
+ callerExportName: `create${exportBase}Caller`,
2791
+ handlerExportName: `create${exportBase}Handler`
2792
+ });
2793
+ }
2794
+ }
2795
+ return resolvedNames;
2796
+ }
2797
+ function getModuleRuntimeExportNames(moduleName) {
2798
+ const base = toRuntimeExportBase(moduleName.split("/").filter(Boolean));
2799
+ return {
2800
+ callerExportName: `create${base}Caller`,
2801
+ handlerExportName: `create${base}Handler`
2802
+ };
2803
+ }
2804
+ function getModuleImportPath(outputFile, functionsDir, moduleName) {
2805
+ const moduleFile = path.join(functionsDir, moduleName);
2806
+ return ensureRelativeImportPath(normalizeImportPath(path.relative(path.dirname(outputFile), moduleFile)));
2807
+ }
2808
+ function getRuntimeApiImportPath(outputFile, functionsDir) {
2809
+ const runtimeApiFile = path.join(functionsDir, "_generated", "api.js");
2810
+ return ensureRelativeImportPath(normalizeImportPath(path.relative(path.dirname(outputFile), runtimeApiFile)));
2811
+ }
2812
+ function getRuntimeApiTypesImportPath(outputFile, functionsDir) {
2813
+ const runtimeApiFile = path.join(functionsDir, "_generated", "api");
2814
+ return ensureRelativeImportPath(normalizeImportPath(path.relative(path.dirname(outputFile), runtimeApiFile)));
2815
+ }
2816
+ function moduleUsesOwnGeneratedRuntime(functionsDir, moduleName) {
2817
+ if (moduleName === "generated/server") return true;
2818
+ const moduleFilePath = path.join(functionsDir, `${moduleName}.ts`);
2819
+ if (!fs.existsSync(moduleFilePath)) return false;
2820
+ const source = fs.readFileSync(moduleFilePath, "utf8");
2821
+ const escapedRuntimeImportPath = escapeRegex(ensureRelativeImportPath(normalizeImportPath(path.relative(path.dirname(moduleFilePath), path.join(functionsDir, "generated", `${moduleName}.runtime`)))));
2822
+ return [new RegExp(`from\\s+['"]${escapedRuntimeImportPath}(?:\\.[jt]s)?['"]`), new RegExp(`require\\(\\s*['"]${escapedRuntimeImportPath}(?:\\.[jt]s)?['"]\\s*\\)`)].some((pattern) => pattern.test(source));
2823
+ }
2824
+ function getBracketAccessPath(rootIdentifier, pathSegments) {
2825
+ return pathSegments.reduce((accessPath, segment) => `${accessPath}[${JSON.stringify(segment)}]`, rootIdentifier);
2826
+ }
2827
+ function getSchemaImportPath(outputFile, functionsDir) {
2828
+ const schemaFile = path.join(functionsDir, "schema");
2829
+ return ensureRelativeImportPath(normalizeImportPath(path.relative(path.dirname(outputFile), schemaFile)));
2830
+ }
2831
+ function getServerTypesImportPath(outputFile, functionsDir) {
2832
+ const serverTypesFile = path.join(functionsDir, "_generated", "server");
2833
+ return ensureRelativeImportPath(normalizeImportPath(path.relative(path.dirname(outputFile), serverTypesFile)));
2834
+ }
2835
+ function getDataModelImportPath(outputFile, functionsDir) {
2836
+ const dataModelFile = path.join(functionsDir, "_generated", "dataModel");
2837
+ return ensureRelativeImportPath(normalizeImportPath(path.relative(path.dirname(outputFile), dataModelFile)));
2838
+ }
2839
+ function getHttpImportPath(outputFile, functionsDir) {
2840
+ const httpFile = path.join(functionsDir, "http");
2841
+ return ensureRelativeImportPath(normalizeImportPath(path.relative(path.dirname(outputFile), httpFile)));
2842
+ }
2843
+ const GENERATED_DIR = "generated";
2844
+ function getGeneratedServerOutputFile(functionsDir) {
2845
+ return path.join(functionsDir, GENERATED_DIR, "server.ts");
2846
+ }
2847
+ function getGeneratedOrmOutputFile(functionsDir) {
2848
+ return path.join(functionsDir, GENERATED_DIR, "orm.ts");
2849
+ }
2850
+ function getGeneratedCrpcOutputFile(functionsDir) {
2851
+ return path.join(functionsDir, GENERATED_DIR, "crpc.ts");
2852
+ }
2853
+ function getGeneratedAuthOutputFile(functionsDir) {
2854
+ return path.join(functionsDir, GENERATED_DIR, "auth.ts");
2855
+ }
2856
+ function getGeneratedMigrationsHelperOutputFile(functionsDir) {
2857
+ return path.join(functionsDir, GENERATED_DIR, "migrations.gen.ts");
2858
+ }
2859
+ function getLegacyGeneratedOutputFile(functionsDir) {
2860
+ return path.join(functionsDir, "generated.ts");
2861
+ }
2862
+ function getModuleNameFromOutputFile(outputFile, functionsDir) {
2863
+ return normalizeImportPath(path.relative(functionsDir, outputFile)).replace(TS_EXTENSION_RE, "");
2864
+ }
2865
+ function getGeneratedServerImportPath(outputFile, functionsDir) {
2866
+ const generatedServerFile = getGeneratedServerOutputFile(functionsDir);
2867
+ return ensureRelativeImportPath(normalizeImportPath(path.relative(path.dirname(outputFile), generatedServerFile)).replace(TS_EXTENSION_RE, ""));
2868
+ }
2869
+ function getGeneratedRuntimeOutputFile(functionsDir, moduleName) {
2870
+ const runtimeModuleName = moduleName.startsWith(`${GENERATED_DIR}/`) ? moduleName.slice(10) : moduleName;
2871
+ return path.join(functionsDir, GENERATED_DIR, `${runtimeModuleName}.runtime.ts`);
2872
+ }
2873
+ function emitGeneratedServerPlaceholderFile(functionsDir) {
2874
+ const functionsDirHint = normalizeImportPath(path.relative(process.cwd(), functionsDir)) || "convex";
2875
+ return `// @ts-nocheck
2876
+ // biome-ignore-all format: generated
2877
+ /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-unused-vars */
2878
+ // This file is auto-generated by kitcn
2879
+ // Do not edit manually. Run \`kitcn codegen\` to regenerate.
2880
+
2881
+ import {
2882
+ registerProcedureNameLookup,
2883
+ } from 'kitcn/server';
2884
+ import type {
2885
+ ActionCtx as ServerActionCtx,
2886
+ MutationCtx as ServerMutationCtx,
2887
+ QueryCtx as ServerQueryCtx,
2888
+ } from '../_generated/server';
2889
+
2890
+ export type QueryCtx = ServerQueryCtx;
2891
+ export type MutationCtx = ServerMutationCtx;
2892
+ export type ActionCtx = ServerActionCtx;
2893
+ export type GenericCtx = QueryCtx | MutationCtx | ActionCtx;
2894
+
2895
+ ${CRPC_BUILDER_STUB_SOURCE}
2896
+
2897
+ registerProcedureNameLookup({}, ${JSON.stringify(functionsDirHint)});
2898
+
2899
+ export function withOrm<Ctx>(ctx: Ctx): Ctx {
2900
+ return ctx;
2901
+ }
2902
+ `;
2903
+ }
2904
+ function emitGeneratedAuthPlaceholderFile() {
2905
+ return `// biome-ignore-all format: generated
2906
+ /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-unused-vars */
2907
+ // This file is auto-generated by kitcn
2908
+ // Do not edit manually. Run \`kitcn codegen\` to regenerate.
2909
+
2910
+ export function defineAuth<TDefinition>(definition: TDefinition): TDefinition {
2911
+ return definition;
2912
+ }
2913
+
2914
+ export const authEnabled = false;
2915
+ export const authClient = {} as Record<string, unknown>;
2916
+ export const getAuth = () => ({} as Record<string, unknown>);
2917
+ export const auth = {} as Record<string, unknown>;
2918
+ `;
2919
+ }
2920
+ function emitGeneratedMigrationsPlaceholderFile() {
2921
+ return `// biome-ignore-all format: generated
2922
+ /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-unused-vars */
2923
+ // This file is auto-generated by kitcn
2924
+ // Do not edit manually. Run \`kitcn codegen\` to regenerate.
2925
+
2926
+ export { defineMigration } from 'kitcn/orm';
2927
+ `;
2928
+ }
2929
+ function writeFileIfChanged(filePath, content) {
2930
+ if (fs.existsSync(filePath)) {
2931
+ if (fs.readFileSync(filePath, "utf8") === content) return false;
2932
+ }
2933
+ fs.writeFileSync(filePath, content);
2934
+ return true;
2935
+ }
2936
+ function ensureGeneratedSupportPlaceholders(functionsDir, options) {
2937
+ const createdPlaceholderFiles = [];
2938
+ const serverOutputFile = getGeneratedServerOutputFile(functionsDir);
2939
+ const authOutputFile = getGeneratedAuthOutputFile(functionsDir);
2940
+ const migrationsHelperOutputFile = getGeneratedMigrationsHelperOutputFile(functionsDir);
2941
+ const generatedDir = path.dirname(serverOutputFile);
2942
+ fs.mkdirSync(generatedDir, { recursive: true });
2943
+ const includeAuth = options?.includeAuth ?? true;
2944
+ if (!fs.existsSync(serverOutputFile)) {
2945
+ writeFileIfChanged(serverOutputFile, emitGeneratedServerPlaceholderFile(functionsDir));
2946
+ createdPlaceholderFiles.push(serverOutputFile);
2947
+ }
2948
+ if (includeAuth && !fs.existsSync(authOutputFile)) {
2949
+ writeFileIfChanged(authOutputFile, emitGeneratedAuthPlaceholderFile());
2950
+ createdPlaceholderFiles.push(authOutputFile);
2951
+ }
2952
+ if (!fs.existsSync(migrationsHelperOutputFile)) {
2953
+ writeFileIfChanged(migrationsHelperOutputFile, emitGeneratedMigrationsPlaceholderFile());
2954
+ createdPlaceholderFiles.push(migrationsHelperOutputFile);
2955
+ }
2956
+ return createdPlaceholderFiles;
2957
+ }
2958
+ function emitGeneratedRuntimePlaceholderFile(exportNames) {
2959
+ const { callerExportName, handlerExportName } = exportNames;
2960
+ return `// biome-ignore-all format: generated
2961
+ // This file is auto-generated by kitcn
2962
+ // Do not edit manually. Run \`kitcn codegen\` to regenerate.
2963
+
2964
+ export function ${callerExportName}(_ctx: unknown) {
2965
+ throw new Error('[kitcn] Runtime caller is not generated yet. Run kitcn codegen.');
2966
+ }
2967
+
2968
+ export function ${handlerExportName}(_ctx: unknown) {
2969
+ throw new Error('[kitcn] Runtime handler is not generated yet. Run kitcn codegen.');
2970
+ }
2971
+ `;
2972
+ }
2973
+ function ensureGeneratedRuntimePlaceholders(functionsDir, moduleNames, runtimeExportNames) {
2974
+ const createdPlaceholderFiles = [];
2975
+ for (const moduleName of moduleNames) {
2976
+ const runtimeOutputFile = getGeneratedRuntimeOutputFile(functionsDir, moduleName);
2977
+ if (fs.existsSync(runtimeOutputFile)) continue;
2978
+ const exportNames = runtimeExportNames.get(moduleName) ?? getModuleRuntimeExportNames(moduleName);
2979
+ fs.mkdirSync(path.dirname(runtimeOutputFile), { recursive: true });
2980
+ writeFileIfChanged(runtimeOutputFile, emitGeneratedRuntimePlaceholderFile(exportNames));
2981
+ createdPlaceholderFiles.push(runtimeOutputFile);
2982
+ }
2983
+ return createdPlaceholderFiles;
2984
+ }
2985
+ function listGeneratedRuntimeFiles(functionsDir) {
2986
+ const generatedDir = path.join(functionsDir, "generated");
2987
+ if (!fs.existsSync(generatedDir)) return [];
2988
+ return listFilesRecursive(generatedDir).filter((file) => file.endsWith(".runtime.ts")).map((file) => path.join(generatedDir, file));
2989
+ }
2990
+ async function resolveSchemaMetadataForCodegen(functionsDir, debug, createJitiInstance = createProjectJiti) {
2991
+ const schemaPath = path.join(functionsDir, "schema.ts");
2992
+ if (!fs.existsSync(schemaPath)) return {
2993
+ hasOrmSchema: false,
2994
+ hasRelations: false,
2995
+ hasTriggers: false
2996
+ };
2997
+ const jitiInstance = createJitiInstance();
2998
+ try {
2999
+ const schemaModule = await jitiInstance.import(schemaPath);
3000
+ const schemaValue = schemaModule && typeof schemaModule === "object" ? schemaModule.default ?? schemaModule : null;
3001
+ if (!schemaValue || typeof schemaValue !== "object") return {
3002
+ hasOrmSchema: false,
3003
+ hasRelations: false,
3004
+ hasTriggers: false
3005
+ };
3006
+ return {
3007
+ hasOrmSchema: OrmSchemaOptions in schemaValue,
3008
+ hasRelations: Boolean(getSchemaRelations(schemaValue)),
3009
+ hasTriggers: Boolean(getSchemaTriggers(schemaValue))
3010
+ };
3011
+ } catch (error) {
3012
+ if (debug) logger.warn(`⚠️ Failed to load schema extensions from ${schemaPath}: ${error.message}`);
3013
+ return {
3014
+ hasOrmSchema: false,
3015
+ hasRelations: false,
3016
+ hasTriggers: false
3017
+ };
3018
+ }
3019
+ }
3020
+ function cleanupGeneratedPluginArtifacts(functionsDir) {
3021
+ fs.rmSync(path.join(functionsDir, GENERATED_DIR, "plugins"), {
3022
+ recursive: true,
3023
+ force: true
3024
+ });
3025
+ }
3026
+ function emitGeneratedServerFile(outputFile, functionsDir, hasOrmSchema, hasMigrationsManifest, procedureNameLookup) {
3027
+ const asSingleQuotedImport = (importPath) => `'${importPath.replaceAll("'", "\\'")}'`;
3028
+ const serverTypesImportPath = getServerTypesImportPath(outputFile, functionsDir);
3029
+ const dataModelImportPath = getDataModelImportPath(outputFile, functionsDir);
3030
+ const schemaImportPath = getSchemaImportPath(outputFile, functionsDir);
3031
+ const migrationsManifestImportPath = getModuleImportPath(outputFile, functionsDir, "migrations/manifest");
3032
+ const serverTypesImportLiteral = asSingleQuotedImport(serverTypesImportPath);
3033
+ const dataModelImportLiteral = asSingleQuotedImport(dataModelImportPath);
3034
+ const schemaImportLiteral = asSingleQuotedImport(schemaImportPath);
3035
+ const migrationsManifestImportLiteral = asSingleQuotedImport(migrationsManifestImportPath);
3036
+ const migrationsImportLine = hasMigrationsManifest ? `import { migrations } from ${migrationsManifestImportLiteral};\n` : "";
3037
+ const migrationsConfigLine = hasMigrationsManifest ? " migrations,\n" : "";
3038
+ const functionsDirHint = normalizeImportPath(path.relative(process.cwd(), functionsDir)) || "convex";
3039
+ const procedureNameLookupLiteral = emitProcedureNameLookupLiteral(procedureNameLookup);
3040
+ if (!hasOrmSchema) return `// biome-ignore-all format: generated
3041
+ // This file is auto-generated by kitcn
3042
+ // Do not edit manually. Run \`kitcn codegen\` to regenerate.
3043
+
3044
+ import {
3045
+ initCRPC as baseInitCRPC,
3046
+ registerProcedureNameLookup,
3047
+ } from 'kitcn/server';
3048
+ import type { DataModel } from ${dataModelImportLiteral};
3049
+ import type {
3050
+ ActionCtx as ServerActionCtx,
3051
+ MutationCtx as ServerMutationCtx,
3052
+ QueryCtx as ServerQueryCtx,
3053
+ } from ${serverTypesImportLiteral};
3054
+ import { httpAction, internalMutation } from ${serverTypesImportLiteral};
3055
+
3056
+ export type QueryCtx = ServerQueryCtx;
3057
+ export type MutationCtx = ServerMutationCtx;
3058
+ export type ActionCtx = ServerActionCtx;
3059
+ export type GenericCtx = QueryCtx | MutationCtx | ActionCtx;
3060
+ export type OrmCtx<Ctx = QueryCtx> = Ctx;
3061
+
3062
+ registerProcedureNameLookup(
3063
+ ${procedureNameLookupLiteral},
3064
+ ${JSON.stringify(functionsDirHint)}
3065
+ );
3066
+
3067
+ export function withOrm<Ctx extends ServerQueryCtx | ServerMutationCtx>(ctx: Ctx): OrmCtx<Ctx> {
3068
+ return ctx as OrmCtx<Ctx>;
3069
+ }
3070
+
3071
+ export const initCRPC = baseInitCRPC.dataModel<DataModel>().context({
3072
+ query: (ctx) => ctx,
3073
+ mutation: (ctx) => ctx,
3074
+ action: (ctx) => ctx,
3075
+ });
3076
+ export { httpAction, internalMutation };
3077
+ `;
3078
+ const moduleNamespace = getModuleNameFromOutputFile(outputFile, functionsDir);
3079
+ return `// biome-ignore-all format: generated
3080
+ // This file is auto-generated by kitcn
3081
+ // Do not edit manually. Run \`kitcn codegen\` to regenerate.
3082
+
3083
+ import {
3084
+ createOrm,
3085
+ type GenericOrmCtx,
3086
+ type OrmFunctions,
3087
+ } from 'kitcn/orm';
3088
+ import {
3089
+ createGeneratedFunctionReference,
3090
+ initCRPC as baseInitCRPC,
3091
+ registerProcedureNameLookup,
3092
+ } from 'kitcn/server';
3093
+ import type { DataModel } from ${dataModelImportLiteral};
3094
+ import type {
3095
+ ActionCtx as ServerActionCtx,
3096
+ MutationCtx as ServerMutationCtx,
3097
+ QueryCtx as ServerQueryCtx,
3098
+ } from ${serverTypesImportLiteral};
3099
+ import { httpAction, internalMutation } from ${serverTypesImportLiteral};
3100
+ import schema from ${schemaImportLiteral};
3101
+ ${migrationsImportLine}
3102
+
3103
+ ${`const ormFunctions: OrmFunctions = {
3104
+ scheduledMutationBatch: createGeneratedFunctionReference<"mutation", "internal", unknown>(${JSON.stringify(`${moduleNamespace}:scheduledMutationBatch`)}),
3105
+ scheduledDelete: createGeneratedFunctionReference<"mutation", "internal", unknown>(${JSON.stringify(`${moduleNamespace}:scheduledDelete`)}),
3106
+ aggregateBackfillChunk: createGeneratedFunctionReference<"mutation", "internal", unknown>(${JSON.stringify(`${moduleNamespace}:aggregateBackfillChunk`)}),
3107
+ migrationRunChunk: createGeneratedFunctionReference<"mutation", "internal", unknown>(${JSON.stringify(`${moduleNamespace}:migrationRunChunk`)}),
3108
+ resetChunk: createGeneratedFunctionReference<"mutation", "internal", unknown>(${JSON.stringify(`${moduleNamespace}:resetChunk`)}),
3109
+ };`}
3110
+ const ormSchema = schema;
3111
+
3112
+ registerProcedureNameLookup(
3113
+ ${procedureNameLookupLiteral},
3114
+ ${JSON.stringify(functionsDirHint)}
3115
+ );
3116
+
3117
+ export const orm = createOrm({
3118
+ schema: ormSchema,
3119
+ ormFunctions,
3120
+ ${migrationsConfigLine} internalMutation,
3121
+ });
3122
+
3123
+ export type OrmCtx<Ctx extends ServerQueryCtx | ServerMutationCtx = ServerQueryCtx> = GenericOrmCtx<Ctx, typeof ormSchema>;
3124
+ export type QueryCtx = OrmCtx<ServerQueryCtx>;
3125
+ export type MutationCtx = OrmCtx<ServerMutationCtx>;
3126
+ export type ActionCtx = ServerActionCtx;
3127
+ export type GenericCtx = QueryCtx | MutationCtx | ActionCtx;
3128
+
3129
+ export function withOrm<Ctx extends ServerQueryCtx | ServerMutationCtx>(ctx: Ctx) {
3130
+ return orm.with(ctx) as OrmCtx<Ctx>;
3131
+ }
3132
+
3133
+ export const initCRPC = baseInitCRPC.dataModel<DataModel>().context({
3134
+ query: (ctx) => withOrm(ctx),
3135
+ mutation: (ctx) => withOrm(ctx),
3136
+ action: (ctx) => ctx,
3137
+ });
3138
+ export { httpAction, internalMutation };
3139
+
3140
+ export const {
3141
+ scheduledMutationBatch,
3142
+ scheduledDelete,
3143
+ aggregateBackfill,
3144
+ aggregateBackfillChunk,
3145
+ aggregateBackfillStatus,
3146
+ migrationRun,
3147
+ migrationRunChunk,
3148
+ migrationStatus,
3149
+ migrationCancel,
3150
+ resetChunk,
3151
+ reset,
3152
+ } = orm.api();
3153
+ `;
3154
+ }
3155
+ function emitGeneratedAuthFile(outputFile, functionsDir, hasOrmSchema, authContract) {
3156
+ const asSingleQuotedImport = (importPath) => `'${importPath.replaceAll("'", "\\'")}'`;
3157
+ const runtimeApiImportPath = getRuntimeApiImportPath(outputFile, functionsDir);
3158
+ const dataModelImportPath = getDataModelImportPath(outputFile, functionsDir);
3159
+ const schemaImportPath = getSchemaImportPath(outputFile, functionsDir);
3160
+ const serverImportPath = getGeneratedServerImportPath(outputFile, functionsDir);
3161
+ const moduleNamespace = getModuleNameFromOutputFile(outputFile, functionsDir);
3162
+ const authDefinitionImportPath = getModuleImportPath(outputFile, functionsDir, "auth");
3163
+ const runtimeApiImportLiteral = asSingleQuotedImport(runtimeApiImportPath);
3164
+ const dataModelImportLiteral = asSingleQuotedImport(dataModelImportPath);
3165
+ const schemaImportLiteral = asSingleQuotedImport(schemaImportPath);
3166
+ const serverImportLiteral = asSingleQuotedImport(serverImportPath);
3167
+ const authDefinitionImportLiteral = asSingleQuotedImport(authDefinitionImportPath);
3168
+ const authDefinitionFilePath = normalizeImportPath(path.relative(process.cwd(), path.join(functionsDir, "auth.ts")));
3169
+ const hasAuthFile = authContract.hasAuthFile;
3170
+ const hasAuthDefaultExport = authContract.hasAuthDefaultExport;
3171
+ const authRuntimeModule = hasAuthDefaultExport ? "kitcn/auth" : "kitcn/auth/generated";
3172
+ const disabledAuthReasonKind = hasAuthFile ? hasAuthDefaultExport ? "default_export_unavailable" : "missing_default_export" : "missing_auth_file";
3173
+ const authRuntimeImports = `import {
3174
+ ${(hasAuthDefaultExport ? [
3175
+ "type BetterAuthOptionsWithoutDatabase",
3176
+ "type AuthRuntime",
3177
+ "defineAuth as baseDefineAuth",
3178
+ "createAuthRuntime",
3179
+ "type GenericAuthDefinition",
3180
+ "getInvalidAuthDefinitionExportReason",
3181
+ "resolveGeneratedAuthDefinition"
3182
+ ] : [
3183
+ "type BetterAuthOptionsWithoutDatabase",
3184
+ "type AuthRuntime",
3185
+ "defineAuth as baseDefineAuth",
3186
+ "type GenericAuthDefinition",
3187
+ "getGeneratedAuthDisabledReason",
3188
+ "createDisabledAuthRuntime"
3189
+ ]).join(",\n ")},
3190
+ } from '${authRuntimeModule}';`;
3191
+ const authDefinitionImport = hasAuthDefaultExport ? `import * as authDefinitionModule from ${authDefinitionImportLiteral};` : "";
3192
+ const runtimeApiImport = hasAuthDefaultExport ? `import { internal } from ${runtimeApiImportLiteral};` : "";
3193
+ const withOrmImport = hasOrmSchema && hasAuthDefaultExport ? `import { withOrm } from ${serverImportLiteral};` : "";
3194
+ const usesSchemaFallback = !hasOrmSchema && !hasAuthDefaultExport;
3195
+ const schemaTypeImports = usesSchemaFallback ? `import type { GenericSchema, SchemaDefinition } from 'convex/server';` : "";
3196
+ const generatedSchemaType = usesSchemaFallback ? "GeneratedSchema" : "typeof schema";
3197
+ return `// biome-ignore-all format: generated
3198
+ // This file is auto-generated by kitcn
3199
+ // Do not edit manually. Run \`kitcn codegen\` to regenerate.
3200
+
3201
+ ${authRuntimeImports}
3202
+ ${runtimeApiImport}
3203
+ import type { DataModel } from ${dataModelImportLiteral};
3204
+ import type { GenericCtx, MutationCtx } from ${serverImportLiteral};
3205
+ ${schemaTypeImports}
3206
+ ${withOrmImport}
3207
+ ${usesSchemaFallback ? "" : `import schema from ${schemaImportLiteral};`}
3208
+ ${authDefinitionImport}
3209
+
3210
+ ${usesSchemaFallback ? "type GeneratedSchema = SchemaDefinition<GenericSchema, true>;" : ""}
3211
+
3212
+ export function defineAuth<
3213
+ AuthOptions extends BetterAuthOptionsWithoutDatabase = BetterAuthOptionsWithoutDatabase,
3214
+ >(
3215
+ definition: GenericAuthDefinition<GenericCtx, DataModel, ${generatedSchemaType}, AuthOptions>
3216
+ ) {
3217
+ return baseDefineAuth(definition);
3218
+ }
3219
+
3220
+ ${hasAuthDefaultExport ? `type AuthDefinitionFromFile = typeof authDefinitionModule.default;
3221
+
3222
+ const authDefinition = resolveGeneratedAuthDefinition<AuthDefinitionFromFile>(
3223
+ authDefinitionModule,
3224
+ getInvalidAuthDefinitionExportReason(${JSON.stringify(authDefinitionFilePath)})
3225
+ );
3226
+ ` : ""}
3227
+ const authRuntime: ${hasAuthDefaultExport ? `AuthRuntime<
3228
+ DataModel,
3229
+ ${generatedSchemaType},
3230
+ MutationCtx,
3231
+ GenericCtx,
3232
+ ReturnType<AuthDefinitionFromFile>
3233
+ > = createAuthRuntime<
3234
+ DataModel,
3235
+ ${generatedSchemaType},
3236
+ MutationCtx,
3237
+ GenericCtx,
3238
+ ReturnType<AuthDefinitionFromFile>
3239
+ >({
3240
+ internal,
3241
+ moduleName: ${JSON.stringify(moduleNamespace)},
3242
+ schema,
3243
+ auth: authDefinition,${hasOrmSchema ? "\n context: withOrm," : ""}
3244
+ })` : `AuthRuntime<
3245
+ DataModel,
3246
+ ${generatedSchemaType},
3247
+ MutationCtx,
3248
+ GenericCtx
3249
+ > = createDisabledAuthRuntime<DataModel, ${generatedSchemaType}, MutationCtx, GenericCtx>({
3250
+ reason: getGeneratedAuthDisabledReason(
3251
+ ${JSON.stringify(disabledAuthReasonKind)},
3252
+ ${JSON.stringify(authDefinitionFilePath)}
3253
+ ),
3254
+ })`};
3255
+
3256
+ export const {
3257
+ authEnabled,
3258
+ authClient,
3259
+ getAuth,
3260
+ auth,
3261
+ create,
3262
+ deleteMany,
3263
+ deleteOne,
3264
+ findMany,
3265
+ findOne,
3266
+ updateMany,
3267
+ updateOne,
3268
+ getLatestJwks,
3269
+ rotateKeys,
3270
+ } = authRuntime;
3271
+ `;
3272
+ }
3273
+ function emitGeneratedMigrationsFile(outputFile, functionsDir, hasRelationsMetadata) {
3274
+ if (!hasRelationsMetadata) return emitGeneratedMigrationsPlaceholderFile();
3275
+ const asSingleQuotedImport = (importPath) => `'${importPath.replaceAll("'", "\\'")}'`;
3276
+ const schemaImportLiteral = asSingleQuotedImport(getSchemaImportPath(outputFile, functionsDir));
3277
+ const migrationSchemaImport = "schema";
3278
+ const migrationSchemaType = "typeof schema";
3279
+ return `// biome-ignore-all format: generated
3280
+ // This file is auto-generated by kitcn
3281
+ // Do not edit manually. Run \`kitcn codegen\` to regenerate.
3282
+
3283
+ import {
3284
+ defineMigration as baseDefineMigration,
3285
+ type MigrationDefinition,
3286
+ } from 'kitcn/orm';
3287
+ import ${migrationSchemaImport} from ${schemaImportLiteral};
3288
+
3289
+ export function defineMigration(
3290
+ migration: MigrationDefinition<${migrationSchemaType}>
3291
+ ): MigrationDefinition<${migrationSchemaType}> {
3292
+ return baseDefineMigration<${migrationSchemaType}>(migration);
3293
+ }
3294
+ `;
3295
+ }
3296
+ function renderRuntimeApiTypesImport(entries, importPath) {
3297
+ const specifiers = [];
3298
+ if (entries.some((entry) => !entry.internal)) specifiers.push("api as generatedApi");
3299
+ if (entries.some((entry) => entry.internal)) specifiers.push("internal as generatedInternal");
3300
+ if (specifiers.length === 0) return "";
3301
+ if (specifiers.length === 1) return `import type { ${specifiers[0]} } from '${importPath}';\n`;
3302
+ return `import type {\n${specifiers.map((specifier) => ` ${specifier},\n`).join("")}} from '${importPath}';\n`;
3303
+ }
3304
+ function emitGeneratedModuleRuntimeFile(outputFile, functionsDir, moduleName, procedureEntries, runtimeExportNames) {
3305
+ const { callerExportName, handlerExportName } = runtimeExportNames?.get(moduleName) ?? getModuleRuntimeExportNames(moduleName);
3306
+ const useGeneratedApiTypes = moduleUsesOwnGeneratedRuntime(functionsDir, moduleName);
3307
+ const runtimeApiTypesImportPath = useGeneratedApiTypes ? getRuntimeApiTypesImportPath(outputFile, functionsDir) : null;
3308
+ const generatedServerImportPath = getGeneratedServerImportPath(outputFile, functionsDir);
3309
+ const { callerEntries, handlerEntries } = partitionRuntimeEntriesForEmission(procedureEntries);
3310
+ const runtimeApiTypesImport = runtimeApiTypesImportPath ? renderRuntimeApiTypesImport(callerEntries, runtimeApiTypesImportPath) : "";
3311
+ const callerRegistryLines = emitProcedureRegistryEntries(callerEntries, outputFile, functionsDir, moduleName, useGeneratedApiTypes);
3312
+ const callerRegistryBody = callerRegistryLines.length > 0 ? `\n${callerRegistryLines.join("\n")}\n` : "\n";
3313
+ const hasHandlerRegistry = handlerEntries.length > 0;
3314
+ const handlerRegistryLines = hasHandlerRegistry ? emitProcedureRegistryEntries(handlerEntries, outputFile, functionsDir, moduleName, useGeneratedApiTypes) : [];
3315
+ const handlerRegistryBody = handlerRegistryLines.length > 0 ? `\n${handlerRegistryLines.join("\n")}\n` : "\n";
3316
+ const allEntriesAreCrpc = callerEntries.length > 0 && callerEntries.length === handlerEntries.length;
3317
+ const handlerRegistryDeclaration = hasHandlerRegistry ? allEntriesAreCrpc ? "\n const handlerRegistry = procedureRegistry;\n" : `\n const handlerRegistry = {${handlerRegistryBody}} as const;\n` : "";
3318
+ const handlerTypeDeclarations = hasHandlerRegistry ? `
3319
+ type ProcedureHandlerContext = QueryCtx | MutationCtx;
3320
+ type GeneratedProcedureHandler<
3321
+ TCtx extends ProcedureHandlerContext = ProcedureHandlerContext,
3322
+ > = GeneratedRegistryHandlerForContext<
3323
+ ProcedureHandlerRegistry,
3324
+ TCtx,
3325
+ QueryCtx,
3326
+ MutationCtx
3327
+ >;
3328
+ ` : "";
3329
+ const generatedRuntimeTypeArgs = hasHandlerRegistry ? `
3330
+ QueryCtx,
3331
+ MutationCtx,
3332
+ ProcedureCallerRegistry,
3333
+ ActionCtx,
3334
+ ProcedureHandlerRegistry
3335
+ ` : `
3336
+ QueryCtx,
3337
+ MutationCtx,
3338
+ ProcedureCallerRegistry,
3339
+ ActionCtx
3340
+ `;
3341
+ const handlerExport = hasHandlerRegistry ? `
3342
+ export function ${handlerExportName}<TCtx extends ProcedureHandlerContext>(
3343
+ ctx: TCtx
3344
+ ): GeneratedProcedureHandler<TCtx> {
3345
+ return generatedRuntime.getHandlerFactory()(ctx) as GeneratedProcedureHandler<TCtx>;
3346
+ }
3347
+ ` : "";
3348
+ return `// biome-ignore-all format: generated
3349
+ /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-unused-vars */
3350
+ // This file is auto-generated by kitcn
3351
+ // Do not edit manually. Run \`kitcn codegen\` to regenerate.
3352
+
3353
+ import {
3354
+ createGeneratedFunctionReference,
3355
+ createGeneratedRegistryRuntime,
3356
+ typedProcedureResolver,
3357
+ type GeneratedRegistryCallerForContext,${hasHandlerRegistry ? "\n type GeneratedRegistryHandlerForContext," : ""}
3358
+ } from 'kitcn/server';
3359
+ ${runtimeApiTypesImport}import type { ActionCtx, MutationCtx, QueryCtx } from '${generatedServerImportPath}';
3360
+ import type { OrmTriggerContext } from 'kitcn/orm';
3361
+
3362
+ const procedureRegistry = {${callerRegistryBody}} as const;
3363
+ ${handlerRegistryDeclaration}
3364
+ type ProcedureCallerRegistry = typeof procedureRegistry;
3365
+ ${hasHandlerRegistry ? `type ProcedureHandlerRegistry = typeof handlerRegistry;
3366
+ ` : ""}
3367
+
3368
+ const generatedRuntime = createGeneratedRegistryRuntime<${generatedRuntimeTypeArgs}>({
3369
+ procedureRegistry,${hasHandlerRegistry ? "\n handlerRegistry," : ""}
3370
+ });
3371
+
3372
+ type MutationCallerContext = MutationCtx | OrmTriggerContext<any, MutationCtx>;
3373
+ type ProcedureCallerContext = QueryCtx | MutationCallerContext | ActionCtx;
3374
+ type GeneratedProcedureCaller<
3375
+ TCtx extends ProcedureCallerContext = ProcedureCallerContext,
3376
+ > = GeneratedRegistryCallerForContext<
3377
+ ProcedureCallerRegistry,
3378
+ TCtx,
3379
+ QueryCtx,
3380
+ MutationCallerContext,
3381
+ ActionCtx
3382
+ >;
3383
+ ${handlerTypeDeclarations}
3384
+
3385
+ export function ${callerExportName}<TCtx extends ProcedureCallerContext>(
3386
+ ctx: TCtx
3387
+ ): GeneratedProcedureCaller<TCtx> {
3388
+ return generatedRuntime.getCallerFactory()(
3389
+ ctx as any
3390
+ ) as GeneratedProcedureCaller<TCtx>;
3391
+ }
3392
+ ${handlerExport}
3393
+ `;
3394
+ }
3395
+ function hasNamedExport(filePath, exportName) {
3396
+ if (!fs.existsSync(filePath)) return false;
3397
+ const source = fs.readFileSync(filePath, "utf-8");
3398
+ if (new RegExp(`\\bexport\\s+(?:const|let|var|function|class|type|interface)\\s+${exportName}\\b`).test(source)) return true;
3399
+ for (const match of source.matchAll(/\bexport\s*{([^}]*)}/g)) {
3400
+ const exportList = match[1] ?? "";
3401
+ if (new RegExp(`\\b${exportName}\\b`).test(exportList)) return true;
3402
+ }
3403
+ return false;
3404
+ }
3405
+ function hasDefaultExport(filePath) {
3406
+ if (!fs.existsSync(filePath)) return false;
3407
+ const source = fs.readFileSync(filePath, "utf-8");
3408
+ return DEFAULT_EXPORT_RE.test(source);
3409
+ }
3410
+ function createApiTree(meta) {
3411
+ const root = {
3412
+ children: {},
3413
+ functions: []
3414
+ };
3415
+ for (const [moduleName, fns] of Object.entries(meta)) {
3416
+ const pathSegments = moduleName.split("/").filter(Boolean);
3417
+ let node = root;
3418
+ for (const segment of pathSegments) {
3419
+ node.children[segment] ??= {
3420
+ children: {},
3421
+ functions: []
3422
+ };
3423
+ node = node.children[segment];
3424
+ }
3425
+ for (const fnName of Object.keys(fns).sort()) {
3426
+ const fnMeta = fns[fnName] ?? {};
3427
+ const type = fnMeta.type;
3428
+ const fnType = type === "query" || type === "mutation" || type === "action" ? type : "query";
3429
+ node.functions.push({
3430
+ fnName,
3431
+ fnType,
3432
+ moduleName,
3433
+ fnMeta: {
3434
+ ...fnMeta,
3435
+ type: fnType
3436
+ }
3437
+ });
3438
+ }
3439
+ }
3440
+ return root;
3441
+ }
3442
+ function formatMetaValue(value) {
3443
+ if (typeof value === "string") return JSON.stringify(value);
3444
+ if (typeof value === "boolean" || typeof value === "number") return String(value);
3445
+ return null;
3446
+ }
3447
+ function emitFnMetaLiteral(fnMeta) {
3448
+ const metaProps = [];
3449
+ for (const [key, value] of Object.entries(fnMeta).sort(([a], [b]) => a.localeCompare(b))) {
3450
+ if (value === void 0) continue;
3451
+ const formatted = formatMetaValue(value);
3452
+ if (formatted !== null) metaProps.push(`${key}: ${formatted}`);
3453
+ }
3454
+ return `{ ${metaProps.join(", ")} }`;
3455
+ }
3456
+ function emitHttpRoutes(dedupedRoutes, indentLevel) {
3457
+ const indent = " ".repeat(indentLevel);
3458
+ const lines = [];
3459
+ for (const [routeKey, route] of Object.entries(dedupedRoutes).sort(([a], [b]) => a.localeCompare(b))) lines.push(`${indent}${formatKey(routeKey)}: { path: ${JSON.stringify(route.path)}, method: ${JSON.stringify(route.method)} },`);
3460
+ return lines;
3461
+ }
3462
+ function emitApiObject(tree, pathSegments, outputFile, functionsDir, indentLevel, dedupedRoutes, hasHttpRouterExport) {
3463
+ const indent = " ".repeat(indentLevel);
3464
+ const lines = [];
3465
+ const childKeys = Object.keys(tree.children).sort((a, b) => a.localeCompare(b));
3466
+ const functionEntries = [...tree.functions].sort((a, b) => a.fnName.localeCompare(b.fnName));
3467
+ const childSet = new Set(childKeys);
3468
+ for (const entry of functionEntries) if (childSet.has(entry.fnName)) throw new Error(`Codegen conflict at ${pathSegments.join("/")}: export "${entry.fnName}" conflicts with directory of same name.`);
3469
+ const mergedKeys = [...childKeys, ...functionEntries.map((entry) => entry.fnName)].sort((a, b) => a.localeCompare(b));
3470
+ for (const key of mergedKeys) {
3471
+ if (childSet.has(key)) {
3472
+ const childNode = tree.children[key];
3473
+ lines.push(`${indent}${formatKey(key)}: {`);
3474
+ lines.push(...emitApiObject(childNode, [...pathSegments, key], outputFile, functionsDir, indentLevel + 1, dedupedRoutes, hasHttpRouterExport));
3475
+ lines.push(`${indent}},`);
3476
+ continue;
3477
+ }
3478
+ const fnEntry = functionEntries.find((entry) => entry.fnName === key);
3479
+ if (!fnEntry) continue;
3480
+ const moduleImportPath = getModuleImportPath(outputFile, functionsDir, fnEntry.moduleName);
3481
+ const fnMetaLiteral = emitFnMetaLiteral(fnEntry.fnMeta);
3482
+ const functionRef = `createGeneratedFunctionReference<${JSON.stringify(fnEntry.fnType)}, "public", typeof import(${JSON.stringify(moduleImportPath)}).${key}>(${JSON.stringify(getGeneratedFunctionName(fnEntry.moduleName, key))})`;
3483
+ lines.push(`${indent}${formatKey(key)}: createApiLeaf<${JSON.stringify(fnEntry.fnType)}, typeof import(${JSON.stringify(moduleImportPath)}).${key}>(${functionRef}, ${fnMetaLiteral}),`);
3484
+ }
3485
+ if (pathSegments.length === 0) {
3486
+ if (hasHttpRouterExport) lines.push(`${indent}http: undefined as unknown as typeof httpRouter,`);
3487
+ lines.push(`${indent}_http: {`);
3488
+ lines.push(...emitHttpRoutes(dedupedRoutes, indentLevel + 1));
3489
+ lines.push(`${indent}},`);
3490
+ }
3491
+ return lines;
3492
+ }
3493
+ function emitProcedureRegistryEntries(entries, outputFile, functionsDir, moduleName, useGeneratedApiTypes) {
3494
+ return entries.map((entry) => {
3495
+ const pathKey = entry.moduleName === moduleName ? entry.exportName : [...entry.moduleName.split("/"), entry.exportName].join(".");
3496
+ const moduleImportPath = getModuleImportPath(outputFile, functionsDir, entry.moduleName);
3497
+ const functionRefTypeAccess = useGeneratedApiTypes ? getBracketAccessPath(entry.internal ? "generatedInternal" : "generatedApi", entry.exportName === "default" ? entry.moduleName.split("/") : [...entry.moduleName.split("/"), entry.exportName]) : `import(${JSON.stringify(moduleImportPath)}).${entry.exportName}`;
3498
+ const functionRefAccess = `createGeneratedFunctionReference<${JSON.stringify(entry.type)}, ${JSON.stringify(entry.internal ? "internal" : "public")}, typeof ${functionRefTypeAccess}>(${JSON.stringify(getGeneratedFunctionName(entry.moduleName, entry.exportName))})`;
3499
+ const resolver = `(require(${JSON.stringify(moduleImportPath)}) as Record<string, unknown>)[${JSON.stringify(entry.exportName)}]`;
3500
+ return ` ${JSON.stringify(pathKey)}: [${JSON.stringify(entry.type)}, typedProcedureResolver(${functionRefAccess}, () => ${resolver})],`;
3501
+ }).sort((a, b) => a.localeCompare(b));
3502
+ }
3503
+ function getGeneratedFunctionName(moduleName, exportName) {
3504
+ return exportName === "default" ? moduleName : `${moduleName}:${exportName}`;
3505
+ }
3506
+ function buildAuthRuntimeProcedureEntries(moduleName) {
3507
+ return AUTH_RUNTIME_PROCEDURES.map((entry) => ({
3508
+ ...entry,
3509
+ moduleName,
3510
+ kind: "crpc"
3511
+ }));
3512
+ }
3513
+ function buildGeneratedOrmRuntimeProcedureEntries(moduleName) {
3514
+ return GENERATED_ORM_RUNTIME_PROCEDURES.map((entry) => ({
3515
+ ...entry,
3516
+ moduleName,
3517
+ kind: "dispatch"
3518
+ }));
3519
+ }
3520
+ function partitionRuntimeEntriesForEmission(entries) {
3521
+ return {
3522
+ callerEntries: entries,
3523
+ handlerEntries: entries.filter((entry) => entry.kind === "crpc")
3524
+ };
3525
+ }
3526
+ function dedupeProcedureEntries(entries) {
3527
+ const seen = /* @__PURE__ */ new Set();
3528
+ const deduped = [];
3529
+ for (const entry of entries) {
3530
+ const key = `${entry.moduleName}.${entry.exportName}`;
3531
+ if (seen.has(key)) continue;
3532
+ seen.add(key);
3533
+ deduped.push(entry);
3534
+ }
3535
+ return deduped;
3536
+ }
3537
+ function shouldSuppressHttpParseWarning(error) {
3538
+ return MISSING_KITCN_IMPORT_RE.test(String(error));
3539
+ }
3540
+ function getConvexConfig(sharedDir) {
3541
+ const convexConfigPath = path.join(process.cwd(), "convex.json");
3542
+ const functionsDir = (fs.existsSync(convexConfigPath) ? JSON.parse(fs.readFileSync(convexConfigPath, "utf-8")) : {}).functions || "convex";
3543
+ return {
3544
+ functionsDir: path.join(process.cwd(), functionsDir),
3545
+ outputFile: path.join(process.cwd(), sharedDir || "convex/shared", "api.ts")
3546
+ };
3547
+ }
3548
+ /**
3549
+ * Check if a value is a CRPCHttpRouter (has _def.router === true)
3550
+ */
3551
+ function isCRPCHttpRouter(value) {
3552
+ return typeof value === "object" && value !== null && "_def" in value && value._def?.router === true;
3553
+ }
3554
+ /**
3555
+ * Import a module using jiti and extract cRPC metadata from exports.
3556
+ */
3557
+ async function parseModuleRuntime(filePath, jitiInstance, serverShimSpecifier) {
3558
+ const source = fs.readFileSync(filePath, "utf8");
3559
+ const rewrittenSource = source.replaceAll(/from\s+(['"])kitcn\/server\1/g, `from ${JSON.stringify(serverShimSpecifier)}`);
3560
+ const result = {};
3561
+ const httpRoutes = {};
3562
+ const procedures = [];
3563
+ const isHttp = filePath.endsWith("http.ts");
3564
+ const module = rewrittenSource === source ? await jitiInstance.import(filePath) : await jitiInstance.evalModule(rewrittenSource, {
3565
+ async: true,
3566
+ ext: ".ts",
3567
+ filename: filePath
3568
+ });
3569
+ if (!module || typeof module !== "object") {
3570
+ if (isHttp) logger.error(" http.ts: module is empty or not an object");
3571
+ return {
3572
+ meta: null,
3573
+ httpRoutes: {},
3574
+ procedures: []
3575
+ };
3576
+ }
3577
+ for (const [name, value] of Object.entries(module)) {
3578
+ if (name.startsWith("_")) continue;
3579
+ const meta = value?._crpcMeta;
3580
+ if (meta?.type) {
3581
+ procedures.push({
3582
+ exportName: name,
3583
+ internal: Boolean(meta.internal),
3584
+ type: meta.type
3585
+ });
3586
+ if (meta.internal) continue;
3587
+ const fnMeta = { type: meta.type };
3588
+ if (meta.auth) fnMeta.auth = meta.auth;
3589
+ for (const [key, val] of Object.entries(meta)) if (key !== "type" && key !== "internal" && val !== void 0) fnMeta[key] = val;
3590
+ result[name] = fnMeta;
3591
+ }
3592
+ const httpRoute = value?._crpcHttpRoute;
3593
+ if (httpRoute?.path && httpRoute?.method) httpRoutes[name] = {
3594
+ path: httpRoute.path,
3595
+ method: httpRoute.method
3596
+ };
3597
+ if (isCRPCHttpRouter(value)) for (const [procPath, procedure] of Object.entries(value._def.procedures)) {
3598
+ const route = procedure._crpcHttpRoute;
3599
+ if (route?.path && route?.method) httpRoutes[procPath] = {
3600
+ path: route.path,
3601
+ method: route.method
3602
+ };
3603
+ }
3604
+ }
3605
+ return {
3606
+ meta: Object.keys(result).length > 0 ? result : null,
3607
+ httpRoutes,
3608
+ procedures
3609
+ };
3610
+ }
3611
+ async function generateMeta(sharedDir, options) {
3612
+ const startTime = Date.now();
3613
+ const { functionsDir, outputFile } = getConvexConfig(sharedDir);
3614
+ const serverOutputFile = getGeneratedServerOutputFile(functionsDir);
3615
+ const ormOutputFile = getGeneratedOrmOutputFile(functionsDir);
3616
+ const crpcOutputFile = getGeneratedCrpcOutputFile(functionsDir);
3617
+ const authOutputFile = getGeneratedAuthOutputFile(functionsDir);
3618
+ const migrationsHelperOutputFile = getGeneratedMigrationsHelperOutputFile(functionsDir);
3619
+ const legacyGeneratedMigrationsOutputFile = path.join(functionsDir, GENERATED_DIR, "migrations.ts");
3620
+ const legacyGeneratedMigrationsRuntimeOutputFile = path.join(functionsDir, GENERATED_DIR, "migrations.runtime.ts");
3621
+ const legacyGeneratedMigrationsUnderscoreOutputFile = path.join(functionsDir, GENERATED_DIR, "_migrations.ts");
3622
+ const generatedAuthModuleName = getModuleNameFromOutputFile(authOutputFile, functionsDir);
3623
+ const debug = options?.debug ?? false;
3624
+ const silent = options?.silent ?? false;
3625
+ const { generateApi, generateAuth, modeLabel } = resolveGenerationMode(options);
3626
+ const normalizedTrimSegments = normalizeTrimSegments(options?.trimSegments);
3627
+ if (debug) if (generateApi) logger.info("Scanning Convex functions for cRPC metadata...\n");
3628
+ else logger.info(`Running kitcn codegen (mode=${modeLabel})...\n`);
3629
+ const meta = {};
3630
+ const allHttpRoutes = {};
3631
+ const procedureEntries = [];
3632
+ const procedureNameLookup = {};
3633
+ const fatalParseFailures = [];
3634
+ let createdRuntimePlaceholders = [];
3635
+ let createdSupportPlaceholders = [];
3636
+ const runtimeFilesPreservedFromParseFailures = /* @__PURE__ */ new Set();
3637
+ let totalFunctions = 0;
3638
+ const authFilePath = path.join(functionsDir, "auth.ts");
3639
+ const hasAuthFile = fs.existsSync(authFilePath);
3640
+ const hasAuthDefaultExport = hasDefaultExport(authFilePath);
3641
+ const authContract = {
3642
+ hasAuthFile,
3643
+ hasAuthDefaultExport
3644
+ };
3645
+ let sharedJitiInstance;
3646
+ const getSharedJitiInstance = () => sharedJitiInstance ??= createProjectJiti();
3647
+ const schemaMetadata = await resolveSchemaMetadataForCodegen(functionsDir, debug, getSharedJitiInstance);
3648
+ const hasOrmSchemaMetadata = schemaMetadata.hasOrmSchema;
3649
+ const hasRelationsMetadata = schemaMetadata.hasRelations;
3650
+ const hasRelationsExport = hasNamedExport(path.join(functionsDir, "schema.ts"), "relations");
3651
+ const hasSchemaTriggersExport = hasNamedExport(path.join(functionsDir, "schema.ts"), "triggers");
3652
+ const hasDedicatedTriggersExport = hasNamedExport(path.join(functionsDir, "triggers.ts"), "triggers");
3653
+ const hasMigrationsManifest = fs.existsSync(path.join(functionsDir, "migrations", "manifest.ts"));
3654
+ if (hasRelationsExport) throw new Error("Codegen error: do not export `relations` from schema.ts. Chain relations on the default schema export with `defineSchema(...).relations(...)`.");
3655
+ if (hasSchemaTriggersExport || hasDedicatedTriggersExport) throw new Error("Codegen error: do not export `triggers` from schema.ts or triggers.ts. Chain triggers on the default schema export with `defineSchema(...).relations(...).triggers(...)`.");
3656
+ const hasOrmSchema = hasOrmSchemaMetadata;
3657
+ createdSupportPlaceholders = ensureGeneratedSupportPlaceholders(functionsDir, { includeAuth: generateAuth });
3658
+ if (generateApi) {
3659
+ globalThis.__KITCN_CODEGEN__ = true;
3660
+ try {
3661
+ const jitiInstance = getSharedJitiInstance();
3662
+ const serverShimSpecifier = normalizeImportPath(getProjectServerParserShimPath());
3663
+ const files = listFilesRecursive(functionsDir).filter((file) => file.endsWith(".ts") && isValidConvexFile(file));
3664
+ const parseCandidateFiles = files.filter((file) => hasPotentialCodegenExports(fs.readFileSync(path.join(functionsDir, file), "utf8"), file));
3665
+ const existingRuntimeFilesBeforeParse = new Set(listGeneratedRuntimeFiles(functionsDir));
3666
+ const runtimePlaceholderModules = [...new Set([
3667
+ ...files.map((file) => file.replace(TS_EXTENSION_RE, "")),
3668
+ ...hasOrmSchema ? ["generated/server"] : [],
3669
+ ...generateAuth ? [generatedAuthModuleName] : []
3670
+ ])];
3671
+ createdRuntimePlaceholders = ensureGeneratedRuntimePlaceholders(functionsDir, runtimePlaceholderModules, resolveModuleRuntimeExportNames(runtimePlaceholderModules, normalizedTrimSegments));
3672
+ for (const file of parseCandidateFiles) {
3673
+ const filePath = path.join(functionsDir, file);
3674
+ const moduleName = file.replace(TS_EXTENSION_RE, "");
3675
+ try {
3676
+ const { meta: moduleMeta, httpRoutes, procedures } = await parseModuleRuntime(filePath, jitiInstance, serverShimSpecifier);
3677
+ if (moduleMeta) {
3678
+ meta[moduleName] = moduleMeta;
3679
+ const fnCount = Object.keys(moduleMeta).length;
3680
+ totalFunctions += fnCount;
3681
+ if (debug) logger.info(` ✓ ${moduleName}: ${fnCount} functions`);
3682
+ }
3683
+ if (Object.keys(httpRoutes).length > 0 && debug) logger.info(` ✓ ${moduleName}: ${Object.keys(httpRoutes).length} HTTP routes`);
3684
+ Object.assign(allHttpRoutes, httpRoutes);
3685
+ for (const procedure of procedures) procedureEntries.push({
3686
+ moduleName,
3687
+ exportName: procedure.exportName,
3688
+ internal: procedure.internal,
3689
+ type: procedure.type,
3690
+ kind: "crpc"
3691
+ });
3692
+ const procedureNameEntries = buildProcedureNameLookupEntries({
3693
+ file,
3694
+ filePath,
3695
+ moduleName,
3696
+ procedures
3697
+ });
3698
+ if (procedureNameEntries.length > 0) procedureNameLookup[file] = procedureNameEntries;
3699
+ } catch (error) {
3700
+ const runtimeFile = getGeneratedRuntimeOutputFile(functionsDir, moduleName);
3701
+ if (existingRuntimeFilesBeforeParse.has(runtimeFile)) runtimeFilesPreservedFromParseFailures.add(runtimeFile);
3702
+ const shouldLogParseFailure = debug || file === "http.ts" && !shouldSuppressHttpParseWarning(error);
3703
+ const shouldTreatParseFailureAsFatal = !(file === "http.ts" && shouldSuppressHttpParseWarning(error));
3704
+ if (shouldLogParseFailure) logger.error(` ⚠ Failed to parse ${file}:`, error);
3705
+ if (shouldTreatParseFailureAsFatal) fatalParseFailures.push({
3706
+ file,
3707
+ error
3708
+ });
3709
+ }
3710
+ }
3711
+ } finally {
3712
+ delete globalThis.__KITCN_CODEGEN__;
3713
+ }
3714
+ }
3715
+ if (fatalParseFailures.length > 0) {
3716
+ for (const createdRuntimePlaceholder of createdRuntimePlaceholders) fs.rmSync(createdRuntimePlaceholder, { force: true });
3717
+ for (const createdSupportPlaceholder of createdSupportPlaceholders) fs.rmSync(createdSupportPlaceholder, { force: true });
3718
+ const failureSummary = fatalParseFailures.map(({ file, error }) => `- ${file}: ${error instanceof Error ? error.message : String(error)}`).join("\n");
3719
+ throw new Error(`kitcn codegen aborted because module parsing failed:\n${failureSummary}`);
3720
+ }
3721
+ cleanupGeneratedPluginArtifacts(functionsDir);
3722
+ if (generateApi) {
3723
+ const routesByPath = /* @__PURE__ */ new Map();
3724
+ for (const [key, route] of Object.entries(allHttpRoutes)) {
3725
+ const pathKey = `${route.path}:${route.method}`;
3726
+ const existing = routesByPath.get(pathKey) || [];
3727
+ existing.push({
3728
+ key,
3729
+ route
3730
+ });
3731
+ routesByPath.set(pathKey, existing);
3732
+ }
3733
+ const dedupedRoutes = {};
3734
+ for (const entries of routesByPath.values()) {
3735
+ const best = entries.reduce((a, b) => a.key.length >= b.key.length ? a : b);
3736
+ dedupedRoutes[best.key] = best.route;
3737
+ }
3738
+ const schemaImportPath = getSchemaImportPath(outputFile, functionsDir);
3739
+ const httpImportPath = getHttpImportPath(outputFile, functionsDir);
3740
+ const hasTablesExport = hasNamedExport(path.join(functionsDir, "schema.ts"), "tables");
3741
+ const needsInferSelectModelImport = hasTablesExport;
3742
+ const needsInferInsertModelImport = hasTablesExport;
3743
+ const hasHttpRouterExport = hasNamedExport(path.join(functionsDir, "http.ts"), "httpRouter");
3744
+ const apiTree = createApiTree(meta);
3745
+ if (Object.hasOwn(apiTree.children, "http") || apiTree.functions.some((entry) => entry.fnName === "http")) throw new Error("Codegen conflict: root \"http\" namespace is reserved for generated HTTP router types. Rename your Convex module/function.");
3746
+ const apiObjectLines = emitApiObject(apiTree, [], outputFile, functionsDir, 1, dedupedRoutes, hasHttpRouterExport);
3747
+ const apiObjectBody = apiObjectLines.length > 0 ? `\n${apiObjectLines.join("\n")}\n` : "\n";
3748
+ const serverTypeImports = "import type { inferApiInputs, inferApiOutputs } from \"kitcn/server\";";
3749
+ const ormTypeImports = [needsInferInsertModelImport ? "InferInsertModel" : null, needsInferSelectModelImport ? "InferSelectModel" : null].filter((item) => !!item);
3750
+ const optionalImports = [
3751
+ ormTypeImports.length > 0 ? `import type { ${ormTypeImports.join(", ")} } from "kitcn/orm";` : null,
3752
+ hasHttpRouterExport ? `import type { httpRouter } from ${JSON.stringify(httpImportPath)};` : null,
3753
+ hasTablesExport ? `import type { tables } from ${JSON.stringify(schemaImportPath)};` : null
3754
+ ].filter((line) => !!line).join("\n");
3755
+ const apiTypeLine = "export type Api = typeof api;";
3756
+ const optionalTypeExports = [hasTablesExport ? `
3757
+ export type TableName = keyof typeof tables;
3758
+ export type Select<T extends TableName> = InferSelectModel<(typeof tables)[T]>;
3759
+ export type Insert<T extends TableName> = InferInsertModel<(typeof tables)[T]>;` : null].filter((entry) => !!entry).join("\n");
3760
+ const output = `// biome-ignore-all format: generated
3761
+ // This file is auto-generated by kitcn
3762
+ // Do not edit manually. Run \`kitcn codegen\` to regenerate.
3763
+
3764
+ import { createApiLeaf, createGeneratedFunctionReference } from "kitcn/server";
3765
+ ${serverTypeImports}
3766
+ ${optionalImports ? `\n${optionalImports}` : ""}
3767
+
3768
+ export const api = {${apiObjectBody}} as const;
3769
+
3770
+ ${apiTypeLine}
3771
+ export type ApiInputs = inferApiInputs<Api>;
3772
+ export type ApiOutputs = inferApiOutputs<Api>;
3773
+ ${optionalTypeExports}
3774
+ `;
3775
+ const outputDirname = path.dirname(outputFile);
3776
+ if (!fs.existsSync(outputDirname)) fs.mkdirSync(outputDirname, { recursive: true });
3777
+ writeFileIfChanged(outputFile, output);
3778
+ } else fs.rmSync(outputFile, { force: true });
3779
+ const serverOutput = emitGeneratedServerFile(serverOutputFile, functionsDir, hasOrmSchema, hasMigrationsManifest, procedureNameLookup);
3780
+ const generatedOutputDirname = path.dirname(serverOutputFile);
3781
+ if (!fs.existsSync(generatedOutputDirname)) fs.mkdirSync(generatedOutputDirname, { recursive: true });
3782
+ writeFileIfChanged(serverOutputFile, serverOutput);
3783
+ fs.rmSync(ormOutputFile, { force: true });
3784
+ fs.rmSync(crpcOutputFile, { force: true });
3785
+ writeFileIfChanged(migrationsHelperOutputFile, emitGeneratedMigrationsFile(migrationsHelperOutputFile, functionsDir, hasRelationsMetadata));
3786
+ fs.rmSync(legacyGeneratedMigrationsOutputFile, { force: true });
3787
+ fs.rmSync(legacyGeneratedMigrationsRuntimeOutputFile, { force: true });
3788
+ fs.rmSync(legacyGeneratedMigrationsUnderscoreOutputFile, { force: true });
3789
+ if (generateAuth) writeFileIfChanged(authOutputFile, emitGeneratedAuthFile(authOutputFile, functionsDir, hasOrmSchema, authContract));
3790
+ else fs.rmSync(authOutputFile, { force: true });
3791
+ fs.rmSync(getLegacyGeneratedOutputFile(functionsDir), { force: true });
3792
+ const mergedProcedureEntries = dedupeProcedureEntries([
3793
+ ...hasOrmSchema ? buildGeneratedOrmRuntimeProcedureEntries("generated/server") : [],
3794
+ ...generateApi ? procedureEntries : [],
3795
+ ...generateAuth && hasAuthDefaultExport ? buildAuthRuntimeProcedureEntries(generatedAuthModuleName) : []
3796
+ ]);
3797
+ const runtimeProcedureEntriesByModule = /* @__PURE__ */ new Map();
3798
+ for (const entry of mergedProcedureEntries) {
3799
+ if (RUNTIME_CALLER_RESERVED_EXPORTS.has(entry.exportName)) throw new Error(`Codegen conflict: "${entry.moduleName}.${entry.exportName}" uses reserved runtime caller namespace "${entry.exportName}". Rename the procedure export.`);
3800
+ const existingEntries = runtimeProcedureEntriesByModule.get(entry.moduleName);
3801
+ if (existingEntries) {
3802
+ existingEntries.push(entry);
3803
+ continue;
3804
+ }
3805
+ runtimeProcedureEntriesByModule.set(entry.moduleName, [entry]);
3806
+ }
3807
+ const runtimeOutputFiles = [];
3808
+ const runtimeExportNames = resolveModuleRuntimeExportNames([...runtimeProcedureEntriesByModule.keys()], normalizedTrimSegments);
3809
+ for (const [moduleName, moduleEntries] of [...runtimeProcedureEntriesByModule].sort(([moduleA], [moduleB]) => moduleA.localeCompare(moduleB))) {
3810
+ const runtimeOutputFile = getGeneratedRuntimeOutputFile(functionsDir, moduleName);
3811
+ const runtimeOutput = emitGeneratedModuleRuntimeFile(runtimeOutputFile, functionsDir, moduleName, moduleEntries, runtimeExportNames);
3812
+ fs.mkdirSync(path.dirname(runtimeOutputFile), { recursive: true });
3813
+ writeFileIfChanged(runtimeOutputFile, runtimeOutput);
3814
+ runtimeOutputFiles.push(runtimeOutputFile);
3815
+ }
3816
+ const runtimeOutputFileSet = new Set(runtimeOutputFiles);
3817
+ const existingRuntimeFiles = listGeneratedRuntimeFiles(functionsDir);
3818
+ for (const existingRuntimeFile of existingRuntimeFiles) {
3819
+ if (runtimeOutputFileSet.has(existingRuntimeFile) || runtimeFilesPreservedFromParseFailures.has(existingRuntimeFile)) continue;
3820
+ fs.rmSync(existingRuntimeFile, { force: true });
3821
+ }
3822
+ for (const createdRuntimePlaceholder of createdRuntimePlaceholders) {
3823
+ if (runtimeOutputFileSet.has(createdRuntimePlaceholder) || runtimeFilesPreservedFromParseFailures.has(createdRuntimePlaceholder)) continue;
3824
+ fs.rmSync(createdRuntimePlaceholder, { force: true });
3825
+ }
3826
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(2);
3827
+ const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", {
3828
+ hour12: false,
3829
+ hour: "2-digit",
3830
+ minute: "2-digit",
3831
+ second: "2-digit"
3832
+ });
3833
+ if (!silent) if (debug) {
3834
+ if (generateApi) logger.success(`\nGenerated ${outputFile}`);
3835
+ else logger.info(`\nRemoved ${outputFile}`);
3836
+ logger.success(`Generated ${serverOutputFile}`);
3837
+ logger.success(`Generated ${migrationsHelperOutputFile}`);
3838
+ if (generateAuth) logger.success(`Generated ${authOutputFile}`);
3839
+ else logger.info(`Removed ${authOutputFile}`);
3840
+ for (const runtimeOutputFile of runtimeOutputFiles) logger.success(`Generated ${runtimeOutputFile}`);
3841
+ if (generateApi) logger.info(` ${Object.keys(meta).length} modules, ${totalFunctions} functions`);
3842
+ else logger.info(" cRPC scan skipped for scoped generation");
3843
+ } else logger.success(`${time} Convex api ready! (${elapsed}s)`);
3844
+ }
3845
+
3846
+ //#endregion
3847
+ //#region src/cli/config.ts
3848
+ const DEFAULT_JSON_CONFIG_PATH = "kitcn.json";
3849
+ const LEGACY_JSON_CONFIG_PATH = "concave.json";
3850
+ const CODEGEN_SCOPES = new Set([
3851
+ "all",
3852
+ "auth",
3853
+ "orm"
3854
+ ]);
3855
+ const BACKFILL_ENABLED_VALUES = new Set([
3856
+ "auto",
3857
+ "on",
3858
+ "off"
3859
+ ]);
3860
+ const BACKEND_VALUES = new Set(["convex", "concave"]);
3861
+ function createDefaultConfig() {
3862
+ return {
3863
+ backend: "convex",
3864
+ paths: {
3865
+ lib: "convex/lib",
3866
+ shared: "convex/shared"
3867
+ },
3868
+ hooks: { postAdd: [] },
3869
+ dev: {
3870
+ debug: false,
3871
+ args: [],
3872
+ aggregateBackfill: {
3873
+ enabled: "auto",
3874
+ wait: true,
3875
+ batchSize: 1e3,
3876
+ pollIntervalMs: 1e3,
3877
+ timeoutMs: 9e5,
3878
+ strict: false
3879
+ },
3880
+ migrations: {
3881
+ enabled: "auto",
3882
+ wait: true,
3883
+ batchSize: 256,
3884
+ pollIntervalMs: 1e3,
3885
+ timeoutMs: 9e5,
3886
+ strict: false,
3887
+ allowDrift: true
3888
+ }
3889
+ },
3890
+ codegen: {
3891
+ debug: false,
3892
+ args: [],
3893
+ trimSegments: ["plugins"]
3894
+ },
3895
+ deploy: {
3896
+ args: [],
3897
+ aggregateBackfill: {
3898
+ enabled: "auto",
3899
+ wait: true,
3900
+ batchSize: 1e3,
3901
+ pollIntervalMs: 1e3,
3902
+ timeoutMs: 9e5,
3903
+ strict: true
3904
+ },
3905
+ migrations: {
3906
+ enabled: "auto",
3907
+ wait: true,
3908
+ batchSize: 256,
3909
+ pollIntervalMs: 1e3,
3910
+ timeoutMs: 9e5,
3911
+ strict: true,
3912
+ allowDrift: false
3913
+ }
3914
+ }
3915
+ };
3916
+ }
3917
+ function isRecord(value) {
3918
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3919
+ }
3920
+ function parseBoolean(value, fieldName, configPath) {
3921
+ if (typeof value === "boolean") return value;
3922
+ throw new Error(`Invalid ${fieldName} in ${configPath}: expected boolean, got ${typeof value}.`);
3923
+ }
3924
+ function parseStringArray(value, fieldName, configPath) {
3925
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) throw new Error(`Invalid ${fieldName} in ${configPath}: expected string array.`);
3926
+ return [...value];
3927
+ }
3928
+ function parseTrimSegments(value, fieldName, configPath) {
3929
+ const segments = parseStringArray(value, fieldName, configPath).map((segment) => segment.trim()).filter((segment) => segment.length > 0);
3930
+ for (const segment of segments) if (segment.includes("/") || segment.includes("\\")) throw new Error(`Invalid ${fieldName} in ${configPath}: segment "${segment}" must not contain path separators.`);
3931
+ return [...new Set(segments)];
3932
+ }
3933
+ function parsePositiveInteger(value, fieldName, configPath) {
3934
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) return value;
3935
+ throw new Error(`Invalid ${fieldName} in ${configPath}: expected a positive integer.`);
3936
+ }
3937
+ function parseNonEmptyString(value, fieldName, configPath) {
3938
+ if (typeof value === "string" && value.trim().length > 0) return value;
3939
+ throw new Error(`Invalid ${fieldName} in ${configPath}: expected non-empty string.`);
3940
+ }
3941
+ function parseSafeRelativePath(value, fieldName, configPath) {
3942
+ if (typeof value !== "string" || value.trim().length === 0) throw new Error(`Invalid ${fieldName} in ${configPath}: expected non-empty string.`);
3943
+ if (value.includes("\0")) throw new Error(`Invalid ${fieldName} in ${configPath}: null byte is not allowed.`);
3944
+ if (path.isAbsolute(value)) throw new Error(`Invalid ${fieldName} in ${configPath}: absolute paths are not allowed.`);
3945
+ const normalizedPosix = path.posix.normalize(value.replace(/\\/g, "/"));
3946
+ if (normalizedPosix === "." || normalizedPosix === ".." || normalizedPosix.startsWith("../") || normalizedPosix.startsWith("/")) throw new Error(`Invalid ${fieldName} in ${configPath}: path traversal is not allowed.`);
3947
+ return normalizedPosix;
3948
+ }
3949
+ function parseScope(value, fieldName, configPath) {
3950
+ if (typeof value === "string" && CODEGEN_SCOPES.has(value)) return value;
3951
+ throw new Error(`Invalid ${fieldName} in ${configPath}: expected one of all, auth, orm.`);
3952
+ }
3953
+ function parseBackend(value, fieldName, configPath) {
3954
+ if (typeof value === "string" && BACKEND_VALUES.has(value)) return value;
3955
+ throw new Error(`Invalid ${fieldName} in ${configPath}: expected one of convex, concave.`);
3956
+ }
3957
+ function parseBackfillEnabled(value, fieldName, configPath) {
3958
+ if (value === true) return "on";
3959
+ if (value === false) return "off";
3960
+ if (typeof value === "string" && BACKFILL_ENABLED_VALUES.has(value)) return value;
3961
+ throw new Error(`Invalid ${fieldName} in ${configPath}: expected boolean or one of auto, on, off.`);
3962
+ }
3963
+ function parseAggregateBackfillConfig(value, fieldName, configPath) {
3964
+ if (!isRecord(value)) throw new Error(`Invalid ${fieldName} in ${configPath}: expected object.`);
3965
+ assertNoUnknownKeys(value, [
3966
+ "enabled",
3967
+ "wait",
3968
+ "batchSize",
3969
+ "pollIntervalMs",
3970
+ "timeoutMs",
3971
+ "strict"
3972
+ ], configPath, fieldName);
3973
+ const parsed = {};
3974
+ if ("enabled" in value) parsed.enabled = parseBackfillEnabled(value.enabled, `${fieldName}.enabled`, configPath);
3975
+ if ("wait" in value) parsed.wait = parseBoolean(value.wait, `${fieldName}.wait`, configPath);
3976
+ if ("batchSize" in value) parsed.batchSize = parsePositiveInteger(value.batchSize, `${fieldName}.batchSize`, configPath);
3977
+ if ("pollIntervalMs" in value) parsed.pollIntervalMs = parsePositiveInteger(value.pollIntervalMs, `${fieldName}.pollIntervalMs`, configPath);
3978
+ if ("timeoutMs" in value) parsed.timeoutMs = parsePositiveInteger(value.timeoutMs, `${fieldName}.timeoutMs`, configPath);
3979
+ if ("strict" in value) parsed.strict = parseBoolean(value.strict, `${fieldName}.strict`, configPath);
3980
+ return parsed;
3981
+ }
3982
+ function parseMigrationConfig(value, fieldName, configPath) {
3983
+ if (!isRecord(value)) throw new Error(`Invalid ${fieldName} in ${configPath}: expected object.`);
3984
+ assertNoUnknownKeys(value, [
3985
+ "enabled",
3986
+ "wait",
3987
+ "batchSize",
3988
+ "pollIntervalMs",
3989
+ "timeoutMs",
3990
+ "strict",
3991
+ "allowDrift"
3992
+ ], configPath, fieldName);
3993
+ const parsed = {};
3994
+ if ("enabled" in value) parsed.enabled = parseBackfillEnabled(value.enabled, `${fieldName}.enabled`, configPath);
3995
+ if ("wait" in value) parsed.wait = parseBoolean(value.wait, `${fieldName}.wait`, configPath);
3996
+ if ("batchSize" in value) parsed.batchSize = parsePositiveInteger(value.batchSize, `${fieldName}.batchSize`, configPath);
3997
+ if ("pollIntervalMs" in value) parsed.pollIntervalMs = parsePositiveInteger(value.pollIntervalMs, `${fieldName}.pollIntervalMs`, configPath);
3998
+ if ("timeoutMs" in value) parsed.timeoutMs = parsePositiveInteger(value.timeoutMs, `${fieldName}.timeoutMs`, configPath);
3999
+ if ("strict" in value) parsed.strict = parseBoolean(value.strict, `${fieldName}.strict`, configPath);
4000
+ if ("allowDrift" in value) parsed.allowDrift = parseBoolean(value.allowDrift, `${fieldName}.allowDrift`, configPath);
4001
+ return parsed;
4002
+ }
4003
+ function assertNoUnknownKeys(value, allowedKeys, configPath, scope) {
4004
+ const allowed = new Set(allowedKeys);
4005
+ for (const key of Object.keys(value)) {
4006
+ if (allowed.has(key)) continue;
4007
+ const qualifiedKey = scope ? `${scope}.${key}` : key;
4008
+ throw new Error(`Unknown config key "${qualifiedKey}" in ${configPath}.`);
4009
+ }
4010
+ }
4011
+ function parseCommandConfig(value, fieldName, configPath) {
4012
+ if (!isRecord(value)) throw new Error(`Invalid ${fieldName} in ${configPath}: expected object.`);
4013
+ assertNoUnknownKeys(value, fieldName === "dev" ? [
4014
+ "debug",
4015
+ "args",
4016
+ "preRun",
4017
+ "aggregateBackfill",
4018
+ "migrations"
4019
+ ] : [
4020
+ "debug",
4021
+ "args",
4022
+ "scope",
4023
+ "trimSegments"
4024
+ ], configPath, fieldName);
4025
+ const parsed = {};
4026
+ if ("debug" in value) parsed.debug = parseBoolean(value.debug, `${fieldName}.debug`, configPath);
4027
+ if ("args" in value) parsed.args = parseStringArray(value.args, `${fieldName}.args`, configPath);
4028
+ if (fieldName === "dev" && "preRun" in value && value.preRun !== void 0) parsed.preRun = parseNonEmptyString(value.preRun, `${fieldName}.preRun`, configPath);
4029
+ if (fieldName === "codegen" && "scope" in value && value.scope !== void 0) parsed.scope = parseScope(value.scope, `${fieldName}.scope`, configPath);
4030
+ if (fieldName === "codegen" && "trimSegments" in value && value.trimSegments !== void 0) parsed.trimSegments = parseTrimSegments(value.trimSegments, `${fieldName}.trimSegments`, configPath);
4031
+ if (fieldName === "dev" && "aggregateBackfill" in value && value.aggregateBackfill !== void 0) parsed.aggregateBackfill = parseAggregateBackfillConfig(value.aggregateBackfill, `${fieldName}.aggregateBackfill`, configPath);
4032
+ if (fieldName === "dev" && "migrations" in value && value.migrations !== void 0) parsed.migrations = parseMigrationConfig(value.migrations, `${fieldName}.migrations`, configPath);
4033
+ return parsed;
4034
+ }
4035
+ function parseDeployConfig(value, configPath) {
4036
+ if (!isRecord(value)) throw new Error(`Invalid deploy in ${configPath}: expected object.`);
4037
+ assertNoUnknownKeys(value, [
4038
+ "args",
4039
+ "aggregateBackfill",
4040
+ "migrations"
4041
+ ], configPath, "deploy");
4042
+ const parsed = {};
4043
+ if ("args" in value) parsed.args = parseStringArray(value.args, "deploy.args", configPath);
4044
+ if ("aggregateBackfill" in value && value.aggregateBackfill !== void 0) parsed.aggregateBackfill = parseAggregateBackfillConfig(value.aggregateBackfill, "deploy.aggregateBackfill", configPath);
4045
+ if ("migrations" in value && value.migrations !== void 0) parsed.migrations = parseMigrationConfig(value.migrations, "deploy.migrations", configPath);
4046
+ return parsed;
4047
+ }
4048
+ function parseHooksConfig(value, configPath) {
4049
+ if (!isRecord(value)) throw new Error(`Invalid hooks in ${configPath}: expected object.`);
4050
+ assertNoUnknownKeys(value, ["postAdd"], configPath, "hooks");
4051
+ const parsed = {};
4052
+ if ("postAdd" in value && value.postAdd !== void 0) parsed.postAdd = parseStringArray(value.postAdd, "hooks.postAdd", configPath).map((script) => script.trim()).filter((script) => script.length > 0);
4053
+ return parsed;
4054
+ }
4055
+ function parsePathsConfig(value, configPath) {
4056
+ if (!isRecord(value)) throw new Error(`Invalid paths in ${configPath}: expected object.`);
4057
+ assertNoUnknownKeys(value, [
4058
+ "lib",
4059
+ "shared",
4060
+ "env"
4061
+ ], configPath, "paths");
4062
+ const parsed = {};
4063
+ if ("lib" in value && value.lib !== void 0) parsed.lib = parseSafeRelativePath(value.lib, "paths.lib", configPath);
4064
+ if ("shared" in value && value.shared !== void 0) parsed.shared = parseSafeRelativePath(value.shared, "paths.shared", configPath);
4065
+ if ("env" in value && value.env !== void 0) parsed.env = parseSafeRelativePath(value.env, "paths.env", configPath);
4066
+ return parsed;
4067
+ }
4068
+ function resolveDefaultConfigPath(configPathArg) {
4069
+ if (typeof configPathArg === "string") return {
4070
+ resolvedPath: path.resolve(process.cwd(), configPathArg),
4071
+ explicit: true,
4072
+ legacyPath: null
4073
+ };
4074
+ const jsonPath = path.resolve(process.cwd(), DEFAULT_JSON_CONFIG_PATH);
4075
+ if (fs.existsSync(jsonPath)) return {
4076
+ resolvedPath: jsonPath,
4077
+ explicit: false,
4078
+ legacyPath: null
4079
+ };
4080
+ const legacyPath = path.resolve(process.cwd(), LEGACY_JSON_CONFIG_PATH);
4081
+ if (fs.existsSync(legacyPath)) return {
4082
+ resolvedPath: null,
4083
+ explicit: false,
4084
+ legacyPath
4085
+ };
4086
+ return {
4087
+ resolvedPath: null,
4088
+ explicit: false,
4089
+ legacyPath: null
4090
+ };
4091
+ }
4092
+ function loadRawConfigFile(resolvedConfigPath) {
4093
+ if (path.extname(resolvedConfigPath).toLowerCase() !== ".json") throw new Error(`Only JSON config files are supported. Received: ${resolvedConfigPath}`);
4094
+ return JSON.parse(fs.readFileSync(resolvedConfigPath, "utf-8"));
4095
+ }
4096
+ function loadCliConfig(configPathArg) {
4097
+ const { resolvedPath: resolvedConfigPath, explicit: hasExplicitConfigPath, legacyPath } = resolveDefaultConfigPath(configPathArg);
4098
+ if (!hasExplicitConfigPath && legacyPath) throw new Error(`Legacy config file ${LEGACY_JSON_CONFIG_PATH} is no longer supported. Use ${DEFAULT_JSON_CONFIG_PATH}.`);
4099
+ if (!resolvedConfigPath || !fs.existsSync(resolvedConfigPath)) {
4100
+ if (hasExplicitConfigPath) throw new Error(`Config file not found: ${resolvedConfigPath ?? String(configPathArg)}`);
4101
+ return createDefaultConfig();
4102
+ }
4103
+ let rawConfig;
4104
+ try {
4105
+ rawConfig = loadRawConfigFile(resolvedConfigPath);
4106
+ } catch (error) {
4107
+ throw new Error(`Failed to parse config file ${resolvedConfigPath}: ${error.message}`);
4108
+ }
4109
+ if (!isRecord(rawConfig)) throw new Error(`Invalid config file ${resolvedConfigPath}: expected top-level object.`);
4110
+ const parsedConfig = rawConfig;
4111
+ assertNoUnknownKeys(parsedConfig, [
4112
+ "backend",
4113
+ "paths",
4114
+ "hooks",
4115
+ "dev",
4116
+ "codegen",
4117
+ "deploy"
4118
+ ], resolvedConfigPath);
4119
+ const config = createDefaultConfig();
4120
+ if ("backend" in parsedConfig && parsedConfig.backend !== void 0) config.backend = parseBackend(parsedConfig.backend, "backend", resolvedConfigPath);
4121
+ if ("hooks" in parsedConfig && parsedConfig.hooks !== void 0) {
4122
+ const parsed = parseHooksConfig(parsedConfig.hooks, resolvedConfigPath);
4123
+ if (parsed.postAdd !== void 0) config.hooks.postAdd = parsed.postAdd;
4124
+ }
4125
+ if ("paths" in parsedConfig && parsedConfig.paths !== void 0) {
4126
+ const parsed = parsePathsConfig(parsedConfig.paths, resolvedConfigPath);
4127
+ if (parsed.lib !== void 0) config.paths.lib = parsed.lib;
4128
+ if (parsed.shared !== void 0) config.paths.shared = parsed.shared;
4129
+ if (parsed.env !== void 0) config.paths.env = parsed.env;
4130
+ }
4131
+ if ("dev" in parsedConfig) {
4132
+ const parsed = parseCommandConfig(parsedConfig.dev, "dev", resolvedConfigPath);
4133
+ if (parsed.debug !== void 0) config.dev.debug = parsed.debug;
4134
+ if (parsed.args !== void 0) config.dev.args = parsed.args;
4135
+ if (parsed.preRun !== void 0) config.dev.preRun = parsed.preRun;
4136
+ if (parsed.aggregateBackfill !== void 0) config.dev.aggregateBackfill = {
4137
+ ...config.dev.aggregateBackfill,
4138
+ ...parsed.aggregateBackfill
4139
+ };
4140
+ if (parsed.migrations !== void 0) config.dev.migrations = {
4141
+ ...config.dev.migrations,
4142
+ ...parsed.migrations
4143
+ };
4144
+ }
4145
+ if ("codegen" in parsedConfig) {
4146
+ const parsed = parseCommandConfig(parsedConfig.codegen, "codegen", resolvedConfigPath);
4147
+ if (parsed.debug !== void 0) config.codegen.debug = parsed.debug;
4148
+ if (parsed.args !== void 0) config.codegen.args = parsed.args;
4149
+ if (parsed.scope !== void 0) config.codegen.scope = parsed.scope;
4150
+ if (parsed.trimSegments !== void 0) config.codegen.trimSegments = parsed.trimSegments;
4151
+ }
4152
+ if ("deploy" in parsedConfig) {
4153
+ const parsed = parseDeployConfig(parsedConfig.deploy, resolvedConfigPath);
4154
+ if (parsed.args !== void 0) config.deploy.args = parsed.args;
4155
+ if (parsed.aggregateBackfill !== void 0) config.deploy.aggregateBackfill = {
4156
+ ...config.deploy.aggregateBackfill,
4157
+ ...parsed.aggregateBackfill
4158
+ };
4159
+ if (parsed.migrations !== void 0) config.deploy.migrations = {
4160
+ ...config.deploy.migrations,
4161
+ ...parsed.migrations
4162
+ };
4163
+ }
4164
+ return config;
4165
+ }
4166
+ function resolveConfiguredBackend(params) {
4167
+ return params.backendArg ?? params.config?.backend ?? "convex";
4168
+ }
4169
+
4170
+ //#endregion
4171
+ //#region src/cli/local-env.ts
4172
+ /**
4173
+ * Local `.env` resolution for codegen and backend spawns.
4174
+ *
4175
+ * Lives outside `backend-core.ts` so the long-lived `kitcn dev` watcher child
4176
+ * can reach it without pulling the CLI's full command graph (execa, the prompt
4177
+ * stack, the analyzer) into a process that only runs codegen.
4178
+ */
4179
+ const resolveEnvPaths = (sharedDir) => {
4180
+ const { functionsDir } = getConvexConfig(sharedDir);
4181
+ return {
4182
+ backendEnvPath: join(functionsDir, "..", ".env"),
4183
+ rootEnvPath: join(process.cwd(), ".env")
4184
+ };
4185
+ };
4186
+ const mergeEnvFiles = (envPaths) => {
4187
+ const mergedEnv = {};
4188
+ for (const envPath of envPaths) {
4189
+ if (!fs.existsSync(envPath)) continue;
4190
+ Object.assign(mergedEnv, loadDotenv().parse(fs.readFileSync(envPath, "utf8")));
4191
+ }
4192
+ return mergedEnv;
4193
+ };
4194
+ function getLocalParseEnvVars(sharedDir, backend) {
4195
+ const { backendEnvPath, rootEnvPath } = resolveEnvPaths(sharedDir);
4196
+ return mergeEnvFiles(backend === "concave" ? [backendEnvPath, rootEnvPath] : [rootEnvPath, backendEnvPath]);
4197
+ }
4198
+ function getLocalBackendEnvVars(sharedDir, backend) {
4199
+ const { backendEnvPath, rootEnvPath } = resolveEnvPaths(sharedDir);
4200
+ return mergeEnvFiles(backend === "concave" ? [backendEnvPath, rootEnvPath] : [backendEnvPath]);
4201
+ }
4202
+ async function withLocalCodegenEnv(sharedDir, backend, fn) {
4203
+ const envVars = getLocalParseEnvVars(sharedDir, backend);
4204
+ if (Object.keys(envVars).length === 0) return fn();
4205
+ const previousValues = /* @__PURE__ */ new Map();
4206
+ for (const [key, value] of Object.entries(envVars)) {
4207
+ previousValues.set(key, process.env[key]);
4208
+ process.env[key] = value;
4209
+ }
4210
+ try {
4211
+ return await fn();
4212
+ } finally {
4213
+ for (const [key, value] of previousValues.entries()) if (value === void 0) delete process.env[key];
4214
+ else process.env[key] = value;
4215
+ }
4216
+ }
4217
+
4218
+ //#endregion
4219
+ 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 };