schema-idb 0.0.4 → 0.0.6
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 +728 -27
- package/dist/createSchemaDB.d.ts +4 -2
- package/dist/createSchemaDB.js +9 -1
- package/dist/field.d.ts +1 -0
- package/dist/query.d.ts +38 -11
- package/dist/schemaDetection.d.ts +8 -7
- package/dist/schemaDetection.js +3 -3
- package/dist/storeAccessor.js +1 -1
- package/dist/types.d.ts +2 -1
- package/package.json +1 -1
- package/dist/createDB.d.ts +0 -2
- package/dist/createDB.js +0 -1
- package/dist/defineStore.d.ts +0 -5
- package/dist/defineStore.js +0 -1
- package/dist/readChain.d.ts +0 -2
- package/dist/readChain.js +0 -1
- package/dist/writeChain.d.ts +0 -2
- package/dist/writeChain.js +0 -1
package/README.md
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
A type-safe IndexedDB layer that brings structure to client-side storage.
|
|
4
4
|
|
|
5
|
+
[Live Example](https://stackblitz.com/edit/schema-idb)
|
|
6
|
+
|
|
5
7
|
```ts
|
|
6
8
|
const db = openDB({
|
|
7
9
|
name: "MyApp",
|
|
8
|
-
|
|
10
|
+
versionStrategy: "auto",
|
|
9
11
|
stores: [usersStore] as const,
|
|
10
12
|
});
|
|
11
13
|
|
|
@@ -13,8 +15,6 @@ await db.users.put({ id: "u1", name: "Kim", email: "kim@example.com" });
|
|
|
13
15
|
const user = await db.users.get("u1");
|
|
14
16
|
```
|
|
15
17
|
|
|
16
|
-
[Live Example](https://stackblitz.com/edit/schema-idb)
|
|
17
|
-
|
|
18
18
|
---
|
|
19
19
|
|
|
20
20
|
## What schema-idb provides
|
|
@@ -60,7 +60,7 @@ import { openDB } from "schema-idb";
|
|
|
60
60
|
|
|
61
61
|
const db = openDB({
|
|
62
62
|
name: "MyApp",
|
|
63
|
-
|
|
63
|
+
versionStrategy: "auto",
|
|
64
64
|
stores: [usersStore] as const,
|
|
65
65
|
});
|
|
66
66
|
```
|
|
@@ -223,6 +223,10 @@ const user = await db.users
|
|
|
223
223
|
schema-idb exposes transactions as synchronous write batches across multiple stores.
|
|
224
224
|
|
|
225
225
|
```ts
|
|
226
|
+
// Single store
|
|
227
|
+
const tx = db.startTransaction("accounts");
|
|
228
|
+
|
|
229
|
+
// Multiple stores
|
|
226
230
|
const tx = db.startTransaction(["accounts", "logs"]);
|
|
227
231
|
|
|
228
232
|
// Queue operations (no await between them)
|
|
@@ -235,9 +239,12 @@ await tx.commit();
|
|
|
235
239
|
|
|
236
240
|
// Or abort
|
|
237
241
|
tx.abort();
|
|
242
|
+
|
|
243
|
+
// Access underlying IDBTransaction if needed
|
|
244
|
+
tx.raw;
|
|
238
245
|
```
|
|
239
246
|
|
|
240
|
-
Read operations are not available inside transactions. IndexedDB transactions auto-commit after any `await`, so
|
|
247
|
+
Read operations are not available inside transactions. IndexedDB transactions auto-commit after any `await`, so schema-idb only supports synchronous write batching.
|
|
241
248
|
|
|
242
249
|
---
|
|
243
250
|
|
|
@@ -312,11 +319,55 @@ const db = openDB({
|
|
|
312
319
|
- Index modifications
|
|
313
320
|
- Index deletions
|
|
314
321
|
|
|
315
|
-
### Requires manual migration (throws error)
|
|
322
|
+
### Requires manual migration (throws error by default)
|
|
316
323
|
|
|
317
324
|
- Store deletions (data loss)
|
|
318
325
|
- keyPath changes (requires store recreation)
|
|
319
326
|
|
|
327
|
+
### Handling removed stores
|
|
328
|
+
|
|
329
|
+
When a store is removed from the schema, you can choose how to handle it:
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
const db = openDB({
|
|
333
|
+
name: "MyApp",
|
|
334
|
+
versionStrategy: "auto",
|
|
335
|
+
// 'error' (default): Throws an error when stores are removed
|
|
336
|
+
// 'preserve': Renames removed stores to __storeName_deleted_v{version}__ as backup.
|
|
337
|
+
// Preserved stores are isolated from the typed API to avoid future name collisions.
|
|
338
|
+
removedStoreStrategy: "preserve",
|
|
339
|
+
stores: [usersStore] as const,
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
#### Behavior with explicit versioning
|
|
344
|
+
|
|
345
|
+
When `versionStrategy` is `"explicit"`:
|
|
346
|
+
|
|
347
|
+
- Schema changes are **detected** but **NOT applied** automatically
|
|
348
|
+
- `removedStoreStrategy` is evaluated for preview purposes only
|
|
349
|
+
- A warning is logged if schema changes are detected but version is not bumped
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
[schema-idb] Schema changes detected but version not bumped:
|
|
353
|
+
- Rename store "oldStore" to "__oldStore_deleted_v2__"
|
|
354
|
+
Current DB version: 1, Provided version: 1
|
|
355
|
+
Bump the version to apply these changes.
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**Important:** `removedStoreStrategy` does not perform migrations in explicit mode. It only describes what _would_ happen after a version bump. To apply the changes, increment the `version` number.
|
|
359
|
+
|
|
360
|
+
To explicitly delete a store (including backups), use a migration:
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
const usersStore = defineStore("users", {
|
|
364
|
+
// ...
|
|
365
|
+
}).addMigration("003-delete-old-store", (db) => {
|
|
366
|
+
db.deleteObjectStore("oldStore");
|
|
367
|
+
db.deleteObjectStore("__oldStore_deleted_v2__"); // Remove backup too
|
|
368
|
+
});
|
|
369
|
+
```
|
|
370
|
+
|
|
320
371
|
---
|
|
321
372
|
|
|
322
373
|
## Type Inference
|
|
@@ -333,39 +384,689 @@ type User = InferStore<typeof usersStore>;
|
|
|
333
384
|
|
|
334
385
|
## API Reference
|
|
335
386
|
|
|
336
|
-
|
|
387
|
+
> This section is intended as a complete, authoritative reference.
|
|
388
|
+
> Most users will not need to read it top-to-bottom.
|
|
389
|
+
> For a guided introduction and examples, see the sections above.
|
|
390
|
+
|
|
391
|
+
### openDB
|
|
392
|
+
|
|
393
|
+
Opens a database connection with the given configuration.
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
function openDB<T extends readonly SchemaStoreDefinition[]>(options: {
|
|
397
|
+
name: string;
|
|
398
|
+
stores: T;
|
|
399
|
+
versionStrategy?: "auto" | "explicit";
|
|
400
|
+
version?: number;
|
|
401
|
+
removedStoreStrategy?: "error" | "preserve";
|
|
402
|
+
}): SchemaDatabase<T>;
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
| Option | Type | Description |
|
|
406
|
+
| ------ | ---- | ----------- |
|
|
407
|
+
| `name` | `string` | Database name |
|
|
408
|
+
| `stores` | `readonly SchemaStoreDefinition[]` | Store definitions created with `defineStore` |
|
|
409
|
+
| `versionStrategy` | `"auto" \| "explicit"` | `"auto"` detects schema changes automatically. Default: `"explicit"` (recommended for production control) |
|
|
410
|
+
| `version` | `number` | Required when `versionStrategy` is `"explicit"` |
|
|
411
|
+
| `removedStoreStrategy` | `"error" \| "preserve"` | How to handle removed stores. Default: `"error"` |
|
|
412
|
+
|
|
413
|
+
### SchemaDatabase
|
|
414
|
+
|
|
415
|
+
The database object returned by `openDB`.
|
|
416
|
+
|
|
417
|
+
| Property | Type | Description |
|
|
418
|
+
| -------- | ---- | ----------- |
|
|
419
|
+
| `name` | `string` | Database name |
|
|
420
|
+
| `version` | `number` | Current schema version |
|
|
421
|
+
| `ready` | `boolean` | Whether the database is ready |
|
|
422
|
+
| `raw` | `IDBDatabase` | Underlying IndexedDB instance |
|
|
423
|
+
| `[storeName]` | `StoreAccessor` | Direct access to stores (e.g., `db.users`) |
|
|
424
|
+
|
|
425
|
+
| Method | Signature | Description |
|
|
426
|
+
| ------ | --------- | ----------- |
|
|
427
|
+
| `waitForReady` | `() => Promise<void>` | Wait for database initialization |
|
|
428
|
+
| `close` | `() => void` | Close the database connection |
|
|
429
|
+
| `startTransaction` | `(stores, options?) => Transaction` | Start a multi-store transaction |
|
|
430
|
+
|
|
431
|
+
### Store Accessor
|
|
432
|
+
|
|
433
|
+
Each store is accessible as a property on the database object (e.g., `db.users`).
|
|
434
|
+
|
|
435
|
+
#### get
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
get(key: K): Promise<T | undefined>
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
| Param | Type | Description |
|
|
442
|
+
| ----- | ---- | ----------- |
|
|
443
|
+
| `key` | `K` | Primary key value |
|
|
444
|
+
|
|
445
|
+
Returns the record matching the key, or `undefined` if not found.
|
|
446
|
+
|
|
447
|
+
#### getAll
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
getAll(): Promise<T[]>
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Returns all records in the store.
|
|
454
|
+
|
|
455
|
+
#### getBy
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
getBy<I extends IndexedFields>(indexName: I, query: V | IDBKeyRange): Promise<T | undefined>
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
| Param | Type | Description |
|
|
462
|
+
| ----- | ---- | ----------- |
|
|
463
|
+
| `indexName` | `I` | Name of the index to query |
|
|
464
|
+
| `query` | `V \| IDBKeyRange` | Value to match or key range |
|
|
465
|
+
|
|
466
|
+
Returns the first record matching the index value.
|
|
467
|
+
|
|
468
|
+
#### getAllBy
|
|
469
|
+
|
|
470
|
+
```ts
|
|
471
|
+
getAllBy<I extends IndexedFields>(indexName: I, query?: V | IDBKeyRange): Promise<T[]>
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
| Param | Type | Description |
|
|
475
|
+
| ----- | ---- | ----------- |
|
|
476
|
+
| `indexName` | `I` | Name of the index to query |
|
|
477
|
+
| `query` | `V \| IDBKeyRange` | Value to match or key range (optional) |
|
|
478
|
+
|
|
479
|
+
Returns all records matching the index value. If `query` is omitted, returns all records ordered by the index.
|
|
480
|
+
|
|
481
|
+
#### put
|
|
482
|
+
|
|
483
|
+
```ts
|
|
484
|
+
put(value: T, key?: K): Promise<K>
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
| Param | Type | Description |
|
|
488
|
+
| ----- | ---- | ----------- |
|
|
489
|
+
| `value` | `T` | Record to insert or update |
|
|
490
|
+
| `key` | `K` | Optional key (only needed if store has no keyPath) |
|
|
491
|
+
|
|
492
|
+
Inserts a new record or updates an existing one. Returns the primary key.
|
|
493
|
+
|
|
494
|
+
#### add
|
|
495
|
+
|
|
496
|
+
```ts
|
|
497
|
+
add(value: T, key?: K): Promise<K>
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
| Param | Type | Description |
|
|
501
|
+
| ----- | ---- | ----------- |
|
|
502
|
+
| `value` | `T` | Record to insert |
|
|
503
|
+
| `key` | `K` | Optional key (only needed if store has no keyPath) |
|
|
504
|
+
|
|
505
|
+
Inserts a new record. Throws an error if the key already exists.
|
|
506
|
+
|
|
507
|
+
#### delete
|
|
508
|
+
|
|
509
|
+
```ts
|
|
510
|
+
delete(key: K | IDBKeyRange): Promise<void>
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
| Param | Type | Description |
|
|
514
|
+
| ----- | ---- | ----------- |
|
|
515
|
+
| `key` | `K \| IDBKeyRange` | Primary key or key range to delete |
|
|
516
|
+
|
|
517
|
+
Deletes record(s) matching the key or range.
|
|
518
|
+
|
|
519
|
+
#### clear
|
|
520
|
+
|
|
521
|
+
```ts
|
|
522
|
+
clear(): Promise<void>
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Deletes all records in the store.
|
|
526
|
+
|
|
527
|
+
#### count
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
count(query?: IDBKeyRange | IDBValidKey): Promise<number>
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
| Param | Type | Description |
|
|
534
|
+
| ----- | ---- | ----------- |
|
|
535
|
+
| `query` | `IDBKeyRange \| IDBValidKey` | Optional key or range to count |
|
|
536
|
+
|
|
537
|
+
Returns the number of records. If `query` is provided, counts only matching records.
|
|
538
|
+
|
|
539
|
+
#### query
|
|
540
|
+
|
|
541
|
+
```ts
|
|
542
|
+
query(options: QueryOptions): Promise<T[]>
|
|
543
|
+
query(): QueryBuilder
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
| Param | Type | Description |
|
|
547
|
+
| ----- | ---- | ----------- |
|
|
548
|
+
| `options` | `QueryOptions` | Query configuration (optional) |
|
|
549
|
+
|
|
550
|
+
When called with options, executes the query and returns results. When called without arguments, returns a `QueryBuilder` for chaining.
|
|
551
|
+
|
|
552
|
+
### Query Options
|
|
553
|
+
|
|
554
|
+
Used with `db.store.query(options)`.
|
|
555
|
+
|
|
556
|
+
```ts
|
|
557
|
+
interface QueryOptions {
|
|
558
|
+
index: string;
|
|
559
|
+
where?: WhereCondition;
|
|
560
|
+
orderBy?: "asc" | "desc";
|
|
561
|
+
limit?: number;
|
|
562
|
+
offset?: number;
|
|
563
|
+
}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
| Option | Type | Description |
|
|
567
|
+
| ------ | ---- | ----------- |
|
|
568
|
+
| `index` | `string` | Index name to query on |
|
|
569
|
+
| `where` | `WhereCondition` | Filter conditions (optional) |
|
|
570
|
+
| `orderBy` | `"asc" \| "desc"` | Sort order. Default: `"asc"` |
|
|
571
|
+
| `limit` | `number` | Maximum number of results (optional) |
|
|
572
|
+
| `offset` | `number` | Number of results to skip. Default: `0` |
|
|
573
|
+
|
|
574
|
+
#### WhereCondition
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
interface WhereCondition {
|
|
578
|
+
eq?: T;
|
|
579
|
+
gt?: T;
|
|
580
|
+
gte?: T;
|
|
581
|
+
lt?: T;
|
|
582
|
+
lte?: T;
|
|
583
|
+
between?: [T, T];
|
|
584
|
+
startsWith?: string;
|
|
585
|
+
}
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
| Option | Type | Description |
|
|
589
|
+
| ------ | ---- | ----------- |
|
|
590
|
+
| `eq` | `T` | Exact match |
|
|
591
|
+
| `gt` | `T` | Greater than |
|
|
592
|
+
| `gte` | `T` | Greater than or equal |
|
|
593
|
+
| `lt` | `T` | Less than |
|
|
594
|
+
| `lte` | `T` | Less than or equal |
|
|
595
|
+
| `between` | `[T, T]` | Inclusive range `[lower, upper]` |
|
|
596
|
+
| `startsWith` | `string` | Prefix match (string indexes only) |
|
|
597
|
+
|
|
598
|
+
### Query Builder
|
|
599
|
+
|
|
600
|
+
Returned when calling `db.store.query()` without arguments.
|
|
601
|
+
|
|
602
|
+
#### index
|
|
603
|
+
|
|
604
|
+
```ts
|
|
605
|
+
index<I extends IndexedFields>(name: I): IndexQueryBuilder
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
| Param | Type | Description |
|
|
609
|
+
| ----- | ---- | ----------- |
|
|
610
|
+
| `name` | `I` | Index name to query on |
|
|
611
|
+
|
|
612
|
+
Returns an `IndexQueryBuilder` for the specified index.
|
|
613
|
+
|
|
614
|
+
#### key
|
|
615
|
+
|
|
616
|
+
```ts
|
|
617
|
+
key(): IndexQueryBuilder
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
Returns an `IndexQueryBuilder` that queries by primary key.
|
|
621
|
+
|
|
622
|
+
#### findAll
|
|
623
|
+
|
|
624
|
+
```ts
|
|
625
|
+
findAll(): Promise<T[]>
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
Executes the query and returns all matching records.
|
|
629
|
+
|
|
630
|
+
### IndexQueryBuilder
|
|
631
|
+
|
|
632
|
+
Provides condition methods for filtering. All methods return a `FinalQueryBuilder`.
|
|
633
|
+
|
|
634
|
+
#### equals
|
|
635
|
+
|
|
636
|
+
```ts
|
|
637
|
+
equals(value: V): FinalQueryBuilder
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
| Param | Type | Description |
|
|
641
|
+
| ----- | ---- | ----------- |
|
|
642
|
+
| `value` | `V` | Value to match exactly |
|
|
643
|
+
|
|
644
|
+
#### gt / gte / lt / lte
|
|
645
|
+
|
|
646
|
+
```ts
|
|
647
|
+
gt(value: V): FinalQueryBuilder // Greater than
|
|
648
|
+
gte(value: V): FinalQueryBuilder // Greater than or equal
|
|
649
|
+
lt(value: V): FinalQueryBuilder // Less than
|
|
650
|
+
lte(value: V): FinalQueryBuilder // Less than or equal
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
| Param | Type | Description |
|
|
654
|
+
| ----- | ---- | ----------- |
|
|
655
|
+
| `value` | `V` | Boundary value for comparison |
|
|
656
|
+
|
|
657
|
+
#### between
|
|
658
|
+
|
|
659
|
+
```ts
|
|
660
|
+
between(lower: V, upper: V): FinalQueryBuilder
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
| Param | Type | Description |
|
|
664
|
+
| ----- | ---- | ----------- |
|
|
665
|
+
| `lower` | `V` | Lower bound (inclusive) |
|
|
666
|
+
| `upper` | `V` | Upper bound (inclusive) |
|
|
667
|
+
|
|
668
|
+
#### startsWith
|
|
669
|
+
|
|
670
|
+
```ts
|
|
671
|
+
startsWith(prefix: string): FinalQueryBuilder
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
| Param | Type | Description |
|
|
675
|
+
| ----- | ---- | ----------- |
|
|
676
|
+
| `prefix` | `string` | Prefix to match |
|
|
677
|
+
|
|
678
|
+
Only available for string indexes.
|
|
679
|
+
|
|
680
|
+
### FinalQueryBuilder
|
|
681
|
+
|
|
682
|
+
Provides result modifiers and execution methods.
|
|
683
|
+
|
|
684
|
+
#### orderBy
|
|
685
|
+
|
|
686
|
+
```ts
|
|
687
|
+
orderBy(order: "asc" | "desc"): FinalQueryBuilder
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
| Param | Type | Description |
|
|
691
|
+
| ----- | ---- | ----------- |
|
|
692
|
+
| `order` | `"asc" \| "desc"` | Sort direction |
|
|
693
|
+
|
|
694
|
+
#### limit
|
|
695
|
+
|
|
696
|
+
```ts
|
|
697
|
+
limit(count: number): FinalQueryBuilder
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
| Param | Type | Description |
|
|
701
|
+
| ----- | ---- | ----------- |
|
|
702
|
+
| `count` | `number` | Maximum number of results |
|
|
703
|
+
|
|
704
|
+
#### offset
|
|
705
|
+
|
|
706
|
+
```ts
|
|
707
|
+
offset(count: number): FinalQueryBuilder
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
| Param | Type | Description |
|
|
711
|
+
| ----- | ---- | ----------- |
|
|
712
|
+
| `count` | `number` | Number of results to skip |
|
|
713
|
+
|
|
714
|
+
#### findAll
|
|
715
|
+
|
|
716
|
+
```ts
|
|
717
|
+
findAll(): Promise<T[]>
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
Executes the query and returns all matching records.
|
|
721
|
+
|
|
722
|
+
#### find
|
|
723
|
+
|
|
724
|
+
```ts
|
|
725
|
+
find(): Promise<T | undefined>
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
Executes the query and returns the first matching record, or `undefined` if none found.
|
|
729
|
+
|
|
730
|
+
#### count
|
|
731
|
+
|
|
732
|
+
```ts
|
|
733
|
+
count(): Promise<number>
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
Returns the number of matching records without fetching them.
|
|
737
|
+
|
|
738
|
+
### Transaction
|
|
739
|
+
|
|
740
|
+
#### startTransaction
|
|
741
|
+
|
|
742
|
+
```ts
|
|
743
|
+
startTransaction(
|
|
744
|
+
stores: string | string[],
|
|
745
|
+
options?: TransactionOptions
|
|
746
|
+
): Transaction
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
| Param | Type | Description |
|
|
750
|
+
| ----- | ---- | ----------- |
|
|
751
|
+
| `stores` | `string \| string[]` | Store name(s) to include in the transaction |
|
|
752
|
+
| `options` | `TransactionOptions` | Transaction configuration (optional) |
|
|
753
|
+
|
|
754
|
+
##### TransactionOptions
|
|
755
|
+
|
|
756
|
+
| Option | Type | Description |
|
|
757
|
+
| ------ | ---- | ----------- |
|
|
758
|
+
| `mode` | `"write"` | Transaction mode. Currently only `"write"` is supported |
|
|
759
|
+
| `durability` | `"default" \| "strict" \| "relaxed"` | Durability hint for the transaction. Default: `"default"` |
|
|
760
|
+
|
|
761
|
+
#### Transaction Object
|
|
762
|
+
|
|
763
|
+
| Property | Type | Description |
|
|
764
|
+
| -------- | ---- | ----------- |
|
|
765
|
+
| `raw` | `IDBTransaction` | Underlying IndexedDB transaction |
|
|
766
|
+
| `[storeName]` | `TransactionStoreAccessor` | Synchronous store accessor for each included store |
|
|
767
|
+
|
|
768
|
+
##### commit
|
|
769
|
+
|
|
770
|
+
```ts
|
|
771
|
+
commit(): Promise<void>
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
Commits all queued operations and waits for completion.
|
|
775
|
+
|
|
776
|
+
##### abort
|
|
777
|
+
|
|
778
|
+
```ts
|
|
779
|
+
abort(): void
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
Aborts the transaction, discarding all queued operations.
|
|
783
|
+
|
|
784
|
+
#### TransactionStoreAccessor
|
|
785
|
+
|
|
786
|
+
Synchronous operations for use within transactions. Operations are queued and executed when `commit()` is called.
|
|
787
|
+
|
|
788
|
+
##### put
|
|
789
|
+
|
|
790
|
+
```ts
|
|
791
|
+
put(value: T, key?: K): void
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
| Param | Type | Description |
|
|
795
|
+
| ----- | ---- | ----------- |
|
|
796
|
+
| `value` | `T` | Record to insert or update |
|
|
797
|
+
| `key` | `K` | Optional key (only needed if store has no keyPath) |
|
|
798
|
+
|
|
799
|
+
##### add
|
|
800
|
+
|
|
801
|
+
```ts
|
|
802
|
+
add(value: T, key?: K): void
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
| Param | Type | Description |
|
|
806
|
+
| ----- | ---- | ----------- |
|
|
807
|
+
| `value` | `T` | Record to insert |
|
|
808
|
+
| `key` | `K` | Optional key (only needed if store has no keyPath) |
|
|
809
|
+
|
|
810
|
+
##### delete
|
|
811
|
+
|
|
812
|
+
```ts
|
|
813
|
+
delete(key: K | IDBKeyRange): void
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
| Param | Type | Description |
|
|
817
|
+
| ----- | ---- | ----------- |
|
|
818
|
+
| `key` | `K \| IDBKeyRange` | Primary key or key range to delete |
|
|
819
|
+
|
|
820
|
+
##### clear
|
|
821
|
+
|
|
822
|
+
```ts
|
|
823
|
+
clear(): void
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
Queues deletion of all records in the store.
|
|
827
|
+
|
|
828
|
+
### defineStore
|
|
829
|
+
|
|
830
|
+
Creates a store definition with schema.
|
|
831
|
+
|
|
832
|
+
```ts
|
|
833
|
+
defineStore<N extends string, S extends StoreSchema>(
|
|
834
|
+
name: N,
|
|
835
|
+
schema: S
|
|
836
|
+
): SchemaStoreDefinition<N, S>
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
| Param | Type | Description |
|
|
840
|
+
| ----- | ---- | ----------- |
|
|
841
|
+
| `name` | `N` | Store name (used as `db.name` accessor) |
|
|
842
|
+
| `schema` | `S` | Object defining fields using `field` builders |
|
|
843
|
+
|
|
844
|
+
Returns a `SchemaStoreDefinition` with the following method:
|
|
845
|
+
|
|
846
|
+
#### addMigration
|
|
847
|
+
|
|
848
|
+
```ts
|
|
849
|
+
addMigration(
|
|
850
|
+
name: string,
|
|
851
|
+
fn: (db: IDBDatabase, tx: IDBTransaction) => void
|
|
852
|
+
): this
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
| Param | Type | Description |
|
|
856
|
+
| ----- | ---- | ----------- |
|
|
857
|
+
| `name` | `string` | Unique migration identifier (sorted alphabetically) |
|
|
858
|
+
| `fn` | `MigrationFn` | Migration function with access to database and transaction |
|
|
859
|
+
|
|
860
|
+
Migrations run during version upgrades in alphabetical order by name.
|
|
861
|
+
|
|
862
|
+
### field
|
|
863
|
+
|
|
864
|
+
Field type builders for schema definition.
|
|
865
|
+
|
|
866
|
+
#### field.string
|
|
867
|
+
|
|
868
|
+
```ts
|
|
869
|
+
field.string(): FieldBuilder<string>
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
Creates a string field.
|
|
873
|
+
|
|
874
|
+
#### field.number
|
|
875
|
+
|
|
876
|
+
```ts
|
|
877
|
+
field.number(): FieldBuilder<number>
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
Creates a number field.
|
|
881
|
+
|
|
882
|
+
#### field.boolean
|
|
337
883
|
|
|
338
884
|
```ts
|
|
339
|
-
|
|
885
|
+
field.boolean(): FieldBuilder<boolean>
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
Creates a boolean field.
|
|
340
889
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
db.raw; // Underlying IDBDatabase
|
|
890
|
+
#### field.date
|
|
891
|
+
|
|
892
|
+
```ts
|
|
893
|
+
field.date(): FieldBuilder<Date>
|
|
346
894
|
```
|
|
347
895
|
|
|
348
|
-
|
|
896
|
+
Creates a Date field.
|
|
897
|
+
|
|
898
|
+
#### field.object
|
|
349
899
|
|
|
350
900
|
```ts
|
|
351
|
-
|
|
352
|
-
db.store.getAll(); // Get all records
|
|
353
|
-
db.store.getAllByIndex(index, value); // Get by index value
|
|
354
|
-
db.store.put(value); // Insert or update
|
|
355
|
-
db.store.add(value); // Insert (fails if exists)
|
|
356
|
-
db.store.delete(key); // Delete by key
|
|
357
|
-
db.store.clear(); // Delete all
|
|
358
|
-
db.store.count(); // Count records
|
|
359
|
-
db.store.query(options); // Query with conditions
|
|
901
|
+
field.object<S>(schema: (t: TypeFactory) => S): FieldBuilder<InferObjectType<S>>
|
|
360
902
|
```
|
|
361
903
|
|
|
362
|
-
|
|
904
|
+
| Param | Type | Description |
|
|
905
|
+
| ----- | ---- | ----------- |
|
|
906
|
+
| `schema` | `(t: TypeFactory) => S` | Function returning an object schema using type builders |
|
|
907
|
+
|
|
908
|
+
Creates a nested object field.
|
|
363
909
|
|
|
364
910
|
```ts
|
|
365
|
-
|
|
911
|
+
field.object(t => ({
|
|
912
|
+
street: t.string(),
|
|
913
|
+
city: t.string(),
|
|
914
|
+
zipCode: t.number().optional(),
|
|
915
|
+
}))
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
#### field.tuple
|
|
366
919
|
|
|
367
|
-
|
|
368
|
-
|
|
920
|
+
```ts
|
|
921
|
+
field.tuple<T>(schema: (t: TypeFactory) => T): FieldBuilder<InferTupleType<T>>
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
| Param | Type | Description |
|
|
925
|
+
| ----- | ---- | ----------- |
|
|
926
|
+
| `schema` | `(t: TypeFactory) => T` | Function returning a tuple schema as array |
|
|
927
|
+
|
|
928
|
+
Creates a fixed-length tuple field.
|
|
929
|
+
|
|
930
|
+
```ts
|
|
931
|
+
field.tuple(t => [t.number(), t.number()]) // [number, number]
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
#### field.enum
|
|
935
|
+
|
|
936
|
+
```ts
|
|
937
|
+
field.enum<T extends readonly string[]>(values: T): FieldBuilder<T[number]>
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
| Param | Type | Description |
|
|
941
|
+
| ----- | ---- | ----------- |
|
|
942
|
+
| `values` | `readonly string[]` | Array of allowed string values |
|
|
943
|
+
|
|
944
|
+
Creates a string union type field.
|
|
945
|
+
|
|
946
|
+
```ts
|
|
947
|
+
field.enum(['active', 'inactive', 'pending'] as const)
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
#### field.nativeEnum
|
|
951
|
+
|
|
952
|
+
```ts
|
|
953
|
+
field.nativeEnum<T extends Record<string, string | number>>(enumObj: T): FieldBuilder<T[keyof T]>
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
| Param | Type | Description |
|
|
957
|
+
| ----- | ---- | ----------- |
|
|
958
|
+
| `enumObj` | `T` | TypeScript enum object |
|
|
959
|
+
|
|
960
|
+
Creates a field from a TypeScript enum.
|
|
961
|
+
|
|
962
|
+
```ts
|
|
963
|
+
enum Status { Active = 'active', Inactive = 'inactive' }
|
|
964
|
+
field.nativeEnum(Status)
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
### FieldBuilder
|
|
968
|
+
|
|
969
|
+
Methods available on all field builders. All methods return `this` for chaining.
|
|
970
|
+
|
|
971
|
+
#### primaryKey
|
|
972
|
+
|
|
973
|
+
```ts
|
|
974
|
+
primaryKey(): FieldBuilder
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
Marks the field as the store's primary key. Exactly one field per store must be marked as primary key.
|
|
978
|
+
|
|
979
|
+
#### index
|
|
980
|
+
|
|
981
|
+
```ts
|
|
982
|
+
index(options?: IndexOptions): FieldBuilder
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
| Param | Type | Description |
|
|
986
|
+
| ----- | ---- | ----------- |
|
|
987
|
+
| `options.unique` | `boolean` | If `true`, enforces unique values. Default: `false` |
|
|
988
|
+
| `options.multiEntry` | `boolean` | If `true`, indexes each array element separately. Default: `false` |
|
|
989
|
+
|
|
990
|
+
Creates an index on this field, enabling queries via `query()`, `getBy()`, and `getAllBy()`.
|
|
991
|
+
|
|
992
|
+
#### optional
|
|
993
|
+
|
|
994
|
+
```ts
|
|
995
|
+
optional(): FieldBuilder
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
Marks the field as optional, allowing `undefined` values.
|
|
999
|
+
|
|
1000
|
+
#### default
|
|
1001
|
+
|
|
1002
|
+
```ts
|
|
1003
|
+
default(value: T | (() => T)): FieldBuilder
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
| Param | Type | Description |
|
|
1007
|
+
| ----- | ---- | ----------- |
|
|
1008
|
+
| `value` | `T \| (() => T)` | Default value or factory function |
|
|
1009
|
+
|
|
1010
|
+
Sets a default value applied on read when the field is missing. Factory functions are called for each read.
|
|
1011
|
+
|
|
1012
|
+
```ts
|
|
1013
|
+
field.number().default(0)
|
|
1014
|
+
field.date().default(() => new Date())
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
#### array
|
|
1018
|
+
|
|
1019
|
+
```ts
|
|
1020
|
+
array(): FieldBuilder<T[]>
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
Converts the field type to an array.
|
|
1024
|
+
|
|
1025
|
+
```ts
|
|
1026
|
+
field.string().array() // string[]
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
### Utility Functions
|
|
1030
|
+
|
|
1031
|
+
#### deleteDB
|
|
1032
|
+
|
|
1033
|
+
```ts
|
|
1034
|
+
deleteDB(name: string): Promise<void>
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
| Param | Type | Description |
|
|
1038
|
+
| ----- | ---- | ----------- |
|
|
1039
|
+
| `name` | `string` | Database name to delete |
|
|
1040
|
+
|
|
1041
|
+
Deletes the database and all its data.
|
|
1042
|
+
|
|
1043
|
+
#### isIndexedDBAvailable
|
|
1044
|
+
|
|
1045
|
+
```ts
|
|
1046
|
+
isIndexedDBAvailable(): boolean
|
|
1047
|
+
```
|
|
1048
|
+
|
|
1049
|
+
Returns `true` if IndexedDB is available in the current environment.
|
|
1050
|
+
|
|
1051
|
+
### Type Utilities
|
|
1052
|
+
|
|
1053
|
+
#### InferStore
|
|
1054
|
+
|
|
1055
|
+
```ts
|
|
1056
|
+
type InferStore<T> = /* inferred output type from store definition */
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
Extracts the TypeScript type from a store definition:
|
|
1060
|
+
|
|
1061
|
+
```ts
|
|
1062
|
+
const usersStore = defineStore("users", {
|
|
1063
|
+
id: field.string().primaryKey(),
|
|
1064
|
+
name: field.string(),
|
|
1065
|
+
age: field.number().optional().default(0),
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
type User = InferStore<typeof usersStore>;
|
|
1069
|
+
// { id: string; name: string; age: number }
|
|
369
1070
|
```
|
|
370
1071
|
|
|
371
1072
|
---
|
package/dist/createSchemaDB.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { StoreSchema, InferInput, InferOutput, PrimaryKeyType, IndexedFields } from './field.js';
|
|
1
|
+
import type { StoreSchema, InferInput, InferOutput, PrimaryKeyType, IndexedFields, IndexFieldTypes } from './field.js';
|
|
2
2
|
import type { SchemaStoreDefinition } from './schema.js';
|
|
3
3
|
import type { TypedQueryOptions, TypedQueryBuilder } from './query.js';
|
|
4
4
|
import type { Transaction, TransactionOptions } from './transaction.js';
|
|
@@ -7,7 +7,8 @@ type StoreNames<TStores extends readonly AnySchemaStore[]> = TStores[number]['na
|
|
|
7
7
|
export interface SchemaStoreAccessor<S extends StoreSchema> {
|
|
8
8
|
get(key: PrimaryKeyType<S>): Promise<InferOutput<S> | undefined>;
|
|
9
9
|
getAll(): Promise<InferOutput<S>[]>;
|
|
10
|
-
|
|
10
|
+
getBy<I extends IndexedFields<S> & string>(indexName: I, query: IDBKeyRange | IndexFieldTypes<S>[I]): Promise<InferOutput<S> | undefined>;
|
|
11
|
+
getAllBy<I extends IndexedFields<S> & string>(indexName: I, query?: IDBKeyRange | IndexFieldTypes<S>[I]): Promise<InferOutput<S>[]>;
|
|
11
12
|
put(value: InferInput<S>, key?: PrimaryKeyType<S>): Promise<PrimaryKeyType<S>>;
|
|
12
13
|
add(value: InferInput<S>, key?: PrimaryKeyType<S>): Promise<PrimaryKeyType<S>>;
|
|
13
14
|
delete(key: PrimaryKeyType<S> | IDBKeyRange): Promise<void>;
|
|
@@ -20,6 +21,7 @@ export interface SchemaDBConfig<TStores extends readonly AnySchemaStore[]> {
|
|
|
20
21
|
name: string;
|
|
21
22
|
version?: number;
|
|
22
23
|
versionStrategy?: 'explicit' | 'auto';
|
|
24
|
+
removedStoreStrategy?: 'error' | 'preserve';
|
|
23
25
|
stores: TStores;
|
|
24
26
|
onBlocked?: () => void;
|
|
25
27
|
onVersionChange?: () => void;
|
package/dist/createSchemaDB.js
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var E=Object.defineProperty;var d=(e,r)=>E(e,"name",{value:r,configurable:!0});import{openDatabase as T}from"./utils.js";import{createStoreAccessor as D}from"./storeAccessor.js";import{createStartTransaction as A}from"./transaction.js";import{determineAutoVersion as k,applySafeChanges as R,openDatabaseForSchemaRead as $,readExistingSchema as M,toDesiredSchema as z,detectSchemaChanges as j}from"./schemaDetection.js";import{ensureSchemaHistoryStore as B,getAppliedMigrations as x,recordMigrationApplied as C,initializeSchemaHistory as q}from"./migrationHistory.js";function Y(e){return e}d(Y,"getKeyPathString");function U(e,r,a){const n=d(async()=>{if(await e.readyPromise,!e.idb)throw new Error("Database initialization failed");return D(e.idb,r,a)},"getAccessor");return new Proxy({},{get(t,o){return o==="query"?c=>c?n().then(u=>u.query(c)):F(e,r,a):async(...c)=>(await n())[o](...c)}})}d(U,"createLazyStoreAccessor");function F(e,r,a){const n=d(async()=>{if(await e.readyPromise,!e.idb)throw new Error("Database initialization failed");return D(e.idb,r,a).query()},"getQueryBuilder"),t=d(o=>new Proxy({},{get(c,u){return u==="findAll"||u==="find"||u==="count"?()=>o().then(s=>s[u]()):(...s)=>t(d(()=>o().then(l=>l[u](...s)),"newGetBuilder"))}}),"createBuilderProxy");return t(n)}d(F,"createLazyQueryBuilder");function K(e,r){const a={get name(){return e.idb?.name??r},get version(){return e.idb?.version??0},get raw(){if(!e.idb)throw new Error("Database not ready. Call waitForReady() first or check ready property.");return e.idb},get ready(){return e.ready},waitForReady(){return e.readyPromise},close(){e.idb?.close()},startTransaction(...n){if(!e.startTransaction){const[t,o]=n,c=Array.isArray(t)?t:[t];return L(e,c,o)}return e.startTransaction(...n)}};for(const n of e.stores)Object.defineProperty(a,n.name,{get(){return U(e,n.name,n.defaults)},enumerable:!0});return a}d(K,"buildSchemaDatabase");function L(e,r,a){const n={get raw(){throw new Error("Transaction raw is not available before ready state. Use await db.waitForReady() before starting transactions.")},async commit(){if(await e.readyPromise,!e.startTransaction)throw new Error("Database initialization failed");return e.startTransaction(r,a).commit()},abort(){}};for(const t of r)e.stores.find(c=>c.name===t)&&Object.defineProperty(n,t,{get(){throw new Error("Transaction operations before ready state are not yet supported. Use await db.waitForReady() before starting transactions.")},enumerable:!0});return n}d(L,"createLazyTransaction");function O(e){const r=[],a=new Set;for(const n of e)for(const t of n.migrations){if(a.has(t.name))throw new Error(`Duplicate migration name "${t.name}" found across stores`);a.add(t.name),r.push(t)}return r.sort((n,t)=>n.name.localeCompare(t.name))}d(O,"collectMigrations");function N(e,r){const a=new Set(r);return e.filter(n=>!a.has(n.name)).sort((n,t)=>n.name.localeCompare(t.name))}d(N,"filterPendingMigrations");function V(e,r,a,n,t,o,c){if(B(e),a===0){for(const s of n){const i=e.createObjectStore(s.name,{keyPath:s.keyPath});for(const l of s.indexes)i.createIndex(l.name,l.keyPath,{unique:l.unique??!1,multiEntry:l.multiEntry??!1})}q(r)}else c&&c.safe.length>0&&R(e,r,c.safe,n);let u=[...o];for(const s of t)try{const i=s.up(e,r);i instanceof Promise&&i.catch(l=>{console.error(`Migration "${s.name}" failed:`,l),r.abort()}),C(r,s.name,u),u=[...u,s.name].sort()}catch(i){throw console.error(`Migration "${s.name}" failed:`,i),r.abort(),i}}d(V,"handleUpgrade");export function openDB(e){const{name:r,version:a,versionStrategy:n="explicit",removedStoreStrategy:t="error",stores:o,onBlocked:c,onVersionChange:u}=e,s=new Set;for(const f of o){if(s.has(f.name))throw new Error(`Duplicate store name: "${f.name}"`);s.add(f.name)}const i=O(o);let l=d(()=>{},"readyResolve"),g=d(()=>{},"readyReject");const p={idb:null,ready:!1,error:null,readyPromise:new Promise((f,y)=>{l=f,g=y}),readyResolve:l,readyReject:g,stores:o,startTransaction:null},m=K(p,r);return G(p,r,a,n,t,o,i,c,u),m}d(openDB,"openDB");async function G(e,r,a,n,t,o,c,u,s){try{let i=[],l=null,g;if(n==="auto"){const m=await k(r,o,{removedStoreStrategy:t});if(g=m.version,l=m.changes,m.version>1){const y=await $(r);y&&(i=await x(y),y.close())}N(c,i).length>0&&!m.needsUpgrade&&(g=m.version+1)}else{if(a===void 0)throw new Error('Version is required when versionStrategy is "explicit"');g=a;const m=await $(r);if(m){const f=m.version;i=await x(m);const y=M(m),_=z(o);m.close();const b=j(y,_);if(b.hasChanges){const S=[];for(const w of b.dangerous)w.type==="store_delete"&&t==="preserve"?b.safe.push({type:"store_rename",oldName:w.storeName,newName:`__${w.storeName}_deleted_v${f}__`}):S.push(w);if(S.length>0){const h=`Dangerous schema changes detected:
|
|
2
|
+
${S.map(v=>{switch(v.type){case"store_delete":return`Store "${v.storeName}" would be deleted. Use removedStoreStrategy: 'preserve' to backup, or add a migration to explicitly delete it.`;case"keypath_change":return`Store "${v.storeName}" keyPath changed from "${v.oldKeyPath}" to "${v.newKeyPath}". This requires recreating the store with a manual migration.`;default:return"Unknown dangerous change"}}).join(`
|
|
3
|
+
`)}
|
|
4
|
+
|
|
5
|
+
Add explicit migrations to handle these changes safely.`;throw console.error("[schema-idb]",h),new Error(h)}if(b.dangerous=S,l=b,g<=f){const w=b.safe.map(h=>{switch(h.type){case"store_add":return`- Add store "${h.storeName}"`;case"store_rename":return`- Rename store "${h.oldName}" to "${h.newName}"`;case"index_add":return`- Add index "${h.indexName}" on "${h.storeName}"`;case"index_delete":return`- Delete index "${h.indexName}" from "${h.storeName}"`;default:return"- Schema change"}});console.warn(`[schema-idb] Schema changes detected but version not bumped:
|
|
6
|
+
${w.join(`
|
|
7
|
+
`)}
|
|
8
|
+
Current DB version: ${f}, Provided version: ${g}
|
|
9
|
+
Bump the version to apply these changes.`)}}}}const P=N(c,i),p=await T(r,g,(m,f,y)=>{V(m,f,y,o,P,i,l)},u);s&&(p.onversionchange=s),e.idb=p,e.startTransaction=A(p,o),e.ready=!0,e.readyResolve()}catch(i){e.error=i instanceof Error?i:new Error(String(i)),e.readyReject(e.error)}}d(G,"initializeDatabase");
|
package/dist/field.d.ts
CHANGED
|
@@ -92,6 +92,7 @@ export type IndexedFields<S extends StoreSchema> = {
|
|
|
92
92
|
export type IndexFieldTypes<S extends StoreSchema> = {
|
|
93
93
|
[K in IndexedFields<S>]: S[K] extends FieldBuilder<infer T, boolean, boolean, boolean> ? T : never;
|
|
94
94
|
};
|
|
95
|
+
export type FieldType<S extends StoreSchema, K extends keyof S> = S[K] extends FieldBuilder<infer T, boolean, boolean, boolean> ? T : never;
|
|
95
96
|
export type InferStore<TStore> = TStore extends {
|
|
96
97
|
schema: infer S;
|
|
97
98
|
} ? S extends StoreSchema ? InferOutput<S> : never : never;
|
package/dist/query.d.ts
CHANGED
|
@@ -9,6 +9,22 @@ export interface WhereCondition<T = unknown> {
|
|
|
9
9
|
between?: [T, T];
|
|
10
10
|
startsWith?: string;
|
|
11
11
|
}
|
|
12
|
+
export type TypedWhereCondition<T> = T extends string ? {
|
|
13
|
+
eq?: T;
|
|
14
|
+
gt?: T;
|
|
15
|
+
gte?: T;
|
|
16
|
+
lt?: T;
|
|
17
|
+
lte?: T;
|
|
18
|
+
between?: [T, T];
|
|
19
|
+
startsWith?: string;
|
|
20
|
+
} : {
|
|
21
|
+
eq?: T;
|
|
22
|
+
gt?: T;
|
|
23
|
+
gte?: T;
|
|
24
|
+
lt?: T;
|
|
25
|
+
lte?: T;
|
|
26
|
+
between?: [T, T];
|
|
27
|
+
};
|
|
12
28
|
export interface QueryOptions {
|
|
13
29
|
index?: string;
|
|
14
30
|
where?: WhereCondition;
|
|
@@ -16,13 +32,21 @@ export interface QueryOptions {
|
|
|
16
32
|
limit?: number;
|
|
17
33
|
offset?: number;
|
|
18
34
|
}
|
|
19
|
-
export
|
|
20
|
-
|
|
35
|
+
export type TypedQueryOptions<S extends StoreSchema> = {
|
|
36
|
+
[I in IndexedFields<S>]: {
|
|
37
|
+
index: I;
|
|
38
|
+
where?: TypedWhereCondition<IndexFieldTypes<S>[I]>;
|
|
39
|
+
orderBy?: SortOrder;
|
|
40
|
+
limit?: number;
|
|
41
|
+
offset?: number;
|
|
42
|
+
};
|
|
43
|
+
}[IndexedFields<S>] | {
|
|
44
|
+
index?: undefined;
|
|
21
45
|
where?: WhereCondition<unknown>;
|
|
22
46
|
orderBy?: SortOrder;
|
|
23
47
|
limit?: number;
|
|
24
48
|
offset?: number;
|
|
25
|
-
}
|
|
49
|
+
};
|
|
26
50
|
interface QueryState {
|
|
27
51
|
indexName?: string;
|
|
28
52
|
useKey: boolean;
|
|
@@ -125,20 +149,23 @@ export interface IndexQueryBuilder<T> {
|
|
|
125
149
|
findAll(): Promise<T[]>;
|
|
126
150
|
find(): Promise<T | undefined>;
|
|
127
151
|
}
|
|
128
|
-
|
|
129
|
-
equals(value:
|
|
130
|
-
gt(value:
|
|
131
|
-
gte(value:
|
|
132
|
-
lt(value:
|
|
133
|
-
lte(value:
|
|
134
|
-
between(lower:
|
|
135
|
-
startsWith(prefix: IndexFieldTypes<S>[I] extends string ? string : never): FinalQueryBuilder<T>;
|
|
152
|
+
interface BaseIndexQueryBuilder<T, V> {
|
|
153
|
+
equals(value: V): FinalQueryBuilder<T>;
|
|
154
|
+
gt(value: V): FinalQueryBuilder<T>;
|
|
155
|
+
gte(value: V): FinalQueryBuilder<T>;
|
|
156
|
+
lt(value: V): FinalQueryBuilder<T>;
|
|
157
|
+
lte(value: V): FinalQueryBuilder<T>;
|
|
158
|
+
between(lower: V, upper: V): FinalQueryBuilder<T>;
|
|
136
159
|
orderBy(order: SortOrder): FinalQueryBuilder<T>;
|
|
137
160
|
limit(count: number): FinalQueryBuilder<T>;
|
|
138
161
|
offset(count: number): FinalQueryBuilder<T>;
|
|
139
162
|
findAll(): Promise<T[]>;
|
|
140
163
|
find(): Promise<T | undefined>;
|
|
141
164
|
}
|
|
165
|
+
interface StringIndexQueryBuilder<T> {
|
|
166
|
+
startsWith(prefix: string): FinalQueryBuilder<T>;
|
|
167
|
+
}
|
|
168
|
+
export type TypedIndexQueryBuilder<T, S extends StoreSchema, I extends IndexedFields<S>> = BaseIndexQueryBuilder<T, IndexFieldTypes<S>[I]> & (IndexFieldTypes<S>[I] extends string ? StringIndexQueryBuilder<T> : {});
|
|
142
169
|
export interface FinalQueryBuilder<T> {
|
|
143
170
|
orderBy(order: SortOrder): FinalQueryBuilder<T>;
|
|
144
171
|
limit(count: number): FinalQueryBuilder<T>;
|
|
@@ -19,6 +19,10 @@ export type SchemaChangeType = {
|
|
|
19
19
|
} | {
|
|
20
20
|
type: 'store_delete';
|
|
21
21
|
storeName: string;
|
|
22
|
+
} | {
|
|
23
|
+
type: 'store_rename';
|
|
24
|
+
oldName: string;
|
|
25
|
+
newName: string;
|
|
22
26
|
} | {
|
|
23
27
|
type: 'keypath_change';
|
|
24
28
|
storeName: string;
|
|
@@ -61,19 +65,16 @@ export declare function applySafeChanges(db: IDBDatabase, tx: IDBTransaction, ch
|
|
|
61
65
|
keyPath: string | string[] | undefined;
|
|
62
66
|
indexes: IndexDefinition[];
|
|
63
67
|
}[]): void;
|
|
64
|
-
export declare function generateSchemaFingerprint(stores: readonly {
|
|
65
|
-
name: string;
|
|
66
|
-
keyPath: string | string[] | undefined;
|
|
67
|
-
indexes: IndexDefinition[];
|
|
68
|
-
}[]): string;
|
|
69
|
-
export declare function hashFingerprint(fingerprint: string): number;
|
|
70
68
|
export declare function getCurrentDatabaseVersion(dbName: string): Promise<number>;
|
|
71
69
|
export declare function openDatabaseForSchemaRead(dbName: string): Promise<IDBDatabase | null>;
|
|
70
|
+
export interface AutoVersionOptions {
|
|
71
|
+
removedStoreStrategy?: 'error' | 'preserve';
|
|
72
|
+
}
|
|
72
73
|
export declare function determineAutoVersion(dbName: string, stores: readonly {
|
|
73
74
|
name: string;
|
|
74
75
|
keyPath: string | string[] | undefined;
|
|
75
76
|
indexes: IndexDefinition[];
|
|
76
|
-
}[]): Promise<{
|
|
77
|
+
}[], options?: AutoVersionOptions): Promise<{
|
|
77
78
|
version: number;
|
|
78
79
|
changes: SchemaChanges | null;
|
|
79
80
|
needsUpgrade: boolean;
|
package/dist/schemaDetection.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
${
|
|
1
|
+
"use strict";var g=Object.defineProperty;var l=(r,t)=>g(r,"name",{value:t,configurable:!0});function p(r){return r.startsWith("__")}l(p,"isInternalStore");export function readExistingSchema(r){const t=new Map,n=Array.from({length:r.objectStoreNames.length},(a,o)=>r.objectStoreNames.item(o)).filter(a=>!p(a));for(const a of n){const o=r.transaction(a,"readonly"),u=o.objectStore(a),s=new Map,c=Array.from({length:u.indexNames.length},(e,i)=>u.indexNames.item(i));for(const e of c){const i=u.index(e);s.set(e,{keyPath:i.keyPath,unique:i.unique,multiEntry:i.multiEntry})}t.set(a,{name:a,keyPath:u.keyPath,indexes:s}),o.abort()}return t}l(readExistingSchema,"readExistingSchema");export function toDesiredSchema(r){const t=new Map;for(const n of r)t.set(n.name,{name:n.name,keyPath:n.keyPath,indexes:n.indexes});return t}l(toDesiredSchema,"toDesiredSchema");function x(r,t){return r===t||r==null&&t==null?!0:r==null||t==null?!1:Array.isArray(r)&&Array.isArray(t)?r.length!==t.length?!1:r.every((n,a)=>n===t[a]):r===t}l(x,"keyPathEquals");export function detectSchemaChanges(r,t){const n=[],a=[];for(const[o,u]of t){const s=r.get(o);if(!s){n.push({type:"store_add",storeName:o});continue}if(!x(s.keyPath,u.keyPath)){a.push({type:"keypath_change",storeName:o,oldKeyPath:s.keyPath,newKeyPath:u.keyPath});continue}for(const e of u.indexes){const i=s.indexes.get(e.name);if(!i)n.push({type:"index_add",storeName:o,indexName:e.name,index:e});else{const m=!x(i.keyPath,e.keyPath),h=i.unique!==(e.unique??!1),d=i.multiEntry!==(e.multiEntry??!1);(m||h||d)&&(n.push({type:"index_delete",storeName:o,indexName:e.name}),n.push({type:"index_add",storeName:o,indexName:e.name,index:e}))}}const c=new Set(u.indexes.map(e=>e.name));for(const e of s.indexes.keys())c.has(e)||n.push({type:"index_delete",storeName:o,indexName:e})}for(const o of r.keys())t.has(o)||a.push({type:"store_delete",storeName:o});return{safe:n,dangerous:a,hasChanges:n.length>0||a.length>0}}l(detectSchemaChanges,"detectSchemaChanges");export function applySafeChanges(r,t,n,a){const o=n.filter(s=>s.type==="store_rename"),u=new Map;for(const s of o){const c=t.objectStore(s.oldName),e=[],i=Array.from({length:c.indexNames.length},(h,d)=>c.indexNames.item(d));for(const h of i){const d=c.index(h);e.push({name:h,keyPath:d.keyPath,unique:d.unique,multiEntry:d.multiEntry})}u.set(s.oldName,{keyPath:c.keyPath,indexes:e,newName:s.newName});const m=c.getAll();m.onsuccess=()=>{const h=m.result,d=u.get(s.oldName),y=r.createObjectStore(d.newName,{keyPath:d.keyPath});for(const f of d.indexes)y.createIndex(f.name,f.keyPath,{unique:f.unique,multiEntry:f.multiEntry});for(const f of h)y.put(f)},r.deleteObjectStore(s.oldName)}for(const s of n)switch(s.type){case"store_add":{const c=a.find(e=>e.name===s.storeName);if(c){const e=r.createObjectStore(c.name,{keyPath:c.keyPath});for(const i of c.indexes)e.createIndex(i.name,i.keyPath,{unique:i.unique??!1,multiEntry:i.multiEntry??!1})}break}case"index_add":{t.objectStore(s.storeName).createIndex(s.indexName,s.index.keyPath,{unique:s.index.unique??!1,multiEntry:s.index.multiEntry??!1});break}case"index_delete":{t.objectStore(s.storeName).deleteIndex(s.indexName);break}}}l(applySafeChanges,"applySafeChanges");export async function getCurrentDatabaseVersion(r){return new Promise(t=>{const n=indexedDB.open(r);n.onsuccess=()=>{const a=n.result,o=a.version;a.close(),t(o)},n.onerror=()=>{t(0)}})}l(getCurrentDatabaseVersion,"getCurrentDatabaseVersion");export async function openDatabaseForSchemaRead(r){return new Promise(t=>{const n=indexedDB.open(r);n.onsuccess=()=>{t(n.result)},n.onerror=()=>{t(null)},n.onupgradeneeded=()=>{n.transaction?.abort(),t(null)}})}l(openDatabaseForSchemaRead,"openDatabaseForSchemaRead");export async function determineAutoVersion(r,t,n={}){const{removedStoreStrategy:a="error"}=n,o=await openDatabaseForSchemaRead(r);if(!o)return{version:1,changes:null,needsUpgrade:!0};const u=o.version,s=readExistingSchema(o),c=toDesiredSchema(t);o.close();const e=detectSchemaChanges(s,c);if(!e.hasChanges)return{version:u,changes:null,needsUpgrade:!1};const i=[];for(const m of e.dangerous)m.type==="store_delete"&&a==="preserve"?e.safe.push({type:"store_rename",oldName:m.storeName,newName:`__${m.storeName}_deleted_v${u}__`}):i.push(m);if(e.dangerous=i,e.dangerous.length>0){const h=`Dangerous schema changes detected:
|
|
2
|
+
${e.dangerous.map(d=>{switch(d.type){case"store_delete":return`Store "${d.storeName}" would be deleted. Use removedStoreStrategy: 'preserve' to backup, or add a migration to explicitly delete it.`;case"keypath_change":return`Store "${d.storeName}" keyPath changed from "${d.oldKeyPath}" to "${d.newKeyPath}". This requires recreating the store with a manual migration.`;default:return"Unknown dangerous change"}}).join(`
|
|
3
3
|
`)}
|
|
4
4
|
|
|
5
|
-
Add explicit migrations to handle these changes safely
|
|
5
|
+
Add explicit migrations to handle these changes safely.`;throw console.error("[schema-idb]",h),new Error(h)}return{version:u+1,changes:e,needsUpgrade:!0}}l(determineAutoVersion,"determineAutoVersion");
|
package/dist/storeAccessor.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var S=Object.defineProperty;var u=(r,t)=>S(r,"name",{value:t,configurable:!0});import{promisifyTransaction as y}from"./utils.js";import{createQueryFunction as p}from"./query.js";async function a(r,t){return await y(r),t.result}u(a,"getResult");function x(r,t){if(r!==void 0)return{...t,...r}}u(x,"applyDefaults");function j(r,t){return Object.keys(t).length===0?r:r.map(s=>({...t,...s}))}u(j,"applyDefaultsToArray");export function createStoreAccessor(r,t,s={}){const l=Object.keys(s).length>0,g=p(r,t,s);return{async get(c){const n=r.transaction(t,"readonly"),o=n.objectStore(t),e=await a(n,o.get(c));return l?x(e,s):e},async getAll(c){const n=r.transaction(t,"readonly"),o=n.objectStore(t),e=await a(n,o.getAll(c?.query,c?.count));return l?j(e,s):e},async getBy(c,n){const o=r.transaction(t,"readonly"),w=o.objectStore(t).index(c),i=await a(o,w.get(n));return l?x(i,s):i},async getAllBy(c,n){const o=r.transaction(t,"readonly"),w=o.objectStore(t).index(c),i=await a(o,w.getAll(n));return l?j(i,s):i},async put(c,n){const o=r.transaction(t,"readwrite"),e=o.objectStore(t);return a(o,e.put(c,n))},async add(c,n){const o=r.transaction(t,"readwrite"),e=o.objectStore(t);return a(o,e.add(c,n))},async delete(c){const n=r.transaction(t,"readwrite");n.objectStore(t).delete(c),await y(n)},async clear(){const c=r.transaction(t,"readwrite");c.objectStore(t).clear(),await y(c)},async count(c){const n=r.transaction(t,"readonly"),e=n.objectStore(t).count(c);return await y(n),e.result},async raw(c){const n=r.transaction(t,"readwrite"),o=n.objectStore(t),e=c(o);return await y(n),e.result},query:g}}u(createStoreAccessor,"createStoreAccessor");
|
package/dist/types.d.ts
CHANGED
|
@@ -83,7 +83,8 @@ export interface WriteChain<TStores extends readonly StoreDefinition[]> {
|
|
|
83
83
|
export interface StoreAccessor<T, K> {
|
|
84
84
|
get(key: K): Promise<T | undefined>;
|
|
85
85
|
getAll(options?: GetAllOptions): Promise<T[]>;
|
|
86
|
-
|
|
86
|
+
getBy(indexName: string, query: IDBKeyRange | IDBValidKey): Promise<T | undefined>;
|
|
87
|
+
getAllBy(indexName: string, query?: IDBKeyRange | IDBValidKey): Promise<T[]>;
|
|
87
88
|
put(value: T, key?: K): Promise<K>;
|
|
88
89
|
add(value: T, key?: K): Promise<K>;
|
|
89
90
|
delete(key: K | IDBKeyRange): Promise<void>;
|
package/package.json
CHANGED
package/dist/createDB.d.ts
DELETED
package/dist/createDB.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";var w=Object.defineProperty;var f=(n,t)=>w(n,"name",{value:t,configurable:!0});import{openDatabase as y,promisifyTransaction as b}from"./utils.js";import{createReadChain as S}from"./readChain.js";import{createWriteChain as v}from"./writeChain.js";import{createStoreAccessor as x}from"./storeAccessor.js";function D(n){const t=[],s=new Set;for(const r of n)for(const e of r.migrations){if(s.has(e.name))throw new Error(`Duplicate migration name "${e.name}" found across stores`);s.add(e.name),t.push(e)}return t.sort((r,e)=>r.name.localeCompare(e.name))}f(D,"collectMigrations");function E(n,t,s){const r={...n};for(const e of s)r[e.name]=x(t,e.name);return r}f(E,"buildDatabaseWithStores");function P(n,t,s,r,e){if(s===0)for(const a of r){const i=n.createObjectStore(a.name,{keyPath:a.keyPath,autoIncrement:a.autoIncrement});for(const c of a.indexes)i.createIndex(c.name,c.keyPath,{unique:c.unique??!1,multiEntry:c.multiEntry??!1})}for(const a of e)try{const i=a.up(n,t);i instanceof Promise&&i.catch(c=>{console.error(`Migration "${a.name}" failed:`,c),t.abort()})}catch(i){throw console.error(`Migration "${a.name}" failed:`,i),t.abort(),i}}f(P,"handleUpgrade");export async function openDB(n){const{name:t,version:s,versionStrategy:r="explicit",stores:e,onBlocked:a,onVersionChange:i}=n,c=new Set;for(const o of e){if(c.has(o.name))throw new Error(`Duplicate store name: "${o.name}"`);c.add(o.name)}let u;if(r==="auto")u=1;else{if(s===void 0)throw new Error('Version is required when versionStrategy is "explicit"');u=s}const p=D(e),m=await y(t,u,(o,l,d)=>{P(o,l,d,e,p)},a);return i&&(m.onversionchange=i),E({get name(){return m.name},get version(){return m.version},get raw(){return m},close(){m.close()},read(o){return S(m,o)},write(o){return v(m,o)},async transaction(o,l,d){const h=m.transaction(o,l),g=d(h);g instanceof Promise&&await g,await b(h)}},m,e)}f(openDB,"openDB");
|
package/dist/defineStore.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { StoreDefinition, StoreKeyPath, ExtractKeyType, StoreOptionsWithKeyPath, StoreOptionsWithoutKeyPath } from './types.js';
|
|
2
|
-
export declare function defineStore<T>(): {
|
|
3
|
-
<const TName extends string, const KP extends StoreKeyPath<T>>(name: TName, options: StoreOptionsWithKeyPath<T, KP>): StoreDefinition<T, ExtractKeyType<T, KP>, TName>;
|
|
4
|
-
<const TName extends string, K extends IDBValidKey = IDBValidKey>(name: TName, options?: StoreOptionsWithoutKeyPath<T, K>): StoreDefinition<T, K, TName>;
|
|
5
|
-
};
|
package/dist/defineStore.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";var p=Object.defineProperty;var i=(t,e)=>p(t,"name",{value:e,configurable:!0});function g(t){if(!t)return[];const e=[];for(const[r,o]of Object.entries(t))o!==!1&&(o===!0?e.push({name:r,keyPath:r}):e.push({name:r,keyPath:r,unique:o.unique,multiEntry:o.multiEntry}));return e}i(g,"parseIndexConfig");export function defineStore(){function t(e,r={}){const{keyPath:o,autoIncrement:u=!1,indexes:m,migrations:a=[]}=r,f=g(m);if(!e||typeof e!="string")throw new Error("Store name is required and must be a string");const s=new Set;for(const n of a){if(!n.name||typeof n.name!="string")throw new Error(`Invalid migration name in store "${e}": must be a non-empty string`);if(s.has(n.name))throw new Error(`Duplicate migration name "${n.name}" in store "${e}"`);s.add(n.name)}return{name:e,keyPath:o,autoIncrement:u,indexes:f,migrations:[...a].sort((n,c)=>n.name.localeCompare(c.name)),_schema:{},_keyType:{}}}return i(t,"createStore"),t}i(defineStore,"defineStore");
|
package/dist/readChain.d.ts
DELETED
package/dist/readChain.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";var l=Object.defineProperty;var i=(s,u)=>l(s,"name",{value:u,configurable:!0});import{promisifyTransaction as p}from"./utils.js";function g(s){return s}i(g,"toReadChain");export function createReadChain(s,u){const r=[],c={get(n,t){return r.push({type:"get",storeName:n,key:t}),c},getAll(n,t){return r.push({type:"getAll",storeName:n,query:t?.query,count:t?.count}),c},getAllByIndex(n,t,o){return r.push({type:"getAllByIndex",storeName:n,indexName:t,query:o}),c},count(n,t){return r.push({type:"count",storeName:n,query:t}),c},async execute(){if(r.length===0)return[];const n=[...new Set(r.map(e=>e.storeName))];for(const e of n)if(!u.includes(e))throw new Error(`Store "${e}" is not in the transaction scope. Available stores: ${u.join(", ")}`);const t=s.transaction(u,"readonly"),o=[];for(const e of r){const a=t.objectStore(e.storeName);switch(e.type){case"get":o.push(a.get(e.key));break;case"getAll":o.push(a.getAll(e.query,e.count));break;case"getAllByIndex":o.push(a.index(e.indexName).getAll(e.query));break;case"count":o.push(a.count(e.query));break}}return await p(t),o.map(e=>e.result)}};return c}i(createReadChain,"createReadChain");
|
package/dist/writeChain.d.ts
DELETED
package/dist/writeChain.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";var u=Object.defineProperty;var i=(n,o)=>u(n,"name",{value:o,configurable:!0});import{promisifyTransaction as p}from"./utils.js";function h(n){return n}i(h,"toWriteChain");export function createWriteChain(n,o){const a=[],s={put(t,r,e){return a.push({type:"put",storeName:t,value:r,key:e}),s},add(t,r,e){return a.push({type:"add",storeName:t,value:r,key:e}),s},delete(t,r){return a.push({type:"delete",storeName:t,key:r}),s},clear(t){return a.push({type:"clear",storeName:t}),s},async execute(){if(a.length===0)return;const t=[...new Set(a.map(e=>e.storeName))];for(const e of t)if(!o.includes(e))throw new Error(`Store "${e}" is not in the transaction scope. Available stores: ${o.join(", ")}`);const r=n.transaction(o,"readwrite");for(const e of a){const c=r.objectStore(e.storeName);switch(e.type){case"put":c.put(e.value,e.key);break;case"add":c.add(e.value,e.key);break;case"delete":c.delete(e.key);break;case"clear":c.clear();break}}await p(r)}};return s}i(createWriteChain,"createWriteChain");
|