idb-ts 3.13.0 → 3.15.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 +198 -74
- package/lib/index.cjs +1169 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.esm.js +73 -69
- package/lib/index.js +39 -44
- package/package.json +8 -5
- package/lib/jest.setup.d.ts +0 -1
- package/lib/jest.setup.js +0 -6
package/README.md
CHANGED
|
@@ -55,18 +55,18 @@ yarn add idb-ts
|
|
|
55
55
|
|
|
56
56
|
## Feature Overview
|
|
57
57
|
|
|
58
|
-
| Feature
|
|
59
|
-
|
|
60
|
-
| **Declarative entity definition** | Define stores, keys, and indexes with class decorators
|
|
61
|
-
| **Full CRUD API**
|
|
62
|
-
| **Typed query builder**
|
|
63
|
-
| **Key generation**
|
|
64
|
-
| **Composite keys**
|
|
65
|
-
| **Field validation**
|
|
66
|
-
| **Schema versioning**
|
|
67
|
-
| **Transaction API**
|
|
68
|
-
| **Data retention**
|
|
69
|
-
| **Automatic timestamps**
|
|
58
|
+
| Feature | Description |
|
|
59
|
+
| --------------------------------- | -------------------------------------------------------------- |
|
|
60
|
+
| **Declarative entity definition** | Define stores, keys, and indexes with class decorators |
|
|
61
|
+
| **Full CRUD API** | Create, read, update, delete, list, paginate, and count |
|
|
62
|
+
| **Typed query builder** | Chainable, type-checked filter, sort, and aggregation DSL |
|
|
63
|
+
| **Key generation** | Auto-increment, UUID v4, timestamp, random, or custom function |
|
|
64
|
+
| **Composite keys** | Multi-field primary keys for relational associations |
|
|
65
|
+
| **Field validation** | Per-property predicate rules enforced on write |
|
|
66
|
+
| **Schema versioning** | Automatic `onupgradeneeded` migration based on entity versions |
|
|
67
|
+
| **Transaction API** | Callback-based and explicit commit/rollback patterns |
|
|
68
|
+
| **Data retention** | Periodic background cleanup of expired records |
|
|
69
|
+
| **Automatic timestamps** | `__idb_createdAt` / `__idb_updatedAt` injected on every write |
|
|
70
70
|
|
|
71
71
|
---
|
|
72
72
|
|
|
@@ -88,9 +88,16 @@ class User {
|
|
|
88
88
|
age!: number;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
const db = await Database.build<{ User: EntityRepository<User> }>('mydb', [
|
|
91
|
+
const db = await Database.build<{ User: EntityRepository<User> }>('mydb', [
|
|
92
|
+
User,
|
|
93
|
+
]);
|
|
92
94
|
|
|
93
|
-
await db.User.create({
|
|
95
|
+
await db.User.create({
|
|
96
|
+
id: '',
|
|
97
|
+
name: 'Alice',
|
|
98
|
+
age: 30,
|
|
99
|
+
email: 'alice@example.com',
|
|
100
|
+
});
|
|
94
101
|
const alice = await db.User.findOneByIndex('email', 'alice@example.com');
|
|
95
102
|
```
|
|
96
103
|
|
|
@@ -109,7 +116,10 @@ class User {
|
|
|
109
116
|
id!: string;
|
|
110
117
|
|
|
111
118
|
@Index({ unique: true })
|
|
112
|
-
@Validate(
|
|
119
|
+
@Validate(
|
|
120
|
+
(v) => typeof v === 'string' && v.includes('@'),
|
|
121
|
+
'must be a valid email',
|
|
122
|
+
)
|
|
113
123
|
email!: string;
|
|
114
124
|
|
|
115
125
|
@Validate((v) => typeof v === 'number' && v >= 0, 'age must be non-negative')
|
|
@@ -125,18 +135,18 @@ class User {
|
|
|
125
135
|
|
|
126
136
|
Marks a class as a managed entity. Must be applied exactly once per class, after all other idb-ts decorators.
|
|
127
137
|
|
|
128
|
-
| Option
|
|
129
|
-
|
|
130
|
-
| `version` | `number` | `1`
|
|
138
|
+
| Option | Type | Default | Description |
|
|
139
|
+
| --------- | -------- | ------- | -------------------------------------------------------------------- |
|
|
140
|
+
| `version` | `number` | `1` | Schema version. Increment when the entity's store or indexes change. |
|
|
131
141
|
|
|
132
142
|
#### `@KeyPath(options?)`
|
|
133
143
|
|
|
134
144
|
Designates the decorated property as the primary key of the object store. Exactly one property per class may carry this decorator. For multi-field keys, use `@CompositeKeyPath` at the class level instead.
|
|
135
145
|
|
|
136
|
-
| Option
|
|
137
|
-
|
|
138
|
-
| `autoIncrement` | `boolean`
|
|
139
|
-
| `generator`
|
|
146
|
+
| Option | Type | Default | Description |
|
|
147
|
+
| --------------- | ----------------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------- |
|
|
148
|
+
| `autoIncrement` | `boolean` | `false` | Delegate key assignment to IndexedDB's auto-increment mechanism. |
|
|
149
|
+
| `generator` | `'uuid'` \| `'timestamp'` \| `'random'` \| `(item) => string \| number` | - | Automatic key generator invoked when the key field is absent or empty on `create`. |
|
|
140
150
|
|
|
141
151
|
#### `@CompositeKeyPath(fields, options?)`
|
|
142
152
|
|
|
@@ -156,8 +166,8 @@ class UserProject {
|
|
|
156
166
|
|
|
157
167
|
Creates an IDB index on the decorated field, enabling efficient lookups via `findByIndex` and `findOneByIndex`.
|
|
158
168
|
|
|
159
|
-
| Option
|
|
160
|
-
|
|
169
|
+
| Option | Type | Description |
|
|
170
|
+
| -------- | --------- | ---------------------------------------- |
|
|
161
171
|
| `unique` | `boolean` | Enforce uniqueness on the indexed field. |
|
|
162
172
|
|
|
163
173
|
#### `@Validate(predicate, message)`
|
|
@@ -174,7 +184,7 @@ Class-level decorator that configures automatic expiry and deletion of records.
|
|
|
174
184
|
|
|
175
185
|
```typescript
|
|
176
186
|
const db = await Database.build<{
|
|
177
|
-
User:
|
|
187
|
+
User: EntityRepository<User>;
|
|
178
188
|
Order: EntityRepository<Order>;
|
|
179
189
|
}>('shop', [User, Order]);
|
|
180
190
|
```
|
|
@@ -186,10 +196,10 @@ The effective database version is the highest `version` value declared across al
|
|
|
186
196
|
### Inspecting database metadata
|
|
187
197
|
|
|
188
198
|
```typescript
|
|
189
|
-
db.getDatabaseVersion();
|
|
190
|
-
db.getEntityVersions();
|
|
191
|
-
db.getEntityVersion('User');
|
|
192
|
-
db.getAvailableEntities();
|
|
199
|
+
db.getDatabaseVersion(); // number - current IDB version
|
|
200
|
+
db.getEntityVersions(); // Map<string, number>
|
|
201
|
+
db.getEntityVersion('User'); // number | undefined
|
|
202
|
+
db.getAvailableEntities(); // string[]
|
|
193
203
|
```
|
|
194
204
|
|
|
195
205
|
### Closing the connection
|
|
@@ -210,9 +220,9 @@ await db.User.create(user);
|
|
|
210
220
|
await db.User.createMany([alice, bob, charlie]);
|
|
211
221
|
|
|
212
222
|
// Read
|
|
213
|
-
const user = await db.User.read('u1');
|
|
223
|
+
const user = await db.User.read('u1'); // by primary key
|
|
214
224
|
const page = await db.User.listPaginated(1, 20); // 1-based pagination
|
|
215
|
-
const all
|
|
225
|
+
const all = await db.User.list();
|
|
216
226
|
|
|
217
227
|
// Update
|
|
218
228
|
await db.User.update(updatedUser);
|
|
@@ -224,7 +234,7 @@ await db.User.deleteMany(['u1', 'u2']);
|
|
|
224
234
|
await db.User.deleteWhere((q) => q.where('age').lt(18));
|
|
225
235
|
|
|
226
236
|
// Utilities
|
|
227
|
-
const count
|
|
237
|
+
const count = await db.User.count();
|
|
228
238
|
const exists = await db.User.exists('u1');
|
|
229
239
|
await db.User.clear();
|
|
230
240
|
```
|
|
@@ -232,7 +242,7 @@ await db.User.clear();
|
|
|
232
242
|
### Index lookups
|
|
233
243
|
|
|
234
244
|
```typescript
|
|
235
|
-
const allAdmins
|
|
245
|
+
const allAdmins = await db.User.findByIndex('role', 'admin');
|
|
236
246
|
const firstAdmin = await db.User.findOneByIndex('role', 'admin');
|
|
237
247
|
```
|
|
238
248
|
|
|
@@ -244,9 +254,9 @@ Querying a non-existent index throws immediately.
|
|
|
244
254
|
|
|
245
255
|
Every record written through a repository automatically receives two internal fields:
|
|
246
256
|
|
|
247
|
-
| Field
|
|
248
|
-
|
|
249
|
-
| `__idb_createdAt` | `number` (ms since epoch) | `create` only
|
|
257
|
+
| Field | Type | Set on |
|
|
258
|
+
| ----------------- | ------------------------- | --------------------- |
|
|
259
|
+
| `__idb_createdAt` | `number` (ms since epoch) | `create` only |
|
|
250
260
|
| `__idb_updatedAt` | `number` (ms since epoch) | `create` and `update` |
|
|
251
261
|
|
|
252
262
|
`__idb_createdAt` is preserved across updates; `__idb_updatedAt` is refreshed on every write.
|
|
@@ -266,25 +276,27 @@ console.log(item.__idb_createdAt, item.__idb_updatedAt);
|
|
|
266
276
|
|
|
267
277
|
```typescript
|
|
268
278
|
const results = await db.User.query()
|
|
269
|
-
.where('age')
|
|
270
|
-
.
|
|
279
|
+
.where('age')
|
|
280
|
+
.gte(18)
|
|
281
|
+
.and('status')
|
|
282
|
+
.equals('active')
|
|
271
283
|
.execute();
|
|
272
284
|
```
|
|
273
285
|
|
|
274
286
|
#### Available operators
|
|
275
287
|
|
|
276
|
-
| Operator
|
|
277
|
-
|
|
278
|
-
| `equals`
|
|
279
|
-
| `gt` / `gte` / `lt` / `lte`
|
|
280
|
-
| `between(start, end)`
|
|
281
|
-
| `notBetween(start, end)`
|
|
282
|
-
| `startsWith` / `endsWith`
|
|
283
|
-
| `contains`
|
|
284
|
-
| `matches`
|
|
285
|
-
| `in(values)` / `notIn(values)` | any
|
|
286
|
-
| `containsAny(values)`
|
|
287
|
-
| `containsAll(values)`
|
|
288
|
+
| Operator | Field types | Description |
|
|
289
|
+
| ------------------------------ | ----------------- | ------------------------------- |
|
|
290
|
+
| `equals` | any | Strict equality (`===`) |
|
|
291
|
+
| `gt` / `gte` / `lt` / `lte` | `ComparableValue` | Comparison |
|
|
292
|
+
| `between(start, end)` | `ComparableValue` | Inclusive range |
|
|
293
|
+
| `notBetween(start, end)` | `ComparableValue` | Outside range |
|
|
294
|
+
| `startsWith` / `endsWith` | `string` | Prefix / suffix match |
|
|
295
|
+
| `contains` | `string` \| array | Substring or element membership |
|
|
296
|
+
| `matches` | `string` | Regular expression test |
|
|
297
|
+
| `in(values)` / `notIn(values)` | any | Membership test |
|
|
298
|
+
| `containsAny(values)` | array | At least one element matches |
|
|
299
|
+
| `containsAll(values)` | array | All elements present |
|
|
288
300
|
|
|
289
301
|
TypeScript enforces operator/type compatibility at compile time - string-only operators are not exposed on numeric fields, and so on.
|
|
290
302
|
|
|
@@ -293,9 +305,11 @@ TypeScript enforces operator/type compatibility at compile time - string-only op
|
|
|
293
305
|
```typescript
|
|
294
306
|
// OR connector
|
|
295
307
|
const results = await db.User.query()
|
|
296
|
-
.where('age')
|
|
308
|
+
.where('age')
|
|
309
|
+
.gte(18)
|
|
297
310
|
.or()
|
|
298
|
-
.where('hasParentalConsent')
|
|
311
|
+
.where('hasParentalConsent')
|
|
312
|
+
.equals(true)
|
|
299
313
|
.execute();
|
|
300
314
|
|
|
301
315
|
// Grouped sub-expression
|
|
@@ -304,7 +318,8 @@ const premiumOrTrial = await db.User.query()
|
|
|
304
318
|
qb.where('type').equals('premium').and('status').equals('active'),
|
|
305
319
|
)
|
|
306
320
|
.or()
|
|
307
|
-
.where('isTrial')
|
|
321
|
+
.where('isTrial')
|
|
322
|
+
.equals(true)
|
|
308
323
|
.execute();
|
|
309
324
|
```
|
|
310
325
|
|
|
@@ -312,7 +327,8 @@ const premiumOrTrial = await db.User.query()
|
|
|
312
327
|
|
|
313
328
|
```typescript
|
|
314
329
|
await db.User.query()
|
|
315
|
-
.where('status')
|
|
330
|
+
.where('status')
|
|
331
|
+
.equals('active')
|
|
316
332
|
.orderBy('createdAt', 'desc')
|
|
317
333
|
.offset(20)
|
|
318
334
|
.limit(10)
|
|
@@ -324,10 +340,7 @@ await db.User.query()
|
|
|
324
340
|
When a field is indexed, you can constrain the initial IDB candidate set at the storage layer before in-memory filtering begins:
|
|
325
341
|
|
|
326
342
|
```typescript
|
|
327
|
-
await db.Product.query()
|
|
328
|
-
.useIndex('price')
|
|
329
|
-
.range(10, 100)
|
|
330
|
-
.execute();
|
|
343
|
+
await db.Product.query().useIndex('price').range(10, 100).execute();
|
|
331
344
|
```
|
|
332
345
|
|
|
333
346
|
### Aggregations
|
|
@@ -356,7 +369,7 @@ const byStatus = await db.Order.query().groupBy('status').count();
|
|
|
356
369
|
@DataClass()
|
|
357
370
|
class Task {
|
|
358
371
|
@KeyPath({ autoIncrement: true })
|
|
359
|
-
id!: number;
|
|
372
|
+
id!: number; // Assigned by IndexedDB: 1, 2, 3, …
|
|
360
373
|
|
|
361
374
|
title!: string;
|
|
362
375
|
}
|
|
@@ -367,7 +380,7 @@ class Task {
|
|
|
367
380
|
```typescript
|
|
368
381
|
@DataClass()
|
|
369
382
|
class Document {
|
|
370
|
-
@KeyPath({ generator: 'uuid' })
|
|
383
|
+
@KeyPath({ generator: 'uuid' }) // RFC 4122 v4
|
|
371
384
|
id!: string;
|
|
372
385
|
}
|
|
373
386
|
|
|
@@ -379,7 +392,7 @@ class Event {
|
|
|
379
392
|
|
|
380
393
|
@DataClass()
|
|
381
394
|
class Session {
|
|
382
|
-
@KeyPath({ generator: 'random' })
|
|
395
|
+
@KeyPath({ generator: 'random' }) // Base-36 random string
|
|
383
396
|
id!: string;
|
|
384
397
|
}
|
|
385
398
|
```
|
|
@@ -406,9 +419,9 @@ class Invoice {
|
|
|
406
419
|
```typescript
|
|
407
420
|
import { KeyGenerators } from 'idb-ts';
|
|
408
421
|
|
|
409
|
-
KeyGenerators.uuid();
|
|
422
|
+
KeyGenerators.uuid(); // "a1b2c3d4-..."
|
|
410
423
|
KeyGenerators.timestamp(); // 1696118400000
|
|
411
|
-
KeyGenerators.random();
|
|
424
|
+
KeyGenerators.random(); // "xyz789abc"
|
|
412
425
|
```
|
|
413
426
|
|
|
414
427
|
### Composite keys
|
|
@@ -452,7 +465,10 @@ class User {
|
|
|
452
465
|
)
|
|
453
466
|
email!: string;
|
|
454
467
|
|
|
455
|
-
@Validate(
|
|
468
|
+
@Validate(
|
|
469
|
+
(v) => Number.isInteger(v) && v >= 0,
|
|
470
|
+
'must be a non-negative integer',
|
|
471
|
+
)
|
|
456
472
|
age!: number;
|
|
457
473
|
}
|
|
458
474
|
```
|
|
@@ -514,11 +530,11 @@ class Session {
|
|
|
514
530
|
}
|
|
515
531
|
```
|
|
516
532
|
|
|
517
|
-
| Option
|
|
518
|
-
|
|
519
|
-
| `seconds` | `number`
|
|
520
|
-
| `enabled` | `boolean` | `true`
|
|
521
|
-
| `field`
|
|
533
|
+
| Option | Type | Default | Description |
|
|
534
|
+
| --------- | --------- | ------------------- | ----------------------------------------------------------------------- |
|
|
535
|
+
| `seconds` | `number` | - | **(Required)** Retention window in seconds. Must be a positive integer. |
|
|
536
|
+
| `enabled` | `boolean` | `true` | Set to `false` to suspend cleanup without removing the policy. |
|
|
537
|
+
| `field` | `string` | `'__idb_createdAt'` | Numeric timestamp field used to compute record age. |
|
|
522
538
|
|
|
523
539
|
When multiple entities define retention policies, the cleanup interval is set to the GCD of all configured `seconds` values in milliseconds, so a single timer satisfies every policy efficiently. The job runs immediately on database open and then on each interval tick, using cursor-based `readwrite` transactions.
|
|
524
540
|
|
|
@@ -529,9 +545,18 @@ When multiple entities define retention policies, the cleanup interval is set to
|
|
|
529
545
|
Increment an entity's `version` to trigger `onupgradeneeded` and update its object store on the user's next visit. The effective database version is the maximum across all registered entities, so adding a new high-version entity is sufficient to initiate a migration.
|
|
530
546
|
|
|
531
547
|
```typescript
|
|
532
|
-
@DataClass({ version: 1 })
|
|
533
|
-
|
|
534
|
-
|
|
548
|
+
@DataClass({ version: 1 })
|
|
549
|
+
class User {
|
|
550
|
+
/* ... */
|
|
551
|
+
}
|
|
552
|
+
@DataClass({ version: 2 })
|
|
553
|
+
class Post {
|
|
554
|
+
/* ... */
|
|
555
|
+
}
|
|
556
|
+
@DataClass({ version: 3 })
|
|
557
|
+
class Comment {
|
|
558
|
+
/* ... */
|
|
559
|
+
}
|
|
535
560
|
|
|
536
561
|
// Database opens at version 3.
|
|
537
562
|
// If a user was on version 1, only Post (v2) and Comment (v3) stores are
|
|
@@ -555,13 +580,112 @@ await db.User.deleteMany(['u1', 'u2', 'u3']);
|
|
|
555
580
|
|
|
556
581
|
---
|
|
557
582
|
|
|
583
|
+
## Performance
|
|
584
|
+
|
|
585
|
+
<!-- performance start -->
|
|
586
|
+
|
|
587
|
+
Already up to date
|
|
588
|
+
Done in 443ms using pnpm v11.5.1
|
|
589
|
+
|
|
590
|
+
### Suite 1: CRUD Operations
|
|
591
|
+
|
|
592
|
+
| Operation | Ops | Total ms | Ops/s | Avg ms | P50 | P95 | P99 | Min | Max |
|
|
593
|
+
|-----------|-----:|---------:|------:|-------:|----:|----:|----:|----:|----:|
|
|
594
|
+
| create (single) | 200 | 15.46 | 12,936.753 | 0.077 | 0.058 | 0.137 | 0.162 | 0.052 | 1.01 |
|
|
595
|
+
| read (by PK) | 200 | 10.987 | 18,203.475 | 0.054 | 0.047 | 0.089 | 0.163 | 0.04 | 0.175 |
|
|
596
|
+
| update (single) | 200 | 121.183 | 1,650.4 | 0.605 | 0.535 | 0.991 | 1.739 | 0.459 | 2.227 |
|
|
597
|
+
| findByIndex (email) | 200 | 11.218 | 17,828.706 | 0.056 | 0.052 | 0.083 | 0.111 | 0.044 | 0.143 |
|
|
598
|
+
| findOneByIndex (email) | 200 | 13.331 | 15,002.256 | 0.066 | 0.057 | 0.081 | 0.103 | 0.048 | 1.11 |
|
|
599
|
+
| count | 200 | 10.209 | 19,589.884 | 0.051 | 0.049 | 0.064 | 0.088 | 0.044 | 0.101 |
|
|
600
|
+
| exists | 200 | 16.586 | 12,058.587 | 0.083 | 0.064 | 0.097 | 0.18 | 0.057 | 2.444 |
|
|
601
|
+
| list (all) | 50 | 97.836 | 511.058 | 1.956 | 1.724 | 4.683 | 7.585 | 1.481 | 7.585 |
|
|
602
|
+
| listPaginated (1, 20) | 200 | 374.357 | 534.25 | 1.871 | 1.692 | 2.005 | 9.138 | 1.447 | 12.441 |
|
|
603
|
+
| query().where().gte().execute() | 100 | 182.997 | 546.458 | 1.829 | 1.542 | 1.975 | 2.457 | 1.485 | 14.388 |
|
|
604
|
+
| delete (single) | 200 | 138.608 | 1,442.92 | 0.693 | 0.658 | 0.764 | 0.857 | 0.556 | 5.689 |
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
### Suite 2: Batched CRUD (by batch size)
|
|
608
|
+
|
|
609
|
+
| Operation | Ops | Total ms | Ops/s | Avg ms | P50 | P95 | P99 | Min | Max |
|
|
610
|
+
|-----------|-----:|---------:|------:|-------:|----:|----:|----:|----:|----:|
|
|
611
|
+
| createMany (10) | 3 | 1.482 | 2,023.901 | 0.493 | 0.531 | 0.534 | 0.534 | 0.415 | 0.534 |
|
|
612
|
+
| read batch (10 keys) | 3 | 0.522 | 5,741.792 | 0.174 | 0.169 | 0.191 | 0.191 | 0.161 | 0.191 |
|
|
613
|
+
| updateMany (10) | 3 | 5.066 | 592.172 | 1.688 | 1.488 | 2.093 | 2.093 | 1.482 | 2.093 |
|
|
614
|
+
| deleteMany (10) | 3 | 3.06 | 980.235 | 1.02 | 1.031 | 1.099 | 1.099 | 0.929 | 1.099 |
|
|
615
|
+
| deleteWhere (10+ match) | 3 | 0.984 | 3,048.328 | 0.327 | 0.302 | 0.401 | 0.401 | 0.279 | 0.401 |
|
|
616
|
+
| createMany (50) | 3 | 5.753 | 521.463 | 1.917 | 1.933 | 2.106 | 2.106 | 1.712 | 2.106 |
|
|
617
|
+
| read batch (50 keys) | 3 | 4.12 | 728.179 | 1.372 | 1.386 | 1.468 | 1.468 | 1.263 | 1.468 |
|
|
618
|
+
| updateMany (50) | 3 | 78.35 | 38.29 | 26.116 | 25.63 | 28.291 | 28.291 | 24.426 | 28.291 |
|
|
619
|
+
| deleteMany (50) | 3 | 76.547 | 39.191 | 25.514 | 24.517 | 27.751 | 27.751 | 24.276 | 27.751 |
|
|
620
|
+
| deleteWhere (50+ match) | 3 | 3.43 | 874.636 | 1.143 | 1.069 | 1.294 | 1.294 | 1.065 | 1.294 |
|
|
621
|
+
| createMany (100) | 3 | 11.795 | 254.341 | 3.931 | 4.058 | 4.244 | 4.244 | 3.491 | 4.244 |
|
|
622
|
+
| read batch (100 keys) | 3 | 29.324 | 102.305 | 9.773 | 8.39 | 14.867 | 14.867 | 6.062 | 14.867 |
|
|
623
|
+
| updateMany (100) | 3 | 272.998 | 10.989 | 90.997 | 89.999 | 95.133 | 95.133 | 87.86 | 95.133 |
|
|
624
|
+
| deleteMany (100) | 3 | 270.164 | 11.104 | 90.048 | 88.568 | 98.799 | 98.799 | 82.777 | 98.799 |
|
|
625
|
+
| deleteWhere (100+ match) | 3 | 6.852 | 437.839 | 2.283 | 2.136 | 2.591 | 2.591 | 2.123 | 2.591 |
|
|
626
|
+
| createMany (500) | 3 | 89.252 | 33.613 | 29.749 | 31.124 | 34.061 | 34.061 | 24.062 | 34.061 |
|
|
627
|
+
| read batch (500 keys) | 3 | 163.408 | 18.359 | 54.467 | 59.069 | 60.496 | 60.496 | 43.837 | 60.496 |
|
|
628
|
+
| updateMany (500) | 3 | 7,195.857 | 0.417 | 2,398.617 | 2,397.101 | 2,406.21 | 2,406.21 | 2,392.539 | 2,406.21 |
|
|
629
|
+
| deleteMany (500) | 3 | 7,951.45 | 0.377 | 2,650.481 | 2,603.507 | 2,912.156 | 2,912.156 | 2,435.78 | 2,912.156 |
|
|
630
|
+
| deleteWhere (500+ match) | 3 | 36.772 | 81.584 | 12.256 | 11.967 | 12.995 | 12.995 | 11.806 | 12.995 |
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
### Suite 3: Mixed CRUD Operations
|
|
634
|
+
|
|
635
|
+
| Operation | Ops | Total ms | Ops/s | Avg ms | P50 | P95 | P99 | Min | Max |
|
|
636
|
+
|-----------|-----:|---------:|------:|-------:|----:|----:|----:|----:|----:|
|
|
637
|
+
| Read-heavy mix (70R/15U/10C/5D) | 200 | 29.799 | 6,711.698 | 0.149 | 0.029 | 0.606 | 0.619 | 0.016 | 0.652 |
|
|
638
|
+
| Write-heavy mix (20R/15U/50C/15D) | 200 | 41.659 | 4,800.892 | 0.208 | 0.042 | 0.582 | 0.608 | 0.022 | 3.973 |
|
|
639
|
+
| Mixed CRUD + queries | 200 | 118.964 | 1,681.186 | 0.594 | 0.051 | 2.065 | 7.636 | 0.013 | 10.791 |
|
|
640
|
+
| Cross-entity mix (User.read + Order.create) | 200 | 7.316 | 27,336.962 | 0.036 | 0.03 | 0.069 | 0.081 | 0.021 | 0.091 |
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
### Suite 4: Mixed Batched CRUD
|
|
644
|
+
|
|
645
|
+
| Operation | Ops | Total ms | Ops/s | Avg ms | P50 | P95 | P99 | Min | Max |
|
|
646
|
+
|-----------|-----:|---------:|------:|-------:|----:|----:|----:|----:|----:|
|
|
647
|
+
| Cycle: createMany -> readAll -> updateMany -> deleteMany (50) | 5 | 66.384 | 75.319 | 13.276 | 13.426 | 17.594 | 17.594 | 10.02 | 17.594 |
|
|
648
|
+
| createMany -> query filter -> deleteMany (50) | 5 | 36.888 | 135.546 | 7.377 | 6.693 | 9.052 | 9.052 | 6.469 | 9.052 |
|
|
649
|
+
| 5 waves × createMany(50) + deleteMany(50) | 3 | 249.923 | 12.004 | 83.307 | 85.413 | 85.568 | 85.568 | 78.939 | 85.568 |
|
|
650
|
+
| Cross-entity batch: createMany(User) + createMany(Order) + deleteMany (×50) | 3 | 140.09 | 21.415 | 46.696 | 47.77 | 51.181 | 51.181 | 41.137 | 51.181 |
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
### Suite 5: Transaction Operations
|
|
654
|
+
|
|
655
|
+
| Operation | Ops | Total ms | Ops/s | Avg ms | P50 | P95 | P99 | Min | Max |
|
|
656
|
+
|-----------|-----:|---------:|------:|-------:|----:|----:|----:|----:|----:|
|
|
657
|
+
| tx: single create | 100 | 8.886 | 11,253.788 | 0.089 | 0.073 | 0.178 | 0.228 | 0.07 | 0.253 |
|
|
658
|
+
| tx: create 10 users | 100 | 25.153 | 3,975.637 | 0.251 | 0.238 | 0.288 | 0.489 | 0.223 | 0.517 |
|
|
659
|
+
| tx: read + update | 100 | 159.349 | 627.553 | 1.593 | 1.525 | 1.63 | 2.939 | 1.499 | 5.745 |
|
|
660
|
+
| tx: multi-entity create (User+Order+Session) | 100 | 9.628 | 10,386.133 | 0.096 | 0.087 | 0.128 | 0.189 | 0.081 | 0.23 |
|
|
661
|
+
| tx: 10 reads | 100 | 20.01 | 4,997.52 | 0.2 | 0.169 | 0.343 | 0.399 | 0.153 | 0.441 |
|
|
662
|
+
| tx: batch create 50 users | 20 | 24.816 | 805.936 | 1.24 | 1.019 | 1.284 | 4.769 | 0.976 | 4.769 |
|
|
663
|
+
| tx: query().where().gte() | 100 | 933.212 | 107.157 | 9.331 | 8.393 | 19.189 | 19.988 | 7.73 | 19.998 |
|
|
664
|
+
| tx (explicit): begin -> create -> commit | 100 | 6.974 | 14,337.972 | 0.07 | 0.065 | 0.091 | 0.108 | 0.061 | 0.162 |
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
### Suite 6: Mixed Transactions
|
|
668
|
+
|
|
669
|
+
| Operation | Ops | Total ms | Ops/s | Avg ms | P50 | P95 | P99 | Min | Max |
|
|
670
|
+
|-----------|-----:|---------:|------:|-------:|----:|----:|----:|----:|----:|
|
|
671
|
+
| tx mixed: read User -> create Order -> update User | 100 | 51.695 | 1,934.439 | 0.517 | 0.41 | 0.485 | 0.724 | 0.38 | 10.259 |
|
|
672
|
+
| tx mixed: query User + read Order + create Session | 100 | 113.605 | 880.244 | 1.136 | 1.037 | 1.134 | 1.274 | 1.018 | 9.734 |
|
|
673
|
+
| tx multi-entity: create User+Order+Session | 100 | 11.079 | 9,025.916 | 0.111 | 0.106 | 0.138 | 0.143 | 0.1 | 0.166 |
|
|
674
|
+
| tx batched: create 20 Users + 40 Orders + 20 Sessions | 10 | 22.237 | 449.703 | 2.223 | 1.238 | 11.051 | 11.051 | 1.164 | 11.051 |
|
|
675
|
+
| tx mixed: delete old orders -> create new orders | 100 | 104.138 | 960.266 | 1.041 | 0.971 | 1.09 | 1.189 | 0.829 | 8.342 |
|
|
676
|
+
| tx mixed: count Orders -> conditional create | 100 | 9.371 | 10,671.27 | 0.093 | 0.088 | 0.121 | 0.141 | 0.081 | 0.166 |
|
|
677
|
+
| tx complex: read User+Orders -> aggregate -> create Session | 100 | 18.87 | 5,299.428 | 0.188 | 0.15 | 0.223 | 0.288 | 0.135 | 3.002 |
|
|
678
|
+
|
|
679
|
+
<!-- performance end -->
|
|
680
|
+
|
|
558
681
|
## Useful Links
|
|
559
682
|
|
|
560
683
|
- **GitHub**: [maifeeulasad/idb-ts](https://github.com/maifeeulasad/idb-ts)
|
|
561
684
|
- **NPM**: [idb-ts](https://www.npmjs.com/package/idb-ts)
|
|
562
|
-
- **
|
|
685
|
+
- **Demos**: https://maifeeulasad.github.io/idb-ts/
|
|
686
|
+
- **Live Editor**: https://maifeeulasad.github.io/idb-ts/typescript/
|
|
563
687
|
- **Code Coverage report**: https://maifeeulasad.github.io/idb-ts/coverage/lcov-report/
|
|
564
688
|
|
|
565
689
|
🎉 **Enjoy seamless IndexedDB integration with TypeScript! Happy coding!** 🚀
|
|
566
690
|
|
|
567
|
-
Made by [Maifee Ulasad](https://github.com/maifeeulasad) with :
|
|
691
|
+
Made by [Maifee Ulasad](https://github.com/maifeeulasad) with :heart: and :tea:. Licensed under [MIT](./LICENSE).
|