A lightweight Persisted In-Memory Database written in TypeScript.
A lightweight, persisted in-memory database built from the ground up for the browser. PimDB delivers fast and efficient text indexing with substring, n-gram, and sorted indexes, enabling quick lookups for both partial and exact matches. On a dataset of 100,000 documents, it's currently 4,000x+ faster than Array.filter
for sorted lookups and 700x+ faster for substring searches.
- 🚀 Lightweight and fast
- 📦 Zero dependencies
- 💪 TypeScript support
- 🔒 Type-safe operations
- 🛠️ Simple API
- 🔍 Pluggable indexes
- 🔄 Reactivity (React hook coming soon)
PimDB is published on npmjs.com.
# Using bun
bun add pimdb
# Using pnpm
pnpm add pimdb
# Using npm
npm install pimdb
# Using yarn
yarn add pimdb
// db.ts
import {
createPimDB,
PimCollection,
PimPrimaryIndex,
PimSortedIndex,
PimSubstringIndex,
} from "pimdb";
interface User {
id: string;
name: string;
age: number;
}
interface Post {
id: string;
title: string;
content: string;
isPublished?: boolean;
}
// Define user indexes
const userIndexes = {
primary: new PimPrimaryIndex<User>(),
byName: new PimSortedIndex<User>("name"),
nameSearch: new PimSubstringIndex<User>("name"),
};
// Define post indexes
const postIndexes = {
primary: new PimPrimaryIndex<Post>(),
byTitle: new PimSortedIndex<Post>("title"),
titleSearch: new PimSubstringIndex<Post>("title"),
};
// Create and export database with collections
export const db = createPimDB({
users: new PimCollection<User, typeof userIndexes>(userIndexes),
posts: new PimCollection<Post, typeof postIndexes>(postIndexes),
});
import { db } from "./db";
// Insert data
db.users.insert({
id: "1",
name: "Alice",
age: 30,
});
db.posts.insert({
id: "1",
title: "Hello, world!",
content: "Welcome to the universe.",
isPublished: true,
});
// All read operations are performed directly on the indexes
const user = db.users.indexes.primary.get("1");
const aliceUsers = db.users.indexes.byName.find("Alice");
const searchResults = db.users.indexes.nameSearch.search("li");
const thirtyPlus = db.users.indexes.byAge.findInRange({ gte: 30 });
PimDB comes with three index types to optimize your data queries.
const primaryIndex = new PimPrimaryIndex<User>();
- Unique index, mandatory for each collection
- Supports retrieving single documents or all documents in the collection
- Provides O(1) performance for lookups by document ID
const sortedIndex = new PimSortedIndex<User>("name");
- Enables efficient exact matches and range queries (case-sensitive)
- Maintains documents sorted by a specified field, with document ID as a tie-breaker for consistent result ordering
- Provides O(log n) performance for lookups
const substringIndex = new PimSubstringIndex<User>("name");
- Optimized for real-time search and partial text matching
- Supports case-insensitive substring searches within text fields
- Provides O(1) performance for partial matches
- Coming soon.
Create your own indexes by implementing the PimIndex
interface.
interface PimIndex<T> {
insert(item: T): boolean;
update(item: T): boolean;
delete(item: T): boolean;
}
export class MyIndex<T extends BaseDocument> implements PimIndex<T> {
/**
* Insert a document into the index.
*
* Returns true if the document was updated, false if it was not found.
*/
insert(doc: T): boolean {
// Implement me
return false;
}
/**
* Update a document in the index.
*
* Returns true if the document was updated, false if it was not found.
*/
update(doc: T): boolean {
// Implement me
return false;
}
/**
* Delete a document from the index.
*
* Returns true if the document was deleted, false if it was not found.
*/
delete(doc: T): boolean {
// Implement me
return false;
}
/**
* Implement your query methods here.
*/
myQuery(id: T["id"]): T | undefined {
// Implement me
return undefined;
}
}
Initial benchmarks were conducted on a MacBook Pro M1 Max with 64 GB RAM.
Setup: 100,000 documents with a name
field.
Name | Hz | Min | Max | Mean | P75 | P99 | P995 | P999 | RME | Samples |
---|---|---|---|---|---|---|---|---|---|---|
array.filter |
1,593.88 | 0.5000 | 1.1000 | 0.6274 | 0.7000 | 0.9000 | 1.0000 | 1.1000 | ±0.86% | 1000 |
sorted.find |
3,482,412.00 | 0.0000 | 3.8000 | 0.0003 | 0.0000 | 0.0000 | 0.0000 | 0.1000 | ±3.13% | 1741206 |
Summary: 2184.87x faster than native Array.filter().
Setup: 100,000 documents with a title
field.
Name | Hz | Min | Max | Mean | P75 | P99 | P995 | P999 | RME | Samples | Notes |
---|---|---|---|---|---|---|---|---|---|---|---|
array.filter |
183.81 | 4.8000 | 7.2000 | 5.4404 | 5.6000 | 6.5000 | 6.8000 | 7.0000 | ±0.43% | 1000 | |
substring.search |
151,486.00 | 0.0000 | 0.3000 | 0.0066 | 0.0000 | 0.1000 | 0.1000 | 0.1000 | ±2.72% | 75743 | Fastest |
Summary: 824.14x faster than native Array.filter().
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a pull request
This project is open-source and available under the MIT License. Feel free to use it in your projects!
Authored and maintained by Mikael Lirbank (@lirbank).
If you find this project helpful, consider giving it a ⭐️ on GitHub!
I'm an experienced developer passionate about building performant and elegant solutions. Currently open to new consulting projects or full-time opportunities. Visit lirbank.com to connect.