@formspec/runtime 0.1.0-alpha.0 → 0.1.0-alpha.10

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 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
@@ -7,13 +7,16 @@ describe("defineResolvers", () => {
7
7
  it("should create a resolver registry", () => {
8
8
  const form = formspec(field.dynamicEnum("country", "countries", { label: "Country" }));
9
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
- }),
10
+ countries: async () => {
11
+ await Promise.resolve(); // Needed for async interface compliance
12
+ return {
13
+ options: [
14
+ { value: "us", label: "United States" },
15
+ { value: "ca", label: "Canada" },
16
+ ],
17
+ validity: "valid",
18
+ };
19
+ },
17
20
  });
18
21
  expect(resolvers.has("countries")).toBe(true);
19
22
  expect(resolvers.sources()).toEqual(["countries"]);
@@ -21,12 +24,13 @@ describe("defineResolvers", () => {
21
24
  it("should fetch options from resolver", async () => {
22
25
  const form = formspec(field.dynamicEnum("country", "countries"));
23
26
  const resolvers = defineResolvers(form, {
24
- countries: async () => ({
25
- options: [
26
- { value: "us", label: "United States" },
27
- ],
28
- validity: "valid",
29
- }),
27
+ countries: async () => {
28
+ await Promise.resolve(); // Needed for async interface compliance
29
+ return {
30
+ options: [{ value: "us", label: "United States" }],
31
+ validity: "valid",
32
+ };
33
+ },
30
34
  });
31
35
  const result = await resolvers.get("countries")();
32
36
  expect(result.validity).toBe("valid");
@@ -36,22 +40,34 @@ describe("defineResolvers", () => {
36
40
  it("should extract sources from nested groups", () => {
37
41
  const form = formspec(group("Location", field.dynamicEnum("country", "countries"), field.dynamicEnum("city", "cities")));
38
42
  const resolvers = defineResolvers(form, {
39
- countries: async () => ({ options: [], validity: "valid" }),
40
- cities: async () => ({ options: [], validity: "valid" }),
43
+ countries: async () => {
44
+ await Promise.resolve(); // Needed for async interface compliance
45
+ return { options: [], validity: "valid" };
46
+ },
47
+ cities: async () => {
48
+ await Promise.resolve(); // Needed for async interface compliance
49
+ return { options: [], validity: "valid" };
50
+ },
41
51
  });
42
52
  expect(resolvers.sources().sort()).toEqual(["cities", "countries"]);
43
53
  });
44
54
  it("should extract sources from conditionals", () => {
45
55
  const form = formspec(field.enum("type", ["a", "b"]), when(is("type", "a"), field.dynamicEnum("extra", "extras")));
46
56
  const resolvers = defineResolvers(form, {
47
- extras: async () => ({ options: [], validity: "valid" }),
57
+ extras: async () => {
58
+ await Promise.resolve(); // Needed for async interface compliance
59
+ return { options: [], validity: "valid" };
60
+ },
48
61
  });
49
62
  expect(resolvers.has("extras")).toBe(true);
50
63
  });
51
64
  it("should throw when getting unknown resolver", () => {
52
65
  const form = formspec(field.dynamicEnum("country", "countries"));
53
66
  const resolvers = defineResolvers(form, {
54
- countries: async () => ({ options: [], validity: "valid" }),
67
+ countries: async () => {
68
+ await Promise.resolve(); // Needed for async interface compliance
69
+ return { options: [], validity: "valid" };
70
+ },
55
71
  });
56
72
  expect(() => resolvers.get("unknown")).toThrow("No resolver found for data source: unknown");
57
73
  });
@@ -60,6 +76,7 @@ describe("defineResolvers", () => {
60
76
  let receivedParams;
61
77
  const resolvers = defineResolvers(form, {
62
78
  products: async (params) => {
79
+ await Promise.resolve(); // Needed for async interface compliance
63
80
  receivedParams = params;
64
81
  return { options: [], validity: "valid" };
65
82
  },
@@ -1 +1 @@
1
- {"version":3,"file":"resolvers.test.js","sourceRoot":"","sources":["../../src/__tests__/resolvers.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resolvers.test.js","sourceRoot":"","sources":["../../src/__tests__/resolvers.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,eAAe,CAAC;AAEjE,6DAA6D;AAC7D,6CAA6C;AAE7C,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;QAEvF,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,EAAE;YACtC,SAAS,EAAE,KAAK,IAAI,EAAE;gBACpB,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,wCAAwC;gBACjE,OAAO;oBACL,OAAO,EAAE;wBACP,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,eAAe,EAAE;wBACvC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE;qBACjC;oBACD,QAAQ,EAAE,OAAgB;iBAC3B,CAAC;YACJ,CAAC;SACF,CAAC,CAAC;QAEH,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9C,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC;QAEjE,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,EAAE;YACtC,SAAS,EAAE,KAAK,IAAI,EAAE;gBACpB,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,wCAAwC;gBACjE,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;oBAClD,QAAQ,EAAE,OAAgB;iBAC3B,CAAC;YACJ,CAAC;SACF,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;QAElD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACvC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,IAAI,GAAG,QAAQ,CACnB,KAAK,CACH,UAAU,EACV,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,WAAW,CAAC,EACzC,KAAK,CAAC,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CACpC,CACF,CAAC;QAEF,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,EAAE;YACtC,SAAS,EAAE,KAAK,IAAI,EAAE;gBACpB,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,wCAAwC;gBACjE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAgB,EAAE,CAAC;YACrD,CAAC;YACD,MAAM,EAAE,KAAK,IAAI,EAAE;gBACjB,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,wCAAwC;gBACjE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAgB,EAAE,CAAC;YACrD,CAAC;SACF,CAAC,CAAC;QAEH,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,IAAI,GAAG,QAAQ,CACnB,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,CAAU,CAAC,EACvC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAC5D,CAAC;QAEF,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,EAAE;YACtC,MAAM,EAAE,KAAK,IAAI,EAAE;gBACjB,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,wCAAwC;gBACjE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAgB,EAAE,CAAC;YACrD,CAAC;SACF,CAAC,CAAC;QAEH,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC;QAEjE,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,EAAE;YACtC,SAAS,EAAE,KAAK,IAAI,EAAE;gBACpB,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,wCAAwC;gBACjE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAgB,EAAE,CAAC;YACrD,CAAC;SACF,CAAC,CAAC;QAEH,MAAM,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,SAAwB,CAAC,CAAC,CAAC,OAAO,CAC3D,4CAA4C,CAC7C,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC;QAE5F,IAAI,cAAmD,CAAC;QAExD,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,EAAE;YACtC,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;gBACzB,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,wCAAwC;gBACjE,cAAc,GAAG,MAAM,CAAC;gBACxB,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAgB,EAAE,CAAC;YACrD,CAAC;SACF,CAAC,CAAC;QAEH,MAAM,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;QAEvD,MAAM,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -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,CAClB,MAAM,SAAS,MAAM,kBAAkB,EACvC,CAAC,GAAG,kBAAkB,CAAC,MAAM,CAAC,IAC5B,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3E;;GAEG;AACH,KAAK,qBAAqB,CAAC,CAAC,IAAI,CAAC,SAAS,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACvE,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;AAEd,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;AAsBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,eAAe,CAC7B,CAAC,SAAS,SAAS,WAAW,EAAE,EAChC,OAAO,SAAS,MAAM,GAAG,8BAA8B,CAAC,CAAC,CAAC,GAAG,MAAM,EAEnE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EACjB,SAAS,EAAE,WAAW,CAAC,OAAO,CAAC,GAC9B,gBAAgB,CAAC,OAAO,CAAC,CAgC3B"}
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"}
package/dist/resolvers.js CHANGED
@@ -12,7 +12,9 @@ function extractSources(elements) {
12
12
  const sources = new Set();
13
13
  function visit(el) {
14
14
  if (el._type === "field" && el._field === "dynamic_enum") {
15
- sources.add(el.source);
15
+ // After checking _field, we know this is a DynamicEnumField
16
+ const dynamicField = el;
17
+ sources.add(dynamicField.source);
16
18
  }
17
19
  else if (el._type === "group") {
18
20
  el.elements.forEach(visit);
@@ -1 +1 @@
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"}
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,4DAA4D;YAC5D,MAAM,YAAY,GAAqC,EAAE,CAAC;YAC1D,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACnC,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,CAG7B,IAAiB,EAAE,SAA+B;IAClD,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,WAAW,GAAG,IAAI,GAAG,CACzB,MAAM,CAAC,OAAO,CAAC,SAAS,CAAmD,CAC5E,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"}
@@ -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.0",
3
+ "version": "0.1.0-alpha.10",
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.0"
19
+ "@formspec/core": "0.1.0-alpha.10"
19
20
  },
20
21
  "devDependencies": {
21
22
  "vitest": "^3.0.0",
22
- "@formspec/dsl": "0.1.0-alpha.0"
23
+ "@formspec/dsl": "0.1.0-alpha.10"
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",