@powersync/web 1.35.0 → 1.37.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 (91) hide show
  1. package/dist/index.umd.js +1126 -1231
  2. package/dist/index.umd.js.map +1 -1
  3. package/dist/worker/SharedSyncImplementation.umd.js +599 -3086
  4. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  5. package/dist/worker/WASQLiteDB.umd.js +860 -868
  6. package/dist/worker/WASQLiteDB.umd.js.map +1 -1
  7. package/lib/package.json +2 -3
  8. package/lib/src/db/PowerSyncDatabase.d.ts +1 -2
  9. package/lib/src/db/PowerSyncDatabase.js +3 -4
  10. package/lib/src/db/adapters/AsyncWebAdapter.d.ts +40 -0
  11. package/lib/src/db/adapters/AsyncWebAdapter.js +69 -0
  12. package/lib/src/db/adapters/SSRDBAdapter.d.ts +1 -2
  13. package/lib/src/db/adapters/SSRDBAdapter.js +5 -6
  14. package/lib/src/db/adapters/wa-sqlite/ConcurrentConnection.d.ts +56 -0
  15. package/lib/src/db/adapters/wa-sqlite/ConcurrentConnection.js +121 -0
  16. package/lib/src/db/adapters/wa-sqlite/DatabaseClient.d.ts +54 -0
  17. package/lib/src/db/adapters/wa-sqlite/DatabaseClient.js +227 -0
  18. package/lib/src/db/adapters/wa-sqlite/DatabaseServer.d.ts +47 -0
  19. package/lib/src/db/adapters/wa-sqlite/DatabaseServer.js +146 -0
  20. package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.d.ts +46 -0
  21. package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.js +147 -0
  22. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +14 -6
  23. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +66 -39
  24. package/lib/src/db/adapters/wa-sqlite/vfs.d.ts +61 -0
  25. package/lib/src/db/adapters/wa-sqlite/vfs.js +91 -0
  26. package/lib/src/db/adapters/web-sql-flags.d.ts +5 -0
  27. package/lib/src/db/sync/SSRWebStreamingSyncImplementation.d.ts +1 -2
  28. package/lib/src/db/sync/SSRWebStreamingSyncImplementation.js +2 -3
  29. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +4 -19
  30. package/lib/src/index.d.ts +1 -4
  31. package/lib/src/index.js +1 -4
  32. package/lib/src/shared/tab_close_signal.d.ts +11 -0
  33. package/lib/src/shared/tab_close_signal.js +26 -0
  34. package/lib/src/worker/db/MultiDatabaseServer.d.ts +17 -0
  35. package/lib/src/worker/db/MultiDatabaseServer.js +86 -0
  36. package/lib/src/worker/db/WASQLiteDB.worker.js +9 -48
  37. package/lib/src/worker/db/open-worker-database.d.ts +3 -3
  38. package/lib/src/worker/db/open-worker-database.js +1 -1
  39. package/lib/src/worker/sync/SharedSyncImplementation.d.ts +5 -6
  40. package/lib/src/worker/sync/SharedSyncImplementation.js +92 -54
  41. package/lib/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +3 -4
  43. package/src/db/PowerSyncDatabase.ts +3 -3
  44. package/src/db/adapters/AsyncWebAdapter.ts +91 -0
  45. package/src/db/adapters/SSRDBAdapter.ts +7 -7
  46. package/src/db/adapters/wa-sqlite/ConcurrentConnection.ts +137 -0
  47. package/src/db/adapters/wa-sqlite/DatabaseClient.ts +325 -0
  48. package/src/db/adapters/wa-sqlite/DatabaseServer.ts +201 -0
  49. package/src/db/adapters/wa-sqlite/RawSqliteConnection.ts +191 -0
  50. package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +87 -43
  51. package/src/db/adapters/wa-sqlite/vfs.ts +112 -0
  52. package/src/db/adapters/web-sql-flags.ts +6 -0
  53. package/src/db/sync/SSRWebStreamingSyncImplementation.ts +2 -3
  54. package/src/db/sync/SharedWebStreamingSyncImplementation.ts +4 -20
  55. package/src/index.ts +1 -4
  56. package/src/shared/tab_close_signal.ts +28 -0
  57. package/src/worker/db/MultiDatabaseServer.ts +104 -0
  58. package/src/worker/db/WASQLiteDB.worker.ts +10 -57
  59. package/src/worker/db/open-worker-database.ts +3 -3
  60. package/src/worker/sync/SharedSyncImplementation.ts +118 -58
  61. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js +0 -1878
  62. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js.map +0 -1
  63. package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-97ebe9.index.umd.js +0 -555
  64. package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-97ebe9.index.umd.js.map +0 -1
  65. package/lib/src/db/adapters/AbstractWebSQLOpenFactory.d.ts +0 -17
  66. package/lib/src/db/adapters/AbstractWebSQLOpenFactory.js +0 -33
  67. package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +0 -49
  68. package/lib/src/db/adapters/AsyncDatabaseConnection.js +0 -1
  69. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +0 -109
  70. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +0 -401
  71. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +0 -59
  72. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +0 -147
  73. package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.d.ts +0 -12
  74. package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.js +0 -19
  75. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.d.ts +0 -155
  76. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js +0 -401
  77. package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.d.ts +0 -32
  78. package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.js +0 -49
  79. package/lib/src/worker/db/SharedWASQLiteConnection.d.ts +0 -42
  80. package/lib/src/worker/db/SharedWASQLiteConnection.js +0 -90
  81. package/lib/src/worker/db/WorkerWASQLiteConnection.d.ts +0 -9
  82. package/lib/src/worker/db/WorkerWASQLiteConnection.js +0 -12
  83. package/src/db/adapters/AbstractWebSQLOpenFactory.ts +0 -48
  84. package/src/db/adapters/AsyncDatabaseConnection.ts +0 -55
  85. package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +0 -490
  86. package/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts +0 -201
  87. package/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.ts +0 -23
  88. package/src/db/adapters/wa-sqlite/WASQLiteConnection.ts +0 -497
  89. package/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts +0 -86
  90. package/src/worker/db/SharedWASQLiteConnection.ts +0 -131
  91. package/src/worker/db/WorkerWASQLiteConnection.ts +0 -14
@@ -0,0 +1,91 @@
1
+ /**
2
+ * List of currently tested virtual filesystems
3
+ */
4
+ export var WASQLiteVFS;
5
+ (function (WASQLiteVFS) {
6
+ WASQLiteVFS["IDBBatchAtomicVFS"] = "IDBBatchAtomicVFS";
7
+ WASQLiteVFS["OPFSCoopSyncVFS"] = "OPFSCoopSyncVFS";
8
+ WASQLiteVFS["AccessHandlePoolVFS"] = "AccessHandlePoolVFS";
9
+ })(WASQLiteVFS || (WASQLiteVFS = {}));
10
+ export function vfsRequiresDedicatedWorkers(vfs) {
11
+ return vfs != WASQLiteVFS.IDBBatchAtomicVFS;
12
+ }
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
+ const vfs = await OPFSCoopSyncVFS.create(options.dbFileName, module);
86
+ return {
87
+ module,
88
+ vfs
89
+ };
90
+ }
91
+ };
@@ -62,6 +62,11 @@ export interface WebSQLOpenFactoryOptions extends SQLOpenOptions {
62
62
  * or a factory method that returns a worker.
63
63
  */
64
64
  worker?: string | URL | ((options: ResolvedWebSQLOpenOptions) => Worker | SharedWorker);
65
+ /**
66
+ * Use an existing port to an initialized worker.
67
+ * A worker will be initialized if none is provided
68
+ */
69
+ workerPort?: MessagePort;
65
70
  logger?: ILogger;
66
71
  /**
67
72
  * Where to store SQLite temporary files. Defaults to 'MEMORY'.
@@ -1,5 +1,4 @@
1
- import { AbstractStreamingSyncImplementationOptions, BaseObserver, LockOptions, PowerSyncConnectionOptions, StreamingSyncImplementation, SyncStatus, SyncStatusOptions } from '@powersync/common';
2
- import { Mutex } from 'async-mutex';
1
+ import { AbstractStreamingSyncImplementationOptions, BaseObserver, LockOptions, Mutex, PowerSyncConnectionOptions, StreamingSyncImplementation, SyncStatus, SyncStatusOptions } from '@powersync/common';
3
2
  export declare class SSRStreamingSyncImplementation extends BaseObserver implements StreamingSyncImplementation {
4
3
  syncMutex: Mutex;
5
4
  crudMutex: Mutex;
@@ -1,5 +1,4 @@
1
- import { BaseObserver, LockType, SyncStatus } from '@powersync/common';
2
- import { Mutex } from 'async-mutex';
1
+ import { BaseObserver, LockType, Mutex, SyncStatus } from '@powersync/common';
3
2
  export class SSRStreamingSyncImplementation extends BaseObserver {
4
3
  syncMutex;
5
4
  crudMutex;
@@ -15,7 +14,7 @@ export class SSRStreamingSyncImplementation extends BaseObserver {
15
14
  }
16
15
  obtainLock(lockOptions) {
17
16
  const mutex = lockOptions.type == LockType.CRUD ? this.crudMutex : this.syncMutex;
18
- return mutex.runExclusive(lockOptions.callback);
17
+ return mutex.runExclusive(lockOptions.callback, lockOptions.signal);
19
18
  }
20
19
  /**
21
20
  * This is a no-op in SSR mode
@@ -1,9 +1,9 @@
1
1
  import * as Comlink from 'comlink';
2
- import { getNavigatorLocks } from '../../shared/navigator.js';
3
2
  import { AbstractSharedSyncClientProvider } from '../../worker/sync/AbstractSharedSyncClientProvider.js';
4
3
  import { SharedSyncClientEvent } from '../../worker/sync/SharedSyncImplementation.js';
5
4
  import { DEFAULT_CACHE_SIZE_KB, TemporaryStorageOption, resolveWebSQLFlags } from '../adapters/web-sql-flags.js';
6
5
  import { WebStreamingSyncImplementation } from './WebStreamingSyncImplementation.js';
6
+ import { generateTabCloseSignal } from '../../shared/tab_close_signal.js';
7
7
  /**
8
8
  * The shared worker will trigger methods on this side of the message port
9
9
  * via this client provider.
@@ -156,24 +156,9 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
156
156
  * - We resolve the top-level promise after the lock has been registered with the shared worker.
157
157
  * - The client sends the params to the shared worker after locks have been registered.
158
158
  */
159
- await new Promise((resolve) => {
160
- // Request a random lock until this client is disposed. The name of the lock is sent to the shared worker, which
161
- // will also attempt to acquire it. Since the lock is returned when the tab is closed, this allows the share worker
162
- // to free resources associated with this tab.
163
- // We take hold of this lock as soon-as-possible in order to cater for potentially closed tabs.
164
- getNavigatorLocks().request(`tab-close-signal-${crypto.randomUUID()}`, async (lock) => {
165
- if (this.abortOnClose.signal.aborted) {
166
- return;
167
- }
168
- // Awaiting here ensures the worker is waiting for the lock
169
- await this.syncManager.addLockBasedCloseSignal(lock.name);
170
- // The lock has been registered, we can continue with the initialization
171
- resolve();
172
- await new Promise((r) => {
173
- this.abortOnClose.signal.onabort = () => r();
174
- });
175
- });
176
- });
159
+ const closeSignal = await generateTabCloseSignal(this.abortOnClose.signal);
160
+ // Awaiting here ensures the worker is waiting for the lock
161
+ await this.syncManager.addLockBasedCloseSignal(closeSignal);
177
162
  const { crudUploadThrottleMs, identifier, retryDelayMs } = this.options;
178
163
  const flags = { ...this.webOptions.flags, workers: undefined };
179
164
  await this.syncManager.setParams({
@@ -1,10 +1,7 @@
1
1
  export * from '@powersync/common';
2
2
  export * from './attachments/IndexDBFileSystemAdapter.js';
3
3
  export * from './db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.js';
4
- export * from './db/adapters/AbstractWebSQLOpenFactory.js';
5
- export * from './db/adapters/AsyncDatabaseConnection.js';
6
- export * from './db/adapters/wa-sqlite/WASQLiteConnection.js';
7
- export * from './db/adapters/wa-sqlite/WASQLiteDBAdapter.js';
4
+ export { WASQLiteVFS } from './db/adapters/wa-sqlite/vfs.js';
8
5
  export * from './db/adapters/wa-sqlite/WASQLiteOpenFactory.js';
9
6
  export * from './db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.js';
10
7
  export * from './db/adapters/web-sql-flags.js';
package/lib/src/index.js CHANGED
@@ -1,10 +1,7 @@
1
1
  export * from '@powersync/common';
2
2
  export * from './attachments/IndexDBFileSystemAdapter.js';
3
3
  export * from './db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.js';
4
- export * from './db/adapters/AbstractWebSQLOpenFactory.js';
5
- export * from './db/adapters/AsyncDatabaseConnection.js';
6
- export * from './db/adapters/wa-sqlite/WASQLiteConnection.js';
7
- export * from './db/adapters/wa-sqlite/WASQLiteDBAdapter.js';
4
+ export { WASQLiteVFS } from './db/adapters/wa-sqlite/vfs.js';
8
5
  export * from './db/adapters/wa-sqlite/WASQLiteOpenFactory.js';
9
6
  export * from './db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.js';
10
7
  export * from './db/adapters/web-sql-flags.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Requests a random lock that will be released once the optional signal is aborted (or, if no signal is given, when the
3
+ * tab is closed).
4
+ *
5
+ * This allows sending the name of the lock to another context (e.g. a shared worker), which will also attempt to
6
+ * acquire it. Since the lock is returned when the tab is closed, this allows the shared worker to free resources
7
+ * assocatiated with this tab.
8
+ *
9
+ * We take hold of this lock as soon-as-possible in order to cater for potentially closed tabs.
10
+ */
11
+ export declare function generateTabCloseSignal(abort?: AbortSignal): Promise<string>;
@@ -0,0 +1,26 @@
1
+ import { getNavigatorLocks } from './navigator.js';
2
+ /**
3
+ * Requests a random lock that will be released once the optional signal is aborted (or, if no signal is given, when the
4
+ * tab is closed).
5
+ *
6
+ * This allows sending the name of the lock to another context (e.g. a shared worker), which will also attempt to
7
+ * acquire it. Since the lock is returned when the tab is closed, this allows the shared worker to free resources
8
+ * assocatiated with this tab.
9
+ *
10
+ * We take hold of this lock as soon-as-possible in order to cater for potentially closed tabs.
11
+ */
12
+ export function generateTabCloseSignal(abort) {
13
+ return new Promise((resolve, reject) => {
14
+ const options = { signal: abort };
15
+ getNavigatorLocks()
16
+ .request(`tab-close-signal-${crypto.randomUUID()}`, options, (lock) => {
17
+ resolve(lock.name);
18
+ return new Promise((resolve) => {
19
+ if (abort) {
20
+ abort.addEventListener('abort', () => resolve());
21
+ }
22
+ });
23
+ })
24
+ .catch(reject);
25
+ });
26
+ }
@@ -0,0 +1,17 @@
1
+ import { ILogger } from '@powersync/common';
2
+ import { ClientConnectionView } from '../../db/adapters/wa-sqlite/DatabaseServer.js';
3
+ import { ResolvedWASQLiteOpenFactoryOptions, WorkerDBOpenerOptions } from '../../db/adapters/wa-sqlite/WASQLiteOpenFactory.js';
4
+ /**
5
+ * Shared state to manage multiple database connections hosted by a worker.
6
+ */
7
+ export declare class MultiDatabaseServer {
8
+ readonly logger: ILogger;
9
+ private activeDatabases;
10
+ constructor(logger: ILogger);
11
+ handleConnection(options: WorkerDBOpenerOptions): Promise<ClientConnectionView>;
12
+ connectToExisting(name: string, lockName: string): Promise<ClientConnectionView>;
13
+ openConnectionLocally(options: ResolvedWASQLiteOpenFactoryOptions, lockName?: string): Promise<ClientConnectionView>;
14
+ private databaseOpenAttempt;
15
+ closeAll(): Promise<void[]>;
16
+ }
17
+ export declare const isSharedWorker: boolean;
@@ -0,0 +1,86 @@
1
+ import * as Comlink from 'comlink';
2
+ import { DatabaseServer } from '../../db/adapters/wa-sqlite/DatabaseServer.js';
3
+ import { getNavigatorLocks } from '../../shared/navigator.js';
4
+ import { RawSqliteConnection } from '../../db/adapters/wa-sqlite/RawSqliteConnection.js';
5
+ import { ConcurrentSqliteConnection } from '../../db/adapters/wa-sqlite/ConcurrentConnection.js';
6
+ const OPEN_DB_LOCK = 'open-wasqlite-db';
7
+ /**
8
+ * Shared state to manage multiple database connections hosted by a worker.
9
+ */
10
+ export class MultiDatabaseServer {
11
+ logger;
12
+ activeDatabases = new Map();
13
+ constructor(logger) {
14
+ this.logger = logger;
15
+ }
16
+ async handleConnection(options) {
17
+ this.logger.setLevel(options.logLevel);
18
+ return Comlink.proxy(await this.openConnectionLocally(options, options.lockName));
19
+ }
20
+ async connectToExisting(name, lockName) {
21
+ return getNavigatorLocks().request(OPEN_DB_LOCK, async () => {
22
+ const server = this.activeDatabases.get(name);
23
+ if (server == null) {
24
+ throw new Error(`connectToExisting(${name}) failed because the worker doesn't own a database with that name.`);
25
+ }
26
+ return Comlink.proxy(await server.connect(lockName));
27
+ });
28
+ }
29
+ async openConnectionLocally(options, lockName) {
30
+ // Especially on Firefox, we're sometimes seeing "NoModificationAllowedError"s when opening OPFS databases we can
31
+ // work around by retrying.
32
+ const maxAttempts = 3;
33
+ let server;
34
+ for (let count = 0; count < maxAttempts - 1; count++) {
35
+ try {
36
+ server = await this.databaseOpenAttempt(options);
37
+ }
38
+ catch (ex) {
39
+ this.logger.warn(`Attempt ${count + 1} of ${maxAttempts} to open database failed, retrying in 1 second...`, ex);
40
+ await new Promise((resolve) => setTimeout(resolve, 1000));
41
+ }
42
+ }
43
+ // Final attempt if we haven't been able to open the server - rethrow errors if we still can't open.
44
+ server ??= await this.databaseOpenAttempt(options);
45
+ return server.connect(lockName);
46
+ }
47
+ async databaseOpenAttempt(options) {
48
+ return getNavigatorLocks().request(OPEN_DB_LOCK, async () => {
49
+ const { dbFilename } = options;
50
+ let server = this.activeDatabases.get(dbFilename);
51
+ if (server == null) {
52
+ const needsNavigatorLocks = !isSharedWorker;
53
+ const connection = new RawSqliteConnection(options);
54
+ const withSafeConcurrency = new ConcurrentSqliteConnection(connection, needsNavigatorLocks);
55
+ // Initializing the RawSqliteConnection will run some pragmas that might write to the database file, so we want
56
+ // to do that in an exclusive lock. Note that OPEN_DB_LOCK is not enough for that, as another tab might have
57
+ // already created a connection (and is thus outside of OPEN_DB_LOCK) while currently writing to it.
58
+ const returnLease = await withSafeConcurrency.acquireMutex();
59
+ try {
60
+ await connection.init();
61
+ }
62
+ catch (e) {
63
+ returnLease();
64
+ await connection.close();
65
+ throw e;
66
+ }
67
+ returnLease();
68
+ const onClose = () => this.activeDatabases.delete(dbFilename);
69
+ server = new DatabaseServer({
70
+ inner: withSafeConcurrency,
71
+ logger: this.logger,
72
+ onClose
73
+ });
74
+ this.activeDatabases.set(dbFilename, server);
75
+ }
76
+ return server;
77
+ });
78
+ }
79
+ closeAll() {
80
+ const existingDatabases = [...this.activeDatabases.values()];
81
+ return Promise.all(existingDatabases.map((db) => {
82
+ db.forceClose();
83
+ }));
84
+ }
85
+ }
86
+ export const isSharedWorker = 'SharedWorkerGlobalScope' in globalThis;
@@ -4,66 +4,27 @@
4
4
  import '@journeyapps/wa-sqlite';
5
5
  import { createBaseLogger, createLogger } from '@powersync/common';
6
6
  import * as Comlink from 'comlink';
7
- import { getNavigatorLocks } from '../../shared/navigator.js';
8
- import { SharedWASQLiteConnection } from './SharedWASQLiteConnection.js';
9
- import { WorkerWASQLiteConnection } from './WorkerWASQLiteConnection.js';
7
+ import { isSharedWorker, MultiDatabaseServer } from './MultiDatabaseServer.js';
10
8
  const baseLogger = createBaseLogger();
11
9
  baseLogger.useDefaults();
12
10
  const logger = createLogger('db-worker');
13
- const DBMap = new Map();
14
- const OPEN_DB_LOCK = 'open-wasqlite-db';
15
- let nextClientId = 1;
16
- const openDBShared = async (options) => {
17
- // Prevent multiple simultaneous opens from causing race conditions
18
- return getNavigatorLocks().request(OPEN_DB_LOCK, async () => {
19
- const clientId = nextClientId++;
20
- const { dbFilename, logLevel } = options;
21
- logger.setLevel(logLevel);
22
- if (!DBMap.has(dbFilename)) {
23
- const clientIds = new Set();
24
- // This format returns proxy objects for function callbacks
25
- const connection = new WorkerWASQLiteConnection(options);
26
- await connection.init();
27
- connection.registerListener({
28
- holdOverwritten: async () => {
29
- /**
30
- * The previous hold has been overwritten, without being released.
31
- * we need to cleanup any resources associated with it.
32
- * We can perform a rollback to release any potential transactions that were started.
33
- */
34
- await connection.execute('ROLLBACK').catch(() => { });
35
- }
36
- });
37
- DBMap.set(dbFilename, {
38
- clientIds,
39
- db: connection
40
- });
41
- }
42
- // Associates this clientId with the shared connection entry
43
- const sharedConnection = new SharedWASQLiteConnection({
44
- dbMap: DBMap,
45
- dbFilename,
46
- clientId,
47
- logger
48
- });
49
- return Comlink.proxy(sharedConnection);
50
- });
11
+ const server = new MultiDatabaseServer(logger);
12
+ const exposedFunctions = {
13
+ connect: (config) => server.handleConnection(config),
14
+ connectToExisting: ({ identifier, lockName }) => server.connectToExisting(identifier, lockName)
51
15
  };
52
16
  // Check if we're in a SharedWorker context
53
- if (typeof SharedWorkerGlobalScope !== 'undefined') {
17
+ if (isSharedWorker) {
54
18
  const _self = self;
55
19
  _self.onconnect = function (event) {
56
20
  const port = event.ports[0];
57
- Comlink.expose(openDBShared, port);
21
+ Comlink.expose(exposedFunctions, port);
58
22
  };
59
23
  }
60
24
  else {
61
25
  // A dedicated worker can be shared externally
62
- Comlink.expose(openDBShared);
26
+ Comlink.expose(exposedFunctions);
63
27
  }
64
28
  addEventListener('unload', () => {
65
- Array.from(DBMap.values()).forEach(async (dbConnection) => {
66
- const { db } = dbConnection;
67
- db.close?.();
68
- });
29
+ server.closeAll();
69
30
  });
@@ -1,6 +1,6 @@
1
1
  import * as Comlink from 'comlink';
2
- import { OpenAsyncDatabaseConnection } from '../../db/adapters/AsyncDatabaseConnection.js';
3
- import { WASQLiteVFS } from '../../db/adapters/wa-sqlite/WASQLiteConnection.js';
2
+ import { WASQLiteVFS } from '../../db/adapters/wa-sqlite/vfs.js';
3
+ import { OpenWorkerConnection } from '../../db/adapters/wa-sqlite/DatabaseClient.js';
4
4
  /**
5
5
  * Opens a shared or dedicated worker which exposes opening of database connections
6
6
  */
@@ -9,6 +9,6 @@ export declare function openWorkerDatabasePort(workerIdentifier: string, multipl
9
9
  * @returns A function which allows for opening database connections inside
10
10
  * a worker.
11
11
  */
12
- export declare function getWorkerDatabaseOpener(workerIdentifier: string, multipleTabs?: boolean, worker?: string | URL): Comlink.Remote<OpenAsyncDatabaseConnection>;
12
+ export declare function getWorkerDatabaseOpener(workerIdentifier: string, multipleTabs?: boolean, worker?: string | URL): Comlink.Remote<OpenWorkerConnection>;
13
13
  export declare function resolveWorkerDatabasePortFactory(worker: () => Worker | SharedWorker): Worker | MessagePort;
14
14
  export declare function isSharedWorker(worker: Worker | SharedWorker): worker is SharedWorker;
@@ -1,5 +1,5 @@
1
1
  import * as Comlink from 'comlink';
2
- import { WASQLiteVFS } from '../../db/adapters/wa-sqlite/WASQLiteConnection.js';
2
+ import { WASQLiteVFS } from '../../db/adapters/wa-sqlite/vfs.js';
3
3
  /**
4
4
  * Opens a shared or dedicated worker which exposes opening of database connections
5
5
  */
@@ -1,8 +1,6 @@
1
- import { BaseObserver, ConnectionManager, DBAdapter, SubscribedStream, SyncStatus, type ILogLevel, type ILogger, type PowerSyncConnectionOptions, type StreamingSyncImplementation, type StreamingSyncImplementationListener, type SyncStatusOptions } from '@powersync/common';
2
- import { Mutex } from 'async-mutex';
1
+ import { BaseObserver, ConnectionManager, DBAdapter, SubscribedStream, SyncStatus, Mutex, type ILogLevel, type ILogger, type PowerSyncConnectionOptions, type StreamingSyncImplementation, type StreamingSyncImplementationListener, type SyncStatusOptions } from '@powersync/common';
3
2
  import * as Comlink from 'comlink';
4
3
  import { WebStreamingSyncImplementation, WebStreamingSyncImplementationOptions } from '../../db/sync/WebStreamingSyncImplementation.js';
5
- import { WorkerWrappedAsyncDatabaseConnection } from '../../db/adapters/WorkerWrappedAsyncDatabaseConnection.js';
6
4
  import { ResolvedWebSQLOpenOptions } from '../../db/adapters/web-sql-flags.js';
7
5
  import { AbstractSharedSyncClientProvider } from './AbstractSharedSyncClientProvider.js';
8
6
  /**
@@ -73,7 +71,7 @@ export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImp
73
71
  protected connectionManager: ConnectionManager;
74
72
  syncStatus: SyncStatus;
75
73
  broadCastLogger: ILogger;
76
- protected distributedDB: DBAdapter | null;
74
+ protected readonly database: DBAdapter;
77
75
  constructor();
78
76
  get lastSyncedAt(): Date | undefined;
79
77
  get isConnected(): boolean;
@@ -126,9 +124,10 @@ export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImp
126
124
  protected withSyncImplementation<T>(callback: (sync: StreamingSyncImplementation) => Promise<T>): Promise<T>;
127
125
  protected generateStreamingImplementation(): WebStreamingSyncImplementation;
128
126
  /**
129
- * Opens a worker wrapped database connection. Using the last connected client port.
127
+ * Requests a random client to share its database connection with us.
130
128
  */
131
- protected openInternalDB(): Promise<WorkerWrappedAsyncDatabaseConnection<ResolvedWebSQLOpenOptions>>;
129
+ private openInternalDB;
130
+ private generateReconnectableDatabase;
132
131
  /**
133
132
  * A method to update the all shared statuses for each
134
133
  * client.