11import {
22 FormanSchemaExtendedOptions ,
33 FormanSchemaField ,
4+ FormanSchemaFieldState ,
45 FormanSchemaFieldType ,
56 FormanSchemaOption ,
67 FormanSchemaOptionGroup ,
@@ -9,7 +10,39 @@ import {
910/**
1011 * Visual types are not a real input fields, they are used to display information in the UI.
1112 */
12- export const FORMAN_VISUAL_TYPES = [ 'banner' , 'markdown' , 'html' , 'separator' ] ;
13+ export const FORMAN_VISUAL_TYPES = [ 'banner' , 'markdown' , 'html' , 'separator' ] as const ;
14+
15+ /**
16+ * Type guard to check if a field type is a visual type.
17+ * @param type The field type to check
18+ * @returns true if the type is a visual type
19+ */
20+ export function isVisualType ( type : FormanSchemaFieldType ) : type is ( typeof FORMAN_VISUAL_TYPES ) [ number ] {
21+ return ( FORMAN_VISUAL_TYPES as readonly string [ ] ) . includes ( type ) ;
22+ }
23+
24+ /**
25+ * Reference types are types of type select that reference external resources.
26+ */
27+ export const FORMAN_REFERENCE_TYPES = [
28+ 'account' ,
29+ 'hook' ,
30+ 'device' ,
31+ 'keychain' ,
32+ 'datastore' ,
33+ 'aiagent' ,
34+ 'udt' ,
35+ 'scenario' ,
36+ ] as const ;
37+
38+ /**
39+ * Type guard to check if a field type is a reference type.
40+ * @param type The field type to check
41+ * @returns true if the type is a reference type
42+ */
43+ export function isReferenceType ( type : FormanSchemaFieldType ) : type is ( typeof FORMAN_REFERENCE_TYPES ) [ number ] {
44+ return ( FORMAN_REFERENCE_TYPES as readonly string [ ] ) . includes ( type ) ;
45+ }
1346
1447/**
1548 * Utility function to handle empty strings by converting them to undefined.
@@ -64,8 +97,10 @@ export const API_ENDPOINTS = {
6497 aiagent : 'api://ai-agents/v1/agents' ,
6598 datastore : 'api://data-stores' ,
6699 hook : 'api://hooks/{{kind}}' ,
100+ device : 'api://devices' ,
67101 keychain : 'api://keys/{{kind}}' ,
68102 udt : 'api://data-structures' ,
103+ scenario : 'api://scenario-list' ,
69104} as const ;
70105
71106/**
@@ -94,3 +129,100 @@ export function normalizeFormanFieldType(field: FormanSchemaField): FormanSchema
94129 } ,
95130 } ;
96131}
132+
133+ /**
134+ * Transforms a flat array of domain/path/state items into a nested object structure.
135+ * Intermediate path levels are placed in a 'nested' property.
136+ * @param items Array of items with domain, path, and state properties
137+ * @returns Nested object structure organized by domain
138+ */
139+ export function buildRestoreStructure (
140+ items : Array < {
141+ domain : string ;
142+ path : ( string | number ) [ ] ;
143+ state : Omit < FormanSchemaFieldState , 'nested' | 'items' > ;
144+ } > ,
145+ ) : Record < string , FormanSchemaFieldState > {
146+ const result : Record < string , Record < string , FormanSchemaFieldState > > = { } ;
147+
148+ for ( const item of items ) {
149+ const { domain, path, state } = item ;
150+
151+ // Ensure domain exists
152+ if ( ! result [ domain ] ) {
153+ result [ domain ] = { } ;
154+ }
155+
156+ let current : Record < string , FormanSchemaFieldState > | Record < string , FormanSchemaFieldState > [ ] = result [ domain ] ;
157+
158+ // Navigate through the path
159+ for ( const [ index , key ] of path . entries ( ) ) {
160+ const nextKey = path [ index + 1 ] ;
161+ const isLastElement = index === path . length - 1 ;
162+ const nextIsNumber = typeof nextKey === 'number' ;
163+
164+ if ( Array . isArray ( current ) ) {
165+ // We're in an array context
166+ if ( typeof key !== 'number' ) {
167+ throw new Error ( 'Invalid path' ) ;
168+ }
169+ // Ensure the array item exists
170+ if ( ! current [ key ] ) {
171+ current [ key ] = { } ;
172+ }
173+
174+ if ( nextIsNumber ) {
175+ // Next level is also an array - create nested array structure
176+ if ( ! current [ key ] . value ) {
177+ current [ key ] . value = {
178+ mode : 'chose' ,
179+ items : [ ] ,
180+ } ;
181+ }
182+ if ( ! current [ key ] . value . items ) {
183+ current [ key ] . value . items = [ ] ;
184+ }
185+ current = current [ key ] . value . items ;
186+ } else if ( isLastElement ) {
187+ // Last element - merge the state
188+ Object . assign ( current [ key ] , state ) ;
189+ } else {
190+ // Move to the object at this array index
191+ current = current [ key ] ;
192+ }
193+ } else {
194+ // We're in an object context
195+ if ( typeof key !== 'string' ) {
196+ throw new Error ( 'Invalid path' ) ;
197+ }
198+ // Ensure the object item exists
199+ if ( ! current [ key ] ) {
200+ current [ key ] = { } ;
201+ }
202+
203+ if ( isLastElement ) {
204+ // Last element - merge the state
205+ Object . assign ( current [ key ] , state ) ;
206+ } else {
207+ // Intermediate element - ensure it exists and navigate
208+ if ( nextIsNumber ) {
209+ // Next level is an array - create array structure
210+ if ( ! current [ key ] . items ) {
211+ current [ key ] . items = [ ] ;
212+ current [ key ] . mode = 'chose' ;
213+ }
214+ current = current [ key ] . items ;
215+ } else {
216+ // Next level is an object - navigate to nested
217+ if ( ! current [ key ] . nested ) {
218+ current [ key ] . nested = { } ;
219+ }
220+ current = current [ key ] . nested ;
221+ }
222+ }
223+ }
224+ }
225+ }
226+
227+ return result ;
228+ }
0 commit comments