@lifeart/async-dom 2.0.0-alpha.3
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/README.md +623 -0
- package/dist/base.d.cts +398 -0
- package/dist/base.d.cts.map +1 -0
- package/dist/base.d.ts +398 -0
- package/dist/base.d.ts.map +1 -0
- package/dist/cli.cjs +528 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +493 -0
- package/dist/cli.js.map +1 -0
- package/dist/debug.d.cts +145 -0
- package/dist/debug.d.cts.map +1 -0
- package/dist/debug.d.ts +145 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/index.cjs +26 -0
- package/dist/index.d.cts +560 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +560 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index2.d.cts +5 -0
- package/dist/index2.d.ts +5 -0
- package/dist/index3.d.cts +882 -0
- package/dist/index3.d.cts.map +1 -0
- package/dist/index3.d.ts +882 -0
- package/dist/index3.d.ts.map +1 -0
- package/dist/main-thread.cjs +5459 -0
- package/dist/main-thread.cjs.map +1 -0
- package/dist/main-thread.js +5429 -0
- package/dist/main-thread.js.map +1 -0
- package/dist/react.cjs +116 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +91 -0
- package/dist/react.d.cts.map +1 -0
- package/dist/react.d.ts +91 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +113 -0
- package/dist/react.js.map +1 -0
- package/dist/resolve-debug.cjs +24 -0
- package/dist/resolve-debug.cjs.map +1 -0
- package/dist/resolve-debug.js +19 -0
- package/dist/resolve-debug.js.map +1 -0
- package/dist/server.cjs +250 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +127 -0
- package/dist/server.d.cts.map +1 -0
- package/dist/server.d.ts +127 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +245 -0
- package/dist/server.js.map +1 -0
- package/dist/svelte.cjs +48 -0
- package/dist/svelte.cjs.map +1 -0
- package/dist/svelte.d.cts +38 -0
- package/dist/svelte.d.cts.map +1 -0
- package/dist/svelte.d.ts +38 -0
- package/dist/svelte.d.ts.map +1 -0
- package/dist/svelte.js +47 -0
- package/dist/svelte.js.map +1 -0
- package/dist/sync-channel.cjs +532 -0
- package/dist/sync-channel.cjs.map +1 -0
- package/dist/sync-channel.js +425 -0
- package/dist/sync-channel.js.map +1 -0
- package/dist/transport.cjs +213 -0
- package/dist/transport.cjs.map +1 -0
- package/dist/transport.d.cts +79 -0
- package/dist/transport.d.cts.map +1 -0
- package/dist/transport.d.ts +79 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +202 -0
- package/dist/transport.js.map +1 -0
- package/dist/vite-plugin.cjs +112 -0
- package/dist/vite-plugin.cjs.map +1 -0
- package/dist/vite-plugin.d.cts +39 -0
- package/dist/vite-plugin.d.cts.map +1 -0
- package/dist/vite-plugin.d.ts +39 -0
- package/dist/vite-plugin.d.ts.map +1 -0
- package/dist/vite-plugin.js +107 -0
- package/dist/vite-plugin.js.map +1 -0
- package/dist/vue.cjs +123 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.d.cts +126 -0
- package/dist/vue.d.cts.map +1 -0
- package/dist/vue.d.ts +126 -0
- package/dist/vue.d.ts.map +1 -0
- package/dist/vue.js +120 -0
- package/dist/vue.js.map +1 -0
- package/dist/worker-thread.cjs +2751 -0
- package/dist/worker-thread.cjs.map +1 -0
- package/dist/worker-thread.js +2692 -0
- package/dist/worker-thread.js.map +1 -0
- package/dist/worker-transport.cjs +136 -0
- package/dist/worker-transport.cjs.map +1 -0
- package/dist/worker-transport.d.cts +162 -0
- package/dist/worker-transport.d.cts.map +1 -0
- package/dist/worker-transport.d.ts +162 -0
- package/dist/worker-transport.d.ts.map +1 -0
- package/dist/worker-transport.js +125 -0
- package/dist/worker-transport.js.map +1 -0
- package/dist/worker.cjs +12 -0
- package/dist/worker.d.cts +2 -0
- package/dist/worker.d.ts +2 -0
- package/dist/worker.js +2 -0
- package/dist/ws-server-transport.cjs +147 -0
- package/dist/ws-server-transport.cjs.map +1 -0
- package/dist/ws-server-transport.d.cts +64 -0
- package/dist/ws-server-transport.d.cts.map +1 -0
- package/dist/ws-server-transport.d.ts +64 -0
- package/dist/ws-server-transport.d.ts.map +1 -0
- package/dist/ws-server-transport.js +142 -0
- package/dist/ws-server-transport.js.map +1 -0
- package/dist/ws-transport.cjs +954 -0
- package/dist/ws-transport.cjs.map +1 -0
- package/dist/ws-transport.js +913 -0
- package/dist/ws-transport.js.map +1 -0
- package/package.json +145 -0
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_sync_channel = require("./sync-channel.cjs");
|
|
3
|
+
const require_worker_thread = require("./worker-thread.cjs");
|
|
4
|
+
const require_ws_server_transport = require("./ws-server-transport.cjs");
|
|
5
|
+
//#region src/server/mutation-log.ts
|
|
6
|
+
/**
|
|
7
|
+
* Ring buffer that stores recent MutationMessages for replay to new clients.
|
|
8
|
+
*
|
|
9
|
+
* Uses a fixed-capacity circular buffer with O(1) append and O(n) replay,
|
|
10
|
+
* avoiding the O(n) cost of Array.shift() for eviction.
|
|
11
|
+
*/
|
|
12
|
+
var MutationLog = class {
|
|
13
|
+
buffer;
|
|
14
|
+
head = 0;
|
|
15
|
+
count = 0;
|
|
16
|
+
maxEntries;
|
|
17
|
+
constructor(config) {
|
|
18
|
+
this.maxEntries = Math.max(0, config?.maxEntries ?? 1e4);
|
|
19
|
+
this.buffer = this.maxEntries > 0 ? new Array(this.maxEntries) : [];
|
|
20
|
+
}
|
|
21
|
+
append(message) {
|
|
22
|
+
if (this.maxEntries === 0) return;
|
|
23
|
+
if (this.count < this.maxEntries) {
|
|
24
|
+
const writeIndex = (this.head + this.count) % this.maxEntries;
|
|
25
|
+
this.buffer[writeIndex] = message;
|
|
26
|
+
this.count++;
|
|
27
|
+
} else {
|
|
28
|
+
this.buffer[this.head] = message;
|
|
29
|
+
this.head = (this.head + 1) % this.maxEntries;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
getReplayMessages() {
|
|
33
|
+
const result = new Array(this.count);
|
|
34
|
+
for (let i = 0; i < this.count; i++) result[i] = this.buffer[(this.head + i) % this.maxEntries];
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
size() {
|
|
38
|
+
return this.count;
|
|
39
|
+
}
|
|
40
|
+
clear() {
|
|
41
|
+
this.head = 0;
|
|
42
|
+
this.count = 0;
|
|
43
|
+
this.buffer.fill(void 0);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/server/broadcast-transport.ts
|
|
48
|
+
/**
|
|
49
|
+
* Transport that fans out messages from a single source to N client transports.
|
|
50
|
+
*
|
|
51
|
+
* Used by StreamingServer to broadcast DOM mutations to all connected readers.
|
|
52
|
+
*/
|
|
53
|
+
var BroadcastTransport = class {
|
|
54
|
+
clients = /* @__PURE__ */ new Map();
|
|
55
|
+
handlers = [];
|
|
56
|
+
log;
|
|
57
|
+
_readyState = "open";
|
|
58
|
+
config;
|
|
59
|
+
onError;
|
|
60
|
+
onClose;
|
|
61
|
+
constructor(config) {
|
|
62
|
+
this.config = config ?? {};
|
|
63
|
+
this.log = new MutationLog(config?.mutationLog);
|
|
64
|
+
}
|
|
65
|
+
send(message) {
|
|
66
|
+
if (this._readyState === "closed") return;
|
|
67
|
+
if (require_sync_channel.isMutationMessage(message)) this.log.append(message);
|
|
68
|
+
const failedClientIds = [];
|
|
69
|
+
for (const [clientId, transport] of this.clients) try {
|
|
70
|
+
transport.send(message);
|
|
71
|
+
if (transport.readyState === "closed") failedClientIds.push(clientId);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error(`[async-dom] Failed to send to client ${clientId}:`, err);
|
|
74
|
+
failedClientIds.push(clientId);
|
|
75
|
+
}
|
|
76
|
+
for (const clientId of failedClientIds) this.removeClient(clientId);
|
|
77
|
+
}
|
|
78
|
+
onMessage(handler) {
|
|
79
|
+
this.handlers.push(handler);
|
|
80
|
+
}
|
|
81
|
+
close() {
|
|
82
|
+
if (this._readyState === "closed") return;
|
|
83
|
+
this._readyState = "closed";
|
|
84
|
+
for (const clientId of [...this.clients.keys()]) this.removeClient(clientId);
|
|
85
|
+
this.handlers.length = 0;
|
|
86
|
+
this.log.clear();
|
|
87
|
+
this.onClose?.();
|
|
88
|
+
}
|
|
89
|
+
get readyState() {
|
|
90
|
+
return this._readyState;
|
|
91
|
+
}
|
|
92
|
+
addClient(clientId, transport) {
|
|
93
|
+
if (this._readyState === "closed") return;
|
|
94
|
+
if (this.config.maxClients !== void 0 && this.clients.size >= this.config.maxClients) {
|
|
95
|
+
console.error(`[async-dom] Max clients (${this.config.maxClients}) reached, rejecting ${clientId}`);
|
|
96
|
+
transport.close();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (this.clients.has(clientId)) this.removeClient(clientId);
|
|
100
|
+
const replay = this.log.getReplayMessages();
|
|
101
|
+
for (const msg of replay) try {
|
|
102
|
+
transport.send(msg);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.error(`[async-dom] Failed to replay to client ${clientId}:`, err);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
transport.send({ type: "snapshotComplete" });
|
|
109
|
+
} catch (err) {
|
|
110
|
+
console.error(`[async-dom] Failed to send snapshotComplete to client ${clientId}:`, err);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.clients.set(clientId, transport);
|
|
114
|
+
transport.onMessage((message) => {
|
|
115
|
+
if (message.type === "event") message.clientId = clientId;
|
|
116
|
+
for (const h of this.handlers) try {
|
|
117
|
+
h(message);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.error("[async-dom] BroadcastTransport handler error:", err);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
const previousOnClose = transport.onClose;
|
|
123
|
+
transport.onClose = () => {
|
|
124
|
+
this.removeClient(clientId);
|
|
125
|
+
previousOnClose?.();
|
|
126
|
+
};
|
|
127
|
+
for (const h of this.handlers) try {
|
|
128
|
+
h({
|
|
129
|
+
type: "clientConnect",
|
|
130
|
+
clientId
|
|
131
|
+
});
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.error("[async-dom] BroadcastTransport handler error:", err);
|
|
134
|
+
}
|
|
135
|
+
this.config.onClientConnect?.(clientId);
|
|
136
|
+
}
|
|
137
|
+
removeClient(clientId) {
|
|
138
|
+
const transport = this.clients.get(clientId);
|
|
139
|
+
if (!transport) return;
|
|
140
|
+
transport.onClose = void 0;
|
|
141
|
+
this.clients.delete(clientId);
|
|
142
|
+
try {
|
|
143
|
+
transport.close();
|
|
144
|
+
} catch {}
|
|
145
|
+
for (const h of this.handlers) try {
|
|
146
|
+
h({
|
|
147
|
+
type: "clientDisconnect",
|
|
148
|
+
clientId
|
|
149
|
+
});
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.error("[async-dom] BroadcastTransport handler error:", err);
|
|
152
|
+
}
|
|
153
|
+
this.config.onClientDisconnect?.(clientId);
|
|
154
|
+
}
|
|
155
|
+
getClientCount() {
|
|
156
|
+
return this.clients.size;
|
|
157
|
+
}
|
|
158
|
+
getClientIds() {
|
|
159
|
+
return [...this.clients.keys()];
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/server/runner.ts
|
|
164
|
+
/**
|
|
165
|
+
* Creates a server-side async-dom app instance.
|
|
166
|
+
*
|
|
167
|
+
* Wraps `createWorkerDom` with the provided transport and runs the user's
|
|
168
|
+
* app module. Returns a destroy handle for cleanup on disconnect.
|
|
169
|
+
*
|
|
170
|
+
* Note: No SharedArrayBuffer is used — the async query fallback is used instead.
|
|
171
|
+
*/
|
|
172
|
+
function createServerApp(options) {
|
|
173
|
+
const { transport, appModule } = options;
|
|
174
|
+
const dom = require_worker_thread.createWorkerDom({ transport });
|
|
175
|
+
let ready;
|
|
176
|
+
try {
|
|
177
|
+
const result = appModule(dom);
|
|
178
|
+
ready = result instanceof Promise ? result.catch((err) => {
|
|
179
|
+
console.error("[async-dom] Server app module error:", err);
|
|
180
|
+
}) : Promise.resolve();
|
|
181
|
+
} catch (err) {
|
|
182
|
+
console.error("[async-dom] Server app module error:", err);
|
|
183
|
+
ready = Promise.resolve();
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
ready,
|
|
187
|
+
destroy() {
|
|
188
|
+
dom.destroy();
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/server/streaming-server.ts
|
|
194
|
+
/**
|
|
195
|
+
* Creates a streaming server that broadcasts one app's DOM mutations to N clients.
|
|
196
|
+
*
|
|
197
|
+
* This is an OPTIONAL alternative to `createServerApp` for scenarios where
|
|
198
|
+
* a single source of truth needs to be observed by multiple readers.
|
|
199
|
+
*/
|
|
200
|
+
function createStreamingServer(config) {
|
|
201
|
+
const broadcastTransport = new BroadcastTransport(config.broadcast);
|
|
202
|
+
const dom = require_worker_thread.createWorkerDom({
|
|
203
|
+
...config.workerDomConfig,
|
|
204
|
+
transport: broadcastTransport
|
|
205
|
+
});
|
|
206
|
+
let ready;
|
|
207
|
+
try {
|
|
208
|
+
const result = config.createApp(dom);
|
|
209
|
+
ready = result instanceof Promise ? result.catch((err) => {
|
|
210
|
+
console.error("[async-dom] Streaming server app error:", err);
|
|
211
|
+
}) : Promise.resolve();
|
|
212
|
+
} catch (err) {
|
|
213
|
+
console.error("[async-dom] Streaming server app error:", err);
|
|
214
|
+
ready = Promise.resolve();
|
|
215
|
+
}
|
|
216
|
+
let clientCounter = 0;
|
|
217
|
+
return {
|
|
218
|
+
handleConnection(socket, clientId) {
|
|
219
|
+
const id = require_sync_channel.createClientId(clientId ?? `client-${++clientCounter}`);
|
|
220
|
+
const transport = new require_ws_server_transport.WebSocketServerTransport(socket);
|
|
221
|
+
broadcastTransport.addClient(id, transport);
|
|
222
|
+
return id;
|
|
223
|
+
},
|
|
224
|
+
disconnectClient(clientId) {
|
|
225
|
+
broadcastTransport.removeClient(clientId);
|
|
226
|
+
},
|
|
227
|
+
getClientCount() {
|
|
228
|
+
return broadcastTransport.getClientCount();
|
|
229
|
+
},
|
|
230
|
+
getClientIds() {
|
|
231
|
+
return broadcastTransport.getClientIds();
|
|
232
|
+
},
|
|
233
|
+
getDom() {
|
|
234
|
+
return dom;
|
|
235
|
+
},
|
|
236
|
+
destroy() {
|
|
237
|
+
broadcastTransport.close();
|
|
238
|
+
dom.destroy();
|
|
239
|
+
},
|
|
240
|
+
ready
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
//#endregion
|
|
244
|
+
exports.BroadcastTransport = BroadcastTransport;
|
|
245
|
+
exports.MutationLog = MutationLog;
|
|
246
|
+
exports.WebSocketServerTransport = require_ws_server_transport.WebSocketServerTransport;
|
|
247
|
+
exports.createServerApp = createServerApp;
|
|
248
|
+
exports.createStreamingServer = createStreamingServer;
|
|
249
|
+
|
|
250
|
+
//# sourceMappingURL=server.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.cjs","names":["isMutationMessage","createWorkerDom","createWorkerDom","createClientId","WebSocketServerTransport"],"sources":["../src/server/mutation-log.ts","../src/server/broadcast-transport.ts","../src/server/runner.ts","../src/server/streaming-server.ts"],"sourcesContent":["import type { MutationMessage } from \"../core/protocol.ts\";\n\n/** Configuration for the mutation replay log. */\nexport interface MutationLogConfig {\n\t/** Maximum number of mutation messages to retain for replay to new clients. Default: `10_000`. */\n\tmaxEntries?: number;\n}\n\n/**\n * Ring buffer that stores recent MutationMessages for replay to new clients.\n *\n * Uses a fixed-capacity circular buffer with O(1) append and O(n) replay,\n * avoiding the O(n) cost of Array.shift() for eviction.\n */\nexport class MutationLog {\n\tprivate buffer: (MutationMessage | undefined)[];\n\tprivate head = 0; // index of the oldest entry (read pointer)\n\tprivate count = 0; // number of valid entries currently stored\n\tprivate maxEntries: number;\n\n\tconstructor(config?: MutationLogConfig) {\n\t\tthis.maxEntries = Math.max(0, config?.maxEntries ?? 10_000);\n\t\t// Allocate the backing array once; sparse for maxEntries=0\n\t\tthis.buffer = this.maxEntries > 0 ? new Array(this.maxEntries) : [];\n\t}\n\n\tappend(message: MutationMessage): void {\n\t\tif (this.maxEntries === 0) return;\n\n\t\tif (this.count < this.maxEntries) {\n\t\t\t// Buffer not yet full — write at (head + count) % capacity\n\t\t\tconst writeIndex = (this.head + this.count) % this.maxEntries;\n\t\t\tthis.buffer[writeIndex] = message;\n\t\t\tthis.count++;\n\t\t} else {\n\t\t\t// Buffer full — overwrite the oldest slot and advance head\n\t\t\tthis.buffer[this.head] = message;\n\t\t\tthis.head = (this.head + 1) % this.maxEntries;\n\t\t}\n\t}\n\n\tgetReplayMessages(): MutationMessage[] {\n\t\tconst result: MutationMessage[] = new Array(this.count);\n\t\tfor (let i = 0; i < this.count; i++) {\n\t\t\tresult[i] = this.buffer[(this.head + i) % this.maxEntries] as MutationMessage;\n\t\t}\n\t\treturn result;\n\t}\n\n\tsize(): number {\n\t\treturn this.count;\n\t}\n\n\tclear(): void {\n\t\tthis.head = 0;\n\t\tthis.count = 0;\n\t\t// Release object references so they can be GC'd\n\t\tthis.buffer.fill(undefined);\n\t}\n}\n","import type { ClientId, EventMessage, Message } from \"../core/protocol.ts\";\nimport { isMutationMessage } from \"../core/protocol.ts\";\nimport type { Transport, TransportReadyState } from \"../transport/base.ts\";\nimport type { MutationLogConfig } from \"./mutation-log.ts\";\nimport { MutationLog } from \"./mutation-log.ts\";\n\n/** Configuration for the broadcast transport used by the streaming server. */\nexport interface BroadcastTransportConfig {\n\t/** Settings for the mutation replay log sent to newly connecting clients. */\n\tmutationLog?: MutationLogConfig;\n\t/** Maximum number of concurrent clients. New connections are rejected when the limit is reached. */\n\tmaxClients?: number;\n\t/** Called when a new client connects. */\n\tonClientConnect?: (clientId: ClientId) => void;\n\t/** Called when a client disconnects. */\n\tonClientDisconnect?: (clientId: ClientId) => void;\n}\n\n/**\n * Transport that fans out messages from a single source to N client transports.\n *\n * Used by StreamingServer to broadcast DOM mutations to all connected readers.\n */\nexport class BroadcastTransport implements Transport {\n\tprivate clients = new Map<ClientId, Transport>();\n\tprivate handlers: Array<(message: Message) => void> = [];\n\tprivate log: MutationLog;\n\tprivate _readyState: TransportReadyState = \"open\";\n\tprivate config: BroadcastTransportConfig;\n\n\tonError?: (error: Error) => void;\n\tonClose?: () => void;\n\n\tconstructor(config?: BroadcastTransportConfig) {\n\t\tthis.config = config ?? {};\n\t\tthis.log = new MutationLog(config?.mutationLog);\n\t}\n\n\tsend(message: Message): void {\n\t\tif (this._readyState === \"closed\") return;\n\n\t\tif (isMutationMessage(message)) {\n\t\t\tthis.log.append(message);\n\t\t}\n\n\t\t// Collect failed clients, then remove after iteration.\n\t\t// A client is considered failed if send() throws OR if the transport\n\t\t// transitions to \"closed\" during send (e.g. sendRaw swallows the exception\n\t\t// but closes the underlying socket).\n\t\tconst failedClientIds: ClientId[] = [];\n\t\tfor (const [clientId, transport] of this.clients) {\n\t\t\ttry {\n\t\t\t\ttransport.send(message);\n\t\t\t\t// Fix 1: detect transports that closed silently during send\n\t\t\t\tif (transport.readyState === \"closed\") {\n\t\t\t\t\tfailedClientIds.push(clientId);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`[async-dom] Failed to send to client ${clientId}:`, err);\n\t\t\t\tfailedClientIds.push(clientId);\n\t\t\t}\n\t\t}\n\t\tfor (const clientId of failedClientIds) {\n\t\t\tthis.removeClient(clientId);\n\t\t}\n\t}\n\n\tonMessage(handler: (message: Message) => void): void {\n\t\tthis.handlers.push(handler);\n\t}\n\n\tclose(): void {\n\t\tif (this._readyState === \"closed\") return;\n\t\tthis._readyState = \"closed\";\n\n\t\t// Remove all clients (this also fires disconnect callbacks and closes transports)\n\t\tfor (const clientId of [...this.clients.keys()]) {\n\t\t\tthis.removeClient(clientId);\n\t\t}\n\n\t\t// Fix 3: clear handler array so retained references can be GC'd\n\t\tthis.handlers.length = 0;\n\n\t\tthis.log.clear();\n\t\tthis.onClose?.();\n\t}\n\n\tget readyState(): TransportReadyState {\n\t\treturn this._readyState;\n\t}\n\n\taddClient(clientId: ClientId, transport: Transport): void {\n\t\tif (this._readyState === \"closed\") return;\n\n\t\tif (this.config.maxClients !== undefined && this.clients.size >= this.config.maxClients) {\n\t\t\tconsole.error(\n\t\t\t\t`[async-dom] Max clients (${this.config.maxClients}) reached, rejecting ${clientId}`,\n\t\t\t);\n\t\t\ttransport.close();\n\t\t\treturn;\n\t\t}\n\n\t\t// Fix E: handle duplicate clientId by removing the old one first\n\t\tif (this.clients.has(clientId)) {\n\t\t\tthis.removeClient(clientId);\n\t\t}\n\n\t\t// Fix A: do replay BEFORE adding to this.clients so live mutations\n\t\t// from send() cannot interleave with the replay.\n\t\tconst replay = this.log.getReplayMessages();\n\t\tfor (const msg of replay) {\n\t\t\ttry {\n\t\t\t\ttransport.send(msg);\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`[async-dom] Failed to replay to client ${clientId}:`, err);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Fix B: wrap snapshotComplete in try/catch; on failure don't add client\n\t\ttry {\n\t\t\ttransport.send({ type: \"snapshotComplete\" });\n\t\t} catch (err) {\n\t\t\tconsole.error(`[async-dom] Failed to send snapshotComplete to client ${clientId}:`, err);\n\t\t\treturn;\n\t\t}\n\n\t\t// Now safe to register as a live client\n\t\tthis.clients.set(clientId, transport);\n\n\t\t// Forward events from this client to the source (stamp clientId)\n\t\ttransport.onMessage((message: Message) => {\n\t\t\tif (message.type === \"event\") {\n\t\t\t\t(message as EventMessage).clientId = clientId;\n\t\t\t}\n\t\t\tfor (const h of this.handlers) {\n\t\t\t\ttry {\n\t\t\t\t\th(message);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconsole.error(\"[async-dom] BroadcastTransport handler error:\", err);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\t// Fix D: chain existing onClose callback instead of overwriting\n\t\tconst previousOnClose = transport.onClose;\n\t\t// Auto-remove on disconnect\n\t\ttransport.onClose = () => {\n\t\t\tthis.removeClient(clientId);\n\t\t\tpreviousOnClose?.();\n\t\t};\n\n\t\t// Notify source of new client\n\t\tfor (const h of this.handlers) {\n\t\t\ttry {\n\t\t\t\th({ type: \"clientConnect\", clientId });\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\"[async-dom] BroadcastTransport handler error:\", err);\n\t\t\t}\n\t\t}\n\n\t\tthis.config.onClientConnect?.(clientId);\n\t}\n\n\tremoveClient(clientId: ClientId): void {\n\t\tconst transport = this.clients.get(clientId);\n\t\tif (!transport) return;\n\n\t\t// Nullify transport.onClose before doing anything else to prevent\n\t\t// re-entrant calls (e.g. if close() triggers onClose again)\n\t\ttransport.onClose = undefined;\n\n\t\tthis.clients.delete(clientId);\n\n\t\t// Fix 2: close the underlying transport so the socket is torn down,\n\t\t// drain timer stops, and no further messages are queued.\n\t\ttry {\n\t\t\ttransport.close();\n\t\t} catch {\n\t\t\t// Already closed — ignore\n\t\t}\n\n\t\t// Notify source of disconnection\n\t\tfor (const h of this.handlers) {\n\t\t\ttry {\n\t\t\t\th({ type: \"clientDisconnect\", clientId });\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\"[async-dom] BroadcastTransport handler error:\", err);\n\t\t\t}\n\t\t}\n\n\t\tthis.config.onClientDisconnect?.(clientId);\n\t}\n\n\tgetClientCount(): number {\n\t\treturn this.clients.size;\n\t}\n\n\tgetClientIds(): ClientId[] {\n\t\treturn [...this.clients.keys()];\n\t}\n}\n","import type { Transport } from \"../transport/base.ts\";\nimport type { WorkerDomResult } from \"../worker-thread/index.ts\";\nimport { createWorkerDom } from \"../worker-thread/index.ts\";\n\n/** Configuration for {@link createServerApp}. */\nexport interface ServerAppOptions {\n\t/** The transport connecting this server-side app to its main-thread client. */\n\ttransport: Transport;\n\t/** The user's application entry point. Receives a virtual DOM environment. May return a Promise. */\n\tappModule: (dom: WorkerDomResult) => void | Promise<void>;\n}\n\n/**\n * Creates a server-side async-dom app instance.\n *\n * Wraps `createWorkerDom` with the provided transport and runs the user's\n * app module. Returns a destroy handle for cleanup on disconnect.\n *\n * Note: No SharedArrayBuffer is used — the async query fallback is used instead.\n */\nexport function createServerApp(options: ServerAppOptions): {\n\tdestroy: () => void;\n\tready: Promise<void>;\n} {\n\tconst { transport, appModule } = options;\n\n\tconst dom = createWorkerDom({ transport });\n\n\t// Run the user's app module, catching errors so one connection\n\t// failure doesn't crash the server process\n\tlet ready: Promise<void>;\n\ttry {\n\t\tconst result = appModule(dom);\n\t\tready =\n\t\t\tresult instanceof Promise\n\t\t\t\t? result.catch((err) => {\n\t\t\t\t\t\tconsole.error(\"[async-dom] Server app module error:\", err);\n\t\t\t\t\t})\n\t\t\t\t: Promise.resolve();\n\t} catch (err) {\n\t\tconsole.error(\"[async-dom] Server app module error:\", err);\n\t\tready = Promise.resolve();\n\t}\n\n\treturn {\n\t\tready,\n\t\tdestroy() {\n\t\t\tdom.destroy();\n\t\t},\n\t};\n}\n","import type { ClientId } from \"../core/protocol.ts\";\nimport { createClientId } from \"../core/protocol.ts\";\nimport type { WebSocketLike } from \"../transport/ws-server-transport.ts\";\nimport { WebSocketServerTransport } from \"../transport/ws-server-transport.ts\";\nimport type { WorkerDomConfig, WorkerDomResult } from \"../worker-thread/index.ts\";\nimport { createWorkerDom } from \"../worker-thread/index.ts\";\nimport type { BroadcastTransportConfig } from \"./broadcast-transport.ts\";\nimport { BroadcastTransport } from \"./broadcast-transport.ts\";\n\n/** Configuration for {@link createStreamingServer}. */\nexport interface StreamingServerConfig {\n\t/** The application entry point. Receives a virtual DOM environment shared across all clients. */\n\tcreateApp: (dom: WorkerDomResult) => void | Promise<void>;\n\t/** Optional overrides for the underlying `WorkerDomConfig` (transport is managed internally). */\n\tworkerDomConfig?: Partial<Omit<WorkerDomConfig, \"transport\">>;\n\t/** Configuration for the broadcast transport (mutation log size, max clients, etc.). */\n\tbroadcast?: BroadcastTransportConfig;\n}\n\n/** Handle returned by {@link createStreamingServer}. */\nexport interface StreamingServerInstance {\n\t/** Register a new WebSocket client. Returns the assigned `ClientId`. */\n\thandleConnection(socket: WebSocketLike, clientId?: string): ClientId;\n\t/** Disconnect and clean up a client by its `ClientId`. */\n\tdisconnectClient(clientId: ClientId): void;\n\t/** Return the number of currently connected clients. */\n\tgetClientCount(): number;\n\t/** Return the IDs of all currently connected clients. */\n\tgetClientIds(): ClientId[];\n\t/** Access the shared virtual DOM instance. */\n\tgetDom(): WorkerDomResult;\n\t/** Shut down the server, disconnecting all clients and destroying the virtual DOM. */\n\tdestroy(): void;\n\t/** Resolves when the app module has finished initializing. */\n\tready: Promise<void>;\n}\n\n/**\n * Creates a streaming server that broadcasts one app's DOM mutations to N clients.\n *\n * This is an OPTIONAL alternative to `createServerApp` for scenarios where\n * a single source of truth needs to be observed by multiple readers.\n */\nexport function createStreamingServer(config: StreamingServerConfig): StreamingServerInstance {\n\tconst broadcastTransport = new BroadcastTransport(config.broadcast);\n\n\tconst dom = createWorkerDom({\n\t\t...config.workerDomConfig,\n\t\ttransport: broadcastTransport,\n\t});\n\n\t// Run the user's app module, catching errors so one failure\n\t// doesn't crash the server process\n\tlet ready: Promise<void>;\n\ttry {\n\t\tconst result = config.createApp(dom);\n\t\tready =\n\t\t\tresult instanceof Promise\n\t\t\t\t? result.catch((err) => {\n\t\t\t\t\t\tconsole.error(\"[async-dom] Streaming server app error:\", err);\n\t\t\t\t\t})\n\t\t\t\t: Promise.resolve();\n\t} catch (err) {\n\t\tconsole.error(\"[async-dom] Streaming server app error:\", err);\n\t\tready = Promise.resolve();\n\t}\n\n\tlet clientCounter = 0;\n\n\treturn {\n\t\thandleConnection(socket: WebSocketLike, clientId?: string): ClientId {\n\t\t\tconst id = createClientId(clientId ?? `client-${++clientCounter}`);\n\t\t\tconst transport = new WebSocketServerTransport(socket);\n\t\t\tbroadcastTransport.addClient(id, transport);\n\t\t\treturn id;\n\t\t},\n\n\t\tdisconnectClient(clientId: ClientId): void {\n\t\t\tbroadcastTransport.removeClient(clientId);\n\t\t},\n\n\t\tgetClientCount(): number {\n\t\t\treturn broadcastTransport.getClientCount();\n\t\t},\n\n\t\tgetClientIds(): ClientId[] {\n\t\t\treturn broadcastTransport.getClientIds();\n\t\t},\n\n\t\tgetDom(): WorkerDomResult {\n\t\t\treturn dom;\n\t\t},\n\n\t\tdestroy(): void {\n\t\t\tbroadcastTransport.close();\n\t\t\tdom.destroy();\n\t\t},\n\n\t\tready,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { h as MutationMessage, n as TransportReadyState, o as ClientId, p as Message, t as Transport } from "./base.cjs";
|
|
2
|
+
import { n as WebSocketServerTransport, t as WebSocketLike } from "./ws-server-transport.cjs";
|
|
3
|
+
import { n as WorkerDomResult, t as WorkerDomConfig } from "./index3.cjs";
|
|
4
|
+
|
|
5
|
+
//#region src/server/mutation-log.d.ts
|
|
6
|
+
/** Configuration for the mutation replay log. */
|
|
7
|
+
interface MutationLogConfig {
|
|
8
|
+
/** Maximum number of mutation messages to retain for replay to new clients. Default: `10_000`. */
|
|
9
|
+
maxEntries?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Ring buffer that stores recent MutationMessages for replay to new clients.
|
|
13
|
+
*
|
|
14
|
+
* Uses a fixed-capacity circular buffer with O(1) append and O(n) replay,
|
|
15
|
+
* avoiding the O(n) cost of Array.shift() for eviction.
|
|
16
|
+
*/
|
|
17
|
+
declare class MutationLog {
|
|
18
|
+
private buffer;
|
|
19
|
+
private head;
|
|
20
|
+
private count;
|
|
21
|
+
private maxEntries;
|
|
22
|
+
constructor(config?: MutationLogConfig);
|
|
23
|
+
append(message: MutationMessage): void;
|
|
24
|
+
getReplayMessages(): MutationMessage[];
|
|
25
|
+
size(): number;
|
|
26
|
+
clear(): void;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=mutation-log.d.ts.map
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/server/broadcast-transport.d.ts
|
|
31
|
+
/** Configuration for the broadcast transport used by the streaming server. */
|
|
32
|
+
interface BroadcastTransportConfig {
|
|
33
|
+
/** Settings for the mutation replay log sent to newly connecting clients. */
|
|
34
|
+
mutationLog?: MutationLogConfig;
|
|
35
|
+
/** Maximum number of concurrent clients. New connections are rejected when the limit is reached. */
|
|
36
|
+
maxClients?: number;
|
|
37
|
+
/** Called when a new client connects. */
|
|
38
|
+
onClientConnect?: (clientId: ClientId) => void;
|
|
39
|
+
/** Called when a client disconnects. */
|
|
40
|
+
onClientDisconnect?: (clientId: ClientId) => void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Transport that fans out messages from a single source to N client transports.
|
|
44
|
+
*
|
|
45
|
+
* Used by StreamingServer to broadcast DOM mutations to all connected readers.
|
|
46
|
+
*/
|
|
47
|
+
declare class BroadcastTransport implements Transport {
|
|
48
|
+
private clients;
|
|
49
|
+
private handlers;
|
|
50
|
+
private log;
|
|
51
|
+
private _readyState;
|
|
52
|
+
private config;
|
|
53
|
+
onError?: (error: Error) => void;
|
|
54
|
+
onClose?: () => void;
|
|
55
|
+
constructor(config?: BroadcastTransportConfig);
|
|
56
|
+
send(message: Message): void;
|
|
57
|
+
onMessage(handler: (message: Message) => void): void;
|
|
58
|
+
close(): void;
|
|
59
|
+
get readyState(): TransportReadyState;
|
|
60
|
+
addClient(clientId: ClientId, transport: Transport): void;
|
|
61
|
+
removeClient(clientId: ClientId): void;
|
|
62
|
+
getClientCount(): number;
|
|
63
|
+
getClientIds(): ClientId[];
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=broadcast-transport.d.ts.map
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/server/runner.d.ts
|
|
68
|
+
/** Configuration for {@link createServerApp}. */
|
|
69
|
+
interface ServerAppOptions {
|
|
70
|
+
/** The transport connecting this server-side app to its main-thread client. */
|
|
71
|
+
transport: Transport;
|
|
72
|
+
/** The user's application entry point. Receives a virtual DOM environment. May return a Promise. */
|
|
73
|
+
appModule: (dom: WorkerDomResult) => void | Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Creates a server-side async-dom app instance.
|
|
77
|
+
*
|
|
78
|
+
* Wraps `createWorkerDom` with the provided transport and runs the user's
|
|
79
|
+
* app module. Returns a destroy handle for cleanup on disconnect.
|
|
80
|
+
*
|
|
81
|
+
* Note: No SharedArrayBuffer is used — the async query fallback is used instead.
|
|
82
|
+
*/
|
|
83
|
+
declare function createServerApp(options: ServerAppOptions): {
|
|
84
|
+
destroy: () => void;
|
|
85
|
+
ready: Promise<void>;
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/server/streaming-server.d.ts
|
|
90
|
+
/** Configuration for {@link createStreamingServer}. */
|
|
91
|
+
interface StreamingServerConfig {
|
|
92
|
+
/** The application entry point. Receives a virtual DOM environment shared across all clients. */
|
|
93
|
+
createApp: (dom: WorkerDomResult) => void | Promise<void>;
|
|
94
|
+
/** Optional overrides for the underlying `WorkerDomConfig` (transport is managed internally). */
|
|
95
|
+
workerDomConfig?: Partial<Omit<WorkerDomConfig, "transport">>;
|
|
96
|
+
/** Configuration for the broadcast transport (mutation log size, max clients, etc.). */
|
|
97
|
+
broadcast?: BroadcastTransportConfig;
|
|
98
|
+
}
|
|
99
|
+
/** Handle returned by {@link createStreamingServer}. */
|
|
100
|
+
interface StreamingServerInstance {
|
|
101
|
+
/** Register a new WebSocket client. Returns the assigned `ClientId`. */
|
|
102
|
+
handleConnection(socket: WebSocketLike, clientId?: string): ClientId;
|
|
103
|
+
/** Disconnect and clean up a client by its `ClientId`. */
|
|
104
|
+
disconnectClient(clientId: ClientId): void;
|
|
105
|
+
/** Return the number of currently connected clients. */
|
|
106
|
+
getClientCount(): number;
|
|
107
|
+
/** Return the IDs of all currently connected clients. */
|
|
108
|
+
getClientIds(): ClientId[];
|
|
109
|
+
/** Access the shared virtual DOM instance. */
|
|
110
|
+
getDom(): WorkerDomResult;
|
|
111
|
+
/** Shut down the server, disconnecting all clients and destroying the virtual DOM. */
|
|
112
|
+
destroy(): void;
|
|
113
|
+
/** Resolves when the app module has finished initializing. */
|
|
114
|
+
ready: Promise<void>;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Creates a streaming server that broadcasts one app's DOM mutations to N clients.
|
|
118
|
+
*
|
|
119
|
+
* This is an OPTIONAL alternative to `createServerApp` for scenarios where
|
|
120
|
+
* a single source of truth needs to be observed by multiple readers.
|
|
121
|
+
*/
|
|
122
|
+
declare function createStreamingServer(config: StreamingServerConfig): StreamingServerInstance;
|
|
123
|
+
//# sourceMappingURL=streaming-server.d.ts.map
|
|
124
|
+
|
|
125
|
+
//#endregion
|
|
126
|
+
export { BroadcastTransport, type BroadcastTransportConfig, MutationLog, type MutationLogConfig, type ServerAppOptions, type StreamingServerConfig, type StreamingServerInstance, type WebSocketLike, WebSocketServerTransport, createServerApp, createStreamingServer };
|
|
127
|
+
//# sourceMappingURL=server.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.cts","names":[],"sources":["../src/server/mutation-log.ts","../src/server/broadcast-transport.ts","../src/server/runner.ts","../src/server/streaming-server.ts"],"sourcesContent":[],"mappings":";;;;;;UAGiB,iBAAA;;;AAAjB;AAWA;;;;;;cAAa,WAAA;;;ECPI,QAAA,KAAA;EAAwB,QAAA,UAAA;aAE1B,CAAA,MAAA,CAAA,EDWO,iBCXP;QAIe,CAAA,OAAA,EDab,eCba,CAAA,EAAA,IAAA;mBAEG,CAAA,CAAA,ED0BX,eC1BW,EAAA;EAAQ,IAAA,CAAA,CAAA,EAAA,MAAA;EAQ5B,KAAA,CAAA,CAAA,EAAA,IAAA;;;;;;UAhBI,wBAAA;EDJA;EAWJ,WAAA,CAAA,ECLE,iBDKS;EAAA;YAMF,CAAA,EAAA,MAAA;;iBAqBA,CAAA,EAAA,CAAA,QAAA,EC5BQ,QD4BR,EAAA,GAAA,IAAA;EAAe;kCC1BJ;;;AARjC;;;;AAQiC,cAQpB,kBAAA,YAA8B,SARV,CAAA;EAAQ,QAAA,OAAA;EAQ5B,QAAA,QAAA;EAAmB,QAAA,GAAA;UAOb,WAAA;UAGG,MAAA;SAKP,CAAA,EAAA,CAAA,KAAA,EARI,KAQJ,EAAA,GAAA,IAAA;SA6Be,CAAA,EAAA,GAAA,GAAA,IAAA;aAoBX,CAAA,MAAA,CAAA,EAtDG,wBAsDH;MAIE,CAAA,OAAA,EArDN,OAqDM,CAAA,EAAA,IAAA;WAAqB,CAAA,OAAA,EAAA,CAAA,OAAA,EAxBZ,OAwBY,EAAA,GAAA,IAAA,CAAA,EAAA,IAAA;OAyElB,CAAA,CAAA,EAAA,IAAA;MAkCP,UAAA,CAAA,CAAA,EA/GE,mBA+GF;WA/K0B,CAAA,QAAA,EAoEtB,QApEsB,EAAA,SAAA,EAoED,SApEC,CAAA,EAAA,IAAA;EAAS,YAAA,CAAA,QAAA,EA6I5B,QA7I4B,CAAA,EAAA,IAAA;;kBA+KnC;;ACjMjB;;;;UAAiB,gBAAA;;EFFA,SAAA,EEIL,SFJsB;EAWrB;EAAW,SAAA,EAAA,CAAA,GAAA,EELN,eFKM,EAAA,GAAA,IAAA,GELqB,OFKrB,CAAA,IAAA,CAAA;;;;;;;;ACPxB;;AAEe,iBCWC,eAAA,CDXD,OAAA,ECW0B,gBDX1B,CAAA,EAAA;SAIe,EAAA,GAAA,GAAA,IAAA;OAEG,ECOzB,ODPyB,CAAA,IAAA,CAAA;CAAQ;AAQzC;;;;ADpBiB,UGOA,qBAAA,CHPiB;EAWrB;EAAW,SAAA,EAAA,CAAA,GAAA,EGFN,eHEM,EAAA,GAAA,IAAA,GGFqB,OHErB,CAAA,IAAA,CAAA;;iBAYP,CAAA,EGZE,OHYF,CGZU,IHYV,CGZe,eHYf,EAAA,WAAA,CAAA,CAAA;;EAeoB,SAAA,CAAA,EGzBxB,wBHyBwB;;;UGrBpB,uBAAA;EFbA;EAAwB,gBAAA,CAAA,MAAA,EEef,aFfe,EAAA,QAAA,CAAA,EAAA,MAAA,CAAA,EEeoB,QFfpB;;kBAMX,CAAA,QAAA,EEWF,QFXE,CAAA,EAAA,IAAA;;EAEW,cAAA,EAAA,EAAA,MAAA;EAQ5B;EAAmB,YAAA,EAAA,EEKf,QFLe,EAAA;;QAUV,EAAA,EEHX,eFGW;;SAkCQ,EAAA,EAAA,IAAA;;OAwBT,EEzDb,OFyDa,CAAA,IAAA,CAAA;;;;;;;;iBEhDL,qBAAA,SAA8B,wBAAwB;ADtCtE"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { h as MutationMessage, n as TransportReadyState, o as ClientId, p as Message, t as Transport } from "./base.js";
|
|
2
|
+
import { n as WebSocketServerTransport, t as WebSocketLike } from "./ws-server-transport.js";
|
|
3
|
+
import { n as WorkerDomResult, t as WorkerDomConfig } from "./index3.js";
|
|
4
|
+
|
|
5
|
+
//#region src/server/mutation-log.d.ts
|
|
6
|
+
/** Configuration for the mutation replay log. */
|
|
7
|
+
interface MutationLogConfig {
|
|
8
|
+
/** Maximum number of mutation messages to retain for replay to new clients. Default: `10_000`. */
|
|
9
|
+
maxEntries?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Ring buffer that stores recent MutationMessages for replay to new clients.
|
|
13
|
+
*
|
|
14
|
+
* Uses a fixed-capacity circular buffer with O(1) append and O(n) replay,
|
|
15
|
+
* avoiding the O(n) cost of Array.shift() for eviction.
|
|
16
|
+
*/
|
|
17
|
+
declare class MutationLog {
|
|
18
|
+
private buffer;
|
|
19
|
+
private head;
|
|
20
|
+
private count;
|
|
21
|
+
private maxEntries;
|
|
22
|
+
constructor(config?: MutationLogConfig);
|
|
23
|
+
append(message: MutationMessage): void;
|
|
24
|
+
getReplayMessages(): MutationMessage[];
|
|
25
|
+
size(): number;
|
|
26
|
+
clear(): void;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=mutation-log.d.ts.map
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/server/broadcast-transport.d.ts
|
|
31
|
+
/** Configuration for the broadcast transport used by the streaming server. */
|
|
32
|
+
interface BroadcastTransportConfig {
|
|
33
|
+
/** Settings for the mutation replay log sent to newly connecting clients. */
|
|
34
|
+
mutationLog?: MutationLogConfig;
|
|
35
|
+
/** Maximum number of concurrent clients. New connections are rejected when the limit is reached. */
|
|
36
|
+
maxClients?: number;
|
|
37
|
+
/** Called when a new client connects. */
|
|
38
|
+
onClientConnect?: (clientId: ClientId) => void;
|
|
39
|
+
/** Called when a client disconnects. */
|
|
40
|
+
onClientDisconnect?: (clientId: ClientId) => void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Transport that fans out messages from a single source to N client transports.
|
|
44
|
+
*
|
|
45
|
+
* Used by StreamingServer to broadcast DOM mutations to all connected readers.
|
|
46
|
+
*/
|
|
47
|
+
declare class BroadcastTransport implements Transport {
|
|
48
|
+
private clients;
|
|
49
|
+
private handlers;
|
|
50
|
+
private log;
|
|
51
|
+
private _readyState;
|
|
52
|
+
private config;
|
|
53
|
+
onError?: (error: Error) => void;
|
|
54
|
+
onClose?: () => void;
|
|
55
|
+
constructor(config?: BroadcastTransportConfig);
|
|
56
|
+
send(message: Message): void;
|
|
57
|
+
onMessage(handler: (message: Message) => void): void;
|
|
58
|
+
close(): void;
|
|
59
|
+
get readyState(): TransportReadyState;
|
|
60
|
+
addClient(clientId: ClientId, transport: Transport): void;
|
|
61
|
+
removeClient(clientId: ClientId): void;
|
|
62
|
+
getClientCount(): number;
|
|
63
|
+
getClientIds(): ClientId[];
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=broadcast-transport.d.ts.map
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/server/runner.d.ts
|
|
68
|
+
/** Configuration for {@link createServerApp}. */
|
|
69
|
+
interface ServerAppOptions {
|
|
70
|
+
/** The transport connecting this server-side app to its main-thread client. */
|
|
71
|
+
transport: Transport;
|
|
72
|
+
/** The user's application entry point. Receives a virtual DOM environment. May return a Promise. */
|
|
73
|
+
appModule: (dom: WorkerDomResult) => void | Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Creates a server-side async-dom app instance.
|
|
77
|
+
*
|
|
78
|
+
* Wraps `createWorkerDom` with the provided transport and runs the user's
|
|
79
|
+
* app module. Returns a destroy handle for cleanup on disconnect.
|
|
80
|
+
*
|
|
81
|
+
* Note: No SharedArrayBuffer is used — the async query fallback is used instead.
|
|
82
|
+
*/
|
|
83
|
+
declare function createServerApp(options: ServerAppOptions): {
|
|
84
|
+
destroy: () => void;
|
|
85
|
+
ready: Promise<void>;
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/server/streaming-server.d.ts
|
|
90
|
+
/** Configuration for {@link createStreamingServer}. */
|
|
91
|
+
interface StreamingServerConfig {
|
|
92
|
+
/** The application entry point. Receives a virtual DOM environment shared across all clients. */
|
|
93
|
+
createApp: (dom: WorkerDomResult) => void | Promise<void>;
|
|
94
|
+
/** Optional overrides for the underlying `WorkerDomConfig` (transport is managed internally). */
|
|
95
|
+
workerDomConfig?: Partial<Omit<WorkerDomConfig, "transport">>;
|
|
96
|
+
/** Configuration for the broadcast transport (mutation log size, max clients, etc.). */
|
|
97
|
+
broadcast?: BroadcastTransportConfig;
|
|
98
|
+
}
|
|
99
|
+
/** Handle returned by {@link createStreamingServer}. */
|
|
100
|
+
interface StreamingServerInstance {
|
|
101
|
+
/** Register a new WebSocket client. Returns the assigned `ClientId`. */
|
|
102
|
+
handleConnection(socket: WebSocketLike, clientId?: string): ClientId;
|
|
103
|
+
/** Disconnect and clean up a client by its `ClientId`. */
|
|
104
|
+
disconnectClient(clientId: ClientId): void;
|
|
105
|
+
/** Return the number of currently connected clients. */
|
|
106
|
+
getClientCount(): number;
|
|
107
|
+
/** Return the IDs of all currently connected clients. */
|
|
108
|
+
getClientIds(): ClientId[];
|
|
109
|
+
/** Access the shared virtual DOM instance. */
|
|
110
|
+
getDom(): WorkerDomResult;
|
|
111
|
+
/** Shut down the server, disconnecting all clients and destroying the virtual DOM. */
|
|
112
|
+
destroy(): void;
|
|
113
|
+
/** Resolves when the app module has finished initializing. */
|
|
114
|
+
ready: Promise<void>;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Creates a streaming server that broadcasts one app's DOM mutations to N clients.
|
|
118
|
+
*
|
|
119
|
+
* This is an OPTIONAL alternative to `createServerApp` for scenarios where
|
|
120
|
+
* a single source of truth needs to be observed by multiple readers.
|
|
121
|
+
*/
|
|
122
|
+
declare function createStreamingServer(config: StreamingServerConfig): StreamingServerInstance;
|
|
123
|
+
//# sourceMappingURL=streaming-server.d.ts.map
|
|
124
|
+
|
|
125
|
+
//#endregion
|
|
126
|
+
export { BroadcastTransport, type BroadcastTransportConfig, MutationLog, type MutationLogConfig, type ServerAppOptions, type StreamingServerConfig, type StreamingServerInstance, type WebSocketLike, WebSocketServerTransport, createServerApp, createStreamingServer };
|
|
127
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","names":[],"sources":["../src/server/mutation-log.ts","../src/server/broadcast-transport.ts","../src/server/runner.ts","../src/server/streaming-server.ts"],"sourcesContent":[],"mappings":";;;;;;UAGiB,iBAAA;;;AAAjB;AAWA;;;;;;cAAa,WAAA;;;ECPI,QAAA,KAAA;EAAwB,QAAA,UAAA;aAE1B,CAAA,MAAA,CAAA,EDWO,iBCXP;QAIe,CAAA,OAAA,EDab,eCba,CAAA,EAAA,IAAA;mBAEG,CAAA,CAAA,ED0BX,eC1BW,EAAA;EAAQ,IAAA,CAAA,CAAA,EAAA,MAAA;EAQ5B,KAAA,CAAA,CAAA,EAAA,IAAA;;;;;;UAhBI,wBAAA;EDJA;EAWJ,WAAA,CAAA,ECLE,iBDKS;EAAA;YAMF,CAAA,EAAA,MAAA;;iBAqBA,CAAA,EAAA,CAAA,QAAA,EC5BQ,QD4BR,EAAA,GAAA,IAAA;EAAe;kCC1BJ;;;AARjC;;;;AAQiC,cAQpB,kBAAA,YAA8B,SARV,CAAA;EAAQ,QAAA,OAAA;EAQ5B,QAAA,QAAA;EAAmB,QAAA,GAAA;UAOb,WAAA;UAGG,MAAA;SAKP,CAAA,EAAA,CAAA,KAAA,EARI,KAQJ,EAAA,GAAA,IAAA;SA6Be,CAAA,EAAA,GAAA,GAAA,IAAA;aAoBX,CAAA,MAAA,CAAA,EAtDG,wBAsDH;MAIE,CAAA,OAAA,EArDN,OAqDM,CAAA,EAAA,IAAA;WAAqB,CAAA,OAAA,EAAA,CAAA,OAAA,EAxBZ,OAwBY,EAAA,GAAA,IAAA,CAAA,EAAA,IAAA;OAyElB,CAAA,CAAA,EAAA,IAAA;MAkCP,UAAA,CAAA,CAAA,EA/GE,mBA+GF;WA/K0B,CAAA,QAAA,EAoEtB,QApEsB,EAAA,SAAA,EAoED,SApEC,CAAA,EAAA,IAAA;EAAS,YAAA,CAAA,QAAA,EA6I5B,QA7I4B,CAAA,EAAA,IAAA;;kBA+KnC;;ACjMjB;;;;UAAiB,gBAAA;;EFFA,SAAA,EEIL,SFJsB;EAWrB;EAAW,SAAA,EAAA,CAAA,GAAA,EELN,eFKM,EAAA,GAAA,IAAA,GELqB,OFKrB,CAAA,IAAA,CAAA;;;;;;;;ACPxB;;AAEe,iBCWC,eAAA,CDXD,OAAA,ECW0B,gBDX1B,CAAA,EAAA;SAIe,EAAA,GAAA,GAAA,IAAA;OAEG,ECOzB,ODPyB,CAAA,IAAA,CAAA;CAAQ;AAQzC;;;;ADpBiB,UGOA,qBAAA,CHPiB;EAWrB;EAAW,SAAA,EAAA,CAAA,GAAA,EGFN,eHEM,EAAA,GAAA,IAAA,GGFqB,OHErB,CAAA,IAAA,CAAA;;iBAYP,CAAA,EGZE,OHYF,CGZU,IHYV,CGZe,eHYf,EAAA,WAAA,CAAA,CAAA;;EAeoB,SAAA,CAAA,EGzBxB,wBHyBwB;;;UGrBpB,uBAAA;EFbA;EAAwB,gBAAA,CAAA,MAAA,EEef,aFfe,EAAA,QAAA,CAAA,EAAA,MAAA,CAAA,EEeoB,QFfpB;;kBAMX,CAAA,QAAA,EEWF,QFXE,CAAA,EAAA,IAAA;;EAEW,cAAA,EAAA,EAAA,MAAA;EAQ5B;EAAmB,YAAA,EAAA,EEKf,QFLe,EAAA;;QAUV,EAAA,EEHX,eFGW;;SAkCQ,EAAA,EAAA,IAAA;;OAwBT,EEzDb,OFyDa,CAAA,IAAA,CAAA;;;;;;;;iBEhDL,qBAAA,SAA8B,wBAAwB;ADtCtE"}
|