@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.
- package/README.md +77 -0
- package/dist/Thread-0eUyc0Hw.cjs +591 -0
- package/dist/Thread-0eUyc0Hw.cjs.map +1 -0
- package/dist/Thread-BiW7rXts.mjs +574 -0
- package/dist/Thread-BiW7rXts.mjs.map +1 -0
- package/dist/index.cjs +6 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +3 -0
- package/dist/remote-dom-renderer-BU-73KvK.d.mts +90 -0
- package/dist/remote-dom-renderer-BU-73KvK.d.mts.map +1 -0
- package/dist/remote-dom-renderer-DSHFetvm.d.cts +90 -0
- package/dist/remote-dom-renderer-DSHFetvm.d.cts.map +1 -0
- package/dist/remote-dom-renderer-DUQf80Nu.cjs +2116 -0
- package/dist/remote-dom-renderer-DUQf80Nu.cjs.map +1 -0
- package/dist/remote-dom-renderer-DcwcNPsu.mjs +2081 -0
- package/dist/remote-dom-renderer-DcwcNPsu.mjs.map +1 -0
- package/dist/remote-dom-values-BSAxrOE7.mjs +238 -0
- package/dist/remote-dom-values-BSAxrOE7.mjs.map +1 -0
- package/dist/remote-dom-values-D4AgBNWr.d.cts +32 -0
- package/dist/remote-dom-values-D4AgBNWr.d.cts.map +1 -0
- package/dist/remote-dom-values-DbgIedSg.cjs +297 -0
- package/dist/remote-dom-values-DbgIedSg.cjs.map +1 -0
- package/dist/remote-dom-values-EwoLXnSn.d.mts +32 -0
- package/dist/remote-dom-values-EwoLXnSn.d.mts.map +1 -0
- package/dist/remote-dom.cjs +6 -0
- package/dist/remote-dom.d.cts +3 -0
- package/dist/remote-dom.d.mts +3 -0
- package/dist/remote-dom.mjs +3 -0
- package/dist/worker-react-bootstrap-C7tCMYGA.mjs +13 -0
- package/dist/worker-react-bootstrap-C7tCMYGA.mjs.map +1 -0
- package/dist/worker-react-bootstrap-N8jcWZ3L.cjs +13 -0
- package/dist/worker-react-bootstrap-N8jcWZ3L.cjs.map +1 -0
- package/dist/worker-react-renderer-BKzig_Vy.mjs +185 -0
- package/dist/worker-react-renderer-BKzig_Vy.mjs.map +1 -0
- package/dist/worker-react-renderer-BSFxMMf7.cjs +186 -0
- package/dist/worker-react-renderer-BSFxMMf7.cjs.map +1 -0
- package/dist/worker.cjs +325 -0
- package/dist/worker.cjs.map +1 -0
- package/dist/worker.d.cts +46 -0
- package/dist/worker.d.cts.map +1 -0
- package/dist/worker.d.mts +46 -0
- package/dist/worker.d.mts.map +1 -0
- package/dist/worker.mjs +319 -0
- package/dist/worker.mjs.map +1 -0
- 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
|