@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 CHANGED
@@ -116,14 +116,16 @@ while (streaming) {
116
116
  const finalSpec = compiler.getResult();
117
117
  ```
118
118
 
119
- SpecStream format (each line is a JSON patch):
119
+ SpecStream format uses [RFC 6902 JSON Patch](https://datatracker.ietf.org/doc/html/rfc6902) operations (each line is a patch):
120
120
 
121
121
  ```jsonl
122
- {"op":"set","path":"/root/type","value":"Card"}
123
- {"op":"set","path":"/root/props","value":{"title":"Hello"}}
124
- {"op":"set","path":"/root/children/0","value":{"type":"Button","props":{"label":"Click"}}}
122
+ {"op":"add","path":"/root/type","value":"Card"}
123
+ {"op":"add","path":"/root/props","value":{"title":"Hello"}}
124
+ {"op":"add","path":"/root/children/0","value":{"type":"Button","props":{"label":"Click"}}}
125
125
  ```
126
126
 
127
+ All six RFC 6902 operations are supported: `add`, `remove`, `replace`, `move`, `copy`, `test`.
128
+
127
129
  ### Low-Level Utilities
128
130
 
129
131
  ```typescript
@@ -134,8 +136,8 @@ import {
134
136
  } from "@json-render/core";
135
137
 
136
138
  // Parse a single line
137
- const patch = parseSpecStreamLine('{"op":"set","path":"/root","value":{}}');
138
- // { op: "set", path: "/root", value: {} }
139
+ const patch = parseSpecStreamLine('{"op":"add","path":"/root","value":{}}');
140
+ // { op: "add", path: "/root", value: {} }
139
141
 
140
142
  // Apply a patch to an object
141
143
  const obj = {};
package/dist/index.d.mts CHANGED
@@ -37,19 +37,26 @@ declare const DynamicBooleanSchema: z.ZodUnion<readonly [z.ZodBoolean, z.ZodObje
37
37
  * Base UI element structure for v2
38
38
  */
39
39
  interface UIElement<T extends string = string, P = Record<string, unknown>> {
40
- /** Unique key for reconciliation */
41
- key: string;
42
40
  /** Component type from the catalog */
43
41
  type: T;
44
42
  /** Component props */
45
43
  props: P;
46
44
  /** Child element keys (flat structure) */
47
45
  children?: string[];
48
- /** Parent element key (null for root) */
49
- parentKey?: string | null;
50
46
  /** Visibility condition */
51
47
  visible?: VisibilityCondition;
52
48
  }
49
+ /**
50
+ * Element with key and parentKey for use with flatToTree.
51
+ * When elements are in an array (not a keyed map), key and parentKey
52
+ * are needed to establish identity and parent-child relationships.
53
+ */
54
+ interface FlatElement<T extends string = string, P = Record<string, unknown>> extends UIElement<T, P> {
55
+ /** Unique key identifying this element */
56
+ key: string;
57
+ /** Parent element key (null for root) */
58
+ parentKey?: string | null;
59
+ }
53
60
  /**
54
61
  * Visibility condition types
55
62
  */
@@ -111,30 +118,45 @@ type ComponentSchema = z.ZodType<Record<string, unknown>>;
111
118
  */
112
119
  type ValidationMode = "strict" | "warn" | "ignore";
113
120
  /**
114
- * JSON patch operation types
121
+ * JSON patch operation types (RFC 6902)
115
122
  */
116
- type PatchOp = "add" | "remove" | "replace" | "set";
123
+ type PatchOp = "add" | "remove" | "replace" | "move" | "copy" | "test";
117
124
  /**
118
- * JSON patch operation
125
+ * JSON patch operation (RFC 6902)
119
126
  */
120
127
  interface JsonPatch {
121
128
  op: PatchOp;
122
129
  path: string;
130
+ /** Required for add, replace, test */
123
131
  value?: unknown;
132
+ /** Required for move, copy (source location) */
133
+ from?: string;
124
134
  }
125
135
  /**
126
136
  * Resolve a dynamic value against a data model
127
137
  */
128
138
  declare function resolveDynamicValue<T>(value: DynamicValue<T>, dataModel: DataModel): T | undefined;
129
139
  /**
130
- * Get a value from an object by JSON Pointer path
140
+ * Get a value from an object by JSON Pointer path (RFC 6901)
131
141
  */
132
142
  declare function getByPath(obj: unknown, path: string): unknown;
133
143
  /**
134
- * Set a value in an object by JSON Pointer path.
144
+ * Set a value in an object by JSON Pointer path (RFC 6901).
135
145
  * Automatically creates arrays when the path segment is a numeric index.
136
146
  */
137
147
  declare function setByPath(obj: Record<string, unknown>, path: string, value: unknown): void;
148
+ /**
149
+ * Add a value per RFC 6902 "add" semantics.
150
+ * For objects: create-or-replace the member.
151
+ * For arrays: insert before the given index, or append if "-".
152
+ */
153
+ declare function addByPath(obj: Record<string, unknown>, path: string, value: unknown): void;
154
+ /**
155
+ * Remove a value per RFC 6902 "remove" semantics.
156
+ * For objects: delete the property.
157
+ * For arrays: splice out the element at the given index.
158
+ */
159
+ declare function removeByPath(obj: Record<string, unknown>, path: string): void;
138
160
  /**
139
161
  * Find a form value from params and/or data.
140
162
  * Useful in action handlers to locate form input values regardless of path format.
@@ -165,8 +187,12 @@ type SpecStreamLine = JsonPatch;
165
187
  */
166
188
  declare function parseSpecStreamLine(line: string): SpecStreamLine | null;
167
189
  /**
168
- * Apply a single SpecStream patch to an object.
190
+ * Apply a single RFC 6902 JSON Patch operation to an object.
169
191
  * Mutates the object in place.
192
+ *
193
+ * Supports all six RFC 6902 operations: add, remove, replace, move, copy, test.
194
+ *
195
+ * @throws {Error} If a "test" operation fails (value mismatch).
170
196
  */
171
197
  declare function applySpecStreamPatch<T extends Record<string, unknown>>(obj: T, patch: SpecStreamLine): T;
172
198
  /**
@@ -174,8 +200,8 @@ declare function applySpecStreamPatch<T extends Record<string, unknown>>(obj: T,
174
200
  * Each line should be a patch operation.
175
201
  *
176
202
  * @example
177
- * const stream = `{"op":"set","path":"/name","value":"Alice"}
178
- * {"op":"set","path":"/age","value":30}`;
203
+ * const stream = `{"op":"add","path":"/name","value":"Alice"}
204
+ * {"op":"add","path":"/age","value":30}`;
179
205
  * const result = compileSpecStream(stream);
180
206
  * // { name: "Alice", age: 30 }
181
207
  */
@@ -874,4 +900,4 @@ interface SystemPromptOptions {
874
900
  */
875
901
  declare function generateSystemPrompt<TComponents extends Record<string, ComponentDefinition>, TActions extends Record<string, ActionDefinition>, TFunctions extends Record<string, ValidationFunction>>(catalog: Catalog<TComponents, TActions, TFunctions>, options?: SystemPromptOptions): string;
876
902
 
877
- export { type Action, type ActionConfirm, ActionConfirmSchema, type ActionDefinition, type ActionExecutionContext, type ActionHandler, type ActionOnError, ActionOnErrorSchema, type ActionOnSuccess, ActionOnSuccessSchema, ActionSchema, type AuthState, type Catalog$1 as Catalog, type CatalogConfig, type ComponentDefinition, type ComponentSchema, type DataModel, type DynamicBoolean, DynamicBooleanSchema, type DynamicNumber, DynamicNumberSchema, type DynamicString, DynamicStringSchema, type DynamicValue, DynamicValueSchema, type InferActionParams, type InferCatalogActions, type InferCatalogComponentProps, type InferCatalogComponents, type InferCatalogInput, type InferComponentProps, type InferSpec, type JsonPatch, type Catalog as LegacyCatalog, type LogicExpression, LogicExpressionSchema, type PatchOp, type PromptContext, type PromptOptions, type PromptTemplate, type ResolvedAction, type Schema, type SchemaBuilder, type SchemaDefinition, type SchemaOptions, type SchemaType, type Spec, type SpecStreamCompiler, type SpecStreamLine, type SpecValidationResult, type SystemPromptOptions, type UIElement, type ValidationCheck, type ValidationCheckResult, ValidationCheckSchema, type ValidationConfig, ValidationConfigSchema, type ValidationContext, type ValidationFunction, type ValidationFunctionDefinition, type ValidationMode, type ValidationResult, type VisibilityCondition, VisibilityConditionSchema, type VisibilityContext, action, applySpecStreamPatch, builtInValidationFunctions, check, compileSpecStream, createCatalog, createSpecStreamCompiler, defineCatalog, defineSchema, evaluateLogicExpression, evaluateVisibility, executeAction, findFormValue, generateCatalogPrompt, generateSystemPrompt, getByPath, interpolateString, parseSpecStreamLine, resolveAction, resolveDynamicValue, runValidation, runValidationCheck, setByPath, visibility };
903
+ export { type Action, type ActionConfirm, ActionConfirmSchema, type ActionDefinition, type ActionExecutionContext, type ActionHandler, type ActionOnError, ActionOnErrorSchema, type ActionOnSuccess, ActionOnSuccessSchema, ActionSchema, type AuthState, type Catalog$1 as Catalog, type CatalogConfig, type ComponentDefinition, type ComponentSchema, type DataModel, type DynamicBoolean, DynamicBooleanSchema, type DynamicNumber, DynamicNumberSchema, type DynamicString, DynamicStringSchema, type DynamicValue, DynamicValueSchema, type FlatElement, type InferActionParams, type InferCatalogActions, type InferCatalogComponentProps, type InferCatalogComponents, type InferCatalogInput, type InferComponentProps, type InferSpec, type JsonPatch, type Catalog as LegacyCatalog, type LogicExpression, LogicExpressionSchema, type PatchOp, type PromptContext, type PromptOptions, type PromptTemplate, type ResolvedAction, type Schema, type SchemaBuilder, type SchemaDefinition, type SchemaOptions, type SchemaType, type Spec, type SpecStreamCompiler, type SpecStreamLine, type SpecValidationResult, type SystemPromptOptions, type UIElement, type ValidationCheck, type ValidationCheckResult, ValidationCheckSchema, type ValidationConfig, ValidationConfigSchema, type ValidationContext, type ValidationFunction, type ValidationFunctionDefinition, type ValidationMode, type ValidationResult, type VisibilityCondition, VisibilityConditionSchema, type VisibilityContext, action, addByPath, applySpecStreamPatch, builtInValidationFunctions, check, compileSpecStream, createCatalog, createSpecStreamCompiler, defineCatalog, defineSchema, evaluateLogicExpression, evaluateVisibility, executeAction, findFormValue, generateCatalogPrompt, generateSystemPrompt, getByPath, interpolateString, parseSpecStreamLine, removeByPath, resolveAction, resolveDynamicValue, runValidation, runValidationCheck, setByPath, visibility };
package/dist/index.d.ts CHANGED
@@ -37,19 +37,26 @@ declare const DynamicBooleanSchema: z.ZodUnion<readonly [z.ZodBoolean, z.ZodObje
37
37
  * Base UI element structure for v2
38
38
  */
39
39
  interface UIElement<T extends string = string, P = Record<string, unknown>> {
40
- /** Unique key for reconciliation */
41
- key: string;
42
40
  /** Component type from the catalog */
43
41
  type: T;
44
42
  /** Component props */
45
43
  props: P;
46
44
  /** Child element keys (flat structure) */
47
45
  children?: string[];
48
- /** Parent element key (null for root) */
49
- parentKey?: string | null;
50
46
  /** Visibility condition */
51
47
  visible?: VisibilityCondition;
52
48
  }
49
+ /**
50
+ * Element with key and parentKey for use with flatToTree.
51
+ * When elements are in an array (not a keyed map), key and parentKey
52
+ * are needed to establish identity and parent-child relationships.
53
+ */
54
+ interface FlatElement<T extends string = string, P = Record<string, unknown>> extends UIElement<T, P> {
55
+ /** Unique key identifying this element */
56
+ key: string;
57
+ /** Parent element key (null for root) */
58
+ parentKey?: string | null;
59
+ }
53
60
  /**
54
61
  * Visibility condition types
55
62
  */
@@ -111,30 +118,45 @@ type ComponentSchema = z.ZodType<Record<string, unknown>>;
111
118
  */
112
119
  type ValidationMode = "strict" | "warn" | "ignore";
113
120
  /**
114
- * JSON patch operation types
121
+ * JSON patch operation types (RFC 6902)
115
122
  */
116
- type PatchOp = "add" | "remove" | "replace" | "set";
123
+ type PatchOp = "add" | "remove" | "replace" | "move" | "copy" | "test";
117
124
  /**
118
- * JSON patch operation
125
+ * JSON patch operation (RFC 6902)
119
126
  */
120
127
  interface JsonPatch {
121
128
  op: PatchOp;
122
129
  path: string;
130
+ /** Required for add, replace, test */
123
131
  value?: unknown;
132
+ /** Required for move, copy (source location) */
133
+ from?: string;
124
134
  }
125
135
  /**
126
136
  * Resolve a dynamic value against a data model
127
137
  */
128
138
  declare function resolveDynamicValue<T>(value: DynamicValue<T>, dataModel: DataModel): T | undefined;
129
139
  /**
130
- * Get a value from an object by JSON Pointer path
140
+ * Get a value from an object by JSON Pointer path (RFC 6901)
131
141
  */
132
142
  declare function getByPath(obj: unknown, path: string): unknown;
133
143
  /**
134
- * Set a value in an object by JSON Pointer path.
144
+ * Set a value in an object by JSON Pointer path (RFC 6901).
135
145
  * Automatically creates arrays when the path segment is a numeric index.
136
146
  */
137
147
  declare function setByPath(obj: Record<string, unknown>, path: string, value: unknown): void;
148
+ /**
149
+ * Add a value per RFC 6902 "add" semantics.
150
+ * For objects: create-or-replace the member.
151
+ * For arrays: insert before the given index, or append if "-".
152
+ */
153
+ declare function addByPath(obj: Record<string, unknown>, path: string, value: unknown): void;
154
+ /**
155
+ * Remove a value per RFC 6902 "remove" semantics.
156
+ * For objects: delete the property.
157
+ * For arrays: splice out the element at the given index.
158
+ */
159
+ declare function removeByPath(obj: Record<string, unknown>, path: string): void;
138
160
  /**
139
161
  * Find a form value from params and/or data.
140
162
  * Useful in action handlers to locate form input values regardless of path format.
@@ -165,8 +187,12 @@ type SpecStreamLine = JsonPatch;
165
187
  */
166
188
  declare function parseSpecStreamLine(line: string): SpecStreamLine | null;
167
189
  /**
168
- * Apply a single SpecStream patch to an object.
190
+ * Apply a single RFC 6902 JSON Patch operation to an object.
169
191
  * Mutates the object in place.
192
+ *
193
+ * Supports all six RFC 6902 operations: add, remove, replace, move, copy, test.
194
+ *
195
+ * @throws {Error} If a "test" operation fails (value mismatch).
170
196
  */
171
197
  declare function applySpecStreamPatch<T extends Record<string, unknown>>(obj: T, patch: SpecStreamLine): T;
172
198
  /**
@@ -174,8 +200,8 @@ declare function applySpecStreamPatch<T extends Record<string, unknown>>(obj: T,
174
200
  * Each line should be a patch operation.
175
201
  *
176
202
  * @example
177
- * const stream = `{"op":"set","path":"/name","value":"Alice"}
178
- * {"op":"set","path":"/age","value":30}`;
203
+ * const stream = `{"op":"add","path":"/name","value":"Alice"}
204
+ * {"op":"add","path":"/age","value":30}`;
179
205
  * const result = compileSpecStream(stream);
180
206
  * // { name: "Alice", age: 30 }
181
207
  */
@@ -874,4 +900,4 @@ interface SystemPromptOptions {
874
900
  */
875
901
  declare function generateSystemPrompt<TComponents extends Record<string, ComponentDefinition>, TActions extends Record<string, ActionDefinition>, TFunctions extends Record<string, ValidationFunction>>(catalog: Catalog<TComponents, TActions, TFunctions>, options?: SystemPromptOptions): string;
876
902
 
877
- export { type Action, type ActionConfirm, ActionConfirmSchema, type ActionDefinition, type ActionExecutionContext, type ActionHandler, type ActionOnError, ActionOnErrorSchema, type ActionOnSuccess, ActionOnSuccessSchema, ActionSchema, type AuthState, type Catalog$1 as Catalog, type CatalogConfig, type ComponentDefinition, type ComponentSchema, type DataModel, type DynamicBoolean, DynamicBooleanSchema, type DynamicNumber, DynamicNumberSchema, type DynamicString, DynamicStringSchema, type DynamicValue, DynamicValueSchema, type InferActionParams, type InferCatalogActions, type InferCatalogComponentProps, type InferCatalogComponents, type InferCatalogInput, type InferComponentProps, type InferSpec, type JsonPatch, type Catalog as LegacyCatalog, type LogicExpression, LogicExpressionSchema, type PatchOp, type PromptContext, type PromptOptions, type PromptTemplate, type ResolvedAction, type Schema, type SchemaBuilder, type SchemaDefinition, type SchemaOptions, type SchemaType, type Spec, type SpecStreamCompiler, type SpecStreamLine, type SpecValidationResult, type SystemPromptOptions, type UIElement, type ValidationCheck, type ValidationCheckResult, ValidationCheckSchema, type ValidationConfig, ValidationConfigSchema, type ValidationContext, type ValidationFunction, type ValidationFunctionDefinition, type ValidationMode, type ValidationResult, type VisibilityCondition, VisibilityConditionSchema, type VisibilityContext, action, applySpecStreamPatch, builtInValidationFunctions, check, compileSpecStream, createCatalog, createSpecStreamCompiler, defineCatalog, defineSchema, evaluateLogicExpression, evaluateVisibility, executeAction, findFormValue, generateCatalogPrompt, generateSystemPrompt, getByPath, interpolateString, parseSpecStreamLine, resolveAction, resolveDynamicValue, runValidation, runValidationCheck, setByPath, visibility };
903
+ export { type Action, type ActionConfirm, ActionConfirmSchema, type ActionDefinition, type ActionExecutionContext, type ActionHandler, type ActionOnError, ActionOnErrorSchema, type ActionOnSuccess, ActionOnSuccessSchema, ActionSchema, type AuthState, type Catalog$1 as Catalog, type CatalogConfig, type ComponentDefinition, type ComponentSchema, type DataModel, type DynamicBoolean, DynamicBooleanSchema, type DynamicNumber, DynamicNumberSchema, type DynamicString, DynamicStringSchema, type DynamicValue, DynamicValueSchema, type FlatElement, type InferActionParams, type InferCatalogActions, type InferCatalogComponentProps, type InferCatalogComponents, type InferCatalogInput, type InferComponentProps, type InferSpec, type JsonPatch, type Catalog as LegacyCatalog, type LogicExpression, LogicExpressionSchema, type PatchOp, type PromptContext, type PromptOptions, type PromptTemplate, type ResolvedAction, type Schema, type SchemaBuilder, type SchemaDefinition, type SchemaOptions, type SchemaType, type Spec, type SpecStreamCompiler, type SpecStreamLine, type SpecValidationResult, type SystemPromptOptions, type UIElement, type ValidationCheck, type ValidationCheckResult, ValidationCheckSchema, type ValidationConfig, ValidationConfigSchema, type ValidationContext, type ValidationFunction, type ValidationFunctionDefinition, type ValidationMode, type ValidationResult, type VisibilityCondition, VisibilityConditionSchema, type VisibilityContext, action, addByPath, applySpecStreamPatch, builtInValidationFunctions, check, compileSpecStream, createCatalog, createSpecStreamCompiler, defineCatalog, defineSchema, evaluateLogicExpression, evaluateVisibility, executeAction, findFormValue, generateCatalogPrompt, generateSystemPrompt, getByPath, interpolateString, parseSpecStreamLine, removeByPath, resolveAction, resolveDynamicValue, runValidation, runValidationCheck, setByPath, visibility };
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  ValidationConfigSchema: () => ValidationConfigSchema,
34
34
  VisibilityConditionSchema: () => VisibilityConditionSchema,
35
35
  action: () => action,
36
+ addByPath: () => addByPath,
36
37
  applySpecStreamPatch: () => applySpecStreamPatch,
37
38
  builtInValidationFunctions: () => builtInValidationFunctions,
38
39
  check: () => check,
@@ -50,6 +51,7 @@ __export(index_exports, {
50
51
  getByPath: () => getByPath,
51
52
  interpolateString: () => interpolateString,
52
53
  parseSpecStreamLine: () => parseSpecStreamLine,
54
+ removeByPath: () => removeByPath,
53
55
  resolveAction: () => resolveAction,
54
56
  resolveDynamicValue: () => resolveDynamicValue,
55
57
  runValidation: () => runValidation,
@@ -89,17 +91,27 @@ function resolveDynamicValue(value, dataModel) {
89
91
  }
90
92
  return value;
91
93
  }
94
+ function unescapeJsonPointer(token) {
95
+ return token.replace(/~1/g, "/").replace(/~0/g, "~");
96
+ }
97
+ function parseJsonPointer(path) {
98
+ const raw = path.startsWith("/") ? path.slice(1).split("/") : path.split("/");
99
+ return raw.map(unescapeJsonPointer);
100
+ }
92
101
  function getByPath(obj, path) {
93
102
  if (!path || path === "/") {
94
103
  return obj;
95
104
  }
96
- const segments = path.startsWith("/") ? path.slice(1).split("/") : path.split("/");
105
+ const segments = parseJsonPointer(path);
97
106
  let current = obj;
98
107
  for (const segment of segments) {
99
108
  if (current === null || current === void 0) {
100
109
  return void 0;
101
110
  }
102
- if (typeof current === "object") {
111
+ if (Array.isArray(current)) {
112
+ const index = parseInt(segment, 10);
113
+ current = current[index];
114
+ } else if (typeof current === "object") {
103
115
  current = current[segment];
104
116
  } else {
105
117
  return void 0;
@@ -111,13 +123,13 @@ function isNumericIndex(str) {
111
123
  return /^\d+$/.test(str);
112
124
  }
113
125
  function setByPath(obj, path, value) {
114
- const segments = path.startsWith("/") ? path.slice(1).split("/") : path.split("/");
126
+ const segments = parseJsonPointer(path);
115
127
  if (segments.length === 0) return;
116
128
  let current = obj;
117
129
  for (let i = 0; i < segments.length - 1; i++) {
118
130
  const segment = segments[i];
119
131
  const nextSegment = segments[i + 1];
120
- const nextIsNumeric = nextSegment !== void 0 && isNumericIndex(nextSegment);
132
+ const nextIsNumeric = nextSegment !== void 0 && (isNumericIndex(nextSegment) || nextSegment === "-");
121
133
  if (Array.isArray(current)) {
122
134
  const index = parseInt(segment, 10);
123
135
  if (current[index] === void 0 || typeof current[index] !== "object") {
@@ -133,12 +145,95 @@ function setByPath(obj, path, value) {
133
145
  }
134
146
  const lastSegment = segments[segments.length - 1];
135
147
  if (Array.isArray(current)) {
136
- const index = parseInt(lastSegment, 10);
137
- current[index] = value;
148
+ if (lastSegment === "-") {
149
+ current.push(value);
150
+ } else {
151
+ const index = parseInt(lastSegment, 10);
152
+ current[index] = value;
153
+ }
154
+ } else {
155
+ current[lastSegment] = value;
156
+ }
157
+ }
158
+ function addByPath(obj, path, value) {
159
+ const segments = parseJsonPointer(path);
160
+ if (segments.length === 0) return;
161
+ let current = obj;
162
+ for (let i = 0; i < segments.length - 1; i++) {
163
+ const segment = segments[i];
164
+ const nextSegment = segments[i + 1];
165
+ const nextIsNumeric = nextSegment !== void 0 && (isNumericIndex(nextSegment) || nextSegment === "-");
166
+ if (Array.isArray(current)) {
167
+ const index = parseInt(segment, 10);
168
+ if (current[index] === void 0 || typeof current[index] !== "object") {
169
+ current[index] = nextIsNumeric ? [] : {};
170
+ }
171
+ current = current[index];
172
+ } else {
173
+ if (!(segment in current) || typeof current[segment] !== "object") {
174
+ current[segment] = nextIsNumeric ? [] : {};
175
+ }
176
+ current = current[segment];
177
+ }
178
+ }
179
+ const lastSegment = segments[segments.length - 1];
180
+ if (Array.isArray(current)) {
181
+ if (lastSegment === "-") {
182
+ current.push(value);
183
+ } else {
184
+ const index = parseInt(lastSegment, 10);
185
+ current.splice(index, 0, value);
186
+ }
138
187
  } else {
139
188
  current[lastSegment] = value;
140
189
  }
141
190
  }
191
+ function removeByPath(obj, path) {
192
+ const segments = parseJsonPointer(path);
193
+ if (segments.length === 0) return;
194
+ let current = obj;
195
+ for (let i = 0; i < segments.length - 1; i++) {
196
+ const segment = segments[i];
197
+ if (Array.isArray(current)) {
198
+ const index = parseInt(segment, 10);
199
+ if (current[index] === void 0 || typeof current[index] !== "object") {
200
+ return;
201
+ }
202
+ current = current[index];
203
+ } else {
204
+ if (!(segment in current) || typeof current[segment] !== "object") {
205
+ return;
206
+ }
207
+ current = current[segment];
208
+ }
209
+ }
210
+ const lastSegment = segments[segments.length - 1];
211
+ if (Array.isArray(current)) {
212
+ const index = parseInt(lastSegment, 10);
213
+ if (index >= 0 && index < current.length) {
214
+ current.splice(index, 1);
215
+ }
216
+ } else {
217
+ delete current[lastSegment];
218
+ }
219
+ }
220
+ function deepEqual(a, b) {
221
+ if (a === b) return true;
222
+ if (a === null || b === null) return false;
223
+ if (typeof a !== typeof b) return false;
224
+ if (typeof a !== "object") return false;
225
+ if (Array.isArray(a)) {
226
+ if (!Array.isArray(b)) return false;
227
+ if (a.length !== b.length) return false;
228
+ return a.every((item, i) => deepEqual(item, b[i]));
229
+ }
230
+ const aObj = a;
231
+ const bObj = b;
232
+ const aKeys = Object.keys(aObj);
233
+ const bKeys = Object.keys(bObj);
234
+ if (aKeys.length !== bKeys.length) return false;
235
+ return aKeys.every((key) => deepEqual(aObj[key], bObj[key]));
236
+ }
142
237
  function findFormValue(fieldName, params, data) {
143
238
  if (params?.[fieldName] !== void 0) {
144
239
  const val = params[fieldName];
@@ -187,10 +282,38 @@ function parseSpecStreamLine(line) {
187
282
  }
188
283
  }
189
284
  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);
285
+ switch (patch.op) {
286
+ case "add":
287
+ addByPath(obj, patch.path, patch.value);
288
+ break;
289
+ case "replace":
290
+ setByPath(obj, patch.path, patch.value);
291
+ break;
292
+ case "remove":
293
+ removeByPath(obj, patch.path);
294
+ break;
295
+ case "move": {
296
+ if (!patch.from) break;
297
+ const moveValue = getByPath(obj, patch.from);
298
+ removeByPath(obj, patch.from);
299
+ addByPath(obj, patch.path, moveValue);
300
+ break;
301
+ }
302
+ case "copy": {
303
+ if (!patch.from) break;
304
+ const copyValue = getByPath(obj, patch.from);
305
+ addByPath(obj, patch.path, copyValue);
306
+ break;
307
+ }
308
+ case "test": {
309
+ const actual = getByPath(obj, patch.path);
310
+ if (!deepEqual(actual, patch.value)) {
311
+ throw new Error(
312
+ `Test operation failed: value at "${patch.path}" does not match`
313
+ );
314
+ }
315
+ break;
316
+ }
194
317
  }
195
318
  return obj;
196
319
  }
@@ -933,10 +1056,10 @@ function generatePrompt(catalog, options) {
933
1056
  lines.push("");
934
1057
  lines.push("Example output (each line is a separate JSON object):");
935
1058
  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"}}`);
1059
+ lines.push(`{"op":"add","path":"/root","value":"card-1"}
1060
+ {"op":"add","path":"/elements/card-1","value":{"type":"Card","props":{"title":"Dashboard"},"children":["metric-1","chart-1"]}}
1061
+ {"op":"add","path":"/elements/metric-1","value":{"type":"Metric","props":{"label":"Revenue","valuePath":"analytics.revenue","format":"currency"},"children":[]}}
1062
+ {"op":"add","path":"/elements/chart-1","value":{"type":"Chart","props":{"type":"bar","dataPath":"analytics.salesByRegion"},"children":[]}}`);
940
1063
  lines.push("");
941
1064
  const components = catalog.data.components;
942
1065
  if (components) {
@@ -963,12 +1086,11 @@ function generatePrompt(catalog, options) {
963
1086
  lines.push("RULES:");
964
1087
  const baseRules = [
965
1088
  "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":{...}}',
1089
+ 'First line sets root: {"op":"add","path":"/root","value":"<root-key>"}',
1090
+ 'Then add each element: {"op":"add","path":"/elements/<key>","value":{...}}',
968
1091
  "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"
1092
+ "Each element value needs: type, props, children (array of child keys)",
1093
+ "Use unique keys for the element map entries (e.g., 'header', 'metric-1', 'chart-revenue')"
972
1094
  ];
973
1095
  const allRules = [...baseRules, ...customRules];
974
1096
  allRules.forEach((rule, i) => {
@@ -1124,22 +1246,18 @@ function createCatalog(config) {
1124
1246
  const componentSchemas = componentNames.map((componentName) => {
1125
1247
  const def = components[componentName];
1126
1248
  return import_zod6.z.object({
1127
- key: import_zod6.z.string(),
1128
1249
  type: import_zod6.z.literal(componentName),
1129
1250
  props: def.props,
1130
1251
  children: import_zod6.z.array(import_zod6.z.string()).optional(),
1131
- parentKey: import_zod6.z.string().nullable().optional(),
1132
1252
  visible: VisibilityConditionSchema.optional()
1133
1253
  });
1134
1254
  });
1135
1255
  let elementSchema;
1136
1256
  if (componentSchemas.length === 0) {
1137
1257
  elementSchema = import_zod6.z.object({
1138
- key: import_zod6.z.string(),
1139
1258
  type: import_zod6.z.string(),
1140
1259
  props: import_zod6.z.record(import_zod6.z.string(), import_zod6.z.unknown()),
1141
1260
  children: import_zod6.z.array(import_zod6.z.string()).optional(),
1142
- parentKey: import_zod6.z.string().nullable().optional(),
1143
1261
  visible: VisibilityConditionSchema.optional()
1144
1262
  });
1145
1263
  } else if (componentSchemas.length === 1) {
@@ -1360,21 +1478,21 @@ function generateSystemPrompt(catalog, options = {}) {
1360
1478
  }
1361
1479
  lines.push("");
1362
1480
  }
1363
- lines.push("OUTPUT FORMAT (JSONL):");
1364
- lines.push('{"op":"set","path":"/root","value":"element-key"}');
1481
+ lines.push("OUTPUT FORMAT (JSONL, RFC 6902 JSON Patch):");
1482
+ lines.push('{"op":"add","path":"/root","value":"element-key"}');
1365
1483
  lines.push(
1366
- '{"op":"add","path":"/elements/key","value":{"key":"...","type":"...","props":{...},"children":[...]}}'
1484
+ '{"op":"add","path":"/elements/key","value":{"type":"...","props":{...},"children":[...]}}'
1367
1485
  );
1368
1486
  lines.push('{"op":"remove","path":"/elements/key"}');
1369
1487
  lines.push("");
1370
1488
  lines.push("RULES:");
1371
1489
  const baseRules = [
1372
- "First line sets /root to root element key",
1373
- "Add elements with /elements/{key}",
1490
+ 'First line sets /root to root element key: {"op":"add","path":"/root","value":"<key>"}',
1491
+ 'Add elements with /elements/{key}: {"op":"add","path":"/elements/<key>","value":{...}}',
1374
1492
  "Remove elements with op:remove - also update the parent's children array to exclude the removed key",
1375
1493
  "Children array contains string keys, not objects",
1376
1494
  "Parent first, then children",
1377
- "Each element needs: key, type, props",
1495
+ "Each element needs: type, props",
1378
1496
  "ONLY use props listed above - never invent new props"
1379
1497
  ];
1380
1498
  const allRules = [...baseRules, ...customRules];
@@ -1405,6 +1523,7 @@ function generateSystemPrompt(catalog, options = {}) {
1405
1523
  ValidationConfigSchema,
1406
1524
  VisibilityConditionSchema,
1407
1525
  action,
1526
+ addByPath,
1408
1527
  applySpecStreamPatch,
1409
1528
  builtInValidationFunctions,
1410
1529
  check,
@@ -1422,6 +1541,7 @@ function generateSystemPrompt(catalog, options = {}) {
1422
1541
  getByPath,
1423
1542
  interpolateString,
1424
1543
  parseSpecStreamLine,
1544
+ removeByPath,
1425
1545
  resolveAction,
1426
1546
  resolveDynamicValue,
1427
1547
  runValidation,