https://github.com/maifeeulasad/idb-ts
Use IndexedDB with TypeScript in a declarative style
https://github.com/maifeeulasad/idb-ts
crud database database-management db idb indexed-db indexed-db-orm indexeddb indexeddb-api indexeddb-examples indexeddb-tools indexeddb-wrapper reflect reflect-metadata storage ts typescript typescript-library
Last synced: 8 days ago
JSON representation
Use IndexedDB with TypeScript in a declarative style
- Host: GitHub
- URL: https://github.com/maifeeulasad/idb-ts
- Owner: maifeeulasad
- License: mit
- Created: 2025-01-16T17:56:47.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-10-09T06:34:26.000Z (8 months ago)
- Last Synced: 2025-10-19T15:42:56.996Z (8 months ago)
- Topics: crud, database, database-management, db, idb, indexed-db, indexed-db-orm, indexeddb, indexeddb-api, indexeddb-examples, indexeddb-tools, indexeddb-wrapper, reflect, reflect-metadata, storage, ts, typescript, typescript-library
- Language: TypeScript
- Homepage: https://www.npmjs.com/package/idb-ts
- Size: 4.98 MB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# π idb-ts
## π Introduction
**idb-ts** is a lightweight, declarative, and type-safe way to work with IndexedDB using TypeScript. Effortlessly perform CRUD operations on your database with clean, structured code! π₯
## π¦ Installation
Install via npm and start using IndexedDB like a pro! β‘
```sh
npm i idb-ts # for pure npm users
pnpm add idb-ts # for pnpm users
yarn add idb-ts # for yarn users
```
## β¨ Features
- β
**Declarative & Type-Safe** - Define your data models with decorators.
- β‘ **Easy CRUD Operations** - Perform create, read, update, and delete seamlessly.
- π **Fully Typed API** - Benefit from TypeScriptβs powerful type system.
- ποΈ **Performance Optimized** - Minimal overhead with IndexedDB's native capabilities.
- π **Schema Versioning** - Manage database schema evolution with automatic migration support.
- π **Advanced Key Management** - Auto-increment, UUID, timestamp, custom generators, and composite keys.
---
## π Example Usage
### ποΈ Declaring Entities
Use decorators to define your data models. Each class must have exactly one `@KeyPath()` and be decorated with `@DataClass()`.
```typescript
import { Database, DataClass, KeyPath, Index } from "idb-ts";
@DataClass()
class User {
@KeyPath()
id!: string;
@Index()
email!: string;
name!: string;
age!: number;
constructor(id: string, name: string, age: number, email?: string) {
this.id = id;
this.name = name;
this.age = age;
this.email = email || `${name.toLowerCase()}@example.com`;
}
}
@DataClass()
class Location {
@KeyPath()
id!: string;
@Index()
city!: string;
country!: string;
constructor(id: string, city: string, country: string) {
this.id = id;
this.city = city;
this.country = country;
}
}
```
### π CRUD Operations
Perform database operations using the repository API:
```typescript
const db = await Database.build("idb-crud", [User, Location]);
const alice = new User("u1", "Alice", 25);
const bob = new User("u2", "Bob", 30);
const nyc = new Location("1", "New York", "USA");
const sf = new Location("2", "San Francisco", "USA");
await db.User.create(alice);
await db.User.create(bob);
await db.Location.create(nyc);
await db.Location.create(sf);
const readAlice = await db.User.read("u1");
console.log("π€ Read user:", readAlice);
alice.age = 26;
await db.User.update(alice);
const users = await db.User.list();
console.log("π All users:", users);
// Pagination
const page1 = await db.User.listPaginated(1, 2); // page 1, 2 users per page
console.log("π Page 1:", page1);
await db.User.delete("u1");
console.log("β User Alice deleted.");
const remainingUsers = await db.User.list();
console.log("π Remaining users:", remainingUsers);
const locations = await db.Location.list();
console.log("π All locations:", locations);
```
### π Indexing Support
Create indexes on fields for fast querying. Query indexes using the repository API:
```typescript
@DataClass()
class Product {
@KeyPath()
id!: string;
@Index()
category!: string;
@Index()
price!: number;
name!: string;
description!: string;
constructor(id: string, category: string, price: number, name: string, description: string) {
this.id = id;
this.category = category;
this.price = price;
this.name = name;
this.description = description;
}
}
const db = await Database.build("products-db", [Product]);
const electronics = await db.Product.findByIndex('category', 'Electronics');
const expensiveItems = await db.Product.findByIndex('price', 999.99);
const firstElectronic = await db.Product.findOneByIndex('category', 'Electronics');
```
#### Index Methods:
- `findByIndex(indexName, value): Promise` - Find all records matching the index value
- `findOneByIndex(indexName, value): Promise` - Find the first record matching the index value
### Creation & Update Timestamps
Each entity managed by `idb-ts` automatically gets two internal timestamp fields:
- `__idb_createdAt`: numeric epoch milliseconds set when the record is first created.
- `__idb_updatedAt`: numeric epoch milliseconds updated on each successful update.
These fields are applied automatically during `create` and `update` operations and can be used for auditing, sorting, or retention policies. They are stored as numbers (milliseconds since Unix epoch).
Example usage (reading timestamps):
```ts
const item = await db.MyEntity.read('key');
console.log(item.__idb_createdAt, item.__idb_updatedAt);
```
### Retention Policy & Cleanup Job
`idb-ts` supports per-entity data retention via the `@RetentionPolicy()` class decorator. It accepts the following options:
- `seconds` (required): number of seconds after which records are considered expired.
- `enabled` (optional, default `true`): whether cleanup is active for this entity.
- `field` (optional, default `__idb_createdAt`): the numeric field to use for age calculation (usually creation timestamp).
When one or more entities register retention policies, the library computes a single cleanup interval equal to the greatest common divisor (GCD) of all configured `seconds` values and runs a background cleanup job at that interval. On each tick the job scans the configured entity stores and deletes records whose `field` value is older than the configured retention window.
Example:
```ts
@RetentionPolicy({ seconds: 60 * 60 * 24 * 30 }) // 30 days
@DataClass()
class Session { /* ... */ }
// Database will run a periodic cleanup that removes sessions older than 30 days
```
Notes:
- Cleanup runs with readwrite transactions and deletes records one-by-one via cursors. It runs at startup and then periodically. Logs are emitted for inspection when debug logging is enabled.
- To temporarily disable cleanup for an entity, set `enabled: false` on the decorator.
### Field Validation
You can declare validation rules for individual properties using the `@Validate(predicate, message)` property decorator. Each rule must provide a predicate function that receives the property value and the full item and returns `true` when valid.
Validation is enforced on `create` and `update` operations. If any rule fails, the repository operation throws an error with a concise message describing the failing fields.
Example:
```ts
@DataClass()
class User {
@KeyPath()
id!: string;
@Validate((v) => typeof v === 'string' && v.includes('@'), 'must be a valid email')
email!: string;
@Validate((v) => typeof v === 'number' && v >= 0, 'age must be >= 0')
age!: number;
}
await db.User.create(new User('u1', 'alice@example.com', 30));
```
The thrown error contains all failing rules in the format `field: message` joined by `; `.
### Bulk Operations
Repositories include convenience bulk helpers for common batch operations:
- `createMany(items: T[])`: creates multiple items (runs validators and generators for each item).
- `updateMany(items: T[])`: updates multiple items.
- `deleteMany(keys: Array)`: deletes multiple keys.
These helpers are implemented by iterating the corresponding single-item operations. They are convenient for simple bulk workloads but are not currently implemented as a single atomic transaction across all items. For high-throughput or atomic requirements, consider batching items into a single transaction or performing multiple operations inside a custom `performOperation` call.
Example:
```ts
await db.User.createMany([alice, bob, charlie]);
await db.User.deleteMany(['u1', 'u2']);
```
Performance note: `createMany` will trigger validation and key generation per item. If you need large batch inserts frequently, batching these into a single transaction or adding a dedicated bulk API may improve throughput.
#### Error Handling
- If you query a non-existent index, an error is thrown:
```typescript
await db.Product.findByIndex('nonexistent', 'value'); // throws
```
---
## π Multi-Field & Composite Key Support
idb-ts provides flexible key management options including auto-increment keys, key generators, and composite keys for complex data relationships.
### Auto-Increment Keys
Perfect for entities where you want the database to automatically generate sequential IDs:
```typescript
@DataClass()
class Task {
@KeyPath({ autoIncrement: true })
id!: number;
title!: string;
completed!: boolean;
constructor(title: string, completed = false) {
this.title = title;
this.completed = completed;
}
}
const db = await Database.build("tasks-db", [Task]);
// IDs are automatically generated: 1, 2, 3, etc.
const task1 = await db.Task.create(new Task("Learn TypeScript"));
const task2 = await db.Task.create(new Task("Build amazing apps"));
console.log(task1.id); // 1
console.log(task2.id); // 2
```
### Key Generators
Generate keys automatically using built-in generators:
#### UUID Keys
```typescript
@DataClass()
class Document {
@KeyPath({ generator: 'uuid' })
uuid!: string;
@Index()
category!: string;
title!: string;
content!: string;
constructor(category: string, title: string, content: string) {
this.category = category;
this.title = title;
this.content = content;
}
}
const db = await Database.build("docs-db", [Document]);
const doc = await db.Document.create(new Document("tutorial", "Getting Started", "Welcome..."));
console.log(doc.uuid); // e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
```
#### Timestamp Keys
```typescript
@DataClass()
class Event {
@KeyPath({ generator: 'timestamp' })
timestamp!: number;
@Index()
type!: string;
data!: any;
constructor(type: string, data: any) {
this.type = type;
this.data = data;
}
}
const event = await db.Event.create(new Event("user_login", { userId: "123" }));
console.log(event.timestamp); // e.g., 1696118400000
```
#### Random Keys
```typescript
@DataClass()
class Session {
@KeyPath({ generator: 'random' })
sessionId!: string;
userId!: string;
expiresAt!: Date;
constructor(userId: string, expiresAt: Date) {
this.userId = userId;
this.expiresAt = expiresAt;
}
}
const session = await db.Session.create(new Session("user123", new Date()));
console.log(session.sessionId); // e.g., "xyz789abc123"
```
### Custom Key Generators
Create your own key generation logic:
```typescript
@DataClass()
class Invoice {
@KeyPath({ generator: (entity: any) => `INV-${entity.year}-${String(entity.number).padStart(4, '0')}` })
invoiceId!: string;
year!: number;
number!: number;
amount!: number;
constructor(year: number, number: number, amount: number) {
this.year = year;
this.number = number;
this.amount = amount;
}
}
const invoice = await db.Invoice.create(new Invoice(2024, 1, 1500.00));
console.log(invoice.invoiceId); // "INV-2024-0001"
```
### Composite Keys
Handle many-to-many relationships with composite keys using the `@CompositeKeyPath` decorator:
```typescript
import { CompositeKeyPath } from "idb-ts";
@CompositeKeyPath(['userId', 'projectId'])
@DataClass()
class UserProject {
userId!: string;
projectId!: string;
@Index()
role!: string;
joinedAt!: Date;
constructor(userId: string, projectId: string, role: string) {
this.userId = userId;
this.projectId = projectId;
this.role = role;
this.joinedAt = new Date();
}
}
const db = await Database.build("collaboration-db", [UserProject]);
// Create relationships
await db.UserProject.create(new UserProject("user123", "project456", "developer"));
await db.UserProject.create(new UserProject("user123", "project789", "admin"));
await db.UserProject.create(new UserProject("user456", "project456", "viewer"));
// Read with composite key
const relationship = await db.UserProject.read(['user123', 'project456']);
console.log(relationship?.role); // "developer"
// Update relationship
if (relationship) {
relationship.role = "maintainer";
await db.UserProject.update(relationship);
}
// Delete with composite key
await db.UserProject.delete(['user123', 'project789']);
// Query by role index
const developers = await db.UserProject.findByIndex('role', 'developer');
```
### Key Generation Utilities
Access key generators directly for your custom logic:
```typescript
import { KeyGenerators } from "idb-ts";
const uuid = KeyGenerators.uuid(); // Generate UUID
const timestamp = KeyGenerators.timestamp(); // Current timestamp
const random = KeyGenerators.random(); // Random string
```
---
## π Schema Versioning
idb-ts supports schema versioning to manage database evolution over time. Version your entities and let the library handle automatic migration!
### Basic Usage
```typescript
@DataClass({ version: 1 })
class User {
@KeyPath() id!: string;
@Index() email!: string;
name!: string;
}
@DataClass({ version: 2 })
class Post {
@KeyPath() id!: string;
@Index() authorId!: string;
title!: string;
content!: string;
}
@DataClass({ version: 3 })
class Comment {
@KeyPath() id!: string;
@Index() postId!: string;
@Index() authorId!: string;
text!: string;
}
// Database version will be 3 (highest entity version)
const db = await Database.build("blog", [User, Post, Comment]);
console.log(db.getDatabaseVersion()); // 3
console.log(db.getEntityVersions()); // Map with entity versions
```
### Key Features
- **Automatic Version Calculation**: Database version = highest entity version
- **Seamless Migration**: Only new/updated entities are processed during upgrades
- **Backward Compatibility**: Entities without version default to version 1
- **Index Evolution**: New indexes are automatically created during migration
### Version Management
```typescript
// Check versions
const dbVersion = db.getDatabaseVersion();
const entityVersions = db.getEntityVersions();
const userVersion = db.getEntityVersion('User');
// Version upgrade flow:
// v1.0: User(v1) -> Database v1
// v1.1: User(v1), Post(v2) -> Database v2
// v1.2: User(v1), Post(v2), Comment(v3) -> Database v3
```
---
## π Useful Links
- π **GitHub**: [maifeeulasad/idb-ts](https://github.com/maifeeulasad/idb-ts)
- π¦ **NPM**: [idb-ts](https://www.npmjs.com/package/idb-ts)
- Demo: https://maifeeulasad.github.io/idb-ts/
- Code Coverage report: https://maifeeulasad.github.io/idb-ts/coverage/lcov-report/
π **Enjoy seamless IndexedDB integration with TypeScript! Happy coding!** π