npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@daltonr/pathwrite-store-http

v0.8.0

Published

Persistence adapters for PathEngine — LocalStorageStore (browser) and HttpStore (REST API)

Downloads

749

Readme

@daltonr/pathwrite-store-http

Persistence adapters for PathEngine. Supports both browser-local storage (LocalStorageStore) and REST API storage (HttpStore). Both implement the same PathStore interface, so they're interchangeable — switch backends without changing your code.


Installation

npm install @daltonr/pathwrite-core @daltonr/pathwrite-store-http
# Plus your framework adapter:
npm install @daltonr/pathwrite-vue   # or react / angular / svelte

Storage Adapters

| Adapter | Storage location | Best for | |---------|------------------|----------| | LocalStorageStore | Browser localStorage (or in-memory fallback) | Quick prototyping, single-device sessions, offline-capable wizards | | HttpStore | REST API server | Multi-device sessions, team collaboration, persistent backend storage |

Both adapters implement the PathStore interface (save, load, delete), so you can swap between them without rewriting your wizard logic.


Exports

import {
  // Storage adapters
  LocalStorageStore,            // Browser-local storage (localStorage or in-memory fallback)
  HttpStore,                    // REST API storage
  
  // Observer factory
  httpPersistence,              // Returns a PathObserver that auto-saves state
  
  // Load/restore orchestration
  restoreOrStart,               // Handles the load/restore-or-start pattern
  
  // Types
  LocalStorageStoreOptions,
  StorageAdapter,               // Interface for custom storage backends
  HttpStoreOptions,
  HttpPersistenceOptions,
  RestoreOrStartOptions,
  ObserverStrategy,
  PathStore,                    // Interface that both adapters implement
  
  // Re-exported from core
  PathData, PathDefinition, PathEvent, PathObserver,
  PathEngineOptions, PathSnapshot, PathStep,
  PathStepContext, SerializedPathState,
  matchesStrategy,
} from "@daltonr/pathwrite-store-http";

Quick Start: LocalStorageStore

Browser-local persistence — perfect for prototyping and single-device wizards:

import { LocalStorageStore, httpPersistence, restoreOrStart } from "@daltonr/pathwrite-store-http";

const store = new LocalStorageStore({ prefix: "myapp:" });
const key = `user:${userId}:onboarding`;

const { engine, restored } = await restoreOrStart({
  store,
  key,
  path: onboardingWizard,
  initialData: { name: "", email: "" },
  observers: [
    httpPersistence({ store, key, strategy: "onNext" }),
  ],
});

// engine is a plain PathEngine — pass it to any adapter
const { snapshot, next } = usePath({ engine });

if (restored) {
  console.log("Welcome back! Resuming from step", engine.snapshot()?.stepId);
}

LocalStorageStore Options

const store = new LocalStorageStore({
  // Optional: prefix for all keys (default: "@daltonr/pathwrite:")
  prefix: "myapp:",
  
  // Optional: custom storage backend
  // - undefined (default): uses global localStorage, falls back to in-memory if unavailable
  // - sessionStorage: uses sessionStorage instead of localStorage
  // - null: forces in-memory fallback (useful for SSR/testing)
  // - custom StorageAdapter: any object with getItem/setItem/removeItem/getAllKeys
  storage: sessionStorage,
});

LocalStorageStore Methods

| Method | Description | |--------|-------------| | save(key, state) | Save a snapshot to storage | | load(key) | Load a snapshot (returns null if not found) | | delete(key) | Delete a snapshot | | list() | Return all keys saved under this store's prefix | | clear() | Delete all snapshots under this store's prefix |

Example: Session picker

const store = new LocalStorageStore({ prefix: "wizard:" });

// List all saved sessions
const sessionKeys = await store.list();
console.log(sessionKeys); // → ["user:1:session", "user:2:session"]

// Load a specific session
const { engine, restored } = await restoreOrStart({
  store,
  key: sessionKeys[0],
  path: myPath,
  observers: [httpPersistence({ store, key: sessionKeys[0] })],
});

// Clear all sessions (e.g., on logout)
await store.clear();

Quick Start: HttpStore

REST API persistence — for multi-device sessions and server-backed storage:

import { HttpStore, httpPersistence, restoreOrStart } from "@daltonr/pathwrite-store-http";

const store = new HttpStore({ baseUrl: "/api/wizard" });
const key = `user:${userId}:onboarding`;

const { engine, restored } = await restoreOrStart({
  store,
  key,
  path: onboardingWizard,
  initialData: { name: "", email: "" },
  observers: [httpPersistence({ store, key })],
});


The one-call approach — restoreOrStart

For most use cases, one async call is all you need. Works with both LocalStorageStore and HttpStore:

import { LocalStorageStore, httpPersistence, restoreOrStart } from "@daltonr/pathwrite-store-http";

const store = new LocalStorageStore({ prefix: "myapp:" });
// Or: const store = new HttpStore({ baseUrl: "/api/wizard" });

const key = `user:${userId}:onboarding`;

const { engine, restored } = await restoreOrStart({
  store,
  key,
  path: onboardingWizard,
  initialData: { name: "", email: "" },
  observers: [
    httpPersistence({ store, key, strategy: "onNext" }),
  ],
});

// engine is a plain PathEngine — pass it to any adapter
const { snapshot, next } = usePath({ engine });

if (restored) {
  console.log("Welcome back! Resuming from step", engine.snapshot()?.stepId);
}

restoreOrStart returns { engine, restored }:

  • engine — a PathEngine pre-wired with persistence, ready to pass to usePath({ engine }) or <PathShell :engine="engine">
  • restored: booleantrue if state was loaded from storage, false if it started fresh

Vue example

<script setup lang="ts">
import { PathShell } from "@daltonr/pathwrite-vue";
import { LocalStorageStore, httpPersistence, restoreOrStart } from "@daltonr/pathwrite-store-http";

const store = new LocalStorageStore({ prefix: "myapp:" });
const key = `user:${userId}:onboarding`;

const { engine } = await restoreOrStart({
  store,
  key,
  path: onboardingWizard,
  initialData: { name: "", email: "" },
  observers: [httpPersistence({ store, key })],
});
</script>

<template>
  <PathShell :path="onboardingWizard" :engine="engine" />
</template>

React example

import { LocalStorageStore, httpPersistence, restoreOrStart } from "@daltonr/pathwrite-store-http";

const store = new LocalStorageStore({ prefix: "myapp:" });
const key = `user:${userId}:onboarding`;

// In a route loader or equivalent — runs before the component mounts
export async function loader() {
  return restoreOrStart({
    store,
    key,
    path: onboardingWizard,
    initialData: { name: "", email: "" },
    observers: [httpPersistence({ store, key })],
  });
}

function WizardPage({ engine }: { engine: PathEngine }) {
  // engine is ready — no loading state needed
  return <PathShell engine={engine} path={onboardingWizard} steps={{ ... }} />;
}

The observer approach — httpPersistence

httpPersistence() returns a PathObserver — a plain function (event, engine) => void. Pass it to PathEngine via the observers option:

import { PathEngine } from "@daltonr/pathwrite-core";
import { HttpStore, httpPersistence } from "@daltonr/pathwrite-store-http";

const store = new HttpStore({ baseUrl: "/api/wizard" });

const engine = new PathEngine({
  observers: [
    httpPersistence({ store, key: "user:123:onboarding", strategy: "onNext" }),
    // add as many observers as you like — logger, analytics, SQL capture, etc.
  ],
});

await engine.start(myPath, initialData);

For restoration, pass observers through fromState():

import { PathEngine } from "@daltonr/pathwrite-core";

const saved = await store.load("user:123:onboarding");
const engine = saved
  ? PathEngine.fromState(saved, pathDefs, { observers: [httpPersistence({ store, key: "user:123:onboarding" })] })
  : new PathEngine({ observers: [httpPersistence({ store, key: "user:123:onboarding" })] });

if (!saved) await engine.start(myPath, initialData);

This is exactly what createPersistedEngine does internally — use it directly if you need more control over the load step or want to add other observers.


Persistence strategies

| Strategy | Saves when | Best for | |---|---|---| | "onNext" (default) | next() completes navigation to a new step | Text-heavy forms — 1 save per step | | "onEveryChange" | Any stateChanged event (data changes + navigation) | Checkbox/dropdown wizards, crash protection | | "onSubPathComplete" | A sub-path finishes and the parent resumes | Wizards with nested sub-flows | | "onComplete" | The entire path completes | Audit trail / record-keeping only | | "manual" | Never automatically | Full control — call store.save(key, engine.exportState()!) yourself |

"onEveryChange" + text inputs? Add debounceMs: 500 to avoid saving on every keystroke.

httpPersistence({
  store,
  key: "user:123:onboarding",
  strategy: "onEveryChange",
  debounceMs: 500,   // collapse rapid keystrokes into one save
})

HttpStore

HttpStore is a thin REST transport. It has three methods:

| Method | HTTP verb | Default URL | |---|---|---| | store.save(key, state) | PUT | ${baseUrl}/state/${encodeURIComponent(key)} | | store.load(key) | GET | ${baseUrl}/state/${encodeURIComponent(key)} | | store.delete(key) | DELETE | ${baseUrl}/state/${encodeURIComponent(key)} |

Returns null from load() when the server responds with 404.

Options

const store = new HttpStore({
  baseUrl: "/api/wizard",

  // Optional: custom URL builders
  saveUrl:   (key) => `/v2/paths/${key}`,
  loadUrl:   (key) => `/v2/paths/${key}`,
  deleteUrl: (key) => `/v2/paths/${key}`,

  // Optional: auth headers (static object or async function)
  headers: async () => ({ Authorization: `Bearer ${await getToken()}` }),

  // Optional: custom fetch (for testing, SSR, etc.)
  fetch: myCustomFetch,

  // Optional: transport-level error callback
  onError: (err, operation, key) => console.error(`${operation} failed for ${key}:`, err),
});

Multiple observers

Observers compose freely. Each one is a plain function, independent of all others:

import { PathEngine } from "@daltonr/pathwrite-core";
import { HttpStore, httpPersistence } from "@daltonr/pathwrite-store-http";

const store = new HttpStore({ baseUrl: "/api/wizard" });

const logger: PathObserver = (event) =>
  console.log(`[wizard] ${event.type}`, 'cause' in event ? event.cause : '');

const analytics: PathObserver = (event) => {
  if (event.type === "stateChanged" && !event.snapshot.isNavigating) {
    trackEvent("wizard_step", { stepId: event.snapshot.stepId });
  }
};

const engine = new PathEngine({
  observers: [
    httpPersistence({ store, key: "user:123:onboarding" }),
    logger,
    analytics,
  ],
});

API server contract

Your API server must handle these three endpoints for the default URL scheme:

| Endpoint | Method | Body | Success response | |---|---|---|---| | /state/:key | PUT | JSON SerializedPathState | 200 OK | | /state/:key | GET | — | 200 OK + JSON body, or 404 if not found | | /state/:key | DELETE | — | 200 OK or 404 |

The state is automatically deleted from the server when the path completes (except with the "onComplete" strategy, which saves a final record instead).


Callbacks

httpPersistence({
  store,
  key: "user:123:onboarding",
  onSaveSuccess: () => console.log("Saved ✓"),
  onSaveError:   (err) => toast.error(`Save failed: ${err.message}`),
});

createPersistedEngine accepts the same onSaveSuccess / onSaveError options and passes them through to the persistence observer.


© 2026 Devjoy Ltd. MIT License.