@net-mesh/sdk 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cortex.js ADDED
@@ -0,0 +1,584 @@
1
+ "use strict";
2
+ /**
3
+ * CortEX + NetDb — typed event-sourced state with reactive watches.
4
+ *
5
+ * Wraps the native `@net-mesh/core` CortEX bindings with ergonomic
6
+ * TypeScript APIs: `AsyncIterable`-shaped watches (so `for await`
7
+ * works naturally), typed errors via `CortexError` / `NetDbError`
8
+ * pattern matching, and the `snapshotAndWatch` primitive whose race
9
+ * fix landed on v2 (see `docs/STORAGE_AND_CORTEX.md`).
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { NetDb, TaskStatus, CortexError } from '@net-mesh/sdk';
14
+ *
15
+ * const db = await NetDb.open({ originHash: 0xABCDEF01, withTasks: true });
16
+ * const tasks = db.tasks!;
17
+ *
18
+ * try {
19
+ * tasks.create(1n, 'write docs', 100n);
20
+ * await tasks.waitForSeq(seq);
21
+ * } catch (e) {
22
+ * if (e instanceof CortexError) { /* handle adapter-level error *\/ }
23
+ * else { throw e; }
24
+ * }
25
+ *
26
+ * // "Paint what's here now, then react to changes":
27
+ * const { snapshot, updates } = await tasks.snapshotAndWatch({
28
+ * status: TaskStatus.Pending,
29
+ * });
30
+ * render(snapshot);
31
+ * for await (const next of updates) render(next);
32
+ * ```
33
+ */
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.NetDb = exports.MemoriesAdapter = exports.TasksAdapter = exports.RedexFile = exports.Redex = exports.RedexError = exports.NetDbError = exports.CortexError = void 0;
36
+ const core_1 = require("@net-mesh/core");
37
+ // Typed error classes shipped by `@net-mesh/core/errors`. Re-exported here
38
+ // so SDK consumers can `import { CortexError } from '@net-mesh/sdk'`
39
+ // without a second package path. `classifyError` is what the wrappers
40
+ // below use internally.
41
+ var errors_1 = require("@net-mesh/core/errors");
42
+ Object.defineProperty(exports, "CortexError", { enumerable: true, get: function () { return errors_1.CortexError; } });
43
+ Object.defineProperty(exports, "NetDbError", { enumerable: true, get: function () { return errors_1.NetDbError; } });
44
+ const errors_2 = require("@net-mesh/core/errors");
45
+ /**
46
+ * Raised on `redex:` prefixed failures: append / tail / read / sync /
47
+ * close, invalid channel names, mutually-exclusive config options.
48
+ * Extends `Error`; catch with `instanceof RedexError`.
49
+ */
50
+ class RedexError extends Error {
51
+ constructor(detail) {
52
+ super(detail ?? 'redex error');
53
+ this.name = 'RedexError';
54
+ Object.setPrototypeOf(this, RedexError.prototype);
55
+ }
56
+ }
57
+ exports.RedexError = RedexError;
58
+ /** Classify a napi-thrown error. Mirrors `@net-mesh/core/errors` for the
59
+ * `redex:` prefix which that package does not yet recognize. */
60
+ function classifyWithRedex(e) {
61
+ const classified = (0, errors_2.classifyError)(e);
62
+ if (classified !== e)
63
+ return classified; // cortex: / netdb: already handled
64
+ const msg = e?.message ?? '';
65
+ if (msg.startsWith('redex:'))
66
+ return new RedexError(msg);
67
+ return e;
68
+ }
69
+ /**
70
+ * Local RedEX manager. Holds the set of open files on this process.
71
+ * Cheap to share; adapters borrow it by reference.
72
+ */
73
+ class Redex {
74
+ /** @internal */
75
+ napi;
76
+ constructor(opts) {
77
+ this.napi = new core_1.Redex(opts?.persistentDir);
78
+ }
79
+ /**
80
+ * Open (or get) a raw RedEX file for domain-agnostic persistent
81
+ * logging. Returns the same handle across repeat calls with the
82
+ * same `name`; `config` is honored only on first open.
83
+ *
84
+ * Use this when you want an append-only event log without the
85
+ * CortEX fold / typed-adapter layer. Appends, tailing, and range
86
+ * reads work directly over the file.
87
+ *
88
+ * With `config.persistent = true`, this manager must have been
89
+ * constructed with a `persistentDir`.
90
+ */
91
+ openFile(name, config) {
92
+ try {
93
+ const napiCfg = toNapiFileConfig(config);
94
+ return new RedexFile(this.napi.openFile(name, napiCfg ?? null));
95
+ }
96
+ catch (e) {
97
+ throw classifyWithRedex(e);
98
+ }
99
+ }
100
+ }
101
+ exports.Redex = Redex;
102
+ function toNapiFileConfig(c) {
103
+ if (!c)
104
+ return undefined;
105
+ return {
106
+ persistent: c.persistent,
107
+ fsyncEveryN: c.fsyncEveryN,
108
+ fsyncIntervalMs: c.fsyncIntervalMs,
109
+ retentionMaxEvents: c.retentionMaxEvents,
110
+ retentionMaxBytes: c.retentionMaxBytes,
111
+ retentionMaxAgeMs: c.retentionMaxAgeMs,
112
+ };
113
+ }
114
+ function toRedexEvent(raw) {
115
+ return {
116
+ seq: raw.seq,
117
+ payload: raw.payload,
118
+ checksum: raw.checksum,
119
+ isInline: raw.isInline,
120
+ };
121
+ }
122
+ /** Raw RedEX file handle. Append, tail, range-read without the
123
+ * CortEX adapter layer. */
124
+ class RedexFile {
125
+ /** @internal */
126
+ napi;
127
+ constructor(inner) {
128
+ this.napi = inner;
129
+ }
130
+ /** Append one payload. Returns the assigned sequence number. */
131
+ append(payload) {
132
+ try {
133
+ return this.napi.append(payload);
134
+ }
135
+ catch (e) {
136
+ throw classifyWithRedex(e);
137
+ }
138
+ }
139
+ /** Append a batch atomically. Returns the seq of the first event
140
+ * (subsequent events are `first + 0, first + 1, ...`), or `null`
141
+ * for an empty batch (no seq is allocated when there's nothing to
142
+ * append). */
143
+ appendBatch(payloads) {
144
+ try {
145
+ return this.napi.appendBatch(payloads);
146
+ }
147
+ catch (e) {
148
+ throw classifyWithRedex(e);
149
+ }
150
+ }
151
+ /** Read the half-open range `[start, end)` from the in-memory
152
+ * index. Only retained entries are returned. */
153
+ readRange(start, end) {
154
+ try {
155
+ return this.napi.readRange(start, end).map(toRedexEvent);
156
+ }
157
+ catch (e) {
158
+ throw classifyWithRedex(e);
159
+ }
160
+ }
161
+ /** Number of retained events (post-retention eviction). Returned
162
+ * as `bigint` because event counts can exceed `Number.MAX_SAFE_INTEGER`
163
+ * (~9 P) in theory — though in practice they'll fit in a `number`,
164
+ * the lossless type is the safe surface. */
165
+ len() {
166
+ return this.napi.len();
167
+ }
168
+ /** Open a live tail. Yields every event with `seq >= fromSeq`
169
+ * (default `0n`) — atomically backfills the retained range and
170
+ * then streams appends. Breaking out of `for await` releases the
171
+ * native iterator via `return()`. */
172
+ async tail(fromSeq) {
173
+ try {
174
+ const iter = await this.napi.tail(fromSeq ?? null);
175
+ const raw = {
176
+ // The napi tail emits `redex:` errors for tail-time failures
177
+ // (file closed mid-stream, decode failures on reopen, etc.).
178
+ // Without classifying here, those surface as bare `Error`s
179
+ // — callers doing `instanceof RedexError` miss them.
180
+ async next() {
181
+ try {
182
+ const v = await iter.next();
183
+ return v === null ? null : toRedexEvent(v);
184
+ }
185
+ catch (e) {
186
+ throw classifyWithRedex(e);
187
+ }
188
+ },
189
+ close: () => iter.close(),
190
+ };
191
+ return wrapWatchIter(raw);
192
+ }
193
+ catch (e) {
194
+ throw classifyWithRedex(e);
195
+ }
196
+ }
197
+ /** Explicit fsync. Always fsyncs regardless of policy; no-op on
198
+ * heap-only files. */
199
+ sync() {
200
+ try {
201
+ this.napi.sync();
202
+ }
203
+ catch (e) {
204
+ throw classifyWithRedex(e);
205
+ }
206
+ }
207
+ /** Close the file. Outstanding tail iterators end cleanly on
208
+ * their next emission. */
209
+ close() {
210
+ try {
211
+ this.napi.close();
212
+ }
213
+ catch (e) {
214
+ throw classifyWithRedex(e);
215
+ }
216
+ }
217
+ }
218
+ exports.RedexFile = RedexFile;
219
+ /**
220
+ * Turn a pull-based NAPI iterator into an `AsyncIterable` that plays
221
+ * nicely with `for await (...)`. The `return()` hook (fired by `break`
222
+ * / `throw` inside the loop) calls `close()` so native resources are
223
+ * released deterministically — dropping the loop is enough, no manual
224
+ * cleanup needed.
225
+ */
226
+ function wrapWatchIter(raw) {
227
+ return {
228
+ [Symbol.asyncIterator]() {
229
+ let done = false;
230
+ const finish = () => ({ value: undefined, done: true });
231
+ return {
232
+ async next() {
233
+ if (done)
234
+ return finish();
235
+ const v = await raw.next();
236
+ if (v === null) {
237
+ done = true;
238
+ return finish();
239
+ }
240
+ return { value: v, done: false };
241
+ },
242
+ async return() {
243
+ if (!done) {
244
+ done = true;
245
+ raw.close();
246
+ }
247
+ return finish();
248
+ },
249
+ };
250
+ },
251
+ };
252
+ }
253
+ // ---------------------------------------------------------------------------
254
+ // Tasks adapter
255
+ // ---------------------------------------------------------------------------
256
+ /**
257
+ * Typed tasks adapter. CRUD plus `listTasks` / `watch` /
258
+ * `snapshotAndWatch` for reactive consumers.
259
+ */
260
+ class TasksAdapter {
261
+ /** @internal */
262
+ napi;
263
+ constructor(inner) {
264
+ this.napi = inner;
265
+ }
266
+ /**
267
+ * Open a standalone tasks adapter against a `Redex`. For bundled
268
+ * tasks + memories access, prefer {@link NetDb.open}.
269
+ */
270
+ static async open(redex, originHash, opts) {
271
+ try {
272
+ const inner = await core_1.TasksAdapter.open(redex.napi, originHash, opts?.persistent ?? null);
273
+ return new TasksAdapter(inner);
274
+ }
275
+ catch (e) {
276
+ throw (0, errors_2.classifyError)(e);
277
+ }
278
+ }
279
+ /** Restore from a snapshot captured via {@link TasksAdapter.snapshot}. */
280
+ static async openFromSnapshot(redex, originHash, snapshot, opts) {
281
+ try {
282
+ const inner = await core_1.TasksAdapter.openFromSnapshot(redex.napi, originHash, snapshot.stateBytes, snapshot.lastSeq ?? null, opts?.persistent ?? null);
283
+ return new TasksAdapter(inner);
284
+ }
285
+ catch (e) {
286
+ throw (0, errors_2.classifyError)(e);
287
+ }
288
+ }
289
+ create(id, title, nowNs) {
290
+ try {
291
+ return this.napi.create(id, title, nowNs);
292
+ }
293
+ catch (e) {
294
+ throw (0, errors_2.classifyError)(e);
295
+ }
296
+ }
297
+ rename(id, newTitle, nowNs) {
298
+ try {
299
+ return this.napi.rename(id, newTitle, nowNs);
300
+ }
301
+ catch (e) {
302
+ throw (0, errors_2.classifyError)(e);
303
+ }
304
+ }
305
+ complete(id, nowNs) {
306
+ try {
307
+ return this.napi.complete(id, nowNs);
308
+ }
309
+ catch (e) {
310
+ throw (0, errors_2.classifyError)(e);
311
+ }
312
+ }
313
+ delete(id) {
314
+ try {
315
+ return this.napi.delete(id);
316
+ }
317
+ catch (e) {
318
+ throw (0, errors_2.classifyError)(e);
319
+ }
320
+ }
321
+ /** Total count in the materialized state (ignores any filter). */
322
+ count() {
323
+ try {
324
+ return this.napi.count();
325
+ }
326
+ catch (e) {
327
+ throw (0, errors_2.classifyError)(e);
328
+ }
329
+ }
330
+ /** Wait for the fold task to have applied every event up through `seq`. */
331
+ async waitForSeq(seq) {
332
+ try {
333
+ return await this.napi.waitForSeq(seq);
334
+ }
335
+ catch (e) {
336
+ throw (0, errors_2.classifyError)(e);
337
+ }
338
+ }
339
+ /** Snapshot query over the materialized state. */
340
+ listTasks(filter) {
341
+ try {
342
+ return this.napi.listTasks(filter ?? null);
343
+ }
344
+ catch (e) {
345
+ throw (0, errors_2.classifyError)(e);
346
+ }
347
+ }
348
+ /** Capture a serialized state snapshot for {@link TasksAdapter.openFromSnapshot}. */
349
+ snapshot() {
350
+ try {
351
+ return this.napi.snapshot();
352
+ }
353
+ catch (e) {
354
+ throw (0, errors_2.classifyError)(e);
355
+ }
356
+ }
357
+ /**
358
+ * Reactive watch. Yields the current filter result first, then once
359
+ * per fold tick where the result differs from the previous emission.
360
+ * Breaking out of `for await` calls `close()` automatically.
361
+ */
362
+ async watch(filter) {
363
+ try {
364
+ const iter = await this.napi.watchTasks(filter ?? null);
365
+ return wrapWatchIter(iter);
366
+ }
367
+ catch (e) {
368
+ throw (0, errors_2.classifyError)(e);
369
+ }
370
+ }
371
+ /**
372
+ * Atomic "paint what's here now, then react to changes." Returns the
373
+ * snapshot and an `AsyncIterable` of subsequent filter results.
374
+ *
375
+ * Prefer this to calling `listTasks` + `watch` separately — they
376
+ * race each other, and a mutation landing between the two reads
377
+ * would be silently lost.
378
+ */
379
+ async snapshotAndWatch(filter) {
380
+ try {
381
+ const combined = await this.napi.snapshotAndWatchTasks(filter ?? null);
382
+ const iter = {
383
+ next: () => combined.next(),
384
+ close: () => combined.close(),
385
+ };
386
+ return {
387
+ snapshot: combined.snapshot,
388
+ updates: wrapWatchIter(iter),
389
+ };
390
+ }
391
+ catch (e) {
392
+ throw (0, errors_2.classifyError)(e);
393
+ }
394
+ }
395
+ }
396
+ exports.TasksAdapter = TasksAdapter;
397
+ // ---------------------------------------------------------------------------
398
+ // Memories adapter
399
+ // ---------------------------------------------------------------------------
400
+ /**
401
+ * Typed memories adapter. CRUD plus `listMemories` / `watch` /
402
+ * `snapshotAndWatch`.
403
+ */
404
+ class MemoriesAdapter {
405
+ /** @internal */
406
+ napi;
407
+ constructor(inner) {
408
+ this.napi = inner;
409
+ }
410
+ static async open(redex, originHash, opts) {
411
+ try {
412
+ const inner = await core_1.MemoriesAdapter.open(redex.napi, originHash, opts?.persistent ?? null);
413
+ return new MemoriesAdapter(inner);
414
+ }
415
+ catch (e) {
416
+ throw (0, errors_2.classifyError)(e);
417
+ }
418
+ }
419
+ static async openFromSnapshot(redex, originHash, snapshot, opts) {
420
+ try {
421
+ const inner = await core_1.MemoriesAdapter.openFromSnapshot(redex.napi, originHash, snapshot.stateBytes, snapshot.lastSeq ?? null, opts?.persistent ?? null);
422
+ return new MemoriesAdapter(inner);
423
+ }
424
+ catch (e) {
425
+ throw (0, errors_2.classifyError)(e);
426
+ }
427
+ }
428
+ store(id, content, tags, source, nowNs) {
429
+ try {
430
+ return this.napi.store(id, content, tags, source, nowNs);
431
+ }
432
+ catch (e) {
433
+ throw (0, errors_2.classifyError)(e);
434
+ }
435
+ }
436
+ retag(id, tags, nowNs) {
437
+ try {
438
+ return this.napi.retag(id, tags, nowNs);
439
+ }
440
+ catch (e) {
441
+ throw (0, errors_2.classifyError)(e);
442
+ }
443
+ }
444
+ pin(id, nowNs) {
445
+ try {
446
+ return this.napi.pin(id, nowNs);
447
+ }
448
+ catch (e) {
449
+ throw (0, errors_2.classifyError)(e);
450
+ }
451
+ }
452
+ unpin(id, nowNs) {
453
+ try {
454
+ return this.napi.unpin(id, nowNs);
455
+ }
456
+ catch (e) {
457
+ throw (0, errors_2.classifyError)(e);
458
+ }
459
+ }
460
+ delete(id) {
461
+ try {
462
+ return this.napi.delete(id);
463
+ }
464
+ catch (e) {
465
+ throw (0, errors_2.classifyError)(e);
466
+ }
467
+ }
468
+ count() {
469
+ try {
470
+ return this.napi.count();
471
+ }
472
+ catch (e) {
473
+ throw (0, errors_2.classifyError)(e);
474
+ }
475
+ }
476
+ async waitForSeq(seq) {
477
+ try {
478
+ return await this.napi.waitForSeq(seq);
479
+ }
480
+ catch (e) {
481
+ throw (0, errors_2.classifyError)(e);
482
+ }
483
+ }
484
+ listMemories(filter) {
485
+ try {
486
+ return this.napi.listMemories(filter ?? null);
487
+ }
488
+ catch (e) {
489
+ throw (0, errors_2.classifyError)(e);
490
+ }
491
+ }
492
+ snapshot() {
493
+ try {
494
+ return this.napi.snapshot();
495
+ }
496
+ catch (e) {
497
+ throw (0, errors_2.classifyError)(e);
498
+ }
499
+ }
500
+ async watch(filter) {
501
+ try {
502
+ const iter = await this.napi.watchMemories(filter ?? null);
503
+ return wrapWatchIter(iter);
504
+ }
505
+ catch (e) {
506
+ throw (0, errors_2.classifyError)(e);
507
+ }
508
+ }
509
+ /** Atomic snapshot + delta stream. See {@link TasksAdapter.snapshotAndWatch}. */
510
+ async snapshotAndWatch(filter) {
511
+ try {
512
+ const combined = await this.napi.snapshotAndWatchMemories(filter ?? null);
513
+ const iter = {
514
+ next: () => combined.next(),
515
+ close: () => combined.close(),
516
+ };
517
+ return {
518
+ snapshot: combined.snapshot,
519
+ updates: wrapWatchIter(iter),
520
+ };
521
+ }
522
+ catch (e) {
523
+ throw (0, errors_2.classifyError)(e);
524
+ }
525
+ }
526
+ }
527
+ exports.MemoriesAdapter = MemoriesAdapter;
528
+ // ---------------------------------------------------------------------------
529
+ // NetDb facade
530
+ // ---------------------------------------------------------------------------
531
+ /**
532
+ * Unified NetDB handle. Bundles `TasksAdapter` + `MemoriesAdapter`
533
+ * under one object. Open with both models for the common case, or
534
+ * with only one if the other isn't needed.
535
+ */
536
+ class NetDb {
537
+ /** @internal */
538
+ napi;
539
+ constructor(inner) {
540
+ this.napi = inner;
541
+ }
542
+ static async open(config) {
543
+ try {
544
+ const inner = await core_1.NetDb.open(config);
545
+ return new NetDb(inner);
546
+ }
547
+ catch (e) {
548
+ throw (0, errors_2.classifyError)(e);
549
+ }
550
+ }
551
+ static async openFromSnapshot(config, bundle) {
552
+ try {
553
+ const inner = await core_1.NetDb.openFromSnapshot(config, bundle);
554
+ return new NetDb(inner);
555
+ }
556
+ catch (e) {
557
+ throw (0, errors_2.classifyError)(e);
558
+ }
559
+ }
560
+ /** The tasks adapter, or `null` if `withTasks` wasn't set at open. */
561
+ get tasks() {
562
+ const t = this.napi.tasks;
563
+ return t ? new TasksAdapter(t) : null;
564
+ }
565
+ /** The memories adapter, or `null` if `withMemories` wasn't set. */
566
+ get memories() {
567
+ const m = this.napi.memories;
568
+ return m ? new MemoriesAdapter(m) : null;
569
+ }
570
+ /** Snapshot every enabled model into one bundle. */
571
+ snapshot() {
572
+ try {
573
+ return this.napi.snapshot();
574
+ }
575
+ catch (e) {
576
+ throw (0, errors_2.classifyError)(e);
577
+ }
578
+ }
579
+ /** Close every enabled adapter. Idempotent. */
580
+ close() {
581
+ this.napi.close();
582
+ }
583
+ }
584
+ exports.NetDb = NetDb;