@rewindkit/runtime
v0.1.0-alpha.1
Published
Zero-config record/replay toolkit for React + Redux Toolkit + Module Federation — recorder, player, redaction, and React companion
Maintainers
Readme
@rewindkit/runtime
Record Redux actions and state snapshots from your React app — including across Module Federation remotes — into a single origin-tagged session, and time-travel through them in-page.
What this adds over Redux DevTools: zero-config injection (coming in the Vite plugin), cross-remote Module Federation capture into one session, component render instrumentation, configurable redaction, and shareable session bundles. This is not a Redux DevTools replacement — it builds on top of Redux's own store, and the DevTools remain the right tool for inspecting individual actions and state diffs.
The "compiler" mentioned in some docs is a Vite plugin + Babel/SWC transform, not a DSL compiler. This alpha is manual wiring — the zero-config Vite plugin that auto-injects the enhancer and panel is a future release.
Install
npm install @rewindkit/runtime@alpha
# or
bun add @rewindkit/runtime@alphaPeer dependencies: redux ^5, react ^18 || ^19 (react is optional — only needed
for the ./react subpath).
Quickstart
1. Add the enhancer and configure recording
import { configureStore } from "@reduxjs/toolkit";
import { replayEnhancer, configureReplay } from "@rewindkit/runtime";
export const store = configureStore({
reducer: {
orders: ordersReducer,
trades: tradesReducer,
positions: positionsReducer,
watchlist: watchlistReducer,
},
// replayEnhancer wraps the store so snapshot-restore round-trips work.
// Prepend it so it sits closest to the base reducer/dispatch.
enhancers: (getDefaultEnhancers) =>
getDefaultEnhancers().prepend(replayEnhancer),
});
// Opt-in recording — OFF by default (privacy-safe).
const replayHandle = configureReplay({
store,
includeStateKeys: ["orders", "trades", "positions", "watchlist"],
keyframeEvery: 1, // snapshot every action (suitable for low-frequency stores)
force: true, // enable in dev even though isProd() returns false
});
// Recording does not auto-start. Call start() when ready.
replayHandle.start();Recording is OFF by default. Nothing is captured until you call
configureReplay() and start(). This is intentional — state may contain PII,
and opt-in is the safe default. See Privacy & Redaction.
2. Time-travel (re-drive)
Use the player API to restore the app to any recorded point:
import { createPlayer, seek, returnToLive } from "@rewindkit/runtime";
const player = createPlayer(bundle); // a SessionBundle from exportBundle()
seek(player, 5); // jump to event index 5
returnToLive(); // back to the live store3. React companion (./react)
import { useReplayMode } from "@rewindkit/runtime/react";
const MyComponent = () => {
const mode = useReplayMode(); // "live" | "history"
// Use mode to show replay chrome, disable inputs, etc.
};useReplayMode is for rendering UI chrome only (badges, disabled inputs,
replay overlays). It must NOT be used to gate side-channel handlers like WebSocket
onmessage callbacks — see the next section.
Replaying apps with live data
If your app has live data feeds (WebSockets, polling, SSE), a naive replay will be stomped within a frame as live data overwrites the restored state. You need an app-owned feed gate:
import { getReplayMode } from "@rewindkit/runtime";
// In your WebSocket onmessage handler:
socket.onmessage = (event) => {
// Read getReplayMode() SYNCHRONOUSLY — not useReplayMode().
// A React render lags the event loop, so useReplayMode() can read stale "live"
// and let a tick stomp the re-driven state.
if (getReplayMode() === "history") return;
// Process the tick normally...
dispatch(updateQuote(JSON.parse(event.data)));
};Why getReplayMode() and not useReplayMode()? The hook is a React render
subscription — it lags the event loop by a frame. A side-channel handler that reads
the hook's value can see stale "live" after a seek and let a live tick overwrite
restored history. getReplayMode() reads the current value synchronously and is the
correct gate for non-React code paths.
Privacy & Redaction
RewindKit captures Redux state, which may contain PII. The privacy posture:
- Client-only by default. No data leaves the browser unless you add a transport.
- Allowlist-first. Use
includeStateKeysto capture only the slices you intend. In production, an allowlist is required — omitting it logs a warning. - Redaction. Use
redactKeysto mask sensitive fields before capture. - No tokens in web storage. Session bundles never persist auth tokens.
See PRIVACY.md for the full privacy posture.
Session bundles (export / import)
import { exportBundle, importBundle } from "@rewindkit/runtime";
// Export the current session
const json = exportBundle();
// Import a shared session
const bundle = importBundle(jsonString);
const player = createPlayer(bundle);Bundles are JSON-serializable and can be saved to a file or shared between
developers. They contain recorded actions, state snapshots, and metadata — but
never auth tokens or fields matched by redactKeys.
ESM only
This package is ESM-only ("type": "module"). It works with Vite, esbuild, and
any bundler that supports ESM. A require() call will get ERR_REQUIRE_ESM —
this is intentional to prevent the dual-package hazard (two recorder instances).
API overview
Core (@rewindkit/runtime)
| Export | Description |
|---|---|
| replayEnhancer | Redux store enhancer — prepend to your enhancers |
| configureReplay(opts) | Configure and return a ReplayHandle (start/stop/export) |
| registerStore(name, store) | Register a remote's store (Module Federation) |
| createPlayer(bundle) | Create a player for time-travel |
| seek(player, index) | Jump to an event index |
| returnToLive() | Return to the live store state |
| exportBundle() / importBundle(json) | Session bundle I/O |
| getReplayMode() / subscribeReplayMode() | Synchronous replay mode signal |
| VERSION, SCHEMA_VERSION, DELTA_FORMAT_VERSION | Versioning constants |
React companion (@rewindkit/runtime/react)
| Export | Description |
|---|---|
| useReplayMode() | Hook returning "live" or "history" (render-only) |
