UNPKG

thorish

Version:

This is a library of useful JS concepts and data structures for Node and the browser. It it, unashamedly, a dumping ground for code needed by [@samthor](https://twitter.com/samthor)'s projects.

2,101 lines (2,082 loc) 107 kB
// src/aatree.ts var AANode = class { constructor(data) { this.data = data; } level = 1; left = null; right = null; }; var AATree = class _AATree { constructor(compare) { this.compare = compare; } root = null; _count = 0; _change = false; /** * Clones this {@link AATree} using {@link structuredClone}. */ clone() { const t = new _AATree(this.compare); t.root = structuredClone(this.root); t._count = this._count; return t; } clear() { this.root = null; this._count = 0; } /** * The count of items in this tree. */ count() { return this._count; } /** * Query for this exact node. */ query(data) { let node = this.root; while (node !== null) { const c = this.compare(data, node.data); if (c < 0) { node = node.left; } else if (c > 0) { node = node.right; } else { return node.data; } } return void 0; } /** * Finds the target node or the node closest before the query. */ equalBefore(data) { return this._equalBefore(this.root, data); } /** * Finds the node immediately before the query. */ before(data) { let best; let node = this.root; while (node !== null) { const c = this.compare(data, node.data); if (c > 0) { best = node.data; node = node.right; } else { node = node.left; } } return best; } /** * Finds the target node or the node closest after the query. */ equalAfter(data) { return this._equalAfter(this.root, data); } /** * Finds the node immediately after the query. */ after(data) { let best; let node = this.root; while (node !== null) { const c = this.compare(data, node.data); if (c < 0) { best = node.data; node = node.left; } else { node = node.right; } } return best; } /** * Inserts the value. Updates the previous value if compare is zero. * * @return if there was a change */ insert(data) { this._change = false; this.root = this._insert(this.root, data); return this._change; } /** * Removes the value. * * @return if there was a change */ remove(data) { this._change = false; this.root = this._remove(this.root, data); return this._change; } skew(node) { if (node.left?.level !== node.level) { return node; } const leftNode = node.left; node.left = leftNode.right; leftNode.right = node; return leftNode; } split(node) { if (node.right?.right?.level !== node.level) { return node; } const rightNode = node.right; node.right = rightNode.left; rightNode.left = node; ++rightNode.level; return rightNode; } _equalBefore(node, data) { while (node !== null) { const c = this.compare(data, node.data); if (c < 0) { node = node.left; continue; } if (c === 0) { return node.data; } const within = this._equalBefore(node.right, data); return within === void 0 ? node.data : within; } } _equalAfter(node, data) { while (node !== null) { const c = this.compare(data, node.data); if (c > 0) { node = node.right; continue; } if (c === 0) { return node.data; } const within = this._equalAfter(node.left, data); return within === void 0 ? node.data : within; } } _insert(node, data) { if (node === null) { ++this._count; this._change = true; return new AANode(data); } const c = this.compare(data, node.data); if (c < 0) { node.left = this._insert(node.left, data); } else if (c > 0) { node.right = this._insert(node.right, data); } else { if (node.data !== data) { this._change = true; node.data = data; } return node; } node = this.skew(node); node = this.split(node); return node; } _remove(node, data) { if (node === null) { return null; } const c = this.compare(data, node.data); if (c < 0) { node.left = this._remove(node.left, data); } else if (c > 0) { node.right = this._remove(node.right, data); } else { --this._count; this._change = true; if (node.left === null && node.right === null) { return null; } else if (node.left === null) { return node.right; } else if (node.right === null) { return node.left; } else { const successor = this._findMin(node.right); node.data = successor.data; node.right = this._remove(node.right, successor.data); } } const newLevel = Math.min(node.left?.level ?? 0, node.right?.level ?? 0) + 1; if (newLevel < node.level) { node.level = newLevel; if (node.right && newLevel < node.right.level) { node.right.level = newLevel; } } node = this.skew(node); node = this.split(node); if (node.right) { node.right = this.skew(node.right); node.right = this.split(node.right); if (node.right?.right) { node.right.right = this.split(node.right.right); } } return node; } _findMin(node) { while (node.left) { node = node.left; } return node; } }; // src/array.ts function findAllIndex(arr, predicate) { const out = []; let index = 0; for (const check of arr) { if (predicate(check)) { out.push(index); } ++index; } return out; } function arrayContainsSub(arr, sub) { return findSubArray(arr, sub) !== -1; } function findSubArray(arr, sub) { outer: for (let i = 0; i <= arr.length - sub.length; ++i) { for (let j = 0; j < sub.length; ++j) { if (arr[i + j] !== sub[j]) { continue outer; } } return i; } return -1; } function arraySwapRemoveAt(arr, at) { if (at < 0) { at = arr.length + at; } if (at < 0 || at >= arr.length) { return void 0; } const last = arr.pop(); if (arr.length === at) { return last; } const out = arr[at]; arr[at] = last; return out; } function arraySwapInsertAt(arr, at, value) { if (at < 0) { at = arr.length + at; } if (at >= arr.length) { arr.push(value); } else { at = Math.max(at, 0); const prev = arr[at]; arr[at] = value; arr.push(prev); } } // src/bound-proxy.ts function neverFunctionForProxy() { throw new Error(`should not get here`); } function buildBoundProxy(mod) { const fakeModule = {}; const functionRef = {}; for (const key of Reflect.ownKeys(mod)) { if (typeof mod[key] !== "function") { fakeModule[key] = mod[key]; continue; } functionRef[key] = mod[key]; const currentValueReflect = new Proxy(Reflect, { get(_reflect, reflectKey) { return (...args) => Reflect[reflectKey](functionRef[key], ...args.slice(1)); } }); fakeModule[key] = new Proxy(neverFunctionForProxy, currentValueReflect); } const setValue = (property, value) => { if (property in functionRef) { if (fakeModule[property] === value) { return true; } else if (typeof value !== "function") { throw new Error(`can't redefine function => primitive for ${String(property)}`); } functionRef[property] = value; return true; } else if (property in fakeModule) { fakeModule[property] = value; return true; } return false; }; return new Proxy( {}, { ownKeys(_target) { return Reflect.ownKeys(fakeModule); }, set(_target, property, value) { return setValue(property, value); }, get(_target, property) { return fakeModule[property]; }, getOwnPropertyDescriptor(_target, property) { const target = property in functionRef ? functionRef : fakeModule; return Reflect.getOwnPropertyDescriptor(target, property); }, defineProperty(_target, property, attributes) { if (attributes.get || attributes.set) { throw new Error(`can't defineProperty with get/set on fake module`); } else if (!("value" in attributes)) { return false; } return setValue(property, attributes.value); } } ); } // src/cache.ts var SimpleCache = class { constructor(gen) { this.gen = gen; } m = /* @__PURE__ */ new Map(); /** * Copies this as a regular {@link Map}. */ copy() { return new Map(this.m); } has(k) { return this.m.has(k); } get(k) { const prev = this.m.get(k); if (prev !== void 0) { return prev; } const value = this.gen(k); if (value !== void 0) { this.m.set(k, value); } return value; } get size() { return this.m.size; } clear() { this.m.clear(); } keys() { return this.m.keys(); } entries() { return this.m.entries(); } values() { return this.m.values(); } delete(k) { return this.m.delete(k); } }; function once(fn) { let run = false; let result; return () => { if (!run) { run = true; result = fn(); } return result; }; } function lazyWeak(fn) { const wm = /* @__PURE__ */ new WeakMap(); return (w) => { let value = wm.get(w); if (value === void 0) { value = fn(w); wm.set(w, value); } return value; }; } // src/check.ts async function checkCond(check, ms = defaultCheckCondTimeout) { const target = performance.now() + ms; for (; ; ) { const remain = target - performance.now(); if (remain <= 0) { return await check(); } const delay = Math.max(0, Math.pow(remain, 0.65)); await new Promise((r) => setTimeout(r, delay)); try { return await check(); } catch { } } } var defaultCheckCondTimeout = 2e3; // src/cgroup.ts var CGroup = class { shutdownCause = void 0; controller; active = 0; tasks = []; halts = []; resumeController; resumeStart; /** * Adds a signal to this group. * Returns `true` if the signal was not already aborted and was added to the active set. */ add(signal) { if (signal.aborted || this.controller?.signal.aborted) { return false; } ++this.active; signal.addEventListener("abort", () => { --this.active; if (this.active < 0) { throw new Error("cgroup active went -ve"); } else if (this.active > 0 || !this.controller) { return; } if (this.resumeController) { throw new Error("cgroup abort with resumeController"); } if (this.halts.length === 0) { this.controller.abort(signal.reason); return; } this.resumeController = new AbortController(); const resumeSignal = this.resumeController.signal; let haltActive = 0; this.resumeStart = async (halt) => { ++haltActive; try { await halt(this.controller.signal, resumeSignal); } catch (err) { this.shutdownCause ??= err; this.controller.abort(err); } --haltActive; if (haltActive === 0 && this.resumeController?.signal === resumeSignal) { this.controller.abort("CGroup halt"); } }; this.halts.forEach(this.resumeStart); }); if (this.controller && this.resumeController) { this.resumeController.abort("resume aborted"); this.resumeController = void 0; this.resumeStart = void 0; } return !this.controller?.signal.aborted; } /** * Starts the group and returns its {@link AbortSignal}. * If no valid signals were added, this will be immediately aborted. */ start() { if (!this.controller) { this.controller = new AbortController(); if (this.active === 0) { this.controller.abort("no AbortSignal added to CGroup"); } else { this.tasks.forEach((fn) => this.runTask(fn)); this.tasks = []; } } return this.controller.signal; } /** * Ensures the group has started, then waits until the group's signal is aborted. */ async wait() { const signal = this.start(); const buildPromise = () => { if (this.shutdownCause !== void 0) { return Promise.reject(this.shutdownCause); } else { return Promise.resolve(); } }; if (signal.aborted) { return buildPromise(); } return new Promise((resolve) => { signal.addEventListener("abort", () => resolve(buildPromise()), { once: true }); }); } /** * Runs the given function as part of this group. * It will only start after `start()` has been called. * Any returned error will cancel the group's signal. */ go(fn) { if (!this.controller) { this.tasks.push(fn); return true; } if (this.controller.signal.aborted) { return false; } this.runTask(fn); return true; } /** * Registers a function to run when the group is about to shut down. * It is passed the group's signal and a "resume" signal which is aborted if the group restarts. * Any returned error will cancel the group's signal. */ halt(fn) { if (this.controller?.signal.aborted) { return false; } this.resumeStart?.(fn); this.halts.push(fn); return true; } /** * Helper to execute a task and handle its potential error. */ async runTask(fn) { try { await fn(this.controller.signal); } catch (err) { this.shutdownCause ??= err; this.controller.abort(err); } } }; // src/cond.ts var Condition = class { listeners = /* @__PURE__ */ new Map(); signal; _state; constructor(defaultValue, options) { options?.signal?.addEventListener("abort", () => { this.listeners.clear(); }); this._state = defaultValue; this.signal = options?.signal; } get state() { return this._state; } set state(v) { if (this._state === v) { return; } this._state = v; for (const [fn, both] of this.listeners.entries()) { if (both || v) { fn(v); } } } /** * Does this {@link Condition} currently have any listeners. */ observed() { return this.listeners.size !== 0; } /** * For subclasses to override. The actions to take when the first listener is added. */ setup() { } /** * For subclasses to override. The actions to take when the last listener is removed, or this * {@link Condition} is aborted. */ teardown() { } /** * Adds a listener to this {@link Condition}. */ addListener(fn, options) { if (this.signal?.aborted || options?.signal?.aborted || this.listeners.has(fn)) { return false; } const first = this.listeners.size === 0; this.listeners.set(fn, options?.both ?? false); options?.signal?.addEventListener("abort", () => this.removeListener(fn)); if (first) { this.setup(); } return true; } /** * Removes a listener from this {@link Condition}. */ removeListener(fn) { if (this.listeners.size === 0) { return false; } if (!this.listeners.delete(fn)) { return false; } if (this.listeners.size === 0) { this.teardown(); } return true; } }; // src/limit.ts function buildLimiter(c) { const maxTokens = (c?.b ?? 100) * 1; const rateOfIncrease = (c?.r ?? 10) * 1; if (maxTokens <= 0 || rateOfIncrease <= 0) { throw new Error(`invalid LimitConfig, no requests allowed`); } let last = performance.now(); let tokens = maxTokens; return async (signal) => { for (; ; ) { signal.throwIfAborted(); const now = performance.now(); const delta = (now - last) / 1e3; const increase = delta * rateOfIncrease; tokens = Math.min(maxTokens, tokens + increase); last = now; const secondsToWait = (1 - tokens) / rateOfIncrease; if (secondsToWait <= 0) { tokens -= 1; return; } await new Promise((r) => { const timeout2 = setTimeout(r, secondsToWait * 1e3); signal.addEventListener("abort", () => { clearTimeout(timeout2); r(); }); }); } }; } var PERSISTENT_DELAY_EXP = 1.65; var PERSISTENT_DELAY_BASE = 230; var PERSISTENT_DELAY_MAX = 6e4; var PERSISTENT_DELAY_RAND = 0.1; function createBackoff(baseDelay) { baseDelay ||= 0; let extraDelay = 0; let nextDelay = 0; return { get delay() { return nextDelay; }, async timeout(signal) { if (signal?.aborted) { return; } return new Promise((r) => { setTimeout(r, nextDelay); signal?.addEventListener("abort", () => r(), { once: true }); }); }, success() { extraDelay = 0; nextDelay = 0; }, error() { extraDelay = (extraDelay + PERSISTENT_DELAY_BASE) * PERSISTENT_DELAY_EXP; extraDelay = Math.min(extraDelay, PERSISTENT_DELAY_MAX); const factor = 1 - (Math.random() * PERSISTENT_DELAY_RAND * 2 - PERSISTENT_DELAY_RAND); const delay = (baseDelay + extraDelay) * factor; nextDelay = Math.max(0, delay); } }; } // src/promise.ts var unresolvedPromise = /* @__PURE__ */ new Promise(() => { }); var resolvedPromise = /* @__PURE__ */ Promise.resolve(void 0); function wrapTrigger(trigger, ...moreArgs) { return new Promise((resolve) => { trigger(resolve, ...moreArgs); }); } function timeout(ms, signal) { if (signal?.aborted) { return Promise.resolve(); } return new Promise((resolve) => { const t = setTimeout(resolve, ms); signal?.addEventListener("abort", () => { clearTimeout(t); resolve(); }); }); } var promiseWithResolvers = /* @__PURE__ */ (() => Promise.withResolvers ? Promise.withResolvers.bind(Promise) : function localResolvable() { let resolve, reject; const promise = new Promise((localResolve, localReject) => { resolve = localResolve; reject = localReject; }); return { resolve, reject, promise }; })(); var resolvable = promiseWithResolvers; function promiseForEvent(target, eventName, options = {}) { if (options.signal?.aborted) { return Promise.reject(); } return new Promise((resolve, reject) => { options.signal?.addEventListener("abort", () => reject()); target.addEventListener(eventName, (e) => resolve(e), { ...options, once: true }); }); } async function spliceNextPromise(arr) { if (!arr.length) { return void 0; } const internal = arr.map((x, i) => x.then((ret) => ({ ret, i }))); const next = await Promise.race(internal); arr.splice(next.i, 1); return next.ret; } function buildCallTrain(fn) { let activePromise; return () => { if (!activePromise) { activePromise = fn().then((ret) => { activePromise = void 0; return ret; }); } return activePromise; }; } function rafRunner(callback, options) { return internalBuildRunner(requestAnimationFrame, callback, options); } function fastFrameRunner(callback, options) { return internalBuildRunner( (cb) => { requestAnimationFrame(cb); setTimeout(cb, 0); }, callback, options ); } function tickRunner(callback, options) { return internalBuildRunner((m) => Promise.resolve().then(m), callback, options); } function internalBuildRunner(runner2, callback, options) { const { immediate, signal } = options ?? {}; let activePromise; const o = () => { if (activePromise === void 0) { const pr = promiseWithResolvers(); runner2(() => { if (activePromise !== pr.promise) { return; } activePromise = void 0; if (signal?.aborted) { pr.reject(signal.reason); } else { pr.resolve(callback()); } }); pr.promise.catch(() => { }); activePromise = pr.promise; } return activePromise; }; immediate && o(); return o; } // src/signal.ts function afterSignal(signal, fn) { let shouldRun = true; if (signal.aborted) { Promise.resolve().then(() => { if (!shouldRun) { return; } shouldRun = false; fn(); }); return () => { try { return shouldRun; } finally { shouldRun = false; } }; } const wrap = () => { shouldRun = false; fn(); }; signal.addEventListener("abort", wrap); return () => { if (shouldRun) { signal.removeEventListener("abort", wrap); shouldRun = false; return true; } return false; }; } function promiseVoidForSignal(signal) { if (signal.aborted) { return Promise.resolve(); } return new Promise((resolve) => { signal.addEventListener("abort", () => resolve()); }); } function promiseForSignal(signal, resolveWith) { if (resolveWith === void 0 && arguments.length < 2) { if (signal.aborted) { return Promise.reject(signal.reason); } return new Promise((reject) => signal.addEventListener("abort", () => reject(signal.reason))); } if (signal.aborted) { return Promise.resolve(resolveWith); } return new Promise((resolve) => { signal.addEventListener("abort", () => resolve(resolveWith)); }); } var abortSignalAny = /* @__PURE__ */ (() => AbortSignal.any ? AbortSignal.any : (all) => { const previouslyAborted = all.find((x) => x.aborted); if (previouslyAborted !== void 0) { return previouslyAborted; } const c = new AbortController(); all.forEach((p) => p.addEventListener("abort", () => c.abort(p.reason))); return c.signal; })(); var buildTimeout = () => new DOMException("The operation was aborted due to timeout", "TimeoutError"); function abortSignalTimeout(timeout2) { const c = new AbortController(); const s = AbortSignal.timeout(timeout2); s.addEventListener("abort", () => { if (s.reason instanceof DOMException && s.reason.name === "TimeoutError") { c.abort(s.reason); } else { c.abort(buildTimeout()); } }); return c.signal; } function tickAbortSignal() { const c = new AbortController(); Promise.resolve().then(() => c.abort(buildTimeout())); return c.signal; } function derivedSignal(...raw) { const previous = raw.filter(Boolean); const c = new AbortController(); previous.push(c.signal); const signal = abortSignalAny(previous); const abort = (reason) => c.abort(reason ?? "aborted"); return { signal, abort }; } var abortedSignal = /* @__PURE__ */ (() => { const c = new AbortController(); c.abort(); return c.signal; })(); var neverAbortedSignal = /* @__PURE__ */ (() => new AbortController().signal)(); var todoSignal = neverAbortedSignal; // src/queue.ts var WorkQueue = class { pending = []; queue = []; releaseActive = false; releaseTask = Promise.resolve(); /** * The {@link WorkQueue} releases waiting tasks one-per-microtask. This maintains the invariant * that every time `wait` returns or resolves, there's at least one item in the queue: otherwise, * resolving everything at once might allow other waiters to steal all items. */ releasePending() { if (this.releaseActive) { return; } this.releaseTask = this.releaseTask.then(async () => { this.releaseActive = true; try { while (this.queue.length && this.pending.length) { const resolve = this.queue.shift(); resolve(); await new Promise((r) => queueMicrotask(r)); } } finally { this.releaseActive = false; } }); } /** * Iterates through the items in this queue _forever_, waiting for more items to appear. */ async *[Symbol.asyncIterator]() { for (; ; ) { await this.wait(); yield this.pending.shift(); } } asyncGenerator() { return this[Symbol.asyncIterator](); } /** * Iterates through the items in this queue, stopping when no more are available synchronously. */ *[Symbol.iterator]() { while (this.pending.length) { yield this.shift(); } } /** * Waits until there is something in the queue that your task can process. This does _not_ return * the item itself. This returns `undefined` if no waiting is required. */ wait() { if (this.pending.length === 0) { return new Promise((r) => { this.queue.push(r); }); } } /** * Takes a token for the next item from the front of the queue. The returned {@link Promise} will * always receive the next item, so don't throw it away. */ async next() { await this.wait(); return this.pending.shift(); } /** * Push items into the queue. Wakes up any pending requests. */ push(...items) { try { return this.pending.push(...items); } finally { if (items.length) { this.releasePending(); } } } pop() { return this.pending.pop(); } /** * Push items at the start of the queue. Wakes up any pending requests. */ unshift(...items) { try { return this.pending.unshift(...items); } finally { if (items.length) { this.releasePending(); } } } shift() { return this.pending.shift(); } get length() { return this.pending.length; } }; function listenerToAsyncGenerator(l) { const fn = async function* () { for (; ; ) { const value = await l.next(); if (value === void 0) { return; } yield value; } }; return fn(); } var emptyQueueRef = {}; function buildEmptyListener() { return new EmptyListenerImpl(); } var ListenerImpl = class { async batch() { const next = await this.next(); if (next === void 0) { return []; } const out = [next]; while (this.peek()) { const next2 = await this.next(); if (next2 === void 0) { break; } out.push(next2); } return out; } async last() { const out = await this.batch(); return out.at(-1); } }; var EmptyListenerImpl = class extends ListenerImpl { next() { return Promise.resolve(void 0); } peek() { return void 0; } }; var LinkQueueImpl = class { head = {}; p; push(...all) { if (!all.length) { return false; } for (const each of all) { const prev = this.head; this.head = {}; prev.value = each; prev.next = this.head; } if (!this.p) { return false; } const { resolve } = this.p; this.p = void 0; resolve(); return true; } join(signal) { let waitNext; let ref = this.head; if (!signal) { waitNext = () => this.p.promise; } else if (signal.aborted) { waitNext = () => Promise.resolve(); ref = emptyQueueRef; } else { const signalPromise = promiseVoidForSignal(signal); waitNext = () => Promise.race([signalPromise, this.p.promise]); } const outer = this; return new class extends ListenerImpl { peek() { return ref.value; } async next() { if (signal?.aborted) { return void 0; } let { value } = ref; if (value !== void 0) { ref = ref.next; return value; } outer.p ??= promiseWithResolvers(); await waitNext(); return this.next(); } async last() { let value = await this.next(); if (value === void 0) { return void 0; } while (ref.value !== void 0) { value = ref.value; ref = ref.next; } return value; } }(); } }; var ArrayQueueImpl = class { head = 0; data = []; subs = /* @__PURE__ */ new Map(); p; trimTask = false; push(...all) { if (all.length === 0) { return false; } this.head += all.length; if (this.subs.size === 0) { if (this.data.length) { this.data = []; } return false; } this.data.push(...all); if (!this.p) { return false; } const { resolve } = this.p; this.p = void 0; resolve(); return true; } join(signal) { const outer = this; const signalPromise = promiseVoidForSignal(signal); const waitFor = async (cb) => { for (; ; ) { const last = outer.subs.get(l); if (last === void 0) { return []; } else if (last < outer.head) { const start = outer.head - outer.data.length; const skip = last - start; const avail = outer.data.length - skip; const toReturn = cb(avail); const out = outer.data.slice(skip, skip + toReturn); outer.subs.set(l, last + out.length); outer.queueTrimEvents(); return out; } outer.p ??= promiseWithResolvers(); await Promise.race([signalPromise, outer.p.promise]); } }; const l = new class extends ListenerImpl { peek() { const last = outer.subs.get(l); if (last === void 0 || last >= outer.head) { return void 0; } const start = outer.head - outer.data.length; const skip = last - start; return outer.data[skip]; } async next() { const out = await waitFor(() => 1); return out[0]; } async batch() { return waitFor((avail) => avail); } }(); if (!signal.aborted) { this.subs.set(l, this.head); signal.addEventListener("abort", () => { this.subs.delete(l); this.queueTrimEvents(); }); } return l; } queueTrimEvents() { if (this.trimTask) { return; } this.trimTask = true; const timeoutMs = 250; setTimeout(() => { this.trimTask = false; const min = Math.min(this.head, ...this.subs.values()); if (min === this.head) { if (this.data.length) { this.data = []; } return; } const start = this.head - this.data.length; const strip = min - start; this.data.splice(0, strip); }, timeoutMs); } }; function buildLinkQueue() { return new LinkQueueImpl(); } function buildArrayQueue() { return new ArrayQueueImpl(); } // src/socket.ts function socketQueueSend(socket, transformMessage) { let p = Promise.resolve(socket); transformMessage ??= (m) => m; let resolvedSocket; const attemptSend = async (raw) => { if (resolvedSocket === void 0 || resolvedSocket.readyState !== resolvedSocket.OPEN) { return; } let untransformed; if (typeof raw === "function") { const fn = raw; untransformed = await fn(); } else { untransformed = await raw; } const transformed = transformMessage(untransformed); if (transformed != null) { resolvedSocket?.send(transformed); } }; p = p.then(async (s) => { resolvedSocket = s; switch (s.readyState) { case s.CLOSED: case s.CLOSING: case s.OPEN: return s; } await new Promise((resolve) => { s.addEventListener("open", resolve, { once: true }); s.addEventListener("close", resolve, { once: true }); }); return s; }); return (message) => { const maySend = resolvedSocket === void 0 || resolvedSocket.readyState === resolvedSocket.CONNECTING || resolvedSocket.readyState === resolvedSocket.OPEN; if (maySend) { p = p.finally(() => attemptSend(message)); } return maySend; }; } async function socketConnect(url, opts) { if (opts?.delay) { const p = opts.signal ? promiseVoidForSignal(opts.signal) : void 0; await Promise.race([p, timeout(opts.delay)]); } opts?.signal?.throwIfAborted(); const s = new WebSocket(url, opts?.protocols); if (opts?.signal) { afterSignal(opts?.signal, () => s.close()); } if (opts?.message) { s.addEventListener("message", opts.message); } let wasOpen = false; return new Promise((resolve, reject) => { s.addEventListener( "open", () => { wasOpen = true; resolve(s); }, { once: true } ); s.addEventListener("close", () => reject(new Error("WebSocket close")), { once: true }); s.addEventListener( "error", () => { if (!wasOpen) { s.close(); } reject(new Error("WebSocket error")); }, { once: true } ); }); } // src/call.ts var startSymbol = /* @__PURE__ */ Symbol("start"); async function startRemoteCall(signal, url) { const sock = await socketConnect(url, { signal }); const internalController = derivedSignal(signal); sock.addEventListener("close", internalController.abort); const helloPromise = new Promise((resolve, reject) => { sock.addEventListener( "message", (e) => { try { const p = JSON.parse(e.data); if (!p.ok) { throw new Error("no ok"); } resolve(p); } catch (e2) { reject(e2); } }, { once: true } ); sock.addEventListener("close", reject); signal.addEventListener("abort", reject); if (signal.aborted) { reject(); } }); sock.send(JSON.stringify({ p: "1" })); const hello = await helloPromise; const callLimiter = buildLimiter(hello.l.c); const packetLimiter = buildLimiter(hello.l.p); let lastNewCall = 0; const activeCalls = /* @__PURE__ */ new Map(); const listener = /* @__PURE__ */ (() => { let activeInCall = 0; return (e) => { if (typeof e.data === "string" && e.data[0] === ":") { const control = safeJSONParse(e.data.substring(1)); if (control?.c !== void 0) { activeInCall = control.c; } if (control?.stop !== void 0) { const active = activeCalls.get(activeInCall); active?.({ error: new RemoteCallError(control.stop) }); activeCalls.delete(activeInCall); } } else { const data = safeJSONParse(e.data); if (data) { const active = activeCalls.get(activeInCall); active?.({ data }); } } }; })(); sock.addEventListener("message", listener); const outboundQueue = buildLinkQueue(); const task = (async () => { const listener2 = outboundQueue.join(internalController.signal); let activeOutCall = 0; for (; ; ) { const next = await listener2.next(); if (!next) { return; } if (next.stop !== void 0) { const o = { stop: next.stop }; if (activeOutCall !== next.c) { o.c = next.c; activeOutCall = next.c; } sock.send(`:` + JSON.stringify(o)); continue; } if (next.data === startSymbol) { await callLimiter(internalController.signal); } else { await packetLimiter(internalController.signal); } if (activeOutCall !== next.c) { sock.send(`:` + JSON.stringify({ c: next.c })); activeOutCall = next.c; } if (next.data !== startSymbol) { sock.send(JSON.stringify(next.data)); } } })(); task.catch((e) => internalController.abort(e)); sock.addEventListener("error", (e) => internalController.abort(e)); internalController.signal.addEventListener("abort", () => sock.close()); const done = promiseForSignal(internalController.signal); done.then(() => { }); return { call(signal2) { if (signal2.aborted) { return { send(data) { }, gen: throwAsyncGenerator(signal2.reason) }; } const callId = ++lastNewCall; const q = buildLinkQueue(); activeCalls.set(callId, q.push.bind(q)); const listener2 = q.join(signal2); const gen = async function* () { for (; ; ) { const next = await listener2.next(); if (next?.data) { yield next.data; } if (next?.error || next?.data === void 0) { return next?.error; } } }(); outboundQueue.push({ data: startSymbol, c: callId }); signal2.addEventListener("abort", () => { activeCalls.delete(callId); outboundQueue.push({ stop: "", c: callId }); }); const send = (data) => { if (!signal2.aborted) { outboundQueue.push({ data, c: callId }); } }; return { send, gen }; }, init: hello.i, done }; } var RemoteCallError = class extends Error { constructor(stop) { super(stop); this.stop = stop; } }; function throwAsyncGenerator(reason) { return async function* () { throw reason instanceof Error ? reason : new Error(reason); }(); } function safeJSONParse(raw) { let out; try { out = JSON.parse(raw); } catch { return; } return out; } // src/maps.ts var CountSet = class { m = /* @__PURE__ */ new Map(); count = 0; /** * The total number of values (aka, the number of calls to {@link CountSetprivate add}). */ total() { return this.count; } add(t) { this.m.set(t, (this.m.get(t) ?? 0) + 1); ++this.count; return true; } entries() { return this.m.entries(); } delete(t) { const prev = this.m.get(t); if (prev === void 0) { return false; } if (prev === 1) { this.m.delete(t); } else { this.m.set(t, prev - 1); } --this.count; return true; } has(t) { return this.m.has(t); } uniques() { return this.m.keys(); } *keys() { for (const [t, count] of this.m.entries()) { for (let i = 0; i < count; ++i) { yield t; } } } }; var PairSet = class { m = new PairMap(); size() { return this.m.size(); } add(a, b) { return this.m.set(a, b, true); } delete(a, b) { return this.m.delete(a, b); } has(a, b) { return this.m.has(a, b); } hasAny(k) { return this.m.hasAny(k); } otherKeys(k) { return this.m.otherKeys(k); } pairsWith(k) { return this.m.pairsWith(k); } keys() { return this.m.keys(); } pairs() { return this.m.pairs(); } clear() { this.m.clear(); } }; var PairMap = class { m = /* @__PURE__ */ new Map(); implicitGet(k) { const has = this.m.get(k); if (has !== void 0) { return has; } const update = /* @__PURE__ */ new Map(); this.m.set(k, update); return update; } set(a, b, v) { const mapA = this.implicitGet(a); if (mapA.get(b) === v) { return false; } mapA.set(b, v); this.implicitGet(b).set(a, v); return true; } size() { return this.m.size; } pairsWith(k) { return this.m.get(k)?.size ?? 0; } otherKeys(k) { return this.m.get(k)?.keys() ?? [][Symbol.iterator](); } otherEntries(k) { return this.m.get(k)?.entries() ?? [][Symbol.iterator](); } *pairs() { const seen = /* @__PURE__ */ new Set(); for (const e of this.m.entries()) { const left = e[0]; for (const right of e[1].keys()) { if (!seen.has(right)) { yield [left, right]; } } seen.add(left); } } *pairsEntries() { const seen = /* @__PURE__ */ new Set(); for (const e of this.m.entries()) { const left = e[0]; for (const [right, value] of e[1].entries()) { if (!seen.has(right)) { yield [left, right, value]; } } seen.add(left); } } delete(a, b) { const mapA = this.m.get(a); if (!mapA?.has(b)) { return false; } mapA.delete(b); if (mapA.size === 0) { this.m.delete(a); } const mapB = this.m.get(b); mapB.delete(a); if (mapB.size === 0) { this.m.delete(b); } return true; } has(a, b) { return this.m.get(a)?.has(b) ?? false; } hasAny(k) { return this.m.has(k); } get(a, b) { return this.m.get(a)?.get(b); } keys() { return this.m.keys(); } clear() { this.m.clear(); } }; var MultiMap = class { m = /* @__PURE__ */ new Map(); _totalSize = 0; add(k, v) { let set = this.m.get(k); if (set === void 0) { set = /* @__PURE__ */ new Set(); this.m.set(k, set); } if (set.has(v)) { return false; } set.add(v); this._totalSize++; return true; } /** * Clears all values for this key. */ clearKey(k) { const had = this.m.get(k); if (had !== void 0) { this._totalSize -= had.size; this.m.delete(k); return true; } return false; } /** * Delete a specific key/value combination. */ delete(k, v) { const set = this.m.get(k); if (set === void 0) { return false; } const deleted = set.delete(v); if (deleted) { this._totalSize--; if (set.size === 0) { this.m.delete(k); } } return deleted; } has(k, v) { return this.m.get(k)?.has(v) ?? false; } get(k) { return this.m.get(k) ?? []; } /** * Returns the count of values for this key. */ count(k) { return this.m.get(k)?.size ?? 0; } /** * Returns the size of this map; the number of keys with valid values. * * This is not the total number of values. */ get size() { return this.m.size; } /** * Returns the total number of values set for all keys. */ get totalSize() { return this._totalSize; } /** * Iterates through all active keys (with any values). */ keys() { return this.m.keys(); } clear() { this.m.clear(); } }; var BiMap = class _BiMap { constructor(fwd = /* @__PURE__ */ new Map(), rwd = /* @__PURE__ */ new Map()) { this.fwd = fwd; this.rwd = rwd; } get size() { return this.fwd.size; } clear() { this.fwd.clear(); this.rwd.clear(); } keys() { return this.fwd.keys(); } values() { return this.fwd.values(); } entries() { return this.fwd.entries(); } invert() { return new _BiMap(this.rwd, this.fwd); } set(a, b) { const prevB = this.fwd.get(a); const prevA = this.rwd.get(b); if (prevB !== b) { this.rwd.delete(prevB); } this.rwd.set(b, a); if (prevA !== a) { this.fwd.delete(prevA); } this.fwd.set(a, b); return this; } has(a) { return this.fwd.has(a); } hasFar(b) { return this.rwd.has(b); } get(a) { return this.fwd.get(a); } getFar(b) { return this.rwd.get(b); } delete(a) { if (!this.fwd.has(a)) { return false; } const prevB = this.fwd.get(a); this.rwd.delete(prevB); this.fwd.delete(a); return true; } deleteFar(b) { if (!this.rwd.has(b)) { return false; } const prevA = this.rwd.get(b); this.fwd.delete(prevA); this.rwd.delete(b); return true; } static create() { return new _BiMap(); } }; var TransformMap = class { data = /* @__PURE__ */ new Map(); defaultValue; transform; constructor(defaultValue, transform) { this.defaultValue = defaultValue; this.transform = transform; } /** * Deletes the given key. Basically reverts it to its default value. */ delete(k) { return this.data.delete(k); } /** * Update the given key. */ update(k, update) { const prev = this.get(k); const result = this.transform(prev, update); if (result === this.defaultValue) { this.data.delete(k); } else { this.data.set(k, result); } return result; } /** * Return the current value for this key, or the default. */ get(k) { const prev = this.data.get(k); if (prev === void 0) { return this.defaultValue; } return prev; } /** * Return all keys for non-default values. */ keys() { return this.data.keys(); } /** * Whether this key has a non-default value. */ has(k) { return this.data.has(k); } clear() { this.data.clear(); } }; // src/counter.ts var StatsCount = class extends TransformMap { constructor() { super(0, (v, update) => v + update); } inc(k, by) { return this.update(k, by); } }; // src/effect.ts function prepareEffectTrigger() { const build = (cb, required) => { const requiredSet = new Set(required ?? []); const have = /* @__PURE__ */ new Set(); let abort = (reason) => { }; const o = {}; const maybeTrigger = () => { abort("trigger"); if (have.size !== requiredSet.size) { return; } const c = new AbortController(); abort = (reason) => c.abort(reason); cb(c.signal, { ...o }); }; const p = new Proxy(o, { deleteProperty(_, key) { delete o[key]; if (requiredSet.has(key)) { have.delete(key); } maybeTrigger(); return true; }, set(_, key, value) { if (requiredSet.has(key)) { if (value !== void 0) { have.add(key); } else { have.delete(key); } } const prev = o[key]; o[key] = value; if (prev !== value) { maybeTrigger(); } return true; } }); return p; }; return { build }; } // src/expirable.ts function buildAsyncExpirable(fn) { let activePromise; return () => { if (activePromise !== void 0) { return activePromise; } activePromise = fn().then((ret) => { if (ret.signal.aborted) { activePromise = void 0; } else { ret.signal.addEventListener("abort", () => activePromise = void 0); } return ret; }); return activePromise; }; } function buildExpirable(fn) { let active; return () => { if (active !== void 0) { return active; } const localActive = fn(); if (!localActive.signal.aborted) { active = localActive; localActive.signal.addEventListener("abort", () => active = void 0); } return localActive; }; } // src/internal.ts async function promiseForSignal2(signal) { if (signal === void 0) { return unresolvedPromise; } else if (!signal.aborted) { await new Promise((resolve) => signal.addEventListener("abort", resolve)); } throw symbolAbortSignal; } var symbolAbortSignal = /* @__PURE__ */ Symbol("known"); // src/generator.ts async function* combineAsyncGenerators(gen) { gen = gen.slice(); const buildNext = async (index) => { const p = gen[index].next(); const res = await p; return { index, res }; }; const nexts = gen.map((_, index) => buildNext(index)); let doneCount = 0; const doneValues = new Array(gen.length); while (doneCount !== gen.length) { const next = await Promise.race(nexts); if (next.res.done) { nexts[next.index] = unresolvedPromise; doneValues[next.index] = next.res.value; ++doneCount; } else { nexts[next.index] = buildNext(next.index); yield { index: next.index, value: next.res.value }; } } return doneValues; } async function* asyncGeneratorForHandler(handler, args) { const signalPromise = promiseForSignal2(args?.signal); for (; ; ) { let v; try { const p = handler(); v = await Promise.race([signalPromise, p]); } catch (e) { if (e === symbolAbortSignal) { return; } throw e; } yield v; } } var doneSymbol = /* @__PURE__ */ Symbol("done"); function asyncGeneratorQueue() { let { promise, resolve } = promiseWithResolvers(); let isDone = false; const pending = []; const push = (t) => { if (isDone) { throw new Error("Can't push into completed asyncGeneratorQueue"); } pending.push(t); resolve(); }; let doneValue; const done = (y) => { if (isDone) { throw new Error("Can't complete already completed asyncGeneratorQueue"); } pending.push(doneSymbol); isDone = true; doneValue = y; resolve(); }; const generator = async function* () { for (; ; ) { await promise; while (pending.length) { const next = pending.shift(); if (next === doneSymbol) { return doneValue; } yield next; } ({ promise, resolve } = promiseWithResolvers()); } }(); return { generator, push, done }; } var doneAsyncGenerator = /* @__PURE__ */ async function* () { }(); var AsyncGeneratorCache = class { _knownValues = []; _done = false; doneValue; pendingPromise; gen; constructor(gen) { this.gen = gen; } waitFor() { if (this.pendingPromise) { return this.pendingPromise; } return this.pendingPromise = this.gen.next().then((res) => { if (res.done) { this._done = true; this.doneValue = res.value; this.gen = doneAsyncGenerator; } else { this._knownValues.push(res.value); this.pendingPromise = void 0; } }); } async *read() { let at = 0; for (; ; ) { while (at < this._knownValues.length) { yield this._knownValues[at]; ++at; } if (this.done) { return this.doneValue; } await this.waitFor(); } } get done() { return this._done; } knownValues() { return this._knownValues; } }; async function forEachAsync(async, cb) { let result; let next = []; while (!(result = await async.next(...next)).done) { const internalResult = await cb(result.value); next = [internalResult]; } return result.value; } // src/support/node.ts import { isDeepStrictEqual } from "node:util"; function base64UrlToBytes(s) { return Buffer.from(s, "base64url"); } function base64UrlToString(s) { return new TextDecoder("utf-8").decode(base64UrlToBytes(s)); } function toBase64Url(s) { let b; if (typeof s === "string") { b = Buffer.from(s, "utf-8"); } else { b = Buffer.from(s); } return b.toString("base64url"); } function concatBytes(chunks) { return Buffer.concat(chunks); } var nextTick = /* @__PURE__ */ (() => process.nextTick)(); function escapeHtmlEntites(str) { return str.replaceAl