@firtoz/drizzle-sqlite-wasm 1.0.4 → 1.1.1

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 (85) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +9 -1
  3. package/dist/chunk-6LNYKOKR.js +24 -0
  4. package/dist/chunk-6LNYKOKR.js.map +1 -0
  5. package/dist/chunk-7JJHY44Q.js +19 -0
  6. package/dist/chunk-7JJHY44Q.js.map +1 -0
  7. package/dist/chunk-AEYHRJVN.js +130 -0
  8. package/dist/chunk-AEYHRJVN.js.map +1 -0
  9. package/dist/chunk-AGMKOHXO.js +238 -0
  10. package/dist/chunk-AGMKOHXO.js.map +1 -0
  11. package/dist/chunk-BJDPMGFF.js +87 -0
  12. package/dist/chunk-BJDPMGFF.js.map +1 -0
  13. package/dist/chunk-BZVMUTJ7.js +49 -0
  14. package/dist/chunk-BZVMUTJ7.js.map +1 -0
  15. package/dist/chunk-FIVEPVOM.js +43 -0
  16. package/dist/chunk-FIVEPVOM.js.map +1 -0
  17. package/dist/chunk-FRONXNEA.js +123 -0
  18. package/dist/chunk-FRONXNEA.js.map +1 -0
  19. package/dist/chunk-GVUNHU6J.js +85 -0
  20. package/dist/chunk-GVUNHU6J.js.map +1 -0
  21. package/dist/chunk-H2F2HZ2A.js +22 -0
  22. package/dist/chunk-H2F2HZ2A.js.map +1 -0
  23. package/dist/chunk-NSPVPJKE.js +117 -0
  24. package/dist/chunk-NSPVPJKE.js.map +1 -0
  25. package/dist/chunk-TZP4AIBR.js +22 -0
  26. package/dist/chunk-TZP4AIBR.js.map +1 -0
  27. package/dist/chunk-WFFFP6DB.js +124 -0
  28. package/dist/chunk-WFFFP6DB.js.map +1 -0
  29. package/dist/collections/sqlite-collection.d.ts +40 -0
  30. package/dist/collections/sqlite-collection.js +3 -0
  31. package/dist/collections/sqlite-collection.js.map +1 -0
  32. package/dist/collections/synced-sqlite-collection.d.ts +19 -0
  33. package/dist/collections/synced-sqlite-collection.js +4 -0
  34. package/dist/collections/synced-sqlite-collection.js.map +1 -0
  35. package/dist/collections/websocket-collection.d.ts +18 -0
  36. package/dist/collections/websocket-collection.js +185 -0
  37. package/dist/collections/websocket-collection.js.map +1 -0
  38. package/dist/context/DrizzleSqliteProvider.d.ts +50 -0
  39. package/dist/context/DrizzleSqliteProvider.js +11 -0
  40. package/dist/context/DrizzleSqliteProvider.js.map +1 -0
  41. package/dist/context/useDrizzleSqlite.d.ts +23 -0
  42. package/dist/context/useDrizzleSqlite.js +12 -0
  43. package/dist/context/useDrizzleSqlite.js.map +1 -0
  44. package/dist/drizzle/direct.d.ts +8 -0
  45. package/dist/drizzle/direct.js +4 -0
  46. package/dist/drizzle/direct.js.map +1 -0
  47. package/dist/drizzle/handle-callback.d.ts +15 -0
  48. package/dist/drizzle/handle-callback.js +3 -0
  49. package/dist/drizzle/handle-callback.js.map +1 -0
  50. package/dist/drizzle/worker.d.ts +15 -0
  51. package/dist/drizzle/worker.js +3 -0
  52. package/dist/drizzle/worker.js.map +1 -0
  53. package/dist/hooks/useDrizzleSqliteDb.d.ts +24 -0
  54. package/dist/hooks/useDrizzleSqliteDb.js +9 -0
  55. package/dist/hooks/useDrizzleSqliteDb.js.map +1 -0
  56. package/dist/index.d.ts +25 -0
  57. package/dist/index.js +16 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/migration/migrator.d.ts +7 -0
  60. package/dist/migration/migrator.js +3 -0
  61. package/dist/migration/migrator.js.map +1 -0
  62. package/dist/types.d.ts +6 -0
  63. package/dist/types.js +3 -0
  64. package/dist/types.js.map +1 -0
  65. package/dist/worker/client.d.ts +12 -0
  66. package/dist/worker/client.js +3 -0
  67. package/dist/worker/client.js.map +1 -0
  68. package/dist/worker/global-manager.d.ts +36 -0
  69. package/dist/worker/global-manager.js +6 -0
  70. package/dist/worker/global-manager.js.map +1 -0
  71. package/dist/worker/manager.d.ts +71 -0
  72. package/dist/worker/manager.js +5 -0
  73. package/dist/worker/manager.js.map +1 -0
  74. package/dist/worker/schema.d.ts +125 -0
  75. package/dist/worker/schema.js +4 -0
  76. package/dist/worker/schema.js.map +1 -0
  77. package/dist/worker/sqlite-open-options.d.ts +45 -0
  78. package/dist/worker/sqlite-open-options.js +3 -0
  79. package/dist/worker/sqlite-open-options.js.map +1 -0
  80. package/dist/worker/sqlite.worker.d.ts +2 -0
  81. package/dist/worker/sqlite.worker.js +196 -0
  82. package/dist/worker/sqlite.worker.js.map +1 -0
  83. package/package.json +25 -25
  84. package/src/collections/sqlite-collection.ts +4 -4
  85. package/src/hooks/useDrizzleSqliteDb.ts +2 -2
@@ -0,0 +1,124 @@
1
+ import { customSqliteMigrate } from './chunk-NSPVPJKE.js';
2
+ import { isSqliteWorkerInitialized, initializeSqliteWorker } from './chunk-FIVEPVOM.js';
3
+ import { createInstrumentedDrizzle, drizzleSqliteWasmWorker } from './chunk-FRONXNEA.js';
4
+ import { useRef, useState, useMemo, useEffect } from 'react';
5
+
6
+ var useDrizzleSqliteDb = (WorkerConstructor, dbName, schema, migrations, debug, interceptor, workerOpenOptions) => {
7
+ const resolveRef = useRef(null);
8
+ const rejectRef = useRef(null);
9
+ const [sqliteClient, setSqliteClient] = useState(
10
+ null
11
+ );
12
+ const sqliteClientRef = useRef(null);
13
+ const readyPromise = useMemo(() => {
14
+ return new Promise((resolve, reject) => {
15
+ resolveRef.current = resolve;
16
+ rejectRef.current = reject;
17
+ });
18
+ }, []);
19
+ useEffect(() => {
20
+ if (typeof window === "undefined") {
21
+ setSqliteClient({
22
+ performRemoteCallback: () => {
23
+ },
24
+ checkpoint: () => Promise.resolve(),
25
+ onStarted: () => {
26
+ },
27
+ terminate: () => {
28
+ }
29
+ });
30
+ return;
31
+ }
32
+ let mounted = true;
33
+ const init = async () => {
34
+ if (!isSqliteWorkerInitialized()) {
35
+ await initializeSqliteWorker(WorkerConstructor);
36
+ }
37
+ const { getSqliteWorkerManager } = await import('./worker/global-manager.js');
38
+ const manager = getSqliteWorkerManager();
39
+ const instance = await manager.getDbInstance(dbName, workerOpenOptions);
40
+ if (mounted) {
41
+ sqliteClientRef.current = instance;
42
+ setSqliteClient(instance);
43
+ }
44
+ };
45
+ init();
46
+ return () => {
47
+ mounted = false;
48
+ };
49
+ }, [dbName, WorkerConstructor, workerOpenOptions]);
50
+ const interceptorRef = useRef(interceptor);
51
+ interceptorRef.current = interceptor;
52
+ const drizzle = useMemo(() => {
53
+ if (debug) {
54
+ console.log(`[DEBUG] ${dbName} - creating drizzle proxy wrapper`);
55
+ }
56
+ const client = {
57
+ performRemoteCallback: (data, resolve, reject) => {
58
+ const actualClient = sqliteClientRef.current;
59
+ if (!actualClient) {
60
+ console.error(
61
+ `[DEBUG] ${dbName} - performRemoteCallback called but no sqliteClient yet`
62
+ );
63
+ reject(
64
+ new Error(`Database ${dbName} not ready yet - still initializing`)
65
+ );
66
+ return;
67
+ }
68
+ actualClient.performRemoteCallback(data, resolve, reject);
69
+ },
70
+ onStarted: (callback) => {
71
+ const actualClient = sqliteClientRef.current;
72
+ if (!actualClient) {
73
+ console.warn(
74
+ `[DEBUG] ${dbName} - onStarted called but no sqliteClient yet`
75
+ );
76
+ return;
77
+ }
78
+ actualClient.onStarted(callback);
79
+ },
80
+ terminate: () => {
81
+ sqliteClientRef.current?.terminate();
82
+ },
83
+ checkpoint: () => {
84
+ return sqliteClientRef.current?.checkpoint() ?? Promise.resolve();
85
+ }
86
+ };
87
+ const interceptorWrapper = {
88
+ onOperation: (op) => interceptorRef.current?.onOperation?.(op)
89
+ };
90
+ if (interceptor) {
91
+ return createInstrumentedDrizzle(
92
+ client,
93
+ { schema },
94
+ interceptorWrapper
95
+ );
96
+ }
97
+ return drizzleSqliteWasmWorker(client, { schema });
98
+ }, [schema, dbName, !!interceptor]);
99
+ useEffect(() => {
100
+ if (!sqliteClient) {
101
+ if (debug) {
102
+ console.log(`[DEBUG] ${dbName} - waiting for sqliteClient...`);
103
+ }
104
+ return;
105
+ }
106
+ sqliteClient.onStarted(async () => {
107
+ try {
108
+ await customSqliteMigrate(drizzle, migrations);
109
+ resolveRef.current?.();
110
+ } catch (error) {
111
+ console.error(`Migration error for ${dbName}:`, error);
112
+ rejectRef.current?.(error);
113
+ }
114
+ });
115
+ return () => {
116
+ sqliteClient.terminate();
117
+ };
118
+ }, [sqliteClient, drizzle, migrations, dbName]);
119
+ return { drizzle, readyPromise, sqliteClient };
120
+ };
121
+
122
+ export { useDrizzleSqliteDb };
123
+ //# sourceMappingURL=chunk-WFFFP6DB.js.map
124
+ //# sourceMappingURL=chunk-WFFFP6DB.js.map
@@ -0,0 +1 @@
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"]}
@@ -0,0 +1,40 @@
1
+ import { SyncMode, CollectionConfig, InferSchemaOutput } from '@tanstack/db';
2
+ import { Table } from 'drizzle-orm';
3
+ import { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core';
4
+ import { CollectionUtils } from '@firtoz/db-helpers';
5
+ import { TableWithRequiredFields, SQLInterceptor, SelectSchema, IdOf, InsertToSelectSchema } from '@firtoz/drizzle-utils';
6
+ export { SQLInterceptor, SQLOperation } from '@firtoz/drizzle-utils';
7
+
8
+ type AnyDrizzleDatabase = BaseSQLiteDatabase<"async", any, Record<string, unknown>>;
9
+ type DrizzleSchema<TDrizzle extends AnyDrizzleDatabase> = TDrizzle["_"]["fullSchema"];
10
+ interface DrizzleSqliteCollectionConfig<TDrizzle extends AnyDrizzleDatabase, TTableName extends ValidTableNames<DrizzleSchema<TDrizzle>>> {
11
+ drizzle: TDrizzle;
12
+ tableName: ValidTableNames<DrizzleSchema<TDrizzle>> extends never ? {
13
+ $error: "The schema needs to include at least one table that uses the syncableTable function.";
14
+ } : TTableName;
15
+ readyPromise: Promise<void>;
16
+ syncMode?: SyncMode;
17
+ /**
18
+ * Enable debug logging for query execution and mutations
19
+ */
20
+ debug?: boolean;
21
+ /**
22
+ * Optional callback to checkpoint the database after mutations
23
+ * This ensures WAL is flushed to the main database file for OPFS persistence
24
+ */
25
+ checkpoint?: () => Promise<void>;
26
+ /**
27
+ * Optional interceptor for tracking SQLite operations (for testing/debugging)
28
+ */
29
+ interceptor?: SQLInterceptor;
30
+ }
31
+ type ValidTableNames<TSchema extends Record<string, unknown>> = {
32
+ [K in keyof TSchema]: TSchema[K] extends TableWithRequiredFields ? K : never;
33
+ }[keyof TSchema];
34
+ type SqliteCollectionConfig<TTable extends Table> = Omit<CollectionConfig<InferSchemaOutput<SelectSchema<TTable>>, IdOf<TTable>, InsertToSelectSchema<TTable>, CollectionUtils<InferSchemaOutput<SelectSchema<TTable>>>>, "utils"> & {
35
+ schema: InsertToSelectSchema<TTable>;
36
+ utils: CollectionUtils<InferSchemaOutput<SelectSchema<TTable>>>;
37
+ };
38
+ declare function sqliteCollectionOptions<const TDrizzle extends AnyDrizzleDatabase, const TTableName extends string & ValidTableNames<DrizzleSchema<TDrizzle>>, TTable extends DrizzleSchema<TDrizzle>[TTableName] & TableWithRequiredFields>(config: DrizzleSqliteCollectionConfig<TDrizzle, TTableName>): SqliteCollectionConfig<TTable>;
39
+
40
+ export { type AnyDrizzleDatabase, type DrizzleSchema, type DrizzleSqliteCollectionConfig, type SqliteCollectionConfig, type ValidTableNames, sqliteCollectionOptions };
@@ -0,0 +1,3 @@
1
+ export { sqliteCollectionOptions } from '../chunk-BZVMUTJ7.js';
2
+ //# sourceMappingURL=sqlite-collection.js.map
3
+ //# sourceMappingURL=sqlite-collection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"sqlite-collection.js"}
@@ -0,0 +1,19 @@
1
+ import { WithSyncOptions, SyncClientBridge, SyncableCollectionItem, SyncClientMessage } from '@firtoz/collection-sync';
2
+ import { Collection } from '@tanstack/db';
3
+ import { TableWithRequiredFields } from '@firtoz/drizzle-utils';
4
+ import { InferSelectModel } from 'drizzle-orm';
5
+ import { AnyDrizzleDatabase, ValidTableNames, DrizzleSchema, DrizzleSqliteCollectionConfig } from './sqlite-collection.js';
6
+ import 'drizzle-orm/sqlite-core';
7
+ import '@firtoz/db-helpers';
8
+
9
+ /**
10
+ * Like {@link createSyncedCollection} from `@firtoz/collection-sync`, but row type uses Drizzle’s
11
+ * {@link InferSelectModel} so branded columns (e.g. ids) match `$inferSelect`, not Valibot schema output.
12
+ */
13
+ declare function createSyncedSqliteCollection<const TDrizzle extends AnyDrizzleDatabase, const TTableName extends string & ValidTableNames<DrizzleSchema<TDrizzle>>, TTable extends DrizzleSchema<TDrizzle>[TTableName] & TableWithRequiredFields>(config: DrizzleSqliteCollectionConfig<TDrizzle, TTableName>, syncOptions?: WithSyncOptions): {
14
+ collection: Collection<InferSelectModel<TTable>>;
15
+ bridge: SyncClientBridge<InferSelectModel<TTable> & SyncableCollectionItem>;
16
+ setTransportSend: (send: (msg: SyncClientMessage) => void) => void;
17
+ };
18
+
19
+ export { createSyncedSqliteCollection };
@@ -0,0 +1,4 @@
1
+ export { createSyncedSqliteCollection } from '../chunk-7JJHY44Q.js';
2
+ import '../chunk-BZVMUTJ7.js';
3
+ //# sourceMappingURL=synced-sqlite-collection.js.map
4
+ //# sourceMappingURL=synced-sqlite-collection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"synced-sqlite-collection.js"}
@@ -0,0 +1,18 @@
1
+ import { BaseCollectionConfig, InferSchemaOutput, CollectionConfig, UtilsRecord } from '@tanstack/db';
2
+ import { StandardSchemaV1 } from '@standard-schema/spec';
3
+
4
+ interface WebSocketCollectionConfig<TSchema extends StandardSchemaV1> extends Omit<BaseCollectionConfig<InferSchemaOutput<TSchema>, string | number, TSchema>, "onInsert" | "onUpdate" | "onDelete" | "sync" | "schema"> {
5
+ url: string;
6
+ reconnectInterval?: number;
7
+ schema: TSchema;
8
+ }
9
+ interface WebSocketUtils extends UtilsRecord {
10
+ reconnect: () => void;
11
+ getConnectionState: () => "connected" | "disconnected" | "connecting";
12
+ }
13
+ declare function webSocketCollectionOptions<TSchema extends StandardSchemaV1>(config: WebSocketCollectionConfig<TSchema>): CollectionConfig<InferSchemaOutput<TSchema>, string | number, TSchema> & {
14
+ utils: WebSocketUtils;
15
+ schema: TSchema;
16
+ };
17
+
18
+ export { webSocketCollectionOptions };
@@ -0,0 +1,185 @@
1
+ import { exhaustiveGuard } from '@firtoz/maybe-error';
2
+
3
+ // src/collections/websocket-collection.ts
4
+ function webSocketCollectionOptions(config) {
5
+ let ws = null;
6
+ let reconnectTimer = null;
7
+ let connectionState = "disconnected";
8
+ const pendingTransactions = /* @__PURE__ */ new Map();
9
+ const handlers = {};
10
+ const connect = () => {
11
+ connectionState = "connecting";
12
+ ws = new WebSocket(config.url);
13
+ ws.onopen = () => {
14
+ handlers.onOpen?.();
15
+ };
16
+ ws.onmessage = (event) => {
17
+ handlers.onMessage?.(event);
18
+ };
19
+ ws.onerror = (error) => {
20
+ handlers.onError?.(error);
21
+ };
22
+ ws.onclose = () => {
23
+ handlers.onClose?.();
24
+ };
25
+ };
26
+ const sync = (params) => {
27
+ const { begin, write, commit, markReady } = params;
28
+ handlers.onOpen = () => {
29
+ if (!ws) return;
30
+ connectionState = "connected";
31
+ ws.send(JSON.stringify({ type: "sync" }));
32
+ };
33
+ handlers.onMessage = (event) => {
34
+ if (!ws) return;
35
+ const message = JSON.parse(event.data);
36
+ switch (message.type) {
37
+ case "sync":
38
+ begin();
39
+ if (Array.isArray(message.data)) {
40
+ for (const item of message.data) {
41
+ write({ type: "insert", value: item });
42
+ }
43
+ }
44
+ commit();
45
+ markReady();
46
+ break;
47
+ case "insert":
48
+ case "update":
49
+ case "delete":
50
+ begin();
51
+ write({
52
+ type: message.type,
53
+ value: message.data
54
+ });
55
+ commit();
56
+ break;
57
+ case "ack":
58
+ if (message.transactionId) {
59
+ const pending = pendingTransactions.get(message.transactionId);
60
+ if (pending) {
61
+ clearTimeout(pending.timeout);
62
+ pendingTransactions.delete(message.transactionId);
63
+ pending.resolve();
64
+ }
65
+ }
66
+ break;
67
+ case "transaction":
68
+ if (message.mutations) {
69
+ begin();
70
+ for (const mutation of message.mutations) {
71
+ write({
72
+ type: mutation.type,
73
+ value: mutation.data
74
+ });
75
+ }
76
+ commit();
77
+ }
78
+ break;
79
+ default:
80
+ exhaustiveGuard(message.type);
81
+ }
82
+ };
83
+ handlers.onError = (error) => {
84
+ console.error("WebSocket error:", error);
85
+ connectionState = "disconnected";
86
+ };
87
+ handlers.onClose = () => {
88
+ connectionState = "disconnected";
89
+ if (!reconnectTimer) {
90
+ reconnectTimer = setTimeout(() => {
91
+ reconnectTimer = null;
92
+ connect();
93
+ }, config.reconnectInterval || 5e3);
94
+ }
95
+ };
96
+ connect();
97
+ return () => {
98
+ if (reconnectTimer) {
99
+ clearTimeout(reconnectTimer);
100
+ reconnectTimer = null;
101
+ }
102
+ if (ws) {
103
+ ws.close();
104
+ ws = null;
105
+ }
106
+ };
107
+ };
108
+ const sendTransaction = async (params) => {
109
+ if (ws?.readyState !== WebSocket.OPEN) {
110
+ throw new Error("WebSocket not connected");
111
+ }
112
+ const transactionId = crypto.randomUUID();
113
+ const mutations = params.transaction.mutations.map((mutation) => {
114
+ switch (mutation.type) {
115
+ case "insert":
116
+ return {
117
+ type: mutation.type,
118
+ id: mutation.key,
119
+ data: mutation.modified
120
+ };
121
+ case "update":
122
+ return {
123
+ type: mutation.type,
124
+ id: mutation.key,
125
+ data: mutation.changes
126
+ };
127
+ case "delete":
128
+ return {
129
+ type: mutation.type,
130
+ id: mutation.key,
131
+ data: void 0
132
+ };
133
+ default:
134
+ return exhaustiveGuard(mutation);
135
+ }
136
+ });
137
+ ws.send(
138
+ JSON.stringify({
139
+ type: "transaction",
140
+ transactionId,
141
+ mutations
142
+ })
143
+ );
144
+ return new Promise((resolve, reject) => {
145
+ const timeout = setTimeout(() => {
146
+ pendingTransactions.delete(transactionId);
147
+ reject(new Error(`Transaction ${transactionId} timed out`));
148
+ }, 1e4);
149
+ pendingTransactions.set(transactionId, {
150
+ resolve,
151
+ reject,
152
+ timeout
153
+ });
154
+ });
155
+ };
156
+ const onInsert = async (params) => {
157
+ await sendTransaction(params);
158
+ };
159
+ const onUpdate = async (params) => {
160
+ await sendTransaction(params);
161
+ };
162
+ const onDelete = async (params) => {
163
+ await sendTransaction(params);
164
+ };
165
+ return {
166
+ id: config.id,
167
+ schema: config.schema,
168
+ getKey: config.getKey,
169
+ sync: { sync },
170
+ onInsert,
171
+ onUpdate,
172
+ onDelete,
173
+ utils: {
174
+ reconnect: () => {
175
+ if (ws) ws.close();
176
+ connect();
177
+ },
178
+ getConnectionState: () => connectionState
179
+ }
180
+ };
181
+ }
182
+
183
+ export { webSocketCollectionOptions };
184
+ //# sourceMappingURL=websocket-collection.js.map
185
+ //# sourceMappingURL=websocket-collection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/collections/websocket-collection.ts"],"names":[],"mappings":"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","file":"websocket-collection.js","sourcesContent":["import type {\n\tCollectionConfig,\n\tSyncConfig,\n\tInsertMutationFnParams,\n\tUpdateMutationFnParams,\n\tDeleteMutationFnParams,\n\tUtilsRecord,\n\tBaseCollectionConfig,\n\tInferSchemaOutput,\n\tInferSchemaInput,\n} from \"@tanstack/db\";\nimport { exhaustiveGuard } from \"@firtoz/maybe-error\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\ninterface WebSocketMessage<T> {\n\ttype: \"insert\" | \"update\" | \"delete\" | \"sync\" | \"transaction\" | \"ack\";\n\tdata?: T | T[];\n\tmutations?: Array<{\n\t\ttype: \"insert\" | \"update\" | \"delete\";\n\t\tdata: T;\n\t\tid?: string;\n\t}>;\n\ttransactionId?: string;\n\tid?: string;\n}\n\ninterface WebSocketCollectionConfig<TSchema extends StandardSchemaV1>\n\textends Omit<\n\t\tBaseCollectionConfig<InferSchemaOutput<TSchema>, string | number, TSchema>,\n\t\t\"onInsert\" | \"onUpdate\" | \"onDelete\" | \"sync\" | \"schema\"\n\t> {\n\turl: string;\n\treconnectInterval?: number;\n\tschema: TSchema;\n\n\t// Note: onInsert/onUpdate/onDelete are handled by the WebSocket connection\n\t// Users don't provide these handlers\n}\n\ninterface WebSocketUtils extends UtilsRecord {\n\treconnect: () => void;\n\tgetConnectionState: () => \"connected\" | \"disconnected\" | \"connecting\";\n}\n\nexport function webSocketCollectionOptions<TSchema extends StandardSchemaV1>(\n\tconfig: WebSocketCollectionConfig<TSchema>,\n): CollectionConfig<InferSchemaOutput<TSchema>, string | number, TSchema> & {\n\tutils: WebSocketUtils;\n\tschema: TSchema;\n} {\n\ttype TItem = InferSchemaOutput<TSchema>;\n\n\tlet ws: WebSocket | null = null;\n\tlet reconnectTimer: NodeJS.Timeout | null = null;\n\tlet connectionState: \"connected\" | \"disconnected\" | \"connecting\" =\n\t\t\"disconnected\";\n\n\t// Track pending transactions awaiting acknowledgment\n\tconst pendingTransactions = new Map<\n\t\tstring,\n\t\t{\n\t\t\tresolve: () => void;\n\t\t\treject: (error: Error) => void;\n\t\t\ttimeout: NodeJS.Timeout;\n\t\t}\n\t>();\n\n\tconst handlers: {\n\t\tonOpen?: () => void;\n\t\tonMessage?: (event: MessageEvent) => void;\n\t\tonError?: (error: Event) => void;\n\t\tonClose?: () => void;\n\t} = {};\n\n\tconst connect = () => {\n\t\tconnectionState = \"connecting\";\n\t\tws = new WebSocket(config.url);\n\n\t\tws.onopen = () => {\n\t\t\thandlers.onOpen?.();\n\t\t};\n\t\tws.onmessage = (event) => {\n\t\t\thandlers.onMessage?.(event);\n\t\t};\n\t\tws.onerror = (error) => {\n\t\t\thandlers.onError?.(error);\n\t\t};\n\t\tws.onclose = () => {\n\t\t\thandlers.onClose?.();\n\t\t};\n\t};\n\n\tconst sync: SyncConfig<TItem>[\"sync\"] = (params) => {\n\t\tconst { begin, write, commit, markReady } = params;\n\n\t\thandlers.onOpen = () => {\n\t\t\tif (!ws) return;\n\n\t\t\tconnectionState = \"connected\";\n\t\t\t// Request initial sync\n\t\t\tws.send(JSON.stringify({ type: \"sync\" }));\n\t\t};\n\n\t\thandlers.onMessage = (event) => {\n\t\t\tif (!ws) return;\n\n\t\t\tconst message: WebSocketMessage<TItem> = JSON.parse(event.data);\n\n\t\t\tswitch (message.type) {\n\t\t\t\tcase \"sync\":\n\t\t\t\t\t// Initial sync with array of items\n\t\t\t\t\tbegin();\n\t\t\t\t\tif (Array.isArray(message.data)) {\n\t\t\t\t\t\tfor (const item of message.data) {\n\t\t\t\t\t\t\twrite({ type: \"insert\", value: item });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcommit();\n\t\t\t\t\tmarkReady();\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"insert\":\n\t\t\t\tcase \"update\":\n\t\t\t\tcase \"delete\":\n\t\t\t\t\t// Real-time updates from other clients\n\t\t\t\t\tbegin();\n\t\t\t\t\twrite({\n\t\t\t\t\t\ttype: message.type,\n\t\t\t\t\t\tvalue: message.data as InferSchemaInput<TSchema>,\n\t\t\t\t\t});\n\t\t\t\t\tcommit();\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"ack\":\n\t\t\t\t\t// Server acknowledged our transaction\n\t\t\t\t\tif (message.transactionId) {\n\t\t\t\t\t\tconst pending = pendingTransactions.get(message.transactionId);\n\t\t\t\t\t\tif (pending) {\n\t\t\t\t\t\t\tclearTimeout(pending.timeout);\n\t\t\t\t\t\t\tpendingTransactions.delete(message.transactionId);\n\t\t\t\t\t\t\tpending.resolve();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"transaction\":\n\t\t\t\t\t// Server sending back the actual data after processing our transaction\n\t\t\t\t\tif (message.mutations) {\n\t\t\t\t\t\tbegin();\n\t\t\t\t\t\tfor (const mutation of message.mutations) {\n\t\t\t\t\t\t\twrite({\n\t\t\t\t\t\t\t\ttype: mutation.type,\n\t\t\t\t\t\t\t\tvalue: mutation.data,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcommit();\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\texhaustiveGuard(message.type);\n\t\t\t}\n\t\t};\n\n\t\thandlers.onError = (error) => {\n\t\t\tconsole.error(\"WebSocket error:\", error);\n\t\t\tconnectionState = \"disconnected\";\n\t\t};\n\n\t\thandlers.onClose = () => {\n\t\t\tconnectionState = \"disconnected\";\n\t\t\t// Auto-reconnect\n\t\t\tif (!reconnectTimer) {\n\t\t\t\treconnectTimer = setTimeout(() => {\n\t\t\t\t\treconnectTimer = null;\n\t\t\t\t\tconnect();\n\t\t\t\t}, config.reconnectInterval || 5000);\n\t\t\t}\n\t\t};\n\n\t\t// Start connection\n\t\tconnect();\n\n\t\t// Return cleanup function\n\t\treturn () => {\n\t\t\tif (reconnectTimer) {\n\t\t\t\tclearTimeout(reconnectTimer);\n\t\t\t\treconnectTimer = null;\n\t\t\t}\n\t\t\tif (ws) {\n\t\t\t\tws.close();\n\t\t\t\tws = null;\n\t\t\t}\n\t\t};\n\t};\n\n\t// Helper function to send transaction and wait for server acknowledgment\n\tconst sendTransaction = async (\n\t\tparams:\n\t\t\t| InsertMutationFnParams<TItem>\n\t\t\t| UpdateMutationFnParams<TItem>\n\t\t\t| DeleteMutationFnParams<TItem>,\n\t): Promise<void> => {\n\t\tif (ws?.readyState !== WebSocket.OPEN) {\n\t\t\tthrow new Error(\"WebSocket not connected\");\n\t\t}\n\n\t\tconst transactionId = crypto.randomUUID();\n\n\t\t// Convert all mutations in the transaction to the wire format\n\t\tconst mutations = params.transaction.mutations.map((mutation) => {\n\t\t\tswitch (mutation.type) {\n\t\t\t\tcase \"insert\":\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: mutation.type,\n\t\t\t\t\t\tid: mutation.key,\n\t\t\t\t\t\tdata: mutation.modified,\n\t\t\t\t\t};\n\t\t\t\tcase \"update\":\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: mutation.type,\n\t\t\t\t\t\tid: mutation.key,\n\t\t\t\t\t\tdata: mutation.changes,\n\t\t\t\t\t};\n\t\t\t\tcase \"delete\":\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: mutation.type,\n\t\t\t\t\t\tid: mutation.key,\n\t\t\t\t\t\tdata: undefined,\n\t\t\t\t\t};\n\t\t\t\tdefault:\n\t\t\t\t\treturn exhaustiveGuard(mutation);\n\t\t\t}\n\t\t});\n\n\t\t// Send the entire transaction at once\n\t\tws.send(\n\t\t\tJSON.stringify({\n\t\t\t\ttype: \"transaction\",\n\t\t\t\ttransactionId,\n\t\t\t\tmutations,\n\t\t\t}),\n\t\t);\n\n\t\t// Wait for server acknowledgment\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tpendingTransactions.delete(transactionId);\n\t\t\t\treject(new Error(`Transaction ${transactionId} timed out`));\n\t\t\t}, 10000); // 10 second timeout\n\n\t\t\tpendingTransactions.set(transactionId, {\n\t\t\t\tresolve,\n\t\t\t\treject,\n\t\t\t\ttimeout,\n\t\t\t});\n\t\t});\n\t};\n\n\t// All mutation handlers use the same transaction sender\n\tconst onInsert = async (params: InsertMutationFnParams<TItem>) => {\n\t\tawait sendTransaction(params);\n\t};\n\n\tconst onUpdate = async (params: UpdateMutationFnParams<TItem>) => {\n\t\tawait sendTransaction(params);\n\t};\n\n\tconst onDelete = async (params: DeleteMutationFnParams<TItem>) => {\n\t\tawait sendTransaction(params);\n\t};\n\n\treturn {\n\t\tid: config.id,\n\t\tschema: config.schema,\n\t\tgetKey: config.getKey,\n\t\tsync: { sync },\n\t\tonInsert,\n\t\tonUpdate,\n\t\tonDelete,\n\t\tutils: {\n\t\t\treconnect: () => {\n\t\t\t\tif (ws) ws.close();\n\t\t\t\tconnect();\n\t\t\t},\n\t\t\tgetConnectionState: () => connectionState,\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,50 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as react from 'react';
3
+ import { PropsWithChildren } from 'react';
4
+ import { SqliteRemoteDatabase } from 'drizzle-orm/sqlite-proxy';
5
+ import { Collection, InferSchemaOutput } from '@tanstack/db';
6
+ import { ValidTableNames } from '../collections/sqlite-collection.js';
7
+ import { DurableSqliteMigrationConfig } from '../migration/migrator.js';
8
+ import { SqliteWasmWorkerOpenOptions } from '../worker/sqlite-open-options.js';
9
+ import { GetTableFromSchema, IdOf, SQLInterceptor, InferCollectionFromTable } from '@firtoz/drizzle-utils';
10
+ import 'drizzle-orm';
11
+ import 'drizzle-orm/sqlite-core';
12
+ import '@firtoz/db-helpers';
13
+ import 'drizzle-orm/durable-sqlite/migrator';
14
+ import 'zod';
15
+
16
+ type SqliteCollection<TSchema extends Record<string, unknown>, TTableName extends string & ValidTableNames<TSchema>> = Collection<InferSchemaOutput<GetTableFromSchema<TSchema, TTableName>["$inferSelect"]>, IdOf<GetTableFromSchema<TSchema, TTableName>>, any, any, Omit<GetTableFromSchema<TSchema, TTableName>["$inferInsert"], "id"> & {
17
+ id?: IdOf<GetTableFromSchema<TSchema, TTableName>>;
18
+ }>;
19
+ type DrizzleSqliteContextValue<TSchema extends Record<string, unknown>> = {
20
+ drizzle: SqliteRemoteDatabase<TSchema>;
21
+ readyPromise: Promise<void>;
22
+ getCollection: <TTableName extends string & ValidTableNames<TSchema>>(tableName: TTableName) => SqliteCollection<TSchema, TTableName>;
23
+ incrementRefCount: (tableName: string) => void;
24
+ decrementRefCount: (tableName: string) => void;
25
+ };
26
+ declare const DrizzleSqliteContext: react.Context<DrizzleSqliteContextValue<any> | null>;
27
+ type DrizzleSqliteProviderProps<TSchema extends Record<string, unknown>> = PropsWithChildren<{
28
+ worker: new () => Worker;
29
+ dbName: string;
30
+ schema: TSchema;
31
+ migrations: DurableSqliteMigrationConfig;
32
+ debug?: boolean;
33
+ enableCheckpoint?: boolean;
34
+ /**
35
+ * Sync mode: 'eager' (immediate) or 'on-demand' (lazy)
36
+ */
37
+ syncMode?: "eager" | "on-demand";
38
+ /**
39
+ * Optional interceptor for tracking SQLite operations (for testing/debugging)
40
+ */
41
+ interceptor?: SQLInterceptor;
42
+ /**
43
+ * Worker DB pragmas on first open of `dbName` this session (see `useDrizzleSqliteDb`).
44
+ */
45
+ workerOpenOptions?: SqliteWasmWorkerOpenOptions;
46
+ }>;
47
+ declare function DrizzleSqliteProvider<TSchema extends Record<string, unknown>>({ children, worker, dbName, schema, migrations, debug, enableCheckpoint, syncMode, interceptor, workerOpenOptions, }: DrizzleSqliteProviderProps<TSchema>): react_jsx_runtime.JSX.Element;
48
+ declare function useSqliteCollection<TSchema extends Record<string, unknown>, TTableName extends string & ValidTableNames<TSchema>>(context: DrizzleSqliteContextValue<TSchema>, tableName: TTableName): InferCollectionFromTable<GetTableFromSchema<TSchema, TTableName>>;
49
+
50
+ export { DrizzleSqliteContext, type DrizzleSqliteContextValue, DrizzleSqliteProvider, useSqliteCollection };
@@ -0,0 +1,11 @@
1
+ export { DrizzleSqliteContext, DrizzleSqliteProvider, useSqliteCollection } from '../chunk-AEYHRJVN.js';
2
+ import '../chunk-WFFFP6DB.js';
3
+ import '../chunk-NSPVPJKE.js';
4
+ import '../chunk-FIVEPVOM.js';
5
+ import '../chunk-AGMKOHXO.js';
6
+ import '../chunk-GVUNHU6J.js';
7
+ import '../chunk-6LNYKOKR.js';
8
+ import '../chunk-BZVMUTJ7.js';
9
+ import '../chunk-FRONXNEA.js';
10
+ //# sourceMappingURL=DrizzleSqliteProvider.js.map
11
+ //# sourceMappingURL=DrizzleSqliteProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"DrizzleSqliteProvider.js"}
@@ -0,0 +1,23 @@
1
+ import { DrizzleSqliteContextValue, useSqliteCollection } from './DrizzleSqliteProvider.js';
2
+ import { ValidTableNames } from '../collections/sqlite-collection.js';
3
+ import 'react/jsx-runtime';
4
+ import 'react';
5
+ import 'drizzle-orm/sqlite-proxy';
6
+ import '@tanstack/db';
7
+ import '../migration/migrator.js';
8
+ import 'drizzle-orm/durable-sqlite/migrator';
9
+ import '../worker/sqlite-open-options.js';
10
+ import 'zod';
11
+ import '@firtoz/drizzle-utils';
12
+ import 'drizzle-orm';
13
+ import 'drizzle-orm/sqlite-core';
14
+ import '@firtoz/db-helpers';
15
+
16
+ type UseDrizzleSqliteReturn<TSchema extends Record<string, unknown>> = {
17
+ drizzle: DrizzleSqliteContextValue<TSchema>["drizzle"];
18
+ readyPromise: DrizzleSqliteContextValue<TSchema>["readyPromise"];
19
+ useCollection: <TTableName extends string & ValidTableNames<TSchema>>(tableName: TTableName) => ReturnType<typeof useSqliteCollection<TSchema, TTableName>>;
20
+ };
21
+ declare function useDrizzleSqlite<TSchema extends Record<string, unknown>>(): UseDrizzleSqliteReturn<TSchema>;
22
+
23
+ export { type UseDrizzleSqliteReturn, useDrizzleSqlite };
@@ -0,0 +1,12 @@
1
+ export { useDrizzleSqlite } from '../chunk-H2F2HZ2A.js';
2
+ import '../chunk-AEYHRJVN.js';
3
+ import '../chunk-WFFFP6DB.js';
4
+ import '../chunk-NSPVPJKE.js';
5
+ import '../chunk-FIVEPVOM.js';
6
+ import '../chunk-AGMKOHXO.js';
7
+ import '../chunk-GVUNHU6J.js';
8
+ import '../chunk-6LNYKOKR.js';
9
+ import '../chunk-BZVMUTJ7.js';
10
+ import '../chunk-FRONXNEA.js';
11
+ //# sourceMappingURL=useDrizzleSqlite.js.map
12
+ //# sourceMappingURL=useDrizzleSqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"useDrizzleSqlite.js"}
@@ -0,0 +1,8 @@
1
+ import * as drizzle_orm_sqlite_proxy from 'drizzle-orm/sqlite-proxy';
2
+ import { Database } from '../types.js';
3
+ import { DrizzleConfig } from 'drizzle-orm';
4
+ import '@sqlite.org/sqlite-wasm';
5
+
6
+ declare const drizzleSqliteWasm: <TSchema extends Record<string, unknown> = Record<string, never>>(sqliteDb: Database, config?: DrizzleConfig<TSchema>, debug?: boolean) => drizzle_orm_sqlite_proxy.SqliteRemoteDatabase<TSchema>;
7
+
8
+ export { drizzleSqliteWasm };
@@ -0,0 +1,4 @@
1
+ export { drizzleSqliteWasm } from '../chunk-TZP4AIBR.js';
2
+ import '../chunk-BJDPMGFF.js';
3
+ //# sourceMappingURL=direct.js.map
4
+ //# sourceMappingURL=direct.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"direct.js"}
@@ -0,0 +1,15 @@
1
+ import { MaybeError } from '@firtoz/maybe-error';
2
+ import { Database } from '../types.js';
3
+ import '@sqlite.org/sqlite-wasm';
4
+
5
+ declare const handleRemoteCallback: ({ sqliteDb, sql, params, method, debug: _debug, }: {
6
+ sqliteDb: Database;
7
+ sql: string;
8
+ params: any[];
9
+ method: "run" | "all" | "values" | "get";
10
+ debug?: boolean;
11
+ }) => Promise<MaybeError<{
12
+ rows: unknown[];
13
+ }, string>>;
14
+
15
+ export { handleRemoteCallback };
@@ -0,0 +1,3 @@
1
+ export { handleRemoteCallback } from '../chunk-BJDPMGFF.js';
2
+ //# sourceMappingURL=handle-callback.js.map
3
+ //# sourceMappingURL=handle-callback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"handle-callback.js"}
@@ -0,0 +1,15 @@
1
+ import * as drizzle_orm_sqlite_proxy from 'drizzle-orm/sqlite-proxy';
2
+ import { DrizzleConfig } from 'drizzle-orm';
3
+ import { ISqliteWorkerClient } from '../worker/client.js';
4
+ import { SQLInterceptor } from '@firtoz/drizzle-utils';
5
+ import '../worker/schema.js';
6
+ import 'zod';
7
+
8
+ declare const drizzleSqliteWasmWorker: <TSchema extends Record<string, unknown> = Record<string, never>>(client: ISqliteWorkerClient, config?: DrizzleConfig<TSchema>) => drizzle_orm_sqlite_proxy.SqliteRemoteDatabase<TSchema>;
9
+ /**
10
+ * Creates an instrumented Drizzle instance that logs all SQL queries.
11
+ * This wraps the standard drizzleSqliteWasmWorker to intercept every query.
12
+ */
13
+ declare const createInstrumentedDrizzle: <TSchema extends Record<string, unknown> = Record<string, never>>(client: ISqliteWorkerClient, config?: DrizzleConfig<TSchema>, interceptor?: SQLInterceptor) => drizzle_orm_sqlite_proxy.SqliteRemoteDatabase<TSchema>;
14
+
15
+ export { createInstrumentedDrizzle, drizzleSqliteWasmWorker };
@@ -0,0 +1,3 @@
1
+ export { createInstrumentedDrizzle, drizzleSqliteWasmWorker } from '../chunk-FRONXNEA.js';
2
+ //# sourceMappingURL=worker.js.map
3
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"worker.js"}