@qezor/structkit 1.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/LICENSE +21 -0
- package/README.md +100 -0
- package/bridge.d.ts +14 -0
- package/bridge.js +3 -0
- package/bridge.mjs +11 -0
- package/index.d.ts +53 -0
- package/index.js +30 -0
- package/index.mjs +39 -0
- package/lib/array.js +188 -0
- package/lib/bridge.js +49 -0
- package/lib/compare.js +94 -0
- package/lib/object.js +202 -0
- package/lib/shared.js +249 -0
- package/package.json +57 -0
- package/test.js +90 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Abdul Ahad
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# @qezor/structkit
|
|
2
|
+
|
|
3
|
+
`@qezor/structkit` is a focused data-manipulation toolkit for:
|
|
4
|
+
- arrays
|
|
5
|
+
- objects
|
|
6
|
+
- path access
|
|
7
|
+
- iterative deep equality
|
|
8
|
+
- iterative deep merge and clone
|
|
9
|
+
|
|
10
|
+
It is built to cover the high-use utility surface people often reach for in lodash, without turning into a bag of random helpers.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @qezor/structkit
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## What It Prioritizes
|
|
19
|
+
|
|
20
|
+
- iterative internals for deep operations
|
|
21
|
+
- no callback-style APIs
|
|
22
|
+
- no recursion-heavy deep walkers
|
|
23
|
+
- predictable helpers for arrays, objects, and paths
|
|
24
|
+
- a shared Qezor Bridge contract so other libraries can detect capabilities cleanly
|
|
25
|
+
|
|
26
|
+
## Main Helpers
|
|
27
|
+
|
|
28
|
+
- `toArray(value)`
|
|
29
|
+
- `chunk(array, size)`
|
|
30
|
+
- `compact(array)`
|
|
31
|
+
- `uniq(array)`
|
|
32
|
+
- `uniqBy(array, iteratee)`
|
|
33
|
+
- `difference(array, values)`
|
|
34
|
+
- `intersection(array, values)`
|
|
35
|
+
- `partition(array, iteratee)`
|
|
36
|
+
- `groupBy(array, iteratee)`
|
|
37
|
+
- `countBy(array, iteratee)`
|
|
38
|
+
- `keyBy(array, iteratee)`
|
|
39
|
+
- `sortBy(array, iteratee)`
|
|
40
|
+
- `flatMap(array, iteratee)`
|
|
41
|
+
- `sumBy(array, iteratee)`
|
|
42
|
+
- `tokenizePath(path)`
|
|
43
|
+
- `get(value, path, defaultValue)`
|
|
44
|
+
- `has(value, path)`
|
|
45
|
+
- `set(value, path, nextValue)`
|
|
46
|
+
- `unset(value, path)`
|
|
47
|
+
- `pick(value, paths)`
|
|
48
|
+
- `omit(value, paths)`
|
|
49
|
+
- `mapValues(object, iteratee)`
|
|
50
|
+
- `defaults(target, ...sources)`
|
|
51
|
+
- `assignDefined(target, ...sources)`
|
|
52
|
+
- `cloneShallow(value)`
|
|
53
|
+
- `cloneDeep(value)`
|
|
54
|
+
- `mergeDeep(target, ...sources)`
|
|
55
|
+
- `isEqual(left, right)`
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
const {
|
|
61
|
+
groupBy,
|
|
62
|
+
get,
|
|
63
|
+
set,
|
|
64
|
+
mergeDeep,
|
|
65
|
+
isEqual,
|
|
66
|
+
} = require("@qezor/structkit")
|
|
67
|
+
|
|
68
|
+
const users = [
|
|
69
|
+
{ id: "u_1", role: "buyer" },
|
|
70
|
+
{ id: "u_2", role: "buyer" },
|
|
71
|
+
{ id: "u_3", role: "seller" },
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
const grouped = groupBy(users, "role")
|
|
75
|
+
const state = {}
|
|
76
|
+
|
|
77
|
+
set(state, "queue.region.city", "Pune")
|
|
78
|
+
|
|
79
|
+
const region = get(state, "queue.region.city")
|
|
80
|
+
const merged = mergeDeep({}, { demand: { joinedUnits: 12 } }, { demand: { buyers: 3 } })
|
|
81
|
+
const same = isEqual(merged, { demand: { joinedUnits: 12, buyers: 3 } })
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Qezor Bridge
|
|
85
|
+
|
|
86
|
+
The root export carries bridge metadata through `Symbol.for("qezor.bridge")`.
|
|
87
|
+
|
|
88
|
+
That lets other Qezor libraries detect things like:
|
|
89
|
+
- `structure`
|
|
90
|
+
- `structure:array`
|
|
91
|
+
- `structure:object`
|
|
92
|
+
- `structure:path`
|
|
93
|
+
- `structure:compare`
|
|
94
|
+
|
|
95
|
+
## Notes
|
|
96
|
+
|
|
97
|
+
- deep clone, deep merge, and deep equality use iterative walkers instead of recursion
|
|
98
|
+
- arrays are replaced during `mergeDeep()` instead of element-wise merged
|
|
99
|
+
- `omit()` works on cloned data, so the original input is left untouched
|
|
100
|
+
- `set()` mutates the provided target, like many performance-oriented utility helpers do
|
package/bridge.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface QezorBridge {
|
|
2
|
+
name: string
|
|
3
|
+
kind: string
|
|
4
|
+
version: string
|
|
5
|
+
capabilities: string[]
|
|
6
|
+
adapters: string[]
|
|
7
|
+
dependencies: string[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const QEZOR_BRIDGE: unique symbol
|
|
11
|
+
export function createQezorBridge(input?: Partial<QezorBridge>): QezorBridge
|
|
12
|
+
export function attachQezorBridge<T extends object>(target: T, input?: Partial<QezorBridge>): T
|
|
13
|
+
export function readQezorBridge(target: unknown): QezorBridge | null
|
|
14
|
+
export function hasQezorCapability(target: unknown, capability: string): boolean
|
package/bridge.js
ADDED
package/bridge.mjs
ADDED
package/index.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type PathToken = string | number
|
|
2
|
+
export type PathInput = string | number | readonly PathToken[]
|
|
3
|
+
export type Iteratee<T = unknown, R = unknown> =
|
|
4
|
+
| ((value: T, indexOrKey?: number | string, collection?: unknown) => R)
|
|
5
|
+
| PathInput
|
|
6
|
+
| null
|
|
7
|
+
| undefined
|
|
8
|
+
|
|
9
|
+
export interface QezorBridge {
|
|
10
|
+
name: string
|
|
11
|
+
kind: string
|
|
12
|
+
version: string
|
|
13
|
+
capabilities: string[]
|
|
14
|
+
adapters: string[]
|
|
15
|
+
dependencies: string[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const QEZOR_BRIDGE: unique symbol
|
|
19
|
+
export function createQezorBridge(input?: Partial<QezorBridge>): QezorBridge
|
|
20
|
+
export function attachQezorBridge<T extends object>(target: T, input?: Partial<QezorBridge>): T
|
|
21
|
+
export function readQezorBridge(target: unknown): QezorBridge | null
|
|
22
|
+
export function hasQezorCapability(target: unknown, capability: string): boolean
|
|
23
|
+
|
|
24
|
+
export function toArray<T = unknown>(value: unknown): T[]
|
|
25
|
+
export function chunk<T = unknown>(value: unknown, size?: number): T[][]
|
|
26
|
+
export function compact<T = unknown>(value: unknown): T[]
|
|
27
|
+
export function uniq<T = unknown>(value: unknown): T[]
|
|
28
|
+
export function uniqBy<T = unknown>(value: unknown, iteratee?: Iteratee<T, unknown>): T[]
|
|
29
|
+
export function difference<T = unknown>(value: unknown, values: unknown): T[]
|
|
30
|
+
export function intersection<T = unknown>(value: unknown, values: unknown): T[]
|
|
31
|
+
export function partition<T = unknown>(value: unknown, iteratee?: Iteratee<T, unknown>): [T[], T[]]
|
|
32
|
+
export function groupBy<T = unknown>(value: unknown, iteratee?: Iteratee<T, unknown>): Record<string, T[]>
|
|
33
|
+
export function countBy<T = unknown>(value: unknown, iteratee?: Iteratee<T, unknown>): Record<string, number>
|
|
34
|
+
export function keyBy<T = unknown>(value: unknown, iteratee?: Iteratee<T, unknown>): Record<string, T>
|
|
35
|
+
export function sortBy<T = unknown>(value: unknown, iteratee?: Iteratee<T, unknown>): T[]
|
|
36
|
+
export function flatMap<T = unknown, R = unknown>(value: unknown, iteratee?: Iteratee<T, R | R[]>): R[]
|
|
37
|
+
export function sumBy<T = unknown>(value: unknown, iteratee?: Iteratee<T, number>): number
|
|
38
|
+
|
|
39
|
+
export function tokenizePath(path: PathInput): PathToken[]
|
|
40
|
+
export function get<T = unknown>(value: unknown, path: PathInput, defaultValue?: T): T
|
|
41
|
+
export function has(value: unknown, path: PathInput): boolean
|
|
42
|
+
export function set<T extends object>(target: T, path: PathInput, nextValue: unknown): T
|
|
43
|
+
export function unset(target: object, path: PathInput): boolean
|
|
44
|
+
export function pick(value: unknown, paths: PathInput | PathInput[]): Record<string, unknown>
|
|
45
|
+
export function omit<T = unknown>(value: T, paths: PathInput | PathInput[]): T
|
|
46
|
+
export function mapValues<T extends Record<string, unknown>, R = unknown>(value: T, iteratee?: Iteratee<unknown, R>): Record<string, R>
|
|
47
|
+
export function defaults<T extends Record<string, unknown>>(target: T, ...sources: Array<Record<string, unknown>>): T
|
|
48
|
+
export function assignDefined<T extends Record<string, unknown>>(target: T, ...sources: Array<Record<string, unknown>>): T
|
|
49
|
+
export function cloneShallow<T = unknown>(value: T): T
|
|
50
|
+
export function cloneDeep<T = unknown>(value: T): T
|
|
51
|
+
export function mergeDeep<T extends Record<string, unknown>>(target: T, ...sources: Array<Record<string, unknown>>): T
|
|
52
|
+
|
|
53
|
+
export function isEqual(left: unknown, right: unknown): boolean
|
package/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
const bridge = require("./lib/bridge.js")
|
|
4
|
+
const array = require("./lib/array.js")
|
|
5
|
+
const object = require("./lib/object.js")
|
|
6
|
+
const compare = require("./lib/compare.js")
|
|
7
|
+
|
|
8
|
+
module.exports = bridge.attachQezorBridge({
|
|
9
|
+
...array,
|
|
10
|
+
...object,
|
|
11
|
+
...compare,
|
|
12
|
+
QEZOR_BRIDGE: bridge.QEZOR_BRIDGE,
|
|
13
|
+
createQezorBridge: bridge.createQezorBridge,
|
|
14
|
+
attachQezorBridge: bridge.attachQezorBridge,
|
|
15
|
+
readQezorBridge: bridge.readQezorBridge,
|
|
16
|
+
hasQezorCapability: bridge.hasQezorCapability,
|
|
17
|
+
}, {
|
|
18
|
+
name: "@qezor/structkit",
|
|
19
|
+
kind: "library",
|
|
20
|
+
version: "1.0.0",
|
|
21
|
+
capabilities: [
|
|
22
|
+
"bridge",
|
|
23
|
+
"structure",
|
|
24
|
+
"structure:array",
|
|
25
|
+
"structure:object",
|
|
26
|
+
"structure:path",
|
|
27
|
+
"structure:compare",
|
|
28
|
+
],
|
|
29
|
+
adapters: ["plain-data"],
|
|
30
|
+
})
|
package/index.mjs
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import mod from "./index.js"
|
|
2
|
+
|
|
3
|
+
export const {
|
|
4
|
+
toArray,
|
|
5
|
+
chunk,
|
|
6
|
+
compact,
|
|
7
|
+
uniq,
|
|
8
|
+
uniqBy,
|
|
9
|
+
difference,
|
|
10
|
+
intersection,
|
|
11
|
+
partition,
|
|
12
|
+
groupBy,
|
|
13
|
+
countBy,
|
|
14
|
+
keyBy,
|
|
15
|
+
sortBy,
|
|
16
|
+
flatMap,
|
|
17
|
+
sumBy,
|
|
18
|
+
tokenizePath,
|
|
19
|
+
get,
|
|
20
|
+
has,
|
|
21
|
+
set,
|
|
22
|
+
unset,
|
|
23
|
+
pick,
|
|
24
|
+
omit,
|
|
25
|
+
mapValues,
|
|
26
|
+
defaults,
|
|
27
|
+
assignDefined,
|
|
28
|
+
cloneShallow,
|
|
29
|
+
cloneDeep,
|
|
30
|
+
mergeDeep,
|
|
31
|
+
isEqual,
|
|
32
|
+
QEZOR_BRIDGE,
|
|
33
|
+
createQezorBridge,
|
|
34
|
+
attachQezorBridge,
|
|
35
|
+
readQezorBridge,
|
|
36
|
+
hasQezorCapability,
|
|
37
|
+
} = mod
|
|
38
|
+
|
|
39
|
+
export default mod
|
package/lib/array.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
const { createIteratee } = require("./shared.js")
|
|
4
|
+
|
|
5
|
+
function toArray(value) {
|
|
6
|
+
if (value == null) return []
|
|
7
|
+
if (Array.isArray(value)) return value.slice()
|
|
8
|
+
if (typeof value === "string") return Array.from(value)
|
|
9
|
+
if (typeof value[Symbol.iterator] === "function") return Array.from(value)
|
|
10
|
+
if (typeof value === "object") return Object.values(value)
|
|
11
|
+
return [value]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function chunk(value, size = 1) {
|
|
15
|
+
const list = toArray(value)
|
|
16
|
+
const width = Math.max(1, Number.parseInt(String(size), 10) || 1)
|
|
17
|
+
const output = []
|
|
18
|
+
for (let index = 0; index < list.length; index += width) {
|
|
19
|
+
output.push(list.slice(index, index + width))
|
|
20
|
+
}
|
|
21
|
+
return output
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function compact(value) {
|
|
25
|
+
return toArray(value).filter(Boolean)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function uniq(value) {
|
|
29
|
+
return [...new Set(toArray(value))]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function uniqBy(value, iteratee) {
|
|
33
|
+
const list = toArray(value)
|
|
34
|
+
const resolve = createIteratee(iteratee)
|
|
35
|
+
const seen = new Set()
|
|
36
|
+
const output = []
|
|
37
|
+
|
|
38
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
39
|
+
const item = list[index]
|
|
40
|
+
const marker = resolve(item, index, list)
|
|
41
|
+
if (seen.has(marker)) continue
|
|
42
|
+
seen.add(marker)
|
|
43
|
+
output.push(item)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return output
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function difference(value, values) {
|
|
50
|
+
const blocked = new Set(toArray(values))
|
|
51
|
+
return toArray(value).filter((item) => !blocked.has(item))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function intersection(value, values) {
|
|
55
|
+
const allowed = new Set(toArray(values))
|
|
56
|
+
return toArray(value).filter((item) => allowed.has(item))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function partition(value, iteratee) {
|
|
60
|
+
const list = toArray(value)
|
|
61
|
+
const resolve = createIteratee(iteratee)
|
|
62
|
+
const truthy = []
|
|
63
|
+
const falsy = []
|
|
64
|
+
|
|
65
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
66
|
+
const item = list[index]
|
|
67
|
+
if (resolve(item, index, list)) {
|
|
68
|
+
truthy.push(item)
|
|
69
|
+
} else {
|
|
70
|
+
falsy.push(item)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return [truthy, falsy]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function groupBy(value, iteratee) {
|
|
78
|
+
const list = toArray(value)
|
|
79
|
+
const resolve = createIteratee(iteratee)
|
|
80
|
+
const output = {}
|
|
81
|
+
|
|
82
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
83
|
+
const item = list[index]
|
|
84
|
+
const key = String(resolve(item, index, list))
|
|
85
|
+
if (!Object.prototype.hasOwnProperty.call(output, key)) output[key] = []
|
|
86
|
+
output[key].push(item)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return output
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function countBy(value, iteratee) {
|
|
93
|
+
const list = toArray(value)
|
|
94
|
+
const resolve = createIteratee(iteratee)
|
|
95
|
+
const output = {}
|
|
96
|
+
|
|
97
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
98
|
+
const item = list[index]
|
|
99
|
+
const key = String(resolve(item, index, list))
|
|
100
|
+
output[key] = (output[key] || 0) + 1
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return output
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function keyBy(value, iteratee) {
|
|
107
|
+
const list = toArray(value)
|
|
108
|
+
const resolve = createIteratee(iteratee)
|
|
109
|
+
const output = {}
|
|
110
|
+
|
|
111
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
112
|
+
const item = list[index]
|
|
113
|
+
output[String(resolve(item, index, list))] = item
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return output
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function sortBy(value, iteratee) {
|
|
120
|
+
const list = toArray(value)
|
|
121
|
+
const resolve = createIteratee(iteratee)
|
|
122
|
+
const ranked = []
|
|
123
|
+
|
|
124
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
125
|
+
ranked.push({
|
|
126
|
+
item: list[index],
|
|
127
|
+
index,
|
|
128
|
+
rank: resolve(list[index], index, list),
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
ranked.sort((left, right) => {
|
|
133
|
+
if (left.rank < right.rank) return -1
|
|
134
|
+
if (left.rank > right.rank) return 1
|
|
135
|
+
return left.index - right.index
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
return ranked.map((entry) => entry.item)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function flatMap(value, iteratee) {
|
|
142
|
+
const list = toArray(value)
|
|
143
|
+
const resolve = createIteratee(iteratee)
|
|
144
|
+
const output = []
|
|
145
|
+
|
|
146
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
147
|
+
const result = resolve(list[index], index, list)
|
|
148
|
+
if (Array.isArray(result)) {
|
|
149
|
+
for (let inner = 0; inner < result.length; inner += 1) {
|
|
150
|
+
output.push(result[inner])
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
output.push(result)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return output
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function sumBy(value, iteratee) {
|
|
161
|
+
const list = toArray(value)
|
|
162
|
+
const resolve = createIteratee(iteratee)
|
|
163
|
+
let total = 0
|
|
164
|
+
|
|
165
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
166
|
+
const number = Number(resolve(list[index], index, list))
|
|
167
|
+
if (Number.isFinite(number)) total += number
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return total
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = {
|
|
174
|
+
toArray,
|
|
175
|
+
chunk,
|
|
176
|
+
compact,
|
|
177
|
+
uniq,
|
|
178
|
+
uniqBy,
|
|
179
|
+
difference,
|
|
180
|
+
intersection,
|
|
181
|
+
partition,
|
|
182
|
+
groupBy,
|
|
183
|
+
countBy,
|
|
184
|
+
keyBy,
|
|
185
|
+
sortBy,
|
|
186
|
+
flatMap,
|
|
187
|
+
sumBy,
|
|
188
|
+
}
|
package/lib/bridge.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
const QEZOR_BRIDGE = Symbol.for("qezor.bridge")
|
|
4
|
+
|
|
5
|
+
function normalizeList(values) {
|
|
6
|
+
if (!Array.isArray(values)) return []
|
|
7
|
+
return [...new Set(values.map((value) => String(value).trim()).filter(Boolean))].sort()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function createQezorBridge(input = {}) {
|
|
11
|
+
return {
|
|
12
|
+
name: input.name || "unknown",
|
|
13
|
+
kind: input.kind || "library",
|
|
14
|
+
version: input.version || "0.0.0",
|
|
15
|
+
capabilities: normalizeList(input.capabilities),
|
|
16
|
+
adapters: normalizeList(input.adapters),
|
|
17
|
+
dependencies: normalizeList(input.dependencies),
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function attachQezorBridge(target, input = {}) {
|
|
22
|
+
const bridge = createQezorBridge(input)
|
|
23
|
+
Object.defineProperty(target, QEZOR_BRIDGE, {
|
|
24
|
+
enumerable: false,
|
|
25
|
+
configurable: true,
|
|
26
|
+
writable: false,
|
|
27
|
+
value: bridge,
|
|
28
|
+
})
|
|
29
|
+
return target
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readQezorBridge(target) {
|
|
33
|
+
if (!target || (typeof target !== "object" && typeof target !== "function")) return null
|
|
34
|
+
return target[QEZOR_BRIDGE] || null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function hasQezorCapability(target, capability) {
|
|
38
|
+
const bridge = readQezorBridge(target)
|
|
39
|
+
if (!bridge) return false
|
|
40
|
+
return bridge.capabilities.includes(String(capability))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
QEZOR_BRIDGE,
|
|
45
|
+
createQezorBridge,
|
|
46
|
+
attachQezorBridge,
|
|
47
|
+
readQezorBridge,
|
|
48
|
+
hasQezorCapability,
|
|
49
|
+
}
|
package/lib/compare.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
const { isObjectLike, isTypedArray } = require("./shared.js")
|
|
4
|
+
|
|
5
|
+
function isEqual(left, right) {
|
|
6
|
+
if (Object.is(left, right)) return true
|
|
7
|
+
|
|
8
|
+
const stack = [{ left, right }]
|
|
9
|
+
const seen = new WeakMap()
|
|
10
|
+
|
|
11
|
+
while (stack.length) {
|
|
12
|
+
const frame = stack.pop()
|
|
13
|
+
const a = frame.left
|
|
14
|
+
const b = frame.right
|
|
15
|
+
|
|
16
|
+
if (Object.is(a, b)) continue
|
|
17
|
+
|
|
18
|
+
const aIsObject = isObjectLike(a)
|
|
19
|
+
const bIsObject = isObjectLike(b)
|
|
20
|
+
|
|
21
|
+
if (!aIsObject || !bIsObject) return false
|
|
22
|
+
|
|
23
|
+
const leftTag = Object.prototype.toString.call(a)
|
|
24
|
+
const rightTag = Object.prototype.toString.call(b)
|
|
25
|
+
if (leftTag !== rightTag) return false
|
|
26
|
+
|
|
27
|
+
let seenRights = seen.get(a)
|
|
28
|
+
if (seenRights && seenRights.has(b)) continue
|
|
29
|
+
if (!seenRights) {
|
|
30
|
+
seenRights = new WeakSet()
|
|
31
|
+
seen.set(a, seenRights)
|
|
32
|
+
}
|
|
33
|
+
seenRights.add(b)
|
|
34
|
+
|
|
35
|
+
if (Array.isArray(a)) {
|
|
36
|
+
if (!Array.isArray(b) || a.length !== b.length) return false
|
|
37
|
+
for (let index = 0; index < a.length; index += 1) {
|
|
38
|
+
stack.push({ left: a[index], right: b[index] })
|
|
39
|
+
}
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (a instanceof Date) {
|
|
44
|
+
if (a.getTime() !== b.getTime()) return false
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (a instanceof RegExp) {
|
|
49
|
+
if (a.source !== b.source || a.flags !== b.flags) return false
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (isTypedArray(a)) {
|
|
54
|
+
if (!isTypedArray(b) || a.length !== b.length) return false
|
|
55
|
+
for (let index = 0; index < a.length; index += 1) {
|
|
56
|
+
if (a[index] !== b[index]) return false
|
|
57
|
+
}
|
|
58
|
+
continue
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (a instanceof Map) {
|
|
62
|
+
if (!(b instanceof Map) || a.size !== b.size) return false
|
|
63
|
+
for (const [key, value] of a.entries()) {
|
|
64
|
+
if (!b.has(key)) return false
|
|
65
|
+
stack.push({ left: value, right: b.get(key) })
|
|
66
|
+
}
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (a instanceof Set) {
|
|
71
|
+
if (!(b instanceof Set) || a.size !== b.size) return false
|
|
72
|
+
for (const value of a.values()) {
|
|
73
|
+
if (!b.has(value)) return false
|
|
74
|
+
}
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const keysA = Object.keys(a)
|
|
79
|
+
const keysB = Object.keys(b)
|
|
80
|
+
if (keysA.length !== keysB.length) return false
|
|
81
|
+
|
|
82
|
+
for (let index = 0; index < keysA.length; index += 1) {
|
|
83
|
+
const key = keysA[index]
|
|
84
|
+
if (!Object.prototype.hasOwnProperty.call(b, key)) return false
|
|
85
|
+
stack.push({ left: a[key], right: b[key] })
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return true
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
isEqual,
|
|
94
|
+
}
|
package/lib/object.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
isObjectLike,
|
|
5
|
+
isPlainObject,
|
|
6
|
+
tokenizePath,
|
|
7
|
+
getValueByPath,
|
|
8
|
+
createIteratee,
|
|
9
|
+
cloneShallow,
|
|
10
|
+
cloneDeep,
|
|
11
|
+
} = require("./shared.js")
|
|
12
|
+
|
|
13
|
+
function get(value, path, defaultValue) {
|
|
14
|
+
const resolved = getValueByPath(value, path)
|
|
15
|
+
return resolved === undefined ? defaultValue : resolved
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function has(value, path) {
|
|
19
|
+
const tokens = tokenizePath(path)
|
|
20
|
+
if (!tokens.length) return false
|
|
21
|
+
|
|
22
|
+
let current = value
|
|
23
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
24
|
+
if (!isObjectLike(current) && !Array.isArray(current)) return false
|
|
25
|
+
if (!Object.prototype.hasOwnProperty.call(current, tokens[index])) return false
|
|
26
|
+
current = current[tokens[index]]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function set(target, path, nextValue) {
|
|
33
|
+
if (!isObjectLike(target) && !Array.isArray(target)) return target
|
|
34
|
+
|
|
35
|
+
const tokens = tokenizePath(path)
|
|
36
|
+
if (!tokens.length) return target
|
|
37
|
+
|
|
38
|
+
let current = target
|
|
39
|
+
for (let index = 0; index < tokens.length - 1; index += 1) {
|
|
40
|
+
const token = tokens[index]
|
|
41
|
+
const nextToken = tokens[index + 1]
|
|
42
|
+
const existing = current[token]
|
|
43
|
+
|
|
44
|
+
if (!isObjectLike(existing) && !Array.isArray(existing)) {
|
|
45
|
+
current[token] = typeof nextToken === "number" ? [] : {}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
current = current[token]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
current[tokens[tokens.length - 1]] = nextValue
|
|
52
|
+
return target
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function unset(target, path) {
|
|
56
|
+
if (!isObjectLike(target) && !Array.isArray(target)) return false
|
|
57
|
+
|
|
58
|
+
const tokens = tokenizePath(path)
|
|
59
|
+
if (!tokens.length) return false
|
|
60
|
+
|
|
61
|
+
let current = target
|
|
62
|
+
for (let index = 0; index < tokens.length - 1; index += 1) {
|
|
63
|
+
if (!isObjectLike(current) && !Array.isArray(current)) return false
|
|
64
|
+
if (!Object.prototype.hasOwnProperty.call(current, tokens[index])) return false
|
|
65
|
+
current = current[tokens[index]]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const last = tokens[tokens.length - 1]
|
|
69
|
+
if (!isObjectLike(current) && !Array.isArray(current)) return false
|
|
70
|
+
if (!Object.prototype.hasOwnProperty.call(current, last)) return false
|
|
71
|
+
|
|
72
|
+
if (Array.isArray(current) && typeof last === "number") {
|
|
73
|
+
current.splice(last, 1)
|
|
74
|
+
} else {
|
|
75
|
+
delete current[last]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return true
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function pick(value, paths) {
|
|
82
|
+
const selectedPaths = Array.isArray(paths) ? paths : [paths]
|
|
83
|
+
const output = {}
|
|
84
|
+
|
|
85
|
+
for (const path of selectedPaths) {
|
|
86
|
+
if (!has(value, path)) continue
|
|
87
|
+
set(output, path, get(value, path))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return output
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function omit(value, paths) {
|
|
94
|
+
const output = cloneDeep(value)
|
|
95
|
+
const omittedPaths = Array.isArray(paths) ? paths : [paths]
|
|
96
|
+
|
|
97
|
+
for (const path of omittedPaths) {
|
|
98
|
+
unset(output, path)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return output
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function mapValues(value, iteratee) {
|
|
105
|
+
if (!isPlainObject(value)) return {}
|
|
106
|
+
const resolve = createIteratee(iteratee)
|
|
107
|
+
const output = {}
|
|
108
|
+
const keys = Object.keys(value)
|
|
109
|
+
|
|
110
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
111
|
+
const key = keys[index]
|
|
112
|
+
output[key] = resolve(value[key], key, value)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return output
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function defaults(target, ...sources) {
|
|
119
|
+
const output = isPlainObject(target) ? target : {}
|
|
120
|
+
|
|
121
|
+
for (const source of sources) {
|
|
122
|
+
if (!isPlainObject(source)) continue
|
|
123
|
+
for (const key of Object.keys(source)) {
|
|
124
|
+
if (output[key] === undefined) output[key] = source[key]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return output
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function assignDefined(target, ...sources) {
|
|
132
|
+
const output = isObjectLike(target) || Array.isArray(target) ? target : {}
|
|
133
|
+
|
|
134
|
+
for (const source of sources) {
|
|
135
|
+
if (!isObjectLike(source) && !Array.isArray(source)) continue
|
|
136
|
+
for (const [key, value] of Object.entries(source)) {
|
|
137
|
+
if (value !== undefined) output[key] = value
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return output
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function mergeDeep(target, ...sources) {
|
|
145
|
+
const output = isPlainObject(target) ? target : {}
|
|
146
|
+
|
|
147
|
+
for (const source of sources) {
|
|
148
|
+
if (!isPlainObject(source)) continue
|
|
149
|
+
|
|
150
|
+
const stack = [{ destination: output, source }]
|
|
151
|
+
while (stack.length) {
|
|
152
|
+
const frame = stack.pop()
|
|
153
|
+
const { destination, source: currentSource } = frame
|
|
154
|
+
const keys = Object.keys(currentSource)
|
|
155
|
+
|
|
156
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
157
|
+
const key = keys[index]
|
|
158
|
+
const nextValue = currentSource[key]
|
|
159
|
+
const currentValue = destination[key]
|
|
160
|
+
|
|
161
|
+
if (isPlainObject(nextValue)) {
|
|
162
|
+
if (!isPlainObject(currentValue)) destination[key] = {}
|
|
163
|
+
stack.push({
|
|
164
|
+
destination: destination[key],
|
|
165
|
+
source: nextValue,
|
|
166
|
+
})
|
|
167
|
+
continue
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (Array.isArray(nextValue)) {
|
|
171
|
+
destination[key] = cloneDeep(nextValue)
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (isObjectLike(nextValue)) {
|
|
176
|
+
destination[key] = cloneDeep(nextValue)
|
|
177
|
+
continue
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
destination[key] = nextValue
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return output
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
tokenizePath,
|
|
190
|
+
get,
|
|
191
|
+
has,
|
|
192
|
+
set,
|
|
193
|
+
unset,
|
|
194
|
+
pick,
|
|
195
|
+
omit,
|
|
196
|
+
mapValues,
|
|
197
|
+
defaults,
|
|
198
|
+
assignDefined,
|
|
199
|
+
cloneShallow,
|
|
200
|
+
cloneDeep,
|
|
201
|
+
mergeDeep,
|
|
202
|
+
}
|
package/lib/shared.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
function isObjectLike(value) {
|
|
4
|
+
return value !== null && typeof value === "object"
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function isPlainObject(value) {
|
|
8
|
+
if (!isObjectLike(value)) return false
|
|
9
|
+
const prototype = Object.getPrototypeOf(value)
|
|
10
|
+
return prototype === Object.prototype || prototype === null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isTypedArray(value) {
|
|
14
|
+
return ArrayBuffer.isView(value) && !(value instanceof DataView)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizePathToken(value) {
|
|
18
|
+
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) return value
|
|
19
|
+
const text = String(value).trim()
|
|
20
|
+
if (!text) return ""
|
|
21
|
+
if (/^(0|[1-9]\d*)$/.test(text)) return Number(text)
|
|
22
|
+
return text
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function tokenizePath(path) {
|
|
26
|
+
if (Array.isArray(path)) {
|
|
27
|
+
const tokens = []
|
|
28
|
+
for (const token of path) {
|
|
29
|
+
const normalized = normalizePathToken(token)
|
|
30
|
+
if (normalized === "") continue
|
|
31
|
+
tokens.push(normalized)
|
|
32
|
+
}
|
|
33
|
+
return tokens
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (typeof path === "number") return [normalizePathToken(path)]
|
|
37
|
+
if (path == null) return []
|
|
38
|
+
|
|
39
|
+
const text = String(path)
|
|
40
|
+
if (!text.trim()) return []
|
|
41
|
+
|
|
42
|
+
const tokens = []
|
|
43
|
+
let buffer = ""
|
|
44
|
+
let bracket = ""
|
|
45
|
+
let inBracket = false
|
|
46
|
+
let quote = ""
|
|
47
|
+
|
|
48
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
49
|
+
const character = text[index]
|
|
50
|
+
|
|
51
|
+
if (!inBracket) {
|
|
52
|
+
if (character === ".") {
|
|
53
|
+
if (buffer) {
|
|
54
|
+
tokens.push(normalizePathToken(buffer))
|
|
55
|
+
buffer = ""
|
|
56
|
+
}
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (character === "[") {
|
|
61
|
+
if (buffer) {
|
|
62
|
+
tokens.push(normalizePathToken(buffer))
|
|
63
|
+
buffer = ""
|
|
64
|
+
}
|
|
65
|
+
inBracket = true
|
|
66
|
+
bracket = ""
|
|
67
|
+
quote = ""
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
buffer += character
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (quote) {
|
|
76
|
+
if (character === quote) {
|
|
77
|
+
quote = ""
|
|
78
|
+
} else {
|
|
79
|
+
bracket += character
|
|
80
|
+
}
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (character === "'" || character === "\"") {
|
|
85
|
+
quote = character
|
|
86
|
+
continue
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (character === "]") {
|
|
90
|
+
const normalized = normalizePathToken(bracket)
|
|
91
|
+
if (normalized !== "") tokens.push(normalized)
|
|
92
|
+
inBracket = false
|
|
93
|
+
bracket = ""
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
bracket += character
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (buffer) tokens.push(normalizePathToken(buffer))
|
|
101
|
+
return tokens
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function getValueByPath(value, path) {
|
|
105
|
+
const tokens = tokenizePath(path)
|
|
106
|
+
let current = value
|
|
107
|
+
|
|
108
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
109
|
+
if (current == null) return undefined
|
|
110
|
+
current = current[tokens[index]]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return current
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function createIteratee(iteratee) {
|
|
117
|
+
if (typeof iteratee === "function") return iteratee
|
|
118
|
+
if (iteratee == null) return (value) => value
|
|
119
|
+
return (value) => getValueByPath(value, iteratee)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function cloneShallow(value) {
|
|
123
|
+
if (Array.isArray(value)) return value.slice()
|
|
124
|
+
if (isPlainObject(value)) return { ...value }
|
|
125
|
+
if (value instanceof Date) return new Date(value.getTime())
|
|
126
|
+
if (value instanceof RegExp) return new RegExp(value.source, value.flags)
|
|
127
|
+
if (value instanceof Map) return new Map(value)
|
|
128
|
+
if (value instanceof Set) return new Set(value)
|
|
129
|
+
if (isTypedArray(value)) return value.slice()
|
|
130
|
+
return value
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function initClone(value) {
|
|
134
|
+
if (Array.isArray(value)) return []
|
|
135
|
+
if (isPlainObject(value)) return {}
|
|
136
|
+
if (value instanceof Date) return new Date(value.getTime())
|
|
137
|
+
if (value instanceof RegExp) return new RegExp(value.source, value.flags)
|
|
138
|
+
if (value instanceof Map) return new Map()
|
|
139
|
+
if (value instanceof Set) return new Set()
|
|
140
|
+
if (isTypedArray(value)) return value.slice()
|
|
141
|
+
return value
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function cloneDeep(value) {
|
|
145
|
+
if (!isObjectLike(value)) return value
|
|
146
|
+
|
|
147
|
+
const root = initClone(value)
|
|
148
|
+
if (!isObjectLike(root)) return root
|
|
149
|
+
|
|
150
|
+
const seen = new Map([[value, root]])
|
|
151
|
+
const stack = [{ source: value, target: root }]
|
|
152
|
+
|
|
153
|
+
while (stack.length) {
|
|
154
|
+
const frame = stack.pop()
|
|
155
|
+
const { source, target } = frame
|
|
156
|
+
|
|
157
|
+
if (Array.isArray(source)) {
|
|
158
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
159
|
+
const item = source[index]
|
|
160
|
+
if (!isObjectLike(item)) {
|
|
161
|
+
target[index] = item
|
|
162
|
+
continue
|
|
163
|
+
}
|
|
164
|
+
if (seen.has(item)) {
|
|
165
|
+
target[index] = seen.get(item)
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
const child = initClone(item)
|
|
169
|
+
target[index] = child
|
|
170
|
+
if (isObjectLike(child)) {
|
|
171
|
+
seen.set(item, child)
|
|
172
|
+
stack.push({ source: item, target: child })
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (source instanceof Map) {
|
|
179
|
+
for (const [key, item] of source.entries()) {
|
|
180
|
+
if (!isObjectLike(item)) {
|
|
181
|
+
target.set(key, item)
|
|
182
|
+
continue
|
|
183
|
+
}
|
|
184
|
+
if (seen.has(item)) {
|
|
185
|
+
target.set(key, seen.get(item))
|
|
186
|
+
continue
|
|
187
|
+
}
|
|
188
|
+
const child = initClone(item)
|
|
189
|
+
target.set(key, child)
|
|
190
|
+
if (isObjectLike(child)) {
|
|
191
|
+
seen.set(item, child)
|
|
192
|
+
stack.push({ source: item, target: child })
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
continue
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (source instanceof Set) {
|
|
199
|
+
for (const item of source.values()) {
|
|
200
|
+
if (!isObjectLike(item)) {
|
|
201
|
+
target.add(item)
|
|
202
|
+
continue
|
|
203
|
+
}
|
|
204
|
+
if (seen.has(item)) {
|
|
205
|
+
target.add(seen.get(item))
|
|
206
|
+
continue
|
|
207
|
+
}
|
|
208
|
+
const child = initClone(item)
|
|
209
|
+
target.add(child)
|
|
210
|
+
if (isObjectLike(child)) {
|
|
211
|
+
seen.set(item, child)
|
|
212
|
+
stack.push({ source: item, target: child })
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
continue
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
for (const key of Object.keys(source)) {
|
|
219
|
+
const item = source[key]
|
|
220
|
+
if (!isObjectLike(item)) {
|
|
221
|
+
target[key] = item
|
|
222
|
+
continue
|
|
223
|
+
}
|
|
224
|
+
if (seen.has(item)) {
|
|
225
|
+
target[key] = seen.get(item)
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
228
|
+
const child = initClone(item)
|
|
229
|
+
target[key] = child
|
|
230
|
+
if (isObjectLike(child)) {
|
|
231
|
+
seen.set(item, child)
|
|
232
|
+
stack.push({ source: item, target: child })
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return root
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
module.exports = {
|
|
241
|
+
isObjectLike,
|
|
242
|
+
isPlainObject,
|
|
243
|
+
isTypedArray,
|
|
244
|
+
tokenizePath,
|
|
245
|
+
getValueByPath,
|
|
246
|
+
createIteratee,
|
|
247
|
+
cloneShallow,
|
|
248
|
+
cloneDeep,
|
|
249
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qezor/structkit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Iterative array and object utilities for high-use data shaping without callback hell or recursion-heavy internals.",
|
|
5
|
+
"files": [
|
|
6
|
+
"LICENSE",
|
|
7
|
+
"README.md",
|
|
8
|
+
"index.js",
|
|
9
|
+
"index.mjs",
|
|
10
|
+
"index.d.ts",
|
|
11
|
+
"bridge.js",
|
|
12
|
+
"bridge.mjs",
|
|
13
|
+
"bridge.d.ts",
|
|
14
|
+
"lib",
|
|
15
|
+
"test.js"
|
|
16
|
+
],
|
|
17
|
+
"main": "index.js",
|
|
18
|
+
"types": "index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./index.d.ts",
|
|
22
|
+
"require": "./index.js",
|
|
23
|
+
"import": "./index.mjs",
|
|
24
|
+
"default": "./index.js"
|
|
25
|
+
},
|
|
26
|
+
"./bridge": {
|
|
27
|
+
"types": "./bridge.d.ts",
|
|
28
|
+
"require": "./bridge.js",
|
|
29
|
+
"import": "./bridge.mjs",
|
|
30
|
+
"default": "./bridge.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"test": "node --test test.js",
|
|
35
|
+
"check": "node --test test.js",
|
|
36
|
+
"pack:dry": "npm pack --dry-run",
|
|
37
|
+
"prepublishOnly": "npm test"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"array",
|
|
41
|
+
"object",
|
|
42
|
+
"utility",
|
|
43
|
+
"lodash",
|
|
44
|
+
"iterative",
|
|
45
|
+
"performance"
|
|
46
|
+
],
|
|
47
|
+
"author": "Abdul Ahad",
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18"
|
|
54
|
+
},
|
|
55
|
+
"type": "commonjs",
|
|
56
|
+
"sideEffects": false
|
|
57
|
+
}
|
package/test.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
const test = require("node:test")
|
|
4
|
+
const assert = require("node:assert/strict")
|
|
5
|
+
const structkit = require("./index.js")
|
|
6
|
+
|
|
7
|
+
test("array helpers cover grouping uniqueness and sums", () => {
|
|
8
|
+
const buyers = [
|
|
9
|
+
{ id: "u1", role: "buyer", qty: 2 },
|
|
10
|
+
{ id: "u2", role: "buyer", qty: 5 },
|
|
11
|
+
{ id: "u3", role: "seller", qty: 1 },
|
|
12
|
+
{ id: "u3", role: "seller", qty: 1 },
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
assert.deepEqual(structkit.chunk([1, 2, 3, 4, 5], 2), [[1, 2], [3, 4], [5]])
|
|
16
|
+
assert.deepEqual(structkit.compact([0, 1, false, 2, "", 3]), [1, 2, 3])
|
|
17
|
+
assert.deepEqual(structkit.uniq([1, 1, 2, 3, 3]), [1, 2, 3])
|
|
18
|
+
assert.deepEqual(structkit.uniqBy(buyers, "id").map((item) => item.id), ["u1", "u2", "u3"])
|
|
19
|
+
assert.deepEqual(structkit.groupBy(buyers, "role"), {
|
|
20
|
+
buyer: [buyers[0], buyers[1]],
|
|
21
|
+
seller: [buyers[2], buyers[3]],
|
|
22
|
+
})
|
|
23
|
+
assert.deepEqual(structkit.countBy(buyers, "role"), {
|
|
24
|
+
buyer: 2,
|
|
25
|
+
seller: 2,
|
|
26
|
+
})
|
|
27
|
+
assert.equal(structkit.sumBy(buyers, "qty"), 9)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test("path and object helpers work iteratively", () => {
|
|
31
|
+
const state = {}
|
|
32
|
+
|
|
33
|
+
structkit.set(state, "queue.regions[0].city", "Pune")
|
|
34
|
+
structkit.set(state, "queue.regions[0].active", true)
|
|
35
|
+
structkit.set(state, ["queue", "meta", "moq"], 500)
|
|
36
|
+
|
|
37
|
+
assert.equal(structkit.get(state, "queue.regions[0].city"), "Pune")
|
|
38
|
+
assert.equal(structkit.has(state, "queue.meta.moq"), true)
|
|
39
|
+
assert.deepEqual(structkit.tokenizePath("queue.regions[0].city"), ["queue", "regions", 0, "city"])
|
|
40
|
+
assert.deepEqual(structkit.pick(state, ["queue.meta.moq", "queue.regions[0].city"]), {
|
|
41
|
+
queue: {
|
|
42
|
+
meta: { moq: 500 },
|
|
43
|
+
regions: [{ city: "Pune" }],
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const omitted = structkit.omit(state, "queue.regions[0].active")
|
|
48
|
+
assert.equal(structkit.has(omitted, "queue.regions[0].active"), false)
|
|
49
|
+
assert.equal(structkit.has(state, "queue.regions[0].active"), true)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test("cloneDeep and mergeDeep avoid shallow aliasing", () => {
|
|
53
|
+
const input = {
|
|
54
|
+
demand: {
|
|
55
|
+
buyers: [{ id: "u1" }],
|
|
56
|
+
counts: { joined: 2 },
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const cloned = structkit.cloneDeep(input)
|
|
61
|
+
cloned.demand.buyers[0].id = "u2"
|
|
62
|
+
|
|
63
|
+
assert.equal(input.demand.buyers[0].id, "u1")
|
|
64
|
+
|
|
65
|
+
const merged = structkit.mergeDeep({}, { demand: { joined: 2, nested: { a: 1 } } }, { demand: { buyers: 3, nested: { b: 2 } } })
|
|
66
|
+
assert.deepEqual(merged, {
|
|
67
|
+
demand: {
|
|
68
|
+
joined: 2,
|
|
69
|
+
buyers: 3,
|
|
70
|
+
nested: { a: 1, b: 2 },
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test("isEqual handles deep and cyclic values without recursion", () => {
|
|
76
|
+
const left = { demand: { buyers: [1, 2, 3] } }
|
|
77
|
+
const right = { demand: { buyers: [1, 2, 3] } }
|
|
78
|
+
|
|
79
|
+
left.self = left
|
|
80
|
+
right.self = right
|
|
81
|
+
|
|
82
|
+
assert.equal(structkit.isEqual(left, right), true)
|
|
83
|
+
assert.equal(structkit.isEqual(left, { demand: { buyers: [1, 2] } }), false)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test("bridge metadata is attached", () => {
|
|
87
|
+
const bridge = structkit.readQezorBridge(structkit)
|
|
88
|
+
assert.equal(bridge.name, "@qezor/structkit")
|
|
89
|
+
assert.equal(structkit.hasQezorCapability(structkit, "structure:path"), true)
|
|
90
|
+
})
|