locality-idb 1.1.1 → 1.2.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
@@ -196,26 +196,182 @@ const multiPkSchema = defineSchema({
196
196
 
197
197
  Locality IDB supports a wide range of column types:
198
198
 
199
- | Type | Description | Example |
200
- | ---------------------- | ---------------------------------------------------------- | ---------------------------------- |
201
- | `number()` / `float()` | Numeric values (integer or float) | `column.int()` |
202
- | `int()` | Numeric values (only integer is allowed) | `column.int()` |
203
- | `numeric()` | Number or numeric string | `column.numeric()` |
204
- | `bigint()` | Large integers | `column.bigint()` |
205
- | `text()` / `string()` | Text strings | `column.text()` |
206
- | `char(length?)` | Fixed-length string | `column.char(10)` |
207
- | `varchar(length?)` | Variable-length string | `column.varchar(255)` |
208
- | `bool()` / `boolean()` | Boolean values | `column.bool()` |
209
- | `date()` | Date objects | `column.date()` |
210
- | `timestamp()` | ISO 8601 timestamps ([auto-generated](#utility-functions)) | `column.timestamp()` |
211
- | `uuid()` | UUID strings ([auto-generated](#utility-functions) v4) | `column.uuid()` |
212
- | `object<T>()` | Generic objects | `column.object<UserData>()` |
213
- | `array<T>()` | Arrays | `column.array<number>()` |
214
- | `list<T>()` | Read-only arrays | `column.list<string>()` |
215
- | `tuple<T>()` | Fixed-size tuples | `column.tuple<[string, number]>()` |
216
- | `set<T>()` | Sets | `column.set<string>()` |
217
- | `map<K,V>()` | Maps | `column.map<string, number>()` |
218
- | `custom<T>()` | Custom types | `column.custom<MyType>()` |
199
+ | Type | Description | Example |
200
+ | ---------------------- | ---------------------------------------------------------- | -------------------------------- |
201
+ | `number()` / `float()` | Numeric values (integer or float) | `column.number()` |
202
+ | `int()` | Numeric values (only integer is allowed) | `column.int()` |
203
+ | `numeric()` | Number or numeric string | `column.numeric()` |
204
+ | `bigint()` | Large integers | `column.bigint()` |
205
+ | `text()` / `string()` | Text strings | `column.text()` |
206
+ | `char(length?)` | Fixed-length string | `column.char(10)` |
207
+ | `varchar(length?)` | Variable-length string | `column.varchar(255)` |
208
+ | `bool()` / `boolean()` | Boolean values | `column.bool()` |
209
+ | `date()` | Date objects | `column.date()` |
210
+ | `timestamp()` | ISO 8601 timestamps ([auto-generated](#utility-functions)) | `column.timestamp()` |
211
+ | `uuid()` | UUID strings ([auto-generated](#utility-functions) v4) | `column.uuid()` |
212
+ | `object<T>()` | Generic objects | `column.object<UserData>()` |
213
+ | `array<T>()` | Arrays | `column.array<number>()` |
214
+ | `list<T>()` | Read-only arrays | `column.list<string>()` |
215
+ | `tuple<T>()` | Fixed-size tuples | `column.tuple<string, number>()` |
216
+ | `set<T>()` | Sets | `column.set<string>()` |
217
+ | `map<K,V>()` | Maps | `column.map<string, number>()` |
218
+ | `custom<T>()` | Custom types | `column.custom<MyType>()` |
219
+
220
+ #### Type Extensions
221
+
222
+ Most column types support **generic type parameters** for creating branded types, literal unions, or domain-specific types:
223
+
224
+ ##### Numeric Types (`int`, `float`, `number`)
225
+
226
+ ```typescript
227
+ // Basic usage
228
+ const age = column.int();
229
+ const price = column.float();
230
+ const score = column.number();
231
+
232
+ // Branded types for type safety
233
+ type UserId = Branded<number, 'UserId'>;
234
+ type ProductId = Branded<number, 'ProductId'>;
235
+
236
+ const schema = defineSchema({
237
+ users: {
238
+ id: column.int<UserId>().pk().auto(),
239
+ age: column.int(),
240
+ },
241
+ products: {
242
+ id: column.int<ProductId>().pk().auto(),
243
+ userId: column.int<UserId>(), // Type-safe foreign key
244
+ price: column.float(),
245
+ },
246
+ });
247
+
248
+ // ✅ Type safety prevents mixing IDs
249
+ const userId: UserId = 1 as UserId;
250
+ const productId: ProductId = 2 as ProductId;
251
+ // userId = productId; // ❌ Type error!
252
+ ```
253
+
254
+ ##### String Types (`text`, `string`, `char`, `varchar`)
255
+
256
+ ```typescript
257
+ // Literal unions for enum-like behavior
258
+ type Role = 'admin' | 'user' | 'guest';
259
+ type Status = 'draft' | 'published' | 'archived';
260
+
261
+ const schema = defineSchema({
262
+ users: {
263
+ id: column.int().pk().auto(),
264
+ role: column.text<Role>().default('user'),
265
+ status: column.string<Status>().default('draft'),
266
+ },
267
+ });
268
+
269
+ // Branded types for domain-specific strings
270
+ type Email = Branded<string, 'Email'>;
271
+ type URL = Branded<string, 'URL'>;
272
+
273
+ const profileSchema = defineSchema({
274
+ profiles: {
275
+ id: column.int().pk().auto(),
276
+ email: column.varchar<Email>(255).unique(),
277
+ website: column.varchar<URL>(500).optional(),
278
+ },
279
+ });
280
+ ```
281
+
282
+ ##### Boolean Types (`bool`, `boolean`)
283
+
284
+ ```typescript
285
+ // Branded booleans for clarity
286
+ type EmailVerified = Branded<boolean, 'EmailVerified'>;
287
+ type TwoFactorEnabled = Branded<boolean, 'TwoFactorEnabled'>;
288
+
289
+ const schema = defineSchema({
290
+ users: {
291
+ id: column.int().pk().auto(),
292
+ emailVerified: column.bool<EmailVerified>().default(false as EmailVerified),
293
+ twoFactorEnabled: column.boolean<TwoFactorEnabled>().default(false as TwoFactorEnabled),
294
+ },
295
+ });
296
+ ```
297
+
298
+ ##### Complex Types (`object`, `array`, `list`, `tuple`, `set`, `map`)
299
+
300
+ ```typescript
301
+ // Object with typed structure
302
+ interface UserProfile {
303
+ avatar: string;
304
+ bio: string;
305
+ socials: {
306
+ twitter?: string;
307
+ github?: string;
308
+ };
309
+ }
310
+
311
+ // Array of typed elements
312
+ interface Comment {
313
+ author: string;
314
+ text: string;
315
+ date: string;
316
+ }
317
+
318
+ // Map with typed keys and values
319
+ interface CacheEntry {
320
+ value: any;
321
+ expires: number;
322
+ }
323
+
324
+ const schema = defineSchema({
325
+ users: {
326
+ id: column.int().pk().auto(),
327
+ profile: column.object<UserProfile>(),
328
+ tags: column.array<string>(),
329
+ comments: column.array<Comment>(),
330
+ permissions: column.set<'read' | 'write' | 'delete'>(),
331
+ cache: column.map<string, CacheEntry>(),
332
+ },
333
+
334
+ // Tuples for fixed structures
335
+ locations: {
336
+ id: column.int().pk().auto(),
337
+ coordinates: column.tuple<number, number>(), // [latitude, longitude]
338
+ rgbColor: column.tuple<number, number, number>(), // [r, g, b]
339
+ },
340
+
341
+ // List (readonly array)
342
+ config: {
343
+ id: column.int().pk().auto(),
344
+ allowedOrigins: column.list<string>(), // Immutable at type level
345
+ },
346
+ });
347
+ ```
348
+
349
+ ##### Numeric & Bigint with `Numeric` & `bigint` Types
350
+
351
+ ```typescript
352
+ // Numeric accepts both number and numeric strings
353
+ const schema = defineSchema({
354
+ products: {
355
+ id: column.int().pk().auto(),
356
+ serialNumber: column.numeric(), // Can be 123 or "123"
357
+ largeId: column.bigint(), // For very large integers
358
+ },
359
+ });
360
+
361
+ // Branded Numeric types
362
+ type SerialNumber = Branded<Numeric, 'SerialNumber'>;
363
+ type SnowflakeId = Branded<bigint, 'SnowflakeId'>;
364
+
365
+ const advancedSchema = defineSchema({
366
+ items: {
367
+ id: column.int().pk().auto(),
368
+ serial: column.numeric<SerialNumber>(),
369
+ snowflake: column.bigint<SnowflakeId>(),
370
+ },
371
+ });
372
+ ```
373
+
374
+ > **Note:** Type extensions are compile-time only and do not affect runtime validation. Use [custom validators](#validatevalidator-value-t--string--null--undefined-column) for runtime type enforcement.
219
375
 
220
376
  ### Type Inference
221
377
 
@@ -340,6 +496,7 @@ const allUsers = await db.from('users').findAll();
340
496
  #### Filter with Where
341
497
 
342
498
  ```typescript
499
+ // Predicate-based filtering (in-memory)
343
500
  const admins = await db
344
501
  .from('users')
345
502
  .where((user) => user.role === 'admin')
@@ -349,6 +506,18 @@ const activeUsers = await db
349
506
  .from('users')
350
507
  .where((user) => user.isActive && user.age >= 18)
351
508
  .findAll();
509
+
510
+ // Index-based filtering (optimized) - requires index or primary key
511
+ const usersByEmail = await db
512
+ .from('users')
513
+ .where('email', 'alice@example.com')
514
+ .findAll();
515
+
516
+ // Range queries with IDBKeyRange
517
+ const adults = await db
518
+ .from('users')
519
+ .where('age', IDBKeyRange.bound(18, 65))
520
+ .findAll();
352
521
  ```
353
522
 
354
523
  #### Select Specific Columns
@@ -627,7 +796,7 @@ Gets the underlying `IDBDatabase` instance.
627
796
  const idb = await db.getDBInstance();
628
797
  ```
629
798
 
630
- #### `seed<T>(table: T, data: InferInsertType<Schema[T]> | InferInsertType<Schema[T]>[]): Promise<InferSelectType<Schema[T]> | InferSelectType<Schema[T]>[]>`
799
+ #### `seed<T>(table: T, data: InferInsertType<Schema[T]>[]): Promise<InferSelectType<Schema[T]>[]>`
631
800
 
632
801
  Inserts seed data into the specified table.
633
802
 
@@ -636,13 +805,14 @@ Inserts seed data into the specified table.
636
805
  > - This is a convenience method for inserting initial data.
637
806
  > - It uses the `insert` method internally.
638
807
  > - It does not clear existing data before inserting.
808
+ > - Accepts only an **array** of records (for single record insertion, use `insert().values().run()`)
639
809
 
640
810
  **Parameters:**
641
811
 
642
812
  - `table`: Table name
643
- - `data`: Single record or array of records to insert
813
+ - `data`: Array of records to insert
644
814
 
645
- **Returns:** Inserted record(s)
815
+ **Returns:** Array of inserted record(s)
646
816
 
647
817
  **Example:**
648
818
 
@@ -761,6 +931,103 @@ column.bool().default(true)
761
931
  column.text().default('N/A')
762
932
  ```
763
933
 
934
+ #### `validate(validator: (value: T) => string | null | undefined): Column`
935
+
936
+ Adds custom validation logic to the column. The validation function receives the column value and should return:
937
+
938
+ - `null` or `undefined` if the value is valid
939
+ - An error message `string` if the value is invalid
940
+
941
+ **When it runs:** During insert and update operations, before data is saved to `IndexedDB`.
942
+
943
+ > **Error Handling:** If validation fails, a `TypeError` is thrown with details about the invalid field.
944
+
945
+ **Precedence:** Custom validators override built-in type validation. If you provide a custom validator, the built-in type check for that column will be skipped.
946
+
947
+ > **Note:**
948
+ >
949
+ > - Custom validation is not applied to auto-generated values (e.g. auto-increment, UUID, timestamp). But default values are validated if `.default(value)` is used.
950
+ > - If multiple validators are chained, only the last one is used.
951
+ > - Built-in type validation still applies to all other columns without custom validators.
952
+ > - If the column is optional, the validator is only called when a value is provided (not `undefined`).
953
+
954
+ ```typescript
955
+ // Email validation
956
+ const schema = defineSchema({
957
+ users: {
958
+ id: column.int().pk().auto(),
959
+ email: column.text().validate((val) => {
960
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
961
+ return emailRegex.test(val) ? null : 'Invalid email format';
962
+ }),
963
+ age: column.int().validate((val) => {
964
+ if (val < 0) return 'Age cannot be negative';
965
+ if (val > 120) return 'Age must be 120 or less';
966
+ return null; // Valid
967
+ }),
968
+ username: column.text().validate((val) => {
969
+ if (val.length < 3) return 'Username must be at least 3 characters';
970
+ if (!/^[a-zA-Z0-9_]+$/.test(val)) return 'Username can only contain letters, numbers, and underscores';
971
+ return null;
972
+ }),
973
+ },
974
+ });
975
+
976
+ // ✅ Valid insert
977
+ await db.insert('users').values({
978
+ email: 'user@example.com',
979
+ age: 25,
980
+ username: 'john_doe'
981
+ }).run();
982
+
983
+ // ❌ Throws TypeError: Invalid value for field 'email' in table 'users': Invalid email format
984
+ await db.insert('users').values({
985
+ email: 'invalid-email',
986
+ age: 25,
987
+ username: 'john_doe'
988
+ }).run();
989
+
990
+ // ❌ Throws TypeError: Invalid value for field 'age' in table 'users': Age cannot be negative
991
+ await db.insert('users').values({
992
+ email: 'user@example.com',
993
+ age: -5,
994
+ username: 'john_doe'
995
+ }).run();
996
+ ```
997
+
998
+ **Combining with `.optional()`:**
999
+
1000
+ ```typescript
1001
+ const schema = defineSchema({
1002
+ users: {
1003
+ id: column.int().pk().auto(),
1004
+ // Custom validation only runs when value is provided
1005
+ bio: column.text().optional().validate((val) => {
1006
+ return val.length <= 500 ? null : 'Bio must be 500 characters or less';
1007
+ }),
1008
+ },
1009
+ });
1010
+
1011
+ // ✅ Valid - bio is optional and omitted
1012
+ await db.insert('users').values({}).run();
1013
+
1014
+ // ✅ Valid - bio is provided and valid
1015
+ await db.insert('users').values({ bio: 'Short bio' }).run();
1016
+
1017
+ // ❌ Throws TypeError - bio provided but exceeds 500 chars
1018
+ await db.insert('users').values({ bio: 'x'.repeat(501) }).run();
1019
+ ```
1020
+
1021
+ **Access the `ValidateFn` symbol (advanced):**
1022
+
1023
+ ```typescript
1024
+ import { ValidateFn } from 'locality-idb';
1025
+
1026
+ // Access validator function programmatically
1027
+ const emailColumn = column.text().validate((val) => { /* ... */ });
1028
+ const validatorFn = emailColumn[ValidateFn]; // Function reference
1029
+ ```
1030
+
764
1031
  ---
765
1032
 
766
1033
  ### Query Methods
@@ -789,17 +1056,25 @@ db.from('users').where((user) => user.age >= 18)
789
1056
 
790
1057
  ##### `where<IdxKey>(indexName: IdxKey, query: T[IdxKey] | IDBKeyRange): SelectQuery`
791
1058
 
792
- Filters rows using an indexed field.
1059
+ Filters rows using an indexed field or primary key.
1060
+
1061
+ **Type Safety:** `indexName` must be either an indexed field or the primary key.
1062
+
1063
+ **Performance:** Uses IndexedDB's optimized index/key query for efficient lookups.
793
1064
 
794
1065
  ```typescript
1066
+ // Using an indexed field
795
1067
  db.from('users').where('age', IDBKeyRange.bound(18, 30))
1068
+
1069
+ // Using primary key
1070
+ db.from('users').where('id', IDBKeyRange.bound(1, 100))
796
1071
  ```
797
1072
 
798
1073
  ##### `sortByIndex<IdxKey>(indexName: IdxKey, dir?: 'asc' | 'desc'): SelectQuery`
799
1074
 
800
1075
  Sorts results by an indexed field using IndexedDB cursor iteration (avoiding in-memory sorting).
801
1076
 
802
- **Type Safety:** `indexName` must be a field with an index.
1077
+ **Type Safety:** `indexName` must be a field with an index or the primary key.
803
1078
 
804
1079
  **Performance:** Uses IndexedDB's cursor for optimized sorting. For large datasets, this is significantly more efficient than in-memory sorting.
805
1080
 
@@ -890,8 +1165,10 @@ const userCount = await db.from('users').where((user) => user.isActive).count()
890
1165
 
891
1166
  > **Note:**
892
1167
  >
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.
1168
+ > - Uses IndexedDB's optimized `count()` when:
1169
+ > - No `where()` clause is applied, OR
1170
+ > - `where()` uses an index or primary key
1171
+ > - Falls back to in-memory counting when `where()` uses a predicate function
895
1172
 
896
1173
  ##### `exists(): Promise<boolean>`
897
1174
 
package/dist/index.cjs CHANGED
@@ -153,6 +153,8 @@ const IsIndexed = Symbol("IsIndexed");
153
153
  const IsUnique = Symbol("IsUnique");
154
154
  /** Symbol key for default value */
155
155
  const DefaultValue = Symbol("DefaultValue");
156
+ /** Symbol key for custom validation function */
157
+ const ValidateFn = Symbol("ValidateFn");
156
158
  /** @class Represents a column definition. */
157
159
  var Column = class {
158
160
  constructor(type) {
@@ -200,6 +202,34 @@ var Column = class {
200
202
  this[IsOptional] = true;
201
203
  return this;
202
204
  }
205
+ /**
206
+ * @instance Sets a custom validation function for the column
207
+ *
208
+ * @param validator - Custom validation function that receives the value and returns `null`/`undefined` if valid, or an error message `string` if invalid
209
+ *
210
+ * @returns The column instance with the validation function attached
211
+ *
212
+ * @remarks
213
+ * - Custom validation is not applied to auto-generated values (e.g. auto-increment, UUID, timestamp). But default values are validated if {@link default()} is used.
214
+ * - If multiple validators are chained, only the last one is used.
215
+ * - Built-in type validation still applies to all other columns without custom validators.
216
+ * - If the column is optional, the validator is only called when a value is provided (not `undefined`).
217
+ *
218
+ * @example
219
+ * // Email validation
220
+ * email: text().validate((val) => {
221
+ * return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val) ? null : 'Invalid email format';
222
+ * })
223
+ *
224
+ * // Range validation
225
+ * age: int().validate((val) => {
226
+ * return val >= 0 && val <= 120 ? null : 'Age must be between 0 and 120';
227
+ * })
228
+ */
229
+ validate(validator) {
230
+ this[ValidateFn] = validator;
231
+ return this;
232
+ }
203
233
  };
204
234
  /** @class Represents a table. */
205
235
  var Table = class {
@@ -381,15 +411,15 @@ function validateColumnType(type, value) {
381
411
  case "tuple":
382
412
  if (isArray(value)) return null;
383
413
  return `${strVal} is not a tuple`;
384
- case "set":
385
- if (isSet(value)) return null;
386
- return `${strVal} is not a set`;
387
414
  case "object":
388
415
  if (isObject(value)) return null;
389
416
  return `${strVal} is not an object`;
390
417
  case "date":
391
418
  if (isDate(value)) return null;
392
419
  return `${strVal} is not a Date object`;
420
+ case "set":
421
+ if (isSet(value)) return null;
422
+ return `${strVal} is not a set`;
393
423
  case "map":
394
424
  if (isMap(value)) return null;
395
425
  return `${strVal} is not a Map object`;
@@ -426,22 +456,48 @@ function validateColumnType(type, value) {
426
456
  * @returns The validated and prepared data object
427
457
  * @throws
428
458
  * - A {@link TypeError} if any value does not match the expected column type
429
- * - A {@link RangeError} if any field is not defined in the table schema
459
+ * - A {@link RangeError} if any field is not defined in the table schema or required field is missing
430
460
  */
431
461
  function validateAndPrepareData(data, columns, keyPath, tableName, forUpdate = false) {
432
462
  const prepared = { ...data };
433
463
  if (columns) {
434
- for (const fieldName of Object.keys(prepared)) if (!Object.keys(columns).includes(fieldName)) throw new RangeError(`'${fieldName}' is not defined in the table (${tableName}) schema!`);
464
+ for (const fieldName of Object.keys(prepared)) if (!Object.keys(columns).includes(fieldName)) throw new RangeError(`Field '${fieldName}' is not defined in the table '${tableName}' schema!`);
435
465
  Object.entries(columns).forEach((entry) => {
436
466
  const [fieldName, column] = entry;
437
- const defaultValue = column[DefaultValue];
438
- if (!(fieldName in prepared) && defaultValue !== void 0 && !forUpdate) prepared[fieldName] = defaultValue;
439
467
  const columnType = column[ColumnType];
440
- if (columnType === "uuid" && !(fieldName in prepared) && !forUpdate) prepared[fieldName] = uuidV4();
441
- if (columnType === "timestamp" && !(fieldName in prepared) && !forUpdate) prepared[fieldName] = getTimestamp();
442
- if (fieldName !== keyPath) {
443
- const errorMsg = validateColumnType(columnType, prepared[fieldName]);
444
- if (errorMsg) throw new TypeError(errorMsg);
468
+ const defaultValue = column[DefaultValue];
469
+ const isOptional = column[IsOptional] ?? false;
470
+ let fieldNotPresent = !(fieldName in prepared);
471
+ if (!forUpdate && fieldNotPresent) {
472
+ if (columnType === "uuid" && isUndefined(defaultValue)) {
473
+ prepared[fieldName] = uuidV4();
474
+ return;
475
+ }
476
+ if (columnType === "timestamp" && isUndefined(defaultValue)) {
477
+ prepared[fieldName] = getTimestamp();
478
+ return;
479
+ }
480
+ if (!isUndefined(defaultValue)) {
481
+ prepared[fieldName] = defaultValue;
482
+ fieldNotPresent = false;
483
+ }
484
+ }
485
+ const fieldValue = prepared[fieldName];
486
+ if (fieldNotPresent) {
487
+ if (forUpdate) return;
488
+ if (!isOptional && fieldName !== keyPath) throw new RangeError(`Required field '${String(fieldName)}' is missing in table '${tableName}'!`);
489
+ return;
490
+ }
491
+ if (isUndefined(fieldValue)) {
492
+ if (!isOptional && fieldName !== keyPath) throw new TypeError(`Field '${String(fieldName)}' in table '${tableName}' cannot be undefined. It is a required field.`);
493
+ return;
494
+ }
495
+ if (!(!forUpdate && fieldName === keyPath && (column[IsAutoInc] ?? false))) {
496
+ const customValidator = column[ValidateFn];
497
+ let errorMsg;
498
+ if (isFunction(customValidator)) errorMsg = customValidator(fieldValue);
499
+ else errorMsg = validateColumnType(columnType, fieldValue);
500
+ if (errorMsg) throw new TypeError(`Invalid value for field '${String(fieldName)}' in table '${tableName}': ${errorMsg}`);
445
501
  }
446
502
  });
447
503
  }
@@ -473,11 +529,13 @@ var SelectQuery = class {
473
529
  this.#dbGetter = dbGetter;
474
530
  this.#readyPromise = readyPromise;
475
531
  }
476
- #createTransaction() {
477
- return this.#dbGetter().transaction(this.#table, "readonly");
478
- }
479
- #getStoreWithTransaction() {
480
- return this.#createTransaction().objectStore(this.#table);
532
+ /** @internal Create a readonly transaction and return the store */
533
+ #getStore() {
534
+ const transaction = this.#dbGetter().transaction(this.#table, "readonly");
535
+ return {
536
+ transaction,
537
+ store: transaction.objectStore(this.#table)
538
+ };
481
539
  }
482
540
  /** @internal Check if key is an index on the store for the `#whereIndexName` */
483
541
  #isIndexKey(store) {
@@ -505,6 +563,12 @@ var SelectQuery = class {
505
563
  });
506
564
  return data;
507
565
  }
566
+ /** @internal Apply sort, limit, and projection pipeline to results */
567
+ #applyPipeline(results) {
568
+ let processed = this.#sort(results);
569
+ if (this.#limitCount) processed = processed.slice(0, this.#limitCount);
570
+ return processed.map((row) => this.#projectRow(row));
571
+ }
508
572
  /** Projects a row based on selected fields */
509
573
  #projectRow(row) {
510
574
  if (!isNotEmptyObject(this?.[Selected])) return row;
@@ -577,16 +641,13 @@ var SelectQuery = class {
577
641
  async findAll() {
578
642
  await this.#readyPromise;
579
643
  return new Promise((resolve, reject) => {
580
- const store = this.#getStoreWithTransaction();
644
+ const { store } = this.#getStore();
581
645
  if (this.#whereIndexName && !isUndefined(this.#whereIndexQuery)) {
582
646
  const source = this.#buildIndexedStore(store, reject);
583
647
  if (!source) return;
584
648
  const request = source.getAll(this.#whereIndexQuery);
585
649
  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)));
650
+ resolve(this.#applyPipeline(request.result));
590
651
  };
591
652
  request.onerror = () => reject(request.error);
592
653
  return;
@@ -615,9 +676,7 @@ var SelectQuery = class {
615
676
  request.onsuccess = () => {
616
677
  let results = request.result;
617
678
  if (this.#whereCondition) results = results.filter(this.#whereCondition);
618
- results = this.#sort(results);
619
- if (this.#limitCount) results = results.slice(0, this.#limitCount);
620
- resolve(results.map((row) => this.#projectRow(row)));
679
+ resolve(this.#applyPipeline(results));
621
680
  };
622
681
  request.onerror = () => reject(request.error);
623
682
  }
@@ -626,15 +685,14 @@ var SelectQuery = class {
626
685
  async findFirst() {
627
686
  await this.#readyPromise;
628
687
  return new Promise((resolve, reject) => {
629
- const store = this.#getStoreWithTransaction();
688
+ const { store } = this.#getStore();
630
689
  if (this.#whereIndexName && !isUndefined(this.#whereIndexQuery)) {
631
690
  const source = this.#buildIndexedStore(store, reject);
632
691
  if (!source) return;
633
692
  const request = source.getAll(this.#whereIndexQuery);
634
693
  request.onsuccess = () => {
635
694
  const results = request.result;
636
- if (results.length > 0) resolve(this.#projectRow(results[0]));
637
- else resolve(null);
695
+ resolve(results.length > 0 ? this.#projectRow(results[0]) : null);
638
696
  };
639
697
  request.onerror = () => reject(request.error);
640
698
  return;
@@ -643,8 +701,7 @@ var SelectQuery = class {
643
701
  request.onsuccess = () => {
644
702
  let results = request.result;
645
703
  if (this.#whereCondition) results = results.filter(this.#whereCondition);
646
- if (results.length > 0) resolve(this.#projectRow(results[0]));
647
- else resolve(null);
704
+ resolve(results.length > 0 ? this.#projectRow(results[0]) : null);
648
705
  };
649
706
  request.onerror = () => reject(request.error);
650
707
  });
@@ -661,7 +718,8 @@ var SelectQuery = class {
661
718
  async findByPk(key) {
662
719
  await this.#readyPromise;
663
720
  return new Promise((resolve, reject) => {
664
- const request = this.#getStoreWithTransaction().get(key);
721
+ const { store } = this.#getStore();
722
+ const request = store.get(key);
665
723
  request.onsuccess = () => {
666
724
  const result = request.result;
667
725
  if (!result) {
@@ -690,18 +748,16 @@ var SelectQuery = class {
690
748
  async findByIndex(indexName, query) {
691
749
  await this.#readyPromise;
692
750
  return new Promise((resolve, reject) => {
693
- const store = this.#getStoreWithTransaction();
751
+ const { store } = this.#getStore();
694
752
  if (!store.indexNames.contains(indexName)) {
695
- reject(/* @__PURE__ */ new Error(`Index '${indexName}' does not exist on table '${this.#table}'`));
753
+ reject(/* @__PURE__ */ new RangeError(`Index '${indexName}' does not exist on table '${this.#table}'`));
696
754
  return;
697
755
  }
698
756
  const request = store.index(indexName).getAll(query);
699
757
  request.onsuccess = () => {
700
758
  let results = request.result;
701
759
  if (this.#whereCondition) results = results.filter(this.#whereCondition);
702
- results = this.#sort(results);
703
- if (this.#limitCount) results = results.slice(0, this.#limitCount);
704
- resolve(results.map((row) => this.#projectRow(row)));
760
+ resolve(this.#applyPipeline(results));
705
761
  };
706
762
  request.onerror = () => reject(request.error);
707
763
  });
@@ -710,7 +766,7 @@ var SelectQuery = class {
710
766
  async count() {
711
767
  await this.#readyPromise;
712
768
  return new Promise((resolve, reject) => {
713
- const store = this.#getStoreWithTransaction();
769
+ const { store } = this.#getStore();
714
770
  if (this.#whereIndexName && !isUndefined(this.#whereIndexQuery)) {
715
771
  const source = this.#buildIndexedStore(store, reject);
716
772
  if (!source) return;
@@ -1224,6 +1280,7 @@ const column = {
1224
1280
 
1225
1281
  //#endregion
1226
1282
  exports.Locality = Locality;
1283
+ exports.ValidateFn = ValidateFn;
1227
1284
  exports.column = column;
1228
1285
  exports.defineSchema = defineSchema;
1229
1286
  exports.deleteDB = deleteDB;