Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions openspec/changes/add-snapshot-cache/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Design: Snapshot Cache for React useState

## Goals
- Show cached data immediately on page load (perceived speed) without altering network/cache strategies.
- Keep the change local to the **React hook layer** (front-end concern).
- Ensure snapshot persistence across reloads via IndexedDB, with memory fallback for non-browser environments.

## Approach
- Add snapshot support to `useState(input, options)` in the chain-effect React adapter.
- New options (non-breaking):
- `useSnapshot?: boolean` (default `true`)
- `snapshotMaxAgeMs?: number` (optional; default `Infinity`)
- Snapshot storage:
- Key format: `chain-effect:snapshot:<sourceName>:<inputKey>`
- Value: `superjson.stringify({ data, timestamp })`
- Storage: **IndexedDB** when available, otherwise in-memory Map

## Behavior
1. **Hook initialization**
- If `useSnapshot` is true, attempt to read snapshot.
- Use in-memory snapshot immediately if present (same-session fast path).
- Otherwise read IndexedDB asynchronously; if snapshot exists and not expired, set it as initial `data`.
- Continue normal subscription flow; network requests remain unchanged.

2. **Update flow**
- On every successful update emitted by the data source, persist a snapshot.

3. **Error handling**
- Snapshot read failures are ignored (fallback to normal behavior).
- Snapshot write failures are ignored (no impact to data flow).

## Non-goals
- No changes to `httpFetchCached` or cache strategy semantics.
- No server-side snapshot support.

## Risks / Mitigations
- **Stale data**: optional `snapshotMaxAgeMs` and default to immediate refresh via existing data sources.
- **Storage size**: snapshots are per key and only store latest value; no history.
- **Serialization**: use `superjson` to handle BigInt/Amount types safely.
15 changes: 15 additions & 0 deletions openspec/changes/add-snapshot-cache/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Change: Add snapshot cache for chain-effect React

## Why
Current `httpFetchCached` returns a single value per call, so UI cannot immediately display cached data while a network request is in-flight. We need a front-end-only snapshot layer to show cached data as soon as it becomes available without changing existing cache strategy semantics.

## What Changes
- Add a **snapshot cache** at the React hook layer (`useState`) to hydrate UI from the last successful data.
- Introduce a `useSnapshot` option (default `true`) to enable/disable snapshot hydration per hook call.
- Persist snapshots in a **front-end-owned storage** (IndexedDB with memory fallback) keyed by data source + input key.
- Preserve existing cache strategies (`ttl`, `network-first`, `cache-first`) and network behavior; snapshot only affects initial UI state.

## Impact
- Affected specs: `production-quality` (performance / perceived latency)
- Affected code: `packages/chain-effect` React adapter + hook options
- Behavior change: UI can show cached data immediately before network refresh completes (no change to request policy)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## MODIFIED Requirements

### Requirement: Performance Targets

The application SHALL provide immediate perceived responses by hydrating UI from recent cached snapshots while preserving existing network cache strategies.

#### Scenario: Snapshot-first UI hydration
- **GIVEN** the user opens the app with previously cached data
- **WHEN** a data source hook initializes
- **THEN** the UI shows the cached snapshot as soon as it is available from storage
- **AND** the network request proceeds according to the configured cache strategy
- **AND** the UI updates again when fresh data arrives
12 changes: 12 additions & 0 deletions openspec/changes/add-snapshot-cache/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## 1. Implementation
- [x] Add snapshot storage utility (IndexedDB + memory fallback) in chain-effect React adapter
- [x] Extend `useState` options with `useSnapshot` and `snapshotMaxAgeMs`
- [x] Hydrate initial hook state from snapshot when enabled
- [x] Persist snapshot on successful updates

## 2. Validation
- [x] Add/adjust unit tests for snapshot hydration (if feasible)
- [x] Ensure typecheck/lint pass

## 3. Documentation
- [x] Update relevant docs or inline comments for new options
15 changes: 12 additions & 3 deletions packages/chain-effect/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ export { Schema } from "effect"
export { isChainEffectDebugEnabled } from "./debug"

// SuperJSON for serialization (handles BigInt, Amount, etc.)
import { SuperJSON } from "superjson"
export const superjson = new SuperJSON({ dedupe: true })
export { SuperJSON } from "superjson"
export { superjson, SuperJSON } from "./superjson"

// Schema definitions
export * from "./schema"
Expand Down Expand Up @@ -105,6 +103,17 @@ export {
type PollMeta,
} from "./poll-meta"

// Snapshot Store (IndexedDB + memory fallback)
export {
readSnapshot,
writeSnapshot,
deleteSnapshot,
readMemorySnapshot,
writeMemorySnapshot,
deleteMemorySnapshot,
type SnapshotEntry,
} from "./snapshot-store"

// Source Registry (global singleton + ref counting)
export {
acquireSource,
Expand Down
Loading