@powersync/web 0.0.0-dev-20260311103504 → 0.0.0-dev-20260503073249

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 (121) hide show
  1. package/dist/2075a31bb151adbb9767.wasm +0 -0
  2. package/dist/3322bc84de986b63c2cd.wasm +0 -0
  3. package/dist/8e97452e297be23b5e50.wasm +0 -0
  4. package/dist/fbc178b70d530e8ce02b.wasm +0 -0
  5. package/dist/index.umd.js +5341 -1279
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/worker/SharedSyncImplementation.umd.js +1113 -3526
  8. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  9. package/dist/worker/WASQLiteDB.umd.js +1397 -1332
  10. package/dist/worker/WASQLiteDB.umd.js.map +1 -1
  11. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-9af0a7.umd.js +31 -0
  12. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-9af0a7.umd.js.map +1 -0
  13. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-bbf5a9.umd.js +31 -0
  14. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-bbf5a9.umd.js.map +1 -0
  15. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-c26e0f.umd.js +31 -0
  16. package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-cc5fcc.umd.js.map → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-c26e0f.umd.js.map} +1 -1
  17. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +31 -0
  18. package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map} +1 -1
  19. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-2fb422.umd.js +3562 -0
  20. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-2fb422.umd.js.map +1 -0
  21. package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-0df390.umd.js → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-96fb23.umd.js} +16 -16
  22. package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-0df390.umd.js.map → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-96fb23.umd.js.map} +1 -1
  23. package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-151024.umd.js → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-c89911.umd.js} +12 -12
  24. package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-151024.umd.js.map → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-c89911.umd.js.map} +1 -1
  25. package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-c01ef0.umd.js → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-ec4eb1.umd.js} +14 -14
  26. package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-c01ef0.umd.js.map → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-ec4eb1.umd.js.map} +1 -1
  27. package/lib/package.json +4 -5
  28. package/lib/src/db/PowerSyncDatabase.d.ts +2 -3
  29. package/lib/src/db/PowerSyncDatabase.js +3 -12
  30. package/lib/src/db/adapters/AsyncWebAdapter.d.ts +50 -0
  31. package/lib/src/db/adapters/AsyncWebAdapter.js +163 -0
  32. package/lib/src/db/adapters/SSRDBAdapter.d.ts +1 -2
  33. package/lib/src/db/adapters/SSRDBAdapter.js +5 -6
  34. package/lib/src/db/adapters/wa-sqlite/ConcurrentConnection.d.ts +56 -0
  35. package/lib/src/db/adapters/wa-sqlite/ConcurrentConnection.js +121 -0
  36. package/lib/src/db/adapters/wa-sqlite/DatabaseClient.d.ts +54 -0
  37. package/lib/src/db/adapters/wa-sqlite/DatabaseClient.js +227 -0
  38. package/lib/src/db/adapters/wa-sqlite/DatabaseServer.d.ts +47 -0
  39. package/lib/src/db/adapters/wa-sqlite/DatabaseServer.js +145 -0
  40. package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.d.ts +46 -0
  41. package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.js +147 -0
  42. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +28 -6
  43. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +104 -55
  44. package/lib/src/db/adapters/wa-sqlite/vfs.d.ts +50 -0
  45. package/lib/src/db/adapters/wa-sqlite/vfs.js +76 -0
  46. package/lib/src/db/adapters/web-sql-flags.d.ts +5 -0
  47. package/lib/src/db/sync/SSRWebStreamingSyncImplementation.d.ts +5 -2
  48. package/lib/src/db/sync/SSRWebStreamingSyncImplementation.js +6 -3
  49. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +4 -19
  50. package/lib/src/index.d.ts +1 -4
  51. package/lib/src/index.js +1 -4
  52. package/lib/src/shared/tab_close_signal.d.ts +11 -0
  53. package/lib/src/shared/tab_close_signal.js +26 -0
  54. package/lib/src/worker/db/MultiDatabaseServer.d.ts +17 -0
  55. package/lib/src/worker/db/MultiDatabaseServer.js +89 -0
  56. package/lib/src/worker/db/WASQLiteDB.worker.js +9 -48
  57. package/lib/src/worker/db/open-worker-database.d.ts +3 -3
  58. package/lib/src/worker/db/open-worker-database.js +2 -2
  59. package/lib/src/worker/sync/SharedSyncImplementation.d.ts +5 -6
  60. package/lib/src/worker/sync/SharedSyncImplementation.js +88 -54
  61. package/lib/tsconfig.tsbuildinfo +1 -1
  62. package/package.json +5 -6
  63. package/src/db/PowerSyncDatabase.ts +4 -12
  64. package/src/db/adapters/AsyncWebAdapter.ts +207 -0
  65. package/src/db/adapters/SSRDBAdapter.ts +7 -7
  66. package/src/db/adapters/wa-sqlite/ConcurrentConnection.ts +137 -0
  67. package/src/db/adapters/wa-sqlite/DatabaseClient.ts +325 -0
  68. package/src/db/adapters/wa-sqlite/DatabaseServer.ts +203 -0
  69. package/src/db/adapters/wa-sqlite/RawSqliteConnection.ts +194 -0
  70. package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +152 -63
  71. package/src/db/adapters/wa-sqlite/vfs.ts +96 -0
  72. package/src/db/adapters/web-sql-flags.ts +6 -0
  73. package/src/db/sync/SSRWebStreamingSyncImplementation.ts +7 -3
  74. package/src/db/sync/SharedWebStreamingSyncImplementation.ts +4 -20
  75. package/src/index.ts +1 -4
  76. package/src/shared/tab_close_signal.ts +28 -0
  77. package/src/worker/db/MultiDatabaseServer.ts +107 -0
  78. package/src/worker/db/WASQLiteDB.worker.ts +10 -57
  79. package/src/worker/db/open-worker-database.ts +4 -4
  80. package/src/worker/sync/SharedSyncImplementation.ts +114 -58
  81. package/dist/26d61ca9f5694d064635.wasm +0 -0
  82. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js +0 -1878
  83. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js.map +0 -1
  84. package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-97ebe9.index.umd.js +0 -555
  85. package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-97ebe9.index.umd.js.map +0 -1
  86. package/dist/b4c6283dc473b6b3fd24.wasm +0 -0
  87. package/dist/c78985091a0b22aaef03.wasm +0 -0
  88. package/dist/ca59e199e1138b553fad.wasm +0 -0
  89. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-b9c070.umd.js +0 -31
  90. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-b9c070.umd.js.map +0 -1
  91. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-c99c07.umd.js +0 -31
  92. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-c99c07.umd.js.map +0 -1
  93. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-cc5fcc.umd.js +0 -31
  94. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +0 -31
  95. package/lib/src/db/adapters/AbstractWebSQLOpenFactory.d.ts +0 -17
  96. package/lib/src/db/adapters/AbstractWebSQLOpenFactory.js +0 -33
  97. package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +0 -49
  98. package/lib/src/db/adapters/AsyncDatabaseConnection.js +0 -1
  99. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +0 -109
  100. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +0 -401
  101. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +0 -59
  102. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +0 -147
  103. package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.d.ts +0 -12
  104. package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.js +0 -19
  105. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.d.ts +0 -155
  106. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js +0 -401
  107. package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.d.ts +0 -32
  108. package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.js +0 -49
  109. package/lib/src/worker/db/SharedWASQLiteConnection.d.ts +0 -42
  110. package/lib/src/worker/db/SharedWASQLiteConnection.js +0 -90
  111. package/lib/src/worker/db/WorkerWASQLiteConnection.d.ts +0 -9
  112. package/lib/src/worker/db/WorkerWASQLiteConnection.js +0 -12
  113. package/src/db/adapters/AbstractWebSQLOpenFactory.ts +0 -48
  114. package/src/db/adapters/AsyncDatabaseConnection.ts +0 -55
  115. package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +0 -490
  116. package/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts +0 -201
  117. package/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.ts +0 -23
  118. package/src/db/adapters/wa-sqlite/WASQLiteConnection.ts +0 -497
  119. package/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts +0 -86
  120. package/src/worker/db/SharedWASQLiteConnection.ts +0 -131
  121. package/src/worker/db/WorkerWASQLiteConnection.ts +0 -14
@@ -1,29 +1,48 @@
1
+ import { createLogger } from '@powersync/common';
1
2
  import * as Comlink from 'comlink';
2
3
  import { openWorkerDatabasePort, resolveWorkerDatabasePortFactory } from '../../../worker/db/open-worker-database.js';
3
- import { AbstractWebSQLOpenFactory } from '../AbstractWebSQLOpenFactory.js';
4
- import { WorkerWrappedAsyncDatabaseConnection } from '../WorkerWrappedAsyncDatabaseConnection.js';
5
- import { DEFAULT_CACHE_SIZE_KB, TemporaryStorageOption } from '../web-sql-flags.js';
6
- import { InternalWASQLiteDBAdapter } from './InternalWASQLiteDBAdapter.js';
7
- import { WASQLiteVFS, WASqliteConnection } from './WASQLiteConnection.js';
4
+ import { DEFAULT_CACHE_SIZE_KB, isServerSide, resolveWebSQLFlags, TemporaryStorageOption } from '../web-sql-flags.js';
5
+ import { SSRDBAdapter } from '../SSRDBAdapter.js';
6
+ import { vfsRequiresDedicatedWorkers, WASQLiteVFS } from './vfs.js';
7
+ import { MultiDatabaseServer } from '../../../worker/db/MultiDatabaseServer.js';
8
+ import { DatabaseClient } from './DatabaseClient.js';
9
+ import { generateTabCloseSignal } from '../../../shared/tab_close_signal.js';
10
+ import { AsyncDbAdapter } from '../AsyncWebAdapter.js';
8
11
  /**
9
12
  * Opens a SQLite connection using WA-SQLite.
10
13
  */
11
- export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
14
+ export class WASQLiteOpenFactory {
15
+ options;
16
+ resolvedFlags;
17
+ logger;
12
18
  constructor(options) {
13
- super(options);
19
+ this.options = options;
14
20
  assertValidWASQLiteOpenFactoryOptions(options);
21
+ this.resolvedFlags = resolveWebSQLFlags(options.flags);
22
+ this.logger = options.logger ?? createLogger(`WASQLiteOpenFactory - ${this.options.dbFilename}`);
15
23
  }
16
24
  get waOptions() {
17
25
  // Cast to extended type
18
26
  return this.options;
19
27
  }
20
28
  openAdapter() {
21
- return new InternalWASQLiteDBAdapter({
22
- name: this.options.dbFilename,
23
- openConnection: () => this.openConnection(),
24
- debugMode: this.options.debugMode,
25
- logger: this.logger
26
- });
29
+ return new AsyncDbAdapter(this.openConnection(), this.options.dbFilename);
30
+ }
31
+ openDB() {
32
+ const { resolvedFlags: { disableSSRWarning, enableMultiTabs, ssrMode = isServerSide() } } = this;
33
+ if (ssrMode) {
34
+ if (!disableSSRWarning) {
35
+ this.logger.warn(`
36
+ Running PowerSync in SSR mode.
37
+ Only empty query results will be returned.
38
+ Disable this warning by setting 'disableSSRWarning: true' in options.`);
39
+ }
40
+ return new SSRDBAdapter();
41
+ }
42
+ if (!enableMultiTabs) {
43
+ this.logger.warn('Multiple tab support is not enabled. Using this site across multiple tabs may not function correctly.');
44
+ }
45
+ return this.openAdapter();
27
46
  }
28
47
  async openConnection() {
29
48
  const { enableMultiTabs, useWebWorker } = this.resolvedFlags;
@@ -31,55 +50,85 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
31
50
  if (!enableMultiTabs) {
32
51
  this.logger.warn('Multiple tabs are not enabled in this browser');
33
52
  }
53
+ const resolveOptions = (isReadOnly) => ({
54
+ dbFilename: this.options.dbFilename,
55
+ dbLocation: this.options.dbLocation,
56
+ debugMode: this.options.debugMode,
57
+ vfs,
58
+ temporaryStorage,
59
+ cacheSizeKb,
60
+ flags: this.resolvedFlags,
61
+ encryptionKey: encryptionKey,
62
+ isReadOnly
63
+ });
64
+ let client;
65
+ let additionalReaders = [];
66
+ let requiresPersistentTriggers = vfsRequiresDedicatedWorkers(vfs);
34
67
  if (useWebWorker) {
35
68
  const optionsDbWorker = this.options.worker;
36
- const workerPort = typeof optionsDbWorker == 'function'
37
- ? resolveWorkerDatabasePortFactory(() => optionsDbWorker({
38
- ...this.options,
39
- temporaryStorage,
40
- cacheSizeKb,
41
- flags: this.resolvedFlags,
42
- encryptionKey
43
- }))
44
- : openWorkerDatabasePort(this.options.dbFilename, enableMultiTabs, optionsDbWorker, this.waOptions.vfs);
45
- const workerDBOpener = Comlink.wrap(workerPort);
46
- return new WorkerWrappedAsyncDatabaseConnection({
47
- remote: workerDBOpener,
48
- // This tab owns the worker, so we're guaranteed to outlive it.
49
- remoteCanCloseUnexpectedly: false,
50
- baseConnection: await workerDBOpener({
51
- dbFilename: this.options.dbFilename,
52
- vfs,
53
- temporaryStorage,
54
- cacheSizeKb,
55
- flags: this.resolvedFlags,
56
- encryptionKey: encryptionKey,
57
- logLevel: this.logger.getLevel()
58
- }),
59
- identifier: this.options.dbFilename,
60
- onClose: () => {
61
- if (workerPort instanceof Worker) {
62
- workerPort.terminate();
63
- }
64
- else {
65
- workerPort.close();
69
+ const openDatabaseWorker = async (resolvedOptions) => {
70
+ const workerPort = typeof optionsDbWorker == 'function'
71
+ ? resolveWorkerDatabasePortFactory(() => optionsDbWorker({
72
+ ...this.options,
73
+ temporaryStorage,
74
+ cacheSizeKb,
75
+ flags: this.resolvedFlags,
76
+ encryptionKey
77
+ }))
78
+ : openWorkerDatabasePort(this.options.dbFilename, enableMultiTabs, optionsDbWorker, this.waOptions.vfs);
79
+ const source = Comlink.wrap(workerPort);
80
+ const closeSignal = new AbortController();
81
+ const connection = await source.connect({
82
+ ...resolvedOptions,
83
+ logLevel: this.logger.getLevel(),
84
+ lockName: await generateTabCloseSignal(closeSignal.signal)
85
+ });
86
+ const clientOptions = {
87
+ connection,
88
+ source,
89
+ // This tab owns the worker, so we're guaranteed to outlive it.
90
+ remoteCanCloseUnexpectedly: false,
91
+ onClose: () => {
92
+ closeSignal.abort();
93
+ if (workerPort instanceof Worker) {
94
+ workerPort.terminate();
95
+ }
96
+ else {
97
+ workerPort.close();
98
+ }
66
99
  }
100
+ };
101
+ return new DatabaseClient(clientOptions, {
102
+ ...resolvedOptions,
103
+ requiresPersistentTriggers
104
+ });
105
+ };
106
+ client = await openDatabaseWorker(resolveOptions(false));
107
+ if (vfs == WASQLiteVFS.OPFSWriteAheadVFS) {
108
+ // This VFS supports concurrent reads, so we can open additional workers to host read-only connections for
109
+ // concurrent reads / writes.
110
+ const additionalReadersCount = this.options.additionalReaders ?? 1;
111
+ for (let i = 0; i < additionalReadersCount; i++) {
112
+ const reader = await openDatabaseWorker(resolveOptions(true));
113
+ additionalReaders.push(reader);
67
114
  }
68
- });
115
+ }
69
116
  }
70
117
  else {
71
- // Don't use a web worker
72
- return new WASqliteConnection({
73
- dbFilename: this.options.dbFilename,
74
- dbLocation: this.options.dbLocation,
75
- debugMode: this.options.debugMode,
76
- vfs,
77
- temporaryStorage,
78
- cacheSizeKb,
79
- flags: this.resolvedFlags,
80
- encryptionKey: encryptionKey
118
+ // Don't use a web worker. Instead, open the MultiDatabaseServer a worker would use locally.
119
+ const localServer = new MultiDatabaseServer(this.logger);
120
+ requiresPersistentTriggers = true;
121
+ const resolvedOptions = resolveOptions(false);
122
+ const connection = await localServer.openConnectionLocally(resolvedOptions);
123
+ client = new DatabaseClient({ connection, source: null, remoteCanCloseUnexpectedly: false }, {
124
+ ...resolvedOptions,
125
+ requiresPersistentTriggers
81
126
  });
82
127
  }
128
+ return {
129
+ writer: client,
130
+ additionalReaders
131
+ };
83
132
  }
84
133
  }
85
134
  /**
@@ -89,7 +138,7 @@ function assertValidWASQLiteOpenFactoryOptions(options) {
89
138
  // The OPFS VFS only works in dedicated web workers.
90
139
  if ('vfs' in options && 'flags' in options) {
91
140
  const { vfs, flags = {} } = options;
92
- if (vfs !== WASQLiteVFS.IDBBatchAtomicVFS && 'useWebWorker' in flags && !flags.useWebWorker) {
141
+ if (vfs && vfsRequiresDedicatedWorkers(vfs) && 'useWebWorker' in flags && !flags.useWebWorker) {
93
142
  throw new Error(`Invalid configuration: The 'useWebWorker' flag must be true when using an OPFS-based VFS (${vfs}).`);
94
143
  }
95
144
  }
@@ -0,0 +1,50 @@
1
+ import type * as SQLite from '@journeyapps/wa-sqlite';
2
+ /**
3
+ * List of currently tested virtual filesystems
4
+ */
5
+ export declare enum WASQLiteVFS {
6
+ IDBBatchAtomicVFS = "IDBBatchAtomicVFS",
7
+ OPFSCoopSyncVFS = "OPFSCoopSyncVFS",
8
+ AccessHandlePoolVFS = "AccessHandlePoolVFS",
9
+ OPFSWriteAheadVFS = "OPFSWriteAheadVFS"
10
+ }
11
+ export declare function vfsRequiresDedicatedWorkers(vfs: WASQLiteVFS): vfs is WASQLiteVFS.OPFSCoopSyncVFS | WASQLiteVFS.AccessHandlePoolVFS | WASQLiteVFS.OPFSWriteAheadVFS;
12
+ /**
13
+ * @internal
14
+ */
15
+ export type WASQLiteModuleFactoryOptions = {
16
+ dbFileName: string;
17
+ encryptionKey?: string;
18
+ };
19
+ /**
20
+ * @internal
21
+ */
22
+ export type SQLiteModule = Parameters<typeof SQLite.Factory>[0];
23
+ /**
24
+ * @internal
25
+ */
26
+ export type WASQLiteModuleFactory = (options: WASQLiteModuleFactoryOptions) => Promise<{
27
+ module: SQLiteModule;
28
+ vfs: SQLiteVFS;
29
+ }>;
30
+ /**
31
+ * @internal
32
+ */
33
+ export declare const DEFAULT_MODULE_FACTORIES: {
34
+ IDBBatchAtomicVFS: (options: WASQLiteModuleFactoryOptions) => Promise<{
35
+ module: any;
36
+ vfs: any;
37
+ }>;
38
+ AccessHandlePoolVFS: (options: WASQLiteModuleFactoryOptions) => Promise<{
39
+ module: any;
40
+ vfs: any;
41
+ }>;
42
+ OPFSCoopSyncVFS: (options: WASQLiteModuleFactoryOptions) => Promise<{
43
+ module: any;
44
+ vfs: any;
45
+ }>;
46
+ OPFSWriteAheadVFS: (options: WASQLiteModuleFactoryOptions) => Promise<{
47
+ module: any;
48
+ vfs: any;
49
+ }>;
50
+ };
@@ -0,0 +1,76 @@
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["OPFSWriteAheadVFS"] = "OPFSWriteAheadVFS";
10
+ })(WASQLiteVFS || (WASQLiteVFS = {}));
11
+ export function vfsRequiresDedicatedWorkers(vfs) {
12
+ return vfs != WASQLiteVFS.IDBBatchAtomicVFS;
13
+ }
14
+ async function asyncModuleFactory(encryptionKey) {
15
+ if (encryptionKey) {
16
+ const { default: factory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite-async.mjs');
17
+ return factory();
18
+ }
19
+ else {
20
+ const { default: factory } = await import('@journeyapps/wa-sqlite/dist/wa-sqlite-async.mjs');
21
+ return factory();
22
+ }
23
+ }
24
+ async function syncModuleFactory(encryptionKey) {
25
+ if (encryptionKey) {
26
+ const { default: factory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite.mjs');
27
+ return factory();
28
+ }
29
+ else {
30
+ const { default: factory } = await import('@journeyapps/wa-sqlite/dist/wa-sqlite.mjs');
31
+ return factory();
32
+ }
33
+ }
34
+ /**
35
+ * @internal
36
+ */
37
+ export const DEFAULT_MODULE_FACTORIES = {
38
+ [WASQLiteVFS.IDBBatchAtomicVFS]: async (options) => {
39
+ const module = await asyncModuleFactory(options.encryptionKey);
40
+ const { IDBBatchAtomicVFS } = await import('@journeyapps/wa-sqlite/src/examples/IDBBatchAtomicVFS.js');
41
+ return {
42
+ module,
43
+ // @ts-expect-error The types for this static method are missing upstream
44
+ vfs: await IDBBatchAtomicVFS.create(options.dbFileName, module, { lockPolicy: 'exclusive' })
45
+ };
46
+ },
47
+ [WASQLiteVFS.AccessHandlePoolVFS]: async (options) => {
48
+ const module = await syncModuleFactory(options.encryptionKey);
49
+ // @ts-expect-error The types for this static method are missing upstream
50
+ const { AccessHandlePoolVFS } = await import('@journeyapps/wa-sqlite/src/examples/AccessHandlePoolVFS.js');
51
+ return {
52
+ module,
53
+ vfs: await AccessHandlePoolVFS.create(options.dbFileName, module)
54
+ };
55
+ },
56
+ [WASQLiteVFS.OPFSCoopSyncVFS]: async (options) => {
57
+ const module = await syncModuleFactory(options.encryptionKey);
58
+ // @ts-expect-error The types for this static method are missing upstream
59
+ const { OPFSCoopSyncVFS } = await import('@journeyapps/wa-sqlite/src/examples/OPFSCoopSyncVFS.js');
60
+ const vfs = await OPFSCoopSyncVFS.create(options.dbFileName, module);
61
+ return {
62
+ module,
63
+ vfs
64
+ };
65
+ },
66
+ [WASQLiteVFS.OPFSWriteAheadVFS]: async (options) => {
67
+ const module = await syncModuleFactory(options.encryptionKey);
68
+ // @ts-expect-error The types for this static method are missing upstream
69
+ const { OPFSWriteAheadVFS } = await import('@journeyapps/wa-sqlite/src/examples/OPFSWriteAheadVFS.js');
70
+ const vfs = await OPFSWriteAheadVFS.create(options.dbFileName, module, {});
71
+ return {
72
+ module,
73
+ vfs
74
+ };
75
+ }
76
+ };
@@ -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;
@@ -45,4 +44,8 @@ export declare class SSRStreamingSyncImplementation extends BaseObserver impleme
45
44
  * No-op in SSR mode.
46
45
  */
47
46
  updateSubscriptions(): void;
47
+ /**
48
+ * No-op in SSR mode.
49
+ */
50
+ markConnectionMayHaveChanged(): void;
48
51
  }
@@ -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
@@ -62,4 +61,8 @@ export class SSRStreamingSyncImplementation extends BaseObserver {
62
61
  * No-op in SSR mode.
63
62
  */
64
63
  updateSubscriptions() { }
64
+ /**
65
+ * No-op in SSR mode.
66
+ */
67
+ markConnectionMayHaveChanged() { }
65
68
  }
@@ -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,89 @@
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
+ // We don't need navigator locks for shared workers because all queries run in this shared worker exclusively.
53
+ // For read-only connections, we use a VFS that supports concurrent reads (so a single lock on the connection is
54
+ // fine).
55
+ const needsNavigatorLocks = !(isSharedWorker || options.isReadOnly);
56
+ const connection = new RawSqliteConnection(options);
57
+ const withSafeConcurrency = new ConcurrentSqliteConnection(connection, needsNavigatorLocks);
58
+ // Initializing the RawSqliteConnection will run some pragmas that might write to the database file, so we want
59
+ // to do that in an exclusive lock. Note that OPEN_DB_LOCK is not enough for that, as another tab might have
60
+ // already created a connection (and is thus outside of OPEN_DB_LOCK) while currently writing to it.
61
+ const returnLease = await withSafeConcurrency.acquireMutex();
62
+ try {
63
+ await connection.init();
64
+ }
65
+ catch (e) {
66
+ returnLease();
67
+ await connection.close();
68
+ throw e;
69
+ }
70
+ returnLease();
71
+ const onClose = () => this.activeDatabases.delete(dbFilename);
72
+ server = new DatabaseServer({
73
+ inner: withSafeConcurrency,
74
+ logger: this.logger,
75
+ onClose
76
+ });
77
+ this.activeDatabases.set(dbFilename, server);
78
+ }
79
+ return server;
80
+ });
81
+ }
82
+ closeAll() {
83
+ const existingDatabases = [...this.activeDatabases.values()];
84
+ return Promise.all(existingDatabases.map((db) => {
85
+ db.forceClose();
86
+ }));
87
+ }
88
+ }
89
+ export const isSharedWorker = 'SharedWorkerGlobalScope' in globalThis;