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.
517 lines (511 loc) • 14.1 kB
JavaScript
// 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(runner, callback, options) {
const { immediate, signal } = options ?? {};
let activePromise;
const o = () => {
if (activePromise === void 0) {
const pr = promiseWithResolvers();
runner(() => {
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/support/browser.ts
function isArrayEqualIsh(val1, val2) {
if (val1 === val2) {
return true;
}
if (!Array.isArray(val1) || !Array.isArray(val2) || val1.length !== val2.length) {
return false;
}
for (let i = 0; i < val1.length; ++i) {
if (val1[i] !== val2[i]) {
return false;
}
}
return true;
}
function base64UrlToBytes(s) {
if ("fromBase64" in Uint8Array) {
return Uint8Array.fromBase64(s, { alphabet: "base64url" });
}
const sb = atob(s.replaceAll("-", "+").replaceAll("_", "/"));
const out = new Uint8Array(sb.length);
for (let i = 0; i < out.length; ++i) {
out[i] = sb.charCodeAt(i);
}
return out;
}
function base64UrlToString(s) {
return new TextDecoder("utf-8").decode(base64UrlToBytes(s));
}
function toBase64Url(s) {
if (typeof s === "string") {
s = new TextEncoder().encode(s);
}
if ("toBase64" in s) {
return s.toBase64({ alphabet: "base64url", omitPadding: true });
}
const bs = String.fromCodePoint(...s);
return btoa(bs).replace(/=+$/, "").replaceAll("+", "-").replaceAll("/", "_");
}
function concatBytes(chunks) {
chunks = chunks.filter((chunk) => chunk.length !== 0);
if (chunks.length === 0) {
return new Uint8Array();
} else if (chunks.length === 1) {
return chunks[0];
}
let size = 0;
chunks.forEach((chunk) => size += chunk.length);
const out = new Uint8Array(size);
let at = 0;
chunks.forEach((chunk) => {
out.set(chunk, at);
at += chunk.length;
});
return out;
}
// src/support/index.ts
var isArrayEqualIsh2 = isArrayEqualIsh;
var fauxStructuredClone = (o) => {
if (typeof o !== "object") {
return o;
}
const out = { ...o };
for (const k in out) {
out[k] = fauxStructuredClone(out[k]);
}
return out;
};
var structuredIshClone = typeof structuredClone === "function" ? structuredClone : fauxStructuredClone;
var escapeHtmlEntites = (str) => {
return str.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
};
// src/html-state.ts
var HtmlState = /* @__PURE__ */ ((HtmlState2) => {
HtmlState2[HtmlState2["Normal"] = 0] = "Normal";
HtmlState2[HtmlState2["WithinTag"] = 2] = "WithinTag";
HtmlState2[HtmlState2["TagAttr"] = 3] = "TagAttr";
HtmlState2[HtmlState2["WithinComment"] = 9] = "WithinComment";
HtmlState2[HtmlState2["WithinScriptTag"] = 10] = "WithinScriptTag";
HtmlState2[HtmlState2["WithinStyleTag"] = 11] = "WithinStyleTag";
HtmlState2[HtmlState2["WithinTextAreaTag"] = 12] = "WithinTextAreaTag";
HtmlState2[HtmlState2["WithinTagAttrDoubleQuote"] = 17] = "WithinTagAttrDoubleQuote";
HtmlState2[HtmlState2["WithinTagAttrSingleQuote"] = 18] = "WithinTagAttrSingleQuote";
return HtmlState2;
})(HtmlState || {});
var nextRe = /<(\!--|\w+)/;
var withinTagNext = /(=?\s*\'|=?\s*\"|=\s*|>|\/\>)/;
var closedByRe = /^\s*>/;
function indexOfCloserWithinTagLike(state, check) {
let find;
switch (state) {
case 9 /* WithinComment */: {
const index = check.indexOf("-->");
if (index === -1) {
return -1;
}
return index + 3;
}
case 10 /* WithinScriptTag */:
find = "<\/script";
break;
case 11 /* WithinStyleTag */:
find = "</style";
break;
case 12 /* WithinTextAreaTag */:
find = "</textarea";
break;
default:
return -1;
}
let out = check.indexOf(find);
if (out === -1) {
return -1;
}
out += find.length;
const rest = check.substring(out);
if (!closedByRe.test(rest)) {
return -1;
}
const indexOfEnd = rest.indexOf(">");
if (indexOfEnd === -1) {
throw new Error(`should never happen`);
}
return out + indexOfEnd + 1;
}
function escapeStringFor(state, s) {
s = String(s);
if (indexOfCloserWithinTagLike(state, s) !== -1) {
let msg = "?";
switch (state) {
case 9 /* WithinComment */:
msg = "-->";
break;
case 10 /* WithinScriptTag */:
msg = "<\/script>";
break;
case 11 /* WithinStyleTag */:
msg = "</style>";
break;
case 12 /* WithinTextAreaTag */:
msg = "</textarea>";
break;
}
throw new Error(`can't inline text: dangerously contains closer "${msg}"`);
}
switch (state) {
case 9 /* WithinComment */:
case 10 /* WithinScriptTag */:
case 11 /* WithinStyleTag */:
case 12 /* WithinTextAreaTag */:
return s;
}
const escaped = escapeHtmlEntites(s);
switch (state) {
case 2 /* WithinTag */:
throw new Error(`unsupported interpolation within <tag>`);
case 3 /* TagAttr */:
return `"${escaped.replaceAll('"', """)}"`;
case 17 /* WithinTagAttrDoubleQuote */:
return escaped.replaceAll('"', """);
case 18 /* WithinTagAttrSingleQuote */:
return escaped.replaceAll("'", "'");
case 0 /* Normal */:
return escaped;
}
}
function htmlStateMachine() {
let state = 0 /* Normal */;
let upcomingWithinTag = "";
const internalConsume = (next) => {
if (!next) {
return;
}
switch (state) {
case 3 /* TagAttr */: {
state = 2 /* WithinTag */;
return internalConsume(next);
}
case 17 /* WithinTagAttrDoubleQuote */:
case 18 /* WithinTagAttrSingleQuote */: {
const search = state === 17 /* WithinTagAttrDoubleQuote */ ? '"' : "'";
const escapeIndex = next.indexOf(search);
if (escapeIndex === -1) {
return;
}
state = 2 /* WithinTag */;
return internalConsume(next.substring(escapeIndex + 1));
}
case 2 /* WithinTag */: {
const m = withinTagNext.exec(next);
if (!m) {
return;
}
const inner = m[1];
const last = inner[inner.length - 1];
if (last === '"') {
state = 17 /* WithinTagAttrDoubleQuote */;
} else if (last === "'") {
state = 18 /* WithinTagAttrSingleQuote */;
} else if (inner[0] === "=") {
state = 3 /* TagAttr */;
} else {
switch (upcomingWithinTag) {
case "script":
state = 10 /* WithinScriptTag */;
break;
case "style":
state = 11 /* WithinStyleTag */;
break;
case "textarea":
state = 12 /* WithinTextAreaTag */;
break;
default:
state = 0 /* Normal */;
}
upcomingWithinTag = "";
}
return internalConsume(next.substring(m.index + m[1].length));
}
case 0 /* Normal */: {
const m = nextRe.exec(next);
if (!m) {
return;
}
if (m[1] === "!--") {
state = 9 /* WithinComment */;
return internalConsume(next.substring(m.index));
}
upcomingWithinTag = m[1];
state = 2 /* WithinTag */;
return internalConsume(next.substring(m.index + m[1].length));
}
case 9 /* WithinComment */:
case 10 /* WithinScriptTag */:
case 11 /* WithinStyleTag */:
case 12 /* WithinTextAreaTag */: {
const index = indexOfCloserWithinTagLike(state, next);
if (index === -1) {
return;
}
state = 0 /* Normal */;
next = next.substring(index);
return internalConsume(next);
}
default: {
const x = state;
throw new Error(`unhandled state: ${state}`);
}
}
};
return {
consume(next) {
internalConsume(next);
return state;
}
};
}
var preprocessCache = /* @__PURE__ */ new WeakMap();
function preprocessHtmlTemplateTag(arr) {
const prev = preprocessCache.get(arr);
if (prev) {
return prev;
}
const sm = htmlStateMachine();
const states = [];
for (let i = 0; ; ++i) {
if (i + 1 === arr.length) {
break;
}
const state = sm.consume(arr[i]);
states.push(state);
}
const f = Object.freeze(states);
preprocessCache.set(arr, f);
return f;
}
export {
unresolvedPromise,
resolvedPromise,
wrapTrigger,
timeout,
promiseWithResolvers,
resolvable,
promiseForEvent,
spliceNextPromise,
buildCallTrain,
rafRunner,
fastFrameRunner,
tickRunner,
afterSignal,
promiseVoidForSignal,
promiseForSignal,
abortSignalTimeout,
tickAbortSignal,
derivedSignal,
abortedSignal,
neverAbortedSignal,
todoSignal,
isArrayEqualIsh,
base64UrlToBytes,
base64UrlToString,
toBase64Url,
concatBytes,
isArrayEqualIsh2,
structuredIshClone,
HtmlState,
indexOfCloserWithinTagLike,
escapeStringFor,
htmlStateMachine,
preprocessHtmlTemplateTag
};
//# sourceMappingURL=chunk-FJEQHTCW.js.map