@okutils/array-to-tree 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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}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)};
1
+ "use strict";class r extends Error{constructor(r){super(r),this.name="ArrayToTreeError"}}const t=(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},e=(r,n,o,s)=>Array.isArray(n)?n.map(n=>{const a={...n},d=t(n,o),c=null!=d?r[d]:void 0;return c&&(a[s]=e(r,c,o,s)),a}):[];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=((e,n)=>{const o={};for(const s of e){const e=t(s,n.customId);if(null!=e&&Object.hasOwn(o,e))throw new r(`Duplicate node id "${e}" detected.`);o[e]=s}const s=new Set;return e.reduce((e,a)=>{const d=t(a,n.customId);let c=t(a,n.parentId);if(c===d){if(!n.allowSelfAsParent)throw new r(`Node "${d}" cannot be its own parent (self reference found).`);c=n.rootId}if(null!=d&&!s.has(d)){const e=new Set([d]);let a=c;for(;a&&a!==n.rootId&&!s.has(a);){if(e.has(a)){const t=[...e,a].join(" -> ");throw new r(`Cycle detected in parent chain: ${t}`)}e.add(a);const s=o[a];if(!s)break;a=t(s,n.parentId)}for(const r of e)s.add(r)}return c&&Object.hasOwn(o,c)||(c=n.rootId),Object.hasOwn(e,c)?e[c].push(a):e[c]=[a],e},{})})(n,s);return e(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\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"}
1
+ {"version":3,"file":"index.js","sources":["../../../../src/errors/array-to-tree-error.ts","../../../../src/internal/get.ts","../../../../src/internal/create-tree.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","/**\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 { get } from \"./get\";\n\nexport 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 nodeId = get(node, customId);\n const children = nodeId != null ? grouped[nodeId] : undefined;\n if (children) {\n newNode[childrenProperty] = createTree(\n grouped,\n children,\n customId,\n childrenProperty,\n );\n }\n return newNode;\n });\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 = get(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 const safe = new Set<any>();\n\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 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 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","get","value","path","segments","split","current","key","undefined","dequoted","replace","trim","createTree","grouped","rootNodes","customId","childrenProperty","Array","isArray","map","node","newNode","nodeId","children","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,ECAK,MAAMC,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,GCbIM,EAAa,CACxBC,EACAC,EACAC,EACAC,IAEKC,MAAMC,QAAQJ,GAIZA,EAAUK,IAAKC,IACpB,MAAMC,EAAU,IAAKD,GACfE,EAASrB,EAAImB,EAAML,GACnBQ,EAAqB,MAAVD,EAAiBT,EAAQS,QAAUd,EASpD,OARIe,IACFF,EAAQL,GAAoBJ,EAC1BC,EACAU,EACAR,EACAC,IAGGK,IAfA,kDCFgB,CACzBG,EACAC,KAEA,MAAMC,ECTsB,CAC5BD,IAGO,CACLE,mBAFFF,EAAUA,GAAW,CAAA,GAEQE,oBAAqB,EAChDC,WAAYH,EAAQG,YAAc,WAClCb,SAAUU,EAAQV,UAAY,KAC9Bc,SAAUJ,EAAQI,UAAY,WAC9BC,OAAQL,EAAQK,QAAU,sCDAHC,CACvBN,GAGF,IAAKR,MAAMC,QAAQM,GACjB,MAAM,IAAI9B,EACR,kDAIJ,MAAMmB,EEjBsB,EAC5BmB,EACAP,KAEA,MAAMQ,EAAiC,CAAA,EAGvC,IAAK,MAAMC,KAAQF,EAAO,CACxB,MAAMzB,EAAMN,EAAIiC,EAAMT,EAAQV,UAC9B,GAAW,MAAPR,GAAe4B,OAAOC,OAAOH,EAAW1B,GAC1C,MAAM,IAAIb,EAAiB,sBAAsBa,gBAEnD0B,EAAU1B,GAAO2B,CACnB,CAEA,MAAMG,EAAO,IAAIC,IAEjB,OAAON,EAAMO,OAA8B,CAAC1B,EAASqB,KACnD,MAAMM,EAAKvC,EAAIiC,EAAMT,EAAQV,UAC7B,IAAIc,EAAW5B,EAAIiC,EAAMT,EAAQI,UAGjC,GAAIA,IAAaW,EAAI,CACnB,IAAKf,EAAQE,kBACX,MAAM,IAAIjC,EACR,SAAS8C,uDAGbX,EAAWJ,EAAQK,MACrB,CAEA,GAAU,MAANU,IAAeH,EAAKI,IAAID,GAAK,CAC/B,MAAMrC,EAAO,IAAImC,IAAS,CAACE,IAC3B,IAAIE,EAASb,EAEb,KAAOa,GAAUA,IAAWjB,EAAQK,SAC9BO,EAAKI,IAAIC,IAD6B,CAI1C,GAAIvC,EAAKsC,IAAIC,GAAS,CACpB,MAAMC,EAAY,IAAIxC,EAAMuC,GAAQE,KAAK,QACzC,MAAM,IAAIlD,EACR,mCAAmCiD,IAEvC,CACAxC,EAAK0C,IAAIH,GAET,MAAMI,EAAab,EAAUS,GAC7B,IAAKI,EACH,MAEFJ,EAASzC,EAAI6C,EAAYrB,EAAQI,SACnC,CACA,IAAK,MAAMkB,KAAK5C,EAAMkC,EAAKQ,IAAIE,EACjC,CAWA,OATKlB,GAAaM,OAAOC,OAAOH,EAAWJ,KACzCA,EAAWJ,EAAQK,QAGjBK,OAAOC,OAAOvB,EAASgB,GACzBhB,EAAQgB,GAAUmB,KAAKd,GAEvBrB,EAAQgB,GAAY,CAACK,GAEhBrB,GACN,CAAA,IFjDaoC,CAAezB,EAAME,GAErC,OAAOd,EACLC,EACAA,EAAQa,EAAiBI,QACzBJ,EAAiBX,SACjBW,EAAiBE"}
package/dist/esm/index.js CHANGED
@@ -1,2 +1,2 @@
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};
1
+ class t extends Error{constructor(t){super(t),this.name="ArrayToTreeError"}}const r=(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},e=(t,n,o,d)=>Array.isArray(n)?n.map(n=>{const s={...n},c=r(n,o),a=null!=c?t[c]:void 0;return a&&(s[d]=e(t,a,o,d)),s}):[],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=((e,n)=>{const o={};for(const d of e){const e=r(d,n.customId);if(null!=e&&Object.hasOwn(o,e))throw new t(`Duplicate node id "${e}" detected.`);o[e]=d}const d=new Set;return e.reduce((e,s)=>{const c=r(s,n.customId);let a=r(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 e=new Set([c]);let s=a;for(;s&&s!==n.rootId&&!d.has(s);){if(e.has(s)){const r=[...e,s].join(" -> ");throw new t(`Cycle detected in parent chain: ${r}`)}e.add(s);const d=o[s];if(!d)break;s=r(d,n.parentId)}for(const t of e)d.add(t)}return a&&Object.hasOwn(o,a)||(a=n.rootId),Object.hasOwn(e,a)?e[a].push(s):e[a]=[s],e},{})})(n,d);return e(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\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"}
1
+ {"version":3,"file":"index.js","sources":["../../../../src/errors/array-to-tree-error.ts","../../../../src/internal/get.ts","../../../../src/internal/create-tree.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","/**\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 { get } from \"./get\";\n\nexport 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 nodeId = get(node, customId);\n const children = nodeId != null ? grouped[nodeId] : undefined;\n if (children) {\n newNode[childrenProperty] = createTree(\n grouped,\n children,\n customId,\n childrenProperty,\n );\n }\n return newNode;\n });\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 = get(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 const safe = new Set<any>();\n\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 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 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","get","value","path","segments","split","current","key","undefined","dequoted","replace","trim","createTree","grouped","rootNodes","customId","childrenProperty","Array","isArray","map","node","newNode","nodeId","children","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,ECAK,MAAMC,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,GCbIM,EAAa,CACxBC,EACAC,EACAC,EACAC,IAEKC,MAAMC,QAAQJ,GAIZA,EAAUK,IAAKC,IACpB,MAAMC,EAAU,IAAKD,GACfE,EAASrB,EAAImB,EAAML,GACnBQ,EAAqB,MAAVD,EAAiBT,EAAQS,QAAUd,EASpD,OARIe,IACFF,EAAQL,GAAoBJ,EAC1BC,EACAU,EACAR,EACAC,IAGGK,IAfA,GCFEG,EAAc,CACzBC,EACAC,KAEA,MAAMC,ECTsB,CAC5BD,IAGO,CACLE,mBAFFF,EAAUA,GAAW,CAAA,GAEQE,oBAAqB,EAChDC,WAAYH,EAAQG,YAAc,WAClCd,SAAUW,EAAQX,UAAY,KAC9Be,SAAUJ,EAAQI,UAAY,WAC9BC,OAAQL,EAAQK,QAAU,sCDAHC,CACvBN,GAGF,IAAKT,MAAMC,QAAQO,GACjB,MAAM,IAAI/B,EACR,kDAIJ,MAAMmB,EEjBsB,EAC5BoB,EACAP,KAEA,MAAMQ,EAAiC,CAAA,EAGvC,IAAK,MAAMC,KAAQF,EAAO,CACxB,MAAM1B,EAAMN,EAAIkC,EAAMT,EAAQX,UAC9B,GAAW,MAAPR,GAAe6B,OAAOC,OAAOH,EAAW3B,GAC1C,MAAM,IAAIb,EAAiB,sBAAsBa,gBAEnD2B,EAAU3B,GAAO4B,CACnB,CAEA,MAAMG,EAAO,IAAIC,IAEjB,OAAON,EAAMO,OAA8B,CAAC3B,EAASsB,KACnD,MAAMM,EAAKxC,EAAIkC,EAAMT,EAAQX,UAC7B,IAAIe,EAAW7B,EAAIkC,EAAMT,EAAQI,UAGjC,GAAIA,IAAaW,EAAI,CACnB,IAAKf,EAAQE,kBACX,MAAM,IAAIlC,EACR,SAAS+C,uDAGbX,EAAWJ,EAAQK,MACrB,CAEA,GAAU,MAANU,IAAeH,EAAKI,IAAID,GAAK,CAC/B,MAAMtC,EAAO,IAAIoC,IAAS,CAACE,IAC3B,IAAIE,EAASb,EAEb,KAAOa,GAAUA,IAAWjB,EAAQK,SAC9BO,EAAKI,IAAIC,IAD6B,CAI1C,GAAIxC,EAAKuC,IAAIC,GAAS,CACpB,MAAMC,EAAY,IAAIzC,EAAMwC,GAAQE,KAAK,QACzC,MAAM,IAAInD,EACR,mCAAmCkD,IAEvC,CACAzC,EAAK2C,IAAIH,GAET,MAAMI,EAAab,EAAUS,GAC7B,IAAKI,EACH,MAEFJ,EAAS1C,EAAI8C,EAAYrB,EAAQI,SACnC,CACA,IAAK,MAAMkB,KAAK7C,EAAMmC,EAAKQ,IAAIE,EACjC,CAWA,OATKlB,GAAaM,OAAOC,OAAOH,EAAWJ,KACzCA,EAAWJ,EAAQK,QAGjBK,OAAOC,OAAOxB,EAASiB,GACzBjB,EAAQiB,GAAUmB,KAAKd,GAEvBtB,EAAQiB,GAAY,CAACK,GAEhBtB,GACN,CAAA,IFjDaqC,CAAezB,EAAME,GAErC,OAAOf,EACLC,EACAA,EAAQc,EAAiBI,QACzBJ,EAAiBZ,SACjBY,EAAiBE"}
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"
64
+ "version": "0.0.2"
65
65
  }