@shivaedev/form 0.8.0 → 0.10.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.
- package/CHANGELOG.md +16 -0
- package/README.md +17 -16
- package/dist/form.d.ts +1 -1
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +10 -5
- package/dist/form.js.map +1 -1
- package/dist/messages.d.ts +4 -4
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +30 -9
- package/dist/messages.js.map +1 -1
- package/dist/shape.d.ts +8 -2
- package/dist/shape.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/form.ts +29 -6
- package/src/messages.ts +39 -10
- package/src/shape.ts +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0 - 2026-10-31
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Configure `requiredMessages` per string field to replace the generic required message without rebuilding the schema. Field feedback, path feedback and invalid submission messages use the same wording; explicit schema messages keep precedence.
|
|
8
|
+
|
|
9
|
+
## 0.9.0 - 2026-10-30
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Forms without application service requirements can omit `runtime` and use an empty atom runtime. Handlers, advisory checks and schema decoding that require application services still need a matching runtime.
|
|
14
|
+
|
|
15
|
+
### Breaking
|
|
16
|
+
|
|
17
|
+
- `Config` is a conditional type so service requirements determine whether `runtime` is required. Extend generic configurations with an intersection type instead of an interface that extends `Config`.
|
|
18
|
+
|
|
3
19
|
## 0.8.0 - 2026-10-30
|
|
4
20
|
|
|
5
21
|
### Fixed
|
package/README.md
CHANGED
|
@@ -10,7 +10,6 @@ An input gives you a string; your service needs a number. Meanwhile, fresh serve
|
|
|
10
10
|
const order = form(Schema.Struct({ name: Schema.String, quantity: Schema.NumberFromString }), {
|
|
11
11
|
initialValues: { name: "Paper", quantity: "300" },
|
|
12
12
|
onSubmit: Effect.succeed,
|
|
13
|
-
runtime,
|
|
14
13
|
});
|
|
15
14
|
|
|
16
15
|
order.change("name", "Copy paper");
|
|
@@ -29,21 +28,19 @@ The **baseline** is the encoded value against which the form recognizes edits. I
|
|
|
29
28
|
|
|
30
29
|
Keep one form instance for one editing session. Use a new instance for a different entity or user session. Query acquisition, entity identity, normalized saved rows and create-reset orchestration can be composed with [`useEditor` and `useCreate`](https://github.com/ShivaeDev/platform/blob/main/packages/react/README.md) from `@shivaedev/react`.
|
|
31
30
|
|
|
32
|
-
### Once per application: supply
|
|
31
|
+
### Once per application: supply services and a registry
|
|
33
32
|
|
|
34
|
-
`form`
|
|
33
|
+
`form` uses an empty atom runtime when the handler, field checks and schema decoding need no application services. To use application services, pass `runtime: Atom.runtime(layer)` with a Layer that provides them. Missing service dependencies fail to compile. The native `Scope`, `AtomRegistry` and `Reactivity` services are available without a supplied runtime.
|
|
35
34
|
|
|
36
35
|
Use an `AtomRegistry` to mount and read atoms and write the submit atom. Own its lifetime at the application boundary. In React, provide that registry through `RegistryContext` from `@effect/atom-react`.
|
|
37
36
|
|
|
38
37
|
### Once per feature: define fields and submission
|
|
39
38
|
|
|
40
39
|
```ts
|
|
41
|
-
import { Effect,
|
|
42
|
-
import * as Atom from "effect/reactivity/Atom";
|
|
40
|
+
import { Effect, Schema } from "effect";
|
|
43
41
|
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
44
42
|
import { form } from "@shivaedev/form/form.ts";
|
|
45
43
|
|
|
46
|
-
const runtime = Atom.runtime(Layer.empty);
|
|
47
44
|
const fields = Schema.Struct({
|
|
48
45
|
name: Schema.NonEmptyString,
|
|
49
46
|
quantity: Schema.NumberFromString,
|
|
@@ -52,7 +49,6 @@ const fields = Schema.Struct({
|
|
|
52
49
|
const order = form(fields, {
|
|
53
50
|
initialValues: { name: "Paper", quantity: "300" },
|
|
54
51
|
onSubmit: (values, submitter) => (values.name === "reserved" ? submitter.fail("name", "Choose another name") : Effect.succeed(values)),
|
|
55
|
-
runtime,
|
|
56
52
|
});
|
|
57
53
|
|
|
58
54
|
const registry = AtomRegistry.make();
|
|
@@ -71,21 +67,31 @@ try {
|
|
|
71
67
|
|
|
72
68
|
The input quantity is a string; the successful result contains quantity `350`. Invalid input fails with `Invalid` before `onSubmit` runs. The handler can fail a named field with `submitter.fail`; changing that field clears the rejection, and another submission uses the new input.
|
|
73
69
|
|
|
70
|
+
Set `requiredMessages` to give a top-level string field its own wording while keeping `Schema.NonEmptyString`:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
const order = form(fields, {
|
|
74
|
+
initialValues: { name: "", quantity: "300" },
|
|
75
|
+
onSubmit: Effect.succeed,
|
|
76
|
+
requiredMessages: { name: "Enter the order name" },
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
An empty name shows that message after blur or submission, including through `errorAt(["name"])` and `Invalid.messages`. Entering a name clears it. Explicit schema messages take precedence; longer minimum lengths keep their validation message. Fields without a configured message keep the default `"Required"`.
|
|
81
|
+
|
|
74
82
|
The handler has three arguments: decoded values, the submitter, and the submitted encoded snapshot. Use the third argument when coordinating work against what was submitted, rather than reading live fields after an asynchronous decode. The `react` create adapter uses that snapshot to preserve input typed while decoding.
|
|
75
83
|
|
|
76
84
|
### In the UI: bind the same form
|
|
77
85
|
|
|
78
86
|
```tsx
|
|
79
87
|
import { RegistryContext } from "@effect/atom-react";
|
|
80
|
-
import { Effect,
|
|
81
|
-
import * as Atom from "effect/reactivity/Atom";
|
|
88
|
+
import { Effect, Schema } from "effect";
|
|
82
89
|
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
83
90
|
import { useState } from "react";
|
|
84
91
|
import { createRoot } from "react-dom/client";
|
|
85
92
|
import { form } from "@shivaedev/form/form.ts";
|
|
86
93
|
import { useDirty, useField, useSubmit } from "@shivaedev/form/client/react.ts";
|
|
87
94
|
|
|
88
|
-
const runtime = Atom.runtime(Layer.empty);
|
|
89
95
|
const fields = Schema.Struct({ name: Schema.NonEmptyString, quantity: Schema.NumberFromString });
|
|
90
96
|
|
|
91
97
|
function OrderEditor() {
|
|
@@ -93,7 +99,6 @@ function OrderEditor() {
|
|
|
93
99
|
form(fields, {
|
|
94
100
|
initialValues: { name: "Paper", quantity: "300" },
|
|
95
101
|
onSubmit: Effect.succeed,
|
|
96
|
-
runtime,
|
|
97
102
|
}),
|
|
98
103
|
);
|
|
99
104
|
const name = useField(order, "name");
|
|
@@ -174,7 +179,6 @@ const order = form(fields, {
|
|
|
174
179
|
initialValues: { name: "Paper", quantity: "300" },
|
|
175
180
|
onSubmit: Effect.succeed,
|
|
176
181
|
persistedDraft: { key: "order:123", now, storage: () => window.localStorage },
|
|
177
|
-
runtime,
|
|
178
182
|
});
|
|
179
183
|
```
|
|
180
184
|
|
|
@@ -191,7 +195,6 @@ function OrderEditor({ now }: { readonly now: () => number }) {
|
|
|
191
195
|
initialValues: { name: "Paper", quantity: "300" },
|
|
192
196
|
onSubmit: Effect.succeed,
|
|
193
197
|
persistedDraft,
|
|
194
|
-
runtime,
|
|
195
198
|
}),
|
|
196
199
|
);
|
|
197
200
|
return <OrderFields form={order} />;
|
|
@@ -220,7 +223,6 @@ const profile = form(Schema.Struct({ name: Schema.String }), {
|
|
|
220
223
|
debounce: "300 millis",
|
|
221
224
|
initialValues: { name: "" },
|
|
222
225
|
onSubmit: Effect.succeed,
|
|
223
|
-
runtime,
|
|
224
226
|
});
|
|
225
227
|
```
|
|
226
228
|
|
|
@@ -241,7 +243,6 @@ const answers = form(
|
|
|
241
243
|
arrayKeys: { answers: (answer) => answer.id },
|
|
242
244
|
initialValues: { answers: [{ id: "question", text: "Yes" }] },
|
|
243
245
|
onSubmit: (_values, submitter) => submitter.fail(["answers", 0, "text"], "This answer is closed"),
|
|
244
|
-
runtime,
|
|
245
246
|
},
|
|
246
247
|
);
|
|
247
248
|
```
|
|
@@ -283,7 +284,7 @@ Import the module that defines a name; there is no root entry.
|
|
|
283
284
|
| Option | Type or role |
|
|
284
285
|
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
285
286
|
| `initialValues` | `Encoded<F>` |
|
|
286
|
-
| `runtime` |
|
|
287
|
+
| `runtime` | Optional when no application services are needed; otherwise an atom runtime for handler/check and schema decoding services |
|
|
287
288
|
| `onSubmit` | `(values, submitter, submitted) => Effect<A, E, R>`; the three arguments are decoded values, field failure helper and encoded snapshot |
|
|
288
289
|
| `checks` | Optional functions keyed by field, from encoded input to an Effect of `string \| undefined` |
|
|
289
290
|
| `arrayKeys` | Optional `ArrayKeys<F>` selectors keyed by top-level array field, from encoded element to stable `PropertyKey`; retains nested feedback across reordering |
|
package/dist/form.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
2
|
import { type Config, type Fields, type Form } from "./shape.ts";
|
|
3
|
-
export declare function form<TFields extends Fields, TValue, TError, TServices, TRuntimeError>(schema: Schema.Struct<TFields>, config: Config<TFields, TValue, TError, TServices, TRuntimeError>): Form<TFields, TValue, TError, TRuntimeError>;
|
|
3
|
+
export declare function form<TFields extends Fields, TValue, TError, TServices = never, TRuntimeError = never>(schema: Schema.Struct<TFields>, config: Config<TFields, TValue, TError, TServices, TRuntimeError>): Form<TFields, TValue, TError, TRuntimeError>;
|
|
4
4
|
//# sourceMappingURL=form.d.ts.map
|
package/dist/form.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,MAAM,EAAkC,MAAM,QAAQ,CAAC;AAS9F,OAAO,EACN,KAAK,MAAM,EAIX,KAAK,MAAM,EACX,KAAK,IAAI,EAKT,MAAM,YAAY,CAAC;AAapB,wBAAgB,IAAI,CAAC,OAAO,SAAS,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,KAAK,EAAE,aAAa,GAAG,KAAK,EACpG,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAC9B,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,CAAC,GAC/D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,CAgH9C"}
|
package/dist/form.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Equal, Result, Schema, SchemaParser } from "effect";
|
|
1
|
+
import { Effect, Equal, Layer, Result, Schema, SchemaParser } from "effect";
|
|
2
2
|
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
3
3
|
import * as Atom from "effect/reactivity/Atom";
|
|
4
4
|
import { draft } from "./draft.js";
|
|
@@ -6,11 +6,16 @@ import { literalChoices, messageAt, messagesByField, schemaFeedback } from "./me
|
|
|
6
6
|
import { pathMessageAt } from "./paths.js";
|
|
7
7
|
import { persistence } from "./persistence.js";
|
|
8
8
|
import { fromRef, propertyRef } from "./refs.js";
|
|
9
|
-
import { FieldFailure, Invalid } from "./shape.js";
|
|
9
|
+
import { FieldFailure, Invalid, } from "./shape.js";
|
|
10
10
|
import { statusOf } from "./statusOf.js";
|
|
11
11
|
import { trackRejections } from "./trackRejections.js";
|
|
12
|
+
const emptyRuntime = Atom.runtime(Layer.empty);
|
|
13
|
+
function configuredRuntime(config) {
|
|
14
|
+
return config.runtime ?? emptyRuntime;
|
|
15
|
+
}
|
|
12
16
|
export function form(schema, config) {
|
|
13
|
-
const { initialValues, onSubmit
|
|
17
|
+
const { initialValues, onSubmit } = config;
|
|
18
|
+
const runtime = configuredRuntime(config);
|
|
14
19
|
const debounce = config.debounce ?? "300 millis";
|
|
15
20
|
const checks = new Map(Object.entries(config.checks ?? {}));
|
|
16
21
|
const decode = SchemaParser.decodeUnknownEffect(schema, { errors: "all" });
|
|
@@ -33,7 +38,7 @@ export function form(schema, config) {
|
|
|
33
38
|
onFailure: (issue) => Result.fail(issue),
|
|
34
39
|
onSuccess: (value) => Result.succeed(value),
|
|
35
40
|
})));
|
|
36
|
-
const schemaErrors = schemaFeedback(decoded);
|
|
41
|
+
const schemaErrors = schemaFeedback(decoded, config.requiredMessages);
|
|
37
42
|
const checkError = Atom.family((name) => {
|
|
38
43
|
const check = checks.get(name);
|
|
39
44
|
if (check === undefined) {
|
|
@@ -67,7 +72,7 @@ export function form(schema, config) {
|
|
|
67
72
|
status.attempt();
|
|
68
73
|
const submission = editing.submission();
|
|
69
74
|
const submitted = submission.values;
|
|
70
|
-
const value = yield* decode(submitted).pipe(Effect.mapError((issue) => new Invalid({ messages: messagesByField(issue) })));
|
|
75
|
+
const value = yield* decode(submitted).pipe(Effect.mapError((issue) => new Invalid({ messages: messagesByField(issue, config.requiredMessages) })));
|
|
71
76
|
return yield* onSubmit(value, submitter, submitted).pipe(Effect.tapError(noted(submitted)), Effect.tap(() => Effect.sync(submission.accept)));
|
|
72
77
|
}));
|
|
73
78
|
return {
|
package/dist/form.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.js","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAoB,YAAY,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"form.js","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"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"}
|
package/dist/messages.d.ts
CHANGED
|
@@ -3,12 +3,12 @@ import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
|
3
3
|
import * as Atom from "effect/reactivity/Atom";
|
|
4
4
|
export type FieldMessages = Readonly<Record<string, string>>;
|
|
5
5
|
export declare const noMessages: FieldMessages;
|
|
6
|
-
export declare function messagesByField(issue: SchemaIssue.Issue): FieldMessages;
|
|
7
|
-
export declare function messageAtPath(issue: SchemaIssue.Issue, path: readonly PropertyKey[]): string | undefined;
|
|
6
|
+
export declare function messagesByField(issue: SchemaIssue.Issue, requiredMessages?: Readonly<Partial<Record<string, string>>>): FieldMessages;
|
|
7
|
+
export declare function messageAtPath(issue: SchemaIssue.Issue, path: readonly PropertyKey[], requiredMessages?: Readonly<Partial<Record<string, string>>>): string | undefined;
|
|
8
8
|
export declare function withoutField(messages: FieldMessages, name: string): FieldMessages;
|
|
9
9
|
export declare function literalChoices(input: SchemaAST.AST): readonly SchemaAST.LiteralValue[] | undefined;
|
|
10
|
-
export declare function messageAt(messages:
|
|
11
|
-
export declare function schemaFeedback<A, E>(decoded: Atom.Atom<AsyncResult.AsyncResult<Result.Result<A, SchemaIssue.Issue>, E
|
|
10
|
+
export declare function messageAt(messages: Readonly<Partial<Record<string, string>>>, name: string): string | undefined;
|
|
11
|
+
export declare function schemaFeedback<A, E>(decoded: Atom.Atom<AsyncResult.AsyncResult<Result.Result<A, SchemaIssue.Issue>, E>>, requiredMessages?: Readonly<Partial<Record<string, string>>>): {
|
|
12
12
|
at: (path: readonly PropertyKey[]) => Atom.Atom<string | undefined>;
|
|
13
13
|
field: (arg: string) => Atom.Atom<string | undefined>;
|
|
14
14
|
};
|
package/dist/messages.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,MAAM,EAAE,SAAS,EAAE,WAAW,EAAuB,MAAM,QAAQ,CAAC;AACrF,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAE/C,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAE7D,eAAO,MAAM,UAAU,EAAE,aAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,MAAM,EAAE,SAAS,EAAE,WAAW,EAAuB,MAAM,QAAQ,CAAC;AACrF,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAE/C,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAE7D,eAAO,MAAM,UAAU,EAAE,aAAkB,CAAC;AAgD5C,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAc,GAAG,aAAa,CAajJ;AAED,wBAAgB,aAAa,CAC5B,KAAK,EAAE,WAAW,CAAC,KAAK,EACxB,IAAI,EAAE,SAAS,WAAW,EAAE,EAC5B,gBAAgB,GAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAc,GACtE,MAAM,GAAG,SAAS,CAYpB;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAWjF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,GAAG,SAAS,SAAS,CAAC,YAAY,EAAE,GAAG,SAAS,CAalG;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE/G;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EAClC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EACnF,gBAAgB,GAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAc;IASvE,EAAE,SAAS,SAAS,WAAW,EAAE;IAUjC,KAAK;EAEN"}
|
package/dist/messages.js
CHANGED
|
@@ -15,14 +15,35 @@ function refusesEmpty(check) {
|
|
|
15
15
|
}
|
|
16
16
|
return payload.minLength === 1;
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
function formatter(required = REQUIRED) {
|
|
19
|
+
return SchemaIssue.makeFormatterStandardSchemaV1({
|
|
20
|
+
checkHook: (issue) => SchemaIssue.defaultCheckHook(issue) ?? (refusesEmpty(issue.filter) ? required : undefined),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const formatIssue = formatter();
|
|
24
|
+
function formattedIssues(issue, requiredMessages) {
|
|
25
|
+
const issues = formatIssue(issue).issues;
|
|
26
|
+
const customized = new Map();
|
|
27
|
+
return issues.map((entry, index) => {
|
|
28
|
+
const head = entry.path?.[0];
|
|
29
|
+
const required = head === undefined ? undefined : messageAt(requiredMessages, segmentKey(head));
|
|
30
|
+
if (required === undefined) {
|
|
31
|
+
return entry;
|
|
32
|
+
}
|
|
33
|
+
let entries = customized.get(required);
|
|
34
|
+
if (entries === undefined) {
|
|
35
|
+
entries = formatter(required)(issue).issues;
|
|
36
|
+
customized.set(required, entries);
|
|
37
|
+
}
|
|
38
|
+
return entries[index] ?? entry;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
20
41
|
function segmentKey(segment) {
|
|
21
42
|
return typeof segment === "object" ? String(segment.key) : String(segment);
|
|
22
43
|
}
|
|
23
|
-
export function messagesByField(issue) {
|
|
44
|
+
export function messagesByField(issue, requiredMessages = noMessages) {
|
|
24
45
|
const messages = Object.create(null);
|
|
25
|
-
for (const entry of
|
|
46
|
+
for (const entry of formattedIssues(issue, requiredMessages)) {
|
|
26
47
|
const head = entry.path?.[0];
|
|
27
48
|
if (head === undefined) {
|
|
28
49
|
continue;
|
|
@@ -34,8 +55,8 @@ export function messagesByField(issue) {
|
|
|
34
55
|
}
|
|
35
56
|
return messages;
|
|
36
57
|
}
|
|
37
|
-
export function messageAtPath(issue, path) {
|
|
38
|
-
for (const entry of
|
|
58
|
+
export function messageAtPath(issue, path, requiredMessages = noMessages) {
|
|
59
|
+
for (const entry of formattedIssues(issue, requiredMessages)) {
|
|
39
60
|
if (path.every((segment, index) => {
|
|
40
61
|
const found = entry.path?.[index];
|
|
41
62
|
return found !== undefined && segment === (typeof found === "object" ? found.key : found);
|
|
@@ -74,10 +95,10 @@ export function literalChoices(input) {
|
|
|
74
95
|
export function messageAt(messages, name) {
|
|
75
96
|
return Object.hasOwn(messages, name) ? messages[name] : undefined;
|
|
76
97
|
}
|
|
77
|
-
export function schemaFeedback(decoded) {
|
|
98
|
+
export function schemaFeedback(decoded, requiredMessages = noMessages) {
|
|
78
99
|
const messages = Atom.map(decoded, (result) => Option.match(AsyncResult.value(result), {
|
|
79
100
|
onNone: () => noMessages,
|
|
80
|
-
onSome: (value) => (Result.isFailure(value) ? messagesByField(value.failure) : noMessages),
|
|
101
|
+
onSome: (value) => (Result.isFailure(value) ? messagesByField(value.failure, requiredMessages) : noMessages),
|
|
81
102
|
}));
|
|
82
103
|
return {
|
|
83
104
|
at: (path) => Atom.map(decoded, (result) => {
|
|
@@ -86,7 +107,7 @@ export function schemaFeedback(decoded) {
|
|
|
86
107
|
}
|
|
87
108
|
return Option.match(AsyncResult.value(result), {
|
|
88
109
|
onNone: () => undefined,
|
|
89
|
-
onSome: (value) => (Result.isFailure(value) ? messageAtPath(value.failure, path) : undefined),
|
|
110
|
+
onSome: (value) => (Result.isFailure(value) ? messageAtPath(value.failure, path, requiredMessages) : undefined),
|
|
90
111
|
});
|
|
91
112
|
}),
|
|
92
113
|
field: Atom.family((name) => Atom.map(messages, (value) => messageAt(value, name))),
|
package/dist/messages.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAuB,MAAM,QAAQ,CAAC;AACrF,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAI/C,MAAM,CAAC,MAAM,UAAU,GAAkB,EAAE,CAAC;AAE5C,MAAM,QAAQ,GAAG,UAAU,CAAC;AAE5B,MAAM,cAAc,GAAG,2BAA2B,CAAC;AAEnD,SAAS,YAAY,CAAC,KAAgC;IACrD,MAAM,cAAc,GAAG,KAAK,CAAC,WAAW,EAAE,cAAc,CAAC;IACzD,IAAI,cAAc,KAAK,SAAS,IAAI,cAAc,CAAC,EAAE,KAAK,cAAc,EAAE,CAAC;QAC1E,OAAO,KAAK,CAAC;IACd,CAAC;IACD,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC;IACvC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,WAAW,IAAI,OAAO,CAAC,EAAE,CAAC;QAClF,OAAO,KAAK,CAAC;IACd,CAAC;IACD,OAAO,OAAO,CAAC,SAAS,KAAK,CAAC,CAAC;AAChC,CAAC;AAED,
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"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"}
|
package/dist/shape.d.ts
CHANGED
|
@@ -36,15 +36,21 @@ export interface Submitter<TName extends string> {
|
|
|
36
36
|
export type Checks<TFields extends Fields, R> = {
|
|
37
37
|
readonly [TKey in Name<TFields>]?: (value: Encoded<TFields>[TKey]) => Effect.Effect<string | undefined, never, R>;
|
|
38
38
|
};
|
|
39
|
-
export
|
|
39
|
+
export type Config<TFields extends Fields, A, E, R, TRuntimeError> = {
|
|
40
40
|
readonly arrayKeys?: ArrayKeys<TFields>;
|
|
41
41
|
readonly checks?: Checks<TFields, R>;
|
|
42
42
|
readonly debounce?: Duration.Input;
|
|
43
43
|
readonly initialValues: Encoded<TFields>;
|
|
44
44
|
readonly onSubmit: (values: Decoded<TFields>, submitter: Submitter<Name<TFields>>, submitted: Encoded<TFields>) => Effect.Effect<A, E, R | Scope.Scope | AtomRegistry | Reactivity>;
|
|
45
45
|
readonly persistedDraft?: PersistedDraftConfig | undefined;
|
|
46
|
+
readonly requiredMessages?: {
|
|
47
|
+
readonly [TKey in Name<TFields>]?: NonNullable<Encoded<TFields>[TKey]> extends string ? string : never;
|
|
48
|
+
};
|
|
49
|
+
} & ([Exclude<Services<TFields, R>, Scope.Scope | AtomRegistry | Reactivity>] extends [never] ? {
|
|
50
|
+
readonly runtime?: Atom.AtomRuntime<Services<TFields, R>, TRuntimeError>;
|
|
51
|
+
} : {
|
|
46
52
|
readonly runtime: Atom.AtomRuntime<Services<TFields, R>, TRuntimeError>;
|
|
47
|
-
}
|
|
53
|
+
});
|
|
48
54
|
export interface Form<TFields extends Fields, A, E, TRuntimeError> {
|
|
49
55
|
readonly blur: (name: Name<TFields>) => void;
|
|
50
56
|
readonly change: <TKey extends Name<TFields>>(name: TKey, value: Encoded<TFields>[TKey]) => void;
|
package/dist/shape.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shape.d.ts","sourceRoot":"","sources":["../src/shape.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAC;AACnF,OAAO,KAAK,KAAK,IAAI,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;AAE1C,MAAM,MAAM,OAAO,CAAC,OAAO,SAAS,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AAE7E,MAAM,MAAM,OAAO,CAAC,OAAO,SAAS,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAE1E,MAAM,MAAM,IAAI,CAAC,OAAO,SAAS,MAAM,IAAI,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC;AAE3E,MAAM,MAAM,IAAI,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,IAAI,SAAS,CAAC,KAAK,EAAE,GAAG,WAAW,EAAE,CAAC,CAAC;AAErF,MAAM,MAAM,SAAS,CAAC,OAAO,SAAS,MAAM,IAAI;IAC/C,QAAQ,EAAE,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,SAAS,CAAC,MAAM,QAAQ,CAAC,EAAE,GACvG,CAAC,OAAO,EAAE,QAAQ,KAAK,WAAW,GAClC,KAAK;CACR,CAAC;AAEF,MAAM,MAAM,QAAQ,CAAC,OAAO,SAAS,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;;;;AAE9F,qBAAa,YAAa,SAAQ,kBAAiC;IAClE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC;CACzB,CAAC;CAAG;;;;AAEL,qBAAa,OAAQ,SAAQ,aAA4B;IACxD,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;CACjC,CAAC;CAAG;AAEL,MAAM,WAAW,SAAS,CAAC,KAAK,SAAS,MAAM;IAC9C,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;CAClG;AAED,MAAM,MAAM,MAAM,CAAC,OAAO,SAAS,MAAM,EAAE,CAAC,IAAI;IAC/C,QAAQ,EAAE,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;CACjH,CAAC;AAEF,MAAM,
|
|
1
|
+
{"version":3,"file":"shape.d.ts","sourceRoot":"","sources":["../src/shape.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/form",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Schema-derived Effect atom forms with optional React hooks",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect"
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@effect/atom-react": "4.0.6",
|
|
47
47
|
"@effect/vitest": "4.0.6",
|
|
48
|
-
"@shivaedev/quality": "0.
|
|
49
|
-
"@shivaedev/test": "0.
|
|
50
|
-
"@shivaedev/test-story": "0.
|
|
48
|
+
"@shivaedev/quality": "0.50.0",
|
|
49
|
+
"@shivaedev/test": "0.12.0",
|
|
50
|
+
"@shivaedev/test-story": "0.30.0",
|
|
51
51
|
"@types/node": "24.19.2",
|
|
52
52
|
"@types/react": "19.3.0",
|
|
53
53
|
"effect": "4.0.6",
|
package/src/form.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Equal, Result, Schema, type SchemaIssue, SchemaParser } from "effect";
|
|
1
|
+
import { Effect, Equal, Layer, Result, Schema, type SchemaIssue, SchemaParser } from "effect";
|
|
2
2
|
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
3
3
|
import * as Atom from "effect/reactivity/Atom";
|
|
4
4
|
import type * as AtomRef from "effect/reactivity/AtomRef";
|
|
@@ -7,15 +7,36 @@ import { literalChoices, messageAt, messagesByField, schemaFeedback } from "./me
|
|
|
7
7
|
import { pathMessageAt } from "./paths.ts";
|
|
8
8
|
import { persistence } from "./persistence.ts";
|
|
9
9
|
import { fromRef, propertyRef } from "./refs.ts";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
type Config,
|
|
12
|
+
type Decoded,
|
|
13
|
+
type Encoded,
|
|
14
|
+
FieldFailure,
|
|
15
|
+
type Fields,
|
|
16
|
+
type Form,
|
|
17
|
+
Invalid,
|
|
18
|
+
type Name,
|
|
19
|
+
type Services,
|
|
20
|
+
type Submitter,
|
|
21
|
+
} from "./shape.ts";
|
|
11
22
|
import { statusOf } from "./statusOf.ts";
|
|
12
23
|
import { trackRejections } from "./trackRejections.ts";
|
|
13
24
|
|
|
14
|
-
|
|
25
|
+
const emptyRuntime = Atom.runtime(Layer.empty);
|
|
26
|
+
|
|
27
|
+
function configuredRuntime<TFields extends Fields, A, E, R, TRuntimeError>(
|
|
28
|
+
config: Config<TFields, A, E, R, TRuntimeError>,
|
|
29
|
+
): Atom.AtomRuntime<Services<TFields, R>, TRuntimeError>;
|
|
30
|
+
function configuredRuntime(config: { readonly runtime?: Atom.AtomRuntime<never, unknown> }): Atom.AtomRuntime<never, unknown> {
|
|
31
|
+
return config.runtime ?? emptyRuntime;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function form<TFields extends Fields, TValue, TError, TServices = never, TRuntimeError = never>(
|
|
15
35
|
schema: Schema.Struct<TFields>,
|
|
16
36
|
config: Config<TFields, TValue, TError, TServices, TRuntimeError>,
|
|
17
37
|
): Form<TFields, TValue, TError, TRuntimeError> {
|
|
18
|
-
const { initialValues, onSubmit
|
|
38
|
+
const { initialValues, onSubmit } = config;
|
|
39
|
+
const runtime = configuredRuntime(config);
|
|
19
40
|
const debounce = config.debounce ?? "300 millis";
|
|
20
41
|
const checks: ReadonlyMap<string, (value: Encoded<TFields>[Name<TFields>]) => Effect.Effect<string | undefined, never, TServices>> = new Map(
|
|
21
42
|
Object.entries(config.checks ?? {}),
|
|
@@ -48,7 +69,7 @@ export function form<TFields extends Fields, TValue, TError, TServices, TRuntime
|
|
|
48
69
|
),
|
|
49
70
|
);
|
|
50
71
|
|
|
51
|
-
const schemaErrors = schemaFeedback(decoded);
|
|
72
|
+
const schemaErrors = schemaFeedback(decoded, config.requiredMessages);
|
|
52
73
|
|
|
53
74
|
const checkError = Atom.family((name: Name<TFields>): Atom.Atom<string | undefined> => {
|
|
54
75
|
const check = checks.get(name);
|
|
@@ -89,7 +110,9 @@ export function form<TFields extends Fields, TValue, TError, TServices, TRuntime
|
|
|
89
110
|
status.attempt();
|
|
90
111
|
const submission = editing.submission();
|
|
91
112
|
const submitted = submission.values;
|
|
92
|
-
const value = yield* decode(submitted).pipe(
|
|
113
|
+
const value = yield* decode(submitted).pipe(
|
|
114
|
+
Effect.mapError((issue) => new Invalid({ messages: messagesByField(issue, config.requiredMessages) })),
|
|
115
|
+
);
|
|
93
116
|
return yield* onSubmit(value, submitter, submitted).pipe(
|
|
94
117
|
Effect.tapError(noted(submitted)),
|
|
95
118
|
Effect.tap(() => Effect.sync(submission.accept)),
|
package/src/messages.ts
CHANGED
|
@@ -22,17 +22,39 @@ function refusesEmpty(check: SchemaAST.Filter<unknown>): boolean {
|
|
|
22
22
|
return payload.minLength === 1;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
function formatter(required = REQUIRED) {
|
|
26
|
+
return SchemaIssue.makeFormatterStandardSchemaV1({
|
|
27
|
+
checkHook: (issue) => SchemaIssue.defaultCheckHook(issue) ?? (refusesEmpty(issue.filter) ? required : undefined),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const formatIssue = formatter();
|
|
26
32
|
|
|
27
|
-
|
|
33
|
+
function formattedIssues(issue: SchemaIssue.Issue, requiredMessages: Readonly<Partial<Record<string, string>>>) {
|
|
34
|
+
const issues = formatIssue(issue).issues;
|
|
35
|
+
const customized = new Map<string, ReturnType<typeof formatIssue>["issues"]>();
|
|
36
|
+
return issues.map((entry, index) => {
|
|
37
|
+
const head = entry.path?.[0];
|
|
38
|
+
const required = head === undefined ? undefined : messageAt(requiredMessages, segmentKey(head));
|
|
39
|
+
if (required === undefined) {
|
|
40
|
+
return entry;
|
|
41
|
+
}
|
|
42
|
+
let entries = customized.get(required);
|
|
43
|
+
if (entries === undefined) {
|
|
44
|
+
entries = formatter(required)(issue).issues;
|
|
45
|
+
customized.set(required, entries);
|
|
46
|
+
}
|
|
47
|
+
return entries[index] ?? entry;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
28
50
|
|
|
29
51
|
function segmentKey(segment: PropertyKey | StandardSchema.StandardSchemaV1.PathSegment): string {
|
|
30
52
|
return typeof segment === "object" ? String(segment.key) : String(segment);
|
|
31
53
|
}
|
|
32
54
|
|
|
33
|
-
export function messagesByField(issue: SchemaIssue.Issue): FieldMessages {
|
|
55
|
+
export function messagesByField(issue: SchemaIssue.Issue, requiredMessages: Readonly<Partial<Record<string, string>>> = noMessages): FieldMessages {
|
|
34
56
|
const messages: Record<string, string> = Object.create(null);
|
|
35
|
-
for (const entry of
|
|
57
|
+
for (const entry of formattedIssues(issue, requiredMessages)) {
|
|
36
58
|
const head = entry.path?.[0];
|
|
37
59
|
if (head === undefined) {
|
|
38
60
|
continue;
|
|
@@ -45,8 +67,12 @@ export function messagesByField(issue: SchemaIssue.Issue): FieldMessages {
|
|
|
45
67
|
return messages;
|
|
46
68
|
}
|
|
47
69
|
|
|
48
|
-
export function messageAtPath(
|
|
49
|
-
|
|
70
|
+
export function messageAtPath(
|
|
71
|
+
issue: SchemaIssue.Issue,
|
|
72
|
+
path: readonly PropertyKey[],
|
|
73
|
+
requiredMessages: Readonly<Partial<Record<string, string>>> = noMessages,
|
|
74
|
+
): string | undefined {
|
|
75
|
+
for (const entry of formattedIssues(issue, requiredMessages)) {
|
|
50
76
|
if (
|
|
51
77
|
path.every((segment, index) => {
|
|
52
78
|
const found = entry.path?.[index];
|
|
@@ -87,15 +113,18 @@ export function literalChoices(input: SchemaAST.AST): readonly SchemaAST.Literal
|
|
|
87
113
|
return choices;
|
|
88
114
|
}
|
|
89
115
|
|
|
90
|
-
export function messageAt(messages:
|
|
116
|
+
export function messageAt(messages: Readonly<Partial<Record<string, string>>>, name: string): string | undefined {
|
|
91
117
|
return Object.hasOwn(messages, name) ? messages[name] : undefined;
|
|
92
118
|
}
|
|
93
119
|
|
|
94
|
-
export function schemaFeedback<A, E>(
|
|
120
|
+
export function schemaFeedback<A, E>(
|
|
121
|
+
decoded: Atom.Atom<AsyncResult.AsyncResult<Result.Result<A, SchemaIssue.Issue>, E>>,
|
|
122
|
+
requiredMessages: Readonly<Partial<Record<string, string>>> = noMessages,
|
|
123
|
+
) {
|
|
95
124
|
const messages = Atom.map(decoded, (result) =>
|
|
96
125
|
Option.match(AsyncResult.value(result), {
|
|
97
126
|
onNone: () => noMessages,
|
|
98
|
-
onSome: (value) => (Result.isFailure(value) ? messagesByField(value.failure) : noMessages),
|
|
127
|
+
onSome: (value) => (Result.isFailure(value) ? messagesByField(value.failure, requiredMessages) : noMessages),
|
|
99
128
|
}),
|
|
100
129
|
);
|
|
101
130
|
return {
|
|
@@ -106,7 +135,7 @@ export function schemaFeedback<A, E>(decoded: Atom.Atom<AsyncResult.AsyncResult<
|
|
|
106
135
|
}
|
|
107
136
|
return Option.match(AsyncResult.value(result), {
|
|
108
137
|
onNone: () => undefined,
|
|
109
|
-
onSome: (value) => (Result.isFailure(value) ? messageAtPath(value.failure, path) : undefined),
|
|
138
|
+
onSome: (value) => (Result.isFailure(value) ? messageAtPath(value.failure, path, requiredMessages) : undefined),
|
|
110
139
|
});
|
|
111
140
|
}),
|
|
112
141
|
field: Atom.family((name: string) => Atom.map(messages, (value) => messageAt(value, name))),
|
package/src/shape.ts
CHANGED
|
@@ -42,7 +42,7 @@ export type Checks<TFields extends Fields, R> = {
|
|
|
42
42
|
readonly [TKey in Name<TFields>]?: (value: Encoded<TFields>[TKey]) => Effect.Effect<string | undefined, never, R>;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
export
|
|
45
|
+
export type Config<TFields extends Fields, A, E, R, TRuntimeError> = {
|
|
46
46
|
readonly arrayKeys?: ArrayKeys<TFields>;
|
|
47
47
|
readonly checks?: Checks<TFields, R>;
|
|
48
48
|
readonly debounce?: Duration.Input;
|
|
@@ -53,8 +53,10 @@ export interface Config<TFields extends Fields, A, E, R, TRuntimeError> {
|
|
|
53
53
|
submitted: Encoded<TFields>,
|
|
54
54
|
) => Effect.Effect<A, E, R | Scope.Scope | AtomRegistry | Reactivity>;
|
|
55
55
|
readonly persistedDraft?: PersistedDraftConfig | undefined;
|
|
56
|
-
readonly
|
|
57
|
-
}
|
|
56
|
+
readonly requiredMessages?: { readonly [TKey in Name<TFields>]?: NonNullable<Encoded<TFields>[TKey]> extends string ? string : never };
|
|
57
|
+
} & ([Exclude<Services<TFields, R>, Scope.Scope | AtomRegistry | Reactivity>] extends [never]
|
|
58
|
+
? { readonly runtime?: Atom.AtomRuntime<Services<TFields, R>, TRuntimeError> }
|
|
59
|
+
: { readonly runtime: Atom.AtomRuntime<Services<TFields, R>, TRuntimeError> });
|
|
58
60
|
|
|
59
61
|
export interface Form<TFields extends Fields, A, E, TRuntimeError> {
|
|
60
62
|
readonly blur: (name: Name<TFields>) => void;
|