@shivaedev/form 0.10.0 → 0.12.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +62 -22
  3. package/dist/adapters/browserDraftStorage.d.ts +4 -0
  4. package/dist/adapters/browserDraftStorage.d.ts.map +1 -0
  5. package/dist/adapters/browserDraftStorage.js +4 -0
  6. package/dist/adapters/browserDraftStorage.js.map +1 -0
  7. package/dist/adapters/draftPersistence.d.ts +7 -0
  8. package/dist/adapters/draftPersistence.d.ts.map +1 -0
  9. package/dist/adapters/draftPersistence.js +8 -0
  10. package/dist/adapters/draftPersistence.js.map +1 -0
  11. package/dist/adapters/persistence.d.ts +4 -0
  12. package/dist/adapters/persistence.d.ts.map +1 -0
  13. package/dist/adapters/persistence.js +84 -0
  14. package/dist/adapters/persistence.js.map +1 -0
  15. package/dist/{client → adapters}/react.d.ts +3 -6
  16. package/dist/adapters/react.d.ts.map +1 -0
  17. package/dist/{client → adapters}/react.js +1 -4
  18. package/dist/adapters/react.js.map +1 -0
  19. package/dist/checkFeedback.d.ts +6 -0
  20. package/dist/checkFeedback.d.ts.map +1 -0
  21. package/dist/checkFeedback.js +27 -0
  22. package/dist/checkFeedback.js.map +1 -0
  23. package/dist/draft.d.ts +1 -1
  24. package/dist/draft.d.ts.map +1 -1
  25. package/dist/draft.js +12 -2
  26. package/dist/draft.js.map +1 -1
  27. package/dist/fieldRejection.d.ts +22 -0
  28. package/dist/fieldRejection.d.ts.map +1 -0
  29. package/dist/fieldRejection.js +21 -0
  30. package/dist/fieldRejection.js.map +1 -0
  31. package/dist/form.d.ts.map +1 -1
  32. package/dist/form.js +20 -28
  33. package/dist/form.js.map +1 -1
  34. package/dist/messages.d.ts.map +1 -1
  35. package/dist/messages.js +9 -18
  36. package/dist/messages.js.map +1 -1
  37. package/dist/persistence.d.ts +5 -2
  38. package/dist/persistence.d.ts.map +1 -1
  39. package/dist/persistence.js +2 -82
  40. package/dist/persistence.js.map +1 -1
  41. package/dist/refs.d.ts.map +1 -1
  42. package/dist/refs.js +1 -2
  43. package/dist/refs.js.map +1 -1
  44. package/dist/shape.d.ts +8 -1
  45. package/dist/shape.d.ts.map +1 -1
  46. package/dist/shape.js.map +1 -1
  47. package/dist/statusOf.d.ts.map +1 -1
  48. package/dist/statusOf.js +1 -2
  49. package/dist/statusOf.js.map +1 -1
  50. package/dist/validation.d.ts +7 -0
  51. package/dist/validation.d.ts.map +1 -0
  52. package/dist/validation.js +11 -0
  53. package/dist/validation.js.map +1 -0
  54. package/package.json +7 -4
  55. package/src/adapters/browserDraftStorage.ts +4 -0
  56. package/src/adapters/draftPersistence.ts +8 -0
  57. package/src/adapters/persistence.ts +87 -0
  58. package/src/{client → adapters}/react.ts +5 -11
  59. package/src/checkFeedback.ts +34 -0
  60. package/src/draft.ts +12 -4
  61. package/src/fieldRejection.ts +47 -0
  62. package/src/form.ts +22 -42
  63. package/src/messages.ts +7 -16
  64. package/src/persistence.ts +3 -86
  65. package/src/refs.ts +3 -2
  66. package/src/shape.ts +9 -1
  67. package/src/statusOf.ts +1 -2
  68. package/src/validation.ts +18 -0
  69. package/dist/client/react.d.ts.map +0 -1
  70. package/dist/client/react.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0 - 2026-11-02
4
+
5
+ ### Fixed
6
+
7
+ - Form save rejection mappings pass nested paths to the submitter, so feedback appears on the rejected value. Automatic recognition uses the shared `@shivaedev/errors` decoder.
8
+
9
+ ### Breaking
10
+
11
+ - Import `FieldRejection`, `RejectField`, `FieldRejectionMapping`, `fieldRejectionOf` and `rejecting` from `@shivaedev/form/fieldRejection.ts` instead of `@shivaedev/react/fieldRejection.ts`. The React module is removed. `FieldRejection.field` now accepts a form path tuple as well as a top-level name; consumers that read it must handle both. Return `{ field: ["profile", "name"] as const, message }` from `rejectField` to attach a save rejection to a nested value.
12
+
13
+ ## 0.11.0 - 2026-11-02
14
+
15
+ ### Added
16
+
17
+ - Read whole-form Schema refinement messages with `errorAt([])` after submission and with `Invalid.formMessage` on invalid submissions.
18
+ - Form and React submission configurations accept a `savedValues` mapping to normalize a successful submission against its captured baseline while retaining later field edits.
19
+
20
+ ### Fixed
21
+
22
+ - Hide previous asynchronous Schema messages on top-level fields while decoding changed input.
23
+ - Editors preserve newer received baselines when an earlier save completes, and merge normalized saved fields without erasing later edits.
24
+ - Create forms retain optional keys removed while a submission is in flight when resetting their inputs.
25
+ - Nested field refs read and write object properties and array elements without overflowing the stack.
26
+ - Keep a field's server rejection when it loses focus without a value change. Changing the rejected input still clears the server message.
27
+
28
+ ### Breaking
29
+
30
+ - `messageAtPath(issue, [])` now selects only whole-form issues. Pass a field path such as `["name"]` to select that field's first message.
31
+
3
32
  ## 0.10.0 - 2026-10-31
4
33
 
5
34
  ### Added
package/README.md CHANGED
@@ -81,6 +81,17 @@ An empty name shows that message after blur or submission, including through `er
81
81
 
82
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.
83
83
 
84
+ ### Validate without saving
85
+
86
+ Write the `validate` atom to reveal validation for every field and whole-form refinement without calling `onSubmit`:
87
+
88
+ ```ts
89
+ registry.set(order.validate, undefined);
90
+ const snapshot = await Effect.runPromise(AtomRegistry.getResult(registry, order.validate, { suspendOnWaiting: true }));
91
+ ```
92
+
93
+ The result is a `Snapshot<F>` with `encoded` input captured before decoding and `decoded` domain values. Validation uses the form's runtime, including asynchronous and service-based decoding. Invalid input fails with `Invalid`, carrying the same field and whole-form feedback as submit. Validation leaves the input, dirty baseline and persisted draft intact; it does not save or accept changes.
94
+
84
95
  ### In the UI: bind the same form
85
96
 
86
97
  ```tsx
@@ -90,7 +101,7 @@ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
90
101
  import { useState } from "react";
91
102
  import { createRoot } from "react-dom/client";
92
103
  import { form } from "@shivaedev/form/form.ts";
93
- import { useDirty, useField, useSubmit } from "@shivaedev/form/client/react.ts";
104
+ import { useDirty, useField, useSubmit } from "@shivaedev/form/adapters/react.ts";
94
105
 
95
106
  const fields = Schema.Struct({ name: Schema.NonEmptyString, quantity: Schema.NumberFromString });
96
107
 
@@ -164,32 +175,36 @@ Call `receive` with encoded values from a refreshed or saved row. Encode domain
164
175
  | The handler fails | Keeps input; an intervening refresh remains the baseline. |
165
176
  | `revert()` is called | Restores the latest baseline, including omitted optional keys. |
166
177
 
167
- 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.
178
+ By default, 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. Set `savedValues: (saved) => encodedValues` to display normalized values from the successful handler result. Fields changed since submission keep their input; other fields adopt the normalized values. If a refresh arrived during the save, its newer baseline stays in place and the saved mapping does not replace it. `useEditor` from `react` supplies this mapping from its `values` callback.
168
179
 
169
180
  ### Persist input across reloads
170
181
 
171
182
  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.
172
183
 
173
- 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:
184
+ Acquire draft persistence during the application's Effect setup. `draftPersistence` reads `DraftStorageProvider` and `Clock.Clock` from the environment, returning the storage provider and a callback that reads that clock for every timestamp and expiry check. The browser adapter supplies `localStorage`; storage lookup stays lazy so a browser refusal remains best-effort. Supply a different provider with `Layer.succeed(DraftStorageProvider, () => storage)`.
174
185
 
175
186
  ```ts
176
- const clock = yield* Clock.Clock;
177
- const now = () => clock.currentTimeMillisUnsafe();
187
+ import { Effect } from "effect";
188
+ import { browserDraftStorage } from "@shivaedev/form/adapters/browserDraftStorage.ts";
189
+ import { draftPersistence } from "@shivaedev/form/adapters/draftPersistence.ts";
190
+
191
+ const persistence = yield* draftPersistence.pipe(Effect.provide(browserDraftStorage));
178
192
  const order = form(fields, {
179
- initialValues: { name: "Paper", quantity: "300" },
180
- onSubmit: Effect.succeed,
181
- persistedDraft: { key: "order:123", now, storage: () => window.localStorage },
193
+ initialValues: { name: "Paper", quantity: "300" },
194
+ onSubmit: Effect.succeed,
195
+ persistedDraft: { key: "order:123", ...persistence },
182
196
  });
183
197
  ```
184
198
 
185
- For a React form, call `usePersistedDraft` before creating the form. It defaults to browser `localStorage` and attaches the current `SessionBoundary` draft owner:
199
+ For a React form, pass the acquired persistence into `usePersistedDraft` before creating the form. The hook attaches the current `SessionBoundary` draft owner:
186
200
 
187
201
  ```tsx
188
202
  import { useState } from "react";
189
- import { usePersistedDraft } from "@shivaedev/form/client/react.ts";
203
+ import { usePersistedDraft } from "@shivaedev/form/adapters/react.ts";
204
+ import type { PersistedDraftConfig } from "@shivaedev/form/persistence.ts";
190
205
 
191
- function OrderEditor({ now }: { readonly now: () => number }) {
192
- const persistedDraft = usePersistedDraft({ key: "order:123", now });
206
+ function OrderEditor({ persistence }: { readonly persistence: Omit<PersistedDraftConfig, "key"> }) {
207
+ const persistedDraft = usePersistedDraft({ key: "order:123", ...persistence });
193
208
  const [order] = useState(() =>
194
209
  form(fields, {
195
210
  initialValues: { name: "Paper", quantity: "300" },
@@ -201,7 +216,7 @@ function OrderEditor({ now }: { readonly now: () => number }) {
201
216
  }
202
217
  ```
203
218
 
204
- `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.
219
+ `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, ...persistence }` 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.
205
220
 
206
221
  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.
207
222
 
@@ -230,6 +245,27 @@ This check produces advisory feedback after the configured debounce and completi
230
245
 
231
246
  For a nested value held in one top-level field, `errorAt(["answers", index, "text"])` reads Schema feedback and handler rejections for that answer box. A blur or change of the top-level `answers` field, or a submission, reveals its nested messages. The path selects the first message at or beneath that path; a sibling message does not appear there. `error("answers")` retains the top-level summary. Advisory checks remain top-level. Nested values are still edited as a whole top-level field.
232
247
 
248
+ Whole-form Schema refinements are available through `errorAt([])` after submission. This root path selects only a whole-form message; field errors stay on their field paths. In React, render it with `useAtomValue(form.errorAt([]))`. A failed submission also keeps that message in `Invalid.formMessage`. Matching the refinement clears the feedback. Schema feedback is hidden while a new asynchronous decode is pending.
249
+
250
+ ### Map save rejections to fields
251
+
252
+ `rejecting` from `@shivaedev/form/fieldRejection.ts` wraps a save with field feedback. It takes the form schema, the save function and a `rejectField` callback or `undefined`. With `undefined`, `fieldRejectionOf(schema)` uses `@shivaedev/errors` to recognize tagged string-field rejections and selects names in that schema. Other failures keep their original error.
253
+
254
+ For a service whose rejection names a nested value, return a path tuple from the callback:
255
+
256
+ ```ts
257
+ import { rejecting } from "@shivaedev/form/fieldRejection.ts";
258
+
259
+ const profile = form(Profile, {
260
+ initialValues: { profile: { email: "alice@example.com", name: "Taken" } },
261
+ onSubmit: rejecting(Profile, saveProfile, (error) =>
262
+ error.field === "name" ? { field: ["profile", "name"] as const, message: error.message } : undefined,
263
+ ),
264
+ });
265
+ ```
266
+
267
+ `Profile` is the application's struct with a nested `profile` value, and `saveProfile` is its Effect save function. The mapped message appears at `errorAt(["profile", "name"])`; the email sibling stays clear. Returning `undefined` leaves the save failure unchanged.
268
+
233
269
  ### Errors on array elements
234
270
 
235
271
  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:
@@ -271,13 +307,14 @@ For a form made with these fields, `order.choices("visibility")` returns the enc
271
307
 
272
308
  Import the module that defines a name; there is no root entry.
273
309
 
274
- | Module | Application-facing exports |
275
- | --------------------------------- | ------------------------------------------------------------------------------- |
276
- | `@shivaedev/form/form.ts` | `form(schema, config)` |
277
- | `@shivaedev/form/shape.ts` | `Form`, `Submitter`, `FieldFailure`, `Invalid` and the field types they use |
278
- | `@shivaedev/form/client/react.ts` | `useField`, `useSubmit`, `useDirty`, `usePersistedDraft`, `DraftSessionContext` |
279
- | `@shivaedev/form/draftSession.ts` | `draftSession(id)` |
280
- | `@shivaedev/form/emptyAsNull.ts` | `emptyAsNull` |
310
+ | Module | Application-facing exports |
311
+ | ----------------------------------- | ----------------------------------------------------------------------------------------- |
312
+ | `@shivaedev/form/form.ts` | `form(schema, config)` |
313
+ | `@shivaedev/form/shape.ts` | `Form`, `Submitter`, `FieldFailure`, `Invalid` and the field types they use |
314
+ | `@shivaedev/form/fieldRejection.ts` | `rejecting`, `fieldRejectionOf`, `FieldRejection`, `RejectField`, `FieldRejectionMapping` |
315
+ | `@shivaedev/form/adapters/react.ts` | `useField`, `useSubmit`, `useDirty`, `usePersistedDraft`, `DraftSessionContext` |
316
+ | `@shivaedev/form/draftSession.ts` | `draftSession(id)` |
317
+ | `@shivaedev/form/emptyAsNull.ts` | `emptyAsNull` |
281
318
 
282
319
  `form` takes a `Schema.Struct` and this configuration:
283
320
 
@@ -286,10 +323,11 @@ Import the module that defines a name; there is no root entry.
286
323
  | `initialValues` | `Encoded<F>` |
287
324
  | `runtime` | Optional when no application services are needed; otherwise an atom runtime for handler/check and schema decoding services |
288
325
  | `onSubmit` | `(values, submitter, submitted) => Effect<A, E, R>`; the three arguments are decoded values, field failure helper and encoded snapshot |
326
+ | `savedValues` | Optional `(saved: A) => Encoded<F>`; normalizes successful submissions while preserving later edits and newer received baselines |
289
327
  | `checks` | Optional functions keyed by field, from encoded input to an Effect of `string \| undefined` |
290
328
  | `arrayKeys` | Optional `ArrayKeys<F>` selectors keyed by top-level array field, from encoded element to stable `PropertyKey`; retains nested feedback across reordering |
291
329
  | `debounce` | Optional Effect `Duration.Input` for checks |
292
- | `persistedDraft` | Optional `{ key, storage, maxBytes?, maxAgeMs?, session? }`; React's `usePersistedDraft` supplies storage and session ownership |
330
+ | `persistedDraft` | Optional `{ key, now, storage, maxBytes?, maxAgeMs?, session? }`; React's `usePersistedDraft` supplies session ownership |
293
331
 
294
332
  The returned form exposes:
295
333
 
@@ -301,14 +339,16 @@ The returned form exposes:
301
339
  | `blur(name)` | Field blur operation, supplied as `useField(...).onBlur` |
302
340
  | `error(name)` | Atom of `string \| undefined` |
303
341
  | `errorAt([name, ...path])` | Atom of nested Schema feedback and handler rejections; visibility follows the top-level field |
342
+ | `errorAt([])` | Atom of whole-form Schema feedback, visible after validation or submission |
304
343
  | `choices(name)` | Encoded choices or `undefined` |
305
344
  | `dirty` | Atom of `boolean` |
306
345
  | `receive(values)` | Merge received encoded values |
307
346
  | `revert()` | Restore the baseline |
347
+ | `validate` | `AtomResultFn<void, Snapshot<F>, ER \| Invalid>`; reveal feedback and decode without saving |
308
348
  | `submit` | `AtomResultFn<void, A, E \| ER \| FieldFailure \| Invalid>` |
309
349
  | `submitting` | Atom of `boolean` for submission waiting state |
310
350
 
311
- `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.
351
+ `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, and optional `formMessage` for a whole-form refinement.
312
352
 
313
353
  The lower-level `draft.ts` (`draft`), `refs.ts` (`fromRef`, `propertyRef`) and `statusOf.ts` (`statusOf`) modules hold the machinery the form composes; the examples use the form API instead.
314
354
 
@@ -0,0 +1,4 @@
1
+ import { Layer } from "effect";
2
+ import { DraftStorageProvider } from "#persistence.ts";
3
+ export declare const browserDraftStorage: Layer.Layer<DraftStorageProvider, never, never>;
4
+ //# sourceMappingURL=browserDraftStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browserDraftStorage.d.ts","sourceRoot":"","sources":["../../src/adapters/browserDraftStorage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,eAAO,MAAM,mBAAmB,iDAAqE,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Layer } from "effect";
2
+ import { DraftStorageProvider } from "#persistence.ts";
3
+ export const browserDraftStorage = Layer.succeed(DraftStorageProvider, () => globalThis.localStorage);
4
+ //# sourceMappingURL=browserDraftStorage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browserDraftStorage.js","sourceRoot":"","sources":["../../src/adapters/browserDraftStorage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,CAAC,MAAM,mBAAmB,GAAG,KAAK,CAAC,OAAO,CAAC,oBAAoB,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { Effect } from "effect";
2
+ import { DraftStorageProvider } from "#persistence.ts";
3
+ export declare const draftPersistence: Effect.Effect<{
4
+ now: () => number;
5
+ storage: () => import("#persistence.ts").DraftStorage;
6
+ }, never, DraftStorageProvider>;
7
+ //# sourceMappingURL=draftPersistence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draftPersistence.d.ts","sourceRoot":"","sources":["../../src/adapters/draftPersistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,eAAO,MAAM,gBAAgB;;;+BAI3B,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { Clock, Effect } from "effect";
2
+ import { DraftStorageProvider } from "#persistence.ts";
3
+ export const draftPersistence = Effect.gen(function* () {
4
+ const storage = yield* DraftStorageProvider;
5
+ const clock = yield* Clock.Clock;
6
+ return { now: () => clock.currentTimeMillisUnsafe(), storage };
7
+ });
8
+ //# sourceMappingURL=draftPersistence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draftPersistence.js","sourceRoot":"","sources":["../../src/adapters/draftPersistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,oBAAoB,CAAC;IAC5C,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;IACjC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,uBAAuB,EAAE,EAAE,OAAO,EAAE,CAAC;AAChE,CAAC,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Schema } from "effect";
2
+ import type { PersistedDraftConfig, Persistence } from "#persistence.ts";
3
+ export declare function persistence<TSchema extends Schema.Constraint>(schema: TSchema, config: PersistedDraftConfig): Persistence<TSchema["Encoded"]>;
4
+ //# sourceMappingURL=persistence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../src/adapters/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAgB,MAAM,QAAQ,CAAC;AAC9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEzE,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"}
@@ -0,0 +1,84 @@
1
+ import { Schema, SchemaParser } from "effect";
2
+ export function persistence(schema, config) {
3
+ const encoded = Schema.toEncoded(schema);
4
+ const StoredDraft = Schema.Struct({
5
+ baseline: Schema.Unknown,
6
+ edited: Schema.Array(Schema.String),
7
+ session: Schema.String,
8
+ updatedAt: Schema.Number,
9
+ values: Schema.Unknown,
10
+ version: Schema.Literal(1),
11
+ });
12
+ const valid = Schema.is(StoredDraft);
13
+ const decode = SchemaParser.decodeUnknownSync(encoded, { disableChecks: true });
14
+ const maxAgeMs = config.maxAgeMs ?? 7 * 24 * 60 * 60 * 1000;
15
+ const maxBytes = config.maxBytes ?? 256 * 1024;
16
+ function bytes(value) {
17
+ return new TextEncoder().encode(value).byteLength;
18
+ }
19
+ const storage = config.storage;
20
+ const session = config.session?.id ?? "";
21
+ function active() {
22
+ return config.session?.active !== false;
23
+ }
24
+ function attempt(run, failed) {
25
+ try {
26
+ return run();
27
+ }
28
+ catch {
29
+ failed?.();
30
+ return undefined;
31
+ }
32
+ }
33
+ function clear() {
34
+ attempt(() => storage().removeItem(config.key));
35
+ }
36
+ config.session?.track(config.key, clear);
37
+ return {
38
+ clear: () => {
39
+ if (active()) {
40
+ clear();
41
+ }
42
+ },
43
+ read: () => attempt(() => {
44
+ if (!active()) {
45
+ return undefined;
46
+ }
47
+ const raw = storage().getItem(config.key);
48
+ if (raw === null) {
49
+ return undefined;
50
+ }
51
+ const value = bytes(raw) <= maxBytes ? JSON.parse(raw) : undefined;
52
+ const now = config.now();
53
+ if (valid(value)
54
+ && value.session === session
55
+ && Number.isFinite(maxAgeMs)
56
+ && Number.isFinite(maxBytes)
57
+ && maxAgeMs > 0
58
+ && maxBytes > 0
59
+ && now - value.updatedAt <= maxAgeMs
60
+ && value.updatedAt <= now) {
61
+ return { baseline: decode(value.baseline), edited: value.edited, values: decode(value.values) };
62
+ }
63
+ clear();
64
+ return undefined;
65
+ }, clear),
66
+ write: (baseline, values, edited) => {
67
+ if (!active()) {
68
+ return;
69
+ }
70
+ attempt(() => {
71
+ const raw = JSON.stringify({ baseline, edited, session, updatedAt: config.now(), values, version: 1 });
72
+ const parsed = JSON.parse(raw);
73
+ if (!(maxAgeMs > 0 && Number.isFinite(maxAgeMs) && Number.isFinite(maxBytes) && bytes(raw) <= maxBytes && valid(parsed))) {
74
+ clear();
75
+ return;
76
+ }
77
+ decode(parsed.baseline);
78
+ decode(parsed.values);
79
+ storage().setItem(config.key, raw);
80
+ }, clear);
81
+ },
82
+ };
83
+ }
84
+ //# sourceMappingURL=persistence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence.js","sourceRoot":"","sources":["../../src/adapters/persistence.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,10 @@
1
1
  import type * as AsyncResult from "effect/reactivity/AsyncResult";
2
2
  import type { DraftSession } from "#draftSession.ts";
3
- import type { DraftStorage, PersistedDraftConfig } from "#persistence.ts";
3
+ import type { PersistedDraftConfig } from "#persistence.ts";
4
4
  import type { Encoded, FieldFailure, Fields, Form, Invalid, Name } from "#shape.ts";
5
5
  export declare const DraftSessionContext: import("react").Context<DraftSession | undefined>;
6
- export interface BrowserDraftConfig extends Omit<PersistedDraftConfig, "storage"> {
7
- readonly storage?: () => DraftStorage;
8
- }
9
- export declare function usePersistedDraft(config: BrowserDraftConfig): PersistedDraftConfig;
10
- export declare function usePersistedDraft(config: BrowserDraftConfig | undefined): PersistedDraftConfig | undefined;
6
+ export declare function usePersistedDraft(config: PersistedDraftConfig): PersistedDraftConfig;
7
+ export declare function usePersistedDraft(config: PersistedDraftConfig | undefined): PersistedDraftConfig | undefined;
11
8
  export interface Field<TFields extends Fields, TKey extends Name<TFields>> {
12
9
  readonly choices: readonly Encoded<TFields>[TKey][] | undefined;
13
10
  readonly error: string | undefined;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/adapters/react.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACpF,eAAO,MAAM,mBAAmB,mDAAqD,CAAC;AACtF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,CAAC;AACtF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,oBAAoB,GAAG,SAAS,GAAG,oBAAoB,GAAG,SAAS,CAAC;AAK9G,MAAM,WAAW,KAAK,CAAC,OAAO,SAAS,MAAM,EAAE,IAAI,SAAS,IAAI,CAAC,OAAO,CAAC;IACxE,QAAQ,CAAC,OAAO,EAAE,SAAS,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,SAAS,CAAC;IAChE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;IAC3D,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;CACvC;AACD,wBAAgB,QAAQ,CAAC,OAAO,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,aAAa,EAAE,IAAI,SAAS,IAAI,CAAC,OAAO,CAAC,EAC/F,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,aAAa,CAAC,EACxC,IAAI,EAAE,IAAI,GACR,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAOtB;AACD,MAAM,WAAW,MAAM,CAAC,CAAC,EAAE,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/C,QAAQ,CAAC,GAAG,EAAE,MAAM,IAAI,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC7B;AACD,wBAAgB,SAAS,CAAC,OAAO,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,aAAa,EACpE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,aAAa,CAAC,GACtC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,aAAa,GAAG,YAAY,GAAG,OAAO,CAAC,CAKvD;AACD,wBAAgB,QAAQ,CAAC,OAAO,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,aAAa,CAAC,GAAG,OAAO,CAEvH"}
@@ -1,12 +1,9 @@
1
1
  import { useAtomRef, useAtomSet, useAtomValue } from "@effect/atom-react";
2
2
  import { createContext, useCallback, useContext, useMemo } from "react";
3
3
  export const DraftSessionContext = createContext(undefined);
4
- function browserStorage() {
5
- return globalThis.localStorage;
6
- }
7
4
  export function usePersistedDraft(config) {
8
5
  const session = useContext(DraftSessionContext);
9
- return config ? { ...config, session: session ?? config.session, storage: config.storage ?? browserStorage } : undefined;
6
+ return config ? { ...config, session: session ?? config.session } : undefined;
10
7
  }
11
8
  export function useField(form, name) {
12
9
  const value = useAtomRef(form.field(name));
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/adapters/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE1E,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAIxE,MAAM,CAAC,MAAM,mBAAmB,GAAG,aAAa,CAA2B,SAAS,CAAC,CAAC;AAGtF,MAAM,UAAU,iBAAiB,CAAC,MAAwC;IACzE,MAAM,OAAO,GAAG,UAAU,CAAC,mBAAmB,CAAC,CAAC;IAChD,OAAO,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,OAAO,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/E,CAAC;AASD,MAAM,UAAU,QAAQ,CACvB,IAAwC,EACxC,IAAU;IAEV,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,IAA4B,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IACtG,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAChE,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAChE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAC1D,CAAC;AAMD,MAAM,UAAU,SAAS,CACxB,IAAwC;IAExC,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5C,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;AACpD,CAAC;AACD,MAAM,UAAU,QAAQ,CAA8C,IAAwC;IAC7G,OAAO,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { type Duration, Effect } from "effect";
2
+ import * as Atom from "effect/reactivity/Atom";
3
+ import type * as AtomRef from "effect/reactivity/AtomRef";
4
+ import type { Encoded, Fields, Name, Services } from "./shape.ts";
5
+ export declare function checkFeedback<TFields extends Fields, R, E>(runtime: Atom.AtomRuntime<Services<TFields, R>, E>, checks: ReadonlyMap<string, (value: Encoded<TFields>[Name<TFields>]) => Effect.Effect<string | undefined, never, R>>, refFor: <TKey extends Name<TFields>>(name: TKey) => AtomRef.AtomRef<Encoded<TFields>[TKey]>, debounce: Duration.Input): (arg: Name<TFields>) => Atom.Atom<string | undefined>;
6
+ //# sourceMappingURL=checkFeedback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkFeedback.d.ts","sourceRoot":"","sources":["../src/checkFeedback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,EAAS,MAAM,QAAQ,CAAC;AAEtD,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAC/C,OAAO,KAAK,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAE1D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAElE,wBAAgB,aAAa,CAAC,OAAO,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,EACzD,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAClD,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EACpH,MAAM,EAAE,CAAC,IAAI,SAAS,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,EAC3F,QAAQ,EAAE,QAAQ,CAAC,KAAK,yDAsBxB"}
@@ -0,0 +1,27 @@
1
+ import { Effect, Equal } from "effect";
2
+ import * as AsyncResult from "effect/reactivity/AsyncResult";
3
+ import * as Atom from "effect/reactivity/Atom";
4
+ import { fromRef } from "./refs.js";
5
+ export function checkFeedback(runtime, checks, refFor, debounce) {
6
+ return Atom.family((name) => {
7
+ const check = checks.get(name);
8
+ if (check === undefined) {
9
+ return Atom.make(() => undefined);
10
+ }
11
+ const current = fromRef(refFor(name));
12
+ const settled = Atom.debounce(current, debounce);
13
+ const answered = runtime.atom((get) => {
14
+ const input = get(settled);
15
+ return Effect.map(check(input), (message) => ({
16
+ input,
17
+ message,
18
+ }));
19
+ });
20
+ return Atom.readable((get) => {
21
+ const result = get(answered);
22
+ const value = get(current);
23
+ return AsyncResult.isSuccess(result) && !result.waiting && Equal.equals(result.value.input, value) ? result.value.message : undefined;
24
+ });
25
+ });
26
+ }
27
+ //# sourceMappingURL=checkFeedback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkFeedback.js","sourceRoot":"","sources":["../src/checkFeedback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACtD,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAE/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,MAAM,UAAU,aAAa,CAC5B,OAAkD,EAClD,MAAoH,EACpH,MAA2F,EAC3F,QAAwB;IAExB,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,IAAmB,EAAiC,EAAE;QACzE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,IAAI,CAAC,GAAuB,EAAE,CAAC,SAAS,CAAC,CAAC;QACvD,CAAC;QACD,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACjD,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;YACrC,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;YAC3B,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;gBAC7C,KAAK;gBACL,OAAO;aACP,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,EAAsB,EAAE;YAChD,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC7B,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;YAC3B,OAAO,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;QACvI,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;AACJ,CAAC"}
package/dist/draft.d.ts CHANGED
@@ -7,7 +7,7 @@ export declare function draft<TValues extends Entries>(initial: TValues, persist
7
7
  receive: (incoming: TValues) => void;
8
8
  revert: () => void;
9
9
  submission: () => {
10
- accept: () => void;
10
+ accept: (incoming?: TValues) => void;
11
11
  values: TValues;
12
12
  };
13
13
  values: AtomRef.AtomRef<TValues>;
@@ -1 +1 @@
1
- {"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAC/C,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,KAAK,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAsCjD,wBAAgB,KAAK,CAAC,OAAO,SAAS,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC;IAuBjG,KAAK;IAIL,OAAO,aAAa,OAAO,KAAG,IAAI;IAOlC,MAAM,QAAM,IAAI;IAOhB,UAAU;QAGR,MAAM,QAAM,IAAI;QAKhB,MAAM;;IAGR,MAAM;EAEP"}
1
+ {"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAC/C,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,KAAK,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAsCjD,wBAAgB,KAAK,CAAC,OAAO,SAAS,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC;IAuBjG,KAAK;IAIL,OAAO,aAAa,OAAO,KAAG,IAAI;IAOlC,MAAM,QAAM,IAAI;IAOhB,UAAU;QAGR,MAAM,cAAc,OAAO,KAAG,IAAI;QAalC,MAAM;;IAGR,MAAM;EAEP"}
package/dist/draft.js CHANGED
@@ -68,8 +68,18 @@ export function draft(initial, persistence) {
68
68
  submission: () => {
69
69
  const { baseline, current: submitted } = state.value;
70
70
  return {
71
- accept: () => {
72
- state.update((held) => held.baseline === baseline ? { ...held, baseline: submitted, settled: none } : { ...held, settled: matching(held.current, submitted) });
71
+ accept: (incoming) => {
72
+ state.update((held) => {
73
+ if (held.baseline !== baseline) {
74
+ return { ...held, settled: matching(held.current, submitted) };
75
+ }
76
+ return {
77
+ ...held,
78
+ baseline: incoming ?? submitted,
79
+ current: incoming === undefined ? held.current : merged({ ...held, baseline: submitted, settled: none }, incoming),
80
+ settled: none,
81
+ };
82
+ });
73
83
  },
74
84
  values: submitted,
75
85
  };
package/dist/draft.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"draft.js","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"draft.js","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { Effect, type Schema } from "effect";
2
+ import type { FieldFailure, Fields, Name, Path, Submitter } from "#shape.ts";
3
+ export interface FieldRejection<TName extends string> {
4
+ readonly field: TName | Path<TName>;
5
+ readonly message: string;
6
+ }
7
+ export type RejectField<TFields extends Fields, TError> = (error: TError) => FieldRejection<Name<TFields>> | undefined;
8
+ type Unmatched<TFields extends Fields, TError> = TError extends {
9
+ readonly _tag: string;
10
+ readonly message: string;
11
+ } ? "field" extends keyof TError ? Exclude<TError["field"], Name<TFields> | undefined> : never : never;
12
+ export type FieldRejectionMapping<TFields extends Fields, TError> = {
13
+ readonly rejectField?: RejectField<TFields, TError>;
14
+ } & ([
15
+ Unmatched<TFields, TError>
16
+ ] extends [never] ? unknown : {
17
+ readonly rejectField: RejectField<TFields, TError>;
18
+ });
19
+ export declare function fieldRejectionOf<TFields extends Fields>(schema: Schema.Struct<TFields>): (error: unknown) => FieldRejection<Name<TFields>> | undefined;
20
+ export declare function rejecting<TFields extends Fields, TValues, TResult, TError, TRequirements>(schema: Schema.Struct<TFields>, save: (values: TValues) => Effect.Effect<TResult, TError, TRequirements>, reject: RejectField<TFields, TError> | undefined): (values: TValues, submitter: Submitter<Name<TFields>>) => Effect.Effect<TResult, TError | FieldFailure, TRequirements>;
21
+ export {};
22
+ //# sourceMappingURL=fieldRejection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fieldRejection.d.ts","sourceRoot":"","sources":["../src/fieldRejection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE7E,MAAM,WAAW,cAAc,CAAC,KAAK,SAAS,MAAM;IACnD,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,WAAW,CAAC,OAAO,SAAS,MAAM,EAAE,MAAM,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;AAEvH,KAAK,SAAS,CAAC,OAAO,SAAS,MAAM,EAAE,MAAM,IAAI,MAAM,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAChH,OAAO,SAAS,MAAM,MAAM,GAC3B,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,GACnD,KAAK,GACN,KAAK,CAAC;AAET,MAAM,MAAM,qBAAqB,CAAC,OAAO,SAAS,MAAM,EAAE,MAAM,IAAI;IAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,CAAC;IAC9H,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,SAAS,CAAC,KAAK,CAAC,GACd,OAAO,GACP;IAAE,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC,CAAC;AAE3D,wBAAgB,gBAAgB,CAAC,OAAO,SAAS,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,WACvE,OAAO,KAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CASlE;AAED,wBAAgB,SAAS,CAAC,OAAO,SAAS,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EACxF,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAC9B,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,EACxE,MAAM,EAAE,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,SAAS,YAGhC,OAAO,aAAa,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,EAAE,aAAa,CAAC,CAK3H"}
@@ -0,0 +1,21 @@
1
+ import { Effect, Option } from "effect";
2
+ import { rejectedField } from "@shivaedev/errors/rejectedField.ts";
3
+ export function fieldRejectionOf(schema) {
4
+ return (error) => {
5
+ function isName(name) {
6
+ return Object.hasOwn(schema.fields, name);
7
+ }
8
+ return Option.match(rejectedField(error), {
9
+ onNone: () => undefined,
10
+ onSome: ({ field, message }) => (isName(field) ? { field, message } : undefined),
11
+ });
12
+ };
13
+ }
14
+ export function rejecting(schema, save, reject) {
15
+ const rejection = reject ?? fieldRejectionOf(schema);
16
+ return (values, submitter) => Effect.catch(save(values), (error) => {
17
+ const found = rejection(error);
18
+ return found === undefined ? Effect.fail(error) : submitter.fail(found.field, found.message);
19
+ });
20
+ }
21
+ //# sourceMappingURL=fieldRejection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fieldRejection.js","sourceRoot":"","sources":["../src/fieldRejection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAe,MAAM,QAAQ,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAsBnE,MAAM,UAAU,gBAAgB,CAAyB,MAA8B;IACtF,OAAO,CAAC,KAAc,EAA6C,EAAE;QACpE,SAAS,MAAM,CAAC,IAAY;YAC3B,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE;YACzC,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS;YACvB,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;SAChF,CAAC,CAAC;IACJ,CAAC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CACxB,MAA8B,EAC9B,IAAwE,EACxE,MAAgD;IAEhD,MAAM,SAAS,GAAG,MAAM,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACrD,OAAO,CAAC,MAAe,EAAE,SAAmC,EAAgE,EAAE,CAC7H,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAA+C,EAAE;QACjF,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAC/B,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,MAAM,EAAkC,MAAM,QAAQ,CAAC;AAUvF,OAAO,EAAE,KAAK,MAAM,EAA4C,KAAK,MAAM,EAAE,KAAK,IAAI,EAA4C,MAAM,YAAY,CAAC;AAcrJ,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,CAqG9C"}