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

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
@@ -1,4 +1,5 @@
1
1
  import type { ColumnDefault, Contract, JsonValue } from '@prisma-next/contract/types';
2
+ import type { CodecRef } from '@prisma-next/framework-components/codec';
2
3
  import type { MigrationPlannerConflict } from '@prisma-next/framework-components/control';
3
4
  import {
4
5
  type CheckConstraint,
@@ -12,16 +13,17 @@ import {
12
13
  type UniqueConstraint,
13
14
  } from '@prisma-next/sql-contract/types';
14
15
  import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
15
- import type {
16
- SqlAnnotations,
17
- SqlCheckConstraintIRInput,
18
- SqlColumnIR,
19
- SqlForeignKeyIR,
20
- SqlIndexIR,
16
+ import {
17
+ type SqlAnnotations,
18
+ type SqlCheckConstraintIRInput,
19
+ type SqlColumnIRInput,
20
+ type SqlForeignKeyIRInput,
21
+ type SqlIndexIRInput,
21
22
  SqlSchemaIR,
22
23
  SqlTableIR,
23
- SqlUniqueIR,
24
+ type SqlUniqueIRInput,
24
25
  } from '@prisma-next/sql-schema-ir/types';
26
+ import { blindCast } from '@prisma-next/utils/casts';
25
27
  import { ifDefined } from '@prisma-next/utils/defined';
26
28
 
27
29
  /**
@@ -72,7 +74,7 @@ function convertColumn(
72
74
  storageTypes: ResolvedStorageTypes,
73
75
  expandNativeType: NativeTypeExpander | undefined,
74
76
  renderDefault: DefaultRenderer | undefined,
75
- ): SqlColumnIR {
77
+ ): SqlColumnIRInput {
76
78
  // Resolve `typeRef` so columns that delegate their `nativeType`/`codecId`/
77
79
  // `typeParams` to a named `storage.types` entry expand the same way as
78
80
  // columns that inline those fields. Without this resolution, a
@@ -82,13 +84,14 @@ function convertColumn(
82
84
  // typeRef and produces `"vector(1536)"` — making diffs on the same
83
85
  // contract falsely report a `type_mismatch`.
84
86
  const resolved = resolveColumnTypeMetadata(column, storageTypes);
85
- const nativeType = expandNativeType
87
+ const baseNativeType = expandNativeType
86
88
  ? expandNativeType({
87
89
  nativeType: resolved.nativeType,
88
90
  codecId: resolved.codecId,
89
91
  ...ifDefined('typeParams', resolved.typeParams),
90
92
  })
91
93
  : resolved.nativeType;
94
+ const nativeType = column.many ? `${baseNativeType}[]` : baseNativeType;
92
95
  return {
93
96
  name,
94
97
  nativeType,
@@ -97,6 +100,43 @@ function convertColumn(
97
100
  'default',
98
101
  column.default != null && renderDefault ? renderDefault(column.default, column) : undefined,
99
102
  ),
103
+ // Contract-derived columns are resolved by construction: the computed
104
+ // full native type doubles as the resolved value, and the contract's
105
+ // structured default is the resolved default (the introspected side
106
+ // stamps its normalizer's parse of the raw expression).
107
+ resolvedNativeType: nativeType,
108
+ ...ifDefined('resolvedDefault', column.default ?? undefined),
109
+ // The column's codec identity, carried the same way the query AST
110
+ // carries `CodecRef` (TML-2456) — the migration planner's op-builders
111
+ // resolve DDL rendering from this at plan time (Decision 5), instead of
112
+ // reading a derivation-precomputed render payload.
113
+ codecRef: buildColumnCodecRef(resolved, column.many),
114
+ codecBaseNativeType: resolved.nativeType,
115
+ ...(column.typeRef !== undefined ? { codecNamedType: true } : {}),
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Builds the column's `CodecRef` from its resolved (post-`typeRef`) codec
121
+ * identity — the same construction the query AST and the migration DDL
122
+ * renderer already use (TML-2456, TML-2918).
123
+ */
124
+ function buildColumnCodecRef(
125
+ resolved: Pick<StorageColumn, 'codecId' | 'nativeType' | 'typeParams'>,
126
+ many: boolean | undefined,
127
+ ): CodecRef {
128
+ return {
129
+ codecId: resolved.codecId,
130
+ ...ifDefined(
131
+ 'typeParams',
132
+ resolved.typeParams !== undefined
133
+ ? blindCast<
134
+ JsonValue,
135
+ 'resolved.typeParams is JsonValue-shaped storage metadata; the narrowed (non-undefined) value lands in CodecRef.typeParams which is JsonValue'
136
+ >(resolved.typeParams)
137
+ : undefined,
138
+ ),
139
+ ...ifDefined('many', many),
100
140
  };
101
141
  }
102
142
 
@@ -192,26 +232,42 @@ function convertCheck(check: CheckConstraint, storage: SqlStorage): SqlCheckCons
192
232
  };
193
233
  }
194
234
 
195
- function convertUnique(unique: UniqueConstraint): SqlUniqueIR {
235
+ function convertUnique(unique: UniqueConstraint): SqlUniqueIRInput {
196
236
  return {
197
237
  columns: unique.columns,
198
238
  ...ifDefined('name', unique.name),
199
239
  };
200
240
  }
201
241
 
202
- function convertIndex(index: Index): SqlIndexIR {
242
+ function convertIndex(index: Index): SqlIndexIRInput {
203
243
  return {
204
244
  columns: index.columns,
205
245
  unique: false,
206
246
  ...ifDefined('name', index.name),
247
+ // Carried so the derived index node compares type/options against the
248
+ // introspected side (the legacy walk read them from the contract).
249
+ ...ifDefined('type', index.type),
250
+ ...ifDefined('options', index.options),
207
251
  };
208
252
  }
209
253
 
210
- function convertForeignKey(fk: ForeignKey): SqlForeignKeyIR {
254
+ /**
255
+ * The FK's referenced-namespace identity comes from the target's namespace
256
+ * node, not the raw namespace-id string. An unbound target namespace stamps
257
+ * no `referencedSchema` at all — the FK node's id renders the absence as the
258
+ * empty segment, which is what flat (single-schema) introspection produces,
259
+ * so both diff sides' FK ids meet by construction. A bound namespace (or a
260
+ * cross-space target whose namespace lives in another contract's storage)
261
+ * stamps its coordinate verbatim; namespaced targets (Postgres) resolve the
262
+ * real DDL schema downstream.
263
+ */
264
+ function convertForeignKey(fk: ForeignKey, storage: SqlStorage): SqlForeignKeyIRInput {
265
+ const targetNamespace = storage.namespaces[fk.target.namespaceId];
266
+ const targetIsUnbound = targetNamespace?.isUnbound === true;
211
267
  return {
212
268
  columns: fk.source.columns,
213
269
  referencedTable: fk.target.tableName,
214
- referencedSchema: fk.target.namespaceId,
270
+ ...(targetIsUnbound ? {} : { referencedSchema: fk.target.namespaceId }),
215
271
  referencedColumns: fk.target.columns,
216
272
  ...ifDefined('name', fk.name),
217
273
  ...ifDefined('onDelete', fk.onDelete),
@@ -227,7 +283,7 @@ function convertTable(
227
283
  renderDefault: DefaultRenderer | undefined,
228
284
  storage: SqlStorage,
229
285
  ): SqlTableIR {
230
- const columns: Record<string, SqlColumnIR> = {};
286
+ const columns: Record<string, SqlColumnIRInput> = {};
231
287
  for (const [colName, colDef] of Object.entries(table.columns)) {
232
288
  columns[colName] = convertColumn(
233
289
  colName,
@@ -243,7 +299,7 @@ function convertTable(
243
299
  ...table.uniques.map((unique) => unique.columns.join(',')),
244
300
  ...(table.primaryKey ? [table.primaryKey.columns.join(',')] : []),
245
301
  ]);
246
- const fkBackingIndexes: SqlIndexIR[] = [];
302
+ const fkBackingIndexes: SqlIndexIRInput[] = [];
247
303
  for (const fk of table.foreignKeys) {
248
304
  if (fk.index === false) continue;
249
305
  const key = fk.source.columns.join(',');
@@ -261,15 +317,17 @@ function convertTable(
261
317
  ? table.checks.map((c) => convertCheck(c, storage))
262
318
  : undefined;
263
319
 
264
- return {
320
+ return new SqlTableIR({
265
321
  name,
266
322
  columns,
267
323
  ...ifDefined('primaryKey', table.primaryKey),
268
- foreignKeys: table.foreignKeys.filter((fk) => fk.constraint !== false).map(convertForeignKey),
324
+ foreignKeys: table.foreignKeys
325
+ .filter((fk) => fk.constraint !== false)
326
+ .map((fk) => convertForeignKey(fk, storage)),
269
327
  uniques: table.uniques.map(convertUnique),
270
328
  indexes: [...table.indexes.map(convertIndex), ...fkBackingIndexes],
271
329
  ...ifDefined('checks', checks),
272
- };
330
+ });
273
331
  }
274
332
 
275
333
  /**
@@ -303,7 +361,7 @@ export function detectDestructiveChanges(
303
361
 
304
362
  for (const tableName of Object.keys(fromTables)) {
305
363
  const toTableRaw = toNs?.entries.table?.[tableName];
306
- if (!(toTableRaw instanceof StorageTable)) {
364
+ if (!StorageTable.is(toTableRaw)) {
307
365
  conflicts.push({
308
366
  kind: 'tableRemoved',
309
367
  summary: `Table "${tableName}" was removed`,
@@ -313,7 +371,7 @@ export function detectDestructiveChanges(
313
371
  const toTable = toTableRaw;
314
372
 
315
373
  const fromTableRaw = fromTables[tableName];
316
- if (!(fromTableRaw instanceof StorageTable)) continue;
374
+ if (!StorageTable.is(fromTableRaw)) continue;
317
375
  const fromTable = fromTableRaw;
318
376
 
319
377
  for (const columnName of Object.keys(fromTable.columns)) {
@@ -358,6 +416,48 @@ export interface ContractToSchemaIROptions {
358
416
  *
359
417
  * Returns an empty schema IR when `contract` is `null` (new project).
360
418
  */
419
+ /**
420
+ * Converts the tables of a single namespace into a `SqlSchemaIR`, keyed by
421
+ * table name within that namespace. Unlike {@link contractToSchemaIR}, which
422
+ * flattens every namespace's tables into one bare-keyed record (and throws on a
423
+ * cross-namespace name collision), this scopes the table iteration to one
424
+ * namespace so the same table name can exist in two schemas.
425
+ *
426
+ * The full `storage` is still passed to `convertTable`, so value-set / enum /
427
+ * type resolution that legitimately spans namespaces is unaffected. Foreign
428
+ * keys are built purely from the FK descriptor (`fk.target`), so cross-namespace
429
+ * FKs survive per-namespace conversion. The `annotations` block (storage-type
430
+ * derived) is omitted here — the per-namespace tree consumer reads only the
431
+ * per-table fields.
432
+ */
433
+ export function contractNamespaceToSchemaIR(
434
+ storage: SqlStorage,
435
+ namespaceId: string,
436
+ options: ContractToSchemaIROptions,
437
+ ): SqlSchemaIR {
438
+ if (options.annotationNamespace.length === 0) {
439
+ throw new Error('annotationNamespace must be a non-empty string');
440
+ }
441
+ const namespace = storage.namespaces[namespaceId];
442
+ if (!namespace) {
443
+ return new SqlSchemaIR({ tables: {} });
444
+ }
445
+ const storageTypes: ResolvedStorageTypes = { ...(storage.types ?? {}) };
446
+ const tables: Record<string, SqlTableIR> = {};
447
+ for (const [tableName, tableDefRaw] of Object.entries(namespace.entries.table ?? {})) {
448
+ StorageTable.assert(tableDefRaw, `namespaces.${namespaceId}.entries.table.${tableName}`);
449
+ tables[tableName] = convertTable(
450
+ tableName,
451
+ tableDefRaw,
452
+ storageTypes,
453
+ options.expandNativeType,
454
+ options.renderDefault,
455
+ storage,
456
+ );
457
+ }
458
+ return new SqlSchemaIR({ tables });
459
+ }
460
+
361
461
  export function contractToSchemaIR(
362
462
  contract: Contract<SqlStorage> | null,
363
463
  options: ContractToSchemaIROptions,
@@ -367,21 +467,15 @@ export function contractToSchemaIR(
367
467
  }
368
468
 
369
469
  if (!contract) {
370
- return { tables: {} };
470
+ return new SqlSchemaIR({ tables: {} });
371
471
  }
372
472
 
373
473
  const storage = contract.storage;
374
- const storageTypes: ResolvedStorageTypes = {
375
- ...((storage.types ?? {}) as ResolvedStorageTypes),
376
- };
474
+ const storageTypes: ResolvedStorageTypes = { ...(storage.types ?? {}) };
377
475
  const tables: Record<string, SqlTableIR> = {};
378
476
  for (const ns of Object.values(storage.namespaces)) {
379
477
  for (const [tableName, tableDefRaw] of Object.entries(ns.entries.table ?? {})) {
380
- if (!(tableDefRaw instanceof StorageTable)) {
381
- throw new Error(
382
- `contractToSchemaIR: expected StorageTable at namespaces.${ns.id}.entries.table.${tableName}`,
383
- );
384
- }
478
+ StorageTable.assert(tableDefRaw, `namespaces.${ns.id}.entries.table.${tableName}`);
385
479
  const tableDef = tableDefRaw;
386
480
  if (tables[tableName] !== undefined) {
387
481
  throw new Error(
@@ -405,10 +499,10 @@ export function contractToSchemaIR(
405
499
  options.resolveEnumNamespaceSchema,
406
500
  );
407
501
 
408
- return {
502
+ return new SqlSchemaIR({
409
503
  tables,
410
504
  ...ifDefined('annotations', annotations),
411
- };
505
+ });
412
506
  }
413
507
 
414
508
  function deriveAnnotations(
@@ -418,7 +512,7 @@ function deriveAnnotations(
418
512
  ): SqlAnnotations | undefined {
419
513
  const storageTypes: Record<string, StorageTypeInstance> = {};
420
514
 
421
- for (const typeInstance of Object.values((storage.types ?? {}) as ResolvedStorageTypes)) {
515
+ for (const typeInstance of Object.values(storage.types ?? {})) {
422
516
  if (isStorageTypeInstance(typeInstance)) {
423
517
  storageTypes[typeInstance.nativeType] = typeInstance;
424
518
  }
@@ -88,8 +88,8 @@ export function planFieldEventOperations(
88
88
  for (const tableName of tableNames) {
89
89
  const priorTableRaw = priorTables?.[tableName];
90
90
  const newTableRaw = newTables?.[tableName];
91
- const priorTable = priorTableRaw instanceof StorageTable ? priorTableRaw : undefined;
92
- const newTable = newTableRaw instanceof StorageTable ? newTableRaw : undefined;
91
+ const priorTable = StorageTable.is(priorTableRaw) ? priorTableRaw : undefined;
92
+ const newTable = StorageTable.is(newTableRaw) ? newTableRaw : undefined;
93
93
  const fieldNames = unionSorted(
94
94
  priorTable ? Object.keys(priorTable.columns) : [],
95
95
  newTable ? Object.keys(newTable.columns) : [],
@@ -0,0 +1,28 @@
1
+ import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
2
+ import { extractCodecControlHooks } from '../assembly';
3
+
4
+ /**
5
+ * Builds the codec-hook-composed `expandNativeType` callback the contract→IR
6
+ * derivation uses to expand parameterized native types (e.g. `character` +
7
+ * `{ length: 36 }` → `character(36)`). Returns `undefined` when no framework
8
+ * components are supplied, so callers can omit the option entirely.
9
+ */
10
+ export function buildNativeTypeExpander(
11
+ frameworkComponents?: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>,
12
+ ) {
13
+ if (!frameworkComponents) {
14
+ return undefined;
15
+ }
16
+ const codecHooks = extractCodecControlHooks(frameworkComponents);
17
+ return (input: {
18
+ readonly nativeType: string;
19
+ readonly codecId?: string;
20
+ readonly typeParams?: Record<string, unknown>;
21
+ }) => {
22
+ if (!input.typeParams) return input.nativeType;
23
+ if (!input.codecId) return input.nativeType;
24
+ const hooks = codecHooks.get(input.codecId);
25
+ if (!hooks?.expandNativeType) return input.nativeType;
26
+ return hooks.expandNativeType(input);
27
+ };
28
+ }
@@ -0,0 +1,41 @@
1
+ import type { Contract, ControlPolicy } from '@prisma-next/contract/types';
2
+ import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
3
+ import type { SchemaDiffIssue } from '@prisma-next/framework-components/control';
4
+ import type { SqlStorage } from '@prisma-next/sql-contract/types';
5
+ import type { SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
6
+
7
+ /**
8
+ * The full-tree node diff a SQL target produces for the family verify
9
+ * verdict: the target derives the expected tree from the contract, applies
10
+ * the pre-diff normalizations (semantic satisfaction, FK schema-segment
11
+ * resolution), runs the generic differ, and ownership-scopes the result.
12
+ * Strict gating, control-policy disposition, and the verdict itself are the
13
+ * family's post-diff filters over this output.
14
+ */
15
+ export interface SqlSchemaDiffResult {
16
+ /** The full, ownership-scoped diff issue list. */
17
+ readonly issues: readonly SchemaDiffIssue[];
18
+ /**
19
+ * Resolves a diff issue's subject table's declared control policy directly
20
+ * from the contract (Decision 5's own-layer-per-concern discipline extends
21
+ * here too: control policy is a contract concern, resolved by the target
22
+ * at disposition time — never stamped on the diff node). `undefined`
23
+ * when the issue's path resolves to no contract table (a genuine orphan,
24
+ * or a non-table subject).
25
+ */
26
+ readonly resolveControlPolicy: (issue: SchemaDiffIssue) => ControlPolicy | undefined;
27
+ /**
28
+ * The expected/actual namespace-node pairs the codec `verifyType` hooks
29
+ * run over — one per contract namespace with tables, paired by DDL
30
+ * schema; a flat target repeats its sole actual root per such namespace.
31
+ */
32
+ readonly namespacePairs: ReadonlyArray<{ readonly actual: SqlSchemaIRNode | undefined }>;
33
+ }
34
+
35
+ export interface SqlSchemaDiffInput {
36
+ readonly contract: Contract<SqlStorage>;
37
+ readonly schema: SqlSchemaIRNode;
38
+ readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
39
+ }
40
+
41
+ export type SqlSchemaDiffFn = (input: SqlSchemaDiffInput) => SqlSchemaDiffResult;
@@ -1,11 +1,9 @@
1
1
  import type { Contract } from '@prisma-next/contract/types';
2
2
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
3
3
  import type {
4
- ContractSerializer,
5
4
  ContractSpace,
6
5
  ControlAdapterDescriptor,
7
6
  ControlExtensionDescriptor,
8
- MigratableTargetDescriptor,
9
7
  MigrationOperationPolicy,
10
8
  MigrationPlan,
11
9
  MigrationPlannerConflict,
@@ -18,8 +16,8 @@ import type {
18
16
  MigrationRunnerResult,
19
17
  OperationContext,
20
18
  OpFactoryCall,
21
- SchemaIssue,
22
- SchemaVerifier,
19
+ SchemaDiffIssue,
20
+ SchemaOwnership,
23
21
  } from '@prisma-next/framework-components/control';
24
22
  import type { AggregateMigrationEdgeRef } from '@prisma-next/migration-tools/aggregate';
25
23
  import type {
@@ -30,10 +28,9 @@ import type {
30
28
  StorageTypeInstance,
31
29
  } from '@prisma-next/sql-contract/types';
32
30
  import type { SqlOperationDescriptors } from '@prisma-next/sql-operations';
33
- import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
31
+ import type { SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
34
32
  import type { Result } from '@prisma-next/utils/result';
35
33
  import type { SqlControlAdapter } from '../control-adapter';
36
- import type { SqlControlFamilyInstance } from '../control-instance';
37
34
 
38
35
  export type AnyRecord = Readonly<Record<string, unknown>>;
39
36
 
@@ -102,20 +99,29 @@ export interface FieldEventContext {
102
99
  }
103
100
 
104
101
  export interface CodecControlHooks<TTargetDetails = unknown> {
102
+ /**
103
+ * `schema` is typed as the family-level `SqlSchemaIRNode` (not the concrete
104
+ * `SqlSchemaIR` class) because the actual value handed in is whatever
105
+ * per-namespace node the calling target's tree shape produces — a flat
106
+ * `SqlSchemaIR` for SQLite, a `PostgresNamespaceSchemaNode` for Postgres —
107
+ * read structurally for its `tables`/`nativeEnumTypeNames` fields. Hooks
108
+ * that need the concrete Postgres shape narrow via
109
+ * `PostgresNamespaceSchemaNode.is(schema)`.
110
+ */
105
111
  planTypeOperations?: (options: {
106
112
  readonly typeName: string;
107
113
  readonly typeInstance: StorageTypeInstance;
108
114
  readonly contract: Contract<SqlStorage>;
109
- readonly schema: SqlSchemaIR;
115
+ readonly schema: SqlSchemaIRNode;
110
116
  readonly schemaName?: string;
111
117
  readonly policy: MigrationOperationPolicy;
112
118
  }) => StorageTypePlanResult<TTargetDetails>;
113
119
  verifyType?: (options: {
114
120
  readonly typeName: string;
115
121
  readonly typeInstance: StorageTypeInstance;
116
- readonly schema: SqlSchemaIR;
122
+ readonly schema: SqlSchemaIRNode;
117
123
  readonly schemaName?: string;
118
- }) => readonly SchemaIssue[];
124
+ }) => readonly SchemaDiffIssue[];
119
125
  introspectTypes?: (options: {
120
126
  readonly driver: SqlControlDriverInstance<string>;
121
127
  readonly schemaName?: string;
@@ -308,7 +314,12 @@ export type SqlPlannerResult<TTargetDetails> =
308
314
 
309
315
  export interface SqlMigrationPlannerPlanOptions {
310
316
  readonly contract: Contract<SqlStorage>;
311
- readonly schema: SqlSchemaIR;
317
+ /**
318
+ * The "from"/live schema as the target's introspected node (SQLite a flat
319
+ * `SqlSchemaIR`, Postgres a `PostgresDatabaseSchemaNode` root). Structure-aware
320
+ * consumers narrow the concrete shape before walking it.
321
+ */
322
+ readonly schema: SqlSchemaIRNode;
312
323
  readonly policy: MigrationOperationPolicy;
313
324
  readonly schemaName?: string;
314
325
  /**
@@ -343,6 +354,16 @@ export interface SqlMigrationPlannerPlanOptions {
343
354
  * All components must have matching familyId ('sql') and targetId.
344
355
  */
345
356
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
357
+ /**
358
+ * Ownership oracle over the whole contract-space composition (the passive
359
+ * aggregate). The planner asks it, per live extra node, whether any space
360
+ * declares that entity: a sibling-owned node is left untouched, an unowned
361
+ * node is a genuine extra it may drop under a destructive policy. The
362
+ * planner holds no list of other spaces' names — ownership lives in the
363
+ * aggregate; it only asks. Absent for a single-space plan handed no
364
+ * aggregate. See {@link SchemaOwnership}.
365
+ */
366
+ readonly ownership?: SchemaOwnership;
346
367
  }
347
368
 
348
369
  export interface SqlMigrationPlanner<TTargetDetails> {
@@ -456,30 +477,6 @@ export interface SqlMigrationRunner<TTargetDetails> {
456
477
  ): Promise<SqlMigrationRunnerResult>;
457
478
  }
458
479
 
459
- export interface SqlControlTargetDescriptor<
460
- TTargetId extends string,
461
- TTargetDetails,
462
- TContract extends Contract<SqlStorage> = Contract<SqlStorage>,
463
- > extends MigratableTargetDescriptor<'sql', TTargetId, SqlControlFamilyInstance> {
464
- readonly queryOperations?: () => SqlOperationDescriptors;
465
- /**
466
- * JSON ⇄ class boundary for the SQL target's contract. The descriptor
467
- * composes a concrete `SqlContractSerializerBase` subclass; the rest
468
- * of the control stack reaches `descriptor.contractSerializer` rather
469
- * than importing a per-target deserialization function.
470
- */
471
- readonly contractSerializer: ContractSerializer<TContract>;
472
- /**
473
- * Per-target schema verifier walking the contract against
474
- * `SqlSchemaIR`. The descriptor composes a concrete
475
- * `SqlSchemaVerifierBase` subclass; the family-shared walk lives on
476
- * the base, the target-specific dispatch on the subclass.
477
- */
478
- readonly schemaVerifier: SchemaVerifier<TContract, SqlSchemaIR>;
479
- createPlanner(adapter: SqlControlAdapter<TTargetId>): SqlMigrationPlanner<TTargetDetails>;
480
- createRunner(family: SqlControlFamilyInstance): SqlMigrationRunner<TTargetDetails>;
481
- }
482
-
483
480
  export interface CreateSqlMigrationPlanOptions<TTargetDetails> {
484
481
  readonly targetId: string;
485
482
  /**
@@ -127,6 +127,21 @@ export function toEnumName(pgTypeName: string): NameResult {
127
127
  return { name };
128
128
  }
129
129
 
130
+ const VALID_IDENTIFIER_PATTERN = /^[A-Za-z_]\w*$/;
131
+
132
+ /**
133
+ * PSL member name for a native-enum value. The value itself always prints
134
+ * explicitly (`member = "value"`), so the returned name never needs a map:
135
+ * a value that already is a valid, non-reserved identifier is kept verbatim
136
+ * (case included); anything else is camelCased/escaped like a field name.
137
+ */
138
+ export function toEnumMemberName(value: string): string {
139
+ if (VALID_IDENTIFIER_PATTERN.test(value) && !needsEscaping(value)) {
140
+ return value;
141
+ }
142
+ return escapeIfNeeded(snakeToCamelCase(value));
143
+ }
144
+
130
145
  export function pluralize(word: string): string {
131
146
  if (
132
147
  word.endsWith('s') ||
@@ -1,12 +1,6 @@
1
1
  import type { ColumnDefault } from '@prisma-next/contract/types';
2
2
  import type { DefaultMappingOptions } from './default-mapping';
3
3
 
4
- /**
5
- * Internal printer-shaped configuration, used by the SQL family's
6
- * `sqlSchemaIrToPslAst` helper (M2). The framework-level psl-printer no longer
7
- * exposes these — they're consumed only inside the SQL family.
8
- */
9
-
10
4
  export type PslNativeTypeAttribute = {
11
5
  readonly name: string;
12
6
  readonly args?: readonly string[];
@@ -43,6 +37,18 @@ export interface PslPrinterOptions {
43
37
  export type RelationField = {
44
38
  readonly fieldName: string;
45
39
  readonly typeName: string;
40
+ /**
41
+ * Namespace qualifier for a cross-space relation (e.g. `"auth"` for a
42
+ * relation into `supabase:auth.AuthUser`). Absent for a same-namespace
43
+ * relation.
44
+ */
45
+ readonly typeNamespaceId?: string | undefined;
46
+ /**
47
+ * Contract-space qualifier for a relation into another stack extension
48
+ * pack's contract space (e.g. `"supabase"`). Absent for a same-space
49
+ * relation.
50
+ */
51
+ readonly typeContractSpaceId?: string | undefined;
46
52
  readonly referencedTableName?: string | undefined;
47
53
  readonly optional: boolean;
48
54
  readonly list: boolean;
@@ -136,7 +136,15 @@ function deriveRelationName(
136
136
  return fk.columns.join('_');
137
137
  }
138
138
 
139
- function buildChildRelationField(
139
+ /**
140
+ * Builds the child-side {@link RelationField} for a single foreign key:
141
+ * `typeName` is the parent model name, `fields`/`references` are the FK's raw
142
+ * columns, and `onDelete`/`onUpdate` are normalized to their PSL spelling.
143
+ * Exported so a caller resolving a foreign key against a model outside
144
+ * `tables` (e.g. a cross-space reference into another contract) can reuse the
145
+ * same normalization instead of duplicating it.
146
+ */
147
+ export function buildChildRelationField(
140
148
  fieldName: string,
141
149
  parentModelName: string,
142
150
  fk: SqlForeignKeyIR,
@@ -1,5 +1,7 @@
1
+ import type { Contract } from '@prisma-next/contract/types';
1
2
  import { deriveProvidedInvariants } from '@prisma-next/migration-tools/invariants';
2
3
  import { Migration } from '@prisma-next/migration-tools/migration';
4
+ import type { SqlStorage } from '@prisma-next/sql-contract/types';
3
5
  import { isThenable } from '@prisma-next/utils/promise';
4
6
  import type { SqlMigrationPlanOperation, SqlPlanTargetDetails } from './migrations/types';
5
7
 
@@ -14,13 +16,22 @@ import type { SqlMigrationPlanOperation, SqlPlanTargetDetails } from './migratio
14
16
  * fully concrete operation shape. Target-free code in SQL family / tooling
15
17
  * parameterises over `TDetails` (and usually `TTargetId = string`).
16
18
  *
19
+ * The `Start` / `End` contract generics are forwarded to the framework
20
+ * `Migration` base so the derived `describe()` and the per-target view getters
21
+ * (`PostgresMigration` / `SqliteMigration`) carry each migration's precise
22
+ * contract types. The view getters themselves live on the per-target bases
23
+ * because the SQL ContractView shapes differ per target (SQLite unwraps its
24
+ * sole namespace to the root; Postgres is schema-qualified).
25
+ *
17
26
  * Keeps target-free contract/runtime features in the family layer while
18
27
  * letting adapters own target shape.
19
28
  */
20
29
  export abstract class SqlMigration<
21
30
  TDetails extends SqlPlanTargetDetails,
22
31
  TTargetId extends string = string,
23
- > extends Migration<SqlMigrationPlanOperation<TDetails>, 'sql', TTargetId> {
32
+ Start extends Contract<SqlStorage> = Contract<SqlStorage>,
33
+ End extends Contract<SqlStorage> = Contract<SqlStorage>,
34
+ > extends Migration<SqlMigrationPlanOperation<TDetails>, 'sql', TTargetId, Start, End> {
24
35
  /**
25
36
  * Sorted, deduplicated invariant ids declared by this migration's
26
37
  * data-transform ops. Derived from `this.operations` so the field remains
@@ -14,6 +14,10 @@ export type {
14
14
  export { assembleAuthoringContributions } from '@prisma-next/framework-components/control';
15
15
  export { extractCodecControlHooks } from '../core/assembly';
16
16
  export type { SqlControlFamilyInstance } from '../core/control-instance';
17
+ export type {
18
+ SqlControlTargetDescriptor,
19
+ SqlDescribedContractSpace,
20
+ } from '../core/control-target-descriptor';
17
21
  export type {
18
22
  ContractToSchemaIROptions,
19
23
  DefaultRenderer,
@@ -22,6 +26,7 @@ export type {
22
26
  } from '../core/migrations/contract-to-schema-ir';
23
27
  // Contract → SchemaIR conversion for offline migration planning
24
28
  export {
29
+ contractNamespaceToSchemaIR,
25
30
  contractToSchemaIR,
26
31
  detectDestructiveChanges,
27
32
  resolveValueSetValues,
@@ -34,6 +39,7 @@ export {
34
39
  } from '../core/migrations/control-policy';
35
40
  export type { PlanFieldEventOperationsOptions } from '../core/migrations/field-event-planner';
36
41
  export { planFieldEventOperations } from '../core/migrations/field-event-planner';
42
+ export { buildNativeTypeExpander } from '../core/migrations/native-type-expander';
37
43
  export {
38
44
  createMigrationPlan,
39
45
  plannerFailure,
@@ -42,6 +48,11 @@ export {
42
48
  runnerSuccess,
43
49
  } from '../core/migrations/plan-helpers';
44
50
  export { INIT_ADDITIVE_POLICY } from '../core/migrations/policies';
51
+ export type {
52
+ SqlSchemaDiffFn,
53
+ SqlSchemaDiffInput,
54
+ SqlSchemaDiffResult,
55
+ } from '../core/migrations/schema-differ';
45
56
  export type {
46
57
  CodecControlHooks,
47
58
  CreateSqlMigrationPlanOptions,
@@ -51,7 +62,6 @@ export type {
51
62
  ResolveIdentityValueInput,
52
63
  SqlControlAdapterDescriptor,
53
64
  SqlControlExtensionDescriptor,
54
- SqlControlTargetDescriptor,
55
65
  SqlMigrationPlan,
56
66
  SqlMigrationPlanContractInfo,
57
67
  SqlMigrationPlanner,