@powersync/web 1.38.7 → 1.39.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/index.umd.js +344 -83
  2. package/dist/index.umd.js.map +1 -1
  3. package/dist/worker/SharedSyncImplementation.umd.js +18 -9
  4. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  5. package/dist/worker/WASQLiteDB.umd.js +94 -60
  6. package/dist/worker/WASQLiteDB.umd.js.map +1 -1
  7. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-833585.umd.js +1400 -0
  8. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-833585.umd.js.map +1 -0
  9. package/lib/package.json +2 -2
  10. package/lib/src/db/adapters/AsyncWebAdapter.js +1 -1
  11. package/lib/src/db/adapters/AsyncWebAdapter.js.map +1 -1
  12. package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.d.ts +8 -1
  13. package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.js +13 -9
  14. package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.js.map +1 -1
  15. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +8 -2
  16. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js.map +1 -1
  17. package/lib/src/db/adapters/wa-sqlite/vfs.d.ts +20 -30
  18. package/lib/src/db/adapters/wa-sqlite/vfs.js +57 -39
  19. package/lib/src/db/adapters/wa-sqlite/vfs.js.map +1 -1
  20. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +1 -0
  21. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +10 -5
  22. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js.map +1 -1
  23. package/lib/src/worker/db/MultiDatabaseServer.js +5 -3
  24. package/lib/src/worker/db/MultiDatabaseServer.js.map +1 -1
  25. package/lib/src/worker/db/open-worker-database.d.ts +3 -2
  26. package/lib/src/worker/db/open-worker-database.js +30 -22
  27. package/lib/src/worker/db/open-worker-database.js.map +1 -1
  28. package/lib/src/worker/errors.d.ts +2 -0
  29. package/lib/src/worker/errors.js +7 -0
  30. package/lib/src/worker/errors.js.map +1 -0
  31. package/lib/src/worker/sync/SharedSyncImplementation.d.ts +1 -0
  32. package/lib/src/worker/sync/SharedSyncImplementation.js +2 -1
  33. package/lib/src/worker/sync/SharedSyncImplementation.js.map +1 -1
  34. package/lib/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +3 -3
  36. package/src/db/adapters/AsyncWebAdapter.ts +1 -1
  37. package/src/db/adapters/wa-sqlite/RawSqliteConnection.ts +15 -11
  38. package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +24 -9
  39. package/src/db/adapters/wa-sqlite/vfs.ts +67 -54
  40. package/src/db/sync/SharedWebStreamingSyncImplementation.ts +17 -11
  41. package/src/worker/db/MultiDatabaseServer.ts +5 -3
  42. package/src/worker/db/open-worker-database.ts +34 -22
  43. package/src/worker/errors.ts +9 -0
  44. package/src/worker/sync/SharedSyncImplementation.ts +3 -1
@@ -1,4 +1,5 @@
1
1
  import type * as SQLite from '@journeyapps/wa-sqlite';
2
+ import { ResolvedWASQLiteOpenFactoryOptions } from './WASQLiteOpenFactory.js';
2
3
 
3
4
  /**
4
5
  * List of currently tested virtual filesystems
@@ -7,31 +8,36 @@ export enum WASQLiteVFS {
7
8
  IDBBatchAtomicVFS = 'IDBBatchAtomicVFS',
8
9
  OPFSCoopSyncVFS = 'OPFSCoopSyncVFS',
9
10
  AccessHandlePoolVFS = 'AccessHandlePoolVFS',
10
- OPFSWriteAheadVFS = 'OPFSWriteAheadVFS'
11
+ OPFSWriteAheadVFS = 'OPFSWriteAheadVFS',
12
+ /**
13
+ * A virtual file system storing data in-memory only, without persistence.
14
+ *
15
+ * This file system can be used in three configurations:
16
+ *
17
+ * 1. In shared workers (the default when available): All tabs share the same in-memory database, which is cleared
18
+ * once the last tab is closed.
19
+ * 2. In dedicated workers (used when `enableMultiTabs` is disabled). Each tab has its own in-memory database cleared
20
+ * when the tab is closed. Queries are offloaded to a dedicated worker.
21
+ * 3. In the context of the tab itself (used when both `enableMultiTabs` and `useWebWorker` are disabled). The per-tab
22
+ * database is hosted in the tab itself, and queries run synchronously. This is _a lot_ faster than any other
23
+ * single-threadedVFS, but can block JavaScript for computationally-intensive queries.
24
+ *
25
+ * This VFS primarily intended for development, but it also useful for online-first deployments not syncing large
26
+ * amounts of data, as it is quicker to start up.
27
+ */
28
+ InMemoryVfs = 'InMemoryVFS'
11
29
  }
12
30
 
13
31
  export function vfsRequiresDedicatedWorkers(vfs: WASQLiteVFS) {
14
- return vfs != WASQLiteVFS.IDBBatchAtomicVFS;
32
+ return vfs != WASQLiteVFS.IDBBatchAtomicVFS && vfs != WASQLiteVFS.InMemoryVfs;
15
33
  }
16
34
 
17
- /**
18
- * @internal
19
- */
20
- export type WASQLiteModuleFactoryOptions = { dbFileName: string; encryptionKey?: string };
21
-
22
35
  /**
23
36
  * @internal
24
37
  */
25
38
  export type SQLiteModule = Parameters<typeof SQLite.Factory>[0];
26
39
 
27
- /**
28
- * @internal
29
- */
30
- export type WASQLiteModuleFactory = (
31
- options: WASQLiteModuleFactoryOptions
32
- ) => Promise<{ module: SQLiteModule; vfs: SQLiteVFS }>;
33
-
34
- async function asyncModuleFactory(encryptionKey: unknown) {
40
+ async function asyncModuleFactory(encryptionKey: string | undefined): Promise<SQLiteModule> {
35
41
  if (encryptionKey) {
36
42
  const { default: factory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite-async.mjs');
37
43
  return factory();
@@ -41,7 +47,7 @@ async function asyncModuleFactory(encryptionKey: unknown) {
41
47
  }
42
48
  }
43
49
 
44
- async function syncModuleFactory(encryptionKey: unknown) {
50
+ async function syncModuleFactory(encryptionKey: string | undefined): Promise<SQLiteModule> {
45
51
  if (encryptionKey) {
46
52
  const { default: factory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite.mjs');
47
53
  return factory();
@@ -54,43 +60,50 @@ async function syncModuleFactory(encryptionKey: unknown) {
54
60
  /**
55
61
  * @internal
56
62
  */
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
+ export async function loadModuleAndVfs({
64
+ vfs,
65
+ dbFilename,
66
+ encryptionKey
67
+ }: ResolvedWASQLiteOpenFactoryOptions): Promise<{ module: SQLiteModule; vfs: SQLiteVFS }> {
68
+ let moduleFactory = syncModuleFactory;
69
+ let resolveVfs: (module: any) => Promise<SQLiteVFS>;
70
+
71
+ switch (vfs) {
72
+ case WASQLiteVFS.IDBBatchAtomicVFS: {
73
+ moduleFactory = asyncModuleFactory;
74
+ const { IDBBatchAtomicVFS } = await import('@journeyapps/wa-sqlite/src/examples/IDBBatchAtomicVFS.js');
75
+ resolveVfs = (module) => {
76
+ // @ts-expect-error The types for this static method are missing upstream
77
+ return IDBBatchAtomicVFS.create(dbFilename, module, { lockPolicy: 'exclusive' });
78
+ };
79
+ break;
80
+ }
81
+ case WASQLiteVFS.AccessHandlePoolVFS: {
82
+ // @ts-expect-error The types for this import are missing upstream
83
+ const { AccessHandlePoolVFS } = await import('@journeyapps/wa-sqlite/src/examples/AccessHandlePoolVFS.js');
84
+ resolveVfs = (module) => AccessHandlePoolVFS.create(dbFilename, module);
85
+ break;
86
+ }
87
+ case WASQLiteVFS.OPFSCoopSyncVFS: {
88
+ // @ts-expect-error The types for this import are missing upstream
89
+ const { OPFSCoopSyncVFS } = await import('@journeyapps/wa-sqlite/src/examples/OPFSCoopSyncVFS.js');
90
+ resolveVfs = (module) => OPFSCoopSyncVFS.create(dbFilename, module);
91
+ break;
92
+ }
93
+ case WASQLiteVFS.OPFSWriteAheadVFS: {
94
+ // @ts-expect-error The types for this import are missing upstream
95
+ const { OPFSWriteAheadVFS } = await import('@journeyapps/wa-sqlite/src/examples/OPFSWriteAheadVFS.js');
96
+ resolveVfs = (module) => OPFSWriteAheadVFS.create(dbFilename, module, {});
97
+ break;
98
+ }
99
+ case WASQLiteVFS.InMemoryVfs: {
100
+ const { MemoryVFS } = await import('@journeyapps/wa-sqlite/src/examples/MemoryVFS.js');
63
101
  // @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
- };
102
+ resolveVfs = (module) => MemoryVFS.create(dbFilename, module);
103
+ break;
104
+ }
95
105
  }
96
- };
106
+
107
+ const module = await moduleFactory(encryptionKey);
108
+ return { module, vfs: await resolveVfs(module) };
109
+ }
@@ -5,7 +5,6 @@ import {
5
5
  SyncStatusOptions
6
6
  } from '@powersync/common';
7
7
  import * as Comlink from 'comlink';
8
- import { getNavigatorLocks } from '../../shared/navigator.js';
9
8
  import { AbstractSharedSyncClientProvider } from '../../worker/sync/AbstractSharedSyncClientProvider.js';
10
9
  import { ManualSharedSyncPayload, SharedSyncClientEvent } from '../../worker/sync/SharedSyncImplementation.js';
11
10
  import { WorkerClient } from '../../worker/sync/WorkerClient.js';
@@ -16,6 +15,7 @@ import {
16
15
  WebStreamingSyncImplementationOptions
17
16
  } from './WebStreamingSyncImplementation.js';
18
17
  import { generateTabCloseSignal } from '../../shared/tab_close_signal.js';
18
+ import { logWorkerErrors } from '../../worker/errors.js';
19
19
 
20
20
  /**
21
21
  * The shared worker will trigger methods on this side of the message port
@@ -128,22 +128,23 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
128
128
  const syncWorker = options.sync?.worker;
129
129
  if (syncWorker) {
130
130
  if (typeof syncWorker === 'function') {
131
- this.messagePort = syncWorker(resolvedWorkerOptions).port;
131
+ this.messagePort = this.workerPort(syncWorker(resolvedWorkerOptions));
132
132
  } else {
133
- this.messagePort = new SharedWorker(`${syncWorker}`, {
134
- /* @vite-ignore */
135
- name: `shared-sync-${this.webOptions.identifier}`
136
- }).port;
133
+ this.messagePort = this.workerPort(
134
+ new SharedWorker(`${syncWorker}`, {
135
+ /* @vite-ignore */
136
+ name: `shared-sync-${this.webOptions.identifier}`
137
+ })
138
+ );
137
139
  }
138
140
  } else {
139
- this.messagePort = new SharedWorker(
140
- new URL('../../worker/sync/SharedSyncImplementation.worker.js', import.meta.url),
141
- {
141
+ this.messagePort = this.workerPort(
142
+ new SharedWorker(new URL('../../worker/sync/SharedSyncImplementation.worker.js', import.meta.url), {
142
143
  /* @vite-ignore */
143
144
  name: `shared-sync-${this.webOptions.identifier}`,
144
145
  type: 'module'
145
- }
146
- ).port;
146
+ })
147
+ );
147
148
  }
148
149
 
149
150
  /**
@@ -179,6 +180,11 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
179
180
  this.isInitialized = this._init();
180
181
  }
181
182
 
183
+ private workerPort(worker: SharedWorker): MessagePort {
184
+ logWorkerErrors(worker, this.logger);
185
+ return worker.port;
186
+ }
187
+
182
188
  protected async _init() {
183
189
  /**
184
190
  * The general flow of initialization is:
@@ -8,6 +8,7 @@ import {
8
8
  import { getNavigatorLocks } from '../../shared/navigator.js';
9
9
  import { RawSqliteConnection } from '../../db/adapters/wa-sqlite/RawSqliteConnection.js';
10
10
  import { ConcurrentSqliteConnection } from '../../db/adapters/wa-sqlite/ConcurrentConnection.js';
11
+ import { WASQLiteVFS } from '../../db/adapters/wa-sqlite/vfs.js';
11
12
 
12
13
  const OPEN_DB_LOCK = 'open-wasqlite-db';
13
14
 
@@ -57,14 +58,15 @@ export class MultiDatabaseServer {
57
58
 
58
59
  private async databaseOpenAttempt(options: ResolvedWASQLiteOpenFactoryOptions): Promise<DatabaseServer> {
59
60
  return getNavigatorLocks().request(OPEN_DB_LOCK, async () => {
60
- const { dbFilename } = options;
61
+ const { dbFilename, isReadOnly, vfs } = options;
61
62
 
62
63
  let server: DatabaseServer | undefined = this.activeDatabases.get(dbFilename);
63
64
  if (server == null) {
64
65
  // We don't need navigator locks for shared workers because all queries run in this shared worker exclusively.
65
66
  // For read-only connections, we use a VFS that supports concurrent reads (so a single lock on the connection is
66
- // fine).
67
- const needsNavigatorLocks = !(isSharedWorker || options.isReadOnly);
67
+ // fine). In-memory databases either run in a shared worker or aren't shared across tabs at all, so the internal
68
+ // lock is enough.
69
+ const needsNavigatorLocks = !(isSharedWorker || isReadOnly || vfs == WASQLiteVFS.InMemoryVfs);
68
70
  const connection = new RawSqliteConnection(options);
69
71
  const withSafeConcurrency = new ConcurrentSqliteConnection(connection, needsNavigatorLocks);
70
72
 
@@ -1,6 +1,8 @@
1
1
  import * as Comlink from 'comlink';
2
2
  import { vfsRequiresDedicatedWorkers, WASQLiteVFS } from '../../db/adapters/wa-sqlite/vfs.js';
3
3
  import { OpenWorkerConnection } from '../../db/adapters/wa-sqlite/DatabaseClient.js';
4
+ import type { ILogger } from '@powersync/common';
5
+ import { logWorkerErrors } from '../errors.js';
4
6
 
5
7
  /**
6
8
  * Opens a shared or dedicated worker which exposes opening of database connections
@@ -9,20 +11,23 @@ export function openWorkerDatabasePort(
9
11
  workerIdentifier: string,
10
12
  multipleTabs = true,
11
13
  worker: string | URL = '',
12
- vfs?: WASQLiteVFS
14
+ vfs?: WASQLiteVFS,
15
+ logger?: ILogger
13
16
  ) {
14
17
  const needsDedicated = vfs && vfsRequiresDedicatedWorkers(vfs);
18
+ let resolvedWorker: Worker | SharedWorker;
15
19
 
16
20
  if (worker) {
17
- return !needsDedicated && multipleTabs
18
- ? new SharedWorker(`${worker}`, {
19
- /* @vite-ignore */
20
- name: `shared-DB-worker-${workerIdentifier}`
21
- }).port
22
- : new Worker(`${worker}`, {
23
- /* @vite-ignore */
24
- name: `DB-worker-${workerIdentifier}`
25
- });
21
+ resolvedWorker =
22
+ !needsDedicated && multipleTabs
23
+ ? new SharedWorker(`${worker}`, {
24
+ /* @vite-ignore */
25
+ name: `shared-DB-worker-${workerIdentifier}`
26
+ })
27
+ : new Worker(`${worker}`, {
28
+ /* @vite-ignore */
29
+ name: `DB-worker-${workerIdentifier}`
30
+ });
26
31
  } else {
27
32
  /**
28
33
  * Webpack V5 can bundle the worker automatically if the full Worker constructor syntax is used
@@ -30,18 +35,21 @@ export function openWorkerDatabasePort(
30
35
  * This enables multi tab support by default, but falls back if SharedWorker is not available
31
36
  * (in the case of Android)
32
37
  */
33
- return !needsDedicated && multipleTabs
34
- ? new SharedWorker(new URL('./WASQLiteDB.worker.js', import.meta.url), {
35
- /* @vite-ignore */
36
- name: `shared-DB-worker-${workerIdentifier}`,
37
- type: 'module'
38
- }).port
39
- : new Worker(new URL('./WASQLiteDB.worker.js', import.meta.url), {
40
- /* @vite-ignore */
41
- name: `DB-worker-${workerIdentifier}`,
42
- type: 'module'
43
- });
38
+ resolvedWorker =
39
+ !needsDedicated && multipleTabs
40
+ ? new SharedWorker(new URL('./WASQLiteDB.worker.js', import.meta.url), {
41
+ /* @vite-ignore */
42
+ name: `shared-DB-worker-${workerIdentifier}`,
43
+ type: 'module'
44
+ })
45
+ : new Worker(new URL('./WASQLiteDB.worker.js', import.meta.url), {
46
+ /* @vite-ignore */
47
+ name: `DB-worker-${workerIdentifier}`,
48
+ type: 'module'
49
+ });
44
50
  }
51
+
52
+ return resolveWorkerDatabasePortFactory(() => resolvedWorker, logger);
45
53
  }
46
54
 
47
55
  /**
@@ -52,8 +60,12 @@ export function getWorkerDatabaseOpener(workerIdentifier: string, multipleTabs =
52
60
  return Comlink.wrap<OpenWorkerConnection>(openWorkerDatabasePort(workerIdentifier, multipleTabs, worker));
53
61
  }
54
62
 
55
- export function resolveWorkerDatabasePortFactory(worker: () => Worker | SharedWorker) {
63
+ export function resolveWorkerDatabasePortFactory(worker: () => Worker | SharedWorker, logger?: ILogger) {
56
64
  const workerInstance = worker();
65
+ if (logger) {
66
+ logWorkerErrors(workerInstance, logger);
67
+ }
68
+
57
69
  return isSharedWorker(workerInstance) ? workerInstance.port : workerInstance;
58
70
  }
59
71
 
@@ -0,0 +1,9 @@
1
+ import type { ILogger } from '@powersync/common';
2
+
3
+ export function logWorkerErrors(worker: AbstractWorker, logger: ILogger) {
4
+ function logError(event: ErrorEvent) {
5
+ logger.error('Error in database or sync worker, this likely disrupts PowerSync.', event.error);
6
+ }
7
+
8
+ worker.addEventListener('error', logError);
9
+ }
@@ -122,6 +122,8 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
122
122
  broadCastLogger: ILogger;
123
123
  protected readonly database = this.generateReconnectableDatabase();
124
124
 
125
+ private sharedCloseSignal = generateTabCloseSignal();
126
+
125
127
  constructor() {
126
128
  super();
127
129
  this.ports = [];
@@ -508,7 +510,7 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
508
510
  const remote = Comlink.wrap<OpenWorkerConnection>(workerPort);
509
511
  const identifier = this.syncParams!.dbParams.dbFilename;
510
512
 
511
- const clientLockName = await generateTabCloseSignal();
513
+ const clientLockName = await this.sharedCloseSignal;
512
514
 
513
515
  /**
514
516
  * The open could fail if the tab is closed while we're busy opening the database.