@prisma-next/sql-contract-ts 0.12.0-dev.6 → 0.12.0-dev.60
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-6Rvs461H.mjs} +152 -58
- package/dist/build-contract-6Rvs461H.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 +310 -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/src/build-contract.ts +216 -65
- package/src/config-types.ts +24 -6
- package/src/contract-builder.ts +137 -16
- package/src/contract-definition.ts +49 -3
- package/src/contract-dsl.ts +346 -18
- package/src/contract-lowering.ts +188 -15
- package/src/contract-types.ts +17 -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
package/package.json
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/sql-contract-ts",
|
|
3
|
-
"version": "0.12.0-dev.
|
|
3
|
+
"version": "0.12.0-dev.60",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"description": "SQL-specific TypeScript contract authoring surface for Prisma Next",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@prisma-next/config": "0.12.0-dev.
|
|
10
|
-
"@prisma-next/contract": "0.12.0-dev.
|
|
11
|
-
"@prisma-next/contract-authoring": "0.12.0-dev.
|
|
12
|
-
"@prisma-next/framework-components": "0.12.0-dev.
|
|
13
|
-
"@prisma-next/sql-contract": "0.12.0-dev.
|
|
14
|
-
"@prisma-next/utils": "0.12.0-dev.
|
|
9
|
+
"@prisma-next/config": "0.12.0-dev.60",
|
|
10
|
+
"@prisma-next/contract": "0.12.0-dev.60",
|
|
11
|
+
"@prisma-next/contract-authoring": "0.12.0-dev.60",
|
|
12
|
+
"@prisma-next/framework-components": "0.12.0-dev.60",
|
|
13
|
+
"@prisma-next/sql-contract": "0.12.0-dev.60",
|
|
14
|
+
"@prisma-next/utils": "0.12.0-dev.60",
|
|
15
15
|
"arktype": "^2.2.0",
|
|
16
16
|
"pathe": "^2.0.3",
|
|
17
17
|
"ts-toolbelt": "^9.6.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@prisma-next/test-utils": "0.12.0-dev.
|
|
21
|
-
"@prisma-next/tsconfig": "0.12.0-dev.
|
|
20
|
+
"@prisma-next/test-utils": "0.12.0-dev.60",
|
|
21
|
+
"@prisma-next/tsconfig": "0.12.0-dev.60",
|
|
22
22
|
"@types/pg": "8.20.0",
|
|
23
|
-
"pg": "8.
|
|
24
|
-
"@prisma-next/tsdown": "0.12.0-dev.
|
|
25
|
-
"tsdown": "0.22.
|
|
23
|
+
"pg": "8.21.0",
|
|
24
|
+
"@prisma-next/tsdown": "0.12.0-dev.60",
|
|
25
|
+
"tsdown": "0.22.1",
|
|
26
26
|
"typescript": "5.9.3",
|
|
27
|
-
"vitest": "4.1.
|
|
27
|
+
"vitest": "4.1.7"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"typescript": ">=5.9"
|
package/src/build-contract.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type ColumnDefault,
|
|
9
9
|
type ColumnDefaultLiteralInputValue,
|
|
10
10
|
type Contract,
|
|
11
|
+
type ContractEnum,
|
|
11
12
|
type ContractField,
|
|
12
13
|
type ContractModel,
|
|
13
14
|
type ContractRelation,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
type ExecutionMutationDefault,
|
|
19
20
|
type JsonValue,
|
|
20
21
|
type StorageHashBase,
|
|
22
|
+
type ValueSetRef,
|
|
21
23
|
} from '@prisma-next/contract/types';
|
|
22
24
|
import { type CapabilityMatrix, mergeCapabilityMatrices } from '@prisma-next/contract-authoring';
|
|
23
25
|
import type { CodecLookup } from '@prisma-next/framework-components/codec';
|
|
@@ -41,6 +43,7 @@ import {
|
|
|
41
43
|
StorageTable,
|
|
42
44
|
type StorageTableInput,
|
|
43
45
|
type StorageTypeInstance,
|
|
46
|
+
type StorageValueSetInput,
|
|
44
47
|
toStorageTypeInstance,
|
|
45
48
|
} from '@prisma-next/sql-contract/types';
|
|
46
49
|
import { validateStorageSemantics } from '@prisma-next/sql-contract/validators';
|
|
@@ -156,16 +159,17 @@ const JSONB_NATIVE_TYPE = 'jsonb';
|
|
|
156
159
|
function resolveModelNamespaceId(
|
|
157
160
|
model: ModelNode,
|
|
158
161
|
modelNameToNamespaceId: ReadonlyMap<string, string>,
|
|
159
|
-
|
|
162
|
+
defaultNamespaceId: string,
|
|
160
163
|
): string {
|
|
161
164
|
if (model.namespaceId !== undefined && model.namespaceId.length > 0) {
|
|
162
165
|
return model.namespaceId;
|
|
163
166
|
}
|
|
164
|
-
return modelNameToNamespaceId.get(model.modelName) ??
|
|
167
|
+
return modelNameToNamespaceId.get(model.modelName) ?? defaultNamespaceId;
|
|
165
168
|
}
|
|
166
169
|
|
|
167
170
|
function buildStorageColumn(
|
|
168
171
|
field: FieldNode | ValueObjectFieldNode,
|
|
172
|
+
storageValueSetRef: ValueSetRef | undefined,
|
|
169
173
|
codecLookup?: CodecLookup,
|
|
170
174
|
): StorageColumn {
|
|
171
175
|
if (isValueObjectField(field)) {
|
|
@@ -203,12 +207,14 @@ function buildStorageColumn(
|
|
|
203
207
|
...ifDefined('typeParams', field.descriptor.typeParams),
|
|
204
208
|
...ifDefined('default', encodedDefault),
|
|
205
209
|
...ifDefined('typeRef', field.descriptor.typeRef),
|
|
210
|
+
...ifDefined('valueSet', storageValueSetRef),
|
|
206
211
|
};
|
|
207
212
|
}
|
|
208
213
|
|
|
209
214
|
function buildDomainField(
|
|
210
215
|
field: FieldNode | ValueObjectFieldNode,
|
|
211
216
|
column: StorageColumn,
|
|
217
|
+
domainValueSetRef: ValueSetRef | undefined,
|
|
212
218
|
): ContractField {
|
|
213
219
|
if (isValueObjectField(field)) {
|
|
214
220
|
return {
|
|
@@ -226,12 +232,13 @@ function buildDomainField(
|
|
|
226
232
|
},
|
|
227
233
|
nullable: column.nullable,
|
|
228
234
|
...(field.many ? { many: true } : {}),
|
|
235
|
+
...ifDefined('valueSet', domainValueSetRef),
|
|
229
236
|
};
|
|
230
237
|
}
|
|
231
238
|
|
|
232
239
|
function collectStorageNamespaceCoordinateIds(definition: ContractDefinition): Set<string> {
|
|
233
240
|
const ids = new Set<string>();
|
|
234
|
-
ids.add(
|
|
241
|
+
ids.add(definition.target.defaultNamespaceId);
|
|
235
242
|
for (const id of definition.namespaces ?? []) {
|
|
236
243
|
if (id.length > 0) {
|
|
237
244
|
ids.add(id);
|
|
@@ -245,22 +252,38 @@ function collectStorageNamespaceCoordinateIds(definition: ContractDefinition): S
|
|
|
245
252
|
return ids;
|
|
246
253
|
}
|
|
247
254
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
255
|
+
function ensureUnboundNamespaceSlot(
|
|
256
|
+
namespaces: SqlStorageInput['namespaces'],
|
|
257
|
+
createNamespace: ContractDefinition['createNamespace'],
|
|
258
|
+
): SqlStorageInput['namespaces'] {
|
|
259
|
+
if (Object.hasOwn(namespaces, UNBOUND_NAMESPACE_ID)) {
|
|
260
|
+
return namespaces;
|
|
261
|
+
}
|
|
262
|
+
const unboundInput: SqlNamespaceTablesInput = {
|
|
263
|
+
id: UNBOUND_NAMESPACE_ID,
|
|
264
|
+
entries: { table: {} },
|
|
265
|
+
};
|
|
266
|
+
const unbound = createNamespace ? createNamespace(unboundInput) : buildSqlNamespace(unboundInput);
|
|
267
|
+
return blindCast<
|
|
268
|
+
SqlStorageInput['namespaces'],
|
|
269
|
+
'createNamespace may return a target namespace concretion; the unbound slot matches SqlNamespace at runtime'
|
|
270
|
+
>({
|
|
271
|
+
[UNBOUND_NAMESPACE_ID]: unbound,
|
|
272
|
+
...namespaces,
|
|
273
|
+
});
|
|
253
274
|
}
|
|
254
275
|
|
|
276
|
+
const POSTGRES_ENUM_NAMESPACE_ID = 'public';
|
|
277
|
+
|
|
255
278
|
function partitionStorageTypesForTarget(
|
|
256
279
|
targetId: string,
|
|
257
280
|
types: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
|
|
258
281
|
namespaceTypes?: Readonly<Record<string, Readonly<Record<string, PostgresEnumStorageEntry>>>>,
|
|
259
282
|
): {
|
|
260
|
-
readonly documentTypes: Record<string, StorageTypeInstance
|
|
283
|
+
readonly documentTypes: Record<string, StorageTypeInstance>;
|
|
261
284
|
readonly namespaceEnumTypesById: Record<string, Record<string, PostgresEnumStorageEntry>>;
|
|
262
285
|
} {
|
|
263
|
-
const documentTypes: Record<string, StorageTypeInstance
|
|
286
|
+
const documentTypes: Record<string, StorageTypeInstance> = {};
|
|
264
287
|
const namespaceEnumTypesById: Record<string, Record<string, PostgresEnumStorageEntry>> = {};
|
|
265
288
|
for (const [name, entry] of Object.entries(types)) {
|
|
266
289
|
if (isPostgresEnumStorageEntry(entry)) {
|
|
@@ -304,6 +327,7 @@ export function buildSqlContractFromDefinition(
|
|
|
304
327
|
codecLookup?: CodecLookup,
|
|
305
328
|
): Contract<SqlStorage> {
|
|
306
329
|
const target = definition.target.targetId;
|
|
330
|
+
const defaultNamespaceId = definition.target.defaultNamespaceId;
|
|
307
331
|
const targetFamily = 'sql';
|
|
308
332
|
const modelsByName = new Map(definition.models.map((m) => [m.modelName, m]));
|
|
309
333
|
|
|
@@ -319,9 +343,13 @@ export function buildSqlContractFromDefinition(
|
|
|
319
343
|
const namespaceId =
|
|
320
344
|
semanticModel.namespaceId !== undefined && semanticModel.namespaceId.length > 0
|
|
321
345
|
? semanticModel.namespaceId
|
|
322
|
-
:
|
|
346
|
+
: defaultNamespaceId;
|
|
323
347
|
modelNameToNamespaceId.set(semanticModel.modelName, namespaceId);
|
|
324
|
-
|
|
348
|
+
// STI variants share the base table; the base model already owns this
|
|
349
|
+
// table name and its root, so the variant contributes neither.
|
|
350
|
+
if (!semanticModel.sharesBaseTable) {
|
|
351
|
+
roots[tableName] = crossRef(semanticModel.modelName, namespaceId);
|
|
352
|
+
}
|
|
325
353
|
|
|
326
354
|
// --- Build storage table ---
|
|
327
355
|
|
|
@@ -348,11 +376,34 @@ export function buildSqlContractFromDefinition(
|
|
|
348
376
|
}
|
|
349
377
|
}
|
|
350
378
|
|
|
351
|
-
const
|
|
379
|
+
const enumHandle = !isValueObjectField(field) ? field.enumTypeHandle : undefined;
|
|
380
|
+
// Authored enums are always registered under the contract's defaultNamespaceId
|
|
381
|
+
// (see the enum registration loop below), so refs must point there regardless
|
|
382
|
+
// of which namespace the consuming model lives in.
|
|
383
|
+
const storageValueSetRef: ValueSetRef | undefined =
|
|
384
|
+
enumHandle !== undefined
|
|
385
|
+
? {
|
|
386
|
+
plane: 'storage',
|
|
387
|
+
entityKind: 'value-set',
|
|
388
|
+
namespaceId: defaultNamespaceId,
|
|
389
|
+
name: enumHandle.enumName,
|
|
390
|
+
}
|
|
391
|
+
: undefined;
|
|
392
|
+
const domainValueSetRef: ValueSetRef | undefined =
|
|
393
|
+
enumHandle !== undefined
|
|
394
|
+
? {
|
|
395
|
+
plane: 'domain',
|
|
396
|
+
entityKind: 'enum',
|
|
397
|
+
namespaceId: defaultNamespaceId,
|
|
398
|
+
name: enumHandle.enumName,
|
|
399
|
+
}
|
|
400
|
+
: undefined;
|
|
401
|
+
|
|
402
|
+
const column = buildStorageColumn(field, storageValueSetRef, codecLookup);
|
|
352
403
|
columns[field.columnName] = column;
|
|
353
404
|
fieldToColumn[field.fieldName] = field.columnName;
|
|
354
405
|
|
|
355
|
-
domainFields[field.fieldName] = buildDomainField(field, column);
|
|
406
|
+
domainFields[field.fieldName] = buildDomainField(field, column, domainValueSetRef);
|
|
356
407
|
|
|
357
408
|
if (isValueObjectField(field)) {
|
|
358
409
|
domainFieldRefs[field.fieldName] = {
|
|
@@ -374,6 +425,31 @@ export function buildSqlContractFromDefinition(
|
|
|
374
425
|
}
|
|
375
426
|
|
|
376
427
|
const foreignKeys = (semanticModel.foreignKeys ?? []).map((fk) => {
|
|
428
|
+
if (fk.references.spaceId !== undefined) {
|
|
429
|
+
// Cross-space FK: the target lives in a different contract space.
|
|
430
|
+
// Skip local model lookup and carry the spaceId coordinate through.
|
|
431
|
+
const targetNamespaceId = fk.references.namespaceId ?? defaultNamespaceId;
|
|
432
|
+
return {
|
|
433
|
+
source: { namespaceId: asNamespaceId(namespaceId), tableName, columns: fk.columns },
|
|
434
|
+
target: {
|
|
435
|
+
namespaceId: asNamespaceId(targetNamespaceId),
|
|
436
|
+
tableName: fk.references.table,
|
|
437
|
+
columns: fk.references.columns,
|
|
438
|
+
spaceId: fk.references.spaceId,
|
|
439
|
+
},
|
|
440
|
+
...applyFkDefaults(
|
|
441
|
+
{
|
|
442
|
+
...ifDefined('constraint', fk.constraint),
|
|
443
|
+
...ifDefined('index', fk.index),
|
|
444
|
+
},
|
|
445
|
+
definition.foreignKeyDefaults,
|
|
446
|
+
),
|
|
447
|
+
...ifDefined('name', fk.name),
|
|
448
|
+
...ifDefined('onDelete', fk.onDelete),
|
|
449
|
+
...ifDefined('onUpdate', fk.onUpdate),
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
377
453
|
const targetModel = assertKnownTargetModel(
|
|
378
454
|
modelsByName,
|
|
379
455
|
semanticModel.modelName,
|
|
@@ -390,7 +466,7 @@ export function buildSqlContractFromDefinition(
|
|
|
390
466
|
fk.references.namespaceId ??
|
|
391
467
|
(targetModel.namespaceId !== undefined && targetModel.namespaceId.length > 0
|
|
392
468
|
? targetModel.namespaceId
|
|
393
|
-
:
|
|
469
|
+
: defaultNamespaceId);
|
|
394
470
|
return {
|
|
395
471
|
source: { namespaceId: asNamespaceId(namespaceId), tableName, columns: fk.columns },
|
|
396
472
|
target: {
|
|
@@ -411,48 +487,54 @@ export function buildSqlContractFromDefinition(
|
|
|
411
487
|
};
|
|
412
488
|
});
|
|
413
489
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
);
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
490
|
+
// STI variants share the base table: their columns are already
|
|
491
|
+
// materialised onto the base `ModelNode`, so the variant builds a domain
|
|
492
|
+
// model (below) but no storage table of its own.
|
|
493
|
+
if (!semanticModel.sharesBaseTable) {
|
|
494
|
+
const existingNs = tableNameToNamespaceId.get(tableName);
|
|
495
|
+
if (existingNs !== undefined && existingNs !== namespaceId) {
|
|
496
|
+
throw new Error(
|
|
497
|
+
`buildSqlContractFromDefinition: table "${tableName}" is mapped in namespace "${namespaceId}" but already exists in namespace "${existingNs}".`,
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
tableNameToNamespaceId.set(tableName, namespaceId);
|
|
501
|
+
|
|
502
|
+
const tableInput: StorageTableInput = {
|
|
503
|
+
columns,
|
|
504
|
+
...ifDefined('control', semanticModel.control),
|
|
505
|
+
uniques: (semanticModel.uniques ?? []).map((u) => ({
|
|
506
|
+
columns: u.columns,
|
|
507
|
+
...ifDefined('name', u.name),
|
|
508
|
+
})),
|
|
509
|
+
indexes: (semanticModel.indexes ?? []).map((i) => ({
|
|
510
|
+
columns: i.columns,
|
|
511
|
+
...ifDefined('name', i.name),
|
|
512
|
+
...ifDefined('type', i.type),
|
|
513
|
+
...ifDefined('options', i.options),
|
|
514
|
+
})),
|
|
515
|
+
foreignKeys,
|
|
516
|
+
...(semanticModel.id
|
|
517
|
+
? {
|
|
518
|
+
primaryKey: {
|
|
519
|
+
columns: semanticModel.id.columns,
|
|
520
|
+
...ifDefined('name', semanticModel.id.name),
|
|
521
|
+
},
|
|
522
|
+
}
|
|
523
|
+
: {}),
|
|
524
|
+
};
|
|
444
525
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
526
|
+
let nsTables = tablesByNamespace[namespaceId];
|
|
527
|
+
if (nsTables === undefined) {
|
|
528
|
+
nsTables = {};
|
|
529
|
+
tablesByNamespace[namespaceId] = nsTables;
|
|
530
|
+
}
|
|
531
|
+
if (nsTables[tableName] !== undefined) {
|
|
532
|
+
throw new Error(
|
|
533
|
+
`buildSqlContractFromDefinition: duplicate table "${tableName}" in namespace "${namespaceId}".`,
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
nsTables[tableName] = new StorageTable(tableInput);
|
|
454
537
|
}
|
|
455
|
-
nsTables[tableName] = new StorageTable(tableInput);
|
|
456
538
|
|
|
457
539
|
// --- Build contract model ---
|
|
458
540
|
|
|
@@ -466,6 +548,24 @@ export function buildSqlContractFromDefinition(
|
|
|
466
548
|
);
|
|
467
549
|
const modelRelations: Record<string, ContractRelation> = {};
|
|
468
550
|
for (const relation of semanticModel.relations ?? []) {
|
|
551
|
+
// Cross-space relations have `spaceId` set — the target model lives in
|
|
552
|
+
// a different contract space, so skip local model lookup and validation.
|
|
553
|
+
if (relation.spaceId !== undefined) {
|
|
554
|
+
const targetNamespaceId = relation.namespaceId ?? defaultNamespaceId;
|
|
555
|
+
modelRelations[relation.fieldName] = {
|
|
556
|
+
to: crossRef(relation.toModel, targetNamespaceId, relation.spaceId),
|
|
557
|
+
// Cross-space belongsTo relations are always N:1 (the FK-owning side).
|
|
558
|
+
cardinality: 'N:1',
|
|
559
|
+
on: {
|
|
560
|
+
localFields: relation.on.parentColumns.map((col) => columnToField.get(col) ?? col),
|
|
561
|
+
// For cross-space targets the lowering carries field names directly
|
|
562
|
+
// (no fieldToColumn map available for the remote model).
|
|
563
|
+
targetFields: relation.on.childColumns,
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
|
|
469
569
|
const targetModel = assertKnownTargetModel(
|
|
470
570
|
modelsByName,
|
|
471
571
|
semanticModel.modelName,
|
|
@@ -487,7 +587,7 @@ export function buildSqlContractFromDefinition(
|
|
|
487
587
|
modelRelations[relation.fieldName] = {
|
|
488
588
|
to: crossRef(
|
|
489
589
|
relation.toModel,
|
|
490
|
-
resolveModelNamespaceId(targetModel, modelNameToNamespaceId,
|
|
590
|
+
resolveModelNamespaceId(targetModel, modelNameToNamespaceId, defaultNamespaceId),
|
|
491
591
|
),
|
|
492
592
|
// RelationDefinition.cardinality includes 'N:M' which isn't in
|
|
493
593
|
// ContractReferenceRelation yet — cast is needed until the contract
|
|
@@ -517,6 +617,7 @@ export function buildSqlContractFromDefinition(
|
|
|
517
617
|
namespaceModels[semanticModel.modelName] = {
|
|
518
618
|
storage: {
|
|
519
619
|
table: tableName,
|
|
620
|
+
namespaceId,
|
|
520
621
|
fields: storageFields,
|
|
521
622
|
},
|
|
522
623
|
fields: domainFields,
|
|
@@ -557,6 +658,39 @@ export function buildSqlContractFromDefinition(
|
|
|
557
658
|
for (const id of Object.keys(namespaceEnumTypesById)) {
|
|
558
659
|
namespaceCoordinateIds.add(id);
|
|
559
660
|
}
|
|
661
|
+
|
|
662
|
+
// Build per-namespace registries for `enumType()` handles.
|
|
663
|
+
// All authored enums target the contract's default namespace.
|
|
664
|
+
const domainEnumsByNs: Record<string, Record<string, ContractEnum>> = {};
|
|
665
|
+
const storageValueSetsByNs: Record<string, Record<string, StorageValueSetInput>> = {};
|
|
666
|
+
for (const [enumName, handle] of Object.entries(definition.enums ?? {})) {
|
|
667
|
+
if (enumName !== handle.enumName) {
|
|
668
|
+
throw new Error(
|
|
669
|
+
`enum declaration key "${enumName}" must match enumType name "${handle.enumName}". Aliases are not supported.`,
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
const nsId = defaultNamespaceId;
|
|
673
|
+
let domainSlot = domainEnumsByNs[nsId];
|
|
674
|
+
if (domainSlot === undefined) {
|
|
675
|
+
domainSlot = {};
|
|
676
|
+
domainEnumsByNs[nsId] = domainSlot;
|
|
677
|
+
}
|
|
678
|
+
domainSlot[enumName] = {
|
|
679
|
+
codecId: handle.codecId,
|
|
680
|
+
members: handle.enumMembers,
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
let storageSlot = storageValueSetsByNs[nsId];
|
|
684
|
+
if (storageSlot === undefined) {
|
|
685
|
+
storageSlot = {};
|
|
686
|
+
storageValueSetsByNs[nsId] = storageSlot;
|
|
687
|
+
}
|
|
688
|
+
storageSlot[enumName] = {
|
|
689
|
+
kind: 'value-set',
|
|
690
|
+
values: handle.values,
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
|
|
560
694
|
const { createNamespace } = definition;
|
|
561
695
|
const namespaces = blindCast<
|
|
562
696
|
SqlStorageInput['namespaces'],
|
|
@@ -565,18 +699,26 @@ export function buildSqlContractFromDefinition(
|
|
|
565
699
|
Object.fromEntries(
|
|
566
700
|
[...namespaceCoordinateIds].sort().map((id) => {
|
|
567
701
|
const enumTypes = namespaceEnumTypesById[id];
|
|
702
|
+
const valueSetEntries = storageValueSetsByNs[id];
|
|
568
703
|
const nsInput: SqlNamespaceTablesInput = {
|
|
569
704
|
id,
|
|
570
|
-
|
|
571
|
-
|
|
705
|
+
entries: {
|
|
706
|
+
table: tablesByNamespace[id] ?? {},
|
|
707
|
+
...(valueSetEntries !== undefined && Object.keys(valueSetEntries).length > 0
|
|
708
|
+
? { valueSet: valueSetEntries }
|
|
709
|
+
: {}),
|
|
710
|
+
},
|
|
572
711
|
};
|
|
573
|
-
return [
|
|
712
|
+
return [
|
|
713
|
+
id,
|
|
714
|
+
createNamespace ? createNamespace(nsInput, enumTypes) : buildSqlNamespace(nsInput),
|
|
715
|
+
];
|
|
574
716
|
}),
|
|
575
717
|
),
|
|
576
718
|
);
|
|
577
719
|
const storageWithoutHash = {
|
|
578
720
|
...(Object.keys(documentTypes).length > 0 ? { types: documentTypes } : {}),
|
|
579
|
-
namespaces,
|
|
721
|
+
namespaces: ensureUnboundNamespaceSlot(namespaces, createNamespace),
|
|
580
722
|
};
|
|
581
723
|
const storageHash: StorageHashBase<string> = definition.storageHash
|
|
582
724
|
? coreHash(definition.storageHash)
|
|
@@ -672,7 +814,6 @@ export function buildSqlContractFromDefinition(
|
|
|
672
814
|
)
|
|
673
815
|
: undefined;
|
|
674
816
|
|
|
675
|
-
const defaultNamespaceId = defaultModelNamespaceId(target);
|
|
676
817
|
const domainNamespaceIds = new Set(Object.keys(modelsByNamespace));
|
|
677
818
|
if (domainNamespaceIds.size === 0) {
|
|
678
819
|
domainNamespaceIds.add(defaultNamespaceId);
|
|
@@ -680,13 +821,22 @@ export function buildSqlContractFromDefinition(
|
|
|
680
821
|
if (valueObjects !== undefined) {
|
|
681
822
|
domainNamespaceIds.add(defaultNamespaceId);
|
|
682
823
|
}
|
|
824
|
+
for (const nsId of Object.keys(domainEnumsByNs)) {
|
|
825
|
+
domainNamespaceIds.add(nsId);
|
|
826
|
+
}
|
|
683
827
|
const domainNamespaces = Object.fromEntries(
|
|
684
828
|
[...domainNamespaceIds].sort().map((namespaceId) => {
|
|
685
829
|
const modelsInNs = modelsByNamespace[namespaceId] ?? {};
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
830
|
+
const enumsInNs = domainEnumsByNs[namespaceId];
|
|
831
|
+
const namespaceSlice = {
|
|
832
|
+
models: modelsInNs,
|
|
833
|
+
...(namespaceId === defaultNamespaceId && valueObjects !== undefined
|
|
834
|
+
? { valueObjects }
|
|
835
|
+
: {}),
|
|
836
|
+
...(enumsInNs !== undefined && Object.keys(enumsInNs).length > 0
|
|
837
|
+
? { enum: enumsInNs }
|
|
838
|
+
: {}),
|
|
839
|
+
};
|
|
690
840
|
return [namespaceId, namespaceSlice];
|
|
691
841
|
}),
|
|
692
842
|
);
|
|
@@ -694,6 +844,7 @@ export function buildSqlContractFromDefinition(
|
|
|
694
844
|
const contract: Contract<SqlStorage> = {
|
|
695
845
|
target,
|
|
696
846
|
targetFamily,
|
|
847
|
+
...ifDefined('defaultControlPolicy', definition.defaultControlPolicy),
|
|
697
848
|
domain: { namespaces: domainNamespaces },
|
|
698
849
|
roots,
|
|
699
850
|
storage,
|
package/src/config-types.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { pathToFileURL } from 'node:url';
|
|
2
2
|
import type { ContractConfig } from '@prisma-next/config/config-types';
|
|
3
|
-
import
|
|
3
|
+
import { applySpecifierDefaultControlPolicy } from '@prisma-next/contract/apply-specifier-default-control-policy';
|
|
4
|
+
import type { Contract, ControlPolicy } from '@prisma-next/contract/types';
|
|
4
5
|
import type { TargetPackRef } from '@prisma-next/framework-components/components';
|
|
5
6
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
6
7
|
import { ok } from '@prisma-next/utils/result';
|
|
@@ -19,22 +20,35 @@ function defaultOutputFromContractPath(contractPath: string): string {
|
|
|
19
20
|
return `${contractPath.slice(0, -ext.length)}.json`;
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
export interface TypeScriptContractSpecifierOptions {
|
|
24
|
+
readonly defaultControlPolicy?: ControlPolicy;
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
export function emptyContract(options: {
|
|
23
28
|
readonly output?: string;
|
|
24
29
|
readonly target: TargetPackRef<'sql', string>;
|
|
30
|
+
readonly defaultControlPolicy?: ControlPolicy;
|
|
25
31
|
}): ContractConfig {
|
|
26
32
|
return {
|
|
27
33
|
source: {
|
|
28
|
-
load: async () =>
|
|
34
|
+
load: async () => {
|
|
35
|
+
const built = buildSqlContractFromDefinition({ target: options.target, models: [] });
|
|
36
|
+
return ok(applySpecifierDefaultControlPolicy(built, options.defaultControlPolicy));
|
|
37
|
+
},
|
|
29
38
|
},
|
|
30
39
|
...ifDefined('output', options.output),
|
|
31
40
|
};
|
|
32
41
|
}
|
|
33
42
|
|
|
34
|
-
export function typescriptContract(
|
|
43
|
+
export function typescriptContract(
|
|
44
|
+
contract: Contract,
|
|
45
|
+
output?: string,
|
|
46
|
+
options?: TypeScriptContractSpecifierOptions,
|
|
47
|
+
): ContractConfig {
|
|
35
48
|
return {
|
|
36
49
|
source: {
|
|
37
|
-
load: async () =>
|
|
50
|
+
load: async () =>
|
|
51
|
+
ok(applySpecifierDefaultControlPolicy(contract, options?.defaultControlPolicy)),
|
|
38
52
|
},
|
|
39
53
|
// The in-memory variant has no input path to anchor on; fall through to
|
|
40
54
|
// the global default in `normalizeContractConfig` when caller doesn't pin it.
|
|
@@ -42,7 +56,11 @@ export function typescriptContract(contract: Contract, output?: string): Contrac
|
|
|
42
56
|
};
|
|
43
57
|
}
|
|
44
58
|
|
|
45
|
-
export function typescriptContractFromPath(
|
|
59
|
+
export function typescriptContractFromPath(
|
|
60
|
+
contractPath: string,
|
|
61
|
+
output?: string,
|
|
62
|
+
options?: TypeScriptContractSpecifierOptions,
|
|
63
|
+
): ContractConfig {
|
|
46
64
|
return {
|
|
47
65
|
source: {
|
|
48
66
|
inputs: [contractPath],
|
|
@@ -60,7 +78,7 @@ export function typescriptContractFromPath(contractPath: string, output?: string
|
|
|
60
78
|
`typescriptContractFromPath: module at "${absolutePath}" has no "default" or "contract" export.`,
|
|
61
79
|
);
|
|
62
80
|
}
|
|
63
|
-
return ok(contract);
|
|
81
|
+
return ok(applySpecifierDefaultControlPolicy(contract, options?.defaultControlPolicy));
|
|
64
82
|
},
|
|
65
83
|
},
|
|
66
84
|
output: output ?? defaultOutputFromContractPath(contractPath),
|