@json-render/core 0.2.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/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
- if (!(segment in current) || typeof current[segment] !== "object") {
109
- current[segment] = {};
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[lastSegment] = value;
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/catalog.ts
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 import_zod5.z.object({
625
- key: import_zod5.z.string(),
626
- type: import_zod5.z.literal(componentName),
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: import_zod5.z.array(import_zod5.z.string()).optional(),
629
- parentKey: import_zod5.z.string().nullable().optional(),
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 = import_zod5.z.object({
636
- key: import_zod5.z.string(),
637
- type: import_zod5.z.string(),
638
- props: import_zod5.z.record(import_zod5.z.string(), import_zod5.z.unknown()),
639
- children: import_zod5.z.array(import_zod5.z.string()).optional(),
640
- parentKey: import_zod5.z.string().nullable().optional(),
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 = import_zod5.z.discriminatedUnion("type", [
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 treeSchema = import_zod5.z.object({
653
- root: import_zod5.z.string(),
654
- elements: import_zod5.z.record(import_zod5.z.string(), elementSchema)
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
- treeSchema,
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
- validateTree(tree) {
684
- const result = treeSchema.safeParse(tree);
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,