@okutils/array-to-tree 0.0.1-alpha.1 → 0.0.1

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/README.md CHANGED
@@ -1,7 +1,7 @@
1
- # @oktils/array-to-tree
1
+ # @okutils/array-to-tree
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@oktils/array-to-tree.svg)](https://www.npmjs.com/package/@oktils/array-to-tree)
4
- [![license](https://img.shields.io/npm/l/@oktils/array-to-tree.svg)](LICENSE)
3
+ [![npm version](https://img.shields.io/npm/v/@okutils/array-to-tree.svg)](https://www.npmjs.com/package/@okutils/array-to-tree)
4
+ [![license](https://img.shields.io/npm/l/@okutils/array-to-tree.svg)](LICENSE)
5
5
 
6
6
  A TypeScript utility library for converting a flat array structure into a hierarchical tree structure based on parent-child relationships.
7
7
 
@@ -9,7 +9,7 @@ This project is based on [alferov/array-to-tree](https://github.com/alferov/arra
9
9
 
10
10
  > [!IMPORTANT]
11
11
  >
12
- > Currently in Alpha stage. Bugs or breaking changes may occur.
12
+ > This package has not yet reached version 1.0. Bugs or breaking changes may occur.
13
13
 
14
14
  ## Features
15
15
 
@@ -21,13 +21,16 @@ This project is based on [alferov/array-to-tree](https://github.com/alferov/arra
21
21
 
22
22
  ```bash
23
23
  # Using pnpm
24
- pnpm add @oktils/array-to-tree
24
+ pnpm add @okutils/array-to-tree
25
25
 
26
26
  # Using yarn
27
- yarn add @oktils/array-to-tree
27
+ yarn add @okutils/array-to-tree
28
28
 
29
29
  # Using npm
30
- npm install @oktils/array-to-tree
30
+ npm install @okutils/array-to-tree
31
+
32
+ # Using Bun
33
+ bun add @okutils/array-to-tree
31
34
  ```
32
35
 
33
36
  ## Usage
@@ -35,7 +38,7 @@ npm install @oktils/array-to-tree
35
38
  ### Basic Usage
36
39
 
37
40
  ```typescript
38
- import { arrayToTree } from "@oktils/array-to-tree";
41
+ import { arrayToTree } from "@okutils/array-to-tree";
39
42
 
40
43
  const data = [
41
44
  { id: 1, name: "A", parentId: null },
@@ -119,7 +122,7 @@ export interface ArrayToTreeOptions {
119
122
  #### Example: Using Custom Fields
120
123
 
121
124
  ```typescript
122
- import { arrayToTree } from "@oktils/array-to-tree";
125
+ import { arrayToTree } from "@okutils/array-to-tree";
123
126
 
124
127
  const data = [
125
128
  { key: "node-1", parent: null, title: "Node 1" },
@@ -152,6 +155,82 @@ const tree = arrayToTree(data, {
152
155
  ]
153
156
  ```
154
157
 
155
- ## 📄 License
158
+ ### Error Classes
159
+
160
+ #### ArrayToTreeError
161
+
162
+ Represents an error that occurs when converting an array to a tree structure.
163
+
164
+ ```typescript
165
+ export class ArrayToTreeError extends Error {
166
+ constructor(message: string) {
167
+ super(message);
168
+ this.name = "ArrayToTreeError";
169
+ }
170
+ }
171
+ ```
172
+
173
+ ##### Use Cases
174
+
175
+ `ArrayToTreeError` is thrown in the following situations:
176
+
177
+ 1. **Invalid Input Type**: When the `data` argument passed is not an array.
178
+
179
+ ```typescript
180
+ arrayToTree("not array"); // Throws ArrayToTreeError: Expected an array but got an invalid argument.
181
+ ```
182
+
183
+ 2. **Duplicate Node ID**: When there are nodes with the same `id` in the array.
184
+
185
+ ```typescript
186
+ const data = [
187
+ { id: 1, name: "A", parentId: null },
188
+ { id: 1, name: "A-duplicate", parentId: null }, // Duplicate id
189
+ ];
190
+ arrayToTree(data); // Throws ArrayToTreeError: Duplicate node id "1" detected.
191
+ ```
192
+
193
+ 3. **Self-Reference Error**: When a node's `parentId` is equal to its own `id`, and the `allowSelfAsParent` option is `false`.
194
+
195
+ ```typescript
196
+ const data = [
197
+ { id: 1, name: "A", parentId: 1 }, // Self-reference
198
+ ];
199
+ arrayToTree(data, { allowSelfAsParent: false });
200
+ // Throws ArrayToTreeError: Node "1" cannot be its own parent (self reference found).
201
+ ```
202
+
203
+ 4. **Circular Reference Detection**: When a circular dependency is formed between nodes.
204
+
205
+ ```typescript
206
+ const data = [
207
+ { id: 1, name: "A", parentId: 3 },
208
+ { id: 2, name: "B", parentId: 1 },
209
+ { id: 3, name: "C", parentId: 2 }, // Forms a cycle: 1 -> 3 -> 2 -> 1
210
+ ];
211
+ arrayToTree(data); // Throws ArrayToTreeError: Cycle detected in parent chain: 1 -> 3 -> 2 -> 1
212
+ ```
213
+
214
+ ##### Error Handling
215
+
216
+ It is recommended to use `try-catch` to capture and handle these errors:
217
+
218
+ ```typescript
219
+ import { arrayToTree, ArrayToTreeError } from "@okutils/array-to-tree";
220
+
221
+ try {
222
+ const tree = arrayToTree(data);
223
+ console.log(tree);
224
+ } catch (error) {
225
+ if (error instanceof ArrayToTreeError) {
226
+ console.error("Conversion failed:", error.message);
227
+ // Handle specific conversion errors
228
+ } else {
229
+ console.error("Unknown error:", error);
230
+ }
231
+ }
232
+ ```
233
+
234
+ ## License
156
235
 
157
236
  [MIT](LICENSE) © Luke Na
package/README.zh-CN.md CHANGED
@@ -1,7 +1,7 @@
1
- # @oktils/array-to-tree
1
+ # @okutils/array-to-tree
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@oktils/array-to-tree.svg)](https://www.npmjs.com/package/@oktils/array-to-tree)
4
- [![license](https://img.shields.io/npm/l/@oktils/array-to-tree.svg)](LICENSE)
3
+ [![npm version](https://img.shields.io/npm/v/@okutils/array-to-tree.svg)](https://www.npmjs.com/package/@okutils/array-to-tree)
4
+ [![license](https://img.shields.io/npm/l/@okutils/array-to-tree.svg)](LICENSE)
5
5
 
6
6
  一个 TypeScript 工具库,用于将扁平的数组结构通过父子关系转换为层级树结构。
7
7
 
@@ -9,7 +9,7 @@
9
9
 
10
10
  > [!IMPORTANT]
11
11
  >
12
- > 目前处于 Alpha 阶段,可能会存在 bug 或不兼容的变更。
12
+ > 目前还没有发布 1.0 版,可能会存在 bug 或不兼容的变更。
13
13
 
14
14
  ## 特性
15
15
 
@@ -21,13 +21,16 @@
21
21
 
22
22
  ```bash
23
23
  # 使用 pnpm
24
- pnpm add @oktils/array-to-tree
24
+ pnpm add @okutils/array-to-tree
25
25
 
26
26
  # 使用 yarn
27
- yarn add @oktils/array-to-tree
27
+ yarn add @okutils/array-to-tree
28
28
 
29
29
  # 使用 npm
30
- npm install @oktils/array-to-tree
30
+ npm install @okutils/array-to-tree
31
+
32
+ # 使用 Bun
33
+ bun add @okutils/array-to-tree
31
34
  ```
32
35
 
33
36
  ## 使用方法
@@ -35,7 +38,7 @@ npm install @oktils/array-to-tree
35
38
  ### 基本用法
36
39
 
37
40
  ```typescript
38
- import { arrayToTree } from "@oktils/array-to-tree";
41
+ import { arrayToTree } from "@okutils/array-to-tree";
39
42
 
40
43
  const data = [
41
44
  { id: 1, name: "A", parentId: null },
@@ -119,7 +122,7 @@ export interface ArrayToTreeOptions {
119
122
  #### 示例:使用自定义字段
120
123
 
121
124
  ```typescript
122
- import { arrayToTree } from "@oktils/array-to-tree";
125
+ import { arrayToTree } from "@okutils/array-to-tree";
123
126
 
124
127
  const data = [
125
128
  { key: "node-1", parent: null, title: "Node 1" },
@@ -152,6 +155,82 @@ const tree = arrayToTree(data, {
152
155
  ]
153
156
  ```
154
157
 
155
- ## 📄 许可证
158
+ ### 错误类
159
+
160
+ #### ArrayToTreeError
161
+
162
+ 用于表示在将数组转换为树结构时发生的错误。
163
+
164
+ ```typescript
165
+ export class ArrayToTreeError extends Error {
166
+ constructor(message: string) {
167
+ super(message);
168
+ this.name = "ArrayToTreeError";
169
+ }
170
+ }
171
+ ```
172
+
173
+ ##### 使用场景
174
+
175
+ `ArrayToTreeError` 会在以下情况下被抛出:
176
+
177
+ 1. **输入类型错误**: 当传入的 `data` 参数不是数组时
178
+
179
+ ```typescript
180
+ arrayToTree("not array"); // 抛出 ArrayToTreeError: Expected an array but got an invalid argument.
181
+ ```
182
+
183
+ 2. **重复的节点 ID**: 当数组中存在相同 `id` 的节点时
184
+
185
+ ```typescript
186
+ const data = [
187
+ { id: 1, name: "A", parentId: null },
188
+ { id: 1, name: "A-duplicate", parentId: null }, // 重复的 id
189
+ ];
190
+ arrayToTree(data); // 抛出 ArrayToTreeError: Duplicate node id "1" detected.
191
+ ```
192
+
193
+ 3. **自引用错误**: 当节点的 `parentId` 等于自身 `id`,且 `allowSelfAsParent` 选项为 `false` 时
194
+
195
+ ```typescript
196
+ const data = [
197
+ { id: 1, name: "A", parentId: 1 }, // 自引用
198
+ ];
199
+ arrayToTree(data, { allowSelfAsParent: false });
200
+ // 抛出 ArrayToTreeError: Node "1" cannot be its own parent (self reference found).
201
+ ```
202
+
203
+ 4. **循环引用检测**: 当节点之间形成循环依赖时
204
+
205
+ ```typescript
206
+ const data = [
207
+ { id: 1, name: "A", parentId: 3 },
208
+ { id: 2, name: "B", parentId: 1 },
209
+ { id: 3, name: "C", parentId: 2 }, // 形成循环: 1 -> 3 -> 2 -> 1
210
+ ];
211
+ arrayToTree(data); // 抛出 ArrayToTreeError: Cycle detected in parent chain: 1 -> 3 -> 2 -> 1
212
+ ```
213
+
214
+ ##### 错误处理
215
+
216
+ 建议使用 `try-catch` 捕获并处理这些错误:
217
+
218
+ ```typescript
219
+ import { arrayToTree, ArrayToTreeError } from "@okutils/array-to-tree";
220
+
221
+ try {
222
+ const tree = arrayToTree(data);
223
+ console.log(tree);
224
+ } catch (error) {
225
+ if (error instanceof ArrayToTreeError) {
226
+ console.error("转换失败:", error.message);
227
+ // 处理特定的转换错误
228
+ } else {
229
+ console.error("未知错误:", error);
230
+ }
231
+ }
232
+ ```
233
+
234
+ ## 许可证
156
235
 
157
236
  [MIT](LICENSE) © Luke Na
package/dist/cjs/index.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";class r extends Error{constructor(r){super(r),this.name="ArrayToTreeError"}}const t=(r,e,n,o)=>Array.isArray(e)?e.map(e=>{const s={...e},a=r[e[n]];return a&&(s[o]=t(r,a,n,o)),s}):[],e=(r,t)=>{const e=t.split(/[.[\]]/g);let n=r;for(const r of e){if(null===n)return null;if(void 0===n)return null;const t=r.replace(/['"]/g,"");""!==t.trim()&&(n=n[t])}return void 0===n?null:n};exports.ArrayToTreeError=r,exports.arrayToTree=(n,o)=>{const s=(r=>({allowSelfAsParent:(r=r??{}).allowSelfAsParent??!1,childrenId:r.childrenId??"children",customId:r.customId??"id",parentId:r.parentId??"parentId",rootId:r.rootId??"__ARRAY_TO_TREE_VIRTUAL_ROOT_ID__"}))(o);if(!Array.isArray(n))throw new r("Expected an array but got an invalid argument.");const a=((t,n)=>{const o={};for(const e of t){const t=e[n.customId];if(null!=t&&Object.hasOwn(o,t))throw new r(`Duplicate node id "${t}" detected.`);o[t]=e}return t.reduce((t,s)=>{const a=e(s,n.customId);let c=e(s,n.parentId);if(c===a){if(!n.allowSelfAsParent)throw new r(`Node "${a}" cannot be its own parent (self reference found).`);c=n.rootId}return c&&Object.hasOwn(o,c)||(c=n.rootId),c&&Object.hasOwn(t,c)?(t[c].push(s),t):(t[c]=[s],t)},{})})(n,s);return t(a,a[s.rootId],s.customId,s.childrenId)};
1
+ "use strict";class r extends Error{constructor(r){super(r),this.name="ArrayToTreeError"}}const t=(r,e,n,o)=>Array.isArray(e)?e.map(e=>{const s={...e},a=r[e[n]];return a&&(s[o]=t(r,a,n,o)),s}):[],e=(r,t)=>{const e=t.split(/[.[\]]/g);let n=r;for(const r of e){if(null===n)return null;if(void 0===n)return null;const t=r.replace(/['"]/g,"");""!==t.trim()&&(n=n[t])}return void 0===n?null:n};exports.ArrayToTreeError=r,exports.arrayToTree=(n,o)=>{const s=(r=>({allowSelfAsParent:(r=r??{}).allowSelfAsParent??!1,childrenId:r.childrenId??"children",customId:r.customId??"id",parentId:r.parentId??"parentId",rootId:r.rootId??"__ARRAY_TO_TREE_VIRTUAL_ROOT_ID__"}))(o);if(!Array.isArray(n))throw new r("Expected an array but got an invalid argument.");const a=((t,n)=>{const o={};for(const e of t){const t=e[n.customId];if(null!=t&&Object.hasOwn(o,t))throw new r(`Duplicate node id "${t}" detected.`);o[t]=e}const s=new Set;return t.reduce((t,a)=>{const c=e(a,n.customId);let d=e(a,n.parentId);if(d===c){if(!n.allowSelfAsParent)throw new r(`Node "${c}" cannot be its own parent (self reference found).`);d=n.rootId}if(null!=c&&!s.has(c)){const t=new Set([c]);let a=d;for(;a&&a!==n.rootId&&!s.has(a);){if(t.has(a)){const e=[...t,a].join(" -> ");throw new r(`Cycle detected in parent chain: ${e}`)}t.add(a);const s=o[a];if(!s)break;a=e(s,n.parentId)}for(const r of t)s.add(r)}return d&&Object.hasOwn(o,d)||(d=n.rootId),Object.hasOwn(t,d)?t[d].push(a):t[d]=[a],t},{})})(n,s);return t(a,a[s.rootId],s.customId,s.childrenId)};
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../src/errors/array-to-tree-error.ts","../../../../src/internal/create-tree.ts","../../../../src/internal/get.ts","../../../../src/utils/array-to-tree.ts","../../../../src/internal/resolve-options.ts","../../../../src/internal/group-by-parents.ts"],"sourcesContent":["export class ArrayToTreeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ArrayToTreeError\";\n }\n}\n","export const createTree = (\n grouped: Record<string, any[]>,\n rootNodes: any[],\n customId: string,\n childrenProperty: string,\n) => {\n if (!Array.isArray(rootNodes)) {\n return [];\n }\n\n return rootNodes.map((node) => {\n const newNode = { ...node };\n const children = grouped[node[customId]];\n if (children) {\n newNode[childrenProperty] = createTree(\n grouped,\n children,\n customId,\n childrenProperty,\n );\n }\n return newNode;\n });\n};\n","/**\n * This code is modified from the radash project.\n * @see https://github.com/sodiray/radash\n */\nexport const get = (value: any, path: string) => {\n const segments = path.split(/[.[\\]]/g);\n let current: any = value;\n for (const key of segments) {\n if (current === null) return null;\n if (current === undefined) return null;\n const dequoted = key.replace(/['\"]/g, \"\");\n if (dequoted.trim() === \"\") continue;\n current = current[dequoted];\n }\n if (current === undefined) return null;\n return current;\n};\n","import { ArrayToTreeError } from \"../errors\";\nimport { createTree, groupByParents, resolveOptions } from \"../internal\";\nimport type {\n ArrayToTreeOptions,\n NormalizedArrayToTreeOptions,\n} from \"../types\";\n\nexport const arrayToTree = (\n data: Record<string, any>[],\n options?: ArrayToTreeOptions,\n) => {\n const normalizeOptions = resolveOptions(\n options,\n ) as NormalizedArrayToTreeOptions;\n\n if (!Array.isArray(data)) {\n throw new ArrayToTreeError(\n \"Expected an array but got an invalid argument.\",\n );\n }\n\n const grouped = groupByParents(data, normalizeOptions);\n\n return createTree(\n grouped,\n grouped[normalizeOptions.rootId],\n normalizeOptions.customId,\n normalizeOptions.childrenId,\n );\n};\n","import type { ArrayToTreeOptions } from \"../types\";\n\nexport const resolveOptions = (\n options?: ArrayToTreeOptions,\n): Required<ArrayToTreeOptions> => {\n options = options ?? {};\n return {\n allowSelfAsParent: options.allowSelfAsParent ?? false,\n childrenId: options.childrenId ?? \"children\",\n customId: options.customId ?? \"id\",\n parentId: options.parentId ?? \"parentId\",\n rootId: options.rootId ?? \"__ARRAY_TO_TREE_VIRTUAL_ROOT_ID__\",\n };\n};\n","import { ArrayToTreeError } from \"../errors\";\nimport type { NormalizedArrayToTreeOptions } from \"../types\";\nimport { get } from \"./get\";\n\nexport const groupByParents = (\n array: Record<string, any>[],\n options: NormalizedArrayToTreeOptions,\n) => {\n const arrayById: Record<string, any> = {};\n for (const item of array) {\n const key = item[options.customId];\n if (key != null && Object.hasOwn(arrayById, key)) {\n throw new ArrayToTreeError(`Duplicate node id \"${key}\" detected.`);\n }\n arrayById[key] = item;\n }\n\n return array.reduce((prev, item) => {\n const id = get(item, options.customId);\n let parentId = get(item, options.parentId);\n\n // Handle self-referencing nodes\n if (parentId === id) {\n if (!options.allowSelfAsParent) {\n throw new ArrayToTreeError(\n `Node \"${id}\" cannot be its own parent (self reference found).`,\n );\n }\n parentId = options.rootId;\n }\n\n if (!parentId || !Object.hasOwn(arrayById, parentId)) {\n parentId = options.rootId;\n }\n\n if (parentId && Object.hasOwn(prev, parentId)) {\n prev[parentId].push(item);\n return prev;\n }\n\n prev[parentId] = [item];\n return prev;\n }, {});\n};\n"],"names":["ArrayToTreeError","Error","constructor","message","super","this","name","createTree","grouped","rootNodes","customId","childrenProperty","Array","isArray","map","node","newNode","children","get","value","path","segments","split","current","key","undefined","dequoted","replace","trim","data","options","normalizeOptions","allowSelfAsParent","childrenId","parentId","rootId","resolveOptions","array","arrayById","item","Object","hasOwn","reduce","prev","id","push","groupByParents"],"mappings":"aAAM,MAAOA,UAAyBC,MACpC,WAAAC,CAAYC,GACVC,MAAMD,GACNE,KAAKC,KAAO,kBACd,ECJK,MAAMC,EAAa,CACxBC,EACAC,EACAC,EACAC,IAEKC,MAAMC,QAAQJ,GAIZA,EAAUK,IAAKC,IACpB,MAAMC,EAAU,IAAKD,GACfE,EAAWT,EAAQO,EAAKL,IAS9B,OARIO,IACFD,EAAQL,GAAoBJ,EAC1BC,EACAS,EACAP,EACAC,IAGGK,IAdA,GCHEE,EAAM,CAACC,EAAYC,KAC9B,MAAMC,EAAWD,EAAKE,MAAM,WAC5B,IAAIC,EAAeJ,EACnB,IAAK,MAAMK,KAAOH,EAAU,CAC1B,GAAgB,OAAZE,EAAkB,OAAO,KAC7B,QAAgBE,IAAZF,EAAuB,OAAO,KAClC,MAAMG,EAAWF,EAAIG,QAAQ,QAAS,IACd,KAApBD,EAASE,SACbL,EAAUA,EAAQG,GACpB,CACA,YAAgBD,IAAZF,EAA8B,KAC3BA,kDCRkB,CACzBM,EACAC,KAEA,MAAMC,ECTsB,CAC5BD,IAGO,CACLE,mBAFFF,EAAUA,GAAW,CAAA,GAEQE,oBAAqB,EAChDC,WAAYH,EAAQG,YAAc,WAClCvB,SAAUoB,EAAQpB,UAAY,KAC9BwB,SAAUJ,EAAQI,UAAY,WAC9BC,OAAQL,EAAQK,QAAU,sCDAHC,CACvBN,GAGF,IAAKlB,MAAMC,QAAQgB,GACjB,MAAM,IAAI7B,EACR,kDAIJ,MAAMQ,EEjBsB,EAC5B6B,EACAP,KAEA,MAAMQ,EAAiC,CAAA,EACvC,IAAK,MAAMC,KAAQF,EAAO,CACxB,MAAMb,EAAMe,EAAKT,EAAQpB,UACzB,GAAW,MAAPc,GAAegB,OAAOC,OAAOH,EAAWd,GAC1C,MAAM,IAAIxB,EAAiB,sBAAsBwB,gBAEnDc,EAAUd,GAAOe,CACnB,CAEA,OAAOF,EAAMK,OAAO,CAACC,EAAMJ,KACzB,MAAMK,EAAK1B,EAAIqB,EAAMT,EAAQpB,UAC7B,IAAIwB,EAAWhB,EAAIqB,EAAMT,EAAQI,UAGjC,GAAIA,IAAaU,EAAI,CACnB,IAAKd,EAAQE,kBACX,MAAM,IAAIhC,EACR,SAAS4C,uDAGbV,EAAWJ,EAAQK,MACrB,CAMA,OAJKD,GAAaM,OAAOC,OAAOH,EAAWJ,KACzCA,EAAWJ,EAAQK,QAGjBD,GAAYM,OAAOC,OAAOE,EAAMT,IAClCS,EAAKT,GAAUW,KAAKN,GACbI,IAGTA,EAAKT,GAAY,CAACK,GACXI,IACN,CAAA,IFrBaG,CAAejB,EAAME,GAErC,OAAOxB,EACLC,EACAA,EAAQuB,EAAiBI,QACzBJ,EAAiBrB,SACjBqB,EAAiBE"}
1
+ {"version":3,"file":"index.js","sources":["../../../../src/errors/array-to-tree-error.ts","../../../../src/internal/create-tree.ts","../../../../src/internal/get.ts","../../../../src/utils/array-to-tree.ts","../../../../src/internal/resolve-options.ts","../../../../src/internal/group-by-parents.ts"],"sourcesContent":["export class ArrayToTreeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ArrayToTreeError\";\n }\n}\n","export const createTree = (\n grouped: Record<string, any[]>,\n rootNodes: any[],\n customId: string,\n childrenProperty: string,\n) => {\n if (!Array.isArray(rootNodes)) {\n return [];\n }\n\n return rootNodes.map((node) => {\n const newNode = { ...node };\n const children = grouped[node[customId]];\n if (children) {\n newNode[childrenProperty] = createTree(\n grouped,\n children,\n customId,\n childrenProperty,\n );\n }\n return newNode;\n });\n};\n","/**\n * This code is modified from the radash project.\n * @see https://github.com/sodiray/radash\n */\nexport const get = (value: any, path: string) => {\n const segments = path.split(/[.[\\]]/g);\n let current: any = value;\n for (const key of segments) {\n if (current === null) return null;\n if (current === undefined) return null;\n const dequoted = key.replace(/['\"]/g, \"\");\n if (dequoted.trim() === \"\") continue;\n current = current[dequoted];\n }\n if (current === undefined) return null;\n return current;\n};\n","import { ArrayToTreeError } from \"../errors\";\nimport { createTree, groupByParents, resolveOptions } from \"../internal\";\nimport type {\n ArrayToTreeOptions,\n NormalizedArrayToTreeOptions,\n} from \"../types\";\n\nexport const arrayToTree = (\n data: Record<string, any>[],\n options?: ArrayToTreeOptions,\n) => {\n const normalizeOptions = resolveOptions(\n options,\n ) as NormalizedArrayToTreeOptions;\n\n if (!Array.isArray(data)) {\n throw new ArrayToTreeError(\n \"Expected an array but got an invalid argument.\",\n );\n }\n\n const grouped = groupByParents(data, normalizeOptions);\n\n return createTree(\n grouped,\n grouped[normalizeOptions.rootId],\n normalizeOptions.customId,\n normalizeOptions.childrenId,\n );\n};\n","import type { ArrayToTreeOptions } from \"../types\";\n\nexport const resolveOptions = (\n options?: ArrayToTreeOptions,\n): Required<ArrayToTreeOptions> => {\n options = options ?? {};\n return {\n allowSelfAsParent: options.allowSelfAsParent ?? false,\n childrenId: options.childrenId ?? \"children\",\n customId: options.customId ?? \"id\",\n parentId: options.parentId ?? \"parentId\",\n rootId: options.rootId ?? \"__ARRAY_TO_TREE_VIRTUAL_ROOT_ID__\",\n };\n};\n","import { ArrayToTreeError } from \"../errors\";\nimport type { NormalizedArrayToTreeOptions } from \"../types\";\nimport { get } from \"./get\";\n\nexport const groupByParents = (\n array: Record<string, any>[],\n options: NormalizedArrayToTreeOptions,\n) => {\n const arrayById: Record<string, any> = {};\n\n // 第一次遍历:构建索引并检测重复 id\n for (const item of array) {\n const key = item[options.customId];\n if (key != null && Object.hasOwn(arrayById, key)) {\n throw new ArrayToTreeError(`Duplicate node id \"${key}\" detected.`);\n }\n arrayById[key] = item;\n }\n\n // 用于缓存已确认“安全(无环)”的节点(其向上链路无环)\n const safe = new Set<any>();\n\n // ...existing code...\n return array.reduce<Record<string, any[]>>((grouped, item) => {\n const id = get(item, options.customId);\n let parentId = get(item, options.parentId);\n\n // 1. 先处理自指节点\n if (parentId === id) {\n if (!options.allowSelfAsParent) {\n throw new ArrayToTreeError(\n `Node \"${id}\" cannot be its own parent (self reference found).`,\n );\n }\n parentId = options.rootId;\n }\n\n // 2. 再进行环检测(仅在 id 存在时)\n if (id != null && !safe.has(id)) {\n const path = new Set<any>([id]);\n let cursor = parentId;\n\n while (cursor && cursor !== options.rootId) {\n if (safe.has(cursor)) {\n break;\n }\n if (path.has(cursor)) {\n const cyclePath = [...path, cursor].join(\" -> \");\n throw new ArrayToTreeError(\n `Cycle detected in parent chain: ${cyclePath}`,\n );\n }\n path.add(cursor);\n\n const parentNode = arrayById[cursor];\n if (!parentNode) {\n break;\n }\n cursor = get(parentNode, options.parentId);\n }\n for (const n of path) safe.add(n);\n }\n\n // 3. 父不存在或为空,归为 root\n if (!parentId || !Object.hasOwn(arrayById, parentId)) {\n parentId = options.rootId;\n }\n\n if (Object.hasOwn(grouped, parentId)) {\n grouped[parentId].push(item);\n } else {\n grouped[parentId] = [item];\n }\n return grouped;\n }, {});\n};\n"],"names":["ArrayToTreeError","Error","constructor","message","super","this","name","createTree","grouped","rootNodes","customId","childrenProperty","Array","isArray","map","node","newNode","children","get","value","path","segments","split","current","key","undefined","dequoted","replace","trim","data","options","normalizeOptions","allowSelfAsParent","childrenId","parentId","rootId","resolveOptions","array","arrayById","item","Object","hasOwn","safe","Set","reduce","id","has","cursor","cyclePath","join","add","parentNode","n","push","groupByParents"],"mappings":"aAAM,MAAOA,UAAyBC,MACpC,WAAAC,CAAYC,GACVC,MAAMD,GACNE,KAAKC,KAAO,kBACd,ECJK,MAAMC,EAAa,CACxBC,EACAC,EACAC,EACAC,IAEKC,MAAMC,QAAQJ,GAIZA,EAAUK,IAAKC,IACpB,MAAMC,EAAU,IAAKD,GACfE,EAAWT,EAAQO,EAAKL,IAS9B,OARIO,IACFD,EAAQL,GAAoBJ,EAC1BC,EACAS,EACAP,EACAC,IAGGK,IAdA,GCHEE,EAAM,CAACC,EAAYC,KAC9B,MAAMC,EAAWD,EAAKE,MAAM,WAC5B,IAAIC,EAAeJ,EACnB,IAAK,MAAMK,KAAOH,EAAU,CAC1B,GAAgB,OAAZE,EAAkB,OAAO,KAC7B,QAAgBE,IAAZF,EAAuB,OAAO,KAClC,MAAMG,EAAWF,EAAIG,QAAQ,QAAS,IACd,KAApBD,EAASE,SACbL,EAAUA,EAAQG,GACpB,CACA,YAAgBD,IAAZF,EAA8B,KAC3BA,kDCRkB,CACzBM,EACAC,KAEA,MAAMC,ECTsB,CAC5BD,IAGO,CACLE,mBAFFF,EAAUA,GAAW,CAAA,GAEQE,oBAAqB,EAChDC,WAAYH,EAAQG,YAAc,WAClCvB,SAAUoB,EAAQpB,UAAY,KAC9BwB,SAAUJ,EAAQI,UAAY,WAC9BC,OAAQL,EAAQK,QAAU,sCDAHC,CACvBN,GAGF,IAAKlB,MAAMC,QAAQgB,GACjB,MAAM,IAAI7B,EACR,kDAIJ,MAAMQ,EEjBsB,EAC5B6B,EACAP,KAEA,MAAMQ,EAAiC,CAAA,EAGvC,IAAK,MAAMC,KAAQF,EAAO,CACxB,MAAMb,EAAMe,EAAKT,EAAQpB,UACzB,GAAW,MAAPc,GAAegB,OAAOC,OAAOH,EAAWd,GAC1C,MAAM,IAAIxB,EAAiB,sBAAsBwB,gBAEnDc,EAAUd,GAAOe,CACnB,CAGA,MAAMG,EAAO,IAAIC,IAGjB,OAAON,EAAMO,OAA8B,CAACpC,EAAS+B,KACnD,MAAMM,EAAK3B,EAAIqB,EAAMT,EAAQpB,UAC7B,IAAIwB,EAAWhB,EAAIqB,EAAMT,EAAQI,UAGjC,GAAIA,IAAaW,EAAI,CACnB,IAAKf,EAAQE,kBACX,MAAM,IAAIhC,EACR,SAAS6C,uDAGbX,EAAWJ,EAAQK,MACrB,CAGA,GAAU,MAANU,IAAeH,EAAKI,IAAID,GAAK,CAC/B,MAAMzB,EAAO,IAAIuB,IAAS,CAACE,IAC3B,IAAIE,EAASb,EAEb,KAAOa,GAAUA,IAAWjB,EAAQK,SAC9BO,EAAKI,IAAIC,IAD6B,CAI1C,GAAI3B,EAAK0B,IAAIC,GAAS,CACpB,MAAMC,EAAY,IAAI5B,EAAM2B,GAAQE,KAAK,QACzC,MAAM,IAAIjD,EACR,mCAAmCgD,IAEvC,CACA5B,EAAK8B,IAAIH,GAET,MAAMI,EAAab,EAAUS,GAC7B,IAAKI,EACH,MAEFJ,EAAS7B,EAAIiC,EAAYrB,EAAQI,SACnC,CACA,IAAK,MAAMkB,KAAKhC,EAAMsB,EAAKQ,IAAIE,EACjC,CAYA,OATKlB,GAAaM,OAAOC,OAAOH,EAAWJ,KACzCA,EAAWJ,EAAQK,QAGjBK,OAAOC,OAAOjC,EAAS0B,GACzB1B,EAAQ0B,GAAUmB,KAAKd,GAEvB/B,EAAQ0B,GAAY,CAACK,GAEhB/B,GACN,CAAA,IFrDa8C,CAAezB,EAAME,GAErC,OAAOxB,EACLC,EACAA,EAAQuB,EAAiBI,QACzBJ,EAAiBrB,SACjBqB,EAAiBE"}
package/dist/esm/index.js CHANGED
@@ -1,2 +1,2 @@
1
- class r extends Error{constructor(r){super(r),this.name="ArrayToTreeError"}}const t=(r,e,n,o)=>Array.isArray(e)?e.map(e=>{const d={...e},s=r[e[n]];return s&&(d[o]=t(r,s,n,o)),d}):[],e=(r,t)=>{const e=t.split(/[.[\]]/g);let n=r;for(const r of e){if(null===n)return null;if(void 0===n)return null;const t=r.replace(/['"]/g,"");""!==t.trim()&&(n=n[t])}return void 0===n?null:n},n=(n,o)=>{const d=(r=>({allowSelfAsParent:(r=r??{}).allowSelfAsParent??!1,childrenId:r.childrenId??"children",customId:r.customId??"id",parentId:r.parentId??"parentId",rootId:r.rootId??"__ARRAY_TO_TREE_VIRTUAL_ROOT_ID__"}))(o);if(!Array.isArray(n))throw new r("Expected an array but got an invalid argument.");const s=((t,n)=>{const o={};for(const e of t){const t=e[n.customId];if(null!=t&&Object.hasOwn(o,t))throw new r(`Duplicate node id "${t}" detected.`);o[t]=e}return t.reduce((t,d)=>{const s=e(d,n.customId);let c=e(d,n.parentId);if(c===s){if(!n.allowSelfAsParent)throw new r(`Node "${s}" cannot be its own parent (self reference found).`);c=n.rootId}return c&&Object.hasOwn(o,c)||(c=n.rootId),c&&Object.hasOwn(t,c)?(t[c].push(d),t):(t[c]=[d],t)},{})})(n,d);return t(s,s[d.rootId],d.customId,d.childrenId)};export{r as ArrayToTreeError,n as arrayToTree};
1
+ class t extends Error{constructor(t){super(t),this.name="ArrayToTreeError"}}const r=(t,e,n,o)=>Array.isArray(e)?e.map(e=>{const d={...e},s=t[e[n]];return s&&(d[o]=r(t,s,n,o)),d}):[],e=(t,r)=>{const e=r.split(/[.[\]]/g);let n=t;for(const t of e){if(null===n)return null;if(void 0===n)return null;const r=t.replace(/['"]/g,"");""!==r.trim()&&(n=n[r])}return void 0===n?null:n},n=(n,o)=>{const d=(t=>({allowSelfAsParent:(t=t??{}).allowSelfAsParent??!1,childrenId:t.childrenId??"children",customId:t.customId??"id",parentId:t.parentId??"parentId",rootId:t.rootId??"__ARRAY_TO_TREE_VIRTUAL_ROOT_ID__"}))(o);if(!Array.isArray(n))throw new t("Expected an array but got an invalid argument.");const s=((r,n)=>{const o={};for(const e of r){const r=e[n.customId];if(null!=r&&Object.hasOwn(o,r))throw new t(`Duplicate node id "${r}" detected.`);o[r]=e}const d=new Set;return r.reduce((r,s)=>{const c=e(s,n.customId);let a=e(s,n.parentId);if(a===c){if(!n.allowSelfAsParent)throw new t(`Node "${c}" cannot be its own parent (self reference found).`);a=n.rootId}if(null!=c&&!d.has(c)){const r=new Set([c]);let s=a;for(;s&&s!==n.rootId&&!d.has(s);){if(r.has(s)){const e=[...r,s].join(" -> ");throw new t(`Cycle detected in parent chain: ${e}`)}r.add(s);const d=o[s];if(!d)break;s=e(d,n.parentId)}for(const t of r)d.add(t)}return a&&Object.hasOwn(o,a)||(a=n.rootId),Object.hasOwn(r,a)?r[a].push(s):r[a]=[s],r},{})})(n,d);return r(s,s[d.rootId],d.customId,d.childrenId)};export{t as ArrayToTreeError,n as arrayToTree};
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../src/errors/array-to-tree-error.ts","../../../../src/internal/create-tree.ts","../../../../src/internal/get.ts","../../../../src/utils/array-to-tree.ts","../../../../src/internal/resolve-options.ts","../../../../src/internal/group-by-parents.ts"],"sourcesContent":["export class ArrayToTreeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ArrayToTreeError\";\n }\n}\n","export const createTree = (\n grouped: Record<string, any[]>,\n rootNodes: any[],\n customId: string,\n childrenProperty: string,\n) => {\n if (!Array.isArray(rootNodes)) {\n return [];\n }\n\n return rootNodes.map((node) => {\n const newNode = { ...node };\n const children = grouped[node[customId]];\n if (children) {\n newNode[childrenProperty] = createTree(\n grouped,\n children,\n customId,\n childrenProperty,\n );\n }\n return newNode;\n });\n};\n","/**\n * This code is modified from the radash project.\n * @see https://github.com/sodiray/radash\n */\nexport const get = (value: any, path: string) => {\n const segments = path.split(/[.[\\]]/g);\n let current: any = value;\n for (const key of segments) {\n if (current === null) return null;\n if (current === undefined) return null;\n const dequoted = key.replace(/['\"]/g, \"\");\n if (dequoted.trim() === \"\") continue;\n current = current[dequoted];\n }\n if (current === undefined) return null;\n return current;\n};\n","import { ArrayToTreeError } from \"../errors\";\nimport { createTree, groupByParents, resolveOptions } from \"../internal\";\nimport type {\n ArrayToTreeOptions,\n NormalizedArrayToTreeOptions,\n} from \"../types\";\n\nexport const arrayToTree = (\n data: Record<string, any>[],\n options?: ArrayToTreeOptions,\n) => {\n const normalizeOptions = resolveOptions(\n options,\n ) as NormalizedArrayToTreeOptions;\n\n if (!Array.isArray(data)) {\n throw new ArrayToTreeError(\n \"Expected an array but got an invalid argument.\",\n );\n }\n\n const grouped = groupByParents(data, normalizeOptions);\n\n return createTree(\n grouped,\n grouped[normalizeOptions.rootId],\n normalizeOptions.customId,\n normalizeOptions.childrenId,\n );\n};\n","import type { ArrayToTreeOptions } from \"../types\";\n\nexport const resolveOptions = (\n options?: ArrayToTreeOptions,\n): Required<ArrayToTreeOptions> => {\n options = options ?? {};\n return {\n allowSelfAsParent: options.allowSelfAsParent ?? false,\n childrenId: options.childrenId ?? \"children\",\n customId: options.customId ?? \"id\",\n parentId: options.parentId ?? \"parentId\",\n rootId: options.rootId ?? \"__ARRAY_TO_TREE_VIRTUAL_ROOT_ID__\",\n };\n};\n","import { ArrayToTreeError } from \"../errors\";\nimport type { NormalizedArrayToTreeOptions } from \"../types\";\nimport { get } from \"./get\";\n\nexport const groupByParents = (\n array: Record<string, any>[],\n options: NormalizedArrayToTreeOptions,\n) => {\n const arrayById: Record<string, any> = {};\n for (const item of array) {\n const key = item[options.customId];\n if (key != null && Object.hasOwn(arrayById, key)) {\n throw new ArrayToTreeError(`Duplicate node id \"${key}\" detected.`);\n }\n arrayById[key] = item;\n }\n\n return array.reduce((prev, item) => {\n const id = get(item, options.customId);\n let parentId = get(item, options.parentId);\n\n // Handle self-referencing nodes\n if (parentId === id) {\n if (!options.allowSelfAsParent) {\n throw new ArrayToTreeError(\n `Node \"${id}\" cannot be its own parent (self reference found).`,\n );\n }\n parentId = options.rootId;\n }\n\n if (!parentId || !Object.hasOwn(arrayById, parentId)) {\n parentId = options.rootId;\n }\n\n if (parentId && Object.hasOwn(prev, parentId)) {\n prev[parentId].push(item);\n return prev;\n }\n\n prev[parentId] = [item];\n return prev;\n }, {});\n};\n"],"names":["ArrayToTreeError","Error","constructor","message","super","this","name","createTree","grouped","rootNodes","customId","childrenProperty","Array","isArray","map","node","newNode","children","get","value","path","segments","split","current","key","undefined","dequoted","replace","trim","arrayToTree","data","options","normalizeOptions","allowSelfAsParent","childrenId","parentId","rootId","resolveOptions","array","arrayById","item","Object","hasOwn","reduce","prev","id","push","groupByParents"],"mappings":"AAAM,MAAOA,UAAyBC,MACpC,WAAAC,CAAYC,GACVC,MAAMD,GACNE,KAAKC,KAAO,kBACd,ECJK,MAAMC,EAAa,CACxBC,EACAC,EACAC,EACAC,IAEKC,MAAMC,QAAQJ,GAIZA,EAAUK,IAAKC,IACpB,MAAMC,EAAU,IAAKD,GACfE,EAAWT,EAAQO,EAAKL,IAS9B,OARIO,IACFD,EAAQL,GAAoBJ,EAC1BC,EACAS,EACAP,EACAC,IAGGK,IAdA,GCHEE,EAAM,CAACC,EAAYC,KAC9B,MAAMC,EAAWD,EAAKE,MAAM,WAC5B,IAAIC,EAAeJ,EACnB,IAAK,MAAMK,KAAOH,EAAU,CAC1B,GAAgB,OAAZE,EAAkB,OAAO,KAC7B,QAAgBE,IAAZF,EAAuB,OAAO,KAClC,MAAMG,EAAWF,EAAIG,QAAQ,QAAS,IACd,KAApBD,EAASE,SACbL,EAAUA,EAAQG,GACpB,CACA,YAAgBD,IAAZF,EAA8B,KAC3BA,GCRIM,EAAc,CACzBC,EACAC,KAEA,MAAMC,ECTsB,CAC5BD,IAGO,CACLE,mBAFFF,EAAUA,GAAW,CAAA,GAEQE,oBAAqB,EAChDC,WAAYH,EAAQG,YAAc,WAClCxB,SAAUqB,EAAQrB,UAAY,KAC9ByB,SAAUJ,EAAQI,UAAY,WAC9BC,OAAQL,EAAQK,QAAU,sCDAHC,CACvBN,GAGF,IAAKnB,MAAMC,QAAQiB,GACjB,MAAM,IAAI9B,EACR,kDAIJ,MAAMQ,EEjBsB,EAC5B8B,EACAP,KAEA,MAAMQ,EAAiC,CAAA,EACvC,IAAK,MAAMC,KAAQF,EAAO,CACxB,MAAMd,EAAMgB,EAAKT,EAAQrB,UACzB,GAAW,MAAPc,GAAeiB,OAAOC,OAAOH,EAAWf,GAC1C,MAAM,IAAIxB,EAAiB,sBAAsBwB,gBAEnDe,EAAUf,GAAOgB,CACnB,CAEA,OAAOF,EAAMK,OAAO,CAACC,EAAMJ,KACzB,MAAMK,EAAK3B,EAAIsB,EAAMT,EAAQrB,UAC7B,IAAIyB,EAAWjB,EAAIsB,EAAMT,EAAQI,UAGjC,GAAIA,IAAaU,EAAI,CACnB,IAAKd,EAAQE,kBACX,MAAM,IAAIjC,EACR,SAAS6C,uDAGbV,EAAWJ,EAAQK,MACrB,CAMA,OAJKD,GAAaM,OAAOC,OAAOH,EAAWJ,KACzCA,EAAWJ,EAAQK,QAGjBD,GAAYM,OAAOC,OAAOE,EAAMT,IAClCS,EAAKT,GAAUW,KAAKN,GACbI,IAGTA,EAAKT,GAAY,CAACK,GACXI,IACN,CAAA,IFrBaG,CAAejB,EAAME,GAErC,OAAOzB,EACLC,EACAA,EAAQwB,EAAiBI,QACzBJ,EAAiBtB,SACjBsB,EAAiBE"}
1
+ {"version":3,"file":"index.js","sources":["../../../../src/errors/array-to-tree-error.ts","../../../../src/internal/create-tree.ts","../../../../src/internal/get.ts","../../../../src/utils/array-to-tree.ts","../../../../src/internal/resolve-options.ts","../../../../src/internal/group-by-parents.ts"],"sourcesContent":["export class ArrayToTreeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ArrayToTreeError\";\n }\n}\n","export const createTree = (\n grouped: Record<string, any[]>,\n rootNodes: any[],\n customId: string,\n childrenProperty: string,\n) => {\n if (!Array.isArray(rootNodes)) {\n return [];\n }\n\n return rootNodes.map((node) => {\n const newNode = { ...node };\n const children = grouped[node[customId]];\n if (children) {\n newNode[childrenProperty] = createTree(\n grouped,\n children,\n customId,\n childrenProperty,\n );\n }\n return newNode;\n });\n};\n","/**\n * This code is modified from the radash project.\n * @see https://github.com/sodiray/radash\n */\nexport const get = (value: any, path: string) => {\n const segments = path.split(/[.[\\]]/g);\n let current: any = value;\n for (const key of segments) {\n if (current === null) return null;\n if (current === undefined) return null;\n const dequoted = key.replace(/['\"]/g, \"\");\n if (dequoted.trim() === \"\") continue;\n current = current[dequoted];\n }\n if (current === undefined) return null;\n return current;\n};\n","import { ArrayToTreeError } from \"../errors\";\nimport { createTree, groupByParents, resolveOptions } from \"../internal\";\nimport type {\n ArrayToTreeOptions,\n NormalizedArrayToTreeOptions,\n} from \"../types\";\n\nexport const arrayToTree = (\n data: Record<string, any>[],\n options?: ArrayToTreeOptions,\n) => {\n const normalizeOptions = resolveOptions(\n options,\n ) as NormalizedArrayToTreeOptions;\n\n if (!Array.isArray(data)) {\n throw new ArrayToTreeError(\n \"Expected an array but got an invalid argument.\",\n );\n }\n\n const grouped = groupByParents(data, normalizeOptions);\n\n return createTree(\n grouped,\n grouped[normalizeOptions.rootId],\n normalizeOptions.customId,\n normalizeOptions.childrenId,\n );\n};\n","import type { ArrayToTreeOptions } from \"../types\";\n\nexport const resolveOptions = (\n options?: ArrayToTreeOptions,\n): Required<ArrayToTreeOptions> => {\n options = options ?? {};\n return {\n allowSelfAsParent: options.allowSelfAsParent ?? false,\n childrenId: options.childrenId ?? \"children\",\n customId: options.customId ?? \"id\",\n parentId: options.parentId ?? \"parentId\",\n rootId: options.rootId ?? \"__ARRAY_TO_TREE_VIRTUAL_ROOT_ID__\",\n };\n};\n","import { ArrayToTreeError } from \"../errors\";\nimport type { NormalizedArrayToTreeOptions } from \"../types\";\nimport { get } from \"./get\";\n\nexport const groupByParents = (\n array: Record<string, any>[],\n options: NormalizedArrayToTreeOptions,\n) => {\n const arrayById: Record<string, any> = {};\n\n // 第一次遍历:构建索引并检测重复 id\n for (const item of array) {\n const key = item[options.customId];\n if (key != null && Object.hasOwn(arrayById, key)) {\n throw new ArrayToTreeError(`Duplicate node id \"${key}\" detected.`);\n }\n arrayById[key] = item;\n }\n\n // 用于缓存已确认“安全(无环)”的节点(其向上链路无环)\n const safe = new Set<any>();\n\n // ...existing code...\n return array.reduce<Record<string, any[]>>((grouped, item) => {\n const id = get(item, options.customId);\n let parentId = get(item, options.parentId);\n\n // 1. 先处理自指节点\n if (parentId === id) {\n if (!options.allowSelfAsParent) {\n throw new ArrayToTreeError(\n `Node \"${id}\" cannot be its own parent (self reference found).`,\n );\n }\n parentId = options.rootId;\n }\n\n // 2. 再进行环检测(仅在 id 存在时)\n if (id != null && !safe.has(id)) {\n const path = new Set<any>([id]);\n let cursor = parentId;\n\n while (cursor && cursor !== options.rootId) {\n if (safe.has(cursor)) {\n break;\n }\n if (path.has(cursor)) {\n const cyclePath = [...path, cursor].join(\" -> \");\n throw new ArrayToTreeError(\n `Cycle detected in parent chain: ${cyclePath}`,\n );\n }\n path.add(cursor);\n\n const parentNode = arrayById[cursor];\n if (!parentNode) {\n break;\n }\n cursor = get(parentNode, options.parentId);\n }\n for (const n of path) safe.add(n);\n }\n\n // 3. 父不存在或为空,归为 root\n if (!parentId || !Object.hasOwn(arrayById, parentId)) {\n parentId = options.rootId;\n }\n\n if (Object.hasOwn(grouped, parentId)) {\n grouped[parentId].push(item);\n } else {\n grouped[parentId] = [item];\n }\n return grouped;\n }, {});\n};\n"],"names":["ArrayToTreeError","Error","constructor","message","super","this","name","createTree","grouped","rootNodes","customId","childrenProperty","Array","isArray","map","node","newNode","children","get","value","path","segments","split","current","key","undefined","dequoted","replace","trim","arrayToTree","data","options","normalizeOptions","allowSelfAsParent","childrenId","parentId","rootId","resolveOptions","array","arrayById","item","Object","hasOwn","safe","Set","reduce","id","has","cursor","cyclePath","join","add","parentNode","n","push","groupByParents"],"mappings":"AAAM,MAAOA,UAAyBC,MACpC,WAAAC,CAAYC,GACVC,MAAMD,GACNE,KAAKC,KAAO,kBACd,ECJK,MAAMC,EAAa,CACxBC,EACAC,EACAC,EACAC,IAEKC,MAAMC,QAAQJ,GAIZA,EAAUK,IAAKC,IACpB,MAAMC,EAAU,IAAKD,GACfE,EAAWT,EAAQO,EAAKL,IAS9B,OARIO,IACFD,EAAQL,GAAoBJ,EAC1BC,EACAS,EACAP,EACAC,IAGGK,IAdA,GCHEE,EAAM,CAACC,EAAYC,KAC9B,MAAMC,EAAWD,EAAKE,MAAM,WAC5B,IAAIC,EAAeJ,EACnB,IAAK,MAAMK,KAAOH,EAAU,CAC1B,GAAgB,OAAZE,EAAkB,OAAO,KAC7B,QAAgBE,IAAZF,EAAuB,OAAO,KAClC,MAAMG,EAAWF,EAAIG,QAAQ,QAAS,IACd,KAApBD,EAASE,SACbL,EAAUA,EAAQG,GACpB,CACA,YAAgBD,IAAZF,EAA8B,KAC3BA,GCRIM,EAAc,CACzBC,EACAC,KAEA,MAAMC,ECTsB,CAC5BD,IAGO,CACLE,mBAFFF,EAAUA,GAAW,CAAA,GAEQE,oBAAqB,EAChDC,WAAYH,EAAQG,YAAc,WAClCxB,SAAUqB,EAAQrB,UAAY,KAC9ByB,SAAUJ,EAAQI,UAAY,WAC9BC,OAAQL,EAAQK,QAAU,sCDAHC,CACvBN,GAGF,IAAKnB,MAAMC,QAAQiB,GACjB,MAAM,IAAI9B,EACR,kDAIJ,MAAMQ,EEjBsB,EAC5B8B,EACAP,KAEA,MAAMQ,EAAiC,CAAA,EAGvC,IAAK,MAAMC,KAAQF,EAAO,CACxB,MAAMd,EAAMgB,EAAKT,EAAQrB,UACzB,GAAW,MAAPc,GAAeiB,OAAOC,OAAOH,EAAWf,GAC1C,MAAM,IAAIxB,EAAiB,sBAAsBwB,gBAEnDe,EAAUf,GAAOgB,CACnB,CAGA,MAAMG,EAAO,IAAIC,IAGjB,OAAON,EAAMO,OAA8B,CAACrC,EAASgC,KACnD,MAAMM,EAAK5B,EAAIsB,EAAMT,EAAQrB,UAC7B,IAAIyB,EAAWjB,EAAIsB,EAAMT,EAAQI,UAGjC,GAAIA,IAAaW,EAAI,CACnB,IAAKf,EAAQE,kBACX,MAAM,IAAIjC,EACR,SAAS8C,uDAGbX,EAAWJ,EAAQK,MACrB,CAGA,GAAU,MAANU,IAAeH,EAAKI,IAAID,GAAK,CAC/B,MAAM1B,EAAO,IAAIwB,IAAS,CAACE,IAC3B,IAAIE,EAASb,EAEb,KAAOa,GAAUA,IAAWjB,EAAQK,SAC9BO,EAAKI,IAAIC,IAD6B,CAI1C,GAAI5B,EAAK2B,IAAIC,GAAS,CACpB,MAAMC,EAAY,IAAI7B,EAAM4B,GAAQE,KAAK,QACzC,MAAM,IAAIlD,EACR,mCAAmCiD,IAEvC,CACA7B,EAAK+B,IAAIH,GAET,MAAMI,EAAab,EAAUS,GAC7B,IAAKI,EACH,MAEFJ,EAAS9B,EAAIkC,EAAYrB,EAAQI,SACnC,CACA,IAAK,MAAMkB,KAAKjC,EAAMuB,EAAKQ,IAAIE,EACjC,CAYA,OATKlB,GAAaM,OAAOC,OAAOH,EAAWJ,KACzCA,EAAWJ,EAAQK,QAGjBK,OAAOC,OAAOlC,EAAS2B,GACzB3B,EAAQ2B,GAAUmB,KAAKd,GAEvBhC,EAAQ2B,GAAY,CAACK,GAEhBhC,GACN,CAAA,IFrDa+C,CAAezB,EAAME,GAErC,OAAOzB,EACLC,EACAA,EAAQwB,EAAiBI,QACzBJ,EAAiBtB,SACjBsB,EAAiBE"}
@@ -2,6 +2,7 @@ declare class ArrayToTreeError extends Error {
2
2
  constructor(message: string);
3
3
  }
4
4
 
5
+ type DepulicateStrategy = "keep-first" | "keep-last";
5
6
  interface ArrayToTreeOptions {
6
7
  childrenId?: string;
7
8
  customId?: string;
@@ -14,4 +15,4 @@ type NormalizedArrayToTreeOptions = Required<ArrayToTreeOptions>;
14
15
  declare const arrayToTree: (data: Record<string, any>[], options?: ArrayToTreeOptions) => any[];
15
16
 
16
17
  export { ArrayToTreeError, arrayToTree };
17
- export type { ArrayToTreeOptions, NormalizedArrayToTreeOptions };
18
+ export type { ArrayToTreeOptions, DepulicateStrategy, NormalizedArrayToTreeOptions };
package/package.json CHANGED
@@ -61,5 +61,5 @@
61
61
  "type-check": "tsc --noEmit"
62
62
  },
63
63
  "type": "module",
64
- "version": "0.0.1-alpha.1"
64
+ "version": "0.0.1"
65
65
  }