idb-ts 3.11.1 β 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 +174 -41
- package/lib/index.d.ts +95 -6
- package/lib/index.esm.js +562 -186
- package/lib/index.js +562 -186
- package/package.json +11 -8
package/README.md
CHANGED
|
@@ -24,12 +24,14 @@
|
|
|
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
36
|
npm i idb-ts # for pure npm users
|
|
35
37
|
pnpm add idb-ts # for pnpm users
|
|
@@ -37,6 +39,7 @@ yarn add idb-ts # for yarn users
|
|
|
37
39
|
```
|
|
38
40
|
|
|
39
41
|
## β¨ Features
|
|
42
|
+
|
|
40
43
|
- β
**Declarative & Type-Safe** - Define your data models with decorators.
|
|
41
44
|
- β‘ **Easy CRUD Operations** - Perform create, read, update, and delete seamlessly.
|
|
42
45
|
- π **Fully Typed API** - Benefit from TypeScriptβs powerful type system.
|
|
@@ -49,10 +52,11 @@ yarn add idb-ts # for yarn users
|
|
|
49
52
|
## π Example Usage
|
|
50
53
|
|
|
51
54
|
### ποΈ Declaring Entities
|
|
55
|
+
|
|
52
56
|
Use decorators to define your data models. Each class must have exactly one `@KeyPath()` and be decorated with `@DataClass()`.
|
|
53
57
|
|
|
54
58
|
```typescript
|
|
55
|
-
import { Database, DataClass, KeyPath, Index } from
|
|
59
|
+
import { Database, DataClass, KeyPath, Index } from 'idb-ts';
|
|
56
60
|
|
|
57
61
|
@DataClass()
|
|
58
62
|
class User {
|
|
@@ -92,45 +96,47 @@ class Location {
|
|
|
92
96
|
```
|
|
93
97
|
|
|
94
98
|
### π CRUD Operations
|
|
99
|
+
|
|
95
100
|
Perform database operations using the repository API:
|
|
96
101
|
|
|
97
102
|
```typescript
|
|
98
|
-
const db = await Database.build(
|
|
103
|
+
const db = await Database.build('idb-crud', [User, Location]);
|
|
99
104
|
|
|
100
|
-
const alice = new User(
|
|
101
|
-
const bob = new User(
|
|
102
|
-
const nyc = new Location(
|
|
103
|
-
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');
|
|
104
109
|
|
|
105
110
|
await db.User.create(alice);
|
|
106
111
|
await db.User.create(bob);
|
|
107
112
|
await db.Location.create(nyc);
|
|
108
113
|
await db.Location.create(sf);
|
|
109
114
|
|
|
110
|
-
const readAlice = await db.User.read(
|
|
111
|
-
console.log(
|
|
115
|
+
const readAlice = await db.User.read('u1');
|
|
116
|
+
console.log('π€ Read user:', readAlice);
|
|
112
117
|
|
|
113
118
|
alice.age = 26;
|
|
114
119
|
await db.User.update(alice);
|
|
115
120
|
|
|
116
121
|
const users = await db.User.list();
|
|
117
|
-
console.log(
|
|
122
|
+
console.log('π All users:', users);
|
|
118
123
|
|
|
119
124
|
// Pagination
|
|
120
125
|
const page1 = await db.User.listPaginated(1, 2); // page 1, 2 users per page
|
|
121
|
-
console.log(
|
|
126
|
+
console.log('π Page 1:', page1);
|
|
122
127
|
|
|
123
|
-
await db.User.delete(
|
|
124
|
-
console.log(
|
|
128
|
+
await db.User.delete('u1');
|
|
129
|
+
console.log('β User Alice deleted.');
|
|
125
130
|
|
|
126
131
|
const remainingUsers = await db.User.list();
|
|
127
|
-
console.log(
|
|
132
|
+
console.log('π Remaining users:', remainingUsers);
|
|
128
133
|
|
|
129
134
|
const locations = await db.Location.list();
|
|
130
|
-
console.log(
|
|
135
|
+
console.log('π All locations:', locations);
|
|
131
136
|
```
|
|
132
137
|
|
|
133
138
|
### π Indexing Support
|
|
139
|
+
|
|
134
140
|
Create indexes on fields for fast querying. Query indexes using the repository API:
|
|
135
141
|
|
|
136
142
|
```typescript
|
|
@@ -148,7 +154,13 @@ class Product {
|
|
|
148
154
|
name!: string;
|
|
149
155
|
description!: string;
|
|
150
156
|
|
|
151
|
-
constructor(
|
|
157
|
+
constructor(
|
|
158
|
+
id: string,
|
|
159
|
+
category: string,
|
|
160
|
+
price: number,
|
|
161
|
+
name: string,
|
|
162
|
+
description: string,
|
|
163
|
+
) {
|
|
152
164
|
this.id = id;
|
|
153
165
|
this.category = category;
|
|
154
166
|
this.price = price;
|
|
@@ -157,14 +169,18 @@ class Product {
|
|
|
157
169
|
}
|
|
158
170
|
}
|
|
159
171
|
|
|
160
|
-
const db = await Database.build(
|
|
172
|
+
const db = await Database.build('products-db', [Product]);
|
|
161
173
|
|
|
162
174
|
const electronics = await db.Product.findByIndex('category', 'Electronics');
|
|
163
175
|
const expensiveItems = await db.Product.findByIndex('price', 999.99);
|
|
164
|
-
const firstElectronic = await db.Product.findOneByIndex(
|
|
176
|
+
const firstElectronic = await db.Product.findOneByIndex(
|
|
177
|
+
'category',
|
|
178
|
+
'Electronics',
|
|
179
|
+
);
|
|
165
180
|
```
|
|
166
181
|
|
|
167
182
|
#### Index Methods:
|
|
183
|
+
|
|
168
184
|
- `findByIndex(indexName, value): Promise<T[]>` - Find all records matching the index value
|
|
169
185
|
- `findOneByIndex(indexName, value): Promise<T | undefined>` - Find the first record matching the index value
|
|
170
186
|
|
|
@@ -199,7 +215,9 @@ Example:
|
|
|
199
215
|
```ts
|
|
200
216
|
@RetentionPolicy({ seconds: 60 * 60 * 24 * 30 }) // 30 days
|
|
201
217
|
@DataClass()
|
|
202
|
-
class Session {
|
|
218
|
+
class Session {
|
|
219
|
+
/* ... */
|
|
220
|
+
}
|
|
203
221
|
|
|
204
222
|
// Database will run a periodic cleanup that removes sessions older than 30 days
|
|
205
223
|
```
|
|
@@ -223,7 +241,10 @@ class User {
|
|
|
223
241
|
@KeyPath()
|
|
224
242
|
id!: string;
|
|
225
243
|
|
|
226
|
-
@Validate(
|
|
244
|
+
@Validate(
|
|
245
|
+
(v) => typeof v === 'string' && v.includes('@'),
|
|
246
|
+
'must be a valid email',
|
|
247
|
+
)
|
|
227
248
|
email!: string;
|
|
228
249
|
|
|
229
250
|
@Validate((v) => typeof v === 'number' && v >= 0, 'age must be >= 0')
|
|
@@ -255,6 +276,7 @@ await db.User.deleteMany(['u1', 'u2']);
|
|
|
255
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.
|
|
256
277
|
|
|
257
278
|
#### Error Handling
|
|
279
|
+
|
|
258
280
|
- If you query a non-existent index, an error is thrown:
|
|
259
281
|
```typescript
|
|
260
282
|
await db.Product.findByIndex('nonexistent', 'value'); // throws
|
|
@@ -267,6 +289,7 @@ Performance note: `createMany` will trigger validation and key generation per it
|
|
|
267
289
|
idb-ts provides flexible key management options including auto-increment keys, key generators, and composite keys for complex data relationships.
|
|
268
290
|
|
|
269
291
|
### Auto-Increment Keys
|
|
292
|
+
|
|
270
293
|
Perfect for entities where you want the database to automatically generate sequential IDs:
|
|
271
294
|
|
|
272
295
|
```typescript
|
|
@@ -284,19 +307,21 @@ class Task {
|
|
|
284
307
|
}
|
|
285
308
|
}
|
|
286
309
|
|
|
287
|
-
const db = await Database.build(
|
|
310
|
+
const db = await Database.build('tasks-db', [Task]);
|
|
288
311
|
|
|
289
312
|
// IDs are automatically generated: 1, 2, 3, etc.
|
|
290
|
-
const task1 = await db.Task.create(new Task(
|
|
291
|
-
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'));
|
|
292
315
|
console.log(task1.id); // 1
|
|
293
316
|
console.log(task2.id); // 2
|
|
294
317
|
```
|
|
295
318
|
|
|
296
319
|
### Key Generators
|
|
320
|
+
|
|
297
321
|
Generate keys automatically using built-in generators:
|
|
298
322
|
|
|
299
323
|
#### UUID Keys
|
|
324
|
+
|
|
300
325
|
```typescript
|
|
301
326
|
@DataClass()
|
|
302
327
|
class Document {
|
|
@@ -316,13 +341,16 @@ class Document {
|
|
|
316
341
|
}
|
|
317
342
|
}
|
|
318
343
|
|
|
319
|
-
const db = await Database.build(
|
|
344
|
+
const db = await Database.build('docs-db', [Document]);
|
|
320
345
|
|
|
321
|
-
const doc = await db.Document.create(
|
|
346
|
+
const doc = await db.Document.create(
|
|
347
|
+
new Document('tutorial', 'Getting Started', 'Welcome...'),
|
|
348
|
+
);
|
|
322
349
|
console.log(doc.uuid); // e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
|
323
350
|
```
|
|
324
351
|
|
|
325
352
|
#### Timestamp Keys
|
|
353
|
+
|
|
326
354
|
```typescript
|
|
327
355
|
@DataClass()
|
|
328
356
|
class Event {
|
|
@@ -340,11 +368,12 @@ class Event {
|
|
|
340
368
|
}
|
|
341
369
|
}
|
|
342
370
|
|
|
343
|
-
const event = await db.Event.create(new Event(
|
|
371
|
+
const event = await db.Event.create(new Event('user_login', { userId: '123' }));
|
|
344
372
|
console.log(event.timestamp); // e.g., 1696118400000
|
|
345
373
|
```
|
|
346
374
|
|
|
347
375
|
#### Random Keys
|
|
376
|
+
|
|
348
377
|
```typescript
|
|
349
378
|
@DataClass()
|
|
350
379
|
class Session {
|
|
@@ -360,17 +389,21 @@ class Session {
|
|
|
360
389
|
}
|
|
361
390
|
}
|
|
362
391
|
|
|
363
|
-
const session = await db.Session.create(new Session(
|
|
392
|
+
const session = await db.Session.create(new Session('user123', new Date()));
|
|
364
393
|
console.log(session.sessionId); // e.g., "xyz789abc123"
|
|
365
394
|
```
|
|
366
395
|
|
|
367
396
|
### Custom Key Generators
|
|
397
|
+
|
|
368
398
|
Create your own key generation logic:
|
|
369
399
|
|
|
370
400
|
```typescript
|
|
371
401
|
@DataClass()
|
|
372
402
|
class Invoice {
|
|
373
|
-
@KeyPath({
|
|
403
|
+
@KeyPath({
|
|
404
|
+
generator: (entity: any) =>
|
|
405
|
+
`INV-${entity.year}-${String(entity.number).padStart(4, '0')}`,
|
|
406
|
+
})
|
|
374
407
|
invoiceId!: string;
|
|
375
408
|
|
|
376
409
|
year!: number;
|
|
@@ -384,15 +417,16 @@ class Invoice {
|
|
|
384
417
|
}
|
|
385
418
|
}
|
|
386
419
|
|
|
387
|
-
const invoice = await db.Invoice.create(new Invoice(2024, 1, 1500.
|
|
420
|
+
const invoice = await db.Invoice.create(new Invoice(2024, 1, 1500.0));
|
|
388
421
|
console.log(invoice.invoiceId); // "INV-2024-0001"
|
|
389
422
|
```
|
|
390
423
|
|
|
391
424
|
### Composite Keys
|
|
425
|
+
|
|
392
426
|
Handle many-to-many relationships with composite keys using the `@CompositeKeyPath` decorator:
|
|
393
427
|
|
|
394
428
|
```typescript
|
|
395
|
-
import { CompositeKeyPath } from
|
|
429
|
+
import { CompositeKeyPath } from 'idb-ts';
|
|
396
430
|
|
|
397
431
|
@CompositeKeyPath(['userId', 'projectId'])
|
|
398
432
|
@DataClass()
|
|
@@ -413,12 +447,14 @@ class UserProject {
|
|
|
413
447
|
}
|
|
414
448
|
}
|
|
415
449
|
|
|
416
|
-
const db = await Database.build(
|
|
450
|
+
const db = await Database.build('collaboration-db', [UserProject]);
|
|
417
451
|
|
|
418
452
|
// Create relationships
|
|
419
|
-
await db.UserProject.create(
|
|
420
|
-
|
|
421
|
-
|
|
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'));
|
|
422
458
|
|
|
423
459
|
// Read with composite key
|
|
424
460
|
const relationship = await db.UserProject.read(['user123', 'project456']);
|
|
@@ -426,7 +462,7 @@ console.log(relationship?.role); // "developer"
|
|
|
426
462
|
|
|
427
463
|
// Update relationship
|
|
428
464
|
if (relationship) {
|
|
429
|
-
relationship.role =
|
|
465
|
+
relationship.role = 'maintainer';
|
|
430
466
|
await db.UserProject.update(relationship);
|
|
431
467
|
}
|
|
432
468
|
|
|
@@ -438,16 +474,113 @@ const developers = await db.UserProject.findByIndex('role', 'developer');
|
|
|
438
474
|
```
|
|
439
475
|
|
|
440
476
|
### Key Generation Utilities
|
|
477
|
+
|
|
441
478
|
Access key generators directly for your custom logic:
|
|
442
479
|
|
|
443
480
|
```typescript
|
|
444
|
-
import { KeyGenerators } from
|
|
481
|
+
import { KeyGenerators } from 'idb-ts';
|
|
445
482
|
|
|
446
|
-
const uuid = KeyGenerators.uuid();
|
|
483
|
+
const uuid = KeyGenerators.uuid(); // Generate UUID
|
|
447
484
|
const timestamp = KeyGenerators.timestamp(); // Current timestamp
|
|
448
|
-
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
|
+
});
|
|
449
502
|
```
|
|
450
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
|
+
}
|
|
515
|
+
```
|
|
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
|
+
|
|
451
584
|
---
|
|
452
585
|
|
|
453
586
|
## π Schema Versioning
|
|
@@ -481,7 +614,7 @@ class Comment {
|
|
|
481
614
|
}
|
|
482
615
|
|
|
483
616
|
// Database version will be 3 (highest entity version)
|
|
484
|
-
const db = await Database.build(
|
|
617
|
+
const db = await Database.build('blog', [User, Post, Comment]);
|
|
485
618
|
|
|
486
619
|
console.log(db.getDatabaseVersion()); // 3
|
|
487
620
|
console.log(db.getEntityVersions()); // Map with entity versions
|
|
@@ -504,17 +637,17 @@ const userVersion = db.getEntityVersion('User');
|
|
|
504
637
|
|
|
505
638
|
// Version upgrade flow:
|
|
506
639
|
// v1.0: User(v1) -> Database v1
|
|
507
|
-
// v1.1: User(v1), Post(v2) -> Database v2
|
|
640
|
+
// v1.1: User(v1), Post(v2) -> Database v2
|
|
508
641
|
// v1.2: User(v1), Post(v2), Comment(v3) -> Database v3
|
|
509
642
|
```
|
|
510
643
|
|
|
511
644
|
---
|
|
512
645
|
|
|
513
646
|
## π Useful Links
|
|
647
|
+
|
|
514
648
|
- π **GitHub**: [maifeeulasad/idb-ts](https://github.com/maifeeulasad/idb-ts)
|
|
515
649
|
- π¦ **NPM**: [idb-ts](https://www.npmjs.com/package/idb-ts)
|
|
516
650
|
- Demo: https://maifeeulasad.github.io/idb-ts/
|
|
517
651
|
- Code Coverage report: https://maifeeulasad.github.io/idb-ts/coverage/lcov-report/
|
|
518
652
|
|
|
519
653
|
π **Enjoy seamless IndexedDB integration with TypeScript! Happy coding!** π
|
|
520
|
-
|
package/lib/index.d.ts
CHANGED
|
@@ -1,9 +1,52 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
type QueryDirection = 'asc' | 'desc';
|
|
3
|
+
type QueryFieldKey<T> = Extract<keyof T, string>;
|
|
4
|
+
type ComparableValue = string | number | bigint | Date;
|
|
5
|
+
type ComparableFieldKey<T> = {
|
|
6
|
+
[K in QueryFieldKey<T>]-?: T[K] extends ComparableValue ? K : never;
|
|
7
|
+
}[QueryFieldKey<T>];
|
|
8
|
+
type NumericFieldKey<T> = {
|
|
9
|
+
[K in QueryFieldKey<T>]-?: T[K] extends number ? K : never;
|
|
10
|
+
}[QueryFieldKey<T>];
|
|
11
|
+
type StringFieldKey<T> = {
|
|
12
|
+
[K in QueryFieldKey<T>]-?: T[K] extends string ? K : never;
|
|
13
|
+
}[QueryFieldKey<T>];
|
|
14
|
+
type ArrayFieldKey<T> = {
|
|
15
|
+
[K in QueryFieldKey<T>]-?: T[K] extends readonly any[] ? K : never;
|
|
16
|
+
}[QueryFieldKey<T>];
|
|
17
|
+
type ArrayElement<T> = T extends readonly (infer U)[] ? U : never;
|
|
18
|
+
type ContainsValue<T> = T extends string ? string : T extends readonly (infer U)[] ? U : never;
|
|
19
|
+
type QueryOperator = 'equals' | 'gt' | 'gte' | 'lt' | 'lte' | 'startsWith' | 'endsWith' | 'contains' | 'matches' | 'between' | 'notBetween' | 'in' | 'notIn' | 'containsAny' | 'containsAll';
|
|
20
|
+
type GroupCountResult<T, K extends Extract<keyof T, string>> = Array<Record<K, T[K]> & {
|
|
21
|
+
count: number;
|
|
22
|
+
}>;
|
|
23
|
+
declare class FieldQueryBuilder<T, K extends QueryFieldKey<T>> {
|
|
24
|
+
private parent;
|
|
25
|
+
private field;
|
|
26
|
+
constructor(parent: QueryBuilder<T>, field: K);
|
|
27
|
+
equals(value: T[K]): QueryBuilder<T>;
|
|
28
|
+
gt(value: T[K] extends ComparableValue ? T[K] : never): QueryBuilder<T>;
|
|
29
|
+
gte(value: T[K] extends ComparableValue ? T[K] : never): QueryBuilder<T>;
|
|
30
|
+
lt(value: T[K] extends ComparableValue ? T[K] : never): QueryBuilder<T>;
|
|
31
|
+
lte(value: T[K] extends ComparableValue ? T[K] : never): QueryBuilder<T>;
|
|
32
|
+
startsWith(this: FieldQueryBuilder<T, StringFieldKey<T>>, value: string): QueryBuilder<T>;
|
|
33
|
+
endsWith(this: FieldQueryBuilder<T, StringFieldKey<T>>, value: string): QueryBuilder<T>;
|
|
34
|
+
contains(this: FieldQueryBuilder<T, StringFieldKey<T>> | FieldQueryBuilder<T, ArrayFieldKey<T>>, value: ContainsValue<T[K]>): QueryBuilder<T>;
|
|
35
|
+
matches(this: FieldQueryBuilder<T, StringFieldKey<T>>, value: RegExp | string): QueryBuilder<T>;
|
|
36
|
+
between(start: T[K] extends ComparableValue ? T[K] : never, end: T[K] extends ComparableValue ? T[K] : never): QueryBuilder<T>;
|
|
37
|
+
notBetween(start: T[K] extends ComparableValue ? T[K] : never, end: T[K] extends ComparableValue ? T[K] : never): QueryBuilder<T>;
|
|
38
|
+
in(values: Array<T[K]>): QueryBuilder<T>;
|
|
39
|
+
notIn(values: Array<T[K]>): QueryBuilder<T>;
|
|
40
|
+
containsAny(this: FieldQueryBuilder<T, ArrayFieldKey<T>>, values: Array<ArrayElement<T[K]>>): QueryBuilder<T>;
|
|
41
|
+
containsAll(this: FieldQueryBuilder<T, ArrayFieldKey<T>>, values: Array<ArrayElement<T[K]>>): QueryBuilder<T>;
|
|
42
|
+
and<K2 extends QueryFieldKey<T>>(field: K2): FieldQueryBuilder<T, K2>;
|
|
43
|
+
or(): QueryBuilder<T>;
|
|
44
|
+
}
|
|
3
45
|
declare class QueryBuilder<T> {
|
|
4
46
|
private db;
|
|
5
47
|
private storeName;
|
|
6
|
-
private
|
|
48
|
+
private transaction?;
|
|
49
|
+
private clauses;
|
|
7
50
|
private orderField?;
|
|
8
51
|
private orderDirection;
|
|
9
52
|
private limitCount?;
|
|
@@ -12,20 +55,58 @@ declare class QueryBuilder<T> {
|
|
|
12
55
|
private rangeStart?;
|
|
13
56
|
private rangeEnd?;
|
|
14
57
|
private currentField?;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
58
|
+
private groupField?;
|
|
59
|
+
private pendingConnector;
|
|
60
|
+
constructor(db: IDBDatabase, storeName: string, transaction?: IDBTransaction);
|
|
61
|
+
where<K extends QueryFieldKey<T>>(field: K): FieldQueryBuilder<T, K>;
|
|
62
|
+
where(builder: (query: QueryBuilder<T>) => QueryBuilder<T> | void): this;
|
|
63
|
+
and<K extends QueryFieldKey<T>>(field: K): FieldQueryBuilder<T, K>;
|
|
64
|
+
and(builder: (query: QueryBuilder<T>) => QueryBuilder<T> | void): this;
|
|
65
|
+
or(): this;
|
|
66
|
+
private addNestedGroup;
|
|
67
|
+
clearCurrentField(): void;
|
|
68
|
+
appendCondition(field: string, op: QueryOperator, value: any): void;
|
|
69
|
+
private appendClause;
|
|
70
|
+
private requireCurrentField;
|
|
71
|
+
private addCondition;
|
|
18
72
|
equals(value: any): this;
|
|
19
73
|
gt(value: any): this;
|
|
20
74
|
gte(value: any): this;
|
|
21
75
|
lt(value: any): this;
|
|
22
76
|
lte(value: any): this;
|
|
77
|
+
startsWith(value: string): this;
|
|
78
|
+
endsWith(value: string): this;
|
|
79
|
+
contains(value: any): this;
|
|
80
|
+
matches(value: RegExp | string): this;
|
|
81
|
+
between(start: any, end: any): this;
|
|
82
|
+
notBetween(start: any, end: any): this;
|
|
83
|
+
['in'](values: any[]): this;
|
|
84
|
+
notIn(values: any[]): this;
|
|
85
|
+
containsAny(values: any[]): this;
|
|
86
|
+
containsAll(values: any[]): this;
|
|
23
87
|
orderBy(field: Extract<keyof T, string>, direction?: QueryDirection): this;
|
|
24
88
|
limit(n: number): this;
|
|
25
89
|
offset(n: number): this;
|
|
26
90
|
useIndex(indexName: string): this;
|
|
27
91
|
range(start: any, end: any): this;
|
|
92
|
+
groupBy<K extends QueryFieldKey<T>>(field: K): QueryBuilder<T> & {
|
|
93
|
+
count(): Promise<GroupCountResult<T, K>>;
|
|
94
|
+
};
|
|
95
|
+
private loadCandidates;
|
|
96
|
+
private createReadRequest;
|
|
97
|
+
private matchesClause;
|
|
98
|
+
private evaluateClauses;
|
|
99
|
+
private collectMatches;
|
|
100
|
+
private sortResults;
|
|
101
|
+
private applyPagination;
|
|
102
|
+
private aggregateValues;
|
|
103
|
+
private aggregateGroupedCount;
|
|
28
104
|
execute(): Promise<T[]>;
|
|
105
|
+
count(): Promise<number | GroupCountResult<T, Extract<keyof T, string>>>;
|
|
106
|
+
sum(field: NumericFieldKey<T>): Promise<number>;
|
|
107
|
+
avg(field: NumericFieldKey<T>): Promise<number>;
|
|
108
|
+
min(field: ComparableFieldKey<T>): Promise<T[typeof field] | null>;
|
|
109
|
+
max(field: ComparableFieldKey<T>): Promise<T[typeof field] | null>;
|
|
29
110
|
}
|
|
30
111
|
interface KeyPathOptions {
|
|
31
112
|
autoIncrement?: boolean;
|
|
@@ -77,6 +158,11 @@ interface EntityRepository<T> {
|
|
|
77
158
|
clear(): Promise<void>;
|
|
78
159
|
query(): QueryBuilder<T>;
|
|
79
160
|
}
|
|
161
|
+
interface TransactionController {
|
|
162
|
+
commit(): Promise<void>;
|
|
163
|
+
rollback(): Promise<void>;
|
|
164
|
+
}
|
|
165
|
+
type TransactionalDatabase<T extends Record<string, EntityRepository<any>>> = T & TransactionController;
|
|
80
166
|
type DatabaseWithRepositories<T extends Record<string, any>> = Database & T;
|
|
81
167
|
declare class Database {
|
|
82
168
|
private dbName;
|
|
@@ -98,11 +184,14 @@ declare class Database {
|
|
|
98
184
|
private cleanupExpiredRecords;
|
|
99
185
|
private createEntityRepository;
|
|
100
186
|
private performOperation;
|
|
187
|
+
private createTransactionHandle;
|
|
188
|
+
beginTransaction(entityNames: string[], mode?: IDBTransactionMode): Promise<TransactionalDatabase<Record<string, EntityRepository<any>>>>;
|
|
189
|
+
transaction<T>(callback: (tx: TransactionalDatabase<Record<string, EntityRepository<any>>>) => Promise<T> | T): Promise<T>;
|
|
101
190
|
close(): void;
|
|
102
191
|
getAvailableEntities(): string[];
|
|
103
192
|
getDatabaseVersion(): number;
|
|
104
193
|
getEntityVersions(): Map<string, number>;
|
|
105
194
|
getEntityVersion(entityName: string): number | undefined;
|
|
106
195
|
}
|
|
107
|
-
export { Database, KeyPath, CompositeKeyPath, DataClass, Index, Validate, RetentionPolicy, EntityRepository, KeyGenerators };
|
|
108
|
-
export type { DatabaseWithRepositories, DataClassOptions, KeyPathOptions, KeyPathMetadata, RetentionPolicyOptions, RetentionPolicyMetadata };
|
|
196
|
+
export { Database, KeyPath, CompositeKeyPath, DataClass, Index, Validate, RetentionPolicy, EntityRepository, KeyGenerators, };
|
|
197
|
+
export type { DatabaseWithRepositories, DataClassOptions, KeyPathOptions, KeyPathMetadata, RetentionPolicyOptions, RetentionPolicyMetadata, };
|