@prisma-next/migration-tools 0.4.0-dev.8 → 0.4.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 +1 -1
- package/dist/{attestation-DnebS4XZ.mjs → attestation-DtF8tEOM.mjs} +24 -23
- package/dist/attestation-DtF8tEOM.mjs.map +1 -0
- package/dist/{errors-C_XuSbX7.mjs → errors-BKbRGCJM.mjs} +9 -2
- package/dist/errors-BKbRGCJM.mjs.map +1 -0
- package/dist/exports/attestation.d.mts +20 -6
- package/dist/exports/attestation.d.mts.map +1 -1
- package/dist/exports/attestation.mjs +3 -3
- package/dist/exports/dag.d.mts +8 -6
- package/dist/exports/dag.d.mts.map +1 -1
- package/dist/exports/dag.mjs +181 -107
- package/dist/exports/dag.mjs.map +1 -1
- package/dist/exports/io.d.mts +16 -13
- package/dist/exports/io.d.mts.map +1 -1
- package/dist/exports/io.mjs +2 -2
- package/dist/exports/migration-ts.d.mts +10 -20
- package/dist/exports/migration-ts.d.mts.map +1 -1
- package/dist/exports/migration-ts.mjs +23 -35
- package/dist/exports/migration-ts.mjs.map +1 -1
- package/dist/exports/migration.d.mts +1 -1
- package/dist/exports/migration.mjs +20 -13
- package/dist/exports/migration.mjs.map +1 -1
- package/dist/exports/refs.mjs +1 -1
- package/dist/exports/types.d.mts +2 -2
- package/dist/exports/types.mjs +2 -16
- package/dist/{io-Cun81AIZ.mjs → io-CCnYsUHU.mjs} +18 -22
- package/dist/io-CCnYsUHU.mjs.map +1 -0
- package/dist/types-DyGXcWWp.d.mts +71 -0
- package/dist/types-DyGXcWWp.d.mts.map +1 -0
- package/package.json +5 -4
- package/src/attestation.ts +34 -26
- package/src/dag.ts +140 -154
- package/src/errors.ts +8 -0
- package/src/exports/attestation.ts +2 -1
- package/src/exports/io.ts +1 -1
- package/src/exports/migration-ts.ts +1 -1
- package/src/exports/types.ts +2 -8
- package/src/graph-ops.ts +65 -0
- package/src/io.ts +23 -24
- package/src/migration-base.ts +21 -13
- package/src/migration-ts.ts +23 -49
- package/src/queue.ts +37 -0
- package/src/types.ts +15 -55
- package/dist/attestation-DnebS4XZ.mjs.map +0 -1
- package/dist/errors-C_XuSbX7.mjs.map +0 -1
- package/dist/exports/types.mjs.map +0 -1
- package/dist/io-Cun81AIZ.mjs.map +0 -1
- package/dist/types-D2uX4ql7.d.mts +0 -100
- package/dist/types-D2uX4ql7.d.mts.map +0 -1
package/src/graph-ops.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Queue } from './queue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One step of a BFS traversal.
|
|
5
|
+
*
|
|
6
|
+
* `parent` and `incomingEdge` are `null` for start nodes — they were not
|
|
7
|
+
* reached via any edge. For every other node they record the node and edge
|
|
8
|
+
* by which this node was first reached.
|
|
9
|
+
*/
|
|
10
|
+
export interface BfsStep<E> {
|
|
11
|
+
readonly node: string;
|
|
12
|
+
readonly parent: string | null;
|
|
13
|
+
readonly incomingEdge: E | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generic breadth-first traversal.
|
|
18
|
+
*
|
|
19
|
+
* Direction (forward/reverse) is expressed by the caller's `neighbours`
|
|
20
|
+
* closure: return `{ next, edge }` pairs where `next` is the node to visit
|
|
21
|
+
* next and `edge` is the edge that connects them. Callers that don't need
|
|
22
|
+
* path reconstruction can ignore the `parent`/`incomingEdge` fields of each
|
|
23
|
+
* yielded step.
|
|
24
|
+
*
|
|
25
|
+
* Stops are intrinsic — callers `break` out of the `for..of` loop when
|
|
26
|
+
* they've found what they're looking for.
|
|
27
|
+
*
|
|
28
|
+
* `ordering`, if provided, controls the order in which neighbours of each
|
|
29
|
+
* node are enqueued. Only matters for path-finding: a deterministic ordering
|
|
30
|
+
* makes BFS return a deterministic shortest path when multiple exist.
|
|
31
|
+
*/
|
|
32
|
+
export function* bfs<E>(
|
|
33
|
+
starts: Iterable<string>,
|
|
34
|
+
neighbours: (node: string) => Iterable<{ next: string; edge: E }>,
|
|
35
|
+
ordering?: (items: readonly { next: string; edge: E }[]) => readonly { next: string; edge: E }[],
|
|
36
|
+
): Generator<BfsStep<E>> {
|
|
37
|
+
const visited = new Set<string>();
|
|
38
|
+
const parentMap = new Map<string, { parent: string; edge: E }>();
|
|
39
|
+
const queue = new Queue<string>();
|
|
40
|
+
for (const start of starts) {
|
|
41
|
+
if (!visited.has(start)) {
|
|
42
|
+
visited.add(start);
|
|
43
|
+
queue.push(start);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
while (!queue.isEmpty) {
|
|
47
|
+
const current = queue.shift();
|
|
48
|
+
const parentInfo = parentMap.get(current);
|
|
49
|
+
yield {
|
|
50
|
+
node: current,
|
|
51
|
+
parent: parentInfo?.parent ?? null,
|
|
52
|
+
incomingEdge: parentInfo?.edge ?? null,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const items = neighbours(current);
|
|
56
|
+
const toVisit = ordering ? ordering([...items]) : items;
|
|
57
|
+
for (const { next, edge } of toVisit) {
|
|
58
|
+
if (!visited.has(next)) {
|
|
59
|
+
visited.add(next);
|
|
60
|
+
parentMap.set(next, { parent: current, edge });
|
|
61
|
+
queue.push(next);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/io.ts
CHANGED
|
@@ -3,12 +3,13 @@ import { type } from 'arktype';
|
|
|
3
3
|
import { basename, dirname, join } from 'pathe';
|
|
4
4
|
import {
|
|
5
5
|
errorDirectoryExists,
|
|
6
|
+
errorInvalidDestName,
|
|
6
7
|
errorInvalidJson,
|
|
7
8
|
errorInvalidManifest,
|
|
8
9
|
errorInvalidSlug,
|
|
9
10
|
errorMissingFile,
|
|
10
11
|
} from './errors';
|
|
11
|
-
import type {
|
|
12
|
+
import type { MigrationBundle, MigrationManifest, MigrationOps } from './types';
|
|
12
13
|
|
|
13
14
|
const MANIFEST_FILE = 'migration.json';
|
|
14
15
|
const OPS_FILE = 'ops.json';
|
|
@@ -22,13 +23,12 @@ const MigrationHintsSchema = type({
|
|
|
22
23
|
used: 'string[]',
|
|
23
24
|
applied: 'string[]',
|
|
24
25
|
plannerVersion: 'string',
|
|
25
|
-
planningStrategy: 'string',
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
const MigrationManifestSchema = type({
|
|
29
29
|
from: 'string',
|
|
30
30
|
to: 'string',
|
|
31
|
-
migrationId: 'string
|
|
31
|
+
migrationId: 'string',
|
|
32
32
|
kind: "'regular' | 'baseline'",
|
|
33
33
|
fromContract: 'object | null',
|
|
34
34
|
toContract: 'object',
|
|
@@ -75,27 +75,26 @@ export async function writeMigrationPackage(
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
|
-
* Copy
|
|
79
|
-
* colocated `contract.d.ts`) into the migration package directory so
|
|
80
|
-
* authors of the scaffolded `migration.ts` can import the typed
|
|
81
|
-
* contract relative to the migration directory
|
|
82
|
-
* (`import type { Contract } from './contract'`).
|
|
78
|
+
* Copy a list of files into `destDir`, optionally renaming each one.
|
|
83
79
|
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
80
|
+
* The destination directory is created (with `recursive: true`) if it
|
|
81
|
+
* does not already exist. Each source path is copied byte-for-byte into
|
|
82
|
+
* `destDir/<destName>`; missing sources throw `ENOENT`. The helper is
|
|
83
|
+
* intentionally generic: callers own the list of files (e.g. a contract
|
|
84
|
+
* emitter's emitted output) and the naming convention (e.g. renaming
|
|
85
|
+
* the destination contract to `end-contract.*` and the source contract
|
|
86
|
+
* to `start-contract.*`).
|
|
87
87
|
*/
|
|
88
|
-
export async function
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
export async function copyFilesWithRename(
|
|
89
|
+
destDir: string,
|
|
90
|
+
files: readonly { readonly sourcePath: string; readonly destName: string }[],
|
|
91
91
|
): Promise<void> {
|
|
92
|
-
await
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
throw error;
|
|
92
|
+
await mkdir(destDir, { recursive: true });
|
|
93
|
+
for (const file of files) {
|
|
94
|
+
if (basename(file.destName) !== file.destName) {
|
|
95
|
+
throw errorInvalidDestName(file.destName);
|
|
96
|
+
}
|
|
97
|
+
await copyFile(file.sourcePath, join(destDir, file.destName));
|
|
99
98
|
}
|
|
100
99
|
}
|
|
101
100
|
|
|
@@ -110,7 +109,7 @@ export async function writeMigrationOps(dir: string, ops: MigrationOps): Promise
|
|
|
110
109
|
await writeFile(join(dir, OPS_FILE), `${JSON.stringify(ops, null, 2)}\n`);
|
|
111
110
|
}
|
|
112
111
|
|
|
113
|
-
export async function readMigrationPackage(dir: string): Promise<
|
|
112
|
+
export async function readMigrationPackage(dir: string): Promise<MigrationBundle> {
|
|
114
113
|
const manifestPath = join(dir, MANIFEST_FILE);
|
|
115
114
|
const opsPath = join(dir, OPS_FILE);
|
|
116
115
|
|
|
@@ -178,7 +177,7 @@ function validateOps(ops: unknown, filePath: string): asserts ops is MigrationOp
|
|
|
178
177
|
|
|
179
178
|
export async function readMigrationsDir(
|
|
180
179
|
migrationsRoot: string,
|
|
181
|
-
): Promise<readonly
|
|
180
|
+
): Promise<readonly MigrationBundle[]> {
|
|
182
181
|
let entries: string[];
|
|
183
182
|
try {
|
|
184
183
|
entries = await readdir(migrationsRoot);
|
|
@@ -189,7 +188,7 @@ export async function readMigrationsDir(
|
|
|
189
188
|
throw error;
|
|
190
189
|
}
|
|
191
190
|
|
|
192
|
-
const packages:
|
|
191
|
+
const packages: MigrationBundle[] = [];
|
|
193
192
|
|
|
194
193
|
for (const entry of entries.sort()) {
|
|
195
194
|
const entryPath = join(migrationsRoot, entry);
|
package/src/migration-base.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { ifDefined } from '@prisma-next/utils/defined';
|
|
|
9
9
|
import { type } from 'arktype';
|
|
10
10
|
import { dirname, join } from 'pathe';
|
|
11
11
|
import { computeMigrationId } from './attestation';
|
|
12
|
-
import type { MigrationManifest, MigrationOps } from './types';
|
|
12
|
+
import type { MigrationHints, MigrationManifest, MigrationOps } from './types';
|
|
13
13
|
|
|
14
14
|
export interface MigrationMeta {
|
|
15
15
|
readonly from: string;
|
|
@@ -26,7 +26,7 @@ const MigrationMetaSchema = type({
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Base class for
|
|
29
|
+
* Base class for migrations.
|
|
30
30
|
*
|
|
31
31
|
* A `Migration` subclass is itself a `MigrationPlan`: CLI commands and the
|
|
32
32
|
* runner can consume it directly via `targetId`, `operations`, `origin`, and
|
|
@@ -140,9 +140,8 @@ function printHelp(): void {
|
|
|
140
140
|
* schema-conformant manifest so the resulting package can still be read,
|
|
141
141
|
* verified, and applied.
|
|
142
142
|
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* draft (`migrationId: null`) ever leaves this function.
|
|
143
|
+
* The `migrationId` is recomputed against the current manifest + ops so
|
|
144
|
+
* the on-disk artifacts are always fully attested.
|
|
146
145
|
*/
|
|
147
146
|
function buildAttestedManifest(
|
|
148
147
|
migrationDir: string,
|
|
@@ -151,8 +150,7 @@ function buildAttestedManifest(
|
|
|
151
150
|
): MigrationManifest {
|
|
152
151
|
const existing = readExistingManifest(join(migrationDir, 'migration.json'));
|
|
153
152
|
|
|
154
|
-
const baseManifest: MigrationManifest = {
|
|
155
|
-
migrationId: null,
|
|
153
|
+
const baseManifest: Omit<MigrationManifest, 'migrationId'> = {
|
|
156
154
|
from: meta.from,
|
|
157
155
|
to: meta.to,
|
|
158
156
|
kind: meta.kind ?? 'regular',
|
|
@@ -165,12 +163,7 @@ function buildAttestedManifest(
|
|
|
165
163
|
// (everything else is stripped by `computeMigrationId`), and a real
|
|
166
164
|
// contract bookend would only be available after `migration plan`.
|
|
167
165
|
toContract: existing?.toContract ?? ({ storage: { storageHash: meta.to } } as Contract),
|
|
168
|
-
hints: existing?.hints
|
|
169
|
-
used: [],
|
|
170
|
-
applied: [],
|
|
171
|
-
plannerVersion: '2.0.0',
|
|
172
|
-
planningStrategy: 'class-based',
|
|
173
|
-
},
|
|
166
|
+
hints: normalizeHints(existing?.hints),
|
|
174
167
|
...ifDefined('authorship', existing?.authorship),
|
|
175
168
|
};
|
|
176
169
|
|
|
@@ -178,6 +171,21 @@ function buildAttestedManifest(
|
|
|
178
171
|
return { ...baseManifest, migrationId };
|
|
179
172
|
}
|
|
180
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Project `existing.hints` down to the known `MigrationHints` shape, dropping
|
|
176
|
+
* any legacy keys that may linger in manifests scaffolded by older CLI
|
|
177
|
+
* versions (e.g. `planningStrategy`). Picking fields explicitly instead of
|
|
178
|
+
* spreading keeps refreshed `migration.json` files schema-clean regardless
|
|
179
|
+
* of what was on disk before.
|
|
180
|
+
*/
|
|
181
|
+
function normalizeHints(existing: MigrationHints | undefined): MigrationHints {
|
|
182
|
+
return {
|
|
183
|
+
used: existing?.used ?? [],
|
|
184
|
+
applied: existing?.applied ?? [],
|
|
185
|
+
plannerVersion: existing?.plannerVersion ?? '2.0.0',
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
181
189
|
function readExistingManifest(manifestPath: string): Partial<MigrationManifest> | null {
|
|
182
190
|
let raw: string;
|
|
183
191
|
try {
|
package/src/migration-ts.ts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utilities for reading/writing `migration.ts` files.
|
|
3
3
|
*
|
|
4
|
-
* Rendering migration.ts source is
|
|
5
|
-
* obtains source strings
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* here is limited to file I/O: writing the returned source with the right
|
|
9
|
-
* executable bit, probing for existence, and evaluating legacy descriptor-
|
|
10
|
-
* flow files.
|
|
4
|
+
* Rendering migration.ts source is the target's responsibility — the CLI
|
|
5
|
+
* obtains source strings from a planner's `plan.renderTypeScript()`. The
|
|
6
|
+
* helper here is limited to file I/O: writing the returned source with the
|
|
7
|
+
* right executable bit and probing for existence.
|
|
11
8
|
*/
|
|
12
9
|
|
|
13
10
|
import { stat, writeFile } from 'node:fs/promises';
|
|
14
|
-
import { join
|
|
11
|
+
import { join } from 'pathe';
|
|
12
|
+
import { format } from 'prettier';
|
|
15
13
|
|
|
16
14
|
const MIGRATION_TS_FILE = 'migration.ts';
|
|
17
15
|
|
|
@@ -20,16 +18,31 @@ const MIGRATION_TS_FILE = 'migration.ts';
|
|
|
20
18
|
* directory. If the source begins with a shebang, the file is written with
|
|
21
19
|
* executable permissions (0o755) so it can be run directly via
|
|
22
20
|
* `./migration.ts` by the authoring class's `Migration.run(...)` guard.
|
|
21
|
+
*
|
|
22
|
+
* The source is run through prettier before writing so migration renderers
|
|
23
|
+
* can produce structurally-correct but loosely-indented source and rely on
|
|
24
|
+
* a single canonical format on disk. Matches what `@prisma-next/emitter`
|
|
25
|
+
* already does for generated `contract.d.ts`.
|
|
23
26
|
*/
|
|
24
27
|
export async function writeMigrationTs(packageDir: string, content: string): Promise<void> {
|
|
25
|
-
const
|
|
28
|
+
const formatted = await formatMigrationTsSource(content);
|
|
29
|
+
const isExecutable = formatted.startsWith('#!');
|
|
26
30
|
await writeFile(
|
|
27
31
|
join(packageDir, MIGRATION_TS_FILE),
|
|
28
|
-
|
|
32
|
+
formatted,
|
|
29
33
|
isExecutable ? { mode: 0o755 } : undefined,
|
|
30
34
|
);
|
|
31
35
|
}
|
|
32
36
|
|
|
37
|
+
async function formatMigrationTsSource(source: string): Promise<string> {
|
|
38
|
+
return format(source, {
|
|
39
|
+
parser: 'typescript',
|
|
40
|
+
singleQuote: true,
|
|
41
|
+
semi: true,
|
|
42
|
+
printWidth: 100,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
/**
|
|
34
47
|
* Checks whether a migration.ts file exists in the package directory.
|
|
35
48
|
*/
|
|
@@ -41,42 +54,3 @@ export async function hasMigrationTs(packageDir: string): Promise<boolean> {
|
|
|
41
54
|
return false;
|
|
42
55
|
}
|
|
43
56
|
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Evaluates a descriptor-flow migration.ts file by loading it via native
|
|
47
|
-
* Node import. Returns the result of calling the default export (expected
|
|
48
|
-
* to be a function returning an array of operation descriptors).
|
|
49
|
-
*
|
|
50
|
-
* Class-flow migration.ts files use a different shape — their default
|
|
51
|
-
* export is a class that extends `Migration` — and are evaluated by the
|
|
52
|
-
* target's `emit` capability, not this helper.
|
|
53
|
-
*
|
|
54
|
-
* Requires Node ≥24 for native TypeScript support.
|
|
55
|
-
*/
|
|
56
|
-
export async function evaluateMigrationTs(packageDir: string): Promise<readonly unknown[]> {
|
|
57
|
-
const filePath = resolve(join(packageDir, MIGRATION_TS_FILE));
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
await stat(filePath);
|
|
61
|
-
} catch {
|
|
62
|
-
throw new Error(`migration.ts not found at "${filePath}"`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const mod = (await import(filePath)) as { default?: unknown };
|
|
66
|
-
|
|
67
|
-
if (typeof mod.default !== 'function') {
|
|
68
|
-
throw new Error(
|
|
69
|
-
`migration.ts must export a default function returning an operation list. Got: ${typeof mod.default}`,
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const result: unknown = mod.default();
|
|
74
|
-
|
|
75
|
-
if (!Array.isArray(result)) {
|
|
76
|
-
throw new Error(
|
|
77
|
-
`migration.ts default export must return an array of operations. Got: ${typeof result}`,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return result;
|
|
82
|
-
}
|
package/src/queue.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FIFO queue with amortised O(1) push and shift.
|
|
3
|
+
*
|
|
4
|
+
* Uses a head-index cursor over a backing array rather than
|
|
5
|
+
* `Array.prototype.shift()`, which is O(n) on V8. Intended for BFS-shaped
|
|
6
|
+
* traversals where the queue is drained in a single pass — it does not
|
|
7
|
+
* reclaim memory for already-shifted items, so it is not suitable for
|
|
8
|
+
* long-lived queues with many push/shift cycles.
|
|
9
|
+
*/
|
|
10
|
+
export class Queue<T> {
|
|
11
|
+
private readonly items: T[];
|
|
12
|
+
private head = 0;
|
|
13
|
+
|
|
14
|
+
constructor(initial: Iterable<T> = []) {
|
|
15
|
+
this.items = [...initial];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
push(item: T): void {
|
|
19
|
+
this.items.push(item);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Remove and return the next item. Caller must check `isEmpty` first —
|
|
24
|
+
* shifting an empty queue throws.
|
|
25
|
+
*/
|
|
26
|
+
shift(): T {
|
|
27
|
+
if (this.head >= this.items.length) {
|
|
28
|
+
throw new Error('Queue.shift called on empty queue');
|
|
29
|
+
}
|
|
30
|
+
// biome-ignore lint/style/noNonNullAssertion: bounds-checked on the line above
|
|
31
|
+
return this.items[this.head++]!;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get isEmpty(): boolean {
|
|
35
|
+
return this.head >= this.items.length;
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -5,13 +5,22 @@ export interface MigrationHints {
|
|
|
5
5
|
readonly used: readonly string[];
|
|
6
6
|
readonly applied: readonly string[];
|
|
7
7
|
readonly plannerVersion: string;
|
|
8
|
-
readonly planningStrategy: string;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
11
|
+
* On-disk migration manifest. Every migration is content-addressed: the
|
|
12
|
+
* `migrationId` is a hash over the manifest envelope plus the operations
|
|
13
|
+
* list, computed at write time. There is no draft state — a migration
|
|
14
|
+
* directory either exists with a fully attested manifest or it does not.
|
|
15
|
+
*
|
|
16
|
+
* When the planner cannot lower an operation because of an unfilled
|
|
17
|
+
* `placeholder(...)` slot, the migration is still written with
|
|
18
|
+
* `migrationId` hashed over `ops: []`. Re-running self-emit after the
|
|
19
|
+
* user fills the placeholder produces a *different* `migrationId`
|
|
20
|
+
* (committed to the real ops); this is intentional.
|
|
13
21
|
*/
|
|
14
|
-
interface
|
|
22
|
+
export interface MigrationManifest {
|
|
23
|
+
readonly migrationId: string;
|
|
15
24
|
readonly from: string;
|
|
16
25
|
readonly to: string;
|
|
17
26
|
readonly kind: 'regular' | 'baseline';
|
|
@@ -24,37 +33,12 @@ interface MigrationManifestBase {
|
|
|
24
33
|
readonly createdAt: string;
|
|
25
34
|
}
|
|
26
35
|
|
|
27
|
-
/**
|
|
28
|
-
* A draft migration that has been planned but not yet attested.
|
|
29
|
-
* Draft migrations have `migrationId: null` and are excluded from
|
|
30
|
-
* graph reconstruction and apply.
|
|
31
|
-
*/
|
|
32
|
-
export interface DraftMigrationManifest extends MigrationManifestBase {
|
|
33
|
-
readonly migrationId: null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* An attested migration with a content-addressed migrationId.
|
|
38
|
-
* Only attested migrations participate in the migration graph.
|
|
39
|
-
*/
|
|
40
|
-
export interface AttestedMigrationManifest extends MigrationManifestBase {
|
|
41
|
-
readonly migrationId: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Union of draft and attested manifests. This is what the on-disk
|
|
46
|
-
* format represents — `migrationId` is `null` for drafts, a string
|
|
47
|
-
* for attested migrations.
|
|
48
|
-
*/
|
|
49
|
-
export type MigrationManifest = DraftMigrationManifest | AttestedMigrationManifest;
|
|
50
|
-
|
|
51
36
|
export type MigrationOps = readonly MigrationPlanOperation[];
|
|
52
37
|
|
|
53
38
|
/**
|
|
54
39
|
* An on-disk migration directory containing a manifest and operations.
|
|
55
|
-
* The manifest may be draft or attested.
|
|
56
40
|
*/
|
|
57
|
-
export interface
|
|
41
|
+
export interface MigrationBundle {
|
|
58
42
|
readonly dirName: string;
|
|
59
43
|
readonly dirPath: string;
|
|
60
44
|
readonly manifest: MigrationManifest;
|
|
@@ -62,20 +46,8 @@ export interface BaseMigrationBundle {
|
|
|
62
46
|
}
|
|
63
47
|
|
|
64
48
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*/
|
|
68
|
-
export interface AttestedMigrationBundle extends BaseMigrationBundle {
|
|
69
|
-
readonly manifest: AttestedMigrationManifest;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface DraftMigrationBundle extends BaseMigrationBundle {
|
|
73
|
-
readonly manifest: DraftMigrationManifest;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* An entry in the migration graph. Only attested migrations appear in the
|
|
78
|
-
* graph, so `migrationId` is always a string.
|
|
49
|
+
* An entry in the migration graph. All on-disk migrations are attested,
|
|
50
|
+
* so `migrationId` is always a string.
|
|
79
51
|
*/
|
|
80
52
|
export interface MigrationChainEntry {
|
|
81
53
|
readonly from: string;
|
|
@@ -92,15 +64,3 @@ export interface MigrationGraph {
|
|
|
92
64
|
readonly reverseChain: ReadonlyMap<string, readonly MigrationChainEntry[]>;
|
|
93
65
|
readonly migrationById: ReadonlyMap<string, MigrationChainEntry>;
|
|
94
66
|
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Type guard that narrows a MigrationBundle to an AttestedMigrationBundle.
|
|
98
|
-
* Use with `.filter(isAttested)` to get a typed array of attested bundles.
|
|
99
|
-
*/
|
|
100
|
-
export function isAttested(bundle: BaseMigrationBundle): bundle is AttestedMigrationBundle {
|
|
101
|
-
return typeof bundle.manifest.migrationId === 'string';
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function isDraft(bundle: BaseMigrationBundle): bundle is DraftMigrationBundle {
|
|
105
|
-
return bundle.manifest.migrationId === null;
|
|
106
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"attestation-DnebS4XZ.mjs","names":["sorted: Record<string, unknown>"],"sources":["../src/canonicalize-json.ts","../src/attestation.ts"],"sourcesContent":["function sortKeys(value: unknown): unknown {\n if (value === null || typeof value !== 'object') {\n return value;\n }\n if (Array.isArray(value)) {\n return value.map(sortKeys);\n }\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(value).sort()) {\n sorted[key] = sortKeys((value as Record<string, unknown>)[key]);\n }\n return sorted;\n}\n\nexport function canonicalizeJson(value: unknown): string {\n return JSON.stringify(sortKeys(value));\n}\n","import { createHash } from 'node:crypto';\nimport { canonicalizeJson } from './canonicalize-json';\nimport { readMigrationPackage, writeMigrationManifest } from './io';\nimport type { MigrationManifest, MigrationOps } from './types';\n\nexport interface VerifyResult {\n readonly ok: boolean;\n readonly reason?: 'draft' | 'mismatch';\n readonly storedMigrationId?: string;\n readonly computedMigrationId?: string;\n}\n\nfunction sha256Hex(input: string): string {\n return createHash('sha256').update(input).digest('hex');\n}\n\n/**\n * Content-addressed migration identity over (manifest envelope sans\n * contracts/hints, ops). See ADR 199 \"Storage-only migration identity\"\n * for the rationale: contracts are anchored separately by the\n * storage-hash bookends inside the envelope; planner hints are advisory\n * and must not affect identity.\n */\nexport function computeMigrationId(manifest: MigrationManifest, ops: MigrationOps): string {\n const {\n migrationId: _migrationId,\n signature: _signature,\n fromContract: _fromContract,\n toContract: _toContract,\n hints: _hints,\n ...strippedMeta\n } = manifest;\n\n const canonicalManifest = canonicalizeJson(strippedMeta);\n const canonicalOps = canonicalizeJson(ops);\n\n const partHashes = [canonicalManifest, canonicalOps].map(sha256Hex);\n const hash = sha256Hex(canonicalizeJson(partHashes));\n\n return `sha256:${hash}`;\n}\n\n/** Compute and persist `migrationId` to `manifest.json`. */\nexport async function attestMigration(dir: string): Promise<string> {\n const pkg = await readMigrationPackage(dir);\n const migrationId = computeMigrationId(pkg.manifest, pkg.ops);\n\n const updated = { ...pkg.manifest, migrationId };\n await writeMigrationManifest(dir, updated);\n\n return migrationId;\n}\n\nexport async function verifyMigration(dir: string): Promise<VerifyResult> {\n const pkg = await readMigrationPackage(dir);\n\n if (pkg.manifest.migrationId === null) {\n return { ok: false, reason: 'draft' };\n }\n\n const computed = computeMigrationId(pkg.manifest, pkg.ops);\n\n if (pkg.manifest.migrationId === computed) {\n return { ok: true, storedMigrationId: pkg.manifest.migrationId, computedMigrationId: computed };\n }\n\n return {\n ok: false,\n reason: 'mismatch',\n storedMigrationId: pkg.manifest.migrationId,\n computedMigrationId: computed,\n };\n}\n"],"mappings":";;;;AAAA,SAAS,SAAS,OAAyB;AACzC,KAAI,UAAU,QAAQ,OAAO,UAAU,SACrC,QAAO;AAET,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,IAAI,SAAS;CAE5B,MAAMA,SAAkC,EAAE;AAC1C,MAAK,MAAM,OAAO,OAAO,KAAK,MAAM,CAAC,MAAM,CACzC,QAAO,OAAO,SAAU,MAAkC,KAAK;AAEjE,QAAO;;AAGT,SAAgB,iBAAiB,OAAwB;AACvD,QAAO,KAAK,UAAU,SAAS,MAAM,CAAC;;;;;ACHxC,SAAS,UAAU,OAAuB;AACxC,QAAO,WAAW,SAAS,CAAC,OAAO,MAAM,CAAC,OAAO,MAAM;;;;;;;;;AAUzD,SAAgB,mBAAmB,UAA6B,KAA2B;CACzF,MAAM,EACJ,aAAa,cACb,WAAW,YACX,cAAc,eACd,YAAY,aACZ,OAAO,QACP,GAAG,iBACD;AAQJ,QAAO,UAFM,UAAU,iBADJ,CAHO,iBAAiB,aAAa,EACnC,iBAAiB,IAAI,CAEU,CAAC,IAAI,UAAU,CAChB,CAAC;;;AAMtD,eAAsB,gBAAgB,KAA8B;CAClE,MAAM,MAAM,MAAM,qBAAqB,IAAI;CAC3C,MAAM,cAAc,mBAAmB,IAAI,UAAU,IAAI,IAAI;AAG7D,OAAM,uBAAuB,KADb;EAAE,GAAG,IAAI;EAAU;EAAa,CACN;AAE1C,QAAO;;AAGT,eAAsB,gBAAgB,KAAoC;CACxE,MAAM,MAAM,MAAM,qBAAqB,IAAI;AAE3C,KAAI,IAAI,SAAS,gBAAgB,KAC/B,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAS;CAGvC,MAAM,WAAW,mBAAmB,IAAI,UAAU,IAAI,IAAI;AAE1D,KAAI,IAAI,SAAS,gBAAgB,SAC/B,QAAO;EAAE,IAAI;EAAM,mBAAmB,IAAI,SAAS;EAAa,qBAAqB;EAAU;AAGjG,QAAO;EACL,IAAI;EACJ,QAAQ;EACR,mBAAmB,IAAI,SAAS;EAChC,qBAAqB;EACtB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors-C_XuSbX7.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Structured error for migration tooling operations.\n *\n * Follows the NAMESPACE.SUBCODE convention from ADR 027. All codes live under\n * the MIGRATION namespace. These are tooling-time errors (file I/O, attestation,\n * migration history reconstruction), distinct from the runtime MIGRATION.* codes for apply-time\n * failures (PRECHECK_FAILED, POSTCHECK_FAILED, etc.).\n *\n * Fields:\n * - code: Stable machine-readable code (MIGRATION.SUBCODE)\n * - category: Always 'MIGRATION'\n * - why: Explains the cause in plain language\n * - fix: Actionable remediation step\n * - details: Machine-readable structured data for agents\n */\nexport class MigrationToolsError extends Error {\n readonly code: string;\n readonly category = 'MIGRATION' as const;\n readonly why: string;\n readonly fix: string;\n readonly details: Record<string, unknown> | undefined;\n\n constructor(\n code: string,\n summary: string,\n options: {\n readonly why: string;\n readonly fix: string;\n readonly details?: Record<string, unknown>;\n },\n ) {\n super(summary);\n this.name = 'MigrationToolsError';\n this.code = code;\n this.why = options.why;\n this.fix = options.fix;\n this.details = options.details;\n }\n\n static is(error: unknown): error is MigrationToolsError {\n if (!(error instanceof Error)) return false;\n const candidate = error as MigrationToolsError;\n return candidate.name === 'MigrationToolsError' && typeof candidate.code === 'string';\n }\n}\n\nexport function errorDirectoryExists(dir: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.DIR_EXISTS', 'Migration directory already exists', {\n why: `The directory \"${dir}\" already exists. Each migration must have a unique directory.`,\n fix: 'Use --name to pick a different name, or delete the existing directory and re-run.',\n details: { dir },\n });\n}\n\nexport function errorMissingFile(file: string, dir: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.FILE_MISSING', `Missing ${file}`, {\n why: `Expected \"${file}\" in \"${dir}\" but the file does not exist.`,\n fix: 'Ensure the migration directory contains both migration.json and ops.json. If the directory is corrupt, delete it and re-run migration plan.',\n details: { file, dir },\n });\n}\n\nexport function errorInvalidJson(filePath: string, parseError: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_JSON', 'Invalid JSON in migration file', {\n why: `Failed to parse \"${filePath}\": ${parseError}`,\n fix: 'Fix the JSON syntax error, or delete the migration directory and re-run migration plan.',\n details: { filePath, parseError },\n });\n}\n\nexport function errorInvalidManifest(filePath: string, reason: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_MANIFEST', 'Invalid migration manifest', {\n why: `Manifest at \"${filePath}\" is invalid: ${reason}`,\n fix: 'Ensure the manifest has all required fields (from, to, kind, toContract). If corrupt, delete and re-plan.',\n details: { filePath, reason },\n });\n}\n\nexport function errorInvalidSlug(slug: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_NAME', 'Invalid migration name', {\n why: `The slug \"${slug}\" contains no valid characters after sanitization (only a-z, 0-9 are kept).`,\n fix: 'Provide a name with at least one alphanumeric character, e.g. --name add_users.',\n details: { slug },\n });\n}\n\nexport function errorSameSourceAndTarget(dirName: string, hash: string): MigrationToolsError {\n return new MigrationToolsError(\n 'MIGRATION.SAME_SOURCE_AND_TARGET',\n 'Migration has same source and target',\n {\n why: `Migration \"${dirName}\" has from === to === \"${hash}\". A migration must transition between two different contract states.`,\n fix: 'Delete the invalid migration directory and re-run migration plan.',\n details: { dirName, hash },\n },\n );\n}\n\nexport function errorAmbiguousTarget(\n branchTips: readonly string[],\n context?: {\n divergencePoint: string;\n branches: readonly {\n tip: string;\n edges: readonly { dirName: string; from: string; to: string }[];\n }[];\n },\n): MigrationToolsError {\n const divergenceInfo = context\n ? `\\nDivergence point: ${context.divergencePoint}\\nBranches:\\n${context.branches.map((b) => ` → ${b.tip} (${b.edges.length} edge(s): ${b.edges.map((e) => e.dirName).join(' → ') || 'direct'})`).join('\\n')}`\n : '';\n return new MigrationToolsError('MIGRATION.AMBIGUOUS_TARGET', 'Ambiguous migration target', {\n why: `The migration history has diverged into multiple branches: ${branchTips.join(', ')}. This typically happens when two developers plan migrations from the same starting point.${divergenceInfo}`,\n fix: 'Use `migration ref set <name> <hash>` to target a specific branch, delete one of the conflicting migration directories and re-run `migration plan`, or use --from <hash> to explicitly select a starting point.',\n details: {\n branchTips,\n ...(context ? { divergencePoint: context.divergencePoint, branches: context.branches } : {}),\n },\n });\n}\n\nexport function errorNoInitialMigration(nodes: readonly string[]): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.NO_INITIAL_MIGRATION', 'No initial migration found', {\n why: `No migration starts from the empty contract state (known hashes: ${nodes.join(', ')}). At least one migration must originate from the empty state.`,\n fix: 'Inspect the migrations directory for corrupted migration.json files. At least one migration must start from the empty contract hash.',\n details: { nodes },\n });\n}\n\nexport function errorInvalidRefs(refsPath: string, reason: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REFS', 'Invalid refs.json', {\n why: `refs.json at \"${refsPath}\" is invalid: ${reason}`,\n fix: 'Ensure refs.json is a flat object mapping valid ref names to contract hash strings.',\n details: { path: refsPath, reason },\n });\n}\n\nexport function errorInvalidRefFile(filePath: string, reason: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REF_FILE', 'Invalid ref file', {\n why: `Ref file at \"${filePath}\" is invalid: ${reason}`,\n fix: 'Ensure the ref file contains valid JSON with { \"hash\": \"sha256:<64 hex chars>\", \"invariants\": [\"...\"] }.',\n details: { path: filePath, reason },\n });\n}\n\nexport function errorInvalidRefName(refName: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REF_NAME', 'Invalid ref name', {\n why: `Ref name \"${refName}\" is invalid. Names must be lowercase alphanumeric with hyphens or forward slashes (no \".\" or \"..\" segments).`,\n fix: `Use a valid ref name (e.g., \"staging\", \"envs/production\").`,\n details: { refName },\n });\n}\n\nexport function errorNoTarget(reachableHashes: readonly string[]): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.NO_TARGET', 'No migration target could be resolved', {\n why: `The migration history contains cycles and no target can be resolved automatically (reachable hashes: ${reachableHashes.join(', ')}). This typically happens after rollback migrations (e.g., C1→C2→C1).`,\n fix: 'Use --from <hash> to specify the planning origin explicitly.',\n details: { reachableHashes },\n });\n}\n\nexport function errorInvalidRefValue(value: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REF_VALUE', 'Invalid ref value', {\n why: `Ref value \"${value}\" is not a valid contract hash. Values must be in the format \"sha256:<64 hex chars>\" or \"sha256:empty\".`,\n fix: 'Use a valid storage hash from `prisma-next contract emit` output or an existing migration.',\n details: { value },\n });\n}\n\nexport function errorDuplicateMigrationId(migrationId: string): MigrationToolsError {\n return new MigrationToolsError(\n 'MIGRATION.DUPLICATE_MIGRATION_ID',\n 'Duplicate migrationId in migration graph',\n {\n why: `Multiple migrations share migrationId \"${migrationId}\". Each migration must have a unique content-addressed identity.`,\n fix: 'Regenerate one of the conflicting migrations so each migrationId is unique, then re-run migration commands.',\n details: { migrationId },\n },\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.mjs","names":[],"sources":["../../src/types.ts"],"sourcesContent":["import type { Contract } from '@prisma-next/contract/types';\nimport type { MigrationPlanOperation } from '@prisma-next/framework-components/control';\n\nexport interface MigrationHints {\n readonly used: readonly string[];\n readonly applied: readonly string[];\n readonly plannerVersion: string;\n readonly planningStrategy: string;\n}\n\n/**\n * Shared fields for all migration manifests (draft and attested).\n */\ninterface MigrationManifestBase {\n readonly from: string;\n readonly to: string;\n readonly kind: 'regular' | 'baseline';\n readonly fromContract: Contract | null;\n readonly toContract: Contract;\n readonly hints: MigrationHints;\n readonly labels: readonly string[];\n readonly authorship?: { readonly author?: string; readonly email?: string };\n readonly signature?: { readonly keyId: string; readonly value: string } | null;\n readonly createdAt: string;\n}\n\n/**\n * A draft migration that has been planned but not yet attested.\n * Draft migrations have `migrationId: null` and are excluded from\n * graph reconstruction and apply.\n */\nexport interface DraftMigrationManifest extends MigrationManifestBase {\n readonly migrationId: null;\n}\n\n/**\n * An attested migration with a content-addressed migrationId.\n * Only attested migrations participate in the migration graph.\n */\nexport interface AttestedMigrationManifest extends MigrationManifestBase {\n readonly migrationId: string;\n}\n\n/**\n * Union of draft and attested manifests. This is what the on-disk\n * format represents — `migrationId` is `null` for drafts, a string\n * for attested migrations.\n */\nexport type MigrationManifest = DraftMigrationManifest | AttestedMigrationManifest;\n\nexport type MigrationOps = readonly MigrationPlanOperation[];\n\n/**\n * An on-disk migration directory containing a manifest and operations.\n * The manifest may be draft or attested.\n */\nexport interface BaseMigrationBundle {\n readonly dirName: string;\n readonly dirPath: string;\n readonly manifest: MigrationManifest;\n readonly ops: MigrationOps;\n}\n\n/**\n * A bundle known to be attested (migrationId is a string).\n * Use this after filtering bundles to attested-only.\n */\nexport interface AttestedMigrationBundle extends BaseMigrationBundle {\n readonly manifest: AttestedMigrationManifest;\n}\n\nexport interface DraftMigrationBundle extends BaseMigrationBundle {\n readonly manifest: DraftMigrationManifest;\n}\n\n/**\n * An entry in the migration graph. Only attested migrations appear in the\n * graph, so `migrationId` is always a string.\n */\nexport interface MigrationChainEntry {\n readonly from: string;\n readonly to: string;\n readonly migrationId: string;\n readonly dirName: string;\n readonly createdAt: string;\n readonly labels: readonly string[];\n}\n\nexport interface MigrationGraph {\n readonly nodes: ReadonlySet<string>;\n readonly forwardChain: ReadonlyMap<string, readonly MigrationChainEntry[]>;\n readonly reverseChain: ReadonlyMap<string, readonly MigrationChainEntry[]>;\n readonly migrationById: ReadonlyMap<string, MigrationChainEntry>;\n}\n\n/**\n * Type guard that narrows a MigrationBundle to an AttestedMigrationBundle.\n * Use with `.filter(isAttested)` to get a typed array of attested bundles.\n */\nexport function isAttested(bundle: BaseMigrationBundle): bundle is AttestedMigrationBundle {\n return typeof bundle.manifest.migrationId === 'string';\n}\n\nexport function isDraft(bundle: BaseMigrationBundle): bundle is DraftMigrationBundle {\n return bundle.manifest.migrationId === null;\n}\n"],"mappings":";;;;;;;AAmGA,SAAgB,WAAW,QAAgE;AACzF,QAAO,OAAO,OAAO,SAAS,gBAAgB;;AAGhD,SAAgB,QAAQ,QAA6D;AACnF,QAAO,OAAO,SAAS,gBAAgB"}
|
package/dist/io-Cun81AIZ.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"io-Cun81AIZ.mjs","names":["manifestRaw: string","opsRaw: string","manifest: MigrationManifest","ops: MigrationOps","entries: string[]","packages: BaseMigrationBundle[]"],"sources":["../src/io.ts"],"sourcesContent":["import { copyFile, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';\nimport { type } from 'arktype';\nimport { basename, dirname, join } from 'pathe';\nimport {\n errorDirectoryExists,\n errorInvalidJson,\n errorInvalidManifest,\n errorInvalidSlug,\n errorMissingFile,\n} from './errors';\nimport type { BaseMigrationBundle, MigrationManifest, MigrationOps } from './types';\n\nconst MANIFEST_FILE = 'migration.json';\nconst OPS_FILE = 'ops.json';\nconst MAX_SLUG_LENGTH = 64;\n\nfunction hasErrnoCode(error: unknown, code: string): boolean {\n return error instanceof Error && (error as { code?: string }).code === code;\n}\n\nconst MigrationHintsSchema = type({\n used: 'string[]',\n applied: 'string[]',\n plannerVersion: 'string',\n planningStrategy: 'string',\n});\n\nconst MigrationManifestSchema = type({\n from: 'string',\n to: 'string',\n migrationId: 'string | null',\n kind: \"'regular' | 'baseline'\",\n fromContract: 'object | null',\n toContract: 'object',\n hints: MigrationHintsSchema,\n labels: 'string[]',\n 'authorship?': type({\n 'author?': 'string',\n 'email?': 'string',\n }),\n 'signature?': type({\n keyId: 'string',\n value: 'string',\n }).or('null'),\n createdAt: 'string',\n});\n\nconst MigrationOpSchema = type({\n id: 'string',\n label: 'string',\n operationClass: \"'additive' | 'widening' | 'destructive' | 'data'\",\n});\n\n// Intentionally shallow: operation-specific payload validation is owned by planner/runner layers.\nconst MigrationOpsSchema = MigrationOpSchema.array();\n\nexport async function writeMigrationPackage(\n dir: string,\n manifest: MigrationManifest,\n ops: MigrationOps,\n): Promise<void> {\n await mkdir(dirname(dir), { recursive: true });\n\n try {\n await mkdir(dir);\n } catch (error) {\n if (hasErrnoCode(error, 'EEXIST')) {\n throw errorDirectoryExists(dir);\n }\n throw error;\n }\n\n await writeFile(join(dir, MANIFEST_FILE), JSON.stringify(manifest, null, 2), { flag: 'wx' });\n await writeFile(join(dir, OPS_FILE), JSON.stringify(ops, null, 2), { flag: 'wx' });\n}\n\n/**\n * Copy the destination contract artifacts (`contract.json` and the\n * colocated `contract.d.ts`) into the migration package directory so\n * authors of the scaffolded `migration.ts` can import the typed\n * contract relative to the migration directory\n * (`import type { Contract } from './contract'`).\n *\n * A missing `.d.ts` is tolerated (only the `.json` is required) so the\n * helper stays usable in tests that hand-roll a bare `contract.json`.\n * A missing `contract.json` — or any other I/O failure — throws.\n */\nexport async function copyContractToMigrationDir(\n packageDir: string,\n contractJsonPath: string,\n): Promise<void> {\n await copyFile(contractJsonPath, join(packageDir, 'contract.json'));\n const dtsPath = `${contractJsonPath.slice(0, -'.json'.length)}.d.ts`;\n try {\n await copyFile(dtsPath, join(packageDir, 'contract.d.ts'));\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) return;\n throw error;\n }\n}\n\nexport async function writeMigrationManifest(\n dir: string,\n manifest: MigrationManifest,\n): Promise<void> {\n await writeFile(join(dir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}\\n`);\n}\n\nexport async function writeMigrationOps(dir: string, ops: MigrationOps): Promise<void> {\n await writeFile(join(dir, OPS_FILE), `${JSON.stringify(ops, null, 2)}\\n`);\n}\n\nexport async function readMigrationPackage(dir: string): Promise<BaseMigrationBundle> {\n const manifestPath = join(dir, MANIFEST_FILE);\n const opsPath = join(dir, OPS_FILE);\n\n let manifestRaw: string;\n try {\n manifestRaw = await readFile(manifestPath, 'utf-8');\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) {\n throw errorMissingFile(MANIFEST_FILE, dir);\n }\n throw error;\n }\n\n let opsRaw: string;\n try {\n opsRaw = await readFile(opsPath, 'utf-8');\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) {\n throw errorMissingFile(OPS_FILE, dir);\n }\n throw error;\n }\n\n let manifest: MigrationManifest;\n try {\n manifest = JSON.parse(manifestRaw);\n } catch (e) {\n throw errorInvalidJson(manifestPath, e instanceof Error ? e.message : String(e));\n }\n\n let ops: MigrationOps;\n try {\n ops = JSON.parse(opsRaw);\n } catch (e) {\n throw errorInvalidJson(opsPath, e instanceof Error ? e.message : String(e));\n }\n\n validateManifest(manifest, manifestPath);\n validateOps(ops, opsPath);\n\n return {\n dirName: basename(dir),\n dirPath: dir,\n manifest,\n ops,\n };\n}\n\nfunction validateManifest(\n manifest: unknown,\n filePath: string,\n): asserts manifest is MigrationManifest {\n const result = MigrationManifestSchema(manifest);\n if (result instanceof type.errors) {\n throw errorInvalidManifest(filePath, result.summary);\n }\n}\n\nfunction validateOps(ops: unknown, filePath: string): asserts ops is MigrationOps {\n const result = MigrationOpsSchema(ops);\n if (result instanceof type.errors) {\n throw errorInvalidManifest(filePath, result.summary);\n }\n}\n\nexport async function readMigrationsDir(\n migrationsRoot: string,\n): Promise<readonly BaseMigrationBundle[]> {\n let entries: string[];\n try {\n entries = await readdir(migrationsRoot);\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) {\n return [];\n }\n throw error;\n }\n\n const packages: BaseMigrationBundle[] = [];\n\n for (const entry of entries.sort()) {\n const entryPath = join(migrationsRoot, entry);\n const entryStat = await stat(entryPath);\n if (!entryStat.isDirectory()) continue;\n\n const manifestPath = join(entryPath, MANIFEST_FILE);\n try {\n await stat(manifestPath);\n } catch {\n continue; // skip non-migration directories\n }\n\n packages.push(await readMigrationPackage(entryPath));\n }\n\n return packages;\n}\n\nexport function formatMigrationDirName(timestamp: Date, slug: string): string {\n const sanitized = slug\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_|_$/g, '');\n\n if (sanitized.length === 0) {\n throw errorInvalidSlug(slug);\n }\n\n const truncated = sanitized.slice(0, MAX_SLUG_LENGTH);\n\n const y = timestamp.getUTCFullYear();\n const mo = String(timestamp.getUTCMonth() + 1).padStart(2, '0');\n const d = String(timestamp.getUTCDate()).padStart(2, '0');\n const h = String(timestamp.getUTCHours()).padStart(2, '0');\n const mi = String(timestamp.getUTCMinutes()).padStart(2, '0');\n\n return `${y}${mo}${d}T${h}${mi}_${truncated}`;\n}\n"],"mappings":"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"}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { Contract } from "@prisma-next/contract/types";
|
|
2
|
-
import { MigrationPlanOperation } from "@prisma-next/framework-components/control";
|
|
3
|
-
|
|
4
|
-
//#region src/types.d.ts
|
|
5
|
-
interface MigrationHints {
|
|
6
|
-
readonly used: readonly string[];
|
|
7
|
-
readonly applied: readonly string[];
|
|
8
|
-
readonly plannerVersion: string;
|
|
9
|
-
readonly planningStrategy: string;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Shared fields for all migration manifests (draft and attested).
|
|
13
|
-
*/
|
|
14
|
-
interface MigrationManifestBase {
|
|
15
|
-
readonly from: string;
|
|
16
|
-
readonly to: string;
|
|
17
|
-
readonly kind: 'regular' | 'baseline';
|
|
18
|
-
readonly fromContract: Contract | null;
|
|
19
|
-
readonly toContract: Contract;
|
|
20
|
-
readonly hints: MigrationHints;
|
|
21
|
-
readonly labels: readonly string[];
|
|
22
|
-
readonly authorship?: {
|
|
23
|
-
readonly author?: string;
|
|
24
|
-
readonly email?: string;
|
|
25
|
-
};
|
|
26
|
-
readonly signature?: {
|
|
27
|
-
readonly keyId: string;
|
|
28
|
-
readonly value: string;
|
|
29
|
-
} | null;
|
|
30
|
-
readonly createdAt: string;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* A draft migration that has been planned but not yet attested.
|
|
34
|
-
* Draft migrations have `migrationId: null` and are excluded from
|
|
35
|
-
* graph reconstruction and apply.
|
|
36
|
-
*/
|
|
37
|
-
interface DraftMigrationManifest extends MigrationManifestBase {
|
|
38
|
-
readonly migrationId: null;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* An attested migration with a content-addressed migrationId.
|
|
42
|
-
* Only attested migrations participate in the migration graph.
|
|
43
|
-
*/
|
|
44
|
-
interface AttestedMigrationManifest extends MigrationManifestBase {
|
|
45
|
-
readonly migrationId: string;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Union of draft and attested manifests. This is what the on-disk
|
|
49
|
-
* format represents — `migrationId` is `null` for drafts, a string
|
|
50
|
-
* for attested migrations.
|
|
51
|
-
*/
|
|
52
|
-
type MigrationManifest = DraftMigrationManifest | AttestedMigrationManifest;
|
|
53
|
-
type MigrationOps = readonly MigrationPlanOperation[];
|
|
54
|
-
/**
|
|
55
|
-
* An on-disk migration directory containing a manifest and operations.
|
|
56
|
-
* The manifest may be draft or attested.
|
|
57
|
-
*/
|
|
58
|
-
interface BaseMigrationBundle {
|
|
59
|
-
readonly dirName: string;
|
|
60
|
-
readonly dirPath: string;
|
|
61
|
-
readonly manifest: MigrationManifest;
|
|
62
|
-
readonly ops: MigrationOps;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* A bundle known to be attested (migrationId is a string).
|
|
66
|
-
* Use this after filtering bundles to attested-only.
|
|
67
|
-
*/
|
|
68
|
-
interface AttestedMigrationBundle extends BaseMigrationBundle {
|
|
69
|
-
readonly manifest: AttestedMigrationManifest;
|
|
70
|
-
}
|
|
71
|
-
interface DraftMigrationBundle extends BaseMigrationBundle {
|
|
72
|
-
readonly manifest: DraftMigrationManifest;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* An entry in the migration graph. Only attested migrations appear in the
|
|
76
|
-
* graph, so `migrationId` is always a string.
|
|
77
|
-
*/
|
|
78
|
-
interface MigrationChainEntry {
|
|
79
|
-
readonly from: string;
|
|
80
|
-
readonly to: string;
|
|
81
|
-
readonly migrationId: string;
|
|
82
|
-
readonly dirName: string;
|
|
83
|
-
readonly createdAt: string;
|
|
84
|
-
readonly labels: readonly string[];
|
|
85
|
-
}
|
|
86
|
-
interface MigrationGraph {
|
|
87
|
-
readonly nodes: ReadonlySet<string>;
|
|
88
|
-
readonly forwardChain: ReadonlyMap<string, readonly MigrationChainEntry[]>;
|
|
89
|
-
readonly reverseChain: ReadonlyMap<string, readonly MigrationChainEntry[]>;
|
|
90
|
-
readonly migrationById: ReadonlyMap<string, MigrationChainEntry>;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Type guard that narrows a MigrationBundle to an AttestedMigrationBundle.
|
|
94
|
-
* Use with `.filter(isAttested)` to get a typed array of attested bundles.
|
|
95
|
-
*/
|
|
96
|
-
declare function isAttested(bundle: BaseMigrationBundle): bundle is AttestedMigrationBundle;
|
|
97
|
-
declare function isDraft(bundle: BaseMigrationBundle): bundle is DraftMigrationBundle;
|
|
98
|
-
//#endregion
|
|
99
|
-
export { DraftMigrationManifest as a, MigrationHints as c, isAttested as d, isDraft as f, DraftMigrationBundle as i, MigrationManifest as l, AttestedMigrationManifest as n, MigrationChainEntry as o, BaseMigrationBundle as r, MigrationGraph as s, AttestedMigrationBundle as t, MigrationOps as u };
|
|
100
|
-
//# sourceMappingURL=types-D2uX4ql7.d.mts.map
|