@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
package/src/contract-lowering.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ColumnTypeDescriptor } from '@prisma-next/framework-components/codec';
|
|
2
|
+
import type { ExtensionPackRef } from '@prisma-next/framework-components/components';
|
|
2
3
|
import {
|
|
3
4
|
isPostgresEnumStorageEntry,
|
|
4
5
|
type PostgresEnumStorageEntry,
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
emitTypedCrossModelFallbackWarnings,
|
|
36
37
|
emitTypedNamedTypeFallbackWarnings,
|
|
37
38
|
} from './contract-warnings';
|
|
39
|
+
import { isEnumTypeHandle } from './enum-type';
|
|
38
40
|
|
|
39
41
|
type RuntimeModel = ContractModelBuilder<
|
|
40
42
|
string | undefined,
|
|
@@ -84,6 +86,13 @@ function resolveFieldDescriptor(
|
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
if ('typeRef' in fieldState && fieldState.typeRef) {
|
|
89
|
+
if (isEnumTypeHandle(fieldState.typeRef)) {
|
|
90
|
+
return {
|
|
91
|
+
codecId: fieldState.typeRef.codecId,
|
|
92
|
+
nativeType: fieldState.typeRef.nativeType,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
87
96
|
const typeRef =
|
|
88
97
|
typeof fieldState.typeRef === 'string'
|
|
89
98
|
? fieldState.typeRef
|
|
@@ -253,6 +262,41 @@ function resolveRelationForeignKeys(
|
|
|
253
262
|
}
|
|
254
263
|
|
|
255
264
|
const targetModelName = resolveRelationModelName(relation.toModel);
|
|
265
|
+
|
|
266
|
+
// F-relfk: cross-space relations carry a spaceId; skip the local spec lookup
|
|
267
|
+
// and include cross-space coordinates so resolveForeignKeyNodes routes the FK
|
|
268
|
+
// through the cross-space path.
|
|
269
|
+
if (relation.spaceId !== undefined) {
|
|
270
|
+
const fields = normalizeRelationFieldNames(relation.from);
|
|
271
|
+
const targetFields = normalizeRelationFieldNames(relation.to);
|
|
272
|
+
assertRelationFieldArity({
|
|
273
|
+
modelName: spec.modelName,
|
|
274
|
+
relationName,
|
|
275
|
+
leftLabel: 'source',
|
|
276
|
+
leftFields: fields,
|
|
277
|
+
rightLabel: 'target',
|
|
278
|
+
rightFields: targetFields,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
foreignKeys.push({
|
|
282
|
+
kind: 'fk',
|
|
283
|
+
fields,
|
|
284
|
+
targetModel: targetModelName,
|
|
285
|
+
targetFields,
|
|
286
|
+
targetSpaceId: relation.spaceId,
|
|
287
|
+
...(relation.namespaceId !== undefined ? { targetNamespaceId: relation.namespaceId } : {}),
|
|
288
|
+
...(relation.tableName !== undefined ? { targetTableName: relation.tableName } : {}),
|
|
289
|
+
...(relation.sql.fk.name ? { name: relation.sql.fk.name } : {}),
|
|
290
|
+
...(relation.sql.fk.onDelete ? { onDelete: relation.sql.fk.onDelete } : {}),
|
|
291
|
+
...(relation.sql.fk.onUpdate ? { onUpdate: relation.sql.fk.onUpdate } : {}),
|
|
292
|
+
...(relation.sql.fk.constraint !== undefined
|
|
293
|
+
? { constraint: relation.sql.fk.constraint }
|
|
294
|
+
: {}),
|
|
295
|
+
...(relation.sql.fk.index !== undefined ? { index: relation.sql.fk.index } : {}),
|
|
296
|
+
});
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
|
|
256
300
|
if (!allSpecs.has(targetModelName)) {
|
|
257
301
|
throw new Error(
|
|
258
302
|
`Relation "${spec.modelName}.${relationName}" references unknown model "${targetModelName}"`,
|
|
@@ -308,17 +352,12 @@ function lowerBelongsToRelation(
|
|
|
308
352
|
relation: Extract<RelationState, { kind: 'belongsTo' }>,
|
|
309
353
|
currentSpec: RuntimeModelSpec,
|
|
310
354
|
allSpecs: ReadonlyMap<string, RuntimeModelSpec>,
|
|
355
|
+
extensionPacks?: Record<string, ExtensionPackRef<'sql', string>>,
|
|
311
356
|
): RelationNode {
|
|
312
357
|
const targetModelName = resolveRelationModelName(relation.toModel);
|
|
313
|
-
const targetSpec = allSpecs.get(targetModelName);
|
|
314
|
-
if (!targetSpec) {
|
|
315
|
-
throw new Error(
|
|
316
|
-
`Relation "${currentSpec.modelName}.${relationName}" references unknown model "${targetModelName}"`,
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
358
|
const fromFields = normalizeRelationFieldNames(relation.from);
|
|
321
359
|
const toFields = normalizeRelationFieldNames(relation.to);
|
|
360
|
+
|
|
322
361
|
assertRelationFieldArity({
|
|
323
362
|
modelName: currentSpec.modelName,
|
|
324
363
|
relationName,
|
|
@@ -328,6 +367,48 @@ function lowerBelongsToRelation(
|
|
|
328
367
|
rightFields: toFields,
|
|
329
368
|
});
|
|
330
369
|
|
|
370
|
+
// Cross-space path: the target lives in a different contract space.
|
|
371
|
+
// Resolve from the brand carried on the BelongsToRelation instead of
|
|
372
|
+
// requiring a local model spec — matching how the FK lowering works.
|
|
373
|
+
if (relation.spaceId !== undefined) {
|
|
374
|
+
assertKnownExtensionPack(
|
|
375
|
+
extensionPacks,
|
|
376
|
+
relation.spaceId,
|
|
377
|
+
`Relation "${currentSpec.modelName}.${relationName}"`,
|
|
378
|
+
);
|
|
379
|
+
const targetTable = relation.tableName ?? targetModelName.toLowerCase();
|
|
380
|
+
const parentColumns = mapFieldNamesToColumnNames(
|
|
381
|
+
currentSpec.modelName,
|
|
382
|
+
fromFields,
|
|
383
|
+
currentSpec.fieldToColumn,
|
|
384
|
+
);
|
|
385
|
+
// For cross-space relations, the `to` field names map directly to column
|
|
386
|
+
// names because we have no fieldToColumn map for the remote model.
|
|
387
|
+
// (The brand carries the table name; field→column resolution on the remote
|
|
388
|
+
// side is deferred to the planner which has access to the remote contract.)
|
|
389
|
+
return {
|
|
390
|
+
fieldName: relationName,
|
|
391
|
+
toModel: targetModelName,
|
|
392
|
+
toTable: targetTable,
|
|
393
|
+
cardinality: 'N:1',
|
|
394
|
+
spaceId: relation.spaceId,
|
|
395
|
+
...(relation.namespaceId !== undefined ? { namespaceId: relation.namespaceId } : {}),
|
|
396
|
+
on: {
|
|
397
|
+
parentTable: currentSpec.tableName,
|
|
398
|
+
parentColumns,
|
|
399
|
+
childTable: targetTable,
|
|
400
|
+
childColumns: toFields,
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const targetSpec = allSpecs.get(targetModelName);
|
|
406
|
+
if (!targetSpec) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
`Relation "${currentSpec.modelName}.${relationName}" references unknown model "${targetModelName}"`,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
331
412
|
return {
|
|
332
413
|
fieldName: relationName,
|
|
333
414
|
toModel: targetModelName,
|
|
@@ -472,9 +553,10 @@ function resolveRelationNode(
|
|
|
472
553
|
relation: RelationState,
|
|
473
554
|
currentSpec: RuntimeModelSpec,
|
|
474
555
|
allSpecs: ReadonlyMap<string, RuntimeModelSpec>,
|
|
556
|
+
extensionPacks?: Record<string, ExtensionPackRef<'sql', string>>,
|
|
475
557
|
): RelationNode {
|
|
476
558
|
if (relation.kind === 'belongsTo') {
|
|
477
|
-
return lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs);
|
|
559
|
+
return lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs, extensionPacks);
|
|
478
560
|
}
|
|
479
561
|
|
|
480
562
|
if (relation.kind === 'hasMany' || relation.kind === 'hasOne') {
|
|
@@ -484,7 +566,7 @@ function resolveRelationNode(
|
|
|
484
566
|
return lowerManyToManyRelation(relationName, relation, currentSpec, allSpecs);
|
|
485
567
|
}
|
|
486
568
|
|
|
487
|
-
function
|
|
569
|
+
function lowerLocalForeignKeyNode(
|
|
488
570
|
spec: RuntimeModelSpec,
|
|
489
571
|
targetSpec: RuntimeModelSpec,
|
|
490
572
|
foreignKey: {
|
|
@@ -516,11 +598,74 @@ function lowerForeignKeyNode(
|
|
|
516
598
|
};
|
|
517
599
|
}
|
|
518
600
|
|
|
601
|
+
function lowerCrossSpaceForeignKeyNode(
|
|
602
|
+
spec: RuntimeModelSpec,
|
|
603
|
+
foreignKey: {
|
|
604
|
+
readonly fields: readonly string[];
|
|
605
|
+
readonly targetFields: readonly string[];
|
|
606
|
+
readonly targetModel: string;
|
|
607
|
+
readonly targetSpaceId: string;
|
|
608
|
+
readonly targetNamespaceId?: string;
|
|
609
|
+
readonly targetTableName?: string;
|
|
610
|
+
readonly name?: string | undefined;
|
|
611
|
+
readonly onDelete?: ForeignKeyConstraint['onDelete'] | undefined;
|
|
612
|
+
readonly onUpdate?: ForeignKeyConstraint['onUpdate'] | undefined;
|
|
613
|
+
readonly constraint?: boolean | undefined;
|
|
614
|
+
readonly index?: boolean | undefined;
|
|
615
|
+
},
|
|
616
|
+
): ForeignKeyNode {
|
|
617
|
+
return {
|
|
618
|
+
columns: mapFieldNamesToColumnNames(spec.modelName, foreignKey.fields, spec.fieldToColumn),
|
|
619
|
+
references: {
|
|
620
|
+
model: foreignKey.targetModel,
|
|
621
|
+
table: foreignKey.targetTableName ?? foreignKey.targetModel.toLowerCase(),
|
|
622
|
+
columns: foreignKey.targetFields,
|
|
623
|
+
...(foreignKey.targetNamespaceId !== undefined
|
|
624
|
+
? { namespaceId: foreignKey.targetNamespaceId }
|
|
625
|
+
: {}),
|
|
626
|
+
spaceId: foreignKey.targetSpaceId,
|
|
627
|
+
},
|
|
628
|
+
...(foreignKey.name ? { name: foreignKey.name } : {}),
|
|
629
|
+
...(foreignKey.onDelete ? { onDelete: foreignKey.onDelete } : {}),
|
|
630
|
+
...(foreignKey.onUpdate ? { onUpdate: foreignKey.onUpdate } : {}),
|
|
631
|
+
...(foreignKey.constraint !== undefined ? { constraint: foreignKey.constraint } : {}),
|
|
632
|
+
...(foreignKey.index !== undefined ? { index: foreignKey.index } : {}),
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function assertKnownExtensionPack(
|
|
637
|
+
extensionPacks: Record<string, ExtensionPackRef<'sql', string>> | undefined,
|
|
638
|
+
spaceId: string,
|
|
639
|
+
context: string,
|
|
640
|
+
): void {
|
|
641
|
+
if (extensionPacks !== undefined && Object.hasOwn(extensionPacks, spaceId)) {
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
throw new Error(
|
|
645
|
+
`${context} references contract space "${spaceId}" but "${spaceId}" is not declared in extensionPacks. Add the pack to extensionPacks.`,
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
|
|
519
649
|
function resolveForeignKeyNodes(
|
|
520
650
|
spec: RuntimeModelSpec,
|
|
521
651
|
allSpecs: ReadonlyMap<string, RuntimeModelSpec>,
|
|
652
|
+
extensionPacks?: Record<string, ExtensionPackRef<'sql', string>>,
|
|
522
653
|
): readonly ForeignKeyNode[] {
|
|
523
654
|
const relationForeignKeys = resolveRelationForeignKeys(spec, allSpecs).map((foreignKey) => {
|
|
655
|
+
// F-relfk: relation-derived FKs for cross-space targets carry targetSpaceId;
|
|
656
|
+
// route them through the cross-space path, just like explicit sql() FKs.
|
|
657
|
+
if (foreignKey.targetSpaceId !== undefined) {
|
|
658
|
+
assertKnownExtensionPack(
|
|
659
|
+
extensionPacks,
|
|
660
|
+
foreignKey.targetSpaceId,
|
|
661
|
+
`Relation-derived foreign key on "${spec.modelName}"`,
|
|
662
|
+
);
|
|
663
|
+
return lowerCrossSpaceForeignKeyNode(spec, {
|
|
664
|
+
...foreignKey,
|
|
665
|
+
targetSpaceId: foreignKey.targetSpaceId,
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
524
669
|
const targetSpec = allSpecs.get(foreignKey.targetModel);
|
|
525
670
|
if (!targetSpec) {
|
|
526
671
|
throw new Error(
|
|
@@ -528,10 +673,22 @@ function resolveForeignKeyNodes(
|
|
|
528
673
|
);
|
|
529
674
|
}
|
|
530
675
|
|
|
531
|
-
return
|
|
676
|
+
return lowerLocalForeignKeyNode(spec, targetSpec, foreignKey);
|
|
532
677
|
});
|
|
533
678
|
|
|
534
679
|
const sqlForeignKeys = (spec.sqlSpec?.foreignKeys ?? []).map((foreignKey) => {
|
|
680
|
+
if (foreignKey.targetSpaceId !== undefined) {
|
|
681
|
+
assertKnownExtensionPack(
|
|
682
|
+
extensionPacks,
|
|
683
|
+
foreignKey.targetSpaceId,
|
|
684
|
+
`Foreign key on "${spec.modelName}"`,
|
|
685
|
+
);
|
|
686
|
+
return lowerCrossSpaceForeignKeyNode(spec, {
|
|
687
|
+
...foreignKey,
|
|
688
|
+
targetSpaceId: foreignKey.targetSpaceId,
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
535
692
|
const targetSpec = allSpecs.get(foreignKey.targetModel);
|
|
536
693
|
if (!targetSpec) {
|
|
537
694
|
throw new Error(
|
|
@@ -539,7 +696,7 @@ function resolveForeignKeyNodes(
|
|
|
539
696
|
);
|
|
540
697
|
}
|
|
541
698
|
|
|
542
|
-
return
|
|
699
|
+
return lowerLocalForeignKeyNode(spec, targetSpec, foreignKey);
|
|
543
700
|
});
|
|
544
701
|
|
|
545
702
|
return [...relationForeignKeys, ...sqlForeignKeys];
|
|
@@ -550,6 +707,7 @@ function resolveModelNode(
|
|
|
550
707
|
allSpecs: ReadonlyMap<string, RuntimeModelSpec>,
|
|
551
708
|
storageTypes: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
|
|
552
709
|
storageTypeReverseLookup: ReadonlyMap<StorageTypeInstance | PostgresEnumStorageEntry, string>,
|
|
710
|
+
extensionPacks?: Record<string, ExtensionPackRef<'sql', string>>,
|
|
553
711
|
): ModelNode {
|
|
554
712
|
const fields: FieldNode[] = [];
|
|
555
713
|
|
|
@@ -567,6 +725,11 @@ function resolveModelNode(
|
|
|
567
725
|
throw new Error(`Column name resolution failed for "${spec.modelName}.${fieldName}"`);
|
|
568
726
|
}
|
|
569
727
|
|
|
728
|
+
const enumHandle =
|
|
729
|
+
'typeRef' in fieldState && isEnumTypeHandle(fieldState.typeRef)
|
|
730
|
+
? fieldState.typeRef
|
|
731
|
+
: undefined;
|
|
732
|
+
|
|
570
733
|
fields.push({
|
|
571
734
|
fieldName,
|
|
572
735
|
columnName,
|
|
@@ -574,6 +737,7 @@ function resolveModelNode(
|
|
|
574
737
|
nullable: fieldState.nullable,
|
|
575
738
|
...(fieldState.default ? { default: fieldState.default } : {}),
|
|
576
739
|
...(fieldState.executionDefaults ? { executionDefaults: fieldState.executionDefaults } : {}),
|
|
740
|
+
...(enumHandle !== undefined ? { enumTypeHandle: enumHandle } : {}),
|
|
577
741
|
});
|
|
578
742
|
}
|
|
579
743
|
|
|
@@ -588,9 +752,9 @@ function resolveModelNode(
|
|
|
588
752
|
...ifDefined('type', index.type),
|
|
589
753
|
...ifDefined('options', index.options),
|
|
590
754
|
})) satisfies readonly IndexNode[];
|
|
591
|
-
const foreignKeys = resolveForeignKeyNodes(spec, allSpecs);
|
|
755
|
+
const foreignKeys = resolveForeignKeyNodes(spec, allSpecs, extensionPacks);
|
|
592
756
|
const relations = Object.entries(spec.relations).map(([relationName, relationBuilder]) =>
|
|
593
|
-
resolveRelationNode(relationName, relationBuilder.build(), spec, allSpecs),
|
|
757
|
+
resolveRelationNode(relationName, relationBuilder.build(), spec, allSpecs, extensionPacks),
|
|
594
758
|
);
|
|
595
759
|
|
|
596
760
|
return {
|
|
@@ -614,6 +778,7 @@ function resolveModelNode(
|
|
|
614
778
|
...(indexes.length > 0 ? { indexes } : {}),
|
|
615
779
|
...(foreignKeys.length > 0 ? { foreignKeys } : {}),
|
|
616
780
|
...(relations.length > 0 ? { relations } : {}),
|
|
781
|
+
...ifDefined('control', spec.sqlSpec?.control),
|
|
617
782
|
};
|
|
618
783
|
}
|
|
619
784
|
|
|
@@ -687,7 +852,10 @@ function collectRuntimeModelSpecs(definition: ContractInput): RuntimeCollection
|
|
|
687
852
|
};
|
|
688
853
|
}
|
|
689
854
|
|
|
690
|
-
function lowerModels(
|
|
855
|
+
function lowerModels(
|
|
856
|
+
collection: RuntimeCollection,
|
|
857
|
+
extensionPacks?: Record<string, ExtensionPackRef<'sql', string>>,
|
|
858
|
+
): readonly ModelNode[] {
|
|
691
859
|
emitTypedCrossModelFallbackWarnings(collection);
|
|
692
860
|
|
|
693
861
|
const storageTypeReverseLookup = buildStorageTypeReverseLookup(collection.storageTypes);
|
|
@@ -697,16 +865,18 @@ function lowerModels(collection: RuntimeCollection): readonly ModelNode[] {
|
|
|
697
865
|
collection.modelSpecs,
|
|
698
866
|
collection.storageTypes,
|
|
699
867
|
storageTypeReverseLookup,
|
|
868
|
+
extensionPacks,
|
|
700
869
|
),
|
|
701
870
|
);
|
|
702
871
|
}
|
|
703
872
|
|
|
704
873
|
export function buildContractDefinition(definition: ContractInput): ContractDefinition {
|
|
705
874
|
const collection = collectRuntimeModelSpecs(definition);
|
|
706
|
-
const models = lowerModels(collection);
|
|
875
|
+
const models = lowerModels(collection, definition.extensionPacks);
|
|
707
876
|
|
|
708
877
|
return {
|
|
709
878
|
target: definition.target,
|
|
879
|
+
...ifDefined('defaultControlPolicy', definition.defaultControlPolicy),
|
|
710
880
|
...(definition.extensionPacks ? { extensionPacks: definition.extensionPacks } : {}),
|
|
711
881
|
...(definition.storageHash ? { storageHash: definition.storageHash } : {}),
|
|
712
882
|
...(definition.foreignKeyDefaults ? { foreignKeyDefaults: definition.foreignKeyDefaults } : {}),
|
|
@@ -715,6 +885,9 @@ export function buildContractDefinition(definition: ContractInput): ContractDefi
|
|
|
715
885
|
: {}),
|
|
716
886
|
...(definition.namespaces ? { namespaces: definition.namespaces } : {}),
|
|
717
887
|
...(definition.createNamespace ? { createNamespace: definition.createNamespace } : {}),
|
|
888
|
+
...(definition.enums && Object.keys(definition.enums).length > 0
|
|
889
|
+
? { enums: definition.enums }
|
|
890
|
+
: {}),
|
|
718
891
|
models,
|
|
719
892
|
};
|
|
720
893
|
}
|
package/src/contract-types.ts
CHANGED
|
@@ -6,17 +6,18 @@ import type {
|
|
|
6
6
|
StorageHashBase,
|
|
7
7
|
} from '@prisma-next/contract/types';
|
|
8
8
|
import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';
|
|
9
|
+
import type { StorageType } from '@prisma-next/framework-components/ir';
|
|
9
10
|
import type { IndexTypeRegistration } from '@prisma-next/sql-contract/index-types';
|
|
10
11
|
import type {
|
|
11
12
|
ContractWithTypeMaps,
|
|
12
13
|
Index,
|
|
13
|
-
PostgresEnumStorageEntry,
|
|
14
14
|
ReferentialAction,
|
|
15
15
|
StorageTypeInstance,
|
|
16
16
|
TypeMaps,
|
|
17
17
|
} from '@prisma-next/sql-contract/types';
|
|
18
18
|
import type { UnionToIntersection } from './authoring-type-utils';
|
|
19
19
|
import type { AttributeStageIdFieldNames, FieldStateOf, ScalarFieldBuilder } from './contract-dsl';
|
|
20
|
+
import type { EnumTypeHandle } from './enum-type';
|
|
20
21
|
|
|
21
22
|
export type ExtractCodecTypesFromPack<P> = P extends { __codecTypes?: infer C }
|
|
22
23
|
? C extends Record<string, { output: unknown }>
|
|
@@ -144,10 +145,7 @@ type DefinitionNamespaces<Definition> = Definition extends {
|
|
|
144
145
|
type DefinitionTypes<Definition> = Definition extends {
|
|
145
146
|
readonly types?: unknown;
|
|
146
147
|
}
|
|
147
|
-
? Present<Definition['types']> extends Record<
|
|
148
|
-
string,
|
|
149
|
-
StorageTypeInstance | PostgresEnumStorageEntry
|
|
150
|
-
>
|
|
148
|
+
? Present<Definition['types']> extends Record<string, StorageType>
|
|
151
149
|
? Present<Definition['types']>
|
|
152
150
|
: Record<never, never>
|
|
153
151
|
: Record<never, never>;
|
|
@@ -311,10 +309,7 @@ type DescriptorTypeRef<Descriptor> = Descriptor extends {
|
|
|
311
309
|
? TypeRef
|
|
312
310
|
: undefined;
|
|
313
311
|
|
|
314
|
-
type LookupNamedStorageTypeKeyByValue<
|
|
315
|
-
Definition,
|
|
316
|
-
TypeRef extends StorageTypeInstance | PostgresEnumStorageEntry,
|
|
317
|
-
> = {
|
|
312
|
+
type LookupNamedStorageTypeKeyByValue<Definition, TypeRef extends StorageType> = {
|
|
318
313
|
[TypeName in keyof DefinitionTypes<Definition> & string]: [TypeRef] extends [
|
|
319
314
|
DefinitionTypes<Definition>[TypeName],
|
|
320
315
|
]
|
|
@@ -326,7 +321,7 @@ type LookupNamedStorageTypeKeyByValue<
|
|
|
326
321
|
|
|
327
322
|
type ResolveNamedStorageTypeKey<Definition, TypeRef> = TypeRef extends string
|
|
328
323
|
? TypeRef
|
|
329
|
-
: TypeRef extends
|
|
324
|
+
: TypeRef extends StorageType
|
|
330
325
|
? [LookupNamedStorageTypeKeyByValue<Definition, TypeRef>] extends [never]
|
|
331
326
|
? string
|
|
332
327
|
: LookupNamedStorageTypeKeyByValue<Definition, TypeRef>
|
|
@@ -339,17 +334,39 @@ type ResolveNamedStorageType<Definition, TypeRef> =
|
|
|
339
334
|
: StorageTypeInstance
|
|
340
335
|
: StorageTypeInstance;
|
|
341
336
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
337
|
+
// An enum-typed field carries its `EnumTypeHandle` (an object with a `codecId`
|
|
338
|
+
// and `nativeType`, but no `kind`) as the field's `typeRef`. It is neither a
|
|
339
|
+
// string nor a registered `StorageType`, so the named-type lookup cannot reach
|
|
340
|
+
// it; `EnumFieldHandle` short-circuits the resolvers to read codec + native type
|
|
341
|
+
// straight off the handle, with no column type-ref (the enum name is carried
|
|
342
|
+
// elsewhere). The `[...] extends [never]` guard excludes plain column fields,
|
|
343
|
+
// whose `typeRef` is `never`.
|
|
344
|
+
type EnumFieldHandle<FieldState> = [FieldTypeRefOf<FieldState>] extends [never]
|
|
345
|
+
? never
|
|
346
|
+
: FieldTypeRefOf<FieldState> extends EnumTypeHandle
|
|
347
|
+
? FieldTypeRefOf<FieldState>
|
|
348
|
+
: never;
|
|
347
349
|
|
|
348
|
-
type
|
|
350
|
+
type EnumHandleDescriptor<Handle> = Handle extends {
|
|
351
|
+
readonly codecId: infer CodecId extends string;
|
|
352
|
+
readonly nativeType: infer NativeType extends string;
|
|
353
|
+
}
|
|
354
|
+
? { readonly codecId: CodecId; readonly nativeType: NativeType }
|
|
355
|
+
: never;
|
|
356
|
+
|
|
357
|
+
type ResolveFieldDescriptor<Definition, FieldState> = [EnumFieldHandle<FieldState>] extends [never]
|
|
358
|
+
? [FieldDescriptorOf<FieldState>] extends [never]
|
|
359
|
+
? ResolveNamedStorageType<Definition, FieldTypeRefOf<FieldState>>
|
|
360
|
+
: FieldDescriptorOf<FieldState>
|
|
361
|
+
: EnumHandleDescriptor<EnumFieldHandle<FieldState>>;
|
|
362
|
+
|
|
363
|
+
type ResolveFieldColumnTypeRef<Definition, FieldState> = [EnumFieldHandle<FieldState>] extends [
|
|
349
364
|
never,
|
|
350
365
|
]
|
|
351
|
-
?
|
|
352
|
-
|
|
366
|
+
? [FieldTypeRefOf<FieldState>] extends [never]
|
|
367
|
+
? DescriptorTypeRef<FieldDescriptorOf<FieldState>>
|
|
368
|
+
: ResolveNamedStorageTypeKey<Definition, FieldTypeRefOf<FieldState>>
|
|
369
|
+
: undefined;
|
|
353
370
|
|
|
354
371
|
type ResolveFieldColumnTypeParams<Definition, FieldState> = [
|
|
355
372
|
ResolveFieldColumnTypeRef<Definition, FieldState>,
|
|
@@ -540,6 +557,7 @@ type BuiltStorageTables<Definition> = {
|
|
|
540
557
|
};
|
|
541
558
|
readonly target: {
|
|
542
559
|
readonly namespaceId: NamespaceId;
|
|
560
|
+
readonly spaceId?: string;
|
|
543
561
|
readonly tableName: string;
|
|
544
562
|
readonly columns: readonly string[];
|
|
545
563
|
};
|
|
@@ -563,10 +581,41 @@ type BuiltStorageTables<Definition> = {
|
|
|
563
581
|
: Record<string, never>);
|
|
564
582
|
};
|
|
565
583
|
|
|
584
|
+
type DefinitionEnums<Definition> = Definition extends {
|
|
585
|
+
readonly enums?: infer E;
|
|
586
|
+
}
|
|
587
|
+
? Present<E> extends Record<string, EnumTypeHandle>
|
|
588
|
+
? // A bare `Record<string, EnumTypeHandle>` (no literal keys) is the
|
|
589
|
+
// widened default for a contract authored without enums; treat it as
|
|
590
|
+
// empty so `db.enums` carries only literally-authored enums.
|
|
591
|
+
string extends keyof Present<E>
|
|
592
|
+
? Record<never, never>
|
|
593
|
+
: Present<E>
|
|
594
|
+
: Record<never, never>
|
|
595
|
+
: Record<never, never>;
|
|
596
|
+
|
|
597
|
+
type EnumHandleAccessorType<Handle> =
|
|
598
|
+
Handle extends EnumTypeHandle<infer _Name, infer Values, infer Names, infer MembersMap>
|
|
599
|
+
? {
|
|
600
|
+
readonly values: Values;
|
|
601
|
+
readonly names: Names;
|
|
602
|
+
readonly members: MembersMap;
|
|
603
|
+
has(v: Values[number]): boolean;
|
|
604
|
+
nameOf(v: Values[number]): string | undefined;
|
|
605
|
+
ordinalOf(v: Values[number]): number;
|
|
606
|
+
}
|
|
607
|
+
: never;
|
|
608
|
+
|
|
609
|
+
type BuiltEnumAccessors<Definition> = {
|
|
610
|
+
readonly [K in keyof DefinitionEnums<Definition>]: EnumHandleAccessorType<
|
|
611
|
+
DefinitionEnums<Definition>[K]
|
|
612
|
+
>;
|
|
613
|
+
};
|
|
614
|
+
|
|
566
615
|
type BuiltDocumentScopedTypes<Definition> = {
|
|
567
|
-
readonly [K in keyof DefinitionTypes<Definition> as DefinitionTypes<Definition>[K] extends
|
|
568
|
-
?
|
|
569
|
-
:
|
|
616
|
+
readonly [K in keyof DefinitionTypes<Definition> as DefinitionTypes<Definition>[K] extends StorageTypeInstance
|
|
617
|
+
? K
|
|
618
|
+
: never]: DefinitionTypes<Definition>[K];
|
|
570
619
|
};
|
|
571
620
|
|
|
572
621
|
type BuiltDomain<Definition> =
|
|
@@ -586,19 +635,20 @@ type BuiltStorage<Definition> = {
|
|
|
586
635
|
readonly types?: BuiltDocumentScopedTypes<Definition>;
|
|
587
636
|
// The primary namespace key is target-specific: Postgres uses `public` (the
|
|
588
637
|
// default schema), all other SQL targets use `__unbound__`. The namespace
|
|
589
|
-
// carries the narrowed
|
|
638
|
+
// carries the narrowed `entries.table` shape so downstream DSL surfaces keep
|
|
590
639
|
// literal-keyed access without an optional-narrowing dance. The shape is
|
|
591
640
|
// described inline (rather than intersecting with `SqlStorage['namespaces']`)
|
|
592
641
|
// so its `Readonly<Record<string, Namespace>>` index signature doesn't
|
|
593
|
-
// collapse
|
|
594
|
-
//
|
|
595
|
-
//
|
|
642
|
+
// collapse slot keys to `string`. The literal object is still structurally
|
|
643
|
+
// assignable to `SqlStorage['namespaces']` because every value satisfies the
|
|
644
|
+
// framework `Namespace` interface.
|
|
596
645
|
readonly namespaces: {
|
|
597
646
|
readonly [K in DefaultStorageNamespaceId<Definition>]: {
|
|
598
647
|
readonly id: K;
|
|
599
648
|
readonly kind: string;
|
|
600
|
-
readonly
|
|
601
|
-
|
|
649
|
+
readonly entries: {
|
|
650
|
+
readonly table: BuiltStorageTables<Definition>;
|
|
651
|
+
};
|
|
602
652
|
};
|
|
603
653
|
} & {
|
|
604
654
|
readonly [Ns in Exclude<
|
|
@@ -607,35 +657,60 @@ type BuiltStorage<Definition> = {
|
|
|
607
657
|
>]: {
|
|
608
658
|
readonly id: Ns;
|
|
609
659
|
readonly kind: string;
|
|
610
|
-
readonly
|
|
611
|
-
|
|
660
|
+
readonly entries: {
|
|
661
|
+
readonly table: Record<never, never>;
|
|
662
|
+
};
|
|
612
663
|
};
|
|
613
664
|
};
|
|
614
665
|
};
|
|
615
666
|
|
|
616
|
-
|
|
667
|
+
// The enum value union for an enum-typed field, or `never` for a non-enum
|
|
668
|
+
// field. The field's `typeRef` carries the authored `EnumTypeHandle`, whose
|
|
669
|
+
// `Values` tuple preserves the literal member values (text or numeric).
|
|
670
|
+
type EnumValueUnion<FieldState> = [FieldTypeRefOf<FieldState>] extends [
|
|
671
|
+
EnumTypeHandle<string, infer Values>,
|
|
672
|
+
]
|
|
673
|
+
? readonly unknown[] extends Values
|
|
674
|
+
? never
|
|
675
|
+
: Values[number]
|
|
676
|
+
: never;
|
|
677
|
+
|
|
678
|
+
// The codec's `output` / `input` JS type for a field's column, before
|
|
679
|
+
// nullability. `unknown` when the codec is not in the definition's codec map.
|
|
680
|
+
type CodecChannelType<
|
|
617
681
|
Definition,
|
|
618
682
|
ModelName extends ModelNames<Definition>,
|
|
619
683
|
FieldName extends ModelFieldNames<Definition, ModelName>,
|
|
684
|
+
Channel extends 'output' | 'input',
|
|
685
|
+
> = ModelStorageColumn<Definition, ModelName, FieldName>['codecId'] extends infer Id extends
|
|
686
|
+
keyof CodecTypesFromDefinition<Definition>
|
|
687
|
+
? CodecTypesFromDefinition<Definition>[Id] extends { readonly [K in Channel]: infer T }
|
|
688
|
+
? T
|
|
689
|
+
: unknown
|
|
690
|
+
: unknown;
|
|
691
|
+
|
|
692
|
+
// A field's read/write JS type: the enum value union when the field is
|
|
693
|
+
// enum-typed, otherwise the codec channel type, with column nullability applied.
|
|
694
|
+
type FieldChannelType<
|
|
695
|
+
Definition,
|
|
696
|
+
ModelName extends ModelNames<Definition>,
|
|
697
|
+
FieldName extends ModelFieldNames<Definition, ModelName>,
|
|
698
|
+
Channel extends 'output' | 'input',
|
|
620
699
|
> =
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
: unknown
|
|
630
|
-
: unknown
|
|
631
|
-
: unknown;
|
|
632
|
-
|
|
633
|
-
type FieldOutputTypes<Definition> = {
|
|
700
|
+
| ([EnumValueUnion<ModelFieldState<Definition, ModelName, FieldName>>] extends [never]
|
|
701
|
+
? CodecChannelType<Definition, ModelName, FieldName, Channel>
|
|
702
|
+
: EnumValueUnion<ModelFieldState<Definition, ModelName, FieldName>>)
|
|
703
|
+
| (FieldNullableOf<ModelFieldState<Definition, ModelName, FieldName>> extends true
|
|
704
|
+
? null
|
|
705
|
+
: never);
|
|
706
|
+
|
|
707
|
+
type FieldChannelTypes<Definition, Channel extends 'output' | 'input'> = {
|
|
634
708
|
readonly [ModelName in ModelNames<Definition>]: {
|
|
635
|
-
readonly [FieldName in ModelFieldNames<Definition, ModelName>]:
|
|
709
|
+
readonly [FieldName in ModelFieldNames<Definition, ModelName>]: FieldChannelType<
|
|
636
710
|
Definition,
|
|
637
711
|
ModelName,
|
|
638
|
-
FieldName
|
|
712
|
+
FieldName,
|
|
713
|
+
Channel
|
|
639
714
|
>;
|
|
640
715
|
};
|
|
641
716
|
};
|
|
@@ -649,11 +724,12 @@ export type SqlContractResult<Definition> = ContractWithTypeMaps<
|
|
|
649
724
|
? Record<string, never>
|
|
650
725
|
: DefinitionExtensionPacks<Definition>;
|
|
651
726
|
readonly capabilities: DerivedCapabilities<Definition>;
|
|
727
|
+
readonly enumAccessors: BuiltEnumAccessors<Definition>;
|
|
652
728
|
},
|
|
653
729
|
TypeMaps<
|
|
654
730
|
CodecTypesFromDefinition<Definition>,
|
|
655
731
|
Record<string, never>,
|
|
656
|
-
|
|
657
|
-
|
|
732
|
+
FieldChannelTypes<Definition, 'output'>,
|
|
733
|
+
FieldChannelTypes<Definition, 'input'>
|
|
658
734
|
>
|
|
659
735
|
>;
|