UNPKG

ixfx

Version:

A framework for programming interactivity

1,381 lines (1,356 loc) 36.7 kB
import { StateMachine_exports, Stopwatch_exports, elapsedMillisecondsAbsolute, elapsedTicksAbsolute, frequencyTimer, hasElapsed, infinity, interval, ofTotal, ofTotalTicks, once, relative, run, runSingle, since, timerAlwaysDone, timerNeverDone, timerWithFunction } from "./chunk-LHELVIWO.js"; import { isIterable, mutable } from "./chunk-VYCNRTDD.js"; import { resolveLogOption } from "./chunk-N6YIY4CM.js"; import { SimpleEventEmitter, elapsedToHumanString, intervalToMs, isInterval } from "./chunk-72EKR3DZ.js"; import { getErrorMessage } from "./chunk-4IJNRUE7.js"; import { integerTest, throwIntegerTest, throwNumberTest } from "./chunk-UC4AQMTL.js"; import { __export } from "./chunk-L5EJU35C.js"; // src/flow/index.ts var flow_exports = {}; __export(flow_exports, { BehaviourTree: () => BehaviourTree_exports, DispatchList: () => DispatchList, Elapsed: () => Stopwatch_exports, RequestResponseMatch: () => RequestResponseMatch, StateMachine: () => StateMachine_exports, SyncWait: () => SyncWait, TaskQueueMutable: () => TaskQueueMutable, WaitForValue: () => WaitForValue, backoffGenerator: () => backoffGenerator, continuously: () => continuously, debounce: () => debounce, delay: () => delay, delayLoop: () => delayLoop, elapsedMillisecondsAbsolute: () => elapsedMillisecondsAbsolute, elapsedTicksAbsolute: () => elapsedTicksAbsolute, elapsedToHumanString: () => elapsedToHumanString, everyNth: () => everyNth, frequencyTimer: () => frequencyTimer, hasElapsed: () => hasElapsed, infinity: () => infinity, interval: () => interval, intervalToMs: () => intervalToMs, isInterval: () => isInterval, ofTotal: () => ofTotal, ofTotalTicks: () => ofTotalTicks, once: () => once, promiseFromEvent: () => promiseFromEvent, promiseWithResolvers: () => promiseWithResolvers, rateMinimum: () => rateMinimum, relative: () => relative, repeat: () => repeat, repeatSync: () => repeatSync, retryFunction: () => retryFunction, retryTask: () => retryTask, run: () => run, runOnce: () => runOnce, runSingle: () => runSingle, since: () => since, singleItem: () => singleItem, sleep: () => sleep, sleepWhile: () => sleepWhile, throttle: () => throttle, timeout: () => timeout, timerAlwaysDone: () => timerAlwaysDone, timerNeverDone: () => timerNeverDone, timerWithFunction: () => timerWithFunction, updateOutdated: () => updateOutdated, waitFor: () => waitFor }); // src/flow/BehaviourTree.ts var BehaviourTree_exports = {}; __export(BehaviourTree_exports, { iterateBreadth: () => iterateBreadth, iterateDepth: () => iterateDepth }); var getName = (t, defaultValue = ``) => { if (typeof t === `object` && `name` in t && t.name !== void 0) return t.name; return defaultValue; }; function* iterateBreadth(t, pathPrefix) { if (typeof pathPrefix === `undefined`) { pathPrefix = getName(t); } for (const [index, n] of entries(t)) { yield [n, pathPrefix]; } for (const [index, n] of entries(t)) { const name = getName(n, `?`); const prefix = pathPrefix.length > 0 ? pathPrefix + `.` + name : name; yield* iterateBreadth(n, prefix); } } function* iterateDepth(t, pathPrefix) { if (typeof pathPrefix === `undefined`) { pathPrefix = getName(t); } for (const [index, n] of entries(t)) { yield [n, pathPrefix]; const name = getName(n, `?`); const prefix = pathPrefix.length > 0 ? pathPrefix + `.` + name : name; yield* iterateBreadth(n, prefix); } } function isSeqNode(n) { return n.seq !== void 0; } function isSelNode(n) { return n.sel !== void 0; } function* entries(n) { if (isSeqNode(n)) { yield* n.seq.entries(); } else if (isSelNode(n)) { yield* n.sel.entries(); } else if (typeof n === `string`) { } else { throw new TypeError(`Unexpected shape of node. seq/sel missing`); } } // src/flow/Continuously.ts var continuously = (callback, interval2, options = {}) => { let intervalMs = intervalToMs(interval2, 0); throwIntegerTest(intervalMs, `positive`, `interval`); const fireBeforeWait = options.fireBeforeWait ?? false; const onStartCalled = options.onStartCalled; const signal = options.signal; let disposed = false; let runState = `idle`; let startCount = 0; let startCountTotal = 0; let startedAt = performance.now(); let intervalUsed = interval2 ?? 0; let cancelled = false; let currentTimer; const deschedule = () => { if (currentTimer === void 0) return; globalThis.clearTimeout(currentTimer); currentTimer = void 0; startCount = 0; startedAt = Number.NaN; }; const schedule = (scheduledCallback) => { if (intervalMs === 0) { if (typeof requestAnimationFrame === `undefined`) { currentTimer = globalThis.setTimeout(scheduledCallback, 0); } else { currentTimer = void 0; requestAnimationFrame(scheduledCallback); } } else { currentTimer = globalThis.setTimeout(scheduledCallback, intervalMs); } }; const cancel = () => { if (cancelled) return; cancelled = true; if (runState === `idle`) return; runState = `idle`; deschedule(); }; const loop = async () => { if (signal?.aborted) { runState = `idle`; } if (runState === `idle`) return; runState = `running`; startCount++; startCountTotal++; const valueOrPromise = callback(startCount, performance.now() - startedAt); const value = typeof valueOrPromise === `object` ? await valueOrPromise : valueOrPromise; if (cancelled) { return; } runState = `scheduled`; if (value !== void 0 && !value) { cancel(); return; } if (cancelled) return; schedule(loop); }; const start = () => { if (disposed) throw new Error(`Disposed`); cancelled = false; if (onStartCalled !== void 0) { const doWhat = onStartCalled(startCount, performance.now() - startedAt); switch (doWhat) { case `cancel`: { cancel(); return; } case `reset`: { reset(); return; } case `dispose`: { disposed = true; cancel(); return; } } } if (runState === `idle`) { startCount = 0; startedAt = performance.now(); runState = `scheduled`; if (fireBeforeWait) { void loop(); } else { schedule(loop); } } }; const reset = () => { if (disposed) throw new Error(`Disposed`); cancelled = false; startCount = 0; startedAt = Number.NaN; if (runState !== `idle`) { cancel(); } start(); }; return { start, reset, cancel, get interval() { return intervalUsed; }, get runState() { return runState; }, get startCountTotal() { return startCountTotal; }, get startCount() { return startCount; }, set interval(interval3) { const ms = intervalToMs(interval3, 0); throwIntegerTest(ms, `positive`, `interval`); intervalMs = ms; intervalUsed = interval3; }, get isDisposed() { return disposed; }, get elapsedMs() { return performance.now() - startedAt; } }; }; // src/flow/Timeout.ts var timeout = (callback, interval2) => { if (callback === void 0) { throw new Error(`callback parameter is undefined`); } const intervalMs = intervalToMs(interval2); throwIntegerTest(intervalMs, `aboveZero`, `interval`); let timer; let startedAt = 0; let startCount = 0; let startCountTotal = 0; let state = `idle`; const clear = () => { startedAt = 0; globalThis.clearTimeout(timer); state = `idle`; }; const start = async (altInterval = interval2, args) => { const p = new Promise((resolve2, reject) => { startedAt = performance.now(); const altTimeoutMs = intervalToMs(altInterval); const it = integerTest(altTimeoutMs, `aboveZero`, `altTimeoutMs`); if (!it[0]) { reject(new Error(it[1])); return; } switch (state) { case `scheduled`: { cancel(); break; } case `running`: { break; } } state = `scheduled`; timer = globalThis.setTimeout(async () => { if (state !== `scheduled`) { console.warn(`Timeout skipping execution since state is not 'scheduled'`); clear(); return; } const args_ = args ?? []; startCount++; startCountTotal++; state = `running`; await callback(performance.now() - startedAt, ...args_); state = `idle`; clear(); resolve2(); }, altTimeoutMs); }); return p; }; const cancel = () => { if (state === `idle`) return; clear(); }; return { // eslint-disable-next-line @typescript-eslint/no-misused-promises start, cancel, get runState() { return state; }, get startCount() { return startCount; }, get startCountTotal() { return startCountTotal; } }; }; // src/flow/Debounce.ts var debounce = (callback, interval2) => { const t = timeout(callback, interval2); return (...args) => { t.start(void 0, args); }; }; // src/flow/Sleep.ts if (typeof window === `undefined` || !(`requestAnimationFrame` in window)) { if (typeof window === `undefined`) { globalThis.requestAnimationFrame = (callback) => { setTimeout(callback, 1); }; } } var sleep = (optsOrMillis) => { const timeoutMs = intervalToMs(optsOrMillis, 1); const signal = optsOrMillis.signal; const value = optsOrMillis.value; throwNumberTest(timeoutMs, `positive`, `timeoutMs`); if (timeoutMs === 0) { return new Promise( (resolve2) => requestAnimationFrame((_) => { resolve2(value); }) ); } else { return new Promise((resolve2, reject) => { const onAbortSignal = () => { clearTimeout(t); if (signal) { signal.removeEventListener(`abort`, onAbortSignal); reject(new Error(signal.reason)); } else { reject(new Error(`Cancelled`)); } }; if (signal) { signal.addEventListener(`abort`, onAbortSignal); } const t = setTimeout(() => { signal?.removeEventListener(`abort`, onAbortSignal); if (signal?.aborted) { reject(new Error(signal.reason)); return; } resolve2(value); }, timeoutMs); }); } }; var sleepWhile = async (predicate, checkInterval = 100) => { while (predicate()) { await sleep(checkInterval); } }; // src/flow/Delay.ts var delay = async (callback, optsOrMillis) => { const opts = typeof optsOrMillis === `number` ? { millis: optsOrMillis } : optsOrMillis; const delayWhen = opts.delay ?? `before`; if (delayWhen === `before` || delayWhen === `both`) { await sleep(opts); } const r = Promise.resolve(await callback()); if (delayWhen === `after` || delayWhen === `both`) { await sleep(opts); } return r; }; async function* delayAnimationLoop() { let resolve2; let p = new Promise((r) => resolve2 = r); let timer = 0; const callback = () => { if (resolve2) resolve2(); p = new Promise((r) => resolve2 = r); }; try { while (true) { timer = globalThis.requestAnimationFrame(callback); const _ = await p; yield _; } } finally { if (resolve2) resolve2(); globalThis.cancelAnimationFrame(timer); } } async function* delayLoop(timeout2) { const timeoutMs = intervalToMs(timeout2); if (typeof timeoutMs === `undefined`) throw new Error(`timeout is undefined`); if (timeoutMs < 0) throw new Error(`Timeout is less than zero`); if (timeoutMs === 0) return yield* delayAnimationLoop(); let resolve2; let p = new Promise((r) => resolve2 = r); let timer; const callback = () => { if (resolve2) resolve2(); p = new Promise((r) => resolve2 = r); }; try { while (true) { timer = globalThis.setTimeout(callback, timeoutMs); const _ = await p; yield _; } } finally { if (resolve2) resolve2(); if (timer !== void 0) globalThis.clearTimeout(timer); timer = void 0; } } // src/flow/DispatchList.ts var DispatchList = class { #handlers; #counter = 0; #id = Math.floor(Math.random() * 100); constructor() { this.#handlers = []; } /** * Returns _true_ if list is empty * @returns */ isEmpty() { return this.#handlers.length === 0; } /** * Adds a handler * @param handler * @param options * @returns */ add(handler, options = {}) { this.#counter++; const once2 = options.once ?? false; const wrap = { id: `${this.#id} - ${this.#counter}`, handler, once: once2 }; this.#handlers.push(wrap); return wrap.id; } remove(id) { const length = this.#handlers.length; this.#handlers = this.#handlers.filter((handler) => handler.id !== id); return this.#handlers.length !== length; } notify(value) { for (const handler of this.#handlers) { handler.handler(value); if (handler.once) { this.remove(handler.id); } } } clear() { this.#handlers = []; } }; // src/flow/Every.ts var everyNth = (nth, callback) => { throwIntegerTest(nth, `positive`, `nth`); let counter = 0; return (data) => { counter++; if (counter === nth) { counter = 0; if (callback) callback(data); return true; } return false; }; }; // src/flow/PromiseFromEvent.ts var promiseFromEvent = (target, name) => { return new Promise((resolve2) => { const handler = (...args) => { target.removeEventListener(name, handler); if (Array.isArray(args) && args.length === 1) resolve2(args[0]); else resolve2(args); }; target.addEventListener(name, handler); }); }; // src/flow/PromiseWithResolvers.ts function promiseWithResolvers() { let resolve2; let reject; const promise = new Promise( (_resolve, _reject) => { resolve2 = _resolve; reject = _reject; } ); return { promise, resolve: resolve2, reject }; } // src/flow/RateMinimum.ts var rateMinimum = (options) => { let disposed = false; const t = timeout(() => { if (disposed) return; t.start(); options.whatToCall(options.fallback()); }, options.interval); if (options.abort) { options.abort.addEventListener(`abort`, (_) => { disposed = true; t.cancel(); }); } t.start(); return (args) => { if (disposed) throw new Error(`AbortSignal has been fired`); t.start(); options.whatToCall(args); }; }; // src/rx/Util.ts function messageIsSignal(message) { if (message.value !== void 0) return false; if (`signal` in message && message.signal !== void 0) return true; return false; } function messageIsDoneSignal(message) { if (message.value !== void 0) return false; if (`signal` in message && message.signal === `done`) return true; return false; } function messageHasValue(v) { if (v.value !== void 0) return true; return false; } var isPingable = (rx) => { if (!isReactive(rx)) return false; if (`ping` in rx) { return true; } return false; }; var hasLast = (rx) => { if (!isReactive(rx)) return false; if (`last` in rx) { const v = rx.last(); if (v !== void 0) return true; } return false; }; var isReactive = (rx) => { if (typeof rx !== `object`) return false; if (rx === null) return false; return `on` in rx && `onValue` in rx; }; var isWritable = (rx) => { if (!isReactive(rx)) return false; if (`set` in rx) return true; return false; }; var isWrapped = (v) => { if (typeof v !== `object`) return false; if (!(`source` in v)) return false; if (!(`annotate` in v)) return false; return true; }; var opify = (fn, ...args) => { return (source) => { return fn(source, ...args); }; }; var isTriggerValue = (t) => `value` in t; var isTriggerFunction = (t) => `fn` in t; var isTriggerGenerator = (t) => isIterable(t); var isTrigger = (t) => { if (typeof t !== `object`) return false; if (isTriggerValue(t)) return true; if (isTriggerFunction(t)) return true; if (isTriggerGenerator(t)) return true; return false; }; function resolveTriggerValue(t) { if (isTriggerValue(t)) return [t.value, false]; if (isTriggerFunction(t)) { const v = t.fn(); if (v === void 0) return [void 0, true]; return [v, false]; } if (isTriggerGenerator(t)) { const v = t.gen.next(); if (v.done) return [void 0, true]; return [v.value, false]; } throw new Error(`Invalid trigger. Missing 'value' or 'fn' fields`); } // src/data/Resolve.ts async function resolve(r, ...args) { if (typeof r === `object`) { if (`next` in r) { const tag = r[Symbol.toStringTag]; if (tag === `Generator` || tag == `Array Iterator`) { const v = r.next(); if (`done` in v && `value` in v) return v.value; return v; } else if (tag === `AsyncGenerator`) { const v = await r.next(); if (`done` in v && `value` in v) return v.value; return v; } else { throw new Error(`Object has 'next' prop, but does not have 'AsyncGenerator', 'Generator' or 'Array Iterator' string tag symbol. Got: '${tag}'`); } } else if (isReactive(r)) { if (hasLast(r)) return r.last(); throw new Error(`Reactive does not have last value`); } else { return r; } } else if (typeof r === `function`) { const v = await r(args); return v; } else { return r; } } function resolveSync(r, ...args) { if (typeof r === `object`) { if (`next` in r) { const tag = r[Symbol.toStringTag]; if (tag === `Generator` || tag == `Array Iterator`) { const v = r.next(); if (`done` in v && `value` in v) return v.value; return v; } else if (tag === `AsyncGenerator`) { throw new Error(`resolveSync cannot work with an async generator`); } else { throw new Error(`Object has 'next' prop, but does not have 'Generator' or 'Array Iterator' string tag symbol. Got: '${tag}'`); } } else if (isReactive(r)) { if (hasLast(r)) return r.last(); throw new Error(`Reactive does not have last value`); } else { return r; } } else if (typeof r === `function`) { return r(args); } else { return r; } } async function resolveWithFallback(p, fallback, ...args) { let errored = false; let fallbackValue = fallback.value; const overrideWithLast = fallback.overrideWithLast ?? false; if (fallbackValue === void 0) throw new Error(`Needs a fallback value`); try { const r = await resolve(p, ...args); if (typeof r === `undefined`) return fallbackValue; if (typeof r === `number` && Number.isNaN(r)) return fallbackValue; if (overrideWithLast) fallbackValue = r; return r; } catch (error) { if (!errored) { errored = true; console.warn(`resolveWithFallback swallowed an error. Additional errors not reported.`, getErrorMessage(error)); } return fallbackValue; } } function resolveWithFallbackSync(p, fallback, ...args) { let errored = false; let fallbackValue = fallback.value; const overrideWithLast = fallback.overrideWithLast ?? false; if (fallbackValue === void 0) throw new Error(`Needs a fallback value`); try { const r = resolveSync(p, ...args); if (typeof r === `undefined`) return fallbackValue; if (typeof r === `number` && Number.isNaN(r)) return fallbackValue; if (overrideWithLast) fallbackValue = r; return r; } catch (error) { if (!errored) { errored = true; console.warn(`resolveWithFallbackSync swallowed an error. Additional errors not reported.`, getErrorMessage(error)); } return fallbackValue; } } // src/flow/Repeat.ts async function* repeat(produce, opts) { const signal = opts.signal ?? void 0; const delayWhen = opts.delayWhen ?? `before`; const count = opts.count ?? void 0; const allowUndefined = opts.allowUndefined ?? false; const minIntervalMs = opts.delayMinimum ? intervalToMs(opts.delayMinimum) : void 0; const whileFunc = opts.while; let cancelled = false; let sleepMs = intervalToMs(opts.delay, intervalToMs(opts.delayMinimum, 0)); let started = performance.now(); const doDelay = async () => { const elapsed = performance.now() - started; if (typeof minIntervalMs !== `undefined`) { sleepMs = Math.max(0, minIntervalMs - elapsed); } if (sleepMs) { await sleep({ millis: sleepMs, signal }); } started = performance.now(); if (signal?.aborted) throw new Error(`Signal aborted ${signal.reason}`); }; if (Array.isArray(produce)) produce = produce.values(); if (opts.onStart) opts.onStart(); let errored = true; let loopedTimes = 0; try { while (!cancelled) { loopedTimes++; if (delayWhen === `before` || delayWhen === `both`) await doDelay(); const result = await resolve(produce); if (typeof result === `undefined` && !allowUndefined) { cancelled = true; } else { yield result; if (delayWhen === `after` || delayWhen === `both`) await doDelay(); if (count !== void 0 && loopedTimes >= count) cancelled = true; } if (whileFunc) { if (!whileFunc(loopedTimes)) { cancelled = true; } } } errored = false; } finally { cancelled = true; if (opts.onComplete) opts.onComplete(errored); } } function* repeatSync(produce, opts) { const signal = opts.signal ?? void 0; const count = opts.count ?? void 0; const allowUndefined = opts.allowUndefined ?? false; let cancelled = false; if (Array.isArray(produce)) produce = produce.values(); if (opts.onStart) opts.onStart(); let errored = true; let loopedTimes = 0; try { while (!cancelled) { loopedTimes++; const result = resolveSync(produce); if (typeof result === `undefined` && !allowUndefined) { cancelled = true; } else { yield result; if (count !== void 0 && loopedTimes >= count) cancelled = true; if (signal?.aborted) cancelled = true; } } errored = false; } finally { cancelled = true; if (opts.onComplete) opts.onComplete(errored); } } // src/flow/RequestResponseMatch.ts var RequestResponseMatch = class extends SimpleEventEmitter { #outgoing = /* @__PURE__ */ new Map(); #maintainLoop; constructor(options = {}) { super(); if (typeof window === `undefined`) { globalThis.window = { setTimeout, clearTimeout }; } this.timeoutMs = options.timeoutMs ?? 1e3; this.whenUnmatchedResponse = options.whenUnmatchedResponse ?? `throw`; this.#maintainLoop = continuously(() => this.#maintain(), this.timeoutMs * 2); if (options.key) { if (options.keyRequest) throw new Error(`Cannot set 'keyRequest' when 'key' is set `); if (options.keyResponse) throw new Error(`Cannot set 'keyResponse' when 'key' is set `); this.keyRequest = options.key; this.keyResponse = options.key; } else { if (!options.keyRequest || !options.keyResponse) { throw new Error(`Expects 'keyRequest' & 'keyResponse' fields to be set if 'key' is not set`); } this.keyRequest = options.keyRequest; this.keyResponse = options.keyResponse; } } #maintain() { const values = [...this.#outgoing.values()]; const now = Date.now(); for (const v of values) { if (v.expiresAt <= now) { if (v.promiseReject) { v.promiseReject(`Request timeout`); } const callback = v.callback; if (callback) { setTimeout(() => { callback(true, `Request timeout`); }, 1); } this.fireEvent(`completed`, { request: v.req, response: `Request timeout`, success: false }); this.#outgoing.delete(v.id); } } this.debugDump(); return this.#outgoing.size > 0; } debugDump() { const values = [...this.#outgoing.values()]; const now = Date.now(); for (const v of values) { const expire = now - v.expiresAt; console.log(`${v.id} Expires in: ${Math.floor(expire / 1e3)}s`); } } /** * Makes a request. * If `callback` is set, it's equivalent to calling `requestCallback`. * If `callback` is not set, a promise is returned * @param request * @param callback * @returns */ // eslint-disable-next-line @typescript-eslint/no-invalid-void-type request(request, callback) { if (callback !== void 0) { this.#requestCallback(request, callback); return; } return this.#requestAwait(request); } /** * Make a request and don't wait for the outcome. * @param request */ requestAndForget(request) { const id = this.keyRequest(request); if (this.#outgoing.has(id)) throw new Error(`Already a request pending with id '${id}'`); const r = { expiresAt: Date.now() + this.timeoutMs, id, req: request }; this.#outgoing.set(id, r); this.#maintainLoop.start(); } /** * Make a request, returning a Promise for the outcome. * Errors will throw an exception. * @param request * @returns */ #requestAwait(request) { const id = this.keyRequest(request); if (this.#outgoing.has(id)) throw new Error(`Already a request pending with id '${id}'`); const p = new Promise((resolve2, reject) => { const r = { expiresAt: Date.now() + this.timeoutMs, id, req: request, promiseResolve: resolve2, promiseReject: reject }; this.#outgoing.set(id, r); this.#maintainLoop.start(); }); return p; } /** * Make a request, and get notified of outcome with a callback * @param request * @param callback */ #requestCallback(request, callback) { const id = this.keyRequest(request); if (this.#outgoing.has(id)) throw new Error(`Already a request pending with id '${id}'`); const r = { expiresAt: Date.now() + this.timeoutMs, id, req: request, callback }; this.#outgoing.set(id, r); this.#maintainLoop.start(); } /** * Response has been received * @param response Response * @returns _True_ if response matched a request */ response(response, keepAlive) { const id = this.keyResponse(response); const request = this.#outgoing.get(id); if (!request) { if (this.whenUnmatchedResponse === `throw`) throw new Error(`Unmatched response with id: '${id}'`, { cause: response }); return false; } if (keepAlive) { request.expiresAt = Date.now() + this.timeoutMs; } else { this.#outgoing.delete(id); } if (request.promiseResolve) { request.promiseResolve(response); } if (request.callback) { request.callback(false, response); } this.fireEvent(`match`, { request: request.req, response }); if (!keepAlive) { this.fireEvent(`completed`, { request: request.req, response, success: true }); } return true; } }; // src/flow/Retry.ts function* backoffGenerator(options = {}) { const startAt = options.startAt ?? 1; let limitAttempts = options.limitAttempts ?? Number.MAX_SAFE_INTEGER; const limitValue = options.limitValue; const power = options.power ?? 1.1; let value = startAt; throwIntegerTest(limitAttempts, `aboveZero`, `limitAttempts`); throwNumberTest(startAt, ``, `startAt`); throwNumberTest(limitAttempts, ``, `limitAttempts`); if (limitValue !== void 0) throwNumberTest(limitValue, ``, `limitValue`); throwNumberTest(power, ``, `power`); while (limitAttempts > 0) { if (limitValue && value >= limitValue) return; limitAttempts--; yield value; value += Math.pow(value, power); } } var retryFunction = (callback, options = {}) => { const task = { async probe() { try { const v = await callback(); if (v === void 0) return { value: options.taskValueFallback, error: `Fallback`, success: false }; return { value: v, success: true }; } catch (error) { return { success: false, error }; } } }; return retryTask(task, options); }; var retryTask = async (task, opts = {}) => { const signal = opts.abort; const log = resolveLogOption(opts.log); const predelayMs = opts.predelayMs ?? 0; const startedAt = since(); let attempts = 0; const initialValue = opts.startAt ?? 1e3; const limitAttempts = opts.limitAttempts ?? Number.MAX_SAFE_INTEGER; const backoffGen = backoffGenerator({ ...opts, startAt: initialValue, limitAttempts }); if (initialValue <= 0) throw new Error(`Param 'initialValue' must be above zero`); if (predelayMs > 0) { try { await sleep({ millis: predelayMs, signal }); } catch (error) { return { success: false, attempts, value: opts.taskValueFallback, elapsed: startedAt(), message: getErrorMessage(error) }; } } for (const t of backoffGen) { attempts++; const result = await task.probe(attempts); if (result.success) { return { success: result.success, value: result.value, attempts, elapsed: startedAt() }; } log({ msg: `retry attempts: ${attempts} t: ${elapsedToHumanString(t)}` }); if (attempts >= limitAttempts) { break; } try { await sleep({ millis: t, signal }); } catch (error) { return { success: false, attempts, value: opts.taskValueFallback, message: getErrorMessage(error), elapsed: startedAt() }; } } return { message: `Giving up after ${attempts} attempts.`, success: false, attempts, value: opts.taskValueFallback, elapsed: startedAt() }; }; // src/flow/RunOnce.ts var runOnce = (onRun) => { let run2 = false; let success = false; return () => { if (run2) return success; run2 = true; success = onRun(); return success; }; }; // src/flow/SyncWait.ts var SyncWait = class { #resolve; #reject; #promise; signal() { if (this.#resolve) { this.#resolve(); this.#resolve = void 0; } this.#promise = Promise.resolve(); } /** * Throw away any previous signalled state. * This will cause any currently waiters to throw */ flush() { if (this.#reject) { this.#reject(`Flushed`); this.#reject = void 0; } this.#resolve = void 0; this.#promise = void 0; } #initPromise() { const p = new Promise((resolve2, reject) => { this.#resolve = resolve2; this.#reject = reject; }); this.#promise = p; return p; } /** * Call with `await` to wait until .signal() happens. * If a wait period is specified, an exception is thrown if signal does not happen within this time. * @param maximumWaitMs */ async forSignal(maximumWaitMs) { let p = this.#promise; if (!p) p = this.#initPromise(); if (maximumWaitMs) { const reject = this.#reject; setTimeout(() => { if (reject) { reject(`Timeout elapsed ${maximumWaitMs}`); } }, maximumWaitMs); } await p; this.#promise = void 0; this.#resolve = void 0; this.#reject = void 0; } /** * An alternative to {@link forSignal}, returning _true_ * if signalled, or _false_ if wait period was exceeded * * ```js * const s = await sw.didSignal(5000); * ``` * @param maximumWaitMs * @returns */ async didSignal(maximumWaitMs) { try { await this.forSignal(maximumWaitMs); return true; } catch { return false; } } }; // src/flow/TaskQueueMutable.ts var TaskQueueMutable = class _TaskQueueMutable extends SimpleEventEmitter { static { this.shared = new _TaskQueueMutable(); } constructor() { super(); this._queue = mutable(); this._loop = continuously(() => { return this.processQueue(); }, 100); } /** * Adds a task. This triggers processing loop if not already started. * * ```js * queue.add(async () => { * await sleep(1000); * }); * ``` * @param task Task to run */ enqueue(task) { const length = this._queue.enqueue(task); if (this._loop.runState === `idle`) { this.fireEvent(`started`, {}); this._loop.start(); } return length; } dequeue() { return this._queue.dequeue(); } async processQueue() { const task = this._queue.dequeue(); if (task === void 0) { this.fireEvent(`empty`, {}); return false; } try { await task(); } catch (error) { console.error(error); } } /** * Clears all tasks, and stops any scheduled processing. * Currently running tasks will continue. * @returns */ clear() { if (this._queue.length === 0) return; this._queue.clear(); this._loop.cancel(); this.fireEvent(`empty`, {}); } /** * Returns true if queue is empty */ get isEmpty() { return this._queue.isEmpty; } /** * Number of items in queue */ get length() { return this._queue.length; } }; // src/flow/Throttle.ts var throttle = (callback, intervalMinMs) => { let trigger = 0; return async (...args) => { const elapsed = performance.now() - trigger; if (elapsed >= intervalMinMs) { const r = callback(elapsed, ...args); if (typeof r === `object`) await r; trigger = performance.now(); } }; }; // src/flow/UpdateOutdated.ts var updateOutdated = (fn, interval2, updateFail = `slow`) => { let lastRun = 0; let lastValue; let intervalMsCurrent = intervalToMs(interval2, 1e3); return () => ( // eslint-disable-next-line @typescript-eslint/no-misused-promises new Promise(async (resolve2, reject) => { const elapsed = performance.now() - lastRun; if (lastValue === void 0 || elapsed > intervalMsCurrent) { try { lastRun = performance.now(); lastValue = await fn(elapsed); intervalMsCurrent = intervalToMs(interval2, 1e3); } catch (error) { if (updateFail === `fast`) { lastValue = void 0; lastRun = 0; } else if (updateFail === `backoff`) { intervalMsCurrent = Math.floor(intervalMsCurrent * 1.2); } reject(error); return; } } resolve2(lastValue); }) ); }; // src/flow/WaitFor.ts var waitFor = (timeoutMs, onAborted, onComplete) => { let t; let success = false; const done = (error) => { if (t !== void 0) { window.clearTimeout(t); t = void 0; } if (error) { onAborted(error); } else { success = true; } if (onComplete !== void 0) onComplete(success); }; t = globalThis.setTimeout(() => { t = void 0; try { onAborted(`Timeout after ${timeoutMs}ms`); } finally { if (onComplete !== void 0) onComplete(success); } }, timeoutMs); return done; }; // src/flow/WaitForValue.ts var WaitForValue = class { #promise; #resolve; #written = false; constructor() { const { promise, resolve: resolve2 } = promiseWithResolvers(); this.#promise = promise; this.#resolve = resolve2; } get() { return this.#promise; } add(value) { if (this.#written) throw new Error(`QueueSingleUse has already been used`); this.#written = true; this.#resolve(value); } /** * Returns _true_ if a value has been added * and therefore no more values can be written */ get isUsed() { return this.#written; } }; var singleItem = () => new WaitForValue(); export { messageIsSignal, messageIsDoneSignal, messageHasValue, isPingable, hasLast, isReactive, isWritable, isWrapped, opify, isTriggerValue, isTriggerFunction, isTriggerGenerator, isTrigger, resolveTriggerValue, DispatchList, continuously, sleep, sleepWhile, BehaviourTree_exports, timeout, debounce, delay, delayLoop, everyNth, promiseFromEvent, promiseWithResolvers, rateMinimum, resolve, resolveSync, resolveWithFallback, resolveWithFallbackSync, repeat, repeatSync, RequestResponseMatch, backoffGenerator, retryFunction, retryTask, runOnce, SyncWait, TaskQueueMutable, throttle, updateOutdated, waitFor, WaitForValue, singleItem, flow_exports }; //# sourceMappingURL=chunk-3PQXJLTZ.js.map