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
JavaScript
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