@prisma-next/family-sql 0.14.0-dev.6 → 0.14.0-dev.60

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 (79) 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-BeN8TDZ5.d.mts} +29 -8
  4. package/dist/control-adapter-BeN8TDZ5.d.mts.map +1 -0
  5. package/dist/control-adapter.d.mts +1 -1
  6. package/dist/control.d.mts +238 -6
  7. package/dist/control.d.mts.map +1 -1
  8. package/dist/control.mjs +347 -942
  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 +108 -0
  24. package/dist/psl-infer.d.mts.map +1 -0
  25. package/dist/psl-infer.mjs +353 -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-W3r631Jh.mjs +226 -0
  30. package/dist/schema-verify-W3r631Jh.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-COTsneBU.d.mts} +34 -139
  34. package/dist/types-COTsneBU.d.mts.map +1 -0
  35. package/package.json +23 -23
  36. package/src/core/authoring-entity-types.ts +12 -37
  37. package/src/core/control-adapter.ts +10 -5
  38. package/src/core/control-instance.ts +210 -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 +126 -32
  47. package/src/core/migrations/field-event-planner.ts +2 -2
  48. package/src/core/migrations/native-type-expander.ts +28 -0
  49. package/src/core/migrations/schema-differ.ts +41 -0
  50. package/src/core/migrations/types.ts +31 -34
  51. package/src/core/psl-contract-infer/name-transforms.ts +15 -0
  52. package/src/core/psl-contract-infer/printer-config.ts +12 -6
  53. package/src/core/psl-contract-infer/relation-inference.ts +9 -1
  54. package/src/core/sql-migration.ts +12 -1
  55. package/src/exports/control.ts +11 -1
  56. package/src/exports/diff.ts +25 -0
  57. package/src/exports/psl-infer.ts +40 -0
  58. package/dist/authoring-type-constructors-CjFfO6LM.mjs.map +0 -1
  59. package/dist/control-adapter-Cmw9LvEP.d.mts.map +0 -1
  60. package/dist/schema-verify.d.mts +0 -39
  61. package/dist/schema-verify.d.mts.map +0 -1
  62. package/dist/schema-verify.mjs +0 -2
  63. package/dist/sql-contract-serializer-BR2vC7Z-.mjs.map +0 -1
  64. package/dist/test-utils.d.mts +0 -2
  65. package/dist/test-utils.mjs +0 -2
  66. package/dist/types-kgstZ_Zd.d.mts.map +0 -1
  67. package/dist/verify-sql-schema-thU-jKpf.d.mts +0 -66
  68. package/dist/verify-sql-schema-thU-jKpf.d.mts.map +0 -1
  69. package/dist/verify-sql-schema-xT4udQLQ.mjs +0 -1501
  70. package/dist/verify-sql-schema-xT4udQLQ.mjs.map +0 -1
  71. package/src/core/psl-contract-infer/postgres-default-mapping.ts +0 -16
  72. package/src/core/psl-contract-infer/postgres-type-map.ts +0 -157
  73. package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +0 -795
  74. package/src/core/schema-verify/control-verify-emit.ts +0 -46
  75. package/src/core/schema-verify/verifier-disposition.ts +0 -58
  76. package/src/core/schema-verify/verify-helpers.ts +0 -820
  77. package/src/core/schema-verify/verify-sql-schema.ts +0 -1311
  78. package/src/exports/schema-verify.ts +0 -18
  79. 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,
@@ -294,8 +325,6 @@ export interface SqlControlFamilyInstance
294
325
 
295
326
  bootstrapControlTableQueries(): readonly DdlNode[];
296
327
 
297
- bootstrapSignMarkerQueries(): readonly DdlNode[];
298
-
299
328
  toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview;
300
329
  }
301
330
 
@@ -523,12 +552,66 @@ export function createSqlFamilyInstance<TTargetId extends string>(
523
552
 
524
553
  const targetSerializer = (
525
554
  target as unknown as {
526
- contractSerializer?: { deserializeContract(json: unknown): Contract<SqlStorage> };
555
+ contractSerializer?: {
556
+ deserializeContract(json: unknown): Contract<SqlStorage>;
557
+ serializeContract(contract: Contract<SqlStorage>): unknown;
558
+ };
527
559
  }
528
560
  ).contractSerializer;
529
- const deserializeWithTargetSerializer = (contractJson: unknown): Contract<SqlStorage> => {
561
+ // Database→PSL inference is target logic (it owns the dialect type/default
562
+ // maps and walks its own schema tree), so it is read off the descriptor like
563
+ // `contractSerializer`. Absent for targets without `contract infer` (Mongo).
564
+ const targetInferPslContract = blindCast<
565
+ SqlControlTargetDescriptor<TTargetId, unknown>,
566
+ 'reading the optional target-descriptor inferPslContract hook'
567
+ >(target).inferPslContract;
568
+ // The full-tree node diff the verify VERDICT derives from. Read lazily so
569
+ // construction-only stub descriptors (schema-view tests) keep working; the
570
+ // throw happens at verify time.
571
+ const diffSchema = blindCast<
572
+ { readonly diffSchema?: SqlSchemaDiffFn },
573
+ 'reading the target-descriptor diffSchema hook'
574
+ >(target).diffSchema;
575
+ // The target's nodeKind → granularity classifier, resolved on demand by the
576
+ // verdict and by the instance's `classifySubjectGranularity` method below —
577
+ // never stamped onto an issue. Read lazily for the same
578
+ // construction-only-stub reason as `diffSchema`; the throw happens at call
579
+ // time.
580
+ const targetGranularityOf = blindCast<
581
+ { readonly classifySubjectGranularity?: (nodeKind: string) => DiffSubjectGranularity },
582
+ 'reading the target-descriptor classifySubjectGranularity hook'
583
+ >(target).classifySubjectGranularity;
584
+ // The target's nodeKind → storage entityKind classifier — sibling of
585
+ // `targetGranularityOf` above, read the same lazy way for the same
586
+ // construction-only-stub reason.
587
+ const targetEntityKindOf = blindCast<
588
+ { readonly classifyEntityKind?: (nodeKind: string) => string | undefined },
589
+ 'reading the target-descriptor classifyEntityKind hook'
590
+ >(target).classifyEntityKind;
591
+ // `contract infer` needs each extension pack's already-assembled contract,
592
+ // carried as-is (no merging — that is the contract-spaces machinery's
593
+ // concern), paired with the `spaceId` its descriptor was registered under
594
+ // (neither the contract JSON nor `ContractSpace` self-declares it), so the
595
+ // target hook can omit elements those contracts describe and qualify a
596
+ // cross-space relation with the owning pack's space id.
597
+ const describedContracts: readonly SqlDescribedContractSpace[] = extensions.flatMap(
598
+ (extension) =>
599
+ extension.contractSpace
600
+ ? [{ spaceId: extension.id, contract: extension.contractSpace.contractJson }]
601
+ : [],
602
+ );
603
+ const deserializeWithTargetSerializer = (contractOrJson: unknown): Contract<SqlStorage> => {
530
604
  const serializer = targetSerializer ?? new SqlContractSerializer();
531
- return serializer.deserializeContract(contractJson) as Contract<SqlStorage>;
605
+ const json =
606
+ targetSerializer !== undefined && !isPlainRecord(contractOrJson)
607
+ ? targetSerializer.serializeContract(
608
+ blindCast<
609
+ Contract<SqlStorage>,
610
+ 'isPlainRecord returned false, so contractOrJson is a class instance, not raw JSON'
611
+ >(contractOrJson),
612
+ )
613
+ : contractOrJson;
614
+ return serializer.deserializeContract(json) as Contract<SqlStorage>;
532
615
  };
533
616
 
534
617
  return {
@@ -669,22 +752,67 @@ export function createSqlFamilyInstance<TTargetId extends string>(
669
752
 
670
753
  verifySchema(options: {
671
754
  readonly contract: unknown;
672
- readonly schema: SqlSchemaIR;
755
+ readonly schema: SqlSchemaIRNode;
673
756
  readonly strict: boolean;
674
757
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
675
758
  }): VerifyDatabaseSchemaResult {
676
759
  const contract = deserializeWithTargetSerializer(options.contract) as Contract<SqlStorage>;
677
- const controlAdapter = getControlAdapter();
678
- return verifySqlSchema({
760
+ if (!diffSchema) {
761
+ throw new Error(
762
+ `SQL target "${target.targetId}" is missing the required diffSchema descriptor operation`,
763
+ );
764
+ }
765
+ if (!targetGranularityOf) {
766
+ throw new Error(
767
+ `SQL target "${target.targetId}" is missing the required classifySubjectGranularity descriptor operation`,
768
+ );
769
+ }
770
+ // THE VERDICT: the target's full-tree node diff, graded by the
771
+ // family's post-diff filters (strict gating + control-policy
772
+ // disposition), plus the codec verifyType hook findings. The result
773
+ // is issue-based — `ok` holds exactly when both issue lists are empty.
774
+ return verifySqlSchemaByDiff({
679
775
  contract,
680
776
  schema: options.schema,
681
777
  strict: options.strict,
682
- typeMetadataRegistry,
683
778
  frameworkComponents: options.frameworkComponents,
684
- ...ifDefined('normalizeDefault', controlAdapter.normalizeDefault),
685
- ...ifDefined('normalizeNativeType', controlAdapter.normalizeNativeType),
779
+ diffSchema,
780
+ granularityOf: targetGranularityOf,
686
781
  });
687
782
  },
783
+
784
+ /**
785
+ * Classifies a diff issue's subject granularity on demand, by resolving
786
+ * its node's `nodeKind` through the target's classifier — the
787
+ * {@link import('@prisma-next/framework-components/control').SchemaSubjectClassifierCapable}
788
+ * capability. Framework consumers spanning contract spaces (the
789
+ * migration aggregate's unclaimed-elements sweep) detect and call this
790
+ * instead of reaching into the concrete schema-IR node, which they
791
+ * cannot read; nothing is stamped on the issue or the node.
792
+ */
793
+ classifySubjectGranularity(issue: SchemaDiffIssue): DiffSubjectGranularity | undefined {
794
+ if (!targetGranularityOf) {
795
+ throw new Error(
796
+ `SQL target "${target.targetId}" is missing the required classifySubjectGranularity descriptor operation`,
797
+ );
798
+ }
799
+ return classifyDiffSubjectGranularity(issue, targetGranularityOf);
800
+ },
801
+ /**
802
+ * Classifies a diff issue's subject storage `entityKind` on demand, by
803
+ * resolving its node's `nodeKind` through the target's classifier —
804
+ * the sibling of `classifySubjectGranularity` above, and part of the
805
+ * same {@link import('@prisma-next/framework-components/control').SchemaSubjectClassifierCapable}
806
+ * capability.
807
+ */
808
+ classifyEntityKind(issue: SchemaDiffIssue): string | undefined {
809
+ if (!targetEntityKindOf) {
810
+ throw new Error(
811
+ `SQL target "${target.targetId}" is missing the required classifyEntityKind descriptor operation`,
812
+ );
813
+ }
814
+ return classifyDiffEntityKind(issue, targetEntityKindOf);
815
+ },
688
816
  async sign(options: {
689
817
  readonly driver: SqlControlDriverInstance<string>;
690
818
  readonly contract: unknown;
@@ -848,12 +976,17 @@ export function createSqlFamilyInstance<TTargetId extends string>(
848
976
  async introspect(options: {
849
977
  readonly driver: SqlControlDriverInstance<string>;
850
978
  readonly contract?: unknown;
851
- }): Promise<SqlSchemaIR> {
979
+ }): Promise<SqlSchemaIRNode> {
852
980
  return getControlAdapter().introspect(options.driver, options.contract);
853
981
  },
854
982
 
855
- inferPslContract(schemaIR: SqlSchemaIR): PslDocumentAst {
856
- return sqlSchemaIrToPslAst(schemaIR);
983
+ inferPslContract(schemaIR: SqlSchemaIRNode): PslDocumentAst {
984
+ if (!targetInferPslContract) {
985
+ throw new Error(
986
+ `Target "${target.targetId}" does not support contract infer (no inferPslContract on its descriptor).`,
987
+ );
988
+ }
989
+ return targetInferPslContract(schemaIR, describedContracts);
857
990
  },
858
991
 
859
992
  lowerAst(
@@ -867,17 +1000,52 @@ export function createSqlFamilyInstance<TTargetId extends string>(
867
1000
  return getControlAdapter().bootstrapControlTableQueries();
868
1001
  },
869
1002
 
870
- bootstrapSignMarkerQueries(): readonly DdlNode[] {
871
- return getControlAdapter().bootstrapSignMarkerQueries();
872
- },
873
-
874
1003
  toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview {
875
1004
  return sqlOperationsToPreview(operations);
876
1005
  },
877
1006
 
878
- toSchemaView(schema: SqlSchemaIR): CoreSchemaView {
879
- const tableNodes: readonly SchemaTreeNode[] = Object.entries(schema.tables).map(
880
- ([tableName, table]: [string, SqlTableIR]) => {
1007
+ toSchemaView(schema: SqlSchemaIRNode): CoreSchemaView {
1008
+ // Walk the schema-IR tree's own structure (root → namespaces → tables)
1009
+ // into one flat list of table nodes. A root that exposes a `namespaces`
1010
+ // record (Postgres) contributes each namespace's tables; a flat root
1011
+ // (SQLite) is its own single namespace. The single-schema common case
1012
+ // renders the same table-level view as today — no synthetic namespace
1013
+ // level.
1014
+ const root = blindCast<
1015
+ {
1016
+ readonly namespaces?: Readonly<
1017
+ Record<string, { readonly tables: Record<string, SqlTableIR> }>
1018
+ >;
1019
+ readonly tables?: Record<string, SqlTableIR>;
1020
+ },
1021
+ 'structural read of the schema-IR tree own namespaces/tables records'
1022
+ >(schema);
1023
+ // A multi-namespace root qualifies every table's display name with its
1024
+ // namespace key, so same-named tables in different namespaces (e.g.
1025
+ // public.thing and auth.thing) render distinct ids and labels. The
1026
+ // single-namespace case (and a flat SQLite root) keeps today's bare
1027
+ // names. Synthesized constraint/index fallback names keep the bare table
1028
+ // name — that is the name the database derives them from.
1029
+ const namespaceEntries: ReadonlyArray<[string | undefined, Record<string, SqlTableIR>]> =
1030
+ root.namespaces !== undefined
1031
+ ? Object.entries(root.namespaces).map(
1032
+ ([namespaceKey, namespace]): [string, Record<string, SqlTableIR>] => [
1033
+ namespaceKey,
1034
+ namespace.tables,
1035
+ ],
1036
+ )
1037
+ : [[undefined, root.tables ?? {}]];
1038
+ const qualify = namespaceEntries.length > 1;
1039
+ const tableEntries: ReadonlyArray<[string, string, SqlTableIR]> = namespaceEntries.flatMap(
1040
+ ([namespaceKey, tables]) =>
1041
+ Object.entries(tables).map(([tableName, table]): [string, string, SqlTableIR] => [
1042
+ qualify && namespaceKey !== undefined ? `${namespaceKey}.${tableName}` : tableName,
1043
+ tableName,
1044
+ table,
1045
+ ]),
1046
+ );
1047
+ const tableNodes: readonly SchemaTreeNode[] = tableEntries.map(
1048
+ ([displayName, tableName, table]: [string, string, SqlTableIR]) => {
881
1049
  const children: SchemaTreeNode[] = [];
882
1050
 
883
1051
  const columnNodes: SchemaTreeNode[] = [];
@@ -888,7 +1056,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
888
1056
  columnNodes.push(
889
1057
  new SchemaTreeNode({
890
1058
  kind: 'field',
891
- id: `column-${tableName}-${columnName}`,
1059
+ id: `column-${displayName}-${columnName}`,
892
1060
  label,
893
1061
  meta: {
894
1062
  nativeType: column.nativeType,
@@ -903,7 +1071,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
903
1071
  children.push(
904
1072
  new SchemaTreeNode({
905
1073
  kind: 'collection',
906
- id: `columns-${tableName}`,
1074
+ id: `columns-${displayName}`,
907
1075
  label: 'columns',
908
1076
  children: columnNodes,
909
1077
  }),
@@ -915,7 +1083,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
915
1083
  children.push(
916
1084
  new SchemaTreeNode({
917
1085
  kind: 'index',
918
- id: `primary-key-${tableName}`,
1086
+ id: `primary-key-${displayName}`,
919
1087
  label: `primary key: ${pkColumns}`,
920
1088
  meta: {
921
1089
  columns: table.primaryKey.columns,
@@ -931,7 +1099,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
931
1099
  children.push(
932
1100
  new SchemaTreeNode({
933
1101
  kind: 'index',
934
- id: `unique-${tableName}-${name}`,
1102
+ id: `unique-${displayName}-${name}`,
935
1103
  label,
936
1104
  meta: {
937
1105
  columns: unique.columns,
@@ -947,7 +1115,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
947
1115
  children.push(
948
1116
  new SchemaTreeNode({
949
1117
  kind: 'index',
950
- id: `index-${tableName}-${name}`,
1118
+ id: `index-${displayName}-${name}`,
951
1119
  label,
952
1120
  meta: {
953
1121
  columns: index.columns,
@@ -975,8 +1143,8 @@ export function createSqlFamilyInstance<TTargetId extends string>(
975
1143
 
976
1144
  return new SchemaTreeNode({
977
1145
  kind: 'entity',
978
- id: `table-${tableName}`,
979
- label: `table ${tableName}`,
1146
+ id: `table-${displayName}`,
1147
+ label: `table ${displayName}`,
980
1148
  ...(Object.keys(tableMeta).length > 0 ? { meta: tableMeta } : {}),
981
1149
  ...(children.length > 0 ? { children } : {}),
982
1150
  });
@@ -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
+ }