@formspec/runtime 0.1.0-alpha.0

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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=resolvers.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolvers.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/resolvers.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,71 @@
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolvers.test.js","sourceRoot":"","sources":["../../src/__tests__/resolvers.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
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
+ export { defineResolvers, type Resolver, type ResolverMap, type ResolverRegistry, } from "./resolvers.js";
36
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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"}
package/dist/index.js ADDED
@@ -0,0 +1,36 @@
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
+ export { defineResolvers, } from "./resolvers.js";
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EACL,eAAe,GAIhB,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,85 @@
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
+ import type { DataSourceRegistry, FetchOptionsResponse, FormElement, FormSpec, DynamicEnumField, Group, Conditional } from "@formspec/core";
9
+ /**
10
+ * A resolver function that fetches options for a data source.
11
+ *
12
+ * @typeParam Source - The data source key from DataSourceRegistry
13
+ * @typeParam T - The data type for options (from DataSourceRegistry)
14
+ */
15
+ export type Resolver<Source extends keyof DataSourceRegistry, T = DataSourceRegistry[Source]> = (params?: Record<string, unknown>) => Promise<FetchOptionsResponse<T>>;
16
+ /**
17
+ * Extracts all dynamic enum source keys from a form's elements.
18
+ */
19
+ 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;
20
+ type ExtractDynamicSourcesFromArray<Elements> = Elements extends readonly [
21
+ infer First,
22
+ ...infer Rest
23
+ ] ? ExtractDynamicSources<First> | ExtractDynamicSourcesFromArray<Rest> : never;
24
+ /**
25
+ * Map of resolver functions for a form's dynamic data sources.
26
+ */
27
+ export type ResolverMap<Sources extends string> = {
28
+ [S in Sources]: S extends keyof DataSourceRegistry ? Resolver<S> : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;
29
+ };
30
+ /**
31
+ * A resolver registry that provides type-safe access to resolvers.
32
+ */
33
+ export interface ResolverRegistry<Sources extends string> {
34
+ /**
35
+ * Gets a resolver by data source name.
36
+ */
37
+ get<S extends Sources>(source: S): S extends keyof DataSourceRegistry ? Resolver<S> : (params?: Record<string, unknown>) => Promise<FetchOptionsResponse>;
38
+ /**
39
+ * Checks if a resolver exists for a data source.
40
+ */
41
+ has(source: string): boolean;
42
+ /**
43
+ * Gets all registered data source names.
44
+ */
45
+ sources(): Sources[];
46
+ }
47
+ /**
48
+ * Defines resolvers for a form's dynamic data sources.
49
+ *
50
+ * This function provides type-safe resolver definitions that match
51
+ * the form's dynamic enum fields.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * declare module "@formspec/core" {
56
+ * interface DataSourceRegistry {
57
+ * countries: { id: string; code: string; name: string };
58
+ * }
59
+ * }
60
+ *
61
+ * const form = formspec(
62
+ * field.dynamicEnum("country", "countries", { label: "Country" }),
63
+ * );
64
+ *
65
+ * const resolvers = defineResolvers(form, {
66
+ * countries: async () => ({
67
+ * options: [
68
+ * { value: "us", label: "United States", data: { id: "us", code: "US", name: "United States" } },
69
+ * { value: "ca", label: "Canada", data: { id: "ca", code: "CA", name: "Canada" } },
70
+ * ],
71
+ * validity: "valid",
72
+ * }),
73
+ * });
74
+ *
75
+ * // Use the resolver
76
+ * const result = await resolvers.get("countries")();
77
+ * ```
78
+ *
79
+ * @param form - The FormSpec containing dynamic enum fields
80
+ * @param resolvers - Map of resolver functions for each data source
81
+ * @returns A ResolverRegistry for type-safe access to resolvers
82
+ */
83
+ export declare function defineResolvers<E extends readonly FormElement[], Sources extends string = ExtractDynamicSourcesFromArray<E> & string>(form: FormSpec<E>, resolvers: ResolverMap<Sources>): ResolverRegistry<Sources>;
84
+ export {};
85
+ //# sourceMappingURL=resolvers.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,88 @@
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
@@ -0,0 +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"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@formspec/runtime",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "Runtime helpers for FormSpec - resolvers and data fetching",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/runtime.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/runtime.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "dependencies": {
18
+ "@formspec/core": "0.1.0-alpha.0"
19
+ },
20
+ "devDependencies": {
21
+ "vitest": "^3.0.0",
22
+ "@formspec/dsl": "0.1.0-alpha.0"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "keywords": [],
28
+ "license": "UNLICENSED",
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "clean": "rm -rf dist temp",
32
+ "typecheck": "tsc --noEmit",
33
+ "test": "vitest run",
34
+ "api-extractor": "api-extractor run",
35
+ "api-extractor:local": "api-extractor run --local",
36
+ "api-documenter": "api-documenter markdown -i temp -o docs"
37
+ }
38
+ }