@prisma-next/sql-contract-ts 0.13.0-dev.4 → 0.13.0-dev.40
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 +12 -9
- package/dist/{build-contract-C-x2pfu4.mjs → build-contract-eFuxF46U.mjs} +19 -50
- package/dist/build-contract-eFuxF46U.mjs.map +1 -0
- package/dist/config-types.mjs +1 -1
- package/dist/contract-builder.d.mts +121 -63
- package/dist/contract-builder.d.mts.map +1 -1
- package/dist/contract-builder.mjs +43 -9
- package/dist/contract-builder.mjs.map +1 -1
- package/package.json +10 -10
- package/src/build-contract.ts +24 -88
- package/src/contract-builder.ts +47 -31
- package/src/contract-definition.ts +3 -23
- package/src/contract-dsl.ts +41 -17
- package/src/contract-lowering.ts +12 -24
- package/src/contract-types.ts +135 -32
- package/src/contract-warnings.ts +3 -6
- package/src/enum-type.ts +98 -28
- package/src/exports/contract-builder.ts +10 -2
- package/dist/build-contract-C-x2pfu4.mjs.map +0 -1
package/src/contract-lowering.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import type { ColumnTypeDescriptor } from '@prisma-next/framework-components/codec';
|
|
2
2
|
import type { ExtensionPackRef } from '@prisma-next/framework-components/components';
|
|
3
|
-
import {
|
|
4
|
-
isPostgresEnumStorageEntry,
|
|
5
|
-
type PostgresEnumStorageEntry,
|
|
6
|
-
type StorageTypeInstance,
|
|
7
|
-
} from '@prisma-next/sql-contract/types';
|
|
3
|
+
import type { StorageTypeInstance } from '@prisma-next/sql-contract/types';
|
|
8
4
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
9
5
|
import type {
|
|
10
6
|
ContractDefinition,
|
|
@@ -59,15 +55,15 @@ type RuntimeModelSpec = {
|
|
|
59
55
|
};
|
|
60
56
|
|
|
61
57
|
type RuntimeCollection = {
|
|
62
|
-
readonly storageTypes: Record<string, StorageTypeInstance
|
|
58
|
+
readonly storageTypes: Record<string, StorageTypeInstance>;
|
|
63
59
|
readonly models: Record<string, RuntimeModel>;
|
|
64
60
|
readonly modelSpecs: ReadonlyMap<string, RuntimeModelSpec>;
|
|
65
61
|
};
|
|
66
62
|
|
|
67
63
|
function buildStorageTypeReverseLookup(
|
|
68
|
-
storageTypes: Record<string, StorageTypeInstance
|
|
69
|
-
): ReadonlyMap<StorageTypeInstance
|
|
70
|
-
const lookup = new Map<StorageTypeInstance
|
|
64
|
+
storageTypes: Record<string, StorageTypeInstance>,
|
|
65
|
+
): ReadonlyMap<StorageTypeInstance, string> {
|
|
66
|
+
const lookup = new Map<StorageTypeInstance, string>();
|
|
71
67
|
for (const [key, instance] of Object.entries(storageTypes)) {
|
|
72
68
|
lookup.set(instance, key);
|
|
73
69
|
}
|
|
@@ -78,8 +74,8 @@ function resolveFieldDescriptor(
|
|
|
78
74
|
modelName: string,
|
|
79
75
|
fieldName: string,
|
|
80
76
|
fieldState: FieldStateOf<ScalarFieldBuilder>,
|
|
81
|
-
storageTypes: Record<string, StorageTypeInstance
|
|
82
|
-
storageTypeReverseLookup: ReadonlyMap<StorageTypeInstance
|
|
77
|
+
storageTypes: Record<string, StorageTypeInstance>,
|
|
78
|
+
storageTypeReverseLookup: ReadonlyMap<StorageTypeInstance, string>,
|
|
83
79
|
): ColumnTypeDescriptor {
|
|
84
80
|
if ('descriptor' in fieldState && fieldState.descriptor) {
|
|
85
81
|
return fieldState.descriptor;
|
|
@@ -96,9 +92,7 @@ function resolveFieldDescriptor(
|
|
|
96
92
|
const typeRef =
|
|
97
93
|
typeof fieldState.typeRef === 'string'
|
|
98
94
|
? fieldState.typeRef
|
|
99
|
-
: storageTypeReverseLookup.get(
|
|
100
|
-
fieldState.typeRef as StorageTypeInstance | PostgresEnumStorageEntry,
|
|
101
|
-
);
|
|
95
|
+
: storageTypeReverseLookup.get(fieldState.typeRef as StorageTypeInstance);
|
|
102
96
|
|
|
103
97
|
if (!typeRef) {
|
|
104
98
|
throw new Error(
|
|
@@ -113,11 +107,8 @@ function resolveFieldDescriptor(
|
|
|
113
107
|
);
|
|
114
108
|
}
|
|
115
109
|
|
|
116
|
-
const codecId = isPostgresEnumStorageEntry(referencedType)
|
|
117
|
-
? referencedType.codecId
|
|
118
|
-
: referencedType.codecId;
|
|
119
110
|
return {
|
|
120
|
-
codecId,
|
|
111
|
+
codecId: referencedType.codecId,
|
|
121
112
|
nativeType: referencedType.nativeType,
|
|
122
113
|
typeRef,
|
|
123
114
|
};
|
|
@@ -705,8 +696,8 @@ function resolveForeignKeyNodes(
|
|
|
705
696
|
function resolveModelNode(
|
|
706
697
|
spec: RuntimeModelSpec,
|
|
707
698
|
allSpecs: ReadonlyMap<string, RuntimeModelSpec>,
|
|
708
|
-
storageTypes: Record<string, StorageTypeInstance
|
|
709
|
-
storageTypeReverseLookup: ReadonlyMap<StorageTypeInstance
|
|
699
|
+
storageTypes: Record<string, StorageTypeInstance>,
|
|
700
|
+
storageTypeReverseLookup: ReadonlyMap<StorageTypeInstance, string>,
|
|
710
701
|
extensionPacks?: Record<string, ExtensionPackRef<'sql', string>>,
|
|
711
702
|
): ModelNode {
|
|
712
703
|
const fields: FieldNode[] = [];
|
|
@@ -783,10 +774,7 @@ function resolveModelNode(
|
|
|
783
774
|
}
|
|
784
775
|
|
|
785
776
|
function collectRuntimeModelSpecs(definition: ContractInput): RuntimeCollection {
|
|
786
|
-
const storageTypes = { ...(definition.types ?? {}) } as Record<
|
|
787
|
-
string,
|
|
788
|
-
StorageTypeInstance | PostgresEnumStorageEntry
|
|
789
|
-
>;
|
|
777
|
+
const storageTypes = { ...(definition.types ?? {}) } as Record<string, StorageTypeInstance>;
|
|
790
778
|
const models = { ...(definition.models ?? {}) } as Record<string, RuntimeModel>;
|
|
791
779
|
|
|
792
780
|
emitTypedNamedTypeFallbackWarnings(models, storageTypes);
|
package/src/contract-types.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ColumnDefault,
|
|
3
3
|
Contract,
|
|
4
|
+
ContractEnum,
|
|
4
5
|
ContractRelation,
|
|
6
|
+
ContractValueObject,
|
|
5
7
|
NamespaceId,
|
|
6
8
|
StorageHashBase,
|
|
7
9
|
} from '@prisma-next/contract/types';
|
|
@@ -17,6 +19,7 @@ import type {
|
|
|
17
19
|
} from '@prisma-next/sql-contract/types';
|
|
18
20
|
import type { UnionToIntersection } from './authoring-type-utils';
|
|
19
21
|
import type { AttributeStageIdFieldNames, FieldStateOf, ScalarFieldBuilder } from './contract-dsl';
|
|
22
|
+
import type { EnumTypeHandle } from './enum-type';
|
|
20
23
|
|
|
21
24
|
export type ExtractCodecTypesFromPack<P> = P extends { __codecTypes?: infer C }
|
|
22
25
|
? C extends Record<string, { output: unknown }>
|
|
@@ -333,17 +336,39 @@ type ResolveNamedStorageType<Definition, TypeRef> =
|
|
|
333
336
|
: StorageTypeInstance
|
|
334
337
|
: StorageTypeInstance;
|
|
335
338
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
339
|
+
// An enum-typed field carries its `EnumTypeHandle` (an object with a `codecId`
|
|
340
|
+
// and `nativeType`, but no `kind`) as the field's `typeRef`. It is neither a
|
|
341
|
+
// string nor a registered `StorageType`, so the named-type lookup cannot reach
|
|
342
|
+
// it; `EnumFieldHandle` short-circuits the resolvers to read codec + native type
|
|
343
|
+
// straight off the handle, with no column type-ref (the enum name is carried
|
|
344
|
+
// elsewhere). The `[...] extends [never]` guard excludes plain column fields,
|
|
345
|
+
// whose `typeRef` is `never`.
|
|
346
|
+
type EnumFieldHandle<FieldState> = [FieldTypeRefOf<FieldState>] extends [never]
|
|
347
|
+
? never
|
|
348
|
+
: FieldTypeRefOf<FieldState> extends EnumTypeHandle
|
|
349
|
+
? FieldTypeRefOf<FieldState>
|
|
350
|
+
: never;
|
|
351
|
+
|
|
352
|
+
type EnumHandleDescriptor<Handle> = Handle extends {
|
|
353
|
+
readonly codecId: infer CodecId extends string;
|
|
354
|
+
readonly nativeType: infer NativeType extends string;
|
|
355
|
+
}
|
|
356
|
+
? { readonly codecId: CodecId; readonly nativeType: NativeType }
|
|
357
|
+
: never;
|
|
358
|
+
|
|
359
|
+
type ResolveFieldDescriptor<Definition, FieldState> = [EnumFieldHandle<FieldState>] extends [never]
|
|
360
|
+
? [FieldDescriptorOf<FieldState>] extends [never]
|
|
361
|
+
? ResolveNamedStorageType<Definition, FieldTypeRefOf<FieldState>>
|
|
362
|
+
: FieldDescriptorOf<FieldState>
|
|
363
|
+
: EnumHandleDescriptor<EnumFieldHandle<FieldState>>;
|
|
341
364
|
|
|
342
|
-
type ResolveFieldColumnTypeRef<Definition, FieldState> = [
|
|
365
|
+
type ResolveFieldColumnTypeRef<Definition, FieldState> = [EnumFieldHandle<FieldState>] extends [
|
|
343
366
|
never,
|
|
344
367
|
]
|
|
345
|
-
?
|
|
346
|
-
|
|
368
|
+
? [FieldTypeRefOf<FieldState>] extends [never]
|
|
369
|
+
? DescriptorTypeRef<FieldDescriptorOf<FieldState>>
|
|
370
|
+
: ResolveNamedStorageTypeKey<Definition, FieldTypeRefOf<FieldState>>
|
|
371
|
+
: undefined;
|
|
347
372
|
|
|
348
373
|
type ResolveFieldColumnTypeParams<Definition, FieldState> = [
|
|
349
374
|
ResolveFieldColumnTypeRef<Definition, FieldState>,
|
|
@@ -558,6 +583,37 @@ type BuiltStorageTables<Definition> = {
|
|
|
558
583
|
: Record<string, never>);
|
|
559
584
|
};
|
|
560
585
|
|
|
586
|
+
type DefinitionEnums<Definition> = Definition extends {
|
|
587
|
+
readonly enums?: infer E;
|
|
588
|
+
}
|
|
589
|
+
? Present<E> extends Record<string, EnumTypeHandle>
|
|
590
|
+
? // A bare `Record<string, EnumTypeHandle>` (no literal keys) is the
|
|
591
|
+
// widened default for a contract authored without enums; treat it as
|
|
592
|
+
// empty so `db.enums` carries only literally-authored enums.
|
|
593
|
+
string extends keyof Present<E>
|
|
594
|
+
? Record<never, never>
|
|
595
|
+
: Present<E>
|
|
596
|
+
: Record<never, never>
|
|
597
|
+
: Record<never, never>;
|
|
598
|
+
|
|
599
|
+
type EnumHandleAccessorType<Handle> =
|
|
600
|
+
Handle extends EnumTypeHandle<infer _Name, infer Values, infer Names, infer MembersMap>
|
|
601
|
+
? {
|
|
602
|
+
readonly values: Values;
|
|
603
|
+
readonly names: Names;
|
|
604
|
+
readonly members: MembersMap;
|
|
605
|
+
has(v: Values[number]): boolean;
|
|
606
|
+
nameOf(v: Values[number]): string | undefined;
|
|
607
|
+
ordinalOf(v: Values[number]): number;
|
|
608
|
+
}
|
|
609
|
+
: never;
|
|
610
|
+
|
|
611
|
+
type BuiltEnumAccessors<Definition> = {
|
|
612
|
+
readonly [K in keyof DefinitionEnums<Definition>]: EnumHandleAccessorType<
|
|
613
|
+
DefinitionEnums<Definition>[K]
|
|
614
|
+
>;
|
|
615
|
+
};
|
|
616
|
+
|
|
561
617
|
type BuiltDocumentScopedTypes<Definition> = {
|
|
562
618
|
readonly [K in keyof DefinitionTypes<Definition> as DefinitionTypes<Definition>[K] extends StorageTypeInstance
|
|
563
619
|
? K
|
|
@@ -573,6 +629,17 @@ type BuiltDomain<Definition> =
|
|
|
573
629
|
};
|
|
574
630
|
};
|
|
575
631
|
|
|
632
|
+
// Per-namespace domain entry carrying the precise per-model field/storage shapes
|
|
633
|
+
// for DSL inference. Modelled as an index signature (rather than enumerating
|
|
634
|
+
// namespace ids) so that any namespace coordinate resolves the full model map,
|
|
635
|
+
// matching how the authoring path lumps every model under the default storage
|
|
636
|
+
// namespace.
|
|
637
|
+
type BuiltDomainNamespace<Definition> = {
|
|
638
|
+
readonly models: BuiltModels<Definition>;
|
|
639
|
+
readonly valueObjects?: Record<string, ContractValueObject>;
|
|
640
|
+
readonly enum?: Record<string, ContractEnum>;
|
|
641
|
+
};
|
|
642
|
+
|
|
576
643
|
type DefaultStorageNamespaceId<Definition> =
|
|
577
644
|
DefinitionTargetId<Definition> extends 'postgres' ? 'public' : '__unbound__';
|
|
578
645
|
|
|
@@ -610,47 +677,83 @@ type BuiltStorage<Definition> = {
|
|
|
610
677
|
};
|
|
611
678
|
};
|
|
612
679
|
|
|
613
|
-
|
|
680
|
+
// The enum value union for an enum-typed field, or `never` for a non-enum
|
|
681
|
+
// field. The field's `typeRef` carries the authored `EnumTypeHandle`, whose
|
|
682
|
+
// `Values` tuple preserves the literal member values (text or numeric).
|
|
683
|
+
type EnumValueUnion<FieldState> = [FieldTypeRefOf<FieldState>] extends [
|
|
684
|
+
EnumTypeHandle<string, infer Values>,
|
|
685
|
+
]
|
|
686
|
+
? readonly unknown[] extends Values
|
|
687
|
+
? never
|
|
688
|
+
: Values[number]
|
|
689
|
+
: never;
|
|
690
|
+
|
|
691
|
+
// The codec's `output` / `input` JS type for a field's column, before
|
|
692
|
+
// nullability. `unknown` when the codec is not in the definition's codec map.
|
|
693
|
+
type CodecChannelType<
|
|
614
694
|
Definition,
|
|
615
695
|
ModelName extends ModelNames<Definition>,
|
|
616
696
|
FieldName extends ModelFieldNames<Definition, ModelName>,
|
|
697
|
+
Channel extends 'output' | 'input',
|
|
698
|
+
> = ModelStorageColumn<Definition, ModelName, FieldName>['codecId'] extends infer Id extends
|
|
699
|
+
keyof CodecTypesFromDefinition<Definition>
|
|
700
|
+
? CodecTypesFromDefinition<Definition>[Id] extends { readonly [K in Channel]: infer T }
|
|
701
|
+
? T
|
|
702
|
+
: unknown
|
|
703
|
+
: unknown;
|
|
704
|
+
|
|
705
|
+
// A field's read/write JS type: the enum value union when the field is
|
|
706
|
+
// enum-typed, otherwise the codec channel type, with column nullability applied.
|
|
707
|
+
type FieldChannelType<
|
|
708
|
+
Definition,
|
|
709
|
+
ModelName extends ModelNames<Definition>,
|
|
710
|
+
FieldName extends ModelFieldNames<Definition, ModelName>,
|
|
711
|
+
Channel extends 'output' | 'input',
|
|
617
712
|
> =
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
type
|
|
631
|
-
readonly [
|
|
632
|
-
readonly [
|
|
633
|
-
Definition,
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
713
|
+
| ([EnumValueUnion<ModelFieldState<Definition, ModelName, FieldName>>] extends [never]
|
|
714
|
+
? CodecChannelType<Definition, ModelName, FieldName, Channel>
|
|
715
|
+
: EnumValueUnion<ModelFieldState<Definition, ModelName, FieldName>>)
|
|
716
|
+
| (FieldNullableOf<ModelFieldState<Definition, ModelName, FieldName>> extends true
|
|
717
|
+
? null
|
|
718
|
+
: never);
|
|
719
|
+
|
|
720
|
+
// Nested by namespace coordinate (`{ [ns]: { [model]: { [field]: type } } }`)
|
|
721
|
+
// to mirror the emitter's namespace-nested `FieldOutputTypes` (and the
|
|
722
|
+
// `TypeMaps` constraint). The TS authoring path lumps every model under the
|
|
723
|
+
// target's default storage namespace (see `BuiltStorage`), so the per-model
|
|
724
|
+
// field-type map nests under that same coordinate.
|
|
725
|
+
type FieldChannelTypes<Definition, Channel extends 'output' | 'input'> = {
|
|
726
|
+
readonly [Ns in DefaultStorageNamespaceId<Definition>]: {
|
|
727
|
+
readonly [ModelName in ModelNames<Definition>]: {
|
|
728
|
+
readonly [FieldName in ModelFieldNames<Definition, ModelName>]: FieldChannelType<
|
|
729
|
+
Definition,
|
|
730
|
+
ModelName,
|
|
731
|
+
FieldName,
|
|
732
|
+
Channel
|
|
733
|
+
>;
|
|
734
|
+
};
|
|
637
735
|
};
|
|
638
736
|
};
|
|
639
737
|
|
|
640
738
|
export type SqlContractResult<Definition> = ContractWithTypeMaps<
|
|
641
|
-
Contract<BuiltStorage<Definition
|
|
739
|
+
Omit<Contract<BuiltStorage<Definition>>, 'domain'> & {
|
|
642
740
|
readonly target: DefinitionTargetId<Definition>;
|
|
643
741
|
readonly targetFamily: 'sql';
|
|
644
|
-
} & {
|
|
742
|
+
} & {
|
|
743
|
+
readonly domain: {
|
|
744
|
+
readonly namespaces: Readonly<Record<string, BuiltDomainNamespace<Definition>>>;
|
|
745
|
+
} & BuiltDomain<Definition>;
|
|
746
|
+
} & {
|
|
645
747
|
readonly extensionPacks: keyof DefinitionExtensionPacks<Definition> extends never
|
|
646
748
|
? Record<string, never>
|
|
647
749
|
: DefinitionExtensionPacks<Definition>;
|
|
648
750
|
readonly capabilities: DerivedCapabilities<Definition>;
|
|
751
|
+
readonly enumAccessors: BuiltEnumAccessors<Definition>;
|
|
649
752
|
},
|
|
650
753
|
TypeMaps<
|
|
651
754
|
CodecTypesFromDefinition<Definition>,
|
|
652
755
|
Record<string, never>,
|
|
653
|
-
|
|
654
|
-
|
|
756
|
+
FieldChannelTypes<Definition, 'output'>,
|
|
757
|
+
FieldChannelTypes<Definition, 'input'>
|
|
655
758
|
>
|
|
656
759
|
>;
|
package/src/contract-warnings.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
PostgresEnumStorageEntry,
|
|
3
|
-
StorageTypeInstance,
|
|
4
|
-
} from '@prisma-next/sql-contract/types';
|
|
1
|
+
import type { StorageTypeInstance } from '@prisma-next/sql-contract/types';
|
|
5
2
|
import {
|
|
6
3
|
type ContractModelBuilder,
|
|
7
4
|
type ModelAttributesSpec,
|
|
@@ -28,7 +25,7 @@ type RuntimeModelSpec = {
|
|
|
28
25
|
};
|
|
29
26
|
|
|
30
27
|
type RuntimeCollection = {
|
|
31
|
-
readonly storageTypes: Record<string, StorageTypeInstance
|
|
28
|
+
readonly storageTypes: Record<string, StorageTypeInstance>;
|
|
32
29
|
readonly models: Record<string, RuntimeModel>;
|
|
33
30
|
readonly modelSpecs: ReadonlyMap<string, RuntimeModelSpec>;
|
|
34
31
|
};
|
|
@@ -136,7 +133,7 @@ function formatFallbackWarning(location: string, current: string, suggested: str
|
|
|
136
133
|
|
|
137
134
|
export function emitTypedNamedTypeFallbackWarnings(
|
|
138
135
|
models: Record<string, RuntimeModel>,
|
|
139
|
-
storageTypes: Record<string, StorageTypeInstance
|
|
136
|
+
storageTypes: Record<string, StorageTypeInstance>,
|
|
140
137
|
): void {
|
|
141
138
|
const warnings: string[] = [];
|
|
142
139
|
const warnedFields = new Set<string>();
|
package/src/enum-type.ts
CHANGED
|
@@ -8,24 +8,27 @@ import { blindCast } from '@prisma-next/utils/casts';
|
|
|
8
8
|
/**
|
|
9
9
|
* A single enum member produced by `member()`. The `Name` and `Value` generics
|
|
10
10
|
* are preserved as literal types so `enumType()` can carry the ordered value
|
|
11
|
-
* tuple in its return type.
|
|
11
|
+
* tuple in its return type. `Value` is whatever the codec dictates — its type
|
|
12
|
+
* is constrained at `enumType` against the codec's input type, not here.
|
|
12
13
|
*/
|
|
13
|
-
export interface EnumMember<Name extends string, Value
|
|
14
|
+
export interface EnumMember<Name extends string, Value> {
|
|
14
15
|
readonly name: Name;
|
|
15
16
|
readonly value: Value;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
|
-
* Declare an enum member. The `value` defaults to `name` when omitted.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* Declare an enum member. The `value` defaults to `name` when omitted. The
|
|
21
|
+
* value is an unconstrained literal here; `enumType` constrains it against the
|
|
22
|
+
* codec's input type. Both generics are preserved as literals so downstream
|
|
23
|
+
* `enumType` carries the value union in its type; the value is serialized to its
|
|
24
|
+
* codec string form only at lowering.
|
|
22
25
|
*/
|
|
23
26
|
export function member<const Name extends string>(name: Name): EnumMember<Name, Name>;
|
|
24
|
-
export function member<const Name extends string, const Value
|
|
27
|
+
export function member<const Name extends string, const Value>(
|
|
25
28
|
name: Name,
|
|
26
29
|
value: Value,
|
|
27
30
|
): EnumMember<Name, Value>;
|
|
28
|
-
export function member<const Name extends string, const Value
|
|
31
|
+
export function member<const Name extends string, const Value = Name>(
|
|
29
32
|
name: Name,
|
|
30
33
|
value?: Value,
|
|
31
34
|
): EnumMember<Name, Value> {
|
|
@@ -42,15 +45,15 @@ export function member<const Name extends string, const Value extends string = N
|
|
|
42
45
|
// Internal types for inferring the literal tuple from the members spread
|
|
43
46
|
// ---------------------------------------------------------------------------
|
|
44
47
|
|
|
45
|
-
type MembersToValues<Members extends readonly EnumMember<string,
|
|
48
|
+
type MembersToValues<Members extends readonly EnumMember<string, unknown>[]> = {
|
|
46
49
|
readonly [K in keyof Members]: Members[K] extends EnumMember<string, infer V> ? V : never;
|
|
47
50
|
};
|
|
48
51
|
|
|
49
|
-
type MembersToNames<Members extends readonly EnumMember<string,
|
|
50
|
-
readonly [K in keyof Members]: Members[K] extends EnumMember<infer N,
|
|
52
|
+
type MembersToNames<Members extends readonly EnumMember<string, unknown>[]> = {
|
|
53
|
+
readonly [K in keyof Members]: Members[K] extends EnumMember<infer N, unknown> ? N : never;
|
|
51
54
|
};
|
|
52
55
|
|
|
53
|
-
type MembersAccessorMap<Members extends readonly EnumMember<string,
|
|
56
|
+
type MembersAccessorMap<Members extends readonly EnumMember<string, unknown>[]> = {
|
|
54
57
|
readonly [M in Members[number] as M['name']]: M['value'];
|
|
55
58
|
};
|
|
56
59
|
|
|
@@ -80,9 +83,9 @@ export const ENUM_TYPE_HANDLE_BRAND = Symbol('EnumTypeHandle');
|
|
|
80
83
|
*/
|
|
81
84
|
export interface EnumTypeHandle<
|
|
82
85
|
Name extends string = string,
|
|
83
|
-
Values extends readonly
|
|
86
|
+
Values extends readonly unknown[] = readonly unknown[],
|
|
84
87
|
Names extends readonly string[] = readonly string[],
|
|
85
|
-
MembersMap extends Record<string,
|
|
88
|
+
MembersMap extends Record<string, unknown> = Record<string, unknown>,
|
|
86
89
|
> {
|
|
87
90
|
/** Internal brand for lowering-pipeline detection. */
|
|
88
91
|
readonly [ENUM_TYPE_HANDLE_BRAND]: true;
|
|
@@ -97,7 +100,7 @@ export interface EnumTypeHandle<
|
|
|
97
100
|
readonly nativeType: string;
|
|
98
101
|
|
|
99
102
|
/** Ordered member list for lowering (name + value pairs). */
|
|
100
|
-
readonly enumMembers: readonly { readonly name: string; readonly value:
|
|
103
|
+
readonly enumMembers: readonly { readonly name: string; readonly value: Values[number] }[];
|
|
101
104
|
|
|
102
105
|
/** Ordered literal value tuple. Declaration order is preserved. */
|
|
103
106
|
readonly values: Values;
|
|
@@ -112,19 +115,43 @@ export interface EnumTypeHandle<
|
|
|
112
115
|
readonly members: MembersMap;
|
|
113
116
|
|
|
114
117
|
/** Returns `true` if `v` is a declared member value. */
|
|
115
|
-
has(v:
|
|
118
|
+
has(v: Values[number]): boolean;
|
|
116
119
|
|
|
117
120
|
/** Returns the member name for a value, or `undefined` if not found. */
|
|
118
|
-
nameOf(v:
|
|
121
|
+
nameOf(v: Values[number]): string | undefined;
|
|
119
122
|
|
|
120
123
|
/** Returns the zero-based declaration index of a value, or `-1` if not found. */
|
|
121
|
-
ordinalOf(v:
|
|
124
|
+
ordinalOf(v: Values[number]): number;
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
// ---------------------------------------------------------------------------
|
|
125
128
|
// enumType()
|
|
126
129
|
// ---------------------------------------------------------------------------
|
|
127
130
|
|
|
131
|
+
/**
|
|
132
|
+
* A codec typemap: codecId → `{ input, output }`, the same shape the query
|
|
133
|
+
* lanes consume (e.g. `{ 'pg/text@1': { input: string }, 'pg/int4@1': { input: number } }`).
|
|
134
|
+
* The bound `enumType` wrappers supply the target pack's typemap; the core
|
|
135
|
+
* defaults to an empty map (no codec is known), so member values stay
|
|
136
|
+
* unconstrained.
|
|
137
|
+
*/
|
|
138
|
+
export type CodecTypeMap = Record<string, { readonly input?: unknown }>;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The application input type the codec dictates for an enum's member values:
|
|
142
|
+
* looks `Codec['codecId']` up in the supplied codec typemap. When the codecId
|
|
143
|
+
* isn't in the map (the core's empty default, or an unknown codec) the input is
|
|
144
|
+
* unconstrained, so any member-value literal is accepted and inferred verbatim.
|
|
145
|
+
*/
|
|
146
|
+
export type CodecInput<
|
|
147
|
+
CodecTypes extends CodecTypeMap,
|
|
148
|
+
Codec extends { readonly codecId: string },
|
|
149
|
+
> = Codec['codecId'] extends keyof CodecTypes
|
|
150
|
+
? CodecTypes[Codec['codecId']] extends { readonly input: infer In }
|
|
151
|
+
? In
|
|
152
|
+
: unknown
|
|
153
|
+
: unknown;
|
|
154
|
+
|
|
128
155
|
/**
|
|
129
156
|
* Declare a domain enum for use in TS-authoring contracts.
|
|
130
157
|
*
|
|
@@ -152,9 +179,16 @@ export interface EnumTypeHandle<
|
|
|
152
179
|
* ```
|
|
153
180
|
*/
|
|
154
181
|
export function enumType<
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
const
|
|
182
|
+
CodecTypes extends CodecTypeMap = Record<string, never>,
|
|
183
|
+
const Name extends string = string,
|
|
184
|
+
const Codec extends Pick<ColumnTypeDescriptor, 'codecId' | 'nativeType'> = Pick<
|
|
185
|
+
ColumnTypeDescriptor,
|
|
186
|
+
'codecId' | 'nativeType'
|
|
187
|
+
>,
|
|
188
|
+
const Members extends readonly [
|
|
189
|
+
EnumMember<string, CodecInput<CodecTypes, Codec>>,
|
|
190
|
+
...EnumMember<string, CodecInput<CodecTypes, Codec>>[],
|
|
191
|
+
] = readonly [EnumMember<string, CodecInput<CodecTypes, Codec>>],
|
|
158
192
|
>(
|
|
159
193
|
name: Name,
|
|
160
194
|
codec: Codec,
|
|
@@ -168,12 +202,12 @@ export function enumType<
|
|
|
168
202
|
export function enumType(
|
|
169
203
|
name: string,
|
|
170
204
|
codec: Pick<ColumnTypeDescriptor, 'codecId' | 'nativeType'>,
|
|
171
|
-
...members: EnumMember<string,
|
|
205
|
+
...members: EnumMember<string, unknown>[]
|
|
172
206
|
): EnumTypeHandle;
|
|
173
207
|
export function enumType(
|
|
174
208
|
name: string,
|
|
175
209
|
codec: Pick<ColumnTypeDescriptor, 'codecId' | 'nativeType'>,
|
|
176
|
-
...members: EnumMember<string,
|
|
210
|
+
...members: EnumMember<string, unknown>[]
|
|
177
211
|
): EnumTypeHandle {
|
|
178
212
|
if (members.length === 0) {
|
|
179
213
|
throw new Error(`enumType("${name}"): must have at least one member.`);
|
|
@@ -189,12 +223,13 @@ export function enumType(
|
|
|
189
223
|
}
|
|
190
224
|
seenNames.add(m.name);
|
|
191
225
|
|
|
192
|
-
|
|
226
|
+
const loweredValue = String(m.value);
|
|
227
|
+
if (seenValues.has(loweredValue)) {
|
|
193
228
|
throw new Error(
|
|
194
|
-
`enumType("${name}"): duplicate member value "${
|
|
229
|
+
`enumType("${name}"): duplicate member value "${loweredValue}". Member values must be unique.`,
|
|
195
230
|
);
|
|
196
231
|
}
|
|
197
|
-
seenValues.add(
|
|
232
|
+
seenValues.add(loweredValue);
|
|
198
233
|
}
|
|
199
234
|
|
|
200
235
|
const values = Object.freeze(members.map((m) => m.value));
|
|
@@ -216,12 +251,47 @@ export function enumType(
|
|
|
216
251
|
values,
|
|
217
252
|
names,
|
|
218
253
|
members: membersAccessor,
|
|
219
|
-
has: (v:
|
|
220
|
-
nameOf: (v:
|
|
221
|
-
ordinalOf: (v:
|
|
254
|
+
has: (v: unknown) => valueSet.has(v),
|
|
255
|
+
nameOf: (v: unknown) => valueToName.get(v),
|
|
256
|
+
ordinalOf: (v: unknown) => valueToOrdinal.get(v) ?? -1,
|
|
222
257
|
};
|
|
223
258
|
}
|
|
224
259
|
|
|
260
|
+
/**
|
|
261
|
+
* The signature of an `enumType` whose codec typemap is already bound — the
|
|
262
|
+
* shape a target-bound wrapper (e.g. `@prisma-next/postgres/contract-builder`)
|
|
263
|
+
* exposes. The member values are constrained to the codec's input type drawn
|
|
264
|
+
* from `CodecTypes` (so a `pg/text@1` codec rejects numeric members, etc.),
|
|
265
|
+
* while `Name`, `Codec`, and the member tuple still infer from the call.
|
|
266
|
+
*/
|
|
267
|
+
export type BoundEnumType<CodecTypes extends CodecTypeMap> = <
|
|
268
|
+
const Name extends string,
|
|
269
|
+
const Codec extends Pick<ColumnTypeDescriptor, 'codecId' | 'nativeType'>,
|
|
270
|
+
const Members extends readonly [
|
|
271
|
+
EnumMember<string, CodecInput<CodecTypes, Codec>>,
|
|
272
|
+
...EnumMember<string, CodecInput<CodecTypes, Codec>>[],
|
|
273
|
+
],
|
|
274
|
+
>(
|
|
275
|
+
name: Name,
|
|
276
|
+
codec: Codec,
|
|
277
|
+
...members: Members
|
|
278
|
+
) => EnumTypeHandle<
|
|
279
|
+
Name,
|
|
280
|
+
MembersToValues<[...Members]>,
|
|
281
|
+
MembersToNames<[...Members]>,
|
|
282
|
+
MembersAccessorMap<[...Members]>
|
|
283
|
+
>;
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Bind `enumType` to a target's codec typemap. The returned function is the
|
|
287
|
+
* same runtime `enumType`, retyped so member values are constrained to the
|
|
288
|
+
* codec's input type. Target packages call this with their pack's
|
|
289
|
+
* `ExtractCodecTypesFromPack<Pack>` to expose a codec-aware `enumType`.
|
|
290
|
+
*/
|
|
291
|
+
export function bindEnumType<CodecTypes extends CodecTypeMap>(): BoundEnumType<CodecTypes> {
|
|
292
|
+
return enumType;
|
|
293
|
+
}
|
|
294
|
+
|
|
225
295
|
/**
|
|
226
296
|
* Returns true when the value is an `EnumTypeHandle` produced by
|
|
227
297
|
* `enumType()`. Used in the lowering pipeline to detect enum handles
|
|
@@ -2,6 +2,7 @@ export type {
|
|
|
2
2
|
ComposedAuthoringHelpers,
|
|
3
3
|
ContractInput,
|
|
4
4
|
ContractModelBuilder,
|
|
5
|
+
MergeEnums,
|
|
5
6
|
ModelLike,
|
|
6
7
|
ScalarFieldBuilder,
|
|
7
8
|
} from '../contract-builder';
|
|
@@ -25,5 +26,12 @@ export type {
|
|
|
25
26
|
UniqueConstraintNode,
|
|
26
27
|
} from '../contract-definition';
|
|
27
28
|
export type { TargetFieldRef } from '../contract-dsl';
|
|
28
|
-
export type {
|
|
29
|
-
export {
|
|
29
|
+
export type { ExtractCodecTypesFromPack } from '../contract-types';
|
|
30
|
+
export type {
|
|
31
|
+
BoundEnumType,
|
|
32
|
+
CodecInput,
|
|
33
|
+
CodecTypeMap,
|
|
34
|
+
EnumMember,
|
|
35
|
+
EnumTypeHandle,
|
|
36
|
+
} from '../enum-type';
|
|
37
|
+
export { bindEnumType, enumType, member } from '../enum-type';
|