rqlite-js 2.6.0 → 3.0.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 (98) hide show
  1. package/README.md +207 -29
  2. package/dist/index.cjs +904 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +329 -0
  5. package/dist/index.d.ts +329 -0
  6. package/dist/index.js +865 -0
  7. package/dist/index.js.map +1 -0
  8. package/docs/MIGRATION-v2-to-v3.md +71 -0
  9. package/package.json +56 -35
  10. package/src/api/backup/index.ts +69 -0
  11. package/src/api/backup-formats.ts +8 -0
  12. package/src/api/client/index.ts +97 -0
  13. package/src/api/consistency-levels.ts +10 -0
  14. package/src/api/data/index.ts +87 -0
  15. package/src/api/paths.ts +13 -0
  16. package/src/api/query-params.ts +15 -0
  17. package/src/api/results/data-result-error.ts +41 -0
  18. package/src/api/results/data-result.ts +91 -0
  19. package/src/api/results/data-results.ts +91 -0
  20. package/src/api/results/index.ts +7 -0
  21. package/src/api/results/result-fields.ts +14 -0
  22. package/src/api/status/index.ts +53 -0
  23. package/src/http-request/auth-keys.ts +14 -0
  24. package/src/http-request/content-types.ts +4 -0
  25. package/src/http-request/errors.ts +17 -0
  26. package/src/http-request/http-headers.ts +19 -0
  27. package/src/http-request/http-methods.ts +12 -0
  28. package/src/http-request/index.ts +676 -0
  29. package/src/http-request/retryable.ts +41 -0
  30. package/src/index.ts +38 -0
  31. package/lib/api/backup/index.js +0 -72
  32. package/lib/api/backup/index.js.map +0 -1
  33. package/lib/api/backup/test.unit.js +0 -45
  34. package/lib/api/backup/test.unit.js.map +0 -1
  35. package/lib/api/backup-formats.js +0 -16
  36. package/lib/api/backup-formats.js.map +0 -1
  37. package/lib/api/client/index.js +0 -105
  38. package/lib/api/client/index.js.map +0 -1
  39. package/lib/api/client/test.unit.js +0 -53
  40. package/lib/api/client/test.unit.js.map +0 -1
  41. package/lib/api/consistency-levels.js +0 -30
  42. package/lib/api/consistency-levels.js.map +0 -1
  43. package/lib/api/data/index.js +0 -108
  44. package/lib/api/data/index.js.map +0 -1
  45. package/lib/api/data/test.unit.js +0 -98
  46. package/lib/api/data/test.unit.js.map +0 -1
  47. package/lib/api/paths.js +0 -33
  48. package/lib/api/paths.js.map +0 -1
  49. package/lib/api/query-params.js +0 -43
  50. package/lib/api/query-params.js.map +0 -1
  51. package/lib/api/results/data-result-error.js +0 -62
  52. package/lib/api/results/data-result-error.js.map +0 -1
  53. package/lib/api/results/data-result.js +0 -141
  54. package/lib/api/results/data-result.js.map +0 -1
  55. package/lib/api/results/data-results.js +0 -142
  56. package/lib/api/results/data-results.js.map +0 -1
  57. package/lib/api/results/index.js +0 -8
  58. package/lib/api/results/index.js.map +0 -1
  59. package/lib/api/results/result-fields.js +0 -47
  60. package/lib/api/results/result-fields.js.map +0 -1
  61. package/lib/api/results/test.unit.js +0 -238
  62. package/lib/api/results/test.unit.js.map +0 -1
  63. package/lib/api/status/index.js +0 -67
  64. package/lib/api/status/index.js.map +0 -1
  65. package/lib/api/status/test.unit.js +0 -57
  66. package/lib/api/status/test.unit.js.map +0 -1
  67. package/lib/http-request/auth-keys.js +0 -15
  68. package/lib/http-request/auth-keys.js.map +0 -1
  69. package/lib/http-request/content-types.js +0 -5
  70. package/lib/http-request/content-types.js.map +0 -1
  71. package/lib/http-request/errors.js +0 -17
  72. package/lib/http-request/errors.js.map +0 -1
  73. package/lib/http-request/http-headers.js +0 -20
  74. package/lib/http-request/http-headers.js.map +0 -1
  75. package/lib/http-request/http-methods.js +0 -40
  76. package/lib/http-request/http-methods.js.map +0 -1
  77. package/lib/http-request/index.js +0 -764
  78. package/lib/http-request/index.js.map +0 -1
  79. package/lib/http-request/retryable.js +0 -42
  80. package/lib/http-request/retryable.js.map +0 -1
  81. package/lib/http-request/test.unit.js +0 -560
  82. package/lib/http-request/test.unit.js.map +0 -1
  83. package/lib/index.js +0 -39
  84. package/lib/index.js.map +0 -1
  85. package/lib/test/api-data-execute-nock.js +0 -142
  86. package/lib/test/api-data-execute-nock.js.map +0 -1
  87. package/lib/test/api-data-query-nock.js +0 -210
  88. package/lib/test/api-data-query-nock.js.map +0 -1
  89. package/lib/test/backups/backup-nock.js +0 -29
  90. package/lib/test/backups/backup-nock.js.map +0 -1
  91. package/lib/test/backups/restore-nock.js +0 -30
  92. package/lib/test/backups/restore-nock.js.map +0 -1
  93. package/lib/test/env.js +0 -15
  94. package/lib/test/env.js.map +0 -1
  95. package/lib/test/unit/index.unit.js +0 -27
  96. package/lib/test/unit/index.unit.js.map +0 -1
  97. package/lib/test.integration.js +0 -238
  98. package/lib/test.integration.js.map +0 -1
package/README.md CHANGED
@@ -1,22 +1,31 @@
1
1
  # rqlite-js · [![npm version](https://img.shields.io/npm/v/rqlite-js.svg?style=flat)](https://www.npmjs.com/package/rqlite-js) · [![CI](https://github.com/rqlite/rqlite-js/actions/workflows/ci.yml/badge.svg)](https://github.com/rqlite/rqlite-js/actions/workflows/ci.yml)
2
2
 
3
- A promise-based client library for [rqlite](https://rqlite.io), the lightweight, distributed database built on SQLite. Clients mirror the HTTP API endpoints (`/db/query`, `/db/execute`, `/db/backup`, `/status`). This package does not generate SQL—use your own SQL strings or another SQL builder.
3
+ A promise-based TypeScript/JavaScript client for [rqlite](https://rqlite.io) — the lightweight, distributed database built on SQLite. It speaks the HTTP API (`/db/query`, `/db/execute`, `/db/backup`, `/status`) and returns structured results you can use in Node or the browser.
4
4
 
5
- **Requirements:** Node.js `>= 18`. CI tests Node 18–24 on `ubuntu-latest`; Docker integrations use Node 24. Tested against **rqlite 10.x** (Docker image `rqlite/rqlite:10.6.6`).
5
+ **This package does not generate SQL.** Pass SQL strings (or rqlite parameterized statement arrays). Build those strings however you like — by hand, or with any SQL builder you choose.
6
6
 
7
- See the official docs:
7
+ **Prefer parameterized statements for anything that includes user or external input.** Interpolating values into SQL strings (`WHERE name = "${name}"`) risks SQL injection. Use rqlite’s bound-parameter form (`[["SELECT … WHERE name = ?", name]]`) or equivalent bindings from your own SQL tooling. Treat string-concatenated SQL as unsafe unless the values are strictly trusted constants you control.
8
+
9
+ **Requirements:** Node.js `>= 20.19`. **Zero runtime dependencies** (native `fetch`). Types ship in-box. CI tests Node 20–24; Docker integrations use Node 24 against **rqlite 10.x** (`rqlite/rqlite:10.6.6`).
10
+
11
+ Upgrading from v2? See [docs/MIGRATION-v2-to-v3.md](docs/MIGRATION-v2-to-v3.md).
12
+
13
+ Official rqlite docs:
8
14
 
9
15
  - [Accessing rqlite (HTTP API)](https://rqlite.io/docs/api/api/)
10
16
  - [Read consistency](https://rqlite.io/docs/api/read-consistency/)
17
+ - [Bulk writes](https://rqlite.io/docs/api/bulk-api/)
11
18
  - [Backup and restore](https://rqlite.io/docs/guides/backup/)
12
19
  - [Monitoring / status](https://rqlite.io/docs/guides/monitoring-rqlite/)
13
20
 
14
21
  ## Features
15
22
 
16
- - Follow 301/302 redirects from followers to the leader
17
- - Round-robin host balancing for non-leader reads
18
- - HTTP keepalive via `http` / `https` agents
19
- - Unit and Docker integration tests
23
+ - Dual **ESM + CJS** builds with TypeScript declarations (import from `rqlite-js` only — no deep paths)
24
+ - Native `fetch` HTTP (optional undici `Dispatcher` for keepalive)
25
+ - Follow 301/302 redirects to the leader; round-robin hosts for non-leader reads
26
+ - Single or bulk `query` / `execute`, parameterized statements, consistency levels, transactions
27
+ - `DataResults` helpers (`get`, `toObject`, `toArray`, `hasError`, `getFirstError` / `error_v2`)
28
+ - Backup / restore streams and `/status` clients
20
29
 
21
30
  ## Install
22
31
 
@@ -24,6 +33,16 @@ See the official docs:
24
33
  npm install rqlite-js
25
34
  ```
26
35
 
36
+ ```javascript
37
+ // ESM
38
+ import { DataApiClient } from 'rqlite-js'
39
+
40
+ // CommonJS
41
+ const { DataApiClient } = require('rqlite-js')
42
+ ```
43
+
44
+ Consumers get **no transitive runtime packages**. Build tooling stays in this repo’s `devDependencies` only.
45
+
27
46
  ## Quick start
28
47
 
29
48
  ```javascript
@@ -31,7 +50,9 @@ import { DataApiClient } from 'rqlite-js'
31
50
 
32
51
  const dataApiClient = new DataApiClient('http://localhost:4001')
33
52
 
34
- await dataApiClient.execute('CREATE TABLE IF NOT EXISTS foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)')
53
+ await dataApiClient.execute(
54
+ 'CREATE TABLE IF NOT EXISTS foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)',
55
+ )
35
56
  const insert = await dataApiClient.execute('INSERT INTO foo(name) VALUES("fiona")')
36
57
  if (insert.hasError()) {
37
58
  throw insert.getFirstError()
@@ -41,14 +62,107 @@ const select = await dataApiClient.query(`SELECT id, name FROM foo WHERE id=${id
41
62
  console.log(select.get(0).toObject()) // { id: 1, name: 'fiona' }
42
63
  ```
43
64
 
65
+ ## Optional: building SQL with Knex (not included)
66
+
67
+ **Knex is not part of `rqlite-js`.** It is an optional third-party dependency you may install separately if you want a query builder. This repo does not depend on or bundle Knex. The examples below are for illustration only.
68
+
69
+ `rqlite-js` only accepts SQL (or rqlite’s parameterized JSON form). If you use [Knex](https://knexjs.org/), generate SQL with it, then pass that into this client. You do **not** need a Knex connection pool to rqlite — configure the SQLite dialect for SQL generation only.
70
+
71
+ ```bash
72
+ npm install knex # optional — only if you want Knex in your app
73
+ ```
74
+
75
+ ```javascript
76
+ import knex from 'knex' // your app's dependency, not rqlite-js
77
+ import { DataApiClient } from 'rqlite-js'
78
+
79
+ const dataApiClient = new DataApiClient('http://localhost:4001')
80
+
81
+ // Dialect only — no DB connection. SQLite placeholders (?) match rqlite.
82
+ const qb = knex({ client: 'sqlite3' })
83
+
84
+ async function execBuilder (builder) {
85
+ const { sql, bindings } = builder.toSQL()
86
+ // Safer than toQuery(): keep values as bound parameters
87
+ return dataApiClient.execute([[sql, ...bindings]])
88
+ }
89
+
90
+ async function queryBuilder (builder, options) {
91
+ const { sql, bindings } = builder.toSQL()
92
+ return dataApiClient.query([[sql, ...bindings]], options)
93
+ }
94
+
95
+ await execBuilder(
96
+ qb.schema.createTableIfNotExists('foo', (table) => {
97
+ table.increments('id').primary()
98
+ table.text('name')
99
+ }),
100
+ )
101
+
102
+ await execBuilder(qb('foo').insert({ name: 'fiona' }))
103
+
104
+ const rows = await queryBuilder(
105
+ qb('foo').select('id', 'name').where('name', 'fiona').limit(10),
106
+ )
107
+ console.log(rows.toArray()) // [{ id: 1, name: 'fiona' }]
108
+ ```
109
+
110
+ Bulk inserts from Knex builders (same idea — Knex stays in your app):
111
+
112
+ ```javascript
113
+ const statements = ['fiona', 'justin'].map((name) => {
114
+ const { sql, bindings } = qb('foo').insert({ name }).toSQL()
115
+ return [sql, ...bindings]
116
+ })
117
+ await dataApiClient.execute(statements, { transaction: true })
118
+ ```
119
+
120
+ Fully interpolated SQL via Knex `toQuery()` works with plain string `execute` / `query`, but **prefer `toSQL()` + bindings** whenever values are not compile-time constants:
121
+
122
+ ```javascript
123
+ // OK only when every value is trusted (e.g. fixed id in your own code)
124
+ const sql = qb('foo').where('id', 1).select('*').toQuery()
125
+ await dataApiClient.query(sql)
126
+ ```
127
+
128
+
44
129
  ## DataApiClient
45
130
 
46
- Access the [data API](https://rqlite.io/docs/api/api/) (`query` / `execute`). By default methods return a `DataResults` instance. Pass `{ raw: true }` to get `{ status, body }` instead.
131
+ Access the [data API](https://rqlite.io/docs/api/api/). By default methods return a `DataResults` instance. Pass `{ raw: true }` to get `{ status, body }` instead.
47
132
 
48
133
  ### Methods
49
134
 
50
- - `query(sql, options)` — read via HTTP GET (or POST when `sql` is an array)
51
- - `execute(sql, options)` — write via HTTP POST (`sql` may be a string or string array)
135
+ - `query(sql, options)` — read via HTTP GET (or POST when `sql` is an array / parameterized batch)
136
+ - `execute(sql, options)` — write via HTTP POST (`sql` may be a string, string array, or parameterized nested arrays)
137
+
138
+ ### Parameterized statements (recommended for safety)
139
+
140
+ rqlite accepts bound parameters as nested JSON arrays. The client forwards them as-is. **This is the safest way to pass dynamic values** — the server binds parameters separately from the SQL text, so user input cannot change the statement structure.
141
+
142
+ ```javascript
143
+ // Safe: values are parameters, not concatenated into the SQL string
144
+ const name = req.body.name // untrusted input
145
+ await dataApiClient.execute([
146
+ ['INSERT INTO foo(name, age) VALUES(?, ?)', name, 36],
147
+ ])
148
+
149
+ const results = await dataApiClient.query([
150
+ ['SELECT name, age FROM foo WHERE name = ?', name],
151
+ ])
152
+ console.log(results.get(0).toObject())
153
+ ```
154
+
155
+ ```javascript
156
+ // Unsafe: do not build SQL by string interpolation with untrusted data
157
+ // await dataApiClient.query(`SELECT * FROM foo WHERE name = "${name}"`)
158
+ ```
159
+
160
+ **Security tips for npm consumers:**
161
+
162
+ - Use parameterized statements (or your SQL builder’s bound parameters, e.g. Knex `toSQL()` if you use it) for all request/query-string/body values.
163
+ - Never concatenate untrusted input into SQL identifiers either (table/column names). Keep those as fixed allow-lists in your app code.
164
+ - `{ transaction: true }` / `{ atomic: true }` help with atomicity, not injection — they do not replace parameterization.
165
+ - Review any helper that calls `.toQuery()` or similar “print full SQL” APIs; those embed values into the string and are only appropriate for trusted constants.
52
166
 
53
167
  ### Create table
54
168
 
@@ -65,7 +179,6 @@ if (dataResults.hasError()) {
65
179
  console.log(dataResults.toArray())
66
180
  }
67
181
 
68
- // Raw HTTP response shape from this client:
69
182
  const { status, body } = await dataApiClient.execute(
70
183
  'CREATE TABLE IF NOT EXISTS bar (id INTEGER NOT NULL PRIMARY KEY)',
71
184
  { raw: true },
@@ -94,9 +207,9 @@ dataResults = await dataApiClient.execute('UPDATE foo SET name="justin" WHERE na
94
207
  console.log(dataResults.get(0).getRowsAffected()) // 1
95
208
  ```
96
209
 
97
- ### Transactions (rqlite v5+)
210
+ ### Bulk statements and transactions
98
211
 
99
- Use `{ atomic: true }` for a single transaction ([API docs](https://rqlite.io/docs/api/api/)). Legacy `{ transaction: true }` remains for older rqlite v4 servers.
212
+ Send multiple statements in one HTTP request as a JSON array. Use `{ atomic: true }` (rqlite v5+) or `{ transaction: true }` to wrap them in a SQLite transaction (failed batches roll back when `transaction` is set).
100
213
 
101
214
  ```javascript
102
215
  import { DataApiClient } from 'rqlite-js'
@@ -106,7 +219,7 @@ const sql = [
106
219
  'INSERT INTO foo(name) VALUES("fiona")',
107
220
  'INSERT INTO foo(name) VALUES("justin")',
108
221
  ]
109
- const dataResults = await dataApiClient.execute(sql, { atomic: true })
222
+ const dataResults = await dataApiClient.execute(sql, { transaction: true })
110
223
  if (dataResults.hasError()) {
111
224
  throw dataResults.getFirstError()
112
225
  }
@@ -114,10 +227,58 @@ console.log(dataResults.get(0).getLastInsertId())
114
227
  console.log(dataResults.get(1).getLastInsertId())
115
228
  ```
116
229
 
230
+ Bulk **reads** use the same array form (HTTP POST):
231
+
232
+ ```javascript
233
+ const dataResults = await dataApiClient.query([
234
+ 'SELECT name FROM foo WHERE id = 1',
235
+ 'SELECT COUNT(*) AS total FROM foo',
236
+ ])
237
+ ```
238
+
239
+ ### Paging large reads
240
+
241
+ rqlite has no separate cursor API — page in SQL with `LIMIT` / `OFFSET` (or the equivalent helpers in whatever SQL builder you use):
242
+
243
+ ```javascript
244
+ const pageSize = 50
245
+ for (let offset = 0; ; offset += pageSize) {
246
+ const page = await dataApiClient.query(
247
+ `SELECT id, name FROM foo ORDER BY id LIMIT ${pageSize} OFFSET ${offset}`,
248
+ )
249
+ const rows = page.toArray()
250
+ if (rows.length === 0) break
251
+ console.log(rows)
252
+ if (rows.length < pageSize) break
253
+ }
254
+ ```
255
+
256
+ ### Results shape notes
257
+
258
+ - One SELECT with **N value rows** → N `DataResult` entries (`toArray()` / `get(i)`).
259
+ - A SELECT with **no matching rows** often omits `values`; this client then yields a single empty `DataResult` (`toObject()` → `{}`). Check `toArray()` / column getters rather than assuming `getResults().length === 0`.
260
+ - Aggregates like `COUNT(*)` still return one row object.
261
+
262
+ Map rows into plain objects or your own types with `toObject()` / `toArray()` — Knex has no model layer, and this client does not integrate with ORMs:
263
+
264
+ ```javascript
265
+ const dataResults = await dataApiClient.query('SELECT id, name FROM foo')
266
+ const rows = dataResults.toArray() // [{ id: 1, name: 'fiona' }, ...]
267
+
268
+ // Optional: map into your app's types
269
+ const users = rows.map((row) => ({ id: row.id, displayName: row.name }))
270
+ ```
271
+
117
272
  ### Consistency
118
273
 
119
274
  ```javascript
120
- import { DataApiClient, CONSISTENCY_LEVEL_STRONG } from 'rqlite-js'
275
+ import {
276
+ DataApiClient,
277
+ CONSISTENCY_LEVEL_NONE,
278
+ CONSISTENCY_LEVEL_WEAK,
279
+ CONSISTENCY_LEVEL_STRONG,
280
+ CONSISTENCY_LEVEL_LINEARIZABLE,
281
+ } from 'rqlite-js'
121
282
 
122
283
  const dataApiClient = new DataApiClient('http://localhost:4001')
123
284
  const dataResults = await dataApiClient.query(
@@ -126,11 +287,13 @@ const dataResults = await dataApiClient.query(
126
287
  )
127
288
  ```
128
289
 
129
- See [read consistency](https://rqlite.io/docs/api/read-consistency/) for `none`, `weak`, `strong`, and `linearizable`.
290
+ Levels: `none`, `weak` (default), `strong`, `linearizable`. See [read consistency](https://rqlite.io/docs/api/read-consistency/). On a single node, all levels succeed; choose based on your cluster topology.
291
+
292
+ Optional flags: `{ timings: true }` (response timing fields), `{ pretty: true }` (pretty-printed JSON from the server).
130
293
 
131
294
  ### Errors (`error` and `error_v2`)
132
295
 
133
- Failed statements still use the legacy `error` string. On rqlite v10+, structured `error_v2` is also exposed:
296
+ Failed statements use the legacy `error` string. On rqlite v10+, structured `error_v2` is also exposed:
134
297
 
135
298
  ```javascript
136
299
  const dataResults = await dataApiClient.execute('INSERT INTO nonsense')
@@ -158,16 +321,17 @@ const dataApiClient = new DataApiClient('http://localhost:4001', {
158
321
  ### Client options (hosts, agents, retries)
159
322
 
160
323
  ```javascript
161
- import http from 'http'
162
- import https from 'https'
324
+ import { Agent } from 'undici'
163
325
  import { DataApiClient } from 'rqlite-js'
164
326
 
327
+ const dispatcher = new Agent({ keepAliveTimeout: 10_000 })
328
+
165
329
  const dataApiClient = new DataApiClient(
166
330
  ['http://localhost:4001', 'http://localhost:4002', 'http://localhost:4003'],
167
331
  {
168
- httpAgent: new http.Agent({ keepAlive: true }),
169
- httpsAgent: new https.Agent({ keepAlive: true }),
170
- // Optional retry customization:
332
+ // undici Dispatcher (has .dispatch) — used by native fetch for keepalive pools
333
+ httpAgent: dispatcher,
334
+ httpsAgent: dispatcher,
171
335
  retryableStatusCodes: new Set([408, 429, 500, 502, 503, 504]),
172
336
  retryableErrorCodes: new Set(['ECONNRESET', 'ETIMEDOUT']),
173
337
  retryableHttpMethods: new Set(['GET', 'POST']),
@@ -176,7 +340,7 @@ const dataApiClient = new DataApiClient(
176
340
  )
177
341
  ```
178
342
 
179
- Hosts may also be a comma-separated string: `'http://localhost:4001,http://localhost:4002'`.
343
+ Hosts may also be a comma-separated string: `'http://localhost:4001,http://localhost:4002'`. Classic `http.Agent` / `https.Agent` option names remain for API compatibility; pass an undici `Dispatcher` when you need fetch keepalive.
180
344
 
181
345
  ## StatusApiClient
182
346
 
@@ -187,7 +351,6 @@ const statusApiClient = new StatusApiClient('http://localhost:4001')
187
351
  const { status, body } = await statusApiClient.status()
188
352
  console.log(status, body.store?.ready)
189
353
 
190
- // One status call per configured host:
191
354
  const all = await statusApiClient.statusAllHosts()
192
355
  all.forEach(({ host, response }) => {
193
356
  console.log(host, response.body?.store?.ready)
@@ -198,34 +361,49 @@ See [Monitoring rqlite](https://rqlite.io/docs/guides/monitoring-rqlite/).
198
361
 
199
362
  ## BackupApiClient
200
363
 
364
+ Backup/load return Node `Readable` streams.
365
+
201
366
  ```javascript
202
367
  import { BackupApiClient, BACKUP_DATA_FORMAT_SQL } from 'rqlite-js'
368
+ import { pipeline } from 'node:stream/promises'
369
+ import { createWriteStream } from 'node:fs'
203
370
 
204
371
  const backupApiClient = new BackupApiClient('http://localhost:4001')
205
372
 
206
- // Binary SQLite backup stream (default):
373
+ // Binary SQLite dump (default):
207
374
  const backupStream = await backupApiClient.backup()
375
+ await pipeline(backupStream, createWriteStream('backup.sqlite'))
208
376
 
209
377
  // SQL text backup:
210
378
  const sqlStream = await backupApiClient.backup(BACKUP_DATA_FORMAT_SQL)
211
379
 
212
380
  // Restore from SQL buffer/string:
213
381
  await backupApiClient.load(
214
- Buffer.from('CREATE TABLE foo (id INTEGER PRIMARY KEY);'),
382
+ Buffer.from('CREATE TABLE foo (id INTEGER PRIMARY KEY); INSERT INTO foo DEFAULT VALUES;'),
215
383
  BACKUP_DATA_FORMAT_SQL,
216
384
  )
217
385
  ```
218
386
 
219
387
  See [Backup and restore](https://rqlite.io/docs/guides/backup/).
220
388
 
389
+ ## Package surface (npm)
390
+
391
+ | Item | Detail |
392
+ |------|--------|
393
+ | Import | `rqlite-js` only (ESM + CJS) |
394
+ | Types | Bundled `.d.ts` / `.d.cts` — no `@types/rqlite-js` |
395
+ | Runtime deps | None |
396
+ | Engines | `node >= 20.19` |
397
+ | Published | `dist/` (runtime) + `src/` (TypeScript sources for debugging) |
398
+
221
399
  ## Releases
222
400
 
223
- Versions and `CHANGELOG.md` are managed with [release-please](https://github.com/googleapis/release-please). Merging the release PR creates a GitHub Release; the publish workflow re-runs the full test suite (including Docker integrations) then runs [`npm stage publish`](https://docs.npmjs.com/staged-publishing/) via [trusted publishing](https://docs.npmjs.com/trusted-publishers/) (OIDC). A maintainer must then approve the staged version with 2FA on npmjs.com (or `npm stage approve <id>`) before it goes live.
401
+ Versions and `CHANGELOG.md` are managed with [release-please](https://github.com/googleapis/release-please). Merging the release PR creates a GitHub Release; the workflow re-runs the full suite (including Docker integrations) and runs [`npm stage publish`](https://docs.npmjs.com/staged-publishing/) via [trusted publishing](https://docs.npmjs.com/trusted-publishers/) (OIDC). A maintainer must approve the staged version with 2FA on npmjs.com (or `npm stage approve <id>`) before it goes live.
224
402
 
225
403
  **Local quality gate (required before commit/push/publish):**
226
404
 
227
405
  ```bash
228
- npm run verify # lint, type-check, unit, build, compiled unit
406
+ npm run verify # lint, type-check, unit, build, pack checks
229
407
  npm run verify:integrations # Docker + rqlite 10.6.6 integrations
230
408
  ```
231
409