@prisma-next/sql-contract-ts 0.12.0 → 0.13.0-dev.10
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-HP3IjjLv.mjs} +220 -79
- package/dist/build-contract-HP3IjjLv.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 +395 -39
- package/dist/contract-builder.d.mts.map +1 -1
- package/dist/contract-builder.mjs +295 -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 +348 -102
- package/src/config-types.ts +24 -6
- package/src/contract-builder.ts +171 -21
- 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 +123 -47
- package/src/enum-type.ts +306 -0
- package/src/exports/contract-builder.ts +13 -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-HP3IjjLv.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,71 @@ 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
|
+
const loweredValue = String(m.value);
|
|
102
|
+
if (seenValues.has(loweredValue)) throw new Error(`enumType("${name}"): duplicate member value "${loweredValue}". Member values must be unique.`);
|
|
103
|
+
seenValues.add(loweredValue);
|
|
104
|
+
}
|
|
105
|
+
const values = Object.freeze(members.map((m) => m.value));
|
|
106
|
+
const names = Object.freeze(members.map((m) => m.name));
|
|
107
|
+
const enumMembers = Object.freeze(members.map((m) => ({
|
|
108
|
+
name: m.name,
|
|
109
|
+
value: m.value
|
|
110
|
+
})));
|
|
111
|
+
const membersAccessor = Object.freeze(Object.fromEntries(members.map((m) => [m.name, m.value])));
|
|
112
|
+
const valueSet = new Set(values);
|
|
113
|
+
const valueToName = new Map(members.map((m) => [m.value, m.name]));
|
|
114
|
+
const valueToOrdinal = new Map(values.map((v, i) => [v, i]));
|
|
115
|
+
return {
|
|
116
|
+
[ENUM_TYPE_HANDLE_BRAND]: true,
|
|
117
|
+
enumName: name,
|
|
118
|
+
codecId: codec.codecId,
|
|
119
|
+
nativeType: codec.nativeType,
|
|
120
|
+
enumMembers,
|
|
121
|
+
values,
|
|
122
|
+
names,
|
|
123
|
+
members: membersAccessor,
|
|
124
|
+
has: (v) => valueSet.has(v),
|
|
125
|
+
nameOf: (v) => valueToName.get(v),
|
|
126
|
+
ordinalOf: (v) => valueToOrdinal.get(v) ?? -1
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Bind `enumType` to a target's codec typemap. The returned function is the
|
|
131
|
+
* same runtime `enumType`, retyped so member values are constrained to the
|
|
132
|
+
* codec's input type. Target packages call this with their pack's
|
|
133
|
+
* `ExtractCodecTypesFromPack<Pack>` to expose a codec-aware `enumType`.
|
|
134
|
+
*/
|
|
135
|
+
function bindEnumType() {
|
|
136
|
+
return enumType;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Returns true when the value is an `EnumTypeHandle` produced by
|
|
140
|
+
* `enumType()`. Used in the lowering pipeline to detect enum handles
|
|
141
|
+
* in field state without importing the BRAND symbol at every call site.
|
|
142
|
+
*/
|
|
143
|
+
function isEnumTypeHandle(value) {
|
|
144
|
+
return typeof value === "object" && value !== null && Reflect.get(value, ENUM_TYPE_HANDLE_BRAND) === true;
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
82
147
|
//#region src/contract-dsl.ts
|
|
83
148
|
function toColumnDefault(value) {
|
|
84
149
|
if (isColumnDefault(value)) return value;
|
|
@@ -92,6 +157,15 @@ var ScalarFieldBuilder = class ScalarFieldBuilder {
|
|
|
92
157
|
constructor(state) {
|
|
93
158
|
this.state = state;
|
|
94
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Returns the physical column name when `.column(name)` was called, or
|
|
162
|
+
* `undefined` when the field uses the default (logical field name) mapping.
|
|
163
|
+
* Used by cross-space FK lowering to stamp the physical column name onto
|
|
164
|
+
* `TargetFieldRef.columnName` so FK target columns are resolved correctly.
|
|
165
|
+
*/
|
|
166
|
+
get physicalColumnName() {
|
|
167
|
+
return this.state.columnName;
|
|
168
|
+
}
|
|
95
169
|
optional() {
|
|
96
170
|
return new ScalarFieldBuilder({
|
|
97
171
|
...this.state,
|
|
@@ -166,6 +240,11 @@ function generatedField(spec) {
|
|
|
166
240
|
});
|
|
167
241
|
}
|
|
168
242
|
function namedTypeField(typeRef) {
|
|
243
|
+
if (isEnumTypeHandle(typeRef)) return new ScalarFieldBuilder({
|
|
244
|
+
kind: "scalar",
|
|
245
|
+
typeRef,
|
|
246
|
+
nullable: false
|
|
247
|
+
});
|
|
169
248
|
return new ScalarFieldBuilder({
|
|
170
249
|
kind: "scalar",
|
|
171
250
|
typeRef,
|
|
@@ -208,10 +287,16 @@ function normalizeTargetFieldRefInput(input) {
|
|
|
208
287
|
const [first] = refs;
|
|
209
288
|
if (!first) throw new Error("Expected at least one target ref");
|
|
210
289
|
if (refs.some((ref) => ref.modelName !== first.modelName)) throw new Error("All target refs in a foreign key must point to the same model");
|
|
290
|
+
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>"}")`);
|
|
291
|
+
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)");
|
|
292
|
+
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
293
|
return {
|
|
212
294
|
modelName: first.modelName,
|
|
213
|
-
fieldNames: refs.map((ref) => ref.fieldName),
|
|
214
|
-
source: refs.some((ref) => ref.source === "string") ? "string" : "token"
|
|
295
|
+
fieldNames: refs.map((ref) => ref.columnName ?? ref.fieldName),
|
|
296
|
+
source: refs.some((ref) => ref.source === "string") ? "string" : "token",
|
|
297
|
+
spaceId: first.spaceId,
|
|
298
|
+
namespaceId: first.namespaceId,
|
|
299
|
+
tableName: first.tableName
|
|
215
300
|
};
|
|
216
301
|
}
|
|
217
302
|
function createConstraintsDsl() {
|
|
@@ -254,6 +339,9 @@ function createConstraintsDsl() {
|
|
|
254
339
|
targetModel: normalizedTarget.modelName,
|
|
255
340
|
targetFields: normalizedTarget.fieldNames,
|
|
256
341
|
targetSource: normalizedTarget.source,
|
|
342
|
+
...normalizedTarget.spaceId !== void 0 ? { targetSpaceId: normalizedTarget.spaceId } : {},
|
|
343
|
+
...normalizedTarget.namespaceId !== void 0 ? { targetNamespaceId: normalizedTarget.namespaceId } : {},
|
|
344
|
+
...normalizedTarget.tableName !== void 0 ? { targetTableName: normalizedTarget.tableName } : {},
|
|
257
345
|
...options?.name ? { name: options.name } : {},
|
|
258
346
|
...options?.onDelete ? { onDelete: options.onDelete } : {},
|
|
259
347
|
...options?.onUpdate ? { onUpdate: options.onUpdate } : {},
|
|
@@ -277,14 +365,23 @@ function createFieldRefs(fields) {
|
|
|
277
365
|
};
|
|
278
366
|
return refs;
|
|
279
367
|
}
|
|
280
|
-
function createModelTokenRefs(modelName, fields) {
|
|
368
|
+
function createModelTokenRefs(modelName, fields, crossSpaceCoordinate) {
|
|
281
369
|
const refs = {};
|
|
282
|
-
for (const fieldName of Object.
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
370
|
+
for (const [fieldName, fieldBuilder] of Object.entries(fields)) {
|
|
371
|
+
const physicalColumn = crossSpaceCoordinate !== void 0 ? fieldBuilder.physicalColumnName : void 0;
|
|
372
|
+
refs[fieldName] = {
|
|
373
|
+
kind: "targetFieldRef",
|
|
374
|
+
source: "token",
|
|
375
|
+
modelName,
|
|
376
|
+
fieldName,
|
|
377
|
+
...crossSpaceCoordinate !== void 0 ? {
|
|
378
|
+
spaceId: crossSpaceCoordinate.spaceId,
|
|
379
|
+
...crossSpaceCoordinate.namespaceId !== void 0 ? { namespaceId: crossSpaceCoordinate.namespaceId } : {},
|
|
380
|
+
...crossSpaceCoordinate.tableName !== void 0 ? { tableName: crossSpaceCoordinate.tableName } : {},
|
|
381
|
+
...physicalColumn !== void 0 ? { columnName: physicalColumn } : {}
|
|
382
|
+
} : {}
|
|
383
|
+
};
|
|
384
|
+
}
|
|
288
385
|
return refs;
|
|
289
386
|
}
|
|
290
387
|
function buildStageSpec(stageInput, context) {
|
|
@@ -316,12 +413,21 @@ var ContractModelBuilder = class ContractModelBuilder {
|
|
|
316
413
|
stageOne;
|
|
317
414
|
attributesFactory;
|
|
318
415
|
sqlFactory;
|
|
416
|
+
spaceId;
|
|
417
|
+
tableName;
|
|
319
418
|
refs;
|
|
320
|
-
constructor(stageOne, attributesFactory, sqlFactory) {
|
|
419
|
+
constructor(stageOne, attributesFactory, sqlFactory, spaceId, tableName) {
|
|
321
420
|
this.stageOne = stageOne;
|
|
322
421
|
this.attributesFactory = attributesFactory;
|
|
323
422
|
this.sqlFactory = sqlFactory;
|
|
324
|
-
this.
|
|
423
|
+
this.spaceId = spaceId;
|
|
424
|
+
this.tableName = tableName;
|
|
425
|
+
const crossSpaceCoordinate = spaceId !== void 0 ? {
|
|
426
|
+
spaceId,
|
|
427
|
+
...stageOne.namespace !== void 0 ? { namespaceId: stageOne.namespace } : {},
|
|
428
|
+
...tableName !== void 0 ? { tableName } : {}
|
|
429
|
+
} : void 0;
|
|
430
|
+
this.refs = blindCast(stageOne.modelName ? createModelTokenRefs(stageOne.modelName, stageOne.fields, crossSpaceCoordinate) : void 0);
|
|
325
431
|
}
|
|
326
432
|
ref(fieldName) {
|
|
327
433
|
const modelName = this.stageOne.modelName;
|
|
@@ -342,13 +448,14 @@ var ContractModelBuilder = class ContractModelBuilder {
|
|
|
342
448
|
...this.stageOne.relations,
|
|
343
449
|
...relations
|
|
344
450
|
}
|
|
345
|
-
}, this.attributesFactory, this.sqlFactory);
|
|
451
|
+
}, this.attributesFactory, this.sqlFactory, this.spaceId, this.tableName);
|
|
346
452
|
}
|
|
347
453
|
attributes(specOrFactory) {
|
|
348
|
-
return new ContractModelBuilder(this.stageOne, specOrFactory, this.sqlFactory);
|
|
454
|
+
return new ContractModelBuilder(this.stageOne, specOrFactory, this.sqlFactory, this.spaceId, this.tableName);
|
|
349
455
|
}
|
|
350
456
|
sql(specOrFactory) {
|
|
351
|
-
|
|
457
|
+
const nextTableName = typeof specOrFactory !== "function" ? specOrFactory.table : this.tableName;
|
|
458
|
+
return blindCast(new ContractModelBuilder(this.stageOne, this.attributesFactory, specOrFactory, this.spaceId, nextTableName));
|
|
352
459
|
}
|
|
353
460
|
buildAttributesSpec() {
|
|
354
461
|
if (!this.attributesFactory) return;
|
|
@@ -400,12 +507,47 @@ function model(modelNameOrInput, maybeInput) {
|
|
|
400
507
|
relations: input.relations ?? {}
|
|
401
508
|
});
|
|
402
509
|
}
|
|
510
|
+
/**
|
|
511
|
+
* Factory for building a standalone branded extension model handle.
|
|
512
|
+
*
|
|
513
|
+
* Use this instead of `new ContractModelBuilder(…)` when constructing handles
|
|
514
|
+
* for models that live in a foreign contract space (e.g. a Supabase extension
|
|
515
|
+
* model referenced by a user's contract). The `spaceId` brands the returned
|
|
516
|
+
* handle so `refs.<field>.spaceId` carries the foreign space identifier.
|
|
517
|
+
*
|
|
518
|
+
* @param name - The domain model name as declared in the foreign contract
|
|
519
|
+
* (e.g. `'AuthUser'`, not a bare table alias like `'User'`).
|
|
520
|
+
* @param input.namespace - The namespace within the foreign space (e.g. `'auth'`).
|
|
521
|
+
* @param input.fields - Field definitions (use `field.column(…)`).
|
|
522
|
+
* @param input.table - The physical table name in the foreign schema.
|
|
523
|
+
* @param spaceId - The extension space identifier (e.g. `'supabase'`).
|
|
524
|
+
*/
|
|
525
|
+
function extensionModel(name, input, spaceId) {
|
|
526
|
+
return new ContractModelBuilder({
|
|
527
|
+
modelName: name,
|
|
528
|
+
namespace: input.namespace,
|
|
529
|
+
fields: input.fields,
|
|
530
|
+
relations: {}
|
|
531
|
+
}, void 0, void 0, spaceId, input.table);
|
|
532
|
+
}
|
|
533
|
+
function isCrossSpaceHandle(value) {
|
|
534
|
+
if (typeof value !== "object" || value === null) return false;
|
|
535
|
+
const rec = blindCast(value);
|
|
536
|
+
return typeof rec["spaceId"] === "string" && typeof rec["stageOne"] === "object" && rec["stageOne"] !== null;
|
|
537
|
+
}
|
|
403
538
|
function belongsTo(toModel, options) {
|
|
539
|
+
const resolvedModel = typeof toModel === "function" ? toModel() : toModel;
|
|
540
|
+
const crossSpaceCoordinate = isCrossSpaceHandle(resolvedModel) ? {
|
|
541
|
+
spaceId: resolvedModel.spaceId,
|
|
542
|
+
...resolvedModel.tableName !== void 0 ? { tableName: resolvedModel.tableName } : {},
|
|
543
|
+
...resolvedModel.stageOne.namespace !== void 0 ? { namespaceId: resolvedModel.stageOne.namespace } : {}
|
|
544
|
+
} : void 0;
|
|
404
545
|
return new RelationBuilder({
|
|
405
546
|
kind: "belongsTo",
|
|
406
547
|
toModel: normalizeRelationModelSource(toModel),
|
|
407
548
|
from: options.from,
|
|
408
|
-
to: options.to
|
|
549
|
+
to: options.to,
|
|
550
|
+
...crossSpaceCoordinate !== void 0 ? crossSpaceCoordinate : {}
|
|
409
551
|
});
|
|
410
552
|
}
|
|
411
553
|
function hasMany(toModel, options) {
|
|
@@ -677,6 +819,10 @@ function buildStorageTypeReverseLookup(storageTypes) {
|
|
|
677
819
|
function resolveFieldDescriptor(modelName, fieldName, fieldState, storageTypes, storageTypeReverseLookup) {
|
|
678
820
|
if ("descriptor" in fieldState && fieldState.descriptor) return fieldState.descriptor;
|
|
679
821
|
if ("typeRef" in fieldState && fieldState.typeRef) {
|
|
822
|
+
if (isEnumTypeHandle(fieldState.typeRef)) return {
|
|
823
|
+
codecId: fieldState.typeRef.codecId,
|
|
824
|
+
nativeType: fieldState.typeRef.nativeType
|
|
825
|
+
};
|
|
680
826
|
const typeRef = typeof fieldState.typeRef === "string" ? fieldState.typeRef : storageTypeReverseLookup.get(fieldState.typeRef);
|
|
681
827
|
if (!typeRef) throw new Error(`Field "${modelName}.${fieldName}" references a storage type instance that is not present in definition.types`);
|
|
682
828
|
const referencedType = storageTypes[typeRef];
|
|
@@ -751,6 +897,33 @@ function resolveRelationForeignKeys(spec, allSpecs) {
|
|
|
751
897
|
const relation = relationBuilder.build();
|
|
752
898
|
if (relation.kind !== "belongsTo" || !relation.sql?.fk) continue;
|
|
753
899
|
const targetModelName = resolveRelationModelName(relation.toModel);
|
|
900
|
+
if (relation.spaceId !== void 0) {
|
|
901
|
+
const fields = normalizeRelationFieldNames(relation.from);
|
|
902
|
+
const targetFields = normalizeRelationFieldNames(relation.to);
|
|
903
|
+
assertRelationFieldArity({
|
|
904
|
+
modelName: spec.modelName,
|
|
905
|
+
relationName,
|
|
906
|
+
leftLabel: "source",
|
|
907
|
+
leftFields: fields,
|
|
908
|
+
rightLabel: "target",
|
|
909
|
+
rightFields: targetFields
|
|
910
|
+
});
|
|
911
|
+
foreignKeys.push({
|
|
912
|
+
kind: "fk",
|
|
913
|
+
fields,
|
|
914
|
+
targetModel: targetModelName,
|
|
915
|
+
targetFields,
|
|
916
|
+
targetSpaceId: relation.spaceId,
|
|
917
|
+
...relation.namespaceId !== void 0 ? { targetNamespaceId: relation.namespaceId } : {},
|
|
918
|
+
...relation.tableName !== void 0 ? { targetTableName: relation.tableName } : {},
|
|
919
|
+
...relation.sql.fk.name ? { name: relation.sql.fk.name } : {},
|
|
920
|
+
...relation.sql.fk.onDelete ? { onDelete: relation.sql.fk.onDelete } : {},
|
|
921
|
+
...relation.sql.fk.onUpdate ? { onUpdate: relation.sql.fk.onUpdate } : {},
|
|
922
|
+
...relation.sql.fk.constraint !== void 0 ? { constraint: relation.sql.fk.constraint } : {},
|
|
923
|
+
...relation.sql.fk.index !== void 0 ? { index: relation.sql.fk.index } : {}
|
|
924
|
+
});
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
754
927
|
if (!allSpecs.has(targetModelName)) throw new Error(`Relation "${spec.modelName}.${relationName}" references unknown model "${targetModelName}"`);
|
|
755
928
|
const fields = normalizeRelationFieldNames(relation.from);
|
|
756
929
|
const targetFields = normalizeRelationFieldNames(relation.to);
|
|
@@ -782,10 +955,8 @@ function resolveRelationAnchorFields(spec) {
|
|
|
782
955
|
if ("id" in spec.fieldToColumn) return ["id"];
|
|
783
956
|
throw new Error(`Model "${spec.modelName}" needs an explicit id or an "id" field to anchor non-owning relations`);
|
|
784
957
|
}
|
|
785
|
-
function lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs) {
|
|
958
|
+
function lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs, extensionPacks) {
|
|
786
959
|
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
960
|
const fromFields = normalizeRelationFieldNames(relation.from);
|
|
790
961
|
const toFields = normalizeRelationFieldNames(relation.to);
|
|
791
962
|
assertRelationFieldArity({
|
|
@@ -796,6 +967,27 @@ function lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs) {
|
|
|
796
967
|
rightLabel: "target",
|
|
797
968
|
rightFields: toFields
|
|
798
969
|
});
|
|
970
|
+
if (relation.spaceId !== void 0) {
|
|
971
|
+
assertKnownExtensionPack(extensionPacks, relation.spaceId, `Relation "${currentSpec.modelName}.${relationName}"`);
|
|
972
|
+
const targetTable = relation.tableName ?? targetModelName.toLowerCase();
|
|
973
|
+
const parentColumns = mapFieldNamesToColumnNames(currentSpec.modelName, fromFields, currentSpec.fieldToColumn);
|
|
974
|
+
return {
|
|
975
|
+
fieldName: relationName,
|
|
976
|
+
toModel: targetModelName,
|
|
977
|
+
toTable: targetTable,
|
|
978
|
+
cardinality: "N:1",
|
|
979
|
+
spaceId: relation.spaceId,
|
|
980
|
+
...relation.namespaceId !== void 0 ? { namespaceId: relation.namespaceId } : {},
|
|
981
|
+
on: {
|
|
982
|
+
parentTable: currentSpec.tableName,
|
|
983
|
+
parentColumns,
|
|
984
|
+
childTable: targetTable,
|
|
985
|
+
childColumns: toFields
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
const targetSpec = allSpecs.get(targetModelName);
|
|
990
|
+
if (!targetSpec) throw new Error(`Relation "${currentSpec.modelName}.${relationName}" references unknown model "${targetModelName}"`);
|
|
799
991
|
return {
|
|
800
992
|
fieldName: relationName,
|
|
801
993
|
toModel: targetModelName,
|
|
@@ -866,12 +1058,12 @@ function lowerManyToManyRelation(relationName, relation, currentSpec, allSpecs)
|
|
|
866
1058
|
}
|
|
867
1059
|
};
|
|
868
1060
|
}
|
|
869
|
-
function resolveRelationNode(relationName, relation, currentSpec, allSpecs) {
|
|
870
|
-
if (relation.kind === "belongsTo") return lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs);
|
|
1061
|
+
function resolveRelationNode(relationName, relation, currentSpec, allSpecs, extensionPacks) {
|
|
1062
|
+
if (relation.kind === "belongsTo") return lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs, extensionPacks);
|
|
871
1063
|
if (relation.kind === "hasMany" || relation.kind === "hasOne") return lowerHasOwnershipRelation(relationName, relation, currentSpec, allSpecs);
|
|
872
1064
|
return lowerManyToManyRelation(relationName, relation, currentSpec, allSpecs);
|
|
873
1065
|
}
|
|
874
|
-
function
|
|
1066
|
+
function lowerLocalForeignKeyNode(spec, targetSpec, foreignKey) {
|
|
875
1067
|
return {
|
|
876
1068
|
columns: mapFieldNamesToColumnNames(spec.modelName, foreignKey.fields, spec.fieldToColumn),
|
|
877
1069
|
references: {
|
|
@@ -886,33 +1078,70 @@ function lowerForeignKeyNode(spec, targetSpec, foreignKey) {
|
|
|
886
1078
|
...foreignKey.index !== void 0 ? { index: foreignKey.index } : {}
|
|
887
1079
|
};
|
|
888
1080
|
}
|
|
889
|
-
function
|
|
1081
|
+
function lowerCrossSpaceForeignKeyNode(spec, foreignKey) {
|
|
1082
|
+
return {
|
|
1083
|
+
columns: mapFieldNamesToColumnNames(spec.modelName, foreignKey.fields, spec.fieldToColumn),
|
|
1084
|
+
references: {
|
|
1085
|
+
model: foreignKey.targetModel,
|
|
1086
|
+
table: foreignKey.targetTableName ?? foreignKey.targetModel.toLowerCase(),
|
|
1087
|
+
columns: foreignKey.targetFields,
|
|
1088
|
+
...foreignKey.targetNamespaceId !== void 0 ? { namespaceId: foreignKey.targetNamespaceId } : {},
|
|
1089
|
+
spaceId: foreignKey.targetSpaceId
|
|
1090
|
+
},
|
|
1091
|
+
...foreignKey.name ? { name: foreignKey.name } : {},
|
|
1092
|
+
...foreignKey.onDelete ? { onDelete: foreignKey.onDelete } : {},
|
|
1093
|
+
...foreignKey.onUpdate ? { onUpdate: foreignKey.onUpdate } : {},
|
|
1094
|
+
...foreignKey.constraint !== void 0 ? { constraint: foreignKey.constraint } : {},
|
|
1095
|
+
...foreignKey.index !== void 0 ? { index: foreignKey.index } : {}
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
function assertKnownExtensionPack(extensionPacks, spaceId, context) {
|
|
1099
|
+
if (extensionPacks !== void 0 && Object.hasOwn(extensionPacks, spaceId)) return;
|
|
1100
|
+
throw new Error(`${context} references contract space "${spaceId}" but "${spaceId}" is not declared in extensionPacks. Add the pack to extensionPacks.`);
|
|
1101
|
+
}
|
|
1102
|
+
function resolveForeignKeyNodes(spec, allSpecs, extensionPacks) {
|
|
890
1103
|
const relationForeignKeys = resolveRelationForeignKeys(spec, allSpecs).map((foreignKey) => {
|
|
1104
|
+
if (foreignKey.targetSpaceId !== void 0) {
|
|
1105
|
+
assertKnownExtensionPack(extensionPacks, foreignKey.targetSpaceId, `Relation-derived foreign key on "${spec.modelName}"`);
|
|
1106
|
+
return lowerCrossSpaceForeignKeyNode(spec, {
|
|
1107
|
+
...foreignKey,
|
|
1108
|
+
targetSpaceId: foreignKey.targetSpaceId
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
891
1111
|
const targetSpec = allSpecs.get(foreignKey.targetModel);
|
|
892
1112
|
if (!targetSpec) throw new Error(`Foreign key on "${spec.modelName}" references unknown model "${foreignKey.targetModel}"`);
|
|
893
|
-
return
|
|
1113
|
+
return lowerLocalForeignKeyNode(spec, targetSpec, foreignKey);
|
|
894
1114
|
});
|
|
895
1115
|
const sqlForeignKeys = (spec.sqlSpec?.foreignKeys ?? []).map((foreignKey) => {
|
|
1116
|
+
if (foreignKey.targetSpaceId !== void 0) {
|
|
1117
|
+
assertKnownExtensionPack(extensionPacks, foreignKey.targetSpaceId, `Foreign key on "${spec.modelName}"`);
|
|
1118
|
+
return lowerCrossSpaceForeignKeyNode(spec, {
|
|
1119
|
+
...foreignKey,
|
|
1120
|
+
targetSpaceId: foreignKey.targetSpaceId
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
896
1123
|
const targetSpec = allSpecs.get(foreignKey.targetModel);
|
|
897
1124
|
if (!targetSpec) throw new Error(`Foreign key on "${spec.modelName}" references unknown model "${foreignKey.targetModel}"`);
|
|
898
|
-
return
|
|
1125
|
+
return lowerLocalForeignKeyNode(spec, targetSpec, foreignKey);
|
|
899
1126
|
});
|
|
900
1127
|
return [...relationForeignKeys, ...sqlForeignKeys];
|
|
901
1128
|
}
|
|
902
|
-
function resolveModelNode(spec, allSpecs, storageTypes, storageTypeReverseLookup) {
|
|
1129
|
+
function resolveModelNode(spec, allSpecs, storageTypes, storageTypeReverseLookup, extensionPacks) {
|
|
903
1130
|
const fields = [];
|
|
904
1131
|
for (const [fieldName, fieldBuilder] of Object.entries(spec.fieldBuilders)) {
|
|
905
1132
|
const fieldState = fieldBuilder.build();
|
|
906
1133
|
const descriptor = resolveFieldDescriptor(spec.modelName, fieldName, fieldState, storageTypes, storageTypeReverseLookup);
|
|
907
1134
|
const columnName = spec.fieldToColumn[fieldName];
|
|
908
1135
|
if (!columnName) throw new Error(`Column name resolution failed for "${spec.modelName}.${fieldName}"`);
|
|
1136
|
+
const enumHandle = "typeRef" in fieldState && isEnumTypeHandle(fieldState.typeRef) ? fieldState.typeRef : void 0;
|
|
909
1137
|
fields.push({
|
|
910
1138
|
fieldName,
|
|
911
1139
|
columnName,
|
|
912
1140
|
descriptor,
|
|
913
1141
|
nullable: fieldState.nullable,
|
|
914
1142
|
...fieldState.default ? { default: fieldState.default } : {},
|
|
915
|
-
...fieldState.executionDefaults ? { executionDefaults: fieldState.executionDefaults } : {}
|
|
1143
|
+
...fieldState.executionDefaults ? { executionDefaults: fieldState.executionDefaults } : {},
|
|
1144
|
+
...enumHandle !== void 0 ? { enumTypeHandle: enumHandle } : {}
|
|
916
1145
|
});
|
|
917
1146
|
}
|
|
918
1147
|
const { idConstraint } = spec;
|
|
@@ -926,8 +1155,8 @@ function resolveModelNode(spec, allSpecs, storageTypes, storageTypeReverseLookup
|
|
|
926
1155
|
...ifDefined("type", index.type),
|
|
927
1156
|
...ifDefined("options", index.options)
|
|
928
1157
|
}));
|
|
929
|
-
const foreignKeys = resolveForeignKeyNodes(spec, allSpecs);
|
|
930
|
-
const relations = Object.entries(spec.relations).map(([relationName, relationBuilder]) => resolveRelationNode(relationName, relationBuilder.build(), spec, allSpecs));
|
|
1158
|
+
const foreignKeys = resolveForeignKeyNodes(spec, allSpecs, extensionPacks);
|
|
1159
|
+
const relations = Object.entries(spec.relations).map(([relationName, relationBuilder]) => resolveRelationNode(relationName, relationBuilder.build(), spec, allSpecs, extensionPacks));
|
|
931
1160
|
return {
|
|
932
1161
|
modelName: spec.modelName,
|
|
933
1162
|
tableName: spec.tableName,
|
|
@@ -940,7 +1169,8 @@ function resolveModelNode(spec, allSpecs, storageTypes, storageTypeReverseLookup
|
|
|
940
1169
|
...uniques.length > 0 ? { uniques } : {},
|
|
941
1170
|
...indexes.length > 0 ? { indexes } : {},
|
|
942
1171
|
...foreignKeys.length > 0 ? { foreignKeys } : {},
|
|
943
|
-
...relations.length > 0 ? { relations } : {}
|
|
1172
|
+
...relations.length > 0 ? { relations } : {},
|
|
1173
|
+
...ifDefined("control", spec.sqlSpec?.control)
|
|
944
1174
|
};
|
|
945
1175
|
}
|
|
946
1176
|
function collectRuntimeModelSpecs(definition) {
|
|
@@ -991,22 +1221,24 @@ function collectRuntimeModelSpecs(definition) {
|
|
|
991
1221
|
modelSpecs
|
|
992
1222
|
};
|
|
993
1223
|
}
|
|
994
|
-
function lowerModels(collection) {
|
|
1224
|
+
function lowerModels(collection, extensionPacks) {
|
|
995
1225
|
emitTypedCrossModelFallbackWarnings(collection);
|
|
996
1226
|
const storageTypeReverseLookup = buildStorageTypeReverseLookup(collection.storageTypes);
|
|
997
|
-
return Array.from(collection.modelSpecs.values()).map((spec) => resolveModelNode(spec, collection.modelSpecs, collection.storageTypes, storageTypeReverseLookup));
|
|
1227
|
+
return Array.from(collection.modelSpecs.values()).map((spec) => resolveModelNode(spec, collection.modelSpecs, collection.storageTypes, storageTypeReverseLookup, extensionPacks));
|
|
998
1228
|
}
|
|
999
1229
|
function buildContractDefinition(definition) {
|
|
1000
1230
|
const collection = collectRuntimeModelSpecs(definition);
|
|
1001
|
-
const models = lowerModels(collection);
|
|
1231
|
+
const models = lowerModels(collection, definition.extensionPacks);
|
|
1002
1232
|
return {
|
|
1003
1233
|
target: definition.target,
|
|
1234
|
+
...ifDefined("defaultControlPolicy", definition.defaultControlPolicy),
|
|
1004
1235
|
...definition.extensionPacks ? { extensionPacks: definition.extensionPacks } : {},
|
|
1005
1236
|
...definition.storageHash ? { storageHash: definition.storageHash } : {},
|
|
1006
1237
|
...definition.foreignKeyDefaults ? { foreignKeyDefaults: definition.foreignKeyDefaults } : {},
|
|
1007
1238
|
...Object.keys(collection.storageTypes).length > 0 ? { storageTypes: collection.storageTypes } : {},
|
|
1008
1239
|
...definition.namespaces ? { namespaces: definition.namespaces } : {},
|
|
1009
1240
|
...definition.createNamespace ? { createNamespace: definition.createNamespace } : {},
|
|
1241
|
+
...definition.enums && Object.keys(definition.enums).length > 0 ? { enums: definition.enums } : {},
|
|
1010
1242
|
models
|
|
1011
1243
|
};
|
|
1012
1244
|
}
|
|
@@ -1094,19 +1326,38 @@ function buildContractFromDsl(definition) {
|
|
|
1094
1326
|
validatePerModelNamespaces(definition.target, definition.namespaces, definition.models ?? {});
|
|
1095
1327
|
return blindCast(buildSqlContractFromDefinition(buildContractDefinition(definition), definition.codecLookup));
|
|
1096
1328
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
return buildContractFromDsl({
|
|
1329
|
+
/** Implementation. */
|
|
1330
|
+
function buildBoundContract(family, target, definition, factory) {
|
|
1331
|
+
const full = {
|
|
1101
1332
|
...definition,
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1333
|
+
family,
|
|
1334
|
+
target
|
|
1335
|
+
};
|
|
1336
|
+
if (factory !== void 0) {
|
|
1337
|
+
const built = factory(createComposedAuthoringHelpers({
|
|
1338
|
+
family,
|
|
1339
|
+
target,
|
|
1105
1340
|
extensionPacks: definition.extensionPacks
|
|
1106
|
-
}))
|
|
1107
|
-
|
|
1341
|
+
}));
|
|
1342
|
+
const mergedEnums = {
|
|
1343
|
+
...definition.enums ?? {},
|
|
1344
|
+
...built.enums
|
|
1345
|
+
};
|
|
1346
|
+
return buildContractFromDsl({
|
|
1347
|
+
...full,
|
|
1348
|
+
...ifDefined("types", built.types),
|
|
1349
|
+
...ifDefined("models", built.models),
|
|
1350
|
+
...ifDefined("enums", Object.keys(mergedEnums).length > 0 ? mergedEnums : void 0)
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
return buildContractFromDsl(full);
|
|
1354
|
+
}
|
|
1355
|
+
function defineContract(definition, factory) {
|
|
1356
|
+
if (!isContractInput(definition)) throw new TypeError("defineContract expects a contract definition object. Define your contract with defineContract({ family, target, models, ... }).");
|
|
1357
|
+
if (factory !== void 0) return buildBoundContract(definition.family, definition.target, definition, factory);
|
|
1358
|
+
return buildBoundContract(definition.family, definition.target, definition);
|
|
1108
1359
|
}
|
|
1109
1360
|
//#endregion
|
|
1110
|
-
export { buildSqlContractFromDefinition, defineContract, field, model, rel };
|
|
1361
|
+
export { bindEnumType, buildBoundContract, buildSqlContractFromDefinition, defineContract, enumType, extensionModel, field, member, model, rel };
|
|
1111
1362
|
|
|
1112
1363
|
//# sourceMappingURL=contract-builder.mjs.map
|