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 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 "idb-ts";
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("idb-crud", [User, Location]);
103
+ const db = await Database.build('idb-crud', [User, Location]);
99
104
 
100
- const alice = new User("u1", "Alice", 25);
101
- const bob = new User("u2", "Bob", 30);
102
- const nyc = new Location("1", "New York", "USA");
103
- const sf = new Location("2", "San Francisco", "USA");
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("u1");
111
- console.log("πŸ‘€ Read user:", readAlice);
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("πŸ“‹ All users:", users);
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("πŸ“„ Page 1:", page1);
126
+ console.log('πŸ“„ Page 1:', page1);
122
127
 
123
- await db.User.delete("u1");
124
- console.log("❌ User Alice deleted.");
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("πŸ” Remaining users:", remainingUsers);
132
+ console.log('πŸ” Remaining users:', remainingUsers);
128
133
 
129
134
  const locations = await db.Location.list();
130
- console.log("🌍 All locations:", locations);
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(id: string, category: string, price: number, name: string, description: string) {
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("products-db", [Product]);
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('category', 'Electronics');
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((v) => typeof v === 'string' && v.includes('@'), 'must be a valid email')
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("tasks-db", [Task]);
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("Learn TypeScript"));
291
- const task2 = await db.Task.create(new Task("Build amazing apps"));
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("docs-db", [Document]);
344
+ const db = await Database.build('docs-db', [Document]);
320
345
 
321
- const doc = await db.Document.create(new Document("tutorial", "Getting Started", "Welcome..."));
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("user_login", { userId: "123" }));
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("user123", new Date()));
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({ generator: (entity: any) => `INV-${entity.year}-${String(entity.number).padStart(4, '0')}` })
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.00));
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 "idb-ts";
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("collaboration-db", [UserProject]);
450
+ const db = await Database.build('collaboration-db', [UserProject]);
417
451
 
418
452
  // Create relationships
419
- await db.UserProject.create(new UserProject("user123", "project456", "developer"));
420
- await db.UserProject.create(new UserProject("user123", "project789", "admin"));
421
- await db.UserProject.create(new UserProject("user456", "project456", "viewer"));
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 = "maintainer";
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 "idb-ts";
481
+ import { KeyGenerators } from 'idb-ts';
445
482
 
446
- const uuid = KeyGenerators.uuid(); // Generate UUID
483
+ const uuid = KeyGenerators.uuid(); // Generate UUID
447
484
  const timestamp = KeyGenerators.timestamp(); // Current timestamp
448
- const random = KeyGenerators.random(); // Random string
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("blog", [User, Post, Comment]);
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 conditions;
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
- constructor(db: IDBDatabase, storeName: string);
16
- where(field: Extract<keyof T, string>): this;
17
- and(field: Extract<keyof T, string>): this;
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, };