@rljson/io 0.0.64 → 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.
- package/README.architecture.md +465 -0
- package/README.public.md +635 -3
- package/dist/README.architecture.md +465 -0
- package/dist/README.public.md +635 -3
- package/dist/directional-socket-mock.d.ts +44 -0
- package/dist/example.d.ts +15 -0
- package/dist/index.d.ts +1 -0
- package/dist/io.js +147 -0
- package/dist/io.js.map +1 -1
- package/dist/socket.d.ts +1 -1
- package/dist/src/example.ts +246 -1
- package/package.json +11 -11
package/README.architecture.md
CHANGED
|
@@ -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
|