dbgate-sqlite-dumper 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,234 @@
1
+ # Restore API
2
+
3
+ ```ts
4
+ restoreSqlDump({ connection, source, options?, progress?, signal? }): Promise<SqlDumpRestoreResult>
5
+ ```
6
+
7
+ Restores a plain-SQL SQLite dump using only the `SqliteConnection` abstraction — no
8
+ `sqlite3` shell, no external process. Works on dumps produced by this package **and** on
9
+ dumps produced by the native `.dump`, and on any other script the shell would accept.
10
+
11
+ ```ts
12
+ import { createReadStream } from 'node:fs';
13
+ import { restoreSqlDump } from 'dbgate-sqlite-dumper';
14
+
15
+ const result = await restoreSqlDump({
16
+ connection,
17
+ source: createReadStream('shop.sql'),
18
+ progress: event => {
19
+ if (event.phase === 'executing' && event.executionState === 'finished') {
20
+ console.log(`${event.statementsProcessed} statements, ${event.rowsRestored} rows`);
21
+ }
22
+ },
23
+ });
24
+ ```
25
+
26
+ ## `source`
27
+
28
+ ```ts
29
+ type SqlDumpSource =
30
+ string | Buffer | Uint8Array | Readable | AsyncIterable<string | Buffer | Uint8Array>;
31
+ ```
32
+
33
+ Parsed incrementally: only the current statement's text plus a few carried bytes are held
34
+ at a time, so a multi-gigabyte dump never lands in memory.
35
+
36
+ `Buffer` is accepted alongside `string` for a reason that matters: the native `.dump`
37
+ writes a `TEXT` value's stored bytes verbatim, so a database holding text that is not valid
38
+ UTF-8 produces a dump that is not valid UTF-8 either. Pass the bytes; forcing them through
39
+ `.toString()` would replace every invalid sequence with U+FFFD.
40
+
41
+ ## How a script is split
42
+
43
+ Exactly where the `sqlite3` shell splits it. Two layers, both ported from SQLite's sources:
44
+
45
+ | Construct | Behaviour |
46
+ | ------------------------------- | --------------------------------------------------------------------------------- |
47
+ | `'…'` | String; `''` is a quote. **No** backslash escapes — SQLite has none. |
48
+ | `"…"`, `` `…` ``, `[…]` | Quoted identifiers; a `;` inside never splits. |
49
+ | `-- …`, `/* … */` | Comments (block comments do not nest). A block comment may run to end of input. |
50
+ | `CREATE TRIGGER … BEGIN … END;` | One statement, inner `;`s included — the `sqlite3_complete()` state machine. |
51
+ | `GO` / `/` alone on a line | Ends the pending statement, as in the shell. |
52
+ | `.command` at column 0 | A shell dot-command; see below. |
53
+ | `# …` at column 0 | A comment line, as in the shell. |
54
+ | `\r\n` | The `\r` is dropped — everywhere, literals included — as the shell's reader does. |
55
+ | UTF-8 byte-order mark | Whitespace. |
56
+ | missing final `;` | The last statement still runs, as in the shell. |
57
+
58
+ The dot-command and `#` rules apply only when no SQL is pending — `SELECT 1\n.5;` is one
59
+ statement — exactly as the shell decides it.
60
+
61
+ `tests/statementParser.test.ts` asserts identical output at **every** chunk size and
62
+ **every** single split point; `integration/scripts.integration.test.ts` runs a set of
63
+ hand-written scripts through both the shell and this package and requires identical
64
+ databases afterwards.
65
+
66
+ ### Dot-commands
67
+
68
+ The shell runs lines beginning with `.` itself. Under the default
69
+ `dotCommands: 'skip-presentational'`:
70
+
71
+ - commands that only affect the shell's own output (`.mode`, `.headers`, `.print`, `.echo`,
72
+ `.timer`, `.output`, …) are skipped and reported as `dot-command-skipped`;
73
+ - `.quit` and `.exit` end the input, as in the shell;
74
+ - every other command (`.read`, `.import`, `.open`, `.load`, `.shell`, `.parameter`, …) is
75
+ refused with `UnsupportedClientCommandError`. Silently skipping a `.read` would leave the
76
+ referenced file's objects missing — a corrupted restore that looks like a successful one.
77
+
78
+ `dotCommands: 'error'` refuses every dot-command.
79
+
80
+ ### Text that is not valid UTF-8
81
+
82
+ A statement whose string literal holds such bytes is executed with that literal rewritten
83
+ to `(CAST(X'…' AS TEXT))`, which stores the identical bytes, and a `text-literal-rewritten`
84
+ warning says how many. Such bytes anywhere else — in an identifier — cannot be represented
85
+ and raise `InvalidTextEncodingError`.
86
+
87
+ ## `RestoreOptions`
88
+
89
+ | Option | Default | Meaning |
90
+ | --------------------- | ----------------------- | ---------------------------------------------------------------------------- |
91
+ | `stopOnError` | `true` | Stop at the first failing statement. |
92
+ | `restoreSessionState` | `true` | Put the connection back as it was; see below. |
93
+ | `schemaWrites` | `'allow'` | Virtual-table dumps write `sqlite_schema`; `'refuse'` fails such statements. |
94
+ | `transaction` | `'script'` | `'wrap'`: run everything in one transaction of the restore's own. |
95
+ | `disableForeignKeys` | `false` | `PRAGMA foreign_keys=OFF` for the restore (data-only dumps). |
96
+ | `verifyForeignKeys` | `false` | Run `PRAGMA foreign_key_check` afterwards and report violations. |
97
+ | `maxStatementBytes` | 256 MiB | Bound on one statement's buffered text. |
98
+ | `dotCommands` | `'skip-presentational'` | See above. |
99
+
100
+ ### `restoreSessionState`
101
+
102
+ A native dump is written for a shell that exits when it is done. Run through a long-lived
103
+ handle, three things would otherwise leak into the caller's session:
104
+
105
+ - **`foreign_keys`.** A dump's first line turns enforcement off, and nothing turns it back
106
+ on. It is returned to its value before the restore.
107
+ - **An open transaction.** A restore that stops at a failing statement, is cancelled, or
108
+ reads a dump truncated before its `COMMIT` would hand the handle back mid-transaction,
109
+ holding a write lock on the file. The transaction the _script_ opened is rolled back —
110
+ as the shell's exit would roll it back — and reported as `transaction-rolled-back`. A
111
+ transaction the caller opened before the restore is never touched.
112
+ - **`writable_schema`.** Turned back off if the script left it on.
113
+
114
+ ### `schemaWrites`
115
+
116
+ A dump of a database with virtual tables recreates them by inserting into `sqlite_schema`
117
+ under `PRAGMA writable_schema=ON` — the native `.dump` does exactly this, and opens with a
118
+ comment saying it requires `SQLITE_DBCONFIG_DEFENSIVE` to be off. Under `'allow'`, when the
119
+ adapter can (`setDefensive`), defensive mode is lifted at the `writable_schema=ON`
120
+ statement, restored afterwards, and reported as `defensive-mode-disabled`. The schema is
121
+ then reloaded (`PRAGMA writable_schema=RESET`), so the restored virtual tables work on the
122
+ same handle — without it they would only appear on the next connection.
123
+
124
+ ### `transaction: 'wrap'`
125
+
126
+ The restore opens its own transaction before the first statement that is not a `PRAGMA` —
127
+ so the dump's leading `PRAGMA foreign_keys=OFF` still takes effect — skips the script's own
128
+ `BEGIN`/`COMMIT`/`ROLLBACK`, and commits at the end, or rolls back everything on failure.
129
+ Meant for data-only dumps, which have no transaction of their own and would otherwise sync
130
+ to disk once per row.
131
+
132
+ ## `SqlDumpRestoreResult`
133
+
134
+ ```ts
135
+ {
136
+ statementsExecuted: number;
137
+ statementsFailed: number;
138
+ rowsRestored: number; // sum of rows changed
139
+ bytesConsumed: number;
140
+ errors: readonly RestoreStatementError[];
141
+ warnings: readonly RestoreWarning[];
142
+ cancelled: boolean;
143
+ }
144
+ ```
145
+
146
+ ## Errors
147
+
148
+ ### Parse errors — always fatal, thrown
149
+
150
+ The statement boundaries themselves cannot be trusted past the failure point.
151
+
152
+ | Error | Cause |
153
+ | ------------------------------- | --------------------------------------------------------------------- |
154
+ | `MalformedSqlDumpError` | Input ends inside a string or quoted identifier — usually truncation. |
155
+ | `StatementTooLargeError` | One statement exceeded `maxStatementBytes`. |
156
+ | `UnsupportedClientCommandError` | A dot-command that would change the database. |
157
+ | `InvalidTextEncodingError` | Bytes that are not valid UTF-8 outside any string literal. |
158
+
159
+ All carry `line`, and all extend `SqlParseError` → `RestoreError` → `SqliteDumperError`
160
+ (which has a stable `code`).
161
+
162
+ ### Execution errors — scoped to one statement
163
+
164
+ Recorded in `result.errors`; with `stopOnError: false` the restore continues.
165
+
166
+ ```ts
167
+ {
168
+ statementIndex: number;
169
+ location: { startLine: number; endLine: number };
170
+ sqlPreview: string; // ≤200 chars, whitespace-collapsed, keys redacted
171
+ message: string;
172
+ sqliteError?: { code?: string; errno?: number; message: string };
173
+ }
174
+ ```
175
+
176
+ `sqliteError.code` is SQLite's extended result code (`SQLITE_CONSTRAINT_UNIQUE`), so a
177
+ caller can branch on it instead of matching message text. `location.startLine` points at
178
+ the first real SQL character, not at the comments before it.
179
+
180
+ **No error or preview ever contains an encryption key.** `redactSecrets` covers the syntax
181
+ SQLCipher and the SQLite Encryption Extension use — `PRAGMA key`/`rekey`/`hexkey`/`textkey`
182
+ and `ATTACH … KEY` — and is applied to driver messages too.
183
+
184
+ ## Progress
185
+
186
+ ```ts
187
+ progress: event => {
188
+ // 'connecting' | 'parsing' | 'executing' | 'finalizing'
189
+ console.log(event.phase, event.statementIndex, event.currentObject, event.bytesConsumed);
190
+ };
191
+ ```
192
+
193
+ `currentObject` is read from the statements themselves (`CREATE TABLE t`, `INSERT INTO t`),
194
+ since a native dump carries no section banners.
195
+
196
+ ## Preflight
197
+
198
+ ```ts
199
+ import { introspectSqlite, preflightRestore } from 'dbgate-sqlite-dumper';
200
+
201
+ const { database } = await introspectSqlite(source);
202
+ const report = await preflightRestore({ connection: target, database });
203
+ if (report.diagnostics.some(diagnostic => diagnostic.severity === 'error')) {
204
+ // e.g. object-already-exists, unsupported-target-feature (STRICT tables on 3.31),
205
+ // virtual-table-module-unavailable, statistics-table-unsupported
206
+ }
207
+ ```
208
+
209
+ Turns a failure part-way through a restore into an up-front report. Also reports the
210
+ target's version, encoding, `foreign_keys`, open transaction, available modules and compile
211
+ options, and whether defensive mode can be lifted. Never writes to the target.
212
+
213
+ ## Using the parser on its own
214
+
215
+ ```ts
216
+ import {
217
+ parseSqlStatements,
218
+ streamSqlStatements,
219
+ isSqliteDump,
220
+ isCompleteStatement,
221
+ } from 'dbgate-sqlite-dumper';
222
+
223
+ if (!isSqliteDump(head)) throw new Error('not a SQLite dump');
224
+
225
+ for (const statement of parseSqlStatements(sql)) {
226
+ console.log(statement.statementIndex, statement.info.verb, statement.location.startLine);
227
+ }
228
+
229
+ for await (const statement of streamSqlStatements(createReadStream('big.sql'))) {
230
+ // constant memory
231
+ }
232
+
233
+ isCompleteStatement('CREATE TRIGGER t AFTER INSERT ON x BEGIN SELECT 1;'); // false
234
+ ```
@@ -0,0 +1,84 @@
1
+ # Round-trip testing
2
+
3
+ Two suites, kept apart on purpose.
4
+
5
+ | Suite | Command | Needs | What it proves |
6
+ | -------------- | -------------------------- | -------------------------- | ----------------------------------------------- |
7
+ | `tests/` | `npm test` | nothing but `node_modules` | Every layer, against in-memory databases. |
8
+ | `integration/` | `npm run test:integration` | the `sqlite3` shell | Two-way interoperability with the native shell. |
9
+
10
+ The unit suite uses `better-sqlite3` in-process, so it needs no server, no network and no
11
+ binary beyond `node_modules`. The integration suite runs the real shell.
12
+
13
+ ## Running the integration suite
14
+
15
+ ```sh
16
+ sudo apt-get install sqlite3 # or brew install sqlite
17
+ npm run test:integration
18
+ ```
19
+
20
+ - When the shell is not installed the suites skip themselves with a message.
21
+ `SQLITE_TEST_REQUIRED=1` (set in CI) turns that into a hard failure, so the suite can
22
+ never silently no-op where it was supposed to run.
23
+ - `SQLITE3_BIN=/path/to/sqlite3` tests a specific shell.
24
+ - `KEEP_TEST_OUTPUT=1` keeps every dump and database under `test-output/` for inspection;
25
+ CI uploads that directory when a run fails.
26
+
27
+ ## What is tested
28
+
29
+ ### Byte identity (`interop.integration.test.ts`)
30
+
31
+ The fixture is dumped by this package and by the shell, with each native switch and its
32
+ equivalent option (`.dump`, `--data-only`, `--preserve-rowids`, `--newlines`, `--nosys`).
33
+ Every line must be identical; a line may differ only in the digits of a `REAL` literal, and
34
+ is then compared again with each literal parsed as a double.
35
+
36
+ ### The interoperability matrix
37
+
38
+ | Path |
39
+ | ------------------------------------------ |
40
+ | this package → native `sqlite3` restore |
41
+ | native `.dump` → this package's restore |
42
+ | this package → this package |
43
+ | native `.dump` → native restore (baseline) |
44
+
45
+ Each ends by snapshotting the restored database — every `sqlite_schema` row except
46
+ `rootpage`, and every row of every table with text compared as bytes — and deep-comparing
47
+ it with the source. The baseline proves the fixture itself round-trips natively, so a
48
+ failure elsewhere is this package's.
49
+
50
+ On top of the matrix: dumping the restored database reproduces the first dump byte for byte,
51
+ restored virtual tables answer FTS5 and R-tree queries, `NUL` characters survive through
52
+ both restores, `user_version` is carried on request, and a data-only dump loads into an
53
+ existing schema with `transaction: 'wrap'`.
54
+
55
+ ### Scripts beyond `.dump` (`scripts.integration.test.ts`)
56
+
57
+ Hand-written scripts in shapes the shell accepts — trigger bodies with `;`, `END` and
58
+ `CASE`; comments everywhere; `GO` and `/` terminator lines; CRLF files and carriage returns
59
+ inside literals; `#` comments and dot-commands; every kind of quoted identifier; several
60
+ statements on a line. Each runs through the shell and through this package, and the two
61
+ databases must be identical — including where both stop on the same error.
62
+
63
+ ### Streaming (`streaming.integration.test.ts`)
64
+
65
+ A 200 000-row table (over 30 MB of SQL) is dumped with bounded heap growth, compared with
66
+ the shell's `.dump`, and restored both ways.
67
+
68
+ ## The fixture
69
+
70
+ `integration/fixture/schema.ts`: one statement per array element, executed one at a time
71
+ with `better-sqlite3` — **never through this package's parser**, so a statement-splitting
72
+ bug cannot corrupt the fixture and hide itself.
73
+
74
+ It is deliberately unfriendly: a view created before the table it reads; two tables
75
+ referencing each other; deleted rows leaving gaps in hidden rowids; names that are
76
+ keywords, contain spaces and quotes, or are quoted with `'`, `"` and `[…]`; `WITHOUT ROWID`,
77
+ `STRICT` and generated columns; partial, expression and `DESC` indexes; triggers whose
78
+ bodies contain `; END`; an FTS5 and an R-tree table; statistics from `ANALYZE`; 64-bit
79
+ extremes, infinities, `REAL`s without short representations, empty blobs, text with CRLF,
80
+ emoji, and bytes that are not valid UTF-8.
81
+
82
+ `sqlite_stat4` is dropped after `ANALYZE`: `better-sqlite3` builds with STAT4 and the
83
+ Ubuntu shell does not, and a dump carrying it cannot restore there — natively either (see
84
+ [known-limitations.md](known-limitations.md#sqlite_stat4-across-builds)).
@@ -0,0 +1,74 @@
1
+ # Supported data types
2
+
3
+ SQLite stores every value in one of five storage classes, whatever a column is declared
4
+ as. A dump writes each value according to its storage class, exactly as the native `.dump`
5
+ writes it — and restoring it yields the same storage class and the same value.
6
+
7
+ | Storage class | Written as | Example |
8
+ | ------------- | --------------------------------------------- | ---------------------------------------- |
9
+ | `NULL` | `NULL` | `NULL` |
10
+ | `INTEGER` | decimal | `9223372036854775807` |
11
+ | `REAL` | `%lld.0` when integral, else `%!.20g` | `3.0`, `0.100000000000000005`, `1.0e+20` |
12
+ | | infinities | `9.0e+999`, `-9.0e+999` |
13
+ | `TEXT` | quoted, `'` doubled, newlines via `replace()` | `replace('a\nb','\n',char(10))` |
14
+ | `BLOB` | lower-case hex | `X'00ff'`, `X''` |
15
+
16
+ ## How values are read
17
+
18
+ Row data never passes through a JavaScript number, and text never passes through the
19
+ driver's decoder. The `SELECT` that reads a table has SQLite itself produce, per column:
20
+
21
+ - `INTEGER` → `CAST(x AS TEXT)`: exact at 64 bits, whatever the driver would have done;
22
+ - `REAL` → the literal, computed in SQL with the shell's own rule — including SQLite's
23
+ `printf('%!.20g')`, so the digits are the ones SQLite writes, which no JavaScript number
24
+ formatting reproduces;
25
+ - `TEXT` → `CAST(x AS BLOB)`: the stored bytes (in a UTF-16 database, the text itself);
26
+ - `BLOB` → the bytes.
27
+
28
+ ## Notes per class
29
+
30
+ ### `INTEGER`
31
+
32
+ Exact across the full 64-bit range. `AUTOINCREMENT` counters are read as text too, so a
33
+ counter past 2^53 resumes at exactly the right value.
34
+
35
+ ### `REAL`
36
+
37
+ The literal round-trips to the identical double: `%!.20g` carries more than the 17
38
+ significant digits that identify one. The digits beyond the 17th vary between SQLite
39
+ releases (see [native-compatibility.md](native-compatibility.md#the-one-exception-real-digit-tails)),
40
+ never the value.
41
+
42
+ A `REAL` holding an integral value is written with `.0` (`3.0`), so it is restored as a
43
+ `REAL`, not an `INTEGER`. `-0.0` is written as `0.0`, as natively.
44
+
45
+ ### `TEXT`
46
+
47
+ - **Quoting:** the only escape SQLite has is a doubled `'`. There are no backslash escapes.
48
+ - **Newlines and carriage returns** are written as `replace('…\n…','\n',char(10))` (and
49
+ `char(13)`), with a placeholder that does not otherwise occur in the text — so each
50
+ `INSERT` stays on one line and survives tools that rewrite line endings. With
51
+ `dataExport.rawNewlines` (native `--newlines`) they are written raw instead.
52
+ - **Bytes that are not valid UTF-8** — SQLite does not validate what it stores — are
53
+ written raw, as natively, so the dump reproduces the stored value exactly. Such a dump is
54
+ not valid UTF-8, which is why the writer and the restore handle bytes. On restore through
55
+ a JavaScript driver, such a literal is executed as `CAST(X'…' AS TEXT)`, the identical
56
+ bytes.
57
+ - **`NUL` characters** are kept, written as `'a'||char(0)||'b'`. The native shell truncates
58
+ text at the first `NUL`; this is the one place this package writes something the shell
59
+ would not, and only for text the shell cannot represent.
60
+
61
+ ### `BLOB`
62
+
63
+ Always hex. An empty blob is `X''`, distinct from `NULL` and from the empty string `''`.
64
+
65
+ ## Declared types
66
+
67
+ A column's declared type — `VARCHAR(20)`, `DATETIME`, `BOOLEAN`, `JSON`, or none at all —
68
+ is part of the table's DDL and is reproduced verbatim. It only determines the column's
69
+ _affinity_, which SQLite applies on insert; since every value is written in the storage
70
+ class it already has, affinity cannot change it on restore. `STRICT` tables are restored as
71
+ `STRICT`, with their type checks.
72
+
73
+ Dates and times have no storage class of their own in SQLite; they are whatever text,
74
+ number or blob the application stored, and are dumped as such.
@@ -0,0 +1,85 @@
1
+ # Supported objects
2
+
3
+ "Round-trip tested" means the object survives dump → restore and the restored database's
4
+ schema and rows deep-compare equal to the source, across all four paths of the
5
+ interoperability matrix (this package and the native `sqlite3` shell, each dumping and each
6
+ restoring).
7
+
8
+ ## Object matrix
9
+
10
+ | Object | Dumped | Restored | Round-trip tested | Notes |
11
+ | -------------------------------------- | :----: | :------: | :---------------: | ---------------------------------------------------------------------- |
12
+ | Tables | ✅ | ✅ | ✅ | Verbatim stored DDL, so every clause survives. |
13
+ | Columns, defaults, collations, `CHECK` | ✅ | ✅ | ✅ | Inline in the table DDL. |
14
+ | Primary keys, `UNIQUE` constraints | ✅ | ✅ | ✅ | Their automatic indexes are recreated by the table DDL. |
15
+ | Foreign keys | ✅ | ✅ | ✅ | Circular and self-referencing both covered. |
16
+ | `INTEGER PRIMARY KEY` (rowid alias) | ✅ | ✅ | ✅ | The rowid _is_ the column, so it is always kept. |
17
+ | Hidden rowids | ✅ | ⚠️ | ✅ | Renumbered unless `preserveRowids`; see below. |
18
+ | `AUTOINCREMENT` counters | ✅ | ✅ | ✅ | `sqlite_sequence`, set after the rows. |
19
+ | `WITHOUT ROWID` tables | ✅ | ✅ | ✅ | |
20
+ | `STRICT` tables | ✅ | ✅ | ✅ | SQLite 3.37+ on the restoring side. |
21
+ | Generated columns (`VIRTUAL`/`STORED`) | ✅ | ✅ | ✅ | Not inserted; SQLite recomputes them. |
22
+ | Quoted and keyword names | ✅ | ✅ | ✅ | `"…"`, `'…'`, `[…]`, keywords, non-ASCII. |
23
+ | Indexes | ✅ | ✅ | ✅ | Partial, expression, `DESC`, `COLLATE`. |
24
+ | Views | ✅ | ✅ | ✅ | Including one created before the table it reads. |
25
+ | Triggers | ✅ | ✅ | ✅ | Multi-statement bodies; `INSTEAD OF` on views; created after the data. |
26
+ | Virtual tables (FTS5, R-tree, …) | ✅ | ✅ | ✅ | Shadow tables copied exactly; queryable after restore. |
27
+ | Statistics (`sqlite_stat1`/`stat4`) | ✅ | ✅ | ✅ | `ANALYZE sqlite_schema;` then the rows. |
28
+ | `user_version`, `application_id` | ⚙️ | ✅ | ✅ | With `render.includeDatabaseSettings`; otherwise reported. |
29
+ | Attached databases | ⚙️ | ✅ | — | One schema per dump: pass `schemaName`. |
30
+ | File settings (page size, WAL, …) | ❌ | — | — | See [known-limitations.md](known-limitations.md). |
31
+ | Application functions and collations | ❌ | — | — | Registered by the application, not stored in the database. |
32
+
33
+ ⚙️ = opt-in.
34
+
35
+ ## Model coverage
36
+
37
+ `introspectSqlite` returns a normalized `SqliteDatabase`:
38
+
39
+ ```ts
40
+ {
41
+ schemaName, encoding, userVersion, applicationId, pageSize,
42
+ tables: SqliteTable[], // kind (table | virtual | shadow | system), sql, withoutRowid, strict,
43
+ // hasAutoincrement, virtualModule, ownerVirtualTable, rowidAliasColumn, columns
44
+ indexes: SqliteIndex[], // tableName, sql (null for automatic), isUnique, origin, isPartial, columns
45
+ views: SqliteView[],
46
+ triggers: SqliteTrigger[], // tableName
47
+ foreignKeys: SqliteForeignKey[], // referencedTableName, column pairs, onUpdate, onDelete, match
48
+ sequences: SqliteSequence[], // AUTOINCREMENT counters, as exact text
49
+ }
50
+ ```
51
+
52
+ Per column: `declaredType` as written, `notNull`, `defaultValue` as written,
53
+ `primaryKeyPosition`, and `hidden`/`generated` from `PRAGMA table_xinfo`.
54
+
55
+ Every object also carries its `sqlite_schema` rowid (`schemaRowid`), which is the order the
56
+ dump uses.
57
+
58
+ ## Notable behaviours
59
+
60
+ ### Hidden rowids
61
+
62
+ A table without an `INTEGER PRIMARY KEY` still has a rowid, just not a declared column. The
63
+ native `.dump` does not write it, so a restore numbers the rows 1, 2, 3… in dump order —
64
+ closing any gaps deletions left. With `dataExport.preserveRowids` (the native
65
+ `--preserve-rowids`), the rowid is named in each `INSERT` and kept, under the first of
66
+ `rowid`, `_rowid_`, `oid` that is not a real column.
67
+
68
+ ### Triggers
69
+
70
+ Created after all table data. A trigger created before the load would fire once per
71
+ restored row — an `AFTER INSERT` trigger writing an audit table would fabricate rows the
72
+ source never had. The archive records that as a _hard_ dependency. A trigger whose table is
73
+ not selected is dropped and reported.
74
+
75
+ ### Virtual tables
76
+
77
+ Recreated the way the native `.dump` does it — by inserting the table's `sqlite_schema` row
78
+ directly, under `PRAGMA writable_schema=ON` — then filling the shadow tables with the
79
+ source's exact rows. The module's constructor never runs, so an FTS5 index or an R-tree
80
+ comes back byte-identical rather than rebuilt. This needs defensive mode off on the
81
+ restoring handle, which the restore arranges and undoes (see
82
+ [restore-api.md](restore-api.md#schemawrites)).
83
+
84
+ A schema-only dump instead creates each virtual table with its own `CREATE VIRTUAL TABLE`,
85
+ since no shadow rows follow and the module must set up its storage itself.
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "dbgate-sqlite-dumper",
3
+ "version": "0.1.0",
4
+ "description": "Standalone, client-agnostic SQLite SQL dump and restore library for Node.js, compatible with the native sqlite3 .dump command",
5
+ "type": "module",
6
+ "license": "GPL-3.0-only",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/dbgate/dbgate-sqlite-dumper.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/dbgate/dbgate-sqlite-dumper/issues"
13
+ },
14
+ "homepage": "https://github.com/dbgate/dbgate-sqlite-dumper#readme",
15
+ "engines": {
16
+ "node": ">=20"
17
+ },
18
+ "files": [
19
+ "LICENSE",
20
+ "README.md",
21
+ "dist",
22
+ "docs"
23
+ ],
24
+ "main": "./dist/index.cjs",
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js",
31
+ "require": "./dist/index.cjs"
32
+ },
33
+ "./better-sqlite3": {
34
+ "types": "./dist/better-sqlite3.d.ts",
35
+ "import": "./dist/better-sqlite3.js",
36
+ "require": "./dist/better-sqlite3.cjs"
37
+ }
38
+ },
39
+ "scripts": {
40
+ "build": "tsup",
41
+ "clean": "tsup --clean",
42
+ "format": "prettier --write .",
43
+ "format:check": "prettier --check .",
44
+ "lint": "eslint . --max-warnings 0",
45
+ "prepublishOnly": "npm run lint && npm run typecheck && npm test && npm run build && node scripts/smoke.mjs",
46
+ "test": "vitest run",
47
+ "test:all": "npm run test && npm run test:integration",
48
+ "test:integration": "vitest run --config vitest.integration.config.ts",
49
+ "test:package": "npm run build && node scripts/smoke.mjs",
50
+ "test:watch": "vitest",
51
+ "typecheck": "tsc --noEmit"
52
+ },
53
+ "keywords": [
54
+ "sqlite",
55
+ "sqlite3",
56
+ "dump",
57
+ ".dump",
58
+ "sql",
59
+ "backup",
60
+ "restore",
61
+ "database",
62
+ "better-sqlite3"
63
+ ],
64
+ "sideEffects": false,
65
+ "publishConfig": {
66
+ "access": "public",
67
+ "provenance": true
68
+ },
69
+ "peerDependencies": {
70
+ "better-sqlite3": ">=9.0.0"
71
+ },
72
+ "peerDependenciesMeta": {
73
+ "better-sqlite3": {
74
+ "optional": true
75
+ }
76
+ },
77
+ "devDependencies": {
78
+ "@eslint/js": "^9.25.1",
79
+ "@types/better-sqlite3": "^7.6.13",
80
+ "@types/node": "^22.15.3",
81
+ "better-sqlite3": "^12.2.0",
82
+ "eslint": "^9.25.1",
83
+ "prettier": "^3.5.3",
84
+ "tsup": "^8.4.0",
85
+ "typescript": "^5.8.3",
86
+ "typescript-eslint": "^8.31.0",
87
+ "vitest": "^3.1.2"
88
+ }
89
+ }