@prisma-next/family-sql 0.11.0 → 0.12.0-dev.10
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-F4JpCJl7.mjs.map +1 -1
- package/dist/control-adapter-Br03xBEv.d.mts +115 -0
- package/dist/control-adapter-Br03xBEv.d.mts.map +1 -0
- package/dist/control-adapter.d.mts +2 -102
- package/dist/control.d.mts +25 -2
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +50 -34
- package/dist/control.mjs.map +1 -1
- package/dist/ir.d.mts +4 -1
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +1 -1
- package/dist/ir.mjs.map +1 -1
- package/dist/migration.d.mts +1 -1
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs.map +1 -1
- package/dist/pack.mjs.map +1 -1
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +1 -1
- package/dist/runtime.mjs.map +1 -1
- package/dist/schema-verify.d.mts +1 -1
- package/dist/schema-verify.d.mts.map +1 -1
- package/dist/schema-verify.mjs +1 -1
- package/dist/{sql-contract-serializer-COnYiewe.mjs → sql-contract-serializer-8axtK4lg.mjs} +31 -13
- package/dist/sql-contract-serializer-8axtK4lg.mjs.map +1 -0
- package/dist/{timestamp-now-generator-r7BP5n3l.mjs → timestamp-now-generator-BkjCQIde.mjs} +2 -1
- package/dist/{timestamp-now-generator-r7BP5n3l.mjs.map → timestamp-now-generator-BkjCQIde.mjs.map} +1 -1
- package/dist/{types-hQoMXr54.d.mts → types-DbGXj_Si.d.mts} +30 -42
- package/dist/types-DbGXj_Si.d.mts.map +1 -0
- package/dist/verify-Crewz6hG.mjs.map +1 -1
- package/dist/{verify-sql-schema-Bfvz07Ik.d.mts → verify-sql-schema-CN7pPoTC.d.mts} +2 -2
- package/dist/verify-sql-schema-CN7pPoTC.d.mts.map +1 -0
- package/dist/{verify-sql-schema-Bj4Wqe2c.mjs → verify-sql-schema-CYLsGCFO.mjs} +52 -38
- package/dist/verify-sql-schema-CYLsGCFO.mjs.map +1 -0
- package/dist/verify.d.mts.map +1 -1
- package/package.json +32 -21
- package/src/core/control-adapter.ts +30 -2
- package/src/core/control-instance.ts +22 -32
- package/src/core/ir/sql-contract-serializer-base.ts +67 -33
- package/src/core/migrations/contract-to-schema-ir.ts +86 -37
- package/src/core/migrations/types.ts +29 -46
- package/src/core/schema-verify/verify-sql-schema.ts +92 -43
- package/src/core/timestamp-now-generator.ts +1 -0
- package/src/exports/control.ts +1 -3
- package/dist/control-adapter.d.mts.map +0 -1
- package/dist/sql-contract-serializer-COnYiewe.mjs.map +0 -1
- package/dist/types-hQoMXr54.d.mts.map +0 -1
- package/dist/verify-sql-schema-Bfvz07Ik.d.mts.map +0 -1
- package/dist/verify-sql-schema-Bj4Wqe2c.mjs.map +0 -1
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { ContractValidationError } from '@prisma-next/contract/contract-validation-error';
|
|
2
2
|
import type { Contract } from '@prisma-next/contract/types';
|
|
3
3
|
import type { ContractSerializer } from '@prisma-next/framework-components/control';
|
|
4
|
-
import { type Namespace, NamespaceBase } from '@prisma-next/framework-components/ir';
|
|
5
4
|
import {
|
|
5
|
+
type Namespace,
|
|
6
|
+
NamespaceBase,
|
|
7
|
+
UNBOUND_NAMESPACE_ID,
|
|
8
|
+
} from '@prisma-next/framework-components/ir';
|
|
9
|
+
import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks';
|
|
10
|
+
import {
|
|
11
|
+
buildSqlNamespace,
|
|
6
12
|
type SqlNamespaceTablesInput,
|
|
7
13
|
SqlStorage,
|
|
14
|
+
type SqlStorageInput,
|
|
8
15
|
type SqlStorageTypeEntry,
|
|
16
|
+
SqlUnboundNamespace,
|
|
9
17
|
StorageTable,
|
|
10
18
|
type StorageTableInput,
|
|
11
19
|
} from '@prisma-next/sql-contract/types';
|
|
@@ -13,12 +21,17 @@ import {
|
|
|
13
21
|
createSqlContractSchema,
|
|
14
22
|
validateSqlContractFully,
|
|
15
23
|
} from '@prisma-next/sql-contract/validators';
|
|
24
|
+
import { blindCast } from '@prisma-next/utils/casts';
|
|
25
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
16
26
|
import type { JsonObject } from '@prisma-next/utils/json';
|
|
17
27
|
import { type Type, type } from 'arktype';
|
|
18
28
|
|
|
19
29
|
const NamespaceRawSchema = type({
|
|
20
30
|
id: 'string',
|
|
21
31
|
'kind?': 'string',
|
|
32
|
+
// Undeclared keys (`tables`, `enum`, and any pack-contributed slot maps)
|
|
33
|
+
// intentionally pass through; the slot loop below iterates them by name.
|
|
34
|
+
'+': 'ignore',
|
|
22
35
|
});
|
|
23
36
|
|
|
24
37
|
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -79,6 +92,10 @@ export abstract class SqlContractSerializerBase<TContract extends Contract<SqlSt
|
|
|
79
92
|
return contract as unknown as JsonObject;
|
|
80
93
|
}
|
|
81
94
|
|
|
95
|
+
shouldPreserveEmpty = sqlContractCanonicalizationHooks.shouldPreserveEmpty;
|
|
96
|
+
|
|
97
|
+
sortStorage = sqlContractCanonicalizationHooks.sortStorage;
|
|
98
|
+
|
|
82
99
|
protected parseSqlContractStructure(json: unknown): Contract<SqlStorage> {
|
|
83
100
|
return validateSqlContractFully<Contract<SqlStorage>>(
|
|
84
101
|
json,
|
|
@@ -99,27 +116,51 @@ export abstract class SqlContractSerializerBase<TContract extends Contract<SqlSt
|
|
|
99
116
|
: undefined;
|
|
100
117
|
|
|
101
118
|
const rawNamespaces = validated.storage.namespaces;
|
|
102
|
-
|
|
103
|
-
|
|
119
|
+
if (rawNamespaces === undefined) {
|
|
120
|
+
throw new ContractValidationError(
|
|
121
|
+
'Contract storage.namespaces is required after structural validation',
|
|
122
|
+
'structural',
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
const hydratedNamespaces = this.hydrateSqlNamespaceMap(rawNamespaces);
|
|
126
|
+
// Compatibility shim: production code that addresses `__unbound__` for table
|
|
127
|
+
// metadata lookups (collection-contract, query-plan-mutations, model-accessor,
|
|
128
|
+
// query-plan-meta, where-binding) uses optional chaining and tolerates absence,
|
|
129
|
+
// but runtime-qualification (TML-2605) has not yet landed cross-namespace table
|
|
130
|
+
// routing. Injecting the empty singleton here keeps helpers that augment the
|
|
131
|
+
// deserialized JSON (e.g. buildMixedPolyContract) working by providing a slot to
|
|
132
|
+
// write into. Once runtime-qualification routes table lookups by namespace, this
|
|
133
|
+
// shim should be removed.
|
|
134
|
+
const unbound = hydratedNamespaces[UNBOUND_NAMESPACE_ID] ?? SqlUnboundNamespace.instance;
|
|
104
135
|
|
|
105
136
|
return {
|
|
106
137
|
...validated,
|
|
107
138
|
storage: new SqlStorage({
|
|
108
139
|
storageHash: validated.storage.storageHash,
|
|
109
|
-
...(
|
|
110
|
-
|
|
140
|
+
...ifDefined('types', hydratedTypes),
|
|
141
|
+
// Cast narrows the result of hydrateSqlNamespaceMap from the wider
|
|
142
|
+
// framework `Namespace` to the SQL-family `SqlNamespace`.
|
|
143
|
+
namespaces: blindCast<
|
|
144
|
+
SqlStorageInput['namespaces'],
|
|
145
|
+
'hydrated SQL namespaces are SqlNamespace instances (family hydration guarantees this)'
|
|
146
|
+
>({ ...hydratedNamespaces, [UNBOUND_NAMESPACE_ID]: unbound }),
|
|
111
147
|
}),
|
|
112
148
|
};
|
|
113
149
|
}
|
|
114
150
|
|
|
115
151
|
protected hydrateSqlNamespaceMap(
|
|
116
152
|
namespaces: Readonly<Record<string, Namespace | Record<string, unknown>>>,
|
|
117
|
-
): Readonly<Record<string, Namespace
|
|
153
|
+
): Readonly<Record<string, Namespace>> {
|
|
118
154
|
return Object.fromEntries(
|
|
119
|
-
Object.entries(namespaces).map(([nsId,
|
|
120
|
-
|
|
121
|
-
this.hydrateSqlNamespaceEntry(nsId,
|
|
122
|
-
|
|
155
|
+
Object.entries(namespaces).map(([nsId, namespaceEntryRaw]) => {
|
|
156
|
+
// Raw entries passed structural validation; hydrate materialises family IR class instances.
|
|
157
|
+
const namespaceHydrated = this.hydrateSqlNamespaceEntry(nsId, namespaceEntryRaw);
|
|
158
|
+
const namespaceMaterialised =
|
|
159
|
+
namespaceHydrated instanceof NamespaceBase
|
|
160
|
+
? namespaceHydrated
|
|
161
|
+
: buildSqlNamespace(namespaceHydrated);
|
|
162
|
+
return [nsId, namespaceMaterialised];
|
|
163
|
+
}),
|
|
123
164
|
);
|
|
124
165
|
}
|
|
125
166
|
|
|
@@ -173,34 +214,27 @@ export abstract class SqlContractSerializerBase<TContract extends Contract<SqlSt
|
|
|
173
214
|
}
|
|
174
215
|
}
|
|
175
216
|
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
hasUnhydratedPostgresEnumEntry &&
|
|
189
|
-
this.entityTypeRegistry.get('postgres-enum') === undefined
|
|
190
|
-
) {
|
|
191
|
-
throw new ContractValidationError(
|
|
192
|
-
'Per-schema database types (e.g. postgres-enum) under storage.namespaces[..].types require PostgresContractSerializer.',
|
|
193
|
-
'structural',
|
|
194
|
-
);
|
|
217
|
+
const enumRaw = rawRecord['enum'];
|
|
218
|
+
if (enumRaw !== undefined && typeof enumRaw === 'object' && enumRaw !== null) {
|
|
219
|
+
for (const entry of Object.values(enumRaw as Record<string, unknown>)) {
|
|
220
|
+
if (typeof entry !== 'object' || entry === null) continue;
|
|
221
|
+
const kind = (entry as { kind?: unknown }).kind;
|
|
222
|
+
if (typeof kind === 'string' && this.entityTypeRegistry.get(kind) === undefined) {
|
|
223
|
+
throw new ContractValidationError(
|
|
224
|
+
`Entry kind '${kind}' has no registered hydration factory.`,
|
|
225
|
+
'structural',
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
195
229
|
}
|
|
196
230
|
|
|
197
231
|
const tables = (result['tables'] ?? {}) as Record<string, StorageTable>;
|
|
198
|
-
const
|
|
232
|
+
const enumSlot = result['enum'] as NonNullable<SqlNamespaceTablesInput['enum']> | undefined;
|
|
199
233
|
return {
|
|
200
|
-
|
|
234
|
+
...result,
|
|
201
235
|
tables,
|
|
202
|
-
...(
|
|
203
|
-
};
|
|
236
|
+
...(enumSlot !== undefined ? { enum: enumSlot } : {}),
|
|
237
|
+
} as SqlNamespaceTablesInput;
|
|
204
238
|
}
|
|
205
239
|
|
|
206
240
|
protected hydrateStorageTypeEntry(entry: SqlStorageTypeEntry): SqlStorageTypeEntry {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ColumnDefault, Contract } from '@prisma-next/contract/types';
|
|
2
2
|
import type { MigrationPlannerConflict } from '@prisma-next/framework-components/control';
|
|
3
|
+
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
3
4
|
import {
|
|
4
5
|
type ForeignKey,
|
|
5
6
|
type Index,
|
|
@@ -52,6 +53,26 @@ export type NativeTypeExpander = (input: {
|
|
|
52
53
|
*/
|
|
53
54
|
export type DefaultRenderer = (def: ColumnDefault, column: StorageColumn) => string;
|
|
54
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Target-supplied callback that computes the schema-qualified annotation-map
|
|
58
|
+
* key for a namespace-scoped enum storage type.
|
|
59
|
+
*
|
|
60
|
+
* Enum lookups (`readExistingEnumValues`) are namespace/schema-qualified so two
|
|
61
|
+
* namespaces holding an enum with the same TypeScript name (and even the same
|
|
62
|
+
* native type) resolve to distinct live-database types. The *format* of that
|
|
63
|
+
* key — and the namespace → DDL-schema resolution it depends on — is a
|
|
64
|
+
* target-specific concern (Postgres schemas; SQLite/MySQL differ), so the
|
|
65
|
+
* target injects it here as data rather than the family layer importing a
|
|
66
|
+
* concrete `ddlSchemaName`/key implementation. This keeps the family layer
|
|
67
|
+
* target-agnostic (no `@prisma-next/target-*` dependency) while the projection
|
|
68
|
+
* still emits keys that match the target's read side exactly.
|
|
69
|
+
*/
|
|
70
|
+
export type EnumStorageKeyResolver = (
|
|
71
|
+
storage: SqlStorage,
|
|
72
|
+
namespaceId: string,
|
|
73
|
+
nativeType: string,
|
|
74
|
+
) => string;
|
|
75
|
+
|
|
55
76
|
function convertColumn(
|
|
56
77
|
name: string,
|
|
57
78
|
column: StorageColumn,
|
|
@@ -152,6 +173,8 @@ function convertForeignKey(fk: ForeignKey): SqlForeignKeyIR {
|
|
|
152
173
|
referencedSchema: fk.target.namespaceId,
|
|
153
174
|
referencedColumns: fk.target.columns,
|
|
154
175
|
...ifDefined('name', fk.name),
|
|
176
|
+
...ifDefined('onDelete', fk.onDelete),
|
|
177
|
+
...ifDefined('onUpdate', fk.onUpdate),
|
|
155
178
|
};
|
|
156
179
|
}
|
|
157
180
|
|
|
@@ -263,6 +286,14 @@ export interface ContractToSchemaIROptions {
|
|
|
263
286
|
readonly annotationNamespace: string;
|
|
264
287
|
readonly expandNativeType?: NativeTypeExpander;
|
|
265
288
|
readonly renderDefault?: DefaultRenderer;
|
|
289
|
+
/**
|
|
290
|
+
* Target-supplied resolver for namespace/schema-qualified enum annotation
|
|
291
|
+
* keys. When provided (Postgres), every namespace-scoped enum is keyed by the
|
|
292
|
+
* resolver's output so the projected `storageTypes` map matches the target's
|
|
293
|
+
* `readExistingEnumValues` lookup. Targets without namespace-qualified enum
|
|
294
|
+
* storage (SQLite) omit it; enums are absent there.
|
|
295
|
+
*/
|
|
296
|
+
readonly resolveEnumStorageKey?: EnumStorageKeyResolver;
|
|
266
297
|
}
|
|
267
298
|
|
|
268
299
|
/**
|
|
@@ -296,9 +327,9 @@ export function contractToSchemaIR(
|
|
|
296
327
|
...((storage.types ?? {}) as ResolvedStorageTypes),
|
|
297
328
|
};
|
|
298
329
|
for (const ns of Object.values(storage.namespaces)) {
|
|
299
|
-
const
|
|
300
|
-
if (
|
|
301
|
-
for (const [k, v] of Object.entries(
|
|
330
|
+
const nsEnums = (ns as { enum?: Record<string, PostgresEnumStorageEntry> }).enum;
|
|
331
|
+
if (nsEnums) {
|
|
332
|
+
for (const [k, v] of Object.entries(nsEnums)) {
|
|
302
333
|
allTypes[k] = v;
|
|
303
334
|
}
|
|
304
335
|
}
|
|
@@ -328,7 +359,11 @@ export function contractToSchemaIR(
|
|
|
328
359
|
}
|
|
329
360
|
}
|
|
330
361
|
|
|
331
|
-
const annotations = deriveAnnotations(
|
|
362
|
+
const annotations = deriveAnnotations(
|
|
363
|
+
storage,
|
|
364
|
+
options.annotationNamespace,
|
|
365
|
+
options.resolveEnumStorageKey,
|
|
366
|
+
);
|
|
332
367
|
|
|
333
368
|
return {
|
|
334
369
|
tables,
|
|
@@ -336,47 +371,61 @@ export function contractToSchemaIR(
|
|
|
336
371
|
};
|
|
337
372
|
}
|
|
338
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Normalises a native enum storage entry to the codec-typed annotation shape
|
|
376
|
+
* `{codecId, nativeType, typeParams}` the introspector writes and
|
|
377
|
+
* `readExistingEnumValues` reads (`existing.codecId` + `existing.typeParams.values`).
|
|
378
|
+
* Without this the projector would emit the raw `PostgresEnumStorageEntry`
|
|
379
|
+
* shape (top-level `values`, no `typeParams`) and the enum would read as new.
|
|
380
|
+
*/
|
|
381
|
+
function normalizeEnumAnnotation(entry: PostgresEnumStorageEntry): StorageTypeInstance {
|
|
382
|
+
return toStorageTypeInstance({
|
|
383
|
+
codecId: entry.codecId,
|
|
384
|
+
nativeType: entry.nativeType,
|
|
385
|
+
typeParams: { values: entry.values },
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
339
389
|
function deriveAnnotations(
|
|
340
390
|
storage: SqlStorage,
|
|
341
391
|
annotationNamespace: string,
|
|
392
|
+
resolveEnumStorageKey: EnumStorageKeyResolver | undefined,
|
|
342
393
|
): SqlAnnotations | undefined {
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
const types = allTypes as ResolvedStorageTypes;
|
|
355
|
-
if (Object.keys(types).length === 0) return undefined;
|
|
356
|
-
// Re-key by nativeType, normalising every variant to the codec-typed
|
|
357
|
-
// annotation shape `{codecId, nativeType, typeParams}` produced by the
|
|
358
|
-
// adapter introspector (`introspectPostgresEnumTypes` writes that shape;
|
|
359
|
-
// see also `enum-planning.ts § readExistingEnumValues`, which reads
|
|
360
|
-
// `existing.codecId` + `existing.typeParams.values`). Without this
|
|
361
|
-
// normalisation, the projector would emit the raw
|
|
362
|
-
// `PostgresEnumStorageEntry` shape (top-level `values`, no `typeParams`)
|
|
363
|
-
// and downstream Schema IR consumers that walk the codec-typed shape
|
|
364
|
-
// would see enum entries as new (e.g. the planner emits a fresh
|
|
365
|
-
// `CreateEnumTypeCall` instead of the rebuild recipe). Unknown future
|
|
366
|
-
// kinds without `nativeType` are skipped rather than crashing.
|
|
367
|
-
const byNativeType: Record<string, StorageTypeInstance> = {};
|
|
368
|
-
for (const typeInstance of Object.values(types)) {
|
|
394
|
+
const storageTypes: Record<string, StorageTypeInstance> = {};
|
|
395
|
+
|
|
396
|
+
// Top-level `storage.types`: codec-typed entries (vector, decimal, …) keyed
|
|
397
|
+
// by bare `nativeType` (unchanged). Post-S1.B enums live in
|
|
398
|
+
// `namespaces[*].enum`, not here; a defensive top-level enum is still
|
|
399
|
+
// namespace/schema-qualified via the resolver under the unbound coordinate
|
|
400
|
+
// so it never collides on a bare name.
|
|
401
|
+
for (const typeInstance of Object.values((storage.types ?? {}) as ResolvedStorageTypes)) {
|
|
369
402
|
if (isPostgresEnumStorageEntry(typeInstance)) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
});
|
|
403
|
+
const key = resolveEnumStorageKey
|
|
404
|
+
? resolveEnumStorageKey(storage, UNBOUND_NAMESPACE_ID, typeInstance.nativeType)
|
|
405
|
+
: typeInstance.nativeType;
|
|
406
|
+
storageTypes[key] = normalizeEnumAnnotation(typeInstance);
|
|
375
407
|
continue;
|
|
376
408
|
}
|
|
377
409
|
if (isStorageTypeInstance(typeInstance)) {
|
|
378
|
-
|
|
410
|
+
storageTypes[typeInstance.nativeType] = typeInstance;
|
|
379
411
|
}
|
|
380
412
|
}
|
|
381
|
-
|
|
413
|
+
|
|
414
|
+
// Namespace-scoped enums: schema-qualified compound key matching the target's
|
|
415
|
+
// `readExistingEnumValues` read side, so two namespaces sharing an enum name
|
|
416
|
+
// (or native type) resolve to distinct live-database types.
|
|
417
|
+
for (const [namespaceId, ns] of Object.entries(storage.namespaces)) {
|
|
418
|
+
const nsEnums = (ns as { enum?: Record<string, PostgresEnumStorageEntry> }).enum;
|
|
419
|
+
if (!nsEnums) continue;
|
|
420
|
+
for (const entry of Object.values(nsEnums)) {
|
|
421
|
+
if (!isPostgresEnumStorageEntry(entry)) continue;
|
|
422
|
+
const key = resolveEnumStorageKey
|
|
423
|
+
? resolveEnumStorageKey(storage, namespaceId, entry.nativeType)
|
|
424
|
+
: entry.nativeType;
|
|
425
|
+
storageTypes[key] = normalizeEnumAnnotation(entry);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (Object.keys(storageTypes).length === 0) return undefined;
|
|
430
|
+
return { [annotationNamespace]: { storageTypes } };
|
|
382
431
|
}
|
|
@@ -15,12 +15,14 @@ import type {
|
|
|
15
15
|
MigrationPlanOperation,
|
|
16
16
|
MigrationRunnerExecutionChecks,
|
|
17
17
|
MigrationRunnerFailure,
|
|
18
|
-
|
|
18
|
+
MigrationRunnerPerSpaceSuccessValue,
|
|
19
|
+
MigrationRunnerResult,
|
|
19
20
|
OperationContext,
|
|
20
21
|
OpFactoryCall,
|
|
21
22
|
SchemaIssue,
|
|
22
23
|
SchemaVerifier,
|
|
23
24
|
} from '@prisma-next/framework-components/control';
|
|
25
|
+
import type { AggregateMigrationEdgeRef } from '@prisma-next/migration-tools/aggregate';
|
|
24
26
|
import type {
|
|
25
27
|
SqlStorage,
|
|
26
28
|
StorageColumn,
|
|
@@ -30,6 +32,7 @@ import type {
|
|
|
30
32
|
import type { SqlOperationDescriptors } from '@prisma-next/sql-operations';
|
|
31
33
|
import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
32
34
|
import type { Result } from '@prisma-next/utils/result';
|
|
35
|
+
import type { SqlControlAdapter } from '../control-adapter';
|
|
33
36
|
import type { SqlControlFamilyInstance } from '../control-instance';
|
|
34
37
|
|
|
35
38
|
export type AnyRecord = Readonly<Record<string, unknown>>;
|
|
@@ -173,7 +176,7 @@ export interface SqlControlExtensionDescriptor<TTargetId extends string>
|
|
|
173
176
|
}
|
|
174
177
|
|
|
175
178
|
export interface SqlControlAdapterDescriptor<TTargetId extends string>
|
|
176
|
-
extends ControlAdapterDescriptor<'sql', TTargetId
|
|
179
|
+
extends ControlAdapterDescriptor<'sql', TTargetId, SqlControlAdapter<TTargetId>> {
|
|
177
180
|
readonly queryOperations?: () => SqlOperationDescriptors;
|
|
178
181
|
}
|
|
179
182
|
|
|
@@ -234,7 +237,7 @@ export interface SqlMigrationPlan<TTargetDetails> extends MigrationPlan {
|
|
|
234
237
|
* pass the extension's space id. Required at every call site so the
|
|
235
238
|
* type system surfaces every place that needs to thread the value
|
|
236
239
|
* (rather than letting an `?? APP_SPACE_ID` fall-through silently
|
|
237
|
-
* collapse
|
|
240
|
+
* collapse per-space markers onto the `'app'` row).
|
|
238
241
|
*
|
|
239
242
|
* @see specs/framework-mechanism.spec.md § 2.
|
|
240
243
|
*/
|
|
@@ -383,6 +386,11 @@ export interface SqlMigrationRunnerExecuteOptions<TTargetDetails> {
|
|
|
383
386
|
* All components must have matching familyId ('sql') and targetId.
|
|
384
387
|
*/
|
|
385
388
|
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
|
|
389
|
+
/**
|
|
390
|
+
* Per-edge breakdown from graph-walk planning. When present, the runner
|
|
391
|
+
* writes one ledger row per edge instead of one collapsed row per apply.
|
|
392
|
+
*/
|
|
393
|
+
readonly migrationEdges: readonly AggregateMigrationEdgeRef[];
|
|
386
394
|
}
|
|
387
395
|
|
|
388
396
|
export type SqlMigrationRunnerErrorCode =
|
|
@@ -401,7 +409,7 @@ export interface SqlMigrationRunnerFailure extends MigrationRunnerFailure {
|
|
|
401
409
|
readonly meta?: AnyRecord;
|
|
402
410
|
}
|
|
403
411
|
|
|
404
|
-
export interface SqlMigrationRunnerSuccessValue extends
|
|
412
|
+
export interface SqlMigrationRunnerSuccessValue extends MigrationRunnerPerSpaceSuccessValue {}
|
|
405
413
|
|
|
406
414
|
export type SqlMigrationRunnerResult = Result<
|
|
407
415
|
SqlMigrationRunnerSuccessValue,
|
|
@@ -410,22 +418,29 @@ export type SqlMigrationRunnerResult = Result<
|
|
|
410
418
|
|
|
411
419
|
export interface SqlMigrationRunner<TTargetDetails> {
|
|
412
420
|
/**
|
|
413
|
-
* Apply
|
|
414
|
-
* transaction (and any target-specific connection-level setup, e.g.
|
|
415
|
-
* SQLite's `PRAGMA foreign_keys` toggle).
|
|
416
|
-
*
|
|
421
|
+
* Apply one or more per-space migration plans, opening and managing the
|
|
422
|
+
* outer transaction (and any target-specific connection-level setup, e.g.
|
|
423
|
+
* SQLite's `PRAGMA foreign_keys` toggle). An apply that targets one space
|
|
424
|
+
* passes a one-element `perSpaceOptions` list.
|
|
425
|
+
*
|
|
426
|
+
* The caller orders the input list (typically via the aggregate planner's
|
|
427
|
+
* `applyOrder`: extensions alphabetical, then app). A failure on any space
|
|
428
|
+
* rolls back every space's writes.
|
|
429
|
+
*
|
|
430
|
+
* Each entry must reference the same `driver` as the top-level `driver`
|
|
431
|
+
* (the connection the outer transaction is open on).
|
|
417
432
|
*/
|
|
418
|
-
execute(
|
|
419
|
-
|
|
420
|
-
|
|
433
|
+
execute(options: {
|
|
434
|
+
readonly driver: ControlDriverInstance<'sql', string>;
|
|
435
|
+
readonly perSpaceOptions: ReadonlyArray<SqlMigrationRunnerExecuteOptions<TTargetDetails>>;
|
|
436
|
+
}): Promise<MigrationRunnerResult>;
|
|
421
437
|
|
|
422
438
|
/**
|
|
423
439
|
* Apply a single migration plan against an already-open connection
|
|
424
440
|
* **without** opening a transaction. The caller is responsible for
|
|
425
441
|
* wrapping the call (and any siblings) in `BEGIN` / `COMMIT` /
|
|
426
|
-
* `ROLLBACK`. Used by
|
|
427
|
-
* contract spaces inside one outer transaction
|
|
428
|
-
* failure rolls back every space's writes.
|
|
442
|
+
* `ROLLBACK`. Used by {@link SqlMigrationRunner.execute} to fan out
|
|
443
|
+
* across contract spaces inside one outer transaction.
|
|
429
444
|
*
|
|
430
445
|
* Idempotent control-table setup (`prisma_contract.*`) and marker
|
|
431
446
|
* writes use `options.space` to address the per-space marker row.
|
|
@@ -433,40 +448,8 @@ export interface SqlMigrationRunner<TTargetDetails> {
|
|
|
433
448
|
executeOnConnection(
|
|
434
449
|
options: SqlMigrationRunnerExecuteOptions<TTargetDetails>,
|
|
435
450
|
): Promise<SqlMigrationRunnerResult>;
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Apply per-space plans across multiple contract spaces inside a
|
|
439
|
-
* single outer transaction. The caller orders the input list
|
|
440
|
-
* (typically via the aggregate planner's `applyOrder`: extensions
|
|
441
|
-
* alphabetical, then app); the runner is responsible for opening
|
|
442
|
-
* / committing the outer
|
|
443
|
-
* transaction (and any target-specific connection-level setup such
|
|
444
|
-
* as the SQLite FK pragma toggle). A failure on any space rolls
|
|
445
|
-
* back every space's writes.
|
|
446
|
-
*
|
|
447
|
-
* Each space's `SqlMigrationRunnerExecuteOptions` must reference the
|
|
448
|
-
* same `driver` (the connection the outer transaction is open on).
|
|
449
|
-
* Per-space marker writes use `options.space` to address the row.
|
|
450
|
-
*/
|
|
451
|
-
executeAcrossSpaces(options: {
|
|
452
|
-
readonly driver: ControlDriverInstance<'sql', string>;
|
|
453
|
-
readonly perSpaceOptions: ReadonlyArray<SqlMigrationRunnerExecuteOptions<TTargetDetails>>;
|
|
454
|
-
}): Promise<MultiSpaceRunnerResult>;
|
|
455
451
|
}
|
|
456
452
|
|
|
457
|
-
export interface MultiSpaceRunnerSuccessValue {
|
|
458
|
-
readonly perSpaceResults: ReadonlyArray<{
|
|
459
|
-
readonly space: string;
|
|
460
|
-
readonly value: SqlMigrationRunnerSuccessValue;
|
|
461
|
-
}>;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
export interface MultiSpaceRunnerFailure extends SqlMigrationRunnerFailure {
|
|
465
|
-
readonly failingSpace: string;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
export type MultiSpaceRunnerResult = Result<MultiSpaceRunnerSuccessValue, MultiSpaceRunnerFailure>;
|
|
469
|
-
|
|
470
453
|
export interface SqlControlTargetDescriptor<
|
|
471
454
|
TTargetId extends string,
|
|
472
455
|
TTargetDetails,
|