@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.mjs
CHANGED
|
@@ -46,19 +46,154 @@ function getByPath(obj, path) {
|
|
|
46
46
|
}
|
|
47
47
|
return current;
|
|
48
48
|
}
|
|
49
|
+
function isNumericIndex(str) {
|
|
50
|
+
return /^\d+$/.test(str);
|
|
51
|
+
}
|
|
49
52
|
function setByPath(obj, path, value) {
|
|
50
53
|
const segments = path.startsWith("/") ? path.slice(1).split("/") : path.split("/");
|
|
51
54
|
if (segments.length === 0) return;
|
|
52
55
|
let current = obj;
|
|
53
56
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
54
57
|
const segment = segments[i];
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
const nextSegment = segments[i + 1];
|
|
59
|
+
const nextIsNumeric = nextSegment !== void 0 && isNumericIndex(nextSegment);
|
|
60
|
+
if (Array.isArray(current)) {
|
|
61
|
+
const index = parseInt(segment, 10);
|
|
62
|
+
if (current[index] === void 0 || typeof current[index] !== "object") {
|
|
63
|
+
current[index] = nextIsNumeric ? [] : {};
|
|
64
|
+
}
|
|
65
|
+
current = current[index];
|
|
66
|
+
} else {
|
|
67
|
+
if (!(segment in current) || typeof current[segment] !== "object") {
|
|
68
|
+
current[segment] = nextIsNumeric ? [] : {};
|
|
69
|
+
}
|
|
70
|
+
current = current[segment];
|
|
57
71
|
}
|
|
58
|
-
current = current[segment];
|
|
59
72
|
}
|
|
60
73
|
const lastSegment = segments[segments.length - 1];
|
|
61
|
-
current
|
|
74
|
+
if (Array.isArray(current)) {
|
|
75
|
+
const index = parseInt(lastSegment, 10);
|
|
76
|
+
current[index] = value;
|
|
77
|
+
} else {
|
|
78
|
+
current[lastSegment] = value;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function findFormValue(fieldName, params, data) {
|
|
82
|
+
if (params?.[fieldName] !== void 0) {
|
|
83
|
+
const val = params[fieldName];
|
|
84
|
+
if (typeof val !== "string" || !val.includes(".")) {
|
|
85
|
+
return val;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (params) {
|
|
89
|
+
for (const key of Object.keys(params)) {
|
|
90
|
+
if (key.endsWith(`.${fieldName}`)) {
|
|
91
|
+
const val = params[key];
|
|
92
|
+
if (typeof val !== "string" || !val.includes(".")) {
|
|
93
|
+
return val;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (data) {
|
|
99
|
+
for (const key of Object.keys(data)) {
|
|
100
|
+
if (key === fieldName || key.endsWith(`.${fieldName}`)) {
|
|
101
|
+
return data[key];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const prefixes = ["form", "newCustomer", "customer", ""];
|
|
105
|
+
for (const prefix of prefixes) {
|
|
106
|
+
const path = prefix ? `${prefix}/${fieldName}` : fieldName;
|
|
107
|
+
const val = getByPath(data, path);
|
|
108
|
+
if (val !== void 0) {
|
|
109
|
+
return val;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
function parseSpecStreamLine(line) {
|
|
116
|
+
const trimmed = line.trim();
|
|
117
|
+
if (!trimmed || !trimmed.startsWith("{")) return null;
|
|
118
|
+
try {
|
|
119
|
+
const patch = JSON.parse(trimmed);
|
|
120
|
+
if (patch.op && patch.path !== void 0) {
|
|
121
|
+
return patch;
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function applySpecStreamPatch(obj, patch) {
|
|
129
|
+
if (patch.op === "set" || patch.op === "add" || patch.op === "replace") {
|
|
130
|
+
setByPath(obj, patch.path, patch.value);
|
|
131
|
+
} else if (patch.op === "remove") {
|
|
132
|
+
setByPath(obj, patch.path, void 0);
|
|
133
|
+
}
|
|
134
|
+
return obj;
|
|
135
|
+
}
|
|
136
|
+
function compileSpecStream(stream, initial = {}) {
|
|
137
|
+
const lines = stream.split("\n");
|
|
138
|
+
const result = { ...initial };
|
|
139
|
+
for (const line of lines) {
|
|
140
|
+
const patch = parseSpecStreamLine(line);
|
|
141
|
+
if (patch) {
|
|
142
|
+
applySpecStreamPatch(result, patch);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
function createSpecStreamCompiler(initial = {}) {
|
|
148
|
+
let result = { ...initial };
|
|
149
|
+
let buffer = "";
|
|
150
|
+
const appliedPatches = [];
|
|
151
|
+
const processedLines = /* @__PURE__ */ new Set();
|
|
152
|
+
return {
|
|
153
|
+
push(chunk) {
|
|
154
|
+
buffer += chunk;
|
|
155
|
+
const newPatches = [];
|
|
156
|
+
const lines = buffer.split("\n");
|
|
157
|
+
buffer = lines.pop() || "";
|
|
158
|
+
for (const line of lines) {
|
|
159
|
+
const trimmed = line.trim();
|
|
160
|
+
if (!trimmed || processedLines.has(trimmed)) continue;
|
|
161
|
+
processedLines.add(trimmed);
|
|
162
|
+
const patch = parseSpecStreamLine(trimmed);
|
|
163
|
+
if (patch) {
|
|
164
|
+
applySpecStreamPatch(result, patch);
|
|
165
|
+
appliedPatches.push(patch);
|
|
166
|
+
newPatches.push(patch);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (newPatches.length > 0) {
|
|
170
|
+
result = { ...result };
|
|
171
|
+
}
|
|
172
|
+
return { result, newPatches };
|
|
173
|
+
},
|
|
174
|
+
getResult() {
|
|
175
|
+
if (buffer.trim()) {
|
|
176
|
+
const patch = parseSpecStreamLine(buffer);
|
|
177
|
+
if (patch && !processedLines.has(buffer.trim())) {
|
|
178
|
+
processedLines.add(buffer.trim());
|
|
179
|
+
applySpecStreamPatch(result, patch);
|
|
180
|
+
appliedPatches.push(patch);
|
|
181
|
+
result = { ...result };
|
|
182
|
+
}
|
|
183
|
+
buffer = "";
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
},
|
|
187
|
+
getPatches() {
|
|
188
|
+
return [...appliedPatches];
|
|
189
|
+
},
|
|
190
|
+
reset(newInitial = {}) {
|
|
191
|
+
result = { ...newInitial };
|
|
192
|
+
buffer = "";
|
|
193
|
+
appliedPatches.length = 0;
|
|
194
|
+
processedLines.clear();
|
|
195
|
+
}
|
|
196
|
+
};
|
|
62
197
|
}
|
|
63
198
|
|
|
64
199
|
// src/visibility.ts
|
|
@@ -553,8 +688,367 @@ var check = {
|
|
|
553
688
|
})
|
|
554
689
|
};
|
|
555
690
|
|
|
556
|
-
// src/
|
|
691
|
+
// src/schema.ts
|
|
557
692
|
import { z as z5 } from "zod";
|
|
693
|
+
function createBuilder() {
|
|
694
|
+
return {
|
|
695
|
+
string: () => ({ kind: "string" }),
|
|
696
|
+
number: () => ({ kind: "number" }),
|
|
697
|
+
boolean: () => ({ kind: "boolean" }),
|
|
698
|
+
array: (item) => ({ kind: "array", inner: item }),
|
|
699
|
+
object: (shape) => ({ kind: "object", inner: shape }),
|
|
700
|
+
record: (value) => ({ kind: "record", inner: value }),
|
|
701
|
+
any: () => ({ kind: "any" }),
|
|
702
|
+
zod: () => ({ kind: "zod" }),
|
|
703
|
+
ref: (path) => ({ kind: "ref", inner: path }),
|
|
704
|
+
propsOf: (path) => ({ kind: "propsOf", inner: path }),
|
|
705
|
+
map: (entryShape) => ({ kind: "map", inner: entryShape }),
|
|
706
|
+
optional: () => ({ optional: true })
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
function defineSchema(builder, options) {
|
|
710
|
+
const s = createBuilder();
|
|
711
|
+
const definition = builder(s);
|
|
712
|
+
return {
|
|
713
|
+
definition,
|
|
714
|
+
promptTemplate: options?.promptTemplate,
|
|
715
|
+
createCatalog(catalog) {
|
|
716
|
+
return createCatalogFromSchema(this, catalog);
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
function createCatalogFromSchema(schema, catalogData) {
|
|
721
|
+
const components = catalogData.components;
|
|
722
|
+
const actions = catalogData.actions;
|
|
723
|
+
const componentNames = components ? Object.keys(components) : [];
|
|
724
|
+
const actionNames = actions ? Object.keys(actions) : [];
|
|
725
|
+
const zodSchema = buildZodSchemaFromDefinition(
|
|
726
|
+
schema.definition,
|
|
727
|
+
catalogData
|
|
728
|
+
);
|
|
729
|
+
return {
|
|
730
|
+
schema,
|
|
731
|
+
data: catalogData,
|
|
732
|
+
componentNames,
|
|
733
|
+
actionNames,
|
|
734
|
+
prompt(options = {}) {
|
|
735
|
+
return generatePrompt(this, options);
|
|
736
|
+
},
|
|
737
|
+
jsonSchema() {
|
|
738
|
+
return zodToJsonSchema(zodSchema);
|
|
739
|
+
},
|
|
740
|
+
validate(spec) {
|
|
741
|
+
const result = zodSchema.safeParse(spec);
|
|
742
|
+
if (result.success) {
|
|
743
|
+
return {
|
|
744
|
+
success: true,
|
|
745
|
+
data: result.data
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
return { success: false, error: result.error };
|
|
749
|
+
},
|
|
750
|
+
zodSchema() {
|
|
751
|
+
return zodSchema;
|
|
752
|
+
},
|
|
753
|
+
get _specType() {
|
|
754
|
+
throw new Error("_specType is only for type inference");
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
function buildZodSchemaFromDefinition(definition, catalogData) {
|
|
759
|
+
return buildZodType(definition.spec, catalogData);
|
|
760
|
+
}
|
|
761
|
+
function buildZodType(schemaType, catalogData) {
|
|
762
|
+
switch (schemaType.kind) {
|
|
763
|
+
case "string":
|
|
764
|
+
return z5.string();
|
|
765
|
+
case "number":
|
|
766
|
+
return z5.number();
|
|
767
|
+
case "boolean":
|
|
768
|
+
return z5.boolean();
|
|
769
|
+
case "any":
|
|
770
|
+
return z5.any();
|
|
771
|
+
case "array": {
|
|
772
|
+
const inner = buildZodType(schemaType.inner, catalogData);
|
|
773
|
+
return z5.array(inner);
|
|
774
|
+
}
|
|
775
|
+
case "object": {
|
|
776
|
+
const shape = schemaType.inner;
|
|
777
|
+
const zodShape = {};
|
|
778
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
779
|
+
let zodType = buildZodType(value, catalogData);
|
|
780
|
+
if (value.optional) {
|
|
781
|
+
zodType = zodType.optional();
|
|
782
|
+
}
|
|
783
|
+
zodShape[key] = zodType;
|
|
784
|
+
}
|
|
785
|
+
return z5.object(zodShape);
|
|
786
|
+
}
|
|
787
|
+
case "record": {
|
|
788
|
+
const inner = buildZodType(schemaType.inner, catalogData);
|
|
789
|
+
return z5.record(z5.string(), inner);
|
|
790
|
+
}
|
|
791
|
+
case "ref": {
|
|
792
|
+
const path = schemaType.inner;
|
|
793
|
+
const keys = getKeysFromPath(path, catalogData);
|
|
794
|
+
if (keys.length === 0) {
|
|
795
|
+
return z5.string();
|
|
796
|
+
}
|
|
797
|
+
if (keys.length === 1) {
|
|
798
|
+
return z5.literal(keys[0]);
|
|
799
|
+
}
|
|
800
|
+
return z5.enum(keys);
|
|
801
|
+
}
|
|
802
|
+
case "propsOf": {
|
|
803
|
+
const path = schemaType.inner;
|
|
804
|
+
const propsSchemas = getPropsFromPath(path, catalogData);
|
|
805
|
+
if (propsSchemas.length === 0) {
|
|
806
|
+
return z5.record(z5.string(), z5.unknown());
|
|
807
|
+
}
|
|
808
|
+
if (propsSchemas.length === 1) {
|
|
809
|
+
return propsSchemas[0];
|
|
810
|
+
}
|
|
811
|
+
return z5.record(z5.string(), z5.unknown());
|
|
812
|
+
}
|
|
813
|
+
default:
|
|
814
|
+
return z5.unknown();
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
function getKeysFromPath(path, catalogData) {
|
|
818
|
+
const parts = path.split(".");
|
|
819
|
+
let current = { catalog: catalogData };
|
|
820
|
+
for (const part of parts) {
|
|
821
|
+
if (current && typeof current === "object") {
|
|
822
|
+
current = current[part];
|
|
823
|
+
} else {
|
|
824
|
+
return [];
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (current && typeof current === "object") {
|
|
828
|
+
return Object.keys(current);
|
|
829
|
+
}
|
|
830
|
+
return [];
|
|
831
|
+
}
|
|
832
|
+
function getPropsFromPath(path, catalogData) {
|
|
833
|
+
const parts = path.split(".");
|
|
834
|
+
let current = { catalog: catalogData };
|
|
835
|
+
for (const part of parts) {
|
|
836
|
+
if (current && typeof current === "object") {
|
|
837
|
+
current = current[part];
|
|
838
|
+
} else {
|
|
839
|
+
return [];
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
if (current && typeof current === "object") {
|
|
843
|
+
return Object.values(current).map((entry) => entry.props).filter((props) => props !== void 0);
|
|
844
|
+
}
|
|
845
|
+
return [];
|
|
846
|
+
}
|
|
847
|
+
function generatePrompt(catalog, options) {
|
|
848
|
+
if (catalog.schema.promptTemplate) {
|
|
849
|
+
const context = {
|
|
850
|
+
catalog: catalog.data,
|
|
851
|
+
componentNames: catalog.componentNames,
|
|
852
|
+
actionNames: catalog.actionNames,
|
|
853
|
+
options,
|
|
854
|
+
formatZodType
|
|
855
|
+
};
|
|
856
|
+
return catalog.schema.promptTemplate(context);
|
|
857
|
+
}
|
|
858
|
+
const {
|
|
859
|
+
system = "You are a UI generator that outputs JSON.",
|
|
860
|
+
customRules = []
|
|
861
|
+
} = options;
|
|
862
|
+
const lines = [];
|
|
863
|
+
lines.push(system);
|
|
864
|
+
lines.push("");
|
|
865
|
+
lines.push("OUTPUT FORMAT:");
|
|
866
|
+
lines.push(
|
|
867
|
+
"Output JSONL (one JSON object per line) with patches to build a UI tree."
|
|
868
|
+
);
|
|
869
|
+
lines.push(
|
|
870
|
+
"Each line is a JSON patch operation. Start with the root, then add each element."
|
|
871
|
+
);
|
|
872
|
+
lines.push("");
|
|
873
|
+
lines.push("Example output (each line is a separate JSON object):");
|
|
874
|
+
lines.push("");
|
|
875
|
+
lines.push(`{"op":"set","path":"/root","value":"card-1"}
|
|
876
|
+
{"op":"set","path":"/elements/card-1","value":{"key":"card-1","type":"Card","props":{"title":"Dashboard"},"children":["metric-1","chart-1"],"parentKey":""}}
|
|
877
|
+
{"op":"set","path":"/elements/metric-1","value":{"key":"metric-1","type":"Metric","props":{"label":"Revenue","valuePath":"analytics.revenue","format":"currency"},"children":[],"parentKey":"card-1"}}
|
|
878
|
+
{"op":"set","path":"/elements/chart-1","value":{"key":"chart-1","type":"Chart","props":{"type":"bar","dataPath":"analytics.salesByRegion"},"children":[],"parentKey":"card-1"}}`);
|
|
879
|
+
lines.push("");
|
|
880
|
+
const components = catalog.data.components;
|
|
881
|
+
if (components) {
|
|
882
|
+
lines.push(`AVAILABLE COMPONENTS (${catalog.componentNames.length}):`);
|
|
883
|
+
lines.push("");
|
|
884
|
+
for (const [name, def] of Object.entries(components)) {
|
|
885
|
+
const propsStr = def.props ? formatZodType(def.props) : "{}";
|
|
886
|
+
const hasChildren = def.slots && def.slots.length > 0;
|
|
887
|
+
const childrenStr = hasChildren ? " [accepts children]" : "";
|
|
888
|
+
const descStr = def.description ? ` - ${def.description}` : "";
|
|
889
|
+
lines.push(`- ${name}: ${propsStr}${descStr}${childrenStr}`);
|
|
890
|
+
}
|
|
891
|
+
lines.push("");
|
|
892
|
+
}
|
|
893
|
+
const actions = catalog.data.actions;
|
|
894
|
+
if (actions && catalog.actionNames.length > 0) {
|
|
895
|
+
lines.push("AVAILABLE ACTIONS:");
|
|
896
|
+
lines.push("");
|
|
897
|
+
for (const [name, def] of Object.entries(actions)) {
|
|
898
|
+
lines.push(`- ${name}${def.description ? `: ${def.description}` : ""}`);
|
|
899
|
+
}
|
|
900
|
+
lines.push("");
|
|
901
|
+
}
|
|
902
|
+
lines.push("RULES:");
|
|
903
|
+
const baseRules = [
|
|
904
|
+
"Output ONLY JSONL patches - one JSON object per line, no markdown, no code fences",
|
|
905
|
+
'First line sets root: {"op":"set","path":"/root","value":"<root-key>"}',
|
|
906
|
+
'Then add each element: {"op":"set","path":"/elements/<key>","value":{...}}',
|
|
907
|
+
"ONLY use components listed above",
|
|
908
|
+
"Each element value needs: key, type, props, children (array of child keys), parentKey",
|
|
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"
|
|
911
|
+
];
|
|
912
|
+
const allRules = [...baseRules, ...customRules];
|
|
913
|
+
allRules.forEach((rule, i) => {
|
|
914
|
+
lines.push(`${i + 1}. ${rule}`);
|
|
915
|
+
});
|
|
916
|
+
return lines.join("\n");
|
|
917
|
+
}
|
|
918
|
+
function getZodTypeName(schema) {
|
|
919
|
+
if (!schema || !schema._def) return "";
|
|
920
|
+
const def = schema._def;
|
|
921
|
+
return def.typeName ?? def.type ?? "";
|
|
922
|
+
}
|
|
923
|
+
function formatZodType(schema) {
|
|
924
|
+
if (!schema || !schema._def) return "unknown";
|
|
925
|
+
const def = schema._def;
|
|
926
|
+
const typeName = getZodTypeName(schema);
|
|
927
|
+
switch (typeName) {
|
|
928
|
+
case "ZodString":
|
|
929
|
+
case "string":
|
|
930
|
+
return "string";
|
|
931
|
+
case "ZodNumber":
|
|
932
|
+
case "number":
|
|
933
|
+
return "number";
|
|
934
|
+
case "ZodBoolean":
|
|
935
|
+
case "boolean":
|
|
936
|
+
return "boolean";
|
|
937
|
+
case "ZodLiteral":
|
|
938
|
+
case "literal":
|
|
939
|
+
return JSON.stringify(def.value);
|
|
940
|
+
case "ZodEnum":
|
|
941
|
+
case "enum": {
|
|
942
|
+
let values;
|
|
943
|
+
if (Array.isArray(def.values)) {
|
|
944
|
+
values = def.values;
|
|
945
|
+
} else if (def.entries && typeof def.entries === "object") {
|
|
946
|
+
values = Object.values(def.entries);
|
|
947
|
+
} else {
|
|
948
|
+
return "enum";
|
|
949
|
+
}
|
|
950
|
+
return values.map((v) => `"${v}"`).join(" | ");
|
|
951
|
+
}
|
|
952
|
+
case "ZodArray":
|
|
953
|
+
case "array": {
|
|
954
|
+
const inner = def.type ?? def.element;
|
|
955
|
+
return inner ? `Array<${formatZodType(inner)}>` : "Array<unknown>";
|
|
956
|
+
}
|
|
957
|
+
case "ZodObject":
|
|
958
|
+
case "object": {
|
|
959
|
+
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
960
|
+
if (!shape) return "object";
|
|
961
|
+
const props = Object.entries(shape).map(([key, value]) => {
|
|
962
|
+
const innerTypeName = getZodTypeName(value);
|
|
963
|
+
const isOptional = innerTypeName === "ZodOptional" || innerTypeName === "ZodNullable" || innerTypeName === "optional" || innerTypeName === "nullable";
|
|
964
|
+
return `${key}${isOptional ? "?" : ""}: ${formatZodType(value)}`;
|
|
965
|
+
}).join(", ");
|
|
966
|
+
return `{ ${props} }`;
|
|
967
|
+
}
|
|
968
|
+
case "ZodOptional":
|
|
969
|
+
case "optional":
|
|
970
|
+
case "ZodNullable":
|
|
971
|
+
case "nullable": {
|
|
972
|
+
const inner = def.innerType ?? def.wrapped;
|
|
973
|
+
return inner ? formatZodType(inner) : "unknown";
|
|
974
|
+
}
|
|
975
|
+
case "ZodUnion":
|
|
976
|
+
case "union": {
|
|
977
|
+
const options = def.options;
|
|
978
|
+
return options ? options.map((opt) => formatZodType(opt)).join(" | ") : "unknown";
|
|
979
|
+
}
|
|
980
|
+
default:
|
|
981
|
+
return "unknown";
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
function zodToJsonSchema(schema) {
|
|
985
|
+
const def = schema._def;
|
|
986
|
+
const typeName = def.typeName ?? "";
|
|
987
|
+
switch (typeName) {
|
|
988
|
+
case "ZodString":
|
|
989
|
+
return { type: "string" };
|
|
990
|
+
case "ZodNumber":
|
|
991
|
+
return { type: "number" };
|
|
992
|
+
case "ZodBoolean":
|
|
993
|
+
return { type: "boolean" };
|
|
994
|
+
case "ZodLiteral":
|
|
995
|
+
return { const: def.value };
|
|
996
|
+
case "ZodEnum":
|
|
997
|
+
return { enum: def.values };
|
|
998
|
+
case "ZodArray": {
|
|
999
|
+
const inner = def.type;
|
|
1000
|
+
return {
|
|
1001
|
+
type: "array",
|
|
1002
|
+
items: inner ? zodToJsonSchema(inner) : {}
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
case "ZodObject": {
|
|
1006
|
+
const shape = def.shape?.();
|
|
1007
|
+
if (!shape) return { type: "object" };
|
|
1008
|
+
const properties = {};
|
|
1009
|
+
const required = [];
|
|
1010
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
1011
|
+
properties[key] = zodToJsonSchema(value);
|
|
1012
|
+
const innerDef = value._def;
|
|
1013
|
+
if (innerDef.typeName !== "ZodOptional" && innerDef.typeName !== "ZodNullable") {
|
|
1014
|
+
required.push(key);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return {
|
|
1018
|
+
type: "object",
|
|
1019
|
+
properties,
|
|
1020
|
+
required: required.length > 0 ? required : void 0,
|
|
1021
|
+
additionalProperties: false
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
case "ZodRecord": {
|
|
1025
|
+
const valueType = def.valueType;
|
|
1026
|
+
return {
|
|
1027
|
+
type: "object",
|
|
1028
|
+
additionalProperties: valueType ? zodToJsonSchema(valueType) : true
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
case "ZodOptional":
|
|
1032
|
+
case "ZodNullable": {
|
|
1033
|
+
const inner = def.innerType;
|
|
1034
|
+
return inner ? zodToJsonSchema(inner) : {};
|
|
1035
|
+
}
|
|
1036
|
+
case "ZodUnion": {
|
|
1037
|
+
const options = def.options;
|
|
1038
|
+
return options ? { anyOf: options.map(zodToJsonSchema) } : {};
|
|
1039
|
+
}
|
|
1040
|
+
case "ZodAny":
|
|
1041
|
+
return {};
|
|
1042
|
+
default:
|
|
1043
|
+
return {};
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
function defineCatalog(schema, catalog) {
|
|
1047
|
+
return schema.createCatalog(catalog);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// src/catalog.ts
|
|
1051
|
+
import { z as z6 } from "zod";
|
|
558
1052
|
function createCatalog(config) {
|
|
559
1053
|
const {
|
|
560
1054
|
name = "unnamed",
|
|
@@ -568,37 +1062,37 @@ function createCatalog(config) {
|
|
|
568
1062
|
const functionNames = Object.keys(functions);
|
|
569
1063
|
const componentSchemas = componentNames.map((componentName) => {
|
|
570
1064
|
const def = components[componentName];
|
|
571
|
-
return
|
|
572
|
-
key:
|
|
573
|
-
type:
|
|
1065
|
+
return z6.object({
|
|
1066
|
+
key: z6.string(),
|
|
1067
|
+
type: z6.literal(componentName),
|
|
574
1068
|
props: def.props,
|
|
575
|
-
children:
|
|
576
|
-
parentKey:
|
|
1069
|
+
children: z6.array(z6.string()).optional(),
|
|
1070
|
+
parentKey: z6.string().nullable().optional(),
|
|
577
1071
|
visible: VisibilityConditionSchema.optional()
|
|
578
1072
|
});
|
|
579
1073
|
});
|
|
580
1074
|
let elementSchema;
|
|
581
1075
|
if (componentSchemas.length === 0) {
|
|
582
|
-
elementSchema =
|
|
583
|
-
key:
|
|
584
|
-
type:
|
|
585
|
-
props:
|
|
586
|
-
children:
|
|
587
|
-
parentKey:
|
|
1076
|
+
elementSchema = z6.object({
|
|
1077
|
+
key: z6.string(),
|
|
1078
|
+
type: z6.string(),
|
|
1079
|
+
props: z6.record(z6.string(), z6.unknown()),
|
|
1080
|
+
children: z6.array(z6.string()).optional(),
|
|
1081
|
+
parentKey: z6.string().nullable().optional(),
|
|
588
1082
|
visible: VisibilityConditionSchema.optional()
|
|
589
1083
|
});
|
|
590
1084
|
} else if (componentSchemas.length === 1) {
|
|
591
1085
|
elementSchema = componentSchemas[0];
|
|
592
1086
|
} else {
|
|
593
|
-
elementSchema =
|
|
1087
|
+
elementSchema = z6.discriminatedUnion("type", [
|
|
594
1088
|
componentSchemas[0],
|
|
595
1089
|
componentSchemas[1],
|
|
596
1090
|
...componentSchemas.slice(2)
|
|
597
1091
|
]);
|
|
598
1092
|
}
|
|
599
|
-
const
|
|
600
|
-
root:
|
|
601
|
-
elements:
|
|
1093
|
+
const specSchema = z6.object({
|
|
1094
|
+
root: z6.string(),
|
|
1095
|
+
elements: z6.record(z6.string(), elementSchema)
|
|
602
1096
|
});
|
|
603
1097
|
return {
|
|
604
1098
|
name,
|
|
@@ -610,7 +1104,7 @@ function createCatalog(config) {
|
|
|
610
1104
|
actions,
|
|
611
1105
|
functions,
|
|
612
1106
|
elementSchema,
|
|
613
|
-
|
|
1107
|
+
specSchema,
|
|
614
1108
|
hasComponent(type) {
|
|
615
1109
|
return type in components;
|
|
616
1110
|
},
|
|
@@ -627,8 +1121,8 @@ function createCatalog(config) {
|
|
|
627
1121
|
}
|
|
628
1122
|
return { success: false, error: result.error };
|
|
629
1123
|
},
|
|
630
|
-
|
|
631
|
-
const result =
|
|
1124
|
+
validateSpec(spec) {
|
|
1125
|
+
const result = specSchema.safeParse(spec);
|
|
632
1126
|
if (result.success) {
|
|
633
1127
|
return { success: true, data: result.data };
|
|
634
1128
|
}
|
|
@@ -684,6 +1178,155 @@ function generateCatalogPrompt(catalog) {
|
|
|
684
1178
|
lines.push("");
|
|
685
1179
|
return lines.join("\n");
|
|
686
1180
|
}
|
|
1181
|
+
function formatZodType2(schema, isOptional = false) {
|
|
1182
|
+
const def = schema._def;
|
|
1183
|
+
const typeName = def.typeName ?? "";
|
|
1184
|
+
let result;
|
|
1185
|
+
switch (typeName) {
|
|
1186
|
+
case "ZodString":
|
|
1187
|
+
result = "string";
|
|
1188
|
+
break;
|
|
1189
|
+
case "ZodNumber":
|
|
1190
|
+
result = "number";
|
|
1191
|
+
break;
|
|
1192
|
+
case "ZodBoolean":
|
|
1193
|
+
result = "boolean";
|
|
1194
|
+
break;
|
|
1195
|
+
case "ZodLiteral":
|
|
1196
|
+
result = JSON.stringify(def.value);
|
|
1197
|
+
break;
|
|
1198
|
+
case "ZodEnum":
|
|
1199
|
+
result = def.values.map((v) => `"${v}"`).join("|");
|
|
1200
|
+
break;
|
|
1201
|
+
case "ZodNativeEnum":
|
|
1202
|
+
result = Object.values(def.values).map((v) => `"${v}"`).join("|");
|
|
1203
|
+
break;
|
|
1204
|
+
case "ZodArray":
|
|
1205
|
+
result = def.type ? `Array<${formatZodType2(def.type)}>` : "Array<unknown>";
|
|
1206
|
+
break;
|
|
1207
|
+
case "ZodObject": {
|
|
1208
|
+
if (!def.shape) {
|
|
1209
|
+
result = "object";
|
|
1210
|
+
break;
|
|
1211
|
+
}
|
|
1212
|
+
const shape = def.shape();
|
|
1213
|
+
const props = Object.entries(shape).map(([key, value]) => {
|
|
1214
|
+
const innerDef = value._def;
|
|
1215
|
+
const innerOptional = innerDef.typeName === "ZodOptional" || innerDef.typeName === "ZodNullable";
|
|
1216
|
+
return `${key}${innerOptional ? "?" : ""}: ${formatZodType2(value)}`;
|
|
1217
|
+
}).join(", ");
|
|
1218
|
+
result = `{ ${props} }`;
|
|
1219
|
+
break;
|
|
1220
|
+
}
|
|
1221
|
+
case "ZodOptional":
|
|
1222
|
+
return def.innerType ? formatZodType2(def.innerType, true) : "unknown?";
|
|
1223
|
+
case "ZodNullable":
|
|
1224
|
+
return def.innerType ? formatZodType2(def.innerType, true) : "unknown?";
|
|
1225
|
+
case "ZodDefault":
|
|
1226
|
+
return def.innerType ? formatZodType2(def.innerType, isOptional) : "unknown";
|
|
1227
|
+
case "ZodUnion":
|
|
1228
|
+
result = def.options ? def.options.map((opt) => formatZodType2(opt)).join("|") : "unknown";
|
|
1229
|
+
break;
|
|
1230
|
+
case "ZodNull":
|
|
1231
|
+
result = "null";
|
|
1232
|
+
break;
|
|
1233
|
+
case "ZodUndefined":
|
|
1234
|
+
result = "undefined";
|
|
1235
|
+
break;
|
|
1236
|
+
case "ZodAny":
|
|
1237
|
+
result = "any";
|
|
1238
|
+
break;
|
|
1239
|
+
case "ZodUnknown":
|
|
1240
|
+
result = "unknown";
|
|
1241
|
+
break;
|
|
1242
|
+
default:
|
|
1243
|
+
result = "unknown";
|
|
1244
|
+
}
|
|
1245
|
+
return isOptional ? `${result}?` : result;
|
|
1246
|
+
}
|
|
1247
|
+
function extractPropsFromSchema(schema) {
|
|
1248
|
+
const def = schema._def;
|
|
1249
|
+
const typeName = def.typeName ?? "";
|
|
1250
|
+
if (typeName !== "ZodObject" || !def.shape) {
|
|
1251
|
+
return [];
|
|
1252
|
+
}
|
|
1253
|
+
const shape = def.shape();
|
|
1254
|
+
return Object.entries(shape).map(([name, value]) => {
|
|
1255
|
+
const innerDef = value._def;
|
|
1256
|
+
const optional = innerDef.typeName === "ZodOptional" || innerDef.typeName === "ZodNullable";
|
|
1257
|
+
return {
|
|
1258
|
+
name,
|
|
1259
|
+
type: formatZodType2(value),
|
|
1260
|
+
optional
|
|
1261
|
+
};
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
function formatPropsCompact(props) {
|
|
1265
|
+
if (props.length === 0) return "{}";
|
|
1266
|
+
const entries = props.map(
|
|
1267
|
+
(p) => `${p.name}${p.optional ? "?" : ""}: ${p.type}`
|
|
1268
|
+
);
|
|
1269
|
+
return `{ ${entries.join(", ")} }`;
|
|
1270
|
+
}
|
|
1271
|
+
function generateSystemPrompt(catalog, options = {}) {
|
|
1272
|
+
const {
|
|
1273
|
+
system = "You are a UI generator that outputs JSONL (JSON Lines) patches.",
|
|
1274
|
+
customRules = []
|
|
1275
|
+
} = options;
|
|
1276
|
+
const lines = [];
|
|
1277
|
+
lines.push(system);
|
|
1278
|
+
lines.push("");
|
|
1279
|
+
const componentCount = catalog.componentNames.length;
|
|
1280
|
+
lines.push(`AVAILABLE COMPONENTS (${componentCount}):`);
|
|
1281
|
+
lines.push("");
|
|
1282
|
+
for (const name of catalog.componentNames) {
|
|
1283
|
+
const def = catalog.components[name];
|
|
1284
|
+
const props = extractPropsFromSchema(def.props);
|
|
1285
|
+
const propsStr = formatPropsCompact(props);
|
|
1286
|
+
const hasChildrenStr = def.hasChildren ? " Has children." : "";
|
|
1287
|
+
const descStr = def.description ? ` ${def.description}` : "";
|
|
1288
|
+
lines.push(`- ${String(name)}: ${propsStr}${descStr}${hasChildrenStr}`);
|
|
1289
|
+
}
|
|
1290
|
+
lines.push("");
|
|
1291
|
+
if (catalog.actionNames.length > 0) {
|
|
1292
|
+
lines.push("AVAILABLE ACTIONS:");
|
|
1293
|
+
lines.push("");
|
|
1294
|
+
for (const name of catalog.actionNames) {
|
|
1295
|
+
const def = catalog.actions[name];
|
|
1296
|
+
lines.push(
|
|
1297
|
+
`- ${String(name)}${def.description ? `: ${def.description}` : ""}`
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
lines.push("");
|
|
1301
|
+
}
|
|
1302
|
+
lines.push("OUTPUT FORMAT (JSONL):");
|
|
1303
|
+
lines.push('{"op":"set","path":"/root","value":"element-key"}');
|
|
1304
|
+
lines.push(
|
|
1305
|
+
'{"op":"add","path":"/elements/key","value":{"key":"...","type":"...","props":{...},"children":[...]}}'
|
|
1306
|
+
);
|
|
1307
|
+
lines.push("");
|
|
1308
|
+
lines.push("RULES:");
|
|
1309
|
+
const baseRules = [
|
|
1310
|
+
"First line sets /root to root element key",
|
|
1311
|
+
"Add elements with /elements/{key}",
|
|
1312
|
+
"Children array contains string keys, not objects",
|
|
1313
|
+
"Parent first, then children",
|
|
1314
|
+
"Each element needs: key, type, props",
|
|
1315
|
+
"ONLY use props listed above - never invent new props"
|
|
1316
|
+
];
|
|
1317
|
+
const allRules = [...baseRules, ...customRules];
|
|
1318
|
+
allRules.forEach((rule, i) => {
|
|
1319
|
+
lines.push(`${i + 1}. ${rule}`);
|
|
1320
|
+
});
|
|
1321
|
+
lines.push("");
|
|
1322
|
+
if (catalog.functionNames.length > 0) {
|
|
1323
|
+
lines.push("CUSTOM VALIDATION FUNCTIONS:");
|
|
1324
|
+
lines.push(catalog.functionNames.map(String).join(", "));
|
|
1325
|
+
lines.push("");
|
|
1326
|
+
}
|
|
1327
|
+
lines.push("Generate JSONL:");
|
|
1328
|
+
return lines.join("\n");
|
|
1329
|
+
}
|
|
687
1330
|
export {
|
|
688
1331
|
ActionConfirmSchema,
|
|
689
1332
|
ActionOnErrorSchema,
|
|
@@ -698,15 +1341,23 @@ export {
|
|
|
698
1341
|
ValidationConfigSchema,
|
|
699
1342
|
VisibilityConditionSchema,
|
|
700
1343
|
action,
|
|
1344
|
+
applySpecStreamPatch,
|
|
701
1345
|
builtInValidationFunctions,
|
|
702
1346
|
check,
|
|
1347
|
+
compileSpecStream,
|
|
703
1348
|
createCatalog,
|
|
1349
|
+
createSpecStreamCompiler,
|
|
1350
|
+
defineCatalog,
|
|
1351
|
+
defineSchema,
|
|
704
1352
|
evaluateLogicExpression,
|
|
705
1353
|
evaluateVisibility,
|
|
706
1354
|
executeAction,
|
|
1355
|
+
findFormValue,
|
|
707
1356
|
generateCatalogPrompt,
|
|
1357
|
+
generateSystemPrompt,
|
|
708
1358
|
getByPath,
|
|
709
1359
|
interpolateString,
|
|
1360
|
+
parseSpecStreamLine,
|
|
710
1361
|
resolveAction,
|
|
711
1362
|
resolveDynamicValue,
|
|
712
1363
|
runValidation,
|