@prisma-next/sql-contract-psl 0.12.0-dev.3 → 0.12.0-dev.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{interpreter-QE6eZZof.mjs → interpreter-B75uZQde.mjs} +78 -10
- package/dist/interpreter-B75uZQde.mjs.map +1 -0
- package/dist/provider.d.mts +2 -0
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +3 -2
- package/dist/provider.mjs.map +1 -1
- package/package.json +12 -12
- package/src/interpreter.ts +30 -9
- package/src/provider.ts +6 -1
- package/src/psl-attribute-parsing.ts +66 -0
- package/dist/interpreter-QE6eZZof.mjs.map +0 -1
package/dist/provider.mjs.map
CHANGED
|
@@ -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":"
|
|
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 { 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 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 });\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":";;;;;;;;;;;;;;;AA2BA,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,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.
|
|
3
|
+
"version": "0.12.0-dev.31",
|
|
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.
|
|
10
|
-
"@prisma-next/contract": "0.12.0-dev.
|
|
11
|
-
"@prisma-next/framework-components": "0.12.0-dev.
|
|
12
|
-
"@prisma-next/psl-parser": "0.12.0-dev.
|
|
13
|
-
"@prisma-next/sql-contract": "0.12.0-dev.
|
|
14
|
-
"@prisma-next/sql-contract-ts": "0.12.0-dev.
|
|
15
|
-
"@prisma-next/utils": "0.12.0-dev.
|
|
9
|
+
"@prisma-next/config": "0.12.0-dev.31",
|
|
10
|
+
"@prisma-next/contract": "0.12.0-dev.31",
|
|
11
|
+
"@prisma-next/framework-components": "0.12.0-dev.31",
|
|
12
|
+
"@prisma-next/psl-parser": "0.12.0-dev.31",
|
|
13
|
+
"@prisma-next/sql-contract": "0.12.0-dev.31",
|
|
14
|
+
"@prisma-next/sql-contract-ts": "0.12.0-dev.31",
|
|
15
|
+
"@prisma-next/utils": "0.12.0-dev.31",
|
|
16
16
|
"pathe": "^2.0.3"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@prisma-next/contract-authoring": "0.12.0-dev.
|
|
20
|
-
"@prisma-next/test-utils": "0.12.0-dev.
|
|
21
|
-
"@prisma-next/tsconfig": "0.12.0-dev.
|
|
22
|
-
"@prisma-next/tsdown": "0.12.0-dev.
|
|
19
|
+
"@prisma-next/contract-authoring": "0.12.0-dev.31",
|
|
20
|
+
"@prisma-next/test-utils": "0.12.0-dev.31",
|
|
21
|
+
"@prisma-next/tsconfig": "0.12.0-dev.31",
|
|
22
|
+
"@prisma-next/tsdown": "0.12.0-dev.31",
|
|
23
23
|
"arktype": "^2.2.0",
|
|
24
24
|
"tsdown": "0.22.0",
|
|
25
25
|
"typescript": "5.9.3",
|
package/src/interpreter.ts
CHANGED
|
@@ -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
|
-
|
|
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) ??
|
|
1440
|
+
modelNamespaceIds.get(baseDecl.baseName) ?? defaultNamespaceId,
|
|
1420
1441
|
),
|
|
1421
1442
|
...(resolvedTable ? { storage: { ...variantModel.storage, table: resolvedTable } } : {}),
|
|
1422
1443
|
},
|
|
@@ -1700,7 +1721,7 @@ export function interpretPslDocumentToSqlContract(
|
|
|
1700
1721
|
modelNames,
|
|
1701
1722
|
modelMappings,
|
|
1702
1723
|
modelNamespaceIds,
|
|
1703
|
-
input.target.
|
|
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 ===
|
|
1760
|
+
...(namespaceId === input.target.defaultNamespaceId &&
|
|
1740
1761
|
Object.keys(valueObjects).length > 0
|
|
1741
1762
|
? { valueObjects }
|
|
1742
1763
|
: {}),
|
package/src/provider.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
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';
|
|
5
7
|
import { parsePslDocument } from '@prisma-next/psl-parser';
|
|
@@ -13,6 +15,7 @@ export interface PrismaContractOptions {
|
|
|
13
15
|
readonly output?: string;
|
|
14
16
|
readonly target: TargetPackRef<'sql', string>;
|
|
15
17
|
readonly composedExtensionPackRefs?: readonly ExtensionPackRef<'sql', string>[];
|
|
18
|
+
readonly defaultControlPolicy?: ControlPolicy;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
/**
|
|
@@ -111,7 +114,9 @@ export function prismaContract(schemaPath: string, options: PrismaContractOption
|
|
|
111
114
|
return interpreted;
|
|
112
115
|
}
|
|
113
116
|
|
|
114
|
-
return ok(
|
|
117
|
+
return ok(
|
|
118
|
+
applySpecifierDefaultControlPolicy(interpreted.value, options.defaultControlPolicy),
|
|
119
|
+
);
|
|
115
120
|
},
|
|
116
121
|
},
|
|
117
122
|
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[];
|