@formspec/runtime 0.1.0-alpha.0 → 0.1.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -0
- package/dist/runtime.d.ts +125 -0
- package/package.json +6 -5
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
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formspec/runtime",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.4",
|
|
4
4
|
"description": "Runtime helpers for FormSpec - resolvers and data fetching",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,14 +12,15 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
-
"dist"
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
16
17
|
],
|
|
17
18
|
"dependencies": {
|
|
18
|
-
"@formspec/core": "0.1.0-alpha.
|
|
19
|
+
"@formspec/core": "0.1.0-alpha.4"
|
|
19
20
|
},
|
|
20
21
|
"devDependencies": {
|
|
21
22
|
"vitest": "^3.0.0",
|
|
22
|
-
"@formspec/dsl": "0.1.0-alpha.
|
|
23
|
+
"@formspec/dsl": "0.1.0-alpha.4"
|
|
23
24
|
},
|
|
24
25
|
"publishConfig": {
|
|
25
26
|
"access": "public"
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
"keywords": [],
|
|
28
29
|
"license": "UNLICENSED",
|
|
29
30
|
"scripts": {
|
|
30
|
-
"build": "tsc",
|
|
31
|
+
"build": "tsc && api-extractor run --local",
|
|
31
32
|
"clean": "rm -rf dist temp",
|
|
32
33
|
"typecheck": "tsc --noEmit",
|
|
33
34
|
"test": "vitest run",
|