@powersync/web 0.0.0-dev-20260414110516 → 0.0.0-dev-20260504100448

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 (76) 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 +4289 -4786
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/worker/SharedSyncImplementation.umd.js +37 -798
  8. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  9. package/dist/worker/WASQLiteDB.umd.js +99 -737
  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 -7
  28. package/lib/src/db/PowerSyncDatabase.d.ts +1 -1
  29. package/lib/src/db/PowerSyncDatabase.js +0 -8
  30. package/lib/src/db/adapters/AsyncWebAdapter.d.ts +13 -3
  31. package/lib/src/db/adapters/AsyncWebAdapter.js +115 -21
  32. package/lib/src/db/adapters/wa-sqlite/DatabaseServer.js +1 -2
  33. package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.js +1 -1
  34. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +16 -2
  35. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +60 -38
  36. package/lib/src/db/adapters/wa-sqlite/vfs.d.ts +7 -18
  37. package/lib/src/db/adapters/wa-sqlite/vfs.js +34 -49
  38. package/lib/src/db/sync/SSRWebStreamingSyncImplementation.d.ts +4 -0
  39. package/lib/src/db/sync/SSRWebStreamingSyncImplementation.js +4 -0
  40. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +0 -1
  41. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +0 -3
  42. package/lib/src/db/sync/WebRemote.d.ts +1 -3
  43. package/lib/src/db/sync/WebRemote.js +0 -12
  44. package/lib/src/worker/db/MultiDatabaseServer.js +4 -1
  45. package/lib/src/worker/db/open-worker-database.js +2 -2
  46. package/lib/src/worker/sync/SharedSyncImplementation.d.ts +0 -2
  47. package/lib/src/worker/sync/SharedSyncImplementation.js +4 -16
  48. package/lib/src/worker/sync/WorkerClient.d.ts +0 -1
  49. package/lib/src/worker/sync/WorkerClient.js +0 -3
  50. package/lib/tsconfig.tsbuildinfo +1 -1
  51. package/package.json +5 -8
  52. package/src/db/PowerSyncDatabase.ts +1 -9
  53. package/src/db/adapters/AsyncWebAdapter.ts +138 -22
  54. package/src/db/adapters/wa-sqlite/DatabaseServer.ts +4 -2
  55. package/src/db/adapters/wa-sqlite/RawSqliteConnection.ts +4 -1
  56. package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +89 -44
  57. package/src/db/adapters/wa-sqlite/vfs.ts +33 -49
  58. package/src/db/sync/SSRWebStreamingSyncImplementation.ts +5 -0
  59. package/src/db/sync/SharedWebStreamingSyncImplementation.ts +0 -4
  60. package/src/db/sync/WebRemote.ts +0 -16
  61. package/src/worker/db/MultiDatabaseServer.ts +4 -1
  62. package/src/worker/db/open-worker-database.ts +2 -2
  63. package/src/worker/sync/SharedSyncImplementation.ts +4 -18
  64. package/src/worker/sync/WorkerClient.ts +0 -4
  65. package/dist/26d61ca9f5694d064635.wasm +0 -0
  66. package/dist/b4c6283dc473b6b3fd24.wasm +0 -0
  67. package/dist/c78985091a0b22aaef03.wasm +0 -0
  68. package/dist/ca59e199e1138b553fad.wasm +0 -0
  69. package/dist/worker/node_modules_pnpm_bson_6_10_4_node_modules_bson_lib_bson_mjs.umd.js +0 -4646
  70. package/dist/worker/node_modules_pnpm_bson_6_10_4_node_modules_bson_lib_bson_mjs.umd.js.map +0 -1
  71. 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
  72. 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
  73. 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
  74. 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
  75. 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
  76. 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
package/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powersync/web",
3
- "version": "1.37.1",
3
+ "version": "1.37.2",
4
4
  "description": "PowerSync Web SDK",
5
5
  "main": "lib/src/index.js",
6
6
  "module": "lib/src/index.js",
@@ -67,26 +67,23 @@
67
67
  "license": "Apache-2.0",
68
68
  "peerDependencies": {
69
69
  "@journeyapps/wa-sqlite": "catalog:",
70
- "@powersync/common": "workspace:^1.51.0"
70
+ "@powersync/common": "workspace:^1.52.0"
71
71
  },
72
72
  "dependencies": {
73
73
  "@powersync/common": "workspace:*",
74
- "bson": "catalog:",
75
74
  "comlink": "catalog:",
76
75
  "commander": "^12.1.0"
77
76
  },
78
77
  "devDependencies": {
79
- "@journeyapps/wa-sqlite": "^1.5.0",
78
+ "@journeyapps/wa-sqlite": "^1.7.0",
80
79
  "@types/uuid": "catalog:",
81
- "crypto-browserify": "^3.12.0",
80
+ "bson": "catalog:",
82
81
  "glob": "catalog:",
83
82
  "p-defer": "catalog:",
84
83
  "source-map-loader": "^5.0.0",
85
- "stream-browserify": "^3.0.0",
86
84
  "terser-webpack-plugin": "^5.3.9",
87
85
  "uuid": "catalog:",
88
86
  "vite": "catalog:",
89
- "vm-browserify": "^1.1.2",
90
87
  "webpack": "^5.90.1",
91
88
  "webpack-cli": "^5.1.4",
92
89
  "webpack-node-externals": "^3.0.0"
@@ -2,6 +2,7 @@ import { AbstractPowerSyncDatabase, DBAdapter, PowerSyncDatabaseOptions, PowerSy
2
2
  import { ResolvedWebSQLOpenOptions, WebSQLFlags } from './adapters/web-sql-flags.js';
3
3
  export interface WebPowerSyncFlags extends WebSQLFlags {
4
4
  /**
5
+ * @deprecated This flag is no longer used. Navigator locks now handle tab detection automatically.
5
6
  * Externally unload open PowerSync database instances when the window closes.
6
7
  * Setting this to `true` requires calling `close` on all open PowerSyncDatabase
7
8
  * instances before the window unloads
@@ -54,7 +55,6 @@ export declare const resolveWebPowerSyncFlags: (flags?: WebPowerSyncFlags) => Re
54
55
  export declare class PowerSyncDatabase extends AbstractPowerSyncDatabase {
55
56
  protected options: WebPowerSyncDatabaseOptions;
56
57
  static SHARED_MUTEX: Mutex;
57
- protected unloadListener?: () => Promise<void>;
58
58
  protected resolvedFlags: WebPowerSyncFlags;
59
59
  constructor(options: WebPowerSyncDatabaseOptionsWithAdapter);
60
60
  constructor(options: WebPowerSyncDatabaseOptionsWithOpenFactory);
@@ -47,17 +47,12 @@ function assertValidDatabaseOptions(options) {
47
47
  export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
48
48
  options;
49
49
  static SHARED_MUTEX = new Mutex();
50
- unloadListener;
51
50
  resolvedFlags;
52
51
  constructor(options) {
53
52
  super(options);
54
53
  this.options = options;
55
54
  assertValidDatabaseOptions(options);
56
55
  this.resolvedFlags = resolveWebPowerSyncFlags(options.flags);
57
- if (this.resolvedFlags.enableMultiTabs && !this.resolvedFlags.externallyUnload) {
58
- this.unloadListener = () => this.close({ disconnect: false });
59
- window.addEventListener('unload', this.unloadListener);
60
- }
61
56
  }
62
57
  async _initialize() {
63
58
  if (this.database instanceof AsyncDbAdapter) {
@@ -98,9 +93,6 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
98
93
  * multiple tabs are not enabled.
99
94
  */
100
95
  close(options) {
101
- if (this.unloadListener) {
102
- window.removeEventListener('unload', this.unloadListener);
103
- }
104
96
  return super.close({
105
97
  // Don't disconnect by default if multiple tabs are enabled
106
98
  disconnect: options?.disconnect ?? !this.resolvedFlags.enableMultiTabs
@@ -6,10 +6,10 @@ import { DatabaseClient } from './wa-sqlite/DatabaseClient.js';
6
6
  */
7
7
  declare class AsyncConnectionPool implements ConnectionPool {
8
8
  readonly name: string;
9
- protected readonly inner: Promise<DatabaseClient>;
10
- protected resolvedClient?: DatabaseClient;
9
+ protected readonly state: Promise<PoolState>;
10
+ protected resolvedWriter?: DatabaseClient;
11
11
  private readonly pendingListeners;
12
- constructor(inner: Promise<DatabaseClient>, name: string);
12
+ constructor(inner: Promise<PoolConnection>, name: string);
13
13
  init(): Promise<void>;
14
14
  close(): Promise<void>;
15
15
  readLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions): Promise<T>;
@@ -17,6 +17,16 @@ declare class AsyncConnectionPool implements ConnectionPool {
17
17
  refreshSchema(): Promise<void>;
18
18
  registerListener(listener: Partial<DBAdapterListener>): () => void;
19
19
  }
20
+ export interface PoolConnection {
21
+ writer: DatabaseClient;
22
+ additionalReaders: DatabaseClient[];
23
+ }
24
+ interface PoolState {
25
+ writer: DatabaseClient;
26
+ withConnection<T>(allowReadOnly: boolean, fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions): Promise<T>;
27
+ close(): Promise<void>;
28
+ refreshSchema(): Promise<void>;
29
+ }
20
30
  declare const AsyncDbAdapter_base: (new (...args: any[]) => {
21
31
  readTransaction<T>(fn: (tx: import("@powersync/common").Transaction) => Promise<T>, options?: DBLockOptions): Promise<T>;
22
32
  writeTransaction<T>(fn: (tx: import("@powersync/common").Transaction) => Promise<T>, options?: DBLockOptions): Promise<T>;
@@ -1,44 +1,48 @@
1
- import { DBAdapterDefaultMixin } from '@powersync/common';
1
+ import { DBAdapterDefaultMixin, Mutex, Semaphore } from '@powersync/common';
2
2
  /**
3
3
  * A connection pool implementation delegating to another pool opened asynchronnously.
4
4
  */
5
5
  class AsyncConnectionPool {
6
6
  name;
7
- inner;
8
- resolvedClient;
7
+ state;
8
+ resolvedWriter;
9
9
  pendingListeners = new Set();
10
10
  constructor(inner, name) {
11
11
  this.name = name;
12
- this.inner = inner.then((client) => {
12
+ this.state = inner.then((client) => {
13
13
  for (const pending of this.pendingListeners) {
14
- pending.closeAfterRegisteredOnResolvedPool = client.registerListener(pending.listener);
14
+ pending.closeAfterRegisteredOnResolvedPool = client.writer.registerListener(pending.listener);
15
15
  }
16
16
  this.pendingListeners.clear();
17
- this.resolvedClient = client;
18
- return client;
17
+ this.resolvedWriter = client.writer;
18
+ if (client.additionalReaders.length) {
19
+ return readWritePoolState(client.writer, client.additionalReaders);
20
+ }
21
+ return singleConnectionPoolState(client.writer);
19
22
  });
20
23
  }
21
24
  async init() {
22
- await this.inner;
25
+ await this.state;
23
26
  }
24
27
  async close() {
25
- const inner = await this.inner;
26
- return await inner.close();
28
+ const state = await this.state;
29
+ await state.close();
27
30
  }
28
31
  async readLock(fn, options) {
29
- const inner = await this.inner;
30
- return await inner.readLock(fn, options);
32
+ const state = await this.state;
33
+ return state.withConnection(true, fn, options);
31
34
  }
32
35
  async writeLock(fn, options) {
33
- const inner = await this.inner;
34
- return await inner.writeLock(fn, options);
36
+ const state = await this.state;
37
+ return state.withConnection(false, fn, options);
35
38
  }
36
39
  async refreshSchema() {
37
- await (await this.inner).refreshSchema();
40
+ const state = await this.state;
41
+ await state.refreshSchema();
38
42
  }
39
43
  registerListener(listener) {
40
- if (this.resolvedClient) {
41
- return this.resolvedClient.registerListener(listener);
44
+ if (this.resolvedWriter) {
45
+ return this.resolvedWriter.registerListener(listener);
42
46
  }
43
47
  else {
44
48
  const pending = { listener };
@@ -55,14 +59,104 @@ class AsyncConnectionPool {
55
59
  }
56
60
  }
57
61
  }
62
+ function singleConnectionPoolState(connection) {
63
+ return {
64
+ writer: connection,
65
+ withConnection: (allowReadOnly, fn, options) => {
66
+ if (allowReadOnly) {
67
+ return connection.readLock(fn, options);
68
+ }
69
+ else {
70
+ return connection.writeLock(fn, options);
71
+ }
72
+ },
73
+ close: () => connection.close(),
74
+ refreshSchema: () => connection.refreshSchema()
75
+ };
76
+ }
77
+ function readWritePoolState(writer, readers) {
78
+ // DatabaseClients have locks internally, so these aren't necessary for correctness. However, our mutex and semaphore
79
+ // implementations are very cheap to cancel, which we use to dispatch reads to the first available connection (by
80
+ // simply requesting all of them and sticking with the first connection we get).
81
+ const writerMutex = new Mutex();
82
+ const readerSemaphore = new Semaphore(readers);
83
+ return {
84
+ writer,
85
+ async withConnection(allowReadOnly, fn, options) {
86
+ const abortController = new AbortController();
87
+ const abortSignal = abortController.signal;
88
+ let timeout = null;
89
+ let release;
90
+ if (options?.timeoutMs) {
91
+ timeout = setTimeout(() => abortController.abort, options.timeoutMs);
92
+ }
93
+ try {
94
+ if (allowReadOnly) {
95
+ let connection;
96
+ // Even if we have a pool of read connections, it's typically very small and we assume that most queries are
97
+ // reads. So, we want to request any connection from the read pool and the dedicated write connection (which
98
+ // can also serve reads). We race for the first connection we can obtain this way, and then abort the other
99
+ // request.
100
+ [connection, release] = await new Promise((resolve, reject) => {
101
+ let didComplete = false;
102
+ function complete() {
103
+ didComplete = true;
104
+ abortController.abort();
105
+ }
106
+ function completeSuccess(connection, returnFn) {
107
+ if (didComplete) {
108
+ // We're not going to use this connection, so return it immediately.
109
+ returnFn();
110
+ }
111
+ else {
112
+ complete();
113
+ resolve([connection, returnFn]);
114
+ }
115
+ }
116
+ function completeError(error) {
117
+ // We either have a working connection already, or we've rejected the promise. Either way, we don't need
118
+ // to do either thing again.
119
+ if (didComplete)
120
+ return;
121
+ complete();
122
+ reject(error);
123
+ }
124
+ writerMutex.acquire(abortSignal).then((unlock) => completeSuccess(writer, unlock), completeError);
125
+ readerSemaphore
126
+ .requestOne(abortSignal)
127
+ .then(({ item, release }) => completeSuccess(item, release), completeError);
128
+ });
129
+ return await connection.readLock(fn);
130
+ }
131
+ else {
132
+ return await writerMutex.runExclusive(() => writer.writeLock(fn), abortSignal);
133
+ }
134
+ }
135
+ finally {
136
+ if (timeout != null) {
137
+ clearTimeout(timeout);
138
+ }
139
+ release?.();
140
+ }
141
+ },
142
+ async close() {
143
+ await writer.close();
144
+ await Promise.all(readers.map((r) => r.close()));
145
+ },
146
+ async refreshSchema() {
147
+ await writer.refreshSchema();
148
+ await Promise.all(readers.map((r) => r.refreshSchema()));
149
+ }
150
+ };
151
+ }
58
152
  export class AsyncDbAdapter extends DBAdapterDefaultMixin(AsyncConnectionPool) {
59
153
  async shareConnection() {
60
- const inner = await this.inner;
61
- return inner.shareConnection();
154
+ const state = await this.state;
155
+ return state.writer.shareConnection();
62
156
  }
63
157
  getConfiguration() {
64
- if (this.resolvedClient) {
65
- return this.resolvedClient.getConfiguration();
158
+ if (this.resolvedWriter) {
159
+ return this.resolvedWriter.getConfiguration();
66
160
  }
67
161
  throw new Error('AsyncDbAdapter.getConfiguration() can only be called after initializing it.');
68
162
  }
@@ -85,8 +85,7 @@ export class DatabaseServer {
85
85
  },
86
86
  requestAccess: async (write, timeoutMs) => {
87
87
  requireOpen();
88
- // TODO: Support timeouts, they don't seem to be supported by the async-mutex package.
89
- const lease = await this.#inner.acquireConnection();
88
+ const lease = await this.#inner.acquireConnection(timeoutMs != null ? AbortSignal.timeout(timeoutMs) : undefined);
90
89
  if (!isOpen) {
91
90
  // Race between requestAccess and close(), the connection was closed while we tried to acquire a lease.
92
91
  await lease.returnLease();
@@ -23,7 +23,7 @@ export class RawSqliteConnection {
23
23
  }
24
24
  async init() {
25
25
  const api = (this._sqliteAPI = await this.openSQLiteAPI());
26
- this.db = await api.open_v2(this.options.dbFilename);
26
+ this.db = await api.open_v2(this.options.dbFilename, this.options.isReadOnly ? 1 /* SQLITE_OPEN_READONLY */ : 6 /* SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE */);
27
27
  await this.executeRaw(`PRAGMA temp_store = ${this.options.temporaryStorage};`);
28
28
  if (this.options.encryptionKey) {
29
29
  const escapedKey = this.options.encryptionKey.replace("'", "''");
@@ -1,12 +1,26 @@
1
1
  import { DBAdapter, SQLOpenFactory, type ILogLevel } from '@powersync/common';
2
2
  import { ResolvedWebSQLOpenOptions, WebSQLOpenFactoryOptions } from '../web-sql-flags.js';
3
3
  import { WASQLiteVFS } from './vfs.js';
4
- import { DatabaseClient } from './DatabaseClient.js';
4
+ import { PoolConnection } from '../AsyncWebAdapter.js';
5
5
  export interface WASQLiteOpenFactoryOptions extends WebSQLOpenFactoryOptions {
6
6
  vfs?: WASQLiteVFS;
7
+ /**
8
+ * If the {@link vfs} supports it, an additional amount of read-only connections to open. Using additional read
9
+ * connections can speed up queries by dispatching them to multiple workers running them concurrently.
10
+ *
11
+ * {@link WASQLiteVFS.OPFSWriteAheadVFS} is the only VFS with support for multiple connections, so this option is
12
+ * ignored for other VFS implementations.
13
+ *
14
+ * Defaults to 1.
15
+ */
16
+ additionalReaders?: number;
7
17
  }
8
18
  export interface ResolvedWASQLiteOpenFactoryOptions extends ResolvedWebSQLOpenOptions {
9
19
  vfs: WASQLiteVFS;
20
+ /**
21
+ * Whether this is a read-only connection opened for the `OPFSWriteAheadVFS` file system.
22
+ */
23
+ isReadOnly: boolean;
10
24
  }
11
25
  export interface WorkerDBOpenerOptions extends ResolvedWASQLiteOpenFactoryOptions {
12
26
  logLevel: ILogLevel;
@@ -27,5 +41,5 @@ export declare class WASQLiteOpenFactory implements SQLOpenFactory {
27
41
  get waOptions(): WASQLiteOpenFactoryOptions;
28
42
  protected openAdapter(): DBAdapter;
29
43
  openDB(): DBAdapter;
30
- openConnection(): Promise<DatabaseClient>;
44
+ openConnection(): Promise<PoolConnection>;
31
45
  }
@@ -50,7 +50,7 @@ export class WASQLiteOpenFactory {
50
50
  if (!enableMultiTabs) {
51
51
  this.logger.warn('Multiple tabs are not enabled in this browser');
52
52
  }
53
- const resolvedOptions = {
53
+ const resolveOptions = (isReadOnly) => ({
54
54
  dbFilename: this.options.dbFilename,
55
55
  dbLocation: this.options.dbLocation,
56
56
  debugMode: this.options.debugMode,
@@ -58,55 +58,77 @@ export class WASQLiteOpenFactory {
58
58
  temporaryStorage,
59
59
  cacheSizeKb,
60
60
  flags: this.resolvedFlags,
61
- encryptionKey: encryptionKey
62
- };
63
- let clientOptions;
61
+ encryptionKey: encryptionKey,
62
+ isReadOnly
63
+ });
64
+ let client;
65
+ let additionalReaders = [];
64
66
  let requiresPersistentTriggers = vfsRequiresDedicatedWorkers(vfs);
65
67
  if (useWebWorker) {
66
68
  const optionsDbWorker = this.options.worker;
67
- const workerPort = typeof optionsDbWorker == 'function'
68
- ? resolveWorkerDatabasePortFactory(() => optionsDbWorker({
69
- ...this.options,
70
- temporaryStorage,
71
- cacheSizeKb,
72
- flags: this.resolvedFlags,
73
- encryptionKey
74
- }))
75
- : openWorkerDatabasePort(this.options.dbFilename, enableMultiTabs, optionsDbWorker, this.waOptions.vfs);
76
- const source = Comlink.wrap(workerPort);
77
- const closeSignal = new AbortController();
78
- const connection = await source.connect({
79
- ...resolvedOptions,
80
- logLevel: this.logger.getLevel(),
81
- lockName: await generateTabCloseSignal(closeSignal.signal)
82
- });
83
- clientOptions = {
84
- connection,
85
- source,
86
- // This tab owns the worker, so we're guaranteed to outlive it.
87
- remoteCanCloseUnexpectedly: false,
88
- onClose: () => {
89
- closeSignal.abort();
90
- if (workerPort instanceof Worker) {
91
- workerPort.terminate();
92
- }
93
- else {
94
- 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
+ }
95
99
  }
96
- }
100
+ };
101
+ return new DatabaseClient(clientOptions, {
102
+ ...resolvedOptions,
103
+ requiresPersistentTriggers
104
+ });
97
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);
114
+ }
115
+ }
98
116
  }
99
117
  else {
100
118
  // Don't use a web worker. Instead, open the MultiDatabaseServer a worker would use locally.
101
119
  const localServer = new MultiDatabaseServer(this.logger);
102
120
  requiresPersistentTriggers = true;
121
+ const resolvedOptions = resolveOptions(false);
103
122
  const connection = await localServer.openConnectionLocally(resolvedOptions);
104
- clientOptions = { connection, source: null, remoteCanCloseUnexpectedly: false };
123
+ client = new DatabaseClient({ connection, source: null, remoteCanCloseUnexpectedly: false }, {
124
+ ...resolvedOptions,
125
+ requiresPersistentTriggers
126
+ });
105
127
  }
106
- return new DatabaseClient(clientOptions, {
107
- ...resolvedOptions,
108
- requiresPersistentTriggers
109
- });
128
+ return {
129
+ writer: client,
130
+ additionalReaders
131
+ };
110
132
  }
111
133
  }
112
134
  /**
@@ -5,9 +5,10 @@ import type * as SQLite from '@journeyapps/wa-sqlite';
5
5
  export declare enum WASQLiteVFS {
6
6
  IDBBatchAtomicVFS = "IDBBatchAtomicVFS",
7
7
  OPFSCoopSyncVFS = "OPFSCoopSyncVFS",
8
- AccessHandlePoolVFS = "AccessHandlePoolVFS"
8
+ AccessHandlePoolVFS = "AccessHandlePoolVFS",
9
+ OPFSWriteAheadVFS = "OPFSWriteAheadVFS"
9
10
  }
10
- export declare function vfsRequiresDedicatedWorkers(vfs: WASQLiteVFS): vfs is WASQLiteVFS.OPFSCoopSyncVFS | WASQLiteVFS.AccessHandlePoolVFS;
11
+ export declare function vfsRequiresDedicatedWorkers(vfs: WASQLiteVFS): vfs is WASQLiteVFS.OPFSCoopSyncVFS | WASQLiteVFS.AccessHandlePoolVFS | WASQLiteVFS.OPFSWriteAheadVFS;
11
12
  /**
12
13
  * @internal
13
14
  */
@@ -26,22 +27,6 @@ export type WASQLiteModuleFactory = (options: WASQLiteModuleFactoryOptions) => P
26
27
  module: SQLiteModule;
27
28
  vfs: SQLiteVFS;
28
29
  }>;
29
- /**
30
- * @internal
31
- */
32
- export declare const AsyncWASQLiteModuleFactory: () => Promise<any>;
33
- /**
34
- * @internal
35
- */
36
- export declare const MultiCipherAsyncWASQLiteModuleFactory: () => Promise<any>;
37
- /**
38
- * @internal
39
- */
40
- export declare const SyncWASQLiteModuleFactory: () => Promise<any>;
41
- /**
42
- * @internal
43
- */
44
- export declare const MultiCipherSyncWASQLiteModuleFactory: () => Promise<any>;
45
30
  /**
46
31
  * @internal
47
32
  */
@@ -58,4 +43,8 @@ export declare const DEFAULT_MODULE_FACTORIES: {
58
43
  module: any;
59
44
  vfs: any;
60
45
  }>;
46
+ OPFSWriteAheadVFS: (options: WASQLiteModuleFactoryOptions) => Promise<{
47
+ module: any;
48
+ vfs: any;
49
+ }>;
61
50
  };
@@ -6,50 +6,37 @@ export var WASQLiteVFS;
6
6
  WASQLiteVFS["IDBBatchAtomicVFS"] = "IDBBatchAtomicVFS";
7
7
  WASQLiteVFS["OPFSCoopSyncVFS"] = "OPFSCoopSyncVFS";
8
8
  WASQLiteVFS["AccessHandlePoolVFS"] = "AccessHandlePoolVFS";
9
+ WASQLiteVFS["OPFSWriteAheadVFS"] = "OPFSWriteAheadVFS";
9
10
  })(WASQLiteVFS || (WASQLiteVFS = {}));
10
11
  export function vfsRequiresDedicatedWorkers(vfs) {
11
12
  return vfs != WASQLiteVFS.IDBBatchAtomicVFS;
12
13
  }
13
- /**
14
- * @internal
15
- */
16
- export const AsyncWASQLiteModuleFactory = async () => {
17
- const { default: factory } = await import('@journeyapps/wa-sqlite/dist/wa-sqlite-async.mjs');
18
- return factory();
19
- };
20
- /**
21
- * @internal
22
- */
23
- export const MultiCipherAsyncWASQLiteModuleFactory = async () => {
24
- const { default: factory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite-async.mjs');
25
- return factory();
26
- };
27
- /**
28
- * @internal
29
- */
30
- export const SyncWASQLiteModuleFactory = async () => {
31
- const { default: factory } = await import('@journeyapps/wa-sqlite/dist/wa-sqlite.mjs');
32
- return factory();
33
- };
34
- /**
35
- * @internal
36
- */
37
- export const MultiCipherSyncWASQLiteModuleFactory = async () => {
38
- const { default: factory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite.mjs');
39
- return factory();
40
- };
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
+ }
41
34
  /**
42
35
  * @internal
43
36
  */
44
37
  export const DEFAULT_MODULE_FACTORIES = {
45
38
  [WASQLiteVFS.IDBBatchAtomicVFS]: async (options) => {
46
- let module;
47
- if (options.encryptionKey) {
48
- module = await MultiCipherAsyncWASQLiteModuleFactory();
49
- }
50
- else {
51
- module = await AsyncWASQLiteModuleFactory();
52
- }
39
+ const module = await asyncModuleFactory(options.encryptionKey);
53
40
  const { IDBBatchAtomicVFS } = await import('@journeyapps/wa-sqlite/src/examples/IDBBatchAtomicVFS.js');
54
41
  return {
55
42
  module,
@@ -58,13 +45,7 @@ export const DEFAULT_MODULE_FACTORIES = {
58
45
  };
59
46
  },
60
47
  [WASQLiteVFS.AccessHandlePoolVFS]: async (options) => {
61
- let module;
62
- if (options.encryptionKey) {
63
- module = await MultiCipherSyncWASQLiteModuleFactory();
64
- }
65
- else {
66
- module = await SyncWASQLiteModuleFactory();
67
- }
48
+ const module = await syncModuleFactory(options.encryptionKey);
68
49
  // @ts-expect-error The types for this static method are missing upstream
69
50
  const { AccessHandlePoolVFS } = await import('@journeyapps/wa-sqlite/src/examples/AccessHandlePoolVFS.js');
70
51
  return {
@@ -73,13 +54,7 @@ export const DEFAULT_MODULE_FACTORIES = {
73
54
  };
74
55
  },
75
56
  [WASQLiteVFS.OPFSCoopSyncVFS]: async (options) => {
76
- let module;
77
- if (options.encryptionKey) {
78
- module = await MultiCipherSyncWASQLiteModuleFactory();
79
- }
80
- else {
81
- module = await SyncWASQLiteModuleFactory();
82
- }
57
+ const module = await syncModuleFactory(options.encryptionKey);
83
58
  // @ts-expect-error The types for this static method are missing upstream
84
59
  const { OPFSCoopSyncVFS } = await import('@journeyapps/wa-sqlite/src/examples/OPFSCoopSyncVFS.js');
85
60
  const vfs = await OPFSCoopSyncVFS.create(options.dbFileName, module);
@@ -87,5 +62,15 @@ export const DEFAULT_MODULE_FACTORIES = {
87
62
  module,
88
63
  vfs
89
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
+ };
90
75
  }
91
76
  };
@@ -44,4 +44,8 @@ export declare class SSRStreamingSyncImplementation extends BaseObserver impleme
44
44
  * No-op in SSR mode.
45
45
  */
46
46
  updateSubscriptions(): void;
47
+ /**
48
+ * No-op in SSR mode.
49
+ */
50
+ markConnectionMayHaveChanged(): void;
47
51
  }
@@ -61,4 +61,8 @@ export class SSRStreamingSyncImplementation extends BaseObserver {
61
61
  * No-op in SSR mode.
62
62
  */
63
63
  updateSubscriptions() { }
64
+ /**
65
+ * No-op in SSR mode.
66
+ */
67
+ markConnectionMayHaveChanged() { }
64
68
  }