@sumicom/quicksave-message-bus 0.7.0
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/LICENSE +21 -0
- package/dist/client.d.ts +44 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +195 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/path.d.ts +33 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +68 -0
- package/dist/path.js.map +1 -0
- package/dist/server.d.ts +47 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +196 -0
- package/dist/server.js.map +1 -0
- package/dist/transport.d.ts +30 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +2 -0
- package/dist/transport.js.map +1 -0
- package/dist/transports/fake.d.ts +53 -0
- package/dist/transports/fake.d.ts.map +1 -0
- package/dist/transports/fake.js +130 -0
- package/dist/transports/fake.js.map +1 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Quicksave, KingYoung Technology
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ClientTransport } from './transport.js';
|
|
2
|
+
export type SubscribeCallbacks<S = unknown, U = unknown> = {
|
|
3
|
+
onSnapshot: (data: S) => void;
|
|
4
|
+
onUpdate: (data: U) => void;
|
|
5
|
+
onError?: (error: string) => void;
|
|
6
|
+
};
|
|
7
|
+
export type CommandOptions = {
|
|
8
|
+
/** Timeout in ms. 0 or undefined disables. */
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
/**
|
|
11
|
+
* If the transport is disconnected when command is issued, queue until
|
|
12
|
+
* reconnect. Default: false (reject immediately).
|
|
13
|
+
*/
|
|
14
|
+
queueWhileDisconnected?: boolean;
|
|
15
|
+
};
|
|
16
|
+
export declare class MessageBusClient {
|
|
17
|
+
private transport;
|
|
18
|
+
private pending;
|
|
19
|
+
private subs;
|
|
20
|
+
private queue;
|
|
21
|
+
constructor(transport: ClientTransport);
|
|
22
|
+
/**
|
|
23
|
+
* Issue a one-shot command. Resolves with the server's response data,
|
|
24
|
+
* rejects with the server's error string or a timeout.
|
|
25
|
+
*/
|
|
26
|
+
command<R = unknown, P = unknown>(verb: string, payload: P, opts?: CommandOptions): Promise<R>;
|
|
27
|
+
/**
|
|
28
|
+
* Subscribe to a path. If another subscriber is already attached, the new
|
|
29
|
+
* callbacks immediately receive the last snapshot (if any) without a
|
|
30
|
+
* wire-level resubscribe; refcount is incremented.
|
|
31
|
+
*
|
|
32
|
+
* Returns an unsubscribe function. On the last unsubscribe for a path, an
|
|
33
|
+
* `unsub` frame is sent.
|
|
34
|
+
*/
|
|
35
|
+
subscribe<S = unknown, U = unknown>(path: string, callbacks: SubscribeCallbacks<S, U>): () => void;
|
|
36
|
+
private handleFrame;
|
|
37
|
+
private handleResult;
|
|
38
|
+
private handleSnapshot;
|
|
39
|
+
private handleUpdate;
|
|
40
|
+
private handleSubError;
|
|
41
|
+
private handleConnected;
|
|
42
|
+
private handleDisconnected;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGtD,MAAM,MAAM,kBAAkB,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,IAAI;IACzD,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,CAAC;AAoBF,MAAM,MAAM,cAAc,GAAG;IAC3B,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC;AAQF,qBAAa,gBAAgB;IAKf,OAAO,CAAC,SAAS;IAJ7B,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,IAAI,CAAwC;IACpD,OAAO,CAAC,KAAK,CAAyB;gBAElB,SAAS,EAAE,eAAe;IAM9C;;;OAGG;IACH,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,EACV,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,CAAC,CAAC;IA8Bb;;;;;;;OAOG;IACH,SAAS,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAChC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,GAClC,MAAM,IAAI;IA+Cb,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,kBAAkB;CAiB3B"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
let idCounter = 0;
|
|
2
|
+
function nextId() {
|
|
3
|
+
idCounter = (idCounter + 1) | 0;
|
|
4
|
+
return `c${Date.now().toString(36)}-${idCounter.toString(36)}`;
|
|
5
|
+
}
|
|
6
|
+
export class MessageBusClient {
|
|
7
|
+
transport;
|
|
8
|
+
pending = new Map();
|
|
9
|
+
subs = new Map();
|
|
10
|
+
queue = [];
|
|
11
|
+
constructor(transport) {
|
|
12
|
+
this.transport = transport;
|
|
13
|
+
transport.onFrame((frame) => this.handleFrame(frame));
|
|
14
|
+
transport.onConnected(() => this.handleConnected());
|
|
15
|
+
transport.onDisconnected(() => this.handleDisconnected());
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Issue a one-shot command. Resolves with the server's response data,
|
|
19
|
+
* rejects with the server's error string or a timeout.
|
|
20
|
+
*/
|
|
21
|
+
command(verb, payload, opts = {}) {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const id = nextId();
|
|
24
|
+
const pending = {
|
|
25
|
+
resolve: (data) => resolve(data),
|
|
26
|
+
reject,
|
|
27
|
+
};
|
|
28
|
+
if (opts.timeoutMs && opts.timeoutMs > 0) {
|
|
29
|
+
pending.timer = setTimeout(() => {
|
|
30
|
+
if (this.pending.delete(id)) {
|
|
31
|
+
reject(new Error(`Command "${verb}" timed out after ${opts.timeoutMs}ms`));
|
|
32
|
+
}
|
|
33
|
+
}, opts.timeoutMs);
|
|
34
|
+
}
|
|
35
|
+
this.pending.set(id, pending);
|
|
36
|
+
const send = () => {
|
|
37
|
+
this.transport.send({ kind: 'cmd', id, verb, payload });
|
|
38
|
+
};
|
|
39
|
+
if (this.transport.isConnected()) {
|
|
40
|
+
send();
|
|
41
|
+
}
|
|
42
|
+
else if (opts.queueWhileDisconnected) {
|
|
43
|
+
this.queue.push(send);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
this.pending.delete(id);
|
|
47
|
+
if (pending.timer)
|
|
48
|
+
clearTimeout(pending.timer);
|
|
49
|
+
reject(new Error(`Transport is disconnected; cannot send "${verb}"`));
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Subscribe to a path. If another subscriber is already attached, the new
|
|
55
|
+
* callbacks immediately receive the last snapshot (if any) without a
|
|
56
|
+
* wire-level resubscribe; refcount is incremented.
|
|
57
|
+
*
|
|
58
|
+
* Returns an unsubscribe function. On the last unsubscribe for a path, an
|
|
59
|
+
* `unsub` frame is sent.
|
|
60
|
+
*/
|
|
61
|
+
subscribe(path, callbacks) {
|
|
62
|
+
const cb = callbacks;
|
|
63
|
+
let state = this.subs.get(path);
|
|
64
|
+
if (!state) {
|
|
65
|
+
state = {
|
|
66
|
+
path,
|
|
67
|
+
refcount: 0,
|
|
68
|
+
subscribers: new Set(),
|
|
69
|
+
wireActive: false,
|
|
70
|
+
};
|
|
71
|
+
this.subs.set(path, state);
|
|
72
|
+
}
|
|
73
|
+
state.subscribers.add(cb);
|
|
74
|
+
state.refcount++;
|
|
75
|
+
// If we already have a cached snapshot, replay it to the new subscriber.
|
|
76
|
+
if (state.lastSnapshot) {
|
|
77
|
+
queueMicrotask(() => {
|
|
78
|
+
if (state.subscribers.has(cb))
|
|
79
|
+
cb.onSnapshot(state.lastSnapshot.data);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// Ensure a wire-level subscription exists.
|
|
83
|
+
if (!state.wireActive) {
|
|
84
|
+
const send = () => {
|
|
85
|
+
if (!this.subs.get(path))
|
|
86
|
+
return;
|
|
87
|
+
this.transport.send({ kind: 'sub', path });
|
|
88
|
+
state.wireActive = true;
|
|
89
|
+
};
|
|
90
|
+
if (this.transport.isConnected())
|
|
91
|
+
send();
|
|
92
|
+
else
|
|
93
|
+
this.queue.push(send);
|
|
94
|
+
}
|
|
95
|
+
let released = false;
|
|
96
|
+
return () => {
|
|
97
|
+
if (released)
|
|
98
|
+
return;
|
|
99
|
+
released = true;
|
|
100
|
+
const current = this.subs.get(path);
|
|
101
|
+
if (!current)
|
|
102
|
+
return;
|
|
103
|
+
current.subscribers.delete(cb);
|
|
104
|
+
current.refcount = Math.max(0, current.refcount - 1);
|
|
105
|
+
if (current.refcount === 0) {
|
|
106
|
+
this.subs.delete(path);
|
|
107
|
+
if (current.wireActive && this.transport.isConnected()) {
|
|
108
|
+
this.transport.send({ kind: 'unsub', path });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
handleFrame(frame) {
|
|
114
|
+
switch (frame.kind) {
|
|
115
|
+
case 'result':
|
|
116
|
+
this.handleResult(frame);
|
|
117
|
+
return;
|
|
118
|
+
case 'snap':
|
|
119
|
+
this.handleSnapshot(frame.path, frame.data);
|
|
120
|
+
return;
|
|
121
|
+
case 'upd':
|
|
122
|
+
this.handleUpdate(frame.path, frame.data);
|
|
123
|
+
return;
|
|
124
|
+
case 'sub-error':
|
|
125
|
+
this.handleSubError(frame.path, frame.error);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
handleResult(frame) {
|
|
130
|
+
const pending = this.pending.get(frame.id);
|
|
131
|
+
if (!pending)
|
|
132
|
+
return;
|
|
133
|
+
this.pending.delete(frame.id);
|
|
134
|
+
if (pending.timer)
|
|
135
|
+
clearTimeout(pending.timer);
|
|
136
|
+
if (frame.ok)
|
|
137
|
+
pending.resolve(frame.data);
|
|
138
|
+
else
|
|
139
|
+
pending.reject(new Error(frame.error));
|
|
140
|
+
}
|
|
141
|
+
handleSnapshot(path, data) {
|
|
142
|
+
const state = this.subs.get(path);
|
|
143
|
+
if (!state)
|
|
144
|
+
return;
|
|
145
|
+
state.lastSnapshot = { data };
|
|
146
|
+
for (const cb of state.subscribers)
|
|
147
|
+
cb.onSnapshot(data);
|
|
148
|
+
}
|
|
149
|
+
handleUpdate(path, data) {
|
|
150
|
+
const state = this.subs.get(path);
|
|
151
|
+
if (!state)
|
|
152
|
+
return;
|
|
153
|
+
state.lastSnapshot = { data };
|
|
154
|
+
for (const cb of state.subscribers)
|
|
155
|
+
cb.onUpdate(data);
|
|
156
|
+
}
|
|
157
|
+
handleSubError(path, error) {
|
|
158
|
+
const state = this.subs.get(path);
|
|
159
|
+
if (!state)
|
|
160
|
+
return;
|
|
161
|
+
for (const cb of state.subscribers)
|
|
162
|
+
cb.onError?.(error);
|
|
163
|
+
}
|
|
164
|
+
handleConnected() {
|
|
165
|
+
// Flush any queued sends (commands issued while disconnected).
|
|
166
|
+
const pending = this.queue;
|
|
167
|
+
this.queue = [];
|
|
168
|
+
for (const fn of pending)
|
|
169
|
+
fn();
|
|
170
|
+
// Re-establish wire subscriptions.
|
|
171
|
+
for (const state of this.subs.values()) {
|
|
172
|
+
this.transport.send({ kind: 'sub', path: state.path });
|
|
173
|
+
state.wireActive = true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
handleDisconnected() {
|
|
177
|
+
for (const state of this.subs.values()) {
|
|
178
|
+
state.wireActive = false;
|
|
179
|
+
}
|
|
180
|
+
// Reject all in-flight commands. They were sent to a peer that's no
|
|
181
|
+
// longer reachable; waiting for their timeout would surface misleading
|
|
182
|
+
// "timed out after Nms" errors to the caller. The caller can retry
|
|
183
|
+
// after reconnect if needed.
|
|
184
|
+
if (this.pending.size > 0) {
|
|
185
|
+
const stranded = Array.from(this.pending.values());
|
|
186
|
+
this.pending.clear();
|
|
187
|
+
for (const p of stranded) {
|
|
188
|
+
if (p.timer)
|
|
189
|
+
clearTimeout(p.timer);
|
|
190
|
+
p.reject(new Error('Transport disconnected before response'));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { MessageBusServer } from './server.js';
|
|
2
|
+
export type { CommandHandler, SubscribeHandler } from './server.js';
|
|
3
|
+
export { MessageBusClient } from './client.js';
|
|
4
|
+
export type { CommandOptions, SubscribeCallbacks } from './client.js';
|
|
5
|
+
export type { ClientTransport, PeerId, ServerTransport, } from './transport.js';
|
|
6
|
+
export type { AnyFrame, ClientFrame, CommandFrame, CommandResultFrame, PathParams, ServerFrame, SnapshotFrame, SubscribeErrorFrame, SubscribeFrame, UnsubscribeFrame, UpdateFrame, } from './types.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtE,YAAY,EACV,eAAe,EACf,MAAM,EACN,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,UAAU,EACV,WAAW,EACX,aAAa,EACb,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,WAAW,GACZ,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/path.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path matching for subscription patterns.
|
|
3
|
+
*
|
|
4
|
+
* Patterns use `:name` for single-segment params (e.g. `/sessions/:id`).
|
|
5
|
+
* Segments are separated by `/`. Leading `/` is optional but normalized away.
|
|
6
|
+
*
|
|
7
|
+
* Priority: static segments beat params at the same position.
|
|
8
|
+
* Example: `/sessions/active` is preferred over `/sessions/:id` when matching
|
|
9
|
+
* the path `/sessions/active`.
|
|
10
|
+
*/
|
|
11
|
+
export type PathPattern = {
|
|
12
|
+
pattern: string;
|
|
13
|
+
segments: Segment[];
|
|
14
|
+
specificity: number;
|
|
15
|
+
};
|
|
16
|
+
type Segment = {
|
|
17
|
+
kind: 'static';
|
|
18
|
+
value: string;
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'param';
|
|
21
|
+
name: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function parsePattern(pattern: string): PathPattern;
|
|
24
|
+
export declare function matchPattern(pattern: PathPattern, path: string): Record<string, string> | null;
|
|
25
|
+
/**
|
|
26
|
+
* Sort patterns by descending specificity so the first match wins.
|
|
27
|
+
* Mutates the input array.
|
|
28
|
+
*/
|
|
29
|
+
export declare function sortPatternsBySpecificity<T extends {
|
|
30
|
+
pattern: PathPattern;
|
|
31
|
+
}>(entries: T[]): T[];
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,KAAK,OAAO,GACR;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAQpC,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAoBzD;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAiB/B;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,SAAS;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,EAC1E,OAAO,EAAE,CAAC,EAAE,GACX,CAAC,EAAE,CAGL"}
|
package/dist/path.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path matching for subscription patterns.
|
|
3
|
+
*
|
|
4
|
+
* Patterns use `:name` for single-segment params (e.g. `/sessions/:id`).
|
|
5
|
+
* Segments are separated by `/`. Leading `/` is optional but normalized away.
|
|
6
|
+
*
|
|
7
|
+
* Priority: static segments beat params at the same position.
|
|
8
|
+
* Example: `/sessions/active` is preferred over `/sessions/:id` when matching
|
|
9
|
+
* the path `/sessions/active`.
|
|
10
|
+
*/
|
|
11
|
+
function normalize(path) {
|
|
12
|
+
if (path.length === 0)
|
|
13
|
+
return '';
|
|
14
|
+
const trimmed = path.startsWith('/') ? path.slice(1) : path;
|
|
15
|
+
return trimmed.endsWith('/') ? trimmed.slice(0, -1) : trimmed;
|
|
16
|
+
}
|
|
17
|
+
export function parsePattern(pattern) {
|
|
18
|
+
const normalized = normalize(pattern);
|
|
19
|
+
const rawSegments = normalized.length === 0 ? [] : normalized.split('/');
|
|
20
|
+
const segments = rawSegments.map((seg) => {
|
|
21
|
+
if (seg.startsWith(':')) {
|
|
22
|
+
const name = seg.slice(1);
|
|
23
|
+
if (name.length === 0) {
|
|
24
|
+
throw new Error(`Invalid pattern "${pattern}": param must have a name`);
|
|
25
|
+
}
|
|
26
|
+
return { kind: 'param', name };
|
|
27
|
+
}
|
|
28
|
+
return { kind: 'static', value: seg };
|
|
29
|
+
});
|
|
30
|
+
// Specificity: each static segment contributes a higher weight than a param.
|
|
31
|
+
// Longer paths are more specific than shorter ones at equal static counts.
|
|
32
|
+
let specificity = 0;
|
|
33
|
+
for (const seg of segments) {
|
|
34
|
+
specificity = specificity * 4 + (seg.kind === 'static' ? 2 : 1);
|
|
35
|
+
}
|
|
36
|
+
return { pattern, segments, specificity };
|
|
37
|
+
}
|
|
38
|
+
export function matchPattern(pattern, path) {
|
|
39
|
+
const pathSegments = normalize(path).split('/');
|
|
40
|
+
const normalizedPath = normalize(path);
|
|
41
|
+
const actualSegments = normalizedPath.length === 0 ? [] : pathSegments;
|
|
42
|
+
if (actualSegments.length !== pattern.segments.length)
|
|
43
|
+
return null;
|
|
44
|
+
const params = {};
|
|
45
|
+
for (let i = 0; i < pattern.segments.length; i++) {
|
|
46
|
+
const patternSeg = pattern.segments[i];
|
|
47
|
+
const pathSeg = actualSegments[i];
|
|
48
|
+
if (patternSeg.kind === 'static') {
|
|
49
|
+
if (patternSeg.value !== pathSeg)
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
if (pathSeg.length === 0)
|
|
54
|
+
return null;
|
|
55
|
+
params[patternSeg.name] = pathSeg;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return params;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Sort patterns by descending specificity so the first match wins.
|
|
62
|
+
* Mutates the input array.
|
|
63
|
+
*/
|
|
64
|
+
export function sortPatternsBySpecificity(entries) {
|
|
65
|
+
entries.sort((a, b) => b.pattern.specificity - a.pattern.specificity);
|
|
66
|
+
return entries;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=path.js.map
|
package/dist/path.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.js","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ServerTransport, PeerId } from './transport.js';
|
|
2
|
+
import type { PathParams } from './types.js';
|
|
3
|
+
export type CommandHandler<P = unknown, R = unknown> = (payload: P, ctx: {
|
|
4
|
+
peer: PeerId;
|
|
5
|
+
}) => Promise<R> | R;
|
|
6
|
+
export type SubscribeHandler<Path extends string = string, S = unknown, _U = unknown> = {
|
|
7
|
+
snapshot: (ctx: {
|
|
8
|
+
path: string;
|
|
9
|
+
params: PathParams<Path>;
|
|
10
|
+
peer: PeerId;
|
|
11
|
+
}) => Promise<S> | S;
|
|
12
|
+
onSubscribed?: (ctx: {
|
|
13
|
+
path: string;
|
|
14
|
+
params: PathParams<Path>;
|
|
15
|
+
peer: PeerId;
|
|
16
|
+
}) => void;
|
|
17
|
+
onUnsubscribed?: (ctx: {
|
|
18
|
+
path: string;
|
|
19
|
+
params: PathParams<Path>;
|
|
20
|
+
peer: PeerId;
|
|
21
|
+
}) => void;
|
|
22
|
+
};
|
|
23
|
+
export declare class MessageBusServer {
|
|
24
|
+
private transport;
|
|
25
|
+
private commands;
|
|
26
|
+
private subscriptions;
|
|
27
|
+
private active;
|
|
28
|
+
private byPeer;
|
|
29
|
+
constructor(transport: ServerTransport);
|
|
30
|
+
onCommand<P = unknown, R = unknown>(verb: string, handler: CommandHandler<P, R>): void;
|
|
31
|
+
onSubscribe<Path extends string, S = unknown, U = unknown>(pattern: Path, handler: SubscribeHandler<Path, S, U>): void;
|
|
32
|
+
/**
|
|
33
|
+
* Broadcast an update to every peer currently subscribed to `path`.
|
|
34
|
+
* Returns the number of peers that received the update.
|
|
35
|
+
*/
|
|
36
|
+
publish<U = unknown>(path: string, data: U): number;
|
|
37
|
+
/** Number of peers currently subscribed to the exact `path`. */
|
|
38
|
+
subscriberCount(path: string): number;
|
|
39
|
+
private handleFrame;
|
|
40
|
+
private handleCommand;
|
|
41
|
+
private handleSubscribe;
|
|
42
|
+
private sendSnapshot;
|
|
43
|
+
private handleUnsubscribe;
|
|
44
|
+
private handlePeerDisconnected;
|
|
45
|
+
private findMatch;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,KAAK,EAAe,UAAU,EAAE,MAAM,YAAY,CAAC;AAE1D,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,IAAI,CACrD,OAAO,EAAE,CAAC,EACV,GAAG,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,KAClB,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AAEpB,MAAM,MAAM,gBAAgB,CAC1B,IAAI,SAAS,MAAM,GAAG,MAAM,EAC5B,CAAC,GAAG,OAAO,EAKX,EAAE,GAAG,OAAO,IACV;IACF,QAAQ,EAAE,CAAC,GAAG,EAAE;QACd,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACrB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,IAAI,CAAC;IACX,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,IAAI,CAAC;CACZ,CAAC;AAcF,qBAAa,gBAAgB;IAMf,OAAO,CAAC,SAAS;IAL7B,OAAO,CAAC,QAAQ,CAAqC;IACrD,OAAO,CAAC,aAAa,CAA2B;IAChD,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,MAAM,CAAkC;gBAE5B,SAAS,EAAE,eAAe;IAK9C,SAAS,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAChC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,GAC5B,IAAI;IAOP,WAAW,CAAC,IAAI,SAAS,MAAM,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EACvD,OAAO,EAAE,IAAI,EACb,OAAO,EAAE,gBAAgB,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,GACpC,IAAI;IAYP;;;OAGG;IACH,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,MAAM;IAWnD,gEAAgE;IAChE,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAQrC,OAAO,CAAC,WAAW;YAcL,aAAa;YA6Bb,eAAe;YAkDf,YAAY;IAiB1B,OAAO,CAAC,iBAAiB;IAgBzB,OAAO,CAAC,sBAAsB;IAmB9B,OAAO,CAAC,SAAS;CASlB"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { matchPattern, parsePattern, sortPatternsBySpecificity, } from './path.js';
|
|
2
|
+
export class MessageBusServer {
|
|
3
|
+
transport;
|
|
4
|
+
commands = new Map();
|
|
5
|
+
subscriptions = [];
|
|
6
|
+
active = new Map();
|
|
7
|
+
byPeer = new Map();
|
|
8
|
+
constructor(transport) {
|
|
9
|
+
this.transport = transport;
|
|
10
|
+
transport.onFrame((peer, frame) => this.handleFrame(peer, frame));
|
|
11
|
+
transport.onPeerDisconnected((peer) => this.handlePeerDisconnected(peer));
|
|
12
|
+
}
|
|
13
|
+
onCommand(verb, handler) {
|
|
14
|
+
if (this.commands.has(verb)) {
|
|
15
|
+
throw new Error(`Command "${verb}" is already registered`);
|
|
16
|
+
}
|
|
17
|
+
this.commands.set(verb, handler);
|
|
18
|
+
}
|
|
19
|
+
onSubscribe(pattern, handler) {
|
|
20
|
+
const parsed = parsePattern(pattern);
|
|
21
|
+
if (this.subscriptions.some((e) => e.pattern.pattern === parsed.pattern)) {
|
|
22
|
+
throw new Error(`Subscription pattern "${pattern}" is already registered`);
|
|
23
|
+
}
|
|
24
|
+
this.subscriptions.push({
|
|
25
|
+
pattern: parsed,
|
|
26
|
+
handler: handler,
|
|
27
|
+
});
|
|
28
|
+
sortPatternsBySpecificity(this.subscriptions);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Broadcast an update to every peer currently subscribed to `path`.
|
|
32
|
+
* Returns the number of peers that received the update.
|
|
33
|
+
*/
|
|
34
|
+
publish(path, data) {
|
|
35
|
+
let count = 0;
|
|
36
|
+
for (const sub of this.active.values()) {
|
|
37
|
+
if (sub.path === path) {
|
|
38
|
+
this.transport.send(sub.peer, { kind: 'upd', path, data });
|
|
39
|
+
count++;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return count;
|
|
43
|
+
}
|
|
44
|
+
/** Number of peers currently subscribed to the exact `path`. */
|
|
45
|
+
subscriberCount(path) {
|
|
46
|
+
let count = 0;
|
|
47
|
+
for (const sub of this.active.values()) {
|
|
48
|
+
if (sub.path === path)
|
|
49
|
+
count++;
|
|
50
|
+
}
|
|
51
|
+
return count;
|
|
52
|
+
}
|
|
53
|
+
handleFrame(peer, frame) {
|
|
54
|
+
switch (frame.kind) {
|
|
55
|
+
case 'cmd':
|
|
56
|
+
void this.handleCommand(peer, frame.id, frame.verb, frame.payload);
|
|
57
|
+
return;
|
|
58
|
+
case 'sub':
|
|
59
|
+
void this.handleSubscribe(peer, frame.path);
|
|
60
|
+
return;
|
|
61
|
+
case 'unsub':
|
|
62
|
+
this.handleUnsubscribe(peer, frame.path);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async handleCommand(peer, id, verb, payload) {
|
|
67
|
+
const handler = this.commands.get(verb);
|
|
68
|
+
if (!handler) {
|
|
69
|
+
this.transport.send(peer, {
|
|
70
|
+
kind: 'result',
|
|
71
|
+
id,
|
|
72
|
+
ok: false,
|
|
73
|
+
error: `Unknown command: ${verb}`,
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const data = await handler(payload, { peer });
|
|
79
|
+
this.transport.send(peer, { kind: 'result', id, ok: true, data });
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
this.transport.send(peer, {
|
|
83
|
+
kind: 'result',
|
|
84
|
+
id,
|
|
85
|
+
ok: false,
|
|
86
|
+
error: err instanceof Error ? err.message : String(err),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async handleSubscribe(peer, path) {
|
|
91
|
+
const key = subKey(peer, path);
|
|
92
|
+
if (this.active.has(key)) {
|
|
93
|
+
// Duplicate subscribe from same peer — client layer dedups, but be
|
|
94
|
+
// defensive on the wire. Re-send snapshot using existing handler.
|
|
95
|
+
const existing = this.active.get(key);
|
|
96
|
+
await this.sendSnapshot(peer, existing);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const matched = this.findMatch(path);
|
|
100
|
+
if (!matched) {
|
|
101
|
+
this.transport.send(peer, {
|
|
102
|
+
kind: 'sub-error',
|
|
103
|
+
path,
|
|
104
|
+
error: `No subscription handler for path: ${path}`,
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const entry = {
|
|
109
|
+
peer,
|
|
110
|
+
path,
|
|
111
|
+
pattern: matched.entry.pattern,
|
|
112
|
+
params: matched.params,
|
|
113
|
+
};
|
|
114
|
+
this.active.set(key, entry);
|
|
115
|
+
let peerSet = this.byPeer.get(peer);
|
|
116
|
+
if (!peerSet) {
|
|
117
|
+
peerSet = new Set();
|
|
118
|
+
this.byPeer.set(peer, peerSet);
|
|
119
|
+
}
|
|
120
|
+
peerSet.add(key);
|
|
121
|
+
try {
|
|
122
|
+
await this.sendSnapshot(peer, entry);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
this.active.delete(key);
|
|
126
|
+
peerSet.delete(key);
|
|
127
|
+
this.transport.send(peer, {
|
|
128
|
+
kind: 'sub-error',
|
|
129
|
+
path,
|
|
130
|
+
error: err instanceof Error ? err.message : String(err),
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
matched.entry.handler.onSubscribed?.({
|
|
135
|
+
path,
|
|
136
|
+
params: entry.params,
|
|
137
|
+
peer,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async sendSnapshot(peer, entry) {
|
|
141
|
+
const handler = this.subscriptions.find((e) => e.pattern.pattern === entry.pattern.pattern)?.handler;
|
|
142
|
+
if (!handler) {
|
|
143
|
+
throw new Error(`Subscription pattern "${entry.pattern.pattern}" is no longer registered`);
|
|
144
|
+
}
|
|
145
|
+
const data = await handler.snapshot({
|
|
146
|
+
path: entry.path,
|
|
147
|
+
params: entry.params,
|
|
148
|
+
peer,
|
|
149
|
+
});
|
|
150
|
+
this.transport.send(peer, { kind: 'snap', path: entry.path, data });
|
|
151
|
+
}
|
|
152
|
+
handleUnsubscribe(peer, path) {
|
|
153
|
+
const key = subKey(peer, path);
|
|
154
|
+
const entry = this.active.get(key);
|
|
155
|
+
if (!entry)
|
|
156
|
+
return;
|
|
157
|
+
this.active.delete(key);
|
|
158
|
+
this.byPeer.get(peer)?.delete(key);
|
|
159
|
+
const handler = this.subscriptions.find((e) => e.pattern.pattern === entry.pattern.pattern)?.handler;
|
|
160
|
+
handler?.onUnsubscribed?.({
|
|
161
|
+
path: entry.path,
|
|
162
|
+
params: entry.params,
|
|
163
|
+
peer,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
handlePeerDisconnected(peer) {
|
|
167
|
+
const keys = this.byPeer.get(peer);
|
|
168
|
+
if (!keys)
|
|
169
|
+
return;
|
|
170
|
+
for (const key of keys) {
|
|
171
|
+
const entry = this.active.get(key);
|
|
172
|
+
if (!entry)
|
|
173
|
+
continue;
|
|
174
|
+
this.active.delete(key);
|
|
175
|
+
const handler = this.subscriptions.find((e) => e.pattern.pattern === entry.pattern.pattern)?.handler;
|
|
176
|
+
handler?.onUnsubscribed?.({
|
|
177
|
+
path: entry.path,
|
|
178
|
+
params: entry.params,
|
|
179
|
+
peer,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
this.byPeer.delete(peer);
|
|
183
|
+
}
|
|
184
|
+
findMatch(path) {
|
|
185
|
+
for (const entry of this.subscriptions) {
|
|
186
|
+
const params = matchPattern(entry.pattern, path);
|
|
187
|
+
if (params)
|
|
188
|
+
return { entry, params };
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function subKey(peer, path) {
|
|
194
|
+
return `${peer}\u0000${path}`;
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ClientFrame, ServerFrame } from './types.js';
|
|
2
|
+
/** Identifier for a connected client from the server's perspective. */
|
|
3
|
+
export type PeerId = string;
|
|
4
|
+
/**
|
|
5
|
+
* Server-side transport. Frames are addressed per-peer so the server can
|
|
6
|
+
* deliver snapshots/updates to specific subscribers.
|
|
7
|
+
*
|
|
8
|
+
* Transports are responsible for wrapping the frame JSON in whatever envelope
|
|
9
|
+
* the underlying wire needs (encryption, routing metadata, etc.) and for
|
|
10
|
+
* emitting `peer-connected` / `peer-disconnected` so the server can track
|
|
11
|
+
* liveness and drop subscriptions.
|
|
12
|
+
*/
|
|
13
|
+
export interface ServerTransport {
|
|
14
|
+
send(peer: PeerId, frame: ServerFrame): void;
|
|
15
|
+
onFrame(handler: (peer: PeerId, frame: ClientFrame) => void): void;
|
|
16
|
+
onPeerConnected(handler: (peer: PeerId) => void): void;
|
|
17
|
+
onPeerDisconnected(handler: (peer: PeerId) => void): void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Client-side transport. A client has exactly one upstream, so frames are
|
|
21
|
+
* not addressed.
|
|
22
|
+
*/
|
|
23
|
+
export interface ClientTransport {
|
|
24
|
+
send(frame: ClientFrame): void;
|
|
25
|
+
onFrame(handler: (frame: ServerFrame) => void): void;
|
|
26
|
+
onConnected(handler: () => void): void;
|
|
27
|
+
onDisconnected(handler: () => void): void;
|
|
28
|
+
isConnected(): boolean;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE3D,uEAAuE;AACvE,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAE5B;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7C,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI,CAAC;IACnE,eAAe,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IACvD,kBAAkB,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;CAC3D;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/B,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI,CAAC;IACrD,WAAW,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACvC,cAAc,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC1C,WAAW,IAAI,OAAO,CAAC;CACxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ClientTransport, PeerId, ServerTransport } from '../transport.js';
|
|
2
|
+
import type { ClientFrame, ServerFrame } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* In-memory transport pair for testing. Supports multiple clients connecting
|
|
5
|
+
* to a single server. Messages are delivered via `queueMicrotask` to surface
|
|
6
|
+
* any ordering assumptions.
|
|
7
|
+
*/
|
|
8
|
+
export declare class FakePipe {
|
|
9
|
+
readonly server: FakeServerTransport;
|
|
10
|
+
private clients;
|
|
11
|
+
private idCounter;
|
|
12
|
+
constructor();
|
|
13
|
+
/** Create a new client connected to the shared server. */
|
|
14
|
+
createClient(): FakeClientTransport;
|
|
15
|
+
/** Used internally by clients to deliver frames to the server. */
|
|
16
|
+
_deliverToServer(peer: PeerId, frame: ClientFrame): void;
|
|
17
|
+
/** Used internally by the server to deliver frames to a client. */
|
|
18
|
+
_deliverToClient(peer: PeerId, frame: ServerFrame): void;
|
|
19
|
+
_notifyServerOfConnect(peer: PeerId): void;
|
|
20
|
+
_notifyServerOfDisconnect(peer: PeerId): void;
|
|
21
|
+
}
|
|
22
|
+
export declare class FakeServerTransport implements ServerTransport {
|
|
23
|
+
private frameHandlers;
|
|
24
|
+
private connectHandlers;
|
|
25
|
+
private disconnectHandlers;
|
|
26
|
+
private pipes;
|
|
27
|
+
send(peer: PeerId, frame: ServerFrame): void;
|
|
28
|
+
onFrame(handler: (peer: PeerId, frame: ClientFrame) => void): void;
|
|
29
|
+
onPeerConnected(handler: (peer: PeerId) => void): void;
|
|
30
|
+
onPeerDisconnected(handler: (peer: PeerId) => void): void;
|
|
31
|
+
_registerPeer(peer: PeerId, pipe: FakePipe): void;
|
|
32
|
+
_receive(peer: PeerId, frame: ClientFrame): void;
|
|
33
|
+
_peerConnected(peer: PeerId): void;
|
|
34
|
+
_peerDisconnected(peer: PeerId): void;
|
|
35
|
+
}
|
|
36
|
+
export declare class FakeClientTransport implements ClientTransport {
|
|
37
|
+
private peerId;
|
|
38
|
+
private pipe;
|
|
39
|
+
private frameHandlers;
|
|
40
|
+
private connectHandlers;
|
|
41
|
+
private disconnectHandlers;
|
|
42
|
+
private connected;
|
|
43
|
+
constructor(peerId: PeerId, pipe: FakePipe);
|
|
44
|
+
connect(): void;
|
|
45
|
+
disconnect(): void;
|
|
46
|
+
send(frame: ClientFrame): void;
|
|
47
|
+
onFrame(handler: (frame: ServerFrame) => void): void;
|
|
48
|
+
onConnected(handler: () => void): void;
|
|
49
|
+
onDisconnected(handler: () => void): void;
|
|
50
|
+
isConnected(): boolean;
|
|
51
|
+
_receive(frame: ServerFrame): void;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=fake.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake.d.ts","sourceRoot":"","sources":["../../src/transports/fake.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,MAAM,EACN,eAAe,EAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE5D;;;;GAIG;AACH,qBAAa,QAAQ;IACnB,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,OAAO,CAAC,OAAO,CAA0C;IACzD,OAAO,CAAC,SAAS,CAAK;;IAMtB,0DAA0D;IAC1D,YAAY,IAAI,mBAAmB;IAOnC,kEAAkE;IAClE,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI;IAIxD,mEAAmE;IACnE,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI;IAMxD,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI1C,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;CAI9C;AAED,qBAAa,mBAAoB,YAAW,eAAe;IACzD,OAAO,CAAC,aAAa,CAAyD;IAC9E,OAAO,CAAC,eAAe,CAAqC;IAC5D,OAAO,CAAC,kBAAkB,CAAqC;IAC/D,OAAO,CAAC,KAAK,CAA+B;IAE5C,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI;IAM5C,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI;IAIlE,eAAe,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAItD,kBAAkB,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAIzD,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI;IAIjD,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI;IAIhD,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIlC,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;CAItC;AAED,qBAAa,mBAAoB,YAAW,eAAe;IAOvD,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,IAAI;IAPd,OAAO,CAAC,aAAa,CAA2C;IAChE,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,kBAAkB,CAAyB;IACnD,OAAO,CAAC,SAAS,CAAS;gBAGhB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,QAAQ;IAGxB,OAAO,IAAI,IAAI;IAWf,UAAU,IAAI,IAAI;IAOlB,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAO9B,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI;IAIpD,WAAW,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAItC,cAAc,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAIzC,WAAW,IAAI,OAAO;IAItB,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;CAGnC"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory transport pair for testing. Supports multiple clients connecting
|
|
3
|
+
* to a single server. Messages are delivered via `queueMicrotask` to surface
|
|
4
|
+
* any ordering assumptions.
|
|
5
|
+
*/
|
|
6
|
+
export class FakePipe {
|
|
7
|
+
server;
|
|
8
|
+
clients = new Map();
|
|
9
|
+
idCounter = 0;
|
|
10
|
+
constructor() {
|
|
11
|
+
this.server = new FakeServerTransport();
|
|
12
|
+
}
|
|
13
|
+
/** Create a new client connected to the shared server. */
|
|
14
|
+
createClient() {
|
|
15
|
+
const peerId = `peer-${++this.idCounter}`;
|
|
16
|
+
const client = new FakeClientTransport(peerId, this);
|
|
17
|
+
this.clients.set(peerId, client);
|
|
18
|
+
return client;
|
|
19
|
+
}
|
|
20
|
+
/** Used internally by clients to deliver frames to the server. */
|
|
21
|
+
_deliverToServer(peer, frame) {
|
|
22
|
+
queueMicrotask(() => this.server._receive(peer, frame));
|
|
23
|
+
}
|
|
24
|
+
/** Used internally by the server to deliver frames to a client. */
|
|
25
|
+
_deliverToClient(peer, frame) {
|
|
26
|
+
const client = this.clients.get(peer);
|
|
27
|
+
if (!client)
|
|
28
|
+
return;
|
|
29
|
+
queueMicrotask(() => client._receive(frame));
|
|
30
|
+
}
|
|
31
|
+
_notifyServerOfConnect(peer) {
|
|
32
|
+
queueMicrotask(() => this.server._peerConnected(peer));
|
|
33
|
+
}
|
|
34
|
+
_notifyServerOfDisconnect(peer) {
|
|
35
|
+
this.clients.delete(peer);
|
|
36
|
+
queueMicrotask(() => this.server._peerDisconnected(peer));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export class FakeServerTransport {
|
|
40
|
+
frameHandlers = [];
|
|
41
|
+
connectHandlers = [];
|
|
42
|
+
disconnectHandlers = [];
|
|
43
|
+
pipes = new Map();
|
|
44
|
+
send(peer, frame) {
|
|
45
|
+
const pipe = this.pipes.get(peer);
|
|
46
|
+
if (!pipe)
|
|
47
|
+
return;
|
|
48
|
+
pipe._deliverToClient(peer, frame);
|
|
49
|
+
}
|
|
50
|
+
onFrame(handler) {
|
|
51
|
+
this.frameHandlers.push(handler);
|
|
52
|
+
}
|
|
53
|
+
onPeerConnected(handler) {
|
|
54
|
+
this.connectHandlers.push(handler);
|
|
55
|
+
}
|
|
56
|
+
onPeerDisconnected(handler) {
|
|
57
|
+
this.disconnectHandlers.push(handler);
|
|
58
|
+
}
|
|
59
|
+
_registerPeer(peer, pipe) {
|
|
60
|
+
this.pipes.set(peer, pipe);
|
|
61
|
+
}
|
|
62
|
+
_receive(peer, frame) {
|
|
63
|
+
for (const handler of this.frameHandlers)
|
|
64
|
+
handler(peer, frame);
|
|
65
|
+
}
|
|
66
|
+
_peerConnected(peer) {
|
|
67
|
+
for (const handler of this.connectHandlers)
|
|
68
|
+
handler(peer);
|
|
69
|
+
}
|
|
70
|
+
_peerDisconnected(peer) {
|
|
71
|
+
this.pipes.delete(peer);
|
|
72
|
+
for (const handler of this.disconnectHandlers)
|
|
73
|
+
handler(peer);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export class FakeClientTransport {
|
|
77
|
+
peerId;
|
|
78
|
+
pipe;
|
|
79
|
+
frameHandlers = [];
|
|
80
|
+
connectHandlers = [];
|
|
81
|
+
disconnectHandlers = [];
|
|
82
|
+
connected = false;
|
|
83
|
+
constructor(peerId, pipe) {
|
|
84
|
+
this.peerId = peerId;
|
|
85
|
+
this.pipe = pipe;
|
|
86
|
+
}
|
|
87
|
+
connect() {
|
|
88
|
+
if (this.connected)
|
|
89
|
+
return;
|
|
90
|
+
this.connected = true;
|
|
91
|
+
this.pipe.server._registerPeer(this.peerId, this.pipe);
|
|
92
|
+
this.pipe._notifyServerOfConnect(this.peerId);
|
|
93
|
+
// Fire local handlers on next microtask so subscribers can wire up first.
|
|
94
|
+
queueMicrotask(() => {
|
|
95
|
+
for (const handler of this.connectHandlers)
|
|
96
|
+
handler();
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
disconnect() {
|
|
100
|
+
if (!this.connected)
|
|
101
|
+
return;
|
|
102
|
+
this.connected = false;
|
|
103
|
+
this.pipe._notifyServerOfDisconnect(this.peerId);
|
|
104
|
+
for (const handler of this.disconnectHandlers)
|
|
105
|
+
handler();
|
|
106
|
+
}
|
|
107
|
+
send(frame) {
|
|
108
|
+
if (!this.connected) {
|
|
109
|
+
throw new Error('FakeClientTransport: not connected');
|
|
110
|
+
}
|
|
111
|
+
this.pipe._deliverToServer(this.peerId, frame);
|
|
112
|
+
}
|
|
113
|
+
onFrame(handler) {
|
|
114
|
+
this.frameHandlers.push(handler);
|
|
115
|
+
}
|
|
116
|
+
onConnected(handler) {
|
|
117
|
+
this.connectHandlers.push(handler);
|
|
118
|
+
}
|
|
119
|
+
onDisconnected(handler) {
|
|
120
|
+
this.disconnectHandlers.push(handler);
|
|
121
|
+
}
|
|
122
|
+
isConnected() {
|
|
123
|
+
return this.connected;
|
|
124
|
+
}
|
|
125
|
+
_receive(frame) {
|
|
126
|
+
for (const handler of this.frameHandlers)
|
|
127
|
+
handler(frame);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=fake.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake.js","sourceRoot":"","sources":["../../src/transports/fake.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire frames exchanged between client and server.
|
|
3
|
+
*
|
|
4
|
+
* Commands are one-shot request/response.
|
|
5
|
+
* Subscriptions deliver an initial snapshot followed by updates until unsub.
|
|
6
|
+
*/
|
|
7
|
+
export type CommandFrame = {
|
|
8
|
+
kind: 'cmd';
|
|
9
|
+
id: string;
|
|
10
|
+
verb: string;
|
|
11
|
+
payload: unknown;
|
|
12
|
+
};
|
|
13
|
+
export type CommandResultFrame = {
|
|
14
|
+
kind: 'result';
|
|
15
|
+
id: string;
|
|
16
|
+
ok: true;
|
|
17
|
+
data: unknown;
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'result';
|
|
20
|
+
id: string;
|
|
21
|
+
ok: false;
|
|
22
|
+
error: string;
|
|
23
|
+
};
|
|
24
|
+
export type SubscribeFrame = {
|
|
25
|
+
kind: 'sub';
|
|
26
|
+
path: string;
|
|
27
|
+
};
|
|
28
|
+
export type SnapshotFrame = {
|
|
29
|
+
kind: 'snap';
|
|
30
|
+
path: string;
|
|
31
|
+
data: unknown;
|
|
32
|
+
};
|
|
33
|
+
export type UpdateFrame = {
|
|
34
|
+
kind: 'upd';
|
|
35
|
+
path: string;
|
|
36
|
+
data: unknown;
|
|
37
|
+
};
|
|
38
|
+
export type SubscribeErrorFrame = {
|
|
39
|
+
kind: 'sub-error';
|
|
40
|
+
path: string;
|
|
41
|
+
error: string;
|
|
42
|
+
};
|
|
43
|
+
export type UnsubscribeFrame = {
|
|
44
|
+
kind: 'unsub';
|
|
45
|
+
path: string;
|
|
46
|
+
};
|
|
47
|
+
export type ClientFrame = CommandFrame | SubscribeFrame | UnsubscribeFrame;
|
|
48
|
+
export type ServerFrame = CommandResultFrame | SnapshotFrame | UpdateFrame | SubscribeErrorFrame;
|
|
49
|
+
export type AnyFrame = ClientFrame | ServerFrame;
|
|
50
|
+
/** Extract `:param` names from a path pattern as a union of string literals. */
|
|
51
|
+
export type PathParams<P extends string> = P extends `${string}:${infer Param}/${infer Rest}` ? {
|
|
52
|
+
[K in Param | keyof PathParams<`/${Rest}`>]: string;
|
|
53
|
+
} : P extends `${string}:${infer Param}` ? {
|
|
54
|
+
[K in Param]: string;
|
|
55
|
+
} : Record<string, never>;
|
|
56
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,KAAK,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7D,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,KAAK,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,KAAK,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,cAAc,GAAG,gBAAgB,CAAC;AAC3E,MAAM,MAAM,WAAW,GACnB,kBAAkB,GAClB,aAAa,GACb,WAAW,GACX,mBAAmB,CAAC;AAExB,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,WAAW,CAAC;AAEjD,gFAAgF;AAChF,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,MAAM,IACrC,CAAC,SAAS,GAAG,MAAM,IAAI,MAAM,KAAK,IAAI,MAAM,IAAI,EAAE,GAC9C;KAAG,CAAC,IAAI,KAAK,GAAG,MAAM,UAAU,CAAC,IAAI,IAAI,EAAE,CAAC,GAAG,MAAM;CAAE,GACvD,CAAC,SAAS,GAAG,MAAM,IAAI,MAAM,KAAK,EAAE,GAClC;KAAG,CAAC,IAAI,KAAK,GAAG,MAAM;CAAE,GACxB,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sumicom/quicksave-message-bus",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Transport-agnostic command + subscribe message bus for Quicksave",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/KingYoung-Sumicom/quicksave.git",
|
|
9
|
+
"directory": "packages/message-bus"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/KingYoung-Sumicom/quicksave#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/KingYoung-Sumicom/quicksave/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"quicksave",
|
|
17
|
+
"message-bus",
|
|
18
|
+
"transport"
|
|
19
|
+
],
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"branch": "stable"
|
|
26
|
+
},
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"import": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts"
|
|
33
|
+
},
|
|
34
|
+
"./fake": {
|
|
35
|
+
"import": "./dist/transports/fake.js",
|
|
36
|
+
"types": "./dist/transports/fake.d.ts"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"typescript": "^5.3.3",
|
|
41
|
+
"vitest": "^1.2.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc",
|
|
45
|
+
"dev": "tsc --watch",
|
|
46
|
+
"typecheck": "tsc --noEmit",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:unit": "vitest run",
|
|
49
|
+
"test:watch": "vitest"
|
|
50
|
+
}
|
|
51
|
+
}
|