@prisma-next/family-sql 0.15.0 → 0.16.0-dev.1

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 (49) hide show
  1. package/dist/{control-adapter-BQgad8Zc.d.mts → control-adapter-CW7FdxeS.d.mts} +1 -2
  2. package/dist/control-adapter-CW7FdxeS.d.mts.map +1 -0
  3. package/dist/control-adapter.d.mts +1 -1
  4. package/dist/control.d.mts +127 -5
  5. package/dist/control.d.mts.map +1 -1
  6. package/dist/control.mjs +71 -33
  7. package/dist/control.mjs.map +1 -1
  8. package/dist/diff.d.mts +3 -4
  9. package/dist/diff.d.mts.map +1 -1
  10. package/dist/diff.mjs +1 -1
  11. package/dist/ir.d.mts +0 -1
  12. package/dist/ir.d.mts.map +1 -1
  13. package/dist/migration.d.mts +1 -2
  14. package/dist/migration.d.mts.map +1 -1
  15. package/dist/pack.d.mts.map +1 -1
  16. package/dist/psl-infer.d.mts +2 -2
  17. package/dist/psl-infer.d.mts.map +1 -1
  18. package/dist/psl-infer.mjs +3 -4
  19. package/dist/psl-infer.mjs.map +1 -1
  20. package/dist/runtime.d.mts +0 -1
  21. package/dist/runtime.d.mts.map +1 -1
  22. package/dist/runtime.mjs +1 -1
  23. package/dist/{schema-differ-DnoopSXm.d.mts → schema-differ-C8OHo1V5.d.mts} +1 -2
  24. package/dist/schema-differ-C8OHo1V5.d.mts.map +1 -0
  25. package/dist/{schema-verify-W3r631Jh.mjs → schema-verify-DY21bMwy.mjs} +8 -8
  26. package/dist/schema-verify-DY21bMwy.mjs.map +1 -0
  27. package/dist/{timestamp-now-generator-CloimujU.mjs → timestamp-now-generator-DRXygu32.mjs} +88 -2
  28. package/dist/timestamp-now-generator-DRXygu32.mjs.map +1 -0
  29. package/dist/{types-BPv_y7iS.d.mts → types-6JMCRorL.d.mts} +3 -4
  30. package/dist/types-6JMCRorL.d.mts.map +1 -0
  31. package/dist/verify.d.mts +0 -1
  32. package/dist/verify.d.mts.map +1 -1
  33. package/package.json +25 -23
  34. package/src/core/diff/schema-verify.ts +5 -5
  35. package/src/core/diff/verifier-disposition.ts +3 -3
  36. package/src/core/migrations/contract-to-schema-ir.ts +96 -29
  37. package/src/core/migrations/types.ts +10 -10
  38. package/src/core/psl-contract-infer/name-transforms.ts +3 -13
  39. package/src/core/psl-contract-infer/relation-inference.ts +4 -1
  40. package/src/core/timestamp-now-generator.ts +88 -0
  41. package/src/exports/control.ts +3 -0
  42. package/dist/control-adapter-BQgad8Zc.d.mts.map +0 -1
  43. package/dist/foreign-key-index-backing-BP71iI-Q.mjs +0 -32
  44. package/dist/foreign-key-index-backing-BP71iI-Q.mjs.map +0 -1
  45. package/dist/schema-differ-DnoopSXm.d.mts.map +0 -1
  46. package/dist/schema-verify-W3r631Jh.mjs.map +0 -1
  47. package/dist/timestamp-now-generator-CloimujU.mjs.map +0 -1
  48. package/dist/types-BPv_y7iS.d.mts.map +0 -1
  49. package/src/core/foreign-key-index-backing.ts +0 -37
package/package.json CHANGED
@@ -1,35 +1,37 @@
1
1
  {
2
2
  "name": "@prisma-next/family-sql",
3
- "version": "0.15.0",
3
+ "version": "0.16.0-dev.1",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "description": "SQL family descriptor for Prisma Next",
8
8
  "dependencies": {
9
- "@prisma-next/contract": "0.15.0",
10
- "@prisma-next/emitter": "0.15.0",
11
- "@prisma-next/framework-components": "0.15.0",
12
- "@prisma-next/migration-tools": "0.15.0",
13
- "@prisma-next/operations": "0.15.0",
14
- "@prisma-next/sql-contract": "0.15.0",
15
- "@prisma-next/sql-contract-emitter": "0.15.0",
16
- "@prisma-next/sql-contract-ts": "0.15.0",
17
- "@prisma-next/sql-operations": "0.15.0",
18
- "@prisma-next/sql-relational-core": "0.15.0",
19
- "@prisma-next/sql-runtime": "0.15.0",
20
- "@prisma-next/sql-schema-ir": "0.15.0",
21
- "@prisma-next/utils": "0.15.0",
22
- "arktype": "^2.2.2"
9
+ "@prisma-next/contract": "0.16.0-dev.1",
10
+ "@prisma-next/emitter": "0.16.0-dev.1",
11
+ "@prisma-next/framework-components": "0.16.0-dev.1",
12
+ "@prisma-next/migration-tools": "0.16.0-dev.1",
13
+ "@prisma-next/operations": "0.16.0-dev.1",
14
+ "@prisma-next/sql-contract": "0.16.0-dev.1",
15
+ "@prisma-next/sql-contract-emitter": "0.16.0-dev.1",
16
+ "@prisma-next/sql-contract-ts": "0.16.0-dev.1",
17
+ "@prisma-next/sql-operations": "0.16.0-dev.1",
18
+ "@prisma-next/sql-relational-core": "0.16.0-dev.1",
19
+ "@prisma-next/sql-runtime": "0.16.0-dev.1",
20
+ "@prisma-next/sql-schema-ir": "0.16.0-dev.1",
21
+ "@prisma-next/utils": "0.16.0-dev.1",
22
+ "arktype": "^2.2.2",
23
+ "pluralize": "^8.0.0"
23
24
  },
24
25
  "devDependencies": {
25
- "@prisma-next/driver-postgres": "0.15.0",
26
- "@prisma-next/psl-parser": "0.15.0",
27
- "@prisma-next/psl-printer": "0.15.0",
28
- "@prisma-next/sql-contract-psl": "0.15.0",
29
- "@prisma-next/test-utils": "0.15.0",
30
- "@prisma-next/tsconfig": "0.15.0",
31
- "@prisma-next/tsdown": "0.15.0",
32
- "tsdown": "0.22.3",
26
+ "@prisma-next/driver-postgres": "0.16.0-dev.1",
27
+ "@prisma-next/psl-parser": "0.16.0-dev.1",
28
+ "@prisma-next/psl-printer": "0.16.0-dev.1",
29
+ "@prisma-next/sql-contract-psl": "0.16.0-dev.1",
30
+ "@prisma-next/test-utils": "0.16.0-dev.1",
31
+ "@prisma-next/tsconfig": "0.16.0-dev.1",
32
+ "@prisma-next/tsdown": "0.16.0-dev.1",
33
+ "@types/pluralize": "^0.0.33",
34
+ "tsdown": "0.22.8",
33
35
  "typescript": "5.9.3",
34
36
  "vitest": "4.1.10"
35
37
  },
@@ -23,7 +23,7 @@ import type {
23
23
  VerifierOutcome,
24
24
  VerifyDatabaseSchemaResult,
25
25
  } from '@prisma-next/framework-components/control';
26
- import { dispositionForCategory } from '@prisma-next/framework-components/control';
26
+ import { dispositionForCategory, issueOutcome } from '@prisma-next/framework-components/control';
27
27
  import { isStorageTypeInstance, type SqlStorage } from '@prisma-next/sql-contract/types';
28
28
  import { RelationalSchemaNodeKind, type SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
29
29
  import { blindCast } from '@prisma-next/utils/casts';
@@ -101,10 +101,10 @@ export function classifySqlDiffIssue(
101
101
  issue: SchemaDiffIssue,
102
102
  granularityOf: (nodeKind: string) => DiffSubjectGranularity,
103
103
  ): VerifierIssueCategory {
104
- if (issue.reason === 'not-found') {
104
+ if (issueOutcome(issue) === 'not-found') {
105
105
  return 'declaredMissing';
106
106
  }
107
- if (issue.reason === 'not-expected') {
107
+ if (issueOutcome(issue) === 'not-expected') {
108
108
  const granularity = classifyDiffSubjectGranularity(issue, granularityOf);
109
109
  if (granularity === 'entity' || granularity === 'namespace') {
110
110
  return 'extraTopLevelObject';
@@ -173,7 +173,7 @@ export function computeSqlDiffVerdict(input: SqlDiffVerdictInput): SqlDiffVerdic
173
173
  for (const issue of input.issues) {
174
174
  if (
175
175
  !input.strict &&
176
- issue.reason === 'not-expected' &&
176
+ issueOutcome(issue) === 'not-expected' &&
177
177
  isStrictOnlyExtra(issue, input.granularityOf)
178
178
  ) {
179
179
  continue;
@@ -300,7 +300,7 @@ export function verifySqlSchemaByDiff(
300
300
  : undefined;
301
301
  return {
302
302
  ok,
303
- ...(ok ? {} : { code: 'PN-SCHEMA-0001' }),
303
+ ...(ok ? {} : { code: 'CONTRACT.SCHEMA_VERIFICATION_FAILED' }),
304
304
  summary: ok
305
305
  ? 'Database schema satisfies contract'
306
306
  : `Database schema does not satisfy contract (${failCount} failure${failCount === 1 ? '' : 's'})`,
@@ -4,7 +4,7 @@ import type {
4
4
  VerifierIssueCategory,
5
5
  VerifierOutcome,
6
6
  } from '@prisma-next/framework-components/control';
7
- import { dispositionForCategory } from '@prisma-next/framework-components/control';
7
+ import { dispositionForCategory, issueOutcome } from '@prisma-next/framework-components/control';
8
8
 
9
9
  /**
10
10
  * Classifies a codec `verifyType` hook finding into the target-neutral
@@ -13,10 +13,10 @@ import { dispositionForCategory } from '@prisma-next/framework-components/contro
13
13
  * to its value set, so a paired mismatch always classifies as `valueDrift`.
14
14
  */
15
15
  export function classifyStorageTypeDiffIssue(issue: SchemaDiffIssue): VerifierIssueCategory {
16
- if (issue.reason === 'not-found') {
16
+ if (issueOutcome(issue) === 'not-found') {
17
17
  return 'declaredMissing';
18
18
  }
19
- if (issue.reason === 'not-expected') {
19
+ if (issueOutcome(issue) === 'not-expected') {
20
20
  return 'extraAuxiliary';
21
21
  }
22
22
  return 'valueDrift';
@@ -1,6 +1,9 @@
1
1
  import type { ColumnDefault, Contract, JsonValue } from '@prisma-next/contract/types';
2
2
  import type { CodecRef } from '@prisma-next/framework-components/codec';
3
- import type { MigrationPlannerConflict } from '@prisma-next/framework-components/control';
3
+ import type {
4
+ MigrationPlannerConflict,
5
+ SchemaNodeRef,
6
+ } from '@prisma-next/framework-components/control';
4
7
  import {
5
8
  type CheckConstraint,
6
9
  type ForeignKey,
@@ -12,8 +15,8 @@ import {
12
15
  type StorageTypeInstance,
13
16
  type UniqueConstraint,
14
17
  } from '@prisma-next/sql-contract/types';
15
- import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
16
18
  import {
19
+ RelationalSchemaNodeKind,
17
20
  type SqlAnnotations,
18
21
  type SqlCheckConstraintIRInput,
19
22
  type SqlColumnIRInput,
@@ -25,7 +28,6 @@ import {
25
28
  } from '@prisma-next/sql-schema-ir/types';
26
29
  import { blindCast } from '@prisma-next/utils/casts';
27
30
  import { ifDefined } from '@prisma-next/utils/defined';
28
- import { backingIndexColumnKeys } from '../foreign-key-index-backing';
29
31
 
30
32
  /**
31
33
  * Target-specific callback that expands a column's base `nativeType` and optional
@@ -54,6 +56,17 @@ export type NativeTypeExpander = (input: {
54
56
  */
55
57
  export type DefaultRenderer = (def: ColumnDefault, column: StorageColumn) => string;
56
58
 
59
+ /**
60
+ * Target-supplied hook (same IoC seam as `NativeTypeExpander`/`DefaultRenderer`)
61
+ * that normalizes a contract-declared `ColumnDefault` into the resolved shape
62
+ * the target's introspection parses from the live database — e.g. a
63
+ * `dbgenerated("'{}'::jsonb")` function call and the literal Postgres reports
64
+ * are the same value in different shapes, and `resolvedDefaultsEqual`
65
+ * compares `kind` before content. When omitted, the contract's raw default
66
+ * is the resolved default unchanged.
67
+ */
68
+ export type DefaultResolver = (def: ColumnDefault, resolvedNativeType: string) => ColumnDefault;
69
+
57
70
  /**
58
71
  * Target-supplied callback that resolves a contract namespace to the live
59
72
  * database schema its enums are stored under.
@@ -75,6 +88,7 @@ function convertColumn(
75
88
  storageTypes: ResolvedStorageTypes,
76
89
  expandNativeType: NativeTypeExpander | undefined,
77
90
  renderDefault: DefaultRenderer | undefined,
91
+ resolveDefault: DefaultResolver | undefined,
78
92
  ): SqlColumnIRInput {
79
93
  // Resolve `typeRef` so columns that delegate their `nativeType`/`codecId`/
80
94
  // `typeParams` to a named `storage.types` entry expand the same way as
@@ -105,6 +119,11 @@ function convertColumn(
105
119
  // agree on as the comparable "expanded" type.
106
120
  const nativeType = baseNativeType;
107
121
  const resolvedNativeType = column.many ? `${baseNativeType}[]` : baseNativeType;
122
+ const rawColumnDefault = column.default ?? undefined;
123
+ const resolvedColumnDefault =
124
+ rawColumnDefault !== undefined && resolveDefault
125
+ ? resolveDefault(rawColumnDefault, resolvedNativeType)
126
+ : rawColumnDefault;
108
127
  return {
109
128
  name,
110
129
  nativeType,
@@ -115,11 +134,14 @@ function convertColumn(
115
134
  column.default != null && renderDefault ? renderDefault(column.default, column) : undefined,
116
135
  ),
117
136
  // Contract-derived columns are resolved by construction: the computed
118
- // full native type doubles as the resolved value, and the contract's
119
- // structured default is the resolved default (the introspected side
120
- // stamps its normalizer's parse of the raw expression).
137
+ // full native type doubles as the resolved value. The contract's raw
138
+ // structured default becomes the resolved default after passing through
139
+ // the target's `resolveDefault` hook (when supplied), so a default the
140
+ // target's introspection side would normalize differently (e.g. a
141
+ // `dbgenerated(...)` function call that is actually a literal) compares
142
+ // equal instead of drifting on `kind` alone.
121
143
  resolvedNativeType,
122
- ...ifDefined('resolvedDefault', column.default ?? undefined),
144
+ ...ifDefined('resolvedDefault', resolvedColumnDefault),
123
145
  // The column's codec identity, carried the same way the query AST
124
146
  // carries `CodecRef` (TML-2456) — the migration planner's op-builders
125
147
  // resolve DDL rendering from this at plan time (Decision 5), instead of
@@ -246,14 +268,15 @@ function convertCheck(check: CheckConstraint, storage: SqlStorage): SqlCheckCons
246
268
  };
247
269
  }
248
270
 
249
- function convertUnique(unique: UniqueConstraint): SqlUniqueIRInput {
271
+ function convertUnique(unique: UniqueConstraint, tableName: string): SqlUniqueIRInput {
250
272
  return {
251
273
  columns: unique.columns,
252
274
  ...ifDefined('name', unique.name),
275
+ dependsOn: flatColumnDependsOn(tableName, unique.columns),
253
276
  };
254
277
  }
255
278
 
256
- function convertIndex(index: Index): SqlIndexIRInput {
279
+ function convertIndex(index: Index, tableName: string): SqlIndexIRInput {
257
280
  return {
258
281
  columns: index.columns,
259
282
  unique: false,
@@ -262,9 +285,42 @@ function convertIndex(index: Index): SqlIndexIRInput {
262
285
  // introspected side (the legacy walk read them from the contract).
263
286
  ...ifDefined('type', index.type),
264
287
  ...ifDefined('options', index.options),
288
+ dependsOn: flatColumnDependsOn(tableName, index.columns),
265
289
  };
266
290
  }
267
291
 
292
+ /**
293
+ * The referenced table's chain in the flat (single-schema) tree
294
+ * `contractToSchemaIR`/`contractNamespaceToSchemaIR` build: the root
295
+ * (`SqlSchemaIR`, fixed `'database'` id) followed by the table's own id.
296
+ * Postgres discards this when it re-derives the FK against its own
297
+ * multi-schema tree shape (`contractToPostgresDatabaseSchemaNode`); SQLite's
298
+ * flat tree uses it as-is.
299
+ */
300
+ function flatSchemaDependsOn(tableName: string): SchemaNodeRef {
301
+ return [
302
+ { nodeKind: RelationalSchemaNodeKind.schema, id: 'database' },
303
+ { nodeKind: RelationalSchemaNodeKind.table, id: tableName },
304
+ ];
305
+ }
306
+
307
+ /**
308
+ * The chains from a table-child object (foreign key, index, unique, primary
309
+ * key) to each of the own columns it is built on, in the flat tree. Dropping
310
+ * a covered column auto-drops the object, so the object's drop must precede
311
+ * the column's; the graph derives that direction from these edges.
312
+ */
313
+ function flatColumnDependsOn(
314
+ tableName: string,
315
+ columns: readonly string[],
316
+ ): readonly SchemaNodeRef[] {
317
+ return columns.map((column) => [
318
+ { nodeKind: RelationalSchemaNodeKind.schema, id: 'database' },
319
+ { nodeKind: RelationalSchemaNodeKind.table, id: tableName },
320
+ { nodeKind: RelationalSchemaNodeKind.column, id: `column:${column}` },
321
+ ]);
322
+ }
323
+
268
324
  /**
269
325
  * The FK's referenced-namespace identity comes from the target's namespace
270
326
  * node, not the raw namespace-id string. An unbound target namespace stamps
@@ -274,6 +330,10 @@ function convertIndex(index: Index): SqlIndexIRInput {
274
330
  * cross-space target whose namespace lives in another contract's storage)
275
331
  * stamps its coordinate verbatim; namespaced targets (Postgres) resolve the
276
332
  * real DDL schema downstream.
333
+ *
334
+ * `dependsOn` carries the referenced table (created before the FK, dropped
335
+ * after it) plus the FK's own columns (dropped after the FK, since dropping a
336
+ * column auto-drops the FK built on it).
277
337
  */
278
338
  function convertForeignKey(fk: ForeignKey, storage: SqlStorage): SqlForeignKeyIRInput {
279
339
  const targetNamespace = storage.namespaces[fk.target.namespaceId];
@@ -286,6 +346,10 @@ function convertForeignKey(fk: ForeignKey, storage: SqlStorage): SqlForeignKeyIR
286
346
  ...ifDefined('name', fk.name),
287
347
  ...ifDefined('onDelete', fk.onDelete),
288
348
  ...ifDefined('onUpdate', fk.onUpdate),
349
+ dependsOn: [
350
+ flatSchemaDependsOn(fk.target.tableName),
351
+ ...flatColumnDependsOn(fk.source.tableName, fk.source.columns),
352
+ ],
289
353
  };
290
354
  }
291
355
 
@@ -295,6 +359,7 @@ function convertTable(
295
359
  storageTypes: ResolvedStorageTypes,
296
360
  expandNativeType: NativeTypeExpander | undefined,
297
361
  renderDefault: DefaultRenderer | undefined,
362
+ resolveDefault: DefaultResolver | undefined,
298
363
  storage: SqlStorage,
299
364
  ): SqlTableIR {
300
365
  const columns: Record<string, SqlColumnIRInput> = {};
@@ -305,37 +370,36 @@ function convertTable(
305
370
  storageTypes,
306
371
  expandNativeType,
307
372
  renderDefault,
373
+ resolveDefault,
308
374
  );
309
375
  }
310
376
 
311
- const satisfiedIndexColumns = new Set(backingIndexColumnKeys(table));
312
- const fkBackingIndexes: SqlIndexIRInput[] = [];
313
- for (const fk of table.foreignKeys) {
314
- if (fk.index === false) continue;
315
- const key = fk.source.columns.join(',');
316
- if (satisfiedIndexColumns.has(key)) continue;
317
- fkBackingIndexes.push({
318
- columns: fk.source.columns,
319
- unique: false,
320
- name: defaultIndexName(name, fk.source.columns),
321
- });
322
- satisfiedIndexColumns.add(key);
323
- }
324
-
325
377
  const checks: SqlCheckConstraintIRInput[] | undefined =
326
378
  table.checks && table.checks.length > 0
327
379
  ? table.checks.map((c) => convertCheck(c, storage))
328
380
  : undefined;
329
381
 
382
+ const primaryKey =
383
+ table.primaryKey !== undefined
384
+ ? {
385
+ columns: table.primaryKey.columns,
386
+ ...ifDefined('name', table.primaryKey.name),
387
+ dependsOn: flatColumnDependsOn(name, table.primaryKey.columns),
388
+ }
389
+ : undefined;
390
+
330
391
  return new SqlTableIR({
331
392
  name,
332
393
  columns,
333
- ...ifDefined('primaryKey', table.primaryKey),
334
- foreignKeys: table.foreignKeys
335
- .filter((fk) => fk.constraint !== false)
336
- .map((fk) => convertForeignKey(fk, storage)),
337
- uniques: table.uniques.map(convertUnique),
338
- indexes: [...table.indexes.map(convertIndex), ...fkBackingIndexes],
394
+ ...ifDefined('primaryKey', primaryKey),
395
+ // #989 persists a `constraint: false` FK's absence and its backing index as
396
+ // discrete entities at contract construction, so every `foreignKeys[]` entry
397
+ // is now constraint-bearing (no filter) and each FK-backing index is already
398
+ // a `table.indexes[]` entry — it flows through `convertIndex` below, carrying
399
+ // the object→own-column `dependsOn` edge like any other index.
400
+ foreignKeys: table.foreignKeys.map((fk) => convertForeignKey(fk, storage)),
401
+ uniques: table.uniques.map((u) => convertUnique(u, name)),
402
+ indexes: table.indexes.map((i) => convertIndex(i, name)),
339
403
  ...ifDefined('checks', checks),
340
404
  });
341
405
  }
@@ -402,6 +466,7 @@ export interface ContractToSchemaIROptions {
402
466
  readonly annotationNamespace: string;
403
467
  readonly expandNativeType?: NativeTypeExpander;
404
468
  readonly renderDefault?: DefaultRenderer;
469
+ readonly resolveDefault?: DefaultResolver;
405
470
  /**
406
471
  * Target-supplied resolver mapping a namespace to the live database schema
407
472
  * its enums are stored under. When provided (Postgres), namespace-scoped
@@ -462,6 +527,7 @@ export function contractNamespaceToSchemaIR(
462
527
  storageTypes,
463
528
  options.expandNativeType,
464
529
  options.renderDefault,
530
+ options.resolveDefault,
465
531
  storage,
466
532
  );
467
533
  }
@@ -498,6 +564,7 @@ export function contractToSchemaIR(
498
564
  storageTypes,
499
565
  options.expandNativeType,
500
566
  options.renderDefault,
567
+ options.resolveDefault,
501
568
  storage,
502
569
  );
503
570
  }
@@ -419,16 +419,16 @@ export interface SqlMigrationRunnerExecuteOptions<TTargetDetails> {
419
419
  }
420
420
 
421
421
  export type SqlMigrationRunnerErrorCode =
422
- | 'DESTINATION_CONTRACT_MISMATCH'
423
- | 'LEGACY_MARKER_SHAPE'
424
- | 'MARKER_ORIGIN_MISMATCH'
425
- | 'MARKER_CAS_FAILURE'
426
- | 'POLICY_VIOLATION'
427
- | 'PRECHECK_FAILED'
428
- | 'POSTCHECK_FAILED'
429
- | 'SCHEMA_VERIFY_FAILED'
430
- | 'FOREIGN_KEY_VIOLATION'
431
- | 'EXECUTION_FAILED';
422
+ | 'MIGRATION.DESTINATION_CONTRACT_MISMATCH'
423
+ | 'MIGRATION.LEGACY_MARKER_SHAPE'
424
+ | 'MIGRATION.MARKER_ORIGIN_MISMATCH'
425
+ | 'MIGRATION.MARKER_CAS_FAILURE'
426
+ | 'MIGRATION.POLICY_VIOLATION'
427
+ | 'MIGRATION.PRECHECK_FAILED'
428
+ | 'MIGRATION.POSTCHECK_FAILED'
429
+ | 'MIGRATION.SCHEMA_VERIFY_FAILED'
430
+ | 'MIGRATION.FOREIGN_KEY_VIOLATION'
431
+ | 'MIGRATION.EXECUTION_FAILED';
432
432
 
433
433
  export interface SqlMigrationRunnerFailure extends MigrationRunnerFailure {
434
434
  readonly code: SqlMigrationRunnerErrorCode;
@@ -1,3 +1,5 @@
1
+ import pluralizeLib from 'pluralize';
2
+
1
3
  const PSL_RESERVED_WORDS = new Set(['model', 'enum', 'types', 'type', 'generator', 'datasource']);
2
4
 
3
5
  const IDENTIFIER_PART_PATTERN = /[A-Za-z0-9]+/g;
@@ -143,19 +145,7 @@ export function toEnumMemberName(value: string): string {
143
145
  }
144
146
 
145
147
  export function pluralize(word: string): string {
146
- if (
147
- word.endsWith('s') ||
148
- word.endsWith('x') ||
149
- word.endsWith('z') ||
150
- word.endsWith('ch') ||
151
- word.endsWith('sh')
152
- ) {
153
- return `${word}es`;
154
- }
155
- if (word.endsWith('y') && !/[aeiou]y$/i.test(word)) {
156
- return `${word.slice(0, -1)}ies`;
157
- }
158
- return `${word}s`;
148
+ return pluralizeLib.plural(word);
159
149
  }
160
150
 
161
151
  export function deriveRelationFieldName(
@@ -1,5 +1,8 @@
1
+ import {
2
+ backingIndexColumnKeys,
3
+ isBackedByColumnKeys,
4
+ } from '@prisma-next/sql-contract/foreign-key-materialization';
1
5
  import type { SqlForeignKeyIR, SqlTableIR } from '@prisma-next/sql-schema-ir/types';
2
- import { backingIndexColumnKeys, isBackedByColumnKeys } from '../foreign-key-index-backing';
3
6
  import { deriveBackRelationFieldName, deriveRelationFieldName, pluralize } from './name-transforms';
4
7
  import type { RelationField } from './printer-config';
5
8
 
@@ -73,3 +73,91 @@ export function temporalAuthoringPresets<
73
73
  },
74
74
  } as const satisfies Record<string, AuthoringFieldPresetDescriptor>;
75
75
  }
76
+
77
+ const TEMPORAL_PRECISION_ARG = {
78
+ name: 'precision',
79
+ kind: 'number',
80
+ optional: true,
81
+ integer: true,
82
+ minimum: 0,
83
+ } as const;
84
+
85
+ const TEMPORAL_ON_CREATE_ARG = {
86
+ name: 'onCreate',
87
+ kind: 'option',
88
+ values: ['now'],
89
+ optional: true,
90
+ } as const;
91
+
92
+ const TEMPORAL_ON_UPDATE_ARG = {
93
+ name: 'onUpdate',
94
+ kind: 'option',
95
+ values: ['now'],
96
+ optional: true,
97
+ } as const;
98
+
99
+ /**
100
+ * Selects the `timestampNow` generator descriptor for the preset's `now`
101
+ * token. The token is preset vocabulary; the generator id never appears in a
102
+ * user's spelling (ADR 169 — `timestampNow` is preset-only).
103
+ */
104
+ function temporalPhaseTemplate<const Index extends number>(index: Index) {
105
+ return {
106
+ kind: 'select',
107
+ index,
108
+ cases: { now: { kind: 'generator', id: TIMESTAMP_NOW_GENERATOR_ID } },
109
+ } as const;
110
+ }
111
+
112
+ /**
113
+ * Builds a `temporal.<codec>` field preset for a codec that takes a precision
114
+ * parameter (`pg/timestamp@1`, `pg/timestamptz@1`). Arguments change field
115
+ * properties only — never the codec, which the caller fixes here.
116
+ *
117
+ * All three arguments are optional: omitting `precision` omits `typeParams`
118
+ * entirely, and omitting a phase omits that phase (both omitted omits
119
+ * `executionDefaults`).
120
+ */
121
+ /* @__NO_SIDE_EFFECTS__ */
122
+ export function temporalCodecPresetWithPrecision<
123
+ const CodecId extends string,
124
+ const NativeType extends string,
125
+ >(input: { readonly codecId: CodecId; readonly nativeType: NativeType }) {
126
+ return {
127
+ kind: 'fieldPreset',
128
+ args: [TEMPORAL_PRECISION_ARG, TEMPORAL_ON_CREATE_ARG, TEMPORAL_ON_UPDATE_ARG],
129
+ output: {
130
+ codecId: input.codecId,
131
+ nativeType: input.nativeType,
132
+ typeParams: { precision: { kind: 'arg', index: 0 } },
133
+ executionDefaults: {
134
+ onCreate: temporalPhaseTemplate(1),
135
+ onUpdate: temporalPhaseTemplate(2),
136
+ },
137
+ },
138
+ } as const satisfies AuthoringFieldPresetDescriptor;
139
+ }
140
+
141
+ /**
142
+ * Builds a `temporal.<codec>` field preset for a codec with no type
143
+ * parameters (`sqlite/datetime@1`). As with the precision-bearing variant,
144
+ * both phase arguments are optional and omitting one omits that phase.
145
+ */
146
+ /* @__NO_SIDE_EFFECTS__ */
147
+ export function temporalCodecPreset<
148
+ const CodecId extends string,
149
+ const NativeType extends string,
150
+ >(input: { readonly codecId: CodecId; readonly nativeType: NativeType }) {
151
+ return {
152
+ kind: 'fieldPreset',
153
+ args: [TEMPORAL_ON_CREATE_ARG, TEMPORAL_ON_UPDATE_ARG],
154
+ output: {
155
+ codecId: input.codecId,
156
+ nativeType: input.nativeType,
157
+ executionDefaults: {
158
+ onCreate: temporalPhaseTemplate(0),
159
+ onUpdate: temporalPhaseTemplate(1),
160
+ },
161
+ },
162
+ } as const satisfies AuthoringFieldPresetDescriptor;
163
+ }
@@ -21,6 +21,7 @@ export type {
21
21
  export type {
22
22
  ContractToSchemaIROptions,
23
23
  DefaultRenderer,
24
+ DefaultResolver,
24
25
  EnumNamespaceSchemaResolver,
25
26
  NativeTypeExpander,
26
27
  } from '../core/migrations/contract-to-schema-ir';
@@ -87,6 +88,8 @@ export type {
87
88
  } from '../core/migrations/types';
88
89
  export {
89
90
  temporalAuthoringPresets,
91
+ temporalCodecPreset,
92
+ temporalCodecPresetWithPrecision,
90
93
  timestampNowControlDescriptor,
91
94
  } from '../core/timestamp-now-generator';
92
95
 
@@ -1 +0,0 @@
1
- {"version":3,"file":"control-adapter-BQgad8Zc.d.mts","names":[],"sources":["../src/core/diff/sql-schema-diff.ts","../src/core/control-adapter.ts"],"mappings":";;;;;;;;;;;KAeY,iBAAA,IACV,UAAA,UACA,UAAA,aACG,aAAa;;AAAA;AAOlB;;;KAAY,oBAAA,IAAwB,UAAkB;AAAA;AAKtD;;AALsD,iBAKtC,WAAA,CAAY,CAAA,qBAAsB,CAAoB;;;;AAftE;;;;;;UCQiB,OAAA;EACf,KAAA,CAAM,GAAA,EAAK,WAAA,GAAc,OAAA,EAAS,OAAA,EAAS,cAAA,YAA0B,gBAAA;AAAA;ADCvE;;;;AAAsD;AAKtD;AALA,UCQiB,qBAAA,SAA8B,OAAA;EAC7C,qBAAA,CACE,GAAA,EAAK,WAAA,GAAc,OAAA,EACnB,OAAA,GAAU,cAAA,YACT,OAAA,CAAQ,iBAAA;AAAA;ADPyD;;;;ACPtE;;ADOsE,UCgBrD,iBAAA,0CACP,sBAAA,QAA8B,OAAA,GACpC,qBAAA;EAxBS;;;;;;;;;;;;;;AAA0E;EAwCrF,UAAA,CACE,MAAA,EAAQ,wBAAA,CAAyB,OAAA,GACjC,KAAA,WACC,OAAA,CAAQ,oBAAA;EAlC0B;;;;;;;EA2CrC,cAAA,CACE,MAAA,EAAQ,wBAAA,CAAyB,OAAA,IAChC,OAAA,CAAQ,WAAA,SAAoB,oBAAA;EA7CqB;;;;EAmDpD,UAAA,CACE,MAAA,EAAQ,wBAAA,CAAyB,OAAA,GACjC,KAAA,YACC,OAAA,UAAiB,iBAAA;EApDC;;;;;;EA4DrB,YAAA,CACE,MAAA,EAAQ,wBAAA,CAAyB,OAAA,GACjC,KAAA,UACA,WAAA;IAAA,SACW,WAAA;IAAA,SACA,WAAA;IAAA,SACA,UAAA;EAAA,IAEV,OAAA;EAxDmC;;;;;;EAgEtC,UAAA,CACE,MAAA,EAAQ,wBAAA,CAAyB,OAAA,GACjC,KAAA,UACA,WAAA;IAAA,SACW,WAAA;IAAA,SACA,WAAA;IAAA,SACA,UAAA;EAAA,IAEV,OAAA;EAlCO;;;;;;;EA2CV,YAAA,CACE,MAAA,EAAQ,wBAAA,CAAyB,OAAA,GACjC,KAAA,UACA,YAAA,UACA,WAAA;IAAA,SACW,WAAA;IAAA,SACA,WAAA;IAAA,SACA,UAAA;EAAA,IAEV,OAAA;EASgC;;;;;;EADnC,gBAAA,CACE,MAAA,EAAQ,wBAAA,CAAyB,OAAA,GACjC,KAAA,UACA,KAAA;IAAA,SACW,MAAA;IAAA,SACA,IAAA;IAAA,SACA,EAAA;IAAA,SACA,aAAA;IAAA,SACA,aAAA;IAAA,SACA,UAAA;IAAA,SACA,uBAAA;EAAA,IAEV,OAAA;EA/G8B;;;;;;;;;;;;;;;;;EAkIjC,UAAA,CACE,MAAA,EAAQ,wBAAA,CAAyB,OAAA,GACjC,QAAA,YACA,MAAA,YACC,OAAA,CAAQ,eAAA;EAhGX;;;;;EAAA,SAuGS,gBAAA,GAAmB,iBAAA;EApGR;;;;;EAAA,SA2GX,mBAAA,GAAsB,oBAAA;EA/FlB;;;;EAqGb,4BAAA,aAAyC,OAAA;EAzFzC;;;;EA+FA,0BAAA,aAAuC,OAAA;AAAA;;;;;;;UASxB,2BAAA;EAtFb;;;;;;EA6FF,MAAA,CAAO,KAAA,EAAO,YAAA,QAAoB,OAAA,IAAW,iBAAA,CAAkB,OAAA;AAAA"}
@@ -1,32 +0,0 @@
1
- //#region src/core/foreign-key-index-backing.ts
2
- /**
3
- * The column-list keys (`"colA,colB"`, order preserved) a table's own
4
- * indexes, unique constraints, and primary key already back. A foreign key
5
- * whose source columns join to one of these keys needs no separately
6
- * derived backing index.
7
- *
8
- * Shared by {@link isBackedByColumnKeys}'s two callers, which must agree on
9
- * what counts as "backed": `contract-to-schema-ir.ts`'s `convertTable`
10
- * (deriving the FK-backing-index expectation `db verify` checks against a
11
- * live database) and the postgres PSL inferrer (deciding whether an
12
- * introspected relation needs an explicit `index: false`).
13
- */
14
- function backingIndexColumnKeys(table) {
15
- return [
16
- ...table.indexes.map((index) => index.columns.join(",")),
17
- ...table.uniques.map((unique) => unique.columns.join(",")),
18
- ...table.primaryKey ? [table.primaryKey.columns.join(",")] : []
19
- ];
20
- }
21
- /**
22
- * Whether `columns`, in order, matches one of `backingKeys` (see
23
- * {@link backingIndexColumnKeys}). Order-sensitive: `(a, b)` does not
24
- * satisfy a backing index declared as `(b, a)`.
25
- */
26
- function isBackedByColumnKeys(columns, backingKeys) {
27
- return backingKeys.includes(columns.join(","));
28
- }
29
- //#endregion
30
- export { isBackedByColumnKeys as n, backingIndexColumnKeys as t };
31
-
32
- //# sourceMappingURL=foreign-key-index-backing-BP71iI-Q.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"foreign-key-index-backing-BP71iI-Q.mjs","names":[],"sources":["../src/core/foreign-key-index-backing.ts"],"sourcesContent":["export type BackingIndexCandidates = {\n readonly indexes: readonly { readonly columns: readonly string[] }[];\n readonly uniques: readonly { readonly columns: readonly string[] }[];\n readonly primaryKey?: { readonly columns: readonly string[] } | undefined;\n};\n\n/**\n * The column-list keys (`\"colA,colB\"`, order preserved) a table's own\n * indexes, unique constraints, and primary key already back. A foreign key\n * whose source columns join to one of these keys needs no separately\n * derived backing index.\n *\n * Shared by {@link isBackedByColumnKeys}'s two callers, which must agree on\n * what counts as \"backed\": `contract-to-schema-ir.ts`'s `convertTable`\n * (deriving the FK-backing-index expectation `db verify` checks against a\n * live database) and the postgres PSL inferrer (deciding whether an\n * introspected relation needs an explicit `index: false`).\n */\nexport function backingIndexColumnKeys(table: BackingIndexCandidates): readonly string[] {\n return [\n ...table.indexes.map((index) => index.columns.join(',')),\n ...table.uniques.map((unique) => unique.columns.join(',')),\n ...(table.primaryKey ? [table.primaryKey.columns.join(',')] : []),\n ];\n}\n\n/**\n * Whether `columns`, in order, matches one of `backingKeys` (see\n * {@link backingIndexColumnKeys}). Order-sensitive: `(a, b)` does not\n * satisfy a backing index declared as `(b, a)`.\n */\nexport function isBackedByColumnKeys(\n columns: readonly string[],\n backingKeys: readonly string[],\n): boolean {\n return backingKeys.includes(columns.join(','));\n}\n"],"mappings":";;;;;;;;;;;;;AAkBA,SAAgB,uBAAuB,OAAkD;CACvF,OAAO;EACL,GAAG,MAAM,QAAQ,KAAK,UAAU,MAAM,QAAQ,KAAK,GAAG,CAAC;EACvD,GAAG,MAAM,QAAQ,KAAK,WAAW,OAAO,QAAQ,KAAK,GAAG,CAAC;EACzD,GAAI,MAAM,aAAa,CAAC,MAAM,WAAW,QAAQ,KAAK,GAAG,CAAC,IAAI,CAAC;CACjE;AACF;;;;;;AAOA,SAAgB,qBACd,SACA,aACS;CACT,OAAO,YAAY,SAAS,QAAQ,KAAK,GAAG,CAAC;AAC/C"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"schema-differ-DnoopSXm.d.mts","names":[],"sources":["../src/core/migrations/schema-differ.ts"],"mappings":";;;;;;;;;AAcA;;;;;;UAAiB,mBAAA;EAiBU;EAAA,SAfhB,MAAA,WAAiB,eAAA;EAeY;;;;;;;;EAAA,SAN7B,oBAAA,GAAuB,KAAA,EAAO,eAAA,KAAoB,aAAA;EAMT;;;AAAuB;AAG3E;EAHoD,SAAzC,cAAA,EAAgB,aAAA;IAAA,SAAyB,MAAA,EAAQ,eAAA;EAAA;AAAA;AAAA,UAG3C,kBAAA;EAAA,SACN,QAAA,EAAU,QAAA,CAAS,UAAA;EAAA,SACnB,MAAA,EAAQ,eAAA;EAAA,SACR,mBAAA,EAAqB,aAAA,CAAc,8BAAA;AAAA;AAAA,KAGlC,eAAA,IAAmB,KAAA,EAAO,kBAAA,KAAuB,mBAAmB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"schema-verify-W3r631Jh.mjs","names":["d"],"sources":["../src/core/assembly.ts","../src/core/diff/verifier-disposition.ts","../src/core/diff/schema-verify.ts"],"sourcesContent":["import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';\nimport { assertUniqueCodecOwner } from '@prisma-next/framework-components/control';\nimport type { CodecControlHooks } from './migrations/types';\n\ntype CodecControlHooksMap = Record<string, CodecControlHooks>;\n\nfunction hasCodecControlHooks(descriptor: unknown): descriptor is {\n readonly id: string;\n readonly types: {\n readonly codecTypes: {\n readonly controlPlaneHooks: CodecControlHooksMap;\n };\n };\n} {\n if (typeof descriptor !== 'object' || descriptor === null) {\n return false;\n }\n const d = descriptor as { types?: { codecTypes?: { controlPlaneHooks?: unknown } } };\n const hooks = d.types?.codecTypes?.controlPlaneHooks;\n return hooks !== null && hooks !== undefined && typeof hooks === 'object';\n}\n\nexport function extractCodecControlHooks(\n descriptors: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>,\n): Map<string, CodecControlHooks> {\n const hooks = new Map<string, CodecControlHooks>();\n const owners = new Map<string, string>();\n\n for (const descriptor of descriptors) {\n if (typeof descriptor !== 'object' || descriptor === null) {\n continue;\n }\n if (!hasCodecControlHooks(descriptor)) {\n continue;\n }\n const controlPlaneHooks = descriptor.types.codecTypes.controlPlaneHooks;\n for (const [codecId, hook] of Object.entries(controlPlaneHooks)) {\n assertUniqueCodecOwner({\n codecId,\n owners,\n descriptorId: descriptor.id,\n entityLabel: 'control hooks',\n entityOwnershipLabel: 'owner',\n });\n hooks.set(codecId, hook);\n owners.set(codecId, descriptor.id);\n }\n }\n\n return hooks;\n}\n","import type { ControlPolicy } from '@prisma-next/contract/types';\nimport type {\n SchemaDiffIssue,\n VerifierIssueCategory,\n VerifierOutcome,\n} from '@prisma-next/framework-components/control';\nimport { dispositionForCategory } from '@prisma-next/framework-components/control';\n\n/**\n * Classifies a codec `verifyType` hook finding into the target-neutral\n * categories the framework grades. A storage type is a named type instance\n * (e.g. a native enum); the only shape divergence it can carry is a change\n * to its value set, so a paired mismatch always classifies as `valueDrift`.\n */\nexport function classifyStorageTypeDiffIssue(issue: SchemaDiffIssue): VerifierIssueCategory {\n if (issue.reason === 'not-found') {\n return 'declaredMissing';\n }\n if (issue.reason === 'not-expected') {\n return 'extraAuxiliary';\n }\n return 'valueDrift';\n}\n\nexport function verifierDisposition(\n controlPolicy: ControlPolicy,\n issue: SchemaDiffIssue,\n): VerifierOutcome {\n return dispositionForCategory(controlPolicy, classifyStorageTypeDiffIssue(issue));\n}\n","/**\n * The differ-based SQL schema verify: post-diff filters and verdict.\n *\n * The generic node differ (`diffSchemas`) reports every node-level\n * difference between the derived expected tree and the introspected actual\n * tree. This module is the consumer side the spec assigns to the SQL\n * family: strict-mode extras gating and control-policy disposition are\n * reason/kind-keyed filters applied AFTER the diff — never inside it — and\n * the verify verdict derives from the filtered issue list.\n *\n * `verifySqlSchemaByDiff` wraps the verdict in the issue-based result\n * envelope — this is THE SQL schema verify (the legacy relational walk and\n * its verification tree are retired).\n */\n\nimport type { Contract, ControlPolicy } from '@prisma-next/contract/types';\nimport { effectiveControlPolicy } from '@prisma-next/contract/types';\nimport type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';\nimport type {\n DiffSubjectGranularity,\n SchemaDiffIssue,\n VerifierIssueCategory,\n VerifierOutcome,\n VerifyDatabaseSchemaResult,\n} from '@prisma-next/framework-components/control';\nimport { dispositionForCategory } from '@prisma-next/framework-components/control';\nimport { isStorageTypeInstance, type SqlStorage } from '@prisma-next/sql-contract/types';\nimport { RelationalSchemaNodeKind, type SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { extractCodecControlHooks } from '../assembly';\nimport type { SqlSchemaDiffFn } from '../migrations/schema-differ';\nimport type { CodecControlHooks } from '../migrations/types';\nimport { verifierDisposition } from './verifier-disposition';\n\n// ============================================================================\n// Subject-granularity classification — nodeKind → framework-neutral granularity\n// ============================================================================\n\nfunction issueNode(issue: SchemaDiffIssue): SqlSchemaIRNode | undefined {\n const node = issue.expected ?? issue.actual;\n if (node === undefined) return undefined;\n return blindCast<\n SqlSchemaIRNode,\n 'every node in a SQL schema diff tree is a SqlSchemaIRNode; nodeKind is its identity'\n >(node);\n}\n\n/**\n * Resolves an issue's framework-neutral {@link DiffSubjectGranularity} on\n * demand, from the issue's node's `nodeKind` via the target-provided\n * `granularityOf` map. The node carries only its `nodeKind` identity, never a\n * classification, and nothing is stamped onto the issue — every consumer\n * (the family verdict below, the framework aggregate's unclaimed-elements\n * sweep via {@link import('@prisma-next/framework-components/control').SchemaSubjectClassifierCapable})\n * calls this the same way, resolved by the family/target that owns the node\n * vocabulary. `undefined` for an issue with no node.\n */\nexport function classifyDiffSubjectGranularity(\n issue: SchemaDiffIssue,\n granularityOf: (nodeKind: string) => DiffSubjectGranularity,\n): DiffSubjectGranularity | undefined {\n const node = issueNode(issue);\n return node === undefined ? undefined : granularityOf(node.nodeKind);\n}\n\n/**\n * Resolves an issue's storage `entityKind` on demand, from the issue's\n * node's `nodeKind` via the target-provided `entityKindOf` map — the sibling\n * of {@link classifyDiffSubjectGranularity}, called the same way by the same\n * consumers (via\n * {@link import('@prisma-next/framework-components/control').SchemaSubjectClassifierCapable}).\n * `undefined` for an issue with no node, or for a node kind with no storage\n * entity of its own.\n */\nexport function classifyDiffEntityKind(\n issue: SchemaDiffIssue,\n entityKindOf: (nodeKind: string) => string | undefined,\n): string | undefined {\n const node = issueNode(issue);\n return node === undefined ? undefined : entityKindOf(node.nodeKind);\n}\n\n// ============================================================================\n// Issue classification — subject granularity + reason → target-neutral category\n// ============================================================================\n\n/**\n * Re-keys the legacy `classifySqlVerifierIssueKind` category mapping on the\n * issue's {@link DiffSubjectGranularity} (resolved via `granularityOf`) + the\n * issue reason. The vocabulary maps one-to-one: an undeclared live entity or\n * namespace is `extraTopLevelObject`, an undeclared live field\n * `extraNestedElement`, undeclared auxiliaries (constraints, indexes,\n * defaults) and structural leaves (policies) `extraAuxiliary`; a value-set\n * drift on a check node is `valueDrift`; every other paired divergence is\n * `declaredIncompatible`; anything the database lacks is `declaredMissing`.\n * `granularityOf` is the target's classifier, so target and extension node\n * kinds classify without the family importing them.\n */\nexport function classifySqlDiffIssue(\n issue: SchemaDiffIssue,\n granularityOf: (nodeKind: string) => DiffSubjectGranularity,\n): VerifierIssueCategory {\n if (issue.reason === 'not-found') {\n return 'declaredMissing';\n }\n if (issue.reason === 'not-expected') {\n const granularity = classifyDiffSubjectGranularity(issue, granularityOf);\n if (granularity === 'entity' || granularity === 'namespace') {\n return 'extraTopLevelObject';\n }\n if (granularity === 'field') {\n return 'extraNestedElement';\n }\n return 'extraAuxiliary';\n }\n if (issueNode(issue)?.nodeKind === RelationalSchemaNodeKind.check) {\n return 'valueDrift';\n }\n return 'declaredIncompatible';\n}\n\n/**\n * Whether a `not-expected` issue is a strict-mode-only finding. The legacy\n * walk detected every relational extra (namespaces, entities, fields, and\n * their auxiliaries) only under `--strict`; the structural diff (roots, RLS\n * policies, roles) was never strict-gated — its extras fail in both modes.\n * Keyed on the issue's granularity, resolved via `granularityOf`.\n */\nfunction isStrictOnlyExtra(\n issue: SchemaDiffIssue,\n granularityOf: (nodeKind: string) => DiffSubjectGranularity,\n): boolean {\n const granularity = classifyDiffSubjectGranularity(issue, granularityOf);\n return (\n granularity === 'namespace' ||\n granularity === 'entity' ||\n granularity === 'field' ||\n granularity === 'auxiliary'\n );\n}\n\n// ============================================================================\n// The post-diff filter + verdict\n// ============================================================================\n\nexport interface SqlDiffVerdictInput {\n /** The full, ownership-scoped diff issue list from the target's differ. */\n readonly issues: readonly SchemaDiffIssue[];\n /** Resolves a diff issue's subject table's declared control policy directly from the contract. */\n readonly resolveControlPolicy: (issue: SchemaDiffIssue) => ControlPolicy | undefined;\n readonly strict: boolean;\n readonly defaultControlPolicy: ControlPolicy | undefined;\n /** The target's classifier: a diff issue node's `nodeKind` → its subject granularity. */\n readonly granularityOf: (nodeKind: string) => DiffSubjectGranularity;\n}\n\nexport interface SqlDiffVerdict {\n readonly failures: readonly SchemaDiffIssue[];\n readonly warnings: readonly SchemaDiffIssue[];\n}\n\n/**\n * Applies the two consumer filters to a diff issue list: strict gating\n * (relational `not-expected` findings drop in lenient mode) and\n * control-policy disposition (each surviving issue grades against its\n * subject table's effective policy; suppressed issues drop, `observed`\n * subjects warn). The verify verdict is `failures.length === 0`.\n */\nexport function computeSqlDiffVerdict(input: SqlDiffVerdictInput): SqlDiffVerdict {\n const failures: SchemaDiffIssue[] = [];\n const warnings: SchemaDiffIssue[] = [];\n for (const issue of input.issues) {\n if (\n !input.strict &&\n issue.reason === 'not-expected' &&\n isStrictOnlyExtra(issue, input.granularityOf)\n ) {\n continue;\n }\n const tablePolicy = input.resolveControlPolicy(issue);\n const policy = effectiveControlPolicy(tablePolicy, input.defaultControlPolicy);\n const disposition: VerifierOutcome = dispositionForCategory(\n policy,\n classifySqlDiffIssue(issue, input.granularityOf),\n );\n if (disposition === 'suppress') continue;\n if (disposition === 'warn') {\n warnings.push(issue);\n continue;\n }\n failures.push(issue);\n }\n return { failures, warnings };\n}\n\n// ============================================================================\n// Storage-types check — the codec verifyType hook path\n// ============================================================================\n\nexport interface StorageTypeVerdictInput {\n readonly contract: Contract<SqlStorage>;\n /**\n * Expected/actual namespace-node pairs the target's differ input produced:\n * for a namespaced tree, one entry per expected namespace with a\n * non-empty table set, paired by DDL schema name (absent actual side for\n * a schema the database lacks); a flat tree is the sole pair.\n */\n readonly namespacePairs: ReadonlyArray<{\n readonly actual: SqlSchemaIRNode | undefined;\n }>;\n readonly codecHooks: ReadonlyMap<string, CodecControlHooks>;\n}\n\n/**\n * Runs the codec `verifyType` hooks the way the legacy walk did: once per\n * contract namespace with tables, against that namespace's paired actual\n * node (the hook reads namespace-scoped state such as `enums` off it).\n * Issue dispositions grade against the contract default policy, matching\n * the legacy `pushTypeNode` semantics.\n */\nexport interface StorageTypeVerdict {\n readonly failures: readonly SchemaDiffIssue[];\n readonly warnings: readonly SchemaDiffIssue[];\n}\n\nexport function computeStorageTypeVerdict(input: StorageTypeVerdictInput): StorageTypeVerdict {\n const failures: SchemaDiffIssue[] = [];\n const warnings: SchemaDiffIssue[] = [];\n const policy = effectiveControlPolicy(undefined, input.contract.defaultControlPolicy);\n for (const pair of input.namespacePairs) {\n if (pair.actual === undefined) continue;\n for (const [typeName, typeInstance] of Object.entries(input.contract.storage.types ?? {})) {\n if (!isStorageTypeInstance(typeInstance)) continue;\n const hook = input.codecHooks.get(typeInstance.codecId);\n if (!hook?.verifyType) continue;\n const typeIssues = hook.verifyType({ typeName, typeInstance, schema: pair.actual });\n for (const issue of typeIssues) {\n const disposition = verifierDisposition(policy, issue);\n if (disposition === 'suppress') continue;\n if (disposition === 'warn') {\n warnings.push(issue);\n continue;\n }\n failures.push(issue);\n }\n }\n }\n return { failures, warnings };\n}\n\n// ============================================================================\n// The issue-based verify envelope\n// ============================================================================\n\nexport interface VerifySqlSchemaByDiffInput {\n readonly contract: Contract<SqlStorage>;\n readonly schema: SqlSchemaIRNode;\n readonly strict: boolean;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;\n /** The target's full-tree node diff (`diffSchema` descriptor hook). */\n readonly diffSchema: SqlSchemaDiffFn;\n /** The target's classifier: a diff issue node's `nodeKind` → its subject granularity. */\n readonly granularityOf: (nodeKind: string) => DiffSubjectGranularity;\n}\n\n/**\n * THE SQL schema verify: runs the target's full-tree node diff, grades it\n * through the family's post-diff filters (strict gating + control-policy\n * disposition) plus the codec `verifyType` hook findings, and wraps the\n * verdict in the issue-based result envelope. `ok` holds exactly when both\n * issue lists are empty — the lists carry the verdict's failures.\n */\nexport function verifySqlSchemaByDiff(\n input: VerifySqlSchemaByDiffInput,\n): VerifyDatabaseSchemaResult {\n const startTime = Date.now();\n const verdictDiff = input.diffSchema({\n contract: input.contract,\n schema: input.schema,\n frameworkComponents: input.frameworkComponents,\n });\n const diffVerdict = computeSqlDiffVerdict({\n issues: verdictDiff.issues,\n resolveControlPolicy: verdictDiff.resolveControlPolicy,\n strict: input.strict,\n defaultControlPolicy: input.contract.defaultControlPolicy,\n granularityOf: input.granularityOf,\n });\n const storageTypeVerdict = computeStorageTypeVerdict({\n contract: input.contract,\n namespacePairs: verdictDiff.namespacePairs,\n codecHooks: extractCodecControlHooks(input.frameworkComponents),\n });\n const failCount = diffVerdict.failures.length + storageTypeVerdict.failures.length;\n const ok = failCount === 0;\n const profileHash =\n 'profileHash' in input.contract && typeof input.contract.profileHash === 'string'\n ? input.contract.profileHash\n : undefined;\n return {\n ok,\n ...(ok ? {} : { code: 'PN-SCHEMA-0001' }),\n summary: ok\n ? 'Database schema satisfies contract'\n : `Database schema does not satisfy contract (${failCount} failure${failCount === 1 ? '' : 's'})`,\n contract: {\n storageHash: input.contract.storage.storageHash,\n ...ifDefined('profileHash', profileHash),\n },\n target: {\n expected: input.contract.target,\n actual: input.contract.target,\n },\n schema: {\n issues: [...diffVerdict.failures, ...storageTypeVerdict.failures],\n warnings: {\n issues: [...diffVerdict.warnings, ...storageTypeVerdict.warnings],\n },\n },\n meta: { strict: input.strict },\n timings: { total: Date.now() - startTime },\n };\n}\n"],"mappings":"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"}