@prisma-next/cli 0.5.0-dev.9 → 0.6.0-dev.1
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 +61 -26
- package/dist/cli-errors-B9OBbled.d.mts +3 -0
- package/dist/cli-errors-D3_sMh2K.mjs +33 -0
- package/dist/cli-errors-D3_sMh2K.mjs.map +1 -0
- package/dist/cli.mjs +16 -78
- package/dist/cli.mjs.map +1 -1
- package/dist/client-qVH-rEgd.mjs +1595 -0
- package/dist/client-qVH-rEgd.mjs.map +1 -0
- package/dist/{result-handler-Ba3zWQsI.mjs → command-helpers-BeZHkxV8.mjs} +70 -47
- package/dist/command-helpers-BeZHkxV8.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +2 -4
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +2 -4
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +16 -13
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.d.mts.map +1 -1
- package/dist/commands/db-schema.mjs +6 -7
- package/dist/commands/db-schema.mjs.map +1 -1
- package/dist/commands/db-sign.d.mts.map +1 -1
- package/dist/commands/db-sign.mjs +9 -9
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.d.mts.map +1 -1
- package/dist/commands/db-update.mjs +15 -13
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.d.mts.map +1 -1
- package/dist/commands/db-verify.mjs +1 -321
- package/dist/commands/migration-apply.d.mts +28 -13
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +55 -151
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.d.mts +0 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +34 -40
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +33 -6
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +2 -348
- package/dist/commands/migration-ref.d.mts +1 -1
- package/dist/commands/migration-ref.d.mts.map +1 -1
- package/dist/commands/migration-ref.mjs +8 -12
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +13 -7
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +35 -36
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +126 -5
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +2 -4
- package/dist/{config-loader-C25b63rJ.mjs → config-loader-B6sJjXTv.mjs} +3 -5
- package/dist/config-loader-B6sJjXTv.mjs.map +1 -0
- package/dist/config-loader.d.mts +0 -1
- package/dist/config-loader.d.mts.map +1 -1
- package/dist/config-loader.mjs +2 -3
- package/dist/contract-emit-9DBda5Ou.mjs +150 -0
- package/dist/contract-emit-9DBda5Ou.mjs.map +1 -0
- package/dist/contract-emit-B77TsJqf.mjs +327 -0
- package/dist/contract-emit-B77TsJqf.mjs.map +1 -0
- package/dist/{contract-enrichment-CAOELa-H.mjs → contract-enrichment-Dani0mMW.mjs} +4 -6
- package/dist/contract-enrichment-Dani0mMW.mjs.map +1 -0
- package/dist/{contract-infer-D9cC3rJm.mjs → contract-infer-BK9YFGEG.mjs} +13 -22
- package/dist/contract-infer-BK9YFGEG.mjs.map +1 -0
- package/dist/db-verify-C0y1PCO2.mjs +404 -0
- package/dist/db-verify-C0y1PCO2.mjs.map +1 -0
- package/dist/exports/config-types.mjs +1 -2
- package/dist/exports/control-api.d.mts +101 -586
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +4 -6
- package/dist/exports/index.d.mts.map +1 -1
- package/dist/exports/index.mjs +28 -30
- package/dist/exports/index.mjs.map +1 -1
- package/dist/exports/init-output.d.mts +2 -4
- package/dist/exports/init-output.d.mts.map +1 -1
- package/dist/exports/init-output.mjs +2 -3
- package/dist/extension-pack-inputs-C7xgE-vv.mjs +74 -0
- package/dist/extension-pack-inputs-C7xgE-vv.mjs.map +1 -0
- package/dist/{framework-components-Cr--XBKy.mjs → framework-components-ChqVUxR-.mjs} +3 -4
- package/dist/{framework-components-Cr--XBKy.mjs.map → framework-components-ChqVUxR-.mjs.map} +1 -1
- package/dist/global-flags-Icqpxk23.d.mts +12 -0
- package/dist/global-flags-Icqpxk23.d.mts.map +1 -0
- package/dist/helpers-eqdN8tH6.mjs +25 -0
- package/dist/helpers-eqdN8tH6.mjs.map +1 -0
- package/dist/{init-C5220SY9.mjs → init-DETSgw3h.mjs} +40 -49
- package/dist/init-DETSgw3h.mjs.map +1 -0
- package/dist/{inspect-live-schema-yrHAvG71.mjs → inspect-live-schema-CWYxGKlb.mjs} +10 -11
- package/dist/inspect-live-schema-CWYxGKlb.mjs.map +1 -0
- package/dist/migration-cli.d.mts +41 -12
- package/dist/migration-cli.d.mts.map +1 -1
- package/dist/migration-cli.mjs +309 -86
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-B3B09et6.mjs → migration-command-scaffold-B5dORFEv.mjs} +8 -9
- package/dist/migration-command-scaffold-B5dORFEv.mjs.map +1 -0
- package/dist/migration-plan-C6lVaHsO.mjs +554 -0
- package/dist/migration-plan-C6lVaHsO.mjs.map +1 -0
- package/dist/{migration-status-DUMiH8_G.mjs → migration-status-CZ-D5k7k.mjs} +272 -65
- package/dist/migration-status-CZ-D5k7k.mjs.map +1 -0
- package/dist/migrations-D_UJnpuW.mjs +216 -0
- package/dist/migrations-D_UJnpuW.mjs.map +1 -0
- package/dist/{output-BpcQrnnq.mjs → output-B16Kefzx.mjs} +9 -3
- package/dist/output-B16Kefzx.mjs.map +1 -0
- package/dist/{progress-adapter-DvQWB1nK.mjs → progress-adapter-DFfvZcYL.mjs} +2 -2
- package/dist/{progress-adapter-DvQWB1nK.mjs.map → progress-adapter-DFfvZcYL.mjs.map} +1 -1
- package/dist/result-handler-rmPVKIP2.mjs +25 -0
- package/dist/result-handler-rmPVKIP2.mjs.map +1 -0
- package/dist/rolldown-runtime-twds-ZHy.mjs +14 -0
- package/dist/{terminal-ui-C3ZLwQxK.mjs → terminal-ui-C_hFNbAn.mjs} +4 -28
- package/dist/terminal-ui-C_hFNbAn.mjs.map +1 -0
- package/dist/types-D7x-IFLO.d.mts +858 -0
- package/dist/types-D7x-IFLO.d.mts.map +1 -0
- package/dist/{verify-Bkycc-Tf.mjs → verify-CiwNWM9N.mjs} +3 -4
- package/dist/verify-CiwNWM9N.mjs.map +1 -0
- package/package.json +28 -26
- package/src/cli.ts +32 -6
- package/src/commands/contract-emit.ts +67 -163
- package/src/commands/contract-infer.ts +7 -20
- package/src/commands/db-init.ts +15 -3
- package/src/commands/db-update.ts +9 -4
- package/src/commands/db-verify.ts +47 -15
- package/src/commands/init/index.ts +1 -1
- package/src/commands/init/init.ts +2 -2
- package/src/commands/init/templates/code-templates.ts +26 -18
- package/src/commands/inspect-live-schema.ts +10 -5
- package/src/commands/migration-apply.ts +114 -212
- package/src/commands/migration-new.ts +42 -45
- package/src/commands/migration-plan.ts +212 -72
- package/src/commands/migration-ref.ts +8 -7
- package/src/commands/migration-show.ts +60 -41
- package/src/commands/migration-status.ts +483 -64
- package/src/config-path-validation.ts +0 -1
- package/src/control-api/client.ts +85 -5
- package/src/control-api/contract-enrichment.ts +6 -4
- package/src/control-api/operations/apply-aggregate.ts +290 -0
- package/src/control-api/operations/contract-emit.ts +198 -115
- package/src/control-api/operations/db-apply-aggregate.ts +397 -0
- package/src/control-api/operations/db-init.ts +51 -253
- package/src/control-api/operations/db-update.ts +66 -183
- package/src/control-api/operations/db-verify.ts +342 -0
- package/src/control-api/operations/migration-apply.ts +424 -131
- package/src/control-api/types.ts +280 -29
- package/src/exports/control-api.ts +15 -3
- package/src/load-ts-contract.ts +28 -26
- package/src/migration-cli.ts +445 -122
- package/src/utils/cli-errors.ts +49 -2
- package/src/utils/combine-schema-results.ts +84 -0
- package/src/utils/command-helpers.ts +69 -25
- package/src/utils/contract-space-aggregate-loader.ts +204 -0
- package/src/utils/contract-space-extension-migrations-pass.ts +120 -0
- package/src/utils/contract-space-migrate-pass.ts +156 -0
- package/src/utils/emit-queue.ts +26 -0
- package/src/utils/extension-pack-inputs.ts +170 -0
- package/src/utils/formatters/graph-migration-mapper.ts +7 -3
- package/src/utils/formatters/migrations.ts +197 -61
- package/src/utils/publish-contract-artifact-pair.ts +134 -0
- package/dist/cli-errors-BFYgBH3L.d.mts +0 -4
- package/dist/cli-errors-Cd79vmTH.mjs +0 -5
- package/dist/client-CrsnY58k.mjs +0 -997
- package/dist/client-CrsnY58k.mjs.map +0 -1
- package/dist/commands/db-verify.mjs.map +0 -1
- package/dist/commands/migration-plan.mjs.map +0 -1
- package/dist/config-loader-C25b63rJ.mjs.map +0 -1
- package/dist/contract-emit--feXyNd7.mjs +0 -4
- package/dist/contract-emit-NJ01hiiv.mjs +0 -195
- package/dist/contract-emit-NJ01hiiv.mjs.map +0 -1
- package/dist/contract-emit-V5SSitUT.mjs +0 -122
- package/dist/contract-emit-V5SSitUT.mjs.map +0 -1
- package/dist/contract-enrichment-CAOELa-H.mjs.map +0 -1
- package/dist/contract-infer-D9cC3rJm.mjs.map +0 -1
- package/dist/extract-operation-statements-DsFfxXVZ.mjs +0 -13
- package/dist/extract-operation-statements-DsFfxXVZ.mjs.map +0 -1
- package/dist/extract-sql-ddl-D9UbZDyz.mjs +0 -26
- package/dist/extract-sql-ddl-D9UbZDyz.mjs.map +0 -1
- package/dist/init-C5220SY9.mjs.map +0 -1
- package/dist/inspect-live-schema-yrHAvG71.mjs.map +0 -1
- package/dist/migration-command-scaffold-B3B09et6.mjs.map +0 -1
- package/dist/migration-status-DUMiH8_G.mjs.map +0 -1
- package/dist/migrations-Bo5WtTla.mjs +0 -153
- package/dist/migrations-Bo5WtTla.mjs.map +0 -1
- package/dist/output-BpcQrnnq.mjs.map +0 -1
- package/dist/result-handler-Ba3zWQsI.mjs.map +0 -1
- package/dist/terminal-ui-C3ZLwQxK.mjs.map +0 -1
- package/dist/validate-contract-deps-B_Cs29TL.mjs +0 -37
- package/dist/validate-contract-deps-B_Cs29TL.mjs.map +0 -1
- package/dist/verify-Bkycc-Tf.mjs.map +0 -1
- package/src/control-api/operations/extract-operation-statements.ts +0 -14
- package/src/control-api/operations/extract-sql-ddl.ts +0 -47
|
@@ -57,7 +57,6 @@ export function finalizeConfig(config: PrismaNextConfig, configDir: string): Pri
|
|
|
57
57
|
if (!config.contract) {
|
|
58
58
|
return config;
|
|
59
59
|
}
|
|
60
|
-
|
|
61
60
|
const contract = normalizeContractConfig(config.contract);
|
|
62
61
|
const source = finalizeContractSource(contract.source, configDir);
|
|
63
62
|
const output = resolve(configDir, contract.output);
|
|
@@ -6,15 +6,21 @@ import type {
|
|
|
6
6
|
ControlFamilyInstance,
|
|
7
7
|
ControlStack,
|
|
8
8
|
CoreSchemaView,
|
|
9
|
+
MigrationPlanOperation,
|
|
10
|
+
OperationPreview,
|
|
9
11
|
SignDatabaseResult,
|
|
10
12
|
VerifyDatabaseResult,
|
|
11
13
|
VerifyDatabaseSchemaResult,
|
|
12
14
|
} from '@prisma-next/framework-components/control';
|
|
13
15
|
import {
|
|
16
|
+
APP_SPACE_ID,
|
|
14
17
|
createControlStack,
|
|
15
18
|
hasMigrations,
|
|
19
|
+
hasOperationPreview,
|
|
20
|
+
hasPslContractInfer,
|
|
16
21
|
hasSchemaView,
|
|
17
22
|
} from '@prisma-next/framework-components/control';
|
|
23
|
+
import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
|
|
18
24
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
19
25
|
import { notOk, ok } from '@prisma-next/utils/result';
|
|
20
26
|
import { assertFrameworkComponentsCompatible } from '../utils/framework-components';
|
|
@@ -22,7 +28,9 @@ import { enrichContract } from './contract-enrichment';
|
|
|
22
28
|
import { ContractValidationError } from './errors';
|
|
23
29
|
import { executeDbInit } from './operations/db-init';
|
|
24
30
|
import { executeDbUpdate } from './operations/db-update';
|
|
31
|
+
import { type ExecuteDbVerifyResult, executeDbVerify } from './operations/db-verify';
|
|
25
32
|
import { executeMigrationApply } from './operations/migration-apply';
|
|
33
|
+
|
|
26
34
|
import type {
|
|
27
35
|
ControlActionName,
|
|
28
36
|
ControlClient,
|
|
@@ -31,6 +39,7 @@ import type {
|
|
|
31
39
|
DbInitResult,
|
|
32
40
|
DbUpdateOptions,
|
|
33
41
|
DbUpdateResult,
|
|
42
|
+
DbVerifyOptions,
|
|
34
43
|
EmitOptions,
|
|
35
44
|
EmitResult,
|
|
36
45
|
IntrospectOptions,
|
|
@@ -362,6 +371,9 @@ class ControlClientImpl implements ControlClient {
|
|
|
362
371
|
mode: options.mode,
|
|
363
372
|
migrations: this.options.target.migrations,
|
|
364
373
|
frameworkComponents,
|
|
374
|
+
migrationsDir: options.migrationsDir,
|
|
375
|
+
targetId: this.options.target.targetId,
|
|
376
|
+
extensionPacks: this.options.extensionPacks ?? [],
|
|
365
377
|
...ifDefined('onProgress', onProgress),
|
|
366
378
|
});
|
|
367
379
|
}
|
|
@@ -390,14 +402,54 @@ class ControlClientImpl implements ControlClient {
|
|
|
390
402
|
mode: options.mode,
|
|
391
403
|
migrations: this.options.target.migrations,
|
|
392
404
|
frameworkComponents,
|
|
405
|
+
migrationsDir: options.migrationsDir,
|
|
406
|
+
targetId: this.options.target.targetId,
|
|
407
|
+
extensionPacks: this.options.extensionPacks ?? [],
|
|
393
408
|
...ifDefined('acceptDataLoss', options.acceptDataLoss),
|
|
394
409
|
...ifDefined('onProgress', onProgress),
|
|
395
410
|
});
|
|
396
411
|
}
|
|
397
412
|
|
|
413
|
+
async dbVerify(options: DbVerifyOptions): Promise<ExecuteDbVerifyResult> {
|
|
414
|
+
const { onProgress } = options;
|
|
415
|
+
await this.connectWithProgress(options.connection, 'dbVerify', onProgress);
|
|
416
|
+
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
417
|
+
|
|
418
|
+
let contract: Contract;
|
|
419
|
+
try {
|
|
420
|
+
contract = familyInstance.validateContract(options.contract);
|
|
421
|
+
} catch (error) {
|
|
422
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
423
|
+
throw new ContractValidationError(message, error);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return executeDbVerify({
|
|
427
|
+
driver,
|
|
428
|
+
familyInstance,
|
|
429
|
+
contract,
|
|
430
|
+
migrationsDir: options.migrationsDir,
|
|
431
|
+
targetId: this.options.target.targetId,
|
|
432
|
+
extensionPacks: this.options.extensionPacks ?? [],
|
|
433
|
+
frameworkComponents,
|
|
434
|
+
mode: options.strict ? 'strict' : 'lenient',
|
|
435
|
+
skipSchema: options.skipSchema,
|
|
436
|
+
skipMarker: options.skipMarker,
|
|
437
|
+
...ifDefined('onProgress', onProgress),
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
398
441
|
async readMarker(): Promise<ContractMarkerRecord | null> {
|
|
399
442
|
const { driver, familyInstance } = await this.ensureConnected();
|
|
400
|
-
|
|
443
|
+
// The CLI client's readMarker reads the app's marker. Per-extension
|
|
444
|
+
// readers go through the orchestrator's per-space planner / runner
|
|
445
|
+
// boundary, which threads the extension's space id through the
|
|
446
|
+
// family interface explicitly.
|
|
447
|
+
return familyInstance.readMarker({ driver, space: APP_SPACE_ID });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async readAllMarkers(): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
|
|
451
|
+
const { driver, familyInstance } = await this.ensureConnected();
|
|
452
|
+
return familyInstance.readAllMarkers({ driver });
|
|
401
453
|
}
|
|
402
454
|
|
|
403
455
|
async migrationApply(options: MigrationApplyOptions): Promise<MigrationApplyResult> {
|
|
@@ -409,16 +461,28 @@ class ControlClientImpl implements ControlClient {
|
|
|
409
461
|
throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
|
|
410
462
|
}
|
|
411
463
|
|
|
464
|
+
let contract: Contract;
|
|
465
|
+
try {
|
|
466
|
+
contract = familyInstance.validateContract(options.contract);
|
|
467
|
+
} catch (error) {
|
|
468
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
469
|
+
throw new ContractValidationError(message, error);
|
|
470
|
+
}
|
|
471
|
+
|
|
412
472
|
return executeMigrationApply({
|
|
413
473
|
driver,
|
|
414
474
|
familyInstance,
|
|
415
|
-
|
|
416
|
-
destinationHash: options.destinationHash,
|
|
417
|
-
pendingMigrations: options.pendingMigrations,
|
|
475
|
+
contract,
|
|
418
476
|
migrations: this.options.target.migrations,
|
|
419
477
|
frameworkComponents,
|
|
478
|
+
migrationsDir: options.migrationsDir,
|
|
479
|
+
extensionPacks: this.options.extensionPacks ?? [],
|
|
420
480
|
targetId: this.options.target.targetId,
|
|
421
|
-
|
|
481
|
+
appMigrationPackages: options.appMigrationPackages,
|
|
482
|
+
...ifDefined('refHash', options.refHash),
|
|
483
|
+
...ifDefined('refInvariants', options.refInvariants),
|
|
484
|
+
...ifDefined('refName', options.refName),
|
|
485
|
+
...ifDefined('onProgress', onProgress),
|
|
422
486
|
});
|
|
423
487
|
}
|
|
424
488
|
|
|
@@ -469,6 +533,22 @@ class ControlClientImpl implements ControlClient {
|
|
|
469
533
|
return undefined;
|
|
470
534
|
}
|
|
471
535
|
|
|
536
|
+
inferPslContract(schemaIR: unknown): PslDocumentAst | undefined {
|
|
537
|
+
this.init();
|
|
538
|
+
if (this.familyInstance && hasPslContractInfer(this.familyInstance)) {
|
|
539
|
+
return this.familyInstance.inferPslContract(schemaIR);
|
|
540
|
+
}
|
|
541
|
+
return undefined;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview | undefined {
|
|
545
|
+
this.init();
|
|
546
|
+
if (this.familyInstance && hasOperationPreview(this.familyInstance)) {
|
|
547
|
+
return this.familyInstance.toOperationPreview(operations);
|
|
548
|
+
}
|
|
549
|
+
return undefined;
|
|
550
|
+
}
|
|
551
|
+
|
|
472
552
|
async emit(options: EmitOptions): Promise<EmitResult> {
|
|
473
553
|
const { onProgress, contractConfig } = options;
|
|
474
554
|
|
|
@@ -73,7 +73,11 @@ function extractExtensionPackMeta(
|
|
|
73
73
|
}
|
|
74
74
|
if (types) {
|
|
75
75
|
if (types.codecTypes) {
|
|
76
|
-
const {
|
|
76
|
+
const {
|
|
77
|
+
controlPlaneHooks: _,
|
|
78
|
+
codecDescriptors: _cd,
|
|
79
|
+
...cleanedCodecTypes
|
|
80
|
+
} = types.codecTypes;
|
|
77
81
|
base['types'] = { ...types, codecTypes: cleanedCodecTypes };
|
|
78
82
|
} else {
|
|
79
83
|
base['types'] = types;
|
|
@@ -83,9 +87,7 @@ function extractExtensionPackMeta(
|
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
/**
|
|
86
|
-
* Enriches a raw contract with framework-derived metadata:
|
|
87
|
-
* capabilities from all component descriptors and extension pack metadata
|
|
88
|
-
* from extension descriptors. Produces deterministically sorted output.
|
|
90
|
+
* Enriches a raw contract with framework-derived metadata: capabilities from all component descriptors and extension pack metadata from extension descriptors. Produces deterministically sorted output.
|
|
89
91
|
*/
|
|
90
92
|
export function enrichContract(
|
|
91
93
|
ir: Contract,
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
2
|
+
import type {
|
|
3
|
+
ControlDriverInstance,
|
|
4
|
+
ControlFamilyInstance,
|
|
5
|
+
MigrationOperationPolicy,
|
|
6
|
+
MultiSpaceCapableRunner,
|
|
7
|
+
MultiSpaceRunnerPerSpaceOptions,
|
|
8
|
+
TargetMigrationsCapability,
|
|
9
|
+
} from '@prisma-next/framework-components/control';
|
|
10
|
+
import { hasMultiSpaceRunner } from '@prisma-next/framework-components/control';
|
|
11
|
+
import type {
|
|
12
|
+
AggregatePerSpacePlan,
|
|
13
|
+
ContractSpaceAggregate,
|
|
14
|
+
} from '@prisma-next/migration-tools/aggregate';
|
|
15
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
16
|
+
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
17
|
+
import { errorRunnerFailed } from '../../utils/cli-errors';
|
|
18
|
+
import type { AggregatePerSpaceExecutionEntry, OnControlProgress } from '../types';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Span id emitted via `onProgress` for the apply phase. Stable
|
|
22
|
+
* identifier consumed by the structured-output renderer and by tests.
|
|
23
|
+
*/
|
|
24
|
+
const APPLY_SPAN_ID = 'apply' as const;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Action that originated this apply call. Threaded into `OnControlProgress`
|
|
28
|
+
* events so the parent CLI command can attribute the span correctly,
|
|
29
|
+
* and used to compose action-specific summary phrasing.
|
|
30
|
+
*/
|
|
31
|
+
export type AggregateApplyAction = 'dbInit' | 'dbUpdate' | 'migrationApply';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Failure variant emitted by {@link applyAggregate} when the multi-space
|
|
35
|
+
* runner itself rejects the apply. Mirrors the failure shape callers
|
|
36
|
+
* already wrap into their own action-specific failure envelopes
|
|
37
|
+
* (`DbInitFailure`, `DbUpdateFailure`, `MigrationApplyFailure`) so each
|
|
38
|
+
* caller keeps owning its own discriminated failure code.
|
|
39
|
+
*/
|
|
40
|
+
export interface AggregateApplyRunnerFailure {
|
|
41
|
+
readonly summary: string;
|
|
42
|
+
readonly why?: string;
|
|
43
|
+
readonly meta: Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ApplyAggregateInputs<TFamilyId extends string, TTargetId extends string> {
|
|
47
|
+
readonly aggregate: ContractSpaceAggregate;
|
|
48
|
+
/**
|
|
49
|
+
* Per-space plans, keyed by `spaceId`. Produced by either the full
|
|
50
|
+
* {@link planAggregate} pipeline (`db init` / `db update` — synth
|
|
51
|
+
* for the app, graph-walk for extensions) or by direct
|
|
52
|
+
* {@link graphWalkStrategy} calls (`migration apply` — graph-walk
|
|
53
|
+
* for every member). Either way, the runner consumes the same shape.
|
|
54
|
+
*/
|
|
55
|
+
readonly perSpacePlans: ReadonlyMap<string, AggregatePerSpacePlan>;
|
|
56
|
+
/**
|
|
57
|
+
* Canonical schedule order — extensions alphabetically by `spaceId`,
|
|
58
|
+
* then app. Mirrors {@link import('@prisma-next/migration-tools/concatenate-space-apply-inputs').concatenateSpaceApplyInputs}'s
|
|
59
|
+
* convention so `MultiSpaceRunnerFailure.failingSpace` attribution
|
|
60
|
+
* stays byte-for-byte stable across callers.
|
|
61
|
+
*/
|
|
62
|
+
readonly applyOrder: readonly string[];
|
|
63
|
+
readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;
|
|
64
|
+
readonly familyInstance: ControlFamilyInstance<TFamilyId, unknown>;
|
|
65
|
+
readonly migrations: TargetMigrationsCapability<
|
|
66
|
+
TFamilyId,
|
|
67
|
+
TTargetId,
|
|
68
|
+
ControlFamilyInstance<TFamilyId, unknown>
|
|
69
|
+
>;
|
|
70
|
+
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;
|
|
71
|
+
readonly policy: MigrationOperationPolicy;
|
|
72
|
+
readonly action: AggregateApplyAction;
|
|
73
|
+
readonly onProgress?: OnControlProgress;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Resolved per-space plan in canonical schedule order. Surfaced from
|
|
78
|
+
* {@link applyAggregate} to callers so each one can build its own
|
|
79
|
+
* action-specific success envelope (e.g. `DbInitSuccess` vs
|
|
80
|
+
* `MigrationApplySuccess`) without re-deriving the ordering.
|
|
81
|
+
*/
|
|
82
|
+
export interface OrderedResolution {
|
|
83
|
+
readonly spaceId: string;
|
|
84
|
+
readonly entry: AggregatePerSpacePlan;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ApplyAggregateValue {
|
|
88
|
+
readonly orderedResolutions: readonly OrderedResolution[];
|
|
89
|
+
readonly totalOpsPlanned: number;
|
|
90
|
+
readonly totalOpsExecuted: number;
|
|
91
|
+
/**
|
|
92
|
+
* Per-space breakdown ready to thread into action-specific success
|
|
93
|
+
* envelopes. Each entry carries the post-apply marker (live storage hash
|
|
94
|
+
* plus invariants) so callers can render it directly without re-reading.
|
|
95
|
+
*/
|
|
96
|
+
readonly perSpace: readonly AggregatePerSpaceExecutionEntry[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type ApplyAggregateResult = Result<ApplyAggregateValue, AggregateApplyRunnerFailure>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Runner-driving tail shared by every aggregate apply caller — `db init`,
|
|
103
|
+
* `db update`, and `migration apply`. Consumes already-resolved per-space
|
|
104
|
+
* plans (the planner-vs-replay distinction is owned by the caller) and
|
|
105
|
+
* dispatches them to the multi-space runner in canonical order.
|
|
106
|
+
*
|
|
107
|
+
* Marker advancement is part of the runner's per-space transaction
|
|
108
|
+
* (the SQL family runner writes the marker as the last step of each
|
|
109
|
+
* space's transaction), so this primitive does not advance markers
|
|
110
|
+
* separately — by the time `executeAcrossSpaces` returns ok, every
|
|
111
|
+
* space's marker has been advanced to its plan's destination.
|
|
112
|
+
*
|
|
113
|
+
* Span emission (`spanStart 'apply'` / `spanEnd 'apply'`) is owned here
|
|
114
|
+
* so callers don't have to duplicate it; the `action` field on each
|
|
115
|
+
* progress event is taken from the caller's `action` argument.
|
|
116
|
+
*/
|
|
117
|
+
export async function applyAggregate<TFamilyId extends string, TTargetId extends string>(
|
|
118
|
+
inputs: ApplyAggregateInputs<TFamilyId, TTargetId>,
|
|
119
|
+
): Promise<ApplyAggregateResult> {
|
|
120
|
+
const {
|
|
121
|
+
aggregate,
|
|
122
|
+
perSpacePlans,
|
|
123
|
+
applyOrder,
|
|
124
|
+
driver,
|
|
125
|
+
familyInstance,
|
|
126
|
+
migrations,
|
|
127
|
+
frameworkComponents,
|
|
128
|
+
policy,
|
|
129
|
+
action,
|
|
130
|
+
onProgress,
|
|
131
|
+
} = inputs;
|
|
132
|
+
|
|
133
|
+
const orderedResolutions = collectOrdered(applyOrder, perSpacePlans);
|
|
134
|
+
|
|
135
|
+
const runner = migrations.createRunner(familyInstance);
|
|
136
|
+
if (!hasMultiSpaceRunner(runner)) {
|
|
137
|
+
throw errorRunnerFailed(
|
|
138
|
+
`Runner for target "${aggregate.targetId}" does not implement \`executeAcrossSpaces\``,
|
|
139
|
+
{
|
|
140
|
+
why: `${labelForAction(action)} requires multi-space-capable runners (today: every SQL family runner).`,
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
onProgress?.({
|
|
146
|
+
action,
|
|
147
|
+
kind: 'spanStart',
|
|
148
|
+
spanId: APPLY_SPAN_ID,
|
|
149
|
+
label: progressLabelForAction(action),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const perSpaceOptions: MultiSpaceRunnerPerSpaceOptions<TFamilyId, TTargetId>[] =
|
|
153
|
+
orderedResolutions.map((r) => ({
|
|
154
|
+
space: r.spaceId,
|
|
155
|
+
plan: r.entry.plan,
|
|
156
|
+
driver,
|
|
157
|
+
destinationContract: r.entry.destinationContract,
|
|
158
|
+
policy,
|
|
159
|
+
frameworkComponents,
|
|
160
|
+
// Per-space post-apply schema verification is non-strict: each
|
|
161
|
+
// space's `destinationContract` describes only its own slice; a
|
|
162
|
+
// strict verifier would treat every other space's tables as
|
|
163
|
+
// `extras`. Tolerant mode still catches missing tables / columns.
|
|
164
|
+
// SQL family runners read `strictVerification` via structural
|
|
165
|
+
// typing.
|
|
166
|
+
strictVerification: false,
|
|
167
|
+
})) as MultiSpaceRunnerPerSpaceOptions<TFamilyId, TTargetId>[];
|
|
168
|
+
|
|
169
|
+
const runnerResult = await (
|
|
170
|
+
runner as MultiSpaceCapableRunner<TFamilyId, TTargetId>
|
|
171
|
+
).executeAcrossSpaces({ driver, perSpaceOptions });
|
|
172
|
+
|
|
173
|
+
if (!runnerResult.ok) {
|
|
174
|
+
onProgress?.({ action, kind: 'spanEnd', spanId: APPLY_SPAN_ID, outcome: 'error' });
|
|
175
|
+
return notOk({
|
|
176
|
+
summary: runnerResult.failure.summary,
|
|
177
|
+
...ifDefined('why', runnerResult.failure.why),
|
|
178
|
+
meta: {
|
|
179
|
+
...(runnerResult.failure.meta ?? {}),
|
|
180
|
+
failingSpace: runnerResult.failure.failingSpace,
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
onProgress?.({ action, kind: 'spanEnd', spanId: APPLY_SPAN_ID, outcome: 'ok' });
|
|
185
|
+
|
|
186
|
+
const totalOpsPlanned = runnerResult.value.perSpaceResults.reduce(
|
|
187
|
+
(sum, r) => sum + r.value.operationsPlanned,
|
|
188
|
+
0,
|
|
189
|
+
);
|
|
190
|
+
const totalOpsExecuted = runnerResult.value.perSpaceResults.reduce(
|
|
191
|
+
(sum, r) => sum + r.value.operationsExecuted,
|
|
192
|
+
0,
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const perSpace = buildPerSpaceBreakdown(orderedResolutions, aggregate.app.spaceId, {
|
|
196
|
+
includeMarkers: true,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return ok({
|
|
200
|
+
orderedResolutions,
|
|
201
|
+
totalOpsPlanned,
|
|
202
|
+
totalOpsExecuted,
|
|
203
|
+
perSpace,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Project the planner's per-space resolutions into the
|
|
209
|
+
* `AggregatePerSpaceExecutionEntry[]` shape the CLI surfaces.
|
|
210
|
+
*
|
|
211
|
+
* `includeMarkers` is `true` for apply-mode (each space's marker is
|
|
212
|
+
* the `destination.storageHash` of its plan, which the runner
|
|
213
|
+
* advances as the last step of each space's transaction) and `false`
|
|
214
|
+
* for plan-mode (no marker has been written yet).
|
|
215
|
+
*
|
|
216
|
+
* Exported alongside {@link applyAggregate} so plan-mode callers can
|
|
217
|
+
* assemble the same per-space block without going through the runner.
|
|
218
|
+
*/
|
|
219
|
+
export function buildPerSpaceBreakdown(
|
|
220
|
+
orderedResolutions: readonly OrderedResolution[],
|
|
221
|
+
appSpaceId: string,
|
|
222
|
+
options: { readonly includeMarkers: boolean },
|
|
223
|
+
): readonly AggregatePerSpaceExecutionEntry[] {
|
|
224
|
+
return orderedResolutions.map((r) => {
|
|
225
|
+
const operations = r.entry.displayOps.map((op) => ({
|
|
226
|
+
id: op.id,
|
|
227
|
+
label: op.label,
|
|
228
|
+
operationClass: op.operationClass,
|
|
229
|
+
}));
|
|
230
|
+
const base: AggregatePerSpaceExecutionEntry = {
|
|
231
|
+
spaceId: r.spaceId,
|
|
232
|
+
kind: r.spaceId === appSpaceId ? 'app' : 'extension',
|
|
233
|
+
operations,
|
|
234
|
+
};
|
|
235
|
+
if (!options.includeMarkers) return base;
|
|
236
|
+
return {
|
|
237
|
+
...base,
|
|
238
|
+
marker: { storageHash: r.entry.plan.destination.storageHash },
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Materialise the `applyOrder` ordering into resolved per-space
|
|
245
|
+
* entries. Throws if the planner output is missing a member listed
|
|
246
|
+
* in `applyOrder` — a wiring bug that should never reach runtime.
|
|
247
|
+
*
|
|
248
|
+
* Exported so callers building their own success envelopes after a
|
|
249
|
+
* plan-mode dispatch can replay the same ordering.
|
|
250
|
+
*/
|
|
251
|
+
export function collectOrdered(
|
|
252
|
+
applyOrder: readonly string[],
|
|
253
|
+
perSpace: ReadonlyMap<string, AggregatePerSpacePlan>,
|
|
254
|
+
): readonly OrderedResolution[] {
|
|
255
|
+
return applyOrder.map((spaceId) => {
|
|
256
|
+
const entry = perSpace.get(spaceId);
|
|
257
|
+
if (!entry) {
|
|
258
|
+
throw new Error(`Aggregate planner output missing per-space plan for "${spaceId}"`);
|
|
259
|
+
}
|
|
260
|
+
return { spaceId, entry };
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Action-appropriate label for the `spanStart` event the apply
|
|
266
|
+
* primitive emits. `applyAggregate` is shared by `db init`, `db update`,
|
|
267
|
+
* and `migration apply`; the span label tracks the user-visible action
|
|
268
|
+
* so structured-progress output reads naturally for each surface.
|
|
269
|
+
*/
|
|
270
|
+
export function progressLabelForAction(action: AggregateApplyAction): string {
|
|
271
|
+
switch (action) {
|
|
272
|
+
case 'dbInit':
|
|
273
|
+
return 'Initialising database across spaces';
|
|
274
|
+
case 'dbUpdate':
|
|
275
|
+
return 'Updating database across spaces';
|
|
276
|
+
case 'migrationApply':
|
|
277
|
+
return 'Applying migration plan across spaces';
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function labelForAction(action: AggregateApplyAction): string {
|
|
282
|
+
switch (action) {
|
|
283
|
+
case 'dbInit':
|
|
284
|
+
return 'db init';
|
|
285
|
+
case 'dbUpdate':
|
|
286
|
+
return 'db update';
|
|
287
|
+
case 'migrationApply':
|
|
288
|
+
return 'migration apply';
|
|
289
|
+
}
|
|
290
|
+
}
|