@rimbu/common 0.12.3 → 1.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -4
- package/{src/async-optlazy.ts → dist/bun/async-optlazy.mts} +1 -1
- package/{src/async-reducer.ts → dist/bun/async-reducer.mts} +17 -8
- package/{src/collect.ts → dist/bun/collect.mts} +1 -1
- package/{src/comp.ts → dist/bun/comp.mts} +1 -1
- package/{src/index-range.ts → dist/bun/index-range.mts} +1 -1
- package/{src/index.ts → dist/bun/index.mts} +1 -1
- package/dist/bun/internal.mts +13 -0
- package/{src/reducer.ts → dist/bun/reducer.mts} +5 -3
- package/dist/cjs/async-optlazy.cjs +44 -0
- package/dist/cjs/async-reducer.cjs +1621 -0
- package/dist/cjs/collect.cjs +33 -0
- package/dist/cjs/comp.cjs +1621 -0
- package/dist/cjs/eq.cjs +210 -0
- package/dist/cjs/err.cjs +55 -0
- package/dist/cjs/index-range.cjs +114 -0
- package/dist/cjs/index.cjs +1685 -0
- package/dist/cjs/internal.cjs +1685 -0
- package/dist/cjs/optlazy.cjs +41 -0
- package/dist/cjs/range.cjs +52 -0
- package/dist/cjs/reducer.cjs +1621 -0
- package/dist/cjs/traverse-state.cjs +49 -0
- package/dist/cjs/types.cjs +18 -0
- package/dist/cjs/update.cjs +35 -0
- package/dist/{module/async-optlazy.js → esm/async-optlazy.mjs} +5 -8
- package/dist/esm/async-optlazy.mjs.map +1 -0
- package/dist/{module/async-reducer.js → esm/async-reducer.mjs} +67 -69
- package/dist/esm/async-reducer.mjs.map +1 -0
- package/dist/{module/collect.js → esm/collect.mjs} +1 -1
- package/dist/esm/collect.mjs.map +1 -0
- package/dist/{module/comp.js → esm/comp.mjs} +2 -2
- package/dist/esm/comp.mjs.map +1 -0
- package/dist/{module/eq.js → esm/eq.mjs} +1 -1
- package/dist/esm/eq.mjs.map +1 -0
- package/dist/{module/err.js → esm/err.mjs} +1 -1
- package/dist/esm/err.mjs.map +1 -0
- package/dist/{module/index-range.js → esm/index-range.mjs} +2 -3
- package/dist/esm/index-range.mjs.map +1 -0
- package/dist/esm/index.mjs +7 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/internal.mjs +14 -0
- package/dist/esm/internal.mjs.map +1 -0
- package/dist/{module/optlazy.js → esm/optlazy.mjs} +1 -1
- package/dist/esm/optlazy.mjs.map +1 -0
- package/dist/{module/range.js → esm/range.mjs} +1 -1
- package/dist/esm/range.mjs.map +1 -0
- package/dist/{module/reducer.js → esm/reducer.mjs} +10 -8
- package/dist/esm/reducer.mjs.map +1 -0
- package/dist/{module/traverse-state.js → esm/traverse-state.mjs} +1 -1
- package/dist/esm/traverse-state.mjs.map +1 -0
- package/dist/esm/types.mjs +2 -0
- package/dist/esm/types.mjs.map +1 -0
- package/dist/{module/update.js → esm/update.mjs} +1 -1
- package/dist/esm/update.mjs.map +1 -0
- package/dist/types/{async-optlazy.d.ts → async-optlazy.d.mts} +1 -1
- package/dist/types/{async-reducer.d.ts → async-reducer.d.mts} +2 -2
- package/dist/types/{collect.d.ts → collect.d.mts} +1 -1
- package/dist/types/{comp.d.ts → comp.d.mts} +1 -1
- package/dist/types/{index-range.d.ts → index-range.d.mts} +1 -1
- package/dist/types/{index.d.ts → index.d.mts} +1 -1
- package/dist/types/internal.d.mts +13 -0
- package/dist/types/{reducer.d.ts → reducer.d.mts} +1 -1
- package/package.json +27 -21
- package/src/async-optlazy.mts +49 -0
- package/src/async-reducer.mts +1157 -0
- package/src/collect.mts +43 -0
- package/src/comp.mts +625 -0
- package/src/eq.mts +406 -0
- package/src/err.mts +43 -0
- package/src/index-range.mts +120 -0
- package/{dist/module/index.js → src/index.mts} +2 -2
- package/src/internal.mts +13 -0
- package/{dist/main/optlazy.js → src/optlazy.mts} +24 -14
- package/src/range.mts +58 -0
- package/src/reducer.mts +1025 -0
- package/src/traverse-state.mts +59 -0
- package/src/types.mts +40 -0
- package/{dist/main/update.js → src/update.mts} +10 -10
- package/dist/main/async-optlazy.js +0 -48
- package/dist/main/async-optlazy.js.map +0 -1
- package/dist/main/async-reducer.js +0 -872
- package/dist/main/async-reducer.js.map +0 -1
- package/dist/main/collect.js +0 -11
- package/dist/main/collect.js.map +0 -1
- package/dist/main/comp.js +0 -542
- package/dist/main/comp.js.map +0 -1
- package/dist/main/eq.js +0 -382
- package/dist/main/eq.js.map +0 -1
- package/dist/main/err.js +0 -62
- package/dist/main/err.js.map +0 -1
- package/dist/main/index-range.js +0 -110
- package/dist/main/index-range.js.map +0 -1
- package/dist/main/index.js +0 -10
- package/dist/main/index.js.map +0 -1
- package/dist/main/internal.js +0 -17
- package/dist/main/internal.js.map +0 -1
- package/dist/main/optlazy.js.map +0 -1
- package/dist/main/range.js +0 -38
- package/dist/main/range.js.map +0 -1
- package/dist/main/reducer.js +0 -694
- package/dist/main/reducer.js.map +0 -1
- package/dist/main/traverse-state.js +0 -33
- package/dist/main/traverse-state.js.map +0 -1
- package/dist/main/types.js +0 -3
- package/dist/main/types.js.map +0 -1
- package/dist/main/update.js.map +0 -1
- package/dist/module/async-optlazy.js.map +0 -1
- package/dist/module/async-reducer.js.map +0 -1
- package/dist/module/collect.js.map +0 -1
- package/dist/module/comp.js.map +0 -1
- package/dist/module/eq.js.map +0 -1
- package/dist/module/err.js.map +0 -1
- package/dist/module/index-range.js.map +0 -1
- package/dist/module/index.js.map +0 -1
- package/dist/module/internal.js +0 -14
- package/dist/module/internal.js.map +0 -1
- package/dist/module/optlazy.js.map +0 -1
- package/dist/module/range.js.map +0 -1
- package/dist/module/reducer.js.map +0 -1
- package/dist/module/traverse-state.js.map +0 -1
- package/dist/module/types.js +0 -2
- package/dist/module/types.js.map +0 -1
- package/dist/module/update.js.map +0 -1
- package/dist/types/internal.d.ts +0 -13
- package/src/internal.ts +0 -13
- /package/{src/eq.ts → dist/bun/eq.mts} +0 -0
- /package/{src/err.ts → dist/bun/err.mts} +0 -0
- /package/{src/optlazy.ts → dist/bun/optlazy.mts} +0 -0
- /package/{src/range.ts → dist/bun/range.mts} +0 -0
- /package/{src/traverse-state.ts → dist/bun/traverse-state.mts} +0 -0
- /package/{src/types.ts → dist/bun/types.mts} +0 -0
- /package/{src/update.ts → dist/bun/update.mts} +0 -0
- /package/dist/types/{eq.d.ts → eq.d.mts} +0 -0
- /package/dist/types/{err.d.ts → err.d.mts} +0 -0
- /package/dist/types/{optlazy.d.ts → optlazy.d.mts} +0 -0
- /package/dist/types/{range.d.ts → range.d.mts} +0 -0
- /package/dist/types/{traverse-state.d.ts → traverse-state.d.mts} +0 -0
- /package/dist/types/{types.d.ts → types.d.mts} +0 -0
- /package/dist/types/{update.d.ts → update.d.mts} +0 -0
package/src/eq.mts
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A function returning true if given `v1` and `v2` should be considered equal.
|
|
3
|
+
*/
|
|
4
|
+
export type Eq<T> = (v1: T, v2: T) => boolean;
|
|
5
|
+
|
|
6
|
+
export namespace Eq {
|
|
7
|
+
export function convertAnyToString(value: any): string {
|
|
8
|
+
if (
|
|
9
|
+
typeof value !== 'object' ||
|
|
10
|
+
null === value ||
|
|
11
|
+
!('toString' in value) ||
|
|
12
|
+
typeof value.toString !== 'function' ||
|
|
13
|
+
value.toString !== Object.prototype.toString
|
|
14
|
+
) {
|
|
15
|
+
return String(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return JSON.stringify(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const _anyFlatEq: Eq<any> = createAnyEq('FLAT');
|
|
22
|
+
const _anyShallowEq: Eq<any> = createAnyEq('SHALLOW');
|
|
23
|
+
const _anyDeepEq: Eq<any> = createAnyEq('DEEP');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Returns the default Eq instance, which is the Eq.anyDeepEq() instance.
|
|
27
|
+
*/
|
|
28
|
+
export function defaultEq(): Eq<any> {
|
|
29
|
+
return _anyDeepEq;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* An Eq instance that uses `Object.is` to determine if two objects are equal.
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* const eq = Eq.objectIs
|
|
37
|
+
* console.log(eq(5, 5))
|
|
38
|
+
* // => true
|
|
39
|
+
* console.log(eq(5, 'a'))
|
|
40
|
+
* // => false
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export const objectIs: Eq<any> = Object.is;
|
|
44
|
+
|
|
45
|
+
const _valueOfEq: Eq<{ valueOf(): any }> = (v1, v2) =>
|
|
46
|
+
Object.is(v1.valueOf(), v2.valueOf());
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns an Eq instance for objects that have a `valueOf` method. It returns true if the `.valueOf` values of both given objects are equal.
|
|
50
|
+
* @typeparam T - the object type containing a valueOf function of type V
|
|
51
|
+
* @typeparam V - the valueOf result type
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* const eq = Eq.valueOfEq()
|
|
55
|
+
* console.log(eq(new Number(5), new Number(5)))
|
|
56
|
+
* // => true
|
|
57
|
+
* console.log(eq(new Number(5), new Number(3)))
|
|
58
|
+
* // => false
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export function valueOfEq<T extends { valueOf(): V }, V>(): Eq<T> {
|
|
62
|
+
return _valueOfEq;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Returns an Eq instance that compares Date objects according to their `valueOf` value.
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* const eq = Eq.dateEq()
|
|
70
|
+
* console.log(eq(new Date(2020, 1, 1), new Date(2020, 1, 1))
|
|
71
|
+
* // => true
|
|
72
|
+
* console.log(eq(new Date(2020, 1, 1), new Date(2020, 2, 1))
|
|
73
|
+
* // => false
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export function dateEq(): Eq<Date> {
|
|
77
|
+
return _valueOfEq;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function createIterableEq<T>(itemEq: Eq<T>): Eq<Iterable<T>> {
|
|
81
|
+
return (v1, v2) => {
|
|
82
|
+
if (Object.is(v1, v2)) return true;
|
|
83
|
+
|
|
84
|
+
const iter1 = v1[Symbol.iterator]();
|
|
85
|
+
const iter2 = v2[Symbol.iterator]();
|
|
86
|
+
|
|
87
|
+
while (true) {
|
|
88
|
+
const value1 = iter1.next();
|
|
89
|
+
const value2 = iter2.next();
|
|
90
|
+
|
|
91
|
+
if (value1.done || value2.done) return value1.done === value2.done;
|
|
92
|
+
|
|
93
|
+
if (!itemEq(value1.value, value2.value)) return false;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const _iterableAnyEq: Eq<Iterable<any>> = createIterableEq(defaultEq());
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Returns an Eq instance that compares Iterables by comparing their elements with the given `itemEq` Eq instance.
|
|
102
|
+
* @typeparam T - the Iterable element type
|
|
103
|
+
* @param itemEq - (optional) the Eq instance to use to compare the Iterable's elements
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* const eq = Eq.iterableEq();
|
|
107
|
+
* console.log(eq([1, 2, 3], [1, 2, 3])
|
|
108
|
+
* // => true
|
|
109
|
+
* console.log(eq([1, 2, 3], [1, 3, 2])
|
|
110
|
+
* // => false
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function iterableEq<T>(itemEq?: Eq<T>): Eq<Iterable<T>> {
|
|
114
|
+
if (undefined === itemEq) return _iterableAnyEq;
|
|
115
|
+
|
|
116
|
+
return createIterableEq(itemEq);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function createObjectEq(valueEq: Eq<any>): Eq<Record<any, any>> {
|
|
120
|
+
return (v1, v2) => {
|
|
121
|
+
if (Object.is(v1, v2)) return true;
|
|
122
|
+
|
|
123
|
+
if (v1.constructor !== v2.constructor) return false;
|
|
124
|
+
|
|
125
|
+
for (const key in v1) {
|
|
126
|
+
if (!(key in v2)) return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const key in v2) {
|
|
130
|
+
if (!(key in v1)) return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const key in v1) {
|
|
134
|
+
const value1 = v1[key];
|
|
135
|
+
const value2 = v2[key];
|
|
136
|
+
|
|
137
|
+
if (!valueEq(value1, value2)) return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return true;
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const _objectEq: Eq<Record<any, any>> = createObjectEq(defaultEq());
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Returns an Eq instance that checks equality of objects containing property values of type V by iteratively
|
|
148
|
+
* applying given `valueEq` to each of the object's property values.
|
|
149
|
+
* @typeparam - the object property value type
|
|
150
|
+
* @param valueEq - (optional) the Eq instance to use to compare property values
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* const eq = Eq.objectEq()
|
|
154
|
+
* console.log(eq({ a: 1, b: { c: 2 }}, { b: { c: 2 }, a: 1 }))
|
|
155
|
+
* // => true
|
|
156
|
+
* console.log(eq({ a: 1, b: { c: 2 }}, { a: 1, b: { c: 3 }}))
|
|
157
|
+
* // => false
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export function objectEq<V = any>(valueEq?: Eq<V>): Eq<Record<any, V>> {
|
|
161
|
+
if (undefined === valueEq) return _objectEq;
|
|
162
|
+
|
|
163
|
+
return createObjectEq(valueEq);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function createAnyEq(mode: 'FLAT' | 'SHALLOW' | 'DEEP'): Eq<any> {
|
|
167
|
+
const result: Eq<any> = (v1, v2): boolean => {
|
|
168
|
+
if (Object.is(v1, v2)) return true;
|
|
169
|
+
|
|
170
|
+
const type1 = typeof v1;
|
|
171
|
+
const type2 = typeof v2;
|
|
172
|
+
|
|
173
|
+
if (type1 !== type2) return false;
|
|
174
|
+
|
|
175
|
+
switch (type1) {
|
|
176
|
+
case 'undefined':
|
|
177
|
+
case 'bigint':
|
|
178
|
+
case 'boolean':
|
|
179
|
+
case 'number':
|
|
180
|
+
case 'string':
|
|
181
|
+
case 'symbol':
|
|
182
|
+
case 'function':
|
|
183
|
+
return Object.is(v1, v2);
|
|
184
|
+
case 'object': {
|
|
185
|
+
if (v1 === null || v2 === null) return false;
|
|
186
|
+
|
|
187
|
+
if (v1.constructor !== v2.constructor) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (
|
|
192
|
+
v1 instanceof Boolean ||
|
|
193
|
+
v1 instanceof Date ||
|
|
194
|
+
v1 instanceof Number ||
|
|
195
|
+
v1 instanceof String
|
|
196
|
+
) {
|
|
197
|
+
return _valueOfEq(v1, v2);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (mode !== 'FLAT') {
|
|
201
|
+
if (Symbol.iterator in v1 && Symbol.iterator in v2) {
|
|
202
|
+
if (mode === 'SHALLOW') {
|
|
203
|
+
return createIterableEq(_anyFlatEq)(v1, v2);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return createIterableEq(result)(v1, v2);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (mode === 'SHALLOW') {
|
|
210
|
+
return createObjectEq(_anyFlatEq)(v1, v2);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return _objectEq(v1, v2);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// cannot establish that they are equal in flat mode
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Returns an Eq instance that checks equality of any values. For composed values (objects and iterables)
|
|
227
|
+
* it will compare with Object.is.
|
|
228
|
+
* @typeparam T - the value type
|
|
229
|
+
* @example
|
|
230
|
+
* ```ts
|
|
231
|
+
* const eq = anyFlatEq()
|
|
232
|
+
* console.log(eq(1, 'a'))
|
|
233
|
+
* // => false
|
|
234
|
+
* console.log(eq({ a: 1, b: 2 }, { b: 2, a: 1 }))
|
|
235
|
+
* // => false
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
export function anyFlatEq<T = any>(): Eq<T> {
|
|
239
|
+
return _anyFlatEq;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Returns an Eq instance that checks equality of any values. For composed values (objects and iterables)
|
|
244
|
+
* it will enter 1 level, and if again compound values are found, they are compared
|
|
245
|
+
* with Object.is.
|
|
246
|
+
* @typeparam T - the value type
|
|
247
|
+
* @example
|
|
248
|
+
* ```ts
|
|
249
|
+
* const eq = anyFlatEq()
|
|
250
|
+
* console.log(eq(1, 'a'))
|
|
251
|
+
* // => false
|
|
252
|
+
* console.log(eq({ a: 1, b: 2 }, { b: 2, a: 1 }))
|
|
253
|
+
* // => true
|
|
254
|
+
* console.log(eq([{ a: 1, b: 2 }], [{ b: 2, a: 1 }]))
|
|
255
|
+
* // => false
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
export function anyShallowEq<T = any>(): Eq<T> {
|
|
259
|
+
return _anyShallowEq;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Returns an Eq instance that checks equality of any values. For composed values (objects and iterables)
|
|
264
|
+
* it will recursively compare the contained values.
|
|
265
|
+
* @note may have poor performance for deeply nested types and large arrays, and objects with circular structures
|
|
266
|
+
* may cause infinite loops
|
|
267
|
+
* @typeparam T - the value type
|
|
268
|
+
* @example
|
|
269
|
+
* ```ts
|
|
270
|
+
* const eq = anyFlatEq()
|
|
271
|
+
* console.log(eq(1, 'a'))
|
|
272
|
+
* // => false
|
|
273
|
+
* console.log(eq({ a: 1, b: 2 }, { b: 2, a: 1 }))
|
|
274
|
+
* // => true
|
|
275
|
+
* console.log(eq([{ a: 1, b: 2 }], [{ b: 2, a: 1 }]))
|
|
276
|
+
* // => false
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
export function anyDeepEq<T = any>(): Eq<T> {
|
|
280
|
+
return _anyDeepEq;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const _defaultCollator = Intl.Collator('und');
|
|
284
|
+
|
|
285
|
+
const _defaultStringCollatorEq: Eq<any> = (v1, v2) =>
|
|
286
|
+
_defaultCollator.compare(v1, v2) === 0;
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Returns an Eq instance that considers strings equal taking the given or default locale into account.
|
|
290
|
+
* @param locales - (optional) a locale or list of locales
|
|
291
|
+
* @param options - (optional) see String.localeCompare for details
|
|
292
|
+
* @example
|
|
293
|
+
* ```ts
|
|
294
|
+
* const eq = Eq.createStringCollatorEq()
|
|
295
|
+
* console.log(eq('a', 'a'))
|
|
296
|
+
* // => true
|
|
297
|
+
* console.log(eq('abc', 'aBc'))
|
|
298
|
+
* // => false
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
export function createStringCollatorEq(
|
|
302
|
+
...args: ConstructorParameters<typeof Intl.Collator>
|
|
303
|
+
): Eq<string> {
|
|
304
|
+
if (args.length === 0) return _defaultStringCollatorEq;
|
|
305
|
+
|
|
306
|
+
const collator = Intl.Collator(...args);
|
|
307
|
+
|
|
308
|
+
return (v1, v2) => collator.compare(v1, v2) === 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const _stringCaseInsensitiveEq: Eq<string> = createStringCollatorEq('und', {
|
|
312
|
+
sensitivity: 'accent',
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Returns an Eq instance that considers strings equal regardless of their case.
|
|
317
|
+
* @example
|
|
318
|
+
* ```ts
|
|
319
|
+
* const eq = Eq.stringCaseInsentitiveEq()
|
|
320
|
+
* console.log(eq('aB', 'Ab'))
|
|
321
|
+
* // => true
|
|
322
|
+
* console.log(eq('aBc', 'abB'))
|
|
323
|
+
* // => false
|
|
324
|
+
* ```
|
|
325
|
+
*/
|
|
326
|
+
export function stringCaseInsentitiveEq(): Eq<string> {
|
|
327
|
+
return _stringCaseInsensitiveEq;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const _stringCharCodeEq: Eq<string> = (v1, v2) => {
|
|
331
|
+
const len = v1.length;
|
|
332
|
+
|
|
333
|
+
if (len !== v2.length) return false;
|
|
334
|
+
|
|
335
|
+
let i = -1;
|
|
336
|
+
|
|
337
|
+
while (++i < len) {
|
|
338
|
+
if (v1.charCodeAt(i) !== v2.charCodeAt(i)) return false;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return true;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Returns an Eq instance that considers strings equal when all their charcodes are equal.
|
|
346
|
+
* @example
|
|
347
|
+
* ```ts
|
|
348
|
+
* const eq = Eq.stringCharCodeEq()
|
|
349
|
+
* console.log(eq('a', 'a'))
|
|
350
|
+
* // => true
|
|
351
|
+
* console.log(eq('abc', 'aBc'))
|
|
352
|
+
* // => false
|
|
353
|
+
* ```
|
|
354
|
+
*/
|
|
355
|
+
export function stringCharCodeEq(): Eq<string> {
|
|
356
|
+
return _stringCharCodeEq;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const _anyToStringEq: Eq<any> = (v1, v2) =>
|
|
360
|
+
convertAnyToString(v1) === convertAnyToString(v2);
|
|
361
|
+
|
|
362
|
+
export function anyToStringEq(): Eq<any> {
|
|
363
|
+
return _anyToStringEq;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const _anyJsonEq: Eq<any> = (v1, v2) =>
|
|
367
|
+
JSON.stringify(v1) === JSON.stringify(v2);
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Returns an Eq instance that considers values equal their JSON.stringify values are equal.
|
|
371
|
+
* @example
|
|
372
|
+
* ```ts
|
|
373
|
+
* const eq = Eq.anyJsonEq()
|
|
374
|
+
* console.log(eq({ a: 1, b: 2 }, { a: 1, b: 2 }))
|
|
375
|
+
* // => true
|
|
376
|
+
* console.log(eq({ a: 1, b: 2 }, { b: 2, a: 1 }))
|
|
377
|
+
* // => false
|
|
378
|
+
* ```
|
|
379
|
+
*/
|
|
380
|
+
export function anyJsonEq(): Eq<any> {
|
|
381
|
+
return _anyJsonEq;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Returns an `Eq` instance for tuples that considers two tuples [A, B] and [C, D] equal if [A, B] equals [C, D],
|
|
386
|
+
* or if [A, B] equals [D, C]
|
|
387
|
+
* @param eq - (optional) an alternative `Eq` instance to use for the values in the tuple
|
|
388
|
+
* @example
|
|
389
|
+
* ```ts
|
|
390
|
+
* const eq = Eq.tupleSymmetric()
|
|
391
|
+
* console.log(eq([1, 2], [1, 2]))
|
|
392
|
+
* // => true
|
|
393
|
+
* console.log(eq([1, 2], [2, 1]))
|
|
394
|
+
* // => true
|
|
395
|
+
* console.log(eq([1, 3], [2, 1]))
|
|
396
|
+
* // => false
|
|
397
|
+
* ```
|
|
398
|
+
*/
|
|
399
|
+
export function tupleSymmetric<T>(
|
|
400
|
+
eq: Eq<T> = defaultEq()
|
|
401
|
+
): Eq<readonly [T, T]> {
|
|
402
|
+
return (tup1: readonly [T, T], tup2: readonly [T, T]): boolean =>
|
|
403
|
+
(eq(tup1[0], tup2[0]) && eq(tup1[1], tup2[1])) ||
|
|
404
|
+
(eq(tup1[0], tup2[1]) && eq(tup1[1], tup2[0]));
|
|
405
|
+
}
|
|
406
|
+
}
|
package/src/err.mts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throws an `Err.ForcedError` error when called.
|
|
3
|
+
* @example
|
|
4
|
+
* ```ts
|
|
5
|
+
* const emptyMap = HashMap.empty<number, string>()
|
|
6
|
+
* emptyMap.get(5, Err);
|
|
7
|
+
* // throws: Err.CustomError(message: 'Err: forced to throw error')
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
export function Err(): never {
|
|
11
|
+
return ErrBase.msg('Err: Forced to throw error')();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export namespace ErrBase {
|
|
15
|
+
/**
|
|
16
|
+
* A custom error instance.
|
|
17
|
+
*/
|
|
18
|
+
export abstract class CustomError {
|
|
19
|
+
constructor(readonly message: string) {}
|
|
20
|
+
|
|
21
|
+
get name() {
|
|
22
|
+
return this.constructor.name;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class ForcedError extends CustomError {}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Returns a function that, when called, throws an `Err.ForcedError` with the given `message` string.
|
|
30
|
+
* @param message - the message to put in the `Err.ForcedError` instance.
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const emptyMap = HashMap.empty<number, string>()
|
|
34
|
+
* emptyMap.get(5, ErrBase.msg('not found'));
|
|
35
|
+
* // throws: Err.CustomError(message: 'not found')
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function msg(message: string): () => never {
|
|
39
|
+
return function (): never {
|
|
40
|
+
throw new ErrBase.ForcedError(message);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { Range } from './internal.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A flexible range specification for numeric indices.
|
|
5
|
+
* If a start or end is defined, a tuple can be used where the second item is a boolean
|
|
6
|
+
* indicating whether that end is inclusive or exclusive.<br/>
|
|
7
|
+
* An IndexRange can have one of the following forms:<br/>
|
|
8
|
+
* <br/>
|
|
9
|
+
* - { amount: number }<br/>
|
|
10
|
+
* - { start: number }<br/>
|
|
11
|
+
* - { start: number, amount: number }<br/>
|
|
12
|
+
* - { start: number, end: number }<br/>
|
|
13
|
+
* - { start: number, end: [number, boolean] }<br/>
|
|
14
|
+
* - { start: [number, boolean] }<br/>
|
|
15
|
+
* - { start: [number, boolean], amount: number }<br/>
|
|
16
|
+
* - { start: [number, boolean], end: number }<br/>
|
|
17
|
+
* - { start: [number, boolean], end: [number, boolean] }<br/>
|
|
18
|
+
* - { end: number }<br/>
|
|
19
|
+
* - { end: [number, boolean] }<br/>
|
|
20
|
+
*/
|
|
21
|
+
export type IndexRange =
|
|
22
|
+
| { amount: number; start?: number | [number, boolean]; end?: undefined }
|
|
23
|
+
| Range<number>;
|
|
24
|
+
|
|
25
|
+
export namespace IndexRange {
|
|
26
|
+
/**
|
|
27
|
+
* Returns, given the `range` `IndexRange`, a normalized tuple containing the
|
|
28
|
+
* start index, and optionally an end index.
|
|
29
|
+
* @param range - the `IndexRange` to use
|
|
30
|
+
*/
|
|
31
|
+
export function getIndexRangeIndices(
|
|
32
|
+
range: IndexRange
|
|
33
|
+
): [number, number | undefined] {
|
|
34
|
+
if (undefined !== range.amount) {
|
|
35
|
+
if (undefined === range.start) return [0, range.amount - 1];
|
|
36
|
+
|
|
37
|
+
if (Array.isArray(range.start)) {
|
|
38
|
+
const [start, includeStart] = range.start;
|
|
39
|
+
if (includeStart) return [start, start + range.amount - 1];
|
|
40
|
+
return [start + 1, start + 1 + range.amount - 1];
|
|
41
|
+
}
|
|
42
|
+
return [range.start, range.start + range.amount - 1];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let start = 0;
|
|
46
|
+
let end: number | undefined = undefined;
|
|
47
|
+
|
|
48
|
+
if (`start` in range) {
|
|
49
|
+
if (Array.isArray(range.start)) {
|
|
50
|
+
if (range.start[1]) start = range.start[0];
|
|
51
|
+
else start = range.start[0] + 1;
|
|
52
|
+
} else start = range.start ?? 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (`end` in range) {
|
|
56
|
+
if (Array.isArray(range.end)) {
|
|
57
|
+
if (range.end[1]) end = range.end[0];
|
|
58
|
+
else end = range.end[0] - 1;
|
|
59
|
+
} else end = range.end;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return [start, end];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Returns, given the `range` `IndexRange`, and a target maximum `length`, the actual index range.
|
|
67
|
+
* This can be one of three options:
|
|
68
|
+
* - 'empty': there are no elements within the range
|
|
69
|
+
* - 'all': all elements are within the range
|
|
70
|
+
* - [start: number, end: number]: an inclusive range of element indices within the given range
|
|
71
|
+
* @param range - the `IndexRange` to use
|
|
72
|
+
* @param length - the target maximum length
|
|
73
|
+
*/
|
|
74
|
+
export function getIndicesFor(
|
|
75
|
+
range: IndexRange,
|
|
76
|
+
length: number
|
|
77
|
+
): [number, number] | 'empty' | 'all' {
|
|
78
|
+
if (length <= 0) return 'empty';
|
|
79
|
+
|
|
80
|
+
let start = 0;
|
|
81
|
+
let end = length - 1;
|
|
82
|
+
|
|
83
|
+
if (undefined !== range.start) {
|
|
84
|
+
if (Array.isArray(range.start)) {
|
|
85
|
+
start = range.start[0];
|
|
86
|
+
if (!range.start[1]) start++;
|
|
87
|
+
} else start = range.start;
|
|
88
|
+
|
|
89
|
+
if (start >= length || -start > length) return 'empty';
|
|
90
|
+
if (start < 0) start = length + start;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (undefined !== range.amount) {
|
|
94
|
+
if (range.amount <= 0) return 'empty';
|
|
95
|
+
if (undefined === range.start) {
|
|
96
|
+
if (range.amount >= length) return 'all';
|
|
97
|
+
return [0, Math.min(end, range.amount - 1)];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
end = start + range.amount - 1;
|
|
101
|
+
} else if (undefined !== range.end) {
|
|
102
|
+
if (Array.isArray(range.end)) {
|
|
103
|
+
end = range.end[0];
|
|
104
|
+
if (!range.end[1]) {
|
|
105
|
+
if (end === 0) return 'empty';
|
|
106
|
+
end--;
|
|
107
|
+
}
|
|
108
|
+
} else end = range.end;
|
|
109
|
+
|
|
110
|
+
if (end < 0) end = length + end;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (end < start) return 'empty';
|
|
114
|
+
end = Math.min(length - 1, end);
|
|
115
|
+
|
|
116
|
+
if (start === 0 && end === length - 1) return 'all';
|
|
117
|
+
|
|
118
|
+
return [start, end];
|
|
119
|
+
}
|
|
120
|
+
}
|
package/src/internal.mts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './collect.mjs';
|
|
2
|
+
export * from './comp.mjs';
|
|
3
|
+
export * from './eq.mjs';
|
|
4
|
+
export * from './err.mjs';
|
|
5
|
+
export * from './index-range.mjs';
|
|
6
|
+
export * from './optlazy.mjs';
|
|
7
|
+
export * from './range.mjs';
|
|
8
|
+
export * from './reducer.mjs';
|
|
9
|
+
export * from './traverse-state.mjs';
|
|
10
|
+
export * from './types.mjs';
|
|
11
|
+
export * from './update.mjs';
|
|
12
|
+
export * from './async-optlazy.mjs';
|
|
13
|
+
export * from './async-reducer.mjs';
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* A potentially lazy value of type T.
|
|
3
|
+
* @typeparam T - the value type
|
|
4
|
+
*/
|
|
5
|
+
export type OptLazy<T> = T | (() => T);
|
|
6
|
+
|
|
4
7
|
/**
|
|
5
8
|
* Returns the value contained in an `OptLazy` instance of type T.
|
|
6
9
|
* @param optLazy - the `OptLazy` value of type T
|
|
@@ -11,12 +14,19 @@ exports.OptLazyOr = exports.OptLazy = void 0;
|
|
|
11
14
|
* OptLazy(() => () => 1) // => () => 1
|
|
12
15
|
* ```
|
|
13
16
|
*/
|
|
14
|
-
function OptLazy(optLazy) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return optLazy;
|
|
17
|
+
export function OptLazy<T>(optLazy: OptLazy<T>): T {
|
|
18
|
+
if (optLazy instanceof Function) return optLazy();
|
|
19
|
+
return optLazy;
|
|
18
20
|
}
|
|
19
|
-
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A potentially lazy value that, in case of a lazy function, received a default value
|
|
24
|
+
* that it can return.
|
|
25
|
+
* @typeparam T - the value type
|
|
26
|
+
* @typeparam O - the default value type
|
|
27
|
+
*/
|
|
28
|
+
export type OptLazyOr<T, O> = T | ((none: O) => T | O);
|
|
29
|
+
|
|
20
30
|
/**
|
|
21
31
|
* Returns the value contained in an `OptLazyOr` instance of type T, or the given
|
|
22
32
|
* `otherValue` if the lazy function returns it.
|
|
@@ -29,10 +39,10 @@ exports.OptLazy = OptLazy;
|
|
|
29
39
|
* OptLazyOr((none) => none, 'a') // => 'a'
|
|
30
40
|
* ```
|
|
31
41
|
*/
|
|
32
|
-
function OptLazyOr
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
export function OptLazyOr<T, O>(
|
|
43
|
+
optLazyOr: OptLazyOr<T, O>,
|
|
44
|
+
otherValue: O
|
|
45
|
+
): T | O {
|
|
46
|
+
if (optLazyOr instanceof Function) return optLazyOr(otherValue);
|
|
47
|
+
return optLazyOr;
|
|
36
48
|
}
|
|
37
|
-
exports.OptLazyOr = OptLazyOr;
|
|
38
|
-
//# sourceMappingURL=optlazy.js.map
|
package/src/range.mts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A range definition for any type of (orderable) value.
|
|
3
|
+
* If a start or end is defined, a tuple can be used where the second item is a boolean
|
|
4
|
+
* indicating whether that end is inclusive (true) or exclusive (false).<br/>
|
|
5
|
+
* A Range of type T can have one of the following forms:<br/>
|
|
6
|
+
* <br/>
|
|
7
|
+
* - { end: T }<br/>
|
|
8
|
+
* - { end: [T, boolean] }<br/>
|
|
9
|
+
* - { start: T }<br/>
|
|
10
|
+
* - { start: T, end: T }<br/>
|
|
11
|
+
* - { start: T, end: [T, boolean] }<br/>
|
|
12
|
+
* - { start: [T, boolean] }<br/>
|
|
13
|
+
* - { start: [T, boolean], end: T }<br/>
|
|
14
|
+
* - { start: [T, boolean], end: [T, boolean] }<br/>
|
|
15
|
+
*/
|
|
16
|
+
export type Range<T> =
|
|
17
|
+
| { start: T | [T, boolean]; end?: T | [T, boolean]; amount?: undefined }
|
|
18
|
+
| { start?: T | [T, boolean]; end: T | [T, boolean]; amount?: undefined };
|
|
19
|
+
|
|
20
|
+
export namespace Range {
|
|
21
|
+
/**
|
|
22
|
+
* Simplifies a given `range` `Range` input for easier processing, by returning optional
|
|
23
|
+
* start and end ranges including whether they are inclusive or exclusive
|
|
24
|
+
* @param range - the `Range` to use
|
|
25
|
+
*/
|
|
26
|
+
export function getNormalizedRange<T>(range: Range<T>): {
|
|
27
|
+
start?: [T, boolean] | undefined;
|
|
28
|
+
end?: [T, boolean] | undefined;
|
|
29
|
+
} {
|
|
30
|
+
let start: [T, boolean] | undefined = undefined;
|
|
31
|
+
let end: [T, boolean] | undefined = undefined;
|
|
32
|
+
|
|
33
|
+
if (`start` in range && undefined !== range.start) {
|
|
34
|
+
if (
|
|
35
|
+
Array.isArray(range.start) &&
|
|
36
|
+
range.start.length === 2 &&
|
|
37
|
+
typeof range.start[1] === 'boolean'
|
|
38
|
+
) {
|
|
39
|
+
start = range.start;
|
|
40
|
+
} else {
|
|
41
|
+
start = [range.start as T, true];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (`end` in range && undefined !== range.end) {
|
|
45
|
+
if (
|
|
46
|
+
Array.isArray(range.end) &&
|
|
47
|
+
range.end.length === 2 &&
|
|
48
|
+
typeof range.end[1] === 'boolean'
|
|
49
|
+
) {
|
|
50
|
+
end = range.end;
|
|
51
|
+
} else {
|
|
52
|
+
end = [range.end as T, true];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { start, end };
|
|
57
|
+
}
|
|
58
|
+
}
|