@prisma-next/sql-contract-ts 0.14.0-dev.80 → 0.14.0-dev.82
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/dist/{build-contract-C07F2P2K.mjs → build-contract-BzAyIu0y.mjs} +42 -40
- package/dist/build-contract-BzAyIu0y.mjs.map +1 -0
- package/dist/config-types.mjs +1 -1
- package/dist/contract-builder.d.mts +205 -196
- package/dist/contract-builder.d.mts.map +1 -1
- package/dist/contract-builder.mjs +118 -29
- package/dist/contract-builder.mjs.map +1 -1
- package/package.json +10 -10
- package/src/build-contract.ts +52 -53
- package/src/contract-builder.ts +9 -63
- package/src/contract-definition.ts +17 -12
- package/src/contract-dsl.ts +8 -8
- package/src/contract-lowering.ts +153 -3
- package/src/exports/contract-builder.ts +2 -0
- package/dist/build-contract-C07F2P2K.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.14.0-dev.
|
|
3
|
+
"version": "0.14.0-dev.82",
|
|
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.14.0-dev.
|
|
10
|
-
"@prisma-next/contract": "0.14.0-dev.
|
|
11
|
-
"@prisma-next/contract-authoring": "0.14.0-dev.
|
|
12
|
-
"@prisma-next/framework-components": "0.14.0-dev.
|
|
13
|
-
"@prisma-next/sql-contract": "0.14.0-dev.
|
|
14
|
-
"@prisma-next/utils": "0.14.0-dev.
|
|
9
|
+
"@prisma-next/config": "0.14.0-dev.82",
|
|
10
|
+
"@prisma-next/contract": "0.14.0-dev.82",
|
|
11
|
+
"@prisma-next/contract-authoring": "0.14.0-dev.82",
|
|
12
|
+
"@prisma-next/framework-components": "0.14.0-dev.82",
|
|
13
|
+
"@prisma-next/sql-contract": "0.14.0-dev.82",
|
|
14
|
+
"@prisma-next/utils": "0.14.0-dev.82",
|
|
15
15
|
"arktype": "^2.2.2",
|
|
16
16
|
"pathe": "^2.0.3",
|
|
17
17
|
"ts-toolbelt": "^9.6.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@prisma-next/test-utils": "0.14.0-dev.
|
|
21
|
-
"@prisma-next/tsconfig": "0.14.0-dev.
|
|
20
|
+
"@prisma-next/test-utils": "0.14.0-dev.82",
|
|
21
|
+
"@prisma-next/tsconfig": "0.14.0-dev.82",
|
|
22
22
|
"@types/pg": "8.20.0",
|
|
23
23
|
"pg": "8.22.0",
|
|
24
|
-
"@prisma-next/tsdown": "0.14.0-dev.
|
|
24
|
+
"@prisma-next/tsdown": "0.14.0-dev.82",
|
|
25
25
|
"tsdown": "0.22.3",
|
|
26
26
|
"typescript": "5.9.3",
|
|
27
27
|
"vitest": "4.1.10"
|
package/src/build-contract.ts
CHANGED
|
@@ -281,14 +281,14 @@ function qualifyColumnDescriptor(
|
|
|
281
281
|
};
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
type
|
|
284
|
+
type CollectedColumnEntities = Record<string, Record<string, Record<string, unknown>>>;
|
|
285
285
|
|
|
286
286
|
/**
|
|
287
287
|
* Records a deferred column's entity-ref into the namespace-scoped collection
|
|
288
|
-
* accumulator
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
288
|
+
* accumulator (`namespaceId → entityKind → entityName`) — folded into the same
|
|
289
|
+
* namespace assembly `deriveEntityValueSets`/`entries.<kind>` step as the
|
|
290
|
+
* entities-channel attachments, so a column-collected entity gets its
|
|
291
|
+
* value-set the same way an entities-channel one does.
|
|
292
292
|
*
|
|
293
293
|
* The same handle reused by many columns in one namespace is normal (a native
|
|
294
294
|
* enum type backs any number of columns) and records the identical entity once.
|
|
@@ -297,8 +297,8 @@ type CollectedPackEntities = Record<string, Record<string, Record<string, unknow
|
|
|
297
297
|
* of them, silently mismatching the other column's type/cast. PSL hard-errors
|
|
298
298
|
* on the equivalent (`PSL_DUPLICATE_DECLARATION`); the TS path rejects it too.
|
|
299
299
|
*/
|
|
300
|
-
function
|
|
301
|
-
collected:
|
|
300
|
+
function collectEntityFromColumn(
|
|
301
|
+
collected: CollectedColumnEntities,
|
|
302
302
|
namespaceId: string,
|
|
303
303
|
entityRef: NonNullable<ColumnTypeDescriptor['entityRef']>,
|
|
304
304
|
): void {
|
|
@@ -316,34 +316,36 @@ function collectPackEntityFromColumn(
|
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
/**
|
|
319
|
-
* Merges
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
319
|
+
* Merges a namespace's entities-channel attachments (lowered from the
|
|
320
|
+
* `entities` handle list, carried on `ContractDefinition.attachedEntities`)
|
|
321
|
+
* with the entities collected from that namespace's deferred entity-ref
|
|
322
|
+
* columns. A column-collected entity that shadows a *different* attached
|
|
323
|
+
* entity of the same kind+name (or vice-versa) is the same name-collision bug
|
|
324
|
+
* `collectEntityFromColumn` guards against across columns, so it is rejected
|
|
325
|
+
* the same way — by entity identity, so the same handle attached and used by
|
|
326
|
+
* a column does not throw.
|
|
325
327
|
*/
|
|
326
|
-
function
|
|
328
|
+
function mergeColumnAndAttachedEntities(
|
|
327
329
|
namespaceId: string,
|
|
328
|
-
|
|
329
|
-
|
|
330
|
+
attached: Readonly<Record<string, Readonly<Record<string, unknown>>>> | undefined,
|
|
331
|
+
columnCollected: Readonly<Record<string, Readonly<Record<string, unknown>>>> | undefined,
|
|
330
332
|
): Readonly<Record<string, Readonly<Record<string, unknown>>>> | undefined {
|
|
331
|
-
if (
|
|
332
|
-
if (
|
|
333
|
-
const kinds = new Set([...Object.keys(
|
|
333
|
+
if (attached === undefined) return columnCollected;
|
|
334
|
+
if (columnCollected === undefined) return attached;
|
|
335
|
+
const kinds = new Set([...Object.keys(attached), ...Object.keys(columnCollected)]);
|
|
334
336
|
const result: Record<string, Readonly<Record<string, unknown>>> = {};
|
|
335
337
|
for (const kind of kinds) {
|
|
336
|
-
const
|
|
337
|
-
const
|
|
338
|
-
for (const [name, entity] of Object.entries(
|
|
339
|
-
const existing =
|
|
338
|
+
const attachedForKind = attached[kind];
|
|
339
|
+
const columnForKind = columnCollected[kind];
|
|
340
|
+
for (const [name, entity] of Object.entries(columnForKind ?? {})) {
|
|
341
|
+
const existing = attachedForKind?.[name];
|
|
340
342
|
if (existing !== undefined && existing !== entity) {
|
|
341
343
|
throw new Error(
|
|
342
|
-
`buildSqlContractFromDefinition: two different "${kind}" entities named "${name}" in namespace "${namespaceId}" — a
|
|
344
|
+
`buildSqlContractFromDefinition: two different "${kind}" entities named "${name}" in namespace "${namespaceId}" — a column-referenced entity conflicts with an attached one; pack-entity names must be unique per namespace.`,
|
|
343
345
|
);
|
|
344
346
|
}
|
|
345
347
|
}
|
|
346
|
-
result[kind] = { ...
|
|
348
|
+
result[kind] = { ...attachedForKind, ...columnForKind };
|
|
347
349
|
}
|
|
348
350
|
return result;
|
|
349
351
|
}
|
|
@@ -486,7 +488,7 @@ function collectStorageNamespaceCoordinateIds(definition: ContractDefinition): S
|
|
|
486
488
|
ids.add(model.namespaceId);
|
|
487
489
|
}
|
|
488
490
|
}
|
|
489
|
-
for (const id of Object.keys(definition.
|
|
491
|
+
for (const id of Object.keys(definition.attachedEntities ?? {})) {
|
|
490
492
|
if (id.length > 0) {
|
|
491
493
|
ids.add(id);
|
|
492
494
|
}
|
|
@@ -496,21 +498,21 @@ function collectStorageNamespaceCoordinateIds(definition: ContractDefinition): S
|
|
|
496
498
|
|
|
497
499
|
/**
|
|
498
500
|
* Entry kinds the framework assembler itself manages (`table` from models,
|
|
499
|
-
* `valueSet` from `enums` and
|
|
500
|
-
*
|
|
501
|
-
*
|
|
501
|
+
* `valueSet` from `enums` and attached-entity value-set derivation). A
|
|
502
|
+
* pack-attached entity claiming one of these would silently clobber or be
|
|
503
|
+
* clobbered by the managed slot, so it is rejected outright.
|
|
502
504
|
*/
|
|
503
505
|
const MANAGED_ENTRY_KINDS = new Set([tableEntityKind.kind, valueSetEntityKind.kind]);
|
|
504
506
|
|
|
505
|
-
function
|
|
507
|
+
function assertNoManagedEntityKinds(
|
|
506
508
|
namespaceId: string,
|
|
507
|
-
|
|
509
|
+
entitiesForNs: Readonly<Record<string, unknown>> | undefined,
|
|
508
510
|
): void {
|
|
509
|
-
if (
|
|
510
|
-
for (const kind of Object.keys(
|
|
511
|
+
if (entitiesForNs === undefined) return;
|
|
512
|
+
for (const kind of Object.keys(entitiesForNs)) {
|
|
511
513
|
if (MANAGED_ENTRY_KINDS.has(kind)) {
|
|
512
514
|
throw new Error(
|
|
513
|
-
`buildSqlContractFromDefinition:
|
|
515
|
+
`buildSqlContractFromDefinition: attached entity in namespace "${namespaceId}" declares entry kind "${kind}", which is managed by the framework (table/valueSet) and cannot be attached.`,
|
|
514
516
|
);
|
|
515
517
|
}
|
|
516
518
|
}
|
|
@@ -557,13 +559,13 @@ function collectEntityTypeDescriptorsByDiscriminator(
|
|
|
557
559
|
* registered descriptor, or whose descriptor output doesn't derive a
|
|
558
560
|
* value-set, contribute nothing.
|
|
559
561
|
*/
|
|
560
|
-
function
|
|
561
|
-
|
|
562
|
+
function deriveEntityValueSets(
|
|
563
|
+
entitiesForNs: Readonly<Record<string, Readonly<Record<string, unknown>>>> | undefined,
|
|
562
564
|
entityTypesByDiscriminator: ReadonlyMap<string, AuthoringEntityTypeDescriptor>,
|
|
563
565
|
): Record<string, StorageValueSetInput> | undefined {
|
|
564
|
-
if (
|
|
566
|
+
if (entitiesForNs === undefined) return undefined;
|
|
565
567
|
let result: Record<string, StorageValueSetInput> | undefined;
|
|
566
|
-
for (const [kind, entitiesByName] of Object.entries(
|
|
568
|
+
for (const [kind, entitiesByName] of Object.entries(entitiesForNs)) {
|
|
567
569
|
const descriptor = entityTypesByDiscriminator.get(kind);
|
|
568
570
|
if (descriptor === undefined) continue;
|
|
569
571
|
for (const [name, entity] of Object.entries(entitiesByName)) {
|
|
@@ -582,7 +584,7 @@ function derivePackEntityValueSets(
|
|
|
582
584
|
* which drives value-set → codec typing and the domain-enum CHECK — so a
|
|
583
585
|
* same-named entry in both would let one silently overwrite the other and
|
|
584
586
|
* corrupt whichever column resolves against it. The same collision class the
|
|
585
|
-
* `
|
|
587
|
+
* `mergeColumnAndAttachedEntities` guard rejects; the PSL path already hard-errors
|
|
586
588
|
* on the equivalent (`interpretPslDocumentToSqlContract`). Reject it here too.
|
|
587
589
|
*/
|
|
588
590
|
function mergeNamespaceValueSets(
|
|
@@ -645,7 +647,7 @@ export function buildSqlContractFromDefinition(
|
|
|
645
647
|
const modelNameToNamespaceId = new Map<string, string>();
|
|
646
648
|
const executionDefaults: ExecutionMutationDefault[] = [];
|
|
647
649
|
const modelsByNamespace: Record<string, Record<string, ContractModel>> = {};
|
|
648
|
-
const
|
|
650
|
+
const collectedColumnEntities: CollectedColumnEntities = {};
|
|
649
651
|
const rootEntries: Array<{
|
|
650
652
|
readonly tableName: string;
|
|
651
653
|
readonly namespaceId: string;
|
|
@@ -720,9 +722,9 @@ export function buildSqlContractFromDefinition(
|
|
|
720
722
|
|
|
721
723
|
// A field authored through a deferred entity-ref column helper (e.g.
|
|
722
724
|
// `pg.enum(handle)`) carries `descriptor.entityRef`: the referenced
|
|
723
|
-
// entity is collected into `
|
|
724
|
-
// same `entries.<kind>` + `entries.valueSet` assembly an
|
|
725
|
-
//
|
|
725
|
+
// entity is collected into `collectedColumnEntities` (folded into the
|
|
726
|
+
// same `entries.<kind>` + `entries.valueSet` assembly an entities-channel
|
|
727
|
+
// attachment goes through) and the descriptor is resolved
|
|
726
728
|
// against this field's now-known `namespaceId` — the builder call that
|
|
727
729
|
// produced it ran before the enclosing model associated one. The
|
|
728
730
|
// descriptor is then handed to the target's `qualifyColumnType` hook,
|
|
@@ -738,7 +740,7 @@ export function buildSqlContractFromDefinition(
|
|
|
738
740
|
let descriptor = field.descriptor;
|
|
739
741
|
const entityRef = descriptor.entityRef;
|
|
740
742
|
if (entityRef !== undefined) {
|
|
741
|
-
|
|
743
|
+
collectEntityFromColumn(collectedColumnEntities, namespaceId, entityRef);
|
|
742
744
|
descriptor = resolveEntityRefDescriptor(descriptor, namespaceId);
|
|
743
745
|
}
|
|
744
746
|
descriptor = qualifyColumnDescriptor(descriptor, namespaceId, qualifyColumnType);
|
|
@@ -1072,18 +1074,15 @@ export function buildSqlContractFromDefinition(
|
|
|
1072
1074
|
const entityTypesByDiscriminator = collectEntityTypeDescriptorsByDiscriminator(definition);
|
|
1073
1075
|
const namespaces: SqlStorageInput['namespaces'] = Object.fromEntries(
|
|
1074
1076
|
[...namespaceCoordinateIds].sort().map((id) => {
|
|
1075
|
-
const
|
|
1077
|
+
const entitiesForNs = mergeColumnAndAttachedEntities(
|
|
1076
1078
|
id,
|
|
1077
|
-
definition.
|
|
1078
|
-
|
|
1079
|
+
definition.attachedEntities?.[id],
|
|
1080
|
+
collectedColumnEntities[id],
|
|
1079
1081
|
);
|
|
1080
|
-
|
|
1082
|
+
assertNoManagedEntityKinds(id, entitiesForNs);
|
|
1081
1083
|
|
|
1082
1084
|
const enumValueSetEntries = storageValueSetsByNs[id];
|
|
1083
|
-
const packValueSetEntries =
|
|
1084
|
-
packEntitiesForNs,
|
|
1085
|
-
entityTypesByDiscriminator,
|
|
1086
|
-
);
|
|
1085
|
+
const packValueSetEntries = deriveEntityValueSets(entitiesForNs, entityTypesByDiscriminator);
|
|
1087
1086
|
const valueSetEntries =
|
|
1088
1087
|
enumValueSetEntries !== undefined || packValueSetEntries !== undefined
|
|
1089
1088
|
? mergeNamespaceValueSets(id, enumValueSetEntries, packValueSetEntries)
|
|
@@ -1093,7 +1092,7 @@ export function buildSqlContractFromDefinition(
|
|
|
1093
1092
|
id,
|
|
1094
1093
|
entries: {
|
|
1095
1094
|
table: tablesByNamespace[id] ?? {},
|
|
1096
|
-
...
|
|
1095
|
+
...entitiesForNs,
|
|
1097
1096
|
...(valueSetEntries !== undefined && Object.keys(valueSetEntries).length > 0
|
|
1098
1097
|
? { valueSet: valueSetEntries }
|
|
1099
1098
|
: {}),
|
package/src/contract-builder.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
FamilyPackRef,
|
|
7
7
|
TargetPackRef,
|
|
8
8
|
} from '@prisma-next/framework-components/components';
|
|
9
|
+
import type { PackEntityHandle } from '@prisma-next/sql-contract/entity-handle-lowering-hook';
|
|
9
10
|
import type {
|
|
10
11
|
SqlNamespaceBase,
|
|
11
12
|
SqlNamespaceInput,
|
|
@@ -18,7 +19,6 @@ import {
|
|
|
18
19
|
type ComposedAuthoringHelpers,
|
|
19
20
|
createComposedAuthoringHelpers,
|
|
20
21
|
} from './composed-authoring-helpers';
|
|
21
|
-
import type { PackEntitiesInput } from './contract-definition';
|
|
22
22
|
import {
|
|
23
23
|
type ContractInput,
|
|
24
24
|
type ContractModelBuilder,
|
|
@@ -77,7 +77,7 @@ type ContractDefinition<
|
|
|
77
77
|
readonly models?: Models;
|
|
78
78
|
readonly codecLookup?: CodecLookup;
|
|
79
79
|
readonly enums?: Enums;
|
|
80
|
-
readonly
|
|
80
|
+
readonly entities?: readonly PackEntityHandle[];
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
type ContractScaffold<
|
|
@@ -103,7 +103,7 @@ type ContractScaffold<
|
|
|
103
103
|
readonly models?: never;
|
|
104
104
|
readonly codecLookup?: CodecLookup;
|
|
105
105
|
readonly enums?: Enums;
|
|
106
|
-
readonly
|
|
106
|
+
readonly entities?: readonly PackEntityHandle[];
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
type ContractFactory<
|
|
@@ -117,7 +117,7 @@ type ContractFactory<
|
|
|
117
117
|
readonly types?: Types;
|
|
118
118
|
readonly models?: Models;
|
|
119
119
|
readonly enums?: Enums;
|
|
120
|
-
readonly
|
|
120
|
+
readonly entities?: readonly PackEntityHandle[];
|
|
121
121
|
};
|
|
122
122
|
|
|
123
123
|
function validateTargetPackRef(
|
|
@@ -326,7 +326,7 @@ type BoundDefinitionInput<
|
|
|
326
326
|
readonly models?: Models;
|
|
327
327
|
readonly codecLookup?: CodecLookup;
|
|
328
328
|
readonly enums?: Record<string, EnumTypeHandle>;
|
|
329
|
-
readonly
|
|
329
|
+
readonly entities?: readonly PackEntityHandle[];
|
|
330
330
|
};
|
|
331
331
|
|
|
332
332
|
// A bare `Record<string, EnumTypeHandle>` (no literal keys) is the widened
|
|
@@ -350,60 +350,6 @@ type WithFamilyTarget<
|
|
|
350
350
|
T extends TargetPackRef<'sql', string>,
|
|
351
351
|
> = Input & { readonly family: F; readonly target: T };
|
|
352
352
|
|
|
353
|
-
// Deep-merges packEntities authored on the scaffold definition with those
|
|
354
|
-
// returned from the factory callback — three levels deep (namespace, kind,
|
|
355
|
-
// name), unlike `enums`' flat shallow merge, since a factory-declared
|
|
356
|
-
// namespace/kind must not silently drop a scaffold-declared sibling entry
|
|
357
|
-
// under the same namespace or kind. A same-name key present on both sides is
|
|
358
|
-
// only merged when it is the identical entity instance; two *different*
|
|
359
|
-
// entities of the same namespace/kind/name is a collision (the emitted
|
|
360
|
-
// entry could reflect only one), rejected here the same way
|
|
361
|
-
// `mergeCollectedPackEntities` in `build-contract.ts` rejects it.
|
|
362
|
-
function mergePackEntityNames(
|
|
363
|
-
namespaceId: string,
|
|
364
|
-
kind: string,
|
|
365
|
-
a: Readonly<Record<string, unknown>> | undefined,
|
|
366
|
-
b: Readonly<Record<string, unknown>> | undefined,
|
|
367
|
-
): Readonly<Record<string, unknown>> {
|
|
368
|
-
if (a !== undefined && b !== undefined) {
|
|
369
|
-
for (const [name, entity] of Object.entries(b)) {
|
|
370
|
-
const existing = a[name];
|
|
371
|
-
if (existing !== undefined && existing !== entity) {
|
|
372
|
-
throw new Error(
|
|
373
|
-
`defineContract: two different "${kind}" entities named "${name}" in namespace "${namespaceId}" — a factory-returned pack entity conflicts with a scaffold-declared one; pack-entity names must be unique per namespace.`,
|
|
374
|
-
);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
return { ...(a ?? {}), ...(b ?? {}) };
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function mergePackEntityKinds(
|
|
382
|
-
namespaceId: string,
|
|
383
|
-
a: Readonly<Record<string, Readonly<Record<string, unknown>>>> | undefined,
|
|
384
|
-
b: Readonly<Record<string, Readonly<Record<string, unknown>>>> | undefined,
|
|
385
|
-
): Readonly<Record<string, Readonly<Record<string, unknown>>>> {
|
|
386
|
-
const kinds = new Set([...Object.keys(a ?? {}), ...Object.keys(b ?? {})]);
|
|
387
|
-
const result: Record<string, Readonly<Record<string, unknown>>> = {};
|
|
388
|
-
for (const kind of kinds) {
|
|
389
|
-
result[kind] = mergePackEntityNames(namespaceId, kind, a?.[kind], b?.[kind]);
|
|
390
|
-
}
|
|
391
|
-
return result;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function mergePackEntities(
|
|
395
|
-
a: PackEntitiesInput | undefined,
|
|
396
|
-
b: PackEntitiesInput | undefined,
|
|
397
|
-
): PackEntitiesInput | undefined {
|
|
398
|
-
if (a === undefined && b === undefined) return undefined;
|
|
399
|
-
const namespaces = new Set([...Object.keys(a ?? {}), ...Object.keys(b ?? {})]);
|
|
400
|
-
const result: Record<string, Readonly<Record<string, Readonly<Record<string, unknown>>>>> = {};
|
|
401
|
-
for (const namespaceId of namespaces) {
|
|
402
|
-
result[namespaceId] = mergePackEntityKinds(namespaceId, a?.[namespaceId], b?.[namespaceId]);
|
|
403
|
-
}
|
|
404
|
-
return result;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
353
|
/**
|
|
408
354
|
* Shared builder that assembles a SqlContract with pre-bound family and target.
|
|
409
355
|
* Extension wrappers keep their own public overloads and delegate their impl body here;
|
|
@@ -449,7 +395,7 @@ export function buildBoundContract<
|
|
|
449
395
|
readonly types?: Record<string, StorageTypeInstance>;
|
|
450
396
|
readonly models?: Record<string, ModelLike>;
|
|
451
397
|
readonly enums?: Record<string, EnumTypeHandle>;
|
|
452
|
-
readonly
|
|
398
|
+
readonly entities?: readonly PackEntityHandle[];
|
|
453
399
|
},
|
|
454
400
|
>(
|
|
455
401
|
family: F,
|
|
@@ -475,7 +421,7 @@ export function buildBoundContract(
|
|
|
475
421
|
readonly types?: Record<string, StorageTypeInstance>;
|
|
476
422
|
readonly models?: Record<string, ModelLike>;
|
|
477
423
|
readonly enums?: Record<string, EnumTypeHandle>;
|
|
478
|
-
readonly
|
|
424
|
+
readonly entities?: readonly PackEntityHandle[];
|
|
479
425
|
})
|
|
480
426
|
| undefined,
|
|
481
427
|
) {
|
|
@@ -490,13 +436,13 @@ export function buildBoundContract(
|
|
|
490
436
|
}),
|
|
491
437
|
);
|
|
492
438
|
const mergedEnums = { ...(definition.enums ?? {}), ...built.enums };
|
|
493
|
-
const
|
|
439
|
+
const mergedEntities = [...(definition.entities ?? []), ...(built.entities ?? [])];
|
|
494
440
|
return buildContractFromDsl({
|
|
495
441
|
...full,
|
|
496
442
|
...ifDefined('types', built.types),
|
|
497
443
|
...ifDefined('models', built.models),
|
|
498
444
|
...ifDefined('enums', Object.keys(mergedEnums).length > 0 ? mergedEnums : undefined),
|
|
499
|
-
...ifDefined('
|
|
445
|
+
...ifDefined('entities', mergedEntities.length > 0 ? mergedEntities : undefined),
|
|
500
446
|
});
|
|
501
447
|
}
|
|
502
448
|
|
|
@@ -17,16 +17,19 @@ import type { EnumTypeHandle } from './enum-type';
|
|
|
17
17
|
export type { ExecutionMutationDefaultPhases };
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* Namespace-scoped pack-entity attachments, the internal build IR carrying
|
|
21
|
+
* the lowered `entities` handle list: namespace id → entity kind (the
|
|
22
|
+
* discriminator the target/extension pack registered its
|
|
22
23
|
* `AuthoringContributions.entityTypes` descriptor under, e.g. `native_enum`)
|
|
23
24
|
* → entity name → the lowered entity instance. Generic on purpose — neither
|
|
24
25
|
* the framework nor `contract-ts` names a specific entity kind here; the
|
|
25
26
|
* shape mirrors `SqlNamespaceInput.entries` (`entries.<kind>[name]`), just
|
|
26
|
-
* namespace-nested so an
|
|
27
|
-
* or named), not only the contract's default namespace.
|
|
27
|
+
* namespace-nested so an attachment can target any declared namespace
|
|
28
|
+
* (default or named), not only the contract's default namespace. Produced by
|
|
29
|
+
* the generic entity-handle walk in `buildContractDefinition`; never an
|
|
30
|
+
* author-facing input.
|
|
28
31
|
*/
|
|
29
|
-
export type
|
|
32
|
+
export type AttachedEntities = Readonly<
|
|
30
33
|
Record<string, Readonly<Record<string, Readonly<Record<string, unknown>>>>>
|
|
31
34
|
>;
|
|
32
35
|
|
|
@@ -200,12 +203,14 @@ export interface ContractDefinition {
|
|
|
200
203
|
*/
|
|
201
204
|
readonly enums?: Record<string, EnumTypeHandle>;
|
|
202
205
|
/**
|
|
203
|
-
*
|
|
204
|
-
* name. Each entity
|
|
205
|
-
* when the registered entity-type
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* `enums` flows
|
|
206
|
+
* Pack-entity attachments lowered from the `entities` handle list, keyed by
|
|
207
|
+
* namespace then entity kind then name. Each entity lands in
|
|
208
|
+
* `storage.namespaces[ns].entries.<kind>`; when the registered entity-type
|
|
209
|
+
* descriptor's factory output implements the
|
|
210
|
+
* `SqlValueSetDerivingEntityTypeOutput.deriveValueSet` hook, the derived
|
|
211
|
+
* value-set also folds into `entries.valueSet`, mirroring how `enums` flows
|
|
212
|
+
* there. Internal build IR — populated by `buildContractDefinition`, not an
|
|
213
|
+
* author input.
|
|
209
214
|
*/
|
|
210
|
-
readonly
|
|
215
|
+
readonly attachedEntities?: AttachedEntities;
|
|
211
216
|
}
|
package/src/contract-dsl.ts
CHANGED
|
@@ -1615,14 +1615,14 @@ export type ContractInput<
|
|
|
1615
1615
|
*/
|
|
1616
1616
|
readonly enums?: Record<string, import('./enum-type').EnumTypeHandle>;
|
|
1617
1617
|
/**
|
|
1618
|
-
* Author-declared pack
|
|
1619
|
-
*
|
|
1620
|
-
*
|
|
1621
|
-
*
|
|
1622
|
-
*
|
|
1623
|
-
*
|
|
1618
|
+
* Author-declared pack-entity handles, lowered by the pack that registered
|
|
1619
|
+
* each handle's `entityKind` (through the SQL-family batch lowering hook)
|
|
1620
|
+
* into namespace-scoped entry attachments at build time. Generic on
|
|
1621
|
+
* purpose — neither this type nor the walk names a specific kind; a handle
|
|
1622
|
+
* whose kind no composed pack registers is a build error. This is the only
|
|
1623
|
+
* public channel for attaching pack entities.
|
|
1624
1624
|
*/
|
|
1625
|
-
readonly
|
|
1625
|
+
readonly entities?: readonly import('@prisma-next/sql-contract/entity-handle-lowering-hook').PackEntityHandle[];
|
|
1626
1626
|
};
|
|
1627
1627
|
|
|
1628
1628
|
export function model<
|
|
@@ -1744,7 +1744,7 @@ type CrossSpaceHandle = {
|
|
|
1744
1744
|
readonly stageOne: { readonly namespace?: string };
|
|
1745
1745
|
};
|
|
1746
1746
|
|
|
1747
|
-
function isCrossSpaceHandle(value: unknown): value is CrossSpaceHandle {
|
|
1747
|
+
export function isCrossSpaceHandle(value: unknown): value is CrossSpaceHandle {
|
|
1748
1748
|
if (typeof value !== 'object' || value === null) {
|
|
1749
1749
|
return false;
|
|
1750
1750
|
}
|