@rljson/io 0.0.63 → 0.0.65

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.
@@ -7,3 +7,468 @@ found in the LICENSE file in the root of this package.
7
7
  -->
8
8
 
9
9
  # Architecture
10
+
11
+ ## Overview
12
+
13
+ `@rljson/io` provides a unified abstraction layer for working with RLJSON (Relational JSON) data. The architecture follows a layered approach with multiple implementations of the core `Io` interface, each serving different use cases.
14
+
15
+ ## Core Components
16
+
17
+ ### 1. Io Interface (`io.ts`)
18
+
19
+ The central abstraction that defines all database operations:
20
+
21
+ ```
22
+ ┌─────────────────────────────────────────────┐
23
+ │ Io Interface │
24
+ ├─────────────────────────────────────────────┤
25
+ │ Lifecycle: init, close, isReady │
26
+ │ Data Ops: write, readRows, dump │
27
+ │ Schema: createOrExtendTable, tableExists │
28
+ │ Metadata: contentType, rowCount, lastUpdate│
29
+ └─────────────────────────────────────────────┘
30
+ ```
31
+
32
+ **Key Methods:**
33
+
34
+ - `init()`: Initialize the Io instance
35
+ - `write()`: Persist RLJSON data
36
+ - `readRows()`: Query data with conditions
37
+ - `dump()`: Export complete database state
38
+ - `createOrExtendTable()`: Schema evolution
39
+ - `tableExists()`: Check table existence
40
+
41
+ ### 2. IoMem (`io-mem.ts`)
42
+
43
+ In-memory implementation using JavaScript objects.
44
+
45
+ ```
46
+ ┌─────────────────────┐
47
+ │ IoMem │
48
+ ├─────────────────────┤
49
+ │ Storage: In-Memory │
50
+ │ Speed: Very Fast │
51
+ │ Persistence: No │
52
+ │ Use Case: Testing │
53
+ │ Caching │
54
+ └─────────────────────┘
55
+ ```
56
+
57
+ **Implementation Details:**
58
+
59
+ - Data stored in `_mem` private property as plain objects
60
+ - Synchronous operations wrapped in promises for API consistency
61
+ - Uses `@rljson/hash` for data hashing and identity
62
+ - `IsReady` pattern for initialization tracking
63
+
64
+ **Data Structure:**
65
+
66
+ ```typescript
67
+ _mem = {
68
+ tableName: {
69
+ _type: 'components', // Content type identifier
70
+ _data: [...rows] // Array of row objects
71
+ }
72
+ }
73
+ ```
74
+
75
+ ### 3. IoPeer (`io-peer.ts`)
76
+
77
+ Remote database connection over sockets (Socket.IO compatible).
78
+
79
+ ```
80
+ ┌──────────────┐ Socket ┌──────────────┐
81
+ │ IoPeer │◄───────────────────────►│ IoPeerBridge │
82
+ │ (Client) │ Events/Acks │ (Server) │
83
+ └──────────────┘ └──────────────┘
84
+ │ │
85
+ │ ▼
86
+ │ ┌──────────┐
87
+ └─────────── Io Interface ────────┤ Io │
88
+ └──────────┘
89
+ ```
90
+
91
+ **Protocol:**
92
+
93
+ - Emits socket events for each Io operation
94
+ - Uses acknowledgment callbacks for responses
95
+ - Handles connection lifecycle (connect/disconnect)
96
+ - Error propagation through callbacks
97
+
98
+ **Socket Events:**
99
+
100
+ - `dump` → dump database
101
+ - `readRows` → query with conditions
102
+ - `write` → persist data
103
+ - `tableExists` → check table
104
+ - `createOrExtendTable` → schema operations
105
+
106
+ ### 4. IoPeerBridge (`io-peer-bridge.ts`)
107
+
108
+ Server-side handler that bridges socket events to Io operations.
109
+
110
+ ```
111
+ ┌─────────────────────────────────────┐
112
+ │ IoPeerBridge │
113
+ ├─────────────────────────────────────┤
114
+ │ Socket Event → Io Method │
115
+ │ │
116
+ │ 'dump' → io.dump() │
117
+ │ 'readRows' → io.readRows()│
118
+ │ 'write' → io.write() │
119
+ │ 'tableExists' → io.tableExists()│
120
+ │ 'createOrExtendTable' → io.createOrExtendTable()│
121
+ └─────────────────────────────────────┘
122
+ ```
123
+
124
+ **Responsibilities:**
125
+
126
+ - Register socket event listeners
127
+ - Forward requests to underlying Io
128
+ - Send responses via acknowledgment callbacks
129
+ - Handle errors and propagate to client
130
+
131
+ ### 5. IoMulti (`io-multi.ts`)
132
+
133
+ Aggregates multiple Io instances with priority-based cascading.
134
+
135
+ ```
136
+ ┌────────────────────────────────────────────┐
137
+ │ IoMulti │
138
+ ├────────────────────────────────────────────┤
139
+ │ Readables (Priority 1, 2, 3...) │
140
+ │ ┌────┐ ┌────┐ ┌────┐ │
141
+ │ │ Io │→ │ Io │→ │ Io │ (Cascade) │
142
+ │ └────┘ └────┘ └────┘ │
143
+ │ │
144
+ │ Writables (All receive writes) │
145
+ │ ┌────┐ ┌────┐ │
146
+ │ │ Io │ │ Io │ (Parallel) │
147
+ │ └────┘ └────┘ │
148
+ └────────────────────────────────────────────┘
149
+ ```
150
+
151
+ **Configuration:**
152
+
153
+ ```typescript
154
+ IoMultiIo {
155
+ io: Io // Underlying Io instance
156
+ priority: number // Lower = higher priority
157
+ read: boolean // Include in read operations
158
+ write: boolean // Include in write operations
159
+ dump: boolean // Use for dump operations
160
+ }
161
+ ```
162
+
163
+ **Read Behavior:**
164
+
165
+ - Query readables in priority order (lowest number first)
166
+ - Stop at first successful response with data
167
+ - If table exists but has 0 rows, continue cascade
168
+ - Merge results from multiple sources if needed
169
+
170
+ **Write Behavior:**
171
+
172
+ - Write to all writables in parallel
173
+ - Hot-swap cache: Successful reads written back to higher priority writables
174
+
175
+ **Use Cases:**
176
+
177
+ - Local cache + remote database
178
+ - Primary + fallback databases
179
+ - Multi-tier data architecture
180
+
181
+ ### 6. IoDbNameMapping (`io-db-name-mapping.ts`)
182
+
183
+ Provides name mapping between different table name formats.
184
+
185
+ ```
186
+ ┌──────────────────────────────────────┐
187
+ │ IoDbNameMapping │
188
+ ├──────────────────────────────────────┤
189
+ │ 'user_accounts' ↔ 'userAccounts' │
190
+ │ 'order_items' ↔ 'orderItems' │
191
+ └──────────────────────────────────────┘
192
+
193
+
194
+ ┌──────────┐
195
+ │ Io │
196
+ └──────────┘
197
+ ```
198
+
199
+ **Use Case:** Bridging between snake_case database names and camelCase application names.
200
+
201
+ ### 7. IoServer (`io-server.ts`)
202
+
203
+ Server implementation that combines Socket.IO with Io backends.
204
+
205
+ ```
206
+ ┌─────────────────────────────────────┐
207
+ │ IoServer │
208
+ ├─────────────────────────────────────┤
209
+ │ Socket.IO Server │
210
+ │ │ │
211
+ │ ▼ │
212
+ │ ┌─────────────────┐ │
213
+ │ │ IoPeerBridge │ │
214
+ │ └────────┬────────┘ │
215
+ │ │ │
216
+ │ ▼ │
217
+ │ ┌────────┐ │
218
+ │ │ Io │ │
219
+ │ └────────┘ │
220
+ └─────────────────────────────────────┘
221
+ ```
222
+
223
+ **Features:**
224
+
225
+ - Manages multiple client connections
226
+ - Each client gets its own IoPeerBridge
227
+ - Can serve shared or isolated Io instances
228
+
229
+ ## Data Flow
230
+
231
+ ### Write Operation
232
+
233
+ ```
234
+ Client Code
235
+
236
+
237
+ io.write(data)
238
+
239
+ ├──── IoMem ──► Store in _mem object
240
+
241
+ ├──── IoPeer ──► Emit 'write' event ──► IoPeerBridge ──► io.write()
242
+
243
+ └──── IoMulti ──► Parallel write to all writables
244
+
245
+ ├──► Cache (IoMem)
246
+ └──► Remote (IoPeer)
247
+ ```
248
+
249
+ ### Read Operation (IoMulti Cascade)
250
+
251
+ ```
252
+ io.readRows({table: 'users', where: {id: 1}})
253
+
254
+
255
+ Priority 1: Cache (IoMem)
256
+ ├──► Has table, has data ──► Return immediately ✓
257
+ ├──► Has table, no data ──► Continue cascade →
258
+ └──► No table ──► Continue cascade →
259
+
260
+ Priority 2: Remote (IoPeer)
261
+ ├──► Has table, has data ──► Return + Write to cache ✓
262
+ ├──► Has table, no data ──► Continue cascade →
263
+ └──► No table ──► Continue cascade →
264
+
265
+ Priority 3+: Additional sources...
266
+
267
+ No data found anywhere ──► Return empty result or error
268
+ ```
269
+
270
+ ### Bug Fix: Empty Table Cascade (v0.0.65)
271
+
272
+ **Problem:** IoMulti stopped querying after finding a readable with the table, even if it returned 0 rows.
273
+
274
+ **Solution:** Removed early return condition. Now continues cascade when `tableExistsAny=true` but `rows.size=0`.
275
+
276
+ ```typescript
277
+ // Before (Bug):
278
+ if (!tableExistsAny) {
279
+ throw new Error("Table not found");
280
+ } else {
281
+ return rljson; // ❌ Stops even with 0 rows
282
+ }
283
+
284
+ // After (Fixed):
285
+ if (!tableExistsAny) {
286
+ throw new Error("Table not found");
287
+ }
288
+ // Continue loop if rows.size === 0 ✓
289
+ return rljson; // Only after loop completes
290
+ ```
291
+
292
+ ## Socket Communication
293
+
294
+ ### Event-Based Protocol
295
+
296
+ ```
297
+ Client (IoPeer) Server (IoPeerBridge)
298
+ │ │
299
+ ├─── emit('readRows', request) ────►│
300
+ │ │
301
+ │ [Process Request]
302
+ │ │
303
+ │ io.readRows()
304
+ │ │
305
+ │◄──── ack(result, error) ──────────┤
306
+ │ │
307
+ Resolve Return
308
+ Promise Result
309
+ ```
310
+
311
+ ### Socket Implementations
312
+
313
+ **SocketMock (`socket-mock.ts`):**
314
+
315
+ - Single-socket mock for unit testing
316
+ - Stores listeners locally
317
+ - `emit()` triggers local listeners
318
+
319
+ **DirectionalSocketMock (`directional-socket-mock.ts`):**
320
+
321
+ - Bidirectional socket pair
322
+ - `emit()` sends to peer socket (not local)
323
+ - Critical for client-server testing
324
+ - Supports full EventEmitter API:
325
+ - `on()`, `once()`, `off()`
326
+ - `removeAllListeners()`
327
+ - `listenerCount()`, `listeners()`, `eventNames()`
328
+
329
+ **PeerSocketMock (`peer-socket-mock.ts`):**
330
+
331
+ - Pre-configured pair for IoPeer/IoPeerBridge testing
332
+ - Simulates real Socket.IO behavior
333
+ - Automatic connection handling
334
+
335
+ ## Testing Utilities
336
+
337
+ ### IoTestSetup (`io-test-setup.ts`)
338
+
339
+ Standard interface for test fixtures:
340
+
341
+ ```typescript
342
+ interface IoTestSetup {
343
+ io: Io;
344
+ before(): Promise<void>; // Initialize
345
+ after(): Promise<void>; // Cleanup
346
+ }
347
+ ```
348
+
349
+ ### Test Patterns
350
+
351
+ ```typescript
352
+ // Unit Test (IoMem)
353
+ const io = new IoMem();
354
+ await io.init();
355
+ // ... test operations
356
+ await io.close();
357
+
358
+ // Integration Test (IoPeer + Bridge)
359
+ const [clientSocket, serverSocket] = PeerSocketMock.createPeerSocketPair();
360
+ const serverIo = new IoMem();
361
+ await serverIo.init();
362
+
363
+ const bridge = new IoPeerBridge(serverSocket, serverIo);
364
+ bridge.start();
365
+
366
+ const clientIo = new IoPeer(clientSocket);
367
+ await clientIo.init();
368
+ // ... test client operations against server
369
+ ```
370
+
371
+ ## IoTools (`io-tools.ts`)
372
+
373
+ Utility functions for working with Io instances:
374
+
375
+ - **Type merging**: Combine type definitions from multiple sources
376
+ - **Data validation**: Verify RLJSON structure
377
+ - **Table configuration**: Extract and manipulate TableCfg
378
+ - **Content type detection**: Determine table content types
379
+
380
+ ## Design Patterns
381
+
382
+ ### 1. Interface Segregation
383
+
384
+ All implementations conform to single `Io` interface, making them interchangeable.
385
+
386
+ ### 2. Composite Pattern
387
+
388
+ `IoMulti` composes multiple Io instances into unified interface.
389
+
390
+ ### 3. Bridge Pattern
391
+
392
+ `IoPeerBridge` bridges socket events to Io operations.
393
+
394
+ ### 4. Proxy Pattern
395
+
396
+ `IoDbNameMapping` proxies requests with name translation.
397
+
398
+ ### 5. Strategy Pattern
399
+
400
+ Different Io implementations provide different storage strategies.
401
+
402
+ ### 6. Observer Pattern
403
+
404
+ Socket-based implementations use event observers for async communication.
405
+
406
+ ## Error Handling
407
+
408
+ ### Standard Error Flows
409
+
410
+ ```typescript
411
+ // Synchronous errors → Rejected promises
412
+ try {
413
+ await io.readRows({table: 'users', where: {}});
414
+ } catch (error) {
415
+ // Handle table not found, connection errors, etc.
416
+ }
417
+
418
+ // Socket errors → Callback error parameter
419
+ socket.emit('readRows', request, (result, error) => {
420
+ if (error) {
421
+ // Handle remote errors
422
+ }
423
+ });
424
+ ```
425
+
426
+ ### IoMulti Error Strategy
427
+
428
+ - Collect errors from all sources
429
+ - Filter out generic "table not found" errors if table exists anywhere
430
+ - Throw most specific error available
431
+ - If no data found anywhere but table exists: Return empty result
432
+
433
+ ## Performance Considerations
434
+
435
+ ### IoMem
436
+
437
+ - **Pros:** Instant access, no I/O overhead
438
+ - **Cons:** Memory limited, no persistence
439
+ - **Best For:** Testing, caching, temporary data
440
+
441
+ ### IoPeer
442
+
443
+ - **Pros:** Distributed, scalable, persistent
444
+ - **Cons:** Network latency, connection overhead
445
+ - **Best For:** Client-server apps, microservices
446
+
447
+ ### IoMulti
448
+
449
+ - **Pros:** Combines benefits of multiple sources, hot-swap caching
450
+ - **Cons:** Overhead of managing multiple instances
451
+ - **Best For:** Production apps needing performance + reliability
452
+
453
+ ## Version History
454
+
455
+ ### v0.0.65
456
+
457
+ - **Fix:** IoMulti.readRows() cascade now continues when table exists but returns 0 rows
458
+ - **Feature:** Added DirectionalSocketMock with full EventEmitter API
459
+ - **Breaking:** Socket.off() listener parameter is now optional
460
+
461
+ ### Earlier Versions
462
+
463
+ See [CHANGELOG.md](CHANGELOG.md) for complete history.
464
+
465
+ ## Future Architecture
466
+
467
+ ### Potential Enhancements
468
+
469
+ - Connection pooling for IoPeer
470
+ - Transaction support across IoMulti
471
+ - Query optimization and caching strategies
472
+ - Streaming support for large datasets
473
+ - Compression for socket communication
474
+ - Authentication and authorization layers