@formspec/runtime 0.1.0-alpha.21 → 0.1.0-alpha.26

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/resolvers.ts"],"sourcesContent":["/**\n * `@formspec/runtime` - Runtime helpers for FormSpec\n *\n * This package provides utilities for working with FormSpec forms at runtime:\n * - `defineResolvers()` - Type-safe resolver definitions for dynamic enum fields\n *\n * @example\n * ```typescript\n * import { defineResolvers } from \"@formspec/runtime\";\n * import { formspec, field } from \"@formspec/dsl\";\n *\n * // Define a form with dynamic enum fields\n * const form = formspec(\n * field.dynamicEnum(\"country\", \"countries\", { label: \"Country\" }),\n * );\n *\n * // Define resolvers for the form's data sources\n * const resolvers = defineResolvers(form, {\n * countries: async () => ({\n * options: [\n * { value: \"us\", label: \"United States\" },\n * { value: \"ca\", label: \"Canada\" },\n * ],\n * validity: \"valid\",\n * }),\n * });\n *\n * // Use the resolver\n * const result = await resolvers.get(\"countries\")();\n * console.log(result.options); // [{ value: \"us\", ... }, { value: \"ca\", ... }]\n * ```\n *\n * @packageDocumentation\n */\n\nexport {\n defineResolvers,\n type Resolver,\n type ResolverMap,\n type ResolverRegistry,\n} from \"./resolvers.js\";\n","/**\n * Resolver helpers for dynamic FormSpec data.\n *\n * Resolvers are functions that fetch options for dynamic enum fields\n * at runtime. This module provides type-safe utilities for defining\n * and using resolvers.\n */\n\nimport type {\n DataSourceRegistry,\n FetchOptionsResponse,\n FormElement,\n FormSpec,\n DynamicEnumField,\n Group,\n Conditional,\n} from \"@formspec/core\";\n\n/**\n * A resolver function that fetches options for a data source.\n *\n * @typeParam Source - The data source key from DataSourceRegistry\n * @typeParam T - The data type for options (from DataSourceRegistry)\n *\n * @public\n */\nexport type Resolver<Source extends keyof DataSourceRegistry, T = DataSourceRegistry[Source]> = (\n params?: Record<string, unknown>\n) => Promise<FetchOptionsResponse<T>>;\n\n/**\n * Extracts all dynamic enum source keys from a form's elements.\n */\ntype ExtractDynamicSources<E> =\n E extends DynamicEnumField<string, infer S>\n ? S\n : E extends Group<infer Elements>\n ? ExtractDynamicSourcesFromArray<Elements>\n : E extends Conditional<string, unknown, infer Elements>\n ? ExtractDynamicSourcesFromArray<Elements>\n : never;\n\ntype ExtractDynamicSourcesFromArray<Elements> = Elements extends readonly [\n infer First,\n ...infer Rest,\n]\n ? ExtractDynamicSources<First> | ExtractDynamicSourcesFromArray<Rest>\n : never;\n\n/**\n * Map of resolver functions for a form's dynamic data sources.\n *\n * @public\n */\nexport type ResolverMap<Sources extends string> = {\n [S in Sources]: S extends keyof DataSourceRegistry\n ? Resolver<S>\n : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;\n};\n\n/**\n * A resolver registry that provides type-safe access to resolvers.\n *\n * @public\n */\nexport interface ResolverRegistry<Sources extends string> {\n /**\n * Gets a resolver by data source name.\n */\n get<S extends Sources>(\n source: S\n ): S extends keyof DataSourceRegistry\n ? Resolver<S>\n : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;\n\n /**\n * Checks if a resolver exists for a data source.\n */\n has(source: string): boolean;\n\n /**\n * Gets all registered data source names.\n */\n sources(): Sources[];\n}\n\n/**\n * Extracts all dynamic enum field sources from form elements.\n */\nfunction extractSources(elements: readonly FormElement[]): Set<string> {\n const sources = new Set<string>();\n\n function visit(el: FormElement): void {\n if (el._type === \"field\" && el._field === \"dynamic_enum\") {\n // After checking _field, we know this is a DynamicEnumField\n const dynamicField: DynamicEnumField<string, string> = el;\n sources.add(dynamicField.source);\n } else if (el._type === \"group\") {\n (el as Group<readonly FormElement[]>).elements.forEach(visit);\n } else if (el._type === \"conditional\") {\n (el as Conditional<string, unknown, readonly FormElement[]>).elements.forEach(visit);\n }\n }\n\n elements.forEach(visit);\n return sources;\n}\n\n/**\n * Defines resolvers for a form's dynamic data sources.\n *\n * This function provides type-safe resolver definitions that match\n * the form's dynamic enum fields.\n *\n * @example\n * ```typescript\n * declare module \"@formspec/core\" {\n * interface DataSourceRegistry {\n * countries: { id: string; code: string; name: string };\n * }\n * }\n *\n * const form = formspec(\n * field.dynamicEnum(\"country\", \"countries\", { label: \"Country\" }),\n * );\n *\n * const resolvers = defineResolvers(form, {\n * countries: async () => ({\n * options: [\n * { value: \"us\", label: \"United States\", data: { id: \"us\", code: \"US\", name: \"United States\" } },\n * { value: \"ca\", label: \"Canada\", data: { id: \"ca\", code: \"CA\", name: \"Canada\" } },\n * ],\n * validity: \"valid\",\n * }),\n * });\n *\n * // Use the resolver\n * const result = await resolvers.get(\"countries\")();\n * ```\n *\n * @param form - The FormSpec containing dynamic enum fields\n * @param resolvers - Map of resolver functions for each data source\n * @returns A ResolverRegistry for type-safe access to resolvers\n *\n * @public\n */\nexport function defineResolvers<\n E extends readonly FormElement[],\n Sources extends string = ExtractDynamicSourcesFromArray<E> & string,\n>(form: FormSpec<E>, resolvers: ResolverMap<Sources>): ResolverRegistry<Sources> {\n const sourceSet = extractSources(form.elements);\n const resolverMap = new Map<string, Resolver<keyof DataSourceRegistry>>(\n Object.entries(resolvers) as [string, Resolver<keyof DataSourceRegistry>][]\n );\n\n // Validate that all sources have resolvers\n for (const source of sourceSet) {\n if (!resolverMap.has(source)) {\n console.warn(`Missing resolver for data source: ${source}`);\n }\n }\n\n return {\n get<S extends Sources>(source: S) {\n const resolver = resolverMap.get(source);\n if (resolver === undefined) {\n throw new Error(`No resolver found for data source: ${source}`);\n }\n return resolver as S extends keyof DataSourceRegistry\n ? Resolver<S>\n : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;\n },\n\n has(source: string): boolean {\n return resolverMap.has(source);\n },\n\n sources(): Sources[] {\n return Array.from(resolverMap.keys()) as Sources[];\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACyFA,SAAS,eAAe,UAA+C;AACrE,QAAM,UAAU,oBAAI,IAAY;AAEhC,WAAS,MAAM,IAAuB;AACpC,QAAI,GAAG,UAAU,WAAW,GAAG,WAAW,gBAAgB;AAExD,YAAM,eAAiD;AACvD,cAAQ,IAAI,aAAa,MAAM;AAAA,IACjC,WAAW,GAAG,UAAU,SAAS;AAC/B,MAAC,GAAqC,SAAS,QAAQ,KAAK;AAAA,IAC9D,WAAW,GAAG,UAAU,eAAe;AACrC,MAAC,GAA4D,SAAS,QAAQ,KAAK;AAAA,IACrF;AAAA,EACF;AAEA,WAAS,QAAQ,KAAK;AACtB,SAAO;AACT;AAwCO,SAAS,gBAGd,MAAmB,WAA4D;AAC/E,QAAM,YAAY,eAAe,KAAK,QAAQ;AAC9C,QAAM,cAAc,IAAI;AAAA,IACtB,OAAO,QAAQ,SAAS;AAAA,EAC1B;AAGA,aAAW,UAAU,WAAW;AAC9B,QAAI,CAAC,YAAY,IAAI,MAAM,GAAG;AAC5B,cAAQ,KAAK,qCAAqC,MAAM,EAAE;AAAA,IAC5D;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAuB,QAAW;AAChC,YAAM,WAAW,YAAY,IAAI,MAAM;AACvC,UAAI,aAAa,QAAW;AAC1B,cAAM,IAAI,MAAM,sCAAsC,MAAM,EAAE;AAAA,MAChE;AACA,aAAO;AAAA,IAGT;AAAA,IAEA,IAAI,QAAyB;AAC3B,aAAO,YAAY,IAAI,MAAM;AAAA,IAC/B;AAAA,IAEA,UAAqB;AACnB,aAAO,MAAM,KAAK,YAAY,KAAK,CAAC;AAAA,IACtC;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/resolvers.ts"],"sourcesContent":["/**\n * `@formspec/runtime` - Runtime helpers for FormSpec\n *\n * This package provides utilities for working with FormSpec forms at runtime:\n * - `defineResolvers()` - Type-safe resolver definitions for dynamic enum fields\n *\n * @example\n * ```typescript\n * import { defineResolvers } from \"@formspec/runtime\";\n * import { formspec, field } from \"@formspec/dsl\";\n *\n * // Define a form with dynamic enum fields\n * const form = formspec(\n * field.dynamicEnum(\"country\", \"countries\", { label: \"Country\" }),\n * );\n *\n * // Define resolvers for the form's data sources\n * const resolvers = defineResolvers(form, {\n * countries: async () => ({\n * options: [\n * { value: \"us\", label: \"United States\" },\n * { value: \"ca\", label: \"Canada\" },\n * ],\n * validity: \"valid\",\n * }),\n * });\n *\n * // Use the resolver\n * const result = await resolvers.get(\"countries\")();\n * console.log(result.options); // [{ value: \"us\", ... }, { value: \"ca\", ... }]\n * ```\n *\n * @packageDocumentation\n */\n\nexport {\n defineResolvers,\n type ExtractDynamicSources,\n type ExtractDynamicSourcesFromArray,\n type Resolver,\n type ResolverMap,\n type ResolverRegistry,\n type ResolverSourcesForForm,\n} from \"./resolvers.js\";\n","/**\n * Resolver helpers for dynamic FormSpec data.\n *\n * Resolvers are functions that fetch options for dynamic enum fields\n * at runtime. This module provides type-safe utilities for defining\n * and using resolvers.\n */\n\nimport type {\n DataSourceRegistry,\n FetchOptionsResponse,\n FormElement,\n FormSpec,\n DynamicEnumField,\n Group,\n Conditional,\n} from \"@formspec/core\";\n\n/**\n * A resolver function that fetches options for a data source.\n *\n * @typeParam Source - The data source key from DataSourceRegistry\n * @typeParam T - The data type for options (from DataSourceRegistry)\n *\n * @public\n */\nexport type Resolver<Source extends keyof DataSourceRegistry, T = DataSourceRegistry[Source]> = (\n params?: Record<string, unknown>\n) => Promise<FetchOptionsResponse<T>>;\n\n/**\n * Extracts dynamic data-source names referenced by a single FormSpec element.\n *\n * @public\n */\nexport type ExtractDynamicSources<E> =\n E extends DynamicEnumField<string, infer S>\n ? S\n : E extends Group<infer Elements>\n ? ExtractDynamicSourcesFromArray<Elements>\n : E extends Conditional<string, unknown, infer Elements>\n ? ExtractDynamicSourcesFromArray<Elements>\n : never;\n\n/**\n * Extracts dynamic data-source names referenced anywhere in an element array.\n *\n * @public\n */\nexport type ExtractDynamicSourcesFromArray<Elements> = Elements extends readonly [\n infer First,\n ...infer Rest,\n]\n ? ExtractDynamicSources<First> | ExtractDynamicSourcesFromArray<Rest>\n : never;\n\n/**\n * Derives the resolver source-key union for a FormSpec element array.\n *\n * @public\n */\nexport type ResolverSourcesForForm<E extends readonly FormElement[]> =\n ExtractDynamicSourcesFromArray<E> & string;\n\n/**\n * Map of resolver functions for a form's dynamic data sources.\n *\n * @public\n */\nexport type ResolverMap<Sources extends string> = {\n [S in Sources]: S extends keyof DataSourceRegistry\n ? Resolver<S>\n : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;\n};\n\n/**\n * A resolver registry that provides type-safe access to resolvers.\n *\n * @public\n */\nexport interface ResolverRegistry<Sources extends string> {\n /**\n * Gets a resolver by data source name.\n */\n get<S extends Sources>(\n source: S\n ): S extends keyof DataSourceRegistry\n ? Resolver<S>\n : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;\n\n /**\n * Checks if a resolver exists for a data source.\n */\n has(source: string): boolean;\n\n /**\n * Gets all registered data source names.\n */\n sources(): Sources[];\n}\n\n/**\n * Extracts all dynamic enum field sources from form elements.\n */\nfunction extractSources(elements: readonly FormElement[]): Set<string> {\n const sources = new Set<string>();\n\n function visit(el: FormElement): void {\n if (el._type === \"field\" && el._field === \"dynamic_enum\") {\n // After checking _field, we know this is a DynamicEnumField\n const dynamicField: DynamicEnumField<string, string> = el;\n sources.add(dynamicField.source);\n } else if (el._type === \"group\") {\n (el as Group<readonly FormElement[]>).elements.forEach(visit);\n } else if (el._type === \"conditional\") {\n (el as Conditional<string, unknown, readonly FormElement[]>).elements.forEach(visit);\n }\n }\n\n elements.forEach(visit);\n return sources;\n}\n\n/**\n * Defines resolvers for a form's dynamic data sources.\n *\n * This function provides type-safe resolver definitions that match\n * the form's dynamic enum fields.\n *\n * @example\n * ```typescript\n * declare module \"@formspec/core\" {\n * interface DataSourceRegistry {\n * countries: { id: string; code: string; name: string };\n * }\n * }\n *\n * const form = formspec(\n * field.dynamicEnum(\"country\", \"countries\", { label: \"Country\" }),\n * );\n *\n * const resolvers = defineResolvers(form, {\n * countries: async () => ({\n * options: [\n * { value: \"us\", label: \"United States\", data: { id: \"us\", code: \"US\", name: \"United States\" } },\n * { value: \"ca\", label: \"Canada\", data: { id: \"ca\", code: \"CA\", name: \"Canada\" } },\n * ],\n * validity: \"valid\",\n * }),\n * });\n *\n * // Use the resolver\n * const result = await resolvers.get(\"countries\")();\n * ```\n *\n * @param form - The FormSpec containing dynamic enum fields\n * @param resolvers - Map of resolver functions for each data source\n * @returns A ResolverRegistry for type-safe access to resolvers\n *\n * @public\n */\nexport function defineResolvers<\n E extends readonly FormElement[],\n Sources extends string = ResolverSourcesForForm<E>,\n>(form: FormSpec<E>, resolvers: ResolverMap<Sources>): ResolverRegistry<Sources> {\n const sourceSet = extractSources(form.elements);\n const resolverMap = new Map<string, Resolver<keyof DataSourceRegistry>>(\n Object.entries(resolvers) as [string, Resolver<keyof DataSourceRegistry>][]\n );\n\n // Validate that all sources have resolvers\n for (const source of sourceSet) {\n if (!resolverMap.has(source)) {\n console.warn(`Missing resolver for data source: ${source}`);\n }\n }\n\n return {\n get<S extends Sources>(source: S) {\n const resolver = resolverMap.get(source);\n if (resolver === undefined) {\n throw new Error(`No resolver found for data source: ${source}`);\n }\n return resolver as S extends keyof DataSourceRegistry\n ? Resolver<S>\n : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;\n },\n\n has(source: string): boolean {\n return resolverMap.has(source);\n },\n\n sources(): Sources[] {\n return Array.from(resolverMap.keys()) as Sources[];\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACwGA,SAAS,eAAe,UAA+C;AACrE,QAAM,UAAU,oBAAI,IAAY;AAEhC,WAAS,MAAM,IAAuB;AACpC,QAAI,GAAG,UAAU,WAAW,GAAG,WAAW,gBAAgB;AAExD,YAAM,eAAiD;AACvD,cAAQ,IAAI,aAAa,MAAM;AAAA,IACjC,WAAW,GAAG,UAAU,SAAS;AAC/B,MAAC,GAAqC,SAAS,QAAQ,KAAK;AAAA,IAC9D,WAAW,GAAG,UAAU,eAAe;AACrC,MAAC,GAA4D,SAAS,QAAQ,KAAK;AAAA,IACrF;AAAA,EACF;AAEA,WAAS,QAAQ,KAAK;AACtB,SAAO;AACT;AAwCO,SAAS,gBAGd,MAAmB,WAA4D;AAC/E,QAAM,YAAY,eAAe,KAAK,QAAQ;AAC9C,QAAM,cAAc,IAAI;AAAA,IACtB,OAAO,QAAQ,SAAS;AAAA,EAC1B;AAGA,aAAW,UAAU,WAAW;AAC9B,QAAI,CAAC,YAAY,IAAI,MAAM,GAAG;AAC5B,cAAQ,KAAK,qCAAqC,MAAM,EAAE;AAAA,IAC5D;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAuB,QAAW;AAChC,YAAM,WAAW,YAAY,IAAI,MAAM;AACvC,UAAI,aAAa,QAAW;AAC1B,cAAM,IAAI,MAAM,sCAAsC,MAAM,EAAE;AAAA,MAChE;AACA,aAAO;AAAA,IAGT;AAAA,IAEA,IAAI,QAAyB;AAC3B,aAAO,YAAY,IAAI,MAAM;AAAA,IAC/B;AAAA,IAEA,UAAqB;AACnB,aAAO,MAAM,KAAK,YAAY,KAAK,CAAC;AAAA,IACtC;AAAA,EACF;AACF;","names":[]}
package/dist/index.d.ts CHANGED
@@ -32,5 +32,5 @@
32
32
  *
33
33
  * @packageDocumentation
34
34
  */
35
- export { defineResolvers, type Resolver, type ResolverMap, type ResolverRegistry, } from "./resolvers.js";
35
+ export { defineResolvers, type ExtractDynamicSources, type ExtractDynamicSourcesFromArray, type Resolver, type ResolverMap, type ResolverRegistry, type ResolverSourcesForForm, } from "./resolvers.js";
36
36
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EACL,eAAe,EACf,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,EACnC,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,GAC5B,MAAM,gBAAgB,CAAC"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/resolvers.ts"],"sourcesContent":["/**\n * Resolver helpers for dynamic FormSpec data.\n *\n * Resolvers are functions that fetch options for dynamic enum fields\n * at runtime. This module provides type-safe utilities for defining\n * and using resolvers.\n */\n\nimport type {\n DataSourceRegistry,\n FetchOptionsResponse,\n FormElement,\n FormSpec,\n DynamicEnumField,\n Group,\n Conditional,\n} from \"@formspec/core\";\n\n/**\n * A resolver function that fetches options for a data source.\n *\n * @typeParam Source - The data source key from DataSourceRegistry\n * @typeParam T - The data type for options (from DataSourceRegistry)\n *\n * @public\n */\nexport type Resolver<Source extends keyof DataSourceRegistry, T = DataSourceRegistry[Source]> = (\n params?: Record<string, unknown>\n) => Promise<FetchOptionsResponse<T>>;\n\n/**\n * Extracts all dynamic enum source keys from a form's elements.\n */\ntype ExtractDynamicSources<E> =\n E extends DynamicEnumField<string, infer S>\n ? S\n : E extends Group<infer Elements>\n ? ExtractDynamicSourcesFromArray<Elements>\n : E extends Conditional<string, unknown, infer Elements>\n ? ExtractDynamicSourcesFromArray<Elements>\n : never;\n\ntype ExtractDynamicSourcesFromArray<Elements> = Elements extends readonly [\n infer First,\n ...infer Rest,\n]\n ? ExtractDynamicSources<First> | ExtractDynamicSourcesFromArray<Rest>\n : never;\n\n/**\n * Map of resolver functions for a form's dynamic data sources.\n *\n * @public\n */\nexport type ResolverMap<Sources extends string> = {\n [S in Sources]: S extends keyof DataSourceRegistry\n ? Resolver<S>\n : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;\n};\n\n/**\n * A resolver registry that provides type-safe access to resolvers.\n *\n * @public\n */\nexport interface ResolverRegistry<Sources extends string> {\n /**\n * Gets a resolver by data source name.\n */\n get<S extends Sources>(\n source: S\n ): S extends keyof DataSourceRegistry\n ? Resolver<S>\n : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;\n\n /**\n * Checks if a resolver exists for a data source.\n */\n has(source: string): boolean;\n\n /**\n * Gets all registered data source names.\n */\n sources(): Sources[];\n}\n\n/**\n * Extracts all dynamic enum field sources from form elements.\n */\nfunction extractSources(elements: readonly FormElement[]): Set<string> {\n const sources = new Set<string>();\n\n function visit(el: FormElement): void {\n if (el._type === \"field\" && el._field === \"dynamic_enum\") {\n // After checking _field, we know this is a DynamicEnumField\n const dynamicField: DynamicEnumField<string, string> = el;\n sources.add(dynamicField.source);\n } else if (el._type === \"group\") {\n (el as Group<readonly FormElement[]>).elements.forEach(visit);\n } else if (el._type === \"conditional\") {\n (el as Conditional<string, unknown, readonly FormElement[]>).elements.forEach(visit);\n }\n }\n\n elements.forEach(visit);\n return sources;\n}\n\n/**\n * Defines resolvers for a form's dynamic data sources.\n *\n * This function provides type-safe resolver definitions that match\n * the form's dynamic enum fields.\n *\n * @example\n * ```typescript\n * declare module \"@formspec/core\" {\n * interface DataSourceRegistry {\n * countries: { id: string; code: string; name: string };\n * }\n * }\n *\n * const form = formspec(\n * field.dynamicEnum(\"country\", \"countries\", { label: \"Country\" }),\n * );\n *\n * const resolvers = defineResolvers(form, {\n * countries: async () => ({\n * options: [\n * { value: \"us\", label: \"United States\", data: { id: \"us\", code: \"US\", name: \"United States\" } },\n * { value: \"ca\", label: \"Canada\", data: { id: \"ca\", code: \"CA\", name: \"Canada\" } },\n * ],\n * validity: \"valid\",\n * }),\n * });\n *\n * // Use the resolver\n * const result = await resolvers.get(\"countries\")();\n * ```\n *\n * @param form - The FormSpec containing dynamic enum fields\n * @param resolvers - Map of resolver functions for each data source\n * @returns A ResolverRegistry for type-safe access to resolvers\n *\n * @public\n */\nexport function defineResolvers<\n E extends readonly FormElement[],\n Sources extends string = ExtractDynamicSourcesFromArray<E> & string,\n>(form: FormSpec<E>, resolvers: ResolverMap<Sources>): ResolverRegistry<Sources> {\n const sourceSet = extractSources(form.elements);\n const resolverMap = new Map<string, Resolver<keyof DataSourceRegistry>>(\n Object.entries(resolvers) as [string, Resolver<keyof DataSourceRegistry>][]\n );\n\n // Validate that all sources have resolvers\n for (const source of sourceSet) {\n if (!resolverMap.has(source)) {\n console.warn(`Missing resolver for data source: ${source}`);\n }\n }\n\n return {\n get<S extends Sources>(source: S) {\n const resolver = resolverMap.get(source);\n if (resolver === undefined) {\n throw new Error(`No resolver found for data source: ${source}`);\n }\n return resolver as S extends keyof DataSourceRegistry\n ? Resolver<S>\n : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;\n },\n\n has(source: string): boolean {\n return resolverMap.has(source);\n },\n\n sources(): Sources[] {\n return Array.from(resolverMap.keys()) as Sources[];\n },\n };\n}\n"],"mappings":";AAyFA,SAAS,eAAe,UAA+C;AACrE,QAAM,UAAU,oBAAI,IAAY;AAEhC,WAAS,MAAM,IAAuB;AACpC,QAAI,GAAG,UAAU,WAAW,GAAG,WAAW,gBAAgB;AAExD,YAAM,eAAiD;AACvD,cAAQ,IAAI,aAAa,MAAM;AAAA,IACjC,WAAW,GAAG,UAAU,SAAS;AAC/B,MAAC,GAAqC,SAAS,QAAQ,KAAK;AAAA,IAC9D,WAAW,GAAG,UAAU,eAAe;AACrC,MAAC,GAA4D,SAAS,QAAQ,KAAK;AAAA,IACrF;AAAA,EACF;AAEA,WAAS,QAAQ,KAAK;AACtB,SAAO;AACT;AAwCO,SAAS,gBAGd,MAAmB,WAA4D;AAC/E,QAAM,YAAY,eAAe,KAAK,QAAQ;AAC9C,QAAM,cAAc,IAAI;AAAA,IACtB,OAAO,QAAQ,SAAS;AAAA,EAC1B;AAGA,aAAW,UAAU,WAAW;AAC9B,QAAI,CAAC,YAAY,IAAI,MAAM,GAAG;AAC5B,cAAQ,KAAK,qCAAqC,MAAM,EAAE;AAAA,IAC5D;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAuB,QAAW;AAChC,YAAM,WAAW,YAAY,IAAI,MAAM;AACvC,UAAI,aAAa,QAAW;AAC1B,cAAM,IAAI,MAAM,sCAAsC,MAAM,EAAE;AAAA,MAChE;AACA,aAAO;AAAA,IAGT;AAAA,IAEA,IAAI,QAAyB;AAC3B,aAAO,YAAY,IAAI,MAAM;AAAA,IAC/B;AAAA,IAEA,UAAqB;AACnB,aAAO,MAAM,KAAK,YAAY,KAAK,CAAC;AAAA,IACtC;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/resolvers.ts"],"sourcesContent":["/**\n * Resolver helpers for dynamic FormSpec data.\n *\n * Resolvers are functions that fetch options for dynamic enum fields\n * at runtime. This module provides type-safe utilities for defining\n * and using resolvers.\n */\n\nimport type {\n DataSourceRegistry,\n FetchOptionsResponse,\n FormElement,\n FormSpec,\n DynamicEnumField,\n Group,\n Conditional,\n} from \"@formspec/core\";\n\n/**\n * A resolver function that fetches options for a data source.\n *\n * @typeParam Source - The data source key from DataSourceRegistry\n * @typeParam T - The data type for options (from DataSourceRegistry)\n *\n * @public\n */\nexport type Resolver<Source extends keyof DataSourceRegistry, T = DataSourceRegistry[Source]> = (\n params?: Record<string, unknown>\n) => Promise<FetchOptionsResponse<T>>;\n\n/**\n * Extracts dynamic data-source names referenced by a single FormSpec element.\n *\n * @public\n */\nexport type ExtractDynamicSources<E> =\n E extends DynamicEnumField<string, infer S>\n ? S\n : E extends Group<infer Elements>\n ? ExtractDynamicSourcesFromArray<Elements>\n : E extends Conditional<string, unknown, infer Elements>\n ? ExtractDynamicSourcesFromArray<Elements>\n : never;\n\n/**\n * Extracts dynamic data-source names referenced anywhere in an element array.\n *\n * @public\n */\nexport type ExtractDynamicSourcesFromArray<Elements> = Elements extends readonly [\n infer First,\n ...infer Rest,\n]\n ? ExtractDynamicSources<First> | ExtractDynamicSourcesFromArray<Rest>\n : never;\n\n/**\n * Derives the resolver source-key union for a FormSpec element array.\n *\n * @public\n */\nexport type ResolverSourcesForForm<E extends readonly FormElement[]> =\n ExtractDynamicSourcesFromArray<E> & string;\n\n/**\n * Map of resolver functions for a form's dynamic data sources.\n *\n * @public\n */\nexport type ResolverMap<Sources extends string> = {\n [S in Sources]: S extends keyof DataSourceRegistry\n ? Resolver<S>\n : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;\n};\n\n/**\n * A resolver registry that provides type-safe access to resolvers.\n *\n * @public\n */\nexport interface ResolverRegistry<Sources extends string> {\n /**\n * Gets a resolver by data source name.\n */\n get<S extends Sources>(\n source: S\n ): S extends keyof DataSourceRegistry\n ? Resolver<S>\n : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;\n\n /**\n * Checks if a resolver exists for a data source.\n */\n has(source: string): boolean;\n\n /**\n * Gets all registered data source names.\n */\n sources(): Sources[];\n}\n\n/**\n * Extracts all dynamic enum field sources from form elements.\n */\nfunction extractSources(elements: readonly FormElement[]): Set<string> {\n const sources = new Set<string>();\n\n function visit(el: FormElement): void {\n if (el._type === \"field\" && el._field === \"dynamic_enum\") {\n // After checking _field, we know this is a DynamicEnumField\n const dynamicField: DynamicEnumField<string, string> = el;\n sources.add(dynamicField.source);\n } else if (el._type === \"group\") {\n (el as Group<readonly FormElement[]>).elements.forEach(visit);\n } else if (el._type === \"conditional\") {\n (el as Conditional<string, unknown, readonly FormElement[]>).elements.forEach(visit);\n }\n }\n\n elements.forEach(visit);\n return sources;\n}\n\n/**\n * Defines resolvers for a form's dynamic data sources.\n *\n * This function provides type-safe resolver definitions that match\n * the form's dynamic enum fields.\n *\n * @example\n * ```typescript\n * declare module \"@formspec/core\" {\n * interface DataSourceRegistry {\n * countries: { id: string; code: string; name: string };\n * }\n * }\n *\n * const form = formspec(\n * field.dynamicEnum(\"country\", \"countries\", { label: \"Country\" }),\n * );\n *\n * const resolvers = defineResolvers(form, {\n * countries: async () => ({\n * options: [\n * { value: \"us\", label: \"United States\", data: { id: \"us\", code: \"US\", name: \"United States\" } },\n * { value: \"ca\", label: \"Canada\", data: { id: \"ca\", code: \"CA\", name: \"Canada\" } },\n * ],\n * validity: \"valid\",\n * }),\n * });\n *\n * // Use the resolver\n * const result = await resolvers.get(\"countries\")();\n * ```\n *\n * @param form - The FormSpec containing dynamic enum fields\n * @param resolvers - Map of resolver functions for each data source\n * @returns A ResolverRegistry for type-safe access to resolvers\n *\n * @public\n */\nexport function defineResolvers<\n E extends readonly FormElement[],\n Sources extends string = ResolverSourcesForForm<E>,\n>(form: FormSpec<E>, resolvers: ResolverMap<Sources>): ResolverRegistry<Sources> {\n const sourceSet = extractSources(form.elements);\n const resolverMap = new Map<string, Resolver<keyof DataSourceRegistry>>(\n Object.entries(resolvers) as [string, Resolver<keyof DataSourceRegistry>][]\n );\n\n // Validate that all sources have resolvers\n for (const source of sourceSet) {\n if (!resolverMap.has(source)) {\n console.warn(`Missing resolver for data source: ${source}`);\n }\n }\n\n return {\n get<S extends Sources>(source: S) {\n const resolver = resolverMap.get(source);\n if (resolver === undefined) {\n throw new Error(`No resolver found for data source: ${source}`);\n }\n return resolver as S extends keyof DataSourceRegistry\n ? Resolver<S>\n : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;\n },\n\n has(source: string): boolean {\n return resolverMap.has(source);\n },\n\n sources(): Sources[] {\n return Array.from(resolverMap.keys()) as Sources[];\n },\n };\n}\n"],"mappings":";AAwGA,SAAS,eAAe,UAA+C;AACrE,QAAM,UAAU,oBAAI,IAAY;AAEhC,WAAS,MAAM,IAAuB;AACpC,QAAI,GAAG,UAAU,WAAW,GAAG,WAAW,gBAAgB;AAExD,YAAM,eAAiD;AACvD,cAAQ,IAAI,aAAa,MAAM;AAAA,IACjC,WAAW,GAAG,UAAU,SAAS;AAC/B,MAAC,GAAqC,SAAS,QAAQ,KAAK;AAAA,IAC9D,WAAW,GAAG,UAAU,eAAe;AACrC,MAAC,GAA4D,SAAS,QAAQ,KAAK;AAAA,IACrF;AAAA,EACF;AAEA,WAAS,QAAQ,KAAK;AACtB,SAAO;AACT;AAwCO,SAAS,gBAGd,MAAmB,WAA4D;AAC/E,QAAM,YAAY,eAAe,KAAK,QAAQ;AAC9C,QAAM,cAAc,IAAI;AAAA,IACtB,OAAO,QAAQ,SAAS;AAAA,EAC1B;AAGA,aAAW,UAAU,WAAW;AAC9B,QAAI,CAAC,YAAY,IAAI,MAAM,GAAG;AAC5B,cAAQ,KAAK,qCAAqC,MAAM,EAAE;AAAA,IAC5D;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAuB,QAAW;AAChC,YAAM,WAAW,YAAY,IAAI,MAAM;AACvC,UAAI,aAAa,QAAW;AAC1B,cAAM,IAAI,MAAM,sCAAsC,MAAM,EAAE;AAAA,MAChE;AACA,aAAO;AAAA,IAGT;AAAA,IAEA,IAAI,QAAyB;AAC3B,aAAO,YAAY,IAAI,MAAM;AAAA,IAC/B;AAAA,IAEA,UAAqB;AACnB,aAAO,MAAM,KAAK,YAAY,KAAK,CAAC;AAAA,IACtC;AAAA,EACF;AACF;","names":[]}
@@ -16,13 +16,26 @@ import type { DataSourceRegistry, FetchOptionsResponse, FormElement, FormSpec, D
16
16
  */
17
17
  export type Resolver<Source extends keyof DataSourceRegistry, T = DataSourceRegistry[Source]> = (params?: Record<string, unknown>) => Promise<FetchOptionsResponse<T>>;
18
18
  /**
19
- * Extracts all dynamic enum source keys from a form's elements.
19
+ * Extracts dynamic data-source names referenced by a single FormSpec element.
20
+ *
21
+ * @public
22
+ */
23
+ export type ExtractDynamicSources<E> = E extends DynamicEnumField<string, infer S> ? S : E extends Group<infer Elements> ? ExtractDynamicSourcesFromArray<Elements> : E extends Conditional<string, unknown, infer Elements> ? ExtractDynamicSourcesFromArray<Elements> : never;
24
+ /**
25
+ * Extracts dynamic data-source names referenced anywhere in an element array.
26
+ *
27
+ * @public
20
28
  */
21
- type ExtractDynamicSources<E> = E extends DynamicEnumField<string, infer S> ? S : E extends Group<infer Elements> ? ExtractDynamicSourcesFromArray<Elements> : E extends Conditional<string, unknown, infer Elements> ? ExtractDynamicSourcesFromArray<Elements> : never;
22
- type ExtractDynamicSourcesFromArray<Elements> = Elements extends readonly [
29
+ export type ExtractDynamicSourcesFromArray<Elements> = Elements extends readonly [
23
30
  infer First,
24
31
  ...infer Rest
25
32
  ] ? ExtractDynamicSources<First> | ExtractDynamicSourcesFromArray<Rest> : never;
33
+ /**
34
+ * Derives the resolver source-key union for a FormSpec element array.
35
+ *
36
+ * @public
37
+ */
38
+ export type ResolverSourcesForForm<E extends readonly FormElement[]> = ExtractDynamicSourcesFromArray<E> & string;
26
39
  /**
27
40
  * Map of resolver functions for a form's dynamic data sources.
28
41
  *
@@ -88,6 +101,5 @@ export interface ResolverRegistry<Sources extends string> {
88
101
  *
89
102
  * @public
90
103
  */
91
- export declare function defineResolvers<E extends readonly FormElement[], Sources extends string = ExtractDynamicSourcesFromArray<E> & string>(form: FormSpec<E>, resolvers: ResolverMap<Sources>): ResolverRegistry<Sources>;
92
- export {};
104
+ export declare function defineResolvers<E extends readonly FormElement[], Sources extends string = ResolverSourcesForForm<E>>(form: FormSpec<E>, resolvers: ResolverMap<Sources>): ResolverRegistry<Sources>;
93
105
  //# sourceMappingURL=resolvers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../src/resolvers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,WAAW,EACX,QAAQ,EACR,gBAAgB,EAChB,KAAK,EACL,WAAW,EACZ,MAAM,gBAAgB,CAAC;AAExB;;;;;;;GAOG;AACH,MAAM,MAAM,QAAQ,CAAC,MAAM,SAAS,MAAM,kBAAkB,EAAE,CAAC,GAAG,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAC9F,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC7B,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC;AAEtC;;GAEG;AACH,KAAK,qBAAqB,CAAC,CAAC,IAC1B,CAAC,SAAS,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACvC,CAAC,GACD,CAAC,SAAS,KAAK,CAAC,MAAM,QAAQ,CAAC,GAC7B,8BAA8B,CAAC,QAAQ,CAAC,GACxC,CAAC,SAAS,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC,GACpD,8BAA8B,CAAC,QAAQ,CAAC,GACxC,KAAK,CAAC;AAEhB,KAAK,8BAA8B,CAAC,QAAQ,IAAI,QAAQ,SAAS,SAAS;IACxE,MAAM,KAAK;IACX,GAAG,MAAM,IAAI;CACd,GACG,qBAAqB,CAAC,KAAK,CAAC,GAAG,8BAA8B,CAAC,IAAI,CAAC,GACnE,KAAK,CAAC;AAEV;;;;GAIG;AACH,MAAM,MAAM,WAAW,CAAC,OAAO,SAAS,MAAM,IAAI;KAC/C,CAAC,IAAI,OAAO,GAAG,CAAC,SAAS,MAAM,kBAAkB,GAC9C,QAAQ,CAAC,CAAC,CAAC,GACX,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,oBAAoB,CAAC;CACxE,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,gBAAgB,CAAC,OAAO,SAAS,MAAM;IACtD;;OAEG;IACH,GAAG,CAAC,CAAC,SAAS,OAAO,EACnB,MAAM,EAAE,CAAC,GACR,CAAC,SAAS,MAAM,kBAAkB,GACjC,QAAQ,CAAC,CAAC,CAAC,GACX,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAExE;;OAEG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAE7B;;OAEG;IACH,OAAO,IAAI,OAAO,EAAE,CAAC;CACtB;AAwBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,eAAe,CAC7B,CAAC,SAAS,SAAS,WAAW,EAAE,EAChC,OAAO,SAAS,MAAM,GAAG,8BAA8B,CAAC,CAAC,CAAC,GAAG,MAAM,EACnE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAgC/E"}
1
+ {"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../src/resolvers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,WAAW,EACX,QAAQ,EACR,gBAAgB,EAChB,KAAK,EACL,WAAW,EACZ,MAAM,gBAAgB,CAAC;AAExB;;;;;;;GAOG;AACH,MAAM,MAAM,QAAQ,CAAC,MAAM,SAAS,MAAM,kBAAkB,EAAE,CAAC,GAAG,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAC9F,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC7B,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC;AAEtC;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,CAAC,CAAC,IACjC,CAAC,SAAS,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACvC,CAAC,GACD,CAAC,SAAS,KAAK,CAAC,MAAM,QAAQ,CAAC,GAC7B,8BAA8B,CAAC,QAAQ,CAAC,GACxC,CAAC,SAAS,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC,GACpD,8BAA8B,CAAC,QAAQ,CAAC,GACxC,KAAK,CAAC;AAEhB;;;;GAIG;AACH,MAAM,MAAM,8BAA8B,CAAC,QAAQ,IAAI,QAAQ,SAAS,SAAS;IAC/E,MAAM,KAAK;IACX,GAAG,MAAM,IAAI;CACd,GACG,qBAAqB,CAAC,KAAK,CAAC,GAAG,8BAA8B,CAAC,IAAI,CAAC,GACnE,KAAK,CAAC;AAEV;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,CAAC,CAAC,SAAS,SAAS,WAAW,EAAE,IACjE,8BAA8B,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;AAE7C;;;;GAIG;AACH,MAAM,MAAM,WAAW,CAAC,OAAO,SAAS,MAAM,IAAI;KAC/C,CAAC,IAAI,OAAO,GAAG,CAAC,SAAS,MAAM,kBAAkB,GAC9C,QAAQ,CAAC,CAAC,CAAC,GACX,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,oBAAoB,CAAC;CACxE,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,gBAAgB,CAAC,OAAO,SAAS,MAAM;IACtD;;OAEG;IACH,GAAG,CAAC,CAAC,SAAS,OAAO,EACnB,MAAM,EAAE,CAAC,GACR,CAAC,SAAS,MAAM,kBAAkB,GACjC,QAAQ,CAAC,CAAC,CAAC,GACX,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAExE;;OAEG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAE7B;;OAEG;IACH,OAAO,IAAI,OAAO,EAAE,CAAC;CACtB;AAwBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,eAAe,CAC7B,CAAC,SAAS,SAAS,WAAW,EAAE,EAChC,OAAO,SAAS,MAAM,GAAG,sBAAsB,CAAC,CAAC,CAAC,EAClD,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAgC/E"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * `@formspec/runtime` - Runtime helpers for FormSpec
3
+ *
4
+ * This package provides utilities for working with FormSpec forms at runtime:
5
+ * - `defineResolvers()` - Type-safe resolver definitions for dynamic enum fields
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { defineResolvers } from "@formspec/runtime";
10
+ * import { formspec, field } from "@formspec/dsl";
11
+ *
12
+ * // Define a form with dynamic enum fields
13
+ * const form = formspec(
14
+ * field.dynamicEnum("country", "countries", { label: "Country" }),
15
+ * );
16
+ *
17
+ * // Define resolvers for the form's data sources
18
+ * const resolvers = defineResolvers(form, {
19
+ * countries: async () => ({
20
+ * options: [
21
+ * { value: "us", label: "United States" },
22
+ * { value: "ca", label: "Canada" },
23
+ * ],
24
+ * validity: "valid",
25
+ * }),
26
+ * });
27
+ *
28
+ * // Use the resolver
29
+ * const result = await resolvers.get("countries")();
30
+ * console.log(result.options); // [{ value: "us", ... }, { value: "ca", ... }]
31
+ * ```
32
+ *
33
+ * @packageDocumentation
34
+ */
35
+
36
+ import type { Conditional } from '@formspec/core';
37
+ import type { DataSourceRegistry } from '@formspec/core';
38
+ import type { DynamicEnumField } from '@formspec/core';
39
+ import type { FetchOptionsResponse } from '@formspec/core';
40
+ import type { FormElement } from '@formspec/core';
41
+ import type { FormSpec } from '@formspec/core';
42
+ import type { Group } from '@formspec/core';
43
+
44
+ /**
45
+ * Defines resolvers for a form's dynamic data sources.
46
+ *
47
+ * This function provides type-safe resolver definitions that match
48
+ * the form's dynamic enum fields.
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * declare module "@formspec/core" {
53
+ * interface DataSourceRegistry {
54
+ * countries: { id: string; code: string; name: string };
55
+ * }
56
+ * }
57
+ *
58
+ * const form = formspec(
59
+ * field.dynamicEnum("country", "countries", { label: "Country" }),
60
+ * );
61
+ *
62
+ * const resolvers = defineResolvers(form, {
63
+ * countries: async () => ({
64
+ * options: [
65
+ * { value: "us", label: "United States", data: { id: "us", code: "US", name: "United States" } },
66
+ * { value: "ca", label: "Canada", data: { id: "ca", code: "CA", name: "Canada" } },
67
+ * ],
68
+ * validity: "valid",
69
+ * }),
70
+ * });
71
+ *
72
+ * // Use the resolver
73
+ * const result = await resolvers.get("countries")();
74
+ * ```
75
+ *
76
+ * @param form - The FormSpec containing dynamic enum fields
77
+ * @param resolvers - Map of resolver functions for each data source
78
+ * @returns A ResolverRegistry for type-safe access to resolvers
79
+ *
80
+ * @public
81
+ */
82
+ export declare function defineResolvers<E extends readonly FormElement[], Sources extends string = ResolverSourcesForForm<E>>(form: FormSpec<E>, resolvers: ResolverMap<Sources>): ResolverRegistry<Sources>;
83
+
84
+ /**
85
+ * Extracts dynamic data-source names referenced by a single FormSpec element.
86
+ *
87
+ * @public
88
+ */
89
+ export declare type ExtractDynamicSources<E> = E extends DynamicEnumField<string, infer S> ? S : E extends Group<infer Elements> ? ExtractDynamicSourcesFromArray<Elements> : E extends Conditional<string, unknown, infer Elements> ? ExtractDynamicSourcesFromArray<Elements> : never;
90
+
91
+ /**
92
+ * Extracts dynamic data-source names referenced anywhere in an element array.
93
+ *
94
+ * @public
95
+ */
96
+ export declare type ExtractDynamicSourcesFromArray<Elements> = Elements extends readonly [
97
+ infer First,
98
+ ...infer Rest
99
+ ] ? ExtractDynamicSources<First> | ExtractDynamicSourcesFromArray<Rest> : never;
100
+
101
+ /**
102
+ * A resolver function that fetches options for a data source.
103
+ *
104
+ * @typeParam Source - The data source key from DataSourceRegistry
105
+ * @typeParam T - The data type for options (from DataSourceRegistry)
106
+ *
107
+ * @public
108
+ */
109
+ export declare type Resolver<Source extends keyof DataSourceRegistry, T = DataSourceRegistry[Source]> = (params?: Record<string, unknown>) => Promise<FetchOptionsResponse<T>>;
110
+
111
+ /**
112
+ * Map of resolver functions for a form's dynamic data sources.
113
+ *
114
+ * @public
115
+ */
116
+ export declare type ResolverMap<Sources extends string> = {
117
+ [S in Sources]: S extends keyof DataSourceRegistry ? Resolver<S> : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;
118
+ };
119
+
120
+ /**
121
+ * A resolver registry that provides type-safe access to resolvers.
122
+ *
123
+ * @public
124
+ */
125
+ export declare interface ResolverRegistry<Sources extends string> {
126
+ /**
127
+ * Gets a resolver by data source name.
128
+ */
129
+ get<S extends Sources>(source: S): S extends keyof DataSourceRegistry ? Resolver<S> : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;
130
+ /**
131
+ * Checks if a resolver exists for a data source.
132
+ */
133
+ has(source: string): boolean;
134
+ /**
135
+ * Gets all registered data source names.
136
+ */
137
+ sources(): Sources[];
138
+ }
139
+
140
+ /**
141
+ * Derives the resolver source-key union for a FormSpec element array.
142
+ *
143
+ * @public
144
+ */
145
+ export declare type ResolverSourcesForForm<E extends readonly FormElement[]> = ExtractDynamicSourcesFromArray<E> & string;
146
+
147
+ export { }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * `@formspec/runtime` - Runtime helpers for FormSpec
3
+ *
4
+ * This package provides utilities for working with FormSpec forms at runtime:
5
+ * - `defineResolvers()` - Type-safe resolver definitions for dynamic enum fields
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { defineResolvers } from "@formspec/runtime";
10
+ * import { formspec, field } from "@formspec/dsl";
11
+ *
12
+ * // Define a form with dynamic enum fields
13
+ * const form = formspec(
14
+ * field.dynamicEnum("country", "countries", { label: "Country" }),
15
+ * );
16
+ *
17
+ * // Define resolvers for the form's data sources
18
+ * const resolvers = defineResolvers(form, {
19
+ * countries: async () => ({
20
+ * options: [
21
+ * { value: "us", label: "United States" },
22
+ * { value: "ca", label: "Canada" },
23
+ * ],
24
+ * validity: "valid",
25
+ * }),
26
+ * });
27
+ *
28
+ * // Use the resolver
29
+ * const result = await resolvers.get("countries")();
30
+ * console.log(result.options); // [{ value: "us", ... }, { value: "ca", ... }]
31
+ * ```
32
+ *
33
+ * @packageDocumentation
34
+ */
35
+
36
+ import type { Conditional } from '@formspec/core';
37
+ import type { DataSourceRegistry } from '@formspec/core';
38
+ import type { DynamicEnumField } from '@formspec/core';
39
+ import type { FetchOptionsResponse } from '@formspec/core';
40
+ import type { FormElement } from '@formspec/core';
41
+ import type { FormSpec } from '@formspec/core';
42
+ import type { Group } from '@formspec/core';
43
+
44
+ /**
45
+ * Defines resolvers for a form's dynamic data sources.
46
+ *
47
+ * This function provides type-safe resolver definitions that match
48
+ * the form's dynamic enum fields.
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * declare module "@formspec/core" {
53
+ * interface DataSourceRegistry {
54
+ * countries: { id: string; code: string; name: string };
55
+ * }
56
+ * }
57
+ *
58
+ * const form = formspec(
59
+ * field.dynamicEnum("country", "countries", { label: "Country" }),
60
+ * );
61
+ *
62
+ * const resolvers = defineResolvers(form, {
63
+ * countries: async () => ({
64
+ * options: [
65
+ * { value: "us", label: "United States", data: { id: "us", code: "US", name: "United States" } },
66
+ * { value: "ca", label: "Canada", data: { id: "ca", code: "CA", name: "Canada" } },
67
+ * ],
68
+ * validity: "valid",
69
+ * }),
70
+ * });
71
+ *
72
+ * // Use the resolver
73
+ * const result = await resolvers.get("countries")();
74
+ * ```
75
+ *
76
+ * @param form - The FormSpec containing dynamic enum fields
77
+ * @param resolvers - Map of resolver functions for each data source
78
+ * @returns A ResolverRegistry for type-safe access to resolvers
79
+ *
80
+ * @public
81
+ */
82
+ export declare function defineResolvers<E extends readonly FormElement[], Sources extends string = ResolverSourcesForForm<E>>(form: FormSpec<E>, resolvers: ResolverMap<Sources>): ResolverRegistry<Sources>;
83
+
84
+ /**
85
+ * Extracts dynamic data-source names referenced by a single FormSpec element.
86
+ *
87
+ * @public
88
+ */
89
+ export declare type ExtractDynamicSources<E> = E extends DynamicEnumField<string, infer S> ? S : E extends Group<infer Elements> ? ExtractDynamicSourcesFromArray<Elements> : E extends Conditional<string, unknown, infer Elements> ? ExtractDynamicSourcesFromArray<Elements> : never;
90
+
91
+ /**
92
+ * Extracts dynamic data-source names referenced anywhere in an element array.
93
+ *
94
+ * @public
95
+ */
96
+ export declare type ExtractDynamicSourcesFromArray<Elements> = Elements extends readonly [
97
+ infer First,
98
+ ...infer Rest
99
+ ] ? ExtractDynamicSources<First> | ExtractDynamicSourcesFromArray<Rest> : never;
100
+
101
+ /**
102
+ * A resolver function that fetches options for a data source.
103
+ *
104
+ * @typeParam Source - The data source key from DataSourceRegistry
105
+ * @typeParam T - The data type for options (from DataSourceRegistry)
106
+ *
107
+ * @public
108
+ */
109
+ export declare type Resolver<Source extends keyof DataSourceRegistry, T = DataSourceRegistry[Source]> = (params?: Record<string, unknown>) => Promise<FetchOptionsResponse<T>>;
110
+
111
+ /**
112
+ * Map of resolver functions for a form's dynamic data sources.
113
+ *
114
+ * @public
115
+ */
116
+ export declare type ResolverMap<Sources extends string> = {
117
+ [S in Sources]: S extends keyof DataSourceRegistry ? Resolver<S> : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;
118
+ };
119
+
120
+ /**
121
+ * A resolver registry that provides type-safe access to resolvers.
122
+ *
123
+ * @public
124
+ */
125
+ export declare interface ResolverRegistry<Sources extends string> {
126
+ /**
127
+ * Gets a resolver by data source name.
128
+ */
129
+ get<S extends Sources>(source: S): S extends keyof DataSourceRegistry ? Resolver<S> : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;
130
+ /**
131
+ * Checks if a resolver exists for a data source.
132
+ */
133
+ has(source: string): boolean;
134
+ /**
135
+ * Gets all registered data source names.
136
+ */
137
+ sources(): Sources[];
138
+ }
139
+
140
+ /**
141
+ * Derives the resolver source-key union for a FormSpec element array.
142
+ *
143
+ * @public
144
+ */
145
+ export declare type ResolverSourcesForForm<E extends readonly FormElement[]> = ExtractDynamicSourcesFromArray<E> & string;
146
+
147
+ export { }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * `@formspec/runtime` - Runtime helpers for FormSpec
3
+ *
4
+ * This package provides utilities for working with FormSpec forms at runtime:
5
+ * - `defineResolvers()` - Type-safe resolver definitions for dynamic enum fields
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { defineResolvers } from "@formspec/runtime";
10
+ * import { formspec, field } from "@formspec/dsl";
11
+ *
12
+ * // Define a form with dynamic enum fields
13
+ * const form = formspec(
14
+ * field.dynamicEnum("country", "countries", { label: "Country" }),
15
+ * );
16
+ *
17
+ * // Define resolvers for the form's data sources
18
+ * const resolvers = defineResolvers(form, {
19
+ * countries: async () => ({
20
+ * options: [
21
+ * { value: "us", label: "United States" },
22
+ * { value: "ca", label: "Canada" },
23
+ * ],
24
+ * validity: "valid",
25
+ * }),
26
+ * });
27
+ *
28
+ * // Use the resolver
29
+ * const result = await resolvers.get("countries")();
30
+ * console.log(result.options); // [{ value: "us", ... }, { value: "ca", ... }]
31
+ * ```
32
+ *
33
+ * @packageDocumentation
34
+ */
35
+
36
+ import type { Conditional } from '@formspec/core';
37
+ import type { DataSourceRegistry } from '@formspec/core';
38
+ import type { DynamicEnumField } from '@formspec/core';
39
+ import type { FetchOptionsResponse } from '@formspec/core';
40
+ import type { FormElement } from '@formspec/core';
41
+ import type { FormSpec } from '@formspec/core';
42
+ import type { Group } from '@formspec/core';
43
+
44
+ /**
45
+ * Defines resolvers for a form's dynamic data sources.
46
+ *
47
+ * This function provides type-safe resolver definitions that match
48
+ * the form's dynamic enum fields.
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * declare module "@formspec/core" {
53
+ * interface DataSourceRegistry {
54
+ * countries: { id: string; code: string; name: string };
55
+ * }
56
+ * }
57
+ *
58
+ * const form = formspec(
59
+ * field.dynamicEnum("country", "countries", { label: "Country" }),
60
+ * );
61
+ *
62
+ * const resolvers = defineResolvers(form, {
63
+ * countries: async () => ({
64
+ * options: [
65
+ * { value: "us", label: "United States", data: { id: "us", code: "US", name: "United States" } },
66
+ * { value: "ca", label: "Canada", data: { id: "ca", code: "CA", name: "Canada" } },
67
+ * ],
68
+ * validity: "valid",
69
+ * }),
70
+ * });
71
+ *
72
+ * // Use the resolver
73
+ * const result = await resolvers.get("countries")();
74
+ * ```
75
+ *
76
+ * @param form - The FormSpec containing dynamic enum fields
77
+ * @param resolvers - Map of resolver functions for each data source
78
+ * @returns A ResolverRegistry for type-safe access to resolvers
79
+ *
80
+ * @public
81
+ */
82
+ export declare function defineResolvers<E extends readonly FormElement[], Sources extends string = ResolverSourcesForForm<E>>(form: FormSpec<E>, resolvers: ResolverMap<Sources>): ResolverRegistry<Sources>;
83
+
84
+ /**
85
+ * Extracts dynamic data-source names referenced by a single FormSpec element.
86
+ *
87
+ * @public
88
+ */
89
+ export declare type ExtractDynamicSources<E> = E extends DynamicEnumField<string, infer S> ? S : E extends Group<infer Elements> ? ExtractDynamicSourcesFromArray<Elements> : E extends Conditional<string, unknown, infer Elements> ? ExtractDynamicSourcesFromArray<Elements> : never;
90
+
91
+ /**
92
+ * Extracts dynamic data-source names referenced anywhere in an element array.
93
+ *
94
+ * @public
95
+ */
96
+ export declare type ExtractDynamicSourcesFromArray<Elements> = Elements extends readonly [
97
+ infer First,
98
+ ...infer Rest
99
+ ] ? ExtractDynamicSources<First> | ExtractDynamicSourcesFromArray<Rest> : never;
100
+
101
+ /**
102
+ * A resolver function that fetches options for a data source.
103
+ *
104
+ * @typeParam Source - The data source key from DataSourceRegistry
105
+ * @typeParam T - The data type for options (from DataSourceRegistry)
106
+ *
107
+ * @public
108
+ */
109
+ export declare type Resolver<Source extends keyof DataSourceRegistry, T = DataSourceRegistry[Source]> = (params?: Record<string, unknown>) => Promise<FetchOptionsResponse<T>>;
110
+
111
+ /**
112
+ * Map of resolver functions for a form's dynamic data sources.
113
+ *
114
+ * @public
115
+ */
116
+ export declare type ResolverMap<Sources extends string> = {
117
+ [S in Sources]: S extends keyof DataSourceRegistry ? Resolver<S> : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;
118
+ };
119
+
120
+ /**
121
+ * A resolver registry that provides type-safe access to resolvers.
122
+ *
123
+ * @public
124
+ */
125
+ export declare interface ResolverRegistry<Sources extends string> {
126
+ /**
127
+ * Gets a resolver by data source name.
128
+ */
129
+ get<S extends Sources>(source: S): S extends keyof DataSourceRegistry ? Resolver<S> : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;
130
+ /**
131
+ * Checks if a resolver exists for a data source.
132
+ */
133
+ has(source: string): boolean;
134
+ /**
135
+ * Gets all registered data source names.
136
+ */
137
+ sources(): Sources[];
138
+ }
139
+
140
+ /**
141
+ * Derives the resolver source-key union for a FormSpec element array.
142
+ *
143
+ * @public
144
+ */
145
+ export declare type ResolverSourcesForForm<E extends readonly FormElement[]> = ExtractDynamicSourcesFromArray<E> & string;
146
+
147
+ export { }
package/dist/runtime.d.ts CHANGED
@@ -79,14 +79,21 @@ import type { Group } from '@formspec/core';
79
79
  *
80
80
  * @public
81
81
  */
82
- export declare function defineResolvers<E extends readonly FormElement[], Sources extends string = ExtractDynamicSourcesFromArray<E> & string>(form: FormSpec<E>, resolvers: ResolverMap<Sources>): ResolverRegistry<Sources>;
82
+ export declare function defineResolvers<E extends readonly FormElement[], Sources extends string = ResolverSourcesForForm<E>>(form: FormSpec<E>, resolvers: ResolverMap<Sources>): ResolverRegistry<Sources>;
83
83
 
84
84
  /**
85
- * Extracts all dynamic enum source keys from a form's elements.
85
+ * Extracts dynamic data-source names referenced by a single FormSpec element.
86
+ *
87
+ * @public
86
88
  */
87
- declare type ExtractDynamicSources<E> = E extends DynamicEnumField<string, infer S> ? S : E extends Group<infer Elements> ? ExtractDynamicSourcesFromArray<Elements> : E extends Conditional<string, unknown, infer Elements> ? ExtractDynamicSourcesFromArray<Elements> : never;
89
+ export declare type ExtractDynamicSources<E> = E extends DynamicEnumField<string, infer S> ? S : E extends Group<infer Elements> ? ExtractDynamicSourcesFromArray<Elements> : E extends Conditional<string, unknown, infer Elements> ? ExtractDynamicSourcesFromArray<Elements> : never;
88
90
 
89
- declare type ExtractDynamicSourcesFromArray<Elements> = Elements extends readonly [
91
+ /**
92
+ * Extracts dynamic data-source names referenced anywhere in an element array.
93
+ *
94
+ * @public
95
+ */
96
+ export declare type ExtractDynamicSourcesFromArray<Elements> = Elements extends readonly [
90
97
  infer First,
91
98
  ...infer Rest
92
99
  ] ? ExtractDynamicSources<First> | ExtractDynamicSourcesFromArray<Rest> : never;
@@ -130,4 +137,11 @@ export declare interface ResolverRegistry<Sources extends string> {
130
137
  sources(): Sources[];
131
138
  }
132
139
 
140
+ /**
141
+ * Derives the resolver source-key union for a FormSpec element array.
142
+ *
143
+ * @public
144
+ */
145
+ export declare type ResolverSourcesForForm<E extends readonly FormElement[]> = ExtractDynamicSourcesFromArray<E> & string;
146
+
133
147
  export { }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formspec/runtime",
3
- "version": "0.1.0-alpha.21",
3
+ "version": "0.1.0-alpha.26",
4
4
  "description": "Runtime helpers for FormSpec - resolvers and data fetching",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -18,11 +18,11 @@
18
18
  "README.md"
19
19
  ],
20
20
  "dependencies": {
21
- "@formspec/core": "0.1.0-alpha.21"
21
+ "@formspec/core": "0.1.0-alpha.26"
22
22
  },
23
23
  "devDependencies": {
24
24
  "vitest": "^3.0.0",
25
- "@formspec/dsl": "0.1.0-alpha.21"
25
+ "@formspec/dsl": "0.1.0-alpha.26"
26
26
  },
27
27
  "publishConfig": {
28
28
  "access": "public"
@@ -30,12 +30,12 @@
30
30
  "keywords": [],
31
31
  "license": "UNLICENSED",
32
32
  "scripts": {
33
- "build": "tsup && tsc --emitDeclarationOnly && api-extractor run --local",
33
+ "build": "tsup && tsc --emitDeclarationOnly && pnpm run api-extractor:local",
34
34
  "clean": "rm -rf dist temp",
35
35
  "typecheck": "tsc --noEmit",
36
36
  "test": "vitest run",
37
- "api-extractor": "api-extractor run",
38
- "api-extractor:local": "api-extractor run --local",
39
- "api-documenter": "api-documenter markdown -i temp -o docs"
37
+ "api-extractor": "api-extractor run && node ../../scripts/normalize-generated-markdown.mjs api-report",
38
+ "api-extractor:local": "api-extractor run --local && node ../../scripts/normalize-generated-markdown.mjs api-report",
39
+ "api-documenter": "api-documenter markdown -i temp -o docs && node ../../scripts/normalize-generated-markdown.mjs docs"
40
40
  }
41
41
  }