@powersync/web 1.13.1 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powersync/web",
3
- "version": "1.13.1",
3
+ "version": "1.14.1",
4
4
  "description": "A Web SDK for JourneyApps PowerSync",
5
5
  "main": "lib/src/index.js",
6
6
  "types": "lib/src/index.d.ts",
@@ -61,7 +61,7 @@
61
61
  "license": "Apache-2.0",
62
62
  "peerDependencies": {
63
63
  "@journeyapps/wa-sqlite": "^1.2.0",
64
- "@powersync/common": "workspace:^1.23.0"
64
+ "@powersync/common": "workspace:^1.24.0"
65
65
  },
66
66
  "dependencies": {
67
67
  "@powersync/common": "workspace:*",
@@ -74,20 +74,16 @@
74
74
  "devDependencies": {
75
75
  "@journeyapps/wa-sqlite": "^1.2.0",
76
76
  "@types/uuid": "^9.0.6",
77
- "@vitest/browser": "^2.1.4",
78
77
  "crypto-browserify": "^3.12.0",
79
78
  "p-defer": "^4.0.1",
80
79
  "source-map-loader": "^5.0.0",
81
80
  "stream-browserify": "^3.0.0",
82
81
  "terser-webpack-plugin": "^5.3.9",
83
- "typescript": "^5.5.3",
84
82
  "uuid": "^9.0.1",
85
- "vite": "^5.4.10",
86
- "vite-plugin-top-level-await": "^1.4.1",
83
+ "vite": "^6.1.0",
84
+ "vite-plugin-top-level-await": "^1.4.4",
87
85
  "vite-plugin-wasm": "^3.3.0",
88
- "vitest": "^2.1.4",
89
86
  "vm-browserify": "^1.1.2",
90
- "webdriverio": "^8.40.6",
91
87
  "webpack": "^5.90.1",
92
88
  "webpack-cli": "^5.1.4",
93
89
  "webpack-node-externals": "^3.0.0"
@@ -184,6 +184,7 @@ export class WASqliteConnection extends BaseObserver {
184
184
  await this.openDB();
185
185
  this.registerBroadcastListeners();
186
186
  await this.executeSingleStatement(`PRAGMA temp_store = ${this.options.temporaryStorage};`);
187
+ await this.executeSingleStatement(`PRAGMA cache_size = -${this.options.cacheSizeKb};`);
187
188
  await this.executeEncryptionPragma();
188
189
  this.sqliteAPI.update_hook(this.dbP, (updateType, dbName, tableName) => {
189
190
  if (!tableName) {
@@ -17,6 +17,7 @@ export interface WASQLiteDBAdapterOptions extends Omit<PowerSyncOpenFactoryOptio
17
17
  worker?: string | URL | ((options: ResolvedWebSQLOpenOptions) => Worker | SharedWorker);
18
18
  vfs?: WASQLiteVFS;
19
19
  temporaryStorage?: TemporaryStorageOption;
20
+ cacheSizeKb?: number;
20
21
  /**
21
22
  * Encryption key for the database.
22
23
  * If set, the database will be encrypted using multiple-ciphers.
@@ -1,7 +1,7 @@
1
1
  import * as Comlink from 'comlink';
2
2
  import { resolveWebPowerSyncFlags } from '../../PowerSyncDatabase';
3
3
  import { LockedAsyncDatabaseAdapter } from '../LockedAsyncDatabaseAdapter';
4
- import { TemporaryStorageOption } from '../web-sql-flags';
4
+ import { DEFAULT_CACHE_SIZE_KB, TemporaryStorageOption } from '../web-sql-flags';
5
5
  import { WorkerWrappedAsyncDatabaseConnection } from '../WorkerWrappedAsyncDatabaseConnection';
6
6
  import { WASQLiteOpenFactory } from './WASQLiteOpenFactory';
7
7
  /**
@@ -12,7 +12,7 @@ export class WASQLiteDBAdapter extends LockedAsyncDatabaseAdapter {
12
12
  super({
13
13
  name: options.dbFilename,
14
14
  openConnection: async () => {
15
- const { workerPort, temporaryStorage } = options;
15
+ const { workerPort, temporaryStorage, cacheSizeKb } = options;
16
16
  if (workerPort) {
17
17
  const remote = Comlink.wrap(workerPort);
18
18
  return new WorkerWrappedAsyncDatabaseConnection({
@@ -21,6 +21,7 @@ export class WASQLiteDBAdapter extends LockedAsyncDatabaseAdapter {
21
21
  baseConnection: await remote({
22
22
  ...options,
23
23
  temporaryStorage: temporaryStorage ?? TemporaryStorageOption.MEMORY,
24
+ cacheSizeKb: cacheSizeKb ?? DEFAULT_CACHE_SIZE_KB,
24
25
  flags: resolveWebPowerSyncFlags(options.flags),
25
26
  encryptionKey: options.encryptionKey
26
27
  })
@@ -32,6 +33,7 @@ export class WASQLiteDBAdapter extends LockedAsyncDatabaseAdapter {
32
33
  debugMode: options.debugMode,
33
34
  flags: options.flags,
34
35
  temporaryStorage,
36
+ cacheSizeKb,
35
37
  logger: options.logger,
36
38
  vfs: options.vfs,
37
39
  encryptionKey: options.encryptionKey,
@@ -2,7 +2,7 @@ import * as Comlink from 'comlink';
2
2
  import { openWorkerDatabasePort, resolveWorkerDatabasePortFactory } from '../../../worker/db/open-worker-database';
3
3
  import { AbstractWebSQLOpenFactory } from '../AbstractWebSQLOpenFactory';
4
4
  import { LockedAsyncDatabaseAdapter } from '../LockedAsyncDatabaseAdapter';
5
- import { TemporaryStorageOption } from '../web-sql-flags';
5
+ import { DEFAULT_CACHE_SIZE_KB, TemporaryStorageOption } from '../web-sql-flags';
6
6
  import { WorkerWrappedAsyncDatabaseConnection } from '../WorkerWrappedAsyncDatabaseConnection';
7
7
  import { WASqliteConnection, WASQLiteVFS } from './WASQLiteConnection';
8
8
  /**
@@ -27,7 +27,7 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
27
27
  }
28
28
  async openConnection() {
29
29
  const { enableMultiTabs, useWebWorker } = this.resolvedFlags;
30
- const { vfs = WASQLiteVFS.IDBBatchAtomicVFS, temporaryStorage = TemporaryStorageOption.MEMORY, encryptionKey } = this.waOptions;
30
+ const { vfs = WASQLiteVFS.IDBBatchAtomicVFS, temporaryStorage = TemporaryStorageOption.MEMORY, cacheSizeKb = DEFAULT_CACHE_SIZE_KB, encryptionKey } = this.waOptions;
31
31
  if (!enableMultiTabs) {
32
32
  this.logger.warn('Multiple tabs are not enabled in this browser');
33
33
  }
@@ -37,6 +37,7 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
37
37
  ? resolveWorkerDatabasePortFactory(() => optionsDbWorker({
38
38
  ...this.options,
39
39
  temporaryStorage,
40
+ cacheSizeKb,
40
41
  flags: this.resolvedFlags,
41
42
  encryptionKey
42
43
  }))
@@ -48,6 +49,7 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
48
49
  dbFilename: this.options.dbFilename,
49
50
  vfs,
50
51
  temporaryStorage,
52
+ cacheSizeKb,
51
53
  flags: this.resolvedFlags,
52
54
  encryptionKey: encryptionKey
53
55
  }),
@@ -70,6 +72,7 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
70
72
  debugMode: this.options.debugMode,
71
73
  vfs,
72
74
  temporaryStorage,
75
+ cacheSizeKb,
73
76
  flags: this.resolvedFlags,
74
77
  encryptionKey: encryptionKey
75
78
  });
@@ -39,6 +39,7 @@ export interface ResolvedWebSQLOpenOptions extends SQLOpenOptions {
39
39
  * Setting this to `FILESYSTEM` can cause issues with larger queries or datasets.
40
40
  */
41
41
  temporaryStorage: TemporaryStorageOption;
42
+ cacheSizeKb: number;
42
43
  /**
43
44
  * Encryption key for the database.
44
45
  * If set, the database will be encrypted using ChaCha20.
@@ -49,6 +50,7 @@ export declare enum TemporaryStorageOption {
49
50
  MEMORY = "memory",
50
51
  FILESYSTEM = "file"
51
52
  }
53
+ export declare const DEFAULT_CACHE_SIZE_KB: number;
52
54
  /**
53
55
  * Options for opening a Web SQL connection
54
56
  */
@@ -65,8 +67,16 @@ export interface WebSQLOpenFactoryOptions extends SQLOpenOptions {
65
67
  /**
66
68
  * Where to store SQLite temporary files. Defaults to 'MEMORY'.
67
69
  * Setting this to `FILESYSTEM` can cause issues with larger queries or datasets.
70
+ *
71
+ * For details, see: https://www.sqlite.org/pragma.html#pragma_temp_store
68
72
  */
69
73
  temporaryStorage?: TemporaryStorageOption;
74
+ /**
75
+ * Maximum SQLite cache size. Defaults to 50MB.
76
+ *
77
+ * For details, see: https://www.sqlite.org/pragma.html#pragma_cache_size
78
+ */
79
+ cacheSizeKb?: number;
70
80
  /**
71
81
  * Encryption key for the database.
72
82
  * If set, the database will be encrypted using ChaCha20.
@@ -3,6 +3,7 @@ export var TemporaryStorageOption;
3
3
  TemporaryStorageOption["MEMORY"] = "memory";
4
4
  TemporaryStorageOption["FILESYSTEM"] = "file";
5
5
  })(TemporaryStorageOption || (TemporaryStorageOption = {}));
6
+ export const DEFAULT_CACHE_SIZE_KB = 50 * 1024;
6
7
  export function isServerSide() {
7
8
  return typeof window == 'undefined';
8
9
  }
@@ -1,7 +1,7 @@
1
1
  import * as Comlink from 'comlink';
2
2
  import { AbstractSharedSyncClientProvider } from '../../worker/sync/AbstractSharedSyncClientProvider';
3
3
  import { SharedSyncClientEvent } from '../../worker/sync/SharedSyncImplementation';
4
- import { resolveWebSQLFlags, TemporaryStorageOption } from '../adapters/web-sql-flags';
4
+ import { DEFAULT_CACHE_SIZE_KB, resolveWebSQLFlags, TemporaryStorageOption } from '../adapters/web-sql-flags';
5
5
  import { WebStreamingSyncImplementation } from './WebStreamingSyncImplementation';
6
6
  /**
7
7
  * The shared worker will trigger methods on this side of the message port
@@ -86,10 +86,10 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
86
86
  * This worker will manage all syncing operations remotely.
87
87
  */
88
88
  const resolvedWorkerOptions = {
89
- ...options,
90
89
  dbFilename: this.options.identifier,
91
- // TODO
92
90
  temporaryStorage: TemporaryStorageOption.MEMORY,
91
+ cacheSizeKb: DEFAULT_CACHE_SIZE_KB,
92
+ ...options,
93
93
  flags: resolveWebSQLFlags(options.flags)
94
94
  };
95
95
  const syncWorker = options.sync?.worker;
@@ -102,6 +102,9 @@ export class SharedSyncImplementation extends BaseObserver {
102
102
  await this.waitForReady();
103
103
  // This effectively queues connect and disconnect calls. Ensuring multiple tabs' requests are synchronized
104
104
  return getNavigatorLocks().request('shared-sync-connect', async () => {
105
+ if (!this.dbAdapter) {
106
+ await this.openInternalDB();
107
+ }
105
108
  this.syncStreamClient = this.generateStreamingImplementation();
106
109
  this.lastConnectOptions = options;
107
110
  this.syncStreamClient.registerListener({
@@ -147,11 +150,7 @@ export class SharedSyncImplementation extends BaseObserver {
147
150
  return;
148
151
  }
149
152
  const trackedPort = this.ports[index];
150
- if (trackedPort.db) {
151
- trackedPort.db.close();
152
- }
153
- // Release proxy
154
- trackedPort.clientProvider[Comlink.releaseProxy]();
153
+ // Remove from the list of active ports
155
154
  this.ports.splice(index, 1);
156
155
  /**
157
156
  * The port might currently be in use. Any active functions might
@@ -162,12 +161,22 @@ export class SharedSyncImplementation extends BaseObserver {
162
161
  abortController.controller.abort(new AbortOperation('Closing pending requests after client port is removed'));
163
162
  }
164
163
  });
165
- if (this.dbAdapter == trackedPort.db && this.syncStreamClient) {
166
- await this.disconnect();
167
- // Ask for a new DB worker port handler
168
- await this.openInternalDB();
169
- await this.connect(this.lastConnectOptions);
164
+ const shouldReconnect = !!this.syncStreamClient;
165
+ if (this.dbAdapter && this.dbAdapter == trackedPort.db) {
166
+ if (shouldReconnect) {
167
+ await this.disconnect();
168
+ }
169
+ // Clearing the adapter will result in a new one being opened in connect
170
+ this.dbAdapter = null;
171
+ if (shouldReconnect) {
172
+ await this.connect(this.lastConnectOptions);
173
+ }
174
+ }
175
+ if (trackedPort.db) {
176
+ trackedPort.db.close();
170
177
  }
178
+ // Release proxy
179
+ trackedPort.clientProvider[Comlink.releaseProxy]();
171
180
  }
172
181
  triggerCrudUpload() {
173
182
  this.waitForReady().then(() => this.syncStreamClient?.triggerCrudUpload());
@@ -241,6 +250,10 @@ export class SharedSyncImplementation extends BaseObserver {
241
250
  }
242
251
  async openInternalDB() {
243
252
  const lastClient = this.ports[this.ports.length - 1];
253
+ if (!lastClient) {
254
+ // Should not really happen in practice
255
+ throw new Error(`Could not open DB connection since no client is connected.`);
256
+ }
244
257
  const workerPort = await lastClient.clientProvider.getDBWorkerPort();
245
258
  const remote = Comlink.wrap(workerPort);
246
259
  const identifier = this.syncParams.dbParams.dbFilename;