@prisma-next/framework-components 0.5.0-dev.7 → 0.5.0-dev.71
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/README.md +63 -3
- package/dist/authoring.d.mts +2 -2
- package/dist/authoring.mjs +2 -122
- package/dist/codec-m_-FAyQn.d.mts +168 -0
- package/dist/codec-m_-FAyQn.d.mts.map +1 -0
- package/dist/codec.d.mts +48 -2
- package/dist/codec.d.mts.map +1 -0
- package/dist/codec.mjs +67 -4
- package/dist/codec.mjs.map +1 -1
- package/dist/components.d.mts +1 -1
- package/dist/components.mjs +2 -3
- package/dist/control.d.mts +370 -72
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +82 -49
- package/dist/control.mjs.map +1 -1
- package/dist/emission-types-BKa4bR9m.d.mts +39 -0
- package/dist/emission-types-BKa4bR9m.d.mts.map +1 -0
- package/dist/emission.d.mts +2 -2
- package/dist/emission.mjs +1 -1
- package/dist/execution.d.mts +5 -5
- package/dist/execution.d.mts.map +1 -1
- package/dist/execution.mjs +4 -6
- package/dist/execution.mjs.map +1 -1
- package/dist/framework-authoring-BwWNqTlD.mjs +205 -0
- package/dist/framework-authoring-BwWNqTlD.mjs.map +1 -0
- package/dist/{framework-authoring-D1-JZ37B.d.mts → framework-authoring-Cm5f9U64.d.mts} +41 -12
- package/dist/framework-authoring-Cm5f9U64.d.mts.map +1 -0
- package/dist/{framework-components-EJXe-pum.d.mts → framework-components-DgIEy9eJ.d.mts} +45 -55
- package/dist/framework-components-DgIEy9eJ.d.mts.map +1 -0
- package/dist/{framework-components-C8ZhSwXe.mjs → framework-components-FdqmlGUj.mjs} +3 -3
- package/dist/framework-components-FdqmlGUj.mjs.map +1 -0
- package/dist/psl-ast-Ckn_G-jv.d.mts +159 -0
- package/dist/psl-ast-Ckn_G-jv.d.mts.map +1 -0
- package/dist/psl-ast.d.mts +2 -0
- package/dist/psl-ast.mjs +1 -0
- package/dist/runtime.d.mts +346 -19
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +254 -7
- package/dist/runtime.mjs.map +1 -1
- package/dist/{types-import-spec-C4sc7wbb.d.mts → types-import-spec-BxI5cSQy.d.mts} +2 -2
- package/dist/types-import-spec-BxI5cSQy.d.mts.map +1 -0
- package/package.json +11 -8
- package/src/control/control-capabilities.ts +95 -0
- package/src/{control-descriptors.ts → control/control-descriptors.ts} +7 -7
- package/src/{control-instances.ts → control/control-instances.ts} +52 -6
- package/src/{control-migration-types.ts → control/control-migration-types.ts} +202 -61
- package/src/control/control-operation-preview.ts +23 -0
- package/src/control/control-spaces.ts +82 -0
- package/src/{control-stack.ts → control/control-stack.ts} +77 -94
- package/src/control/emission-types.ts +49 -0
- package/src/control/psl-ast.ts +193 -0
- package/src/{execution-descriptors.ts → execution/execution-descriptors.ts} +7 -7
- package/src/{execution-instances.ts → execution/execution-instances.ts} +1 -1
- package/src/{execution-requirements.ts → execution/execution-requirements.ts} +1 -1
- package/src/execution/query-plan.ts +53 -0
- package/src/execution/race-against-abort.ts +85 -0
- package/src/execution/run-with-middleware.ts +132 -0
- package/src/execution/runtime-core.ts +133 -0
- package/src/execution/runtime-error.ts +83 -0
- package/src/execution/runtime-middleware.ts +182 -0
- package/src/exports/authoring.ts +5 -2
- package/src/exports/codec.ts +27 -2
- package/src/exports/components.ts +2 -2
- package/src/exports/control.ts +40 -13
- package/src/exports/emission.ts +2 -2
- package/src/exports/execution.ts +5 -5
- package/src/exports/psl-ast.ts +1 -0
- package/src/exports/runtime.ts +17 -5
- package/src/shared/codec-descriptor.ts +87 -0
- package/src/shared/codec-types.ts +79 -0
- package/src/shared/codec.ts +80 -0
- package/src/shared/column-spec.ts +83 -0
- package/src/{framework-authoring.ts → shared/framework-authoring.ts} +202 -23
- package/src/{framework-components.ts → shared/framework-components.ts} +22 -48
- package/src/{mutation-default-types.ts → shared/mutation-default-types.ts} +22 -2
- package/dist/authoring.mjs.map +0 -1
- package/dist/codec-types-B58nCJiu.d.mts +0 -40
- package/dist/codec-types-B58nCJiu.d.mts.map +0 -1
- package/dist/emission-types-BPAALJbF.d.mts +0 -24
- package/dist/emission-types-BPAALJbF.d.mts.map +0 -1
- package/dist/framework-authoring-D1-JZ37B.d.mts.map +0 -1
- package/dist/framework-components-C8ZhSwXe.mjs.map +0 -1
- package/dist/framework-components-EJXe-pum.d.mts.map +0 -1
- package/dist/types-import-spec-C4sc7wbb.d.mts.map +0 -1
- package/src/codec-types.ts +0 -46
- package/src/control-capabilities.ts +0 -34
- package/src/emission-types.ts +0 -28
- package/src/runtime-error.ts +0 -39
- package/src/runtime-middleware.ts +0 -83
- /package/src/{control-result-types.ts → control/control-result-types.ts} +0 -0
- /package/src/{control-schema-view.ts → control/control-schema-view.ts} +0 -0
- /package/src/{async-iterable-result.ts → execution/async-iterable-result.ts} +0 -0
- /package/src/{execution-stack.ts → execution/execution-stack.ts} +0 -0
- /package/src/{types-import-spec.ts → shared/types-import-spec.ts} +0 -0
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import type { Contract, ContractMarkerRecord } from '@prisma-next/contract/types';
|
|
2
|
-
import type {
|
|
3
|
-
SignDatabaseResult,
|
|
4
|
-
VerifyDatabaseResult,
|
|
5
|
-
VerifyDatabaseSchemaResult,
|
|
6
|
-
} from './control-result-types';
|
|
7
2
|
import type {
|
|
8
3
|
AdapterInstance,
|
|
9
4
|
DriverInstance,
|
|
@@ -11,7 +6,12 @@ import type {
|
|
|
11
6
|
FamilyInstance,
|
|
12
7
|
TargetBoundComponentDescriptor,
|
|
13
8
|
TargetInstance,
|
|
14
|
-
} from '
|
|
9
|
+
} from '../shared/framework-components';
|
|
10
|
+
import type {
|
|
11
|
+
SignDatabaseResult,
|
|
12
|
+
VerifyDatabaseResult,
|
|
13
|
+
VerifyDatabaseSchemaResult,
|
|
14
|
+
} from './control-result-types';
|
|
15
15
|
|
|
16
16
|
export interface ControlFamilyInstance<TFamilyId extends string, TSchemaIR>
|
|
17
17
|
extends FamilyInstance<TFamilyId> {
|
|
@@ -34,6 +34,24 @@ export interface ControlFamilyInstance<TFamilyId extends string, TSchemaIR>
|
|
|
34
34
|
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, string>>;
|
|
35
35
|
}): Promise<VerifyDatabaseSchemaResult>;
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Verify a contract against an already-introspected schema slice.
|
|
39
|
+
*
|
|
40
|
+
* Difference from {@link schemaVerify}: no `driver`, no introspection
|
|
41
|
+
* — the caller hands over the schema directly. Used by the aggregate
|
|
42
|
+
* verifier to invoke the family's verification logic per member,
|
|
43
|
+
* with the schema **pre-projected** to that member's claimed slice
|
|
44
|
+
* via {@link import('@prisma-next/migration-tools/aggregate').projectSchemaToSpace}.
|
|
45
|
+
*
|
|
46
|
+
* Synchronous — no I/O. Idempotent.
|
|
47
|
+
*/
|
|
48
|
+
schemaVerifyAgainstSchema(options: {
|
|
49
|
+
readonly contract: unknown;
|
|
50
|
+
readonly schema: TSchemaIR;
|
|
51
|
+
readonly strict: boolean;
|
|
52
|
+
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, string>>;
|
|
53
|
+
}): VerifyDatabaseSchemaResult;
|
|
54
|
+
|
|
37
55
|
sign(options: {
|
|
38
56
|
readonly driver: ControlDriverInstance<TFamilyId, string>;
|
|
39
57
|
readonly contract: unknown;
|
|
@@ -41,10 +59,38 @@ export interface ControlFamilyInstance<TFamilyId extends string, TSchemaIR>
|
|
|
41
59
|
readonly configPath?: string;
|
|
42
60
|
}): Promise<SignDatabaseResult>;
|
|
43
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Reads the contract marker for `space` from the database, returning
|
|
64
|
+
* `null` if no marker row exists for that space (or if the marker
|
|
65
|
+
* table itself is missing).
|
|
66
|
+
*
|
|
67
|
+
* `space` is required at every call site so the type system surfaces
|
|
68
|
+
* every place that needs to thread the value: callers in single-app
|
|
69
|
+
* paths pass {@link import('./control-spaces').APP_SPACE_ID}
|
|
70
|
+
* (`'app'`); per-extension callers pass the extension's space id.
|
|
71
|
+
* Defaulting at the family-interface level was a silent bug door —
|
|
72
|
+
* it let multi-space-aware callers forget to pass `space` and
|
|
73
|
+
* collapse onto the app's marker row.
|
|
74
|
+
*
|
|
75
|
+
* Families whose underlying storage doesn't yet support per-space
|
|
76
|
+
* markers (Mongo, today) accept `space` for interface conformance and
|
|
77
|
+
* reject any non-`APP_SPACE_ID` value rather than silently ignoring
|
|
78
|
+
* it; see the family-specific implementation for details.
|
|
79
|
+
*/
|
|
44
80
|
readMarker(options: {
|
|
45
81
|
readonly driver: ControlDriverInstance<TFamilyId, string>;
|
|
82
|
+
readonly space: string;
|
|
46
83
|
}): Promise<ContractMarkerRecord | null>;
|
|
47
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Reads every marker row keyed by `space`. Used by the per-space
|
|
87
|
+
* verifier to detect orphan marker rows and marker-vs-on-disk drift.
|
|
88
|
+
* Returns an empty map when the marker table does not yet exist.
|
|
89
|
+
*/
|
|
90
|
+
readAllMarkers(options: {
|
|
91
|
+
readonly driver: ControlDriverInstance<TFamilyId, string>;
|
|
92
|
+
}): Promise<ReadonlyMap<string, ContractMarkerRecord>>;
|
|
93
|
+
|
|
48
94
|
introspect(options: {
|
|
49
95
|
readonly driver: ControlDriverInstance<TFamilyId, string>;
|
|
50
96
|
readonly contract?: unknown;
|
|
@@ -11,8 +11,63 @@
|
|
|
11
11
|
|
|
12
12
|
import type { Contract } from '@prisma-next/contract/types';
|
|
13
13
|
import type { Result } from '@prisma-next/utils/result';
|
|
14
|
+
import type { TargetBoundComponentDescriptor } from '../shared/framework-components';
|
|
14
15
|
import type { ControlDriverInstance, ControlFamilyInstance } from './control-instances';
|
|
15
|
-
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Migration Package Metadata
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Planner provenance recorded inside {@link MigrationMetadata}.
|
|
23
|
+
*
|
|
24
|
+
* `used` / `applied` track which migration hints the planner consulted
|
|
25
|
+
* vs. which it actually applied during emission; `plannerVersion`
|
|
26
|
+
* pins the planner build that produced the migration so future
|
|
27
|
+
* verification passes can recognise plans authored against an older
|
|
28
|
+
* planner.
|
|
29
|
+
*/
|
|
30
|
+
export interface MigrationHints {
|
|
31
|
+
readonly used: readonly string[];
|
|
32
|
+
readonly applied: readonly string[];
|
|
33
|
+
readonly plannerVersion: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* In-memory migration metadata envelope. Every migration is
|
|
38
|
+
* content-addressed: the `migrationHash` is a hash over the metadata
|
|
39
|
+
* envelope plus the operations list, computed at write time. There is no
|
|
40
|
+
* draft state — a migration directory either exists with fully attested
|
|
41
|
+
* metadata or it does not.
|
|
42
|
+
*
|
|
43
|
+
* When the planner cannot lower an operation because of an unfilled
|
|
44
|
+
* `placeholder(...)` slot, the migration is still written with
|
|
45
|
+
* `migrationHash` hashed over `ops: []`. Re-running self-emit after the
|
|
46
|
+
* user fills the placeholder produces a *different* `migrationHash`
|
|
47
|
+
* (committed to the real ops); this is intentional.
|
|
48
|
+
*
|
|
49
|
+
* The on-disk JSON shape in `migration.json` matches this type
|
|
50
|
+
* field-for-field — `JSON.stringify(metadata, null, 2)` is the canonical
|
|
51
|
+
* writer output (defined in `@prisma-next/migration-tools/io`).
|
|
52
|
+
*/
|
|
53
|
+
export interface MigrationMetadata {
|
|
54
|
+
readonly migrationHash: string;
|
|
55
|
+
readonly from: string | null;
|
|
56
|
+
readonly to: string;
|
|
57
|
+
readonly fromContract: Contract | null;
|
|
58
|
+
readonly toContract: Contract;
|
|
59
|
+
readonly hints: MigrationHints;
|
|
60
|
+
readonly labels: readonly string[];
|
|
61
|
+
/**
|
|
62
|
+
* Sorted, deduplicated list of `invariantId`s declared by the
|
|
63
|
+
* migration's data-transform ops. Always present; an empty array
|
|
64
|
+
* means the migration has no routing-visible data transforms.
|
|
65
|
+
*/
|
|
66
|
+
readonly providedInvariants: readonly string[];
|
|
67
|
+
readonly authorship?: { readonly author?: string; readonly email?: string };
|
|
68
|
+
readonly signature?: { readonly keyId: string; readonly value: string } | null;
|
|
69
|
+
readonly createdAt: string;
|
|
70
|
+
}
|
|
16
71
|
|
|
17
72
|
// ============================================================================
|
|
18
73
|
// Operation Classes and Policy
|
|
@@ -28,61 +83,23 @@ import type { TargetBoundComponentDescriptor } from './framework-components';
|
|
|
28
83
|
export type MigrationOperationClass = 'additive' | 'widening' | 'destructive' | 'data';
|
|
29
84
|
|
|
30
85
|
// ============================================================================
|
|
31
|
-
//
|
|
86
|
+
// Serialized Query Plan
|
|
32
87
|
// ============================================================================
|
|
33
88
|
|
|
34
89
|
/**
|
|
35
90
|
* A lowered query statement as stored in ops.json.
|
|
36
91
|
* Contains the SQL string and parameter values — ready for execution.
|
|
37
92
|
* Lowering from query builder AST to SQL happens at verify time.
|
|
93
|
+
*
|
|
94
|
+
* The Postgres `dataTransform` factory uses this shape internally to
|
|
95
|
+
* carry the user's lowered `check`/`run` plans before wrapping them
|
|
96
|
+
* into precheck/execute/postcheck steps on the unified migration op.
|
|
38
97
|
*/
|
|
39
98
|
export interface SerializedQueryPlan {
|
|
40
99
|
readonly sql: string;
|
|
41
100
|
readonly params: readonly unknown[];
|
|
42
101
|
}
|
|
43
102
|
|
|
44
|
-
/**
|
|
45
|
-
* A data transform operation within a migration edge.
|
|
46
|
-
*
|
|
47
|
-
* Data transforms are authored in TypeScript using the query builder,
|
|
48
|
-
* serialized to JSON ASTs at verification time, and rendered to SQL
|
|
49
|
-
* by the target adapter at apply time.
|
|
50
|
-
*
|
|
51
|
-
* The `name` serves as the invariant identity — it's recorded in the
|
|
52
|
-
* ledger and used for invariant-aware routing via environment refs.
|
|
53
|
-
*
|
|
54
|
-
* In draft state (before verification), `check` and `run` are null.
|
|
55
|
-
* After verification, they contain the serialized query ASTs.
|
|
56
|
-
*/
|
|
57
|
-
export interface DataTransformOperation extends MigrationPlanOperation {
|
|
58
|
-
readonly operationClass: 'data';
|
|
59
|
-
/**
|
|
60
|
-
* The invariant name for this data transform.
|
|
61
|
-
* Recorded in the ledger on successful edge completion.
|
|
62
|
-
* Used by environment refs to declare required invariants.
|
|
63
|
-
*/
|
|
64
|
-
readonly name: string;
|
|
65
|
-
/**
|
|
66
|
-
* Path to the TypeScript source file that produced this operation.
|
|
67
|
-
* Not part of edgeId computation — for traceability only.
|
|
68
|
-
*/
|
|
69
|
-
readonly source: string;
|
|
70
|
-
/**
|
|
71
|
-
* Serialized check query plan, or a boolean literal.
|
|
72
|
-
* - SerializedQueryPlan: describes violations; empty result = already applied.
|
|
73
|
-
* - false: always run (no check).
|
|
74
|
-
* - true: always skip.
|
|
75
|
-
* - null: not yet serialized (draft state).
|
|
76
|
-
*/
|
|
77
|
-
readonly check: SerializedQueryPlan | boolean | null;
|
|
78
|
-
/**
|
|
79
|
-
* Serialized run query plans.
|
|
80
|
-
* - Array of serialized query plans to execute sequentially.
|
|
81
|
-
* - null: not yet serialized (draft state).
|
|
82
|
-
*/
|
|
83
|
-
readonly run: readonly SerializedQueryPlan[] | null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
103
|
/**
|
|
87
104
|
* Policy defining which operation classes are allowed during a migration.
|
|
88
105
|
*/
|
|
@@ -105,6 +122,17 @@ export interface MigrationPlanOperation {
|
|
|
105
122
|
readonly label: string;
|
|
106
123
|
/** The class of operation (additive, widening, destructive). */
|
|
107
124
|
readonly operationClass: MigrationOperationClass;
|
|
125
|
+
/**
|
|
126
|
+
* Optional opt-in routing identity for data-transform operations.
|
|
127
|
+
* Presence opts the transform into invariant-aware routing; absence
|
|
128
|
+
* means it is path-dependent and not referenceable from refs.
|
|
129
|
+
*
|
|
130
|
+
* Lives on the base op so the manifest emitter and
|
|
131
|
+
* `deriveProvidedInvariants` can read it without depending on a
|
|
132
|
+
* target-specific shape. Schema-DDL ops (additive / widening /
|
|
133
|
+
* destructive) leave it undefined.
|
|
134
|
+
*/
|
|
135
|
+
readonly invariantId?: string;
|
|
108
136
|
}
|
|
109
137
|
|
|
110
138
|
// ============================================================================
|
|
@@ -136,6 +164,16 @@ export interface OpFactoryCall {
|
|
|
136
164
|
export interface MigrationPlan {
|
|
137
165
|
/** The target ID this plan is for (e.g., 'postgres'). */
|
|
138
166
|
readonly targetId: string;
|
|
167
|
+
/**
|
|
168
|
+
* Contract space this plan applies to. Runners cross-check
|
|
169
|
+
* `options.space` against `plan.spaceId` so the marker row gets keyed
|
|
170
|
+
* by the right space when applying via `executeAcrossSpaces`.
|
|
171
|
+
*
|
|
172
|
+
* Optional for backward compatibility with single-space callers that
|
|
173
|
+
* pre-date the contract-space aggregate; when present, runners
|
|
174
|
+
* enforce that it matches `options.space`.
|
|
175
|
+
*/
|
|
176
|
+
readonly spaceId?: string;
|
|
139
177
|
/**
|
|
140
178
|
* Origin contract identity that the plan expects the database to currently be at.
|
|
141
179
|
* If omitted or null, the runner skips origin validation entirely.
|
|
@@ -151,6 +189,17 @@ export interface MigrationPlan {
|
|
|
151
189
|
};
|
|
152
190
|
/** Ordered list of operations to execute. */
|
|
153
191
|
readonly operations: readonly MigrationPlanOperation[];
|
|
192
|
+
/**
|
|
193
|
+
* Sorted, deduplicated invariant ids declared by this plan's data-transform
|
|
194
|
+
* ops. Authored migrations carry the canonical value from
|
|
195
|
+
* `migration.json.providedInvariants`; planner-built plans (`db init`,
|
|
196
|
+
* `db update`) omit it (the runner treats it as `[]`). Runners read this
|
|
197
|
+
* field for marker writes and self-edge no-op detection rather than
|
|
198
|
+
* re-deriving from `operations`, since the manifest is the canonical
|
|
199
|
+
* source for the invariant set across all runners (postgres, sqlite,
|
|
200
|
+
* mongo).
|
|
201
|
+
*/
|
|
202
|
+
readonly providedInvariants?: readonly string[];
|
|
154
203
|
}
|
|
155
204
|
|
|
156
205
|
/**
|
|
@@ -289,21 +338,23 @@ export interface MigrationPlanner<
|
|
|
289
338
|
readonly contract: unknown;
|
|
290
339
|
readonly schema: unknown;
|
|
291
340
|
readonly policy: MigrationOperationPolicy;
|
|
292
|
-
/**
|
|
293
|
-
* Storage hash of the "from" contract (the state the planner assumes the
|
|
294
|
-
* database starts at). Planners use this to populate `describe()` on the
|
|
295
|
-
* produced plan so the rendered `migration.ts` has correct `from`/`to`
|
|
296
|
-
* metadata.
|
|
297
|
-
*/
|
|
298
|
-
readonly fromHash: string;
|
|
299
341
|
/**
|
|
300
342
|
* The "from" contract (the state the planner assumes the database starts
|
|
301
|
-
* at)
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
343
|
+
* at), or `null` for a baseline plan with no prior state.
|
|
344
|
+
*
|
|
345
|
+
* Planners derive any "from" identity they need to stamp onto the
|
|
346
|
+
* produced plan's `describe()` from `fromContract?.storage.storageHash
|
|
347
|
+
* ?? null`. They also pass this to data-safety strategies so they can
|
|
348
|
+
* compare `from` and `to` column shapes (e.g. to detect unsafe type
|
|
349
|
+
* changes).
|
|
350
|
+
*
|
|
351
|
+
* Required at every call site to make the structural fact "I have a
|
|
352
|
+
* prior contract / I don't" visible in the type. Reconciliation
|
|
353
|
+
* commands (`db init`, `db update`) introspect a live schema and pass
|
|
354
|
+
* `null`; authoring commands (`migration plan`) pass the previous
|
|
355
|
+
* bundle's `metadata.toContract`.
|
|
305
356
|
*/
|
|
306
|
-
readonly fromContract
|
|
357
|
+
readonly fromContract: Contract | null;
|
|
307
358
|
/**
|
|
308
359
|
* Active framework components participating in this composition.
|
|
309
360
|
* Families/targets can interpret this list to derive family-specific metadata.
|
|
@@ -312,6 +363,13 @@ export interface MigrationPlanner<
|
|
|
312
363
|
readonly frameworkComponents: ReadonlyArray<
|
|
313
364
|
TargetBoundComponentDescriptor<TFamilyId, TTargetId>
|
|
314
365
|
>;
|
|
366
|
+
/**
|
|
367
|
+
* Contract space this plan applies to. Stamped onto the produced
|
|
368
|
+
* plan so the runner keys the marker row by the right space when
|
|
369
|
+
* executing. App-plan callers pass `APP_SPACE_ID` (`'app'`);
|
|
370
|
+
* per-extension callers pass the extension's space id.
|
|
371
|
+
*/
|
|
372
|
+
readonly spaceId: string;
|
|
315
373
|
}): MigrationPlannerResult;
|
|
316
374
|
|
|
317
375
|
/**
|
|
@@ -320,8 +378,15 @@ export interface MigrationPlanner<
|
|
|
320
378
|
* Used by `migration new` to scaffold a fresh `migration.ts`. The
|
|
321
379
|
* returned plan has no operations; its `renderTypeScript()` yields a
|
|
322
380
|
* stub the user can edit.
|
|
381
|
+
*
|
|
382
|
+
* `spaceId` is stamped onto the produced plan; reconciliation flows
|
|
383
|
+
* (`db init`, `db update`) and authoring flows (`migration new`) all
|
|
384
|
+
* pass it explicitly.
|
|
323
385
|
*/
|
|
324
|
-
emptyMigration(
|
|
386
|
+
emptyMigration(
|
|
387
|
+
context: MigrationScaffoldContext,
|
|
388
|
+
spaceId: string,
|
|
389
|
+
): MigrationPlanWithAuthoringSurface;
|
|
325
390
|
}
|
|
326
391
|
|
|
327
392
|
/**
|
|
@@ -335,6 +400,16 @@ export interface MigrationRunner<
|
|
|
335
400
|
TFamilyId extends string = string,
|
|
336
401
|
TTargetId extends string = string,
|
|
337
402
|
> {
|
|
403
|
+
/**
|
|
404
|
+
* Execute a migration plan against the configured driver.
|
|
405
|
+
*
|
|
406
|
+
* The `plan` parameter is trusted input. Callers are responsible for
|
|
407
|
+
* upstream verification of the originating migration package — typically
|
|
408
|
+
* by obtaining the package via `readMigrationPackage` from
|
|
409
|
+
* `@prisma-next/migration-tools/io`, which performs hash-integrity checks
|
|
410
|
+
* at the load boundary. Runners do not re-verify the plan and assume the
|
|
411
|
+
* `(metadata, ops)` pair on disk has not been tampered with since emit.
|
|
412
|
+
*/
|
|
338
413
|
execute(options: {
|
|
339
414
|
readonly plan: MigrationPlan;
|
|
340
415
|
readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;
|
|
@@ -360,6 +435,71 @@ export interface MigrationRunner<
|
|
|
360
435
|
}): Promise<MigrationRunnerResult>;
|
|
361
436
|
}
|
|
362
437
|
|
|
438
|
+
// ============================================================================
|
|
439
|
+
// Multi-space runner protocol (extension contract spaces, TML-2397)
|
|
440
|
+
// ============================================================================
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Per-space input for {@link MultiSpaceCapableRunner.executeAcrossSpaces}.
|
|
444
|
+
*
|
|
445
|
+
* Mirrors the single-space `MigrationRunner.execute` options, extended with a
|
|
446
|
+
* required `space` identifier. Each entry's `driver` must reference the same
|
|
447
|
+
* connection the outer transaction is opened on (typically the same value as
|
|
448
|
+
* the top-level `driver` on `executeAcrossSpaces`).
|
|
449
|
+
*
|
|
450
|
+
* Family-specific runners (e.g. the SQL family's `SqlMigrationRunner`) define
|
|
451
|
+
* a richer per-space option shape that is structurally compatible with this
|
|
452
|
+
* one — additional optional fields (e.g. SQL's `strictVerification`,
|
|
453
|
+
* `schemaName`, `callbacks`) are tolerated by the underlying runner without
|
|
454
|
+
* affecting cross-target wiring.
|
|
455
|
+
*/
|
|
456
|
+
export interface MultiSpaceRunnerPerSpaceOptions<
|
|
457
|
+
TFamilyId extends string = string,
|
|
458
|
+
TTargetId extends string = string,
|
|
459
|
+
> {
|
|
460
|
+
readonly space: string;
|
|
461
|
+
readonly plan: MigrationPlan;
|
|
462
|
+
readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;
|
|
463
|
+
readonly destinationContract: unknown;
|
|
464
|
+
readonly policy: MigrationOperationPolicy;
|
|
465
|
+
readonly executionChecks?: MigrationRunnerExecutionChecks;
|
|
466
|
+
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export interface MultiSpaceRunnerSuccessValue {
|
|
470
|
+
readonly perSpaceResults: ReadonlyArray<{
|
|
471
|
+
readonly space: string;
|
|
472
|
+
readonly value: MigrationRunnerSuccessValue;
|
|
473
|
+
}>;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export interface MultiSpaceRunnerFailure extends MigrationRunnerFailure {
|
|
477
|
+
/** Identifier of the space whose plan caused the rollback. */
|
|
478
|
+
readonly failingSpace: string;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export type MultiSpaceRunnerResult = Result<MultiSpaceRunnerSuccessValue, MultiSpaceRunnerFailure>;
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Optional capability for runners that can apply a list of per-space plans
|
|
485
|
+
* inside a single outer transaction. A failure on any space rolls back every
|
|
486
|
+
* space's writes.
|
|
487
|
+
*
|
|
488
|
+
* Today's only implementer is the SQL family (`SqlMigrationRunner`); Mongo
|
|
489
|
+
* per-space is a non-goal per the project spec. The capability is declared
|
|
490
|
+
* at the framework layer so CLI utilities can route through it without
|
|
491
|
+
* importing the SQL family directly.
|
|
492
|
+
*/
|
|
493
|
+
export interface MultiSpaceCapableRunner<
|
|
494
|
+
TFamilyId extends string = string,
|
|
495
|
+
TTargetId extends string = string,
|
|
496
|
+
> {
|
|
497
|
+
executeAcrossSpaces(options: {
|
|
498
|
+
readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;
|
|
499
|
+
readonly perSpaceOptions: ReadonlyArray<MultiSpaceRunnerPerSpaceOptions<TFamilyId, TTargetId>>;
|
|
500
|
+
}): Promise<MultiSpaceRunnerResult>;
|
|
501
|
+
}
|
|
502
|
+
|
|
363
503
|
// ============================================================================
|
|
364
504
|
// Target Migrations Capability
|
|
365
505
|
// ============================================================================
|
|
@@ -414,11 +554,12 @@ export interface MigrationScaffoldContext {
|
|
|
414
554
|
/** Absolute path to the contract.json file, if one exists. Used by targets that emit typed-contract imports. */
|
|
415
555
|
readonly contractJsonPath?: string;
|
|
416
556
|
/**
|
|
417
|
-
* Storage hash of the "from" contract
|
|
418
|
-
* `describe()` on the
|
|
419
|
-
* is correctly
|
|
557
|
+
* Storage hash of the "from" contract, or `null` for a baseline scaffold
|
|
558
|
+
* with no prior state. Targets use this to populate `describe()` on the
|
|
559
|
+
* rendered empty migration so that identity metadata is correctly
|
|
560
|
+
* populated.
|
|
420
561
|
*/
|
|
421
|
-
readonly fromHash: string;
|
|
562
|
+
readonly fromHash: string | null;
|
|
422
563
|
/**
|
|
423
564
|
* Storage hash of the "to" contract. Same purpose as `fromHash` — threaded
|
|
424
565
|
* through so the rendered class's `describe()` declares the correct
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Family-agnostic textual preview of a migration plan, used by the CLI to
|
|
3
|
+
* render a "DDL preview" section for `db init` / `db update` / `migration plan`
|
|
4
|
+
* / `migration show`. Each statement carries a free-form `language` tag so
|
|
5
|
+
* formatters can suffix `;` for SQL but render Mongo shell lines verbatim.
|
|
6
|
+
*
|
|
7
|
+
* Producers are family-specific: SQL emits `language: 'sql'` (existing DDL
|
|
8
|
+
* extraction); Mongo emits `language: 'mongodb-shell'` via the
|
|
9
|
+
* `MongoDdlCommandFormatter` visitor.
|
|
10
|
+
*
|
|
11
|
+
* The capability `OperationPreviewCapable` (declared in
|
|
12
|
+
* `./control-capabilities`) is how a family announces it can produce these.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface OperationPreviewStatement {
|
|
16
|
+
readonly text: string;
|
|
17
|
+
/** Dialect identifier, e.g. `'sql'`, `'mongodb-shell'`. Free-form by design (OQ-3). */
|
|
18
|
+
readonly language: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface OperationPreview {
|
|
22
|
+
readonly statements: readonly OperationPreviewStatement[];
|
|
23
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
+
import type { MigrationMetadata, MigrationPlanOperation } from './control-migration-types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Canonical control-plane identifiers for contract spaces.
|
|
6
|
+
*
|
|
7
|
+
* A contract space is the disjoint `(contract.json, migration-graph)` unit
|
|
8
|
+
* the per-space planner / runner / verifier (project: extension contract
|
|
9
|
+
* spaces, TML-2397) operates on. The application owns one well-known
|
|
10
|
+
* space — the value below — and each loaded extension that contributes
|
|
11
|
+
* schema owns a uniquely-named space.
|
|
12
|
+
*
|
|
13
|
+
* Lives in `framework-components/control` so every layer that has to
|
|
14
|
+
* reason about space identity (the migration tooling, the SQL runtime's
|
|
15
|
+
* marker reader, target-side statement builders, target-side adapters)
|
|
16
|
+
* can import a single value rather than duplicating the literal. Raw
|
|
17
|
+
* `'app'` string literals in framework / target / runtime / adapter
|
|
18
|
+
* source code are forbidden and policed by
|
|
19
|
+
* `scripts/lint-app-space-id.mjs` (wired into `pnpm lint:deps`).
|
|
20
|
+
*
|
|
21
|
+
* @see specs/framework-mechanism.spec.md § 3 — Layout convention (γ).
|
|
22
|
+
*/
|
|
23
|
+
export const APP_SPACE_ID = 'app' as const;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Head ref for a contract space — the `(hash, invariants)` tuple
|
|
27
|
+
* a runner targets when applying that space's migration graph. Identical
|
|
28
|
+
* in shape to the on-disk `migrations/<space-id>/refs/head.json` the
|
|
29
|
+
* framework writes per loaded extension, and to the app-space
|
|
30
|
+
* `<projectRoot>/refs/head.json`. Family-agnostic: SQL, Mongo, and any
|
|
31
|
+
* future family share the same head-ref shape.
|
|
32
|
+
*
|
|
33
|
+
* @see specs/framework-mechanism.spec.md § 1.
|
|
34
|
+
*/
|
|
35
|
+
export interface ContractSpaceHeadRef {
|
|
36
|
+
readonly hash: string;
|
|
37
|
+
readonly invariants: readonly string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Canonical structural shape of a migration package — the unit a planner
|
|
42
|
+
* produces and a runner consumes: a directory name, the ADR 197 metadata
|
|
43
|
+
* envelope (which carries the `toContract` snapshot), and the operation
|
|
44
|
+
* list.
|
|
45
|
+
*
|
|
46
|
+
* In-memory by default. Readers in `@prisma-next/migration-tools`
|
|
47
|
+
* (`readMigrationPackage` / `readMigrationsDir`) return the augmented
|
|
48
|
+
* {@link import('@prisma-next/migration-tools/package').OnDiskMigrationPackage}
|
|
49
|
+
* variant which adds `dirPath`; everything else operates against the
|
|
50
|
+
* canonical shape so the same value flows through pre-emission
|
|
51
|
+
* authoring, on-disk loading, and runner execution without conversion.
|
|
52
|
+
*
|
|
53
|
+
* @see specs/framework-mechanism.spec.md § 1.
|
|
54
|
+
*/
|
|
55
|
+
export interface MigrationPackage {
|
|
56
|
+
readonly dirName: string;
|
|
57
|
+
readonly metadata: MigrationMetadata;
|
|
58
|
+
readonly ops: readonly MigrationPlanOperation[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Canonical structural shape of a contract space — one disjoint
|
|
63
|
+
* `(contractJson, migration-graph)` unit the per-space planner / runner
|
|
64
|
+
* / verifier operates on. The application owns one well-known space
|
|
65
|
+
* ({@link APP_SPACE_ID}); each loaded extension that contributes schema
|
|
66
|
+
* owns a uniquely-named space. Whether a value is the app's space or an
|
|
67
|
+
* extension's space is a control-plane concern; the type carries no
|
|
68
|
+
* such distinction.
|
|
69
|
+
*
|
|
70
|
+
* Generic over the contract so each family pins a typed contract value
|
|
71
|
+
* at consumption time. The SQL family specialises to
|
|
72
|
+
* `ContractSpace<Contract<SqlStorage>>` at the descriptor surface;
|
|
73
|
+
* Mongo's symmetrical `ContractSpace<Contract<MongoStorage>>` will land
|
|
74
|
+
* with that family.
|
|
75
|
+
*
|
|
76
|
+
* @see specs/framework-mechanism.spec.md § 1.
|
|
77
|
+
*/
|
|
78
|
+
export interface ContractSpace<TContract extends Contract = Contract> {
|
|
79
|
+
readonly contractJson: TContract;
|
|
80
|
+
readonly migrations: readonly MigrationPackage[];
|
|
81
|
+
readonly headRef: ContractSpaceHeadRef;
|
|
82
|
+
}
|