@prisma-next/contract-authoring 0.14.0-dev.4 → 0.14.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/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AuthoringEntityContext, AuthoringEntityTypeDescriptor, AuthoringEntityTypeNamespace } from "@prisma-next/framework-components/authoring";
2
+ import { ColumnTypeDescriptor } from "@prisma-next/framework-components/codec";
2
3
 
3
4
  //#region src/capability-registry.d.ts
4
5
  type CapabilityMatrix = Readonly<Record<string, Readonly<Record<string, boolean>>>>;
@@ -82,5 +83,130 @@ interface ForeignKeyDefaultsState {
82
83
  readonly index: boolean;
83
84
  }
84
85
  //#endregion
85
- export { type AuthoringNamespaceKey, type CapabilityMatrix, type EntityHelperFactoryOptions, type EntityHelperFunction, type EntityHelpersFromNamespace, type ExtractAuthoringNamespaceFromPack, type ForeignKeyDefaultsState, type IndexDef, type MergeExtensionAuthoringNamespaces, type UnionToIntersection, createEntityHelpersFromNamespace, mergeCapabilityMatrices };
86
+ //#region src/enum-type.d.ts
87
+ /**
88
+ * A single enum member produced by `member()`. The `Name` and `Value` generics
89
+ * are preserved as literal types so `enumType()` can carry the ordered value
90
+ * tuple in its return type. `Value` is whatever the codec dictates — its type
91
+ * is constrained at `enumType` against the codec's input type, not here.
92
+ */
93
+ interface EnumMember<Name extends string, Value> {
94
+ readonly name: Name;
95
+ readonly value: Value;
96
+ }
97
+ /**
98
+ * Declare an enum member. The `value` defaults to `name` when omitted. The
99
+ * value is an unconstrained literal here; `enumType` constrains it against the
100
+ * codec's input type. Both generics are preserved as literals so downstream
101
+ * `enumType` carries the value union in its type; the value is serialized to its
102
+ * codec string form only at lowering.
103
+ */
104
+ declare function member<const Name extends string>(name: Name): EnumMember<Name, Name>;
105
+ declare function member<const Name extends string, const Value>(name: Name, value: Value): EnumMember<Name, Value>;
106
+ type MembersToValues<Members extends readonly EnumMember<string, unknown>[]> = { readonly [K in keyof Members]: Members[K] extends EnumMember<string, infer V> ? V : never };
107
+ type MembersToNames<Members extends readonly EnumMember<string, unknown>[]> = { readonly [K in keyof Members]: Members[K] extends EnumMember<infer N, unknown> ? N : never };
108
+ type MembersAccessorMap<Members extends readonly EnumMember<string, unknown>[]> = { readonly [M in Members[number] as M['name']]: M['value'] };
109
+ declare const ENUM_TYPE_HANDLE_BRAND = "__prismaNextEnumTypeHandle__";
110
+ /** Authoring handle returned by `enumType()`. Generic over the ordered value tuple so `const Role = enumType(...)` retains literal types. */
111
+ interface EnumTypeHandle<Name extends string = string, Values extends readonly unknown[] = readonly unknown[], Names extends readonly string[] = readonly string[], MembersMap extends Record<string, unknown> = Record<string, unknown>> {
112
+ /** Internal brand for lowering-pipeline detection. */
113
+ readonly [ENUM_TYPE_HANDLE_BRAND]: true;
114
+ /** The enum's declared name (used as the key in domain `enum` / storage `valueSet`). */
115
+ readonly enumName: Name;
116
+ /** codecId from the codec passed to `enumType`. */
117
+ readonly codecId: string;
118
+ /** nativeType from the codec passed to `enumType`. */
119
+ readonly nativeType: string;
120
+ /** Ordered member list for lowering (name + value pairs). */
121
+ readonly enumMembers: readonly {
122
+ readonly name: string;
123
+ readonly value: Values[number];
124
+ }[];
125
+ /** Ordered literal value tuple. Declaration order is preserved. */
126
+ readonly values: Values;
127
+ /** Ordered literal name tuple. Declaration order is preserved. */
128
+ readonly names: Names;
129
+ /**
130
+ * Namespaced accessor map: `Role.members.User === 'user'`.
131
+ * Namespaced under `.members` to avoid collisions with `.values` / `.has`.
132
+ */
133
+ readonly members: MembersMap;
134
+ /** Returns `true` if `v` is a declared member value. */
135
+ has(v: Values[number]): boolean;
136
+ /** Returns the member name for a value, or `undefined` if not found. */
137
+ nameOf(v: Values[number]): string | undefined;
138
+ /** Returns the zero-based declaration index of a value, or `-1` if not found. */
139
+ ordinalOf(v: Values[number]): number;
140
+ }
141
+ /**
142
+ * A codec typemap: codecId → `{ input, output }`, the same shape the query
143
+ * lanes consume. The bound `enumType` wrappers supply the target pack's
144
+ * typemap; the core defaults to an empty map (no codec is known), so member
145
+ * values stay unconstrained.
146
+ */
147
+ type CodecTypeMap = Record<string, {
148
+ readonly input?: unknown;
149
+ }>;
150
+ /**
151
+ * The application input type the codec dictates for an enum's member values:
152
+ * looks `Codec['codecId']` up in the supplied codec typemap. When the codecId
153
+ * isn't in the map the input is unconstrained, so any member-value literal is
154
+ * accepted and inferred verbatim.
155
+ */
156
+ type CodecInput<CodecTypes extends CodecTypeMap, Codec extends {
157
+ readonly codecId: string;
158
+ }> = Codec['codecId'] extends keyof CodecTypes ? CodecTypes[Codec['codecId']] extends {
159
+ readonly input: infer In;
160
+ } ? In : unknown : unknown;
161
+ /**
162
+ * Declare a domain enum for use in TS-authoring contracts.
163
+ *
164
+ * - The codec is an explicit required argument — the `codecId` and
165
+ * `nativeType` are taken from the passed `ColumnTypeDescriptor` (e.g.
166
+ * `{ codecId: 'pg/text@1', nativeType: 'text' }` from a field preset
167
+ * output or a direct inline object).
168
+ * - `const` generics on the members spread preserve the ordered literal
169
+ * value tuple so `Role.values` is `readonly ['user','admin']`, not
170
+ * `string[]`.
171
+ * - Well-formedness assertions at construction: non-empty member list;
172
+ * unique names; unique values.
173
+ *
174
+ * The returned handle wires into `field.namedType(handle)` to set
175
+ * `valueSet` refs on both the domain field and the storage column.
176
+ *
177
+ * @example
178
+ * ```ts
179
+ * const Role = enumType('Role', { codecId: 'pg/text@1', nativeType: 'text' },
180
+ * member('User', 'user'),
181
+ * member('Admin', 'admin'),
182
+ * );
183
+ * // Role.values → readonly ['user', 'admin']
184
+ * // Role.members.User → 'user'
185
+ * ```
186
+ */
187
+ declare function enumType<CodecTypes extends CodecTypeMap = Record<string, never>, const Name extends string = string, const Codec extends Pick<ColumnTypeDescriptor, 'codecId' | 'nativeType'> = Pick<ColumnTypeDescriptor, 'codecId' | 'nativeType'>, const Members extends readonly [EnumMember<string, CodecInput<CodecTypes, Codec>>, ...EnumMember<string, CodecInput<CodecTypes, Codec>>[]] = readonly [EnumMember<string, CodecInput<CodecTypes, Codec>>]>(name: Name, codec: Codec, ...members: Members): EnumTypeHandle<Name, MembersToValues<[...Members]>, MembersToNames<[...Members]>, MembersAccessorMap<[...Members]>>;
188
+ declare function enumType(name: string, codec: Pick<ColumnTypeDescriptor, 'codecId' | 'nativeType'>, ...members: EnumMember<string, unknown>[]): EnumTypeHandle;
189
+ /**
190
+ * The signature of an `enumType` whose codec typemap is already bound — the
191
+ * shape a target-bound wrapper (e.g. `@prisma-next/postgres/contract-builder`)
192
+ * exposes. The member values are constrained to the codec's input type drawn
193
+ * from `CodecTypes`, while `Name`, `Codec`, and the member tuple still infer
194
+ * from the call.
195
+ */
196
+ type BoundEnumType<CodecTypes extends CodecTypeMap> = <const Name extends string, const Codec extends Pick<ColumnTypeDescriptor, 'codecId' | 'nativeType'>, const Members extends readonly [EnumMember<string, CodecInput<CodecTypes, Codec>>, ...EnumMember<string, CodecInput<CodecTypes, Codec>>[]]>(name: Name, codec: Codec, ...members: Members) => EnumTypeHandle<Name, MembersToValues<[...Members]>, MembersToNames<[...Members]>, MembersAccessorMap<[...Members]>>;
197
+ /**
198
+ * Bind `enumType` to a target's codec typemap. The returned function is the
199
+ * same runtime `enumType`, retyped so member values are constrained to the
200
+ * codec's input type. Target packages call this with their pack's
201
+ * `ExtractCodecTypesFromPack<Pack>` to expose a codec-aware `enumType`.
202
+ */
203
+ declare function bindEnumType<CodecTypes extends CodecTypeMap>(): BoundEnumType<CodecTypes>;
204
+ /**
205
+ * Returns true when the value is an `EnumTypeHandle` produced by
206
+ * `enumType()`. Used in the lowering pipeline to detect enum handles
207
+ * in field state without importing the brand key at every call site.
208
+ */
209
+ declare function isEnumTypeHandle(value: unknown): value is EnumTypeHandle;
210
+ //#endregion
211
+ export { type AuthoringNamespaceKey, type BoundEnumType, type CapabilityMatrix, type CodecInput, type CodecTypeMap, ENUM_TYPE_HANDLE_BRAND, type EntityHelperFactoryOptions, type EntityHelperFunction, type EntityHelpersFromNamespace, type EnumMember, type EnumTypeHandle, type ExtractAuthoringNamespaceFromPack, type ForeignKeyDefaultsState, type IndexDef, type MergeExtensionAuthoringNamespaces, type UnionToIntersection, bindEnumType, createEntityHelpersFromNamespace, enumType, isEnumTypeHandle, member, mergeCapabilityMatrices };
86
212
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/capability-registry.ts","../src/composed-helpers-scaffolding.ts","../src/descriptors.ts"],"mappings":";;;KAAY,gBAAA,GAAmB,QAAA,CAAS,MAAA,SAAe,QAAA,CAAS,MAAA;;;AAAhE;;;;;;;;;;iBAcgB,uBAAA,IACX,OAAA,EAAS,aAAA,CAAc,gBAAA,gBACzB,MAAA,SAAe,MAAA;;;;;AAhBlB;;;;;;;;;;;;;;AAAsE;KCyB1D,mBAAA,OAA0B,CAAA,oBAAqB,KAAA,EAAO,CAAC,6BACjE,KAAA,sBAEE,CAAA;AAAA,KAGQ,qBAAA;AAAA,KAEA,iCAAA,mBAEE,qBAAA,oBAEV,IAAA;EAAA,SACO,SAAA,oBAA6B,GAAA;AAAA,IAEpC,SAAA,SAAkB,MAAA,oBAChB,SAAA,GACA,cAAA,GACF,cAAA;AAAA,KAEQ,iCAAA,6BAEE,qBAAA,mBACK,MAAA,kBAEjB,cAAA,SAAuB,MAAA,0BACb,cAAA,iBACJ,cAAA,GACA,mBAAA,eAEgB,cAAA,GAAiB,iCAAA,CAC3B,cAAA,CAAe,CAAA,GACf,GAAA,EACA,cAAA,UAEI,cAAA,KAEZ,cAAA;;;;;;KAOD,4BAAA,oBAAgD,6BAAA,IACnD,UAAA;EAAA,SACW,OAAA,GAAU,KAAA,eAAoB,GAAA,EAAK,sBAAA;AAAA;EAExC,KAAA,EAAO,KAAA;EAAO,MAAA,EAAQ,MAAA;AAAA;EACtB,KAAA;EAAgB,MAAA;AAAA;AAAA,KAEZ,oBAAA,oBAAwC,6BAAA,IAClD,4BAAA,CAA6B,UAAA;EAAsB,KAAA;EAAoB,MAAA;AAAA,KAClE,KAAA,EAAO,KAAA,KAAU,MAAA;AAAA,KAGZ,0BAAA,qCACW,SAAA,GAAY,SAAA,CAAU,CAAA,UAAW,6BAAA,GAClD,oBAAA,CAAqB,SAAA,CAAU,CAAA,KAC/B,SAAA,CAAU,CAAA,UAAW,MAAA,oBACnB,0BAAA,CAA2B,SAAA,CAAU,CAAA;AAAA,UAI5B,0BAAA;EAAA,SACN,GAAA,EAAK,sBAAsB;AAAA;;;AA3DL;AAEjC;;;iBAkEgB,gCAAA,CACd,SAAA,EAAW,4BAAA,EACX,OAAA,EAAS,0BAAA,EACT,IAAA,uBACC,MAAA;;;UCvGc,QAAA;EAAA,SACN,OAAA;EAAA,SACA,IAAA;EAAA,SACA,IAAA;EAAA,SACA,OAAA,GAAU,MAAM;AAAA;AAAA,UAGV,uBAAA;EAAA,SACN,UAAA;EAAA,SACA,KAAK;AAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/capability-registry.ts","../src/composed-helpers-scaffolding.ts","../src/descriptors.ts","../src/enum-type.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { instantiateAuthoringEntityType } from "@prisma-next/framework-components/authoring";
2
+ import { blindCast } from "@prisma-next/utils/casts";
2
3
  //#region src/capability-registry.ts
3
4
  /**
4
5
  * Deep-merges any number of capability matrices into a single matrix.
@@ -55,6 +56,67 @@ function createEntityHelper(helperPath, descriptor, options) {
55
56
  return (...args) => instantiateAuthoringEntityType(helperPath, descriptor, args, options.ctx);
56
57
  }
57
58
  //#endregion
58
- export { createEntityHelpersFromNamespace, mergeCapabilityMatrices };
59
+ //#region src/enum-type.ts
60
+ function member(name, value) {
61
+ return {
62
+ name,
63
+ value: blindCast(value === void 0 ? name : value)
64
+ };
65
+ }
66
+ const ENUM_TYPE_HANDLE_BRAND = "__prismaNextEnumTypeHandle__";
67
+ function enumType(name, codec, ...members) {
68
+ if (members.length === 0) throw new Error(`enumType("${name}"): must have at least one member.`);
69
+ const seenNames = /* @__PURE__ */ new Set();
70
+ const seenValues = /* @__PURE__ */ new Set();
71
+ for (const m of members) {
72
+ if (seenNames.has(m.name)) throw new Error(`enumType("${name}"): duplicate member name "${m.name}". Member names must be unique.`);
73
+ seenNames.add(m.name);
74
+ const loweredValue = String(m.value);
75
+ if (seenValues.has(loweredValue)) throw new Error(`enumType("${name}"): duplicate member value "${loweredValue}". Member values must be unique.`);
76
+ seenValues.add(loweredValue);
77
+ }
78
+ const values = Object.freeze(members.map((m) => m.value));
79
+ const names = Object.freeze(members.map((m) => m.name));
80
+ const enumMembers = Object.freeze(members.map((m) => ({
81
+ name: m.name,
82
+ value: m.value
83
+ })));
84
+ const membersAccessor = Object.freeze(Object.fromEntries(members.map((m) => [m.name, m.value])));
85
+ const valueSet = new Set(values);
86
+ const valueToName = new Map(members.map((m) => [m.value, m.name]));
87
+ const valueToOrdinal = new Map(values.map((v, i) => [v, i]));
88
+ return {
89
+ [ENUM_TYPE_HANDLE_BRAND]: true,
90
+ enumName: name,
91
+ codecId: codec.codecId,
92
+ nativeType: codec.nativeType,
93
+ enumMembers,
94
+ values,
95
+ names,
96
+ members: membersAccessor,
97
+ has: (v) => valueSet.has(v),
98
+ nameOf: (v) => valueToName.get(v),
99
+ ordinalOf: (v) => valueToOrdinal.get(v) ?? -1
100
+ };
101
+ }
102
+ /**
103
+ * Bind `enumType` to a target's codec typemap. The returned function is the
104
+ * same runtime `enumType`, retyped so member values are constrained to the
105
+ * codec's input type. Target packages call this with their pack's
106
+ * `ExtractCodecTypesFromPack<Pack>` to expose a codec-aware `enumType`.
107
+ */
108
+ function bindEnumType() {
109
+ return enumType;
110
+ }
111
+ /**
112
+ * Returns true when the value is an `EnumTypeHandle` produced by
113
+ * `enumType()`. Used in the lowering pipeline to detect enum handles
114
+ * in field state without importing the brand key at every call site.
115
+ */
116
+ function isEnumTypeHandle(value) {
117
+ return typeof value === "object" && value !== null && Reflect.get(value, "__prismaNextEnumTypeHandle__") === true;
118
+ }
119
+ //#endregion
120
+ export { ENUM_TYPE_HANDLE_BRAND, bindEnumType, createEntityHelpersFromNamespace, enumType, isEnumTypeHandle, member, mergeCapabilityMatrices };
59
121
 
60
122
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/capability-registry.ts","../src/composed-helpers-scaffolding.ts"],"sourcesContent":["export type CapabilityMatrix = Readonly<Record<string, Readonly<Record<string, boolean>>>>;\n\n/**\n * Deep-merges any number of capability matrices into a single matrix.\n *\n * Merge is purely structural — namespaces and capability flags from later\n * sources overlay earlier ones. Undefined entries are skipped so callers\n * can pass optional sources without pre-filtering.\n *\n * The helper is target-agnostic: it contains no SQL or family-specific\n * knowledge. Higher layers contribute their own capability matrices (for\n * example, a target pack, an extension pack, or the contract author's\n * own `capabilities` block) and call this helper to fold them together.\n */\nexport function mergeCapabilityMatrices(\n ...sources: ReadonlyArray<CapabilityMatrix | undefined>\n): Record<string, Record<string, boolean>> {\n const result: Record<string, Record<string, boolean>> = {};\n for (const source of sources) {\n if (source === undefined) continue;\n for (const [namespace, capabilities] of Object.entries(source)) {\n const existing = result[namespace];\n result[namespace] = existing ? { ...existing, ...capabilities } : { ...capabilities };\n }\n }\n return result;\n}\n","import type {\n AuthoringEntityContext,\n AuthoringEntityTypeDescriptor,\n AuthoringEntityTypeNamespace,\n} from '@prisma-next/framework-components/authoring';\nimport { instantiateAuthoringEntityType } from '@prisma-next/framework-components/authoring';\n\n/**\n * Family-agnostic merge / instantiation scaffolding for pack-bag-driven\n * authoring contributions. The per-namespace shape (`field`, `type`,\n * `entityTypes`) is parameterized by the discriminator key so each\n * namespace can reuse the same extractor + cross-pack merger without\n * re-deriving the template per family. Call sites flatten merged\n * `entityTypes` onto the user-facing top-level helpers surface\n * alongside the built-in `model` / `rel` (e.g. `helpers.enum(...)`).\n * The contribution data structure stays as\n * `authoring.entityTypes.<name>` — pack authors keep contributing\n * through the namespace; the composed-helpers template performs the\n * rename in the type system.\n *\n * SQL-specific composition (the `field` / `model` / `rel` / `type` core\n * helpers, the SQL index-types merge) lives in the SQL contract-ts\n * package and imports from here.\n */\n\nexport type UnionToIntersection<U> = (U extends unknown ? (value: U) => void : never) extends (\n value: infer I,\n) => void\n ? I\n : never;\n\nexport type AuthoringNamespaceKey = 'field' | 'type' | 'entityTypes';\n\nexport type ExtractAuthoringNamespaceFromPack<\n Pack,\n Key extends AuthoringNamespaceKey,\n EmptyNamespace,\n> = Pack extends {\n readonly authoring?: { readonly [P in Key]?: infer Namespace };\n}\n ? Namespace extends Record<string, unknown>\n ? Namespace\n : EmptyNamespace\n : EmptyNamespace;\n\nexport type MergeExtensionAuthoringNamespaces<\n ExtensionPacks,\n Key extends AuthoringNamespaceKey,\n EmptyNamespace = Record<never, never>,\n> =\n ExtensionPacks extends Record<string, unknown>\n ? keyof ExtensionPacks extends never\n ? EmptyNamespace\n : UnionToIntersection<\n {\n [K in keyof ExtensionPacks]: ExtractAuthoringNamespaceFromPack<\n ExtensionPacks[K],\n Key,\n EmptyNamespace\n >;\n }[keyof ExtensionPacks]\n >\n : EmptyNamespace;\n\n/**\n * Entity-helper shape derivation. Mirrors `FieldHelpersFromNamespace` /\n * `TypeHelpersFromNamespace` in the SQL package: leaf descriptors become\n * callable helpers; nested namespaces recurse.\n */\ntype ExtractFactoryInputAndOutput<Descriptor extends AuthoringEntityTypeDescriptor> =\n Descriptor['output'] extends {\n readonly factory: (input: infer Input, ctx: AuthoringEntityContext) => infer Output;\n }\n ? { input: Input; output: Output }\n : { input: unknown; output: unknown };\n\nexport type EntityHelperFunction<Descriptor extends AuthoringEntityTypeDescriptor> =\n ExtractFactoryInputAndOutput<Descriptor> extends { input: infer Input; output: infer Output }\n ? (input: Input) => Output\n : never;\n\nexport type EntityHelpersFromNamespace<Namespace> = {\n readonly [K in keyof Namespace]: Namespace[K] extends AuthoringEntityTypeDescriptor\n ? EntityHelperFunction<Namespace[K]>\n : Namespace[K] extends Record<string, unknown>\n ? EntityHelpersFromNamespace<Namespace[K]>\n : never;\n};\n\nexport interface EntityHelperFactoryOptions {\n readonly ctx: AuthoringEntityContext;\n}\n\n/**\n * Walks an entity-type namespace (after cross-pack merge) and produces the\n * runtime callable surface mirroring its tree shape. Each leaf\n * descriptor becomes a function `(input) => factory(input, ctx)`;\n * nested namespace objects recurse.\n */\nexport function createEntityHelpersFromNamespace(\n namespace: AuthoringEntityTypeNamespace,\n options: EntityHelperFactoryOptions,\n path: readonly string[] = [],\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(namespace)) {\n const currentPath = [...path, key];\n if (isLeafEntityDescriptor(value)) {\n result[key] = createEntityHelper(currentPath.join('.'), value, options);\n continue;\n }\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n result[key] = createEntityHelpersFromNamespace(\n value as AuthoringEntityTypeNamespace,\n options,\n currentPath,\n );\n }\n }\n return result;\n}\n\nfunction isLeafEntityDescriptor(value: unknown): value is AuthoringEntityTypeDescriptor {\n if (\n typeof value !== 'object' ||\n value === null ||\n (value as { kind?: unknown }).kind !== 'entity'\n ) {\n return false;\n }\n const discriminator = (value as { discriminator?: unknown }).discriminator;\n return typeof discriminator === 'string' && discriminator.length > 0;\n}\n\nfunction createEntityHelper(\n helperPath: string,\n descriptor: AuthoringEntityTypeDescriptor,\n options: EntityHelperFactoryOptions,\n): (...args: readonly unknown[]) => unknown {\n return (...args: readonly unknown[]) =>\n instantiateAuthoringEntityType(helperPath, descriptor, args, options.ctx);\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,SAAgB,wBACd,GAAG,SACsC;CACzC,MAAM,SAAkD,CAAC;CACzD,KAAK,MAAM,UAAU,SAAS;EAC5B,IAAI,WAAW,KAAA,GAAW;EAC1B,KAAK,MAAM,CAAC,WAAW,iBAAiB,OAAO,QAAQ,MAAM,GAAG;GAC9D,MAAM,WAAW,OAAO;GACxB,OAAO,aAAa,WAAW;IAAE,GAAG;IAAU,GAAG;GAAa,IAAI,EAAE,GAAG,aAAa;EACtF;CACF;CACA,OAAO;AACT;;;;;;;;;ACyEA,SAAgB,iCACd,WACA,SACA,OAA0B,CAAC,GACF;CACzB,MAAM,SAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,GAAG;EACpD,MAAM,cAAc,CAAC,GAAG,MAAM,GAAG;EACjC,IAAI,uBAAuB,KAAK,GAAG;GACjC,OAAO,OAAO,mBAAmB,YAAY,KAAK,GAAG,GAAG,OAAO,OAAO;GACtE;EACF;EACA,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,GACrE,OAAO,OAAO,iCACZ,OACA,SACA,WACF;CAEJ;CACA,OAAO;AACT;AAEA,SAAS,uBAAuB,OAAwD;CACtF,IACE,OAAO,UAAU,YACjB,UAAU,QACT,MAA6B,SAAS,UAEvC,OAAO;CAET,MAAM,gBAAiB,MAAsC;CAC7D,OAAO,OAAO,kBAAkB,YAAY,cAAc,SAAS;AACrE;AAEA,SAAS,mBACP,YACA,YACA,SAC0C;CAC1C,QAAQ,GAAG,SACT,+BAA+B,YAAY,YAAY,MAAM,QAAQ,GAAG;AAC5E"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/capability-registry.ts","../src/composed-helpers-scaffolding.ts","../src/enum-type.ts"],"sourcesContent":["export type CapabilityMatrix = Readonly<Record<string, Readonly<Record<string, boolean>>>>;\n\n/**\n * Deep-merges any number of capability matrices into a single matrix.\n *\n * Merge is purely structural — namespaces and capability flags from later\n * sources overlay earlier ones. Undefined entries are skipped so callers\n * can pass optional sources without pre-filtering.\n *\n * The helper is target-agnostic: it contains no SQL or family-specific\n * knowledge. Higher layers contribute their own capability matrices (for\n * example, a target pack, an extension pack, or the contract author's\n * own `capabilities` block) and call this helper to fold them together.\n */\nexport function mergeCapabilityMatrices(\n ...sources: ReadonlyArray<CapabilityMatrix | undefined>\n): Record<string, Record<string, boolean>> {\n const result: Record<string, Record<string, boolean>> = {};\n for (const source of sources) {\n if (source === undefined) continue;\n for (const [namespace, capabilities] of Object.entries(source)) {\n const existing = result[namespace];\n result[namespace] = existing ? { ...existing, ...capabilities } : { ...capabilities };\n }\n }\n return result;\n}\n","import type {\n AuthoringEntityContext,\n AuthoringEntityTypeDescriptor,\n AuthoringEntityTypeNamespace,\n} from '@prisma-next/framework-components/authoring';\nimport { instantiateAuthoringEntityType } from '@prisma-next/framework-components/authoring';\n\n/**\n * Family-agnostic merge / instantiation scaffolding for pack-bag-driven\n * authoring contributions. The per-namespace shape (`field`, `type`,\n * `entityTypes`) is parameterized by the discriminator key so each\n * namespace can reuse the same extractor + cross-pack merger without\n * re-deriving the template per family. Call sites flatten merged\n * `entityTypes` onto the user-facing top-level helpers surface\n * alongside the built-in `model` / `rel` (e.g. `helpers.enum(...)`).\n * The contribution data structure stays as\n * `authoring.entityTypes.<name>` — pack authors keep contributing\n * through the namespace; the composed-helpers template performs the\n * rename in the type system.\n *\n * SQL-specific composition (the `field` / `model` / `rel` / `type` core\n * helpers, the SQL index-types merge) lives in the SQL contract-ts\n * package and imports from here.\n */\n\nexport type UnionToIntersection<U> = (U extends unknown ? (value: U) => void : never) extends (\n value: infer I,\n) => void\n ? I\n : never;\n\nexport type AuthoringNamespaceKey = 'field' | 'type' | 'entityTypes';\n\nexport type ExtractAuthoringNamespaceFromPack<\n Pack,\n Key extends AuthoringNamespaceKey,\n EmptyNamespace,\n> = Pack extends {\n readonly authoring?: { readonly [P in Key]?: infer Namespace };\n}\n ? Namespace extends Record<string, unknown>\n ? Namespace\n : EmptyNamespace\n : EmptyNamespace;\n\nexport type MergeExtensionAuthoringNamespaces<\n ExtensionPacks,\n Key extends AuthoringNamespaceKey,\n EmptyNamespace = Record<never, never>,\n> =\n ExtensionPacks extends Record<string, unknown>\n ? keyof ExtensionPacks extends never\n ? EmptyNamespace\n : UnionToIntersection<\n {\n [K in keyof ExtensionPacks]: ExtractAuthoringNamespaceFromPack<\n ExtensionPacks[K],\n Key,\n EmptyNamespace\n >;\n }[keyof ExtensionPacks]\n >\n : EmptyNamespace;\n\n/**\n * Entity-helper shape derivation. Mirrors `FieldHelpersFromNamespace` /\n * `TypeHelpersFromNamespace` in the SQL package: leaf descriptors become\n * callable helpers; nested namespaces recurse.\n */\ntype ExtractFactoryInputAndOutput<Descriptor extends AuthoringEntityTypeDescriptor> =\n Descriptor['output'] extends {\n readonly factory: (input: infer Input, ctx: AuthoringEntityContext) => infer Output;\n }\n ? { input: Input; output: Output }\n : { input: unknown; output: unknown };\n\nexport type EntityHelperFunction<Descriptor extends AuthoringEntityTypeDescriptor> =\n ExtractFactoryInputAndOutput<Descriptor> extends { input: infer Input; output: infer Output }\n ? (input: Input) => Output\n : never;\n\nexport type EntityHelpersFromNamespace<Namespace> = {\n readonly [K in keyof Namespace]: Namespace[K] extends AuthoringEntityTypeDescriptor\n ? EntityHelperFunction<Namespace[K]>\n : Namespace[K] extends Record<string, unknown>\n ? EntityHelpersFromNamespace<Namespace[K]>\n : never;\n};\n\nexport interface EntityHelperFactoryOptions {\n readonly ctx: AuthoringEntityContext;\n}\n\n/**\n * Walks an entity-type namespace (after cross-pack merge) and produces the\n * runtime callable surface mirroring its tree shape. Each leaf\n * descriptor becomes a function `(input) => factory(input, ctx)`;\n * nested namespace objects recurse.\n */\nexport function createEntityHelpersFromNamespace(\n namespace: AuthoringEntityTypeNamespace,\n options: EntityHelperFactoryOptions,\n path: readonly string[] = [],\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(namespace)) {\n const currentPath = [...path, key];\n if (isLeafEntityDescriptor(value)) {\n result[key] = createEntityHelper(currentPath.join('.'), value, options);\n continue;\n }\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n result[key] = createEntityHelpersFromNamespace(\n value as AuthoringEntityTypeNamespace,\n options,\n currentPath,\n );\n }\n }\n return result;\n}\n\nfunction isLeafEntityDescriptor(value: unknown): value is AuthoringEntityTypeDescriptor {\n if (\n typeof value !== 'object' ||\n value === null ||\n (value as { kind?: unknown }).kind !== 'entity'\n ) {\n return false;\n }\n const discriminator = (value as { discriminator?: unknown }).discriminator;\n return typeof discriminator === 'string' && discriminator.length > 0;\n}\n\nfunction createEntityHelper(\n helperPath: string,\n descriptor: AuthoringEntityTypeDescriptor,\n options: EntityHelperFactoryOptions,\n): (...args: readonly unknown[]) => unknown {\n return (...args: readonly unknown[]) =>\n instantiateAuthoringEntityType(helperPath, descriptor, args, options.ctx);\n}\n","import type { ColumnTypeDescriptor } from '@prisma-next/framework-components/codec';\nimport { blindCast } from '@prisma-next/utils/casts';\n\n/**\n * A single enum member produced by `member()`. The `Name` and `Value` generics\n * are preserved as literal types so `enumType()` can carry the ordered value\n * tuple in its return type. `Value` is whatever the codec dictates — its type\n * is constrained at `enumType` against the codec's input type, not here.\n */\nexport interface EnumMember<Name extends string, Value> {\n readonly name: Name;\n readonly value: Value;\n}\n\n/**\n * Declare an enum member. The `value` defaults to `name` when omitted. The\n * value is an unconstrained literal here; `enumType` constrains it against the\n * codec's input type. Both generics are preserved as literals so downstream\n * `enumType` carries the value union in its type; the value is serialized to its\n * codec string form only at lowering.\n */\nexport function member<const Name extends string>(name: Name): EnumMember<Name, Name>;\nexport function member<const Name extends string, const Value>(\n name: Name,\n value: Value,\n): EnumMember<Name, Value>;\nexport function member<const Name extends string, const Value = Name>(\n name: Name,\n value?: Value,\n): EnumMember<Name, Value> {\n return {\n name,\n value: blindCast<\n Value,\n 'overload signatures enforce Value=Name when value is omitted; default generic Value=Name makes this safe'\n >(value === undefined ? name : value),\n };\n}\n\ntype MembersToValues<Members extends readonly EnumMember<string, unknown>[]> = {\n readonly [K in keyof Members]: Members[K] extends EnumMember<string, infer V> ? V : never;\n};\n\ntype MembersToNames<Members extends readonly EnumMember<string, unknown>[]> = {\n readonly [K in keyof Members]: Members[K] extends EnumMember<infer N, unknown> ? N : never;\n};\n\ntype MembersAccessorMap<Members extends readonly EnumMember<string, unknown>[]> = {\n readonly [M in Members[number] as M['name']]: M['value'];\n};\n\n// String-key brand rather than unique symbol: tsdown inlines a fresh unique\n// symbol into each extension's .d.mts when the source package is transitive,\n// breaking nominal type identity across chunk boundaries. A string constant\n// survives .d.mts de-duplication because string equality is preserved by value.\n// Mirrors the FILTER_EXPR_BRAND pattern in mongo-query-ast/filter-expressions.ts.\nexport const ENUM_TYPE_HANDLE_BRAND = '__prismaNextEnumTypeHandle__';\n\n/** Authoring handle returned by `enumType()`. Generic over the ordered value tuple so `const Role = enumType(...)` retains literal types. */\nexport interface EnumTypeHandle<\n Name extends string = string,\n Values extends readonly unknown[] = readonly unknown[],\n Names extends readonly string[] = readonly string[],\n MembersMap extends Record<string, unknown> = Record<string, unknown>,\n> {\n /** Internal brand for lowering-pipeline detection. */\n readonly [ENUM_TYPE_HANDLE_BRAND]: true;\n\n /** The enum's declared name (used as the key in domain `enum` / storage `valueSet`). */\n readonly enumName: Name;\n\n /** codecId from the codec passed to `enumType`. */\n readonly codecId: string;\n\n /** nativeType from the codec passed to `enumType`. */\n readonly nativeType: string;\n\n /** Ordered member list for lowering (name + value pairs). */\n readonly enumMembers: readonly { readonly name: string; readonly value: Values[number] }[];\n\n /** Ordered literal value tuple. Declaration order is preserved. */\n readonly values: Values;\n\n /** Ordered literal name tuple. Declaration order is preserved. */\n readonly names: Names;\n\n /**\n * Namespaced accessor map: `Role.members.User === 'user'`.\n * Namespaced under `.members` to avoid collisions with `.values` / `.has`.\n */\n readonly members: MembersMap;\n\n /** Returns `true` if `v` is a declared member value. */\n has(v: Values[number]): boolean;\n\n /** Returns the member name for a value, or `undefined` if not found. */\n nameOf(v: Values[number]): string | undefined;\n\n /** Returns the zero-based declaration index of a value, or `-1` if not found. */\n ordinalOf(v: Values[number]): number;\n}\n\n/**\n * A codec typemap: codecId → `{ input, output }`, the same shape the query\n * lanes consume. The bound `enumType` wrappers supply the target pack's\n * typemap; the core defaults to an empty map (no codec is known), so member\n * values stay unconstrained.\n */\nexport type CodecTypeMap = Record<string, { readonly input?: unknown }>;\n\n/**\n * The application input type the codec dictates for an enum's member values:\n * looks `Codec['codecId']` up in the supplied codec typemap. When the codecId\n * isn't in the map the input is unconstrained, so any member-value literal is\n * accepted and inferred verbatim.\n */\nexport type CodecInput<\n CodecTypes extends CodecTypeMap,\n Codec extends { readonly codecId: string },\n> = Codec['codecId'] extends keyof CodecTypes\n ? CodecTypes[Codec['codecId']] extends { readonly input: infer In }\n ? In\n : unknown\n : unknown;\n\n/**\n * Declare a domain enum for use in TS-authoring contracts.\n *\n * - The codec is an explicit required argument — the `codecId` and\n * `nativeType` are taken from the passed `ColumnTypeDescriptor` (e.g.\n * `{ codecId: 'pg/text@1', nativeType: 'text' }` from a field preset\n * output or a direct inline object).\n * - `const` generics on the members spread preserve the ordered literal\n * value tuple so `Role.values` is `readonly ['user','admin']`, not\n * `string[]`.\n * - Well-formedness assertions at construction: non-empty member list;\n * unique names; unique values.\n *\n * The returned handle wires into `field.namedType(handle)` to set\n * `valueSet` refs on both the domain field and the storage column.\n *\n * @example\n * ```ts\n * const Role = enumType('Role', { codecId: 'pg/text@1', nativeType: 'text' },\n * member('User', 'user'),\n * member('Admin', 'admin'),\n * );\n * // Role.values → readonly ['user', 'admin']\n * // Role.members.User → 'user'\n * ```\n */\nexport function enumType<\n CodecTypes extends CodecTypeMap = Record<string, never>,\n const Name extends string = string,\n const Codec extends Pick<ColumnTypeDescriptor, 'codecId' | 'nativeType'> = Pick<\n ColumnTypeDescriptor,\n 'codecId' | 'nativeType'\n >,\n const Members extends readonly [\n EnumMember<string, CodecInput<CodecTypes, Codec>>,\n ...EnumMember<string, CodecInput<CodecTypes, Codec>>[],\n ] = readonly [EnumMember<string, CodecInput<CodecTypes, Codec>>],\n>(\n name: Name,\n codec: Codec,\n ...members: Members\n): EnumTypeHandle<\n Name,\n MembersToValues<[...Members]>,\n MembersToNames<[...Members]>,\n MembersAccessorMap<[...Members]>\n>;\nexport function enumType(\n name: string,\n codec: Pick<ColumnTypeDescriptor, 'codecId' | 'nativeType'>,\n ...members: EnumMember<string, unknown>[]\n): EnumTypeHandle;\nexport function enumType(\n name: string,\n codec: Pick<ColumnTypeDescriptor, 'codecId' | 'nativeType'>,\n ...members: EnumMember<string, unknown>[]\n): EnumTypeHandle {\n if (members.length === 0) {\n throw new Error(`enumType(\"${name}\"): must have at least one member.`);\n }\n\n const seenNames = new Set<string>();\n const seenValues = new Set<string>();\n for (const m of members) {\n if (seenNames.has(m.name)) {\n throw new Error(\n `enumType(\"${name}\"): duplicate member name \"${m.name}\". Member names must be unique.`,\n );\n }\n seenNames.add(m.name);\n\n const loweredValue = String(m.value);\n if (seenValues.has(loweredValue)) {\n throw new Error(\n `enumType(\"${name}\"): duplicate member value \"${loweredValue}\". Member values must be unique.`,\n );\n }\n seenValues.add(loweredValue);\n }\n\n const values = Object.freeze(members.map((m) => m.value));\n const names = Object.freeze(members.map((m) => m.name));\n const enumMembers = Object.freeze(members.map((m) => ({ name: m.name, value: m.value })));\n\n const membersAccessor = Object.freeze(Object.fromEntries(members.map((m) => [m.name, m.value])));\n\n const valueSet = new Set(values);\n const valueToName = new Map(members.map((m) => [m.value, m.name]));\n const valueToOrdinal = new Map(values.map((v, i) => [v, i]));\n\n return {\n [ENUM_TYPE_HANDLE_BRAND]: true,\n enumName: name,\n codecId: codec.codecId,\n nativeType: codec.nativeType,\n enumMembers,\n values,\n names,\n members: membersAccessor,\n has: (v: unknown) => valueSet.has(v),\n nameOf: (v: unknown) => valueToName.get(v),\n ordinalOf: (v: unknown) => valueToOrdinal.get(v) ?? -1,\n };\n}\n\n/**\n * The signature of an `enumType` whose codec typemap is already bound — the\n * shape a target-bound wrapper (e.g. `@prisma-next/postgres/contract-builder`)\n * exposes. The member values are constrained to the codec's input type drawn\n * from `CodecTypes`, while `Name`, `Codec`, and the member tuple still infer\n * from the call.\n */\nexport type BoundEnumType<CodecTypes extends CodecTypeMap> = <\n const Name extends string,\n const Codec extends Pick<ColumnTypeDescriptor, 'codecId' | 'nativeType'>,\n const Members extends readonly [\n EnumMember<string, CodecInput<CodecTypes, Codec>>,\n ...EnumMember<string, CodecInput<CodecTypes, Codec>>[],\n ],\n>(\n name: Name,\n codec: Codec,\n ...members: Members\n) => EnumTypeHandle<\n Name,\n MembersToValues<[...Members]>,\n MembersToNames<[...Members]>,\n MembersAccessorMap<[...Members]>\n>;\n\n/**\n * Bind `enumType` to a target's codec typemap. The returned function is the\n * same runtime `enumType`, retyped so member values are constrained to the\n * codec's input type. Target packages call this with their pack's\n * `ExtractCodecTypesFromPack<Pack>` to expose a codec-aware `enumType`.\n */\nexport function bindEnumType<CodecTypes extends CodecTypeMap>(): BoundEnumType<CodecTypes> {\n return enumType;\n}\n\n/**\n * Returns true when the value is an `EnumTypeHandle` produced by\n * `enumType()`. Used in the lowering pipeline to detect enum handles\n * in field state without importing the brand key at every call site.\n */\nexport function isEnumTypeHandle(value: unknown): value is EnumTypeHandle {\n return (\n typeof value === 'object' &&\n value !== null &&\n Reflect.get(value, ENUM_TYPE_HANDLE_BRAND) === true\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "@prisma-next/contract-authoring",
3
- "version": "0.14.0-dev.4",
3
+ "version": "0.14.0-dev.40",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "description": "Target-agnostic authored storage descriptor types for Prisma Next",
8
8
  "dependencies": {
9
- "@prisma-next/framework-components": "0.14.0-dev.4"
9
+ "@prisma-next/framework-components": "0.14.0-dev.40",
10
+ "@prisma-next/utils": "0.14.0-dev.40"
10
11
  },
11
12
  "devDependencies": {
12
- "@prisma-next/tsconfig": "0.14.0-dev.4",
13
- "@prisma-next/tsdown": "0.14.0-dev.4",
13
+ "@prisma-next/tsconfig": "0.14.0-dev.40",
14
+ "@prisma-next/tsdown": "0.14.0-dev.40",
14
15
  "tsdown": "0.22.1",
15
16
  "typescript": "5.9.3",
16
17
  "vitest": "4.1.8"
@@ -0,0 +1,277 @@
1
+ import type { ColumnTypeDescriptor } from '@prisma-next/framework-components/codec';
2
+ import { blindCast } from '@prisma-next/utils/casts';
3
+
4
+ /**
5
+ * A single enum member produced by `member()`. The `Name` and `Value` generics
6
+ * are preserved as literal types so `enumType()` can carry the ordered value
7
+ * tuple in its return type. `Value` is whatever the codec dictates — its type
8
+ * is constrained at `enumType` against the codec's input type, not here.
9
+ */
10
+ export interface EnumMember<Name extends string, Value> {
11
+ readonly name: Name;
12
+ readonly value: Value;
13
+ }
14
+
15
+ /**
16
+ * Declare an enum member. The `value` defaults to `name` when omitted. The
17
+ * value is an unconstrained literal here; `enumType` constrains it against the
18
+ * codec's input type. Both generics are preserved as literals so downstream
19
+ * `enumType` carries the value union in its type; the value is serialized to its
20
+ * codec string form only at lowering.
21
+ */
22
+ export function member<const Name extends string>(name: Name): EnumMember<Name, Name>;
23
+ export function member<const Name extends string, const Value>(
24
+ name: Name,
25
+ value: Value,
26
+ ): EnumMember<Name, Value>;
27
+ export function member<const Name extends string, const Value = Name>(
28
+ name: Name,
29
+ value?: Value,
30
+ ): EnumMember<Name, Value> {
31
+ return {
32
+ name,
33
+ value: blindCast<
34
+ Value,
35
+ 'overload signatures enforce Value=Name when value is omitted; default generic Value=Name makes this safe'
36
+ >(value === undefined ? name : value),
37
+ };
38
+ }
39
+
40
+ type MembersToValues<Members extends readonly EnumMember<string, unknown>[]> = {
41
+ readonly [K in keyof Members]: Members[K] extends EnumMember<string, infer V> ? V : never;
42
+ };
43
+
44
+ type MembersToNames<Members extends readonly EnumMember<string, unknown>[]> = {
45
+ readonly [K in keyof Members]: Members[K] extends EnumMember<infer N, unknown> ? N : never;
46
+ };
47
+
48
+ type MembersAccessorMap<Members extends readonly EnumMember<string, unknown>[]> = {
49
+ readonly [M in Members[number] as M['name']]: M['value'];
50
+ };
51
+
52
+ // String-key brand rather than unique symbol: tsdown inlines a fresh unique
53
+ // symbol into each extension's .d.mts when the source package is transitive,
54
+ // breaking nominal type identity across chunk boundaries. A string constant
55
+ // survives .d.mts de-duplication because string equality is preserved by value.
56
+ // Mirrors the FILTER_EXPR_BRAND pattern in mongo-query-ast/filter-expressions.ts.
57
+ export const ENUM_TYPE_HANDLE_BRAND = '__prismaNextEnumTypeHandle__';
58
+
59
+ /** Authoring handle returned by `enumType()`. Generic over the ordered value tuple so `const Role = enumType(...)` retains literal types. */
60
+ export interface EnumTypeHandle<
61
+ Name extends string = string,
62
+ Values extends readonly unknown[] = readonly unknown[],
63
+ Names extends readonly string[] = readonly string[],
64
+ MembersMap extends Record<string, unknown> = Record<string, unknown>,
65
+ > {
66
+ /** Internal brand for lowering-pipeline detection. */
67
+ readonly [ENUM_TYPE_HANDLE_BRAND]: true;
68
+
69
+ /** The enum's declared name (used as the key in domain `enum` / storage `valueSet`). */
70
+ readonly enumName: Name;
71
+
72
+ /** codecId from the codec passed to `enumType`. */
73
+ readonly codecId: string;
74
+
75
+ /** nativeType from the codec passed to `enumType`. */
76
+ readonly nativeType: string;
77
+
78
+ /** Ordered member list for lowering (name + value pairs). */
79
+ readonly enumMembers: readonly { readonly name: string; readonly value: Values[number] }[];
80
+
81
+ /** Ordered literal value tuple. Declaration order is preserved. */
82
+ readonly values: Values;
83
+
84
+ /** Ordered literal name tuple. Declaration order is preserved. */
85
+ readonly names: Names;
86
+
87
+ /**
88
+ * Namespaced accessor map: `Role.members.User === 'user'`.
89
+ * Namespaced under `.members` to avoid collisions with `.values` / `.has`.
90
+ */
91
+ readonly members: MembersMap;
92
+
93
+ /** Returns `true` if `v` is a declared member value. */
94
+ has(v: Values[number]): boolean;
95
+
96
+ /** Returns the member name for a value, or `undefined` if not found. */
97
+ nameOf(v: Values[number]): string | undefined;
98
+
99
+ /** Returns the zero-based declaration index of a value, or `-1` if not found. */
100
+ ordinalOf(v: Values[number]): number;
101
+ }
102
+
103
+ /**
104
+ * A codec typemap: codecId → `{ input, output }`, the same shape the query
105
+ * lanes consume. The bound `enumType` wrappers supply the target pack's
106
+ * typemap; the core defaults to an empty map (no codec is known), so member
107
+ * values stay unconstrained.
108
+ */
109
+ export type CodecTypeMap = Record<string, { readonly input?: unknown }>;
110
+
111
+ /**
112
+ * The application input type the codec dictates for an enum's member values:
113
+ * looks `Codec['codecId']` up in the supplied codec typemap. When the codecId
114
+ * isn't in the map the input is unconstrained, so any member-value literal is
115
+ * accepted and inferred verbatim.
116
+ */
117
+ export type CodecInput<
118
+ CodecTypes extends CodecTypeMap,
119
+ Codec extends { readonly codecId: string },
120
+ > = Codec['codecId'] extends keyof CodecTypes
121
+ ? CodecTypes[Codec['codecId']] extends { readonly input: infer In }
122
+ ? In
123
+ : unknown
124
+ : unknown;
125
+
126
+ /**
127
+ * Declare a domain enum for use in TS-authoring contracts.
128
+ *
129
+ * - The codec is an explicit required argument — the `codecId` and
130
+ * `nativeType` are taken from the passed `ColumnTypeDescriptor` (e.g.
131
+ * `{ codecId: 'pg/text@1', nativeType: 'text' }` from a field preset
132
+ * output or a direct inline object).
133
+ * - `const` generics on the members spread preserve the ordered literal
134
+ * value tuple so `Role.values` is `readonly ['user','admin']`, not
135
+ * `string[]`.
136
+ * - Well-formedness assertions at construction: non-empty member list;
137
+ * unique names; unique values.
138
+ *
139
+ * The returned handle wires into `field.namedType(handle)` to set
140
+ * `valueSet` refs on both the domain field and the storage column.
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * const Role = enumType('Role', { codecId: 'pg/text@1', nativeType: 'text' },
145
+ * member('User', 'user'),
146
+ * member('Admin', 'admin'),
147
+ * );
148
+ * // Role.values → readonly ['user', 'admin']
149
+ * // Role.members.User → 'user'
150
+ * ```
151
+ */
152
+ export function enumType<
153
+ CodecTypes extends CodecTypeMap = Record<string, never>,
154
+ const Name extends string = string,
155
+ const Codec extends Pick<ColumnTypeDescriptor, 'codecId' | 'nativeType'> = Pick<
156
+ ColumnTypeDescriptor,
157
+ 'codecId' | 'nativeType'
158
+ >,
159
+ const Members extends readonly [
160
+ EnumMember<string, CodecInput<CodecTypes, Codec>>,
161
+ ...EnumMember<string, CodecInput<CodecTypes, Codec>>[],
162
+ ] = readonly [EnumMember<string, CodecInput<CodecTypes, Codec>>],
163
+ >(
164
+ name: Name,
165
+ codec: Codec,
166
+ ...members: Members
167
+ ): EnumTypeHandle<
168
+ Name,
169
+ MembersToValues<[...Members]>,
170
+ MembersToNames<[...Members]>,
171
+ MembersAccessorMap<[...Members]>
172
+ >;
173
+ export function enumType(
174
+ name: string,
175
+ codec: Pick<ColumnTypeDescriptor, 'codecId' | 'nativeType'>,
176
+ ...members: EnumMember<string, unknown>[]
177
+ ): EnumTypeHandle;
178
+ export function enumType(
179
+ name: string,
180
+ codec: Pick<ColumnTypeDescriptor, 'codecId' | 'nativeType'>,
181
+ ...members: EnumMember<string, unknown>[]
182
+ ): EnumTypeHandle {
183
+ if (members.length === 0) {
184
+ throw new Error(`enumType("${name}"): must have at least one member.`);
185
+ }
186
+
187
+ const seenNames = new Set<string>();
188
+ const seenValues = new Set<string>();
189
+ for (const m of members) {
190
+ if (seenNames.has(m.name)) {
191
+ throw new Error(
192
+ `enumType("${name}"): duplicate member name "${m.name}". Member names must be unique.`,
193
+ );
194
+ }
195
+ seenNames.add(m.name);
196
+
197
+ const loweredValue = String(m.value);
198
+ if (seenValues.has(loweredValue)) {
199
+ throw new Error(
200
+ `enumType("${name}"): duplicate member value "${loweredValue}". Member values must be unique.`,
201
+ );
202
+ }
203
+ seenValues.add(loweredValue);
204
+ }
205
+
206
+ const values = Object.freeze(members.map((m) => m.value));
207
+ const names = Object.freeze(members.map((m) => m.name));
208
+ const enumMembers = Object.freeze(members.map((m) => ({ name: m.name, value: m.value })));
209
+
210
+ const membersAccessor = Object.freeze(Object.fromEntries(members.map((m) => [m.name, m.value])));
211
+
212
+ const valueSet = new Set(values);
213
+ const valueToName = new Map(members.map((m) => [m.value, m.name]));
214
+ const valueToOrdinal = new Map(values.map((v, i) => [v, i]));
215
+
216
+ return {
217
+ [ENUM_TYPE_HANDLE_BRAND]: true,
218
+ enumName: name,
219
+ codecId: codec.codecId,
220
+ nativeType: codec.nativeType,
221
+ enumMembers,
222
+ values,
223
+ names,
224
+ members: membersAccessor,
225
+ has: (v: unknown) => valueSet.has(v),
226
+ nameOf: (v: unknown) => valueToName.get(v),
227
+ ordinalOf: (v: unknown) => valueToOrdinal.get(v) ?? -1,
228
+ };
229
+ }
230
+
231
+ /**
232
+ * The signature of an `enumType` whose codec typemap is already bound — the
233
+ * shape a target-bound wrapper (e.g. `@prisma-next/postgres/contract-builder`)
234
+ * exposes. The member values are constrained to the codec's input type drawn
235
+ * from `CodecTypes`, while `Name`, `Codec`, and the member tuple still infer
236
+ * from the call.
237
+ */
238
+ export type BoundEnumType<CodecTypes extends CodecTypeMap> = <
239
+ const Name extends string,
240
+ const Codec extends Pick<ColumnTypeDescriptor, 'codecId' | 'nativeType'>,
241
+ const Members extends readonly [
242
+ EnumMember<string, CodecInput<CodecTypes, Codec>>,
243
+ ...EnumMember<string, CodecInput<CodecTypes, Codec>>[],
244
+ ],
245
+ >(
246
+ name: Name,
247
+ codec: Codec,
248
+ ...members: Members
249
+ ) => EnumTypeHandle<
250
+ Name,
251
+ MembersToValues<[...Members]>,
252
+ MembersToNames<[...Members]>,
253
+ MembersAccessorMap<[...Members]>
254
+ >;
255
+
256
+ /**
257
+ * Bind `enumType` to a target's codec typemap. The returned function is the
258
+ * same runtime `enumType`, retyped so member values are constrained to the
259
+ * codec's input type. Target packages call this with their pack's
260
+ * `ExtractCodecTypesFromPack<Pack>` to expose a codec-aware `enumType`.
261
+ */
262
+ export function bindEnumType<CodecTypes extends CodecTypeMap>(): BoundEnumType<CodecTypes> {
263
+ return enumType;
264
+ }
265
+
266
+ /**
267
+ * Returns true when the value is an `EnumTypeHandle` produced by
268
+ * `enumType()`. Used in the lowering pipeline to detect enum handles
269
+ * in field state without importing the brand key at every call site.
270
+ */
271
+ export function isEnumTypeHandle(value: unknown): value is EnumTypeHandle {
272
+ return (
273
+ typeof value === 'object' &&
274
+ value !== null &&
275
+ Reflect.get(value, ENUM_TYPE_HANDLE_BRAND) === true
276
+ );
277
+ }
package/src/index.ts CHANGED
@@ -11,3 +11,17 @@ export type {
11
11
  } from './composed-helpers-scaffolding';
12
12
  export { createEntityHelpersFromNamespace } from './composed-helpers-scaffolding';
13
13
  export type { ForeignKeyDefaultsState, IndexDef } from './descriptors';
14
+ export type {
15
+ BoundEnumType,
16
+ CodecInput,
17
+ CodecTypeMap,
18
+ EnumMember,
19
+ EnumTypeHandle,
20
+ } from './enum-type';
21
+ export {
22
+ bindEnumType,
23
+ ENUM_TYPE_HANDLE_BRAND,
24
+ enumType,
25
+ isEnumTypeHandle,
26
+ member,
27
+ } from './enum-type';