@powersync/web 0.0.0-dev-20250207081035 → 0.0.0-dev-20250220093908

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.0",
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:*",
@@ -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({
@@ -125,10 +128,7 @@ export class SharedSyncImplementation extends BaseObserver {
125
128
  * Adds a new client tab's message port to the list of connected ports
126
129
  */
127
130
  addPort(port) {
128
- const portProvider = {
129
- port,
130
- clientProvider: Comlink.wrap(port)
131
- };
131
+ const portProvider = { port, clientProvider: Comlink.wrap(port) };
132
132
  this.ports.push(portProvider);
133
133
  // Give the newly connected client the latest status
134
134
  const status = this.syncStreamClient?.syncStatus;
@@ -147,11 +147,7 @@ export class SharedSyncImplementation extends BaseObserver {
147
147
  return;
148
148
  }
149
149
  const trackedPort = this.ports[index];
150
- if (trackedPort.db) {
151
- trackedPort.db.close();
152
- }
153
- // Release proxy
154
- trackedPort.clientProvider[Comlink.releaseProxy]();
150
+ // Remove from the list of active ports
155
151
  this.ports.splice(index, 1);
156
152
  /**
157
153
  * The port might currently be in use. Any active functions might
@@ -162,12 +158,22 @@ export class SharedSyncImplementation extends BaseObserver {
162
158
  abortController.controller.abort(new AbortOperation('Closing pending requests after client port is removed'));
163
159
  }
164
160
  });
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);
161
+ const shouldReconnect = !!this.syncStreamClient;
162
+ if (this.dbAdapter && this.dbAdapter == trackedPort.db) {
163
+ if (shouldReconnect) {
164
+ await this.disconnect();
165
+ }
166
+ // Clearing the adapter will result in a new one being opened in connect
167
+ this.dbAdapter = null;
168
+ if (shouldReconnect) {
169
+ await this.connect(this.lastConnectOptions);
170
+ }
171
+ }
172
+ if (trackedPort.db) {
173
+ trackedPort.db.close();
170
174
  }
175
+ // Release proxy
176
+ trackedPort.clientProvider[Comlink.releaseProxy]();
171
177
  }
172
178
  triggerCrudUpload() {
173
179
  this.waitForReady().then(() => this.syncStreamClient?.triggerCrudUpload());
@@ -195,10 +201,7 @@ export class SharedSyncImplementation extends BaseObserver {
195
201
  const lastPort = this.ports[this.ports.length - 1];
196
202
  return new Promise(async (resolve, reject) => {
197
203
  const abortController = new AbortController();
198
- this.fetchCredentialsController = {
199
- controller: abortController,
200
- activePort: lastPort
201
- };
204
+ this.fetchCredentialsController = { controller: abortController, activePort: lastPort };
202
205
  abortController.signal.onabort = reject;
203
206
  try {
204
207
  console.log('calling the last port client provider for credentials');
@@ -217,10 +220,7 @@ export class SharedSyncImplementation extends BaseObserver {
217
220
  const lastPort = this.ports[this.ports.length - 1];
218
221
  return new Promise(async (resolve, reject) => {
219
222
  const abortController = new AbortController();
220
- this.uploadDataController = {
221
- controller: abortController,
222
- activePort: lastPort
223
- };
223
+ this.uploadDataController = { controller: abortController, activePort: lastPort };
224
224
  // Resolving will make it retry
225
225
  abortController.signal.onabort = () => resolve();
226
226
  try {
@@ -241,6 +241,10 @@ export class SharedSyncImplementation extends BaseObserver {
241
241
  }
242
242
  async openInternalDB() {
243
243
  const lastClient = this.ports[this.ports.length - 1];
244
+ if (!lastClient) {
245
+ // Should not really happen in practice
246
+ throw new Error(`Could not open DB connection since no client is connected.`);
247
+ }
244
248
  const workerPort = await lastClient.clientProvider.getDBWorkerPort();
245
249
  const remote = Comlink.wrap(workerPort);
246
250
  const identifier = this.syncParams.dbParams.dbFilename;
@@ -248,11 +252,7 @@ export class SharedSyncImplementation extends BaseObserver {
248
252
  const locked = new LockedAsyncDatabaseAdapter({
249
253
  name: identifier,
250
254
  openConnection: async () => {
251
- return new WorkerWrappedAsyncDatabaseConnection({
252
- remote,
253
- baseConnection: db,
254
- identifier
255
- });
255
+ return new WorkerWrappedAsyncDatabaseConnection({ remote, baseConnection: db, identifier });
256
256
  },
257
257
  logger: this.logger
258
258
  });