@prisma-next/framework-components 0.5.0-dev.7 → 0.5.0-dev.70

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.
Files changed (94) hide show
  1. package/README.md +63 -3
  2. package/dist/authoring.d.mts +2 -2
  3. package/dist/authoring.mjs +2 -122
  4. package/dist/codec-m_-FAyQn.d.mts +168 -0
  5. package/dist/codec-m_-FAyQn.d.mts.map +1 -0
  6. package/dist/codec.d.mts +48 -2
  7. package/dist/codec.d.mts.map +1 -0
  8. package/dist/codec.mjs +67 -4
  9. package/dist/codec.mjs.map +1 -1
  10. package/dist/components.d.mts +1 -1
  11. package/dist/components.mjs +2 -3
  12. package/dist/control.d.mts +370 -72
  13. package/dist/control.d.mts.map +1 -1
  14. package/dist/control.mjs +82 -49
  15. package/dist/control.mjs.map +1 -1
  16. package/dist/emission-types-BKa4bR9m.d.mts +39 -0
  17. package/dist/emission-types-BKa4bR9m.d.mts.map +1 -0
  18. package/dist/emission.d.mts +2 -2
  19. package/dist/emission.mjs +1 -1
  20. package/dist/execution.d.mts +5 -5
  21. package/dist/execution.d.mts.map +1 -1
  22. package/dist/execution.mjs +4 -6
  23. package/dist/execution.mjs.map +1 -1
  24. package/dist/framework-authoring-BwWNqTlD.mjs +205 -0
  25. package/dist/framework-authoring-BwWNqTlD.mjs.map +1 -0
  26. package/dist/{framework-authoring-D1-JZ37B.d.mts → framework-authoring-Cm5f9U64.d.mts} +41 -12
  27. package/dist/framework-authoring-Cm5f9U64.d.mts.map +1 -0
  28. package/dist/{framework-components-EJXe-pum.d.mts → framework-components-DgIEy9eJ.d.mts} +45 -55
  29. package/dist/framework-components-DgIEy9eJ.d.mts.map +1 -0
  30. package/dist/{framework-components-C8ZhSwXe.mjs → framework-components-FdqmlGUj.mjs} +3 -3
  31. package/dist/framework-components-FdqmlGUj.mjs.map +1 -0
  32. package/dist/psl-ast-Ckn_G-jv.d.mts +159 -0
  33. package/dist/psl-ast-Ckn_G-jv.d.mts.map +1 -0
  34. package/dist/psl-ast.d.mts +2 -0
  35. package/dist/psl-ast.mjs +1 -0
  36. package/dist/runtime.d.mts +346 -19
  37. package/dist/runtime.d.mts.map +1 -1
  38. package/dist/runtime.mjs +254 -7
  39. package/dist/runtime.mjs.map +1 -1
  40. package/dist/{types-import-spec-C4sc7wbb.d.mts → types-import-spec-BxI5cSQy.d.mts} +2 -2
  41. package/dist/types-import-spec-BxI5cSQy.d.mts.map +1 -0
  42. package/package.json +11 -8
  43. package/src/control/control-capabilities.ts +95 -0
  44. package/src/{control-descriptors.ts → control/control-descriptors.ts} +7 -7
  45. package/src/{control-instances.ts → control/control-instances.ts} +52 -6
  46. package/src/{control-migration-types.ts → control/control-migration-types.ts} +202 -61
  47. package/src/control/control-operation-preview.ts +23 -0
  48. package/src/control/control-spaces.ts +82 -0
  49. package/src/{control-stack.ts → control/control-stack.ts} +77 -94
  50. package/src/control/emission-types.ts +49 -0
  51. package/src/control/psl-ast.ts +193 -0
  52. package/src/{execution-descriptors.ts → execution/execution-descriptors.ts} +7 -7
  53. package/src/{execution-instances.ts → execution/execution-instances.ts} +1 -1
  54. package/src/{execution-requirements.ts → execution/execution-requirements.ts} +1 -1
  55. package/src/execution/query-plan.ts +53 -0
  56. package/src/execution/race-against-abort.ts +85 -0
  57. package/src/execution/run-with-middleware.ts +132 -0
  58. package/src/execution/runtime-core.ts +133 -0
  59. package/src/execution/runtime-error.ts +83 -0
  60. package/src/execution/runtime-middleware.ts +182 -0
  61. package/src/exports/authoring.ts +5 -2
  62. package/src/exports/codec.ts +27 -2
  63. package/src/exports/components.ts +2 -2
  64. package/src/exports/control.ts +40 -13
  65. package/src/exports/emission.ts +2 -2
  66. package/src/exports/execution.ts +5 -5
  67. package/src/exports/psl-ast.ts +1 -0
  68. package/src/exports/runtime.ts +17 -5
  69. package/src/shared/codec-descriptor.ts +87 -0
  70. package/src/shared/codec-types.ts +79 -0
  71. package/src/shared/codec.ts +80 -0
  72. package/src/shared/column-spec.ts +83 -0
  73. package/src/{framework-authoring.ts → shared/framework-authoring.ts} +202 -23
  74. package/src/{framework-components.ts → shared/framework-components.ts} +22 -48
  75. package/src/{mutation-default-types.ts → shared/mutation-default-types.ts} +22 -2
  76. package/dist/authoring.mjs.map +0 -1
  77. package/dist/codec-types-B58nCJiu.d.mts +0 -40
  78. package/dist/codec-types-B58nCJiu.d.mts.map +0 -1
  79. package/dist/emission-types-BPAALJbF.d.mts +0 -24
  80. package/dist/emission-types-BPAALJbF.d.mts.map +0 -1
  81. package/dist/framework-authoring-D1-JZ37B.d.mts.map +0 -1
  82. package/dist/framework-components-C8ZhSwXe.mjs.map +0 -1
  83. package/dist/framework-components-EJXe-pum.d.mts.map +0 -1
  84. package/dist/types-import-spec-C4sc7wbb.d.mts.map +0 -1
  85. package/src/codec-types.ts +0 -46
  86. package/src/control-capabilities.ts +0 -34
  87. package/src/emission-types.ts +0 -28
  88. package/src/runtime-error.ts +0 -39
  89. package/src/runtime-middleware.ts +0 -83
  90. /package/src/{control-result-types.ts → control/control-result-types.ts} +0 -0
  91. /package/src/{control-schema-view.ts → control/control-schema-view.ts} +0 -0
  92. /package/src/{async-iterable-result.ts → execution/async-iterable-result.ts} +0 -0
  93. /package/src/{execution-stack.ts → execution/execution-stack.ts} +0 -0
  94. /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 './framework-components';
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
- import type { TargetBoundComponentDescriptor } from './framework-components';
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
- // Data Transform Operation
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). Planners pass this to data-safety strategies so they can compare
302
- * `from` and `to` column shapes (e.g. to detect unsafe type changes).
303
- * `db update` / `db init` reconcile against the live schema and have no
304
- * "from" contract; only `migration plan` provides one.
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?: unknown;
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(context: MigrationScaffoldContext): MigrationPlanWithAuthoringSurface;
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. Targets use this to populate
418
- * `describe()` on the rendered empty migration so that identity metadata
419
- * is correctly populated.
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
+ }