@json-render/core 0.3.0 → 0.4.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 +139 -125
- package/dist/index.d.mts +313 -8
- package/dist/index.d.ts +313 -8
- package/dist/index.js +682 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +674 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33,15 +33,23 @@ __export(index_exports, {
|
|
|
33
33
|
ValidationConfigSchema: () => ValidationConfigSchema,
|
|
34
34
|
VisibilityConditionSchema: () => VisibilityConditionSchema,
|
|
35
35
|
action: () => action,
|
|
36
|
+
applySpecStreamPatch: () => applySpecStreamPatch,
|
|
36
37
|
builtInValidationFunctions: () => builtInValidationFunctions,
|
|
37
38
|
check: () => check,
|
|
39
|
+
compileSpecStream: () => compileSpecStream,
|
|
38
40
|
createCatalog: () => createCatalog,
|
|
41
|
+
createSpecStreamCompiler: () => createSpecStreamCompiler,
|
|
42
|
+
defineCatalog: () => defineCatalog,
|
|
43
|
+
defineSchema: () => defineSchema,
|
|
39
44
|
evaluateLogicExpression: () => evaluateLogicExpression,
|
|
40
45
|
evaluateVisibility: () => evaluateVisibility,
|
|
41
46
|
executeAction: () => executeAction,
|
|
47
|
+
findFormValue: () => findFormValue,
|
|
42
48
|
generateCatalogPrompt: () => generateCatalogPrompt,
|
|
49
|
+
generateSystemPrompt: () => generateSystemPrompt,
|
|
43
50
|
getByPath: () => getByPath,
|
|
44
51
|
interpolateString: () => interpolateString,
|
|
52
|
+
parseSpecStreamLine: () => parseSpecStreamLine,
|
|
45
53
|
resolveAction: () => resolveAction,
|
|
46
54
|
resolveDynamicValue: () => resolveDynamicValue,
|
|
47
55
|
runValidation: () => runValidation,
|
|
@@ -99,19 +107,154 @@ function getByPath(obj, path) {
|
|
|
99
107
|
}
|
|
100
108
|
return current;
|
|
101
109
|
}
|
|
110
|
+
function isNumericIndex(str) {
|
|
111
|
+
return /^\d+$/.test(str);
|
|
112
|
+
}
|
|
102
113
|
function setByPath(obj, path, value) {
|
|
103
114
|
const segments = path.startsWith("/") ? path.slice(1).split("/") : path.split("/");
|
|
104
115
|
if (segments.length === 0) return;
|
|
105
116
|
let current = obj;
|
|
106
117
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
107
118
|
const segment = segments[i];
|
|
108
|
-
|
|
109
|
-
|
|
119
|
+
const nextSegment = segments[i + 1];
|
|
120
|
+
const nextIsNumeric = nextSegment !== void 0 && isNumericIndex(nextSegment);
|
|
121
|
+
if (Array.isArray(current)) {
|
|
122
|
+
const index = parseInt(segment, 10);
|
|
123
|
+
if (current[index] === void 0 || typeof current[index] !== "object") {
|
|
124
|
+
current[index] = nextIsNumeric ? [] : {};
|
|
125
|
+
}
|
|
126
|
+
current = current[index];
|
|
127
|
+
} else {
|
|
128
|
+
if (!(segment in current) || typeof current[segment] !== "object") {
|
|
129
|
+
current[segment] = nextIsNumeric ? [] : {};
|
|
130
|
+
}
|
|
131
|
+
current = current[segment];
|
|
110
132
|
}
|
|
111
|
-
current = current[segment];
|
|
112
133
|
}
|
|
113
134
|
const lastSegment = segments[segments.length - 1];
|
|
114
|
-
current
|
|
135
|
+
if (Array.isArray(current)) {
|
|
136
|
+
const index = parseInt(lastSegment, 10);
|
|
137
|
+
current[index] = value;
|
|
138
|
+
} else {
|
|
139
|
+
current[lastSegment] = value;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function findFormValue(fieldName, params, data) {
|
|
143
|
+
if (params?.[fieldName] !== void 0) {
|
|
144
|
+
const val = params[fieldName];
|
|
145
|
+
if (typeof val !== "string" || !val.includes(".")) {
|
|
146
|
+
return val;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (params) {
|
|
150
|
+
for (const key of Object.keys(params)) {
|
|
151
|
+
if (key.endsWith(`.${fieldName}`)) {
|
|
152
|
+
const val = params[key];
|
|
153
|
+
if (typeof val !== "string" || !val.includes(".")) {
|
|
154
|
+
return val;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (data) {
|
|
160
|
+
for (const key of Object.keys(data)) {
|
|
161
|
+
if (key === fieldName || key.endsWith(`.${fieldName}`)) {
|
|
162
|
+
return data[key];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const prefixes = ["form", "newCustomer", "customer", ""];
|
|
166
|
+
for (const prefix of prefixes) {
|
|
167
|
+
const path = prefix ? `${prefix}/${fieldName}` : fieldName;
|
|
168
|
+
const val = getByPath(data, path);
|
|
169
|
+
if (val !== void 0) {
|
|
170
|
+
return val;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return void 0;
|
|
175
|
+
}
|
|
176
|
+
function parseSpecStreamLine(line) {
|
|
177
|
+
const trimmed = line.trim();
|
|
178
|
+
if (!trimmed || !trimmed.startsWith("{")) return null;
|
|
179
|
+
try {
|
|
180
|
+
const patch = JSON.parse(trimmed);
|
|
181
|
+
if (patch.op && patch.path !== void 0) {
|
|
182
|
+
return patch;
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function applySpecStreamPatch(obj, patch) {
|
|
190
|
+
if (patch.op === "set" || patch.op === "add" || patch.op === "replace") {
|
|
191
|
+
setByPath(obj, patch.path, patch.value);
|
|
192
|
+
} else if (patch.op === "remove") {
|
|
193
|
+
setByPath(obj, patch.path, void 0);
|
|
194
|
+
}
|
|
195
|
+
return obj;
|
|
196
|
+
}
|
|
197
|
+
function compileSpecStream(stream, initial = {}) {
|
|
198
|
+
const lines = stream.split("\n");
|
|
199
|
+
const result = { ...initial };
|
|
200
|
+
for (const line of lines) {
|
|
201
|
+
const patch = parseSpecStreamLine(line);
|
|
202
|
+
if (patch) {
|
|
203
|
+
applySpecStreamPatch(result, patch);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
function createSpecStreamCompiler(initial = {}) {
|
|
209
|
+
let result = { ...initial };
|
|
210
|
+
let buffer = "";
|
|
211
|
+
const appliedPatches = [];
|
|
212
|
+
const processedLines = /* @__PURE__ */ new Set();
|
|
213
|
+
return {
|
|
214
|
+
push(chunk) {
|
|
215
|
+
buffer += chunk;
|
|
216
|
+
const newPatches = [];
|
|
217
|
+
const lines = buffer.split("\n");
|
|
218
|
+
buffer = lines.pop() || "";
|
|
219
|
+
for (const line of lines) {
|
|
220
|
+
const trimmed = line.trim();
|
|
221
|
+
if (!trimmed || processedLines.has(trimmed)) continue;
|
|
222
|
+
processedLines.add(trimmed);
|
|
223
|
+
const patch = parseSpecStreamLine(trimmed);
|
|
224
|
+
if (patch) {
|
|
225
|
+
applySpecStreamPatch(result, patch);
|
|
226
|
+
appliedPatches.push(patch);
|
|
227
|
+
newPatches.push(patch);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (newPatches.length > 0) {
|
|
231
|
+
result = { ...result };
|
|
232
|
+
}
|
|
233
|
+
return { result, newPatches };
|
|
234
|
+
},
|
|
235
|
+
getResult() {
|
|
236
|
+
if (buffer.trim()) {
|
|
237
|
+
const patch = parseSpecStreamLine(buffer);
|
|
238
|
+
if (patch && !processedLines.has(buffer.trim())) {
|
|
239
|
+
processedLines.add(buffer.trim());
|
|
240
|
+
applySpecStreamPatch(result, patch);
|
|
241
|
+
appliedPatches.push(patch);
|
|
242
|
+
result = { ...result };
|
|
243
|
+
}
|
|
244
|
+
buffer = "";
|
|
245
|
+
}
|
|
246
|
+
return result;
|
|
247
|
+
},
|
|
248
|
+
getPatches() {
|
|
249
|
+
return [...appliedPatches];
|
|
250
|
+
},
|
|
251
|
+
reset(newInitial = {}) {
|
|
252
|
+
result = { ...newInitial };
|
|
253
|
+
buffer = "";
|
|
254
|
+
appliedPatches.length = 0;
|
|
255
|
+
processedLines.clear();
|
|
256
|
+
}
|
|
257
|
+
};
|
|
115
258
|
}
|
|
116
259
|
|
|
117
260
|
// src/visibility.ts
|
|
@@ -606,8 +749,367 @@ var check = {
|
|
|
606
749
|
})
|
|
607
750
|
};
|
|
608
751
|
|
|
609
|
-
// src/
|
|
752
|
+
// src/schema.ts
|
|
610
753
|
var import_zod5 = require("zod");
|
|
754
|
+
function createBuilder() {
|
|
755
|
+
return {
|
|
756
|
+
string: () => ({ kind: "string" }),
|
|
757
|
+
number: () => ({ kind: "number" }),
|
|
758
|
+
boolean: () => ({ kind: "boolean" }),
|
|
759
|
+
array: (item) => ({ kind: "array", inner: item }),
|
|
760
|
+
object: (shape) => ({ kind: "object", inner: shape }),
|
|
761
|
+
record: (value) => ({ kind: "record", inner: value }),
|
|
762
|
+
any: () => ({ kind: "any" }),
|
|
763
|
+
zod: () => ({ kind: "zod" }),
|
|
764
|
+
ref: (path) => ({ kind: "ref", inner: path }),
|
|
765
|
+
propsOf: (path) => ({ kind: "propsOf", inner: path }),
|
|
766
|
+
map: (entryShape) => ({ kind: "map", inner: entryShape }),
|
|
767
|
+
optional: () => ({ optional: true })
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
function defineSchema(builder, options) {
|
|
771
|
+
const s = createBuilder();
|
|
772
|
+
const definition = builder(s);
|
|
773
|
+
return {
|
|
774
|
+
definition,
|
|
775
|
+
promptTemplate: options?.promptTemplate,
|
|
776
|
+
createCatalog(catalog) {
|
|
777
|
+
return createCatalogFromSchema(this, catalog);
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
function createCatalogFromSchema(schema, catalogData) {
|
|
782
|
+
const components = catalogData.components;
|
|
783
|
+
const actions = catalogData.actions;
|
|
784
|
+
const componentNames = components ? Object.keys(components) : [];
|
|
785
|
+
const actionNames = actions ? Object.keys(actions) : [];
|
|
786
|
+
const zodSchema = buildZodSchemaFromDefinition(
|
|
787
|
+
schema.definition,
|
|
788
|
+
catalogData
|
|
789
|
+
);
|
|
790
|
+
return {
|
|
791
|
+
schema,
|
|
792
|
+
data: catalogData,
|
|
793
|
+
componentNames,
|
|
794
|
+
actionNames,
|
|
795
|
+
prompt(options = {}) {
|
|
796
|
+
return generatePrompt(this, options);
|
|
797
|
+
},
|
|
798
|
+
jsonSchema() {
|
|
799
|
+
return zodToJsonSchema(zodSchema);
|
|
800
|
+
},
|
|
801
|
+
validate(spec) {
|
|
802
|
+
const result = zodSchema.safeParse(spec);
|
|
803
|
+
if (result.success) {
|
|
804
|
+
return {
|
|
805
|
+
success: true,
|
|
806
|
+
data: result.data
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
return { success: false, error: result.error };
|
|
810
|
+
},
|
|
811
|
+
zodSchema() {
|
|
812
|
+
return zodSchema;
|
|
813
|
+
},
|
|
814
|
+
get _specType() {
|
|
815
|
+
throw new Error("_specType is only for type inference");
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
function buildZodSchemaFromDefinition(definition, catalogData) {
|
|
820
|
+
return buildZodType(definition.spec, catalogData);
|
|
821
|
+
}
|
|
822
|
+
function buildZodType(schemaType, catalogData) {
|
|
823
|
+
switch (schemaType.kind) {
|
|
824
|
+
case "string":
|
|
825
|
+
return import_zod5.z.string();
|
|
826
|
+
case "number":
|
|
827
|
+
return import_zod5.z.number();
|
|
828
|
+
case "boolean":
|
|
829
|
+
return import_zod5.z.boolean();
|
|
830
|
+
case "any":
|
|
831
|
+
return import_zod5.z.any();
|
|
832
|
+
case "array": {
|
|
833
|
+
const inner = buildZodType(schemaType.inner, catalogData);
|
|
834
|
+
return import_zod5.z.array(inner);
|
|
835
|
+
}
|
|
836
|
+
case "object": {
|
|
837
|
+
const shape = schemaType.inner;
|
|
838
|
+
const zodShape = {};
|
|
839
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
840
|
+
let zodType = buildZodType(value, catalogData);
|
|
841
|
+
if (value.optional) {
|
|
842
|
+
zodType = zodType.optional();
|
|
843
|
+
}
|
|
844
|
+
zodShape[key] = zodType;
|
|
845
|
+
}
|
|
846
|
+
return import_zod5.z.object(zodShape);
|
|
847
|
+
}
|
|
848
|
+
case "record": {
|
|
849
|
+
const inner = buildZodType(schemaType.inner, catalogData);
|
|
850
|
+
return import_zod5.z.record(import_zod5.z.string(), inner);
|
|
851
|
+
}
|
|
852
|
+
case "ref": {
|
|
853
|
+
const path = schemaType.inner;
|
|
854
|
+
const keys = getKeysFromPath(path, catalogData);
|
|
855
|
+
if (keys.length === 0) {
|
|
856
|
+
return import_zod5.z.string();
|
|
857
|
+
}
|
|
858
|
+
if (keys.length === 1) {
|
|
859
|
+
return import_zod5.z.literal(keys[0]);
|
|
860
|
+
}
|
|
861
|
+
return import_zod5.z.enum(keys);
|
|
862
|
+
}
|
|
863
|
+
case "propsOf": {
|
|
864
|
+
const path = schemaType.inner;
|
|
865
|
+
const propsSchemas = getPropsFromPath(path, catalogData);
|
|
866
|
+
if (propsSchemas.length === 0) {
|
|
867
|
+
return import_zod5.z.record(import_zod5.z.string(), import_zod5.z.unknown());
|
|
868
|
+
}
|
|
869
|
+
if (propsSchemas.length === 1) {
|
|
870
|
+
return propsSchemas[0];
|
|
871
|
+
}
|
|
872
|
+
return import_zod5.z.record(import_zod5.z.string(), import_zod5.z.unknown());
|
|
873
|
+
}
|
|
874
|
+
default:
|
|
875
|
+
return import_zod5.z.unknown();
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
function getKeysFromPath(path, catalogData) {
|
|
879
|
+
const parts = path.split(".");
|
|
880
|
+
let current = { catalog: catalogData };
|
|
881
|
+
for (const part of parts) {
|
|
882
|
+
if (current && typeof current === "object") {
|
|
883
|
+
current = current[part];
|
|
884
|
+
} else {
|
|
885
|
+
return [];
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
if (current && typeof current === "object") {
|
|
889
|
+
return Object.keys(current);
|
|
890
|
+
}
|
|
891
|
+
return [];
|
|
892
|
+
}
|
|
893
|
+
function getPropsFromPath(path, catalogData) {
|
|
894
|
+
const parts = path.split(".");
|
|
895
|
+
let current = { catalog: catalogData };
|
|
896
|
+
for (const part of parts) {
|
|
897
|
+
if (current && typeof current === "object") {
|
|
898
|
+
current = current[part];
|
|
899
|
+
} else {
|
|
900
|
+
return [];
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
if (current && typeof current === "object") {
|
|
904
|
+
return Object.values(current).map((entry) => entry.props).filter((props) => props !== void 0);
|
|
905
|
+
}
|
|
906
|
+
return [];
|
|
907
|
+
}
|
|
908
|
+
function generatePrompt(catalog, options) {
|
|
909
|
+
if (catalog.schema.promptTemplate) {
|
|
910
|
+
const context = {
|
|
911
|
+
catalog: catalog.data,
|
|
912
|
+
componentNames: catalog.componentNames,
|
|
913
|
+
actionNames: catalog.actionNames,
|
|
914
|
+
options,
|
|
915
|
+
formatZodType
|
|
916
|
+
};
|
|
917
|
+
return catalog.schema.promptTemplate(context);
|
|
918
|
+
}
|
|
919
|
+
const {
|
|
920
|
+
system = "You are a UI generator that outputs JSON.",
|
|
921
|
+
customRules = []
|
|
922
|
+
} = options;
|
|
923
|
+
const lines = [];
|
|
924
|
+
lines.push(system);
|
|
925
|
+
lines.push("");
|
|
926
|
+
lines.push("OUTPUT FORMAT:");
|
|
927
|
+
lines.push(
|
|
928
|
+
"Output JSONL (one JSON object per line) with patches to build a UI tree."
|
|
929
|
+
);
|
|
930
|
+
lines.push(
|
|
931
|
+
"Each line is a JSON patch operation. Start with the root, then add each element."
|
|
932
|
+
);
|
|
933
|
+
lines.push("");
|
|
934
|
+
lines.push("Example output (each line is a separate JSON object):");
|
|
935
|
+
lines.push("");
|
|
936
|
+
lines.push(`{"op":"set","path":"/root","value":"card-1"}
|
|
937
|
+
{"op":"set","path":"/elements/card-1","value":{"key":"card-1","type":"Card","props":{"title":"Dashboard"},"children":["metric-1","chart-1"],"parentKey":""}}
|
|
938
|
+
{"op":"set","path":"/elements/metric-1","value":{"key":"metric-1","type":"Metric","props":{"label":"Revenue","valuePath":"analytics.revenue","format":"currency"},"children":[],"parentKey":"card-1"}}
|
|
939
|
+
{"op":"set","path":"/elements/chart-1","value":{"key":"chart-1","type":"Chart","props":{"type":"bar","dataPath":"analytics.salesByRegion"},"children":[],"parentKey":"card-1"}}`);
|
|
940
|
+
lines.push("");
|
|
941
|
+
const components = catalog.data.components;
|
|
942
|
+
if (components) {
|
|
943
|
+
lines.push(`AVAILABLE COMPONENTS (${catalog.componentNames.length}):`);
|
|
944
|
+
lines.push("");
|
|
945
|
+
for (const [name, def] of Object.entries(components)) {
|
|
946
|
+
const propsStr = def.props ? formatZodType(def.props) : "{}";
|
|
947
|
+
const hasChildren = def.slots && def.slots.length > 0;
|
|
948
|
+
const childrenStr = hasChildren ? " [accepts children]" : "";
|
|
949
|
+
const descStr = def.description ? ` - ${def.description}` : "";
|
|
950
|
+
lines.push(`- ${name}: ${propsStr}${descStr}${childrenStr}`);
|
|
951
|
+
}
|
|
952
|
+
lines.push("");
|
|
953
|
+
}
|
|
954
|
+
const actions = catalog.data.actions;
|
|
955
|
+
if (actions && catalog.actionNames.length > 0) {
|
|
956
|
+
lines.push("AVAILABLE ACTIONS:");
|
|
957
|
+
lines.push("");
|
|
958
|
+
for (const [name, def] of Object.entries(actions)) {
|
|
959
|
+
lines.push(`- ${name}${def.description ? `: ${def.description}` : ""}`);
|
|
960
|
+
}
|
|
961
|
+
lines.push("");
|
|
962
|
+
}
|
|
963
|
+
lines.push("RULES:");
|
|
964
|
+
const baseRules = [
|
|
965
|
+
"Output ONLY JSONL patches - one JSON object per line, no markdown, no code fences",
|
|
966
|
+
'First line sets root: {"op":"set","path":"/root","value":"<root-key>"}',
|
|
967
|
+
'Then add each element: {"op":"set","path":"/elements/<key>","value":{...}}',
|
|
968
|
+
"ONLY use components listed above",
|
|
969
|
+
"Each element value needs: key, type, props, children (array of child keys), parentKey",
|
|
970
|
+
"Use unique keys (e.g., 'header', 'metric-1', 'chart-revenue')",
|
|
971
|
+
"Root element's parentKey is empty string, children reference their parent's key"
|
|
972
|
+
];
|
|
973
|
+
const allRules = [...baseRules, ...customRules];
|
|
974
|
+
allRules.forEach((rule, i) => {
|
|
975
|
+
lines.push(`${i + 1}. ${rule}`);
|
|
976
|
+
});
|
|
977
|
+
return lines.join("\n");
|
|
978
|
+
}
|
|
979
|
+
function getZodTypeName(schema) {
|
|
980
|
+
if (!schema || !schema._def) return "";
|
|
981
|
+
const def = schema._def;
|
|
982
|
+
return def.typeName ?? def.type ?? "";
|
|
983
|
+
}
|
|
984
|
+
function formatZodType(schema) {
|
|
985
|
+
if (!schema || !schema._def) return "unknown";
|
|
986
|
+
const def = schema._def;
|
|
987
|
+
const typeName = getZodTypeName(schema);
|
|
988
|
+
switch (typeName) {
|
|
989
|
+
case "ZodString":
|
|
990
|
+
case "string":
|
|
991
|
+
return "string";
|
|
992
|
+
case "ZodNumber":
|
|
993
|
+
case "number":
|
|
994
|
+
return "number";
|
|
995
|
+
case "ZodBoolean":
|
|
996
|
+
case "boolean":
|
|
997
|
+
return "boolean";
|
|
998
|
+
case "ZodLiteral":
|
|
999
|
+
case "literal":
|
|
1000
|
+
return JSON.stringify(def.value);
|
|
1001
|
+
case "ZodEnum":
|
|
1002
|
+
case "enum": {
|
|
1003
|
+
let values;
|
|
1004
|
+
if (Array.isArray(def.values)) {
|
|
1005
|
+
values = def.values;
|
|
1006
|
+
} else if (def.entries && typeof def.entries === "object") {
|
|
1007
|
+
values = Object.values(def.entries);
|
|
1008
|
+
} else {
|
|
1009
|
+
return "enum";
|
|
1010
|
+
}
|
|
1011
|
+
return values.map((v) => `"${v}"`).join(" | ");
|
|
1012
|
+
}
|
|
1013
|
+
case "ZodArray":
|
|
1014
|
+
case "array": {
|
|
1015
|
+
const inner = def.type ?? def.element;
|
|
1016
|
+
return inner ? `Array<${formatZodType(inner)}>` : "Array<unknown>";
|
|
1017
|
+
}
|
|
1018
|
+
case "ZodObject":
|
|
1019
|
+
case "object": {
|
|
1020
|
+
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
1021
|
+
if (!shape) return "object";
|
|
1022
|
+
const props = Object.entries(shape).map(([key, value]) => {
|
|
1023
|
+
const innerTypeName = getZodTypeName(value);
|
|
1024
|
+
const isOptional = innerTypeName === "ZodOptional" || innerTypeName === "ZodNullable" || innerTypeName === "optional" || innerTypeName === "nullable";
|
|
1025
|
+
return `${key}${isOptional ? "?" : ""}: ${formatZodType(value)}`;
|
|
1026
|
+
}).join(", ");
|
|
1027
|
+
return `{ ${props} }`;
|
|
1028
|
+
}
|
|
1029
|
+
case "ZodOptional":
|
|
1030
|
+
case "optional":
|
|
1031
|
+
case "ZodNullable":
|
|
1032
|
+
case "nullable": {
|
|
1033
|
+
const inner = def.innerType ?? def.wrapped;
|
|
1034
|
+
return inner ? formatZodType(inner) : "unknown";
|
|
1035
|
+
}
|
|
1036
|
+
case "ZodUnion":
|
|
1037
|
+
case "union": {
|
|
1038
|
+
const options = def.options;
|
|
1039
|
+
return options ? options.map((opt) => formatZodType(opt)).join(" | ") : "unknown";
|
|
1040
|
+
}
|
|
1041
|
+
default:
|
|
1042
|
+
return "unknown";
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
function zodToJsonSchema(schema) {
|
|
1046
|
+
const def = schema._def;
|
|
1047
|
+
const typeName = def.typeName ?? "";
|
|
1048
|
+
switch (typeName) {
|
|
1049
|
+
case "ZodString":
|
|
1050
|
+
return { type: "string" };
|
|
1051
|
+
case "ZodNumber":
|
|
1052
|
+
return { type: "number" };
|
|
1053
|
+
case "ZodBoolean":
|
|
1054
|
+
return { type: "boolean" };
|
|
1055
|
+
case "ZodLiteral":
|
|
1056
|
+
return { const: def.value };
|
|
1057
|
+
case "ZodEnum":
|
|
1058
|
+
return { enum: def.values };
|
|
1059
|
+
case "ZodArray": {
|
|
1060
|
+
const inner = def.type;
|
|
1061
|
+
return {
|
|
1062
|
+
type: "array",
|
|
1063
|
+
items: inner ? zodToJsonSchema(inner) : {}
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
case "ZodObject": {
|
|
1067
|
+
const shape = def.shape?.();
|
|
1068
|
+
if (!shape) return { type: "object" };
|
|
1069
|
+
const properties = {};
|
|
1070
|
+
const required = [];
|
|
1071
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
1072
|
+
properties[key] = zodToJsonSchema(value);
|
|
1073
|
+
const innerDef = value._def;
|
|
1074
|
+
if (innerDef.typeName !== "ZodOptional" && innerDef.typeName !== "ZodNullable") {
|
|
1075
|
+
required.push(key);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return {
|
|
1079
|
+
type: "object",
|
|
1080
|
+
properties,
|
|
1081
|
+
required: required.length > 0 ? required : void 0,
|
|
1082
|
+
additionalProperties: false
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
case "ZodRecord": {
|
|
1086
|
+
const valueType = def.valueType;
|
|
1087
|
+
return {
|
|
1088
|
+
type: "object",
|
|
1089
|
+
additionalProperties: valueType ? zodToJsonSchema(valueType) : true
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
case "ZodOptional":
|
|
1093
|
+
case "ZodNullable": {
|
|
1094
|
+
const inner = def.innerType;
|
|
1095
|
+
return inner ? zodToJsonSchema(inner) : {};
|
|
1096
|
+
}
|
|
1097
|
+
case "ZodUnion": {
|
|
1098
|
+
const options = def.options;
|
|
1099
|
+
return options ? { anyOf: options.map(zodToJsonSchema) } : {};
|
|
1100
|
+
}
|
|
1101
|
+
case "ZodAny":
|
|
1102
|
+
return {};
|
|
1103
|
+
default:
|
|
1104
|
+
return {};
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
function defineCatalog(schema, catalog) {
|
|
1108
|
+
return schema.createCatalog(catalog);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// src/catalog.ts
|
|
1112
|
+
var import_zod6 = require("zod");
|
|
611
1113
|
function createCatalog(config) {
|
|
612
1114
|
const {
|
|
613
1115
|
name = "unnamed",
|
|
@@ -621,37 +1123,37 @@ function createCatalog(config) {
|
|
|
621
1123
|
const functionNames = Object.keys(functions);
|
|
622
1124
|
const componentSchemas = componentNames.map((componentName) => {
|
|
623
1125
|
const def = components[componentName];
|
|
624
|
-
return
|
|
625
|
-
key:
|
|
626
|
-
type:
|
|
1126
|
+
return import_zod6.z.object({
|
|
1127
|
+
key: import_zod6.z.string(),
|
|
1128
|
+
type: import_zod6.z.literal(componentName),
|
|
627
1129
|
props: def.props,
|
|
628
|
-
children:
|
|
629
|
-
parentKey:
|
|
1130
|
+
children: import_zod6.z.array(import_zod6.z.string()).optional(),
|
|
1131
|
+
parentKey: import_zod6.z.string().nullable().optional(),
|
|
630
1132
|
visible: VisibilityConditionSchema.optional()
|
|
631
1133
|
});
|
|
632
1134
|
});
|
|
633
1135
|
let elementSchema;
|
|
634
1136
|
if (componentSchemas.length === 0) {
|
|
635
|
-
elementSchema =
|
|
636
|
-
key:
|
|
637
|
-
type:
|
|
638
|
-
props:
|
|
639
|
-
children:
|
|
640
|
-
parentKey:
|
|
1137
|
+
elementSchema = import_zod6.z.object({
|
|
1138
|
+
key: import_zod6.z.string(),
|
|
1139
|
+
type: import_zod6.z.string(),
|
|
1140
|
+
props: import_zod6.z.record(import_zod6.z.string(), import_zod6.z.unknown()),
|
|
1141
|
+
children: import_zod6.z.array(import_zod6.z.string()).optional(),
|
|
1142
|
+
parentKey: import_zod6.z.string().nullable().optional(),
|
|
641
1143
|
visible: VisibilityConditionSchema.optional()
|
|
642
1144
|
});
|
|
643
1145
|
} else if (componentSchemas.length === 1) {
|
|
644
1146
|
elementSchema = componentSchemas[0];
|
|
645
1147
|
} else {
|
|
646
|
-
elementSchema =
|
|
1148
|
+
elementSchema = import_zod6.z.discriminatedUnion("type", [
|
|
647
1149
|
componentSchemas[0],
|
|
648
1150
|
componentSchemas[1],
|
|
649
1151
|
...componentSchemas.slice(2)
|
|
650
1152
|
]);
|
|
651
1153
|
}
|
|
652
|
-
const
|
|
653
|
-
root:
|
|
654
|
-
elements:
|
|
1154
|
+
const specSchema = import_zod6.z.object({
|
|
1155
|
+
root: import_zod6.z.string(),
|
|
1156
|
+
elements: import_zod6.z.record(import_zod6.z.string(), elementSchema)
|
|
655
1157
|
});
|
|
656
1158
|
return {
|
|
657
1159
|
name,
|
|
@@ -663,7 +1165,7 @@ function createCatalog(config) {
|
|
|
663
1165
|
actions,
|
|
664
1166
|
functions,
|
|
665
1167
|
elementSchema,
|
|
666
|
-
|
|
1168
|
+
specSchema,
|
|
667
1169
|
hasComponent(type) {
|
|
668
1170
|
return type in components;
|
|
669
1171
|
},
|
|
@@ -680,8 +1182,8 @@ function createCatalog(config) {
|
|
|
680
1182
|
}
|
|
681
1183
|
return { success: false, error: result.error };
|
|
682
1184
|
},
|
|
683
|
-
|
|
684
|
-
const result =
|
|
1185
|
+
validateSpec(spec) {
|
|
1186
|
+
const result = specSchema.safeParse(spec);
|
|
685
1187
|
if (result.success) {
|
|
686
1188
|
return { success: true, data: result.data };
|
|
687
1189
|
}
|
|
@@ -737,6 +1239,155 @@ function generateCatalogPrompt(catalog) {
|
|
|
737
1239
|
lines.push("");
|
|
738
1240
|
return lines.join("\n");
|
|
739
1241
|
}
|
|
1242
|
+
function formatZodType2(schema, isOptional = false) {
|
|
1243
|
+
const def = schema._def;
|
|
1244
|
+
const typeName = def.typeName ?? "";
|
|
1245
|
+
let result;
|
|
1246
|
+
switch (typeName) {
|
|
1247
|
+
case "ZodString":
|
|
1248
|
+
result = "string";
|
|
1249
|
+
break;
|
|
1250
|
+
case "ZodNumber":
|
|
1251
|
+
result = "number";
|
|
1252
|
+
break;
|
|
1253
|
+
case "ZodBoolean":
|
|
1254
|
+
result = "boolean";
|
|
1255
|
+
break;
|
|
1256
|
+
case "ZodLiteral":
|
|
1257
|
+
result = JSON.stringify(def.value);
|
|
1258
|
+
break;
|
|
1259
|
+
case "ZodEnum":
|
|
1260
|
+
result = def.values.map((v) => `"${v}"`).join("|");
|
|
1261
|
+
break;
|
|
1262
|
+
case "ZodNativeEnum":
|
|
1263
|
+
result = Object.values(def.values).map((v) => `"${v}"`).join("|");
|
|
1264
|
+
break;
|
|
1265
|
+
case "ZodArray":
|
|
1266
|
+
result = def.type ? `Array<${formatZodType2(def.type)}>` : "Array<unknown>";
|
|
1267
|
+
break;
|
|
1268
|
+
case "ZodObject": {
|
|
1269
|
+
if (!def.shape) {
|
|
1270
|
+
result = "object";
|
|
1271
|
+
break;
|
|
1272
|
+
}
|
|
1273
|
+
const shape = def.shape();
|
|
1274
|
+
const props = Object.entries(shape).map(([key, value]) => {
|
|
1275
|
+
const innerDef = value._def;
|
|
1276
|
+
const innerOptional = innerDef.typeName === "ZodOptional" || innerDef.typeName === "ZodNullable";
|
|
1277
|
+
return `${key}${innerOptional ? "?" : ""}: ${formatZodType2(value)}`;
|
|
1278
|
+
}).join(", ");
|
|
1279
|
+
result = `{ ${props} }`;
|
|
1280
|
+
break;
|
|
1281
|
+
}
|
|
1282
|
+
case "ZodOptional":
|
|
1283
|
+
return def.innerType ? formatZodType2(def.innerType, true) : "unknown?";
|
|
1284
|
+
case "ZodNullable":
|
|
1285
|
+
return def.innerType ? formatZodType2(def.innerType, true) : "unknown?";
|
|
1286
|
+
case "ZodDefault":
|
|
1287
|
+
return def.innerType ? formatZodType2(def.innerType, isOptional) : "unknown";
|
|
1288
|
+
case "ZodUnion":
|
|
1289
|
+
result = def.options ? def.options.map((opt) => formatZodType2(opt)).join("|") : "unknown";
|
|
1290
|
+
break;
|
|
1291
|
+
case "ZodNull":
|
|
1292
|
+
result = "null";
|
|
1293
|
+
break;
|
|
1294
|
+
case "ZodUndefined":
|
|
1295
|
+
result = "undefined";
|
|
1296
|
+
break;
|
|
1297
|
+
case "ZodAny":
|
|
1298
|
+
result = "any";
|
|
1299
|
+
break;
|
|
1300
|
+
case "ZodUnknown":
|
|
1301
|
+
result = "unknown";
|
|
1302
|
+
break;
|
|
1303
|
+
default:
|
|
1304
|
+
result = "unknown";
|
|
1305
|
+
}
|
|
1306
|
+
return isOptional ? `${result}?` : result;
|
|
1307
|
+
}
|
|
1308
|
+
function extractPropsFromSchema(schema) {
|
|
1309
|
+
const def = schema._def;
|
|
1310
|
+
const typeName = def.typeName ?? "";
|
|
1311
|
+
if (typeName !== "ZodObject" || !def.shape) {
|
|
1312
|
+
return [];
|
|
1313
|
+
}
|
|
1314
|
+
const shape = def.shape();
|
|
1315
|
+
return Object.entries(shape).map(([name, value]) => {
|
|
1316
|
+
const innerDef = value._def;
|
|
1317
|
+
const optional = innerDef.typeName === "ZodOptional" || innerDef.typeName === "ZodNullable";
|
|
1318
|
+
return {
|
|
1319
|
+
name,
|
|
1320
|
+
type: formatZodType2(value),
|
|
1321
|
+
optional
|
|
1322
|
+
};
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
function formatPropsCompact(props) {
|
|
1326
|
+
if (props.length === 0) return "{}";
|
|
1327
|
+
const entries = props.map(
|
|
1328
|
+
(p) => `${p.name}${p.optional ? "?" : ""}: ${p.type}`
|
|
1329
|
+
);
|
|
1330
|
+
return `{ ${entries.join(", ")} }`;
|
|
1331
|
+
}
|
|
1332
|
+
function generateSystemPrompt(catalog, options = {}) {
|
|
1333
|
+
const {
|
|
1334
|
+
system = "You are a UI generator that outputs JSONL (JSON Lines) patches.",
|
|
1335
|
+
customRules = []
|
|
1336
|
+
} = options;
|
|
1337
|
+
const lines = [];
|
|
1338
|
+
lines.push(system);
|
|
1339
|
+
lines.push("");
|
|
1340
|
+
const componentCount = catalog.componentNames.length;
|
|
1341
|
+
lines.push(`AVAILABLE COMPONENTS (${componentCount}):`);
|
|
1342
|
+
lines.push("");
|
|
1343
|
+
for (const name of catalog.componentNames) {
|
|
1344
|
+
const def = catalog.components[name];
|
|
1345
|
+
const props = extractPropsFromSchema(def.props);
|
|
1346
|
+
const propsStr = formatPropsCompact(props);
|
|
1347
|
+
const hasChildrenStr = def.hasChildren ? " Has children." : "";
|
|
1348
|
+
const descStr = def.description ? ` ${def.description}` : "";
|
|
1349
|
+
lines.push(`- ${String(name)}: ${propsStr}${descStr}${hasChildrenStr}`);
|
|
1350
|
+
}
|
|
1351
|
+
lines.push("");
|
|
1352
|
+
if (catalog.actionNames.length > 0) {
|
|
1353
|
+
lines.push("AVAILABLE ACTIONS:");
|
|
1354
|
+
lines.push("");
|
|
1355
|
+
for (const name of catalog.actionNames) {
|
|
1356
|
+
const def = catalog.actions[name];
|
|
1357
|
+
lines.push(
|
|
1358
|
+
`- ${String(name)}${def.description ? `: ${def.description}` : ""}`
|
|
1359
|
+
);
|
|
1360
|
+
}
|
|
1361
|
+
lines.push("");
|
|
1362
|
+
}
|
|
1363
|
+
lines.push("OUTPUT FORMAT (JSONL):");
|
|
1364
|
+
lines.push('{"op":"set","path":"/root","value":"element-key"}');
|
|
1365
|
+
lines.push(
|
|
1366
|
+
'{"op":"add","path":"/elements/key","value":{"key":"...","type":"...","props":{...},"children":[...]}}'
|
|
1367
|
+
);
|
|
1368
|
+
lines.push("");
|
|
1369
|
+
lines.push("RULES:");
|
|
1370
|
+
const baseRules = [
|
|
1371
|
+
"First line sets /root to root element key",
|
|
1372
|
+
"Add elements with /elements/{key}",
|
|
1373
|
+
"Children array contains string keys, not objects",
|
|
1374
|
+
"Parent first, then children",
|
|
1375
|
+
"Each element needs: key, type, props",
|
|
1376
|
+
"ONLY use props listed above - never invent new props"
|
|
1377
|
+
];
|
|
1378
|
+
const allRules = [...baseRules, ...customRules];
|
|
1379
|
+
allRules.forEach((rule, i) => {
|
|
1380
|
+
lines.push(`${i + 1}. ${rule}`);
|
|
1381
|
+
});
|
|
1382
|
+
lines.push("");
|
|
1383
|
+
if (catalog.functionNames.length > 0) {
|
|
1384
|
+
lines.push("CUSTOM VALIDATION FUNCTIONS:");
|
|
1385
|
+
lines.push(catalog.functionNames.map(String).join(", "));
|
|
1386
|
+
lines.push("");
|
|
1387
|
+
}
|
|
1388
|
+
lines.push("Generate JSONL:");
|
|
1389
|
+
return lines.join("\n");
|
|
1390
|
+
}
|
|
740
1391
|
// Annotate the CommonJS export names for ESM import in node:
|
|
741
1392
|
0 && (module.exports = {
|
|
742
1393
|
ActionConfirmSchema,
|
|
@@ -752,15 +1403,23 @@ function generateCatalogPrompt(catalog) {
|
|
|
752
1403
|
ValidationConfigSchema,
|
|
753
1404
|
VisibilityConditionSchema,
|
|
754
1405
|
action,
|
|
1406
|
+
applySpecStreamPatch,
|
|
755
1407
|
builtInValidationFunctions,
|
|
756
1408
|
check,
|
|
1409
|
+
compileSpecStream,
|
|
757
1410
|
createCatalog,
|
|
1411
|
+
createSpecStreamCompiler,
|
|
1412
|
+
defineCatalog,
|
|
1413
|
+
defineSchema,
|
|
758
1414
|
evaluateLogicExpression,
|
|
759
1415
|
evaluateVisibility,
|
|
760
1416
|
executeAction,
|
|
1417
|
+
findFormValue,
|
|
761
1418
|
generateCatalogPrompt,
|
|
1419
|
+
generateSystemPrompt,
|
|
762
1420
|
getByPath,
|
|
763
1421
|
interpolateString,
|
|
1422
|
+
parseSpecStreamLine,
|
|
764
1423
|
resolveAction,
|
|
765
1424
|
resolveDynamicValue,
|
|
766
1425
|
runValidation,
|