@powersync/web 0.0.0-dev-20250127153955 → 0.0.0-dev-20250210155038

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.12.3",
3
+ "version": "1.13.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.22.2"
64
+ "@powersync/common": "workspace:^1.23.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"
@@ -1,4 +1,4 @@
1
- import { type BucketStorageAdapter, type PowerSyncBackendConnector, type PowerSyncCloseOptions, type PowerSyncConnectionOptions, AbstractPowerSyncDatabase, DBAdapter, PowerSyncDatabaseOptions, PowerSyncDatabaseOptionsWithDBAdapter, PowerSyncDatabaseOptionsWithOpenFactory, PowerSyncDatabaseOptionsWithSettings, StreamingSyncImplementation } from '@powersync/common';
1
+ import { type BucketStorageAdapter, type PowerSyncBackendConnector, type PowerSyncCloseOptions, type PowerSyncConnectionOptions, type RequiredAdditionalConnectionOptions, AbstractPowerSyncDatabase, DBAdapter, PowerSyncDatabaseOptions, PowerSyncDatabaseOptionsWithDBAdapter, PowerSyncDatabaseOptionsWithOpenFactory, PowerSyncDatabaseOptionsWithSettings, StreamingSyncImplementation } from '@powersync/common';
2
2
  import { Mutex } from 'async-mutex';
3
3
  import { ResolvedWebSQLOpenOptions, WebSQLFlags } from './adapters/web-sql-flags';
4
4
  export interface WebPowerSyncFlags extends WebSQLFlags {
@@ -72,6 +72,6 @@ export declare class PowerSyncDatabase extends AbstractPowerSyncDatabase {
72
72
  connect(connector: PowerSyncBackendConnector, options?: PowerSyncConnectionOptions): Promise<void>;
73
73
  protected generateBucketStorageAdapter(): BucketStorageAdapter;
74
74
  protected runExclusive<T>(cb: () => Promise<T>): Promise<any>;
75
- protected generateSyncStreamImplementation(connector: PowerSyncBackendConnector): StreamingSyncImplementation;
75
+ protected generateSyncStreamImplementation(connector: PowerSyncBackendConnector, options: RequiredAdditionalConnectionOptions): StreamingSyncImplementation;
76
76
  }
77
77
  export {};
@@ -1,4 +1,4 @@
1
- import { AbstractPowerSyncDatabase, DEFAULT_POWERSYNC_CLOSE_OPTIONS, isDBAdapter, isSQLOpenFactory, SqliteBucketStorage } from '@powersync/common';
1
+ import { AbstractPowerSyncDatabase, DEFAULT_POWERSYNC_CLOSE_OPTIONS, isDBAdapter, isSQLOpenFactory, SqliteBucketStorage, } from '@powersync/common';
2
2
  import { Mutex } from 'async-mutex';
3
3
  import { getNavigatorLocks } from '../shared/navigator';
4
4
  import { WASQLiteOpenFactory } from './adapters/wa-sqlite/WASQLiteOpenFactory';
@@ -101,10 +101,12 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
101
101
  }
102
102
  return getNavigatorLocks().request(`lock-${this.database.name}`, cb);
103
103
  }
104
- generateSyncStreamImplementation(connector) {
104
+ generateSyncStreamImplementation(connector, options) {
105
105
  const remote = new WebRemote(connector);
106
106
  const syncOptions = {
107
107
  ...this.options,
108
+ retryDelayMs: options.retryDelayMs,
109
+ crudUploadThrottleMs: options.crudUploadThrottleMs,
108
110
  flags: this.resolvedFlags,
109
111
  adapter: this.bucketStorageAdapter,
110
112
  remote,
@@ -20,6 +20,8 @@ export type WrappedWorkerConnectionOptions<Config extends ResolvedWebSQLOpenOpti
20
20
  */
21
21
  export declare class WorkerWrappedAsyncDatabaseConnection<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions> implements AsyncDatabaseConnection {
22
22
  protected options: WrappedWorkerConnectionOptions<Config>;
23
+ protected releaseSharedConnectionLock: (() => void) | null;
24
+ protected lockAbortController: AbortController;
23
25
  constructor(options: WrappedWorkerConnectionOptions<Config>);
24
26
  protected get baseConnection(): AsyncDatabaseConnection<ResolvedWebSQLOpenOptions>;
25
27
  init(): Promise<void>;
@@ -5,8 +5,12 @@ import * as Comlink from 'comlink';
5
5
  */
6
6
  export class WorkerWrappedAsyncDatabaseConnection {
7
7
  options;
8
+ releaseSharedConnectionLock;
9
+ lockAbortController;
8
10
  constructor(options) {
9
11
  this.options = options;
12
+ this.releaseSharedConnectionLock = null;
13
+ this.lockAbortController = new AbortController();
10
14
  }
11
15
  get baseConnection() {
12
16
  return this.options.baseConnection;
@@ -19,6 +23,30 @@ export class WorkerWrappedAsyncDatabaseConnection {
19
23
  */
20
24
  async shareConnection() {
21
25
  const { identifier, remote } = this.options;
26
+ /**
27
+ * Hold a navigator lock in order to avoid features such as Chrome's frozen tabs,
28
+ * or Edge's sleeping tabs from pausing the thread for this connection.
29
+ * This promise resolves once a lock is obtained.
30
+ * This lock will be held as long as this connection is open.
31
+ * The `shareConnection` method should not be called on multiple tabs concurrently.
32
+ */
33
+ await new Promise((lockObtained) => navigator.locks
34
+ .request(`shared-connection-${this.options.identifier}`, {
35
+ signal: this.lockAbortController.signal
36
+ }, async () => {
37
+ lockObtained();
38
+ // Free the lock when the connection is already closed.
39
+ if (this.lockAbortController.signal.aborted) {
40
+ return;
41
+ }
42
+ // Hold the lock while the shared connection is in use.
43
+ await new Promise((releaseLock) => {
44
+ // We can use the resolver to free the lock
45
+ this.releaseSharedConnectionLock = releaseLock;
46
+ });
47
+ })
48
+ // We aren't concerned with errors here
49
+ .catch(() => { }));
22
50
  const newPort = await remote[Comlink.createEndpoint]();
23
51
  return { port: newPort, identifier };
24
52
  }
@@ -30,6 +58,9 @@ export class WorkerWrappedAsyncDatabaseConnection {
30
58
  return this.baseConnection.registerOnTableChange(Comlink.proxy(callback));
31
59
  }
32
60
  async close() {
61
+ // Abort any pending lock requests.
62
+ this.lockAbortController.abort();
63
+ this.releaseSharedConnectionLock?.();
33
64
  await this.baseConnection.close();
34
65
  this.options.remote[Comlink.releaseProxy]();
35
66
  this.options.onClose?.();
@@ -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;