@prisma-next/migration-tools 0.11.0-dev.9 → 0.12.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/{errors-CoEN114u.mjs → errors-vFROOhCR.mjs} +34 -21
- package/dist/{errors-CoEN114u.mjs.map → errors-vFROOhCR.mjs.map} +1 -1
- package/dist/exports/aggregate.d.mts +328 -204
- package/dist/exports/aggregate.d.mts.map +1 -1
- package/dist/exports/aggregate.mjs +480 -243
- package/dist/exports/aggregate.mjs.map +1 -1
- package/dist/exports/errors.d.mts +2 -2
- package/dist/exports/errors.d.mts.map +1 -1
- package/dist/exports/errors.mjs +1 -1
- package/dist/exports/graph.d.mts +1 -1
- package/dist/exports/hash.d.mts +8 -9
- package/dist/exports/hash.d.mts.map +1 -1
- package/dist/exports/hash.mjs +1 -1
- package/dist/exports/invariants.d.mts +1 -1
- package/dist/exports/invariants.d.mts.map +1 -1
- package/dist/exports/invariants.mjs +1 -1
- package/dist/exports/io.d.mts +2 -83
- package/dist/exports/io.mjs +1 -1
- package/dist/exports/metadata.d.mts +2 -2
- package/dist/exports/migration-graph.d.mts +2 -2
- package/dist/exports/migration-graph.d.mts.map +1 -1
- package/dist/exports/migration-graph.mjs +2 -15
- package/dist/exports/migration-ts.d.mts.map +1 -1
- package/dist/exports/migration-ts.mjs.map +1 -1
- package/dist/exports/migration.d.mts +5 -6
- package/dist/exports/migration.d.mts.map +1 -1
- package/dist/exports/migration.mjs +14 -32
- package/dist/exports/migration.mjs.map +1 -1
- package/dist/exports/package.d.mts +1 -1
- package/dist/exports/ref-resolution.d.mts +2 -2
- package/dist/exports/ref-resolution.d.mts.map +1 -1
- package/dist/exports/ref-resolution.mjs +1 -1
- package/dist/exports/ref-resolution.mjs.map +1 -1
- package/dist/exports/refs.d.mts +2 -2
- package/dist/exports/refs.d.mts.map +1 -1
- package/dist/exports/refs.mjs +3 -137
- package/dist/exports/spaces.d.mts +31 -132
- package/dist/exports/spaces.d.mts.map +1 -1
- package/dist/exports/spaces.mjs +13 -9
- package/dist/exports/spaces.mjs.map +1 -1
- package/dist/{graph-BrLXqoUc.d.mts → graph-3dLMZp5l.d.mts} +1 -2
- package/dist/graph-3dLMZp5l.d.mts.map +1 -0
- package/dist/graph-membership-BV23F1IV.mjs +15 -0
- package/dist/graph-membership-BV23F1IV.mjs.map +1 -0
- package/dist/{hash-Cr4WIr4Z.mjs → hash--Y7vCpN3.mjs} +8 -9
- package/dist/hash--Y7vCpN3.mjs.map +1 -0
- package/dist/{invariants-lbJddL-S.mjs → invariants-C23nXy1c.mjs} +2 -2
- package/dist/{invariants-lbJddL-S.mjs.map → invariants-C23nXy1c.mjs.map} +1 -1
- package/dist/{io-gHmDrSjQ.mjs → io-BGlPOt9b.mjs} +100 -13
- package/dist/io-BGlPOt9b.mjs.map +1 -0
- package/dist/io-BH4G3F-i.d.mts +124 -0
- package/dist/io-BH4G3F-i.d.mts.map +1 -0
- package/dist/metadata-Bp9X04gM.d.mts +2 -0
- package/dist/{migration-graph-C2iNX8dk.mjs → migration-graph-BMAqSfv9.mjs} +6 -26
- package/dist/migration-graph-BMAqSfv9.mjs.map +1 -0
- package/dist/{migration-graph-De0dUZoC.d.mts → migration-graph-CWEM2SLR.d.mts} +6 -6
- package/dist/migration-graph-CWEM2SLR.d.mts.map +1 -0
- package/dist/op-schema-D5qkXfEf.mjs.map +1 -1
- package/dist/{package-DZj8YvD0.d.mts → package-Ca-J_z_0.d.mts} +1 -1
- package/dist/package-Ca-J_z_0.d.mts.map +1 -0
- package/dist/{read-contract-space-contract-Cglige7P.mjs → read-contract-space-contract-TbeXuJXL.mjs} +32 -5
- package/dist/read-contract-space-contract-TbeXuJXL.mjs.map +1 -0
- package/dist/{refs-HhOkD8BT.mjs → refs-C-_WUrPw.mjs} +96 -3
- package/dist/refs-C-_WUrPw.mjs.map +1 -0
- package/dist/refs-C7wuYFqZ.d.mts +42 -0
- package/dist/refs-C7wuYFqZ.d.mts.map +1 -0
- package/dist/snapshot-Bazwo13S.mjs +137 -0
- package/dist/snapshot-Bazwo13S.mjs.map +1 -0
- package/dist/verify-contract-spaces-BdysZdQk.d.mts +132 -0
- package/dist/verify-contract-spaces-BdysZdQk.d.mts.map +1 -0
- package/package.json +18 -9
- package/src/aggregate/aggregate.ts +266 -0
- package/src/aggregate/check-integrity.ts +243 -0
- package/src/aggregate/loader.ts +161 -334
- package/src/aggregate/planner-types.ts +14 -14
- package/src/aggregate/planner.ts +20 -23
- package/src/aggregate/project-schema-to-space.ts +3 -8
- package/src/aggregate/strategies/graph-walk.ts +15 -10
- package/src/aggregate/strategies/synth.ts +4 -4
- package/src/aggregate/types.ts +81 -62
- package/src/aggregate/verifier.ts +23 -23
- package/src/assert-descriptor-self-consistency.ts +6 -0
- package/src/compute-extension-space-apply-path.ts +1 -1
- package/src/emit-contract-space-artefacts.ts +4 -3
- package/src/errors.ts +43 -2
- package/src/exports/aggregate.ts +29 -19
- package/src/exports/io.ts +2 -0
- package/src/exports/metadata.ts +1 -1
- package/src/exports/refs.ts +3 -0
- package/src/exports/spaces.ts +3 -0
- package/src/graph.ts +0 -1
- package/src/hash.ts +7 -8
- package/src/integrity-violation.ts +114 -0
- package/src/io.ts +139 -14
- package/src/metadata.ts +1 -1
- package/src/migration-base.ts +10 -30
- package/src/migration-graph.ts +7 -35
- package/src/read-contract-space-head-ref.ts +5 -2
- package/src/refs/snapshot.ts +3 -1
- package/src/refs.ts +121 -0
- package/src/space-layout.ts +30 -0
- package/dist/exports/io.d.mts.map +0 -1
- package/dist/exports/migration-graph.mjs.map +0 -1
- package/dist/exports/refs.mjs.map +0 -1
- package/dist/graph-BrLXqoUc.d.mts.map +0 -1
- package/dist/hash-Cr4WIr4Z.mjs.map +0 -1
- package/dist/io-gHmDrSjQ.mjs.map +0 -1
- package/dist/metadata-BFX0xdz8.d.mts +0 -2
- package/dist/migration-graph-C2iNX8dk.mjs.map +0 -1
- package/dist/migration-graph-De0dUZoC.d.mts.map +0 -1
- package/dist/package-DZj8YvD0.d.mts.map +0 -1
- package/dist/read-contract-space-contract-Cglige7P.mjs.map +0 -1
- package/dist/refs-CDaNerhT.d.mts +0 -16
- package/dist/refs-CDaNerhT.d.mts.map +0 -1
- package/dist/refs-HhOkD8BT.mjs.map +0 -1
- package/src/aggregate/extract-storage-element-names.ts +0 -75
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refs-C-_WUrPw.mjs","names":[],"sources":["../src/refs.ts"],"sourcesContent":["import { mkdir, readdir, readFile, rename, rmdir, unlink, writeFile } from 'node:fs/promises';\nimport { type } from 'arktype';\nimport { dirname, join, relative } from 'pathe';\nimport {\n errorInvalidRefFile,\n errorInvalidRefName,\n errorInvalidRefValue,\n MigrationToolsError,\n} from './errors';\n\nexport interface RefEntry {\n readonly hash: string;\n readonly invariants: readonly string[];\n}\n\nexport type Refs = Readonly<Record<string, RefEntry>>;\n\n/**\n * The system head ref lives at `refs/head.json`. It is read (and its\n * corruption judged) through `readContractSpaceHeadRef`, not as a\n * user-authored ref, so {@link readRefsTolerant} excludes it.\n */\nexport const HEAD_REF_NAME = 'head';\n\n/**\n * A single ref file that exists on disk but cannot be turned into a\n * {@link RefEntry} (unparseable JSON or schema-invalid content). The ref\n * is omitted from the result; the problem is surfaced for the integrity\n * layer to report as `refUnreadable` rather than aborting the load.\n */\nexport interface RefLoadProblem {\n readonly refName: string;\n readonly detail: string;\n}\n\nexport interface TolerantRefsResult {\n readonly refs: Refs;\n readonly problems: readonly RefLoadProblem[];\n}\n\nconst REF_NAME_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\/[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/;\nconst REF_VALUE_PATTERN = /^sha256:(empty|[0-9a-f]{64})$/;\n\nexport function validateRefName(name: string): boolean {\n if (name.length === 0) return false;\n if (name.includes('..')) return false;\n if (name.includes('//')) return false;\n if (name.startsWith('.')) return false;\n return REF_NAME_PATTERN.test(name);\n}\n\nexport function validateRefValue(value: string): boolean {\n return REF_VALUE_PATTERN.test(value);\n}\n\nconst RefEntrySchema = type({\n hash: 'string',\n invariants: 'string[]',\n}).narrow((entry, ctx) => {\n if (!validateRefValue(entry.hash))\n return ctx.mustBe(`a valid contract hash (got \"${entry.hash}\")`);\n return true;\n});\n\nfunction refFilePath(refsDir: string, name: string): string {\n return join(refsDir, `${name}.json`);\n}\n\nfunction refNameFromPath(refsDir: string, filePath: string): string {\n const rel = relative(refsDir, filePath);\n return rel.replace(/\\.json$/, '');\n}\n\nexport async function readRef(refsDir: string, name: string): Promise<RefEntry> {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n\n const filePath = refFilePath(refsDir, name);\n let raw: string;\n try {\n raw = await readFile(filePath, 'utf-8');\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n throw new MigrationToolsError('MIGRATION.UNKNOWN_REF', `Unknown ref \"${name}\"`, {\n why: `No ref file found at \"${filePath}\".`,\n fix: `Create the ref with: prisma-next ref set ${name} <hash>`,\n details: { refName: name, filePath },\n });\n }\n throw error;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw errorInvalidRefFile(filePath, 'Failed to parse as JSON');\n }\n\n const result = RefEntrySchema(parsed);\n if (result instanceof type.errors) {\n throw errorInvalidRefFile(filePath, result.summary);\n }\n\n return result;\n}\n\nexport async function readRefs(refsDir: string): Promise<Refs> {\n let entries: string[];\n try {\n entries = await readdir(refsDir, { recursive: true, encoding: 'utf-8' });\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n return {};\n }\n throw error;\n }\n\n const jsonFiles = entries.filter(\n (entry) => entry.endsWith('.json') && !entry.endsWith('.contract.json'),\n );\n const refs: Record<string, RefEntry> = {};\n\n for (const jsonFile of jsonFiles) {\n const filePath = join(refsDir, jsonFile);\n const name = refNameFromPath(refsDir, filePath);\n\n let raw: string;\n try {\n raw = await readFile(filePath, 'utf-8');\n } catch (error) {\n // Tolerate the TOCTOU race between `readdir` and `readFile` (ENOENT) and\n // benign EISDIR if a directory happens to end in `.json`. Anything else\n // (EACCES, EIO, EMFILE, …) is a real failure and propagates so the CLI\n // surfaces it rather than silently dropping the ref.\n const code = error instanceof Error ? (error as { code?: string }).code : undefined;\n if (code === 'ENOENT' || code === 'EISDIR') {\n continue;\n }\n throw error;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw errorInvalidRefFile(filePath, 'Failed to parse as JSON');\n }\n\n const result = RefEntrySchema(parsed);\n if (result instanceof type.errors) {\n throw errorInvalidRefFile(filePath, result.summary);\n }\n\n refs[name] = result;\n }\n\n return refs;\n}\n\n/**\n * Read a space's user-authored refs without ever throwing on disk\n * content. A ref whose JSON is unparseable or whose shape fails\n * {@link RefEntrySchema} is omitted from `refs` and reported as a\n * {@link RefLoadProblem}; the remaining well-formed refs are still\n * returned. A missing `refs/` directory yields no refs and no problems.\n *\n * `refs/head.json` is deliberately skipped here: the system head ref is\n * read through `readContractSpaceHeadRef` (which validates head-ref\n * shape, distinct from the strict user-ref hash grammar), so it is judged\n * there and never doubles as a user ref. Genuine I/O faults (EACCES, EIO,\n * …) still propagate — only parse / schema problems are made tolerant.\n */\nexport async function readRefsTolerant(refsDir: string): Promise<TolerantRefsResult> {\n let entries: string[];\n try {\n entries = await readdir(refsDir, { recursive: true, encoding: 'utf-8' });\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n return { refs: {}, problems: [] };\n }\n throw error;\n }\n\n const jsonFiles = entries.filter(\n (entry) =>\n entry.endsWith('.json') &&\n !entry.endsWith('.contract.json') &&\n entry !== `${HEAD_REF_NAME}.json`,\n );\n const refs: Record<string, RefEntry> = {};\n const problems: RefLoadProblem[] = [];\n\n for (const jsonFile of jsonFiles) {\n const filePath = join(refsDir, jsonFile);\n const name = refNameFromPath(refsDir, filePath);\n\n let raw: string;\n try {\n raw = await readFile(filePath, 'utf-8');\n } catch (error) {\n // Tolerate the TOCTOU race between `readdir` and `readFile` (ENOENT)\n // and benign EISDIR if a directory happens to end in `.json`.\n // Anything else (EACCES, EIO, …) is a real failure and propagates.\n const code = error instanceof Error ? (error as { code?: string }).code : undefined;\n if (code === 'ENOENT' || code === 'EISDIR') {\n continue;\n }\n throw error;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (e) {\n problems.push({ refName: name, detail: e instanceof Error ? e.message : String(e) });\n continue;\n }\n\n const result = RefEntrySchema(parsed);\n if (result instanceof type.errors) {\n problems.push({ refName: name, detail: result.summary });\n continue;\n }\n\n refs[name] = result;\n }\n\n return { refs, problems };\n}\n\nexport async function writeRef(refsDir: string, name: string, entry: RefEntry): Promise<void> {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n if (!validateRefValue(entry.hash)) {\n throw errorInvalidRefValue(entry.hash);\n }\n\n const filePath = refFilePath(refsDir, name);\n const dir = dirname(filePath);\n await mkdir(dir, { recursive: true });\n\n const tmpPath = join(dir, `.${name.split('/').pop()}.json.${Date.now()}.tmp`);\n await writeFile(\n tmpPath,\n `${JSON.stringify({ hash: entry.hash, invariants: [...entry.invariants] }, null, 2)}\\n`,\n );\n await rename(tmpPath, filePath);\n}\n\nexport async function deleteRef(refsDir: string, name: string): Promise<void> {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n\n const filePath = refFilePath(refsDir, name);\n try {\n await unlink(filePath);\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n throw new MigrationToolsError('MIGRATION.UNKNOWN_REF', `Unknown ref \"${name}\"`, {\n why: `No ref file found at \"${filePath}\".`,\n fix: 'Run `prisma-next ref list` to see available refs.',\n details: { refName: name, filePath },\n });\n }\n throw error;\n }\n\n // Clean empty parent directories up to refsDir. Stop walking on the expected\n // \"directory has siblings\" signal (ENOTEMPTY on Linux, EEXIST on some BSDs)\n // and on ENOENT (concurrent removal). Anything else (EACCES, EIO, …) is a\n // real failure and propagates.\n let dir = dirname(filePath);\n while (dir !== refsDir && dir.startsWith(refsDir)) {\n try {\n await rmdir(dir);\n dir = dirname(dir);\n } catch (error) {\n const code = error instanceof Error ? (error as { code?: string }).code : undefined;\n if (code === 'ENOTEMPTY' || code === 'EEXIST' || code === 'ENOENT') {\n break;\n }\n throw error;\n }\n }\n}\n\n/**\n * Index user-authored refs by the contract hash each ref points at.\n * Each bucket is sorted lex-asc for deterministic output.\n */\nexport function refsByContractHash(refs: Refs): ReadonlyMap<string, readonly string[]> {\n const byHash = new Map<string, string[]>();\n for (const [name, entry] of Object.entries(refs)) {\n const bucket = byHash.get(entry.hash);\n if (bucket) bucket.push(name);\n else byHash.set(entry.hash, [name]);\n }\n for (const bucket of byHash.values()) {\n bucket.sort();\n }\n return byHash;\n}\n\n/**\n * Read `migrations/<space>/refs/*.json` and index by destination hash.\n * Returns an empty map when the refs directory does not exist.\n */\nexport async function resolveRefsByContractHash(\n refsDir: string,\n): Promise<ReadonlyMap<string, readonly string[]>> {\n return refsByContractHash(await readRefs(refsDir));\n}\n\nexport function resolveRef(refs: Refs, name: string): RefEntry {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n\n // Object.hasOwn gate: plain-object `refs` would otherwise let\n // `refs['constructor']` return Object.prototype.constructor and bypass the\n // UNKNOWN_REF throw. validateRefName accepts `\"constructor\"` as a name shape.\n if (!Object.hasOwn(refs, name)) {\n throw new MigrationToolsError('MIGRATION.UNKNOWN_REF', `Unknown ref \"${name}\"`, {\n why: `No ref named \"${name}\" exists.`,\n fix: `Available refs: ${Object.keys(refs).join(', ') || '(none)'}. Create a ref with: prisma-next ref set ${name} <hash>`,\n details: { refName: name, availableRefs: Object.keys(refs) },\n });\n }\n\n // biome-ignore lint/style/noNonNullAssertion: Object.hasOwn gate above guarantees this is defined\n return refs[name]!;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//#region src/refs.d.ts
|
|
2
|
+
interface RefEntry {
|
|
3
|
+
readonly hash: string;
|
|
4
|
+
readonly invariants: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
type Refs = Readonly<Record<string, RefEntry>>;
|
|
7
|
+
/**
|
|
8
|
+
* The system head ref lives at `refs/head.json`. It is read (and its
|
|
9
|
+
* corruption judged) through `readContractSpaceHeadRef`, not as a
|
|
10
|
+
* user-authored ref, so {@link readRefsTolerant} excludes it.
|
|
11
|
+
*/
|
|
12
|
+
declare const HEAD_REF_NAME = "head";
|
|
13
|
+
/**
|
|
14
|
+
* A single ref file that exists on disk but cannot be turned into a
|
|
15
|
+
* {@link RefEntry} (unparseable JSON or schema-invalid content). The ref
|
|
16
|
+
* is omitted from the result; the problem is surfaced for the integrity
|
|
17
|
+
* layer to report as `refUnreadable` rather than aborting the load.
|
|
18
|
+
*/
|
|
19
|
+
interface RefLoadProblem {
|
|
20
|
+
readonly refName: string;
|
|
21
|
+
readonly detail: string;
|
|
22
|
+
}
|
|
23
|
+
declare function validateRefName(name: string): boolean;
|
|
24
|
+
declare function validateRefValue(value: string): boolean;
|
|
25
|
+
declare function readRef(refsDir: string, name: string): Promise<RefEntry>;
|
|
26
|
+
declare function readRefs(refsDir: string): Promise<Refs>;
|
|
27
|
+
declare function writeRef(refsDir: string, name: string, entry: RefEntry): Promise<void>;
|
|
28
|
+
declare function deleteRef(refsDir: string, name: string): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Index user-authored refs by the contract hash each ref points at.
|
|
31
|
+
* Each bucket is sorted lex-asc for deterministic output.
|
|
32
|
+
*/
|
|
33
|
+
declare function refsByContractHash(refs: Refs): ReadonlyMap<string, readonly string[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Read `migrations/<space>/refs/*.json` and index by destination hash.
|
|
36
|
+
* Returns an empty map when the refs directory does not exist.
|
|
37
|
+
*/
|
|
38
|
+
declare function resolveRefsByContractHash(refsDir: string): Promise<ReadonlyMap<string, readonly string[]>>;
|
|
39
|
+
declare function resolveRef(refs: Refs, name: string): RefEntry;
|
|
40
|
+
//#endregion
|
|
41
|
+
export { deleteRef as a, refsByContractHash as c, validateRefName as d, validateRefValue as f, Refs as i, resolveRef as l, RefEntry as n, readRef as o, writeRef as p, RefLoadProblem as r, readRefs as s, HEAD_REF_NAME as t, resolveRefsByContractHash as u };
|
|
42
|
+
//# sourceMappingURL=refs-C7wuYFqZ.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refs-C7wuYFqZ.d.mts","names":[],"sources":["../src/refs.ts"],"mappings":";UAUiB,QAAA;EAAA,SACN,IAAA;EAAA,SACA,UAAU;AAAA;AAAA,KAGT,IAAA,GAAO,QAAA,CAAS,MAAA,SAAe,QAAA;AAHtB;AAGrB;;;;AAHqB,cAUR,aAAA;;;;;;;UAQI,cAAA;EAAA,SACN,OAAA;EAAA,SACA,MAAM;AAAA;AAAA,iBAWD,eAAA,CAAgB,IAAY;AAAA,iBAQ5B,gBAAA,CAAiB,KAAa;AAAA,iBAsBxB,OAAA,CAAQ,OAAA,UAAiB,IAAA,WAAe,OAAO,CAAC,QAAA;AAAA,iBAmChD,QAAA,CAAS,OAAA,WAAkB,OAAO,CAAC,IAAA;AAAA,iBA4HnC,QAAA,CAAS,OAAA,UAAiB,IAAA,UAAc,KAAA,EAAO,QAAA,GAAW,OAAO;AAAA,iBAoBjE,SAAA,CAAU,OAAA,UAAiB,IAAA,WAAe,OAAO;;;;AAnLO;iBA6N9D,kBAAA,CAAmB,IAAA,EAAM,IAAA,GAAO,WAAW;;;;;iBAiBrC,yBAAA,CACpB,OAAA,WACC,OAAO,CAAC,WAAA;AAAA,iBAIK,UAAA,CAAW,IAAA,EAAM,IAAA,EAAM,IAAA,WAAe,QAAQ"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { g as errorInvalidRefName, h as errorInvalidRefFile, t as MigrationToolsError } from "./errors-vFROOhCR.mjs";
|
|
2
|
+
import { d as writeRef, l as validateRefName, n as deleteRef } from "./refs-C-_WUrPw.mjs";
|
|
3
|
+
import { basename, dirname, join } from "pathe";
|
|
4
|
+
import { access, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
5
|
+
import { type } from "arktype";
|
|
6
|
+
import { randomBytes } from "node:crypto";
|
|
7
|
+
import { canonicalizeJson } from "@prisma-next/framework-components/utils";
|
|
8
|
+
//#region src/refs/snapshot.ts
|
|
9
|
+
const ContractIrSchema = type({
|
|
10
|
+
targetFamily: "string",
|
|
11
|
+
target: "string",
|
|
12
|
+
profileHash: "string",
|
|
13
|
+
storage: type({ storageHash: "string" }),
|
|
14
|
+
domain: type({ namespaces: "object" })
|
|
15
|
+
});
|
|
16
|
+
function hasErrnoCode(error, code) {
|
|
17
|
+
return error instanceof Error && error.code === code;
|
|
18
|
+
}
|
|
19
|
+
function snapshotJsonPath(refsDir, name) {
|
|
20
|
+
return join(refsDir, `${name}.contract.json`);
|
|
21
|
+
}
|
|
22
|
+
function snapshotDtsPath(refsDir, name) {
|
|
23
|
+
return join(refsDir, `${name}.contract.d.ts`);
|
|
24
|
+
}
|
|
25
|
+
function tmpPathFor(finalPath) {
|
|
26
|
+
return join(dirname(finalPath), `.${basename(finalPath)}.${Date.now()}-${randomBytes(4).toString("hex")}.tmp`);
|
|
27
|
+
}
|
|
28
|
+
async function atomicWriteFile(finalPath, content) {
|
|
29
|
+
await mkdir(dirname(finalPath), { recursive: true });
|
|
30
|
+
const tmpPath = tmpPathFor(finalPath);
|
|
31
|
+
await writeFile(tmpPath, content);
|
|
32
|
+
await rename(tmpPath, finalPath);
|
|
33
|
+
}
|
|
34
|
+
async function unlinkIfExists(filePath) {
|
|
35
|
+
try {
|
|
36
|
+
await unlink(filePath);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (hasErrnoCode(error, "ENOENT")) return;
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function parseContractSnapshotJson(filePath, raw) {
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(raw);
|
|
46
|
+
} catch {
|
|
47
|
+
throw errorInvalidRefFile(filePath, "Failed to parse as JSON");
|
|
48
|
+
}
|
|
49
|
+
const result = ContractIrSchema(parsed);
|
|
50
|
+
if (result instanceof type.errors) throw errorInvalidRefFile(filePath, result.summary);
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
async function writeRefSnapshot(refsDir, name, snapshot) {
|
|
54
|
+
if (!validateRefName(name)) throw errorInvalidRefName(name);
|
|
55
|
+
const jsonPath = snapshotJsonPath(refsDir, name);
|
|
56
|
+
const dtsPath = snapshotDtsPath(refsDir, name);
|
|
57
|
+
const jsonContent = `${canonicalizeJson(snapshot.contract)}\n`;
|
|
58
|
+
const dtsContent = snapshot.contractDts.endsWith("\n") ? snapshot.contractDts : `${snapshot.contractDts}\n`;
|
|
59
|
+
try {
|
|
60
|
+
await atomicWriteFile(jsonPath, jsonContent);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
await unlinkIfExists(jsonPath);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
await atomicWriteFile(dtsPath, dtsContent);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
await unlinkIfExists(jsonPath);
|
|
69
|
+
await unlinkIfExists(dtsPath);
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function readRefSnapshot(refsDir, name) {
|
|
74
|
+
if (!validateRefName(name)) throw errorInvalidRefName(name);
|
|
75
|
+
const jsonPath = snapshotJsonPath(refsDir, name);
|
|
76
|
+
const dtsPath = snapshotDtsPath(refsDir, name);
|
|
77
|
+
let raw;
|
|
78
|
+
try {
|
|
79
|
+
raw = await readFile(jsonPath, "utf-8");
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (hasErrnoCode(error, "ENOENT")) return null;
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
const contract = parseContractSnapshotJson(jsonPath, raw);
|
|
85
|
+
let contractDts;
|
|
86
|
+
try {
|
|
87
|
+
contractDts = await readFile(dtsPath, "utf-8");
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (hasErrnoCode(error, "ENOENT")) throw errorInvalidRefFile(dtsPath, "Missing paired contract.d.ts snapshot file");
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
contract,
|
|
94
|
+
contractDts
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
async function deleteRefSnapshot(refsDir, name) {
|
|
98
|
+
if (!validateRefName(name)) throw errorInvalidRefName(name);
|
|
99
|
+
await unlinkIfExists(snapshotJsonPath(refsDir, name));
|
|
100
|
+
await unlinkIfExists(snapshotDtsPath(refsDir, name));
|
|
101
|
+
}
|
|
102
|
+
async function writeRefPaired(refsDir, name, entry, snapshot) {
|
|
103
|
+
await writeRefSnapshot(refsDir, name, snapshot);
|
|
104
|
+
try {
|
|
105
|
+
await writeRef(refsDir, name, entry);
|
|
106
|
+
} catch (writeError) {
|
|
107
|
+
try {
|
|
108
|
+
await deleteRefSnapshot(refsDir, name);
|
|
109
|
+
} catch {}
|
|
110
|
+
throw writeError;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function isUnknownRefError(error) {
|
|
114
|
+
return MigrationToolsError.is(error) && error.code === "MIGRATION.UNKNOWN_REF";
|
|
115
|
+
}
|
|
116
|
+
async function snapshotFilesExist(refsDir, name) {
|
|
117
|
+
if (!validateRefName(name)) throw errorInvalidRefName(name);
|
|
118
|
+
const paths = [snapshotJsonPath(refsDir, name), snapshotDtsPath(refsDir, name)];
|
|
119
|
+
return (await Promise.allSettled(paths.map((filePath) => access(filePath)))).some((result) => result.status === "fulfilled");
|
|
120
|
+
}
|
|
121
|
+
async function deleteRefPaired(refsDir, name) {
|
|
122
|
+
if (await snapshotFilesExist(refsDir, name)) {
|
|
123
|
+
try {
|
|
124
|
+
await deleteRef(refsDir, name);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (!isUnknownRefError(error)) throw error;
|
|
127
|
+
}
|
|
128
|
+
await deleteRefSnapshot(refsDir, name);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
await deleteRef(refsDir, name);
|
|
132
|
+
await deleteRefSnapshot(refsDir, name);
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
export { writeRefSnapshot as a, writeRefPaired as i, deleteRefSnapshot as n, readRefSnapshot as r, deleteRefPaired as t };
|
|
136
|
+
|
|
137
|
+
//# sourceMappingURL=snapshot-Bazwo13S.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-Bazwo13S.mjs","names":[],"sources":["../src/refs/snapshot.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { access, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';\nimport { canonicalizeJson } from '@prisma-next/framework-components/utils';\nimport { type } from 'arktype';\nimport { basename, dirname, join } from 'pathe';\nimport { errorInvalidRefFile, errorInvalidRefName, MigrationToolsError } from '../errors';\nimport { deleteRef, type RefEntry, validateRefName, writeRef } from '../refs';\n\nexport interface ContractIR {\n readonly contract: unknown;\n readonly contractDts: string;\n}\n\nconst ContractIrSchema = type({\n targetFamily: 'string',\n target: 'string',\n profileHash: 'string',\n storage: type({\n storageHash: 'string',\n }),\n domain: type({\n namespaces: 'object',\n }),\n});\n\nfunction hasErrnoCode(error: unknown, code: string): boolean {\n return error instanceof Error && (error as { code?: string }).code === code;\n}\n\nfunction snapshotJsonPath(refsDir: string, name: string): string {\n return join(refsDir, `${name}.contract.json`);\n}\n\nfunction snapshotDtsPath(refsDir: string, name: string): string {\n return join(refsDir, `${name}.contract.d.ts`);\n}\n\nfunction tmpPathFor(finalPath: string): string {\n const dir = dirname(finalPath);\n const fileName = basename(finalPath);\n return join(dir, `.${fileName}.${Date.now()}-${randomBytes(4).toString('hex')}.tmp`);\n}\n\nasync function atomicWriteFile(finalPath: string, content: string): Promise<void> {\n const dir = dirname(finalPath);\n await mkdir(dir, { recursive: true });\n const tmpPath = tmpPathFor(finalPath);\n await writeFile(tmpPath, content);\n await rename(tmpPath, finalPath);\n}\n\nasync function unlinkIfExists(filePath: string): Promise<void> {\n try {\n await unlink(filePath);\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) return;\n throw error;\n }\n}\n\nfunction parseContractSnapshotJson(filePath: string, raw: string): unknown {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw errorInvalidRefFile(filePath, 'Failed to parse as JSON');\n }\n\n const result = ContractIrSchema(parsed);\n if (result instanceof type.errors) {\n throw errorInvalidRefFile(filePath, result.summary);\n }\n\n return result;\n}\n\nexport async function writeRefSnapshot(\n refsDir: string,\n name: string,\n snapshot: ContractIR,\n): Promise<void> {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n\n const jsonPath = snapshotJsonPath(refsDir, name);\n const dtsPath = snapshotDtsPath(refsDir, name);\n const jsonContent = `${canonicalizeJson(snapshot.contract)}\\n`;\n const dtsContent = snapshot.contractDts.endsWith('\\n')\n ? snapshot.contractDts\n : `${snapshot.contractDts}\\n`;\n\n try {\n await atomicWriteFile(jsonPath, jsonContent);\n } catch (error) {\n await unlinkIfExists(jsonPath);\n throw error;\n }\n\n try {\n await atomicWriteFile(dtsPath, dtsContent);\n } catch (error) {\n await unlinkIfExists(jsonPath);\n await unlinkIfExists(dtsPath);\n throw error;\n }\n}\n\nexport async function readRefSnapshot(refsDir: string, name: string): Promise<ContractIR | null> {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n\n const jsonPath = snapshotJsonPath(refsDir, name);\n const dtsPath = snapshotDtsPath(refsDir, name);\n\n let raw: string;\n try {\n raw = await readFile(jsonPath, 'utf-8');\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) {\n return null;\n }\n throw error;\n }\n\n const contract = parseContractSnapshotJson(jsonPath, raw);\n\n let contractDts: string;\n try {\n contractDts = await readFile(dtsPath, 'utf-8');\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) {\n throw errorInvalidRefFile(dtsPath, 'Missing paired contract.d.ts snapshot file');\n }\n throw error;\n }\n\n return { contract, contractDts };\n}\n\nexport async function deleteRefSnapshot(refsDir: string, name: string): Promise<void> {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n\n await unlinkIfExists(snapshotJsonPath(refsDir, name));\n await unlinkIfExists(snapshotDtsPath(refsDir, name));\n}\n\nexport async function writeRefPaired(\n refsDir: string,\n name: string,\n entry: RefEntry,\n snapshot: ContractIR,\n): Promise<void> {\n await writeRefSnapshot(refsDir, name, snapshot);\n try {\n await writeRef(refsDir, name, entry);\n } catch (writeError) {\n try {\n await deleteRefSnapshot(refsDir, name);\n } catch {\n // Rollback failure is secondary; preserve the original writeRef error.\n }\n throw writeError;\n }\n}\n\nfunction isUnknownRefError(error: unknown): boolean {\n return MigrationToolsError.is(error) && error.code === 'MIGRATION.UNKNOWN_REF';\n}\n\nasync function snapshotFilesExist(refsDir: string, name: string): Promise<boolean> {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n\n const paths = [snapshotJsonPath(refsDir, name), snapshotDtsPath(refsDir, name)];\n const checks = await Promise.allSettled(paths.map((filePath) => access(filePath)));\n return checks.some((result) => result.status === 'fulfilled');\n}\n\nexport async function deleteRefPaired(refsDir: string, name: string): Promise<void> {\n if (await snapshotFilesExist(refsDir, name)) {\n try {\n await deleteRef(refsDir, name);\n } catch (error) {\n if (!isUnknownRefError(error)) {\n throw error;\n }\n }\n await deleteRefSnapshot(refsDir, name);\n return;\n }\n\n await deleteRef(refsDir, name);\n await deleteRefSnapshot(refsDir, name);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
//#region src/verify-contract-spaces.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* List the per-space subdirectories under
|
|
4
|
+
* `<projectRoot>/migrations/`. Returns space-id directory names (sorted
|
|
5
|
+
* alphabetically) — i.e. any non-dot-prefixed subdirectory whose root
|
|
6
|
+
* does **not** contain a `migration.json` manifest. The manifest is the
|
|
7
|
+
* structural marker of a user-authored migration directory (see
|
|
8
|
+
* `readMigrationsDir` in `./io`); directory names themselves belong to
|
|
9
|
+
* the user and are not part of the contract.
|
|
10
|
+
*
|
|
11
|
+
* Returns `[]` if the migrations directory does not exist (greenfield
|
|
12
|
+
* project).
|
|
13
|
+
*
|
|
14
|
+
* Reads only the user's repo. **No descriptor import.** The caller
|
|
15
|
+
* (verifier) feeds the result into {@link verifyContractSpaces} alongside
|
|
16
|
+
* the loaded-space set and the marker rows.
|
|
17
|
+
*/
|
|
18
|
+
declare function listContractSpaceDirectories(projectMigrationsDir: string): Promise<readonly string[]>;
|
|
19
|
+
/**
|
|
20
|
+
* On-disk head value (`(hash, invariants)`) for one contract space.
|
|
21
|
+
* The verifier compares this against the marker row for the same space
|
|
22
|
+
* to detect drift between the user-emitted artefacts and the live DB
|
|
23
|
+
* marker.
|
|
24
|
+
*/
|
|
25
|
+
interface ContractSpaceHeadRecord {
|
|
26
|
+
readonly hash: string;
|
|
27
|
+
readonly invariants: readonly string[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Marker row read from `prisma_contract.marker` (one per `space`).
|
|
31
|
+
* Caller resolves these via the family runtime's marker reader before
|
|
32
|
+
* invoking {@link verifyContractSpaces}.
|
|
33
|
+
*/
|
|
34
|
+
interface SpaceMarkerRecord {
|
|
35
|
+
readonly hash: string;
|
|
36
|
+
readonly invariants: readonly string[];
|
|
37
|
+
}
|
|
38
|
+
interface VerifyContractSpacesInputs {
|
|
39
|
+
/**
|
|
40
|
+
* Set of contract spaces the project declares: `'app'` plus each
|
|
41
|
+
* extension space in `extensionPacks`. The caller's discovery path
|
|
42
|
+
* never reads the extension descriptor module — it walks the
|
|
43
|
+
* `extensionPacks` configuration in `prisma-next.config.ts` for the
|
|
44
|
+
* space ids.
|
|
45
|
+
*/
|
|
46
|
+
readonly loadedSpaces: ReadonlySet<string>;
|
|
47
|
+
/**
|
|
48
|
+
* Per-space subdirectories observed under
|
|
49
|
+
* `<projectRoot>/migrations/`. Resolved via
|
|
50
|
+
* {@link listContractSpaceDirectories}.
|
|
51
|
+
*/
|
|
52
|
+
readonly spaceDirsOnDisk: readonly string[];
|
|
53
|
+
/**
|
|
54
|
+
* Head ref per space, keyed by space id. Caller reads
|
|
55
|
+
* `<projectRoot>/migrations/<space-id>/contract.json` and
|
|
56
|
+
* `<projectRoot>/migrations/<space-id>/refs/head.json` to construct
|
|
57
|
+
* this map. Spaces with no contract-space dir on disk simply omit a
|
|
58
|
+
* map entry.
|
|
59
|
+
*/
|
|
60
|
+
readonly headRefsBySpace: ReadonlyMap<string, ContractSpaceHeadRecord>;
|
|
61
|
+
/**
|
|
62
|
+
* Marker rows keyed by `space`. Caller reads them from the
|
|
63
|
+
* `prisma_contract.marker` table.
|
|
64
|
+
*/
|
|
65
|
+
readonly markerRowsBySpace: ReadonlyMap<string, SpaceMarkerRecord>;
|
|
66
|
+
}
|
|
67
|
+
type SpaceVerifierViolation = {
|
|
68
|
+
readonly kind: 'declaredButUnmigrated';
|
|
69
|
+
readonly spaceId: string;
|
|
70
|
+
readonly remediation: string;
|
|
71
|
+
} | {
|
|
72
|
+
readonly kind: 'orphanMarker';
|
|
73
|
+
readonly spaceId: string;
|
|
74
|
+
readonly remediation: string;
|
|
75
|
+
} | {
|
|
76
|
+
readonly kind: 'orphanSpaceDir';
|
|
77
|
+
readonly spaceId: string;
|
|
78
|
+
readonly remediation: string;
|
|
79
|
+
} | {
|
|
80
|
+
readonly kind: 'hashMismatch';
|
|
81
|
+
readonly spaceId: string;
|
|
82
|
+
readonly priorHeadHash: string;
|
|
83
|
+
readonly markerHash: string;
|
|
84
|
+
readonly remediation: string;
|
|
85
|
+
} | {
|
|
86
|
+
readonly kind: 'invariantsMismatch';
|
|
87
|
+
readonly spaceId: string;
|
|
88
|
+
readonly onDiskInvariants: readonly string[];
|
|
89
|
+
readonly markerInvariants: readonly string[];
|
|
90
|
+
readonly remediation: string;
|
|
91
|
+
};
|
|
92
|
+
type VerifyContractSpacesResult = {
|
|
93
|
+
readonly ok: true;
|
|
94
|
+
} | {
|
|
95
|
+
readonly ok: false;
|
|
96
|
+
readonly violations: readonly SpaceVerifierViolation[];
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Pure structural verifier for the per-space mechanism. Aggregates the
|
|
100
|
+
* three orphan / missing checks plus per-space hash and invariant
|
|
101
|
+
* comparison.
|
|
102
|
+
*
|
|
103
|
+
* Algorithm:
|
|
104
|
+
*
|
|
105
|
+
* - For every extension space declared in `loadedSpaces` (`'app'`
|
|
106
|
+
* excluded — the per-space verifier is scoped to extension members;
|
|
107
|
+
* the app is verified through the aggregate path):
|
|
108
|
+
* - If no contract-space dir on disk → `declaredButUnmigrated`.
|
|
109
|
+
* - Else if `markerRowsBySpace` lacks an entry → no violation here;
|
|
110
|
+
* the live-DB compare done outside this helper is where the
|
|
111
|
+
* absence shows up.
|
|
112
|
+
* - Else compare marker hash / invariants vs. on-disk head hash /
|
|
113
|
+
* invariants → `hashMismatch` / `invariantsMismatch` on drift.
|
|
114
|
+
* - For every contract-space dir on disk that is not in `loadedSpaces` →
|
|
115
|
+
* `orphanSpaceDir`.
|
|
116
|
+
* - For every marker row whose `space` is not in `loadedSpaces` →
|
|
117
|
+
* `orphanMarker`. The app-space marker is always loaded (`'app'` is
|
|
118
|
+
* in `loadedSpaces` by definition).
|
|
119
|
+
*
|
|
120
|
+
* Output is deterministic: violations are sorted first by `kind`
|
|
121
|
+
* (`declaredButUnmigrated` → `orphanMarker` → `orphanSpaceDir` →
|
|
122
|
+
* `hashMismatch` → `invariantsMismatch`) then by `spaceId`. Two callers
|
|
123
|
+
* passing equivalent inputs see byte-identical violation lists.
|
|
124
|
+
*
|
|
125
|
+
* Synchronous, pure, no I/O. **Does not import the extension descriptor**
|
|
126
|
+
* (the inputs are pre-resolved by the caller); the verifier reads only
|
|
127
|
+
* the user repo, not `node_modules`.
|
|
128
|
+
*/
|
|
129
|
+
declare function verifyContractSpaces(inputs: VerifyContractSpacesInputs): VerifyContractSpacesResult;
|
|
130
|
+
//#endregion
|
|
131
|
+
export { VerifyContractSpacesResult as a, VerifyContractSpacesInputs as i, SpaceMarkerRecord as n, listContractSpaceDirectories as o, SpaceVerifierViolation as r, verifyContractSpaces as s, ContractSpaceHeadRecord as t };
|
|
132
|
+
//# sourceMappingURL=verify-contract-spaces-BdysZdQk.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-contract-spaces-BdysZdQk.d.mts","names":[],"sources":["../src/verify-contract-spaces.ts"],"mappings":";;AAyBA;;;;AAEU;AAyCV;;;;AAEqB;AAQrB;;;;AAEqB;iBAvDC,4BAAA,CACpB,oBAAA,WACC,OAAO;;;;;;;UAyCO,uBAAA;EAAA,SACN,IAAA;EAAA,SACA,UAAU;AAAA;;;;;;UAQJ,iBAAA;EAAA,SACN,IAAA;EAAA,SACA,UAAU;AAAA;AAAA,UAGJ,0BAAA;EAiCL;;;;;;;EAAA,SAzBD,YAAA,EAAc,WAAA;EAiCV;;;;;EAAA,SA1BJ,eAAA;EAoCI;;;;;;;EAAA,SA3BJ,eAAA,EAAiB,WAAA,SAAoB,uBAAA;EAqCjC;;AAAW;AAG1B;EAHe,SA/BJ,iBAAA,EAAmB,WAAA,SAAoB,iBAAA;AAAA;AAAA,KAGtC,sBAAA;EAAA,SAEG,IAAA;EAAA,SACA,OAAA;EAAA,SACA,WAAA;AAAA;EAAA,SAGA,IAAA;EAAA,SACA,OAAA;EAAA,SACA,WAAA;AAAA;EAAA,SAGA,IAAA;EAAA,SACA,OAAA;EAAA,SACA,WAAA;AAAA;EAAA,SAGA,IAAA;EAAA,SACA,OAAA;EAAA,SACA,aAAA;EAAA,SACA,UAAA;EAAA,SACA,WAAA;AAAA;EAAA,SAGA,IAAA;EAAA,SACA,OAAA;EAAA,SACA,gBAAA;EAAA,SACA,gBAAA;EAAA,SACA,WAAA;AAAA;AAAA,KAGH,0BAAA;EAAA,SACG,EAAA;AAAA;EAAA,SACA,EAAA;EAAA,SAAoB,UAAA,WAAqB,sBAAsB;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiC9D,oBAAA,CACd,MAAA,EAAQ,0BAAA,GACP,0BAA0B"}
|
package/package.json
CHANGED
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/migration-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0-dev.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"description": "On-disk migration persistence, hash verification, and chain reconstruction for Prisma Next",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@prisma-next/contract": "0.
|
|
10
|
-
"@prisma-next/framework-components": "0.
|
|
11
|
-
"@prisma-next/utils": "0.
|
|
9
|
+
"@prisma-next/contract": "0.12.0-dev.1",
|
|
10
|
+
"@prisma-next/framework-components": "0.12.0-dev.1",
|
|
11
|
+
"@prisma-next/utils": "0.12.0-dev.1",
|
|
12
12
|
"arktype": "^2.2.0",
|
|
13
13
|
"pathe": "^2.0.3",
|
|
14
14
|
"prettier": "^3.8.3"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@prisma-next/
|
|
18
|
-
"@prisma-next/
|
|
17
|
+
"@prisma-next/test-utils": "0.12.0-dev.1",
|
|
18
|
+
"@prisma-next/tsconfig": "0.12.0-dev.1",
|
|
19
|
+
"@prisma-next/tsdown": "0.12.0-dev.1",
|
|
19
20
|
"tsdown": "0.22.0",
|
|
20
21
|
"typescript": "5.9.3",
|
|
21
22
|
"vitest": "4.1.6"
|
|
22
23
|
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"typescript": ">=5.9"
|
|
26
|
+
},
|
|
27
|
+
"peerDependenciesMeta": {
|
|
28
|
+
"typescript": {
|
|
29
|
+
"optional": true
|
|
30
|
+
}
|
|
31
|
+
},
|
|
23
32
|
"files": [
|
|
24
33
|
"dist",
|
|
25
34
|
"src"
|
|
26
35
|
],
|
|
27
|
-
"engines": {
|
|
28
|
-
"node": ">=20"
|
|
29
|
-
},
|
|
30
36
|
"exports": {
|
|
31
37
|
"./metadata": {
|
|
32
38
|
"types": "./dist/exports/metadata.d.mts",
|
|
@@ -90,6 +96,9 @@
|
|
|
90
96
|
},
|
|
91
97
|
"./package.json": "./package.json"
|
|
92
98
|
},
|
|
99
|
+
"engines": {
|
|
100
|
+
"node": ">=24"
|
|
101
|
+
},
|
|
93
102
|
"repository": {
|
|
94
103
|
"type": "git",
|
|
95
104
|
"url": "https://github.com/prisma/prisma-next.git",
|