@shivaedev/form 0.7.0 → 0.8.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 +23 -0
- package/README.md +137 -123
- package/dist/form.d.ts +1 -1
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +14 -17
- package/dist/form.js.map +1 -1
- package/dist/paths.d.ts +1 -3
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +0 -3
- package/dist/paths.js.map +1 -1
- package/dist/persistence.d.ts +1 -0
- package/dist/persistence.d.ts.map +1 -1
- package/dist/persistence.js +3 -3
- package/dist/persistence.js.map +1 -1
- package/dist/shape.d.ts +1 -5
- package/dist/shape.d.ts.map +1 -1
- package/dist/shape.js +0 -12
- package/dist/shape.js.map +1 -1
- package/dist/trackRejections.d.ts +2 -2
- package/dist/trackRejections.d.ts.map +1 -1
- package/dist/trackRejections.js +2 -2
- package/dist/trackRejections.js.map +1 -1
- package/package.json +9 -9
- package/src/form.ts +20 -36
- package/src/paths.ts +1 -6
- package/src/persistence.ts +4 -3
- package/src/shape.ts +1 -21
- package/src/trackRejections.ts +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0 - 2026-10-30
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Work Board reads draft timestamps and expiry through its form client’s runtime Clock.
|
|
8
|
+
|
|
9
|
+
### Breaking
|
|
10
|
+
|
|
11
|
+
- Persisted drafts require a `now: () => number` callback in core forms, `usePersistedDraft`, `useEditor` and `useCreate`. Capture `Clock.Clock` in the application’s Effect setup and pass `now: () => clock.currentTimeMillisUnsafe()` so timestamps and expiry follow that clock, including TestClock in stories.
|
|
12
|
+
- Form no longer exports the identity cast helpers `holder`, `fieldOf`, `choicesOf` or `checkOf` from `shape.ts`, or `keySelector` from `paths.ts`. Use declared reference, field, check and selector types directly; get field refs and choices through the form’s typed `field` and `choices` methods.
|
|
13
|
+
|
|
14
|
+
## 0.7.1 - 2026-10-30
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Require Effect 4.0.6 and align the Effect companion package peers and generated starter catalog to the same exact release. Install the 4.0.6 Effect stack together.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Register hosted RPC cache-control handling through Effect 4.0.6's request-scoped pre-response API so responses keep `private, no-store`.
|
|
23
|
+
- Accept native RPC's `application/x-ndjson` media type while preserving `application/ndjson` callers at the transport guard.
|
|
24
|
+
- Keep subscriptions open after their HTTP request body completes; closing the socket still cancels them.
|
|
25
|
+
|
|
3
26
|
## 0.7.0 - 2026-10-27
|
|
4
27
|
|
|
5
28
|
### Breaking
|
package/README.md
CHANGED
|
@@ -8,9 +8,9 @@ An input gives you a string; your service needs a number. Meanwhile, fresh serve
|
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
const order = form(Schema.Struct({ name: Schema.String, quantity: Schema.NumberFromString }), {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
initialValues: { name: "Paper", quantity: "300" },
|
|
12
|
+
onSubmit: Effect.succeed,
|
|
13
|
+
runtime,
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
order.change("name", "Copy paper");
|
|
@@ -45,32 +45,27 @@ import { form } from "@shivaedev/form/form.ts";
|
|
|
45
45
|
|
|
46
46
|
const runtime = Atom.runtime(Layer.empty);
|
|
47
47
|
const fields = Schema.Struct({
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
name: Schema.NonEmptyString,
|
|
49
|
+
quantity: Schema.NumberFromString,
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
const order = form(fields, {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
? submitter.fail("name", "Choose another name")
|
|
57
|
-
: Effect.succeed(values),
|
|
58
|
-
runtime,
|
|
53
|
+
initialValues: { name: "Paper", quantity: "300" },
|
|
54
|
+
onSubmit: (values, submitter) => (values.name === "reserved" ? submitter.fail("name", "Choose another name") : Effect.succeed(values)),
|
|
55
|
+
runtime,
|
|
59
56
|
});
|
|
60
57
|
|
|
61
58
|
const registry = AtomRegistry.make();
|
|
62
59
|
const release = registry.mount(order.dirty);
|
|
63
60
|
|
|
64
61
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
);
|
|
70
|
-
console.log(saved);
|
|
62
|
+
order.change("quantity", "350");
|
|
63
|
+
registry.set(order.submit, undefined);
|
|
64
|
+
const saved = await Effect.runPromise(AtomRegistry.getResult(registry, order.submit, { suspendOnWaiting: true }));
|
|
65
|
+
console.log(saved);
|
|
71
66
|
} finally {
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
release();
|
|
68
|
+
registry.dispose();
|
|
74
69
|
}
|
|
75
70
|
```
|
|
76
71
|
|
|
@@ -94,40 +89,54 @@ const runtime = Atom.runtime(Layer.empty);
|
|
|
94
89
|
const fields = Schema.Struct({ name: Schema.NonEmptyString, quantity: Schema.NumberFromString });
|
|
95
90
|
|
|
96
91
|
function OrderEditor() {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
92
|
+
const [order] = useState(() =>
|
|
93
|
+
form(fields, {
|
|
94
|
+
initialValues: { name: "Paper", quantity: "300" },
|
|
95
|
+
onSubmit: Effect.succeed,
|
|
96
|
+
runtime,
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
const name = useField(order, "name");
|
|
100
|
+
const quantity = useField(order, "quantity");
|
|
101
|
+
const submit = useSubmit(order);
|
|
102
|
+
const dirty = useDirty(order);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<form
|
|
106
|
+
onSubmit={(event) => {
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
if (!submit.submitting) submit.run();
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<label>
|
|
112
|
+
Name <input value={name.value} onBlur={name.onBlur} onChange={(event) => name.onChange(event.target.value)} />
|
|
113
|
+
</label>
|
|
114
|
+
{name.error && <p role="alert">{name.error}</p>}
|
|
115
|
+
<label>
|
|
116
|
+
Quantity <input value={quantity.value} onBlur={quantity.onBlur} onChange={(event) => quantity.onChange(event.target.value)} />
|
|
117
|
+
</label>
|
|
118
|
+
{quantity.error && <p role="alert">{quantity.error}</p>}
|
|
119
|
+
<button disabled={submit.submitting}>Save</button>
|
|
120
|
+
<button type="button" onClick={order.revert}>
|
|
121
|
+
Revert
|
|
122
|
+
</button>
|
|
123
|
+
<p role="status">{submit.submitting ? "Saving…" : dirty ? "Unsaved changes" : "Saved"}</p>
|
|
124
|
+
</form>
|
|
125
|
+
);
|
|
121
126
|
}
|
|
122
127
|
|
|
123
128
|
export function mountOrderEditor(container: HTMLElement) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
const registry = AtomRegistry.make();
|
|
130
|
+
const root = createRoot(container);
|
|
131
|
+
root.render(
|
|
132
|
+
<RegistryContext.Provider value={registry}>
|
|
133
|
+
<OrderEditor />
|
|
134
|
+
</RegistryContext.Provider>,
|
|
135
|
+
);
|
|
136
|
+
return () => {
|
|
137
|
+
root.unmount();
|
|
138
|
+
registry.dispose();
|
|
139
|
+
};
|
|
131
140
|
}
|
|
132
141
|
```
|
|
133
142
|
|
|
@@ -139,16 +148,16 @@ Render general submission failures from `submit.result` in the application's own
|
|
|
139
148
|
|
|
140
149
|
Call `receive` with encoded values from a refreshed or saved row. Encode domain values before receiving them; for a numeric string field, receive `String(row.quantity)`.
|
|
141
150
|
|
|
142
|
-
| Situation
|
|
143
|
-
|
|
|
144
|
-
| A field still equals its baseline
|
|
145
|
-
| A field was edited locally
|
|
146
|
-
| A refresh omits an untouched optional key
|
|
147
|
-
| An optional key was added locally
|
|
148
|
-
| The handler succeeds
|
|
151
|
+
| Situation | What the form does |
|
|
152
|
+
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
153
|
+
| A field still equals its baseline | Receives the new value. |
|
|
154
|
+
| A field was edited locally | Keeps that input; received values become the new baseline. |
|
|
155
|
+
| A refresh omits an untouched optional key | Removes the key. |
|
|
156
|
+
| An optional key was added locally | Keeps it when a refresh omits it; revert restores the received values. |
|
|
157
|
+
| The handler succeeds | Accepts the submitted encoded values without replacing later edits. |
|
|
149
158
|
| A refresh arrived during a successful save | Keeps that refresh as the baseline; values still equal to the accepted submission count as saved and adopt the next refresh. |
|
|
150
|
-
| The handler fails
|
|
151
|
-
| `revert()` is called
|
|
159
|
+
| The handler fails | Keeps input; an intervening refresh remains the baseline. |
|
|
160
|
+
| `revert()` is called | Restores the latest baseline, including omitted optional keys. |
|
|
152
161
|
|
|
153
162
|
The form does not replace submitted encoded input with its decoded representation. A trimmed name can be accepted as saved while the input still contains its spaces. Receive the normalized saved row when the application wants that representation on screen. `useEditor` from `react` performs that composition.
|
|
154
163
|
|
|
@@ -156,14 +165,16 @@ The form does not replace submitted encoded input with its decoded representatio
|
|
|
156
165
|
|
|
157
166
|
Opt in with `persistedDraft`. The application chooses a key for one logical editing session. Keep that key stable across server revisions so a refresh can find the same draft; choose a new key when the encoded field shape changes. Stored input includes its baseline and edited field names, so edited fields survive reload while untouched fields adopt the latest received values.
|
|
158
167
|
|
|
159
|
-
For a core form, supply the storage provider explicitly:
|
|
168
|
+
Supply `now`, a function that reads epoch milliseconds from the caller’s clock. Capture `Clock.Clock` in the application’s Effect setup and pass its `currentTimeMillisUnsafe` method through a closure. The same callback reads time on each write and expiry check, so a supplied TestClock controls draft age. For a core form, supply the storage provider explicitly:
|
|
160
169
|
|
|
161
170
|
```ts
|
|
171
|
+
const clock = yield* Clock.Clock;
|
|
172
|
+
const now = () => clock.currentTimeMillisUnsafe();
|
|
162
173
|
const order = form(fields, {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
174
|
+
initialValues: { name: "Paper", quantity: "300" },
|
|
175
|
+
onSubmit: Effect.succeed,
|
|
176
|
+
persistedDraft: { key: "order:123", now, storage: () => window.localStorage },
|
|
177
|
+
runtime,
|
|
167
178
|
});
|
|
168
179
|
```
|
|
169
180
|
|
|
@@ -173,19 +184,21 @@ For a React form, call `usePersistedDraft` before creating the form. It defaults
|
|
|
173
184
|
import { useState } from "react";
|
|
174
185
|
import { usePersistedDraft } from "@shivaedev/form/client/react.ts";
|
|
175
186
|
|
|
176
|
-
function OrderEditor() {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
187
|
+
function OrderEditor({ now }: { readonly now: () => number }) {
|
|
188
|
+
const persistedDraft = usePersistedDraft({ key: "order:123", now });
|
|
189
|
+
const [order] = useState(() =>
|
|
190
|
+
form(fields, {
|
|
191
|
+
initialValues: { name: "Paper", quantity: "300" },
|
|
192
|
+
onSubmit: Effect.succeed,
|
|
193
|
+
persistedDraft,
|
|
194
|
+
runtime,
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
return <OrderFields form={order} />;
|
|
185
198
|
}
|
|
186
199
|
```
|
|
187
200
|
|
|
188
|
-
`OrderFields` is the application's field UI. Keep this component mounted for one record; remount it when its identity changes. `useEditor` and `useCreate` from `react` accept `persistedDraft: { key }` directly and attach the same owner. A committed `SessionBoundary` session replacement or sign-out drops its tracked drafts, including screens already unmounted, and disables writes from its old forms. A suspended replacement that is canceled preserves the visible session's stored draft and future persistence. Ordinary unmounts and React effect replay preserve stored drafts. A stored draft with a different session generation is discarded when read.
|
|
201
|
+
`OrderFields` is the application's field UI. Keep this component mounted for one record; remount it when its identity changes. `useEditor` and `useCreate` from `react` accept `persistedDraft: { key, now }` directly and attach the same owner. A committed `SessionBoundary` session replacement or sign-out drops its tracked drafts, including screens already unmounted, and disables writes from its old forms. A suspended replacement that is canceled preserves the visible session's stored draft and future persistence. Ordinary unmounts and React effect replay preserve stored drafts. A stored draft with a different session generation is discarded when read.
|
|
189
202
|
|
|
190
203
|
Each stored record defaults to at most 256 KiB of UTF-8 JSON and seven days since its last write. Set `maxBytes` and `maxAgeMs` to finite positive limits to change those bounds. Oversized, expired, malformed or incompatible records are dropped when encountered. Persistence requires JSON-compatible encoded input; invalid input such as an empty required string or an unfinished numeric string still persists. Storage access, serialization and quota failures leave the form usable.
|
|
191
204
|
|
|
@@ -201,13 +214,13 @@ For `Schema.NonEmptyString`, an empty input's message is `Required`. Longer mini
|
|
|
201
214
|
|
|
202
215
|
```ts
|
|
203
216
|
const profile = form(Schema.Struct({ name: Schema.String }), {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
217
|
+
checks: {
|
|
218
|
+
name: (value) => Effect.succeed(value === "taken" ? "Name is taken" : undefined),
|
|
219
|
+
},
|
|
220
|
+
debounce: "300 millis",
|
|
221
|
+
initialValues: { name: "" },
|
|
222
|
+
onSubmit: Effect.succeed,
|
|
223
|
+
runtime,
|
|
211
224
|
});
|
|
212
225
|
```
|
|
213
226
|
|
|
@@ -220,15 +233,17 @@ For a nested value held in one top-level field, `errorAt(["answers", index, "tex
|
|
|
220
233
|
Pass a path tuple to `submitter.fail` to reject one element or a property inside it. Paths use indices in the submitted encoded snapshot. Optional `arrayKeys` selectors receive encoded elements of top-level array fields and return their stable, unique keys:
|
|
221
234
|
|
|
222
235
|
```ts
|
|
223
|
-
const answers = form(
|
|
224
|
-
|
|
225
|
-
}),
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
236
|
+
const answers = form(
|
|
237
|
+
Schema.Struct({
|
|
238
|
+
answers: Schema.Array(Schema.Struct({ id: Schema.String, text: Schema.NonEmptyString })),
|
|
239
|
+
}),
|
|
240
|
+
{
|
|
241
|
+
arrayKeys: { answers: (answer) => answer.id },
|
|
242
|
+
initialValues: { answers: [{ id: "question", text: "Yes" }] },
|
|
243
|
+
onSubmit: (_values, submitter) => submitter.fail(["answers", 0, "text"], "This answer is closed"),
|
|
244
|
+
runtime,
|
|
245
|
+
},
|
|
246
|
+
);
|
|
232
247
|
```
|
|
233
248
|
|
|
234
249
|
`FieldFailure.path` keeps the top-level field name as a string. For a tuple rejection, optional `FieldFailure.segments` contains its submitted path.
|
|
@@ -244,8 +259,8 @@ import { Schema } from "effect";
|
|
|
244
259
|
import { emptyAsNull } from "@shivaedev/form/emptyAsNull.ts";
|
|
245
260
|
|
|
246
261
|
const fields = Schema.Struct({
|
|
247
|
-
|
|
248
|
-
|
|
262
|
+
visibility: Schema.Literals(["private", "public"]),
|
|
263
|
+
quantity: emptyAsNull(Schema.NumberFromString),
|
|
249
264
|
});
|
|
250
265
|
```
|
|
251
266
|
|
|
@@ -255,42 +270,42 @@ For a form made with these fields, `order.choices("visibility")` returns the enc
|
|
|
255
270
|
|
|
256
271
|
Import the module that defines a name; there is no root entry.
|
|
257
272
|
|
|
258
|
-
| Module
|
|
259
|
-
|
|
|
260
|
-
| `@shivaedev/form/form.ts`
|
|
261
|
-
| `@shivaedev/form/shape.ts`
|
|
273
|
+
| Module | Application-facing exports |
|
|
274
|
+
| --------------------------------- | ------------------------------------------------------------------------------- |
|
|
275
|
+
| `@shivaedev/form/form.ts` | `form(schema, config)` |
|
|
276
|
+
| `@shivaedev/form/shape.ts` | `Form`, `Submitter`, `FieldFailure`, `Invalid` and the field types they use |
|
|
262
277
|
| `@shivaedev/form/client/react.ts` | `useField`, `useSubmit`, `useDirty`, `usePersistedDraft`, `DraftSessionContext` |
|
|
263
|
-
| `@shivaedev/form/draftSession.ts` | `draftSession(id)`
|
|
264
|
-
| `@shivaedev/form/emptyAsNull.ts`
|
|
278
|
+
| `@shivaedev/form/draftSession.ts` | `draftSession(id)` |
|
|
279
|
+
| `@shivaedev/form/emptyAsNull.ts` | `emptyAsNull` |
|
|
265
280
|
|
|
266
281
|
`form` takes a `Schema.Struct` and this configuration:
|
|
267
282
|
|
|
268
|
-
| Option
|
|
269
|
-
|
|
|
270
|
-
| `initialValues`
|
|
271
|
-
| `runtime`
|
|
272
|
-
| `onSubmit`
|
|
273
|
-
| `checks`
|
|
274
|
-
| `arrayKeys`
|
|
275
|
-
| `debounce`
|
|
276
|
-
| `persistedDraft` | Optional `{ key, storage, maxBytes?, maxAgeMs?, session? }`; React's `usePersistedDraft` supplies storage and session ownership
|
|
283
|
+
| Option | Type or role |
|
|
284
|
+
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
285
|
+
| `initialValues` | `Encoded<F>` |
|
|
286
|
+
| `runtime` | Atom runtime for the handler/check services and schema decoding services |
|
|
287
|
+
| `onSubmit` | `(values, submitter, submitted) => Effect<A, E, R>`; the three arguments are decoded values, field failure helper and encoded snapshot |
|
|
288
|
+
| `checks` | Optional functions keyed by field, from encoded input to an Effect of `string \| undefined` |
|
|
289
|
+
| `arrayKeys` | Optional `ArrayKeys<F>` selectors keyed by top-level array field, from encoded element to stable `PropertyKey`; retains nested feedback across reordering |
|
|
290
|
+
| `debounce` | Optional Effect `Duration.Input` for checks |
|
|
291
|
+
| `persistedDraft` | Optional `{ key, storage, maxBytes?, maxAgeMs?, session? }`; React's `usePersistedDraft` supplies storage and session ownership |
|
|
277
292
|
|
|
278
293
|
The returned form exposes:
|
|
279
294
|
|
|
280
|
-
| Member
|
|
281
|
-
|
|
|
282
|
-
| `values`
|
|
283
|
-
| `field(name)`
|
|
284
|
-
| `change(name, value)`
|
|
285
|
-
| `blur(name)`
|
|
286
|
-
| `error(name)`
|
|
295
|
+
| Member | Type or use |
|
|
296
|
+
| -------------------------- | --------------------------------------------------------------------------------------------- |
|
|
297
|
+
| `values` | `AtomRef<Encoded<F>>` for the whole input |
|
|
298
|
+
| `field(name)` | `AtomRef<Encoded<F>[K]>` for a field |
|
|
299
|
+
| `change(name, value)` | Write encoded input through the form's editing operation |
|
|
300
|
+
| `blur(name)` | Field blur operation, supplied as `useField(...).onBlur` |
|
|
301
|
+
| `error(name)` | Atom of `string \| undefined` |
|
|
287
302
|
| `errorAt([name, ...path])` | Atom of nested Schema feedback and handler rejections; visibility follows the top-level field |
|
|
288
|
-
| `choices(name)`
|
|
289
|
-
| `dirty`
|
|
290
|
-
| `receive(values)`
|
|
291
|
-
| `revert()`
|
|
292
|
-
| `submit`
|
|
293
|
-
| `submitting`
|
|
303
|
+
| `choices(name)` | Encoded choices or `undefined` |
|
|
304
|
+
| `dirty` | Atom of `boolean` |
|
|
305
|
+
| `receive(values)` | Merge received encoded values |
|
|
306
|
+
| `revert()` | Restore the baseline |
|
|
307
|
+
| `submit` | `AtomResultFn<void, A, E \| ER \| FieldFailure \| Invalid>` |
|
|
308
|
+
| `submitting` | Atom of `boolean` for submission waiting state |
|
|
294
309
|
|
|
295
310
|
`F` is the schema's fields, `A` and `E` are the handler's success and failure types, and `ER` is the atom runtime's failure type. `FieldFailure` contains the top-level string `path`, `message` and optional submitted path `segments`; `Invalid` contains `messages`, a `FieldMessages` record.
|
|
296
311
|
|
|
@@ -299,17 +314,16 @@ The lower-level `draft.ts` (`draft`), `refs.ts` (`fromRef`, `propertyRef`) and `
|
|
|
299
314
|
### Install and limits
|
|
300
315
|
|
|
301
316
|
```sh
|
|
302
|
-
pnpm add @shivaedev/form effect
|
|
317
|
+
pnpm add @shivaedev/form effect
|
|
303
318
|
```
|
|
304
319
|
|
|
305
320
|
For React bindings:
|
|
306
321
|
|
|
307
322
|
```sh
|
|
308
|
-
pnpm add @effect/atom-react
|
|
323
|
+
pnpm add @effect/atom-react react
|
|
309
324
|
```
|
|
310
325
|
|
|
311
326
|
The React mounting example also uses the application's `react-dom`. React and `@effect/atom-react` are optional peers; the core modules do not import them. The package needs Node 24 or newer.
|
|
312
|
-
|
|
313
327
|
- Field names are top-level schema keys. There is no path-addressed field-array API.
|
|
314
328
|
- Replace encoded values rather than mutating objects in place; use `change` for ordinary UI edits.
|
|
315
329
|
- Keep the form and registry for their intended session, and clean up the registry when that session ends.
|
package/dist/form.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Schema } from "effect";
|
|
2
2
|
import { type Config, type Fields, type Form } from "./shape.ts";
|
|
3
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>;
|
|
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,EAAyB,
|
|
1
|
+
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,MAAM,EAAkC,MAAM,QAAQ,CAAC;AASvF,OAAO,EAAE,KAAK,MAAM,EAA4C,KAAK,MAAM,EAAE,KAAK,IAAI,EAAsC,MAAM,YAAY,CAAC;AAI/I,wBAAgB,IAAI,CAAC,OAAO,SAAS,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EACpF,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,CA6G9C"}
|
package/dist/form.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Equal, Result, SchemaParser } from "effect";
|
|
1
|
+
import { Effect, Equal, 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,7 +6,7 @@ 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 {
|
|
9
|
+
import { FieldFailure, Invalid } from "./shape.js";
|
|
10
10
|
import { statusOf } from "./statusOf.js";
|
|
11
11
|
import { trackRejections } from "./trackRejections.js";
|
|
12
12
|
export function form(schema, config) {
|
|
@@ -17,24 +17,17 @@ export function form(schema, config) {
|
|
|
17
17
|
const editing = draft(initialValues, config.persistedDraft ? persistence(schema, config.persistedDraft) : undefined);
|
|
18
18
|
const { values } = editing;
|
|
19
19
|
const status = statusOf();
|
|
20
|
-
const noted = trackRejections(
|
|
21
|
-
const held =
|
|
20
|
+
const noted = trackRejections(values, config.arrayKeys, status);
|
|
21
|
+
const held = {};
|
|
22
22
|
function refFor(name) {
|
|
23
|
-
const known =
|
|
23
|
+
const known = Object.hasOwn(held, name) ? held[name] : undefined;
|
|
24
24
|
if (known !== undefined) {
|
|
25
25
|
return known;
|
|
26
26
|
}
|
|
27
|
-
const made = propertyRef(
|
|
28
|
-
held
|
|
27
|
+
const made = propertyRef(values, name);
|
|
28
|
+
held[name] = made;
|
|
29
29
|
return made;
|
|
30
30
|
}
|
|
31
|
-
const offered = new Map();
|
|
32
|
-
for (const [name, member] of Object.entries(schema.fields)) {
|
|
33
|
-
const choices = literalChoices(member.ast);
|
|
34
|
-
if (choices !== undefined) {
|
|
35
|
-
offered.set(name, choices);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
31
|
const valuesAtom = fromRef(values);
|
|
39
32
|
const decoded = runtime.atom((get) => decode(get(valuesAtom)).pipe(Effect.match({
|
|
40
33
|
onFailure: (issue) => Result.fail(issue),
|
|
@@ -50,7 +43,7 @@ export function form(schema, config) {
|
|
|
50
43
|
const settled = Atom.debounce(current, debounce);
|
|
51
44
|
const answered = runtime.atom((get) => {
|
|
52
45
|
const input = get(settled);
|
|
53
|
-
return Effect.map(
|
|
46
|
+
return Effect.map(check(input), (message) => ({
|
|
54
47
|
input,
|
|
55
48
|
message,
|
|
56
49
|
}));
|
|
@@ -83,14 +76,18 @@ export function form(schema, config) {
|
|
|
83
76
|
refFor(name).set(value);
|
|
84
77
|
status.touch(name);
|
|
85
78
|
},
|
|
86
|
-
choices: (name) =>
|
|
79
|
+
choices: (name) => {
|
|
80
|
+
const member = schema.fields[name];
|
|
81
|
+
const decodeChoice = SchemaParser.decodeUnknownSync(Schema.toEncoded(schema), { disableChecks: true });
|
|
82
|
+
return (member ? literalChoices(member.ast) : undefined)?.map((value) => decodeChoice({ ...initialValues, [name]: value })[name]);
|
|
83
|
+
},
|
|
87
84
|
dirty: editing.dirty,
|
|
88
85
|
error,
|
|
89
86
|
errorAt: Atom.family((path) => {
|
|
90
87
|
const message = schemaErrors.at(path);
|
|
91
88
|
return Atom.readable((get) => get(status.touched)[path[0]] === true || get(status.submitted) ? (pathMessageAt(get(status.paths), path) ?? get(message)) : undefined);
|
|
92
89
|
}),
|
|
93
|
-
field:
|
|
90
|
+
field: refFor,
|
|
94
91
|
receive: editing.receive,
|
|
95
92
|
revert: editing.revert,
|
|
96
93
|
submit,
|
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,
|
|
1
|
+
{"version":3,"file":"form.js","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"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"}
|
package/dist/paths.d.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import type { Path } from "./shape.ts";
|
|
2
|
-
type Key = (element: unknown) => PropertyKey;
|
|
2
|
+
export type Key = (element: unknown) => PropertyKey;
|
|
3
3
|
export type KeySelectors = ReadonlyMap<string, Key>;
|
|
4
4
|
export interface PathMessage {
|
|
5
5
|
readonly message: string;
|
|
6
6
|
readonly path: Path;
|
|
7
7
|
}
|
|
8
|
-
export declare function keySelector(selector: unknown): Key;
|
|
9
8
|
export declare function asPath(path: string | Path): Path;
|
|
10
9
|
export declare function retainedPath(path: Path, previous: unknown, current: unknown, keys: KeySelectors): Path | undefined;
|
|
11
10
|
export declare function pathMessageAt(messages: readonly PathMessage[], path: readonly PropertyKey[]): string | undefined;
|
|
12
|
-
export {};
|
|
13
11
|
//# sourceMappingURL=paths.d.ts.map
|
package/dist/paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,WAAW,CAAC;AACpD,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAEpD,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;CACpB;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEhD;AAqBD,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,CAmBlH;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,GAAG,SAAS,CAEhH"}
|
package/dist/paths.js
CHANGED
package/dist/paths.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"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"}
|
package/dist/persistence.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAgB,MAAM,QAAQ,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACjD,QAAQ,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,MAAM,YAAY,CAAC;CACrC;AAED,MAAM,WAAW,WAAW,CAAC,OAAO;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM;QAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;IAC9H,QAAQ,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAC;CACxF;AAED,wBAAgB,WAAW,CAAC,OAAO,SAAS,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,oBAAoB,GAAG,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAmF7I"}
|
package/dist/persistence.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Schema, SchemaParser } from "effect";
|
|
2
2
|
export function persistence(schema, config) {
|
|
3
3
|
const encoded = Schema.toEncoded(schema);
|
|
4
4
|
const StoredDraft = Schema.Struct({
|
|
@@ -49,7 +49,7 @@ export function persistence(schema, config) {
|
|
|
49
49
|
return undefined;
|
|
50
50
|
}
|
|
51
51
|
const value = bytes(raw) <= maxBytes ? JSON.parse(raw) : undefined;
|
|
52
|
-
const now =
|
|
52
|
+
const now = config.now();
|
|
53
53
|
if (valid(value)
|
|
54
54
|
&& value.session === session
|
|
55
55
|
&& Number.isFinite(maxAgeMs)
|
|
@@ -68,7 +68,7 @@ export function persistence(schema, config) {
|
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
70
70
|
attempt(() => {
|
|
71
|
-
const raw = JSON.stringify({ baseline, edited, session, updatedAt:
|
|
71
|
+
const raw = JSON.stringify({ baseline, edited, session, updatedAt: config.now(), values, version: 1 });
|
|
72
72
|
const parsed = JSON.parse(raw);
|
|
73
73
|
if (!(maxAgeMs > 0 && Number.isFinite(maxAgeMs) && Number.isFinite(maxBytes) && bytes(raw) <= maxBytes && valid(parsed))) {
|
|
74
74
|
clear();
|
package/dist/persistence.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"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"}
|
package/dist/shape.d.ts
CHANGED
|
@@ -59,10 +59,6 @@ export interface Form<TFields extends Fields, A, E, TRuntimeError> {
|
|
|
59
59
|
readonly submitting: Atom.Atom<boolean>;
|
|
60
60
|
readonly values: AtomRef.AtomRef<Encoded<TFields>>;
|
|
61
61
|
}
|
|
62
|
-
export type Holder = AtomRef.
|
|
63
|
-
export declare function holder(values: object): Holder;
|
|
64
|
-
export declare function fieldOf<TValue>(ref: AtomRef.AtomRef<unknown>): AtomRef.AtomRef<TValue>;
|
|
65
|
-
export declare function choicesOf<TValue>(choices: readonly unknown[] | undefined): readonly TValue[] | undefined;
|
|
66
|
-
export declare function checkOf<R>(check: unknown): (value: unknown) => Effect.Effect<string | undefined, never, R>;
|
|
62
|
+
export type Holder = AtomRef.ReadonlyRef<Readonly<Record<string, unknown>>>;
|
|
67
63
|
export {};
|
|
68
64
|
//# sourceMappingURL=shape.d.ts.map
|
package/dist/shape.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shape.d.ts","sourceRoot":"","sources":["../src/shape.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"shape.d.ts","sourceRoot":"","sources":["../src/shape.ts"],"names":[],"mappings":"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"}
|
package/dist/shape.js
CHANGED
|
@@ -3,16 +3,4 @@ export class FieldFailure extends Data.TaggedError("FieldFailure") {
|
|
|
3
3
|
}
|
|
4
4
|
export class Invalid extends Data.TaggedError("Invalid") {
|
|
5
5
|
}
|
|
6
|
-
export function holder(values) {
|
|
7
|
-
return values;
|
|
8
|
-
}
|
|
9
|
-
export function fieldOf(ref) {
|
|
10
|
-
return ref;
|
|
11
|
-
}
|
|
12
|
-
export function choicesOf(choices) {
|
|
13
|
-
return choices;
|
|
14
|
-
}
|
|
15
|
-
export function checkOf(check) {
|
|
16
|
-
return check;
|
|
17
|
-
}
|
|
18
6
|
//# sourceMappingURL=shape.js.map
|
package/dist/shape.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shape.js","sourceRoot":"","sources":["../src/shape.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAuD,MAAM,QAAQ,CAAC;AA0BnF,MAAM,OAAO,YAAa,SAAQ,IAAI,CAAC,WAAW,CAAC,cAAc,CAI/D;CAAG;AAEL,MAAM,OAAO,OAAQ,SAAQ,IAAI,CAAC,WAAW,CAAC,SAAS,CAErD;CAAG
|
|
1
|
+
{"version":3,"file":"shape.js","sourceRoot":"","sources":["../src/shape.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAuD,MAAM,QAAQ,CAAC;AA0BnF,MAAM,OAAO,YAAa,SAAQ,IAAI,CAAC,WAAW,CAAC,cAAc,CAI/D;CAAG;AAEL,MAAM,OAAO,OAAQ,SAAQ,IAAI,CAAC,WAAW,CAAC,SAAS,CAErD;CAAG"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
-
import { type Holder } from "./shape.ts";
|
|
2
|
+
import { type ArrayKeys, type Fields, type Holder } from "./shape.ts";
|
|
3
3
|
import type { statusOf } from "./statusOf.ts";
|
|
4
|
-
export declare function trackRejections(values: Holder, selectors:
|
|
4
|
+
export declare function trackRejections<TFields extends Fields>(values: Holder, selectors: ArrayKeys<TFields> | undefined, status: ReturnType<typeof statusOf>): (submitted: unknown) => (cause: unknown) => Effect.Effect<void>;
|
|
5
5
|
//# sourceMappingURL=trackRejections.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trackRejections.d.ts","sourceRoot":"","sources":["../src/trackRejections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"trackRejections.d.ts","sourceRoot":"","sources":["../src/trackRejections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,KAAK,SAAS,EAAgB,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AACpF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,wBAAgB,eAAe,CAAC,OAAO,SAAS,MAAM,EACrD,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,SAAS,EACzC,MAAM,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,eAQhB,OAAO,aACjB,OAAO,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAStC"}
|
package/dist/trackRejections.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
-
import { asPath,
|
|
2
|
+
import { asPath, retainedPath } from "./paths.js";
|
|
3
3
|
import { FieldFailure } from "./shape.js";
|
|
4
4
|
export function trackRejections(values, selectors, status) {
|
|
5
|
-
const keys = new Map(Object.entries(selectors ?? {})
|
|
5
|
+
const keys = new Map(Object.entries(selectors ?? {}));
|
|
6
6
|
let previous = values.value;
|
|
7
7
|
values.subscribe((next) => {
|
|
8
8
|
status.rebase(previous, next, keys);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trackRejections.js","sourceRoot":"","sources":["../src/trackRejections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"trackRejections.js","sourceRoot":"","sources":["../src/trackRejections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,MAAM,EAAY,YAAY,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAkB,YAAY,EAA4B,MAAM,YAAY,CAAC;AAGpF,MAAM,UAAU,eAAe,CAC9B,MAAc,EACd,SAAyC,EACzC,MAAmC;IAEnC,MAAM,IAAI,GAA6B,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC;IAChF,IAAI,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC;IAC5B,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE;QACzB,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QACpC,QAAQ,GAAG,IAAI,CAAC;IACjB,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,SAAkB,EAAE,EAAE,CAC7B,CAAC,KAAc,EAAuB,EAAE,CACvC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;QAChB,IAAI,KAAK,YAAY,YAAY,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,QAAQ,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC/F,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACxB,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YAClC,CAAC;QACF,CAAC;IACF,CAAC,CAAC,CAAC;AACN,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/form",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Schema-derived Effect atom forms with optional React hooks",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect"
|
|
@@ -43,21 +43,21 @@
|
|
|
43
43
|
"README.md"
|
|
44
44
|
],
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@effect/atom-react": "4.0.
|
|
47
|
-
"@effect/vitest": "4.0.
|
|
48
|
-
"@shivaedev/quality": "0.
|
|
49
|
-
"@shivaedev/test": "0.
|
|
50
|
-
"@shivaedev/test-story": "0.
|
|
46
|
+
"@effect/atom-react": "4.0.6",
|
|
47
|
+
"@effect/vitest": "4.0.6",
|
|
48
|
+
"@shivaedev/quality": "0.46.0",
|
|
49
|
+
"@shivaedev/test": "0.11.0",
|
|
50
|
+
"@shivaedev/test-story": "0.25.0",
|
|
51
51
|
"@types/node": "24.19.2",
|
|
52
52
|
"@types/react": "19.3.0",
|
|
53
|
-
"effect": "4.0.
|
|
53
|
+
"effect": "4.0.6",
|
|
54
54
|
"react": "19.3.1",
|
|
55
55
|
"typescript": "7.0.2",
|
|
56
56
|
"vitest": "5.0.5"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@effect/atom-react": "4.0.
|
|
60
|
-
"effect": "4.0.
|
|
59
|
+
"@effect/atom-react": "4.0.6",
|
|
60
|
+
"effect": "4.0.6",
|
|
61
61
|
"react": "19.3.1"
|
|
62
62
|
},
|
|
63
63
|
"peerDependenciesMeta": {
|
package/src/form.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Equal, Result,
|
|
1
|
+
import { Effect, Equal, 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,21 +7,7 @@ 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 {
|
|
11
|
-
type Config,
|
|
12
|
-
checkOf,
|
|
13
|
-
choicesOf,
|
|
14
|
-
type Decoded,
|
|
15
|
-
type Encoded,
|
|
16
|
-
FieldFailure,
|
|
17
|
-
type Fields,
|
|
18
|
-
type Form,
|
|
19
|
-
fieldOf,
|
|
20
|
-
holder,
|
|
21
|
-
Invalid,
|
|
22
|
-
type Name,
|
|
23
|
-
type Submitter,
|
|
24
|
-
} from "./shape.ts";
|
|
10
|
+
import { type Config, type Decoded, type Encoded, FieldFailure, type Fields, type Form, Invalid, type Name, type Submitter } from "./shape.ts";
|
|
25
11
|
import { statusOf } from "./statusOf.ts";
|
|
26
12
|
import { trackRejections } from "./trackRejections.ts";
|
|
27
13
|
|
|
@@ -31,32 +17,26 @@ export function form<TFields extends Fields, TValue, TError, TServices, TRuntime
|
|
|
31
17
|
): Form<TFields, TValue, TError, TRuntimeError> {
|
|
32
18
|
const { initialValues, onSubmit, runtime } = config;
|
|
33
19
|
const debounce = config.debounce ?? "300 millis";
|
|
34
|
-
const checks: ReadonlyMap<string,
|
|
20
|
+
const checks: ReadonlyMap<string, (value: Encoded<TFields>[Name<TFields>]) => Effect.Effect<string | undefined, never, TServices>> = new Map(
|
|
21
|
+
Object.entries(config.checks ?? {}),
|
|
22
|
+
);
|
|
35
23
|
const decode = SchemaParser.decodeUnknownEffect(schema, { errors: "all" });
|
|
36
24
|
|
|
37
25
|
const editing = draft(initialValues, config.persistedDraft ? persistence(schema, config.persistedDraft) : undefined);
|
|
38
26
|
const { values } = editing;
|
|
39
27
|
const status = statusOf();
|
|
40
|
-
const noted = trackRejections(
|
|
41
|
-
const held
|
|
42
|
-
function refFor(name:
|
|
43
|
-
const known =
|
|
28
|
+
const noted = trackRejections(values, config.arrayKeys, status);
|
|
29
|
+
const held: { [TKey in Name<TFields>]?: AtomRef.AtomRef<Encoded<TFields>[TKey]> } = {};
|
|
30
|
+
function refFor<TKey extends Name<TFields>>(name: TKey): AtomRef.AtomRef<Encoded<TFields>[TKey]> {
|
|
31
|
+
const known = Object.hasOwn(held, name) ? held[name] : undefined;
|
|
44
32
|
if (known !== undefined) {
|
|
45
33
|
return known;
|
|
46
34
|
}
|
|
47
|
-
const made = propertyRef(
|
|
48
|
-
held
|
|
35
|
+
const made = propertyRef(values, name);
|
|
36
|
+
held[name] = made;
|
|
49
37
|
return made;
|
|
50
38
|
}
|
|
51
39
|
|
|
52
|
-
const offered = new Map<string, readonly unknown[]>();
|
|
53
|
-
for (const [name, member] of Object.entries(schema.fields)) {
|
|
54
|
-
const choices = literalChoices(member.ast);
|
|
55
|
-
if (choices !== undefined) {
|
|
56
|
-
offered.set(name, choices);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
40
|
const valuesAtom = fromRef(values);
|
|
61
41
|
|
|
62
42
|
const decoded = runtime.atom((get) =>
|
|
@@ -70,7 +50,7 @@ export function form<TFields extends Fields, TValue, TError, TServices, TRuntime
|
|
|
70
50
|
|
|
71
51
|
const schemaErrors = schemaFeedback(decoded);
|
|
72
52
|
|
|
73
|
-
const checkError = Atom.family((name:
|
|
53
|
+
const checkError = Atom.family((name: Name<TFields>): Atom.Atom<string | undefined> => {
|
|
74
54
|
const check = checks.get(name);
|
|
75
55
|
if (check === undefined) {
|
|
76
56
|
return Atom.make((): string | undefined => undefined);
|
|
@@ -79,7 +59,7 @@ export function form<TFields extends Fields, TValue, TError, TServices, TRuntime
|
|
|
79
59
|
const settled = Atom.debounce(current, debounce);
|
|
80
60
|
const answered = runtime.atom((get) => {
|
|
81
61
|
const input = get(settled);
|
|
82
|
-
return Effect.map(
|
|
62
|
+
return Effect.map(check(input), (message) => ({
|
|
83
63
|
input,
|
|
84
64
|
message,
|
|
85
65
|
}));
|
|
@@ -91,7 +71,7 @@ export function form<TFields extends Fields, TValue, TError, TServices, TRuntime
|
|
|
91
71
|
});
|
|
92
72
|
});
|
|
93
73
|
|
|
94
|
-
const error = Atom.family((name:
|
|
74
|
+
const error = Atom.family((name: Name<TFields>) =>
|
|
95
75
|
Atom.readable((get): string | undefined => {
|
|
96
76
|
if (get(status.touched)[name] !== true && !get(status.submitted)) {
|
|
97
77
|
return undefined;
|
|
@@ -123,7 +103,11 @@ export function form<TFields extends Fields, TValue, TError, TServices, TRuntime
|
|
|
123
103
|
refFor(name).set(value);
|
|
124
104
|
status.touch(name);
|
|
125
105
|
},
|
|
126
|
-
choices: <TName extends Name<TFields>>(name: TName) =>
|
|
106
|
+
choices: <TName extends Name<TFields>>(name: TName) => {
|
|
107
|
+
const member = schema.fields[name];
|
|
108
|
+
const decodeChoice = SchemaParser.decodeUnknownSync(Schema.toEncoded(schema), { disableChecks: true });
|
|
109
|
+
return (member ? literalChoices(member.ast) : undefined)?.map((value) => decodeChoice({ ...initialValues, [name]: value })[name]);
|
|
110
|
+
},
|
|
127
111
|
dirty: editing.dirty,
|
|
128
112
|
error,
|
|
129
113
|
errorAt: Atom.family((path: readonly [Name<TFields>, ...PropertyKey[]]) => {
|
|
@@ -132,7 +116,7 @@ export function form<TFields extends Fields, TValue, TError, TServices, TRuntime
|
|
|
132
116
|
get(status.touched)[path[0]] === true || get(status.submitted) ? (pathMessageAt(get(status.paths), path) ?? get(message)) : undefined,
|
|
133
117
|
);
|
|
134
118
|
}),
|
|
135
|
-
field:
|
|
119
|
+
field: refFor,
|
|
136
120
|
receive: editing.receive,
|
|
137
121
|
revert: editing.revert,
|
|
138
122
|
submit,
|
package/src/paths.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Equal } from "effect";
|
|
2
2
|
import type { Path } from "./shape.ts";
|
|
3
3
|
|
|
4
|
-
type Key = (element: unknown) => PropertyKey;
|
|
4
|
+
export type Key = (element: unknown) => PropertyKey;
|
|
5
5
|
export type KeySelectors = ReadonlyMap<string, Key>;
|
|
6
6
|
|
|
7
7
|
export interface PathMessage {
|
|
@@ -9,11 +9,6 @@ export interface PathMessage {
|
|
|
9
9
|
readonly path: Path;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function keySelector(selector: unknown): Key;
|
|
13
|
-
export function keySelector(selector: unknown): unknown {
|
|
14
|
-
return selector;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
12
|
export function asPath(path: string | Path): Path {
|
|
18
13
|
return typeof path === "string" ? [path] : path;
|
|
19
14
|
}
|
package/src/persistence.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Schema, SchemaParser } from "effect";
|
|
2
2
|
import type { DraftSession } from "./draftSession.ts";
|
|
3
3
|
|
|
4
4
|
export interface DraftStorage {
|
|
@@ -11,6 +11,7 @@ export interface PersistedDraftConfig {
|
|
|
11
11
|
readonly key: string;
|
|
12
12
|
readonly maxAgeMs?: number;
|
|
13
13
|
readonly maxBytes?: number;
|
|
14
|
+
readonly now: () => number;
|
|
14
15
|
readonly session?: DraftSession | undefined;
|
|
15
16
|
readonly storage: () => DraftStorage;
|
|
16
17
|
}
|
|
@@ -71,7 +72,7 @@ export function persistence<TSchema extends Schema.Constraint>(schema: TSchema,
|
|
|
71
72
|
return undefined;
|
|
72
73
|
}
|
|
73
74
|
const value: unknown = bytes(raw) <= maxBytes ? JSON.parse(raw) : undefined;
|
|
74
|
-
const now =
|
|
75
|
+
const now = config.now();
|
|
75
76
|
if (
|
|
76
77
|
valid(value)
|
|
77
78
|
&& value.session === session
|
|
@@ -92,7 +93,7 @@ export function persistence<TSchema extends Schema.Constraint>(schema: TSchema,
|
|
|
92
93
|
return;
|
|
93
94
|
}
|
|
94
95
|
attempt(() => {
|
|
95
|
-
const raw = JSON.stringify({ baseline, edited, session, updatedAt:
|
|
96
|
+
const raw = JSON.stringify({ baseline, edited, session, updatedAt: config.now(), values, version: 1 });
|
|
96
97
|
const parsed: unknown = JSON.parse(raw);
|
|
97
98
|
if (!(maxAgeMs > 0 && Number.isFinite(maxAgeMs) && Number.isFinite(maxBytes) && bytes(raw) <= maxBytes && valid(parsed))) {
|
|
98
99
|
clear();
|
package/src/shape.ts
CHANGED
|
@@ -71,24 +71,4 @@ export interface Form<TFields extends Fields, A, E, TRuntimeError> {
|
|
|
71
71
|
readonly values: AtomRef.AtomRef<Encoded<TFields>>;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
export type Holder = AtomRef.
|
|
75
|
-
|
|
76
|
-
export function holder(values: object): Holder;
|
|
77
|
-
export function holder(values: object): unknown {
|
|
78
|
-
return values;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function fieldOf<TValue>(ref: AtomRef.AtomRef<unknown>): AtomRef.AtomRef<TValue>;
|
|
82
|
-
export function fieldOf(ref: AtomRef.AtomRef<unknown>): unknown {
|
|
83
|
-
return ref;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function choicesOf<TValue>(choices: readonly unknown[] | undefined): readonly TValue[] | undefined;
|
|
87
|
-
export function choicesOf(choices: readonly unknown[] | undefined): unknown {
|
|
88
|
-
return choices;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function checkOf<R>(check: unknown): (value: unknown) => Effect.Effect<string | undefined, never, R>;
|
|
92
|
-
export function checkOf(check: unknown): unknown {
|
|
93
|
-
return check;
|
|
94
|
-
}
|
|
74
|
+
export type Holder = AtomRef.ReadonlyRef<Readonly<Record<string, unknown>>>;
|
package/src/trackRejections.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
-
import { asPath,
|
|
3
|
-
import { FieldFailure, type Holder } from "./shape.ts";
|
|
2
|
+
import { asPath, type Key, retainedPath } from "./paths.ts";
|
|
3
|
+
import { type ArrayKeys, FieldFailure, type Fields, type Holder } from "./shape.ts";
|
|
4
4
|
import type { statusOf } from "./statusOf.ts";
|
|
5
5
|
|
|
6
|
-
export function trackRejections
|
|
7
|
-
|
|
6
|
+
export function trackRejections<TFields extends Fields>(
|
|
7
|
+
values: Holder,
|
|
8
|
+
selectors: ArrayKeys<TFields> | undefined,
|
|
9
|
+
status: ReturnType<typeof statusOf>,
|
|
10
|
+
) {
|
|
11
|
+
const keys: ReadonlyMap<string, Key> = new Map(Object.entries(selectors ?? {}));
|
|
8
12
|
let previous = values.value;
|
|
9
13
|
values.subscribe((next) => {
|
|
10
14
|
status.rebase(previous, next, keys);
|