@jsonup/operation 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Cocova
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # @jsonup/operation
2
+
3
+ 供 `jsonup` 生态系统使用的不可变 JSON 树操作库。
4
+
5
+ ## 特性
6
+
7
+ - **不可变更新**:安全地操作 JSON 文档,而无需修改原始对象。
8
+ - **保持身份 (Identity Preservation)**:当节点被更新、插入、移动或删除时,内部的 `IdentityTree` 会被保留,确保 UI 状态不会发生跳动或重置。
9
+ - **全面的 API**:支持更新值、插入元素、删除节点、跨父级移动节点以及重命名键。
10
+
11
+ ## 安装
12
+
13
+ ```bash
14
+ npm install @jsonup/operation
15
+ # 或
16
+ pnpm add @jsonup/operation
17
+ # 或
18
+ yarn add @jsonup/operation
19
+ ```
20
+
21
+ ## 基础用法
22
+
23
+ ```ts
24
+ import { createDocument } from "@jsonup/core";
25
+ import { insert, remove, updateValue } from "@jsonup/operation";
26
+
27
+ const doc = createDocument({ a: 1, b: 2 });
28
+
29
+ // 更新值
30
+ const nextDoc = updateValue(doc, "node-id-of-a", 42);
31
+
32
+ // 插入新节点
33
+ const docWithInsert = insert(doc, "node-id-of-root", { c: 3 });
34
+
35
+ // 删除节点
36
+ const docWithoutB = remove(doc, "node-id-of-b");
37
+ ```
38
+
39
+ ## 许可证
40
+
41
+ [MIT](../../LICENSE)
@@ -0,0 +1,95 @@
1
+ import { JsonDocument, JsonNodeId } from "@jsonup/core";
2
+
3
+ //#region src/index.d.ts
4
+ /**
5
+ * 对象节点插入输入的参数接口。
6
+ * 用于向 JSON 对象节点中插入新属性。
7
+ */
8
+ interface ObjectInsertInput {
9
+ /**
10
+ * 插入属性的键名。
11
+ */
12
+ key: string;
13
+ /**
14
+ * 插入属性的值,可以是任何合法的 JSON 对应值,将会被转化为标准的 JsonValue。
15
+ */
16
+ value: unknown;
17
+ }
18
+ /**
19
+ * 数组节点插入输入的参数接口。
20
+ * 用于向 JSON 数组节点中插入新元素。
21
+ */
22
+ interface ArrayInsertInput {
23
+ /**
24
+ * 插入元素的目标索引。如果未提供,默认插入到数组末尾。
25
+ */
26
+ index?: number;
27
+ /**
28
+ * 插入元素的值,可以是任何合法的 JSON 对应值,将会被转化为标准的 JsonValue。
29
+ */
30
+ value: unknown;
31
+ }
32
+ /**
33
+ * 插入操作的输入类型,可以是对象插入输入或数组插入输入。
34
+ */
35
+ type InsertInput = ObjectInsertInput | ArrayInsertInput;
36
+ /**
37
+ * 移动节点时的选项接口。
38
+ */
39
+ interface MoveOptions {
40
+ /**
41
+ * 移动到对象节点时使用的新键名。如果未提供,将尝试使用原节点的键名。
42
+ */
43
+ key?: string;
44
+ /**
45
+ * 移动到数组节点时使用的目标索引。如果未提供,默认移动到数组末尾。
46
+ */
47
+ index?: number;
48
+ }
49
+ /**
50
+ * 更新 JSON 文档中指定节点的值。
51
+ * @param document 当前的 JSON 文档对象。
52
+ * @param id 需要更新的节点的唯一标识符 (JsonNodeId)。
53
+ * @param value 新的值,将会被转化为标准的 JsonValue。
54
+ * @returns 返回一个新的包含更新后内容的 JSON 文档。
55
+ * @throws 当指定的节点不存在,或者更新根节点时值不是对象或数组时抛出错误。
56
+ */
57
+ declare function updateValue(document: JsonDocument, id: JsonNodeId, value: unknown): JsonDocument;
58
+ /**
59
+ * 在 JSON 文档的指定父节点中插入新的子节点。
60
+ * @param document 当前的 JSON 文档对象。
61
+ * @param parentId 目标父节点的唯一标识符 (JsonNodeId)。
62
+ * @param input 插入的参数,包含键名/索引和值。
63
+ * @returns 返回一个新的包含插入节点后的 JSON 文档。
64
+ * @throws 当目标父节点不是对象或数组,或者在对象中键已存在,或输入类型不匹配时抛出错误。
65
+ */
66
+ declare function insert(document: JsonDocument, parentId: JsonNodeId, input: InsertInput): JsonDocument;
67
+ /**
68
+ * 从 JSON 文档中移除指定的节点。
69
+ * @param document 当前的 JSON 文档对象。
70
+ * @param id 需要移除的节点的唯一标识符 (JsonNodeId)。
71
+ * @returns 返回一个新的移除了指定节点后的 JSON 文档。
72
+ * @throws 当指定的节点不存在,或者尝试移除根节点时抛出错误。
73
+ */
74
+ declare function remove(document: JsonDocument, id: JsonNodeId): JsonDocument;
75
+ /**
76
+ * 重命名 JSON 文档中指定对象子节点的键名。
77
+ * @param document 当前的 JSON 文档对象。
78
+ * @param id 需要重命名键的节点的唯一标识符 (JsonNodeId)。
79
+ * @param nextKey 新的键名。
80
+ * @returns 返回一个新的重命名键后的 JSON 文档。
81
+ * @throws 当目标节点不是对象子节点,或者新键名已存在,或键名为空时抛出错误。
82
+ */
83
+ declare function renameKey(document: JsonDocument, id: JsonNodeId, nextKey: string): JsonDocument;
84
+ /**
85
+ * 将 JSON 文档中的指定节点移动到另一个父节点下。
86
+ * @param document 当前的 JSON 文档对象。
87
+ * @param id 需要移动的节点的唯一标识符 (JsonNodeId)。
88
+ * @param parentId 目标父节点的唯一标识符 (JsonNodeId)。
89
+ * @param options 移动时的额外选项,如移动到对象中的新键名,或移动到数组中的目标索引。
90
+ * @returns 返回一个新的包含移动节点后的 JSON 文档。
91
+ * @throws 当目标父节点不是对象或数组,尝试移动根节点,或者尝试将节点移动到其自身的子代中时抛出错误。
92
+ */
93
+ declare function move(document: JsonDocument, id: JsonNodeId, parentId: JsonNodeId, options?: MoveOptions): JsonDocument;
94
+ //#endregion
95
+ export { ArrayInsertInput, InsertInput, MoveOptions, ObjectInsertInput, insert, move, remove, renameKey, updateValue };
package/dist/index.mjs ADDED
@@ -0,0 +1,309 @@
1
+ import { createDocument, createJsonNodeId, getDocumentIdentityTree, getNode, isContainerValue, isJsonObject, toJsonValue } from "@jsonup/core";
2
+ //#region src/index.ts
3
+ /**
4
+ * 更新 JSON 文档中指定节点的值。
5
+ * @param document 当前的 JSON 文档对象。
6
+ * @param id 需要更新的节点的唯一标识符 (JsonNodeId)。
7
+ * @param value 新的值,将会被转化为标准的 JsonValue。
8
+ * @returns 返回一个新的包含更新后内容的 JSON 文档。
9
+ * @throws 当指定的节点不存在,或者更新根节点时值不是对象或数组时抛出错误。
10
+ */
11
+ function updateValue(document, id, value) {
12
+ getRequiredNode(document, id);
13
+ const nextValue = toJsonValue(value);
14
+ let nextRaw = cloneJsonValue(document.raw);
15
+ let nextIdentityTree = cloneIdentityTree(getDocumentIdentityTree(document));
16
+ const location = findRequiredLocation(nextRaw, nextIdentityTree, id);
17
+ if (location.parentValue === null || location.parentIdentity === null) {
18
+ if (!isContainerValue(nextValue)) throw new TypeError("JSON root must be an object or array.");
19
+ nextRaw = nextValue;
20
+ nextIdentityTree = createIdentityTree(nextValue, location.identity.id);
21
+ } else {
22
+ assignChild(location.parentValue, location.key, nextValue);
23
+ replaceIdentityChild(location.parentIdentity, location.key, createIdentityTree(nextValue, location.identity.id));
24
+ }
25
+ return rebuildDocument(document, nextRaw, nextIdentityTree);
26
+ }
27
+ /**
28
+ * 在 JSON 文档的指定父节点中插入新的子节点。
29
+ * @param document 当前的 JSON 文档对象。
30
+ * @param parentId 目标父节点的唯一标识符 (JsonNodeId)。
31
+ * @param input 插入的参数,包含键名/索引和值。
32
+ * @returns 返回一个新的包含插入节点后的 JSON 文档。
33
+ * @throws 当目标父节点不是对象或数组,或者在对象中键已存在,或输入类型不匹配时抛出错误。
34
+ */
35
+ function insert(document, parentId, input) {
36
+ const parent = getRequiredNode(document, parentId);
37
+ const nextRaw = cloneJsonValue(document.raw);
38
+ const nextIdentityTree = cloneIdentityTree(getDocumentIdentityTree(document));
39
+ const parentLocation = findRequiredLocation(nextRaw, nextIdentityTree, parentId);
40
+ const parentValue = parentLocation.value;
41
+ if (parent.type === "object") {
42
+ if (!isObjectInsertInput(input)) throw new TypeError("Object insert requires a key.");
43
+ if (!isJsonObject(parentValue)) throw new TypeError("Target parent is not an object.");
44
+ if (Object.hasOwn(parentValue, input.key)) throw new Error(`Key "${input.key}" already exists.`);
45
+ parentValue[input.key] = toJsonValue(input.value);
46
+ ensureObjectIdentityChildren(parentLocation.identity)[input.key] = createIdentityTree(parentValue[input.key]);
47
+ return rebuildDocument(document, nextRaw, nextIdentityTree);
48
+ }
49
+ if (parent.type === "array") {
50
+ if (isObjectInsertInput(input)) throw new TypeError("Array insert does not accept an object key.");
51
+ if (!Array.isArray(parentValue)) throw new TypeError("Target parent is not an array.");
52
+ const index = normalizeArrayIndex(input.index ?? parentValue.length, true);
53
+ const childValue = toJsonValue(input.value);
54
+ parentValue.splice(index, 0, childValue);
55
+ ensureArrayIdentityChildren(parentLocation.identity).splice(index, 0, createIdentityTree(childValue));
56
+ return rebuildDocument(document, nextRaw, nextIdentityTree);
57
+ }
58
+ throw new TypeError("Insert target must be an object or array node.");
59
+ }
60
+ /**
61
+ * 从 JSON 文档中移除指定的节点。
62
+ * @param document 当前的 JSON 文档对象。
63
+ * @param id 需要移除的节点的唯一标识符 (JsonNodeId)。
64
+ * @returns 返回一个新的移除了指定节点后的 JSON 文档。
65
+ * @throws 当指定的节点不存在,或者尝试移除根节点时抛出错误。
66
+ */
67
+ function remove(document, id) {
68
+ if (getRequiredNode(document, id).parent === null) throw new Error("Cannot remove the root node.");
69
+ const nextRaw = cloneJsonValue(document.raw);
70
+ const nextIdentityTree = cloneIdentityTree(getDocumentIdentityTree(document));
71
+ const location = findRequiredLocation(nextRaw, nextIdentityTree, id);
72
+ if (location.parentValue === null || location.parentIdentity === null) throw new Error("Cannot remove the root node.");
73
+ removeChild(location.parentValue, location.parentIdentity, location.key);
74
+ return rebuildDocument(document, nextRaw, nextIdentityTree);
75
+ }
76
+ /**
77
+ * 重命名 JSON 文档中指定对象子节点的键名。
78
+ * @param document 当前的 JSON 文档对象。
79
+ * @param id 需要重命名键的节点的唯一标识符 (JsonNodeId)。
80
+ * @param nextKey 新的键名。
81
+ * @returns 返回一个新的重命名键后的 JSON 文档。
82
+ * @throws 当目标节点不是对象子节点,或者新键名已存在,或键名为空时抛出错误。
83
+ */
84
+ function renameKey(document, id, nextKey) {
85
+ const node = getRequiredNode(document, id);
86
+ if (getRequiredParent(document, id).type !== "object") throw new TypeError("Only object children can be renamed.");
87
+ if (!node.key) throw new Error("Target node does not have a key.");
88
+ if (!nextKey) throw new Error("Key must not be empty.");
89
+ const nextRaw = cloneJsonValue(document.raw);
90
+ const nextIdentityTree = cloneIdentityTree(getDocumentIdentityTree(document));
91
+ const location = findRequiredLocation(nextRaw, nextIdentityTree, id);
92
+ if (location.parentValue === null || location.parentIdentity === null) throw new Error("Cannot rename the root node.");
93
+ if (typeof location.key !== "string") throw new TypeError("Array items cannot be renamed.");
94
+ renameObjectChild(location.parentValue, location.parentIdentity, location.key, nextKey);
95
+ return rebuildDocument(document, nextRaw, nextIdentityTree);
96
+ }
97
+ /**
98
+ * 将 JSON 文档中的指定节点移动到另一个父节点下。
99
+ * @param document 当前的 JSON 文档对象。
100
+ * @param id 需要移动的节点的唯一标识符 (JsonNodeId)。
101
+ * @param parentId 目标父节点的唯一标识符 (JsonNodeId)。
102
+ * @param options 移动时的额外选项,如移动到对象中的新键名,或移动到数组中的目标索引。
103
+ * @returns 返回一个新的包含移动节点后的 JSON 文档。
104
+ * @throws 当目标父节点不是对象或数组,尝试移动根节点,或者尝试将节点移动到其自身的子代中时抛出错误。
105
+ */
106
+ function move(document, id, parentId, options = {}) {
107
+ const node = getRequiredNode(document, id);
108
+ const targetParent = getRequiredNode(document, parentId);
109
+ if (node.parent === null) throw new Error("Cannot move the root node.");
110
+ if (targetParent.type !== "object" && targetParent.type !== "array") throw new TypeError("Move target must be an object or array node.");
111
+ if (isSubPath(targetParent.path, node.path)) throw new Error("Cannot move a node into its own descendant.");
112
+ const nextRaw = cloneJsonValue(document.raw);
113
+ const nextIdentityTree = cloneIdentityTree(getDocumentIdentityTree(document));
114
+ const sourceLocation = findRequiredLocation(nextRaw, nextIdentityTree, id);
115
+ if (sourceLocation.parentValue === null || sourceLocation.parentIdentity === null) throw new Error("Cannot move the root node.");
116
+ const movedValue = cloneJsonValue(sourceLocation.value);
117
+ const movedIdentity = cloneIdentityTree(sourceLocation.identity);
118
+ removeChild(sourceLocation.parentValue, sourceLocation.parentIdentity, sourceLocation.key);
119
+ const targetLocation = findRequiredLocation(nextRaw, nextIdentityTree, parentId);
120
+ const targetValue = targetLocation.value;
121
+ if (targetParent.type === "object") {
122
+ if (!isJsonObject(targetValue)) throw new TypeError("Move target is not an object.");
123
+ const nextKey = options.key ?? (isArrayItemKey(node.key) ? void 0 : node.key);
124
+ if (!nextKey) throw new Error("Moving into an object requires a stable key.");
125
+ if (Object.hasOwn(targetValue, nextKey)) throw new Error(`Key "${nextKey}" already exists.`);
126
+ targetValue[nextKey] = movedValue;
127
+ ensureObjectIdentityChildren(targetLocation.identity)[nextKey] = movedIdentity;
128
+ return rebuildDocument(document, nextRaw, nextIdentityTree);
129
+ }
130
+ if (!Array.isArray(targetValue)) throw new TypeError("Move target is not an array.");
131
+ const index = normalizeArrayIndex(options.index ?? targetValue.length, true);
132
+ targetValue.splice(index, 0, movedValue);
133
+ ensureArrayIdentityChildren(targetLocation.identity).splice(index, 0, movedIdentity);
134
+ return rebuildDocument(document, nextRaw, nextIdentityTree);
135
+ }
136
+ function rebuildDocument(previous, nextRaw, nextIdentityTree) {
137
+ return createDocument(nextRaw, {
138
+ defaultExpandedRoot: false,
139
+ defaultExpandedAll: false,
140
+ expandedPaths: previous.state.expandedPaths,
141
+ identityTree: nextIdentityTree,
142
+ cloneRaw: false
143
+ });
144
+ }
145
+ function getRequiredNode(document, id) {
146
+ const node = getNode(document, id);
147
+ if (!node) throw new Error(`Node ${id} does not exist.`);
148
+ return node;
149
+ }
150
+ function getRequiredParent(document, id) {
151
+ const node = getRequiredNode(document, id);
152
+ if (node.parent === null) throw new Error("Root node does not have a parent.");
153
+ return getRequiredNode(document, node.parent);
154
+ }
155
+ function findRequiredLocation(raw, identity, targetId) {
156
+ const location = findLocationById(raw, identity, targetId);
157
+ if (!location) throw new Error(`Node ${targetId} does not exist.`);
158
+ return location;
159
+ }
160
+ function findLocationById(value, identity, targetId, parentValue = null, parentIdentity = null, key = null) {
161
+ if (identity.id === targetId) return {
162
+ value,
163
+ identity,
164
+ parentValue,
165
+ parentIdentity,
166
+ key
167
+ };
168
+ if (Array.isArray(value)) {
169
+ const children = ensureArrayIdentityChildren(identity);
170
+ for (const [index, item] of value.entries()) {
171
+ const childIdentity = children[index];
172
+ const found = findLocationById(item, childIdentity, targetId, value, identity, index);
173
+ if (found) return found;
174
+ }
175
+ return;
176
+ }
177
+ if (isJsonObject(value)) {
178
+ const children = ensureObjectIdentityChildren(identity);
179
+ for (const [childKey, item] of Object.entries(value)) {
180
+ const childIdentity = children[childKey];
181
+ const found = findLocationById(item, childIdentity, targetId, value, identity, childKey);
182
+ if (found) return found;
183
+ }
184
+ }
185
+ }
186
+ function assignChild(parentValue, key, value) {
187
+ if (typeof key === "number") {
188
+ if (!Array.isArray(parentValue)) throw new TypeError("Target parent is not an array.");
189
+ parentValue[key] = value;
190
+ return;
191
+ }
192
+ if (typeof key === "string") {
193
+ if (!isJsonObject(parentValue)) throw new TypeError("Target parent is not an object.");
194
+ parentValue[key] = value;
195
+ return;
196
+ }
197
+ throw new Error("Child key is missing.");
198
+ }
199
+ function replaceIdentityChild(parentIdentity, key, childIdentity) {
200
+ if (typeof key === "number") {
201
+ ensureArrayIdentityChildren(parentIdentity)[key] = childIdentity;
202
+ return;
203
+ }
204
+ if (typeof key === "string") {
205
+ ensureObjectIdentityChildren(parentIdentity)[key] = childIdentity;
206
+ return;
207
+ }
208
+ throw new Error("Child key is missing.");
209
+ }
210
+ function removeChild(parentValue, parentIdentity, key) {
211
+ if (typeof key === "number") {
212
+ if (!Array.isArray(parentValue)) throw new TypeError("Target parent is not an array.");
213
+ parentValue.splice(key, 1);
214
+ ensureArrayIdentityChildren(parentIdentity).splice(key, 1);
215
+ return;
216
+ }
217
+ if (typeof key === "string") {
218
+ if (!isJsonObject(parentValue)) throw new TypeError("Target parent is not an object.");
219
+ delete parentValue[key];
220
+ delete ensureObjectIdentityChildren(parentIdentity)[key];
221
+ return;
222
+ }
223
+ throw new Error("Child key is missing.");
224
+ }
225
+ function renameObjectChild(parentValue, parentIdentity, currentKey, nextKey) {
226
+ if (!isJsonObject(parentValue)) throw new TypeError("Target parent is not an object.");
227
+ if (currentKey !== nextKey && Object.hasOwn(parentValue, nextKey)) throw new Error(`Key "${nextKey}" already exists.`);
228
+ const objectEntries = Object.entries(parentValue);
229
+ const identityEntries = Object.entries(ensureObjectIdentityChildren(parentIdentity));
230
+ const nextObject = {};
231
+ const nextIdentityChildren = {};
232
+ for (const [key, value] of objectEntries) {
233
+ const outputKey = key === currentKey ? nextKey : key;
234
+ nextObject[outputKey] = value;
235
+ }
236
+ for (const [key, value] of identityEntries) {
237
+ const outputKey = key === currentKey ? nextKey : key;
238
+ nextIdentityChildren[outputKey] = value;
239
+ }
240
+ for (const key of Object.keys(parentValue)) delete parentValue[key];
241
+ for (const [key, value] of Object.entries(nextObject)) parentValue[key] = value;
242
+ parentIdentity.children = nextIdentityChildren;
243
+ }
244
+ function ensureObjectIdentityChildren(identity) {
245
+ if (identity.children === void 0) identity.children = {};
246
+ if (Array.isArray(identity.children)) throw new TypeError("Expected object identity children.");
247
+ return identity.children;
248
+ }
249
+ function ensureArrayIdentityChildren(identity) {
250
+ if (identity.children === void 0) identity.children = [];
251
+ if (!Array.isArray(identity.children)) throw new TypeError("Expected array identity children.");
252
+ return identity.children;
253
+ }
254
+ function normalizeArrayIndex(index, allowEnd) {
255
+ if (!Number.isInteger(index)) throw new TypeError("Array index must be an integer.");
256
+ if (index < 0) throw new RangeError("Array index must be greater than or equal to 0.");
257
+ return allowEnd ? index : Math.max(index, 0);
258
+ }
259
+ function createIdentityTree(value, existingId) {
260
+ if (Array.isArray(value)) return {
261
+ id: existingId ?? createJsonNodeId(),
262
+ children: value.map((item) => createIdentityTree(item))
263
+ };
264
+ if (isJsonObject(value)) {
265
+ const children = {};
266
+ for (const [key, item] of Object.entries(value)) children[key] = createIdentityTree(item);
267
+ return {
268
+ id: existingId ?? createJsonNodeId(),
269
+ children
270
+ };
271
+ }
272
+ return { id: existingId ?? createJsonNodeId() };
273
+ }
274
+ function isObjectInsertInput(input) {
275
+ return typeof input.key === "string";
276
+ }
277
+ function isArrayItemKey(key) {
278
+ return key !== void 0 && /^\d+$/.test(key);
279
+ }
280
+ function isSubPath(candidate, parentPath) {
281
+ if (!parentPath) return candidate.length > 0;
282
+ return candidate === parentPath || candidate.startsWith(`${parentPath}.`) || candidate.startsWith(`${parentPath}[`);
283
+ }
284
+ function cloneJsonValue(value) {
285
+ if (Array.isArray(value)) return value.map((item) => cloneJsonValue(item));
286
+ if (isJsonObject(value)) {
287
+ const result = {};
288
+ for (const [key, item] of Object.entries(value)) result[key] = cloneJsonValue(item);
289
+ return result;
290
+ }
291
+ return value;
292
+ }
293
+ function cloneIdentityTree(identity) {
294
+ if (Array.isArray(identity.children)) return {
295
+ id: identity.id,
296
+ children: identity.children.map((child) => cloneIdentityTree(child))
297
+ };
298
+ if (identity.children) {
299
+ const children = {};
300
+ for (const [key, child] of Object.entries(identity.children)) children[key] = cloneIdentityTree(child);
301
+ return {
302
+ id: identity.id,
303
+ children
304
+ };
305
+ }
306
+ return { id: identity.id };
307
+ }
308
+ //#endregion
309
+ export { insert, move, remove, renameKey, updateValue };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@jsonup/operation",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "description": "Immutable JSON tree operations for the jsonup ecosystem.",
6
+ "author": {
7
+ "name": "Michael Cocova",
8
+ "email": "michael.cocova@gmail.com"
9
+ },
10
+ "license": "MIT",
11
+ "homepage": "https://github.com/michaelcocova/jsonup#readme",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/michaelcocova/jsonup.git"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/michaelcocova/jsonup/issues"
18
+ },
19
+ "exports": {
20
+ ".": "./dist/index.mjs",
21
+ "./package.json": "./package.json"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "dependencies": {
27
+ "@jsonup/core": "0.0.1"
28
+ },
29
+ "devDependencies": {
30
+ "tsdown": "^0.22.0",
31
+ "typescript": "^6.0.3"
32
+ },
33
+ "scripts": {
34
+ "build": "tsdown",
35
+ "dev": "tsdown --watch",
36
+ "test": "vitest run"
37
+ }
38
+ }