UNPKG

5.09 kBPlain TextView Raw
1import { produce as createNextState, isDraftable } from 'immer'
2import type { Middleware, StoreEnhancer } from 'redux'
3
4export function getTimeMeasureUtils(maxDelay: number, fnName: string) {
5 let elapsed = 0
6 return {
7 measureTime<T>(fn: () => T): T {
8 const started = Date.now()
9 try {
10 return fn()
11 } finally {
12 const finished = Date.now()
13 elapsed += finished - started
14 }
15 },
16 warnIfExceeded() {
17 if (elapsed > maxDelay) {
18 console.warn(`${fnName} took ${elapsed}ms, which is more than the warning threshold of ${maxDelay}ms.
19If 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.
20It is disabled in production builds, so you don't need to worry about that.`)
21 }
22 },
23 }
24}
25
26export function delay(ms: number) {
27 return new Promise((resolve) => setTimeout(resolve, ms))
28}
29
30export function find<T>(
31 iterable: Iterable<T>,
32 comparator: (item: T) => boolean,
33): T | undefined {
34 for (const entry of iterable) {
35 if (comparator(entry)) {
36 return entry
37 }
38 }
39
40 return undefined
41}
42
43export class Tuple<Items extends ReadonlyArray<unknown> = []> extends Array<
44 Items[number]
45> {
46 constructor(length: number)
47 constructor(...items: Items)
48 constructor(...items: any[]) {
49 super(...items)
50 Object.setPrototypeOf(this, Tuple.prototype)
51 }
52
53 static get [Symbol.species]() {
54 return Tuple as any
55 }
56
57 concat<AdditionalItems extends ReadonlyArray<unknown>>(
58 items: Tuple<AdditionalItems>,
59 ): Tuple<[...Items, ...AdditionalItems]>
60 concat<AdditionalItems extends ReadonlyArray<unknown>>(
61 items: AdditionalItems,
62 ): Tuple<[...Items, ...AdditionalItems]>
63 concat<AdditionalItems extends ReadonlyArray<unknown>>(
64 ...items: AdditionalItems
65 ): Tuple<[...Items, ...AdditionalItems]>
66 concat(...arr: any[]) {
67 return super.concat.apply(this, arr)
68 }
69
70 prepend<AdditionalItems extends ReadonlyArray<unknown>>(
71 items: Tuple<AdditionalItems>,
72 ): Tuple<[...AdditionalItems, ...Items]>
73 prepend<AdditionalItems extends ReadonlyArray<unknown>>(
74 items: AdditionalItems,
75 ): Tuple<[...AdditionalItems, ...Items]>
76 prepend<AdditionalItems extends ReadonlyArray<unknown>>(
77 ...items: AdditionalItems
78 ): Tuple<[...AdditionalItems, ...Items]>
79 prepend(...arr: any[]) {
80 if (arr.length === 1 && Array.isArray(arr[0])) {
81 return new Tuple(...arr[0].concat(this))
82 }
83 return new Tuple(...arr.concat(this))
84 }
85}
86
87export function freezeDraftable<T>(val: T) {
88 return isDraftable(val) ? createNextState(val, () => {}) : val
89}
90
91interface WeakMapEmplaceHandler<K extends object, V> {
92 /**
93 * Will be called to get value, if no value is currently in map.
94 */
95 insert?(key: K, map: WeakMap<K, V>): V
96 /**
97 * Will be called to update a value, if one exists already.
98 */
99 update?(previous: V, key: K, map: WeakMap<K, V>): V
100}
101
102interface MapEmplaceHandler<K, V> {
103 /**
104 * Will be called to get value, if no value is currently in map.
105 */
106 insert?(key: K, map: Map<K, V>): V
107 /**
108 * Will be called to update a value, if one exists already.
109 */
110 update?(previous: V, key: K, map: Map<K, V>): V
111}
112
113export function emplace<K, V>(
114 map: Map<K, V>,
115 key: K,
116 handler: MapEmplaceHandler<K, V>,
117): V
118export function emplace<K extends object, V>(
119 map: WeakMap<K, V>,
120 key: K,
121 handler: WeakMapEmplaceHandler<K, V>,
122): V
123/**
124 * Allow inserting a new value, or updating an existing one
125 * @throws if called for a key with no current value and no `insert` handler is provided
126 * @returns current value in map (after insertion/updating)
127 * ```ts
128 * // return current value if already in map, otherwise initialise to 0 and return that
129 * const num = emplace(map, key, {
130 * insert: () => 0
131 * })
132 *
133 * // increase current value by one if already in map, otherwise initialise to 0
134 * const num = emplace(map, key, {
135 * update: (n) => n + 1,
136 * insert: () => 0,
137 * })
138 *
139 * // only update if value's already in the map - and increase it by one
140 * if (map.has(key)) {
141 * const num = emplace(map, key, {
142 * update: (n) => n + 1,
143 * })
144 * }
145 * ```
146 *
147 * @remarks
148 * 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
149 */
150export function emplace<K extends object, V>(
151 map: WeakMap<K, V>,
152 key: K,
153 handler: WeakMapEmplaceHandler<K, V>,
154): V {
155 if (map.has(key)) {
156 let value = map.get(key) as V
157 if (handler.update) {
158 value = handler.update(value, key, map)
159 map.set(key, value)
160 }
161 return value
162 }
163 if (!handler.insert)
164 throw new Error('No insert provided for key not already in map')
165 const inserted = handler.insert(key, map)
166 map.set(key, inserted)
167 return inserted
168}
169
\No newline at end of file