@prisma-next/sql-contract-ts 0.12.0-dev.5 → 0.12.0-dev.50
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-B1odoaHR.mjs} +65 -52
- package/dist/build-contract-B1odoaHR.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 +64 -13
- package/dist/contract-builder.d.mts.map +1 -1
- package/dist/contract-builder.mjs +29 -13
- package/dist/contract-builder.mjs.map +1 -1
- package/package.json +10 -10
- package/src/build-contract.ts +91 -59
- package/src/config-types.ts +24 -6
- package/src/contract-builder.ts +131 -15
- package/src/contract-definition.ts +23 -3
- package/src/contract-dsl.ts +3 -0
- package/src/contract-lowering.ts +2 -0
- package/src/contract-types.ts +17 -21
- package/src/exports/contract-builder.ts +1 -0
- package/dist/build-contract-BCYW3_wE.mjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/sql-contract-ts",
|
|
3
|
-
"version": "0.12.0-dev.
|
|
3
|
+
"version": "0.12.0-dev.50",
|
|
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.50",
|
|
10
|
+
"@prisma-next/contract": "0.12.0-dev.50",
|
|
11
|
+
"@prisma-next/contract-authoring": "0.12.0-dev.50",
|
|
12
|
+
"@prisma-next/framework-components": "0.12.0-dev.50",
|
|
13
|
+
"@prisma-next/sql-contract": "0.12.0-dev.50",
|
|
14
|
+
"@prisma-next/utils": "0.12.0-dev.50",
|
|
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.50",
|
|
21
|
+
"@prisma-next/tsconfig": "0.12.0-dev.50",
|
|
22
22
|
"@types/pg": "8.20.0",
|
|
23
23
|
"pg": "8.20.0",
|
|
24
|
-
"@prisma-next/tsdown": "0.12.0-dev.
|
|
24
|
+
"@prisma-next/tsdown": "0.12.0-dev.50",
|
|
25
25
|
"tsdown": "0.22.0",
|
|
26
26
|
"typescript": "5.9.3",
|
|
27
27
|
"vitest": "4.1.6"
|
package/src/build-contract.ts
CHANGED
|
@@ -156,12 +156,12 @@ const JSONB_NATIVE_TYPE = 'jsonb';
|
|
|
156
156
|
function resolveModelNamespaceId(
|
|
157
157
|
model: ModelNode,
|
|
158
158
|
modelNameToNamespaceId: ReadonlyMap<string, string>,
|
|
159
|
-
|
|
159
|
+
defaultNamespaceId: string,
|
|
160
160
|
): string {
|
|
161
161
|
if (model.namespaceId !== undefined && model.namespaceId.length > 0) {
|
|
162
162
|
return model.namespaceId;
|
|
163
163
|
}
|
|
164
|
-
return modelNameToNamespaceId.get(model.modelName) ??
|
|
164
|
+
return modelNameToNamespaceId.get(model.modelName) ?? defaultNamespaceId;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
function buildStorageColumn(
|
|
@@ -231,7 +231,7 @@ function buildDomainField(
|
|
|
231
231
|
|
|
232
232
|
function collectStorageNamespaceCoordinateIds(definition: ContractDefinition): Set<string> {
|
|
233
233
|
const ids = new Set<string>();
|
|
234
|
-
ids.add(
|
|
234
|
+
ids.add(definition.target.defaultNamespaceId);
|
|
235
235
|
for (const id of definition.namespaces ?? []) {
|
|
236
236
|
if (id.length > 0) {
|
|
237
237
|
ids.add(id);
|
|
@@ -245,22 +245,38 @@ function collectStorageNamespaceCoordinateIds(definition: ContractDefinition): S
|
|
|
245
245
|
return ids;
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
248
|
+
function ensureUnboundNamespaceSlot(
|
|
249
|
+
namespaces: SqlStorageInput['namespaces'],
|
|
250
|
+
createNamespace: ContractDefinition['createNamespace'],
|
|
251
|
+
): SqlStorageInput['namespaces'] {
|
|
252
|
+
if (Object.hasOwn(namespaces, UNBOUND_NAMESPACE_ID)) {
|
|
253
|
+
return namespaces;
|
|
254
|
+
}
|
|
255
|
+
const unboundInput: SqlNamespaceTablesInput = {
|
|
256
|
+
id: UNBOUND_NAMESPACE_ID,
|
|
257
|
+
entries: { table: {} },
|
|
258
|
+
};
|
|
259
|
+
const unbound = createNamespace ? createNamespace(unboundInput) : buildSqlNamespace(unboundInput);
|
|
260
|
+
return blindCast<
|
|
261
|
+
SqlStorageInput['namespaces'],
|
|
262
|
+
'createNamespace may return a target namespace concretion; the unbound slot matches SqlNamespace at runtime'
|
|
263
|
+
>({
|
|
264
|
+
[UNBOUND_NAMESPACE_ID]: unbound,
|
|
265
|
+
...namespaces,
|
|
266
|
+
});
|
|
253
267
|
}
|
|
254
268
|
|
|
269
|
+
const POSTGRES_ENUM_NAMESPACE_ID = 'public';
|
|
270
|
+
|
|
255
271
|
function partitionStorageTypesForTarget(
|
|
256
272
|
targetId: string,
|
|
257
273
|
types: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
|
|
258
274
|
namespaceTypes?: Readonly<Record<string, Readonly<Record<string, PostgresEnumStorageEntry>>>>,
|
|
259
275
|
): {
|
|
260
|
-
readonly documentTypes: Record<string, StorageTypeInstance
|
|
276
|
+
readonly documentTypes: Record<string, StorageTypeInstance>;
|
|
261
277
|
readonly namespaceEnumTypesById: Record<string, Record<string, PostgresEnumStorageEntry>>;
|
|
262
278
|
} {
|
|
263
|
-
const documentTypes: Record<string, StorageTypeInstance
|
|
279
|
+
const documentTypes: Record<string, StorageTypeInstance> = {};
|
|
264
280
|
const namespaceEnumTypesById: Record<string, Record<string, PostgresEnumStorageEntry>> = {};
|
|
265
281
|
for (const [name, entry] of Object.entries(types)) {
|
|
266
282
|
if (isPostgresEnumStorageEntry(entry)) {
|
|
@@ -304,6 +320,7 @@ export function buildSqlContractFromDefinition(
|
|
|
304
320
|
codecLookup?: CodecLookup,
|
|
305
321
|
): Contract<SqlStorage> {
|
|
306
322
|
const target = definition.target.targetId;
|
|
323
|
+
const defaultNamespaceId = definition.target.defaultNamespaceId;
|
|
307
324
|
const targetFamily = 'sql';
|
|
308
325
|
const modelsByName = new Map(definition.models.map((m) => [m.modelName, m]));
|
|
309
326
|
|
|
@@ -319,9 +336,13 @@ export function buildSqlContractFromDefinition(
|
|
|
319
336
|
const namespaceId =
|
|
320
337
|
semanticModel.namespaceId !== undefined && semanticModel.namespaceId.length > 0
|
|
321
338
|
? semanticModel.namespaceId
|
|
322
|
-
:
|
|
339
|
+
: defaultNamespaceId;
|
|
323
340
|
modelNameToNamespaceId.set(semanticModel.modelName, namespaceId);
|
|
324
|
-
|
|
341
|
+
// STI variants share the base table; the base model already owns this
|
|
342
|
+
// table name and its root, so the variant contributes neither.
|
|
343
|
+
if (!semanticModel.sharesBaseTable) {
|
|
344
|
+
roots[tableName] = crossRef(semanticModel.modelName, namespaceId);
|
|
345
|
+
}
|
|
325
346
|
|
|
326
347
|
// --- Build storage table ---
|
|
327
348
|
|
|
@@ -390,7 +411,7 @@ export function buildSqlContractFromDefinition(
|
|
|
390
411
|
fk.references.namespaceId ??
|
|
391
412
|
(targetModel.namespaceId !== undefined && targetModel.namespaceId.length > 0
|
|
392
413
|
? targetModel.namespaceId
|
|
393
|
-
:
|
|
414
|
+
: defaultNamespaceId);
|
|
394
415
|
return {
|
|
395
416
|
source: { namespaceId: asNamespaceId(namespaceId), tableName, columns: fk.columns },
|
|
396
417
|
target: {
|
|
@@ -411,48 +432,54 @@ export function buildSqlContractFromDefinition(
|
|
|
411
432
|
};
|
|
412
433
|
});
|
|
413
434
|
|
|
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
|
-
|
|
435
|
+
// STI variants share the base table: their columns are already
|
|
436
|
+
// materialised onto the base `ModelNode`, so the variant builds a domain
|
|
437
|
+
// model (below) but no storage table of its own.
|
|
438
|
+
if (!semanticModel.sharesBaseTable) {
|
|
439
|
+
const existingNs = tableNameToNamespaceId.get(tableName);
|
|
440
|
+
if (existingNs !== undefined && existingNs !== namespaceId) {
|
|
441
|
+
throw new Error(
|
|
442
|
+
`buildSqlContractFromDefinition: table "${tableName}" is mapped in namespace "${namespaceId}" but already exists in namespace "${existingNs}".`,
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
tableNameToNamespaceId.set(tableName, namespaceId);
|
|
446
|
+
|
|
447
|
+
const tableInput: StorageTableInput = {
|
|
448
|
+
columns,
|
|
449
|
+
...ifDefined('control', semanticModel.control),
|
|
450
|
+
uniques: (semanticModel.uniques ?? []).map((u) => ({
|
|
451
|
+
columns: u.columns,
|
|
452
|
+
...ifDefined('name', u.name),
|
|
453
|
+
})),
|
|
454
|
+
indexes: (semanticModel.indexes ?? []).map((i) => ({
|
|
455
|
+
columns: i.columns,
|
|
456
|
+
...ifDefined('name', i.name),
|
|
457
|
+
...ifDefined('type', i.type),
|
|
458
|
+
...ifDefined('options', i.options),
|
|
459
|
+
})),
|
|
460
|
+
foreignKeys,
|
|
461
|
+
...(semanticModel.id
|
|
462
|
+
? {
|
|
463
|
+
primaryKey: {
|
|
464
|
+
columns: semanticModel.id.columns,
|
|
465
|
+
...ifDefined('name', semanticModel.id.name),
|
|
466
|
+
},
|
|
467
|
+
}
|
|
468
|
+
: {}),
|
|
469
|
+
};
|
|
444
470
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
471
|
+
let nsTables = tablesByNamespace[namespaceId];
|
|
472
|
+
if (nsTables === undefined) {
|
|
473
|
+
nsTables = {};
|
|
474
|
+
tablesByNamespace[namespaceId] = nsTables;
|
|
475
|
+
}
|
|
476
|
+
if (nsTables[tableName] !== undefined) {
|
|
477
|
+
throw new Error(
|
|
478
|
+
`buildSqlContractFromDefinition: duplicate table "${tableName}" in namespace "${namespaceId}".`,
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
nsTables[tableName] = new StorageTable(tableInput);
|
|
454
482
|
}
|
|
455
|
-
nsTables[tableName] = new StorageTable(tableInput);
|
|
456
483
|
|
|
457
484
|
// --- Build contract model ---
|
|
458
485
|
|
|
@@ -487,7 +514,7 @@ export function buildSqlContractFromDefinition(
|
|
|
487
514
|
modelRelations[relation.fieldName] = {
|
|
488
515
|
to: crossRef(
|
|
489
516
|
relation.toModel,
|
|
490
|
-
resolveModelNamespaceId(targetModel, modelNameToNamespaceId,
|
|
517
|
+
resolveModelNamespaceId(targetModel, modelNameToNamespaceId, defaultNamespaceId),
|
|
491
518
|
),
|
|
492
519
|
// RelationDefinition.cardinality includes 'N:M' which isn't in
|
|
493
520
|
// ContractReferenceRelation yet — cast is needed until the contract
|
|
@@ -517,6 +544,7 @@ export function buildSqlContractFromDefinition(
|
|
|
517
544
|
namespaceModels[semanticModel.modelName] = {
|
|
518
545
|
storage: {
|
|
519
546
|
table: tableName,
|
|
547
|
+
namespaceId,
|
|
520
548
|
fields: storageFields,
|
|
521
549
|
},
|
|
522
550
|
fields: domainFields,
|
|
@@ -567,16 +595,20 @@ export function buildSqlContractFromDefinition(
|
|
|
567
595
|
const enumTypes = namespaceEnumTypesById[id];
|
|
568
596
|
const nsInput: SqlNamespaceTablesInput = {
|
|
569
597
|
id,
|
|
570
|
-
|
|
571
|
-
|
|
598
|
+
entries: {
|
|
599
|
+
table: tablesByNamespace[id] ?? {},
|
|
600
|
+
},
|
|
572
601
|
};
|
|
573
|
-
return [
|
|
602
|
+
return [
|
|
603
|
+
id,
|
|
604
|
+
createNamespace ? createNamespace(nsInput, enumTypes) : buildSqlNamespace(nsInput),
|
|
605
|
+
];
|
|
574
606
|
}),
|
|
575
607
|
),
|
|
576
608
|
);
|
|
577
609
|
const storageWithoutHash = {
|
|
578
610
|
...(Object.keys(documentTypes).length > 0 ? { types: documentTypes } : {}),
|
|
579
|
-
namespaces,
|
|
611
|
+
namespaces: ensureUnboundNamespaceSlot(namespaces, createNamespace),
|
|
580
612
|
};
|
|
581
613
|
const storageHash: StorageHashBase<string> = definition.storageHash
|
|
582
614
|
? coreHash(definition.storageHash)
|
|
@@ -672,7 +704,6 @@ export function buildSqlContractFromDefinition(
|
|
|
672
704
|
)
|
|
673
705
|
: undefined;
|
|
674
706
|
|
|
675
|
-
const defaultNamespaceId = defaultModelNamespaceId(target);
|
|
676
707
|
const domainNamespaceIds = new Set(Object.keys(modelsByNamespace));
|
|
677
708
|
if (domainNamespaceIds.size === 0) {
|
|
678
709
|
domainNamespaceIds.add(defaultNamespaceId);
|
|
@@ -694,6 +725,7 @@ export function buildSqlContractFromDefinition(
|
|
|
694
725
|
const contract: Contract<SqlStorage> = {
|
|
695
726
|
target,
|
|
696
727
|
targetFamily,
|
|
728
|
+
...ifDefined('defaultControlPolicy', definition.defaultControlPolicy),
|
|
697
729
|
domain: { namespaces: domainNamespaces },
|
|
698
730
|
roots,
|
|
699
731
|
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),
|
package/src/contract-builder.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ControlPolicy } from '@prisma-next/contract/types';
|
|
1
2
|
import type { ForeignKeyDefaultsState } from '@prisma-next/contract-authoring';
|
|
2
3
|
import type { CodecLookup } from '@prisma-next/framework-components/codec';
|
|
3
4
|
import type {
|
|
@@ -12,6 +13,7 @@ import type {
|
|
|
12
13
|
StorageTypeInstance,
|
|
13
14
|
} from '@prisma-next/sql-contract/types';
|
|
14
15
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
16
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
15
17
|
import { buildSqlContractFromDefinition } from './build-contract';
|
|
16
18
|
import {
|
|
17
19
|
type ComposedAuthoringHelpers,
|
|
@@ -65,6 +67,7 @@ type ContractDefinition<
|
|
|
65
67
|
readonly naming?: Naming;
|
|
66
68
|
readonly storageHash?: StorageHash;
|
|
67
69
|
readonly foreignKeyDefaults?: ForeignKeyDefaults;
|
|
70
|
+
readonly defaultControlPolicy?: ControlPolicy;
|
|
68
71
|
readonly namespaces?: Namespaces;
|
|
69
72
|
readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
|
|
70
73
|
readonly types?: Types;
|
|
@@ -87,6 +90,7 @@ type ContractScaffold<
|
|
|
87
90
|
readonly naming?: Naming;
|
|
88
91
|
readonly storageHash?: StorageHash;
|
|
89
92
|
readonly foreignKeyDefaults?: ForeignKeyDefaults;
|
|
93
|
+
readonly defaultControlPolicy?: ControlPolicy;
|
|
90
94
|
readonly namespaces?: Namespaces;
|
|
91
95
|
readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
|
|
92
96
|
readonly types?: never;
|
|
@@ -289,6 +293,130 @@ function buildContractFromDsl<Definition extends ContractInput>(
|
|
|
289
293
|
>(buildSqlContractFromDefinition(buildContractDefinition(definition), definition.codecLookup));
|
|
290
294
|
}
|
|
291
295
|
|
|
296
|
+
// Input for buildBoundContract — all fields from ContractInput except family/target
|
|
297
|
+
// (those are injected by the builder, pre-bound at the call site).
|
|
298
|
+
type BoundDefinitionInput<
|
|
299
|
+
Types extends Record<string, StorageTypeInstance | PostgresEnumStorageEntry> = Record<
|
|
300
|
+
never,
|
|
301
|
+
never
|
|
302
|
+
>,
|
|
303
|
+
Models extends Record<string, ModelLike> = Record<never, never>,
|
|
304
|
+
ExtensionPacks extends Record<string, ExtensionPackRef<'sql', string>> | undefined = undefined,
|
|
305
|
+
Naming extends ContractInput['naming'] | undefined = undefined,
|
|
306
|
+
StorageHash extends string | undefined = undefined,
|
|
307
|
+
ForeignKeyDefaults extends ForeignKeyDefaultsState | undefined = undefined,
|
|
308
|
+
Namespaces extends readonly string[] | undefined = undefined,
|
|
309
|
+
> = {
|
|
310
|
+
readonly extensionPacks?: ExtensionPacks;
|
|
311
|
+
readonly naming?: Naming;
|
|
312
|
+
readonly storageHash?: StorageHash;
|
|
313
|
+
readonly foreignKeyDefaults?: ForeignKeyDefaults;
|
|
314
|
+
readonly defaultControlPolicy?: ControlPolicy;
|
|
315
|
+
readonly namespaces?: Namespaces;
|
|
316
|
+
readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
|
|
317
|
+
readonly types?: Types;
|
|
318
|
+
readonly models?: Models;
|
|
319
|
+
readonly codecLookup?: CodecLookup;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Merges a bound input with the pre-bound family/target to produce a full ContractDefinition.
|
|
323
|
+
type WithFamilyTarget<
|
|
324
|
+
Input,
|
|
325
|
+
F extends FamilyPackRef<string>,
|
|
326
|
+
T extends TargetPackRef<'sql', string>,
|
|
327
|
+
> = Input & { readonly family: F; readonly target: T };
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Shared builder that assembles a SqlContract with pre-bound family and target.
|
|
331
|
+
* Extension wrappers keep their own public overloads and delegate their impl body here;
|
|
332
|
+
* this is a plain overloaded function (not a factory returning an overloaded function)
|
|
333
|
+
* so no overloaded-function-return cast is needed.
|
|
334
|
+
*
|
|
335
|
+
* Overload 1: definition form (no factory).
|
|
336
|
+
*/
|
|
337
|
+
export function buildBoundContract<
|
|
338
|
+
const F extends FamilyPackRef<string>,
|
|
339
|
+
const T extends TargetPackRef<'sql', string>,
|
|
340
|
+
const Definition extends BoundDefinitionInput<
|
|
341
|
+
Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
|
|
342
|
+
Record<string, ModelLike>,
|
|
343
|
+
Record<string, ExtensionPackRef<'sql', string>> | undefined,
|
|
344
|
+
ContractInput['naming'] | undefined,
|
|
345
|
+
string | undefined,
|
|
346
|
+
ForeignKeyDefaultsState | undefined,
|
|
347
|
+
readonly string[] | undefined
|
|
348
|
+
>,
|
|
349
|
+
>(
|
|
350
|
+
family: F,
|
|
351
|
+
target: T,
|
|
352
|
+
definition: Definition,
|
|
353
|
+
factory?: undefined,
|
|
354
|
+
): SqlContractResult<WithFamilyTarget<Definition, F, T>>;
|
|
355
|
+
/**
|
|
356
|
+
* Overload 2: factory form.
|
|
357
|
+
*/
|
|
358
|
+
export function buildBoundContract<
|
|
359
|
+
const F extends FamilyPackRef<string>,
|
|
360
|
+
const T extends TargetPackRef<'sql', string>,
|
|
361
|
+
const Definition extends BoundDefinitionInput<
|
|
362
|
+
Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
|
|
363
|
+
Record<string, ModelLike>,
|
|
364
|
+
Record<string, ExtensionPackRef<'sql', string>> | undefined,
|
|
365
|
+
ContractInput['naming'] | undefined,
|
|
366
|
+
string | undefined,
|
|
367
|
+
ForeignKeyDefaultsState | undefined,
|
|
368
|
+
readonly string[] | undefined
|
|
369
|
+
>,
|
|
370
|
+
const Built extends {
|
|
371
|
+
readonly types?: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>;
|
|
372
|
+
readonly models?: Record<string, ModelLike>;
|
|
373
|
+
},
|
|
374
|
+
>(
|
|
375
|
+
family: F,
|
|
376
|
+
target: T,
|
|
377
|
+
definition: Definition,
|
|
378
|
+
factory: (
|
|
379
|
+
helpers: ComposedAuthoringHelpers<F, T, NonNullable<Definition['extensionPacks']>>,
|
|
380
|
+
) => Built,
|
|
381
|
+
): SqlContractResult<WithFamilyTarget<Definition & Built, F, T>>;
|
|
382
|
+
/** Implementation. */
|
|
383
|
+
export function buildBoundContract(
|
|
384
|
+
family: FamilyPackRef<string>,
|
|
385
|
+
target: TargetPackRef<'sql', string>,
|
|
386
|
+
definition: Omit<ContractInput, 'family' | 'target'>,
|
|
387
|
+
factory?:
|
|
388
|
+
| ((
|
|
389
|
+
helpers: ComposedAuthoringHelpers<
|
|
390
|
+
FamilyPackRef<string>,
|
|
391
|
+
TargetPackRef<'sql', string>,
|
|
392
|
+
Record<string, ExtensionPackRef<'sql', string>> | undefined
|
|
393
|
+
>,
|
|
394
|
+
) => {
|
|
395
|
+
readonly types?: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>;
|
|
396
|
+
readonly models?: Record<string, ModelLike>;
|
|
397
|
+
})
|
|
398
|
+
| undefined,
|
|
399
|
+
) {
|
|
400
|
+
const full = { ...definition, family, target };
|
|
401
|
+
|
|
402
|
+
if (factory !== undefined) {
|
|
403
|
+
const built = factory(
|
|
404
|
+
createComposedAuthoringHelpers({
|
|
405
|
+
family,
|
|
406
|
+
target,
|
|
407
|
+
extensionPacks: definition.extensionPacks,
|
|
408
|
+
}),
|
|
409
|
+
);
|
|
410
|
+
return buildContractFromDsl({
|
|
411
|
+
...full,
|
|
412
|
+
...ifDefined('types', built.types),
|
|
413
|
+
...ifDefined('models', built.models),
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return buildContractFromDsl(full);
|
|
418
|
+
}
|
|
419
|
+
|
|
292
420
|
export function defineContract<
|
|
293
421
|
const Family extends FamilyPackRef<string>,
|
|
294
422
|
const Target extends TargetPackRef<'sql', string>,
|
|
@@ -384,22 +512,10 @@ export function defineContract(
|
|
|
384
512
|
);
|
|
385
513
|
}
|
|
386
514
|
|
|
387
|
-
if (
|
|
388
|
-
return
|
|
515
|
+
if (factory !== undefined) {
|
|
516
|
+
return buildBoundContract(definition.family, definition.target, definition, factory);
|
|
389
517
|
}
|
|
390
|
-
|
|
391
|
-
const builtDefinition = {
|
|
392
|
-
...definition,
|
|
393
|
-
...factory(
|
|
394
|
-
createComposedAuthoringHelpers({
|
|
395
|
-
family: definition.family,
|
|
396
|
-
target: definition.target,
|
|
397
|
-
extensionPacks: definition.extensionPacks,
|
|
398
|
-
}),
|
|
399
|
-
),
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
return buildContractFromDsl(builtDefinition);
|
|
518
|
+
return buildBoundContract(definition.family, definition.target, definition);
|
|
403
519
|
}
|
|
404
520
|
|
|
405
521
|
export type {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ColumnDefault,
|
|
3
|
+
ControlPolicy,
|
|
4
|
+
ExecutionMutationDefaultPhases,
|
|
5
|
+
} from '@prisma-next/contract/types';
|
|
2
6
|
import type { ForeignKeyDefaultsState } from '@prisma-next/contract-authoring';
|
|
3
7
|
import type { ColumnTypeDescriptor } from '@prisma-next/framework-components/codec';
|
|
4
8
|
import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';
|
|
@@ -113,10 +117,20 @@ export interface ModelNode {
|
|
|
113
117
|
readonly indexes?: readonly IndexNode[];
|
|
114
118
|
readonly foreignKeys?: readonly ForeignKeyNode[];
|
|
115
119
|
readonly relations?: readonly RelationNode[];
|
|
120
|
+
readonly control?: ControlPolicy;
|
|
121
|
+
/**
|
|
122
|
+
* Single-table-inheritance variants share their base model's storage table:
|
|
123
|
+
* the variant's columns are materialised onto the base `ModelNode`, and this
|
|
124
|
+
* model contributes a domain model but no storage table of its own. When set,
|
|
125
|
+
* the assembler builds the domain model but skips creating a (shadow) storage
|
|
126
|
+
* table and a root for this model — the base owns both.
|
|
127
|
+
*/
|
|
128
|
+
readonly sharesBaseTable?: boolean;
|
|
116
129
|
}
|
|
117
130
|
|
|
118
131
|
export interface ContractDefinition {
|
|
119
132
|
readonly target: TargetPackRef<'sql', string>;
|
|
133
|
+
readonly defaultControlPolicy?: ControlPolicy;
|
|
120
134
|
readonly extensionPacks?: Record<string, ExtensionPackRef<'sql', string>>;
|
|
121
135
|
readonly storageHash?: string;
|
|
122
136
|
readonly foreignKeyDefaults?: ForeignKeyDefaultsState;
|
|
@@ -124,7 +138,7 @@ export interface ContractDefinition {
|
|
|
124
138
|
/**
|
|
125
139
|
* Enum types declared inside a named `namespace { enum … }` block,
|
|
126
140
|
* keyed first by namespace id then by type name. These are routed to
|
|
127
|
-
* `storage.namespaces[nsId].
|
|
141
|
+
* `storage.namespaces[nsId].entries.type` rather than the implicit fallback
|
|
128
142
|
* namespace used for top-level `storageTypes` enums.
|
|
129
143
|
*/
|
|
130
144
|
readonly namespaceTypes?: Readonly<
|
|
@@ -139,8 +153,14 @@ export interface ContractDefinition {
|
|
|
139
153
|
* Target-supplied factory that materialises a `Namespace` concretion
|
|
140
154
|
* for a declared namespace coordinate. Mirrors
|
|
141
155
|
* `ContractInput.createNamespace`.
|
|
156
|
+
*
|
|
157
|
+
* The optional second argument carries target-specific enum types for the
|
|
158
|
+
* namespace (e.g. postgres enum registrations keyed by type name).
|
|
142
159
|
*/
|
|
143
|
-
readonly createNamespace?: (
|
|
160
|
+
readonly createNamespace?: (
|
|
161
|
+
input: SqlNamespaceTablesInput,
|
|
162
|
+
enumTypes?: Readonly<Record<string, PostgresEnumStorageEntry>>,
|
|
163
|
+
) => Namespace;
|
|
144
164
|
readonly models: readonly ModelNode[];
|
|
145
165
|
readonly valueObjects?: readonly ValueObjectNode[];
|
|
146
166
|
}
|
package/src/contract-dsl.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ColumnDefault,
|
|
3
3
|
ColumnDefaultLiteralInputValue,
|
|
4
|
+
ControlPolicy,
|
|
4
5
|
ExecutionMutationDefaultPhases,
|
|
5
6
|
ExecutionMutationDefaultValue,
|
|
6
7
|
} from '@prisma-next/contract/types';
|
|
@@ -785,6 +786,7 @@ export type ModelAttributesSpec = {
|
|
|
785
786
|
|
|
786
787
|
export type SqlStageSpec = {
|
|
787
788
|
readonly table?: string;
|
|
789
|
+
readonly control?: ControlPolicy;
|
|
788
790
|
readonly indexes?: readonly IndexConstraint[];
|
|
789
791
|
readonly foreignKeys?: readonly ForeignKeyConstraint[];
|
|
790
792
|
};
|
|
@@ -1216,6 +1218,7 @@ export type ContractInput<
|
|
|
1216
1218
|
readonly naming?: NamingConfig;
|
|
1217
1219
|
readonly storageHash?: string;
|
|
1218
1220
|
readonly foreignKeyDefaults?: ForeignKeyDefaultsState;
|
|
1221
|
+
readonly defaultControlPolicy?: ControlPolicy;
|
|
1219
1222
|
/**
|
|
1220
1223
|
* Declared namespace coordinates the contract recognises. Per-model
|
|
1221
1224
|
* `namespace` references must reference an entry in this list (or the
|
package/src/contract-lowering.ts
CHANGED
|
@@ -614,6 +614,7 @@ function resolveModelNode(
|
|
|
614
614
|
...(indexes.length > 0 ? { indexes } : {}),
|
|
615
615
|
...(foreignKeys.length > 0 ? { foreignKeys } : {}),
|
|
616
616
|
...(relations.length > 0 ? { relations } : {}),
|
|
617
|
+
...ifDefined('control', spec.sqlSpec?.control),
|
|
617
618
|
};
|
|
618
619
|
}
|
|
619
620
|
|
|
@@ -707,6 +708,7 @@ export function buildContractDefinition(definition: ContractInput): ContractDefi
|
|
|
707
708
|
|
|
708
709
|
return {
|
|
709
710
|
target: definition.target,
|
|
711
|
+
...ifDefined('defaultControlPolicy', definition.defaultControlPolicy),
|
|
710
712
|
...(definition.extensionPacks ? { extensionPacks: definition.extensionPacks } : {}),
|
|
711
713
|
...(definition.storageHash ? { storageHash: definition.storageHash } : {}),
|
|
712
714
|
...(definition.foreignKeyDefaults ? { foreignKeyDefaults: definition.foreignKeyDefaults } : {}),
|