@prisma-next/sql-contract-ts 0.3.0-dev.134 → 0.3.0-dev.135

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.
@@ -1,8 +1,556 @@
1
1
  import { ContractBuilder, ModelBuilder, TableBuilder, createTable } from "@prisma-next/contract-authoring";
2
- import { applyFkDefaults } from "@prisma-next/sql-contract/types";
2
+ import { instantiateAuthoringFieldPreset, instantiateAuthoringTypeConstructor, isAuthoringFieldPresetDescriptor, isAuthoringTypeConstructorDescriptor, validateAuthoringHelperArguments } from "@prisma-next/contract/framework-components";
3
3
  import { ifDefined } from "@prisma-next/utils/defined";
4
+ import { applyFkDefaults } from "@prisma-next/sql-contract/types";
5
+ import { validateStorageSemantics } from "@prisma-next/sql-contract/validators";
4
6
 
5
- //#region src/contract-builder.ts
7
+ //#region src/authoring-helper-runtime.ts
8
+ function isNamedConstraintOptionsLike(value) {
9
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
10
+ if (Object.keys(value).some((key) => key !== "name")) return false;
11
+ const name = value.name;
12
+ return name === void 0 || typeof name === "string";
13
+ }
14
+ const blockedSegments = new Set([
15
+ "__proto__",
16
+ "constructor",
17
+ "prototype"
18
+ ]);
19
+ function assertSafeHelperKey(key, path) {
20
+ if (blockedSegments.has(key)) throw new Error(`Invalid authoring helper "${[...path, key].join(".")}". Helper path segments must not use "${key}".`);
21
+ }
22
+ function createTypeHelpersFromNamespace(namespace, path = []) {
23
+ const helpers = {};
24
+ for (const [key, value] of Object.entries(namespace)) {
25
+ assertSafeHelperKey(key, path);
26
+ const currentPath = [...path, key];
27
+ if (isAuthoringTypeConstructorDescriptor(value)) {
28
+ const helperPath = currentPath.join(".");
29
+ helpers[key] = (...args) => {
30
+ validateAuthoringHelperArguments(helperPath, value.args, args);
31
+ return instantiateAuthoringTypeConstructor(value, args);
32
+ };
33
+ continue;
34
+ }
35
+ helpers[key] = createTypeHelpersFromNamespace(value, currentPath);
36
+ }
37
+ return helpers;
38
+ }
39
+ function createFieldPresetHelper(options) {
40
+ return (...rawArgs) => {
41
+ const acceptsNamedConstraintOptions = options.descriptor.output.id === true || options.descriptor.output.unique === true;
42
+ const declaredArguments = options.descriptor.args ?? [];
43
+ if (acceptsNamedConstraintOptions && rawArgs.length > declaredArguments.length + 1) throw new Error(`${options.helperPath} expects at most ${declaredArguments.length + 1} argument(s), received ${rawArgs.length}`);
44
+ let args = rawArgs;
45
+ let namedConstraintOptions;
46
+ if (acceptsNamedConstraintOptions && rawArgs.length === declaredArguments.length + 1) {
47
+ const maybeNamedConstraintOptions = rawArgs.at(-1);
48
+ if (!isNamedConstraintOptionsLike(maybeNamedConstraintOptions)) throw new Error(`${options.helperPath} accepts an optional trailing { name?: string } constraint options object`);
49
+ namedConstraintOptions = maybeNamedConstraintOptions;
50
+ args = rawArgs.slice(0, -1);
51
+ }
52
+ validateAuthoringHelperArguments(options.helperPath, options.descriptor.args, args);
53
+ return options.build({
54
+ args,
55
+ ...namedConstraintOptions ? { namedConstraintOptions } : {}
56
+ });
57
+ };
58
+ }
59
+ function createFieldHelpersFromNamespace(namespace, createLeafHelper, path = []) {
60
+ const helpers = {};
61
+ for (const [key, value] of Object.entries(namespace)) {
62
+ assertSafeHelperKey(key, path);
63
+ const currentPath = [...path, key];
64
+ if (isAuthoringFieldPresetDescriptor(value)) {
65
+ helpers[key] = createLeafHelper({
66
+ helperPath: currentPath.join("."),
67
+ descriptor: value
68
+ });
69
+ continue;
70
+ }
71
+ helpers[key] = createFieldHelpersFromNamespace(value, createLeafHelper, currentPath);
72
+ }
73
+ return helpers;
74
+ }
75
+
76
+ //#endregion
77
+ //#region src/staged-contract-dsl.ts
78
+ function isColumnDefault(value) {
79
+ if (typeof value !== "object" || value === null) return false;
80
+ const kind = value.kind;
81
+ return kind === "literal" || kind === "function";
82
+ }
83
+ function toColumnDefault(value) {
84
+ if (isColumnDefault(value)) return value;
85
+ return {
86
+ kind: "literal",
87
+ value
88
+ };
89
+ }
90
+ var ScalarFieldBuilder = class ScalarFieldBuilder {
91
+ constructor(state) {
92
+ this.state = state;
93
+ }
94
+ optional() {
95
+ return new ScalarFieldBuilder({
96
+ ...this.state,
97
+ nullable: true
98
+ });
99
+ }
100
+ column(name) {
101
+ return new ScalarFieldBuilder({
102
+ ...this.state,
103
+ columnName: name
104
+ });
105
+ }
106
+ default(value) {
107
+ return new ScalarFieldBuilder({
108
+ ...this.state,
109
+ default: toColumnDefault(value)
110
+ });
111
+ }
112
+ defaultSql(expression) {
113
+ return new ScalarFieldBuilder({
114
+ ...this.state,
115
+ default: {
116
+ kind: "function",
117
+ expression
118
+ }
119
+ });
120
+ }
121
+ id(options) {
122
+ return new ScalarFieldBuilder({
123
+ ...this.state,
124
+ id: options?.name ? { name: options.name } : {}
125
+ });
126
+ }
127
+ unique(options) {
128
+ return new ScalarFieldBuilder({
129
+ ...this.state,
130
+ unique: options?.name ? { name: options.name } : {}
131
+ });
132
+ }
133
+ sql(spec) {
134
+ const idSpec = "id" in spec ? spec.id : void 0;
135
+ const uniqueSpec = "unique" in spec ? spec.unique : void 0;
136
+ if (idSpec && !this.state.id) throw new Error("field.sql({ id }) requires an existing inline .id(...) declaration.");
137
+ if (uniqueSpec && !this.state.unique) throw new Error("field.sql({ unique }) requires an existing inline .unique(...) declaration.");
138
+ return new ScalarFieldBuilder({
139
+ ...this.state,
140
+ ...spec.column ? { columnName: spec.column } : {},
141
+ ...idSpec ? { id: { name: idSpec.name } } : {},
142
+ ...uniqueSpec ? { unique: { name: uniqueSpec.name } } : {}
143
+ });
144
+ }
145
+ build() {
146
+ return this.state;
147
+ }
148
+ };
149
+ function columnField(descriptor) {
150
+ return new ScalarFieldBuilder({
151
+ kind: "scalar",
152
+ descriptor,
153
+ nullable: false
154
+ });
155
+ }
156
+ function generatedField(spec) {
157
+ return new ScalarFieldBuilder({
158
+ kind: "scalar",
159
+ descriptor: {
160
+ ...spec.type,
161
+ ...spec.typeParams ? { typeParams: spec.typeParams } : {}
162
+ },
163
+ nullable: false,
164
+ executionDefault: spec.generated
165
+ });
166
+ }
167
+ function namedTypeField(typeRef) {
168
+ return new ScalarFieldBuilder({
169
+ kind: "scalar",
170
+ typeRef,
171
+ nullable: false
172
+ });
173
+ }
174
+ function buildFieldPreset(descriptor, args, namedConstraintOptions) {
175
+ const preset = instantiateAuthoringFieldPreset(descriptor, args);
176
+ return new ScalarFieldBuilder({
177
+ kind: "scalar",
178
+ descriptor: preset.descriptor,
179
+ nullable: preset.nullable,
180
+ ...ifDefined("default", preset.default),
181
+ ...ifDefined("executionDefault", preset.executionDefault),
182
+ ...preset.id ? { id: namedConstraintOptions?.name ? { name: namedConstraintOptions.name } : {} } : {},
183
+ ...preset.unique ? { unique: namedConstraintOptions?.name ? { name: namedConstraintOptions.name } : {} } : {}
184
+ });
185
+ }
186
+ var RelationBuilder = class RelationBuilder {
187
+ constructor(state) {
188
+ this.state = state;
189
+ }
190
+ sql(spec) {
191
+ if (this.state.kind !== "belongsTo") throw new Error("relation.sql(...) is only supported for belongsTo relations.");
192
+ return new RelationBuilder({
193
+ ...this.state,
194
+ sql: spec
195
+ });
196
+ }
197
+ build() {
198
+ return this.state;
199
+ }
200
+ };
201
+ function normalizeFieldRefInput(input) {
202
+ return (Array.isArray(input) ? input : [input]).map((ref) => ref.fieldName);
203
+ }
204
+ function normalizeTargetFieldRefInput(input) {
205
+ const refs = Array.isArray(input) ? input : [input];
206
+ const [first] = refs;
207
+ if (!first) throw new Error("Expected at least one target ref");
208
+ if (refs.some((ref) => ref.modelName !== first.modelName)) throw new Error("All target refs in a foreign key must point to the same model");
209
+ return {
210
+ modelName: first.modelName,
211
+ fieldNames: refs.map((ref) => ref.fieldName),
212
+ source: refs.some((ref) => ref.source === "string") ? "string" : "token"
213
+ };
214
+ }
215
+ function createConstraintsDsl() {
216
+ function ref(modelName, fieldName) {
217
+ return {
218
+ kind: "targetFieldRef",
219
+ source: "string",
220
+ modelName,
221
+ fieldName
222
+ };
223
+ }
224
+ function id(fieldOrFields, options) {
225
+ return {
226
+ kind: "id",
227
+ fields: normalizeFieldRefInput(fieldOrFields),
228
+ ...options?.name ? { name: options.name } : {}
229
+ };
230
+ }
231
+ function unique(fieldOrFields, options) {
232
+ return {
233
+ kind: "unique",
234
+ fields: normalizeFieldRefInput(fieldOrFields),
235
+ ...options?.name ? { name: options.name } : {}
236
+ };
237
+ }
238
+ function index(fieldOrFields, options) {
239
+ return {
240
+ kind: "index",
241
+ fields: normalizeFieldRefInput(fieldOrFields),
242
+ ...options?.name ? { name: options.name } : {},
243
+ ...options?.using ? { using: options.using } : {},
244
+ ...options?.config ? { config: options.config } : {}
245
+ };
246
+ }
247
+ function foreignKey(fieldOrFields, target, options) {
248
+ const normalizedTarget = normalizeTargetFieldRefInput(target);
249
+ return {
250
+ kind: "fk",
251
+ fields: normalizeFieldRefInput(fieldOrFields),
252
+ targetModel: normalizedTarget.modelName,
253
+ targetFields: normalizedTarget.fieldNames,
254
+ targetSource: normalizedTarget.source,
255
+ ...options?.name ? { name: options.name } : {},
256
+ ...options?.onDelete ? { onDelete: options.onDelete } : {},
257
+ ...options?.onUpdate ? { onUpdate: options.onUpdate } : {},
258
+ ...options?.constraint !== void 0 ? { constraint: options.constraint } : {},
259
+ ...options?.index !== void 0 ? { index: options.index } : {}
260
+ };
261
+ }
262
+ return {
263
+ ref,
264
+ id,
265
+ unique,
266
+ index,
267
+ foreignKey
268
+ };
269
+ }
270
+ function createFieldRefs(fields) {
271
+ const refs = {};
272
+ for (const fieldName of Object.keys(fields)) refs[fieldName] = {
273
+ kind: "columnRef",
274
+ fieldName
275
+ };
276
+ return refs;
277
+ }
278
+ function createModelTokenRefs(modelName, fields) {
279
+ const refs = {};
280
+ for (const fieldName of Object.keys(fields)) refs[fieldName] = {
281
+ kind: "targetFieldRef",
282
+ source: "token",
283
+ modelName,
284
+ fieldName
285
+ };
286
+ return refs;
287
+ }
288
+ function buildStageSpec(stageInput, context) {
289
+ if (typeof stageInput === "function") return stageInput(context);
290
+ return stageInput;
291
+ }
292
+ function createAttributeConstraintsDsl() {
293
+ const constraints = createConstraintsDsl();
294
+ return {
295
+ id: constraints.id,
296
+ unique: constraints.unique
297
+ };
298
+ }
299
+ function createSqlConstraintsDsl() {
300
+ const constraints = createConstraintsDsl();
301
+ return {
302
+ index: constraints.index,
303
+ foreignKey: constraints.foreignKey,
304
+ ref: constraints.ref
305
+ };
306
+ }
307
+ function createColumnRefs(fields) {
308
+ return createFieldRefs(fields);
309
+ }
310
+ function findDuplicateRelationName(existingRelations, nextRelations) {
311
+ return Object.keys(nextRelations).find((relationName) => Object.hasOwn(existingRelations, relationName));
312
+ }
313
+ var StagedModelBuilder = class StagedModelBuilder {
314
+ refs;
315
+ constructor(stageOne, attributesFactory, sqlFactory) {
316
+ this.stageOne = stageOne;
317
+ this.attributesFactory = attributesFactory;
318
+ this.sqlFactory = sqlFactory;
319
+ this.refs = stageOne.modelName ? createModelTokenRefs(stageOne.modelName, stageOne.fields) : void 0;
320
+ }
321
+ ref(fieldName) {
322
+ const modelName = this.stageOne.modelName;
323
+ if (!modelName) throw new Error("Model tokens require model(\"ModelName\", ...) before calling .ref(...)");
324
+ return {
325
+ kind: "targetFieldRef",
326
+ source: "token",
327
+ modelName,
328
+ fieldName
329
+ };
330
+ }
331
+ relations(relations) {
332
+ const duplicateRelationName = findDuplicateRelationName(this.stageOne.relations, relations);
333
+ if (duplicateRelationName) throw new Error(`Model "${this.stageOne.modelName ?? "<anonymous>"}" already defines relation "${duplicateRelationName}".`);
334
+ return new StagedModelBuilder({
335
+ ...this.stageOne,
336
+ relations: {
337
+ ...this.stageOne.relations,
338
+ ...relations
339
+ }
340
+ }, this.attributesFactory, this.sqlFactory);
341
+ }
342
+ attributes(specOrFactory) {
343
+ return new StagedModelBuilder(this.stageOne, specOrFactory, this.sqlFactory);
344
+ }
345
+ sql(specOrFactory) {
346
+ return new StagedModelBuilder(this.stageOne, this.attributesFactory, specOrFactory);
347
+ }
348
+ buildAttributesSpec() {
349
+ if (!this.attributesFactory) return;
350
+ return buildStageSpec(this.attributesFactory, {
351
+ fields: createFieldRefs(this.stageOne.fields),
352
+ constraints: createAttributeConstraintsDsl()
353
+ });
354
+ }
355
+ buildSqlSpec() {
356
+ if (!this.sqlFactory) return;
357
+ return buildStageSpec(this.sqlFactory, {
358
+ cols: createColumnRefs(this.stageOne.fields),
359
+ constraints: createSqlConstraintsDsl()
360
+ });
361
+ }
362
+ };
363
+ function isLazyRelationModelName(value) {
364
+ return typeof value === "object" && value !== null && "kind" in value && value.kind === "lazyRelationModelName" && "resolve" in value && typeof value.resolve === "function";
365
+ }
366
+ function resolveNamedModelTokenName(token) {
367
+ const modelName = token.stageOne.modelName;
368
+ if (!modelName) throw new Error("Relation targets require named model tokens. Use model(\"ModelName\", ...) before passing a token to rel.*(...).");
369
+ return modelName;
370
+ }
371
+ function normalizeRelationModelSource(target) {
372
+ if (typeof target === "string") return {
373
+ kind: "relationModelName",
374
+ source: "string",
375
+ modelName: target
376
+ };
377
+ if (typeof target === "function") return {
378
+ kind: "lazyRelationModelName",
379
+ source: "lazyToken",
380
+ resolve: () => resolveNamedModelTokenName(target())
381
+ };
382
+ return {
383
+ kind: "relationModelName",
384
+ source: "token",
385
+ modelName: resolveNamedModelTokenName(target)
386
+ };
387
+ }
388
+ function model(modelNameOrInput, maybeInput) {
389
+ const input = typeof modelNameOrInput === "string" ? maybeInput : modelNameOrInput;
390
+ if (!input) throw new Error("model(\"ModelName\", ...) requires a model definition.");
391
+ return new StagedModelBuilder({
392
+ ...typeof modelNameOrInput === "string" ? { modelName: modelNameOrInput } : {},
393
+ fields: input.fields,
394
+ relations: input.relations ?? {}
395
+ });
396
+ }
397
+ function belongsTo(toModel, options) {
398
+ return new RelationBuilder({
399
+ kind: "belongsTo",
400
+ toModel: normalizeRelationModelSource(toModel),
401
+ from: options.from,
402
+ to: options.to
403
+ });
404
+ }
405
+ function hasMany(toModel, options) {
406
+ return new RelationBuilder({
407
+ kind: "hasMany",
408
+ toModel: normalizeRelationModelSource(toModel),
409
+ by: options.by
410
+ });
411
+ }
412
+ function hasOne(toModel, options) {
413
+ return new RelationBuilder({
414
+ kind: "hasOne",
415
+ toModel: normalizeRelationModelSource(toModel),
416
+ by: options.by
417
+ });
418
+ }
419
+ function manyToMany(toModel, options) {
420
+ return new RelationBuilder({
421
+ kind: "manyToMany",
422
+ toModel: normalizeRelationModelSource(toModel),
423
+ through: normalizeRelationModelSource(options.through),
424
+ from: options.from,
425
+ to: options.to
426
+ });
427
+ }
428
+ const rel = {
429
+ belongsTo,
430
+ hasMany,
431
+ hasOne,
432
+ manyToMany
433
+ };
434
+ const field = {
435
+ column: columnField,
436
+ generated: generatedField,
437
+ namedType: namedTypeField
438
+ };
439
+ function isStagedContractInput(value) {
440
+ if (typeof value !== "object" || value === null || !("target" in value) || !("family" in value)) return false;
441
+ const target = value.target;
442
+ const family = value.family;
443
+ return typeof target === "object" && target !== null && "kind" in target && target.kind === "target" && typeof family === "object" && family !== null && "kind" in family && family.kind === "family";
444
+ }
445
+ function isRelationFieldArray(value) {
446
+ return Array.isArray(value);
447
+ }
448
+ function normalizeRelationFieldNames(value) {
449
+ if (isRelationFieldArray(value)) return value;
450
+ return [value];
451
+ }
452
+ function resolveRelationModelName(value) {
453
+ if (isLazyRelationModelName(value)) return value.resolve();
454
+ return value.modelName;
455
+ }
456
+ function applyNaming(name, strategy) {
457
+ if (!strategy || strategy === "identity") return name;
458
+ let result = "";
459
+ for (let index = 0; index < name.length; index += 1) {
460
+ const char = name[index];
461
+ if (!char) continue;
462
+ const lower = char.toLowerCase();
463
+ if (char !== lower && index > 0) {
464
+ const prev = name[index - 1];
465
+ const next = name[index + 1];
466
+ const prevIsLower = !!prev && prev === prev.toLowerCase();
467
+ const nextIsLower = !!next && next === next.toLowerCase();
468
+ if (prevIsLower || nextIsLower) result += "_";
469
+ }
470
+ result += lower;
471
+ }
472
+ return result;
473
+ }
474
+
475
+ //#endregion
476
+ //#region src/composed-authoring-helpers.ts
477
+ function extractTypeNamespace(pack) {
478
+ return pack.authoring?.type ?? {};
479
+ }
480
+ function extractFieldNamespace(pack) {
481
+ return pack.authoring?.field ?? {};
482
+ }
483
+ function mergeHelperNamespaces(target, source, path, leafGuard, label) {
484
+ const assertSafePath = (currentPath) => {
485
+ const blockedSegment = currentPath.find((segment) => segment === "__proto__" || segment === "constructor" || segment === "prototype");
486
+ if (blockedSegment) throw new Error(`Invalid authoring ${label} helper "${currentPath.join(".")}". Helper path segments must not use "${blockedSegment}".`);
487
+ };
488
+ for (const [key, sourceValue] of Object.entries(source)) {
489
+ const currentPath = [...path, key];
490
+ assertSafePath(currentPath);
491
+ const hasExistingValue = Object.hasOwn(target, key);
492
+ const existingValue = hasExistingValue ? target[key] : void 0;
493
+ if (!hasExistingValue) {
494
+ target[key] = sourceValue;
495
+ continue;
496
+ }
497
+ const existingIsLeaf = leafGuard(existingValue);
498
+ const sourceIsLeaf = leafGuard(sourceValue);
499
+ if (existingIsLeaf || sourceIsLeaf) throw new Error(`Duplicate authoring ${label} helper "${currentPath.join(".")}". Helper names must be unique across composed packs.`);
500
+ mergeHelperNamespaces(existingValue, sourceValue, currentPath, leafGuard, label);
501
+ }
502
+ }
503
+ function composeTypeNamespace(components) {
504
+ const merged = {};
505
+ for (const component of components) {
506
+ const ns = extractTypeNamespace(component);
507
+ if (Object.keys(ns).length > 0) mergeHelperNamespaces(merged, ns, [], isAuthoringTypeConstructorDescriptor, "type");
508
+ }
509
+ return merged;
510
+ }
511
+ function composeFieldNamespace(components) {
512
+ const merged = {};
513
+ for (const component of components) {
514
+ const ns = extractFieldNamespace(component);
515
+ if (Object.keys(ns).length > 0) mergeHelperNamespaces(merged, ns, [], isAuthoringFieldPresetDescriptor, "field");
516
+ }
517
+ return merged;
518
+ }
519
+ function createComposedFieldHelpers(components) {
520
+ const helperNamespace = createFieldHelpersFromNamespace(composeFieldNamespace(components), ({ helperPath, descriptor }) => createFieldPresetHelper({
521
+ helperPath,
522
+ descriptor,
523
+ build: ({ args, namedConstraintOptions }) => buildFieldPreset(descriptor, args, namedConstraintOptions)
524
+ }));
525
+ const coreFieldHelpers = {
526
+ column: field.column,
527
+ generated: field.generated,
528
+ namedType: field.namedType
529
+ };
530
+ const coreHelperNames = new Set(Object.keys(coreFieldHelpers));
531
+ for (const helperName of Object.keys(helperNamespace)) if (coreHelperNames.has(helperName)) throw new Error(`Duplicate authoring field helper "${helperName}". Core field helpers reserve that name.`);
532
+ return {
533
+ ...coreFieldHelpers,
534
+ ...helperNamespace
535
+ };
536
+ }
537
+ function createComposedAuthoringHelpers(options) {
538
+ const extensionValues = Object.values(options.extensionPacks ?? {});
539
+ const components = [
540
+ options.family,
541
+ options.target,
542
+ ...extensionValues
543
+ ];
544
+ return {
545
+ field: createComposedFieldHelpers(components),
546
+ model,
547
+ rel,
548
+ type: createTypeHelpersFromNamespace(composeTypeNamespace(components))
549
+ };
550
+ }
551
+
552
+ //#endregion
553
+ //#region src/contract-ir-builder.ts
6
554
  function isPlainObject(value) {
7
555
  if (typeof value !== "object" || value === null) return false;
8
556
  const proto = Object.getPrototypeOf(value);
@@ -41,6 +589,692 @@ function encodeColumnDefault(defaultInput) {
41
589
  value: encodeDefaultLiteralValue(defaultInput.value)
42
590
  };
43
591
  }
592
+ function assertStorageSemantics(storage) {
593
+ const semanticErrors = validateStorageSemantics(storage);
594
+ if (semanticErrors.length > 0) throw new Error(`Contract semantic validation failed: ${semanticErrors.join("; ")}`);
595
+ }
596
+ function buildContractIR(state) {
597
+ if (!state.target) throw new Error("target is required. Call .target() before .build()");
598
+ const target = state.target;
599
+ const storageTables = {};
600
+ const executionDefaults = [];
601
+ for (const tableName of Object.keys(state.tables)) {
602
+ const tableState = state.tables[tableName];
603
+ if (!tableState) continue;
604
+ const columns = {};
605
+ for (const columnName in tableState.columns) {
606
+ const columnState = tableState.columns[columnName];
607
+ if (!columnState) continue;
608
+ const encodedDefault = columnState.default !== void 0 ? encodeColumnDefault(columnState.default) : void 0;
609
+ columns[columnName] = {
610
+ nativeType: columnState.nativeType,
611
+ codecId: columnState.type,
612
+ nullable: columnState.nullable ?? false,
613
+ ...ifDefined("typeParams", columnState.typeParams),
614
+ ...ifDefined("default", encodedDefault),
615
+ ...ifDefined("typeRef", columnState.typeRef)
616
+ };
617
+ if ("executionDefault" in columnState && columnState.executionDefault) executionDefaults.push({
618
+ ref: {
619
+ table: tableName,
620
+ column: columnName
621
+ },
622
+ onCreate: columnState.executionDefault
623
+ });
624
+ }
625
+ storageTables[tableName] = {
626
+ columns,
627
+ uniques: (tableState.uniques ?? []).map((u) => ({
628
+ columns: u.columns,
629
+ ...u.name ? { name: u.name } : {}
630
+ })),
631
+ indexes: (tableState.indexes ?? []).map((i) => ({
632
+ columns: i.columns,
633
+ ...i.name ? { name: i.name } : {},
634
+ ...i.using ? { using: i.using } : {},
635
+ ...i.config ? { config: i.config } : {}
636
+ })),
637
+ foreignKeys: (tableState.foreignKeys ?? []).map((fk) => ({
638
+ columns: fk.columns,
639
+ references: fk.references,
640
+ ...applyFkDefaults(fk, state.foreignKeyDefaults),
641
+ ...fk.name ? { name: fk.name } : {},
642
+ ...fk.onDelete !== void 0 ? { onDelete: fk.onDelete } : {},
643
+ ...fk.onUpdate !== void 0 ? { onUpdate: fk.onUpdate } : {}
644
+ })),
645
+ ...tableState.primaryKey ? { primaryKey: {
646
+ columns: tableState.primaryKey,
647
+ ...tableState.primaryKeyName ? { name: tableState.primaryKeyName } : {}
648
+ } } : {}
649
+ };
650
+ }
651
+ const storage = {
652
+ tables: storageTables,
653
+ types: state.storageTypes ?? {}
654
+ };
655
+ const execution = executionDefaults.length > 0 ? { mutations: { defaults: executionDefaults.sort((a, b) => {
656
+ const tableCompare = a.ref.table.localeCompare(b.ref.table);
657
+ if (tableCompare !== 0) return tableCompare;
658
+ return a.ref.column.localeCompare(b.ref.column);
659
+ }) } } : void 0;
660
+ const models = {};
661
+ for (const modelName in state.models) {
662
+ const modelState = state.models[modelName];
663
+ if (!modelState) continue;
664
+ const tableName = modelState.table;
665
+ const tableState = state.tables[tableName];
666
+ const tableColumns = tableState ? tableState.columns : {};
667
+ const storageFields = {};
668
+ const domainFields = {};
669
+ for (const fieldName in modelState.fields) {
670
+ const columnName = modelState.fields[fieldName];
671
+ if (!columnName) continue;
672
+ storageFields[fieldName] = { column: columnName };
673
+ const column = tableColumns[columnName];
674
+ if (column) domainFields[fieldName] = {
675
+ codecId: column.type,
676
+ nullable: column.nullable ?? false
677
+ };
678
+ }
679
+ const modelRelations = {};
680
+ if (modelState.relations) for (const relName in modelState.relations) {
681
+ const rel$1 = modelState.relations[relName];
682
+ if (!rel$1) continue;
683
+ modelRelations[relName] = {
684
+ to: rel$1.to,
685
+ cardinality: rel$1.cardinality,
686
+ on: {
687
+ localFields: rel$1.on.parentCols,
688
+ targetFields: rel$1.on.childCols
689
+ }
690
+ };
691
+ }
692
+ models[modelName] = {
693
+ storage: {
694
+ table: tableName,
695
+ fields: storageFields
696
+ },
697
+ fields: domainFields,
698
+ relations: modelRelations
699
+ };
700
+ }
701
+ const extensionNamespaces = state.extensionNamespaces ?? [];
702
+ const extensionPacks = { ...state.extensionPacks || {} };
703
+ for (const namespace of extensionNamespaces) if (!Object.hasOwn(extensionPacks, namespace)) extensionPacks[namespace] = {};
704
+ const contract = {
705
+ schemaVersion: "1",
706
+ target,
707
+ targetFamily: "sql",
708
+ storageHash: state.storageHash || "sha256:ts-builder-placeholder",
709
+ models,
710
+ roots: {},
711
+ storage,
712
+ ...execution ? { execution } : {},
713
+ extensionPacks,
714
+ capabilities: state.capabilities || {},
715
+ meta: {},
716
+ sources: {}
717
+ };
718
+ assertStorageSemantics(contract.storage);
719
+ return contract;
720
+ }
721
+ function assertKnownTargetModel(modelsByName, sourceModelName, targetModelName, context) {
722
+ const targetModel = modelsByName.get(targetModelName);
723
+ if (!targetModel) throw new Error(`${context} on model "${sourceModelName}" references unknown model "${targetModelName}"`);
724
+ return targetModel;
725
+ }
726
+ function assertTargetTableMatches(sourceModelName, targetModel, referencedTableName, context) {
727
+ if (targetModel.tableName !== referencedTableName) throw new Error(`${context} on model "${sourceModelName}" references table "${referencedTableName}" but model "${targetModel.modelName}" maps to "${targetModel.tableName}"`);
728
+ }
729
+ function buildSqlContractFromSemanticDefinition(definition) {
730
+ const modelsByName = new Map(definition.models.map((m) => [m.modelName, m]));
731
+ const tables = {};
732
+ for (const model$1 of definition.models) {
733
+ const columns = {};
734
+ for (const field$1 of model$1.fields) {
735
+ if (field$1.executionDefault) {
736
+ if (field$1.default !== void 0) throw new Error(`Field "${model$1.modelName}.${field$1.fieldName}" cannot define both default and executionDefault.`);
737
+ if (field$1.nullable) throw new Error(`Field "${model$1.modelName}.${field$1.fieldName}" cannot be nullable when executionDefault is present.`);
738
+ columns[field$1.columnName] = {
739
+ name: field$1.columnName,
740
+ type: field$1.descriptor.codecId,
741
+ nativeType: field$1.descriptor.nativeType,
742
+ nullable: false,
743
+ ...ifDefined("typeParams", field$1.descriptor.typeParams),
744
+ ...ifDefined("typeRef", field$1.descriptor.typeRef),
745
+ executionDefault: field$1.executionDefault
746
+ };
747
+ continue;
748
+ }
749
+ columns[field$1.columnName] = {
750
+ name: field$1.columnName,
751
+ type: field$1.descriptor.codecId,
752
+ nativeType: field$1.descriptor.nativeType,
753
+ nullable: field$1.nullable,
754
+ ...ifDefined("typeParams", field$1.descriptor.typeParams),
755
+ ...ifDefined("typeRef", field$1.descriptor.typeRef),
756
+ ...ifDefined("default", field$1.default)
757
+ };
758
+ }
759
+ if (model$1.id) {
760
+ const fieldsByColumnName = new Map(model$1.fields.map((field$1) => [field$1.columnName, field$1]));
761
+ for (const columnName of model$1.id.columns) {
762
+ const field$1 = fieldsByColumnName.get(columnName);
763
+ if (field$1?.nullable) throw new Error(`Model "${model$1.modelName}" uses nullable field "${field$1.fieldName}" in its identity.`);
764
+ }
765
+ }
766
+ const foreignKeys = (model$1.foreignKeys ?? []).map((fk) => {
767
+ const targetModel = assertKnownTargetModel(modelsByName, model$1.modelName, fk.references.model, "Foreign key");
768
+ assertTargetTableMatches(model$1.modelName, targetModel, fk.references.table, "Foreign key");
769
+ return {
770
+ columns: fk.columns,
771
+ references: {
772
+ table: fk.references.table,
773
+ columns: fk.references.columns
774
+ },
775
+ ...ifDefined("name", fk.name),
776
+ ...ifDefined("onDelete", fk.onDelete),
777
+ ...ifDefined("onUpdate", fk.onUpdate),
778
+ ...ifDefined("constraint", fk.constraint),
779
+ ...ifDefined("index", fk.index)
780
+ };
781
+ });
782
+ tables[model$1.tableName] = {
783
+ name: model$1.tableName,
784
+ columns,
785
+ ...model$1.id ? { primaryKey: model$1.id.columns } : {},
786
+ ...model$1.id?.name ? { primaryKeyName: model$1.id.name } : {},
787
+ uniques: model$1.uniques ?? [],
788
+ indexes: model$1.indexes ?? [],
789
+ foreignKeys
790
+ };
791
+ }
792
+ const modelStates = {};
793
+ for (const model$1 of definition.models) {
794
+ const fields = {};
795
+ for (const field$1 of model$1.fields) fields[field$1.fieldName] = field$1.columnName;
796
+ const relations = {};
797
+ for (const relation of model$1.relations ?? []) {
798
+ const targetModel = assertKnownTargetModel(modelsByName, model$1.modelName, relation.toModel, "Relation");
799
+ assertTargetTableMatches(model$1.modelName, targetModel, relation.toTable, "Relation");
800
+ if (relation.cardinality === "N:M" && !relation.through) throw new Error(`Relation "${model$1.modelName}.${relation.fieldName}" with cardinality "N:M" requires through metadata`);
801
+ relations[relation.fieldName] = {
802
+ to: relation.toModel,
803
+ cardinality: relation.cardinality,
804
+ on: {
805
+ parentCols: relation.on.parentColumns,
806
+ childCols: relation.on.childColumns
807
+ },
808
+ ...relation.through ? { through: {
809
+ table: relation.through.table,
810
+ parentCols: relation.through.parentColumns,
811
+ childCols: relation.through.childColumns
812
+ } } : void 0
813
+ };
814
+ }
815
+ modelStates[model$1.modelName] = {
816
+ name: model$1.modelName,
817
+ table: model$1.tableName,
818
+ fields,
819
+ relations
820
+ };
821
+ }
822
+ const extensionNamespaces = definition.extensionPacks ? Object.values(definition.extensionPacks).map((pack) => pack.id) : void 0;
823
+ return buildContractIR({
824
+ target: definition.target.targetId,
825
+ tables,
826
+ models: modelStates,
827
+ ...ifDefined("storageTypes", definition.storageTypes),
828
+ ...ifDefined("storageHash", definition.storageHash),
829
+ ...ifDefined("extensionPacks", definition.extensionPacks),
830
+ ...ifDefined("capabilities", definition.capabilities),
831
+ ...ifDefined("foreignKeyDefaults", definition.foreignKeyDefaults),
832
+ ...ifDefined("extensionNamespaces", extensionNamespaces)
833
+ });
834
+ }
835
+
836
+ //#endregion
837
+ //#region src/staged-contract-warnings.ts
838
+ function hasNamedModelToken(models, modelName) {
839
+ return models[modelName]?.stageOne.modelName === modelName;
840
+ }
841
+ function formatFieldSelection(fieldNames) {
842
+ if (fieldNames.length === 1) return `'${fieldNames[0]}'`;
843
+ return `[${fieldNames.map((fieldName) => `'${fieldName}'`).join(", ")}]`;
844
+ }
845
+ function formatTokenFieldSelection(modelName, fieldNames) {
846
+ if (fieldNames.length === 1) return `${modelName}.refs.${fieldNames[0]}`;
847
+ return `[${fieldNames.map((fieldName) => `${modelName}.refs.${fieldName}`).join(", ")}]`;
848
+ }
849
+ function formatConstraintsRefCall(modelName, fieldNames) {
850
+ if (fieldNames.length === 1) return `constraints.ref('${modelName}', '${fieldNames[0]}')`;
851
+ return `[${fieldNames.map((fieldName) => `constraints.ref('${modelName}', '${fieldName}')`).join(", ")}]`;
852
+ }
853
+ function formatRelationModelDisplay(relationModel) {
854
+ if (relationModel.kind === "lazyRelationModelName") return `() => ${relationModel.resolve()}`;
855
+ return relationModel.source === "string" ? `'${relationModel.modelName}'` : relationModel.modelName;
856
+ }
857
+ function formatRelationCall(relation, targetModelDisplay) {
858
+ if (relation.kind === "belongsTo") return `rel.belongsTo(${targetModelDisplay}, { from: ${formatFieldSelection(normalizeRelationFieldNames(relation.from))}, to: ${formatFieldSelection(normalizeRelationFieldNames(relation.to))} })`;
859
+ if (relation.kind === "hasMany" || relation.kind === "hasOne") {
860
+ const by = formatFieldSelection(normalizeRelationFieldNames(relation.by));
861
+ return `rel.${relation.kind}(${targetModelDisplay}, { by: ${by} })`;
862
+ }
863
+ return `rel.manyToMany(${targetModelDisplay}, { through: ${formatRelationModelDisplay(relation.through)}, from: ${formatFieldSelection(normalizeRelationFieldNames(relation.from))}, to: ${formatFieldSelection(normalizeRelationFieldNames(relation.to))} })`;
864
+ }
865
+ function formatManyToManyCallWithThrough(relation, throughDisplay) {
866
+ return `rel.manyToMany(${formatRelationModelDisplay(relation.toModel)}, { through: ${throughDisplay}, from: ${formatFieldSelection(normalizeRelationFieldNames(relation.from))}, to: ${formatFieldSelection(normalizeRelationFieldNames(relation.to))} })`;
867
+ }
868
+ const WARNING_BATCH_THRESHOLD = 5;
869
+ function flushWarnings(warnings) {
870
+ if (warnings.length === 0) return;
871
+ if (warnings.length <= WARNING_BATCH_THRESHOLD) {
872
+ for (const message of warnings) process.emitWarning(message, { code: "PN_CONTRACT_TYPED_FALLBACK_AVAILABLE" });
873
+ return;
874
+ }
875
+ process.emitWarning(`${warnings.length} staged contract references use string fallbacks where typed alternatives are available. Use named model tokens and typed storage type refs for autocomplete and type safety.
876
+ ` + warnings.map((w) => ` - ${w}`).join("\n"), { code: "PN_CONTRACT_TYPED_FALLBACK_AVAILABLE" });
877
+ }
878
+ function formatFallbackWarning(location, current, suggested) {
879
+ return `Staged contract ${location} uses ${current}. Use ${suggested} when the named model token is available in the same contract to keep typed relation targets and model refs.`;
880
+ }
881
+ function emitTypedNamedTypeFallbackWarnings(models, storageTypes) {
882
+ const warnings = [];
883
+ const warnedFields = /* @__PURE__ */ new Set();
884
+ for (const [modelName, modelDefinition] of Object.entries(models)) for (const [fieldName, fieldBuilder] of Object.entries(modelDefinition.stageOne.fields)) {
885
+ const fieldState = fieldBuilder.build();
886
+ if (typeof fieldState.typeRef !== "string" || !(fieldState.typeRef in storageTypes)) continue;
887
+ const warningKey = `${modelName}.${fieldName}`;
888
+ if (warnedFields.has(warningKey)) continue;
889
+ warnedFields.add(warningKey);
890
+ warnings.push(`Staged contract field "${modelName}.${fieldName}" uses field.namedType('${fieldState.typeRef}'). Use field.namedType(types.${fieldState.typeRef}) when the storage type is declared in the same contract to keep autocomplete and typed local refs.`);
891
+ }
892
+ flushWarnings(warnings);
893
+ }
894
+ function emitTypedCrossModelFallbackWarnings(collection) {
895
+ const warnings = [];
896
+ const warnedKeys = /* @__PURE__ */ new Set();
897
+ for (const spec of collection.modelSpecs.values()) {
898
+ for (const [relationName, relationBuilder] of Object.entries(spec.relations)) {
899
+ const relation = relationBuilder.build();
900
+ if (relation.toModel.kind === "relationModelName" && relation.toModel.source === "string" && hasNamedModelToken(collection.models, relation.toModel.modelName)) {
901
+ const warningKey = `${spec.modelName}.${relationName}.toModel`;
902
+ if (!warnedKeys.has(warningKey)) {
903
+ warnedKeys.add(warningKey);
904
+ const current = formatRelationCall(relation, `'${relation.toModel.modelName}'`);
905
+ const suggested = formatRelationCall(relation, relation.toModel.modelName);
906
+ warnings.push(formatFallbackWarning(`relation "${spec.modelName}.${relationName}"`, current, suggested));
907
+ }
908
+ }
909
+ if (relation.kind === "manyToMany" && relation.through.kind === "relationModelName" && relation.through.source === "string" && hasNamedModelToken(collection.models, relation.through.modelName)) {
910
+ const warningKey = `${spec.modelName}.${relationName}.through`;
911
+ if (!warnedKeys.has(warningKey)) {
912
+ warnedKeys.add(warningKey);
913
+ const current = formatManyToManyCallWithThrough(relation, `'${relation.through.modelName}'`);
914
+ const suggested = formatManyToManyCallWithThrough(relation, relation.through.modelName);
915
+ warnings.push(formatFallbackWarning(`relation "${spec.modelName}.${relationName}"`, current, suggested));
916
+ }
917
+ }
918
+ }
919
+ for (const [foreignKeyIndex, foreignKey] of (spec.sqlSpec?.foreignKeys ?? []).entries()) {
920
+ if (foreignKey.targetSource !== "string" || !hasNamedModelToken(collection.models, foreignKey.targetModel)) continue;
921
+ const warningKey = `${spec.modelName}.sql.foreignKeys.${foreignKeyIndex}`;
922
+ if (warnedKeys.has(warningKey)) continue;
923
+ warnedKeys.add(warningKey);
924
+ const current = formatConstraintsRefCall(foreignKey.targetModel, foreignKey.targetFields);
925
+ const suggested = formatTokenFieldSelection(foreignKey.targetModel, foreignKey.targetFields);
926
+ warnings.push(formatFallbackWarning(`model "${spec.modelName}"`, `${current} in .sql(...)`, suggested));
927
+ }
928
+ }
929
+ flushWarnings(warnings);
930
+ }
931
+
932
+ //#endregion
933
+ //#region src/staged-contract-lowering.ts
934
+ function buildStorageTypeReverseLookup(storageTypes) {
935
+ const lookup = /* @__PURE__ */ new Map();
936
+ for (const [key, instance] of Object.entries(storageTypes)) lookup.set(instance, key);
937
+ return lookup;
938
+ }
939
+ function resolveFieldDescriptor(modelName, fieldName, fieldState, storageTypes, storageTypeReverseLookup) {
940
+ if ("descriptor" in fieldState && fieldState.descriptor) return fieldState.descriptor;
941
+ if ("typeRef" in fieldState && fieldState.typeRef) {
942
+ const typeRef = typeof fieldState.typeRef === "string" ? fieldState.typeRef : storageTypeReverseLookup.get(fieldState.typeRef);
943
+ if (!typeRef) throw new Error(`Field "${modelName}.${fieldName}" references a storage type instance that is not present in definition.types`);
944
+ const referencedType = storageTypes[typeRef];
945
+ if (!referencedType) throw new Error(`Field "${modelName}.${fieldName}" references unknown storage type "${typeRef}"`);
946
+ return {
947
+ codecId: referencedType.codecId,
948
+ nativeType: referencedType.nativeType,
949
+ typeRef
950
+ };
951
+ }
952
+ throw new Error(`Field "${modelName}.${fieldName}" does not resolve to a storage descriptor`);
953
+ }
954
+ function mapFieldNamesToColumnNames(modelName, fieldNames, fieldToColumn) {
955
+ return fieldNames.map((fieldName) => {
956
+ const columnName = fieldToColumn[fieldName];
957
+ if (!columnName) throw new Error(`Unknown field "${modelName}.${fieldName}" in staged contract definition`);
958
+ return columnName;
959
+ });
960
+ }
961
+ function assertRelationFieldArity(params) {
962
+ if (params.leftFields.length === params.rightFields.length) return;
963
+ throw new Error(`Relation "${params.modelName}.${params.relationName}" maps ${params.leftFields.length} ${params.leftLabel} field(s) to ${params.rightFields.length} ${params.rightLabel} field(s).`);
964
+ }
965
+ function resolveInlineIdConstraint(spec) {
966
+ const inlineIdFields = [];
967
+ let idName;
968
+ for (const [fieldName, fieldBuilder] of Object.entries(spec.fieldBuilders)) {
969
+ const fieldState = fieldBuilder.build();
970
+ if (!fieldState.id) continue;
971
+ inlineIdFields.push(fieldName);
972
+ if (fieldState.id.name) idName = fieldState.id.name;
973
+ }
974
+ if (inlineIdFields.length === 0) return;
975
+ if (inlineIdFields.length > 1) throw new Error(`Model "${spec.modelName}" marks multiple fields with .id(). Use .attributes(...) for compound identities.`);
976
+ const [inlineIdField] = inlineIdFields;
977
+ if (!inlineIdField) return;
978
+ return {
979
+ kind: "id",
980
+ fields: [inlineIdField],
981
+ ...idName ? { name: idName } : {}
982
+ };
983
+ }
984
+ function collectInlineUniqueConstraints(spec) {
985
+ const constraints = [];
986
+ for (const [fieldName, fieldBuilder] of Object.entries(spec.fieldBuilders)) {
987
+ const fieldState = fieldBuilder.build();
988
+ if (!fieldState.unique) continue;
989
+ constraints.push({
990
+ kind: "unique",
991
+ fields: [fieldName],
992
+ ...fieldState.unique.name ? { name: fieldState.unique.name } : {}
993
+ });
994
+ }
995
+ return constraints;
996
+ }
997
+ function resolveModelIdConstraint(spec) {
998
+ const inlineId = resolveInlineIdConstraint(spec);
999
+ const attributeId = spec.attributesSpec?.id;
1000
+ if (inlineId && attributeId) throw new Error(`Model "${spec.modelName}" defines identity both inline and in .attributes(...). Pick one identity style.`);
1001
+ const resolvedId = attributeId ?? inlineId;
1002
+ if (resolvedId && resolvedId.fields.length === 0) throw new Error(`Model "${spec.modelName}" defines an empty identity. Add at least one field.`);
1003
+ return resolvedId;
1004
+ }
1005
+ function resolveModelUniqueConstraints(spec) {
1006
+ const attributeUniques = spec.attributesSpec?.uniques ?? [];
1007
+ for (const unique of attributeUniques) if (unique.fields.length === 0) throw new Error(`Model "${spec.modelName}" defines an empty unique constraint. Add at least one field.`);
1008
+ return [...collectInlineUniqueConstraints(spec), ...attributeUniques];
1009
+ }
1010
+ function resolveRelationForeignKeys(spec, allSpecs) {
1011
+ const foreignKeys = [];
1012
+ for (const [relationName, relationBuilder] of Object.entries(spec.relations)) {
1013
+ const relation = relationBuilder.build();
1014
+ if (relation.kind !== "belongsTo" || !relation.sql?.fk) continue;
1015
+ const targetModelName = resolveRelationModelName(relation.toModel);
1016
+ if (!allSpecs.has(targetModelName)) throw new Error(`Relation "${spec.modelName}.${relationName}" references unknown model "${targetModelName}"`);
1017
+ const fields = normalizeRelationFieldNames(relation.from);
1018
+ const targetFields = normalizeRelationFieldNames(relation.to);
1019
+ assertRelationFieldArity({
1020
+ modelName: spec.modelName,
1021
+ relationName,
1022
+ leftLabel: "source",
1023
+ leftFields: fields,
1024
+ rightLabel: "target",
1025
+ rightFields: targetFields
1026
+ });
1027
+ foreignKeys.push({
1028
+ kind: "fk",
1029
+ fields,
1030
+ targetModel: targetModelName,
1031
+ targetFields,
1032
+ ...relation.sql.fk.name ? { name: relation.sql.fk.name } : {},
1033
+ ...relation.sql.fk.onDelete ? { onDelete: relation.sql.fk.onDelete } : {},
1034
+ ...relation.sql.fk.onUpdate ? { onUpdate: relation.sql.fk.onUpdate } : {},
1035
+ ...relation.sql.fk.constraint !== void 0 ? { constraint: relation.sql.fk.constraint } : {},
1036
+ ...relation.sql.fk.index !== void 0 ? { index: relation.sql.fk.index } : {}
1037
+ });
1038
+ }
1039
+ return foreignKeys;
1040
+ }
1041
+ function resolveRelationAnchorFields(spec) {
1042
+ const idFields = spec.idConstraint?.fields;
1043
+ if (idFields && idFields.length > 0) return idFields;
1044
+ if ("id" in spec.fieldToColumn) return ["id"];
1045
+ throw new Error(`Model "${spec.modelName}" needs an explicit id or an "id" field to anchor non-owning relations`);
1046
+ }
1047
+ function lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs) {
1048
+ const targetModelName = resolveRelationModelName(relation.toModel);
1049
+ const targetSpec = allSpecs.get(targetModelName);
1050
+ if (!targetSpec) throw new Error(`Relation "${currentSpec.modelName}.${relationName}" references unknown model "${targetModelName}"`);
1051
+ const fromFields = normalizeRelationFieldNames(relation.from);
1052
+ const toFields = normalizeRelationFieldNames(relation.to);
1053
+ assertRelationFieldArity({
1054
+ modelName: currentSpec.modelName,
1055
+ relationName,
1056
+ leftLabel: "source",
1057
+ leftFields: fromFields,
1058
+ rightLabel: "target",
1059
+ rightFields: toFields
1060
+ });
1061
+ return {
1062
+ fieldName: relationName,
1063
+ toModel: targetModelName,
1064
+ toTable: targetSpec.tableName,
1065
+ cardinality: "N:1",
1066
+ on: {
1067
+ parentTable: currentSpec.tableName,
1068
+ parentColumns: mapFieldNamesToColumnNames(currentSpec.modelName, fromFields, currentSpec.fieldToColumn),
1069
+ childTable: targetSpec.tableName,
1070
+ childColumns: mapFieldNamesToColumnNames(targetSpec.modelName, toFields, targetSpec.fieldToColumn)
1071
+ }
1072
+ };
1073
+ }
1074
+ function lowerHasOwnershipRelation(relationName, relation, currentSpec, allSpecs) {
1075
+ const targetModelName = resolveRelationModelName(relation.toModel);
1076
+ const targetSpec = allSpecs.get(targetModelName);
1077
+ if (!targetSpec) throw new Error(`Relation "${currentSpec.modelName}.${relationName}" references unknown model "${targetModelName}"`);
1078
+ const parentFields = resolveRelationAnchorFields(currentSpec);
1079
+ const childFields = normalizeRelationFieldNames(relation.by);
1080
+ assertRelationFieldArity({
1081
+ modelName: currentSpec.modelName,
1082
+ relationName,
1083
+ leftLabel: "anchor",
1084
+ leftFields: parentFields,
1085
+ rightLabel: "child",
1086
+ rightFields: childFields
1087
+ });
1088
+ return {
1089
+ fieldName: relationName,
1090
+ toModel: targetModelName,
1091
+ toTable: targetSpec.tableName,
1092
+ cardinality: relation.kind === "hasMany" ? "1:N" : "1:1",
1093
+ on: {
1094
+ parentTable: currentSpec.tableName,
1095
+ parentColumns: mapFieldNamesToColumnNames(currentSpec.modelName, parentFields, currentSpec.fieldToColumn),
1096
+ childTable: targetSpec.tableName,
1097
+ childColumns: mapFieldNamesToColumnNames(targetSpec.modelName, childFields, targetSpec.fieldToColumn)
1098
+ }
1099
+ };
1100
+ }
1101
+ function lowerManyToManyRelation(relationName, relation, currentSpec, allSpecs) {
1102
+ const targetModelName = resolveRelationModelName(relation.toModel);
1103
+ const targetSpec = allSpecs.get(targetModelName);
1104
+ if (!targetSpec) throw new Error(`Relation "${currentSpec.modelName}.${relationName}" references unknown model "${targetModelName}"`);
1105
+ const throughModelName = resolveRelationModelName(relation.through);
1106
+ const throughSpec = allSpecs.get(throughModelName);
1107
+ if (!throughSpec) throw new Error(`Relation "${currentSpec.modelName}.${relationName}" references unknown through model "${throughModelName}"`);
1108
+ const currentAnchorFields = resolveRelationAnchorFields(currentSpec);
1109
+ const targetAnchorFields = resolveRelationAnchorFields(targetSpec);
1110
+ const throughFromFields = normalizeRelationFieldNames(relation.from);
1111
+ const throughToFields = normalizeRelationFieldNames(relation.to);
1112
+ if (currentAnchorFields.length !== throughFromFields.length || targetAnchorFields.length !== throughToFields.length) throw new Error(`Relation "${currentSpec.modelName}.${relationName}" has mismatched many-to-many field counts.`);
1113
+ return {
1114
+ fieldName: relationName,
1115
+ toModel: targetModelName,
1116
+ toTable: targetSpec.tableName,
1117
+ cardinality: "N:M",
1118
+ through: {
1119
+ table: throughSpec.tableName,
1120
+ parentColumns: mapFieldNamesToColumnNames(throughSpec.modelName, throughFromFields, throughSpec.fieldToColumn),
1121
+ childColumns: mapFieldNamesToColumnNames(throughSpec.modelName, throughToFields, throughSpec.fieldToColumn)
1122
+ },
1123
+ on: {
1124
+ parentTable: currentSpec.tableName,
1125
+ parentColumns: mapFieldNamesToColumnNames(currentSpec.modelName, currentAnchorFields, currentSpec.fieldToColumn),
1126
+ childTable: throughSpec.tableName,
1127
+ childColumns: mapFieldNamesToColumnNames(throughSpec.modelName, throughFromFields, throughSpec.fieldToColumn)
1128
+ }
1129
+ };
1130
+ }
1131
+ function resolveSemanticRelationNode(relationName, relation, currentSpec, allSpecs) {
1132
+ if (relation.kind === "belongsTo") return lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs);
1133
+ if (relation.kind === "hasMany" || relation.kind === "hasOne") return lowerHasOwnershipRelation(relationName, relation, currentSpec, allSpecs);
1134
+ return lowerManyToManyRelation(relationName, relation, currentSpec, allSpecs);
1135
+ }
1136
+ function lowerForeignKeyNode(spec, targetSpec, foreignKey) {
1137
+ return {
1138
+ columns: mapFieldNamesToColumnNames(spec.modelName, foreignKey.fields, spec.fieldToColumn),
1139
+ references: {
1140
+ model: targetSpec.modelName,
1141
+ table: targetSpec.tableName,
1142
+ columns: mapFieldNamesToColumnNames(targetSpec.modelName, foreignKey.targetFields, targetSpec.fieldToColumn)
1143
+ },
1144
+ ...foreignKey.name ? { name: foreignKey.name } : {},
1145
+ ...foreignKey.onDelete ? { onDelete: foreignKey.onDelete } : {},
1146
+ ...foreignKey.onUpdate ? { onUpdate: foreignKey.onUpdate } : {},
1147
+ ...foreignKey.constraint !== void 0 ? { constraint: foreignKey.constraint } : {},
1148
+ ...foreignKey.index !== void 0 ? { index: foreignKey.index } : {}
1149
+ };
1150
+ }
1151
+ function resolveSemanticForeignKeyNodes(spec, allSpecs) {
1152
+ const relationForeignKeys = resolveRelationForeignKeys(spec, allSpecs).map((foreignKey) => {
1153
+ const targetSpec = allSpecs.get(foreignKey.targetModel);
1154
+ if (!targetSpec) throw new Error(`Foreign key on "${spec.modelName}" references unknown model "${foreignKey.targetModel}"`);
1155
+ return lowerForeignKeyNode(spec, targetSpec, foreignKey);
1156
+ });
1157
+ const sqlForeignKeys = (spec.sqlSpec?.foreignKeys ?? []).map((foreignKey) => {
1158
+ const targetSpec = allSpecs.get(foreignKey.targetModel);
1159
+ if (!targetSpec) throw new Error(`Foreign key on "${spec.modelName}" references unknown model "${foreignKey.targetModel}"`);
1160
+ return lowerForeignKeyNode(spec, targetSpec, foreignKey);
1161
+ });
1162
+ return [...relationForeignKeys, ...sqlForeignKeys];
1163
+ }
1164
+ function resolveSemanticModelNode(spec, allSpecs, storageTypes, storageTypeReverseLookup) {
1165
+ const fields = [];
1166
+ for (const [fieldName, fieldBuilder] of Object.entries(spec.fieldBuilders)) {
1167
+ const fieldState = fieldBuilder.build();
1168
+ const descriptor = resolveFieldDescriptor(spec.modelName, fieldName, fieldState, storageTypes, storageTypeReverseLookup);
1169
+ const columnName = spec.fieldToColumn[fieldName];
1170
+ if (!columnName) throw new Error(`Column name resolution failed for "${spec.modelName}.${fieldName}"`);
1171
+ fields.push({
1172
+ fieldName,
1173
+ columnName,
1174
+ descriptor,
1175
+ nullable: fieldState.nullable,
1176
+ ...fieldState.default ? { default: fieldState.default } : {},
1177
+ ...fieldState.executionDefault ? { executionDefault: fieldState.executionDefault } : {}
1178
+ });
1179
+ }
1180
+ const { idConstraint } = spec;
1181
+ const uniques = resolveModelUniqueConstraints(spec).map((unique) => ({
1182
+ columns: mapFieldNamesToColumnNames(spec.modelName, unique.fields, spec.fieldToColumn),
1183
+ ...unique.name ? { name: unique.name } : {}
1184
+ }));
1185
+ const indexes = (spec.sqlSpec?.indexes ?? []).map((index) => ({
1186
+ columns: mapFieldNamesToColumnNames(spec.modelName, index.fields, spec.fieldToColumn),
1187
+ ...index.name ? { name: index.name } : {},
1188
+ ...index.using ? { using: index.using } : {},
1189
+ ...index.config ? { config: index.config } : {}
1190
+ }));
1191
+ const foreignKeys = resolveSemanticForeignKeyNodes(spec, allSpecs);
1192
+ const relations = Object.entries(spec.relations).map(([relationName, relationBuilder]) => resolveSemanticRelationNode(relationName, relationBuilder.build(), spec, allSpecs));
1193
+ return {
1194
+ modelName: spec.modelName,
1195
+ tableName: spec.tableName,
1196
+ fields,
1197
+ ...idConstraint ? { id: {
1198
+ columns: mapFieldNamesToColumnNames(spec.modelName, idConstraint.fields, spec.fieldToColumn),
1199
+ ...idConstraint.name ? { name: idConstraint.name } : {}
1200
+ } } : {},
1201
+ ...uniques.length > 0 ? { uniques } : {},
1202
+ ...indexes.length > 0 ? { indexes } : {},
1203
+ ...foreignKeys.length > 0 ? { foreignKeys } : {},
1204
+ ...relations.length > 0 ? { relations } : {}
1205
+ };
1206
+ }
1207
+ function collectRuntimeModelSpecs(definition) {
1208
+ const storageTypes = { ...definition.types ?? {} };
1209
+ const models = { ...definition.models ?? {} };
1210
+ emitTypedNamedTypeFallbackWarnings(models, storageTypes);
1211
+ const modelSpecs = /* @__PURE__ */ new Map();
1212
+ const tableOwners = /* @__PURE__ */ new Map();
1213
+ for (const [modelName, modelDefinition] of Object.entries(models)) {
1214
+ const tokenModelName = modelDefinition.stageOne.modelName;
1215
+ if (tokenModelName && tokenModelName !== modelName) throw new Error(`Model token "${tokenModelName}" must be assigned to models.${tokenModelName}. Received models.${modelName}.`);
1216
+ const attributesSpec = modelDefinition.buildAttributesSpec();
1217
+ const sqlSpec = modelDefinition.buildSqlSpec();
1218
+ const tableName = sqlSpec?.table ?? applyNaming(modelName, definition.naming?.tables);
1219
+ const existingModel = tableOwners.get(tableName);
1220
+ if (existingModel) throw new Error(`Models "${existingModel}" and "${modelName}" both map to table "${tableName}".`);
1221
+ tableOwners.set(tableName, modelName);
1222
+ const fieldToColumn = {};
1223
+ const columnOwners = /* @__PURE__ */ new Map();
1224
+ for (const [fieldName, fieldBuilder] of Object.entries(modelDefinition.stageOne.fields)) {
1225
+ const columnName = fieldBuilder.build().columnName ?? applyNaming(fieldName, definition.naming?.columns);
1226
+ const existingField = columnOwners.get(columnName);
1227
+ if (existingField) throw new Error(`Model "${modelName}" maps both "${existingField}" and "${fieldName}" to column "${columnName}".`);
1228
+ columnOwners.set(columnName, fieldName);
1229
+ fieldToColumn[fieldName] = columnName;
1230
+ }
1231
+ const fieldBuilders = modelDefinition.stageOne.fields;
1232
+ const idConstraint = resolveModelIdConstraint({
1233
+ modelName,
1234
+ fieldBuilders,
1235
+ attributesSpec
1236
+ });
1237
+ modelSpecs.set(modelName, {
1238
+ modelName,
1239
+ tableName,
1240
+ fieldBuilders,
1241
+ fieldToColumn,
1242
+ relations: modelDefinition.stageOne.relations,
1243
+ attributesSpec,
1244
+ sqlSpec,
1245
+ idConstraint
1246
+ });
1247
+ }
1248
+ return {
1249
+ storageTypes,
1250
+ models,
1251
+ modelSpecs
1252
+ };
1253
+ }
1254
+ function lowerSemanticModels(collection) {
1255
+ emitTypedCrossModelFallbackWarnings(collection);
1256
+ const storageTypeReverseLookup = buildStorageTypeReverseLookup(collection.storageTypes);
1257
+ return Array.from(collection.modelSpecs.values()).map((spec) => resolveSemanticModelNode(spec, collection.modelSpecs, collection.storageTypes, storageTypeReverseLookup));
1258
+ }
1259
+ function buildStagedSemanticContractDefinition(definition) {
1260
+ const collection = collectRuntimeModelSpecs(definition);
1261
+ const models = lowerSemanticModels(collection);
1262
+ return {
1263
+ target: definition.target,
1264
+ ...definition.extensionPacks ? { extensionPacks: definition.extensionPacks } : {},
1265
+ ...definition.capabilities ? { capabilities: definition.capabilities } : {},
1266
+ ...definition.storageHash ? { storageHash: definition.storageHash } : {},
1267
+ ...definition.foreignKeyDefaults ? { foreignKeyDefaults: definition.foreignKeyDefaults } : {},
1268
+ ...Object.keys(collection.storageTypes).length > 0 ? { storageTypes: collection.storageTypes } : {},
1269
+ models
1270
+ };
1271
+ }
1272
+
1273
+ //#endregion
1274
+ //#region src/contract-builder.ts
1275
+ function buildStagedContract(definition) {
1276
+ return buildSqlContractFromSemanticDefinition(buildStagedSemanticContractDefinition(definition));
1277
+ }
44
1278
  var SqlContractBuilder = class SqlContractBuilder extends ContractBuilder {
45
1279
  /**
46
1280
  * This method is responsible for normalizing the contract IR by setting default values
@@ -61,132 +1295,7 @@ var SqlContractBuilder = class SqlContractBuilder extends ContractBuilder {
61
1295
  * @returns A normalized SqlContract with all required fields present
62
1296
  */
63
1297
  build() {
64
- if (!this.state.target) throw new Error("target is required. Call .target() before .build()");
65
- const target = this.state.target;
66
- const storageTables = {};
67
- const executionDefaults = [];
68
- for (const tableName of Object.keys(this.state.tables)) {
69
- const tableState = this.state.tables[tableName];
70
- if (!tableState) continue;
71
- const columns = {};
72
- for (const columnName in tableState.columns) {
73
- const columnState = tableState.columns[columnName];
74
- if (!columnState) continue;
75
- const codecId = columnState.type;
76
- const nativeType = columnState.nativeType;
77
- const typeRef = columnState.typeRef;
78
- const encodedDefault = columnState.default !== void 0 ? encodeColumnDefault(columnState.default) : void 0;
79
- columns[columnName] = {
80
- nativeType,
81
- codecId,
82
- nullable: columnState.nullable ?? false,
83
- ...ifDefined("typeParams", columnState.typeParams),
84
- ...ifDefined("default", encodedDefault),
85
- ...ifDefined("typeRef", typeRef)
86
- };
87
- if ("executionDefault" in columnState && columnState.executionDefault) executionDefaults.push({
88
- ref: {
89
- table: tableName,
90
- column: columnName
91
- },
92
- onCreate: columnState.executionDefault
93
- });
94
- }
95
- storageTables[tableName] = {
96
- columns,
97
- uniques: (tableState.uniques ?? []).map((u) => ({
98
- columns: u.columns,
99
- ...u.name ? { name: u.name } : {}
100
- })),
101
- indexes: (tableState.indexes ?? []).map((i) => ({
102
- columns: i.columns,
103
- ...i.name ? { name: i.name } : {},
104
- ...i.using ? { using: i.using } : {},
105
- ...i.config ? { config: i.config } : {}
106
- })),
107
- foreignKeys: (tableState.foreignKeys ?? []).map((fk) => ({
108
- columns: fk.columns,
109
- references: fk.references,
110
- ...applyFkDefaults(fk, this.state.foreignKeyDefaults),
111
- ...fk.name ? { name: fk.name } : {},
112
- ...fk.onDelete !== void 0 ? { onDelete: fk.onDelete } : {},
113
- ...fk.onUpdate !== void 0 ? { onUpdate: fk.onUpdate } : {}
114
- })),
115
- ...tableState.primaryKey ? { primaryKey: {
116
- columns: tableState.primaryKey,
117
- ...tableState.primaryKeyName ? { name: tableState.primaryKeyName } : {}
118
- } } : {}
119
- };
120
- }
121
- const storage = {
122
- tables: storageTables,
123
- types: this.state.storageTypes ?? {}
124
- };
125
- const execution = executionDefaults.length > 0 ? { mutations: { defaults: executionDefaults.sort((a, b) => {
126
- const tableCompare = a.ref.table.localeCompare(b.ref.table);
127
- if (tableCompare !== 0) return tableCompare;
128
- return a.ref.column.localeCompare(b.ref.column);
129
- }) } } : void 0;
130
- const modelsPartial = {};
131
- for (const modelName in this.state.models) {
132
- const modelState = this.state.models[modelName];
133
- if (!modelState) continue;
134
- const modelStateTyped = modelState;
135
- const tableName = modelStateTyped.table;
136
- const tableState = this.state.tables[tableName];
137
- const tableColumns = tableState ? tableState.columns : {};
138
- const storageFields = {};
139
- const domainFields = {};
140
- for (const fieldName in modelStateTyped.fields) {
141
- const columnName = modelStateTyped.fields[fieldName];
142
- if (!columnName) continue;
143
- storageFields[fieldName] = { column: columnName };
144
- const column = tableColumns[columnName];
145
- if (column) domainFields[fieldName] = {
146
- codecId: column.type,
147
- nullable: column.nullable ?? false
148
- };
149
- }
150
- const modelRelations = {};
151
- if (modelStateTyped.relations) for (const relName in modelStateTyped.relations) {
152
- const rel = modelStateTyped.relations[relName];
153
- if (!rel) continue;
154
- modelRelations[relName] = {
155
- to: rel.to,
156
- cardinality: rel.cardinality,
157
- on: {
158
- localFields: rel.on.parentCols,
159
- targetFields: rel.on.childCols
160
- }
161
- };
162
- }
163
- modelsPartial[modelName] = {
164
- storage: {
165
- table: tableName,
166
- fields: storageFields
167
- },
168
- fields: domainFields,
169
- relations: modelRelations
170
- };
171
- }
172
- const models = modelsPartial;
173
- const extensionNamespaces = this.state.extensionNamespaces ?? [];
174
- const extensionPacks = { ...this.state.extensionPacks || {} };
175
- for (const namespace of extensionNamespaces) if (!Object.hasOwn(extensionPacks, namespace)) extensionPacks[namespace] = {};
176
- return {
177
- schemaVersion: "1",
178
- target,
179
- targetFamily: "sql",
180
- storageHash: this.state.storageHash || "sha256:ts-builder-placeholder",
181
- models,
182
- roots: {},
183
- storage,
184
- ...execution ? { execution } : {},
185
- extensionPacks,
186
- capabilities: this.state.capabilities || {},
187
- meta: {},
188
- sources: {}
189
- };
1298
+ return buildContractIR(this.state);
190
1299
  }
191
1300
  target(packRef) {
192
1301
  return new SqlContractBuilder({
@@ -197,15 +1306,18 @@ var SqlContractBuilder = class SqlContractBuilder extends ContractBuilder {
197
1306
  extensionPacks(packs) {
198
1307
  if (!this.state.target) throw new Error("extensionPacks() requires target() to be called first");
199
1308
  const namespaces = new Set(this.state.extensionNamespaces ?? []);
200
- for (const packRef of Object.values(packs)) {
1309
+ const nextExtensionPacks = { ...this.state.extensionPacks ?? {} };
1310
+ for (const [name, packRef] of Object.entries(packs)) {
201
1311
  if (!packRef) continue;
202
1312
  if (packRef.kind !== "extension") throw new Error(`extensionPacks() only accepts extension pack refs. Received kind "${packRef.kind}".`);
203
1313
  if (packRef.familyId !== "sql") throw new Error(`extension pack "${packRef.id}" targets family "${packRef.familyId}" but this builder targets "sql".`);
204
1314
  if (packRef.targetId && packRef.targetId !== this.state.target) throw new Error(`extension pack "${packRef.id}" targets "${packRef.targetId}" but builder target is "${this.state.target}".`);
205
1315
  namespaces.add(packRef.id);
1316
+ nextExtensionPacks[name] = packRef;
206
1317
  }
207
1318
  return new SqlContractBuilder({
208
1319
  ...this.state,
1320
+ extensionPacks: nextExtensionPacks,
209
1321
  extensionNamespaces: [...namespaces]
210
1322
  });
211
1323
  }
@@ -261,10 +1373,21 @@ var SqlContractBuilder = class SqlContractBuilder extends ContractBuilder {
261
1373
  });
262
1374
  }
263
1375
  };
264
- function defineContract() {
1376
+ function defineContract(definition, factory) {
1377
+ if (definition && isStagedContractInput(definition)) {
1378
+ if (factory) return buildStagedContract({
1379
+ ...definition,
1380
+ ...factory(createComposedAuthoringHelpers({
1381
+ family: definition.family,
1382
+ target: definition.target,
1383
+ extensionPacks: definition.extensionPacks
1384
+ }))
1385
+ });
1386
+ return buildStagedContract(definition);
1387
+ }
265
1388
  return new SqlContractBuilder();
266
1389
  }
267
1390
 
268
1391
  //#endregion
269
- export { defineContract };
1392
+ export { buildSqlContractFromSemanticDefinition, defineContract, field, model, rel };
270
1393
  //# sourceMappingURL=contract-builder.mjs.map