@prisma-next/family-sql 0.15.0-dev.2 → 0.15.0-dev.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/control.d.mts +124 -1
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +70 -18
- package/dist/control.mjs.map +1 -1
- package/dist/diff.mjs +1 -1
- package/dist/psl-infer.d.mts.map +1 -1
- package/dist/psl-infer.mjs +2 -3
- package/dist/psl-infer.mjs.map +1 -1
- package/dist/runtime.mjs +1 -1
- package/dist/{schema-verify-W3r631Jh.mjs → schema-verify-3sOPsdn_.mjs} +7 -7
- package/dist/schema-verify-3sOPsdn_.mjs.map +1 -0
- package/dist/{timestamp-now-generator-CloimujU.mjs → timestamp-now-generator-DRXygu32.mjs} +88 -2
- package/dist/timestamp-now-generator-DRXygu32.mjs.map +1 -0
- package/package.json +24 -22
- package/src/core/diff/schema-verify.ts +4 -4
- package/src/core/diff/verifier-disposition.ts +3 -3
- package/src/core/migrations/contract-to-schema-ir.ts +95 -10
- package/src/core/psl-contract-infer/name-transforms.ts +3 -13
- package/src/core/timestamp-now-generator.ts +88 -0
- package/src/exports/control.ts +3 -0
- package/dist/schema-verify-W3r631Jh.mjs.map +0 -1
- package/dist/timestamp-now-generator-CloimujU.mjs.map +0 -1
|
@@ -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 {
|
|
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,
|
|
@@ -13,6 +16,7 @@ import {
|
|
|
13
16
|
type UniqueConstraint,
|
|
14
17
|
} from '@prisma-next/sql-contract/types';
|
|
15
18
|
import {
|
|
19
|
+
RelationalSchemaNodeKind,
|
|
16
20
|
type SqlAnnotations,
|
|
17
21
|
type SqlCheckConstraintIRInput,
|
|
18
22
|
type SqlColumnIRInput,
|
|
@@ -52,6 +56,17 @@ export type NativeTypeExpander = (input: {
|
|
|
52
56
|
*/
|
|
53
57
|
export type DefaultRenderer = (def: ColumnDefault, column: StorageColumn) => string;
|
|
54
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
|
+
|
|
55
70
|
/**
|
|
56
71
|
* Target-supplied callback that resolves a contract namespace to the live
|
|
57
72
|
* database schema its enums are stored under.
|
|
@@ -73,6 +88,7 @@ function convertColumn(
|
|
|
73
88
|
storageTypes: ResolvedStorageTypes,
|
|
74
89
|
expandNativeType: NativeTypeExpander | undefined,
|
|
75
90
|
renderDefault: DefaultRenderer | undefined,
|
|
91
|
+
resolveDefault: DefaultResolver | undefined,
|
|
76
92
|
): SqlColumnIRInput {
|
|
77
93
|
// Resolve `typeRef` so columns that delegate their `nativeType`/`codecId`/
|
|
78
94
|
// `typeParams` to a named `storage.types` entry expand the same way as
|
|
@@ -103,6 +119,11 @@ function convertColumn(
|
|
|
103
119
|
// agree on as the comparable "expanded" type.
|
|
104
120
|
const nativeType = baseNativeType;
|
|
105
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;
|
|
106
127
|
return {
|
|
107
128
|
name,
|
|
108
129
|
nativeType,
|
|
@@ -113,11 +134,14 @@ function convertColumn(
|
|
|
113
134
|
column.default != null && renderDefault ? renderDefault(column.default, column) : undefined,
|
|
114
135
|
),
|
|
115
136
|
// Contract-derived columns are resolved by construction: the computed
|
|
116
|
-
// full native type doubles as the resolved value
|
|
117
|
-
// structured default
|
|
118
|
-
//
|
|
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.
|
|
119
143
|
resolvedNativeType,
|
|
120
|
-
...ifDefined('resolvedDefault',
|
|
144
|
+
...ifDefined('resolvedDefault', resolvedColumnDefault),
|
|
121
145
|
// The column's codec identity, carried the same way the query AST
|
|
122
146
|
// carries `CodecRef` (TML-2456) — the migration planner's op-builders
|
|
123
147
|
// resolve DDL rendering from this at plan time (Decision 5), instead of
|
|
@@ -244,14 +268,15 @@ function convertCheck(check: CheckConstraint, storage: SqlStorage): SqlCheckCons
|
|
|
244
268
|
};
|
|
245
269
|
}
|
|
246
270
|
|
|
247
|
-
function convertUnique(unique: UniqueConstraint): SqlUniqueIRInput {
|
|
271
|
+
function convertUnique(unique: UniqueConstraint, tableName: string): SqlUniqueIRInput {
|
|
248
272
|
return {
|
|
249
273
|
columns: unique.columns,
|
|
250
274
|
...ifDefined('name', unique.name),
|
|
275
|
+
dependsOn: flatColumnDependsOn(tableName, unique.columns),
|
|
251
276
|
};
|
|
252
277
|
}
|
|
253
278
|
|
|
254
|
-
function convertIndex(index: Index): SqlIndexIRInput {
|
|
279
|
+
function convertIndex(index: Index, tableName: string): SqlIndexIRInput {
|
|
255
280
|
return {
|
|
256
281
|
columns: index.columns,
|
|
257
282
|
unique: false,
|
|
@@ -260,9 +285,42 @@ function convertIndex(index: Index): SqlIndexIRInput {
|
|
|
260
285
|
// introspected side (the legacy walk read them from the contract).
|
|
261
286
|
...ifDefined('type', index.type),
|
|
262
287
|
...ifDefined('options', index.options),
|
|
288
|
+
dependsOn: flatColumnDependsOn(tableName, index.columns),
|
|
263
289
|
};
|
|
264
290
|
}
|
|
265
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
|
+
|
|
266
324
|
/**
|
|
267
325
|
* The FK's referenced-namespace identity comes from the target's namespace
|
|
268
326
|
* node, not the raw namespace-id string. An unbound target namespace stamps
|
|
@@ -272,6 +330,10 @@ function convertIndex(index: Index): SqlIndexIRInput {
|
|
|
272
330
|
* cross-space target whose namespace lives in another contract's storage)
|
|
273
331
|
* stamps its coordinate verbatim; namespaced targets (Postgres) resolve the
|
|
274
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).
|
|
275
337
|
*/
|
|
276
338
|
function convertForeignKey(fk: ForeignKey, storage: SqlStorage): SqlForeignKeyIRInput {
|
|
277
339
|
const targetNamespace = storage.namespaces[fk.target.namespaceId];
|
|
@@ -284,6 +346,10 @@ function convertForeignKey(fk: ForeignKey, storage: SqlStorage): SqlForeignKeyIR
|
|
|
284
346
|
...ifDefined('name', fk.name),
|
|
285
347
|
...ifDefined('onDelete', fk.onDelete),
|
|
286
348
|
...ifDefined('onUpdate', fk.onUpdate),
|
|
349
|
+
dependsOn: [
|
|
350
|
+
flatSchemaDependsOn(fk.target.tableName),
|
|
351
|
+
...flatColumnDependsOn(fk.source.tableName, fk.source.columns),
|
|
352
|
+
],
|
|
287
353
|
};
|
|
288
354
|
}
|
|
289
355
|
|
|
@@ -293,6 +359,7 @@ function convertTable(
|
|
|
293
359
|
storageTypes: ResolvedStorageTypes,
|
|
294
360
|
expandNativeType: NativeTypeExpander | undefined,
|
|
295
361
|
renderDefault: DefaultRenderer | undefined,
|
|
362
|
+
resolveDefault: DefaultResolver | undefined,
|
|
296
363
|
storage: SqlStorage,
|
|
297
364
|
): SqlTableIR {
|
|
298
365
|
const columns: Record<string, SqlColumnIRInput> = {};
|
|
@@ -303,6 +370,7 @@ function convertTable(
|
|
|
303
370
|
storageTypes,
|
|
304
371
|
expandNativeType,
|
|
305
372
|
renderDefault,
|
|
373
|
+
resolveDefault,
|
|
306
374
|
);
|
|
307
375
|
}
|
|
308
376
|
|
|
@@ -311,13 +379,27 @@ function convertTable(
|
|
|
311
379
|
? table.checks.map((c) => convertCheck(c, storage))
|
|
312
380
|
: undefined;
|
|
313
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
|
+
|
|
314
391
|
return new SqlTableIR({
|
|
315
392
|
name,
|
|
316
393
|
columns,
|
|
317
|
-
...ifDefined('primaryKey',
|
|
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.
|
|
318
400
|
foreignKeys: table.foreignKeys.map((fk) => convertForeignKey(fk, storage)),
|
|
319
|
-
uniques: table.uniques.map(convertUnique),
|
|
320
|
-
indexes: table.indexes.map(convertIndex),
|
|
401
|
+
uniques: table.uniques.map((u) => convertUnique(u, name)),
|
|
402
|
+
indexes: table.indexes.map((i) => convertIndex(i, name)),
|
|
321
403
|
...ifDefined('checks', checks),
|
|
322
404
|
});
|
|
323
405
|
}
|
|
@@ -384,6 +466,7 @@ export interface ContractToSchemaIROptions {
|
|
|
384
466
|
readonly annotationNamespace: string;
|
|
385
467
|
readonly expandNativeType?: NativeTypeExpander;
|
|
386
468
|
readonly renderDefault?: DefaultRenderer;
|
|
469
|
+
readonly resolveDefault?: DefaultResolver;
|
|
387
470
|
/**
|
|
388
471
|
* Target-supplied resolver mapping a namespace to the live database schema
|
|
389
472
|
* its enums are stored under. When provided (Postgres), namespace-scoped
|
|
@@ -444,6 +527,7 @@ export function contractNamespaceToSchemaIR(
|
|
|
444
527
|
storageTypes,
|
|
445
528
|
options.expandNativeType,
|
|
446
529
|
options.renderDefault,
|
|
530
|
+
options.resolveDefault,
|
|
447
531
|
storage,
|
|
448
532
|
);
|
|
449
533
|
}
|
|
@@ -480,6 +564,7 @@ export function contractToSchemaIR(
|
|
|
480
564
|
storageTypes,
|
|
481
565
|
options.expandNativeType,
|
|
482
566
|
options.renderDefault,
|
|
567
|
+
options.resolveDefault,
|
|
483
568
|
storage,
|
|
484
569
|
);
|
|
485
570
|
}
|
|
@@ -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
|
-
|
|
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(
|
|
@@ -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
|
+
}
|
package/src/exports/control.ts
CHANGED
|
@@ -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":"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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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"timestamp-now-generator-CloimujU.mjs","names":[],"sources":["../src/core/timestamp-now-generator.ts"],"sourcesContent":["import type { AuthoringFieldPresetDescriptor } from '@prisma-next/framework-components/authoring';\nimport type { MutationDefaultGeneratorDescriptor } from '@prisma-next/framework-components/control';\n\n/**\n * Canonical id for the wall-clock-now mutation default generator.\n *\n * Owned by `family-sql` because that's where the generator lives. The\n * id flows out from here to (1) the control-plane descriptor and the\n * temporal field-preset pair below, (2) the runtime-plane sibling\n * `timestamp-now-runtime-generator.ts`, and (3) authoring surfaces\n * (PSL `temporal.updatedAt()`, TS `field.temporal.updatedAt()`) via\n * the descriptor flow. Co-locating the constant with its only owner\n * keeps the framework layer free of concrete generator ids.\n */\nexport const TIMESTAMP_NOW_GENERATOR_ID = 'timestampNow' as const;\n\n/**\n * Builds the canonical control-plane descriptor for the wall-clock-now\n * mutation default generator. The descriptor's `id` and `buildPhases`\n * are target-agnostic so PSL `temporal.updatedAt()` and TS\n * `field.temporal.updatedAt()` lower to byte-identical contracts.\n *\n * `applicableCodecIds` is omitted: `timestampNow` is preset-only (not\n * reachable via `@default(timestampNow())` lowering), and the codec is\n * co-registered by the preset descriptor itself, so the\n * `@default(...)` compatibility check has no role to play here.\n */\nexport function timestampNowControlDescriptor(): MutationDefaultGeneratorDescriptor {\n return {\n id: TIMESTAMP_NOW_GENERATOR_ID,\n buildPhases: () => ({\n onCreate: { kind: 'generator', id: TIMESTAMP_NOW_GENERATOR_ID },\n onUpdate: { kind: 'generator', id: TIMESTAMP_NOW_GENERATOR_ID },\n }),\n };\n}\n\n/**\n * Builds the canonical `temporal.{createdAt,updatedAt}` field-preset pair\n * for a SQL target. `createdAt` lowers to a `now()` storage default;\n * `updatedAt` lowers to the `timestampNow` execution generator on both\n * `onCreate` and `onUpdate` (RD: \"last modified time\", non-null). Targets\n * supply the codec/native-type pair that matches their timestamp column;\n * everything else is shared so PSL `temporal.updatedAt()` and TS\n * `field.temporal.updatedAt()` lower to byte-identical contracts across\n * targets by construction.\n */\n/* @__NO_SIDE_EFFECTS__ */\nexport function temporalAuthoringPresets<\n const CodecId extends string,\n const NativeType extends string,\n>(input: { readonly codecId: CodecId; readonly nativeType: NativeType }) {\n const { codecId, nativeType } = input;\n return {\n createdAt: {\n kind: 'fieldPreset',\n output: {\n codecId,\n nativeType,\n default: { kind: 'function', expression: 'now()' },\n },\n },\n updatedAt: {\n kind: 'fieldPreset',\n output: {\n codecId,\n nativeType,\n executionDefaults: {\n onCreate: { kind: 'generator', id: TIMESTAMP_NOW_GENERATOR_ID },\n onUpdate: { kind: 'generator', id: TIMESTAMP_NOW_GENERATOR_ID },\n },\n },\n },\n } as const satisfies Record<string, AuthoringFieldPresetDescriptor>;\n}\n"],"mappings":";;;;;;;;;;;;AAcA,MAAa,6BAA6B;;;;;;;;;;;;AAa1C,SAAgB,gCAAoE;CAClF,OAAO;EACL,IAAI;EACJ,oBAAoB;GAClB,UAAU;IAAE,MAAM;IAAa,IAAI;GAA2B;GAC9D,UAAU;IAAE,MAAM;IAAa,IAAI;GAA2B;EAChE;CACF;AACF;;;;;;;;;;;;AAaA,SAAgB,yBAGd,OAAuE;CACvE,MAAM,EAAE,SAAS,eAAe;CAChC,OAAO;EACL,WAAW;GACT,MAAM;GACN,QAAQ;IACN;IACA;IACA,SAAS;KAAE,MAAM;KAAY,YAAY;IAAQ;GACnD;EACF;EACA,WAAW;GACT,MAAM;GACN,QAAQ;IACN;IACA;IACA,mBAAmB;KACjB,UAAU;MAAE,MAAM;MAAa,IAAI;KAA2B;KAC9D,UAAU;MAAE,MAAM;MAAa,IAAI;KAA2B;IAChE;GACF;EACF;CACF;AACF"}
|