@json-render/core 0.4.3 → 0.4.4
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 +8 -6
- package/dist/index.d.mts +39 -13
- package/dist/index.d.ts +39 -13
- package/dist/index.js +149 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +147 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -28,17 +28,27 @@ function resolveDynamicValue(value, dataModel) {
|
|
|
28
28
|
}
|
|
29
29
|
return value;
|
|
30
30
|
}
|
|
31
|
+
function unescapeJsonPointer(token) {
|
|
32
|
+
return token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
33
|
+
}
|
|
34
|
+
function parseJsonPointer(path) {
|
|
35
|
+
const raw = path.startsWith("/") ? path.slice(1).split("/") : path.split("/");
|
|
36
|
+
return raw.map(unescapeJsonPointer);
|
|
37
|
+
}
|
|
31
38
|
function getByPath(obj, path) {
|
|
32
39
|
if (!path || path === "/") {
|
|
33
40
|
return obj;
|
|
34
41
|
}
|
|
35
|
-
const segments =
|
|
42
|
+
const segments = parseJsonPointer(path);
|
|
36
43
|
let current = obj;
|
|
37
44
|
for (const segment of segments) {
|
|
38
45
|
if (current === null || current === void 0) {
|
|
39
46
|
return void 0;
|
|
40
47
|
}
|
|
41
|
-
if (
|
|
48
|
+
if (Array.isArray(current)) {
|
|
49
|
+
const index = parseInt(segment, 10);
|
|
50
|
+
current = current[index];
|
|
51
|
+
} else if (typeof current === "object") {
|
|
42
52
|
current = current[segment];
|
|
43
53
|
} else {
|
|
44
54
|
return void 0;
|
|
@@ -50,13 +60,13 @@ function isNumericIndex(str) {
|
|
|
50
60
|
return /^\d+$/.test(str);
|
|
51
61
|
}
|
|
52
62
|
function setByPath(obj, path, value) {
|
|
53
|
-
const segments =
|
|
63
|
+
const segments = parseJsonPointer(path);
|
|
54
64
|
if (segments.length === 0) return;
|
|
55
65
|
let current = obj;
|
|
56
66
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
57
67
|
const segment = segments[i];
|
|
58
68
|
const nextSegment = segments[i + 1];
|
|
59
|
-
const nextIsNumeric = nextSegment !== void 0 && isNumericIndex(nextSegment);
|
|
69
|
+
const nextIsNumeric = nextSegment !== void 0 && (isNumericIndex(nextSegment) || nextSegment === "-");
|
|
60
70
|
if (Array.isArray(current)) {
|
|
61
71
|
const index = parseInt(segment, 10);
|
|
62
72
|
if (current[index] === void 0 || typeof current[index] !== "object") {
|
|
@@ -72,12 +82,95 @@ function setByPath(obj, path, value) {
|
|
|
72
82
|
}
|
|
73
83
|
const lastSegment = segments[segments.length - 1];
|
|
74
84
|
if (Array.isArray(current)) {
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
if (lastSegment === "-") {
|
|
86
|
+
current.push(value);
|
|
87
|
+
} else {
|
|
88
|
+
const index = parseInt(lastSegment, 10);
|
|
89
|
+
current[index] = value;
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
current[lastSegment] = value;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function addByPath(obj, path, value) {
|
|
96
|
+
const segments = parseJsonPointer(path);
|
|
97
|
+
if (segments.length === 0) return;
|
|
98
|
+
let current = obj;
|
|
99
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
100
|
+
const segment = segments[i];
|
|
101
|
+
const nextSegment = segments[i + 1];
|
|
102
|
+
const nextIsNumeric = nextSegment !== void 0 && (isNumericIndex(nextSegment) || nextSegment === "-");
|
|
103
|
+
if (Array.isArray(current)) {
|
|
104
|
+
const index = parseInt(segment, 10);
|
|
105
|
+
if (current[index] === void 0 || typeof current[index] !== "object") {
|
|
106
|
+
current[index] = nextIsNumeric ? [] : {};
|
|
107
|
+
}
|
|
108
|
+
current = current[index];
|
|
109
|
+
} else {
|
|
110
|
+
if (!(segment in current) || typeof current[segment] !== "object") {
|
|
111
|
+
current[segment] = nextIsNumeric ? [] : {};
|
|
112
|
+
}
|
|
113
|
+
current = current[segment];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const lastSegment = segments[segments.length - 1];
|
|
117
|
+
if (Array.isArray(current)) {
|
|
118
|
+
if (lastSegment === "-") {
|
|
119
|
+
current.push(value);
|
|
120
|
+
} else {
|
|
121
|
+
const index = parseInt(lastSegment, 10);
|
|
122
|
+
current.splice(index, 0, value);
|
|
123
|
+
}
|
|
77
124
|
} else {
|
|
78
125
|
current[lastSegment] = value;
|
|
79
126
|
}
|
|
80
127
|
}
|
|
128
|
+
function removeByPath(obj, path) {
|
|
129
|
+
const segments = parseJsonPointer(path);
|
|
130
|
+
if (segments.length === 0) return;
|
|
131
|
+
let current = obj;
|
|
132
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
133
|
+
const segment = segments[i];
|
|
134
|
+
if (Array.isArray(current)) {
|
|
135
|
+
const index = parseInt(segment, 10);
|
|
136
|
+
if (current[index] === void 0 || typeof current[index] !== "object") {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
current = current[index];
|
|
140
|
+
} else {
|
|
141
|
+
if (!(segment in current) || typeof current[segment] !== "object") {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
current = current[segment];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const lastSegment = segments[segments.length - 1];
|
|
148
|
+
if (Array.isArray(current)) {
|
|
149
|
+
const index = parseInt(lastSegment, 10);
|
|
150
|
+
if (index >= 0 && index < current.length) {
|
|
151
|
+
current.splice(index, 1);
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
delete current[lastSegment];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function deepEqual(a, b) {
|
|
158
|
+
if (a === b) return true;
|
|
159
|
+
if (a === null || b === null) return false;
|
|
160
|
+
if (typeof a !== typeof b) return false;
|
|
161
|
+
if (typeof a !== "object") return false;
|
|
162
|
+
if (Array.isArray(a)) {
|
|
163
|
+
if (!Array.isArray(b)) return false;
|
|
164
|
+
if (a.length !== b.length) return false;
|
|
165
|
+
return a.every((item, i) => deepEqual(item, b[i]));
|
|
166
|
+
}
|
|
167
|
+
const aObj = a;
|
|
168
|
+
const bObj = b;
|
|
169
|
+
const aKeys = Object.keys(aObj);
|
|
170
|
+
const bKeys = Object.keys(bObj);
|
|
171
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
172
|
+
return aKeys.every((key) => deepEqual(aObj[key], bObj[key]));
|
|
173
|
+
}
|
|
81
174
|
function findFormValue(fieldName, params, data) {
|
|
82
175
|
if (params?.[fieldName] !== void 0) {
|
|
83
176
|
const val = params[fieldName];
|
|
@@ -126,10 +219,38 @@ function parseSpecStreamLine(line) {
|
|
|
126
219
|
}
|
|
127
220
|
}
|
|
128
221
|
function applySpecStreamPatch(obj, patch) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
222
|
+
switch (patch.op) {
|
|
223
|
+
case "add":
|
|
224
|
+
addByPath(obj, patch.path, patch.value);
|
|
225
|
+
break;
|
|
226
|
+
case "replace":
|
|
227
|
+
setByPath(obj, patch.path, patch.value);
|
|
228
|
+
break;
|
|
229
|
+
case "remove":
|
|
230
|
+
removeByPath(obj, patch.path);
|
|
231
|
+
break;
|
|
232
|
+
case "move": {
|
|
233
|
+
if (!patch.from) break;
|
|
234
|
+
const moveValue = getByPath(obj, patch.from);
|
|
235
|
+
removeByPath(obj, patch.from);
|
|
236
|
+
addByPath(obj, patch.path, moveValue);
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
case "copy": {
|
|
240
|
+
if (!patch.from) break;
|
|
241
|
+
const copyValue = getByPath(obj, patch.from);
|
|
242
|
+
addByPath(obj, patch.path, copyValue);
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
case "test": {
|
|
246
|
+
const actual = getByPath(obj, patch.path);
|
|
247
|
+
if (!deepEqual(actual, patch.value)) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
`Test operation failed: value at "${patch.path}" does not match`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
133
254
|
}
|
|
134
255
|
return obj;
|
|
135
256
|
}
|
|
@@ -872,10 +993,10 @@ function generatePrompt(catalog, options) {
|
|
|
872
993
|
lines.push("");
|
|
873
994
|
lines.push("Example output (each line is a separate JSON object):");
|
|
874
995
|
lines.push("");
|
|
875
|
-
lines.push(`{"op":"
|
|
876
|
-
{"op":"
|
|
877
|
-
{"op":"
|
|
878
|
-
{"op":"
|
|
996
|
+
lines.push(`{"op":"add","path":"/root","value":"card-1"}
|
|
997
|
+
{"op":"add","path":"/elements/card-1","value":{"type":"Card","props":{"title":"Dashboard"},"children":["metric-1","chart-1"]}}
|
|
998
|
+
{"op":"add","path":"/elements/metric-1","value":{"type":"Metric","props":{"label":"Revenue","valuePath":"analytics.revenue","format":"currency"},"children":[]}}
|
|
999
|
+
{"op":"add","path":"/elements/chart-1","value":{"type":"Chart","props":{"type":"bar","dataPath":"analytics.salesByRegion"},"children":[]}}`);
|
|
879
1000
|
lines.push("");
|
|
880
1001
|
const components = catalog.data.components;
|
|
881
1002
|
if (components) {
|
|
@@ -902,12 +1023,11 @@ function generatePrompt(catalog, options) {
|
|
|
902
1023
|
lines.push("RULES:");
|
|
903
1024
|
const baseRules = [
|
|
904
1025
|
"Output ONLY JSONL patches - one JSON object per line, no markdown, no code fences",
|
|
905
|
-
'First line sets root: {"op":"
|
|
906
|
-
'Then add each element: {"op":"
|
|
1026
|
+
'First line sets root: {"op":"add","path":"/root","value":"<root-key>"}',
|
|
1027
|
+
'Then add each element: {"op":"add","path":"/elements/<key>","value":{...}}',
|
|
907
1028
|
"ONLY use components listed above",
|
|
908
|
-
"Each element value needs:
|
|
909
|
-
"Use unique keys (e.g., 'header', 'metric-1', 'chart-revenue')"
|
|
910
|
-
"Root element's parentKey is empty string, children reference their parent's key"
|
|
1029
|
+
"Each element value needs: type, props, children (array of child keys)",
|
|
1030
|
+
"Use unique keys for the element map entries (e.g., 'header', 'metric-1', 'chart-revenue')"
|
|
911
1031
|
];
|
|
912
1032
|
const allRules = [...baseRules, ...customRules];
|
|
913
1033
|
allRules.forEach((rule, i) => {
|
|
@@ -1063,22 +1183,18 @@ function createCatalog(config) {
|
|
|
1063
1183
|
const componentSchemas = componentNames.map((componentName) => {
|
|
1064
1184
|
const def = components[componentName];
|
|
1065
1185
|
return z6.object({
|
|
1066
|
-
key: z6.string(),
|
|
1067
1186
|
type: z6.literal(componentName),
|
|
1068
1187
|
props: def.props,
|
|
1069
1188
|
children: z6.array(z6.string()).optional(),
|
|
1070
|
-
parentKey: z6.string().nullable().optional(),
|
|
1071
1189
|
visible: VisibilityConditionSchema.optional()
|
|
1072
1190
|
});
|
|
1073
1191
|
});
|
|
1074
1192
|
let elementSchema;
|
|
1075
1193
|
if (componentSchemas.length === 0) {
|
|
1076
1194
|
elementSchema = z6.object({
|
|
1077
|
-
key: z6.string(),
|
|
1078
1195
|
type: z6.string(),
|
|
1079
1196
|
props: z6.record(z6.string(), z6.unknown()),
|
|
1080
1197
|
children: z6.array(z6.string()).optional(),
|
|
1081
|
-
parentKey: z6.string().nullable().optional(),
|
|
1082
1198
|
visible: VisibilityConditionSchema.optional()
|
|
1083
1199
|
});
|
|
1084
1200
|
} else if (componentSchemas.length === 1) {
|
|
@@ -1299,21 +1415,21 @@ function generateSystemPrompt(catalog, options = {}) {
|
|
|
1299
1415
|
}
|
|
1300
1416
|
lines.push("");
|
|
1301
1417
|
}
|
|
1302
|
-
lines.push("OUTPUT FORMAT (JSONL):");
|
|
1303
|
-
lines.push('{"op":"
|
|
1418
|
+
lines.push("OUTPUT FORMAT (JSONL, RFC 6902 JSON Patch):");
|
|
1419
|
+
lines.push('{"op":"add","path":"/root","value":"element-key"}');
|
|
1304
1420
|
lines.push(
|
|
1305
|
-
'{"op":"add","path":"/elements/key","value":{"
|
|
1421
|
+
'{"op":"add","path":"/elements/key","value":{"type":"...","props":{...},"children":[...]}}'
|
|
1306
1422
|
);
|
|
1307
1423
|
lines.push('{"op":"remove","path":"/elements/key"}');
|
|
1308
1424
|
lines.push("");
|
|
1309
1425
|
lines.push("RULES:");
|
|
1310
1426
|
const baseRules = [
|
|
1311
|
-
|
|
1312
|
-
|
|
1427
|
+
'First line sets /root to root element key: {"op":"add","path":"/root","value":"<key>"}',
|
|
1428
|
+
'Add elements with /elements/{key}: {"op":"add","path":"/elements/<key>","value":{...}}',
|
|
1313
1429
|
"Remove elements with op:remove - also update the parent's children array to exclude the removed key",
|
|
1314
1430
|
"Children array contains string keys, not objects",
|
|
1315
1431
|
"Parent first, then children",
|
|
1316
|
-
"Each element needs:
|
|
1432
|
+
"Each element needs: type, props",
|
|
1317
1433
|
"ONLY use props listed above - never invent new props"
|
|
1318
1434
|
];
|
|
1319
1435
|
const allRules = [...baseRules, ...customRules];
|
|
@@ -1343,6 +1459,7 @@ export {
|
|
|
1343
1459
|
ValidationConfigSchema,
|
|
1344
1460
|
VisibilityConditionSchema,
|
|
1345
1461
|
action,
|
|
1462
|
+
addByPath,
|
|
1346
1463
|
applySpecStreamPatch,
|
|
1347
1464
|
builtInValidationFunctions,
|
|
1348
1465
|
check,
|
|
@@ -1360,6 +1477,7 @@ export {
|
|
|
1360
1477
|
getByPath,
|
|
1361
1478
|
interpolateString,
|
|
1362
1479
|
parseSpecStreamLine,
|
|
1480
|
+
removeByPath,
|
|
1363
1481
|
resolveAction,
|
|
1364
1482
|
resolveDynamicValue,
|
|
1365
1483
|
runValidation,
|