@prisma/orm-mongo 8.0.0-rc.5 → 8.0.0-rc.5-dev.2

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,194 @@
1
+ ---
2
+ from: "0.17"
3
+ to: "8.0.0-rc.1"
4
+ changes:
5
+ - id: codec-conformance-harness-moves-to-testkit-packages
6
+ summary: |
7
+ The database-backed codec conformance harness moves into two dedicated dev-only packages,
8
+ `@internal/postgres-codec-testkit` and `@internal/sqlite-codec-testkit`. Like every
9
+ `@internal/*` package they are workspace-private — a published home under the `@prisma/*`
10
+ scope is a follow-up — so today's consumers are packs developed in this repository. A pack
11
+ that reached the in-repo harness through a relative cross-package import
12
+ (`../../../3-targets/6-adapters/postgres/test/codec-conformance/harness`) adds the matching
13
+ testkit as a workspace devDependency and imports `runPostgresCodecProjection` /
14
+ `runSqliteCodecProjection` from the package instead; a `tsconfig` whose `rootDir` was widened
15
+ to reach across that boundary can be narrowed back to the pack. The harness API is unchanged —
16
+ caller-supplied connection, one case per codec and value — so the cases themselves move
17
+ verbatim. Production adapters take no dependency on either testkit.
18
+ detection:
19
+ glob: "**/*.{ts,tsx,json}"
20
+ contains:
21
+ - "codec-conformance"
22
+ - "runPostgresCodecProjection"
23
+ - "runSqliteCodecProjection"
24
+ anyMatch: true
25
+ - id: aggregate-result-codecs-are-target-declared
26
+ summary: |
27
+ Targets declare what each aggregate returns, through `SqlAggregateDescriptor` contributions on
28
+ `types.aggregateDescriptors`. A descriptor maps an operation and an input match —
29
+ no input, an exact codec id, a codec trait, or input-agnostic — to a result codec and a
30
+ declared nullability, and resolution consults exact matches, then traits, then the
31
+ input-agnostic entry. Emission and the runtime read the same contributions, so an extension
32
+ that contributes an aggregate cannot type one result and decode another. Two consequences for
33
+ existing packs. First, aggregate results now carry their declared codec into decoding, so a
34
+ pack asserting an aggregate reads back as a `number` updates that expectation — on PostgreSQL
35
+ `count` and widened sums are bigints and integer averages are decimal strings; on SQLite `avg`
36
+ is a number and integer sums are bigints. Second, a pack that asserts rendered SQL for SQLite
37
+ aggregates sees `CAST(… AS text)` around any aggregate whose result is `sqlite/bigint@1`:
38
+ SQLite computes those into an INTEGER that `node:sqlite` refuses to hand over past 2^53, so
39
+ the target's descriptor renders the cast that keeps the value readable. Regenerate contracts —
40
+ the emitted `contract.d.ts` gains an `AggregateTypes` block that the ORM and SQL builder
41
+ resolve their result types from.
42
+ detection:
43
+ glob: "**/*.{ts,tsx}"
44
+ contains:
45
+ - "aggregateDescriptors"
46
+ - "AggregateExpr"
47
+ - "codecTypes"
48
+ anyMatch: true
49
+ - id: rendered-ts-literals-are-double-quoted
50
+ summary: |
51
+ `renderTsLiteral` (`@internal/framework-components/codec`) now returns a double-quoted
52
+ TypeScript literal — `"low"` where it used to return `'low'`. It delegates to a single
53
+ shared renderer (`JSON.stringify` plus an explicit U+2028/U+2029 escape), which also
54
+ closes escaping gaps the old implementation had: `\t`, `\v`, `\b`, `\f` and the remaining
55
+ C0 control characters were previously emitted raw. Calling code needs no change, and the
56
+ emitted `contract.d.ts` is byte-identical either way because `contract emit` formats with
57
+ prettier at `singleQuote: true`. What does change is any assertion your pack makes on the
58
+ *unformatted* return value — a codec unit test pinning `renderValueLiteral` output, or a
59
+ test that calls `generateContractDts` directly and greps the result. Update those
60
+ expectations to the double-quoted form. If your pack hand-rolls a `renderValueLiteral`
61
+ that builds its own quoted literal, it keeps working, but switch it to `renderTsLiteral`
62
+ so your pack's escaping matches the framework's.
63
+ detection:
64
+ glob: "**/*.{test,test-d}.ts"
65
+ contains:
66
+ - 'renderValueLiteral'
67
+ - 'generateContractDts'
68
+ anyMatch: true
69
+ - id: driver-spi-splits-query-and-execute
70
+ summary: |
71
+ The relational driver contract (`SqlQueryable` in `@internal/sql-relational-core`) splits
72
+ into one streaming path and one statistics path: `query<Row>(request)` returns
73
+ `AsyncIterable<Row>`, and `execute(request)` runs a statement without row output and
74
+ resolves to `SqlStatementStats` (`{ affectedRows }`). The prepared-specific
75
+ `executePrepared` driver method and the buffered `query(sql, params)` convenience (with its
76
+ `SqlQueryResult` type) are gone — preparedness travels as the optional
77
+ `preparedStatementHandle` on `SqlExecuteRequest`, and `query()` serves ad-hoc and prepared
78
+ requests alike. A pack that implements a driver, wraps one, or ships a driver fake for
79
+ tests implements the two-method surface, reports its engine's native affected-row count
80
+ unnormalized, and surfaces a failed prepared-statement retry as the structural
81
+ `DRIVER.PREPARE_FAILED` error envelope.
82
+ detection:
83
+ glob: "**/*.{ts,tsx}"
84
+ contains:
85
+ - "SqlQueryable"
86
+ - "PreparedExecuteRequest"
87
+ - "SqlQueryResult"
88
+ anyMatch: true
89
+ ---
90
+
91
+ # 0.17 → 8.0.0-rc.1 — Extension-author upgrade instructions
92
+
93
+ ## `codec-conformance-harness-moves-to-testkit-packages`
94
+
95
+ The conformance harness that measures a codec's JSON projection against a real database lives in dedicated packages, `@internal/postgres-codec-testkit` and `@internal/sqlite-codec-testkit`. They are workspace-private like every `@internal/*` package (a published `@prisma/*` home is a follow-up), so packs developed in this repository that reached the harness by relative path across the adapter's `test/` directory declare the workspace dependency instead:
96
+
97
+ ```jsonc
98
+ // package.json
99
+ "devDependencies": {
100
+ "@internal/postgres-codec-testkit": "workspace:*"
101
+ }
102
+ ```
103
+
104
+ ```ts
105
+ import { runPostgresCodecProjection } from '@internal/postgres-codec-testkit';
106
+ ```
107
+
108
+ The harness API is the same one the in-repo version had — you supply the connection and the cases; it encodes, stores, projects through your descriptor, executes, and compares the database's JSON against `encodeJson` and back through `decodeJson`. Cases move verbatim. If your `tsconfig.json` widened `rootDir` to make the cross-package relative import resolve, narrow it back to the pack.
109
+
110
+ ## `aggregate-result-codecs-are-target-declared`
111
+
112
+ Aggregate result typing is now declared rather than inferred. A target (or an extension) contributes `SqlAggregateDescriptor`s on `types.aggregateDescriptors`, a sibling of `codecTypes`:
113
+
114
+ ```ts
115
+ import type { SqlAggregateDescriptor } from '@internal/sql-relational-core/aggregate-descriptor-registry';
116
+
117
+ const sumOverMoney: SqlAggregateDescriptor = {
118
+ operation: 'sum',
119
+ input: { kind: 'codec', codecId: 'ext/money@1' },
120
+ output: { kind: 'codec', codecId: 'ext/money@1' },
121
+ nullable: true,
122
+ };
123
+ ```
124
+
125
+ Each descriptor claims one `(operation, input)` pair, and exactly one component may claim it. The input match is one of four kinds — `none` (an operation over rows), `codec` (an exact codec id), `trait` (any codec carrying that trait), or `any` (a result that does not depend on its input) — and resolution consults them in that order of specificity: exact, then trait, then input-agnostic. The result is either `self` (the matched input's codec, for an aggregate that returns one of its inputs) or a named codec id. Emission reads the same contributions the runtime resolves against, so an emitted result type and a decoded value cannot disagree.
126
+
127
+ Two things to check in an existing pack:
128
+
129
+ - **Expectations that an aggregate reads back as a `number`.** On PostgreSQL, `count` and sums that widen to `int8` are bigints, and integer averages are decimal strings; on SQLite, integer sums are bigints while `avg` is a number.
130
+ - **Assertions over rendered SQL for SQLite aggregates.** An aggregate whose declared result is `sqlite/bigint@1` renders as `CAST(… AS text)`. SQLite computes such an aggregate into an INTEGER, and `node:sqlite` raises rather than returning one past 2^53; the descriptor's lowering hook renders the cast that keeps the value readable, and the bigint codec reads the text back. If your pack contributes an aggregate whose result outruns a double, declare the same lowering.
131
+
132
+ Then regenerate contracts (`prisma-next contract emit`): `contract.d.ts` gains an `AggregateTypes` block, and both the ORM client and the SQL builder resolve aggregate result types from it.
133
+
134
+ ## `rendered-ts-literals-are-double-quoted`
135
+
136
+ The framework had three separate implementations of "escape this string for a TypeScript
137
+ literal", each with a different idea of what needed escaping. They are now one function, and the
138
+ one that survived is the `JSON.stringify`-based renderer.
139
+
140
+ For an extension pack, the practical surface is `renderTsLiteral`, which most custom codecs use
141
+ to implement `renderValueLiteral`:
142
+
143
+ ```ts
144
+ // Before — renderTsLiteral returned a single-quoted literal
145
+ expect(codec.renderValueLiteral?.('low', 'output')).toBe("'low'");
146
+
147
+ // After
148
+ expect(codec.renderValueLiteral?.('low', 'output')).toBe('"low"');
149
+ ```
150
+
151
+ Two things worth knowing when you update these:
152
+
153
+ - **The escaping inverted, it did not merely re-quote.** Under `JSON.stringify` a single quote is
154
+ no longer escaped and a double quote is. So `renderTsLiteral("it's")` is `"it's"`, not
155
+ `'it\'s'`. A mechanical quote swap over your test expectations will get the simple cases right
156
+ and the escaping cases wrong — check any assertion whose input contains a quote by hand.
157
+
158
+ - **Values are unaffected; only the rendering is.** No contract hash changes, no re-emit is
159
+ needed, and the artefacts your pack ships (`contract.json`, `contract.d.ts`, migrations) are
160
+ unchanged. If your pack's committed contract artefacts do show a diff after upgrading, that is
161
+ a different change in this transition, not this one.
162
+
163
+ ## `driver-spi-splits-query-and-execute`
164
+
165
+ The driver contract used one streaming method for every statement, a prepared-specific variant beside it, and a buffered convenience query. It is now two methods with distinct jobs:
166
+
167
+ ```ts
168
+ // Before (0.17)
169
+ export interface SqlQueryable {
170
+ execute<Row>(request: SqlExecuteRequest): AsyncIterable<Row>;
171
+ executePrepared<Row>(request: PreparedExecuteRequest): AsyncIterable<Row>;
172
+ explain?(request: SqlExecuteRequest): Promise<SqlExplainResult>;
173
+ query<Row>(sql: string, params?: readonly unknown[]): Promise<SqlQueryResult<Row>>;
174
+ }
175
+
176
+ // After (0.18)
177
+ export interface SqlQueryable {
178
+ query<Row>(request: SqlExecuteRequest): AsyncIterable<Row>;
179
+ execute(request: SqlExecuteRequest): Promise<SqlStatementStats>; // { affectedRows }
180
+ explain?(request: SqlExecuteRequest): Promise<SqlExplainResult>;
181
+ }
182
+ ```
183
+
184
+ For a pack that implements a driver, wraps one, or ships a driver fake:
185
+
186
+ - Move the streaming implementation from `execute(request)` to `query(request)`. The buffered `query(sql, params)` overload and its `SqlQueryResult` type are gone; callers stream and collect instead.
187
+ - Implement `execute(request): Promise<SqlStatementStats>` for statements executed without row output. Report your engine's native affected-row count — the in-tree PostgreSQL driver reports `rowCount`, SQLite reports `stmt.run().changes` — and do not normalize semantics across engines.
188
+ - Delete `executePrepared`. Preparedness is a property of the request: a prepared plan arrives at `query()` carrying `preparedStatementHandle` on the `SqlExecuteRequest`.
189
+ - If your engine cannot return rows from `execute()`, reject `RETURNING`-style statements up front rather than silently dropping their rows (the in-tree SQLite driver does this).
190
+ - If your driver retries stale prepared statements, surface a failed retry as the structural `DRIVER.PREPARE_FAILED` error envelope (ADR 239) with the normalized driver error as its `cause`.
191
+
192
+ <!--
193
+ TML-3171 (nested SQL ORM self-relation predicate scopes): `changes: []`. The `packages/3-extensions/sql-orm-client` diff fixes internal query planning and adds regression coverage; it changes no public API, contract shape, emitted artefact, extension-authoring surface, adapter API, or downstream source translation. Incidental substrate diff only.
194
+ -->
@@ -0,0 +1,8 @@
1
+ ---
2
+ from: "0.7"
3
+ to: "0.8"
4
+ changes: []
5
+ ---
6
+
7
+ No extension-side migration actions are required for this transition at this time.
8
+ `changes: []` intentionally marks this transition as a no-op.
@@ -0,0 +1,57 @@
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` (extension seed migrations included); 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
+ - id: drop-migration-metadata-contract-fields-from-source
15
+ summary: Remove `fromContract` / `toContract` from any extension source code that constructs or destructures `MigrationMetadata`; the SPI dropped both fields.
16
+ detection:
17
+ glob: "**/*.{ts,tsx}"
18
+ contains:
19
+ - "fromContract"
20
+ - "toContract"
21
+ anyMatch: true
22
+ ---
23
+
24
+ # 0.8 → 0.9 — Extension-author upgrade instructions
25
+
26
+ ## `strip-inline-contracts-from-migration-manifests`
27
+
28
+ 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 `MIGRATION.INVALID_MANIFEST`.
29
+
30
+ This applies to **seed migrations** shipped inside an extension package (e.g. `migrations/<edge-id>/migration.json` shipped under `packages/<extension>/migrations/`) just as it does to user-app migrations. The destination contract was already being written to disk next door as `end-contract.json` (and the source as `start-contract.json`); the 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.
31
+
32
+ ### What `strip-inline-contracts.ts` does
33
+
34
+ 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:
35
+
36
+ - Manifests that already lack both keys are left untouched (idempotent — safe to re-run).
37
+ - 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.
38
+ - 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.
39
+
40
+ ## `drop-migration-metadata-contract-fields-from-source`
41
+
42
+ The `MigrationMetadata` type exported by `@internal/migration-tools` no longer declares `fromContract` or `toContract`. Source TypeScript that constructs (or destructures) `MigrationMetadata` from those fields will now fail to compile.
43
+
44
+ There is no codemod — extension authors construct `MigrationMetadata` in too many shapes for a deterministic transform to be safe. Instead, walk every `.ts` / `.tsx` file matched by the `detection.glob` above and apply these rules locally:
45
+
46
+ - **Object-literal construction** (e.g. `const meta: MigrationMetadata = { ..., fromContract, toContract }`): drop both properties from the literal. If the value is later needed, read the contract from the sibling `end-contract.json` (or the predecessor's `end-contract.json` for the from-side) instead of carrying it in metadata.
47
+ - **Spread-into-existing** (e.g. `const meta = { ...prev, fromContract: ..., toContract: ... }`): drop the two keys from the spread. If `prev` was loaded from disk via `readMigrationPackage`, it already lacks the fields under 0.9 — no further work needed.
48
+ - **Destructuring** (e.g. `const { fromContract, toContract, ...rest } = meta`): remove both names from the destructure. If the consuming code used those values, switch the read to the sibling `end-contract.json`.
49
+ - **Type-only references** (e.g. `metadata.toContract`, `Pick<MigrationMetadata, 'fromContract'>`, etc.): TypeScript will surface these as compile errors after the bump. Replace with sibling-file reads or remove the field reference entirely.
50
+
51
+ If your extension also carries seed-migration manifests (the common case for extensions that ship a `migrations/` directory), the `strip-inline-contracts-from-migration-manifests` change above handles those at the JSON layer. Run that script first; the source-code change above only covers TypeScript that produces / consumes `MigrationMetadata` programmatically.
52
+
53
+ While at it, scan any seed `migration.ts` doc-comments in your extension for stale references to `metadata.toContract` (e.g. *"preserving the full `toContract` so `MigrationCLI.run` re-attests it"*). Those references were accurate under 0.8 and are no longer accurate under 0.9 — the `MigrationCLI.run` re-attestation now reads the destination contract from sibling `end-contract.json`, not from `metadata.toContract`. Update or remove the stale prose. This is documentation hygiene, not a structural break.
54
+
55
+ ### Validation
56
+
57
+ After running the script and applying the source-level rules above, run `pnpm typecheck && pnpm test` (or your extension's own equivalent). `prisma-8-check-pins` should also pass, since it does not look at `MigrationMetadata` shape.