@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.
@@ -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.0.0",
4
- "description": "Placeholder reserved for npm trusted publishing; the first real release of @thomasfosterau/effect-drizzle-ext-pg is 0.1.0.",
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": "git+https://github.com/thomasfosterau/effect-drizzle-ext.git",
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
+ }