web-idb-client 1.0.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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +230 -0
  3. package/dist/client/index.d.ts +7 -0
  4. package/dist/client/index.js +12 -0
  5. package/dist/client/index.js.map +1 -0
  6. package/dist/client/indexed-db-client.d.ts +40 -0
  7. package/dist/client/indexed-db-client.js +211 -0
  8. package/dist/client/indexed-db-client.js.map +1 -0
  9. package/dist/client/mutations.d.ts +12 -0
  10. package/dist/client/mutations.js +3 -0
  11. package/dist/client/mutations.js.map +1 -0
  12. package/dist/client/store.d.ts +42 -0
  13. package/dist/client/store.js +356 -0
  14. package/dist/client/store.js.map +1 -0
  15. package/dist/client/transaction.d.ts +14 -0
  16. package/dist/client/transaction.js +37 -0
  17. package/dist/client/transaction.js.map +1 -0
  18. package/dist/errors/index.d.ts +15 -0
  19. package/dist/errors/index.js +35 -0
  20. package/dist/errors/index.js.map +1 -0
  21. package/dist/index.d.ts +10 -0
  22. package/dist/index.js +33 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/liveQuery/index.d.ts +31 -0
  25. package/dist/liveQuery/index.js +216 -0
  26. package/dist/liveQuery/index.js.map +1 -0
  27. package/dist/migrate/diff.d.ts +28 -0
  28. package/dist/migrate/diff.js +89 -0
  29. package/dist/migrate/diff.js.map +1 -0
  30. package/dist/migrate/helpers.d.ts +29 -0
  31. package/dist/migrate/helpers.js +89 -0
  32. package/dist/migrate/helpers.js.map +1 -0
  33. package/dist/migrate/index.d.ts +6 -0
  34. package/dist/migrate/index.js +14 -0
  35. package/dist/migrate/index.js.map +1 -0
  36. package/dist/migrate/upgrade.d.ts +19 -0
  37. package/dist/migrate/upgrade.js +123 -0
  38. package/dist/migrate/upgrade.js.map +1 -0
  39. package/dist/query/order.d.ts +8 -0
  40. package/dist/query/order.js +30 -0
  41. package/dist/query/order.js.map +1 -0
  42. package/dist/query/where.d.ts +17 -0
  43. package/dist/query/where.js +106 -0
  44. package/dist/query/where.js.map +1 -0
  45. package/dist/relation/define-relation.d.ts +49 -0
  46. package/dist/relation/define-relation.js +101 -0
  47. package/dist/relation/define-relation.js.map +1 -0
  48. package/dist/relation/index.d.ts +3 -0
  49. package/dist/relation/index.js +14 -0
  50. package/dist/relation/index.js.map +1 -0
  51. package/dist/relation/load-related.d.ts +21 -0
  52. package/dist/relation/load-related.js +54 -0
  53. package/dist/relation/load-related.js.map +1 -0
  54. package/dist/schema/define-db.d.ts +5 -0
  55. package/dist/schema/define-db.js +41 -0
  56. package/dist/schema/define-db.js.map +1 -0
  57. package/dist/schema/define-store.d.ts +17 -0
  58. package/dist/schema/define-store.js +95 -0
  59. package/dist/schema/define-store.js.map +1 -0
  60. package/dist/schema/fields.d.ts +75 -0
  61. package/dist/schema/fields.js +90 -0
  62. package/dist/schema/fields.js.map +1 -0
  63. package/dist/schema/index.d.ts +5 -0
  64. package/dist/schema/index.js +22 -0
  65. package/dist/schema/index.js.map +1 -0
  66. package/dist/schema/infer.d.ts +131 -0
  67. package/dist/schema/infer.js +3 -0
  68. package/dist/schema/infer.js.map +1 -0
  69. package/dist/schema/infer.test-types.d.ts +11 -0
  70. package/dist/schema/infer.test-types.js +80 -0
  71. package/dist/schema/infer.test-types.js.map +1 -0
  72. package/dist/schema/types.d.ts +55 -0
  73. package/dist/schema/types.js +3 -0
  74. package/dist/schema/types.js.map +1 -0
  75. package/dist/utils/assert-keys.d.ts +3 -0
  76. package/dist/utils/assert-keys.js +15 -0
  77. package/dist/utils/assert-keys.js.map +1 -0
  78. package/dist/utils/boolean-codec.d.ts +23 -0
  79. package/dist/utils/boolean-codec.js +149 -0
  80. package/dist/utils/boolean-codec.js.map +1 -0
  81. package/dist/utils/index.d.ts +5 -0
  82. package/dist/utils/index.js +16 -0
  83. package/dist/utils/index.js.map +1 -0
  84. package/dist/utils/key-range.d.ts +14 -0
  85. package/dist/utils/key-range.js +157 -0
  86. package/dist/utils/key-range.js.map +1 -0
  87. package/dist/utils/promisify.d.ts +2 -0
  88. package/dist/utils/promisify.js +29 -0
  89. package/dist/utils/promisify.js.map +1 -0
  90. package/dist/utils/type-utils.d.ts +2 -0
  91. package/dist/utils/type-utils.js +3 -0
  92. package/dist/utils/type-utils.js.map +1 -0
  93. package/package.json +71 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Sergey Azarov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # IndexedDbClient
2
+
3
+ Typed IndexedDB client for the **browser** (`web-idb-client`) with a Prisma-like store API, schema-in-TypeScript factories, and explicit migrations.
4
+
5
+ > 🇷🇺 [Читать на русском языке](README_RU.md)
6
+
7
+ ## Features
8
+
9
+ - Schema via `defineStore` / `field.*` — `InferSelect` / `InferInsert` without codegen
10
+ - Store API: `insert`, `find`, `findMany`, `findUnique`, `update`, `delete`, `count`, `clear`
11
+ - Index-only `where` (equality + ranges) and `orderBy` / `take` / `skip`
12
+ - Multi-store `transaction('r' | 'rw', stores, fn)`
13
+ - Versioned `open` with schema diff + optional `upgrade` hooks
14
+ - Opt-in [`liveQuery`](docs/liveQuery.md) plugin for same/cross-tab UI invalidation
15
+
16
+ ## When to use / When not to use
17
+
18
+ **Use this library** when you need a typed, Prisma-shaped API over real IndexedDB in the **browser** — typically for **private or corporate web** apps: internal SPAs, account cabinets, form drafts, client-side caches, multi-step wizards with local state. Such cases exist, but they are **relatively uncommon**; pick this library deliberately, not as a default “better localStorage”.
19
+
20
+ **Do not use this library** when you need:
21
+
22
+ - **Mobile / React Native** as the primary platform — use mature options there (e.g. WatermelonDB, RxDB)
23
+ - **Offline-first sync / replication** across devices — that is a different product category (e.g. RxDB)
24
+ - A battle-tested IndexedDB ecosystem with plugins, liveQuery, and years of production hardening — prefer **Dexie**
25
+ - A minimal get/put/cursor wrapper with no ORM surface — prefer **idb**
26
+ - SQL in the browser (WASM SQLite/Postgres) — prefer Drizzle/Kysely-style stacks
27
+
28
+ **Platform:** browser IndexedDB only. Not aimed at Node (unless you polyfill IndexedDB) and **not** planned as a mobile SDK.
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ npm install web-idb-client
34
+ ```
35
+
36
+ ## Quick start
37
+
38
+ ```typescript
39
+ import {
40
+ createClient,
41
+ defineStore,
42
+ field,
43
+ type InferSelect,
44
+ } from 'web-idb-client';
45
+
46
+ const tasks = defineStore('tasks', {
47
+ id: field.number().primaryKey({ autoIncrement: true }),
48
+ name: field.string().index('byName'),
49
+ isDone: field.boolean().index('byDone'),
50
+ createdAt: field.date().index('byCreatedAt'),
51
+ });
52
+
53
+ type Task = InferSelect<typeof tasks>;
54
+
55
+ const db = createClient({
56
+ name: 'myApp',
57
+ version: 1,
58
+ stores: { tasks },
59
+ });
60
+
61
+ await db.open();
62
+
63
+ const id = await db.tasks.insert({
64
+ name: 'Learn IndexedDB',
65
+ isDone: false,
66
+ createdAt: new Date(),
67
+ });
68
+
69
+ const task: Task | null = await db.tasks.find({ where: { id } });
70
+
71
+ const byName = await db.tasks.findMany({
72
+ where: { name: 'Learn IndexedDB' },
73
+ });
74
+
75
+ const newest = await db.tasks.findMany({
76
+ orderBy: { createdAt: 'desc' },
77
+ take: 10,
78
+ });
79
+
80
+ await db.tasks.update({
81
+ where: { id },
82
+ data: { isDone: true },
83
+ });
84
+
85
+ await db.transaction('rw', ['tasks'], async (tx) => {
86
+ await tx.tasks.insert({
87
+ name: 'In a transaction',
88
+ isDone: false,
89
+ createdAt: new Date(),
90
+ });
91
+ });
92
+ ```
93
+
94
+ ## Schema
95
+
96
+ ```typescript
97
+ import { defineStore, compoundIndex, field } from 'web-idb-client';
98
+
99
+ const tasks = defineStore(
100
+ 'tasks',
101
+ {
102
+ id: field.number().primaryKey({ autoIncrement: true }),
103
+ title: field.string(),
104
+ status: field.string().index('byStatus'),
105
+ tags: field.array(field.string()).multiEntry('byTags'),
106
+ userId: field.string(),
107
+ dueAt: field.date().optional(),
108
+ },
109
+ () => [compoundIndex('byUserDue', ['userId', 'dueAt'])],
110
+ );
111
+ ```
112
+
113
+ Field helpers: `field.string`, `field.number`, `field.boolean`, `field.date`, `field.json`, `field.array`.
114
+ Chain: `.optional()`, `.primaryKey({ autoIncrement? })`, `.ref(store)`, `.index(name)`, `.uniqueIndex(name)`, `.multiEntry(name)`.
115
+
116
+ Only PK and fields with `.index()` / `.uniqueIndex()` / `.multiEntry()` / `compoundIndex()` become IndexedDB indexes. Other fields are typed for insert/select only.
117
+
118
+ FK fields: prefer `.ref(users).index('byUserId')` (or `.ref('users')`) then `loadMany` / `loadOne`; relation endpoints via `end(users, 'id')` — see [docs/relations.md](docs/relations.md). There is **no** Prisma-style `include`.
119
+
120
+ ### Boolean fields and indexes
121
+
122
+ IndexedDB keys **cannot** be `true` / `false`. For every `field.boolean()` (indexed or not) the client:
123
+
124
+ - accepts and returns **`boolean`** in the TypeScript API (`insert`, `where`, `findMany`, …);
125
+ - stores **`0 | 1`** in the object store so indexes like `.index('byDone')` work.
126
+
127
+ **In DevTools / Application → IndexedDB you will see numbers (`0` / `1`), not booleans.** The app still gets `boolean`. Legacy rows that already contain `true`/`false` are decoded on read; rewrite them to `0|1` on upgrade if you add a boolean index later.
128
+
129
+ ## Store methods
130
+
131
+ | Method | Returns | Notes |
132
+ | --- | --- | --- |
133
+ | `insert(data)` | `IDBValidKey` | `add`; autoIncrement PK optional on insert |
134
+ | `insertMany(data[])` | `IDBValidKey[]` | Sequential adds on one store tx |
135
+ | `find({ where, orderBy? })` | `T \| null` | First match (Prisma `findFirst` style) |
136
+ | `findMany({ where?, orderBy?, take?, skip?, sortInMemory? })` | `T[]` | List |
137
+ | `findUnique({ where })` | `T \| null` | PK or unique index only |
138
+ | `update({ where, data })` / `updateMany` | `number` | Rows modified |
139
+ | `delete({ where })` / `deleteMany` | `number` | Rows deleted |
140
+ | `count({ where? })` | `number` | |
141
+ | `clear()` | `void` | Clears the store |
142
+
143
+ `where` must target the primary key or a declared index (field name or index name).
144
+
145
+ Range operators (map to `IDBKeyRange`): `equals`, `gt` / `gte` / `lt` / `lte`, aliases `above` / `aboveOrEqual` / `below` / `belowOrEqual`, `startsWith` (string prefix), `between: [lo, hi]` (optional open flags).
146
+
147
+ Compound indexes:
148
+
149
+ ```typescript
150
+ // exact key
151
+ where: { byUserDue: [userId, dueAt] }
152
+ // exact prefix + range on the last part only
153
+ where: { byUserDue: [userId, { gte: dueAt }] }
154
+ // or object form matching the compound keyPath fields
155
+ where: { userId, dueAt: { between: [a, b] } }
156
+ ```
157
+
158
+ If `where` and `orderBy` use different indexes, pass `sortInMemory: true` (fetches the `where` set, then sorts in JS — can be expensive). Otherwise the client throws `QUERY_INVALID`.
159
+
160
+ Bulk helpers: `insertMany`, `updateMany`, `deleteMany` (the last two are aliases of multi-row `update` / `delete`).
161
+
162
+ ## Client methods
163
+
164
+ | Method | Notes |
165
+ | --- | --- |
166
+ | `open()` / `init()` | Opens DB; creates/upgrades stores & indexes |
167
+ | `close()` | Closes connection |
168
+ | `deleteDatabase()` | Deletes the whole DB |
169
+ | `transaction(mode, storeNames, fn)` | `mode`: `'r'` \| `'rw'`; `fn` receives typed stores on one `IDBTransaction` |
170
+
171
+ Also available: `new IndexedDbClient({ name, version, stores })` (same config as `createClient`).
172
+
173
+ Upgrade hooks (keyed by target version):
174
+
175
+ ```typescript
176
+ const db = createClient({
177
+ name: 'myApp',
178
+ version: 2,
179
+ stores: { tasks },
180
+ upgrade: {
181
+ 2: (tx) => {
182
+ // data transforms during versionchange on `tx`
183
+ },
184
+ },
185
+ });
186
+ ```
187
+
188
+ Changing a store `keyPath` in place is rejected with `SCHEMA_MISMATCH` unless you recreate the store in an upgrade hook via `copyStoreAndDrop` — see [docs/upgrades.md](docs/upgrades.md).
189
+
190
+ ## Errors
191
+
192
+ Thrown as `IndexedDbError` with `code`: `NOT_OPEN`, `SCHEMA_INVALID`, `SCHEMA_MISMATCH`, `QUERY_INVALID`, `TRANSACTION_FAILED`, `OPEN_FAILED`, `BLOCKED`.
193
+ Optional context fields: `store`, `index`, `field` (also appended to `message`).
194
+
195
+ ```typescript
196
+ const db = createClient({
197
+ name: 'myApp',
198
+ version: 1,
199
+ stores: { tasks },
200
+ // Reject unknown keys on insert (helpful in corp TS codebases)
201
+ strictInsert: true,
202
+ });
203
+ ```
204
+
205
+ ## Breaking changes from `@azarov-serge/indexed-db-client`
206
+
207
+ - Install / import **`web-idb-client`** (this package)
208
+ - Removed `from().select` / string-based config (`dbName`, `storageNames`, `storeNameToIndexes`)
209
+ - Use `defineStore` + `createClient` / `new IndexedDbClient({ name, version, stores })`
210
+ - Prefer `find` / `findMany` instead of `select`
211
+
212
+ ## Docs
213
+
214
+ - [Upgrades / migrations](docs/upgrades.md) · [RU](docs/upgrades.ru.md)
215
+ - [Relations (FK helpers)](docs/relations.md) · [RU](docs/relations.ru.md)
216
+ - [liveQuery (opt-in)](docs/liveQuery.md) · [RU](docs/liveQuery.ru.md)
217
+ - Internal notes: [API design (v2)](.docs/indexeddb-client-v2-api-design.md) · [ORM research](.docs/indexeddb-orm-research.md) · [PLAN](.docs/PLAN.md)
218
+
219
+ ## Examples
220
+
221
+ - **UI demo:** [examples/todo](examples/todo) — Vite + Tailwind CSS
222
+ Local: `npm run demo:dev` → open the URL Vite prints (base path `/indexed-db-client/`)
223
+ Overview: [examples/README.md](examples/README.md)
224
+ - **Relations demo:** [examples/relations](examples/relations) — users + tasks (`npm run relations:dev`)
225
+ - **Upgrade recipes:** [docs/upgrades.md](docs/upgrades.md)
226
+ - **Relations guide:** [docs/relations.md](docs/relations.md)
227
+
228
+ ## License
229
+
230
+ MIT
@@ -0,0 +1,7 @@
1
+ export { IndexedDbClient, createClient } from './indexed-db-client';
2
+ export type { IndexedDbClientWithStores } from './indexed-db-client';
3
+ export { Store } from './store';
4
+ export type { StoreContextInterface } from './store';
5
+ export { Transaction, toIndexedDbTransactionMode } from './transaction';
6
+ export type { TransactionMode, TxStores } from './transaction';
7
+ export type { MutationBatch, MutationListener, MutationOp, StoreMutationEvent, } from './mutations';
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toIndexedDbTransactionMode = exports.Transaction = exports.Store = exports.createClient = exports.IndexedDbClient = void 0;
4
+ var indexed_db_client_1 = require("./indexed-db-client");
5
+ Object.defineProperty(exports, "IndexedDbClient", { enumerable: true, get: function () { return indexed_db_client_1.IndexedDbClient; } });
6
+ Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return indexed_db_client_1.createClient; } });
7
+ var store_1 = require("./store");
8
+ Object.defineProperty(exports, "Store", { enumerable: true, get: function () { return store_1.Store; } });
9
+ var transaction_1 = require("./transaction");
10
+ Object.defineProperty(exports, "Transaction", { enumerable: true, get: function () { return transaction_1.Transaction; } });
11
+ Object.defineProperty(exports, "toIndexedDbTransactionMode", { enumerable: true, get: function () { return transaction_1.toIndexedDbTransactionMode; } });
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":";;;AAAA,yDAAoE;AAA3D,oHAAA,eAAe,OAAA;AAAE,iHAAA,YAAY,OAAA;AAEtC,iCAAgC;AAAvB,8FAAA,KAAK,OAAA;AAEd,6CAAwE;AAA/D,0GAAA,WAAW,OAAA;AAAE,yHAAA,0BAA0B,OAAA"}
@@ -0,0 +1,40 @@
1
+ import type { DbConfigInterface, DbStoresInterface } from '../schema/types';
2
+ import { Store } from './store';
3
+ import { type TransactionMode, type TxStores } from './transaction';
4
+ import type { MutationListener } from './mutations';
5
+ type ClientStores<TStores extends DbStoresInterface> = {
6
+ [K in keyof TStores]: Store<TStores[K]>;
7
+ };
8
+ export declare class IndexedDbClient<TStores extends DbStoresInterface = DbStoresInterface> {
9
+ private readonly config;
10
+ private db;
11
+ private readonly _stores;
12
+ private readonly mutationListeners;
13
+ constructor(config: DbConfigInterface<TStores>);
14
+ /** Typed store accessors (also available as `db.<storeName>`). */
15
+ get stores(): ClientStores<TStores>;
16
+ get isOpen(): boolean;
17
+ get name(): string;
18
+ get version(): number;
19
+ /**
20
+ * Subscribe to store mutations (same-tab). Used by the liveQuery plugin;
21
+ * also useful for custom UI invalidation.
22
+ */
23
+ subscribeMutations(listener: MutationListener): () => void;
24
+ /** @internal Notify mutation listeners (and liveQuery / cross-tab bridges). */
25
+ emitMutations(stores: string[], remote?: boolean): void;
26
+ open(): Promise<this>;
27
+ /** Alias for open() for familiarity with v1 `init`. */
28
+ init(): Promise<this>;
29
+ close(): void;
30
+ deleteDatabase(): Promise<void>;
31
+ transaction<T>(mode: TransactionMode, storeNames: (keyof TStores & string)[], fn: (tx: TxStores<TStores>) => Promise<T> | T): Promise<T>;
32
+ private openDatabase;
33
+ private deleteDatabaseRequest;
34
+ private requireDb;
35
+ private requireStore;
36
+ }
37
+ export type IndexedDbClientWithStores<TStores extends DbStoresInterface> = IndexedDbClient<TStores> & ClientStores<TStores>;
38
+ /** Construct a client with store properties typed on the instance. */
39
+ export declare function createClient<TStores extends DbStoresInterface>(config: DbConfigInterface<TStores>): IndexedDbClientWithStores<TStores>;
40
+ export {};
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.createClient = exports.IndexedDbClient = void 0;
13
+ const define_db_1 = require("../schema/define-db");
14
+ const upgrade_1 = require("../migrate/upgrade");
15
+ const store_1 = require("./store");
16
+ const transaction_1 = require("./transaction");
17
+ const errors_1 = require("../errors");
18
+ class IndexedDbClient {
19
+ constructor(config) {
20
+ this.db = null;
21
+ this.mutationListeners = new Set();
22
+ this.config = config;
23
+ const stores = {};
24
+ for (const key of Object.keys(config.stores)) {
25
+ const def = config.stores[key];
26
+ stores[key] = new store_1.Store(def, {
27
+ getObjectStore: (mode) => this.requireStore(def.name, mode),
28
+ strictInsert: Boolean(config.strictInsert),
29
+ onMutate: (event) => this.emitMutations([event.store]),
30
+ });
31
+ }
32
+ this._stores = stores;
33
+ // Expose stores as enumerable properties: db.tasks
34
+ for (const key of Object.keys(stores)) {
35
+ Object.defineProperty(this, key, {
36
+ get: () => this._stores[key],
37
+ enumerable: true,
38
+ });
39
+ }
40
+ }
41
+ /** Typed store accessors (also available as `db.<storeName>`). */
42
+ get stores() {
43
+ return this._stores;
44
+ }
45
+ get isOpen() {
46
+ return this.db !== null;
47
+ }
48
+ get name() {
49
+ return this.config.name;
50
+ }
51
+ get version() {
52
+ return this.config.version;
53
+ }
54
+ /**
55
+ * Subscribe to store mutations (same-tab). Used by the liveQuery plugin;
56
+ * also useful for custom UI invalidation.
57
+ */
58
+ subscribeMutations(listener) {
59
+ this.mutationListeners.add(listener);
60
+ return () => {
61
+ this.mutationListeners.delete(listener);
62
+ };
63
+ }
64
+ /** @internal Notify mutation listeners (and liveQuery / cross-tab bridges). */
65
+ emitMutations(stores, remote = false) {
66
+ const unique = [...new Set(stores.filter(Boolean))];
67
+ if (unique.length === 0 || this.mutationListeners.size === 0) {
68
+ return;
69
+ }
70
+ const batch = { stores: unique, remote };
71
+ for (const listener of this.mutationListeners) {
72
+ listener(batch);
73
+ }
74
+ }
75
+ open() {
76
+ return __awaiter(this, void 0, void 0, function* () {
77
+ if (this.db) {
78
+ return this;
79
+ }
80
+ const schema = (0, define_db_1.toDeclaredDbSchema)(this.config);
81
+ this.db = yield this.openDatabase(schema);
82
+ return this;
83
+ });
84
+ }
85
+ /** Alias for open() for familiarity with v1 `init`. */
86
+ init() {
87
+ return __awaiter(this, void 0, void 0, function* () {
88
+ return this.open();
89
+ });
90
+ }
91
+ close() {
92
+ if (this.db) {
93
+ this.db.close();
94
+ this.db = null;
95
+ }
96
+ }
97
+ deleteDatabase() {
98
+ return __awaiter(this, void 0, void 0, function* () {
99
+ this.close();
100
+ yield this.deleteDatabaseRequest(this.config.name);
101
+ });
102
+ }
103
+ transaction(mode, storeNames, fn) {
104
+ return __awaiter(this, void 0, void 0, function* () {
105
+ const db = this.requireDb();
106
+ if (storeNames.length === 0) {
107
+ throw new errors_1.IndexedDbError('QUERY_INVALID', 'transaction() requires at least one store');
108
+ }
109
+ const idbTx = db.transaction(storeNames, (0, transaction_1.toIndexedDbTransactionMode)(mode));
110
+ const pending = new Set();
111
+ const tx = new transaction_1.Transaction(idbTx, this.config.stores, storeNames, Boolean(this.config.strictInsert), (event) => {
112
+ pending.add(event.store);
113
+ });
114
+ try {
115
+ const result = yield fn(tx.stores);
116
+ yield tx.done();
117
+ if (pending.size > 0) {
118
+ this.emitMutations([...pending]);
119
+ }
120
+ return result;
121
+ }
122
+ catch (error) {
123
+ try {
124
+ idbTx.abort();
125
+ }
126
+ catch (_a) {
127
+ // ignore
128
+ }
129
+ throw error;
130
+ }
131
+ });
132
+ }
133
+ openDatabase(schema) {
134
+ return new Promise((resolve, reject) => {
135
+ const request = indexedDB.open(schema.name, schema.version);
136
+ request.onblocked = () => {
137
+ reject(new errors_1.IndexedDbError('BLOCKED', `Opening database "${schema.name}" is blocked by another connection`));
138
+ };
139
+ request.onerror = () => {
140
+ var _a, _b;
141
+ reject(new errors_1.IndexedDbError('OPEN_FAILED', (_b = (_a = request.error) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : `Failed to open database "${schema.name}"`));
142
+ };
143
+ request.onupgradeneeded = (event) => {
144
+ var _a;
145
+ const db = request.result;
146
+ const tx = request.transaction;
147
+ if (!tx) {
148
+ reject(new errors_1.IndexedDbError('OPEN_FAILED', 'Missing versionchange transaction during upgrade'));
149
+ return;
150
+ }
151
+ try {
152
+ (0, upgrade_1.applySchemaUpgrade)({
153
+ db,
154
+ tx,
155
+ schema,
156
+ oldVersion: event.oldVersion,
157
+ newVersion: (_a = event.newVersion) !== null && _a !== void 0 ? _a : schema.version,
158
+ upgradeHooks: this.config.upgrade,
159
+ });
160
+ }
161
+ catch (error) {
162
+ reject(error);
163
+ }
164
+ };
165
+ request.onsuccess = () => {
166
+ const db = request.result;
167
+ try {
168
+ (0, upgrade_1.assertKeyPathsMatchSchema)(db, schema);
169
+ }
170
+ catch (error) {
171
+ db.close();
172
+ reject(error);
173
+ return;
174
+ }
175
+ db.onversionchange = () => {
176
+ db.close();
177
+ };
178
+ resolve(db);
179
+ };
180
+ });
181
+ }
182
+ deleteDatabaseRequest(name) {
183
+ return new Promise((resolve, reject) => {
184
+ const request = indexedDB.deleteDatabase(name);
185
+ request.onsuccess = () => resolve();
186
+ request.onerror = () => {
187
+ var _a, _b;
188
+ return reject(new errors_1.IndexedDbError('OPEN_FAILED', (_b = (_a = request.error) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : `Failed to delete database "${name}"`));
189
+ };
190
+ request.onblocked = () => reject(new errors_1.IndexedDbError('BLOCKED', `Deleting database "${name}" is blocked by another connection`));
191
+ });
192
+ }
193
+ requireDb() {
194
+ if (!this.db) {
195
+ throw new errors_1.IndexedDbError('NOT_OPEN', 'Database is not open. Call open() first.');
196
+ }
197
+ return this.db;
198
+ }
199
+ requireStore(name, mode) {
200
+ const db = this.requireDb();
201
+ const tx = db.transaction(name, mode);
202
+ return tx.objectStore(name);
203
+ }
204
+ }
205
+ exports.IndexedDbClient = IndexedDbClient;
206
+ /** Construct a client with store properties typed on the instance. */
207
+ function createClient(config) {
208
+ return new IndexedDbClient(config);
209
+ }
210
+ exports.createClient = createClient;
211
+ //# sourceMappingURL=indexed-db-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexed-db-client.js","sourceRoot":"","sources":["../../src/client/indexed-db-client.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,mDAAyD;AACzD,gDAAmF;AACnF,mCAAgC;AAChC,+CAKuB;AAEvB,sCAA2C;AAM3C,MAAa,eAAe;IAM1B,YAAY,MAAkC;QAJtC,OAAE,GAAuB,IAAI,CAAC;QAErB,sBAAiB,GAAG,IAAI,GAAG,EAAoB,CAAC;QAG/D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,MAAM,MAAM,GAAG,EAA2B,CAAC;QAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAA+B,EAAE;YAC1E,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAmB,CAAC;YACjD,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,aAAK,CAAC,GAAG,EAAE;gBAC3B,cAAc,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC;gBAC3D,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC;gBAC1C,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;aACvD,CAAsC,CAAC;SACzC;QACD,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QAEtB,mDAAmD;QACnD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAA+B,EAAE;YACnE,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE;gBAC/B,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;gBAC5B,UAAU,EAAE,IAAI;aACjB,CAAC,CAAC;SACJ;IACH,CAAC;IAED,kEAAkE;IAClE,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC;IAC1B,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;IAC1B,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACH,kBAAkB,CAAC,QAA0B;QAC3C,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACrC,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1C,CAAC,CAAC;IACJ,CAAC;IAED,+EAA+E;IAC/E,aAAa,CAAC,MAAgB,EAAE,MAAM,GAAG,KAAK;QAC5C,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACpD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,EAAE;YAC5D,OAAO;SACR;QACD,MAAM,KAAK,GAAkB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QACxD,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,iBAAiB,EAAE;YAC7C,QAAQ,CAAC,KAAK,CAAC,CAAC;SACjB;IACH,CAAC;IAEK,IAAI;;YACR,IAAI,IAAI,CAAC,EAAE,EAAE;gBACX,OAAO,IAAI,CAAC;aACb;YACD,MAAM,MAAM,GAAG,IAAA,8BAAkB,EAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC/C,IAAI,CAAC,EAAE,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YAC1C,OAAO,IAAI,CAAC;QACd,CAAC;KAAA;IAED,uDAAuD;IACjD,IAAI;;YACR,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;QACrB,CAAC;KAAA;IAED,KAAK;QACH,IAAI,IAAI,CAAC,EAAE,EAAE;YACX,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;SAChB;IACH,CAAC;IAEK,cAAc;;YAClB,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,MAAM,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrD,CAAC;KAAA;IAEK,WAAW,CACf,IAAqB,EACrB,UAAsC,EACtC,EAA6C;;YAE7C,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YAC5B,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE;gBAC3B,MAAM,IAAI,uBAAc,CACtB,eAAe,EACf,2CAA2C,CAC5C,CAAC;aACH;YACD,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,IAAA,wCAA0B,EAAC,IAAI,CAAC,CAAC,CAAC;YAC3E,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;YAClC,MAAM,EAAE,GAAG,IAAI,yBAAW,CACxB,KAAK,EACL,IAAI,CAAC,MAAM,CAAC,MAAM,EAClB,UAAU,EACV,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,EACjC,CAAC,KAAK,EAAE,EAAE;gBACR,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC3B,CAAC,CACF,CAAC;YACF,IAAI;gBACF,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;gBACnC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC;gBAChB,IAAI,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE;oBACpB,IAAI,CAAC,aAAa,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;iBAClC;gBACD,OAAO,MAAM,CAAC;aACf;YAAC,OAAO,KAAK,EAAE;gBACd,IAAI;oBACF,KAAK,CAAC,KAAK,EAAE,CAAC;iBACf;gBAAC,WAAM;oBACN,SAAS;iBACV;gBACD,MAAM,KAAK,CAAC;aACb;QACH,CAAC;KAAA;IAEO,YAAY,CAAC,MAA2B;QAC9C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YAE5D,OAAO,CAAC,SAAS,GAAG,GAAG,EAAE;gBACvB,MAAM,CACJ,IAAI,uBAAc,CAChB,SAAS,EACT,qBAAqB,MAAM,CAAC,IAAI,oCAAoC,CACrE,CACF,CAAC;YACJ,CAAC,CAAC;YAEF,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE;;gBACrB,MAAM,CACJ,IAAI,uBAAc,CAChB,aAAa,EACb,MAAA,MAAA,OAAO,CAAC,KAAK,0CAAE,OAAO,mCAAI,4BAA4B,MAAM,CAAC,IAAI,GAAG,CACrE,CACF,CAAC;YACJ,CAAC,CAAC;YAEF,OAAO,CAAC,eAAe,GAAG,CAAC,KAAK,EAAE,EAAE;;gBAClC,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;gBAC1B,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC;gBAC/B,IAAI,CAAC,EAAE,EAAE;oBACP,MAAM,CACJ,IAAI,uBAAc,CAChB,aAAa,EACb,kDAAkD,CACnD,CACF,CAAC;oBACF,OAAO;iBACR;gBAED,IAAI;oBACF,IAAA,4BAAkB,EAAC;wBACjB,EAAE;wBACF,EAAE;wBACF,MAAM;wBACN,UAAU,EAAE,KAAK,CAAC,UAAU;wBAC5B,UAAU,EAAE,MAAA,KAAK,CAAC,UAAU,mCAAI,MAAM,CAAC,OAAO;wBAC9C,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;qBAClC,CAAC,CAAC;iBACJ;gBAAC,OAAO,KAAK,EAAE;oBACd,MAAM,CAAC,KAAK,CAAC,CAAC;iBACf;YACH,CAAC,CAAC;YAEF,OAAO,CAAC,SAAS,GAAG,GAAG,EAAE;gBACvB,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;gBAC1B,IAAI;oBACF,IAAA,mCAAyB,EAAC,EAAE,EAAE,MAAM,CAAC,CAAC;iBACvC;gBAAC,OAAO,KAAK,EAAE;oBACd,EAAE,CAAC,KAAK,EAAE,CAAC;oBACX,MAAM,CAAC,KAAK,CAAC,CAAC;oBACd,OAAO;iBACR;gBACD,EAAE,CAAC,eAAe,GAAG,GAAG,EAAE;oBACxB,EAAE,CAAC,KAAK,EAAE,CAAC;gBACb,CAAC,CAAC;gBACF,OAAO,CAAC,EAAE,CAAC,CAAC;YACd,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,qBAAqB,CAAC,IAAY;QACxC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,OAAO,GAAG,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;YAC/C,OAAO,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;YACpC,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE;;gBACrB,OAAA,MAAM,CACJ,IAAI,uBAAc,CAChB,aAAa,EACb,MAAA,MAAA,OAAO,CAAC,KAAK,0CAAE,OAAO,mCAAI,8BAA8B,IAAI,GAAG,CAChE,CACF,CAAA;aAAA,CAAC;YACJ,OAAO,CAAC,SAAS,GAAG,GAAG,EAAE,CACvB,MAAM,CACJ,IAAI,uBAAc,CAChB,SAAS,EACT,sBAAsB,IAAI,oCAAoC,CAC/D,CACF,CAAC;QACN,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,SAAS;QACf,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE;YACZ,MAAM,IAAI,uBAAc,CACtB,UAAU,EACV,0CAA0C,CAC3C,CAAC;SACH;QACD,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IAEO,YAAY,CAAC,IAAY,EAAE,IAAwB;QACzD,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACtC,OAAO,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;CACF;AA5OD,0CA4OC;AAKD,sEAAsE;AACtE,SAAgB,YAAY,CAC1B,MAAkC;IAElC,OAAO,IAAI,eAAe,CAAC,MAAM,CAAuC,CAAC;AAC3E,CAAC;AAJD,oCAIC"}
@@ -0,0 +1,12 @@
1
+ export type MutationOp = 'insert' | 'insertMany' | 'update' | 'delete' | 'clear';
2
+ export interface StoreMutationEvent {
3
+ store: string;
4
+ op: MutationOp;
5
+ }
6
+ /** One or more store mutations (coalesced for multi-store transactions). */
7
+ export interface MutationBatch {
8
+ stores: string[];
9
+ /** True when the batch came from another browser tab. */
10
+ remote?: boolean;
11
+ }
12
+ export type MutationListener = (batch: MutationBatch) => void;
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=mutations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutations.js","sourceRoot":"","sources":["../../src/client/mutations.ts"],"names":[],"mappings":""}
@@ -0,0 +1,42 @@
1
+ import type { StoreInterface } from '../schema/types';
2
+ import type { CountParamsInterface, DeleteParamsInterface, FindManyParamsInterface, FindParamsInterface, InferInsert, InferSelect, UniqueWhereInputInterface, UpdateParamsInterface } from '../schema/infer';
3
+ import type { StoreMutationEvent } from './mutations';
4
+ export interface StoreContextInterface {
5
+ getObjectStore: (mode: IDBTransactionMode) => IDBObjectStore;
6
+ /** See DbConfigInterface.strictInsert */
7
+ strictInsert?: boolean;
8
+ /** Optional mutation hook (used by liveQuery plugin / tests). */
9
+ onMutate?: (event: StoreMutationEvent) => void;
10
+ }
11
+ export declare class Store<TStore extends StoreInterface> {
12
+ readonly def: TStore;
13
+ private readonly ctx;
14
+ constructor(def: TStore, ctx: StoreContextInterface);
15
+ get name(): string;
16
+ private encodeWhere;
17
+ private decodeRow;
18
+ private decodeRows;
19
+ private notify;
20
+ insert(data: InferInsert<TStore>): Promise<IDBValidKey>;
21
+ insertMany(data: InferInsert<TStore>[]): Promise<IDBValidKey[]>;
22
+ find(params: FindParamsInterface<TStore>): Promise<InferSelect<TStore> | null>;
23
+ findUnique(params: {
24
+ where: UniqueWhereInputInterface<TStore>;
25
+ }): Promise<InferSelect<TStore> | null>;
26
+ findMany(params?: FindManyParamsInterface<TStore>): Promise<InferSelect<TStore>[]>;
27
+ count(params?: CountParamsInterface<TStore>): Promise<number>;
28
+ update(params: UpdateParamsInterface<TStore>): Promise<number>;
29
+ /** Alias of `update` — updates all rows matching `where`. */
30
+ updateMany(params: UpdateParamsInterface<TStore>): Promise<number>;
31
+ delete(params: DeleteParamsInterface<TStore>): Promise<number>;
32
+ /** Alias of `delete` — deletes all rows matching `where`. */
33
+ deleteMany(params: DeleteParamsInterface<TStore>): Promise<number>;
34
+ clear(): Promise<void>;
35
+ /**
36
+ * Transform each record (typically inside an upgrade transaction).
37
+ * Return `null` to delete the record.
38
+ * Callback receives / returns app-level types (boolean, not 0|1).
39
+ */
40
+ migrate(map: (row: InferSelect<TStore>) => InferSelect<TStore> | null | Promise<InferSelect<TStore> | null>): Promise<void>;
41
+ private collectWithCursor;
42
+ }