@prisma-next/family-sql 0.5.0-dev.67 → 0.5.0-dev.69
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-adapter.d.mts +8 -0
- package/dist/control-adapter.d.mts.map +1 -1
- package/dist/control.d.mts +26 -2
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +143 -3
- package/dist/control.mjs.map +1 -1
- package/dist/migration.d.mts +1 -1
- package/dist/schema-verify.d.mts +1 -1
- package/dist/{types-Ds4dJyTI.d.mts → types-CIdiS2QK.d.mts} +117 -3
- package/dist/types-CIdiS2QK.d.mts.map +1 -0
- package/dist/verify-sql-schema-C84vejAP.mjs.map +1 -1
- package/package.json +18 -18
- package/src/core/control-adapter.ts +11 -0
- package/src/core/control-instance.ts +70 -2
- package/src/core/migrations/field-event-planner.ts +196 -0
- package/src/core/migrations/types.ts +117 -1
- package/src/exports/control.ts +7 -0
- package/dist/types-Ds4dJyTI.d.mts.map +0 -1
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from '@prisma-next/framework-components/control';
|
|
29
29
|
import type { TypesImportSpec } from '@prisma-next/framework-components/emission';
|
|
30
30
|
import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
|
|
31
|
+
import { assertDescriptorSelfConsistency } from '@prisma-next/migration-tools/spaces';
|
|
31
32
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
32
33
|
import { validateContract as sqlValidateContract } from '@prisma-next/sql-contract/validate';
|
|
33
34
|
import {
|
|
@@ -205,6 +206,22 @@ export interface SqlControlFamilyInstance
|
|
|
205
206
|
|
|
206
207
|
schemaVerify(options: SchemaVerifyOptions): Promise<VerifyDatabaseSchemaResult>;
|
|
207
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Verify a contract against an already-introspected schema slice.
|
|
211
|
+
*
|
|
212
|
+
* Used by the aggregate verifier to invoke per-member verification
|
|
213
|
+
* with the live schema pre-projected to the member's claimed slice
|
|
214
|
+
* via `projectSchemaToSpace`. Closes F23 — without per-member
|
|
215
|
+
* pre-projection, single-contract verifiers see other-space tables
|
|
216
|
+
* as `extras`.
|
|
217
|
+
*/
|
|
218
|
+
schemaVerifyAgainstSchema(options: {
|
|
219
|
+
readonly contract: unknown;
|
|
220
|
+
readonly schema: SqlSchemaIR;
|
|
221
|
+
readonly strict: boolean;
|
|
222
|
+
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
|
|
223
|
+
}): VerifyDatabaseSchemaResult;
|
|
224
|
+
|
|
208
225
|
sign(options: {
|
|
209
226
|
readonly driver: ControlDriverInstance<'sql', string>;
|
|
210
227
|
readonly contract: unknown;
|
|
@@ -233,7 +250,9 @@ function isSqlControlAdapter<TTargetId extends string>(
|
|
|
233
250
|
'introspect' in value &&
|
|
234
251
|
typeof (value as { introspect: unknown }).introspect === 'function' &&
|
|
235
252
|
'readMarker' in value &&
|
|
236
|
-
typeof (value as { readMarker: unknown }).readMarker === 'function'
|
|
253
|
+
typeof (value as { readMarker: unknown }).readMarker === 'function' &&
|
|
254
|
+
'readAllMarkers' in value &&
|
|
255
|
+
typeof (value as { readAllMarkers: unknown }).readAllMarkers === 'function'
|
|
237
256
|
);
|
|
238
257
|
}
|
|
239
258
|
|
|
@@ -301,6 +320,29 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
301
320
|
stack.extensionPacks as unknown as readonly (SqlControlExtensionDescriptor<TTargetId> &
|
|
302
321
|
DescriptorWithStorageTypes)[];
|
|
303
322
|
|
|
323
|
+
// Descriptor self-consistency check.
|
|
324
|
+
// Each extension that exposes a `contractSpace` must publish a
|
|
325
|
+
// `headRef.hash` that matches the canonical hash recomputed from its
|
|
326
|
+
// `contractJson`. A stale value would silently corrupt every downstream
|
|
327
|
+
// boundary that trusts `headRef.hash` as the canonical identity (drift
|
|
328
|
+
// detection, on-disk artefact emission, runner marker writes). Failing
|
|
329
|
+
// fast at descriptor-load time turns "extension author shipped an
|
|
330
|
+
// inconsistent descriptor" into an explicit, actionable error
|
|
331
|
+
// (`MIGRATION.DESCRIPTOR_HEAD_HASH_MISMATCH`) rather than a confusing
|
|
332
|
+
// mismatch surfacing several layers downstream.
|
|
333
|
+
for (const extension of extensions) {
|
|
334
|
+
if (extension.contractSpace) {
|
|
335
|
+
const { contractJson, headRef } = extension.contractSpace;
|
|
336
|
+
assertDescriptorSelfConsistency({
|
|
337
|
+
extensionId: extension.id,
|
|
338
|
+
target: contractJson.target,
|
|
339
|
+
targetFamily: contractJson.targetFamily,
|
|
340
|
+
storage: contractJson.storage,
|
|
341
|
+
headRefHash: headRef.hash,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
304
346
|
const { codecTypeImports, operationTypeImports, extensionIds } = stack;
|
|
305
347
|
|
|
306
348
|
const typeMetadataRegistry = buildSqlTypeMetadataRegistry({
|
|
@@ -317,7 +359,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
317
359
|
const controlAdapter = adapter.create(stack);
|
|
318
360
|
if (!isSqlControlAdapter(controlAdapter)) {
|
|
319
361
|
throw new Error(
|
|
320
|
-
'Adapter does not implement SqlControlAdapter (missing introspect or
|
|
362
|
+
'Adapter does not implement SqlControlAdapter (missing introspect, readMarker, or readAllMarkers)',
|
|
321
363
|
);
|
|
322
364
|
}
|
|
323
365
|
return controlAdapter;
|
|
@@ -480,6 +522,27 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
480
522
|
...ifDefined('normalizeNativeType', controlAdapter.normalizeNativeType),
|
|
481
523
|
});
|
|
482
524
|
},
|
|
525
|
+
schemaVerifyAgainstSchema(options: {
|
|
526
|
+
readonly contract: unknown;
|
|
527
|
+
readonly schema: SqlSchemaIR;
|
|
528
|
+
readonly strict: boolean;
|
|
529
|
+
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
|
|
530
|
+
}): VerifyDatabaseSchemaResult {
|
|
531
|
+
const contract = sqlValidateContract<Contract<SqlStorage>>(
|
|
532
|
+
options.contract,
|
|
533
|
+
emptyCodecLookup,
|
|
534
|
+
);
|
|
535
|
+
const controlAdapter = getControlAdapter();
|
|
536
|
+
return verifySqlSchema({
|
|
537
|
+
contract,
|
|
538
|
+
schema: options.schema,
|
|
539
|
+
strict: options.strict,
|
|
540
|
+
typeMetadataRegistry,
|
|
541
|
+
frameworkComponents: options.frameworkComponents,
|
|
542
|
+
...ifDefined('normalizeDefault', controlAdapter.normalizeDefault),
|
|
543
|
+
...ifDefined('normalizeNativeType', controlAdapter.normalizeNativeType),
|
|
544
|
+
});
|
|
545
|
+
},
|
|
483
546
|
async sign(options: {
|
|
484
547
|
readonly driver: ControlDriverInstance<'sql', string>;
|
|
485
548
|
readonly contract: unknown;
|
|
@@ -583,6 +646,11 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
583
646
|
}): Promise<ContractMarkerRecord | null> {
|
|
584
647
|
return getControlAdapter().readMarker(options.driver, options.space);
|
|
585
648
|
},
|
|
649
|
+
async readAllMarkers(options: {
|
|
650
|
+
readonly driver: ControlDriverInstance<'sql', string>;
|
|
651
|
+
}): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
|
|
652
|
+
return getControlAdapter().readAllMarkers(options.driver);
|
|
653
|
+
},
|
|
586
654
|
async introspect(options: {
|
|
587
655
|
readonly driver: ControlDriverInstance<'sql', string>;
|
|
588
656
|
readonly contract?: unknown;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codec lifecycle hook planner — runs `onFieldEvent` for every per-field
|
|
3
|
+
* delta between two contracts and concatenates the returned ops in a
|
|
4
|
+
* deterministic order.
|
|
5
|
+
*
|
|
6
|
+
* Wired by each target's planner (`PostgresMigrationPlanner`,
|
|
7
|
+
* `SqliteMigrationPlanner`) so codec-emitted ops are inlined alongside
|
|
8
|
+
* structural DDL in the app-space migration's `ops.json`. Pure, target-
|
|
9
|
+
* agnostic, and only ever invoked at the app-space emitter; extension-space
|
|
10
|
+
* planning never reaches this helper.
|
|
11
|
+
*
|
|
12
|
+
* Ordering rules:
|
|
13
|
+
*
|
|
14
|
+
* - Events are grouped by phase: `'added'` → `'dropped'` → `'altered'`.
|
|
15
|
+
* - Within each phase, entries are sorted alphabetically by
|
|
16
|
+
* `(tableName, fieldName)`.
|
|
17
|
+
* - The hook's returned ops are appended in the order the hook returned them.
|
|
18
|
+
*
|
|
19
|
+
* `'altered'` is suppressed when only `codecId` differs (codec rotation is a
|
|
20
|
+
* v1 non-goal).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
24
|
+
import type { SqlStorage, StorageColumn, StorageTable } from '@prisma-next/sql-contract/types';
|
|
25
|
+
import type {
|
|
26
|
+
CodecControlHooks,
|
|
27
|
+
FieldEvent,
|
|
28
|
+
FieldEventContext,
|
|
29
|
+
SqlMigrationPlanOperation,
|
|
30
|
+
} from './types';
|
|
31
|
+
|
|
32
|
+
export interface PlanFieldEventOperationsOptions {
|
|
33
|
+
/**
|
|
34
|
+
* Prior contract the planner is diffing against. `null` for first emits
|
|
35
|
+
* (every field is treated as added).
|
|
36
|
+
*/
|
|
37
|
+
readonly priorContract: Contract<SqlStorage> | null;
|
|
38
|
+
/**
|
|
39
|
+
* New contract the user just authored.
|
|
40
|
+
*/
|
|
41
|
+
readonly newContract: Contract<SqlStorage>;
|
|
42
|
+
/**
|
|
43
|
+
* Codec-id keyed map of control hooks, as produced by
|
|
44
|
+
* {@link import('./assembly').extractCodecControlHooks}. Hooks carry
|
|
45
|
+
* `unknown` target details after extraction; the caller casts the
|
|
46
|
+
* helper's returned ops to its target's `SqlMigrationPlanOperation`
|
|
47
|
+
* specialisation at the integration boundary, mirroring how
|
|
48
|
+
* `storageTypePlanCallStrategy` lifts `planTypeOperations` results into
|
|
49
|
+
* `RawSqlCall`.
|
|
50
|
+
*/
|
|
51
|
+
readonly codecHooks: ReadonlyMap<string, CodecControlHooks>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface FieldEntry {
|
|
55
|
+
readonly tableName: string;
|
|
56
|
+
readonly fieldName: string;
|
|
57
|
+
readonly priorTable: StorageTable | undefined;
|
|
58
|
+
readonly newTable: StorageTable | undefined;
|
|
59
|
+
readonly priorField: StorageColumn | undefined;
|
|
60
|
+
readonly newField: StorageColumn | undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function planFieldEventOperations(
|
|
64
|
+
options: PlanFieldEventOperationsOptions,
|
|
65
|
+
): readonly SqlMigrationPlanOperation<unknown>[] {
|
|
66
|
+
const priorTables = options.priorContract?.storage.tables ?? {};
|
|
67
|
+
const newTables = options.newContract.storage.tables;
|
|
68
|
+
|
|
69
|
+
const added: FieldEntry[] = [];
|
|
70
|
+
const dropped: FieldEntry[] = [];
|
|
71
|
+
const altered: FieldEntry[] = [];
|
|
72
|
+
|
|
73
|
+
const tableNames = unionSorted(Object.keys(priorTables), Object.keys(newTables));
|
|
74
|
+
for (const tableName of tableNames) {
|
|
75
|
+
const priorTable = priorTables[tableName];
|
|
76
|
+
const newTable = newTables[tableName];
|
|
77
|
+
const fieldNames = unionSorted(
|
|
78
|
+
priorTable ? Object.keys(priorTable.columns) : [],
|
|
79
|
+
newTable ? Object.keys(newTable.columns) : [],
|
|
80
|
+
);
|
|
81
|
+
for (const fieldName of fieldNames) {
|
|
82
|
+
const priorField = priorTable?.columns[fieldName];
|
|
83
|
+
const newField = newTable?.columns[fieldName];
|
|
84
|
+
const entry: FieldEntry = {
|
|
85
|
+
tableName,
|
|
86
|
+
fieldName,
|
|
87
|
+
priorTable,
|
|
88
|
+
newTable,
|
|
89
|
+
priorField,
|
|
90
|
+
newField,
|
|
91
|
+
};
|
|
92
|
+
if (priorField === undefined && newField !== undefined) {
|
|
93
|
+
added.push(entry);
|
|
94
|
+
} else if (priorField !== undefined && newField === undefined) {
|
|
95
|
+
dropped.push(entry);
|
|
96
|
+
} else if (priorField !== undefined && newField !== undefined) {
|
|
97
|
+
if (isAlteration(priorField, newField)) altered.push(entry);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const ops: SqlMigrationPlanOperation<unknown>[] = [];
|
|
103
|
+
appendOps('added', added, options.codecHooks, ops, (e) => e.newField?.codecId);
|
|
104
|
+
appendOps('dropped', dropped, options.codecHooks, ops, (e) => e.priorField?.codecId);
|
|
105
|
+
appendOps('altered', altered, options.codecHooks, ops, (e) => e.newField?.codecId);
|
|
106
|
+
return ops;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function appendOps(
|
|
110
|
+
event: FieldEvent,
|
|
111
|
+
entries: readonly FieldEntry[],
|
|
112
|
+
codecHooks: ReadonlyMap<string, CodecControlHooks>,
|
|
113
|
+
ops: SqlMigrationPlanOperation<unknown>[],
|
|
114
|
+
pickCodecId: (entry: FieldEntry) => string | undefined,
|
|
115
|
+
): void {
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
const codecId = pickCodecId(entry);
|
|
118
|
+
if (codecId === undefined) continue;
|
|
119
|
+
const hook = codecHooks.get(codecId);
|
|
120
|
+
if (!hook?.onFieldEvent) continue;
|
|
121
|
+
const ctx = buildContext(event, entry);
|
|
122
|
+
const emitted = hook.onFieldEvent(event, ctx);
|
|
123
|
+
for (const op of emitted) ops.push(op);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The context's prior/new sides are scoped to the event:
|
|
129
|
+
*
|
|
130
|
+
* - `'added'` — only `newTable` / `newField` populated.
|
|
131
|
+
* - `'dropped'` — only `priorTable` / `priorField` populated.
|
|
132
|
+
* - `'altered'` — both sides populated.
|
|
133
|
+
*/
|
|
134
|
+
function buildContext(event: FieldEvent, entry: FieldEntry): FieldEventContext {
|
|
135
|
+
const base = { tableName: entry.tableName, fieldName: entry.fieldName };
|
|
136
|
+
if (event === 'added') {
|
|
137
|
+
return {
|
|
138
|
+
...base,
|
|
139
|
+
...(entry.newTable !== undefined ? { newTable: entry.newTable } : {}),
|
|
140
|
+
...(entry.newField !== undefined ? { newField: entry.newField } : {}),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (event === 'dropped') {
|
|
144
|
+
return {
|
|
145
|
+
...base,
|
|
146
|
+
...(entry.priorTable !== undefined ? { priorTable: entry.priorTable } : {}),
|
|
147
|
+
...(entry.priorField !== undefined ? { priorField: entry.priorField } : {}),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
...base,
|
|
152
|
+
...(entry.priorTable !== undefined ? { priorTable: entry.priorTable } : {}),
|
|
153
|
+
...(entry.newTable !== undefined ? { newTable: entry.newTable } : {}),
|
|
154
|
+
...(entry.priorField !== undefined ? { priorField: entry.priorField } : {}),
|
|
155
|
+
...(entry.newField !== undefined ? { newField: entry.newField } : {}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* `'altered'` predicate. Returns `false` whenever `codecId` differs —
|
|
161
|
+
* any codec change suppresses the `altered` event entirely, including
|
|
162
|
+
* cases where another property also differs in the same diff. Codec
|
|
163
|
+
* rotation is a v1 non-goal (project spec § Non-goals); avoiding the
|
|
164
|
+
* mixed event keeps the migration semantics for codec changes explicit
|
|
165
|
+
* (out of scope) rather than smuggling them through as `altered`.
|
|
166
|
+
*
|
|
167
|
+
* For non-`codecId` diffs, returns `true` iff any other column property
|
|
168
|
+
* differs.
|
|
169
|
+
*/
|
|
170
|
+
function isAlteration(prior: StorageColumn, current: StorageColumn): boolean {
|
|
171
|
+
if (prior.codecId !== current.codecId) return false;
|
|
172
|
+
return !sameStorageColumn(prior, current);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function sameStorageColumn(a: StorageColumn, b: StorageColumn): boolean {
|
|
176
|
+
if (a === b) return true;
|
|
177
|
+
if (a.nativeType !== b.nativeType) return false;
|
|
178
|
+
if (a.nullable !== b.nullable) return false;
|
|
179
|
+
if (a.typeRef !== b.typeRef) return false;
|
|
180
|
+
if (!sameJson(a.typeParams, b.typeParams)) return false;
|
|
181
|
+
if (!sameJson(a.default, b.default)) return false;
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function sameJson(a: unknown, b: unknown): boolean {
|
|
186
|
+
if (a === b) return true;
|
|
187
|
+
if (a === undefined || b === undefined) return false;
|
|
188
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function unionSorted(a: readonly string[], b: readonly string[]): readonly string[] {
|
|
192
|
+
const set = new Set<string>();
|
|
193
|
+
for (const name of a) set.add(name);
|
|
194
|
+
for (const name of b) set.add(name);
|
|
195
|
+
return [...set].sort((x, y) => (x < y ? -1 : x > y ? 1 : 0));
|
|
196
|
+
}
|
|
@@ -18,7 +18,12 @@ import type {
|
|
|
18
18
|
OperationContext,
|
|
19
19
|
SchemaIssue,
|
|
20
20
|
} from '@prisma-next/framework-components/control';
|
|
21
|
-
import type {
|
|
21
|
+
import type {
|
|
22
|
+
SqlStorage,
|
|
23
|
+
StorageColumn,
|
|
24
|
+
StorageTable,
|
|
25
|
+
StorageTypeInstance,
|
|
26
|
+
} from '@prisma-next/sql-contract/types';
|
|
22
27
|
import type { SqlOperationDescriptor } from '@prisma-next/sql-operations';
|
|
23
28
|
import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
24
29
|
import type { Result } from '@prisma-next/utils/result';
|
|
@@ -83,6 +88,41 @@ export interface ResolveIdentityValueInput {
|
|
|
83
88
|
readonly typeParams?: Record<string, unknown>;
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Per-field lifecycle event a codec hook can react to.
|
|
93
|
+
*
|
|
94
|
+
* Fired during app-space migration emission as the SQL family diffs the
|
|
95
|
+
* prior contract against the new contract.
|
|
96
|
+
*
|
|
97
|
+
* - `'added'` — the field is present in the new contract but not the prior.
|
|
98
|
+
* - `'dropped'` — the field is present in the prior contract but not the new.
|
|
99
|
+
* - `'altered'` — the field is present in both and any property other than
|
|
100
|
+
* `codecId` differs. Codec-id changes are a v1 non-goal:
|
|
101
|
+
* when only `codecId` differs, no `'altered'` event fires.
|
|
102
|
+
*/
|
|
103
|
+
export type FieldEvent = 'added' | 'dropped' | 'altered';
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Context passed to {@link CodecControlHooks.onFieldEvent}.
|
|
107
|
+
*
|
|
108
|
+
* `tableName` and `fieldName` are always populated; `priorTable` /
|
|
109
|
+
* `priorField` carry the prior contract's view of the table and column
|
|
110
|
+
* (present for `'dropped'` and `'altered'`); `newTable` / `newField`
|
|
111
|
+
* carry the new contract's view (present for `'added'` and `'altered'`).
|
|
112
|
+
*
|
|
113
|
+
* The hook only ever receives app-space contract IR — extension-space
|
|
114
|
+
* fields are scoped out by the API: the hook is wired at the
|
|
115
|
+
* application emitter only.
|
|
116
|
+
*/
|
|
117
|
+
export interface FieldEventContext {
|
|
118
|
+
readonly tableName: string;
|
|
119
|
+
readonly fieldName: string;
|
|
120
|
+
readonly priorTable?: StorageTable;
|
|
121
|
+
readonly newTable?: StorageTable;
|
|
122
|
+
readonly priorField?: StorageColumn;
|
|
123
|
+
readonly newField?: StorageColumn;
|
|
124
|
+
}
|
|
125
|
+
|
|
86
126
|
export interface CodecControlHooks<TTargetDetails = unknown> {
|
|
87
127
|
planTypeOperations?: (options: {
|
|
88
128
|
readonly typeName: string;
|
|
@@ -123,6 +163,21 @@ export interface CodecControlHooks<TTargetDetails = unknown> {
|
|
|
123
163
|
* - undefined: no opinion; planner may use built-in fallbacks
|
|
124
164
|
*/
|
|
125
165
|
resolveIdentityValue?: (input: ResolveIdentityValueInput) => string | null | undefined;
|
|
166
|
+
/**
|
|
167
|
+
* Reacts to per-field added / dropped / altered events as the app-space
|
|
168
|
+
* emitter diffs the prior contract against the new contract. Returned
|
|
169
|
+
* ops are inlined into the app-space migration's `ops.json` alongside
|
|
170
|
+
* the user's structural ops.
|
|
171
|
+
*
|
|
172
|
+
* Synchronous. Each returned op must carry its own `invariantId`. Hooks
|
|
173
|
+
* are dispatched per `(table, field)` based on the field's `codecId`
|
|
174
|
+
* (the new field's codec for `'added'` / `'altered'`; the prior field's
|
|
175
|
+
* codec for `'dropped'`).
|
|
176
|
+
*/
|
|
177
|
+
onFieldEvent?: (
|
|
178
|
+
event: FieldEvent,
|
|
179
|
+
ctx: FieldEventContext,
|
|
180
|
+
) => readonly SqlMigrationPlanOperation<TTargetDetails>[];
|
|
126
181
|
}
|
|
127
182
|
|
|
128
183
|
export interface SqlControlExtensionDescriptor<TTargetId extends string>
|
|
@@ -320,6 +375,14 @@ export interface SqlMigrationRunnerExecuteCallbacks<TTargetDetails> {
|
|
|
320
375
|
export interface SqlMigrationRunnerExecuteOptions<TTargetDetails> {
|
|
321
376
|
readonly plan: SqlMigrationPlan<TTargetDetails>;
|
|
322
377
|
readonly driver: ControlDriverInstance<'sql', string>;
|
|
378
|
+
/**
|
|
379
|
+
* Logical contract space this plan applies to. When omitted the
|
|
380
|
+
* runner derives the space from {@link SqlMigrationPlan.spaceId};
|
|
381
|
+
* when supplied, the runner asserts it matches `plan.spaceId` so a
|
|
382
|
+
* caller cannot accidentally write the marker row for a different
|
|
383
|
+
* space than the plan was produced for.
|
|
384
|
+
*/
|
|
385
|
+
readonly space?: string;
|
|
323
386
|
/**
|
|
324
387
|
* Destination contract IR.
|
|
325
388
|
* Must correspond to `plan.destination` and is used for schema verification and marker/ledger writes.
|
|
@@ -371,11 +434,64 @@ export type SqlMigrationRunnerResult = Result<
|
|
|
371
434
|
>;
|
|
372
435
|
|
|
373
436
|
export interface SqlMigrationRunner<TTargetDetails> {
|
|
437
|
+
/**
|
|
438
|
+
* Apply a single migration plan, opening and managing its own
|
|
439
|
+
* transaction (and any target-specific connection-level setup, e.g.
|
|
440
|
+
* SQLite's `PRAGMA foreign_keys` toggle). Existing single-space
|
|
441
|
+
* callers route through here.
|
|
442
|
+
*/
|
|
374
443
|
execute(
|
|
375
444
|
options: SqlMigrationRunnerExecuteOptions<TTargetDetails>,
|
|
376
445
|
): Promise<SqlMigrationRunnerResult>;
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Apply a single migration plan against an already-open connection
|
|
449
|
+
* **without** opening a transaction. The caller is responsible for
|
|
450
|
+
* wrapping the call (and any siblings) in `BEGIN` / `COMMIT` /
|
|
451
|
+
* `ROLLBACK`. Used by the per-space runner wiring to fan out across
|
|
452
|
+
* contract spaces inside one outer transaction so a mid-apply
|
|
453
|
+
* failure rolls back every space's writes.
|
|
454
|
+
*
|
|
455
|
+
* Idempotent control-table setup (`prisma_contract.*`) and marker
|
|
456
|
+
* writes use `options.space` to address the per-space marker row.
|
|
457
|
+
*/
|
|
458
|
+
executeOnConnection(
|
|
459
|
+
options: SqlMigrationRunnerExecuteOptions<TTargetDetails>,
|
|
460
|
+
): Promise<SqlMigrationRunnerResult>;
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Apply per-space plans across multiple contract spaces inside a
|
|
464
|
+
* single outer transaction. The caller orders the input list
|
|
465
|
+
* (typically via the aggregate planner's `applyOrder`: extensions
|
|
466
|
+
* alphabetical, then app); the runner is responsible for opening
|
|
467
|
+
* / committing the outer
|
|
468
|
+
* transaction (and any target-specific connection-level setup such
|
|
469
|
+
* as the SQLite FK pragma toggle). A failure on any space rolls
|
|
470
|
+
* back every space's writes.
|
|
471
|
+
*
|
|
472
|
+
* Each space's `SqlMigrationRunnerExecuteOptions` must reference the
|
|
473
|
+
* same `driver` (the connection the outer transaction is open on).
|
|
474
|
+
* Per-space marker writes use `options.space` to address the row.
|
|
475
|
+
*/
|
|
476
|
+
executeAcrossSpaces(options: {
|
|
477
|
+
readonly driver: ControlDriverInstance<'sql', string>;
|
|
478
|
+
readonly perSpaceOptions: ReadonlyArray<SqlMigrationRunnerExecuteOptions<TTargetDetails>>;
|
|
479
|
+
}): Promise<MultiSpaceRunnerResult>;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
export interface MultiSpaceRunnerSuccessValue {
|
|
483
|
+
readonly perSpaceResults: ReadonlyArray<{
|
|
484
|
+
readonly space: string;
|
|
485
|
+
readonly value: SqlMigrationRunnerSuccessValue;
|
|
486
|
+
}>;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export interface MultiSpaceRunnerFailure extends SqlMigrationRunnerFailure {
|
|
490
|
+
readonly failingSpace: string;
|
|
377
491
|
}
|
|
378
492
|
|
|
493
|
+
export type MultiSpaceRunnerResult = Result<MultiSpaceRunnerSuccessValue, MultiSpaceRunnerFailure>;
|
|
494
|
+
|
|
379
495
|
export interface SqlControlTargetDescriptor<TTargetId extends string, TTargetDetails>
|
|
380
496
|
extends MigratableTargetDescriptor<'sql', TTargetId, SqlControlFamilyInstance> {
|
|
381
497
|
readonly queryOperations?: () => ReadonlyArray<SqlOperationDescriptor>;
|
package/src/exports/control.ts
CHANGED
|
@@ -24,6 +24,8 @@ export {
|
|
|
24
24
|
contractToSchemaIR,
|
|
25
25
|
detectDestructiveChanges,
|
|
26
26
|
} from '../core/migrations/contract-to-schema-ir';
|
|
27
|
+
export type { PlanFieldEventOperationsOptions } from '../core/migrations/field-event-planner';
|
|
28
|
+
export { planFieldEventOperations } from '../core/migrations/field-event-planner';
|
|
27
29
|
export {
|
|
28
30
|
createMigrationPlan,
|
|
29
31
|
plannerFailure,
|
|
@@ -38,6 +40,11 @@ export type {
|
|
|
38
40
|
ComponentDatabaseDependency,
|
|
39
41
|
CreateSqlMigrationPlanOptions,
|
|
40
42
|
ExpandNativeTypeInput,
|
|
43
|
+
FieldEvent,
|
|
44
|
+
FieldEventContext,
|
|
45
|
+
MultiSpaceRunnerFailure,
|
|
46
|
+
MultiSpaceRunnerResult,
|
|
47
|
+
MultiSpaceRunnerSuccessValue,
|
|
41
48
|
ResolveIdentityValueInput,
|
|
42
49
|
SqlControlAdapterDescriptor,
|
|
43
50
|
SqlControlExtensionDescriptor,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-Ds4dJyTI.d.mts","names":[],"sources":["../src/core/control-instance.ts","../src/core/migrations/types.ts"],"mappings":"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"}
|