@shivaedev/form 0.13.1 → 0.14.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 (39) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +7 -1
  3. package/dist/adapters/browserDraftStorage.d.ts.map +1 -1
  4. package/dist/adapters/browserDraftStorage.js +27 -2
  5. package/dist/adapters/browserDraftStorage.js.map +1 -1
  6. package/dist/adapters/draftCodec.d.ts +7 -0
  7. package/dist/adapters/draftCodec.d.ts.map +1 -0
  8. package/dist/adapters/draftCodec.js +40 -0
  9. package/dist/adapters/draftCodec.js.map +1 -0
  10. package/dist/adapters/persistence.d.ts +1 -1
  11. package/dist/adapters/persistence.d.ts.map +1 -1
  12. package/dist/adapters/persistence.js +91 -61
  13. package/dist/adapters/persistence.js.map +1 -1
  14. package/dist/adapters/react.d.ts.map +1 -1
  15. package/dist/adapters/react.js +3 -1
  16. package/dist/adapters/react.js.map +1 -1
  17. package/dist/draft.d.ts +2 -2
  18. package/dist/draft.d.ts.map +1 -1
  19. package/dist/draft.js +67 -45
  20. package/dist/draft.js.map +1 -1
  21. package/dist/draftValues.d.ts +13 -0
  22. package/dist/draftValues.d.ts.map +1 -0
  23. package/dist/draftValues.js +47 -0
  24. package/dist/draftValues.js.map +1 -0
  25. package/dist/form.d.ts.map +1 -1
  26. package/dist/form.js +17 -9
  27. package/dist/form.js.map +1 -1
  28. package/dist/persistence.d.ts +15 -11
  29. package/dist/persistence.d.ts.map +1 -1
  30. package/dist/persistence.js.map +1 -1
  31. package/package.json +1 -1
  32. package/src/adapters/browserDraftStorage.ts +30 -2
  33. package/src/adapters/draftCodec.ts +43 -0
  34. package/src/adapters/persistence.ts +97 -63
  35. package/src/adapters/react.ts +3 -1
  36. package/src/draft.ts +70 -62
  37. package/src/draftValues.ts +65 -0
  38. package/src/form.ts +12 -6
  39. package/src/persistence.ts +17 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.14.0 - 2026-11-03
4
+
5
+ ### Added
6
+
7
+ - Persist drafts through asynchronous native Effect storage operations, with optional observation across editors. Observed changes preserve local edits and adopt untouched fields.
8
+
9
+ ### Fixed
10
+
11
+ - Clear only the submitted draft revision after a successful save, keeping newer stored input from another editor. Serialize asynchronous writes and clears, and condition metadata writes on the owned revision.
12
+
13
+ ### Breaking
14
+
15
+ - Custom `DraftStorage.removeItem` implementations must accept `(key, expected)` and atomically remove only when the stored serialized value equals `expected`. Custom `setItem(key, value, expected?)` implementations must also compare atomically when `expected` is supplied, so rebasing accepted fields cannot overwrite newer stored input. Replace unconditional mutations with conditional mutations at the storage owner; writes without `expected` still store new local input. The browser adapter handles this contract; providers can keep synchronous reads and writes or return native Effects.
16
+ - Lower-level `Persistence` integrations use `observe` and `revision` in place of `read`; mount the draft's atoms to own observation. Core form consumers mount `form.dirty` before reading restored input; asynchronous restoration arrives through the existing field refs. Form's React hooks attach this lifetime automatically.
17
+
3
18
  ## 0.13.1 - 2026-11-03
4
19
 
5
20
  ### Changed
package/README.md CHANGED
@@ -220,7 +220,11 @@ function OrderEditor({ persistence }: { readonly persistence: Omit<PersistedDraf
220
220
 
221
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.
222
222
 
223
- Edits and received values update the stored draft. Revert removes it. A successful submit clears accepted input; edits made after the submitted snapshot remain persisted. A failed submit keeps the draft.
223
+ Edits and received values update the stored draft. Revert conditionally removes the form's owned revision. A successful submit clears only the revision associated with its submitted input, including refreshes of its untouched fields; newer edits made locally or in another editor remain persisted. A failed submit keeps the draft.
224
+
225
+ A custom `DraftStorage` implements `getItem(key)`, `setItem(key, value, expected?)` and `removeItem(key, expected)`. Each operation returns its synchronous result or a native `Effect` with no remaining services; acquire application services when constructing the provider. `expected` is the exact serialized value previously read or written, containing an opaque revision. The storage owner must compare and mutate atomically: remove the key, or replace it when `setItem` supplies `expected`, only if its current value equals `expected`. A `setItem` without `expected` stores a new local edit; a conditional write rebases accepted or refreshed fields without replacing another editor's newer draft. For an Electron bridge, perform these comparisons in the process that owns the shared store. Writes and clears from one form run in order even when storage is asynchronous. Storage failures remain best-effort and do not fail the form's save.
226
+
227
+ Optional `subscribe(key, listener)` reports the current serialized value, or `null` when cleared, and returns an unsubscribe function. Report changes in storage order. Observation starts while the form's atoms are mounted and ends with their lifetime. Form's React hooks mount that lifetime automatically; direct atom consumers mount `form.dirty` or its validation/submission atoms. An untouched editor follows observed changes. Locally edited fields keep their current input while untouched fields adopt the observed draft. Receiving a storage event does not echo a write. The browser adapter observes other windows through storage events and serializes mutations with Web Locks where available; without Web Locks its compare and remove run synchronously.
224
228
 
225
229
  Outside React, pass a `session` from `draftSession(id)` in `@shivaedev/form/draftSession.ts` and call its `drop()` when the application replaces that session. Core forms without a session owner use only the application's key. `DraftSessionContext` supplies the owner to React integrations.
226
230
 
@@ -352,6 +356,8 @@ The returned form exposes:
352
356
 
353
357
  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.
354
358
 
359
+ For a custom lower-level `Persistence`, `observe(receive)` calls `receive` with decoded stored input (or `undefined` after removal) and returns a cleanup function. The receiver returns `true` only when it adopted the complete incoming input; `false` preserves the local revision when local fields remain. `revision()` captures a revision reader for a submission; `write(..., sameInput)` can keep that captured reader current when only untouched server fields refresh.
360
+
355
361
  ### Install and limits
356
362
 
357
363
  ```sh
@@ -1 +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"}
1
+ {"version":3,"file":"browserDraftStorage.d.ts","sourceRoot":"","sources":["../../src/adapters/browserDraftStorage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAOvD,eAAO,MAAM,mBAAmB,iDAuB7B,CAAC"}
@@ -1,4 +1,29 @@
1
- import { Layer } from "effect";
1
+ import { Effect, Layer } from "effect";
2
2
  import { DraftStorageProvider } from "#persistence.ts";
3
- export const browserDraftStorage = Layer.succeed(DraftStorageProvider, () => globalThis.localStorage);
3
+ function mutate(key, run) {
4
+ const locks = globalThis.navigator?.locks;
5
+ return locks ? Effect.promise(() => locks.request(`@shivaedev/form/${key}`, run)) : run();
6
+ }
7
+ export const browserDraftStorage = Layer.succeed(DraftStorageProvider, () => ({
8
+ getItem: (key) => globalThis.localStorage.getItem(key),
9
+ removeItem: (key, expected) => mutate(key, () => {
10
+ if (globalThis.localStorage.getItem(key) === expected) {
11
+ globalThis.localStorage.removeItem(key);
12
+ }
13
+ }),
14
+ setItem: (key, value, expected) => mutate(key, () => {
15
+ if (expected === undefined || globalThis.localStorage.getItem(key) === expected) {
16
+ globalThis.localStorage.setItem(key, value);
17
+ }
18
+ }),
19
+ subscribe: (key, listener) => {
20
+ function changed(event) {
21
+ if (event.storageArea === globalThis.localStorage && (event.key === key || event.key === null)) {
22
+ listener(event.newValue);
23
+ }
24
+ }
25
+ globalThis.addEventListener("storage", changed);
26
+ return () => globalThis.removeEventListener("storage", changed);
27
+ },
28
+ }));
4
29
  //# sourceMappingURL=browserDraftStorage.js.map
@@ -1 +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"}
1
+ {"version":3,"file":"browserDraftStorage.js","sourceRoot":"","sources":["../../src/adapters/browserDraftStorage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,SAAS,MAAM,CAAC,GAAW,EAAE,GAAe;IAC3C,MAAM,KAAK,GAAG,UAAU,CAAC,SAAS,EAAE,KAAK,CAAC;IAC1C,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,mBAAmB,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;AAC3F,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,KAAK,CAAC,OAAO,CAAC,oBAAoB,EAAE,GAAG,EAAE,CAAC,CAAC;IAC7E,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC;IACtD,UAAU,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE,CAC7B,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE;QAChB,IAAI,UAAU,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;YACvD,UAAU,CAAC,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACzC,CAAC;IACF,CAAC,CAAC;IACH,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CACjC,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE;QAChB,IAAI,QAAQ,KAAK,SAAS,IAAI,UAAU,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;YACjF,UAAU,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC7C,CAAC;IACF,CAAC,CAAC;IACH,SAAS,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE;QAC5B,SAAS,OAAO,CAAC,KAAmB;YACnC,IAAI,KAAK,CAAC,WAAW,KAAK,UAAU,CAAC,YAAY,IAAI,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,CAAC,EAAE,CAAC;gBAChG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC1B,CAAC;QACF,CAAC;QACD,UAAU,CAAC,gBAAgB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAChD,OAAO,GAAG,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACjE,CAAC;CACD,CAAC,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { Schema } from "effect";
2
+ import type { PersistedDraftConfig, StoredValues } from "#persistence.ts";
3
+ export declare function draftCodec<TSchema extends Schema.Constraint>(schema: TSchema, config: PersistedDraftConfig): {
4
+ read: (raw: string) => StoredValues<TSchema["Encoded"]>;
5
+ write: (baseline: TSchema["Encoded"], values: TSchema["Encoded"], edited: readonly string[]) => string;
6
+ };
7
+ //# sourceMappingURL=draftCodec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draftCodec.d.ts","sourceRoot":"","sources":["../../src/adapters/draftCodec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAgB,MAAM,QAAQ,CAAC;AAC9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE1E,wBAAgB,UAAU,CAAC,OAAO,SAAS,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,oBAAoB;gBAcvF,MAAM,KAAG,YAAY,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;sBAmBzC,OAAO,CAAC,SAAS,CAAC,UAAU,OAAO,CAAC,SAAS,CAAC,UAAU,SAAS,MAAM,EAAE;EAM5F"}
@@ -0,0 +1,40 @@
1
+ import { Schema, SchemaParser } from "effect";
2
+ export function draftCodec(schema, config) {
3
+ const StoredDraft = Schema.Struct({
4
+ baseline: Schema.Unknown,
5
+ edited: Schema.Array(Schema.String),
6
+ session: Schema.String,
7
+ updatedAt: Schema.Number,
8
+ values: Schema.Unknown,
9
+ version: Schema.Literal(1),
10
+ });
11
+ const valid = Schema.is(StoredDraft);
12
+ const decode = SchemaParser.decodeUnknownSync(Schema.toEncoded(schema), { disableChecks: true });
13
+ const maxAgeMs = config.maxAgeMs ?? 7 * 24 * 60 * 60 * 1000;
14
+ const maxBytes = config.maxBytes ?? 256 * 1024;
15
+ const session = config.session?.id ?? "";
16
+ function read(raw) {
17
+ const value = new TextEncoder().encode(raw).byteLength <= maxBytes ? JSON.parse(raw) : undefined;
18
+ const now = config.now();
19
+ if (!valid(value)
20
+ || value.session !== session
21
+ || !Number.isFinite(maxAgeMs)
22
+ || !Number.isFinite(maxBytes)
23
+ || maxAgeMs <= 0
24
+ || maxBytes <= 0
25
+ || now - value.updatedAt > maxAgeMs
26
+ || value.updatedAt > now) {
27
+ throw new Error("Invalid stored draft");
28
+ }
29
+ return { baseline: decode(value.baseline), edited: value.edited, values: decode(value.values) };
30
+ }
31
+ return {
32
+ read,
33
+ write: (baseline, values, edited) => {
34
+ const raw = JSON.stringify({ baseline, edited, revision: crypto.randomUUID(), session, updatedAt: config.now(), values, version: 1 });
35
+ read(raw);
36
+ return raw;
37
+ },
38
+ };
39
+ }
40
+ //# sourceMappingURL=draftCodec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draftCodec.js","sourceRoot":"","sources":["../../src/adapters/draftCodec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAG9C,MAAM,UAAU,UAAU,CAAoC,MAAe,EAAE,MAA4B;IAC1G,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;QACjC,QAAQ,EAAE,MAAM,CAAC,OAAO;QACxB,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;QACnC,OAAO,EAAE,MAAM,CAAC,MAAM;QACtB,SAAS,EAAE,MAAM,CAAC,MAAM;QACxB,MAAM,EAAE,MAAM,CAAC,OAAO;QACtB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;KAC1B,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,YAAY,CAAC,iBAAiB,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACjG,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5D,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,GAAG,GAAG,IAAI,CAAC;IAC/C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC;IACzC,SAAS,IAAI,CAAC,GAAW;QACxB,MAAM,KAAK,GAAY,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,UAAU,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC1G,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC;QACzB,IACC,CAAC,KAAK,CAAC,KAAK,CAAC;eACV,KAAK,CAAC,OAAO,KAAK,OAAO;eACzB,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;eAC1B,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;eAC1B,QAAQ,IAAI,CAAC;eACb,QAAQ,IAAI,CAAC;eACb,GAAG,GAAG,KAAK,CAAC,SAAS,GAAG,QAAQ;eAChC,KAAK,CAAC,SAAS,GAAG,GAAG,EACvB,CAAC;YACF,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACzC,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;IACjG,CAAC;IACD,OAAO;QACN,IAAI;QACJ,KAAK,EAAE,CAAC,QAA4B,EAAE,MAA0B,EAAE,MAAyB,EAAE,EAAE;YAC9F,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;YACtI,IAAI,CAAC,GAAG,CAAC,CAAC;YACV,OAAO,GAAG,CAAC;QACZ,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -1,4 +1,4 @@
1
- import { Schema } from "effect";
1
+ import { type Schema } from "effect";
2
2
  import type { PersistedDraftConfig, Persistence } from "#persistence.ts";
3
3
  export declare function persistence<TSchema extends Schema.Constraint>(schema: TSchema, config: PersistedDraftConfig): Persistence<TSchema["Encoded"]>;
4
4
  //# sourceMappingURL=persistence.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../src/adapters/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,EAAa,MAAM,QAAQ,CAAC;AAC9D,OAAO,KAAK,EAAkB,oBAAoB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAYzF,wBAAgB,WAAW,CAAC,OAAO,SAAS,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,oBAAoB,GAAG,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CA2G7I"}
@@ -1,83 +1,113 @@
1
- import { Schema, SchemaParser } from "effect";
1
+ import { Data, Effect, Semaphore } from "effect";
2
+ import { draftCodec } from "./draftCodec.js";
3
+ class DraftStorageUnavailable extends Data.TaggedError("DraftStorageUnavailable") {
4
+ }
5
+ function operation(run) {
6
+ return Effect.suspend(() => {
7
+ const value = run();
8
+ return Effect.isEffect(value) ? value : Effect.succeed(value);
9
+ }).pipe(Effect.catchCause((cause) => Effect.fail(new DraftStorageUnavailable({ cause }))));
10
+ }
2
11
  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 ?? "";
12
+ const codec = draftCodec(schema, config);
13
+ const serial = Semaphore.makeUnsafe(1);
14
+ let revision = { value: undefined };
21
15
  function active() {
22
16
  return config.session?.active !== false;
23
17
  }
24
- function attempt(run, failed) {
18
+ function run(effect) {
19
+ return Effect.runCallback(serial.withPermit(effect).pipe(Effect.catchTag("DraftStorageUnavailable", () => Effect.void)));
20
+ }
21
+ function clear(expected = revision.value) {
22
+ if (expected !== undefined) {
23
+ run(operation(() => config.storage().removeItem(config.key, expected)));
24
+ }
25
+ }
26
+ function read(raw) {
25
27
  try {
26
- return run();
28
+ return { values: raw === null ? undefined : codec.read(raw) };
27
29
  }
28
30
  catch {
29
- failed?.();
31
+ if (raw !== null) {
32
+ clear(raw);
33
+ }
30
34
  return undefined;
31
35
  }
32
36
  }
33
- function clear() {
34
- attempt(() => storage().removeItem(config.key));
35
- }
36
- config.session?.track(config.key, clear);
37
+ config.session?.track(config.key, () => run(Effect.gen(function* () {
38
+ const storage = yield* operation(config.storage);
39
+ const raw = yield* operation(() => storage.getItem(config.key));
40
+ if (raw !== null) {
41
+ yield* operation(() => codec.read(raw));
42
+ yield* operation(() => storage.removeItem(config.key, raw));
43
+ }
44
+ })));
37
45
  return {
38
- clear: () => {
46
+ clear: (expected = revision.value) => {
39
47
  if (active()) {
40
- clear();
48
+ clear(expected);
41
49
  }
42
50
  },
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) };
51
+ observe: (receive) => {
52
+ let stopped = false;
53
+ let changes = 0;
54
+ function changed(raw, loaded = false) {
55
+ if (stopped || !active()) {
56
+ return;
57
+ }
58
+ if (!loaded && revision.value === (raw ?? undefined)) {
59
+ return;
60
+ }
61
+ const parsed = read(raw);
62
+ if (parsed && receive(parsed.values)) {
63
+ revision = { value: raw ?? undefined };
64
+ }
62
65
  }
63
- clear();
64
- return undefined;
65
- }, clear),
66
- write: (baseline, values, edited) => {
66
+ let unsubscribe;
67
+ const stop = Effect.runCallback(Effect.gen(function* () {
68
+ if (!active()) {
69
+ return;
70
+ }
71
+ const storage = yield* operation(config.storage);
72
+ unsubscribe = yield* operation(() => storage.subscribe?.(config.key, (incoming) => {
73
+ changes += 1;
74
+ changed(incoming);
75
+ }));
76
+ const before = changes;
77
+ const captured = revision;
78
+ const raw = yield* operation(() => storage.getItem(config.key));
79
+ if (before === changes && captured === revision) {
80
+ changed(raw, true);
81
+ }
82
+ }).pipe(Effect.catchTag("DraftStorageUnavailable", () => Effect.void)));
83
+ return () => {
84
+ stopped = true;
85
+ unsubscribe?.();
86
+ stop();
87
+ };
88
+ },
89
+ revision: () => {
90
+ const captured = revision;
91
+ return () => captured.value;
92
+ },
93
+ write: (baseline, values, edited, sameInput) => {
67
94
  if (!active()) {
68
95
  return;
69
96
  }
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;
97
+ try {
98
+ const expected = sameInput ? revision.value : undefined;
99
+ const raw = codec.write(baseline, values, edited);
100
+ if (sameInput) {
101
+ revision.value = raw;
102
+ }
103
+ else {
104
+ revision = { value: raw };
76
105
  }
77
- decode(parsed.baseline);
78
- decode(parsed.values);
79
- storage().setItem(config.key, raw);
80
- }, clear);
106
+ run(operation(() => (active() ? config.storage().setItem(config.key, raw, expected) : undefined)));
107
+ }
108
+ catch {
109
+ clear();
110
+ }
81
111
  },
82
112
  };
83
113
  }
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.js","sourceRoot":"","sources":["../../src/adapters/persistence.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"persistence.js","sourceRoot":"","sources":["../../src/adapters/persistence.ts"],"names":[],"mappings":"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"}
@@ -1 +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
+ {"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,CAQtB;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,CAMvD;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,4 +1,4 @@
1
- import { useAtomRef, useAtomSet, useAtomValue } from "@effect/atom-react";
1
+ import { useAtomMount, useAtomRef, useAtomSet, useAtomValue } from "@effect/atom-react";
2
2
  import { createContext, useCallback, useContext, useMemo } from "react";
3
3
  export const DraftSessionContext = createContext(undefined);
4
4
  export function usePersistedDraft(config) {
@@ -6,6 +6,7 @@ export function usePersistedDraft(config) {
6
6
  return config ? { ...config, session: session ?? config.session } : undefined;
7
7
  }
8
8
  export function useField(form, name) {
9
+ useAtomMount(form.dirty);
9
10
  const value = useAtomRef(form.field(name));
10
11
  const error = useAtomValue(form.error(name));
11
12
  const onChange = useCallback((next) => form.change(name, next), [form, name]);
@@ -14,6 +15,7 @@ export function useField(form, name) {
14
15
  return { choices, error, name, onBlur, onChange, value };
15
16
  }
16
17
  export function useSubmit(form) {
18
+ useAtomMount(form.dirty);
17
19
  const result = useAtomValue(form.submit);
18
20
  const set = useAtomSet(form.submit);
19
21
  const run = useCallback(() => set(), [set]);
@@ -1 +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"}
1
+ {"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/adapters/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAExF,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,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzB,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,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzB,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"}
package/dist/draft.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import * as Atom from "effect/reactivity/Atom";
2
2
  import * as AtomRef from "effect/reactivity/AtomRef";
3
+ import { type Entries } from "./draftValues.ts";
3
4
  import type { Persistence } from "./persistence.ts";
4
- type Entries = Readonly<Record<string, unknown>>;
5
5
  export declare function draft<TValues extends Entries>(initial: TValues, persistence?: Persistence<TValues>): {
6
6
  dirty: Atom.Atom<boolean>;
7
+ mounted: Atom.Atom<void>;
7
8
  receive: (incoming: TValues) => void;
8
9
  revert: () => void;
9
10
  submission: () => {
@@ -12,5 +13,4 @@ export declare function draft<TValues extends Entries>(initial: TValues, persist
12
13
  };
13
14
  values: AtomRef.AtomRef<TValues>;
14
15
  };
15
- export {};
16
16
  //# sourceMappingURL=draft.d.ts.map
@@ -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,cAAc,OAAO,KAAG,IAAI;QAalC,MAAM;;IAGR,MAAM;EAEP"}
1
+ {"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAC/C,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAgD,KAAK,OAAO,EAAoC,MAAM,kBAAkB,CAAC;AAChI,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,kBAAkB,CAAC;AAElE,wBAAgB,KAAK,CAAC,OAAO,SAAS,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC;;;wBAsE7E,OAAO,KAAG,IAAI;kBAUtB,IAAI;;QAWd,MAAM,cAAc,OAAO,KAAG,IAAI;QAYlC,MAAM;;;EAKT"}
package/dist/draft.js CHANGED
@@ -1,45 +1,61 @@
1
- import { Equal } from "effect";
2
1
  import * as Atom from "effect/reactivity/Atom";
3
2
  import * as AtomRef from "effect/reactivity/AtomRef";
4
- const none = {};
5
- function untouched({ baseline, current, settled }, name) {
6
- return Equal.equals(current[name], baseline[name]) || (Object.hasOwn(settled, name) && Equal.equals(current[name], settled[name]));
7
- }
8
- function changed(held) {
9
- return Object.keys({ ...held.baseline, ...held.current }).some((name) => !untouched(held, name));
10
- }
11
- function merged(held, incoming) {
12
- const result = {};
13
- for (const name of Object.keys({ ...held.current, ...incoming })) {
14
- const source = untouched(held, name) ? incoming : held.current;
15
- if (Object.hasOwn(source, name)) {
16
- result[name] = source[name];
17
- }
18
- }
19
- return result;
20
- }
21
- function matching(current, submitted) {
22
- return Object.fromEntries(Object.keys({ ...current, ...submitted })
23
- .filter((name) => Equal.equals(current[name], submitted[name]))
24
- .map((name) => [name, submitted[name]]));
25
- }
3
+ import { accepted, changed, changedFields, merged, mergeObserved, untouched } from "./draftValues.js";
26
4
  export function draft(initial, persistence) {
27
- const restored = persistence?.read();
28
- const loaded = {
29
- baseline: restored?.baseline ?? initial,
30
- current: restored?.values ?? initial,
31
- settled: restored ? Object.fromEntries(Object.entries(restored.values).filter(([name]) => !restored.edited.includes(name))) : none,
32
- };
33
- const state = AtomRef.make({
34
- baseline: initial,
35
- current: restored ? merged(loaded, initial) : initial,
36
- settled: none,
5
+ const none = {};
6
+ const state = AtomRef.make({ baseline: initial, current: initial, settled: none });
7
+ const local = new Set();
8
+ let previous = initial;
9
+ let receiving = false;
10
+ let refreshing = false;
11
+ function withoutWriting(run) {
12
+ receiving = true;
13
+ run();
14
+ receiving = false;
15
+ }
16
+ function restore(restored) {
17
+ const held = state.value;
18
+ const incoming = restored
19
+ ? merged({
20
+ baseline: restored.baseline,
21
+ current: restored.values,
22
+ settled: Object.fromEntries(Object.entries(restored.values).filter(([name]) => !restored.edited.includes(name))),
23
+ }, held.baseline)
24
+ : held.baseline;
25
+ withoutWriting(() => state.update((value) => ({ ...value, current: mergeObserved(value.current, incoming, local) })));
26
+ return local.size === 0;
27
+ }
28
+ const mounted = Atom.readable((get) => {
29
+ if (persistence) {
30
+ get.addFinalizer(persistence.observe(restore));
31
+ }
37
32
  });
33
+ function releaseCleanFields() {
34
+ for (const name of local) {
35
+ if (untouched(state.value, name)) {
36
+ local.delete(name);
37
+ }
38
+ }
39
+ }
40
+ function store(value, sameInput = refreshing) {
41
+ const edited = Object.keys({ ...value.baseline, ...value.current }).filter((name) => !untouched(value, name));
42
+ persistence?.write({ ...value.baseline, ...value.settled }, value.current, edited, sameInput);
43
+ }
38
44
  if (persistence) {
39
45
  state.subscribe((value) => {
46
+ const before = previous;
47
+ previous = value.current;
48
+ if (receiving) {
49
+ return;
50
+ }
51
+ if (!refreshing) {
52
+ for (const name of changedFields(before, value.current)) {
53
+ local.add(name);
54
+ }
55
+ }
56
+ releaseCleanFields();
40
57
  if (changed(value)) {
41
- const edited = Object.keys({ ...value.baseline, ...value.current }).filter((name) => !untouched(value, name));
42
- persistence.write({ ...value.baseline, ...value.settled }, value.current, edited);
58
+ store(value);
43
59
  }
44
60
  else {
45
61
  persistence.clear();
@@ -48,15 +64,20 @@ export function draft(initial, persistence) {
48
64
  }
49
65
  return {
50
66
  dirty: Atom.readable((get) => {
67
+ get(mounted);
51
68
  get.addFinalizer(state.subscribe((value) => get.setSelf(changed(value))));
52
69
  return changed(state.value);
53
70
  }),
71
+ mounted,
54
72
  receive: (incoming) => {
73
+ refreshing = true;
55
74
  state.update((held) => ({
56
75
  baseline: incoming,
57
76
  current: changed(held) ? merged(held, incoming) : incoming,
58
77
  settled: none,
59
78
  }));
79
+ refreshing = false;
80
+ releaseCleanFields();
60
81
  },
61
82
  revert: () => {
62
83
  state.update((held) => ({
@@ -67,19 +88,20 @@ export function draft(initial, persistence) {
67
88
  },
68
89
  submission: () => {
69
90
  const { baseline, current: submitted } = state.value;
91
+ const revision = persistence?.revision();
70
92
  return {
71
93
  accept: (incoming) => {
72
- state.update((held) => {
73
- if (held.baseline !== baseline) {
74
- return { ...held, settled: matching(held.current, submitted) };
94
+ withoutWriting(() => state.update((held) => accepted(held, baseline, submitted, incoming)));
95
+ releaseCleanFields();
96
+ if (changed(state.value)) {
97
+ store(state.value, true);
98
+ }
99
+ else {
100
+ const expected = revision?.();
101
+ if (expected !== undefined) {
102
+ persistence?.clear(expected);
75
103
  }
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
- });
104
+ }
83
105
  },
84
106
  values: submitted,
85
107
  };