@beignet/react-hook-form 0.0.49 → 0.0.51

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @beignet/react-hook-form
2
2
 
3
+ ## 0.0.51
4
+
5
+ ## 0.0.50
6
+
7
+ ### Patch Changes
8
+
9
+ - cede255: Keep JSON body nulls distinct in React Query keys, expose scoped service work
10
+ through Fetch servers, preserve streamed responses, and reject non-object form
11
+ body inputs at compile time.
12
+
3
13
  ## 0.0.49
4
14
 
5
15
  ### Patch Changes
package/README.md CHANGED
@@ -9,7 +9,10 @@
9
9
  > Beignet is experimental alpha software. The `0.0.x` package line is for early
10
10
  > evaluation, and APIs may change between releases while the framework settles.
11
11
 
12
- This package provides automatic form validation using your contract's body schema. Works with any [Standard Schema](https://github.com/standard-schema/standard-schema) library (Zod, Valibot, ArkType, etc.).
12
+ This package provides automatic form validation using a contract body schema
13
+ whose input is object-shaped. It works with any
14
+ [Standard Schema](https://github.com/standard-schema/standard-schema) library
15
+ (Zod, Valibot, ArkType, etc.).
13
16
 
14
17
  ## Installation
15
18
 
@@ -176,7 +179,10 @@ const rhf = createReactHookForm();
176
179
 
177
180
  ### `rhf(contract)`
178
181
 
179
- Creates a React Hook Form adapter for a contract.
182
+ Creates a React Hook Form adapter for a contract whose body schema input is an
183
+ object. TypeScript rejects contracts with scalar or array body inputs because
184
+ they do not define named form fields. The schema output may still transform to
185
+ any submit value.
180
186
 
181
187
  ```ts
182
188
  const adapter = rhf(createTodo);
@@ -222,6 +228,20 @@ input/output split:
222
228
  - `handleSubmit` callbacks receive the schema **output**: the parsed values
223
229
  after coercion, transforms, and defaults run.
224
230
 
231
+ The schema input must be object-shaped. Keep lists and scalar values inside
232
+ named fields rather than making the request body itself an array or scalar:
233
+
234
+ ```ts
235
+ const replaceTags = todos
236
+ .put("/api/todos/:id/tags")
237
+ .body(z.object({ tags: z.array(z.string()) }))
238
+ .responses({ 200: TodoSchema });
239
+
240
+ const form = rhf(replaceTags).useForm({
241
+ defaultValues: { tags: [] },
242
+ });
243
+ ```
244
+
225
245
  ```ts
226
246
  // Contract definition
227
247
  const createTodo = todos
package/dist/index.d.ts CHANGED
@@ -2,10 +2,11 @@ import { type ErrorMessageOverrides } from "@beignet/core/client";
2
2
  import { type ContractLike, type HttpContractConfig, type InferInput, type InferOutput, type ResolveContract, type StandardSchemaV1 } from "@beignet/core/contracts";
3
3
  import { type FieldValues, type UseFormProps, type UseFormReturn } from "react-hook-form";
4
4
  /**
5
- * Constrain a schema input type to React Hook Form field values without
6
- * widening field name inference for object inputs.
5
+ * Keep only object-shaped React Hook Form field values. Arrays are objects in
6
+ * JavaScript, but they do not define the named field-value record expected by
7
+ * this adapter.
7
8
  */
8
- type AsFieldValues<T> = T extends FieldValues ? T : T & FieldValues;
9
+ type ObjectFieldValues<T> = [T] extends [FieldValues] ? Extract<T, readonly unknown[]> extends never ? T : never : never;
9
10
  /**
10
11
  * Live form field values inferred from the contract body schema input.
11
12
  *
@@ -13,13 +14,19 @@ type AsFieldValues<T> = T extends FieldValues ? T : T & FieldValues;
13
14
  * `defaultValues`) hold pre-validation input, so coercing or transforming body
14
15
  * schemas type fields as what the user edits, not what the schema produces.
15
16
  */
16
- type FormInput<TContract extends HttpContractConfig> = TContract["body"] extends StandardSchemaV1 ? AsFieldValues<InferInput<TContract["body"]>> : FieldValues;
17
+ type FormInput<TContract extends HttpContractConfig> = TContract["body"] extends StandardSchemaV1 ? ObjectFieldValues<InferInput<TContract["body"]>> : never;
17
18
  /**
18
19
  * Validated submit values inferred from the contract body schema output.
19
20
  *
20
21
  * `handleSubmit` callbacks receive the resolver-parsed output.
21
22
  */
22
- type FormOutput<TContract extends HttpContractConfig> = TContract["body"] extends StandardSchemaV1 ? InferOutput<TContract["body"]> : FieldValues;
23
+ type FormOutput<TContract extends HttpContractConfig> = TContract["body"] extends StandardSchemaV1 ? InferOutput<TContract["body"]> : never;
24
+ /**
25
+ * Contract input accepted by the React Hook Form adapter.
26
+ */
27
+ type ReactHookFormContract<TContractLike extends ContractLike> = [
28
+ FormInput<ResolveContract<TContractLike>>
29
+ ] extends [never] ? never : TContractLike;
23
30
  /**
24
31
  * React Hook Form options accepted by the Beignet adapter.
25
32
  */
@@ -36,9 +43,9 @@ type RhfFormOptions<TContract extends HttpContractConfig> = Omit<UseFormProps<Fo
36
43
  resolverEnabled?: boolean;
37
44
  };
38
45
  /**
39
- * React Hook Form adapter for one contract.
46
+ * React Hook Form adapter methods for one compatible contract.
40
47
  */
41
- export type ReactHookFormContractAdapter<TContract extends HttpContractConfig> = {
48
+ type ReactHookFormContractAdapterShape<TContract extends HttpContractConfig> = {
42
49
  /**
43
50
  * Generate `UseFormProps` for any React Hook Form usage.
44
51
  */
@@ -48,15 +55,23 @@ export type ReactHookFormContractAdapter<TContract extends HttpContractConfig> =
48
55
  */
49
56
  useForm: (props?: RhfFormOptions<TContract>) => UseFormReturn<FormInput<TContract>, unknown, FormOutput<TContract>>;
50
57
  };
58
+ /**
59
+ * React Hook Form adapter for one contract whose body schema input is
60
+ * object-shaped.
61
+ */
62
+ export type ReactHookFormContractAdapter<TContract extends HttpContractConfig> = [
63
+ FormInput<TContract>
64
+ ] extends [never] ? never : ReactHookFormContractAdapterShape<TContract>;
51
65
  /**
52
66
  * Create a React Hook Form adapter factory.
53
67
  *
54
68
  * Mirrors the React integration pattern used by `createReactQuery()` and `createNuqs()`:
55
69
  * create the adapter once, then bind individual contracts with `rhf(contract)`.
56
- * Contracts must declare a body schema. After binding, callers may override or
57
- * disable the generated resolver through `formOptions(...)` or `useForm(...)`.
70
+ * Contracts must declare a body schema whose input is object-shaped. After
71
+ * binding, callers may override or disable the generated resolver through
72
+ * `formOptions(...)` or `useForm(...)`.
58
73
  */
59
- export declare function createReactHookForm(): <TContractLike extends ContractLike>(contract: TContractLike) => ReactHookFormContractAdapter<ResolveContract<TContractLike>>;
74
+ export declare function createReactHookForm(): <TContractLike extends ContractLike>(contract: ReactHookFormContract<TContractLike>) => ReactHookFormContractAdapter<ResolveContract<TContractLike>>;
60
75
  /**
61
76
  * Root-level form error for `form.setError("root", ...)`.
62
77
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,eAAe,EAEpB,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,KAAK,WAAW,EAEhB,KAAK,YAAY,EACjB,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;AAEzB;;;GAGG;AACH,KAAK,aAAa,CAAC,CAAC,IAAI,CAAC,SAAS,WAAW,GAAG,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC;AAEpE;;;;;;GAMG;AACH,KAAK,SAAS,CAAC,SAAS,SAAS,kBAAkB,IACjD,SAAS,CAAC,MAAM,CAAC,SAAS,gBAAgB,GACtC,aAAa,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,GAC5C,WAAW,CAAC;AAElB;;;;GAIG;AACH,KAAK,UAAU,CAAC,SAAS,SAAS,kBAAkB,IAClD,SAAS,CAAC,MAAM,CAAC,SAAS,gBAAgB,GACtC,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,GAC9B,WAAW,CAAC;AAElB;;GAEG;AACH,KAAK,cAAc,CAAC,SAAS,SAAS,kBAAkB,IAAI,IAAI,CAC9D,YAAY,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,EAClE,UAAU,CACX,GAAG;IACF;;;OAGG;IACH,QAAQ,CAAC,EAAE,YAAY,CACrB,SAAS,CAAC,SAAS,CAAC,EACpB,OAAO,EACP,UAAU,CAAC,SAAS,CAAC,CACtB,CAAC,UAAU,CAAC,CAAC;IAEd;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,4BAA4B,CAAC,SAAS,SAAS,kBAAkB,IAC3E;IACE;;OAEG;IACH,WAAW,EAAE,CACX,KAAK,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,KAC9B,YAAY,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;IAExE;;OAEG;IACH,OAAO,EAAE,CACP,KAAK,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,KAC9B,aAAa,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;CAC1E,CAAC;AAsDJ;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,KACb,aAAa,SAAS,YAAY,EACpD,UAAU,aAAa,KACtB,4BAA4B,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CAGhE;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,qBAAqB,GAChC,aAAa,CAKf"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ function createReactHookFormAdapter(contract) {
14
14
  const bodySchema = resolvedContract.body;
15
15
  const { resolverEnabled = true, resolver: resolverOverride, ...rest } = props ?? {};
16
16
  const resolver = resolverOverride ??
17
- (bodySchema && resolverEnabled
17
+ (resolverEnabled
18
18
  ? standardSchemaResolver(bodySchema)
19
19
  : undefined);
20
20
  return {
@@ -33,8 +33,9 @@ function createReactHookFormAdapter(contract) {
33
33
  *
34
34
  * Mirrors the React integration pattern used by `createReactQuery()` and `createNuqs()`:
35
35
  * create the adapter once, then bind individual contracts with `rhf(contract)`.
36
- * Contracts must declare a body schema. After binding, callers may override or
37
- * disable the generated resolver through `formOptions(...)` or `useForm(...)`.
36
+ * Contracts must declare a body schema whose input is object-shaped. After
37
+ * binding, callers may override or disable the generated resolver through
38
+ * `formOptions(...)` or `useForm(...)`.
38
39
  */
39
40
  export function createReactHookForm() {
40
41
  return function rhf(contract) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EACL,oBAAoB,GAErB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAML,eAAe,GAEhB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAEL,OAAO,IAAI,UAAU,GAGtB,MAAM,iBAAiB,CAAC;AA0EzB,SAAS,0BAA0B,CACjC,QAAuB;IAEvB,MAAM,gBAAgB,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IAEnD,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,OAAO,gBAAgB,CAAC,IAAI,kCAAkC;YAC5D,iFAAiF;YACjF,6EAA6E,CAChF,CAAC;IACJ,CAAC;IAKD,SAAS,WAAW,CAClB,KAAsD;QAEtD,MAAM,UAAU,GAAG,gBAAgB,CAAC,IAGvB,CAAC;QAEd,MAAM,EACJ,eAAe,GAAG,IAAI,EACtB,QAAQ,EAAE,gBAAgB,EAC1B,GAAG,IAAI,EACR,GAAG,KAAK,IAAI,EAAE,CAAC;QAEhB,MAAM,QAAQ,GACZ,gBAAgB;YAChB,CAAC,UAAU,IAAI,eAAe;gBAC5B,CAAC,CAAC,sBAAsB,CAAyB,UAAU,CAAC;gBAC5D,CAAC,CAAC,SAAS,CAAC,CAAC;QAEjB,OAAO;YACL,GAAI,IAA6C;YACjD,QAAQ;SACT,CAAC;IACJ,CAAC;IAED,SAAS,OAAO,CACd,KAAsD;QAEtD,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QACnC,OAAO,UAAU,CAAyB,OAAO,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;AAClC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,SAAS,GAAG,CACjB,QAAuB;QAEvB,OAAO,0BAA0B,CAAC,QAAQ,CAAC,CAAC;IAC9C,CAAC,CAAC;AACJ,CAAC;AAUD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,aAAa,CAC3B,KAAc,EACd,QAAgB,EAChB,SAAiC;IAEjC,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC;KAC1D,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EACL,oBAAoB,GAErB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAML,eAAe,GAEhB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAEL,OAAO,IAAI,UAAU,GAGtB,MAAM,iBAAiB,CAAC;AAmGzB,SAAS,0BAA0B,CACjC,QAA8C;IAE9C,MAAM,gBAAgB,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IAEnD,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,OAAO,gBAAgB,CAAC,IAAI,kCAAkC;YAC5D,iFAAiF;YACjF,6EAA6E,CAChF,CAAC;IACJ,CAAC;IAKD,SAAS,WAAW,CAClB,KAAsD;QAEtD,MAAM,UAAU,GAAG,gBAAgB,CAAC,IAAuC,CAAC;QAE5E,MAAM,EACJ,eAAe,GAAG,IAAI,EACtB,QAAQ,EAAE,gBAAgB,EAC1B,GAAG,IAAI,EACR,GAAG,KAAK,IAAI,EAAE,CAAC;QAEhB,MAAM,QAAQ,GACZ,gBAAgB;YAChB,CAAC,eAAe;gBACd,CAAC,CAAC,sBAAsB,CAAyB,UAAU,CAAC;gBAC5D,CAAC,CAAC,SAAS,CAAC,CAAC;QAEjB,OAAO;YACL,GAAI,IAA6C;YACjD,QAAQ;SACT,CAAC;IACJ,CAAC;IAED,SAAS,OAAO,CACd,KAAsD;QAEtD,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QACnC,OAAO,UAAU,CAAyB,OAAO,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;AAClC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,SAAS,GAAG,CACjB,QAA8C;QAE9C,OAAO,0BAA0B,CAAC,QAAQ,CAAC,CAAC;IAC9C,CAAC,CAAC;AACJ,CAAC;AAUD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,aAAa,CAC3B,KAAc,EACd,QAAgB,EAChB,SAAiC;IAEjC,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC;KAC1D,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beignet/react-hook-form",
3
- "version": "0.0.49",
3
+ "version": "0.0.51",
4
4
  "type": "module",
5
5
  "description": "React Hook Form integration for Beignet with Standard Schema support",
6
6
  "main": "./dist/index.js",
@@ -26,9 +26,14 @@ routes, providers, infra adapters, `server/`, or `app-context.ts`.
26
26
 
27
27
  ## Contract Requirements
28
28
 
29
- React Hook Form binds to the contract body schema. If a contract has no
30
- `.body(...)`, `rhf(contract)` throws because there are no form fields or body
31
- validation to infer.
29
+ React Hook Form binds to the contract body schema. TypeScript rejects contracts
30
+ without `.body(...)` because there are no form fields or body validation to
31
+ infer; untyped JavaScript calls receive the same failure at runtime.
32
+
33
+ The body schema input must be an object with named fields. TypeScript rejects
34
+ scalar and array body inputs. Put scalar values or lists under a field, for
35
+ example `.body(z.object({ tags: z.array(z.string()) }))`, when the endpoint is
36
+ backed by a form.
32
37
 
33
38
  Keep shared form DTOs in `features/<feature>/schemas.ts`, then reuse them from
34
39
  contracts, use cases, tests, and components.
@@ -119,6 +124,8 @@ codes declared on the contract with `.errors(...)`.
119
124
  params, headers, tenant IDs, and auth-derived values belong in mutation
120
125
  variables or app context, not form fields.
121
126
  - Contracts without `.body(...)` are not form contracts.
127
+ - Scalar and array request bodies are not form contracts; wrap their values in
128
+ an object with named fields.
122
129
  - `handleSubmit` returns parsed schema output. For transforms that change field
123
130
  types, submit `form.getValues()` after validation when the contract client
124
131
  expects body input.
package/src/index.ts CHANGED
@@ -22,10 +22,15 @@ import {
22
22
  } from "react-hook-form";
23
23
 
24
24
  /**
25
- * Constrain a schema input type to React Hook Form field values without
26
- * widening field name inference for object inputs.
25
+ * Keep only object-shaped React Hook Form field values. Arrays are objects in
26
+ * JavaScript, but they do not define the named field-value record expected by
27
+ * this adapter.
27
28
  */
28
- type AsFieldValues<T> = T extends FieldValues ? T : T & FieldValues;
29
+ type ObjectFieldValues<T> = [T] extends [FieldValues]
30
+ ? Extract<T, readonly unknown[]> extends never
31
+ ? T
32
+ : never
33
+ : never;
29
34
 
30
35
  /**
31
36
  * Live form field values inferred from the contract body schema input.
@@ -36,8 +41,8 @@ type AsFieldValues<T> = T extends FieldValues ? T : T & FieldValues;
36
41
  */
37
42
  type FormInput<TContract extends HttpContractConfig> =
38
43
  TContract["body"] extends StandardSchemaV1
39
- ? AsFieldValues<InferInput<TContract["body"]>>
40
- : FieldValues;
44
+ ? ObjectFieldValues<InferInput<TContract["body"]>>
45
+ : never;
41
46
 
42
47
  /**
43
48
  * Validated submit values inferred from the contract body schema output.
@@ -47,7 +52,16 @@ type FormInput<TContract extends HttpContractConfig> =
47
52
  type FormOutput<TContract extends HttpContractConfig> =
48
53
  TContract["body"] extends StandardSchemaV1
49
54
  ? InferOutput<TContract["body"]>
50
- : FieldValues;
55
+ : never;
56
+
57
+ /**
58
+ * Contract input accepted by the React Hook Form adapter.
59
+ */
60
+ type ReactHookFormContract<TContractLike extends ContractLike> = [
61
+ FormInput<ResolveContract<TContractLike>>,
62
+ ] extends [never]
63
+ ? never
64
+ : TContractLike;
51
65
 
52
66
  /**
53
67
  * React Hook Form options accepted by the Beignet adapter.
@@ -74,28 +88,39 @@ type RhfFormOptions<TContract extends HttpContractConfig> = Omit<
74
88
  };
75
89
 
76
90
  /**
77
- * React Hook Form adapter for one contract.
91
+ * React Hook Form adapter methods for one compatible contract.
92
+ */
93
+ type ReactHookFormContractAdapterShape<TContract extends HttpContractConfig> = {
94
+ /**
95
+ * Generate `UseFormProps` for any React Hook Form usage.
96
+ */
97
+ formOptions: (
98
+ props?: RhfFormOptions<TContract>,
99
+ ) => UseFormProps<FormInput<TContract>, unknown, FormOutput<TContract>>;
100
+
101
+ /**
102
+ * Convenience wrapper around `useForm(formOptions(props))`.
103
+ */
104
+ useForm: (
105
+ props?: RhfFormOptions<TContract>,
106
+ ) => UseFormReturn<FormInput<TContract>, unknown, FormOutput<TContract>>;
107
+ };
108
+
109
+ /**
110
+ * React Hook Form adapter for one contract whose body schema input is
111
+ * object-shaped.
78
112
  */
79
113
  export type ReactHookFormContractAdapter<TContract extends HttpContractConfig> =
80
- {
81
- /**
82
- * Generate `UseFormProps` for any React Hook Form usage.
83
- */
84
- formOptions: (
85
- props?: RhfFormOptions<TContract>,
86
- ) => UseFormProps<FormInput<TContract>, unknown, FormOutput<TContract>>;
87
-
88
- /**
89
- * Convenience wrapper around `useForm(formOptions(props))`.
90
- */
91
- useForm: (
92
- props?: RhfFormOptions<TContract>,
93
- ) => UseFormReturn<FormInput<TContract>, unknown, FormOutput<TContract>>;
94
- };
114
+ [FormInput<TContract>] extends [never]
115
+ ? never
116
+ : ReactHookFormContractAdapterShape<TContract>;
95
117
 
96
118
  function createReactHookFormAdapter<TContractLike extends ContractLike>(
97
- contract: TContractLike,
98
- ): ReactHookFormContractAdapter<ResolveContract<TContractLike>> {
119
+ contract: ReactHookFormContract<TContractLike>,
120
+ ): ReactHookFormContractAdapter<ResolveContract<TContractLike>>;
121
+ function createReactHookFormAdapter<TContractLike extends ContractLike>(
122
+ contract: ReactHookFormContract<TContractLike>,
123
+ ): ReactHookFormContractAdapterShape<ResolveContract<TContractLike>> {
99
124
  const resolvedContract = resolveContract(contract);
100
125
 
101
126
  if (!resolvedContract.body) {
@@ -112,10 +137,7 @@ function createReactHookFormAdapter<TContractLike extends ContractLike>(
112
137
  function formOptions(
113
138
  props?: RhfFormOptions<ResolveContract<TContractLike>>,
114
139
  ): UseFormProps<Input, unknown, Output> {
115
- const bodySchema = resolvedContract.body as
116
- | StandardSchemaV1<Input, Output>
117
- | null
118
- | undefined;
140
+ const bodySchema = resolvedContract.body as StandardSchemaV1<Input, Output>;
119
141
 
120
142
  const {
121
143
  resolverEnabled = true,
@@ -125,7 +147,7 @@ function createReactHookFormAdapter<TContractLike extends ContractLike>(
125
147
 
126
148
  const resolver =
127
149
  resolverOverride ??
128
- (bodySchema && resolverEnabled
150
+ (resolverEnabled
129
151
  ? standardSchemaResolver<Input, unknown, Output>(bodySchema)
130
152
  : undefined);
131
153
 
@@ -150,12 +172,13 @@ function createReactHookFormAdapter<TContractLike extends ContractLike>(
150
172
  *
151
173
  * Mirrors the React integration pattern used by `createReactQuery()` and `createNuqs()`:
152
174
  * create the adapter once, then bind individual contracts with `rhf(contract)`.
153
- * Contracts must declare a body schema. After binding, callers may override or
154
- * disable the generated resolver through `formOptions(...)` or `useForm(...)`.
175
+ * Contracts must declare a body schema whose input is object-shaped. After
176
+ * binding, callers may override or disable the generated resolver through
177
+ * `formOptions(...)` or `useForm(...)`.
155
178
  */
156
179
  export function createReactHookForm() {
157
180
  return function rhf<TContractLike extends ContractLike>(
158
- contract: TContractLike,
181
+ contract: ReactHookFormContract<TContractLike>,
159
182
  ): ReactHookFormContractAdapter<ResolveContract<TContractLike>> {
160
183
  return createReactHookFormAdapter(contract);
161
184
  };