locality-idb 1.1.0 → 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
@@ -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.
@@ -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<IdxKey>(indexName: IdxKey, 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
@@ -487,6 +487,7 @@ var SelectQuery = class {
487
487
  #isPrimaryKey(store) {
488
488
  return isNonEmptyString(this.#whereIndexName) && store.keyPath === this.#whereIndexName;
489
489
  }
490
+ /** @internal Build indexed store (primary key or index) for where queries */
490
491
  #buildIndexedStore(store, reject) {
491
492
  const isPK = this.#isPrimaryKey(store);
492
493
  const isIndex = this.#isIndexKey(store);
@@ -523,13 +524,13 @@ var SelectQuery = class {
523
524
  this[Selected] = cols;
524
525
  return this;
525
526
  }
526
- where(predicate, query) {
527
- if (isFunction(predicate)) {
528
- this.#whereCondition = predicate;
527
+ where(condition, query) {
528
+ if (isFunction(condition)) {
529
+ this.#whereCondition = condition;
529
530
  this.#whereIndexName = void 0;
530
531
  this.#whereIndexQuery = void 0;
531
- } else if (isNonEmptyString(predicate) && !isUndefined(query)) {
532
- this.#whereIndexName = predicate;
532
+ } else if (isNonEmptyString(condition) && !isUndefined(query)) {
533
+ this.#whereIndexName = condition;
533
534
  this.#whereIndexQuery = query;
534
535
  this.#whereCondition = void 0;
535
536
  }
@@ -731,6 +732,9 @@ var SelectQuery = class {
731
732
  request.onerror = () => reject(request.error);
732
733
  });
733
734
  }
735
+ async exists() {
736
+ return await this.count() > 0;
737
+ }
734
738
  };
735
739
  /** @class Insert query builder. */
736
740
  var InsertQuery = class {
package/dist/index.d.cts CHANGED
@@ -384,7 +384,7 @@ declare class SelectQuery<T extends GenericObject, S extends Partial<Record<stri
384
384
  * @param indexName Name of the index/primary key to query
385
385
  * @param query Key value or {@link IDBKeyRange} to search for
386
386
  */
387
- where<IdxKey extends $InferPrimaryKey<Tbl['columns']> | $InferIndex<Tbl['columns']>>(indexName: IdxKey, query: IDBKeyRange | T[keyof T]): this;
387
+ where<IdxKey extends $InferPrimaryKey<Tbl['columns']> | $InferIndex<Tbl['columns']>>(indexName: IdxKey, query: IDBKeyRange | T[IdxKey]): this;
388
388
  /**
389
389
  * @instance Order results by specified key and direction
390
390
  * @param key Key to order by
@@ -442,6 +442,7 @@ declare class SelectQuery<T extends GenericObject, S extends Partial<Record<stri
442
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
443
  /** @instance Count matching records */
444
444
  count(): Promise<number>;
445
+ exists(): Promise<boolean>;
445
446
  }
446
447
  /** @class Insert query builder. */
447
448
  declare class InsertQuery<Raw extends GenericObject, Inserted extends Raw | Raw[], Data extends GenericObject, Return extends (Inserted extends Array<infer _> ? Data[] : Data)> {
package/dist/index.d.mts CHANGED
@@ -384,7 +384,7 @@ declare class SelectQuery<T extends GenericObject, S extends Partial<Record<stri
384
384
  * @param indexName Name of the index/primary key to query
385
385
  * @param query Key value or {@link IDBKeyRange} to search for
386
386
  */
387
- where<IdxKey extends $InferPrimaryKey<Tbl['columns']> | $InferIndex<Tbl['columns']>>(indexName: IdxKey, query: IDBKeyRange | T[keyof T]): this;
387
+ where<IdxKey extends $InferPrimaryKey<Tbl['columns']> | $InferIndex<Tbl['columns']>>(indexName: IdxKey, query: IDBKeyRange | T[IdxKey]): this;
388
388
  /**
389
389
  * @instance Order results by specified key and direction
390
390
  * @param key Key to order by
@@ -442,6 +442,7 @@ declare class SelectQuery<T extends GenericObject, S extends Partial<Record<stri
442
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
443
  /** @instance Count matching records */
444
444
  count(): Promise<number>;
445
+ exists(): Promise<boolean>;
445
446
  }
446
447
  /** @class Insert query builder. */
447
448
  declare class InsertQuery<Raw extends GenericObject, Inserted extends Raw | Raw[], Data extends GenericObject, Return extends (Inserted extends Array<infer _> ? Data[] : Data)> {
@@ -489,6 +489,7 @@ var LocalityIDB = (function(exports) {
489
489
  #isPrimaryKey(store) {
490
490
  return isNonEmptyString(this.#whereIndexName) && store.keyPath === this.#whereIndexName;
491
491
  }
492
+ /** @internal Build indexed store (primary key or index) for where queries */
492
493
  #buildIndexedStore(store, reject) {
493
494
  const isPK = this.#isPrimaryKey(store);
494
495
  const isIndex = this.#isIndexKey(store);
@@ -525,13 +526,13 @@ var LocalityIDB = (function(exports) {
525
526
  this[Selected] = cols;
526
527
  return this;
527
528
  }
528
- where(predicate, query) {
529
- if (isFunction(predicate)) {
530
- this.#whereCondition = predicate;
529
+ where(condition, query) {
530
+ if (isFunction(condition)) {
531
+ this.#whereCondition = condition;
531
532
  this.#whereIndexName = void 0;
532
533
  this.#whereIndexQuery = void 0;
533
- } else if (isNonEmptyString(predicate) && !isUndefined(query)) {
534
- this.#whereIndexName = predicate;
534
+ } else if (isNonEmptyString(condition) && !isUndefined(query)) {
535
+ this.#whereIndexName = condition;
535
536
  this.#whereIndexQuery = query;
536
537
  this.#whereCondition = void 0;
537
538
  }
@@ -733,6 +734,9 @@ var LocalityIDB = (function(exports) {
733
734
  request.onerror = () => reject(request.error);
734
735
  });
735
736
  }
737
+ async exists() {
738
+ return await this.count() > 0;
739
+ }
736
740
  };
737
741
  /** @class Insert query builder. */
738
742
  var InsertQuery = class {
package/dist/index.mjs CHANGED
@@ -486,6 +486,7 @@ var SelectQuery = class {
486
486
  #isPrimaryKey(store) {
487
487
  return isNonEmptyString(this.#whereIndexName) && store.keyPath === this.#whereIndexName;
488
488
  }
489
+ /** @internal Build indexed store (primary key or index) for where queries */
489
490
  #buildIndexedStore(store, reject) {
490
491
  const isPK = this.#isPrimaryKey(store);
491
492
  const isIndex = this.#isIndexKey(store);
@@ -522,13 +523,13 @@ var SelectQuery = class {
522
523
  this[Selected] = cols;
523
524
  return this;
524
525
  }
525
- where(predicate, query) {
526
- if (isFunction(predicate)) {
527
- this.#whereCondition = predicate;
526
+ where(condition, query) {
527
+ if (isFunction(condition)) {
528
+ this.#whereCondition = condition;
528
529
  this.#whereIndexName = void 0;
529
530
  this.#whereIndexQuery = void 0;
530
- } else if (isNonEmptyString(predicate) && !isUndefined(query)) {
531
- this.#whereIndexName = predicate;
531
+ } else if (isNonEmptyString(condition) && !isUndefined(query)) {
532
+ this.#whereIndexName = condition;
532
533
  this.#whereIndexQuery = query;
533
534
  this.#whereCondition = void 0;
534
535
  }
@@ -730,6 +731,9 @@ var SelectQuery = class {
730
731
  request.onerror = () => reject(request.error);
731
732
  });
732
733
  }
734
+ async exists() {
735
+ return await this.count() > 0;
736
+ }
733
737
  };
734
738
  /** @class Insert query builder. */
735
739
  var InsertQuery = class {
package/dist/index.umd.js CHANGED
@@ -492,6 +492,7 @@
492
492
  #isPrimaryKey(store) {
493
493
  return isNonEmptyString(this.#whereIndexName) && store.keyPath === this.#whereIndexName;
494
494
  }
495
+ /** @internal Build indexed store (primary key or index) for where queries */
495
496
  #buildIndexedStore(store, reject) {
496
497
  const isPK = this.#isPrimaryKey(store);
497
498
  const isIndex = this.#isIndexKey(store);
@@ -528,13 +529,13 @@
528
529
  this[Selected] = cols;
529
530
  return this;
530
531
  }
531
- where(predicate, query) {
532
- if (isFunction(predicate)) {
533
- this.#whereCondition = predicate;
532
+ where(condition, query) {
533
+ if (isFunction(condition)) {
534
+ this.#whereCondition = condition;
534
535
  this.#whereIndexName = void 0;
535
536
  this.#whereIndexQuery = void 0;
536
- } else if (isNonEmptyString(predicate) && !isUndefined(query)) {
537
- this.#whereIndexName = predicate;
537
+ } else if (isNonEmptyString(condition) && !isUndefined(query)) {
538
+ this.#whereIndexName = condition;
538
539
  this.#whereIndexQuery = query;
539
540
  this.#whereCondition = void 0;
540
541
  }
@@ -736,6 +737,9 @@
736
737
  request.onerror = () => reject(request.error);
737
738
  });
738
739
  }
740
+ async exists() {
741
+ return await this.count() > 0;
742
+ }
739
743
  };
740
744
  /** @class Insert query builder. */
741
745
  var InsertQuery = class {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "locality-idb",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "SQL-like query builder for IndexedDB with Drizzle-style API",
5
5
  "type": "module",
6
6
  "sideEffects": false,