@thomasfosterau/effect-drizzle-ext-pg 0.0.0 → 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/README.md +442 -2
- package/dist/index.d.ts +159 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +109 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations-Dp67FrXh.d.ts +68 -0
- package/dist/migrations-Dp67FrXh.d.ts.map +1 -0
- package/dist/migrations.d.ts +2 -0
- package/dist/migrations.js +136 -0
- package/dist/migrations.js.map +1 -0
- package/dist/testing.d.ts +270 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +408 -0
- package/dist/testing.js.map +1 -0
- package/dist/tooling/index.d.ts +217 -0
- package/dist/tooling/index.d.ts.map +1 -0
- package/dist/tooling/index.js +479 -0
- package/dist/tooling/index.js.map +1 -0
- package/package.json +76 -4
- package/src/index.ts +231 -0
- package/src/migrations.ts +173 -0
- package/src/provisioning.ts +407 -0
- package/src/testing.ts +447 -0
- package/src/tooling/index.ts +46 -0
- package/src/tooling/migration-drift.ts +640 -0
- package/src/tooling/view-dependencies.ts +168 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Arr from "effect/Array";
|
|
3
|
+
import * as FileSystem from "effect/FileSystem";
|
|
4
|
+
import * as Order from "effect/Order";
|
|
5
|
+
import * as Path from "effect/Path";
|
|
6
|
+
import * as Schema from "effect/Schema";
|
|
7
|
+
import * as HashSet from "effect/HashSet";
|
|
8
|
+
import * as Predicate from "effect/Predicate";
|
|
9
|
+
import * as R from "effect/Record";
|
|
10
|
+
import { generateDrizzleJson, generateMigration, reconstruct } from "drizzle-kit/api-postgres";
|
|
11
|
+
import * as Option from "effect/Option";
|
|
12
|
+
import { ChildProcess } from "effect/process";
|
|
13
|
+
import { ChildProcessSpawner } from "effect/process/ChildProcessSpawner";
|
|
14
|
+
// src/tooling/migration-drift.ts
|
|
15
|
+
/**
|
|
16
|
+
* @copyright Thomas Foster
|
|
17
|
+
*
|
|
18
|
+
* **The migration-drift gate**: fails when a committed Drizzle schema and its
|
|
19
|
+
* committed drizzle-kit `migrations/` snapshots disagree, without a database
|
|
20
|
+
* or a deployed stage. Ported from the applications'
|
|
21
|
+
* `packages/database/scripts/check-migration-drift.ts` (website #1200,
|
|
22
|
+
* #1471; preferenceflow's copy is the reference), as a bin-less Effect an
|
|
23
|
+
* application's `db:migration-drift` task runs with its own schema module.
|
|
24
|
+
*
|
|
25
|
+
* Three checks, each guarding "snapshot history is a DAG, not a chain" from a
|
|
26
|
+
* different side:
|
|
27
|
+
*
|
|
28
|
+
* 1. **DAG integrity.** drizzle-kit 1.0's snapshots chain via `prevIds`
|
|
29
|
+
* (plural — a migration generated after two branches merge names both
|
|
30
|
+
* tips), so history is a DAG. Divergence is normal while unmerged; it is
|
|
31
|
+
* broken when two branches both land without a later migration's
|
|
32
|
+
* `prevIds` reconverging them. Every snapshot id must be unique, every
|
|
33
|
+
* `prevIds` entry must resolve, and the lineage must have exactly one head
|
|
34
|
+
* from which everything is reachable, with no cycle.
|
|
35
|
+
* 2. **Zero drift.** The exact drizzle-kit calls a deploy-time regeneration
|
|
36
|
+
* makes (`generateDrizzleJson` / `generateMigration` from
|
|
37
|
+
* `drizzle-kit/api-postgres`) against the schema module and the
|
|
38
|
+
* **lexicographically-latest** migration directory's snapshot — the same
|
|
39
|
+
* "latest by directory name" selection alchemy's
|
|
40
|
+
* `Drizzle.Schema.regenerate` uses, NOT a DAG-aware pick — must produce
|
|
41
|
+
* zero statements. A diff drizzle-kit can only resolve interactively
|
|
42
|
+
* (rename-or-recreate) is drift too.
|
|
43
|
+
* 3. **Base ancestry (pull requests only).** A migration must be generated on
|
|
44
|
+
* a checkout whose snapshot chain descends from the base branch's CURRENT
|
|
45
|
+
* head; checks 1–2 are internal to the checkout, so a branch forked from
|
|
46
|
+
* week-old `trunk` passes both while carrying a migration whose regenerated
|
|
47
|
+
* constraints silently omit everything the base added since. Runs only
|
|
48
|
+
* when a base is supplied ({@link resolveBaseRef} reads one from
|
|
49
|
+
* `MIGRATION_BASE_REF` / `GITHUB_BASE_REF`), and only when the branch adds
|
|
50
|
+
* migration directories.
|
|
51
|
+
*
|
|
52
|
+
* Zero drift runs only on a structurally sound DAG (and a base-descended
|
|
53
|
+
* one): with two heads the latest-by-name snapshot is an arbitrary pick of
|
|
54
|
+
* one fork, so its diff would be noise next to the real report.
|
|
55
|
+
*
|
|
56
|
+
* @module tooling/migration-drift
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* drizzle-kit's sentinel for "no real parent" (its `originUUID`): the genesis
|
|
60
|
+
* migration's `prevIds` names this instead of being empty.
|
|
61
|
+
*/
|
|
62
|
+
const ROOT_SENTINEL_ID = "00000000-0000-0000-0000-000000000000";
|
|
63
|
+
const SnapshotIdentity = Schema.Struct({
|
|
64
|
+
id: Schema.String,
|
|
65
|
+
prevIds: Schema.optionalKey(Schema.Array(Schema.String))
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* A **full** (version `"8"`) snapshot, which earlier drizzle-kit 1.0
|
|
69
|
+
* prereleases wrote: the members the zero-drift diff relies on beyond the
|
|
70
|
+
* identity, checked, not decoded, since the snapshot is handed to
|
|
71
|
+
* `generateMigration` unchanged.
|
|
72
|
+
*/
|
|
73
|
+
const PostgresSnapshotContract = Schema.Struct({
|
|
74
|
+
version: Schema.Literal("8"),
|
|
75
|
+
dialect: Schema.Literal("postgres"),
|
|
76
|
+
ddl: Schema.Array(Schema.Unknown)
|
|
77
|
+
});
|
|
78
|
+
const isPostgresSnapshot = (raw) => Schema.is(PostgresSnapshotContract)(raw);
|
|
79
|
+
/**
|
|
80
|
+
* A version `"9"` snapshot, which drizzle-kit 1.0.0-rc.5 writes (and
|
|
81
|
+
* `drizzle-kit up` upgrades a v8 chain to): a `base` or a `diff` over its
|
|
82
|
+
* parents, so the state at one is rebuilt from the whole chain with
|
|
83
|
+
* drizzle-kit's own `reconstruct`.
|
|
84
|
+
*/
|
|
85
|
+
const ChainSnapshotContract = Schema.Struct({
|
|
86
|
+
version: Schema.Literal("9"),
|
|
87
|
+
dialect: Schema.Literal("postgres"),
|
|
88
|
+
type: Schema.Literals(["base", "diff"])
|
|
89
|
+
});
|
|
90
|
+
const isChainSnapshot = Schema.is(ChainSnapshotContract);
|
|
91
|
+
/** A snapshot.json could not be read as a drizzle-kit snapshot. */
|
|
92
|
+
var MalformedSnapshot = class extends Schema.TaggedError()("MalformedSnapshot", {
|
|
93
|
+
file: Schema.String,
|
|
94
|
+
cause: Schema.Defect()
|
|
95
|
+
}) {
|
|
96
|
+
get message() {
|
|
97
|
+
return `${this.file} is not a drizzle-kit snapshot (malformed JSON, or no string \`id\` / string[] \`prevIds\`) — never hand-edit migrations/.`;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* drizzle-kit itself failed while generating or diffing a snapshot — broken
|
|
102
|
+
* input (a schema module drizzle-kit cannot read) rather than drift.
|
|
103
|
+
*/
|
|
104
|
+
var SchemaDiffError = class extends Schema.TaggedError()("SchemaDiffError", { cause: Schema.Defect() }) {
|
|
105
|
+
get message() {
|
|
106
|
+
return "drizzle-kit failed to generate or diff the schema snapshot";
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
/** Run one drizzle-kit API call, its rejection a {@link SchemaDiffError}. */
|
|
110
|
+
const drizzleKit = (run) => Effect.tryPromise({
|
|
111
|
+
try: run,
|
|
112
|
+
catch: (cause) => new SchemaDiffError({ cause })
|
|
113
|
+
});
|
|
114
|
+
/** The gate found problems; `issues` holds one human-readable report per violation. */
|
|
115
|
+
var MigrationDriftError = class extends Schema.TaggedError()("MigrationDriftError", { issues: Schema.Array(Schema.String) }) {
|
|
116
|
+
get message() {
|
|
117
|
+
return `Migration snapshot drift check failed (${this.issues.length} issue(s)):\n\n${this.issues.join("\n\n")}`;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
/** Snapshots not named in any other snapshot's `prevIds` — the DAG's tips. */
|
|
121
|
+
const findHeads = (entries) => {
|
|
122
|
+
const referenced = HashSet.fromIterable(entries.flatMap((entry) => entry.snapshot.prevIds));
|
|
123
|
+
return entries.filter((entry) => !HashSet.has(referenced, entry.snapshot.id));
|
|
124
|
+
};
|
|
125
|
+
const findDuplicateIds = (entries) => entries.filter((entry, index) => entries.findIndex((other) => other.snapshot.id === entry.snapshot.id) !== index);
|
|
126
|
+
const findDanglingParents = (entries) => {
|
|
127
|
+
const ids = HashSet.fromIterable(entries.map((entry) => entry.snapshot.id));
|
|
128
|
+
return entries.flatMap((entry) => entry.snapshot.prevIds.filter((prevId) => prevId !== "00000000-0000-0000-0000-000000000000" && !HashSet.has(ids, prevId)).map((prevId) => ({
|
|
129
|
+
dir: entry.dir,
|
|
130
|
+
prevId
|
|
131
|
+
})));
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* DFS from `starts` backward through `prevIds`, three-state (visiting /
|
|
135
|
+
* done) so a back-edge (a cycle) is told apart from ordinary convergence.
|
|
136
|
+
* Returns everything reached and the first cycle found, as directory labels.
|
|
137
|
+
*/
|
|
138
|
+
const traverse = (starts, entries) => {
|
|
139
|
+
const byId = new Map(entries.map((entry) => [entry.snapshot.id, entry]));
|
|
140
|
+
const state = /* @__PURE__ */ new Map();
|
|
141
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
142
|
+
const label = (id) => byId.get(id)?.dir ?? id;
|
|
143
|
+
const visit = (id, path) => {
|
|
144
|
+
if (state.get(id) === "visiting") return Option.some([...path, label(id)]);
|
|
145
|
+
if (state.get(id) === "done") return Option.none();
|
|
146
|
+
state.set(id, "visiting");
|
|
147
|
+
reachable.add(id);
|
|
148
|
+
const next = [...path, label(id)];
|
|
149
|
+
for (const prevId of byId.get(id)?.snapshot.prevIds ?? []) {
|
|
150
|
+
const cycle = visit(prevId, next);
|
|
151
|
+
if (Option.isSome(cycle)) return cycle;
|
|
152
|
+
}
|
|
153
|
+
state.set(id, "done");
|
|
154
|
+
return Option.none();
|
|
155
|
+
};
|
|
156
|
+
for (const start of starts) {
|
|
157
|
+
const cycle = visit(start.snapshot.id, []);
|
|
158
|
+
if (Option.isSome(cycle)) return {
|
|
159
|
+
reachable,
|
|
160
|
+
cycle
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
reachable,
|
|
165
|
+
cycle: Option.none()
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Every snapshot id is unique, every `prevIds` reference resolves (or is
|
|
170
|
+
* {@link ROOT_SENTINEL_ID}), the lineage has exactly one head and no cycle,
|
|
171
|
+
* and every snapshot is reachable from that head. One human-readable report
|
|
172
|
+
* per violation; empty when clean.
|
|
173
|
+
*/
|
|
174
|
+
const checkDagIntegrity = (entries) => {
|
|
175
|
+
const issues = [];
|
|
176
|
+
const duplicates = findDuplicateIds(entries);
|
|
177
|
+
if (Arr.isReadonlyArrayNonEmpty(duplicates)) issues.push(`${duplicates.length} migration snapshot(s) share an \`id\` with an earlier snapshot — every snapshot must have a unique id: ${duplicates.map((d) => d.dir).join(", ")}.\n\`drizzle-kit generate\` never produces this; check for a copied or hand-edited snapshot.json.`);
|
|
178
|
+
const dangling = findDanglingParents(entries);
|
|
179
|
+
if (Arr.isReadonlyArrayNonEmpty(dangling)) issues.push(`${dangling.length} \`prevIds\` entry(ies) name a parent snapshot id that no committed snapshot has: ${dangling.map((d) => `${d.dir} -> ${d.prevId}`).join(", ")}.\nThe migrations directory may be missing a file, or a snapshot.json was hand-edited.`);
|
|
180
|
+
const heads = findHeads(entries);
|
|
181
|
+
if (heads.length !== 1) issues.push(`Expected exactly 1 head in the migration snapshot DAG, found ${heads.length}` + (Arr.isReadonlyArrayNonEmpty(heads) ? `: ${heads.map((h) => h.dir).join(", ")}` : "") + ".\n" + (Arr.isReadonlyArrayNonEmpty(heads) ? "Two or more migrations were generated from the same parent snapshot and both merged without a later migration's `prevIds` reconverging them (a snapshot-chain fork). Fix: rebase and regenerate, so the new migration's `prevIds` lists every outstanding head." : "Every snapshot is named as some other snapshot's parent: the lineage has no real root (see any reported cycle)."));
|
|
182
|
+
const { reachable } = traverse(heads, entries);
|
|
183
|
+
const { cycle } = traverse(entries, entries);
|
|
184
|
+
if (Option.isSome(cycle)) issues.push(`The migration snapshot DAG contains a cycle in its \`prevIds\` lineage: ${cycle.value.join(" -> ")}.\nHistory can never legitimately cycle back to an ancestor — this means corrupted or hand-edited snapshot.json files.`);
|
|
185
|
+
const orphaned = entries.filter((entry) => !reachable.has(entry.snapshot.id));
|
|
186
|
+
if (Arr.isReadonlyArrayNonEmpty(orphaned)) issues.push(`${orphaned.length} migration snapshot(s) are not reachable from the DAG head(s) via \`prevIds\`: ${orphaned.map((o) => o.dir).join(", ")}.\nA deploy that regenerates against the lexicographically-latest snapshot would silently drop these branches' schema changes.`);
|
|
187
|
+
return issues;
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Every migration this branch ADDS must descend from the base's current
|
|
191
|
+
* head. Presence of the base head's snapshot id in this checkout's entries is
|
|
192
|
+
* sufficient: {@link checkDagIntegrity} separately proves the local DAG has a
|
|
193
|
+
* single head from which everything is reachable, so containing the base head
|
|
194
|
+
* means the new tail grew on top of it. A branch that adds no migration
|
|
195
|
+
* directory passes.
|
|
196
|
+
*/
|
|
197
|
+
const checkBaseAncestry = (entries, base) => {
|
|
198
|
+
const baseDirs = HashSet.fromIterable(base.dirs);
|
|
199
|
+
const added = entries.filter((entry) => !HashSet.has(baseDirs, entry.dir));
|
|
200
|
+
if (Arr.isReadonlyArrayEmpty(added) || Option.isNone(base.head)) return [];
|
|
201
|
+
const head = base.head.value;
|
|
202
|
+
if (entries.some((entry) => entry.snapshot.id === head.snapshot.id)) return [];
|
|
203
|
+
return [`New migration(s) ${added.map((entry) => entry.dir).join(", ")} were generated on a checkout that predates ${base.ref}'s current migration head (${head.dir}, snapshot id ${head.snapshot.id}) — the base head is not an ancestor of this branch's snapshot chain, so the generated DDL was computed from a stale schema (a regenerated CHECK constraint silently reverting members the base added since the fork). Fix: rebase onto ${base.ref}, delete the stale migration directory, and regenerate it.`];
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* The base ref for the ancestry check, when one is knowable: an explicit
|
|
207
|
+
* `MIGRATION_BASE_REF` wins (local use: `MIGRATION_BASE_REF=origin/trunk`),
|
|
208
|
+
* else `origin/$GITHUB_BASE_REF` — which GitHub Actions sets only on
|
|
209
|
+
* `pull_request` events, so pushes and local runs skip the check.
|
|
210
|
+
*/
|
|
211
|
+
const resolveBaseRef = (env) => {
|
|
212
|
+
const explicit = env.MIGRATION_BASE_REF ?? "";
|
|
213
|
+
if (explicit !== "") return Option.some(explicit);
|
|
214
|
+
const base = env.GITHUB_BASE_REF ?? "";
|
|
215
|
+
return base === "" ? Option.none() : Option.some(`origin/${base}`);
|
|
216
|
+
};
|
|
217
|
+
/** A migration directory name: drizzle-kit's all-digit prefix and a slug. */
|
|
218
|
+
const MIGRATION_DIR = /^\d+_/;
|
|
219
|
+
const parseSnapshot = (file, text) => Schema.decodeEffect(Schema.fromJsonString(Schema.Unknown))(text).pipe(Effect.filterOrFail(Schema.is(SnapshotIdentity), (raw) => raw), Effect.mapError((cause) => new MalformedSnapshot({
|
|
220
|
+
file,
|
|
221
|
+
cause
|
|
222
|
+
})));
|
|
223
|
+
const toEntry = (dir, raw) => ({
|
|
224
|
+
dir,
|
|
225
|
+
raw,
|
|
226
|
+
snapshot: {
|
|
227
|
+
id: raw.id,
|
|
228
|
+
prevIds: raw.prevIds ?? []
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
/**
|
|
232
|
+
* Every migration directory under `migrationsDir` (sorted by name) and the
|
|
233
|
+
* snapshot of each that has one. A directory without a snapshot (a
|
|
234
|
+
* hand-written bootstrap) is listed but carries no entry — exactly like
|
|
235
|
+
* alchemy's `readLatestSnapshot`.
|
|
236
|
+
*/
|
|
237
|
+
const readLocal = (migrationsDir) => Effect.gen(function* () {
|
|
238
|
+
const fs = yield* FileSystem.FileSystem;
|
|
239
|
+
const path = yield* Path.Path;
|
|
240
|
+
const names = Arr.sort((yield* fs.readDirectory(migrationsDir)).filter((name) => MIGRATION_DIR.test(name)), Order.String);
|
|
241
|
+
const dirs = yield* Effect.filter(names, (name) => Effect.map(fs.stat(path.join(migrationsDir, name)), (info) => info.type === "Directory"));
|
|
242
|
+
return {
|
|
243
|
+
dirs,
|
|
244
|
+
entries: (yield* Effect.forEach(dirs, (dir) => Effect.gen(function* () {
|
|
245
|
+
const file = path.join(migrationsDir, dir, "snapshot.json");
|
|
246
|
+
if (!(yield* fs.exists(file))) return [];
|
|
247
|
+
const raw = yield* parseSnapshot(file, yield* fs.readFileString(file));
|
|
248
|
+
return [toEntry(dir, raw)];
|
|
249
|
+
}), { concurrency: 1 })).flat()
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
const git = (base, args) => Effect.gen(function* () {
|
|
253
|
+
return yield* (yield* ChildProcessSpawner).string(ChildProcess.make("git", [...args], { cwd: base.cwd }));
|
|
254
|
+
});
|
|
255
|
+
/**
|
|
256
|
+
* The latest-by-name base directory carrying a readable snapshot — the same
|
|
257
|
+
* selection the zero-drift check makes locally. A directory without one (a
|
|
258
|
+
* hand-written bootstrap) is passed over.
|
|
259
|
+
*/
|
|
260
|
+
const findBaseHead = (base, dirs) => Effect.findFirstFilter(dirs.toReversed(), (dir) => Effect.result(Effect.flatMap(git(base, ["show", `${base.ref}:${base.migrationsPath}/${dir}/snapshot.json`]), (text) => Effect.map(parseSnapshot(dir, text), (raw) => toEntry(dir, raw)))));
|
|
261
|
+
/**
|
|
262
|
+
* The base branch's migration state, read with `git ls-tree` / `git show` so
|
|
263
|
+
* it reflects the base's CURRENT tip. Fetches the base first when the
|
|
264
|
+
* checkout lacks it (a shallow pull-request checkout does).
|
|
265
|
+
*/
|
|
266
|
+
const readBaseMigrations = (base) => Effect.gen(function* () {
|
|
267
|
+
if ((yield* (yield* ChildProcessSpawner).exitCode(ChildProcess.make("git", [
|
|
268
|
+
"rev-parse",
|
|
269
|
+
"--verify",
|
|
270
|
+
"--quiet",
|
|
271
|
+
`${base.ref}^{commit}`
|
|
272
|
+
], { cwd: base.cwd }))) !== 0) yield* git(base, [
|
|
273
|
+
"fetch",
|
|
274
|
+
"--depth=1",
|
|
275
|
+
"origin",
|
|
276
|
+
base.ref.replace(/^origin\//, "")
|
|
277
|
+
]);
|
|
278
|
+
const listing = yield* git(base, [
|
|
279
|
+
"ls-tree",
|
|
280
|
+
"--name-only",
|
|
281
|
+
base.ref,
|
|
282
|
+
`${base.migrationsPath}/`
|
|
283
|
+
]);
|
|
284
|
+
const dirs = Arr.sort(listing.split("\n").map((line) => line.trim().split("/").at(-1) ?? "").filter((name) => MIGRATION_DIR.test(name)), Order.String);
|
|
285
|
+
return {
|
|
286
|
+
ref: base.ref,
|
|
287
|
+
dirs,
|
|
288
|
+
head: yield* findBaseHead(base, dirs)
|
|
289
|
+
};
|
|
290
|
+
});
|
|
291
|
+
/**
|
|
292
|
+
* drizzle-kit's resolvers throw `… was called without a HintsHandler` when
|
|
293
|
+
* the diff holds a created+deleted pair it would interactively ask about
|
|
294
|
+
* (rename or recreate). That is proof of a non-empty diff, reported as drift;
|
|
295
|
+
* every other failure is broken input and stays a defect.
|
|
296
|
+
*/
|
|
297
|
+
const isAmbiguousDiff = (cause) => Predicate.isError(cause) && cause.message.includes("was called without a HintsHandler");
|
|
298
|
+
const checkZeroDrift = (schema, entries, latest, label) => Effect.gen(function* () {
|
|
299
|
+
const where = Option.match(latest, {
|
|
300
|
+
onNone: () => `${label} (no snapshot yet)`,
|
|
301
|
+
onSome: (entry) => `${label}/${entry.dir}/snapshot.json`
|
|
302
|
+
});
|
|
303
|
+
const stateOf = (entry) => isPostgresSnapshot(entry.raw) ? Option.some(Effect.succeed(entry.raw)) : isChainSnapshot(entry.raw) ? Option.some(drizzleKit(() => reconstruct(R.fromEntries(Arr.map(entries, (each) => [each.dir, each.raw])), entry.snapshot.id))) : Option.none();
|
|
304
|
+
const prevState = Option.match(latest, {
|
|
305
|
+
onNone: () => Option.some(drizzleKit(() => generateDrizzleJson({}))),
|
|
306
|
+
onSome: stateOf
|
|
307
|
+
});
|
|
308
|
+
if (Option.isNone(prevState)) return [`${where} doesn't match a drizzle-kit snapshot contract \`generateMigration\` reads (\`version: "8"\` with a \`ddl\` array, or \`version: "9"\` with \`type\` \`base\` / \`diff\`; \`dialect: "postgres"\`): a stale (pre-v8) or hand-edited snapshot.`];
|
|
309
|
+
const cur = yield* drizzleKit(() => generateDrizzleJson(schema, Option.getOrUndefined(Option.map(latest, (entry) => entry.snapshot.id))));
|
|
310
|
+
const prev = yield* prevState.value;
|
|
311
|
+
return yield* drizzleKit(() => generateMigration(prev, cur)).pipe(Effect.map((statements) => Arr.isReadonlyArrayNonEmpty(statements) ? [`Schema drift detected against the latest committed migration snapshot (${where}): ${statements.length} statement(s) would be generated:\n\n` + statements.map((statement) => ` ${statement}`).join("\n") + "\n\nFix: generate a migration (`drizzle-kit generate --name <slug>`) and commit it with its snapshot."] : []), Effect.catchTag("SchemaDiffError", ({ cause }) => isAmbiguousDiff(cause) ? Effect.succeed([`Schema drift detected against the latest committed migration snapshot (${where}), and the diff is ambiguous: drizzle-kit found a created+deleted pair it would interactively ask to resolve as rename-or-recreate. An ambiguous diff is still a non-empty diff.
|
|
312
|
+
Fix: generate a migration (\`drizzle-kit generate --name <slug>\`) and commit it with its snapshot.`]) : Effect.fail(new SchemaDiffError({ cause }))));
|
|
313
|
+
});
|
|
314
|
+
/**
|
|
315
|
+
* Run the three checks. Succeeds with a {@link MigrationDriftReport};
|
|
316
|
+
* fails with a {@link MigrationDriftError} carrying every issue found.
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* ```ts
|
|
320
|
+
* // An application's `db:migration-drift` task, run with `NodeRuntime.runMain`.
|
|
321
|
+
* import * as NodeServices from "@effect/platform-node/NodeServices";
|
|
322
|
+
* import { checkMigrationDrift, resolveBaseRef } from "@thomasfosterau/effect-drizzle-ext-pg/tooling";
|
|
323
|
+
* import { integer, pgTable } from "drizzle-orm/pg-core";
|
|
324
|
+
* import * as Effect from "effect/Effect";
|
|
325
|
+
* import * as Option from "effect/Option";
|
|
326
|
+
* import * as path from "node:path";
|
|
327
|
+
*
|
|
328
|
+
* const schema = { widget: pgTable("widget", { id: integer().primaryKey() }) };
|
|
329
|
+
* const repoRoot = process.cwd();
|
|
330
|
+
* const base = Option.map(resolveBaseRef(process.env), (ref) => ({
|
|
331
|
+
* ref,
|
|
332
|
+
* migrationsPath: "packages/database/migrations",
|
|
333
|
+
* cwd: repoRoot,
|
|
334
|
+
* }));
|
|
335
|
+
* export const program = checkMigrationDrift({
|
|
336
|
+
* schema,
|
|
337
|
+
* migrationsDir: path.join(repoRoot, "packages/database/migrations"),
|
|
338
|
+
* base,
|
|
339
|
+
* }).pipe(Effect.provide(NodeServices.layer));
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
const checkMigrationDrift = (options) => Effect.gen(function* () {
|
|
343
|
+
const { dirs, entries } = yield* readLocal(options.migrationsDir);
|
|
344
|
+
const base = yield* Effect.transposeOption(Option.map(options.base ?? Option.none(), readBaseMigrations));
|
|
345
|
+
const structural = [...checkDagIntegrity(entries), ...Option.match(base, {
|
|
346
|
+
onNone: () => [],
|
|
347
|
+
onSome: (b) => checkBaseAncestry(entries, b)
|
|
348
|
+
})];
|
|
349
|
+
const latest = Arr.findFirst(entries.toReversed(), (entry) => dirs.includes(entry.dir));
|
|
350
|
+
const issues = Arr.isReadonlyArrayNonEmpty(structural) ? structural : yield* checkZeroDrift(options.schema, entries, latest, options.migrationsDir);
|
|
351
|
+
if (Arr.isReadonlyArrayNonEmpty(issues)) return yield* new MigrationDriftError({ issues });
|
|
352
|
+
return {
|
|
353
|
+
snapshots: entries.length,
|
|
354
|
+
baseRef: Option.map(base, (b) => b.ref)
|
|
355
|
+
};
|
|
356
|
+
});
|
|
357
|
+
// src/tooling/view-dependencies.ts
|
|
358
|
+
/**
|
|
359
|
+
* @copyright Thomas Foster
|
|
360
|
+
*
|
|
361
|
+
* **The view-on-view gate** (`DB-VIEW-001`, website #1228): fails when any
|
|
362
|
+
* database view in a Drizzle schema selects from another database view.
|
|
363
|
+
* Ported from the website and gardeneria applications'
|
|
364
|
+
* `packages/database/scripts/check-view-dependencies.ts`.
|
|
365
|
+
*
|
|
366
|
+
* drizzle-kit derives migrations from a schema diff, and for views that diff
|
|
367
|
+
* is only ever `DROP VIEW` + `CREATE VIEW`, emitted as two flat blocks in one
|
|
368
|
+
* shared order with **no topological sort** and **no cascade**. A
|
|
369
|
+
* view-on-view edge is then unmigratable two ways:
|
|
370
|
+
*
|
|
371
|
+
* 1. **Both ends change.** The drops come out in the create order, so a drop
|
|
372
|
+
* runs dependency-first and PostgreSQL rejects it.
|
|
373
|
+
* 2. **Only the dependency changes.** The dependent view is not in the diff
|
|
374
|
+
* at all, the dependency's `DROP VIEW` still fails, and no reordering of
|
|
375
|
+
* the emitted statements can help: the one that would have to run first
|
|
376
|
+
* was never emitted.
|
|
377
|
+
*
|
|
378
|
+
* The only remedy once such a migration exists is hand-editing it, so the
|
|
379
|
+
* constraint is pushed up into the schema: **no view may reference another
|
|
380
|
+
* view**. The graph is read from drizzle-kit's own DDL — the whole schema
|
|
381
|
+
* generated from an empty snapshot, one `CREATE VIEW` per view carrying the
|
|
382
|
+
* exact SQL a migration would — so the gate needs no database.
|
|
383
|
+
*
|
|
384
|
+
* @module tooling/view-dependencies
|
|
385
|
+
*/
|
|
386
|
+
/**
|
|
387
|
+
* `CREATE [MATERIALIZED] VIEW "<schema>"."<name>"` — drizzle-kit omits the
|
|
388
|
+
* schema qualifier for `public`, so the schema group is optional.
|
|
389
|
+
*/
|
|
390
|
+
const CREATE_VIEW = /^CREATE\s+(?:MATERIALIZED\s+)?VIEW\s+(?:"([^"]+)"\.)?"([^"]+)"/i;
|
|
391
|
+
/** `schema.name`, the identity used in messages and in reference matching. */
|
|
392
|
+
const viewKey = (view) => `${view.schema}.${view.name}`;
|
|
393
|
+
/** Every `CREATE VIEW` in a statement list, as {@link ViewDefinition}s. */
|
|
394
|
+
const parseViewDefinitions = (statements) => statements.flatMap((statement) => {
|
|
395
|
+
const match = CREATE_VIEW.exec(statement.trim());
|
|
396
|
+
const name = match?.[2];
|
|
397
|
+
return name === void 0 ? [] : [{
|
|
398
|
+
schema: match?.[1] ?? "public",
|
|
399
|
+
name,
|
|
400
|
+
sql: statement
|
|
401
|
+
}];
|
|
402
|
+
});
|
|
403
|
+
/**
|
|
404
|
+
* Names come out of quoted identifiers, so they may contain regex
|
|
405
|
+
* metacharacters; escape them rather than let one change the pattern.
|
|
406
|
+
*/
|
|
407
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
408
|
+
/**
|
|
409
|
+
* Whether `definition` selects from `target`.
|
|
410
|
+
*
|
|
411
|
+
* Matches both spellings a definition can carry: the quoted,
|
|
412
|
+
* schema-qualified form Drizzle's query builder emits (`"app"."work_view"`),
|
|
413
|
+
* and the bare form a hand-written `sql.raw` FROM clause may use
|
|
414
|
+
* (`app.work_view`), guarded so it cannot fire on a longer identifier ending
|
|
415
|
+
* the same way. A derived table aliased with a view's *unqualified* name is
|
|
416
|
+
* deliberately not a match — it names no schema, so references no view.
|
|
417
|
+
* The one blind spot that leaves: a hand-written FROM naming a view
|
|
418
|
+
* unqualified and leaning on `search_path`.
|
|
419
|
+
*/
|
|
420
|
+
const referencesView = (definition, target) => {
|
|
421
|
+
if (viewKey(definition) === viewKey(target)) return false;
|
|
422
|
+
if (definition.sql.includes(`"${target.schema}"."${target.name}"`)) return true;
|
|
423
|
+
const qualified = `${escapeRegExp(target.schema)}\\.${escapeRegExp(target.name)}`;
|
|
424
|
+
return new RegExp(`(^|[^\\w."])${qualified}\\b`).test(definition.sql);
|
|
425
|
+
};
|
|
426
|
+
/** Every view-on-view edge among `views`. Empty is the passing state. */
|
|
427
|
+
const findViewEdges = (views) => views.flatMap((from) => views.filter((to) => referencesView(from, to)).map((to) => ({
|
|
428
|
+
from,
|
|
429
|
+
to
|
|
430
|
+
})));
|
|
431
|
+
/** The schema has at least one view-on-view edge. */
|
|
432
|
+
var ViewDependencyError = class extends Schema.TaggedError()("ViewDependencyError", { edges: Schema.Array(Schema.Struct({
|
|
433
|
+
from: Schema.String,
|
|
434
|
+
to: Schema.String
|
|
435
|
+
})) }) {
|
|
436
|
+
get message() {
|
|
437
|
+
const grouped = Arr.groupBy(this.edges, (edge) => edge.from);
|
|
438
|
+
const listed = Arr.sort(R.toEntries(grouped).map(([from, group]) => ` ${from} selects from ${Arr.sort(group.map((edge) => edge.to), Order.String).join(", ")}`), Order.String).join("\n");
|
|
439
|
+
return `${this.edges.length} view-on-view dependency(ies) in the schema:\n\n${listed}\n\ndrizzle-kit emits DROP VIEW / CREATE VIEW with no topological ordering and no cascade to unchanged dependents, so a migration touching either end of one of these edges produces a drop order PostgreSQL rejects, fixable only by hand-editing the migration.
|
|
440
|
+
Fix: resolve the dependency against the underlying tables instead of the sibling view.`;
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
/**
|
|
444
|
+
* Generate the whole schema from an empty snapshot — every view exactly as a
|
|
445
|
+
* migration would create it — and assert no definition references another
|
|
446
|
+
* view. Succeeds with the number of views checked.
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* ```ts
|
|
450
|
+
* // An application's `db:view-dependencies` task passes its own schema module.
|
|
451
|
+
* import { checkViewDependencies } from "@thomasfosterau/effect-drizzle-ext-pg/tooling";
|
|
452
|
+
* import { integer, pgTable, pgView, text } from "drizzle-orm/pg-core";
|
|
453
|
+
* import * as Effect from "effect/Effect";
|
|
454
|
+
*
|
|
455
|
+
* const article = pgTable("article", { id: integer().primaryKey(), title: text().notNull() });
|
|
456
|
+
* const articleTitles = pgView("article_titles").as((qb) =>
|
|
457
|
+
* qb.select({ title: article.title }).from(article),
|
|
458
|
+
* );
|
|
459
|
+
*
|
|
460
|
+
* const { viewCount } = await Effect.runPromise(
|
|
461
|
+
* checkViewDependencies({ schema: { article, articleTitles } }),
|
|
462
|
+
* );
|
|
463
|
+
* console.log(viewCount); // 1
|
|
464
|
+
* ```
|
|
465
|
+
*/
|
|
466
|
+
const checkViewDependencies = (options) => Effect.gen(function* () {
|
|
467
|
+
const cur = yield* drizzleKit(() => generateDrizzleJson(options.schema));
|
|
468
|
+
const empty = yield* drizzleKit(() => generateDrizzleJson({}));
|
|
469
|
+
const views = parseViewDefinitions(yield* drizzleKit(() => generateMigration(empty, cur)));
|
|
470
|
+
const edges = findViewEdges(views);
|
|
471
|
+
if (Arr.isReadonlyArrayNonEmpty(edges)) return yield* new ViewDependencyError({ edges: edges.map((edge) => ({
|
|
472
|
+
from: viewKey(edge.from),
|
|
473
|
+
to: viewKey(edge.to)
|
|
474
|
+
})) });
|
|
475
|
+
return { viewCount: views.length };
|
|
476
|
+
});
|
|
477
|
+
export { MalformedSnapshot, MigrationDriftError, ROOT_SENTINEL_ID, SchemaDiffError, ViewDependencyError, checkBaseAncestry, checkDagIntegrity, checkMigrationDrift, checkViewDependencies, findHeads, findViewEdges, parseViewDefinitions, readBaseMigrations, referencesView, resolveBaseRef, viewKey };
|
|
478
|
+
|
|
479
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["Record"],"sources":["../../src/tooling/migration-drift.ts","../../src/tooling/view-dependencies.ts"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,86 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thomasfosterau/effect-drizzle-ext-pg",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The @effect/sql-pg binding for @thomasfosterau/effect-drizzle-ext: Database / CurrentTransaction services over drizzle-orm/effect-postgres, PgClient layers, a sentinel-rollback test runner with test-database provisioning, a fresh-database migration runner, and migration-drift / view-dependency guards.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"drizzle",
|
|
7
|
+
"drizzle-orm",
|
|
8
|
+
"effect",
|
|
9
|
+
"postgres",
|
|
10
|
+
"typescript"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/thomasfosterau/effect-drizzle-ext#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/thomasfosterau/effect-drizzle-ext/issues"
|
|
15
|
+
},
|
|
5
16
|
"license": "MIT",
|
|
17
|
+
"author": "Thomas Foster <me@thomasfoster.au>",
|
|
6
18
|
"repository": {
|
|
7
19
|
"type": "git",
|
|
8
|
-
"url": "
|
|
20
|
+
"url": "https://github.com/thomasfosterau/effect-drizzle-ext.git",
|
|
9
21
|
"directory": "packages/pg"
|
|
10
22
|
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"src",
|
|
26
|
+
"!src/**/*.test.ts",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"type": "module",
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"default": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./migrations": {
|
|
38
|
+
"types": "./dist/migrations.d.ts",
|
|
39
|
+
"default": "./dist/migrations.js"
|
|
40
|
+
},
|
|
41
|
+
"./testing": {
|
|
42
|
+
"types": "./dist/testing.d.ts",
|
|
43
|
+
"default": "./dist/testing.js"
|
|
44
|
+
},
|
|
45
|
+
"./tooling": {
|
|
46
|
+
"types": "./dist/tooling/index.d.ts",
|
|
47
|
+
"default": "./dist/tooling/index.js"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
11
50
|
"publishConfig": {
|
|
12
51
|
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@thomasfosterau/effect-drizzle-ext": "^0.1.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@effect/language-service": "^0.87.5",
|
|
58
|
+
"@effect/platform-node": "4.1.2",
|
|
59
|
+
"@effect/sql-pg": "4.1.2",
|
|
60
|
+
"@testcontainers/postgresql": "^12.2.1",
|
|
61
|
+
"@types/node": "^26.6.6",
|
|
62
|
+
"@vitest/coverage-istanbul": "5.0.5",
|
|
63
|
+
"drizzle-kit": "1.0.0-rc.5",
|
|
64
|
+
"drizzle-orm": "1.0.0-rc.5",
|
|
65
|
+
"effect": "4.1.2",
|
|
66
|
+
"vitest": "5.0.5"
|
|
67
|
+
},
|
|
68
|
+
"peerDependencies": {
|
|
69
|
+
"@effect/sql-pg": "^4.0.0",
|
|
70
|
+
"@testcontainers/postgresql": ">=12",
|
|
71
|
+
"drizzle-kit": ">=1.0.0-rc.5",
|
|
72
|
+
"drizzle-orm": ">=1.0.0-rc.5",
|
|
73
|
+
"effect": "^4.0.0"
|
|
74
|
+
},
|
|
75
|
+
"peerDependenciesMeta": {
|
|
76
|
+
"@testcontainers/postgresql": {
|
|
77
|
+
"optional": true
|
|
78
|
+
},
|
|
79
|
+
"drizzle-kit": {
|
|
80
|
+
"optional": true
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"engines": {
|
|
84
|
+
"node": ">=22"
|
|
13
85
|
}
|
|
14
|
-
}
|
|
86
|
+
}
|