@hyperfrontend/list-utils 0.0.3 → 0.0.5
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/CHANGELOG.md +15 -5
- package/README.md +6 -3
- package/_dependencies/@hyperfrontend/data-utils/index.cjs.js +214 -0
- package/_dependencies/@hyperfrontend/data-utils/index.esm.js +202 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/array/index.cjs.js +7 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/array/index.esm.js +5 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/date/index.cjs.js +8 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/date/index.esm.js +7 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/error/index.cjs.js +6 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/error/index.esm.js +5 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/map/index.cjs.js +6 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/map/index.esm.js +5 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/math/index.cjs.js +7 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/math/index.esm.js +5 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/object/index.cjs.js +7 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/object/index.esm.js +5 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/set/index.cjs.js +6 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/set/index.esm.js +5 -0
- package/bundle/index.iife.js +119 -50
- package/bundle/index.iife.min.js +1 -2
- package/bundle/index.umd.js +119 -50
- package/bundle/index.umd.min.js +1 -2
- package/index.cjs.js +85 -122
- package/index.d.ts +169 -8
- package/index.d.ts.map +1 -1
- package/index.esm.js +71 -108
- package/package.json +12 -7
- package/bundle/index.iife.js.map +0 -1
- package/bundle/index.iife.min.js.map +0 -1
- package/bundle/index.umd.js.map +0 -1
- package/bundle/index.umd.min.js.map +0 -1
- package/create-fifo-list.d.ts +0 -18
- package/create-fifo-list.d.ts.map +0 -1
- package/create-lifo-list.d.ts +0 -18
- package/create-lifo-list.d.ts.map +0 -1
- package/create-range.d.ts +0 -9
- package/create-range.d.ts.map +0 -1
- package/create-value-picker.d.ts +0 -12
- package/create-value-picker.d.ts.map +0 -1
- package/get-last-key-in-map.d.ts +0 -2
- package/get-last-key-in-map.d.ts.map +0 -1
- package/index.cjs.js.map +0 -1
- package/index.esm.js.map +0 -1
- package/non-empty-strings.d.ts +0 -8
- package/non-empty-strings.d.ts.map +0 -1
- package/unique-strings.d.ts +0 -8
- package/unique-strings.d.ts.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [0.0.
|
|
5
|
+
## [0.0.5](https://github.com/AndrewRedican/hyperfrontend/compare/c8db08be8b183addd26caf81fdd17fb3693f296f...466c0388c4cd516b9c704214140b4df1004098e6) - 2026-06-23
|
|
6
6
|
|
|
7
|
+
### Other
|
|
8
|
+
|
|
9
|
+
- **@hyperfrontend/workspace:** remove lib-builder and tool-package as implicit dependencies for all lib projects
|
|
10
|
+
|
|
11
|
+
## [0.0.4](https://github.com/AndrewRedican/hyperfrontend/compare/lib-list-utils@0.0.3...lib-list-utils@0.0.4) - 2026-03-08
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [0.0.3](https://github.com/AndrewRedican/hyperfrontend/compare/lib-list-utils@0.0.2...lib-list-utils@0.0.3) - 2026-03-02
|
|
7
15
|
|
|
8
16
|
### Bug Fixes
|
|
9
17
|
|
|
10
|
-
|
|
18
|
+
- **lib-list-utils:** correct package exports ([5966537](https://github.com/AndrewRedican/hyperfrontend/commit/5966537bda3addd6d3bc22bb7b2e9a5cfef7c247))
|
|
19
|
+
|
|
20
|
+
## [0.0.2](https://github.com/AndrewRedican/hyperfrontend/compare/lib-list-utils@0.0.1...lib-list-utils@0.0.2) - 2026-02-26
|
|
21
|
+
|
|
11
22
|
|
|
12
|
-
##
|
|
23
|
+
## 0.0.1 - 2026-02-15
|
|
13
24
|
|
|
14
|
-
## 0.0.1 (2026-02-15)
|
package/README.md
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
<a href="https://www.npmjs.com/package/@hyperfrontend/list-utils">
|
|
11
11
|
<img src="https://img.shields.io/npm/v/@hyperfrontend/list-utils?style=flat-square" alt="npm version">
|
|
12
12
|
</a>
|
|
13
|
+
<a href="https://bundlephobia.com/package/@hyperfrontend/list-utils">
|
|
14
|
+
<img src="https://img.shields.io/bundlephobia/min/%40hyperfrontend%2Flist-utils?style=flat-square" alt="npm bundle size">
|
|
15
|
+
</a>
|
|
13
16
|
</p>
|
|
14
17
|
<p align="center">
|
|
15
18
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
@@ -32,6 +35,8 @@
|
|
|
32
35
|
|
|
33
36
|
Purpose-built collection utilities for queue management, filtering, and iteration patterns.
|
|
34
37
|
|
|
38
|
+
• 👉 See [**documentation**](https://www.hyperfrontend.dev/docs/libraries/utils/list/)
|
|
39
|
+
|
|
35
40
|
## What is @hyperfrontend/list-utils?
|
|
36
41
|
|
|
37
42
|
@hyperfrontend/list-utils provides specialized collection utilities focused on common programming patterns that native JavaScript arrays don't handle elegantly. Rather than replicating lodash, this library targets specific use cases: FIFO/LIFO queue management with object reference tracking, cyclical value iteration, and string array sanitization.
|
|
@@ -137,8 +142,6 @@ const cleaned = uniqueStrings(nonEmptyStrings(userInputs)) // ['hello', 'world']
|
|
|
137
142
|
| IIFE | `bundle/index.iife.min.js` | ❌ |
|
|
138
143
|
| UMD | `bundle/index.umd.min.js` | ❌ |
|
|
139
144
|
|
|
140
|
-
**Bundle size:** 1 KB (minified, self-contained)
|
|
141
|
-
|
|
142
145
|
### CDN Usage
|
|
143
146
|
|
|
144
147
|
```html
|
|
@@ -165,4 +168,4 @@ This library is part of the [hyperfrontend](https://github.com/AndrewRedican/hyp
|
|
|
165
168
|
|
|
166
169
|
## License
|
|
167
170
|
|
|
168
|
-
MIT
|
|
171
|
+
[MIT](https://github.com/AndrewRedican/hyperfrontend/blob/main/LICENSE.md)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isArray } = require('../immutable-api-utils/built-in-copy/array/index.cjs.js');
|
|
4
|
+
const { createError } = require('../immutable-api-utils/built-in-copy/error/index.cjs.js');
|
|
5
|
+
const { freeze, keys: keys$1 } = require('../immutable-api-utils/built-in-copy/object/index.cjs.js');
|
|
6
|
+
const { createMap } = require('../immutable-api-utils/built-in-copy/map/index.cjs.js');
|
|
7
|
+
const { createDate } = require('../immutable-api-utils/built-in-copy/date/index.cjs.js');
|
|
8
|
+
const { random, round } = require('../immutable-api-utils/built-in-copy/math/index.cjs.js');
|
|
9
|
+
|
|
10
|
+
const isMarker = (text) => {
|
|
11
|
+
if (typeof text !== 'string' || !text.startsWith('__$'))
|
|
12
|
+
return false;
|
|
13
|
+
return /^__\$[0-9]+$/.test(text);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const registeredClasses = [];
|
|
17
|
+
const registeredIterableClasses = [
|
|
18
|
+
{
|
|
19
|
+
classRef: Array,
|
|
20
|
+
instantiate: () => [],
|
|
21
|
+
getKeys: (target) => {
|
|
22
|
+
const keysArray = keys$1(target);
|
|
23
|
+
if (getConfig().detectCircularReferences) {
|
|
24
|
+
return keysArray.filter((key) => !isMarker(key));
|
|
25
|
+
}
|
|
26
|
+
return keysArray;
|
|
27
|
+
},
|
|
28
|
+
read: (target, key) => target[key],
|
|
29
|
+
write: (target, value, key) => (target[key] = value),
|
|
30
|
+
remove: (target, value) => target.splice(value, 1),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
classRef: Object,
|
|
34
|
+
instantiate: () => ({}),
|
|
35
|
+
getKeys: (target) => {
|
|
36
|
+
const keysArray = keys$1(target);
|
|
37
|
+
if (getConfig().detectCircularReferences) {
|
|
38
|
+
return keysArray.filter((key) => !isMarker(key));
|
|
39
|
+
}
|
|
40
|
+
return keysArray;
|
|
41
|
+
},
|
|
42
|
+
read: (target, key) => target[key],
|
|
43
|
+
write: (target, value, key) => (target[key] = value),
|
|
44
|
+
remove: (target, value) => delete target[value],
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
let samePositionOfOwnProperties = false;
|
|
48
|
+
let detectCircularReferences = false;
|
|
49
|
+
const getConfig = () => ({
|
|
50
|
+
samePositionOfOwnProperties,
|
|
51
|
+
detectCircularReferences,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const getKeysFromIterable = (target, dataType) => {
|
|
55
|
+
if (dataType === 'array')
|
|
56
|
+
dataType = Array.name;
|
|
57
|
+
if (dataType === 'object')
|
|
58
|
+
dataType = Object.name;
|
|
59
|
+
const iterableClass = registeredIterableClasses.find(({ classRef }) => dataType === classRef.name);
|
|
60
|
+
if (iterableClass === undefined)
|
|
61
|
+
return [];
|
|
62
|
+
return iterableClass.getKeys(target);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const getType = (target) => {
|
|
66
|
+
if (target === null)
|
|
67
|
+
return 'null';
|
|
68
|
+
const nativeDataType = typeof target;
|
|
69
|
+
if (nativeDataType === 'object') {
|
|
70
|
+
if (isArray(target))
|
|
71
|
+
return 'array';
|
|
72
|
+
for (const registeredClass of registeredClasses) {
|
|
73
|
+
if (target instanceof registeredClass)
|
|
74
|
+
return registeredClass.name;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return nativeDataType;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const getIterableTypes = () => registeredIterableClasses.map(({ classRef }) => {
|
|
81
|
+
const name = classRef.name;
|
|
82
|
+
if (name === Object.name)
|
|
83
|
+
return 'object';
|
|
84
|
+
if (name === Array.name)
|
|
85
|
+
return 'array';
|
|
86
|
+
return name;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const isIterableType = (dataType) => getIterableTypes().includes(dataType);
|
|
90
|
+
|
|
91
|
+
const isIterable = (target) => isIterableType(getType(target));
|
|
92
|
+
|
|
93
|
+
const marker = () => {
|
|
94
|
+
const randomValue = round(random() * 10000000000000);
|
|
95
|
+
const sequential = createDate().getTime();
|
|
96
|
+
const unique = `${randomValue}${sequential}`;
|
|
97
|
+
const prefix = `__$`;
|
|
98
|
+
return `${prefix}${unique}`;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const referenceStack = () => {
|
|
102
|
+
const records = createMap();
|
|
103
|
+
const flag = marker();
|
|
104
|
+
const exists = (ref) => (isIterable(ref) ? flag in ref && records.has(ref[flag]) : false);
|
|
105
|
+
const add = (ref) => {
|
|
106
|
+
if (!isIterable(ref) || exists(ref))
|
|
107
|
+
return;
|
|
108
|
+
ref[flag] = Symbol();
|
|
109
|
+
records.set(ref[flag], [flag, ref, records.size]);
|
|
110
|
+
};
|
|
111
|
+
const lastSeen = (ref) => {
|
|
112
|
+
if (!isIterable(ref))
|
|
113
|
+
return null;
|
|
114
|
+
const record = records.get(ref[flag]);
|
|
115
|
+
return record ? record[2] - records.size : null;
|
|
116
|
+
};
|
|
117
|
+
const clear = () => (records.forEach(([key, ref]) => {
|
|
118
|
+
delete ref[key];
|
|
119
|
+
}),
|
|
120
|
+
records.clear());
|
|
121
|
+
return {
|
|
122
|
+
add: (ref) => add(ref),
|
|
123
|
+
exists: (ref) => exists(ref),
|
|
124
|
+
lastSeen: (ref) => lastSeen(ref),
|
|
125
|
+
clear: () => clear(),
|
|
126
|
+
get size() {
|
|
127
|
+
return records.size;
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const errorMessage = (thing, type) => `Expected ${thing} to be ${type}.`;
|
|
133
|
+
const nextIterationDetails = (path, key, value) => ({
|
|
134
|
+
nextPath: [...path, key],
|
|
135
|
+
nextValue: value[key],
|
|
136
|
+
});
|
|
137
|
+
const circularDependencyTraversal = (condition, callback, config, key, path, value, parent, state, stack, root = false) => {
|
|
138
|
+
if (stack.exists(value))
|
|
139
|
+
return state;
|
|
140
|
+
const ok = condition(config, key, value, path, parent);
|
|
141
|
+
if (config.exitEarly)
|
|
142
|
+
return state;
|
|
143
|
+
if (ok)
|
|
144
|
+
callback(key, value, path, state, parent);
|
|
145
|
+
stack.add(value);
|
|
146
|
+
const type = getType(value);
|
|
147
|
+
if (!isIterableType(type))
|
|
148
|
+
return state;
|
|
149
|
+
const keys = getKeysFromIterable(value, type);
|
|
150
|
+
keys.forEach((key) => {
|
|
151
|
+
const { nextPath, nextValue } = nextIterationDetails(path, key, value);
|
|
152
|
+
circularDependencyTraversal(condition, callback, config, key, nextPath, nextValue, value, state, stack);
|
|
153
|
+
});
|
|
154
|
+
if (root)
|
|
155
|
+
stack.clear();
|
|
156
|
+
return state;
|
|
157
|
+
};
|
|
158
|
+
const nonCircularDependencyTraversal = (condition, callback, config, key, path, value, parent, state) => {
|
|
159
|
+
const ok = condition(config, key, value, path, parent);
|
|
160
|
+
if (config.exitEarly)
|
|
161
|
+
return state;
|
|
162
|
+
if (ok)
|
|
163
|
+
callback(key, value, path, state, parent);
|
|
164
|
+
const type = getType(value);
|
|
165
|
+
if (!isIterableType(type))
|
|
166
|
+
return state;
|
|
167
|
+
const keys = getKeysFromIterable(value, type);
|
|
168
|
+
keys.forEach((key) => {
|
|
169
|
+
const { nextPath, nextValue } = nextIterationDetails(path, key, value);
|
|
170
|
+
nonCircularDependencyTraversal(condition, callback, config, key, nextPath, nextValue, value, state);
|
|
171
|
+
});
|
|
172
|
+
return state;
|
|
173
|
+
};
|
|
174
|
+
const traversal = (target, condition, callback, options, state) => {
|
|
175
|
+
if (typeof callback !== 'function')
|
|
176
|
+
throw createError(errorMessage('callback', 'a function'));
|
|
177
|
+
if (!(typeof options === 'object' && !isArray(options)))
|
|
178
|
+
throw createError(errorMessage('options', 'an object'));
|
|
179
|
+
if (!isArray(options.depth))
|
|
180
|
+
throw createError(errorMessage('options.depth', 'an array'));
|
|
181
|
+
const [startDepth, maxDepth] = options.depth;
|
|
182
|
+
if (startDepth !== void 0 && typeof startDepth !== 'number')
|
|
183
|
+
throw createError(errorMessage('options.depth.0', 'a number'));
|
|
184
|
+
if (maxDepth !== void 0) {
|
|
185
|
+
const maxDepthType = typeof maxDepth;
|
|
186
|
+
if (!['number', 'string'].includes(maxDepthType))
|
|
187
|
+
throw createError(errorMessage('options.depth.1', 'a number or a string'));
|
|
188
|
+
if (maxDepthType === 'string' && maxDepth !== '*')
|
|
189
|
+
throw createError("Only valid string value in options.depth.1 is '*'.");
|
|
190
|
+
}
|
|
191
|
+
const config = {
|
|
192
|
+
depth: freeze([options.depth[0] ?? 0, options.depth[1] ?? '*']),
|
|
193
|
+
exitEarly: false,
|
|
194
|
+
};
|
|
195
|
+
const initialArgs = [condition, callback, config, '', [], target, void 0, state];
|
|
196
|
+
if (getConfig().detectCircularReferences)
|
|
197
|
+
return circularDependencyTraversal(...initialArgs, referenceStack(), true);
|
|
198
|
+
return nonCircularDependencyTraversal(...initialArgs);
|
|
199
|
+
};
|
|
200
|
+
const createTraversal = (condition) => (target, callback, options, state) => traversal(target, condition, callback, options ?? { depth: [0, '*'] }, state ?? {});
|
|
201
|
+
const condition = (config, key, value, path) => !(path.length < config.depth[0] || config.depth[1] < path.length);
|
|
202
|
+
const traverseBetweenDepthRange = createTraversal(condition);
|
|
203
|
+
exports.createTraversal = createTraversal;
|
|
204
|
+
exports.getConfig = getConfig;
|
|
205
|
+
exports.getIterableTypes = getIterableTypes;
|
|
206
|
+
exports.getKeysFromIterable = getKeysFromIterable;
|
|
207
|
+
exports.getType = getType;
|
|
208
|
+
exports.isIterable = isIterable;
|
|
209
|
+
exports.isIterableType = isIterableType;
|
|
210
|
+
exports.isMarker = isMarker;
|
|
211
|
+
exports.marker = marker;
|
|
212
|
+
exports.referenceStack = referenceStack;
|
|
213
|
+
exports.registeredClasses = registeredClasses;
|
|
214
|
+
exports.registeredIterableClasses = registeredIterableClasses;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { isArray } from '../immutable-api-utils/built-in-copy/array/index.esm.js';
|
|
2
|
+
import { createError } from '../immutable-api-utils/built-in-copy/error/index.esm.js';
|
|
3
|
+
import { keys, freeze } from '../immutable-api-utils/built-in-copy/object/index.esm.js';
|
|
4
|
+
import { createMap } from '../immutable-api-utils/built-in-copy/map/index.esm.js';
|
|
5
|
+
import { createDate } from '../immutable-api-utils/built-in-copy/date/index.esm.js';
|
|
6
|
+
import { round, random } from '../immutable-api-utils/built-in-copy/math/index.esm.js';
|
|
7
|
+
|
|
8
|
+
const isMarker = (text) => {
|
|
9
|
+
if (typeof text !== 'string' || !text.startsWith('__$'))
|
|
10
|
+
return false;
|
|
11
|
+
return /^__\$[0-9]+$/.test(text);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const registeredClasses = [];
|
|
15
|
+
const registeredIterableClasses = [
|
|
16
|
+
{
|
|
17
|
+
classRef: Array,
|
|
18
|
+
instantiate: () => [],
|
|
19
|
+
getKeys: (target) => {
|
|
20
|
+
const keysArray = keys(target);
|
|
21
|
+
if (getConfig().detectCircularReferences) {
|
|
22
|
+
return keysArray.filter((key) => !isMarker(key));
|
|
23
|
+
}
|
|
24
|
+
return keysArray;
|
|
25
|
+
},
|
|
26
|
+
read: (target, key) => target[key],
|
|
27
|
+
write: (target, value, key) => (target[key] = value),
|
|
28
|
+
remove: (target, value) => target.splice(value, 1),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
classRef: Object,
|
|
32
|
+
instantiate: () => ({}),
|
|
33
|
+
getKeys: (target) => {
|
|
34
|
+
const keysArray = keys(target);
|
|
35
|
+
if (getConfig().detectCircularReferences) {
|
|
36
|
+
return keysArray.filter((key) => !isMarker(key));
|
|
37
|
+
}
|
|
38
|
+
return keysArray;
|
|
39
|
+
},
|
|
40
|
+
read: (target, key) => target[key],
|
|
41
|
+
write: (target, value, key) => (target[key] = value),
|
|
42
|
+
remove: (target, value) => delete target[value],
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
let samePositionOfOwnProperties = false;
|
|
46
|
+
let detectCircularReferences = false;
|
|
47
|
+
const getConfig = () => ({
|
|
48
|
+
samePositionOfOwnProperties,
|
|
49
|
+
detectCircularReferences,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const getKeysFromIterable = (target, dataType) => {
|
|
53
|
+
if (dataType === 'array')
|
|
54
|
+
dataType = Array.name;
|
|
55
|
+
if (dataType === 'object')
|
|
56
|
+
dataType = Object.name;
|
|
57
|
+
const iterableClass = registeredIterableClasses.find(({ classRef }) => dataType === classRef.name);
|
|
58
|
+
if (iterableClass === undefined)
|
|
59
|
+
return [];
|
|
60
|
+
return iterableClass.getKeys(target);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const getType = (target) => {
|
|
64
|
+
if (target === null)
|
|
65
|
+
return 'null';
|
|
66
|
+
const nativeDataType = typeof target;
|
|
67
|
+
if (nativeDataType === 'object') {
|
|
68
|
+
if (isArray(target))
|
|
69
|
+
return 'array';
|
|
70
|
+
for (const registeredClass of registeredClasses) {
|
|
71
|
+
if (target instanceof registeredClass)
|
|
72
|
+
return registeredClass.name;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return nativeDataType;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const getIterableTypes = () => registeredIterableClasses.map(({ classRef }) => {
|
|
79
|
+
const name = classRef.name;
|
|
80
|
+
if (name === Object.name)
|
|
81
|
+
return 'object';
|
|
82
|
+
if (name === Array.name)
|
|
83
|
+
return 'array';
|
|
84
|
+
return name;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const isIterableType = (dataType) => getIterableTypes().includes(dataType);
|
|
88
|
+
|
|
89
|
+
const isIterable = (target) => isIterableType(getType(target));
|
|
90
|
+
|
|
91
|
+
const marker = () => {
|
|
92
|
+
const randomValue = round(random() * 10000000000000);
|
|
93
|
+
const sequential = createDate().getTime();
|
|
94
|
+
const unique = `${randomValue}${sequential}`;
|
|
95
|
+
const prefix = `__$`;
|
|
96
|
+
return `${prefix}${unique}`;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const referenceStack = () => {
|
|
100
|
+
const records = createMap();
|
|
101
|
+
const flag = marker();
|
|
102
|
+
const exists = (ref) => (isIterable(ref) ? flag in ref && records.has(ref[flag]) : false);
|
|
103
|
+
const add = (ref) => {
|
|
104
|
+
if (!isIterable(ref) || exists(ref))
|
|
105
|
+
return;
|
|
106
|
+
ref[flag] = Symbol();
|
|
107
|
+
records.set(ref[flag], [flag, ref, records.size]);
|
|
108
|
+
};
|
|
109
|
+
const lastSeen = (ref) => {
|
|
110
|
+
if (!isIterable(ref))
|
|
111
|
+
return null;
|
|
112
|
+
const record = records.get(ref[flag]);
|
|
113
|
+
return record ? record[2] - records.size : null;
|
|
114
|
+
};
|
|
115
|
+
const clear = () => (records.forEach(([key, ref]) => {
|
|
116
|
+
delete ref[key];
|
|
117
|
+
}),
|
|
118
|
+
records.clear());
|
|
119
|
+
return {
|
|
120
|
+
add: (ref) => add(ref),
|
|
121
|
+
exists: (ref) => exists(ref),
|
|
122
|
+
lastSeen: (ref) => lastSeen(ref),
|
|
123
|
+
clear: () => clear(),
|
|
124
|
+
get size() {
|
|
125
|
+
return records.size;
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const errorMessage = (thing, type) => `Expected ${thing} to be ${type}.`;
|
|
131
|
+
const nextIterationDetails = (path, key, value) => ({
|
|
132
|
+
nextPath: [...path, key],
|
|
133
|
+
nextValue: value[key],
|
|
134
|
+
});
|
|
135
|
+
const circularDependencyTraversal = (condition, callback, config, key, path, value, parent, state, stack, root = false) => {
|
|
136
|
+
if (stack.exists(value))
|
|
137
|
+
return state;
|
|
138
|
+
const ok = condition(config, key, value, path, parent);
|
|
139
|
+
if (config.exitEarly)
|
|
140
|
+
return state;
|
|
141
|
+
if (ok)
|
|
142
|
+
callback(key, value, path, state, parent);
|
|
143
|
+
stack.add(value);
|
|
144
|
+
const type = getType(value);
|
|
145
|
+
if (!isIterableType(type))
|
|
146
|
+
return state;
|
|
147
|
+
const keys = getKeysFromIterable(value, type);
|
|
148
|
+
keys.forEach((key) => {
|
|
149
|
+
const { nextPath, nextValue } = nextIterationDetails(path, key, value);
|
|
150
|
+
circularDependencyTraversal(condition, callback, config, key, nextPath, nextValue, value, state, stack);
|
|
151
|
+
});
|
|
152
|
+
if (root)
|
|
153
|
+
stack.clear();
|
|
154
|
+
return state;
|
|
155
|
+
};
|
|
156
|
+
const nonCircularDependencyTraversal = (condition, callback, config, key, path, value, parent, state) => {
|
|
157
|
+
const ok = condition(config, key, value, path, parent);
|
|
158
|
+
if (config.exitEarly)
|
|
159
|
+
return state;
|
|
160
|
+
if (ok)
|
|
161
|
+
callback(key, value, path, state, parent);
|
|
162
|
+
const type = getType(value);
|
|
163
|
+
if (!isIterableType(type))
|
|
164
|
+
return state;
|
|
165
|
+
const keys = getKeysFromIterable(value, type);
|
|
166
|
+
keys.forEach((key) => {
|
|
167
|
+
const { nextPath, nextValue } = nextIterationDetails(path, key, value);
|
|
168
|
+
nonCircularDependencyTraversal(condition, callback, config, key, nextPath, nextValue, value, state);
|
|
169
|
+
});
|
|
170
|
+
return state;
|
|
171
|
+
};
|
|
172
|
+
const traversal = (target, condition, callback, options, state) => {
|
|
173
|
+
if (typeof callback !== 'function')
|
|
174
|
+
throw createError(errorMessage('callback', 'a function'));
|
|
175
|
+
if (!(typeof options === 'object' && !isArray(options)))
|
|
176
|
+
throw createError(errorMessage('options', 'an object'));
|
|
177
|
+
if (!isArray(options.depth))
|
|
178
|
+
throw createError(errorMessage('options.depth', 'an array'));
|
|
179
|
+
const [startDepth, maxDepth] = options.depth;
|
|
180
|
+
if (startDepth !== void 0 && typeof startDepth !== 'number')
|
|
181
|
+
throw createError(errorMessage('options.depth.0', 'a number'));
|
|
182
|
+
if (maxDepth !== void 0) {
|
|
183
|
+
const maxDepthType = typeof maxDepth;
|
|
184
|
+
if (!['number', 'string'].includes(maxDepthType))
|
|
185
|
+
throw createError(errorMessage('options.depth.1', 'a number or a string'));
|
|
186
|
+
if (maxDepthType === 'string' && maxDepth !== '*')
|
|
187
|
+
throw createError("Only valid string value in options.depth.1 is '*'.");
|
|
188
|
+
}
|
|
189
|
+
const config = {
|
|
190
|
+
depth: freeze([options.depth[0] ?? 0, options.depth[1] ?? '*']),
|
|
191
|
+
exitEarly: false,
|
|
192
|
+
};
|
|
193
|
+
const initialArgs = [condition, callback, config, '', [], target, void 0, state];
|
|
194
|
+
if (getConfig().detectCircularReferences)
|
|
195
|
+
return circularDependencyTraversal(...initialArgs, referenceStack(), true);
|
|
196
|
+
return nonCircularDependencyTraversal(...initialArgs);
|
|
197
|
+
};
|
|
198
|
+
const createTraversal = (condition) => (target, callback, options, state) => traversal(target, condition, callback, options ?? { depth: [0, '*'] }, state ?? {});
|
|
199
|
+
const condition = (config, key, value, path) => !(path.length < config.depth[0] || config.depth[1] < path.length);
|
|
200
|
+
const traverseBetweenDepthRange = createTraversal(condition);
|
|
201
|
+
|
|
202
|
+
export { createTraversal, getConfig, getIterableTypes, getKeysFromIterable, getType, isIterable, isIterableType, isMarker, marker, referenceStack, registeredClasses, registeredIterableClasses };
|