@firtoz/drizzle-indexeddb 0.6.2 → 2.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.
- package/CHANGELOG.md +31 -0
- package/package.json +10 -7
- package/src/collections/drizzle-indexeddb-collection.ts +310 -0
- package/src/context/DrizzleIndexedDBProvider.tsx +6 -91
- package/src/function-migrator.ts +3 -0
- package/src/idb-types.ts +2 -12
- package/src/index.ts +4 -32
- package/src/instrumented-idb-database.ts +1 -1
- package/src/native-idb-database.ts +4 -1
- package/src/standalone-collection.ts +8 -12
- package/src/collections/indexeddb-collection.ts +0 -579
- package/src/proxy/idb-proxy-client.ts +0 -341
- package/src/proxy/idb-proxy-server.ts +0 -313
- package/src/proxy/idb-proxy-transport.ts +0 -174
- package/src/proxy/idb-proxy-types.ts +0 -77
- package/src/proxy/idb-sync-adapter.ts +0 -95
- package/src/proxy/index.ts +0 -37
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @firtoz/drizzle-indexeddb
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- [`5c667ec`](https://github.com/firtoz/fullstack-toolkit/commit/5c667ecfce1ed4f22ccf9686ad37f00e7a4ecee3) Thanks [@firtoz](https://github.com/firtoz)! - BREAKING: Renamed Drizzle-specific exports with `Drizzle` prefix for clarity. `indexedDBCollectionOptions` → `drizzleIndexedDBCollectionOptions`, `IndexedDBCollectionConfig` → `DrizzleIndexedDBCollectionConfig`, `IndexedDBSyncItem` → `DrizzleIndexedDBSyncItem`. Removed `KeyRangeSpec` re-export (import from `@firtoz/idb-collections` instead). `tryExtractIndexedQuery` moved to `@firtoz/idb-collections`.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`5c667ec`](https://github.com/firtoz/fullstack-toolkit/commit/5c667ecfce1ed4f22ccf9686ad37f00e7a4ecee3), [`5c667ec`](https://github.com/firtoz/fullstack-toolkit/commit/5c667ecfce1ed4f22ccf9686ad37f00e7a4ecee3), [`5c667ec`](https://github.com/firtoz/fullstack-toolkit/commit/5c667ecfce1ed4f22ccf9686ad37f00e7a4ecee3), [`5c667ec`](https://github.com/firtoz/fullstack-toolkit/commit/5c667ecfce1ed4f22ccf9686ad37f00e7a4ecee3)]:
|
|
12
|
+
- @firtoz/idb-collections@0.2.0
|
|
13
|
+
- @firtoz/db-helpers@2.0.0
|
|
14
|
+
- @firtoz/drizzle-utils@1.0.1
|
|
15
|
+
|
|
16
|
+
## 1.0.0
|
|
17
|
+
|
|
18
|
+
### Major Changes
|
|
19
|
+
|
|
20
|
+
- [`3c7ce1d`](https://github.com/firtoz/fullstack-toolkit/commit/3c7ce1dbca5c5396386db9927ae7f5e19a562cf6) Thanks [@firtoz](https://github.com/firtoz)! - **IndexedDB proxy removed**
|
|
21
|
+
|
|
22
|
+
The IDB proxy layer (proxy client/server, transport, sync adapter) has been removed. Provider no longer exposes proxy sync; use native IndexedDB only.
|
|
23
|
+
|
|
24
|
+
**Upgrade:** If you were using the IDB proxy (e.g. `idb-proxy-client`, `idb-proxy-server`, `handleProxySync`, `onSyncReady`): remove that code. There is no replacement API; you implement the transport and call `receiveSync` with `SyncMessage[]` from `@firtoz/drizzle-utils` on each side.
|
|
25
|
+
|
|
26
|
+
**Remote / multi-context setup:** Use a **memory collection** (`@firtoz/db-helpers`) in the context that cannot access IndexedDB and keep the **IDB collection** where IndexedDB is available (source of truth). Implement your own channel (e.g. BroadcastChannel, `postMessage`, or WebSocket): when a context receives sync messages, call `utils.receiveSync(messages)` on its collection. Use `SyncMessage` from `@firtoz/db-helpers` (or `@firtoz/drizzle-utils`). The side that holds IDB can push initial state and updates as `SyncMessage[]` so the other side’s memory collection stays in sync.
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- Updated dependencies [[`3c7ce1d`](https://github.com/firtoz/fullstack-toolkit/commit/3c7ce1dbca5c5396386db9927ae7f5e19a562cf6), [`3c7ce1d`](https://github.com/firtoz/fullstack-toolkit/commit/3c7ce1dbca5c5396386db9927ae7f5e19a562cf6)]:
|
|
31
|
+
- @firtoz/db-helpers@1.0.0
|
|
32
|
+
- @firtoz/drizzle-utils@1.0.0
|
|
33
|
+
|
|
3
34
|
## 0.6.2
|
|
4
35
|
|
|
5
36
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firtoz/drizzle-indexeddb",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "IndexedDB migrations powered by Drizzle ORM",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -68,23 +68,26 @@
|
|
|
68
68
|
"access": "public"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
|
-
"@firtoz/drizzle-utils": ">=0.
|
|
72
|
-
"@tanstack/db": ">=0.5.
|
|
71
|
+
"@firtoz/drizzle-utils": ">=1.0.1",
|
|
72
|
+
"@tanstack/db": ">=0.5.33",
|
|
73
73
|
"drizzle-orm": ">=0.45.1",
|
|
74
74
|
"drizzle-valibot": ">=0.4.0",
|
|
75
75
|
"react": ">=19.2.4",
|
|
76
76
|
"valibot": ">=1.0.0"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
-
"@firtoz/drizzle-utils": "^0.
|
|
80
|
-
"@tanstack/db": "^0.5.
|
|
81
|
-
"@types/react": "^19.2.
|
|
79
|
+
"@firtoz/drizzle-utils": "^1.0.1",
|
|
80
|
+
"@tanstack/db": "^0.5.33",
|
|
81
|
+
"@types/react": "^19.2.14",
|
|
82
82
|
"drizzle-orm": "^0.45.1",
|
|
83
83
|
"drizzle-valibot": "^0.4.2",
|
|
84
84
|
"react": "^19.2.4",
|
|
85
85
|
"valibot": "^1.2.0"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"
|
|
88
|
+
"@firtoz/db-helpers": "^2.0.0",
|
|
89
|
+
"@firtoz/idb-collections": "^0.2.0",
|
|
90
|
+
"@firtoz/maybe-error": "^1.5.2",
|
|
91
|
+
"citty": "^0.2.1"
|
|
89
92
|
}
|
|
90
93
|
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import type { InferSchemaOutput, SyncMode } from "@tanstack/db";
|
|
2
|
+
import type { IR } from "@tanstack/db";
|
|
3
|
+
import { parseOrderByExpression } from "@tanstack/db";
|
|
4
|
+
import type { Table } from "drizzle-orm";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type IdOf,
|
|
8
|
+
type SelectSchema,
|
|
9
|
+
type BaseSyncConfig,
|
|
10
|
+
type SyncBackend,
|
|
11
|
+
createSyncFunction,
|
|
12
|
+
createInsertSchemaWithDefaults,
|
|
13
|
+
createGetKeyFunction,
|
|
14
|
+
createCollectionConfig,
|
|
15
|
+
} from "@firtoz/drizzle-utils";
|
|
16
|
+
import { evaluateExpression } from "@firtoz/db-helpers";
|
|
17
|
+
import { tryExtractIndexedQuery } from "@firtoz/idb-collections";
|
|
18
|
+
|
|
19
|
+
import type { IDBDatabaseLike } from "../idb-types";
|
|
20
|
+
|
|
21
|
+
// biome-ignore lint/suspicious/noExplicitAny: intentional
|
|
22
|
+
type AnyId = IdOf<any>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Type for items stored in IndexedDB (must have required sync fields)
|
|
26
|
+
*/
|
|
27
|
+
export type DrizzleIndexedDBSyncItem = {
|
|
28
|
+
id: AnyId;
|
|
29
|
+
createdAt: Date;
|
|
30
|
+
updatedAt: Date;
|
|
31
|
+
deletedAt: Date | null;
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export interface DrizzleIndexedDBCollectionConfig<TTable extends Table> {
|
|
36
|
+
/**
|
|
37
|
+
* Ref to the IndexedDB database instance
|
|
38
|
+
*/
|
|
39
|
+
indexedDBRef: React.RefObject<IDBDatabaseLike | null>;
|
|
40
|
+
/**
|
|
41
|
+
* The Drizzle table definition (used for schema and type inference only)
|
|
42
|
+
*/
|
|
43
|
+
table: TTable;
|
|
44
|
+
/**
|
|
45
|
+
* The name of the IndexedDB object store (should match the table name)
|
|
46
|
+
*/
|
|
47
|
+
storeName: string;
|
|
48
|
+
/**
|
|
49
|
+
* Promise that resolves when the database is ready
|
|
50
|
+
*/
|
|
51
|
+
readyPromise: Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Sync mode: 'eager' (immediate) or 'on-demand'
|
|
54
|
+
*/
|
|
55
|
+
syncMode?: SyncMode;
|
|
56
|
+
/**
|
|
57
|
+
* Enable debug logging for index discovery and query optimization
|
|
58
|
+
*/
|
|
59
|
+
debug?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Auto-discovers indexes from the IndexedDB store.
|
|
64
|
+
* Returns a map of field names to index names for single-column indexes.
|
|
65
|
+
*/
|
|
66
|
+
function discoverIndexes(
|
|
67
|
+
db: IDBDatabaseLike,
|
|
68
|
+
storeName: string,
|
|
69
|
+
): Record<string, string> {
|
|
70
|
+
const indexes = db.getStoreIndexes(storeName);
|
|
71
|
+
const indexMap: Record<string, string> = {};
|
|
72
|
+
|
|
73
|
+
for (const index of indexes) {
|
|
74
|
+
if (typeof index.keyPath === "string") {
|
|
75
|
+
indexMap[index.keyPath] = index.name;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return indexMap;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Creates a TanStack DB collection config for IndexedDB backed by Drizzle ORM.
|
|
84
|
+
*/
|
|
85
|
+
export function drizzleIndexedDBCollectionOptions<const TTable extends Table>(
|
|
86
|
+
config: DrizzleIndexedDBCollectionConfig<TTable>,
|
|
87
|
+
) {
|
|
88
|
+
let discoveredIndexes: Record<string, string> = {};
|
|
89
|
+
let indexesDiscovered = false;
|
|
90
|
+
|
|
91
|
+
const table = config.table;
|
|
92
|
+
|
|
93
|
+
const discoverIndexesOnce = async () => {
|
|
94
|
+
await config.readyPromise;
|
|
95
|
+
|
|
96
|
+
const db = config.indexedDBRef.current;
|
|
97
|
+
if (!db) {
|
|
98
|
+
throw new Error("Database not ready");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!indexesDiscovered) {
|
|
102
|
+
discoveredIndexes = discoverIndexes(db, config.storeName);
|
|
103
|
+
|
|
104
|
+
indexesDiscovered = true;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const backend: SyncBackend<TTable> = {
|
|
109
|
+
initialLoad: async () => {
|
|
110
|
+
const db = config.indexedDBRef.current;
|
|
111
|
+
if (!db) {
|
|
112
|
+
throw new Error("Database not ready");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
await discoverIndexesOnce();
|
|
116
|
+
|
|
117
|
+
const items = await db.getAll<DrizzleIndexedDBSyncItem>(config.storeName);
|
|
118
|
+
|
|
119
|
+
return items as unknown as InferSchemaOutput<SelectSchema<TTable>>[];
|
|
120
|
+
},
|
|
121
|
+
loadSubset: async (options) => {
|
|
122
|
+
const db = config.indexedDBRef.current;
|
|
123
|
+
if (!db) {
|
|
124
|
+
throw new Error("Database not ready");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!indexesDiscovered) {
|
|
128
|
+
discoveredIndexes = discoverIndexes(db, config.storeName);
|
|
129
|
+
indexesDiscovered = true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let items: DrizzleIndexedDBSyncItem[];
|
|
133
|
+
|
|
134
|
+
let combinedWhere = options.where;
|
|
135
|
+
if (options.cursor?.whereFrom) {
|
|
136
|
+
if (combinedWhere) {
|
|
137
|
+
combinedWhere = {
|
|
138
|
+
type: "func",
|
|
139
|
+
name: "and",
|
|
140
|
+
args: [combinedWhere, options.cursor.whereFrom],
|
|
141
|
+
} as IR.Func;
|
|
142
|
+
} else {
|
|
143
|
+
combinedWhere = options.cursor.whereFrom;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const indexedQuery = combinedWhere
|
|
148
|
+
? tryExtractIndexedQuery(combinedWhere, discoveredIndexes, config.debug)
|
|
149
|
+
: null;
|
|
150
|
+
|
|
151
|
+
if (indexedQuery) {
|
|
152
|
+
items = await db.getAllByIndex<DrizzleIndexedDBSyncItem>(
|
|
153
|
+
config.storeName,
|
|
154
|
+
indexedQuery.indexName,
|
|
155
|
+
indexedQuery.keyRange,
|
|
156
|
+
);
|
|
157
|
+
} else {
|
|
158
|
+
items = await db.getAll<DrizzleIndexedDBSyncItem>(config.storeName);
|
|
159
|
+
|
|
160
|
+
if (combinedWhere) {
|
|
161
|
+
const whereExpression = combinedWhere;
|
|
162
|
+
items = items.filter((item) =>
|
|
163
|
+
evaluateExpression(
|
|
164
|
+
whereExpression,
|
|
165
|
+
item as Record<string, unknown>,
|
|
166
|
+
),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (options.orderBy) {
|
|
172
|
+
const sorts = parseOrderByExpression(options.orderBy);
|
|
173
|
+
items.sort((a, b) => {
|
|
174
|
+
for (const sort of sorts) {
|
|
175
|
+
// biome-ignore lint/suspicious/noExplicitAny: Need any for dynamic field access
|
|
176
|
+
let aValue: any = a;
|
|
177
|
+
// biome-ignore lint/suspicious/noExplicitAny: Need any for dynamic field access
|
|
178
|
+
let bValue: any = b;
|
|
179
|
+
for (const fieldName of sort.field) {
|
|
180
|
+
aValue = aValue?.[fieldName];
|
|
181
|
+
bValue = bValue?.[fieldName];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (aValue < bValue) {
|
|
185
|
+
return sort.direction === "asc" ? -1 : 1;
|
|
186
|
+
}
|
|
187
|
+
if (aValue > bValue) {
|
|
188
|
+
return sort.direction === "asc" ? 1 : -1;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return 0;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (options.offset !== undefined && options.offset > 0) {
|
|
196
|
+
items = items.slice(options.offset);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (options.limit !== undefined) {
|
|
200
|
+
items = items.slice(0, options.limit);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return items as unknown as InferSchemaOutput<SelectSchema<TTable>>[];
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
handleInsert: async (itemsToInsert) => {
|
|
207
|
+
const db = config.indexedDBRef.current;
|
|
208
|
+
if (!db) {
|
|
209
|
+
throw new Error("Database not ready");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await db.add(config.storeName, itemsToInsert);
|
|
213
|
+
|
|
214
|
+
return itemsToInsert;
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
handleUpdate: async (mutations) => {
|
|
218
|
+
const db = config.indexedDBRef.current;
|
|
219
|
+
|
|
220
|
+
if (!db) {
|
|
221
|
+
throw new Error("Database not ready");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const results: Array<InferSchemaOutput<SelectSchema<TTable>>> = [];
|
|
225
|
+
const itemsToUpdate: DrizzleIndexedDBSyncItem[] = [];
|
|
226
|
+
|
|
227
|
+
for (const mutation of mutations) {
|
|
228
|
+
const existing = await db.get<DrizzleIndexedDBSyncItem>(
|
|
229
|
+
config.storeName,
|
|
230
|
+
mutation.key,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
if (existing) {
|
|
234
|
+
const updateTime = new Date();
|
|
235
|
+
const updatedItem = {
|
|
236
|
+
...existing,
|
|
237
|
+
...mutation.changes,
|
|
238
|
+
updatedAt: updateTime,
|
|
239
|
+
} as DrizzleIndexedDBSyncItem;
|
|
240
|
+
|
|
241
|
+
itemsToUpdate.push(updatedItem);
|
|
242
|
+
results.push(
|
|
243
|
+
updatedItem as unknown as InferSchemaOutput<SelectSchema<TTable>>,
|
|
244
|
+
);
|
|
245
|
+
} else {
|
|
246
|
+
results.push(mutation.original);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (itemsToUpdate.length > 0) {
|
|
251
|
+
await db.put(config.storeName, itemsToUpdate);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return results;
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
handleDelete: async (mutations) => {
|
|
258
|
+
const db = config.indexedDBRef.current;
|
|
259
|
+
|
|
260
|
+
if (!db) {
|
|
261
|
+
throw new Error("Database not ready");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const keysToDelete: IDBValidKey[] = mutations.map((m) => m.key);
|
|
265
|
+
|
|
266
|
+
await db.delete(config.storeName, keysToDelete);
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
handleTruncate: async () => {
|
|
270
|
+
const db = config.indexedDBRef.current;
|
|
271
|
+
|
|
272
|
+
if (!db) {
|
|
273
|
+
throw new Error("Database not ready");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
await db.clear(config.storeName);
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const wrappedBackend: SyncBackend<TTable> = {
|
|
281
|
+
...backend,
|
|
282
|
+
initialLoad: async () => {
|
|
283
|
+
if (config.syncMode === "eager" || !config.syncMode) {
|
|
284
|
+
return await backend.initialLoad();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
await discoverIndexesOnce();
|
|
288
|
+
|
|
289
|
+
return [];
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const baseSyncConfig: BaseSyncConfig<TTable> = {
|
|
294
|
+
table,
|
|
295
|
+
readyPromise: config.readyPromise,
|
|
296
|
+
syncMode: config.syncMode,
|
|
297
|
+
debug: config.debug,
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const syncResult = createSyncFunction(baseSyncConfig, wrappedBackend);
|
|
301
|
+
|
|
302
|
+
const schema = createInsertSchemaWithDefaults(table);
|
|
303
|
+
|
|
304
|
+
return createCollectionConfig({
|
|
305
|
+
schema,
|
|
306
|
+
getKey: createGetKeyFunction<TTable>(),
|
|
307
|
+
syncResult,
|
|
308
|
+
syncMode: config.syncMode,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
@@ -16,16 +16,16 @@ import {
|
|
|
16
16
|
} from "@tanstack/db";
|
|
17
17
|
import { getTableName, type Table } from "drizzle-orm";
|
|
18
18
|
import {
|
|
19
|
-
|
|
20
|
-
type
|
|
19
|
+
drizzleIndexedDBCollectionOptions,
|
|
20
|
+
type DrizzleIndexedDBCollectionConfig,
|
|
21
21
|
} from "@firtoz/drizzle-indexeddb";
|
|
22
|
+
import type { CollectionUtils } from "@firtoz/db-helpers";
|
|
22
23
|
import type {
|
|
23
24
|
IdOf,
|
|
24
25
|
InsertSchema,
|
|
25
26
|
SelectSchema,
|
|
26
27
|
GetTableFromSchema,
|
|
27
28
|
InferCollectionFromTable,
|
|
28
|
-
CollectionUtils,
|
|
29
29
|
} from "@firtoz/drizzle-utils";
|
|
30
30
|
import {
|
|
31
31
|
type Migration,
|
|
@@ -33,7 +33,6 @@ import {
|
|
|
33
33
|
} from "../function-migrator";
|
|
34
34
|
import type { IDBCreator, IDBDatabaseLike } from "../idb-types";
|
|
35
35
|
import { openIndexedDb } from "../idb-operations";
|
|
36
|
-
import type { IDBProxySyncMessage } from "../proxy/idb-proxy-types";
|
|
37
36
|
|
|
38
37
|
interface CollectionCacheEntry {
|
|
39
38
|
// biome-ignore lint/suspicious/noExplicitAny: Cache needs to store collections of various types
|
|
@@ -65,11 +64,6 @@ export type DrizzleIndexedDBContextValue<
|
|
|
65
64
|
) => IndexedDbCollection<TSchema, TTableName>;
|
|
66
65
|
incrementRefCount: (tableName: string) => void;
|
|
67
66
|
decrementRefCount: (tableName: string) => void;
|
|
68
|
-
/**
|
|
69
|
-
* Handle a sync message from a proxy server.
|
|
70
|
-
* Routes the message to the appropriate collection's external sync handler.
|
|
71
|
-
*/
|
|
72
|
-
handleProxySync: (message: IDBProxySyncMessage) => void;
|
|
73
67
|
};
|
|
74
68
|
|
|
75
69
|
export const DrizzleIndexedDBContext =
|
|
@@ -94,18 +88,6 @@ type DrizzleIndexedDBProviderProps<TSchema extends Record<string, unknown>> =
|
|
|
94
88
|
* Use createInstrumentedDbCreator() to track IndexedDB operations.
|
|
95
89
|
*/
|
|
96
90
|
dbCreator?: IDBCreator;
|
|
97
|
-
/**
|
|
98
|
-
* Called when the sync handler is ready.
|
|
99
|
-
* Use this to connect proxy sync messages to the provider.
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* const proxyClient = ...;
|
|
103
|
-
* <DrizzleIndexedDBProvider
|
|
104
|
-
* onSyncReady={(handleSync) => proxyClient.onSync(handleSync)}
|
|
105
|
-
* ...
|
|
106
|
-
* />
|
|
107
|
-
*/
|
|
108
|
-
onSyncReady?: (handleSync: (message: IDBProxySyncMessage) => void) => void;
|
|
109
91
|
}>;
|
|
110
92
|
|
|
111
93
|
export function DrizzleIndexedDBProvider<
|
|
@@ -119,7 +101,6 @@ export function DrizzleIndexedDBProvider<
|
|
|
119
101
|
debug = false,
|
|
120
102
|
syncMode = "eager",
|
|
121
103
|
dbCreator,
|
|
122
|
-
onSyncReady,
|
|
123
104
|
}: DrizzleIndexedDBProviderProps<TSchema>) {
|
|
124
105
|
const [indexedDB, setIndexedDB] = useState<IDBDatabaseLike | null>(null);
|
|
125
106
|
const indexedDBRef = useRef<IDBDatabaseLike | null>(null);
|
|
@@ -192,14 +173,14 @@ export function DrizzleIndexedDBProvider<
|
|
|
192
173
|
const actualTableName = getTableName(table);
|
|
193
174
|
|
|
194
175
|
// Create collection options
|
|
195
|
-
const collectionConfig =
|
|
176
|
+
const collectionConfig = drizzleIndexedDBCollectionOptions({
|
|
196
177
|
indexedDBRef,
|
|
197
178
|
table,
|
|
198
179
|
storeName: actualTableName,
|
|
199
180
|
readyPromise: readyPromise.promise,
|
|
200
181
|
debug,
|
|
201
182
|
syncMode,
|
|
202
|
-
} as
|
|
183
|
+
} as DrizzleIndexedDBCollectionConfig<Table>);
|
|
203
184
|
|
|
204
185
|
// Create new collection and cache it with ref count 0
|
|
205
186
|
// The collection will wait for readyPromise before accessing the database
|
|
@@ -248,80 +229,14 @@ export function DrizzleIndexedDBProvider<
|
|
|
248
229
|
[collections],
|
|
249
230
|
);
|
|
250
231
|
|
|
251
|
-
// Handle proxy sync messages by routing to the appropriate collection
|
|
252
|
-
const handleProxySync: DrizzleIndexedDBContextValue<TSchema>["handleProxySync"] =
|
|
253
|
-
useCallback(
|
|
254
|
-
(message: IDBProxySyncMessage) => {
|
|
255
|
-
// Find the collection for this store by checking the schema
|
|
256
|
-
for (const [tableName, table] of Object.entries(schema)) {
|
|
257
|
-
const actualStoreName = getTableName(table as Table);
|
|
258
|
-
if (actualStoreName === message.storeName) {
|
|
259
|
-
const entry = collections.get(tableName);
|
|
260
|
-
if (entry) {
|
|
261
|
-
const { pushExternalSync } = entry.collection.utils;
|
|
262
|
-
// Route sync message to collection
|
|
263
|
-
switch (message.type) {
|
|
264
|
-
case "sync:add":
|
|
265
|
-
pushExternalSync({
|
|
266
|
-
type: "insert",
|
|
267
|
-
items: message.items,
|
|
268
|
-
});
|
|
269
|
-
break;
|
|
270
|
-
case "sync:put":
|
|
271
|
-
pushExternalSync({
|
|
272
|
-
type: "update",
|
|
273
|
-
items: message.items,
|
|
274
|
-
});
|
|
275
|
-
break;
|
|
276
|
-
case "sync:delete":
|
|
277
|
-
// For delete, construct items with id
|
|
278
|
-
pushExternalSync({
|
|
279
|
-
type: "delete",
|
|
280
|
-
items: message.keys.map((key) => ({ id: key })),
|
|
281
|
-
});
|
|
282
|
-
break;
|
|
283
|
-
case "sync:truncate":
|
|
284
|
-
pushExternalSync({
|
|
285
|
-
type: "truncate",
|
|
286
|
-
});
|
|
287
|
-
break;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (debug) {
|
|
295
|
-
console.warn(
|
|
296
|
-
`[DrizzleIndexedDBProvider] No collection found for store: ${message.storeName}`,
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
},
|
|
300
|
-
[collections, schema, debug],
|
|
301
|
-
);
|
|
302
|
-
|
|
303
|
-
// Call onSyncReady when handleProxySync is available
|
|
304
|
-
useEffect(() => {
|
|
305
|
-
if (onSyncReady) {
|
|
306
|
-
onSyncReady(handleProxySync);
|
|
307
|
-
}
|
|
308
|
-
}, [onSyncReady, handleProxySync]);
|
|
309
|
-
|
|
310
232
|
const contextValue: DrizzleIndexedDBContextValue<TSchema> = useMemo(
|
|
311
233
|
() => ({
|
|
312
234
|
indexedDB,
|
|
313
235
|
getCollection,
|
|
314
236
|
incrementRefCount,
|
|
315
237
|
decrementRefCount,
|
|
316
|
-
handleProxySync,
|
|
317
238
|
}),
|
|
318
|
-
[
|
|
319
|
-
indexedDB,
|
|
320
|
-
getCollection,
|
|
321
|
-
incrementRefCount,
|
|
322
|
-
decrementRefCount,
|
|
323
|
-
handleProxySync,
|
|
324
|
-
],
|
|
239
|
+
[indexedDB, getCollection, incrementRefCount, decrementRefCount],
|
|
325
240
|
);
|
|
326
241
|
|
|
327
242
|
return (
|
package/src/function-migrator.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// IndexedDB migrator with declarative migration format
|
|
2
2
|
|
|
3
|
+
import { exhaustiveGuard } from "@firtoz/maybe-error";
|
|
3
4
|
import type { IDBCreator, IDBDatabaseLike } from "./idb-types";
|
|
4
5
|
import { openIndexedDb } from "./idb-operations";
|
|
5
6
|
|
|
@@ -107,6 +108,8 @@ function executeMigrationOperation(
|
|
|
107
108
|
}
|
|
108
109
|
break;
|
|
109
110
|
}
|
|
111
|
+
default:
|
|
112
|
+
exhaustiveGuard(op);
|
|
110
113
|
}
|
|
111
114
|
}
|
|
112
115
|
|
package/src/idb-types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { KeyRangeSpec } from "@firtoz/idb-collections";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Index information returned by getStoreIndexes
|
|
3
5
|
*/
|
|
@@ -21,18 +23,6 @@ export interface CreateIndexOptions {
|
|
|
21
23
|
unique?: boolean;
|
|
22
24
|
}
|
|
23
25
|
|
|
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
26
|
/**
|
|
37
27
|
* Minimal database interface with high-level async operations.
|
|
38
28
|
* This is the interface that custom implementations (mocks, Chrome extension proxies, etc.) need to implement.
|
package/src/index.ts
CHANGED
|
@@ -17,7 +17,6 @@ export type {
|
|
|
17
17
|
IndexInfo,
|
|
18
18
|
CreateStoreOptions,
|
|
19
19
|
CreateIndexOptions,
|
|
20
|
-
KeyRangeSpec,
|
|
21
20
|
} from "./idb-types";
|
|
22
21
|
|
|
23
22
|
// IDB Interceptor (for testing/debugging)
|
|
@@ -34,10 +33,10 @@ export { createInstrumentedDbCreator } from "./instrumented-idb-database";
|
|
|
34
33
|
|
|
35
34
|
// Collection
|
|
36
35
|
export {
|
|
37
|
-
|
|
38
|
-
type
|
|
39
|
-
type
|
|
40
|
-
} from "./collections/indexeddb-collection";
|
|
36
|
+
drizzleIndexedDBCollectionOptions,
|
|
37
|
+
type DrizzleIndexedDBCollectionConfig,
|
|
38
|
+
type DrizzleIndexedDBSyncItem,
|
|
39
|
+
} from "./collections/drizzle-indexeddb-collection";
|
|
41
40
|
|
|
42
41
|
// Standalone Collection (for use outside React)
|
|
43
42
|
export {
|
|
@@ -59,30 +58,3 @@ export {
|
|
|
59
58
|
useDrizzleIndexedDB,
|
|
60
59
|
type UseDrizzleIndexedDBContextReturn,
|
|
61
60
|
} from "./context/useDrizzleIndexedDB";
|
|
62
|
-
|
|
63
|
-
// IDB Proxy (for Chrome extension, messaging-based IDB access)
|
|
64
|
-
export {
|
|
65
|
-
// Types
|
|
66
|
-
type IDBProxyRequest,
|
|
67
|
-
type IDBProxyRequestBody,
|
|
68
|
-
type IDBProxyResponse,
|
|
69
|
-
type IDBProxySyncMessage,
|
|
70
|
-
generateRequestId,
|
|
71
|
-
generateClientId,
|
|
72
|
-
// Transport
|
|
73
|
-
type IDBProxyClientTransport,
|
|
74
|
-
type IDBProxyServerTransport,
|
|
75
|
-
createInMemoryTransport,
|
|
76
|
-
createMultiClientTransport,
|
|
77
|
-
// Client
|
|
78
|
-
IDBProxyClient,
|
|
79
|
-
createProxyIDbCreator,
|
|
80
|
-
type SyncHandler,
|
|
81
|
-
// Server
|
|
82
|
-
IDBProxyServer,
|
|
83
|
-
createProxyServer,
|
|
84
|
-
type IDBProxyServerOptions,
|
|
85
|
-
// Sync adapter (connects proxy sync to collection)
|
|
86
|
-
createCollectionSyncHandler,
|
|
87
|
-
combineSyncHandlers,
|
|
88
|
-
} from "./proxy";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { KeyRangeSpec } from "@firtoz/idb-collections";
|
|
1
2
|
import type {
|
|
2
3
|
IDBDatabaseLike,
|
|
3
4
|
IDBCreator,
|
|
@@ -5,7 +6,6 @@ import type {
|
|
|
5
6
|
IndexInfo,
|
|
6
7
|
CreateStoreOptions,
|
|
7
8
|
CreateIndexOptions,
|
|
8
|
-
KeyRangeSpec,
|
|
9
9
|
} from "./idb-types";
|
|
10
10
|
import type { IDBInterceptor } from "./idb-interceptor";
|
|
11
11
|
import { defaultIDBCreator } from "./native-idb-database";
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { exhaustiveGuard } from "@firtoz/maybe-error";
|
|
2
|
+
import type { KeyRangeSpec } from "@firtoz/idb-collections";
|
|
1
3
|
import type {
|
|
2
4
|
IDBDatabaseLike,
|
|
3
5
|
IDBCreator,
|
|
@@ -5,7 +7,6 @@ import type {
|
|
|
5
7
|
IndexInfo,
|
|
6
8
|
CreateStoreOptions,
|
|
7
9
|
CreateIndexOptions,
|
|
8
|
-
KeyRangeSpec,
|
|
9
10
|
} from "./idb-types";
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -26,6 +27,8 @@ function createKeyRange(spec: KeyRangeSpec): IDBKeyRange {
|
|
|
26
27
|
spec.lowerOpen,
|
|
27
28
|
spec.upperOpen,
|
|
28
29
|
);
|
|
30
|
+
default:
|
|
31
|
+
exhaustiveGuard(spec.type);
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
34
|
|