nedb-engine 2.5.0 → 2.5.34
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/index.d.ts +28 -0
- package/nedb-inspector.mjs +377 -0
- package/nedb.darwin-arm64.node +0 -0
- package/nedb.darwin-x64.node +0 -0
- package/nedb.linux-x64-gnu.node +0 -0
- package/nedb.win32-x64-msvc.node +0 -0
- package/nedbd-v2-darwin-arm64 +0 -0
- package/nedbd-v2-darwin-x64 +0 -0
- package/nedbd-v2-linux-x64 +0 -0
- package/nedbd-v2-win-x64.exe +0 -0
- package/package.json +8 -4
package/index.d.ts
CHANGED
|
@@ -9,6 +9,11 @@ export declare class NedbCore {
|
|
|
9
9
|
/**
|
|
10
10
|
* Open a durable v2 DAG database at `path`.
|
|
11
11
|
* Automatically migrates v1 AOF → v2 DAG on first open.
|
|
12
|
+
*
|
|
13
|
+
* Durable-mode auto-flush-on-exit is wired in the JS wrapper via
|
|
14
|
+
* `process.on('SIGTERM'|'SIGINT'|'beforeExit', () => db.flush())` — the
|
|
15
|
+
* libuv-cooperative hook — NOT a C-level signal handler here, which would
|
|
16
|
+
* clobber libuv's own signal machinery.
|
|
12
17
|
*/
|
|
13
18
|
static open(path: string): NedbCore
|
|
14
19
|
createIndex(coll: string, field: string, kind: string): void
|
|
@@ -33,4 +38,27 @@ export declare class NedbCore {
|
|
|
33
38
|
seq(): bigint
|
|
34
39
|
/** Flush WAL and MANIFEST — v2 equivalent of v1 flush(). */
|
|
35
40
|
flush(): void
|
|
41
|
+
/**
|
|
42
|
+
* The tip — the most recent write (latest node) as a JSON string, or null if
|
|
43
|
+
* the database is empty. The cheap "give me the latest write" primitive.
|
|
44
|
+
*/
|
|
45
|
+
tip(): string | null
|
|
46
|
+
/**
|
|
47
|
+
* Collection-local tip — the most recent write into `coll` as a JSON string,
|
|
48
|
+
* or null if the collection has no writes. Resume one chain without filtering.
|
|
49
|
+
*/
|
|
50
|
+
tipCollection(coll: string): string | null
|
|
51
|
+
/**
|
|
52
|
+
* Changefeed page after `after_seq` (exclusive), up to `limit` nodes (0 = the
|
|
53
|
+
* engine default cap), as a JSON envelope string:
|
|
54
|
+
* `{nodes, from_seq, to_seq, head_seq, has_more}`. Page while `has_more`,
|
|
55
|
+
* advancing your cursor to `to_seq`, then attach to the live subscribe edge.
|
|
56
|
+
*/
|
|
57
|
+
since(afterSeq: bigint, limit: number): string
|
|
58
|
+
/**
|
|
59
|
+
* Replication readiness as a JSON string: `{scan_complete, tip_seq,
|
|
60
|
+
* indexed_seq_min, indexed_seq_max, indexed_count}`. Wait for
|
|
61
|
+
* `scan_complete == true` before trusting historical `since()` catch-up.
|
|
62
|
+
*/
|
|
63
|
+
scanStatus(): string
|
|
36
64
|
}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// nedb-inspector — deterministic embedder-code checker for NEDB durability.
|
|
3
|
+
//
|
|
4
|
+
// nedb-inspector.mjs <pathToTarget.(rs|js|mjs|ts|py)> [more paths...]
|
|
5
|
+
//
|
|
6
|
+
// It reads how a program EMBEDS NEDB and warns LOUDLY, with the exact correct
|
|
7
|
+
// pattern, when a durable database is opened without flush-on-exit wiring — the
|
|
8
|
+
// "flush on every put? no; lose data on Ctrl+C? also no" mistake. It is the
|
|
9
|
+
// guardrail for the durable-mode auto-flush-on-exit contract.
|
|
10
|
+
//
|
|
11
|
+
// DETERMINISTIC BY DESIGN — no regex, no AI/LLM:
|
|
12
|
+
// 1. A per-language lexer masks comment and string contents (so a `Db::open`
|
|
13
|
+
// inside a comment or a string literal is NEVER matched — the classic
|
|
14
|
+
// regex false-positive), while preserving byte offsets and line numbers.
|
|
15
|
+
// 2. Structural token matching over the masked code finds durable-open call
|
|
16
|
+
// sites and the wiring calls that make them safe.
|
|
17
|
+
// 3. A fixed rule table decides OK / INFO / WARN and prints the exact fix.
|
|
18
|
+
//
|
|
19
|
+
// Exit code: 0 = clean (no warnings), 1 = warnings found, 2 = usage/read error.
|
|
20
|
+
// Set NO_COLOR=1 for plain output. Importable: `import { inspect } from './nedb-inspector.mjs'`.
|
|
21
|
+
//
|
|
22
|
+
// © INTERCHAINED LLC × Claude Opus 4.8
|
|
23
|
+
|
|
24
|
+
import { readFileSync } from 'node:fs';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
26
|
+
import path from 'node:path';
|
|
27
|
+
|
|
28
|
+
// ── presentation ─────────────────────────────────────────────────────────────
|
|
29
|
+
const COLOR = !process.env.NO_COLOR && (process.stdout.isTTY ?? false);
|
|
30
|
+
const sgr = (c) => (s) => (COLOR ? `\x1b[${c}m${s}\x1b[0m` : String(s));
|
|
31
|
+
const bold = sgr('1'), dim = sgr('2'), red = sgr('31'), green = sgr('32');
|
|
32
|
+
const yellow = sgr('33'), cyan = sgr('36'), magenta = sgr('35');
|
|
33
|
+
|
|
34
|
+
// ── language table ─────────────────────────────────────────────────────────
|
|
35
|
+
const LANGS = {
|
|
36
|
+
rust: { exts: ['.rs'], line: ['//'], block: [['/*', '*/']], nestBlock: true,
|
|
37
|
+
quotes: ['"'], rustRaw: true },
|
|
38
|
+
js: { exts: ['.js', '.mjs', '.cjs', '.jsx', '.ts', '.tsx', '.mts', '.cts'],
|
|
39
|
+
line: ['//'], block: [['/*', '*/']], nestBlock: false,
|
|
40
|
+
quotes: ['"', "'", '`'] },
|
|
41
|
+
py: { exts: ['.py', '.pyi'], line: ['#'], block: [], nestBlock: false,
|
|
42
|
+
quotes: ['"', "'"], pyTriple: true },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function langForFile(file) {
|
|
46
|
+
const ext = path.extname(file).toLowerCase();
|
|
47
|
+
for (const [name, cfg] of Object.entries(LANGS)) if (cfg.exts.includes(ext)) return name;
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const isIdent = (ch) => ch !== undefined && (
|
|
52
|
+
(ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
|
|
53
|
+
(ch >= '0' && ch <= '9') || ch === '_');
|
|
54
|
+
|
|
55
|
+
// ── lexer: return { masked, strings } ────────────────────────────────────────
|
|
56
|
+
// `masked` is `src` with every comment and string body replaced by spaces
|
|
57
|
+
// (newlines preserved, so offsets and line numbers are unchanged). `strings` is
|
|
58
|
+
// the list of decoded-ish string-literal bodies with their start line — used to
|
|
59
|
+
// see import specifiers (which the mask would otherwise blank out).
|
|
60
|
+
function maskSource(src, lang) {
|
|
61
|
+
const cfg = LANGS[lang];
|
|
62
|
+
const out = new Array(src.length);
|
|
63
|
+
for (let i = 0; i < src.length; i++) out[i] = src[i] === '\n' ? '\n' : ' ';
|
|
64
|
+
const strings = [];
|
|
65
|
+
let i = 0;
|
|
66
|
+
const n = src.length;
|
|
67
|
+
let line = 1;
|
|
68
|
+
const lineAt = []; // offset -> line (built lazily below is overkill; track inline)
|
|
69
|
+
const bump = (from, to) => { for (let k = from; k < to; k++) if (src[k] === '\n') line++; };
|
|
70
|
+
|
|
71
|
+
const starts = (tok, at) => src.startsWith(tok, at);
|
|
72
|
+
|
|
73
|
+
while (i < n) {
|
|
74
|
+
const ch = src[i];
|
|
75
|
+
|
|
76
|
+
// line comments
|
|
77
|
+
let matchedLine = null;
|
|
78
|
+
for (const lc of cfg.line) if (starts(lc, i)) { matchedLine = lc; break; }
|
|
79
|
+
if (matchedLine) {
|
|
80
|
+
while (i < n && src[i] !== '\n') i++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// block comments (with optional nesting for rust)
|
|
85
|
+
let matchedBlock = null;
|
|
86
|
+
for (const [open, close] of cfg.block) if (starts(open, i)) { matchedBlock = [open, close]; break; }
|
|
87
|
+
if (matchedBlock) {
|
|
88
|
+
const [open, close] = matchedBlock;
|
|
89
|
+
let depth = 1;
|
|
90
|
+
bump(i, i + open.length); i += open.length;
|
|
91
|
+
while (i < n && depth > 0) {
|
|
92
|
+
if (cfg.nestBlock && starts(open, i)) { depth++; bump(i, i + open.length); i += open.length; continue; }
|
|
93
|
+
if (starts(close, i)) { depth--; bump(i, i + close.length); i += close.length; continue; }
|
|
94
|
+
if (src[i] === '\n') line++;
|
|
95
|
+
i++;
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// python triple-quoted strings
|
|
101
|
+
if (cfg.pyTriple && (starts('"""', i) || starts("'''", i))) {
|
|
102
|
+
const q = src.substr(i, 3);
|
|
103
|
+
const startLine = line;
|
|
104
|
+
bump(i, i + 3); i += 3;
|
|
105
|
+
let body = '';
|
|
106
|
+
while (i < n && !starts(q, i)) { if (src[i] === '\n') line++; body += src[i]; i++; }
|
|
107
|
+
bump(i, i + 3); i += 3;
|
|
108
|
+
strings.push({ value: body, line: startLine });
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// rust raw strings: r"...", r#"..."#, r##"..."##
|
|
113
|
+
if (cfg.rustRaw && ch === 'r' && !isIdent(src[i - 1])) {
|
|
114
|
+
let j = i + 1, hashes = 0;
|
|
115
|
+
while (src[j] === '#') { hashes++; j++; }
|
|
116
|
+
if (src[j] === '"') {
|
|
117
|
+
const startLine = line;
|
|
118
|
+
const closeTok = '"' + '#'.repeat(hashes);
|
|
119
|
+
bump(i, j + 1); i = j + 1;
|
|
120
|
+
let body = '';
|
|
121
|
+
while (i < n && !starts(closeTok, i)) { if (src[i] === '\n') line++; body += src[i]; i++; }
|
|
122
|
+
bump(i, i + closeTok.length); i += closeTok.length;
|
|
123
|
+
strings.push({ value: body, line: startLine });
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ordinary quoted strings (with backslash escapes)
|
|
129
|
+
if (cfg.quotes.includes(ch)) {
|
|
130
|
+
const q = ch;
|
|
131
|
+
const startLine = line;
|
|
132
|
+
i++; // opening quote
|
|
133
|
+
let body = '';
|
|
134
|
+
while (i < n && src[i] !== q) {
|
|
135
|
+
if (src[i] === '\\') { if (src[i + 1] === '\n') line++; i += 2; body += ' '; continue; }
|
|
136
|
+
if (src[i] === '\n') { line++; if (q === '`') { body += '\n'; i++; continue; } else break; }
|
|
137
|
+
body += src[i]; i++;
|
|
138
|
+
}
|
|
139
|
+
if (src[i] === q) i++; // closing quote
|
|
140
|
+
strings.push({ value: body, line: startLine });
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (ch === '\n') line++;
|
|
145
|
+
out[i] = ch; // keep code char
|
|
146
|
+
i++;
|
|
147
|
+
}
|
|
148
|
+
return { masked: out.join(''), strings };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── offset → line map ────────────────────────────────────────────────────────
|
|
152
|
+
function lineMapper(src) {
|
|
153
|
+
const starts = [0];
|
|
154
|
+
for (let i = 0; i < src.length; i++) if (src[i] === '\n') starts.push(i + 1);
|
|
155
|
+
return (off) => {
|
|
156
|
+
// binary search
|
|
157
|
+
let lo = 0, hi = starts.length - 1;
|
|
158
|
+
while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (starts[mid] <= off) lo = mid; else hi = mid - 1; }
|
|
159
|
+
return lo + 1;
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Find call sites of `pattern` (a literal token sequence like "Db::open",
|
|
164
|
+
// ".open", "new NedbCore") in masked code: the sequence must appear with a
|
|
165
|
+
// non-identifier left boundary (unless it starts with a non-ident char) and be
|
|
166
|
+
// followed — after optional whitespace — by "(". Returns 1-based line numbers.
|
|
167
|
+
function findCallSites(masked, pattern, toLine) {
|
|
168
|
+
const lines = [];
|
|
169
|
+
const first = pattern[0];
|
|
170
|
+
const needLeftBoundary = isIdent(first);
|
|
171
|
+
let from = 0;
|
|
172
|
+
for (;;) {
|
|
173
|
+
const idx = masked.indexOf(pattern, from);
|
|
174
|
+
if (idx === -1) break;
|
|
175
|
+
from = idx + pattern.length;
|
|
176
|
+
if (needLeftBoundary && isIdent(masked[idx - 1])) continue;
|
|
177
|
+
// right boundary of the final identifier char, then optional ws, then "("
|
|
178
|
+
let k = idx + pattern.length;
|
|
179
|
+
const lastCharIsIdent = isIdent(pattern[pattern.length - 1]);
|
|
180
|
+
if (lastCharIsIdent && isIdent(masked[k])) continue; // e.g. "opener(" when seeking ".open"
|
|
181
|
+
while (k < masked.length && (masked[k] === ' ' || masked[k] === '\t' || masked[k] === '\n' || masked[k] === '\r')) k++;
|
|
182
|
+
if (masked[k] === '(') lines.push(toLine(idx));
|
|
183
|
+
}
|
|
184
|
+
return lines;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Presence of a bare identifier token (full-word) anywhere in masked code.
|
|
188
|
+
function hasIdent(masked, name) {
|
|
189
|
+
let from = 0;
|
|
190
|
+
for (;;) {
|
|
191
|
+
const idx = masked.indexOf(name, from);
|
|
192
|
+
if (idx === -1) return false;
|
|
193
|
+
from = idx + name.length;
|
|
194
|
+
if (!isIdent(masked[idx - 1]) && !isIdent(masked[idx + name.length])) return true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Does `NEDB(` (python pure engine) carry an argument (→ durable path) rather
|
|
199
|
+
// than being empty `NEDB()` (→ in-memory)? Checked on the RAW source, one char
|
|
200
|
+
// past the paren, so a blanked string arg still counts.
|
|
201
|
+
function pyNedbHasArg(src, masked, toLine) {
|
|
202
|
+
const lines = [];
|
|
203
|
+
let from = 0;
|
|
204
|
+
for (;;) {
|
|
205
|
+
const idx = masked.indexOf('NEDB', from);
|
|
206
|
+
if (idx === -1) break;
|
|
207
|
+
from = idx + 4;
|
|
208
|
+
if (isIdent(masked[idx - 1]) || isIdent(masked[idx + 4])) continue;
|
|
209
|
+
let k = idx + 4;
|
|
210
|
+
while (masked[k] === ' ' || masked[k] === '\t') k++;
|
|
211
|
+
if (masked[k] !== '(') continue;
|
|
212
|
+
k++;
|
|
213
|
+
while (k < src.length && (src[k] === ' ' || src[k] === '\t' || src[k] === '\n' || src[k] === '\r')) k++;
|
|
214
|
+
if (src[k] !== ')') lines.push(toLine(idx)); // has an argument
|
|
215
|
+
}
|
|
216
|
+
return lines;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── the fixes the inspector teaches ──────────────────────────────────────────
|
|
220
|
+
const FIX = {
|
|
221
|
+
rust: [
|
|
222
|
+
'use std::sync::Arc;',
|
|
223
|
+
'use nedb_engine::Db;',
|
|
224
|
+
'',
|
|
225
|
+
'let db = Arc::new(Db::open(path, None)?);',
|
|
226
|
+
'Db::install_exit_flush(Arc::clone(&db)); // flush buffered writes on SIGINT/SIGTERM',
|
|
227
|
+
].join('\n'),
|
|
228
|
+
js: [
|
|
229
|
+
"// Import the package entry — it arms process.on(SIGINT/SIGTERM) -> flush for you:",
|
|
230
|
+
"import { NedbCore } from 'nedb-engine';",
|
|
231
|
+
'const db = NedbCore.open(path);',
|
|
232
|
+
'',
|
|
233
|
+
'// If you must use the raw native binding, wire the exit flush yourself:',
|
|
234
|
+
"process.on('SIGTERM', () => { db.flush(); process.exit(143); });",
|
|
235
|
+
"process.on('SIGINT', () => { db.flush(); process.exit(130); });",
|
|
236
|
+
"process.on('exit', () => db.flush());",
|
|
237
|
+
].join('\n'),
|
|
238
|
+
py: [
|
|
239
|
+
'# nedb._native.NedbCore.open() arms a Python atexit flush for you (nedb >= 2.5.3).',
|
|
240
|
+
'# But os._exit() SKIPS atexit — flush explicitly before it:',
|
|
241
|
+
'db = NedbCore.open(path)',
|
|
242
|
+
'db.flush()',
|
|
243
|
+
'os._exit(0)',
|
|
244
|
+
].join('\n'),
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// ── analysis ─────────────────────────────────────────────────────────────────
|
|
248
|
+
function analyze(src, lang) {
|
|
249
|
+
const { masked, strings } = maskSource(src, lang);
|
|
250
|
+
const toLine = lineMapper(src);
|
|
251
|
+
const findings = [], infos = [], warnings = [];
|
|
252
|
+
const strvals = strings.map((s) => s.value);
|
|
253
|
+
|
|
254
|
+
if (lang === 'rust') {
|
|
255
|
+
const opens = findCallSites(masked, 'Db::open', toLine);
|
|
256
|
+
const mem = findCallSites(masked, 'Db::in_memory', toLine);
|
|
257
|
+
const wired = hasIdent(masked, 'install_exit_flush');
|
|
258
|
+
if (opens.length) {
|
|
259
|
+
findings.push(`durable Db::open() at line ${opens.join(', ')}`);
|
|
260
|
+
if (wired) infos.push('install_exit_flush(...) present — flush-on-exit wired');
|
|
261
|
+
else warnings.push({
|
|
262
|
+
code: 'RUST_NO_EXIT_FLUSH',
|
|
263
|
+
lines: opens,
|
|
264
|
+
msg: 'durable Db::open() with NO install_exit_flush — writes staged since the last flush are LOST on SIGINT/SIGTERM (Drop does not run on a signalled exit).',
|
|
265
|
+
fix: FIX.rust,
|
|
266
|
+
});
|
|
267
|
+
} else if (mem.length) {
|
|
268
|
+
infos.push('only Db::in_memory() — ephemeral, nothing to flush');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
else if (lang === 'js') {
|
|
273
|
+
const importsPkg = strvals.includes('nedb-engine');
|
|
274
|
+
// Import-specifier shapes only — NOT prose that merely contains "native".
|
|
275
|
+
const importsNative = strvals.some((v) =>
|
|
276
|
+
v.endsWith('.node') || v === './native' || v === './native.js' || v.endsWith('/native.js'));
|
|
277
|
+
const durable = findCallSites(masked, 'NedbCore.open', toLine);
|
|
278
|
+
const mem = findCallSites(masked, 'new NedbCore', toLine);
|
|
279
|
+
const manualWired = findCallSites(masked, 'process.on', toLine).length > 0 && hasIdent(masked, 'flush');
|
|
280
|
+
const optOut = hasIdent(masked, 'NEDB_NO_EXIT_FLUSH');
|
|
281
|
+
if (durable.length) {
|
|
282
|
+
findings.push(`durable NedbCore.open() at line ${durable.join(', ')}`);
|
|
283
|
+
if (importsPkg && !importsNative) infos.push("imports 'nedb-engine' — durable open auto-arms flush-on-exit");
|
|
284
|
+
else if (manualWired || optOut) infos.push('manual process.on(...) flush wiring detected');
|
|
285
|
+
else warnings.push({
|
|
286
|
+
code: 'JS_NO_EXIT_FLUSH',
|
|
287
|
+
lines: durable,
|
|
288
|
+
msg: 'durable open via the raw native binding with NO flush-on-exit wiring. Import from \'nedb-engine\' (its wrapper arms process.on -> flush) or wire it yourself.',
|
|
289
|
+
fix: FIX.js,
|
|
290
|
+
});
|
|
291
|
+
} else if (mem.length) {
|
|
292
|
+
infos.push('only new NedbCore() — in-memory, nothing to flush');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
else if (lang === 'py') {
|
|
297
|
+
const nativeOpens = findCallSites(masked, 'NedbCore.open', toLine);
|
|
298
|
+
const pureDurable = pyNedbHasArg(src, masked, toLine);
|
|
299
|
+
const usesUnderExit = hasIdent(masked, 'os') && masked.includes('_exit');
|
|
300
|
+
const atexitWired = hasIdent(masked, 'atexit') || (hasIdent(masked, 'signal') && hasIdent(masked, 'flush'));
|
|
301
|
+
if (nativeOpens.length) {
|
|
302
|
+
findings.push(`native NedbCore.open() at line ${nativeOpens.join(', ')}`);
|
|
303
|
+
infos.push('native open arms a Python atexit flush (nedb >= 2.5.3)');
|
|
304
|
+
if (usesUnderExit && !atexitWired) warnings.push({
|
|
305
|
+
code: 'PY_OS_EXIT_BYPASS',
|
|
306
|
+
lines: nativeOpens,
|
|
307
|
+
msg: 'os._exit() bypasses atexit — the native auto-flush will NOT run. Call db.flush() explicitly before os._exit().',
|
|
308
|
+
fix: FIX.py,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
if (pureDurable.length) {
|
|
312
|
+
findings.push(`pure-Python NEDB(path) at line ${pureDurable.join(', ')}`);
|
|
313
|
+
infos.push('pure-Python NEDB(path=...) is per-op fsync durable — no exit flush needed');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return { language: lang, findings, infos, warnings, ok: warnings.length === 0 };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Public, testable entry: analyze source text.
|
|
321
|
+
export function inspect(source, filename) {
|
|
322
|
+
const lang = langForFile(filename);
|
|
323
|
+
if (!lang) return { language: null, findings: [], infos: [], warnings: [], ok: true, skipped: true };
|
|
324
|
+
return analyze(source, filename ? lang : lang);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ── report ───────────────────────────────────────────────────────────────────
|
|
328
|
+
function report(file, res) {
|
|
329
|
+
if (res.skipped) { console.log(`${dim('skip')} ${file} ${dim('(unsupported extension)')}`); return; }
|
|
330
|
+
const tag = res.ok ? green(' OK ') : red(' WARN');
|
|
331
|
+
console.log(`\n${tag} ${bold(file)} ${dim(`[${res.language}]`)}`);
|
|
332
|
+
for (const f of res.findings) console.log(` ${cyan('•')} ${f}`);
|
|
333
|
+
for (const inf of res.infos) console.log(` ${dim('· ' + inf)}`);
|
|
334
|
+
for (const w of res.warnings) {
|
|
335
|
+
console.log('');
|
|
336
|
+
console.log(red(bold(' ┌─ NEDB DURABILITY WARNING ─────────────────────────────────')));
|
|
337
|
+
console.log(red(bold(` │ ${w.code}`)) + dim(` (line ${w.lines.join(', ')})`));
|
|
338
|
+
console.log(` ${red('│')} ${yellow(w.msg)}`);
|
|
339
|
+
console.log(red(' │'));
|
|
340
|
+
console.log(` ${red('│')} ${bold('Use this pattern:')}`);
|
|
341
|
+
for (const ln of w.fix.split('\n')) console.log(` ${red('│')} ${green(ln)}`);
|
|
342
|
+
console.log(red(bold(' └────────────────────────────────────────────────────────────')));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function usage(code) {
|
|
347
|
+
console.log(`${bold('nedb-inspector')} — deterministic NEDB durability checker
|
|
348
|
+
|
|
349
|
+
${cyan('nedb-inspector.mjs')} <path.(rs|js|mjs|ts|py)> [more paths...]
|
|
350
|
+
|
|
351
|
+
Warns when a durable NEDB database is opened without flush-on-exit wiring.
|
|
352
|
+
Exit: 0 clean · 1 warnings · 2 usage/read error. NO_COLOR=1 for plain output.`);
|
|
353
|
+
process.exit(code);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function main(argv) {
|
|
357
|
+
const files = argv.filter((a) => !a.startsWith('-'));
|
|
358
|
+
if (argv.includes('-h') || argv.includes('--help') || files.length === 0) usage(files.length === 0 ? 2 : 0);
|
|
359
|
+
console.log(bold(magenta('\nnedb-inspector')) + dim(' · durable-mode flush-on-exit guardrail'));
|
|
360
|
+
let warned = 0, read = 0;
|
|
361
|
+
for (const file of files) {
|
|
362
|
+
let src;
|
|
363
|
+
try { src = readFileSync(file, 'utf8'); }
|
|
364
|
+
catch (e) { console.log(`${red('ERR ')} ${file} ${dim('(' + (e.code || e.message) + ')')}`); process.exitCode = 2; continue; }
|
|
365
|
+
read++;
|
|
366
|
+
const res = inspect(src, file);
|
|
367
|
+
report(file, res);
|
|
368
|
+
if (!res.ok) warned += res.warnings.length;
|
|
369
|
+
}
|
|
370
|
+
console.log('');
|
|
371
|
+
if (warned > 0) { console.log(red(bold(`✗ ${warned} warning(s) across ${read} file(s)`))); process.exit(1); }
|
|
372
|
+
else { console.log(green(bold(`✓ clean — ${read} file(s) inspected`))); process.exit(process.exitCode === 2 ? 2 : 0); }
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Run as CLI when invoked directly (not when imported by the test suite).
|
|
376
|
+
const invokedDirectly = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
377
|
+
if (invokedDirectly) main(process.argv.slice(2));
|
package/nedb.darwin-arm64.node
CHANGED
|
Binary file
|
package/nedb.darwin-x64.node
CHANGED
|
Binary file
|
package/nedb.linux-x64-gnu.node
CHANGED
|
Binary file
|
package/nedb.win32-x64-msvc.node
CHANGED
|
Binary file
|
package/nedbd-v2-darwin-arm64
CHANGED
|
Binary file
|
package/nedbd-v2-darwin-x64
CHANGED
|
Binary file
|
package/nedbd-v2-linux-x64
CHANGED
|
Binary file
|
package/nedbd-v2-win-x64.exe
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nedb-engine",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.34",
|
|
4
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
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"bin": {
|
|
8
8
|
"nedbd-v2": "./nedbd-v2.js",
|
|
9
|
-
"nedbdv2": "./nedbd-v2.js"
|
|
9
|
+
"nedbdv2": "./nedbd-v2.js",
|
|
10
|
+
"nedb-inspector": "./nedb-inspector.mjs"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
12
13
|
"index.js",
|
|
13
14
|
"index.d.ts",
|
|
15
|
+
"native.js",
|
|
16
|
+
"native.d.ts",
|
|
14
17
|
"nedbd-v2.js",
|
|
18
|
+
"nedb-inspector.mjs",
|
|
15
19
|
"*.node",
|
|
16
20
|
"nedbd-v2*",
|
|
17
21
|
"test/smoke.mjs",
|
|
@@ -53,8 +57,8 @@
|
|
|
53
57
|
}
|
|
54
58
|
},
|
|
55
59
|
"scripts": {
|
|
56
|
-
"build": "napi build --release --platform --cargo-cwd rust/crates/nedb-node",
|
|
57
|
-
"build:debug": "napi build --platform --cargo-cwd rust/crates/nedb-node",
|
|
60
|
+
"build": "napi build --release --platform --cargo-cwd rust/crates/nedb-node --js native.js --dts native.d.ts",
|
|
61
|
+
"build:debug": "napi build --platform --cargo-cwd rust/crates/nedb-node --js native.js --dts native.d.ts",
|
|
58
62
|
"test": "node test/smoke.mjs"
|
|
59
63
|
},
|
|
60
64
|
"devDependencies": {
|