@kedem/okdb 1.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/LICENSE +57 -0
- package/README.md +344 -0
- package/banner.jpg +0 -0
- package/bin/okdb.js +2 -0
- package/docs/OKDB-ARCHITECTURE.md +1036 -0
- package/docs/auth-and-sync.md +558 -0
- package/docs/auth-permissions.md +143 -0
- package/docs/change-log.md +169 -0
- package/docs/data-model.md +174 -0
- package/docs/embeddings.md +338 -0
- package/docs/files.md +131 -0
- package/docs/fts.md +139 -0
- package/docs/functions.md +514 -0
- package/docs/getting-started.md +233 -0
- package/docs/http-api.md +705 -0
- package/docs/index.md +115 -0
- package/docs/indexes.md +207 -0
- package/docs/logging.md +116 -0
- package/docs/manifest.json +38 -0
- package/docs/migration.md +154 -0
- package/docs/pipelines.md +598 -0
- package/docs/plugins.md +144 -0
- package/docs/querying.md +204 -0
- package/docs/queue.md +637 -0
- package/docs/sync.md +156 -0
- package/docs/transactions.md +140 -0
- package/docs/ttl.md +375 -0
- package/okdb.js +1 -0
- package/package.json +26 -0
- package/public/admin-app.js +1 -0
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/components/admin-route-host.ok.js +1 -0
- package/public/components/env-picker.ok.js +1 -0
- package/public/components/field-path-input.ok.js +1 -0
- package/public/components/field.html +6 -0
- package/public/components/geo-map.ok.js +1 -0
- package/public/components/ok-cron-input.ok.js +1 -0
- package/public/components/ok-dropzone.ok.js +1 -0
- package/public/components/ok-js-input.ok.js +1 -0
- package/public/components/ok-json-input.ok.js +1 -0
- package/public/components/ok-text-monaco-input.ok.js +1 -0
- package/public/components/theme-toggle.ok.js +1 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/images/logo.png +0 -0
- package/public/images/logo.svg +333 -0
- package/public/index.html +6 -0
- package/public/layouts/_default.ok.html +1 -0
- package/public/layouts/okdb-cube.ok.html +1 -0
- package/public/login-app.js +1 -0
- package/public/login.html +1 -0
- package/public/modules/ok.js +1 -0
- package/public/sections/api/index.ok.html +1 -0
- package/public/sections/api/parts/api-endpoint.ok.js +1 -0
- package/public/sections/auth/index.ok.html +1 -0
- package/public/sections/auth/modals/create-token-modal.ok.js +1 -0
- package/public/sections/auth/modals/edit-token-modal.ok.js +1 -0
- package/public/sections/auth/modals/perm-info-popover.ok.js +1 -0
- package/public/sections/auth/parts/token-detail.ok.js +1 -0
- package/public/sections/auth/parts/token-list.ok.js +1 -0
- package/public/sections/db/index.ok.html +1 -0
- package/public/sections/db/modals/add-item-modal.ok.js +1 -0
- package/public/sections/db/modals/create-env-modal.ok.js +1 -0
- package/public/sections/db/modals/create-fts-modal.ok.js +1 -0
- package/public/sections/db/modals/create-index-modal.ok.js +1 -0
- package/public/sections/db/modals/create-pipeline-modal.ok.js +1 -0
- package/public/sections/db/modals/demo-env-modal.ok.js +1 -0
- package/public/sections/db/modals/remove-env-modal.ok.js +1 -0
- package/public/sections/db/modals/schema-edit-modal.ok.js +1 -0
- package/public/sections/db/parts/db-overview.ok.js +1 -0
- package/public/sections/db/parts/db-pipelines-panel.ok.js +1 -0
- package/public/sections/db/parts/edit-row.ok.js +1 -0
- package/public/sections/db/parts/tm-timeline.ok.js +1 -0
- package/public/sections/db/parts/type-changelog.ok.js +1 -0
- package/public/sections/db/parts/type-list.ok.js +1 -0
- package/public/sections/db/parts/type-pipelines-tab.ok.js +1 -0
- package/public/sections/db/parts/type-schema-tab.ok.js +1 -0
- package/public/sections/db/parts/type-viewer.ok.js +1 -0
- package/public/sections/db/parts/type-violations-tab.ok.js +1 -0
- package/public/sections/docs/index.ok.html +1 -0
- package/public/sections/embeddings/index.ok.html +1 -0
- package/public/sections/embeddings/modals/register-model-modal.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-create-panel.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-embedder-panel.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-engine-declaration-editor.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-engine-list.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-indexer-panel.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-lifecycle-bar.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-model-catalog.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-overview.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-search-panel.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-worker-panel.ok.js +1 -0
- package/public/sections/embeddings/parts/embeddings-engine-panel.ok.js +1 -0
- package/public/sections/embeddings/parts/engines-overview.ok.js +1 -0
- package/public/sections/embeddings/parts/pipeline-create-panel.ok.js +1 -0
- package/public/sections/engines/engine-panel-shared-style.js +1 -0
- package/public/sections/engines/engine-ui-utils.js +1 -0
- package/public/sections/engines/index.ok.html +1 -0
- package/public/sections/engines/modals/engine-modal.ok.js +1 -0
- package/public/sections/engines/parts/engine-declaration-editor.ok.js +1 -0
- package/public/sections/engines/parts/engine-lifecycle-actions.ok.js +1 -0
- package/public/sections/engines/parts/engine-panel-shell.ok.js +1 -0
- package/public/sections/engines/parts/engine-resource-card.ok.js +1 -0
- package/public/sections/engines/parts/engine-row-item.ok.js +1 -0
- package/public/sections/engines/parts/generic-engine-panel.ok.js +1 -0
- package/public/sections/files/index.ok.html +1 -0
- package/public/sections/files/parts/files-blob-status.ok.js +1 -0
- package/public/sections/files/parts/files-collection-list.ok.js +1 -0
- package/public/sections/files/parts/files-collection-panel.ok.js +1 -0
- package/public/sections/files/parts/files-file-list.ok.js +1 -0
- package/public/sections/files/parts/files-overview.ok.js +1 -0
- package/public/sections/files/parts/files-path-browser.ok.js +1 -0
- package/public/sections/files/parts/files-upload-panel.ok.js +1 -0
- package/public/sections/functions/index.ok.html +1 -0
- package/public/sections/functions/modals/function-modal.ok.js +1 -0
- package/public/sections/functions/parts/function-detail.ok.js +1 -0
- package/public/sections/functions/parts/function-logs.ok.js +1 -0
- package/public/sections/functions/parts/function-runner.ok.js +1 -0
- package/public/sections/functions/parts/functions-list.ok.js +1 -0
- package/public/sections/license/index.ok.html +1 -0
- package/public/sections/license/parts/license-panel.ok.js +1 -0
- package/public/sections/processors/index.ok.html +1 -0
- package/public/sections/queue/index.ok.html +1 -0
- package/public/sections/queue/modals/create-bucket-modal.ok.js +1 -0
- package/public/sections/queue/modals/edit-bucket-modal.ok.js +1 -0
- package/public/sections/queue/modals/edit-job-modal.ok.js +1 -0
- package/public/sections/queue/modals/enqueue-job-modal.ok.js +1 -0
- package/public/sections/queue/modals/job-type-modal.ok.js +1 -0
- package/public/sections/queue/parts/ok-job-form.ok.js +1 -0
- package/public/sections/queue/parts/queue-buckets.ok.js +1 -0
- package/public/sections/queue/parts/queue-job-types.ok.js +1 -0
- package/public/sections/queue/parts/queue-jobs.ok.js +1 -0
- package/public/sections/queue/parts/queue-types.ok.js +1 -0
- package/public/sections/queue/parts/queue-workers.ok.js +1 -0
- package/public/sections/sync/index.ok.html +5 -0
- package/public/sections/sync/parts/sync-node-detail.ok.js +1 -0
- package/public/sections/sync/parts/sync-retention.ok.js +1 -0
- package/public/sections/sync/parts/sync-topology.ok.js +1 -0
- package/public/sections/system/index.ok.html +1 -0
- package/public/sections/system/modals/data-ops-modal.ok.js +1 -0
- package/public/sections/system/parts/system-api-panel.ok.js +1 -0
- package/public/sections/system/parts/system-auth-panel.ok.js +1 -0
- package/public/sections/system/parts/system-log-history.ok.js +1 -0
- package/public/sections/system/parts/system-log.ok.js +1 -0
- package/public/sections/system/parts/system-status.ok.js +1 -0
- package/public/sections/system/parts/system-sync-panel.ok.js +1 -0
- package/public/sections/views/index.ok.html +1 -0
- package/public/sections/views/modals/create-view-modal.ok.js +1 -0
- package/public/sections/views/parts/view-detail.ok.js +1 -0
- package/public/sections/views/parts/views-list.ok.js +1 -0
- package/public/sections/views/parts/views-overview.ok.js +1 -0
- package/public/setup-app.js +1 -0
- package/public/setup.html +1 -0
- package/public/site.webmanifest +1 -0
- package/public/stores/auth-store.js +1 -0
- package/public/stores/events-store.js +1 -0
- package/public/stores/functions-store.js +1 -0
- package/public/stores/okdb-store.js +1 -0
- package/public/stores/theme-store.js +1 -0
- package/public/styles.css +1 -0
- package/public/test-cron-input.html +10 -0
- package/public/test-json-input.html +101 -0
- package/public/util/permissions.js +1 -0
- package/public/util/sift-schema.js +1 -0
- package/public/vendor/okjs/ok.esm.min.js +2 -0
- package/public/vendor/okjs/toolkit/behavior/async.ok.js +1 -0
- package/public/vendor/okjs/toolkit/components/ok-chart.ok.js +1 -0
- package/public/vendor/okjs/toolkit/components/ok-doc-viewer.ok.js +1 -0
- package/public/vendor/okjs/toolkit/components/ok-marked.ok.js +1 -0
- package/public/vendor/okjs/toolkit/components/ok-monaco.ok.js +1 -0
- package/public/vendor/okjs/toolkit/core/ok-layer-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/core/ok-layer.ok.js +1 -0
- package/public/vendor/okjs/toolkit/core/ok-overlay.ok.js +1 -0
- package/public/vendor/okjs/toolkit/core/ok-sandbox.ok.js +1 -0
- package/public/vendor/okjs/toolkit/core/transition.ok.js +1 -0
- package/public/vendor/okjs/toolkit/core/virtualized.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-console-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-console.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-fps-monitor-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-fps-monitor.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-inspector-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-inspector.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-js-tree.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-timeline-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-timeline.ok.js +1 -0
- package/public/vendor/okjs/toolkit/docs/doc-viewer.ok.js +1 -0
- package/public/vendor/okjs/toolkit/editor/ok-live-component.ok.js +1 -0
- package/public/vendor/okjs/toolkit/editor/ok-live-example.ok.js +1 -0
- package/public/vendor/okjs/toolkit/editor/ok-live-shell.ok.js +1 -0
- package/public/vendor/okjs/toolkit/elements/ok-dropdown.ok.js +1 -0
- package/public/vendor/okjs/toolkit/elements/ok-icon.ok.js +1 -0
- package/public/vendor/okjs/toolkit/elements/ok-select.ok.js +1 -0
- package/public/vendor/okjs/toolkit/elements/ok-table.ok.js +1 -0
- package/public/vendor/okjs/toolkit/errors/ok-crash-boundary.ok.js +1 -0
- package/public/vendor/okjs/toolkit/errors/ok-crash-info.ok.js +1 -0
- package/public/vendor/okjs/toolkit/i18n/i18n-editor-examples.ok.js +1 -0
- package/public/vendor/okjs/toolkit/i18n/i18n-editor.ok.js +1 -0
- package/public/vendor/okjs/toolkit/i18n/i18n-key-editor-tips.ok.js +1 -0
- package/public/vendor/okjs/toolkit/i18n/i18n-key-editor.ok.js +1 -0
- package/public/vendor/okjs/toolkit/i18n/i18n-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/i18n/ok-i18n.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-contextmenu.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-dismiss-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-draggable.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-flyover-panel.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-flyover.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-highlight-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-highlight.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-hotkeys-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-picker-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-popout-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-popout.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-portal-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-submenu.ok.js +1 -0
- package/public/vendor/okjs/toolkit/layout/ok-resizer.ok.js +1 -0
- package/public/vendor/okjs/toolkit/manifest.js +1 -0
- package/public/vendor/okjs/toolkit/modals/ok-modal-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/modals/ok-modal.ok.js +1 -0
- package/public/vendor/okjs/toolkit/modals/ok-toast-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/modals/ok-window.ok.js +1 -0
- package/public/vendor/okjs/toolkit/navigation/hash-router.ok.js +1 -0
- package/public/vendor/okjs/toolkit/navigation/history-router.ok.js +1 -0
- package/public/vendor/okjs/toolkit/navigation/router.ok.js +1 -0
- package/public/vendor/okjs/toolkit/sync/reactive-sync-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/sync/reactive-sync.js +1 -0
- package/public/vendor/okjs/toolkit/theming/blocks/ok-design-button.css.js +1 -0
- package/public/vendor/okjs/toolkit/theming/blocks/ok-design-input.css.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-design-style.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-design-vars.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-design.ok.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-input.ok.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-style.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-utils.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-variable.ok.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-vm.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor.ok.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-theme.ok.js +1 -0
- package/public/view-models/type-query-vm.js +1 -0
- package/types/environment.d.ts +130 -0
- package/types/errors.d.ts +98 -0
- package/types/features/admin.d.ts +4 -0
- package/types/features/api.d.ts +18 -0
- package/types/features/auth.d.ts +31 -0
- package/types/features/embeddings.d.ts +39 -0
- package/types/features/engines.d.ts +23 -0
- package/types/features/files.d.ts +29 -0
- package/types/features/fts.d.ts +25 -0
- package/types/features/functions.d.ts +23 -0
- package/types/features/mcp.d.ts +17 -0
- package/types/features/pipelines.d.ts +19 -0
- package/types/features/queue.d.ts +189 -0
- package/types/features/sync.d.ts +34 -0
- package/types/features/time-machine.d.ts +32 -0
- package/types/features/views.d.ts +16 -0
- package/types/index.d.ts +153 -0
- package/types/options.d.ts +202 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Change Log & Events
|
|
2
|
+
|
|
3
|
+
Every write in OKDB — put, remove, registerType, registerIndex — increments a monotonic integer **clock** and appends an entry to the change log. This is the backbone of sync, the processor system, and reactive change handling.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## The clock
|
|
8
|
+
|
|
9
|
+
A per-environment monotonic integer. Starts at 0, increments by 1 on every write transaction.
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
// Current clock value
|
|
13
|
+
const clock = okdb.getClock();
|
|
14
|
+
|
|
15
|
+
// Per-type clock (highest clock seen for that type)
|
|
16
|
+
const usersClock = okdb.getClock('users');
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Reading the change log
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
// Get changes between clock 100 and 200 (both inclusive)
|
|
25
|
+
const changes = okdb.getChanges(null, 100, 200);
|
|
26
|
+
for (const change of changes) {
|
|
27
|
+
console.log(change);
|
|
28
|
+
// {
|
|
29
|
+
// clock: 142,
|
|
30
|
+
// id: 'item:users@alice',
|
|
31
|
+
// type: 'users',
|
|
32
|
+
// key: 'alice',
|
|
33
|
+
// action: 'put', // 'put' | 'remove' | 'type_register' | 'type_drop' | 'index_register' | 'index_drop'
|
|
34
|
+
// timestamp: 1709900000000, // HLC value (wall-clock ms + logical counter)
|
|
35
|
+
// origin: 'node-uuid',
|
|
36
|
+
// txnId: 'txn-uuid',
|
|
37
|
+
// }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Changes for a specific type only
|
|
41
|
+
const userChanges = okdb.getChanges('users', 0, okdb.getClock());
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
:::note Deduplication
|
|
45
|
+
The change log keeps only the **most recent** entry per primary key. If you write to `users/alice` three times, only the last write appears in the log. This is intentional — `getChanges` returns "what is the current state delta", not a full history.
|
|
46
|
+
:::
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Events
|
|
51
|
+
|
|
52
|
+
OKDB emits events on `okdb.events` (a standard Node.js `EventEmitter`) after each transaction commits.
|
|
53
|
+
|
|
54
|
+
### Data events
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
okdb.events.on('item:create', ({ type, key, value, clock, origin }) => {
|
|
58
|
+
console.log(`Created ${type}/${key}`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
okdb.events.on('item:update', ({ type, key, value, oldValue, clock }) => {
|
|
62
|
+
console.log(`Updated ${type}/${key}`);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
okdb.events.on('item:remove', ({ type, key, oldValue, clock }) => {
|
|
66
|
+
console.log(`Removed ${type}/${key}`);
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### System events
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
okdb.events.on('system:clock_change', ({ clock, type }) => { ... });
|
|
74
|
+
okdb.events.on('system:clock_change@users', ({ clock }) => { ... }); // type-specific
|
|
75
|
+
okdb.events.on('system:ready', (okdb) => { ... });
|
|
76
|
+
okdb.events.on('system:stopped', () => { ... });
|
|
77
|
+
okdb.events.on('env:opened', ({ name }) => { ... });
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Events fire **after** the LMDB transaction is durably committed. Listeners never see speculative data.
|
|
81
|
+
|
|
82
|
+
:::warning Backpressure
|
|
83
|
+
Events are emitted synchronously via `EventEmitter`. A slow listener blocks subsequent event delivery. For heavy processing, use the **Processor** system instead.
|
|
84
|
+
:::
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Processor
|
|
89
|
+
|
|
90
|
+
The Processor is OKDB's reactive change handler — a composable, configurable, inline-or-queue mechanism for reacting to type mutations.
|
|
91
|
+
|
|
92
|
+
Think of it as event listeners with superpowers: they can filter by origin, run inline or async, and are managed as named, stoppable handlers.
|
|
93
|
+
|
|
94
|
+
### Registering a handler
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
const stop = okdb.env('default').processor.register('orders', {
|
|
98
|
+
name: 'orders:fulfillment', // human-readable name
|
|
99
|
+
mode: 'inline', // 'inline' = synchronous, post-commit
|
|
100
|
+
originMode: 'all', // 'all' | 'local' | 'remote'
|
|
101
|
+
|
|
102
|
+
handler(changes) {
|
|
103
|
+
for (const ch of changes) {
|
|
104
|
+
if (ch.action !== 'put') continue;
|
|
105
|
+
if (ch.newValue.status === 'paid') {
|
|
106
|
+
scheduleShipment(ch.key, ch.newValue);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Stop the handler
|
|
113
|
+
stop();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Change object shape
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
{
|
|
120
|
+
type: 'orders',
|
|
121
|
+
key: 'o42',
|
|
122
|
+
action: 'put', // 'put' | 'remove'
|
|
123
|
+
oldValue: { ... }, // previous value (null if new)
|
|
124
|
+
newValue: { ... }, // new value (null if removed)
|
|
125
|
+
clock: 182,
|
|
126
|
+
origin: 'node-uuid',
|
|
127
|
+
txnId: 'txn-uuid',
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### originMode
|
|
132
|
+
|
|
133
|
+
| Value | Fires when |
|
|
134
|
+
| ---------- | ------------------------------------ |
|
|
135
|
+
| `'all'` | Any write, local or synced |
|
|
136
|
+
| `'local'` | Only writes originating on this node |
|
|
137
|
+
| `'remote'` | Only writes arriving via sync |
|
|
138
|
+
|
|
139
|
+
This is useful for reacting differently to local vs. replicated changes — e.g., sending a notification only when your own node creates a record, not when it receives one from a peer.
|
|
140
|
+
|
|
141
|
+
### Processor on a custom env
|
|
142
|
+
|
|
143
|
+
Each environment has its own processor:
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
const env = okdb.env('analytics');
|
|
147
|
+
const stop = env.processor.register('events', {
|
|
148
|
+
mode: 'inline',
|
|
149
|
+
originMode: 'all',
|
|
150
|
+
name: 'analytics:aggregate',
|
|
151
|
+
handler(changes) {
|
|
152
|
+
/* ... */
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## HLC timestamps
|
|
160
|
+
|
|
161
|
+
The `timestamp` field on change objects is a **Hybrid Logical Clock** value: a 64-bit integer encoding both wall-clock milliseconds and a logical counter. HLC values are monotonically increasing even when system clocks skew backwards, which makes them safe for LWW conflict resolution in sync.
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
// Decode an HLC value
|
|
165
|
+
const { OKDBHlc } = require('okdb/okdb-hlc');
|
|
166
|
+
const { wallMs, logical } = OKDBHlc.decode(change.timestamp);
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
You don't need to manage HLC values manually — OKDB handles them transparently.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Data Model
|
|
2
|
+
|
|
3
|
+
## Types
|
|
4
|
+
|
|
5
|
+
A **type** is a named collection of records — similar to a table in SQL or a collection in MongoDB.
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
await okdb.registerType('articles');
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Each type gets its own set of LMDB sub-databases: one for data, one for each secondary index, and one for FTS index metadata. All sub-databases live inside the same LMDB environment (directory) and participate in the same transactions.
|
|
12
|
+
|
|
13
|
+
### Type operations
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
// Register (throws if already exists)
|
|
17
|
+
await okdb.registerType('articles');
|
|
18
|
+
|
|
19
|
+
// Idempotent register + index setup
|
|
20
|
+
await okdb.ensureType('articles', {
|
|
21
|
+
indexes: [['author'], ['author', 'publishedAt'], { fields: ['location'], type: 'geo' }],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Check existence
|
|
25
|
+
okdb.hasType('articles'); // → boolean
|
|
26
|
+
|
|
27
|
+
// Drop (deletes all records, indexes, and sub-databases)
|
|
28
|
+
await okdb.dropType('articles');
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Records
|
|
34
|
+
|
|
35
|
+
A **record** is a key-value pair inside a type.
|
|
36
|
+
|
|
37
|
+
- **Key**: a non-empty string (your choice — UUID, slug, user ID, etc.)
|
|
38
|
+
- **Value**: any JSON-serialisable value
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
await okdb.put('articles', 'how-lmdb-works', {
|
|
42
|
+
title: 'How LMDB works',
|
|
43
|
+
author: 'alice',
|
|
44
|
+
body: '...',
|
|
45
|
+
publishedAt: Date.now(),
|
|
46
|
+
tags: ['database', 'internals'],
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Values can also be direct JSON scalars or arrays (`"hello"`, `42`, `true`, `null`, `[]`). Those values are stored and read back normally. However, object-oriented features such as `patch()` and observed field schema only apply to plain object records.
|
|
51
|
+
|
|
52
|
+
Values are stored with LZ4 compression by default. The raw bytes are LMDB's [ordered-binary](https://github.com/DoctorEvidence/ordered-binary) codec, which preserves type ordering for secondary indexes.
|
|
53
|
+
|
|
54
|
+
### Versions
|
|
55
|
+
|
|
56
|
+
Every record implicitly has a **version** — an integer that monotonically increases with each write. OKDB uses LMDB's `useVersions` feature for optimistic concurrency.
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
const entry = okdb.getEntry('articles', 'how-lmdb-works');
|
|
60
|
+
// entry = { value: {...}, version: 3 }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Environments
|
|
66
|
+
|
|
67
|
+
An **environment** is an isolated LMDB directory. OKDB opens several by default and you can create more.
|
|
68
|
+
|
|
69
|
+
### Built-in environments
|
|
70
|
+
|
|
71
|
+
| Name | Purpose | Synced |
|
|
72
|
+
| ------------------- | --------------------------- | ------ |
|
|
73
|
+
| `~system` | Node identity, env registry | yes |
|
|
74
|
+
| `default` | Your types and data | yes |
|
|
75
|
+
| `~<env>:emb:<type>` | Per-type embedding vectors | yes |
|
|
76
|
+
|
|
77
|
+
:::note
|
|
78
|
+
`queue` and `files` live inside each environment — each env has its own queue and file store.
|
|
79
|
+
:::
|
|
80
|
+
|
|
81
|
+
### Creating custom environments
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
// Create a new named environment
|
|
85
|
+
const analyticsEnv = await okdb.createEnvironment('analytics', {
|
|
86
|
+
sync: true, // include in multi-node sync (default: true)
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// It has the same API as okdb itself
|
|
90
|
+
await analyticsEnv.registerType('events');
|
|
91
|
+
await analyticsEnv.put('events', 'e1', { action: 'click', ts: Date.now() });
|
|
92
|
+
const evt = analyticsEnv.get('events', 'e1');
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Accessing an environment:
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
okdb.env('default'); // the default env
|
|
99
|
+
okdb.env('analytics'); // custom env by name
|
|
100
|
+
okdb.default; // shorthand for okdb.env('default')
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### When to use multiple environments
|
|
104
|
+
|
|
105
|
+
- Separate high-churn data from user data (e.g. analytics events vs. user records)
|
|
106
|
+
- Independent encryption keys per dataset
|
|
107
|
+
- Selective sync (set `sync: false` for data that should stay local)
|
|
108
|
+
- Physical isolation for compaction or backup
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Storage layout
|
|
113
|
+
|
|
114
|
+
OKDB creates this structure on disk:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
mydb/
|
|
118
|
+
~system/ ← node identity + env registry
|
|
119
|
+
default/ ← your types and records
|
|
120
|
+
~<env>:emb:<type>/← per-type vector store
|
|
121
|
+
~fts/ ← FTS indexes (one sub-dir per index)
|
|
122
|
+
blobs/ ← file attachment blobs (SHA-256 content-addressable)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Inside each LMDB environment, sub-databases are named by convention:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
"clock" → global monotonic integer
|
|
129
|
+
"types" → type metadata (field schema, FTS config)
|
|
130
|
+
"changes" → changeId → latest clock
|
|
131
|
+
"clocks" → clock → change object (the event log)
|
|
132
|
+
|
|
133
|
+
per type T:
|
|
134
|
+
"type:T" → lifecycle records
|
|
135
|
+
"type:T:data" → key → value (useVersions=true)
|
|
136
|
+
"type:T:index" → index metadata
|
|
137
|
+
"type:T:index:I" → dupSort ordered-binary index entries
|
|
138
|
+
"type:T:clockToChange" → clock → changeId
|
|
139
|
+
"type:T:keyToChange" → key → changeId
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Field schema
|
|
145
|
+
|
|
146
|
+
OKDB observes field types as you write data and maintains a coarse schema (`string | number | boolean | null | array | object`) per field. You can inspect it:
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
const desc = await okdb.meta.describeType('articles');
|
|
150
|
+
// {
|
|
151
|
+
// count: 42,
|
|
152
|
+
// fields: { title: 'string', publishedAt: 'number', tags: 'array', ... },
|
|
153
|
+
// indexes: ['author', 'author~publishedAt'],
|
|
154
|
+
// ftsIndexes: []
|
|
155
|
+
// }
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Schema is **observed-only** — no validation on write. If you need strict validation, do it at the application layer before calling `put`.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Durability modes
|
|
163
|
+
|
|
164
|
+
Set at construction time via the `durability` option:
|
|
165
|
+
|
|
166
|
+
| Mode | Behaviour | Use when |
|
|
167
|
+
| ---------- | -------------------------------------------------------- | ------------------------------ |
|
|
168
|
+
| `strict` | Full fsync on every write | Mission-critical, money |
|
|
169
|
+
| `balanced` | `overlappingSync` — background sync, still safe on crash | Default; most use cases |
|
|
170
|
+
| `fast` | `noSync` — OS controls flush timing | Dev, caches, re-generable data |
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
const okdb = new OKDB('./db', { durability: 'fast' });
|
|
174
|
+
```
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# Embeddings
|
|
2
|
+
|
|
3
|
+
OKDB has a built-in vector embedding pipeline. Point it at a type, tell it which field to embed and which ML model to use, and it handles the rest: watching the change log, calling the embedder, storing vectors, and serving nearest-neighbour queries.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Concepts
|
|
8
|
+
|
|
9
|
+
| Component | Role |
|
|
10
|
+
| ----------------- | ----------------------------------------------------- |
|
|
11
|
+
| **Embedder** | Connection to an ML model (Ollama, OpenAI, or custom) |
|
|
12
|
+
| **Indexer** | Watches a type's change log, embeds changed docs |
|
|
13
|
+
| **Vector store** | Persists `Float32Array` vectors in LMDB |
|
|
14
|
+
| **Search engine** | In-memory HNSW graph for nearest-neighbour queries |
|
|
15
|
+
|
|
16
|
+
These are wired together as **named engines** — persistent service instances whose config lives in OKDB and survive restarts.
|
|
17
|
+
|
|
18
|
+
Embeddings is now also a **built-in pipeline family** on top of the generic `~pipelines` feature:
|
|
19
|
+
|
|
20
|
+
- `okdb.embeddings.createPipeline(...)` still provides the specialized one-call setup
|
|
21
|
+
- the created topology is also persisted as an env-local generic pipeline record
|
|
22
|
+
- the generic record stores explicit ordered member roles (`embedder`, `indexer`, optional `worker`, `search`)
|
|
23
|
+
- embeddings HTTP/admin views read those generic records first and only fall back to legacy engine grouping for older pipelines
|
|
24
|
+
- starter templates now exist too: `GET /api/pipelines/templates` exposes recommended embeddings pipeline blueprints such as `embeddings-inline-fake`, `embeddings-inline-ollama`, `embeddings-queue-fake`, and `embeddings-queue-ollama`
|
|
25
|
+
- MCP clients can discover and expand those blueprints through `okdb_pipeline` actions `template_list`, `template_get`, `template_preview`, and `template_create`
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quick start with `createPipeline`
|
|
30
|
+
|
|
31
|
+
The one-call setup that provisions all four components:
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
await okdb.embeddings.createPipeline('articles', {
|
|
35
|
+
source_type: 'articles', // the OKDB type to watch
|
|
36
|
+
field: 'body', // which field to embed (null = stringify whole record)
|
|
37
|
+
dims: 1024, // embedding dimensions (must match model)
|
|
38
|
+
embedder: {
|
|
39
|
+
type: 'ollama',
|
|
40
|
+
model: 'mxbai-embed-large',
|
|
41
|
+
url: 'http://localhost:11434',
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
After this call, any `put` to `articles` will automatically trigger embedding. Vectors are stored in the `~default:emb:articles` environment.
|
|
47
|
+
|
|
48
|
+
The specialized embeddings facade now also writes a generic `~pipelines` record in the source environment. By default:
|
|
49
|
+
|
|
50
|
+
- the generic pipeline record name is the pipeline `storage_key`
|
|
51
|
+
- `meta.label` preserves the user-facing embeddings pipeline label
|
|
52
|
+
- `meta.family === 'embeddings'` marks the record as embeddings-backed
|
|
53
|
+
|
|
54
|
+
So a call like:
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
await okdb.embeddings.createPipeline('articles-body-pipeline', {
|
|
58
|
+
source_env: 'default',
|
|
59
|
+
source_type: 'articles',
|
|
60
|
+
storage_key: 'articles-body',
|
|
61
|
+
field: 'body',
|
|
62
|
+
dims: 768,
|
|
63
|
+
embedder: { type: 'ollama', model: 'mxbai-embed-large' },
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
creates engines plus a generic pipeline record named `articles-body` in `default/~pipelines`.
|
|
68
|
+
|
|
69
|
+
This `storage_key` behavior matters when you use the new template registry too:
|
|
70
|
+
|
|
71
|
+
- the starter input usually asks for a human-facing `name`
|
|
72
|
+
- embeddings template plans default `storage_key` to that same `name`
|
|
73
|
+
- if you override `storage_key`, the persisted generic pipeline record is stored under that value instead
|
|
74
|
+
- `meta.label` still keeps the original display label so admin/MCP clients can show a friendly name
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Templates and discovery
|
|
79
|
+
|
|
80
|
+
Embeddings templates are presets for the same underlying `okdb.embeddings.createPipeline(...)` flow.
|
|
81
|
+
|
|
82
|
+
You can discover them over HTTP:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
GET /api/pipelines/templates
|
|
86
|
+
GET /api/pipelines/templates/:template
|
|
87
|
+
POST /api/pipelines/templates/:template/preview
|
|
88
|
+
POST /api/env/:env/pipelines/templates/:template/create
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Built-in recommended embeddings pipeline starters currently include:
|
|
92
|
+
|
|
93
|
+
- `embeddings-inline-fake`
|
|
94
|
+
- `embeddings-inline-ollama`
|
|
95
|
+
- `embeddings-queue-fake`
|
|
96
|
+
- `embeddings-queue-ollama`
|
|
97
|
+
|
|
98
|
+
The preview route expands one of those starters into the exact `embeddings-create` plan it would execute, including the derived `storage_key`, `mode`, search `algorithm`, and provider config.
|
|
99
|
+
|
|
100
|
+
`POST /api/env/:env/pipelines/templates/:template/create` then executes that plan by calling the specialized embeddings facade and returning the resulting generic pipeline record.
|
|
101
|
+
|
|
102
|
+
If you want to build the pieces separately, the shared engine template registry also exposes direct embeddings engine starters through the engine API:
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
GET /api/engines/types
|
|
106
|
+
GET /api/engines/templates
|
|
107
|
+
GET /api/engines/templates/:template
|
|
108
|
+
POST /api/engines/templates/:template/preview
|
|
109
|
+
POST /api/env/:env/engines/templates/:template/create
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
That shared registry includes starter definitions such as:
|
|
113
|
+
|
|
114
|
+
- `embedder-fake`, `embedder-ollama`, `embedder-openai`
|
|
115
|
+
- `indexer-inline`, `indexer-queue`
|
|
116
|
+
- `vector-search-flat`, `vector-search-hnsw`
|
|
117
|
+
- `embed-worker-basic`
|
|
118
|
+
|
|
119
|
+
So the usual choice is:
|
|
120
|
+
|
|
121
|
+
- use pipeline templates when you want the full recommended embeddings topology in one step
|
|
122
|
+
- use engine templates when you want to compose or manage the embeddings engines individually
|
|
123
|
+
|
|
124
|
+
### MCP discovery and creation
|
|
125
|
+
|
|
126
|
+
Embeddings-related MCP behavior is now split across three grouped tools:
|
|
127
|
+
|
|
128
|
+
- `okdb_pipeline` — use `template_list`, `template_get`, `template_preview`, and `template_create` for full embeddings pipeline starters
|
|
129
|
+
- `okdb_engine` — use `types`, `templates`, `template_get`, `template_preview`, and `template_create` for direct engine discovery and creation
|
|
130
|
+
- `okdb_embeddings` — use `providers`, `models`, `models_by_provider`, `algorithms`, `probe_ollama_models`, and `embedder_models` to inspect provider schemas and model catalogs before choosing a template or creating engines manually
|
|
131
|
+
|
|
132
|
+
This makes it possible for MCP clients to both discover recommendations and actually execute them, rather than relying on out-of-band docs.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Built-in embedder types
|
|
137
|
+
|
|
138
|
+
### Ollama (local)
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
const embedder = {
|
|
142
|
+
type: 'ollama',
|
|
143
|
+
model: 'mxbai-embed-large', // or 'nomic-embed-text', etc.
|
|
144
|
+
url: 'http://localhost:11434',
|
|
145
|
+
};
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Requires [Ollama](https://ollama.ai) running locally:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
ollama pull mxbai-embed-large
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### OpenAI (and compatible APIs)
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
const embedder = {
|
|
158
|
+
type: 'openai',
|
|
159
|
+
model: 'text-embedding-3-small',
|
|
160
|
+
api_key: process.env.OPENAI_API_KEY,
|
|
161
|
+
// base_url: 'https://api.openai.com/v1' (default)
|
|
162
|
+
};
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Custom / fake
|
|
166
|
+
|
|
167
|
+
For development and testing without a real ML service:
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
okdb.embeddings.registerEmbedderFactory('my-embedder', (config) => ({
|
|
171
|
+
async embed(input) {
|
|
172
|
+
// Return a Float32Array of `config.dims` dimensions
|
|
173
|
+
return new Float32Array(config.dims).fill(0.5);
|
|
174
|
+
},
|
|
175
|
+
async health() {
|
|
176
|
+
return { ok: true };
|
|
177
|
+
},
|
|
178
|
+
}));
|
|
179
|
+
|
|
180
|
+
// Then use it in a pipeline:
|
|
181
|
+
await okdb.embeddings.createPipeline('articles', {
|
|
182
|
+
source_type: 'articles',
|
|
183
|
+
dims: 128,
|
|
184
|
+
embedder: { type: 'my-embedder' },
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Semantic search
|
|
191
|
+
|
|
192
|
+
`createPipeline` returns a pipeline object. Use `pipeline.api.search()` to run nearest-neighbour queries:
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
const pipeline = await okdb.embeddings.createPipeline('articles', {
|
|
196
|
+
source_type: 'articles',
|
|
197
|
+
field: 'body',
|
|
198
|
+
dims: 1024,
|
|
199
|
+
embedder: { type: 'ollama', model: 'mxbai-embed-large' },
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Text query — the pipeline embeds it then finds nearest neighbours
|
|
203
|
+
const results = await pipeline.api.search('database performance tuning', { limit: 5 });
|
|
204
|
+
for (const { key, score } of results) {
|
|
205
|
+
console.log(key, score.toFixed(4));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Float32Array query — skip the embed step
|
|
209
|
+
const vec = new Float32Array(1024).fill(0.1);
|
|
210
|
+
const results2 = await pipeline.api.search(vec, { limit: 10, threshold: 0.7 });
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Search options
|
|
214
|
+
|
|
215
|
+
| Option | Default | Description |
|
|
216
|
+
| ----------- | ------- | ------------------------------ |
|
|
217
|
+
| `limit` | 10 | Maximum results |
|
|
218
|
+
| `threshold` | 0 | Minimum similarity score (0–1) |
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Pipeline modes
|
|
223
|
+
|
|
224
|
+
### Inline (default)
|
|
225
|
+
|
|
226
|
+
The indexer calls the embedder synchronously in the change-log processor. Simple, no extra infrastructure:
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
await okdb.embeddings.createPipeline('articles', {
|
|
230
|
+
source_type: 'articles',
|
|
231
|
+
field: 'body',
|
|
232
|
+
dims: 1024,
|
|
233
|
+
mode: 'inline', // default
|
|
234
|
+
embedder: { type: 'ollama', model: 'mxbai-embed-large' },
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Queue mode
|
|
239
|
+
|
|
240
|
+
For high-throughput or external embedder services, the indexer enqueues jobs and a worker pool consumes them concurrently:
|
|
241
|
+
|
|
242
|
+
```javascript
|
|
243
|
+
await okdb.embeddings.createPipeline('articles', {
|
|
244
|
+
source_type: 'articles',
|
|
245
|
+
field: 'body',
|
|
246
|
+
dims: 1024,
|
|
247
|
+
mode: 'queue',
|
|
248
|
+
embedder: { type: 'openai', model: 'text-embedding-3-small', api_key: '...' },
|
|
249
|
+
worker: {
|
|
250
|
+
concurrency: 4,
|
|
251
|
+
pollInterval: 500,
|
|
252
|
+
ttl: 30_000,
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Queue-mode embeddings records keep the same explicit membership order in the generic pipeline registry, with the worker inserted between the indexer and search members.
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Waiting for indexing to complete
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
// For a pipeline named 'articles' in the default env, the scoped key is 'default:articles'
|
|
265
|
+
const indexer = okdb.embeddings.indexer('default:articles');
|
|
266
|
+
const stats = await indexer.stats();
|
|
267
|
+
// { total, pending, embedded, failed }
|
|
268
|
+
|
|
269
|
+
// Pipeline api.stats() wraps all four components
|
|
270
|
+
const { api } = await okdb.embeddings.createPipeline('articles', { ... });
|
|
271
|
+
const allStats = await api.stats();
|
|
272
|
+
// { embedder: {...}, indexer: {...}, worker: {...}, search: {...} }
|
|
273
|
+
|
|
274
|
+
// Wait for the indexer to finish processing all documents
|
|
275
|
+
function waitForIdle(pipelineKey, timeoutMs = 120_000) {
|
|
276
|
+
return new Promise((resolve, reject) => {
|
|
277
|
+
const timer = setTimeout(() => reject(new Error('timeout')), timeoutMs);
|
|
278
|
+
const check = async () => {
|
|
279
|
+
const api = okdb.embeddings.indexer(pipelineKey);
|
|
280
|
+
if (!api) return;
|
|
281
|
+
const { pending } = await api.stats().catch(() => ({ pending: 1 }));
|
|
282
|
+
if (pending === 0) { clearTimeout(timer); resolve(); }
|
|
283
|
+
};
|
|
284
|
+
okdb.events.on(`embeddings:indexer_flushed@indexer@${pipelineKey}`, check);
|
|
285
|
+
check();
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
await waitForIdle('default:articles');
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Engines
|
|
295
|
+
|
|
296
|
+
Pipelines are built on top of the **engines** feature — a generic lifecycle manager for named, persistent services. Each component (embedder, indexer, search, worker) is an engine with a type, name, config, and status stored in `~engines`.
|
|
297
|
+
|
|
298
|
+
They survive restarts: on `okdb.open()`, all previously-created engines are restored and their `start` methods called.
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
// List all running engines
|
|
302
|
+
const engines = okdb.engines.list();
|
|
303
|
+
// [{ name, type, status, config, meta }, ...]
|
|
304
|
+
|
|
305
|
+
// Get a specific engine
|
|
306
|
+
const embedder = okdb.engines.getEngine('embedder', 'articles');
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Shared embedder lifecycle behavior
|
|
310
|
+
|
|
311
|
+
Embeddings pipelines usually own the env-scoped indexer/search/worker engines, but the embedder is often shared across pipelines and environments.
|
|
312
|
+
|
|
313
|
+
To preserve that behavior when delegating lifecycle operations through generic pipelines, embeddings-backed pipeline records mark the embedder role as lifecycle-skipped.
|
|
314
|
+
|
|
315
|
+
That means specialized or generic pipeline `start` / `stop` operations:
|
|
316
|
+
|
|
317
|
+
- manage env-owned `indexer`, `search`, and optional `worker` members
|
|
318
|
+
- do **not** stop or restart the shared embedder just because one pipeline is paused
|
|
319
|
+
- still preserve the shared embedder on pipeline delete unless the embedder is explicitly managed elsewhere
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Storage layout
|
|
324
|
+
|
|
325
|
+
```
|
|
326
|
+
~default:emb:articles/
|
|
327
|
+
vec:<storage_key> ← Float32Array vectors in LMDB
|
|
328
|
+
doc_status:<storage_key> ← per-document embedding status
|
|
329
|
+
|
|
330
|
+
~system/
|
|
331
|
+
~emb:models ← model catalog (shared)
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
The vector store persists raw `Float32Array` bytes (not JSON), preserving full floating-point precision. The HNSW graph is rebuilt from these stored vectors on each restart.
|
|
335
|
+
|
|
336
|
+
:::note
|
|
337
|
+
Vector data (the raw Float32Array values stored in `vec:` types) **is replicated** via the normal change log — embedding is expensive, so every node receives the computed vectors automatically. The in-memory HNSW search graph is rebuilt locally from these synced vectors on each restart.
|
|
338
|
+
:::
|