@qezor/structkit 1.0.0 → 1.0.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/lib/deep.js ADDED
@@ -0,0 +1,435 @@
1
+ "use strict"
2
+
3
+ const {
4
+ isObjectLike,
5
+ isPlainObject,
6
+ tokenizePath,
7
+ getValueByPath,
8
+ cloneDeep,
9
+ stringifyPathTokens,
10
+ getValueByTokens,
11
+ setValueByTokens,
12
+ deleteByTokens,
13
+ } = require("./shared.js")
14
+ const { isEqual } = require("./compare.js")
15
+ const { normalizeCount } = require("./range.js")
16
+ const { insertValue } = require("./insert.js")
17
+
18
+ function isTraversable(value) {
19
+ return Array.isArray(value) || isPlainObject(value)
20
+ }
21
+
22
+ function getNodeType(value) {
23
+ if (value === null) return "null"
24
+ if (Array.isArray(value)) return "array"
25
+ if (value instanceof Date) return "date"
26
+ if (value instanceof RegExp) return "regexp"
27
+ if (value instanceof Map) return "map"
28
+ if (value instanceof Set) return "set"
29
+ return typeof value === "object" ? "object" : typeof value
30
+ }
31
+
32
+ function toPositiveLimit(value, fallback) {
33
+ if (value == null) return fallback
34
+ const next = normalizeCount(value, fallback)
35
+ return next === 0 ? 0 : next
36
+ }
37
+
38
+ function normalizeDeepOptions(options = {}) {
39
+ return {
40
+ fromPath: options.fromPath ?? options.scope ?? [],
41
+ minDepth: normalizeCount(options.minDepth, 0),
42
+ maxDepth: options.maxDepth == null ? Number.POSITIVE_INFINITY : normalizeCount(options.maxDepth, 0),
43
+ includeRoot: options.includeRoot !== false,
44
+ includeContainers: options.includeContainers !== false,
45
+ includeLeaves: options.includeLeaves !== false,
46
+ order: options.order === "bfs" ? "bfs" : "dfs",
47
+ greedy: options.greedy ?? false,
48
+ limit: options.limit == null ? Number.POSITIVE_INFINITY : toPositiveLimit(options.limit, Number.POSITIVE_INFINITY),
49
+ maxNodes: options.maxNodes == null ? 100000 : toPositiveLimit(options.maxNodes, 100000),
50
+ }
51
+ }
52
+
53
+ function createDeepMatcher(predicate) {
54
+ if (typeof predicate === "function") return predicate
55
+
56
+ if (typeof predicate === "string") {
57
+ const expected = predicate
58
+ return (entry) => entry.path === expected || entry.key === expected
59
+ }
60
+
61
+ if (predicate && typeof predicate === "object" && !Array.isArray(predicate)) {
62
+ const query = predicate
63
+ return (entry) => {
64
+ if (query.path != null && entry.path !== String(query.path)) return false
65
+ if (query.key != null && entry.key !== String(query.key)) return false
66
+ if (query.type != null && entry.type !== String(query.type)) return false
67
+ if (query.depth != null && entry.depth !== Number(query.depth)) return false
68
+ if (query.pathDepth != null && entry.pathDepth !== Number(query.pathDepth)) return false
69
+ if (query.absoluteDepth != null && entry.absoluteDepth !== Number(query.absoluteDepth)) return false
70
+ if (query.minDepth != null && entry.depth < Number(query.minDepth)) return false
71
+ if (query.maxDepth != null && entry.depth > Number(query.maxDepth)) return false
72
+ if (query.minPathDepth != null && entry.pathDepth < Number(query.minPathDepth)) return false
73
+ if (query.maxPathDepth != null && entry.pathDepth > Number(query.maxPathDepth)) return false
74
+ if (Object.prototype.hasOwnProperty.call(query, "value") && !isEqual(entry.value, query.value)) return false
75
+
76
+ if (query.includes != null) {
77
+ const needle = String(query.includes).toLowerCase()
78
+ const keyHit = typeof entry.key === "string" && entry.key.toLowerCase().includes(needle)
79
+ const pathHit = entry.path.toLowerCase().includes(needle)
80
+ const valueHit = typeof entry.value === "string" && entry.value.toLowerCase().includes(needle)
81
+ if (!keyHit && !pathHit && !valueHit) return false
82
+ }
83
+
84
+ return true
85
+ }
86
+ }
87
+
88
+ if (predicate === undefined) return () => true
89
+ return (entry) => Object.is(entry.value, predicate)
90
+ }
91
+
92
+ function createEntry(frame) {
93
+ const value = frame.value
94
+ const isContainer = isTraversable(value)
95
+
96
+ return {
97
+ value,
98
+ parent: frame.parent,
99
+ key: frame.key == null ? null : String(frame.key),
100
+ tokens: frame.tokens.slice(),
101
+ path: stringifyPathTokens(frame.tokens),
102
+ depth: frame.depth,
103
+ pathDepth: frame.pathDepth,
104
+ absoluteDepth: frame.absoluteDepth,
105
+ type: getNodeType(value),
106
+ isContainer,
107
+ isLeaf: !isContainer,
108
+ }
109
+ }
110
+
111
+ function pushChildFrames(stack, frame, options, enqueue) {
112
+ const parentValue = frame.value
113
+ const isArrayParent = Array.isArray(parentValue)
114
+
115
+ const pushChild = (key, childValue) => {
116
+ const childIsContainer = isTraversable(childValue)
117
+ const childDepth = childIsContainer ? frame.depth + 1 : frame.depth
118
+ if (childDepth > options.maxDepth) return
119
+
120
+ enqueue({
121
+ value: childValue,
122
+ parent: parentValue,
123
+ key,
124
+ tokens: frame.tokens.concat(key),
125
+ depth: childDepth,
126
+ pathDepth: frame.pathDepth + 1,
127
+ absoluteDepth: frame.absoluteDepth + 1,
128
+ parentIsArray: isArrayParent,
129
+ })
130
+ }
131
+
132
+ if (Array.isArray(parentValue)) {
133
+ stack(parentValue.length, (index) => pushChild(index, parentValue[index]))
134
+ return
135
+ }
136
+
137
+ const keys = Object.keys(parentValue)
138
+ stack(keys.length, (index) => {
139
+ const key = keys[index]
140
+ pushChild(key, parentValue[key])
141
+ })
142
+ }
143
+
144
+ function* iterateDepthFirst(root, options) {
145
+ const scopeTokens = tokenizePath(options.fromPath)
146
+ const scopeValue = scopeTokens.length ? getValueByPath(root, scopeTokens) : root
147
+
148
+ if (scopeTokens.length && scopeValue === undefined) return
149
+
150
+ const initialParent = scopeTokens.length ? getValueByPath(root, scopeTokens.slice(0, -1)) : undefined
151
+ const initialKey = scopeTokens.length ? scopeTokens[scopeTokens.length - 1] : null
152
+ const stack = [{
153
+ value: scopeValue,
154
+ parent: initialParent,
155
+ key: initialKey,
156
+ tokens: scopeTokens,
157
+ depth: 0,
158
+ pathDepth: 0,
159
+ absoluteDepth: scopeTokens.length,
160
+ }]
161
+
162
+ let visited = 0
163
+ let yielded = 0
164
+
165
+ while (stack.length) {
166
+ const frame = stack.pop()
167
+ visited += 1
168
+
169
+ if (visited > options.maxNodes) {
170
+ const error = new Error(`deep traversal exceeded maxNodes=${options.maxNodes}`)
171
+ error.code = "EMAXNODES"
172
+ throw error
173
+ }
174
+
175
+ const entry = createEntry(frame)
176
+ const shouldYield = (frame.depth > 0 || options.includeRoot)
177
+ && frame.depth >= options.minDepth
178
+ && ((entry.isContainer && options.includeContainers) || (entry.isLeaf && options.includeLeaves))
179
+
180
+ if (shouldYield) {
181
+ yield entry
182
+ yielded += 1
183
+ if (yielded >= options.limit) return
184
+ }
185
+
186
+ if (!entry.isContainer) continue
187
+
188
+ pushChildFrames(
189
+ (length, iteratee) => {
190
+ for (let index = length - 1; index >= 0; index -= 1) {
191
+ iteratee(index)
192
+ }
193
+ },
194
+ frame,
195
+ options,
196
+ (nextFrame) => stack.push(nextFrame)
197
+ )
198
+ }
199
+ }
200
+
201
+ function* iterateBreadthFirst(root, options) {
202
+ const scopeTokens = tokenizePath(options.fromPath)
203
+ const scopeValue = scopeTokens.length ? getValueByPath(root, scopeTokens) : root
204
+
205
+ if (scopeTokens.length && scopeValue === undefined) return
206
+
207
+ const queue = [{
208
+ value: scopeValue,
209
+ parent: scopeTokens.length ? getValueByPath(root, scopeTokens.slice(0, -1)) : undefined,
210
+ key: scopeTokens.length ? scopeTokens[scopeTokens.length - 1] : null,
211
+ tokens: scopeTokens,
212
+ depth: 0,
213
+ pathDepth: 0,
214
+ absoluteDepth: scopeTokens.length,
215
+ }]
216
+
217
+ let cursor = 0
218
+ let visited = 0
219
+ let yielded = 0
220
+
221
+ while (cursor < queue.length) {
222
+ const frame = queue[cursor]
223
+ cursor += 1
224
+ visited += 1
225
+
226
+ if (visited > options.maxNodes) {
227
+ const error = new Error(`deep traversal exceeded maxNodes=${options.maxNodes}`)
228
+ error.code = "EMAXNODES"
229
+ throw error
230
+ }
231
+
232
+ const entry = createEntry(frame)
233
+ const shouldYield = (frame.depth > 0 || options.includeRoot)
234
+ && frame.depth >= options.minDepth
235
+ && ((entry.isContainer && options.includeContainers) || (entry.isLeaf && options.includeLeaves))
236
+
237
+ if (shouldYield) {
238
+ yield entry
239
+ yielded += 1
240
+ if (yielded >= options.limit) return
241
+ }
242
+
243
+ if (!entry.isContainer) continue
244
+
245
+ pushChildFrames(
246
+ (length, iteratee) => {
247
+ for (let index = 0; index < length; index += 1) {
248
+ iteratee(index)
249
+ }
250
+ },
251
+ frame,
252
+ options,
253
+ (nextFrame) => queue.push(nextFrame)
254
+ )
255
+ }
256
+ }
257
+
258
+ function iterateDeep(value, options = {}) {
259
+ const normalized = normalizeDeepOptions(options)
260
+ return normalized.order === "bfs"
261
+ ? iterateBreadthFirst(value, normalized)
262
+ : iterateDepthFirst(value, normalized)
263
+ }
264
+
265
+ function isBetterGreedyMatch(current, next, mode) {
266
+ if (!current) return true
267
+
268
+ if (mode === true || mode === "last") return true
269
+
270
+ if (mode === "deepest") {
271
+ if (next.depth !== current.depth) return next.depth > current.depth
272
+ if (next.pathDepth !== current.pathDepth) return next.pathDepth > current.pathDepth
273
+ return next.path >= current.path
274
+ }
275
+
276
+ if (mode === "path" || mode === "deepest-path") {
277
+ if (next.pathDepth !== current.pathDepth) return next.pathDepth > current.pathDepth
278
+ if (next.depth !== current.depth) return next.depth > current.depth
279
+ return next.path >= current.path
280
+ }
281
+
282
+ return false
283
+ }
284
+
285
+ function findDeep(value, predicate, options = {}) {
286
+ const normalized = normalizeDeepOptions(options)
287
+ const match = createDeepMatcher(predicate)
288
+ let selected
289
+
290
+ for (const entry of iterateDeep(value, options)) {
291
+ if (!match(entry)) continue
292
+ if (!normalized.greedy) return entry
293
+ if (isBetterGreedyMatch(selected, entry, normalized.greedy)) {
294
+ selected = entry
295
+ }
296
+ }
297
+
298
+ return selected
299
+ }
300
+
301
+ function filterDeep(value, predicate, options = {}) {
302
+ const match = createDeepMatcher(predicate)
303
+ const output = []
304
+ for (const entry of iterateDeep(value, options)) {
305
+ if (!match(entry)) continue
306
+ output.push(entry)
307
+ }
308
+ return output
309
+ }
310
+
311
+ function findPaths(value, predicate, options = {}) {
312
+ const output = []
313
+ for (const entry of filterDeep(value, predicate, options)) {
314
+ output.push(entry.path)
315
+ }
316
+ return output
317
+ }
318
+
319
+ function compareTokenPathsDescending(left, right) {
320
+ if (left.tokens.length !== right.tokens.length) {
321
+ return right.tokens.length - left.tokens.length
322
+ }
323
+
324
+ const length = Math.max(left.tokens.length, right.tokens.length)
325
+ for (let index = 0; index < length; index += 1) {
326
+ const leftToken = left.tokens[index]
327
+ const rightToken = right.tokens[index]
328
+ if (leftToken === rightToken) continue
329
+
330
+ if (typeof leftToken === "number" && typeof rightToken === "number") {
331
+ return rightToken - leftToken
332
+ }
333
+
334
+ return String(rightToken).localeCompare(String(leftToken))
335
+ }
336
+
337
+ return 0
338
+ }
339
+
340
+ function mapDeep(value, predicate, updater, options = {}) {
341
+ if (typeof updater !== "function") {
342
+ throw new TypeError("mapDeep requires an updater function")
343
+ }
344
+
345
+ let output = cloneDeep(value)
346
+ const match = createDeepMatcher(predicate)
347
+ const matches = []
348
+
349
+ for (const entry of iterateDeep(output, options)) {
350
+ if (!match(entry)) continue
351
+ matches.push({
352
+ tokens: entry.tokens.slice(),
353
+ path: entry.path,
354
+ depth: entry.depth,
355
+ absoluteDepth: entry.absoluteDepth,
356
+ type: entry.type,
357
+ key: entry.key,
358
+ })
359
+ }
360
+
361
+ matches.sort(compareTokenPathsDescending)
362
+
363
+ for (const matchEntry of matches) {
364
+ const current = getValueByTokens(output, matchEntry.tokens)
365
+ const next = updater(current, {
366
+ ...matchEntry,
367
+ value: current,
368
+ })
369
+ output = setValueByTokens(output, matchEntry.tokens, next)
370
+ }
371
+
372
+ return output
373
+ }
374
+
375
+ function removeDeep(value, predicate, options = {}) {
376
+ let output = cloneDeep(value)
377
+ const matches = filterDeep(output, predicate, options)
378
+ .map((entry) => ({
379
+ tokens: entry.tokens.slice(),
380
+ path: entry.path,
381
+ }))
382
+
383
+ matches.sort(compareTokenPathsDescending)
384
+
385
+ for (const match of matches) {
386
+ output = deleteByTokens(output, match.tokens)
387
+ }
388
+
389
+ return output
390
+ }
391
+
392
+ function insertDeep(value, predicate, insertion, options = {}) {
393
+ let output = cloneDeep(value)
394
+ const match = createDeepMatcher(predicate)
395
+ const matches = []
396
+
397
+ for (const entry of iterateDeep(output, options)) {
398
+ if (!match(entry)) continue
399
+ matches.push({
400
+ tokens: entry.tokens.slice(),
401
+ path: entry.path,
402
+ depth: entry.depth,
403
+ pathDepth: entry.pathDepth,
404
+ absoluteDepth: entry.absoluteDepth,
405
+ type: entry.type,
406
+ key: entry.key,
407
+ })
408
+ }
409
+
410
+ matches.sort(compareTokenPathsDescending)
411
+
412
+ for (const matchEntry of matches) {
413
+ const current = getValueByTokens(output, matchEntry.tokens)
414
+ const resolvedInsertion = typeof insertion === "function"
415
+ ? insertion(current, {
416
+ ...matchEntry,
417
+ value: current,
418
+ })
419
+ : insertion
420
+ const next = insertValue(current, resolvedInsertion, options)
421
+ output = setValueByTokens(output, matchEntry.tokens, next)
422
+ }
423
+
424
+ return output
425
+ }
426
+
427
+ module.exports = {
428
+ iterateDeep,
429
+ findDeep,
430
+ filterDeep,
431
+ findPaths,
432
+ mapDeep,
433
+ removeDeep,
434
+ insertDeep,
435
+ }
package/lib/insert.js ADDED
@@ -0,0 +1,62 @@
1
+ "use strict"
2
+
3
+ const { insertText } = require("./string.js")
4
+ const { insertAt } = require("./array.js")
5
+ const { isPlainObject, cloneDeep } = require("./shared.js")
6
+
7
+ function cloneInsertion(value) {
8
+ if (value == null) return value
9
+ if (typeof value !== "object") return value
10
+ return cloneDeep(value)
11
+ }
12
+
13
+ function getObjectInsertKey(options = {}) {
14
+ const key = options.key ?? options.property
15
+ if (key == null || key === "") return null
16
+ return String(key)
17
+ }
18
+
19
+ function createMissingSeed(options = {}) {
20
+ if (options.kind === "array") return []
21
+ if (options.kind === "string") return ""
22
+ if (options.kind === "object") return {}
23
+ if (getObjectInsertKey(options)) return {}
24
+ return undefined
25
+ }
26
+
27
+ function insertValue(current, insertion, options = {}) {
28
+ let target = current
29
+
30
+ if (target === undefined && options.createMissing === true) {
31
+ target = createMissingSeed(options)
32
+ }
33
+
34
+ if (Array.isArray(target)) {
35
+ return insertAt(target, options.at ?? options.index ?? target.length, cloneInsertion(insertion))
36
+ }
37
+
38
+ if (typeof target === "string") {
39
+ return insertText(target, insertion, options)
40
+ }
41
+
42
+ if (isPlainObject(target)) {
43
+ const key = getObjectInsertKey(options)
44
+ if (!key) {
45
+ if (options.merge === true && isPlainObject(insertion)) {
46
+ return { ...target, ...cloneDeep(insertion) }
47
+ }
48
+ throw new TypeError("object insertion requires options.key or options.property")
49
+ }
50
+
51
+ return {
52
+ ...target,
53
+ [key]: cloneInsertion(insertion),
54
+ }
55
+ }
56
+
57
+ throw new TypeError("insertValue only supports arrays, strings, and plain objects")
58
+ }
59
+
60
+ module.exports = {
61
+ insertValue,
62
+ }
package/lib/object.js CHANGED
@@ -4,11 +4,13 @@ const {
4
4
  isObjectLike,
5
5
  isPlainObject,
6
6
  tokenizePath,
7
+ stringifyPathTokens,
7
8
  getValueByPath,
8
9
  createIteratee,
9
10
  cloneShallow,
10
11
  cloneDeep,
11
12
  } = require("./shared.js")
13
+ const { insertValue } = require("./insert.js")
12
14
 
13
15
  function get(value, path, defaultValue) {
14
16
  const resolved = getValueByPath(value, path)
@@ -52,6 +54,46 @@ function set(target, path, nextValue) {
52
54
  return target
53
55
  }
54
56
 
57
+ function update(target, path, updater, options = {}) {
58
+ if (typeof updater !== "function") {
59
+ throw new TypeError("update requires an updater function")
60
+ }
61
+
62
+ const tokens = tokenizePath(path)
63
+ if (!tokens.length) {
64
+ return updater(target, {
65
+ value: target,
66
+ path: "",
67
+ tokens: [],
68
+ })
69
+ }
70
+
71
+ if (!isObjectLike(target) && !Array.isArray(target)) return target
72
+
73
+ const current = getValueByPath(target, tokens)
74
+ const next = updater(
75
+ current === undefined && Object.prototype.hasOwnProperty.call(options, "defaultValue")
76
+ ? options.defaultValue
77
+ : current,
78
+ {
79
+ value: current,
80
+ path: stringifyPathTokens(tokens),
81
+ tokens: tokens.slice(),
82
+ }
83
+ )
84
+
85
+ return set(target, tokens, next)
86
+ }
87
+
88
+ function insertAtPath(target, path, insertion, options = {}) {
89
+ const tokens = tokenizePath(path)
90
+ const current = tokens.length ? getValueByPath(target, tokens) : target
91
+ const next = insertValue(current, insertion, options)
92
+
93
+ if (!tokens.length) return next
94
+ return set(target, tokens, next)
95
+ }
96
+
55
97
  function unset(target, path) {
56
98
  if (!isObjectLike(target) && !Array.isArray(target)) return false
57
99
 
@@ -190,6 +232,8 @@ module.exports = {
190
232
  get,
191
233
  has,
192
234
  set,
235
+ update,
236
+ insertAtPath,
193
237
  unset,
194
238
  pick,
195
239
  omit,
package/lib/range.js ADDED
@@ -0,0 +1,55 @@
1
+ "use strict"
2
+
3
+ function toFiniteInteger(value, fallback) {
4
+ const number = Number(value)
5
+ if (!Number.isFinite(number)) return fallback
6
+ return Math.trunc(number)
7
+ }
8
+
9
+ function clampIndex(length, value, fallback) {
10
+ const size = Math.max(0, toFiniteInteger(length, 0))
11
+ let index = toFiniteInteger(value, fallback)
12
+ if (index == null) index = fallback
13
+ if (index < 0) index = size + index
14
+ if (index < 0) return 0
15
+ if (index > size) return size
16
+ return index
17
+ }
18
+
19
+ function normalizeRange(length, options = {}) {
20
+ const size = Math.max(0, toFiniteInteger(length, 0))
21
+ const start = clampIndex(size, options.from ?? options.start, 0)
22
+
23
+ let end
24
+ if (options.count != null) {
25
+ const count = Math.max(0, toFiniteInteger(options.count, 0))
26
+ end = Math.min(size, start + count)
27
+ } else {
28
+ end = clampIndex(size, options.to ?? options.end, size)
29
+ }
30
+
31
+ if (end < start) end = start
32
+
33
+ return {
34
+ start,
35
+ end,
36
+ length: end - start,
37
+ }
38
+ }
39
+
40
+ function normalizeInsertIndex(length, value, fallback) {
41
+ const size = Math.max(0, toFiniteInteger(length, 0))
42
+ return clampIndex(size, value, fallback == null ? size : fallback)
43
+ }
44
+
45
+ function normalizeCount(value, fallback = 0) {
46
+ return Math.max(0, toFiniteInteger(value, fallback))
47
+ }
48
+
49
+ module.exports = {
50
+ toFiniteInteger,
51
+ clampIndex,
52
+ normalizeRange,
53
+ normalizeInsertIndex,
54
+ normalizeCount,
55
+ }
package/lib/shared.js CHANGED
@@ -103,6 +103,10 @@ function tokenizePath(path) {
103
103
 
104
104
  function getValueByPath(value, path) {
105
105
  const tokens = tokenizePath(path)
106
+ return getValueByTokens(value, tokens)
107
+ }
108
+
109
+ function getValueByTokens(value, tokens) {
106
110
  let current = value
107
111
 
108
112
  for (let index = 0; index < tokens.length; index += 1) {
@@ -119,6 +123,24 @@ function createIteratee(iteratee) {
119
123
  return (value) => getValueByPath(value, iteratee)
120
124
  }
121
125
 
126
+ function stringifyPathTokens(tokens) {
127
+ if (!Array.isArray(tokens) || !tokens.length) return ""
128
+
129
+ let output = ""
130
+ for (let index = 0; index < tokens.length; index += 1) {
131
+ const token = tokens[index]
132
+ if (typeof token === "number") {
133
+ output += `[${token}]`
134
+ } else if (!output) {
135
+ output = token
136
+ } else {
137
+ output += `.${token}`
138
+ }
139
+ }
140
+
141
+ return output
142
+ }
143
+
122
144
  function cloneShallow(value) {
123
145
  if (Array.isArray(value)) return value.slice()
124
146
  if (isPlainObject(value)) return { ...value }
@@ -237,13 +259,55 @@ function cloneDeep(value) {
237
259
  return root
238
260
  }
239
261
 
262
+ function setValueByTokens(target, tokens, nextValue) {
263
+ if (!Array.isArray(tokens) || !tokens.length) return nextValue
264
+ if (!isObjectLike(target) && !Array.isArray(target)) return target
265
+
266
+ let current = target
267
+
268
+ for (let index = 0; index < tokens.length - 1; index += 1) {
269
+ if (current == null) return target
270
+ current = current[tokens[index]]
271
+ }
272
+
273
+ if (current == null) return target
274
+ current[tokens[tokens.length - 1]] = nextValue
275
+ return target
276
+ }
277
+
278
+ function deleteByTokens(target, tokens) {
279
+ if (!Array.isArray(tokens) || !tokens.length) return undefined
280
+ if (!isObjectLike(target) && !Array.isArray(target)) return target
281
+
282
+ let current = target
283
+ for (let index = 0; index < tokens.length - 1; index += 1) {
284
+ if (current == null) return target
285
+ current = current[tokens[index]]
286
+ }
287
+
288
+ if (current == null) return target
289
+
290
+ const last = tokens[tokens.length - 1]
291
+ if (Array.isArray(current) && typeof last === "number") {
292
+ current.splice(last, 1)
293
+ } else {
294
+ delete current[last]
295
+ }
296
+
297
+ return target
298
+ }
299
+
240
300
  module.exports = {
241
301
  isObjectLike,
242
302
  isPlainObject,
243
303
  isTypedArray,
244
304
  tokenizePath,
305
+ stringifyPathTokens,
245
306
  getValueByPath,
307
+ getValueByTokens,
246
308
  createIteratee,
247
309
  cloneShallow,
248
310
  cloneDeep,
311
+ setValueByTokens,
312
+ deleteByTokens,
249
313
  }