@prisma-next-idb/family-idb 0.1.0
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/LICENSE +21 -0
- package/dist/bin/prisma-next-idb.d.mts +1 -0
- package/dist/bin/prisma-next-idb.mjs +281 -0
- package/dist/bin/prisma-next-idb.mjs.map +1 -0
- package/dist/exports/config-types.d.mts +39 -0
- package/dist/exports/config-types.d.mts.map +1 -0
- package/dist/exports/config-types.mjs +66 -0
- package/dist/exports/config-types.mjs.map +1 -0
- package/dist/exports/contract-psl.d.mts +48 -0
- package/dist/exports/contract-psl.d.mts.map +1 -0
- package/dist/exports/contract-psl.mjs +463 -0
- package/dist/exports/contract-psl.mjs.map +1 -0
- package/dist/exports/contract-ts.d.mts +73 -0
- package/dist/exports/contract-ts.d.mts.map +1 -0
- package/dist/exports/contract-ts.mjs +162 -0
- package/dist/exports/contract-ts.mjs.map +1 -0
- package/dist/exports/control.d.mts +79 -0
- package/dist/exports/control.d.mts.map +1 -0
- package/dist/exports/control.mjs +566 -0
- package/dist/exports/control.mjs.map +1 -0
- package/dist/exports/pack.d.mts +10 -0
- package/dist/exports/pack.d.mts.map +1 -0
- package/dist/exports/pack.mjs +11 -0
- package/dist/exports/pack.mjs.map +1 -0
- package/dist/generate-baseline-Dg3vfBpB.mjs +130 -0
- package/dist/generate-baseline-Dg3vfBpB.mjs.map +1 -0
- package/dist/generate-migration-D8bDx9jo.mjs +150 -0
- package/dist/generate-migration-D8bDx9jo.mjs.map +1 -0
- package/dist/preflight-D8GLQXy3.mjs +112 -0
- package/dist/preflight-D8GLQXy3.mjs.map +1 -0
- package/dist/validate-DL9NthnR.mjs +48 -0
- package/dist/validate-DL9NthnR.mjs.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Prisma IDB
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, relative } from "pathe";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
//#region src/core/chain-order.ts
|
|
6
|
+
/**
|
|
7
|
+
* Walk an unordered set of migration packages by `metadata.from`/`to` edges
|
|
8
|
+
* to produce a chain-ordered list. Starts at the package whose
|
|
9
|
+
* `from === null` (the baseline) and follows each `to` to the next
|
|
10
|
+
* package's matching `from`.
|
|
11
|
+
*
|
|
12
|
+
* Throws on:
|
|
13
|
+
* - No baseline (no package with `from === null`)
|
|
14
|
+
* - Multiple baselines (two packages with `from === null`)
|
|
15
|
+
* - Broken edge (no package whose `from` matches the cursor)
|
|
16
|
+
* - Cycle (revisited hash)
|
|
17
|
+
* - Orphan package (package left unvisited after chain completes)
|
|
18
|
+
*/
|
|
19
|
+
function chainOrderByMetadata(unordered) {
|
|
20
|
+
if (unordered.size === 0) return [];
|
|
21
|
+
const byFrom = /* @__PURE__ */ new Map();
|
|
22
|
+
for (const pkg of unordered.values()) {
|
|
23
|
+
const from = pkg.metadata.from;
|
|
24
|
+
const existing = byFrom.get(from);
|
|
25
|
+
if (existing) throw new Error(`Migration chain conflict: ${existing.dirName} and ${pkg.dirName} both declare from === ${JSON.stringify(from)}. The chain must be linear.`);
|
|
26
|
+
byFrom.set(from, pkg);
|
|
27
|
+
}
|
|
28
|
+
const ordered = [];
|
|
29
|
+
const visited = /* @__PURE__ */ new Set();
|
|
30
|
+
let cursor = null;
|
|
31
|
+
while (true) {
|
|
32
|
+
const next = byFrom.get(cursor);
|
|
33
|
+
if (!next) {
|
|
34
|
+
if (ordered.length === unordered.size) break;
|
|
35
|
+
const orphans = [...unordered.keys()].filter((d) => !visited.has(d));
|
|
36
|
+
throw new Error(`Migration chain broken: no package declares from === ${JSON.stringify(cursor)}. Orphan package(s) not reachable from the baseline: ${orphans.join(", ")}. Either the migrations directory has been hand-edited or a package was deleted; re-emit the affected migration with \`node migration.ts\`.`);
|
|
37
|
+
}
|
|
38
|
+
if (visited.has(next.dirName)) throw new Error(`Migration chain cycle detected at ${next.dirName}.`);
|
|
39
|
+
visited.add(next.dirName);
|
|
40
|
+
ordered.push(next);
|
|
41
|
+
cursor = next.metadata.to;
|
|
42
|
+
}
|
|
43
|
+
return ordered;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/core/contract-space-codegen.ts
|
|
47
|
+
/**
|
|
48
|
+
* Read every migration package under `<migrationsDir>/app/`, validate the
|
|
49
|
+
* chain's connectivity, then emit a generated TypeScript module that
|
|
50
|
+
* JSON-imports each package's `migration.json` + `ops.json` and assembles
|
|
51
|
+
* them into a `ContractSpace` via `contractSpaceFromJson`. The head ref
|
|
52
|
+
* is inlined as `{ hash, invariants }` derived from the last package's
|
|
53
|
+
* metadata — no on-disk `refs/head.json` is written.
|
|
54
|
+
*
|
|
55
|
+
* (We deliberately avoid writing `migrations/refs/` because the framework's
|
|
56
|
+
* contract-space layout treats top-level `migrations/<subdir>/` as a
|
|
57
|
+
* per-space directory; an orphan `refs/` collides with that. Extension
|
|
58
|
+
* packages — postgis, paradedb, etc. — DO write `migrations/refs/head.json`
|
|
59
|
+
* because their package root IS their single space and there's no
|
|
60
|
+
* ambiguity. App-level layouts use `migrations/app/` for the app space, and
|
|
61
|
+
* the head ref for the app space comes from the latest package's `to`.)
|
|
62
|
+
*
|
|
63
|
+
* Idempotent: re-running produces byte-identical output (modulo
|
|
64
|
+
* migration package list changes). Exit code 0 on success.
|
|
65
|
+
*/
|
|
66
|
+
async function generateContractSpace(opts) {
|
|
67
|
+
const appDir = join(opts.migrationsDir ?? join(opts.cwd, "migrations"), "app");
|
|
68
|
+
const contractPath = opts.contractPath ?? join(opts.cwd, "src/lib/prisma/contract.json");
|
|
69
|
+
const outPath = opts.outPath ?? join(opts.cwd, "src/lib/prisma/contract-space.generated.ts");
|
|
70
|
+
const packages = await loadPackages(appDir);
|
|
71
|
+
if (packages.length === 0) process.stderr.write("Warning: no migration packages found in migrations/app/.\nThe generated module will have an empty migrations list, which breaks\n`createAutoMigratingIdbClient` on a fresh database.\nRun `prisma-next-idb generate-baseline` first to create the initial migration.\n\n");
|
|
72
|
+
const outDir = dirname(outPath);
|
|
73
|
+
await mkdir(outDir, { recursive: true });
|
|
74
|
+
await writeFile(outPath, renderModule({
|
|
75
|
+
outDir,
|
|
76
|
+
contractPath,
|
|
77
|
+
appDir,
|
|
78
|
+
packages
|
|
79
|
+
}), "utf-8");
|
|
80
|
+
process.stdout.write(`Wrote ${outPath} (${packages.length} migration${packages.length === 1 ? "" : "s"})\n`);
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
async function loadPackages(appDir) {
|
|
84
|
+
let dirs;
|
|
85
|
+
try {
|
|
86
|
+
dirs = (await readdir(appDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (err.code === "ENOENT") return [];
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
const unordered = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const dirName of dirs) {
|
|
93
|
+
const metaPath = join(appDir, dirName, "migration.json");
|
|
94
|
+
const opsPath = join(appDir, dirName, "ops.json");
|
|
95
|
+
try {
|
|
96
|
+
const metaRaw = await readFile(metaPath, "utf-8");
|
|
97
|
+
const metadata = JSON.parse(metaRaw);
|
|
98
|
+
await readFile(opsPath, "utf-8");
|
|
99
|
+
unordered.set(dirName, {
|
|
100
|
+
dirName,
|
|
101
|
+
metadata
|
|
102
|
+
});
|
|
103
|
+
} catch (err) {
|
|
104
|
+
process.stderr.write(`Skipping ${dirName}: missing or unreadable migration.json/ops.json (${err.message})\n`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return chainOrderByMetadata(unordered);
|
|
108
|
+
}
|
|
109
|
+
function renderModule(input) {
|
|
110
|
+
const contractImportPath = toModuleSpecifier(relative(input.outDir, input.contractPath));
|
|
111
|
+
const importLines = [
|
|
112
|
+
"// THIS FILE IS AUTO-GENERATED — do not edit by hand.",
|
|
113
|
+
"// Regenerate with: prisma-next-idb generate-contract-space",
|
|
114
|
+
"",
|
|
115
|
+
`import type { Contract } from "${contractImportPath.replace(/\.json$/i, "")}";`,
|
|
116
|
+
"import { contractSpaceFromJson } from \"@prisma-next/migration-tools/spaces\";",
|
|
117
|
+
`import contractJson from "${contractImportPath}" with { type: "json" };`
|
|
118
|
+
];
|
|
119
|
+
for (const pkg of input.packages) {
|
|
120
|
+
const id = identFromDir(pkg.dirName);
|
|
121
|
+
const metaPath = toModuleSpecifier(relative(input.outDir, join(input.appDir, pkg.dirName, "migration.json")));
|
|
122
|
+
const opsPath = toModuleSpecifier(relative(input.outDir, join(input.appDir, pkg.dirName, "ops.json")));
|
|
123
|
+
importLines.push(`import ${id}_meta from "${metaPath}" with { type: "json" };`);
|
|
124
|
+
importLines.push(`import ${id}_ops from "${opsPath}" with { type: "json" };`);
|
|
125
|
+
}
|
|
126
|
+
const last = input.packages[input.packages.length - 1];
|
|
127
|
+
const headRefLiteral = last === void 0 ? "{ hash: \"\", invariants: [] as const }" : `{ hash: ${identFromDir(last.dirName)}_meta.to, invariants: (${identFromDir(last.dirName)}_meta.providedInvariants ?? []) as readonly string[] }`;
|
|
128
|
+
const migrationsBody = input.packages.length === 0 ? " migrations: []," : [
|
|
129
|
+
" migrations: [",
|
|
130
|
+
input.packages.map((pkg) => {
|
|
131
|
+
const id = identFromDir(pkg.dirName);
|
|
132
|
+
return ` { dirName: ${JSON.stringify(pkg.dirName)}, metadata: ${id}_meta, ops: ${id}_ops },`;
|
|
133
|
+
}).join("\n"),
|
|
134
|
+
" ],"
|
|
135
|
+
].join("\n");
|
|
136
|
+
return [
|
|
137
|
+
...importLines,
|
|
138
|
+
"",
|
|
139
|
+
"export const contractSpace = contractSpaceFromJson<Contract>({",
|
|
140
|
+
" contractJson,",
|
|
141
|
+
migrationsBody,
|
|
142
|
+
` headRef: ${headRefLiteral},`,
|
|
143
|
+
"});",
|
|
144
|
+
""
|
|
145
|
+
].join("\n");
|
|
146
|
+
}
|
|
147
|
+
function identFromDir(dirName) {
|
|
148
|
+
return `mig_${dirName.replace(/[^a-zA-Z0-9_]/g, "_")}`;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Coerce a `pathe` relative path into a TypeScript module specifier.
|
|
152
|
+
* - Adds a leading `./` if the path doesn't already start with `.` or `/`.
|
|
153
|
+
* - Leaves absolute and parent-relative paths alone.
|
|
154
|
+
*/
|
|
155
|
+
function toModuleSpecifier(p) {
|
|
156
|
+
if (p.startsWith(".") || p.startsWith("/")) return p;
|
|
157
|
+
return `./${p}`;
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/bin/prisma-next-idb.ts
|
|
161
|
+
/**
|
|
162
|
+
* `prisma-next-idb` — IDB-target-specific CLI tooling.
|
|
163
|
+
*
|
|
164
|
+
* Subcommands:
|
|
165
|
+
*
|
|
166
|
+
* - `generate-baseline` — auto-generate the first migration package
|
|
167
|
+
* (`from: null`) from the current `contract.json`. Only valid on a
|
|
168
|
+
* fresh project with no migrations yet.
|
|
169
|
+
* - `generate-migration` — plan and write the next incremental migration
|
|
170
|
+
* package by diffing the head migration's `end-contract.json` against the
|
|
171
|
+
* current `contract.json`. Requires at least one migration to already exist.
|
|
172
|
+
* - `generate-contract-space` — re-write
|
|
173
|
+
* `<project>/src/lib/prisma/contract-space.generated.ts` (or the path
|
|
174
|
+
* specified by `--out`) from the on-disk `migrations/app/` packages.
|
|
175
|
+
* - `preflight` — walk the migration chain from empty → tip against a
|
|
176
|
+
* `fake-indexeddb` shadow, reporting per-step success/failure.
|
|
177
|
+
*
|
|
178
|
+
* Why a separate binary from `prisma-next`: the framework CLI is generic
|
|
179
|
+
* (target-discovery via config); these commands are IDB-specific and own an
|
|
180
|
+
* opinionated layout. Keeping them separate avoids growing the framework CLI
|
|
181
|
+
* surface with target-specific subcommands.
|
|
182
|
+
*
|
|
183
|
+
* Typical new-project workflow:
|
|
184
|
+
* 1. prisma-next contract emit # generate contract.json
|
|
185
|
+
* 2. prisma-next-idb generate-baseline # create migrations/app/<ts>_baseline/
|
|
186
|
+
* 3. prisma-next-idb generate-contract-space # bundle into contract-space.generated.ts
|
|
187
|
+
* 4. prisma-next-idb preflight # (optional) validate chain in CI
|
|
188
|
+
*
|
|
189
|
+
* Adding a subsequent migration:
|
|
190
|
+
* 1. prisma-next contract emit # update contract.json after schema change
|
|
191
|
+
* 2. prisma-next-idb generate-migration \ # plan incremental migration
|
|
192
|
+
* --name <slug> # e.g. --name add_posts
|
|
193
|
+
* 3. prisma-next-idb generate-contract-space # re-bundle contract-space.generated.ts
|
|
194
|
+
* 4. prisma-next-idb preflight # (optional) validate chain in CI
|
|
195
|
+
*/
|
|
196
|
+
function parseFlags() {
|
|
197
|
+
const { values } = parseArgs({
|
|
198
|
+
allowPositionals: true,
|
|
199
|
+
strict: false,
|
|
200
|
+
options: {
|
|
201
|
+
contract: { type: "string" },
|
|
202
|
+
"migrations-dir": { type: "string" },
|
|
203
|
+
out: { type: "string" },
|
|
204
|
+
name: { type: "string" }
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
return {
|
|
208
|
+
contract: values["contract"],
|
|
209
|
+
migrationsDir: values["migrations-dir"],
|
|
210
|
+
out: values["out"],
|
|
211
|
+
name: values["name"]
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
async function main() {
|
|
215
|
+
const { positionals } = parseArgs({
|
|
216
|
+
allowPositionals: true,
|
|
217
|
+
strict: false
|
|
218
|
+
});
|
|
219
|
+
const subcommand = positionals[0];
|
|
220
|
+
const flags = parseFlags();
|
|
221
|
+
switch (subcommand) {
|
|
222
|
+
case "generate-baseline": {
|
|
223
|
+
const { generateBaseline } = await import("../generate-baseline-Dg3vfBpB.mjs");
|
|
224
|
+
return generateBaseline({
|
|
225
|
+
cwd: process.cwd(),
|
|
226
|
+
...flags.contract !== void 0 && { contractPath: flags.contract },
|
|
227
|
+
...flags.migrationsDir !== void 0 && { migrationsDir: flags.migrationsDir },
|
|
228
|
+
...flags.name !== void 0 && { name: flags.name }
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
case "generate-migration": {
|
|
232
|
+
if (flags.name === void 0) {
|
|
233
|
+
process.stderr.write("generate-migration: --name <slug> is required.\n");
|
|
234
|
+
return 2;
|
|
235
|
+
}
|
|
236
|
+
const { generateMigration } = await import("../generate-migration-D8bDx9jo.mjs");
|
|
237
|
+
return generateMigration({
|
|
238
|
+
cwd: process.cwd(),
|
|
239
|
+
name: flags.name,
|
|
240
|
+
...flags.contract !== void 0 && { contractPath: flags.contract },
|
|
241
|
+
...flags.migrationsDir !== void 0 && { migrationsDir: flags.migrationsDir }
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
case "generate-contract-space": return generateContractSpace({
|
|
245
|
+
cwd: process.cwd(),
|
|
246
|
+
...flags.contract !== void 0 && { contractPath: flags.contract },
|
|
247
|
+
...flags.migrationsDir !== void 0 && { migrationsDir: flags.migrationsDir },
|
|
248
|
+
...flags.out !== void 0 && { outPath: flags.out }
|
|
249
|
+
});
|
|
250
|
+
case "preflight": {
|
|
251
|
+
const { runPreflight } = await import("../preflight-D8GLQXy3.mjs");
|
|
252
|
+
return runPreflight({
|
|
253
|
+
cwd: process.cwd(),
|
|
254
|
+
...flags.migrationsDir !== void 0 && { migrationsDir: flags.migrationsDir }
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
case void 0:
|
|
258
|
+
case "help":
|
|
259
|
+
case "--help":
|
|
260
|
+
case "-h":
|
|
261
|
+
printHelp();
|
|
262
|
+
return 0;
|
|
263
|
+
default:
|
|
264
|
+
process.stderr.write(`Unknown command: ${subcommand}\n\n`);
|
|
265
|
+
printHelp();
|
|
266
|
+
return 2;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function printHelp() {
|
|
270
|
+
process.stdout.write("prisma-next-idb — IDB-target tooling for Prisma Next\n\nUsage:\n prisma-next-idb <command> [flags]\n\nCommands:\n generate-baseline Create the initial migration package from contract.json\n generate-migration Plan the next incremental migration (requires --name)\n generate-contract-space Regenerate contract-space.generated.ts\n preflight Validate the migration chain against fake-indexeddb\n help Show this message\n\nFlags (all commands):\n --contract <path> Path to contract.json (default: src/lib/prisma/contract.json)\n --migrations-dir <path> Path to migrations root (default: migrations/)\n\nFlags (generate-baseline only):\n --name <slug> Directory slug (default: baseline)\n\nFlags (generate-migration only):\n --name <slug> Directory slug, e.g. add_posts (required)\n\nFlags (generate-contract-space only):\n --out <path> Output file path (default: src/lib/prisma/contract-space.generated.ts)\n\nNew-project workflow:\n 1. prisma-next contract emit # generate contract.json\n 2. prisma-next-idb generate-baseline \\ # create baseline migration\n --contract src/prisma/contract.json # (if your layout differs)\n 3. prisma-next-idb generate-contract-space \\ # bundle into contract-space.generated.ts\n --contract src/prisma/contract.json \\ # (same override if needed)\n --out src/prisma/contract-space.generated.ts\n 4. prisma-next-idb preflight # validate chain (CI)\n\nAdding a subsequent migration:\n 1. prisma-next contract emit # update contract.json\n 2. prisma-next-idb generate-migration --name <slug> # plan incremental migration\n 3. prisma-next-idb generate-contract-space # re-bundle contract-space\n 4. prisma-next-idb preflight # validate chain (CI)\n");
|
|
271
|
+
}
|
|
272
|
+
main().then((code) => {
|
|
273
|
+
process.exit(code);
|
|
274
|
+
}, (err) => {
|
|
275
|
+
process.stderr.write(`${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
|
|
276
|
+
process.exit(1);
|
|
277
|
+
});
|
|
278
|
+
//#endregion
|
|
279
|
+
export { chainOrderByMetadata as t };
|
|
280
|
+
|
|
281
|
+
//# sourceMappingURL=prisma-next-idb.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prisma-next-idb.mjs","names":[],"sources":["../../src/core/chain-order.ts","../../src/core/contract-space-codegen.ts","../../src/bin/prisma-next-idb.ts"],"sourcesContent":["import type { MigrationMetadata } from \"@prisma-next/migration-tools/metadata\";\n\n/**\n * Minimal shape that {@link chainOrderByMetadata} needs from each package.\n *\n * Callers typically have richer types (ops, file paths) — just make sure\n * `dirName` and `metadata.from` are present.\n */\nexport interface ChainablePackage {\n readonly dirName: string;\n readonly metadata: Pick<MigrationMetadata, \"from\" | \"to\">;\n}\n\n/**\n * Walk an unordered set of migration packages by `metadata.from`/`to` edges\n * to produce a chain-ordered list. Starts at the package whose\n * `from === null` (the baseline) and follows each `to` to the next\n * package's matching `from`.\n *\n * Throws on:\n * - No baseline (no package with `from === null`)\n * - Multiple baselines (two packages with `from === null`)\n * - Broken edge (no package whose `from` matches the cursor)\n * - Cycle (revisited hash)\n * - Orphan package (package left unvisited after chain completes)\n */\nexport function chainOrderByMetadata<P extends ChainablePackage>(unordered: ReadonlyMap<string, P>): P[] {\n if (unordered.size === 0) return [];\n\n const byFrom = new Map<string | null, P>();\n for (const pkg of unordered.values()) {\n const from = pkg.metadata.from;\n const existing = byFrom.get(from);\n if (existing) {\n throw new Error(\n `Migration chain conflict: ${existing.dirName} and ${pkg.dirName} both ` +\n `declare from === ${JSON.stringify(from)}. The chain must be linear.`\n );\n }\n byFrom.set(from, pkg);\n }\n\n const ordered: P[] = [];\n const visited = new Set<string>();\n let cursor: string | null = null;\n while (true) {\n const next = byFrom.get(cursor);\n if (!next) {\n if (ordered.length === unordered.size) break; // chain complete\n const orphans = [...unordered.keys()].filter((d) => !visited.has(d));\n throw new Error(\n `Migration chain broken: no package declares from === ${JSON.stringify(cursor)}. ` +\n `Orphan package(s) not reachable from the baseline: ${orphans.join(\", \")}. ` +\n \"Either the migrations directory has been hand-edited or a package was \" +\n \"deleted; re-emit the affected migration with `node migration.ts`.\"\n );\n }\n if (visited.has(next.dirName)) {\n throw new Error(`Migration chain cycle detected at ${next.dirName}.`);\n }\n visited.add(next.dirName);\n ordered.push(next);\n cursor = next.metadata.to;\n }\n\n return ordered;\n}\n","import { mkdir, readdir, readFile, writeFile } from \"node:fs/promises\";\nimport type { MigrationMetadata } from \"@prisma-next/migration-tools/metadata\";\nimport { dirname, join, relative } from \"pathe\";\nimport { chainOrderByMetadata, type ChainablePackage } from \"./chain-order\";\n\n/**\n * Options for {@link generateContractSpace}.\n *\n * All paths default to framework-conventional values but **should be\n * overridden** when the project's layout differs. The defaults are:\n *\n * - migrations: `<cwd>/migrations/`\n * - contract: `<cwd>/src/lib/prisma/contract.json`\n * - output: `<cwd>/src/lib/prisma/contract-space.generated.ts`\n *\n * Pass explicit values (or the corresponding CLI flags) for any project\n * that keeps its contract and generated files elsewhere — Next.js, Nuxt,\n * plain Vite, etc. all typically use different paths.\n */\nexport interface GenerateContractSpaceOptions {\n readonly cwd: string;\n readonly migrationsDir?: string;\n readonly contractPath?: string;\n readonly outPath?: string;\n}\n\ninterface LoadedPackage extends ChainablePackage {\n readonly metadata: MigrationMetadata;\n}\n\n/**\n * Read every migration package under `<migrationsDir>/app/`, validate the\n * chain's connectivity, then emit a generated TypeScript module that\n * JSON-imports each package's `migration.json` + `ops.json` and assembles\n * them into a `ContractSpace` via `contractSpaceFromJson`. The head ref\n * is inlined as `{ hash, invariants }` derived from the last package's\n * metadata — no on-disk `refs/head.json` is written.\n *\n * (We deliberately avoid writing `migrations/refs/` because the framework's\n * contract-space layout treats top-level `migrations/<subdir>/` as a\n * per-space directory; an orphan `refs/` collides with that. Extension\n * packages — postgis, paradedb, etc. — DO write `migrations/refs/head.json`\n * because their package root IS their single space and there's no\n * ambiguity. App-level layouts use `migrations/app/` for the app space, and\n * the head ref for the app space comes from the latest package's `to`.)\n *\n * Idempotent: re-running produces byte-identical output (modulo\n * migration package list changes). Exit code 0 on success.\n */\nexport async function generateContractSpace(opts: GenerateContractSpaceOptions): Promise<number> {\n const migrationsDir = opts.migrationsDir ?? join(opts.cwd, \"migrations\");\n const appDir = join(migrationsDir, \"app\");\n const contractPath = opts.contractPath ?? join(opts.cwd, \"src/lib/prisma/contract.json\");\n const outPath = opts.outPath ?? join(opts.cwd, \"src/lib/prisma/contract-space.generated.ts\");\n\n const packages = await loadPackages(appDir);\n validateChain(packages);\n\n // Warn when no packages exist — the output module will have an empty\n // migrations list and `hash: \"\"`, which breaks createAutoMigratingIdbClient\n // on a fresh database (walkChain throws: \"no migration with from === null\").\n // The user should run `prisma-next-idb generate-baseline` first.\n if (packages.length === 0) {\n process.stderr.write(\n \"Warning: no migration packages found in migrations/app/.\\n\" +\n \"The generated module will have an empty migrations list, which breaks\\n\" +\n \"`createAutoMigratingIdbClient` on a fresh database.\\n\" +\n \"Run `prisma-next-idb generate-baseline` first to create the initial migration.\\n\\n\"\n );\n }\n\n // Emit the generated module. The head ref is derived inline.\n const outDir = dirname(outPath);\n await mkdir(outDir, { recursive: true });\n const source = renderModule({\n outDir,\n contractPath,\n appDir,\n packages,\n });\n await writeFile(outPath, source, \"utf-8\");\n\n process.stdout.write(`Wrote ${outPath} (${packages.length} migration${packages.length === 1 ? \"\" : \"s\"})\\n`);\n return 0;\n}\n\nasync function loadPackages(appDir: string): Promise<LoadedPackage[]> {\n let dirs: string[];\n try {\n dirs = (await readdir(appDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw err;\n }\n\n // Load all packages unordered, then chain-walk to derive order. Directory\n // name lexicographic order is unreliable because timestamp formats can\n // mix (e.g. framework's `migration plan` writes `T0337`, hand-authored\n // baselines may use `T120000`). The truth is in metadata.from/to edges.\n const unordered = new Map<string, LoadedPackage>();\n for (const dirName of dirs) {\n const metaPath = join(appDir, dirName, \"migration.json\");\n const opsPath = join(appDir, dirName, \"ops.json\");\n try {\n const metaRaw = await readFile(metaPath, \"utf-8\");\n const metadata = JSON.parse(metaRaw) as MigrationMetadata;\n // existence check for ops.json — we don't parse here because the\n // generated module JSON-imports the file at build time.\n await readFile(opsPath, \"utf-8\");\n unordered.set(dirName, { dirName, metadata });\n } catch (err) {\n process.stderr.write(\n `Skipping ${dirName}: missing or unreadable migration.json/ops.json ` + `(${(err as Error).message})\\n`\n );\n }\n }\n\n return chainOrderByMetadata(unordered);\n}\n\n/**\n * No-op — `chainOrderByMetadata` already enforces all invariants during\n * {@link loadPackages}. Kept as a named hook so callers can compose their\n * own package list and re-validate without calling the full load path.\n */\nfunction validateChain(_packages: ReadonlyArray<LoadedPackage>): void {\n // Validation happens inside `chainOrderByMetadata` during load. No-op here.\n}\n\ninterface RenderInput {\n readonly outDir: string;\n readonly contractPath: string;\n readonly appDir: string;\n readonly packages: ReadonlyArray<LoadedPackage>;\n}\n\nfunction renderModule(input: RenderInput): string {\n const contractImportPath = toModuleSpecifier(relative(input.outDir, input.contractPath));\n\n // Local `Contract` import — picks up the IDB-narrowed type from the\n // user's `contract.d.ts` rather than the framework's wider `Contract`.\n // `contract.json` and `contract.d.ts` share a stem, so the .ts module\n // specifier matches via TypeScript's `.json` → bare-import resolution.\n const contractTypeSpecifier = contractImportPath.replace(/\\.json$/i, \"\");\n\n const importLines: string[] = [\n \"// THIS FILE IS AUTO-GENERATED — do not edit by hand.\",\n \"// Regenerate with: prisma-next-idb generate-contract-space\",\n \"\",\n `import type { Contract } from \"${contractTypeSpecifier}\";`,\n 'import { contractSpaceFromJson } from \"@prisma-next/migration-tools/spaces\";',\n `import contractJson from \"${contractImportPath}\" with { type: \"json\" };`,\n ];\n\n for (const pkg of input.packages) {\n const id = identFromDir(pkg.dirName);\n const metaPath = toModuleSpecifier(relative(input.outDir, join(input.appDir, pkg.dirName, \"migration.json\")));\n const opsPath = toModuleSpecifier(relative(input.outDir, join(input.appDir, pkg.dirName, \"ops.json\")));\n importLines.push(`import ${id}_meta from \"${metaPath}\" with { type: \"json\" };`);\n importLines.push(`import ${id}_ops from \"${opsPath}\" with { type: \"json\" };`);\n }\n\n // Inline head ref derivation. The hash comes from the last package's\n // `to`; invariants come from its `providedInvariants` (empty for the\n // IDB app target today — no extension-contributed invariants).\n const last = input.packages[input.packages.length - 1];\n const headRefLiteral =\n last === undefined\n ? '{ hash: \"\", invariants: [] as const }'\n : `{ hash: ${identFromDir(last.dirName)}_meta.to, invariants: (${identFromDir(last.dirName)}_meta.providedInvariants ?? []) as readonly string[] }`;\n\n const migrationsBody =\n input.packages.length === 0\n ? \" migrations: [],\"\n : [\n \" migrations: [\",\n input.packages\n .map((pkg) => {\n const id = identFromDir(pkg.dirName);\n return ` { dirName: ${JSON.stringify(pkg.dirName)}, metadata: ${id}_meta, ops: ${id}_ops },`;\n })\n .join(\"\\n\"),\n \" ],\",\n ].join(\"\\n\");\n\n return [\n ...importLines,\n \"\",\n \"export const contractSpace = contractSpaceFromJson<Contract>({\",\n \" contractJson,\",\n migrationsBody,\n ` headRef: ${headRefLiteral},`,\n \"});\",\n \"\",\n ].join(\"\\n\");\n}\n\nfunction identFromDir(dirName: string): string {\n return `mig_${dirName.replace(/[^a-zA-Z0-9_]/g, \"_\")}`;\n}\n\n/**\n * Coerce a `pathe` relative path into a TypeScript module specifier.\n * - Adds a leading `./` if the path doesn't already start with `.` or `/`.\n * - Leaves absolute and parent-relative paths alone.\n */\nfunction toModuleSpecifier(p: string): string {\n if (p.startsWith(\".\") || p.startsWith(\"/\")) return p;\n return `./${p}`;\n}\n","#!/usr/bin/env node\n/**\n * `prisma-next-idb` — IDB-target-specific CLI tooling.\n *\n * Subcommands:\n *\n * - `generate-baseline` — auto-generate the first migration package\n * (`from: null`) from the current `contract.json`. Only valid on a\n * fresh project with no migrations yet.\n * - `generate-migration` — plan and write the next incremental migration\n * package by diffing the head migration's `end-contract.json` against the\n * current `contract.json`. Requires at least one migration to already exist.\n * - `generate-contract-space` — re-write\n * `<project>/src/lib/prisma/contract-space.generated.ts` (or the path\n * specified by `--out`) from the on-disk `migrations/app/` packages.\n * - `preflight` — walk the migration chain from empty → tip against a\n * `fake-indexeddb` shadow, reporting per-step success/failure.\n *\n * Why a separate binary from `prisma-next`: the framework CLI is generic\n * (target-discovery via config); these commands are IDB-specific and own an\n * opinionated layout. Keeping them separate avoids growing the framework CLI\n * surface with target-specific subcommands.\n *\n * Typical new-project workflow:\n * 1. prisma-next contract emit # generate contract.json\n * 2. prisma-next-idb generate-baseline # create migrations/app/<ts>_baseline/\n * 3. prisma-next-idb generate-contract-space # bundle into contract-space.generated.ts\n * 4. prisma-next-idb preflight # (optional) validate chain in CI\n *\n * Adding a subsequent migration:\n * 1. prisma-next contract emit # update contract.json after schema change\n * 2. prisma-next-idb generate-migration \\ # plan incremental migration\n * --name <slug> # e.g. --name add_posts\n * 3. prisma-next-idb generate-contract-space # re-bundle contract-space.generated.ts\n * 4. prisma-next-idb preflight # (optional) validate chain in CI\n */\nimport { parseArgs } from \"node:util\";\nimport { generateContractSpace } from \"../core/contract-space-codegen\";\n\n/**\n * Flags shared across all subcommands.\n *\n * `--contract <path>` and `--migrations-dir <path>` let callers override the\n * defaults for any project layout (Next.js, Nuxt, plain Vite, etc.). Without\n * them the commands fall back to framework-conventional defaults, but those\n * defaults are just starting points — every project that uses a different\n * layout should pass explicit paths.\n */\ninterface ParsedFlags {\n /** Path to contract.json. */\n contract: string | undefined;\n /** Path to the migrations root (contains `app/` subdirectory). */\n migrationsDir: string | undefined;\n /** Output path for `generate-contract-space`. */\n out: string | undefined;\n /** Slug suffix for the migration directory name. */\n name: string | undefined;\n}\n\nfunction parseFlags(): ParsedFlags {\n const { values } = parseArgs({\n allowPositionals: true,\n strict: false,\n options: {\n contract: { type: \"string\" },\n \"migrations-dir\": { type: \"string\" },\n out: { type: \"string\" },\n name: { type: \"string\" },\n },\n });\n return {\n // parseArgs with strict:false widens all values to string|boolean|undefined;\n // our options are all declared type:\"string\" so at runtime these are always\n // string|undefined — the cast is safe.\n contract: values[\"contract\"] as string | undefined,\n migrationsDir: values[\"migrations-dir\"] as string | undefined,\n out: values[\"out\"] as string | undefined,\n name: values[\"name\"] as string | undefined,\n };\n}\n\nasync function main(): Promise<number> {\n const { positionals } = parseArgs({ allowPositionals: true, strict: false });\n const subcommand = positionals[0];\n const flags = parseFlags();\n\n switch (subcommand) {\n case \"generate-baseline\": {\n const { generateBaseline } = await import(\"../core/generate-baseline\");\n return generateBaseline({\n cwd: process.cwd(),\n // exactOptionalPropertyTypes: omit the key entirely when undefined rather\n // than passing `key: undefined`, which violates the readonly optional contract.\n ...(flags.contract !== undefined && { contractPath: flags.contract }),\n ...(flags.migrationsDir !== undefined && { migrationsDir: flags.migrationsDir }),\n ...(flags.name !== undefined && { name: flags.name }),\n });\n }\n case \"generate-migration\": {\n if (flags.name === undefined) {\n process.stderr.write(\"generate-migration: --name <slug> is required.\\n\");\n return 2;\n }\n const { generateMigration } = await import(\"../core/generate-migration\");\n return generateMigration({\n cwd: process.cwd(),\n name: flags.name,\n ...(flags.contract !== undefined && { contractPath: flags.contract }),\n ...(flags.migrationsDir !== undefined && { migrationsDir: flags.migrationsDir }),\n });\n }\n case \"generate-contract-space\":\n return generateContractSpace({\n cwd: process.cwd(),\n ...(flags.contract !== undefined && { contractPath: flags.contract }),\n ...(flags.migrationsDir !== undefined && { migrationsDir: flags.migrationsDir }),\n ...(flags.out !== undefined && { outPath: flags.out }),\n });\n case \"preflight\": {\n const { runPreflight } = await import(\"../core/preflight\");\n return runPreflight({\n cwd: process.cwd(),\n ...(flags.migrationsDir !== undefined && { migrationsDir: flags.migrationsDir }),\n });\n }\n case undefined:\n case \"help\":\n case \"--help\":\n case \"-h\":\n printHelp();\n return 0;\n default:\n process.stderr.write(`Unknown command: ${subcommand}\\n\\n`);\n printHelp();\n return 2;\n }\n}\n\nfunction printHelp(): void {\n process.stdout.write(\n \"prisma-next-idb — IDB-target tooling for Prisma Next\\n\" +\n \"\\n\" +\n \"Usage:\\n\" +\n \" prisma-next-idb <command> [flags]\\n\" +\n \"\\n\" +\n \"Commands:\\n\" +\n \" generate-baseline Create the initial migration package from contract.json\\n\" +\n \" generate-migration Plan the next incremental migration (requires --name)\\n\" +\n \" generate-contract-space Regenerate contract-space.generated.ts\\n\" +\n \" preflight Validate the migration chain against fake-indexeddb\\n\" +\n \" help Show this message\\n\" +\n \"\\n\" +\n \"Flags (all commands):\\n\" +\n \" --contract <path> Path to contract.json (default: src/lib/prisma/contract.json)\\n\" +\n \" --migrations-dir <path> Path to migrations root (default: migrations/)\\n\" +\n \"\\n\" +\n \"Flags (generate-baseline only):\\n\" +\n \" --name <slug> Directory slug (default: baseline)\\n\" +\n \"\\n\" +\n \"Flags (generate-migration only):\\n\" +\n \" --name <slug> Directory slug, e.g. add_posts (required)\\n\" +\n \"\\n\" +\n \"Flags (generate-contract-space only):\\n\" +\n \" --out <path> Output file path (default: src/lib/prisma/contract-space.generated.ts)\\n\" +\n \"\\n\" +\n \"New-project workflow:\\n\" +\n \" 1. prisma-next contract emit # generate contract.json\\n\" +\n \" 2. prisma-next-idb generate-baseline \\\\ # create baseline migration\\n\" +\n \" --contract src/prisma/contract.json # (if your layout differs)\\n\" +\n \" 3. prisma-next-idb generate-contract-space \\\\ # bundle into contract-space.generated.ts\\n\" +\n \" --contract src/prisma/contract.json \\\\ # (same override if needed)\\n\" +\n \" --out src/prisma/contract-space.generated.ts\\n\" +\n \" 4. prisma-next-idb preflight # validate chain (CI)\\n\" +\n \"\\n\" +\n \"Adding a subsequent migration:\\n\" +\n \" 1. prisma-next contract emit # update contract.json\\n\" +\n \" 2. prisma-next-idb generate-migration --name <slug> # plan incremental migration\\n\" +\n \" 3. prisma-next-idb generate-contract-space # re-bundle contract-space\\n\" +\n \" 4. prisma-next-idb preflight # validate chain (CI)\\n\"\n );\n}\n\nmain().then(\n (code) => {\n process.exit(code);\n },\n (err: unknown) => {\n process.stderr.write(`${err instanceof Error ? (err.stack ?? err.message) : String(err)}\\n`);\n process.exit(1);\n }\n);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Contract } from "@prisma-next/contract/types";
|
|
2
|
+
import { ContractConfig, PrismaNextConfig, defineConfig as defineConfig$1 } from "@prisma-next/config/config-types";
|
|
3
|
+
|
|
4
|
+
//#region src/exports/config-types.d.ts
|
|
5
|
+
declare const defineConfig: typeof defineConfig$1;
|
|
6
|
+
/**
|
|
7
|
+
* Wraps an in-memory contract object for use in `prisma-next.config.ts`.
|
|
8
|
+
*
|
|
9
|
+
* Use this with the no-emit (TypeScript-first) workflow per ADR 006.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { typescriptContract } from '@prisma-next-idb/family-idb/config-types';
|
|
14
|
+
* import contract from './prisma/contract';
|
|
15
|
+
*
|
|
16
|
+
* export default {
|
|
17
|
+
* family: idbFamily,
|
|
18
|
+
* target: idbTarget,
|
|
19
|
+
* contract: typescriptContract(contract, 'src/prisma/contract.json'),
|
|
20
|
+
* };
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare function typescriptContract(contract: Contract, output?: string): ContractConfig;
|
|
24
|
+
/**
|
|
25
|
+
* Loads a contract from a TypeScript file at `contractPath` and wraps it for
|
|
26
|
+
* use in `prisma-next.config.ts`. The file must export the contract as
|
|
27
|
+
* `default` or `contract`.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* export default {
|
|
32
|
+
* contract: typescriptContractFromPath('./prisma/contract.ts'),
|
|
33
|
+
* };
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
declare function typescriptContractFromPath(contractPath: string, output?: string): ContractConfig;
|
|
37
|
+
//#endregion
|
|
38
|
+
export { type PrismaNextConfig, defineConfig, typescriptContract, typescriptContractFromPath };
|
|
39
|
+
//# sourceMappingURL=config-types.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-types.d.mts","names":[],"sources":["../../src/exports/config-types.ts"],"mappings":";;;;cAYa,YAAA,SAAY,cAAmB;;;;AAAA;AAyB5C;;;;;;;;;AAAuF;AAqBvF;;;iBArBgB,kBAAA,CAAmB,QAAA,EAAU,QAAA,EAAU,MAAA,YAAkB,cAAc;;;;;AAqBU;;;;;;;;iBAAjF,0BAAA,CAA2B,YAAA,UAAsB,MAAA,YAAkB,cAAc"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ok } from "@prisma-next/utils/result";
|
|
2
|
+
import { extname } from "pathe";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { defineConfig as defineConfig$1 } from "@prisma-next/config/config-types";
|
|
5
|
+
//#region src/exports/config-types.ts
|
|
6
|
+
const defineConfig = defineConfig$1;
|
|
7
|
+
function defaultOutputFromContractPath(contractPath) {
|
|
8
|
+
const ext = extname(contractPath);
|
|
9
|
+
if (ext.length === 0) return `${contractPath}.json`;
|
|
10
|
+
return `${contractPath.slice(0, -ext.length)}.json`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Wraps an in-memory contract object for use in `prisma-next.config.ts`.
|
|
14
|
+
*
|
|
15
|
+
* Use this with the no-emit (TypeScript-first) workflow per ADR 006.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { typescriptContract } from '@prisma-next-idb/family-idb/config-types';
|
|
20
|
+
* import contract from './prisma/contract';
|
|
21
|
+
*
|
|
22
|
+
* export default {
|
|
23
|
+
* family: idbFamily,
|
|
24
|
+
* target: idbTarget,
|
|
25
|
+
* contract: typescriptContract(contract, 'src/prisma/contract.json'),
|
|
26
|
+
* };
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
function typescriptContract(contract, output) {
|
|
30
|
+
return {
|
|
31
|
+
source: { load: async () => ok(contract) },
|
|
32
|
+
...output !== void 0 ? { output } : {}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Loads a contract from a TypeScript file at `contractPath` and wraps it for
|
|
37
|
+
* use in `prisma-next.config.ts`. The file must export the contract as
|
|
38
|
+
* `default` or `contract`.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* export default {
|
|
43
|
+
* contract: typescriptContractFromPath('./prisma/contract.ts'),
|
|
44
|
+
* };
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
function typescriptContractFromPath(contractPath, output) {
|
|
48
|
+
return {
|
|
49
|
+
source: {
|
|
50
|
+
inputs: [contractPath],
|
|
51
|
+
load: async (context) => {
|
|
52
|
+
const [absolutePath] = context.resolvedInputs;
|
|
53
|
+
if (absolutePath === void 0) throw new Error("typescriptContractFromPath: context.resolvedInputs is empty. The CLI config loader should populate it positional-matched with source.inputs.");
|
|
54
|
+
const mod = await import(pathToFileURL(absolutePath).href);
|
|
55
|
+
const contract = mod.default ?? mod.contract;
|
|
56
|
+
if (contract === void 0) throw new Error(`typescriptContractFromPath: module at "${absolutePath}" has no "default" or "contract" export.`);
|
|
57
|
+
return ok(contract);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
output: output ?? defaultOutputFromContractPath(contractPath)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { defineConfig, typescriptContract, typescriptContractFromPath };
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=config-types.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-types.mjs","names":["coreDefineConfig"],"sources":["../../src/exports/config-types.ts"],"sourcesContent":["// Ported from @prisma-next/sql-contract-ts/src/config-types.ts.\n// These helpers are fully family-agnostic — they wrap any Contract object\n// in a ContractSourceProvider that the prisma-next CLI config loader understands.\nimport { pathToFileURL } from \"node:url\";\nimport type { ContractConfig, PrismaNextConfig } from \"@prisma-next/config/config-types\";\nimport { defineConfig as coreDefineConfig } from \"@prisma-next/config/config-types\";\nimport type { Contract } from \"@prisma-next/contract/types\";\nimport { ok } from \"@prisma-next/utils/result\";\nimport { extname } from \"pathe\";\n\n// Re-export defineConfig so users only need @prisma-next-idb/family-idb.\nexport { type PrismaNextConfig };\nexport const defineConfig = coreDefineConfig;\n\nfunction defaultOutputFromContractPath(contractPath: string): string {\n const ext = extname(contractPath);\n if (ext.length === 0) return `${contractPath}.json`;\n return `${contractPath.slice(0, -ext.length)}.json`;\n}\n\n/**\n * Wraps an in-memory contract object for use in `prisma-next.config.ts`.\n *\n * Use this with the no-emit (TypeScript-first) workflow per ADR 006.\n *\n * @example\n * ```ts\n * import { typescriptContract } from '@prisma-next-idb/family-idb/config-types';\n * import contract from './prisma/contract';\n *\n * export default {\n * family: idbFamily,\n * target: idbTarget,\n * contract: typescriptContract(contract, 'src/prisma/contract.json'),\n * };\n * ```\n */\nexport function typescriptContract(contract: Contract, output?: string): ContractConfig {\n return {\n source: {\n load: async () => ok(contract),\n },\n ...(output !== undefined ? { output } : {}),\n };\n}\n\n/**\n * Loads a contract from a TypeScript file at `contractPath` and wraps it for\n * use in `prisma-next.config.ts`. The file must export the contract as\n * `default` or `contract`.\n *\n * @example\n * ```ts\n * export default {\n * contract: typescriptContractFromPath('./prisma/contract.ts'),\n * };\n * ```\n */\nexport function typescriptContractFromPath(contractPath: string, output?: string): ContractConfig {\n return {\n source: {\n inputs: [contractPath],\n load: async (context) => {\n const [absolutePath] = context.resolvedInputs;\n if (absolutePath === undefined) {\n throw new Error(\n \"typescriptContractFromPath: context.resolvedInputs is empty. The CLI config loader should populate it positional-matched with source.inputs.\"\n );\n }\n const mod = await import(pathToFileURL(absolutePath).href);\n const contract: Contract | undefined = mod.default ?? mod.contract;\n if (contract === undefined) {\n throw new Error(\n `typescriptContractFromPath: module at \"${absolutePath}\" has no \"default\" or \"contract\" export.`\n );\n }\n return ok(contract);\n },\n },\n output: output ?? defaultOutputFromContractPath(contractPath),\n };\n}\n"],"mappings":";;;;;AAYA,MAAa,eAAeA;AAE5B,SAAS,8BAA8B,cAA8B;CACnE,MAAM,MAAM,QAAQ,YAAY;CAChC,IAAI,IAAI,WAAW,GAAG,OAAO,GAAG,aAAa;CAC7C,OAAO,GAAG,aAAa,MAAM,GAAG,CAAC,IAAI,MAAM,EAAE;AAC/C;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,mBAAmB,UAAoB,QAAiC;CACtF,OAAO;EACL,QAAQ,EACN,MAAM,YAAY,GAAG,QAAQ,EAC/B;EACA,GAAI,WAAW,KAAA,IAAY,EAAE,OAAO,IAAI,CAAC;CAC3C;AACF;;;;;;;;;;;;;AAcA,SAAgB,2BAA2B,cAAsB,QAAiC;CAChG,OAAO;EACL,QAAQ;GACN,QAAQ,CAAC,YAAY;GACrB,MAAM,OAAO,YAAY;IACvB,MAAM,CAAC,gBAAgB,QAAQ;IAC/B,IAAI,iBAAiB,KAAA,GACnB,MAAM,IAAI,MACR,8IACF;IAEF,MAAM,MAAM,MAAM,OAAO,cAAc,YAAY,CAAC,CAAC;IACrD,MAAM,WAAiC,IAAI,WAAW,IAAI;IAC1D,IAAI,aAAa,KAAA,GACf,MAAM,IAAI,MACR,0CAA0C,aAAa,yCACzD;IAEF,OAAO,GAAG,QAAQ;GACpB;EACF;EACA,QAAQ,UAAU,8BAA8B,YAAY;CAC9D;AACF"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Contract } from "@prisma-next/contract/types";
|
|
2
|
+
import { PslDocumentAst } from "@prisma-next/framework-components/psl-ast";
|
|
3
|
+
import { Result } from "@prisma-next/utils/result";
|
|
4
|
+
import { ContractConfig, ContractSourceDiagnostics } from "@prisma-next/config/config-types";
|
|
5
|
+
import { IdbStorage } from "@prisma-next-idb/target-idb/pack";
|
|
6
|
+
|
|
7
|
+
//#region src/core/psl-interpreter.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* Interprets a parsed PSL document AST and produces an IDB `Contract`.
|
|
10
|
+
*
|
|
11
|
+
* This is the IDB equivalent of `interpretPslDocumentToSqlContract` from the
|
|
12
|
+
* SQL family. It handles IDB-specific constraints:
|
|
13
|
+
*
|
|
14
|
+
* - No namespace blocks (IDB has a single implicit `__unbound__` namespace)
|
|
15
|
+
* - No compound primary keys (IDB `keyPath` must be a single field)
|
|
16
|
+
* - Relations are FK-side (`@relation`) + backrelation list fields
|
|
17
|
+
* - Indexes map directly to `IDBObjectStore.createIndex()` calls
|
|
18
|
+
*/
|
|
19
|
+
declare function interpretPslDocumentToIdbContract(ast: PslDocumentAst, sourceId: string): Result<Contract<IdbStorage>, ContractSourceDiagnostics>;
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/core/psl-provider.d.ts
|
|
22
|
+
interface PrismaIdbContractOptions {
|
|
23
|
+
readonly output?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Creates a `ContractConfig` that reads an IDB schema from a `.prisma` file.
|
|
27
|
+
*
|
|
28
|
+
* Use this in `prisma-next.config.ts` as the `contract:` value when you prefer
|
|
29
|
+
* PSL authoring over the TypeScript-first `defineContract()` helper.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { defineConfig } from '@prisma-next-idb/family-idb/config-types';
|
|
34
|
+
* import { prismaIdbContract } from '@prisma-next-idb/family-idb/contract-psl';
|
|
35
|
+
*
|
|
36
|
+
* export default defineConfig({
|
|
37
|
+
* // ...
|
|
38
|
+
* contract: prismaIdbContract('./src/prisma/schema.prisma'),
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* The emitted `contract.json` lands next to the schema file by default
|
|
43
|
+
* (`schema.prisma` → `contract.json`). Override with `options.output`.
|
|
44
|
+
*/
|
|
45
|
+
declare function prismaIdbContract(schemaPath: string, options?: PrismaIdbContractOptions): ContractConfig;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { type PrismaIdbContractOptions, interpretPslDocumentToIdbContract, prismaIdbContract };
|
|
48
|
+
//# sourceMappingURL=contract-psl.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-psl.d.mts","names":[],"sources":["../../src/core/psl-interpreter.ts","../../src/core/psl-provider.ts"],"mappings":";;;;;;;;;AAiXA;;;;;;;;;iBAAgB,iCAAA,CACd,GAAA,EAAK,cAAA,EACL,QAAA,WACC,MAAA,CAAO,QAAA,CAAS,UAAA,GAAa,yBAAA;;;UCzVf,wBAAA;EAAA,SACN,MAAM;AAAA;;;;ADqVjB;;;;;;;;;;;;;;;;;iBC9TgB,iBAAA,CAAkB,UAAA,UAAoB,OAAA,GAAU,wBAAA,GAA2B,cAAc"}
|