@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.
- package/dist/{authoring-type-constructors-CjFfO6LM.mjs → authoring-type-constructors-CXd-8ydc.mjs} +7 -30
- package/dist/authoring-type-constructors-CXd-8ydc.mjs.map +1 -0
- package/dist/{control-adapter-Cmw9LvEP.d.mts → control-adapter-BeN8TDZ5.d.mts} +29 -8
- package/dist/control-adapter-BeN8TDZ5.d.mts.map +1 -0
- package/dist/control-adapter.d.mts +1 -1
- package/dist/control.d.mts +238 -6
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +347 -942
- package/dist/control.mjs.map +1 -1
- package/dist/diff.d.mts +102 -0
- package/dist/diff.d.mts.map +1 -0
- package/dist/diff.mjs +14 -0
- package/dist/diff.mjs.map +1 -0
- package/dist/ir.d.mts +26 -9
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +2 -2
- package/dist/ir.mjs.map +1 -1
- package/dist/migration.d.mts +11 -2
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +7 -0
- package/dist/migration.mjs.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/psl-infer.d.mts +108 -0
- package/dist/psl-infer.d.mts.map +1 -0
- package/dist/psl-infer.mjs +353 -0
- package/dist/psl-infer.mjs.map +1 -0
- package/dist/schema-differ-DnoopSXm.d.mts +45 -0
- package/dist/schema-differ-DnoopSXm.d.mts.map +1 -0
- package/dist/schema-verify-W3r631Jh.mjs +226 -0
- package/dist/schema-verify-W3r631Jh.mjs.map +1 -0
- package/dist/{sql-contract-serializer-BR2vC7Z-.mjs → sql-contract-serializer-C75cfMSS.mjs} +46 -20
- package/dist/sql-contract-serializer-C75cfMSS.mjs.map +1 -0
- package/dist/{types-kgstZ_Zd.d.mts → types-COTsneBU.d.mts} +34 -139
- package/dist/types-COTsneBU.d.mts.map +1 -0
- package/package.json +23 -23
- package/src/core/authoring-entity-types.ts +12 -37
- package/src/core/control-adapter.ts +10 -5
- package/src/core/control-instance.ts +210 -42
- package/src/core/control-target-descriptor.ts +98 -0
- package/src/core/diff/schema-verify.ts +324 -0
- package/src/core/diff/sql-schema-diff.ts +41 -0
- package/src/core/diff/verifier-disposition.ts +30 -0
- package/src/core/ir/sql-contract-serializer-base.ts +70 -56
- package/src/core/ir/sql-contract-serializer.ts +5 -7
- package/src/core/ir/sql-schema-verifier-base.ts +5 -5
- package/src/core/migrations/contract-to-schema-ir.ts +126 -32
- package/src/core/migrations/field-event-planner.ts +2 -2
- package/src/core/migrations/native-type-expander.ts +28 -0
- package/src/core/migrations/schema-differ.ts +41 -0
- package/src/core/migrations/types.ts +31 -34
- package/src/core/psl-contract-infer/name-transforms.ts +15 -0
- package/src/core/psl-contract-infer/printer-config.ts +12 -6
- package/src/core/psl-contract-infer/relation-inference.ts +9 -1
- package/src/core/sql-migration.ts +12 -1
- package/src/exports/control.ts +11 -1
- package/src/exports/diff.ts +25 -0
- package/src/exports/psl-infer.ts +40 -0
- package/dist/authoring-type-constructors-CjFfO6LM.mjs.map +0 -1
- package/dist/control-adapter-Cmw9LvEP.d.mts.map +0 -1
- package/dist/schema-verify.d.mts +0 -39
- package/dist/schema-verify.d.mts.map +0 -1
- package/dist/schema-verify.mjs +0 -2
- package/dist/sql-contract-serializer-BR2vC7Z-.mjs.map +0 -1
- package/dist/test-utils.d.mts +0 -2
- package/dist/test-utils.mjs +0 -2
- package/dist/types-kgstZ_Zd.d.mts.map +0 -1
- package/dist/verify-sql-schema-thU-jKpf.d.mts +0 -66
- package/dist/verify-sql-schema-thU-jKpf.d.mts.map +0 -1
- package/dist/verify-sql-schema-xT4udQLQ.mjs +0 -1501
- package/dist/verify-sql-schema-xT4udQLQ.mjs.map +0 -1
- package/src/core/psl-contract-infer/postgres-default-mapping.ts +0 -16
- package/src/core/psl-contract-infer/postgres-type-map.ts +0 -157
- package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +0 -795
- package/src/core/schema-verify/control-verify-emit.ts +0 -46
- package/src/core/schema-verify/verifier-disposition.ts +0 -58
- package/src/core/schema-verify/verify-helpers.ts +0 -820
- package/src/core/schema-verify/verify-sql-schema.ts +0 -1311
- package/src/exports/schema-verify.ts +0 -18
- 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
|
|
16
|
-
SqlAnnotations,
|
|
17
|
-
SqlCheckConstraintIRInput,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
import {
|
|
17
|
+
type SqlAnnotations,
|
|
18
|
+
type SqlCheckConstraintIRInput,
|
|
19
|
+
type SqlColumnIRInput,
|
|
20
|
+
type SqlForeignKeyIRInput,
|
|
21
|
+
type SqlIndexIRInput,
|
|
21
22
|
SqlSchemaIR,
|
|
22
23
|
SqlTableIR,
|
|
23
|
-
|
|
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
|
-
):
|
|
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
|
|
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):
|
|
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):
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
92
|
-
const newTable = newTableRaw
|
|
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
|
-
|
|
22
|
-
|
|
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 {
|
|
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:
|
|
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:
|
|
122
|
+
readonly schema: SqlSchemaIRNode;
|
|
117
123
|
readonly schemaName?: string;
|
|
118
|
-
}) => readonly
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/src/exports/control.ts
CHANGED
|
@@ -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,
|