@powersync/web 0.0.0-dev-20260311103504 → 0.0.0-dev-20260503073249

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 (121) 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 +5341 -1279
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/worker/SharedSyncImplementation.umd.js +1113 -3526
  8. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  9. package/dist/worker/WASQLiteDB.umd.js +1397 -1332
  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 -5
  28. package/lib/src/db/PowerSyncDatabase.d.ts +2 -3
  29. package/lib/src/db/PowerSyncDatabase.js +3 -12
  30. package/lib/src/db/adapters/AsyncWebAdapter.d.ts +50 -0
  31. package/lib/src/db/adapters/AsyncWebAdapter.js +163 -0
  32. package/lib/src/db/adapters/SSRDBAdapter.d.ts +1 -2
  33. package/lib/src/db/adapters/SSRDBAdapter.js +5 -6
  34. package/lib/src/db/adapters/wa-sqlite/ConcurrentConnection.d.ts +56 -0
  35. package/lib/src/db/adapters/wa-sqlite/ConcurrentConnection.js +121 -0
  36. package/lib/src/db/adapters/wa-sqlite/DatabaseClient.d.ts +54 -0
  37. package/lib/src/db/adapters/wa-sqlite/DatabaseClient.js +227 -0
  38. package/lib/src/db/adapters/wa-sqlite/DatabaseServer.d.ts +47 -0
  39. package/lib/src/db/adapters/wa-sqlite/DatabaseServer.js +145 -0
  40. package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.d.ts +46 -0
  41. package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.js +147 -0
  42. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +28 -6
  43. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +104 -55
  44. package/lib/src/db/adapters/wa-sqlite/vfs.d.ts +50 -0
  45. package/lib/src/db/adapters/wa-sqlite/vfs.js +76 -0
  46. package/lib/src/db/adapters/web-sql-flags.d.ts +5 -0
  47. package/lib/src/db/sync/SSRWebStreamingSyncImplementation.d.ts +5 -2
  48. package/lib/src/db/sync/SSRWebStreamingSyncImplementation.js +6 -3
  49. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +4 -19
  50. package/lib/src/index.d.ts +1 -4
  51. package/lib/src/index.js +1 -4
  52. package/lib/src/shared/tab_close_signal.d.ts +11 -0
  53. package/lib/src/shared/tab_close_signal.js +26 -0
  54. package/lib/src/worker/db/MultiDatabaseServer.d.ts +17 -0
  55. package/lib/src/worker/db/MultiDatabaseServer.js +89 -0
  56. package/lib/src/worker/db/WASQLiteDB.worker.js +9 -48
  57. package/lib/src/worker/db/open-worker-database.d.ts +3 -3
  58. package/lib/src/worker/db/open-worker-database.js +2 -2
  59. package/lib/src/worker/sync/SharedSyncImplementation.d.ts +5 -6
  60. package/lib/src/worker/sync/SharedSyncImplementation.js +88 -54
  61. package/lib/tsconfig.tsbuildinfo +1 -1
  62. package/package.json +5 -6
  63. package/src/db/PowerSyncDatabase.ts +4 -12
  64. package/src/db/adapters/AsyncWebAdapter.ts +207 -0
  65. package/src/db/adapters/SSRDBAdapter.ts +7 -7
  66. package/src/db/adapters/wa-sqlite/ConcurrentConnection.ts +137 -0
  67. package/src/db/adapters/wa-sqlite/DatabaseClient.ts +325 -0
  68. package/src/db/adapters/wa-sqlite/DatabaseServer.ts +203 -0
  69. package/src/db/adapters/wa-sqlite/RawSqliteConnection.ts +194 -0
  70. package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +152 -63
  71. package/src/db/adapters/wa-sqlite/vfs.ts +96 -0
  72. package/src/db/adapters/web-sql-flags.ts +6 -0
  73. package/src/db/sync/SSRWebStreamingSyncImplementation.ts +7 -3
  74. package/src/db/sync/SharedWebStreamingSyncImplementation.ts +4 -20
  75. package/src/index.ts +1 -4
  76. package/src/shared/tab_close_signal.ts +28 -0
  77. package/src/worker/db/MultiDatabaseServer.ts +107 -0
  78. package/src/worker/db/WASQLiteDB.worker.ts +10 -57
  79. package/src/worker/db/open-worker-database.ts +4 -4
  80. package/src/worker/sync/SharedSyncImplementation.ts +114 -58
  81. package/dist/26d61ca9f5694d064635.wasm +0 -0
  82. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js +0 -1878
  83. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js.map +0 -1
  84. package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-97ebe9.index.umd.js +0 -555
  85. package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-97ebe9.index.umd.js.map +0 -1
  86. package/dist/b4c6283dc473b6b3fd24.wasm +0 -0
  87. package/dist/c78985091a0b22aaef03.wasm +0 -0
  88. package/dist/ca59e199e1138b553fad.wasm +0 -0
  89. 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
  90. 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
  91. 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
  92. 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
  93. 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
  94. 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
  95. package/lib/src/db/adapters/AbstractWebSQLOpenFactory.d.ts +0 -17
  96. package/lib/src/db/adapters/AbstractWebSQLOpenFactory.js +0 -33
  97. package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +0 -49
  98. package/lib/src/db/adapters/AsyncDatabaseConnection.js +0 -1
  99. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +0 -109
  100. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +0 -401
  101. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +0 -59
  102. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +0 -147
  103. package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.d.ts +0 -12
  104. package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.js +0 -19
  105. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.d.ts +0 -155
  106. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js +0 -401
  107. package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.d.ts +0 -32
  108. package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.js +0 -49
  109. package/lib/src/worker/db/SharedWASQLiteConnection.d.ts +0 -42
  110. package/lib/src/worker/db/SharedWASQLiteConnection.js +0 -90
  111. package/lib/src/worker/db/WorkerWASQLiteConnection.d.ts +0 -9
  112. package/lib/src/worker/db/WorkerWASQLiteConnection.js +0 -12
  113. package/src/db/adapters/AbstractWebSQLOpenFactory.ts +0 -48
  114. package/src/db/adapters/AsyncDatabaseConnection.ts +0 -55
  115. package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +0 -490
  116. package/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts +0 -201
  117. package/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.ts +0 -23
  118. package/src/db/adapters/wa-sqlite/WASQLiteConnection.ts +0 -497
  119. package/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts +0 -86
  120. package/src/worker/db/SharedWASQLiteConnection.ts +0 -131
  121. package/src/worker/db/WorkerWASQLiteConnection.ts +0 -14
@@ -0,0 +1,194 @@
1
+ import { Factory as WaSqliteFactory, SQLITE_ROW } from '@journeyapps/wa-sqlite';
2
+
3
+ import { DEFAULT_MODULE_FACTORIES, WASQLiteModuleFactory } from './vfs.js';
4
+ import { ResolvedWASQLiteOpenFactoryOptions } from './WASQLiteOpenFactory.js';
5
+
6
+ export interface RawResultSet {
7
+ columns: string[];
8
+ rows: SQLiteCompatibleType[][];
9
+ }
10
+
11
+ export interface RawQueryResult {
12
+ changes: number;
13
+ lastInsertRowId: number;
14
+ autocommit: boolean;
15
+ resultSet: RawResultSet | undefined;
16
+ }
17
+
18
+ /**
19
+ * A small wrapper around WA-sqlite to help with opening databases and running statements by preparing them internally.
20
+ *
21
+ * This is an internal class, and it must never be used directly. Wrappers are required to ensure raw connections aren't
22
+ * used concurrently across tabs.
23
+ */
24
+ export class RawSqliteConnection {
25
+ private _sqliteAPI: SQLiteAPI | null = null;
26
+ /**
27
+ * The `sqlite3*` connection pointer.
28
+ */
29
+ private db: number = 0;
30
+ private _moduleFactory: WASQLiteModuleFactory;
31
+
32
+ constructor(readonly options: ResolvedWASQLiteOpenFactoryOptions) {
33
+ this._moduleFactory = DEFAULT_MODULE_FACTORIES[this.options.vfs];
34
+ }
35
+
36
+ get isOpen(): boolean {
37
+ return this.db != 0;
38
+ }
39
+
40
+ async init() {
41
+ const api = (this._sqliteAPI = await this.openSQLiteAPI());
42
+ this.db = await api.open_v2(
43
+ this.options.dbFilename,
44
+ this.options.isReadOnly ? 1 /* SQLITE_OPEN_READONLY */ : 6 /* SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE */
45
+ );
46
+ await this.executeRaw(`PRAGMA temp_store = ${this.options.temporaryStorage};`);
47
+ if (this.options.encryptionKey) {
48
+ const escapedKey = this.options.encryptionKey.replace("'", "''");
49
+ await this.executeRaw(`PRAGMA key = '${escapedKey}'`);
50
+ }
51
+ await this.executeRaw(`PRAGMA cache_size = -${this.options.cacheSizeKb};`);
52
+
53
+ await this.executeRaw(`SELECT powersync_update_hooks('install');`);
54
+ }
55
+
56
+ private async openSQLiteAPI(): Promise<SQLiteAPI> {
57
+ const { module, vfs } = await this._moduleFactory({
58
+ dbFileName: this.options.dbFilename,
59
+ encryptionKey: this.options.encryptionKey
60
+ });
61
+ const sqlite3 = WaSqliteFactory(module);
62
+ sqlite3.vfs_register(vfs, true);
63
+ /**
64
+ * Register the PowerSync core SQLite extension
65
+ */
66
+ module.ccall('powersync_init_static', 'int', []);
67
+
68
+ /**
69
+ * Create the multiple cipher vfs if an encryption key is provided
70
+ */
71
+ if (this.options.encryptionKey) {
72
+ const createResult = module.ccall('sqlite3mc_vfs_create', 'int', ['string', 'int'], [this.options.dbFilename, 1]);
73
+ if (createResult !== 0) {
74
+ throw new Error('Failed to create multiple cipher vfs, Database encryption will not work');
75
+ }
76
+ }
77
+
78
+ return sqlite3;
79
+ }
80
+
81
+ requireSqlite(): SQLiteAPI {
82
+ if (!this._sqliteAPI) {
83
+ throw new Error(`Initialization has not completed`);
84
+ }
85
+ return this._sqliteAPI;
86
+ }
87
+
88
+ /**
89
+ * Checks if the database connection is in autocommit mode.
90
+ * @returns true if in autocommit mode, false if in a transaction
91
+ */
92
+ isAutoCommit(): boolean {
93
+ return this.requireSqlite().get_autocommit(this.db) != 0;
94
+ }
95
+
96
+ async execute(sql: string, bindings?: any[]): Promise<RawQueryResult> {
97
+ const resultSet = await this.executeSingleStatementRaw(sql, bindings);
98
+ return this.wrapQueryResults(this.requireSqlite(), resultSet);
99
+ }
100
+
101
+ async executeBatch(sql: string, bindings: any[][]): Promise<RawQueryResult[]> {
102
+ const results = [];
103
+ const api = this.requireSqlite();
104
+ for await (const stmt of api.statements(this.db, sql)) {
105
+ let columns;
106
+
107
+ for (const parameterSet of bindings) {
108
+ const rs = await this.stepThroughStatement(api, stmt, parameterSet, columns, false);
109
+ results.push(this.wrapQueryResults(api, rs));
110
+ }
111
+
112
+ // executeBatch can only use a single statement
113
+ break;
114
+ }
115
+
116
+ return results;
117
+ }
118
+
119
+ private wrapQueryResults(api: SQLiteAPI, rs: RawResultSet | undefined): RawQueryResult {
120
+ return {
121
+ changes: api.changes(this.db),
122
+ lastInsertRowId: api.last_insert_id(this.db),
123
+ autocommit: api.get_autocommit(this.db) != 0,
124
+ resultSet: rs
125
+ };
126
+ }
127
+
128
+ /**
129
+ * This executes a single statement using SQLite3 and returns the results as a {@link RawResultSet}.
130
+ */
131
+ private async executeSingleStatementRaw(sql: string, bindings?: any[]): Promise<RawResultSet | undefined> {
132
+ const results = await this.executeRaw(sql, bindings);
133
+ return results.length ? results[0] : undefined;
134
+ }
135
+
136
+ async executeRaw(sql: string, bindings?: any[]): Promise<RawResultSet[]> {
137
+ const results = [];
138
+ const api = this.requireSqlite();
139
+ for await (const stmt of api.statements(this.db, sql)) {
140
+ let columns;
141
+
142
+ const rs = await this.stepThroughStatement(api, stmt, bindings ?? [], columns);
143
+ columns = rs.columns;
144
+ if (columns.length) {
145
+ results.push(rs);
146
+ }
147
+
148
+ // When binding parameters, only a single statement is executed.
149
+ if (bindings) {
150
+ break;
151
+ }
152
+ }
153
+
154
+ return results;
155
+ }
156
+
157
+ private async stepThroughStatement(
158
+ api: SQLiteAPI,
159
+ stmt: number,
160
+ bindings: any[],
161
+ knownColumns: string[] | undefined,
162
+ includeResults: boolean = true
163
+ ): Promise<RawResultSet> {
164
+ // TODO not sure why this is needed currently, but booleans break
165
+ bindings.forEach((b, index, arr) => {
166
+ if (typeof b == 'boolean') {
167
+ arr[index] = b ? 1 : 0;
168
+ }
169
+ });
170
+
171
+ api.reset(stmt);
172
+ if (bindings) {
173
+ api.bind_collection(stmt, bindings);
174
+ }
175
+
176
+ const rows = [];
177
+ while ((await api.step(stmt)) === SQLITE_ROW) {
178
+ if (includeResults) {
179
+ const row = api.row(stmt);
180
+ rows.push(row);
181
+ }
182
+ }
183
+
184
+ knownColumns ??= api.column_names(stmt);
185
+ return { columns: knownColumns, rows };
186
+ }
187
+
188
+ async close() {
189
+ if (this.isOpen) {
190
+ await this.requireSqlite().close(this.db);
191
+ this.db = 0;
192
+ }
193
+ }
194
+ }
@@ -1,38 +1,65 @@
1
- import { DBAdapter, type ILogLevel } from '@powersync/common';
1
+ import { createLogger, DBAdapter, ILogger, SQLOpenFactory, type ILogLevel } from '@powersync/common';
2
2
  import * as Comlink from 'comlink';
3
3
  import { openWorkerDatabasePort, resolveWorkerDatabasePortFactory } from '../../../worker/db/open-worker-database.js';
4
- import { AbstractWebSQLOpenFactory } from '../AbstractWebSQLOpenFactory.js';
5
- import { AsyncDatabaseConnection, OpenAsyncDatabaseConnection } from '../AsyncDatabaseConnection.js';
6
- import { WorkerWrappedAsyncDatabaseConnection } from '../WorkerWrappedAsyncDatabaseConnection.js';
7
4
  import {
8
5
  DEFAULT_CACHE_SIZE_KB,
6
+ isServerSide,
7
+ ResolvedWebSQLFlags,
9
8
  ResolvedWebSQLOpenOptions,
9
+ resolveWebSQLFlags,
10
10
  TemporaryStorageOption,
11
11
  WebSQLOpenFactoryOptions
12
12
  } from '../web-sql-flags.js';
13
- import { InternalWASQLiteDBAdapter } from './InternalWASQLiteDBAdapter.js';
14
- import { WASQLiteVFS, WASqliteConnection } from './WASQLiteConnection.js';
13
+ import { SSRDBAdapter } from '../SSRDBAdapter.js';
14
+ import { vfsRequiresDedicatedWorkers, WASQLiteVFS } from './vfs.js';
15
+ import { MultiDatabaseServer } from '../../../worker/db/MultiDatabaseServer.js';
16
+ import { DatabaseClient, OpenWorkerConnection } from './DatabaseClient.js';
17
+ import { generateTabCloseSignal } from '../../../shared/tab_close_signal.js';
18
+ import { AsyncDbAdapter, PoolConnection } from '../AsyncWebAdapter.js';
15
19
 
16
20
  export interface WASQLiteOpenFactoryOptions extends WebSQLOpenFactoryOptions {
17
21
  vfs?: WASQLiteVFS;
22
+ /**
23
+ * If the {@link vfs} supports it, an additional amount of read-only connections to open. Using additional read
24
+ * connections can speed up queries by dispatching them to multiple workers running them concurrently.
25
+ *
26
+ * {@link WASQLiteVFS.OPFSWriteAheadVFS} is the only VFS with support for multiple connections, so this option is
27
+ * ignored for other VFS implementations.
28
+ *
29
+ * Defaults to 1.
30
+ */
31
+ additionalReaders?: number;
18
32
  }
19
33
 
20
34
  export interface ResolvedWASQLiteOpenFactoryOptions extends ResolvedWebSQLOpenOptions {
21
35
  vfs: WASQLiteVFS;
36
+
37
+ /**
38
+ * Whether this is a read-only connection opened for the `OPFSWriteAheadVFS` file system.
39
+ */
40
+ isReadOnly: boolean;
22
41
  }
23
42
 
24
43
  export interface WorkerDBOpenerOptions extends ResolvedWASQLiteOpenFactoryOptions {
25
44
  logLevel: ILogLevel;
45
+ /**
46
+ * A lock that is currently held by the client. When the lock is returned, we know the client is gone and that we need
47
+ * to clean up resources.
48
+ */
49
+ lockName: string;
26
50
  }
27
51
 
28
52
  /**
29
53
  * Opens a SQLite connection using WA-SQLite.
30
54
  */
31
- export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
32
- constructor(options: WASQLiteOpenFactoryOptions) {
33
- super(options);
55
+ export class WASQLiteOpenFactory implements SQLOpenFactory {
56
+ private resolvedFlags: ResolvedWebSQLFlags;
57
+ private logger: ILogger;
34
58
 
59
+ constructor(private options: WASQLiteOpenFactoryOptions) {
35
60
  assertValidWASQLiteOpenFactoryOptions(options);
61
+ this.resolvedFlags = resolveWebSQLFlags(options.flags);
62
+ this.logger = options.logger ?? createLogger(`WASQLiteOpenFactory - ${this.options.dbFilename}`);
36
63
  }
37
64
 
38
65
  get waOptions(): WASQLiteOpenFactoryOptions {
@@ -41,15 +68,36 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
41
68
  }
42
69
 
43
70
  protected openAdapter(): DBAdapter {
44
- return new InternalWASQLiteDBAdapter({
45
- name: this.options.dbFilename,
46
- openConnection: () => this.openConnection(),
47
- debugMode: this.options.debugMode,
48
- logger: this.logger
49
- });
71
+ return new AsyncDbAdapter(this.openConnection(), this.options.dbFilename);
72
+ }
73
+
74
+ openDB(): DBAdapter {
75
+ const {
76
+ resolvedFlags: { disableSSRWarning, enableMultiTabs, ssrMode = isServerSide() }
77
+ } = this;
78
+ if (ssrMode) {
79
+ if (!disableSSRWarning) {
80
+ this.logger.warn(
81
+ `
82
+ Running PowerSync in SSR mode.
83
+ Only empty query results will be returned.
84
+ Disable this warning by setting 'disableSSRWarning: true' in options.`
85
+ );
86
+ }
87
+
88
+ return new SSRDBAdapter();
89
+ }
90
+
91
+ if (!enableMultiTabs) {
92
+ this.logger.warn(
93
+ 'Multiple tab support is not enabled. Using this site across multiple tabs may not function correctly.'
94
+ );
95
+ }
96
+
97
+ return this.openAdapter();
50
98
  }
51
99
 
52
- async openConnection(): Promise<AsyncDatabaseConnection> {
100
+ async openConnection(): Promise<PoolConnection> {
53
101
  const { enableMultiTabs, useWebWorker } = this.resolvedFlags;
54
102
  const {
55
103
  vfs = WASQLiteVFS.IDBBatchAtomicVFS,
@@ -62,59 +110,100 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
62
110
  this.logger.warn('Multiple tabs are not enabled in this browser');
63
111
  }
64
112
 
113
+ const resolveOptions = (isReadOnly: boolean): ResolvedWASQLiteOpenFactoryOptions => ({
114
+ dbFilename: this.options.dbFilename,
115
+ dbLocation: this.options.dbLocation,
116
+ debugMode: this.options.debugMode,
117
+ vfs,
118
+ temporaryStorage,
119
+ cacheSizeKb,
120
+ flags: this.resolvedFlags,
121
+ encryptionKey: encryptionKey,
122
+ isReadOnly
123
+ });
124
+
125
+ let client: DatabaseClient;
126
+ let additionalReaders: DatabaseClient[] = [];
127
+ let requiresPersistentTriggers = vfsRequiresDedicatedWorkers(vfs);
128
+
65
129
  if (useWebWorker) {
66
130
  const optionsDbWorker = this.options.worker;
67
131
 
68
- const workerPort =
69
- typeof optionsDbWorker == 'function'
70
- ? resolveWorkerDatabasePortFactory(() =>
71
- optionsDbWorker({
72
- ...this.options,
73
- temporaryStorage,
74
- cacheSizeKb,
75
- flags: this.resolvedFlags,
76
- encryptionKey
77
- })
78
- )
79
- : openWorkerDatabasePort(this.options.dbFilename, enableMultiTabs, optionsDbWorker, this.waOptions.vfs);
80
-
81
- const workerDBOpener = Comlink.wrap<OpenAsyncDatabaseConnection<WorkerDBOpenerOptions>>(workerPort);
82
-
83
- return new WorkerWrappedAsyncDatabaseConnection({
84
- remote: workerDBOpener,
85
- // This tab owns the worker, so we're guaranteed to outlive it.
86
- remoteCanCloseUnexpectedly: false,
87
- baseConnection: await workerDBOpener({
88
- dbFilename: this.options.dbFilename,
89
- vfs,
90
- temporaryStorage,
91
- cacheSizeKb,
92
- flags: this.resolvedFlags,
93
- encryptionKey: encryptionKey,
94
- logLevel: this.logger.getLevel()
95
- }),
96
- identifier: this.options.dbFilename,
97
- onClose: () => {
98
- if (workerPort instanceof Worker) {
99
- workerPort.terminate();
100
- } else {
101
- workerPort.close();
132
+ const openDatabaseWorker = async (
133
+ resolvedOptions: ResolvedWASQLiteOpenFactoryOptions
134
+ ): Promise<DatabaseClient> => {
135
+ const workerPort =
136
+ typeof optionsDbWorker == 'function'
137
+ ? resolveWorkerDatabasePortFactory(() =>
138
+ optionsDbWorker({
139
+ ...this.options,
140
+ temporaryStorage,
141
+ cacheSizeKb,
142
+ flags: this.resolvedFlags,
143
+ encryptionKey
144
+ })
145
+ )
146
+ : openWorkerDatabasePort(this.options.dbFilename, enableMultiTabs, optionsDbWorker, this.waOptions.vfs);
147
+
148
+ const source = Comlink.wrap<OpenWorkerConnection>(workerPort);
149
+ const closeSignal = new AbortController();
150
+ const connection = await source.connect({
151
+ ...resolvedOptions,
152
+ logLevel: this.logger.getLevel(),
153
+ lockName: await generateTabCloseSignal(closeSignal.signal)
154
+ });
155
+ const clientOptions = {
156
+ connection,
157
+ source,
158
+ // This tab owns the worker, so we're guaranteed to outlive it.
159
+ remoteCanCloseUnexpectedly: false,
160
+ onClose: () => {
161
+ closeSignal.abort();
162
+ if (workerPort instanceof Worker) {
163
+ workerPort.terminate();
164
+ } else {
165
+ workerPort.close();
166
+ }
102
167
  }
168
+ };
169
+
170
+ return new DatabaseClient(clientOptions, {
171
+ ...resolvedOptions,
172
+ requiresPersistentTriggers
173
+ });
174
+ };
175
+
176
+ client = await openDatabaseWorker(resolveOptions(false));
177
+
178
+ if (vfs == WASQLiteVFS.OPFSWriteAheadVFS) {
179
+ // This VFS supports concurrent reads, so we can open additional workers to host read-only connections for
180
+ // concurrent reads / writes.
181
+ const additionalReadersCount = this.options.additionalReaders ?? 1;
182
+ for (let i = 0; i < additionalReadersCount; i++) {
183
+ const reader = await openDatabaseWorker(resolveOptions(true));
184
+ additionalReaders.push(reader);
103
185
  }
104
- });
186
+ }
105
187
  } else {
106
- // Don't use a web worker
107
- return new WASqliteConnection({
108
- dbFilename: this.options.dbFilename,
109
- dbLocation: this.options.dbLocation,
110
- debugMode: this.options.debugMode,
111
- vfs,
112
- temporaryStorage,
113
- cacheSizeKb,
114
- flags: this.resolvedFlags,
115
- encryptionKey: encryptionKey
116
- });
188
+ // Don't use a web worker. Instead, open the MultiDatabaseServer a worker would use locally.
189
+ const localServer = new MultiDatabaseServer(this.logger);
190
+ requiresPersistentTriggers = true;
191
+
192
+ const resolvedOptions = resolveOptions(false);
193
+ const connection = await localServer.openConnectionLocally(resolvedOptions);
194
+ client = new DatabaseClient(
195
+ { connection, source: null, remoteCanCloseUnexpectedly: false },
196
+ {
197
+ ...resolvedOptions,
198
+ requiresPersistentTriggers
199
+ }
200
+ );
117
201
  }
202
+
203
+ return {
204
+ writer: client,
205
+ additionalReaders
206
+ };
118
207
  }
119
208
  }
120
209
 
@@ -125,7 +214,7 @@ function assertValidWASQLiteOpenFactoryOptions(options: WASQLiteOpenFactoryOptio
125
214
  // The OPFS VFS only works in dedicated web workers.
126
215
  if ('vfs' in options && 'flags' in options) {
127
216
  const { vfs, flags = {} } = options;
128
- if (vfs !== WASQLiteVFS.IDBBatchAtomicVFS && 'useWebWorker' in flags && !flags.useWebWorker) {
217
+ if (vfs && vfsRequiresDedicatedWorkers(vfs) && 'useWebWorker' in flags && !flags.useWebWorker) {
129
218
  throw new Error(
130
219
  `Invalid configuration: The 'useWebWorker' flag must be true when using an OPFS-based VFS (${vfs}).`
131
220
  );
@@ -0,0 +1,96 @@
1
+ import type * as SQLite from '@journeyapps/wa-sqlite';
2
+
3
+ /**
4
+ * List of currently tested virtual filesystems
5
+ */
6
+ export enum WASQLiteVFS {
7
+ IDBBatchAtomicVFS = 'IDBBatchAtomicVFS',
8
+ OPFSCoopSyncVFS = 'OPFSCoopSyncVFS',
9
+ AccessHandlePoolVFS = 'AccessHandlePoolVFS',
10
+ OPFSWriteAheadVFS = 'OPFSWriteAheadVFS'
11
+ }
12
+
13
+ export function vfsRequiresDedicatedWorkers(vfs: WASQLiteVFS) {
14
+ return vfs != WASQLiteVFS.IDBBatchAtomicVFS;
15
+ }
16
+
17
+ /**
18
+ * @internal
19
+ */
20
+ export type WASQLiteModuleFactoryOptions = { dbFileName: string; encryptionKey?: string };
21
+
22
+ /**
23
+ * @internal
24
+ */
25
+ export type SQLiteModule = Parameters<typeof SQLite.Factory>[0];
26
+
27
+ /**
28
+ * @internal
29
+ */
30
+ export type WASQLiteModuleFactory = (
31
+ options: WASQLiteModuleFactoryOptions
32
+ ) => Promise<{ module: SQLiteModule; vfs: SQLiteVFS }>;
33
+
34
+ async function asyncModuleFactory(encryptionKey: unknown) {
35
+ if (encryptionKey) {
36
+ const { default: factory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite-async.mjs');
37
+ return factory();
38
+ } else {
39
+ const { default: factory } = await import('@journeyapps/wa-sqlite/dist/wa-sqlite-async.mjs');
40
+ return factory();
41
+ }
42
+ }
43
+
44
+ async function syncModuleFactory(encryptionKey: unknown) {
45
+ if (encryptionKey) {
46
+ const { default: factory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite.mjs');
47
+ return factory();
48
+ } else {
49
+ const { default: factory } = await import('@journeyapps/wa-sqlite/dist/wa-sqlite.mjs');
50
+ return factory();
51
+ }
52
+ }
53
+
54
+ /**
55
+ * @internal
56
+ */
57
+ export const DEFAULT_MODULE_FACTORIES = {
58
+ [WASQLiteVFS.IDBBatchAtomicVFS]: async (options: WASQLiteModuleFactoryOptions) => {
59
+ const module = await asyncModuleFactory(options.encryptionKey);
60
+ const { IDBBatchAtomicVFS } = await import('@journeyapps/wa-sqlite/src/examples/IDBBatchAtomicVFS.js');
61
+ return {
62
+ module,
63
+ // @ts-expect-error The types for this static method are missing upstream
64
+ vfs: await IDBBatchAtomicVFS.create(options.dbFileName, module, { lockPolicy: 'exclusive' })
65
+ };
66
+ },
67
+ [WASQLiteVFS.AccessHandlePoolVFS]: async (options: WASQLiteModuleFactoryOptions) => {
68
+ const module = await syncModuleFactory(options.encryptionKey);
69
+ // @ts-expect-error The types for this static method are missing upstream
70
+ const { AccessHandlePoolVFS } = await import('@journeyapps/wa-sqlite/src/examples/AccessHandlePoolVFS.js');
71
+ return {
72
+ module,
73
+ vfs: await AccessHandlePoolVFS.create(options.dbFileName, module)
74
+ };
75
+ },
76
+ [WASQLiteVFS.OPFSCoopSyncVFS]: async (options: WASQLiteModuleFactoryOptions) => {
77
+ const module = await syncModuleFactory(options.encryptionKey);
78
+ // @ts-expect-error The types for this static method are missing upstream
79
+ const { OPFSCoopSyncVFS } = await import('@journeyapps/wa-sqlite/src/examples/OPFSCoopSyncVFS.js');
80
+ const vfs = await OPFSCoopSyncVFS.create(options.dbFileName, module);
81
+ return {
82
+ module,
83
+ vfs
84
+ };
85
+ },
86
+ [WASQLiteVFS.OPFSWriteAheadVFS]: async (options: WASQLiteModuleFactoryOptions) => {
87
+ const module = await syncModuleFactory(options.encryptionKey);
88
+ // @ts-expect-error The types for this static method are missing upstream
89
+ const { OPFSWriteAheadVFS } = await import('@journeyapps/wa-sqlite/src/examples/OPFSWriteAheadVFS.js');
90
+ const vfs = await OPFSWriteAheadVFS.create(options.dbFileName, module, {});
91
+ return {
92
+ module,
93
+ vfs
94
+ };
95
+ }
96
+ };
@@ -76,6 +76,12 @@ export interface WebSQLOpenFactoryOptions extends SQLOpenOptions {
76
76
  */
77
77
  worker?: string | URL | ((options: ResolvedWebSQLOpenOptions) => Worker | SharedWorker);
78
78
 
79
+ /**
80
+ * Use an existing port to an initialized worker.
81
+ * A worker will be initialized if none is provided
82
+ */
83
+ workerPort?: MessagePort;
84
+
79
85
  logger?: ILogger;
80
86
 
81
87
  /**
@@ -3,13 +3,12 @@ import {
3
3
  BaseObserver,
4
4
  LockOptions,
5
5
  LockType,
6
+ Mutex,
6
7
  PowerSyncConnectionOptions,
7
8
  StreamingSyncImplementation,
8
- SubscribedStream,
9
9
  SyncStatus,
10
10
  SyncStatusOptions
11
11
  } from '@powersync/common';
12
- import { Mutex } from 'async-mutex';
13
12
 
14
13
  export class SSRStreamingSyncImplementation extends BaseObserver implements StreamingSyncImplementation {
15
14
  syncMutex: Mutex;
@@ -29,7 +28,7 @@ export class SSRStreamingSyncImplementation extends BaseObserver implements Stre
29
28
 
30
29
  obtainLock<T>(lockOptions: LockOptions<T>): Promise<T> {
31
30
  const mutex = lockOptions.type == LockType.CRUD ? this.crudMutex : this.syncMutex;
32
- return mutex.runExclusive(lockOptions.callback);
31
+ return mutex.runExclusive(lockOptions.callback, lockOptions.signal);
33
32
  }
34
33
 
35
34
  /**
@@ -86,4 +85,9 @@ export class SSRStreamingSyncImplementation extends BaseObserver implements Stre
86
85
  * No-op in SSR mode.
87
86
  */
88
87
  updateSubscriptions(): void {}
88
+
89
+ /**
90
+ * No-op in SSR mode.
91
+ */
92
+ markConnectionMayHaveChanged(): void {}
89
93
  }
@@ -15,6 +15,7 @@ import {
15
15
  WebStreamingSyncImplementation,
16
16
  WebStreamingSyncImplementationOptions
17
17
  } from './WebStreamingSyncImplementation.js';
18
+ import { generateTabCloseSignal } from '../../shared/tab_close_signal.js';
18
19
 
19
20
  /**
20
21
  * The shared worker will trigger methods on this side of the message port
@@ -192,26 +193,9 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
192
193
  * - We resolve the top-level promise after the lock has been registered with the shared worker.
193
194
  * - The client sends the params to the shared worker after locks have been registered.
194
195
  */
195
- await new Promise<void>((resolve) => {
196
- // Request a random lock until this client is disposed. The name of the lock is sent to the shared worker, which
197
- // will also attempt to acquire it. Since the lock is returned when the tab is closed, this allows the share worker
198
- // to free resources associated with this tab.
199
- // We take hold of this lock as soon-as-possible in order to cater for potentially closed tabs.
200
- getNavigatorLocks().request(`tab-close-signal-${crypto.randomUUID()}`, async (lock) => {
201
- if (this.abortOnClose.signal.aborted) {
202
- return;
203
- }
204
- // Awaiting here ensures the worker is waiting for the lock
205
- await this.syncManager.addLockBasedCloseSignal(lock!.name);
206
-
207
- // The lock has been registered, we can continue with the initialization
208
- resolve();
209
-
210
- await new Promise<void>((r) => {
211
- this.abortOnClose.signal.onabort = () => r();
212
- });
213
- });
214
- });
196
+ const closeSignal = await generateTabCloseSignal(this.abortOnClose.signal);
197
+ // Awaiting here ensures the worker is waiting for the lock
198
+ await this.syncManager.addLockBasedCloseSignal(closeSignal);
215
199
 
216
200
  const { crudUploadThrottleMs, identifier, retryDelayMs } = this.options;
217
201
  const flags = { ...this.webOptions.flags, workers: undefined };
package/src/index.ts CHANGED
@@ -1,10 +1,7 @@
1
1
  export * from '@powersync/common';
2
2
  export * from './attachments/IndexDBFileSystemAdapter.js';
3
3
  export * from './db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.js';
4
- export * from './db/adapters/AbstractWebSQLOpenFactory.js';
5
- export * from './db/adapters/AsyncDatabaseConnection.js';
6
- export * from './db/adapters/wa-sqlite/WASQLiteConnection.js';
7
- export * from './db/adapters/wa-sqlite/WASQLiteDBAdapter.js';
4
+ export { WASQLiteVFS } from './db/adapters/wa-sqlite/vfs.js';
8
5
  export * from './db/adapters/wa-sqlite/WASQLiteOpenFactory.js';
9
6
  export * from './db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.js';
10
7
  export * from './db/adapters/web-sql-flags.js';