atomic-fns 1.1.7 → 2.0.0
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 +405 -60
- package/dist/array/index.d.ts +9 -0
- package/dist/array/index.js +9 -0
- package/dist/array/list.d.ts +99 -0
- package/dist/array/list.js +133 -0
- package/dist/array/sets.d.ts +130 -0
- package/dist/array/sets.js +213 -0
- package/dist/async/index.d.ts +6 -0
- package/dist/async/index.js +6 -0
- package/dist/async/retry.d.ts +38 -0
- package/dist/async/retry.js +56 -0
- package/dist/async/sleep.d.ts +42 -0
- package/dist/async/sleep.js +76 -0
- package/dist/collections/bisect.d.ts +103 -0
- package/dist/collections/bisect.js +157 -0
- package/dist/collections/deque.d.ts +67 -97
- package/dist/collections/deque.js +197 -249
- package/dist/collections/entries.d.ts +89 -0
- package/dist/collections/entries.js +131 -0
- package/dist/collections/frozen-set.d.ts +42 -0
- package/dist/collections/frozen-set.js +64 -0
- package/dist/collections/group.d.ts +141 -0
- package/dist/collections/group.js +241 -0
- package/dist/collections/heap.d.ts +64 -0
- package/dist/collections/heap.js +205 -0
- package/dist/collections/index.d.ts +15 -642
- package/dist/collections/index.js +14 -716
- package/dist/collections/internal/arrayKeys.d.ts +19 -0
- package/dist/collections/internal/arrayKeys.js +83 -0
- package/dist/collections/internal/invariants.d.ts +4 -0
- package/dist/collections/internal/invariants.js +12 -0
- package/dist/collections/internal/iterable.d.ts +25 -0
- package/dist/collections/internal/iterable.js +62 -0
- package/dist/collections/iterate.d.ts +162 -0
- package/dist/collections/iterate.js +188 -0
- package/dist/collections/lru-cache.d.ts +58 -0
- package/dist/collections/lru-cache.js +164 -0
- package/dist/collections/order.d.ts +19 -0
- package/dist/collections/order.js +38 -0
- package/dist/collections/shape.d.ts +32 -0
- package/dist/collections/shape.js +86 -0
- package/dist/collections/sorted-map.d.ts +171 -0
- package/dist/collections/sorted-map.js +845 -0
- package/dist/collections/sorted-set.d.ts +103 -0
- package/dist/collections/sorted-set.js +147 -0
- package/dist/collections/splay-tree.d.ts +82 -0
- package/dist/collections/splay-tree.js +354 -0
- package/dist/collections/trie.d.ts +58 -0
- package/dist/collections/trie.js +196 -0
- package/dist/decimal/currency.d.ts +211 -0
- package/dist/decimal/currency.js +321 -0
- package/dist/decimal/decimal.d.ts +299 -0
- package/dist/decimal/decimal.js +660 -0
- package/dist/decimal/index.d.ts +11 -145
- package/dist/decimal/index.js +10 -374
- package/dist/decimal/internal/bits.d.ts +2 -0
- package/dist/decimal/internal/bits.js +7 -0
- package/dist/decimal/internal/echo.d.ts +4 -0
- package/dist/decimal/internal/echo.js +6 -0
- package/dist/decimal/internal/isqrt.d.ts +9 -0
- package/dist/decimal/internal/isqrt.js +34 -0
- package/dist/decimal/internal/locale.d.ts +7 -0
- package/dist/decimal/internal/locale.js +24 -0
- package/dist/decimal/internal/minor-units.d.ts +2 -0
- package/dist/decimal/internal/minor-units.js +33 -0
- package/dist/decimal/internal/pow10.d.ts +24 -0
- package/dist/decimal/internal/pow10.js +96 -0
- package/dist/encoding/base64.d.ts +39 -0
- package/dist/encoding/base64.js +53 -0
- package/dist/encoding/hex.d.ts +23 -0
- package/dist/encoding/hex.js +34 -0
- package/dist/encoding/index.d.ts +6 -0
- package/dist/encoding/index.js +6 -0
- package/dist/function/debounce.d.ts +68 -0
- package/dist/function/debounce.js +203 -0
- package/dist/function/index.d.ts +6 -0
- package/dist/function/index.js +6 -0
- package/dist/function/shared.d.ts +2 -0
- package/dist/function/shared.js +5 -0
- package/dist/function/wrap.d.ts +130 -0
- package/dist/function/wrap.js +113 -0
- package/dist/hash/digest.d.ts +18 -0
- package/dist/hash/digest.js +26 -0
- package/dist/hash/hash.d.ts +22 -0
- package/dist/hash/hash.js +252 -0
- package/dist/hash/hashString.d.ts +13 -0
- package/dist/hash/hashString.js +53 -0
- package/dist/hash/index.d.ts +8 -0
- package/dist/hash/index.js +8 -0
- package/dist/hash/stableStringify.d.ts +19 -0
- package/dist/hash/stableStringify.js +102 -0
- package/dist/id/index.d.ts +7 -0
- package/dist/id/index.js +7 -0
- package/dist/id/nanoid.d.ts +16 -0
- package/dist/id/nanoid.js +65 -0
- package/dist/id/uniqueId.d.ts +14 -0
- package/dist/id/uniqueId.js +20 -0
- package/dist/id/uuid.d.ts +25 -0
- package/dist/id/uuid.js +64 -0
- package/dist/index.d.ts +15 -8
- package/dist/index.js +15 -8
- package/dist/internal/bytes.d.ts +11 -0
- package/dist/internal/bytes.js +33 -0
- package/dist/internal/codec.d.ts +11 -0
- package/dist/internal/codec.js +84 -0
- package/dist/internal/compare.d.ts +10 -0
- package/dist/internal/compare.js +26 -0
- package/dist/internal/crypto.d.ts +5 -0
- package/dist/internal/crypto.js +11 -0
- package/dist/internal/keys.d.ts +12 -0
- package/dist/internal/keys.js +23 -0
- package/dist/internal/kind.d.ts +44 -0
- package/dist/internal/kind.js +139 -0
- package/dist/internal/sparse.d.ts +51 -0
- package/dist/internal/sparse.js +104 -0
- package/dist/internal/text.d.ts +7 -0
- package/dist/internal/text.js +15 -0
- package/dist/internal/timer.d.ts +10 -0
- package/dist/internal/timer.js +17 -0
- package/dist/internal/truncate.d.ts +10 -0
- package/dist/internal/truncate.js +22 -0
- package/dist/internal/walk.d.ts +47 -0
- package/dist/internal/walk.js +100 -0
- package/dist/is/index.d.ts +8 -0
- package/dist/is/index.js +8 -0
- package/dist/is/isArrayLike.d.ts +18 -0
- package/dist/is/isArrayLike.js +25 -0
- package/dist/is/isError.d.ts +13 -0
- package/dist/is/isError.js +18 -0
- package/dist/is/isNil.d.ts +12 -0
- package/dist/is/isNil.js +14 -0
- package/dist/is/isPlainObject.d.ts +15 -0
- package/dist/is/isPlainObject.js +34 -0
- package/dist/number/index.d.ts +7 -0
- package/dist/number/index.js +7 -0
- package/dist/number/round.d.ts +86 -0
- package/dist/number/round.js +130 -0
- package/dist/number/stats.d.ts +52 -0
- package/dist/number/stats.js +115 -0
- package/dist/object/cloneDeep.d.ts +28 -0
- package/dist/object/cloneDeep.js +200 -0
- package/dist/object/index.d.ts +14 -0
- package/dist/object/index.js +13 -0
- package/dist/object/isEqual.d.ts +64 -0
- package/dist/object/isEqual.js +289 -0
- package/dist/object/matches.d.ts +40 -0
- package/dist/object/matches.js +284 -0
- package/dist/object/merge.d.ts +66 -0
- package/dist/object/merge.js +272 -0
- package/dist/object/parsePath.d.ts +22 -0
- package/dist/object/parsePath.js +111 -0
- package/dist/object/path.d.ts +85 -0
- package/dist/object/path.js +208 -0
- package/dist/object/pick.d.ts +91 -0
- package/dist/object/pick.js +180 -0
- package/dist/object/reshape.d.ts +73 -0
- package/dist/object/reshape.js +144 -0
- package/dist/random/index.d.ts +96 -32
- package/dist/random/index.js +215 -50
- package/dist/string/case.d.ts +71 -0
- package/dist/string/case.js +152 -0
- package/dist/string/fold.d.ts +35 -0
- package/dist/string/fold.js +100 -0
- package/dist/string/index.d.ts +6 -187
- package/dist/string/index.js +6 -226
- package/dist/string/truncate.d.ts +25 -0
- package/dist/string/truncate.js +44 -0
- package/dist/string/words.d.ts +27 -0
- package/dist/string/words.js +241 -0
- package/package.json +101 -58
- package/dist/collections/BTree.d.ts +0 -229
- package/dist/collections/BTree.js +0 -1165
- package/dist/collections/Heap.d.ts +0 -69
- package/dist/collections/Heap.js +0 -181
- package/dist/collections/LRUCache.d.ts +0 -80
- package/dist/collections/LRUCache.js +0 -126
- package/dist/collections/SortedMap.d.ts +0 -92
- package/dist/collections/SortedMap.js +0 -131
- package/dist/collections/SortedSet.d.ts +0 -69
- package/dist/collections/SortedSet.js +0 -99
- package/dist/collections/SortedTree.d.ts +0 -103
- package/dist/collections/SortedTree.js +0 -141
- package/dist/collections/SplayTree.d.ts +0 -105
- package/dist/collections/SplayTree.js +0 -338
- package/dist/collections/Trie.d.ts +0 -42
- package/dist/collections/Trie.js +0 -104
- package/dist/collections/abc.d.ts +0 -152
- package/dist/collections/abc.js +0 -39
- package/dist/collections/frozenset.d.ts +0 -19
- package/dist/collections/frozenset.js +0 -30
- package/dist/functools/index.d.ts +0 -130
- package/dist/functools/index.js +0 -235
- package/dist/globals/index.d.ts +0 -285
- package/dist/globals/index.js +0 -463
- package/dist/i18n/currency.d.ts +0 -24
- package/dist/i18n/currency.js +0 -85
- package/dist/i18n/date/index.d.ts +0 -203
- package/dist/i18n/date/index.js +0 -531
- package/dist/i18n/date/utils.d.ts +0 -65
- package/dist/i18n/date/utils.js +0 -290
- package/dist/i18n/duration.d.ts +0 -112
- package/dist/i18n/duration.js +0 -392
- package/dist/i18n/format.d.ts +0 -31
- package/dist/i18n/format.js +0 -506
- package/dist/i18n/index.d.ts +0 -8
- package/dist/i18n/index.js +0 -8
- package/dist/itertools/index.d.ts +0 -255
- package/dist/itertools/index.js +0 -437
- package/dist/math/index.d.ts +0 -96
- package/dist/math/index.js +0 -98
- package/dist/mod.d.ts +0 -1
- package/dist/mod.js +0 -1
- package/dist/operators/index.d.ts +0 -152
- package/dist/operators/index.js +0 -305
- package/dist/types.d.ts +0 -19
- package/dist/types.js +0 -1
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Partial deep matching: `isMatch`, `matches`.
|
|
3
|
+
*/
|
|
4
|
+
import { isPlainObject } from '../is/isPlainObject.js';
|
|
5
|
+
import { enumerableKeys } from '../internal/keys.js';
|
|
6
|
+
import { walkArray } from '../internal/sparse.js';
|
|
7
|
+
import { enterDepth, exitDepth, guardDepth } from '../internal/walk.js';
|
|
8
|
+
import { isEqual } from './isEqual.js';
|
|
9
|
+
/** Steps one call may take before it throws, so no input can keep it running for long. */
|
|
10
|
+
const MAX_MATCH_STEPS = 2 ** 25;
|
|
11
|
+
/** Message of the `RangeError` a call throws past {@link MAX_MATCH_STEPS}. */
|
|
12
|
+
const STEPS_ERROR = `matching took more than ${String(MAX_MATCH_STEPS)} steps`;
|
|
13
|
+
/** Answers one call remembers; past this it stops remembering, so only time grows, not memory. */
|
|
14
|
+
const MAX_REMEMBERED_PAIRS = 2 ** 20;
|
|
15
|
+
function step(pending, count = 1) {
|
|
16
|
+
pending.steps += count;
|
|
17
|
+
if (pending.steps > MAX_MATCH_STEPS)
|
|
18
|
+
throw new RangeError(STEPS_ERROR);
|
|
19
|
+
}
|
|
20
|
+
function isObject(value) {
|
|
21
|
+
return typeof value === 'object' && value !== null;
|
|
22
|
+
}
|
|
23
|
+
function remember(pending, shape, target, answer) {
|
|
24
|
+
if (pending.remembered >= MAX_REMEMBERED_PAIRS)
|
|
25
|
+
return false;
|
|
26
|
+
let answers = pending.answers.get(shape);
|
|
27
|
+
if (answers === undefined)
|
|
28
|
+
pending.answers.set(shape, (answers = new WeakMap()));
|
|
29
|
+
answers.set(target, answer);
|
|
30
|
+
pending.remembered++;
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
/** Settle the answer of a pair that opened at `depth` when the tentative list was `mark` long. */
|
|
34
|
+
function settle(pending, shape, target, ok, depth, mark, low) {
|
|
35
|
+
const { tentative } = pending;
|
|
36
|
+
if (!ok || low >= depth) {
|
|
37
|
+
for (let i = mark; i < tentative.length; i++) {
|
|
38
|
+
const entry = tentative[i];
|
|
39
|
+
const answers = pending.answers.get(entry.shape);
|
|
40
|
+
if (ok) {
|
|
41
|
+
answers?.set(entry.target, true);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
answers?.delete(entry.target);
|
|
45
|
+
pending.remembered--;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
tentative.length = mark;
|
|
49
|
+
remember(pending, shape, target, ok);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
step(pending, tentative.length - mark);
|
|
53
|
+
for (let i = mark; i < tentative.length; i++)
|
|
54
|
+
tentative[i].low = low;
|
|
55
|
+
pending.low = Math.min(pending.low, low);
|
|
56
|
+
const entry = { shape, target, low };
|
|
57
|
+
if (remember(pending, shape, target, entry))
|
|
58
|
+
pending.tentative.push(entry);
|
|
59
|
+
}
|
|
60
|
+
/** Match a plain-object or array `shape` against an object `target` below the top. */
|
|
61
|
+
function matchPair(target, shape, pending) {
|
|
62
|
+
const known = pending.answers.get(shape)?.get(target);
|
|
63
|
+
if (known !== undefined) {
|
|
64
|
+
if (typeof known === 'boolean')
|
|
65
|
+
return known;
|
|
66
|
+
pending.low = Math.min(pending.low, known.low);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
let partners = pending.open.get(shape);
|
|
70
|
+
const openAt = partners?.get(target);
|
|
71
|
+
if (openAt !== undefined) {
|
|
72
|
+
pending.low = Math.min(pending.low, openAt);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (partners === undefined)
|
|
76
|
+
pending.open.set(shape, (partners = new Map()));
|
|
77
|
+
const depth = ++pending.depth;
|
|
78
|
+
const outerLow = pending.low;
|
|
79
|
+
const mark = pending.tentative.length;
|
|
80
|
+
partners.set(target, depth);
|
|
81
|
+
pending.low = Number.POSITIVE_INFINITY;
|
|
82
|
+
enterDepth();
|
|
83
|
+
let ok;
|
|
84
|
+
try {
|
|
85
|
+
ok = Array.isArray(shape)
|
|
86
|
+
? matchArray(target, shape, pending)
|
|
87
|
+
: matchObject(target, shape, pending, false);
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
exitDepth();
|
|
91
|
+
partners.delete(target);
|
|
92
|
+
pending.depth--;
|
|
93
|
+
}
|
|
94
|
+
const low = pending.low;
|
|
95
|
+
pending.low = outerLow;
|
|
96
|
+
settle(pending, shape, target, ok, depth, mark, low);
|
|
97
|
+
return ok;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Whether each shape in `wanted` can have a candidate of its own, by augmenting paths (Kuhn's
|
|
101
|
+
* algorithm), so the answer does not depend on the order of either array.
|
|
102
|
+
*/
|
|
103
|
+
function assign(candidates, wanted, pending) {
|
|
104
|
+
const owner = new Array(candidates.length).fill(-1);
|
|
105
|
+
const seenBy = new Array(candidates.length).fill(-1);
|
|
106
|
+
const fits = (shape, candidate) => matchValue(candidates[candidate], wanted[shape], pending, false);
|
|
107
|
+
const unplaced = [];
|
|
108
|
+
// First fit, where every candidate before `firstFree` is taken; augmenting paths repair the rest.
|
|
109
|
+
let firstFree = 0;
|
|
110
|
+
for (let shape = 0; shape < wanted.length; shape++) {
|
|
111
|
+
let placed = false;
|
|
112
|
+
for (let candidate = firstFree; candidate < candidates.length && !placed; candidate++) {
|
|
113
|
+
if (owner[candidate] >= 0)
|
|
114
|
+
step(pending);
|
|
115
|
+
else if (fits(shape, candidate)) {
|
|
116
|
+
owner[candidate] = shape;
|
|
117
|
+
placed = true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
while (firstFree < candidates.length && owner[firstFree] >= 0)
|
|
121
|
+
firstFree++;
|
|
122
|
+
if (!placed)
|
|
123
|
+
unplaced.push(shape);
|
|
124
|
+
}
|
|
125
|
+
for (const root of unplaced) {
|
|
126
|
+
const path = [{ shape: root, next: 0 }];
|
|
127
|
+
const taken = [];
|
|
128
|
+
let placed = false;
|
|
129
|
+
while (path.length > 0 && !placed) {
|
|
130
|
+
const frame = path[path.length - 1];
|
|
131
|
+
if (frame.next === candidates.length) {
|
|
132
|
+
path.pop();
|
|
133
|
+
taken.pop();
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const candidate = frame.next++;
|
|
137
|
+
if (seenBy[candidate] === root) {
|
|
138
|
+
step(pending);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (!fits(frame.shape, candidate))
|
|
142
|
+
continue;
|
|
143
|
+
seenBy[candidate] = root;
|
|
144
|
+
taken.push(candidate);
|
|
145
|
+
if (owner[candidate] < 0) {
|
|
146
|
+
for (let i = 0; i < path.length; i++)
|
|
147
|
+
owner[taken[i]] = path[i].shape;
|
|
148
|
+
placed = true;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
path.push({ shape: owner[candidate], next: 0 });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (!placed)
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
function matchArray(target, source, pending) {
|
|
160
|
+
const wantedValues = new Map();
|
|
161
|
+
const wantedShapes = [];
|
|
162
|
+
let unmet = 0;
|
|
163
|
+
walkArray(source, (event) => {
|
|
164
|
+
if (event.kind !== 'value')
|
|
165
|
+
return;
|
|
166
|
+
step(pending);
|
|
167
|
+
if (isObject(event.value)) {
|
|
168
|
+
wantedShapes.push(event.value);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
wantedValues.set(event.value, (wantedValues.get(event.value) ?? 0) + 1);
|
|
172
|
+
unmet++;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
const candidates = [];
|
|
176
|
+
walkArray(target, (event) => {
|
|
177
|
+
if (event.kind !== 'value')
|
|
178
|
+
return;
|
|
179
|
+
step(pending);
|
|
180
|
+
if (isObject(event.value)) {
|
|
181
|
+
if (wantedShapes.length > 0)
|
|
182
|
+
candidates.push(event.value);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const count = wantedValues.get(event.value);
|
|
186
|
+
if (count !== undefined && count > 0) {
|
|
187
|
+
wantedValues.set(event.value, count - 1);
|
|
188
|
+
unmet--;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
if (unmet > 0 || wantedShapes.length > candidates.length)
|
|
192
|
+
return false;
|
|
193
|
+
return assign(candidates, wantedShapes, pending);
|
|
194
|
+
}
|
|
195
|
+
function matchObject(target, source, pending, top) {
|
|
196
|
+
const keys = enumerableKeys(source);
|
|
197
|
+
if (target === null || target === undefined)
|
|
198
|
+
return keys.length === 0;
|
|
199
|
+
if (!top && Array.isArray(target))
|
|
200
|
+
return false;
|
|
201
|
+
const base = Object(target);
|
|
202
|
+
for (const key of keys) {
|
|
203
|
+
const wanted = source[key];
|
|
204
|
+
if (wanted === undefined) {
|
|
205
|
+
if (!(key in base) || base[key] !== undefined)
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
else if (!matchValue(base[key], wanted, pending, false)) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
function matchValue(target, source, pending, top) {
|
|
215
|
+
step(pending);
|
|
216
|
+
const isArray = Array.isArray(source);
|
|
217
|
+
if (!isArray && !isPlainObject(source))
|
|
218
|
+
return isEqual(target, source);
|
|
219
|
+
if (isArray && !Array.isArray(target))
|
|
220
|
+
return false;
|
|
221
|
+
if (top) {
|
|
222
|
+
enterDepth();
|
|
223
|
+
try {
|
|
224
|
+
return isArray
|
|
225
|
+
? matchArray(target, source, pending)
|
|
226
|
+
: matchObject(target, source, pending, true);
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
exitDepth();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (!isObject(target))
|
|
233
|
+
return false;
|
|
234
|
+
return matchPair(target, source, pending);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* True when `obj` has every key of `source`, deeply, with an {@link isEqual} value, as lodash's
|
|
238
|
+
* `isMatch` checks. A plain object in `source` matches an object with at least its own enumerable
|
|
239
|
+
* keys, symbols included, and a key whose source value is `undefined` must be present with the
|
|
240
|
+
* value `undefined`. An array in `source` matches an array that holds a match for each of its
|
|
241
|
+
* elements, each element of `obj` serving one, so arrays match as subsets in any order. A hole is
|
|
242
|
+
* not an element: it wants nothing in `source` and offers nothing in `obj`, as an absent key does.
|
|
243
|
+
* Anything else in `source`, a `Date` or `Map` included, must be {@link isEqual} to the value found.
|
|
244
|
+
* At the top, `obj` may be any value: a primitive is read as its boxed form, and `null` or
|
|
245
|
+
* `undefined` matches only a `source` with no keys.
|
|
246
|
+
*
|
|
247
|
+
* Cycles are assumed to match, and each pair of objects is answered once.
|
|
248
|
+
* @param obj The value to test.
|
|
249
|
+
* @param source The partial shape to look for.
|
|
250
|
+
* @returns Whether `obj` matches.
|
|
251
|
+
* @throws {RangeError} past 1000 levels of nesting, or past 33554432 steps of work.
|
|
252
|
+
* @example
|
|
253
|
+
* ```ts
|
|
254
|
+
* isMatch({ a: 1, b: { c: 2, d: 3 } }, { b: { c: 2 } }) // true
|
|
255
|
+
* isMatch({ tags: ['a', 'b', 'c'] }, { tags: ['c', 'a'] }) // true
|
|
256
|
+
* isMatch({ a: 1 }, { a: undefined }) // false
|
|
257
|
+
* isMatch({}, { a: {} }) // false
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
export function isMatch(obj, source) {
|
|
261
|
+
return guardDepth(() => matchValue(obj, source, {
|
|
262
|
+
open: new Map(),
|
|
263
|
+
answers: new WeakMap(),
|
|
264
|
+
tentative: [],
|
|
265
|
+
remembered: 0,
|
|
266
|
+
depth: 0,
|
|
267
|
+
low: Number.POSITIVE_INFINITY,
|
|
268
|
+
steps: 0
|
|
269
|
+
}, true));
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* A predicate that tells whether a value matches `source`, by the rules of {@link isMatch}; it is
|
|
273
|
+
* lodash's `matches` shorthand as a function to pass to `filter`, `find` and the like.
|
|
274
|
+
* @param source The partial shape to look for.
|
|
275
|
+
* @returns `(obj) => isMatch(obj, source)`.
|
|
276
|
+
* @example
|
|
277
|
+
* ```ts
|
|
278
|
+
* const users = [{ name: 'a', active: true }, { name: 'b', active: false }]
|
|
279
|
+
* users.filter(matches({ active: true })) // [{ name: 'a', active: true }]
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
export function matches(source) {
|
|
283
|
+
return (obj) => isMatch(obj, source);
|
|
284
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writing sources into a target: `merge`, `defaults`, `defaultsDeep`.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Merge the own enumerable string and symbol keys of each source into `target`, deeply, and
|
|
6
|
+
* return `target`, as lodash's `merge` does. A plain object merges into a plain object and an array
|
|
7
|
+
* into an array; anything else, or a container of the other kind, is assigned in its place (a
|
|
8
|
+
* plain object or array from a source is copied first, with its prototype, so `target` never
|
|
9
|
+
* shares it). An `undefined` source value does not replace a key the target has. Keys are
|
|
10
|
+
* assigned, so setters run; `__proto__`, `constructor` and `prototype` are defined as own data.
|
|
11
|
+
* Replacing an own `constructor` function with an object is refused.
|
|
12
|
+
* Each key gets its own copy of a source object it shares with another key.
|
|
13
|
+
* @param target The object to change.
|
|
14
|
+
* @param sources Objects to merge in, left to right; sources that are not objects are skipped.
|
|
15
|
+
* @returns `target`.
|
|
16
|
+
* @throws {TypeError} when `target` is not an object, or for a key of it that is read-only or
|
|
17
|
+
* has only a getter.
|
|
18
|
+
* @throws {RangeError} past 1000 levels of nesting, for an array index or `length` that would
|
|
19
|
+
* grow an array past its end, for a huge sparse source array, or when shared objects would be
|
|
20
|
+
* copied past 500000 units of work.
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* merge({ a: [{ b: 2 }], d: 1 }, { a: [{ c: 3 }] }) // { a: [{ b: 2, c: 3 }], d: 1 }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function merge<T extends object>(target: T, ...sources: unknown[]): T;
|
|
27
|
+
/**
|
|
28
|
+
* Fill each key of `target` that reads `undefined` with the first source that has it, and return
|
|
29
|
+
* `target`, as lodash's `defaults` does. A string key only inherited from `Object.prototype`
|
|
30
|
+
* (such as `toString`) also counts as missing; one inherited from a class does not. A key `target`
|
|
31
|
+
* has only a getter for is left alone. Only the own enumerable string and symbol keys of each
|
|
32
|
+
* source are considered.
|
|
33
|
+
* Existing own symbol data properties keep their descriptors when filled.
|
|
34
|
+
* @param target The object to fill.
|
|
35
|
+
* @param sources Objects to read defaults from, left to right; sources that are not objects are
|
|
36
|
+
* skipped.
|
|
37
|
+
* @returns `target`.
|
|
38
|
+
* @throws {TypeError} when `target` is not an object.
|
|
39
|
+
* @throws {RangeError} for an array index past the end of an array `target`.
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* defaults({ a: 1 }, { a: 3, b: 2 }) // { a: 1, b: 2 }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function defaults<T extends object>(target: T, ...sources: unknown[]): T;
|
|
46
|
+
/**
|
|
47
|
+
* {@link defaults}, all the way down: where `target` and a source both hold a plain object (or
|
|
48
|
+
* both an array) at a key, the source fills the missing keys inside it. A filled plain object or
|
|
49
|
+
* array is copied, with its prototype, so `target` never shares it with a source; a cycle back to
|
|
50
|
+
* a source itself points at `target`.
|
|
51
|
+
* Filling `__proto__` is refused unless the target has a null prototype; `constructor` and
|
|
52
|
+
* `prototype` are filled as own data, and inherited values are never descended into.
|
|
53
|
+
* Only own enumerable string and symbol keys of each source are considered.
|
|
54
|
+
* @param target The object to fill.
|
|
55
|
+
* @param sources Objects to read defaults from, left to right; sources that are not objects are
|
|
56
|
+
* skipped.
|
|
57
|
+
* @returns `target`.
|
|
58
|
+
* @throws {TypeError} when `target` is not an object.
|
|
59
|
+
* @throws {RangeError} past 1000 levels of nesting, for an index past the end of an array, for a
|
|
60
|
+
* huge sparse source array, or when shared objects would be copied past 500000 units of work.
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* defaultsDeep({ a: { b: 2 } }, { a: { b: 1, c: 3 } }) // { a: { b: 2, c: 3 } }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare function defaultsDeep<T extends object>(target: T, ...sources: unknown[]): T;
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writing sources into a target: `merge`, `defaults`, `defaultsDeep`.
|
|
3
|
+
*/
|
|
4
|
+
import { isPlainObject } from '../is/isPlainObject.js';
|
|
5
|
+
import { enumerableKeys } from '../internal/keys.js';
|
|
6
|
+
import { assertNotHugeSparse, isCanonicalArrayIndex } from '../internal/sparse.js';
|
|
7
|
+
import { enterDepth, exitDepth, guardDepth, RevisitBudget } from '../internal/walk.js';
|
|
8
|
+
import { writeKey } from './parsePath.js';
|
|
9
|
+
const MERGE_BUDGET = 500_000;
|
|
10
|
+
const BUDGET_ERROR = `input repeats shared objects past ${String(MERGE_BUDGET)} units of work`;
|
|
11
|
+
/** An index or `length` write that would grow an array past its end. */
|
|
12
|
+
function assertInRange(t, key, value) {
|
|
13
|
+
if (!Array.isArray(t) || typeof key !== 'string')
|
|
14
|
+
return;
|
|
15
|
+
const grows = key === 'length' && (Object(value) === value || Number(value) > t.length);
|
|
16
|
+
if (grows || (isCanonicalArrayIndex(key) && Number(key) > t.length)) {
|
|
17
|
+
throw new RangeError(`array index ${key} is past the end`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function assertTarget(target, name) {
|
|
21
|
+
if (target === null || typeof target !== 'object') {
|
|
22
|
+
throw new TypeError(`${name} target must be an object`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Record the (source, target) pair, and say whether it was recorded already. */
|
|
26
|
+
function walkedBefore(state, s, t) {
|
|
27
|
+
let targets = state.pairs.get(s);
|
|
28
|
+
if (targets?.has(t))
|
|
29
|
+
return true;
|
|
30
|
+
if (targets === undefined) {
|
|
31
|
+
targets = new WeakSet();
|
|
32
|
+
state.pairs.set(s, targets);
|
|
33
|
+
}
|
|
34
|
+
targets.add(t);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Write each key of source `s` into target `t` with `writeEntry`, one level deeper. An `existing`
|
|
39
|
+
* target (one `target` already held, not a fresh copy) is walked once per source object. Charges
|
|
40
|
+
* the revisit budget and grows an array target to an array source's length.
|
|
41
|
+
*/
|
|
42
|
+
function walkInto(t, s, state, existing, writeEntry) {
|
|
43
|
+
if (existing && walkedBefore(state, s, t))
|
|
44
|
+
return;
|
|
45
|
+
const keys = enumerableKeys(s);
|
|
46
|
+
state.budget.note(s, keys.length);
|
|
47
|
+
enterDepth();
|
|
48
|
+
try {
|
|
49
|
+
if (Array.isArray(s) && Array.isArray(t)) {
|
|
50
|
+
assertNotHugeSparse(s);
|
|
51
|
+
if (t.length < s.length)
|
|
52
|
+
t.length = s.length;
|
|
53
|
+
}
|
|
54
|
+
for (const key of keys)
|
|
55
|
+
writeEntry(key);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
exitDepth();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Copy `source` with `fill`, into an empty array or an object with the prototype of `source`.
|
|
63
|
+
* While `fill` runs, `source` maps to the copy, so a cycle in `source` points back at the copy.
|
|
64
|
+
*/
|
|
65
|
+
function copyWith(state, source, fill) {
|
|
66
|
+
const copy = Array.isArray(source)
|
|
67
|
+
? []
|
|
68
|
+
: Object.create(Object.getPrototypeOf(source));
|
|
69
|
+
state.copies.set(source, copy);
|
|
70
|
+
try {
|
|
71
|
+
fill(copy);
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
state.copies.delete(source);
|
|
75
|
+
}
|
|
76
|
+
return copy;
|
|
77
|
+
}
|
|
78
|
+
function mergeKey(t, s, key, state) {
|
|
79
|
+
const sv = s[key];
|
|
80
|
+
assertInRange(t, key, sv);
|
|
81
|
+
const tv = Object.hasOwn(t, key) ? t[key] : undefined;
|
|
82
|
+
if (key === 'constructor' && typeof tv === 'function' && sv !== null && typeof sv === 'object') {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (sv === undefined && key in t)
|
|
86
|
+
return;
|
|
87
|
+
const svArray = Array.isArray(sv);
|
|
88
|
+
if (!svArray && !isPlainObject(sv)) {
|
|
89
|
+
writeKey(t, key, sv);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const source = sv;
|
|
93
|
+
const same = svArray ? Array.isArray(tv) : isPlainObject(tv);
|
|
94
|
+
if (same) {
|
|
95
|
+
mergeInto(tv, source, state, true);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const copy = state.copies.get(source) ??
|
|
99
|
+
copyWith(state, source, (fresh) => mergeInto(fresh, source, state, false));
|
|
100
|
+
writeKey(t, key, copy);
|
|
101
|
+
}
|
|
102
|
+
function mergeInto(t, s, state, existing) {
|
|
103
|
+
walkInto(t, s, state, existing, (key) => mergeKey(t, s, key, state));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Merge the own enumerable string and symbol keys of each source into `target`, deeply, and
|
|
107
|
+
* return `target`, as lodash's `merge` does. A plain object merges into a plain object and an array
|
|
108
|
+
* into an array; anything else, or a container of the other kind, is assigned in its place (a
|
|
109
|
+
* plain object or array from a source is copied first, with its prototype, so `target` never
|
|
110
|
+
* shares it). An `undefined` source value does not replace a key the target has. Keys are
|
|
111
|
+
* assigned, so setters run; `__proto__`, `constructor` and `prototype` are defined as own data.
|
|
112
|
+
* Replacing an own `constructor` function with an object is refused.
|
|
113
|
+
* Each key gets its own copy of a source object it shares with another key.
|
|
114
|
+
* @param target The object to change.
|
|
115
|
+
* @param sources Objects to merge in, left to right; sources that are not objects are skipped.
|
|
116
|
+
* @returns `target`.
|
|
117
|
+
* @throws {TypeError} when `target` is not an object, or for a key of it that is read-only or
|
|
118
|
+
* has only a getter.
|
|
119
|
+
* @throws {RangeError} past 1000 levels of nesting, for an array index or `length` that would
|
|
120
|
+
* grow an array past its end, for a huge sparse source array, or when shared objects would be
|
|
121
|
+
* copied past 500000 units of work.
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* merge({ a: [{ b: 2 }], d: 1 }, { a: [{ c: 3 }] }) // { a: [{ b: 2, c: 3 }], d: 1 }
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export function merge(target, ...sources) {
|
|
128
|
+
assertTarget(target, 'merge');
|
|
129
|
+
const budget = new RevisitBudget(MERGE_BUDGET, BUDGET_ERROR);
|
|
130
|
+
return guardDepth(() => {
|
|
131
|
+
for (const source of sources) {
|
|
132
|
+
if (source === null || typeof source !== 'object')
|
|
133
|
+
continue;
|
|
134
|
+
mergeInto(target, source, { copies: new WeakMap(), pairs: new WeakMap(), budget }, true);
|
|
135
|
+
}
|
|
136
|
+
return target;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function descriptorAlongChain(obj, key) {
|
|
140
|
+
for (let cur = obj; cur; cur = Object.getPrototypeOf(cur)) {
|
|
141
|
+
const desc = Object.getOwnPropertyDescriptor(cur, key);
|
|
142
|
+
if (desc)
|
|
143
|
+
return desc;
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Whether `defaults` fills `key` of `target`: it reads `undefined`, or it is a string key only
|
|
149
|
+
* `Object.prototype` has, and it is not a getter without a setter.
|
|
150
|
+
*/
|
|
151
|
+
function needsDefault(target, key, value) {
|
|
152
|
+
const fill = value === undefined ||
|
|
153
|
+
(typeof key !== 'symbol' &&
|
|
154
|
+
!Object.hasOwn(target, key) &&
|
|
155
|
+
value === Object.prototype[key]);
|
|
156
|
+
if (!fill)
|
|
157
|
+
return false;
|
|
158
|
+
if (key === '__proto__')
|
|
159
|
+
return Object.getPrototypeOf(target) === null;
|
|
160
|
+
const desc = descriptorAlongChain(target, key);
|
|
161
|
+
return !(desc && 'get' in desc && desc.set === undefined);
|
|
162
|
+
}
|
|
163
|
+
function writeDefault(target, key, value) {
|
|
164
|
+
const desc = typeof key === 'symbol' ? Object.getOwnPropertyDescriptor(target, key) : undefined;
|
|
165
|
+
if (desc && 'value' in desc) {
|
|
166
|
+
;
|
|
167
|
+
target[key] = value;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
writeKey(target, key, value);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Fill each key of `target` that reads `undefined` with the first source that has it, and return
|
|
175
|
+
* `target`, as lodash's `defaults` does. A string key only inherited from `Object.prototype`
|
|
176
|
+
* (such as `toString`) also counts as missing; one inherited from a class does not. A key `target`
|
|
177
|
+
* has only a getter for is left alone. Only the own enumerable string and symbol keys of each
|
|
178
|
+
* source are considered.
|
|
179
|
+
* Existing own symbol data properties keep their descriptors when filled.
|
|
180
|
+
* @param target The object to fill.
|
|
181
|
+
* @param sources Objects to read defaults from, left to right; sources that are not objects are
|
|
182
|
+
* skipped.
|
|
183
|
+
* @returns `target`.
|
|
184
|
+
* @throws {TypeError} when `target` is not an object.
|
|
185
|
+
* @throws {RangeError} for an array index past the end of an array `target`.
|
|
186
|
+
* @example
|
|
187
|
+
* ```ts
|
|
188
|
+
* defaults({ a: 1 }, { a: 3, b: 2 }) // { a: 1, b: 2 }
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
export function defaults(target, ...sources) {
|
|
192
|
+
assertTarget(target, 'defaults');
|
|
193
|
+
for (const source of sources) {
|
|
194
|
+
if (source === null || typeof source !== 'object')
|
|
195
|
+
continue;
|
|
196
|
+
const record = source;
|
|
197
|
+
for (const key of enumerableKeys(record)) {
|
|
198
|
+
if (!needsDefault(target, key, target[key]))
|
|
199
|
+
continue;
|
|
200
|
+
const value = record[key];
|
|
201
|
+
assertInRange(target, key, value);
|
|
202
|
+
writeDefault(target, key, value);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return target;
|
|
206
|
+
}
|
|
207
|
+
/** `value`, or a copy of it filled the way `defaultsDeep` fills, when it is a plain object or array. */
|
|
208
|
+
function copyOf(value, state) {
|
|
209
|
+
if (!Array.isArray(value) && !isPlainObject(value))
|
|
210
|
+
return value;
|
|
211
|
+
return (state.copies.get(value) ?? copyWith(state, value, (copy) => fillInto(copy, value, state, false)));
|
|
212
|
+
}
|
|
213
|
+
function fillKey(t, s, key, state) {
|
|
214
|
+
const tv = t[key];
|
|
215
|
+
if (needsDefault(t, key, tv)) {
|
|
216
|
+
const sv = s[key];
|
|
217
|
+
if (sv === undefined)
|
|
218
|
+
return;
|
|
219
|
+
assertInRange(t, key, sv);
|
|
220
|
+
writeDefault(t, key, copyOf(sv, state));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (!Object.hasOwn(t, key))
|
|
224
|
+
return;
|
|
225
|
+
const tvArray = Array.isArray(tv);
|
|
226
|
+
if (!tvArray && !isPlainObject(tv))
|
|
227
|
+
return;
|
|
228
|
+
const sv = s[key];
|
|
229
|
+
const sameKind = tvArray ? Array.isArray(sv) : isPlainObject(sv);
|
|
230
|
+
if (sameKind)
|
|
231
|
+
fillInto(tv, sv, state, true);
|
|
232
|
+
}
|
|
233
|
+
function fillInto(t, s, state, existing) {
|
|
234
|
+
walkInto(t, s, state, existing, (key) => fillKey(t, s, key, state));
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* {@link defaults}, all the way down: where `target` and a source both hold a plain object (or
|
|
238
|
+
* both an array) at a key, the source fills the missing keys inside it. A filled plain object or
|
|
239
|
+
* array is copied, with its prototype, so `target` never shares it with a source; a cycle back to
|
|
240
|
+
* a source itself points at `target`.
|
|
241
|
+
* Filling `__proto__` is refused unless the target has a null prototype; `constructor` and
|
|
242
|
+
* `prototype` are filled as own data, and inherited values are never descended into.
|
|
243
|
+
* Only own enumerable string and symbol keys of each source are considered.
|
|
244
|
+
* @param target The object to fill.
|
|
245
|
+
* @param sources Objects to read defaults from, left to right; sources that are not objects are
|
|
246
|
+
* skipped.
|
|
247
|
+
* @returns `target`.
|
|
248
|
+
* @throws {TypeError} when `target` is not an object.
|
|
249
|
+
* @throws {RangeError} past 1000 levels of nesting, for an index past the end of an array, for a
|
|
250
|
+
* huge sparse source array, or when shared objects would be copied past 500000 units of work.
|
|
251
|
+
* @example
|
|
252
|
+
* ```ts
|
|
253
|
+
* defaultsDeep({ a: { b: 2 } }, { a: { b: 1, c: 3 } }) // { a: { b: 2, c: 3 } }
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
export function defaultsDeep(target, ...sources) {
|
|
257
|
+
assertTarget(target, 'defaultsDeep');
|
|
258
|
+
const budget = new RevisitBudget(MERGE_BUDGET, BUDGET_ERROR);
|
|
259
|
+
return guardDepth(() => {
|
|
260
|
+
for (const source of sources) {
|
|
261
|
+
if (source === null || typeof source !== 'object')
|
|
262
|
+
continue;
|
|
263
|
+
const state = {
|
|
264
|
+
copies: new WeakMap([[source, target]]),
|
|
265
|
+
pairs: new WeakMap(),
|
|
266
|
+
budget
|
|
267
|
+
};
|
|
268
|
+
fillInto(target, source, state, true);
|
|
269
|
+
}
|
|
270
|
+
return target;
|
|
271
|
+
});
|
|
272
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The path grammar behind `get`, `set`, `has`, `unset`, `pick` and `omit`.
|
|
3
|
+
*/
|
|
4
|
+
/** A dotted string with `[0]` and `['key']` segments, or an array of keys. */
|
|
5
|
+
export type Path = string | readonly PropertyKey[];
|
|
6
|
+
/** Segments that lead to a prototype unless they are own data. */
|
|
7
|
+
export declare const DANGEROUS_KEYS: ReadonlySet<PropertyKey>;
|
|
8
|
+
/**
|
|
9
|
+
* Write `value` under `key`: a plain assignment for an ordinary string key, so a setter on `target`
|
|
10
|
+
* runs, and an own data property for a symbol or one of {@link DANGEROUS_KEYS}, so `__proto__`
|
|
11
|
+
* stays a key and no write reaches a prototype.
|
|
12
|
+
*/
|
|
13
|
+
export declare function writeKey(target: object, key: PropertyKey, value: unknown): void;
|
|
14
|
+
/**
|
|
15
|
+
* The segments of `path`. A string splits on `.`, on `[0]` index segments and on `['key']` or
|
|
16
|
+
* `["key"]` segments (with `\\` escaping a quote); an array is taken as its keys, a number segment
|
|
17
|
+
* as its string.
|
|
18
|
+
* @throws {TypeError} for a malformed string path or a segment that is not a string, number or
|
|
19
|
+
* symbol.
|
|
20
|
+
* @throws {RangeError} for a string path over 2^20 characters or a huge sparse array.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parsePath(path: Path): PropertyKey[];
|