@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.
- package/package.json +11 -10
- package/skills/prisma-8/SKILL.md +84 -0
- package/skills/prisma-8/references/build.md +142 -0
- package/skills/prisma-8/references/contract.md +417 -0
- package/skills/prisma-8/references/debug.md +141 -0
- package/skills/prisma-8/references/feedback.md +251 -0
- package/skills/prisma-8/references/migration-review.md +224 -0
- package/skills/prisma-8/references/migrations.md +519 -0
- package/skills/prisma-8/references/queries-mongo.md +236 -0
- package/skills/prisma-8/references/queries-postgres.md +415 -0
- package/skills/prisma-8/references/queries.md +168 -0
- package/skills/prisma-8/references/quickstart.md +326 -0
- package/skills/prisma-8/references/runtime.md +344 -0
- package/skills/prisma-8/references/supabase.md +244 -0
- package/skills/prisma-8/references/upgrade-app.md +101 -0
- package/skills/prisma-8/references/upgrade-extension.md +105 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.10-to-0.11/instructions.md +56 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/instructions.md +381 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-closed-mongo-contracts.ts +202 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-domain-namespaced-contracts.ts +201 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-postgres-public-default.ts +198 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts +340 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/instructions.md +339 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/re-emit-mti-variant-link-columns.ts +229 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/instructions.md +543 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts +290 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/uuid-preset-rename.ts +43 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.14-to-0.15/instructions.md +359 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.15-to-0.16/instructions.md +173 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/instructions.md +805 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts +382 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.17-to-8.0.0-rc.1/instructions.md +72 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md +8 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/instructions.md +36 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/strip-inline-contracts.ts +226 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/instructions.md +86 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts +360 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md +588 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md +5 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md +158 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md +42 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.10-to-0.11/instructions.md +276 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/instructions.md +738 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/migrate-contract-testing-imports.ts +97 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/regenerate-extension-public-baseline.ts +223 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts +340 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.12-to-0.13/instructions.md +266 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/instructions.md +522 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts +290 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/uuid-preset-rename.ts +43 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.14-to-0.15/instructions.md +803 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.15-to-0.16/instructions.md +219 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/instructions.md +731 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts +382 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.17-to-8.0.0-rc.1/instructions.md +194 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.7-to-0.8/instructions.md +8 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/instructions.md +57 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/strip-inline-contracts.ts +226 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/instructions.md +150 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts +360 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md +746 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md +5 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md +137 -0
- 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,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes the inlined `fromContract` / `toContract` fields from every
|
|
3
|
+
* committed `migration.json` manifest reachable from the project root.
|
|
4
|
+
*
|
|
5
|
+
* Background: starting at the 0.9 release, `migration.json` no longer
|
|
6
|
+
* carries `fromContract` / `toContract` (the schema rejects them as
|
|
7
|
+
* unknown keys). The destination contract continues to live next door
|
|
8
|
+
* as `end-contract.json` (and the source as `start-contract.json`); the
|
|
9
|
+
* manifest copy was redundant. `migrationHash` is unaffected — it was
|
|
10
|
+
* already computed without those two fields, so stripping them does
|
|
11
|
+
* not change the stored hash.
|
|
12
|
+
*
|
|
13
|
+
* Behaviour:
|
|
14
|
+
* - Walks the project root recursively, ignoring `node_modules`, `.git`,
|
|
15
|
+
* `dist`, and `build`. Picks up every file named `migration.json`
|
|
16
|
+
* whose JSON object has the migration-manifest shape (`from`, `to`,
|
|
17
|
+
* and `migrationHash` keys). Other `migration.json` files (e.g.
|
|
18
|
+
* unrelated artefacts that happen to share the name) are skipped.
|
|
19
|
+
* - Manifests that already lack both removed keys are left untouched.
|
|
20
|
+
* - Manifests with either removed key are rewritten with the two key /
|
|
21
|
+
* value spans excised at the text level, so the formatting of all
|
|
22
|
+
* surviving fields (whitespace, inline-vs-multiline arrays, key
|
|
23
|
+
* ordering, trailing newline) is preserved byte-for-byte. Only the
|
|
24
|
+
* key being removed and its trailing comma+newline disappear from
|
|
25
|
+
* the diff.
|
|
26
|
+
* - Idempotent: re-running the script after success is a no-op.
|
|
27
|
+
*
|
|
28
|
+
* Flags:
|
|
29
|
+
* --check dry-run; exit 1 if any manifest still needs fixing.
|
|
30
|
+
*/
|
|
31
|
+
import { readdir, readFile, writeFile } from 'node:fs/promises';
|
|
32
|
+
import { join } from 'node:path';
|
|
33
|
+
|
|
34
|
+
const REMOVED_KEYS = ['fromContract', 'toContract'] as const;
|
|
35
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build']);
|
|
36
|
+
|
|
37
|
+
const dryRun = process.argv.includes('--check');
|
|
38
|
+
const projectRoot = process.cwd();
|
|
39
|
+
|
|
40
|
+
interface Result {
|
|
41
|
+
readonly path: string;
|
|
42
|
+
readonly status: 'already-clean' | 'needs-fix' | 'fixed';
|
|
43
|
+
readonly removed: readonly string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function findManifests(root: string): Promise<string[]> {
|
|
47
|
+
const out: string[] = [];
|
|
48
|
+
|
|
49
|
+
async function walk(dir: string): Promise<void> {
|
|
50
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
if (entry.isDirectory()) {
|
|
53
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
54
|
+
await walk(join(dir, entry.name));
|
|
55
|
+
} else if (entry.isFile() && entry.name === 'migration.json') {
|
|
56
|
+
const path = join(dir, entry.name);
|
|
57
|
+
try {
|
|
58
|
+
const parsed: unknown = JSON.parse(await readFile(path, 'utf-8'));
|
|
59
|
+
if (looksLikeMigrationManifest(parsed)) out.push(path);
|
|
60
|
+
} catch {
|
|
61
|
+
// Not valid JSON, or not the manifest shape — skip silently.
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await walk(root);
|
|
68
|
+
return out.sort();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function looksLikeMigrationManifest(value: unknown): value is Record<string, unknown> {
|
|
72
|
+
if (typeof value !== 'object' || value === null) return false;
|
|
73
|
+
const obj = value as Record<string, unknown>;
|
|
74
|
+
return 'from' in obj && 'to' in obj && 'migrationHash' in obj;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Find the end index (exclusive) of a JSON value starting at `start`
|
|
79
|
+
* inside `text`. Handles strings, numbers, booleans, null, arrays, and
|
|
80
|
+
* objects. Brace / bracket nesting is tracked while respecting string
|
|
81
|
+
* literals (with escapes). Throws if `text` is not well-formed JSON
|
|
82
|
+
* starting at `start`.
|
|
83
|
+
*/
|
|
84
|
+
function jsonValueEnd(text: string, start: number): number {
|
|
85
|
+
const head = text[start];
|
|
86
|
+
let depth = 0;
|
|
87
|
+
let inString = false;
|
|
88
|
+
let inEscape = false;
|
|
89
|
+
|
|
90
|
+
if (head === '"') {
|
|
91
|
+
let i = start + 1;
|
|
92
|
+
while (i < text.length) {
|
|
93
|
+
const ch = text[i];
|
|
94
|
+
if (inEscape) inEscape = false;
|
|
95
|
+
else if (ch === '\\') inEscape = true;
|
|
96
|
+
else if (ch === '"') return i + 1;
|
|
97
|
+
i += 1;
|
|
98
|
+
}
|
|
99
|
+
throw new Error(`Unterminated string starting at ${start}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (head === '{' || head === '[') {
|
|
103
|
+
let i = start;
|
|
104
|
+
while (i < text.length) {
|
|
105
|
+
const ch = text[i];
|
|
106
|
+
if (inString) {
|
|
107
|
+
if (inEscape) inEscape = false;
|
|
108
|
+
else if (ch === '\\') inEscape = true;
|
|
109
|
+
else if (ch === '"') inString = false;
|
|
110
|
+
} else {
|
|
111
|
+
if (ch === '"') inString = true;
|
|
112
|
+
else if (ch === '{' || ch === '[') depth += 1;
|
|
113
|
+
else if (ch === '}' || ch === ']') {
|
|
114
|
+
depth -= 1;
|
|
115
|
+
if (depth === 0) return i + 1;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
i += 1;
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`Unterminated container starting at ${start}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let i = start;
|
|
124
|
+
while (i < text.length && !',}\n\r\t '.includes(text[i] ?? '')) i += 1;
|
|
125
|
+
return i;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Remove a top-level key (and its `: value` and trailing comma) from a
|
|
130
|
+
* pretty-printed JSON object text. Returns the new text. If the key
|
|
131
|
+
* isn't present, returns the input unchanged.
|
|
132
|
+
*
|
|
133
|
+
* Preserves all surrounding whitespace and the formatting of every
|
|
134
|
+
* other field byte-for-byte. Handles both "key in the middle" (eats the
|
|
135
|
+
* trailing comma + newline) and "key at the end" (eats the leading
|
|
136
|
+
* comma + newline).
|
|
137
|
+
*/
|
|
138
|
+
function removeTopLevelKey(text: string, key: string): string {
|
|
139
|
+
const needle = `"${key}"`;
|
|
140
|
+
const keyIndex = text.indexOf(needle);
|
|
141
|
+
if (keyIndex < 0) return text;
|
|
142
|
+
|
|
143
|
+
let cursor = keyIndex + needle.length;
|
|
144
|
+
while (cursor < text.length && /\s/.test(text[cursor] ?? '')) cursor += 1;
|
|
145
|
+
if (text[cursor] !== ':') {
|
|
146
|
+
throw new Error(`Expected ':' after ${needle} at ${cursor}`);
|
|
147
|
+
}
|
|
148
|
+
cursor += 1;
|
|
149
|
+
while (cursor < text.length && /\s/.test(text[cursor] ?? '')) cursor += 1;
|
|
150
|
+
|
|
151
|
+
const valueEnd = jsonValueEnd(text, cursor);
|
|
152
|
+
|
|
153
|
+
let removeStart = keyIndex;
|
|
154
|
+
let removeEnd = valueEnd;
|
|
155
|
+
|
|
156
|
+
if (text[removeEnd] === ',') {
|
|
157
|
+
removeEnd += 1;
|
|
158
|
+
if (text[removeEnd] === '\n') removeEnd += 1;
|
|
159
|
+
let lineStart = removeStart;
|
|
160
|
+
while (lineStart > 0 && text[lineStart - 1] !== '\n') lineStart -= 1;
|
|
161
|
+
if (text.slice(lineStart, removeStart).trim() === '') removeStart = lineStart;
|
|
162
|
+
} else {
|
|
163
|
+
let back = removeStart - 1;
|
|
164
|
+
while (back > 0 && /[ \t]/.test(text[back] ?? '')) back -= 1;
|
|
165
|
+
if (text[back] === '\n') {
|
|
166
|
+
let prev = back - 1;
|
|
167
|
+
while (prev > 0 && /[ \t]/.test(text[prev] ?? '')) prev -= 1;
|
|
168
|
+
if (text[prev] === ',') {
|
|
169
|
+
removeStart = prev;
|
|
170
|
+
if (text[removeEnd] === '\n') removeEnd += 1;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return text.slice(0, removeStart) + text.slice(removeEnd);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function processManifest(path: string): Promise<Result> {
|
|
179
|
+
const raw = await readFile(path, 'utf-8');
|
|
180
|
+
const data: Record<string, unknown> = JSON.parse(raw);
|
|
181
|
+
const removed = REMOVED_KEYS.filter((key) => key in data);
|
|
182
|
+
if (removed.length === 0) return { path, status: 'already-clean', removed: [] };
|
|
183
|
+
|
|
184
|
+
let stripped = raw;
|
|
185
|
+
for (const key of removed) stripped = removeTopLevelKey(stripped, key);
|
|
186
|
+
|
|
187
|
+
// Sanity: stripped output must still be valid JSON and must agree on
|
|
188
|
+
// every field except the two we removed.
|
|
189
|
+
const reparsed: Record<string, unknown> = JSON.parse(stripped);
|
|
190
|
+
for (const key of removed) {
|
|
191
|
+
if (key in reparsed) {
|
|
192
|
+
throw new Error(`Internal: ${key} survived strip in ${path}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!dryRun) await writeFile(path, stripped, 'utf-8');
|
|
197
|
+
return { path, status: dryRun ? 'needs-fix' : 'fixed', removed };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const manifests = await findManifests(projectRoot);
|
|
201
|
+
if (manifests.length === 0) {
|
|
202
|
+
console.error(`No migration.json files found under ${projectRoot}.`);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let changed = 0;
|
|
207
|
+
let alreadyClean = 0;
|
|
208
|
+
for (const path of manifests) {
|
|
209
|
+
const result = await processManifest(path);
|
|
210
|
+
const rel = path.slice(projectRoot.length + 1);
|
|
211
|
+
if (result.status === 'already-clean') {
|
|
212
|
+
alreadyClean += 1;
|
|
213
|
+
console.log(`OK ${rel} (already clean)`);
|
|
214
|
+
} else {
|
|
215
|
+
changed += 1;
|
|
216
|
+
const verb = dryRun ? 'WOULD FIX' : 'FIXED';
|
|
217
|
+
console.log(`${verb} ${rel} (removed: ${result.removed.join(', ')})`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
console.log();
|
|
222
|
+
console.log(
|
|
223
|
+
`${manifests.length} manifest(s) scanned: ${changed} ${dryRun ? 'needing fix' : 'fixed'}, ${alreadyClean} already clean.`,
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
if (dryRun && changed > 0) process.exit(1);
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
from: "0.9"
|
|
3
|
+
to: "0.10"
|
|
4
|
+
changes:
|
|
5
|
+
- id: stamp-storage-types-kind-on-contract-snapshots
|
|
6
|
+
summary: Stamp the `kind` discriminator (`"codec-instance"` / `"postgres-enum"`) on every entry in `storage.types` inside every committed `start-contract.json` / `end-contract.json` snapshot (extension seed migrations included). The SQL family's contract serializer is now strict — untagged entries fail to load with a deserializer diagnostic naming the offending entry.
|
|
7
|
+
detection:
|
|
8
|
+
glob: "**/migrations/**/{start,end}-contract.json"
|
|
9
|
+
contains:
|
|
10
|
+
- '"codecId"'
|
|
11
|
+
anyMatch: true
|
|
12
|
+
script: ./stamp-storage-types-kind.ts
|
|
13
|
+
- id: stamp-storage-types-kind-in-source
|
|
14
|
+
summary: Wrap untagged codec-triple inputs to `SqlStorage` (or any builder that materialises `storage.types`) with `toStorageTypeInstance(...)`, and use the target-specific `PostgresEnumType` class for Postgres-enum entries — the `SqlStorage` constructor now throws on untagged entries instead of papering over them.
|
|
15
|
+
detection:
|
|
16
|
+
glob: "**/*.{ts,tsx}"
|
|
17
|
+
contains:
|
|
18
|
+
- "storage.types"
|
|
19
|
+
- "codecId"
|
|
20
|
+
anyMatch: true
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# 0.9 → 0.10 — Extension-author upgrade instructions
|
|
24
|
+
|
|
25
|
+
## `stamp-storage-types-kind-on-contract-snapshots`
|
|
26
|
+
|
|
27
|
+
Starting at the 0.10 release, the SQL family's contract serializer (`familyInstance.validateContract`, the seam every on-disk contract read now crosses) is **strict** about the `storage.types` polymorphic slot. Every entry must carry a `kind` discriminator (`"codec-instance"` for codec triples, `"postgres-enum"` for Postgres enums). The previous silent fallthrough in `normaliseTypeEntry` that quietly accepted untagged codec triples is gone.
|
|
28
|
+
|
|
29
|
+
This applies to **seed migrations** shipped inside an extension package (e.g. `migrations/<edge-id>/{start,end}-contract.json` shipped under `packages/<extension>/migrations/`) just as it does to user-app migrations. Any extension that ships seed `*-contract.json` snapshots with untagged `storage.types` entries will fail to load under 0.10 with a `Contract validation failed` envelope.
|
|
30
|
+
|
|
31
|
+
Before 0.10, seed snapshots looked like this:
|
|
32
|
+
|
|
33
|
+
```jsonc
|
|
34
|
+
{
|
|
35
|
+
"storage": {
|
|
36
|
+
"types": {
|
|
37
|
+
"Embedding1536": {
|
|
38
|
+
"codecId": "pg/vector@1",
|
|
39
|
+
"nativeType": "vector",
|
|
40
|
+
"typeParams": { "length": 1536 }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Starting at 0.10 the same entries must look like this:
|
|
48
|
+
|
|
49
|
+
```jsonc
|
|
50
|
+
{
|
|
51
|
+
"storage": {
|
|
52
|
+
"types": {
|
|
53
|
+
"Embedding1536": {
|
|
54
|
+
"kind": "codec-instance",
|
|
55
|
+
"codecId": "pg/vector@1",
|
|
56
|
+
"nativeType": "vector",
|
|
57
|
+
"typeParams": { "length": 1536 }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Postgres-enum entries undergo a structural change (the `name` is lifted from the entry's map key, `values` is hoisted out of `typeParams`, `typeParams` is dropped). See the user-facing entry (`../../../app/upgrades/0.9-to-0.10/instructions.md`) for the before/after on the enum shape — the on-disk transformation is identical for both audiences.
|
|
65
|
+
|
|
66
|
+
### What `stamp-storage-types-kind.ts` does
|
|
67
|
+
|
|
68
|
+
The colocated script walks the project root, descends into every directory named `migrations/` (skipping `node_modules`, `.git`, `dist`, `build`), and rewrites every `start-contract.json` and `end-contract.json` whose `storage.types` slot has at least one untagged entry:
|
|
69
|
+
|
|
70
|
+
- **Already-stamped entries** are left untouched (idempotent — safe to re-run).
|
|
71
|
+
- **`pg/enum@1`** entries are rewritten as the `"postgres-enum"` shape (lifts `name` from the map key, hoists `values` out of `typeParams`, drops `typeParams`).
|
|
72
|
+
- **Every other codecId** is rewritten as the `"codec-instance"` shape (`kind` prepended; `codecId`, `nativeType`, `typeParams` preserved verbatim).
|
|
73
|
+
- The whole snapshot is re-serialised via a JSON pretty-printer that mirrors the CLI's authoring shape (multi-line objects, inline short arrays of primitives) — the diff outside `storage.types` is zero on CLI-authored snapshots.
|
|
74
|
+
- A `--check` flag turns the script into a dry-run; lists affected snapshots and exits non-zero if any still need fixing.
|
|
75
|
+
|
|
76
|
+
If a snapshot contains a `storage.types` entry that is neither already-stamped nor an untagged codec triple, the script throws and names the offending entry. Hand-edit required.
|
|
77
|
+
|
|
78
|
+
## `stamp-storage-types-kind-in-source`
|
|
79
|
+
|
|
80
|
+
The SQL family's `SqlStorage` class now refuses untagged codec triples at construction time. Source code that builds an in-memory `SqlStorage` (or any contract-builder fixture / test that ends up materialising `storage.types`) with raw `{ codecId, nativeType, typeParams }` literals will throw at construction with a diagnostic like:
|
|
81
|
+
|
|
82
|
+
> `storage.types["Embedding1536"] has missing \`kind\`; expected "codec-instance" or "postgres-enum". Untagged codec triples should be wrapped with toStorageTypeInstance(...) before construction.`
|
|
83
|
+
|
|
84
|
+
There is no codemod for this — extensions construct `SqlStorage` via too many shapes (builder DSL, direct constructor, test fixtures, ad-hoc literals) for a deterministic transform to be safe. Instead, walk every `.ts` / `.tsx` file matched by the `detection.glob` above and apply these rules locally:
|
|
85
|
+
|
|
86
|
+
- **Codec-triple literal passed to `new SqlStorage({...})` or a builder that flattens to it** (e.g. `types: { Embedding1536: { codecId: 'pg/vector@1', nativeType: 'vector', typeParams: { length: 1536 } } }`): wrap each value with `toStorageTypeInstance(...)`:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { toStorageTypeInstance } from '@internal/sql-contract';
|
|
90
|
+
|
|
91
|
+
const storage = new SqlStorage({
|
|
92
|
+
types: {
|
|
93
|
+
Embedding1536: toStorageTypeInstance({
|
|
94
|
+
codecId: 'pg/vector@1',
|
|
95
|
+
nativeType: 'vector',
|
|
96
|
+
typeParams: { length: 1536 },
|
|
97
|
+
}),
|
|
98
|
+
},
|
|
99
|
+
// …
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The helper is idempotent — input already carrying the `kind` field passes through unchanged.
|
|
104
|
+
|
|
105
|
+
- **Postgres-enum literal** (e.g. `types: { user_type: { codecId: 'pg/enum@1', nativeType: 'user_type', typeParams: { values: ['admin', 'user'] } } }`): the canonical fix is to replace with a `PostgresEnumType` class instance from `@internal/postgres`:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { PostgresEnumType } from '@internal/postgres';
|
|
109
|
+
|
|
110
|
+
const storage = new SqlStorage({
|
|
111
|
+
types: {
|
|
112
|
+
user_type: new PostgresEnumType({
|
|
113
|
+
name: 'user_type',
|
|
114
|
+
nativeType: 'user_type',
|
|
115
|
+
values: ['admin', 'user'],
|
|
116
|
+
}),
|
|
117
|
+
},
|
|
118
|
+
// …
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The class instance carries `kind: 'postgres-enum'` and the structural shape the family discriminates on. Plain object literals with `kind: 'postgres-enum'` are rejected — the constructor route is mandatory because hydration of raw JSON envelopes is the target-specific serializer's job (cross-domain layering: the SQL family doesn't know about Postgres-enum's concrete class).
|
|
123
|
+
|
|
124
|
+
**Minimal-fix alternative for fixtures / round-trip tests.** If the code in question only round-trips the codec triple as a plain envelope (no target-specific enum behaviour — e.g. a planner test fixture that never reaches Postgres-enum planning hooks), stamping `kind: 'codec-instance'` on the existing literal is a sufficient and idempotent fix:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
const fixtureStorage: SqlStorageInput = {
|
|
128
|
+
types: {
|
|
129
|
+
user_type: {
|
|
130
|
+
kind: 'codec-instance',
|
|
131
|
+
codecId: 'pg/enum@1',
|
|
132
|
+
nativeType: 'user_type',
|
|
133
|
+
typeParams: { values: ['admin', 'user'] },
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
// …
|
|
137
|
+
};
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The SQL family treats the entry as an opaque codec triple and round-trips it unchanged. Use this form only when you do not need `PostgresEnumType`'s enum-specific structural fields (`name`, `values` lifted out of `typeParams`) or its enum-planning behaviour — otherwise prefer the `PostgresEnumType` constructor above.
|
|
141
|
+
|
|
142
|
+
- **Test fixtures that round-trip a contract through JSON** (e.g. `JSON.parse(JSON.stringify(contract))`): ensure the source-side contract is constructed with stamped entries before the round-trip; the deserializer now refuses to silently re-stamp on the read.
|
|
143
|
+
|
|
144
|
+
- **Extension-contributed codec catalogues** (extensions that register custom codecs): no source change required for the catalogue itself — codec definitions don't carry `kind`. The change is only at the `SqlStorage.types` slot where catalogue entries materialise per-contract.
|
|
145
|
+
|
|
146
|
+
### Validation
|
|
147
|
+
|
|
148
|
+
After running the JSON codemod and applying the source-level rules above, run `pnpm typecheck && pnpm test` (or your extension's equivalent). `prisma-8-check-pins` should also pass — the pin set is unchanged for this transition; the breaking change is in runtime construction behaviour, not the dependency contract.
|
|
149
|
+
|
|
150
|
+
If your extension ships seed migrations under `packages/<extension>/migrations/`, also run the codemod with `--check` against the project root to confirm the seed snapshots all stamp correctly. The user-facing copy of the same codemod (under `../../../app/upgrades/0.9-to-0.10/`) operates on the user's app-space migrations; this copy operates on your extension's seed migrations.
|