@prisma-next/sql-contract-psl 0.12.0-dev.7 → 0.12.0-dev.71

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/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
  /**
@@ -99,6 +105,7 @@ export function prismaContract(schemaPath: string, options: PrismaContractOption
99
105
  ? [...context.composedExtensionPacks]
100
106
  : undefined,
101
107
  ),
108
+ composedExtensionContracts: context.composedExtensionContracts,
102
109
  ...ifDefined(
103
110
  'composedExtensionPackRefs',
104
111
  options.composedExtensionPackRefs?.length
@@ -106,12 +113,15 @@ export function prismaContract(schemaPath: string, options: PrismaContractOption
106
113
  : undefined,
107
114
  ),
108
115
  controlMutationDefaults: context.controlMutationDefaults,
116
+ ...ifDefined('createNamespace', options.createNamespace),
109
117
  });
110
118
  if (!interpreted.ok) {
111
119
  return interpreted;
112
120
  }
113
121
 
114
- return ok(interpreted.value);
122
+ return ok(
123
+ applySpecifierDefaultControlPolicy(interpreted.value, options.defaultControlPolicy),
124
+ );
115
125
  },
116
126
  },
117
127
  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;
@@ -232,6 +232,12 @@ export function collectResolvedFields(input: CollectResolvedFieldsInput): Resolv
232
232
  if (isModelField && relationAttribute) {
233
233
  continue;
234
234
  }
235
+ // Cross-contract-space relation fields (e.g. `supabase:auth.User @relation(...)`) are not
236
+ // local model fields, but they carry a @relation attribute and should be skipped here.
237
+ // Their FK and RelationNode lowering is handled separately in the interpreter.
238
+ if (field.typeContractSpaceId !== undefined && relationAttribute) {
239
+ continue;
240
+ }
235
241
 
236
242
  const isValueObjectField = compositeTypeNames.has(field.typeName);
237
243
  const isListField = field.list;
@@ -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
  }