idb-ts 3.10.0 β 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +263 -44
- package/lib/index.d.ts +121 -8
- package/lib/index.esm.js +792 -190
- package/lib/index.js +792 -190
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -24,17 +24,22 @@
|
|
|
24
24
|
</a>
|
|
25
25
|
</p>
|
|
26
26
|
|
|
27
|
-
|
|
28
27
|
## π Introduction
|
|
28
|
+
|
|
29
29
|
**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! π₯
|
|
30
30
|
|
|
31
31
|
## π¦ Installation
|
|
32
|
+
|
|
32
33
|
Install via npm and start using IndexedDB like a pro! β‘
|
|
34
|
+
|
|
33
35
|
```sh
|
|
34
|
-
npm i idb-ts
|
|
36
|
+
npm i idb-ts # for pure npm users
|
|
37
|
+
pnpm add idb-ts # for pnpm users
|
|
38
|
+
yarn add idb-ts # for yarn users
|
|
35
39
|
```
|
|
36
40
|
|
|
37
41
|
## β¨ Features
|
|
42
|
+
|
|
38
43
|
- β
**Declarative & Type-Safe** - Define your data models with decorators.
|
|
39
44
|
- β‘ **Easy CRUD Operations** - Perform create, read, update, and delete seamlessly.
|
|
40
45
|
- π **Fully Typed API** - Benefit from TypeScriptβs powerful type system.
|
|
@@ -47,10 +52,11 @@ npm i idb-ts
|
|
|
47
52
|
## π Example Usage
|
|
48
53
|
|
|
49
54
|
### ποΈ Declaring Entities
|
|
55
|
+
|
|
50
56
|
Use decorators to define your data models. Each class must have exactly one `@KeyPath()` and be decorated with `@DataClass()`.
|
|
51
57
|
|
|
52
58
|
```typescript
|
|
53
|
-
import { Database, DataClass, KeyPath, Index } from
|
|
59
|
+
import { Database, DataClass, KeyPath, Index } from 'idb-ts';
|
|
54
60
|
|
|
55
61
|
@DataClass()
|
|
56
62
|
class User {
|
|
@@ -90,45 +96,47 @@ class Location {
|
|
|
90
96
|
```
|
|
91
97
|
|
|
92
98
|
### π CRUD Operations
|
|
99
|
+
|
|
93
100
|
Perform database operations using the repository API:
|
|
94
101
|
|
|
95
102
|
```typescript
|
|
96
|
-
const db = await Database.build(
|
|
103
|
+
const db = await Database.build('idb-crud', [User, Location]);
|
|
97
104
|
|
|
98
|
-
const alice = new User(
|
|
99
|
-
const bob = new User(
|
|
100
|
-
const nyc = new Location(
|
|
101
|
-
const sf = new Location(
|
|
105
|
+
const alice = new User('u1', 'Alice', 25);
|
|
106
|
+
const bob = new User('u2', 'Bob', 30);
|
|
107
|
+
const nyc = new Location('1', 'New York', 'USA');
|
|
108
|
+
const sf = new Location('2', 'San Francisco', 'USA');
|
|
102
109
|
|
|
103
110
|
await db.User.create(alice);
|
|
104
111
|
await db.User.create(bob);
|
|
105
112
|
await db.Location.create(nyc);
|
|
106
113
|
await db.Location.create(sf);
|
|
107
114
|
|
|
108
|
-
const readAlice = await db.User.read(
|
|
109
|
-
console.log(
|
|
115
|
+
const readAlice = await db.User.read('u1');
|
|
116
|
+
console.log('π€ Read user:', readAlice);
|
|
110
117
|
|
|
111
118
|
alice.age = 26;
|
|
112
119
|
await db.User.update(alice);
|
|
113
120
|
|
|
114
121
|
const users = await db.User.list();
|
|
115
|
-
console.log(
|
|
122
|
+
console.log('π All users:', users);
|
|
116
123
|
|
|
117
124
|
// Pagination
|
|
118
125
|
const page1 = await db.User.listPaginated(1, 2); // page 1, 2 users per page
|
|
119
|
-
console.log(
|
|
126
|
+
console.log('π Page 1:', page1);
|
|
120
127
|
|
|
121
|
-
await db.User.delete(
|
|
122
|
-
console.log(
|
|
128
|
+
await db.User.delete('u1');
|
|
129
|
+
console.log('β User Alice deleted.');
|
|
123
130
|
|
|
124
131
|
const remainingUsers = await db.User.list();
|
|
125
|
-
console.log(
|
|
132
|
+
console.log('π Remaining users:', remainingUsers);
|
|
126
133
|
|
|
127
134
|
const locations = await db.Location.list();
|
|
128
|
-
console.log(
|
|
135
|
+
console.log('π All locations:', locations);
|
|
129
136
|
```
|
|
130
137
|
|
|
131
138
|
### π Indexing Support
|
|
139
|
+
|
|
132
140
|
Create indexes on fields for fast querying. Query indexes using the repository API:
|
|
133
141
|
|
|
134
142
|
```typescript
|
|
@@ -146,7 +154,13 @@ class Product {
|
|
|
146
154
|
name!: string;
|
|
147
155
|
description!: string;
|
|
148
156
|
|
|
149
|
-
constructor(
|
|
157
|
+
constructor(
|
|
158
|
+
id: string,
|
|
159
|
+
category: string,
|
|
160
|
+
price: number,
|
|
161
|
+
name: string,
|
|
162
|
+
description: string,
|
|
163
|
+
) {
|
|
150
164
|
this.id = id;
|
|
151
165
|
this.category = category;
|
|
152
166
|
this.price = price;
|
|
@@ -155,18 +169,114 @@ class Product {
|
|
|
155
169
|
}
|
|
156
170
|
}
|
|
157
171
|
|
|
158
|
-
const db = await Database.build(
|
|
172
|
+
const db = await Database.build('products-db', [Product]);
|
|
159
173
|
|
|
160
174
|
const electronics = await db.Product.findByIndex('category', 'Electronics');
|
|
161
175
|
const expensiveItems = await db.Product.findByIndex('price', 999.99);
|
|
162
|
-
const firstElectronic = await db.Product.findOneByIndex(
|
|
176
|
+
const firstElectronic = await db.Product.findOneByIndex(
|
|
177
|
+
'category',
|
|
178
|
+
'Electronics',
|
|
179
|
+
);
|
|
163
180
|
```
|
|
164
181
|
|
|
165
182
|
#### Index Methods:
|
|
183
|
+
|
|
166
184
|
- `findByIndex(indexName, value): Promise<T[]>` - Find all records matching the index value
|
|
167
185
|
- `findOneByIndex(indexName, value): Promise<T | undefined>` - Find the first record matching the index value
|
|
168
186
|
|
|
187
|
+
### Creation & Update Timestamps
|
|
188
|
+
|
|
189
|
+
Each entity managed by `idb-ts` automatically gets two internal timestamp fields:
|
|
190
|
+
|
|
191
|
+
- `__idb_createdAt`: numeric epoch milliseconds set when the record is first created.
|
|
192
|
+
- `__idb_updatedAt`: numeric epoch milliseconds updated on each successful update.
|
|
193
|
+
|
|
194
|
+
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).
|
|
195
|
+
|
|
196
|
+
Example usage (reading timestamps):
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
const item = await db.MyEntity.read('key');
|
|
200
|
+
console.log(item.__idb_createdAt, item.__idb_updatedAt);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Retention Policy & Cleanup Job
|
|
204
|
+
|
|
205
|
+
`idb-ts` supports per-entity data retention via the `@RetentionPolicy()` class decorator. It accepts the following options:
|
|
206
|
+
|
|
207
|
+
- `seconds` (required): number of seconds after which records are considered expired.
|
|
208
|
+
- `enabled` (optional, default `true`): whether cleanup is active for this entity.
|
|
209
|
+
- `field` (optional, default `__idb_createdAt`): the numeric field to use for age calculation (usually creation timestamp).
|
|
210
|
+
|
|
211
|
+
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.
|
|
212
|
+
|
|
213
|
+
Example:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
@RetentionPolicy({ seconds: 60 * 60 * 24 * 30 }) // 30 days
|
|
217
|
+
@DataClass()
|
|
218
|
+
class Session {
|
|
219
|
+
/* ... */
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Database will run a periodic cleanup that removes sessions older than 30 days
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Notes:
|
|
226
|
+
|
|
227
|
+
- 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.
|
|
228
|
+
- To temporarily disable cleanup for an entity, set `enabled: false` on the decorator.
|
|
229
|
+
|
|
230
|
+
### Field Validation
|
|
231
|
+
|
|
232
|
+
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.
|
|
233
|
+
|
|
234
|
+
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.
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
@DataClass()
|
|
240
|
+
class User {
|
|
241
|
+
@KeyPath()
|
|
242
|
+
id!: string;
|
|
243
|
+
|
|
244
|
+
@Validate(
|
|
245
|
+
(v) => typeof v === 'string' && v.includes('@'),
|
|
246
|
+
'must be a valid email',
|
|
247
|
+
)
|
|
248
|
+
email!: string;
|
|
249
|
+
|
|
250
|
+
@Validate((v) => typeof v === 'number' && v >= 0, 'age must be >= 0')
|
|
251
|
+
age!: number;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
await db.User.create(new User('u1', 'alice@example.com', 30));
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The thrown error contains all failing rules in the format `field: message` joined by `; `.
|
|
258
|
+
|
|
259
|
+
### Bulk Operations
|
|
260
|
+
|
|
261
|
+
Repositories include convenience bulk helpers for common batch operations:
|
|
262
|
+
|
|
263
|
+
- `createMany(items: T[])`: creates multiple items (runs validators and generators for each item).
|
|
264
|
+
- `updateMany(items: T[])`: updates multiple items.
|
|
265
|
+
- `deleteMany(keys: Array<string | string[] | number>)`: deletes multiple keys.
|
|
266
|
+
|
|
267
|
+
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.
|
|
268
|
+
|
|
269
|
+
Example:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
await db.User.createMany([alice, bob, charlie]);
|
|
273
|
+
await db.User.deleteMany(['u1', 'u2']);
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
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.
|
|
277
|
+
|
|
169
278
|
#### Error Handling
|
|
279
|
+
|
|
170
280
|
- If you query a non-existent index, an error is thrown:
|
|
171
281
|
```typescript
|
|
172
282
|
await db.Product.findByIndex('nonexistent', 'value'); // throws
|
|
@@ -179,6 +289,7 @@ const firstElectronic = await db.Product.findOneByIndex('category', 'Electronics
|
|
|
179
289
|
idb-ts provides flexible key management options including auto-increment keys, key generators, and composite keys for complex data relationships.
|
|
180
290
|
|
|
181
291
|
### Auto-Increment Keys
|
|
292
|
+
|
|
182
293
|
Perfect for entities where you want the database to automatically generate sequential IDs:
|
|
183
294
|
|
|
184
295
|
```typescript
|
|
@@ -196,19 +307,21 @@ class Task {
|
|
|
196
307
|
}
|
|
197
308
|
}
|
|
198
309
|
|
|
199
|
-
const db = await Database.build(
|
|
310
|
+
const db = await Database.build('tasks-db', [Task]);
|
|
200
311
|
|
|
201
312
|
// IDs are automatically generated: 1, 2, 3, etc.
|
|
202
|
-
const task1 = await db.Task.create(new Task(
|
|
203
|
-
const task2 = await db.Task.create(new Task(
|
|
313
|
+
const task1 = await db.Task.create(new Task('Learn TypeScript'));
|
|
314
|
+
const task2 = await db.Task.create(new Task('Build amazing apps'));
|
|
204
315
|
console.log(task1.id); // 1
|
|
205
316
|
console.log(task2.id); // 2
|
|
206
317
|
```
|
|
207
318
|
|
|
208
319
|
### Key Generators
|
|
320
|
+
|
|
209
321
|
Generate keys automatically using built-in generators:
|
|
210
322
|
|
|
211
323
|
#### UUID Keys
|
|
324
|
+
|
|
212
325
|
```typescript
|
|
213
326
|
@DataClass()
|
|
214
327
|
class Document {
|
|
@@ -228,13 +341,16 @@ class Document {
|
|
|
228
341
|
}
|
|
229
342
|
}
|
|
230
343
|
|
|
231
|
-
const db = await Database.build(
|
|
344
|
+
const db = await Database.build('docs-db', [Document]);
|
|
232
345
|
|
|
233
|
-
const doc = await db.Document.create(
|
|
346
|
+
const doc = await db.Document.create(
|
|
347
|
+
new Document('tutorial', 'Getting Started', 'Welcome...'),
|
|
348
|
+
);
|
|
234
349
|
console.log(doc.uuid); // e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
|
235
350
|
```
|
|
236
351
|
|
|
237
352
|
#### Timestamp Keys
|
|
353
|
+
|
|
238
354
|
```typescript
|
|
239
355
|
@DataClass()
|
|
240
356
|
class Event {
|
|
@@ -252,11 +368,12 @@ class Event {
|
|
|
252
368
|
}
|
|
253
369
|
}
|
|
254
370
|
|
|
255
|
-
const event = await db.Event.create(new Event(
|
|
371
|
+
const event = await db.Event.create(new Event('user_login', { userId: '123' }));
|
|
256
372
|
console.log(event.timestamp); // e.g., 1696118400000
|
|
257
373
|
```
|
|
258
374
|
|
|
259
375
|
#### Random Keys
|
|
376
|
+
|
|
260
377
|
```typescript
|
|
261
378
|
@DataClass()
|
|
262
379
|
class Session {
|
|
@@ -272,17 +389,21 @@ class Session {
|
|
|
272
389
|
}
|
|
273
390
|
}
|
|
274
391
|
|
|
275
|
-
const session = await db.Session.create(new Session(
|
|
392
|
+
const session = await db.Session.create(new Session('user123', new Date()));
|
|
276
393
|
console.log(session.sessionId); // e.g., "xyz789abc123"
|
|
277
394
|
```
|
|
278
395
|
|
|
279
396
|
### Custom Key Generators
|
|
397
|
+
|
|
280
398
|
Create your own key generation logic:
|
|
281
399
|
|
|
282
400
|
```typescript
|
|
283
401
|
@DataClass()
|
|
284
402
|
class Invoice {
|
|
285
|
-
@KeyPath({
|
|
403
|
+
@KeyPath({
|
|
404
|
+
generator: (entity: any) =>
|
|
405
|
+
`INV-${entity.year}-${String(entity.number).padStart(4, '0')}`,
|
|
406
|
+
})
|
|
286
407
|
invoiceId!: string;
|
|
287
408
|
|
|
288
409
|
year!: number;
|
|
@@ -296,15 +417,16 @@ class Invoice {
|
|
|
296
417
|
}
|
|
297
418
|
}
|
|
298
419
|
|
|
299
|
-
const invoice = await db.Invoice.create(new Invoice(2024, 1, 1500.
|
|
420
|
+
const invoice = await db.Invoice.create(new Invoice(2024, 1, 1500.0));
|
|
300
421
|
console.log(invoice.invoiceId); // "INV-2024-0001"
|
|
301
422
|
```
|
|
302
423
|
|
|
303
424
|
### Composite Keys
|
|
425
|
+
|
|
304
426
|
Handle many-to-many relationships with composite keys using the `@CompositeKeyPath` decorator:
|
|
305
427
|
|
|
306
428
|
```typescript
|
|
307
|
-
import { CompositeKeyPath } from
|
|
429
|
+
import { CompositeKeyPath } from 'idb-ts';
|
|
308
430
|
|
|
309
431
|
@CompositeKeyPath(['userId', 'projectId'])
|
|
310
432
|
@DataClass()
|
|
@@ -325,12 +447,14 @@ class UserProject {
|
|
|
325
447
|
}
|
|
326
448
|
}
|
|
327
449
|
|
|
328
|
-
const db = await Database.build(
|
|
450
|
+
const db = await Database.build('collaboration-db', [UserProject]);
|
|
329
451
|
|
|
330
452
|
// Create relationships
|
|
331
|
-
await db.UserProject.create(
|
|
332
|
-
|
|
333
|
-
|
|
453
|
+
await db.UserProject.create(
|
|
454
|
+
new UserProject('user123', 'project456', 'developer'),
|
|
455
|
+
);
|
|
456
|
+
await db.UserProject.create(new UserProject('user123', 'project789', 'admin'));
|
|
457
|
+
await db.UserProject.create(new UserProject('user456', 'project456', 'viewer'));
|
|
334
458
|
|
|
335
459
|
// Read with composite key
|
|
336
460
|
const relationship = await db.UserProject.read(['user123', 'project456']);
|
|
@@ -338,7 +462,7 @@ console.log(relationship?.role); // "developer"
|
|
|
338
462
|
|
|
339
463
|
// Update relationship
|
|
340
464
|
if (relationship) {
|
|
341
|
-
relationship.role =
|
|
465
|
+
relationship.role = 'maintainer';
|
|
342
466
|
await db.UserProject.update(relationship);
|
|
343
467
|
}
|
|
344
468
|
|
|
@@ -350,16 +474,113 @@ const developers = await db.UserProject.findByIndex('role', 'developer');
|
|
|
350
474
|
```
|
|
351
475
|
|
|
352
476
|
### Key Generation Utilities
|
|
477
|
+
|
|
353
478
|
Access key generators directly for your custom logic:
|
|
354
479
|
|
|
355
480
|
```typescript
|
|
356
|
-
import { KeyGenerators } from
|
|
481
|
+
import { KeyGenerators } from 'idb-ts';
|
|
357
482
|
|
|
358
|
-
const uuid = KeyGenerators.uuid();
|
|
483
|
+
const uuid = KeyGenerators.uuid(); // Generate UUID
|
|
359
484
|
const timestamp = KeyGenerators.timestamp(); // Current timestamp
|
|
360
|
-
const random = KeyGenerators.random();
|
|
485
|
+
const random = KeyGenerators.random(); // Random string
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Transaction API
|
|
489
|
+
|
|
490
|
+
idb-ts provides a simple, atomic Transaction API that lets you group multiple repository operations into a single native IndexedDB transaction. Operations performed inside a transaction are committed together or discarded together on failure.
|
|
491
|
+
|
|
492
|
+
Usage β callback form (automatic commit/rollback):
|
|
493
|
+
|
|
494
|
+
```ts
|
|
495
|
+
await db.transaction(async (tx) => {
|
|
496
|
+
await tx.User.create(user);
|
|
497
|
+
await tx.Order.create(order);
|
|
498
|
+
await tx.OrderItem.create(item);
|
|
499
|
+
// If the callback returns successfully, the transaction is committed.
|
|
500
|
+
// If an exception is thrown, the transaction is aborted and all writes are rolled back.
|
|
501
|
+
});
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Usage β explicit control:
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
const tx = await db.beginTransaction(['User', 'Order'], 'readwrite');
|
|
508
|
+
try {
|
|
509
|
+
await tx.User.create(user);
|
|
510
|
+
await tx.Order.create(order);
|
|
511
|
+
await tx.commit(); // explicitly commit, but optional
|
|
512
|
+
} catch (e) {
|
|
513
|
+
await tx.rollback(); // abort and rollback
|
|
514
|
+
}
|
|
361
515
|
```
|
|
362
516
|
|
|
517
|
+
Key notes and behavior:
|
|
518
|
+
|
|
519
|
+
- Atomicity: all repository writes that use the transaction handle (`tx.Entity.*`) share the same native `IDBTransaction` and are atomic β either all succeed (commit) or none persist (abort/rollback).
|
|
520
|
+
- Scope: `beginTransaction` accepts an array of entity names (e.g., `['User','Order']`) and opens a native transaction over those object stores. The callback form uses a transaction covering all registered entities.
|
|
521
|
+
- Querying inside transactions: use `tx.Entity.query()` or other `tx.Entity.*` repository methods to ensure those reads/writes are performed on the same underlying transaction.
|
|
522
|
+
- Modes: transactions support standard IndexedDB modes (`'readonly'` or `'readwrite'`). The default for `beginTransaction` and the callback wrapper is `'readwrite'`.
|
|
523
|
+
- Commit/abort semantics: modern browsers may perform implicit commit when the transaction's event loop completes; `commit()` is called when available. `rollback()` triggers `transaction.abort()`.
|
|
524
|
+
- Error handling: if an error is thrown in the callback form the library will call `rollback()` and rethrow the error to the caller.
|
|
525
|
+
- Limitations: composite operations that span many stores still must list all involved entity names when using `beginTransaction`. Long-running synchronous work inside a transaction can increase the risk of versionchange or blocked events β keep transaction work asynchronous and short.
|
|
526
|
+
|
|
527
|
+
Example patterns:
|
|
528
|
+
|
|
529
|
+
- Batch inserts in one transaction for performance and atomic safety.
|
|
530
|
+
- Run a read-modify-write sequence in a single transaction to avoid lost updates.
|
|
531
|
+
|
|
532
|
+
The Transaction API is covered by the test suite in `__tests__/transaction.test.ts` which demonstrates both the callback and explicit modes.
|
|
533
|
+
|
|
534
|
+
### Advanced Querying
|
|
535
|
+
|
|
536
|
+
`idb-ts` includes a typed query builder for field-level filtering, logical grouping, and basic aggregations. The available operators are constrained by the field type, so string-only and array-only operations are only exposed where they make sense.
|
|
537
|
+
|
|
538
|
+
```ts
|
|
539
|
+
const users = await db.User.query()
|
|
540
|
+
.where('name').startsWith('John')
|
|
541
|
+
.and('email').endsWith('@gmail.com')
|
|
542
|
+
.and('description').contains('important')
|
|
543
|
+
.execute();
|
|
544
|
+
|
|
545
|
+
const activeOrTrial = await db.User.query()
|
|
546
|
+
.where('age').gte(18)
|
|
547
|
+
.or()
|
|
548
|
+
.where('hasParentalConsent').equals(true)
|
|
549
|
+
.execute();
|
|
550
|
+
|
|
551
|
+
const premiumUsers = await db.User.query()
|
|
552
|
+
.where((qb) =>
|
|
553
|
+
qb.where('type').equals('premium').and('status').equals('active'),
|
|
554
|
+
)
|
|
555
|
+
.or()
|
|
556
|
+
.where('isTrial').equals(true)
|
|
557
|
+
.execute();
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Supported operators include:
|
|
561
|
+
|
|
562
|
+
- String operations: `startsWith`, `endsWith`, `contains`, `matches`
|
|
563
|
+
- Range operations: `between`, `notBetween`
|
|
564
|
+
- Collection operations: `contains`, `containsAny`, `containsAll`, `in`, `notIn`
|
|
565
|
+
- Logical chaining: `and()`, `or()`, and grouped predicates via `where((qb) => ...)`
|
|
566
|
+
|
|
567
|
+
Aggregations are available directly on the query builder:
|
|
568
|
+
|
|
569
|
+
```ts
|
|
570
|
+
await db.Order.query().sum('amount');
|
|
571
|
+
await db.Order.query().avg('price');
|
|
572
|
+
await db.Order.query().min('date');
|
|
573
|
+
await db.Order.query().max('date');
|
|
574
|
+
await db.Order.query().groupBy('status').count();
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
Notes:
|
|
578
|
+
|
|
579
|
+
- `sum()` and `avg()` are numeric-only.
|
|
580
|
+
- `min()` and `max()` are available for comparable scalar fields.
|
|
581
|
+
- `groupBy(...).count()` returns grouped counts by the selected field.
|
|
582
|
+
- TypeScript will reject unsupported operator/field combinations at compile time.
|
|
583
|
+
|
|
363
584
|
---
|
|
364
585
|
|
|
365
586
|
## π Schema Versioning
|
|
@@ -393,7 +614,7 @@ class Comment {
|
|
|
393
614
|
}
|
|
394
615
|
|
|
395
616
|
// Database version will be 3 (highest entity version)
|
|
396
|
-
const db = await Database.build(
|
|
617
|
+
const db = await Database.build('blog', [User, Post, Comment]);
|
|
397
618
|
|
|
398
619
|
console.log(db.getDatabaseVersion()); // 3
|
|
399
620
|
console.log(db.getEntityVersions()); // Map with entity versions
|
|
@@ -415,20 +636,18 @@ const entityVersions = db.getEntityVersions();
|
|
|
415
636
|
const userVersion = db.getEntityVersion('User');
|
|
416
637
|
|
|
417
638
|
// Version upgrade flow:
|
|
418
|
-
// v1.0: User(v1)
|
|
419
|
-
// v1.1: User(v1), Post(v2)
|
|
420
|
-
// v1.2: User(v1), Post(v2), Comment(v3)
|
|
639
|
+
// v1.0: User(v1) -> Database v1
|
|
640
|
+
// v1.1: User(v1), Post(v2) -> Database v2
|
|
641
|
+
// v1.2: User(v1), Post(v2), Comment(v3) -> Database v3
|
|
421
642
|
```
|
|
422
643
|
|
|
423
|
-
π **[Complete Schema Versioning Guide](./SCHEMA_VERSIONING.md)** - Detailed documentation with examples and best practices.
|
|
424
|
-
|
|
425
644
|
---
|
|
426
645
|
|
|
427
646
|
## π Useful Links
|
|
647
|
+
|
|
428
648
|
- π **GitHub**: [maifeeulasad/idb-ts](https://github.com/maifeeulasad/idb-ts)
|
|
429
649
|
- π¦ **NPM**: [idb-ts](https://www.npmjs.com/package/idb-ts)
|
|
430
650
|
- Demo: https://maifeeulasad.github.io/idb-ts/
|
|
431
651
|
- Code Coverage report: https://maifeeulasad.github.io/idb-ts/coverage/lcov-report/
|
|
432
652
|
|
|
433
653
|
π **Enjoy seamless IndexedDB integration with TypeScript! Happy coding!** π
|
|
434
|
-
|