@prisma-next/sql-contract-psl 0.13.0-dev.16 → 0.13.0-dev.18

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/provider.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as interpretPslDocumentToSqlContract } from "./interpreter-B_KtZusL.mjs";
1
+ import { t as interpretPslDocumentToSqlContract } from "./interpreter-CQdQo427.mjs";
2
2
  import { ifDefined } from "@prisma-next/utils/defined";
3
3
  import { notOk, ok } from "@prisma-next/utils/result";
4
4
  import { parsePslDocument } from "@prisma-next/psl-parser";
@@ -60,7 +60,8 @@ function prismaContract(schemaPath, options) {
60
60
  }
61
61
  const document = parsePslDocument({
62
62
  schema,
63
- sourceId: schemaPath
63
+ sourceId: schemaPath,
64
+ pslBlockDescriptors: context.authoringContributions.pslBlockDescriptors
64
65
  });
65
66
  const scalarTypeDescriptors = buildColumnDescriptorMap(context.scalarTypeDescriptors, context.codecLookup);
66
67
  const interpreted = interpretPslDocumentToSqlContract({
@@ -72,7 +73,8 @@ function prismaContract(schemaPath, options) {
72
73
  composedExtensionContracts: context.composedExtensionContracts,
73
74
  ...ifDefined("composedExtensionPackRefs", options.composedExtensionPackRefs?.length ? options.composedExtensionPackRefs : void 0),
74
75
  controlMutationDefaults: context.controlMutationDefaults,
75
- ...ifDefined("createNamespace", options.createNamespace)
76
+ ...ifDefined("createNamespace", options.createNamespace),
77
+ codecLookup: context.codecLookup
76
78
  });
77
79
  if (!interpreted.ok) return interpreted;
78
80
  return ok(applySpecifierDefaultControlPolicy(interpreted.value, options.defaultControlPolicy));
@@ -1 +1 @@
1
- {"version":3,"file":"provider.mjs","names":[],"sources":["../src/provider.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport type { ContractConfig } from '@prisma-next/config/config-types';\nimport { applySpecifierDefaultControlPolicy } from '@prisma-next/contract/apply-specifier-default-control-policy';\nimport type { ControlPolicy } from '@prisma-next/contract/types';\nimport type { CodecLookup } from '@prisma-next/framework-components/codec';\nimport type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';\nimport type { Namespace } from '@prisma-next/framework-components/ir';\nimport { parsePslDocument } from '@prisma-next/psl-parser';\nimport type { SqlNamespaceTablesInput } from '@prisma-next/sql-contract/types';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok } from '@prisma-next/utils/result';\nimport { basename, extname } from 'pathe';\nimport { interpretPslDocumentToSqlContract } from './interpreter';\nimport type { ColumnDescriptor } from './psl-column-resolution';\n\nexport interface PrismaContractOptions {\n readonly output?: string;\n readonly target: TargetPackRef<'sql', string>;\n readonly composedExtensionPackRefs?: readonly ExtensionPackRef<'sql', string>[];\n readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;\n readonly defaultControlPolicy?: ControlPolicy;\n}\n\n/**\n * Derives the emit output path from the schema input path so artefacts land\n * colocated with the source (e.g. `src/contract/schema.prisma` →\n * `src/contract/contract.json`). The provider owns this because it is the\n * only layer that knows the input path; the upstream `normalizeContractConfig`\n * default is a last-resort fallback for providers that don't carry one.\n */\nfunction defaultOutputFromSchemaPath(schemaPath: string): string {\n const ext = extname(schemaPath);\n if (ext.length === 0) return `${schemaPath}.json`;\n const base = schemaPath.slice(0, -ext.length);\n // PSL schemas commonly use `schema.prisma`; the emitted JSON is called\n // `contract.json` to mirror the rest of the toolchain, not `schema.json`.\n // Match only the exact basename `schema` so files like `my-schema.prisma`\n // are not silently rewritten to `my-contract.json`.\n if (basename(base) === 'schema') {\n return `${base.slice(0, -'schema'.length)}contract.json`;\n }\n return `${base}.json`;\n}\n\nfunction buildColumnDescriptorMap(\n scalarTypeDescriptors: ReadonlyMap<string, string>,\n codecLookup: CodecLookup,\n): ReadonlyMap<string, ColumnDescriptor> {\n const result = new Map<string, ColumnDescriptor>();\n for (const [typeName, codecId] of scalarTypeDescriptors) {\n const nativeType = codecLookup.targetTypesFor(codecId)?.[0];\n if (nativeType === undefined) continue;\n result.set(typeName, { codecId, nativeType });\n }\n return result;\n}\n\nexport function prismaContract(schemaPath: string, options: PrismaContractOptions): ContractConfig {\n return {\n source: {\n inputs: [schemaPath],\n load: async (context) => {\n const [absoluteSchemaPath] = context.resolvedInputs;\n if (absoluteSchemaPath === undefined) {\n throw new Error(\n 'prismaContract: context.resolvedInputs is empty. The CLI config loader should populate it positional-matched with source.inputs.',\n );\n }\n let schema: string;\n try {\n schema = await readFile(absoluteSchemaPath, 'utf-8');\n } catch (error) {\n const message = String(error);\n return notOk({\n summary: `Failed to read Prisma schema at \"${schemaPath}\"`,\n diagnostics: [\n {\n code: 'PSL_SCHEMA_READ_FAILED',\n message,\n sourceId: schemaPath,\n },\n ],\n meta: { schemaPath, absoluteSchemaPath, cause: message },\n });\n }\n\n const document = parsePslDocument({\n schema,\n sourceId: schemaPath,\n });\n\n const scalarTypeDescriptors = buildColumnDescriptorMap(\n context.scalarTypeDescriptors,\n context.codecLookup,\n );\n\n const interpreted = interpretPslDocumentToSqlContract({\n document,\n target: options.target,\n authoringContributions: context.authoringContributions,\n scalarTypeDescriptors,\n ...ifDefined(\n 'composedExtensionPacks',\n context.composedExtensionPacks.length > 0\n ? [...context.composedExtensionPacks]\n : undefined,\n ),\n composedExtensionContracts: context.composedExtensionContracts,\n ...ifDefined(\n 'composedExtensionPackRefs',\n options.composedExtensionPackRefs?.length\n ? options.composedExtensionPackRefs\n : undefined,\n ),\n controlMutationDefaults: context.controlMutationDefaults,\n ...ifDefined('createNamespace', options.createNamespace),\n });\n if (!interpreted.ok) {\n return interpreted;\n }\n\n return ok(\n applySpecifierDefaultControlPolicy(interpreted.value, options.defaultControlPolicy),\n );\n },\n },\n output: options.output ?? defaultOutputFromSchemaPath(schemaPath),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AA8BA,SAAS,4BAA4B,YAA4B;CAC/D,MAAM,MAAM,QAAQ,UAAU;CAC9B,IAAI,IAAI,WAAW,GAAG,OAAO,GAAG,WAAW;CAC3C,MAAM,OAAO,WAAW,MAAM,GAAG,CAAC,IAAI,MAAM;CAK5C,IAAI,SAAS,IAAI,MAAM,UACrB,OAAO,GAAG,KAAK,MAAM,GAAG,EAAgB,EAAE;CAE5C,OAAO,GAAG,KAAK;AACjB;AAEA,SAAS,yBACP,uBACA,aACuC;CACvC,MAAM,yBAAS,IAAI,IAA8B;CACjD,KAAK,MAAM,CAAC,UAAU,YAAY,uBAAuB;EACvD,MAAM,aAAa,YAAY,eAAe,OAAO,CAAC,GAAG;EACzD,IAAI,eAAe,KAAA,GAAW;EAC9B,OAAO,IAAI,UAAU;GAAE;GAAS;EAAW,CAAC;CAC9C;CACA,OAAO;AACT;AAEA,SAAgB,eAAe,YAAoB,SAAgD;CACjG,OAAO;EACL,QAAQ;GACN,QAAQ,CAAC,UAAU;GACnB,MAAM,OAAO,YAAY;IACvB,MAAM,CAAC,sBAAsB,QAAQ;IACrC,IAAI,uBAAuB,KAAA,GACzB,MAAM,IAAI,MACR,kIACF;IAEF,IAAI;IACJ,IAAI;KACF,SAAS,MAAM,SAAS,oBAAoB,OAAO;IACrD,SAAS,OAAO;KACd,MAAM,UAAU,OAAO,KAAK;KAC5B,OAAO,MAAM;MACX,SAAS,oCAAoC,WAAW;MACxD,aAAa,CACX;OACE,MAAM;OACN;OACA,UAAU;MACZ,CACF;MACA,MAAM;OAAE;OAAY;OAAoB,OAAO;MAAQ;KACzD,CAAC;IACH;IAEA,MAAM,WAAW,iBAAiB;KAChC;KACA,UAAU;IACZ,CAAC;IAED,MAAM,wBAAwB,yBAC5B,QAAQ,uBACR,QAAQ,WACV;IAEA,MAAM,cAAc,kCAAkC;KACpD;KACA,QAAQ,QAAQ;KAChB,wBAAwB,QAAQ;KAChC;KACA,GAAG,UACD,0BACA,QAAQ,uBAAuB,SAAS,IACpC,CAAC,GAAG,QAAQ,sBAAsB,IAClC,KAAA,CACN;KACA,4BAA4B,QAAQ;KACpC,GAAG,UACD,6BACA,QAAQ,2BAA2B,SAC/B,QAAQ,4BACR,KAAA,CACN;KACA,yBAAyB,QAAQ;KACjC,GAAG,UAAU,mBAAmB,QAAQ,eAAe;IACzD,CAAC;IACD,IAAI,CAAC,YAAY,IACf,OAAO;IAGT,OAAO,GACL,mCAAmC,YAAY,OAAO,QAAQ,oBAAoB,CACpF;GACF;EACF;EACA,QAAQ,QAAQ,UAAU,4BAA4B,UAAU;CAClE;AACF"}
1
+ {"version":3,"file":"provider.mjs","names":[],"sources":["../src/provider.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport type { ContractConfig } from '@prisma-next/config/config-types';\nimport { applySpecifierDefaultControlPolicy } from '@prisma-next/contract/apply-specifier-default-control-policy';\nimport type { ControlPolicy } from '@prisma-next/contract/types';\nimport type { CodecLookup } from '@prisma-next/framework-components/codec';\nimport type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';\nimport type { Namespace } from '@prisma-next/framework-components/ir';\nimport { parsePslDocument } from '@prisma-next/psl-parser';\nimport type { SqlNamespaceTablesInput } from '@prisma-next/sql-contract/types';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok } from '@prisma-next/utils/result';\nimport { basename, extname } from 'pathe';\nimport { interpretPslDocumentToSqlContract } from './interpreter';\nimport type { ColumnDescriptor } from './psl-column-resolution';\n\nexport interface PrismaContractOptions {\n readonly output?: string;\n readonly target: TargetPackRef<'sql', string>;\n readonly composedExtensionPackRefs?: readonly ExtensionPackRef<'sql', string>[];\n readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;\n readonly defaultControlPolicy?: ControlPolicy;\n}\n\n/**\n * Derives the emit output path from the schema input path so artefacts land\n * colocated with the source (e.g. `src/contract/schema.prisma` →\n * `src/contract/contract.json`). The provider owns this because it is the\n * only layer that knows the input path; the upstream `normalizeContractConfig`\n * default is a last-resort fallback for providers that don't carry one.\n */\nfunction defaultOutputFromSchemaPath(schemaPath: string): string {\n const ext = extname(schemaPath);\n if (ext.length === 0) return `${schemaPath}.json`;\n const base = schemaPath.slice(0, -ext.length);\n // PSL schemas commonly use `schema.prisma`; the emitted JSON is called\n // `contract.json` to mirror the rest of the toolchain, not `schema.json`.\n // Match only the exact basename `schema` so files like `my-schema.prisma`\n // are not silently rewritten to `my-contract.json`.\n if (basename(base) === 'schema') {\n return `${base.slice(0, -'schema'.length)}contract.json`;\n }\n return `${base}.json`;\n}\n\nfunction buildColumnDescriptorMap(\n scalarTypeDescriptors: ReadonlyMap<string, string>,\n codecLookup: CodecLookup,\n): ReadonlyMap<string, ColumnDescriptor> {\n const result = new Map<string, ColumnDescriptor>();\n for (const [typeName, codecId] of scalarTypeDescriptors) {\n const nativeType = codecLookup.targetTypesFor(codecId)?.[0];\n if (nativeType === undefined) continue;\n result.set(typeName, { codecId, nativeType });\n }\n return result;\n}\n\nexport function prismaContract(schemaPath: string, options: PrismaContractOptions): ContractConfig {\n return {\n source: {\n inputs: [schemaPath],\n load: async (context) => {\n const [absoluteSchemaPath] = context.resolvedInputs;\n if (absoluteSchemaPath === undefined) {\n throw new Error(\n 'prismaContract: context.resolvedInputs is empty. The CLI config loader should populate it positional-matched with source.inputs.',\n );\n }\n let schema: string;\n try {\n schema = await readFile(absoluteSchemaPath, 'utf-8');\n } catch (error) {\n const message = String(error);\n return notOk({\n summary: `Failed to read Prisma schema at \"${schemaPath}\"`,\n diagnostics: [\n {\n code: 'PSL_SCHEMA_READ_FAILED',\n message,\n sourceId: schemaPath,\n },\n ],\n meta: { schemaPath, absoluteSchemaPath, cause: message },\n });\n }\n\n const document = parsePslDocument({\n schema,\n sourceId: schemaPath,\n pslBlockDescriptors: context.authoringContributions.pslBlockDescriptors,\n });\n\n const scalarTypeDescriptors = buildColumnDescriptorMap(\n context.scalarTypeDescriptors,\n context.codecLookup,\n );\n\n const interpreted = interpretPslDocumentToSqlContract({\n document,\n target: options.target,\n authoringContributions: context.authoringContributions,\n scalarTypeDescriptors,\n ...ifDefined(\n 'composedExtensionPacks',\n context.composedExtensionPacks.length > 0\n ? [...context.composedExtensionPacks]\n : undefined,\n ),\n composedExtensionContracts: context.composedExtensionContracts,\n ...ifDefined(\n 'composedExtensionPackRefs',\n options.composedExtensionPackRefs?.length\n ? options.composedExtensionPackRefs\n : undefined,\n ),\n controlMutationDefaults: context.controlMutationDefaults,\n ...ifDefined('createNamespace', options.createNamespace),\n codecLookup: context.codecLookup,\n });\n if (!interpreted.ok) {\n return interpreted;\n }\n\n return ok(\n applySpecifierDefaultControlPolicy(interpreted.value, options.defaultControlPolicy),\n );\n },\n },\n output: options.output ?? defaultOutputFromSchemaPath(schemaPath),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AA8BA,SAAS,4BAA4B,YAA4B;CAC/D,MAAM,MAAM,QAAQ,UAAU;CAC9B,IAAI,IAAI,WAAW,GAAG,OAAO,GAAG,WAAW;CAC3C,MAAM,OAAO,WAAW,MAAM,GAAG,CAAC,IAAI,MAAM;CAK5C,IAAI,SAAS,IAAI,MAAM,UACrB,OAAO,GAAG,KAAK,MAAM,GAAG,EAAgB,EAAE;CAE5C,OAAO,GAAG,KAAK;AACjB;AAEA,SAAS,yBACP,uBACA,aACuC;CACvC,MAAM,yBAAS,IAAI,IAA8B;CACjD,KAAK,MAAM,CAAC,UAAU,YAAY,uBAAuB;EACvD,MAAM,aAAa,YAAY,eAAe,OAAO,CAAC,GAAG;EACzD,IAAI,eAAe,KAAA,GAAW;EAC9B,OAAO,IAAI,UAAU;GAAE;GAAS;EAAW,CAAC;CAC9C;CACA,OAAO;AACT;AAEA,SAAgB,eAAe,YAAoB,SAAgD;CACjG,OAAO;EACL,QAAQ;GACN,QAAQ,CAAC,UAAU;GACnB,MAAM,OAAO,YAAY;IACvB,MAAM,CAAC,sBAAsB,QAAQ;IACrC,IAAI,uBAAuB,KAAA,GACzB,MAAM,IAAI,MACR,kIACF;IAEF,IAAI;IACJ,IAAI;KACF,SAAS,MAAM,SAAS,oBAAoB,OAAO;IACrD,SAAS,OAAO;KACd,MAAM,UAAU,OAAO,KAAK;KAC5B,OAAO,MAAM;MACX,SAAS,oCAAoC,WAAW;MACxD,aAAa,CACX;OACE,MAAM;OACN;OACA,UAAU;MACZ,CACF;MACA,MAAM;OAAE;OAAY;OAAoB,OAAO;MAAQ;KACzD,CAAC;IACH;IAEA,MAAM,WAAW,iBAAiB;KAChC;KACA,UAAU;KACV,qBAAqB,QAAQ,uBAAuB;IACtD,CAAC;IAED,MAAM,wBAAwB,yBAC5B,QAAQ,uBACR,QAAQ,WACV;IAEA,MAAM,cAAc,kCAAkC;KACpD;KACA,QAAQ,QAAQ;KAChB,wBAAwB,QAAQ;KAChC;KACA,GAAG,UACD,0BACA,QAAQ,uBAAuB,SAAS,IACpC,CAAC,GAAG,QAAQ,sBAAsB,IAClC,KAAA,CACN;KACA,4BAA4B,QAAQ;KACpC,GAAG,UACD,6BACA,QAAQ,2BAA2B,SAC/B,QAAQ,4BACR,KAAA,CACN;KACA,yBAAyB,QAAQ;KACjC,GAAG,UAAU,mBAAmB,QAAQ,eAAe;KACvD,aAAa,QAAQ;IACvB,CAAC;IACD,IAAI,CAAC,YAAY,IACf,OAAO;IAGT,OAAO,GACL,mCAAmC,YAAY,OAAO,QAAQ,oBAAoB,CACpF;GACF;EACF;EACA,QAAQ,QAAQ,UAAU,4BAA4B,UAAU;CAClE;AACF"}
package/package.json CHANGED
@@ -1,25 +1,25 @@
1
1
  {
2
2
  "name": "@prisma-next/sql-contract-psl",
3
- "version": "0.13.0-dev.16",
3
+ "version": "0.13.0-dev.18",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "description": "PSL-to-SQL ContractIR interpreter for Prisma Next",
8
8
  "dependencies": {
9
- "@prisma-next/config": "0.13.0-dev.16",
10
- "@prisma-next/contract": "0.13.0-dev.16",
11
- "@prisma-next/framework-components": "0.13.0-dev.16",
12
- "@prisma-next/psl-parser": "0.13.0-dev.16",
13
- "@prisma-next/sql-contract": "0.13.0-dev.16",
14
- "@prisma-next/sql-contract-ts": "0.13.0-dev.16",
15
- "@prisma-next/utils": "0.13.0-dev.16",
9
+ "@prisma-next/config": "0.13.0-dev.18",
10
+ "@prisma-next/contract": "0.13.0-dev.18",
11
+ "@prisma-next/framework-components": "0.13.0-dev.18",
12
+ "@prisma-next/psl-parser": "0.13.0-dev.18",
13
+ "@prisma-next/sql-contract": "0.13.0-dev.18",
14
+ "@prisma-next/sql-contract-ts": "0.13.0-dev.18",
15
+ "@prisma-next/utils": "0.13.0-dev.18",
16
16
  "pathe": "^2.0.3"
17
17
  },
18
18
  "devDependencies": {
19
- "@prisma-next/contract-authoring": "0.13.0-dev.16",
20
- "@prisma-next/test-utils": "0.13.0-dev.16",
21
- "@prisma-next/tsconfig": "0.13.0-dev.16",
22
- "@prisma-next/tsdown": "0.13.0-dev.16",
19
+ "@prisma-next/contract-authoring": "0.13.0-dev.18",
20
+ "@prisma-next/test-utils": "0.13.0-dev.18",
21
+ "@prisma-next/tsconfig": "0.13.0-dev.18",
22
+ "@prisma-next/tsdown": "0.13.0-dev.18",
23
23
  "arktype": "^2.2.0",
24
24
  "tsdown": "0.22.1",
25
25
  "typescript": "5.9.3",
@@ -17,6 +17,7 @@ import type {
17
17
  AuthoringEntityTypeDescriptor,
18
18
  } from '@prisma-next/framework-components/authoring';
19
19
  import { instantiateAuthoringEntityType } from '@prisma-next/framework-components/authoring';
20
+ import type { CodecLookup } from '@prisma-next/framework-components/codec';
20
21
  import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';
21
22
  import type {
22
23
  ControlMutationDefaultRegistry,
@@ -24,11 +25,13 @@ import type {
24
25
  MutationDefaultGeneratorDescriptor,
25
26
  } from '@prisma-next/framework-components/control';
26
27
  import type { Namespace } from '@prisma-next/framework-components/ir';
28
+ import { namespacePslExtensionBlocks } from '@prisma-next/framework-components/psl-ast';
27
29
  import type {
28
30
  ParsePslDocumentResult,
29
31
  PslAttribute,
30
32
  PslCompositeType,
31
33
  PslEnum,
34
+ PslExtensionBlock,
32
35
  PslField,
33
36
  PslModel,
34
37
  PslNamedTypeDeclaration,
@@ -43,6 +46,7 @@ import {
43
46
  } from '@prisma-next/sql-contract/types';
44
47
  import {
45
48
  buildSqlContractFromDefinition,
49
+ type EnumTypeHandle,
46
50
  type FieldNode,
47
51
  type ForeignKeyNode,
48
52
  type IndexNode,
@@ -124,6 +128,7 @@ export interface InterpretPslDocumentToSqlContractInput {
124
128
  * `SqlUnboundNamespace` singleton.
125
129
  */
126
130
  readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
131
+ readonly codecLookup?: CodecLookup;
127
132
  }
128
133
 
129
134
  function buildComposedExtensionPackRefs(
@@ -375,6 +380,59 @@ function processEnumDeclarations(input: ProcessEnumDeclarationsInput): {
375
380
  return { storageTypes, enumTypeDescriptors };
376
381
  }
377
382
 
383
+ interface ProcessEnum2DeclarationsInput {
384
+ readonly enum2Blocks: readonly PslExtensionBlock[];
385
+ readonly sourceId: string;
386
+ readonly authoringContributions: AuthoringContributions | undefined;
387
+ readonly entityContext: AuthoringEntityContext;
388
+ readonly diagnostics: ContractSourceDiagnostic[];
389
+ }
390
+
391
+ function processEnum2Declarations(input: ProcessEnum2DeclarationsInput): {
392
+ readonly enumHandles: Record<string, EnumTypeHandle>;
393
+ readonly enumTypeDescriptors: Map<string, ColumnDescriptor>;
394
+ } {
395
+ const enumHandles: Record<string, EnumTypeHandle> = {};
396
+ const enumTypeDescriptors = new Map<string, ColumnDescriptor>();
397
+
398
+ if (input.enum2Blocks.length === 0) {
399
+ return { enumHandles, enumTypeDescriptors };
400
+ }
401
+
402
+ const enum2EntityDescriptor = getAuthoringEntity(input.authoringContributions, ['enum2']);
403
+ if (!enum2EntityDescriptor) {
404
+ for (const decl of input.enum2Blocks) {
405
+ input.diagnostics.push({
406
+ code: 'PSL_ENUM2_MISSING_FACTORY',
407
+ message: `enum2 "${decl.name}" requires an "enum2" entityType factory in the active authoring contributions`,
408
+ sourceId: input.sourceId,
409
+ span: decl.span,
410
+ });
411
+ }
412
+ return { enumHandles, enumTypeDescriptors };
413
+ }
414
+
415
+ for (const decl of input.enum2Blocks) {
416
+ const handle = instantiateAuthoringEntityType(
417
+ 'enum2',
418
+ enum2EntityDescriptor,
419
+ [decl],
420
+ input.entityContext,
421
+ );
422
+
423
+ if (handle === undefined || handle === null) continue;
424
+
425
+ const enumHandle = blindCast<EnumTypeHandle, 'enum2 factory returns EnumTypeHandle'>(handle);
426
+ enumHandles[decl.name] = enumHandle;
427
+ enumTypeDescriptors.set(decl.name, {
428
+ codecId: enumHandle.codecId,
429
+ nativeType: enumHandle.nativeType,
430
+ });
431
+ }
432
+
433
+ return { enumHandles, enumTypeDescriptors };
434
+ }
435
+
378
436
  interface ResolveNamedTypeDeclarationsInput {
379
437
  readonly declarations: readonly PslNamedTypeDeclaration[];
380
438
  readonly sourceId: string;
@@ -619,6 +677,7 @@ interface BuildModelNodeInput {
619
677
  readonly diagnostics: ContractSourceDiagnostic[];
620
678
  /** Resolved namespace id keyed by model name — used to stamp the target namespace on FKs. */
621
679
  readonly modelNamespaceIds: ReadonlyMap<string, string>;
680
+ readonly enum2Handles?: ReadonlyMap<string, EnumTypeHandle>;
622
681
  }
623
682
 
624
683
  interface BuildModelNodeResult {
@@ -650,6 +709,7 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
650
709
  diagnostics,
651
710
  sourceId,
652
711
  scalarTypeDescriptors: input.scalarTypeDescriptors,
712
+ ...ifDefined('enum2Handles', input.enum2Handles),
653
713
  });
654
714
 
655
715
  const inlineIdFields = resolvedFields.filter((field) => field.isId);
@@ -1320,14 +1380,18 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
1320
1380
  modelNode: {
1321
1381
  modelName: model.name,
1322
1382
  tableName,
1323
- fields: resolvedFields.map((resolvedField) => ({
1324
- fieldName: resolvedField.field.name,
1325
- columnName: resolvedField.columnName,
1326
- descriptor: resolvedField.descriptor,
1327
- nullable: resolvedField.field.optional,
1328
- ...ifDefined('default', resolvedField.defaultValue),
1329
- ...ifDefined('executionDefaults', resolvedField.executionDefaults),
1330
- })),
1383
+ fields: resolvedFields.map((resolvedField) => {
1384
+ const enumHandle = input.enum2Handles?.get(resolvedField.field.typeName);
1385
+ return {
1386
+ fieldName: resolvedField.field.name,
1387
+ columnName: resolvedField.columnName,
1388
+ descriptor: resolvedField.descriptor,
1389
+ nullable: resolvedField.field.optional,
1390
+ ...ifDefined('default', resolvedField.defaultValue),
1391
+ ...ifDefined('executionDefaults', resolvedField.executionDefaults),
1392
+ ...ifDefined('enumTypeHandle', enumHandle),
1393
+ };
1394
+ }),
1331
1395
  ...ifDefined('id', primaryKey),
1332
1396
  ...(uniqueConstraints.length > 0 ? { uniques: uniqueConstraints } : {}),
1333
1397
  ...(indexNodes.length > 0 ? { indexes: indexNodes } : {}),
@@ -2006,6 +2070,68 @@ export function interpretPslDocumentToSqlContract(
2006
2070
  }
2007
2071
  }
2008
2072
 
2073
+ const topLevelEnum2s = input.document.ast.namespaces
2074
+ .filter((ns) => ns.name === UNSPECIFIED_PSL_NAMESPACE_NAME)
2075
+ .flatMap((ns) => namespacePslExtensionBlocks(ns).filter((b) => b.kind === 'enum2'));
2076
+ for (const ns of input.document.ast.namespaces) {
2077
+ if (ns.name === UNSPECIFIED_PSL_NAMESPACE_NAME) continue;
2078
+ const nsEnum2s = namespacePslExtensionBlocks(ns).filter((b) => b.kind === 'enum2');
2079
+ if (nsEnum2s.length === 0) continue;
2080
+ for (const decl of nsEnum2s) {
2081
+ diagnostics.push({
2082
+ code: 'PSL_ENUM2_NAMESPACE_NOT_SUPPORTED',
2083
+ message: `enum2 "${decl.name}" inside namespace "${ns.name}" is not supported; declare enum2 at the top level`,
2084
+ sourceId,
2085
+ span: decl.span,
2086
+ });
2087
+ }
2088
+ }
2089
+
2090
+ const enum2Result = processEnum2Declarations({
2091
+ enum2Blocks: topLevelEnum2s,
2092
+ sourceId,
2093
+ authoringContributions: input.authoringContributions,
2094
+ entityContext: {
2095
+ family: input.target.familyId,
2096
+ target: input.target.targetId,
2097
+ ...ifDefined('codecLookup', input.codecLookup),
2098
+ sourceId,
2099
+ diagnostics: {
2100
+ push: (d) => {
2101
+ diagnostics.push(
2102
+ blindCast<ContractSourceDiagnostic, 'sink diagnostics are span-compatible'>(d),
2103
+ );
2104
+ },
2105
+ },
2106
+ },
2107
+ diagnostics,
2108
+ });
2109
+
2110
+ const collidingEnum2Names = new Set<string>();
2111
+ for (const [name, descriptor] of enum2Result.enumTypeDescriptors) {
2112
+ if (allEnumTypeDescriptors.has(name)) {
2113
+ collidingEnum2Names.add(name);
2114
+ const collision = topLevelEnum2s.find((e) => e.name === name);
2115
+ diagnostics.push({
2116
+ code: 'PSL_ENUM2_DUPLICATE_TYPE_NAME',
2117
+ message: `enum2 "${name}" collides with an existing type name; each type name must be unique`,
2118
+ sourceId,
2119
+ ...ifDefined('span', collision?.span),
2120
+ });
2121
+ } else {
2122
+ allEnumTypeDescriptors.set(name, descriptor);
2123
+ }
2124
+ }
2125
+
2126
+ const validEnum2Handles: Record<string, EnumTypeHandle> = {};
2127
+ for (const [name, handle] of Object.entries(enum2Result.enumHandles)) {
2128
+ if (!collidingEnum2Names.has(name)) {
2129
+ validEnum2Handles[name] = handle;
2130
+ }
2131
+ }
2132
+
2133
+ const enum2HandlesByName = new Map(Object.entries(validEnum2Handles));
2134
+
2009
2135
  const namedTypeResult = resolveNamedTypeDeclarations({
2010
2136
  declarations: input.document.ast.types?.declarations ?? [],
2011
2137
  sourceId,
@@ -2068,6 +2194,7 @@ export function interpretPslDocumentToSqlContract(
2068
2194
  sourceId,
2069
2195
  diagnostics,
2070
2196
  modelNamespaceIds,
2197
+ ...(enum2HandlesByName.size > 0 ? { enum2Handles: enum2HandlesByName } : {}),
2071
2198
  });
2072
2199
  modelNodes.push(
2073
2200
  namespaceId !== undefined ? { ...result.modelNode, namespaceId } : result.modelNode,
@@ -2162,6 +2289,7 @@ export function interpretPslDocumentToSqlContract(
2162
2289
  ...(Object.keys(namespaceEnumStorageTypes).length > 0
2163
2290
  ? { namespaceTypes: namespaceEnumStorageTypes }
2164
2291
  : {}),
2292
+ ...(Object.keys(validEnum2Handles).length > 0 ? { enums: validEnum2Handles } : {}),
2165
2293
  ...ifDefined('createNamespace', input.createNamespace),
2166
2294
  models: stiColumnModelNodes.map((model) => ({
2167
2295
  ...model,
@@ -2233,6 +2361,7 @@ export function interpretPslDocumentToSqlContract(
2233
2361
  patchedModels[modelCoordinateKey(namespaceId, modelName)] ?? model,
2234
2362
  ]),
2235
2363
  ),
2364
+ ...(namespaceSlice.enum !== undefined ? { enum: namespaceSlice.enum } : {}),
2236
2365
  ...(namespaceSlice.valueObjects !== undefined
2237
2366
  ? { valueObjects: namespaceSlice.valueObjects }
2238
2367
  : {}),
package/src/provider.ts CHANGED
@@ -87,6 +87,7 @@ export function prismaContract(schemaPath: string, options: PrismaContractOption
87
87
  const document = parsePslDocument({
88
88
  schema,
89
89
  sourceId: schemaPath,
90
+ pslBlockDescriptors: context.authoringContributions.pslBlockDescriptors,
90
91
  });
91
92
 
92
93
  const scalarTypeDescriptors = buildColumnDescriptorMap(
@@ -114,6 +115,7 @@ export function prismaContract(schemaPath: string, options: PrismaContractOption
114
115
  ),
115
116
  controlMutationDefaults: context.controlMutationDefaults,
116
117
  ...ifDefined('createNamespace', options.createNamespace),
118
+ codecLookup: context.codecLookup,
117
119
  });
118
120
  if (!interpreted.ok) {
119
121
  return interpreted;
@@ -1,14 +1,21 @@
1
1
  import type { ContractSourceDiagnostic } from '@prisma-next/config/config-types';
2
- import type { ColumnDefault, ExecutionMutationDefaultPhases } from '@prisma-next/contract/types';
2
+ import type {
3
+ ColumnDefault,
4
+ ColumnDefaultLiteralInputValue,
5
+ ExecutionMutationDefaultPhases,
6
+ } from '@prisma-next/contract/types';
3
7
  import type { AuthoringContributions } from '@prisma-next/framework-components/authoring';
4
8
  import type {
5
9
  ControlMutationDefaultRegistry,
6
10
  MutationDefaultGeneratorDescriptor,
7
11
  } from '@prisma-next/framework-components/control';
8
12
  import type { PslAttribute, PslField, PslModel } from '@prisma-next/psl-parser';
13
+ import type { EnumTypeHandle } from '@prisma-next/sql-contract-ts/contract-builder';
14
+ import { blindCast } from '@prisma-next/utils/casts';
9
15
  import { ifDefined } from '@prisma-next/utils/defined';
10
16
  import {
11
17
  getAttribute,
18
+ getPositionalArgumentEntry,
12
19
  lowerFirst,
13
20
  parseConstraintMapArgument,
14
21
  parseMapName,
@@ -21,6 +28,61 @@ import {
21
28
  resolveFieldTypeDescriptor,
22
29
  } from './psl-column-resolution';
23
30
 
31
+ type LoweredFieldDefault = {
32
+ readonly defaultValue?: ColumnDefault;
33
+ readonly executionDefaults?: ExecutionMutationDefaultPhases;
34
+ };
35
+
36
+ function lowerEnum2DefaultForField(input: {
37
+ readonly modelName: string;
38
+ readonly fieldName: string;
39
+ readonly defaultAttribute: PslAttribute;
40
+ readonly enumHandle: EnumTypeHandle;
41
+ readonly sourceId: string;
42
+ readonly diagnostics: ContractSourceDiagnostic[];
43
+ }): LoweredFieldDefault {
44
+ const expressionEntry = getPositionalArgumentEntry(input.defaultAttribute);
45
+ if (!expressionEntry) {
46
+ return {};
47
+ }
48
+
49
+ const raw = expressionEntry.value.trim();
50
+ const isQuotedString = /^(['"]).*\1$/.test(raw);
51
+ const isFunctionCall = raw.includes('(') && raw.endsWith(')');
52
+
53
+ if (isQuotedString || isFunctionCall) {
54
+ input.diagnostics.push({
55
+ code: 'PSL_ENUM2_DEFAULT_MUST_BE_MEMBER_NAME',
56
+ message: `Field "${input.modelName}.${input.fieldName}" @default on an enum2 field must name a member (e.g. @default(Low)), not a raw value or function.`,
57
+ sourceId: input.sourceId,
58
+ span: input.defaultAttribute.span,
59
+ });
60
+ return {};
61
+ }
62
+
63
+ const match = input.enumHandle.enumMembers.find((m) => m.name === raw);
64
+ if (!match) {
65
+ const validNames = input.enumHandle.enumMembers.map((m) => m.name).join(', ');
66
+ input.diagnostics.push({
67
+ code: 'PSL_ENUM2_UNKNOWN_DEFAULT_MEMBER',
68
+ message: `Field "${input.modelName}.${input.fieldName}" @default(${raw}) does not name a member of ${input.enumHandle.enumName}. Valid members: ${validNames}.`,
69
+ sourceId: input.sourceId,
70
+ span: input.defaultAttribute.span,
71
+ });
72
+ return {};
73
+ }
74
+
75
+ return {
76
+ defaultValue: {
77
+ kind: 'literal',
78
+ value: blindCast<
79
+ ColumnDefaultLiteralInputValue,
80
+ 'enum member values are codec-validated JsonValue-compatible scalars'
81
+ >(match.value),
82
+ },
83
+ };
84
+ }
85
+
24
86
  export type ResolvedField = {
25
87
  readonly field: PslField;
26
88
  readonly columnName: string;
@@ -76,6 +138,7 @@ export interface CollectResolvedFieldsInput {
76
138
  readonly diagnostics: ContractSourceDiagnostic[];
77
139
  readonly sourceId: string;
78
140
  readonly scalarTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
141
+ readonly enum2Handles?: ReadonlyMap<string, EnumTypeHandle>;
79
142
  }
80
143
 
81
144
  const BUILTIN_FIELD_ATTRIBUTE_NAMES: ReadonlySet<string> = new Set([
@@ -225,6 +288,7 @@ export function collectResolvedFields(input: CollectResolvedFieldsInput): Resolv
225
288
  diagnostics,
226
289
  sourceId,
227
290
  scalarTypeDescriptors,
291
+ enum2Handles,
228
292
  } = input;
229
293
  const resolvedFields: ResolvedField[] = [];
230
294
 
@@ -350,17 +414,27 @@ export function collectResolvedFields(input: CollectResolvedFieldsInput): Resolv
350
414
  });
351
415
  continue;
352
416
  }
353
- const loweredDefault = defaultAttribute
354
- ? lowerDefaultForField({
355
- modelName: model.name,
356
- fieldName: field.name,
357
- defaultAttribute,
358
- columnDescriptor: descriptor,
359
- generatorDescriptorById,
360
- sourceId,
361
- defaultFunctionRegistry,
362
- diagnostics,
363
- })
417
+ const enum2Handle = enum2Handles?.get(field.typeName);
418
+ const loweredDefault: LoweredFieldDefault = defaultAttribute
419
+ ? enum2Handle
420
+ ? lowerEnum2DefaultForField({
421
+ modelName: model.name,
422
+ fieldName: field.name,
423
+ defaultAttribute,
424
+ enumHandle: enum2Handle,
425
+ sourceId,
426
+ diagnostics,
427
+ })
428
+ : lowerDefaultForField({
429
+ modelName: model.name,
430
+ fieldName: field.name,
431
+ defaultAttribute,
432
+ columnDescriptor: descriptor,
433
+ generatorDescriptorById,
434
+ sourceId,
435
+ defaultFunctionRegistry,
436
+ diagnostics,
437
+ })
364
438
  : {};
365
439
  const loweredOnCreate = loweredDefault.executionDefaults?.onCreate;
366
440
  if (field.optional && loweredOnCreate) {