@prisma-next/sql-contract-psl 0.9.0 → 0.10.0

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,10 +1,12 @@
1
1
  import { AuthoringContributions } from "@prisma-next/framework-components/authoring";
2
+ import { SqlNamespaceTablesInput } from "@prisma-next/sql-contract/types";
2
3
  import { Result } from "@prisma-next/utils/result";
3
4
  import { ParsePslDocumentResult } from "@prisma-next/psl-parser";
4
5
  import { ControlMutationDefaults, ControlMutationDefaults as ControlMutationDefaults$1, DefaultFunctionLoweringContext, DefaultFunctionLoweringHandler, DefaultFunctionRegistry, DefaultFunctionRegistryEntry, MutationDefaultGeneratorDescriptor } from "@prisma-next/framework-components/control";
5
6
  import { ContractSourceDiagnostics } from "@prisma-next/config/config-types";
6
7
  import { Contract } from "@prisma-next/contract/types";
7
8
  import { ExtensionPackRef, TargetPackRef } from "@prisma-next/framework-components/components";
9
+ import { Namespace } from "@prisma-next/framework-components/ir";
8
10
 
9
11
  //#region src/psl-column-resolution.d.ts
10
12
  type ColumnDescriptor = {
@@ -23,6 +25,16 @@ interface InterpretPslDocumentToSqlContractInput {
23
25
  readonly composedExtensionPackRefs?: readonly ExtensionPackRef<'sql', string>[];
24
26
  readonly controlMutationDefaults?: ControlMutationDefaults$1;
25
27
  readonly authoringContributions?: AuthoringContributions;
28
+ /**
29
+ * Target-supplied `Namespace` factory threaded into
30
+ * `buildSqlContractFromDefinition` for the contract's
31
+ * `SqlStorage.namespaces` population. Required when the document
32
+ * contains any explicit `namespace { … }` block on Postgres; the
33
+ * single-namespace path (top-level declarations only) stays valid
34
+ * without the factory and falls back to the family
35
+ * `SqlUnboundNamespace` singleton.
36
+ */
37
+ readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
26
38
  }
27
39
  declare function interpretPslDocumentToSqlContract(input: InterpretPslDocumentToSqlContractInput): Result<Contract, ContractSourceDiagnostics>;
28
40
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/psl-column-resolution.ts","../src/interpreter.ts"],"mappings":";;;;;;;;;KAyCY,gBAAA;EAAA,SACD,OAAA;EAAA,SACA,UAAA;EAAA,SACA,OAAA;EAAA,SACA,UAAA,GAAa,MAAA;AAAA;;;UCyCP,sCAAA;EAAA,SACN,QAAA,EAAU,sBAAA;EAAA,SACV,MAAA,EAAQ,aAAA;EAAA,SACR,qBAAA,EAAuB,WAAA,SAAoB,gBAAA;EAAA,SAC3C,sBAAA;EAAA,SACA,yBAAA,YAAqC,gBAAA;EAAA,SACrC,uBAAA,GAA0B,yBAAA;EAAA,SAC1B,sBAAA,GAAyB,sBAAA;AAAA;AAAA,iBAypCpB,iCAAA,CACd,KAAA,EAAO,sCAAA,GACN,MAAA,CAAO,QAAA,EAAU,yBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/psl-column-resolution.ts","../src/interpreter.ts"],"mappings":";;;;;;;;;;;KAyCY,gBAAA;EAAA,SACD,OAAA;EAAA,SACA,UAAA;EAAA,SACA,OAAA;EAAA,SACA,UAAA,GAAa,MAAA;AAAA;;;UC4CP,sCAAA;EAAA,SACN,QAAA,EAAU,sBAAA;EAAA,SACV,MAAA,EAAQ,aAAA;EAAA,SACR,qBAAA,EAAuB,WAAA,SAAoB,gBAAA;EAAA,SAC3C,sBAAA;EAAA,SACA,yBAAA,YAAqC,gBAAA;EAAA,SACrC,uBAAA,GAA0B,yBAAA;EAAA,SAC1B,sBAAA,GAAyB,sBAAA;EDnDzB;;;;;;;AC4CX;;ED5CW,SC6DA,eAAA,IAAmB,KAAA,EAAO,uBAAA,KAA4B,SAAA;AAAA;AAAA,iBA+xCjD,iCAAA,CACd,KAAA,EAAO,sCAAA,GACN,MAAA,CAAO,QAAA,EAAU,yBAAA"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { t as interpretPslDocumentToSqlContract } from "./interpreter-D_zkd14G.mjs";
1
+ import { t as interpretPslDocumentToSqlContract } from "./interpreter-fVwMptxi.mjs";
2
2
  export { interpretPslDocumentToSqlContract };
@@ -1797,6 +1797,93 @@ function mapParserDiagnostics(document) {
1797
1797
  span: diagnostic.span
1798
1798
  }));
1799
1799
  }
1800
+ /**
1801
+ * Name of the framework-parser synthesised bucket for top-level
1802
+ * declarations. Re-declared here so the per-target dispatch does not
1803
+ * have to import from `@prisma-next/framework-components/psl-ast`
1804
+ * (which would cross a layer that the interpreter does not otherwise
1805
+ * import from). The value is part of the framework parser's contract;
1806
+ * if it changes there, the matching test in this package's
1807
+ * `interpreter.diagnostics.test.ts` flips first.
1808
+ */
1809
+ const UNSPECIFIED_PSL_NAMESPACE_NAME = "__unspecified__";
1810
+ /**
1811
+ * Per-target namespace-block validation: walk the AST's namespace buckets and
1812
+ * emit diagnostics for syntactic constructs the target does not accept.
1813
+ *
1814
+ * - **SQLite** has no schema concept and rejects every explicit
1815
+ * `namespace { … }` block. The implicit `__unspecified__` bucket
1816
+ * (produced by the parser for top-level declarations outside any
1817
+ * block) is the only namespace SQLite accepts.
1818
+ * - **Postgres** accepts every explicit block — `namespace unbound { … }`
1819
+ * is the late-binding opt-in (lowers to the IR `__unbound__` slot in
1820
+ * a follow-on commit), `namespace public { … }` reopen-merges with
1821
+ * the implicit bucket, and any other name lowers to a named schema.
1822
+ *
1823
+ * Storage-side lowering of these buckets to IR namespace slots is not
1824
+ * yet wired; this helper closes only the diagnostic surface.
1825
+ */
1826
+ /**
1827
+ * Per-target namespace lowering: map a PSL AST namespace bucket name to the
1828
+ * resolved IR namespace id (the key downstream consumers use against
1829
+ * `SqlStorage.namespaces`).
1830
+ *
1831
+ * - **Postgres**: an explicit `namespace unbound { … }` block lowers
1832
+ * to the framework sentinel `__unbound__` — the slot whose binding
1833
+ * the connection's `search_path` resolves at runtime. Every other
1834
+ * explicit bucket name (e.g. `auth`, `public`) passes through as a
1835
+ * named schema id. The implicit `__unspecified__` bucket — top-level
1836
+ * declarations outside any `namespace { … }` block — leaves the
1837
+ * coordinate unset; downstream consumers treat unset as the
1838
+ * late-bound default, and TS / PSL authoring stay byte-identical
1839
+ * on single-namespace contracts. (A future round will add a
1840
+ * target-default-namespace surface so `__unspecified__` lowers to
1841
+ * `public` consistently on both authoring paths.)
1842
+ * - **SQLite**: SQLite has no schema concept; every namespace
1843
+ * collapses to the late-bound default. The namespace-block
1844
+ * validation step (above) has already rejected any explicit
1845
+ * `namespace { … }` block on SQLite, so the only bucket the
1846
+ * lowering ever sees there is `__unspecified__`.
1847
+ *
1848
+ * Returns `undefined` for targets / bucket names with no explicit
1849
+ * namespaceId to assign — callers leave the model's `namespaceId`
1850
+ * slot empty (which means the late-bound default at the `StorageTable`
1851
+ * layer; emitted JSON omits the field).
1852
+ */
1853
+ function resolveNamespaceIdForSqlTarget(input) {
1854
+ if (input.targetId !== "postgres") return;
1855
+ if (input.bucketName === UNSPECIFIED_PSL_NAMESPACE_NAME) return;
1856
+ if (input.bucketName === "unbound") return "__unbound__";
1857
+ return input.bucketName;
1858
+ }
1859
+ function validateNamespaceBlocksForSqlTarget(input) {
1860
+ if (input.targetId === "sqlite") {
1861
+ for (const namespace of input.namespaces) {
1862
+ if (namespace.name === UNSPECIFIED_PSL_NAMESPACE_NAME) continue;
1863
+ input.diagnostics.push({
1864
+ code: "PSL_UNSUPPORTED_NAMESPACE_BLOCK",
1865
+ message: `SQLite does not support \`namespace ${namespace.name} { … }\` blocks (SQLite has no schema concept; declare models at the document top level instead).`,
1866
+ sourceId: input.sourceId,
1867
+ span: namespace.span
1868
+ });
1869
+ }
1870
+ return;
1871
+ }
1872
+ if (input.targetId === "postgres") {
1873
+ const namedBlocks = input.namespaces.filter((ns) => ns.name !== UNSPECIFIED_PSL_NAMESPACE_NAME);
1874
+ const hasUnbound = namedBlocks.some((ns) => ns.name === "unbound");
1875
+ const hasSibling = namedBlocks.some((ns) => ns.name !== "unbound");
1876
+ if (hasUnbound && hasSibling) {
1877
+ const unboundBlock = namedBlocks.find((ns) => ns.name === "unbound");
1878
+ input.diagnostics.push({
1879
+ code: "PSL_RESERVED_NAMESPACE_NAME",
1880
+ message: "Namespace \"unbound\" is reserved for the late-binding sentinel mapping and cannot appear alongside other named namespace blocks. Use `namespace unbound { … }` alone (no sibling named namespaces) for late-binding multi-tenant contracts.",
1881
+ sourceId: input.sourceId,
1882
+ ...ifDefined("span", unboundBlock?.span)
1883
+ });
1884
+ }
1885
+ }
1886
+ }
1800
1887
  function processEnumDeclarations(input) {
1801
1888
  const storageTypes = {};
1802
1889
  const enumTypeDescriptors = /* @__PURE__ */ new Map();
@@ -2293,15 +2380,28 @@ function buildModelNodeFromPsl(input) {
2293
2380
  const resultFkRelationMetadata = [];
2294
2381
  for (const relationAttribute of relationAttributes) {
2295
2382
  if (relationAttribute.field.list) continue;
2296
- if (!input.modelNames.has(relationAttribute.field.typeName)) {
2383
+ const { typeName: fieldTypeName, typeNamespaceId: fieldTypeNamespaceId } = relationAttribute.field;
2384
+ const qualifiedTypeName = fieldTypeNamespaceId ? `${fieldTypeNamespaceId}.${fieldTypeName}` : fieldTypeName;
2385
+ if (!input.modelNames.has(fieldTypeName)) {
2297
2386
  diagnostics.push({
2298
2387
  code: "PSL_INVALID_RELATION_TARGET",
2299
- message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${relationAttribute.field.typeName}"`,
2388
+ message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${qualifiedTypeName}"`,
2300
2389
  sourceId,
2301
2390
  span: relationAttribute.field.span
2302
2391
  });
2303
2392
  continue;
2304
2393
  }
2394
+ if (fieldTypeNamespaceId !== void 0) {
2395
+ if (input.modelNamespaceIds.get(fieldTypeName) !== (fieldTypeNamespaceId === "unbound" ? "__unbound__" : fieldTypeNamespaceId)) {
2396
+ diagnostics.push({
2397
+ code: "PSL_INVALID_RELATION_TARGET",
2398
+ message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${qualifiedTypeName}"`,
2399
+ sourceId,
2400
+ span: relationAttribute.field.span
2401
+ });
2402
+ continue;
2403
+ }
2404
+ }
2305
2405
  const parsedRelation = parseRelationAttribute({
2306
2406
  attribute: relationAttribute.relation,
2307
2407
  modelName: model.name,
@@ -2319,11 +2419,11 @@ function buildModelNodeFromPsl(input) {
2319
2419
  });
2320
2420
  continue;
2321
2421
  }
2322
- const targetMapping = input.modelMappings.get(relationAttribute.field.typeName);
2422
+ const targetMapping = input.modelMappings.get(fieldTypeName);
2323
2423
  if (!targetMapping) {
2324
2424
  diagnostics.push({
2325
2425
  code: "PSL_INVALID_RELATION_TARGET",
2326
- message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${relationAttribute.field.typeName}"`,
2426
+ message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${qualifiedTypeName}"`,
2327
2427
  sourceId,
2328
2428
  span: relationAttribute.field.span
2329
2429
  });
@@ -2376,12 +2476,14 @@ function buildModelNodeFromPsl(input) {
2376
2476
  span: relationAttribute.field.span,
2377
2477
  diagnostics
2378
2478
  }) : void 0;
2479
+ const targetNamespaceId = input.modelNamespaceIds.get(targetMapping.model.name);
2379
2480
  foreignKeyNodes.push({
2380
2481
  columns: localColumns,
2381
2482
  references: {
2382
2483
  model: targetMapping.model.name,
2383
2484
  table: targetMapping.tableName,
2384
- columns: referencedColumns
2485
+ columns: referencedColumns,
2486
+ ...ifDefined("namespaceId", targetNamespaceId)
2385
2487
  },
2386
2488
  ...ifDefined("name", parsedRelation.constraintName),
2387
2489
  ...ifDefined("onDelete", onDelete),
@@ -2697,9 +2799,37 @@ function interpretPslDocumentToSqlContract(input) {
2697
2799
  }]
2698
2800
  });
2699
2801
  const diagnostics = mapParserDiagnostics(input.document);
2700
- const models = input.document.ast.models ?? [];
2701
- const enums = input.document.ast.enums ?? [];
2702
- const compositeTypes = input.document.ast.compositeTypes ?? [];
2802
+ validateNamespaceBlocksForSqlTarget({
2803
+ namespaces: input.document.ast.namespaces,
2804
+ targetId: input.target.targetId,
2805
+ sourceId,
2806
+ diagnostics
2807
+ });
2808
+ const models = [];
2809
+ const modelNamespaceIds = /* @__PURE__ */ new Map();
2810
+ for (const namespace of input.document.ast.namespaces) {
2811
+ const resolvedNamespaceId = resolveNamespaceIdForSqlTarget({
2812
+ bucketName: namespace.name,
2813
+ targetId: input.target.targetId
2814
+ });
2815
+ for (const model of namespace.models) {
2816
+ models.push(model);
2817
+ if (resolvedNamespaceId !== void 0) modelNamespaceIds.set(model.name, resolvedNamespaceId);
2818
+ }
2819
+ }
2820
+ const topLevelEnums = input.document.ast.namespaces.filter((ns) => ns.name === UNSPECIFIED_PSL_NAMESPACE_NAME).flatMap((ns) => ns.enums);
2821
+ const namedNamespaceEnumsByNsId = /* @__PURE__ */ new Map();
2822
+ for (const ns of input.document.ast.namespaces) {
2823
+ if (ns.name === UNSPECIFIED_PSL_NAMESPACE_NAME || ns.enums.length === 0) continue;
2824
+ const resolvedId = resolveNamespaceIdForSqlTarget({
2825
+ bucketName: ns.name,
2826
+ targetId: input.target.targetId
2827
+ });
2828
+ if (resolvedId === void 0) continue;
2829
+ const existing = namedNamespaceEnumsByNsId.get(resolvedId) ?? [];
2830
+ namedNamespaceEnumsByNsId.set(resolvedId, [...existing, ...ns.enums]);
2831
+ }
2832
+ const compositeTypes = input.document.ast.namespaces.flatMap((ns) => ns.compositeTypes);
2703
2833
  const modelNames = new Set(models.map((model) => model.name));
2704
2834
  const compositeTypeNames = new Set(compositeTypes.map((ct) => ct.name));
2705
2835
  const composedExtensions = new Set(input.composedExtensionPacks ?? []);
@@ -2707,20 +2837,37 @@ function interpretPslDocumentToSqlContract(input) {
2707
2837
  const generatorDescriptors = input.controlMutationDefaults?.generatorDescriptors ?? [];
2708
2838
  const generatorDescriptorById = /* @__PURE__ */ new Map();
2709
2839
  for (const descriptor of generatorDescriptors) generatorDescriptorById.set(descriptor.id, descriptor);
2840
+ const enumEntityDescriptor = getAuthoringEntity(input.authoringContributions, ["enum"]);
2841
+ const enumEntityContext = {
2842
+ family: input.target.familyId,
2843
+ target: input.target.targetId
2844
+ };
2710
2845
  const enumResult = processEnumDeclarations({
2711
- enums,
2846
+ enums: topLevelEnums,
2712
2847
  sourceId,
2713
- enumEntityDescriptor: getAuthoringEntity(input.authoringContributions, ["enum"]),
2714
- entityContext: {
2715
- family: input.target.familyId,
2716
- target: input.target.targetId
2717
- },
2848
+ enumEntityDescriptor,
2849
+ entityContext: enumEntityContext,
2718
2850
  diagnostics
2719
2851
  });
2852
+ const allEnumTypeDescriptors = new Map(enumResult.enumTypeDescriptors);
2853
+ const namespaceEnumStorageTypes = {};
2854
+ for (const [nsId, nsEnums] of namedNamespaceEnumsByNsId) {
2855
+ const nsEnumResult = processEnumDeclarations({
2856
+ enums: nsEnums,
2857
+ sourceId,
2858
+ enumEntityDescriptor,
2859
+ entityContext: enumEntityContext,
2860
+ diagnostics
2861
+ });
2862
+ for (const [name, descriptor] of nsEnumResult.enumTypeDescriptors) allEnumTypeDescriptors.set(name, descriptor);
2863
+ const nsEntries = {};
2864
+ for (const [name, entry] of Object.entries(nsEnumResult.storageTypes)) if (isPostgresEnumStorageEntry(entry)) nsEntries[name] = entry;
2865
+ if (Object.keys(nsEntries).length > 0) namespaceEnumStorageTypes[nsId] = nsEntries;
2866
+ }
2720
2867
  const namedTypeResult = resolveNamedTypeDeclarations({
2721
2868
  declarations: input.document.ast.types?.declarations ?? [],
2722
2869
  sourceId,
2723
- enumTypeDescriptors: enumResult.enumTypeDescriptors,
2870
+ enumTypeDescriptors: allEnumTypeDescriptors,
2724
2871
  scalarTypeDescriptors: input.scalarTypeDescriptors,
2725
2872
  composedExtensions,
2726
2873
  familyId: input.target.familyId,
@@ -2746,7 +2893,7 @@ function interpretPslDocumentToSqlContract(input) {
2746
2893
  modelMappings,
2747
2894
  modelNames,
2748
2895
  compositeTypeNames,
2749
- enumTypeDescriptors: enumResult.enumTypeDescriptors,
2896
+ enumTypeDescriptors: allEnumTypeDescriptors,
2750
2897
  namedTypeDescriptors: namedTypeResult.namedTypeDescriptors,
2751
2898
  composedExtensions,
2752
2899
  familyId: input.target.familyId,
@@ -2756,9 +2903,14 @@ function interpretPslDocumentToSqlContract(input) {
2756
2903
  generatorDescriptorById,
2757
2904
  scalarTypeDescriptors: input.scalarTypeDescriptors,
2758
2905
  sourceId,
2759
- diagnostics
2906
+ diagnostics,
2907
+ modelNamespaceIds
2760
2908
  });
2761
- modelNodes.push(result.modelNode);
2909
+ const resolvedNamespaceId = modelNamespaceIds.get(model.name);
2910
+ modelNodes.push(resolvedNamespaceId !== void 0 ? {
2911
+ ...result.modelNode,
2912
+ namespaceId: resolvedNamespaceId
2913
+ } : result.modelNode);
2762
2914
  fkRelationMetadata.push(...result.fkRelationMetadata);
2763
2915
  backrelationCandidates.push(...result.backrelationCandidates);
2764
2916
  modelResolvedFields.set(model.name, result.resolvedFields);
@@ -2774,7 +2926,7 @@ function interpretPslDocumentToSqlContract(input) {
2774
2926
  const { discriminatorDeclarations, baseDeclarations } = collectPolymorphismDeclarations(models, sourceId, diagnostics);
2775
2927
  const valueObjects = buildValueObjects({
2776
2928
  compositeTypes,
2777
- enumTypeDescriptors: enumResult.enumTypeDescriptors,
2929
+ enumTypeDescriptors: allEnumTypeDescriptors,
2778
2930
  namedTypeDescriptors: namedTypeResult.namedTypeDescriptors,
2779
2931
  scalarTypeDescriptors: input.scalarTypeDescriptors,
2780
2932
  composedExtensions,
@@ -2792,6 +2944,8 @@ function interpretPslDocumentToSqlContract(input) {
2792
2944
  target: input.target,
2793
2945
  ...ifDefined("extensionPacks", buildComposedExtensionPackRefs(input.target, [...composedExtensions].sort(compareStrings), input.composedExtensionPackRefs)),
2794
2946
  ...Object.keys(storageTypes).length > 0 ? { storageTypes } : {},
2947
+ ...Object.keys(namespaceEnumStorageTypes).length > 0 ? { namespaceTypes: namespaceEnumStorageTypes } : {},
2948
+ ...ifDefined("createNamespace", input.createNamespace),
2795
2949
  models: modelNodes.map((model) => ({
2796
2950
  ...model,
2797
2951
  ...modelRelations.has(model.modelName) ? { relations: [...modelRelations.get(model.modelName) ?? []].sort((left, right) => compareStrings(left.fieldName, right.fieldName)) } : {}
@@ -2816,4 +2970,4 @@ function interpretPslDocumentToSqlContract(input) {
2816
2970
  //#endregion
2817
2971
  export { interpretPslDocumentToSqlContract as t };
2818
2972
 
2819
- //# sourceMappingURL=interpreter-D_zkd14G.mjs.map
2973
+ //# sourceMappingURL=interpreter-fVwMptxi.mjs.map