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

localspace

v2.0.1

Published

A Promise-first storage toolkit for IndexedDB, localStorage, memory, and React Native AsyncStorage

Downloads

6,015

Readme

localspace

Node CI npm license

localspace is a Promise-first storage toolkit for IndexedDB, localStorage, and React Native AsyncStorage, with TypeScript types and a focused plugin system.

Motivation

localspace keeps the familiar key-value shape of localForage while making Promise semantics, explicit batching, and driver capabilities the actual contract. It adds first-class TypeScript types, IndexedDB transactions, multi-platform drivers, and a plugin architecture.

It is not a callback-compatible drop-in replacement. Existing Promise-based localForage usage is usually straightforward to migrate; callback-based code must be converted to await or .then().

Quick Start

Get started in 5 minutes:

1. Install

pnpm add localspace
# or: npm install localspace

2. Basic Usage

import localspace from 'localspace';

// Store and retrieve data
await localspace.setItem('user', { name: 'Ada', role: 'admin' });
const user = await localspace.getItem<{ name: string; role: string }>('user');

// TypeScript generics for type safety
interface User {
  name: string;
  role: string;
}
const typedUser = await localspace.getItem<User>('user');

3. Create Isolated Instances

const cache = localspace.createInstance({
  name: 'my-app',
  storeName: 'cache',
});

await cache.setItem('token', 'abc123');

4. Batch Operations

// Write multiple items in one transaction (IndexedDB)
await localspace.setItems([
  { key: 'user:1', value: { name: 'Ada' } },
  { key: 'user:2', value: { name: 'Grace' } },
]);

// Read multiple items
const users = await localspace.getItems(['user:1', 'user:2']);

5. Use Plugins

import localspace, { ttlPlugin, encryptionPlugin } from 'localspace';

const secureStore = localspace.createInstance({
  name: 'secure',
  plugins: [
    ttlPlugin({ defaultTTL: 60_000 }), // Auto-expire after 1 minute
    encryptionPlugin({ key: 'your-32-byte-key' }), // Encrypt data
  ],
});

That's it! For more details, see the sections below.


Table of Contents


Installation

pnpm add localspace
# or
npm install localspace
import localspace from 'localspace';

Bundles included: ES modules, CommonJS, UMD, plus .d.ts files.


Core API

Storage Methods

// Set and get items
await localspace.setItem('key', value);
const value = await localspace.getItem<T>('key');

// Remove items
await localspace.removeItem('key');
await localspace.clear();

// Query
const count = await localspace.length();
const keys = await localspace.keys();

// Iterate
await localspace.iterate<T, void>((value, key, index) => {
  console.log(key, value);
});

Driver Selection

// Web fallback order (default bundled drivers)
await localspace.setDriver([localspace.INDEXEDDB, localspace.LOCALSTORAGE]);

// Check current driver
console.log(localspace.driver());
// 'asyncStorage' | 'localStorageWrapper'

In-Memory Fallback

When browser persistent storage is unavailable (for example, cookies/site data are blocked), opt in to the memory driver explicitly:

await localspace.setDriver([
  localspace.INDEXEDDB,
  localspace.LOCALSTORAGE,
  localspace.MEMORY,
]);

The memory driver is runtime-only: data is shared by name/storeName while the page is alive and is lost on reload. It is not part of the default fallback order so persistent-storage failures remain visible unless you opt in.

React Native AsyncStorage

import AsyncStorage from '@react-native-async-storage/async-storage';
import localspace from 'localspace';
import { createReactNativeInstance } from 'localspace/react-native';

const mobileStore = await createReactNativeInstance(localspace, {
  name: 'myapp',
  storeName: 'kv',
  reactNativeAsyncStorage: AsyncStorage,
});

The default localspace entry does not bundle the React Native driver; it is included only when importing localspace/react-native. Explicit reactNativeAsyncStorage injection is recommended.

Advanced usage is still available:

import localspace from 'localspace';
import { installReactNativeAsyncStorageDriver } from 'localspace/react-native';

await installReactNativeAsyncStorageDriver(localspace);
await localspace.setDriver(localspace.REACTNATIVEASYNCSTORAGE);

Integration smoke (official AsyncStorage Jest mock):

pnpm test:rn:integration

See integration/react-native-jest/README.md for details.

Manual Detox smoke workflow (real simulator/emulator runtime):

  • Workflow: .github/workflows/detox-mobile.yml
  • Fixture app folder: integration/react-native-detox/
  • Fixture README: integration/react-native-detox/README.md

📖 Full API Reference: docs/api-reference.md


Batch Operations

Use batch APIs for better performance. IndexedDB runs each batch chunk inside a transaction; when maxBatchSize is unset, the entire call is one chunk. Other drivers expose the same API but may perform grouped work sequentially.

// One transaction on IndexedDB when maxBatchSize is unset
await localspace.setItems([
  { key: 'user:1', value: { name: 'Ada' } },
  { key: 'user:2', value: { name: 'Lin' } },
]);

// Ordered bulk read
const result = await localspace.getItems(['user:1', 'user:2']);
// [{ key: 'user:1', value: {...} }, { key: 'user:2', value: {...} }]

// One transaction on IndexedDB when maxBatchSize is unset
await localspace.removeItems(['user:1', 'user:2']);

Transactions

For atomic multi-step operations on IndexedDB:

await localspace.runTransaction('readwrite', async (tx) => {
  const current = (await tx.get<number>('counter')) ?? 0;
  await tx.set('counter', current + 1);
  await tx.set('lastUpdated', Date.now());
});

runTransaction() is supported by the IndexedDB and memory drivers. On localStorage and React Native AsyncStorage it rejects with UNSUPPORTED_OPERATION; use explicit operations when atomicity is unnecessary.


Plugin System

localspace ships with a powerful plugin engine:

import localspace, {
  ttlPlugin,
  compressionPlugin,
  encryptionPlugin,
} from 'localspace';

const store = localspace.createInstance({
  name: 'secure-store',
  plugins: [
    ttlPlugin({ defaultTTL: 60_000 }), // Auto-expire
    compressionPlugin({ threshold: 1024 }), // Compress > 1KB
    encryptionPlugin({ key: '32-byte-key-here' }), // Encrypt
  ],
  pluginErrorPolicy: 'strict', // Recommended for encryption
});

Built-in Plugins

| Plugin | Purpose | | --------------- | ---------------------------------------------------- | | TTL | Auto-expire items with { data, expiresAt } wrapper | | Encryption | AES-GCM encryption via Web Crypto API | | Compression | LZ-string compression for large values |

Cross-context replication is intentionally not built in. For best-effort single-item notifications, adapt examples/broadcast-notification-plugin.ts to your application-level synchronization protocol.

Serialized-size limits are also application policy rather than browser quota management. The non-published examples/size-limit-plugin.ts example shows a best-effort guard without automatic data eviction.

📖 Full Plugin Documentation: docs/plugins.md 📖 Real-World Examples: docs/examples.md


Configuration

const store = localspace.createInstance({
  // Database
  name: 'myapp', // Database name
  storeName: 'data', // Store name
  version: 1, // Schema version

  // Driver
  driver: [localspace.INDEXEDDB, localspace.LOCALSTORAGE],
  // Add localspace.MEMORY explicitly for runtime-only fallback.

  // IndexedDB performance
  durability: 'relaxed', // 'relaxed' (fast) or 'strict'
  prewarmTransactions: true, // Pre-warm connection

  // Batching
  maxBatchSize: 200, // Split large batches

  // Plugins
  plugins: [],
  pluginErrorPolicy: 'lenient', // 'strict' for encryption
});
// React Native one-step instance
import localspace from 'localspace';
import { createReactNativeInstance } from 'localspace/react-native';

const mobileStore = await createReactNativeInstance(localspace, {
  name: 'myapp',
  storeName: 'data',
  reactNativeAsyncStorage: AsyncStorage,
});

📖 Full Configuration Options: docs/api-reference.md#configuration-options


Performance Notes

  • Batch APIs outperform loops: setItems() ~6x faster, getItems() ~7.7x faster than per-item loops
  • Transaction helpers: runTransaction() is atomic on IndexedDB and rolls back on the memory driver
  • IndexedDB durability: Chrome 121+ uses relaxed durability by default
  • Non-transactional drivers: localStorage and React Native AsyncStorage reject runTransaction()

Troubleshooting

| Issue | Solution | | -------------------------- | ------------------------------------------------------ | | Driver not ready | Call await localspace.ready() before first operation | | Browser storage is full | Check error.code === 'QUOTA_EXCEEDED' | | Persistent storage blocked | Add localspace.MEMORY as an explicit fallback | | Plugin errors swallowed | Set pluginErrorPolicy: 'strict' |

Errors are LocalSpaceError with code, details, and cause properties.


Compatibility

  • Browsers: Modern Chromium/Edge, Firefox, Safari
  • Drivers: IndexedDB (primary), localStorage, memory (opt-in fallback)
  • React Native: AsyncStorage driver available via localspace/react-native opt-in entry
  • WebSQL: Not supported (migrate to IndexedDB)
  • Node/SSR: Custom driver required

Documentation

| Document | Description | | -------------------------------------------- | ------------------------------------- | | API Reference | Complete method documentation | | Plugin System | Built-in plugins & custom development | | Real-World Examples | Production-ready code patterns | | Migration Guide | Migrating from localForage and 1.x |


Roadmap

Complete ✅

  • [x] IndexedDB and localStorage drivers
  • [x] Opt-in memory fallback driver
  • [x] React Native AsyncStorage driver
  • [x] Familiar Promise-based key-value API
  • [x] TypeScript-first implementation
  • [x] Explicit batch operations
  • [x] Plugin system (TTL, Encryption, Compression)

Coming Soon

  • [ ] OPFS driver (Origin Private File System)
  • [ ] Node.js (File system, SQLite)
  • [ ] React Native SQLite driver
  • [ ] Deno (Native KV store)

License

MIT