@firtoz/drizzle-indexeddb 0.6.1 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @firtoz/drizzle-indexeddb
2
2
 
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [`3c7ce1d`](https://github.com/firtoz/fullstack-toolkit/commit/3c7ce1dbca5c5396386db9927ae7f5e19a562cf6) Thanks [@firtoz](https://github.com/firtoz)! - **IndexedDB proxy removed**
8
+
9
+ The IDB proxy layer (proxy client/server, transport, sync adapter) has been removed. Provider no longer exposes proxy sync; use native IndexedDB only.
10
+
11
+ **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.
12
+
13
+ **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.
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [[`3c7ce1d`](https://github.com/firtoz/fullstack-toolkit/commit/3c7ce1dbca5c5396386db9927ae7f5e19a562cf6), [`3c7ce1d`](https://github.com/firtoz/fullstack-toolkit/commit/3c7ce1dbca5c5396386db9927ae7f5e19a562cf6)]:
18
+ - @firtoz/db-helpers@1.0.0
19
+ - @firtoz/drizzle-utils@1.0.0
20
+
21
+ ## 0.6.2
22
+
23
+ ### Patch Changes
24
+
25
+ - [`ec365af`](https://github.com/firtoz/fullstack-toolkit/commit/ec365af8c17bcd7efc2b0cf9b3bed5225b853e72) Thanks [@firtoz](https://github.com/firtoz)! - Update dependencies
26
+
27
+ - Updated dependencies [[`ec365af`](https://github.com/firtoz/fullstack-toolkit/commit/ec365af8c17bcd7efc2b0cf9b3bed5225b853e72)]:
28
+ - @firtoz/drizzle-utils@0.3.3
29
+
3
30
  ## 0.6.1
4
31
 
5
32
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firtoz/drizzle-indexeddb",
3
- "version": "0.6.1",
3
+ "version": "1.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,25 @@
68
68
  "access": "public"
69
69
  },
70
70
  "peerDependencies": {
71
- "@firtoz/drizzle-utils": ">=0.3.2",
72
- "@tanstack/db": ">=0.5.23",
71
+ "@firtoz/drizzle-utils": ">=1.0.0",
72
+ "@tanstack/db": ">=0.5.26",
73
73
  "drizzle-orm": ">=0.45.1",
74
74
  "drizzle-valibot": ">=0.4.0",
75
- "react": ">=19.2.3",
75
+ "react": ">=19.2.4",
76
76
  "valibot": ">=1.0.0"
77
77
  },
78
78
  "devDependencies": {
79
- "@firtoz/drizzle-utils": "^0.3.2",
80
- "@tanstack/db": "^0.5.23",
81
- "@types/react": "^19.2.8",
79
+ "@firtoz/drizzle-utils": "^1.0.0",
80
+ "@tanstack/db": "^0.5.26",
81
+ "@types/react": "^19.2.14",
82
82
  "drizzle-orm": "^0.45.1",
83
83
  "drizzle-valibot": "^0.4.2",
84
- "react": "^19.2.3",
84
+ "react": "^19.2.4",
85
85
  "valibot": "^1.2.0"
86
86
  },
87
87
  "dependencies": {
88
- "citty": "^0.2.0"
88
+ "@firtoz/db-helpers": "^1.0.0",
89
+ "@firtoz/maybe-error": "^1.5.2",
90
+ "citty": "^0.2.1"
89
91
  }
90
92
  }
@@ -65,100 +65,103 @@ export function evaluateExpression(
65
65
  expression: IR.BasicExpression,
66
66
  item: Record<string, unknown>,
67
67
  ): boolean {
68
- if (expression.type === "ref") {
69
- const propRef = expression as IR.PropRef;
70
- const columnName = propRef.path[propRef.path.length - 1];
71
- return item[columnName as string] !== undefined;
72
- }
73
-
74
- if (expression.type === "val") {
75
- const value = expression as IR.Value;
76
- return !!value.value;
77
- }
78
-
79
- if (expression.type === "func") {
80
- const func = expression as IR.Func;
81
-
82
- switch (func.name) {
83
- case "eq": {
84
- const left = getExpressionValue(func.args[0], item);
85
- const right = getExpressionValue(func.args[1], item);
86
- return left === right;
87
- }
88
- case "ne": {
89
- const left = getExpressionValue(func.args[0], item);
90
- const right = getExpressionValue(func.args[1], item);
91
- return left !== right;
92
- }
93
- case "gt": {
94
- const left = getExpressionValue(func.args[0], item);
95
- const right = getExpressionValue(func.args[1], item);
96
- return left > right;
97
- }
98
- case "gte": {
99
- const left = getExpressionValue(func.args[0], item);
100
- const right = getExpressionValue(func.args[1], item);
101
- return left >= right;
102
- }
103
- case "lt": {
104
- const left = getExpressionValue(func.args[0], item);
105
- const right = getExpressionValue(func.args[1], item);
106
- return left < right;
107
- }
108
- case "lte": {
109
- const left = getExpressionValue(func.args[0], item);
110
- const right = getExpressionValue(func.args[1], item);
111
- return left <= right;
112
- }
113
- case "and": {
114
- return func.args.every((arg) => evaluateExpression(arg, item));
115
- }
116
- case "or": {
117
- return func.args.some((arg) => evaluateExpression(arg, item));
118
- }
119
- case "not": {
120
- return !evaluateExpression(func.args[0], item);
121
- }
122
- case "isNull": {
123
- const value = getExpressionValue(func.args[0], item);
124
- return value === null || value === undefined;
125
- }
126
- case "isNotNull": {
127
- const value = getExpressionValue(func.args[0], item);
128
- return value !== null && value !== undefined;
129
- }
130
- case "like": {
131
- const left = String(getExpressionValue(func.args[0], item));
132
- const right = String(getExpressionValue(func.args[1], item));
133
- // Convert SQL LIKE pattern to regex (case-sensitive)
134
- const pattern = right.replace(/%/g, ".*").replace(/_/g, ".");
135
- return new RegExp(`^${pattern}$`).test(left);
136
- }
137
- case "ilike": {
138
- const left = String(getExpressionValue(func.args[0], item));
139
- const right = String(getExpressionValue(func.args[1], item));
140
- // Convert SQL ILIKE pattern to regex (case-insensitive)
141
- const pattern = right.replace(/%/g, ".*").replace(/_/g, ".");
142
- return new RegExp(`^${pattern}$`, "i").test(left);
143
- }
144
- case "in": {
145
- const left = getExpressionValue(func.args[0], item);
146
- const right = getExpressionValue(func.args[1], item);
147
- // Check if left value is in the right array
148
- return Array.isArray(right) && right.includes(left);
149
- }
150
- case "isUndefined": {
151
- const value = getExpressionValue(func.args[0], item);
152
- return value === null || value === undefined;
68
+ switch (expression.type) {
69
+ case "ref": {
70
+ const propRef = expression;
71
+ const columnName = propRef.path[propRef.path.length - 1];
72
+ return item[columnName as string] !== undefined;
73
+ }
74
+ case "val": {
75
+ const value = expression;
76
+ return !!value.value;
77
+ }
78
+ case "func": {
79
+ const func = expression;
80
+
81
+ switch (func.name) {
82
+ case "eq": {
83
+ const left = getExpressionValue(func.args[0], item);
84
+ const right = getExpressionValue(func.args[1], item);
85
+ return left === right;
86
+ }
87
+ case "ne": {
88
+ const left = getExpressionValue(func.args[0], item);
89
+ const right = getExpressionValue(func.args[1], item);
90
+ return left !== right;
91
+ }
92
+ case "gt": {
93
+ const left = getExpressionValue(func.args[0], item);
94
+ const right = getExpressionValue(func.args[1], item);
95
+ return left > right;
96
+ }
97
+ case "gte": {
98
+ const left = getExpressionValue(func.args[0], item);
99
+ const right = getExpressionValue(func.args[1], item);
100
+ return left >= right;
101
+ }
102
+ case "lt": {
103
+ const left = getExpressionValue(func.args[0], item);
104
+ const right = getExpressionValue(func.args[1], item);
105
+ return left < right;
106
+ }
107
+ case "lte": {
108
+ const left = getExpressionValue(func.args[0], item);
109
+ const right = getExpressionValue(func.args[1], item);
110
+ return left <= right;
111
+ }
112
+ case "and": {
113
+ return func.args.every((arg) => evaluateExpression(arg, item));
114
+ }
115
+ case "or": {
116
+ return func.args.some((arg) => evaluateExpression(arg, item));
117
+ }
118
+ case "not": {
119
+ return !evaluateExpression(func.args[0], item);
120
+ }
121
+ case "isNull": {
122
+ const value = getExpressionValue(func.args[0], item);
123
+ return value === null || value === undefined;
124
+ }
125
+ case "isNotNull": {
126
+ const value = getExpressionValue(func.args[0], item);
127
+ return value !== null && value !== undefined;
128
+ }
129
+ case "like": {
130
+ const left = String(getExpressionValue(func.args[0], item));
131
+ const right = String(getExpressionValue(func.args[1], item));
132
+ // Convert SQL LIKE pattern to regex (case-sensitive)
133
+ const pattern = right.replace(/%/g, ".*").replace(/_/g, ".");
134
+ return new RegExp(`^${pattern}$`).test(left);
135
+ }
136
+ case "ilike": {
137
+ const left = String(getExpressionValue(func.args[0], item));
138
+ const right = String(getExpressionValue(func.args[1], item));
139
+ // Convert SQL ILIKE pattern to regex (case-insensitive)
140
+ const pattern = right.replace(/%/g, ".*").replace(/_/g, ".");
141
+ return new RegExp(`^${pattern}$`, "i").test(left);
142
+ }
143
+ case "in": {
144
+ const left = getExpressionValue(func.args[0], item);
145
+ const right = getExpressionValue(func.args[1], item);
146
+ // Check if left value is in the right array
147
+ return Array.isArray(right) && right.includes(left);
148
+ }
149
+ case "isUndefined": {
150
+ const value = getExpressionValue(func.args[0], item);
151
+ return value === null || value === undefined;
152
+ }
153
+ default:
154
+ throw new Error(`Unsupported function: ${func.name}`);
153
155
  }
154
- default:
155
- throw new Error(`Unsupported function: ${func.name}`);
156
+ }
157
+ default: {
158
+ const _ex: never = expression;
159
+ void _ex;
160
+ throw new Error(
161
+ `Unsupported expression type: ${(expression as { type: string }).type}`,
162
+ );
156
163
  }
157
164
  }
158
-
159
- throw new Error(
160
- `Unsupported expression type: ${(expression as { type: string }).type}`,
161
- );
162
165
  }
163
166
 
164
167
  /**
@@ -170,18 +173,26 @@ export function getExpressionValue(
170
173
  item: Record<string, unknown>,
171
174
  // biome-ignore lint/suspicious/noExplicitAny: We need any here for dynamic values
172
175
  ): any {
173
- if (expression.type === "ref") {
174
- const propRef = expression as IR.PropRef;
175
- const columnName = propRef.path[propRef.path.length - 1];
176
- return item[columnName as string];
177
- }
178
-
179
- if (expression.type === "val") {
180
- const value = expression as IR.Value;
181
- return value.value;
176
+ switch (expression.type) {
177
+ case "ref": {
178
+ const propRef = expression;
179
+ const columnName = propRef.path[propRef.path.length - 1];
180
+ return item[columnName as string];
181
+ }
182
+ case "val": {
183
+ const value = expression;
184
+ return value.value;
185
+ }
186
+ case "func":
187
+ throw new Error("Cannot get value from func expression");
188
+ default: {
189
+ const _ex: never = expression;
190
+ void _ex;
191
+ throw new Error(
192
+ `Cannot get value from expression type: ${(expression as { type: string }).type}`,
193
+ );
194
+ }
182
195
  }
183
-
184
- throw new Error(`Cannot get value from expression type: ${expression.type}`);
185
196
  }
186
197
 
187
198
  /**
@@ -19,13 +19,13 @@ import {
19
19
  indexedDBCollectionOptions,
20
20
  type IndexedDBCollectionConfig,
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);
@@ -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 (
@@ -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/index.ts CHANGED
@@ -59,30 +59,3 @@ export {
59
59
  useDrizzleIndexedDB,
60
60
  type UseDrizzleIndexedDBContextReturn,
61
61
  } 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 { exhaustiveGuard } from "@firtoz/maybe-error";
1
2
  import type {
2
3
  IDBDatabaseLike,
3
4
  IDBCreator,
@@ -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
 
@@ -8,12 +8,8 @@ import {
8
8
  type WritableDeep,
9
9
  } from "@tanstack/db";
10
10
  import type { Table } from "drizzle-orm";
11
- import type {
12
- IdOf,
13
- InsertSchema,
14
- SelectSchema,
15
- CollectionUtils,
16
- } from "@firtoz/drizzle-utils";
11
+ import type { CollectionUtils } from "@firtoz/db-helpers";
12
+ import type { IdOf, InsertSchema, SelectSchema } from "@firtoz/drizzle-utils";
17
13
  import {
18
14
  indexedDBCollectionOptions,
19
15
  type IndexedDBCollectionConfig,
@@ -151,7 +147,7 @@ export interface StandaloneCollection<TTable extends Table> {
151
147
  truncate(): Promise<void>;
152
148
 
153
149
  /**
154
- * Access to collection utils (truncate, pushExternalSync)
150
+ * Access to collection utils (truncate, receiveSync)
155
151
  */
156
152
  utils: CollectionUtils<ItemType<TTable>>;
157
153