@powersync/web 1.38.6 → 1.39.0

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.
@@ -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
+ }
@@ -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