@rljson/server 0.0.4 → 0.0.5
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 +1116 -0
- package/README.blog.md +9 -1
- package/README.contributors.md +47 -13
- package/README.md +70 -11
- package/README.public.md +204 -2
- package/README.trouble.md +50 -0
- package/dist/README.architecture.md +1116 -0
- package/dist/README.blog.md +9 -1
- package/dist/README.contributors.md +47 -13
- package/dist/README.md +70 -11
- package/dist/README.public.md +204 -2
- package/dist/README.trouble.md +50 -0
- package/dist/client.d.ts +6 -3
- package/dist/server.d.ts +7 -3
- package/dist/server.js +69 -37
- package/dist/socket-bundle.d.ts +16 -0
- package/package.json +11 -11
package/dist/README.blog.md
CHANGED
|
@@ -8,4 +8,12 @@ found in the LICENSE file in the root of this package.
|
|
|
8
8
|
|
|
9
9
|
# Blog
|
|
10
10
|
|
|
11
|
-
Add
|
|
11
|
+
Add posts as Markdown entries in this file (newest last). Keep each post small and link to source code or PRs when helpful. Template:
|
|
12
|
+
|
|
13
|
+
```md
|
|
14
|
+
## YYYY-MM-DD — Title
|
|
15
|
+
|
|
16
|
+
- What changed (1–3 bullets)
|
|
17
|
+
- Why it matters
|
|
18
|
+
- Links: PRs, docs, demos
|
|
19
|
+
```
|
|
@@ -8,25 +8,59 @@ found in the LICENSE file in the root of this package.
|
|
|
8
8
|
|
|
9
9
|
# Contributors Guide
|
|
10
10
|
|
|
11
|
-
- [
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
11
|
+
- [Prerequisites](#prerequisites)
|
|
12
|
+
- [Setup](#setup)
|
|
13
|
+
- [Everyday development](#everyday-development)
|
|
14
|
+
- [Publishing](#publishing)
|
|
15
|
+
- [More docs](#more-docs)
|
|
15
16
|
|
|
16
|
-
##
|
|
17
|
+
## Prerequisites
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
- Node.js v22.14.0+
|
|
20
|
+
- pnpm v10 (see [install-node-mac.md](doc/install-node-mac.md) or [install-node-win.md](doc/install-node-win.md))
|
|
21
|
+
- Optional: sibling repos `rljson-io`, `rljson-bs`, `rljson-db`, `rljson` if you prefer linking for local development
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
## Setup
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
```sh
|
|
26
|
+
pnpm install
|
|
27
|
+
```
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
By default we consume published npm versions. If you want to work against local packages instead, `pnpm link` them in sibling folders and add temporary overrides as needed.
|
|
25
30
|
|
|
26
|
-
##
|
|
31
|
+
## Everyday development
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
- Run tests + lint (default CI path):
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
```sh
|
|
36
|
+
pnpm test
|
|
37
|
+
```
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
- Lint only:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
pnpm lint
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- Build the package (emits dist, copies README):
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
pnpm build
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- Update goldens for snapshot-like tests:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
pnpm updateGoldens
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Publishing
|
|
58
|
+
|
|
59
|
+
`pnpm build` runs `pnpm test` via `prebuild`. `pnpm publish` (or npm publish) will trigger `prepublishOnly` and uses the built `dist` folder. Keep the changelog and versioning in sync with repo guidelines.
|
|
60
|
+
|
|
61
|
+
## More docs
|
|
62
|
+
|
|
63
|
+
- [doc/prepare.md](doc/prepare.md)
|
|
64
|
+
- [doc/develop.md](doc/develop.md)
|
|
65
|
+
- [doc/create-new-repo.md](doc/create-new-repo.md)
|
|
66
|
+
- [doc/fast-coding-guide.md](doc/fast-coding-guide.md)
|
package/dist/README.md
CHANGED
|
@@ -8,17 +8,76 @@ found in the LICENSE file in the root of this package.
|
|
|
8
8
|
|
|
9
9
|
# @rljson/server
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Local-first, pull-by-reference server layer for Rljson. Clients keep writes local, pull data on demand through multis, and let the server proxy references without duplicating client data.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
- Writes stay local; reads cascade: local ➜ server ➜ peers
|
|
14
|
+
- References (hashes) flow; data is pulled on demand
|
|
15
|
+
- Server aggregates sockets and multicasts refs, but only stores what you explicitly import
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Quick start
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
Install:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pnpm add @rljson/server
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Minimal server:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { BsMem } from '@rljson/bs';
|
|
29
|
+
import { IoMem } from '@rljson/io';
|
|
30
|
+
import { Route } from '@rljson/rljson';
|
|
31
|
+
import { Server, SocketIoBridge } from '@rljson/server';
|
|
32
|
+
|
|
33
|
+
const route = Route.fromFlat('my.app');
|
|
34
|
+
const serverIo = new IoMem();
|
|
35
|
+
await serverIo.init();
|
|
36
|
+
await serverIo.isReady();
|
|
37
|
+
|
|
38
|
+
const server = new Server(route, serverIo, new BsMem());
|
|
39
|
+
await server.init();
|
|
40
|
+
|
|
41
|
+
// When your runtime yields sockets, wrap them:
|
|
42
|
+
// await server.addSocket(new SocketIoBridge(serverSocket));
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Minimal client:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { BsMem } from '@rljson/bs';
|
|
49
|
+
import { IoMem } from '@rljson/io';
|
|
50
|
+
import { Client, SocketIoBridge } from '@rljson/server';
|
|
51
|
+
|
|
52
|
+
const client = new Client(new SocketIoBridge(clientSocket), new IoMem(), new BsMem());
|
|
53
|
+
await client.init();
|
|
54
|
+
|
|
55
|
+
const io = client.io; // IoMulti merged interface
|
|
56
|
+
const bs = client.bs; // BsMulti merged interface
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Run tests and lint:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
pnpm test
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Build distribution:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
pnpm build
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Documentation map
|
|
72
|
+
|
|
73
|
+
| Audience | File | Highlights |
|
|
74
|
+
| --------------- | ------------------------------------------------ | ------------------------------------------------- |
|
|
75
|
+
| Users | [README.public.md](README.public.md) | Install, usage, networking model, examples |
|
|
76
|
+
| Contributors | [README.contributors.md](README.contributors.md) | Setup, dev workflow, publishing, fast coding tips |
|
|
77
|
+
| Architecture | [README.architecture.md](README.architecture.md) | Deep dive into multis, peer bridges, data flows |
|
|
78
|
+
| Troubleshooting | [README.trouble.md](README.trouble.md) | Known issues and fixes |
|
|
79
|
+
| Blog | [README.blog.md](README.blog.md) | Writing and collecting project blog entries |
|
|
80
|
+
|
|
81
|
+
## Example code
|
|
82
|
+
|
|
83
|
+
See [src/example.ts](src/example.ts) for a runnable end-to-end demo and [test/server.spec.ts](test/server.spec.ts) for broader integration cases.
|
package/dist/README.public.md
CHANGED
|
@@ -10,6 +10,19 @@ found in the LICENSE file in the root of this package.
|
|
|
10
10
|
|
|
11
11
|
@rljson/server provides a lightweight client/server layer for Rljson storage. It wires Io (row/table data) and Bs (blob storage) over sockets so clients can read from server storage while still keeping their own local storage.
|
|
12
12
|
|
|
13
|
+
## Prerequisites
|
|
14
|
+
|
|
15
|
+
- Node.js v22.14.0 or newer
|
|
16
|
+
- A socket runtime (examples use Socket.IO)
|
|
17
|
+
- `Io`/`Bs` implementations (in-memory examples use `IoMem` and `BsMem`)
|
|
18
|
+
|
|
19
|
+
## Design pillars
|
|
20
|
+
|
|
21
|
+
- **Local-first, read-through**: Writes stay on the caller; reads walk priorities (local first, then peers via server).
|
|
22
|
+
- **Pull by reference**: Only references (hashes) travel over the wire; data is pulled on demand through `IoMulti`/`BsMulti`.
|
|
23
|
+
- **Server as proxy**: The server aggregates and multicasts refs, but does not duplicate client data unless you intentionally store it there.
|
|
24
|
+
- **Single abstraction surface**: `Client.io`/`Client.bs` and `Server.io`/`Server.bs` expose merged multis so you do not handle peers manually.
|
|
25
|
+
|
|
13
26
|
## What it does (quick overview)
|
|
14
27
|
|
|
15
28
|
- **Server** hosts Io + Bs and exposes them over sockets.
|
|
@@ -22,6 +35,50 @@ found in the LICENSE file in the root of this package.
|
|
|
22
35
|
pnpm add @rljson/server
|
|
23
36
|
```
|
|
24
37
|
|
|
38
|
+
## Quick start (Socket.IO example)
|
|
39
|
+
|
|
40
|
+
Server setup:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { createServer } from 'node:http';
|
|
44
|
+
import { Server as SocketIoServer } from 'socket.io';
|
|
45
|
+
import { BsMem } from '@rljson/bs';
|
|
46
|
+
import { IoMem } from '@rljson/io';
|
|
47
|
+
import { Route } from '@rljson/rljson';
|
|
48
|
+
import { Server, SocketIoBridge } from '@rljson/server';
|
|
49
|
+
|
|
50
|
+
const httpServer = createServer();
|
|
51
|
+
const socketIo = new SocketIoServer(httpServer);
|
|
52
|
+
|
|
53
|
+
const route = Route.fromFlat('my.app');
|
|
54
|
+
const server = new Server(route, new IoMem(), new BsMem());
|
|
55
|
+
await server.init();
|
|
56
|
+
|
|
57
|
+
socketIo.on('connection', async (socket) => {
|
|
58
|
+
await server.addSocket(new SocketIoBridge(socket));
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
httpServer.listen(0);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Client setup:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { io as socketIoClient } from 'socket.io-client';
|
|
68
|
+
import { BsMem } from '@rljson/bs';
|
|
69
|
+
import { IoMem } from '@rljson/io';
|
|
70
|
+
import { Db } from '@rljson/db';
|
|
71
|
+
import { Client, SocketIoBridge } from '@rljson/server';
|
|
72
|
+
|
|
73
|
+
const socket = socketIoClient('http://localhost:3000', { forceNew: true });
|
|
74
|
+
|
|
75
|
+
const client = new Client(new SocketIoBridge(socket), new IoMem(), new BsMem());
|
|
76
|
+
await client.init();
|
|
77
|
+
|
|
78
|
+
const db = new Db(client.io!);
|
|
79
|
+
// db.get/insert now cascade local ➜ server automatically
|
|
80
|
+
```
|
|
81
|
+
|
|
25
82
|
## Basic usage
|
|
26
83
|
|
|
27
84
|
### Server
|
|
@@ -46,7 +103,7 @@ await server.init();
|
|
|
46
103
|
// await server.addSocket(new SocketIoBridge(serverSocket));
|
|
47
104
|
```
|
|
48
105
|
|
|
49
|
-
### Client
|
|
106
|
+
### Client API
|
|
50
107
|
|
|
51
108
|
```ts
|
|
52
109
|
import { BsMem } from '@rljson/bs';
|
|
@@ -87,7 +144,7 @@ This is implemented with `IoMulti` and `BsMulti` internally, but the public API
|
|
|
87
144
|
- `io` – Io interface (multi-layer)
|
|
88
145
|
- `bs` – Bs interface (multi-layer)
|
|
89
146
|
|
|
90
|
-
### Server
|
|
147
|
+
### Server API
|
|
91
148
|
|
|
92
149
|
- `init()` – initializes server multis
|
|
93
150
|
- `ready()` – resolves when Io is ready
|
|
@@ -127,3 +184,148 @@ The same pattern is used for Bs (blob storage).
|
|
|
127
184
|
|
|
128
185
|
- `Client.io` and `Client.bs` are already merged interfaces. No need to access multis directly.
|
|
129
186
|
- `Server.addSocket()` batches refreshes to reduce rebuild overhead when multiple sockets connect.
|
|
187
|
+
- Multicast includes `__origin` markers plus a `_multicastedRefs` set to prevent ref echo loops.
|
|
188
|
+
|
|
189
|
+
## Architecture Overview
|
|
190
|
+
|
|
191
|
+
### Pull-Based Reference Architecture
|
|
192
|
+
|
|
193
|
+
@rljson/server implements a **pull-based architecture** where data is retrieved on-demand using content-addressed references (hashes), not automatically pushed between clients. This fundamentally differs from traditional sync systems:
|
|
194
|
+
|
|
195
|
+
### Key principle: references flow, data is pulled
|
|
196
|
+
|
|
197
|
+
```text
|
|
198
|
+
Reference Flow: Client A → Server → Client B (broadcast)
|
|
199
|
+
Data Flow: Client A ← Server ← Client B (pulled on query)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### How It Works
|
|
203
|
+
|
|
204
|
+
1. **Client A stores data locally** (writes to priority 1 layer)
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
const results = await db.insert(route, [data]);
|
|
208
|
+
const ref = results[0]._hash;
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
2. **Client A broadcasts reference** (not the data)
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
socket.emit(route.flat, ref);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
3. **Client B receives reference** via multicast
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
socket.on(route.flat, (ref) => { /* ... */ });
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
4. **Client B queries by reference**
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
const result = await db.get(route, { _hash: ref });
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
5. **Server automatically pulls from Client A**
|
|
230
|
+
- Client B's query goes to its IoMulti (priority 1: local, not found)
|
|
231
|
+
- Falls back to IoPeer → Server (priority 2)
|
|
232
|
+
- Server's IoMulti cascades: priority 1 (local cache), then priority 2 (IoPeer[Client A])
|
|
233
|
+
- Data flows back: Client A → Server → Client B
|
|
234
|
+
|
|
235
|
+
**This cascade happens automatically** - no explicit pull operation needed.
|
|
236
|
+
|
|
237
|
+
### Three Storage Types
|
|
238
|
+
|
|
239
|
+
#### 1. Io Data (Tables)
|
|
240
|
+
|
|
241
|
+
- **What**: Relational tables (Cake, Cell, custom content types)
|
|
242
|
+
- **Storage**: IoMulti (local Io + IoPeer instances)
|
|
243
|
+
- **Query**: `db.get(route, { _hash: ref })`
|
|
244
|
+
- **Use Cases**: Structured data, records, metadata
|
|
245
|
+
|
|
246
|
+
#### 2. Bs Data (Blobs)
|
|
247
|
+
|
|
248
|
+
- **What**: Binary blobs (files, images, videos)
|
|
249
|
+
- **Storage**: BsMulti (local Bs + BsPeer instances)
|
|
250
|
+
- **Query**: `bs.get(blobHash)` after getting ref from Io table
|
|
251
|
+
- **Use Cases**: Large files, media content
|
|
252
|
+
- **Pattern**: Store blob → get hash → store hash in Io table → others query by hash
|
|
253
|
+
|
|
254
|
+
#### 3. Tree Data (Hierarchical)
|
|
255
|
+
|
|
256
|
+
- **What**: JSON objects converted to tree structures
|
|
257
|
+
- **Storage**: In Io layer with special 'trees' content type
|
|
258
|
+
- **Conversion**: `treeFromObject(jsObject)` creates Tree[] array
|
|
259
|
+
- **Root**: Last element in array (`trees[trees.length - 1]._hash`)
|
|
260
|
+
- **Query**: `db.get(route, { _hash: rootHash })` returns ALL related nodes
|
|
261
|
+
- **Use Cases**: Configuration objects, nested data structures
|
|
262
|
+
|
|
263
|
+
### Client-to-Client Communication
|
|
264
|
+
|
|
265
|
+
### Pattern: Insert on Client A, Get on Client B
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
// Setup: All parties create table definitions
|
|
269
|
+
await server.createTables({ withInsertHistory: [tableCfg] });
|
|
270
|
+
await clientA.createTables({ withInsertHistory: [tableCfg] });
|
|
271
|
+
await clientB.createTables({ withInsertHistory: [tableCfg] });
|
|
272
|
+
|
|
273
|
+
// Client A: Insert data locally
|
|
274
|
+
const result = await dbA.insert(route, [{ name: 'Tesla', model: 'Model S' }]);
|
|
275
|
+
const ref = result[0]._hash;
|
|
276
|
+
|
|
277
|
+
// Client A: Broadcast reference
|
|
278
|
+
clientA.socket.emit(route.flat, ref);
|
|
279
|
+
|
|
280
|
+
// Client B: Listen and pull data
|
|
281
|
+
clientB.socket.on(route.flat, async (ref) => {
|
|
282
|
+
// This query automatically cascades through server to Client A
|
|
283
|
+
const data = await dbB.get(route, { _hash: ref });
|
|
284
|
+
console.log(data.rljson.cars._data[0]); // { name: 'Tesla', ... }
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Server never stores the car data** - it only proxies the query from Client B to Client A.
|
|
289
|
+
|
|
290
|
+
### Why Pull-Based?
|
|
291
|
+
|
|
292
|
+
| Aspect | Pull-Based (@rljson/server) | Push-Based (Traditional) |
|
|
293
|
+
| ------------------- | ----------------------------- | ---------------------------- |
|
|
294
|
+
| **Network Traffic** | Minimal (only refs) | High (all data replicated) |
|
|
295
|
+
| **Data Freshness** | Always latest (pull on query) | Can be stale (cached copies) |
|
|
296
|
+
| **Storage** | Single source of truth | Multiple copies to sync |
|
|
297
|
+
| **Bandwidth** | Low (on-demand only) | High (push all changes) |
|
|
298
|
+
| **Offline** | Works fully offline | Needs sync when reconnected |
|
|
299
|
+
| **Conflicts** | None (read from source) | Requires resolution logic |
|
|
300
|
+
|
|
301
|
+
### When to Use @rljson/server
|
|
302
|
+
|
|
303
|
+
✅ **Good fit:**
|
|
304
|
+
|
|
305
|
+
- Local-first applications with occasional sharing
|
|
306
|
+
- Collaborative tools where users own their data
|
|
307
|
+
- Media sharing apps (store locally, share by reference)
|
|
308
|
+
- Configuration management (pull config by root hash)
|
|
309
|
+
- Document collaboration (pull latest version by ref)
|
|
310
|
+
|
|
311
|
+
❌ **Not ideal for:**
|
|
312
|
+
|
|
313
|
+
- Real-time collaborative editing (character-by-character)
|
|
314
|
+
- Systems requiring strong consistency guarantees
|
|
315
|
+
- Centralized storage where server must have all data
|
|
316
|
+
- Automatic background sync without references
|
|
317
|
+
|
|
318
|
+
### Key Design Principles
|
|
319
|
+
|
|
320
|
+
1. **Local-First**: All writes go to local storage only
|
|
321
|
+
2. **Content-Addressed**: Everything referenced by hash
|
|
322
|
+
3. **Reference-Based Discovery**: Need a reference to query data
|
|
323
|
+
4. **Automatic Cascade**: IoMulti/BsMulti handle priority traversal
|
|
324
|
+
5. **Server as Proxy**: Server doesn't store client data, only routes queries
|
|
325
|
+
6. **Pull on Demand**: Data retrieved only when explicitly queried
|
|
326
|
+
|
|
327
|
+
### Next Steps
|
|
328
|
+
|
|
329
|
+
- See [README.architecture.md](README.architecture.md) for detailed architecture documentation
|
|
330
|
+
- See [test/server.spec.ts](test/server.spec.ts) for comprehensive integration examples
|
|
331
|
+
- See [src/example.ts](src/example.ts) for a basic usage example
|
package/dist/README.trouble.md
CHANGED
|
@@ -11,6 +11,7 @@ found in the LICENSE file in the root of this package.
|
|
|
11
11
|
## Table of contents <!-- omit in toc -->
|
|
12
12
|
|
|
13
13
|
- [Vscode Windows: Debugging is not working](#vscode-windows-debugging-is-not-working)
|
|
14
|
+
- [Test Isolation: Socket.IO event listener accumulation](#test-isolation-socketio-event-listener-accumulation)
|
|
14
15
|
|
|
15
16
|
## Vscode Windows: Debugging is not working
|
|
16
17
|
|
|
@@ -21,3 +22,52 @@ in the VS Code Vitest extension (v1.14.4), which prevents test debugging from
|
|
|
21
22
|
working: <https://github.com/vitest-dev/vscode/issues/548> Please check from
|
|
22
23
|
time to time if the issue has been fixed and remove this note once it is
|
|
23
24
|
resolved.
|
|
25
|
+
|
|
26
|
+
## Test Isolation: Socket.IO event listener accumulation
|
|
27
|
+
|
|
28
|
+
Date: 2025-01-28
|
|
29
|
+
|
|
30
|
+
**Problem:**
|
|
31
|
+
|
|
32
|
+
When running multiple tests that use Socket.IO connections, tests pass individually but fail when run together. This is caused by event listeners from previous tests remaining active on persistent socket instances.
|
|
33
|
+
|
|
34
|
+
**Symptoms:**
|
|
35
|
+
|
|
36
|
+
- Individual tests pass: ✅
|
|
37
|
+
- All tests together fail: ❌
|
|
38
|
+
- Error messages like "received 0 instead of expected number of nodes"
|
|
39
|
+
- Unexpected behavior when sockets receive messages from previous tests
|
|
40
|
+
|
|
41
|
+
**Root Cause:**
|
|
42
|
+
|
|
43
|
+
Socket.IO sockets persist across tests in the `beforeAll` setup. When `SocketIoBridge` instances are created in `beforeEach`, old event listeners accumulate on the underlying sockets, causing interference between tests.
|
|
44
|
+
|
|
45
|
+
**Solution:**
|
|
46
|
+
|
|
47
|
+
Clear all event listeners in `beforeEach` before creating new bridges:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
beforeEach(async () => {
|
|
51
|
+
// Remove all event listeners from previous test to prevent interference
|
|
52
|
+
serverSockets.forEach((socket) => socket.removeAllListeners());
|
|
53
|
+
clientSockets.forEach((socket) => socket.removeAllListeners());
|
|
54
|
+
|
|
55
|
+
// Now proceed with test setup...
|
|
56
|
+
server = new Server(route, serverIo, serverBs);
|
|
57
|
+
await server.init();
|
|
58
|
+
// ... rest of setup
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Why This Works:**
|
|
63
|
+
|
|
64
|
+
- `removeAllListeners()` clears accumulated event handlers
|
|
65
|
+
- Each test starts with clean sockets
|
|
66
|
+
- No interference from previous test's `SocketIoBridge` instances
|
|
67
|
+
- Maintains socket connections established in `beforeAll`
|
|
68
|
+
|
|
69
|
+
**Alternative Approaches Considered:**
|
|
70
|
+
|
|
71
|
+
1. ❌ `tearDown()` in `afterEach`: Caused hook timeouts
|
|
72
|
+
2. ❌ Creating new socket connections per test: Too slow, defeats purpose of `beforeAll`
|
|
73
|
+
3. ✅ Clear listeners while reusing connections: Fast and reliable
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Bs } from '@rljson/bs';
|
|
2
|
-
import { Io, IoMulti
|
|
2
|
+
import { Io, IoMulti } from '@rljson/io';
|
|
3
3
|
import { BaseNode } from './base-node.ts';
|
|
4
|
+
import { SocketLike } from './socket-bundle.ts';
|
|
4
5
|
export declare class Client extends BaseNode {
|
|
5
6
|
private _socketToServer;
|
|
6
7
|
protected _localIo: Io;
|
|
@@ -11,11 +12,11 @@ export declare class Client extends BaseNode {
|
|
|
11
12
|
private _bsMulti?;
|
|
12
13
|
/**
|
|
13
14
|
* Creates a Client instance
|
|
14
|
-
* @param _socketToServer - Socket to connect to server
|
|
15
|
+
* @param _socketToServer - Socket or namespace bundle to connect to server
|
|
15
16
|
* @param _localIo - Local Io for local storage
|
|
16
17
|
* @param _localBs - Local Bs for local blob storage
|
|
17
18
|
*/
|
|
18
|
-
constructor(_socketToServer:
|
|
19
|
+
constructor(_socketToServer: SocketLike, _localIo: Io, _localBs: Bs);
|
|
19
20
|
/**
|
|
20
21
|
* Initializes Io and Bs multis and their peer bridges.
|
|
21
22
|
* @returns The initialized Io implementation.
|
|
@@ -47,10 +48,12 @@ export declare class Client extends BaseNode {
|
|
|
47
48
|
private _setupBs;
|
|
48
49
|
/**
|
|
49
50
|
* Creates and initializes a downstream Io peer.
|
|
51
|
+
* @param socket - Downstream socket to the server Io namespace.
|
|
50
52
|
*/
|
|
51
53
|
private _createIoPeer;
|
|
52
54
|
/**
|
|
53
55
|
* Creates and initializes a downstream Bs peer.
|
|
56
|
+
* @param socket - Downstream socket to the server Bs namespace.
|
|
54
57
|
*/
|
|
55
58
|
private _createBsPeer;
|
|
56
59
|
}
|
package/dist/server.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Bs, BsPeer } from '@rljson/bs';
|
|
|
2
2
|
import { Io, IoPeer, Socket } from '@rljson/io';
|
|
3
3
|
import { Route } from '@rljson/rljson';
|
|
4
4
|
import { BaseNode } from './base-node.ts';
|
|
5
|
+
import { SocketLike } from './socket-bundle.ts';
|
|
5
6
|
export type SocketWithClientId = Socket & {
|
|
6
7
|
__clientId?: string;
|
|
7
8
|
};
|
|
@@ -33,7 +34,7 @@ export declare class Server extends BaseNode {
|
|
|
33
34
|
* @param socket - Client socket to register.
|
|
34
35
|
* @returns The server instance.
|
|
35
36
|
*/
|
|
36
|
-
addSocket(socket:
|
|
37
|
+
addSocket(socket: SocketLike): Promise<this>;
|
|
37
38
|
/**
|
|
38
39
|
* Removes all listeners from all connected clients.
|
|
39
40
|
*/
|
|
@@ -56,7 +57,10 @@ export declare class Server extends BaseNode {
|
|
|
56
57
|
* Returns the connected clients map.
|
|
57
58
|
*/
|
|
58
59
|
get clients(): Map<string, {
|
|
59
|
-
|
|
60
|
+
ioUp: SocketWithClientId;
|
|
61
|
+
ioDown: SocketWithClientId;
|
|
62
|
+
bsUp: SocketWithClientId;
|
|
63
|
+
bsDown: SocketWithClientId;
|
|
60
64
|
io: IoPeer;
|
|
61
65
|
bs: BsPeer;
|
|
62
66
|
}>;
|
|
@@ -73,7 +77,7 @@ export declare class Server extends BaseNode {
|
|
|
73
77
|
/**
|
|
74
78
|
* Registers the client socket and peers.
|
|
75
79
|
* @param clientId - Stable client identifier.
|
|
76
|
-
* @param
|
|
80
|
+
* @param sockets - Directional sockets to register.
|
|
77
81
|
* @param io - Io peer associated with the client.
|
|
78
82
|
* @param bs - Bs peer associated with the client.
|
|
79
83
|
*/
|