@firtoz/drizzle-indexeddb 0.2.0 → 0.4.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/CHANGELOG.md +70 -26
- package/README.md +274 -51
- package/package.json +14 -6
- package/src/bin/generate-migrations.ts +288 -0
- package/src/collections/indexeddb-collection.ts +97 -397
- package/src/context/useDrizzleIndexedDB.ts +2 -1
- package/src/function-migrator.ts +191 -170
- package/src/idb-interceptor.ts +75 -0
- package/src/idb-operations.ts +41 -0
- package/src/idb-types.ts +135 -0
- package/src/index.ts +57 -9
- package/src/instrumented-idb-database.ts +188 -0
- package/src/native-idb-database.ts +355 -0
- package/src/proxy/idb-proxy-client.ts +345 -0
- package/src/proxy/idb-proxy-server.ts +313 -0
- package/src/proxy/idb-proxy-transport.ts +174 -0
- package/src/proxy/idb-proxy-types.ts +77 -0
- package/src/proxy/idb-sync-adapter.ts +95 -0
- package/src/proxy/index.ts +37 -0
- package/src/snapshot-migrator.ts +0 -420
- package/src/utils.ts +0 -15
package/src/function-migrator.ts
CHANGED
|
@@ -1,9 +1,55 @@
|
|
|
1
|
-
// IndexedDB migrator
|
|
1
|
+
// IndexedDB migrator with declarative migration format
|
|
2
|
+
|
|
3
|
+
import type { IDBCreator, IDBDatabaseLike } from "./idb-types";
|
|
4
|
+
import { openIndexedDb } from "./idb-operations";
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Declarative Migration Types
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
export interface CreateTableOperation {
|
|
11
|
+
type: "createTable";
|
|
12
|
+
name: string;
|
|
13
|
+
keyPath?: string;
|
|
14
|
+
autoIncrement?: boolean;
|
|
15
|
+
indexes?: Array<{
|
|
16
|
+
name: string;
|
|
17
|
+
keyPath: string | string[];
|
|
18
|
+
unique?: boolean;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DeleteTableOperation {
|
|
23
|
+
type: "deleteTable";
|
|
24
|
+
name: string;
|
|
25
|
+
}
|
|
2
26
|
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
27
|
+
export interface CreateIndexOperation {
|
|
28
|
+
type: "createIndex";
|
|
29
|
+
tableName: string;
|
|
30
|
+
indexName: string;
|
|
31
|
+
keyPath: string | string[];
|
|
32
|
+
unique?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface DeleteIndexOperation {
|
|
36
|
+
type: "deleteIndex";
|
|
37
|
+
tableName: string;
|
|
38
|
+
indexName: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type MigrationOperation =
|
|
42
|
+
| CreateTableOperation
|
|
43
|
+
| DeleteTableOperation
|
|
44
|
+
| CreateIndexOperation
|
|
45
|
+
| DeleteIndexOperation;
|
|
46
|
+
|
|
47
|
+
/** A migration is an array of operations to perform */
|
|
48
|
+
export type Migration = MigrationOperation[];
|
|
49
|
+
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// Migration Record
|
|
52
|
+
// ============================================================================
|
|
7
53
|
|
|
8
54
|
interface MigrationRecord {
|
|
9
55
|
id: number;
|
|
@@ -12,160 +58,184 @@ interface MigrationRecord {
|
|
|
12
58
|
|
|
13
59
|
const MIGRATIONS_STORE = "__drizzle_migrations";
|
|
14
60
|
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// Migration Executor
|
|
63
|
+
// ============================================================================
|
|
64
|
+
|
|
15
65
|
/**
|
|
16
|
-
*
|
|
66
|
+
* Executes a single migration operation on the database.
|
|
67
|
+
*/
|
|
68
|
+
function executeMigrationOperation(
|
|
69
|
+
db: IDBDatabaseLike,
|
|
70
|
+
op: MigrationOperation,
|
|
71
|
+
): void {
|
|
72
|
+
switch (op.type) {
|
|
73
|
+
case "createTable": {
|
|
74
|
+
if (!db.hasStore(op.name)) {
|
|
75
|
+
db.createStore(op.name, {
|
|
76
|
+
keyPath: op.keyPath,
|
|
77
|
+
autoIncrement: op.autoIncrement,
|
|
78
|
+
});
|
|
79
|
+
// Create indexes if specified
|
|
80
|
+
if (op.indexes) {
|
|
81
|
+
for (const idx of op.indexes) {
|
|
82
|
+
db.createIndex(op.name, idx.name, idx.keyPath, {
|
|
83
|
+
unique: idx.unique,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case "deleteTable": {
|
|
91
|
+
if (db.hasStore(op.name)) {
|
|
92
|
+
db.deleteStore(op.name);
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "createIndex": {
|
|
97
|
+
if (db.hasStore(op.tableName)) {
|
|
98
|
+
db.createIndex(op.tableName, op.indexName, op.keyPath, {
|
|
99
|
+
unique: op.unique,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case "deleteIndex": {
|
|
105
|
+
if (db.hasStore(op.tableName)) {
|
|
106
|
+
db.deleteIndex(op.tableName, op.indexName);
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Executes a full migration (array of operations).
|
|
115
|
+
*/
|
|
116
|
+
function executeMigration(db: IDBDatabaseLike, migration: Migration): void {
|
|
117
|
+
for (const op of migration) {
|
|
118
|
+
executeMigrationOperation(db, op);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ============================================================================
|
|
123
|
+
// Main Migrator
|
|
124
|
+
// ============================================================================
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Runs IndexedDB migrations using declarative migration arrays.
|
|
128
|
+
* Version = total migrations + 1.
|
|
17
129
|
*
|
|
18
130
|
* Example usage:
|
|
19
131
|
* ```typescript
|
|
20
|
-
*
|
|
21
|
-
*
|
|
132
|
+
* const migrations: Migration[] = [
|
|
133
|
+
* [
|
|
134
|
+
* { type: "createTable", name: "todo", keyPath: "id", indexes: [
|
|
135
|
+
* { name: "todo_user_id", keyPath: "user_id" }
|
|
136
|
+
* ]}
|
|
137
|
+
* ],
|
|
138
|
+
* [
|
|
139
|
+
* { type: "createTable", name: "user", keyPath: "id" }
|
|
140
|
+
* ]
|
|
141
|
+
* ];
|
|
22
142
|
*
|
|
23
|
-
* const db = await migrateIndexedDBWithFunctions('my-db', migrations
|
|
143
|
+
* const db = await migrateIndexedDBWithFunctions('my-db', migrations);
|
|
24
144
|
* ```
|
|
25
145
|
*/
|
|
26
146
|
export async function migrateIndexedDBWithFunctions(
|
|
27
147
|
dbName: string,
|
|
28
|
-
migrations:
|
|
148
|
+
migrations: Migration[],
|
|
29
149
|
debug: boolean = false,
|
|
30
|
-
|
|
150
|
+
dbCreator?: IDBCreator,
|
|
151
|
+
): Promise<IDBDatabaseLike> {
|
|
31
152
|
if (debug) {
|
|
32
|
-
console.log(
|
|
33
|
-
`[${new Date().toISOString()}] [PERF] IndexedDB migrator start for ${dbName}`,
|
|
34
|
-
);
|
|
153
|
+
console.log(`[IndexedDB] Starting migration for ${dbName}`);
|
|
35
154
|
}
|
|
36
155
|
|
|
37
|
-
|
|
38
|
-
const currentDb = await openDatabaseForMigrationCheck(dbName);
|
|
39
|
-
|
|
40
|
-
const appliedMigrations = await getAppliedMigrations(currentDb);
|
|
156
|
+
const targetVersion = migrations.length + 1;
|
|
41
157
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
: -1;
|
|
158
|
+
// Open database to check current state
|
|
159
|
+
let db = await openIndexedDb(dbName, dbCreator);
|
|
160
|
+
const currentVersion = db.version;
|
|
46
161
|
|
|
47
162
|
if (debug) {
|
|
48
163
|
console.log(
|
|
49
|
-
`[
|
|
164
|
+
`[IndexedDB] Current version: ${currentVersion}, Target: ${targetVersion}`,
|
|
50
165
|
);
|
|
51
166
|
}
|
|
52
167
|
|
|
53
|
-
//
|
|
168
|
+
// If already at target version, check if all migrations are recorded
|
|
169
|
+
if (currentVersion >= targetVersion) {
|
|
170
|
+
const applied = await getAppliedMigrations(db);
|
|
171
|
+
if (applied.length === migrations.length) {
|
|
172
|
+
if (debug) {
|
|
173
|
+
console.log("[IndexedDB] Already up to date");
|
|
174
|
+
}
|
|
175
|
+
return db;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Get applied migrations before closing
|
|
180
|
+
const appliedMigrations = await getAppliedMigrations(db);
|
|
181
|
+
const appliedSet = new Set(appliedMigrations.map((m) => m.id));
|
|
182
|
+
|
|
183
|
+
// Find pending migrations
|
|
54
184
|
const pendingMigrations = migrations
|
|
55
|
-
.map((
|
|
56
|
-
.filter(({ idx }) => idx
|
|
185
|
+
.map((migration, idx) => ({ migration, idx }))
|
|
186
|
+
.filter(({ idx }) => !appliedSet.has(idx));
|
|
57
187
|
|
|
58
188
|
if (pendingMigrations.length === 0) {
|
|
59
189
|
if (debug) {
|
|
60
|
-
console.log(
|
|
61
|
-
`[${new Date().toISOString()}] [PERF] No pending migrations - database is up to date`,
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
currentDb.close();
|
|
65
|
-
// Re-open with correct version (migrations.length + 1 because version starts at 1)
|
|
66
|
-
const db = await openDatabase(dbName, migrations.length + 1);
|
|
67
|
-
if (debug) {
|
|
68
|
-
console.log(
|
|
69
|
-
`[${new Date().toISOString()}] [PERF] Migrator complete (no migrations needed)`,
|
|
70
|
-
);
|
|
190
|
+
console.log("[IndexedDB] No pending migrations");
|
|
71
191
|
}
|
|
72
192
|
return db;
|
|
73
193
|
}
|
|
74
194
|
|
|
75
195
|
if (debug) {
|
|
76
196
|
console.log(
|
|
77
|
-
`[
|
|
197
|
+
`[IndexedDB] ${pendingMigrations.length} pending migrations to apply`,
|
|
78
198
|
);
|
|
79
199
|
}
|
|
80
200
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
request.onupgradeneeded = async (event) => {
|
|
97
|
-
const db = (event.target as IDBOpenDBRequest).result;
|
|
98
|
-
const transaction = (event.target as IDBOpenDBRequest).transaction;
|
|
99
|
-
|
|
100
|
-
if (!transaction) {
|
|
101
|
-
reject(new Error("Transaction not available during upgrade"));
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (debug) {
|
|
106
|
-
console.log(
|
|
107
|
-
`[${new Date().toISOString()}] [PERF] Upgrade started: v${event.oldVersion} → v${event.newVersion}`,
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
// Ensure migrations store exists
|
|
113
|
-
if (!db.objectStoreNames.contains(MIGRATIONS_STORE)) {
|
|
114
|
-
const migrationStore = db.createObjectStore(MIGRATIONS_STORE, {
|
|
115
|
-
keyPath: "id",
|
|
116
|
-
autoIncrement: false,
|
|
117
|
-
});
|
|
118
|
-
migrationStore.createIndex("appliedAt", "appliedAt", {
|
|
119
|
-
unique: false,
|
|
120
|
-
});
|
|
121
|
-
if (debug) {
|
|
122
|
-
console.log(
|
|
123
|
-
`[${new Date().toISOString()}] [PERF] Created migrations tracking store`,
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Apply each pending migration
|
|
129
|
-
for (const { fn, idx } of pendingMigrations) {
|
|
130
|
-
if (debug) {
|
|
131
|
-
console.log(
|
|
132
|
-
`[${new Date().toISOString()}] [PERF] Applying migration ${idx}...`,
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Execute the migration function
|
|
137
|
-
await fn(db, transaction);
|
|
138
|
-
|
|
139
|
-
// Record the migration
|
|
140
|
-
const migrationStore = transaction.objectStore(MIGRATIONS_STORE);
|
|
141
|
-
migrationStore.add({
|
|
142
|
-
id: idx,
|
|
143
|
-
appliedAt: Date.now(),
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
if (debug) {
|
|
147
|
-
console.log(
|
|
148
|
-
`[${new Date().toISOString()}] [PERF] Migration ${idx} complete`,
|
|
149
|
-
);
|
|
150
|
-
}
|
|
201
|
+
// Close to allow version upgrade
|
|
202
|
+
db.close();
|
|
203
|
+
|
|
204
|
+
// Open with target version, running migrations during upgrade
|
|
205
|
+
await openIndexedDb(dbName, dbCreator, {
|
|
206
|
+
version: targetVersion,
|
|
207
|
+
onUpgrade: (upgradeDb) => {
|
|
208
|
+
// Ensure migrations store exists
|
|
209
|
+
if (!upgradeDb.hasStore(MIGRATIONS_STORE)) {
|
|
210
|
+
upgradeDb.createStore(MIGRATIONS_STORE, {
|
|
211
|
+
keyPath: "id",
|
|
212
|
+
autoIncrement: false,
|
|
213
|
+
});
|
|
214
|
+
if (debug) {
|
|
215
|
+
console.log("[IndexedDB] Created migrations store");
|
|
151
216
|
}
|
|
217
|
+
}
|
|
152
218
|
|
|
219
|
+
// Run pending migrations
|
|
220
|
+
for (const { migration, idx } of pendingMigrations) {
|
|
153
221
|
if (debug) {
|
|
154
|
-
console.log(
|
|
155
|
-
`[${new Date().toISOString()}] [PERF] All ${pendingMigrations.length} migrations applied successfully`,
|
|
156
|
-
);
|
|
222
|
+
console.log(`[IndexedDB] Running migration ${idx}`);
|
|
157
223
|
}
|
|
158
|
-
|
|
159
|
-
console.error("[IndexedDBMigrator] Migration failed:", error);
|
|
160
|
-
transaction.abort();
|
|
161
|
-
reject(error);
|
|
224
|
+
executeMigration(upgradeDb, migration);
|
|
162
225
|
}
|
|
163
|
-
}
|
|
226
|
+
},
|
|
164
227
|
});
|
|
165
228
|
|
|
229
|
+
// Reopen normally and record applied migrations
|
|
230
|
+
db = await openIndexedDb(dbName, dbCreator);
|
|
231
|
+
|
|
232
|
+
for (const { idx } of pendingMigrations) {
|
|
233
|
+
await db.add(MIGRATIONS_STORE, [{ id: idx, appliedAt: Date.now() }]);
|
|
234
|
+
}
|
|
235
|
+
|
|
166
236
|
if (debug) {
|
|
167
237
|
console.log(
|
|
168
|
-
`[${
|
|
238
|
+
`[IndexedDB] Applied ${pendingMigrations.length} migrations, now at version ${targetVersion}`,
|
|
169
239
|
);
|
|
170
240
|
}
|
|
171
241
|
|
|
@@ -173,62 +243,13 @@ export async function migrateIndexedDBWithFunctions(
|
|
|
173
243
|
}
|
|
174
244
|
|
|
175
245
|
/**
|
|
176
|
-
*
|
|
177
|
-
*/
|
|
178
|
-
async function openDatabaseForMigrationCheck(
|
|
179
|
-
dbName: string,
|
|
180
|
-
): Promise<IDBDatabase> {
|
|
181
|
-
return new Promise((resolve, reject) => {
|
|
182
|
-
const request = indexedDB.open(dbName);
|
|
183
|
-
request.onerror = () => {
|
|
184
|
-
reject(request.error);
|
|
185
|
-
};
|
|
186
|
-
request.onsuccess = () => {
|
|
187
|
-
resolve(request.result);
|
|
188
|
-
};
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Opens database with a specific version
|
|
194
|
-
*/
|
|
195
|
-
async function openDatabase(
|
|
196
|
-
dbName: string,
|
|
197
|
-
version: number,
|
|
198
|
-
): Promise<IDBDatabase> {
|
|
199
|
-
return new Promise((resolve, reject) => {
|
|
200
|
-
const request = indexedDB.open(dbName, version);
|
|
201
|
-
request.onerror = () => {
|
|
202
|
-
reject(request.error);
|
|
203
|
-
};
|
|
204
|
-
request.onsuccess = () => {
|
|
205
|
-
resolve(request.result);
|
|
206
|
-
};
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Gets the list of applied migrations from the database
|
|
246
|
+
* Gets applied migrations from the database.
|
|
212
247
|
*/
|
|
213
248
|
async function getAppliedMigrations(
|
|
214
|
-
db:
|
|
249
|
+
db: IDBDatabaseLike,
|
|
215
250
|
): Promise<MigrationRecord[]> {
|
|
216
|
-
if (!db.
|
|
251
|
+
if (!db.hasStore(MIGRATIONS_STORE)) {
|
|
217
252
|
return [];
|
|
218
253
|
}
|
|
219
|
-
|
|
220
|
-
return new Promise((resolve, reject) => {
|
|
221
|
-
const transaction = db.transaction(MIGRATIONS_STORE, "readonly");
|
|
222
|
-
|
|
223
|
-
const store = transaction.objectStore(MIGRATIONS_STORE);
|
|
224
|
-
|
|
225
|
-
const request = store.getAll();
|
|
226
|
-
|
|
227
|
-
request.onerror = () => {
|
|
228
|
-
reject(request.error);
|
|
229
|
-
};
|
|
230
|
-
request.onsuccess = () => {
|
|
231
|
-
resolve(request.result);
|
|
232
|
-
};
|
|
233
|
-
});
|
|
254
|
+
return db.getAll<MigrationRecord>(MIGRATIONS_STORE);
|
|
234
255
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operation tracking for IndexedDB queries
|
|
3
|
+
* Useful for testing and debugging to verify what operations are actually performed
|
|
4
|
+
*
|
|
5
|
+
* Uses discriminated unions for type safety - TypeScript can narrow the type based on the 'type' field
|
|
6
|
+
*/
|
|
7
|
+
export type IDBOperation =
|
|
8
|
+
| {
|
|
9
|
+
type: "getAll";
|
|
10
|
+
storeName: string;
|
|
11
|
+
itemsReturned: unknown[];
|
|
12
|
+
itemCount: number;
|
|
13
|
+
context?: string;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
}
|
|
16
|
+
| {
|
|
17
|
+
type: "index-getAll";
|
|
18
|
+
storeName: string;
|
|
19
|
+
indexName: string;
|
|
20
|
+
keyRange?: IDBKeyRange;
|
|
21
|
+
itemsReturned: unknown[];
|
|
22
|
+
itemCount: number;
|
|
23
|
+
context?: string;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
}
|
|
26
|
+
| {
|
|
27
|
+
type: "write";
|
|
28
|
+
storeName: string;
|
|
29
|
+
itemsWritten: unknown[];
|
|
30
|
+
writeCount: number;
|
|
31
|
+
context?: string;
|
|
32
|
+
timestamp: number;
|
|
33
|
+
}
|
|
34
|
+
| {
|
|
35
|
+
type: "get";
|
|
36
|
+
storeName: string;
|
|
37
|
+
key: IDBValidKey;
|
|
38
|
+
itemReturned?: unknown;
|
|
39
|
+
timestamp: number;
|
|
40
|
+
}
|
|
41
|
+
| {
|
|
42
|
+
type: "put";
|
|
43
|
+
storeName: string;
|
|
44
|
+
items: unknown[];
|
|
45
|
+
itemCount: number;
|
|
46
|
+
timestamp: number;
|
|
47
|
+
}
|
|
48
|
+
| {
|
|
49
|
+
type: "add";
|
|
50
|
+
storeName: string;
|
|
51
|
+
items: unknown[];
|
|
52
|
+
itemCount: number;
|
|
53
|
+
timestamp: number;
|
|
54
|
+
}
|
|
55
|
+
| {
|
|
56
|
+
type: "delete";
|
|
57
|
+
storeName: string;
|
|
58
|
+
keys: IDBValidKey[];
|
|
59
|
+
keyCount: number;
|
|
60
|
+
timestamp: number;
|
|
61
|
+
}
|
|
62
|
+
| {
|
|
63
|
+
type: "clear";
|
|
64
|
+
storeName: string;
|
|
65
|
+
timestamp: number;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Interceptor interface for tracking IndexedDB operations
|
|
70
|
+
* Allows tests and debugging tools to observe what operations are performed
|
|
71
|
+
*/
|
|
72
|
+
export interface IDBInterceptor {
|
|
73
|
+
/** Called when any IndexedDB operation is performed */
|
|
74
|
+
onOperation?: (operation: IDBOperation) => void;
|
|
75
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IDBDatabaseLike,
|
|
3
|
+
IDBCreator,
|
|
4
|
+
IDBOpenOptions,
|
|
5
|
+
IDBDeleter,
|
|
6
|
+
} from "./idb-types";
|
|
7
|
+
import { defaultIDBCreator } from "./native-idb-database";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Opens an IndexedDB database using the provided creator or the default native implementation.
|
|
11
|
+
*/
|
|
12
|
+
export async function openIndexedDb(
|
|
13
|
+
name: string,
|
|
14
|
+
dbCreator?: IDBCreator,
|
|
15
|
+
options?: IDBOpenOptions,
|
|
16
|
+
): Promise<IDBDatabaseLike> {
|
|
17
|
+
const dbCreatorToUse = dbCreator ?? defaultIDBCreator;
|
|
18
|
+
return dbCreatorToUse(name, options);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default IDB deleter that uses the native IndexedDB API.
|
|
23
|
+
*/
|
|
24
|
+
const defaultIDBDeleter: IDBDeleter = (name: string): Promise<void> => {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const request = indexedDB.deleteDatabase(name);
|
|
27
|
+
request.onerror = () => reject(request.error);
|
|
28
|
+
request.onsuccess = () => resolve();
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Deletes an IndexedDB database (useful for testing)
|
|
34
|
+
*/
|
|
35
|
+
export async function deleteIndexedDB(
|
|
36
|
+
dbName: string,
|
|
37
|
+
dbDeleter?: IDBDeleter,
|
|
38
|
+
): Promise<void> {
|
|
39
|
+
const dbDeleterToUse = dbDeleter ?? defaultIDBDeleter;
|
|
40
|
+
return dbDeleterToUse(dbName);
|
|
41
|
+
}
|
package/src/idb-types.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Index information returned by getStoreIndexes
|
|
3
|
+
*/
|
|
4
|
+
export interface IndexInfo {
|
|
5
|
+
name: string;
|
|
6
|
+
keyPath: string | string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Options for creating an object store
|
|
11
|
+
*/
|
|
12
|
+
export interface CreateStoreOptions {
|
|
13
|
+
keyPath?: string;
|
|
14
|
+
autoIncrement?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for creating an index
|
|
19
|
+
*/
|
|
20
|
+
export interface CreateIndexOptions {
|
|
21
|
+
unique?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Key range specification for index queries
|
|
26
|
+
*/
|
|
27
|
+
export interface KeyRangeSpec {
|
|
28
|
+
type: "only" | "lowerBound" | "upperBound" | "bound";
|
|
29
|
+
value?: unknown;
|
|
30
|
+
lower?: unknown;
|
|
31
|
+
upper?: unknown;
|
|
32
|
+
lowerOpen?: boolean;
|
|
33
|
+
upperOpen?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Minimal database interface with high-level async operations.
|
|
38
|
+
* This is the interface that custom implementations (mocks, Chrome extension proxies, etc.) need to implement.
|
|
39
|
+
*
|
|
40
|
+
* All operations are simple async functions - no transactions, requests, or callbacks to deal with.
|
|
41
|
+
*/
|
|
42
|
+
export interface IDBDatabaseLike {
|
|
43
|
+
/** Database version number */
|
|
44
|
+
readonly version: number;
|
|
45
|
+
|
|
46
|
+
// =========================================================================
|
|
47
|
+
// Schema Operations (for migrations)
|
|
48
|
+
// =========================================================================
|
|
49
|
+
|
|
50
|
+
/** Check if a store exists */
|
|
51
|
+
hasStore(storeName: string): boolean;
|
|
52
|
+
|
|
53
|
+
/** Get list of all store names */
|
|
54
|
+
getStoreNames(): string[];
|
|
55
|
+
|
|
56
|
+
/** Create an object store (only valid during migrations) */
|
|
57
|
+
createStore(storeName: string, options?: CreateStoreOptions): void;
|
|
58
|
+
|
|
59
|
+
/** Delete an object store (only valid during migrations) */
|
|
60
|
+
deleteStore(storeName: string): void;
|
|
61
|
+
|
|
62
|
+
/** Create an index on a store (only valid during migrations) */
|
|
63
|
+
createIndex(
|
|
64
|
+
storeName: string,
|
|
65
|
+
indexName: string,
|
|
66
|
+
keyPath: string | string[],
|
|
67
|
+
options?: CreateIndexOptions,
|
|
68
|
+
): void;
|
|
69
|
+
|
|
70
|
+
/** Delete an index from a store (only valid during migrations) */
|
|
71
|
+
deleteIndex(storeName: string, indexName: string): void;
|
|
72
|
+
|
|
73
|
+
/** Get all indexes for a store (for index discovery) */
|
|
74
|
+
getStoreIndexes(storeName: string): IndexInfo[];
|
|
75
|
+
|
|
76
|
+
// =========================================================================
|
|
77
|
+
// Data Operations (all async, handle transactions internally)
|
|
78
|
+
// =========================================================================
|
|
79
|
+
|
|
80
|
+
/** Get all items from a store */
|
|
81
|
+
getAll<T = unknown>(storeName: string): Promise<T[]>;
|
|
82
|
+
|
|
83
|
+
/** Get items from a store using an index with optional key range */
|
|
84
|
+
getAllByIndex<T = unknown>(
|
|
85
|
+
storeName: string,
|
|
86
|
+
indexName: string,
|
|
87
|
+
keyRange?: KeyRangeSpec,
|
|
88
|
+
): Promise<T[]>;
|
|
89
|
+
|
|
90
|
+
/** Get a single item by key */
|
|
91
|
+
get<T = unknown>(storeName: string, key: IDBValidKey): Promise<T | undefined>;
|
|
92
|
+
|
|
93
|
+
/** Add items to a store (batch operation) */
|
|
94
|
+
add(storeName: string, items: unknown[]): Promise<void>;
|
|
95
|
+
|
|
96
|
+
/** Update items in a store (batch operation, uses put) */
|
|
97
|
+
put(storeName: string, items: unknown[]): Promise<void>;
|
|
98
|
+
|
|
99
|
+
/** Delete items from a store by keys (batch operation) */
|
|
100
|
+
delete(storeName: string, keys: IDBValidKey[]): Promise<void>;
|
|
101
|
+
|
|
102
|
+
/** Clear all items from a store */
|
|
103
|
+
clear(storeName: string): Promise<void>;
|
|
104
|
+
|
|
105
|
+
// =========================================================================
|
|
106
|
+
// Lifecycle
|
|
107
|
+
// =========================================================================
|
|
108
|
+
|
|
109
|
+
/** Close the database connection */
|
|
110
|
+
close(): void;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Options for opening a database with version upgrade support.
|
|
115
|
+
*/
|
|
116
|
+
export interface IDBOpenOptions {
|
|
117
|
+
/** Target version for the database. If higher than current, triggers upgrade. */
|
|
118
|
+
version?: number;
|
|
119
|
+
/** Called during version upgrade - this is where schema changes (createStore, createIndex) are allowed. */
|
|
120
|
+
onUpgrade?: (db: IDBDatabaseLike) => void;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Function type for creating/opening an IndexedDB-like database.
|
|
125
|
+
* Custom implementations can use this to provide proxy/mock/alternative backends.
|
|
126
|
+
*/
|
|
127
|
+
export type IDBCreator = (
|
|
128
|
+
name: string,
|
|
129
|
+
options?: IDBOpenOptions,
|
|
130
|
+
) => Promise<IDBDatabaseLike>;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Function type for deleting an IndexedDB database.
|
|
134
|
+
*/
|
|
135
|
+
export type IDBDeleter = (name: string) => Promise<void>;
|