···8899/**
1010 * Create an abort signal tied to a timeout.
1111- * Replaces `AbortSignal.timeout`, which doesn't consistently abort with a TimeoutError cross env.
1111+ * Replaces `AbortSignal.timeout`, which doesn't consistently abort with a `TimeoutError` cross env.
1212 *
1313 * @param {number} ms - timeout in milliseconds
1414 * @returns {TimeoutSignal} the timeout signal
+28
src/common/async/sleep.js
···11+/** @module common/async */
22+33+/**
44+ * @param {number} ms the number of ms to sleep
55+ * @param {AbortSignal} [signal] an aptional abort signal, to cancel the sleep
66+ * @returns {Promise<void>}
77+ * a promise that resolves after given amount of time, and is interruptable with an abort signal.
88+ */
99+export function sleep(ms, signal) {
1010+ signal?.throwIfAborted()
1111+1212+ const { resolve, reject, promise } = Promise.withResolvers()
1313+ const timeout = setTimeout(resolve, ms)
1414+1515+ if (signal) {
1616+ const abortHandler = () => {
1717+ clearTimeout(timeout)
1818+ reject(signal.reason)
1919+ }
2020+2121+ signal.addEventListener('abort', abortHandler)
2222+ promise.finally(() => {
2323+ signal.removeEventListener('abort', abortHandler)
2424+ })
2525+ }
2626+2727+ return promise
2828+}
+45
src/common/strict-map.js
···11+/** @module common */
22+33+/**
44+ * A map with methods to ensure key presence and safe update.
55+ *
66+ * @template K, V
77+ * @augments {Map<K, V>}
88+ */
99+export class StrictMap extends Map {
1010+1111+ /**
1212+ * @param {K} key to lookup in the map, throwing is missing
1313+ * @returns {V} the value from the map
1414+ * @throws {Error} if the key is not present in the map
1515+ */
1616+ require(key) {
1717+ if (!this.has(key)) throw Error(`key is required but not in the map`)
1818+1919+ const value = /** @type {V} */ (this.get(key))
2020+ return value
2121+ }
2222+2323+ /**
2424+ * @param {K} key to lookup in the map
2525+ * @param {function(): V} maker a callback which will create the value in the map if not present.
2626+ * @returns {V} the value from the map, possibly newly created by {maker}
2727+ */
2828+ ensure(key, maker) {
2929+ if (!this.has(key)) {
3030+ this.set(key, maker())
3131+ }
3232+3333+ return /** @type {V} */ (this.get(key))
3434+ }
3535+3636+ /**
3737+ * @param {K} key to update in the map
3838+ * @param {function(V=): V} update function which returns the new value for the map
3939+ */
4040+ update(key, update) {
4141+ const current = this.get(key)
4242+ this.set(key, update(current))
4343+ }
4444+4545+}