@prisma-next/migration-tools 0.11.0-dev.5 → 0.11.0-dev.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +4 -4
  2. package/dist/{errors-DGYwcwXs.mjs → errors-4YabujxZ.mjs} +15 -21
  3. package/dist/errors-4YabujxZ.mjs.map +1 -0
  4. package/dist/exports/aggregate.d.mts +275 -179
  5. package/dist/exports/aggregate.d.mts.map +1 -1
  6. package/dist/exports/aggregate.mjs +363 -184
  7. package/dist/exports/aggregate.mjs.map +1 -1
  8. package/dist/exports/enumerate-migration-spaces.d.mts +53 -0
  9. package/dist/exports/enumerate-migration-spaces.d.mts.map +1 -0
  10. package/dist/exports/enumerate-migration-spaces.mjs +107 -0
  11. package/dist/exports/enumerate-migration-spaces.mjs.map +1 -0
  12. package/dist/exports/errors.d.mts +2 -2
  13. package/dist/exports/errors.d.mts.map +1 -1
  14. package/dist/exports/errors.mjs +1 -1
  15. package/dist/exports/graph.d.mts +1 -1
  16. package/dist/exports/hash.d.mts +8 -9
  17. package/dist/exports/hash.d.mts.map +1 -1
  18. package/dist/exports/hash.mjs +1 -1
  19. package/dist/exports/invariants.d.mts +1 -1
  20. package/dist/exports/invariants.d.mts.map +1 -1
  21. package/dist/exports/invariants.mjs +1 -1
  22. package/dist/exports/io.d.mts +2 -83
  23. package/dist/exports/io.mjs +1 -1
  24. package/dist/exports/metadata.d.mts +2 -2
  25. package/dist/exports/migration-graph.d.mts +9 -2
  26. package/dist/exports/migration-graph.d.mts.map +1 -0
  27. package/dist/exports/migration-graph.mjs +16 -2
  28. package/dist/exports/migration-graph.mjs.map +1 -0
  29. package/dist/exports/migration-list-graph-topology.d.mts +13 -0
  30. package/dist/exports/migration-list-graph-topology.d.mts.map +1 -0
  31. package/dist/exports/migration-list-graph-topology.mjs +105 -0
  32. package/dist/exports/migration-list-graph-topology.mjs.map +1 -0
  33. package/dist/exports/migration-list-types.d.mts +2 -0
  34. package/dist/exports/migration-list-types.mjs +1 -0
  35. package/dist/exports/migration-ts.d.mts.map +1 -1
  36. package/dist/exports/migration-ts.mjs.map +1 -1
  37. package/dist/exports/migration.d.mts +5 -6
  38. package/dist/exports/migration.d.mts.map +1 -1
  39. package/dist/exports/migration.mjs +14 -32
  40. package/dist/exports/migration.mjs.map +1 -1
  41. package/dist/exports/package.d.mts +1 -1
  42. package/dist/exports/ref-resolution.d.mts +2 -2
  43. package/dist/exports/ref-resolution.d.mts.map +1 -1
  44. package/dist/exports/ref-resolution.mjs +1 -1
  45. package/dist/exports/ref-resolution.mjs.map +1 -1
  46. package/dist/exports/refs.d.mts +15 -2
  47. package/dist/exports/refs.d.mts.map +1 -0
  48. package/dist/exports/refs.mjs +137 -2
  49. package/dist/exports/refs.mjs.map +1 -0
  50. package/dist/exports/spaces.d.mts +31 -132
  51. package/dist/exports/spaces.d.mts.map +1 -1
  52. package/dist/exports/spaces.mjs +14 -9
  53. package/dist/exports/spaces.mjs.map +1 -1
  54. package/dist/{graph-BrLXqoUc.d.mts → graph-BUZuUeBC.d.mts} +1 -2
  55. package/dist/graph-BUZuUeBC.d.mts.map +1 -0
  56. package/dist/{hash-Cr4WIr4Z.mjs → hash--Y7vCpN3.mjs} +8 -9
  57. package/dist/hash--Y7vCpN3.mjs.map +1 -0
  58. package/dist/{invariants-0daYEzyo.mjs → invariants-CCOAyg6c.mjs} +2 -2
  59. package/dist/{invariants-0daYEzyo.mjs.map → invariants-CCOAyg6c.mjs.map} +1 -1
  60. package/dist/{io-BPLfzvZe.mjs → io-BHl0amF0.mjs} +100 -13
  61. package/dist/io-BHl0amF0.mjs.map +1 -0
  62. package/dist/io-nqFXSSTN.d.mts +124 -0
  63. package/dist/io-nqFXSSTN.d.mts.map +1 -0
  64. package/dist/metadata-Bp9X04gM.d.mts +2 -0
  65. package/dist/{migration-graph-De0dUZoC.d.mts → migration-graph-DtNT-cqc.d.mts} +6 -6
  66. package/dist/migration-graph-DtNT-cqc.d.mts.map +1 -0
  67. package/dist/{migration-graph-nlS4TRpn.mjs → migration-graph-kGBkIZDa.mjs} +6 -26
  68. package/dist/migration-graph-kGBkIZDa.mjs.map +1 -0
  69. package/dist/migration-list-types-BRTuXR8i.d.mts +23 -0
  70. package/dist/migration-list-types-BRTuXR8i.d.mts.map +1 -0
  71. package/dist/op-schema-D5qkXfEf.mjs.map +1 -1
  72. package/dist/{package-DZj8YvD0.d.mts → package-DIttKL7X.d.mts} +1 -1
  73. package/dist/package-DIttKL7X.d.mts.map +1 -0
  74. package/dist/read-contract-space-contract-BS5Oxbgw.mjs +82 -0
  75. package/dist/read-contract-space-contract-BS5Oxbgw.mjs.map +1 -0
  76. package/dist/{refs-BDHo5l_g.mjs → refs-BBKNL45K.mjs} +76 -4
  77. package/dist/refs-BBKNL45K.mjs.map +1 -0
  78. package/dist/{refs-CDaNerhT.d.mts → refs-C8f2IGM8.d.mts} +12 -2
  79. package/dist/refs-C8f2IGM8.d.mts.map +1 -0
  80. package/dist/{read-contract-space-contract-DRueB4Aa.mjs → verify-contract-spaces-BJX5gqtD.mjs} +32 -80
  81. package/dist/verify-contract-spaces-BJX5gqtD.mjs.map +1 -0
  82. package/dist/verify-contract-spaces-T0aiJlBS.d.mts +132 -0
  83. package/dist/verify-contract-spaces-T0aiJlBS.d.mts.map +1 -0
  84. package/package.json +18 -6
  85. package/src/aggregate/aggregate.ts +90 -0
  86. package/src/aggregate/check-integrity.ts +243 -0
  87. package/src/aggregate/loader.ts +156 -334
  88. package/src/aggregate/planner.ts +8 -6
  89. package/src/aggregate/project-schema-to-space.ts +1 -1
  90. package/src/aggregate/strategies/graph-walk.ts +12 -7
  91. package/src/aggregate/strategies/synth.ts +2 -2
  92. package/src/aggregate/types.ts +56 -64
  93. package/src/aggregate/verifier.ts +6 -4
  94. package/src/assert-descriptor-self-consistency.ts +6 -0
  95. package/src/compute-extension-space-apply-path.ts +1 -1
  96. package/src/emit-contract-space-artefacts.ts +4 -3
  97. package/src/enumerate-migration-spaces.ts +127 -0
  98. package/src/errors.ts +17 -2
  99. package/src/exports/aggregate.ts +17 -12
  100. package/src/exports/enumerate-migration-spaces.ts +4 -0
  101. package/src/exports/io.ts +2 -0
  102. package/src/exports/metadata.ts +1 -1
  103. package/src/exports/migration-graph.ts +1 -0
  104. package/src/exports/migration-list-graph-topology.ts +5 -0
  105. package/src/exports/migration-list-types.ts +5 -0
  106. package/src/exports/refs.ts +8 -0
  107. package/src/exports/spaces.ts +3 -0
  108. package/src/graph-membership.ts +17 -0
  109. package/src/graph.ts +0 -1
  110. package/src/hash.ts +7 -8
  111. package/src/integrity-violation.ts +114 -0
  112. package/src/io.ts +139 -14
  113. package/src/metadata.ts +1 -1
  114. package/src/migration-base.ts +10 -30
  115. package/src/migration-graph.ts +7 -35
  116. package/src/migration-list-graph-topology.ts +158 -0
  117. package/src/migration-list-types.ts +21 -0
  118. package/src/read-contract-space-head-ref.ts +5 -2
  119. package/src/refs/snapshot.ts +197 -0
  120. package/src/refs.ts +97 -1
  121. package/src/space-layout.ts +30 -0
  122. package/dist/errors-DGYwcwXs.mjs.map +0 -1
  123. package/dist/exports/io.d.mts.map +0 -1
  124. package/dist/graph-BrLXqoUc.d.mts.map +0 -1
  125. package/dist/hash-Cr4WIr4Z.mjs.map +0 -1
  126. package/dist/io-BPLfzvZe.mjs.map +0 -1
  127. package/dist/metadata-BFX0xdz8.d.mts +0 -2
  128. package/dist/migration-graph-De0dUZoC.d.mts.map +0 -1
  129. package/dist/migration-graph-nlS4TRpn.mjs.map +0 -1
  130. package/dist/package-DZj8YvD0.d.mts.map +0 -1
  131. package/dist/read-contract-space-contract-DRueB4Aa.mjs.map +0 -1
  132. package/dist/refs-BDHo5l_g.mjs.map +0 -1
  133. package/dist/refs-CDaNerhT.d.mts.map +0 -1
@@ -0,0 +1,90 @@
1
+ import type { Contract } from '@prisma-next/contract/types';
2
+ import type { MigrationGraph } from '../graph';
3
+ import type { IntegrityQueryOptions, IntegrityViolation } from '../integrity-violation';
4
+ import { reconstructGraph } from '../migration-graph';
5
+ import type { OnDiskMigrationPackage } from '../package';
6
+ import type { Refs } from '../refs';
7
+ import type { ContractSpaceHeadRecord } from '../verify-contract-spaces';
8
+ import type { ContractSpaceAggregate, ContractSpaceMember } from './types';
9
+
10
+ /**
11
+ * Resolve a member's head ref, asserting it is present. The apply/verify
12
+ * engine only runs after `checkIntegrity` has refused on `headRefMissing`,
13
+ * so a member reaching the planner / verifier without a head ref is a
14
+ * programming error (the integrity gate was skipped), not a user-facing
15
+ * state. The app member's head ref is always synthesised, so this only
16
+ * ever guards an ungated extension space.
17
+ */
18
+ export function requireHeadRef(member: ContractSpaceMember): ContractSpaceHeadRecord {
19
+ if (member.headRef === null) {
20
+ throw new Error(
21
+ `Contract space "${member.spaceId}" has no head ref; the integrity gate must refuse a missing head ref before planning or verifying.`,
22
+ );
23
+ }
24
+ return member.headRef;
25
+ }
26
+
27
+ /**
28
+ * Build a {@link ContractSpaceMember} with lazily-memoised `graph()` and
29
+ * `contract()` facets.
30
+ *
31
+ * `graph()` reconstructs the migration graph from `packages` on first
32
+ * call and caches it. `contract()` calls `resolveContract` on first call
33
+ * and caches the result; a throwing `resolveContract` (e.g. a missing or
34
+ * undeserializable on-disk contract) re-throws on each call rather than
35
+ * caching a value — `checkIntegrity` surfaces that as `contractUnreadable`.
36
+ */
37
+ export function createContractSpaceMember(args: {
38
+ readonly spaceId: string;
39
+ readonly packages: readonly OnDiskMigrationPackage[];
40
+ readonly refs: Refs;
41
+ readonly headRef: ContractSpaceHeadRecord | null;
42
+ readonly resolveContract: () => Contract;
43
+ }): ContractSpaceMember {
44
+ const { spaceId, packages, refs, headRef, resolveContract } = args;
45
+ let graphMemo: MigrationGraph | undefined;
46
+ let contractMemo: Contract | undefined;
47
+ return {
48
+ spaceId,
49
+ packages,
50
+ refs,
51
+ headRef,
52
+ graph() {
53
+ graphMemo ??= reconstructGraph(packages);
54
+ return graphMemo;
55
+ },
56
+ contract() {
57
+ contractMemo ??= resolveContract();
58
+ return contractMemo;
59
+ },
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Assemble a {@link ContractSpaceAggregate} value from its members and a
65
+ * `checkIntegrity` implementation. The query methods (`listSpaces` /
66
+ * `hasSpace` / `space` / `spaces`) are derived here so every aggregate —
67
+ * loader-built or test-built — shares one query surface: `app` first,
68
+ * then `extensions` in the order supplied (the loader sorts them
69
+ * lex-ascending by `spaceId`).
70
+ */
71
+ export function createContractSpaceAggregate(args: {
72
+ readonly targetId: string;
73
+ readonly app: ContractSpaceMember;
74
+ readonly extensions: readonly ContractSpaceMember[];
75
+ readonly checkIntegrity: (opts?: IntegrityQueryOptions) => readonly IntegrityViolation[];
76
+ }): ContractSpaceAggregate {
77
+ const { targetId, app, extensions, checkIntegrity } = args;
78
+ const ordered: readonly ContractSpaceMember[] = [app, ...extensions];
79
+ const byId = new Map(ordered.map((m) => [m.spaceId, m]));
80
+ return {
81
+ targetId,
82
+ app,
83
+ extensions,
84
+ listSpaces: () => ordered.map((m) => m.spaceId),
85
+ hasSpace: (id) => byId.has(id),
86
+ space: (id) => byId.get(id),
87
+ spaces: () => ordered,
88
+ checkIntegrity,
89
+ };
90
+ }
@@ -0,0 +1,243 @@
1
+ import { EMPTY_CONTRACT_HASH } from '../constants';
2
+ import { MigrationToolsError } from '../errors';
3
+ import type {
4
+ DeclaredExtensionEntry,
5
+ IntegrityQueryOptions,
6
+ IntegrityViolation,
7
+ } from '../integrity-violation';
8
+ import type { PackageLoadProblem } from '../io';
9
+ import type { OnDiskMigrationPackage } from '../package';
10
+ import type { RefLoadProblem } from '../refs';
11
+ import { extractStorageElementNames } from './extract-storage-element-names';
12
+ import type { ContractSpaceMember } from './types';
13
+
14
+ /**
15
+ * One space's load-time facts that `checkIntegrity` judges: the loaded
16
+ * member, the load-time problems `readMigrationsDir` surfaced for it, and
17
+ * whether it is the app space (the app head ref is synthesised, so the
18
+ * head-ref checks are skipped for it).
19
+ */
20
+ export interface IntegritySpaceState {
21
+ readonly member: ContractSpaceMember;
22
+ readonly problems: readonly PackageLoadProblem[];
23
+ /** Per-ref problems: a user ref `*.json` that exists but is unparseable. */
24
+ readonly refProblems: readonly RefLoadProblem[];
25
+ /**
26
+ * The space's `refs/head.json` problem when it exists but is unparseable.
27
+ * `null` means the head ref was read cleanly or is genuinely absent —
28
+ * the absent case is judged `headRefMissing`, the corrupt case here is
29
+ * judged `refUnreadable` (and suppresses `headRefMissing`).
30
+ */
31
+ readonly headRefProblem: RefLoadProblem | null;
32
+ readonly isApp: boolean;
33
+ }
34
+
35
+ export interface IntegrityComputationInput {
36
+ readonly targetId: string;
37
+ readonly spaces: readonly IntegritySpaceState[];
38
+ }
39
+
40
+ /**
41
+ * Walk the loaded model and return **every** integrity violation — never
42
+ * bailing at the first. Structurally-derivable violations (load-time
43
+ * problems, self-edges, missing / unreachable head refs) are always
44
+ * produced; layout-drift checks require `declaredExtensions`, and
45
+ * contract / target / disjointness checks require `checkContracts`.
46
+ */
47
+ export function computeIntegrityViolations(
48
+ input: IntegrityComputationInput,
49
+ opts?: IntegrityQueryOptions,
50
+ ): readonly IntegrityViolation[] {
51
+ const violations: IntegrityViolation[] = [];
52
+
53
+ for (const { member, problems, refProblems, headRefProblem, isApp } of input.spaces) {
54
+ const { spaceId } = member;
55
+
56
+ for (const problem of problems) {
57
+ violations.push(loadProblemToViolation(spaceId, problem));
58
+ }
59
+
60
+ for (const refProblem of refProblems) {
61
+ violations.push({
62
+ kind: 'refUnreadable',
63
+ spaceId,
64
+ refName: refProblem.refName,
65
+ detail: refProblem.detail,
66
+ });
67
+ }
68
+ if (headRefProblem !== null) {
69
+ violations.push({
70
+ kind: 'refUnreadable',
71
+ spaceId,
72
+ refName: headRefProblem.refName,
73
+ detail: headRefProblem.detail,
74
+ });
75
+ }
76
+
77
+ for (const pkg of member.packages) {
78
+ const from = pkg.metadata.from ?? EMPTY_CONTRACT_HASH;
79
+ const isSelfEdge = from === pkg.metadata.to;
80
+ const hasDataOp = pkg.ops.some((op) => op.operationClass === 'data');
81
+ if (isSelfEdge && !hasDataOp) {
82
+ violations.push({ kind: 'sameSourceAndTarget', spaceId, dirName: pkg.dirName, hash: from });
83
+ }
84
+ }
85
+
86
+ violations.push(...duplicateMigrationHashViolations(spaceId, member.packages));
87
+
88
+ // The app head ref is synthesised from the live contract, so it is
89
+ // always present and reachable; only extension spaces read their head
90
+ // ref from disk and can be missing or point outside the graph. A head
91
+ // ref that exists but is unparseable is already surfaced above as
92
+ // `refUnreadable`, so it is not also reported as `headRefMissing`.
93
+ if (!isApp && headRefProblem === null) {
94
+ if (member.headRef === null) {
95
+ violations.push({ kind: 'headRefMissing', spaceId });
96
+ } else if (!headRefPresentInGraph(member, member.headRef.hash)) {
97
+ violations.push({ kind: 'headRefNotInGraph', spaceId, hash: member.headRef.hash });
98
+ }
99
+ }
100
+ }
101
+
102
+ if (opts?.declaredExtensions !== undefined) {
103
+ violations.push(...layoutViolations(input.spaces, opts.declaredExtensions));
104
+ }
105
+
106
+ if (opts?.checkContracts === true) {
107
+ violations.push(...contractViolations(input));
108
+ }
109
+
110
+ return violations;
111
+ }
112
+
113
+ export function loadProblemToViolation(
114
+ spaceId: string,
115
+ problem: PackageLoadProblem,
116
+ ): IntegrityViolation {
117
+ switch (problem.kind) {
118
+ case 'hashMismatch':
119
+ return {
120
+ kind: 'hashMismatch',
121
+ spaceId,
122
+ dirName: problem.dirName,
123
+ stored: problem.stored,
124
+ computed: problem.computed,
125
+ };
126
+ case 'providedInvariantsMismatch':
127
+ return { kind: 'providedInvariantsMismatch', spaceId, dirName: problem.dirName };
128
+ case 'packageUnloadable':
129
+ return {
130
+ kind: 'packageUnloadable',
131
+ spaceId,
132
+ dirName: problem.dirName,
133
+ detail: problem.detail,
134
+ };
135
+ }
136
+ }
137
+
138
+ function duplicateMigrationHashViolations(
139
+ spaceId: string,
140
+ packages: readonly OnDiskMigrationPackage[],
141
+ ): readonly IntegrityViolation[] {
142
+ const dirNamesByHash = new Map<string, string[]>();
143
+ for (const pkg of packages) {
144
+ const hash = pkg.metadata.migrationHash;
145
+ const dirNames = dirNamesByHash.get(hash);
146
+ if (dirNames) dirNames.push(pkg.dirName);
147
+ else dirNamesByHash.set(hash, [pkg.dirName]);
148
+ }
149
+
150
+ const out: IntegrityViolation[] = [];
151
+ for (const [migrationHash, dirNames] of dirNamesByHash) {
152
+ if (dirNames.length > 1) {
153
+ out.push({
154
+ kind: 'duplicateMigrationHash',
155
+ spaceId,
156
+ migrationHash,
157
+ dirNames: [...dirNames].sort(),
158
+ });
159
+ }
160
+ }
161
+ return out;
162
+ }
163
+
164
+ /**
165
+ * Whether a space's head-ref hash is present in its reconstructed graph.
166
+ * An empty graph is reachable only by the empty-contract sentinel.
167
+ */
168
+ function headRefPresentInGraph(member: ContractSpaceMember, headHash: string): boolean {
169
+ const graph = member.graph();
170
+ if (graph.nodes.size === 0) {
171
+ return headHash === EMPTY_CONTRACT_HASH;
172
+ }
173
+ return graph.nodes.has(headHash);
174
+ }
175
+
176
+ function layoutViolations(
177
+ spaces: readonly IntegritySpaceState[],
178
+ declaredExtensions: readonly DeclaredExtensionEntry[],
179
+ ): readonly IntegrityViolation[] {
180
+ const out: IntegrityViolation[] = [];
181
+ const extensionSpaceIds = new Set(spaces.filter((s) => !s.isApp).map((s) => s.member.spaceId));
182
+ const declaredIds = new Set(declaredExtensions.map((d) => d.id));
183
+
184
+ for (const id of [...extensionSpaceIds].sort()) {
185
+ if (!declaredIds.has(id)) {
186
+ out.push({ kind: 'orphanSpaceDir', spaceId: id });
187
+ }
188
+ }
189
+ for (const id of [...declaredIds].sort()) {
190
+ if (!extensionSpaceIds.has(id)) {
191
+ out.push({ kind: 'declaredButUnmigrated', spaceId: id });
192
+ }
193
+ }
194
+ return out;
195
+ }
196
+
197
+ function contractViolations(input: IntegrityComputationInput): readonly IntegrityViolation[] {
198
+ const out: IntegrityViolation[] = [];
199
+ const elementClaimedBy = new Map<string, string[]>();
200
+
201
+ for (const { member } of input.spaces) {
202
+ let contract: ReturnType<ContractSpaceMember['contract']>;
203
+ try {
204
+ contract = member.contract();
205
+ } catch (error) {
206
+ out.push({ kind: 'contractUnreadable', spaceId: member.spaceId, detail: detailOf(error) });
207
+ continue;
208
+ }
209
+
210
+ if (contract.target !== input.targetId) {
211
+ out.push({
212
+ kind: 'targetMismatch',
213
+ spaceId: member.spaceId,
214
+ expected: input.targetId,
215
+ actual: contract.target,
216
+ });
217
+ }
218
+
219
+ for (const elementName of extractStorageElementNames(contract)) {
220
+ const claimers = elementClaimedBy.get(elementName);
221
+ if (claimers) claimers.push(member.spaceId);
222
+ else elementClaimedBy.set(elementName, [member.spaceId]);
223
+ }
224
+ }
225
+
226
+ const disjointness: IntegrityViolation[] = [];
227
+ for (const [element, claimedBy] of elementClaimedBy) {
228
+ if (claimedBy.length > 1) {
229
+ disjointness.push({ kind: 'disjointness', element, claimedBy: [...claimedBy].sort() });
230
+ }
231
+ }
232
+ disjointness.sort((a, b) =>
233
+ a.kind === 'disjointness' && b.kind === 'disjointness' ? a.element.localeCompare(b.element) : 0,
234
+ );
235
+ out.push(...disjointness);
236
+ return out;
237
+ }
238
+
239
+ function detailOf(error: unknown): string {
240
+ if (MigrationToolsError.is(error)) return error.why;
241
+ if (error instanceof Error) return error.message;
242
+ return String(error);
243
+ }