@riebeckite/runtime 0.0.0-stage → 0.0.36
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 +201 -0
- package/README.md +97 -2
- package/README_ja.md +100 -0
- package/dist/client.d.ts +8 -0
- package/dist/client.js +1343 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +617 -0
- package/dist/src/client/errors.d.ts +2 -0
- package/dist/src/client/fallback.d.ts +7 -0
- package/dist/src/client/host.d.ts +10 -0
- package/dist/src/client/iframe.d.ts +16 -0
- package/dist/src/client/main.d.ts +19 -0
- package/dist/src/client/wasm_worker.d.ts +12 -0
- package/dist/src/client/worker.d.ts +31 -0
- package/dist/src/core/abort.d.ts +1 -0
- package/dist/src/core/errors.d.ts +44 -0
- package/dist/src/core/instance.d.ts +32 -0
- package/dist/src/core/outcome.d.ts +7 -0
- package/dist/src/core/payload.d.ts +6 -0
- package/dist/src/core/protocol.d.ts +26 -0
- package/dist/src/core/registry.d.ts +17 -0
- package/dist/src/core/types.d.ts +77 -0
- package/dist/worker.d.ts +37 -0
- package/dist/worker.js +438 -0
- package/package.json +66 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from "./src/core/errors.js";
|
|
2
|
+
export * from "./src/core/instance.js";
|
|
3
|
+
export * from "./src/core/outcome.js";
|
|
4
|
+
export * from "./src/core/payload.js";
|
|
5
|
+
export * from "./src/core/protocol.js";
|
|
6
|
+
export * from "./src/core/registry.js";
|
|
7
|
+
export * from "./src/core/types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
// src/core/errors.ts
|
|
2
|
+
var RuntimeError = class extends Error {
|
|
3
|
+
constructor(message, options) {
|
|
4
|
+
super(message, options);
|
|
5
|
+
this.name = "RuntimeError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
var RuntimeStateError = class extends RuntimeError {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "RuntimeStateError";
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var RuntimeFailureError = class extends RuntimeStateError {
|
|
15
|
+
constructor(message, options) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "RuntimeFailureError";
|
|
18
|
+
if (options?.cause !== void 0) this.cause = options.cause;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var RuntimeAbortError = class extends RuntimeError {
|
|
22
|
+
constructor(message = "Interactive runtime operation was aborted.") {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = "RuntimeAbortError";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var RuntimeTimeoutError = class extends RuntimeError {
|
|
28
|
+
constructor(message = "Interactive runtime operation timed out.") {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "RuntimeTimeoutError";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var RuntimePayloadError = class extends RuntimeError {
|
|
34
|
+
constructor(message) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = "RuntimePayloadError";
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var RuntimeConcurrencyError = class extends RuntimeError {
|
|
40
|
+
constructor(message) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = "RuntimeConcurrencyError";
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var RuntimeWorkerError = class extends RuntimeError {
|
|
46
|
+
remoteName;
|
|
47
|
+
remoteKind;
|
|
48
|
+
constructor(message, options) {
|
|
49
|
+
super(message, options);
|
|
50
|
+
this.name = "RuntimeWorkerError";
|
|
51
|
+
this.remoteName = options?.remoteName;
|
|
52
|
+
this.remoteKind = options?.remoteKind;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var RuntimeWasmError = class extends RuntimeError {
|
|
56
|
+
trap;
|
|
57
|
+
constructor(message, options) {
|
|
58
|
+
super(message, options);
|
|
59
|
+
this.name = "RuntimeWasmError";
|
|
60
|
+
this.trap = options?.trap ?? false;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var RuntimeIframeError = class extends RuntimeError {
|
|
64
|
+
remoteKind;
|
|
65
|
+
constructor(message, options) {
|
|
66
|
+
super(message, options);
|
|
67
|
+
this.name = "RuntimeIframeError";
|
|
68
|
+
this.remoteKind = options?.remoteKind;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var RuntimeUnsupportedError = class extends RuntimeError {
|
|
72
|
+
constructor(message) {
|
|
73
|
+
super(message);
|
|
74
|
+
this.name = "RuntimeUnsupportedError";
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// src/core/abort.ts
|
|
79
|
+
var knownReasonTypes = [
|
|
80
|
+
RuntimeAbortError,
|
|
81
|
+
RuntimeTimeoutError,
|
|
82
|
+
RuntimeWasmError,
|
|
83
|
+
RuntimeWorkerError,
|
|
84
|
+
RuntimeIframeError,
|
|
85
|
+
RuntimeFailureError
|
|
86
|
+
];
|
|
87
|
+
function abortReasonToError(signal) {
|
|
88
|
+
const reason = signal.reason;
|
|
89
|
+
if (reason instanceof Error) {
|
|
90
|
+
for (const type of knownReasonTypes) {
|
|
91
|
+
if (reason instanceof type) return reason;
|
|
92
|
+
}
|
|
93
|
+
if (reason.message) return new RuntimeAbortError(reason.message);
|
|
94
|
+
}
|
|
95
|
+
return new RuntimeAbortError();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/core/outcome.ts
|
|
99
|
+
var RuntimeTransfer = class {
|
|
100
|
+
constructor(value, transfer = []) {
|
|
101
|
+
this.value = value;
|
|
102
|
+
this.transfer = transfer;
|
|
103
|
+
}
|
|
104
|
+
value;
|
|
105
|
+
transfer;
|
|
106
|
+
};
|
|
107
|
+
function transferOutput(value, transfer = []) {
|
|
108
|
+
return new RuntimeTransfer(value, transfer);
|
|
109
|
+
}
|
|
110
|
+
function isRuntimeTransfer(value) {
|
|
111
|
+
return value instanceof RuntimeTransfer;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/core/payload.ts
|
|
115
|
+
var MAX_DEPTH = 16;
|
|
116
|
+
var MAX_ENTRIES = 1e5;
|
|
117
|
+
var encoder = new TextEncoder();
|
|
118
|
+
function estimatePayloadSize(value) {
|
|
119
|
+
return estimate(value, 0, { entries: 0 }, /* @__PURE__ */ new WeakSet());
|
|
120
|
+
}
|
|
121
|
+
function assertPayloadSize(value, limit, label) {
|
|
122
|
+
if (limit <= 0) return;
|
|
123
|
+
const size = estimatePayloadSize(value);
|
|
124
|
+
if (size > limit) {
|
|
125
|
+
throw new RuntimePayloadError(
|
|
126
|
+
`${label} is ${size} bytes, exceeding the ${limit} byte runtime limit.`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function estimate(value, depth, counter, ancestors) {
|
|
131
|
+
counter.entries += 1;
|
|
132
|
+
if (counter.entries > MAX_ENTRIES) {
|
|
133
|
+
throw new RuntimePayloadError(
|
|
134
|
+
"Runtime payload contains too many entries to measure."
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (value === void 0 || value === null) return 0;
|
|
138
|
+
if (typeof value === "string") return encoder.encode(value).byteLength;
|
|
139
|
+
if (typeof value === "number") return 8;
|
|
140
|
+
if (typeof value === "boolean") return 1;
|
|
141
|
+
if (typeof value === "bigint") return 16;
|
|
142
|
+
if (typeof value === "function" || typeof value === "symbol") {
|
|
143
|
+
throw new RuntimePayloadError(
|
|
144
|
+
"Runtime payload is not structured-cloneable."
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
if (value instanceof ArrayBuffer) return value.byteLength;
|
|
148
|
+
if (ArrayBuffer.isView(value)) return value.byteLength;
|
|
149
|
+
if (value instanceof Date) return 8;
|
|
150
|
+
if (depth >= MAX_DEPTH) {
|
|
151
|
+
throw new RuntimePayloadError("Runtime payload is nested too deeply.");
|
|
152
|
+
}
|
|
153
|
+
const objectValue = value;
|
|
154
|
+
if (ancestors.has(objectValue)) {
|
|
155
|
+
throw new RuntimePayloadError("Runtime payload must not contain cycles.");
|
|
156
|
+
}
|
|
157
|
+
ancestors.add(objectValue);
|
|
158
|
+
let total = 0;
|
|
159
|
+
if (value instanceof Map) {
|
|
160
|
+
for (const [key, item] of value) {
|
|
161
|
+
total += estimate(key, depth + 1, counter, ancestors);
|
|
162
|
+
total += estimate(item, depth + 1, counter, ancestors);
|
|
163
|
+
}
|
|
164
|
+
} else if (value instanceof Set) {
|
|
165
|
+
for (const item of value) {
|
|
166
|
+
total += estimate(item, depth + 1, counter, ancestors);
|
|
167
|
+
}
|
|
168
|
+
} else if (Array.isArray(value)) {
|
|
169
|
+
for (const item of value) {
|
|
170
|
+
total += estimate(item, depth + 1, counter, ancestors);
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
for (const key of Object.keys(value)) {
|
|
174
|
+
total += encoder.encode(key).byteLength;
|
|
175
|
+
total += estimate(
|
|
176
|
+
value[key],
|
|
177
|
+
depth + 1,
|
|
178
|
+
counter,
|
|
179
|
+
ancestors
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
ancestors.delete(objectValue);
|
|
184
|
+
return total;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/core/instance.ts
|
|
188
|
+
var instanceCounter = 0;
|
|
189
|
+
var RuntimeInstance = class {
|
|
190
|
+
id;
|
|
191
|
+
instanceId;
|
|
192
|
+
kind;
|
|
193
|
+
capabilities;
|
|
194
|
+
driver;
|
|
195
|
+
controller = new AbortController();
|
|
196
|
+
listeners = /* @__PURE__ */ new Set();
|
|
197
|
+
active = /* @__PURE__ */ new Set();
|
|
198
|
+
limits;
|
|
199
|
+
onError;
|
|
200
|
+
currentState = "created";
|
|
201
|
+
requestCount = 0;
|
|
202
|
+
mountPromise;
|
|
203
|
+
disposePromise;
|
|
204
|
+
stopPromise;
|
|
205
|
+
failure;
|
|
206
|
+
constructor(definition, options) {
|
|
207
|
+
this.id = definition.id;
|
|
208
|
+
this.instanceId = createInstanceId(definition.id);
|
|
209
|
+
this.kind = definition.kind;
|
|
210
|
+
this.capabilities = definition.capabilities;
|
|
211
|
+
this.limits = resolveLimits(definition.limits);
|
|
212
|
+
this.onError = options.onError ?? defaultErrorReporter;
|
|
213
|
+
this.driver = definition.create({
|
|
214
|
+
instanceId: this.instanceId,
|
|
215
|
+
signal: this.controller.signal,
|
|
216
|
+
limits: this.limits,
|
|
217
|
+
root: options.root
|
|
218
|
+
});
|
|
219
|
+
if (options.signal?.aborted) {
|
|
220
|
+
void this.dispose();
|
|
221
|
+
} else {
|
|
222
|
+
options.signal?.addEventListener("abort", () => void this.dispose(), {
|
|
223
|
+
once: true
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
get state() {
|
|
228
|
+
return this.currentState;
|
|
229
|
+
}
|
|
230
|
+
get signal() {
|
|
231
|
+
return this.controller.signal;
|
|
232
|
+
}
|
|
233
|
+
onStateChange(listener) {
|
|
234
|
+
this.listeners.add(listener);
|
|
235
|
+
return () => this.listeners.delete(listener);
|
|
236
|
+
}
|
|
237
|
+
mount() {
|
|
238
|
+
if (this.disposePromise) {
|
|
239
|
+
return Promise.reject(this.disposedError());
|
|
240
|
+
}
|
|
241
|
+
if (this.mountPromise) return this.mountPromise;
|
|
242
|
+
if (this.currentState !== "created") {
|
|
243
|
+
return Promise.reject(
|
|
244
|
+
new RuntimeStateError(
|
|
245
|
+
`Cannot mount runtime "${this.id}" from state "${this.currentState}".`
|
|
246
|
+
)
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
this.mountPromise = this.runStart();
|
|
250
|
+
return this.mountPromise;
|
|
251
|
+
}
|
|
252
|
+
async invoke(input, options = {}) {
|
|
253
|
+
if (this.currentState === "failed") {
|
|
254
|
+
throw new RuntimeFailureError(
|
|
255
|
+
`Runtime "${this.id}" failed and cannot be reused; create a new instance.`,
|
|
256
|
+
{ cause: this.failure }
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
if (this.currentState !== "ready" && this.currentState !== "running") {
|
|
260
|
+
throw new RuntimeStateError(
|
|
261
|
+
`Cannot invoke runtime "${this.id}" from state "${this.currentState}".`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
if (this.active.size >= this.limits.maxConcurrent) {
|
|
265
|
+
throw new RuntimeConcurrencyError(
|
|
266
|
+
`Runtime "${this.id}" rejected an invocation: ${this.active.size} requests are already in flight.`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
if (options.signal?.aborted) throw abortReasonToError(options.signal);
|
|
270
|
+
if (options.capability && !this.capabilities.includes(options.capability)) {
|
|
271
|
+
throw new RuntimeStateError(
|
|
272
|
+
`Runtime "${this.id}" does not declare the "${options.capability}" capability.`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
assertPayloadSize(input, this.limits.maxInputBytes, "Runtime input");
|
|
276
|
+
const requestId = ++this.requestCount;
|
|
277
|
+
const call = new AbortController();
|
|
278
|
+
const unlinkInstance = linkSignal(this.controller.signal, call);
|
|
279
|
+
const unlinkExternal = options.signal ? linkSignal(options.signal, call) : () => {
|
|
280
|
+
};
|
|
281
|
+
const timeoutMs = options.timeoutMs ?? this.limits.invokeTimeoutMs;
|
|
282
|
+
const timer = timeoutMs > 0 ? setTimeout(
|
|
283
|
+
() => call.abort(
|
|
284
|
+
new RuntimeTimeoutError(
|
|
285
|
+
`Runtime "${this.id}" invocation timed out after ${timeoutMs}ms.`
|
|
286
|
+
)
|
|
287
|
+
),
|
|
288
|
+
timeoutMs
|
|
289
|
+
) : void 0;
|
|
290
|
+
this.active.add(requestId);
|
|
291
|
+
this.transition("running");
|
|
292
|
+
try {
|
|
293
|
+
const pending = Promise.resolve(
|
|
294
|
+
this.driver.invoke(input, {
|
|
295
|
+
signal: call.signal,
|
|
296
|
+
requestId,
|
|
297
|
+
capability: options.capability,
|
|
298
|
+
transfer: options.transfer
|
|
299
|
+
})
|
|
300
|
+
);
|
|
301
|
+
const raw = await awaitWithSignal(pending, call.signal);
|
|
302
|
+
const output = isRuntimeTransfer(raw) ? raw.value : raw;
|
|
303
|
+
assertPayloadSize(output, this.limits.maxOutputBytes, "Runtime output");
|
|
304
|
+
return output;
|
|
305
|
+
} catch (error) {
|
|
306
|
+
if (error instanceof RuntimeFailureError) {
|
|
307
|
+
this.failure = error;
|
|
308
|
+
if (this.currentState === "running") this.transition("failed");
|
|
309
|
+
this.report(error, "invoke");
|
|
310
|
+
}
|
|
311
|
+
throw error;
|
|
312
|
+
} finally {
|
|
313
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
314
|
+
unlinkExternal();
|
|
315
|
+
unlinkInstance();
|
|
316
|
+
this.active.delete(requestId);
|
|
317
|
+
if (this.active.size === 0 && this.currentState === "running") {
|
|
318
|
+
this.transition("ready");
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
dispose() {
|
|
323
|
+
if (this.disposePromise) return this.disposePromise;
|
|
324
|
+
if (this.currentState === "disposed") return Promise.resolve();
|
|
325
|
+
this.disposePromise = this.runStop();
|
|
326
|
+
return this.disposePromise;
|
|
327
|
+
}
|
|
328
|
+
async runStart() {
|
|
329
|
+
this.transition("starting");
|
|
330
|
+
try {
|
|
331
|
+
const start = Promise.resolve(this.driver.start());
|
|
332
|
+
await awaitWithSignal(
|
|
333
|
+
withTimeout(
|
|
334
|
+
start,
|
|
335
|
+
this.limits.startTimeoutMs,
|
|
336
|
+
`Runtime "${this.id}" start timed out.`
|
|
337
|
+
),
|
|
338
|
+
this.controller.signal
|
|
339
|
+
);
|
|
340
|
+
if (this.currentState !== "starting") {
|
|
341
|
+
throw this.disposedError();
|
|
342
|
+
}
|
|
343
|
+
this.transition("ready");
|
|
344
|
+
} catch (error) {
|
|
345
|
+
this.failure = error;
|
|
346
|
+
if (this.currentState === "starting") this.transition("failed");
|
|
347
|
+
this.report(error, "start");
|
|
348
|
+
await this.stopDriver();
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async runStop() {
|
|
353
|
+
if (this.currentState !== "disposed") this.transition("stopping");
|
|
354
|
+
this.controller.abort(new RuntimeAbortError("Runtime was disposed."));
|
|
355
|
+
await this.mountPromise?.catch(() => {
|
|
356
|
+
});
|
|
357
|
+
await this.stopDriver();
|
|
358
|
+
if (this.currentState !== "disposed") this.transition("disposed");
|
|
359
|
+
this.listeners.clear();
|
|
360
|
+
}
|
|
361
|
+
stopDriver() {
|
|
362
|
+
this.stopPromise ??= (async () => {
|
|
363
|
+
try {
|
|
364
|
+
await this.driver.stop();
|
|
365
|
+
} catch (error) {
|
|
366
|
+
this.report(error, "stop");
|
|
367
|
+
}
|
|
368
|
+
})();
|
|
369
|
+
return this.stopPromise;
|
|
370
|
+
}
|
|
371
|
+
disposedError() {
|
|
372
|
+
return new RuntimeAbortError(`Runtime "${this.id}" was disposed.`);
|
|
373
|
+
}
|
|
374
|
+
transition(state) {
|
|
375
|
+
const previous = this.currentState;
|
|
376
|
+
if (previous === state) return;
|
|
377
|
+
this.currentState = state;
|
|
378
|
+
const change = { previous, state };
|
|
379
|
+
for (const listener of this.listeners) {
|
|
380
|
+
try {
|
|
381
|
+
listener(change);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
this.report(error, "state-change");
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
report(error, phase) {
|
|
388
|
+
try {
|
|
389
|
+
this.onError(error, phase);
|
|
390
|
+
} catch {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
function resolveLimits(limits) {
|
|
396
|
+
return {
|
|
397
|
+
maxInputBytes: limits.maxInputBytes ?? 4 * 1024 * 1024,
|
|
398
|
+
maxOutputBytes: limits.maxOutputBytes ?? 16 * 1024 * 1024,
|
|
399
|
+
maxConcurrent: limits.maxConcurrent ?? 16,
|
|
400
|
+
invokeTimeoutMs: limits.invokeTimeoutMs ?? 0,
|
|
401
|
+
startTimeoutMs: limits.startTimeoutMs ?? 15e3
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function createInstanceId(id) {
|
|
405
|
+
const random = globalThis.crypto?.randomUUID?.();
|
|
406
|
+
if (random) return `${id}#${random}`;
|
|
407
|
+
instanceCounter += 1;
|
|
408
|
+
return `${id}#${Date.now().toString(36)}-${instanceCounter}`;
|
|
409
|
+
}
|
|
410
|
+
function linkSignal(parent, child) {
|
|
411
|
+
if (parent.aborted) {
|
|
412
|
+
child.abort(parent.reason);
|
|
413
|
+
return () => {
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
const listener = () => child.abort(parent.reason);
|
|
417
|
+
parent.addEventListener("abort", listener, { once: true });
|
|
418
|
+
return () => parent.removeEventListener("abort", listener);
|
|
419
|
+
}
|
|
420
|
+
function abortFrom(signal) {
|
|
421
|
+
return abortReasonToError(signal);
|
|
422
|
+
}
|
|
423
|
+
function awaitWithSignal(promise, signal) {
|
|
424
|
+
if (signal.aborted) {
|
|
425
|
+
void promise.catch(() => {
|
|
426
|
+
});
|
|
427
|
+
return Promise.reject(abortFrom(signal));
|
|
428
|
+
}
|
|
429
|
+
return new Promise((resolve, reject) => {
|
|
430
|
+
const onAbort = () => {
|
|
431
|
+
cleanup();
|
|
432
|
+
reject(abortFrom(signal));
|
|
433
|
+
};
|
|
434
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
435
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
436
|
+
promise.then(
|
|
437
|
+
(value) => {
|
|
438
|
+
cleanup();
|
|
439
|
+
resolve(value);
|
|
440
|
+
},
|
|
441
|
+
(error) => {
|
|
442
|
+
cleanup();
|
|
443
|
+
reject(error);
|
|
444
|
+
}
|
|
445
|
+
);
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
function withTimeout(promise, timeoutMs, message) {
|
|
449
|
+
if (timeoutMs <= 0) return promise;
|
|
450
|
+
return new Promise((resolve, reject) => {
|
|
451
|
+
const timer = setTimeout(
|
|
452
|
+
() => reject(new RuntimeTimeoutError(message)),
|
|
453
|
+
timeoutMs
|
|
454
|
+
);
|
|
455
|
+
promise.then(
|
|
456
|
+
(value) => {
|
|
457
|
+
clearTimeout(timer);
|
|
458
|
+
resolve(value);
|
|
459
|
+
},
|
|
460
|
+
(error) => {
|
|
461
|
+
clearTimeout(timer);
|
|
462
|
+
reject(error);
|
|
463
|
+
}
|
|
464
|
+
);
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
function defaultErrorReporter(error, phase) {
|
|
468
|
+
if (error instanceof RuntimeAbortError) return;
|
|
469
|
+
console.error(`[riebeckite] Interactive runtime ${phase} failed.`, error);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/core/protocol.ts
|
|
473
|
+
var RUNTIME_PROTOCOL = "riebeckite:runtime";
|
|
474
|
+
var RUNTIME_PROTOCOL_VERSION = 1;
|
|
475
|
+
var RUNTIME_MESSAGE_KINDS = [
|
|
476
|
+
"init",
|
|
477
|
+
"invoke",
|
|
478
|
+
"cancel",
|
|
479
|
+
"ready",
|
|
480
|
+
"result",
|
|
481
|
+
"error",
|
|
482
|
+
"fatal",
|
|
483
|
+
"dispose"
|
|
484
|
+
];
|
|
485
|
+
function isRuntimeMessage(value) {
|
|
486
|
+
if (!value || typeof value !== "object") return false;
|
|
487
|
+
const message = value;
|
|
488
|
+
if (message.protocol !== RUNTIME_PROTOCOL) return false;
|
|
489
|
+
if (message.version !== RUNTIME_PROTOCOL_VERSION) return false;
|
|
490
|
+
if (typeof message.instanceId !== "string") return false;
|
|
491
|
+
return RUNTIME_MESSAGE_KINDS.includes(message.kind);
|
|
492
|
+
}
|
|
493
|
+
function serializeRuntimeError(error) {
|
|
494
|
+
if (error instanceof Error) {
|
|
495
|
+
const details = error;
|
|
496
|
+
return {
|
|
497
|
+
name: error.name,
|
|
498
|
+
message: error.message,
|
|
499
|
+
kind: typeof details.kind === "string" ? details.kind : void 0,
|
|
500
|
+
trap: details.trap === true,
|
|
501
|
+
stack: error.stack
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
name: "Error",
|
|
506
|
+
message: typeof error === "string" ? error : "Unknown runtime error."
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/core/registry.ts
|
|
511
|
+
var InteractiveRuntimeRegistry = class {
|
|
512
|
+
definitions = /* @__PURE__ */ new Map();
|
|
513
|
+
register(definition) {
|
|
514
|
+
if (this.definitions.has(definition.id)) {
|
|
515
|
+
throw new RuntimeStateError(
|
|
516
|
+
`Interactive runtime "${definition.id}" is already registered.`
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
this.definitions.set(
|
|
520
|
+
definition.id,
|
|
521
|
+
definition
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
has(id) {
|
|
525
|
+
return this.definitions.has(id);
|
|
526
|
+
}
|
|
527
|
+
get(id) {
|
|
528
|
+
const definition = this.definitions.get(id);
|
|
529
|
+
if (!definition) {
|
|
530
|
+
throw new RuntimeStateError(
|
|
531
|
+
`Interactive runtime "${id}" is not registered.`
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
return definition;
|
|
535
|
+
}
|
|
536
|
+
get ids() {
|
|
537
|
+
return [...this.definitions.keys()];
|
|
538
|
+
}
|
|
539
|
+
create(id, options = {}) {
|
|
540
|
+
return new RuntimeInstance(this.get(id), options);
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
function defineInteractiveRuntime(definition) {
|
|
544
|
+
if (!definition.id.trim()) {
|
|
545
|
+
throw new RuntimeStateError("Interactive runtime id must not be empty.");
|
|
546
|
+
}
|
|
547
|
+
const capabilities = [...definition.capabilities ?? []];
|
|
548
|
+
if (new Set(capabilities).size !== capabilities.length) {
|
|
549
|
+
throw new RuntimeStateError(
|
|
550
|
+
`Interactive runtime "${definition.id}" declares duplicate capabilities.`
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
return Object.freeze({
|
|
554
|
+
id: definition.id,
|
|
555
|
+
kind: definition.kind,
|
|
556
|
+
capabilities: Object.freeze(capabilities),
|
|
557
|
+
limits: Object.freeze({ ...definition.limits ?? {} }),
|
|
558
|
+
create: definition.create
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
function createInteractiveRuntime(definition, options = {}) {
|
|
562
|
+
return new RuntimeInstance(definition, options);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// src/core/types.ts
|
|
566
|
+
var RUNTIME_KINDS = [
|
|
567
|
+
"main",
|
|
568
|
+
"worker",
|
|
569
|
+
"wasm-worker",
|
|
570
|
+
"iframe"
|
|
571
|
+
];
|
|
572
|
+
var RUNTIME_STATES = [
|
|
573
|
+
"created",
|
|
574
|
+
"starting",
|
|
575
|
+
"ready",
|
|
576
|
+
"running",
|
|
577
|
+
"stopping",
|
|
578
|
+
"disposed",
|
|
579
|
+
"failed"
|
|
580
|
+
];
|
|
581
|
+
var RUNTIME_CAPABILITIES = [
|
|
582
|
+
"dom",
|
|
583
|
+
"worker",
|
|
584
|
+
"wasm",
|
|
585
|
+
"network",
|
|
586
|
+
"message"
|
|
587
|
+
];
|
|
588
|
+
export {
|
|
589
|
+
InteractiveRuntimeRegistry,
|
|
590
|
+
RUNTIME_CAPABILITIES,
|
|
591
|
+
RUNTIME_KINDS,
|
|
592
|
+
RUNTIME_MESSAGE_KINDS,
|
|
593
|
+
RUNTIME_PROTOCOL,
|
|
594
|
+
RUNTIME_PROTOCOL_VERSION,
|
|
595
|
+
RUNTIME_STATES,
|
|
596
|
+
RuntimeAbortError,
|
|
597
|
+
RuntimeConcurrencyError,
|
|
598
|
+
RuntimeError,
|
|
599
|
+
RuntimeFailureError,
|
|
600
|
+
RuntimeIframeError,
|
|
601
|
+
RuntimeInstance,
|
|
602
|
+
RuntimePayloadError,
|
|
603
|
+
RuntimeStateError,
|
|
604
|
+
RuntimeTimeoutError,
|
|
605
|
+
RuntimeTransfer,
|
|
606
|
+
RuntimeUnsupportedError,
|
|
607
|
+
RuntimeWasmError,
|
|
608
|
+
RuntimeWorkerError,
|
|
609
|
+
assertPayloadSize,
|
|
610
|
+
createInteractiveRuntime,
|
|
611
|
+
defineInteractiveRuntime,
|
|
612
|
+
estimatePayloadSize,
|
|
613
|
+
isRuntimeMessage,
|
|
614
|
+
isRuntimeTransfer,
|
|
615
|
+
serializeRuntimeError,
|
|
616
|
+
transferOutput
|
|
617
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ClientRuntimeHost } from "@riebeckite/core/client";
|
|
2
|
+
import type { InteractiveRuntimeRegistry } from "../core/registry.js";
|
|
3
|
+
import type { RuntimeInstanceOptions } from "../core/types.js";
|
|
4
|
+
export type ClientRuntimeHostOptions = {
|
|
5
|
+
readonly registry: InteractiveRuntimeRegistry;
|
|
6
|
+
readonly root?: unknown;
|
|
7
|
+
readonly signal?: AbortSignal;
|
|
8
|
+
readonly onError?: RuntimeInstanceOptions["onError"];
|
|
9
|
+
};
|
|
10
|
+
export declare function createClientRuntimeHost(options: ClientRuntimeHostOptions): ClientRuntimeHost;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RuntimeCapability, RuntimeDefinition, RuntimeLimits } from "../core/types.js";
|
|
2
|
+
export declare const IFRAME_PROTOCOL = "riebeckite:iframe";
|
|
3
|
+
export declare const IFRAME_PROTOCOL_VERSION = 1;
|
|
4
|
+
export declare const MAX_ACTIVE_IFRAMES = 4;
|
|
5
|
+
export type IframeRuntimeOptions = {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly capabilities?: readonly RuntimeCapability[];
|
|
8
|
+
readonly limits?: RuntimeLimits;
|
|
9
|
+
readonly html: string;
|
|
10
|
+
readonly title?: string;
|
|
11
|
+
readonly sandbox?: readonly string[];
|
|
12
|
+
readonly expectedOrigin?: string;
|
|
13
|
+
readonly csp?: string;
|
|
14
|
+
readonly container?: Element | string | (() => Element | null);
|
|
15
|
+
};
|
|
16
|
+
export declare function createIframeRuntime<TInput, TOutput>(options: IframeRuntimeOptions): RuntimeDefinition<TInput, TOutput>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RuntimeCapability, RuntimeDefinition, RuntimeLimits } from "../core/types.js";
|
|
2
|
+
export type MainRuntimeContext = {
|
|
3
|
+
readonly instanceId: string;
|
|
4
|
+
readonly signal: AbortSignal;
|
|
5
|
+
readonly limits: RuntimeLimits;
|
|
6
|
+
readonly root: unknown;
|
|
7
|
+
};
|
|
8
|
+
export type MainRuntimeOptions<TInput, TOutput> = {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly capabilities?: readonly RuntimeCapability[];
|
|
11
|
+
readonly limits?: RuntimeLimits;
|
|
12
|
+
readonly start?: (context: MainRuntimeContext) => void | Promise<void>;
|
|
13
|
+
readonly invoke: (input: TInput, context: MainRuntimeContext & {
|
|
14
|
+
readonly requestId: number;
|
|
15
|
+
readonly capability?: RuntimeCapability;
|
|
16
|
+
}) => TOutput | Promise<TOutput>;
|
|
17
|
+
readonly stop?: (context: MainRuntimeContext) => void | Promise<void>;
|
|
18
|
+
};
|
|
19
|
+
export declare function createMainRuntime<TInput, TOutput>(options: MainRuntimeOptions<TInput, TOutput>): RuntimeDefinition<TInput, TOutput>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RuntimeCapability, RuntimeDefinition, RuntimeLimits } from "../core/types.js";
|
|
2
|
+
import { type WorkerLike } from "./worker.js";
|
|
3
|
+
export type WasmWorkerRuntimeOptions<TOutput> = {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly capabilities?: readonly RuntimeCapability[];
|
|
6
|
+
readonly limits?: RuntimeLimits;
|
|
7
|
+
readonly createWorker: () => WorkerLike;
|
|
8
|
+
readonly wasmUrl: string;
|
|
9
|
+
readonly exportName: string;
|
|
10
|
+
readonly mapOutput?: (payload: unknown) => TOutput;
|
|
11
|
+
};
|
|
12
|
+
export declare function createWasmWorkerRuntime<TInput extends readonly unknown[], TOutput = unknown>(options: WasmWorkerRuntimeOptions<TOutput>): RuntimeDefinition<TInput, TOutput>;
|