import { produce as createNextState, isDraftable } from 'immer' import type { Middleware, StoreEnhancer } from 'redux' export function getTimeMeasureUtils(maxDelay: number, fnName: string) { let elapsed = 0 return { measureTime(fn: () => T): T { const started = Date.now() try { return fn() } finally { const finished = Date.now() elapsed += finished - started } }, warnIfExceeded() { if (elapsed > maxDelay) { console.warn(`${fnName} took ${elapsed}ms, which is more than the warning threshold of ${maxDelay}ms. If your state or actions are very large, you may want to disable the middleware as it might cause too much of a slowdown in development mode. See https://redux-toolkit.js.org/api/getDefaultMiddleware for instructions. It is disabled in production builds, so you don't need to worry about that.`) } }, } } export function delay(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)) } export function find( iterable: Iterable, comparator: (item: T) => boolean, ): T | undefined { for (const entry of iterable) { if (comparator(entry)) { return entry } } return undefined } export class Tuple = []> extends Array< Items[number] > { constructor(length: number) constructor(...items: Items) constructor(...items: any[]) { super(...items) Object.setPrototypeOf(this, Tuple.prototype) } static get [Symbol.species]() { return Tuple as any } concat>( items: Tuple, ): Tuple<[...Items, ...AdditionalItems]> concat>( items: AdditionalItems, ): Tuple<[...Items, ...AdditionalItems]> concat>( ...items: AdditionalItems ): Tuple<[...Items, ...AdditionalItems]> concat(...arr: any[]) { return super.concat.apply(this, arr) } prepend>( items: Tuple, ): Tuple<[...AdditionalItems, ...Items]> prepend>( items: AdditionalItems, ): Tuple<[...AdditionalItems, ...Items]> prepend>( ...items: AdditionalItems ): Tuple<[...AdditionalItems, ...Items]> prepend(...arr: any[]) { if (arr.length === 1 && Array.isArray(arr[0])) { return new Tuple(...arr[0].concat(this)) } return new Tuple(...arr.concat(this)) } } export function freezeDraftable(val: T) { return isDraftable(val) ? createNextState(val, () => {}) : val } interface WeakMapEmplaceHandler { /** * Will be called to get value, if no value is currently in map. */ insert?(key: K, map: WeakMap): V /** * Will be called to update a value, if one exists already. */ update?(previous: V, key: K, map: WeakMap): V } interface MapEmplaceHandler { /** * Will be called to get value, if no value is currently in map. */ insert?(key: K, map: Map): V /** * Will be called to update a value, if one exists already. */ update?(previous: V, key: K, map: Map): V } export function emplace( map: Map, key: K, handler: MapEmplaceHandler, ): V export function emplace( map: WeakMap, key: K, handler: WeakMapEmplaceHandler, ): V /** * Allow inserting a new value, or updating an existing one * @throws if called for a key with no current value and no `insert` handler is provided * @returns current value in map (after insertion/updating) * ```ts * // return current value if already in map, otherwise initialise to 0 and return that * const num = emplace(map, key, { * insert: () => 0 * }) * * // increase current value by one if already in map, otherwise initialise to 0 * const num = emplace(map, key, { * update: (n) => n + 1, * insert: () => 0, * }) * * // only update if value's already in the map - and increase it by one * if (map.has(key)) { * const num = emplace(map, key, { * update: (n) => n + 1, * }) * } * ``` * * @remarks * Based on https://github.com/tc39/proposal-upsert currently in Stage 2 - maybe in a few years we'll be able to replace this with direct method calls */ export function emplace( map: WeakMap, key: K, handler: WeakMapEmplaceHandler, ): V { if (map.has(key)) { let value = map.get(key) as V if (handler.update) { value = handler.update(value, key, map) map.set(key, value) } return value } if (!handler.insert) throw new Error('No insert provided for key not already in map') const inserted = handler.insert(key, map) map.set(key, inserted) return inserted }