@rljson/server 0.0.5 → 0.0.6
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 +247 -11
- package/README.md +11 -3
- package/README.public.md +401 -10
- package/dist/README.architecture.md +247 -11
- package/dist/README.md +11 -3
- package/dist/README.public.md +401 -10
- package/dist/client.d.ts +70 -1
- package/dist/index.d.ts +6 -0
- package/dist/logger.d.ts +115 -0
- package/dist/server.d.ts +141 -4
- package/dist/server.js +809 -101
- package/package.json +17 -17
|
@@ -167,9 +167,14 @@ The `Server` class acts as a central coordination point that:
|
|
|
167
167
|
|
|
168
168
|
**Lifecycle and controls:**
|
|
169
169
|
|
|
170
|
-
- `addSocket()` attaches a stable `__clientId`, builds `IoPeer`/`BsPeer
|
|
171
|
-
-
|
|
170
|
+
- `addSocket()` attaches a stable `__clientId`, builds `IoPeer`/`BsPeer` (guarded by `peerInitTimeoutMs`), queues them, rebuilds multis once, refreshes servers in a batch, and registers an auto-disconnect handler.
|
|
171
|
+
- `removeSocket(clientId)` removes a client’s listeners and peers, rebuilds multis, and re-establishes multicast for remaining clients.
|
|
172
|
+
- `tearDown()` stops the eviction timer, removes all listeners/disconnect handlers, clears clients, closes IoMulti, and resets all internal state.
|
|
173
|
+
- Multicast uses `__origin` markers plus a **two-generation ref set** (`_multicastedRefsCurrent` / `_multicastedRefsPrevious`) to avoid echo loops and duplicate ref forwarding. Refs are evicted on a configurable interval (`refEvictionIntervalMs`, default 60 s) to prevent unbounded memory growth.
|
|
172
174
|
- Pending sockets are refreshed together so multiple joins trigger a single multi rebuild.
|
|
175
|
+
- All lifecycle events, errors, and traffic are logged via the injected `ServerLogger` (defaults to `NoopLogger`).
|
|
176
|
+
- Traffic logging captures inbound refs from clients and outbound multicasts with `from`/`to` client IDs.
|
|
177
|
+
- Disconnected sockets are auto-detected: a `'disconnect'` listener triggers `removeSocket()`, cleaning up dead peers and rebuilding multis.
|
|
173
178
|
|
|
174
179
|
### 3. Multi-Layer Priority System
|
|
175
180
|
|
|
@@ -637,9 +642,9 @@ await server.createTables({ withInsertHistory: [cakeCfg] });
|
|
|
637
642
|
await clientA.createTables({ withInsertHistory: [cakeCfg] });
|
|
638
643
|
await clientB.createTables({ withInsertHistory: [cakeCfg] });
|
|
639
644
|
|
|
640
|
-
//
|
|
641
|
-
const dbA =
|
|
642
|
-
const dbB =
|
|
645
|
+
// When route was passed to Client constructor, Db is available directly:
|
|
646
|
+
const dbA = clientA.db!;
|
|
647
|
+
const dbB = clientB.db!;
|
|
643
648
|
|
|
644
649
|
// Client A: Insert data (stores locally)
|
|
645
650
|
const route = Route.fromFlat('carCake');
|
|
@@ -708,8 +713,9 @@ await server.createTables({ withInsertHistory: [treeCfg] });
|
|
|
708
713
|
await clientA.createTables({ withInsertHistory: [treeCfg] });
|
|
709
714
|
await clientB.createTables({ withInsertHistory: [treeCfg] });
|
|
710
715
|
|
|
711
|
-
|
|
712
|
-
const
|
|
716
|
+
// When route was passed to Client constructor, Db is available directly:
|
|
717
|
+
const dbA = clientA.db!;
|
|
718
|
+
const dbB = clientB.db!;
|
|
713
719
|
|
|
714
720
|
// Client A: Create tree from object
|
|
715
721
|
const projectData = {
|
|
@@ -991,11 +997,27 @@ Application configuration distribution:
|
|
|
991
997
|
### Client Initialization
|
|
992
998
|
|
|
993
999
|
```typescript
|
|
994
|
-
|
|
995
|
-
|
|
1000
|
+
// With route: Db and Connector are created automatically during init()
|
|
1001
|
+
const client = new Client(socket, localIo, localBs, route);
|
|
1002
|
+
await client.init(); // Sets up IoMulti, BsMulti, Db, and Connector
|
|
996
1003
|
await client.ready(); // Waits for IoMulti to be ready
|
|
997
1004
|
|
|
998
|
-
const db =
|
|
1005
|
+
const db = client.db!; // Db wrapping IoMulti
|
|
1006
|
+
const connector = client.connector!; // Connector wired to route + socket
|
|
1007
|
+
|
|
1008
|
+
// With logging:
|
|
1009
|
+
import { BufferedLogger } from '@rljson/server';
|
|
1010
|
+
const logger = new BufferedLogger();
|
|
1011
|
+
const client = new Client(socket, localIo, localBs, route, { logger });
|
|
1012
|
+
await client.init();
|
|
1013
|
+
// logger.entries now contains lifecycle events:
|
|
1014
|
+
// Constructing client, Initializing client, Setting up Io multi,
|
|
1015
|
+
// Io peer bridge started, Io multi ready, Setting up Bs multi, ...
|
|
1016
|
+
|
|
1017
|
+
// Without route (legacy): only IoMulti and BsMulti are created
|
|
1018
|
+
const client = new Client(socket, localIo, localBs);
|
|
1019
|
+
await client.init();
|
|
1020
|
+
const db = new Db(client.io!); // Caller creates Db manually
|
|
999
1021
|
```
|
|
1000
1022
|
|
|
1001
1023
|
### Server Initialization
|
|
@@ -1018,10 +1040,12 @@ When `server.addSocket(socket)` is called:
|
|
|
1018
1040
|
4. **Refresh servers**: Update IoServer/BsServer with new multis
|
|
1019
1041
|
5. **Setup multicast**: Register listeners for route broadcasting
|
|
1020
1042
|
|
|
1043
|
+
Each step is logged at `info` level. Errors in any step are logged at `error` level and re-thrown.
|
|
1044
|
+
|
|
1021
1045
|
### Teardown
|
|
1022
1046
|
|
|
1023
1047
|
```typescript
|
|
1024
|
-
await client.tearDown(); // Closes IoMulti, clears state
|
|
1048
|
+
await client.tearDown(); // Closes IoMulti, clears Db, Connector, and all state
|
|
1025
1049
|
```
|
|
1026
1050
|
|
|
1027
1051
|
## Testing Patterns
|
|
@@ -1116,6 +1140,214 @@ await expect(dbB.get(route, {})).rejects.toThrow();
|
|
|
1116
1140
|
- **@rljson/db**: Db operations (insert, get, join, etc.)
|
|
1117
1141
|
- **@rljson/rljson**: Data structures (Route, TableCfg, etc.)
|
|
1118
1142
|
|
|
1143
|
+
## Observability
|
|
1144
|
+
|
|
1145
|
+
### Structured Logging
|
|
1146
|
+
|
|
1147
|
+
Both `Server` and `Client` accept an optional `ServerLogger` via their options parameter. The logger is called at every significant lifecycle point, error boundary, and network traffic event.
|
|
1148
|
+
|
|
1149
|
+
**Logger interface:**
|
|
1150
|
+
|
|
1151
|
+
```typescript
|
|
1152
|
+
interface ServerLogger {
|
|
1153
|
+
info(source: string, message: string, data?: Record<string, unknown>): void;
|
|
1154
|
+
warn(source: string, message: string, data?: Record<string, unknown>): void;
|
|
1155
|
+
error(source: string, message: string, error?: unknown, data?: Record<string, unknown>): void;
|
|
1156
|
+
traffic(direction: 'in' | 'out', source: string, event: string, data?: Record<string, unknown>): void;
|
|
1157
|
+
}
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
**What gets logged:**
|
|
1161
|
+
|
|
1162
|
+
| Phase | Source | Level | Events |
|
|
1163
|
+
| --------------- | ------------------------- | ------- | ------------------------------------------- |
|
|
1164
|
+
| Construction | `Server` / `Client` | info | Route, options |
|
|
1165
|
+
| Initialization | `Server` / `Client` | info | Start, success |
|
|
1166
|
+
| Io/Bs setup | `Client.Io` / `Client.Bs` | info | Multi creation, peer bridges, peer creation |
|
|
1167
|
+
| Peer creation | `Server.Io` / `Server.Bs` | info | Per-client peer setup |
|
|
1168
|
+
| Multi rebuild | `Server` | info | Peer count, rebuild success |
|
|
1169
|
+
| Server refresh | `Server` | info | Pending socket count, completion |
|
|
1170
|
+
| Multicast in | `Server.Multicast` | traffic | Ref, sender clientId |
|
|
1171
|
+
| Multicast out | `Server.Multicast` | traffic | Ref, sender clientId, receiver clientId |
|
|
1172
|
+
| Duplicate ref | `Server.Multicast` | warn | Ref, sender |
|
|
1173
|
+
| Loop prevention | `Server.Multicast` | warn | Ref, origin, sender |
|
|
1174
|
+
| Any failure | Various | error | Error object, context data |
|
|
1175
|
+
| TearDown | `Client` | info | Start, completion |
|
|
1176
|
+
| Socket removal | `Server` | info | Removing, rebuilding multis, removal done |
|
|
1177
|
+
| Server tearDown | `Server` | info | Tearing down, timer stop, completion |
|
|
1178
|
+
| Disconnect | `Server` | info | Client disconnected, auto-removal |
|
|
1179
|
+
|
|
1180
|
+
**Built-in implementations:**
|
|
1181
|
+
|
|
1182
|
+
- `NoopLogger` — zero overhead, used by default
|
|
1183
|
+
- `ConsoleLogger` — `console.log`/`warn`/`error` with formatted prefixes
|
|
1184
|
+
- `BufferedLogger` — in-memory array with `byLevel()`, `bySource()`, `clear()` helpers
|
|
1185
|
+
- `FilteredLogger` — wraps another logger, filters by `levels` and/or `sources`
|
|
1186
|
+
|
|
1187
|
+
**Production recommendation:** Use `FilteredLogger` wrapping your framework's logger, filtering to `['error', 'warn']` levels. Enable `traffic` level only for debugging multicast issues.
|
|
1188
|
+
|
|
1189
|
+
## Sync Protocol (opt-in hardening)
|
|
1190
|
+
|
|
1191
|
+
The server supports an optional sync protocol that provides production-grade guarantees on top of the basic multicast mechanism. Enabled by passing `syncConfig` in `ServerOptions`.
|
|
1192
|
+
|
|
1193
|
+
### Architecture
|
|
1194
|
+
|
|
1195
|
+
```text
|
|
1196
|
+
Client A (Connector) Server Client B (Connector)
|
|
1197
|
+
──────────────────── ────── ────────────────────
|
|
1198
|
+
send(ref) →
|
|
1199
|
+
enriches payload:
|
|
1200
|
+
{o, r, c?, t?, seq?, p?}
|
|
1201
|
+
────emit(route)───►
|
|
1202
|
+
┌─ append to ref log
|
|
1203
|
+
├─ setup ACK collection
|
|
1204
|
+
├─ forward to Client B ──emit(route)──►
|
|
1205
|
+
│ processIncoming()
|
|
1206
|
+
│ ◄──ackClient──
|
|
1207
|
+
├─ collect ackClient
|
|
1208
|
+
├─ emit aggregated ACK
|
|
1209
|
+
◄───ack────────────┘
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
### Wire format reference
|
|
1213
|
+
|
|
1214
|
+
All sync payloads are JSON objects. The types are defined in `@rljson/rljson` (Layer 0) and used unchanged across all layers.
|
|
1215
|
+
|
|
1216
|
+
#### ConnectorPayload (bidirectional, event: `${route}`)
|
|
1217
|
+
|
|
1218
|
+
The main wire message between Connector and Server. Two required fields provide backward compatibility; optional fields activate based on `SyncConfig` flags.
|
|
1219
|
+
|
|
1220
|
+
| Field | Type | Required | Activated by | Purpose |
|
|
1221
|
+
| ------- | ----------------------- | -------- | ----------------------- | ---------------------------------------------------- |
|
|
1222
|
+
| `r` | `string` | ✅ | always | The ref (InsertHistory timeId) being announced |
|
|
1223
|
+
| `o` | `string` | ✅ | always | Ephemeral origin of the sender (self-echo filtering) |
|
|
1224
|
+
| `c` | `ClientId` | ❌ | `includeClientIdentity` | Stable client identity (survives reconnections) |
|
|
1225
|
+
| `t` | `number` | ❌ | `includeClientIdentity` | Client-side wall-clock timestamp (ms since epoch) |
|
|
1226
|
+
| `seq` | `number` | ❌ | `causalOrdering` | Monotonic counter per (client, route) pair |
|
|
1227
|
+
| `p` | `InsertHistoryTimeId[]` | ❌ | `causalOrdering` | Causal predecessor timeIds |
|
|
1228
|
+
| `cksum` | `string` | ❌ | — | Content checksum for ACK verification |
|
|
1229
|
+
|
|
1230
|
+
Minimal payload (no SyncConfig): `{ o: "...", r: "..." }`
|
|
1231
|
+
|
|
1232
|
+
Full payload (all flags): `{ o, r, c, t, seq, p }`
|
|
1233
|
+
|
|
1234
|
+
#### AckPayload (Server → Client, event: `${route}:ack`)
|
|
1235
|
+
|
|
1236
|
+
| Field | Type | Required | Purpose |
|
|
1237
|
+
| -------------- | --------- | -------- | ------------------------------------------------------------- |
|
|
1238
|
+
| `r` | `string` | ✅ | The ref being acknowledged |
|
|
1239
|
+
| `ok` | `boolean` | ✅ | `true` if all clients confirmed; `false` on timeout / partial |
|
|
1240
|
+
| `receivedBy` | `number` | ❌ | Count of clients that confirmed receipt |
|
|
1241
|
+
| `totalClients` | `number` | ❌ | Total receiver clients at broadcast time |
|
|
1242
|
+
|
|
1243
|
+
#### GapFillRequest (Client → Server, event: `${route}:gapfill:req`)
|
|
1244
|
+
|
|
1245
|
+
| Field | Type | Required | Purpose |
|
|
1246
|
+
| ------------- | --------------------- | -------- | ------------------------------------------ |
|
|
1247
|
+
| `route` | `string` | ✅ | The route for which refs are missing |
|
|
1248
|
+
| `afterSeq` | `number` | ✅ | Last seq the client successfully processed |
|
|
1249
|
+
| `afterTimeId` | `InsertHistoryTimeId` | ❌ | Alternative anchor if seq unavailable |
|
|
1250
|
+
|
|
1251
|
+
#### GapFillResponse (Server → Client, event: `${route}:gapfill:res`)
|
|
1252
|
+
|
|
1253
|
+
| Field | Type | Required | Purpose |
|
|
1254
|
+
| ------- | -------------------- | -------- | ----------------------------------------------- |
|
|
1255
|
+
| `route` | `string` | ✅ | The route this response corresponds to |
|
|
1256
|
+
| `refs` | `ConnectorPayload[]` | ✅ | Ordered list of missing payloads (oldest first) |
|
|
1257
|
+
|
|
1258
|
+
#### Event name derivation
|
|
1259
|
+
|
|
1260
|
+
All event names are route-specific, derived by `syncEvents(route)` from `@rljson/rljson`:
|
|
1261
|
+
|
|
1262
|
+
| Property | Derived name | Direction |
|
|
1263
|
+
| ------------ | ------------------------ | --------------- |
|
|
1264
|
+
| `ref` | `"${route}"` | Bidirectional |
|
|
1265
|
+
| `ack` | `"${route}:ack"` | Server → Client |
|
|
1266
|
+
| `ackClient` | `"${route}:ack:client"` | Client → Server |
|
|
1267
|
+
| `gapFillReq` | `"${route}:gapfill:req"` | Client → Server |
|
|
1268
|
+
| `gapFillRes` | `"${route}:gapfill:res"` | Server → Client |
|
|
1269
|
+
| `bootstrap` | `"${route}:bootstrap"` | Server → Client |
|
|
1270
|
+
|
|
1271
|
+
#### SyncConfig flag activation matrix
|
|
1272
|
+
|
|
1273
|
+
| SyncConfig flag | Payload fields activated | Events activated |
|
|
1274
|
+
| ----------------------- | ------------------------------ | ------------------------------ |
|
|
1275
|
+
| _(none / default)_ | `o`, `r` | `${route}` only |
|
|
1276
|
+
| `causalOrdering` | + `seq`, `p` | + `gapfill:req`, `gapfill:res` |
|
|
1277
|
+
| `requireAck` | _(no extra fields)_ | + `ack`, `ack:client` |
|
|
1278
|
+
| `includeClientIdentity` | + `c`, `t` | _(no extra events)_ |
|
|
1279
|
+
| All flags combined | `o`, `r`, `c`, `t`, `seq`, `p` | All 6 events |
|
|
1280
|
+
| `maxDedupSetSize` | _(Connector-only setting)_ | _(no events)_ |
|
|
1281
|
+
| `bootstrapHeartbeatMs` | _(no extra fields)_ | + `bootstrap` (periodic) |
|
|
1282
|
+
|
|
1283
|
+
#### ClientId format
|
|
1284
|
+
|
|
1285
|
+
A `ClientId` is a `"client_"` prefix followed by a 12-character nanoid (e.g. `client_V1StGXR8_Z5j`). Unlike the ephemeral `origin` (which changes per Connector instantiation), a `ClientId` persists across reconnections and should be stored by the application.
|
|
1286
|
+
|
|
1287
|
+
### Ref log (ring buffer)
|
|
1288
|
+
|
|
1289
|
+
The server maintains a bounded ring buffer of recent `ConnectorPayload` entries. When the buffer exceeds `refLogSize` (default: 1000), the oldest entry is dropped. The ref log serves as the data source for gap-fill responses.
|
|
1290
|
+
|
|
1291
|
+
### ACK aggregation
|
|
1292
|
+
|
|
1293
|
+
When `requireAck` is enabled:
|
|
1294
|
+
|
|
1295
|
+
1. **Before broadcast**: The server registers `ackClient` listeners on all receiver sockets.
|
|
1296
|
+
2. **During broadcast**: Payloads are forwarded to all other clients.
|
|
1297
|
+
3. **After broadcast**: The server waits for individual `ackClient` events from each receiver.
|
|
1298
|
+
4. **On completion or timeout**: An aggregated `AckPayload` is emitted back to the sender on the `ack` event.
|
|
1299
|
+
|
|
1300
|
+
The ACK includes `receivedBy` (count of confirmed receivers) and `totalClients` (total receiver count). If all receivers confirm, `ok: true`; if timeout fires first, `ok: false`.
|
|
1301
|
+
|
|
1302
|
+
### Gap-fill responder
|
|
1303
|
+
|
|
1304
|
+
When `causalOrdering` is enabled:
|
|
1305
|
+
|
|
1306
|
+
1. The server listens for `gapfill:req` events from each client.
|
|
1307
|
+
2. On request, it filters the ref log for payloads with `seq > afterSeq`.
|
|
1308
|
+
3. The matching payloads are sent back on the `gapfill:res` event.
|
|
1309
|
+
|
|
1310
|
+
### Bootstrap (late joiner support)
|
|
1311
|
+
|
|
1312
|
+
The server tracks the most recent ref seen on `_latestRef` (updated in `_multicastRefs` on every broadcast). This enables two mechanisms:
|
|
1313
|
+
|
|
1314
|
+
**Immediate bootstrap on connect:**
|
|
1315
|
+
|
|
1316
|
+
When `addSocket()` completes, the server calls `_sendBootstrap(ioDown)` which emits a `ConnectorPayload` with `o: '__server__'` and `r: _latestRef` on the `${route}:bootstrap` event. The Connector's `_registerBootstrapHandler()` feeds this into `_processIncoming()`, triggering listen callbacks and applying dedup automatically.
|
|
1317
|
+
|
|
1318
|
+
**Periodic heartbeat (optional):**
|
|
1319
|
+
|
|
1320
|
+
When `bootstrapHeartbeatMs > 0` in `SyncConfig`, `_startBootstrapHeartbeat()` starts an interval timer that calls `_broadcastBootstrapHeartbeat()` to emit the latest ref to all connected clients. The timer calls `.unref()` so it doesn't keep the process alive. `tearDown()` clears the timer.
|
|
1321
|
+
|
|
1322
|
+
```text
|
|
1323
|
+
addSocket(socketB)
|
|
1324
|
+
│
|
|
1325
|
+
├─ setup IoPeer, BsPeer, multicast listeners
|
|
1326
|
+
├─ _sendBootstrap(ioDown) → emit(bootstrap, { o: '__server__', r: latestRef })
|
|
1327
|
+
└─ _startBootstrapHeartbeat() → setInterval(broadcastBootstrapHeartbeat, ms)
|
|
1328
|
+
```
|
|
1329
|
+
|
|
1330
|
+
**Design decisions:**
|
|
1331
|
+
|
|
1332
|
+
- `_events` is always initialized (even without `syncConfig`) because bootstrap needs event names regardless of sync config
|
|
1333
|
+
- Bootstrap uses a dedicated event (`${route}:bootstrap`) rather than the main `${route}` event to avoid interfering with multicast payload processing
|
|
1334
|
+
- The `'__server__'` origin ensures no Connector treats bootstrap as a self-echo
|
|
1335
|
+
|
|
1336
|
+
### Event registration lifecycle
|
|
1337
|
+
|
|
1338
|
+
- `_multicastRefs()` sets up all sync listeners (ref, ackClient, gapFillReq) per client.
|
|
1339
|
+
- `_removeAllListeners()` tears down all sync listeners (route, ackClient, gapFillReq).
|
|
1340
|
+
- `addSocket()` and `removeSocket()` trigger rebuild of all listeners.
|
|
1341
|
+
- `tearDown()` clears the ref log in addition to existing cleanup.
|
|
1342
|
+
|
|
1343
|
+
### Client-side integration
|
|
1344
|
+
|
|
1345
|
+
The `Client` class accepts `syncConfig`, `clientIdentity`, and `peerInitTimeoutMs` in `ClientOptions`.
|
|
1346
|
+
|
|
1347
|
+
- **`peerInitTimeoutMs`** (default 30 s, 0 = disable): Guards `IoPeer` and `BsPeer` initialization during `init()` with a `Promise.race`-based timeout. If the server is unreachable, `init()` rejects cleanly instead of hanging indefinitely. Uses the same `_withTimeout()` pattern as the server.
|
|
1348
|
+
- **`syncConfig`** + **`clientIdentity`**: When a route is provided, these are passed through to the `Connector` constructor, activating enriched payloads (sequence numbers, causal ordering, client identity) on the client side.
|
|
1349
|
+
- **`tearDown()`**: Calls `connector.tearDown()` to remove all socket listeners before clearing internal references. This prevents leaked listeners that would keep the socket alive after the client is disposed.
|
|
1350
|
+
|
|
1119
1351
|
## Future Considerations
|
|
1120
1352
|
|
|
1121
1353
|
- **Write replication**: Automatically sync writes to server
|
|
@@ -1123,3 +1355,7 @@ await expect(dbB.get(route, {})).rejects.toThrow();
|
|
|
1123
1355
|
- **Change detection**: Notify on data changes
|
|
1124
1356
|
- **Batch operations**: Optimize bulk transfers
|
|
1125
1357
|
- **Compression**: Reduce network payload size
|
|
1358
|
+
- **Authentication hooks**: Verify client identity in `addSocket()`
|
|
1359
|
+
- **Connection health introspection**: Query connected client state, connection time, etc.
|
|
1360
|
+
- **Backpressure / rate limiting**: Protect against misbehaving clients flooding multicast
|
|
1361
|
+
- **Metrics / counters**: Numeric counters (connected clients, refs/sec) for monitoring dashboards
|
package/dist/README.md
CHANGED
|
@@ -13,6 +13,10 @@ Local-first, pull-by-reference server layer for Rljson. Clients keep writes loca
|
|
|
13
13
|
- Writes stay local; reads cascade: local ➜ server ➜ peers
|
|
14
14
|
- References (hashes) flow; data is pulled on demand
|
|
15
15
|
- Server aggregates sockets and multicasts refs, but only stores what you explicitly import
|
|
16
|
+
- Graceful lifecycle: `tearDown()` for both Server and Client, automatic disconnect cleanup, `removeSocket()` for manual removal
|
|
17
|
+
- Configurable production defaults: ref eviction interval, peer init timeout (server and client)
|
|
18
|
+
- Structured logging via injectable `ServerLogger` (NoopLogger default, ConsoleLogger, BufferedLogger, FilteredLogger included)
|
|
19
|
+
- **Sync protocol**: Optional ACK aggregation, causal ordering with gap-fill, enriched payload forwarding via `SyncConfig`
|
|
16
20
|
|
|
17
21
|
## Quick start
|
|
18
22
|
|
|
@@ -49,11 +53,15 @@ import { BsMem } from '@rljson/bs';
|
|
|
49
53
|
import { IoMem } from '@rljson/io';
|
|
50
54
|
import { Client, SocketIoBridge } from '@rljson/server';
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
// Pass the same route as the server to get Db and Connector automatically
|
|
57
|
+
const route = Route.fromFlat('my.app');
|
|
58
|
+
const client = new Client(new SocketIoBridge(clientSocket), new IoMem(), new BsMem(), route);
|
|
53
59
|
await client.init();
|
|
54
60
|
|
|
55
|
-
const io = client.io;
|
|
56
|
-
const bs = client.bs;
|
|
61
|
+
const io = client.io; // IoMulti merged interface
|
|
62
|
+
const bs = client.bs; // BsMulti merged interface
|
|
63
|
+
const db = client.db; // Db (available when route provided)
|
|
64
|
+
const connector = client.connector; // Connector (available when route provided)
|
|
57
65
|
```
|
|
58
66
|
|
|
59
67
|
Run tests and lint:
|