@prisma-next/sql-contract-ts 0.3.0-dev.135 → 0.3.0-dev.146
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.
- package/README.md +107 -154
- package/dist/config-types.d.mts +2 -2
- package/dist/config-types.d.mts.map +1 -1
- package/dist/config-types.mjs +2 -2
- package/dist/config-types.mjs.map +1 -1
- package/dist/contract-builder.d.mts +192 -236
- package/dist/contract-builder.d.mts.map +1 -1
- package/dist/contract-builder.mjs +317 -422
- package/dist/contract-builder.mjs.map +1 -1
- package/package.json +8 -6
- package/schemas/data-contract-sql-v1.json +6 -6
- package/src/authoring-helper-runtime.ts +2 -2
- package/src/authoring-type-utils.ts +2 -2
- package/src/build-contract.ts +463 -0
- package/src/composed-authoring-helpers.ts +8 -6
- package/src/config-types.ts +3 -3
- package/src/contract-builder.ts +122 -636
- package/src/{semantic-contract.ts → contract-definition.ts} +33 -16
- package/src/{staged-contract-dsl.ts → contract-dsl.ts} +30 -28
- package/src/{staged-contract-lowering.ts → contract-lowering.ts} +46 -48
- package/src/{staged-contract-types.ts → contract-types.ts} +185 -145
- package/src/{staged-contract-warnings.ts → contract-warnings.ts} +18 -21
- package/src/exports/contract-builder.ts +12 -13
- package/src/contract-ir-builder.ts +0 -475
- package/src/contract.ts +0 -475
|
@@ -1,9 +1,274 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { ifDefined } from "@prisma-next/utils/defined";
|
|
1
|
+
import { computeExecutionHash, computeProfileHash, computeStorageHash } from "@prisma-next/contract/hashing";
|
|
2
|
+
import { coreHash } from "@prisma-next/contract/types";
|
|
4
3
|
import { applyFkDefaults } from "@prisma-next/sql-contract/types";
|
|
5
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";
|
|
6
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
|
|
7
272
|
//#region src/authoring-helper-runtime.ts
|
|
8
273
|
function isNamedConstraintOptionsLike(value) {
|
|
9
274
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
@@ -74,7 +339,7 @@ function createFieldHelpersFromNamespace(namespace, createLeafHelper, path = [])
|
|
|
74
339
|
}
|
|
75
340
|
|
|
76
341
|
//#endregion
|
|
77
|
-
//#region src/
|
|
342
|
+
//#region src/contract-dsl.ts
|
|
78
343
|
function isColumnDefault(value) {
|
|
79
344
|
if (typeof value !== "object" || value === null) return false;
|
|
80
345
|
const kind = value.kind;
|
|
@@ -310,7 +575,7 @@ function createColumnRefs(fields) {
|
|
|
310
575
|
function findDuplicateRelationName(existingRelations, nextRelations) {
|
|
311
576
|
return Object.keys(nextRelations).find((relationName) => Object.hasOwn(existingRelations, relationName));
|
|
312
577
|
}
|
|
313
|
-
var
|
|
578
|
+
var ContractModelBuilder = class ContractModelBuilder {
|
|
314
579
|
refs;
|
|
315
580
|
constructor(stageOne, attributesFactory, sqlFactory) {
|
|
316
581
|
this.stageOne = stageOne;
|
|
@@ -331,7 +596,7 @@ var StagedModelBuilder = class StagedModelBuilder {
|
|
|
331
596
|
relations(relations) {
|
|
332
597
|
const duplicateRelationName = findDuplicateRelationName(this.stageOne.relations, relations);
|
|
333
598
|
if (duplicateRelationName) throw new Error(`Model "${this.stageOne.modelName ?? "<anonymous>"}" already defines relation "${duplicateRelationName}".`);
|
|
334
|
-
return new
|
|
599
|
+
return new ContractModelBuilder({
|
|
335
600
|
...this.stageOne,
|
|
336
601
|
relations: {
|
|
337
602
|
...this.stageOne.relations,
|
|
@@ -340,10 +605,10 @@ var StagedModelBuilder = class StagedModelBuilder {
|
|
|
340
605
|
}, this.attributesFactory, this.sqlFactory);
|
|
341
606
|
}
|
|
342
607
|
attributes(specOrFactory) {
|
|
343
|
-
return new
|
|
608
|
+
return new ContractModelBuilder(this.stageOne, specOrFactory, this.sqlFactory);
|
|
344
609
|
}
|
|
345
610
|
sql(specOrFactory) {
|
|
346
|
-
return new
|
|
611
|
+
return new ContractModelBuilder(this.stageOne, this.attributesFactory, specOrFactory);
|
|
347
612
|
}
|
|
348
613
|
buildAttributesSpec() {
|
|
349
614
|
if (!this.attributesFactory) return;
|
|
@@ -388,7 +653,7 @@ function normalizeRelationModelSource(target) {
|
|
|
388
653
|
function model(modelNameOrInput, maybeInput) {
|
|
389
654
|
const input = typeof modelNameOrInput === "string" ? maybeInput : modelNameOrInput;
|
|
390
655
|
if (!input) throw new Error("model(\"ModelName\", ...) requires a model definition.");
|
|
391
|
-
return new
|
|
656
|
+
return new ContractModelBuilder({
|
|
392
657
|
...typeof modelNameOrInput === "string" ? { modelName: modelNameOrInput } : {},
|
|
393
658
|
fields: input.fields,
|
|
394
659
|
relations: input.relations ?? {}
|
|
@@ -436,7 +701,7 @@ const field = {
|
|
|
436
701
|
generated: generatedField,
|
|
437
702
|
namedType: namedTypeField
|
|
438
703
|
};
|
|
439
|
-
function
|
|
704
|
+
function isContractInput(value) {
|
|
440
705
|
if (typeof value !== "object" || value === null || !("target" in value) || !("family" in value)) return false;
|
|
441
706
|
const target = value.target;
|
|
442
707
|
const family = value.family;
|
|
@@ -550,291 +815,7 @@ function createComposedAuthoringHelpers(options) {
|
|
|
550
815
|
}
|
|
551
816
|
|
|
552
817
|
//#endregion
|
|
553
|
-
//#region src/contract-
|
|
554
|
-
function isPlainObject(value) {
|
|
555
|
-
if (typeof value !== "object" || value === null) return false;
|
|
556
|
-
const proto = Object.getPrototypeOf(value);
|
|
557
|
-
return proto === Object.prototype || proto === null;
|
|
558
|
-
}
|
|
559
|
-
function isJsonValue(value) {
|
|
560
|
-
if (value === null) return true;
|
|
561
|
-
const valueType = typeof value;
|
|
562
|
-
if (valueType === "string" || valueType === "number" || valueType === "boolean") return true;
|
|
563
|
-
if (Array.isArray(value)) return value.every((item) => isJsonValue(item));
|
|
564
|
-
if (isPlainObject(value)) return Object.values(value).every((item) => isJsonValue(item));
|
|
565
|
-
return false;
|
|
566
|
-
}
|
|
567
|
-
function encodeDefaultLiteralValue(value) {
|
|
568
|
-
if (typeof value === "bigint") return {
|
|
569
|
-
$type: "bigint",
|
|
570
|
-
value: value.toString()
|
|
571
|
-
};
|
|
572
|
-
if (value instanceof Date) return value.toISOString();
|
|
573
|
-
if (isJsonValue(value)) {
|
|
574
|
-
if (isPlainObject(value) && "$type" in value) return {
|
|
575
|
-
$type: "raw",
|
|
576
|
-
value
|
|
577
|
-
};
|
|
578
|
-
return value;
|
|
579
|
-
}
|
|
580
|
-
throw new Error("Unsupported column default literal value: expected JSON-safe value, bigint, or Date.");
|
|
581
|
-
}
|
|
582
|
-
function encodeColumnDefault(defaultInput) {
|
|
583
|
-
if (defaultInput.kind === "function") return {
|
|
584
|
-
kind: "function",
|
|
585
|
-
expression: defaultInput.expression
|
|
586
|
-
};
|
|
587
|
-
return {
|
|
588
|
-
kind: "literal",
|
|
589
|
-
value: encodeDefaultLiteralValue(defaultInput.value)
|
|
590
|
-
};
|
|
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
|
|
818
|
+
//#region src/contract-warnings.ts
|
|
838
819
|
function hasNamedModelToken(models, modelName) {
|
|
839
820
|
return models[modelName]?.stageOne.modelName === modelName;
|
|
840
821
|
}
|
|
@@ -872,11 +853,11 @@ function flushWarnings(warnings) {
|
|
|
872
853
|
for (const message of warnings) process.emitWarning(message, { code: "PN_CONTRACT_TYPED_FALLBACK_AVAILABLE" });
|
|
873
854
|
return;
|
|
874
855
|
}
|
|
875
|
-
process.emitWarning(`${warnings.length}
|
|
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.
|
|
876
857
|
` + warnings.map((w) => ` - ${w}`).join("\n"), { code: "PN_CONTRACT_TYPED_FALLBACK_AVAILABLE" });
|
|
877
858
|
}
|
|
878
859
|
function formatFallbackWarning(location, current, suggested) {
|
|
879
|
-
return `
|
|
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.`;
|
|
880
861
|
}
|
|
881
862
|
function emitTypedNamedTypeFallbackWarnings(models, storageTypes) {
|
|
882
863
|
const warnings = [];
|
|
@@ -887,7 +868,7 @@ function emitTypedNamedTypeFallbackWarnings(models, storageTypes) {
|
|
|
887
868
|
const warningKey = `${modelName}.${fieldName}`;
|
|
888
869
|
if (warnedFields.has(warningKey)) continue;
|
|
889
870
|
warnedFields.add(warningKey);
|
|
890
|
-
warnings.push(`
|
|
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.`);
|
|
891
872
|
}
|
|
892
873
|
flushWarnings(warnings);
|
|
893
874
|
}
|
|
@@ -930,7 +911,7 @@ function emitTypedCrossModelFallbackWarnings(collection) {
|
|
|
930
911
|
}
|
|
931
912
|
|
|
932
913
|
//#endregion
|
|
933
|
-
//#region src/
|
|
914
|
+
//#region src/contract-lowering.ts
|
|
934
915
|
function buildStorageTypeReverseLookup(storageTypes) {
|
|
935
916
|
const lookup = /* @__PURE__ */ new Map();
|
|
936
917
|
for (const [key, instance] of Object.entries(storageTypes)) lookup.set(instance, key);
|
|
@@ -954,7 +935,7 @@ function resolveFieldDescriptor(modelName, fieldName, fieldState, storageTypes,
|
|
|
954
935
|
function mapFieldNamesToColumnNames(modelName, fieldNames, fieldToColumn) {
|
|
955
936
|
return fieldNames.map((fieldName) => {
|
|
956
937
|
const columnName = fieldToColumn[fieldName];
|
|
957
|
-
if (!columnName) throw new Error(`Unknown field "${modelName}.${fieldName}" in
|
|
938
|
+
if (!columnName) throw new Error(`Unknown field "${modelName}.${fieldName}" in contract definition`);
|
|
958
939
|
return columnName;
|
|
959
940
|
});
|
|
960
941
|
}
|
|
@@ -1128,7 +1109,7 @@ function lowerManyToManyRelation(relationName, relation, currentSpec, allSpecs)
|
|
|
1128
1109
|
}
|
|
1129
1110
|
};
|
|
1130
1111
|
}
|
|
1131
|
-
function
|
|
1112
|
+
function resolveRelationNode(relationName, relation, currentSpec, allSpecs) {
|
|
1132
1113
|
if (relation.kind === "belongsTo") return lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs);
|
|
1133
1114
|
if (relation.kind === "hasMany" || relation.kind === "hasOne") return lowerHasOwnershipRelation(relationName, relation, currentSpec, allSpecs);
|
|
1134
1115
|
return lowerManyToManyRelation(relationName, relation, currentSpec, allSpecs);
|
|
@@ -1148,7 +1129,7 @@ function lowerForeignKeyNode(spec, targetSpec, foreignKey) {
|
|
|
1148
1129
|
...foreignKey.index !== void 0 ? { index: foreignKey.index } : {}
|
|
1149
1130
|
};
|
|
1150
1131
|
}
|
|
1151
|
-
function
|
|
1132
|
+
function resolveForeignKeyNodes(spec, allSpecs) {
|
|
1152
1133
|
const relationForeignKeys = resolveRelationForeignKeys(spec, allSpecs).map((foreignKey) => {
|
|
1153
1134
|
const targetSpec = allSpecs.get(foreignKey.targetModel);
|
|
1154
1135
|
if (!targetSpec) throw new Error(`Foreign key on "${spec.modelName}" references unknown model "${foreignKey.targetModel}"`);
|
|
@@ -1161,7 +1142,7 @@ function resolveSemanticForeignKeyNodes(spec, allSpecs) {
|
|
|
1161
1142
|
});
|
|
1162
1143
|
return [...relationForeignKeys, ...sqlForeignKeys];
|
|
1163
1144
|
}
|
|
1164
|
-
function
|
|
1145
|
+
function resolveModelNode(spec, allSpecs, storageTypes, storageTypeReverseLookup) {
|
|
1165
1146
|
const fields = [];
|
|
1166
1147
|
for (const [fieldName, fieldBuilder] of Object.entries(spec.fieldBuilders)) {
|
|
1167
1148
|
const fieldState = fieldBuilder.build();
|
|
@@ -1188,8 +1169,8 @@ function resolveSemanticModelNode(spec, allSpecs, storageTypes, storageTypeRever
|
|
|
1188
1169
|
...index.using ? { using: index.using } : {},
|
|
1189
1170
|
...index.config ? { config: index.config } : {}
|
|
1190
1171
|
}));
|
|
1191
|
-
const foreignKeys =
|
|
1192
|
-
const relations = Object.entries(spec.relations).map(([relationName, relationBuilder]) =>
|
|
1172
|
+
const foreignKeys = resolveForeignKeyNodes(spec, allSpecs);
|
|
1173
|
+
const relations = Object.entries(spec.relations).map(([relationName, relationBuilder]) => resolveRelationNode(relationName, relationBuilder.build(), spec, allSpecs));
|
|
1193
1174
|
return {
|
|
1194
1175
|
modelName: spec.modelName,
|
|
1195
1176
|
tableName: spec.tableName,
|
|
@@ -1251,14 +1232,14 @@ function collectRuntimeModelSpecs(definition) {
|
|
|
1251
1232
|
modelSpecs
|
|
1252
1233
|
};
|
|
1253
1234
|
}
|
|
1254
|
-
function
|
|
1235
|
+
function lowerModels(collection) {
|
|
1255
1236
|
emitTypedCrossModelFallbackWarnings(collection);
|
|
1256
1237
|
const storageTypeReverseLookup = buildStorageTypeReverseLookup(collection.storageTypes);
|
|
1257
|
-
return Array.from(collection.modelSpecs.values()).map((spec) =>
|
|
1238
|
+
return Array.from(collection.modelSpecs.values()).map((spec) => resolveModelNode(spec, collection.modelSpecs, collection.storageTypes, storageTypeReverseLookup));
|
|
1258
1239
|
}
|
|
1259
|
-
function
|
|
1240
|
+
function buildContractDefinition(definition) {
|
|
1260
1241
|
const collection = collectRuntimeModelSpecs(definition);
|
|
1261
|
-
const models =
|
|
1242
|
+
const models = lowerModels(collection);
|
|
1262
1243
|
return {
|
|
1263
1244
|
target: definition.target,
|
|
1264
1245
|
...definition.extensionPacks ? { extensionPacks: definition.extensionPacks } : {},
|
|
@@ -1272,122 +1253,36 @@ function buildStagedSemanticContractDefinition(definition) {
|
|
|
1272
1253
|
|
|
1273
1254
|
//#endregion
|
|
1274
1255
|
//#region src/contract-builder.ts
|
|
1275
|
-
function
|
|
1276
|
-
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
* - `foreignKeys`: defaults to `[]` (empty array)
|
|
1286
|
-
* - model `relations`: defaults to `{}` (empty object) when not populated by the builder
|
|
1287
|
-
* - `nativeType`: required field set from column type descriptor when columns are defined
|
|
1288
|
-
*
|
|
1289
|
-
* The contract builder is the **only** place where normalization should occur.
|
|
1290
|
-
* Validators, parsers, and emitters should assume the contract is already normalized.
|
|
1291
|
-
*
|
|
1292
|
-
* **Required**: Use column type descriptors (e.g., `int4Column`, `textColumn`) when defining columns.
|
|
1293
|
-
* This ensures `nativeType` is set correctly at build time.
|
|
1294
|
-
*
|
|
1295
|
-
* @returns A normalized SqlContract with all required fields present
|
|
1296
|
-
*/
|
|
1297
|
-
build() {
|
|
1298
|
-
return buildContractIR(this.state);
|
|
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}".`);
|
|
1299
1266
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
extensionPacks(packs) {
|
|
1307
|
-
if (!this.state.target) throw new Error("extensionPacks() requires target() to be called first");
|
|
1308
|
-
const namespaces = new Set(this.state.extensionNamespaces ?? []);
|
|
1309
|
-
const nextExtensionPacks = { ...this.state.extensionPacks ?? {} };
|
|
1310
|
-
for (const [name, packRef] of Object.entries(packs)) {
|
|
1311
|
-
if (!packRef) continue;
|
|
1312
|
-
if (packRef.kind !== "extension") throw new Error(`extensionPacks() only accepts extension pack refs. Received kind "${packRef.kind}".`);
|
|
1313
|
-
if (packRef.familyId !== "sql") throw new Error(`extension pack "${packRef.id}" targets family "${packRef.familyId}" but this builder targets "sql".`);
|
|
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}".`);
|
|
1315
|
-
namespaces.add(packRef.id);
|
|
1316
|
-
nextExtensionPacks[name] = packRef;
|
|
1317
|
-
}
|
|
1318
|
-
return new SqlContractBuilder({
|
|
1319
|
-
...this.state,
|
|
1320
|
-
extensionPacks: nextExtensionPacks,
|
|
1321
|
-
extensionNamespaces: [...namespaces]
|
|
1322
|
-
});
|
|
1323
|
-
}
|
|
1324
|
-
capabilities(capabilities) {
|
|
1325
|
-
return new SqlContractBuilder({
|
|
1326
|
-
...this.state,
|
|
1327
|
-
capabilities
|
|
1328
|
-
});
|
|
1329
|
-
}
|
|
1330
|
-
storageHash(hash) {
|
|
1331
|
-
return new SqlContractBuilder({
|
|
1332
|
-
...this.state,
|
|
1333
|
-
storageHash: hash
|
|
1334
|
-
});
|
|
1335
|
-
}
|
|
1336
|
-
table(name, callback) {
|
|
1337
|
-
const tableBuilder = createTable(name);
|
|
1338
|
-
const result = callback(tableBuilder);
|
|
1339
|
-
const tableState = (result instanceof TableBuilder ? result : tableBuilder).build();
|
|
1340
|
-
return new SqlContractBuilder({
|
|
1341
|
-
...this.state,
|
|
1342
|
-
tables: {
|
|
1343
|
-
...this.state.tables,
|
|
1344
|
-
[name]: tableState
|
|
1345
|
-
}
|
|
1346
|
-
});
|
|
1347
|
-
}
|
|
1348
|
-
model(name, table, callback) {
|
|
1349
|
-
const modelBuilder = new ModelBuilder(name, table);
|
|
1350
|
-
const result = callback(modelBuilder);
|
|
1351
|
-
const modelState = (result instanceof ModelBuilder ? result : modelBuilder).build();
|
|
1352
|
-
return new SqlContractBuilder({
|
|
1353
|
-
...this.state,
|
|
1354
|
-
models: {
|
|
1355
|
-
...this.state.models,
|
|
1356
|
-
[name]: modelState
|
|
1357
|
-
}
|
|
1358
|
-
});
|
|
1359
|
-
}
|
|
1360
|
-
foreignKeyDefaults(config) {
|
|
1361
|
-
return new SqlContractBuilder({
|
|
1362
|
-
...this.state,
|
|
1363
|
-
foreignKeyDefaults: config
|
|
1364
|
-
});
|
|
1365
|
-
}
|
|
1366
|
-
storageType(name, typeInstance) {
|
|
1367
|
-
return new SqlContractBuilder({
|
|
1368
|
-
...this.state,
|
|
1369
|
-
storageTypes: {
|
|
1370
|
-
...this.state.storageTypes ?? {},
|
|
1371
|
-
[name]: typeInstance
|
|
1372
|
-
}
|
|
1373
|
-
});
|
|
1374
|
-
}
|
|
1375
|
-
};
|
|
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
|
+
}
|
|
1376
1273
|
function defineContract(definition, factory) {
|
|
1377
|
-
if (definition
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
})
|
|
1386
|
-
|
|
1387
|
-
}
|
|
1388
|
-
return new SqlContractBuilder();
|
|
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
|
+
});
|
|
1389
1284
|
}
|
|
1390
1285
|
|
|
1391
1286
|
//#endregion
|
|
1392
|
-
export {
|
|
1287
|
+
export { buildSqlContractFromDefinition, defineContract, field, model, rel };
|
|
1393
1288
|
//# sourceMappingURL=contract-builder.mjs.map
|