@prisma-next/sql-contract-psl 0.12.0-dev.4 → 0.12.0-dev.41

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.
@@ -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 type { CodecLookup } from '@prisma-next/framework-components/codec';\nimport type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';\nimport { parsePslDocument } from '@prisma-next/psl-parser';\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}\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 ...ifDefined(\n 'composedExtensionPackRefs',\n options.composedExtensionPackRefs?.length\n ? options.composedExtensionPackRefs\n : undefined,\n ),\n controlMutationDefaults: context.controlMutationDefaults,\n });\n if (!interpreted.ok) {\n return interpreted;\n }\n\n return ok(interpreted.value);\n },\n },\n output: options.output ?? defaultOutputFromSchemaPath(schemaPath),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAwBA,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,IAAI;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,GAAG,UACD,6BACA,QAAQ,2BAA2B,SAC/B,QAAQ,4BACR,KAAA,CACN;KACA,yBAAyB,QAAQ;IACnC,CAAC;IACD,IAAI,CAAC,YAAY,IACf,OAAO;IAGT,OAAO,GAAG,YAAY,KAAK;GAC7B;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 });\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 ...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,IAAI;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,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"}
package/package.json CHANGED
@@ -1,25 +1,25 @@
1
1
  {
2
2
  "name": "@prisma-next/sql-contract-psl",
3
- "version": "0.12.0-dev.4",
3
+ "version": "0.12.0-dev.41",
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.12.0-dev.4",
10
- "@prisma-next/contract": "0.12.0-dev.4",
11
- "@prisma-next/framework-components": "0.12.0-dev.4",
12
- "@prisma-next/psl-parser": "0.12.0-dev.4",
13
- "@prisma-next/sql-contract": "0.12.0-dev.4",
14
- "@prisma-next/sql-contract-ts": "0.12.0-dev.4",
15
- "@prisma-next/utils": "0.12.0-dev.4",
9
+ "@prisma-next/config": "0.12.0-dev.41",
10
+ "@prisma-next/contract": "0.12.0-dev.41",
11
+ "@prisma-next/framework-components": "0.12.0-dev.41",
12
+ "@prisma-next/psl-parser": "0.12.0-dev.41",
13
+ "@prisma-next/sql-contract": "0.12.0-dev.41",
14
+ "@prisma-next/sql-contract-ts": "0.12.0-dev.41",
15
+ "@prisma-next/utils": "0.12.0-dev.41",
16
16
  "pathe": "^2.0.3"
17
17
  },
18
18
  "devDependencies": {
19
- "@prisma-next/contract-authoring": "0.12.0-dev.4",
20
- "@prisma-next/test-utils": "0.12.0-dev.4",
21
- "@prisma-next/tsconfig": "0.12.0-dev.4",
22
- "@prisma-next/tsdown": "0.12.0-dev.4",
19
+ "@prisma-next/contract-authoring": "0.12.0-dev.41",
20
+ "@prisma-next/test-utils": "0.12.0-dev.41",
21
+ "@prisma-next/tsconfig": "0.12.0-dev.41",
22
+ "@prisma-next/tsdown": "0.12.0-dev.41",
23
23
  "arktype": "^2.2.0",
24
24
  "tsdown": "0.22.0",
25
25
  "typescript": "5.9.3",
@@ -8,6 +8,7 @@ import type {
8
8
  ContractField,
9
9
  ContractModel,
10
10
  ContractValueObject,
11
+ ControlPolicy,
11
12
  } from '@prisma-next/contract/types';
12
13
  import { crossRef } from '@prisma-next/contract/types';
13
14
  import type {
@@ -23,7 +24,6 @@ import type {
23
24
  MutationDefaultGeneratorDescriptor,
24
25
  } from '@prisma-next/framework-components/control';
25
26
  import type { Namespace } from '@prisma-next/framework-components/ir';
26
- import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
27
27
  import type {
28
28
  ParsePslDocumentResult,
29
29
  PslAttribute,
@@ -58,6 +58,7 @@ import {
58
58
  mapFieldNamesToColumns,
59
59
  parseAttributeFieldList,
60
60
  parseConstraintMapArgument,
61
+ parseControlPolicyAttribute,
61
62
  parseMapName,
62
63
  parseObjectLiteralStringMap,
63
64
  parseQuotedStringLiteral,
@@ -229,10 +230,6 @@ const UNSPECIFIED_PSL_NAMESPACE_NAME = '__unspecified__';
229
230
  * slot empty (which means the late-bound default at the `StorageTable`
230
231
  * layer; emitted JSON omits the field).
231
232
  */
232
- function defaultSqlNamespaceIdForTarget(targetId: string): string {
233
- return targetId === 'postgres' ? 'public' : UNBOUND_NAMESPACE_ID;
234
- }
235
-
236
233
  function resolveNamespaceIdForSqlTarget(input: {
237
234
  readonly bucketName: string;
238
235
  readonly targetId: string;
@@ -647,6 +644,8 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
647
644
  : undefined;
648
645
  const hasInlinePrimaryKey = primaryKey !== undefined;
649
646
  let blockPrimaryKeyDeclared = false;
647
+ let controlPolicyDeclared = false;
648
+ let controlPolicy: ControlPolicy | undefined;
650
649
 
651
650
  const resultBackrelationCandidates: ModelBackrelationCandidate[] = [];
652
651
  for (const field of model.fields) {
@@ -733,6 +732,27 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
733
732
  if (modelAttribute.name === 'discriminator' || modelAttribute.name === 'base') {
734
733
  continue;
735
734
  }
735
+ if (modelAttribute.name === 'control') {
736
+ if (controlPolicyDeclared) {
737
+ diagnostics.push({
738
+ code: 'PSL_DUPLICATE_ATTRIBUTE',
739
+ message: `\`@@control\` declared more than once on model "${model.name}".`,
740
+ sourceId,
741
+ span: modelAttribute.span,
742
+ });
743
+ continue;
744
+ }
745
+ controlPolicyDeclared = true;
746
+ const parsed = parseControlPolicyAttribute({
747
+ attribute: modelAttribute,
748
+ sourceId,
749
+ diagnostics,
750
+ });
751
+ if (parsed !== undefined) {
752
+ controlPolicy = parsed;
753
+ }
754
+ continue;
755
+ }
736
756
  const attributeLabel = `Model "${model.name}" @@${modelAttribute.name}`;
737
757
  if (modelAttribute.name === 'id') {
738
758
  if (blockPrimaryKeyDeclared) {
@@ -1103,6 +1123,7 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
1103
1123
  ...(uniqueConstraints.length > 0 ? { uniques: uniqueConstraints } : {}),
1104
1124
  ...(indexNodes.length > 0 ? { indexes: indexNodes } : {}),
1105
1125
  ...(foreignKeyNodes.length > 0 ? { foreignKeys: foreignKeyNodes } : {}),
1126
+ ...ifDefined('control', controlPolicy),
1106
1127
  },
1107
1128
  fkRelationMetadata: resultFkRelationMetadata,
1108
1129
  backrelationCandidates: resultBackrelationCandidates,
@@ -1310,7 +1331,7 @@ function resolvePolymorphism(
1310
1331
  modelNames: Set<string>,
1311
1332
  modelMappings: ReadonlyMap<string, ModelNameMapping>,
1312
1333
  modelNamespaceIds: ReadonlyMap<string, string>,
1313
- targetId: string,
1334
+ defaultNamespaceId: string,
1314
1335
  sourceId: string,
1315
1336
  diagnostics: ContractSourceDiagnostic[],
1316
1337
  ): Record<string, ContractModel> {
@@ -1416,7 +1437,7 @@ function resolvePolymorphism(
1416
1437
  ...variantModel,
1417
1438
  base: crossRef(
1418
1439
  baseDecl.baseName,
1419
- modelNamespaceIds.get(baseDecl.baseName) ?? defaultSqlNamespaceIdForTarget(targetId),
1440
+ modelNamespaceIds.get(baseDecl.baseName) ?? defaultNamespaceId,
1420
1441
  ),
1421
1442
  ...(resolvedTable ? { storage: { ...variantModel.storage, table: resolvedTable } } : {}),
1422
1443
  },
@@ -1687,7 +1708,7 @@ export function interpretPslDocumentToSqlContract(
1687
1708
  `duplicate model name "${modelName}" across domain namespaces during PSL interpretation`,
1688
1709
  );
1689
1710
  }
1690
- modelsForPatch[modelName] = model as ContractModel;
1711
+ modelsForPatch[modelName] = model;
1691
1712
  }
1692
1713
  }
1693
1714
  let patchedModels = patchModelDomainFields(modelsForPatch, modelResolvedFields);
@@ -1700,7 +1721,7 @@ export function interpretPslDocumentToSqlContract(
1700
1721
  modelNames,
1701
1722
  modelMappings,
1702
1723
  modelNamespaceIds,
1703
- input.target.targetId,
1724
+ input.target.defaultNamespaceId,
1704
1725
  sourceId,
1705
1726
  polyDiagnostics,
1706
1727
  );
@@ -1736,7 +1757,7 @@ export function interpretPslDocumentToSqlContract(
1736
1757
  ...(namespaceSlice.valueObjects !== undefined
1737
1758
  ? { valueObjects: namespaceSlice.valueObjects }
1738
1759
  : {}),
1739
- ...(namespaceId === defaultSqlNamespaceIdForTarget(input.target.targetId) &&
1760
+ ...(namespaceId === input.target.defaultNamespaceId &&
1740
1761
  Object.keys(valueObjects).length > 0
1741
1762
  ? { valueObjects }
1742
1763
  : {}),
package/src/provider.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import type { ContractConfig } from '@prisma-next/config/config-types';
3
+ import { applySpecifierDefaultControlPolicy } from '@prisma-next/contract/apply-specifier-default-control-policy';
4
+ import type { ControlPolicy } from '@prisma-next/contract/types';
3
5
  import type { CodecLookup } from '@prisma-next/framework-components/codec';
4
6
  import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';
7
+ import type { Namespace } from '@prisma-next/framework-components/ir';
5
8
  import { parsePslDocument } from '@prisma-next/psl-parser';
9
+ import type { SqlNamespaceTablesInput } from '@prisma-next/sql-contract/types';
6
10
  import { ifDefined } from '@prisma-next/utils/defined';
7
11
  import { notOk, ok } from '@prisma-next/utils/result';
8
12
  import { basename, extname } from 'pathe';
@@ -13,6 +17,8 @@ export interface PrismaContractOptions {
13
17
  readonly output?: string;
14
18
  readonly target: TargetPackRef<'sql', string>;
15
19
  readonly composedExtensionPackRefs?: readonly ExtensionPackRef<'sql', string>[];
20
+ readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
21
+ readonly defaultControlPolicy?: ControlPolicy;
16
22
  }
17
23
 
18
24
  /**
@@ -106,12 +112,15 @@ export function prismaContract(schemaPath: string, options: PrismaContractOption
106
112
  : undefined,
107
113
  ),
108
114
  controlMutationDefaults: context.controlMutationDefaults,
115
+ ...ifDefined('createNamespace', options.createNamespace),
109
116
  });
110
117
  if (!interpreted.ok) {
111
118
  return interpreted;
112
119
  }
113
120
 
114
- return ok(interpreted.value);
121
+ return ok(
122
+ applySpecifierDefaultControlPolicy(interpreted.value, options.defaultControlPolicy),
123
+ );
115
124
  },
116
125
  },
117
126
  output: options.output ?? defaultOutputFromSchemaPath(schemaPath),
@@ -1,4 +1,5 @@
1
1
  import type { ContractSourceDiagnostic } from '@prisma-next/config/config-types';
2
+ import type { ControlPolicy } from '@prisma-next/contract/types';
2
3
  import type { PslAttribute, PslSpan } from '@prisma-next/psl-parser';
3
4
  import { getPositionalArgument, parseQuotedStringLiteral } from '@prisma-next/psl-parser';
4
5
 
@@ -411,6 +412,71 @@ export function findDuplicateFieldName(fieldNames: readonly string[]): string |
411
412
  return undefined;
412
413
  }
413
414
 
415
+ const CONTROL_POLICY_LITERALS = [
416
+ 'managed',
417
+ 'tolerated',
418
+ 'external',
419
+ 'observed',
420
+ ] as const satisfies readonly ControlPolicy[];
421
+
422
+ const CONTROL_POLICY_LITERAL_SET = new Set<string>(CONTROL_POLICY_LITERALS);
423
+
424
+ function isControlPolicyLiteral(value: string): value is ControlPolicy {
425
+ return CONTROL_POLICY_LITERAL_SET.has(value);
426
+ }
427
+
428
+ export function parseControlPolicyAttribute(input: {
429
+ readonly attribute: PslAttribute;
430
+ readonly sourceId: string;
431
+ readonly diagnostics: ContractSourceDiagnostic[];
432
+ }): ControlPolicy | undefined {
433
+ const namedArgs = input.attribute.args.filter((arg) => arg.kind === 'named');
434
+ if (namedArgs.length > 0) {
435
+ input.diagnostics.push({
436
+ code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
437
+ message:
438
+ '`@@control` does not accept named arguments; pass the policy positionally as `@@control(external)`.',
439
+ sourceId: input.sourceId,
440
+ span: input.attribute.span,
441
+ });
442
+ return undefined;
443
+ }
444
+
445
+ const positionalArgs = getPositionalArguments(input.attribute);
446
+ if (positionalArgs.length === 0) {
447
+ input.diagnostics.push({
448
+ code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
449
+ message:
450
+ '`@@control` requires exactly one positional argument: `managed`, `tolerated`, `external`, or `observed`.',
451
+ sourceId: input.sourceId,
452
+ span: input.attribute.span,
453
+ });
454
+ return undefined;
455
+ }
456
+ if (positionalArgs.length > 1) {
457
+ input.diagnostics.push({
458
+ code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
459
+ message: `\`@@control\` accepts exactly one positional argument; got ${positionalArgs.length}.`,
460
+ sourceId: input.sourceId,
461
+ span: input.attribute.span,
462
+ });
463
+ return undefined;
464
+ }
465
+
466
+ const token = unquoteStringLiteral(positionalArgs[0] ?? '').trim();
467
+ if (!isControlPolicyLiteral(token)) {
468
+ input.diagnostics.push({
469
+ code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
470
+ message: `\`@@control\` argument \`${token}\` is not a known policy. Allowed: \`managed\`, \`tolerated\`, \`external\`, \`observed\`.`,
471
+ sourceId: input.sourceId,
472
+ span: input.attribute.span,
473
+ });
474
+ return undefined;
475
+ }
476
+
477
+ return token;
478
+ }
479
+
414
480
  export function mapFieldNamesToColumns(input: {
415
481
  readonly modelName: string;
416
482
  readonly fieldNames: readonly string[];
@@ -25,6 +25,7 @@ import type {
25
25
  PslSpan,
26
26
  PslTypeConstructorCall,
27
27
  } from '@prisma-next/psl-parser';
28
+ import { blindCast } from '@prisma-next/utils/casts';
28
29
  import {
29
30
  lowerDefaultFunctionWithRegistry,
30
31
  parseDefaultFunctionCall,
@@ -67,7 +68,9 @@ export function getAuthoringTypeConstructor(
67
68
  if (typeof current !== 'object' || current === null || Array.isArray(current)) {
68
69
  return undefined;
69
70
  }
70
- current = (current as Record<string, unknown>)[segment];
71
+ current = blindCast<Record<string, unknown>, 'narrowed by preceding typeof/null/array guards'>(
72
+ current,
73
+ )[segment];
71
74
  }
72
75
 
73
76
  return isAuthoringTypeConstructorDescriptor(current) ? current : undefined;
@@ -94,7 +97,9 @@ export function getAuthoringEntity(
94
97
  if (typeof current !== 'object' || current === null || Array.isArray(current)) {
95
98
  return undefined;
96
99
  }
97
- current = (current as Record<string, unknown>)[segment];
100
+ current = blindCast<Record<string, unknown>, 'narrowed by preceding typeof/null/array guards'>(
101
+ current,
102
+ )[segment];
98
103
  }
99
104
 
100
105
  return isAuthoringEntityTypeDescriptor(current) ? current : undefined;
@@ -115,7 +120,9 @@ export function getAuthoringFieldPreset(
115
120
  if (typeof current !== 'object' || current === null || Array.isArray(current)) {
116
121
  return undefined;
117
122
  }
118
- current = (current as Record<string, unknown>)[segment];
123
+ current = blindCast<Record<string, unknown>, 'narrowed by preceding typeof/null/array guards'>(
124
+ current,
125
+ )[segment];
119
126
  }
120
127
 
121
128
  return isAuthoringFieldPresetDescriptor(current) ? current : undefined;
@@ -14,7 +14,7 @@ import {
14
14
  } from './psl-attribute-parsing';
15
15
  import { checkUncomposedNamespace, reportUncomposedNamespace } from './psl-column-resolution';
16
16
 
17
- export const REFERENTIAL_ACTION_MAP = {
17
+ export const REFERENTIAL_ACTION_MAP: Record<string, ReferentialAction | undefined> = {
18
18
  NoAction: 'noAction',
19
19
  Restrict: 'restrict',
20
20
  Cascade: 'cascade',
@@ -25,7 +25,7 @@ export const REFERENTIAL_ACTION_MAP = {
25
25
  cascade: 'cascade',
26
26
  setNull: 'setNull',
27
27
  setDefault: 'setDefault',
28
- } as const;
28
+ };
29
29
 
30
30
  export type ParsedRelationAttribute = {
31
31
  readonly relationName?: string;
@@ -71,8 +71,7 @@ export function normalizeReferentialAction(input: {
71
71
  readonly span: PslSpan;
72
72
  readonly diagnostics: ContractSourceDiagnostic[];
73
73
  }): ReferentialAction | undefined {
74
- const normalized =
75
- REFERENTIAL_ACTION_MAP[input.actionToken as keyof typeof REFERENTIAL_ACTION_MAP];
74
+ const normalized = REFERENTIAL_ACTION_MAP[input.actionToken];
76
75
  if (normalized) {
77
76
  return normalized;
78
77
  }