@fluid-app/widget-runtime 0.1.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.
Files changed (46) hide show
  1. package/README.md +77 -0
  2. package/dist/Thread-0eUyc0Hw.cjs +591 -0
  3. package/dist/Thread-0eUyc0Hw.cjs.map +1 -0
  4. package/dist/Thread-BiW7rXts.mjs +574 -0
  5. package/dist/Thread-BiW7rXts.mjs.map +1 -0
  6. package/dist/index.cjs +6 -0
  7. package/dist/index.d.cts +3 -0
  8. package/dist/index.d.mts +3 -0
  9. package/dist/index.mjs +3 -0
  10. package/dist/remote-dom-renderer-BU-73KvK.d.mts +90 -0
  11. package/dist/remote-dom-renderer-BU-73KvK.d.mts.map +1 -0
  12. package/dist/remote-dom-renderer-DSHFetvm.d.cts +90 -0
  13. package/dist/remote-dom-renderer-DSHFetvm.d.cts.map +1 -0
  14. package/dist/remote-dom-renderer-DUQf80Nu.cjs +2116 -0
  15. package/dist/remote-dom-renderer-DUQf80Nu.cjs.map +1 -0
  16. package/dist/remote-dom-renderer-DcwcNPsu.mjs +2081 -0
  17. package/dist/remote-dom-renderer-DcwcNPsu.mjs.map +1 -0
  18. package/dist/remote-dom-values-BSAxrOE7.mjs +238 -0
  19. package/dist/remote-dom-values-BSAxrOE7.mjs.map +1 -0
  20. package/dist/remote-dom-values-D4AgBNWr.d.cts +32 -0
  21. package/dist/remote-dom-values-D4AgBNWr.d.cts.map +1 -0
  22. package/dist/remote-dom-values-DbgIedSg.cjs +297 -0
  23. package/dist/remote-dom-values-DbgIedSg.cjs.map +1 -0
  24. package/dist/remote-dom-values-EwoLXnSn.d.mts +32 -0
  25. package/dist/remote-dom-values-EwoLXnSn.d.mts.map +1 -0
  26. package/dist/remote-dom.cjs +6 -0
  27. package/dist/remote-dom.d.cts +3 -0
  28. package/dist/remote-dom.d.mts +3 -0
  29. package/dist/remote-dom.mjs +3 -0
  30. package/dist/worker-react-bootstrap-C7tCMYGA.mjs +13 -0
  31. package/dist/worker-react-bootstrap-C7tCMYGA.mjs.map +1 -0
  32. package/dist/worker-react-bootstrap-N8jcWZ3L.cjs +13 -0
  33. package/dist/worker-react-bootstrap-N8jcWZ3L.cjs.map +1 -0
  34. package/dist/worker-react-renderer-BKzig_Vy.mjs +185 -0
  35. package/dist/worker-react-renderer-BKzig_Vy.mjs.map +1 -0
  36. package/dist/worker-react-renderer-BSFxMMf7.cjs +186 -0
  37. package/dist/worker-react-renderer-BSFxMMf7.cjs.map +1 -0
  38. package/dist/worker.cjs +325 -0
  39. package/dist/worker.cjs.map +1 -0
  40. package/dist/worker.d.cts +46 -0
  41. package/dist/worker.d.cts.map +1 -0
  42. package/dist/worker.d.mts +46 -0
  43. package/dist/worker.d.mts.map +1 -0
  44. package/dist/worker.mjs +319 -0
  45. package/dist/worker.mjs.map +1 -0
  46. package/package.json +68 -0
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # `@fluid-app/widget-runtime`
2
+
3
+ Remote DOM runtime for third-party Fluid widgets.
4
+
5
+ The package has two deliberately small public entry points:
6
+
7
+ - `@fluid-app/widget-runtime` mounts a widget worker into a host element.
8
+ - `@fluid-app/widget-runtime/worker` starts a worker containing authored React widgets and exposes declared host capabilities.
9
+
10
+ Widget discovery, descriptor validation, catalog access, publishing, and builder registration belong to portal packages and the CLI. This package owns only the host/worker execution boundary.
11
+
12
+ ## Host
13
+
14
+ ```tsx
15
+ import { mountRemoteDomWidget } from "@fluid-app/widget-runtime/remote-dom";
16
+
17
+ const handle = mountRemoteDomWidget({
18
+ host: document.querySelector("#widget")!,
19
+ artifact: {
20
+ packageId: "example.widgets",
21
+ version: "1.0.0",
22
+ widgetType: "Greeting",
23
+ workerEntryUrl: "https://cdn.example.com/widget.js",
24
+ cssUrls: [],
25
+ assetUrls: [],
26
+ capabilities: [{ name: "account", version: "1" }],
27
+ },
28
+ props: { name: "Ada" },
29
+ callCapability: (capability, method, payload) =>
30
+ portalCapabilities.call(capability, method, payload),
31
+ });
32
+
33
+ await handle.ready;
34
+ handle.updateProps({ name: "Grace" });
35
+ handle.dispose();
36
+ ```
37
+
38
+ Each mount creates one module worker and one Remote DOM thread. Disposal releases the thread, worker, styles, event listeners, and rendered nodes. The host sanitizes all mutations, URLs, CSS, events, and serializable values before applying them to a shadow root.
39
+
40
+ ## Worker
41
+
42
+ ```tsx
43
+ import {
44
+ startRemoteDomWidgetWorker,
45
+ useRemoteDomCapability,
46
+ } from "@fluid-app/widget-runtime/worker";
47
+
48
+ function Greeting({ name }: { readonly name: string }) {
49
+ const account = useRemoteDomCapability("account");
50
+ return (
51
+ <button onClick={() => account.call("openProfile")}>Hello {name}</button>
52
+ );
53
+ }
54
+
55
+ startRemoteDomWidgetWorker({ widgets: { Greeting } });
56
+ ```
57
+
58
+ Workers can render arbitrary supported HTML and CSS through upstream Remote DOM. The runtime disables selected network, storage, nested-worker, and fresh-realm globals where the worker environment permits it. Host calls are limited to capabilities declared by the widget package descriptor.
59
+
60
+ ## Threat model
61
+
62
+ Widget catalog approval is a trust decision: approved packages are trusted
63
+ application code supplied by a company or Droplet owner. Worker-global
64
+ lockdown, capability validation, mutation sanitization, URL policy, resource
65
+ limits, and shadow-root isolation are defense in depth around that trusted
66
+ code. They protect the host DOM and narrow accidental ambient access, but they
67
+ do not constitute a complete network sandbox or a substitute for package
68
+ review, catalog authorization, browser isolation, or server-side access
69
+ control.
70
+
71
+ ## Non-goals
72
+
73
+ - No classic script loading or browser globals.
74
+ - No legacy widget registration or descriptor conversion.
75
+ - No catalog or package validation API.
76
+ - No worker pooling, heartbeat, diagnostics framework, or version disabling.
77
+ - No compatibility layer for legacy portal SDK hooks.
@@ -0,0 +1,591 @@
1
+ //#region ../../../node_modules/.pnpm/@quilted+threads@4.1.0_@preact+signals-core@1.14.3/node_modules/@quilted/threads/build/esm/constants.mjs
2
+ const SERIALIZE_METHOD = Symbol.for("quilt.threads.serialize");
3
+ const TRANSFERABLE = Symbol.for("quilt.threads.transferable");
4
+ //#endregion
5
+ //#region ../../../node_modules/.pnpm/@quilted+threads@4.1.0_@preact+signals-core@1.14.3/node_modules/@quilted/threads/build/esm/memory.mjs
6
+ const RETAIN_METHOD = Symbol.for("quilt.threads.retain");
7
+ const RELEASE_METHOD = Symbol.for("quilt.threads.release");
8
+ /**
9
+ * Indicates that a value is being manually memory-managed across threads by this library.
10
+ */
11
+ function isMemoryManageable(value) {
12
+ return Boolean(value && value[RETAIN_METHOD] && value[RELEASE_METHOD]);
13
+ }
14
+ /**
15
+ * Marks a value as being used so it will not be automatically released. Calling `retain` will,
16
+ * by default, deeply retain the value — that is, it will traverse into nested array elements
17
+ * and object properties, and retain every `retain`-able thing it finds.
18
+ *
19
+ * You will typically use this alongside also storing that value in a variable that lives outside
20
+ * the context of the function where that value was received.
21
+ *
22
+ * @example
23
+ * import {retain} from '@quilted/threads';
24
+ *
25
+ * const allUsers = new Set<User>();
26
+ *
27
+ * async function sayHello(user: User) {
28
+ * allUsers.add(user);
29
+ * retain(user);
30
+ * return `Hey, ${await user.fullName()}!`;
31
+ * }
32
+ */
33
+ function retain(value, { deep = true } = {}) {
34
+ return retainInternal(value, deep, /* @__PURE__ */ new Map());
35
+ }
36
+ function retainInternal(value, deep, seen) {
37
+ const seenValue = seen.get(value);
38
+ if (seenValue) return seenValue;
39
+ const canRetain = isMemoryManageable(value);
40
+ if (canRetain) value[RETAIN_METHOD]();
41
+ seen.set(value, canRetain);
42
+ if (deep) {
43
+ if (Array.isArray(value)) {
44
+ const nestedCanRetain = value.reduce((canRetain, item) => retainInternal(item, deep, seen) || canRetain, canRetain);
45
+ seen.set(value, nestedCanRetain);
46
+ return nestedCanRetain;
47
+ }
48
+ if (isBasicObject$1(value)) {
49
+ const nestedCanRetain = Object.keys(value).reduce((canRetain, key) => retainInternal(value[key], deep, seen) || canRetain, canRetain);
50
+ seen.set(value, nestedCanRetain);
51
+ return nestedCanRetain;
52
+ }
53
+ }
54
+ return canRetain;
55
+ }
56
+ /**
57
+ * Once you are no longer using the a `retain`-ed value, you can use this function to mark it as
58
+ * being unused. Like `retain()`, this function will apply to all nested array elements and object
59
+ * properties.
60
+ *
61
+ * @example
62
+ * import {retain} from '@quilted/threads';
63
+ *
64
+ * const allUsers = new Set<User>();
65
+ *
66
+ * function removeUser(user: User) {
67
+ * allUsers.delete(user);
68
+ * release(user);
69
+ * }
70
+ */
71
+ function release(value, { deep = true } = {}) {
72
+ return releaseInternal(value, deep, /* @__PURE__ */ new Map());
73
+ }
74
+ function releaseInternal(value, deep, seen) {
75
+ const seenValue = seen.get(value);
76
+ if (seenValue) return seenValue;
77
+ const canRelease = isMemoryManageable(value);
78
+ if (canRelease) value[RELEASE_METHOD]();
79
+ seen.set(value, canRelease);
80
+ if (deep) {
81
+ if (Array.isArray(value)) {
82
+ const nestedCanRelease = value.reduce((canRelease, item) => releaseInternal(item, deep, seen) || canRelease, canRelease);
83
+ seen.set(value, nestedCanRelease);
84
+ return nestedCanRelease;
85
+ }
86
+ if (isBasicObject$1(value)) {
87
+ const nestedCanRelease = Object.keys(value).reduce((canRelease, key) => releaseInternal(value[key], deep, seen) || canRelease, canRelease);
88
+ seen.set(value, nestedCanRelease);
89
+ return nestedCanRelease;
90
+ }
91
+ }
92
+ return canRelease;
93
+ }
94
+ function isBasicObject$1(value) {
95
+ if (value == null || typeof value !== "object") return false;
96
+ const prototype = Object.getPrototypeOf(value);
97
+ return prototype == null || prototype === Object.prototype;
98
+ }
99
+ //#endregion
100
+ //#region ../../../node_modules/.pnpm/@quilted+events@2.1.5/node_modules/@quilted/events/build/esm/abort/NestedAbortController.mjs
101
+ /**
102
+ * An `AbortController` that will automatically abort when any of
103
+ * its "parent" `AbortController`s abort. This is useful for when
104
+ * you have nested asynchronous operations that may abort independently.
105
+ * The `AbortController` can also be independently aborted.
106
+ *
107
+ * To associate the parent `AbortController`s, pass their signals
108
+ * as arguments to the constructor.
109
+ *
110
+ * @example
111
+ * import {NestedAbortController} from '@shopify/events';
112
+ *
113
+ * const parent = new AbortController();
114
+ * const nested = new NestedAbortController(parent.signal);
115
+ *
116
+ * parent.abort();
117
+ * nested.signal.aborted; // true
118
+ */
119
+ var NestedAbortController = class extends AbortController {
120
+ constructor(...parents) {
121
+ super();
122
+ const abortedSignal = parents.find((signal) => signal.aborted);
123
+ if (abortedSignal) this.abort(abortedSignal.reason);
124
+ else {
125
+ const abort = (event) => this.abort(event.target.reason);
126
+ const options = { signal: this.signal };
127
+ for (const signal of parents) signal.addEventListener("abort", abort, options);
128
+ }
129
+ }
130
+ };
131
+ //#endregion
132
+ //#region ../../../node_modules/.pnpm/@quilted+threads@4.1.0_@preact+signals-core@1.14.3/node_modules/@quilted/threads/build/esm/errors.mjs
133
+ var ThreadClosedError = class extends Error {
134
+ constructor() {
135
+ super("You attempted to call a function on a closed thread.");
136
+ }
137
+ };
138
+ //#endregion
139
+ //#region ../../../node_modules/.pnpm/@quilted+threads@4.1.0_@preact+signals-core@1.14.3/node_modules/@quilted/threads/build/esm/nanoid.mjs
140
+ let a = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
141
+ function nanoid(e = 21) {
142
+ let t = "", r = crypto.getRandomValues(new Uint8Array(e));
143
+ for (let n = 0; n < e; n++) t += a[63 & r[n]];
144
+ return t;
145
+ }
146
+ //#endregion
147
+ //#region ../../../node_modules/.pnpm/@quilted+threads@4.1.0_@preact+signals-core@1.14.3/node_modules/@quilted/threads/build/esm/functions/ThreadFunctionsAutomatic.mjs
148
+ /**
149
+ * A strategy for managing functions across threads that automatically releases
150
+ * functions when they are no longer needed. This is done by deserializing functions
151
+ * into a proxy that can be called, and wrapping that proxy in a `FinalizationRegistry`,
152
+ * which allows us to send a message to the parent thread when the function is no
153
+ * longer used.
154
+ *
155
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry
156
+ */
157
+ var ThreadFunctionsAutomatic = class {
158
+ #functionsToId = /* @__PURE__ */ new Map();
159
+ #idsToFunction = /* @__PURE__ */ new Map();
160
+ #idsToProxy = /* @__PURE__ */ new Map();
161
+ #finalization = /* @__PURE__ */ new WeakMap();
162
+ get(id) {
163
+ return this.#idsToFunction.get(id);
164
+ }
165
+ release(id) {
166
+ const func = this.#idsToFunction.get(id);
167
+ if (func) {
168
+ this.#idsToFunction.delete(id);
169
+ this.#functionsToId.delete(func);
170
+ }
171
+ return Boolean(func);
172
+ }
173
+ serialize(func) {
174
+ let id = this.#functionsToId.get(func);
175
+ if (id == null) {
176
+ id = nanoid();
177
+ this.#functionsToId.set(func, id);
178
+ this.#idsToFunction.set(id, func);
179
+ }
180
+ return id;
181
+ }
182
+ deserialize(id, thread) {
183
+ let proxy = this.#idsToProxy.get(id)?.deref();
184
+ if (proxy) return proxy;
185
+ proxy = (...args) => {
186
+ if (!this.#idsToProxy.has(id)) throw new Error("You attempted to call a function that was already revoked.");
187
+ return thread.call((callID, args, transferable) => {
188
+ thread.messages.send([
189
+ 3,
190
+ callID,
191
+ id,
192
+ args
193
+ ], transferable);
194
+ }, args);
195
+ };
196
+ this.#finalizationRegistry(thread)?.register(proxy, id);
197
+ this.#idsToProxy.set(id, new WeakRef(proxy));
198
+ return proxy;
199
+ }
200
+ #finalizationRegistry(thread) {
201
+ let finalization = this.#finalization.get(thread);
202
+ if (typeof FinalizationRegistry === "undefined") return;
203
+ if (!finalization) {
204
+ finalization = new FinalizationRegistry((id) => {
205
+ thread.messages.send([5, id]);
206
+ });
207
+ this.#finalization.set(thread, finalization);
208
+ }
209
+ return finalization;
210
+ }
211
+ };
212
+ //#endregion
213
+ //#region ../../../node_modules/.pnpm/@quilted+threads@4.1.0_@preact+signals-core@1.14.3/node_modules/@quilted/threads/build/esm/serialization/shared.mjs
214
+ function isIterator(value) {
215
+ return value != null && (Symbol.asyncIterator in value || Symbol.iterator in value) && typeof value.next === "function";
216
+ }
217
+ function isBasicObject(value) {
218
+ if (value == null || typeof value !== "object") return false;
219
+ const prototype = Object.getPrototypeOf(value);
220
+ return prototype == null || prototype === Object.prototype;
221
+ }
222
+ //#endregion
223
+ //#region ../../../node_modules/.pnpm/@quilted+threads@4.1.0_@preact+signals-core@1.14.3/node_modules/@quilted/threads/build/esm/serialization/ThreadSerializationStructuredClone.mjs
224
+ const FUNCTION = "_@f";
225
+ const ASYNC_ITERATOR = "_@i";
226
+ /**
227
+ * A thread encoder that can transfer supports a wide variety of JavaScript types by
228
+ * converting them to a format that the [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)
229
+ * algorithm can process. This
230
+ *
231
+ * Additionally, this encoder will convert functions into a JSON-friendly representation.
232
+ * These functions will use a manual memory management technique to ensure they can be
233
+ * garbage collected by default, but can be configured to use a `WeakRef`-based automatic
234
+ * memory management system by setting the `garbageCollection: 'weak-ref'` option.
235
+ */
236
+ var ThreadSerializationStructuredClone = class {
237
+ #customSerializer;
238
+ #customDeserializer;
239
+ constructor(options) {
240
+ this.#customSerializer = options?.serialize;
241
+ this.#customDeserializer = options?.deserialize;
242
+ }
243
+ /**
244
+ * Serializes a value into a structured cloning-compatible format that can be transferred between threads.
245
+ */
246
+ serialize(value, thread, transferable) {
247
+ return this.#serializeInternal(value, thread, transferable);
248
+ }
249
+ #serializeInternal(value, thread, transferable, seen = /* @__PURE__ */ new Map(), isApplyingDefault = false) {
250
+ if (value == null) return value;
251
+ if (seen.has(value)) return seen.get(value);
252
+ seen.set(value, void 0);
253
+ if (typeof value === "object") {
254
+ if (this.#customSerializer && !isApplyingDefault) {
255
+ const customValue = this.#customSerializer(value, (value) => this.#serializeInternal(value, thread, transferable, seen, true), thread, transferable);
256
+ if (customValue !== void 0) {
257
+ seen.set(value, customValue);
258
+ return customValue;
259
+ }
260
+ }
261
+ if (value[TRANSFERABLE]) {
262
+ transferable?.push(value);
263
+ seen.set(value, value);
264
+ return value;
265
+ }
266
+ const serializeValue = (value) => {
267
+ return this.#serializeInternal(value, thread, transferable, seen);
268
+ };
269
+ if (typeof value[SERIALIZE_METHOD] === "function") {
270
+ const result = value[SERIALIZE_METHOD]({ serialize: serializeValue });
271
+ seen.set(value, result);
272
+ return result;
273
+ }
274
+ if (Array.isArray(value)) {
275
+ const result = value.map((item) => serializeValue(item));
276
+ seen.set(value, result);
277
+ return result;
278
+ }
279
+ if (value instanceof Map) {
280
+ const entries = [...value.entries()].map(([key, value]) => {
281
+ return [serializeValue(key), serializeValue(value)];
282
+ });
283
+ const result = new Map(entries);
284
+ seen.set(value, result);
285
+ return result;
286
+ }
287
+ if (value instanceof Set) {
288
+ const entries = [...value].map((entry) => serializeValue(entry));
289
+ const result = new Set(entries);
290
+ seen.set(value, result);
291
+ return result;
292
+ }
293
+ const valueIsIterator = isIterator(value);
294
+ if (isBasicObject(value) || valueIsIterator) {
295
+ const result = {};
296
+ for (const key of Object.keys(value)) result[key] = serializeValue(value[key]);
297
+ if (valueIsIterator) {
298
+ result.next ??= serializeValue(value.next.bind(value));
299
+ result.return ??= serializeValue(value.return.bind(value));
300
+ result.throw ??= serializeValue(value.throw.bind(value));
301
+ result[ASYNC_ITERATOR] = true;
302
+ }
303
+ seen.set(value, result);
304
+ return result;
305
+ }
306
+ }
307
+ if (typeof value === "function") {
308
+ const serialized = thread.functions.serialize(value, thread, transferable);
309
+ const result = { [FUNCTION]: serialized };
310
+ seen.set(value, result);
311
+ return result;
312
+ }
313
+ seen.set(value, value);
314
+ return value;
315
+ }
316
+ /**
317
+ * Deserializes a structured cloning-compatible value from another thread.
318
+ */
319
+ deserialize(value, thread) {
320
+ return this.#deserializeInternal(value, thread);
321
+ }
322
+ #deserializeInternal(value, thread, isApplyingDefault = false) {
323
+ if (value == null) return value;
324
+ if (typeof value === "object") {
325
+ if (this.#customDeserializer && !isApplyingDefault) {
326
+ const customValue = this.#customDeserializer(value, (value) => this.#deserializeInternal(value, thread, true), thread);
327
+ if (customValue !== void 0) return customValue;
328
+ }
329
+ if (value == null) return value;
330
+ if (Array.isArray(value)) return value.map((value) => this.#deserializeInternal(value, thread));
331
+ if (value instanceof Map) return new Map([...value].map(([key, value]) => [this.#deserializeInternal(key, thread), this.#deserializeInternal(value, thread)]));
332
+ if (value instanceof Set) return new Set([...value].map((entry) => this.#deserializeInternal(entry, thread)));
333
+ if (FUNCTION in value) return thread.functions.deserialize(value[FUNCTION], thread);
334
+ if (!isBasicObject(value)) return value;
335
+ const result = {};
336
+ for (const key of Object.keys(value)) if (key === ASYNC_ITERATOR) result[Symbol.asyncIterator] = () => result;
337
+ else result[key] = this.#deserializeInternal(value[key], thread);
338
+ return result;
339
+ }
340
+ return value;
341
+ }
342
+ };
343
+ //#endregion
344
+ //#region ../../../node_modules/.pnpm/@quilted+threads@4.1.0_@preact+signals-core@1.14.3/node_modules/@quilted/threads/build/esm/Thread.mjs
345
+ /**
346
+ * An object that can serialize and deserialize values communicated between two threads.
347
+ */
348
+ /**
349
+ * An object that can serialize and deserialize values communicated between two threads.
350
+ */
351
+ /**
352
+ * Options to customize the creation of a `ThreadSerialization` instance.
353
+ */
354
+ /**
355
+ * Options to customize the creation of a `Thread` instance.
356
+ */
357
+ /**
358
+ * An object backing a `Thread` that provides the message-passing interface
359
+ * that allows communication to flow between environments. This message-passing
360
+ * interface is based on the [`postMessage` interface](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage),
361
+ * which is easily adaptable to many JavaScript objects and environments.
362
+ */
363
+ /**
364
+ * A map of messages that can be sent between threads.
365
+ */
366
+ /**
367
+ * The possible data payloads that can be transferred between threads.
368
+ */
369
+ /**
370
+ * A helper type that extracts all callable methods that can be safely proxied
371
+ * between threads; that is, ones which return a promise or async iterator.
372
+ */
373
+ /**
374
+ * A thread represents a target JavaScript environment that exposes a set
375
+ * of callable, asynchronous methods. The thread takes care of automatically
376
+ * encoding and decoding its arguments and return values, so you can interact
377
+ * with it as if its methods were implemented in the same environment as your
378
+ * own code.
379
+ */
380
+ var Thread = class {
381
+ /**
382
+ * An object that exposes the methods that can be called on the paired thread.
383
+ * This object will automatically encode and decode arguments and return values
384
+ * as necessary.
385
+ */
386
+ /**
387
+ * An object that exposes the methods that can be called on this thread by the
388
+ * paired thread. To set these methods, pass the `exports` option when creating
389
+ * a new `Thread`.
390
+ */
391
+ /**
392
+ * An object that provides the message-passing interface that allows communication
393
+ * to flow between environments.
394
+ */
395
+ /**
396
+ * An object that manages how functions are proxied between threads.
397
+ */
398
+ /**
399
+ * An object that manages how values are serialized and deserialized between threads.
400
+ */
401
+ /**
402
+ * An `AbortSignal` that indicates whether the communication channel is still open.
403
+ */
404
+ get signal() {
405
+ return this.#abort.signal;
406
+ }
407
+ /**
408
+ * A boolean indicating whether the communication channel is still open.
409
+ */
410
+ get closed() {
411
+ return this.#abort.signal.aborted;
412
+ }
413
+ #abort;
414
+ #idsToResolver = /* @__PURE__ */ new Map();
415
+ constructor(messages, { imports, exports, functions = new ThreadFunctionsAutomatic(), serialization = new ThreadSerializationStructuredClone(), signal } = {}) {
416
+ this.messages = messages;
417
+ this.#abort = signal ? new NestedAbortController(signal) : new AbortController();
418
+ this.exports = exports ?? {};
419
+ this.imports = createThreadImports(this.#handlerForCall.bind(this), imports);
420
+ this.functions = functions;
421
+ this.serialization = serialization;
422
+ this.functions.start?.(this);
423
+ this.serialization.start?.(this);
424
+ this.signal.addEventListener("abort", () => {
425
+ for (const id of this.#idsToResolver.keys()) this.#resolveCall(id, void 0, new ThreadClosedError());
426
+ this.#idsToResolver.clear();
427
+ }, { once: true });
428
+ messages.listen(async (rawData) => {
429
+ if (!(Array.isArray(rawData) && typeof rawData[0] === "number")) return;
430
+ const data = rawData;
431
+ switch (data[0]) {
432
+ case 1: {
433
+ const [, id, property, args] = data;
434
+ const func = this.exports[property] ?? (() => {
435
+ throw new Error(`No '${property}' method is exported from this thread`);
436
+ });
437
+ await this.#callLocal(func, args, (value, error, transferable) => {
438
+ this.messages.send([
439
+ 2,
440
+ id,
441
+ value,
442
+ error
443
+ ], transferable);
444
+ });
445
+ break;
446
+ }
447
+ case 3: {
448
+ const [, callID, funcID, args] = data;
449
+ const func = this.functions.get(funcID, this) ?? missingThreadFunction;
450
+ await this.#callLocal(func, args, (value, error, transferable) => {
451
+ this.messages.send([
452
+ 4,
453
+ callID,
454
+ value,
455
+ error
456
+ ], transferable);
457
+ });
458
+ break;
459
+ }
460
+ case 2:
461
+ case 4:
462
+ this.#resolveCall(...data.slice(1));
463
+ break;
464
+ case 5: {
465
+ const id = data[1];
466
+ this.functions.release(id, this);
467
+ break;
468
+ }
469
+ }
470
+ }, { signal: this.signal });
471
+ }
472
+ /**
473
+ * Closes the communication channel between the two threads. This will prevent
474
+ * any further communication between the threads, and will clean up any memory
475
+ * associated with in-progress communication. It will also reject any inflight
476
+ * function calls between threads with a `ThreadClosedError`.
477
+ */
478
+ close() {
479
+ this.#abort.abort();
480
+ }
481
+ /**
482
+ * Requests that the thread provide the context needed to make a function
483
+ * call between threads. You provide this method a function to call and the
484
+ * unserialized arguments you wish to call it with, and the thread will call
485
+ * the function you provided with a serialized call ID, the serialized arguments,
486
+ * and any transferable objects that need to be passed between threads.
487
+ */
488
+ call(func, args) {
489
+ if (this.closed) return Promise.reject(new ThreadClosedError());
490
+ const transferable = [];
491
+ const serialized = this.serialization.serialize(args, this, transferable);
492
+ const id = nanoid();
493
+ const done = this.#waitForResult(id);
494
+ func(id, serialized, transferable);
495
+ return done;
496
+ }
497
+ async #callLocal(func, args, withResult) {
498
+ try {
499
+ const result = this.functions.call ? await this.functions.call(func, args, this) : await func(...this.serialization.deserialize(args, this));
500
+ const transferable = [];
501
+ withResult(this.serialization.serialize(result, this, transferable), void 0, transferable);
502
+ } catch (error) {
503
+ withResult(void 0, this.serialization.serialize(error, this));
504
+ }
505
+ }
506
+ #handlerForCall(property) {
507
+ return (...args) => {
508
+ try {
509
+ if (typeof property !== "string" && typeof property !== "number") throw new Error(`Can’t call a symbol method on a thread: ${property.toString()}`);
510
+ return this.call((id, serializedArgs, transferable) => {
511
+ this.messages.send([
512
+ 1,
513
+ id,
514
+ property,
515
+ serializedArgs
516
+ ], transferable);
517
+ }, args);
518
+ } catch (error) {
519
+ return Promise.reject(error);
520
+ }
521
+ };
522
+ }
523
+ #resolveCall(...args) {
524
+ const callID = args[0];
525
+ const resolver = this.#idsToResolver.get(callID);
526
+ if (resolver) {
527
+ resolver(...args);
528
+ this.#idsToResolver.delete(callID);
529
+ }
530
+ }
531
+ #waitForResult(id) {
532
+ const promise = new Promise((resolve, reject) => {
533
+ this.#idsToResolver.set(id, (_, value, error) => {
534
+ if (error == null) resolve(this.serialization.deserialize(value, this));
535
+ else reject(this.serialization.deserialize(error, this));
536
+ });
537
+ });
538
+ Object.defineProperty(promise, Symbol.asyncIterator, { async *value() {
539
+ const result = await promise;
540
+ Object.defineProperty(result, Symbol.asyncIterator, { value: () => result });
541
+ yield* result;
542
+ } });
543
+ return promise;
544
+ }
545
+ };
546
+ function createThreadImports(handlerForImport, imported) {
547
+ let call;
548
+ if (imported == null) {
549
+ if (typeof Proxy !== "function") throw new Error(`You must pass an array of callable methods in environments without Proxies.`);
550
+ const cache = /* @__PURE__ */ new Map();
551
+ call = new Proxy({}, { get(_target, property) {
552
+ if (cache.has(property)) return cache.get(property);
553
+ const handler = handlerForImport(property);
554
+ cache.set(property, handler);
555
+ return handler;
556
+ } });
557
+ } else {
558
+ call = {};
559
+ for (const method of imported) Object.defineProperty(call, method, {
560
+ value: handlerForImport(method),
561
+ writable: false,
562
+ configurable: true,
563
+ enumerable: true
564
+ });
565
+ }
566
+ return call;
567
+ }
568
+ function missingThreadFunction() {
569
+ throw new Error(`You attempted to call a function that is not stored. It may have already been released.`);
570
+ }
571
+ //#endregion
572
+ Object.defineProperty(exports, "Thread", {
573
+ enumerable: true,
574
+ get: function() {
575
+ return Thread;
576
+ }
577
+ });
578
+ Object.defineProperty(exports, "release", {
579
+ enumerable: true,
580
+ get: function() {
581
+ return release;
582
+ }
583
+ });
584
+ Object.defineProperty(exports, "retain", {
585
+ enumerable: true,
586
+ get: function() {
587
+ return retain;
588
+ }
589
+ });
590
+
591
+ //# sourceMappingURL=Thread-0eUyc0Hw.cjs.map