@formspec/runtime 0.1.0-alpha.0 → 0.1.0-alpha.11
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 +127 -0
- package/dist/index.cjs +73 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +45 -35
- package/dist/index.js.map +1 -1
- package/dist/resolvers.d.ts.map +1 -1
- package/dist/runtime.d.ts +125 -0
- package/package.json +10 -7
- package/dist/__tests__/resolvers.test.js +0 -71
- package/dist/__tests__/resolvers.test.js.map +0 -1
- package/dist/resolvers.js +0 -88
- package/dist/resolvers.js.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# @formspec/runtime
|
|
2
|
+
|
|
3
|
+
Runtime helpers for FormSpec - resolvers and data fetching.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @formspec/runtime
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @formspec/runtime
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
> **Note:** Most users should install the `formspec` umbrella package instead, which re-exports everything from this package.
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
This package is ESM-only and requires:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
// package.json
|
|
21
|
+
{
|
|
22
|
+
"type": "module"
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
// tsconfig.json
|
|
28
|
+
{
|
|
29
|
+
"compilerOptions": {
|
|
30
|
+
"module": "NodeNext",
|
|
31
|
+
"moduleResolution": "NodeNext"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
### Define Resolvers for Dynamic Enums
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { defineResolvers } from "@formspec/runtime";
|
|
42
|
+
import { formspec, field } from "@formspec/dsl";
|
|
43
|
+
|
|
44
|
+
// Define a form with dynamic enum fields
|
|
45
|
+
const OrderForm = formspec(
|
|
46
|
+
field.dynamicEnum("country", "fetch_countries", { label: "Country" }),
|
|
47
|
+
field.dynamicEnum("state", "fetch_states", { label: "State" })
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Define type-safe resolvers
|
|
51
|
+
const resolvers = defineResolvers(OrderForm, {
|
|
52
|
+
fetch_countries: async () => ({
|
|
53
|
+
options: [
|
|
54
|
+
{ value: "us", label: "United States" },
|
|
55
|
+
{ value: "ca", label: "Canada" },
|
|
56
|
+
{ value: "uk", label: "United Kingdom" },
|
|
57
|
+
],
|
|
58
|
+
validity: "valid",
|
|
59
|
+
}),
|
|
60
|
+
|
|
61
|
+
fetch_states: async (params) => {
|
|
62
|
+
// Params can include any context needed for the lookup (e.g. form data)
|
|
63
|
+
const response = await fetch(`/api/states?country=${params?.country}`);
|
|
64
|
+
const states = await response.json();
|
|
65
|
+
return {
|
|
66
|
+
options: states.map((s: { code: string; name: string }) => ({
|
|
67
|
+
value: s.code,
|
|
68
|
+
label: s.name,
|
|
69
|
+
})),
|
|
70
|
+
validity: "valid",
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Use the resolver
|
|
76
|
+
const countries = await resolvers.get("fetch_countries")();
|
|
77
|
+
console.log(countries.options);
|
|
78
|
+
// [{ value: "us", label: "United States" }, ...]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Resolver Response Format
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
interface FetchOptionsResponse {
|
|
85
|
+
options: Array<{
|
|
86
|
+
value: string;
|
|
87
|
+
label: string;
|
|
88
|
+
}>;
|
|
89
|
+
validity: "valid" | "invalid" | "unknown";
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## API Reference
|
|
94
|
+
|
|
95
|
+
### Functions
|
|
96
|
+
|
|
97
|
+
| Function | Description |
|
|
98
|
+
| ---------------------------------- | ------------------------------------ |
|
|
99
|
+
| `defineResolvers(form, resolvers)` | Create a type-safe resolver registry |
|
|
100
|
+
|
|
101
|
+
### Types
|
|
102
|
+
|
|
103
|
+
| Type | Description |
|
|
104
|
+
| --------------------- | ------------------------------------- |
|
|
105
|
+
| `Resolver` | Async function that fetches options |
|
|
106
|
+
| `ResolverMap<F>` | Map of data source names to resolvers |
|
|
107
|
+
| `ResolverRegistry<F>` | Registry with `get()` method |
|
|
108
|
+
|
|
109
|
+
## Type Safety
|
|
110
|
+
|
|
111
|
+
The `defineResolvers` function enforces that:
|
|
112
|
+
|
|
113
|
+
1. All data sources referenced in the form have corresponding resolvers
|
|
114
|
+
2. Resolver names match the data source IDs in the form
|
|
115
|
+
3. Return types match the expected `FetchOptionsResponse` format
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// TypeScript error: Missing resolver for "fetch_states"
|
|
119
|
+
const resolvers = defineResolvers(OrderForm, {
|
|
120
|
+
fetch_countries: async () => ({ ... }),
|
|
121
|
+
// Error: 'fetch_states' is required
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
UNLICENSED
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
defineResolvers: () => defineResolvers
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/resolvers.ts
|
|
28
|
+
function extractSources(elements) {
|
|
29
|
+
const sources = /* @__PURE__ */ new Set();
|
|
30
|
+
function visit(el) {
|
|
31
|
+
if (el._type === "field" && el._field === "dynamic_enum") {
|
|
32
|
+
const dynamicField = el;
|
|
33
|
+
sources.add(dynamicField.source);
|
|
34
|
+
} else if (el._type === "group") {
|
|
35
|
+
el.elements.forEach(visit);
|
|
36
|
+
} else if (el._type === "conditional") {
|
|
37
|
+
el.elements.forEach(visit);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
elements.forEach(visit);
|
|
41
|
+
return sources;
|
|
42
|
+
}
|
|
43
|
+
function defineResolvers(form, resolvers) {
|
|
44
|
+
const sourceSet = extractSources(form.elements);
|
|
45
|
+
const resolverMap = new Map(
|
|
46
|
+
Object.entries(resolvers)
|
|
47
|
+
);
|
|
48
|
+
for (const source of sourceSet) {
|
|
49
|
+
if (!resolverMap.has(source)) {
|
|
50
|
+
console.warn(`Missing resolver for data source: ${source}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
get(source) {
|
|
55
|
+
const resolver = resolverMap.get(source);
|
|
56
|
+
if (resolver === void 0) {
|
|
57
|
+
throw new Error(`No resolver found for data source: ${source}`);
|
|
58
|
+
}
|
|
59
|
+
return resolver;
|
|
60
|
+
},
|
|
61
|
+
has(source) {
|
|
62
|
+
return resolverMap.has(source);
|
|
63
|
+
},
|
|
64
|
+
sources() {
|
|
65
|
+
return Array.from(resolverMap.keys());
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
70
|
+
0 && (module.exports = {
|
|
71
|
+
defineResolvers
|
|
72
|
+
});
|
|
73
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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 */\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 */\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 */\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 */\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;;;ACmFA,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;AAsCO,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.js
CHANGED
|
@@ -1,36 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
1
|
+
// src/resolvers.ts
|
|
2
|
+
function extractSources(elements) {
|
|
3
|
+
const sources = /* @__PURE__ */ new Set();
|
|
4
|
+
function visit(el) {
|
|
5
|
+
if (el._type === "field" && el._field === "dynamic_enum") {
|
|
6
|
+
const dynamicField = el;
|
|
7
|
+
sources.add(dynamicField.source);
|
|
8
|
+
} else if (el._type === "group") {
|
|
9
|
+
el.elements.forEach(visit);
|
|
10
|
+
} else if (el._type === "conditional") {
|
|
11
|
+
el.elements.forEach(visit);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
elements.forEach(visit);
|
|
15
|
+
return sources;
|
|
16
|
+
}
|
|
17
|
+
function defineResolvers(form, resolvers) {
|
|
18
|
+
const sourceSet = extractSources(form.elements);
|
|
19
|
+
const resolverMap = new Map(
|
|
20
|
+
Object.entries(resolvers)
|
|
21
|
+
);
|
|
22
|
+
for (const source of sourceSet) {
|
|
23
|
+
if (!resolverMap.has(source)) {
|
|
24
|
+
console.warn(`Missing resolver for data source: ${source}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
get(source) {
|
|
29
|
+
const resolver = resolverMap.get(source);
|
|
30
|
+
if (resolver === void 0) {
|
|
31
|
+
throw new Error(`No resolver found for data source: ${source}`);
|
|
32
|
+
}
|
|
33
|
+
return resolver;
|
|
34
|
+
},
|
|
35
|
+
has(source) {
|
|
36
|
+
return resolverMap.has(source);
|
|
37
|
+
},
|
|
38
|
+
sources() {
|
|
39
|
+
return Array.from(resolverMap.keys());
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
defineResolvers
|
|
45
|
+
};
|
|
36
46
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
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 */\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 */\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 */\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 */\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":";AAmFA,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;AAsCO,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/resolvers.d.ts.map
CHANGED
|
@@ -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;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,
|
|
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;;;;;GAKG;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;;GAEG;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;;GAEG;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;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"}
|
|
@@ -0,0 +1,125 @@
|
|
|
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
|
+
export declare function defineResolvers<E extends readonly FormElement[], Sources extends string = ExtractDynamicSourcesFromArray<E> & string>(form: FormSpec<E>, resolvers: ResolverMap<Sources>): ResolverRegistry<Sources>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Extracts all dynamic enum source keys from a form's elements.
|
|
84
|
+
*/
|
|
85
|
+
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;
|
|
86
|
+
|
|
87
|
+
declare type ExtractDynamicSourcesFromArray<Elements> = Elements extends readonly [
|
|
88
|
+
infer First,
|
|
89
|
+
...infer Rest
|
|
90
|
+
] ? ExtractDynamicSources<First> | ExtractDynamicSourcesFromArray<Rest> : never;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* A resolver function that fetches options for a data source.
|
|
94
|
+
*
|
|
95
|
+
* @typeParam Source - The data source key from DataSourceRegistry
|
|
96
|
+
* @typeParam T - The data type for options (from DataSourceRegistry)
|
|
97
|
+
*/
|
|
98
|
+
export declare type Resolver<Source extends keyof DataSourceRegistry, T = DataSourceRegistry[Source]> = (params?: Record<string, unknown>) => Promise<FetchOptionsResponse<T>>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Map of resolver functions for a form's dynamic data sources.
|
|
102
|
+
*/
|
|
103
|
+
export declare type ResolverMap<Sources extends string> = {
|
|
104
|
+
[S in Sources]: S extends keyof DataSourceRegistry ? Resolver<S> : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* A resolver registry that provides type-safe access to resolvers.
|
|
109
|
+
*/
|
|
110
|
+
export declare interface ResolverRegistry<Sources extends string> {
|
|
111
|
+
/**
|
|
112
|
+
* Gets a resolver by data source name.
|
|
113
|
+
*/
|
|
114
|
+
get<S extends Sources>(source: S): S extends keyof DataSourceRegistry ? Resolver<S> : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;
|
|
115
|
+
/**
|
|
116
|
+
* Checks if a resolver exists for a data source.
|
|
117
|
+
*/
|
|
118
|
+
has(source: string): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Gets all registered data source names.
|
|
121
|
+
*/
|
|
122
|
+
sources(): Sources[];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export { }
|
package/package.json
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formspec/runtime",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.11",
|
|
4
4
|
"description": "Runtime helpers for FormSpec - resolvers and data fetching",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/index.
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
7
8
|
"types": "./dist/runtime.d.ts",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": {
|
|
10
11
|
"types": "./dist/runtime.d.ts",
|
|
11
|
-
"import": "./dist/index.js"
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
12
14
|
}
|
|
13
15
|
},
|
|
14
16
|
"files": [
|
|
15
|
-
"dist"
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
16
19
|
],
|
|
17
20
|
"dependencies": {
|
|
18
|
-
"@formspec/core": "0.1.0-alpha.
|
|
21
|
+
"@formspec/core": "0.1.0-alpha.11"
|
|
19
22
|
},
|
|
20
23
|
"devDependencies": {
|
|
21
24
|
"vitest": "^3.0.0",
|
|
22
|
-
"@formspec/dsl": "0.1.0-alpha.
|
|
25
|
+
"@formspec/dsl": "0.1.0-alpha.11"
|
|
23
26
|
},
|
|
24
27
|
"publishConfig": {
|
|
25
28
|
"access": "public"
|
|
@@ -27,7 +30,7 @@
|
|
|
27
30
|
"keywords": [],
|
|
28
31
|
"license": "UNLICENSED",
|
|
29
32
|
"scripts": {
|
|
30
|
-
"build": "tsc",
|
|
33
|
+
"build": "tsup && tsc --emitDeclarationOnly && api-extractor run --local",
|
|
31
34
|
"clean": "rm -rf dist temp",
|
|
32
35
|
"typecheck": "tsc --noEmit",
|
|
33
36
|
"test": "vitest run",
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { defineResolvers } from "../index.js";
|
|
3
|
-
import { formspec, field, group, when, is } from "@formspec/dsl";
|
|
4
|
-
// Note: In real usage, you would augment DataSourceRegistry.
|
|
5
|
-
// For tests, we work with the generic types.
|
|
6
|
-
describe("defineResolvers", () => {
|
|
7
|
-
it("should create a resolver registry", () => {
|
|
8
|
-
const form = formspec(field.dynamicEnum("country", "countries", { label: "Country" }));
|
|
9
|
-
const resolvers = defineResolvers(form, {
|
|
10
|
-
countries: async () => ({
|
|
11
|
-
options: [
|
|
12
|
-
{ value: "us", label: "United States" },
|
|
13
|
-
{ value: "ca", label: "Canada" },
|
|
14
|
-
],
|
|
15
|
-
validity: "valid",
|
|
16
|
-
}),
|
|
17
|
-
});
|
|
18
|
-
expect(resolvers.has("countries")).toBe(true);
|
|
19
|
-
expect(resolvers.sources()).toEqual(["countries"]);
|
|
20
|
-
});
|
|
21
|
-
it("should fetch options from resolver", async () => {
|
|
22
|
-
const form = formspec(field.dynamicEnum("country", "countries"));
|
|
23
|
-
const resolvers = defineResolvers(form, {
|
|
24
|
-
countries: async () => ({
|
|
25
|
-
options: [
|
|
26
|
-
{ value: "us", label: "United States" },
|
|
27
|
-
],
|
|
28
|
-
validity: "valid",
|
|
29
|
-
}),
|
|
30
|
-
});
|
|
31
|
-
const result = await resolvers.get("countries")();
|
|
32
|
-
expect(result.validity).toBe("valid");
|
|
33
|
-
expect(result.options).toHaveLength(1);
|
|
34
|
-
expect(result.options[0]?.value).toBe("us");
|
|
35
|
-
});
|
|
36
|
-
it("should extract sources from nested groups", () => {
|
|
37
|
-
const form = formspec(group("Location", field.dynamicEnum("country", "countries"), field.dynamicEnum("city", "cities")));
|
|
38
|
-
const resolvers = defineResolvers(form, {
|
|
39
|
-
countries: async () => ({ options: [], validity: "valid" }),
|
|
40
|
-
cities: async () => ({ options: [], validity: "valid" }),
|
|
41
|
-
});
|
|
42
|
-
expect(resolvers.sources().sort()).toEqual(["cities", "countries"]);
|
|
43
|
-
});
|
|
44
|
-
it("should extract sources from conditionals", () => {
|
|
45
|
-
const form = formspec(field.enum("type", ["a", "b"]), when(is("type", "a"), field.dynamicEnum("extra", "extras")));
|
|
46
|
-
const resolvers = defineResolvers(form, {
|
|
47
|
-
extras: async () => ({ options: [], validity: "valid" }),
|
|
48
|
-
});
|
|
49
|
-
expect(resolvers.has("extras")).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
it("should throw when getting unknown resolver", () => {
|
|
52
|
-
const form = formspec(field.dynamicEnum("country", "countries"));
|
|
53
|
-
const resolvers = defineResolvers(form, {
|
|
54
|
-
countries: async () => ({ options: [], validity: "valid" }),
|
|
55
|
-
});
|
|
56
|
-
expect(() => resolvers.get("unknown")).toThrow("No resolver found for data source: unknown");
|
|
57
|
-
});
|
|
58
|
-
it("should pass params to resolver", async () => {
|
|
59
|
-
const form = formspec(field.dynamicEnum("product", "products", { params: ["merchantId"] }));
|
|
60
|
-
let receivedParams;
|
|
61
|
-
const resolvers = defineResolvers(form, {
|
|
62
|
-
products: async (params) => {
|
|
63
|
-
receivedParams = params;
|
|
64
|
-
return { options: [], validity: "valid" };
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
await resolvers.get("products")({ merchantId: "123" });
|
|
68
|
-
expect(receivedParams).toEqual({ merchantId: "123" });
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
//# sourceMappingURL=resolvers.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resolvers.test.js","sourceRoot":"","sources":["../../src/__tests__/resolvers.test.ts"],"names":[],"mappings":"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"}
|
package/dist/resolvers.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolver helpers for dynamic FormSpec data.
|
|
3
|
-
*
|
|
4
|
-
* Resolvers are functions that fetch options for dynamic enum fields
|
|
5
|
-
* at runtime. This module provides type-safe utilities for defining
|
|
6
|
-
* and using resolvers.
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Extracts all dynamic enum field sources from form elements.
|
|
10
|
-
*/
|
|
11
|
-
function extractSources(elements) {
|
|
12
|
-
const sources = new Set();
|
|
13
|
-
function visit(el) {
|
|
14
|
-
if (el._type === "field" && el._field === "dynamic_enum") {
|
|
15
|
-
sources.add(el.source);
|
|
16
|
-
}
|
|
17
|
-
else if (el._type === "group") {
|
|
18
|
-
el.elements.forEach(visit);
|
|
19
|
-
}
|
|
20
|
-
else if (el._type === "conditional") {
|
|
21
|
-
el.elements.forEach(visit);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
elements.forEach(visit);
|
|
25
|
-
return sources;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Defines resolvers for a form's dynamic data sources.
|
|
29
|
-
*
|
|
30
|
-
* This function provides type-safe resolver definitions that match
|
|
31
|
-
* the form's dynamic enum fields.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```typescript
|
|
35
|
-
* declare module "@formspec/core" {
|
|
36
|
-
* interface DataSourceRegistry {
|
|
37
|
-
* countries: { id: string; code: string; name: string };
|
|
38
|
-
* }
|
|
39
|
-
* }
|
|
40
|
-
*
|
|
41
|
-
* const form = formspec(
|
|
42
|
-
* field.dynamicEnum("country", "countries", { label: "Country" }),
|
|
43
|
-
* );
|
|
44
|
-
*
|
|
45
|
-
* const resolvers = defineResolvers(form, {
|
|
46
|
-
* countries: async () => ({
|
|
47
|
-
* options: [
|
|
48
|
-
* { value: "us", label: "United States", data: { id: "us", code: "US", name: "United States" } },
|
|
49
|
-
* { value: "ca", label: "Canada", data: { id: "ca", code: "CA", name: "Canada" } },
|
|
50
|
-
* ],
|
|
51
|
-
* validity: "valid",
|
|
52
|
-
* }),
|
|
53
|
-
* });
|
|
54
|
-
*
|
|
55
|
-
* // Use the resolver
|
|
56
|
-
* const result = await resolvers.get("countries")();
|
|
57
|
-
* ```
|
|
58
|
-
*
|
|
59
|
-
* @param form - The FormSpec containing dynamic enum fields
|
|
60
|
-
* @param resolvers - Map of resolver functions for each data source
|
|
61
|
-
* @returns A ResolverRegistry for type-safe access to resolvers
|
|
62
|
-
*/
|
|
63
|
-
export function defineResolvers(form, resolvers) {
|
|
64
|
-
const sourceSet = extractSources(form.elements);
|
|
65
|
-
const resolverMap = new Map(Object.entries(resolvers));
|
|
66
|
-
// Validate that all sources have resolvers
|
|
67
|
-
for (const source of sourceSet) {
|
|
68
|
-
if (!resolverMap.has(source)) {
|
|
69
|
-
console.warn(`Missing resolver for data source: ${source}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return {
|
|
73
|
-
get(source) {
|
|
74
|
-
const resolver = resolverMap.get(source);
|
|
75
|
-
if (resolver === undefined) {
|
|
76
|
-
throw new Error(`No resolver found for data source: ${source}`);
|
|
77
|
-
}
|
|
78
|
-
return resolver;
|
|
79
|
-
},
|
|
80
|
-
has(source) {
|
|
81
|
-
return resolverMap.has(source);
|
|
82
|
-
},
|
|
83
|
-
sources() {
|
|
84
|
-
return Array.from(resolverMap.keys());
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
//# sourceMappingURL=resolvers.js.map
|
package/dist/resolvers.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resolvers.js","sourceRoot":"","sources":["../src/resolvers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA0EH;;GAEG;AACH,SAAS,cAAc,CAAC,QAAgC;IACtD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAElC,SAAS,KAAK,CAAC,EAAe;QAC5B,IAAI,EAAE,CAAC,KAAK,KAAK,OAAO,IAAI,EAAE,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;YACzD,OAAO,CAAC,GAAG,CAAE,EAAuC,CAAC,MAAM,CAAC,CAAC;QAC/D,CAAC;aAAM,IAAI,EAAE,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;YAC/B,EAAoC,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAChE,CAAC;aAAM,IAAI,EAAE,CAAC,KAAK,KAAK,aAAa,EAAE,CAAC;YACrC,EAA2D,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IAED,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACxB,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,UAAU,eAAe,CAI7B,IAAiB,EACjB,SAA+B;IAE/B,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,WAAW,GAAG,IAAI,GAAG,CACzB,MAAM,CAAC,OAAO,CAAC,SAAS,CAAwD,CACjF,CAAC;IAEF,2CAA2C;IAC3C,KAAK,MAAM,MAAM,IAAI,SAAS,EAAE,CAAC;QAC/B,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,IAAI,CAAC,qCAAqC,MAAM,EAAE,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IAED,OAAO;QACL,GAAG,CAAoB,MAAS;YAC9B,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACzC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,MAAM,IAAI,KAAK,CAAC,sCAAsC,MAAM,EAAE,CAAC,CAAC;YAClE,CAAC;YACD,OAAO,QAEgE,CAAC;QAC1E,CAAC;QAED,GAAG,CAAC,MAAc;YAChB,OAAO,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC;QAED,OAAO;YACL,OAAO,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAc,CAAC;QACrD,CAAC;KACF,CAAC;AACJ,CAAC"}
|