@visual-json/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -0
- package/dist/index.d.mts +132 -0
- package/dist/index.d.ts +132 -0
- package/dist/index.js +827 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +775 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +32 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
// src/tree.ts
|
|
2
|
+
var nextId = 0;
|
|
3
|
+
function generateId() {
|
|
4
|
+
return `node_${++nextId}`;
|
|
5
|
+
}
|
|
6
|
+
function resetIdCounter() {
|
|
7
|
+
nextId = 0;
|
|
8
|
+
}
|
|
9
|
+
function getNodeType(value) {
|
|
10
|
+
if (value === null) return "null";
|
|
11
|
+
if (Array.isArray(value)) return "array";
|
|
12
|
+
return typeof value;
|
|
13
|
+
}
|
|
14
|
+
function buildSubtree(key, value, parentPath, parentId, nodesById) {
|
|
15
|
+
const id = generateId();
|
|
16
|
+
const path = parentPath ? `${parentPath}/${key}` : `/${key}`;
|
|
17
|
+
const type = getNodeType(value);
|
|
18
|
+
const node = {
|
|
19
|
+
id,
|
|
20
|
+
key,
|
|
21
|
+
path,
|
|
22
|
+
type,
|
|
23
|
+
value: type === "object" || type === "array" ? void 0 : value,
|
|
24
|
+
children: [],
|
|
25
|
+
parentId
|
|
26
|
+
};
|
|
27
|
+
nodesById.set(id, node);
|
|
28
|
+
if (type === "object" && value !== null) {
|
|
29
|
+
const obj = value;
|
|
30
|
+
node.children = Object.keys(obj).map(
|
|
31
|
+
(childKey) => buildSubtree(childKey, obj[childKey], path, id, nodesById)
|
|
32
|
+
);
|
|
33
|
+
} else if (type === "array") {
|
|
34
|
+
const arr = value;
|
|
35
|
+
node.children = arr.map(
|
|
36
|
+
(item, index) => buildSubtree(String(index), item, path, id, nodesById)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return node;
|
|
40
|
+
}
|
|
41
|
+
function fromJson(value) {
|
|
42
|
+
const nodesById = /* @__PURE__ */ new Map();
|
|
43
|
+
const rootType = getNodeType(value);
|
|
44
|
+
const root = {
|
|
45
|
+
id: generateId(),
|
|
46
|
+
key: "",
|
|
47
|
+
path: "/",
|
|
48
|
+
type: rootType,
|
|
49
|
+
value: rootType === "object" || rootType === "array" ? void 0 : value,
|
|
50
|
+
children: [],
|
|
51
|
+
parentId: null
|
|
52
|
+
};
|
|
53
|
+
nodesById.set(root.id, root);
|
|
54
|
+
if (rootType === "object" && value !== null) {
|
|
55
|
+
const obj = value;
|
|
56
|
+
root.children = Object.keys(obj).map(
|
|
57
|
+
(key) => buildSubtree(key, obj[key], "", root.id, nodesById)
|
|
58
|
+
);
|
|
59
|
+
} else if (rootType === "array") {
|
|
60
|
+
const arr = value;
|
|
61
|
+
root.children = arr.map(
|
|
62
|
+
(item, index) => buildSubtree(String(index), item, "", root.id, nodesById)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return { root, nodesById };
|
|
66
|
+
}
|
|
67
|
+
function toJson(node) {
|
|
68
|
+
switch (node.type) {
|
|
69
|
+
case "object": {
|
|
70
|
+
const obj = {};
|
|
71
|
+
for (const child of node.children) {
|
|
72
|
+
obj[child.key] = toJson(child);
|
|
73
|
+
}
|
|
74
|
+
return obj;
|
|
75
|
+
}
|
|
76
|
+
case "array":
|
|
77
|
+
return node.children.map((child) => toJson(child));
|
|
78
|
+
default:
|
|
79
|
+
return node.value;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function findNode(state, nodeId) {
|
|
83
|
+
return state.nodesById.get(nodeId);
|
|
84
|
+
}
|
|
85
|
+
function findNodeByPath(state, path) {
|
|
86
|
+
if (path === "/") return state.root;
|
|
87
|
+
const segments = path.split("/").filter(Boolean);
|
|
88
|
+
let current = state.root;
|
|
89
|
+
for (const segment of segments) {
|
|
90
|
+
const child = current.children.find((c) => c.key === segment);
|
|
91
|
+
if (!child) return void 0;
|
|
92
|
+
current = child;
|
|
93
|
+
}
|
|
94
|
+
return current;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/operations.ts
|
|
98
|
+
function rebuildMap(root) {
|
|
99
|
+
const map = /* @__PURE__ */ new Map();
|
|
100
|
+
function walk(node) {
|
|
101
|
+
map.set(node.id, node);
|
|
102
|
+
for (const child of node.children) walk(child);
|
|
103
|
+
}
|
|
104
|
+
walk(root);
|
|
105
|
+
return map;
|
|
106
|
+
}
|
|
107
|
+
function recomputePaths(node, newParentPath) {
|
|
108
|
+
const newPath = newParentPath ? `${newParentPath}/${node.key}` : `/${node.key}`;
|
|
109
|
+
if (node.path === newPath && node.children.length === 0) return node;
|
|
110
|
+
return {
|
|
111
|
+
...node,
|
|
112
|
+
path: newPath,
|
|
113
|
+
children: node.children.map((child) => recomputePaths(child, newPath))
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function clonePathToNode(state, targetId, updater) {
|
|
117
|
+
const chain = [];
|
|
118
|
+
let cur = state.nodesById.get(targetId);
|
|
119
|
+
while (cur) {
|
|
120
|
+
chain.unshift(cur.id);
|
|
121
|
+
cur = cur.parentId ? state.nodesById.get(cur.parentId) : void 0;
|
|
122
|
+
}
|
|
123
|
+
if (chain.length === 0) return state;
|
|
124
|
+
function cloneAlongPath(node, depth) {
|
|
125
|
+
if (depth === chain.length - 1) {
|
|
126
|
+
return updater(node);
|
|
127
|
+
}
|
|
128
|
+
const nextInChain = chain[depth + 1];
|
|
129
|
+
return {
|
|
130
|
+
...node,
|
|
131
|
+
children: node.children.map(
|
|
132
|
+
(child) => child.id === nextInChain ? cloneAlongPath(child, depth + 1) : child
|
|
133
|
+
)
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const newRoot = cloneAlongPath(state.root, 0);
|
|
137
|
+
return { root: newRoot, nodesById: rebuildMap(newRoot) };
|
|
138
|
+
}
|
|
139
|
+
function reindexArrayChildren(parent) {
|
|
140
|
+
if (parent.type !== "array") return parent;
|
|
141
|
+
const parentPath = parent.path === "/" ? "" : parent.path;
|
|
142
|
+
return {
|
|
143
|
+
...parent,
|
|
144
|
+
children: parent.children.map((child, i) => {
|
|
145
|
+
const newKey = String(i);
|
|
146
|
+
if (child.key === newKey) return child;
|
|
147
|
+
return recomputePaths({ ...child, key: newKey }, parentPath);
|
|
148
|
+
})
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function setValue(state, nodeId, value) {
|
|
152
|
+
const node = state.nodesById.get(nodeId);
|
|
153
|
+
if (!node) return state;
|
|
154
|
+
const newType = getNodeType(value);
|
|
155
|
+
if (newType !== "object" && newType !== "array") {
|
|
156
|
+
return clonePathToNode(state, nodeId, (n) => ({
|
|
157
|
+
...n,
|
|
158
|
+
type: newType,
|
|
159
|
+
value,
|
|
160
|
+
children: []
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
return clonePathToNode(state, nodeId, (n) => {
|
|
164
|
+
const parentPath = n.path.split("/").slice(0, -1).join("/") || "";
|
|
165
|
+
const nodesById = /* @__PURE__ */ new Map();
|
|
166
|
+
const subtree = buildSubtree(n.key, value, parentPath, n.parentId, nodesById);
|
|
167
|
+
return { ...subtree, id: n.id };
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function setKey(state, nodeId, newKey) {
|
|
171
|
+
const node = state.nodesById.get(nodeId);
|
|
172
|
+
if (!node || !node.parentId) return state;
|
|
173
|
+
const parent = state.nodesById.get(node.parentId);
|
|
174
|
+
if (!parent || parent.type !== "object") return state;
|
|
175
|
+
return clonePathToNode(state, nodeId, (n) => {
|
|
176
|
+
const parentPath = parent.path === "/" ? "" : parent.path;
|
|
177
|
+
const newPath = `${parentPath}/${newKey}`;
|
|
178
|
+
const updated = { ...n, key: newKey, path: newPath };
|
|
179
|
+
if (updated.children.length > 0) {
|
|
180
|
+
updated.children = updated.children.map(
|
|
181
|
+
(child) => recomputePaths(child, newPath)
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return updated;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function addProperty(state, parentId, key, value) {
|
|
188
|
+
const parent = state.nodesById.get(parentId);
|
|
189
|
+
if (!parent) return state;
|
|
190
|
+
return clonePathToNode(state, parentId, (p) => {
|
|
191
|
+
const parentPath = p.path === "/" ? "" : p.path;
|
|
192
|
+
const nodesById = /* @__PURE__ */ new Map();
|
|
193
|
+
const newChild = buildSubtree(key, value, parentPath, p.id, nodesById);
|
|
194
|
+
return { ...p, children: [...p.children, newChild] };
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
function removeNode(state, nodeId) {
|
|
198
|
+
const node = state.nodesById.get(nodeId);
|
|
199
|
+
if (!node || !node.parentId) return state;
|
|
200
|
+
return clonePathToNode(state, node.parentId, (p) => {
|
|
201
|
+
const newChildren = p.children.filter((c) => c.id !== nodeId);
|
|
202
|
+
return reindexArrayChildren({ ...p, children: newChildren });
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
function moveNode(state, nodeId, newParentId, index) {
|
|
206
|
+
const node = state.nodesById.get(nodeId);
|
|
207
|
+
if (!node || !node.parentId) return state;
|
|
208
|
+
const srcParent = state.nodesById.get(node.parentId);
|
|
209
|
+
const dstParent = state.nodesById.get(newParentId);
|
|
210
|
+
if (!srcParent || !dstParent) return state;
|
|
211
|
+
const nodeValue = toJson(node);
|
|
212
|
+
const removed = clonePathToNode(state, node.parentId, (p) => {
|
|
213
|
+
const newChildren = p.children.filter((c) => c.id !== nodeId);
|
|
214
|
+
return reindexArrayChildren({ ...p, children: newChildren });
|
|
215
|
+
});
|
|
216
|
+
return clonePathToNode(removed, newParentId, (p) => {
|
|
217
|
+
const parentPath = p.path === "/" ? "" : p.path;
|
|
218
|
+
const nodesById = /* @__PURE__ */ new Map();
|
|
219
|
+
const newChild = buildSubtree(
|
|
220
|
+
p.type === "array" ? String(index ?? p.children.length) : node.key,
|
|
221
|
+
nodeValue,
|
|
222
|
+
parentPath,
|
|
223
|
+
p.id,
|
|
224
|
+
nodesById
|
|
225
|
+
);
|
|
226
|
+
const newChildren = [...p.children];
|
|
227
|
+
const insertAt = index ?? newChildren.length;
|
|
228
|
+
newChildren.splice(insertAt, 0, newChild);
|
|
229
|
+
return reindexArrayChildren({ ...p, children: newChildren });
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function reorderChildren(state, parentId, fromIndex, toIndex) {
|
|
233
|
+
const parent = state.nodesById.get(parentId);
|
|
234
|
+
if (!parent) return state;
|
|
235
|
+
return clonePathToNode(state, parentId, (p) => {
|
|
236
|
+
const newChildren = [...p.children];
|
|
237
|
+
const [item] = newChildren.splice(fromIndex, 1);
|
|
238
|
+
newChildren.splice(toIndex, 0, item);
|
|
239
|
+
return reindexArrayChildren({ ...p, children: newChildren });
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function convertValue(current, newType) {
|
|
243
|
+
switch (newType) {
|
|
244
|
+
case "string":
|
|
245
|
+
if (current === null || current === void 0) return "";
|
|
246
|
+
if (typeof current === "object") return JSON.stringify(current);
|
|
247
|
+
return String(current);
|
|
248
|
+
case "number": {
|
|
249
|
+
const n = Number(current);
|
|
250
|
+
return isNaN(n) ? 0 : n;
|
|
251
|
+
}
|
|
252
|
+
case "boolean":
|
|
253
|
+
return Boolean(current);
|
|
254
|
+
case "null":
|
|
255
|
+
return null;
|
|
256
|
+
case "object":
|
|
257
|
+
if (current !== null && typeof current === "object" && !Array.isArray(current))
|
|
258
|
+
return current;
|
|
259
|
+
if (Array.isArray(current)) {
|
|
260
|
+
const obj = {};
|
|
261
|
+
current.forEach((item, i) => {
|
|
262
|
+
obj[String(i)] = item;
|
|
263
|
+
});
|
|
264
|
+
return obj;
|
|
265
|
+
}
|
|
266
|
+
return {};
|
|
267
|
+
case "array":
|
|
268
|
+
if (Array.isArray(current)) return current;
|
|
269
|
+
if (current !== null && typeof current === "object")
|
|
270
|
+
return Object.values(current);
|
|
271
|
+
return current === null || current === void 0 ? [] : [current];
|
|
272
|
+
default:
|
|
273
|
+
return current;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function changeType(state, nodeId, newType) {
|
|
277
|
+
const node = state.nodesById.get(nodeId);
|
|
278
|
+
if (!node) return state;
|
|
279
|
+
const currentValue = toJson(node);
|
|
280
|
+
const newValue = convertValue(currentValue, newType);
|
|
281
|
+
return setValue(state, nodeId, newValue);
|
|
282
|
+
}
|
|
283
|
+
function duplicateNode(state, nodeId) {
|
|
284
|
+
const node = state.nodesById.get(nodeId);
|
|
285
|
+
if (!node || !node.parentId) return state;
|
|
286
|
+
const parent = state.nodesById.get(node.parentId);
|
|
287
|
+
if (!parent) return state;
|
|
288
|
+
const nodeValue = toJson(node);
|
|
289
|
+
return clonePathToNode(state, node.parentId, (p) => {
|
|
290
|
+
const idx = p.children.findIndex((c) => c.id === nodeId);
|
|
291
|
+
const parentPath = p.path === "/" ? "" : p.path;
|
|
292
|
+
const newKey = p.type === "array" ? String(idx + 1) : `${node.key}_copy`;
|
|
293
|
+
const nodesById = /* @__PURE__ */ new Map();
|
|
294
|
+
const newChild = buildSubtree(
|
|
295
|
+
newKey,
|
|
296
|
+
structuredClone(nodeValue),
|
|
297
|
+
parentPath,
|
|
298
|
+
p.id,
|
|
299
|
+
nodesById
|
|
300
|
+
);
|
|
301
|
+
const newChildren = [...p.children];
|
|
302
|
+
newChildren.splice(idx + 1, 0, newChild);
|
|
303
|
+
return reindexArrayChildren({ ...p, children: newChildren });
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/history.ts
|
|
308
|
+
var MAX_HISTORY = 100;
|
|
309
|
+
var History = class {
|
|
310
|
+
constructor() {
|
|
311
|
+
this.stack = [];
|
|
312
|
+
this.cursor = -1;
|
|
313
|
+
}
|
|
314
|
+
push(state) {
|
|
315
|
+
this.stack = this.stack.slice(0, this.cursor + 1);
|
|
316
|
+
this.stack.push(state);
|
|
317
|
+
if (this.stack.length > MAX_HISTORY) {
|
|
318
|
+
this.stack.shift();
|
|
319
|
+
} else {
|
|
320
|
+
this.cursor++;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
undo() {
|
|
324
|
+
if (!this.canUndo) return null;
|
|
325
|
+
this.cursor--;
|
|
326
|
+
return this.stack[this.cursor];
|
|
327
|
+
}
|
|
328
|
+
redo() {
|
|
329
|
+
if (!this.canRedo) return null;
|
|
330
|
+
this.cursor++;
|
|
331
|
+
return this.stack[this.cursor];
|
|
332
|
+
}
|
|
333
|
+
get canUndo() {
|
|
334
|
+
return this.cursor > 0;
|
|
335
|
+
}
|
|
336
|
+
get canRedo() {
|
|
337
|
+
return this.cursor < this.stack.length - 1;
|
|
338
|
+
}
|
|
339
|
+
get current() {
|
|
340
|
+
return this.stack[this.cursor] ?? null;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// src/schema.ts
|
|
345
|
+
var KNOWN_SCHEMAS = {
|
|
346
|
+
"package.json": "https://json.schemastore.org/package.json",
|
|
347
|
+
"tsconfig.json": "https://json.schemastore.org/tsconfig",
|
|
348
|
+
"tsconfig.base.json": "https://json.schemastore.org/tsconfig",
|
|
349
|
+
".eslintrc.json": "https://json.schemastore.org/eslintrc",
|
|
350
|
+
".prettierrc": "https://json.schemastore.org/prettierrc",
|
|
351
|
+
".prettierrc.json": "https://json.schemastore.org/prettierrc",
|
|
352
|
+
"turbo.json": "https://turborepo.dev/schema.json",
|
|
353
|
+
".babelrc": "https://json.schemastore.org/babelrc",
|
|
354
|
+
"nest-cli.json": "https://json.schemastore.org/nest-cli",
|
|
355
|
+
"vercel.json": "https://openapi.vercel.sh/vercel.json",
|
|
356
|
+
".swcrc": "https://json.schemastore.org/swcrc"
|
|
357
|
+
};
|
|
358
|
+
var MAX_SCHEMA_CACHE = 50;
|
|
359
|
+
var schemaCache = /* @__PURE__ */ new Map();
|
|
360
|
+
async function fetchSchema(url) {
|
|
361
|
+
if (schemaCache.has(url)) {
|
|
362
|
+
return schemaCache.get(url);
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
const res = await fetch(url);
|
|
366
|
+
if (!res.ok) return null;
|
|
367
|
+
const schema = await res.json();
|
|
368
|
+
if (schemaCache.size >= MAX_SCHEMA_CACHE) {
|
|
369
|
+
const oldest = schemaCache.keys().next().value;
|
|
370
|
+
if (oldest !== void 0) schemaCache.delete(oldest);
|
|
371
|
+
}
|
|
372
|
+
schemaCache.set(url, schema);
|
|
373
|
+
return schema;
|
|
374
|
+
} catch {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
async function resolveSchema(json, filename) {
|
|
379
|
+
if (json !== null && typeof json === "object" && !Array.isArray(json) && typeof json["$schema"] === "string") {
|
|
380
|
+
const url = json["$schema"];
|
|
381
|
+
const schema = await fetchSchema(url);
|
|
382
|
+
if (schema) return schema;
|
|
383
|
+
}
|
|
384
|
+
if (filename) {
|
|
385
|
+
const base = filename.split("/").pop() ?? filename;
|
|
386
|
+
const knownUrl = KNOWN_SCHEMAS[base];
|
|
387
|
+
if (knownUrl) {
|
|
388
|
+
return fetchSchema(knownUrl);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
function findDefinition(root, refPath) {
|
|
394
|
+
if (!refPath.startsWith("#/")) return void 0;
|
|
395
|
+
const segments = refPath.slice(2).split("/");
|
|
396
|
+
let current = root;
|
|
397
|
+
for (const seg of segments) {
|
|
398
|
+
if (current === null || typeof current !== "object") return void 0;
|
|
399
|
+
current = current[seg];
|
|
400
|
+
}
|
|
401
|
+
return current;
|
|
402
|
+
}
|
|
403
|
+
function resolveRef(prop, root, visited) {
|
|
404
|
+
const seen = visited ?? /* @__PURE__ */ new Set();
|
|
405
|
+
if (prop.$ref) {
|
|
406
|
+
if (seen.has(prop.$ref)) return prop;
|
|
407
|
+
seen.add(prop.$ref);
|
|
408
|
+
const resolved = findDefinition(root, prop.$ref);
|
|
409
|
+
if (resolved) {
|
|
410
|
+
return resolveRef(resolved, root, seen);
|
|
411
|
+
}
|
|
412
|
+
return prop;
|
|
413
|
+
}
|
|
414
|
+
if (prop.allOf && prop.allOf.length > 0) {
|
|
415
|
+
return mergeAllOf(prop, root, seen);
|
|
416
|
+
}
|
|
417
|
+
return prop;
|
|
418
|
+
}
|
|
419
|
+
function mergeAllOf(prop, root, visited) {
|
|
420
|
+
const merged = { ...prop };
|
|
421
|
+
delete merged.allOf;
|
|
422
|
+
for (const sub of prop.allOf) {
|
|
423
|
+
const resolved = resolveRef(sub, root, new Set(visited));
|
|
424
|
+
if (resolved.type && !merged.type) merged.type = resolved.type;
|
|
425
|
+
if (resolved.properties) {
|
|
426
|
+
merged.properties = { ...merged.properties, ...resolved.properties };
|
|
427
|
+
}
|
|
428
|
+
if (resolved.required) {
|
|
429
|
+
merged.required = [
|
|
430
|
+
.../* @__PURE__ */ new Set([...merged.required ?? [], ...resolved.required])
|
|
431
|
+
];
|
|
432
|
+
}
|
|
433
|
+
if (resolved.additionalProperties !== void 0 && merged.additionalProperties === void 0) {
|
|
434
|
+
merged.additionalProperties = resolved.additionalProperties;
|
|
435
|
+
}
|
|
436
|
+
if (resolved.items && !merged.items) merged.items = resolved.items;
|
|
437
|
+
if (resolved.description && !merged.description)
|
|
438
|
+
merged.description = resolved.description;
|
|
439
|
+
if (resolved.title && !merged.title) merged.title = resolved.title;
|
|
440
|
+
if (resolved.enum && !merged.enum) merged.enum = resolved.enum;
|
|
441
|
+
if (resolved.minimum !== void 0 && merged.minimum === void 0)
|
|
442
|
+
merged.minimum = resolved.minimum;
|
|
443
|
+
if (resolved.maximum !== void 0 && merged.maximum === void 0)
|
|
444
|
+
merged.maximum = resolved.maximum;
|
|
445
|
+
if (resolved.minLength !== void 0 && merged.minLength === void 0)
|
|
446
|
+
merged.minLength = resolved.minLength;
|
|
447
|
+
if (resolved.maxLength !== void 0 && merged.maxLength === void 0)
|
|
448
|
+
merged.maxLength = resolved.maxLength;
|
|
449
|
+
if (resolved.pattern && !merged.pattern) merged.pattern = resolved.pattern;
|
|
450
|
+
if (resolved.format && !merged.format) merged.format = resolved.format;
|
|
451
|
+
}
|
|
452
|
+
return merged;
|
|
453
|
+
}
|
|
454
|
+
function getPropertySchema(schema, path, rootSchema) {
|
|
455
|
+
const root = rootSchema ?? schema;
|
|
456
|
+
const segments = path.split("/").filter(Boolean);
|
|
457
|
+
let current = resolveRef(schema, root);
|
|
458
|
+
for (const seg of segments) {
|
|
459
|
+
if (!current) return void 0;
|
|
460
|
+
current = resolveRef(current, root);
|
|
461
|
+
if (current.properties?.[seg]) {
|
|
462
|
+
current = resolveRef(current.properties[seg], root);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (current.patternProperties) {
|
|
466
|
+
const match = Object.entries(current.patternProperties).find(
|
|
467
|
+
([pattern]) => {
|
|
468
|
+
try {
|
|
469
|
+
return new RegExp(pattern).test(seg);
|
|
470
|
+
} catch {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
);
|
|
475
|
+
if (match) {
|
|
476
|
+
current = resolveRef(match[1], root);
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (current.additionalProperties && typeof current.additionalProperties === "object") {
|
|
481
|
+
current = resolveRef(current.additionalProperties, root);
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (current.items) {
|
|
485
|
+
if (Array.isArray(current.items)) {
|
|
486
|
+
const idx = Number(seg);
|
|
487
|
+
if (!isNaN(idx) && current.items[idx]) {
|
|
488
|
+
current = resolveRef(current.items[idx], root);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
} else {
|
|
492
|
+
current = resolveRef(current.items, root);
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if (current.anyOf || current.oneOf) {
|
|
497
|
+
const variants = current.anyOf ?? current.oneOf ?? [];
|
|
498
|
+
let found;
|
|
499
|
+
for (const variant of variants) {
|
|
500
|
+
const resolved = resolveRef(variant, root);
|
|
501
|
+
found = getPropertySchema(resolved, seg, root);
|
|
502
|
+
if (found) break;
|
|
503
|
+
}
|
|
504
|
+
if (found) {
|
|
505
|
+
current = found;
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
return void 0;
|
|
509
|
+
}
|
|
510
|
+
return void 0;
|
|
511
|
+
}
|
|
512
|
+
return current;
|
|
513
|
+
}
|
|
514
|
+
function clearSchemaCache() {
|
|
515
|
+
schemaCache.clear();
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/validate.ts
|
|
519
|
+
function schemaTypeMatches(nodeType, schemaType) {
|
|
520
|
+
if (!schemaType) return true;
|
|
521
|
+
const types = Array.isArray(schemaType) ? schemaType : [schemaType];
|
|
522
|
+
if (nodeType === "number" && types.includes("integer")) return true;
|
|
523
|
+
return types.includes(nodeType);
|
|
524
|
+
}
|
|
525
|
+
var FORMAT_PATTERNS = {
|
|
526
|
+
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
527
|
+
uri: /^https?:\/\/.+/,
|
|
528
|
+
"uri-reference": /^(https?:\/\/|\/|\.\.?\/|#).*/,
|
|
529
|
+
"date-time": /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/,
|
|
530
|
+
date: /^\d{4}-\d{2}-\d{2}$/,
|
|
531
|
+
time: /^\d{2}:\d{2}:\d{2}/,
|
|
532
|
+
ipv4: /^(\d{1,3}\.){3}\d{1,3}$/,
|
|
533
|
+
ipv6: /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/,
|
|
534
|
+
uuid: /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/,
|
|
535
|
+
hostname: /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$/
|
|
536
|
+
};
|
|
537
|
+
function validateNode(node, schema) {
|
|
538
|
+
const errors = [];
|
|
539
|
+
if (!schema) {
|
|
540
|
+
return { valid: true, errors };
|
|
541
|
+
}
|
|
542
|
+
if (schema.type && !schemaTypeMatches(node.type, schema.type)) {
|
|
543
|
+
const expected = Array.isArray(schema.type) ? schema.type.join(" | ") : schema.type;
|
|
544
|
+
errors.push(`Expected type "${expected}", got "${node.type}"`);
|
|
545
|
+
}
|
|
546
|
+
if (schema.enum && schema.enum.length > 0 && node.value !== void 0) {
|
|
547
|
+
const match = schema.enum.some(
|
|
548
|
+
(v) => JSON.stringify(v) === JSON.stringify(node.value)
|
|
549
|
+
);
|
|
550
|
+
if (!match) {
|
|
551
|
+
errors.push(
|
|
552
|
+
`Value must be one of: ${schema.enum.map((v) => JSON.stringify(v)).join(", ")}`
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (schema.const !== void 0 && node.value !== void 0) {
|
|
557
|
+
if (JSON.stringify(node.value) !== JSON.stringify(schema.const)) {
|
|
558
|
+
errors.push(`Value must be ${JSON.stringify(schema.const)}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (schema.required && (node.type === "object" || node.type === "array")) {
|
|
562
|
+
const childKeys = new Set(node.children.map((c) => c.key));
|
|
563
|
+
for (const req of schema.required) {
|
|
564
|
+
if (!childKeys.has(req)) {
|
|
565
|
+
errors.push(`Missing required property "${req}"`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (node.type === "number" && typeof node.value === "number") {
|
|
570
|
+
const val = node.value;
|
|
571
|
+
if (schema.minimum !== void 0 && val < schema.minimum) {
|
|
572
|
+
errors.push(`Value must be >= ${schema.minimum}`);
|
|
573
|
+
}
|
|
574
|
+
if (schema.maximum !== void 0 && val > schema.maximum) {
|
|
575
|
+
errors.push(`Value must be <= ${schema.maximum}`);
|
|
576
|
+
}
|
|
577
|
+
if (schema.exclusiveMinimum !== void 0) {
|
|
578
|
+
const bound = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : schema.minimum;
|
|
579
|
+
if (bound !== void 0 && val <= bound) {
|
|
580
|
+
errors.push(`Value must be > ${bound}`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (schema.exclusiveMaximum !== void 0) {
|
|
584
|
+
const bound = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : schema.maximum;
|
|
585
|
+
if (bound !== void 0 && val >= bound) {
|
|
586
|
+
errors.push(`Value must be < ${bound}`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (schema.multipleOf !== void 0) {
|
|
590
|
+
const remainder = Math.abs(val % schema.multipleOf);
|
|
591
|
+
if (remainder > 1e-10 && Math.abs(remainder - schema.multipleOf) > 1e-10) {
|
|
592
|
+
errors.push(`Value must be a multiple of ${schema.multipleOf}`);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
if (node.type === "string" && typeof node.value === "string") {
|
|
597
|
+
const val = node.value;
|
|
598
|
+
if (schema.minLength !== void 0 && val.length < schema.minLength) {
|
|
599
|
+
errors.push(`Must be at least ${schema.minLength} characters`);
|
|
600
|
+
}
|
|
601
|
+
if (schema.maxLength !== void 0 && val.length > schema.maxLength) {
|
|
602
|
+
errors.push(`Must be at most ${schema.maxLength} characters`);
|
|
603
|
+
}
|
|
604
|
+
if (schema.pattern) {
|
|
605
|
+
try {
|
|
606
|
+
if (!new RegExp(schema.pattern).test(val)) {
|
|
607
|
+
errors.push(`Must match pattern: ${schema.pattern}`);
|
|
608
|
+
}
|
|
609
|
+
} catch {
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (schema.format && FORMAT_PATTERNS[schema.format]) {
|
|
613
|
+
if (!FORMAT_PATTERNS[schema.format].test(val)) {
|
|
614
|
+
errors.push(`Invalid ${schema.format} format`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
if (node.type === "array") {
|
|
619
|
+
if (schema.minItems !== void 0 && node.children.length < schema.minItems) {
|
|
620
|
+
errors.push(`Must have at least ${schema.minItems} items`);
|
|
621
|
+
}
|
|
622
|
+
if (schema.maxItems !== void 0 && node.children.length > schema.maxItems) {
|
|
623
|
+
errors.push(`Must have at most ${schema.maxItems} items`);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (node.type === "object") {
|
|
627
|
+
if (schema.minProperties !== void 0 && node.children.length < schema.minProperties) {
|
|
628
|
+
errors.push(`Must have at least ${schema.minProperties} properties`);
|
|
629
|
+
}
|
|
630
|
+
if (schema.maxProperties !== void 0 && node.children.length > schema.maxProperties) {
|
|
631
|
+
errors.push(`Must have at most ${schema.maxProperties} properties`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return { valid: errors.length === 0, errors };
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// src/search.ts
|
|
638
|
+
function searchNodes(tree, query) {
|
|
639
|
+
if (!query.trim()) return [];
|
|
640
|
+
const matches = [];
|
|
641
|
+
const lower = query.toLowerCase();
|
|
642
|
+
function walk(node) {
|
|
643
|
+
if (node.key && node.key.toLowerCase().includes(lower)) {
|
|
644
|
+
matches.push({ nodeId: node.id, field: "key" });
|
|
645
|
+
}
|
|
646
|
+
if (node.value !== void 0 && node.value !== null && String(node.value).toLowerCase().includes(lower)) {
|
|
647
|
+
matches.push({ nodeId: node.id, field: "value" });
|
|
648
|
+
}
|
|
649
|
+
for (const child of node.children) {
|
|
650
|
+
walk(child);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
walk(tree.root);
|
|
654
|
+
return matches;
|
|
655
|
+
}
|
|
656
|
+
function getAncestorIds(tree, nodeIds) {
|
|
657
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
658
|
+
for (const nodeId of nodeIds) {
|
|
659
|
+
let current = tree.nodesById.get(nodeId);
|
|
660
|
+
while (current && current.parentId) {
|
|
661
|
+
ancestors.add(current.parentId);
|
|
662
|
+
current = tree.nodesById.get(current.parentId);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return ancestors;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// src/diff.ts
|
|
669
|
+
function computeDiff(original, current, path = "") {
|
|
670
|
+
const entries = [];
|
|
671
|
+
if (original === current) {
|
|
672
|
+
return entries;
|
|
673
|
+
}
|
|
674
|
+
if (original === null || current === null || typeof original !== typeof current || Array.isArray(original) !== Array.isArray(current)) {
|
|
675
|
+
if (path) {
|
|
676
|
+
entries.push({
|
|
677
|
+
path,
|
|
678
|
+
type: "changed",
|
|
679
|
+
oldValue: original,
|
|
680
|
+
newValue: current
|
|
681
|
+
});
|
|
682
|
+
} else {
|
|
683
|
+
diffObject(original, current, path, entries);
|
|
684
|
+
}
|
|
685
|
+
return entries;
|
|
686
|
+
}
|
|
687
|
+
if (typeof original !== "object") {
|
|
688
|
+
if (original !== current) {
|
|
689
|
+
entries.push({
|
|
690
|
+
path: path || "/",
|
|
691
|
+
type: "changed",
|
|
692
|
+
oldValue: original,
|
|
693
|
+
newValue: current
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
return entries;
|
|
697
|
+
}
|
|
698
|
+
if (Array.isArray(original) && Array.isArray(current)) {
|
|
699
|
+
const maxLen = Math.max(original.length, current.length);
|
|
700
|
+
for (let i = 0; i < maxLen; i++) {
|
|
701
|
+
const childPath = path ? `${path}/${i}` : `/${i}`;
|
|
702
|
+
if (i >= original.length) {
|
|
703
|
+
entries.push({ path: childPath, type: "added", newValue: current[i] });
|
|
704
|
+
} else if (i >= current.length) {
|
|
705
|
+
entries.push({
|
|
706
|
+
path: childPath,
|
|
707
|
+
type: "removed",
|
|
708
|
+
oldValue: original[i]
|
|
709
|
+
});
|
|
710
|
+
} else {
|
|
711
|
+
entries.push(...computeDiff(original[i], current[i], childPath));
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return entries;
|
|
715
|
+
}
|
|
716
|
+
diffObject(original, current, path, entries);
|
|
717
|
+
return entries;
|
|
718
|
+
}
|
|
719
|
+
function diffObject(original, current, path, entries) {
|
|
720
|
+
const origObj = original !== null && typeof original === "object" && !Array.isArray(original) ? original : {};
|
|
721
|
+
const currObj = current !== null && typeof current === "object" && !Array.isArray(current) ? current : {};
|
|
722
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(origObj), ...Object.keys(currObj)]);
|
|
723
|
+
for (const key of allKeys) {
|
|
724
|
+
const childPath = path ? `${path}/${key}` : `/${key}`;
|
|
725
|
+
const inOriginal = key in origObj;
|
|
726
|
+
const inCurrent = key in currObj;
|
|
727
|
+
if (!inOriginal && inCurrent) {
|
|
728
|
+
entries.push({ path: childPath, type: "added", newValue: currObj[key] });
|
|
729
|
+
} else if (inOriginal && !inCurrent) {
|
|
730
|
+
entries.push({
|
|
731
|
+
path: childPath,
|
|
732
|
+
type: "removed",
|
|
733
|
+
oldValue: origObj[key]
|
|
734
|
+
});
|
|
735
|
+
} else {
|
|
736
|
+
entries.push(...computeDiff(origObj[key], currObj[key], childPath));
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
function getDiffPaths(entries) {
|
|
741
|
+
const map = /* @__PURE__ */ new Map();
|
|
742
|
+
for (const entry of entries) {
|
|
743
|
+
map.set(entry.path, entry.type);
|
|
744
|
+
}
|
|
745
|
+
return map;
|
|
746
|
+
}
|
|
747
|
+
export {
|
|
748
|
+
History,
|
|
749
|
+
addProperty,
|
|
750
|
+
buildSubtree,
|
|
751
|
+
changeType,
|
|
752
|
+
clearSchemaCache,
|
|
753
|
+
computeDiff,
|
|
754
|
+
duplicateNode,
|
|
755
|
+
findNode,
|
|
756
|
+
findNodeByPath,
|
|
757
|
+
fromJson,
|
|
758
|
+
generateId,
|
|
759
|
+
getAncestorIds,
|
|
760
|
+
getDiffPaths,
|
|
761
|
+
getNodeType,
|
|
762
|
+
getPropertySchema,
|
|
763
|
+
moveNode,
|
|
764
|
+
removeNode,
|
|
765
|
+
reorderChildren,
|
|
766
|
+
resetIdCounter,
|
|
767
|
+
resolveRef,
|
|
768
|
+
resolveSchema,
|
|
769
|
+
searchNodes,
|
|
770
|
+
setKey,
|
|
771
|
+
setValue,
|
|
772
|
+
toJson,
|
|
773
|
+
validateNode
|
|
774
|
+
};
|
|
775
|
+
//# sourceMappingURL=index.mjs.map
|