UNPKG

miniflare

Version:

Fun, full-featured, fully-local simulator for Cloudflare Workers

1,278 lines (1,276 loc) • 90.1 kB
// src/workers/dispatch-namespace/dispatch-namespace-proxy.worker.ts import { WorkerEntrypoint } from "cloudflare:workers"; // src/workers/shared/constants.ts var SharedBindings = { TEXT_NAMESPACE: "MINIFLARE_NAMESPACE", DURABLE_OBJECT_NAMESPACE_OBJECT: "MINIFLARE_OBJECT", MAYBE_SERVICE_BLOBS: "MINIFLARE_BLOBS", MAYBE_SERVICE_LOOPBACK: "MINIFLARE_LOOPBACK", MAYBE_JSON_ENABLE_CONTROL_ENDPOINTS: "MINIFLARE_ENABLE_CONTROL_ENDPOINTS", MAYBE_JSON_ENABLE_STICKY_BLOBS: "MINIFLARE_STICKY_BLOBS" }; // ../../node_modules/.pnpm/capnweb@0.5.0/node_modules/capnweb/dist/index-workers.js import * as cfw from "cloudflare:workers"; var WORKERS_MODULE_SYMBOL = /* @__PURE__ */ Symbol("workers-module"); globalThis[WORKERS_MODULE_SYMBOL] = cfw; Symbol.dispose || (Symbol.dispose = /* @__PURE__ */ Symbol.for("dispose")); Symbol.asyncDispose || (Symbol.asyncDispose = /* @__PURE__ */ Symbol.for("asyncDispose")); Promise.withResolvers || (Promise.withResolvers = function() { let resolve, reject; return { promise: new Promise((res, rej) => { resolve = res, reject = rej; }), resolve, reject }; }); var workersModule = globalThis[WORKERS_MODULE_SYMBOL], RpcTarget = workersModule ? workersModule.RpcTarget : class { }, AsyncFunction = (async function() { }).constructor; function typeForRpc(value) { switch (typeof value) { case "boolean": case "number": case "string": return "primitive"; case "undefined": return "undefined"; case "object": case "function": break; case "bigint": return "bigint"; default: return "unsupported"; } if (value === null) return "primitive"; let prototype = Object.getPrototypeOf(value); switch (prototype) { case Object.prototype: return "object"; case Function.prototype: case AsyncFunction.prototype: return "function"; case Array.prototype: return "array"; case Date.prototype: return "date"; case Uint8Array.prototype: return "bytes"; case WritableStream.prototype: return "writable"; case ReadableStream.prototype: return "readable"; case Headers.prototype: return "headers"; case Request.prototype: return "request"; case Response.prototype: return "response"; // TODO: All other structured clone types. case RpcStub.prototype: return "stub"; case RpcPromise.prototype: return "rpc-promise"; // TODO: Promise<T> or thenable default: if (workersModule) { if (prototype == workersModule.RpcStub.prototype || value instanceof workersModule.ServiceStub) return "rpc-target"; if (prototype == workersModule.RpcPromise.prototype || prototype == workersModule.RpcProperty.prototype) return "rpc-thenable"; } return value instanceof RpcTarget ? "rpc-target" : value instanceof Error ? "error" : "unsupported"; } } function mapNotLoaded() { throw new Error("RPC map() implementation was not loaded."); } var mapImpl = { applyMap: mapNotLoaded, sendMap: mapNotLoaded }; function streamNotLoaded() { throw new Error("Stream implementation was not loaded."); } var streamImpl = { createWritableStreamHook: streamNotLoaded, createWritableStreamFromHook: streamNotLoaded, createReadableStreamHook: streamNotLoaded }, StubHook = class { // Like call(), but designed for streaming calls (e.g. WritableStream writes). Returns: // - promise: A Promise<void> for the completion of the call. // - size: If the call was remote, the byte size of the serialized message. For local calls, // undefined is returned, indicating the caller should await the promise to serialize writes // (no overlapping). stream(path, args) { let pulled = this.call(path, args).pull(), promise; return pulled instanceof Promise ? promise = pulled.then((p) => { p.dispose(); }) : (pulled.dispose(), promise = Promise.resolve()), { promise }; } }, ErrorStubHook = class extends StubHook { constructor(error) { super(), this.error = error; } call(path, args) { return this; } map(path, captures, instructions) { return this; } get(path) { return this; } dup() { return this; } pull() { return Promise.reject(this.error); } ignoreUnhandledRejections() { } dispose() { } onBroken(callback) { try { callback(this.error); } catch (err) { Promise.resolve(err); } } }, DISPOSED_HOOK = new ErrorStubHook( new Error("Attempted to use RPC stub after it has been disposed.") ), doCall = (hook, path, params) => hook.call(path, params); function withCallInterceptor(interceptor, callback) { let oldValue = doCall; doCall = interceptor; try { return callback(); } finally { doCall = oldValue; } } var RAW_STUB = /* @__PURE__ */ Symbol("realStub"), PROXY_HANDLERS = { apply(target, thisArg, argumentsList) { let stub = target.raw; return new RpcPromise(doCall( stub.hook, stub.pathIfPromise || [], RpcPayload.fromAppParams(argumentsList) ), []); }, get(target, prop, receiver) { let stub = target.raw; return prop === RAW_STUB ? stub : prop in RpcPromise.prototype ? stub[prop] : typeof prop == "string" ? new RpcPromise( stub.hook, stub.pathIfPromise ? [...stub.pathIfPromise, prop] : [prop] ) : prop === Symbol.dispose && (!stub.pathIfPromise || stub.pathIfPromise.length == 0) ? () => { stub.hook.dispose(), stub.hook = DISPOSED_HOOK; } : void 0; }, has(target, prop) { let stub = target.raw; return prop === RAW_STUB ? !0 : prop in RpcPromise.prototype ? prop in stub : typeof prop == "string" ? !0 : prop === Symbol.dispose && (!stub.pathIfPromise || stub.pathIfPromise.length == 0); }, construct(target, args) { throw new Error("An RPC stub cannot be used as a constructor."); }, defineProperty(target, property, attributes) { throw new Error("Can't define properties on RPC stubs."); }, deleteProperty(target, p) { throw new Error("Can't delete properties on RPC stubs."); }, getOwnPropertyDescriptor(target, p) { }, getPrototypeOf(target) { return Object.getPrototypeOf(target.raw); }, isExtensible(target) { return !1; }, ownKeys(target) { return []; }, preventExtensions(target) { return !0; }, set(target, p, newValue, receiver) { throw new Error("Can't assign properties on RPC stubs."); }, setPrototypeOf(target, v) { throw new Error("Can't override prototype of RPC stubs."); } }, RpcStub = class _RpcStub extends RpcTarget { // Although `hook` and `path` are declared `public` here, they are effectively hidden by the // proxy. constructor(hook, pathIfPromise) { if (super(), !(hook instanceof StubHook)) { let value = hook; if (value instanceof RpcTarget || value instanceof Function ? hook = TargetStubHook.create(value, void 0) : hook = new PayloadStubHook(RpcPayload.fromAppReturn(value)), pathIfPromise) throw new TypeError("RpcStub constructor expected one argument, received two."); } this.hook = hook, this.pathIfPromise = pathIfPromise; let func = () => { }; return func.raw = this, new Proxy(func, PROXY_HANDLERS); } hook; pathIfPromise; dup() { let target = this[RAW_STUB]; return target.pathIfPromise ? new _RpcStub(target.hook.get(target.pathIfPromise)) : new _RpcStub(target.hook.dup()); } onRpcBroken(callback) { this[RAW_STUB].hook.onBroken(callback); } map(func) { let { hook, pathIfPromise } = this[RAW_STUB]; return mapImpl.sendMap(hook, pathIfPromise || [], func); } toString() { return "[object RpcStub]"; } }, RpcPromise = class extends RpcStub { // TODO: Support passing target value or promise to constructor. constructor(hook, pathIfPromise) { super(hook, pathIfPromise); } then(onfulfilled, onrejected) { return pullPromise(this).then(...arguments); } catch(onrejected) { return pullPromise(this).catch(...arguments); } finally(onfinally) { return pullPromise(this).finally(...arguments); } toString() { return "[object RpcPromise]"; } }; function unwrapStubTakingOwnership(stub) { let { hook, pathIfPromise } = stub[RAW_STUB]; return pathIfPromise && pathIfPromise.length > 0 ? hook.get(pathIfPromise) : hook; } function unwrapStubAndDup(stub) { let { hook, pathIfPromise } = stub[RAW_STUB]; return pathIfPromise ? hook.get(pathIfPromise) : hook.dup(); } function unwrapStubNoProperties(stub) { let { hook, pathIfPromise } = stub[RAW_STUB]; if (!(pathIfPromise && pathIfPromise.length > 0)) return hook; } function unwrapStubOrParent(stub) { return stub[RAW_STUB].hook; } function unwrapStubAndPath(stub) { return stub[RAW_STUB]; } async function pullPromise(promise) { let { hook, pathIfPromise } = promise[RAW_STUB]; return pathIfPromise.length > 0 && (hook = hook.get(pathIfPromise)), (await hook.pull()).deliverResolve(); } var RpcPayload = class _RpcPayload { // Private constructor; use factory functions above to construct. constructor(value, source, hooks, promises) { this.value = value, this.source = source, this.hooks = hooks, this.promises = promises; } // Create a payload from a value passed as params to an RPC from the app. // // The payload does NOT take ownership of any stubs in `value`, and but promises not to modify // `value`. If the payload is delivered locally, `value` will be deep-copied first, so as not // to have the sender and recipient end up sharing the same mutable object. `value` will not be // touched again after the call returns synchronously (returns a promise) -- by that point, // the value has either been copied or serialized to the wire. static fromAppParams(value) { return new _RpcPayload(value, "params"); } // Create a payload from a value return from an RPC implementation by the app. // // Unlike fromAppParams(), in this case the payload takes ownership of all stubs in `value`, and // may hold onto `value` for an arbitrarily long time (e.g. to serve pipelined requests). It // will still avoid modifying `value` and will make a deep copy if it is delivered locally. static fromAppReturn(value) { return new _RpcPayload(value, "return"); } // Combine an array of payloads into a single payload whose value is an array. Ownership of all // stubs is transferred from the inputs to the outputs, hence if the output is disposed, the // inputs should not be. (In case of exception, nothing is disposed, though.) static fromArray(array) { let hooks = [], promises = [], resultArray = []; for (let payload of array) { payload.ensureDeepCopied(); for (let hook of payload.hooks) hooks.push(hook); for (let promise of payload.promises) promise.parent === payload && (promise = { parent: resultArray, property: resultArray.length, promise: promise.promise }), promises.push(promise); resultArray.push(payload.value); } return new _RpcPayload(resultArray, "owned", hooks, promises); } // Create a payload from a value parsed off the wire using Evaluator.evaluate(). // // A payload is constructed with a null value and the given hooks and promises arrays. The value // is expected to be filled in by the evaluator, and the hooks and promises arrays are expected // to be extended with stubs found during parsing. (This weird usage model is necessary so that // if the root value turns out to be a promise, its `parent` in `promises` can be the payload // object itself.) // // When done, the payload takes ownership of the final value and all the stubs within. It may // modify the value in preparation for delivery, and may deliver the value directly to the app // without copying. static forEvaluate(hooks, promises) { return new _RpcPayload(null, "owned", hooks, promises); } // Deep-copy the given value, including dup()ing all stubs. // // If `value` is a function, it should be bound to `oldParent` as its `this`. // // If deep-copying from a branch of some other RpcPayload, it must be provided, to make sure // RpcTargets found within don't get duplicate stubs. static deepCopyFrom(value, oldParent, owner) { let result = new _RpcPayload(null, "owned", [], []); return result.value = result.deepCopy( value, oldParent, "value", result, /*dupStubs=*/ !0, owner ), result; } // For `source === "return"` payloads only, this tracks any StubHooks created around RpcTargets // or WritableStreams found in the payload at the time that it is serialized (or deep-copied) for // return, so that we can make sure they are not disposed before the pipeline ends. // // This is initialized on first use. rpcTargets; // Get the StubHook representing the given RpcTarget found inside this payload. getHookForRpcTarget(target, parent, dupStubs = !0) { if (this.source === "params") { if (dupStubs) { let dupable = target; typeof dupable.dup == "function" && (target = dupable.dup()); } return TargetStubHook.create(target, parent); } else if (this.source === "return") { let hook = this.rpcTargets?.get(target); return hook ? dupStubs ? hook.dup() : (this.rpcTargets?.delete(target), hook) : (hook = TargetStubHook.create(target, parent), dupStubs ? (this.rpcTargets || (this.rpcTargets = /* @__PURE__ */ new Map()), this.rpcTargets.set(target, hook), hook.dup()) : hook); } else throw new Error("owned payload shouldn't contain raw RpcTargets"); } // Get the StubHook representing the given WritableStream found inside this payload. getHookForWritableStream(stream, parent, dupStubs = !0) { if (this.source === "params") return streamImpl.createWritableStreamHook(stream); if (this.source === "return") { let hook = this.rpcTargets?.get(stream); return hook ? dupStubs ? hook.dup() : (this.rpcTargets?.delete(stream), hook) : (hook = streamImpl.createWritableStreamHook(stream), dupStubs ? (this.rpcTargets || (this.rpcTargets = /* @__PURE__ */ new Map()), this.rpcTargets.set(stream, hook), hook.dup()) : hook); } else throw new Error("owned payload shouldn't contain raw WritableStreams"); } // Get the StubHook representing the given ReadableStream found inside this payload. getHookForReadableStream(stream, parent, dupStubs = !0) { if (this.source === "params") return streamImpl.createReadableStreamHook(stream); if (this.source === "return") { let hook = this.rpcTargets?.get(stream); return hook ? dupStubs ? hook.dup() : (this.rpcTargets?.delete(stream), hook) : (hook = streamImpl.createReadableStreamHook(stream), dupStubs ? (this.rpcTargets || (this.rpcTargets = /* @__PURE__ */ new Map()), this.rpcTargets.set(stream, hook), hook.dup()) : hook); } else throw new Error("owned payload shouldn't contain raw ReadableStreams"); } deepCopy(value, oldParent, property, parent, dupStubs, owner) { switch (typeForRpc(value)) { case "unsupported": return value; case "primitive": case "bigint": case "date": case "bytes": case "error": case "undefined": return value; case "array": { let array = value, len = array.length, result = new Array(len); for (let i = 0; i < len; i++) result[i] = this.deepCopy(array[i], array, i, result, dupStubs, owner); return result; } case "object": { let result = {}, object = value; for (let i in object) result[i] = this.deepCopy(object[i], object, i, result, dupStubs, owner); return result; } case "stub": case "rpc-promise": { let stub = value, hook; if (dupStubs ? hook = unwrapStubAndDup(stub) : hook = unwrapStubTakingOwnership(stub), stub instanceof RpcPromise) { let promise = new RpcPromise(hook, []); return this.promises.push({ parent, property, promise }), promise; } else return this.hooks.push(hook), new RpcStub(hook); } case "function": case "rpc-target": { let target = value, hook; return owner ? hook = owner.getHookForRpcTarget(target, oldParent, dupStubs) : hook = TargetStubHook.create(target, oldParent), this.hooks.push(hook), new RpcStub(hook); } case "rpc-thenable": { let target = value, promise; return owner ? promise = new RpcPromise(owner.getHookForRpcTarget(target, oldParent, dupStubs), []) : promise = new RpcPromise(TargetStubHook.create(target, oldParent), []), this.promises.push({ parent, property, promise }), promise; } case "writable": { let stream = value, hook; return owner ? hook = owner.getHookForWritableStream(stream, oldParent, dupStubs) : hook = streamImpl.createWritableStreamHook(stream), this.hooks.push(hook), stream; } case "readable": { let stream = value, hook; return owner ? hook = owner.getHookForReadableStream(stream, oldParent, dupStubs) : hook = streamImpl.createReadableStreamHook(stream), this.hooks.push(hook), stream; } case "headers": return new Headers(value); case "request": { let req = value; return req.body && this.deepCopy(req.body, req, "body", req, dupStubs, owner), new Request(req); } case "response": { let resp = value; return resp.body && this.deepCopy(resp.body, resp, "body", resp, dupStubs, owner), new Response(resp.body, resp); } default: throw new Error("unreachable"); } } // Ensures that if the value originally came from an unowned source, we have replaced it with a // deep copy. ensureDeepCopied() { if (this.source !== "owned") { let dupStubs = this.source === "params"; this.hooks = [], this.promises = []; try { this.value = this.deepCopy(this.value, void 0, "value", this, dupStubs, this); } catch (err) { throw this.hooks = void 0, this.promises = void 0, err; } if (this.source = "owned", this.rpcTargets && this.rpcTargets.size > 0) throw new Error("Not all rpcTargets were accounted for in deep-copy?"); this.rpcTargets = void 0; } } // Resolve all promises in this payload and then assign the final value into `parent[property]`. deliverTo(parent, property, promises) { if (this.ensureDeepCopied(), this.value instanceof RpcPromise) _RpcPayload.deliverRpcPromiseTo(this.value, parent, property, promises); else { parent[property] = this.value; for (let record of this.promises) _RpcPayload.deliverRpcPromiseTo(record.promise, record.parent, record.property, promises); } } static deliverRpcPromiseTo(promise, parent, property, promises) { let hook = unwrapStubNoProperties(promise); if (!hook) throw new Error("property promises should have been resolved earlier"); let inner = hook.pull(); inner instanceof _RpcPayload ? inner.deliverTo(parent, property, promises) : promises.push(inner.then((payload) => { let subPromises = []; if (payload.deliverTo(parent, property, subPromises), subPromises.length > 0) return Promise.all(subPromises); })); } // Call the given function with the payload as an argument. The call is made synchronously if // possible, in order to maintain e-order. However, if any RpcPromises exist in the payload, // they are awaited and substituted before calling the function. The result of the call is // wrapped into another payload. // // The payload is automatically disposed after the call completes. The caller should not call // dispose(). async deliverCall(func, thisArg) { try { let promises = []; this.deliverTo(this, "value", promises), promises.length > 0 && await Promise.all(promises); let result = Function.prototype.apply.call(func, thisArg, this.value); return result instanceof RpcPromise ? _RpcPayload.fromAppReturn(result) : _RpcPayload.fromAppReturn(await result); } finally { this.dispose(); } } // Produce a promise for this payload for return to the application. Any RpcPromises in the // payload are awaited and substituted with their results first. // // The returned object will have a disposer which disposes the payload. The caller should not // separately dispose it. async deliverResolve() { try { let promises = []; this.deliverTo(this, "value", promises), promises.length > 0 && await Promise.all(promises); let result = this.value; return result instanceof Object && (Symbol.dispose in result || Object.defineProperty(result, Symbol.dispose, { // NOTE: Using `this.dispose.bind(this)` here causes Playwright's build of // Chromium 140.0.7339.16 to fail when the object is assigned to a `using` variable, // with the error: // TypeError: Symbol(Symbol.dispose) is not a function // I cannot reproduce this problem in Chrome 140.0.7339.127 nor in Node or workerd, // so maybe it was a short-lived V8 bug or something. To be safe, though, we use // `() => this.dispose()`, which seems to always work. value: () => this.dispose(), writable: !0, enumerable: !1, configurable: !0 })), result; } catch (err) { throw this.dispose(), err; } } dispose() { if (this.source === "owned") this.hooks.forEach((hook) => hook.dispose()), this.promises.forEach((promise) => promise.promise[Symbol.dispose]()); else if (this.source === "return" && (this.disposeImpl(this.value, void 0), this.rpcTargets && this.rpcTargets.size > 0)) throw new Error("Not all rpcTargets were accounted for in disposeImpl()?"); this.source = "owned", this.hooks = [], this.promises = []; } // Recursive dispose, called only when `source` is "return". disposeImpl(value, parent) { switch (typeForRpc(value)) { case "unsupported": case "primitive": case "bigint": case "bytes": case "date": case "error": case "undefined": return; case "array": { let array = value, len = array.length; for (let i = 0; i < len; i++) this.disposeImpl(array[i], array); return; } case "object": { let object = value; for (let i in object) this.disposeImpl(object[i], object); return; } case "stub": case "rpc-promise": { let hook = unwrapStubNoProperties(value); hook && hook.dispose(); return; } case "function": case "rpc-target": { let target = value, hook = this.rpcTargets?.get(target); hook ? (hook.dispose(), this.rpcTargets.delete(target)) : disposeRpcTarget(target); return; } case "rpc-thenable": return; case "headers": return; case "request": { let req = value; req.body && this.disposeImpl(req.body, req); return; } case "response": { let resp = value; resp.body && this.disposeImpl(resp.body, resp); return; } case "writable": { let stream = value, hook = this.rpcTargets?.get(stream); hook ? this.rpcTargets.delete(stream) : hook = streamImpl.createWritableStreamHook(stream), hook.dispose(); return; } case "readable": { let stream = value, hook = this.rpcTargets?.get(stream); hook ? this.rpcTargets.delete(stream) : hook = streamImpl.createReadableStreamHook(stream), hook.dispose(); return; } default: return; } } // Ignore unhandled rejections in all promises in this payload -- that is, all promises that // *would* be awaited if this payload were to be delivered. See the similarly-named method of // StubHook for explanation. ignoreUnhandledRejections() { this.hooks ? (this.hooks.forEach((hook) => { hook.ignoreUnhandledRejections(); }), this.promises.forEach( (promise) => unwrapStubOrParent(promise.promise).ignoreUnhandledRejections() )) : this.ignoreUnhandledRejectionsImpl(this.value); } ignoreUnhandledRejectionsImpl(value) { switch (typeForRpc(value)) { case "unsupported": case "primitive": case "bigint": case "bytes": case "date": case "error": case "undefined": case "function": case "rpc-target": case "writable": case "readable": case "headers": case "request": case "response": return; case "array": { let array = value, len = array.length; for (let i = 0; i < len; i++) this.ignoreUnhandledRejectionsImpl(array[i]); return; } case "object": { let object = value; for (let i in object) this.ignoreUnhandledRejectionsImpl(object[i]); return; } case "stub": case "rpc-promise": unwrapStubOrParent(value).ignoreUnhandledRejections(); return; case "rpc-thenable": value.then((_) => { }, (_) => { }); return; default: return; } } }; function followPath(value, parent, path, owner) { for (let i = 0; i < path.length; i++) { parent = value; let part = path[i]; if (part in Object.prototype) { value = void 0; continue; } switch (typeForRpc(value)) { case "object": case "function": Object.hasOwn(value, part) ? value = value[part] : value = void 0; break; case "array": Number.isInteger(part) && part >= 0 ? value = value[part] : value = void 0; break; case "rpc-target": case "rpc-thenable": { if (Object.hasOwn(value, part)) throw new TypeError( `Attempted to access property '${part}', which is an instance property of the RpcTarget. To avoid leaking private internals, instance properties cannot be accessed over RPC. If you want to make this property available over RPC, define it as a method or getter on the class, instead of an instance property.` ); value = value[part], owner = null; break; } case "stub": case "rpc-promise": { let { hook, pathIfPromise } = unwrapStubAndPath(value); return { hook, remainingPath: pathIfPromise ? pathIfPromise.concat(path.slice(i)) : path.slice(i) }; } case "writable": value = void 0; break; case "readable": value = void 0; break; case "primitive": case "bigint": case "bytes": case "date": case "error": case "headers": case "request": case "response": value = void 0; break; case "undefined": value = value[part]; break; case "unsupported": { if (i === 0) throw new TypeError("RPC stub points at a non-serializable type."); { let prefix = path.slice(0, i).join("."), remainder = path.slice(0, i).join("."); throw new TypeError( `'${prefix}' is not a serializable type, so property ${remainder} cannot be accessed.` ); } } default: throw new TypeError("unreachable"); } } if (value instanceof RpcPromise) { let { hook, pathIfPromise } = unwrapStubAndPath(value); return { hook, remainingPath: pathIfPromise || [] }; } return { value, parent, owner }; } var ValueStubHook = class extends StubHook { call(path, args) { try { let { value, owner } = this.getValue(), followResult = followPath(value, void 0, path, owner); if (followResult.hook) return followResult.hook.call(followResult.remainingPath, args); if (typeof followResult.value != "function") throw new TypeError(`'${path.join(".")}' is not a function.`); let promise = args.deliverCall(followResult.value, followResult.parent); return new PromiseStubHook(promise.then((payload) => new PayloadStubHook(payload))); } catch (err) { return new ErrorStubHook(err); } } map(path, captures, instructions) { try { let followResult; try { let { value, owner } = this.getValue(); followResult = followPath(value, void 0, path, owner); } catch (err) { for (let cap of captures) cap.dispose(); throw err; } return followResult.hook ? followResult.hook.map(followResult.remainingPath, captures, instructions) : mapImpl.applyMap( followResult.value, followResult.parent, followResult.owner, captures, instructions ); } catch (err) { return new ErrorStubHook(err); } } get(path) { try { let { value, owner } = this.getValue(); if (path.length === 0 && owner === null) throw new Error("Can't dup an RpcTarget stub as a promise."); let followResult = followPath(value, void 0, path, owner); return followResult.hook ? followResult.hook.get(followResult.remainingPath) : new PayloadStubHook(RpcPayload.deepCopyFrom( followResult.value, followResult.parent, followResult.owner )); } catch (err) { return new ErrorStubHook(err); } } }, PayloadStubHook = class _PayloadStubHook extends ValueStubHook { constructor(payload) { super(), this.payload = payload; } payload; // cleared when disposed getPayload() { if (this.payload) return this.payload; throw new Error("Attempted to use an RPC StubHook after it was disposed."); } getValue() { let payload = this.getPayload(); return { value: payload.value, owner: payload }; } dup() { let thisPayload = this.getPayload(); return new _PayloadStubHook(RpcPayload.deepCopyFrom( thisPayload.value, void 0, thisPayload )); } pull() { return this.getPayload(); } ignoreUnhandledRejections() { this.payload && this.payload.ignoreUnhandledRejections(); } dispose() { this.payload && (this.payload.dispose(), this.payload = void 0); } onBroken(callback) { this.payload && this.payload.value instanceof RpcStub && this.payload.value.onRpcBroken(callback); } }; function disposeRpcTarget(target) { if (Symbol.dispose in target) try { target[Symbol.dispose](); } catch (err) { Promise.reject(err); } } var TargetStubHook = class _TargetStubHook extends ValueStubHook { // Constructs a TargetStubHook that is not duplicated from an existing hook. // // If `value` is a function, `parent` is bound as its "this". static create(value, parent) { return typeof value != "function" && (parent = void 0), new _TargetStubHook(value, parent); } constructor(target, parent, dupFrom) { super(), this.target = target, this.parent = parent, dupFrom ? dupFrom.refcount && (this.refcount = dupFrom.refcount, ++this.refcount.count) : Symbol.dispose in target && (this.refcount = { count: 1 }); } target; // cleared when disposed parent; // `this` parameter when calling `target` refcount; // undefined if not needed (because target has no disposer) getTarget() { if (this.target) return this.target; throw new Error("Attempted to use an RPC StubHook after it was disposed."); } getValue() { return { value: this.getTarget(), owner: null }; } dup() { return new _TargetStubHook(this.getTarget(), this.parent, this); } pull() { let target = this.getTarget(); return "then" in target ? Promise.resolve(target).then((resolution) => RpcPayload.fromAppReturn(resolution)) : Promise.reject(new Error("Tried to resolve a non-promise stub.")); } ignoreUnhandledRejections() { } dispose() { this.target && (this.refcount && --this.refcount.count == 0 && disposeRpcTarget(this.target), this.target = void 0); } onBroken(callback) { } }, PromiseStubHook = class _PromiseStubHook extends StubHook { promise; resolution; constructor(promise) { super(), this.promise = promise.then((res) => (this.resolution = res, res)); } call(path, args) { return args.ensureDeepCopied(), new _PromiseStubHook(this.promise.then((hook) => hook.call(path, args))); } stream(path, args) { return args.ensureDeepCopied(), { promise: this.promise.then((hook) => hook.stream(path, args).promise) }; } map(path, captures, instructions) { return new _PromiseStubHook(this.promise.then( (hook) => hook.map(path, captures, instructions), (err) => { for (let cap of captures) cap.dispose(); throw err; } )); } get(path) { return new _PromiseStubHook(this.promise.then((hook) => hook.get(path))); } dup() { return this.resolution ? this.resolution.dup() : new _PromiseStubHook(this.promise.then((hook) => hook.dup())); } pull() { return this.resolution ? this.resolution.pull() : this.promise.then((hook) => hook.pull()); } ignoreUnhandledRejections() { this.resolution ? this.resolution.ignoreUnhandledRejections() : this.promise.then((res) => { res.ignoreUnhandledRejections(); }, (err) => { }); } dispose() { this.resolution ? this.resolution.dispose() : this.promise.then((hook) => { hook.dispose(); }, (err) => { }); } onBroken(callback) { this.resolution ? this.resolution.onBroken(callback) : this.promise.then((hook) => { hook.onBroken(callback); }, callback); } }, NullExporter = class { exportStub(stub) { throw new Error("Cannot serialize RPC stubs without an RPC session."); } exportPromise(stub) { throw new Error("Cannot serialize RPC stubs without an RPC session."); } getImport(hook) { } unexport(ids) { } createPipe(readable) { throw new Error("Cannot create pipes without an RPC session."); } onSendError(error) { } }, NULL_EXPORTER = new NullExporter(), ERROR_TYPES = { Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, AggregateError // TODO: DOMError? Others? }, Devaluator = class _Devaluator { constructor(exporter, source) { this.exporter = exporter, this.source = source; } // Devaluate the given value. // * value: The value to devaluate. // * parent: The value's parent object, which would be used as `this` if the value were called // as a function. // * exporter: Callbacks to the RPC session for exporting capabilities found in this message. // * source: The RpcPayload which contains the value, and therefore owns stubs within. // // Returns: The devaluated value, ready to be JSON-serialized. static devaluate(value, parent, exporter = NULL_EXPORTER, source) { let devaluator = new _Devaluator(exporter, source); try { return devaluator.devaluateImpl(value, parent, 0); } catch (err) { if (devaluator.exports) try { exporter.unexport(devaluator.exports); } catch { } throw err; } } exports; devaluateImpl(value, parent, depth) { if (depth >= 64) throw new Error( "Serialization exceeded maximum allowed depth. (Does the message contain cycles?)" ); switch (typeForRpc(value)) { case "unsupported": { let msg; try { msg = `Cannot serialize value: ${value}`; } catch { msg = "Cannot serialize value: (couldn't stringify value)"; } throw new TypeError(msg); } case "primitive": return typeof value == "number" && !isFinite(value) ? value === 1 / 0 ? ["inf"] : value === -1 / 0 ? ["-inf"] : ["nan"] : value; case "object": { let object = value, result = {}; for (let key in object) result[key] = this.devaluateImpl(object[key], object, depth + 1); return result; } case "array": { let array = value, len = array.length, result = new Array(len); for (let i = 0; i < len; i++) result[i] = this.devaluateImpl(array[i], array, depth + 1); return [result]; } case "bigint": return ["bigint", value.toString()]; case "date": return ["date", value.getTime()]; case "bytes": { let bytes = value; return bytes.toBase64 ? ["bytes", bytes.toBase64({ omitPadding: !0 })] : [ "bytes", btoa(String.fromCharCode.apply(null, bytes).replace(/=*$/, "")) ]; } case "headers": return ["headers", [...value]]; case "request": { let req = value, init = {}; req.method !== "GET" && (init.method = req.method); let headers = [...req.headers]; if (headers.length > 0 && (init.headers = headers), req.body) init.body = this.devaluateImpl(req.body, req, depth + 1), init.duplex = req.duplex || "half"; else if (req.body === void 0 && !["GET", "HEAD", "OPTIONS", "TRACE", "DELETE"].includes(req.method)) { let bodyPromise = req.arrayBuffer(), readable = new ReadableStream({ async start(controller) { try { controller.enqueue(new Uint8Array(await bodyPromise)), controller.close(); } catch (err) { controller.error(err); } } }), hook = streamImpl.createReadableStreamHook(readable), importId = this.exporter.createPipe(readable, hook); init.body = ["readable", importId], init.duplex = req.duplex || "half"; } req.cache && req.cache !== "default" && (init.cache = req.cache), req.redirect !== "follow" && (init.redirect = req.redirect), req.integrity && (init.integrity = req.integrity), req.mode && req.mode !== "cors" && (init.mode = req.mode), req.credentials && req.credentials !== "same-origin" && (init.credentials = req.credentials), req.referrer && req.referrer !== "about:client" && (init.referrer = req.referrer), req.referrerPolicy && (init.referrerPolicy = req.referrerPolicy), req.keepalive && (init.keepalive = req.keepalive); let cfReq = req; return cfReq.cf && (init.cf = cfReq.cf), cfReq.encodeResponseBody && cfReq.encodeResponseBody !== "automatic" && (init.encodeResponseBody = cfReq.encodeResponseBody), ["request", req.url, init]; } case "response": { let resp = value, body = this.devaluateImpl(resp.body, resp, depth + 1), init = {}; resp.status !== 200 && (init.status = resp.status), resp.statusText && (init.statusText = resp.statusText); let headers = [...resp.headers]; headers.length > 0 && (init.headers = headers); let cfResp = resp; if (cfResp.cf && (init.cf = cfResp.cf), cfResp.encodeBody && cfResp.encodeBody !== "automatic" && (init.encodeBody = cfResp.encodeBody), cfResp.webSocket) throw new TypeError("Can't serialize a Response containing a webSocket."); return ["response", body, init]; } case "error": { let e = value, rewritten = this.exporter.onSendError(e); rewritten && (e = rewritten); let result = ["error", e.name, e.message]; return rewritten && rewritten.stack && result.push(rewritten.stack), result; } case "undefined": return ["undefined"]; case "stub": case "rpc-promise": { if (!this.source) throw new Error("Can't serialize RPC stubs in this context."); let { hook, pathIfPromise } = unwrapStubAndPath(value), importId = this.exporter.getImport(hook); return importId !== void 0 ? pathIfPromise ? pathIfPromise.length > 0 ? ["pipeline", importId, pathIfPromise] : ["pipeline", importId] : ["import", importId] : (pathIfPromise ? hook = hook.get(pathIfPromise) : hook = hook.dup(), this.devaluateHook(pathIfPromise ? "promise" : "export", hook)); } case "function": case "rpc-target": { if (!this.source) throw new Error("Can't serialize RPC stubs in this context."); let hook = this.source.getHookForRpcTarget(value, parent); return this.devaluateHook("export", hook); } case "rpc-thenable": { if (!this.source) throw new Error("Can't serialize RPC stubs in this context."); let hook = this.source.getHookForRpcTarget(value, parent); return this.devaluateHook("promise", hook); } case "writable": { if (!this.source) throw new Error("Can't serialize WritableStream in this context."); let hook = this.source.getHookForWritableStream(value, parent); return this.devaluateHook("writable", hook); } case "readable": { if (!this.source) throw new Error("Can't serialize ReadableStream in this context."); let ws = value, hook = this.source.getHookForReadableStream(ws, parent); return ["readable", this.exporter.createPipe(ws, hook)]; } default: throw new Error("unreachable"); } } devaluateHook(type, hook) { this.exports || (this.exports = []); let exportId = type === "promise" ? this.exporter.exportPromise(hook) : this.exporter.exportStub(hook); return this.exports.push(exportId), [type, exportId]; } }; var NullImporter = class { importStub(idx) { throw new Error("Cannot deserialize RPC stubs without an RPC session."); } importPromise(idx) { throw new Error("Cannot deserialize RPC stubs without an RPC session."); } getExport(idx) { } getPipeReadable(exportId) { throw new Error("Cannot retrieve pipe readable without an RPC session."); } }, NULL_IMPORTER = new NullImporter(); function fixBrokenRequestBody(request, body) { let promise = new Response(body).arrayBuffer().then((arrayBuffer) => { let bytes = new Uint8Array(arrayBuffer), result = new Request(request, { body: bytes }); return new PayloadStubHook(RpcPayload.fromAppReturn(result)); }); return new RpcPromise(new PromiseStubHook(promise), []); } var Evaluator = class _Evaluator { constructor(importer) { this.importer = importer; } hooks = []; promises = []; evaluate(value) { let payload = RpcPayload.forEvaluate(this.hooks, this.promises); try { return payload.value = this.evaluateImpl(value, payload, "value"), payload; } catch (err) { throw payload.dispose(), err; } } // Evaluate the value without destroying it. evaluateCopy(value) { return this.evaluate(structuredClone(value)); } evaluateImpl(value, parent, property) { if (value instanceof Array) { if (value.length == 1 && value[0] instanceof Array) { let result = value[0]; for (let i = 0; i < result.length; i++) result[i] = this.evaluateImpl(result[i], result, i); return result; } else switch (value[0]) { case "bigint": if (typeof value[1] == "string") return BigInt(value[1]); break; case "date": if (typeof value[1] == "number") return new Date(value[1]); break; case "bytes": { let b64 = Uint8Array; if (typeof value[1] == "string") { if (b64.fromBase64) return b64.fromBase64(value[1]); { let bs = atob(value[1]), len = bs.length, bytes = new Uint8Array(len); for (let i = 0; i < len; i++) bytes[i] = bs.charCodeAt(i); return bytes; } } break; } case "error": if (value.length >= 3 && typeof value[1] == "string" && typeof value[2] == "string") { let cls = ERROR_TYPES[value[1]] || Error, result = new cls(value[2]); return typeof value[3] == "string" && (result.stack = value[3]), result; } break; case "undefined": if (value.length === 1) return; break; case "inf": return 1 / 0; case "-inf": return -1 / 0; case "nan": return NaN; case "headers": if (value.length === 2 && value[1] instanceof Array) return new Headers(value[1]); break; case "request": { if (value.length !== 3 || typeof value[1] != "string") break; let url = value[1], init = value[2]; if (typeof init != "object" || init === null) break; if (init.body && (init.body = this.evaluateImpl(init.body, init, "body"), !(init.body === null || typeof init.body == "string" || init.body instanceof Uint8Array || init.body instanceof ReadableStream))) throw new TypeError("Request body must be of type ReadableStream."); if (init.signal && (init.signal = this.evaluateImpl(init.signal, init, "signal"), !(init.signal instanceof AbortSignal))) throw new TypeError("Request siganl must be of type AbortSignal."); if (init.headers && !(init.headers instanceof Array)) throw new TypeError("Request headers must be serialized as an array of pairs."); let result = new Request(url, init); if (init.body instanceof ReadableStream && result.body === void 0) { let promise = fixBrokenRequestBody(result, init.body); return this.promises.push({ promise, parent, property }), promise; } else return result; } case "response": { if (value.length !== 3) break; let body = this.evaluateImpl(value[1], parent, property); if (!(body === null || typeof body == "string" || body instanceof Uint8Array || body instanceof ReadableStream)) throw new TypeError("Response body must be of type ReadableStream."); let init = value[2]; if (typeof init != "object" || init === null) break; if (init.webSocket) throw new TypeError("Can't deserialize a Response containing a webSocket."); if (init.headers && !(init.headers instanceof Array)) throw new TypeError("Request headers must be serialized as an array of pairs."); return new Response(body, init); } case "import": case "pipeline": { if (value.length < 2 || value.length > 4 || typeof value[1] != "number") break; let hook = this.importer.getExport(value[1]); if (!hook) throw new Error(`no such entry on exports table: ${value[1]}`); let isPromise = value[0] == "pipeline", addStub = (hook2) => { if (isPromise) { let promise = new RpcPromise(hook2, []); return this.promises.push({ promise, parent, property }), promise; } else return this.hooks.push(hook2), new RpcPromise(hook2, []); }; if (value.length == 2) return addStub(isPromise ? hook.get([]) : hook.dup()); let path = value[2]; if (!(path instanceof Array) || !path.every( (part) => typeof part == "string" || typeof part == "number" )) break; if (value.length == 3) return addStub(hook.get(path)); let args = value[3]; if (!(args instanceof Array)) break; return args = new _Evaluator(this.importer).evaluate([args]), addStub(hook.call(path, args)); } case "remap": { if (value.length !== 5 || typeof value[1] != "number" || !(value[2] instanceof Array) || !(value[3] instanceof Array) || !(value[4] instanceof Array)) break; let hook = this.importer.getExport(value[1]); if (!hook) throw new Error(`no such entry on exports table: ${value[1]}`); let path = value[2]; if (!path.every( (part) => typeof part == "string" || typeof part == "number" )) break; let captures = value[3].map((cap) => { if (!(cap instanceof Array) || cap.length !== 2 || cap[0] !== "import" && cap[0] !== "export" || typeof cap[1] != "number") throw new TypeError(`unknown map capture: ${JSON.stringify(cap)}`); if (cap[0] === "export") return this.importer.importStub(cap[1]); { let exp = this.importer.getExport(cap[1]); if (!exp) throw new Error(`no such entry on exports table: ${cap[1]}`); return exp.dup(); } }), instructions = value[4], resultHook = hook.map(path, captures, instructions), promise = new RpcPromise(resultHook, []); return this.promises.push({ promise, parent, property }), promise; } case "export": case "promise": if (typeof value[1] == "number") if (value[0] == "promise") { let hook = this.importer.importPromise(value[1]), promise = new RpcPromise(hook, []); return this.promises.push({ parent, property, promise }), promise; } else { let hook = this.importer.importStub(value[1]); return this.hooks.push(hook), new RpcStub(hook); } break; case "writable": if (typeof value[1] == "number") { let hook = this.importer.importStub(value[1]), stream = streamImpl.createWritableStreamFromHook(hook); return this.hooks.push(hook), stream; } break; case "readable": if (typeof value[1] == "number") { let stream = this.importer.getPipeReadable(value[1]), hook = streamImpl.createReadableStreamHook(stream); return this.hooks.push(hook), stream; } break; } throw new