@libredb/libredb 0.0.1
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 +21 -0
- package/README.md +259 -0
- package/dist/adapter/store.d.ts +32 -0
- package/dist/adapter/store.d.ts.map +1 -0
- package/dist/adapter/store.js +2 -0
- package/dist/adapter/store.js.map +1 -0
- package/dist/core.d.ts +111 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +343 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/lens/document.d.ts +104 -0
- package/dist/lens/document.d.ts.map +1 -0
- package/dist/lens/document.js +152 -0
- package/dist/lens/document.js.map +1 -0
- package/dist/lens/kv.d.ts +59 -0
- package/dist/lens/kv.d.ts.map +1 -0
- package/dist/lens/kv.js +87 -0
- package/dist/lens/kv.js.map +1 -0
- package/dist/lens/relational.d.ts +119 -0
- package/dist/lens/relational.d.ts.map +1 -0
- package/dist/lens/relational.js +213 -0
- package/dist/lens/relational.js.map +1 -0
- package/dist/lens/types.d.ts +58 -0
- package/dist/lens/types.d.ts.map +1 -0
- package/dist/lens/types.js +35 -0
- package/dist/lens/types.js.map +1 -0
- package/dist/query/range.d.ts +44 -0
- package/dist/query/range.d.ts.map +1 -0
- package/dist/query/range.js +60 -0
- package/dist/query/range.js.map +1 -0
- package/package.json +39 -0
package/dist/core.js
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core.ts — the LibreDB kernel.
|
|
3
|
+
*
|
|
4
|
+
* This file is the trust boundary, the comprehension boundary, and the
|
|
5
|
+
* architecture boundary all at once (see DESIGN.md section 5). Everything that
|
|
6
|
+
* touches durability lives here and nowhere else:
|
|
7
|
+
*
|
|
8
|
+
* - storage an ordered key-value substrate
|
|
9
|
+
* - transactions atomic apply with the isolation scoped in DESIGN.md
|
|
10
|
+
* - recovery survive a crash and reopen with consistent state
|
|
11
|
+
*
|
|
12
|
+
* The kernel is small because it is genuinely minimal, never because
|
|
13
|
+
* complexity was swept into sibling files. Model lenses (lens/kv.ts,
|
|
14
|
+
* lens/document.ts, lens/relational.ts) sit on top of this; they never
|
|
15
|
+
* reach around it.
|
|
16
|
+
*
|
|
17
|
+
* The boundaries are declared first as types — the contract every lens builds
|
|
18
|
+
* on — followed by the implementation: an ordered key-value store, serializable
|
|
19
|
+
* transactions, and a write-ahead log for durability. Recovery replays that log
|
|
20
|
+
* and discards any record a crash left half-written.
|
|
21
|
+
*/
|
|
22
|
+
import { closeSync, existsSync, fsyncSync, openSync, readFileSync, truncateSync, writeSync, } from "node:fs";
|
|
23
|
+
/** The LibreDB package version. Kept in sync with package.json. */
|
|
24
|
+
export const version = "0.0.1";
|
|
25
|
+
/**
|
|
26
|
+
* Compare two keys by unsigned byte-lexicographic order: the first differing
|
|
27
|
+
* byte decides, and if one key is a prefix of the other the shorter sorts
|
|
28
|
+
* first. Returns <0, 0, or >0 like every comparator.
|
|
29
|
+
*
|
|
30
|
+
* Bytes are unsigned (0..255) because they come from a Uint8Array, so this is
|
|
31
|
+
* the byte order a sorted file would use — not JavaScript's UTF-16 string
|
|
32
|
+
* order, where "10" would sort before "2".
|
|
33
|
+
*/
|
|
34
|
+
function compareKeys(a, b) {
|
|
35
|
+
const shared = Math.min(a.length, b.length);
|
|
36
|
+
for (let i = 0; i < shared; i++) {
|
|
37
|
+
const delta = a[i] - b[i];
|
|
38
|
+
if (delta !== 0)
|
|
39
|
+
return delta;
|
|
40
|
+
}
|
|
41
|
+
return a.length - b.length;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Binary-search a sorted entry array for `key`. Returns whether it is present
|
|
45
|
+
* and the index: the entry's own index if found, otherwise the index at which
|
|
46
|
+
* inserting would keep the array sorted.
|
|
47
|
+
*/
|
|
48
|
+
function locate(entries, key) {
|
|
49
|
+
let low = 0;
|
|
50
|
+
let high = entries.length;
|
|
51
|
+
while (low < high) {
|
|
52
|
+
const mid = (low + high) >>> 1;
|
|
53
|
+
const order = compareKeys(entries[mid].key, key);
|
|
54
|
+
if (order === 0)
|
|
55
|
+
return { found: true, index: mid };
|
|
56
|
+
if (order < 0)
|
|
57
|
+
low = mid + 1;
|
|
58
|
+
else
|
|
59
|
+
high = mid;
|
|
60
|
+
}
|
|
61
|
+
return { found: false, index: low };
|
|
62
|
+
}
|
|
63
|
+
/** Set `key` to `value` in a sorted entry array, keeping it sorted. Shared by
|
|
64
|
+
* live transaction writes and by log replay during recovery, so "what a set
|
|
65
|
+
* means" has exactly one definition. */
|
|
66
|
+
function applySet(entries, key, value) {
|
|
67
|
+
const { found, index } = locate(entries, key);
|
|
68
|
+
if (found)
|
|
69
|
+
entries[index] = { key, value };
|
|
70
|
+
else
|
|
71
|
+
entries.splice(index, 0, { key, value });
|
|
72
|
+
}
|
|
73
|
+
/** Remove `key` from a sorted entry array. A no-op if it is absent. Shared by
|
|
74
|
+
* live writes and log replay (see {@link applySet}). */
|
|
75
|
+
function applyDelete(entries, key) {
|
|
76
|
+
const { found, index } = locate(entries, key);
|
|
77
|
+
if (found)
|
|
78
|
+
entries.splice(index, 1);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* A transaction backed by `working`, a mutable snapshot of the committed store.
|
|
82
|
+
* Reads and writes hit the snapshot directly, which is what gives
|
|
83
|
+
* read-your-writes; the caller commits or discards the snapshot as a whole.
|
|
84
|
+
* Every mutation is also appended to `journal`, the redo record a durable
|
|
85
|
+
* database writes to its log on commit (and ignores when purely in-memory).
|
|
86
|
+
*/
|
|
87
|
+
function makeTransaction(working, journal) {
|
|
88
|
+
return {
|
|
89
|
+
get(key) {
|
|
90
|
+
const { found, index } = locate(working, key);
|
|
91
|
+
return found ? working[index].value : undefined;
|
|
92
|
+
},
|
|
93
|
+
set(key, value) {
|
|
94
|
+
applySet(working, key, value);
|
|
95
|
+
journal.push({ kind: "set", key, value });
|
|
96
|
+
},
|
|
97
|
+
delete(key) {
|
|
98
|
+
applyDelete(working, key);
|
|
99
|
+
journal.push({ kind: "delete", key });
|
|
100
|
+
},
|
|
101
|
+
*getRange(start, end) {
|
|
102
|
+
// locate() returns the first index whose key is >= start (the insertion
|
|
103
|
+
// point), so the scan is naturally inclusive of start. It stops at the
|
|
104
|
+
// first key that is not < end, making the range half-open [start, end).
|
|
105
|
+
for (let i = locate(working, start).index; i < working.length; i++) {
|
|
106
|
+
const entry = working[i];
|
|
107
|
+
if (compareKeys(entry.key, end) >= 0)
|
|
108
|
+
break;
|
|
109
|
+
yield entry;
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Durability: a write-ahead log
|
|
116
|
+
//
|
|
117
|
+
// Without a path the kernel is purely in-memory. With one, durability is a
|
|
118
|
+
// write-ahead log (a redo log): every committed transaction is appended to the
|
|
119
|
+
// file as a single record and fsync'd BEFORE the commit becomes visible, so a
|
|
120
|
+
// returned transact() is durable. Recovery replays the records in order to
|
|
121
|
+
// rebuild the store. This is the same mechanism real databases use, written
|
|
122
|
+
// plainly so the file still teaches how durability works.
|
|
123
|
+
//
|
|
124
|
+
// On-disk record = an 8-byte header followed by a payload:
|
|
125
|
+
// [u32 payloadLength][u32 crc32(payload)][payload]
|
|
126
|
+
// The payload is the transaction's mutations back to back. Each op is:
|
|
127
|
+
// set: [u8 1][u32 keyLength][key][u32 valueLength][value]
|
|
128
|
+
// delete: [u8 0][u32 keyLength][key]
|
|
129
|
+
// Integers are big-endian. Because the log is append-only and fsync'd, a crash
|
|
130
|
+
// can only ever damage the LAST record, so recovery trusts every record up to
|
|
131
|
+
// the first one that is incomplete or fails its checksum, and truncates the
|
|
132
|
+
// rest away.
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
const OP_SET = 1;
|
|
135
|
+
const OP_DELETE = 0;
|
|
136
|
+
/** Bytes in a record header: the payload length and its checksum, both u32. */
|
|
137
|
+
const RECORD_HEADER = 8;
|
|
138
|
+
/** Write `value` as a big-endian u32 at `offset`. */
|
|
139
|
+
function writeU32(out, offset, value) {
|
|
140
|
+
out[offset] = (value >>> 24) & 0xff;
|
|
141
|
+
out[offset + 1] = (value >>> 16) & 0xff;
|
|
142
|
+
out[offset + 2] = (value >>> 8) & 0xff;
|
|
143
|
+
out[offset + 3] = value & 0xff;
|
|
144
|
+
}
|
|
145
|
+
/** Read a big-endian u32 from `offset`. The top byte is multiplied, not
|
|
146
|
+
* shifted: `<< 24` would land on the sign bit and yield a negative int. */
|
|
147
|
+
function readU32(buffer, offset) {
|
|
148
|
+
return ((buffer[offset] * 0x1000000 +
|
|
149
|
+
(buffer[offset + 1] << 16) +
|
|
150
|
+
(buffer[offset + 2] << 8) +
|
|
151
|
+
buffer[offset + 3]) >>>
|
|
152
|
+
0);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* CRC-32 (IEEE 802.3 polynomial), computed without a lookup table to keep the
|
|
156
|
+
* mechanism in plain sight. It lets recovery tell a fully-written record from
|
|
157
|
+
* one a crash left half-flushed.
|
|
158
|
+
*/
|
|
159
|
+
function crc32(data) {
|
|
160
|
+
let crc = 0xffffffff;
|
|
161
|
+
for (let i = 0; i < data.length; i++) {
|
|
162
|
+
crc ^= data[i];
|
|
163
|
+
for (let bit = 0; bit < 8; bit++) {
|
|
164
|
+
crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
168
|
+
}
|
|
169
|
+
/** Encode one committed transaction's ops as a length-framed, checksummed
|
|
170
|
+
* record ready to append to the log. */
|
|
171
|
+
function encodeRecord(ops) {
|
|
172
|
+
let size = 0;
|
|
173
|
+
for (const op of ops) {
|
|
174
|
+
size += 1 + 4 + op.key.length;
|
|
175
|
+
if (op.kind === "set")
|
|
176
|
+
size += 4 + op.value.length;
|
|
177
|
+
}
|
|
178
|
+
const payload = new Uint8Array(size);
|
|
179
|
+
let off = 0;
|
|
180
|
+
for (const op of ops) {
|
|
181
|
+
payload[off++] = op.kind === "set" ? OP_SET : OP_DELETE;
|
|
182
|
+
writeU32(payload, off, op.key.length);
|
|
183
|
+
off += 4;
|
|
184
|
+
payload.set(op.key, off);
|
|
185
|
+
off += op.key.length;
|
|
186
|
+
if (op.kind === "set") {
|
|
187
|
+
writeU32(payload, off, op.value.length);
|
|
188
|
+
off += 4;
|
|
189
|
+
payload.set(op.value, off);
|
|
190
|
+
off += op.value.length;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const record = new Uint8Array(RECORD_HEADER + size);
|
|
194
|
+
writeU32(record, 0, size);
|
|
195
|
+
writeU32(record, 4, crc32(payload));
|
|
196
|
+
record.set(payload, RECORD_HEADER);
|
|
197
|
+
return record;
|
|
198
|
+
}
|
|
199
|
+
/** Replay one record's payload onto `entries`, in order, reconstructing the
|
|
200
|
+
* committed state the transaction produced. */
|
|
201
|
+
function replayPayload(entries, payload) {
|
|
202
|
+
let off = 0;
|
|
203
|
+
while (off < payload.length) {
|
|
204
|
+
const tag = payload[off++];
|
|
205
|
+
const keyLength = readU32(payload, off);
|
|
206
|
+
off += 4;
|
|
207
|
+
const key = payload.slice(off, off + keyLength);
|
|
208
|
+
off += keyLength;
|
|
209
|
+
if (tag === OP_SET) {
|
|
210
|
+
const valueLength = readU32(payload, off);
|
|
211
|
+
off += 4;
|
|
212
|
+
const value = payload.slice(off, off + valueLength);
|
|
213
|
+
off += valueLength;
|
|
214
|
+
applySet(entries, key, value);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
applyDelete(entries, key);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Rebuild the committed store from the log at `path`, returning its entries.
|
|
223
|
+
* Replays every intact record and stops at the first record a crash left torn
|
|
224
|
+
* (header promises bytes that are not there) or corrupt (checksum mismatch),
|
|
225
|
+
* truncating that tail away so the next append starts from a clean boundary.
|
|
226
|
+
*/
|
|
227
|
+
function recover(path) {
|
|
228
|
+
const entries = [];
|
|
229
|
+
if (!existsSync(path))
|
|
230
|
+
return entries;
|
|
231
|
+
// Copy into a plain Uint8Array so slices are independent copies, not views
|
|
232
|
+
// aliasing a shared Buffer pool.
|
|
233
|
+
const log = new Uint8Array(readFileSync(path));
|
|
234
|
+
let offset = 0;
|
|
235
|
+
while (offset + RECORD_HEADER <= log.length) {
|
|
236
|
+
const size = readU32(log, offset);
|
|
237
|
+
const checksum = readU32(log, offset + 4);
|
|
238
|
+
const start = offset + RECORD_HEADER;
|
|
239
|
+
const end = start + size;
|
|
240
|
+
if (end > log.length)
|
|
241
|
+
break; // torn: fewer bytes than the header promised
|
|
242
|
+
const payload = log.subarray(start, end);
|
|
243
|
+
if (crc32(payload) !== checksum)
|
|
244
|
+
break; // corrupt: record damaged mid-write
|
|
245
|
+
replayPayload(entries, payload);
|
|
246
|
+
offset = end;
|
|
247
|
+
}
|
|
248
|
+
if (offset < log.length)
|
|
249
|
+
truncateSync(path, offset);
|
|
250
|
+
return entries;
|
|
251
|
+
}
|
|
252
|
+
/** Recover the store at `path` and return it together with a {@link Log} that
|
|
253
|
+
* appends future commits to the same file. */
|
|
254
|
+
function openLog(path) {
|
|
255
|
+
const entries = recover(path);
|
|
256
|
+
const fd = openSync(path, "a"); // append-only; creates the file if missing
|
|
257
|
+
return {
|
|
258
|
+
entries,
|
|
259
|
+
log: {
|
|
260
|
+
append(ops) {
|
|
261
|
+
const record = encodeRecord(ops);
|
|
262
|
+
for (let written = 0; written < record.length;) {
|
|
263
|
+
written += writeSync(fd, record, written);
|
|
264
|
+
}
|
|
265
|
+
fsyncSync(fd); // the durability point: bytes are on disk before we return
|
|
266
|
+
},
|
|
267
|
+
close() {
|
|
268
|
+
closeSync(fd);
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Open a kernel instance. See {@link Open} and {@link OpenOptions}.
|
|
275
|
+
*
|
|
276
|
+
* With a `path` the store is recovered from its write-ahead log and future
|
|
277
|
+
* commits are appended to it durably. Without one the store is purely
|
|
278
|
+
* in-memory.
|
|
279
|
+
*/
|
|
280
|
+
export const open = (options) => {
|
|
281
|
+
// The committed store. Each transaction works on a copy and replaces this
|
|
282
|
+
// reference on success, so a commit is one atomic assignment and an abort is
|
|
283
|
+
// simply never reaching that assignment. With a path, the initial contents
|
|
284
|
+
// are recovered from the log and `log` persists every later commit.
|
|
285
|
+
let committed;
|
|
286
|
+
let log;
|
|
287
|
+
if (options?.path !== undefined) {
|
|
288
|
+
const opened = openLog(options.path);
|
|
289
|
+
committed = opened.entries;
|
|
290
|
+
log = opened.log;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
committed = [];
|
|
294
|
+
log = null;
|
|
295
|
+
}
|
|
296
|
+
let closed = false;
|
|
297
|
+
// Guards against re-entrancy. The API is synchronous and single-threaded, so
|
|
298
|
+
// a transaction body runs to completion before the next one begins — making
|
|
299
|
+
// the schedule serial (hence serializable) by construction. The only way to
|
|
300
|
+
// overlap two transactions is to call transact() from inside another, which
|
|
301
|
+
// we forbid: the inner snapshot would miss the outer's pending writes and the
|
|
302
|
+
// outer's later commit would clobber the inner's, a silent lost update. With
|
|
303
|
+
// this guard the serializable guarantee is a fact, not an assumption.
|
|
304
|
+
let inTransaction = false;
|
|
305
|
+
return {
|
|
306
|
+
transact(run) {
|
|
307
|
+
if (closed)
|
|
308
|
+
throw new Error("libredb: database is closed");
|
|
309
|
+
if (inTransaction) {
|
|
310
|
+
throw new Error("libredb: nested transactions are not supported");
|
|
311
|
+
}
|
|
312
|
+
inTransaction = true;
|
|
313
|
+
try {
|
|
314
|
+
const working = committed.slice();
|
|
315
|
+
const journal = [];
|
|
316
|
+
const result = run(makeTransaction(working, journal));
|
|
317
|
+
// Reached only if run() did not throw. Make the commit DURABLE before
|
|
318
|
+
// exposing it in memory: if the append fails, we throw with memory and
|
|
319
|
+
// disk still agreeing on the prior state. A read-only transaction has
|
|
320
|
+
// nothing to persist, so it skips the log entirely. The in-memory
|
|
321
|
+
// commit is then one atomic reference swap.
|
|
322
|
+
if (log !== null && journal.length > 0)
|
|
323
|
+
log.append(journal);
|
|
324
|
+
committed = working;
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
finally {
|
|
328
|
+
// Always clear the guard, even on abort, so a thrown body does not wedge
|
|
329
|
+
// the database against all future transactions.
|
|
330
|
+
inTransaction = false;
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
close() {
|
|
334
|
+
if (closed)
|
|
335
|
+
return; // idempotent: never double-close the underlying file
|
|
336
|
+
closed = true;
|
|
337
|
+
if (log !== null)
|
|
338
|
+
log.close();
|
|
339
|
+
committed = [];
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
};
|
|
343
|
+
//# sourceMappingURL=core.js.map
|
package/dist/core.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* index.ts — the public entry point of the LibreDB npm package.
|
|
3
|
+
*
|
|
4
|
+
* The public surface is the kv lens (DESIGN.md section 6): you `open` a kernel
|
|
5
|
+
* database and put a lens over it — {@link kv} (strings) or {@link doc} (JSON
|
|
6
|
+
* documents). The kernel's byte-level internals (and each lens's private codec)
|
|
7
|
+
* stay unexported on purpose — the lens is the usable face. The relational lens
|
|
8
|
+
* ({@link table}) completes the trio.
|
|
9
|
+
*/
|
|
10
|
+
export { version, open } from "./core.ts";
|
|
11
|
+
export type { Database, OpenOptions } from "./core.ts";
|
|
12
|
+
export { kv } from "./lens/kv.ts";
|
|
13
|
+
export type { Kv, KvEntry } from "./lens/kv.ts";
|
|
14
|
+
export { doc } from "./lens/document.ts";
|
|
15
|
+
export type { DocCollection, Doc, DocEntry, JsonValue } from "./lens/document.ts";
|
|
16
|
+
export { table } from "./lens/relational.ts";
|
|
17
|
+
export type { Table, TableSchema, Row, ColumnType, Query } from "./lens/relational.ts";
|
|
18
|
+
export type { Result, WriteResult } from "./lens/types.ts";
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,YAAY,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzC,YAAY,EAAE,aAAa,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAElF,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC7C,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAEvF,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* index.ts — the public entry point of the LibreDB npm package.
|
|
3
|
+
*
|
|
4
|
+
* The public surface is the kv lens (DESIGN.md section 6): you `open` a kernel
|
|
5
|
+
* database and put a lens over it — {@link kv} (strings) or {@link doc} (JSON
|
|
6
|
+
* documents). The kernel's byte-level internals (and each lens's private codec)
|
|
7
|
+
* stay unexported on purpose — the lens is the usable face. The relational lens
|
|
8
|
+
* ({@link table}) completes the trio.
|
|
9
|
+
*/
|
|
10
|
+
export { version, open } from "./core.js";
|
|
11
|
+
export { kv } from "./lens/kv.js";
|
|
12
|
+
export { doc } from "./lens/document.js";
|
|
13
|
+
export { table } from "./lens/relational.js";
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAG1C,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAGlC,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAGzC,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lens/document.ts — the document lens, LibreDB's differentiator lens.
|
|
3
|
+
*
|
|
4
|
+
* Where kv (lens/kv.ts) is the *string* face over the byte kernel, document is
|
|
5
|
+
* the *JSON* face: a document is a JSON-serializable object stored as UTF-8 JSON
|
|
6
|
+
* bytes (the value) under a `<collection>:<id>` kernel key. The lens adds JSON
|
|
7
|
+
* ergonomics and nothing else — no storage, transactions, or recovery live here;
|
|
8
|
+
* those stay in core.ts (DESIGN.md section 6.1).
|
|
9
|
+
*
|
|
10
|
+
* The collection handle from {@link doc} adds by-id CRUD on top of that codec;
|
|
11
|
+
* collection scans (`all`) and field matching (`find`) build on it in the
|
|
12
|
+
* phases that follow.
|
|
13
|
+
*/
|
|
14
|
+
import { type Result, type WriteResult } from "./types.ts";
|
|
15
|
+
import type { Store } from "../adapter/store.ts";
|
|
16
|
+
/**
|
|
17
|
+
* Any value JSON can represent: the closure of the primitives under arrays and
|
|
18
|
+
* objects. This is the honest type of "what survives a JSON round-trip" —
|
|
19
|
+
* `undefined`, functions, and symbols are deliberately absent because JSON drops
|
|
20
|
+
* them.
|
|
21
|
+
*/
|
|
22
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
23
|
+
[key: string]: JsonValue;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* A document: a JSON object. Stored as one value in the kernel, keyed by its id
|
|
27
|
+
* within a collection. The top level is always an object (the unit a collection
|
|
28
|
+
* holds), while field values range over all of {@link JsonValue}.
|
|
29
|
+
*/
|
|
30
|
+
export type Doc = {
|
|
31
|
+
[key: string]: JsonValue;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* One document from a collection scan: its `id` paired with the decoded `doc`.
|
|
35
|
+
* This is the document-lens Row type — collection reads return
|
|
36
|
+
* `Result<DocEntry>` (lens/types.ts), the same envelope the kv lens uses.
|
|
37
|
+
*/
|
|
38
|
+
export interface DocEntry {
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly doc: Doc;
|
|
41
|
+
}
|
|
42
|
+
/** Serialize a document to UTF-8 JSON bytes — the form the kernel stores. */
|
|
43
|
+
export declare function encodeDoc(doc: Doc): Uint8Array;
|
|
44
|
+
/**
|
|
45
|
+
* Parse UTF-8 JSON bytes back into a document. The inverse of {@link encodeDoc}:
|
|
46
|
+
* bytes produced by it round-trip with full fidelity (numbers stay numbers,
|
|
47
|
+
* nested objects and unicode survive), which the document lens relies on for
|
|
48
|
+
* type-sensitive field matching.
|
|
49
|
+
*/
|
|
50
|
+
export declare function decodeDoc(bytes: Uint8Array): Doc;
|
|
51
|
+
/**
|
|
52
|
+
* Whether `document` satisfies `predicate`: every top-level field named in the
|
|
53
|
+
* predicate is present on the document and deeply equal to the predicate's value
|
|
54
|
+
* (DESIGN.md section 6.1). Multiple fields are an implicit AND. An empty
|
|
55
|
+
* predicate names no fields and so matches every document.
|
|
56
|
+
*
|
|
57
|
+
* Exported so the relational lens's `where` can reuse the exact same matching
|
|
58
|
+
* semantics over rows (a row is a Doc), instead of re-deriving a second matcher.
|
|
59
|
+
*/
|
|
60
|
+
export declare function matches(document: Doc, predicate: Doc): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* A handle on one collection: by-id CRUD over the kernel.
|
|
63
|
+
*
|
|
64
|
+
* Each operation auto-commits in its own kernel transaction, so the collection
|
|
65
|
+
* behaves like a durable map of documents — a write is atomic and, on a
|
|
66
|
+
* file-backed database, fsync'd before it returns. Multi-document atomicity is a
|
|
67
|
+
* conscious omission (DESIGN.md section 6.1): a caller that needs it drops to the
|
|
68
|
+
* kernel's `transact`. The handle is a view — it depends only on the
|
|
69
|
+
* {@link Store} seam and never touches the store's lifecycle.
|
|
70
|
+
*/
|
|
71
|
+
export interface DocCollection {
|
|
72
|
+
/** Store `document` under `id`, overwriting any existing document at that id.
|
|
73
|
+
* Always reports one changed entry. */
|
|
74
|
+
put(id: string, document: Doc): WriteResult;
|
|
75
|
+
/** The document stored at `id`, decoded, or `undefined` if none is set. */
|
|
76
|
+
get(id: string): Doc | undefined;
|
|
77
|
+
/** Remove the document at `id`. Reports one changed entry if it existed, zero
|
|
78
|
+
* if it did not. */
|
|
79
|
+
delete(id: string): WriteResult;
|
|
80
|
+
/** Every document in the collection, as {@link DocEntry} rows in ascending id
|
|
81
|
+
* order (the kernel's byte order over the `<collection>:<id>` keys). The
|
|
82
|
+
* {@link Result} is lazy and re-iterable: each pass re-runs the scan against
|
|
83
|
+
* the current state. A scan sees only this collection's documents — the
|
|
84
|
+
* `<collection>:` prefix is a byte boundary, so a sibling whose name shares a
|
|
85
|
+
* prefix (`users` vs `users2`) is never included. */
|
|
86
|
+
all(): Result<DocEntry>;
|
|
87
|
+
/** Every document whose top-level fields match `predicate` by deep
|
|
88
|
+
* (structural) equality; multiple fields are an implicit AND, and an empty
|
|
89
|
+
* predicate matches everything (so `find({})` equals `all()`). Returns the
|
|
90
|
+
* same lazy, re-iterable {@link Result} as `all`, scoped to this collection.
|
|
91
|
+
*
|
|
92
|
+
* Cost: O(n) in the collection size. `find` is a full collection scan with an
|
|
93
|
+
* in-engine predicate — there are no secondary indexes (DESIGN.md section 6.1,
|
|
94
|
+
* a deliberate v1 omission). Every document is decoded and tested on each
|
|
95
|
+
* pass. */
|
|
96
|
+
find(predicate: Doc): Result<DocEntry>;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Build a {@link DocCollection} handle scoped to `collection` over a
|
|
100
|
+
* {@link Store} (the kernel's `Database` satisfies it, as does any object that
|
|
101
|
+
* can run a transaction).
|
|
102
|
+
*/
|
|
103
|
+
export declare function doc(store: Store, collection: string): DocCollection;
|
|
104
|
+
//# sourceMappingURL=document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/lens/document.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAU,KAAK,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAEnE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,EAAE,GACX;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEjC;;;;GAIG;AACH,MAAM,MAAM,GAAG,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAE/C;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;CACnB;AAKD,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,UAAU,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAEhD;AAsCD;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,GAAG,OAAO,CAE9D;AAaD;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B;2CACuC;IACvC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,GAAG,WAAW,CAAC;IAC5C,2EAA2E;IAC3E,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,CAAC;IACjC;wBACoB;IACpB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,CAAC;IAChC;;;;;yDAKqD;IACrD,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC;IACxB;;;;;;;;eAQW;IACX,IAAI,CAAC,SAAS,EAAE,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;CACxC;AAED;;;;GAIG;AACH,wBAAgB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,GAAG,aAAa,CA2DnE"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lens/document.ts — the document lens, LibreDB's differentiator lens.
|
|
3
|
+
*
|
|
4
|
+
* Where kv (lens/kv.ts) is the *string* face over the byte kernel, document is
|
|
5
|
+
* the *JSON* face: a document is a JSON-serializable object stored as UTF-8 JSON
|
|
6
|
+
* bytes (the value) under a `<collection>:<id>` kernel key. The lens adds JSON
|
|
7
|
+
* ergonomics and nothing else — no storage, transactions, or recovery live here;
|
|
8
|
+
* those stay in core.ts (DESIGN.md section 6.1).
|
|
9
|
+
*
|
|
10
|
+
* The collection handle from {@link doc} adds by-id CRUD on top of that codec;
|
|
11
|
+
* collection scans (`all`) and field matching (`find`) build on it in the
|
|
12
|
+
* phases that follow.
|
|
13
|
+
*/
|
|
14
|
+
import { result } from "./types.js";
|
|
15
|
+
import { prefixRange } from "../query/range.js";
|
|
16
|
+
const utf8 = new TextEncoder();
|
|
17
|
+
const fromUtf8 = new TextDecoder();
|
|
18
|
+
/** Serialize a document to UTF-8 JSON bytes — the form the kernel stores. */
|
|
19
|
+
export function encodeDoc(doc) {
|
|
20
|
+
return utf8.encode(JSON.stringify(doc));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parse UTF-8 JSON bytes back into a document. The inverse of {@link encodeDoc}:
|
|
24
|
+
* bytes produced by it round-trip with full fidelity (numbers stay numbers,
|
|
25
|
+
* nested objects and unicode survive), which the document lens relies on for
|
|
26
|
+
* type-sensitive field matching.
|
|
27
|
+
*/
|
|
28
|
+
export function decodeDoc(bytes) {
|
|
29
|
+
return JSON.parse(fromUtf8.decode(bytes));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Deep structural equality over {@link JsonValue}. This is what document
|
|
33
|
+
* matching compares with, so it must be the JSON notion of "same value", not
|
|
34
|
+
* reference identity and not `JSON.stringify` (which is sensitive to object key
|
|
35
|
+
* order and so would call two equal objects unequal).
|
|
36
|
+
*
|
|
37
|
+
* - Primitives (string/number/boolean/null) compare by `===`, which is
|
|
38
|
+
* type-sensitive on purpose: `1` is not `"1"` and `true` is not `"true"`.
|
|
39
|
+
* - Arrays are equal element-wise, in order (order is part of an array's value).
|
|
40
|
+
* - Objects are equal when they have the same set of keys and every value is
|
|
41
|
+
* deeply equal; key *order* is irrelevant.
|
|
42
|
+
*
|
|
43
|
+
* The arguments are `JsonValue | undefined` so a missing field (`doc[key]` with
|
|
44
|
+
* no such key) is handled directly: it is `undefined`, which equals nothing a
|
|
45
|
+
* predicate can hold (predicate values are always defined {@link JsonValue}).
|
|
46
|
+
*/
|
|
47
|
+
function deepEqual(a, b) {
|
|
48
|
+
if (a === b)
|
|
49
|
+
return true; // same primitive, same reference, or both undefined
|
|
50
|
+
if (a === null || b === null)
|
|
51
|
+
return false; // one is null but not the other (=== caught both)
|
|
52
|
+
if (typeof a !== "object" || typeof b !== "object")
|
|
53
|
+
return false; // unequal primitives/undefined
|
|
54
|
+
const aIsArray = Array.isArray(a);
|
|
55
|
+
if (aIsArray !== Array.isArray(b))
|
|
56
|
+
return false; // an array is never equal to a plain object
|
|
57
|
+
if (aIsArray) {
|
|
58
|
+
const bArr = b;
|
|
59
|
+
if (a.length !== bArr.length)
|
|
60
|
+
return false;
|
|
61
|
+
return a.every((item, i) => deepEqual(item, bArr[i]));
|
|
62
|
+
}
|
|
63
|
+
const aObj = a;
|
|
64
|
+
const bObj = b;
|
|
65
|
+
const aKeys = Object.keys(aObj);
|
|
66
|
+
if (aKeys.length !== Object.keys(bObj).length)
|
|
67
|
+
return false;
|
|
68
|
+
return aKeys.every((key) => Object.prototype.hasOwnProperty.call(bObj, key) && deepEqual(aObj[key], bObj[key]));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Whether `document` satisfies `predicate`: every top-level field named in the
|
|
72
|
+
* predicate is present on the document and deeply equal to the predicate's value
|
|
73
|
+
* (DESIGN.md section 6.1). Multiple fields are an implicit AND. An empty
|
|
74
|
+
* predicate names no fields and so matches every document.
|
|
75
|
+
*
|
|
76
|
+
* Exported so the relational lens's `where` can reuse the exact same matching
|
|
77
|
+
* semantics over rows (a row is a Doc), instead of re-deriving a second matcher.
|
|
78
|
+
*/
|
|
79
|
+
export function matches(document, predicate) {
|
|
80
|
+
return Object.keys(predicate).every((key) => deepEqual(document[key], predicate[key]));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The kernel key for one document: `<collection>:<id>`, UTF-8 encoded (DESIGN.md
|
|
84
|
+
* section 6.1). Prefixing every id with the collection name is what scopes a
|
|
85
|
+
* collection to a contiguous byte range, so a later `<collection>:` prefix scan
|
|
86
|
+
* (D3) selects exactly that collection. For a point operation it just makes the
|
|
87
|
+
* key deterministic: the same `(collection, id)` always maps to the same bytes.
|
|
88
|
+
*/
|
|
89
|
+
function keyOf(collection, id) {
|
|
90
|
+
return utf8.encode(`${collection}:${id}`);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Build a {@link DocCollection} handle scoped to `collection` over a
|
|
94
|
+
* {@link Store} (the kernel's `Database` satisfies it, as does any object that
|
|
95
|
+
* can run a transaction).
|
|
96
|
+
*/
|
|
97
|
+
export function doc(store, collection) {
|
|
98
|
+
// The byte range covering every `<collection>:` key. prefixRange computes the
|
|
99
|
+
// [start, end) bound on raw bytes so it agrees with the kernel's order, which
|
|
100
|
+
// is what makes the colon a sound collection boundary (a sibling like "users2"
|
|
101
|
+
// sorts outside ["users:", "users;") and is never seen). The prefix string
|
|
102
|
+
// length is also where every key's id begins, so stripping it recovers the id.
|
|
103
|
+
const prefix = `${collection}:`;
|
|
104
|
+
const { start, end } = prefixRange(utf8.encode(prefix));
|
|
105
|
+
// The shared collection scan behind both all() and find(): walk the prefix
|
|
106
|
+
// range in id (byte) order and return the documents `keep` accepts. The thunk
|
|
107
|
+
// re-runs per iteration, so the Result stays lazy and re-iterable; rows are
|
|
108
|
+
// materialized inside the transaction because the kernel's getRange is only
|
|
109
|
+
// valid in the tx body.
|
|
110
|
+
const scan = (keep) => result(() => store.transact((tx) => {
|
|
111
|
+
const rows = [];
|
|
112
|
+
for (const entry of tx.getRange(start, end)) {
|
|
113
|
+
const document = decodeDoc(entry.value);
|
|
114
|
+
if (keep(document)) {
|
|
115
|
+
rows.push({ id: fromUtf8.decode(entry.key).slice(prefix.length), doc: document });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return rows;
|
|
119
|
+
}));
|
|
120
|
+
return {
|
|
121
|
+
put(id, document) {
|
|
122
|
+
store.transact((tx) => {
|
|
123
|
+
tx.set(keyOf(collection, id), encodeDoc(document));
|
|
124
|
+
});
|
|
125
|
+
return { changed: 1 };
|
|
126
|
+
},
|
|
127
|
+
get(id) {
|
|
128
|
+
return store.transact((tx) => {
|
|
129
|
+
const bytes = tx.get(keyOf(collection, id));
|
|
130
|
+
return bytes === undefined ? undefined : decodeDoc(bytes);
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
delete(id) {
|
|
134
|
+
// Read-before-delete in one transaction: the kernel's delete is a silent
|
|
135
|
+
// no-op on a missing key, so this is how the lens tells 1 from 0 changes.
|
|
136
|
+
const changed = store.transact((tx) => {
|
|
137
|
+
const k = keyOf(collection, id);
|
|
138
|
+
const existed = tx.get(k) !== undefined;
|
|
139
|
+
tx.delete(k);
|
|
140
|
+
return existed ? 1 : 0;
|
|
141
|
+
});
|
|
142
|
+
return { changed };
|
|
143
|
+
},
|
|
144
|
+
all() {
|
|
145
|
+
return scan(() => true);
|
|
146
|
+
},
|
|
147
|
+
find(predicate) {
|
|
148
|
+
return scan((document) => matches(document, predicate));
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=document.js.map
|