@prisma-next/cli 0.5.0-dev.35 → 0.5.0-dev.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +4 -4
- package/dist/{client-Buy8_40Y.mjs → client-1JqqkiC7.mjs} +3 -2
- package/dist/client-1JqqkiC7.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +3 -3
- package/dist/commands/db-schema.mjs +2 -2
- package/dist/commands/db-sign.mjs +2 -2
- package/dist/commands/db-update.mjs +3 -3
- package/dist/commands/db-verify.mjs +2 -2
- package/dist/commands/migration-apply.d.mts +3 -0
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +46 -23
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.mjs +3 -3
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-ref.mjs +1 -1
- package/dist/commands/migration-show.mjs +2 -2
- package/dist/commands/migration-status.d.mts +18 -1
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +2 -2
- package/dist/{contract-emit-DWtGQYCD.mjs → contract-emit-rt_Nmdwq.mjs} +2 -2
- package/dist/{contract-emit-DWtGQYCD.mjs.map → contract-emit-rt_Nmdwq.mjs.map} +1 -1
- package/dist/{contract-infer-DmLiksNp.mjs → contract-infer-Cf5J2wVg.mjs} +3 -3
- package/dist/{contract-infer-DmLiksNp.mjs.map → contract-infer-Cf5J2wVg.mjs.map} +1 -1
- package/dist/exports/control-api.d.mts +8 -0
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +1 -1
- package/dist/exports/index.mjs +1 -1
- package/dist/{inspect-live-schema-6nsKS6m5.mjs → inspect-live-schema-LWtXfxm_.mjs} +3 -3
- package/dist/{inspect-live-schema-6nsKS6m5.mjs.map → inspect-live-schema-LWtXfxm_.mjs.map} +1 -1
- package/dist/{migration-command-scaffold-Ck1_lcsf.mjs → migration-command-scaffold-CU452v9h.mjs} +3 -3
- package/dist/{migration-command-scaffold-Ck1_lcsf.mjs.map → migration-command-scaffold-CU452v9h.mjs.map} +1 -1
- package/dist/{migration-status-DYJIRnK3.mjs → migration-status-DoPrFIOQ.mjs} +103 -34
- package/dist/migration-status-DoPrFIOQ.mjs.map +1 -0
- package/dist/{result-handler-BmVh8AeV.mjs → result-handler-Ch6hVnOo.mjs} +26 -3
- package/dist/{result-handler-BmVh8AeV.mjs.map → result-handler-Ch6hVnOo.mjs.map} +1 -1
- package/package.json +16 -16
- package/src/commands/migration-apply.ts +72 -16
- package/src/commands/migration-new.ts +2 -2
- package/src/commands/migration-status.ts +171 -23
- package/src/control-api/operations/migration-apply.ts +1 -0
- package/src/control-api/types.ts +8 -0
- package/src/utils/command-helpers.ts +36 -1
- package/src/utils/formatters/graph-migration-mapper.ts +5 -1
- package/dist/client-Buy8_40Y.mjs.map +0 -1
- package/dist/migration-status-DYJIRnK3.mjs.map +0 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { MigrationPlanOperation } from '@prisma-next/framework-components/control';
|
|
2
2
|
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
errorNoInvariantPath,
|
|
5
|
+
errorUnknownInvariant,
|
|
6
|
+
MigrationToolsError,
|
|
7
|
+
} from '@prisma-next/migration-tools/errors';
|
|
4
8
|
import type { MigrationEdge, MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
5
9
|
import {
|
|
6
10
|
findPath,
|
|
@@ -8,7 +12,7 @@ import {
|
|
|
8
12
|
findReachableLeaves,
|
|
9
13
|
} from '@prisma-next/migration-tools/migration-graph';
|
|
10
14
|
import type { MigrationPackage } from '@prisma-next/migration-tools/package';
|
|
11
|
-
import type { Refs } from '@prisma-next/migration-tools/refs';
|
|
15
|
+
import type { RefEntry, Refs } from '@prisma-next/migration-tools/refs';
|
|
12
16
|
import { readRefs, resolveRef } from '@prisma-next/migration-tools/refs';
|
|
13
17
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
14
18
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
@@ -25,6 +29,7 @@ import {
|
|
|
25
29
|
} from '../utils/cli-errors';
|
|
26
30
|
import {
|
|
27
31
|
addGlobalOptions,
|
|
32
|
+
collectDeclaredInvariants,
|
|
28
33
|
loadMigrationPackages,
|
|
29
34
|
maskConnectionUrl,
|
|
30
35
|
readContractEnvelope,
|
|
@@ -32,6 +37,7 @@ import {
|
|
|
32
37
|
setCommandDescriptions,
|
|
33
38
|
setCommandExamples,
|
|
34
39
|
toPathDecisionResult,
|
|
40
|
+
toStructuralEdge,
|
|
35
41
|
} from '../utils/command-helpers';
|
|
36
42
|
import {
|
|
37
43
|
type EdgeStatus,
|
|
@@ -80,17 +86,33 @@ export interface MigrationStatusResult {
|
|
|
80
86
|
readonly targetHash: string;
|
|
81
87
|
readonly contractHash: string;
|
|
82
88
|
readonly refs?: readonly StatusRef[];
|
|
89
|
+
/** Required invariants from the active ref, sorted ascending. Always present (`[]` when no `--ref` or the ref declares none) — knowable offline. */
|
|
90
|
+
readonly requiredInvariants: readonly string[];
|
|
91
|
+
/**
|
|
92
|
+
* Invariants the marker has applied at least once, intersected with
|
|
93
|
+
* `requiredInvariants` for display relevance. JSON consumers see only the
|
|
94
|
+
* subset overlapping the active ref's required set — the full unfiltered
|
|
95
|
+
* marker invariant list lives on `marker.invariants` (control plane) and
|
|
96
|
+
* is not surfaced here. Present only in `mode === 'online'`; absent when
|
|
97
|
+
* offline (the marker is unknown, not empty).
|
|
98
|
+
*/
|
|
99
|
+
readonly appliedInvariants?: readonly string[];
|
|
100
|
+
/** required − applied. Present only in `mode === 'online'`; absent when offline. */
|
|
101
|
+
readonly missingInvariants?: readonly string[];
|
|
83
102
|
readonly pathDecision?: {
|
|
84
103
|
readonly fromHash: string;
|
|
85
104
|
readonly toHash: string;
|
|
86
105
|
readonly alternativeCount: number;
|
|
87
106
|
readonly tieBreakReasons: readonly string[];
|
|
88
107
|
readonly refName?: string;
|
|
108
|
+
readonly requiredInvariants: readonly string[];
|
|
109
|
+
readonly satisfiedInvariants: readonly string[];
|
|
89
110
|
readonly selectedPath: readonly {
|
|
90
111
|
readonly dirName: string;
|
|
91
112
|
readonly migrationHash: string;
|
|
92
113
|
readonly from: string;
|
|
93
114
|
readonly to: string;
|
|
115
|
+
readonly invariants: readonly string[];
|
|
94
116
|
}[];
|
|
95
117
|
};
|
|
96
118
|
readonly summary: string;
|
|
@@ -357,6 +379,7 @@ async function executeMigrationStatusCommand(
|
|
|
357
379
|
|
|
358
380
|
let activeRefName: string | undefined;
|
|
359
381
|
let activeRefHash: string | undefined;
|
|
382
|
+
let activeRefEntry: RefEntry | undefined;
|
|
360
383
|
let allRefs: Refs = {};
|
|
361
384
|
try {
|
|
362
385
|
allRefs = await readRefs(refsDir);
|
|
@@ -370,7 +393,8 @@ async function executeMigrationStatusCommand(
|
|
|
370
393
|
if (options.ref) {
|
|
371
394
|
activeRefName = options.ref;
|
|
372
395
|
try {
|
|
373
|
-
|
|
396
|
+
activeRefEntry = resolveRef(allRefs, activeRefName);
|
|
397
|
+
activeRefHash = activeRefEntry.hash;
|
|
374
398
|
} catch (error) {
|
|
375
399
|
if (MigrationToolsError.is(error)) {
|
|
376
400
|
return notOk(mapMigrationToolsError(error));
|
|
@@ -379,6 +403,8 @@ async function executeMigrationStatusCommand(
|
|
|
379
403
|
}
|
|
380
404
|
}
|
|
381
405
|
|
|
406
|
+
const requiredInvariants: readonly string[] = [...(activeRefEntry?.invariants ?? [])].sort();
|
|
407
|
+
|
|
382
408
|
const statusRefs: StatusRef[] = Object.entries(allRefs).map(([name, entry]) => ({
|
|
383
409
|
name,
|
|
384
410
|
hash: entry.hash,
|
|
@@ -396,6 +422,12 @@ async function executeMigrationStatusCommand(
|
|
|
396
422
|
if (activeRefName) {
|
|
397
423
|
details.push({ label: 'ref', value: activeRefName });
|
|
398
424
|
}
|
|
425
|
+
if (activeRefEntry && activeRefEntry.invariants.length > 0) {
|
|
426
|
+
details.push({
|
|
427
|
+
label: 'required',
|
|
428
|
+
value: formatInvariantList(activeRefEntry.invariants),
|
|
429
|
+
});
|
|
430
|
+
}
|
|
399
431
|
const header = formatStyledHeader({
|
|
400
432
|
command: 'migration status',
|
|
401
433
|
description: 'Show migration history and applied status',
|
|
@@ -453,6 +485,7 @@ async function executeMigrationStatusCommand(
|
|
|
453
485
|
contractHash,
|
|
454
486
|
summary: 'No migrations found',
|
|
455
487
|
diagnostics,
|
|
488
|
+
requiredInvariants,
|
|
456
489
|
});
|
|
457
490
|
}
|
|
458
491
|
|
|
@@ -480,6 +513,7 @@ async function executeMigrationStatusCommand(
|
|
|
480
513
|
}
|
|
481
514
|
|
|
482
515
|
let markerHash: string | undefined;
|
|
516
|
+
let markerInvariants: readonly string[] = [];
|
|
483
517
|
let mode: 'online' | 'offline' = 'offline';
|
|
484
518
|
|
|
485
519
|
if (dbConnection && hasDriver) {
|
|
@@ -492,7 +526,9 @@ async function executeMigrationStatusCommand(
|
|
|
492
526
|
});
|
|
493
527
|
try {
|
|
494
528
|
await client.connect(dbConnection);
|
|
495
|
-
|
|
529
|
+
const marker = await client.readMarker();
|
|
530
|
+
markerHash = marker?.storageHash;
|
|
531
|
+
markerInvariants = marker?.invariants ?? [];
|
|
496
532
|
mode = 'online';
|
|
497
533
|
} catch {
|
|
498
534
|
if (!flags.json && !flags.quiet) {
|
|
@@ -503,6 +539,32 @@ async function executeMigrationStatusCommand(
|
|
|
503
539
|
}
|
|
504
540
|
}
|
|
505
541
|
|
|
542
|
+
// Pre-check unknown invariants. Online: union the graph's declared
|
|
543
|
+
// invariants with the marker's recorded set so a retired-but-applied
|
|
544
|
+
// invariant doesn't surface as MIGRATION.UNKNOWN_INVARIANT — apply would
|
|
545
|
+
// route fine because marker subtraction empties `effectiveRequired`.
|
|
546
|
+
// Offline: keep the check graph-strict (the marker is unknown, and a
|
|
547
|
+
// missing declarer is the dominant signal we can offer).
|
|
548
|
+
if (activeRefEntry && activeRefEntry.invariants.length > 0) {
|
|
549
|
+
const declared = collectDeclaredInvariants(graph);
|
|
550
|
+
const known = new Set<string>(declared);
|
|
551
|
+
if (mode === 'online') {
|
|
552
|
+
for (const id of markerInvariants) known.add(id);
|
|
553
|
+
}
|
|
554
|
+
const unknown = activeRefEntry.invariants.filter((id) => !known.has(id));
|
|
555
|
+
if (unknown.length > 0) {
|
|
556
|
+
return notOk(
|
|
557
|
+
mapMigrationToolsError(
|
|
558
|
+
errorUnknownInvariant({
|
|
559
|
+
...ifDefined('refName', activeRefName),
|
|
560
|
+
unknown,
|
|
561
|
+
declared: [...declared].sort(),
|
|
562
|
+
}),
|
|
563
|
+
),
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
506
568
|
// Marker exists but is not in the migration graph and doesn't match the
|
|
507
569
|
// contract hash. The DB is at an unknown state relative to the graph.
|
|
508
570
|
// Bail out early with a clear diagnostic instead of rendering a confusing
|
|
@@ -548,6 +610,7 @@ async function executeMigrationStatusCommand(
|
|
|
548
610
|
summary: `${bundles.length} migration(s) on disk`,
|
|
549
611
|
diagnostics,
|
|
550
612
|
markerHash,
|
|
613
|
+
requiredInvariants,
|
|
551
614
|
...(statusRefs.length > 0 ? { refs: statusRefs } : {}),
|
|
552
615
|
});
|
|
553
616
|
}
|
|
@@ -588,6 +651,7 @@ async function executeMigrationStatusCommand(
|
|
|
588
651
|
summary: `${bundles.length} migration(s) on disk`,
|
|
589
652
|
diagnostics,
|
|
590
653
|
...ifDefined('markerHash', markerHash),
|
|
654
|
+
requiredInvariants,
|
|
591
655
|
...(statusRefs.length > 0 ? { refs: statusRefs } : {}),
|
|
592
656
|
graph,
|
|
593
657
|
bundles,
|
|
@@ -612,14 +676,39 @@ async function executeMigrationStatusCommand(
|
|
|
612
676
|
const pendingCount = edgeStatuses.filter((e) => e.status === 'pending').length;
|
|
613
677
|
const appliedCount = edgeStatuses.filter((e) => e.status === 'applied').length;
|
|
614
678
|
|
|
679
|
+
let appliedInvariants: readonly string[] | undefined;
|
|
680
|
+
let missingInvariants: readonly string[] | undefined;
|
|
681
|
+
let effectiveRequired = new Set<string>();
|
|
682
|
+
if (mode === 'online') {
|
|
683
|
+
// Mirrors `migration-apply.ts`: compute `effectiveRequired = required −
|
|
684
|
+
// marker.invariants` directly, then derive the display fields from it.
|
|
685
|
+
// `appliedInvariants` is the intersection (`required ∩ marker`), which
|
|
686
|
+
// is what JSON consumers see for the active ref; the unfiltered set
|
|
687
|
+
// lives on `marker.invariants`.
|
|
688
|
+
const markerSet = new Set(markerInvariants);
|
|
689
|
+
effectiveRequired = new Set(requiredInvariants.filter((id) => !markerSet.has(id)));
|
|
690
|
+
appliedInvariants = requiredInvariants.filter((id) => markerSet.has(id));
|
|
691
|
+
missingInvariants = [...effectiveRequired].sort();
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// The marker can match the structural target while still missing required
|
|
695
|
+
// invariants — for example, a self-edge that provides X, applied via a ref
|
|
696
|
+
// declaring X. `pendingCount` (structural) says zero in that case but
|
|
697
|
+
// `effectiveRequired` is non-empty, so up-to-date messaging would mislead.
|
|
698
|
+
const hasInvariantWork = effectiveRequired.size > 0;
|
|
699
|
+
const missingList = [...effectiveRequired].sort().join(', ');
|
|
700
|
+
|
|
615
701
|
let summary: string;
|
|
616
702
|
if (mode === 'online') {
|
|
617
703
|
if (markerHash !== undefined && !graph.nodes.has(markerHash) && markerHash === contractHash) {
|
|
618
704
|
summary = `${bundles.length} migration(s) on disk`;
|
|
619
705
|
} else if (activeRefHash && markerHash !== undefined) {
|
|
620
|
-
|
|
621
|
-
|
|
706
|
+
const distance = summarizeRefDistance(graph, markerHash, activeRefHash, activeRefName!);
|
|
707
|
+
summary = hasInvariantWork ? `${distance} — missing invariant(s): ${missingList}` : distance;
|
|
708
|
+
} else if (pendingCount === 0 && !hasInvariantWork) {
|
|
622
709
|
summary = `Database is up to date (${appliedCount} migration${appliedCount !== 1 ? 's' : ''} applied)`;
|
|
710
|
+
} else if (pendingCount === 0 && hasInvariantWork) {
|
|
711
|
+
summary = `Missing invariant(s): ${missingList} — run 'prisma-next migration apply --ref ${activeRefName ?? '<ref>'}' to apply`;
|
|
623
712
|
} else if (markerHash === undefined) {
|
|
624
713
|
summary = `${pendingCount} pending migration(s) — database has no marker`;
|
|
625
714
|
} else {
|
|
@@ -629,6 +718,37 @@ async function executeMigrationStatusCommand(
|
|
|
629
718
|
summary = `${entries.length} migration(s) on disk`;
|
|
630
719
|
}
|
|
631
720
|
|
|
721
|
+
let pathDecision: MigrationStatusResult['pathDecision'];
|
|
722
|
+
let routingUnreachable = false;
|
|
723
|
+
if (mode === 'online') {
|
|
724
|
+
const originHash = markerHash ?? EMPTY_CONTRACT_HASH;
|
|
725
|
+
const outcome = findPathWithDecision(graph, originHash, targetHash, {
|
|
726
|
+
...ifDefined('refName', activeRefName),
|
|
727
|
+
required: effectiveRequired,
|
|
728
|
+
});
|
|
729
|
+
if (outcome.kind === 'ok') {
|
|
730
|
+
pathDecision = toPathDecisionResult(outcome.decision);
|
|
731
|
+
} else if (outcome.kind === 'unsatisfiable') {
|
|
732
|
+
return notOk(
|
|
733
|
+
mapMigrationToolsError(
|
|
734
|
+
errorNoInvariantPath({
|
|
735
|
+
...ifDefined('refName', activeRefName),
|
|
736
|
+
required: [...effectiveRequired].sort(),
|
|
737
|
+
missing: outcome.missing,
|
|
738
|
+
structuralPath: outcome.structuralPath.map(toStructuralEdge),
|
|
739
|
+
}),
|
|
740
|
+
),
|
|
741
|
+
);
|
|
742
|
+
} else {
|
|
743
|
+
// outcome.kind === 'unreachable' — origin (marker) has no structural
|
|
744
|
+
// path to the active target. `pendingCount` and `hasInvariantWork`
|
|
745
|
+
// both report zero in this case, but emitting MIGRATION.UP_TO_DATE
|
|
746
|
+
// would be wrong: the database simply cannot reach the requested
|
|
747
|
+
// ref/contract from its current state. Suppress UP_TO_DATE below.
|
|
748
|
+
routingUnreachable = true;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
632
752
|
if (mode === 'online') {
|
|
633
753
|
if (markerHash !== undefined && !graph.nodes.has(markerHash) && markerHash === contractHash) {
|
|
634
754
|
diagnostics.push({
|
|
@@ -645,7 +765,16 @@ async function executeMigrationStatusCommand(
|
|
|
645
765
|
message: `${pendingCount} migration(s) pending`,
|
|
646
766
|
hints: ["Run 'prisma-next migration apply' to apply pending migrations"],
|
|
647
767
|
});
|
|
648
|
-
} else {
|
|
768
|
+
} else if (hasInvariantWork) {
|
|
769
|
+
diagnostics.push({
|
|
770
|
+
code: 'MIGRATION.INVARIANTS_PENDING',
|
|
771
|
+
severity: 'info',
|
|
772
|
+
message: `Missing required invariant(s): ${missingList}`,
|
|
773
|
+
hints: [
|
|
774
|
+
`Run 'prisma-next migration apply --ref ${activeRefName ?? '<ref>'}' to apply a path that covers the required invariants`,
|
|
775
|
+
],
|
|
776
|
+
});
|
|
777
|
+
} else if (!routingUnreachable) {
|
|
649
778
|
diagnostics.push({
|
|
650
779
|
code: 'MIGRATION.UP_TO_DATE',
|
|
651
780
|
severity: 'info',
|
|
@@ -655,14 +784,6 @@ async function executeMigrationStatusCommand(
|
|
|
655
784
|
}
|
|
656
785
|
}
|
|
657
786
|
|
|
658
|
-
let pathDecision: MigrationStatusResult['pathDecision'];
|
|
659
|
-
if (mode === 'online' && markerHash !== undefined) {
|
|
660
|
-
const decision = findPathWithDecision(graph, markerHash, targetHash, activeRefName);
|
|
661
|
-
if (decision) {
|
|
662
|
-
pathDecision = toPathDecisionResult(decision);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
|
|
666
787
|
const result: MigrationStatusResult = {
|
|
667
788
|
ok: true,
|
|
668
789
|
mode,
|
|
@@ -672,6 +793,9 @@ async function executeMigrationStatusCommand(
|
|
|
672
793
|
summary,
|
|
673
794
|
diagnostics,
|
|
674
795
|
...ifDefined('markerHash', markerHash),
|
|
796
|
+
requiredInvariants,
|
|
797
|
+
...ifDefined('appliedInvariants', appliedInvariants),
|
|
798
|
+
...ifDefined('missingInvariants', missingInvariants),
|
|
675
799
|
...(statusRefs.length > 0 ? { refs: statusRefs } : {}),
|
|
676
800
|
...ifDefined('pathDecision', pathDecision),
|
|
677
801
|
graph,
|
|
@@ -712,13 +836,19 @@ export function createMigrationStatusCommand(): Command {
|
|
|
712
836
|
|
|
713
837
|
const exitCode = handleResult(result, flags, ui, (statusResult) => {
|
|
714
838
|
if (flags.json) {
|
|
839
|
+
// Strip non-JSON-shape fields before emitting. These belong to
|
|
840
|
+
// the in-memory result so the human renderer can avoid
|
|
841
|
+
// recomputing them, but they would either bloat the wire format
|
|
842
|
+
// (graph, bundles, edgeStatuses) or expose internals
|
|
843
|
+
// (activeRefHash, activeRefName, diverged) that consumers should
|
|
844
|
+
// read off `pathDecision` / `refs` instead.
|
|
715
845
|
const {
|
|
716
|
-
graph:
|
|
717
|
-
bundles:
|
|
718
|
-
edgeStatuses:
|
|
719
|
-
activeRefHash:
|
|
720
|
-
activeRefName:
|
|
721
|
-
diverged:
|
|
846
|
+
graph: _graph,
|
|
847
|
+
bundles: _bundles,
|
|
848
|
+
edgeStatuses: _edgeStatuses,
|
|
849
|
+
activeRefHash: _activeRefHash,
|
|
850
|
+
activeRefName: _activeRefName,
|
|
851
|
+
diverged: _diverged,
|
|
722
852
|
...jsonResult
|
|
723
853
|
} = statusResult;
|
|
724
854
|
ui.output(JSON.stringify(jsonResult, null, 2));
|
|
@@ -777,7 +907,7 @@ function formatLegend(colorize: boolean): string {
|
|
|
777
907
|
return c(dim, parts.join(' '));
|
|
778
908
|
}
|
|
779
909
|
|
|
780
|
-
function formatStatusSummary(result: MigrationStatusResult, colorize: boolean): string {
|
|
910
|
+
export function formatStatusSummary(result: MigrationStatusResult, colorize: boolean): string {
|
|
781
911
|
const c = (fn: (s: string) => string, s: string) => (colorize ? fn(s) : s);
|
|
782
912
|
const lines: string[] = [];
|
|
783
913
|
|
|
@@ -785,11 +915,16 @@ function formatStatusSummary(result: MigrationStatusResult, colorize: boolean):
|
|
|
785
915
|
const pendingCount = result.migrations.filter((e) => e.status === 'pending').length;
|
|
786
916
|
|
|
787
917
|
const hasWarnings = result.diagnostics?.some((d) => d.severity === 'warn') ?? false;
|
|
918
|
+
// INVARIANTS_PENDING is filed at severity 'info' (per ADR 208) so the
|
|
919
|
+
// warn-severity check above doesn't see it. It still represents pending
|
|
920
|
+
// work, so it must promote the summary off the success icon.
|
|
921
|
+
const hasInvariantPending =
|
|
922
|
+
result.diagnostics?.some((d) => d.code === 'MIGRATION.INVARIANTS_PENDING') ?? false;
|
|
788
923
|
|
|
789
924
|
if (result.mode === 'online') {
|
|
790
925
|
if (hasUnknown || hasWarnings) {
|
|
791
926
|
lines.push(`${c(yellow, '⚠')} ${result.summary}`);
|
|
792
|
-
} else if (pendingCount === 0) {
|
|
927
|
+
} else if (pendingCount === 0 && !hasInvariantPending) {
|
|
793
928
|
lines.push(`${c(cyan, '✔')} ${result.summary}`);
|
|
794
929
|
} else {
|
|
795
930
|
lines.push(`${c(yellow, '⧗')} ${result.summary}`);
|
|
@@ -798,6 +933,15 @@ function formatStatusSummary(result: MigrationStatusResult, colorize: boolean):
|
|
|
798
933
|
lines.push(result.summary);
|
|
799
934
|
}
|
|
800
935
|
|
|
936
|
+
if (result.requiredInvariants.length > 0) {
|
|
937
|
+
if (result.appliedInvariants !== undefined && result.missingInvariants !== undefined) {
|
|
938
|
+
lines.push(`${c(dim, 'applied ')}${formatInvariantList(result.appliedInvariants)}`);
|
|
939
|
+
lines.push(`${c(dim, 'missing ')}${formatInvariantList(result.missingInvariants)}`);
|
|
940
|
+
} else {
|
|
941
|
+
lines.push(`${c(dim, 'applied ')}(unknown — connect a database to evaluate)`);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
801
945
|
const warnings = result.diagnostics?.filter((d) => d.severity === 'warn') ?? [];
|
|
802
946
|
for (const diag of warnings) {
|
|
803
947
|
lines.push(`${c(yellow, '⚠')} ${diag.message}`);
|
|
@@ -809,6 +953,10 @@ function formatStatusSummary(result: MigrationStatusResult, colorize: boolean):
|
|
|
809
953
|
return lines.join('\n');
|
|
810
954
|
}
|
|
811
955
|
|
|
956
|
+
function formatInvariantList(ids: readonly string[]): string {
|
|
957
|
+
return ids.length === 0 ? '(none)' : ids.join(', ');
|
|
958
|
+
}
|
|
959
|
+
|
|
812
960
|
function summarizeRefDistance(
|
|
813
961
|
graph: MigrationGraph,
|
|
814
962
|
markerHash: string,
|
|
@@ -147,6 +147,7 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
|
|
|
147
147
|
origin: migration.from === null ? null : { storageHash: migration.from },
|
|
148
148
|
destination: { storageHash: migration.to },
|
|
149
149
|
operations,
|
|
150
|
+
providedInvariants: migration.providedInvariants,
|
|
150
151
|
};
|
|
151
152
|
|
|
152
153
|
const destinationContract = familyInstance.validateContract(migration.toContract);
|
package/src/control-api/types.ts
CHANGED
|
@@ -452,6 +452,14 @@ export interface MigrationApplyStep {
|
|
|
452
452
|
readonly to: string;
|
|
453
453
|
readonly toContract: Contract;
|
|
454
454
|
readonly operations: readonly MigrationPlanOperation[];
|
|
455
|
+
/**
|
|
456
|
+
* Sorted, deduplicated invariant ids from `migration.json.providedInvariants`.
|
|
457
|
+
* Verified at load time by `readMigrationPackage` (manifest copy must equal
|
|
458
|
+
* the value derived from `ops.json`). The control-api passes this through
|
|
459
|
+
* to the runner via `MigrationPlan.providedInvariants` so target runners
|
|
460
|
+
* read the canonical set instead of re-deriving from `operations`.
|
|
461
|
+
*/
|
|
462
|
+
readonly providedInvariants: readonly string[];
|
|
455
463
|
}
|
|
456
464
|
|
|
457
465
|
/**
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import type { ControlTargetDescriptor } from '@prisma-next/framework-components/control';
|
|
3
3
|
import { hasMigrations } from '@prisma-next/framework-components/control';
|
|
4
|
-
import type {
|
|
4
|
+
import type { NoInvariantPathStructuralEdge } from '@prisma-next/migration-tools/errors';
|
|
5
|
+
import type { MigrationEdge, MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
5
6
|
import { readMigrationsDir } from '@prisma-next/migration-tools/io';
|
|
6
7
|
import type { PathDecision } from '@prisma-next/migration-tools/migration-graph';
|
|
7
8
|
import { reconstructGraph } from '@prisma-next/migration-tools/migration-graph';
|
|
@@ -110,14 +111,45 @@ export interface PathDecisionResult {
|
|
|
110
111
|
readonly alternativeCount: number;
|
|
111
112
|
readonly tieBreakReasons: readonly string[];
|
|
112
113
|
readonly refName?: string;
|
|
114
|
+
readonly requiredInvariants: readonly string[];
|
|
115
|
+
readonly satisfiedInvariants: readonly string[];
|
|
113
116
|
readonly selectedPath: readonly {
|
|
114
117
|
readonly dirName: string;
|
|
115
118
|
readonly migrationHash: string;
|
|
116
119
|
readonly from: string;
|
|
117
120
|
readonly to: string;
|
|
121
|
+
readonly invariants: readonly string[];
|
|
118
122
|
}[];
|
|
119
123
|
}
|
|
120
124
|
|
|
125
|
+
export function collectDeclaredInvariants(graph: MigrationGraph): ReadonlySet<string> {
|
|
126
|
+
const declared = new Set<string>();
|
|
127
|
+
for (const edges of graph.forwardChain.values()) {
|
|
128
|
+
for (const edge of edges) {
|
|
129
|
+
for (const inv of edge.invariants) {
|
|
130
|
+
declared.add(inv);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return declared;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Maps a `MigrationEdge` to the structural-edge shape used in the
|
|
139
|
+
* `MIGRATION.NO_INVARIANT_PATH` error envelope. Shared between
|
|
140
|
+
* `migration apply` and `migration status` so both commands surface
|
|
141
|
+
* the same JSON wire shape when an invariant-aware route is unsatisfiable.
|
|
142
|
+
*/
|
|
143
|
+
export function toStructuralEdge(edge: MigrationEdge): NoInvariantPathStructuralEdge {
|
|
144
|
+
return {
|
|
145
|
+
dirName: edge.dirName,
|
|
146
|
+
migrationHash: edge.migrationHash,
|
|
147
|
+
from: edge.from,
|
|
148
|
+
to: edge.to,
|
|
149
|
+
invariants: edge.invariants,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
121
153
|
/**
|
|
122
154
|
* Maps a PathDecision to the slim CLI output representation.
|
|
123
155
|
*/
|
|
@@ -127,12 +159,15 @@ export function toPathDecisionResult(decision: PathDecision): PathDecisionResult
|
|
|
127
159
|
toHash: decision.toHash,
|
|
128
160
|
alternativeCount: decision.alternativeCount,
|
|
129
161
|
tieBreakReasons: decision.tieBreakReasons,
|
|
162
|
+
requiredInvariants: decision.requiredInvariants ?? [],
|
|
163
|
+
satisfiedInvariants: decision.satisfiedInvariants ?? [],
|
|
130
164
|
...ifDefined('refName', decision.refName),
|
|
131
165
|
selectedPath: decision.selectedPath.map((entry) => ({
|
|
132
166
|
dirName: entry.dirName,
|
|
133
167
|
migrationHash: entry.migrationHash,
|
|
134
168
|
from: entry.from,
|
|
135
169
|
to: entry.to,
|
|
170
|
+
invariants: entry.invariants,
|
|
136
171
|
})),
|
|
137
172
|
};
|
|
138
173
|
}
|
|
@@ -106,7 +106,11 @@ export function migrationGraphToRenderInput(input: MigrationGraphInput): Migrati
|
|
|
106
106
|
for (const entry of entries) {
|
|
107
107
|
const status = statusByDirName.get(entry.dirName);
|
|
108
108
|
const icon = status ? STATUS_ICON[status] : '';
|
|
109
|
-
const
|
|
109
|
+
const invariantsSuffix =
|
|
110
|
+
entry.invariants.length > 0
|
|
111
|
+
? ` provides [${entry.invariants.map((id) => JSON.stringify(id)).join(', ')}]`
|
|
112
|
+
: '';
|
|
113
|
+
const label = `${entry.dirName}${icon}${invariantsSuffix}`;
|
|
110
114
|
|
|
111
115
|
edgeList.push({
|
|
112
116
|
from: toShortId(entry.from),
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client-Buy8_40Y.mjs","names":["plannerResult: MigrationPlannerResult","migrationPlan: MigrationPlan","runnerResult: MigrationRunnerResult","plannerResult: MigrationPlannerResult","runnerResult: MigrationRunnerResult","applied: MigrationApplyAppliedEntry[]","runnerResult: MigrationRunnerResult","contract: Contract","contractRaw: unknown","emitContractArtifacts"],"sources":["../src/control-api/errors.ts","../src/control-api/operations/migration-helpers.ts","../src/control-api/operations/db-init.ts","../src/control-api/operations/db-update.ts","../src/control-api/operations/migration-apply.ts","../src/control-api/client.ts"],"sourcesContent":["export class ContractValidationError extends Error {\n override readonly cause?: unknown;\n\n constructor(message: string, cause?: unknown) {\n super(message);\n this.name = 'ContractValidationError';\n this.cause = cause;\n }\n}\n","import type { MigrationPlanOperation } from '@prisma-next/framework-components/control';\nimport type { ControlActionName, OnControlProgress } from '../types';\n\n/**\n * Strips operation objects to their public shape (id, label, operationClass).\n * Used at the API boundary to avoid leaking internal fields (precheck, execute, postcheck, etc.).\n */\nexport function stripOperations(\n operations: readonly MigrationPlanOperation[],\n): ReadonlyArray<{ readonly id: string; readonly label: string; readonly operationClass: string }> {\n return operations.map((op) => ({\n id: op.id,\n label: op.label,\n operationClass: op.operationClass,\n }));\n}\n\n/**\n * Creates per-operation progress callbacks for the runner.\n * Returns undefined when no onProgress callback is provided.\n */\nexport function createOperationCallbacks(\n onProgress: OnControlProgress | undefined,\n action: ControlActionName,\n parentSpanId: string,\n) {\n if (!onProgress) {\n return undefined;\n }\n return {\n onOperationStart: (op: MigrationPlanOperation) => {\n onProgress({\n action,\n kind: 'spanStart',\n spanId: `operation:${op.id}`,\n parentSpanId,\n label: op.label,\n });\n },\n onOperationComplete: (op: MigrationPlanOperation) => {\n onProgress({\n action,\n kind: 'spanEnd',\n spanId: `operation:${op.id}`,\n outcome: 'ok',\n });\n },\n };\n}\n","import type { Contract } from '@prisma-next/contract/types';\nimport type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';\nimport type {\n ControlDriverInstance,\n ControlFamilyInstance,\n MigrationPlan,\n MigrationPlannerResult,\n MigrationRunnerResult,\n TargetMigrationsCapability,\n} from '@prisma-next/framework-components/control';\nimport { hasOperationPreview } from '@prisma-next/framework-components/control';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok } from '@prisma-next/utils/result';\nimport type { DbInitResult, DbInitSuccess, OnControlProgress } from '../types';\nimport { createOperationCallbacks, stripOperations } from './migration-helpers';\n\n/**\n * Options for executing dbInit operation.\n */\nexport interface ExecuteDbInitOptions<TFamilyId extends string, TTargetId extends string> {\n readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;\n readonly familyInstance: ControlFamilyInstance<TFamilyId, unknown>;\n readonly contract: Contract;\n readonly mode: 'plan' | 'apply';\n readonly migrations: TargetMigrationsCapability<\n TFamilyId,\n TTargetId,\n ControlFamilyInstance<TFamilyId, unknown>\n >;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;\n /** Optional progress callback for observing operation progress */\n readonly onProgress?: OnControlProgress;\n}\n\n/**\n * Executes the dbInit operation.\n *\n * This is the core logic extracted from the CLI command, without any file I/O,\n * process.exit(), or console output. It uses the Result pattern to return\n * success or failure details.\n *\n * @param options - The options for executing dbInit\n * @returns Result with DbInitSuccess on success, DbInitFailure on failure\n */\nexport async function executeDbInit<TFamilyId extends string, TTargetId extends string>(\n options: ExecuteDbInitOptions<TFamilyId, TTargetId>,\n): Promise<DbInitResult> {\n const { driver, familyInstance, contract, mode, migrations, frameworkComponents, onProgress } =\n options;\n\n // Create planner and runner from target migrations capability\n const planner = migrations.createPlanner(familyInstance);\n const runner = migrations.createRunner(familyInstance);\n\n // Introspect live schema\n const introspectSpanId = 'introspect';\n onProgress?.({\n action: 'dbInit',\n kind: 'spanStart',\n spanId: introspectSpanId,\n label: 'Introspecting database schema',\n });\n const schemaIR = await familyInstance.introspect({ driver });\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: introspectSpanId,\n outcome: 'ok',\n });\n\n // Policy for init mode (additive only)\n const policy = { allowedOperationClasses: ['additive'] as const };\n\n // Plan migration\n const planSpanId = 'plan';\n onProgress?.({\n action: 'dbInit',\n kind: 'spanStart',\n spanId: planSpanId,\n label: 'Planning migration',\n });\n const plannerResult: MigrationPlannerResult = await planner.plan({\n contract,\n schema: schemaIR,\n policy,\n // `db init` reconciles against the live introspected schema; there is no\n // prior contract to derive a \"from\" identity from. The required\n // `fromContract: null` makes that structural fact visible at the call\n // site (vs. silently letting the planner default to a baseline plan).\n fromContract: null,\n frameworkComponents,\n });\n\n if (plannerResult.kind === 'failure') {\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: planSpanId,\n outcome: 'error',\n });\n return notOk({\n code: 'PLANNING_FAILED' as const,\n summary: 'Migration planning failed due to conflicts',\n conflicts: plannerResult.conflicts,\n why: undefined,\n meta: undefined,\n });\n }\n\n const migrationPlan: MigrationPlan = plannerResult.plan;\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: planSpanId,\n outcome: 'ok',\n });\n\n // Check for existing marker - handle idempotency and mismatch errors\n const checkMarkerSpanId = 'checkMarker';\n onProgress?.({\n action: 'dbInit',\n kind: 'spanStart',\n spanId: checkMarkerSpanId,\n label: 'Checking database signature',\n });\n const existingMarker = await familyInstance.readMarker({ driver });\n if (existingMarker) {\n const markerMatchesDestination =\n existingMarker.storageHash === migrationPlan.destination.storageHash &&\n (!migrationPlan.destination.profileHash ||\n existingMarker.profileHash === migrationPlan.destination.profileHash);\n\n if (markerMatchesDestination) {\n // Already at destination - return success with no operations\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: checkMarkerSpanId,\n outcome: 'skipped',\n });\n const result: DbInitSuccess = {\n mode,\n plan: { operations: [] },\n destination: {\n storageHash: migrationPlan.destination.storageHash,\n ...ifDefined('profileHash', migrationPlan.destination.profileHash),\n },\n ...ifDefined(\n 'execution',\n mode === 'apply' ? { operationsPlanned: 0, operationsExecuted: 0 } : undefined,\n ),\n ...ifDefined(\n 'marker',\n mode === 'apply'\n ? {\n storageHash: existingMarker.storageHash,\n profileHash: existingMarker.profileHash,\n }\n : undefined,\n ),\n summary: 'Database already at target contract state',\n };\n return ok(result);\n }\n\n // Marker exists but doesn't match destination - fail\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: checkMarkerSpanId,\n outcome: 'error',\n });\n return notOk({\n code: 'MARKER_ORIGIN_MISMATCH' as const,\n summary: 'Existing contract marker does not match plan destination',\n marker: {\n storageHash: existingMarker.storageHash,\n profileHash: existingMarker.profileHash,\n },\n destination: {\n storageHash: migrationPlan.destination.storageHash,\n profileHash: migrationPlan.destination.profileHash,\n },\n why: undefined,\n conflicts: undefined,\n meta: undefined,\n });\n }\n\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: checkMarkerSpanId,\n outcome: 'ok',\n });\n\n // Plan mode - don't execute\n if (mode === 'plan') {\n const preview = hasOperationPreview(familyInstance)\n ? familyInstance.toOperationPreview(migrationPlan.operations)\n : undefined;\n const result: DbInitSuccess = {\n mode: 'plan',\n plan: {\n operations: stripOperations(migrationPlan.operations),\n ...ifDefined('preview', preview),\n },\n destination: {\n storageHash: migrationPlan.destination.storageHash,\n ...ifDefined('profileHash', migrationPlan.destination.profileHash),\n },\n summary: `Planned ${migrationPlan.operations.length} operation(s)`,\n };\n return ok(result);\n }\n\n // Apply mode - execute runner\n const applySpanId = 'apply';\n onProgress?.({\n action: 'dbInit',\n kind: 'spanStart',\n spanId: applySpanId,\n label: 'Applying migration plan',\n });\n\n const callbacks = createOperationCallbacks(onProgress, 'dbInit', applySpanId);\n\n const runnerResult: MigrationRunnerResult = await runner.execute({\n plan: migrationPlan,\n driver,\n destinationContract: contract,\n policy,\n ...ifDefined('callbacks', callbacks),\n // db init plans and applies back-to-back from a fresh introspection, so per-operation\n // pre/postchecks and the idempotency probe are usually redundant overhead. We still\n // enforce marker/origin compatibility and a full schema verification after apply.\n executionChecks: {\n prechecks: false,\n postchecks: false,\n idempotencyChecks: false,\n },\n frameworkComponents,\n });\n\n if (!runnerResult.ok) {\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: applySpanId,\n outcome: 'error',\n });\n return notOk({\n code: 'RUNNER_FAILED' as const,\n summary: runnerResult.failure.summary,\n why: runnerResult.failure.why,\n meta: runnerResult.failure.meta,\n conflicts: undefined,\n });\n }\n\n const execution = runnerResult.value;\n\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: applySpanId,\n outcome: 'ok',\n });\n\n const result: DbInitSuccess = {\n mode: 'apply',\n plan: {\n operations: stripOperations(migrationPlan.operations),\n },\n destination: {\n storageHash: migrationPlan.destination.storageHash,\n ...ifDefined('profileHash', migrationPlan.destination.profileHash),\n },\n execution: {\n operationsPlanned: execution.operationsPlanned,\n operationsExecuted: execution.operationsExecuted,\n },\n marker: migrationPlan.destination.profileHash\n ? {\n storageHash: migrationPlan.destination.storageHash,\n profileHash: migrationPlan.destination.profileHash,\n }\n : { storageHash: migrationPlan.destination.storageHash },\n summary: `Applied ${execution.operationsExecuted} operation(s), database signed`,\n };\n return ok(result);\n}\n","import type { Contract } from '@prisma-next/contract/types';\nimport type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';\nimport type {\n ControlDriverInstance,\n ControlFamilyInstance,\n MigrationPlannerResult,\n MigrationRunnerResult,\n TargetMigrationsCapability,\n} from '@prisma-next/framework-components/control';\nimport { hasOperationPreview } from '@prisma-next/framework-components/control';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok } from '@prisma-next/utils/result';\nimport type { DbUpdateResult, DbUpdateSuccess, OnControlProgress } from '../types';\nimport { createOperationCallbacks, stripOperations } from './migration-helpers';\n\n// F12: db update allows additive, widening, and destructive operations.\nconst DB_UPDATE_POLICY = {\n allowedOperationClasses: ['additive', 'widening', 'destructive'] as const,\n} as const;\n\n/**\n * Options for the executeDbUpdate operation.\n * Config-agnostic: receives pre-resolved driver, family, contract, and migrations capability.\n */\nexport interface ExecuteDbUpdateOptions<TFamilyId extends string, TTargetId extends string> {\n readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;\n readonly familyInstance: ControlFamilyInstance<TFamilyId, unknown>;\n readonly contract: Contract;\n readonly mode: 'plan' | 'apply';\n readonly migrations: TargetMigrationsCapability<\n TFamilyId,\n TTargetId,\n ControlFamilyInstance<TFamilyId, unknown>\n >;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;\n readonly acceptDataLoss?: boolean;\n /** Optional progress callback for observing operation progress. */\n readonly onProgress?: OnControlProgress;\n}\n\n/**\n * Executes the db update operation: introspect → plan → (optionally) apply → marker.\n *\n * db update is a pure reconciliation command: it introspects the live schema, plans the diff\n * to the destination contract, and applies operations. The marker is bookkeeping only — written\n * after apply so that `verify` and `db init` can reference it, but never read or validated\n * by db update itself. The runner creates the marker table if it does not exist.\n */\nexport async function executeDbUpdate<TFamilyId extends string, TTargetId extends string>(\n options: ExecuteDbUpdateOptions<TFamilyId, TTargetId>,\n): Promise<DbUpdateResult> {\n const { driver, familyInstance, contract, mode, migrations, frameworkComponents, onProgress } =\n options;\n\n const planner = migrations.createPlanner(familyInstance);\n const runner = migrations.createRunner(familyInstance);\n\n const introspectSpanId = 'introspect';\n onProgress?.({\n action: 'dbUpdate',\n kind: 'spanStart',\n spanId: introspectSpanId,\n label: 'Introspecting database schema',\n });\n const schemaIR = await familyInstance.introspect({ driver });\n onProgress?.({\n action: 'dbUpdate',\n kind: 'spanEnd',\n spanId: introspectSpanId,\n outcome: 'ok',\n });\n\n const policy = DB_UPDATE_POLICY;\n\n const planSpanId = 'plan';\n onProgress?.({\n action: 'dbUpdate',\n kind: 'spanStart',\n spanId: planSpanId,\n label: 'Planning migration',\n });\n const plannerResult: MigrationPlannerResult = await planner.plan({\n contract,\n schema: schemaIR,\n policy,\n // `db update` reconciles against the live introspected schema; there is\n // no prior contract to derive a \"from\" identity from. The required\n // `fromContract: null` makes that structural fact visible at the call\n // site (vs. silently letting the planner default to a baseline plan).\n fromContract: null,\n frameworkComponents,\n });\n if (plannerResult.kind === 'failure') {\n onProgress?.({\n action: 'dbUpdate',\n kind: 'spanEnd',\n spanId: planSpanId,\n outcome: 'error',\n });\n return notOk({\n code: 'PLANNING_FAILED',\n summary: 'Migration planning failed due to conflicts',\n conflicts: plannerResult.conflicts,\n why: undefined,\n meta: undefined,\n });\n }\n onProgress?.({\n action: 'dbUpdate',\n kind: 'spanEnd',\n spanId: planSpanId,\n outcome: 'ok',\n });\n\n const migrationPlan = plannerResult.plan;\n\n if (mode === 'plan') {\n const preview = hasOperationPreview(familyInstance)\n ? familyInstance.toOperationPreview(migrationPlan.operations)\n : undefined;\n const result: DbUpdateSuccess = {\n mode: 'plan',\n plan: {\n operations: stripOperations(migrationPlan.operations),\n ...ifDefined('preview', preview),\n },\n destination: {\n storageHash: migrationPlan.destination.storageHash,\n ...ifDefined('profileHash', migrationPlan.destination.profileHash),\n },\n summary: `Planned ${migrationPlan.operations.length} operation(s)`,\n };\n return ok(result);\n }\n\n // When applying, require explicit acceptance for destructive operations\n if (!options.acceptDataLoss) {\n const destructiveOps = migrationPlan.operations\n .filter((op) => op.operationClass === 'destructive')\n .map((op) => ({ id: op.id, label: op.label }));\n if (destructiveOps.length > 0) {\n return notOk({\n code: 'DESTRUCTIVE_CHANGES',\n summary: `Planned ${destructiveOps.length} destructive operation(s) that require confirmation`,\n why: 'Destructive operations require confirmation — re-run with -y to accept',\n conflicts: undefined,\n meta: { destructiveOperations: destructiveOps },\n });\n }\n }\n\n const applySpanId = 'apply';\n onProgress?.({\n action: 'dbUpdate',\n kind: 'spanStart',\n spanId: applySpanId,\n label: 'Applying migration plan',\n });\n\n const callbacks = createOperationCallbacks(onProgress, 'dbUpdate', applySpanId);\n\n const runnerResult: MigrationRunnerResult = await runner.execute({\n plan: migrationPlan,\n driver,\n destinationContract: contract,\n policy,\n ...(callbacks ? { callbacks } : {}),\n // db update plans and applies from a single introspection pass, so per-operation pre/postchecks\n // and idempotency probes are intentionally disabled to avoid redundant roundtrips.\n executionChecks: {\n prechecks: false,\n postchecks: false,\n idempotencyChecks: false,\n },\n frameworkComponents,\n });\n\n if (!runnerResult.ok) {\n onProgress?.({\n action: 'dbUpdate',\n kind: 'spanEnd',\n spanId: applySpanId,\n outcome: 'error',\n });\n return notOk({\n code: 'RUNNER_FAILED',\n summary: runnerResult.failure.summary,\n why: runnerResult.failure.why,\n meta: runnerResult.failure.meta,\n conflicts: undefined,\n });\n }\n\n const execution = runnerResult.value;\n onProgress?.({\n action: 'dbUpdate',\n kind: 'spanEnd',\n spanId: applySpanId,\n outcome: 'ok',\n });\n\n const result: DbUpdateSuccess = {\n mode: 'apply',\n plan: {\n operations: stripOperations(migrationPlan.operations),\n },\n destination: {\n storageHash: migrationPlan.destination.storageHash,\n ...ifDefined('profileHash', migrationPlan.destination.profileHash),\n },\n execution: {\n operationsPlanned: execution.operationsPlanned,\n operationsExecuted: execution.operationsExecuted,\n },\n marker: migrationPlan.destination.profileHash\n ? {\n storageHash: migrationPlan.destination.storageHash,\n profileHash: migrationPlan.destination.profileHash,\n }\n : { storageHash: migrationPlan.destination.storageHash },\n summary:\n execution.operationsExecuted === 0\n ? 'Database already matches contract, signature updated'\n : `Applied ${execution.operationsExecuted} operation(s), signature updated`,\n };\n return ok(result);\n}\n","import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';\nimport type {\n ControlDriverInstance,\n ControlFamilyInstance,\n MigrationRunnerResult,\n TargetMigrationsCapability,\n} from '@prisma-next/framework-components/control';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport { notOk, ok } from '@prisma-next/utils/result';\nimport type {\n MigrationApplyAppliedEntry,\n MigrationApplyResult,\n MigrationApplyStep,\n OnControlProgress,\n} from '../types';\n\nexport interface ExecuteMigrationApplyOptions<TFamilyId extends string, TTargetId extends string> {\n readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;\n readonly familyInstance: ControlFamilyInstance<TFamilyId, unknown>;\n readonly originHash: string;\n readonly destinationHash: string;\n readonly pendingMigrations: readonly MigrationApplyStep[];\n readonly migrations: TargetMigrationsCapability<\n TFamilyId,\n TTargetId,\n ControlFamilyInstance<TFamilyId, unknown>\n >;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;\n readonly targetId: string;\n readonly onProgress?: OnControlProgress;\n}\n\n/**\n * Apply a sequence of migration packages against the configured driver.\n *\n * Validates the path's continuity (origin → ... → destination, no gaps),\n * then drives the family/target's migration runner over each package's\n * operations in order, surfacing per-migration progress through `onProgress`.\n *\n * The `pendingMigrations` parameter is trusted input. Callers are responsible\n * for upstream verification of the originating migration packages — typically\n * by loading them via `readMigrationPackage` from\n * `@prisma-next/migration-tools/io`, which performs hash-integrity checks at\n * the load boundary. This operation does not re-verify the packages and\n * assumes the `(metadata, ops)` pairs on disk have not been tampered with\n * since emit.\n */\nexport async function executeMigrationApply<TFamilyId extends string, TTargetId extends string>(\n options: ExecuteMigrationApplyOptions<TFamilyId, TTargetId>,\n): Promise<MigrationApplyResult> {\n const {\n driver,\n familyInstance,\n originHash,\n destinationHash,\n pendingMigrations,\n migrations,\n frameworkComponents,\n targetId,\n onProgress,\n } = options;\n\n if (pendingMigrations.length === 0) {\n if (originHash !== destinationHash) {\n return notOk({\n code: 'MIGRATION_PATH_NOT_FOUND' as const,\n summary: 'No migrations provided for requested origin and destination',\n why: `Requested ${originHash} -> ${destinationHash} but pendingMigrations is empty`,\n meta: { originHash, destinationHash },\n });\n }\n return ok({\n migrationsApplied: 0,\n markerHash: originHash,\n applied: [],\n summary: 'Already up to date',\n });\n }\n\n const firstMigration = pendingMigrations[0]!;\n const lastMigration = pendingMigrations[pendingMigrations.length - 1]!;\n // Manifest `from` is `string | null` (null = baseline). The live-marker\n // layer encodes \"no prior state\" as EMPTY_CONTRACT_HASH; bridge here so the\n // string comparisons below work uniformly.\n const firstFromMarker = firstMigration.from ?? EMPTY_CONTRACT_HASH;\n if (firstFromMarker !== originHash || lastMigration.to !== destinationHash) {\n return notOk({\n code: 'MIGRATION_PATH_NOT_FOUND' as const,\n summary: 'Migration apply path does not match requested origin and destination',\n why: `Path resolved as ${firstFromMarker} -> ${lastMigration.to}, but requested ${originHash} -> ${destinationHash}`,\n meta: {\n originHash,\n destinationHash,\n pathOrigin: firstFromMarker,\n pathDestination: lastMigration.to,\n },\n });\n }\n\n for (let i = 1; i < pendingMigrations.length; i++) {\n const previous = pendingMigrations[i - 1]!;\n const current = pendingMigrations[i]!;\n const currentFromMarker = current.from ?? EMPTY_CONTRACT_HASH;\n if (previous.to !== currentFromMarker) {\n return notOk({\n code: 'MIGRATION_PATH_NOT_FOUND' as const,\n summary: 'Migration apply path contains a discontinuity between adjacent migrations',\n why: `Migration \"${previous.dirName}\" ends at ${previous.to}, but next migration \"${current.dirName}\" starts at ${currentFromMarker}`,\n meta: {\n originHash,\n destinationHash,\n previousDirName: previous.dirName,\n previousTo: previous.to,\n currentDirName: current.dirName,\n currentFrom: currentFromMarker,\n discontinuityIndex: i,\n },\n });\n }\n }\n\n const runner = migrations.createRunner(familyInstance);\n const applied: MigrationApplyAppliedEntry[] = [];\n\n for (const migration of pendingMigrations) {\n const migrationSpanId = `migration:${migration.dirName}`;\n onProgress?.({\n action: 'migrationApply',\n kind: 'spanStart',\n spanId: migrationSpanId,\n label: `Applying ${migration.dirName}`,\n });\n\n const { operations } = migration;\n\n // Allow all operation classes. The policy gate belongs at plan time, not\n // apply time — the planner already decided what to emit. Restricting here\n // would be a tautology (the allowed set would just mirror what's in ops).\n const policy = {\n allowedOperationClasses: ['additive', 'widening', 'destructive', 'data'] as const,\n };\n\n // Manifest `from === null` means \"no prior state\" — the runner expects\n // `origin: null` for a fresh database (no marker present).\n const plan = {\n targetId,\n origin: migration.from === null ? null : { storageHash: migration.from },\n destination: { storageHash: migration.to },\n operations,\n };\n\n const destinationContract = familyInstance.validateContract(migration.toContract);\n\n const runnerResult: MigrationRunnerResult = await runner.execute({\n plan,\n driver,\n destinationContract,\n policy,\n executionChecks: {\n prechecks: true,\n postchecks: true,\n idempotencyChecks: true,\n },\n frameworkComponents,\n });\n\n if (!runnerResult.ok) {\n onProgress?.({\n action: 'migrationApply',\n kind: 'spanEnd',\n spanId: migrationSpanId,\n outcome: 'error',\n });\n return notOk({\n code: 'RUNNER_FAILED' as const,\n summary: runnerResult.failure.summary,\n why: runnerResult.failure.why,\n meta: {\n migration: migration.dirName,\n from: migration.from,\n to: migration.to,\n ...(runnerResult.failure.meta ?? {}),\n },\n });\n }\n\n onProgress?.({\n action: 'migrationApply',\n kind: 'spanEnd',\n spanId: migrationSpanId,\n outcome: 'ok',\n });\n\n applied.push({\n dirName: migration.dirName,\n from: migration.from,\n to: migration.to,\n operationsExecuted: runnerResult.value.operationsExecuted,\n });\n }\n\n const finalHash = pendingMigrations[pendingMigrations.length - 1]!.to;\n const totalOps = applied.reduce((sum, a) => sum + a.operationsExecuted, 0);\n\n return ok({\n migrationsApplied: applied.length,\n markerHash: finalHash,\n applied,\n summary: `Applied ${applied.length} migration(s) (${totalOps} operation(s)), marker at ${finalHash}`,\n });\n}\n","import type { Contract, ContractMarkerRecord } from '@prisma-next/contract/types';\nimport { emit as emitContractArtifacts } from '@prisma-next/emitter';\nimport type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';\nimport type {\n ControlDriverInstance,\n ControlFamilyInstance,\n ControlStack,\n CoreSchemaView,\n MigrationPlanOperation,\n OperationPreview,\n SignDatabaseResult,\n VerifyDatabaseResult,\n VerifyDatabaseSchemaResult,\n} from '@prisma-next/framework-components/control';\nimport {\n createControlStack,\n hasMigrations,\n hasOperationPreview,\n hasPslContractInfer,\n hasSchemaView,\n} from '@prisma-next/framework-components/control';\nimport type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok } from '@prisma-next/utils/result';\nimport { assertFrameworkComponentsCompatible } from '../utils/framework-components';\nimport { enrichContract } from './contract-enrichment';\nimport { ContractValidationError } from './errors';\nimport { executeDbInit } from './operations/db-init';\nimport { executeDbUpdate } from './operations/db-update';\nimport { executeMigrationApply } from './operations/migration-apply';\nimport type {\n ControlActionName,\n ControlClient,\n ControlClientOptions,\n DbInitOptions,\n DbInitResult,\n DbUpdateOptions,\n DbUpdateResult,\n EmitOptions,\n EmitResult,\n IntrospectOptions,\n MigrationApplyOptions,\n MigrationApplyResult,\n OnControlProgress,\n SchemaVerifyOptions,\n SignOptions,\n VerifyOptions,\n} from './types';\n\n/**\n * Creates a programmatic control client for Prisma Next operations.\n *\n * The client accepts framework component descriptors at creation time,\n * manages driver lifecycle via connect()/close(), and exposes domain\n * operations that delegate to the existing family instance methods.\n *\n * @see {@link ControlClient} for the client interface\n * @see README.md \"Programmatic Control API\" section for usage examples\n */\nexport function createControlClient(options: ControlClientOptions): ControlClient {\n return new ControlClientImpl(options);\n}\n\n/**\n * Implementation of ControlClient.\n * Manages initialization and connection state, delegates operations to family instance.\n */\nclass ControlClientImpl implements ControlClient {\n private readonly options: ControlClientOptions;\n private stack: ControlStack | null = null;\n private driver: ControlDriverInstance<string, string> | null = null;\n private familyInstance: ControlFamilyInstance<string, unknown> | null = null;\n private frameworkComponents: ReadonlyArray<\n TargetBoundComponentDescriptor<string, string>\n > | null = null;\n private initialized = false;\n private readonly defaultConnection: unknown;\n\n constructor(options: ControlClientOptions) {\n this.options = options;\n this.defaultConnection = options.connection;\n }\n\n init(): void {\n if (this.initialized) {\n return; // Idempotent\n }\n\n this.stack = createControlStack({\n family: this.options.family,\n target: this.options.target,\n adapter: this.options.adapter,\n driver: this.options.driver,\n extensionPacks: this.options.extensionPacks,\n });\n\n this.familyInstance = this.options.family.create(this.stack);\n\n // Validate and type-narrow framework components\n const rawComponents = [\n this.options.target,\n this.options.adapter,\n ...(this.options.extensionPacks ?? []),\n ];\n this.frameworkComponents = assertFrameworkComponentsCompatible(\n this.options.family.familyId,\n this.options.target.targetId,\n rawComponents,\n );\n\n this.initialized = true;\n }\n\n async connect(connection?: unknown): Promise<void> {\n // Auto-init if needed\n this.init();\n\n if (this.driver) {\n throw new Error('Already connected. Call close() before reconnecting.');\n }\n\n // Resolve connection: argument > default from options\n const resolvedConnection = connection ?? this.defaultConnection;\n if (resolvedConnection === undefined) {\n throw new Error(\n 'No connection provided. Pass a connection to connect() or provide a default connection when creating the client.',\n );\n }\n\n // Check for driver descriptor\n if (!this.stack?.driver) {\n throw new Error(\n 'Driver is not configured. Pass a driver descriptor when creating the control client to enable database operations.',\n );\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: required for runtime connection type flexibility\n this.driver = await this.stack.driver.create(resolvedConnection as any);\n }\n\n async close(): Promise<void> {\n if (this.driver) {\n await this.driver.close();\n this.driver = null;\n }\n }\n\n private async ensureConnected(): Promise<{\n driver: ControlDriverInstance<string, string>;\n familyInstance: ControlFamilyInstance<string, unknown>;\n frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<string, string>>;\n }> {\n // Auto-init if needed\n this.init();\n\n // Auto-connect if not connected and default connection is available\n if (!this.driver && this.defaultConnection !== undefined) {\n await this.connect(this.defaultConnection);\n }\n\n if (!this.driver || !this.familyInstance || !this.frameworkComponents) {\n throw new Error('Not connected. Call connect(connection) first.');\n }\n return {\n driver: this.driver,\n familyInstance: this.familyInstance,\n frameworkComponents: this.frameworkComponents,\n };\n }\n\n private async connectWithProgress(\n connection: unknown | undefined,\n action: ControlActionName,\n onProgress?: OnControlProgress,\n ): Promise<void> {\n if (connection === undefined) return;\n onProgress?.({\n action,\n kind: 'spanStart',\n spanId: 'connect',\n label: 'Connecting to database...',\n });\n try {\n await this.connect(connection);\n onProgress?.({ action, kind: 'spanEnd', spanId: 'connect', outcome: 'ok' });\n } catch (error) {\n onProgress?.({ action, kind: 'spanEnd', spanId: 'connect', outcome: 'error' });\n throw error;\n }\n }\n\n async verify(options: VerifyOptions): Promise<VerifyDatabaseResult> {\n const { onProgress } = options;\n await this.connectWithProgress(options.connection, 'verify', onProgress);\n const { driver, familyInstance } = await this.ensureConnected();\n\n // Validate contract using family instance\n let contract: Contract;\n try {\n contract = familyInstance.validateContract(options.contract);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new ContractValidationError(message, error);\n }\n\n // Emit verify span\n onProgress?.({\n action: 'verify',\n kind: 'spanStart',\n spanId: 'verify',\n label: 'Verifying database marker...',\n });\n\n try {\n // Delegate to family instance verify method\n // Note: We pass empty strings for contractPath/configPath since the programmatic\n // API doesn't deal with file paths. The family instance accepts these as optional\n // metadata for error reporting.\n const result = await familyInstance.verify({\n driver,\n contract,\n expectedTargetId: this.options.target.targetId,\n contractPath: '',\n });\n\n onProgress?.({\n action: 'verify',\n kind: 'spanEnd',\n spanId: 'verify',\n outcome: result.ok ? 'ok' : 'error',\n });\n\n return result;\n } catch (error) {\n onProgress?.({\n action: 'verify',\n kind: 'spanEnd',\n spanId: 'verify',\n outcome: 'error',\n });\n throw error;\n }\n }\n\n async schemaVerify(options: SchemaVerifyOptions): Promise<VerifyDatabaseSchemaResult> {\n const { onProgress } = options;\n await this.connectWithProgress(options.connection, 'schemaVerify', onProgress);\n const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();\n\n // Validate contract using family instance\n let contract: Contract;\n try {\n contract = familyInstance.validateContract(options.contract);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new ContractValidationError(message, error);\n }\n\n // Emit schemaVerify span\n onProgress?.({\n action: 'schemaVerify',\n kind: 'spanStart',\n spanId: 'schemaVerify',\n label: 'Verifying database schema...',\n });\n\n try {\n // Delegate to family instance schemaVerify method\n const result = await familyInstance.schemaVerify({\n driver,\n contract,\n strict: options.strict ?? false,\n contractPath: '',\n frameworkComponents,\n });\n\n onProgress?.({\n action: 'schemaVerify',\n kind: 'spanEnd',\n spanId: 'schemaVerify',\n outcome: result.ok ? 'ok' : 'error',\n });\n\n return result;\n } catch (error) {\n onProgress?.({\n action: 'schemaVerify',\n kind: 'spanEnd',\n spanId: 'schemaVerify',\n outcome: 'error',\n });\n throw error;\n }\n }\n\n async sign(options: SignOptions): Promise<SignDatabaseResult> {\n const { onProgress } = options;\n await this.connectWithProgress(options.connection, 'sign', onProgress);\n const { driver, familyInstance } = await this.ensureConnected();\n\n // Validate contract using family instance\n let contract: Contract;\n try {\n contract = familyInstance.validateContract(options.contract);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new ContractValidationError(message, error);\n }\n\n // Emit sign span\n onProgress?.({\n action: 'sign',\n kind: 'spanStart',\n spanId: 'sign',\n label: 'Signing database...',\n });\n\n try {\n // Delegate to family instance sign method\n const result = await familyInstance.sign({\n driver,\n contract,\n contractPath: options.contractPath ?? '',\n ...ifDefined('configPath', options.configPath),\n });\n\n onProgress?.({\n action: 'sign',\n kind: 'spanEnd',\n spanId: 'sign',\n outcome: 'ok',\n });\n\n return result;\n } catch (error) {\n onProgress?.({\n action: 'sign',\n kind: 'spanEnd',\n spanId: 'sign',\n outcome: 'error',\n });\n throw error;\n }\n }\n\n async dbInit(options: DbInitOptions): Promise<DbInitResult> {\n const { onProgress } = options;\n await this.connectWithProgress(options.connection, 'dbInit', onProgress);\n const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();\n\n if (!hasMigrations(this.options.target)) {\n throw new Error(`Target \"${this.options.target.targetId}\" does not support migrations`);\n }\n\n let contract: Contract;\n try {\n contract = familyInstance.validateContract(options.contract);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new ContractValidationError(message, error);\n }\n\n return executeDbInit({\n driver,\n familyInstance,\n contract,\n mode: options.mode,\n migrations: this.options.target.migrations,\n frameworkComponents,\n ...ifDefined('onProgress', onProgress),\n });\n }\n\n async dbUpdate(options: DbUpdateOptions): Promise<DbUpdateResult> {\n const { onProgress } = options;\n await this.connectWithProgress(options.connection, 'dbUpdate', onProgress);\n const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();\n\n if (!hasMigrations(this.options.target)) {\n throw new Error(`Target \"${this.options.target.targetId}\" does not support migrations`);\n }\n\n let contract: Contract;\n try {\n contract = familyInstance.validateContract(options.contract);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new ContractValidationError(message, error);\n }\n\n return executeDbUpdate({\n driver,\n familyInstance,\n contract,\n mode: options.mode,\n migrations: this.options.target.migrations,\n frameworkComponents,\n ...ifDefined('acceptDataLoss', options.acceptDataLoss),\n ...ifDefined('onProgress', onProgress),\n });\n }\n\n async readMarker(): Promise<ContractMarkerRecord | null> {\n const { driver, familyInstance } = await this.ensureConnected();\n return familyInstance.readMarker({ driver });\n }\n\n async migrationApply(options: MigrationApplyOptions): Promise<MigrationApplyResult> {\n const { onProgress } = options;\n await this.connectWithProgress(options.connection, 'migrationApply', onProgress);\n const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();\n\n if (!hasMigrations(this.options.target)) {\n throw new Error(`Target \"${this.options.target.targetId}\" does not support migrations`);\n }\n\n return executeMigrationApply({\n driver,\n familyInstance,\n originHash: options.originHash,\n destinationHash: options.destinationHash,\n pendingMigrations: options.pendingMigrations,\n migrations: this.options.target.migrations,\n frameworkComponents,\n targetId: this.options.target.targetId,\n ...(onProgress ? { onProgress } : {}),\n });\n }\n\n async introspect(options?: IntrospectOptions): Promise<unknown> {\n const onProgress = options?.onProgress;\n await this.connectWithProgress(options?.connection, 'introspect', onProgress);\n const { driver, familyInstance } = await this.ensureConnected();\n\n // TODO: Pass schema option to familyInstance.introspect when schema filtering is implemented\n const _schema = options?.schema;\n void _schema;\n\n // Emit introspect span\n onProgress?.({\n action: 'introspect',\n kind: 'spanStart',\n spanId: 'introspect',\n label: 'Introspecting database schema...',\n });\n\n try {\n const result = await familyInstance.introspect({ driver });\n\n onProgress?.({\n action: 'introspect',\n kind: 'spanEnd',\n spanId: 'introspect',\n outcome: 'ok',\n });\n\n return result;\n } catch (error) {\n onProgress?.({\n action: 'introspect',\n kind: 'spanEnd',\n spanId: 'introspect',\n outcome: 'error',\n });\n throw error;\n }\n }\n\n toSchemaView(schemaIR: unknown): CoreSchemaView | undefined {\n this.init();\n if (this.familyInstance && hasSchemaView(this.familyInstance)) {\n return this.familyInstance.toSchemaView(schemaIR);\n }\n return undefined;\n }\n\n inferPslContract(schemaIR: unknown): PslDocumentAst | undefined {\n this.init();\n if (this.familyInstance && hasPslContractInfer(this.familyInstance)) {\n return this.familyInstance.inferPslContract(schemaIR);\n }\n return undefined;\n }\n\n toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview | undefined {\n this.init();\n if (this.familyInstance && hasOperationPreview(this.familyInstance)) {\n return this.familyInstance.toOperationPreview(operations);\n }\n return undefined;\n }\n\n async emit(options: EmitOptions): Promise<EmitResult> {\n const { onProgress, contractConfig } = options;\n\n // Ensure initialized (creates stack and family instance)\n // emit() does NOT require a database connection\n this.init();\n\n if (!this.familyInstance) {\n throw new Error('Family instance was not initialized. This is a bug.');\n }\n\n let contractRaw: unknown;\n onProgress?.({\n action: 'emit',\n kind: 'spanStart',\n spanId: 'resolveSource',\n label: 'Resolving contract source...',\n });\n\n try {\n const stack = this.stack!;\n const sourceContext = {\n composedExtensionPacks: stack.extensionPacks.map((p) => p.id),\n scalarTypeDescriptors: stack.scalarTypeDescriptors,\n authoringContributions: stack.authoringContributions,\n codecLookup: stack.codecLookup,\n controlMutationDefaults: stack.controlMutationDefaults,\n resolvedInputs: contractConfig.source.inputs ?? [],\n };\n const providerResult = await contractConfig.source.load(sourceContext);\n if (!providerResult.ok) {\n onProgress?.({\n action: 'emit',\n kind: 'spanEnd',\n spanId: 'resolveSource',\n outcome: 'error',\n });\n\n return notOk({\n code: 'CONTRACT_SOURCE_INVALID',\n summary: providerResult.failure.summary,\n why: providerResult.failure.summary,\n meta: providerResult.failure.meta,\n diagnostics: providerResult.failure,\n });\n }\n contractRaw = providerResult.value;\n\n onProgress?.({\n action: 'emit',\n kind: 'spanEnd',\n spanId: 'resolveSource',\n outcome: 'ok',\n });\n } catch (error) {\n onProgress?.({\n action: 'emit',\n kind: 'spanEnd',\n spanId: 'resolveSource',\n outcome: 'error',\n });\n\n const message = error instanceof Error ? error.message : String(error);\n return notOk({\n code: 'CONTRACT_SOURCE_INVALID',\n summary: 'Failed to resolve contract source',\n why: message,\n diagnostics: {\n summary: 'Contract source provider threw an exception',\n diagnostics: [\n {\n code: 'PROVIDER_THROW',\n message,\n },\n ],\n },\n meta: undefined,\n });\n }\n\n // Emit contract\n onProgress?.({\n action: 'emit',\n kind: 'spanStart',\n spanId: 'emit',\n label: 'Emitting contract...',\n });\n\n try {\n const enrichedIR = enrichContract(contractRaw as Contract, this.frameworkComponents ?? []);\n\n try {\n this.familyInstance.validateContract(enrichedIR);\n } catch (error) {\n onProgress?.({\n action: 'emit',\n kind: 'spanEnd',\n spanId: 'emit',\n outcome: 'error',\n });\n const message = error instanceof Error ? error.message : String(error);\n return notOk({\n code: 'CONTRACT_VALIDATION_FAILED',\n summary: 'Contract validation failed',\n why: message,\n meta: undefined,\n });\n }\n\n const result = await emitContractArtifacts(\n enrichedIR,\n this.stack!,\n this.options.family.emission,\n );\n\n onProgress?.({\n action: 'emit',\n kind: 'spanEnd',\n spanId: 'emit',\n outcome: 'ok',\n });\n\n return ok({\n storageHash: result.storageHash,\n ...ifDefined('executionHash', result.executionHash),\n profileHash: result.profileHash,\n contractJson: result.contractJson,\n contractDts: result.contractDts,\n });\n } catch (error) {\n onProgress?.({\n action: 'emit',\n kind: 'spanEnd',\n spanId: 'emit',\n outcome: 'error',\n });\n\n return notOk({\n code: 'EMIT_FAILED',\n summary: 'Failed to emit contract',\n why: error instanceof Error ? error.message : String(error),\n meta: undefined,\n });\n }\n }\n}\n"],"mappings":"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"}
|