@powersync/web 1.30.0 → 1.32.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 (112) hide show
  1. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js +1867 -0
  2. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js.map +1 -0
  3. package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-2530150.index.umd.js +555 -0
  4. package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-2530150.index.umd.js.map +1 -0
  5. package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-2530151.index.umd.js +555 -0
  6. package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-2530151.index.umd.js.map +1 -0
  7. package/dist/index.umd.js +5022 -38504
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/worker/SharedSyncImplementation.umd.js +819 -2220
  10. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  11. package/dist/worker/WASQLiteDB.umd.js +524 -2121
  12. package/dist/worker/WASQLiteDB.umd.js.map +1 -1
  13. package/dist/worker/{node_modules_bson_lib_bson_mjs.umd.js → node_modules_pnpm_bson_6_10_4_node_modules_bson_lib_bson_mjs.umd.js} +8 -8
  14. package/dist/worker/node_modules_pnpm_bson_6_10_4_node_modules_bson_lib_bson_mjs.umd.js.map +1 -0
  15. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-3a94cf.umd.js +44 -0
  16. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-3a94cf.umd.js.map +1 -0
  17. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-868779.umd.js +44 -0
  18. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-868779.umd.js.map +1 -0
  19. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-f60d0d.umd.js +44 -0
  20. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-f60d0d.umd.js.map +1 -0
  21. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +44 -0
  22. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map +1 -0
  23. package/dist/worker/{node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js → node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-0d2437.umd.js} +32 -32
  24. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-0d2437.umd.js.map +1 -0
  25. package/dist/worker/{node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js → node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-1d4e74.umd.js} +24 -24
  26. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-1d4e74.umd.js.map +1 -0
  27. package/dist/worker/{node_modules_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js.umd.js → node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-3622cf.umd.js} +24 -24
  28. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-3622cf.umd.js.map +1 -0
  29. package/lib/package.json +27 -23
  30. package/lib/src/db/NavigatorTriggerClaimManager.d.ts +6 -0
  31. package/lib/src/db/NavigatorTriggerClaimManager.js +20 -0
  32. package/lib/src/db/PowerSyncDatabase.d.ts +5 -2
  33. package/lib/src/db/PowerSyncDatabase.js +49 -11
  34. package/lib/src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.d.ts +1 -1
  35. package/lib/src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.js +1 -1
  36. package/lib/src/db/adapters/AbstractWebSQLOpenFactory.d.ts +2 -2
  37. package/lib/src/db/adapters/AbstractWebSQLOpenFactory.js +2 -2
  38. package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +1 -1
  39. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +21 -4
  40. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +116 -22
  41. package/lib/src/db/adapters/WebDBAdapter.d.ts +5 -2
  42. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +7 -3
  43. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +14 -7
  44. package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.d.ts +12 -0
  45. package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.js +19 -0
  46. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.d.ts +2 -2
  47. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js +11 -2
  48. package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.d.ts +4 -4
  49. package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.js +6 -6
  50. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +5 -5
  51. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +7 -7
  52. package/lib/src/db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.d.ts +1 -1
  53. package/lib/src/db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.js +3 -3
  54. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +6 -9
  55. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +54 -38
  56. package/lib/src/db/sync/WebRemote.js +1 -1
  57. package/lib/src/db/sync/WebStreamingSyncImplementation.d.ts +1 -1
  58. package/lib/src/db/sync/WebStreamingSyncImplementation.js +1 -1
  59. package/lib/src/index.d.ts +12 -12
  60. package/lib/src/index.js +12 -12
  61. package/lib/src/worker/db/SharedWASQLiteConnection.d.ts +2 -2
  62. package/lib/src/worker/db/WASQLiteDB.worker.js +3 -3
  63. package/lib/src/worker/db/WorkerWASQLiteConnection.d.ts +2 -2
  64. package/lib/src/worker/db/WorkerWASQLiteConnection.js +1 -1
  65. package/lib/src/worker/db/open-worker-database.d.ts +2 -2
  66. package/lib/src/worker/db/open-worker-database.js +1 -1
  67. package/lib/src/worker/sync/BroadcastLogger.d.ts +1 -1
  68. package/lib/src/worker/sync/SharedSyncImplementation.d.ts +21 -11
  69. package/lib/src/worker/sync/SharedSyncImplementation.js +209 -113
  70. package/lib/src/worker/sync/SharedSyncImplementation.worker.js +3 -3
  71. package/lib/src/worker/sync/WorkerClient.d.ts +4 -5
  72. package/lib/src/worker/sync/WorkerClient.js +8 -10
  73. package/lib/tsconfig.tsbuildinfo +1 -1
  74. package/package.json +23 -19
  75. package/src/db/NavigatorTriggerClaimManager.ts +23 -0
  76. package/src/db/PowerSyncDatabase.ts +64 -22
  77. package/src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.ts +1 -1
  78. package/src/db/adapters/AbstractWebSQLOpenFactory.ts +3 -3
  79. package/src/db/adapters/AsyncDatabaseConnection.ts +1 -1
  80. package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +138 -33
  81. package/src/db/adapters/WebDBAdapter.ts +6 -2
  82. package/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts +20 -8
  83. package/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.ts +23 -0
  84. package/src/db/adapters/wa-sqlite/WASQLiteConnection.ts +13 -5
  85. package/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts +8 -8
  86. package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +9 -9
  87. package/src/db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.ts +3 -3
  88. package/src/db/sync/SharedWebStreamingSyncImplementation.ts +69 -51
  89. package/src/db/sync/WebRemote.ts +1 -1
  90. package/src/db/sync/WebStreamingSyncImplementation.ts +2 -2
  91. package/src/index.ts +12 -12
  92. package/src/worker/db/SharedWASQLiteConnection.ts +2 -2
  93. package/src/worker/db/WASQLiteDB.worker.ts +5 -6
  94. package/src/worker/db/WorkerWASQLiteConnection.ts +2 -2
  95. package/src/worker/db/open-worker-database.ts +2 -2
  96. package/src/worker/sync/BroadcastLogger.ts +1 -1
  97. package/src/worker/sync/SharedSyncImplementation.ts +241 -126
  98. package/src/worker/sync/SharedSyncImplementation.worker.ts +3 -3
  99. package/src/worker/sync/WorkerClient.ts +10 -14
  100. package/dist/worker/node_modules_bson_lib_bson_mjs.umd.js.map +0 -1
  101. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js +0 -44
  102. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js.map +0 -1
  103. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js +0 -44
  104. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js.map +0 -1
  105. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js +0 -44
  106. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js.map +0 -1
  107. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +0 -44
  108. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map +0 -1
  109. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js.umd.js.map +0 -1
  110. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js.map +0 -1
  111. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js.map +0 -1
  112. /package/bin/{powersync.js → powersync.cjs} +0 -0
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "@powersync/web",
3
- "version": "1.30.0",
3
+ "version": "1.32.0",
4
4
  "description": "PowerSync Web SDK",
5
5
  "main": "lib/src/index.js",
6
+ "module": "lib/src/index.js",
7
+ "type": "module",
6
8
  "types": "lib/src/index.d.ts",
7
9
  "bin": {
8
- "powersync-web": "bin/powersync.js"
10
+ "powersync-web": "bin/powersync.cjs"
9
11
  },
10
12
  "files": [
11
13
  "bin",
@@ -15,21 +17,23 @@
15
17
  "src"
16
18
  ],
17
19
  "exports": {
18
- ".": "./lib/src/index.js",
20
+ ".": {
21
+ "types": "./lib/src/index.d.ts",
22
+ "default": "./lib/src/index.js"
23
+ },
19
24
  "./umd": {
20
- "import": "./dist/index.umd.js",
21
- "require": "./dist/index.umd.js",
22
- "types": "./lib/src/index.d.ts"
25
+ "types": "./lib/src/index.d.ts",
26
+ "default": "./dist/index.umd.js"
23
27
  },
24
28
  "./umd/worker/db": {
29
+ "types": "./lib/src/index.d.ts",
25
30
  "import": "./dist/worker/WASQLiteDB.umd.js",
26
- "require": "./dist/worker/WASQLiteDB.umd.js",
27
- "types": "./lib/src/index.d.ts"
31
+ "require": "./dist/worker/WASQLiteDB.umd.js"
28
32
  },
29
33
  "./umd/worker/sync": {
34
+ "types": "./lib/src/index.d.ts",
30
35
  "import": "./dist/worker/SharedSyncImplementation.umd.js",
31
- "require": "./dist/worker/SharedSyncImplementation.umd.js",
32
- "types": "./lib/src/index.d.ts"
36
+ "require": "./dist/worker/SharedSyncImplementation.umd.js"
33
37
  }
34
38
  },
35
39
  "repository": "https://github.com/powersync-ja/powersync-js",
@@ -51,28 +55,27 @@
51
55
  "author": "JOURNEYAPPS",
52
56
  "license": "Apache-2.0",
53
57
  "peerDependencies": {
54
- "@journeyapps/wa-sqlite": "^1.4.0",
55
- "@powersync/common": "^1.44.0"
58
+ "@journeyapps/wa-sqlite": "^1.4.1",
59
+ "@powersync/common": "^1.46.0"
56
60
  },
57
61
  "dependencies": {
58
62
  "async-mutex": "^0.5.0",
59
63
  "bson": "^6.10.4",
60
64
  "comlink": "^4.4.2",
61
65
  "commander": "^12.1.0",
62
- "@powersync/common": "1.44.0"
66
+ "@powersync/common": "1.46.0"
63
67
  },
64
68
  "devDependencies": {
65
- "@journeyapps/wa-sqlite": "^1.4.0",
69
+ "@journeyapps/wa-sqlite": "^1.4.1",
66
70
  "@types/uuid": "^9.0.6",
67
71
  "crypto-browserify": "^3.12.0",
72
+ "glob": "^11.0.0",
68
73
  "p-defer": "^4.0.1",
69
74
  "source-map-loader": "^5.0.0",
70
75
  "stream-browserify": "^3.0.0",
71
76
  "terser-webpack-plugin": "^5.3.9",
72
- "uuid": "^9.0.1",
73
- "vite": "^6.1.0",
74
- "vite-plugin-top-level-await": "^1.4.4",
75
- "vite-plugin-wasm": "^3.3.0",
77
+ "uuid": "^11.1.0",
78
+ "vite": "^7.3.1",
76
79
  "vm-browserify": "^1.1.2",
77
80
  "webpack": "^5.90.1",
78
81
  "webpack-cli": "^5.1.4",
@@ -86,6 +89,7 @@
86
89
  "build:prod": "pnpm run build:tsc --sourceMap false && pnpm run build:webpack-main && pnpm run build:webpack-workers",
87
90
  "clean": "rm -rf lib dist tsconfig.tsbuildinfo",
88
91
  "watch": "tsc --build -w",
89
- "test": "pnpm build && vitest"
92
+ "test": "pnpm build && vitest",
93
+ "test:exports": "attw --pack . --entrypoints . --profile=esm-only "
90
94
  }
91
95
  }
@@ -0,0 +1,23 @@
1
+ import { TriggerClaimManager } from '@powersync/common';
2
+ import { getNavigatorLocks } from '../shared/navigator.js';
3
+
4
+ /**
5
+ * @internal
6
+ * @experimental
7
+ */
8
+ export const NAVIGATOR_TRIGGER_CLAIM_MANAGER: TriggerClaimManager = {
9
+ async obtainClaim(identifier: string): Promise<() => Promise<void>> {
10
+ return new Promise((resolveReleaser) => {
11
+ getNavigatorLocks().request(identifier, async () => {
12
+ await new Promise<void>((releaseLock) => {
13
+ resolveReleaser(async () => releaseLock());
14
+ });
15
+ });
16
+ });
17
+ },
18
+
19
+ async checkClaim(identifier: string): Promise<boolean> {
20
+ const currentState = await getNavigatorLocks().query();
21
+ return currentState.held?.find((heldLock) => heldLock.name == identifier) != null;
22
+ }
23
+ };
@@ -1,37 +1,40 @@
1
1
  import {
2
- type BucketStorageAdapter,
3
- type PowerSyncBackendConnector,
4
- type PowerSyncCloseOptions,
5
- type RequiredAdditionalConnectionOptions,
6
2
  AbstractPowerSyncDatabase,
7
3
  DBAdapter,
8
- DEFAULT_POWERSYNC_CLOSE_OPTIONS,
9
- isDBAdapter,
10
- isSQLOpenFactory,
11
4
  PowerSyncDatabaseOptions,
12
5
  PowerSyncDatabaseOptionsWithDBAdapter,
13
6
  PowerSyncDatabaseOptionsWithOpenFactory,
14
7
  PowerSyncDatabaseOptionsWithSettings,
15
8
  SqliteBucketStorage,
16
- StreamingSyncImplementation
9
+ StreamingSyncImplementation,
10
+ TriggerManagerConfig,
11
+ isDBAdapter,
12
+ isSQLOpenFactory,
13
+ type BucketStorageAdapter,
14
+ type PowerSyncBackendConnector,
15
+ type PowerSyncCloseOptions,
16
+ type RequiredAdditionalConnectionOptions
17
17
  } from '@powersync/common';
18
18
  import { Mutex } from 'async-mutex';
19
- import { getNavigatorLocks } from '../shared/navigator';
20
- import { WASQLiteOpenFactory } from './adapters/wa-sqlite/WASQLiteOpenFactory';
19
+ import { getNavigatorLocks } from '../shared/navigator.js';
20
+ import { NAVIGATOR_TRIGGER_CLAIM_MANAGER } from './NavigatorTriggerClaimManager.js';
21
+ import { LockedAsyncDatabaseAdapter } from './adapters/LockedAsyncDatabaseAdapter.js';
22
+ import { WebDBAdapter } from './adapters/WebDBAdapter.js';
23
+ import { WASQLiteOpenFactory } from './adapters/wa-sqlite/WASQLiteOpenFactory.js';
21
24
  import {
22
25
  DEFAULT_WEB_SQL_FLAGS,
23
26
  ResolvedWebSQLOpenOptions,
24
- resolveWebSQLFlags,
25
- WebSQLFlags
26
- } from './adapters/web-sql-flags';
27
- import { WebDBAdapter } from './adapters/WebDBAdapter';
28
- import { SharedWebStreamingSyncImplementation } from './sync/SharedWebStreamingSyncImplementation';
29
- import { SSRStreamingSyncImplementation } from './sync/SSRWebStreamingSyncImplementation';
30
- import { WebRemote } from './sync/WebRemote';
27
+ WebSQLFlags,
28
+ isServerSide,
29
+ resolveWebSQLFlags
30
+ } from './adapters/web-sql-flags.js';
31
+ import { SSRStreamingSyncImplementation } from './sync/SSRWebStreamingSyncImplementation.js';
32
+ import { SharedWebStreamingSyncImplementation } from './sync/SharedWebStreamingSyncImplementation.js';
33
+ import { WebRemote } from './sync/WebRemote.js';
31
34
  import {
32
35
  WebStreamingSyncImplementation,
33
36
  WebStreamingSyncImplementationOptions
34
- } from './sync/WebStreamingSyncImplementation';
37
+ } from './sync/WebStreamingSyncImplementation.js';
35
38
 
36
39
  export interface WebPowerSyncFlags extends WebSQLFlags {
37
40
  /**
@@ -144,7 +147,33 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
144
147
  }
145
148
  }
146
149
 
147
- async _initialize(): Promise<void> {}
150
+ async _initialize(): Promise<void> {
151
+ if (this.database instanceof LockedAsyncDatabaseAdapter) {
152
+ /**
153
+ * While init is done automatically,
154
+ * LockedAsyncDatabaseAdapter only exposes config after init.
155
+ * We can explicitly wait for init here in order to access config.
156
+ */
157
+ await this.database.init();
158
+ }
159
+
160
+ // In some cases, like the SQLJs adapter, we don't pass a WebDBAdapter, so we need to check.
161
+ if (typeof (this.database as WebDBAdapter).getConfiguration == 'function') {
162
+ const config = (this.database as WebDBAdapter).getConfiguration();
163
+ if (config.requiresPersistentTriggers) {
164
+ this.triggersImpl.updateDefaults({
165
+ useStorageByDefault: true
166
+ });
167
+ }
168
+ }
169
+ }
170
+
171
+ protected generateTriggerManagerConfig(): TriggerManagerConfig {
172
+ return {
173
+ // We need to share hold information between tabs for web
174
+ claimManager: NAVIGATOR_TRIGGER_CLAIM_MANAGER
175
+ };
176
+ }
148
177
 
149
178
  protected openDBAdapter(options: WebPowerSyncDatabaseOptionsWithSettings): DBAdapter {
150
179
  const defaultFactory = new WASQLiteOpenFactory({
@@ -160,17 +189,30 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
160
189
  * By default the sync stream client is only disconnected if
161
190
  * multiple tabs are not enabled.
162
191
  */
163
- close(options: PowerSyncCloseOptions = DEFAULT_POWERSYNC_CLOSE_OPTIONS): Promise<void> {
192
+ close(options?: PowerSyncCloseOptions): Promise<void> {
164
193
  if (this.unloadListener) {
165
194
  window.removeEventListener('unload', this.unloadListener);
166
195
  }
167
-
168
196
  return super.close({
169
197
  // Don't disconnect by default if multiple tabs are enabled
170
- disconnect: options.disconnect ?? !this.resolvedFlags.enableMultiTabs
198
+ disconnect: options?.disconnect ?? !this.resolvedFlags.enableMultiTabs
171
199
  });
172
200
  }
173
201
 
202
+ protected async loadVersion(): Promise<void> {
203
+ if (isServerSide()) {
204
+ return;
205
+ }
206
+ return super.loadVersion();
207
+ }
208
+
209
+ protected async resolveOfflineSyncStatus() {
210
+ if (isServerSide()) {
211
+ return;
212
+ }
213
+ return super.resolveOfflineSyncStatus();
214
+ }
215
+
174
216
  protected generateBucketStorageAdapter(): BucketStorageAdapter {
175
217
  return new SqliteBucketStorage(this.database);
176
218
  }
@@ -9,7 +9,7 @@ import {
9
9
  WebPowerSyncDatabaseOptions,
10
10
  WebPowerSyncFlags,
11
11
  resolveWebPowerSyncFlags
12
- } from '../../db/PowerSyncDatabase';
12
+ } from '../../db/PowerSyncDatabase.js';
13
13
 
14
14
  /**
15
15
  * {@link WebPowerSyncFlags}
@@ -1,6 +1,6 @@
1
- import { type ILogger, createLogger, DBAdapter, SQLOpenFactory } from '@powersync/common';
2
- import { SSRDBAdapter } from './SSRDBAdapter';
3
- import { ResolvedWebSQLFlags, WebSQLOpenFactoryOptions, isServerSide, resolveWebSQLFlags } from './web-sql-flags';
1
+ import { DBAdapter, type ILogger, SQLOpenFactory, createLogger } from '@powersync/common';
2
+ import { SSRDBAdapter } from './SSRDBAdapter.js';
3
+ import { ResolvedWebSQLFlags, WebSQLOpenFactoryOptions, isServerSide, resolveWebSQLFlags } from './web-sql-flags.js';
4
4
 
5
5
  export abstract class AbstractWebSQLOpenFactory implements SQLOpenFactory {
6
6
  protected resolvedFlags: ResolvedWebSQLFlags;
@@ -1,5 +1,5 @@
1
1
  import { BatchedUpdateNotification, QueryResult } from '@powersync/common';
2
- import { ResolvedWebSQLOpenOptions } from './web-sql-flags';
2
+ import { ResolvedWebSQLOpenOptions } from './web-sql-flags.js';
3
3
 
4
4
  /**
5
5
  * @internal
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  BaseObserver,
3
+ ConnectionClosedError,
3
4
  DBAdapter,
4
5
  DBAdapterListener,
5
6
  DBGetUtils,
@@ -10,13 +11,13 @@ import {
10
11
  createLogger,
11
12
  type ILogger
12
13
  } from '@powersync/common';
13
- import { getNavigatorLocks } from '../..//shared/navigator';
14
- import { AsyncDatabaseConnection } from './AsyncDatabaseConnection';
15
- import { SharedConnectionWorker, WebDBAdapter } from './WebDBAdapter';
16
- import { WorkerWrappedAsyncDatabaseConnection } from './WorkerWrappedAsyncDatabaseConnection';
17
- import { WASQLiteVFS } from './wa-sqlite/WASQLiteConnection';
18
- import { ResolvedWASQLiteOpenFactoryOptions } from './wa-sqlite/WASQLiteOpenFactory';
19
- import { ResolvedWebSQLOpenOptions } from './web-sql-flags';
14
+ import { getNavigatorLocks } from '../../shared/navigator.js';
15
+ import { AsyncDatabaseConnection } from './AsyncDatabaseConnection.js';
16
+ import { SharedConnectionWorker, WebDBAdapter, WebDBAdapterConfiguration } from './WebDBAdapter.js';
17
+ import { WorkerWrappedAsyncDatabaseConnection } from './WorkerWrappedAsyncDatabaseConnection.js';
18
+ import { WASQLiteVFS } from './wa-sqlite/WASQLiteConnection.js';
19
+ import { ResolvedWASQLiteOpenFactoryOptions } from './wa-sqlite/WASQLiteOpenFactory.js';
20
+ import { ResolvedWebSQLOpenOptions } from './web-sql-flags.js';
20
21
 
21
22
  /**
22
23
  * @internal
@@ -26,10 +27,16 @@ export interface LockedAsyncDatabaseAdapterOptions {
26
27
  openConnection: () => Promise<AsyncDatabaseConnection>;
27
28
  debugMode?: boolean;
28
29
  logger?: ILogger;
30
+ defaultLockTimeoutMs?: number;
31
+ reOpenOnConnectionClosed?: boolean;
29
32
  }
30
33
 
31
34
  export type LockedAsyncDatabaseAdapterListener = DBAdapterListener & {
32
35
  initialized?: () => void;
36
+ /**
37
+ * Fired when the database is re-opened after being closed.
38
+ */
39
+ databaseReOpened?: () => void;
33
40
  };
34
41
 
35
42
  /**
@@ -51,6 +58,7 @@ export class LockedAsyncDatabaseAdapter
51
58
  private _config: ResolvedWebSQLOpenOptions | null = null;
52
59
  protected pendingAbortControllers: Set<AbortController>;
53
60
  protected requiresHolds: boolean | null;
61
+ protected databaseOpenPromise: Promise<void> | null = null;
54
62
 
55
63
  closing: boolean;
56
64
  closed: boolean;
@@ -105,23 +113,83 @@ export class LockedAsyncDatabaseAdapter
105
113
  return this.initPromise;
106
114
  }
107
115
 
116
+ protected async openInternalDB() {
117
+ /**
118
+ * Execute opening of the db in a lock in order not to interfere with other operations.
119
+ */
120
+ return this._acquireLock(async () => {
121
+ // Dispose any previous table change listener.
122
+ this._disposeTableChangeListener?.();
123
+ this._disposeTableChangeListener = null;
124
+ this._db?.close().catch((ex) => this.logger.warn(`Error closing database before opening new instance`, ex));
125
+ const isReOpen = !!this._db;
126
+ this._db = null;
127
+
128
+ this._db = await this.options.openConnection();
129
+ await this._db.init();
130
+ this._config = await this._db.getConfig();
131
+ await this.registerOnChangeListener(this._db);
132
+ if (isReOpen) {
133
+ this.iterateListeners((cb) => cb.databaseReOpened?.());
134
+ }
135
+ /**
136
+ * This is only required for the long-lived shared IndexedDB connections.
137
+ */
138
+ this.requiresHolds = (this._config as ResolvedWASQLiteOpenFactoryOptions).vfs == WASQLiteVFS.IDBBatchAtomicVFS;
139
+ });
140
+ }
141
+
142
+ protected _reOpen() {
143
+ this.databaseOpenPromise = this.openInternalDB().finally(() => {
144
+ this.databaseOpenPromise = null;
145
+ });
146
+ return this.databaseOpenPromise;
147
+ }
148
+
149
+ /**
150
+ * Re-opens the underlying database.
151
+ * Returns a pending operation if one is already in progress.
152
+ */
153
+ async reOpenInternalDB(): Promise<void> {
154
+ if (!this.options.reOpenOnConnectionClosed) {
155
+ throw new Error(`Cannot re-open underlying database, reOpenOnConnectionClosed is not enabled`);
156
+ }
157
+ if (this.databaseOpenPromise) {
158
+ return this.databaseOpenPromise;
159
+ }
160
+ return this._reOpen();
161
+ }
162
+
108
163
  protected async _init() {
109
- this._db = await this.options.openConnection();
110
- await this._db.init();
111
- this._config = await this._db.getConfig();
112
- await this.registerOnChangeListener(this._db);
113
- this.iterateListeners((cb) => cb.initialized?.());
114
164
  /**
115
- * This is only required for the long-lived shared IndexedDB connections.
165
+ * For OPFS, we can see this open call sometimes fail due to NoModificationAllowedError.
166
+ * We should be able to recover from this by re-opening the database.
116
167
  */
117
- this.requiresHolds = (this._config as ResolvedWASQLiteOpenFactoryOptions).vfs == WASQLiteVFS.IDBBatchAtomicVFS;
168
+ const maxAttempts = 3;
169
+ for (let count = 0; count < maxAttempts; count++) {
170
+ try {
171
+ await this.openInternalDB();
172
+ break;
173
+ } catch (ex) {
174
+ if (count == maxAttempts - 1) {
175
+ throw ex;
176
+ }
177
+ this.logger.warn(`Attempt ${count + 1} of ${maxAttempts} to open database failed, retrying in 1 second...`, ex);
178
+ await new Promise((resolve) => setTimeout(resolve, 1000));
179
+ }
180
+ }
181
+ this.iterateListeners((cb) => cb.initialized?.());
118
182
  }
119
183
 
120
- getConfiguration(): ResolvedWebSQLOpenOptions {
184
+ getConfiguration(): WebDBAdapterConfiguration {
121
185
  if (!this._config) {
122
186
  throw new Error(`Cannot get config before initialization is completed`);
123
187
  }
124
- return this._config;
188
+ return {
189
+ ...this._config,
190
+ // This can be overridden by the adapter later
191
+ requiresPersistentTriggers: false
192
+ };
125
193
  }
126
194
 
127
195
  protected async waitForInitialized() {
@@ -170,7 +238,14 @@ export class LockedAsyncDatabaseAdapter
170
238
  */
171
239
  async close() {
172
240
  this.closing = true;
173
- this._disposeTableChangeListener?.();
241
+ /**
242
+ * Note that we obtain a reference to the callback to avoid calling the callback with `this` as the context.
243
+ * This is to avoid Comlink attempting to clone `this` when calling the method.
244
+ */
245
+ const dispose = this._disposeTableChangeListener;
246
+ if (dispose) {
247
+ dispose();
248
+ }
174
249
  this.pendingAbortControllers.forEach((controller) => controller.abort('Closed'));
175
250
  await this.baseDB?.close?.();
176
251
  this.closed = true;
@@ -196,7 +271,7 @@ export class LockedAsyncDatabaseAdapter
196
271
  return this.acquireLock(
197
272
  async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })),
198
273
  {
199
- timeoutMs: options?.timeoutMs
274
+ timeoutMs: options?.timeoutMs ?? this.options.defaultLockTimeoutMs
200
275
  }
201
276
  );
202
277
  }
@@ -206,23 +281,20 @@ export class LockedAsyncDatabaseAdapter
206
281
  return this.acquireLock(
207
282
  async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })),
208
283
  {
209
- timeoutMs: options?.timeoutMs
284
+ timeoutMs: options?.timeoutMs ?? this.options.defaultLockTimeoutMs
210
285
  }
211
286
  );
212
287
  }
213
288
 
214
- protected async acquireLock(callback: () => Promise<any>, options?: { timeoutMs?: number }): Promise<any> {
215
- await this.waitForInitialized();
216
-
289
+ protected async _acquireLock(callback: () => Promise<any>, options?: { timeoutMs?: number }): Promise<any> {
217
290
  if (this.closing) {
218
291
  throw new Error(`Cannot acquire lock, the database is closing`);
219
292
  }
220
-
221
293
  const abortController = new AbortController();
222
294
  this.pendingAbortControllers.add(abortController);
223
295
  const { timeoutMs } = options ?? {};
224
296
 
225
- const timoutId = timeoutMs
297
+ const timeoutId = timeoutMs
226
298
  ? setTimeout(() => {
227
299
  abortController.abort(`Timeout after ${timeoutMs}ms`);
228
300
  this.pendingAbortControllers.delete(abortController);
@@ -234,19 +306,52 @@ export class LockedAsyncDatabaseAdapter
234
306
  { signal: abortController.signal },
235
307
  async () => {
236
308
  this.pendingAbortControllers.delete(abortController);
237
- if (timoutId) {
238
- clearTimeout(timoutId);
309
+ if (timeoutId) {
310
+ clearTimeout(timeoutId);
239
311
  }
240
- const holdId = this.requiresHolds ? await this.baseDB.markHold() : null;
241
- try {
242
- return await callback();
243
- } finally {
244
- if (holdId) {
245
- await this.baseDB.releaseHold(holdId);
312
+ return await callback();
313
+ }
314
+ );
315
+ }
316
+
317
+ protected async acquireLock(callback: () => Promise<any>, options?: { timeoutMs?: number }): Promise<any> {
318
+ await this.waitForInitialized();
319
+
320
+ // The database is being opened in the background. Wait for it here.
321
+ if (this.databaseOpenPromise) {
322
+ await this.databaseOpenPromise;
323
+ }
324
+
325
+ return this._acquireLock(async () => {
326
+ let holdId: string | null = null;
327
+ try {
328
+ /**
329
+ * We can't await this since it uses the same lock as we're in now.
330
+ * If there is a pending open, this call will throw.
331
+ * If there is no pending open, but there is also no database - the open
332
+ * might have failed. We need to re-open the database.
333
+ */
334
+ if (this.databaseOpenPromise || !this._db) {
335
+ throw new ConnectionClosedError('Connection is busy re-opening');
336
+ }
337
+
338
+ holdId = this.requiresHolds ? await this.baseDB.markHold() : null;
339
+ return await callback();
340
+ } catch (ex) {
341
+ if (ConnectionClosedError.MATCHES(ex)) {
342
+ if (this.options.reOpenOnConnectionClosed && !this.databaseOpenPromise && !this.closing) {
343
+ // Immediately re-open the database. We need to miss as little table updates as possible.
344
+ // Note, don't await this since it uses the same lock as we're in now.
345
+ this.reOpenInternalDB();
246
346
  }
247
347
  }
348
+ throw ex;
349
+ } finally {
350
+ if (holdId) {
351
+ await this.baseDB.releaseHold(holdId);
352
+ }
248
353
  }
249
- );
354
+ }, options);
250
355
  }
251
356
 
252
357
  async readTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions | undefined): Promise<T> {
@@ -1,11 +1,15 @@
1
1
  import { DBAdapter } from '@powersync/common';
2
- import { ResolvedWebSQLOpenOptions } from './web-sql-flags';
2
+ import { ResolvedWebSQLOpenOptions } from './web-sql-flags.js';
3
3
 
4
4
  export type SharedConnectionWorker = {
5
5
  identifier: string;
6
6
  port: MessagePort;
7
7
  };
8
8
 
9
+ export type WebDBAdapterConfiguration = ResolvedWebSQLOpenOptions & {
10
+ requiresPersistentTriggers: boolean;
11
+ };
12
+
9
13
  export interface WebDBAdapter extends DBAdapter {
10
14
  /**
11
15
  * Get a MessagePort which can be used to share the internals of this connection.
@@ -16,5 +20,5 @@ export interface WebDBAdapter extends DBAdapter {
16
20
  * Get the config options used to open this connection.
17
21
  * This is useful for sharing connections.
18
22
  */
19
- getConfiguration(): ResolvedWebSQLOpenOptions;
23
+ getConfiguration(): WebDBAdapterConfiguration;
20
24
  }
@@ -1,11 +1,12 @@
1
+ import { BaseObserver, ConnectionClosedError } from '@powersync/common';
1
2
  import * as Comlink from 'comlink';
2
3
  import {
3
4
  AsyncDatabaseConnection,
4
5
  OnTableChangeCallback,
5
6
  OpenAsyncDatabaseConnection,
6
7
  ProxiedQueryResult
7
- } from './AsyncDatabaseConnection';
8
- import { ResolvedWebSQLOpenOptions } from './web-sql-flags';
8
+ } from './AsyncDatabaseConnection.js';
9
+ import { ResolvedWebSQLOpenOptions } from './web-sql-flags.js';
9
10
 
10
11
  export type SharedConnectionWorker = {
11
12
  identifier: string;
@@ -23,17 +24,23 @@ export type WrappedWorkerConnectionOptions<Config extends ResolvedWebSQLOpenOpti
23
24
  onClose?: () => void;
24
25
  };
25
26
 
27
+ export type WorkerWrappedAsyncDatabaseConnectionListener = {
28
+ closing: () => void;
29
+ };
26
30
  /**
27
31
  * Wraps a provided instance of {@link AsyncDatabaseConnection}, providing necessary proxy
28
32
  * functions for worker listeners.
29
33
  */
30
34
  export class WorkerWrappedAsyncDatabaseConnection<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions>
35
+ extends BaseObserver<WorkerWrappedAsyncDatabaseConnectionListener>
31
36
  implements AsyncDatabaseConnection
32
37
  {
33
38
  protected lockAbortController = new AbortController();
34
39
  protected notifyRemoteClosed: AbortController | undefined;
35
40
 
36
41
  constructor(protected options: WrappedWorkerConnectionOptions<Config>) {
42
+ super();
43
+
37
44
  if (options.remoteCanCloseUnexpectedly) {
38
45
  this.notifyRemoteClosed = new AbortController();
39
46
  }
@@ -72,18 +79,21 @@ export class WorkerWrappedAsyncDatabaseConnection<Config extends ResolvedWebSQLO
72
79
  return this.withRemote(() => this.baseConnection.isAutoCommit());
73
80
  }
74
81
 
75
- private withRemote<T>(workerPromise: () => Promise<T>): Promise<T> {
82
+ private withRemote<T>(workerPromise: () => Promise<T>, fireActionOnAbort = false): Promise<T> {
76
83
  const controller = this.notifyRemoteClosed;
77
84
  if (controller) {
78
85
  return new Promise((resolve, reject) => {
79
86
  if (controller.signal.aborted) {
80
- reject(new Error('Called operation on closed remote'));
81
- // Don't run the operation if we're going to reject
82
- return;
87
+ reject(new ConnectionClosedError('Called operation on closed remote'));
88
+ if (!fireActionOnAbort) {
89
+ // Don't run the operation if we're going to reject
90
+ // We might want to fire-and-forget the operation in some cases (like a close operation)
91
+ return;
92
+ }
83
93
  }
84
94
 
85
95
  function handleAbort() {
86
- reject(new Error('Remote peer closed with request in flight'));
96
+ reject(new ConnectionClosedError('Remote peer closed with request in flight'));
87
97
  }
88
98
 
89
99
  function completePromise(action: () => void) {
@@ -164,10 +174,12 @@ export class WorkerWrappedAsyncDatabaseConnection<Config extends ResolvedWebSQLO
164
174
  // Abort any pending lock requests.
165
175
  this.lockAbortController.abort();
166
176
  try {
167
- await this.withRemote(() => this.baseConnection.close());
177
+ // fire and forget the close operation
178
+ await this.withRemote(() => this.baseConnection.close(), true);
168
179
  } finally {
169
180
  this.options.remote[Comlink.releaseProxy]();
170
181
  this.options.onClose?.();
182
+ this.iterateListeners((l) => l.closing?.());
171
183
  }
172
184
  }
173
185
 
@@ -0,0 +1,23 @@
1
+ import { LockedAsyncDatabaseAdapter } from '../LockedAsyncDatabaseAdapter.js';
2
+ import { WebDBAdapterConfiguration } from '../WebDBAdapter.js';
3
+ import { WASQLiteVFS } from './WASQLiteConnection.js';
4
+ import { ResolvedWASQLiteOpenFactoryOptions } from './WASQLiteOpenFactory.js';
5
+
6
+ /**
7
+ * @internal
8
+ * An intermediary implementation of WASQLiteDBAdapter, which takes the same
9
+ * constructor arguments as {@link LockedAsyncDatabaseAdapter}, but provides some
10
+ * basic WA-SQLite specific functionality.
11
+ * This base class is used to avoid requiring overloading the constructor of {@link WASQLiteDBAdapter}
12
+ */
13
+ export class InternalWASQLiteDBAdapter extends LockedAsyncDatabaseAdapter {
14
+ getConfiguration(): WebDBAdapterConfiguration {
15
+ // This is valid since we only handle WASQLite connections
16
+ const baseConfig = super.getConfiguration() as unknown as ResolvedWASQLiteOpenFactoryOptions;
17
+ return {
18
+ ...super.getConfiguration(),
19
+ requiresPersistentTriggers:
20
+ baseConfig.vfs == WASQLiteVFS.OPFSCoopSyncVFS || baseConfig.vfs == WASQLiteVFS.AccessHandlePoolVFS
21
+ };
22
+ }
23
+ }