@rljson/io 0.0.69 → 0.0.71

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.
@@ -18,7 +18,7 @@ found in the LICENSE file in the root of this package.
18
18
 
19
19
  The central abstraction that defines all database operations:
20
20
 
21
- ```
21
+ ```text
22
22
  ┌─────────────────────────────────────────────┐
23
23
  │ Io Interface │
24
24
  ├─────────────────────────────────────────────┤
@@ -42,7 +42,7 @@ The central abstraction that defines all database operations:
42
42
 
43
43
  In-memory implementation using JavaScript objects.
44
44
 
45
- ```
45
+ ```text
46
46
  ┌─────────────────────┐
47
47
  │ IoMem │
48
48
  ├─────────────────────┤
@@ -61,6 +61,21 @@ In-memory implementation using JavaScript objects.
61
61
  - Uses `@rljson/hash` for data hashing and identity
62
62
  - `IsReady` pattern for initialization tracking
63
63
 
64
+ **Performance internals** (no observable API change):
65
+
66
+ - Rows are indexed per table by content hash — write dedup is O(1) and
67
+ `readRows` with a string `_hash` in the where clause resolves through
68
+ the index instead of scanning the table
69
+ - Table data is kept hash-sorted incrementally (binary-searched insert)
70
+ instead of re-sorting the whole table per write
71
+ - Table and global hashes are updated lazily: writes mark tables dirty,
72
+ hashes are recomputed before they become observable (`dump`,
73
+ `dumpTable`, `createOrExtendTable`, re-`init`) — deterministic hashes
74
+ make the refreshed state identical to eager updates
75
+ - The latest table configuration and column keys are cached per table
76
+ (updated on create/extend), so validation does not re-scan all
77
+ configurations per read/write
78
+
64
79
  **Data Structure:**
65
80
 
66
81
  ```typescript
@@ -76,7 +91,7 @@ _mem = {
76
91
 
77
92
  Remote database connection over sockets (Socket.IO compatible).
78
93
 
79
- ```
94
+ ```text
80
95
  ┌──────────────┐ Socket ┌──────────────┐
81
96
  │ IoPeer │◄───────────────────────►│ IoPeerBridge │
82
97
  │ (Client) │ Events/Acks │ (Server) │
@@ -107,7 +122,7 @@ Remote database connection over sockets (Socket.IO compatible).
107
122
 
108
123
  Server-side handler that bridges socket events to Io operations.
109
124
 
110
- ```
125
+ ```text
111
126
  ┌─────────────────────────────────────┐
112
127
  │ IoPeerBridge │
113
128
  ├─────────────────────────────────────┤
@@ -132,7 +147,7 @@ Server-side handler that bridges socket events to Io operations.
132
147
 
133
148
  Aggregates multiple Io instances with priority-based cascading.
134
149
 
135
- ```
150
+ ```text
136
151
  ┌────────────────────────────────────────────┐
137
152
  │ IoMulti │
138
153
  ├────────────────────────────────────────────┤
@@ -182,7 +197,7 @@ IoMultiIo {
182
197
 
183
198
  Provides name mapping between different table name formats.
184
199
 
185
- ```
200
+ ```text
186
201
  ┌──────────────────────────────────────┐
187
202
  │ IoDbNameMapping │
188
203
  ├──────────────────────────────────────┤
@@ -202,7 +217,7 @@ Provides name mapping between different table name formats.
202
217
 
203
218
  Server implementation that combines Socket.IO with Io backends.
204
219
 
205
- ```
220
+ ```text
206
221
  ┌─────────────────────────────────────┐
207
222
  │ IoServer │
208
223
  ├─────────────────────────────────────┤
@@ -230,7 +245,7 @@ Server implementation that combines Socket.IO with Io backends.
230
245
 
231
246
  ### Write Operation
232
247
 
233
- ```
248
+ ```text
234
249
  Client Code
235
250
 
236
251
 
@@ -248,7 +263,7 @@ io.write(data)
248
263
 
249
264
  ### Read Operation (IoMulti Cascade)
250
265
 
251
- ```
266
+ ```text
252
267
  io.readRows({table: 'users', where: {id: 1}})
253
268
 
254
269
 
@@ -293,7 +308,7 @@ return rljson; // Only after loop completes
293
308
 
294
309
  ### Event-Based Protocol
295
310
 
296
- ```
311
+ ```text
297
312
  Client (IoPeer) Server (IoPeerBridge)
298
313
  │ │
299
314
  ├─── emit('readRows', request) ────►│
package/README.public.md CHANGED
@@ -78,6 +78,11 @@ The `Io` interface is the core abstraction that defines a standard set of operat
78
78
  - **Data Operations**: `write()`, `readRows()`, `dump()`
79
79
  - **Schema Management**: `createOrExtendTable()`, `tableExists()`, `rawTableCfgs()`
80
80
  - **Metadata**: `contentType()`, `rowCount()`, `lastUpdate()`
81
+ - **Optional Batch Reads**: `readRowsByHashes()` — answers many content
82
+ hash lookups in one request. Implementations may omit it; callers must
83
+ fall back to `readRows()` then. `IoMem`, `IoMulti` (per-hash cascade
84
+ across its members) and `IoPeer`/`IoServer` (one socket round trip,
85
+ with automatic per-hash fallback against older remotes) support it.
81
86
 
82
87
  All implementations (in-memory, remote, multi-source) conform to this interface.
83
88
 
@@ -18,7 +18,7 @@ found in the LICENSE file in the root of this package.
18
18
 
19
19
  The central abstraction that defines all database operations:
20
20
 
21
- ```
21
+ ```text
22
22
  ┌─────────────────────────────────────────────┐
23
23
  │ Io Interface │
24
24
  ├─────────────────────────────────────────────┤
@@ -42,7 +42,7 @@ The central abstraction that defines all database operations:
42
42
 
43
43
  In-memory implementation using JavaScript objects.
44
44
 
45
- ```
45
+ ```text
46
46
  ┌─────────────────────┐
47
47
  │ IoMem │
48
48
  ├─────────────────────┤
@@ -61,6 +61,21 @@ In-memory implementation using JavaScript objects.
61
61
  - Uses `@rljson/hash` for data hashing and identity
62
62
  - `IsReady` pattern for initialization tracking
63
63
 
64
+ **Performance internals** (no observable API change):
65
+
66
+ - Rows are indexed per table by content hash — write dedup is O(1) and
67
+ `readRows` with a string `_hash` in the where clause resolves through
68
+ the index instead of scanning the table
69
+ - Table data is kept hash-sorted incrementally (binary-searched insert)
70
+ instead of re-sorting the whole table per write
71
+ - Table and global hashes are updated lazily: writes mark tables dirty,
72
+ hashes are recomputed before they become observable (`dump`,
73
+ `dumpTable`, `createOrExtendTable`, re-`init`) — deterministic hashes
74
+ make the refreshed state identical to eager updates
75
+ - The latest table configuration and column keys are cached per table
76
+ (updated on create/extend), so validation does not re-scan all
77
+ configurations per read/write
78
+
64
79
  **Data Structure:**
65
80
 
66
81
  ```typescript
@@ -76,7 +91,7 @@ _mem = {
76
91
 
77
92
  Remote database connection over sockets (Socket.IO compatible).
78
93
 
79
- ```
94
+ ```text
80
95
  ┌──────────────┐ Socket ┌──────────────┐
81
96
  │ IoPeer │◄───────────────────────►│ IoPeerBridge │
82
97
  │ (Client) │ Events/Acks │ (Server) │
@@ -107,7 +122,7 @@ Remote database connection over sockets (Socket.IO compatible).
107
122
 
108
123
  Server-side handler that bridges socket events to Io operations.
109
124
 
110
- ```
125
+ ```text
111
126
  ┌─────────────────────────────────────┐
112
127
  │ IoPeerBridge │
113
128
  ├─────────────────────────────────────┤
@@ -132,7 +147,7 @@ Server-side handler that bridges socket events to Io operations.
132
147
 
133
148
  Aggregates multiple Io instances with priority-based cascading.
134
149
 
135
- ```
150
+ ```text
136
151
  ┌────────────────────────────────────────────┐
137
152
  │ IoMulti │
138
153
  ├────────────────────────────────────────────┤
@@ -182,7 +197,7 @@ IoMultiIo {
182
197
 
183
198
  Provides name mapping between different table name formats.
184
199
 
185
- ```
200
+ ```text
186
201
  ┌──────────────────────────────────────┐
187
202
  │ IoDbNameMapping │
188
203
  ├──────────────────────────────────────┤
@@ -202,7 +217,7 @@ Provides name mapping between different table name formats.
202
217
 
203
218
  Server implementation that combines Socket.IO with Io backends.
204
219
 
205
- ```
220
+ ```text
206
221
  ┌─────────────────────────────────────┐
207
222
  │ IoServer │
208
223
  ├─────────────────────────────────────┤
@@ -230,7 +245,7 @@ Server implementation that combines Socket.IO with Io backends.
230
245
 
231
246
  ### Write Operation
232
247
 
233
- ```
248
+ ```text
234
249
  Client Code
235
250
 
236
251
 
@@ -248,7 +263,7 @@ io.write(data)
248
263
 
249
264
  ### Read Operation (IoMulti Cascade)
250
265
 
251
- ```
266
+ ```text
252
267
  io.readRows({table: 'users', where: {id: 1}})
253
268
 
254
269
 
@@ -293,7 +308,7 @@ return rljson; // Only after loop completes
293
308
 
294
309
  ### Event-Based Protocol
295
310
 
296
- ```
311
+ ```text
297
312
  Client (IoPeer) Server (IoPeerBridge)
298
313
  │ │
299
314
  ├─── emit('readRows', request) ────►│
@@ -78,6 +78,11 @@ The `Io` interface is the core abstraction that defines a standard set of operat
78
78
  - **Data Operations**: `write()`, `readRows()`, `dump()`
79
79
  - **Schema Management**: `createOrExtendTable()`, `tableExists()`, `rawTableCfgs()`
80
80
  - **Metadata**: `contentType()`, `rowCount()`, `lastUpdate()`
81
+ - **Optional Batch Reads**: `readRowsByHashes()` — answers many content
82
+ hash lookups in one request. Implementations may omit it; callers must
83
+ fall back to `readRows()` then. `IoMem`, `IoMulti` (per-hash cascade
84
+ across its members) and `IoPeer`/`IoServer` (one socket round trip,
85
+ with automatic per-hash fallback against older remotes) support it.
81
86
 
82
87
  All implementations (in-memory, remote, multi-source) conform to this interface.
83
88
 
package/dist/io-mem.d.ts CHANGED
@@ -23,6 +23,10 @@ export declare class IoMem implements Io {
23
23
  [column: string]: JsonValue;
24
24
  };
25
25
  }): Promise<Rljson>;
26
+ readRowsByHashes(request: {
27
+ table: string;
28
+ hashes: string[];
29
+ }): Promise<Rljson>;
26
30
  rowCount(table: string): Promise<number>;
27
31
  write(request: {
28
32
  data: Rljson;
@@ -36,6 +40,79 @@ export declare class IoMem implements Io {
36
40
  private _isReady;
37
41
  private _isOpen;
38
42
  private _mem;
43
+ /**
44
+ * Latest table configuration per table (the one with the most
45
+ * columns). Kept in sync by _createTable/_extendTable so that reads
46
+ * and writes need no repeated scan over all configurations.
47
+ */
48
+ private readonly _latestCfgs;
49
+ /** Column key sets per table, derived from _latestCfgs */
50
+ private readonly _columnKeys;
51
+ /**
52
+ * Per-table index of rows by content hash. Rows are only ever added,
53
+ * never removed, so the index cannot go stale.
54
+ */
55
+ private readonly _rowIndex;
56
+ /**
57
+ * Tables whose table hash (and thus the global hash) is outdated
58
+ * after writes. Hashes are refreshed lazily before they become
59
+ * observable (dump, dumpTable, create/extend) instead of after every
60
+ * single write — row hashes themselves are always up to date.
61
+ */
62
+ private readonly _dirtyTableHashes;
63
+ /**
64
+ * Recomputes the hashes of all dirty tables and the global hash.
65
+ * Produces exactly the state an eager per-write update would have
66
+ * produced (hashes are deterministic over the same data).
67
+ */
68
+ private _refreshHashes;
69
+ /**
70
+ * Returns the row index of a table, building it lazily from the
71
+ * table data on first access.
72
+ * @param table - The table to index
73
+ */
74
+ private _rowIndexFor;
75
+ /**
76
+ * Inserts a row into hash-sorted table data at its sorted position —
77
+ * preserves the order sortTableDataAndUpdateHash establishes without
78
+ * a full re-sort.
79
+ * @param data - The hash-sorted table data
80
+ * @param row - The row to insert
81
+ */
82
+ private static _insertSortedByHash;
83
+ /**
84
+ * The row filter predicate of readRows. Extracted so that the indexed
85
+ * fast path and the full scan share identical semantics.
86
+ * @param row - The row to check
87
+ * @param where - The where clause
88
+ */
89
+ private static _rowMatchesWhere;
90
+ /**
91
+ * Returns the latest table configuration, filling the cache lazily
92
+ * from IoTools (which picks the config with the most columns).
93
+ * @param table - The table to get the configuration for
94
+ */
95
+ private _latestCfg;
96
+ /**
97
+ * Updates the cached latest configuration of a table
98
+ * @param cfg - The new latest configuration
99
+ */
100
+ private _setLatestCfg;
101
+ /**
102
+ * Throws when one of the given columns does not exist in the table.
103
+ * Mirrors IoTools.throwWhenColumnDoesNotExist but uses the cached
104
+ * configuration.
105
+ * @param table - The table to check
106
+ * @param columns - The columns to check
107
+ */
108
+ private _throwWhenColumnDoesNotExist;
109
+ /**
110
+ * Throws when the data does not match the table configurations.
111
+ * Mirrors IoTools.throwWhenTableDataDoesNotMatchCfg but uses the
112
+ * cached configurations.
113
+ * @param data - The data to validate
114
+ */
115
+ private _throwWhenTableDataDoesNotMatchCfg;
39
116
  private _init;
40
117
  private _initTableCfgs;
41
118
  private _updateGlobalHash;
@@ -48,5 +125,5 @@ export declare class IoMem implements Io {
48
125
  private _contentType;
49
126
  private _write;
50
127
  private _readRows;
51
- _removeNullValues(rljson: Rljson): void;
128
+ _removeNullValues(rljson: Rljson): boolean;
52
129
  }
@@ -112,6 +112,23 @@ export declare class IoMulti implements Io {
112
112
  * @returns A promise that resolves to the row count of the table.
113
113
  */
114
114
  rowCount(table: string): Promise<number>;
115
+ /**
116
+ * Batch read with PER-HASH cascade: every hash not found in a
117
+ * higher-priority readable is looked up in the next one. Readables
118
+ * without readRowsByHashes are queried per hash via readRows.
119
+ * @param request - The table and the row hashes to read
120
+ */
121
+ readRowsByHashes(request: {
122
+ table: string;
123
+ hashes: string[];
124
+ }): Promise<Rljson>;
125
+ /**
126
+ * Per-hash fallback for readables without readRowsByHashes.
127
+ * @param io - The readable io
128
+ * @param table - The table to read from
129
+ * @param hashes - The row hashes to read
130
+ */
131
+ private static _readHashesViaReadRows;
115
132
  /**
116
133
  * Gets the list of underlying readable Io instances, sorted by priority.
117
134
  */
package/dist/io-peer.d.ts CHANGED
@@ -88,6 +88,21 @@ export declare class IoPeer implements Io {
88
88
  [column: string]: JsonValue | null;
89
89
  };
90
90
  }): Promise<Rljson>;
91
+ /**
92
+ * True once the remote side signalled that it does not support batch
93
+ * reads — all further batch reads then use per-hash readRows.
94
+ */
95
+ private _batchReadsUnsupported;
96
+ /**
97
+ * Batch read over the socket. Falls back to per-hash readRows when
98
+ * the remote side does not support it (older server) and remembers
99
+ * the capability for subsequent calls.
100
+ * @param request - The table and the row hashes to read
101
+ */
102
+ readRowsByHashes(request: {
103
+ table: string;
104
+ hashes: string[];
105
+ }): Promise<Rljson>;
91
106
  /**
92
107
  * Retrieves the number of rows in a specific table.
93
108
  * @param table The name of the table to count rows in.
package/dist/io.d.ts CHANGED
@@ -50,6 +50,17 @@ export interface Io {
50
50
  [column: string]: JsonValue | null;
51
51
  };
52
52
  }): Promise<Rljson>;
53
+ /**
54
+ * Optional batch read: returns the rows matching the given content
55
+ * hashes in one request. Hashes without a matching row are simply
56
+ * absent from the result. Implementations answering many hash lookups
57
+ * in one round trip should provide this; callers MUST fall back to
58
+ * readRows when the method is not implemented.
59
+ */
60
+ readRowsByHashes?(request: {
61
+ table: string;
62
+ hashes: string[];
63
+ }): Promise<Rljson>;
53
64
  /** Returns the number of rows in the given table */
54
65
  rowCount(table: string): Promise<number>;
55
66
  }