solid-drift 0.27.0 → 0.28.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/README.md CHANGED
@@ -1600,6 +1600,23 @@ scanline.start();
1600
1600
  - `createShake(options?)` detects shake gestures from `devicemotion`: `{ supported, needsPermission, listening, shakes, error, requestPermission, start, stop }`. A shake counts when the acceleration delta exceeds `threshold` (default 15 m/s^2), rate-limited by `cooldown` (default 800ms); `onShake` fires per shake.
1601
1601
  - `createScanline(options?)` is the animated line of a QR/barcode viewfinder: `{ progress, running, start, stop }`. `progress()` sweeps 0..1 on the shared clock (`direction: "down" | "up" | "alternate"`); bind it to the line's position. Under reduced motion it freezes mid-frame. Every primitive is SSR-safe: server renders get `supported: false` and safe no-op actions.
1602
1602
 
1603
+ ### Optimistic updates
1604
+
1605
+ ```tsx
1606
+ import { createOptimistic } from "solid-drift";
1607
+
1608
+ const feed = createOptimistic<Bid[], Bid>([], (bids, bid) =>
1609
+ bids.some((b) => b.id === bid.id) ? bids : [...bids, bid],
1610
+ );
1611
+
1612
+ // in an event handler:
1613
+ await feed.commit(bid, (b) => sendBidTx(b)).catch(() => {
1614
+ // already rolled back; read feed.error() for a toast
1615
+ });
1616
+ ```
1617
+
1618
+ `createOptimistic(initial, apply)` gives `{ value, setBase, pending, pendingCount, error, commit, reset }`. `commit(update, task)` applies the update instantly, then runs the task; on success the update is promoted into the base truth (no flicker while the server catches up), and on failure it is rolled back, `error()` is set, and the error is rethrown. `setBase()` folds fresh server truth in (after a refetch); write `apply` idempotently, for example upsert by id, so truth that already includes an optimistic update does not duplicate it. `reset()` drops in-flight updates. Pure signals, SSR-safe.
1619
+
1603
1620
  ### Easings
1604
1621
 
1605
1622
  Named easings: `linear`, `easeInQuad`, `easeOutQuad`, `easeInOutQuad`, `easeInCubic`, `easeOutCubic`, `easeInOutCubic`, `easeInQuart`, `easeOutQuart`, `easeInOutQuart`, `easeOutExpo`, `easeOutBack`, plus the cartoon set: `easeInBack` (anticipation dip before movement), `easeInOutBack` (wind-up, overshoot, settle), `easeOutElastic` (decaying rubber-band oscillation), `easeOutBounce` (shrinking cartoon bounces). Also `cubicBezier(x1, y1, x2, y2)` for CSS-style curves. Pass a name or a custom `(t) => number` function anywhere an easing is accepted.
package/dist/index.d.ts CHANGED
@@ -46,3 +46,5 @@ export { createVoiceState, createMicLevel, createSpeech, createWaveform, createT
46
46
  export type { VoiceStatus, VoiceStateControls, MicLevelOptions, MicLevelControls, SpeechOptions, SpeechControls, WaveformOptions, WaveformControls, TTSProvider, TTSOptions, TTSControls, ThinkingOptions, ThinkingControls, PromptOptions, PromptControls, } from "./voice.js";
47
47
  export { createBattery, createNetwork, createWakeLock, createContactPick, createOTP, createShare, createNFC, createTorch, createGyro, createShake, createScanline, } from "./hardware.js";
48
48
  export type { BatteryState, NetworkState, WakeLockState, PickedContact, ContactPickState, OTPState, ShareState, NFCRecord, NFCMessage, NFCState, TorchState, GyroState, ShakeOptions, ShakeState, ScanlineOptions, ScanlineState, } from "./hardware.js";
49
+ export { createOptimistic } from "./optimistic.js";
50
+ export type { OptimisticControls } from "./optimistic.js";
package/dist/index.js CHANGED
@@ -39,3 +39,4 @@ export { createPoll, shortenAddress, isAddress, formatUnits, parseUnits, CHAINS,
39
39
  export { createSSEParser, createSSE, createChatModel, } from "./stream.js";
40
40
  export { createVoiceState, createMicLevel, createSpeech, createWaveform, createTTS, createThinking, createPrompt, } from "./voice.js";
41
41
  export { createBattery, createNetwork, createWakeLock, createContactPick, createOTP, createShare, createNFC, createTorch, createGyro, createShake, createScanline, } from "./hardware.js";
42
+ export { createOptimistic } from "./optimistic.js";
@@ -0,0 +1,44 @@
1
+ export interface OptimisticControls<T, U> {
2
+ /** Server truth plus every in-flight optimistic update, in commit order. */
3
+ value: () => T;
4
+ /** Replace the server truth (for example after a refetch). */
5
+ setBase: (next: T | ((prev: T) => T)) => void;
6
+ /** True while at least one commit is in flight. */
7
+ pending: () => boolean;
8
+ /** Number of commits currently in flight. */
9
+ pendingCount: () => number;
10
+ /** Last commit failure, cleared when the next commit starts. */
11
+ error: () => Error | null;
12
+ /**
13
+ * Apply `update` immediately, then run `task`. On success the update is
14
+ * promoted into the base truth (so the value stays stable while the
15
+ * server catches up); on failure it is rolled back, `error()` is set,
16
+ * and the error is rethrown so callers can react (toast, retry).
17
+ */
18
+ commit: (update: U, task: (update: U) => Promise<unknown>) => Promise<void>;
19
+ /** Drop every in-flight optimistic update without running their tasks. */
20
+ reset: () => void;
21
+ }
22
+ /**
23
+ * createOptimistic
24
+ *
25
+ * Optimistic updates with rollback, in the spirit of React's `useOptimistic`.
26
+ * The UI reflects `update` the moment `commit()` is called; if the backing
27
+ * task (a transaction, a POST, a stream send) fails, that update is rolled
28
+ * back and the error surfaces on `error()`.
29
+ *
30
+ * Write `apply` idempotently (upsert by id rather than blind append) when
31
+ * the server truth delivered through `setBase` may already include an
32
+ * optimistic update.
33
+ *
34
+ * ```ts
35
+ * const feed = createOptimistic<Bid[], Bid>([], (bids, bid) =>
36
+ * bids.some((b) => b.id === bid.id) ? bids : [...bids, bid],
37
+ * );
38
+ *
39
+ * await feed.commit(bid, (b) => sendBidTx(b)).catch(() => {
40
+ * // already rolled back; show a toast from feed.error()
41
+ * });
42
+ * ```
43
+ */
44
+ export declare function createOptimistic<T, U>(initial: T, apply: (state: T, update: U) => T): OptimisticControls<T, U>;
@@ -0,0 +1,76 @@
1
+ import { createSignal } from "solid-js";
2
+ /**
3
+ * createOptimistic
4
+ *
5
+ * Optimistic updates with rollback, in the spirit of React's `useOptimistic`.
6
+ * The UI reflects `update` the moment `commit()` is called; if the backing
7
+ * task (a transaction, a POST, a stream send) fails, that update is rolled
8
+ * back and the error surfaces on `error()`.
9
+ *
10
+ * Write `apply` idempotently (upsert by id rather than blind append) when
11
+ * the server truth delivered through `setBase` may already include an
12
+ * optimistic update.
13
+ *
14
+ * ```ts
15
+ * const feed = createOptimistic<Bid[], Bid>([], (bids, bid) =>
16
+ * bids.some((b) => b.id === bid.id) ? bids : [...bids, bid],
17
+ * );
18
+ *
19
+ * await feed.commit(bid, (b) => sendBidTx(b)).catch(() => {
20
+ * // already rolled back; show a toast from feed.error()
21
+ * });
22
+ * ```
23
+ */
24
+ export function createOptimistic(initial, apply) {
25
+ const [base, setBaseSignal] = createSignal(initial);
26
+ const [queue, setQueue] = createSignal([]);
27
+ const [error, setError] = createSignal(null);
28
+ let nextId = 0;
29
+ const value = () => {
30
+ const truth = base();
31
+ const pending = queue();
32
+ if (pending.length === 0)
33
+ return truth;
34
+ return pending.reduce((state, entry) => apply(state, entry.update), truth);
35
+ };
36
+ const setBase = (next) => {
37
+ if (typeof next === "function") {
38
+ setBaseSignal(next);
39
+ }
40
+ else {
41
+ setBaseSignal(() => next);
42
+ }
43
+ };
44
+ const commit = async (update, task) => {
45
+ const id = nextId++;
46
+ setError(null);
47
+ setQueue((q) => [...q, { id, update }]);
48
+ try {
49
+ await task(update);
50
+ // Promote the confirmed update into the base truth so the value
51
+ // stays stable while the server truth catches up via setBase.
52
+ setBaseSignal((prev) => apply(prev, update));
53
+ }
54
+ catch (e) {
55
+ const err = e instanceof Error ? e : new Error(String(e));
56
+ setError(err);
57
+ throw err;
58
+ }
59
+ finally {
60
+ setQueue((q) => q.filter((entry) => entry.id !== id));
61
+ }
62
+ };
63
+ const reset = () => {
64
+ setQueue([]);
65
+ setError(null);
66
+ };
67
+ return {
68
+ value,
69
+ setBase,
70
+ pending: () => queue().length > 0,
71
+ pendingCount: () => queue().length,
72
+ error,
73
+ commit,
74
+ reset,
75
+ };
76
+ }
package/package.json CHANGED
@@ -43,5 +43,5 @@
43
43
  },
44
44
  "type": "module",
45
45
  "types": "./dist/index.d.ts",
46
- "version": "0.27.0"
46
+ "version": "0.28.0"
47
47
  }