@fougere/adapter-sql 0.5.0-alpha.1 → 0.7.0-alpha.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.
Files changed (74) hide show
  1. package/README.md +4 -4
  2. package/dist/adapter.schema.json +16 -0
  3. package/dist/check.d.ts +2 -27
  4. package/dist/check.d.ts.map +1 -1
  5. package/dist/check.js +2 -27
  6. package/dist/check.js.map +1 -1
  7. package/dist/crud.d.ts +19 -85
  8. package/dist/crud.d.ts.map +1 -1
  9. package/dist/crud.js +25 -102
  10. package/dist/crud.js.map +1 -1
  11. package/dist/ddl.d.ts +8 -53
  12. package/dist/ddl.d.ts.map +1 -1
  13. package/dist/ddl.js +16 -55
  14. package/dist/ddl.js.map +1 -1
  15. package/dist/dialect.d.ts +7 -50
  16. package/dist/dialect.d.ts.map +1 -1
  17. package/dist/dialect.js +2 -5
  18. package/dist/dialect.js.map +1 -1
  19. package/dist/diff.d.ts +6 -44
  20. package/dist/diff.d.ts.map +1 -1
  21. package/dist/diff.js +20 -50
  22. package/dist/diff.js.map +1 -1
  23. package/dist/fields.d.ts +10 -9
  24. package/dist/fields.d.ts.map +1 -1
  25. package/dist/fields.js +8 -1
  26. package/dist/fields.js.map +1 -1
  27. package/dist/index.d.ts +7 -5
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +3 -2
  30. package/dist/index.js.map +1 -1
  31. package/dist/order.d.ts +23 -0
  32. package/dist/order.d.ts.map +1 -0
  33. package/dist/order.js +40 -0
  34. package/dist/order.js.map +1 -0
  35. package/dist/query.d.ts +2 -17
  36. package/dist/query.d.ts.map +1 -1
  37. package/dist/query.js +1 -11
  38. package/dist/query.js.map +1 -1
  39. package/dist/setup.d.ts +15 -35
  40. package/dist/setup.d.ts.map +1 -1
  41. package/dist/setup.js +18 -12
  42. package/dist/setup.js.map +1 -1
  43. package/dist/sqlite.d.ts +2 -2
  44. package/dist/sqlite.d.ts.map +1 -1
  45. package/dist/sqlite.js +22 -12
  46. package/dist/sqlite.js.map +1 -1
  47. package/dist/step.d.ts +4 -33
  48. package/dist/step.d.ts.map +1 -1
  49. package/dist/step.js +11 -46
  50. package/dist/step.js.map +1 -1
  51. package/dist/table.d.ts +15 -77
  52. package/dist/table.d.ts.map +1 -1
  53. package/dist/table.js +34 -146
  54. package/dist/table.js.map +1 -1
  55. package/dist/values.d.ts +1 -17
  56. package/dist/values.d.ts.map +1 -1
  57. package/dist/values.js +2 -7
  58. package/dist/values.js.map +1 -1
  59. package/package.json +4 -4
  60. package/src/adapter.schema.json +16 -0
  61. package/src/check.ts +2 -27
  62. package/src/crud.ts +28 -105
  63. package/src/ddl.ts +14 -55
  64. package/src/dialect.ts +7 -50
  65. package/src/diff.ts +18 -50
  66. package/src/fields.ts +17 -8
  67. package/src/index.ts +6 -6
  68. package/src/order.ts +63 -0
  69. package/src/query.ts +2 -17
  70. package/src/setup.ts +33 -37
  71. package/src/sqlite.ts +28 -14
  72. package/src/step.ts +12 -54
  73. package/src/table.ts +42 -185
  74. package/src/values.ts +3 -24
package/src/index.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  export { createTableSQL, createIndexSQL, indexSQL, addForeignKeyConstraintSQL, generateSQL, autoMigrate, compiler } from './ddl.js';
2
2
  export { onQuery, logQueries, type QueryEvent, type QuerySink } from './query.js';
3
+ export type { FkEdge, TableOrder } from './order.js';
3
4
  export type { GenerateOptions, SqlSink } from './ddl.js';
4
- export { toTable, toTables, toTableName, toSnakeCase, isKeyed, orderTables } from './table.js';
5
+ export { toTable, toTables, toTableName, toSnakeCase, isKeyed } from './table.js';
6
+ export { orderTables } from './order.js';
5
7
  export type {
6
8
  TableDef,
7
9
  ColumnDef,
8
10
  ColumnShape,
9
11
  ColumnReference,
10
12
  RelationResolve,
11
- FkEdge,
12
- TableOrder,
13
13
  EntityEntry,
14
14
  FrondLike,
15
15
  AppLike,
@@ -25,15 +25,15 @@ export {
25
25
  } from './dialect.js';
26
26
  export type { Dialect, DialectName } from './dialect.js';
27
27
  export type { SqlField, SqlFields } from './fields.js';
28
- export { SqlEntityOrm, createOrmFactory } from './crud.js';
29
- export type { OrmFactoryOptions } from './crud.js';
28
+ export { SqlStorage, createStorageFactory } from './crud.js';
29
+ export type { StorageFactoryOptions } from './crud.js';
30
30
  export { codecFor, codecsOf } from './values.js';
31
31
  export type { ValueCodec } from './values.js';
32
32
  // The driver this package owns is NOT here: `better-sqlite3` is native and `node:fs` is a
33
33
  // builtin, and an index that re-exported them made the whole adapter unreachable from a
34
34
  // runtime that has neither. It lives at `@fougere/adapter-sql/sqlite`.
35
35
  export { setupKysely, sqlSink } from './setup.js';
36
- export type { Setup, SetupOptions } from './setup.js';
36
+ export type { Setup, SqlSource, SetupOptions } from './setup.js';
37
37
  export { actualState, desiredTables, delta, orderChanges, changeSQL, planMigration, migrate } from './diff.js';
38
38
  export type { SchemaState, Change } from './diff.js';
39
39
  // The non-additive half — realised only from a step a human wrote down.
package/src/order.ts ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * In what order a batch of tables is created — the one question that reads a `TableDef` and
3
+ * knows nothing above it: no entity, no axis, no schema.
4
+ */
5
+ import type { ColumnDef, TableDef } from './table.js';
6
+
7
+ export interface FkEdge {
8
+ table: TableDef;
9
+ column: ColumnDef;
10
+ }
11
+
12
+ export interface TableOrder {
13
+ /** Tables in dependency order — a `ref()` target always precedes its referrer. */
14
+ ordered: TableDef[];
15
+ /** FK columns whose target could not be ordered first — a cycle. Constrain after creation. */
16
+ deferred: FkEdge[];
17
+ }
18
+
19
+ /**
20
+ * Order a table set so a `ref()`'s target always exists before the table that points at it —
21
+ * required by every engine except SQLite, which resolves FK targets lazily and accepts any order
22
+ * (and has no `ALTER TABLE ADD CONSTRAINT` to close a cycle with — a caller on that dialect skips
23
+ * this function entirely).
24
+ */
25
+ export function orderTables(tables: TableDef[]): TableOrder {
26
+ const byName = new Map(tables.map((table) => [table.name, table]));
27
+ const needs = new Map(
28
+ tables.map((table) => [
29
+ table.name,
30
+ new Set(
31
+ table.columns
32
+ .filter((c) => c.references && c.references.table !== table.name && byName.has(c.references.table))
33
+ .map((c) => c.references!.table),
34
+ ),
35
+ ]),
36
+ );
37
+
38
+ const ordered: TableDef[] = [];
39
+ const deferred: FkEdge[] = [];
40
+ const done = new Set<string>();
41
+
42
+ while (needs.size > 0) {
43
+ const ready = [...needs.keys()].find((name) => [...needs.get(name)!].every((dep) => done.has(dep)));
44
+ if (ready) {
45
+ ordered.push(byName.get(ready)!);
46
+ done.add(ready);
47
+ needs.delete(ready);
48
+ continue;
49
+ }
50
+ // Every table left waits on another table left — a cycle. Break it by
51
+ // deferring one edge: every column of the first remaining table that points
52
+ // at its first unmet dependency.
53
+ const [name, deps] = [...needs.entries()][0];
54
+ const dep = [...deps].find((d) => !done.has(d))!;
55
+ const table = byName.get(name)!;
56
+ for (const column of table.columns) {
57
+ if (column.references?.table === dep) deferred.push({ table, column });
58
+ }
59
+ deps.delete(dep);
60
+ }
61
+
62
+ return { ordered, deferred };
63
+ }
package/src/query.ts CHANGED
@@ -5,12 +5,7 @@ export interface QueryEvent {
5
5
  /** Which storage ran it — a process may open several (`sources:`). */
6
6
  storage: string;
7
7
  sql: string;
8
- /**
9
- * How MANY parameters, never their values.
10
- *
11
- * They are user data nobody chose to expose — the rule the call log states for a body.
12
- * The count is what a reader needs anyway: it is how you see an `IN (?, ?, ?)` grow.
13
- */
8
+ /** How MANY parameters, never their values. */
14
9
  parameters: number;
15
10
  /** Rounded to three decimals: a sub-microsecond statement reports 17 digits otherwise. */
16
11
  ms: number;
@@ -22,17 +17,7 @@ export type QuerySink = (event: QueryEvent) => void;
22
17
 
23
18
  const sinks: QuerySink[] = [];
24
19
 
25
- /**
26
- * Be told about every statement this process runs, and get the unsubscription back.
27
- *
28
- * A subscription and not an option, for one reason that decides it: Kysely takes its `log`
29
- * at CONSTRUCTION, and the storage is built during the boot — before any extension's
30
- * `up(app)` runs. So the only way to subscribe afterwards is for the log to be installed
31
- * from the start and route to whoever is listening.
32
- *
33
- * The same shape core states for `onLog` and observability for `onSpan`. Costs nothing when
34
- * nobody listens: `logQueries` returns on an empty list before touching the event.
35
- */
20
+ /** Be told about every statement this process runs, and get the unsubscription back. */
36
21
  export function onQuery(sink: QuerySink): () => void {
37
22
  sinks.push(sink);
38
23
 
package/src/setup.ts CHANGED
@@ -1,53 +1,46 @@
1
- /**
2
- * Storage setup — the shape every engine answers, and no driver at all.
3
- *
4
- * Fougere has no business choosing a driver (`pg`, `mysql2`, `tedious`, `better-sqlite3`,
5
- * a D1 binding), so the caller builds the Kysely dialect and hands it over — no dynamic
6
- * import, no optional dependency. This file therefore reaches for nothing: it is what a
7
- * runtime with no filesystem imports. The one driver this package does own lives behind
8
- * `@fougere/adapter-sql/sqlite`.
9
- */
1
+ /** Storage setup — the shape every engine answers, and no driver at all. */
10
2
  import { Kysely, sql, type Dialect as KyselyDialect } from 'kysely';
11
- import { createOrmFactory, type OrmFactoryOptions } from './crud.js';
3
+ import type { Source, SourceView } from '@fougere/core';
4
+ import { createStorageFactory, type StorageFactoryOptions } from './crud.js';
12
5
  import { logQueries } from './query.js';
6
+ import { migrate } from './diff.js';
7
+ import { toTableName } from './table.js';
13
8
  import type { DialectName } from './dialect.js';
14
9
  import type { SqlSink } from './ddl.js';
15
10
 
16
11
  export interface SetupOptions {
17
12
  /** Override naming for specific entities (e.g. better-auth wants singular table names). */
18
- ormFactoryOptions?: OrmFactoryOptions;
19
- /**
20
- * What to call this storage when a query is reported.
21
- *
22
- * A process may open several (`sources:`), and a module-level sink sees them all — without
23
- * a name, two statements from two databases read as one stream. Defaults to what
24
- * distinguishes them anyway: the engine here, the file path for SQLite.
25
- */
13
+ storageFactoryOptions?: StorageFactoryOptions;
14
+ /** What to call this storage when a query is reported. */
26
15
  name?: string;
27
16
  }
28
17
 
29
- export interface Setup {
18
+ /** A `Source` realized by SQL — and the three members that are SQL's, not the routing's. */
19
+ export interface SqlSource extends Source {
30
20
  dialect: DialectName;
31
- ormFactory: ReturnType<typeof createOrmFactory>;
21
+ storageFactory: ReturnType<typeof createStorageFactory>;
32
22
  /** Runs raw statements — what `autoMigrate` writes through. */
33
23
  sink: SqlSink;
34
24
  /**
35
- * The Kysely instance, for what precedes any entity: `migrate(app, setup)` writes the
36
- * schema through it, and a script may need it before a container exists.
37
- *
38
- * It is not the way to reach data from inside an app — that is the injected `EntityOrm`,
39
- * whose `client` gives the same handle while keeping the scope of its entity.
25
+ * The Kysely instance, for what precedes any entity: `migrate(app, setup)` writes the schema
26
+ * through it, and a script may need it before a container exists.
40
27
  */
41
28
  db: Kysely<any>;
42
- /**
43
- * Run `fn` inside one transaction of this engine, with an ORM factory bound to it.
44
- *
45
- * The transaction belongs to the engine, so obtaining one is a gesture on the engine and
46
- * nowhere else. Nothing new is handed back: `Transaction<DB> extends Kysely<DB>` and
47
- * `SqlEntityOrm` takes a `Kysely<any>`, so the SAME ORM is rebuilt over the substituted
48
- * connection — which is why a frame needs no support in this package.
49
- */
50
- transacted<R>(fn: (ormFactory: ReturnType<typeof createOrmFactory>) => Promise<R>): Promise<R>;
29
+ /** Run `fn` inside one transaction of this engine, with a storage factory bound to it. */
30
+ transacted<R>(fn: (storageFactory: ReturnType<typeof createStorageFactory>) => Promise<R>): Promise<R>;
31
+ }
32
+
33
+ /** The name this shape answered to before it was one realization among several. */
34
+ export type Setup = SqlSource;
35
+
36
+ /** The migration of what lives in ONE sql source, carrying its own dialect. */
37
+ function migrating(db: Kysely<any>, dialect: DialectName, opts: SetupOptions) {
38
+ return async (view: SourceView): Promise<void> => {
39
+ await migrate(view as never, db, {
40
+ dialect,
41
+ tableName: opts.storageFactoryOptions?.tableName ?? toTableName,
42
+ });
43
+ };
51
44
  }
52
45
 
53
46
  /** A sink that runs statements through Kysely — works on every engine. */
@@ -60,13 +53,16 @@ export function setupKysely(
60
53
  kyselyDialect: KyselyDialect,
61
54
  dialect: DialectName,
62
55
  opts: SetupOptions = {},
63
- ): Setup {
56
+ ): SqlSource {
64
57
  const db = new Kysely<any>({ dialect: kyselyDialect, log: logQueries(opts.name ?? dialect) });
65
58
  return {
66
59
  db,
67
60
  dialect,
68
- ormFactory: createOrmFactory(db, opts.ormFactoryOptions, dialect),
61
+ storageFactory: createStorageFactory(db, opts.storageFactoryOptions, dialect),
69
62
  sink: sqlSink(db),
70
- transacted: (fn) => db.transaction().execute((trx) => fn(createOrmFactory(trx, opts.ormFactoryOptions, dialect))),
63
+ migrate: migrating(db, dialect, opts),
64
+ close: () => db.destroy(),
65
+ name: opts.name ?? dialect,
66
+ transacted: (fn) => db.transaction().execute((trx) => fn(createStorageFactory(trx, opts.storageFactoryOptions, dialect))),
71
67
  };
72
68
  }
package/src/sqlite.ts CHANGED
@@ -1,26 +1,21 @@
1
- /**
2
- * SQLite on a file — the convention a first run meets, and the only driver this package owns.
3
- *
4
- * It sits behind its own subpath because `better-sqlite3` is a NATIVE module and `node:fs`
5
- * is a builtin: a bundler cannot prune a module that imports them, so re-exporting this
6
- * from the index made the whole adapter unreachable from a runtime that has neither. Same
7
- * cut as `@fougere/transport-http/receive`, and for the same reason — share the projection,
8
- * never the plumbing.
9
- */
1
+ /** SQLite on a file — the convention a first run meets, and the only driver this package owns. */
10
2
  import { mkdirSync } from 'node:fs';
11
3
  import { dirname } from 'node:path';
12
4
  import { Kysely, SqliteDialect } from 'kysely';
13
5
  import Database from 'better-sqlite3';
14
- import { createOrmFactory } from './crud.js';
6
+ import { createStorageFactory } from './crud.js';
15
7
  import { logQueries } from './query.js';
16
- import { sqlSink, type Setup, type SetupOptions } from './setup.js';
8
+ import { sqlSink, type SetupOptions, type SqlSource } from './setup.js';
9
+ import { migrate } from './diff.js';
10
+ import { toTableName } from './table.js';
11
+ import { Sources, type Source, type SourceConfig, type SourceView } from '@fougere/core';
17
12
 
18
13
  export interface SqliteSetupOptions extends SetupOptions {
19
14
  /** Filesystem path to the database. Defaults to a project-local file. */
20
15
  path?: string;
21
16
  }
22
17
 
23
- export interface SqliteSetup extends Setup {
18
+ export interface SqliteSetup extends SqlSource {
24
19
  /** The raw handle, for pragmas and synchronous exec. */
25
20
  sqlite: Database.Database;
26
21
  }
@@ -37,8 +32,27 @@ export function setupSqlite(opts: SqliteSetupOptions = {}): SqliteSetup {
37
32
  db,
38
33
  sqlite,
39
34
  dialect: 'sqlite',
40
- ormFactory: createOrmFactory(db, opts.ormFactoryOptions, 'sqlite'),
35
+ storageFactory: createStorageFactory(db, opts.storageFactoryOptions, 'sqlite'),
41
36
  sink: sqlSink(db),
42
- transacted: (fn) => db.transaction().execute((trx) => fn(createOrmFactory(trx, opts.ormFactoryOptions, 'sqlite'))),
37
+ migrate: async (view: SourceView) => {
38
+ await migrate(view as never, db, { dialect: 'sqlite', tableName: opts.storageFactoryOptions?.tableName ?? toTableName });
39
+ },
40
+ close: () => db.destroy(),
41
+ name: opts.name ?? path,
42
+ transacted: (fn) => db.transaction().execute((trx) => fn(createStorageFactory(trx, opts.storageFactoryOptions, 'sqlite'))),
43
43
  };
44
44
  }
45
+
46
+ /** `source. */
47
+ Sources.register('sql', (conf: SourceConfig): Source => {
48
+ const dialect = conf.dialect as string | undefined;
49
+ if (dialect !== undefined && dialect !== 'sqlite') {
50
+ throw new Error(
51
+ `source 'sql', dialect '${dialect}': cannot be built from a name — only 'sqlite' can, `
52
+ + 'because it is the one driver this package owns. Build the Kysely dialect yourself and '
53
+ + `call setupKysely(dialect, '${dialect}'), then hand it in as a source.`,
54
+ );
55
+ }
56
+
57
+ return setupSqlite({ path: conf.path as string | undefined, name: conf.name as string | undefined });
58
+ });
package/src/step.ts CHANGED
@@ -1,15 +1,4 @@
1
- /**
2
- * The half `delta()` refuses — realised from an intention that was written down.
3
- *
4
- * `diff.ts` states its own guarantee: additive only, because "a rename is not even
5
- * detectable from a diff (it reads as a drop plus an add)". That still holds, and this
6
- * file does not weaken it. What changed is upstream: a frozen step (`fougere freeze`)
7
- * carries a rename because a human declared it at the moment they made it. The intention
8
- * exists now, so it can be realised — and only what the step actually says.
9
- *
10
- * A drop and a rename both touch live data, so this is the only place either can come
11
- * from: never introspection, never a guess.
12
- */
1
+ /** The half `delta()` refuses — realised from an intention that was written down. */
13
2
  import { sql, type Kysely } from 'kysely';
14
3
  import type { Change as ShapeChange, SetDiff } from '@fougere/schema';
15
4
  import { dequal } from 'dequal';
@@ -42,25 +31,11 @@ export interface Plan {
42
31
  export interface PlanOptions {
43
32
  /** Entity key → table name. Same resolver `desiredTables` takes. */
44
33
  tableName?: (name: string) => string;
45
- /**
46
- * What the database actually holds, from `actualState`. Given, a change already
47
- * realised is skipped.
48
- *
49
- * Idempotence by OBSERVATION and not by bookkeeping — the same choice `delta` makes.
50
- * A ledger of applied steps would be a second record of a fact the columns already
51
- * carry, and the two would disagree the day someone renamed a column by hand.
52
- */
34
+ /** What the database actually holds, from `actualState`. */
53
35
  actual?: SchemaState;
54
36
  }
55
37
 
56
- /**
57
- * Collapse a chain of steps into one, following each field through its renames.
58
- *
59
- * A step judges itself on two names — old gone, new here — so an intermediate rename is
60
- * unrecognisable once its target has been renamed again: both names are absent and it is
61
- * proposed forever. Composing the chain first asks the question about the name the field
62
- * ENDS on, which is the only one the tables can answer.
63
- */
38
+ /** Collapse a chain of steps into one, following each field through its renames. */
64
39
  export function collapseChain(steps: readonly SetDiff[]): SetDiff {
65
40
  const entities: SetDiff['entities'] = {};
66
41
  const added: string[] = [];
@@ -70,9 +45,9 @@ export function collapseChain(steps: readonly SetDiff[]): SetDiff {
70
45
  added.push(...step.entitiesAdded);
71
46
  removed.push(...step.entitiesRemoved);
72
47
  for (const [entity, answer] of Object.entries(step.entities)) {
73
- const held = (entities[entity] ??= { changes: [], ambiguous: [] });
74
- held.ambiguous.push(...answer.ambiguous);
75
- for (const change of answer.changes) compose(held.changes, change);
48
+ const entry = (entities[entity] ??= { changes: [], ambiguous: [] });
49
+ entry.ambiguous.push(...answer.ambiguous);
50
+ for (const change of answer.changes) compose(entry.changes, change);
76
51
  }
77
52
  }
78
53
 
@@ -80,9 +55,8 @@ export function collapseChain(steps: readonly SetDiff[]): SetDiff {
80
55
  }
81
56
 
82
57
  /**
83
- * Add one change to what the chain has said so far, rewriting rather than appending when
84
- * it continues a field already moved. A rename back to its origin cancels: the tables
85
- * never held the name in between, so there is nothing for them to do.
58
+ * Add one change to what the chain has said so far, rewriting rather than appending when it
59
+ * continues a field already moved.
86
60
  */
87
61
  function compose(held: ShapeChange[], change: ShapeChange): void {
88
62
  if (change.kind === 'renamed') {
@@ -113,11 +87,7 @@ function compose(held: ShapeChange[], change: ShapeChange): void {
113
87
  held.push({ ...change, field: origin.from });
114
88
  }
115
89
 
116
- /**
117
- * Turn a frozen step into what the tables must do, and what nobody may decide for you.
118
- *
119
- * Pure, like `delta` — the comparison is one thing, running it is another.
120
- */
90
+ /** Turn a frozen step into what the tables must do, and what nobody may decide for you. */
121
91
  export function planStep(step: SetDiff, tables: TableDef[], options: PlanOptions = {}): Plan {
122
92
  const resolve = options.tableName ?? toTableName;
123
93
  const actual = options.actual;
@@ -191,7 +161,7 @@ function realise(
191
161
 
192
162
  case 'reshaped':
193
163
  // A tightened bound is a CHECK, and altering one on a live table is engine-specific
194
- // AND may be refused by rows already stored. The judge still enforces it at the door.
164
+ // AND may be refused by rows already stored. The validator still enforces it at the door.
195
165
  return {
196
166
  entity,
197
167
  field: change.field,
@@ -203,13 +173,7 @@ function realise(
203
173
  }
204
174
  }
205
175
 
206
- /**
207
- * An axis other than shape moved. Only some of it is a fact about the table.
208
- *
209
- * `boundary` never is — it says who may read or write, which no column holds. `lifecycle`
210
- * is one only through the DEFAULT clause. `role` is one three times over, and two of those
211
- * are constraints a live table may already contradict.
212
- */
176
+ /** An axis other than shape moved. */
213
177
  function restated(entity: string, change: Extract<ShapeChange, { kind: 'restated' }>): { change?: StepChange } | Refusal {
214
178
  const refuse = (reason: string): Refusal => ({ entity, field: change.field, reason });
215
179
 
@@ -246,13 +210,7 @@ function literalOf(rules: { create?: unknown } | undefined): unknown {
246
210
 
247
211
  const show = (value: unknown): string => (value === undefined ? 'none' : JSON.stringify(value));
248
212
 
249
- /**
250
- * Has the table already moved? Read off the columns themselves.
251
- *
252
- * A rename whose old name is gone and whose new one is there has happened; a drop whose
253
- * column is absent has happened. Unknown state (no introspection given) answers no, so a
254
- * plan built without it proposes everything the step says.
255
- */
213
+ /** Has the table already moved? Read off the columns themselves. */
256
214
  function done(change: StepChange, columns: Set<string> | undefined): boolean {
257
215
  if (!columns) return false;
258
216
  return change.kind === 'renameColumn'