rqlite-js 2.5.7 → 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.
- package/README.md +354 -157
- package/dist/index.cjs +904 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +329 -0
- package/dist/index.d.ts +329 -0
- package/dist/index.js +865 -0
- package/dist/index.js.map +1 -0
- package/docs/MIGRATION-v2-to-v3.md +71 -0
- package/package.json +73 -42
- package/src/api/backup/index.ts +69 -0
- package/src/api/backup-formats.ts +8 -0
- package/src/api/client/index.ts +97 -0
- package/src/api/consistency-levels.ts +10 -0
- package/src/api/data/index.ts +87 -0
- package/src/api/paths.ts +13 -0
- package/src/api/query-params.ts +15 -0
- package/src/api/results/data-result-error.ts +41 -0
- package/src/api/results/data-result.ts +91 -0
- package/src/api/results/data-results.ts +91 -0
- package/src/api/results/index.ts +7 -0
- package/src/api/results/result-fields.ts +14 -0
- package/src/api/status/index.ts +53 -0
- package/src/http-request/auth-keys.ts +14 -0
- package/{es6/http-request/errors.js → src/http-request/errors.ts} +4 -3
- package/src/http-request/http-headers.ts +19 -0
- package/{es6/http-request/http-methods.js → src/http-request/http-methods.ts} +0 -27
- package/src/http-request/index.ts +676 -0
- package/{es6/http-request/retryable.js → src/http-request/retryable.ts} +1 -1
- package/src/index.ts +38 -0
- package/.eslintrc +0 -20
- package/.travis.yml +0 -24
- package/es6/api/backup/index.js +0 -71
- package/es6/api/client/index.js +0 -83
- package/es6/api/data/index.js +0 -129
- package/es6/api/results/data-result-error.js +0 -32
- package/es6/api/results/data-result.js +0 -134
- package/es6/api/results/data-results.js +0 -121
- package/es6/api/results/index.js +0 -7
- package/es6/api/status/index.js +0 -54
- package/es6/http-request/index.js +0 -743
- package/es6/index.js +0 -19
- package/jsconfig.json +0 -19
- package/lib/api/backup/index.js +0 -72
- package/lib/api/backup/index.js.map +0 -1
- package/lib/api/client/index.js +0 -84
- package/lib/api/client/index.js.map +0 -1
- package/lib/api/data/index.js +0 -130
- package/lib/api/data/index.js.map +0 -1
- package/lib/api/results/data-result-error.js +0 -33
- package/lib/api/results/data-result-error.js.map +0 -1
- package/lib/api/results/data-result.js +0 -135
- package/lib/api/results/data-result.js.map +0 -1
- package/lib/api/results/data-results.js +0 -122
- package/lib/api/results/data-results.js.map +0 -1
- package/lib/api/results/index.js +0 -8
- package/lib/api/results/index.js.map +0 -1
- package/lib/api/status/index.js +0 -55
- package/lib/api/status/index.js.map +0 -1
- package/lib/http-request/content-types.js +0 -5
- package/lib/http-request/content-types.js.map +0 -1
- package/lib/http-request/errors.js +0 -17
- package/lib/http-request/errors.js.map +0 -1
- package/lib/http-request/http-methods.js +0 -40
- package/lib/http-request/http-methods.js.map +0 -1
- package/lib/http-request/index.js +0 -744
- package/lib/http-request/index.js.map +0 -1
- package/lib/http-request/retryable.js +0 -42
- package/lib/http-request/retryable.js.map +0 -1
- package/lib/index.js +0 -20
- package/lib/index.js.map +0 -1
- /package/{es6/http-request/content-types.js → src/http-request/content-types.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,215 +1,412 @@
|
|
|
1
|
-
# rqlite-js
|
|
2
|
-
|
|
1
|
+
# rqlite-js · [](https://www.npmjs.com/package/rqlite-js) · [](https://github.com/rqlite/rqlite-js/actions/workflows/ci.yml)
|
|
2
|
+
|
|
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
|
+
|
|
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
|
+
|
|
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:
|
|
14
|
+
|
|
15
|
+
- [Accessing rqlite (HTTP API)](https://rqlite.io/docs/api/api/)
|
|
16
|
+
- [Read consistency](https://rqlite.io/docs/api/read-consistency/)
|
|
17
|
+
- [Bulk writes](https://rqlite.io/docs/api/bulk-api/)
|
|
18
|
+
- [Backup and restore](https://rqlite.io/docs/guides/backup/)
|
|
19
|
+
- [Monitoring / status](https://rqlite.io/docs/guides/monitoring-rqlite/)
|
|
3
20
|
|
|
4
21
|
## Features
|
|
5
|
-
* Automatically follow 301 redirects from replicates to leader node
|
|
6
|
-
* Round robin load balancing of leader node and all replicate nodes for query api requests
|
|
7
|
-
* HTTP Keepalive support through http and https agents when initializing clients or on individual requests see [Client options](#client-options)
|
|
8
|
-
* Unit and integration tests for contributors
|
|
9
22
|
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
12
29
|
|
|
13
|
-
|
|
14
|
-
The follow methods are available on the DataApiClient including their method signatures. Both the query and execute methods return a DataResults instance which provides methods for handling the RQLite response body states. In the case of a query there will only ever be one result in the DataResults.
|
|
30
|
+
## Install
|
|
15
31
|
|
|
16
|
-
|
|
17
|
-
|
|
32
|
+
```bash
|
|
33
|
+
npm install rqlite-js
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
// ESM
|
|
38
|
+
import { DataApiClient } from 'rqlite-js'
|
|
18
39
|
|
|
19
|
-
|
|
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.
|
|
20
45
|
|
|
21
|
-
|
|
22
|
-
The code sample shows how you would connect to a rqlite server and create a table.
|
|
46
|
+
## Quick start
|
|
23
47
|
|
|
24
48
|
```javascript
|
|
25
49
|
import { DataApiClient } from 'rqlite-js'
|
|
26
50
|
|
|
27
51
|
const dataApiClient = new DataApiClient('http://localhost:4001')
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const error = dataResults.getFirstError()
|
|
36
|
-
console.error(error, 'rqlite create tables results contained an error.')
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
// We are successful and have results to use from our SQL query.
|
|
40
|
-
console.log(dataResults.toString(), 'Checkout the rqlite results as a JSON string.')
|
|
41
|
-
console.log(dataResults.toArray(), 'Checkout the rqlite results as plain javascript array for app use.')
|
|
42
|
-
console.log(dataResults.get(0).toString(), 'Checkout just the first rqlite result as a JSON string.')
|
|
43
|
-
|
|
44
|
-
// Lets get the same query as a raw HTTP response
|
|
45
|
-
const { statusCode, body } = await dataApiClient.execute(sql, { raw: true })
|
|
46
|
-
console.log(body, 'HTTP reponse body.')
|
|
47
|
-
console.log(statusCode, 'HTTP response code.')
|
|
48
|
-
} catch (e) {
|
|
49
|
-
console.error(e, 'The HTTP client got an HTTP error, there must be something else going on.')
|
|
52
|
+
|
|
53
|
+
await dataApiClient.execute(
|
|
54
|
+
'CREATE TABLE IF NOT EXISTS foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)',
|
|
55
|
+
)
|
|
56
|
+
const insert = await dataApiClient.execute('INSERT INTO foo(name) VALUES("fiona")')
|
|
57
|
+
if (insert.hasError()) {
|
|
58
|
+
throw insert.getFirstError()
|
|
50
59
|
}
|
|
60
|
+
const id = insert.get(0).getLastInsertId()
|
|
61
|
+
const select = await dataApiClient.query(`SELECT id, name FROM foo WHERE id=${id}`)
|
|
62
|
+
console.log(select.get(0).toObject()) // { id: 1, name: 'fiona' }
|
|
51
63
|
```
|
|
52
64
|
|
|
53
|
-
|
|
54
|
-
|
|
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
|
+
```
|
|
55
74
|
|
|
56
75
|
```javascript
|
|
76
|
+
import knex from 'knex' // your app's dependency, not rqlite-js
|
|
57
77
|
import { DataApiClient } from 'rqlite-js'
|
|
58
78
|
|
|
59
79
|
const dataApiClient = new DataApiClient('http://localhost:4001')
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (dataResults.hasError()) {
|
|
74
|
-
const error = dataResults.getFirstError()
|
|
75
|
-
console.error(error, 'rqlite select results contained an error.')
|
|
76
|
-
return
|
|
77
|
-
}
|
|
78
|
-
console.log(dataResults.get(0).get('name'), 'The value for the name field which should equal fiona')
|
|
79
|
-
console.log(dataResults.get(0).toObject(), 'rqlitejs results are great, but I just want to work with a plain js object maybe to send back in my api')
|
|
80
|
-
console.log(dataResults.get(0).get('id'), 'This is just the id of the first result.')
|
|
81
|
-
dataResults = await dataApiClient.execute(`UPDATE foo SET name="justin" WHERE name="fiona"`)
|
|
82
|
-
if (dataResults.hasError()) {
|
|
83
|
-
const error = dataResults.getFirstError()
|
|
84
|
-
console.error(error, 'rqlite select results contained an error.')
|
|
85
|
-
return
|
|
86
|
-
}
|
|
87
|
-
const rowsAffected = dataResults.get(0).getRowsAffected()
|
|
88
|
-
console.log(rowsAffected, 'The number of rows updated for each update query which should be 1 for the first row')
|
|
89
|
-
dataResults = await dataApiClient.query(`SELECT name FROM foo WHERE id="${id}"`)
|
|
90
|
-
if (dataResults.hasError()) {
|
|
91
|
-
const error = dataResults.getFirstError()
|
|
92
|
-
console.error(error, 'rqlite select results contained an error.')
|
|
93
|
-
return
|
|
94
|
-
}
|
|
95
|
-
console.log(dataResults.get(0).get('name'), 'The value for the name field which should now equal justin')
|
|
96
|
-
} catch (e) {
|
|
97
|
-
console.error(e, 'The HTTP client got an HTTP error, there must be something else going on.')
|
|
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)
|
|
98
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 })
|
|
99
118
|
```
|
|
100
119
|
|
|
101
|
-
|
|
102
|
-
|
|
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
|
+
|
|
129
|
+
## DataApiClient
|
|
130
|
+
|
|
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.
|
|
132
|
+
|
|
133
|
+
### Methods
|
|
134
|
+
|
|
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.
|
|
166
|
+
|
|
167
|
+
### Create table
|
|
103
168
|
|
|
104
169
|
```javascript
|
|
105
170
|
import { DataApiClient } from 'rqlite-js'
|
|
106
171
|
|
|
107
172
|
const dataApiClient = new DataApiClient('http://localhost:4001')
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
]
|
|
116
|
-
const dataResults = await dataApiClient.execute(sql, { transaction: true })
|
|
117
|
-
if (dataResults.hasError()) {
|
|
118
|
-
const error = dataResults.getFirstError()
|
|
119
|
-
console.error(error, 'rqlite insert results contained an error.')
|
|
120
|
-
return
|
|
121
|
-
}
|
|
122
|
-
console.log(dataResults.get(0).getLastInsertId(), 'The id for the first insert is in index 0 last_insert_id')
|
|
123
|
-
console.log(dataResults.get(1).getLastInsertId(), 'The id for the second insert is in index 1 last_insert_id')
|
|
124
|
-
} catch (e) {
|
|
125
|
-
console.error(e, 'The HTTP client got an HTTP error, there must be something else going on.')
|
|
173
|
+
const dataResults = await dataApiClient.execute(
|
|
174
|
+
'CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)',
|
|
175
|
+
)
|
|
176
|
+
if (dataResults.hasError()) {
|
|
177
|
+
console.error(dataResults.getFirstError())
|
|
178
|
+
} else {
|
|
179
|
+
console.log(dataResults.toArray())
|
|
126
180
|
}
|
|
181
|
+
|
|
182
|
+
const { status, body } = await dataApiClient.execute(
|
|
183
|
+
'CREATE TABLE IF NOT EXISTS bar (id INTEGER NOT NULL PRIMARY KEY)',
|
|
184
|
+
{ raw: true },
|
|
185
|
+
)
|
|
186
|
+
console.log(status, body)
|
|
127
187
|
```
|
|
128
188
|
|
|
129
|
-
###
|
|
130
|
-
The code sample shows how would connect to a rqlite and run multiple select queries with [strong consistency](https://github.com/rqlite/rqlite/blob/master/DOC/CONSISTENCY.md).
|
|
189
|
+
### Insert, select, update
|
|
131
190
|
|
|
132
191
|
```javascript
|
|
133
192
|
import { DataApiClient } from 'rqlite-js'
|
|
134
193
|
|
|
135
194
|
const dataApiClient = new DataApiClient('http://localhost:4001')
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
]
|
|
141
|
-
const dataResults = await dataApiClient.query(sql, { level: 'strong' })
|
|
142
|
-
if (dataResults.hasError()) {
|
|
143
|
-
const error = dataResults.getFirstError()
|
|
144
|
-
console.error(error, 'rqlite insert results contained an error.')
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
|
-
console.log(dataResults.get(0).toString(), 'The results for the first select are in index 0')
|
|
148
|
-
console.log(dataResults.get(1).toString(), 'The results for the second select are in index 1')
|
|
149
|
-
} catch (e) {
|
|
150
|
-
console.error(e, 'The HTTP client got an HTTP error, there must be something else going on.')
|
|
195
|
+
|
|
196
|
+
let dataResults = await dataApiClient.execute('INSERT INTO foo(name) VALUES("fiona")')
|
|
197
|
+
if (dataResults.hasError()) {
|
|
198
|
+
throw dataResults.getFirstError()
|
|
151
199
|
}
|
|
200
|
+
const id = dataResults.get(0).getLastInsertId()
|
|
201
|
+
|
|
202
|
+
dataResults = await dataApiClient.query(`SELECT id, name FROM foo WHERE id=${id}`)
|
|
203
|
+
console.log(dataResults.get(0).get('name')) // fiona
|
|
204
|
+
console.log(dataResults.get(0).toObject()) // { id: 1, name: 'fiona' }
|
|
205
|
+
|
|
206
|
+
dataResults = await dataApiClient.execute('UPDATE foo SET name="justin" WHERE name="fiona"')
|
|
207
|
+
console.log(dataResults.get(0).getRowsAffected()) // 1
|
|
152
208
|
```
|
|
153
|
-
|
|
154
|
-
|
|
209
|
+
|
|
210
|
+
### Bulk statements and transactions
|
|
211
|
+
|
|
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).
|
|
155
213
|
|
|
156
214
|
```javascript
|
|
157
215
|
import { DataApiClient } from 'rqlite-js'
|
|
158
216
|
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
'SELECT id FROM bar WHERE name="test"',
|
|
168
|
-
]
|
|
169
|
-
const dataResults = await dataApiClient.query(sql, { level: 'strong' })
|
|
170
|
-
if (dataResults.hasError()) {
|
|
171
|
-
const error = dataResults.getFirstError()
|
|
172
|
-
console.error(error, 'rqlite insert results contained an error.')
|
|
173
|
-
return
|
|
174
|
-
}
|
|
175
|
-
console.log(dataResults.get(0).toString(), 'The results for the first select are in index 0')
|
|
176
|
-
console.log(dataResults.get(1).toString(), 'The results for the second select are in index 1')
|
|
177
|
-
} catch (e) {
|
|
178
|
-
console.error(e, 'The HTTP client got an HTTP error, there must be something else going on.')
|
|
217
|
+
const dataApiClient = new DataApiClient('http://localhost:4001')
|
|
218
|
+
const sql = [
|
|
219
|
+
'INSERT INTO foo(name) VALUES("fiona")',
|
|
220
|
+
'INSERT INTO foo(name) VALUES("justin")',
|
|
221
|
+
]
|
|
222
|
+
const dataResults = await dataApiClient.execute(sql, { transaction: true })
|
|
223
|
+
if (dataResults.hasError()) {
|
|
224
|
+
throw dataResults.getFirstError()
|
|
179
225
|
}
|
|
226
|
+
console.log(dataResults.get(0).getLastInsertId())
|
|
227
|
+
console.log(dataResults.get(1).getLastInsertId())
|
|
180
228
|
```
|
|
181
229
|
|
|
182
|
-
|
|
183
|
-
|
|
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
|
+
|
|
272
|
+
### Consistency
|
|
273
|
+
|
|
274
|
+
```javascript
|
|
275
|
+
import {
|
|
276
|
+
DataApiClient,
|
|
277
|
+
CONSISTENCY_LEVEL_NONE,
|
|
278
|
+
CONSISTENCY_LEVEL_WEAK,
|
|
279
|
+
CONSISTENCY_LEVEL_STRONG,
|
|
280
|
+
CONSISTENCY_LEVEL_LINEARIZABLE,
|
|
281
|
+
} from 'rqlite-js'
|
|
282
|
+
|
|
283
|
+
const dataApiClient = new DataApiClient('http://localhost:4001')
|
|
284
|
+
const dataResults = await dataApiClient.query(
|
|
285
|
+
'SELECT name FROM foo WHERE id=1',
|
|
286
|
+
{ level: CONSISTENCY_LEVEL_STRONG },
|
|
287
|
+
)
|
|
288
|
+
```
|
|
289
|
+
|
|
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).
|
|
293
|
+
|
|
294
|
+
### Errors (`error` and `error_v2`)
|
|
295
|
+
|
|
296
|
+
Failed statements use the legacy `error` string. On rqlite v10+, structured `error_v2` is also exposed:
|
|
297
|
+
|
|
298
|
+
```javascript
|
|
299
|
+
const dataResults = await dataApiClient.execute('INSERT INTO nonsense')
|
|
300
|
+
if (dataResults.hasError()) {
|
|
301
|
+
const err = dataResults.getFirstError()
|
|
302
|
+
console.error(err.message)
|
|
303
|
+
console.error(err.getErrorV2()) // { message, sqlite: { code, extended_code } } when present
|
|
304
|
+
console.error(err.toObject())
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Authentication
|
|
184
309
|
|
|
185
310
|
```javascript
|
|
186
311
|
import { DataApiClient } from 'rqlite-js'
|
|
187
|
-
import http from 'http'
|
|
188
|
-
import https from 'https'
|
|
189
312
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
313
|
+
const dataApiClient = new DataApiClient('http://localhost:4001', {
|
|
314
|
+
authentication: {
|
|
315
|
+
username: 'rqliteUsername',
|
|
316
|
+
password: 'rqlitePassword',
|
|
317
|
+
},
|
|
194
318
|
})
|
|
195
319
|
```
|
|
196
320
|
|
|
197
|
-
###
|
|
198
|
-
All http requests are retried with exponential backoff up to the number of hosts times 3. Each retry tries the next host in the rotation. To see the http status codes, error codes and http methods which cause a retry have a look at [es6/http-request/retryable.js](es6/http-request/retryable.js). When creating an instance you can supply your own Set or Array of replacement values. To manually disable all retry logic provide the contructor an entries properties with the value 0.
|
|
321
|
+
### Client options (hosts, agents, retries)
|
|
199
322
|
|
|
200
323
|
```javascript
|
|
324
|
+
import { Agent } from 'undici'
|
|
201
325
|
import { DataApiClient } from 'rqlite-js'
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
326
|
+
|
|
327
|
+
const dispatcher = new Agent({ keepAliveTimeout: 10_000 })
|
|
328
|
+
|
|
329
|
+
const dataApiClient = new DataApiClient(
|
|
330
|
+
['http://localhost:4001', 'http://localhost:4002', 'http://localhost:4003'],
|
|
331
|
+
{
|
|
332
|
+
// undici Dispatcher (has .dispatch) — used by native fetch for keepalive pools
|
|
333
|
+
httpAgent: dispatcher,
|
|
334
|
+
httpsAgent: dispatcher,
|
|
335
|
+
retryableStatusCodes: new Set([408, 429, 500, 502, 503, 504]),
|
|
336
|
+
retryableErrorCodes: new Set(['ECONNRESET', 'ETIMEDOUT']),
|
|
337
|
+
retryableHttpMethods: new Set(['GET', 'POST']),
|
|
338
|
+
retries: 0, // disable retries
|
|
339
|
+
},
|
|
340
|
+
)
|
|
341
|
+
```
|
|
342
|
+
|
|
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.
|
|
344
|
+
|
|
345
|
+
## StatusApiClient
|
|
346
|
+
|
|
347
|
+
```javascript
|
|
348
|
+
import { StatusApiClient } from 'rqlite-js'
|
|
349
|
+
|
|
350
|
+
const statusApiClient = new StatusApiClient('http://localhost:4001')
|
|
351
|
+
const { status, body } = await statusApiClient.status()
|
|
352
|
+
console.log(status, body.store?.ready)
|
|
353
|
+
|
|
354
|
+
const all = await statusApiClient.statusAllHosts()
|
|
355
|
+
all.forEach(({ host, response }) => {
|
|
356
|
+
console.log(host, response.body?.store?.ready)
|
|
211
357
|
})
|
|
212
358
|
```
|
|
213
359
|
|
|
214
|
-
|
|
215
|
-
|
|
360
|
+
See [Monitoring rqlite](https://rqlite.io/docs/guides/monitoring-rqlite/).
|
|
361
|
+
|
|
362
|
+
## BackupApiClient
|
|
363
|
+
|
|
364
|
+
Backup/load return Node `Readable` streams.
|
|
365
|
+
|
|
366
|
+
```javascript
|
|
367
|
+
import { BackupApiClient, BACKUP_DATA_FORMAT_SQL } from 'rqlite-js'
|
|
368
|
+
import { pipeline } from 'node:stream/promises'
|
|
369
|
+
import { createWriteStream } from 'node:fs'
|
|
370
|
+
|
|
371
|
+
const backupApiClient = new BackupApiClient('http://localhost:4001')
|
|
372
|
+
|
|
373
|
+
// Binary SQLite dump (default):
|
|
374
|
+
const backupStream = await backupApiClient.backup()
|
|
375
|
+
await pipeline(backupStream, createWriteStream('backup.sqlite'))
|
|
376
|
+
|
|
377
|
+
// SQL text backup:
|
|
378
|
+
const sqlStream = await backupApiClient.backup(BACKUP_DATA_FORMAT_SQL)
|
|
379
|
+
|
|
380
|
+
// Restore from SQL buffer/string:
|
|
381
|
+
await backupApiClient.load(
|
|
382
|
+
Buffer.from('CREATE TABLE foo (id INTEGER PRIMARY KEY); INSERT INTO foo DEFAULT VALUES;'),
|
|
383
|
+
BACKUP_DATA_FORMAT_SQL,
|
|
384
|
+
)
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
See [Backup and restore](https://rqlite.io/docs/guides/backup/).
|
|
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
|
+
|
|
399
|
+
## Releases
|
|
400
|
+
|
|
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.
|
|
402
|
+
|
|
403
|
+
**Local quality gate (required before commit/push/publish):**
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
npm run verify # lint, type-check, unit, build, pack checks
|
|
407
|
+
npm run verify:integrations # Docker + rqlite 10.6.6 integrations
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## Contributing / testing
|
|
411
|
+
|
|
412
|
+
See [docs/test/README.md](docs/test/README.md).
|