tempest-db-js 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,391 @@
1
+ import { ColumnType, DefaultValue, ModelClass, Dialect, SyncDriver, AsyncDriver } from '../index.cjs';
2
+
3
+ /**
4
+ * tempest-db-js — Phase 6: the Schema IR (intermediate representation).
5
+ *
6
+ * A canonical, dialect-neutral description of a database schema. Every source of
7
+ * truth — model reflection (here), migration replay, or DB introspection —
8
+ * produces the SAME `SchemaIR`, so the differ compares like with like and SQL is
9
+ * only ever emitted at the dialect edge (the anti-"SQL-stitching" core).
10
+ */
11
+
12
+ /** A column's default in the IR. */
13
+ type DefaultIR = DefaultValue | null;
14
+ /** One column. */
15
+ interface ColumnIR {
16
+ readonly name: string;
17
+ readonly type: ColumnType;
18
+ readonly notNull: boolean;
19
+ readonly primaryKey: boolean;
20
+ readonly default: DefaultIR;
21
+ }
22
+ /** One table. */
23
+ interface TableIR {
24
+ readonly name: string;
25
+ readonly columns: Record<string, ColumnIR>;
26
+ /** Primary-key column names (composite = more than one). */
27
+ readonly primaryKey: readonly string[];
28
+ }
29
+ /** A whole schema, keyed by table name. */
30
+ interface SchemaIR {
31
+ readonly tables: Record<string, TableIR>;
32
+ }
33
+ /** Reflect one model class into a `TableIR`. */
34
+ declare function reflectTable(model: ModelClass): TableIR;
35
+ /**
36
+ * Reflect a set of model classes into a `SchemaIR`. This is the **target** state
37
+ * the differ compares the current (replayed) schema against.
38
+ *
39
+ * @param models The model classes that make up the schema.
40
+ * @returns The reflected schema IR.
41
+ */
42
+ declare function reflectSchema(models: readonly ModelClass[]): SchemaIR;
43
+ /** An empty schema (the baseline before any migration). */
44
+ declare function emptySchema(): SchemaIR;
45
+
46
+ /**
47
+ * tempest-db-js — Phase 6: typed migration operations.
48
+ *
49
+ * Every schema change is a typed `Operation` with a known inverse. This is what
50
+ * gives autogenerated, reversible `down()` and dialect-rendered DDL — never a
51
+ * hand-written `.sql` blob (the anti-Drizzle core).
52
+ */
53
+
54
+ /** A single, reversible schema operation. */
55
+ type Operation = {
56
+ readonly kind: "create_table";
57
+ readonly table: TableIR;
58
+ } | {
59
+ readonly kind: "drop_table";
60
+ readonly table: TableIR;
61
+ } | {
62
+ readonly kind: "rename_table";
63
+ readonly from: string;
64
+ readonly to: string;
65
+ } | {
66
+ readonly kind: "add_column";
67
+ readonly table: string;
68
+ readonly column: ColumnIR;
69
+ } | {
70
+ readonly kind: "drop_column";
71
+ readonly table: string;
72
+ readonly column: ColumnIR;
73
+ } | {
74
+ readonly kind: "alter_column";
75
+ readonly table: string;
76
+ readonly name: string;
77
+ readonly from: ColumnIR;
78
+ readonly to: ColumnIR;
79
+ } | {
80
+ readonly kind: "rename_column";
81
+ readonly table: string;
82
+ readonly from: string;
83
+ readonly to: string;
84
+ } | {
85
+ readonly kind: "recreate_table";
86
+ readonly from: TableIR;
87
+ readonly to: TableIR;
88
+ } | {
89
+ readonly kind: "execute";
90
+ readonly up: string;
91
+ readonly down: string | null;
92
+ };
93
+ /** Raised when an operation has no safe inverse (e.g. a one-way `execute`). */
94
+ declare class IrreversibleMigration extends Error {
95
+ constructor(message: string);
96
+ }
97
+ /**
98
+ * Compute the inverse of an operation, for autogenerated `down()`.
99
+ *
100
+ * @param op The forward operation.
101
+ * @returns The operation that undoes it.
102
+ * @throws IrreversibleMigration When `op` cannot be safely reversed.
103
+ */
104
+ declare function invert(op: Operation): Operation;
105
+ /** Invert a list of operations: reverse order, each inverted. */
106
+ declare function invertAll(ops: readonly Operation[]): Operation[];
107
+
108
+ /**
109
+ * tempest-db-js — Phase 6: DDL rendering.
110
+ *
111
+ * Renders the dialect-neutral IR + operations to concrete SQL per dialect. This
112
+ * is the ONLY place migration SQL is produced — the same operation yields the
113
+ * right DDL for SQLite or PostgreSQL.
114
+ */
115
+
116
+ /** Map a column type to its SQL type string for the dialect. */
117
+ declare function renderColumnType(type: ColumnType, dialect: Dialect): string;
118
+ /** Render a default value into a SQL `DEFAULT` expression for the dialect. */
119
+ declare function renderDefault(def: DefaultValue, dialect: Dialect): string;
120
+ /** Render one column definition: `"name" TYPE [NOT NULL] [DEFAULT x]`. */
121
+ declare function renderColumnDef(col: ColumnIR, dialect: Dialect): string;
122
+ /**
123
+ * Render an operation to one or more SQL statements for the dialect.
124
+ *
125
+ * @param op The operation.
126
+ * @param dialect The target dialect.
127
+ * @returns The SQL statements (usually one).
128
+ * @throws Error When the operation is unsupported on the dialect (e.g. SQLite
129
+ * `alter_column`, which needs the Phase 6e batch/table-rebuild path).
130
+ */
131
+ declare function renderOperation(op: Operation, dialect: Dialect): string[];
132
+
133
+ /**
134
+ * tempest-db-js — Phase 6: the schema differ.
135
+ *
136
+ * Compares two `SchemaIR`s (current vs target) and emits the typed `Operation[]`
137
+ * that turns one into the other. Purely structural — no SQL, no DB.
138
+ */
139
+
140
+ /**
141
+ * Diff `current` → `target`, returning the operations to migrate forward.
142
+ *
143
+ * Order: create new tables, then per-table column adds/alters/drops, then drop
144
+ * removed tables last (so nothing depends on a table being dropped first).
145
+ *
146
+ * @param current The current schema (e.g. from migration replay).
147
+ * @param target The desired schema (e.g. from model reflection).
148
+ * @returns The forward operations.
149
+ */
150
+ declare function diffSchema(current: SchemaIR, target: SchemaIR): Operation[];
151
+
152
+ /**
153
+ * tempest-db-js — Phase 6: the revision DAG.
154
+ *
155
+ * Migrations form a directed acyclic graph via `downRevision` (a list of parents,
156
+ * so branches and merges are first-class). This module orders them for applying
157
+ * (topological, deterministic) and finds heads.
158
+ */
159
+ /** The minimal graph shape a migration must expose. */
160
+ interface RevisionNode {
161
+ readonly revision: string;
162
+ readonly downRevision: readonly string[];
163
+ }
164
+ /** Raised when the revision graph contains a cycle. */
165
+ declare class CyclicMigrationGraph extends Error {
166
+ constructor(remaining: readonly string[]);
167
+ }
168
+ /** Raised when a `downRevision` points at a revision that does not exist. */
169
+ declare class UnknownRevision extends Error {
170
+ constructor(revision: string, parent: string);
171
+ }
172
+ /**
173
+ * Topologically order migrations so every parent precedes its children. Ties are
174
+ * broken by revision id for reproducible builds. Throws on cycles or dangling
175
+ * parents.
176
+ *
177
+ * @param migrations The migrations to order.
178
+ * @returns The migrations in apply order.
179
+ */
180
+ declare function topoOrder<T extends RevisionNode>(migrations: readonly T[]): T[];
181
+ /** Revisions that are nobody's parent — the current head(s) of the graph. */
182
+ declare function heads<T extends RevisionNode>(migrations: readonly T[]): string[];
183
+
184
+ /**
185
+ * tempest-db-js — Phase 6: migration autogeneration.
186
+ *
187
+ * Turns a diff's `Operation[]` into an editable TypeScript migration file with
188
+ * `up()` and an autogenerated, inverted `down()`. Operations are embedded as
189
+ * plain-data literals (they are JSON-serializable), so the file stays readable
190
+ * and hand-editable — never an opaque `.sql` blob.
191
+ */
192
+
193
+ /** Inputs for one generated migration file. */
194
+ interface MigrationDraft {
195
+ readonly revision: string;
196
+ readonly downRevision: readonly string[];
197
+ readonly label: string;
198
+ readonly operations: readonly Operation[];
199
+ }
200
+ /**
201
+ * Render a migration file body as TypeScript source.
202
+ *
203
+ * The generated `down()` inverts `up()`; if any operation is irreversible, the
204
+ * generation still succeeds but `down()` throws at run time with a clear message
205
+ * (so reversibility is never silently broken).
206
+ *
207
+ * @param draft The revision id, parents, label, and forward operations.
208
+ * @returns The migration file source.
209
+ */
210
+ declare function generateMigration(draft: MigrationDraft): string;
211
+ /** Build a short, stable revision id from a label + parent set. */
212
+ declare function makeRevisionId(label: string, parents: readonly string[]): string;
213
+
214
+ /**
215
+ * tempest-db-js — Phase 6: the migration runner.
216
+ *
217
+ * The `Op` facade records operations; the `MigrationRunner` renders them to SQL
218
+ * (per dialect) and runs them through a sync driver, tracking applied revisions
219
+ * in a `tempest_db_js_migrations` table. Upgrades/downgrades respect the revision DAG.
220
+ *
221
+ * SQLite execution is real (via `node:sqlite`); the same code path serves any
222
+ * sync driver.
223
+ */
224
+
225
+ /**
226
+ * The facade injected into `up()`/`down()`. Each method records an operation;
227
+ * the runner renders and executes them after the function returns.
228
+ */
229
+ declare class Op {
230
+ readonly operations: Operation[];
231
+ /** Record a raw operation (used by autogenerated migrations). */
232
+ run(operation: Operation): void;
233
+ createTable(table: TableIR): void;
234
+ dropTable(table: TableIR): void;
235
+ renameTable(from: string, to: string): void;
236
+ addColumn(table: string, column: ColumnIR): void;
237
+ dropColumn(table: string, column: ColumnIR): void;
238
+ alterColumn(table: string, name: string, from: ColumnIR, to: ColumnIR): void;
239
+ renameColumn(table: string, from: string, to: string): void;
240
+ /** Rebuild a table (SQLite batch-mode / PostgreSQL per-column alters). */
241
+ recreateTable(from: TableIR, to: TableIR): void;
242
+ /** Raw SQL escape hatch (e.g. a data migration). `down` may be `null`. */
243
+ execute(up: string, down?: string | null): void;
244
+ }
245
+ /** A migration file's shape. */
246
+ interface Migration {
247
+ readonly revision: string;
248
+ readonly downRevision: readonly string[];
249
+ readonly label?: string;
250
+ up(op: Op): void;
251
+ down(op: Op): void;
252
+ }
253
+ /** Runs migrations against a sync driver, tracking applied revisions. */
254
+ declare class MigrationRunner {
255
+ private readonly driver;
256
+ private readonly dialect;
257
+ constructor(driver: SyncDriver, dialect: Dialect);
258
+ /** Create the version-tracking table if it does not exist. */
259
+ ensureVersionTable(): void;
260
+ /** The set of applied revision ids. */
261
+ applied(): Set<string>;
262
+ private runOps;
263
+ private record;
264
+ private forget;
265
+ /**
266
+ * Apply all pending migrations up to the head(s), in DAG order.
267
+ *
268
+ * @param migrations All known migrations.
269
+ * @param appliedAt Timestamp string to stamp (pass one in — the runtime has no
270
+ * wall clock of its own here).
271
+ * @returns The revision ids that were applied this run.
272
+ */
273
+ upgrade(migrations: readonly Migration[], appliedAt: string): string[];
274
+ /**
275
+ * Revert the last `steps` applied migrations (default 1), newest first.
276
+ *
277
+ * @param migrations All known migrations.
278
+ * @param steps How many applied revisions to roll back.
279
+ * @returns The revision ids that were reverted.
280
+ */
281
+ downgrade(migrations: readonly Migration[], steps?: number): string[];
282
+ }
283
+
284
+ /**
285
+ * tempest-db-js — Phase 6d: SQLite introspection + drift detection.
286
+ *
287
+ * Reads the live schema from a SQLite database (via `PRAGMA table_info`) into a
288
+ * `SchemaIR`, and compares it against the models to detect **drift** — the DB
289
+ * diverging from what the migrations/models say it should be. Comparison is done
290
+ * at SQLite's storage-affinity level, so coarse SQLite typing (every string is
291
+ * `TEXT`) does not produce false positives.
292
+ */
293
+
294
+ /** SQLite's five storage classes / affinities. */
295
+ type SqliteAffinity = "INTEGER" | "TEXT" | "REAL" | "BLOB" | "NUMERIC";
296
+ /** Apply SQLite's affinity rules to a declared type string. */
297
+ declare function sqliteAffinity(declared: string): SqliteAffinity;
298
+ /**
299
+ * Read the current SQLite schema into a `SchemaIR`. Lossy by nature (SQLite
300
+ * collapses types into affinities), so types come back as the affinity's kind.
301
+ *
302
+ * @param driver A sync SQLite driver.
303
+ * @returns The introspected schema.
304
+ */
305
+ declare function introspectSqlite(driver: SyncDriver): SchemaIR;
306
+ /**
307
+ * Compare the live SQLite schema against the models and report drift. Comparison
308
+ * is at the affinity level (so `varchar` vs `TEXT` is not flagged), plus
309
+ * nullability, primary-key, and presence of tables/columns.
310
+ *
311
+ * @param driver A sync SQLite driver.
312
+ * @param models The model classes that define the intended schema.
313
+ * @returns A list of human-readable drift messages — empty means no drift.
314
+ */
315
+ declare function checkDrift(driver: SyncDriver, models: readonly ModelClass[]): string[];
316
+ /**
317
+ * Read the current PostgreSQL schema into a `SchemaIR` from `information_schema`.
318
+ *
319
+ * Not exercised by the in-repo test suite (no PostgreSQL in CI); mirrors
320
+ * {@link introspectSqlite} for the async driver.
321
+ *
322
+ * @param driver An async PostgreSQL driver.
323
+ * @returns The introspected schema.
324
+ */
325
+ declare function introspectPostgres(driver: AsyncDriver): Promise<SchemaIR>;
326
+ /**
327
+ * Drift check for PostgreSQL: compares the live schema (introspected) against the
328
+ * models by column kind, nullability, primary-key, and presence. Structural —
329
+ * not exercised in CI (no PostgreSQL).
330
+ *
331
+ * @param driver An async PostgreSQL driver.
332
+ * @param models The model classes that define the intended schema.
333
+ * @returns A list of drift messages — empty means no drift.
334
+ */
335
+ declare function checkDriftPostgres(driver: AsyncDriver, models: readonly ModelClass[]): Promise<string[]>;
336
+
337
+ /**
338
+ * tempest-db-js — Phase 6: migration replay → virtual schema IR.
339
+ *
340
+ * Applies a migration's operations to an in-memory `SchemaIR`, building the
341
+ * "current" schema without touching a database. This is the hybrid source of
342
+ * truth for autogenerate: diff the replayed schema against the reflected models.
343
+ */
344
+
345
+ /** Apply one operation to a schema, returning the updated schema. */
346
+ declare function applyOperation(schema: SchemaIR, op: Operation): SchemaIR;
347
+ /**
348
+ * Replay migrations (in DAG order) into a virtual `SchemaIR` — the "current"
349
+ * schema, computed without a database.
350
+ *
351
+ * @param migrations All known migrations.
352
+ * @returns The schema after applying every migration's `up()`.
353
+ */
354
+ declare function replaySchema(migrations: readonly Migration[]): SchemaIR;
355
+
356
+ /**
357
+ * tempest-db-js — migration CLI (programmatic core).
358
+ *
359
+ * `runMigrationCli(argv, config)` dispatches Alembic-style commands against a
360
+ * driver + a set of migrations + (optionally) the models. It returns lines and
361
+ * an exit code rather than touching `process`, so it is fully testable; a thin
362
+ * `bin` wrapper just maps `process.argv`/`process.exit` onto it.
363
+ */
364
+
365
+ /** Configuration the CLI operates against. */
366
+ interface CliConfig {
367
+ readonly driver: SyncDriver;
368
+ readonly dialect: Dialect;
369
+ readonly migrations: readonly Migration[];
370
+ readonly models?: readonly ModelClass[];
371
+ /** Timestamp string stamped on applied revisions (no wall clock here). */
372
+ readonly appliedAt?: string;
373
+ }
374
+ /** The result of a CLI run. */
375
+ interface CliResult {
376
+ readonly code: number;
377
+ readonly lines: string[];
378
+ }
379
+ /**
380
+ * Run one CLI command.
381
+ *
382
+ * Commands: `current`, `history`, `heads`, `upgrade [--sql]`, `downgrade [N]`,
383
+ * `check`, `revision -m <msg> [--autogenerate]`.
384
+ *
385
+ * @param argv The command and its arguments (without the program name).
386
+ * @param config The driver, migrations, and models to operate on.
387
+ * @returns Output lines and an exit code.
388
+ */
389
+ declare function runMigrationCli(argv: readonly string[], config: CliConfig): CliResult;
390
+
391
+ export { type CliConfig, type CliResult, type ColumnIR, CyclicMigrationGraph, type DefaultIR, IrreversibleMigration, type Migration, type MigrationDraft, MigrationRunner, Op, type Operation, type RevisionNode, type SchemaIR, type SqliteAffinity, type TableIR, UnknownRevision, applyOperation, checkDrift, checkDriftPostgres, diffSchema, emptySchema, generateMigration, heads, introspectPostgres, introspectSqlite, invert, invertAll, makeRevisionId, reflectSchema, reflectTable, renderColumnDef, renderColumnType, renderDefault, renderOperation, replaySchema, runMigrationCli, sqliteAffinity, topoOrder };