@firtoz/drizzle-sqlite-wasm 1.1.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +21 -4
  3. package/dist/chunk-7TDQNWT6.js +181 -0
  4. package/dist/chunk-7TDQNWT6.js.map +1 -0
  5. package/dist/{chunk-FRONXNEA.js → chunk-BQBL6E44.js} +2 -2
  6. package/dist/chunk-BQBL6E44.js.map +1 -0
  7. package/dist/{chunk-H2F2HZ2A.js → chunk-EQOHJW3Q.js} +3 -3
  8. package/dist/{chunk-H2F2HZ2A.js.map → chunk-EQOHJW3Q.js.map} +1 -1
  9. package/dist/{chunk-7JJHY44Q.js → chunk-NNPU7YTX.js} +3 -3
  10. package/dist/{chunk-7JJHY44Q.js.map → chunk-NNPU7YTX.js.map} +1 -1
  11. package/dist/{chunk-WFFFP6DB.js → chunk-QOFRLODK.js} +74 -19
  12. package/dist/chunk-QOFRLODK.js.map +1 -0
  13. package/dist/{chunk-BZVMUTJ7.js → chunk-VLFDMAFH.js} +2 -2
  14. package/dist/chunk-VLFDMAFH.js.map +1 -0
  15. package/dist/collections/sqlite-collection.d.ts +0 -1
  16. package/dist/collections/sqlite-collection.js +1 -1
  17. package/dist/collections/synced-sqlite-collection.js +2 -2
  18. package/dist/context/DrizzleSqliteProvider.d.ts +46 -4
  19. package/dist/context/DrizzleSqliteProvider.js +4 -4
  20. package/dist/context/useDrizzleSqlite.d.ts +4 -1
  21. package/dist/context/useDrizzleSqlite.js +5 -5
  22. package/dist/drizzle/worker.js +1 -1
  23. package/dist/hooks/useDrizzleSqliteDb.d.ts +35 -7
  24. package/dist/hooks/useDrizzleSqliteDb.js +2 -2
  25. package/dist/index.d.ts +3 -3
  26. package/dist/index.js +6 -7
  27. package/dist/index.js.map +1 -1
  28. package/dist/worker/schema.d.ts +4 -4
  29. package/dist/worker/sqlite.worker.js +1 -1
  30. package/dist/worker/sqlite.worker.js.map +1 -1
  31. package/package.json +5 -5
  32. package/src/collections/sqlite-collection.ts +0 -2
  33. package/src/drizzle/worker.ts +1 -4
  34. package/src/hooks/useDrizzleSqliteDb.ts +133 -15
  35. package/src/index.ts +9 -12
  36. package/src/worker/sqlite.worker.ts +4 -1
  37. package/dist/chunk-AEYHRJVN.js +0 -130
  38. package/dist/chunk-AEYHRJVN.js.map +0 -1
  39. package/dist/chunk-BZVMUTJ7.js.map +0 -1
  40. package/dist/chunk-FRONXNEA.js.map +0 -1
  41. package/dist/chunk-WFFFP6DB.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import { useEffect, useMemo, useRef, useState } from "react";
2
+ import type { SqliteRemoteDatabase } from "drizzle-orm/sqlite-proxy";
2
3
  import {
3
4
  customSqliteMigrate,
4
5
  type DurableSqliteMigrationConfig,
@@ -12,9 +13,86 @@ import {
12
13
  initializeSqliteWorker,
13
14
  isSqliteWorkerInitialized,
14
15
  } from "../worker/global-manager";
15
- import type { SQLInterceptor } from "../collections/sqlite-collection";
16
+ import type { SQLInterceptor } from "@firtoz/drizzle-utils";
16
17
  import type { SqliteWasmWorkerOpenOptions } from "../worker/sqlite-open-options";
17
18
 
19
+ /**
20
+ * `useEffect` can run in Node (e.g. unit tests) with no `window`. We only install the no-op
21
+ * DB stub there when a test runner is active — not for arbitrary headless Node usage.
22
+ *
23
+ * Playwright E2E is irrelevant here: the app under test runs in a real browser (`window` exists),
24
+ * so the normal client path is used, not this stub.
25
+ */
26
+ function isNodeTestRuntime(): boolean {
27
+ if (typeof window !== "undefined") {
28
+ return false;
29
+ }
30
+ if (typeof process === "undefined" || typeof process.env === "undefined") {
31
+ return false;
32
+ }
33
+ const e = process.env;
34
+ if (e.NODE_ENV === "test") {
35
+ return true;
36
+ }
37
+ // Vitest sets this; avoids relying on NODE_ENV alone
38
+ if (e.VITEST !== undefined) {
39
+ return true;
40
+ }
41
+ return false;
42
+ }
43
+
44
+ /**
45
+ * `connecting` — no worker client yet, or migrations not finished.
46
+ * `ready` — migrations applied, `readyPromise` resolved.
47
+ * `error` — migration (or init) failed; see `sessionError`.
48
+ */
49
+ export type DrizzleSqliteSessionStatus = "connecting" | "ready" | "error";
50
+
51
+ /**
52
+ * Error payload when the hook reports `sessionStatus: "error"`.
53
+ * Add further `| { kind: … }` members later; discriminate on `kind` in UI or logging.
54
+ */
55
+ export type DrizzleSqliteSessionError = {
56
+ kind: "migration_failed";
57
+ /** String for display or logs */
58
+ message: string;
59
+ /** The value that was thrown (often an `Error`) */
60
+ original: unknown;
61
+ };
62
+
63
+ /** Normalises `catch` bindings so UI can rely on a stable shape. */
64
+ export function toDrizzleSqliteSessionError(
65
+ caught: unknown,
66
+ ): DrizzleSqliteSessionError {
67
+ const message =
68
+ caught instanceof Error
69
+ ? caught.message
70
+ : typeof caught === "string"
71
+ ? caught
72
+ : "Database error";
73
+ return { kind: "migration_failed", message, original: caught };
74
+ }
75
+
76
+ type InternalSessionState =
77
+ | { status: "connecting" | "ready"; error: null }
78
+ | { status: "error"; error: DrizzleSqliteSessionError };
79
+
80
+ export type UseDrizzleSqliteDbResult<TSchema extends Record<string, unknown>> =
81
+ | {
82
+ drizzle: SqliteRemoteDatabase<TSchema>;
83
+ readyPromise: Promise<void>;
84
+ sqliteClient: ISqliteWorkerClient | null;
85
+ sessionStatus: "connecting" | "ready";
86
+ sessionError: null;
87
+ }
88
+ | {
89
+ drizzle: SqliteRemoteDatabase<TSchema>;
90
+ readyPromise: Promise<void>;
91
+ sqliteClient: ISqliteWorkerClient | null;
92
+ sessionStatus: "error";
93
+ sessionError: DrizzleSqliteSessionError;
94
+ };
95
+
18
96
  export const useDrizzleSqliteDb = <TSchema extends Record<string, unknown>>(
19
97
  WorkerConstructor: new () => Worker,
20
98
  dbName: string,
@@ -28,34 +106,49 @@ export const useDrizzleSqliteDb = <TSchema extends Record<string, unknown>>(
28
106
  * Ignored if that database was already started (same global worker + dbName).
29
107
  */
30
108
  workerOpenOptions?: SqliteWasmWorkerOpenOptions,
31
- ) => {
109
+ ): UseDrizzleSqliteDbResult<TSchema> => {
32
110
  const resolveRef = useRef<null | (() => void)>(null);
33
111
  const rejectRef = useRef<null | ((error: unknown) => void)>(null);
112
+ // "ready" = migrations done and `readyPromise` resolved (or the rare Node `useEffect` stub).
113
+ // Start as "connecting" on every first paint so we do not show "ready" and then go backward
114
+ // to "connecting" when the first client `useEffect` runs.
115
+ const [internalSession, setInternalSession] = useState<InternalSessionState>(
116
+ () => ({ status: "connecting", error: null }),
117
+ );
34
118
  const [sqliteClient, setSqliteClient] = useState<ISqliteWorkerClient | null>(
35
119
  null,
36
120
  );
37
121
  const sqliteClientRef = useRef<ISqliteWorkerClient | null>(null);
38
122
 
123
+ /** New promise per logical DB open so a resolved session does not mask the next `dbName`. */
39
124
  const readyPromise = useMemo(() => {
40
125
  return new Promise<void>((resolve, reject) => {
41
126
  resolveRef.current = resolve;
42
127
  rejectRef.current = reject;
43
128
  });
44
- }, []);
129
+ }, [dbName]);
45
130
 
46
131
  // Initialize the global manager and get db instance
47
132
  useEffect(() => {
48
133
  if (typeof window === "undefined") {
49
- // SSR stub
50
- setSqliteClient({
51
- performRemoteCallback: () => {},
52
- checkpoint: () => Promise.resolve(),
53
- onStarted: () => {},
54
- terminate: () => {},
55
- });
134
+ if (isNodeTestRuntime()) {
135
+ // e.g. Vitest without jsdom: no worker; unlock `ready` + `readyPromise` for the test tree
136
+ setInternalSession({ status: "ready", error: null });
137
+ setSqliteClient({
138
+ performRemoteCallback: () => {},
139
+ checkpoint: () => Promise.resolve(),
140
+ onStarted: () => {},
141
+ terminate: () => {},
142
+ });
143
+ queueMicrotask(() => {
144
+ resolveRef.current?.();
145
+ });
146
+ }
56
147
  return;
57
148
  }
58
149
 
150
+ setInternalSession({ status: "connecting", error: null });
151
+
59
152
  let mounted = true;
60
153
 
61
154
  const init = async () => {
@@ -146,6 +239,9 @@ export const useDrizzleSqliteDb = <TSchema extends Record<string, unknown>>(
146
239
  }, [schema, dbName, !!interceptor]); // Only recreate if interceptor presence changes, not on every render
147
240
 
148
241
  useEffect(() => {
242
+ if (typeof window === "undefined") {
243
+ return;
244
+ }
149
245
  if (!sqliteClient) {
150
246
  if (debug) {
151
247
  console.log(`[DEBUG] ${dbName} - waiting for sqliteClient...`);
@@ -154,19 +250,41 @@ export const useDrizzleSqliteDb = <TSchema extends Record<string, unknown>>(
154
250
  }
155
251
 
156
252
  sqliteClient.onStarted(async () => {
253
+ if (typeof window === "undefined") {
254
+ return;
255
+ }
157
256
  try {
257
+ setInternalSession({ status: "connecting", error: null });
158
258
  await customSqliteMigrate(drizzle, migrations);
159
259
  resolveRef.current?.();
160
- } catch (error) {
161
- console.error(`Migration error for ${dbName}:`, error);
162
- rejectRef.current?.(error);
260
+ setInternalSession({ status: "ready", error: null });
261
+ } catch (caught) {
262
+ console.error(`Migration error for ${dbName}:`, caught);
263
+ const err = toDrizzleSqliteSessionError(caught);
264
+ setInternalSession({ status: "error", error: err });
265
+ rejectRef.current?.(caught);
163
266
  }
164
267
  });
165
268
 
166
269
  return () => {
167
270
  sqliteClient.terminate();
168
271
  };
169
- }, [sqliteClient, drizzle, migrations, dbName]);
272
+ }, [sqliteClient, drizzle, migrations, dbName, debug]);
170
273
 
171
- return { drizzle, readyPromise, sqliteClient };
274
+ if (internalSession.status === "error") {
275
+ return {
276
+ drizzle,
277
+ readyPromise,
278
+ sqliteClient,
279
+ sessionStatus: "error" as const,
280
+ sessionError: internalSession.error,
281
+ };
282
+ }
283
+ return {
284
+ drizzle,
285
+ readyPromise,
286
+ sqliteClient,
287
+ sessionStatus: internalSession.status,
288
+ sessionError: null,
289
+ };
172
290
  };
package/src/index.ts CHANGED
@@ -1,21 +1,18 @@
1
1
  export { drizzleSqliteWasm } from "./drizzle/direct";
2
2
  export {
3
- sqliteCollectionOptions as drizzleCollectionOptions,
3
+ sqliteCollectionOptions,
4
4
  type SqliteCollectionConfig,
5
- type SQLOperation,
6
- type SQLInterceptor,
7
5
  } from "./collections/sqlite-collection";
8
6
  export { createSyncedSqliteCollection } from "./collections/synced-sqlite-collection";
9
- export { syncableTable } from "@firtoz/drizzle-utils";
10
- export { makeId } from "@firtoz/drizzle-utils";
7
+ export {
8
+ toDrizzleSqliteSessionError,
9
+ useDrizzleSqliteDb,
10
+ } from "./hooks/useDrizzleSqliteDb";
11
11
  export type {
12
- IdOf,
13
- TableId,
14
- Branded,
15
- SelectSchema,
16
- InsertSchema,
17
- } from "@firtoz/drizzle-utils";
18
- export { useDrizzleSqliteDb } from "./hooks/useDrizzleSqliteDb";
12
+ DrizzleSqliteSessionError,
13
+ DrizzleSqliteSessionStatus,
14
+ UseDrizzleSqliteDbResult,
15
+ } from "./hooks/useDrizzleSqliteDb";
19
16
  // SQLite WASM Provider
20
17
  export {
21
18
  DrizzleSqliteProvider,
@@ -165,7 +165,10 @@ class SqliteWorkerHelper extends WorkerHelper<
165
165
  const dbFileName = `${dbName}.sqlite3`;
166
166
  let db: Database;
167
167
 
168
- if ("opfs" in sqlite3) {
168
+ // After full init, sqlite-wasm removes `sqlite3.opfs` in non-test builds
169
+ // (`asyncPostInit`), so `"opfs" in sqlite3` is a false negative. Prefer the
170
+ // OpfsDb constructor installed by the OPFS VFS initializer.
171
+ if (typeof sqlite3.oo1.OpfsDb === "function") {
169
172
  db = new sqlite3.oo1.OpfsDb(dbFileName);
170
173
  this.log("OPFS database created:", db.filename);
171
174
  this.applyOpenPragmas(db, openOptions);
@@ -1,130 +0,0 @@
1
- import { useDrizzleSqliteDb } from './chunk-WFFFP6DB.js';
2
- import { sqliteCollectionOptions } from './chunk-BZVMUTJ7.js';
3
- import { createContext, useMemo, useCallback, useEffect } from 'react';
4
- import { createCollection } from '@tanstack/db';
5
- import { jsx } from 'react/jsx-runtime';
6
-
7
- var DrizzleSqliteContext = (
8
- // biome-ignore lint/suspicious/noExplicitAny: Context needs to accept any schema type
9
- createContext(null)
10
- );
11
- function DrizzleSqliteProvider({
12
- children,
13
- worker,
14
- dbName,
15
- schema,
16
- migrations,
17
- debug,
18
- enableCheckpoint = false,
19
- syncMode = "eager",
20
- interceptor,
21
- workerOpenOptions
22
- }) {
23
- const { drizzle, readyPromise, sqliteClient } = useDrizzleSqliteDb(
24
- worker,
25
- dbName,
26
- schema,
27
- migrations,
28
- debug,
29
- interceptor,
30
- // Pass interceptor to log ALL Drizzle queries
31
- workerOpenOptions
32
- );
33
- const collections = useMemo(
34
- () => /* @__PURE__ */ new Map(),
35
- []
36
- );
37
- const getCollection = useCallback(
38
- (tableName) => {
39
- const cacheKey = tableName;
40
- if (!collections.has(cacheKey)) {
41
- const options = sqliteCollectionOptions({
42
- drizzle,
43
- tableName,
44
- readyPromise,
45
- syncMode,
46
- checkpoint: enableCheckpoint && sqliteClient ? () => sqliteClient.checkpoint() : void 0,
47
- interceptor,
48
- debug
49
- });
50
- const collection = createCollection(options);
51
- collections.set(cacheKey, {
52
- collection,
53
- refCount: 0
54
- });
55
- }
56
- return collections.get(cacheKey).collection;
57
- },
58
- [
59
- drizzle,
60
- collections,
61
- schema,
62
- readyPromise,
63
- sqliteClient,
64
- enableCheckpoint,
65
- syncMode,
66
- interceptor,
67
- debug
68
- ]
69
- );
70
- const incrementRefCount = useCallback(
71
- (tableName) => {
72
- const entry = collections.get(tableName);
73
- if (entry) {
74
- entry.refCount++;
75
- }
76
- },
77
- [collections]
78
- );
79
- const decrementRefCount = useCallback(
80
- (tableName) => {
81
- const entry = collections.get(tableName);
82
- if (entry) {
83
- entry.refCount--;
84
- if (entry.refCount <= 0) {
85
- collections.delete(tableName);
86
- }
87
- }
88
- },
89
- [collections]
90
- );
91
- const contextValue = useMemo(
92
- () => ({
93
- drizzle,
94
- readyPromise,
95
- getCollection,
96
- incrementRefCount,
97
- decrementRefCount
98
- }),
99
- [
100
- drizzle,
101
- readyPromise,
102
- getCollection,
103
- incrementRefCount,
104
- decrementRefCount
105
- ]
106
- );
107
- return /* @__PURE__ */ jsx(DrizzleSqliteContext.Provider, { value: contextValue, children });
108
- }
109
- function useSqliteCollection(context, tableName) {
110
- const { collection, unsubscribe } = useMemo(() => {
111
- const col = context.getCollection(tableName);
112
- context.incrementRefCount(tableName);
113
- return {
114
- collection: col,
115
- unsubscribe: () => {
116
- context.decrementRefCount(tableName);
117
- }
118
- };
119
- }, [context, tableName]);
120
- useEffect(() => {
121
- return () => {
122
- unsubscribe();
123
- };
124
- }, [unsubscribe]);
125
- return collection;
126
- }
127
-
128
- export { DrizzleSqliteContext, DrizzleSqliteProvider, useSqliteCollection };
129
- //# sourceMappingURL=chunk-AEYHRJVN.js.map
130
- //# sourceMappingURL=chunk-AEYHRJVN.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/context/DrizzleSqliteProvider.tsx"],"names":[],"mappings":"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","file":"chunk-AEYHRJVN.js","sourcesContent":["import type { PropsWithChildren } from \"react\";\nimport { createContext, useMemo, useCallback, useEffect } from \"react\";\nimport type { SqliteRemoteDatabase } from \"drizzle-orm/sqlite-proxy\";\nimport {\n\tcreateCollection,\n\ttype Collection,\n\ttype InferSchemaOutput,\n\ttype UtilsRecord,\n} from \"@tanstack/db\";\nimport {\n\ttype AnyDrizzleDatabase,\n\ttype ValidTableNames,\n\ttype DrizzleSchema,\n\tsqliteCollectionOptions,\n\ttype SQLInterceptor,\n} from \"../collections/sqlite-collection\";\nimport { useDrizzleSqliteDb } from \"../hooks/useDrizzleSqliteDb\";\nimport type { DurableSqliteMigrationConfig } from \"../migration/migrator\";\nimport type { SqliteWasmWorkerOpenOptions } from \"../worker/sqlite-open-options\";\nimport type {\n\tIdOf,\n\tGetTableFromSchema,\n\tInferCollectionFromTable,\n} from \"@firtoz/drizzle-utils\";\n\ninterface CollectionCacheEntry {\n\t// biome-ignore lint/suspicious/noExplicitAny: Cache needs to store collections of various types\n\tcollection: Collection<any, string>;\n\trefCount: number;\n}\n\ntype SqliteCollection<\n\tTSchema extends Record<string, unknown>,\n\tTTableName extends string & ValidTableNames<TSchema>,\n> = Collection<\n\tInferSchemaOutput<GetTableFromSchema<TSchema, TTableName>[\"$inferSelect\"]>,\n\tIdOf<GetTableFromSchema<TSchema, TTableName>>,\n\t// biome-ignore lint/suspicious/noExplicitAny: We need to use any here to match the Collection type\n\tany,\n\t// biome-ignore lint/suspicious/noExplicitAny: We need to use any here to match the Collection type\n\tany,\n\tOmit<GetTableFromSchema<TSchema, TTableName>[\"$inferInsert\"], \"id\"> & {\n\t\tid?: IdOf<GetTableFromSchema<TSchema, TTableName>>;\n\t}\n>;\n\nexport type DrizzleSqliteContextValue<TSchema extends Record<string, unknown>> =\n\t{\n\t\tdrizzle: SqliteRemoteDatabase<TSchema>;\n\t\treadyPromise: Promise<void>;\n\t\tgetCollection: <TTableName extends string & ValidTableNames<TSchema>>(\n\t\t\ttableName: TTableName,\n\t\t) => SqliteCollection<TSchema, TTableName>;\n\t\tincrementRefCount: (tableName: string) => void;\n\t\tdecrementRefCount: (tableName: string) => void;\n\t};\n\nexport const DrizzleSqliteContext =\n\t// biome-ignore lint/suspicious/noExplicitAny: Context needs to accept any schema type\n\tcreateContext<DrizzleSqliteContextValue<any> | null>(null);\n\ntype DrizzleSqliteProviderProps<TSchema extends Record<string, unknown>> =\n\tPropsWithChildren<{\n\t\tworker: new () => Worker;\n\t\tdbName: string;\n\t\tschema: TSchema;\n\t\tmigrations: DurableSqliteMigrationConfig;\n\t\tdebug?: boolean;\n\t\tenableCheckpoint?: boolean;\n\t\t/**\n\t\t * Sync mode: 'eager' (immediate) or 'on-demand' (lazy)\n\t\t */\n\t\tsyncMode?: \"eager\" | \"on-demand\";\n\t\t/**\n\t\t * Optional interceptor for tracking SQLite operations (for testing/debugging)\n\t\t */\n\t\tinterceptor?: SQLInterceptor;\n\t\t/**\n\t\t * Worker DB pragmas on first open of `dbName` this session (see `useDrizzleSqliteDb`).\n\t\t */\n\t\tworkerOpenOptions?: SqliteWasmWorkerOpenOptions;\n\t}>;\n\nexport function DrizzleSqliteProvider<TSchema extends Record<string, unknown>>({\n\tchildren,\n\tworker,\n\tdbName,\n\tschema,\n\tmigrations,\n\tdebug,\n\tenableCheckpoint = false,\n\tsyncMode = \"eager\",\n\tinterceptor,\n\tworkerOpenOptions,\n}: DrizzleSqliteProviderProps<TSchema>) {\n\tconst { drizzle, readyPromise, sqliteClient } = useDrizzleSqliteDb(\n\t\tworker,\n\t\tdbName,\n\t\tschema,\n\t\tmigrations,\n\t\tdebug,\n\t\tinterceptor, // Pass interceptor to log ALL Drizzle queries\n\t\tworkerOpenOptions,\n\t);\n\n\t// Collection cache with ref counting\n\tconst collections = useMemo<Map<string, CollectionCacheEntry>>(\n\t\t() => new Map(),\n\t\t[],\n\t);\n\n\tconst getCollection = useCallback(\n\t\t<TTableName extends string & ValidTableNames<TSchema>>(\n\t\t\ttableName: TTableName,\n\t\t): SqliteCollection<TSchema, TTableName> => {\n\t\t\tconst cacheKey = tableName;\n\n\t\t\t// Check if collection already exists in cache\n\t\t\tif (!collections.has(cacheKey)) {\n\t\t\t\t// Create new collection and cache it with ref count 0\n\t\t\t\tconst options = sqliteCollectionOptions({\n\t\t\t\t\tdrizzle,\n\t\t\t\t\ttableName: tableName as string &\n\t\t\t\t\t\tValidTableNames<DrizzleSchema<AnyDrizzleDatabase>>,\n\t\t\t\t\treadyPromise,\n\t\t\t\t\tsyncMode,\n\t\t\t\t\tcheckpoint:\n\t\t\t\t\t\tenableCheckpoint && sqliteClient\n\t\t\t\t\t\t\t? () => sqliteClient.checkpoint()\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\tinterceptor,\n\t\t\t\t\tdebug,\n\t\t\t\t});\n\t\t\t\t// biome-ignore lint/suspicious/noExplicitAny: Table type degenerates through AnyDrizzleDatabase; collection is re-typed on cache retrieval\n\t\t\t\tconst collection = createCollection(options as any) as Collection<\n\t\t\t\t\tRecord<string, unknown>,\n\t\t\t\t\tstring,\n\t\t\t\t\tUtilsRecord\n\t\t\t\t>;\n\t\t\t\tcollections.set(cacheKey, {\n\t\t\t\t\tcollection,\n\t\t\t\t\trefCount: 0,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// biome-ignore lint/style/noNonNullAssertion: We just ensured the collection exists\n\t\t\treturn collections.get(cacheKey)!\n\t\t\t\t.collection as unknown as SqliteCollection<TSchema, TTableName>;\n\t\t},\n\t\t[\n\t\t\tdrizzle,\n\t\t\tcollections,\n\t\t\tschema,\n\t\t\treadyPromise,\n\t\t\tsqliteClient,\n\t\t\tenableCheckpoint,\n\t\t\tsyncMode,\n\t\t\tinterceptor,\n\t\t\tdebug,\n\t\t],\n\t);\n\n\tconst incrementRefCount: DrizzleSqliteContextValue<TSchema>[\"incrementRefCount\"] =\n\t\tuseCallback(\n\t\t\t(tableName: string) => {\n\t\t\t\tconst entry = collections.get(tableName);\n\t\t\t\tif (entry) {\n\t\t\t\t\tentry.refCount++;\n\t\t\t\t}\n\t\t\t},\n\t\t\t[collections],\n\t\t);\n\n\tconst decrementRefCount: DrizzleSqliteContextValue<TSchema>[\"decrementRefCount\"] =\n\t\tuseCallback(\n\t\t\t(tableName: string) => {\n\t\t\t\tconst entry = collections.get(tableName);\n\t\t\t\tif (entry) {\n\t\t\t\t\tentry.refCount--;\n\n\t\t\t\t\t// If ref count reaches 0, remove from cache\n\t\t\t\t\tif (entry.refCount <= 0) {\n\t\t\t\t\t\tcollections.delete(tableName);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t[collections],\n\t\t);\n\n\tconst contextValue: DrizzleSqliteContextValue<TSchema> = useMemo(\n\t\t() => ({\n\t\t\tdrizzle,\n\t\t\treadyPromise,\n\t\t\tgetCollection,\n\t\t\tincrementRefCount,\n\t\t\tdecrementRefCount,\n\t\t}),\n\t\t[\n\t\t\tdrizzle,\n\t\t\treadyPromise,\n\t\t\tgetCollection,\n\t\t\tincrementRefCount,\n\t\t\tdecrementRefCount,\n\t\t],\n\t);\n\n\treturn (\n\t\t<DrizzleSqliteContext.Provider value={contextValue}>\n\t\t\t{children}\n\t\t</DrizzleSqliteContext.Provider>\n\t);\n}\n\n// Hook that components use to get a collection with automatic ref counting\nexport function useSqliteCollection<\n\tTSchema extends Record<string, unknown>,\n\tTTableName extends string & ValidTableNames<TSchema>,\n>(\n\tcontext: DrizzleSqliteContextValue<TSchema>,\n\ttableName: TTableName,\n): InferCollectionFromTable<GetTableFromSchema<TSchema, TTableName>> {\n\tconst { collection, unsubscribe } = useMemo(() => {\n\t\t// Get the collection and increment ref count\n\t\tconst col = context.getCollection(tableName);\n\t\tcontext.incrementRefCount(tableName);\n\n\t\t// Return collection and unsubscribe function\n\t\treturn {\n\t\t\tcollection: col,\n\t\t\tunsubscribe: () => {\n\t\t\t\tcontext.decrementRefCount(tableName);\n\t\t\t},\n\t\t};\n\t}, [context, tableName]);\n\n\t// Cleanup on unmount\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tunsubscribe();\n\t\t};\n\t}, [unsubscribe]);\n\n\treturn collection as unknown as InferCollectionFromTable<\n\t\tGetTableFromSchema<TSchema, TTableName>\n\t>;\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/collections/sqlite-collection.ts"],"names":[],"mappings":";;;AA+EO,SAAS,wBAKf,MAAA,EACiC;AACjC,EAAA,MAAM,YAAY,MAAA,CAAO,SAAA;AAGzB,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,OAAA,EAAS,CAAA,CAAE,WAAW,SAAS,CAAA;AAEpD,EAAA,MAAM,MAAA,GAAS,CACd,IAAA,KACmB,IAAA,CAA8B,EAAA;AAElD,EAAA,MAAM,UAAU,4BAAA,CAA6B;AAAA,IAC5C,SAAS,MAAA,CAAO,OAAA;AAAA,IAChB,KAAA;AAAA,IACA,WAAW,MAAA,CAAO,SAAA;AAAA,IAClB,OAAO,MAAA,CAAO,KAAA;AAAA,IACd,YAAY,MAAA,CAAO,UAAA;AAAA,IACnB,aAAa,MAAA,CAAO,WAAA;AAAA,IACpB,UAAA,EAAY;AAAA,GACZ,CAAA;AAED,EAAA,MAAM,cAAA,GAAyC;AAAA,IAC9C,KAAA;AAAA,IACA,cAAc,MAAA,CAAO,YAAA;AAAA,IACrB,UAAU,MAAA,CAAO,QAAA;AAAA,IACjB,OAAO,MAAA,CAAO,KAAA;AAAA,IACd,mBAAmB,CAAC,IAAA,KAAS,MAAA,CAAO,MAAA,CAAO,IAAI,CAAC;AAAA,GACjD;AAEA,EAAA,MAAM,UAAA,GAAa,kBAAA,CAAmB,cAAA,EAAgB,OAAO,CAAA;AAE7D,EAAA,MAAM,MAAA,GAAS,gCAAgC,KAAK,CAAA;AAEpD,EAAA,MAAM,mBAAmB,sBAAA,CAAuB;AAAA,IAC/C,MAAA;AAAA,IACA,MAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA,EAAU,MAAA,CAAO,KAAA,GACd,OAAO,MAAA,KAAW;AAClB,MAAA,OAAA,CAAQ,GAAA,CAAI,YAAY,MAAM,CAAA;AAE9B,MAAA,MAAM,UAAA,CAAW,SAAU,MAAM,CAAA;AAAA,IAClC,CAAA,GACC,MAAA;AAAA,IACH,QAAA,EAAU,MAAA,CAAO,KAAA,GACd,OAAO,MAAA,KAAW;AAClB,MAAA,OAAA,CAAQ,GAAA,CAAI,YAAY,MAAM,CAAA;AAE9B,MAAA,MAAM,UAAA,CAAW,SAAU,MAAM,CAAA;AAAA,IAClC,CAAA,GACC,MAAA;AAAA,IACH,QAAA,EAAU,MAAA,CAAO,KAAA,GACd,OAAO,MAAA,KAAW;AAClB,MAAA,OAAA,CAAQ,GAAA,CAAI,YAAY,MAAM,CAAA;AAE9B,MAAA,MAAM,UAAA,CAAW,SAAU,MAAM,CAAA;AAAA,IAClC,CAAA,GACC,MAAA;AAAA,IACH,UAAU,MAAA,CAAO;AAAA,GACjB,CAAA;AAED,EAAA,OAAO,gBAAA;AACR","file":"chunk-BZVMUTJ7.js","sourcesContent":["import type {\n\tInferSchemaOutput,\n\tSyncMode,\n\tCollectionConfig,\n} from \"@tanstack/db\";\nimport type { Table } from \"drizzle-orm\";\nimport type { BaseSQLiteDatabase } from \"drizzle-orm/sqlite-core\";\nimport type { CollectionUtils } from \"@firtoz/db-helpers\";\nimport type {\n\tSelectSchema,\n\tInsertToSelectSchema,\n\tTableWithRequiredFields,\n\tBaseSyncConfig,\n\tIdOf,\n} from \"@firtoz/drizzle-utils\";\nimport {\n\tcreateSyncFunction,\n\tcreateInsertSchemaWithIdDefault,\n\tcreateCollectionConfig,\n\tcreateSqliteTableSyncBackend,\n\ttype SQLOperation,\n\ttype SQLInterceptor,\n} from \"@firtoz/drizzle-utils\";\nexport type { SQLOperation, SQLInterceptor };\n\nexport type AnyDrizzleDatabase = BaseSQLiteDatabase<\n\t\"async\",\n\t// biome-ignore lint/suspicious/noExplicitAny: We really want to use any here.\n\tany,\n\tRecord<string, unknown>\n>;\n\nexport type DrizzleSchema<TDrizzle extends AnyDrizzleDatabase> =\n\tTDrizzle[\"_\"][\"fullSchema\"];\n\nexport interface DrizzleSqliteCollectionConfig<\n\tTDrizzle extends AnyDrizzleDatabase,\n\tTTableName extends ValidTableNames<DrizzleSchema<TDrizzle>>,\n> {\n\tdrizzle: TDrizzle;\n\ttableName: ValidTableNames<DrizzleSchema<TDrizzle>> extends never\n\t\t? {\n\t\t\t\t$error: \"The schema needs to include at least one table that uses the syncableTable function.\";\n\t\t\t}\n\t\t: TTableName;\n\treadyPromise: Promise<void>;\n\tsyncMode?: SyncMode;\n\t/**\n\t * Enable debug logging for query execution and mutations\n\t */\n\tdebug?: boolean;\n\t/**\n\t * Optional callback to checkpoint the database after mutations\n\t * This ensures WAL is flushed to the main database file for OPFS persistence\n\t */\n\tcheckpoint?: () => Promise<void>;\n\t/**\n\t * Optional interceptor for tracking SQLite operations (for testing/debugging)\n\t */\n\tinterceptor?: SQLInterceptor;\n}\n\nexport type ValidTableNames<TSchema extends Record<string, unknown>> = {\n\t[K in keyof TSchema]: TSchema[K] extends TableWithRequiredFields ? K : never;\n}[keyof TSchema];\n\nexport type SqliteCollectionConfig<TTable extends Table> = Omit<\n\tCollectionConfig<\n\t\tInferSchemaOutput<SelectSchema<TTable>>,\n\t\tIdOf<TTable>,\n\t\tInsertToSelectSchema<TTable>,\n\t\tCollectionUtils<InferSchemaOutput<SelectSchema<TTable>>>\n\t>,\n\t\"utils\"\n> & {\n\tschema: InsertToSelectSchema<TTable>;\n\tutils: CollectionUtils<InferSchemaOutput<SelectSchema<TTable>>>;\n};\n\nexport function sqliteCollectionOptions<\n\tconst TDrizzle extends AnyDrizzleDatabase,\n\tconst TTableName extends string & ValidTableNames<DrizzleSchema<TDrizzle>>,\n\tTTable extends DrizzleSchema<TDrizzle>[TTableName] & TableWithRequiredFields,\n>(\n\tconfig: DrizzleSqliteCollectionConfig<TDrizzle, TTableName>,\n): SqliteCollectionConfig<TTable> {\n\tconst tableName = config.tableName as string &\n\t\tValidTableNames<DrizzleSchema<TDrizzle>>;\n\n\tconst table = config.drizzle?._.fullSchema[tableName] as TTable;\n\n\tconst getKey = (\n\t\titem: InferSchemaOutput<SelectSchema<TTable>>,\n\t): IdOf<TTable> => (item as { id: IdOf<TTable> }).id;\n\n\tconst backend = createSqliteTableSyncBackend({\n\t\tdrizzle: config.drizzle,\n\t\ttable,\n\t\ttableName: config.tableName as string,\n\t\tdebug: config.debug,\n\t\tcheckpoint: config.checkpoint,\n\t\tinterceptor: config.interceptor,\n\t\tdriverMode: \"async\",\n\t});\n\n\tconst baseSyncConfig: BaseSyncConfig<TTable> = {\n\t\ttable,\n\t\treadyPromise: config.readyPromise,\n\t\tsyncMode: config.syncMode,\n\t\tdebug: config.debug,\n\t\tgetSyncPersistKey: (item) => String(getKey(item)),\n\t};\n\n\tconst syncResult = createSyncFunction(baseSyncConfig, backend);\n\n\tconst schema = createInsertSchemaWithIdDefault(table);\n\n\tconst collectionConfig = createCollectionConfig({\n\t\tschema,\n\t\tgetKey,\n\t\tsyncResult,\n\t\tonInsert: config.debug\n\t\t\t? async (params) => {\n\t\t\t\t\tconsole.log(\"onInsert\", params);\n\t\t\t\t\t// biome-ignore lint/style/noNonNullAssertion: onInsert is always defined in createSyncFunction\n\t\t\t\t\tawait syncResult.onInsert!(params);\n\t\t\t\t}\n\t\t\t: undefined,\n\t\tonUpdate: config.debug\n\t\t\t? async (params) => {\n\t\t\t\t\tconsole.log(\"onUpdate\", params);\n\t\t\t\t\t// biome-ignore lint/style/noNonNullAssertion: onUpdate is always defined in createSyncFunction\n\t\t\t\t\tawait syncResult.onUpdate!(params);\n\t\t\t\t}\n\t\t\t: undefined,\n\t\tonDelete: config.debug\n\t\t\t? async (params) => {\n\t\t\t\t\tconsole.log(\"onDelete\", params);\n\t\t\t\t\t// biome-ignore lint/style/noNonNullAssertion: onDelete is always defined in createSyncFunction\n\t\t\t\t\tawait syncResult.onDelete!(params);\n\t\t\t\t}\n\t\t\t: undefined,\n\t\tsyncMode: config.syncMode,\n\t});\n\n\treturn collectionConfig;\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/drizzle/worker.ts"],"names":["drizzleSqliteProxy"],"mappings":"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","file":"chunk-FRONXNEA.js","sourcesContent":["import type { DrizzleConfig } from \"drizzle-orm\";\nimport { drizzle as drizzleSqliteProxy } from \"drizzle-orm/sqlite-proxy\";\nimport type { ISqliteWorkerClient } from \"../worker/client\";\nimport type {\n\tSQLInterceptor,\n\tSQLOperation,\n} from \"../collections/sqlite-collection\";\n\nexport const drizzleSqliteWasmWorker = <\n\tTSchema extends Record<string, unknown> = Record<string, never>,\n>(\n\tclient: ISqliteWorkerClient,\n\tconfig: DrizzleConfig<TSchema> = {},\n) => {\n\treturn drizzleSqliteProxy<TSchema>(async (sql, params, method) => {\n\t\treturn new Promise<{ rows: unknown[] }>((resolve, reject) => {\n\t\t\tclient.performRemoteCallback(\n\t\t\t\t{\n\t\t\t\t\tsql,\n\t\t\t\t\tparams,\n\t\t\t\t\tmethod,\n\t\t\t\t},\n\t\t\t\tresolve,\n\t\t\t\treject,\n\t\t\t);\n\t\t});\n\t}, config);\n};\n\n/**\n * Creates an instrumented Drizzle instance that logs all SQL queries.\n * This wraps the standard drizzleSqliteWasmWorker to intercept every query.\n */\nexport const createInstrumentedDrizzle = <\n\tTSchema extends Record<string, unknown> = Record<string, never>,\n>(\n\tclient: ISqliteWorkerClient,\n\tconfig: DrizzleConfig<TSchema> = {},\n\tinterceptor?: SQLInterceptor,\n) => {\n\treturn drizzleSqliteProxy<TSchema>(async (sql, params, method) => {\n\t\tconst startTime = Date.now();\n\n\t\tconst result = await new Promise<{ rows: unknown[] }>((resolve, reject) => {\n\t\t\tclient.performRemoteCallback(\n\t\t\t\t{\n\t\t\t\t\tsql,\n\t\t\t\t\tparams,\n\t\t\t\t\tmethod,\n\t\t\t\t},\n\t\t\t\tresolve,\n\t\t\t\treject,\n\t\t\t);\n\t\t});\n\n\t\t// Log the operation if interceptor is provided\n\t\tif (interceptor?.onOperation) {\n\t\t\t// Parse SQL to determine context\n\t\t\tconst sqlLower = sql.toLowerCase().trim();\n\t\t\tlet context = \"Direct Drizzle query\";\n\n\t\t\tif (sqlLower.startsWith(\"select\")) {\n\t\t\t\t// Extract table name from SELECT query\n\t\t\t\tconst fromMatch = sql.match(/from\\s+[\"']?(\\w+)[\"']?/i);\n\t\t\t\tconst tableName = fromMatch?.[1] || \"unknown\";\n\n\t\t\t\t// Check for LIMIT/OFFSET - handle both literal values and ? placeholders\n\t\t\t\t// Drizzle uses parameterized queries, so we need to check for `limit ?` style\n\t\t\t\tconst hasLimit = /limit\\s+(\\d+|\\?|\\$\\d+)/i.test(sql);\n\t\t\t\tconst hasOffset = /offset\\s+(\\d+|\\?|\\$\\d+)/i.test(sql);\n\t\t\t\tconst hasOrderBy = /order\\s+by/i.test(sql);\n\n\t\t\t\tif (hasLimit || hasOffset) {\n\t\t\t\t\t// Extract actual values from params if using placeholders\n\t\t\t\t\t// Drizzle typically puts LIMIT as second-to-last param, OFFSET as last\n\t\t\t\t\tlet limitVal = \"?\";\n\t\t\t\t\tlet offsetVal = \"0\";\n\n\t\t\t\t\t// Try to extract literal values first\n\t\t\t\t\tconst literalLimit = sql.match(/limit\\s+(\\d+)/i);\n\t\t\t\t\tconst literalOffset = sql.match(/offset\\s+(\\d+)/i);\n\n\t\t\t\t\tif (literalLimit) {\n\t\t\t\t\t\tlimitVal = literalLimit[1];\n\t\t\t\t\t} else if (params && params.length > 0) {\n\t\t\t\t\t\t// For parameterized queries, try to infer from params\n\t\t\t\t\t\t// LIMIT/OFFSET are usually at the end\n\t\t\t\t\t\tif (hasLimit && hasOffset && params.length >= 2) {\n\t\t\t\t\t\t\tlimitVal = String(params[params.length - 2]);\n\t\t\t\t\t\t\toffsetVal = String(params[params.length - 1]);\n\t\t\t\t\t\t} else if (hasLimit && params.length >= 1) {\n\t\t\t\t\t\t\tlimitVal = String(params[params.length - 1]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (literalOffset) {\n\t\t\t\t\t\toffsetVal = literalOffset[1];\n\t\t\t\t\t}\n\n\t\t\t\t\tcontext = `SELECT with LIMIT ${limitVal} OFFSET ${offsetVal}`;\n\t\t\t\t} else if (hasOrderBy) {\n\t\t\t\t\tcontext = `SELECT all from ${tableName} (ordered)`;\n\t\t\t\t} else {\n\t\t\t\t\tcontext = `SELECT all from ${tableName}`;\n\t\t\t\t}\n\n\t\t\t\tconst operation: SQLOperation = {\n\t\t\t\t\ttype: \"raw-query\",\n\t\t\t\t\tsql,\n\t\t\t\t\tparams: params as unknown[],\n\t\t\t\t\tmethod,\n\t\t\t\t\trowCount: result.rows?.length ?? 0,\n\t\t\t\t\tcontext,\n\t\t\t\t\ttimestamp: startTime,\n\t\t\t\t};\n\t\t\t\tinterceptor.onOperation(operation);\n\t\t\t} else if (sqlLower.startsWith(\"insert\")) {\n\t\t\t\tconst intoMatch = sql.match(/into\\s+[\"']?(\\w+)[\"']?/i);\n\t\t\t\tcontext = `INSERT into ${intoMatch?.[1] || \"unknown\"}`;\n\n\t\t\t\tconst operation: SQLOperation = {\n\t\t\t\t\ttype: \"raw-query\",\n\t\t\t\t\tsql,\n\t\t\t\t\tparams: params as unknown[],\n\t\t\t\t\tmethod,\n\t\t\t\t\trowCount: 0,\n\t\t\t\t\tcontext,\n\t\t\t\t\ttimestamp: startTime,\n\t\t\t\t};\n\t\t\t\tinterceptor.onOperation(operation);\n\t\t\t} else if (sqlLower.startsWith(\"update\")) {\n\t\t\t\tconst tableMatch = sql.match(/update\\s+[\"']?(\\w+)[\"']?/i);\n\t\t\t\tcontext = `UPDATE ${tableMatch?.[1] || \"unknown\"}`;\n\n\t\t\t\tconst operation: SQLOperation = {\n\t\t\t\t\ttype: \"raw-query\",\n\t\t\t\t\tsql,\n\t\t\t\t\tparams: params as unknown[],\n\t\t\t\t\tmethod,\n\t\t\t\t\trowCount: 0,\n\t\t\t\t\tcontext,\n\t\t\t\t\ttimestamp: startTime,\n\t\t\t\t};\n\t\t\t\tinterceptor.onOperation(operation);\n\t\t\t} else if (sqlLower.startsWith(\"delete\")) {\n\t\t\t\tconst fromMatch = sql.match(/from\\s+[\"']?(\\w+)[\"']?/i);\n\t\t\t\tcontext = `DELETE from ${fromMatch?.[1] || \"unknown\"}`;\n\n\t\t\t\tconst operation: SQLOperation = {\n\t\t\t\t\ttype: \"raw-query\",\n\t\t\t\t\tsql,\n\t\t\t\t\tparams: params as unknown[],\n\t\t\t\t\tmethod,\n\t\t\t\t\trowCount: 0,\n\t\t\t\t\tcontext,\n\t\t\t\t\ttimestamp: startTime,\n\t\t\t\t};\n\t\t\t\tinterceptor.onOperation(operation);\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}, config);\n};\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/useDrizzleSqliteDb.ts"],"names":[],"mappings":"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","file":"chunk-WFFFP6DB.js","sourcesContent":["import { useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n\tcustomSqliteMigrate,\n\ttype DurableSqliteMigrationConfig,\n} from \"../migration/migrator\";\nimport {\n\tdrizzleSqliteWasmWorker,\n\tcreateInstrumentedDrizzle,\n} from \"../drizzle/worker\";\nimport type { ISqliteWorkerClient } from \"../worker/manager\";\nimport {\n\tinitializeSqliteWorker,\n\tisSqliteWorkerInitialized,\n} from \"../worker/global-manager\";\nimport type { SQLInterceptor } from \"../collections/sqlite-collection\";\nimport type { SqliteWasmWorkerOpenOptions } from \"../worker/sqlite-open-options\";\n\nexport const useDrizzleSqliteDb = <TSchema extends Record<string, unknown>>(\n\tWorkerConstructor: new () => Worker,\n\tdbName: string,\n\tschema: TSchema,\n\tmigrations: DurableSqliteMigrationConfig,\n\tdebug?: boolean,\n\t/** Optional interceptor to log ALL SQL queries (including direct Drizzle queries) */\n\tinterceptor?: SQLInterceptor,\n\t/**\n\t * Pragmas applied when the worker first opens this `dbName` in the session.\n\t * Ignored if that database was already started (same global worker + dbName).\n\t */\n\tworkerOpenOptions?: SqliteWasmWorkerOpenOptions,\n) => {\n\tconst resolveRef = useRef<null | (() => void)>(null);\n\tconst rejectRef = useRef<null | ((error: unknown) => void)>(null);\n\tconst [sqliteClient, setSqliteClient] = useState<ISqliteWorkerClient | null>(\n\t\tnull,\n\t);\n\tconst sqliteClientRef = useRef<ISqliteWorkerClient | null>(null);\n\n\tconst readyPromise = useMemo(() => {\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tresolveRef.current = resolve;\n\t\t\trejectRef.current = reject;\n\t\t});\n\t}, []);\n\n\t// Initialize the global manager and get db instance\n\tuseEffect(() => {\n\t\tif (typeof window === \"undefined\") {\n\t\t\t// SSR stub\n\t\t\tsetSqliteClient({\n\t\t\t\tperformRemoteCallback: () => {},\n\t\t\t\tcheckpoint: () => Promise.resolve(),\n\t\t\t\tonStarted: () => {},\n\t\t\t\tterminate: () => {},\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tlet mounted = true;\n\n\t\tconst init = async () => {\n\t\t\t// Initialize manager if not already initialized\n\t\t\tif (!isSqliteWorkerInitialized()) {\n\t\t\t\tawait initializeSqliteWorker(WorkerConstructor);\n\t\t\t}\n\n\t\t\t// Get manager and create db instance\n\t\t\tconst { getSqliteWorkerManager } = await import(\n\t\t\t\t\"../worker/global-manager\"\n\t\t\t);\n\t\t\tconst manager = getSqliteWorkerManager();\n\t\t\tconst instance = await manager.getDbInstance(dbName, workerOpenOptions);\n\n\t\t\tif (mounted) {\n\t\t\t\tsqliteClientRef.current = instance;\n\t\t\t\tsetSqliteClient(instance);\n\t\t\t}\n\t\t};\n\n\t\tinit();\n\n\t\treturn () => {\n\t\t\tmounted = false;\n\t\t};\n\t}, [dbName, WorkerConstructor, workerOpenOptions]);\n\n\t// Store interceptor in a ref to avoid recreating drizzle on interceptor changes\n\tconst interceptorRef = useRef(interceptor);\n\tinterceptorRef.current = interceptor;\n\n\t// Create drizzle instance with a callback-based approach that waits for the client\n\t// Use instrumented version if interceptor is provided to log ALL queries\n\tconst drizzle = useMemo(() => {\n\t\tif (debug) {\n\t\t\tconsole.log(`[DEBUG] ${dbName} - creating drizzle proxy wrapper`);\n\t\t}\n\n\t\tconst client: ISqliteWorkerClient = {\n\t\t\tperformRemoteCallback: (data, resolve, reject) => {\n\t\t\t\tconst actualClient = sqliteClientRef.current;\n\t\t\t\tif (!actualClient) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`[DEBUG] ${dbName} - performRemoteCallback called but no sqliteClient yet`,\n\t\t\t\t\t);\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew Error(`Database ${dbName} not ready yet - still initializing`),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tactualClient.performRemoteCallback(data, resolve, reject);\n\t\t\t},\n\t\t\tonStarted: (callback) => {\n\t\t\t\tconst actualClient = sqliteClientRef.current;\n\t\t\t\tif (!actualClient) {\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`[DEBUG] ${dbName} - onStarted called but no sqliteClient yet`,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tactualClient.onStarted(callback);\n\t\t\t},\n\t\t\tterminate: () => {\n\t\t\t\tsqliteClientRef.current?.terminate();\n\t\t\t},\n\t\t\tcheckpoint: () => {\n\t\t\t\treturn sqliteClientRef.current?.checkpoint() ?? Promise.resolve();\n\t\t\t},\n\t\t};\n\n\t\t// Use instrumented version if interceptor is provided\n\t\t// Use a wrapper that accesses the ref so interceptor changes don't recreate drizzle\n\t\tconst interceptorWrapper: SQLInterceptor = {\n\t\t\tonOperation: (op) => interceptorRef.current?.onOperation?.(op),\n\t\t};\n\n\t\t// Always use instrumented if initial interceptor was provided\n\t\tif (interceptor) {\n\t\t\treturn createInstrumentedDrizzle<TSchema>(\n\t\t\t\tclient,\n\t\t\t\t{ schema },\n\t\t\t\tinterceptorWrapper,\n\t\t\t);\n\t\t}\n\n\t\treturn drizzleSqliteWasmWorker<TSchema>(client, { schema });\n\t}, [schema, dbName, !!interceptor]); // Only recreate if interceptor presence changes, not on every render\n\n\tuseEffect(() => {\n\t\tif (!sqliteClient) {\n\t\t\tif (debug) {\n\t\t\t\tconsole.log(`[DEBUG] ${dbName} - waiting for sqliteClient...`);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tsqliteClient.onStarted(async () => {\n\t\t\ttry {\n\t\t\t\tawait customSqliteMigrate(drizzle, migrations);\n\t\t\t\tresolveRef.current?.();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(`Migration error for ${dbName}:`, error);\n\t\t\t\trejectRef.current?.(error);\n\t\t\t}\n\t\t});\n\n\t\treturn () => {\n\t\t\tsqliteClient.terminate();\n\t\t};\n\t}, [sqliteClient, drizzle, migrations, dbName]);\n\n\treturn { drizzle, readyPromise, sqliteClient };\n};\n"]}