@prisma-next/sql-contract-ts 0.12.0 → 0.13.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/{build-contract-BCYW3_wE.mjs → build-contract-C-x2pfu4.mjs} +214 -76
- package/dist/build-contract-C-x2pfu4.mjs.map +1 -0
- package/dist/config-types.d.mts +7 -3
- package/dist/config-types.d.mts.map +1 -1
- package/dist/config-types.mjs +12 -9
- package/dist/config-types.mjs.map +1 -1
- package/dist/contract-builder.d.mts +319 -23
- package/dist/contract-builder.d.mts.map +1 -1
- package/dist/contract-builder.mjs +280 -44
- package/dist/contract-builder.mjs.map +1 -1
- package/package.json +13 -13
- package/schemas/data-contract-sql-v1.json +31 -0
- package/src/build-contract.ts +340 -95
- package/src/config-types.ts +24 -6
- package/src/contract-builder.ts +137 -16
- package/src/contract-definition.ts +57 -3
- package/src/contract-dsl.ts +346 -18
- package/src/contract-lowering.ts +188 -15
- package/src/contract-types.ts +18 -21
- package/src/enum-type.ts +236 -0
- package/src/exports/contract-builder.ts +5 -0
- package/dist/build-contract-BCYW3_wE.mjs.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { t as buildSqlContractFromDefinition } from "./build-contract-
|
|
1
|
+
import { t as buildSqlContractFromDefinition } from "./build-contract-C-x2pfu4.mjs";
|
|
2
2
|
import { blindCast } from "@prisma-next/utils/casts";
|
|
3
|
+
import { ifDefined } from "@prisma-next/utils/defined";
|
|
3
4
|
import { isColumnDefault } from "@prisma-next/contract/types";
|
|
4
5
|
import { createEntityHelpersFromNamespace } from "@prisma-next/contract-authoring";
|
|
5
6
|
import { isPostgresEnumStorageEntry, toStorageTypeInstance } from "@prisma-next/sql-contract/types";
|
|
6
|
-
import { ifDefined } from "@prisma-next/utils/defined";
|
|
7
7
|
import { assertNoCrossRegistryCollisions, instantiateAuthoringFieldPreset, instantiateAuthoringTypeConstructor, isAuthoringEntityTypeDescriptor, isAuthoringFieldPresetDescriptor, isAuthoringTypeConstructorDescriptor, mergeAuthoringNamespaces, validateAuthoringHelperArguments } from "@prisma-next/framework-components/authoring";
|
|
8
8
|
//#region src/authoring-helper-runtime.ts
|
|
9
9
|
function isNamedConstraintOptionsLike(value) {
|
|
@@ -79,6 +79,61 @@ function createFieldHelpersFromNamespace(namespace, createLeafHelper, path = [])
|
|
|
79
79
|
return helpers;
|
|
80
80
|
}
|
|
81
81
|
//#endregion
|
|
82
|
+
//#region src/enum-type.ts
|
|
83
|
+
function member(name, value) {
|
|
84
|
+
return {
|
|
85
|
+
name,
|
|
86
|
+
value: blindCast(value ?? name)
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Internal brand that identifies an EnumTypeHandle in the lowering pipeline.
|
|
91
|
+
* Not exported — callers only interact with `EnumTypeHandle`.
|
|
92
|
+
*/
|
|
93
|
+
const ENUM_TYPE_HANDLE_BRAND = Symbol("EnumTypeHandle");
|
|
94
|
+
function enumType(name, codec, ...members) {
|
|
95
|
+
if (members.length === 0) throw new Error(`enumType("${name}"): must have at least one member.`);
|
|
96
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
97
|
+
const seenValues = /* @__PURE__ */ new Set();
|
|
98
|
+
for (const m of members) {
|
|
99
|
+
if (seenNames.has(m.name)) throw new Error(`enumType("${name}"): duplicate member name "${m.name}". Member names must be unique.`);
|
|
100
|
+
seenNames.add(m.name);
|
|
101
|
+
if (seenValues.has(m.value)) throw new Error(`enumType("${name}"): duplicate member value "${m.value}". Member values must be unique.`);
|
|
102
|
+
seenValues.add(m.value);
|
|
103
|
+
}
|
|
104
|
+
const values = Object.freeze(members.map((m) => m.value));
|
|
105
|
+
const names = Object.freeze(members.map((m) => m.name));
|
|
106
|
+
const enumMembers = Object.freeze(members.map((m) => ({
|
|
107
|
+
name: m.name,
|
|
108
|
+
value: m.value
|
|
109
|
+
})));
|
|
110
|
+
const membersAccessor = Object.freeze(Object.fromEntries(members.map((m) => [m.name, m.value])));
|
|
111
|
+
const valueSet = new Set(values);
|
|
112
|
+
const valueToName = new Map(members.map((m) => [m.value, m.name]));
|
|
113
|
+
const valueToOrdinal = new Map(values.map((v, i) => [v, i]));
|
|
114
|
+
return {
|
|
115
|
+
[ENUM_TYPE_HANDLE_BRAND]: true,
|
|
116
|
+
enumName: name,
|
|
117
|
+
codecId: codec.codecId,
|
|
118
|
+
nativeType: codec.nativeType,
|
|
119
|
+
enumMembers,
|
|
120
|
+
values,
|
|
121
|
+
names,
|
|
122
|
+
members: membersAccessor,
|
|
123
|
+
has: (v) => valueSet.has(v),
|
|
124
|
+
nameOf: (v) => valueToName.get(v),
|
|
125
|
+
ordinalOf: (v) => valueToOrdinal.get(v) ?? -1
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Returns true when the value is an `EnumTypeHandle` produced by
|
|
130
|
+
* `enumType()`. Used in the lowering pipeline to detect enum handles
|
|
131
|
+
* in field state without importing the BRAND symbol at every call site.
|
|
132
|
+
*/
|
|
133
|
+
function isEnumTypeHandle(value) {
|
|
134
|
+
return typeof value === "object" && value !== null && Reflect.get(value, ENUM_TYPE_HANDLE_BRAND) === true;
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
82
137
|
//#region src/contract-dsl.ts
|
|
83
138
|
function toColumnDefault(value) {
|
|
84
139
|
if (isColumnDefault(value)) return value;
|
|
@@ -92,6 +147,15 @@ var ScalarFieldBuilder = class ScalarFieldBuilder {
|
|
|
92
147
|
constructor(state) {
|
|
93
148
|
this.state = state;
|
|
94
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Returns the physical column name when `.column(name)` was called, or
|
|
152
|
+
* `undefined` when the field uses the default (logical field name) mapping.
|
|
153
|
+
* Used by cross-space FK lowering to stamp the physical column name onto
|
|
154
|
+
* `TargetFieldRef.columnName` so FK target columns are resolved correctly.
|
|
155
|
+
*/
|
|
156
|
+
get physicalColumnName() {
|
|
157
|
+
return this.state.columnName;
|
|
158
|
+
}
|
|
95
159
|
optional() {
|
|
96
160
|
return new ScalarFieldBuilder({
|
|
97
161
|
...this.state,
|
|
@@ -166,6 +230,11 @@ function generatedField(spec) {
|
|
|
166
230
|
});
|
|
167
231
|
}
|
|
168
232
|
function namedTypeField(typeRef) {
|
|
233
|
+
if (isEnumTypeHandle(typeRef)) return new ScalarFieldBuilder({
|
|
234
|
+
kind: "scalar",
|
|
235
|
+
typeRef,
|
|
236
|
+
nullable: false
|
|
237
|
+
});
|
|
169
238
|
return new ScalarFieldBuilder({
|
|
170
239
|
kind: "scalar",
|
|
171
240
|
typeRef,
|
|
@@ -208,10 +277,16 @@ function normalizeTargetFieldRefInput(input) {
|
|
|
208
277
|
const [first] = refs;
|
|
209
278
|
if (!first) throw new Error("Expected at least one target ref");
|
|
210
279
|
if (refs.some((ref) => ref.modelName !== first.modelName)) throw new Error("All target refs in a foreign key must point to the same model");
|
|
280
|
+
if (refs.some((ref) => ref.spaceId !== first.spaceId)) throw new Error(`All target refs in a compound foreign key must share the same spaceId (found mismatch: "${first.spaceId ?? "<local>"}" vs "${refs.find((r) => r.spaceId !== first.spaceId)?.spaceId ?? "<local>"}")`);
|
|
281
|
+
if (refs.some((ref) => ref.namespaceId !== first.namespaceId)) throw new Error("All target refs in a compound foreign key must share the same namespaceId (found mismatch)");
|
|
282
|
+
if (refs.some((ref) => ref.tableName !== first.tableName)) throw new Error("All target refs in a compound foreign key must share the same tableName (found mismatch)");
|
|
211
283
|
return {
|
|
212
284
|
modelName: first.modelName,
|
|
213
|
-
fieldNames: refs.map((ref) => ref.fieldName),
|
|
214
|
-
source: refs.some((ref) => ref.source === "string") ? "string" : "token"
|
|
285
|
+
fieldNames: refs.map((ref) => ref.columnName ?? ref.fieldName),
|
|
286
|
+
source: refs.some((ref) => ref.source === "string") ? "string" : "token",
|
|
287
|
+
spaceId: first.spaceId,
|
|
288
|
+
namespaceId: first.namespaceId,
|
|
289
|
+
tableName: first.tableName
|
|
215
290
|
};
|
|
216
291
|
}
|
|
217
292
|
function createConstraintsDsl() {
|
|
@@ -254,6 +329,9 @@ function createConstraintsDsl() {
|
|
|
254
329
|
targetModel: normalizedTarget.modelName,
|
|
255
330
|
targetFields: normalizedTarget.fieldNames,
|
|
256
331
|
targetSource: normalizedTarget.source,
|
|
332
|
+
...normalizedTarget.spaceId !== void 0 ? { targetSpaceId: normalizedTarget.spaceId } : {},
|
|
333
|
+
...normalizedTarget.namespaceId !== void 0 ? { targetNamespaceId: normalizedTarget.namespaceId } : {},
|
|
334
|
+
...normalizedTarget.tableName !== void 0 ? { targetTableName: normalizedTarget.tableName } : {},
|
|
257
335
|
...options?.name ? { name: options.name } : {},
|
|
258
336
|
...options?.onDelete ? { onDelete: options.onDelete } : {},
|
|
259
337
|
...options?.onUpdate ? { onUpdate: options.onUpdate } : {},
|
|
@@ -277,14 +355,23 @@ function createFieldRefs(fields) {
|
|
|
277
355
|
};
|
|
278
356
|
return refs;
|
|
279
357
|
}
|
|
280
|
-
function createModelTokenRefs(modelName, fields) {
|
|
358
|
+
function createModelTokenRefs(modelName, fields, crossSpaceCoordinate) {
|
|
281
359
|
const refs = {};
|
|
282
|
-
for (const fieldName of Object.
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
360
|
+
for (const [fieldName, fieldBuilder] of Object.entries(fields)) {
|
|
361
|
+
const physicalColumn = crossSpaceCoordinate !== void 0 ? fieldBuilder.physicalColumnName : void 0;
|
|
362
|
+
refs[fieldName] = {
|
|
363
|
+
kind: "targetFieldRef",
|
|
364
|
+
source: "token",
|
|
365
|
+
modelName,
|
|
366
|
+
fieldName,
|
|
367
|
+
...crossSpaceCoordinate !== void 0 ? {
|
|
368
|
+
spaceId: crossSpaceCoordinate.spaceId,
|
|
369
|
+
...crossSpaceCoordinate.namespaceId !== void 0 ? { namespaceId: crossSpaceCoordinate.namespaceId } : {},
|
|
370
|
+
...crossSpaceCoordinate.tableName !== void 0 ? { tableName: crossSpaceCoordinate.tableName } : {},
|
|
371
|
+
...physicalColumn !== void 0 ? { columnName: physicalColumn } : {}
|
|
372
|
+
} : {}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
288
375
|
return refs;
|
|
289
376
|
}
|
|
290
377
|
function buildStageSpec(stageInput, context) {
|
|
@@ -316,12 +403,21 @@ var ContractModelBuilder = class ContractModelBuilder {
|
|
|
316
403
|
stageOne;
|
|
317
404
|
attributesFactory;
|
|
318
405
|
sqlFactory;
|
|
406
|
+
spaceId;
|
|
407
|
+
tableName;
|
|
319
408
|
refs;
|
|
320
|
-
constructor(stageOne, attributesFactory, sqlFactory) {
|
|
409
|
+
constructor(stageOne, attributesFactory, sqlFactory, spaceId, tableName) {
|
|
321
410
|
this.stageOne = stageOne;
|
|
322
411
|
this.attributesFactory = attributesFactory;
|
|
323
412
|
this.sqlFactory = sqlFactory;
|
|
324
|
-
this.
|
|
413
|
+
this.spaceId = spaceId;
|
|
414
|
+
this.tableName = tableName;
|
|
415
|
+
const crossSpaceCoordinate = spaceId !== void 0 ? {
|
|
416
|
+
spaceId,
|
|
417
|
+
...stageOne.namespace !== void 0 ? { namespaceId: stageOne.namespace } : {},
|
|
418
|
+
...tableName !== void 0 ? { tableName } : {}
|
|
419
|
+
} : void 0;
|
|
420
|
+
this.refs = blindCast(stageOne.modelName ? createModelTokenRefs(stageOne.modelName, stageOne.fields, crossSpaceCoordinate) : void 0);
|
|
325
421
|
}
|
|
326
422
|
ref(fieldName) {
|
|
327
423
|
const modelName = this.stageOne.modelName;
|
|
@@ -342,13 +438,14 @@ var ContractModelBuilder = class ContractModelBuilder {
|
|
|
342
438
|
...this.stageOne.relations,
|
|
343
439
|
...relations
|
|
344
440
|
}
|
|
345
|
-
}, this.attributesFactory, this.sqlFactory);
|
|
441
|
+
}, this.attributesFactory, this.sqlFactory, this.spaceId, this.tableName);
|
|
346
442
|
}
|
|
347
443
|
attributes(specOrFactory) {
|
|
348
|
-
return new ContractModelBuilder(this.stageOne, specOrFactory, this.sqlFactory);
|
|
444
|
+
return new ContractModelBuilder(this.stageOne, specOrFactory, this.sqlFactory, this.spaceId, this.tableName);
|
|
349
445
|
}
|
|
350
446
|
sql(specOrFactory) {
|
|
351
|
-
|
|
447
|
+
const nextTableName = typeof specOrFactory !== "function" ? specOrFactory.table : this.tableName;
|
|
448
|
+
return blindCast(new ContractModelBuilder(this.stageOne, this.attributesFactory, specOrFactory, this.spaceId, nextTableName));
|
|
352
449
|
}
|
|
353
450
|
buildAttributesSpec() {
|
|
354
451
|
if (!this.attributesFactory) return;
|
|
@@ -400,12 +497,47 @@ function model(modelNameOrInput, maybeInput) {
|
|
|
400
497
|
relations: input.relations ?? {}
|
|
401
498
|
});
|
|
402
499
|
}
|
|
500
|
+
/**
|
|
501
|
+
* Factory for building a standalone branded extension model handle.
|
|
502
|
+
*
|
|
503
|
+
* Use this instead of `new ContractModelBuilder(…)` when constructing handles
|
|
504
|
+
* for models that live in a foreign contract space (e.g. a Supabase extension
|
|
505
|
+
* model referenced by a user's contract). The `spaceId` brands the returned
|
|
506
|
+
* handle so `refs.<field>.spaceId` carries the foreign space identifier.
|
|
507
|
+
*
|
|
508
|
+
* @param name - The domain model name as declared in the foreign contract
|
|
509
|
+
* (e.g. `'AuthUser'`, not a bare table alias like `'User'`).
|
|
510
|
+
* @param input.namespace - The namespace within the foreign space (e.g. `'auth'`).
|
|
511
|
+
* @param input.fields - Field definitions (use `field.column(…)`).
|
|
512
|
+
* @param input.table - The physical table name in the foreign schema.
|
|
513
|
+
* @param spaceId - The extension space identifier (e.g. `'supabase'`).
|
|
514
|
+
*/
|
|
515
|
+
function extensionModel(name, input, spaceId) {
|
|
516
|
+
return new ContractModelBuilder({
|
|
517
|
+
modelName: name,
|
|
518
|
+
namespace: input.namespace,
|
|
519
|
+
fields: input.fields,
|
|
520
|
+
relations: {}
|
|
521
|
+
}, void 0, void 0, spaceId, input.table);
|
|
522
|
+
}
|
|
523
|
+
function isCrossSpaceHandle(value) {
|
|
524
|
+
if (typeof value !== "object" || value === null) return false;
|
|
525
|
+
const rec = blindCast(value);
|
|
526
|
+
return typeof rec["spaceId"] === "string" && typeof rec["stageOne"] === "object" && rec["stageOne"] !== null;
|
|
527
|
+
}
|
|
403
528
|
function belongsTo(toModel, options) {
|
|
529
|
+
const resolvedModel = typeof toModel === "function" ? toModel() : toModel;
|
|
530
|
+
const crossSpaceCoordinate = isCrossSpaceHandle(resolvedModel) ? {
|
|
531
|
+
spaceId: resolvedModel.spaceId,
|
|
532
|
+
...resolvedModel.tableName !== void 0 ? { tableName: resolvedModel.tableName } : {},
|
|
533
|
+
...resolvedModel.stageOne.namespace !== void 0 ? { namespaceId: resolvedModel.stageOne.namespace } : {}
|
|
534
|
+
} : void 0;
|
|
404
535
|
return new RelationBuilder({
|
|
405
536
|
kind: "belongsTo",
|
|
406
537
|
toModel: normalizeRelationModelSource(toModel),
|
|
407
538
|
from: options.from,
|
|
408
|
-
to: options.to
|
|
539
|
+
to: options.to,
|
|
540
|
+
...crossSpaceCoordinate !== void 0 ? crossSpaceCoordinate : {}
|
|
409
541
|
});
|
|
410
542
|
}
|
|
411
543
|
function hasMany(toModel, options) {
|
|
@@ -677,6 +809,10 @@ function buildStorageTypeReverseLookup(storageTypes) {
|
|
|
677
809
|
function resolveFieldDescriptor(modelName, fieldName, fieldState, storageTypes, storageTypeReverseLookup) {
|
|
678
810
|
if ("descriptor" in fieldState && fieldState.descriptor) return fieldState.descriptor;
|
|
679
811
|
if ("typeRef" in fieldState && fieldState.typeRef) {
|
|
812
|
+
if (isEnumTypeHandle(fieldState.typeRef)) return {
|
|
813
|
+
codecId: fieldState.typeRef.codecId,
|
|
814
|
+
nativeType: fieldState.typeRef.nativeType
|
|
815
|
+
};
|
|
680
816
|
const typeRef = typeof fieldState.typeRef === "string" ? fieldState.typeRef : storageTypeReverseLookup.get(fieldState.typeRef);
|
|
681
817
|
if (!typeRef) throw new Error(`Field "${modelName}.${fieldName}" references a storage type instance that is not present in definition.types`);
|
|
682
818
|
const referencedType = storageTypes[typeRef];
|
|
@@ -751,6 +887,33 @@ function resolveRelationForeignKeys(spec, allSpecs) {
|
|
|
751
887
|
const relation = relationBuilder.build();
|
|
752
888
|
if (relation.kind !== "belongsTo" || !relation.sql?.fk) continue;
|
|
753
889
|
const targetModelName = resolveRelationModelName(relation.toModel);
|
|
890
|
+
if (relation.spaceId !== void 0) {
|
|
891
|
+
const fields = normalizeRelationFieldNames(relation.from);
|
|
892
|
+
const targetFields = normalizeRelationFieldNames(relation.to);
|
|
893
|
+
assertRelationFieldArity({
|
|
894
|
+
modelName: spec.modelName,
|
|
895
|
+
relationName,
|
|
896
|
+
leftLabel: "source",
|
|
897
|
+
leftFields: fields,
|
|
898
|
+
rightLabel: "target",
|
|
899
|
+
rightFields: targetFields
|
|
900
|
+
});
|
|
901
|
+
foreignKeys.push({
|
|
902
|
+
kind: "fk",
|
|
903
|
+
fields,
|
|
904
|
+
targetModel: targetModelName,
|
|
905
|
+
targetFields,
|
|
906
|
+
targetSpaceId: relation.spaceId,
|
|
907
|
+
...relation.namespaceId !== void 0 ? { targetNamespaceId: relation.namespaceId } : {},
|
|
908
|
+
...relation.tableName !== void 0 ? { targetTableName: relation.tableName } : {},
|
|
909
|
+
...relation.sql.fk.name ? { name: relation.sql.fk.name } : {},
|
|
910
|
+
...relation.sql.fk.onDelete ? { onDelete: relation.sql.fk.onDelete } : {},
|
|
911
|
+
...relation.sql.fk.onUpdate ? { onUpdate: relation.sql.fk.onUpdate } : {},
|
|
912
|
+
...relation.sql.fk.constraint !== void 0 ? { constraint: relation.sql.fk.constraint } : {},
|
|
913
|
+
...relation.sql.fk.index !== void 0 ? { index: relation.sql.fk.index } : {}
|
|
914
|
+
});
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
754
917
|
if (!allSpecs.has(targetModelName)) throw new Error(`Relation "${spec.modelName}.${relationName}" references unknown model "${targetModelName}"`);
|
|
755
918
|
const fields = normalizeRelationFieldNames(relation.from);
|
|
756
919
|
const targetFields = normalizeRelationFieldNames(relation.to);
|
|
@@ -782,10 +945,8 @@ function resolveRelationAnchorFields(spec) {
|
|
|
782
945
|
if ("id" in spec.fieldToColumn) return ["id"];
|
|
783
946
|
throw new Error(`Model "${spec.modelName}" needs an explicit id or an "id" field to anchor non-owning relations`);
|
|
784
947
|
}
|
|
785
|
-
function lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs) {
|
|
948
|
+
function lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs, extensionPacks) {
|
|
786
949
|
const targetModelName = resolveRelationModelName(relation.toModel);
|
|
787
|
-
const targetSpec = allSpecs.get(targetModelName);
|
|
788
|
-
if (!targetSpec) throw new Error(`Relation "${currentSpec.modelName}.${relationName}" references unknown model "${targetModelName}"`);
|
|
789
950
|
const fromFields = normalizeRelationFieldNames(relation.from);
|
|
790
951
|
const toFields = normalizeRelationFieldNames(relation.to);
|
|
791
952
|
assertRelationFieldArity({
|
|
@@ -796,6 +957,27 @@ function lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs) {
|
|
|
796
957
|
rightLabel: "target",
|
|
797
958
|
rightFields: toFields
|
|
798
959
|
});
|
|
960
|
+
if (relation.spaceId !== void 0) {
|
|
961
|
+
assertKnownExtensionPack(extensionPacks, relation.spaceId, `Relation "${currentSpec.modelName}.${relationName}"`);
|
|
962
|
+
const targetTable = relation.tableName ?? targetModelName.toLowerCase();
|
|
963
|
+
const parentColumns = mapFieldNamesToColumnNames(currentSpec.modelName, fromFields, currentSpec.fieldToColumn);
|
|
964
|
+
return {
|
|
965
|
+
fieldName: relationName,
|
|
966
|
+
toModel: targetModelName,
|
|
967
|
+
toTable: targetTable,
|
|
968
|
+
cardinality: "N:1",
|
|
969
|
+
spaceId: relation.spaceId,
|
|
970
|
+
...relation.namespaceId !== void 0 ? { namespaceId: relation.namespaceId } : {},
|
|
971
|
+
on: {
|
|
972
|
+
parentTable: currentSpec.tableName,
|
|
973
|
+
parentColumns,
|
|
974
|
+
childTable: targetTable,
|
|
975
|
+
childColumns: toFields
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
const targetSpec = allSpecs.get(targetModelName);
|
|
980
|
+
if (!targetSpec) throw new Error(`Relation "${currentSpec.modelName}.${relationName}" references unknown model "${targetModelName}"`);
|
|
799
981
|
return {
|
|
800
982
|
fieldName: relationName,
|
|
801
983
|
toModel: targetModelName,
|
|
@@ -866,12 +1048,12 @@ function lowerManyToManyRelation(relationName, relation, currentSpec, allSpecs)
|
|
|
866
1048
|
}
|
|
867
1049
|
};
|
|
868
1050
|
}
|
|
869
|
-
function resolveRelationNode(relationName, relation, currentSpec, allSpecs) {
|
|
870
|
-
if (relation.kind === "belongsTo") return lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs);
|
|
1051
|
+
function resolveRelationNode(relationName, relation, currentSpec, allSpecs, extensionPacks) {
|
|
1052
|
+
if (relation.kind === "belongsTo") return lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs, extensionPacks);
|
|
871
1053
|
if (relation.kind === "hasMany" || relation.kind === "hasOne") return lowerHasOwnershipRelation(relationName, relation, currentSpec, allSpecs);
|
|
872
1054
|
return lowerManyToManyRelation(relationName, relation, currentSpec, allSpecs);
|
|
873
1055
|
}
|
|
874
|
-
function
|
|
1056
|
+
function lowerLocalForeignKeyNode(spec, targetSpec, foreignKey) {
|
|
875
1057
|
return {
|
|
876
1058
|
columns: mapFieldNamesToColumnNames(spec.modelName, foreignKey.fields, spec.fieldToColumn),
|
|
877
1059
|
references: {
|
|
@@ -886,33 +1068,70 @@ function lowerForeignKeyNode(spec, targetSpec, foreignKey) {
|
|
|
886
1068
|
...foreignKey.index !== void 0 ? { index: foreignKey.index } : {}
|
|
887
1069
|
};
|
|
888
1070
|
}
|
|
889
|
-
function
|
|
1071
|
+
function lowerCrossSpaceForeignKeyNode(spec, foreignKey) {
|
|
1072
|
+
return {
|
|
1073
|
+
columns: mapFieldNamesToColumnNames(spec.modelName, foreignKey.fields, spec.fieldToColumn),
|
|
1074
|
+
references: {
|
|
1075
|
+
model: foreignKey.targetModel,
|
|
1076
|
+
table: foreignKey.targetTableName ?? foreignKey.targetModel.toLowerCase(),
|
|
1077
|
+
columns: foreignKey.targetFields,
|
|
1078
|
+
...foreignKey.targetNamespaceId !== void 0 ? { namespaceId: foreignKey.targetNamespaceId } : {},
|
|
1079
|
+
spaceId: foreignKey.targetSpaceId
|
|
1080
|
+
},
|
|
1081
|
+
...foreignKey.name ? { name: foreignKey.name } : {},
|
|
1082
|
+
...foreignKey.onDelete ? { onDelete: foreignKey.onDelete } : {},
|
|
1083
|
+
...foreignKey.onUpdate ? { onUpdate: foreignKey.onUpdate } : {},
|
|
1084
|
+
...foreignKey.constraint !== void 0 ? { constraint: foreignKey.constraint } : {},
|
|
1085
|
+
...foreignKey.index !== void 0 ? { index: foreignKey.index } : {}
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
function assertKnownExtensionPack(extensionPacks, spaceId, context) {
|
|
1089
|
+
if (extensionPacks !== void 0 && Object.hasOwn(extensionPacks, spaceId)) return;
|
|
1090
|
+
throw new Error(`${context} references contract space "${spaceId}" but "${spaceId}" is not declared in extensionPacks. Add the pack to extensionPacks.`);
|
|
1091
|
+
}
|
|
1092
|
+
function resolveForeignKeyNodes(spec, allSpecs, extensionPacks) {
|
|
890
1093
|
const relationForeignKeys = resolveRelationForeignKeys(spec, allSpecs).map((foreignKey) => {
|
|
1094
|
+
if (foreignKey.targetSpaceId !== void 0) {
|
|
1095
|
+
assertKnownExtensionPack(extensionPacks, foreignKey.targetSpaceId, `Relation-derived foreign key on "${spec.modelName}"`);
|
|
1096
|
+
return lowerCrossSpaceForeignKeyNode(spec, {
|
|
1097
|
+
...foreignKey,
|
|
1098
|
+
targetSpaceId: foreignKey.targetSpaceId
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
891
1101
|
const targetSpec = allSpecs.get(foreignKey.targetModel);
|
|
892
1102
|
if (!targetSpec) throw new Error(`Foreign key on "${spec.modelName}" references unknown model "${foreignKey.targetModel}"`);
|
|
893
|
-
return
|
|
1103
|
+
return lowerLocalForeignKeyNode(spec, targetSpec, foreignKey);
|
|
894
1104
|
});
|
|
895
1105
|
const sqlForeignKeys = (spec.sqlSpec?.foreignKeys ?? []).map((foreignKey) => {
|
|
1106
|
+
if (foreignKey.targetSpaceId !== void 0) {
|
|
1107
|
+
assertKnownExtensionPack(extensionPacks, foreignKey.targetSpaceId, `Foreign key on "${spec.modelName}"`);
|
|
1108
|
+
return lowerCrossSpaceForeignKeyNode(spec, {
|
|
1109
|
+
...foreignKey,
|
|
1110
|
+
targetSpaceId: foreignKey.targetSpaceId
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
896
1113
|
const targetSpec = allSpecs.get(foreignKey.targetModel);
|
|
897
1114
|
if (!targetSpec) throw new Error(`Foreign key on "${spec.modelName}" references unknown model "${foreignKey.targetModel}"`);
|
|
898
|
-
return
|
|
1115
|
+
return lowerLocalForeignKeyNode(spec, targetSpec, foreignKey);
|
|
899
1116
|
});
|
|
900
1117
|
return [...relationForeignKeys, ...sqlForeignKeys];
|
|
901
1118
|
}
|
|
902
|
-
function resolveModelNode(spec, allSpecs, storageTypes, storageTypeReverseLookup) {
|
|
1119
|
+
function resolveModelNode(spec, allSpecs, storageTypes, storageTypeReverseLookup, extensionPacks) {
|
|
903
1120
|
const fields = [];
|
|
904
1121
|
for (const [fieldName, fieldBuilder] of Object.entries(spec.fieldBuilders)) {
|
|
905
1122
|
const fieldState = fieldBuilder.build();
|
|
906
1123
|
const descriptor = resolveFieldDescriptor(spec.modelName, fieldName, fieldState, storageTypes, storageTypeReverseLookup);
|
|
907
1124
|
const columnName = spec.fieldToColumn[fieldName];
|
|
908
1125
|
if (!columnName) throw new Error(`Column name resolution failed for "${spec.modelName}.${fieldName}"`);
|
|
1126
|
+
const enumHandle = "typeRef" in fieldState && isEnumTypeHandle(fieldState.typeRef) ? fieldState.typeRef : void 0;
|
|
909
1127
|
fields.push({
|
|
910
1128
|
fieldName,
|
|
911
1129
|
columnName,
|
|
912
1130
|
descriptor,
|
|
913
1131
|
nullable: fieldState.nullable,
|
|
914
1132
|
...fieldState.default ? { default: fieldState.default } : {},
|
|
915
|
-
...fieldState.executionDefaults ? { executionDefaults: fieldState.executionDefaults } : {}
|
|
1133
|
+
...fieldState.executionDefaults ? { executionDefaults: fieldState.executionDefaults } : {},
|
|
1134
|
+
...enumHandle !== void 0 ? { enumTypeHandle: enumHandle } : {}
|
|
916
1135
|
});
|
|
917
1136
|
}
|
|
918
1137
|
const { idConstraint } = spec;
|
|
@@ -926,8 +1145,8 @@ function resolveModelNode(spec, allSpecs, storageTypes, storageTypeReverseLookup
|
|
|
926
1145
|
...ifDefined("type", index.type),
|
|
927
1146
|
...ifDefined("options", index.options)
|
|
928
1147
|
}));
|
|
929
|
-
const foreignKeys = resolveForeignKeyNodes(spec, allSpecs);
|
|
930
|
-
const relations = Object.entries(spec.relations).map(([relationName, relationBuilder]) => resolveRelationNode(relationName, relationBuilder.build(), spec, allSpecs));
|
|
1148
|
+
const foreignKeys = resolveForeignKeyNodes(spec, allSpecs, extensionPacks);
|
|
1149
|
+
const relations = Object.entries(spec.relations).map(([relationName, relationBuilder]) => resolveRelationNode(relationName, relationBuilder.build(), spec, allSpecs, extensionPacks));
|
|
931
1150
|
return {
|
|
932
1151
|
modelName: spec.modelName,
|
|
933
1152
|
tableName: spec.tableName,
|
|
@@ -940,7 +1159,8 @@ function resolveModelNode(spec, allSpecs, storageTypes, storageTypeReverseLookup
|
|
|
940
1159
|
...uniques.length > 0 ? { uniques } : {},
|
|
941
1160
|
...indexes.length > 0 ? { indexes } : {},
|
|
942
1161
|
...foreignKeys.length > 0 ? { foreignKeys } : {},
|
|
943
|
-
...relations.length > 0 ? { relations } : {}
|
|
1162
|
+
...relations.length > 0 ? { relations } : {},
|
|
1163
|
+
...ifDefined("control", spec.sqlSpec?.control)
|
|
944
1164
|
};
|
|
945
1165
|
}
|
|
946
1166
|
function collectRuntimeModelSpecs(definition) {
|
|
@@ -991,22 +1211,24 @@ function collectRuntimeModelSpecs(definition) {
|
|
|
991
1211
|
modelSpecs
|
|
992
1212
|
};
|
|
993
1213
|
}
|
|
994
|
-
function lowerModels(collection) {
|
|
1214
|
+
function lowerModels(collection, extensionPacks) {
|
|
995
1215
|
emitTypedCrossModelFallbackWarnings(collection);
|
|
996
1216
|
const storageTypeReverseLookup = buildStorageTypeReverseLookup(collection.storageTypes);
|
|
997
|
-
return Array.from(collection.modelSpecs.values()).map((spec) => resolveModelNode(spec, collection.modelSpecs, collection.storageTypes, storageTypeReverseLookup));
|
|
1217
|
+
return Array.from(collection.modelSpecs.values()).map((spec) => resolveModelNode(spec, collection.modelSpecs, collection.storageTypes, storageTypeReverseLookup, extensionPacks));
|
|
998
1218
|
}
|
|
999
1219
|
function buildContractDefinition(definition) {
|
|
1000
1220
|
const collection = collectRuntimeModelSpecs(definition);
|
|
1001
|
-
const models = lowerModels(collection);
|
|
1221
|
+
const models = lowerModels(collection, definition.extensionPacks);
|
|
1002
1222
|
return {
|
|
1003
1223
|
target: definition.target,
|
|
1224
|
+
...ifDefined("defaultControlPolicy", definition.defaultControlPolicy),
|
|
1004
1225
|
...definition.extensionPacks ? { extensionPacks: definition.extensionPacks } : {},
|
|
1005
1226
|
...definition.storageHash ? { storageHash: definition.storageHash } : {},
|
|
1006
1227
|
...definition.foreignKeyDefaults ? { foreignKeyDefaults: definition.foreignKeyDefaults } : {},
|
|
1007
1228
|
...Object.keys(collection.storageTypes).length > 0 ? { storageTypes: collection.storageTypes } : {},
|
|
1008
1229
|
...definition.namespaces ? { namespaces: definition.namespaces } : {},
|
|
1009
1230
|
...definition.createNamespace ? { createNamespace: definition.createNamespace } : {},
|
|
1231
|
+
...definition.enums && Object.keys(definition.enums).length > 0 ? { enums: definition.enums } : {},
|
|
1010
1232
|
models
|
|
1011
1233
|
};
|
|
1012
1234
|
}
|
|
@@ -1094,19 +1316,33 @@ function buildContractFromDsl(definition) {
|
|
|
1094
1316
|
validatePerModelNamespaces(definition.target, definition.namespaces, definition.models ?? {});
|
|
1095
1317
|
return blindCast(buildSqlContractFromDefinition(buildContractDefinition(definition), definition.codecLookup));
|
|
1096
1318
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
return buildContractFromDsl({
|
|
1319
|
+
/** Implementation. */
|
|
1320
|
+
function buildBoundContract(family, target, definition, factory) {
|
|
1321
|
+
const full = {
|
|
1101
1322
|
...definition,
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1323
|
+
family,
|
|
1324
|
+
target
|
|
1325
|
+
};
|
|
1326
|
+
if (factory !== void 0) {
|
|
1327
|
+
const built = factory(createComposedAuthoringHelpers({
|
|
1328
|
+
family,
|
|
1329
|
+
target,
|
|
1105
1330
|
extensionPacks: definition.extensionPacks
|
|
1106
|
-
}))
|
|
1107
|
-
|
|
1331
|
+
}));
|
|
1332
|
+
return buildContractFromDsl({
|
|
1333
|
+
...full,
|
|
1334
|
+
...ifDefined("types", built.types),
|
|
1335
|
+
...ifDefined("models", built.models)
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
return buildContractFromDsl(full);
|
|
1339
|
+
}
|
|
1340
|
+
function defineContract(definition, factory) {
|
|
1341
|
+
if (!isContractInput(definition)) throw new TypeError("defineContract expects a contract definition object. Define your contract with defineContract({ family, target, models, ... }).");
|
|
1342
|
+
if (factory !== void 0) return buildBoundContract(definition.family, definition.target, definition, factory);
|
|
1343
|
+
return buildBoundContract(definition.family, definition.target, definition);
|
|
1108
1344
|
}
|
|
1109
1345
|
//#endregion
|
|
1110
|
-
export { buildSqlContractFromDefinition, defineContract, field, model, rel };
|
|
1346
|
+
export { buildBoundContract, buildSqlContractFromDefinition, defineContract, enumType, extensionModel, field, member, model, rel };
|
|
1111
1347
|
|
|
1112
1348
|
//# sourceMappingURL=contract-builder.mjs.map
|