@prisma-next/family-sql 0.14.0 → 0.15.0-dev.10

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.
Files changed (80) hide show
  1. package/dist/{authoring-type-constructors-CjFfO6LM.mjs → authoring-type-constructors-CXd-8ydc.mjs} +7 -30
  2. package/dist/authoring-type-constructors-CXd-8ydc.mjs.map +1 -0
  3. package/dist/{control-adapter-Cmw9LvEP.d.mts → control-adapter-BQgad8Zc.d.mts} +30 -8
  4. package/dist/control-adapter-BQgad8Zc.d.mts.map +1 -0
  5. package/dist/control-adapter.d.mts +1 -1
  6. package/dist/control.d.mts +279 -34
  7. package/dist/control.d.mts.map +1 -1
  8. package/dist/control.mjs +414 -1005
  9. package/dist/control.mjs.map +1 -1
  10. package/dist/diff.d.mts +102 -0
  11. package/dist/diff.d.mts.map +1 -0
  12. package/dist/diff.mjs +14 -0
  13. package/dist/diff.mjs.map +1 -0
  14. package/dist/ir.d.mts +26 -9
  15. package/dist/ir.d.mts.map +1 -1
  16. package/dist/ir.mjs +2 -2
  17. package/dist/ir.mjs.map +1 -1
  18. package/dist/migration.d.mts +11 -2
  19. package/dist/migration.d.mts.map +1 -1
  20. package/dist/migration.mjs +7 -0
  21. package/dist/migration.mjs.map +1 -1
  22. package/dist/pack.mjs +1 -1
  23. package/dist/psl-infer.d.mts +119 -0
  24. package/dist/psl-infer.d.mts.map +1 -0
  25. package/dist/psl-infer.mjs +366 -0
  26. package/dist/psl-infer.mjs.map +1 -0
  27. package/dist/schema-differ-DnoopSXm.d.mts +45 -0
  28. package/dist/schema-differ-DnoopSXm.d.mts.map +1 -0
  29. package/dist/schema-verify-3sOPsdn_.mjs +226 -0
  30. package/dist/schema-verify-3sOPsdn_.mjs.map +1 -0
  31. package/dist/{sql-contract-serializer-BR2vC7Z-.mjs → sql-contract-serializer-C75cfMSS.mjs} +46 -20
  32. package/dist/sql-contract-serializer-C75cfMSS.mjs.map +1 -0
  33. package/dist/{types-kgstZ_Zd.d.mts → types-BPv_y7iS.d.mts} +36 -142
  34. package/dist/types-BPv_y7iS.d.mts.map +1 -0
  35. package/package.json +26 -26
  36. package/src/core/authoring-entity-types.ts +12 -37
  37. package/src/core/control-adapter.ts +11 -5
  38. package/src/core/control-instance.ts +212 -42
  39. package/src/core/control-target-descriptor.ts +98 -0
  40. package/src/core/diff/schema-verify.ts +324 -0
  41. package/src/core/diff/sql-schema-diff.ts +41 -0
  42. package/src/core/diff/verifier-disposition.ts +30 -0
  43. package/src/core/ir/sql-contract-serializer-base.ts +70 -56
  44. package/src/core/ir/sql-contract-serializer.ts +5 -7
  45. package/src/core/ir/sql-schema-verifier-base.ts +5 -5
  46. package/src/core/migrations/contract-to-schema-ir.ts +200 -54
  47. package/src/core/migrations/control-policy.ts +66 -115
  48. package/src/core/migrations/field-event-planner.ts +2 -2
  49. package/src/core/migrations/native-type-expander.ts +28 -0
  50. package/src/core/migrations/schema-differ.ts +41 -0
  51. package/src/core/migrations/types.ts +33 -37
  52. package/src/core/psl-contract-infer/name-transforms.ts +15 -0
  53. package/src/core/psl-contract-infer/printer-config.ts +14 -6
  54. package/src/core/psl-contract-infer/relation-inference.ts +30 -1
  55. package/src/core/sql-migration.ts +12 -1
  56. package/src/exports/control.ts +12 -2
  57. package/src/exports/diff.ts +25 -0
  58. package/src/exports/psl-infer.ts +40 -0
  59. package/dist/authoring-type-constructors-CjFfO6LM.mjs.map +0 -1
  60. package/dist/control-adapter-Cmw9LvEP.d.mts.map +0 -1
  61. package/dist/schema-verify.d.mts +0 -39
  62. package/dist/schema-verify.d.mts.map +0 -1
  63. package/dist/schema-verify.mjs +0 -2
  64. package/dist/sql-contract-serializer-BR2vC7Z-.mjs.map +0 -1
  65. package/dist/test-utils.d.mts +0 -2
  66. package/dist/test-utils.mjs +0 -2
  67. package/dist/types-kgstZ_Zd.d.mts.map +0 -1
  68. package/dist/verify-sql-schema-thU-jKpf.d.mts +0 -66
  69. package/dist/verify-sql-schema-thU-jKpf.d.mts.map +0 -1
  70. package/dist/verify-sql-schema-xT4udQLQ.mjs +0 -1501
  71. package/dist/verify-sql-schema-xT4udQLQ.mjs.map +0 -1
  72. package/src/core/psl-contract-infer/postgres-default-mapping.ts +0 -16
  73. package/src/core/psl-contract-infer/postgres-type-map.ts +0 -157
  74. package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +0 -795
  75. package/src/core/schema-verify/control-verify-emit.ts +0 -46
  76. package/src/core/schema-verify/verifier-disposition.ts +0 -58
  77. package/src/core/schema-verify/verify-helpers.ts +0 -820
  78. package/src/core/schema-verify/verify-sql-schema.ts +0 -1311
  79. package/src/exports/schema-verify.ts +0 -18
  80. package/src/exports/test-utils.ts +0 -9
@@ -11,10 +11,12 @@ import type {
11
11
  ControlFamilyInstance,
12
12
  ControlStack,
13
13
  CoreSchemaView,
14
+ DiffSubjectGranularity,
14
15
  MigrationPlanOperation,
15
16
  OperationPreview,
16
17
  OperationPreviewCapable,
17
18
  PslContractInferCapable,
19
+ SchemaDiffIssue,
18
20
  SchemaViewCapable,
19
21
  SignDatabaseResult,
20
22
  VerifyDatabaseResult,
@@ -28,6 +30,7 @@ import {
28
30
  VERIFY_CODE_TARGET_MISMATCH,
29
31
  } from '@prisma-next/framework-components/control';
30
32
  import type { TypesImportSpec } from '@prisma-next/framework-components/emission';
33
+ import { isPlainRecord } from '@prisma-next/framework-components/ir';
31
34
  import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
32
35
  import { assertDescriptorSelfConsistency } from '@prisma-next/migration-tools/spaces';
33
36
  import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks';
@@ -39,17 +42,26 @@ import type {
39
42
  SqlExecuteRequest,
40
43
  } from '@prisma-next/sql-relational-core/ast';
41
44
  import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
42
- import type { SqlSchemaIR, SqlTableIR } from '@prisma-next/sql-schema-ir/types';
45
+ import type { SqlSchemaIRNode, SqlTableIR } from '@prisma-next/sql-schema-ir/types';
46
+ import { blindCast } from '@prisma-next/utils/casts';
43
47
  import { ifDefined } from '@prisma-next/utils/defined';
44
48
  import type { SqlControlAdapter } from './control-adapter';
49
+ import type {
50
+ SqlControlTargetDescriptor,
51
+ SqlDescribedContractSpace,
52
+ } from './control-target-descriptor';
53
+ import {
54
+ classifyDiffEntityKind,
55
+ classifyDiffSubjectGranularity,
56
+ verifySqlSchemaByDiff,
57
+ } from './diff/schema-verify';
45
58
  import { SqlContractSerializer } from './ir/sql-contract-serializer';
59
+ import type { SqlSchemaDiffFn } from './migrations/schema-differ';
46
60
  import type {
47
61
  SqlControlAdapterDescriptor,
48
62
  SqlControlExtensionDescriptor,
49
63
  } from './migrations/types';
50
64
  import { sqlOperationsToPreview } from './operation-preview';
51
- import { sqlSchemaIrToPslAst } from './psl-contract-infer/sql-schema-ir-to-psl-ast';
52
- import { verifySqlSchema } from './schema-verify/verify-sql-schema';
53
65
  import { collectSupportedCodecTypeIds } from './verify';
54
66
 
55
67
  function extractCodecTypeIdsFromContract(contract: unknown): readonly string[] {
@@ -186,9 +198,9 @@ interface SqlFamilyInstanceState {
186
198
  }
187
199
 
188
200
  export interface SqlControlFamilyInstance
189
- extends ControlFamilyInstance<'sql', SqlSchemaIR>,
190
- SchemaViewCapable<SqlSchemaIR>,
191
- PslContractInferCapable<SqlSchemaIR>,
201
+ extends ControlFamilyInstance<'sql', SqlSchemaIRNode>,
202
+ SchemaViewCapable<SqlSchemaIRNode>,
203
+ PslContractInferCapable<SqlSchemaIRNode>,
192
204
  OperationPreviewCapable,
193
205
  SqlFamilyInstanceState {
194
206
  /**
@@ -210,22 +222,41 @@ export interface SqlControlFamilyInstance
210
222
  }): Promise<VerifyDatabaseResult>;
211
223
 
212
224
  /**
213
- * Verify a contract against an already-introspected schema slice.
225
+ * Verify a contract against an already-introspected schema.
214
226
  *
215
227
  * Callers that need to verify against the live database compose
216
228
  * `introspect({ driver })` + `verifySchema({ contract, schema, ... })`.
217
- * The aggregate verifier projects each member's claimed slice via
218
- * `projectSchemaToSpace` and hands the projected slice inthis
219
- * keeps per-member verification from surfacing sibling-space tables
220
- * as `extras`.
229
+ * The aggregate verifier hands in the full introspected schema and scopes
230
+ * the returned result to each member's contract space afterwards so
231
+ * sibling-space tables never survive as `extras`.
221
232
  */
222
233
  verifySchema(options: {
223
234
  readonly contract: unknown;
224
- readonly schema: SqlSchemaIR;
235
+ readonly schema: SqlSchemaIRNode;
225
236
  readonly strict: boolean;
226
237
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
227
238
  }): VerifyDatabaseSchemaResult;
228
239
 
240
+ /**
241
+ * Classifies a diff issue's subject granularity on demand, resolved from
242
+ * its node's `nodeKind` via the target's classifier. Satisfies the
243
+ * {@link import('@prisma-next/framework-components/control').SchemaSubjectClassifierCapable}
244
+ * capability that framework consumers spanning contract spaces (the
245
+ * migration aggregate's unclaimed-elements sweep) detect via
246
+ * `hasSchemaSubjectClassifier` and call instead of reading family/target
247
+ * node vocabulary. Nothing is stamped on the issue or the node.
248
+ */
249
+ classifySubjectGranularity(issue: SchemaDiffIssue): DiffSubjectGranularity | undefined;
250
+
251
+ /**
252
+ * Classifies a diff issue's subject storage `entityKind` on demand,
253
+ * resolved from its node's `nodeKind` via the target's classifier —
254
+ * sibling of `classifySubjectGranularity` above, part of the same
255
+ * {@link import('@prisma-next/framework-components/control').SchemaSubjectClassifierCapable}
256
+ * capability. Nothing is stamped on the issue or the node.
257
+ */
258
+ classifyEntityKind(issue: SchemaDiffIssue): string | undefined;
259
+
229
260
  sign(options: {
230
261
  readonly driver: SqlControlDriverInstance<string>;
231
262
  readonly contract: unknown;
@@ -236,9 +267,9 @@ export interface SqlControlFamilyInstance
236
267
  introspect(options: {
237
268
  readonly driver: SqlControlDriverInstance<string>;
238
269
  readonly contract?: unknown;
239
- }): Promise<SqlSchemaIR>;
270
+ }): Promise<SqlSchemaIRNode>;
240
271
 
241
- inferPslContract(schemaIR: SqlSchemaIR): PslDocumentAst;
272
+ inferPslContract(schemaIR: SqlSchemaIRNode): PslDocumentAst;
242
273
 
243
274
  lowerAst(
244
275
  ast: AnyQueryAst | DdlNode,
@@ -289,13 +320,12 @@ export interface SqlControlFamilyInstance
289
320
  readonly migrationName: string;
290
321
  readonly migrationHash: string;
291
322
  readonly operations: readonly unknown[];
323
+ readonly destinationContractJson?: unknown;
292
324
  };
293
325
  }): Promise<void>;
294
326
 
295
327
  bootstrapControlTableQueries(): readonly DdlNode[];
296
328
 
297
- bootstrapSignMarkerQueries(): readonly DdlNode[];
298
-
299
329
  toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview;
300
330
  }
301
331
 
@@ -523,12 +553,66 @@ export function createSqlFamilyInstance<TTargetId extends string>(
523
553
 
524
554
  const targetSerializer = (
525
555
  target as unknown as {
526
- contractSerializer?: { deserializeContract(json: unknown): Contract<SqlStorage> };
556
+ contractSerializer?: {
557
+ deserializeContract(json: unknown): Contract<SqlStorage>;
558
+ serializeContract(contract: Contract<SqlStorage>): unknown;
559
+ };
527
560
  }
528
561
  ).contractSerializer;
529
- const deserializeWithTargetSerializer = (contractJson: unknown): Contract<SqlStorage> => {
562
+ // Database→PSL inference is target logic (it owns the dialect type/default
563
+ // maps and walks its own schema tree), so it is read off the descriptor like
564
+ // `contractSerializer`. Absent for targets without `contract infer` (Mongo).
565
+ const targetInferPslContract = blindCast<
566
+ SqlControlTargetDescriptor<TTargetId, unknown>,
567
+ 'reading the optional target-descriptor inferPslContract hook'
568
+ >(target).inferPslContract;
569
+ // The full-tree node diff the verify VERDICT derives from. Read lazily so
570
+ // construction-only stub descriptors (schema-view tests) keep working; the
571
+ // throw happens at verify time.
572
+ const diffSchema = blindCast<
573
+ { readonly diffSchema?: SqlSchemaDiffFn },
574
+ 'reading the target-descriptor diffSchema hook'
575
+ >(target).diffSchema;
576
+ // The target's nodeKind → granularity classifier, resolved on demand by the
577
+ // verdict and by the instance's `classifySubjectGranularity` method below —
578
+ // never stamped onto an issue. Read lazily for the same
579
+ // construction-only-stub reason as `diffSchema`; the throw happens at call
580
+ // time.
581
+ const targetGranularityOf = blindCast<
582
+ { readonly classifySubjectGranularity?: (nodeKind: string) => DiffSubjectGranularity },
583
+ 'reading the target-descriptor classifySubjectGranularity hook'
584
+ >(target).classifySubjectGranularity;
585
+ // The target's nodeKind → storage entityKind classifier — sibling of
586
+ // `targetGranularityOf` above, read the same lazy way for the same
587
+ // construction-only-stub reason.
588
+ const targetEntityKindOf = blindCast<
589
+ { readonly classifyEntityKind?: (nodeKind: string) => string | undefined },
590
+ 'reading the target-descriptor classifyEntityKind hook'
591
+ >(target).classifyEntityKind;
592
+ // `contract infer` needs each extension pack's already-assembled contract,
593
+ // carried as-is (no merging — that is the contract-spaces machinery's
594
+ // concern), paired with the `spaceId` its descriptor was registered under
595
+ // (neither the contract JSON nor `ContractSpace` self-declares it), so the
596
+ // target hook can omit elements those contracts describe and qualify a
597
+ // cross-space relation with the owning pack's space id.
598
+ const describedContracts: readonly SqlDescribedContractSpace[] = extensions.flatMap(
599
+ (extension) =>
600
+ extension.contractSpace
601
+ ? [{ spaceId: extension.id, contract: extension.contractSpace.contractJson }]
602
+ : [],
603
+ );
604
+ const deserializeWithTargetSerializer = (contractOrJson: unknown): Contract<SqlStorage> => {
530
605
  const serializer = targetSerializer ?? new SqlContractSerializer();
531
- return serializer.deserializeContract(contractJson) as Contract<SqlStorage>;
606
+ const json =
607
+ targetSerializer !== undefined && !isPlainRecord(contractOrJson)
608
+ ? targetSerializer.serializeContract(
609
+ blindCast<
610
+ Contract<SqlStorage>,
611
+ 'isPlainRecord returned false, so contractOrJson is a class instance, not raw JSON'
612
+ >(contractOrJson),
613
+ )
614
+ : contractOrJson;
615
+ return serializer.deserializeContract(json) as Contract<SqlStorage>;
532
616
  };
533
617
 
534
618
  return {
@@ -669,22 +753,67 @@ export function createSqlFamilyInstance<TTargetId extends string>(
669
753
 
670
754
  verifySchema(options: {
671
755
  readonly contract: unknown;
672
- readonly schema: SqlSchemaIR;
756
+ readonly schema: SqlSchemaIRNode;
673
757
  readonly strict: boolean;
674
758
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
675
759
  }): VerifyDatabaseSchemaResult {
676
760
  const contract = deserializeWithTargetSerializer(options.contract) as Contract<SqlStorage>;
677
- const controlAdapter = getControlAdapter();
678
- return verifySqlSchema({
761
+ if (!diffSchema) {
762
+ throw new Error(
763
+ `SQL target "${target.targetId}" is missing the required diffSchema descriptor operation`,
764
+ );
765
+ }
766
+ if (!targetGranularityOf) {
767
+ throw new Error(
768
+ `SQL target "${target.targetId}" is missing the required classifySubjectGranularity descriptor operation`,
769
+ );
770
+ }
771
+ // THE VERDICT: the target's full-tree node diff, graded by the
772
+ // family's post-diff filters (strict gating + control-policy
773
+ // disposition), plus the codec verifyType hook findings. The result
774
+ // is issue-based — `ok` holds exactly when both issue lists are empty.
775
+ return verifySqlSchemaByDiff({
679
776
  contract,
680
777
  schema: options.schema,
681
778
  strict: options.strict,
682
- typeMetadataRegistry,
683
779
  frameworkComponents: options.frameworkComponents,
684
- ...ifDefined('normalizeDefault', controlAdapter.normalizeDefault),
685
- ...ifDefined('normalizeNativeType', controlAdapter.normalizeNativeType),
780
+ diffSchema,
781
+ granularityOf: targetGranularityOf,
686
782
  });
687
783
  },
784
+
785
+ /**
786
+ * Classifies a diff issue's subject granularity on demand, by resolving
787
+ * its node's `nodeKind` through the target's classifier — the
788
+ * {@link import('@prisma-next/framework-components/control').SchemaSubjectClassifierCapable}
789
+ * capability. Framework consumers spanning contract spaces (the
790
+ * migration aggregate's unclaimed-elements sweep) detect and call this
791
+ * instead of reaching into the concrete schema-IR node, which they
792
+ * cannot read; nothing is stamped on the issue or the node.
793
+ */
794
+ classifySubjectGranularity(issue: SchemaDiffIssue): DiffSubjectGranularity | undefined {
795
+ if (!targetGranularityOf) {
796
+ throw new Error(
797
+ `SQL target "${target.targetId}" is missing the required classifySubjectGranularity descriptor operation`,
798
+ );
799
+ }
800
+ return classifyDiffSubjectGranularity(issue, targetGranularityOf);
801
+ },
802
+ /**
803
+ * Classifies a diff issue's subject storage `entityKind` on demand, by
804
+ * resolving its node's `nodeKind` through the target's classifier —
805
+ * the sibling of `classifySubjectGranularity` above, and part of the
806
+ * same {@link import('@prisma-next/framework-components/control').SchemaSubjectClassifierCapable}
807
+ * capability.
808
+ */
809
+ classifyEntityKind(issue: SchemaDiffIssue): string | undefined {
810
+ if (!targetEntityKindOf) {
811
+ throw new Error(
812
+ `SQL target "${target.targetId}" is missing the required classifyEntityKind descriptor operation`,
813
+ );
814
+ }
815
+ return classifyDiffEntityKind(issue, targetEntityKindOf);
816
+ },
688
817
  async sign(options: {
689
818
  readonly driver: SqlControlDriverInstance<string>;
690
819
  readonly contract: unknown;
@@ -841,6 +970,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
841
970
  readonly migrationName: string;
842
971
  readonly migrationHash: string;
843
972
  readonly operations: readonly unknown[];
973
+ readonly destinationContractJson?: unknown;
844
974
  };
845
975
  }): Promise<void> {
846
976
  return getControlAdapter().writeLedgerEntry(options.driver, options.space, options.entry);
@@ -848,12 +978,17 @@ export function createSqlFamilyInstance<TTargetId extends string>(
848
978
  async introspect(options: {
849
979
  readonly driver: SqlControlDriverInstance<string>;
850
980
  readonly contract?: unknown;
851
- }): Promise<SqlSchemaIR> {
981
+ }): Promise<SqlSchemaIRNode> {
852
982
  return getControlAdapter().introspect(options.driver, options.contract);
853
983
  },
854
984
 
855
- inferPslContract(schemaIR: SqlSchemaIR): PslDocumentAst {
856
- return sqlSchemaIrToPslAst(schemaIR);
985
+ inferPslContract(schemaIR: SqlSchemaIRNode): PslDocumentAst {
986
+ if (!targetInferPslContract) {
987
+ throw new Error(
988
+ `Target "${target.targetId}" does not support contract infer (no inferPslContract on its descriptor).`,
989
+ );
990
+ }
991
+ return targetInferPslContract(schemaIR, describedContracts);
857
992
  },
858
993
 
859
994
  lowerAst(
@@ -867,17 +1002,52 @@ export function createSqlFamilyInstance<TTargetId extends string>(
867
1002
  return getControlAdapter().bootstrapControlTableQueries();
868
1003
  },
869
1004
 
870
- bootstrapSignMarkerQueries(): readonly DdlNode[] {
871
- return getControlAdapter().bootstrapSignMarkerQueries();
872
- },
873
-
874
1005
  toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview {
875
1006
  return sqlOperationsToPreview(operations);
876
1007
  },
877
1008
 
878
- toSchemaView(schema: SqlSchemaIR): CoreSchemaView {
879
- const tableNodes: readonly SchemaTreeNode[] = Object.entries(schema.tables).map(
880
- ([tableName, table]: [string, SqlTableIR]) => {
1009
+ toSchemaView(schema: SqlSchemaIRNode): CoreSchemaView {
1010
+ // Walk the schema-IR tree's own structure (root → namespaces → tables)
1011
+ // into one flat list of table nodes. A root that exposes a `namespaces`
1012
+ // record (Postgres) contributes each namespace's tables; a flat root
1013
+ // (SQLite) is its own single namespace. The single-schema common case
1014
+ // renders the same table-level view as today — no synthetic namespace
1015
+ // level.
1016
+ const root = blindCast<
1017
+ {
1018
+ readonly namespaces?: Readonly<
1019
+ Record<string, { readonly tables: Record<string, SqlTableIR> }>
1020
+ >;
1021
+ readonly tables?: Record<string, SqlTableIR>;
1022
+ },
1023
+ 'structural read of the schema-IR tree own namespaces/tables records'
1024
+ >(schema);
1025
+ // A multi-namespace root qualifies every table's display name with its
1026
+ // namespace key, so same-named tables in different namespaces (e.g.
1027
+ // public.thing and auth.thing) render distinct ids and labels. The
1028
+ // single-namespace case (and a flat SQLite root) keeps today's bare
1029
+ // names. Synthesized constraint/index fallback names keep the bare table
1030
+ // name — that is the name the database derives them from.
1031
+ const namespaceEntries: ReadonlyArray<[string | undefined, Record<string, SqlTableIR>]> =
1032
+ root.namespaces !== undefined
1033
+ ? Object.entries(root.namespaces).map(
1034
+ ([namespaceKey, namespace]): [string, Record<string, SqlTableIR>] => [
1035
+ namespaceKey,
1036
+ namespace.tables,
1037
+ ],
1038
+ )
1039
+ : [[undefined, root.tables ?? {}]];
1040
+ const qualify = namespaceEntries.length > 1;
1041
+ const tableEntries: ReadonlyArray<[string, string, SqlTableIR]> = namespaceEntries.flatMap(
1042
+ ([namespaceKey, tables]) =>
1043
+ Object.entries(tables).map(([tableName, table]): [string, string, SqlTableIR] => [
1044
+ qualify && namespaceKey !== undefined ? `${namespaceKey}.${tableName}` : tableName,
1045
+ tableName,
1046
+ table,
1047
+ ]),
1048
+ );
1049
+ const tableNodes: readonly SchemaTreeNode[] = tableEntries.map(
1050
+ ([displayName, tableName, table]: [string, string, SqlTableIR]) => {
881
1051
  const children: SchemaTreeNode[] = [];
882
1052
 
883
1053
  const columnNodes: SchemaTreeNode[] = [];
@@ -888,7 +1058,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
888
1058
  columnNodes.push(
889
1059
  new SchemaTreeNode({
890
1060
  kind: 'field',
891
- id: `column-${tableName}-${columnName}`,
1061
+ id: `column-${displayName}-${columnName}`,
892
1062
  label,
893
1063
  meta: {
894
1064
  nativeType: column.nativeType,
@@ -903,7 +1073,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
903
1073
  children.push(
904
1074
  new SchemaTreeNode({
905
1075
  kind: 'collection',
906
- id: `columns-${tableName}`,
1076
+ id: `columns-${displayName}`,
907
1077
  label: 'columns',
908
1078
  children: columnNodes,
909
1079
  }),
@@ -915,7 +1085,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
915
1085
  children.push(
916
1086
  new SchemaTreeNode({
917
1087
  kind: 'index',
918
- id: `primary-key-${tableName}`,
1088
+ id: `primary-key-${displayName}`,
919
1089
  label: `primary key: ${pkColumns}`,
920
1090
  meta: {
921
1091
  columns: table.primaryKey.columns,
@@ -931,7 +1101,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
931
1101
  children.push(
932
1102
  new SchemaTreeNode({
933
1103
  kind: 'index',
934
- id: `unique-${tableName}-${name}`,
1104
+ id: `unique-${displayName}-${name}`,
935
1105
  label,
936
1106
  meta: {
937
1107
  columns: unique.columns,
@@ -947,7 +1117,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
947
1117
  children.push(
948
1118
  new SchemaTreeNode({
949
1119
  kind: 'index',
950
- id: `index-${tableName}-${name}`,
1120
+ id: `index-${displayName}-${name}`,
951
1121
  label,
952
1122
  meta: {
953
1123
  columns: index.columns,
@@ -975,8 +1145,8 @@ export function createSqlFamilyInstance<TTargetId extends string>(
975
1145
 
976
1146
  return new SchemaTreeNode({
977
1147
  kind: 'entity',
978
- id: `table-${tableName}`,
979
- label: `table ${tableName}`,
1148
+ id: `table-${displayName}`,
1149
+ label: `table ${displayName}`,
980
1150
  ...(Object.keys(tableMeta).length > 0 ? { meta: tableMeta } : {}),
981
1151
  ...(children.length > 0 ? { children } : {}),
982
1152
  });
@@ -0,0 +1,98 @@
1
+ import type { Contract } from '@prisma-next/contract/types';
2
+ import type {
3
+ ContractSerializer,
4
+ DiffSubjectGranularity,
5
+ MigratableTargetDescriptor,
6
+ SchemaVerifier,
7
+ } from '@prisma-next/framework-components/control';
8
+ import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
9
+ import type { SqlStorage } from '@prisma-next/sql-contract/types';
10
+ import type { SqlOperationDescriptors } from '@prisma-next/sql-operations';
11
+ import type { SqlSchemaIR, SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
12
+ import type { SqlControlAdapter } from './control-adapter';
13
+ import type { SqlControlFamilyInstance } from './control-instance';
14
+ import type { SqlSchemaDiffFn } from './migrations/schema-differ';
15
+ import type { SqlMigrationPlanner, SqlMigrationRunner } from './migrations/types';
16
+
17
+ /**
18
+ * One stack extension pack's already-assembled contract, paired with the
19
+ * `spaceId` its extension descriptor was registered under. `contract infer`
20
+ * needs both: the contract to know which elements the pack describes and to
21
+ * resolve the domain model a cross-space foreign key targets, the `spaceId`
22
+ * to qualify the emitted relation type (`<spaceId>:<namespace>.<Model>`).
23
+ * Neither the contract JSON nor the framework's `ContractSpace` wrapper
24
+ * self-declares its owning space id — it is only known from the extension
25
+ * descriptor that carries the `ContractSpace`.
26
+ */
27
+ export interface SqlDescribedContractSpace {
28
+ readonly spaceId: string;
29
+ readonly contract: Contract<SqlStorage>;
30
+ }
31
+
32
+ export interface SqlControlTargetDescriptor<
33
+ TTargetId extends string,
34
+ TTargetDetails,
35
+ TContract extends Contract<SqlStorage> = Contract<SqlStorage>,
36
+ > extends MigratableTargetDescriptor<'sql', TTargetId, SqlControlFamilyInstance> {
37
+ readonly queryOperations?: () => SqlOperationDescriptors;
38
+ /**
39
+ * JSON ⇄ class boundary for the SQL target's contract. The descriptor
40
+ * composes a concrete `SqlContractSerializerBase` subclass; the rest
41
+ * of the control stack reaches `descriptor.contractSerializer` rather
42
+ * than importing a per-target deserialization function.
43
+ */
44
+ readonly contractSerializer: ContractSerializer<TContract>;
45
+ /**
46
+ * Per-target schema verifier walking the contract against
47
+ * `SqlSchemaIR`. The descriptor composes a concrete
48
+ * `SqlSchemaVerifierBase` subclass; the family-shared walk lives on
49
+ * the base, the target-specific dispatch on the subclass.
50
+ */
51
+ readonly schemaVerifier: SchemaVerifier<TContract, SqlSchemaIR>;
52
+ /**
53
+ * Database→PSL inference for `contract infer`. Target logic (owns the dialect
54
+ * maps), so it lives on the descriptor. Optional: targets without `contract
55
+ * infer` (Mongo) omit it, and the family instance throws when it is absent.
56
+ * `describedContracts` carries the stack's extension packs' already-assembled
57
+ * contracts (each paired with its `spaceId`) so the inferrer can omit elements
58
+ * they already describe, and can qualify a cross-space relation with the
59
+ * owning pack's space id.
60
+ */
61
+ readonly inferPslContract?: (
62
+ schema: SqlSchemaIRNode,
63
+ describedContracts?: readonly SqlDescribedContractSpace[],
64
+ ) => PslDocumentAst;
65
+ /**
66
+ * The full-tree node diff the family verify verdict derives from —
67
+ * expected-tree derivation, pre-diff normalization, the generic differ,
68
+ * and ownership scoping, all target-side. The family applies strict
69
+ * gating + control-policy disposition over the returned issues; verify
70
+ * rejects when a surviving issue is a failure.
71
+ */
72
+ readonly diffSchema: SqlSchemaDiffFn;
73
+ /**
74
+ * Classifies a diff-tree node's `nodeKind` into its framework-neutral
75
+ * {@link DiffSubjectGranularity} — the target owns the full node vocabulary
76
+ * that appears in its diff tree (its own kinds plus the relational kinds it
77
+ * delegates to), so it is the one place that can resolve this. The family
78
+ * verdict calls it inline per issue (never stamping the result); the
79
+ * framework aggregate's unclaimed-elements sweep reaches the same
80
+ * classifier via the family instance's `classifySubjectGranularity`
81
+ * capability.
82
+ */
83
+ readonly classifySubjectGranularity: (nodeKind: string) => DiffSubjectGranularity;
84
+ /**
85
+ * Classifies a diff-tree node's `nodeKind` into its storage `entityKind` —
86
+ * the same vocabulary the contract storage's `entries` dictionary keys use
87
+ * (e.g. `'table'`). Sibling of `classifySubjectGranularity`, resolved the
88
+ * same way: the target owns the full node vocabulary, so it is the one
89
+ * place that can resolve this. `undefined` for a node kind with no
90
+ * storage entity of its own (a column, an index, …). The framework
91
+ * aggregate's unclaimed-elements sweep reaches this via the family
92
+ * instance's `classifyEntityKind` capability, so it never hardcodes a
93
+ * family entity kind.
94
+ */
95
+ readonly classifyEntityKind: (nodeKind: string) => string | undefined;
96
+ createPlanner(adapter: SqlControlAdapter<TTargetId>): SqlMigrationPlanner<TTargetDetails>;
97
+ createRunner(family: SqlControlFamilyInstance): SqlMigrationRunner<TTargetDetails>;
98
+ }