@prisma-next/sql-contract-ts 0.3.0-pr.99.5 → 0.3.0

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