@equinor/cpl-utils 0.0.5 → 0.2.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 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/itemIsDefined.ts
29
- function itemIsDefined(item) {
30
- return item !== void 0;
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/itemIsDefined.ts
2
- function itemIsDefined(item) {
3
- return item !== void 0;
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.5",
3
+ "version": "0.2.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
- "@equinor/cpl-eslint-config": "0.0.8",
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",