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

@fimbul-works/storage

v1.3.1

Published

Type-safe storage abstraction for TypeScript with unified CRUD interface

Readme

@fimbul-works/storage

A type-safe, abstract storage system for TypeScript with a unified interface for CRUD operations across multiple backends.

Features

  • 🔷 Type-safe — Full TypeScript support with generics
  • 🗄️ Multiple Backends — In-memory, file-based, Redis, and layered storage
  • 🔌 Custom Serialization — Pluggable adapters for different data formats
  • 📊 Efficient Data Access — Stream large datasets or retrieve all keys without loading entries
  • ⚠️ Error Handling — Specific error types for duplicate keys and missing entries

Installation

npm install @fimbul-works/storage

For Redis support:

npm install redis

Quick Start

import { createMemoryStorage } from '@fimbul-works/storage';

interface User {
  id: string;
  name: string;
  email: string;
}

const storage = createMemoryStorage<User, 'id'>('id');

await storage.create({ id: '1', name: 'John Doe', email: '[email protected]' });
const user = await storage.get('1');
await storage.update({ id: '1', name: 'John Updated', email: '[email protected]' });

// For small datasets
const allUsers = await storage.getAll();

// For large datasets, use streaming
for await (const user of storage.streamAll()) {
  console.log(user.name);
}

await storage.delete('1');

Storage Backends

In-Memory Storage

Fast storage using JavaScript's Map. Perfect for testing or temporary data.

const storage = createMemoryStorage<User, 'id'>('id');

File-Based Storage

Persistent storage using the filesystem. Each entity is stored as a separate file.

import { createFileStorage } from '@fimbul-works/storage';

const storage = createFileStorage<User, 'id'>('id', { path: './data/users' });
await storage.create({ id: '1', name: 'John', email: '[email protected]' });
// Creates: ./data/users/1.json

Redis Storage

Distributed storage with automatic connection management.

import { createRedisStorage } from '@fimbul-works/storage/redis';

const storage = await createRedisStorage<User, 'id'>('id', {
  url: 'redis://localhost:6379',
  keyPrefix: 'users:',
});

await storage.create({ id: '1', name: 'John', email: '[email protected]' });

// Close connection when done
storage.close();

Layered Storage (Caching)

Combine backends for cache-aside patterns. Layers are ordered top to bottom (fastest first).

import { createLayeredStorage, createMemoryStorage, createFileStorage } from '@fimbul-works/storage';

const cache = createMemoryStorage<User, 'id'>('id');
const persistent = createFileStorage<User, 'id'>('id', { path: './data/users' });
const storage = createLayeredStorage([cache, persistent]);

// Reads check layers top-down (cache first)
const user = await storage.get('1');

// Writes persist to all layers
await storage.create({ id: '2', name: 'Jane', email: '[email protected]' });

All layers must share the same key field, which is automatically determined from the first layer.

Layer behavior:

  • exists/get: Check layers top-down, return first match
  • create/update: Write to all layers
  • delete: Remove from all layers that have the key
  • getAll/streamAll/getKeys: Merge all layers (top layer wins for duplicates)

API Reference

All storage implementations implement the Storage<T, K> interface:

| Property/Method | Description | Type/Returns | |----------------|-------------|--------------| | keyField | Read-only field indicating which property is used as the key | K | | exists(key) | Check if entry exists | Promise<boolean> | | create(entry) | Create new entry | Promise<void> | | get(key) | Retrieve entry by key | Promise<T \| null> | | getAll() | Retrieve all entries | Promise<T[]> | | getKeys() | Retrieve all keys | Promise<T[K][]> | | streamAll() | Stream all entries | AsyncIterableIterator<T> | | update(entry) | Update existing entry | Promise<void> | | delete(key) | Delete entry | Promise<void> |

Error Types

  • DuplicateKeyError: Thrown when creating an entry with an existing key
  • KeyNotFoundError: Thrown when updating/deleting a non-existent entry

Advanced Usage

Streaming Large Datasets

For large datasets, use streamAll() to process entries efficiently without loading everything into memory:

const storage = createFileStorage<User, 'id'>('id', { path: './data/users' });

// Process users one at a time
for await (const user of storage.streamAll()) {
  console.log(`Processing: ${user.name}`);
  // Send to API, perform calculations, etc.
}

// Early termination - stop after finding what you need
for await (const user of storage.streamAll()) {
  if (user.email === '[email protected]') {
    console.log('Found target user!');
    break; // Stops iteration, saves resources
  }
}

Working with Keys

Sometimes you only need the keys without loading the full entries:

const storage = createFileStorage<User, 'id'>('id', { path: './data/users' });

// Get all user IDs
const userIds = await storage.getKeys();
console.log(`Found ${userIds.length} users`);

Key Type Coercion

File and Redis storage store keys as strings, but your application might use numbers or other types. Use keyFromStorage to convert keys back to your application type:

interface User {
  id: number;  // Application uses numbers
  name: string;
}

// File storage with number keys
const storage = createFileStorage<User, 'id'>('id', {
  path: './data/users',
  keyFromStorage: (raw) => Number.parseInt(raw, 10),
});

await storage.create({ id: 123, name: 'John' });
const keys = await storage.getKeys();  // Returns [123] as number[]

// Redis storage with number keys
const redisStorage = await createRedisStorage<User, 'id'>('id', {
  url: 'redis://localhost:6379',
  keyFromStorage: (raw) => Number.parseInt(raw, 10),
});

await redisStorage.create({ id: 456, name: 'Jane' });
const redisKeys = await redisStorage.getKeys();  // Returns [456] as number[]
redisStorage.close();

Custom Serialization

Create custom serialization adapters for different data formats:

import { createFileStorage, type FileAdapter } from '@fimbul-works/storage';

const csvAdapter: FileAdapter<User, 'id'> = {
  encoding: 'utf-8',
  fileName: (key) => `user_${key}.csv`,
  serialize: (user) => `${user.id},${user.name},${user.email}`,
  deserialize: (str) => {
    const [id, name, email] = str.split(',');
    return { id, name, email };
  },
};

const storage = createFileStorage('id', { path: './data/users', adapter: csvAdapter });

Different Key Fields

Use any field as the unique key:

interface Product {
  sku: string;
  name: string;
  price: number;
}

const storage = createMemoryStorage<Product, 'sku'>('sku');
await storage.create({ sku: 'ABC123', name: 'Widget', price: 9.99 });

License

MIT License - See LICENSE file for details.


Built with 📦 by FimbulWorks