@reddb-io/sdk 1.20.0-rc.178 → 1.20.0-rc.179
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 +123 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -164,6 +164,71 @@ try {
|
|
|
164
164
|
directly via `tx.query()`. Open another `connect()` handle for independent
|
|
165
165
|
concurrent transactions.
|
|
166
166
|
|
|
167
|
+
### Isolation levels
|
|
168
|
+
|
|
169
|
+
The default isolation is **snapshot isolation** (each transaction reads a
|
|
170
|
+
consistent MVCC snapshot). To request a stronger level, issue the isolation
|
|
171
|
+
clause with the opening statement via `db.query()`:
|
|
172
|
+
|
|
173
|
+
```js
|
|
174
|
+
await db.query('BEGIN ISOLATION LEVEL SERIALIZABLE')
|
|
175
|
+
await db.query("INSERT INTO audit (action) VALUES ('serializable write')")
|
|
176
|
+
await db.query('COMMIT')
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
RedDB accepts the PG-compatible spellings `READ UNCOMMITTED`,
|
|
180
|
+
`READ COMMITTED`, `REPEATABLE READ` (= `SNAPSHOT`), and `SERIALIZABLE`.
|
|
181
|
+
`SERIALIZABLE` engages the serializable-snapshot-isolation (SSI) path, which
|
|
182
|
+
can abort a transaction at commit with a serialization conflict (see below).
|
|
183
|
+
|
|
184
|
+
### Serialization conflicts & retries
|
|
185
|
+
|
|
186
|
+
Under snapshot / serializable isolation, two transactions that write the same
|
|
187
|
+
row on overlapping snapshots resolve **first-committer-wins**: the later
|
|
188
|
+
transaction is aborted with a **retryable serialization conflict**. It
|
|
189
|
+
surfaces as a `RedDBError` with code `QUERY_ERROR` whose message begins
|
|
190
|
+
`serialization conflict`. This is the one error class you should catch and
|
|
191
|
+
**retry** rather than surface to the user:
|
|
192
|
+
|
|
193
|
+
```js
|
|
194
|
+
import { connect, RedDBError } from '@reddb-io/sdk'
|
|
195
|
+
|
|
196
|
+
const db = await connect('file:///var/lib/reddb/data.rdb')
|
|
197
|
+
|
|
198
|
+
/** True only for the retryable first-committer-wins conflict. */
|
|
199
|
+
function isSerializationConflict(err) {
|
|
200
|
+
return (
|
|
201
|
+
err instanceof RedDBError &&
|
|
202
|
+
err.code === 'QUERY_ERROR' &&
|
|
203
|
+
/serialization conflict/i.test(err.message)
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Run a transaction, retrying with backoff on serialization conflicts. */
|
|
208
|
+
async function withRetry(fn, { maxRetries = 5 } = {}) {
|
|
209
|
+
for (let attempt = 0; ; attempt++) {
|
|
210
|
+
try {
|
|
211
|
+
return await db.transaction(fn)
|
|
212
|
+
} catch (err) {
|
|
213
|
+
if (isSerializationConflict(err) && attempt < maxRetries) {
|
|
214
|
+
await new Promise((r) => setTimeout(r, 2 ** attempt * 5)) // backoff
|
|
215
|
+
continue
|
|
216
|
+
}
|
|
217
|
+
throw err // not retryable, or out of attempts
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Concurrent debits stay consistent — losers retry against the fresh snapshot.
|
|
223
|
+
await withRetry(async (tx) => {
|
|
224
|
+
const { rows } = await tx.query('SELECT balance FROM accounts WHERE id = $1', 1)
|
|
225
|
+
await tx.query('UPDATE accounts SET balance = $1 WHERE id = $2', rows[0].balance - 10, 1)
|
|
226
|
+
})
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Only conflicts are retried — a syntax error, a constraint violation, or any
|
|
230
|
+
other `QUERY_ERROR` propagates on the first attempt.
|
|
231
|
+
|
|
167
232
|
## Connection URIs
|
|
168
233
|
|
|
169
234
|
| URI | Mode |
|
|
@@ -299,6 +364,54 @@ await db.queues.len('jobs')
|
|
|
299
364
|
await db.queues.purge('jobs')
|
|
300
365
|
```
|
|
301
366
|
|
|
367
|
+
## Graph, vector and time-series
|
|
368
|
+
|
|
369
|
+
RedDB is one multi-model engine: graphs, vectors and time-series live in the
|
|
370
|
+
same database and are reached through `db.query()` (dedicated helpers arrive in
|
|
371
|
+
Helper Spec v1.1). Every snippet below is executed on each release by
|
|
372
|
+
[`test/readme-examples.test.mjs`](test/readme-examples.test.mjs).
|
|
373
|
+
|
|
374
|
+
**Graph** — nodes and edges in a `network` collection, then a shortest-path
|
|
375
|
+
traversal (the first user row gets `rid` 1024):
|
|
376
|
+
|
|
377
|
+
```js
|
|
378
|
+
await db.query("INSERT INTO network NODE (label, node_type) VALUES ('gateway', 'Host')")
|
|
379
|
+
await db.query("INSERT INTO network NODE (label, node_type) VALUES ('app', 'Host')")
|
|
380
|
+
await db.query("INSERT INTO network NODE (label, node_type) VALUES ('db', 'Host')")
|
|
381
|
+
await db.query("INSERT INTO network EDGE (label, from, to, weight) VALUES ('connects', 1024, 1025, 1.0)")
|
|
382
|
+
await db.query("INSERT INTO network EDGE (label, from, to, weight) VALUES ('connects', 1025, 1026, 1.0)")
|
|
383
|
+
await db.query("INSERT INTO network EDGE (label, from, to, weight) VALUES ('connects', 1024, 1026, 5.0)")
|
|
384
|
+
|
|
385
|
+
const path = await db.query("GRAPH SHORTEST_PATH '1024' TO '1026' ALGORITHM dijkstra")
|
|
386
|
+
path.rows[0].total_weight // 2 — two 1.0 hops beat the single 5.0 edge
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**Vector** — store embeddings, then rank by similarity. A vector bind value is
|
|
390
|
+
a single `$N` param, so it goes in the params array as one element
|
|
391
|
+
(`[[1.0, 0.0]]`), not the variadic form:
|
|
392
|
+
|
|
393
|
+
```js
|
|
394
|
+
await db.query("INSERT INTO embeddings VECTOR (dense, content) VALUES ([1.0, 0.0], 'gateway runbook')")
|
|
395
|
+
await db.query("INSERT INTO embeddings VECTOR (dense, content) VALUES ([0.0, 1.0], 'database manual')")
|
|
396
|
+
|
|
397
|
+
const hits = await db.query('SEARCH SIMILAR $1 COLLECTION embeddings LIMIT 1', [[1.0, 0.0]])
|
|
398
|
+
hits.rows[0].score // 1 — the identical vector scores exactly 1
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
**Time-series** — declare a series with retention + downsampling, ingest
|
|
402
|
+
points (timestamps are nanoseconds), then bucket them:
|
|
403
|
+
|
|
404
|
+
```js
|
|
405
|
+
await db.query('CREATE TIMESERIES metrics RETENTION 7 d CHUNK_SIZE 64 DOWNSAMPLE 1h:5m:avg')
|
|
406
|
+
await db.query("INSERT INTO metrics (metric, value, tags, timestamp) VALUES ('cpu.usage', 10.0, '{\"host\":\"srv-a\"}', 0)")
|
|
407
|
+
await db.query("INSERT INTO metrics (metric, value, tags, timestamp) VALUES ('cpu.usage', 20.0, '{\"host\":\"srv-a\"}', 60000000000)")
|
|
408
|
+
|
|
409
|
+
const rollup = await db.query(
|
|
410
|
+
'SELECT time_bucket(5m) AS bucket, avg(value) AS avg_value, count(*) AS samples ' +
|
|
411
|
+
"FROM metrics WHERE metric = 'cpu.usage' GROUP BY time_bucket(5m)",
|
|
412
|
+
)
|
|
413
|
+
```
|
|
414
|
+
|
|
302
415
|
## SDK Helper Spec conformance
|
|
303
416
|
|
|
304
417
|
This driver implements **SDK Helper Spec v1.0**
|
|
@@ -354,12 +467,16 @@ with `INVALID_ARGUMENT`.
|
|
|
354
467
|
| `wire.graph.sql_round_trip` | reachable via `db.query` (no v1.0 case) |
|
|
355
468
|
| `wire.timeseries.sql_round_trip` | reachable via `db.query` (no v1.0 case) |
|
|
356
469
|
|
|
357
|
-
**Out-of-scope in v1.0** (reach via raw `db.query` until v1.1, per
|
|
358
|
-
first-class `vectors.*`, `graph.*`, `timeseries.*`, and
|
|
359
|
-
helpers
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
470
|
+
**Out-of-scope in v1.0 helpers** (reach via raw `db.query` until v1.1, per
|
|
471
|
+
spec): first-class `vectors.*`, `graph.*`, `timeseries.*`, and
|
|
472
|
+
`probabilistic.*` helpers (see [Graph, vector and
|
|
473
|
+
time-series](#graph-vector-and-time-series)); KV TTL (`kv.expire`) and gRPC
|
|
474
|
+
watch; priority queues, consumer groups, dead-letter routing; cross-shard
|
|
475
|
+
transactions; JSON Patch / nested / array-positional document patches
|
|
476
|
+
(top-level merge only). Transaction **isolation levels** and the retryable
|
|
477
|
+
serialization-conflict class are available today through raw `db.query` — see
|
|
478
|
+
[Isolation levels](#isolation-levels) and [Serialization conflicts &
|
|
479
|
+
retries](#serialization-conflicts--retries).
|
|
363
480
|
|
|
364
481
|
Run the conformance harness against a locally built binary:
|
|
365
482
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reddb-io/sdk",
|
|
3
|
-
"version": "1.20.0-rc.
|
|
3
|
+
"version": "1.20.0-rc.179",
|
|
4
4
|
"description": "Official embedded RedDB SDK — launches a local red binary over stdio JSON-RPC. Use @reddb-io/client for remote HTTP, gRPC, and RedWire.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|