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,141 lines (2,121 loc) 70.7 kB
import { HtmlState, abortSignalTimeout, abortedSignal, afterSignal, base64UrlToBytes, base64UrlToString, buildCallTrain, concatBytes, derivedSignal, escapeStringFor, fastFrameRunner, htmlStateMachine, indexOfCloserWithinTagLike, isArrayEqualIsh, isArrayEqualIsh2, neverAbortedSignal, preprocessHtmlTemplateTag, promiseForEvent, promiseForSignal, promiseVoidForSignal, promiseWithResolvers, rafRunner, resolvable, resolvedPromise, spliceNextPromise, structuredIshClone, tickAbortSignal, tickRunner, timeout, toBase64Url, todoSignal, unresolvedPromise, wrapTrigger } from "./chunk-ZKLKPVTH.js"; // 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; }; } // 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/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/conn.ts function connForSocket(ws) { const c = new AbortController(); ws.addEventListener("close", () => c.abort()); if (ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING) { c.abort(); return { listener: buildEmptyListener(), send() { }, close() { }, signal: c.signal, done: Promise.resolve() }; } const { promise: donePromise, resolve: doneResolve, reject: doneReject } = promiseWithResolvers(); donePromise.finally(() => c.abort()); let sendQueue; if (ws.readyState !== ws.OPEN) { const localSendQueue = []; sendQueue = localSendQueue; ws.addEventListener("open", () => { localSendQueue.forEach((q2) => ws.send(q2)); localSendQueue.splice(0, localSendQueue.length); sendQueue = void 0; }); } const q = buildLinkQueue(); const conn = { listener: q.join(c.signal), send(raw) { if (sendQueue) { sendQueue.push(JSON.stringify(raw)); } else { ws.send(JSON.stringify(raw)); } }, close(cause) { ws.close(cause ? 4e3 : 1e3, String(cause)); cause && doneReject(cause); }, signal: c.signal, done: donePromise }; const messageHandler = (data) => { let j; try { j = JSON.parse(data); } catch { return; } q.push(j); }; if ("on" in ws) { ws.on("message", messageHandler); ws.on("error", (err) => doneReject(err)); ws.on("close", () => doneResolve()); } else { ws.addEventListener("message", (e) => messageHandler(e.data)); ws.addEventListener("error", (e) => doneReject(e)); ws.addEventListener("close", () => doneResolve()); } return conn; } // 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(); } }; 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(); } }; 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(); } }; 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); } }; // 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 = () => { }; const o = {}; const maybeTrigger = () => { abort(); if (have.size !== requiredSet.size) { return; } const c = new AbortController(); abort = () => c.abort(); 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; } }; // src/intermediate.ts function buildAsyncIntermediate() { let wakeup = () => { }; const pending = []; let done = false; const gen = async function* () { for (; ; ) { const next = pending.shift(); if (next === void 0) { await new Promise((r) => wakeup = r); continue; } if (next.done) { next.resolve(); return next.value; } yield next.value; next.resolve(); } }(); const send = (value) => { if (done) { throw new Error(`Cannot send to stopped AsyncIntermediate`); } return new Promise((resolve) => { pending.push({ done: false, value, resolve }); wakeup(); }); }; const stop = (value) => { if (done) { throw new Error(`Cannot stop already stopped AsyncIntermediate`); } done = true; return new Promise((resolve) => { pending.push({ done: true, value, resolve }); wakeup(); }); }; return { gen, send, stop }; } // src/listener.ts function namedListeners() { const listeners = /* @__PURE__ */ new Map(); const ensureListener = (type) => { let s = listeners.get(type); if (s === void 0) { s = { listeners: /* @__PURE__ */ new Set(), any: /* @__PURE__ */ new Set(), activeSignal: abortedSignal, abort: () => { } }; listeners.set(type, s); } return s; }; return { addListener(name, listener, signal) { if (signal.aborted) { return; } const state = ensureListener(name); if (state.listeners.has(listener)) { const original = listener; listener = (arg) => original(arg); } else if (state.listeners.size === 0) { const c = new AbortController(); state.activeSignal = c.signal; state.abort = () => c.abort(); state.any.forEach((l) => l(state.activeSignal)); } signal.addEventListener("abort", () => { state.listeners.delete(listener); if (state.listeners.size === 0) { state.abort(); } }); state.listeners.add(listener); }, any(name, callback, signal) { if (signal?.aborted) { return; } const state = ensureListener(name); if (state.any.has(callback)) { const original = callback; callback = (arg) => original(arg); } state.any.add(callback); signal?.addEventListener("abort", () => state.any.delete(callback)); if (state.listeners.size) { callback(derivedSignal(state.activeSignal, signal).signal); } }, hasAny(name) { return listeners.has(name); }, // @ts-expect-error TS is confused because it thinks we should use `...arg` dispatch(name, arg) { const s = listeners.get(name); s?.listeners.forEach((l) => l(arg)); return Boolean(s?.listeners.size); }, eventTarget(arg) { return namedListenersToEventTarget(this, arg); } }; } function soloListenerFrom(name, nl) { return { addListener(listener, signal) { nl.addListener(name, listener, signal); }, dispatch(v) { return nl.dispatch(name, v); }, any(callback, signal) { nl.any(name, callback, signal); }, hasAny() { return nl.hasAny(name); } }; } function soloListener() { const nl = namedListeners(); return soloListenerFrom("", nl); } function coeerceOptions(options) { if (typeof options === "object") { return { once: options.once, signal: options.signal }; } return { once: false, signal: void 0 }; } function buildForRef(overallSignal, callback, options) { let { once: once2, signal } = coeerceOptions(options); let fn; if ("handleEvent" in callback) { fn = (e) => callback.handleEvent(e); } else { fn = callback; } let abort; if (once2) { const c = new AbortController(); const originalFn = fn; fn = (e) => { c.abort(); originalFn(e); }; ({ signal, abort } = derivedSignal(c.signal, overallSignal, signal)); } else if (!signal && !overallSignal) { signal = neverAbortedSignal; abort = () => { }; } else { ({ signal, abort } = derivedSignal(signal, overallSignal)); } return { ref: callback, signal, fn, abort }; } function namedListenersToEventTarget(nl, opts) { const addedByRef = /* @__PURE__ */ new Map(); const addByRef = (type, ref, signal, abort) => { if (signal.aborted) { return false; } let forType = addedByRef.get(type); if (forType === void 0) { forType = /* @__PURE__ */ new Map(); addedByRef.set(type, forType); } if (forType.has(ref)) { return false; } forType.set(ref, abort); signal.addEventListener("abort", () => forType.delete(ref)); return true; }; const { signal: overallSignal, buildEvent } = opts ?? {}; return { addEventListener(type, callback, options) { if (!callback) { return; } const { ref, signal, abort, fn } = buildForRef(overallSignal, callback, options); if (!addByRef(type, ref, signal, abort)) { return; } const boundFn = (raw) => { const e = buildEvent?.(type, raw) || new CustomEvent(type, { detail: raw }); if (!(e instanceof Event) || e.type !== type) { throw new Error( `buildEvent must return Event with type=${type}, ${e instanceof Event ? e.type : "?"}` ); } fn.call(this, e); }; nl.addListener(type, boundFn, signal); }, dispatchEvent(event) { throw new Error("dispatchEvent unsupported"); }, removeEventListener(type, callback, options) { if (!callback) { return; } const prev = addedByRef.get(type)?.get(callback); prev?.(); } }; } // src/object-utils.ts var matchAny = /* @__PURE__ */ Symbol("matchAny"); function matchPartial(filter, object) { if (object === void 0) { return false; } else if (object === filter || filter === matchAny) { return true; } else if (typeof object === "object" && object) { for (const key in filter) { if (!matchPartial(filter[key], object[key])) { return false; } } return true; } else { return false; } } function readMatchAny(filter, object) { if (filter === matchAny) { return [object]; } else if (typeof filter === "object" && filter) { let agg; const traverseInto = typeof object === "object" || object === void 0 ? object : void 0; for (const key in filter) { const out = readMatchAny(filter[key], traverseInto?.[key]); if (out) { if (agg === void 0) { agg = out; } else { agg.push(...out); } } } return agg; } } function intersectObjects(a, b) { if (a === b) { return a; } if (a && b && typeof a === "object" && typeof b === "object") { const unionObject = {}; for (const key in a) { const out = intersectObjects(a[key], b[key]); if (out !== void 0) { unionObject[key] = out; } } return unionObject; } } function intersectManyObjects(of) { const iter = of[Symbol.iterator](); const first = iter.next(); if (first.done) { return; } let out = first.value; if (typeof out !== "object") { return out; } let ir = iter.next(); if (ir.done) { return out; } for (; ; ) { out = intersectObjects(out, ir.value); if (typeof out !== "object") { return out; } for (const _ in out) { ir = iter.next(); if (ir.done) { return out; } break; } } } function deepFreeze(object) { if (object && typeof object === "object") { const propNames = Object.getOwnPropertyNames(object); for (const name of propNames) { const value = object[name]; deepFreeze(value); } return Object.freeze(object); } return object; } // src/matcher.ts var Matcher = class { objects = /* @__PURE__ */ new Map(); groups = /* @__PURE__ */ new Map(); get(id) { return structuredIshClone(this.objects.get(id)); } /** * Sets this value into the {@link Matcher}. This will trigger group state changes. */ set(id, value) { const prev = this.objects.get(id); const beforeGroups = prev === void 0 ? [] : [...this.groups.keys()].filter(({ filter }) => matchPartial(filter, prev)); if (value === void 0) { this.objects.delete(id); } else { this.objects.set(id, structuredIshClone(value)); } const afterGroupsSet = value === void 0 ? /* @__PURE__ */ new Set() : new Set([...this.groups.keys()].filter(({ filter }) => matchPartial(filter, value))); beforeGroups.forEach((g) => { let triggerChange = false; if (g.hasAny) { const anyValues = readMatchAny(g.filter, prev); const updatedAnyValues = readMatchAny(g.filter, value); triggerChange = !isArrayEqualIsh2(anyValues, updatedAnyValues); } if (triggerChange || !afterGroupsSet.delete(g)) { this.groups.get(g).delete(id); } }); afterGroupsSet.forEach((g) => { this.groups.get(g).add(id); }); } delete(id) { if (this.objects.has(id)) { this.set(id, void 0); return true; } return false; } /** * Does any object match this filter? */ matchAny(filter) { for (const o of this.objects.values()) { if (matchPartial(filter, o)) { return true; } } return false; } /** * Matches objects immediately, without grouping. */ *matchAll(filter) { for (const [id, o] of this.objects.entries()) { if (matchPartial(filter, o)) { yield id; } } } /** * Returns the intersection of reading the given keys. */ read(keys) { const o = this.objects; const gen = function* () { for (const key of keys) { yield o.get(key); } }(); return intersectManyObjects(gen); } /** * Attaches a subscripton to this {@link Matcher} based on the given {@link Filter}. The filter * will be frozen before use, so you cannot change it later. * * This will add all initially matching objects to the {@link MatcherSub}. However, the current * matching set will not be cleared when the passed signal is aborted. */ sub(filter, g, options) { deepFreeze(filter); if (options?.signal?.aborted) { return; } const hasAny = readMatchAny(filter, void 0) !== void 0; const key = { hasAny, filter }; this.groups.set(key, g); for (const id of this.matchAll(filter)) { g.add(id); } options?.signal?.addEventListener("abort", () => { this.groups.delete(key); }); } }; var CombineGroup = class { groups; cond; isActive; static create(groups, isActive) { return new this(groups, isActive); } constructor(groups, isActive) { groups = groups.slice(); this.groups = groups; this.isActive = isActive ?? (() => { for (const g of groups) { if (!g.active()) { return false; } } return true; }); const listener = () => { this.cond.state = this.isActive(groups); }; this.cond = new class extends Condition { setup() { listener(); groups.forEach((g) => g.addListener(listener, { both: true })); } teardown() { groups.forEach((g) => g.removeListener(listener)); } }(false); } active() { if (this.cond.observed()) { return this.cond.state; } return this.isActive(this.groups); } addListener(fn, options) { return this.cond.addListener(fn, options); } removeListener(fn) { return this.cond.removeListener(fn); } }; var MatcherGroup = class { filter; matcher; signal; matchingSet = /* @__PURE__ */ new Set(); cond; static create(filter, matcher, options) { return new this(filter, matcher, options); } constructor(filter, matcher, options) { this.filter = filter; this.matcher = matcher; this.signal = options?.signal; let abortCurrentSubscription = () => { }; this.signal?.addEventListener("abort", () => abortCurrentSubscription()); const outer = this; this.cond = new class extends Condition { setup() { const c = new AbortController(); c.signal.addEventListener("abort", () => outer.matchingSet.clear()); abortCurrentSubscription = () => c.abort(); if (outer.matchingSet.size) { throw new Error(`should not have anything in set on setup: ${outer.matchingSet}`); } this.state = outer.isActive(outer.matchingSet); const cond = this; const sub = { add(id) { outer.matchingSet.add(id); cond.state = outer.isActive(outer.matchingSet); }, delete(id) { outer.matchingSet.delete(id); cond.state = outer.isActive(outer.matchingSet); } }; matcher.sub(filter, sub, { signal: c.signal }); } teardown() { abortCurrentSubscription(); } }(false, options); } isActive(matchingKeys) { return matchingKeys.size !== 0; } active() { if (this.cond.observed()) { return this.cond.state; } return this.matcher.matchAny(this.filter); } matching() { if (this.cond.observed()) { return this.matchingSet.keys(); } return this.matcher.matchAll(this.filter); } addListener(fn, options) { return this.cond.addListener(fn, options); } removeListener(fn) { return this.cond.removeListener(fn); } }; // src/memoize.ts var memoizeMap; var MemoizeState = class { // TODO: this is O(n) with calls. Maybe a tree structure? previousCalls = []; findIndex(query) { const toRemove = []; let index = this.previousCalls.findIndex(({ args }, index2) => { if (query.length !== args.length) { return false; } for (let i = 0; i < args.length; ++i) { let check = args[i]; if (check instanceof WeakRef) { check = check.deref(); if (check === void 0) { toRemove.unshift(index2); return false; } } if (check !== query[i]) { return false; } } return true; }); toRemove.forEach((toRemoveIndex) => { if (toRemoveIndex < index) { --index; } this.previousCalls.splice(toRemoveIndex, 1); }); return index; } remove(args) { const index = this.findIndex(args); if (index === -1) { return false; } this.previousCalls.splice(index, 1); return true; } get(args) { const index = this.findIndex(args); if (index === -1) { return; } const found = this.previousCalls[index]; return { result: found.result }; } /** * Stores a previous call. */ store(args, result) { this.previousCalls.push({ args: args.map((arg) => { if (typeof arg === "object" && arg !== null) { return new WeakRef(arg); } return arg; }), result }); } }; function purgeMemoize(fn) { memoizeMap.delete(fn); } function clearMemoize(fn, ...args) { const state = memoizeMap?.get(fn); if (state === void 0) { return false; } return false; } function callMemoize(fn, ...args) { if (memoizeMap === void 0) { memoizeMap = /* @__PURE__ */ new WeakMap(); } let s = memoizeMap.get(fn); if (s === void 0) { s = new MemoizeState(); memoizeMap.set(fn, s); } const maybeResult = s.get(args); if (maybeResult !== void 0) { return maybeResult.result; } const newResult = fn(...args); s.store(args, newResult); return newResult; } function memoize(fn) { return (...args) => callMemoize(fn, ...args); } // src/record.ts async function mapRecordAsync(raw, cb) { const inputEntries = Object.entries(raw); const outputEntriesPromise = inputEntries.map(async ([key, value]) => { const output = await cb(key, value); return [key, output]; }); const outputEntries = await Promise.all(outputEntriesPromise); return Object.fromEntries(outputEntries); } function mapRecord(raw, cb) { const inputEntries = Object.entries(raw); const outputEntries = inputEntries.map(([key, value]) => { const output = cb(key, value); return [key, output]; }); return Object.fromEntries(outputEntries); } function filterRecord(raw, cb) { if (cb === void 0) { cb = (_, value) => value != null; } const inputEntries = Object.entries(raw); const outputEntries = inputEntries.filter(([key, value]) => cb(key, value)); return Object.fromEntries(outputEntries); } function recordHasAny(rec) { if (rec) { for (const _ in rec) { return true; } } return false; } // src/primitives.ts var randInt32 = /* @__PURE__ */ (() => { const u = new Int32Array(16384); let ui = u.length; return function randInt322() { if (ui === u.length) { crypto.getRandomValues(u); ui = 0; } return u[ui++]; }; })(); function hashCode(s) { if (typeof s === "string") { return hashCodeString(s); } return hashCodeArray(s); } function hashCodeString(s) { let h = 0; for (let i = 0; i < s.length; i++) { h = Math.imul(31, h) + s.charCodeAt(i) | 0; } return Math.abs(h); } function hashCodeArray(s) { let h = 0; for (let i = 0; i < s.length; ++i) { h = Math.imul(31, h) + s[i] | 0; } return Math.abs(h); } function randomArrayChoice(arr) { const index = arr.length <= 1 ? 0 : Math.floor(Math.random() * arr.length); return arr[index]; } function randomPick(iter) { if (Array.isArray(iter)) { return randomArrayChoice(iter); } return randomPickN(iter, 1)[0]; } function randomPickN(iter, count) { const out = []; if (count < 1) { return out; } const it = iter[Symbol.iterator](); while (out.length < count) { const next = it.next(); if (next.done) { return out; } out.push(next.value); } let i = out.length; for (; ; ) { const next = it.next(); if (next.done) { return out; } ++i; const r = randomRangeInt(0, i + 1); if (r <= out.length) { out[r] = next.value; } } } fun