@prisma/orm-mongo 8.0.0-rc.4-dev.17 → 8.0.0-rc.5-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.
Files changed (64) hide show
  1. package/package.json +11 -10
  2. package/skills/prisma-8/SKILL.md +84 -0
  3. package/skills/prisma-8/references/build.md +142 -0
  4. package/skills/prisma-8/references/contract.md +417 -0
  5. package/skills/prisma-8/references/debug.md +141 -0
  6. package/skills/prisma-8/references/feedback.md +251 -0
  7. package/skills/prisma-8/references/migration-review.md +224 -0
  8. package/skills/prisma-8/references/migrations.md +519 -0
  9. package/skills/prisma-8/references/queries-mongo.md +236 -0
  10. package/skills/prisma-8/references/queries-postgres.md +415 -0
  11. package/skills/prisma-8/references/queries.md +168 -0
  12. package/skills/prisma-8/references/quickstart.md +326 -0
  13. package/skills/prisma-8/references/runtime.md +344 -0
  14. package/skills/prisma-8/references/supabase.md +244 -0
  15. package/skills/prisma-8/references/upgrade-app.md +101 -0
  16. package/skills/prisma-8/references/upgrade-extension.md +105 -0
  17. package/skills/prisma-8/upgrading/app/upgrades/0.10-to-0.11/instructions.md +56 -0
  18. package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/instructions.md +381 -0
  19. package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-closed-mongo-contracts.ts +202 -0
  20. package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-domain-namespaced-contracts.ts +201 -0
  21. package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-postgres-public-default.ts +198 -0
  22. package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts +340 -0
  23. package/skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/instructions.md +339 -0
  24. package/skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/re-emit-mti-variant-link-columns.ts +229 -0
  25. package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/instructions.md +543 -0
  26. package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts +290 -0
  27. package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/uuid-preset-rename.ts +43 -0
  28. package/skills/prisma-8/upgrading/app/upgrades/0.14-to-0.15/instructions.md +359 -0
  29. package/skills/prisma-8/upgrading/app/upgrades/0.15-to-0.16/instructions.md +173 -0
  30. package/skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/instructions.md +805 -0
  31. package/skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts +382 -0
  32. package/skills/prisma-8/upgrading/app/upgrades/0.17-to-8.0.0-rc.1/instructions.md +72 -0
  33. package/skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md +8 -0
  34. package/skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/instructions.md +36 -0
  35. package/skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/strip-inline-contracts.ts +226 -0
  36. package/skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/instructions.md +86 -0
  37. package/skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts +360 -0
  38. package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md +588 -0
  39. package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md +5 -0
  40. package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md +158 -0
  41. package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md +42 -0
  42. package/skills/prisma-8/upgrading/extension/upgrades/0.10-to-0.11/instructions.md +276 -0
  43. package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/instructions.md +738 -0
  44. package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/migrate-contract-testing-imports.ts +97 -0
  45. package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/regenerate-extension-public-baseline.ts +223 -0
  46. package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts +340 -0
  47. package/skills/prisma-8/upgrading/extension/upgrades/0.12-to-0.13/instructions.md +266 -0
  48. package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/instructions.md +522 -0
  49. package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts +290 -0
  50. package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/uuid-preset-rename.ts +43 -0
  51. package/skills/prisma-8/upgrading/extension/upgrades/0.14-to-0.15/instructions.md +803 -0
  52. package/skills/prisma-8/upgrading/extension/upgrades/0.15-to-0.16/instructions.md +219 -0
  53. package/skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/instructions.md +731 -0
  54. package/skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts +382 -0
  55. package/skills/prisma-8/upgrading/extension/upgrades/0.17-to-8.0.0-rc.1/instructions.md +194 -0
  56. package/skills/prisma-8/upgrading/extension/upgrades/0.7-to-0.8/instructions.md +8 -0
  57. package/skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/instructions.md +57 -0
  58. package/skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/strip-inline-contracts.ts +226 -0
  59. package/skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/instructions.md +150 -0
  60. package/skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts +360 -0
  61. package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md +746 -0
  62. package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md +5 -0
  63. package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md +137 -0
  64. package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md +129 -0
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Brings checked-in `migrations/` trees onto the 0.17 bare-hex hash
3
+ * representation: strips the legacy `sha256:` prefix from every hash literal,
4
+ * maps the empty-tree sentinel `sha256:empty` to `empty`, recomputes each
5
+ * migration's `migrationHash` over the bare-hex content, and repoints
6
+ * `refs/*.json` at the recomputed hashes.
7
+ *
8
+ * Background: starting at the 0.17 release, content hashes are bare
9
+ * lowercase hex — the `sha256:` prefix is gone from every surface (emitted
10
+ * `contract.json` / `contract.d.ts`, migration manifests, refs, CLI output,
11
+ * and the marker/ledger tables). The algorithm never varied per hash, so the
12
+ * prefix carried no information; a format change is signalled by the hash
13
+ * value changing, not by an in-band tag. Loaders now reject the legacy
14
+ * prefixed form outright.
15
+ *
16
+ * Two distinct effects on checked-in migration artifacts:
17
+ *
18
+ * - Contract hashes (`from` / `to` in `migration.json`, `storageHash` /
19
+ * `profileHash` in contract snapshots and `.d.ts` branded literals,
20
+ * `storageHash` stamps inside `ops.json`) keep their VALUE — only the
21
+ * `sha256:` prefix drops.
22
+ * - `migrationHash` VALUES change, because the hashed manifest bytes embed
23
+ * the (now bare) `from` / `to` strings. Every manifest gets a freshly
24
+ * recomputed hash, and every `refs/*.json` that pointed at an old
25
+ * migration hash is rewritten to the recomputed one.
26
+ *
27
+ * Before 0.17 a manifest was:
28
+ *
29
+ * {
30
+ * "from": "sha256:8ee1e7ce…",
31
+ * "to": "sha256:059f3f35…",
32
+ * "providedInvariants": [],
33
+ * "createdAt": "2026-…",
34
+ * "migrationHash": "sha256:3c5205d2…"
35
+ * }
36
+ *
37
+ * Starting at 0.17 the same manifest is:
38
+ *
39
+ * {
40
+ * "from": "8ee1e7ce…",
41
+ * "to": "059f3f35…",
42
+ * "providedInvariants": [],
43
+ * "createdAt": "2026-…",
44
+ * "migrationHash": "2be2085f…" // recomputed over the bare-hex bytes
45
+ * }
46
+ *
47
+ * Format-preserving edit: hash literals are rewritten in place via a targeted
48
+ * pattern (`"sha256:<64 hex>"` / `"sha256:empty"`, single- or double-quoted),
49
+ * and the `migrationHash` value is swapped in place. Every other byte (key
50
+ * order, indentation, inline-vs-expanded arrays) is left exactly as the
51
+ * authoring tool wrote it, so diffs stay minimal.
52
+ *
53
+ * Confinement: an on-disk migration package is a `migration.json` paired
54
+ * with a sibling `ops.json`. The walk keys off that pair; within a package
55
+ * directory every `.json` / `.ts` sibling (pre-store contract snapshots,
56
+ * `.d.ts` branded types, the executable `migration.ts`) has its hash
57
+ * literals stripped. Content-addressed store entries
58
+ * (`migrations/snapshots/<hex>/contract.json` + `contract.d.ts`) are also
59
+ * covered, so the codemod handles both the pre-store sibling layout and the
60
+ * store layout — run it BEFORE `scripts/migrate-migrations-layout.mjs` when
61
+ * converting the layout in the same upgrade (the 0.17 migrator accepts only
62
+ * bare-hex trees). Store directory names are the hash's hex, which is
63
+ * unchanged by the prefix drop, so no directory is renamed. Ref files are
64
+ * `refs/*.json` under a directory named `migrations`; each gets old-hash →
65
+ * recomputed-hash repointing plus prefix stripping (which also maps the
66
+ * `sha256:empty` sentinel to `empty`).
67
+ *
68
+ * The hash algorithm is replicated inline (canonicalisation rules from
69
+ * `@internal/framework-components` `canonicalizeJson` + the
70
+ * migration-tools `computeMigrationHash`, which returns bare hex from 0.17)
71
+ * so this script stays self-contained — consumers run it via `pnpm exec tsx`
72
+ * from their project root with no dependency on any `@internal/*` package
73
+ * being resolvable from that root.
74
+ *
75
+ * The codemod is idempotent: an already-bare tree carries no `sha256:`
76
+ * literals and its recomputed hashes match the stored ones, so the edit is a
77
+ * no-op and every file is left untouched.
78
+ *
79
+ * Flags:
80
+ * --check dry-run; lists files that still need fixing and exits 1 if
81
+ * any remain.
82
+ */
83
+ import { createHash } from 'node:crypto';
84
+ import { readdir, readFile, writeFile } from 'node:fs/promises';
85
+ import { basename, dirname, join, sep } from 'node:path';
86
+
87
+ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build']);
88
+
89
+ const dryRun = process.argv.includes('--check');
90
+ const projectRoot = process.cwd();
91
+
92
+ // --- Inline canonicalisation + hash --------------------------------------
93
+ // Replicated from `@internal/framework-components` `canonicalizeJson`
94
+ // (sortKeys + JSON.stringify) and the migration-tools `computeMigrationHash`.
95
+ // Kept inline so the script has no `@internal/*` import — pnpm's strict
96
+ // node_modules layout won't resolve transitive framework deps from a
97
+ // consumer's project root.
98
+
99
+ function sortKeys(value: unknown): unknown {
100
+ if (value === null || typeof value !== 'object') {
101
+ return value;
102
+ }
103
+ if (Array.isArray(value)) {
104
+ return value.map(sortKeys);
105
+ }
106
+ const sorted: Record<string, unknown> = Object.create(null);
107
+ for (const [key, entry] of Object.entries(value).sort(([a], [b]) =>
108
+ a < b ? -1 : a > b ? 1 : 0,
109
+ )) {
110
+ sorted[key] = sortKeys(entry);
111
+ }
112
+ return sorted;
113
+ }
114
+
115
+ function canonicalizeJson(value: unknown): string {
116
+ return JSON.stringify(sortKeys(value));
117
+ }
118
+
119
+ function sha256Hex(input: string): string {
120
+ return createHash('sha256').update(input).digest('hex');
121
+ }
122
+
123
+ /**
124
+ * Content-addressed migration hash over (metadata envelope, ops), 0.17 form:
125
+ * bare hex, no prefix. The `migrationHash` field is stripped before hashing
126
+ * so the same function works at write time (no hash yet) and at recompute
127
+ * time (rehashing an already-attested record over the bare-hex envelope).
128
+ */
129
+ function computeMigrationHash(metadata: Record<string, unknown>, ops: unknown): string {
130
+ const { migrationHash: _migrationHash, ...strippedMeta } = metadata;
131
+
132
+ const partHashes = [canonicalizeJson(strippedMeta), canonicalizeJson(ops)].map(sha256Hex);
133
+ return sha256Hex(canonicalizeJson(partHashes));
134
+ }
135
+
136
+ // --- Prefix stripping ------------------------------------------------------
137
+
138
+ // A legacy hash literal: `sha256:` + 64 lowercase hex chars, or the
139
+ // empty-tree sentinel `sha256:empty`, in single or double quotes. Quoting is
140
+ // required so prose that merely mentions the prefix is never rewritten.
141
+ const LEGACY_HASH_LITERAL = /(["'])sha256:([0-9a-f]{64}|empty)\1/g;
142
+
143
+ function stripHashPrefixes(text: string): string {
144
+ return text.replace(LEGACY_HASH_LITERAL, (_full, quote: string, hash: string) => {
145
+ return `${quote}${hash}${quote}`;
146
+ });
147
+ }
148
+
149
+ function replaceMigrationHash(text: string, oldHash: string, newHash: string): string {
150
+ if (oldHash === newHash) return text;
151
+ const re = new RegExp(`("migrationHash"[ \\t]*:[ \\t]*)"${oldHash}"`);
152
+ if (re.exec(text) === null) {
153
+ throw new Error('could not locate the migrationHash value to replace');
154
+ }
155
+ return text.replace(re, (_full, prefix: string) => `${prefix}"${newHash}"`);
156
+ }
157
+
158
+ // --- Filesystem walk ------------------------------------------------------
159
+
160
+ interface WalkResult {
161
+ readonly manifests: string[];
162
+ readonly refFiles: string[];
163
+ readonly snapshotFiles: string[];
164
+ }
165
+
166
+ async function findMigrationArtifacts(root: string): Promise<WalkResult> {
167
+ const manifests: string[] = [];
168
+ const refFiles: string[] = [];
169
+ const snapshotFiles: string[] = [];
170
+
171
+ async function walk(dir: string): Promise<void> {
172
+ let entries: Awaited<ReturnType<typeof readdir>>;
173
+ try {
174
+ entries = await readdir(dir, { withFileTypes: true });
175
+ } catch {
176
+ // Unreadable directory — skip silently. The consumer's project root may
177
+ // legitimately contain restricted directories.
178
+ return;
179
+ }
180
+ for (const entry of entries) {
181
+ const path = join(dir, entry.name);
182
+ if (entry.isDirectory()) {
183
+ if (SKIP_DIRS.has(entry.name)) continue;
184
+ await walk(path);
185
+ } else if (entry.isFile() && entry.name === 'migration.json') {
186
+ manifests.push(path);
187
+ } else if (
188
+ entry.isFile() &&
189
+ entry.name.endsWith('.json') &&
190
+ basename(dir) === 'refs' &&
191
+ dirname(dir).split(sep).includes('migrations')
192
+ ) {
193
+ refFiles.push(path);
194
+ } else if (
195
+ entry.isFile() &&
196
+ (entry.name.endsWith('.json') || entry.name.endsWith('.ts')) &&
197
+ basename(dirname(dir)) === 'snapshots' &&
198
+ dirname(dirname(dir)).split(sep).includes('migrations')
199
+ ) {
200
+ snapshotFiles.push(path);
201
+ }
202
+ }
203
+ }
204
+
205
+ await walk(root);
206
+ return {
207
+ manifests: manifests.sort(),
208
+ refFiles: refFiles.sort(),
209
+ snapshotFiles: snapshotFiles.sort(),
210
+ };
211
+ }
212
+
213
+ // --- Per-file transforms ---------------------------------------------------
214
+
215
+ /** Narrows an arbitrary JSON-parsed value to a plain object (manifest shape). */
216
+ function isJsonObject(value: unknown): value is Record<string, unknown> {
217
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
218
+ }
219
+
220
+ type Status = 'already-clean' | 'needs-fix' | 'fixed' | 'skipped-no-ops';
221
+
222
+ interface Result {
223
+ readonly path: string;
224
+ readonly status: Status;
225
+ }
226
+
227
+ const results: Result[] = [];
228
+ /** Old migration hash (as previously stored, prefixed) → recomputed bare hash. */
229
+ const migrationHashMap = new Map<string, string>();
230
+
231
+ async function emit(path: string, before: string, after: string): Promise<Result> {
232
+ if (after === before) {
233
+ return { path, status: 'already-clean' };
234
+ }
235
+ if (!dryRun) await writeFile(path, after, 'utf-8');
236
+ return { path, status: dryRun ? 'needs-fix' : 'fixed' };
237
+ }
238
+
239
+ async function processSibling(path: string): Promise<Result> {
240
+ const raw = await readFile(path, 'utf-8');
241
+ return emit(path, raw, stripHashPrefixes(raw));
242
+ }
243
+
244
+ async function processPackage(manifestPath: string): Promise<Result[]> {
245
+ const raw = await readFile(manifestPath, 'utf-8');
246
+
247
+ let parsed: unknown;
248
+ try {
249
+ parsed = JSON.parse(raw);
250
+ } catch (error) {
251
+ throw new Error(
252
+ `${manifestPath}: not valid JSON (${error instanceof Error ? error.message : String(error)})`,
253
+ );
254
+ }
255
+ if (!isJsonObject(parsed)) {
256
+ return [{ path: manifestPath, status: 'already-clean' }]; // not a manifest object
257
+ }
258
+
259
+ const packageDir = dirname(manifestPath);
260
+
261
+ // A complete on-disk migration package pairs `migration.json` with a sibling
262
+ // `ops.json` (the operations the hash is computed over); without it we cannot
263
+ // recompute the hash, so this is not a package we should touch.
264
+ const opsPath = join(packageDir, 'ops.json');
265
+ let opsRaw: string;
266
+ try {
267
+ opsRaw = await readFile(opsPath, 'utf-8');
268
+ } catch {
269
+ return [{ path: manifestPath, status: 'skipped-no-ops' }];
270
+ }
271
+
272
+ const out: Result[] = [];
273
+
274
+ // Ops: strip prefixes (e.g. `meta.storageHash` stamps inside operation
275
+ // payloads), then parse the stripped text — the recomputed hash covers the
276
+ // bare-hex operations exactly as they will sit on disk.
277
+ const strippedOpsRaw = stripHashPrefixes(opsRaw);
278
+ const ops: unknown = JSON.parse(strippedOpsRaw);
279
+ out.push(await emit(opsPath, opsRaw, strippedOpsRaw));
280
+
281
+ // Manifest: strip prefixes from `from` / `to` (and the sentinel), then
282
+ // recompute `migrationHash` over the bare-hex envelope + bare-hex ops.
283
+ // Canonicalisation is order/whitespace independent, so parsing the stripped
284
+ // text is the right input regardless of on-disk formatting.
285
+ const strippedManifestRaw = stripHashPrefixes(raw);
286
+ const strippedMeta = JSON.parse(strippedManifestRaw);
287
+ if (!isJsonObject(strippedMeta)) {
288
+ throw new Error(`${manifestPath}: manifest is not a JSON object`);
289
+ }
290
+ const newHash = computeMigrationHash(strippedMeta, ops);
291
+
292
+ const oldStoredHash = parsed['migrationHash'];
293
+ if (typeof oldStoredHash !== 'string') {
294
+ throw new Error(`${manifestPath}: manifest is missing a string \`migrationHash\` field`);
295
+ }
296
+ const strippedOldHash = stripHashPrefixes(`"${oldStoredHash}"`).slice(1, -1);
297
+ migrationHashMap.set(oldStoredHash, newHash);
298
+ migrationHashMap.set(strippedOldHash, newHash);
299
+
300
+ out.push(
301
+ await emit(
302
+ manifestPath,
303
+ raw,
304
+ replaceMigrationHash(strippedManifestRaw, strippedOldHash, newHash),
305
+ ),
306
+ );
307
+
308
+ // Siblings: contract snapshots (`*-contract.json`), branded-literal type
309
+ // files (`*.d.ts`), and the executable `migration.ts` all carry contract
310
+ // hash literals whose value is stable — only the prefix drops.
311
+ let entries: Awaited<ReturnType<typeof readdir>>;
312
+ try {
313
+ entries = await readdir(packageDir, { withFileTypes: true });
314
+ } catch {
315
+ return out;
316
+ }
317
+ for (const entry of entries) {
318
+ if (!entry.isFile()) continue;
319
+ if (entry.name === 'migration.json' || entry.name === 'ops.json') continue;
320
+ if (!entry.name.endsWith('.json') && !entry.name.endsWith('.ts')) continue;
321
+ out.push(await processSibling(join(packageDir, entry.name)));
322
+ }
323
+ return out;
324
+ }
325
+
326
+ async function processRefFile(path: string): Promise<Result> {
327
+ const raw = await readFile(path, 'utf-8');
328
+
329
+ // Repoint at recomputed migration hashes first (a ref stores the migration
330
+ // hash of the package it names), then strip any remaining prefixes — which
331
+ // covers contract-hash refs and maps `sha256:empty` → `empty`.
332
+ let text = raw;
333
+ for (const [oldHash, newHash] of migrationHashMap) {
334
+ if (oldHash === newHash) continue;
335
+ text = text.replaceAll(`"${oldHash}"`, `"${newHash}"`);
336
+ }
337
+ text = stripHashPrefixes(text);
338
+
339
+ return emit(path, raw, text);
340
+ }
341
+
342
+ // --- Driver ---------------------------------------------------------------
343
+
344
+ const { manifests, refFiles, snapshotFiles } = await findMigrationArtifacts(projectRoot);
345
+ if (manifests.length === 0 && refFiles.length === 0 && snapshotFiles.length === 0) {
346
+ console.error(`No migration artifacts found under ${projectRoot}.`);
347
+ process.exit(1);
348
+ }
349
+
350
+ for (const manifestPath of manifests) {
351
+ results.push(...(await processPackage(manifestPath)));
352
+ }
353
+ for (const snapshotPath of snapshotFiles) {
354
+ results.push(await processSibling(snapshotPath));
355
+ }
356
+ for (const refPath of refFiles) {
357
+ results.push(await processRefFile(refPath));
358
+ }
359
+
360
+ let changed = 0;
361
+ let alreadyClean = 0;
362
+ let skipped = 0;
363
+ for (const result of results) {
364
+ const rel = result.path.slice(projectRoot.length + 1);
365
+ if (result.status === 'already-clean') {
366
+ alreadyClean += 1;
367
+ } else if (result.status === 'skipped-no-ops') {
368
+ skipped += 1;
369
+ console.log(`SKIP ${rel} (no sibling ops.json — not a migration package)`);
370
+ } else {
371
+ changed += 1;
372
+ const verb = dryRun ? 'WOULD FIX' : 'FIXED';
373
+ console.log(`${verb} ${rel}`);
374
+ }
375
+ }
376
+
377
+ console.log();
378
+ console.log(
379
+ `${results.length} file(s) scanned: ${changed} ${dryRun ? 'needing fix' : 'fixed'}, ${alreadyClean} already clean${skipped > 0 ? `, ${skipped} skipped (no ops.json)` : ''}.`,
380
+ );
381
+
382
+ if (dryRun && changed > 0) process.exit(1);
@@ -0,0 +1,72 @@
1
+ ---
2
+ from: "0.17"
3
+ to: "8.0.0-rc.1"
4
+ changes:
5
+ - id: aggregate-results-carry-their-target-s-codec
6
+ summary: |
7
+ Aggregates read back through the codec their target declares for the result, so their
8
+ application types change. `count()` is a `bigint` on both targets — a count is a cardinality
9
+ and is not capped at 2^53. On PostgreSQL, `sum` over `int2`/`int4` widens to `int8` and reads
10
+ as a `bigint`, while `sum(int8)` and `avg` over any integer are `numeric` and read as decimal
11
+ **strings**; `min`/`max` keep the column's own type, except over `varchar`, where PostgreSQL
12
+ returns `text`. On SQLite, `sum` over an integer column is a `bigint` and `avg` is always a
13
+ `number` (real). The two targets genuinely diverge on `avg` — a portable query must handle
14
+ both, and there is no shim that flattens them. Include reducers
15
+ (`.include('posts', (p) => p.count())`) decode the same way, so an included count is a
16
+ `bigint` too, and an empty relation reads `0n` rather than `0`. Comparisons are where this
17
+ bites quietly: `count === 2` is false where `count` is `2n`, and `JSON.stringify` throws on a
18
+ bigint. Sweep for `toBe(<number>)` / `=== <number>` / arithmetic against an aggregate result
19
+ and switch to `2n` or the decimal string, and render bigints explicitly (`String(value)`)
20
+ wherever you serialise. `having(...)` operands are the exception and stay numbers — they are
21
+ compared inside SQL and never cross a codec. Regenerate your contracts
22
+ (`prisma-next contract emit`): the emitted `contract.d.ts` gains an `AggregateTypes` block
23
+ that types every aggregate per operation and input codec, and the ORM and SQL builder both
24
+ resolve their result types from it — against a contract emitted before 8.0.0-rc.1 an aggregate
25
+ resolves to `never` in the ORM and to `unknown` in the SQL builder.
26
+ detection:
27
+ glob: "**/*.{ts,tsx,mts,cts}"
28
+ contains:
29
+ - "aggregate("
30
+ - ".count()"
31
+ - "groupBy("
32
+ anyMatch: true
33
+ ---
34
+
35
+ # 0.17 → 8.0.0-rc.1 — User upgrade instructions
36
+
37
+ ## `aggregate-results-carry-their-target-s-codec`
38
+
39
+ An aggregate's result is a value the database computes, and 8.0.0-rc.1 reads it back through the codec its target declares for that result rather than through whatever the driver happened to hand over. What each aggregate returns is now the target's answer, stated in the contract and honoured by the runtime:
40
+
41
+ | Target | Aggregate | Reads as |
42
+ | --- | --- | --- |
43
+ | PostgreSQL | `count()` (with or without an argument) | `bigint` |
44
+ | PostgreSQL | `sum` over `int2` / `int4` | `bigint` (the sum widens to `int8`) |
45
+ | PostgreSQL | `sum` over `int8`, `avg` over any integer | decimal `string` (the result is `numeric`) |
46
+ | PostgreSQL | `sum` / `avg` over `float4` / `float8` | `number` |
47
+ | PostgreSQL | `min` / `max` | the column's own type — except over `varchar`, which returns `text` |
48
+ | SQLite | `count()` | `bigint` |
49
+ | SQLite | `sum` over an integer column | `bigint` |
50
+ | SQLite | `avg` over anything | `number` (SQLite's `avg` is always real) |
51
+ | SQLite | `min` / `max` | the column's own type |
52
+
53
+ The targets diverge on `avg`, and deliberately: PostgreSQL computes it as `numeric`, SQLite as a float. A query written against both handles both.
54
+
55
+ Include reducers follow the same rules — `.include('posts', (posts) => posts.count())` yields a `bigint`, and a parent with no related rows reads `0n` where it read `0`.
56
+
57
+ Two failure modes are worth sweeping for, because neither announces itself:
58
+
59
+ - **Equality against a number.** `row.count === 2` is `false` when `row.count` is `2n`, and `expect(count).toBe(2)` fails the same way. Change the literal (`2n`), or compare through `Number(...)` where the magnitude is known to be small.
60
+ - **Serialisation.** `JSON.stringify` throws `TypeError: Do not know how to serialize a BigInt`. Render explicitly — `String(count)`, or a replacer that maps bigints to strings.
61
+
62
+ Arithmetic mixing a bigint with a number also throws (`2n + 1` is a `TypeError`); convert one side deliberately.
63
+
64
+ `having(...)` is the one place that does not move. A HAVING operand is compared inside SQL against the aggregate the database is computing, so it never crosses a codec: `having.count().gte(2)` keeps the plain number it always took. Only the aggregate's *result* — the value that reaches your code — carries its target's type.
65
+
66
+ Finally, regenerate your contracts:
67
+
68
+ ```bash
69
+ prisma-next contract emit
70
+ ```
71
+
72
+ The emitted `contract.d.ts` gains an `AggregateTypes` block — the settled result identity per operation and per input codec — and both the ORM client and the SQL builder resolve their aggregate result types from it. Against a contract emitted before 8.0.0-rc.1 the block is absent, so an aggregate resolves to `never` in the ORM and to `unknown` in the SQL builder: a type error at the call site in the first case, an untyped value in the second, rather than a wrong runtime value in either.
@@ -0,0 +1,8 @@
1
+ ---
2
+ from: "0.7"
3
+ to: "0.8"
4
+ changes: []
5
+ ---
6
+
7
+ No user-side migration actions are required for this transition at this time.
8
+ `changes: []` intentionally marks this transition as a no-op.
@@ -0,0 +1,36 @@
1
+ ---
2
+ from: "0.8"
3
+ to: "0.9"
4
+ changes:
5
+ - id: strip-inline-contracts-from-migration-manifests
6
+ summary: Remove the inlined `fromContract` / `toContract` fields from every committed `migration.json`; the destination contract continues to live next door as `end-contract.json`.
7
+ detection:
8
+ glob: "**/migrations/**/migration.json"
9
+ contains:
10
+ - '"fromContract"'
11
+ - '"toContract"'
12
+ anyMatch: true
13
+ script: ./strip-inline-contracts.ts
14
+ ---
15
+
16
+ # 0.8 → 0.9 — User upgrade instructions
17
+
18
+ ## `strip-inline-contracts-from-migration-manifests`
19
+
20
+ Starting at the 0.9 release, `migration.json` no longer carries `fromContract` or `toContract`. The schema rejects those keys as unknown, so any committed manifest that still inlines them fails to load with a `MIGRATION.INVALID_MANIFEST` error from the loader (which is what powers `prisma-next migration plan` / `apply` / `verify`).
21
+
22
+ The destination contract was already being written to disk next door as `end-contract.json` (and the source as `start-contract.json`); the in-manifest copy was redundant. `migrationHash` is unaffected — it has always been computed without those two fields, so stripping them does not change the stored hash and existing `from` / `to` storage-hash bookends remain valid.
23
+
24
+ ### What `strip-inline-contracts.ts` does
25
+
26
+ The colocated script walks the project root, descends into every directory named `migrations/` (skipping `node_modules`, `.git`, `dist`, `build`), and rewrites every `migration.json` whose JSON object contains either key:
27
+
28
+ - Manifests that already lack both keys are left untouched (idempotent — safe to re-run).
29
+ - Manifests with either key are rewritten with the two key/value spans excised at the text level. The formatting of every surviving field (whitespace, inline-vs-multiline arrays, key ordering, trailing newline) is preserved byte-for-byte; only the removed keys (and their trailing comma+newline) disappear from the diff. The script reparses the result to guard against accidental corruption.
30
+ - A `--check` flag turns the script into a dry-run; it lists which manifests would be modified and exits non-zero if any still need fixing.
31
+
32
+ ### Validation
33
+
34
+ After running the script, run `pnpm typecheck && pnpm test` per the per-step flow. The migration loader's schema check is the structural validation; the project's own test suite covers any consumer-side code that previously read `metadata.fromContract` / `metadata.toContract` (rare — the fields were unused in the apply / verify path).
35
+
36
+ If you have application code that inspected `metadata.toContract` for any reason, read the contract from the sibling `end-contract.json` file instead (and `metadata.fromContract` becomes the predecessor migration's `end-contract.json`).