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

@statesync/persistence

v2.0.0

Published

Persistence layer for state-sync — save and restore state snapshots

Readme

@statesync/persistence

Persistence layer for @statesync/core with automatic state caching, schema migration, cross-tab sync, and compression.

Installation

pnpm add @statesync/persistence @statesync/core

Quick Start

import { createRevisionSync } from '@statesync/core';
import {
  createPersistenceApplier,
  createLocalStorageBackend,
  loadPersistedSnapshot,
} from '@statesync/persistence';

// 1. Create storage backend
const storage = createLocalStorageBackend({ key: 'my-app-state' });

// 2. Wrap your applier with persistence
const applier = createPersistenceApplier({
  storage,
  applier: myInnerApplier,
  throttling: { debounceMs: 300 },
});

// 3. Load cached state before starting sync
const cached = await loadPersistedSnapshot(storage, applier);
if (cached) {
  console.log('Restored from cache:', cached.revision);
}

// 4. Start sync - state will be automatically persisted
const sync = createRevisionSync({ ... applier ... });
await sync.start();

// 5. Cleanup when done
await applier.flush(); // Save pending data
applier.dispose();

Storage Backends

localStorage

import { createLocalStorageBackend } from '@statesync/persistence';

const storage = createLocalStorageBackend({
  key: 'my-state',
  // Optional custom serialization
  serialize: (snapshot) => JSON.stringify(snapshot),
  deserialize: (data) => JSON.parse(data),
});

IndexedDB

For larger data (>5MB):

import { createIndexedDBBackend } from '@statesync/persistence';

const storage = createIndexedDBBackend({
  dbName: 'my-app',
  storeName: 'state-cache',
  version: 1,
  // Retry logic for blocked database
  retryAttempts: 3,
  onBlocked: () => console.warn('DB blocked, retrying...'),
});

sessionStorage

For temporary state (cleared on tab close):

import { createSessionStorageBackend } from '@statesync/persistence';

const storage = createSessionStorageBackend({ key: 'temp-state' });

In-Memory (for testing)

import { createMemoryStorageBackend } from '@statesync/persistence';

const storage = createMemoryStorageBackend({
  latencyMs: 50, // Simulate network delay
  failOnSave: false, // Toggle to test error handling
  maxSizeBytes: 1024 * 1024, // Simulate quota
});

// Test helpers
storage.getSavedSnapshots(); // Get all saved snapshots
storage.setFailMode({ save: true }); // Inject errors
storage.reset(); // Reset to initial state

Throttling

Control save frequency to reduce storage I/O:

const applier = createPersistenceApplier({
  storage,
  applier: innerApplier,
  throttling: {
    debounceMs: 300,    // Wait 300ms of "silence" before saving
    throttleMs: 1000,   // Maximum one save per second
    maxWaitMs: 5000,    // Force save after 5s of continuous updates
    leading: false,     // Don't save immediately on first update
  },
});

Events & Observability

Subscribe to persistence events:

const applier = createPersistenceApplier({ ... });

// Save lifecycle events
applier.on('saveStart', (snapshot) => {
  console.log('Saving revision:', snapshot.revision);
});

applier.on('saveComplete', (snapshot, durationMs) => {
  console.log(`Saved in ${durationMs}ms`);
});

applier.on('saveError', (error, snapshot) => {
  console.error('Save failed:', error);
});

// Get statistics
const stats = applier.getStats();
console.log({
  saveCount: stats.saveCount,
  saveErrorCount: stats.saveErrorCount,
  totalBytesSaved: stats.totalBytesSaved,
  lastSaveDurationMs: stats.lastSaveDurationMs,
});

Schema Migration

Handle data format changes between app versions:

import { createMigrationBuilder, loadPersistedSnapshot } from '@statesync/persistence';

// Define migrations
const migration = createMigrationBuilder<AppStateV3>()
  .addMigration(1, (v1) => ({ ...v1, newField: 'default' }))
  .addMigration(2, (v2) => ({ ...v2, enabled: true }))
  .withValidator((data): data is AppStateV3 => {
    return typeof data === 'object' && 'enabled' in data;
  })
  .build(3);

// Save with schema version
const applier = createPersistenceApplier({
  storage,
  applier: innerApplier,
  schemaVersion: 3, // Current version
});

// Load with automatic migration
const cached = await loadPersistedSnapshot(storage, applier, {
  migration,
});

TTL (Time-To-Live)

Automatically expire cached data:

const applier = createPersistenceApplier({
  storage,
  applier: innerApplier,
  ttlMs: 24 * 60 * 60 * 1000, // 24 hours
});

// Expired data won't be loaded
const cached = await loadPersistedSnapshot(storage, applier);
// Returns null if data is older than TTL

Compression

Reduce storage usage with built-in LZ compression:

import { createLZCompressionAdapter } from '@statesync/persistence';

const applier = createPersistenceApplier({
  storage,
  applier: innerApplier,
  compression: createLZCompressionAdapter(),
});

Or use external library (better compression):

import LZString from 'lz-string';
import { createLZStringAdapter } from '@statesync/persistence';

const applier = createPersistenceApplier({
  compression: createLZStringAdapter(LZString),
});

Benchmark compression:

import { benchmarkCompression, createLZCompressionAdapter } from '@statesync/persistence';

const adapter = createLZCompressionAdapter();
const result = benchmarkCompression(myJsonData, adapter);

console.log({
  ratio: result.ratio,           // 0.45 = 55% size reduction
  compressTimeMs: result.compressTimeMs,
  decompressTimeMs: result.decompressTimeMs,
});

Cross-Tab Sync

Synchronize state between browser tabs:

import { createCrossTabSync } from '@statesync/persistence';

const applier = createPersistenceApplier({
  storage,
  applier: innerApplier,
  crossTabSync: {
    channelName: 'my-app-state',
    receiveUpdates: true,  // Apply updates from other tabs
    broadcastSaves: true,  // Notify other tabs of saves
  },
});

// Or use the helper
const applier = createPersistenceApplierWithDefaults({
  storage,
  applier: innerApplier,
  topic: 'settings',
  enableCrossTab: true,
});

Integrity Verification

Enable hash verification to detect corruption:

const applier = createPersistenceApplier({
  storage,
  applier: innerApplier,
  enableHash: true,
});

// Load with hash verification
const cached = await loadPersistedSnapshot(storage, applier, {
  verifyHash: true,
});

Error Handling

const applier = createPersistenceApplier({
  storage,
  applier: innerApplier,
  onPersistenceError: (context) => {
    console.error(`${context.operation} failed:`, context.error);

    // Report to monitoring
    Sentry.captureException(context.error, {
      extra: { operation: context.operation },
    });
  },
});

API Reference

Storage Backends

| Function | Description | |----------|-------------| | createLocalStorageBackend | Browser localStorage (~5MB limit) | | createIndexedDBBackend | IndexedDB with retry logic (~50MB+) | | createSessionStorageBackend | Session-scoped storage | | createMemoryStorageBackend | In-memory for testing |

Persistence Applier

| Method | Description | |--------|-------------| | apply(snapshot) | Apply snapshot and schedule save | | dispose() | Cleanup timers and resources | | flush() | Force immediate save of pending data | | hasPendingSave() | Check if save is pending | | on(event, handler) | Subscribe to events | | getStats() | Get persistence statistics |

Utilities

| Function | Description | |----------|-------------| | loadPersistedSnapshot | Load and apply cached state | | clearPersistedData | Clear storage with cross-tab notification | | migrateData | Migrate data between schema versions | | lzCompress / lzDecompress | Built-in LZ compression | | benchmarkCompression | Measure compression performance |

TypeScript Types

import type {
  StorageBackend,
  StorageBackendWithMetadata,
  PersistenceApplierOptions,
  DisposablePersistenceApplier,
  PersistenceStats,
  PersistenceEvents,
  SaveThrottlingOptions,
  MigrationHandler,
  CompressionAdapter,
  LoadOptions,
} from '@statesync/persistence';

License

MIT