@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.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { f as isMutationMessage, l as createClientId } from "./sync-channel.js";
|
|
2
|
+
import { t as createWorkerDom } from "./worker-thread.js";
|
|
3
|
+
import { t as WebSocketServerTransport } from "./ws-server-transport.js";
|
|
4
|
+
//#region src/server/mutation-log.ts
|
|
5
|
+
/**
|
|
6
|
+
* Ring buffer that stores recent MutationMessages for replay to new clients.
|
|
7
|
+
*
|
|
8
|
+
* Uses a fixed-capacity circular buffer with O(1) append and O(n) replay,
|
|
9
|
+
* avoiding the O(n) cost of Array.shift() for eviction.
|
|
10
|
+
*/
|
|
11
|
+
var MutationLog = class {
|
|
12
|
+
buffer;
|
|
13
|
+
head = 0;
|
|
14
|
+
count = 0;
|
|
15
|
+
maxEntries;
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.maxEntries = Math.max(0, config?.maxEntries ?? 1e4);
|
|
18
|
+
this.buffer = this.maxEntries > 0 ? new Array(this.maxEntries) : [];
|
|
19
|
+
}
|
|
20
|
+
append(message) {
|
|
21
|
+
if (this.maxEntries === 0) return;
|
|
22
|
+
if (this.count < this.maxEntries) {
|
|
23
|
+
const writeIndex = (this.head + this.count) % this.maxEntries;
|
|
24
|
+
this.buffer[writeIndex] = message;
|
|
25
|
+
this.count++;
|
|
26
|
+
} else {
|
|
27
|
+
this.buffer[this.head] = message;
|
|
28
|
+
this.head = (this.head + 1) % this.maxEntries;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
getReplayMessages() {
|
|
32
|
+
const result = new Array(this.count);
|
|
33
|
+
for (let i = 0; i < this.count; i++) result[i] = this.buffer[(this.head + i) % this.maxEntries];
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
size() {
|
|
37
|
+
return this.count;
|
|
38
|
+
}
|
|
39
|
+
clear() {
|
|
40
|
+
this.head = 0;
|
|
41
|
+
this.count = 0;
|
|
42
|
+
this.buffer.fill(void 0);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/server/broadcast-transport.ts
|
|
47
|
+
/**
|
|
48
|
+
* Transport that fans out messages from a single source to N client transports.
|
|
49
|
+
*
|
|
50
|
+
* Used by StreamingServer to broadcast DOM mutations to all connected readers.
|
|
51
|
+
*/
|
|
52
|
+
var BroadcastTransport = class {
|
|
53
|
+
clients = /* @__PURE__ */ new Map();
|
|
54
|
+
handlers = [];
|
|
55
|
+
log;
|
|
56
|
+
_readyState = "open";
|
|
57
|
+
config;
|
|
58
|
+
onError;
|
|
59
|
+
onClose;
|
|
60
|
+
constructor(config) {
|
|
61
|
+
this.config = config ?? {};
|
|
62
|
+
this.log = new MutationLog(config?.mutationLog);
|
|
63
|
+
}
|
|
64
|
+
send(message) {
|
|
65
|
+
if (this._readyState === "closed") return;
|
|
66
|
+
if (isMutationMessage(message)) this.log.append(message);
|
|
67
|
+
const failedClientIds = [];
|
|
68
|
+
for (const [clientId, transport] of this.clients) try {
|
|
69
|
+
transport.send(message);
|
|
70
|
+
if (transport.readyState === "closed") failedClientIds.push(clientId);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.error(`[async-dom] Failed to send to client ${clientId}:`, err);
|
|
73
|
+
failedClientIds.push(clientId);
|
|
74
|
+
}
|
|
75
|
+
for (const clientId of failedClientIds) this.removeClient(clientId);
|
|
76
|
+
}
|
|
77
|
+
onMessage(handler) {
|
|
78
|
+
this.handlers.push(handler);
|
|
79
|
+
}
|
|
80
|
+
close() {
|
|
81
|
+
if (this._readyState === "closed") return;
|
|
82
|
+
this._readyState = "closed";
|
|
83
|
+
for (const clientId of [...this.clients.keys()]) this.removeClient(clientId);
|
|
84
|
+
this.handlers.length = 0;
|
|
85
|
+
this.log.clear();
|
|
86
|
+
this.onClose?.();
|
|
87
|
+
}
|
|
88
|
+
get readyState() {
|
|
89
|
+
return this._readyState;
|
|
90
|
+
}
|
|
91
|
+
addClient(clientId, transport) {
|
|
92
|
+
if (this._readyState === "closed") return;
|
|
93
|
+
if (this.config.maxClients !== void 0 && this.clients.size >= this.config.maxClients) {
|
|
94
|
+
console.error(`[async-dom] Max clients (${this.config.maxClients}) reached, rejecting ${clientId}`);
|
|
95
|
+
transport.close();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (this.clients.has(clientId)) this.removeClient(clientId);
|
|
99
|
+
const replay = this.log.getReplayMessages();
|
|
100
|
+
for (const msg of replay) try {
|
|
101
|
+
transport.send(msg);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error(`[async-dom] Failed to replay to client ${clientId}:`, err);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
transport.send({ type: "snapshotComplete" });
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.error(`[async-dom] Failed to send snapshotComplete to client ${clientId}:`, err);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
this.clients.set(clientId, transport);
|
|
113
|
+
transport.onMessage((message) => {
|
|
114
|
+
if (message.type === "event") message.clientId = clientId;
|
|
115
|
+
for (const h of this.handlers) try {
|
|
116
|
+
h(message);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
console.error("[async-dom] BroadcastTransport handler error:", err);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
const previousOnClose = transport.onClose;
|
|
122
|
+
transport.onClose = () => {
|
|
123
|
+
this.removeClient(clientId);
|
|
124
|
+
previousOnClose?.();
|
|
125
|
+
};
|
|
126
|
+
for (const h of this.handlers) try {
|
|
127
|
+
h({
|
|
128
|
+
type: "clientConnect",
|
|
129
|
+
clientId
|
|
130
|
+
});
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.error("[async-dom] BroadcastTransport handler error:", err);
|
|
133
|
+
}
|
|
134
|
+
this.config.onClientConnect?.(clientId);
|
|
135
|
+
}
|
|
136
|
+
removeClient(clientId) {
|
|
137
|
+
const transport = this.clients.get(clientId);
|
|
138
|
+
if (!transport) return;
|
|
139
|
+
transport.onClose = void 0;
|
|
140
|
+
this.clients.delete(clientId);
|
|
141
|
+
try {
|
|
142
|
+
transport.close();
|
|
143
|
+
} catch {}
|
|
144
|
+
for (const h of this.handlers) try {
|
|
145
|
+
h({
|
|
146
|
+
type: "clientDisconnect",
|
|
147
|
+
clientId
|
|
148
|
+
});
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error("[async-dom] BroadcastTransport handler error:", err);
|
|
151
|
+
}
|
|
152
|
+
this.config.onClientDisconnect?.(clientId);
|
|
153
|
+
}
|
|
154
|
+
getClientCount() {
|
|
155
|
+
return this.clients.size;
|
|
156
|
+
}
|
|
157
|
+
getClientIds() {
|
|
158
|
+
return [...this.clients.keys()];
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/server/runner.ts
|
|
163
|
+
/**
|
|
164
|
+
* Creates a server-side async-dom app instance.
|
|
165
|
+
*
|
|
166
|
+
* Wraps `createWorkerDom` with the provided transport and runs the user's
|
|
167
|
+
* app module. Returns a destroy handle for cleanup on disconnect.
|
|
168
|
+
*
|
|
169
|
+
* Note: No SharedArrayBuffer is used — the async query fallback is used instead.
|
|
170
|
+
*/
|
|
171
|
+
function createServerApp(options) {
|
|
172
|
+
const { transport, appModule } = options;
|
|
173
|
+
const dom = createWorkerDom({ transport });
|
|
174
|
+
let ready;
|
|
175
|
+
try {
|
|
176
|
+
const result = appModule(dom);
|
|
177
|
+
ready = result instanceof Promise ? result.catch((err) => {
|
|
178
|
+
console.error("[async-dom] Server app module error:", err);
|
|
179
|
+
}) : Promise.resolve();
|
|
180
|
+
} catch (err) {
|
|
181
|
+
console.error("[async-dom] Server app module error:", err);
|
|
182
|
+
ready = Promise.resolve();
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
ready,
|
|
186
|
+
destroy() {
|
|
187
|
+
dom.destroy();
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/server/streaming-server.ts
|
|
193
|
+
/**
|
|
194
|
+
* Creates a streaming server that broadcasts one app's DOM mutations to N clients.
|
|
195
|
+
*
|
|
196
|
+
* This is an OPTIONAL alternative to `createServerApp` for scenarios where
|
|
197
|
+
* a single source of truth needs to be observed by multiple readers.
|
|
198
|
+
*/
|
|
199
|
+
function createStreamingServer(config) {
|
|
200
|
+
const broadcastTransport = new BroadcastTransport(config.broadcast);
|
|
201
|
+
const dom = createWorkerDom({
|
|
202
|
+
...config.workerDomConfig,
|
|
203
|
+
transport: broadcastTransport
|
|
204
|
+
});
|
|
205
|
+
let ready;
|
|
206
|
+
try {
|
|
207
|
+
const result = config.createApp(dom);
|
|
208
|
+
ready = result instanceof Promise ? result.catch((err) => {
|
|
209
|
+
console.error("[async-dom] Streaming server app error:", err);
|
|
210
|
+
}) : Promise.resolve();
|
|
211
|
+
} catch (err) {
|
|
212
|
+
console.error("[async-dom] Streaming server app error:", err);
|
|
213
|
+
ready = Promise.resolve();
|
|
214
|
+
}
|
|
215
|
+
let clientCounter = 0;
|
|
216
|
+
return {
|
|
217
|
+
handleConnection(socket, clientId) {
|
|
218
|
+
const id = createClientId(clientId ?? `client-${++clientCounter}`);
|
|
219
|
+
const transport = new WebSocketServerTransport(socket);
|
|
220
|
+
broadcastTransport.addClient(id, transport);
|
|
221
|
+
return id;
|
|
222
|
+
},
|
|
223
|
+
disconnectClient(clientId) {
|
|
224
|
+
broadcastTransport.removeClient(clientId);
|
|
225
|
+
},
|
|
226
|
+
getClientCount() {
|
|
227
|
+
return broadcastTransport.getClientCount();
|
|
228
|
+
},
|
|
229
|
+
getClientIds() {
|
|
230
|
+
return broadcastTransport.getClientIds();
|
|
231
|
+
},
|
|
232
|
+
getDom() {
|
|
233
|
+
return dom;
|
|
234
|
+
},
|
|
235
|
+
destroy() {
|
|
236
|
+
broadcastTransport.close();
|
|
237
|
+
dom.destroy();
|
|
238
|
+
},
|
|
239
|
+
ready
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
//#endregion
|
|
243
|
+
export { BroadcastTransport, MutationLog, WebSocketServerTransport, createServerApp, createStreamingServer };
|
|
244
|
+
|
|
245
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"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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"}
|
package/dist/svelte.cjs
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_resolve_debug = require("./resolve-debug.cjs");
|
|
3
|
+
//#region src/svelte/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* Svelte action that creates an async-dom instance on the given element.
|
|
6
|
+
*
|
|
7
|
+
* @param node - The HTML element the action is applied to.
|
|
8
|
+
* @param options - Configuration for the async-dom instance.
|
|
9
|
+
* @returns An object with a `destroy` method to clean up the instance.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```svelte
|
|
13
|
+
* <div use:asyncDom={{ worker: "./app.worker.ts" }} />
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Works with both Svelte 4 and Svelte 5.
|
|
17
|
+
* Config changes should use Svelte's `{#key}` block to force re-creation.
|
|
18
|
+
*/
|
|
19
|
+
function asyncDom(node, options) {
|
|
20
|
+
let instance = null;
|
|
21
|
+
let destroyed = false;
|
|
22
|
+
if (typeof window === "undefined") return { destroy() {} };
|
|
23
|
+
const workerProp = options.worker;
|
|
24
|
+
const worker = typeof workerProp === "string" ? new Worker(new URL(workerProp, require("url").pathToFileURL(__filename).href), { type: "module" }) : workerProp();
|
|
25
|
+
Promise.resolve().then(() => require("./main-thread.cjs")).then((n) => n.main_thread_exports).then(({ createAsyncDom }) => {
|
|
26
|
+
if (destroyed) {
|
|
27
|
+
worker.terminate();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
instance = createAsyncDom({
|
|
31
|
+
target: node,
|
|
32
|
+
worker,
|
|
33
|
+
scheduler: options.scheduler,
|
|
34
|
+
debug: require_resolve_debug.resolveDebugOption(options.debug)
|
|
35
|
+
});
|
|
36
|
+
instance.start();
|
|
37
|
+
options.onReady?.(instance);
|
|
38
|
+
});
|
|
39
|
+
return { destroy() {
|
|
40
|
+
destroyed = true;
|
|
41
|
+
instance?.destroy();
|
|
42
|
+
instance = null;
|
|
43
|
+
} };
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
exports.asyncDom = asyncDom;
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=svelte.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svelte.cjs","names":["resolveDebugOption"],"sources":["../src/svelte/index.ts"],"sourcesContent":["import type { DebugOptions, SchedulerConfig, SerializedError } from \"../index.ts\";\nimport type { AsyncDomInstance } from \"../main-thread/index.ts\";\nimport { resolveDebugOption } from \"../shared/resolve-debug.ts\";\n\nexport interface AsyncDomActionOptions {\n\t/** Worker script path or factory function */\n\tworker: string | (() => Worker);\n\t/** Scheduler configuration */\n\tscheduler?: SchedulerConfig;\n\t/** Debug options (pass `true` for sensible defaults, or a `DebugOptions` object) */\n\tdebug?: DebugOptions | boolean;\n\t/** Called when the async-dom instance is ready */\n\tonReady?: (instance: AsyncDomInstance) => void;\n\t/** Called when a worker error occurs */\n\tonError?: (error: SerializedError) => void;\n}\n\n/**\n * Svelte action that creates an async-dom instance on the given element.\n *\n * @param node - The HTML element the action is applied to.\n * @param options - Configuration for the async-dom instance.\n * @returns An object with a `destroy` method to clean up the instance.\n *\n * @example\n * ```svelte\n * <div use:asyncDom={{ worker: \"./app.worker.ts\" }} />\n * ```\n *\n * Works with both Svelte 4 and Svelte 5.\n * Config changes should use Svelte's `{#key}` block to force re-creation.\n */\nexport function asyncDom(\n\tnode: HTMLElement,\n\toptions: AsyncDomActionOptions,\n): { destroy: () => void } {\n\tlet instance: AsyncDomInstance | null = null;\n\tlet destroyed = false;\n\n\t// SSR guard (shouldn't fire in action, but be safe)\n\tif (typeof window === \"undefined\") {\n\t\treturn { destroy() {} };\n\t}\n\n\tconst workerProp = options.worker;\n\tconst worker =\n\t\ttypeof workerProp === \"string\"\n\t\t\t? new Worker(new URL(workerProp, import.meta.url), { type: \"module\" })\n\t\t\t: workerProp();\n\n\timport(\"../main-thread/index.ts\").then(({ createAsyncDom }) => {\n\t\tif (destroyed) {\n\t\t\tworker.terminate();\n\t\t\treturn;\n\t\t}\n\n\t\tinstance = createAsyncDom({\n\t\t\ttarget: node,\n\t\t\tworker,\n\t\t\tscheduler: options.scheduler,\n\t\t\tdebug: resolveDebugOption(options.debug),\n\t\t});\n\t\tinstance.start();\n\t\toptions.onReady?.(instance);\n\t});\n\n\treturn {\n\t\tdestroy() {\n\t\t\tdestroyed = true;\n\t\t\tinstance?.destroy();\n\t\t\tinstance = null;\n\t\t},\n\t};\n}\n\nexport type { DebugOptions, SerializedError } from \"../index.ts\";\n// Re-export key types for convenience\nexport type { AsyncDomInstance, SchedulerConfig } from \"../main-thread/index.ts\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAgCA,SAAgB,SACf,MACA,SAC0B;CAC1B,IAAI,WAAoC;CACxC,IAAI,YAAY;AAGhB,KAAI,OAAO,WAAW,YACrB,QAAO,EAAE,UAAU,IAAI;CAGxB,MAAM,aAAa,QAAQ;CAC3B,MAAM,SACL,OAAO,eAAe,WACnB,IAAI,OAAO,IAAI,IAAI,YAAA,QAAA,MAAA,CAAA,cAAA,WAAA,CAAA,KAA4B,EAAE,EAAE,MAAM,UAAU,CAAC,GACpE,YAAY;AAEhB,SAAA,SAAA,CAAA,WAAA,QAAA,oBAAA,CAAA,CAAA,MAAA,MAAA,EAAA,oBAAA,CAAkC,MAAM,EAAE,qBAAqB;AAC9D,MAAI,WAAW;AACd,UAAO,WAAW;AAClB;;AAGD,aAAW,eAAe;GACzB,QAAQ;GACR;GACA,WAAW,QAAQ;GACnB,OAAOA,sBAAAA,mBAAmB,QAAQ,MAAM;GACxC,CAAC;AACF,WAAS,OAAO;AAChB,UAAQ,UAAU,SAAS;GAC1B;AAEF,QAAO,EACN,UAAU;AACT,cAAY;AACZ,YAAU,SAAS;AACnB,aAAW;IAEZ"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { v as SerializedError } from "./base.cjs";
|
|
2
|
+
import { n as DebugOptions } from "./debug.cjs";
|
|
3
|
+
import { g as SchedulerConfig, r as AsyncDomInstance } from "./index.cjs";
|
|
4
|
+
|
|
5
|
+
//#region src/svelte/index.d.ts
|
|
6
|
+
interface AsyncDomActionOptions {
|
|
7
|
+
/** Worker script path or factory function */
|
|
8
|
+
worker: string | (() => Worker);
|
|
9
|
+
/** Scheduler configuration */
|
|
10
|
+
scheduler?: SchedulerConfig;
|
|
11
|
+
/** Debug options (pass `true` for sensible defaults, or a `DebugOptions` object) */
|
|
12
|
+
debug?: DebugOptions | boolean;
|
|
13
|
+
/** Called when the async-dom instance is ready */
|
|
14
|
+
onReady?: (instance: AsyncDomInstance) => void;
|
|
15
|
+
/** Called when a worker error occurs */
|
|
16
|
+
onError?: (error: SerializedError) => void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Svelte action that creates an async-dom instance on the given element.
|
|
20
|
+
*
|
|
21
|
+
* @param node - The HTML element the action is applied to.
|
|
22
|
+
* @param options - Configuration for the async-dom instance.
|
|
23
|
+
* @returns An object with a `destroy` method to clean up the instance.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```svelte
|
|
27
|
+
* <div use:asyncDom={{ worker: "./app.worker.ts" }} />
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* Works with both Svelte 4 and Svelte 5.
|
|
31
|
+
* Config changes should use Svelte's `{#key}` block to force re-creation.
|
|
32
|
+
*/
|
|
33
|
+
declare function asyncDom(node: HTMLElement, options: AsyncDomActionOptions): {
|
|
34
|
+
destroy: () => void;
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
export { AsyncDomActionOptions, type AsyncDomInstance, type DebugOptions, type SchedulerConfig, type SerializedError, asyncDom };
|
|
38
|
+
//# sourceMappingURL=svelte.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svelte.d.cts","names":[],"sources":["../src/svelte/index.ts"],"sourcesContent":[],"mappings":";;;;;UAIiB,qBAAA;;0BAEQ;EAFR;EAAqB,SAAA,CAAA,EAIzB,eAJyB;;OAIzB,CAAA,EAEJ,YAFI,GAAA,OAAA;;SAIS,CAAA,EAAA,CAAA,QAAA,EAAA,gBAAA,EAAA,GAAA,IAAA;;EAEY,OAAA,CAAA,EAAA,CAAA,KAAA,EAAf,eAAe,EAAA,GAAA,IAAA;AAkBlC;;;;;;;;;;;;;;;;iBAAgB,QAAA,OACT,sBACG"}
|
package/dist/svelte.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { v as SerializedError } from "./base.js";
|
|
2
|
+
import { n as DebugOptions } from "./debug.js";
|
|
3
|
+
import { g as SchedulerConfig, r as AsyncDomInstance } from "./index.js";
|
|
4
|
+
|
|
5
|
+
//#region src/svelte/index.d.ts
|
|
6
|
+
interface AsyncDomActionOptions {
|
|
7
|
+
/** Worker script path or factory function */
|
|
8
|
+
worker: string | (() => Worker);
|
|
9
|
+
/** Scheduler configuration */
|
|
10
|
+
scheduler?: SchedulerConfig;
|
|
11
|
+
/** Debug options (pass `true` for sensible defaults, or a `DebugOptions` object) */
|
|
12
|
+
debug?: DebugOptions | boolean;
|
|
13
|
+
/** Called when the async-dom instance is ready */
|
|
14
|
+
onReady?: (instance: AsyncDomInstance) => void;
|
|
15
|
+
/** Called when a worker error occurs */
|
|
16
|
+
onError?: (error: SerializedError) => void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Svelte action that creates an async-dom instance on the given element.
|
|
20
|
+
*
|
|
21
|
+
* @param node - The HTML element the action is applied to.
|
|
22
|
+
* @param options - Configuration for the async-dom instance.
|
|
23
|
+
* @returns An object with a `destroy` method to clean up the instance.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```svelte
|
|
27
|
+
* <div use:asyncDom={{ worker: "./app.worker.ts" }} />
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* Works with both Svelte 4 and Svelte 5.
|
|
31
|
+
* Config changes should use Svelte's `{#key}` block to force re-creation.
|
|
32
|
+
*/
|
|
33
|
+
declare function asyncDom(node: HTMLElement, options: AsyncDomActionOptions): {
|
|
34
|
+
destroy: () => void;
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
export { AsyncDomActionOptions, type AsyncDomInstance, type DebugOptions, type SchedulerConfig, type SerializedError, asyncDom };
|
|
38
|
+
//# sourceMappingURL=svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svelte.d.ts","names":[],"sources":["../src/svelte/index.ts"],"sourcesContent":[],"mappings":";;;;;UAIiB,qBAAA;;0BAEQ;EAFR;EAAqB,SAAA,CAAA,EAIzB,eAJyB;;OAIzB,CAAA,EAEJ,YAFI,GAAA,OAAA;;SAIS,CAAA,EAAA,CAAA,QAAA,EAAA,gBAAA,EAAA,GAAA,IAAA;;EAEY,OAAA,CAAA,EAAA,CAAA,KAAA,EAAf,eAAe,EAAA,GAAA,IAAA;AAkBlC;;;;;;;;;;;;;;;;iBAAgB,QAAA,OACT,sBACG"}
|
package/dist/svelte.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { t as resolveDebugOption } from "./resolve-debug.js";
|
|
2
|
+
//#region src/svelte/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* Svelte action that creates an async-dom instance on the given element.
|
|
5
|
+
*
|
|
6
|
+
* @param node - The HTML element the action is applied to.
|
|
7
|
+
* @param options - Configuration for the async-dom instance.
|
|
8
|
+
* @returns An object with a `destroy` method to clean up the instance.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```svelte
|
|
12
|
+
* <div use:asyncDom={{ worker: "./app.worker.ts" }} />
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Works with both Svelte 4 and Svelte 5.
|
|
16
|
+
* Config changes should use Svelte's `{#key}` block to force re-creation.
|
|
17
|
+
*/
|
|
18
|
+
function asyncDom(node, options) {
|
|
19
|
+
let instance = null;
|
|
20
|
+
let destroyed = false;
|
|
21
|
+
if (typeof window === "undefined") return { destroy() {} };
|
|
22
|
+
const workerProp = options.worker;
|
|
23
|
+
const worker = typeof workerProp === "string" ? new Worker(new URL(workerProp, import.meta.url), { type: "module" }) : workerProp();
|
|
24
|
+
import("./main-thread.js").then((n) => n.n).then(({ createAsyncDom }) => {
|
|
25
|
+
if (destroyed) {
|
|
26
|
+
worker.terminate();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
instance = createAsyncDom({
|
|
30
|
+
target: node,
|
|
31
|
+
worker,
|
|
32
|
+
scheduler: options.scheduler,
|
|
33
|
+
debug: resolveDebugOption(options.debug)
|
|
34
|
+
});
|
|
35
|
+
instance.start();
|
|
36
|
+
options.onReady?.(instance);
|
|
37
|
+
});
|
|
38
|
+
return { destroy() {
|
|
39
|
+
destroyed = true;
|
|
40
|
+
instance?.destroy();
|
|
41
|
+
instance = null;
|
|
42
|
+
} };
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { asyncDom };
|
|
46
|
+
|
|
47
|
+
//# sourceMappingURL=svelte.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svelte.js","names":[],"sources":["../src/svelte/index.ts"],"sourcesContent":["import type { DebugOptions, SchedulerConfig, SerializedError } from \"../index.ts\";\nimport type { AsyncDomInstance } from \"../main-thread/index.ts\";\nimport { resolveDebugOption } from \"../shared/resolve-debug.ts\";\n\nexport interface AsyncDomActionOptions {\n\t/** Worker script path or factory function */\n\tworker: string | (() => Worker);\n\t/** Scheduler configuration */\n\tscheduler?: SchedulerConfig;\n\t/** Debug options (pass `true` for sensible defaults, or a `DebugOptions` object) */\n\tdebug?: DebugOptions | boolean;\n\t/** Called when the async-dom instance is ready */\n\tonReady?: (instance: AsyncDomInstance) => void;\n\t/** Called when a worker error occurs */\n\tonError?: (error: SerializedError) => void;\n}\n\n/**\n * Svelte action that creates an async-dom instance on the given element.\n *\n * @param node - The HTML element the action is applied to.\n * @param options - Configuration for the async-dom instance.\n * @returns An object with a `destroy` method to clean up the instance.\n *\n * @example\n * ```svelte\n * <div use:asyncDom={{ worker: \"./app.worker.ts\" }} />\n * ```\n *\n * Works with both Svelte 4 and Svelte 5.\n * Config changes should use Svelte's `{#key}` block to force re-creation.\n */\nexport function asyncDom(\n\tnode: HTMLElement,\n\toptions: AsyncDomActionOptions,\n): { destroy: () => void } {\n\tlet instance: AsyncDomInstance | null = null;\n\tlet destroyed = false;\n\n\t// SSR guard (shouldn't fire in action, but be safe)\n\tif (typeof window === \"undefined\") {\n\t\treturn { destroy() {} };\n\t}\n\n\tconst workerProp = options.worker;\n\tconst worker =\n\t\ttypeof workerProp === \"string\"\n\t\t\t? new Worker(new URL(workerProp, import.meta.url), { type: \"module\" })\n\t\t\t: workerProp();\n\n\timport(\"../main-thread/index.ts\").then(({ createAsyncDom }) => {\n\t\tif (destroyed) {\n\t\t\tworker.terminate();\n\t\t\treturn;\n\t\t}\n\n\t\tinstance = createAsyncDom({\n\t\t\ttarget: node,\n\t\t\tworker,\n\t\t\tscheduler: options.scheduler,\n\t\t\tdebug: resolveDebugOption(options.debug),\n\t\t});\n\t\tinstance.start();\n\t\toptions.onReady?.(instance);\n\t});\n\n\treturn {\n\t\tdestroy() {\n\t\t\tdestroyed = true;\n\t\t\tinstance?.destroy();\n\t\t\tinstance = null;\n\t\t},\n\t};\n}\n\nexport type { DebugOptions, SerializedError } from \"../index.ts\";\n// Re-export key types for convenience\nexport type { AsyncDomInstance, SchedulerConfig } from \"../main-thread/index.ts\";\n"],"mappings":";;;;;;;;;;;;;;;;;AAgCA,SAAgB,SACf,MACA,SAC0B;CAC1B,IAAI,WAAoC;CACxC,IAAI,YAAY;AAGhB,KAAI,OAAO,WAAW,YACrB,QAAO,EAAE,UAAU,IAAI;CAGxB,MAAM,aAAa,QAAQ;CAC3B,MAAM,SACL,OAAO,eAAe,WACnB,IAAI,OAAO,IAAI,IAAI,YAAY,OAAO,KAAK,IAAI,EAAE,EAAE,MAAM,UAAU,CAAC,GACpE,YAAY;AAEhB,QAAO,oBAAA,MAAA,MAAA,EAAA,EAAA,CAA2B,MAAM,EAAE,qBAAqB;AAC9D,MAAI,WAAW;AACd,UAAO,WAAW;AAClB;;AAGD,aAAW,eAAe;GACzB,QAAQ;GACR;GACA,WAAW,QAAQ;GACnB,OAAO,mBAAmB,QAAQ,MAAM;GACxC,CAAC;AACF,WAAS,OAAO;AAChB,UAAQ,UAAU,SAAS;GAC1B;AAEF,QAAO,EACN,UAAU;AACT,cAAY;AACZ,YAAU,SAAS;AACnB,aAAW;IAEZ"}
|