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

haro

v17.0.3

Published

A fast, flexible immutable DataStore for collections of records with indexing, versioning, and advanced querying capabilities

Readme

Haro

npm version Node.js Version License Build Status

A fast, flexible immutable DataStore for collections of records with indexing, versioning, and advanced querying capabilities.

Table of Contents

Key Features

  • ⚡ Blazing Fast: O(1) indexed lookups - up to 500K ops/sec for instant data access
  • 📚 Built-in Versioning: Automatic change tracking without writing audit trail code
  • 🔒 Immutable Mode: Data safety with frozen objects - prevent accidental mutations
  • 🔍 Advanced Querying: Complex queries with find(), where(), search() - no manual filtering
  • 🎯 Deep Indexing: Query nested objects with dot notation (e.g., user.profile.department)
  • 🗄️ LRU Caching: Built-in cache for repeated queries with automatic invalidation
  • 📦 Batch Operations: Process thousands of records in milliseconds with setMany()/deleteMany()
  • 🛠️ Zero Boilerplate: No setup required - just instantiate and query
  • 📝 TypeScript Ready: Full type definitions included - no @types packages needed
  • 🎯 Zero Dependencies: Pure JavaScript, ~8KB gzipped - nothing extra to install

Why Choose Haro?

⏱️ Save Development Time

  • No more manual indexing: Define fields once, get instant O(1) lookups automatically
  • Built-in versioning: Track changes without writing audit trail code
  • Zero boilerplate: No setup, configuration, or initialization code needed
  • Instant queries: Complex filtering with one-liners instead of loops and conditionals

🚀 Performance Benefits

  • 500K+ ops/sec: Blazing fast indexed lookups for real-time applications
  • Automatic optimization: Indexes maintained automatically on every operation
  • Batch operations: Process 10,000 records in milliseconds
  • Memory efficient: Optimized data structures for minimal overhead

🛡️ Data Safety

  • Immutable mode: Prevent accidental mutations with frozen objects
  • Type safety: Full TypeScript support catches errors at compile time
  • Version history: Roll back to previous states when needed
  • Validation: Built-in checks prevent invalid data

Installation

npm

npm install haro

yarn

yarn add haro

pnpm

pnpm add haro

Quick Start

import { haro } from 'haro';

const store = haro([{ id: 1, name: 'Alice' }], { index: ['name'] });
console.log(store.find({ name: 'Alice' }));

Usage

Factory Function

import { haro } from 'haro';
const store = haro(data, config);

Class Constructor

import { Haro } from 'haro';

const store = new Haro({
  index: ['name', 'email', 'department'],
  key: 'id',
  versioning: true,
  immutable: true
});

const users = new Haro([
  { name: 'Alice', email: '[email protected]', department: 'Engineering' },
  { name: 'Bob', email: '[email protected]', department: 'Sales' }
], {
  index: ['name', 'department'],
  versioning: true
});

Class Inheritance

import { Haro } from 'haro';

class UserStore extends Haro {
  constructor(config) {
    super({
      index: ['email', 'department', 'role'],
      key: 'id',
      versioning: true,
      ...config
    });
  }

  isValidEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }
}

const store = new UserStore();
const user = store.set(null, { 
  name: 'John', 
  email: '[email protected]' 
});

Configuration Options

cache

Boolean - Enable LRU caching for search() and where() methods (default: false)

const store = haro(null, { cache: true });

cacheSize

Number - Maximum number of cached query results (default: 1000)

const store = haro(null, { cache: true, cacheSize: 500 });

delimiter

String - Delimiter for composite indexes (default: '|')

const store = haro(null, { delimiter: '::' });

id

String - Unique identifier for this store instance. Auto-generated if not provided.

const store = haro(null, { id: 'user-cache' });

immutable

Boolean - Return frozen/immutable objects for data safety (default: false)

const store = haro(null, { immutable: true });

index

Array - Fields to index for faster searches. Supports composite indexes.

const store = haro(null, {
  index: ['name', 'email', 'name|department', 'department|role']
});

key

String - Primary key field name (default: 'id')

const store = haro(null, { key: 'userId' });

versioning

Boolean - Enable version history tracking (default: false)

const store = haro(null, { versioning: true });

TypeScript Support

TypeScript definitions are included - no separate installation needed.

import { Haro } from 'haro';

const store = new Haro<{ name: string; age: number }>({
  index: ['name'],
  key: 'id'
});

Real-World Examples

⚡ Instant Setup - Zero Boilerplate

import { haro } from 'haro';

// One line to create indexed store
const users = haro(null, { index: ['email', 'name'] });

// Add data
users.set(null, { name: 'Alice', email: '[email protected]' });

// Instant lookup - O(1) performance
const user = users.find({ email: '[email protected]' });

Time saved: No manual index creation, no caching logic, no performance tuning.

Indexing and Queries

import { haro } from 'haro';

const products = haro(null, {
  index: ['category', 'brand', 'price', 'category|brand']
});

products.setMany([
  { sku: '1', name: 'Laptop', category: 'Electronics', brand: 'Apple', price: 2499 },
  { sku: '2', name: 'Phone', category: 'Electronics', brand: 'Apple', price: 999 },
  { sku: '3', name: 'Headphones', category: 'Electronics', brand: 'Sony', price: 299 }
]);

// Find by indexed field
const electronics = products.find({ category: 'Electronics' });

// Complex queries
const appleProducts = products.where({ 
  category: 'Electronics', 
  brand: 'Apple' 
}, '&&');

// Search with regex
const searchResults = products.search(/^Laptop$/, 'name');

// Filter with custom logic
const affordable = products.filter(p => p.price < 500);

// Sort and paginate
const sorted = products.sortBy('price');
const page1 = products.limit(0, 10);

Deep Indexing (Nested Paths)

import { haro } from 'haro';

const users = haro(null, {
  index: ['name', 'user.email', 'user.profile.department', 'user.email|user.profile.department']
});

users.setMany([
  { 
    id: '1', 
    name: 'Alice', 
    user: { 
      email: '[email protected]',
      profile: { department: 'Engineering' }
    } 
  },
  { 
    id: '2', 
    name: 'Bob', 
    user: { 
      email: '[email protected]',
      profile: { department: 'Sales' }
    } 
  }
]);

// Find by nested field
const alice = users.find({ 'user.email': '[email protected]' });

// Query by deeply nested field
const engineers = users.find({ 'user.profile.department': 'Engineering' });

// Composite index with nested fields
const aliceEng = users.find({
  'user.email': '[email protected]',
  'user.profile.department': 'Engineering'
});

// Works with where(), search(), and sortBy()
const results = await users.where({ 'user.profile.department': 'Engineering' });
const sorted = users.sortBy('user.profile.department');

Versioning

import { haro } from 'haro';

const config = haro(null, { versioning: true });

config.set('api.timeout', { value: 30000 });
config.set('api.timeout', { value: 45000 });
config.set('api.timeout', { value: 60000 });

// Access version history
const history = config.versions.get('api.timeout');
console.log(history); // [previous versions]

Immutable Mode

import { haro } from 'haro';

const store = haro(null, { immutable: true });

const user = store.set(null, { name: 'Alice', age: 30 });

// Attempting to modify will throw
try {
  user.age = 31; // TypeError: Cannot assign to read only property
} catch (error) {
  console.error(error.message);
}

Caching

import { haro } from 'haro';

const store = haro(null, { 
  index: ['name'],
  cache: true,
  cacheSize: 1000
});

store.set("user1", { id: "user1", name: "John" });

// First call - cache miss
const results1 = await store.where({ name: "John" });

// Second call - cache hit (much faster)
const results2 = await store.where({ name: "John" });

// Get cache statistics
console.log(store.getCacheStats()); // { hits: 1, misses: 1, sets: 1, ... }

// Clear cache manually
store.clearCache();

Comparison with Alternatives

| Feature | Haro | Map | Object | lowdb | LokiJS | |---------|------|-----|--------|-------|--------| | Indexing | ✅ Multi-field | ❌ | ❌ | ⚠️ Limited | ✅ | | Versioning | ✅ Built-in | ❌ | ❌ | ❌ | ⚠️ Plugins | | Immutable Mode | ✅ | ❌ | ❌ | ❌ | ❌ | | Advanced Queries | ✅ find/where/search | ❌ | ❌ | ⚠️ Basic | ✅ | | Batch Operations | ✅ setMany/deleteMany | ❌ | ❌ | ⚠️ Manual | ✅ | | Persistence | ❌ In-memory | ❌ | ❌ | ✅ JSON/Local | ✅ | | Performance (1k records) | ⚡ Fast | ⚡ Fastest | ⚡ Fast | 🐌 Slower | ⚡ Fast | | Memory Overhead | Medium | Low | Low | Medium | High | | TypeScript Support | ✅ | ✅ | ✅ | ✅ | ⚠️ Community | | Bundle Size | ~6KB gzipped | Native | Native | ~8KB | ~2.6MB | | Learning Curve | Low | Low | Low | Low | Medium |

Legend: ✅ Yes | ❌ No | ⚠️ Limited/Optional

When to Choose Each

  • Map: Simple key-value storage, maximum performance
  • Object: Basic data structures, JSON serialization
  • lowdb: Persistent JSON file storage, simple queries
  • LokiJS: Complex queries, large datasets, in-memory database needs
  • Haro: Indexed queries, versioning, immutable data, moderate datasets

API Reference

For complete API documentation with all methods and examples, see API.md.

Quick Overview:

  • Core Methods: set(), get(), delete(), has(), clear()
  • Query Methods: find(), where(), search(), filter(), sortBy(), limit()
  • Batch Operations: setMany(), deleteMany()
  • Utility Methods: toArray(), dump(), override()
  • Properties: size, registry

Troubleshooting

Common Issues

"Cannot read property 'length' of undefined"

Cause: Passing invalid data to find() or where().

Solution: Ensure query objects have valid field names that exist in your index.

// ❌ Wrong
store.find(undefined);

// ✅ Correct
store.find({ name: 'Alice' });

Performance degradation with large datasets

Cause: Too many indexes or complex queries on large collections.

Solution:

  • Limit indexes to frequently queried fields
  • Use limit() for pagination
  • Consider batch operations for bulk updates
// Optimize indexes
const store = haro(null, { 
  index: ['name', 'email'] // Only essential fields
});

// Use pagination
const results = store.limit(0, 100);

Version history growing unbounded

Cause: Versioning enabled with frequent updates.

Solution: Clear version history periodically or disable versioning if not needed.

// Clear specific version history
store.versions.delete('key123');

// Clear all versions
store.versions.clear();

// Disable versioning if not needed
const store = haro(null, { versioning: false });

Immutable mode causing errors

Cause: Attempting to modify frozen objects.

Solution: Use set() to update records instead of direct mutation.

// ❌ Wrong
const user = store.get('user123');
user.age = 31;

// ✅ Correct
store.set('user123', { age: 31 });

Index not being used for query

Cause: Querying non-indexed fields.

Solution: Add the field to the index configuration.

const store = haro(null, { 
  index: ['name', 'email', 'department'] 
});

Error Messages

| Error | Cause | Solution | |-------|-------|----------| | "Key field validation error" | Missing key field in data | Ensure key field exists in records | | "Index must be an array" | Invalid index configuration | Pass array to index option | | "Function required" | Invalid function parameter | Pass a function to filter() or map() | | "Invalid index name" | Sorting by non-indexed field | Add field to index or use sort() |

Getting Help

Testing

# Run unit tests
npm test

# Run with coverage
npm run coverage

# Run performance benchmarks
npm run benchmark

See CONTRIBUTING.md for detailed testing guidelines.

Benchmarks

Haro includes comprehensive benchmark suites for performance analysis.

Running Benchmarks

# Run all benchmarks
node benchmarks/index.js

# Run specific categories
node benchmarks/index.js --basic-only        # CRUD operations
node benchmarks/index.js --search-only       # Query operations
node benchmarks/index.js --index-only        # Index operations
node benchmarks/index.js --utilities-only    # Utility operations
node benchmarks/index.js --pagination-only   # Pagination benchmarks
node benchmarks/index.js --persistence-only  # Persistence benchmarks
node benchmarks/index.js --core-only         # Core benchmarks (basic, search, index)
node benchmarks/index.js --quiet             # Minimal output

Performance Overview

Haro provides excellent performance for in-memory data operations:

  • Indexed lookups: O(1) performance for find() operations
  • Batch operations: Efficient bulk data processing
  • Memory efficiency: Optimized data structures
  • Scalability: Consistent performance across different data sizes

Benchmark Results (5-run average)

| Operation | Latency (avg) | Throughput (ops/s) | |-----------|---------------|-------------------| | Basic Operations | | set() 10000 records | 1507 ms | 747 ops/s | | get() 10000 records | 35 ms | 29015 ops/s | | has() 10000 keys | 2 ms | 495325 ops/s | | delete() 10000 records | 2353 ms | 432 ops/s | | Search & Filter | | find() by indexed field 10000 records | 98 ms | 10299 ops/s | | where() by indexed field 10000 records | 222 ms | 4516 ops/s | | search() in index 10000 records | 116 ms | 8716 ops/s | | filter() all records 10000 records | 115 ms | 8822 ops/s | | Index Operations | | haro() with indexes 10000 records | 10842 ms | 94 ops/s | | find() with index 10000 records | 3996 ms | 267 ops/s | | reindex() single field 10000 records | 5739 ms | 182 ops/s | | Utility Operations | | toArray() 1000 iterations | 21 ms | 47750 ops/s | | entries() 1000 iterations | 92 ms | 10986 ops/s | | keys() 1000 iterations | 17 ms | 59043 ops/s | | values() 1000 iterations | 17 ms | 60553 ops/s | | Pagination | | limit() 10 10000 records | 15 ms | 69050 ops/s | | limit() 50 10000 records | 16 ms | 65516 ops/s | | limit() 100 10000 records | 17 ms | 61172 ops/s | | limit() with offset 10000 records | 16 ms | 65522 ops/s | | Persistence | | dump() records 5000 records | 75 ms | 13583 ops/s |

See benchmarks/README.md for complete benchmark documentation and detailed results.

Learn More

Community

License

Copyright (c) 2026 Jason Mulligan
Licensed under the BSD-3-Clause license.