@jarenjs/db 0.56.0 → 0.67.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/ARCHITECTURE.md +412 -56
- package/README.md +600 -57
- package/docs/HOSTS.md +269 -0
- package/docs/JOBS-FORMAT.md +293 -45
- package/docs/LIVE-FORMAT.md +169 -20
- package/docs/MIGRATION-FORMAT.md +142 -17
- package/docs/MODEL-FORMAT.md +752 -64
- package/docs/REPLICATION-FORMAT.md +208 -0
- package/package.json +21 -7
- package/schemas/jaren-model.draft-07.schema.json +224 -162
- package/schemas/jaren-model.schema.json +224 -162
- package/schemas/jaren-replication-snapshot.draft-07.schema.json +83 -0
- package/schemas/jaren-replication-snapshot.schema.json +83 -0
- package/schemas/jaren-replication.draft-07.schema.json +82 -0
- package/schemas/jaren-replication.schema.json +82 -0
- package/src/algebra.js +227 -9
- package/src/backup.js +161 -0
- package/src/cancellation.js +48 -0
- package/src/capture.js +230 -47
- package/src/cli.js +165 -59
- package/src/cursor.js +417 -0
- package/src/dag-job.js +154 -21
- package/src/ddl.js +102 -8
- package/src/dialect.js +268 -113
- package/src/dialects/expression-read.js +158 -0
- package/src/dialects/postgres.js +618 -0
- package/src/dialects/rtree-ddl.js +129 -0
- package/src/dialects/sqlite.js +244 -11
- package/src/document-files.js +311 -0
- package/src/document-steps.js +422 -0
- package/src/documents.js +335 -0
- package/src/driver.js +448 -61
- package/src/drivers/bun.js +37 -1
- package/src/drivers/indexeddb-snapshot.js +149 -0
- package/src/drivers/node-pool.js +11 -0
- package/src/drivers/node-worker-endpoint.js +105 -0
- package/src/drivers/node-worker.js +204 -0
- package/src/drivers/node.js +41 -7
- package/src/drivers/postgres.js +331 -0
- package/src/drivers/wasm-oo1.js +97 -0
- package/src/drivers/wasm-session.js +67 -0
- package/src/drivers/wasm.js +17 -83
- package/src/drivers/worker-pool.js +183 -0
- package/src/drivers/worker-protocol.js +79 -0
- package/src/drivers/worker-queue.js +60 -0
- package/src/emit.js +339 -48
- package/src/entity.js +20 -22
- package/src/errors.js +430 -19
- package/src/expression.js +284 -0
- package/src/graph.js +64 -8
- package/src/index.js +48 -17
- package/src/introspect.js +583 -0
- package/src/jobs.js +843 -107
- package/src/json-bytes.js +58 -0
- package/src/live-join.js +250 -0
- package/src/live-nested.js +120 -0
- package/src/live.js +18 -4
- package/src/logical-rows.js +90 -0
- package/src/maintenance.js +175 -0
- package/src/migrate.js +248 -181
- package/src/model.js +68 -0
- package/src/plan.js +1119 -138
- package/src/pragmas.js +314 -0
- package/src/profile.js +151 -3
- package/src/query.js +1634 -323
- package/src/replication-format.js +115 -0
- package/src/replication.js +332 -0
- package/src/residual.js +17 -0
- package/src/series.js +12 -4
- package/src/store.js +1567 -273
- package/src/tracker.js +203 -29
- package/src/udf.js +88 -7
- package/types/index.d.ts +1158 -27
- package/types/node-pool.d.ts +28 -0
- package/types/node-worker.d.ts +54 -0
- package/types/node.d.ts +69 -2
- package/types/postgres.d.ts +46 -0
- package/types/typed.d.ts +27 -4
- package/types/wasm.d.ts +14 -0
package/docs/HOSTS.md
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# Database execution hosts
|
|
2
|
+
|
|
3
|
+
All hosts implement the same Driver and Connection contracts. Model, query,
|
|
4
|
+
transaction, cursor, include and capture behavior stays in the Store. Host
|
|
5
|
+
capabilities are observed when the connection opens. The common oracle and
|
|
6
|
+
lifecycle corpus is `test/db/store-hosts.test.js`; it checks values and coded
|
|
7
|
+
errors across Node, worker, pool, wasm sessions and wasm journal fallback.
|
|
8
|
+
|
|
9
|
+
## Node workers
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
import { openStore } from '@jarenjs/db';
|
|
13
|
+
import { nodeWorkerDriver } from '@jarenjs/db/node-worker';
|
|
14
|
+
import { nodeWorkerPoolDriver } from '@jarenjs/db/node-pool';
|
|
15
|
+
|
|
16
|
+
const driver = nodeWorkerDriver({ windowRows: 64, windowBytes: 1024 * 1024 });
|
|
17
|
+
const store = await openStore(model, { driver, path: 'application.sqlite' });
|
|
18
|
+
// The ordinary Store methods and transaction scopes apply here.
|
|
19
|
+
await store.close();
|
|
20
|
+
|
|
21
|
+
const pool = nodeWorkerPoolDriver({ readers: 2, queueCapacity: 64, graceMs: 5000 });
|
|
22
|
+
const pooledStore = await openStore(model, { driver: pool, path: 'application.sqlite' });
|
|
23
|
+
await pooledStore.close();
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Each worker owns one `DatabaseSync`. Requests use structured clone, versioned
|
|
27
|
+
frames, opaque statement/cursor IDs and a connection generation. A cursor receives
|
|
28
|
+
at most its row and byte credits per frame; the endpoint may retain one lookahead
|
|
29
|
+
row. Credits measure JSON keys/scalars plus raw blob bytes, excluding fixed frame
|
|
30
|
+
metadata. One oversized row is `JD2092`. Abort takes effect at a row boundary;
|
|
31
|
+
`return()` awaits remote cleanup. Neither serializes a function or replays a write.
|
|
32
|
+
|
|
33
|
+
| Worker option | Default | Meaning |
|
|
34
|
+
|---|---:|---|
|
|
35
|
+
| `windowRows` | 64 | Rows per credit frame |
|
|
36
|
+
| `windowBytes` | 1048576 | Counted row bytes per frame |
|
|
37
|
+
| `maxPending` | 64 | Admitted non-cleanup requests |
|
|
38
|
+
| `maxStatements` | 1024 | Remote prepared statements per connection |
|
|
39
|
+
| `maxCursors` | 64 | Remote open cursors |
|
|
40
|
+
| `allMaxRows` | 100000 | Compatibility `all()` maximum rows |
|
|
41
|
+
| `allMaxBytes` | 16777216 | Compatibility `all()` maximum counted bytes |
|
|
42
|
+
| `startupTimeoutMs` | 10000 | Worker readiness deadline |
|
|
43
|
+
| `closeTimeoutMs` | 5000 | Cleanup acknowledgement deadline |
|
|
44
|
+
|
|
45
|
+
All worker options are positive safe integers. Cleanup reserves at most one
|
|
46
|
+
request per cursor plus one close beyond `maxPending`; total pending admission is
|
|
47
|
+
also capped. A compiled transient cursor marks its statement ephemeral, releasing
|
|
48
|
+
its remote ID on exhaustion or return. Cached statements remain subject to the
|
|
49
|
+
statement cap. `all()` drains credited frames under both limits and refuses excess;
|
|
50
|
+
it never receives an unbounded whole-result frame. `metrics()` on an opened driver
|
|
51
|
+
connection reports pending requests, frame/row high-water marks, generation and
|
|
52
|
+
health. It contains no SQL or parameter values.
|
|
53
|
+
|
|
54
|
+
Worker exit or restart fences every old statement, cursor and transaction with
|
|
55
|
+
`JD2090`. The failure is retryable only outside a transaction; this is permission
|
|
56
|
+
to reopen, **not evidence that an uncertain write can be retried safely**. The
|
|
57
|
+
connection's `restart()` returns a new connection; old Store handles stay invalid.
|
|
58
|
+
A failed transaction is never migrated or replayed. Queue overflow is `JD2091`,
|
|
59
|
+
with `retryable: true` and the observed queue `depth`. `JD2093` identifies malformed
|
|
60
|
+
requests; malformed responses invalidate the generation.
|
|
61
|
+
|
|
62
|
+
Close stops admission and awaits cleanup, then requests worker termination at the
|
|
63
|
+
deadline and rejects with `JD2090`. Node cannot preempt a synchronous native SQLite
|
|
64
|
+
call with V8 worker termination. The expired generation is fenced immediately,
|
|
65
|
+
but the native call and its locks may outlive that refusal; process exit can wait
|
|
66
|
+
for the native call too. A timeout therefore has an unknown write outcome, not a
|
|
67
|
+
promised rollback. `capabilities.cancellation.midStatement` remains false.
|
|
68
|
+
|
|
69
|
+
Worker connections declare sessions, user functions, aggregates and online backup
|
|
70
|
+
unavailable: journal capture provides the same logical patches, and query residuals
|
|
71
|
+
run on the caller. Synchronous Store methods and live queries are unavailable on
|
|
72
|
+
these asynchronous connections. Bun can import both subpaths; opening a Node
|
|
73
|
+
SQLite worker there reports the named unavailable-binding failure (`JD0003`).
|
|
74
|
+
|
|
75
|
+
## WAL pool policy
|
|
76
|
+
|
|
77
|
+
A file pool opens exactly one writer and `readers` read-only workers, verifies WAL,
|
|
78
|
+
and routes explicitly classified reads to readers. Unclassified work goes to the
|
|
79
|
+
writer. `:memory:` uses the writer alone. A pool opened with `readOnly: true` has
|
|
80
|
+
only read-only workers and requires an already-WAL file. Default reader count is
|
|
81
|
+
two (allowed range zero through 32); the queue admits at most `queueCapacity`
|
|
82
|
+
waiting requests (default 64, zero allowed), in strict FIFO order. A blocked writer
|
|
83
|
+
at the head can leave a reader idle. Metrics report active/idle/queued, worker
|
|
84
|
+
health/generation/role/executions, and wait p50/p95 over the last 1024 admissions.
|
|
85
|
+
|
|
86
|
+
Compiled operation metadata supplies read classification, and SQLite itself
|
|
87
|
+
refuses a write on a read-only worker. Transactions pin one worker for their entire
|
|
88
|
+
lifetime, including nested savepoints. Normal writable Store transactions pin the
|
|
89
|
+
writer; a read-only opened pool pins a reader. There is no extra transport-specific
|
|
90
|
+
transaction API. Store root admission still serializes unrelated operations to
|
|
91
|
+
preserve existing ownership rules. Multiple classified Connection reads can run
|
|
92
|
+
concurrently; the pool does not promise parallel root Store calls.
|
|
93
|
+
|
|
94
|
+
Worker loss settles in-flight work once and discards the slot's prepared cache.
|
|
95
|
+
Replacement occurs only after its lease/transaction settles. Failed replacement
|
|
96
|
+
stops further admission with its actual error rather than leaving callers queued
|
|
97
|
+
forever. Close refuses queued work, grants active work `graceMs`, and closes the
|
|
98
|
+
workers after that grace. A commit already submitted to SQLite owns its settlement;
|
|
99
|
+
close waits for its acknowledgement even beyond grace, without interrupting that
|
|
100
|
+
writer. Native-call termination has the limitation described above.
|
|
101
|
+
|
|
102
|
+
## Synchronous cursors and include accounting
|
|
103
|
+
|
|
104
|
+
`store.sync.entity(name).cursor(document)`, `.loadCursor(spec)` and `.page(spec,
|
|
105
|
+
options)` use the same plans, classification, keysets and bounds as the asynchronous
|
|
106
|
+
entity set. Cursors implement `Symbol.iterator`, `next`, `return` and
|
|
107
|
+
`Symbol.dispose`; page methods return values. Early break, consumer throw, abort,
|
|
108
|
+
error and Store close release the row source exactly once. A scope cannot outlive
|
|
109
|
+
its transaction, and root cursors acquire admission per pull.
|
|
110
|
+
|
|
111
|
+
Include text is byte-checked before decoding. JSON parsing's construction traversal
|
|
112
|
+
records serialized UTF-8 sizes bottom-up; nested bounds read those sizes before
|
|
113
|
+
attaching reconstructed children. There is no second full nested stringify.
|
|
114
|
+
Numbers, control escapes, paired and lone surrogates have differential coverage
|
|
115
|
+
against the native JSON serializer. This is a bounded decoded graph, not a streaming
|
|
116
|
+
JSON parser: SQLite still produces the outer include text and JSON.parse constructs
|
|
117
|
+
its bounded value. The measurement below records the cost of the counting traversal
|
|
118
|
+
and WeakMap, including its timing and heap-growth regressions.
|
|
119
|
+
|
|
120
|
+
## Wasm capture and browser persistence
|
|
121
|
+
|
|
122
|
+
The wasm adapter probes create/attach/changeset/delete on a disposable live session.
|
|
123
|
+
Only success declares `sessions: true`; missing or failing bindings select journal
|
|
124
|
+
capture with `sessionReason`. Each changeset is copied out of wasm memory before its
|
|
125
|
+
native allocation is freed, so its buffer can be transferred. Every capture extent
|
|
126
|
+
and connection close deletes its owned session handles. Large integral JavaScript
|
|
127
|
+
numbers outside the safe integer range bind as doubles, avoiding the pinned oo1
|
|
128
|
+
binding's int64 truncation.
|
|
129
|
+
|
|
130
|
+
The data studio probes this ordered ladder independently of user agent:
|
|
131
|
+
|
|
132
|
+
| Mode | Requirements and durability | Synchronous/live |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `opfs-sab` | COOP/COEP isolation, SharedArrayBuffer, registered VFS and write/reopen probe | Yes |
|
|
135
|
+
| `opfs-sahpool` | SAH-pool APIs and write/reopen probe; no isolation headers required | Yes |
|
|
136
|
+
| `indexeddb-snapshot` | IndexedDB atomic write/reopen probe and an exclusive Web Lock | No |
|
|
137
|
+
| `memory` | Last fallback; visibly non-durable, lost on reload | Yes |
|
|
138
|
+
|
|
139
|
+
A selected durable mode has an owner worker; other tabs use its channel. The UI
|
|
140
|
+
names the selected mode, durability and each rejected rung. The existing five boot
|
|
141
|
+
stages still name hung or failed work. The isolated fixture sets COOP `same-origin`
|
|
142
|
+
and COEP `require-corp`; this does not change production hosting headers.
|
|
143
|
+
|
|
144
|
+
`indexedDbSnapshotHandle(sqlite3, { name, indexedDB, maxBytes })` is an injected wasm
|
|
145
|
+
handle, not an OPFS VFS. It runs SQLite in memory and atomically replaces a versioned
|
|
146
|
+
IndexedDB snapshot after an autocommit write or transaction commit. A compare-and-swap
|
|
147
|
+
revision prevents independent connections overwriting a newer snapshot. The promise
|
|
148
|
+
resolves only on IndexedDB transaction completion. The default whole-database bound
|
|
149
|
+
is 16 MiB, checked before export and on restore. Each acknowledged write can therefore
|
|
150
|
+
copy the whole database: this fallback favors bounded durability over throughput.
|
|
151
|
+
|
|
152
|
+
Quota, revision conflict or persistence failure preserves the previously committed
|
|
153
|
+
snapshot and invalidates the in-memory connection (`JD2094`, non-retryable). Reopen
|
|
154
|
+
reads the last committed version. Interrupted replacements never publish partial
|
|
155
|
+
bytes. Read-only opens refuse writes. Live maintenance is explicitly unavailable
|
|
156
|
+
because commit acknowledgements are asynchronous; the studio Store pane refreshes
|
|
157
|
+
after writes. `openSnapshotStorage` exposes the versioned storage primitive for
|
|
158
|
+
hosts that need explicit snapshot cleanup.
|
|
159
|
+
|
|
160
|
+
## Measurements and reproducibility
|
|
161
|
+
|
|
162
|
+
<!--fact:db.hosts-->
|
|
163
|
+
|
|
164
|
+
Measured 2026-09-08, v24.19.0, AMD Ryzen 9 5900HX with Radeon Graphics; 7 samples per latency/include case.
|
|
165
|
+
|
|
166
|
+
| Host | Open p50 ms | Slow SQL p50 ms | Event-loop max ms | Tiny reads/s | Mixed work ms | Wait p95 ms |
|
|
167
|
+
|---|---:|---:|---:|---:|---:|---:|
|
|
168
|
+
| node | 0.19 | 87.04 | 92.34 | 876870 | 207.12 | 0.00 |
|
|
169
|
+
| worker | 52.99 | 85.23 | 3.52 | 19168 | 198.57 | 0.00 |
|
|
170
|
+
| pool-1-reader | 76.73 | 81.95 | 1.62 | 25109 | 207.11 | 190.32 |
|
|
171
|
+
| pool-3-readers | 152.95 | 82.50 | 1.86 | 26201 | 74.79 | 63.91 |
|
|
172
|
+
|
|
173
|
+
| Host | Cursor rows | Cursor ms | Sampled heap growth MiB | Sampled total RSS MiB |
|
|
174
|
+
|---|---:|---:|---:|---:|
|
|
175
|
+
| node | 100000 | 113.84 | 8.39 | 70.26 |
|
|
176
|
+
| worker | 100000 | 316.64 | 5.60 | 100.64 |
|
|
177
|
+
| pool-1-reader | 100000 | 295.61 | 8.45 | 124.79 |
|
|
178
|
+
| pool-3-readers | 100000 | 287.91 | 13.29 | 168.73 |
|
|
179
|
+
|
|
180
|
+
| Include accounting | Encoded bytes | Time p50 ms | Uncollected heap growth p50 MiB |
|
|
181
|
+
|---|---:|---:|---:|
|
|
182
|
+
| serialize-again | 3218891 | 12.38 | 8.00 |
|
|
183
|
+
| count-during-decode | 3218891 | 21.36 | 8.84 |
|
|
184
|
+
|
|
185
|
+
The worker event-loop acceptance bound is 50 ms. The original in-process baseline measured p50/p95/max event-loop delay of 1.07/86.97/87.62 ms, bare worker startup p50 27.48 ms, and duplicate include serialization 14.68 ms with 8.00 MiB uncollected heap growth. The current open measurement also includes driver probing; its startup cost is broader than that bare-worker baseline.
|
|
186
|
+
|
|
187
|
+
<!--/fact-->
|
|
188
|
+
|
|
189
|
+
The tiny workload is sequential `SELECT 7`; the mixed workload is 24 classified
|
|
190
|
+
reads with six interspersed writes. RSS includes worker heaps. Heap-growth figures
|
|
191
|
+
are uncollected allocations sampled in this process, not retained memory and not
|
|
192
|
+
portable heap limits. Cursor sampling occurs every 1024 rows. The final runner
|
|
193
|
+
measures all hosts on the same file-based workload. Timing, memory and throughput
|
|
194
|
+
losses are kept beside event-loop gains; these are observations, not speed promises.
|
|
195
|
+
|
|
196
|
+
Run `node --expose-gc benchmark/store-hosts.js` for the current measurements.
|
|
197
|
+
`benchmark/store-hosts-baseline.js` reproduces the separate initial recipe; do not
|
|
198
|
+
replace the baseline when measuring a change. The JSON records include runtime,
|
|
199
|
+
host, SQL and sample count. `npm run docs:derive` bakes these tables;
|
|
200
|
+
`npm run docs:check` rejects drift.
|
|
201
|
+
|
|
202
|
+
<!--fact:db.browserHosts-->
|
|
203
|
+
|
|
204
|
+
15 storage scenarios passed without skips, measured 2026-09-08.
|
|
205
|
+
|
|
206
|
+
| Engine | Version | Isolated | Ordinary | OPFS denied | All storage denied / quota |
|
|
207
|
+
|---|---|---|---|---|---|
|
|
208
|
+
| chromium | 149.0.7827.55 | opfs-sab | opfs-sahpool | indexeddb-snapshot | memory (non-durable) |
|
|
209
|
+
| firefox | 151.0 | opfs-sab | opfs-sahpool | indexeddb-snapshot | memory (non-durable) |
|
|
210
|
+
| webkit | 26.5 | indexeddb-snapshot | indexeddb-snapshot | indexeddb-snapshot | memory (non-durable) |
|
|
211
|
+
|
|
212
|
+
webkit isolated fallback: JD2061 — opfs-sab: the SharedArrayBuffer OPFS VFS is not registered; opfs-sahpool: Missing required OPFS APIs.. Selected indexeddb-snapshot (persistent).
|
|
213
|
+
|
|
214
|
+
<!--/fact-->
|
|
215
|
+
|
|
216
|
+
Build the website, run `storage.spec.js` with Playwright's JSON reporter, then pass
|
|
217
|
+
the report to `node benchmark/store-hosts-browser.js /tmp/storehosts-browser.json`.
|
|
218
|
+
The committed JSON includes the exact recipe. On a host lacking WebKit libraries,
|
|
219
|
+
run the browser command in the Ubuntu Playwright container as the workspace user.
|
|
220
|
+
The matrix tests actual write/reopen, aborted snapshot replacement, denied storage
|
|
221
|
+
and quota failure. Both initial capability observations and the final storage
|
|
222
|
+
results are retained under `benchmark/store-hosts-browser-*.json`.
|
|
223
|
+
|
|
224
|
+
## Scoped quirk review
|
|
225
|
+
|
|
226
|
+
Confirmed and fixed in the host path:
|
|
227
|
+
|
|
228
|
+
- Cursor abort/open races and late rows after return now release the eventual
|
|
229
|
+
source exactly once; asynchronous failure/disposal waits for cleanup.
|
|
230
|
+
- Lost transaction generations unwind only when each owning rollback settles;
|
|
231
|
+
failed commits retain their lease for rollback. Replacement failure stops admission
|
|
232
|
+
with its actual error.
|
|
233
|
+
- Connection close releases active iterators. Remote shutdown starts its deadline
|
|
234
|
+
before waiting for a native row; closed pools report no healthy or active worker.
|
|
235
|
+
- A failed session allocation no longer leaves capture depth open for the next
|
|
236
|
+
write. Wasm session bytes have independent ownership and deterministic deletion.
|
|
237
|
+
- The pinned oo1 numeric binder truncates huge integral Numbers through int64;
|
|
238
|
+
the adapter binds these as doubles, proven by the shared adversarial oracle.
|
|
239
|
+
- IndexedDB synchronous put failures and blocked opens settle and clean up;
|
|
240
|
+
failed replacement preserves the previous committed bytes and invalidates the
|
|
241
|
+
in-memory state. Browser tests prove the same behavior in actual storage.
|
|
242
|
+
- The pinned wasm build removes its private OPFS helper after initialization;
|
|
243
|
+
the SAB path now removes closed files through the public storage API.
|
|
244
|
+
- Storage selection can finish before the Store opens. Query and insert controls
|
|
245
|
+
now wait for the open acknowledgement, preserving input during slower IndexedDB
|
|
246
|
+
boots. Browser assertions use stored rows when live maintenance is unavailable.
|
|
247
|
+
- The requested generation-code slot was already assigned to seek-anchor typing.
|
|
248
|
+
Generation fencing uses the fresh `JD2090` code and preserves `JD2086`.
|
|
249
|
+
|
|
250
|
+
Confirmed beside this path, left unchanged:
|
|
251
|
+
|
|
252
|
+
- Root Store admission intentionally serializes unrelated operations; concurrent
|
|
253
|
+
classified Connection reads are the pool's current parallelism boundary.
|
|
254
|
+
- V8 termination cannot preempt native SQLite execution. A true statement interrupt
|
|
255
|
+
would need binding support; shutdown documents the unknown outcome explicitly.
|
|
256
|
+
- Asynchronous live maintenance remains unavailable; IndexedDB and Node workers
|
|
257
|
+
expose that capability limitation instead of returning stale live results.
|
|
258
|
+
|
|
259
|
+
Checked and dropped:
|
|
260
|
+
|
|
261
|
+
- Browser database work already runs outside the UI thread; adding another Store
|
|
262
|
+
or another query/transaction API was unnecessary.
|
|
263
|
+
- Build flags alone do not establish session support, and isolation headers alone
|
|
264
|
+
do not establish a working VFS. Disposable live probes decide both.
|
|
265
|
+
- A second include serialization was unnecessary for exact Unicode byte bounds;
|
|
266
|
+
the differential counter and deliberate off-by-one mutation detected disagreement.
|
|
267
|
+
- Read-only WAL workers do not accept writes, nested scopes do not migrate,
|
|
268
|
+
transient cursors do not exhaust the statement cap, and failed writes are not
|
|
269
|
+
automatically replayed. The lifecycle and fault corpus exercises each boundary.
|