aof-db 2.4.68

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 ADDED
@@ -0,0 +1,65 @@
1
+ Business Source License 1.1
2
+
3
+ Licensor: Interchained LLC, VibeCode 101, and/or the applicable copyright holders of NEDB
4
+
5
+ Licensed Work: NEDB, including all source code, documentation, examples, tests, build scripts, specifications, APIs, SDKs, packages, binaries, and derivative works distributed from or based on the NEDB repository.
6
+
7
+ Copyright: Copyright (c) 2026 Interchained LLC, VibeCode 101, and contributors. All rights reserved.
8
+
9
+ Change Date: Four years after the first publicly available distribution of each specific version of the Licensed Work under this License.
10
+
11
+ Change License: GNU Affero General Public License v3.0 or later.
12
+
13
+ Additional Use Grant:
14
+
15
+ You may use the Licensed Work only for non-production purposes, including evaluation, development, testing, research, local experimentation, security review, benchmarking, and personal learning.
16
+
17
+ No production use is granted automatically.
18
+
19
+ For clarity, the following uses are not permitted unless you have prior written permission from the Licensor:
20
+
21
+ 1. Using the Licensed Work in production.
22
+ 2. Using the Licensed Work to store, process, serve, index, replicate, query, or manage production data.
23
+ 3. Offering the Licensed Work, or any derivative work, as a hosted, managed, embedded, bundled, white-labeled, commercial, paid, revenue-generating, or customer-facing product or service.
24
+ 4. Offering database-as-a-service, storage-as-a-service, cache-as-a-service, search-as-a-service, analytics-as-a-service, AI-memory-as-a-service, agent-memory-as-a-service, blockchain-indexing-as-a-service, or any substantially similar service using the Licensed Work.
25
+ 5. Using the Licensed Work to compete with NEDB, Interchained LLC, VibeCode 101, AiAssist Secure, or any affiliated product, service, infrastructure platform, database engine, AI runtime, agent platform, or hosted developer infrastructure.
26
+ 6. Embedding the Licensed Work into commercial software, SaaS products, internal business systems, enterprise systems, hosted infrastructure, blockchain infrastructure, AI-agent infrastructure, or customer deliverables.
27
+ 7. Removing, hiding, modifying, or misrepresenting this License, copyright notices, attribution notices, authorship notices, repository references, or licensing notices.
28
+ 8. Circumventing license checks, access controls, paid licensing requirements, commercial-use restrictions, attribution requirements, or technical protections included with the Licensed Work.
29
+ 9. Using the Licensed Work in a way that implies endorsement, partnership, sponsorship, certification, or approval by the Licensor without written permission.
30
+ 10. Reselling, relicensing, sublicensing, renting, leasing, or otherwise commercially exploiting the Licensed Work except as expressly authorized in writing.
31
+
32
+ Additional production, commercial, hosted, enterprise, embedded, OEM, resale, white-label, managed-service, competitive, or otherwise restricted use grants are available only by separate written permission from the Licensor.
33
+
34
+ Written permission must be obtained from at least one of the following authorized licensing contacts:
35
+
36
+ [founders@vibecode-101.com](mailto:founders@vibecode-101.com)
37
+ [dev@interchained.org](mailto:dev@interchained.org)
38
+
39
+ Permission is valid only if it is expressly granted in writing by an authorized representative of the Licensor and specifically identifies the permitted use, scope, duration, parties, and any applicable commercial terms.
40
+
41
+ A general email conversation, informal message, pull request, issue comment, social media message, verbal discussion, repository access, package download, or contribution acceptance does not create a production, commercial, hosted, enterprise, resale, or competitive use license.
42
+
43
+ Terms:
44
+
45
+ The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make non-production use of the Licensed Work. The Licensor may make an Additional Use Grant, above, permitting limited production use.
46
+
47
+ Effective on the Change Date, or the fourth anniversary of the first publicly available distribution of a specific version of the Licensed Work under this License, whichever comes first, the Licensor hereby grants you rights under the terms of the Change License, and the rights granted in the paragraph above terminate.
48
+
49
+ If your use of the Licensed Work does not comply with the requirements currently in effect as described in this License, you must purchase or obtain a separate commercial license from the Licensor, its affiliated entities, or authorized resellers, or you must refrain from using the Licensed Work.
50
+
51
+ All copies of the original and modified Licensed Work, and derivative works of the Licensed Work, are subject to this License. This License applies separately for each version of the Licensed Work, and the Change Date may vary for each version of the Licensed Work released by Licensor.
52
+
53
+ You must conspicuously display this License on each original or modified copy of the Licensed Work. If you receive the Licensed Work in original or modified form from a third party, the terms and conditions set forth in this License apply to your use of that work.
54
+
55
+ Any use of the Licensed Work in violation of this License will automatically terminate your rights under this License for the current and all other versions of the Licensed Work.
56
+
57
+ This License does not grant you any right in any trademark, service mark, trade name, logo, domain name, brand identity, product name, project name, or other identifier of Licensor or its affiliates, except as expressly required to preserve legally required notices.
58
+
59
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN β€œAS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, TITLE, SECURITY, ACCURACY, AVAILABILITY, DATA INTEGRITY, PERFORMANCE, AND FITNESS FOR PRODUCTION USE.
60
+
61
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, LICENSOR SHALL NOT BE LIABLE FOR ANY CLAIM, DAMAGES, LOSSES, COSTS, EXPENSES, BUSINESS INTERRUPTION, LOST PROFITS, LOST REVENUE, LOST DATA, SECURITY INCIDENTS, SERVICE OUTAGES, OR OTHER LIABILITY ARISING FROM OR RELATED TO THE LICENSED WORK OR YOUR USE OF THE LICENSED WORK.
62
+
63
+ Notice:
64
+
65
+ The Business Source License is not an Open Source license. However, each version of the Licensed Work will become available under the Change License on the applicable Change Date, or on the fourth anniversary of the first publicly available distribution of that version under this License, whichever comes first.
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ <h1 align="center">⚑ aof-db</h1>
2
+ <p align="center"><b>The embedded database that goes brrr.</b></p>
3
+ <p align="center"><i>Stupid-fast, append-only, zero-server storage you drop into your app and forget about.</i></p>
4
+
5
+ ---
6
+
7
+ ```
8
+ 63,400 writes/sec 1,340,000 point reads/sec one fsync per batch
9
+ ```
10
+
11
+ aof-db is a **tiny, blazing-fast, in-process** datastore. No daemon. No schema migrations. No 400 MB of dependencies. Just `import` it and start flooding it with data β€” it keeps up.
12
+
13
+ ## Why people can't shut up about it
14
+
15
+ - 🏎️ **Fast where it counts.** Append-only + group-commit: one `fsync` per *batch*, not per record. Bigger batches get **cheaper per item**. Throughput goes *up* under load, not down.
16
+ - πŸͺΆ **Featherweight.** In-process, near-zero footprint. Boots instantly, runs in memory, persists when you say so.
17
+ - 🧷 **Durable, not fragile.** Every write is hash-chained and the log replays clean on open β€” crash-safe without the ceremony.
18
+ - πŸš€ **Scales with your ambition.** Same engine family as **crypto-database** β€” flip on the content-addressed DAG the day you need time-travel and tamper-evidence. Start fast, grow powerful.
19
+
20
+ ## Install (10 seconds, tops)
21
+
22
+ ```bash
23
+ npm install aof-db # pip install aof-db # cargo add aof-db
24
+ ```
25
+
26
+ ```js
27
+ import { NedbCore } from "aof-db";
28
+ const db = new NedbCore();
29
+ console.time("100k");
30
+ for (let i = 0; i < 100_000; i++) db.put("events", String(i), JSON.stringify({ t: Date.now(), i }));
31
+ db.flush(); // one durable group-commit for the whole burst
32
+ console.timeEnd("100k"); // …go on, time it.
33
+ ```
34
+
35
+ ## Built for
36
+
37
+ Edge & embedded Β· high-throughput event logging Β· local-first apps Β· game state Β· CLIs Β· **anywhere "just make it fast" beats "stand up a server."**
38
+
39
+ > **Note:** aof-db was previously published as `nitrodb`. The rename aligns the public package name with what it is β€” an append-only-file (AOF) fast-path distribution of NEDB. Same engine, same speed, new name.
40
+
41
+ <p align="center"><b>If aof-db just saved your afternoon, ⭐ it and tell a friend.</b></p>
42
+
43
+ ---
44
+
45
+ <sub>aof-db is a distribution of the <b>NEDB</b> engine, tuned for speed + simplicity (benchmarks measured on commodity hardware β€” yours will vary). Engine development: <a href="https://github.com/Eth-Interchained/nedb">Eth-Interchained/nedb</a>. Β© Interchained LLC.</sub>
Binary file
Binary file
package/index.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /* auto-generated by NAPI-RS */
5
+
6
+ export declare class NedbCore {
7
+ /** Create an in-memory v2 DAG database β€” zero disk I/O. */
8
+ constructor()
9
+ /**
10
+ * Open a durable v2 DAG database at `path`.
11
+ * Automatically migrates v1 AOF β†’ v2 DAG on first open.
12
+ */
13
+ static open(path: string): NedbCore
14
+ createIndex(coll: string, field: string, kind: string): void
15
+ /** Put a document. Returns the stored doc as a JSON string. */
16
+ put(coll: string, id: string, docJson: string): string
17
+ /** Full put with optional client / nonce β€” API compat, v2 ignores these. */
18
+ putEx(coll: string, id: string, docJson: string, client?: string | undefined | null, nonce?: bigint | undefined | null, idem?: string | undefined | null): string
19
+ delete(coll: string, id: string): void
20
+ deleteEx(coll: string, id: string, client?: string | undefined | null, nonce?: bigint | undefined | null, idem?: string | undefined | null): void
21
+ /** Link: stored as a doc in __links__ collection for NQL traversal. */
22
+ link(frm: string, rel: string, to: string): void
23
+ unlink(frm: string, rel: string, to: string): void
24
+ get(coll: string, id: string): string | null
25
+ getAsOf(coll: string, id: string, asOf: bigint): string | null
26
+ query(nqlStr: string): Array<string>
27
+ neighbors(frm: string, rel: string): Array<string>
28
+ neighborsAsOf(frm: string, rel: string, asOf: bigint): Array<string>
29
+ inbound(to: string, rel: string): Array<string>
30
+ inboundAsOf(to: string, rel: string, asOf: bigint): Array<string>
31
+ verify(): boolean
32
+ head(): string
33
+ seq(): bigint
34
+ /** Flush WAL and MANIFEST β€” v2 equivalent of v1 flush(). */
35
+ flush(): void
36
+ }
package/index.js ADDED
@@ -0,0 +1,315 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /* prettier-ignore */
4
+
5
+ /* auto-generated by NAPI-RS */
6
+
7
+ const { existsSync, readFileSync } = require('fs')
8
+ const { join } = require('path')
9
+
10
+ const { platform, arch } = process
11
+
12
+ let nativeBinding = null
13
+ let localFileExisted = false
14
+ let loadError = null
15
+
16
+ function isMusl() {
17
+ // For Node 10
18
+ if (!process.report || typeof process.report.getReport !== 'function') {
19
+ try {
20
+ const lddPath = require('child_process').execSync('which ldd').toString().trim()
21
+ return readFileSync(lddPath, 'utf8').includes('musl')
22
+ } catch (e) {
23
+ return true
24
+ }
25
+ } else {
26
+ const { glibcVersionRuntime } = process.report.getReport().header
27
+ return !glibcVersionRuntime
28
+ }
29
+ }
30
+
31
+ switch (platform) {
32
+ case 'android':
33
+ switch (arch) {
34
+ case 'arm64':
35
+ localFileExisted = existsSync(join(__dirname, 'aof-db.android-arm64.node'))
36
+ try {
37
+ if (localFileExisted) {
38
+ nativeBinding = require('./aof-db.android-arm64.node')
39
+ } else {
40
+ nativeBinding = require('aof-db-android-arm64')
41
+ }
42
+ } catch (e) {
43
+ loadError = e
44
+ }
45
+ break
46
+ case 'arm':
47
+ localFileExisted = existsSync(join(__dirname, 'aof-db.android-arm-eabi.node'))
48
+ try {
49
+ if (localFileExisted) {
50
+ nativeBinding = require('./aof-db.android-arm-eabi.node')
51
+ } else {
52
+ nativeBinding = require('aof-db-android-arm-eabi')
53
+ }
54
+ } catch (e) {
55
+ loadError = e
56
+ }
57
+ break
58
+ default:
59
+ throw new Error(`Unsupported architecture on Android ${arch}`)
60
+ }
61
+ break
62
+ case 'win32':
63
+ switch (arch) {
64
+ case 'x64':
65
+ localFileExisted = existsSync(
66
+ join(__dirname, 'aof-db.win32-x64-msvc.node')
67
+ )
68
+ try {
69
+ if (localFileExisted) {
70
+ nativeBinding = require('./aof-db.win32-x64-msvc.node')
71
+ } else {
72
+ nativeBinding = require('aof-db-win32-x64-msvc')
73
+ }
74
+ } catch (e) {
75
+ loadError = e
76
+ }
77
+ break
78
+ case 'ia32':
79
+ localFileExisted = existsSync(
80
+ join(__dirname, 'aof-db.win32-ia32-msvc.node')
81
+ )
82
+ try {
83
+ if (localFileExisted) {
84
+ nativeBinding = require('./aof-db.win32-ia32-msvc.node')
85
+ } else {
86
+ nativeBinding = require('aof-db-win32-ia32-msvc')
87
+ }
88
+ } catch (e) {
89
+ loadError = e
90
+ }
91
+ break
92
+ case 'arm64':
93
+ localFileExisted = existsSync(
94
+ join(__dirname, 'aof-db.win32-arm64-msvc.node')
95
+ )
96
+ try {
97
+ if (localFileExisted) {
98
+ nativeBinding = require('./aof-db.win32-arm64-msvc.node')
99
+ } else {
100
+ nativeBinding = require('aof-db-win32-arm64-msvc')
101
+ }
102
+ } catch (e) {
103
+ loadError = e
104
+ }
105
+ break
106
+ default:
107
+ throw new Error(`Unsupported architecture on Windows: ${arch}`)
108
+ }
109
+ break
110
+ case 'darwin':
111
+ localFileExisted = existsSync(join(__dirname, 'aof-db.darwin-universal.node'))
112
+ try {
113
+ if (localFileExisted) {
114
+ nativeBinding = require('./aof-db.darwin-universal.node')
115
+ } else {
116
+ nativeBinding = require('aof-db-darwin-universal')
117
+ }
118
+ break
119
+ } catch {}
120
+ switch (arch) {
121
+ case 'x64':
122
+ localFileExisted = existsSync(join(__dirname, 'aof-db.darwin-x64.node'))
123
+ try {
124
+ if (localFileExisted) {
125
+ nativeBinding = require('./aof-db.darwin-x64.node')
126
+ } else {
127
+ nativeBinding = require('aof-db-darwin-x64')
128
+ }
129
+ } catch (e) {
130
+ loadError = e
131
+ }
132
+ break
133
+ case 'arm64':
134
+ localFileExisted = existsSync(
135
+ join(__dirname, 'aof-db.darwin-arm64.node')
136
+ )
137
+ try {
138
+ if (localFileExisted) {
139
+ nativeBinding = require('./aof-db.darwin-arm64.node')
140
+ } else {
141
+ nativeBinding = require('aof-db-darwin-arm64')
142
+ }
143
+ } catch (e) {
144
+ loadError = e
145
+ }
146
+ break
147
+ default:
148
+ throw new Error(`Unsupported architecture on macOS: ${arch}`)
149
+ }
150
+ break
151
+ case 'freebsd':
152
+ if (arch !== 'x64') {
153
+ throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
154
+ }
155
+ localFileExisted = existsSync(join(__dirname, 'aof-db.freebsd-x64.node'))
156
+ try {
157
+ if (localFileExisted) {
158
+ nativeBinding = require('./aof-db.freebsd-x64.node')
159
+ } else {
160
+ nativeBinding = require('aof-db-freebsd-x64')
161
+ }
162
+ } catch (e) {
163
+ loadError = e
164
+ }
165
+ break
166
+ case 'linux':
167
+ switch (arch) {
168
+ case 'x64':
169
+ if (isMusl()) {
170
+ localFileExisted = existsSync(
171
+ join(__dirname, 'aof-db.linux-x64-musl.node')
172
+ )
173
+ try {
174
+ if (localFileExisted) {
175
+ nativeBinding = require('./aof-db.linux-x64-musl.node')
176
+ } else {
177
+ nativeBinding = require('aof-db-linux-x64-musl')
178
+ }
179
+ } catch (e) {
180
+ loadError = e
181
+ }
182
+ } else {
183
+ localFileExisted = existsSync(
184
+ join(__dirname, 'aof-db.linux-x64-gnu.node')
185
+ )
186
+ try {
187
+ if (localFileExisted) {
188
+ nativeBinding = require('./aof-db.linux-x64-gnu.node')
189
+ } else {
190
+ nativeBinding = require('aof-db-linux-x64-gnu')
191
+ }
192
+ } catch (e) {
193
+ loadError = e
194
+ }
195
+ }
196
+ break
197
+ case 'arm64':
198
+ if (isMusl()) {
199
+ localFileExisted = existsSync(
200
+ join(__dirname, 'aof-db.linux-arm64-musl.node')
201
+ )
202
+ try {
203
+ if (localFileExisted) {
204
+ nativeBinding = require('./aof-db.linux-arm64-musl.node')
205
+ } else {
206
+ nativeBinding = require('aof-db-linux-arm64-musl')
207
+ }
208
+ } catch (e) {
209
+ loadError = e
210
+ }
211
+ } else {
212
+ localFileExisted = existsSync(
213
+ join(__dirname, 'aof-db.linux-arm64-gnu.node')
214
+ )
215
+ try {
216
+ if (localFileExisted) {
217
+ nativeBinding = require('./aof-db.linux-arm64-gnu.node')
218
+ } else {
219
+ nativeBinding = require('aof-db-linux-arm64-gnu')
220
+ }
221
+ } catch (e) {
222
+ loadError = e
223
+ }
224
+ }
225
+ break
226
+ case 'arm':
227
+ if (isMusl()) {
228
+ localFileExisted = existsSync(
229
+ join(__dirname, 'aof-db.linux-arm-musleabihf.node')
230
+ )
231
+ try {
232
+ if (localFileExisted) {
233
+ nativeBinding = require('./aof-db.linux-arm-musleabihf.node')
234
+ } else {
235
+ nativeBinding = require('aof-db-linux-arm-musleabihf')
236
+ }
237
+ } catch (e) {
238
+ loadError = e
239
+ }
240
+ } else {
241
+ localFileExisted = existsSync(
242
+ join(__dirname, 'aof-db.linux-arm-gnueabihf.node')
243
+ )
244
+ try {
245
+ if (localFileExisted) {
246
+ nativeBinding = require('./aof-db.linux-arm-gnueabihf.node')
247
+ } else {
248
+ nativeBinding = require('aof-db-linux-arm-gnueabihf')
249
+ }
250
+ } catch (e) {
251
+ loadError = e
252
+ }
253
+ }
254
+ break
255
+ case 'riscv64':
256
+ if (isMusl()) {
257
+ localFileExisted = existsSync(
258
+ join(__dirname, 'aof-db.linux-riscv64-musl.node')
259
+ )
260
+ try {
261
+ if (localFileExisted) {
262
+ nativeBinding = require('./aof-db.linux-riscv64-musl.node')
263
+ } else {
264
+ nativeBinding = require('aof-db-linux-riscv64-musl')
265
+ }
266
+ } catch (e) {
267
+ loadError = e
268
+ }
269
+ } else {
270
+ localFileExisted = existsSync(
271
+ join(__dirname, 'aof-db.linux-riscv64-gnu.node')
272
+ )
273
+ try {
274
+ if (localFileExisted) {
275
+ nativeBinding = require('./aof-db.linux-riscv64-gnu.node')
276
+ } else {
277
+ nativeBinding = require('aof-db-linux-riscv64-gnu')
278
+ }
279
+ } catch (e) {
280
+ loadError = e
281
+ }
282
+ }
283
+ break
284
+ case 's390x':
285
+ localFileExisted = existsSync(
286
+ join(__dirname, 'aof-db.linux-s390x-gnu.node')
287
+ )
288
+ try {
289
+ if (localFileExisted) {
290
+ nativeBinding = require('./aof-db.linux-s390x-gnu.node')
291
+ } else {
292
+ nativeBinding = require('aof-db-linux-s390x-gnu')
293
+ }
294
+ } catch (e) {
295
+ loadError = e
296
+ }
297
+ break
298
+ default:
299
+ throw new Error(`Unsupported architecture on Linux: ${arch}`)
300
+ }
301
+ break
302
+ default:
303
+ throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
304
+ }
305
+
306
+ if (!nativeBinding) {
307
+ if (loadError) {
308
+ throw loadError
309
+ }
310
+ throw new Error(`Failed to load native binding`)
311
+ }
312
+
313
+ const { NedbCore } = nativeBinding
314
+
315
+ module.exports.NedbCore = NedbCore
Binary file
Binary file
package/nedbd-v2.js ADDED
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+ // nedbd-v2 β€” thin platform shim that locates and spawns the right
3
+ // pre-built native binary for the current platform/arch.
4
+ //
5
+ // The actual native binaries are shipped alongside this file in the
6
+ // npm package root (same directory as package.json). Naming convention
7
+ // matches the .node files produced by napi-rs:
8
+ //
9
+ // Linux x64 -> nedbd-v2-linux-x64
10
+ // Windows x64 -> nedbd-v2-win-x64.exe
11
+ // macOS arm64 -> nedbd-v2-darwin-arm64
12
+ // macOS x64 -> nedbd-v2-darwin-x64
13
+ //
14
+ // We pass through stdin/stdout/stderr and exit with the child's exit code so
15
+ // `npx nedbd-v2 …` is indistinguishable from invoking the binary directly.
16
+
17
+ "use strict";
18
+
19
+ const { spawn } = require("child_process");
20
+ const path = require("path");
21
+ const fs = require("fs");
22
+
23
+ function resolveBinaryName() {
24
+ const platform = process.platform; // 'linux' | 'darwin' | 'win32' | ...
25
+ const arch = process.arch; // 'x64' | 'arm64' | ...
26
+
27
+ if (platform === "linux" && arch === "x64") {
28
+ return "nedbd-v2-linux-x64";
29
+ }
30
+ if (platform === "win32" && arch === "x64") {
31
+ return "nedbd-v2-win-x64.exe";
32
+ }
33
+ if (platform === "darwin" && arch === "arm64") {
34
+ return "nedbd-v2-darwin-arm64";
35
+ }
36
+ if (platform === "darwin" && arch === "x64") {
37
+ return "nedbd-v2-darwin-x64";
38
+ }
39
+ return null;
40
+ }
41
+
42
+ function main() {
43
+ const name = resolveBinaryName();
44
+ if (!name) {
45
+ process.stderr.write(
46
+ `nedbd-v2: unsupported platform/arch: ${process.platform}/${process.arch}\n` +
47
+ `Supported: linux-x64, win32-x64, darwin-arm64, darwin-x64\n`
48
+ );
49
+ process.exit(1);
50
+ }
51
+
52
+ const binPath = path.join(__dirname, name);
53
+ if (!fs.existsSync(binPath)) {
54
+ process.stderr.write(
55
+ `nedbd-v2: binary not found for this platform: ${binPath}\n` +
56
+ `The nedb-engine npm package may be missing the prebuilt binary for ` +
57
+ `${process.platform}/${process.arch}.\n`
58
+ );
59
+ process.exit(1);
60
+ }
61
+
62
+ // Ensure executable bit on POSIX (npm sometimes strips it from tarballs).
63
+ if (process.platform !== "win32") {
64
+ try {
65
+ fs.chmodSync(binPath, 0o755);
66
+ } catch (_err) {
67
+ // best-effort; ignore β€” spawn will surface a clearer error if needed.
68
+ }
69
+ }
70
+
71
+ const child = spawn(binPath, process.argv.slice(2), {
72
+ stdio: "inherit",
73
+ windowsHide: false,
74
+ });
75
+
76
+ child.on("error", (err) => {
77
+ process.stderr.write(`nedbd-v2: failed to spawn ${binPath}: ${err.message}\n`);
78
+ process.exit(1);
79
+ });
80
+
81
+ child.on("exit", (code, signal) => {
82
+ if (signal) {
83
+ // Re-raise the signal so shells see the correct termination cause.
84
+ process.kill(process.pid, signal);
85
+ return;
86
+ }
87
+ process.exit(code === null ? 1 : code);
88
+ });
89
+ }
90
+
91
+ main();
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "aof-db",
3
+ "version": "2.4.68",
4
+ "description": "NEDB β€” hash-chained, time-traveling, bi-temporal embedded database with Rust native core. SQL, Redis, MongoDB adapters. Causal Write Provenance. RESP2 wire protocol.",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "bin": {
8
+ "nedbd-v2": "./nedbd-v2.js",
9
+ "nedbdv2": "./nedbd-v2.js"
10
+ },
11
+ "files": [
12
+ "index.js",
13
+ "index.d.ts",
14
+ "nedbd-v2.js",
15
+ "*.node",
16
+ "nedbd-v2*",
17
+ "test/smoke.mjs",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "license": "GPL-3.0-or-later",
22
+ "homepage": "https://github.com/aiassistsecure/nedb#readme",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/aiassistsecure/nedb.git"
26
+ },
27
+ "keywords": [
28
+ "database",
29
+ "embedded",
30
+ "mvcc",
31
+ "time-travel",
32
+ "bi-temporal",
33
+ "causal-provenance",
34
+ "tamper-evident",
35
+ "hash-chain",
36
+ "versioning",
37
+ "napi-rs",
38
+ "rust",
39
+ "nedb",
40
+ "aof",
41
+ "aof-db"
42
+ ],
43
+ "engines": {
44
+ "node": ">= 16"
45
+ },
46
+ "napi": {
47
+ "name": "aof-db",
48
+ "triples": {
49
+ "defaults": true,
50
+ "additional": [
51
+ "aarch64-apple-darwin",
52
+ "x86_64-unknown-linux-gnu",
53
+ "x86_64-pc-windows-msvc"
54
+ ]
55
+ }
56
+ },
57
+ "scripts": {
58
+ "build": "napi build --release --platform --cargo-cwd rust/crates/nedb-node",
59
+ "build:debug": "napi build --platform --cargo-cwd rust/crates/nedb-node",
60
+ "test": "node test/smoke.mjs"
61
+ },
62
+ "devDependencies": {
63
+ "@napi-rs/cli": "^2.18.0"
64
+ }
65
+ }
package/test/smoke.mjs ADDED
@@ -0,0 +1,299 @@
1
+ #!/usr/bin/env node
2
+ // nedb-engine β€” cinematic smoke test (`npm test`)
3
+ // ---------------------------------------------------------------------------
4
+ // A five-act tour of the engine, driven entirely by the real native addon
5
+ // (NedbCore β€” the same prebuilt .node binary the npm package ships). No external
6
+ // deps; Node built-ins only. Exits 0 on success.
7
+ //
8
+ // Act I β€” v1: the legacy append-only op-log (log.aof)
9
+ // Act II β€” automatic v1 -> v2 DAG migration (zero user action, lossless)
10
+ // Act III β€” v2: the content-addressed, hash-chained, time-traveling DAG
11
+ // Act IV β€” v3: the segment/pack object store (one fsync per group-commit)
12
+ // Act V β€” The Honest Dispatch: a causal rideshare decision you can audit
13
+ //
14
+ // Β© INTERCHAINED LLC Γ— Claude Opus 4.8
15
+ import os from 'node:os';
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+
19
+ // ── tiny presentation toolkit ───────────────────────────────────────────────
20
+ const COLOR = process.stdout.isTTY && !process.env.NO_COLOR;
21
+ const sgr = (code) => (s) => (COLOR ? `\x1b[${code}m${s}\x1b[0m` : String(s));
22
+ const dim = sgr('2'), bold = sgr('1'), ul = sgr('4');
23
+ const cyan = sgr('36'), green = sgr('32'), yellow = sgr('33');
24
+ const magenta = sgr('35'), blue = sgr('34'), red = sgr('31');
25
+
26
+ const log = (...a) => console.log(...a);
27
+ const rule = (ch = '─') => log(dim(ch.repeat(74)));
28
+ function act(n, title, subtitle) {
29
+ log('');
30
+ rule('═');
31
+ log(`${bold(magenta(` ACT ${n}`))} ${bold(title)}`);
32
+ if (subtitle) log(` ${dim(subtitle)}`);
33
+ rule('═');
34
+ }
35
+ const step = (s) => log(` ${cyan('β†’')} ${s}`);
36
+ const tick = (s) => log(` ${green('βœ“')} ${s}`);
37
+ const note = (s) => log(` ${dim(s)}`);
38
+ const kv = (k, v) => log(` ${dim(k.padEnd(16))} ${v}`);
39
+
40
+ // Positive sanity guard β€” confirms an invariant; only speaks up if reality
41
+ // disagrees (which, on an intact build, it won't).
42
+ function expect(cond, msg) {
43
+ if (!cond) {
44
+ log(` ${red('βœ—')} ${bold('unexpected:')} ${msg}`);
45
+ process.exitCode = 1;
46
+ throw new Error(msg);
47
+ }
48
+ }
49
+
50
+ const short = (h) => (h ? `${h.slice(0, 12)}…` : 'βˆ…');
51
+ const tmp = (label) => fs.mkdtempSync(path.join(os.tmpdir(), `nedb-smoke-${label}-`));
52
+ const rm = (d) => { try { fs.rmSync(d, { recursive: true, force: true }); } catch {} };
53
+
54
+ // ── resolve the native addon (installed package, or in-repo after a build) ───
55
+ let NedbCore;
56
+ try {
57
+ ({ NedbCore } = await import('nedb-engine'));
58
+ } catch {
59
+ try {
60
+ ({ NedbCore } = await import(new URL('../index.js', import.meta.url)));
61
+ } catch (err) {
62
+ log(red(bold('\n nedb-engine native addon not found.')));
63
+ note('This smoke test drives the prebuilt NedbCore binding.');
64
+ note('From a source checkout, build it first: npm run build');
65
+ note(`(${err && err.message ? err.message : err})`);
66
+ process.exit(1);
67
+ }
68
+ }
69
+
70
+ // ── banner ───────────────────────────────────────────────────────────────────
71
+ log('');
72
+ log(bold(cyan(' N E D B Β· native smoke test')));
73
+ log(dim(' hash-chained Β· bi-temporal Β· causal-provenance Β· v1β†’v2β†’v3'));
74
+
75
+ const cleanup = [];
76
+ const t0 = Date.now();
77
+ try {
78
+
79
+ // ════════════════════════════════════════════════════════════════════════
80
+ act('I', 'v1 β€” the legacy append-only op-log', 'where NEDB began: one JSON op per line, in log.aof');
81
+
82
+ const v1dir = tmp('v1'); cleanup.push(v1dir);
83
+ // The v1 wire format the migrator understands: {seq, op, ts, payload:{coll,id,doc}}.
84
+ // A tiny legacy "rides ledger" left behind by an older NEDB.
85
+ const v1ops = [
86
+ { seq: 0, op: 'put', ts: 1719400000.0, payload: { coll: 'trips', id: 't-1001', doc: { rider: 'maya', driver: 'sam', fare: 18.5, city: 'metropolis' } } },
87
+ { seq: 1, op: 'put', ts: 1719400100.0, payload: { coll: 'trips', id: 't-1002', doc: { rider: 'omar', driver: 'ana', fare: 24.0, city: 'metropolis' } } },
88
+ { seq: 2, op: 'put', ts: 1719400200.0, payload: { coll: 'drivers', id: 'sam', doc: { name: 'Sam', rating: 4.97, joined: 2024 } } },
89
+ ];
90
+ fs.writeFileSync(path.join(v1dir, 'log.aof'), v1ops.map((o) => JSON.stringify(o)).join('\n') + '\n');
91
+ step(`wrote a legacy ${bold('log.aof')} β€” ${v1ops.length} ops, append-only, plain JSON`);
92
+ for (const o of v1ops) note(`${o.op.toUpperCase()} ${o.payload.coll}/${o.payload.id} ${JSON.stringify(o.payload.doc)}`);
93
+ note('No hashes. No chain. No time-travel. Just an op log β€” that\'s v1.');
94
+
95
+ // ════════════════════════════════════════════════════════════════════════
96
+ act('II', 'v1 β†’ v2 β€” automatic migration', 'open() detects log.aof and rebuilds it as a content-addressed DAG');
97
+
98
+ step(`${bold('NedbCore.open(dir)')} β€” the engine speaks for itself:`);
99
+ const migrated = NedbCore.open(v1dir); // <- triggers migrate_if_needed()
100
+ const t1001 = JSON.parse(migrated.get('trips', 't-1001'));
101
+ tick(`legacy data is live in v2: trips/t-1001 β†’ fare ${bold(t1001.fare)}, rider ${bold(t1001.rider)}`);
102
+ kv('now content-addressed', `_hash ${cyan(short(t1001._hash))} (BLAKE2b-256, ${t1001._hash.length} hex chars)`);
103
+ expect(t1001._hash && t1001._hash.length === 64, 'migrated node is content-addressed');
104
+
105
+ const bakKept = fs.existsSync(path.join(v1dir, 'log.aof.v1.bak'));
106
+ const aofGone = !fs.existsSync(path.join(v1dir, 'log.aof'));
107
+ tick(`non-destructive: original preserved as ${bold('log.aof.v1.bak')} (${bakKept ? 'kept' : 'MISSING'})`);
108
+ expect(bakKept && aofGone, 'log.aof renamed to .v1.bak after migration');
109
+ tick(`integrity after migration: verify() = ${green(String(migrated.verify()))}`);
110
+ expect(migrated.verify() === true, 'migrated store verifies clean');
111
+ note('Zero user action. Lossless. Reversible. The op-log became a DAG.');
112
+
113
+ // ════════════════════════════════════════════════════════════════════════
114
+ act('III', 'v2 β€” the content-addressed DAG', 'hash chain Β· MVCC time-travel Β· causal graph Β· tamper-evident');
115
+
116
+ const v2 = new NedbCore(); // pure in-memory v2 β€” zero disk I/O
117
+ step('a fresh in-memory v2 DAG (no disk) β€” watch the Merkle head advance');
118
+ const h0 = v2.head(), s0 = v2.seq();
119
+ for (let i = 0; i < 5; i++) v2.put('blocks', String(i), JSON.stringify({ height: i, note: `block ${i}` }));
120
+ kv('seq', `${dim(String(s0))} β†’ ${bold(String(v2.seq()))} (every write extends the chain)`);
121
+ kv('head', `${dim(short(h0))} β†’ ${bold(short(v2.head()))}`);
122
+ expect(v2.seq() > s0 && v2.head() !== h0, 'chain advanced');
123
+
124
+ step('NQL β€” a real query language over the DAG');
125
+ kv('FROM blocks', `${v2.query('FROM blocks').length} rows`);
126
+ const q = v2.query('FROM blocks WHERE height = 3').map(JSON.parse);
127
+ kv('… WHERE height = 3', `${q.length} row β†’ ${JSON.stringify({ height: q[0].height, note: q[0].note })}`);
128
+
129
+ step('MVCC time-travel β€” AS OF a past sequence');
130
+ const v1n = JSON.parse(v2.put('account', 'alice', JSON.stringify({ balance: 100 })));
131
+ const asOf = BigInt(v1n._seq);
132
+ v2.put('account', 'alice', JSON.stringify({ balance: 250 })); // new version, same id
133
+ const nowBal = JSON.parse(v2.get('account', 'alice')).balance;
134
+ const thenBal = JSON.parse(v2.getAsOf('account', 'alice', asOf)).balance;
135
+ kv('alice now', bold(`$${nowBal}`));
136
+ kv(`alice AS OF #${asOf}`, bold(`$${thenBal}`) + dim(' ← the past is still there, exactly'));
137
+ expect(nowBal === 250 && thenBal === 100, 'AS OF returns the historical version');
138
+
139
+ step('causal graph β€” typed edges you can traverse');
140
+ v2.link('blocks:4', 'prev', 'blocks:3');
141
+ v2.link('blocks:3', 'prev', 'blocks:2');
142
+ kv('neighbors(4,prev)', JSON.stringify(v2.neighbors('blocks:4', 'prev')));
143
+ kv('inbound(3,prev)', JSON.stringify(v2.inbound('blocks:3', 'prev')) + dim(' ← who points at me?'));
144
+
145
+ step('tamper-evidence β€” the whole store self-verifies');
146
+ tick(`verify() = ${green(String(v2.verify()))} ${dim('β€” every node\'s hash checked against its content')}`);
147
+ expect(v2.verify() === true, 'intact store verifies clean');
148
+
149
+ // ════════════════════════════════════════════════════════════════════════
150
+ act('IV', 'v3 β€” the segment/pack object store', 'same API, denser substrate: one fsync per group-commit, not per object');
151
+
152
+ const docs = 64;
153
+ const sample = (i) => JSON.stringify({ i, payload: `coin-${i}`, ts: 1719400000 + i });
154
+
155
+ // v2 substrate (env unset): loose objects β€” one file per write.
156
+ delete process.env.NEDB_DAG_V3;
157
+ const looseDir = tmp('v2loose'); cleanup.push(looseDir);
158
+ const looseDb = NedbCore.open(looseDir);
159
+ for (let i = 0; i < docs; i++) looseDb.put('utxo', String(i), sample(i));
160
+ looseDb.flush();
161
+ const looseObjs = countFiles(path.join(looseDir, 'objects'));
162
+ step(`v2 default substrate β€” ${bold(docs)} writes`);
163
+ kv('objects/ layout', `${bold(looseObjs)} loose object files ${dim('(content-addressed, one per object)')}`);
164
+
165
+ // v3 substrate (env set BEFORE open β€” the engine reads it per-open).
166
+ process.env.NEDB_DAG_V3 = '1';
167
+ const segDir = tmp('v3seg'); cleanup.push(segDir);
168
+ const segDb = NedbCore.open(segDir);
169
+ for (let i = 0; i < docs; i++) segDb.put('utxo', String(i), sample(i));
170
+ segDb.flush();
171
+ const segPath = path.join(segDir, 'objects', 'segments');
172
+ const segFiles = fs.existsSync(segPath) ? fs.readdirSync(segPath).filter((f) => f.endsWith('.dat')) : [];
173
+ step(`v3 segment substrate β€” ${bold(docs)} writes ${dim('(NEDB_DAG_V3=1)')}`);
174
+ if (segFiles.length) {
175
+ const seg0 = path.join(segPath, segFiles[0]);
176
+ const sz = fs.statSync(seg0).size;
177
+ kv('objects/segments/', `${bold(segFiles.length)} segment file: ${cyan(segFiles[0])} (${sz} bytes)`);
178
+ note(`${docs} objects packed into ${segFiles.length} append-only segment β€” the metadata-write ceiling is gone.`);
179
+ } else {
180
+ note('segment file not present yet (objects buffered) β€” engine still serving from memory+WAL.');
181
+ }
182
+ step('v3 round-trips and verifies after reopen');
183
+ const segReopen = NedbCore.open(segDir); // env still set β†’ reopen as v3
184
+ const rt = JSON.parse(segReopen.get('utxo', '7'));
185
+ kv('reopen β†’ utxo/7', JSON.stringify({ i: rt.i, payload: rt.payload }));
186
+ tick(`verify() = ${green(String(segReopen.verify()))} ${dim('β€” segment store + dual-read of any v2 loose objects')}`);
187
+ expect(rt.i === 7 && segReopen.verify() === true, 'v3 persists and verifies across reopen');
188
+ delete process.env.NEDB_DAG_V3; // leave the environment as we found it
189
+
190
+ // ════════════════════════════════════════════════════════════════════════
191
+ act('V', 'The Honest Dispatch', 'a rideshare match that ends in a good choice β€” because the data says so');
192
+
193
+ const rs = new NedbCore();
194
+ step('the world at request time β€” a rider and three real candidates');
195
+ // Facts. Each put() returns the stored node; we keep its _hash as an immutable
196
+ // citation we can later prove the decision was built from.
197
+ const req = JSON.parse(rs.put('request', 'trip-9001', JSON.stringify({
198
+ rider: 'maya', from: 'zone:downtown', to: 'airport', when: '18:10', wants: 'fast pickup',
199
+ })));
200
+ const surge = JSON.parse(rs.put('surge', 'zone:downtown', JSON.stringify({ multiplier: 1.2, at: '18:10' })));
201
+
202
+ const drivers = {
203
+ sam: { name: 'Sam', rating: 4.97, etaMin: 4, distMi: 0.7, recentCancels: 0, acceptsPool: true },
204
+ lee: { name: 'Lee', rating: 4.61, etaMin: 2, distMi: 0.3, recentCancels: 2, acceptsPool: false },
205
+ ana: { name: 'Ana', rating: 4.99, etaMin: 9, distMi: 1.2, recentCancels: 0, acceptsPool: true },
206
+ };
207
+ const driverHash = {};
208
+ for (const [id, d] of Object.entries(drivers)) {
209
+ driverHash[id] = JSON.parse(rs.put('driver', id, JSON.stringify({ ...d, available: true })))._hash;
210
+ }
211
+ for (const [id, d] of Object.entries(drivers)) {
212
+ kv(`driver:${id}`, `${d.name} β˜…${d.rating} eta ${d.etaMin}m ${d.distMi}mi cancels ${d.recentCancels} ${d.acceptsPool ? 'pool' : 'solo'}`);
213
+ }
214
+ note(`surge in downtown right now: ${bold(surge.multiplier + 'Γ—')}`);
215
+
216
+ step('score every candidate from the stored facts β€” not a hunch, a calculation');
217
+ // Transparent scoring: reward rating + ETA + pool; penalize recent cancels.
218
+ const score = (d) => +(
219
+ d.rating * 2
220
+ - d.etaMin * 0.25
221
+ - d.recentCancels * 1.5
222
+ + (d.acceptsPool ? 0.4 : 0)
223
+ ).toFixed(3);
224
+ const ranked = Object.entries(drivers)
225
+ .map(([id, d]) => ({ id, d, s: score(d) }))
226
+ .sort((a, b) => b.s - a.s);
227
+ for (const r of ranked) {
228
+ const why = r.id === 'lee' ? dim('(closest β€” but 2 recent cancels drag it down)')
229
+ : r.id === 'ana' ? dim('(top-rated β€” but a 9-min ETA for a "fast pickup")')
230
+ : dim('(4.97β˜…, 4-min ETA, no cancels, takes pool)');
231
+ kv(`score driver:${r.id}`, `${bold(r.s.toFixed(2))} ${why}`);
232
+ }
233
+ const winner = ranked[0];
234
+ note(`naive "closest" would pick ${bold('Lee')} (0.3mi). The data picks ${bold(drivers[winner.id].name)}.`);
235
+
236
+ step('record the decision β€” with its causes wired in, permanently');
237
+ const decision = JSON.parse(rs.put('decision', 'trip-9001', JSON.stringify({
238
+ chosen: `driver:${winner.id}`,
239
+ score: winner.s,
240
+ policy: 'rating+eta+reliability+pool',
241
+ // caused_by: the exact immutable facts this choice was built from.
242
+ caused_by: [req._hash, surge._hash, driverHash[winner.id]],
243
+ })));
244
+ const decisionSeq = BigInt(decision._seq);
245
+ rs.link(`decision:trip-9001`, 'chose', `driver:${winner.id}`);
246
+ for (const r of ranked.slice(1)) rs.link(`decision:trip-9001`, 'considered', `driver:${r.id}`);
247
+ tick(`chose ${bold('driver:' + winner.id)} (${drivers[winner.id].name}) Β· decision is now a node in the DAG`);
248
+
249
+ step('AUDIT β€” "why did dispatch pick this driver?" Follow the causal trail.');
250
+ kv('decision.caused_by', `[ ${decision.caused_by.map(short).join(', ')} ]`);
251
+ const causeLabel = { [req._hash]: 'the rider\'s request', [surge._hash]: 'the surge snapshot', [driverHash[winner.id]]: `${drivers[winner.id].name}'s live state` };
252
+ for (const h of decision.caused_by) note(`${cyan(short(h))} β†’ ${causeLabel[h] || 'a fact'}`);
253
+ kv('chose', JSON.stringify(rs.neighbors('decision:trip-9001', 'chose')));
254
+ kv('considered', JSON.stringify(rs.neighbors('decision:trip-9001', 'considered')) + dim(' ← the alternatives, on the record'));
255
+
256
+ step('REPRODUCE β€” surge spikes after the fact; the decision is unmoved');
257
+ rs.put('surge', 'zone:downtown', JSON.stringify({ multiplier: 2.5, at: '18:25' })); // later reality
258
+ const surgeNow = JSON.parse(rs.get('surge', 'zone:downtown')).multiplier;
259
+ const surgeThen = JSON.parse(rs.getAsOf('surge', 'zone:downtown', decisionSeq)).multiplier;
260
+ kv('surge now', bold(surgeNow + 'Γ—'));
261
+ kv('surge AS OF decision', bold(surgeThen + 'Γ—') + dim(' ← what the dispatcher actually saw; the audit is reproducible'));
262
+ expect(surgeThen === 1.2, 'AS OF reconstructs the world at decision time');
263
+
264
+ step('the good ending');
265
+ rs.put('trip', 'trip-9001', JSON.stringify({ status: 'completed', driver: `driver:${winner.id}`, riderRating: 5 }));
266
+ tick(`${bold('Maya')} matched with ${bold(drivers[winner.id].name)} β†’ trip completed β†’ ${yellow('β˜…β˜…β˜…β˜…β˜…')}`);
267
+ tick(`fully auditable, content-addressed, reproducible β€” ${bold('a good choice, grounded in causal data')}`);
268
+ expect(rs.verify() === true, 'rideshare store verifies clean');
269
+
270
+ // ── curtain ────────────────────────────────────────────────────────────────
271
+ log('');
272
+ rule('═');
273
+ log(` ${green(bold('βœ“ all five acts passed'))} ${dim(`in ${Date.now() - t0} ms`)}`);
274
+ log(` ${dim('v1 β†’ v2 migration Β· v2 DAG Β· v3 segments Β· causal audit β€” all on the native engine')}`);
275
+ rule('═');
276
+ log('');
277
+ } catch (err) {
278
+ log('');
279
+ log(red(bold(' smoke test failed:')) + ' ' + (err && err.message ? err.message : String(err)));
280
+ if (err && err.stack) log(dim(err.stack.split('\n').slice(1, 4).join('\n')));
281
+ process.exitCode = 1;
282
+ } finally {
283
+ for (const d of cleanup) rm(d);
284
+ }
285
+
286
+ // ── helpers ────────────────────────────────────────────────────────────────
287
+ function countFiles(root) {
288
+ let n = 0;
289
+ const walk = (d) => {
290
+ let ents;
291
+ try { ents = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
292
+ for (const e of ents) {
293
+ const p = path.join(d, e.name);
294
+ if (e.isDirectory()) walk(p); else n++;
295
+ }
296
+ };
297
+ walk(root);
298
+ return n;
299
+ }