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 🙏

© 2025 – Pkg Stats / Ryan Hefner

json-obj-manager

v1.4.5

Published

Agnostic JSON data manager for Node.js and the browser.

Readme

JSON Object Manager

npm version License: GPL v3 TypeScript

A powerful, flexible TypeScript storage library that provides a unified API for data persistence across different environments. Works seamlessly in Node.js (file system), browsers (localStorage), and in-memory scenarios.

✨ Features

  • 🔄 Universal API: Same interface for Node.js, browser, and in-memory storage
  • 📝 TypeScript First: Full type safety and IntelliSense support
  • 🔌 Adapter Pattern: Easily extensible with custom storage adapters
  • 💬 Chat Memory: Built-in chat message management with persistence
  • 🗂️ String Maps: Specialized key-value storage for configuration
  • 📡 Event System: Real-time notifications for data changes
  • 🧪 Testing Ready: In-memory adapter perfect for unit tests
  • 🚀 Zero Dependencies: Lightweight and fast

📦 Installation

npm install json-obj-manager
yarn add json-obj-manager
pnpm add json-obj-manager

🚀 Quick Start

Node.js (File System)

import { DataStorage } from 'json-obj-manager';
import { JSONFileAdapter } from 'json-obj-manager/node';
import path from 'path';

// Define your data type
interface User {
  name: string;
  age: number;
  email: string;
}

// Create storage with type safety
const userStorage = new DataStorage<User>(
  new JSONFileAdapter(path.join(process.cwd(), 'users.json'))
);

// Save and load data
await userStorage.save('user-1', { 
  name: 'John Doe', 
  age: 30, 
  email: '[email protected]' 
});

const user = await userStorage.load('user-1');
console.log(user); // { name: 'John Doe', age: 30, email: '[email protected]' }

Browser (LocalStorage)

import { DataStorage } from 'json-obj-manager';
import { LocalStorageAdapter } from 'json-obj-manager/browser';

interface AppSettings {
  theme: 'light' | 'dark';
  language: string;
  notifications: boolean;
}

const settingsStorage = new DataStorage<AppSettings>(
  new LocalStorageAdapter('my-app-settings')
);

await settingsStorage.save('config', {
  theme: 'dark',
  language: 'en',
  notifications: true
});

const settings = await settingsStorage.load('config');

In-Memory (Testing)

import { DataStorage, InMemoryAdapter } from 'json-obj-manager';

// Perfect for unit tests
const testStorage = new DataStorage<any>(new InMemoryAdapter());

await testStorage.save('test-data', { value: 'test' });
const data = await testStorage.load('test-data');

Core Classes

DataStorage

Generic storage class that works with any adapter.

Constructor

new DataStorage<T>(adapter: StorageAdapter<T>)

Methods

| Method | Parameters | Returns | Description | |--------|------------|---------|-------------| | save | key: string, data: T | Promise<void> | Save data with a key | | load | key: string | Promise<T \| null> | Load data by key | | delete | key: string | Promise<void> | Delete data by key | | clear | - | Promise<void> | Clear all stored data | | getAll | - | Promise<Record<string, T>> | Get all stored data | | on | event: DataStorageEvents, listener: (data: T) => void | void | Register an event listener | | off | event: DataStorageEvents, listener: (data: T) => void | void | Remove an event listener | | setEmitMode | mode: EmitMode | void | Set emit mode (info, debug, error, none) |

StringMapStorage

Specialized storage for string key-value pairs.

Constructor

new StringMapStorage(adapter?: StorageAdapter<StringMap>)

Methods

| Method | Parameters | Returns | Description | |--------|------------|---------|-------------| | setValue | key: string, value: string | Promise<void> | Set a string value | | getValue | key: string | Promise<string \| undefined> | Get a string value | | removeKey | key: string | Promise<void> | Remove a key | | getAll | - | Promise<StringMap> | Get all key-value pairs |

ChatMemory & PersistentChatMemory

In-memory and persistent chat memory storage.

ChatMemory (In-Memory)

const memory = new ChatMemory();
memory.addUserMessage("Hello!");
memory.addAIMessage("Hi there!");
const messages = memory.getMessages();

PersistentChatMemory (Persistent)

const memory = new PersistentChatMemory(
  new LocalStorageAdapter<Message[]>('chat-history')
);
memory.addUserMessage("Hello!");

| Method | Parameters | Returns | Description | |--------|------------|---------|-------------| | addUserMessage | content: string, timestamp?: Date | void | Add user message | | addAIMessage | content: string, timestamp?: Date | void | Add AI message | | getMessages | - | Message[] | Get all messages | | getLastMessages | count?: number | Message[] | Get last N messages | | getMessagesSince | date: Date | Message[] | Get messages since date | | clear | - | void | Clear all messages | | getMessagesAsync | - | Promise<Message[]> | Async get messages (PersistentChatMemory) | | reload | - | Promise<void> | Reload from storage (PersistentChatMemory) |

Adapters

JSONFileAdapter (Node.js)

File system adapter using JSON files.

Constructor

new JSONFileAdapter<T>(filename: string)

Methods

| Method | Parameters | Returns | Description | |--------|------------|---------|-------------| | save | key: string, data: T | Promise<void> | Save data to file | | load | key: string | Promise<T \| null> | Load data from file | | delete | key: string | Promise<void> | Delete data from file | | clear | - | Promise<void> | Clear file contents | | getAll | - | Promise<Record<string, T>> | Get all data | | getFilePath | - | string | Get file path |

LocalStorageAdapter (Browser)

Browser adapter using localStorage.

Constructor

new LocalStorageAdapter<T>(storageKey?: string)

Methods

| Method | Parameters | Returns | Description | |--------|------------|---------|-------------| | save | key: string, data: T | Promise<void> | Save to localStorage | | load | key: string | Promise<T \| null> | Load from localStorage | | delete | key: string | Promise<void> | Delete from localStorage | | clear | - | Promise<void> | Clear all data | | getAll | - | Promise<Record<string, T>> | Get all data | | getStorageKey | - | string | Get storage key | | sync | - | void | Sync with localStorage | | destroy | - | void | Remove from localStorage |

InMemoryAdapter

Simple in-memory adapter for testing.

Types

Message

interface Message {
  role: 'user' | 'assistant';
  content: string;
  timestamp?: Date;
}

StringMap

type StringMap = Record<string, string>;

StorageAdapter

interface StorageAdapter<T> {
  save(key: string, data: T): Promise<void>;
  load(key: string): Promise<T | null>;
  delete(key: string): Promise<void>;
  clear(): Promise<void>;
}

📚 Examples

Basic CRUD Operations

import { DataStorage } from 'json-obj-manager';
import { LocalStorageAdapter } from 'json-obj-manager/browser';

interface User {
  id: string;
  name: string;
  age: number;
  preferences: {
    theme: string;
    language: string;
  };
}

const userStorage = new DataStorage<User>(new LocalStorageAdapter('users'));

// Create
await userStorage.save('user-1', {
  id: 'user-1',
  name: 'Alice Johnson',
  age: 25,
  preferences: {
    theme: 'dark',
    language: 'en'
  }
});

// Read
const user = await userStorage.load('user-1');
console.log(user?.name); // 'Alice Johnson'

// Update
if (user) {
  user.age = 26;
  await userStorage.save('user-1', user);
}

// Delete
await userStorage.delete('user-1');

// Get all users
const allUsers = await userStorage.getAll();
console.log(Object.keys(allUsers)); // ['user-2', 'user-3', ...]

Chat Application with Persistence

import { PersistentChatMemory, createMemory } from 'json-obj-manager';
import { JSONFileAdapter } from 'json-obj-manager/node';
import { Message } from 'json-obj-manager';

// Persistent chat (saves to file)
const persistentChat = new PersistentChatMemory(
  new JSONFileAdapter('./chat-history.json')
);

// In-memory chat (for temporary sessions)
const sessionChat = createMemory();

// Add messages with timestamps
persistentChat.addUserMessage("What's the weather like?", new Date());
persistentChat.addAIMessage("It's sunny and 75°F today.", new Date());
persistentChat.addUserMessage("Great! Any chance of rain?");
persistentChat.addAIMessage("No rain expected for the next 3 days.");

// Get recent conversation
const lastMessages = persistentChat.getLastMessages(3);
console.log(lastMessages.map(m => `${m.role}: ${m.content}`));

// Get messages since yesterday
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
const recentMessages = persistentChat.getMessagesSince(yesterday);

// Auto-save feature
persistentChat.setAutoSave(true); // Automatically saves after each message

Configuration Management

import { StringMapStorage, createStringMap } from 'json-obj-manager';
import { LocalStorageAdapter } from 'json-obj-manager/browser';

// Browser configuration
const appConfig = new StringMapStorage(
  new LocalStorageAdapter('app-config')
);

// Set configuration values
await appConfig.setValue('api_endpoint', 'https://api.example.com');
await appConfig.setValue('max_retries', '3');
await appConfig.setValue('timeout', '5000');
await appConfig.setValue('debug_mode', 'false');

// Get configuration
const apiEndpoint = await appConfig.getValue('api_endpoint');
const maxRetries = parseInt(await appConfig.getValue('max_retries') || '1');
const isDebug = (await appConfig.getValue('debug_mode')) === 'true';

// Get all configuration
const allConfig = await appConfig.getAll();
console.log('Current configuration:', allConfig);

// Simple in-memory config (no persistence)
const tempConfig = createStringMap();
await tempConfig.setValue('session_id', 'abc123');

Event-Driven Storage

import { DataStorage } from 'json-obj-manager';
import { JSONFileAdapter } from 'json-obj-manager/node';

const storage = new DataStorage(new JSONFileAdapter('./data.json'));

// Enable event emissions
storage.setEmitMode('info');

// Listen to storage events
storage.on('save', (data) => {
  console.log('Data saved:', data);
});

storage.on('load', (data) => {
  console.log('Data loaded:', data);
});

storage.on('delete', (data) => {
  console.log('Data deleted:', data);
});

storage.on('clear', () => {
  console.log('Storage cleared');
});

// These operations will trigger events
await storage.save('item-1', { name: 'Test Item' });
const item = await storage.load('item-1');
await storage.delete('item-1');

Custom Adapter Implementation

import { StorageAdapter } from 'json-obj-manager';

// Example: Redis adapter
class RedisAdapter<T> implements StorageAdapter<T> {
  constructor(private redisClient: any, private keyPrefix: string = '') {}

  async save(key: string, data: T): Promise<void> {
    const fullKey = `${this.keyPrefix}${key}`;
    await this.redisClient.set(fullKey, JSON.stringify(data));
  }

  async load(key: string): Promise<T | null> {
    const fullKey = `${this.keyPrefix}${key}`;
    const data = await this.redisClient.get(fullKey);
    return data ? JSON.parse(data) : null;
  }

  async delete(key: string): Promise<void> {
    const fullKey = `${this.keyPrefix}${key}`;
    await this.redisClient.del(fullKey);
  }

  async clear(): Promise<void> {
    const keys = await this.redisClient.keys(`${this.keyPrefix}*`);
    if (keys.length > 0) {
      await this.redisClient.del(...keys);
    }
  }

  async getAll(): Promise<Record<string, T>> {
    const keys = await this.redisClient.keys(`${this.keyPrefix}*`);
    const result: Record<string, T> = {};
    
    for (const fullKey of keys) {
      const key = fullKey.replace(this.keyPrefix, '');
      const data = await this.redisClient.get(fullKey);
      if (data) {
        result[key] = JSON.parse(data);
      }
    }
    
    return result;
  }
}

// Usage
const redisStorage = new DataStorage(new RedisAdapter(redisClient, 'myapp:'));

🧪 Testing

The library includes comprehensive Jest tests. Run them with:

# Run all tests
npm test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:coverage

Testing Your Code

Use the InMemoryAdapter for testing your applications:

import { DataStorage, InMemoryAdapter } from 'json-obj-manager';

describe('User Service', () => {
  let userStorage: DataStorage<User>;

  beforeEach(() => {
    // Fresh storage for each test
    userStorage = new DataStorage(new InMemoryAdapter());
  });

  test('should create user', async () => {
    const user = { name: 'Test User', age: 25 };
    await userStorage.save('user-1', user);
    
    const savedUser = await userStorage.load('user-1');
    expect(savedUser).toEqual(user);
  });
});

🏗️ Architecture

Adapter Pattern

The library uses the adapter pattern to provide a consistent interface across different storage backends:

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   Your App      │───▶│   DataStorage    │───▶│  StorageAdapter │
└─────────────────┘    └──────────────────┘    └─────────────────┘
                                                         │
                        ┌────────────────────────────────┼────────────────────────────────┐
                        │                                │                                │
                        ▼                                ▼                                ▼
                ┌─────────────────┐              ┌─────────────────┐              ┌─────────────────┐
                │ JSONFileAdapter │              │LocalStorageAdapter│            │ InMemoryAdapter │
                │   (Node.js)     │              │   (Browser)     │              │   (Testing)     │
                └─────────────────┘              └─────────────────┘              └─────────────────┘

Type Safety

Full TypeScript support ensures type safety across all operations:

interface Product {
  id: string;
  name: string;
  price: number;
  inStock: boolean;
}

const productStorage = new DataStorage<Product>(adapter);

// ✅ Type-safe operations
await productStorage.save('prod-1', {
  id: 'prod-1',
  name: 'Laptop',
  price: 999.99,
  inStock: true
});

// ✅ IntelliSense support
const product = await productStorage.load('prod-1');
if (product) {
  console.log(product.name); // TypeScript knows this is a string
  console.log(product.price); // TypeScript knows this is a number
}

🔧 Best Practices

1. Use Type Definitions

Always define interfaces for your data:

// ✅ Good
interface User {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
}

const userStorage = new DataStorage<User>(adapter);

// ❌ Avoid
const userStorage = new DataStorage<any>(adapter);

2. Handle Errors Gracefully

try {
  const user = await userStorage.load('user-1');
  if (!user) {
    console.log('User not found');
    return;
  }
  // Process user...
} catch (error) {
  console.error('Failed to load user:', error);
}

3. Use Events for Monitoring

storage.setEmitMode('info');
storage.on('save', (data) => {
  console.log('Data saved:', Object.keys(data).length, 'items');
});

4. Organize Storage by Domain

// Separate storage instances for different data types
const userStorage = new DataStorage<User>(new JSONFileAdapter('./data/users.json'));
const productStorage = new DataStorage<Product>(new JSONFileAdapter('./data/products.json'));
const orderStorage = new DataStorage<Order>(new JSONFileAdapter('./data/orders.json'));

5. Use Appropriate Adapters

  • JSONFileAdapter: Server-side applications, desktop apps
  • LocalStorageAdapter: Browser applications, client-side storage
  • InMemoryAdapter: Testing, temporary data, caching

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Development Setup

# Clone the repository
git clone https://github.com/nglmercer/json-obj-manager.git
cd json-obj-manager

# Install dependencies
npm install

# Run tests
npm test

# Build the project
npm run build

📄 License

This project is licensed under the GPL-3.0 License - see the LICENSE file for details.

🙏 Acknowledgments

  • Built with TypeScript for maximum developer experience
  • Inspired by the need for universal storage solutions
  • Thanks to all contributors and users of this library

Made with ❤️ by memelser