1 | import { produce as createNextState, isDraftable } from 'immer'
|
2 | import type { Middleware, StoreEnhancer } from 'redux'
|
3 |
|
4 | export 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.
|
19 | 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:
|
20 | It is disabled in production builds, so you don't need to worry about that.`)
|
21 | }
|
22 | },
|
23 | }
|
24 | }
|
25 |
|
26 | export function delay(ms: number) {
|
27 | return new Promise((resolve) => setTimeout(resolve, ms))
|
28 | }
|
29 |
|
30 | export 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 |
|
43 | export 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 |
|
87 | export function freezeDraftable<T>(val: T) {
|
88 | return isDraftable(val) ? createNextState(val, () => {}) : val
|
89 | }
|
90 |
|
91 | interface 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 |
|
102 | interface 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 |
|
113 | export function emplace<K, V>(
|
114 | map: Map<K, V>,
|
115 | key: K,
|
116 | handler: MapEmplaceHandler<K, V>,
|
117 | ): V
|
118 | export 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 | */
|
150 | export 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 |