@kedem/okdb 1.9.1 → 2.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 (48) hide show
  1. package/README.md +3 -2
  2. package/bin/okdb.js +1 -1
  3. package/docs/deployment.md +155 -0
  4. package/docs/diagnostics.md +115 -0
  5. package/docs/embeddings.md +1 -1
  6. package/docs/functions.md +30 -27
  7. package/docs/getting-started.md +14 -3
  8. package/docs/http-api.md +3 -3
  9. package/docs/http-cluster.md +93 -60
  10. package/docs/index.md +13 -1
  11. package/docs/manifest.json +6 -3
  12. package/docs/pipelines.md +1 -1
  13. package/docs/process-registry.md +5 -5
  14. package/docs/processors.md +35 -18
  15. package/docs/queue.md +112 -42
  16. package/docs/sync.md +10 -7
  17. package/docs/ttl.md +1 -1
  18. package/docs/upgrade-2.0.md +290 -0
  19. package/okdb-functions-sandbox-worker.js +1 -1
  20. package/okdb-http-worker-child.js +1 -1
  21. package/okdb-views-bootstrap-worker.js +1 -1
  22. package/okdb.js +1 -1
  23. package/package.json +1 -1
  24. package/public/layouts/_default.ok.html +1 -1
  25. package/public/sections/db/modals/create-env-modal.ok.js +1 -1
  26. package/public/sections/db/modals/demo-env-modal.ok.js +1 -1
  27. package/public/sections/db/parts/db-overview.ok.js +1 -1
  28. package/public/sections/engines/engine-ui-utils.js +1 -1
  29. package/public/sections/engines/parts/engine-declaration-editor.ok.js +1 -1
  30. package/public/sections/engines/parts/generic-engine-panel.ok.js +1 -1
  31. package/public/sections/queue/parts/code-panel.ok.js +1 -1
  32. package/public/sections/queue/parts/job-log-panel.ok.js +1 -1
  33. package/public/sections/queue/parts/queue-jobs.ok.js +1 -1
  34. package/public/sections/sync/parts/sync-topology.ok.js +1 -1
  35. package/public/sections/system/index.ok.html +1 -1
  36. package/public/sections/system/parts/system-process-panel.ok.js +1 -1
  37. package/public/sections/system/parts/system-processing-panel.ok.js +1 -1
  38. package/public/sections/system/parts/system-runtime-overview.ok.js +1 -1
  39. package/public/setup-app.js +1 -1
  40. package/public/setup.html +116 -55
  41. package/types/index.d.ts +33 -3
  42. package/types/options.d.ts +19 -10
  43. package/docs/worker-fleet.md +0 -139
  44. package/okdb-functions-runner-child.js +0 -1
  45. package/okdb-queue-load-handler.js +0 -1
  46. package/okdb-queue-spawn-child.js +0 -1
  47. package/okdb-worker-child.js +0 -1
  48. package/public/sections/system/parts/system-workers-panel.ok.js +0 -1
package/docs/index.md CHANGED
@@ -72,7 +72,7 @@ All environments are LMDB directories on disk. Each has its own clock, changelog
72
72
  ## Quick example
73
73
 
74
74
  ```javascript
75
- const OKDB = require('src/okdb');
75
+ const OKDB = require('@kedem/okdb');
76
76
 
77
77
  const okdb = new OKDB('./mydb');
78
78
  await okdb.open();
@@ -99,18 +99,30 @@ await okdb.close();
99
99
  ## Docs map
100
100
 
101
101
  - [Getting Started](./getting-started.md) — install, open, first writes
102
+ - [Roles & Deployment](./deployment.md) — role flags, single-process vs multi-process topologies, deferred processing
102
103
  - [Data Model](./data-model.md) — types, keys, environments, storage layout
103
104
  - [Querying](./querying.md) — reads, ranges, MongoDB-style filters
104
105
  - [Indexes](./indexes.md) — secondary, composite, unique, geo
105
106
  - [Transactions](./transactions.md) — batching, versioning, optimistic concurrency
106
107
  - [Change Log & Events](./change-log.md) — clock, changelog, processor, events
108
+ - [Processors](./processors.md) — the change-subscription primitive: modes, registration, leases
107
109
  - [Sync](./sync.md) — multi-node replication
108
110
  - [Full-Text Search](./fts.md) — FTS index, tokenizer, query
109
111
  - [Embeddings](./embeddings.md) — vector pipeline, semantic search
112
+ - [Materialized Views](./views.md) — reactive reduce/group views over types
113
+ - [Time Machine](./time-machine.md) — per-type field-level history, point-in-time reads
110
114
  - [Queue](./queue.md) — durable job queue
111
115
  - [Live Subscriptions](./subscriptions.md) — signal-SSE change streams with durable sessions
112
116
  - [Files](./files.md) — blob / file storage
117
+ - [TTL](./ttl.md) — per-record time-to-live
113
118
  - [Custom Functions](./functions.md) — sandboxed stored JS functions, env/global scope, HTTP execution
119
+ - [Pipelines](./pipelines.md) — declarative content/embedding pipelines
114
120
  - [HTTP API](./http-api.md) — built-in REST API
121
+ - [HTTP Clustering](./http-cluster.md) — the CLI's N-capable-nodes cluster
122
+ - [Process Registry](./process-registry.md) — root-wide process visibility and control
123
+ - [Auth & Sync](./auth-and-sync.md) / [Auth Permissions](./auth-permissions.md) — tokens, OAuth, grants
124
+ - [Logging](./logging.md) — structured logs, retention
125
+ - [Diagnostics](./diagnostics.md) — opt-in triage tooling (write-origin ring, fatal report, loop lag)
115
126
  - [Plugins](./plugins.md) — extending OKDB before open
116
127
  - [Migration](./migration.md) — export, import, backup, blob integrity
128
+ - [Upgrading to 2.0](./upgrade-2.0.md) — the node-model runtime redesign: constructor changes, workers dissolved, claimed responsibilities
@@ -1,6 +1,7 @@
1
1
  [
2
2
  { "file": "index.md", "label": "Overview" },
3
3
  { "file": "getting-started.md", "label": "Getting Started" },
4
+ { "file": "deployment.md", "label": "Roles & Deployment" },
4
5
  {
5
6
  "label": "Core",
6
7
  "children": [
@@ -23,7 +24,8 @@
23
24
  { "file": "queue.md", "label": "Queue" },
24
25
  { "file": "files.md", "label": "Files" },
25
26
  { "file": "functions.md", "label": "Custom Functions" },
26
- { "file": "ttl.md", "label": "TTL (Time-to-Live)" }
27
+ { "file": "ttl.md", "label": "TTL (Time-to-Live)" },
28
+ { "file": "time-machine.md", "label": "Time Machine" }
27
29
  ]
28
30
  },
29
31
  {
@@ -36,9 +38,10 @@
36
38
  { "file": "process-registry.md", "label": "Process Registry" },
37
39
  { "file": "plugins.md", "label": "Plugins" },
38
40
  { "file": "processors.md", "label": "Processors (modes & registration)" },
39
- { "file": "worker-fleet.md", "label": "Workers (population & placement)" },
40
41
  { "file": "logging.md", "label": "Logging" },
41
- { "file": "migration.md", "label": "Migration" }
42
+ { "file": "diagnostics.md", "label": "Diagnostics" },
43
+ { "file": "migration.md", "label": "Migration" },
44
+ { "file": "upgrade-2.0.md", "label": "Upgrading to 2.0" }
42
45
  ]
43
46
  }
44
47
  ]
package/docs/pipelines.md CHANGED
@@ -216,7 +216,7 @@ Config notes:
216
216
  - `handler.env` — optional env name for the function registry; defaults to the engine owning env
217
217
  - `concurrency`, `pollInterval`, `ttl` — forwarded to the underlying queue worker runtime
218
218
 
219
- The function runner pool scales dynamically to match `concurrency` — no pool configuration is needed.
219
+ Each claimed payload's function runs in the executing node's local sandbox thread (lazy, idle-reaped); `concurrency` bounds in-flight invocations there is no pool to configure.
220
220
 
221
221
  Behavior:
222
222
 
@@ -29,18 +29,18 @@ The primary entity is the **process**, not the processor. Each long-lived proces
29
29
  pid, ppid, host,
30
30
  supervisorKey, // nodeKey of the supervising process (null = a root) → builds the tree
31
31
  slot, slotKey, // supervised position; slotKey is the STABLE durable control key (see below)
32
- role, // { engines, asyncProcessors, compaction, http }
32
+ role, // { engines, processors, compaction, http } — booleans
33
33
  listenAddr,
34
34
  caps, // { controllable, decommissionable, killable, killConfirm? } — see Capabilities
35
35
  lifecycle, // { state: 'running' | 'decommissioning', desiredVersionApplied }
36
- processing, // FACET (owners only): { pools[], processors[], cap, policy }
36
+ processing, // FACET (participants only): { participate, processors[] }
37
37
  heartbeatAt, expiresAt,
38
38
  }
39
39
  ```
40
40
 
41
41
  A processor (FTS, views, materializer, time-machine, …) is **not** a separate entity — it is summarized
42
- on the owner's `processing` facet. The same is true of process-backend pool workers: they are real OS
43
- processes but are summarized on `processing.pools[]`, not given their own rows.
42
+ on the owner's `processing` facet (`processing.processors[]`). There are no worker pools in 2.0
43
+ drains run on the claiming node's loop, so there is nothing pool-shaped to give its own row.
44
44
 
45
45
  ### Two stores, split by lifetime
46
46
 
@@ -163,7 +163,7 @@ const db = new OKDB('./data', { processes: { register: true, kind: 'embedded' }
163
163
  const db = new OKDB('./data', { processes: { kind: 'standalone', listenAddr: ':8484' } });
164
164
  ```
165
165
 
166
- `register: true` makes a process publish its own row even when it is not an `asyncProcessors` owner.
166
+ `register: true` makes a process publish its own row even when it is not a `processors`-role owner.
167
167
  `bin/okdb` sets `kind` automatically (`primary` / `http-server`); embedders pass it explicitly.
168
168
 
169
169
  ---
@@ -9,15 +9,18 @@ A **processor** maintains derived state from a source type's change log. You reg
9
9
 
10
10
  OKDB's own features (indexes, views, FTS, materializer, embeddings, time-machine) are built on this primitive — and it's a public extension point you can use directly.
11
11
 
12
- > **See also:** [Workers](worker-fleet.md) for _where_ a processor's work runs the worker
13
- > population, claiming, and scaling. This doc defines _what_ a processor is and its **modes**
14
- > (the guarantee); worker-fleet.md covers placement.
12
+ > **What vs. where.** This doc defines _what_ a processor is and its **modes** (the
13
+ > guarantee). _Whether_ a node runs `single` processors is the boolean `processors: true|false`
14
+ > (participate vs. passive); the per-`(env,type)` lease distributes the work across whatever
15
+ > participating nodes share the root (1-of-N, with failover). There is no filtered claim API and
16
+ > no managed "worker population" — see [Upgrading to 2.0](upgrade-2.0.md) for the node-model
17
+ > placement story.
15
18
 
16
19
  ---
17
20
 
18
21
  ## Modes — the delivery guarantee
19
22
 
20
- A mode names the **delivery guarantee**: how many instances run the handler. Handlers are always **closures** that run in **this process** trusted derived work runs on the main event loop, chunked into cooperative quanta so a large drain never blocks the loop monolithically (see `plans/runtime-model/overview.md`). There is no worker-thread or worker-process placement choice: the only off-loop thread in the system is the function sandbox (for untrusted user code), never trusted processor work.
23
+ A mode names the **delivery guarantee**: how many instances run the handler. All drains run on the owner process's **event loop**, chunked into cooperative quanta (`FLUSH_QUANTUM`-bounded batches with a yield between them) so a large drain never blocks the loop monolithically. There is no execution placement to configure — no thread pool, no `runIn`; the only thread in the engine is the **sandbox** that runs untrusted user-function code. Handlers register per process: pass `handler:'<name>'` (named registry), `module:{path,export}`, or for internal features — a closure paired with a reconstructible `quantum`.
21
24
 
22
25
  | mode | cardinality | cursor | use when |
23
26
  | -------- | ----------------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------- |
@@ -69,7 +72,7 @@ The `handler` is always a **closure** (a function). The portable `handler: {modu
69
72
 
70
73
  ## Snapshot bootstrap and chunked drains
71
74
 
72
- A `single` processor with `bootstrap: 'snapshot'` performs a cooperative snapshot scan on the main loop, yielding every batch so the event loop stays responsive while a large type is bootstrapped. Live drains use the same chunked `_flush` (a `FLUSH_QUANTUM` bounds each on-loop quantum, then yields). This is exactly the path FTS uses: every FTS-indexed type registers a `single`-mode processor whose closure handler hydrates + tokenizes + writes postings, drained on the loop in quanta. There is no shared worker thread and no `workerKey` — those were removed in the runtime-model rework.
75
+ A `single` processor with `bootstrap: 'snapshot'` performs a cooperative snapshot scan, yielding every batch so the event loop stays responsive while a large type is bootstrapped. Live drains run on the loop via chunked `_flush` (a `FLUSH_QUANTUM` bounds each quantum, then yields; heavy per-item features pass a smaller per-processor `flushQuantum`). A feature may supply a reconstructible **`quantum`** module — it re-derives the work for a clock range and commits it durably (optionally advancing the cursor atomically with its output, `selfCursor`) before the cursor moves. This is exactly the path FTS uses every FTS-indexed type registers a `single`-mode processor (hydrate + tokenize + posting writes) drained on the loop in small bounded quanta.
73
76
 
74
77
  The cursor only advances on a successful drain, so processing is **at-least-once**; make handlers idempotent. Derived state is eventually-consistent; if you need a deterministic "caught up" point, wait on `stop.status().lastClock` reaching the source clock.
75
78
 
@@ -79,8 +82,12 @@ The cursor only advances on a successful drain, so processing is **at-least-once
79
82
 
80
83
  Each processor takes a lease (the `'proc'` lock). With several processes open on the same env, exactly one runs each `single` processor; the others wait and take over if the holder dies. The lease-holding process drains on its own loop, so there is always a single writer to your derived store.
81
84
 
85
+ Under sustained load the lease **load-shares by cooperative hold-window claiming**: the holder drains for up to `OKDB_LEASE_HOLD_MS` (200 ms) per window, and at the window boundary yields the lease **only if a peer has signalled it wants a turn** (`~lock:want`, a decaying marker written by waiting peers with backlog). A lone process therefore keeps its leases with zero lock churn; two loaded processes rotate and split the work ≈evenly. Waiting peers re-contend on a short cadence (~one hold window) while they have backlog and on the slow `ttl/2` failover cadence when idle.
86
+
82
87
  A processor that hits an error **keeps its lease** (in-place `retry()`/`restart()` keeps its slot against standbys in other processes). Re-bootstrap reuses the held lease handle, and `tryAcquire` treats a fresh lease held by the same pid + processorId as a re-acquire — so an errored processor can never deadlock in `waiting` against its own heartbeat.
83
88
 
89
+ Cross-process wakes ride the UDP bus (`SYSTEM_POKE`), which is lossy by design. An online holder therefore also runs an **idle catch-up tick** (`OKDB_PROCESSOR_CATCHUP_MS`, default 15 000, `0` disables): every interval it compares each single-mode registration's type head against its cursor and schedules a drain when behind, so a lost poke bounds staleness at one tick instead of "until the next write". The changelog GC in turn **never prunes past the slowest durable processor cursor** — a lagging drain finds its entries intact rather than silently skipping writes.
90
+
84
91
  ---
85
92
 
86
93
  ## Cookbook
@@ -110,16 +117,26 @@ that safe. The per-write gate costs one boolean compare per write when no flip i
110
117
 
111
118
  ---
112
119
 
113
- ## Instance processing policy
114
-
115
- The `processing` constructor option controls where **this instance** drains processors. It is independent of the mode declared at `register()`.
116
-
117
- | value | behavior |
118
- | -------- | ----------------------------------------------------------------------------------------------------------------------- |
119
- | `'auto'` | **default.** Self-process on the loop when no workers are configured; yield to the population when desired workers > 0. |
120
- | `'main'` | Self-process on the main event loop (diagnostic / single-process environments). |
121
- | `'none'` | Never drain (passive/read-only instances). Used internally for HTTP cluster workers. |
122
-
123
- > **Deprecated:** `processing: 'threads'` / `'processes'` (the old worker-thread/process pool) are accepted with a one-time deprecation warning and mapped to `'auto'` — the pool executor was removed in the runtime-model rework; trusted drains run on the loop.
124
-
125
- `'auto'` is the recommended default for application code. When a population is configured via `db.workers.ensure()`, `'auto'` instances automatically stop claiming leases so population workers drain instead — no configuration change required at the embedder.
120
+ ## Where a processor runs — claiming
121
+
122
+ The `processors` constructor option is a **boolean** controlling whether **this node**
123
+ participates in claiming `single` (1-of-N) processors. It is independent of the mode declared at
124
+ `register()`.
125
+
126
+ | value | behavior |
127
+ | ------- | ------------------------------------------------------------------------------------------- |
128
+ | `true` | **default.** Participate claim every unclaimed `single` lease this node sees. |
129
+ | `false` | Passive — claim nothing (reads/writes + inline processors still work; another node drains). |
130
+
131
+ The per-`(env,type)` lease + WAITING-retry is the load balancer — start more participating nodes
132
+ over the same path and the work spreads automatically (1-of-N, with failover when a holder dies);
133
+ there is no population to configure and no per-node dedication.
134
+
135
+ > **Removed before 2.0 shipped:** the _filtered_ claim form — `processors: '<filter>'` (throws at
136
+ > construction) and `db.processors.process(filter)` / `processRest()` / `processEverything()`
137
+ > (throw with a migration message), plus per-node dedication/intent. Also removed: the
138
+ > `processing:` option family (`'auto'|'main'|'none'`, `'threads'|'processes'`) — there is no
139
+ > execution placement; every drain runs on its claimant's loop in bounded quanta.
140
+ > `asyncProcessors: true/false` is now `processors: true/false`. To dedicate hardware to
141
+ > processing, run that node `processors:true` and the others `processors:false`. See
142
+ > [Upgrading to 2.0](upgrade-2.0.md).
package/docs/queue.md CHANGED
@@ -22,9 +22,9 @@ async function main() {
22
22
  const okdb = new OKDB('./db');
23
23
  await okdb.open();
24
24
 
25
- // process() runs the consumer IN THIS process (it takes a closure). For a consumer that
26
- // runs on the worker population or a dedicated child, use worker(type, module) /
27
- // spawn(type, module) those cross a process boundary, so they take a module path.
25
+ // process() runs the consumer IN THIS process this is the model.
26
+ // okdb does not auto-run queue handlers; keeping a consumer process alive is the
27
+ // launcher's job. Scale by running more processes (pm2, systemd, docker).
28
28
  const worker = okdb.queue.process(
29
29
  'send-email',
30
30
  async (payload, ctx) => {
@@ -175,6 +175,63 @@ const worker = okdb.queue.process(
175
175
  );
176
176
  ```
177
177
 
178
+ ### Several types, one lane pool (array form)
179
+
180
+ Passing an **array of entries** runs several job types through ONE shared lane pool. Each
181
+ entry is exactly the single form folded into an object — `{ type, handler, ...perTypeOptions }`
182
+ — and the second argument becomes the **pool options** shared by every lane:
183
+
184
+ ```javascript
185
+ const worker = okdb.queue.process(
186
+ [
187
+ { type: 'sync:forward', handler: forwardSync, weight: 3, ttl: 60_000 },
188
+ { type: 'sync:backfill', handler: backfillSync, weight: 1, ttl: 300_000 },
189
+ { type: 'content:page', handler: fetchPage },
190
+ ],
191
+ {
192
+ concurrency: 4, // the PROCESS-WIDE cap: at most 4 handlers in flight across ALL types
193
+ admission: () => okdb.pressure().score < 1, // load-aware: skip claiming under distress
194
+ },
195
+ );
196
+ ```
197
+
198
+ Why this exists: stacking N single-type consumers gives N _independent_ lane pools — a process
199
+ consuming 10 types at `concurrency: 2` each could run 20 handlers at once, with no total cap
200
+ and no cross-type fairness. The array form gives you:
201
+
202
+ - **One concurrency budget** across every type (the pool's `concurrency`).
203
+ - **Weighted fairness** — `weight` (default 1) biases the claim sweep, so under contention a
204
+ weight-3 type gets ~3× the first-claim attempts of a weight-1 type; an idle type costs
205
+ nothing.
206
+ - **One admission gate** — `admission: () => boolean` is consulted before every claim sweep;
207
+ while it returns false the pool claims nothing (in-flight handlers finish normally, and the
208
+ durable jobs remain claimable by other processes). Re-checked every `admissionInterval` ms
209
+ (default 500). Combine with [`db.pressure()`](#load-aware-consumers) for a load-aware
210
+ consumer in one line.
211
+
212
+ Per-type knobs (`ttl`, `autoHeartbeat`, `onPermanentFail`) live on the entry; cluster-wide
213
+ controls (`max_concurrency`, buckets, priority) keep working unchanged — they're enforced at
214
+ claim time regardless of consumer shape.
215
+
216
+ ### Load-aware consumers
217
+
218
+ `db.pressure()` returns a composite load signal for this node, cached for 250 ms (safe to
219
+ poll per claim):
220
+
221
+ ```javascript
222
+ {
223
+ writerStallMs, // max oldest-pending commit age across envs — THE stall tell
224
+ writerDepth, // max in-flight commit depth
225
+ maxDurableLag, // max durable processor lag (FTS/views/TM/embeddings behind)
226
+ queuePending, // pending+retry jobs across envs (workload, not in the score)
227
+ loopLagMs, // mean event-loop delay since the previous read
228
+ score, // max of the normalized distress signals; 1 ≈ at the limit
229
+ }
230
+ ```
231
+
232
+ `admission: () => okdb.pressure().score < 1` makes a consumer yield to indexing/write load
233
+ automatically; an external autoscaler can poll the same read to decide when to add nodes.
234
+
178
235
  ### Handler signature
179
236
 
180
237
  The handler receives:
@@ -198,19 +255,61 @@ These local worker helpers now map to the same public queue API that remote work
198
255
  - `okdb.queue.markJobComplete(jobId, claimId, result?)`
199
256
  - `okdb.queue.markJobFail(jobId, claimId, error, code?)`
200
257
 
201
- ### Placement: `process` vs `worker` vs `spawn`
258
+ ### Placement: `process`
202
259
 
203
- The three verbs split on **where the consumer runs** and therefore on whether they take a closure or a module:
260
+ The queue is a **substrate okdb coordinates but does not place** handlers are trusted user code
261
+ of unknown nature, so okdb never auto-runs them on a population. The surface is:
204
262
 
205
- | Verb | Runs in | 2nd arg | Lifetime |
206
- | ---------------------------- | ------------------------------------------- | -------------------------------------- | -------------------------- |
207
- | `queue.process(type, fn)` | **this** Node process | a **closure** | dies with this instance |
208
- | `queue.worker(type, module)` | the **worker population** (other processes) | a **module path** / `{module, export}` | durable; the fleet runs it |
209
- | `queue.spawn(type, module)` | a **dedicated forked child** | a **module path** | tied to the child |
263
+ | Verb | Runs in | 2nd arg | Lifetime |
264
+ | ------------------------- | --------------------- | ------------- | ----------------------- |
265
+ | `queue.process(type, fn)` | **this** Node process | a **closure** | dies with this instance |
210
266
 
211
- A closure can't cross a process boundary, so `worker()`/`spawn()` require a module. Passing a closure to `worker()` is an error that points you at `process()`.
267
+ `queue.process(type, closure)` is **the model** single-execution and at-least-once come from
268
+ the CAS claim on the durable job row. Scale by running more independent processes: each opens
269
+ okdb and calls `process()`, and the durable CAS claim hands each job to exactly one consumer.
270
+ The canonical worker pattern is a standalone script (`workers/default.js`) launched by pm2,
271
+ systemd, or docker alongside your main service.
212
272
 
213
- `process(...)` is great for I/O-heavy jobs but gives no isolation from CPU-heavy work use `spawn()` (dedicated child) or `worker()` (population) for that.
273
+ > **Removed:** `queue.worker(type, module)` the auto-adopted "shared pool" formthrows
274
+ > `QUEUE_WORKER_REMOVED`. `queue.spawn(type, module)` — the dedicated forked child — was also
275
+ > removed in okdb 2.0. Run independent consumer processes instead; they claim the same durable
276
+ > jobs exactly once via CAS. See docs/upgrade-2.0.md for migration.
277
+
278
+ ---
279
+
280
+ ## The worker pattern
281
+
282
+ The canonical way to run queue consumers outside your main process:
283
+
284
+ ```javascript
285
+ // workers/default.js — run alongside the main service
286
+ const OKDB = require('okdb');
287
+
288
+ async function main() {
289
+ const okdb = new OKDB('./db');
290
+ await okdb.open();
291
+
292
+ okdb.queue.process(
293
+ 'send-email',
294
+ async (payload, ctx) => {
295
+ await sendEmail(payload.to, payload.subject);
296
+ },
297
+ { concurrency: 4 },
298
+ );
299
+
300
+ // okdb.queue.process('resize-image', async (payload, ctx) => { ... });
301
+
302
+ process.on('SIGTERM', async () => {
303
+ await okdb.close();
304
+ process.exit(0);
305
+ });
306
+ }
307
+
308
+ main().catch(console.error);
309
+ ```
310
+
311
+ Launch it with pm2, systemd, docker, or kubernetes — run N replicas and each drains jobs via CAS
312
+ without coordination. A replica that dies leaves its in-flight jobs to re-queue on claim expiry.
214
313
 
215
314
  ---
216
315
 
@@ -320,35 +419,6 @@ POST /api/queue/job/<id>/fail
320
419
 
321
420
  ---
322
421
 
323
- ## Spawned workers
324
-
325
- If you want queue handling in a **separate Node process**, use `spawn(...)`:
326
-
327
- ```javascript
328
- const spawned = await okdb.queue.spawn('send-email', './workers/email.js', {
329
- concurrency: 4,
330
- pollInterval: 100,
331
- });
332
-
333
- // later
334
- await spawned.stop();
335
- ```
336
-
337
- The module must export a function:
338
-
339
- ```javascript
340
- module.exports = async function (payload, ctx) {
341
- await ctx.okdb.put('email_log', payload.orderId, {
342
- sentAt: Date.now(),
343
- jobId: ctx.jobId,
344
- });
345
- };
346
- ```
347
-
348
- The child process opens the same OKDB path and receives the same `ctx.okdb` semantics as a normal worker.
349
-
350
- ---
351
-
352
422
  ## Job lifecycle
353
423
 
354
424
  ```text
@@ -630,7 +700,7 @@ The queue-worker engine is a pipeline-compatible engine type that:
630
700
  - invokes a stored function for each claimed payload
631
701
  - handles completion/failure using normal queue semantics
632
702
 
633
- Worker `concurrency` controls job throughput. The function runner pool scales automatically to support it — no manual pool sizing is needed.
703
+ Worker `concurrency` controls job throughput. Each invoked function runs in the executing node's local sandbox thread (lazy, idle-reaped) — no pool to size.
634
704
 
635
705
  ---
636
706
 
package/docs/sync.md CHANGED
@@ -96,7 +96,7 @@ Returns up to `delta_limit` (default 500) change records from clock `N` onwards
96
96
 
97
97
  ### Multi-environment sync
98
98
 
99
- The delta spans **all `sync:true` environments** simultaneously. The response carries a `clocks` map (`{ default: N, queue: N, ... }`), and each environment's cursor advances independently. Custom environments created via `createEnvironment` are included automatically if `sync: true`.
99
+ The delta spans **all changelog-enabled user environments** simultaneously. The response carries a `clocks` map (`{ default: N, queue: N, ... }`), and each environment's cursor advances independently. Custom environments created via `createEnvironment` are included automatically every user env enables its changelog and participates in sync (the per-env `sync` option was removed in 2.0; internal `~`-prefixed envs never participate).
100
100
 
101
101
  ---
102
102
 
@@ -140,7 +140,11 @@ LWW depends on timestamps. If two nodes have significantly different system cloc
140
140
  :::
141
141
 
142
142
  :::note Non-syncable data
143
- Environments with `sync: false` (FTS indexes, vector stores) are excluded from sync. They are rebuilt locally from synced data. This is intentional — posting lists and HNSW graphs are large, computable, and node-local.
143
+ Internal `~`-prefixed environments (FTS indexes under `~fts`, vector stores, the `~sub`
144
+ subscriptions env) carry no changelog and are excluded from sync. They are rebuilt locally from
145
+ synced data. This is intentional — posting lists and HNSW graphs are large, computable, and
146
+ node-local. (In 1.9 these were opted out with `sync: false`; that option was removed in 2.0 —
147
+ exclusion is now derived from the env being a rebuildable internal store.)
144
148
  :::
145
149
 
146
150
  ---
@@ -149,8 +153,7 @@ Environments with `sync: false` (FTS indexes, vector stores) are excluded from s
149
153
 
150
154
  `okdb-bus` sends a UDP multicast datagram on `239.1.2.3:30303` whenever the clock changes. This is how peers on the same LAN discover that new data is available without polling. Loss is acceptable — sync reconciles correctly even if some pokes are dropped.
151
155
 
152
- To disable:
153
-
154
- ```javascript
155
- const okdb = new OKDB('./db', { bus: false });
156
- ```
156
+ The bus is **auto-enabled** when the LMDB path is shared (the shmbuf native binding present);
157
+ it is off for a lone single-process node. The `bus` constructor option was removed in 2.0 (it
158
+ threw nothing useful as a manual switch — it is load-bearing for POKE/DRAIN coherence and so is
159
+ derived, not asked). See [Upgrading to 2.0](upgrade-2.0.md).
package/docs/ttl.md CHANGED
@@ -7,7 +7,7 @@ OKDB supports per-record TTL — set an expiration on any item and it will be au
7
7
  ## Quick Start
8
8
 
9
9
  ```javascript
10
- const OKDB = require('src/okdb');
10
+ const OKDB = require('@kedem/okdb');
11
11
  const db = new OKDB('./my-data');
12
12
  await db.open();
13
13