@powersync/web 1.12.2 → 1.13.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/README.md +33 -0
- package/dist/3cb48be086dd9edd02ff.wasm +0 -0
- package/dist/{_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js-async-mutex-c-3cff7d0.index.umd.js → _journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js-_powersync_co-780aa20.index.umd.js} +18 -8
- package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js-_powersync_co-780aa20.index.umd.js.map +1 -0
- package/dist/{_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js-async-mutex-c-3cff7d1.index.umd.js → _journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js-_powersync_co-780aa21.index.umd.js} +18 -8
- package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js-_powersync_co-780aa21.index.umd.js.map +1 -0
- package/dist/df958358cadf945bd0fe.wasm +0 -0
- package/dist/f9c8ada26c59f5bf4339.wasm +0 -0
- package/dist/fe5693c7678cf12e05ac.wasm +0 -0
- package/dist/index.umd.js +3709 -499
- package/dist/index.umd.js.map +1 -1
- package/dist/worker/SharedSyncImplementation.umd.js +247 -2172
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
- package/dist/worker/WASQLiteDB.umd.js +767 -196
- package/dist/worker/WASQLiteDB.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js +45 -0
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js.map +1 -0
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js +45 -0
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js.map +1 -0
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js +2 -2
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +45 -0
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map +1 -0
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js.umd.js +1509 -0
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js.umd.js.map +1 -0
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js +39 -0
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js +1641 -0
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js.map +1 -0
- package/lib/package.json +6 -6
- package/lib/src/db/PowerSyncDatabase.d.ts +10 -2
- package/lib/src/db/PowerSyncDatabase.js +20 -4
- package/lib/src/db/adapters/AbstractWebSQLOpenFactory.d.ts +2 -0
- package/lib/src/db/adapters/AbstractWebSQLOpenFactory.js +3 -0
- package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +26 -0
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +82 -0
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +239 -0
- package/lib/src/db/adapters/WebDBAdapter.d.ts +17 -0
- package/lib/src/db/adapters/WebDBAdapter.js +1 -0
- package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +39 -0
- package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +46 -0
- package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.d.ts +127 -0
- package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js +343 -0
- package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.d.ts +10 -42
- package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.js +36 -212
- package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +12 -0
- package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +81 -4
- package/lib/src/db/adapters/web-sql-flags.d.ts +17 -0
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +9 -2
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +16 -10
- package/lib/src/db/sync/WebStreamingSyncImplementation.d.ts +0 -5
- package/lib/src/index.d.ts +8 -7
- package/lib/src/index.js +8 -7
- package/lib/src/worker/db/WASQLiteDB.worker.js +38 -20
- package/lib/src/worker/db/open-worker-database.d.ts +5 -4
- package/lib/src/worker/db/open-worker-database.js +5 -3
- package/lib/src/worker/sync/AbstractSharedSyncClientProvider.d.ts +1 -0
- package/lib/src/worker/sync/SharedSyncImplementation.d.ts +20 -3
- package/lib/src/worker/sync/SharedSyncImplementation.js +40 -11
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/dist/24cd027f23123a1360de.wasm +0 -0
- package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js-async-mutex-c-3cff7d0.index.umd.js.map +0 -1
- package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js-async-mutex-c-3cff7d1.index.umd.js.map +0 -1
- package/lib/src/shared/open-db.d.ts +0 -5
- package/lib/src/shared/open-db.js +0 -193
- package/lib/src/shared/types.d.ts +0 -22
- /package/lib/src/{shared/types.js → db/adapters/AsyncDatabaseConnection.js} +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as Comlink from 'comlink';
|
|
2
|
+
import { AsyncDatabaseConnection, OnTableChangeCallback, OpenAsyncDatabaseConnection, ProxiedQueryResult } from './AsyncDatabaseConnection';
|
|
3
|
+
import { ResolvedWebSQLOpenOptions } from './web-sql-flags';
|
|
4
|
+
export type SharedConnectionWorker = {
|
|
5
|
+
identifier: string;
|
|
6
|
+
port: MessagePort;
|
|
7
|
+
};
|
|
8
|
+
export type WrappedWorkerConnectionOptions<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions> = {
|
|
9
|
+
baseConnection: AsyncDatabaseConnection;
|
|
10
|
+
identifier: string;
|
|
11
|
+
/**
|
|
12
|
+
* Need a remote in order to keep a reference to the Proxied worker
|
|
13
|
+
*/
|
|
14
|
+
remote: Comlink.Remote<OpenAsyncDatabaseConnection<Config>>;
|
|
15
|
+
onClose?: () => void;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Wraps a provided instance of {@link AsyncDatabaseConnection}, providing necessary proxy
|
|
19
|
+
* functions for worker listeners.
|
|
20
|
+
*/
|
|
21
|
+
export declare class WorkerWrappedAsyncDatabaseConnection<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions> implements AsyncDatabaseConnection {
|
|
22
|
+
protected options: WrappedWorkerConnectionOptions<Config>;
|
|
23
|
+
constructor(options: WrappedWorkerConnectionOptions<Config>);
|
|
24
|
+
protected get baseConnection(): AsyncDatabaseConnection<ResolvedWebSQLOpenOptions>;
|
|
25
|
+
init(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Get a MessagePort which can be used to share the internals of this connection.
|
|
28
|
+
*/
|
|
29
|
+
shareConnection(): Promise<SharedConnectionWorker>;
|
|
30
|
+
/**
|
|
31
|
+
* Registers a table change notification callback with the base database.
|
|
32
|
+
* This can be extended by custom implementations in order to handle proxy events.
|
|
33
|
+
*/
|
|
34
|
+
registerOnTableChange(callback: OnTableChangeCallback): Promise<() => void>;
|
|
35
|
+
close(): Promise<void>;
|
|
36
|
+
execute(sql: string, params?: any[]): Promise<ProxiedQueryResult>;
|
|
37
|
+
executeBatch(sql: string, params?: any[]): Promise<ProxiedQueryResult>;
|
|
38
|
+
getConfig(): Promise<ResolvedWebSQLOpenOptions>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as Comlink from 'comlink';
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a provided instance of {@link AsyncDatabaseConnection}, providing necessary proxy
|
|
4
|
+
* functions for worker listeners.
|
|
5
|
+
*/
|
|
6
|
+
export class WorkerWrappedAsyncDatabaseConnection {
|
|
7
|
+
options;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
get baseConnection() {
|
|
12
|
+
return this.options.baseConnection;
|
|
13
|
+
}
|
|
14
|
+
init() {
|
|
15
|
+
return this.baseConnection.init();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get a MessagePort which can be used to share the internals of this connection.
|
|
19
|
+
*/
|
|
20
|
+
async shareConnection() {
|
|
21
|
+
const { identifier, remote } = this.options;
|
|
22
|
+
const newPort = await remote[Comlink.createEndpoint]();
|
|
23
|
+
return { port: newPort, identifier };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Registers a table change notification callback with the base database.
|
|
27
|
+
* This can be extended by custom implementations in order to handle proxy events.
|
|
28
|
+
*/
|
|
29
|
+
async registerOnTableChange(callback) {
|
|
30
|
+
return this.baseConnection.registerOnTableChange(Comlink.proxy(callback));
|
|
31
|
+
}
|
|
32
|
+
async close() {
|
|
33
|
+
await this.baseConnection.close();
|
|
34
|
+
this.options.remote[Comlink.releaseProxy]();
|
|
35
|
+
this.options.onClose?.();
|
|
36
|
+
}
|
|
37
|
+
execute(sql, params) {
|
|
38
|
+
return this.baseConnection.execute(sql, params);
|
|
39
|
+
}
|
|
40
|
+
executeBatch(sql, params) {
|
|
41
|
+
return this.baseConnection.executeBatch(sql, params);
|
|
42
|
+
}
|
|
43
|
+
getConfig() {
|
|
44
|
+
return this.baseConnection.getConfig();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as SQLite from '@journeyapps/wa-sqlite';
|
|
2
|
+
import { BaseObserver, BatchedUpdateNotification } from '@powersync/common';
|
|
3
|
+
import { Mutex } from 'async-mutex';
|
|
4
|
+
import { AsyncDatabaseConnection, OnTableChangeCallback, ProxiedQueryResult } from '../AsyncDatabaseConnection';
|
|
5
|
+
import { ResolvedWASQLiteOpenFactoryOptions } from './WASQLiteOpenFactory';
|
|
6
|
+
/**
|
|
7
|
+
* List of currently tested virtual filesystems
|
|
8
|
+
*/
|
|
9
|
+
export declare enum WASQLiteVFS {
|
|
10
|
+
IDBBatchAtomicVFS = "IDBBatchAtomicVFS",
|
|
11
|
+
OPFSCoopSyncVFS = "OPFSCoopSyncVFS",
|
|
12
|
+
AccessHandlePoolVFS = "AccessHandlePoolVFS"
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export type WASQLiteBroadCastTableUpdateEvent = {
|
|
18
|
+
changedTables: Set<string>;
|
|
19
|
+
connectionId: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export type WASQLiteConnectionListener = {
|
|
25
|
+
tablesUpdated: (event: BatchedUpdateNotification) => void;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export type SQLiteModule = Parameters<typeof SQLite.Factory>[0];
|
|
31
|
+
/**
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
export type WASQLiteModuleFactoryOptions = {
|
|
35
|
+
dbFileName: string;
|
|
36
|
+
encryptionKey?: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
export type WASQLiteModuleFactory = (options: WASQLiteModuleFactoryOptions) => Promise<{
|
|
42
|
+
module: SQLiteModule;
|
|
43
|
+
vfs: SQLiteVFS;
|
|
44
|
+
}>;
|
|
45
|
+
/**
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
export declare const AsyncWASQLiteModuleFactory: () => Promise<any>;
|
|
49
|
+
/**
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
export declare const MultiCipherAsyncWASQLiteModuleFactory: () => Promise<any>;
|
|
53
|
+
/**
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
export declare const SyncWASQLiteModuleFactory: () => Promise<any>;
|
|
57
|
+
/**
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
export declare const MultiCipherSyncWASQLiteModuleFactory: () => Promise<any>;
|
|
61
|
+
/**
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
export declare const DEFAULT_MODULE_FACTORIES: {
|
|
65
|
+
IDBBatchAtomicVFS: (options: WASQLiteModuleFactoryOptions) => Promise<{
|
|
66
|
+
module: any;
|
|
67
|
+
vfs: any;
|
|
68
|
+
}>;
|
|
69
|
+
AccessHandlePoolVFS: (options: WASQLiteModuleFactoryOptions) => Promise<{
|
|
70
|
+
module: any;
|
|
71
|
+
vfs: any;
|
|
72
|
+
}>;
|
|
73
|
+
OPFSCoopSyncVFS: (options: WASQLiteModuleFactoryOptions) => Promise<{
|
|
74
|
+
module: any;
|
|
75
|
+
vfs: any;
|
|
76
|
+
}>;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* @internal
|
|
80
|
+
* WA-SQLite connection which directly interfaces with WA-SQLite.
|
|
81
|
+
* This is usually instantiated inside a worker.
|
|
82
|
+
*/
|
|
83
|
+
export declare class WASqliteConnection extends BaseObserver<WASQLiteConnectionListener> implements AsyncDatabaseConnection<ResolvedWASQLiteOpenFactoryOptions> {
|
|
84
|
+
protected options: ResolvedWASQLiteOpenFactoryOptions;
|
|
85
|
+
private _sqliteAPI;
|
|
86
|
+
private _dbP;
|
|
87
|
+
private _moduleFactory;
|
|
88
|
+
protected updatedTables: Set<string>;
|
|
89
|
+
protected updateTimer: ReturnType<typeof setTimeout> | null;
|
|
90
|
+
protected statementMutex: Mutex;
|
|
91
|
+
protected broadcastChannel: BroadcastChannel | null;
|
|
92
|
+
/**
|
|
93
|
+
* Unique id for this specific connection. This is used to prevent broadcast table change
|
|
94
|
+
* notification loops.
|
|
95
|
+
*/
|
|
96
|
+
protected connectionId: number;
|
|
97
|
+
constructor(options: ResolvedWASQLiteOpenFactoryOptions);
|
|
98
|
+
protected get sqliteAPI(): SQLiteAPI;
|
|
99
|
+
protected get dbP(): number;
|
|
100
|
+
protected openDB(): Promise<number>;
|
|
101
|
+
protected executeEncryptionPragma(): Promise<void>;
|
|
102
|
+
protected openSQLiteAPI(): Promise<SQLiteAPI>;
|
|
103
|
+
protected registerBroadcastListeners(): void;
|
|
104
|
+
protected queueTableUpdate(tableNames: Set<string>, shouldBroadcast?: boolean): void;
|
|
105
|
+
init(): Promise<void>;
|
|
106
|
+
getConfig(): Promise<ResolvedWASQLiteOpenFactoryOptions>;
|
|
107
|
+
fireUpdates(shouldBroadcast?: boolean): void;
|
|
108
|
+
/**
|
|
109
|
+
* This executes SQL statements in a batch.
|
|
110
|
+
*/
|
|
111
|
+
executeBatch(sql: string, bindings?: any[][]): Promise<ProxiedQueryResult>;
|
|
112
|
+
/**
|
|
113
|
+
* This executes single SQL statements inside a requested lock.
|
|
114
|
+
*/
|
|
115
|
+
execute(sql: string | TemplateStringsArray, bindings?: any[]): Promise<ProxiedQueryResult>;
|
|
116
|
+
close(): Promise<void>;
|
|
117
|
+
registerOnTableChange(callback: OnTableChangeCallback): Promise<() => void>;
|
|
118
|
+
/**
|
|
119
|
+
* This requests a lock for executing statements.
|
|
120
|
+
* Should only be used internally.
|
|
121
|
+
*/
|
|
122
|
+
protected acquireExecuteLock: <T>(callback: () => Promise<T>) => Promise<T>;
|
|
123
|
+
/**
|
|
124
|
+
* This executes a single statement using SQLite3.
|
|
125
|
+
*/
|
|
126
|
+
protected executeSingleStatement(sql: string | TemplateStringsArray, bindings?: any[]): Promise<ProxiedQueryResult>;
|
|
127
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import * as SQLite from '@journeyapps/wa-sqlite';
|
|
2
|
+
import { BaseObserver } from '@powersync/common';
|
|
3
|
+
import { Mutex } from 'async-mutex';
|
|
4
|
+
/**
|
|
5
|
+
* List of currently tested virtual filesystems
|
|
6
|
+
*/
|
|
7
|
+
export var WASQLiteVFS;
|
|
8
|
+
(function (WASQLiteVFS) {
|
|
9
|
+
WASQLiteVFS["IDBBatchAtomicVFS"] = "IDBBatchAtomicVFS";
|
|
10
|
+
WASQLiteVFS["OPFSCoopSyncVFS"] = "OPFSCoopSyncVFS";
|
|
11
|
+
WASQLiteVFS["AccessHandlePoolVFS"] = "AccessHandlePoolVFS";
|
|
12
|
+
})(WASQLiteVFS || (WASQLiteVFS = {}));
|
|
13
|
+
/**
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export const AsyncWASQLiteModuleFactory = async () => {
|
|
17
|
+
const { default: factory } = await import('@journeyapps/wa-sqlite/dist/wa-sqlite-async.mjs');
|
|
18
|
+
return factory();
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export const MultiCipherAsyncWASQLiteModuleFactory = async () => {
|
|
24
|
+
const { default: factory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite-async.mjs');
|
|
25
|
+
return factory();
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export const SyncWASQLiteModuleFactory = async () => {
|
|
31
|
+
const { default: factory } = await import('@journeyapps/wa-sqlite/dist/wa-sqlite.mjs');
|
|
32
|
+
return factory();
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export const MultiCipherSyncWASQLiteModuleFactory = async () => {
|
|
38
|
+
const { default: factory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite.mjs');
|
|
39
|
+
return factory();
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
export const DEFAULT_MODULE_FACTORIES = {
|
|
45
|
+
[WASQLiteVFS.IDBBatchAtomicVFS]: async (options) => {
|
|
46
|
+
let module;
|
|
47
|
+
if (options.encryptionKey) {
|
|
48
|
+
module = await MultiCipherAsyncWASQLiteModuleFactory();
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
module = await AsyncWASQLiteModuleFactory();
|
|
52
|
+
}
|
|
53
|
+
const { IDBBatchAtomicVFS } = await import('@journeyapps/wa-sqlite/src/examples/IDBBatchAtomicVFS.js');
|
|
54
|
+
return {
|
|
55
|
+
module,
|
|
56
|
+
// @ts-expect-error The types for this static method are missing upstream
|
|
57
|
+
vfs: await IDBBatchAtomicVFS.create(options.dbFileName, module, { lockPolicy: 'exclusive' })
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
[WASQLiteVFS.AccessHandlePoolVFS]: async (options) => {
|
|
61
|
+
let module;
|
|
62
|
+
if (options.encryptionKey) {
|
|
63
|
+
module = await MultiCipherSyncWASQLiteModuleFactory();
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
module = await SyncWASQLiteModuleFactory();
|
|
67
|
+
}
|
|
68
|
+
// @ts-expect-error The types for this static method are missing upstream
|
|
69
|
+
const { AccessHandlePoolVFS } = await import('@journeyapps/wa-sqlite/src/examples/AccessHandlePoolVFS.js');
|
|
70
|
+
return {
|
|
71
|
+
module,
|
|
72
|
+
vfs: await AccessHandlePoolVFS.create(options.dbFileName, module)
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
[WASQLiteVFS.OPFSCoopSyncVFS]: async (options) => {
|
|
76
|
+
let module;
|
|
77
|
+
if (options.encryptionKey) {
|
|
78
|
+
module = await MultiCipherSyncWASQLiteModuleFactory();
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
module = await SyncWASQLiteModuleFactory();
|
|
82
|
+
}
|
|
83
|
+
// @ts-expect-error The types for this static method are missing upstream
|
|
84
|
+
const { OPFSCoopSyncVFS } = await import('@journeyapps/wa-sqlite/src/examples/OPFSCoopSyncVFS.js');
|
|
85
|
+
return {
|
|
86
|
+
module,
|
|
87
|
+
vfs: await OPFSCoopSyncVFS.create(options.dbFileName, module)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* @internal
|
|
93
|
+
* WA-SQLite connection which directly interfaces with WA-SQLite.
|
|
94
|
+
* This is usually instantiated inside a worker.
|
|
95
|
+
*/
|
|
96
|
+
export class WASqliteConnection extends BaseObserver {
|
|
97
|
+
options;
|
|
98
|
+
_sqliteAPI = null;
|
|
99
|
+
_dbP = null;
|
|
100
|
+
_moduleFactory;
|
|
101
|
+
updatedTables;
|
|
102
|
+
updateTimer;
|
|
103
|
+
statementMutex;
|
|
104
|
+
broadcastChannel;
|
|
105
|
+
/**
|
|
106
|
+
* Unique id for this specific connection. This is used to prevent broadcast table change
|
|
107
|
+
* notification loops.
|
|
108
|
+
*/
|
|
109
|
+
connectionId;
|
|
110
|
+
constructor(options) {
|
|
111
|
+
super();
|
|
112
|
+
this.options = options;
|
|
113
|
+
this.updatedTables = new Set();
|
|
114
|
+
this.updateTimer = null;
|
|
115
|
+
this.broadcastChannel = null;
|
|
116
|
+
this.connectionId = new Date().valueOf() + Math.random();
|
|
117
|
+
this.statementMutex = new Mutex();
|
|
118
|
+
this._moduleFactory = DEFAULT_MODULE_FACTORIES[this.options.vfs];
|
|
119
|
+
}
|
|
120
|
+
get sqliteAPI() {
|
|
121
|
+
if (!this._sqliteAPI) {
|
|
122
|
+
throw new Error(`Initialization has not completed`);
|
|
123
|
+
}
|
|
124
|
+
return this._sqliteAPI;
|
|
125
|
+
}
|
|
126
|
+
get dbP() {
|
|
127
|
+
if (!this._dbP) {
|
|
128
|
+
throw new Error(`Initialization has not completed`);
|
|
129
|
+
}
|
|
130
|
+
return this._dbP;
|
|
131
|
+
}
|
|
132
|
+
async openDB() {
|
|
133
|
+
this._dbP = await this.sqliteAPI.open_v2(this.options.dbFilename);
|
|
134
|
+
return this._dbP;
|
|
135
|
+
}
|
|
136
|
+
async executeEncryptionPragma() {
|
|
137
|
+
if (this.options.encryptionKey) {
|
|
138
|
+
await this.executeSingleStatement(`PRAGMA key = "${this.options.encryptionKey}"`);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
async openSQLiteAPI() {
|
|
143
|
+
const { module, vfs } = await this._moduleFactory({
|
|
144
|
+
dbFileName: this.options.dbFilename,
|
|
145
|
+
encryptionKey: this.options.encryptionKey
|
|
146
|
+
});
|
|
147
|
+
const sqlite3 = SQLite.Factory(module);
|
|
148
|
+
sqlite3.vfs_register(vfs, true);
|
|
149
|
+
/**
|
|
150
|
+
* Register the PowerSync core SQLite extension
|
|
151
|
+
*/
|
|
152
|
+
module.ccall('powersync_init_static', 'int', []);
|
|
153
|
+
/**
|
|
154
|
+
* Create the multiple cipher vfs if an encryption key is provided
|
|
155
|
+
*/
|
|
156
|
+
if (this.options.encryptionKey) {
|
|
157
|
+
const createResult = module.ccall('sqlite3mc_vfs_create', 'int', ['string', 'int'], [this.options.dbFilename, 1]);
|
|
158
|
+
if (createResult !== 0) {
|
|
159
|
+
throw new Error('Failed to create multiple cipher vfs, Database encryption will not work');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return sqlite3;
|
|
163
|
+
}
|
|
164
|
+
registerBroadcastListeners() {
|
|
165
|
+
this.broadcastChannel = new BroadcastChannel(`${this.options.dbFilename}-table-updates`);
|
|
166
|
+
this.broadcastChannel.addEventListener('message', (event) => {
|
|
167
|
+
const data = event.data;
|
|
168
|
+
if (this.connectionId == data.connectionId) {
|
|
169
|
+
// Ignore messages from the same connection
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// Ensuring that we don't rebroadcast the same message
|
|
173
|
+
this.queueTableUpdate(data.changedTables, false);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
queueTableUpdate(tableNames, shouldBroadcast = true) {
|
|
177
|
+
tableNames.forEach((tableName) => this.updatedTables.add(tableName));
|
|
178
|
+
if (this.updateTimer == null) {
|
|
179
|
+
this.updateTimer = setTimeout(() => this.fireUpdates(shouldBroadcast), 0);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async init() {
|
|
183
|
+
this._sqliteAPI = await this.openSQLiteAPI();
|
|
184
|
+
await this.openDB();
|
|
185
|
+
this.registerBroadcastListeners();
|
|
186
|
+
await this.executeSingleStatement(`PRAGMA temp_store = ${this.options.temporaryStorage};`);
|
|
187
|
+
await this.executeEncryptionPragma();
|
|
188
|
+
this.sqliteAPI.update_hook(this.dbP, (updateType, dbName, tableName) => {
|
|
189
|
+
if (!tableName) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const changedTables = new Set([tableName]);
|
|
193
|
+
this.queueTableUpdate(changedTables);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
async getConfig() {
|
|
197
|
+
return this.options;
|
|
198
|
+
}
|
|
199
|
+
fireUpdates(shouldBroadcast = true) {
|
|
200
|
+
this.updateTimer = null;
|
|
201
|
+
const event = { tables: [...this.updatedTables], groupedUpdates: {}, rawUpdates: [] };
|
|
202
|
+
// Share to other connections
|
|
203
|
+
if (shouldBroadcast) {
|
|
204
|
+
this.broadcastChannel.postMessage({
|
|
205
|
+
changedTables: this.updatedTables,
|
|
206
|
+
connectionId: this.connectionId
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
this.updatedTables.clear();
|
|
210
|
+
this.iterateListeners((cb) => cb.tablesUpdated?.(event));
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* This executes SQL statements in a batch.
|
|
214
|
+
*/
|
|
215
|
+
async executeBatch(sql, bindings) {
|
|
216
|
+
return this.acquireExecuteLock(async () => {
|
|
217
|
+
let affectedRows = 0;
|
|
218
|
+
try {
|
|
219
|
+
await this.executeSingleStatement('BEGIN TRANSACTION');
|
|
220
|
+
const wrappedBindings = bindings ? bindings : [];
|
|
221
|
+
for await (const stmt of this.sqliteAPI.statements(this.dbP, sql)) {
|
|
222
|
+
if (stmt === null) {
|
|
223
|
+
return {
|
|
224
|
+
rowsAffected: 0,
|
|
225
|
+
rows: { _array: [], length: 0 }
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
//Prepare statement once
|
|
229
|
+
for (const binding of wrappedBindings) {
|
|
230
|
+
// TODO not sure why this is needed currently, but booleans break
|
|
231
|
+
for (let i = 0; i < binding.length; i++) {
|
|
232
|
+
const b = binding[i];
|
|
233
|
+
if (typeof b == 'boolean') {
|
|
234
|
+
binding[i] = b ? 1 : 0;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (bindings) {
|
|
238
|
+
this.sqliteAPI.bind_collection(stmt, binding);
|
|
239
|
+
}
|
|
240
|
+
const result = await this.sqliteAPI.step(stmt);
|
|
241
|
+
if (result === SQLite.SQLITE_DONE) {
|
|
242
|
+
//The value returned by sqlite3_changes() immediately after an INSERT, UPDATE or DELETE statement run on a view is always zero.
|
|
243
|
+
affectedRows += this.sqliteAPI.changes(this.dbP);
|
|
244
|
+
}
|
|
245
|
+
this.sqliteAPI.reset(stmt);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
await this.executeSingleStatement('COMMIT');
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
await this.executeSingleStatement('ROLLBACK');
|
|
252
|
+
return {
|
|
253
|
+
rowsAffected: 0,
|
|
254
|
+
rows: { _array: [], length: 0 }
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const result = {
|
|
258
|
+
rowsAffected: affectedRows,
|
|
259
|
+
rows: { _array: [], length: 0 }
|
|
260
|
+
};
|
|
261
|
+
return result;
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* This executes single SQL statements inside a requested lock.
|
|
266
|
+
*/
|
|
267
|
+
async execute(sql, bindings) {
|
|
268
|
+
// Running multiple statements on the same connection concurrently should not be allowed
|
|
269
|
+
return this.acquireExecuteLock(async () => {
|
|
270
|
+
return this.executeSingleStatement(sql, bindings);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
async close() {
|
|
274
|
+
this.broadcastChannel?.close();
|
|
275
|
+
await this.sqliteAPI.close(this.dbP);
|
|
276
|
+
}
|
|
277
|
+
async registerOnTableChange(callback) {
|
|
278
|
+
return this.registerListener({
|
|
279
|
+
tablesUpdated: (event) => callback(event)
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* This requests a lock for executing statements.
|
|
284
|
+
* Should only be used internally.
|
|
285
|
+
*/
|
|
286
|
+
acquireExecuteLock = (callback) => {
|
|
287
|
+
return this.statementMutex.runExclusive(callback);
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* This executes a single statement using SQLite3.
|
|
291
|
+
*/
|
|
292
|
+
async executeSingleStatement(sql, bindings) {
|
|
293
|
+
const results = [];
|
|
294
|
+
for await (const stmt of this.sqliteAPI.statements(this.dbP, sql)) {
|
|
295
|
+
let columns;
|
|
296
|
+
const wrappedBindings = bindings ? [bindings] : [[]];
|
|
297
|
+
for (const binding of wrappedBindings) {
|
|
298
|
+
// TODO not sure why this is needed currently, but booleans break
|
|
299
|
+
binding.forEach((b, index, arr) => {
|
|
300
|
+
if (typeof b == 'boolean') {
|
|
301
|
+
arr[index] = b ? 1 : 0;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
this.sqliteAPI.reset(stmt);
|
|
305
|
+
if (bindings) {
|
|
306
|
+
this.sqliteAPI.bind_collection(stmt, binding);
|
|
307
|
+
}
|
|
308
|
+
const rows = [];
|
|
309
|
+
while ((await this.sqliteAPI.step(stmt)) === SQLite.SQLITE_ROW) {
|
|
310
|
+
const row = this.sqliteAPI.row(stmt);
|
|
311
|
+
rows.push(row);
|
|
312
|
+
}
|
|
313
|
+
columns = columns ?? this.sqliteAPI.column_names(stmt);
|
|
314
|
+
if (columns.length) {
|
|
315
|
+
results.push({ columns, rows });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// When binding parameters, only a single statement is executed.
|
|
319
|
+
if (bindings) {
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const rows = [];
|
|
324
|
+
for (const resultSet of results) {
|
|
325
|
+
for (const row of resultSet.rows) {
|
|
326
|
+
const outRow = {};
|
|
327
|
+
resultSet.columns.forEach((key, index) => {
|
|
328
|
+
outRow[key] = row[index];
|
|
329
|
+
});
|
|
330
|
+
rows.push(outRow);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const result = {
|
|
334
|
+
insertId: this.sqliteAPI.last_insert_id(this.dbP),
|
|
335
|
+
rowsAffected: this.sqliteAPI.changes(this.dbP),
|
|
336
|
+
rows: {
|
|
337
|
+
_array: rows,
|
|
338
|
+
length: rows.length
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
return result;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type PowerSyncOpenFactoryOptions } from '@powersync/common';
|
|
2
|
+
import { LockedAsyncDatabaseAdapter } from '../LockedAsyncDatabaseAdapter';
|
|
2
3
|
import { ResolvedWebSQLOpenOptions, TemporaryStorageOption, WebSQLFlags } from '../web-sql-flags';
|
|
4
|
+
import { WASQLiteVFS } from './WASQLiteConnection';
|
|
3
5
|
/**
|
|
4
6
|
* These flags are the same as {@link WebSQLFlags}.
|
|
5
7
|
* This export is maintained only for API consistency
|
|
@@ -13,51 +15,17 @@ export interface WASQLiteDBAdapterOptions extends Omit<PowerSyncOpenFactoryOptio
|
|
|
13
15
|
*/
|
|
14
16
|
workerPort?: MessagePort;
|
|
15
17
|
worker?: string | URL | ((options: ResolvedWebSQLOpenOptions) => Worker | SharedWorker);
|
|
18
|
+
vfs?: WASQLiteVFS;
|
|
16
19
|
temporaryStorage?: TemporaryStorageOption;
|
|
20
|
+
/**
|
|
21
|
+
* Encryption key for the database.
|
|
22
|
+
* If set, the database will be encrypted using multiple-ciphers.
|
|
23
|
+
*/
|
|
24
|
+
encryptionKey?: string;
|
|
17
25
|
}
|
|
18
26
|
/**
|
|
19
27
|
* Adapter for WA-SQLite SQLite connections.
|
|
20
28
|
*/
|
|
21
|
-
export declare class WASQLiteDBAdapter extends
|
|
22
|
-
protected options: WASQLiteDBAdapterOptions;
|
|
23
|
-
private initialized;
|
|
24
|
-
private logger;
|
|
25
|
-
private dbGetHelpers;
|
|
26
|
-
private methods;
|
|
27
|
-
private debugMode;
|
|
29
|
+
export declare class WASQLiteDBAdapter extends LockedAsyncDatabaseAdapter {
|
|
28
30
|
constructor(options: WASQLiteDBAdapterOptions);
|
|
29
|
-
get name(): string;
|
|
30
|
-
protected get flags(): Required<WASQLiteFlags>;
|
|
31
|
-
getWorker(): void;
|
|
32
|
-
protected init(): Promise<void>;
|
|
33
|
-
execute(query: string, params?: any[] | undefined): Promise<QueryResult>;
|
|
34
|
-
executeBatch(query: string, params?: any[][]): Promise<QueryResult>;
|
|
35
|
-
/**
|
|
36
|
-
* Wraps the worker execute function, awaiting for it to be available
|
|
37
|
-
*/
|
|
38
|
-
private _execute;
|
|
39
|
-
/**
|
|
40
|
-
* Wraps the worker executeBatch function, awaiting for it to be available
|
|
41
|
-
*/
|
|
42
|
-
private _executeBatch;
|
|
43
|
-
/**
|
|
44
|
-
* Attempts to close the connection.
|
|
45
|
-
* Shared workers might not actually close the connection if other
|
|
46
|
-
* tabs are still using it.
|
|
47
|
-
*/
|
|
48
|
-
close(): void;
|
|
49
|
-
getAll<T>(sql: string, parameters?: any[] | undefined): Promise<T[]>;
|
|
50
|
-
getOptional<T>(sql: string, parameters?: any[] | undefined): Promise<T | null>;
|
|
51
|
-
get<T>(sql: string, parameters?: any[] | undefined): Promise<T>;
|
|
52
|
-
readLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions | undefined): Promise<T>;
|
|
53
|
-
writeLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions | undefined): Promise<T>;
|
|
54
|
-
protected acquireLock(callback: () => Promise<any>): Promise<any>;
|
|
55
|
-
readTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions | undefined): Promise<T>;
|
|
56
|
-
writeTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions | undefined): Promise<T>;
|
|
57
|
-
/**
|
|
58
|
-
* Wraps a lock context into a transaction context
|
|
59
|
-
*/
|
|
60
|
-
private wrapTransaction;
|
|
61
|
-
private generateDBHelpers;
|
|
62
|
-
refreshSchema(): Promise<void>;
|
|
63
31
|
}
|