@equinor/cpl-utils 0.0.5 → 0.1.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/dist/index.d.mts +96 -1
- package/dist/index.d.ts +96 -1
- package/dist/index.js +78 -3
- package/dist/index.mjs +76 -3
- package/package.json +4 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,98 @@
|
|
|
1
|
+
type TreeNode<T> = T & {
|
|
2
|
+
children?: TreeNode<T>[];
|
|
3
|
+
};
|
|
4
|
+
interface BuildTreeFromArrayOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Whether to throw Error if parent node of an element is not found.
|
|
7
|
+
* @default false
|
|
8
|
+
*/
|
|
9
|
+
throwOnInvalidTree?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Builds a tree structure from a flat array of items.
|
|
13
|
+
*
|
|
14
|
+
* @template T - The type of each item in the array.
|
|
15
|
+
* @param items - The flat array of items.
|
|
16
|
+
* @param idKey - The key representing the value of the id of each item, e.g. "id"
|
|
17
|
+
* @param parentIdKey - The key representing the value of the parent id of each item, e.g. "parentId"
|
|
18
|
+
* @param options - Options for strict mode.
|
|
19
|
+
* @returns Array of TreeNodes.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Basic usage with id/parentId keys
|
|
23
|
+
* const items = [
|
|
24
|
+
* { id: 1, parentId: null, name: 'Root' },
|
|
25
|
+
* { id: 2, parentId: 1, name: 'Child 1' },
|
|
26
|
+
* { id: 3, parentId: 1, name: 'Child 2' },
|
|
27
|
+
* { id: 4, parentId: 2, name: 'Grandchild' }
|
|
28
|
+
* ];
|
|
29
|
+
* const tree = buildTreeNodes(items, 'id', 'parentId');
|
|
30
|
+
* // tree = [{ id: 1, ...children: [...] }]
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // Custom keys
|
|
34
|
+
* const items = [
|
|
35
|
+
* { key: 'a', parent: null },
|
|
36
|
+
* { key: 'b', parent: 'a' },
|
|
37
|
+
* { key: 'c', parent: 'b' }
|
|
38
|
+
* ];
|
|
39
|
+
* const tree = buildTreeNodes(items, 'key', 'parent');
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* // Strict mode (throws on missing parent)
|
|
43
|
+
* buildTreeNodes(items, 'id', 'parentId', { throwOnInvalidTree: true });
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* // TypeScript usage
|
|
47
|
+
* interface Node { id: number; parentId: number | null; name: string }
|
|
48
|
+
* const tree = buildTreeNodes<Node>(items, 'id', 'parentId');
|
|
49
|
+
*/
|
|
50
|
+
declare function buildTreeNodes<T extends object>(items: T[], idKey: keyof T, parentIdKey: keyof T, options?: BuildTreeFromArrayOptions): TreeNode<T>[];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Efficiently checks for circular dependencies in a flat array of objects with id and parentId.
|
|
54
|
+
* Returns true if a cycle is found, false otherwise.
|
|
55
|
+
*
|
|
56
|
+
* @template T - The type of each item in the array.
|
|
57
|
+
* @param items - The flat array of items.
|
|
58
|
+
* @param idKey - The key representing the value of the id of each item, e.g. "id"
|
|
59
|
+
* @param parentIdKey - The key representing the value of the parent id of each item, e.g. "parentId"
|
|
60
|
+
* @returns {boolean} True if a circular dependency is found, false otherwise.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // No circular dependency
|
|
64
|
+
* const items = [
|
|
65
|
+
* { id: 1, parentId: null },
|
|
66
|
+
* { id: 2, parentId: 1 },
|
|
67
|
+
* { id: 3, parentId: 2 }
|
|
68
|
+
* ];
|
|
69
|
+
* hasCircularDependency(items, 'id', 'parentId'); // false
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // Circular dependency (3 -> 1 -> 2 -> 3)
|
|
73
|
+
* const items = [
|
|
74
|
+
* { id: 1, parentId: 2 },
|
|
75
|
+
* { id: 2, parentId: 3 },
|
|
76
|
+
* { id: 3, parentId: 1 }
|
|
77
|
+
* ];
|
|
78
|
+
* hasCircularDependency(items, 'id', 'parentId'); // true
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* // Custom keys
|
|
82
|
+
* const items = [
|
|
83
|
+
* { key: 'a', parent: null },
|
|
84
|
+
* { key: 'b', parent: 'a' },
|
|
85
|
+
* { key: 'c', parent: 'b' }
|
|
86
|
+
* ];
|
|
87
|
+
* hasCircularDependency(items, 'key', 'parent'); // false
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* // TypeScript usage
|
|
91
|
+
* interface Node { id: number; parentId: number | null }
|
|
92
|
+
* hasCircularDependency<Node>(items, 'id', 'parentId');
|
|
93
|
+
*/
|
|
94
|
+
declare function hasCircularDependency<T extends object>(items: T[], idKey: keyof T, parentIdKey: keyof T): boolean;
|
|
95
|
+
|
|
1
96
|
/**
|
|
2
97
|
* A function ensuring returning whether an item is undefined or not. If not undefined, it will exclude undefined from the type.
|
|
3
98
|
*
|
|
@@ -49,4 +144,4 @@ declare function itemIsDefined<T>(item: T): item is Exclude<typeof item, undefin
|
|
|
49
144
|
*/
|
|
50
145
|
declare function objectHasOwnProperty<Key extends PropertyKey>(object: object, key: Key): object is Record<Key, unknown>;
|
|
51
146
|
|
|
52
|
-
export { itemIsDefined, objectHasOwnProperty };
|
|
147
|
+
export { type TreeNode, buildTreeNodes, hasCircularDependency, itemIsDefined, objectHasOwnProperty };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,98 @@
|
|
|
1
|
+
type TreeNode<T> = T & {
|
|
2
|
+
children?: TreeNode<T>[];
|
|
3
|
+
};
|
|
4
|
+
interface BuildTreeFromArrayOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Whether to throw Error if parent node of an element is not found.
|
|
7
|
+
* @default false
|
|
8
|
+
*/
|
|
9
|
+
throwOnInvalidTree?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Builds a tree structure from a flat array of items.
|
|
13
|
+
*
|
|
14
|
+
* @template T - The type of each item in the array.
|
|
15
|
+
* @param items - The flat array of items.
|
|
16
|
+
* @param idKey - The key representing the value of the id of each item, e.g. "id"
|
|
17
|
+
* @param parentIdKey - The key representing the value of the parent id of each item, e.g. "parentId"
|
|
18
|
+
* @param options - Options for strict mode.
|
|
19
|
+
* @returns Array of TreeNodes.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Basic usage with id/parentId keys
|
|
23
|
+
* const items = [
|
|
24
|
+
* { id: 1, parentId: null, name: 'Root' },
|
|
25
|
+
* { id: 2, parentId: 1, name: 'Child 1' },
|
|
26
|
+
* { id: 3, parentId: 1, name: 'Child 2' },
|
|
27
|
+
* { id: 4, parentId: 2, name: 'Grandchild' }
|
|
28
|
+
* ];
|
|
29
|
+
* const tree = buildTreeNodes(items, 'id', 'parentId');
|
|
30
|
+
* // tree = [{ id: 1, ...children: [...] }]
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // Custom keys
|
|
34
|
+
* const items = [
|
|
35
|
+
* { key: 'a', parent: null },
|
|
36
|
+
* { key: 'b', parent: 'a' },
|
|
37
|
+
* { key: 'c', parent: 'b' }
|
|
38
|
+
* ];
|
|
39
|
+
* const tree = buildTreeNodes(items, 'key', 'parent');
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* // Strict mode (throws on missing parent)
|
|
43
|
+
* buildTreeNodes(items, 'id', 'parentId', { throwOnInvalidTree: true });
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* // TypeScript usage
|
|
47
|
+
* interface Node { id: number; parentId: number | null; name: string }
|
|
48
|
+
* const tree = buildTreeNodes<Node>(items, 'id', 'parentId');
|
|
49
|
+
*/
|
|
50
|
+
declare function buildTreeNodes<T extends object>(items: T[], idKey: keyof T, parentIdKey: keyof T, options?: BuildTreeFromArrayOptions): TreeNode<T>[];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Efficiently checks for circular dependencies in a flat array of objects with id and parentId.
|
|
54
|
+
* Returns true if a cycle is found, false otherwise.
|
|
55
|
+
*
|
|
56
|
+
* @template T - The type of each item in the array.
|
|
57
|
+
* @param items - The flat array of items.
|
|
58
|
+
* @param idKey - The key representing the value of the id of each item, e.g. "id"
|
|
59
|
+
* @param parentIdKey - The key representing the value of the parent id of each item, e.g. "parentId"
|
|
60
|
+
* @returns {boolean} True if a circular dependency is found, false otherwise.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // No circular dependency
|
|
64
|
+
* const items = [
|
|
65
|
+
* { id: 1, parentId: null },
|
|
66
|
+
* { id: 2, parentId: 1 },
|
|
67
|
+
* { id: 3, parentId: 2 }
|
|
68
|
+
* ];
|
|
69
|
+
* hasCircularDependency(items, 'id', 'parentId'); // false
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // Circular dependency (3 -> 1 -> 2 -> 3)
|
|
73
|
+
* const items = [
|
|
74
|
+
* { id: 1, parentId: 2 },
|
|
75
|
+
* { id: 2, parentId: 3 },
|
|
76
|
+
* { id: 3, parentId: 1 }
|
|
77
|
+
* ];
|
|
78
|
+
* hasCircularDependency(items, 'id', 'parentId'); // true
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* // Custom keys
|
|
82
|
+
* const items = [
|
|
83
|
+
* { key: 'a', parent: null },
|
|
84
|
+
* { key: 'b', parent: 'a' },
|
|
85
|
+
* { key: 'c', parent: 'b' }
|
|
86
|
+
* ];
|
|
87
|
+
* hasCircularDependency(items, 'key', 'parent'); // false
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* // TypeScript usage
|
|
91
|
+
* interface Node { id: number; parentId: number | null }
|
|
92
|
+
* hasCircularDependency<Node>(items, 'id', 'parentId');
|
|
93
|
+
*/
|
|
94
|
+
declare function hasCircularDependency<T extends object>(items: T[], idKey: keyof T, parentIdKey: keyof T): boolean;
|
|
95
|
+
|
|
1
96
|
/**
|
|
2
97
|
* A function ensuring returning whether an item is undefined or not. If not undefined, it will exclude undefined from the type.
|
|
3
98
|
*
|
|
@@ -49,4 +144,4 @@ declare function itemIsDefined<T>(item: T): item is Exclude<typeof item, undefin
|
|
|
49
144
|
*/
|
|
50
145
|
declare function objectHasOwnProperty<Key extends PropertyKey>(object: object, key: Key): object is Record<Key, unknown>;
|
|
51
146
|
|
|
52
|
-
export { itemIsDefined, objectHasOwnProperty };
|
|
147
|
+
export { type TreeNode, buildTreeNodes, hasCircularDependency, itemIsDefined, objectHasOwnProperty };
|
package/dist/index.js
CHANGED
|
@@ -20,22 +20,97 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
buildTreeNodes: () => buildTreeNodes,
|
|
24
|
+
hasCircularDependency: () => hasCircularDependency,
|
|
23
25
|
itemIsDefined: () => itemIsDefined,
|
|
24
26
|
objectHasOwnProperty: () => objectHasOwnProperty
|
|
25
27
|
});
|
|
26
28
|
module.exports = __toCommonJS(index_exports);
|
|
27
29
|
|
|
28
|
-
// src/
|
|
29
|
-
function
|
|
30
|
-
|
|
30
|
+
// src/has-circular-dependency.ts
|
|
31
|
+
function hasCircularDependency(items, idKey, parentIdKey) {
|
|
32
|
+
const parentMap = /* @__PURE__ */ new Map();
|
|
33
|
+
for (const item of items) {
|
|
34
|
+
parentMap.set(item[idKey], item[parentIdKey]);
|
|
35
|
+
}
|
|
36
|
+
const visited = /* @__PURE__ */ new Set();
|
|
37
|
+
const stack = /* @__PURE__ */ new Set();
|
|
38
|
+
function visit(id) {
|
|
39
|
+
if (stack.has(id)) return true;
|
|
40
|
+
if (visited.has(id)) return false;
|
|
41
|
+
stack.add(id);
|
|
42
|
+
const parentId = parentMap.get(id);
|
|
43
|
+
if (parentId !== void 0 && parentId !== null && parentMap.has(parentId)) {
|
|
44
|
+
if (visit(parentId)) return true;
|
|
45
|
+
}
|
|
46
|
+
stack.delete(id);
|
|
47
|
+
visited.add(id);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
for (const id of parentMap.keys()) {
|
|
51
|
+
if (visit(id)) return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
31
54
|
}
|
|
32
55
|
|
|
33
56
|
// src/objectHasOwnProperty.ts
|
|
34
57
|
function objectHasOwnProperty(object, key) {
|
|
35
58
|
return Object.prototype.hasOwnProperty.call(object, key);
|
|
36
59
|
}
|
|
60
|
+
|
|
61
|
+
// src/build-tree-nodes.ts
|
|
62
|
+
function buildTreeNodes(items, idKey, parentIdKey, options = {}) {
|
|
63
|
+
const { throwOnInvalidTree = false } = options;
|
|
64
|
+
if (!items.length) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
if (throwOnInvalidTree && hasCircularDependency(items, idKey, parentIdKey)) {
|
|
68
|
+
throw new Error("Circular dependency detected");
|
|
69
|
+
}
|
|
70
|
+
const lookupTable = /* @__PURE__ */ new Map();
|
|
71
|
+
for (const item of items) {
|
|
72
|
+
if (!objectHasOwnProperty(item, idKey)) {
|
|
73
|
+
throw new Error(`Item is missing idKey named '${String(idKey)}'`);
|
|
74
|
+
}
|
|
75
|
+
lookupTable.set(item[idKey], item);
|
|
76
|
+
}
|
|
77
|
+
const rootNodes = [];
|
|
78
|
+
for (const item of lookupTable.values()) {
|
|
79
|
+
const id = item[idKey];
|
|
80
|
+
const parentId = item[parentIdKey];
|
|
81
|
+
const node = lookupTable.get(id);
|
|
82
|
+
if (!node) {
|
|
83
|
+
throw new Error(`Node with id '${id}' not found in lookup table.`);
|
|
84
|
+
}
|
|
85
|
+
if (parentId === void 0 || parentId === null) {
|
|
86
|
+
rootNodes.push(node);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const parentNode = lookupTable.get(parentId);
|
|
90
|
+
if (!parentNode) {
|
|
91
|
+
const message = `Parent node with id '${parentId}' not found for node with id '${id}'.`;
|
|
92
|
+
if (throwOnInvalidTree) {
|
|
93
|
+
throw new Error(message);
|
|
94
|
+
}
|
|
95
|
+
rootNodes.push(node);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!parentNode.children) {
|
|
99
|
+
parentNode.children = [];
|
|
100
|
+
}
|
|
101
|
+
parentNode.children.push(node);
|
|
102
|
+
}
|
|
103
|
+
return rootNodes;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/itemIsDefined.ts
|
|
107
|
+
function itemIsDefined(item) {
|
|
108
|
+
return item !== void 0;
|
|
109
|
+
}
|
|
37
110
|
// Annotate the CommonJS export names for ESM import in node:
|
|
38
111
|
0 && (module.exports = {
|
|
112
|
+
buildTreeNodes,
|
|
113
|
+
hasCircularDependency,
|
|
39
114
|
itemIsDefined,
|
|
40
115
|
objectHasOwnProperty
|
|
41
116
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,86 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
function
|
|
3
|
-
|
|
1
|
+
// src/has-circular-dependency.ts
|
|
2
|
+
function hasCircularDependency(items, idKey, parentIdKey) {
|
|
3
|
+
const parentMap = /* @__PURE__ */ new Map();
|
|
4
|
+
for (const item of items) {
|
|
5
|
+
parentMap.set(item[idKey], item[parentIdKey]);
|
|
6
|
+
}
|
|
7
|
+
const visited = /* @__PURE__ */ new Set();
|
|
8
|
+
const stack = /* @__PURE__ */ new Set();
|
|
9
|
+
function visit(id) {
|
|
10
|
+
if (stack.has(id)) return true;
|
|
11
|
+
if (visited.has(id)) return false;
|
|
12
|
+
stack.add(id);
|
|
13
|
+
const parentId = parentMap.get(id);
|
|
14
|
+
if (parentId !== void 0 && parentId !== null && parentMap.has(parentId)) {
|
|
15
|
+
if (visit(parentId)) return true;
|
|
16
|
+
}
|
|
17
|
+
stack.delete(id);
|
|
18
|
+
visited.add(id);
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
for (const id of parentMap.keys()) {
|
|
22
|
+
if (visit(id)) return true;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
4
25
|
}
|
|
5
26
|
|
|
6
27
|
// src/objectHasOwnProperty.ts
|
|
7
28
|
function objectHasOwnProperty(object, key) {
|
|
8
29
|
return Object.prototype.hasOwnProperty.call(object, key);
|
|
9
30
|
}
|
|
31
|
+
|
|
32
|
+
// src/build-tree-nodes.ts
|
|
33
|
+
function buildTreeNodes(items, idKey, parentIdKey, options = {}) {
|
|
34
|
+
const { throwOnInvalidTree = false } = options;
|
|
35
|
+
if (!items.length) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
if (throwOnInvalidTree && hasCircularDependency(items, idKey, parentIdKey)) {
|
|
39
|
+
throw new Error("Circular dependency detected");
|
|
40
|
+
}
|
|
41
|
+
const lookupTable = /* @__PURE__ */ new Map();
|
|
42
|
+
for (const item of items) {
|
|
43
|
+
if (!objectHasOwnProperty(item, idKey)) {
|
|
44
|
+
throw new Error(`Item is missing idKey named '${String(idKey)}'`);
|
|
45
|
+
}
|
|
46
|
+
lookupTable.set(item[idKey], item);
|
|
47
|
+
}
|
|
48
|
+
const rootNodes = [];
|
|
49
|
+
for (const item of lookupTable.values()) {
|
|
50
|
+
const id = item[idKey];
|
|
51
|
+
const parentId = item[parentIdKey];
|
|
52
|
+
const node = lookupTable.get(id);
|
|
53
|
+
if (!node) {
|
|
54
|
+
throw new Error(`Node with id '${id}' not found in lookup table.`);
|
|
55
|
+
}
|
|
56
|
+
if (parentId === void 0 || parentId === null) {
|
|
57
|
+
rootNodes.push(node);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const parentNode = lookupTable.get(parentId);
|
|
61
|
+
if (!parentNode) {
|
|
62
|
+
const message = `Parent node with id '${parentId}' not found for node with id '${id}'.`;
|
|
63
|
+
if (throwOnInvalidTree) {
|
|
64
|
+
throw new Error(message);
|
|
65
|
+
}
|
|
66
|
+
rootNodes.push(node);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (!parentNode.children) {
|
|
70
|
+
parentNode.children = [];
|
|
71
|
+
}
|
|
72
|
+
parentNode.children.push(node);
|
|
73
|
+
}
|
|
74
|
+
return rootNodes;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/itemIsDefined.ts
|
|
78
|
+
function itemIsDefined(item) {
|
|
79
|
+
return item !== void 0;
|
|
80
|
+
}
|
|
10
81
|
export {
|
|
82
|
+
buildTreeNodes,
|
|
83
|
+
hasCircularDependency,
|
|
11
84
|
itemIsDefined,
|
|
12
85
|
objectHasOwnProperty
|
|
13
86
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@equinor/cpl-utils",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"tsup": "^8.3.6",
|
|
14
14
|
"typedoc": "^0.27.6",
|
|
15
15
|
"typedoc-plugin-markdown": "^4.4.1",
|
|
16
|
-
"
|
|
16
|
+
"vitest": "^3.2.4",
|
|
17
|
+
"@equinor/cpl-eslint-config": "0.0.9",
|
|
17
18
|
"@equinor/cpl-typescript-config": "0.0.2"
|
|
18
19
|
},
|
|
19
20
|
"publishConfig": {
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
"build": "tsup src/index.ts --format esm,cjs --dts --external react",
|
|
24
25
|
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
|
|
25
26
|
"dev": "tsup src/index.ts --format esm,cjs --watch --dts --external react",
|
|
27
|
+
"test": "vitest --run",
|
|
26
28
|
"typedoc": "typedoc && rm ./typedoc/README.mdx",
|
|
27
29
|
"typedoc:watch": "typedoc --watch --preserveWatchOutput",
|
|
28
30
|
"lint": "TIMING=1 eslint . --fix",
|