locality-idb 1.0.1 → 1.1.1

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
@@ -128,7 +128,7 @@ const users = await db.from('users').findAll();
128
128
  const alice = await db
129
129
  .from('users')
130
130
  .where((user) => user.email === 'alice@example.com')
131
- .first();
131
+ .findFirst();
132
132
 
133
133
  // Update data
134
134
  await db
@@ -400,7 +400,7 @@ const topTenUsers = await db
400
400
  const user = await db
401
401
  .from('users')
402
402
  .where((user) => user.email === 'john@example.com')
403
- .first();
403
+ .findFirst();
404
404
  // Returns: User | null
405
405
  ```
406
406
 
@@ -467,7 +467,7 @@ const names = await db
467
467
  .findAll();
468
468
  ```
469
469
 
470
- > **Note:** `sortByIndex()` uses IndexedDB cursor iteration for optimal performance when no `where()` filter is applied.
470
+ > **Note:** `sortByIndex()` uses IndexedDB cursor iteration for optimal performance when `where()` filter is applied without index.
471
471
 
472
472
  #### Chain Multiple Methods
473
473
 
@@ -787,6 +787,32 @@ Filters rows based on a predicate function.
787
787
  db.from('users').where((user) => user.age >= 18)
788
788
  ```
789
789
 
790
+ ##### `where<IdxKey>(indexName: IdxKey, query: T[IdxKey] | IDBKeyRange): SelectQuery`
791
+
792
+ Filters rows using an indexed field.
793
+
794
+ ```typescript
795
+ db.from('users').where('age', IDBKeyRange.bound(18, 30))
796
+ ```
797
+
798
+ ##### `sortByIndex<IdxKey>(indexName: IdxKey, dir?: 'asc' | 'desc'): SelectQuery`
799
+
800
+ Sorts results by an indexed field using IndexedDB cursor iteration (avoiding in-memory sorting).
801
+
802
+ **Type Safety:** `indexName` must be a field with an index.
803
+
804
+ **Performance:** Uses IndexedDB's cursor for optimized sorting. For large datasets, this is significantly more efficient than in-memory sorting.
805
+
806
+ > For sorting on non-indexed fields, use [`orderBy()`](#orderbykeykey-key-direction-asc--desc-selectquery) which performs in-memory sorting.
807
+
808
+ ```typescript
809
+ // Optimized cursor-based sort
810
+ const sorted = await db.from('users').sortByIndex('age', 'desc').findAll();
811
+
812
+ // Efficient pagination
813
+ const page = await db.from('users').sortByIndex('createdAt', 'desc').limit(20).findAll();
814
+ ```
815
+
790
816
  ##### `orderBy<Key>(key: Key, direction?: 'asc' | 'desc'): SelectQuery`
791
817
 
792
818
  Orders results by a specified key. Supports nested keys using dot notation.
@@ -796,6 +822,8 @@ db.from('users').orderBy('name', 'asc')
796
822
  db.from('users').orderBy('profile.age', 'desc')
797
823
  ```
798
824
 
825
+ > **Note:** This method performs in-memory sorting. For large datasets, consider using [`sortByIndex()`](#sortbyindexidxkeyindexname-idxkey-dir-asc--desc-selectquery) with an indexed field for better performance.
826
+
799
827
  ##### `limit(count: number): SelectQuery`
800
828
 
801
829
  Limits the number of results.
@@ -812,12 +840,12 @@ Fetches all matching records.
812
840
  const users = await db.from('users').findAll()
813
841
  ```
814
842
 
815
- ##### `first(): Promise<T | null>`
843
+ ##### `findFirst(): Promise<T | null>`
816
844
 
817
845
  Fetches the first matching record.
818
846
 
819
847
  ```typescript
820
- const user = await db.from('users').first()
848
+ const user = await db.from('users').findFirst()
821
849
  ```
822
850
 
823
851
  ##### `findByPk<Key>(key: Key): Promise<T | null>`
@@ -828,10 +856,10 @@ Finds a single record by its primary key value using IndexedDB's optimized `get(
828
856
 
829
857
  ```typescript
830
858
  const user = await db.from('users').findByPk(1);
831
- const post = await db.from('posts').findByPk('uuid-string');
859
+ const post = await db.from('posts').findByPk('some-uuid-string');
832
860
  ```
833
861
 
834
- ##### `findByIndex<IndexKey>(indexName: IndexKey, query: T[IndexKey] | IDBKeyRange): Promise<T[]>`
862
+ ##### `findByIndex<IdxKey>(indexName: IdxKey, query: T[IdxKey] | IDBKeyRange): Promise<T[]>`
835
863
 
836
864
  Finds records using an indexed field. Only accepts field names that are marked with `.index()` or `.unique()`.
837
865
 
@@ -852,22 +880,31 @@ const adults = await db.from('users').findByIndex('age', IDBKeyRange.bound(18, 6
852
880
  > - Unique columns are automatically indexed.
853
881
  > - Unique indexes are recommended for this method to ensure a single result.
854
882
 
855
- ##### `sortByIndex<IndexKey>(indexName: IndexKey, dir?: 'asc' | 'desc'): SelectQuery`
883
+ ##### `count(): Promise<number>`
856
884
 
857
- Sorts results by an indexed field using IndexedDB cursor iteration (avoiding in-memory sorting).
885
+ Counts the number of matching records.
858
886
 
859
- **Type Safety:** `indexName` must be a field with an index.
887
+ ```typescript
888
+ const userCount = await db.from('users').where((user) => user.isActive).count()
889
+ ```
860
890
 
861
- **Performance:** When no `where()` filter is applied, uses optimized cursor iteration. Otherwise falls back to in-memory sorting.
891
+ > **Note:**
892
+ >
893
+ > - This method internally uses IndexedDB's `count()` for optimal performance.
894
+ > - If a `where()` filter is applied without index, it falls back to in-memory counting.
862
895
 
863
- ```typescript
864
- // Optimized cursor-based sort
865
- const sorted = await db.from('users').sortByIndex('age', 'desc').findAll();
896
+ ##### `exists(): Promise<boolean>`
866
897
 
867
- // Efficient pagination
868
- const page = await db.from('users').sortByIndex('createdAt', 'desc').limit(20).findAll();
898
+ Checks if any matching records exist.
899
+
900
+ ```typescript
901
+ const hasAdmins = await db.from('users').where((user) => user.role === 'admin').exists()
869
902
  ```
870
903
 
904
+ > **Note:** This method internally uses [`count()`](#count-promisenumber) for checking existence.
905
+
906
+ ---
907
+
871
908
  #### InsertQuery Methods
872
909
 
873
910
  ##### `values<T>(data: T | T[]): InsertQuery`
@@ -913,6 +950,8 @@ Executes the update query and returns the number of updated records.
913
950
  const count = await db.update('users').set({ name: 'Jane' }).run()
914
951
  ```
915
952
 
953
+ ---
954
+
916
955
  #### DeleteQuery Methods
917
956
 
918
957
  ##### `where(predicate: (row: T) => boolean): DeleteQuery`
package/dist/index.cjs CHANGED
@@ -12,6 +12,9 @@ function isInteger(value) {
12
12
  function isBoolean(value) {
13
13
  return typeof value === "boolean";
14
14
  }
15
+ function isUndefined(value) {
16
+ return value === void 0;
17
+ }
15
18
  function isBigInt(value) {
16
19
  return typeof value === "bigint";
17
20
  }
@@ -33,6 +36,9 @@ function isObject(value) {
33
36
  function isNotEmptyObject(value) {
34
37
  return isObject(value) && Object.keys(value)?.length > 0;
35
38
  }
39
+ function isFunction(value) {
40
+ return typeof value === "function";
41
+ }
36
42
  function isDate(value) {
37
43
  return value instanceof Date;
38
44
  }
@@ -456,6 +462,8 @@ var SelectQuery = class {
456
462
  #readyPromise;
457
463
  #dbGetter;
458
464
  #whereCondition;
465
+ #whereIndexName;
466
+ #whereIndexQuery;
459
467
  #orderByKey;
460
468
  #orderByDir = "asc";
461
469
  #limitCount;
@@ -465,6 +473,49 @@ var SelectQuery = class {
465
473
  this.#dbGetter = dbGetter;
466
474
  this.#readyPromise = readyPromise;
467
475
  }
476
+ #createTransaction() {
477
+ return this.#dbGetter().transaction(this.#table, "readonly");
478
+ }
479
+ #getStoreWithTransaction() {
480
+ return this.#createTransaction().objectStore(this.#table);
481
+ }
482
+ /** @internal Check if key is an index on the store for the `#whereIndexName` */
483
+ #isIndexKey(store) {
484
+ return isNonEmptyString(this.#whereIndexName) && store.indexNames.contains(this.#whereIndexName);
485
+ }
486
+ /** @internal Check if key is the primary key on the store for the `#whereIndexName` */
487
+ #isPrimaryKey(store) {
488
+ return isNonEmptyString(this.#whereIndexName) && store.keyPath === this.#whereIndexName;
489
+ }
490
+ /** @internal Build indexed store (primary key or index) for where queries */
491
+ #buildIndexedStore(store, reject) {
492
+ const isPK = this.#isPrimaryKey(store);
493
+ const isIndex = this.#isIndexKey(store);
494
+ if (!isPK && !isIndex) {
495
+ reject(/* @__PURE__ */ new RangeError(`Index '${this.#whereIndexName}' does not exist on table '${this.#table}'`));
496
+ return null;
497
+ }
498
+ return isPK ? store : store.index(this.#whereIndexName);
499
+ }
500
+ /** @internal Sort data in memory if needed */
501
+ #sort(data) {
502
+ if (this.#orderByKey) return sortAnArray(data, {
503
+ sortOrder: this.#orderByDir,
504
+ sortByField: this.#orderByKey
505
+ });
506
+ return data;
507
+ }
508
+ /** Projects a row based on selected fields */
509
+ #projectRow(row) {
510
+ if (!isNotEmptyObject(this?.[Selected])) return row;
511
+ const projected = {};
512
+ const selectionEntries = Object.entries(this[Selected]);
513
+ const selectionKeys = new Set(Object.keys(this[Selected]));
514
+ if (selectionEntries.some(([, value]) => value === true)) {
515
+ for (const [key, value] of selectionEntries) if (value === true) projected[key] = row[key];
516
+ } else for (const key of Object.keys(row)) if (!selectionKeys.has(key) || this[Selected][key] !== false) projected[key] = row[key];
517
+ return projected;
518
+ }
468
519
  /**
469
520
  * @instance Select or exclude specific columns
470
521
  * @param cols Columns to select or exclude
@@ -473,18 +524,26 @@ var SelectQuery = class {
473
524
  this[Selected] = cols;
474
525
  return this;
475
526
  }
476
- /**
477
- * @instance Filter rows based on predicate function
478
- * @param predicate Filtering function
479
- */
480
- where(predicate) {
481
- this.#whereCondition = predicate;
527
+ where(condition, query) {
528
+ if (isFunction(condition)) {
529
+ this.#whereCondition = condition;
530
+ this.#whereIndexName = void 0;
531
+ this.#whereIndexQuery = void 0;
532
+ } else if (isNonEmptyString(condition) && !isUndefined(query)) {
533
+ this.#whereIndexName = condition;
534
+ this.#whereIndexQuery = query;
535
+ this.#whereCondition = void 0;
536
+ }
482
537
  return this;
483
538
  }
484
539
  /**
485
540
  * @instance Order results by specified key and direction
486
541
  * @param key Key to order by
487
542
  * @param dir Direction: 'asc' | 'desc' (default: 'asc')
543
+ *
544
+ * @remarks
545
+ * - This method performs in-memory sorting.
546
+ * - For optimized sorting using `IndexedDB` indexes, use {@link sortByIndex} instead.
488
547
  */
489
548
  orderBy(key, dir = "asc") {
490
549
  this.#orderByKey = key;
@@ -492,6 +551,22 @@ var SelectQuery = class {
492
551
  return this;
493
552
  }
494
553
  /**
554
+ * @instance Order results by index using optimized `IndexedDB` cursor
555
+ * @param indexName Name of the index to sort by
556
+ * @param dir Direction: 'asc' | 'desc' (default: 'asc')
557
+ *
558
+ * @remarks
559
+ * - This method uses `IndexedDB` indexes for sorting, which is more efficient for large datasets.
560
+ * - Ensure that the specified index exists on the table.
561
+ * - For in-memory sorting, use {@link orderBy} instead.
562
+ */
563
+ sortByIndex(indexName, dir = "asc") {
564
+ this.#orderByKey = indexName;
565
+ this.#orderByDir = dir;
566
+ this.#useIndexCursor = true;
567
+ return this;
568
+ }
569
+ /**
495
570
  * @instance Limit number of results
496
571
  * @param count Maximum number of results to return
497
572
  */
@@ -499,21 +574,23 @@ var SelectQuery = class {
499
574
  this.#limitCount = count;
500
575
  return this;
501
576
  }
502
- /** Projects a row based on selected fields */
503
- #projectRow(row) {
504
- if (!isNotEmptyObject(this?.[Selected])) return row;
505
- const projected = {};
506
- const selectionEntries = Object.entries(this[Selected]);
507
- const selectionKeys = new Set(Object.keys(this[Selected]));
508
- if (selectionEntries.some(([, value]) => value === true)) {
509
- for (const [key, value] of selectionEntries) if (value === true) projected[key] = row[key];
510
- } else for (const key of Object.keys(row)) if (!selectionKeys.has(key) || this[Selected][key] !== false) projected[key] = row[key];
511
- return projected;
512
- }
513
577
  async findAll() {
514
578
  await this.#readyPromise;
515
579
  return new Promise((resolve, reject) => {
516
- const store = this.#dbGetter().transaction(this.#table, "readonly").objectStore(this.#table);
580
+ const store = this.#getStoreWithTransaction();
581
+ if (this.#whereIndexName && !isUndefined(this.#whereIndexQuery)) {
582
+ const source = this.#buildIndexedStore(store, reject);
583
+ if (!source) return;
584
+ const request = source.getAll(this.#whereIndexQuery);
585
+ request.onsuccess = () => {
586
+ let results = request.result;
587
+ results = this.#sort(results);
588
+ if (this.#limitCount) results = results.slice(0, this.#limitCount);
589
+ resolve(results.map((row) => this.#projectRow(row)));
590
+ };
591
+ request.onerror = () => reject(request.error);
592
+ return;
593
+ }
517
594
  if (this.#useIndexCursor && this.#orderByKey && isNonEmptyString(this.#orderByKey) && store.indexNames.contains(this.#orderByKey) && !this.#whereCondition) {
518
595
  const index = store.index(this.#orderByKey);
519
596
  const direction = this.#orderByDir === "desc" ? "prev" : "next";
@@ -546,10 +623,23 @@ var SelectQuery = class {
546
623
  }
547
624
  });
548
625
  }
549
- async first() {
626
+ async findFirst() {
550
627
  await this.#readyPromise;
551
628
  return new Promise((resolve, reject) => {
552
- const request = this.#dbGetter().transaction(this.#table, "readonly").objectStore(this.#table).getAll();
629
+ const store = this.#getStoreWithTransaction();
630
+ if (this.#whereIndexName && !isUndefined(this.#whereIndexQuery)) {
631
+ const source = this.#buildIndexedStore(store, reject);
632
+ if (!source) return;
633
+ const request = source.getAll(this.#whereIndexQuery);
634
+ request.onsuccess = () => {
635
+ const results = request.result;
636
+ if (results.length > 0) resolve(this.#projectRow(results[0]));
637
+ else resolve(null);
638
+ };
639
+ request.onerror = () => reject(request.error);
640
+ return;
641
+ }
642
+ const request = store.getAll();
553
643
  request.onsuccess = () => {
554
644
  let results = request.result;
555
645
  if (this.#whereCondition) results = results.filter(this.#whereCondition);
@@ -562,11 +652,16 @@ var SelectQuery = class {
562
652
  /**
563
653
  * @instance Find record by primary key (optimized `IndexedDB` get)
564
654
  * @param key Primary key value
655
+ *
656
+ * @remarks
657
+ * - This method uses the `IndexedDB` primary key for efficient querying.
658
+ * - Ensure that the specified key exists on the table.
659
+ * - To find by index, use {@link findByIndex} instead.
565
660
  */
566
661
  async findByPk(key) {
567
662
  await this.#readyPromise;
568
663
  return new Promise((resolve, reject) => {
569
- const request = this.#dbGetter().transaction(this.#table, "readonly").objectStore(this.#table).get(key);
664
+ const request = this.#getStoreWithTransaction().get(key);
570
665
  request.onsuccess = () => {
571
666
  const result = request.result;
572
667
  if (!result) {
@@ -583,16 +678,21 @@ var SelectQuery = class {
583
678
  });
584
679
  }
585
680
  /**
586
- * @instance Find records by index (optimized IndexedDB index query)
681
+ * @instance Find records by index (optimized `IndexedDB` index query)
587
682
  * @param indexName Name of the index to query
588
683
  * @param query Key value to search for
684
+ *
685
+ * @remarks
686
+ * - This method uses `IndexedDB` indexes for efficient querying.
687
+ * - Ensure that the specified index exists on the table.
688
+ * - To find by primary key, use {@link findByPk} instead.
589
689
  */
590
690
  async findByIndex(indexName, query) {
591
691
  await this.#readyPromise;
592
692
  return new Promise((resolve, reject) => {
593
- const store = this.#dbGetter().transaction(this.#table, "readonly").objectStore(this.#table);
693
+ const store = this.#getStoreWithTransaction();
594
694
  if (!store.indexNames.contains(indexName)) {
595
- reject(/* @__PURE__ */ new Error(`Index "${indexName}" does not exist on table "${this.#table}"`));
695
+ reject(/* @__PURE__ */ new Error(`Index '${indexName}' does not exist on table '${this.#table}'`));
596
696
  return;
597
697
  }
598
698
  const request = store.index(indexName).getAll(query);
@@ -606,24 +706,34 @@ var SelectQuery = class {
606
706
  request.onerror = () => reject(request.error);
607
707
  });
608
708
  }
609
- /**
610
- * @instance Order results by index using optimized IndexedDB cursor
611
- * @param indexName Name of the index to sort by
612
- * @param dir Direction: 'asc' | 'desc' (default: 'asc')
613
- */
614
- sortByIndex(indexName, dir = "asc") {
615
- this.#orderByKey = indexName;
616
- this.#orderByDir = dir;
617
- this.#useIndexCursor = true;
618
- return this;
619
- }
620
- /** @internal Sort data in memory if needed */
621
- #sort(data) {
622
- if (this.#orderByKey) return sortAnArray(data, {
623
- sortOrder: this.#orderByDir,
624
- sortByField: this.#orderByKey
709
+ /** @instance Count matching records */
710
+ async count() {
711
+ await this.#readyPromise;
712
+ return new Promise((resolve, reject) => {
713
+ const store = this.#getStoreWithTransaction();
714
+ if (this.#whereIndexName && !isUndefined(this.#whereIndexQuery)) {
715
+ const source = this.#buildIndexedStore(store, reject);
716
+ if (!source) return;
717
+ const request = source.count(this.#whereIndexQuery);
718
+ request.onsuccess = () => resolve(request.result);
719
+ request.onerror = () => reject(request.error);
720
+ return;
721
+ }
722
+ if (this.#whereCondition) {
723
+ const request = store.getAll();
724
+ request.onsuccess = () => {
725
+ resolve(request.result.filter(this.#whereCondition).length);
726
+ };
727
+ request.onerror = () => reject(request.error);
728
+ return;
729
+ }
730
+ const request = store.count();
731
+ request.onsuccess = () => resolve(request.result);
732
+ request.onerror = () => reject(request.error);
625
733
  });
626
- return data;
734
+ }
735
+ async exists() {
736
+ return await this.count() > 0;
627
737
  }
628
738
  };
629
739
  /** @class Insert query builder. */
package/dist/index.d.cts CHANGED
@@ -329,6 +329,8 @@ type $InferTimestamp<T extends ColumnDefinition> = { [K in keyof T]: T[K] extend
329
329
  type Timestamp = Branded<string, 'Timestamp'>;
330
330
  /** Sort direction type for ordering queries */
331
331
  type SortDirection = 'asc' | 'desc';
332
+ /** Predicate function type for WHERE clauses in queries */
333
+ type WherePredicate<T extends GenericObject> = (row: T) => boolean;
332
334
  /** Creates a type for insert operations with auto-generated fields optional. */
333
335
  type InferInsertType<T extends Table> = Prettify<Omit<$InferRow<T['columns']>, $InferAutoInc<T['columns']> | $InferDefault<T['columns']> | $InferTimestamp<T['columns']> | $InferUUID<T['columns']>> & { [K in $InferAutoInc<T['columns']> | $InferDefault<T['columns']> | $InferTimestamp<T['columns']> | $InferUUID<T['columns']>]?: K extends keyof $InferRow<T['columns']> ? $InferRow<T['columns']>[K] : never }>;
334
336
  /** Creates a type for update operations with all fields optional except primary key. */
@@ -376,13 +378,34 @@ declare class SelectQuery<T extends GenericObject, S extends Partial<Record<stri
376
378
  * @instance Filter rows based on predicate function
377
379
  * @param predicate Filtering function
378
380
  */
379
- where(predicate: (row: T) => boolean): this;
381
+ where(predicate: WherePredicate<T>): this;
382
+ /**
383
+ * @instance Filter rows based on index query
384
+ * @param indexName Name of the index/primary key to query
385
+ * @param query Key value or {@link IDBKeyRange} to search for
386
+ */
387
+ where<IdxKey extends $InferPrimaryKey<Tbl['columns']> | $InferIndex<Tbl['columns']>>(indexName: IdxKey, query: IDBKeyRange | T[IdxKey]): this;
380
388
  /**
381
389
  * @instance Order results by specified key and direction
382
390
  * @param key Key to order by
383
391
  * @param dir Direction: 'asc' | 'desc' (default: 'asc')
392
+ *
393
+ * @remarks
394
+ * - This method performs in-memory sorting.
395
+ * - For optimized sorting using `IndexedDB` indexes, use {@link sortByIndex} instead.
384
396
  */
385
397
  orderBy<Key extends NestedPrimitiveKey<T>>(key: Key, dir?: SortDirection): this;
398
+ /**
399
+ * @instance Order results by index using optimized `IndexedDB` cursor
400
+ * @param indexName Name of the index to sort by
401
+ * @param dir Direction: 'asc' | 'desc' (default: 'asc')
402
+ *
403
+ * @remarks
404
+ * - This method uses `IndexedDB` indexes for sorting, which is more efficient for large datasets.
405
+ * - Ensure that the specified index exists on the table.
406
+ * - For in-memory sorting, use {@link orderBy} instead.
407
+ */
408
+ sortByIndex<IdxKey extends $InferIndex<Tbl['columns']> | $InferPrimaryKey<Tbl['columns']>>(indexName: IdxKey, dir?: SortDirection): this;
386
409
  /**
387
410
  * @instance Limit number of results
388
411
  * @param count Maximum number of results to return
@@ -393,26 +416,33 @@ declare class SelectQuery<T extends GenericObject, S extends Partial<Record<stri
393
416
  /** Fetch all matching records with selected fields */
394
417
  findAll<Selection extends Partial<Record<keyof T, boolean>>>(this: SelectQuery<T, Selection>): Promise<SelectFields<T, Selection>[]>;
395
418
  /** Fetch first matching record */
396
- first(this: SelectQuery<T, null>): Promise<T | null>;
419
+ findFirst(this: SelectQuery<T, null>): Promise<T | null>;
397
420
  /** Fetch first matching record with selected fields */
398
- first<Selection extends Partial<Record<keyof T, boolean>>>(this: SelectQuery<T, Selection>): Promise<SelectFields<T, Selection> | null>;
421
+ findFirst<Selection extends Partial<Record<keyof T, boolean>>>(this: SelectQuery<T, Selection>): Promise<SelectFields<T, Selection> | null>;
399
422
  /**
400
423
  * @instance Find record by primary key (optimized `IndexedDB` get)
401
424
  * @param key Primary key value
425
+ *
426
+ * @remarks
427
+ * - This method uses the `IndexedDB` primary key for efficient querying.
428
+ * - Ensure that the specified key exists on the table.
429
+ * - To find by index, use {@link findByIndex} instead.
402
430
  */
403
431
  findByPk(key: $InferPrimaryKey<Tbl['columns']> extends keyof T ? T[$InferPrimaryKey<Tbl['columns']>] : T[keyof T]): Promise<S extends null ? T | null : S extends Partial<Record<keyof T, boolean>> ? SelectFields<T, S> | null : never>;
404
432
  /**
405
- * @instance Find records by index (optimized IndexedDB index query)
433
+ * @instance Find records by index (optimized `IndexedDB` index query)
406
434
  * @param indexName Name of the index to query
407
435
  * @param query Key value to search for
408
- */
409
- findByIndex<IndexKey extends $InferIndex<Tbl['columns']> & keyof T & string>(indexName: IndexKey, query: T[IndexKey] | IDBKeyRange): Promise<S extends null ? T[] : S extends Partial<Record<keyof T, boolean>> ? SelectFields<T, S>[] : never>;
410
- /**
411
- * @instance Order results by index using optimized IndexedDB cursor
412
- * @param indexName Name of the index to sort by
413
- * @param dir Direction: 'asc' | 'desc' (default: 'asc')
414
- */
415
- sortByIndex<IndexKey extends ($InferIndex<Tbl['columns']> | $InferPrimaryKey<Tbl['columns']>) & keyof T & string>(indexName: IndexKey, dir?: SortDirection): this;
436
+ *
437
+ * @remarks
438
+ * - This method uses `IndexedDB` indexes for efficient querying.
439
+ * - Ensure that the specified index exists on the table.
440
+ * - To find by primary key, use {@link findByPk} instead.
441
+ */
442
+ findByIndex<IdxKey extends $InferIndex<Tbl['columns']> & keyof T & string>(indexName: IdxKey, query: T[IdxKey] | IDBKeyRange): Promise<S extends null ? T[] : S extends Partial<Record<keyof T, boolean>> ? SelectFields<T, S>[] : never>;
443
+ /** @instance Count matching records */
444
+ count(): Promise<number>;
445
+ exists(): Promise<boolean>;
416
446
  }
417
447
  /** @class Insert query builder. */
418
448
  declare class InsertQuery<Raw extends GenericObject, Inserted extends Raw | Raw[], Data extends GenericObject, Return extends (Inserted extends Array<infer _> ? Data[] : Data)> {
@@ -848,4 +878,4 @@ declare function deleteDB(name: string): Promise<void>;
848
878
  */
849
879
  declare function validateColumnType<T extends TypeName>(type: T, value: unknown): string | null;
850
880
  //#endregion
851
- export { $InferAutoInc, $InferDefault, $InferIndex, $InferOptional, $InferPrimaryKey, $InferRow, $InferTimestamp, $InferUUID, $InferUnique, $UUID, $UUIDVersion, $UnionToIntersection, $ValidateSinglePK, AdvancedTypes, ArrayToTuple, AsyncFunction, BasicPrimitive, Branded, type Column, ColumnDefinition, ColumnRecord, Constructor, DateLike, FirstOverloadParams, GenericFn, GenericObject, IndexConfig, IndexKeyType, InferInsertType, InferSelectType, InferUpdateType, List, Locality, LocalityConfig, LooseLiteral, MapObjectValues, Maybe, NestedPrimitiveKey, NormalPrimitive, Numeric, Prettify, PrimaryKeyType, Primitive, RejectFn, SchemaDefinition, SchemaRecord, SelectFields, SortDirection, StoreConfig, type Table, Timestamp, Tuple, TypeName, UUID, UUIDVersion, UniqueKeyType, ValidatedColumnDefinition, VoidFn, column, defineSchema, deleteDB, getTimestamp, isTimestamp, openDBWithStores, table, uuidV4, validateColumnType };
881
+ export { $InferAutoInc, $InferDefault, $InferIndex, $InferOptional, $InferPrimaryKey, $InferRow, $InferTimestamp, $InferUUID, $InferUnique, $UUID, $UUIDVersion, $UnionToIntersection, $ValidateSinglePK, AdvancedTypes, ArrayToTuple, AsyncFunction, BasicPrimitive, Branded, type Column, ColumnDefinition, ColumnRecord, Constructor, DateLike, FirstOverloadParams, GenericFn, GenericObject, IndexConfig, IndexKeyType, InferInsertType, InferSelectType, InferUpdateType, List, Locality, LocalityConfig, LooseLiteral, MapObjectValues, Maybe, NestedPrimitiveKey, NormalPrimitive, Numeric, Prettify, PrimaryKeyType, Primitive, RejectFn, SchemaDefinition, SchemaRecord, SelectFields, SortDirection, StoreConfig, type Table, Timestamp, Tuple, TypeName, UUID, UUIDVersion, UniqueKeyType, ValidatedColumnDefinition, VoidFn, WherePredicate, column, defineSchema, deleteDB, getTimestamp, isTimestamp, openDBWithStores, table, uuidV4, validateColumnType };
package/dist/index.d.mts CHANGED
@@ -329,6 +329,8 @@ type $InferTimestamp<T extends ColumnDefinition> = { [K in keyof T]: T[K] extend
329
329
  type Timestamp = Branded<string, 'Timestamp'>;
330
330
  /** Sort direction type for ordering queries */
331
331
  type SortDirection = 'asc' | 'desc';
332
+ /** Predicate function type for WHERE clauses in queries */
333
+ type WherePredicate<T extends GenericObject> = (row: T) => boolean;
332
334
  /** Creates a type for insert operations with auto-generated fields optional. */
333
335
  type InferInsertType<T extends Table> = Prettify<Omit<$InferRow<T['columns']>, $InferAutoInc<T['columns']> | $InferDefault<T['columns']> | $InferTimestamp<T['columns']> | $InferUUID<T['columns']>> & { [K in $InferAutoInc<T['columns']> | $InferDefault<T['columns']> | $InferTimestamp<T['columns']> | $InferUUID<T['columns']>]?: K extends keyof $InferRow<T['columns']> ? $InferRow<T['columns']>[K] : never }>;
334
336
  /** Creates a type for update operations with all fields optional except primary key. */
@@ -376,13 +378,34 @@ declare class SelectQuery<T extends GenericObject, S extends Partial<Record<stri
376
378
  * @instance Filter rows based on predicate function
377
379
  * @param predicate Filtering function
378
380
  */
379
- where(predicate: (row: T) => boolean): this;
381
+ where(predicate: WherePredicate<T>): this;
382
+ /**
383
+ * @instance Filter rows based on index query
384
+ * @param indexName Name of the index/primary key to query
385
+ * @param query Key value or {@link IDBKeyRange} to search for
386
+ */
387
+ where<IdxKey extends $InferPrimaryKey<Tbl['columns']> | $InferIndex<Tbl['columns']>>(indexName: IdxKey, query: IDBKeyRange | T[IdxKey]): this;
380
388
  /**
381
389
  * @instance Order results by specified key and direction
382
390
  * @param key Key to order by
383
391
  * @param dir Direction: 'asc' | 'desc' (default: 'asc')
392
+ *
393
+ * @remarks
394
+ * - This method performs in-memory sorting.
395
+ * - For optimized sorting using `IndexedDB` indexes, use {@link sortByIndex} instead.
384
396
  */
385
397
  orderBy<Key extends NestedPrimitiveKey<T>>(key: Key, dir?: SortDirection): this;
398
+ /**
399
+ * @instance Order results by index using optimized `IndexedDB` cursor
400
+ * @param indexName Name of the index to sort by
401
+ * @param dir Direction: 'asc' | 'desc' (default: 'asc')
402
+ *
403
+ * @remarks
404
+ * - This method uses `IndexedDB` indexes for sorting, which is more efficient for large datasets.
405
+ * - Ensure that the specified index exists on the table.
406
+ * - For in-memory sorting, use {@link orderBy} instead.
407
+ */
408
+ sortByIndex<IdxKey extends $InferIndex<Tbl['columns']> | $InferPrimaryKey<Tbl['columns']>>(indexName: IdxKey, dir?: SortDirection): this;
386
409
  /**
387
410
  * @instance Limit number of results
388
411
  * @param count Maximum number of results to return
@@ -393,26 +416,33 @@ declare class SelectQuery<T extends GenericObject, S extends Partial<Record<stri
393
416
  /** Fetch all matching records with selected fields */
394
417
  findAll<Selection extends Partial<Record<keyof T, boolean>>>(this: SelectQuery<T, Selection>): Promise<SelectFields<T, Selection>[]>;
395
418
  /** Fetch first matching record */
396
- first(this: SelectQuery<T, null>): Promise<T | null>;
419
+ findFirst(this: SelectQuery<T, null>): Promise<T | null>;
397
420
  /** Fetch first matching record with selected fields */
398
- first<Selection extends Partial<Record<keyof T, boolean>>>(this: SelectQuery<T, Selection>): Promise<SelectFields<T, Selection> | null>;
421
+ findFirst<Selection extends Partial<Record<keyof T, boolean>>>(this: SelectQuery<T, Selection>): Promise<SelectFields<T, Selection> | null>;
399
422
  /**
400
423
  * @instance Find record by primary key (optimized `IndexedDB` get)
401
424
  * @param key Primary key value
425
+ *
426
+ * @remarks
427
+ * - This method uses the `IndexedDB` primary key for efficient querying.
428
+ * - Ensure that the specified key exists on the table.
429
+ * - To find by index, use {@link findByIndex} instead.
402
430
  */
403
431
  findByPk(key: $InferPrimaryKey<Tbl['columns']> extends keyof T ? T[$InferPrimaryKey<Tbl['columns']>] : T[keyof T]): Promise<S extends null ? T | null : S extends Partial<Record<keyof T, boolean>> ? SelectFields<T, S> | null : never>;
404
432
  /**
405
- * @instance Find records by index (optimized IndexedDB index query)
433
+ * @instance Find records by index (optimized `IndexedDB` index query)
406
434
  * @param indexName Name of the index to query
407
435
  * @param query Key value to search for
408
- */
409
- findByIndex<IndexKey extends $InferIndex<Tbl['columns']> & keyof T & string>(indexName: IndexKey, query: T[IndexKey] | IDBKeyRange): Promise<S extends null ? T[] : S extends Partial<Record<keyof T, boolean>> ? SelectFields<T, S>[] : never>;
410
- /**
411
- * @instance Order results by index using optimized IndexedDB cursor
412
- * @param indexName Name of the index to sort by
413
- * @param dir Direction: 'asc' | 'desc' (default: 'asc')
414
- */
415
- sortByIndex<IndexKey extends ($InferIndex<Tbl['columns']> | $InferPrimaryKey<Tbl['columns']>) & keyof T & string>(indexName: IndexKey, dir?: SortDirection): this;
436
+ *
437
+ * @remarks
438
+ * - This method uses `IndexedDB` indexes for efficient querying.
439
+ * - Ensure that the specified index exists on the table.
440
+ * - To find by primary key, use {@link findByPk} instead.
441
+ */
442
+ findByIndex<IdxKey extends $InferIndex<Tbl['columns']> & keyof T & string>(indexName: IdxKey, query: T[IdxKey] | IDBKeyRange): Promise<S extends null ? T[] : S extends Partial<Record<keyof T, boolean>> ? SelectFields<T, S>[] : never>;
443
+ /** @instance Count matching records */
444
+ count(): Promise<number>;
445
+ exists(): Promise<boolean>;
416
446
  }
417
447
  /** @class Insert query builder. */
418
448
  declare class InsertQuery<Raw extends GenericObject, Inserted extends Raw | Raw[], Data extends GenericObject, Return extends (Inserted extends Array<infer _> ? Data[] : Data)> {
@@ -848,4 +878,4 @@ declare function deleteDB(name: string): Promise<void>;
848
878
  */
849
879
  declare function validateColumnType<T extends TypeName>(type: T, value: unknown): string | null;
850
880
  //#endregion
851
- export { $InferAutoInc, $InferDefault, $InferIndex, $InferOptional, $InferPrimaryKey, $InferRow, $InferTimestamp, $InferUUID, $InferUnique, $UUID, $UUIDVersion, $UnionToIntersection, $ValidateSinglePK, AdvancedTypes, ArrayToTuple, AsyncFunction, BasicPrimitive, Branded, type Column, ColumnDefinition, ColumnRecord, Constructor, DateLike, FirstOverloadParams, GenericFn, GenericObject, IndexConfig, IndexKeyType, InferInsertType, InferSelectType, InferUpdateType, List, Locality, LocalityConfig, LooseLiteral, MapObjectValues, Maybe, NestedPrimitiveKey, NormalPrimitive, Numeric, Prettify, PrimaryKeyType, Primitive, RejectFn, SchemaDefinition, SchemaRecord, SelectFields, SortDirection, StoreConfig, type Table, Timestamp, Tuple, TypeName, UUID, UUIDVersion, UniqueKeyType, ValidatedColumnDefinition, VoidFn, column, defineSchema, deleteDB, getTimestamp, isTimestamp, openDBWithStores, table, uuidV4, validateColumnType };
881
+ export { $InferAutoInc, $InferDefault, $InferIndex, $InferOptional, $InferPrimaryKey, $InferRow, $InferTimestamp, $InferUUID, $InferUnique, $UUID, $UUIDVersion, $UnionToIntersection, $ValidateSinglePK, AdvancedTypes, ArrayToTuple, AsyncFunction, BasicPrimitive, Branded, type Column, ColumnDefinition, ColumnRecord, Constructor, DateLike, FirstOverloadParams, GenericFn, GenericObject, IndexConfig, IndexKeyType, InferInsertType, InferSelectType, InferUpdateType, List, Locality, LocalityConfig, LooseLiteral, MapObjectValues, Maybe, NestedPrimitiveKey, NormalPrimitive, Numeric, Prettify, PrimaryKeyType, Primitive, RejectFn, SchemaDefinition, SchemaRecord, SelectFields, SortDirection, StoreConfig, type Table, Timestamp, Tuple, TypeName, UUID, UUIDVersion, UniqueKeyType, ValidatedColumnDefinition, VoidFn, WherePredicate, column, defineSchema, deleteDB, getTimestamp, isTimestamp, openDBWithStores, table, uuidV4, validateColumnType };