@powersync/web 0.0.0-dev-20260120150240 → 0.0.0-dev-20260128165909

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 (36) hide show
  1. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js +5 -1
  2. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js.map +1 -1
  3. package/dist/index.umd.js +288 -65
  4. package/dist/index.umd.js.map +1 -1
  5. package/dist/worker/SharedSyncImplementation.umd.js +1438 -385
  6. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  7. package/dist/worker/WASQLiteDB.umd.js +1435 -386
  8. package/dist/worker/WASQLiteDB.umd.js.map +1 -1
  9. package/lib/package.json +2 -4
  10. package/lib/src/attachments/IndexDBFileSystemAdapter.d.ts +25 -0
  11. package/lib/src/attachments/IndexDBFileSystemAdapter.js +104 -0
  12. package/lib/src/db/NavigatorTriggerClaimManager.d.ts +6 -0
  13. package/lib/src/db/NavigatorTriggerClaimManager.js +20 -0
  14. package/lib/src/db/PowerSyncDatabase.d.ts +2 -1
  15. package/lib/src/db/PowerSyncDatabase.js +27 -1
  16. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +2 -2
  17. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +5 -1
  18. package/lib/src/db/adapters/WebDBAdapter.d.ts +4 -1
  19. package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.d.ts +12 -0
  20. package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.js +19 -0
  21. package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.d.ts +2 -2
  22. package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.js +2 -2
  23. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +2 -2
  24. package/lib/src/index.d.ts +1 -0
  25. package/lib/src/index.js +1 -0
  26. package/lib/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +3 -5
  28. package/src/attachments/IndexDBFileSystemAdapter.ts +117 -0
  29. package/src/db/NavigatorTriggerClaimManager.ts +23 -0
  30. package/src/db/PowerSyncDatabase.ts +30 -1
  31. package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +7 -3
  32. package/src/db/adapters/WebDBAdapter.ts +5 -1
  33. package/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.ts +23 -0
  34. package/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts +2 -2
  35. package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +2 -2
  36. package/src/index.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powersync/web",
3
- "version": "0.0.0-dev-20260120150240",
3
+ "version": "0.0.0-dev-20260128165909",
4
4
  "description": "PowerSync Web SDK",
5
5
  "main": "lib/src/index.js",
6
6
  "module": "lib/src/index.js",
@@ -56,14 +56,14 @@
56
56
  "license": "Apache-2.0",
57
57
  "peerDependencies": {
58
58
  "@journeyapps/wa-sqlite": "^1.4.1",
59
- "@powersync/common": "0.0.0-dev-20260120150240"
59
+ "@powersync/common": "0.0.0-dev-20260128165909"
60
60
  },
61
61
  "dependencies": {
62
62
  "async-mutex": "^0.5.0",
63
63
  "bson": "^6.10.4",
64
64
  "comlink": "^4.4.2",
65
65
  "commander": "^12.1.0",
66
- "@powersync/common": "0.0.0-dev-20260120150240"
66
+ "@powersync/common": "0.0.0-dev-20260128165909"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@journeyapps/wa-sqlite": "^1.4.1",
@@ -76,8 +76,6 @@
76
76
  "terser-webpack-plugin": "^5.3.9",
77
77
  "uuid": "^11.1.0",
78
78
  "vite": "^7.3.1",
79
- "vite-plugin-top-level-await": "^1.4.4",
80
- "vite-plugin-wasm": "^3.3.0",
81
79
  "vm-browserify": "^1.1.2",
82
80
  "webpack": "^5.90.1",
83
81
  "webpack-cli": "^5.1.4",
@@ -0,0 +1,117 @@
1
+ import { AttachmentData, EncodingType, LocalStorageAdapter } from '@powersync/common';
2
+
3
+ /**
4
+ * IndexDBFileSystemStorageAdapter implements LocalStorageAdapter using IndexedDB.
5
+ * Suitable for web browsers and web-based environments.
6
+ */
7
+ export class IndexDBFileSystemStorageAdapter implements LocalStorageAdapter {
8
+ private dbPromise!: Promise<IDBDatabase>;
9
+
10
+ constructor(private databaseName: string = 'PowerSyncFiles') {}
11
+
12
+ async initialize(): Promise<void> {
13
+ this.dbPromise = new Promise<IDBDatabase>((resolve, reject) => {
14
+ const request = indexedDB.open(this.databaseName, 1);
15
+ request.onupgradeneeded = () => {
16
+ request.result.createObjectStore('files');
17
+ };
18
+ request.onsuccess = () => resolve(request.result);
19
+ request.onerror = () => reject(request.error);
20
+ });
21
+ }
22
+
23
+ async clear(): Promise<void> {
24
+ const db = await this.dbPromise;
25
+ return new Promise((resolve, reject) => {
26
+ const tx = db.transaction('files', 'readwrite');
27
+ const store = tx.objectStore('files');
28
+ const req = store.clear();
29
+ req.onsuccess = () => resolve();
30
+ req.onerror = () => reject(req.error);
31
+ });
32
+ }
33
+
34
+ getLocalUri(filename: string): string {
35
+ return `indexeddb://${this.databaseName}/files/${filename}`;
36
+ }
37
+
38
+ private async getStore(mode: IDBTransactionMode = 'readonly'): Promise<IDBObjectStore> {
39
+ const db = await this.dbPromise;
40
+ const tx = db.transaction('files', mode);
41
+ return tx.objectStore('files');
42
+ }
43
+
44
+ async saveFile(filePath: string, data: AttachmentData): Promise<number> {
45
+ const store = await this.getStore('readwrite');
46
+
47
+ let dataToStore: ArrayBuffer;
48
+ let size: number;
49
+
50
+ if (typeof data === 'string') {
51
+ const binaryString = atob(data);
52
+ const bytes = new Uint8Array(binaryString.length);
53
+ for (let i = 0; i < binaryString.length; i++) {
54
+ bytes[i] = binaryString.charCodeAt(i);
55
+ }
56
+ dataToStore = bytes.buffer;
57
+ size = bytes.byteLength;
58
+ } else {
59
+ dataToStore = data;
60
+ size = dataToStore.byteLength;
61
+ }
62
+
63
+ return await new Promise<number>((resolve, reject) => {
64
+ const req = store.put(dataToStore, filePath);
65
+ req.onsuccess = () => resolve(size);
66
+ req.onerror = () => reject(req.error);
67
+ });
68
+ }
69
+
70
+ async readFile(fileUri: string, options?: { encoding?: EncodingType; mediaType?: string }): Promise<ArrayBuffer> {
71
+ const store = await this.getStore();
72
+ return new Promise<ArrayBuffer>((resolve, reject) => {
73
+ const req = store.get(fileUri);
74
+ req.onsuccess = async () => {
75
+ if (!req.result) {
76
+ reject(new Error('File not found'));
77
+ return;
78
+ }
79
+
80
+ resolve(req.result as ArrayBuffer);
81
+ };
82
+ req.onerror = () => reject(req.error);
83
+ });
84
+ }
85
+
86
+ async deleteFile(uri: string, options?: { filename?: string }): Promise<void> {
87
+ const store = await this.getStore('readwrite');
88
+ await new Promise<void>((resolve, reject) => {
89
+ const req = store.delete(uri);
90
+ req.onsuccess = () => resolve();
91
+ req.onerror = () => reject(req.error);
92
+ });
93
+ }
94
+
95
+ async fileExists(fileUri: string): Promise<boolean> {
96
+ const store = await this.getStore();
97
+ return new Promise<boolean>((resolve, reject) => {
98
+ const req = store.get(fileUri);
99
+ req.onsuccess = () => resolve(!!req.result);
100
+ req.onerror = () => reject(req.error);
101
+ });
102
+ }
103
+
104
+ async makeDir(path: string): Promise<void> {
105
+ // No-op for IndexedDB as it does not have a directory structure
106
+ }
107
+
108
+ async rmDir(path: string): Promise<void> {
109
+ const store = await this.getStore('readwrite');
110
+ const range = IDBKeyRange.bound(path + '/', path + '/\uffff', false, false);
111
+ await new Promise<void>((resolve, reject) => {
112
+ const req = store.delete(range);
113
+ req.onsuccess = () => resolve();
114
+ req.onerror = () => reject(req.error);
115
+ });
116
+ }
117
+ }
@@ -0,0 +1,23 @@
1
+ import { TriggerClaimManager } from '@powersync/common';
2
+ import { getNavigatorLocks } from '../shared/navigator.js';
3
+
4
+ /**
5
+ * @internal
6
+ * @experimental
7
+ */
8
+ export const NAVIGATOR_TRIGGER_CLAIM_MANAGER: TriggerClaimManager = {
9
+ async obtainClaim(identifier: string): Promise<() => Promise<void>> {
10
+ return new Promise((resolveReleaser) => {
11
+ getNavigatorLocks().request(identifier, async () => {
12
+ await new Promise<void>((releaseLock) => {
13
+ resolveReleaser(async () => releaseLock());
14
+ });
15
+ });
16
+ });
17
+ },
18
+
19
+ async checkClaim(identifier: string): Promise<boolean> {
20
+ const currentState = await getNavigatorLocks().query();
21
+ return currentState.held?.find((heldLock) => heldLock.name == identifier) != null;
22
+ }
23
+ };
@@ -7,6 +7,7 @@ import {
7
7
  PowerSyncDatabaseOptionsWithSettings,
8
8
  SqliteBucketStorage,
9
9
  StreamingSyncImplementation,
10
+ TriggerManagerConfig,
10
11
  isDBAdapter,
11
12
  isSQLOpenFactory,
12
13
  type BucketStorageAdapter,
@@ -16,6 +17,8 @@ import {
16
17
  } from '@powersync/common';
17
18
  import { Mutex } from 'async-mutex';
18
19
  import { getNavigatorLocks } from '../shared/navigator.js';
20
+ import { NAVIGATOR_TRIGGER_CLAIM_MANAGER } from './NavigatorTriggerClaimManager.js';
21
+ import { LockedAsyncDatabaseAdapter } from './adapters/LockedAsyncDatabaseAdapter.js';
19
22
  import { WebDBAdapter } from './adapters/WebDBAdapter.js';
20
23
  import { WASQLiteOpenFactory } from './adapters/wa-sqlite/WASQLiteOpenFactory.js';
21
24
  import {
@@ -144,7 +147,33 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
144
147
  }
145
148
  }
146
149
 
147
- async _initialize(): Promise<void> {}
150
+ async _initialize(): Promise<void> {
151
+ if (this.database instanceof LockedAsyncDatabaseAdapter) {
152
+ /**
153
+ * While init is done automatically,
154
+ * LockedAsyncDatabaseAdapter only exposes config after init.
155
+ * We can explicitly wait for init here in order to access config.
156
+ */
157
+ await this.database.init();
158
+ }
159
+
160
+ // In some cases, like the SQLJs adapter, we don't pass a WebDBAdapter, so we need to check.
161
+ if (typeof (this.database as WebDBAdapter).getConfiguration == 'function') {
162
+ const config = (this.database as WebDBAdapter).getConfiguration();
163
+ if (config.requiresPersistentTriggers) {
164
+ this.triggersImpl.updateDefaults({
165
+ useStorageByDefault: true
166
+ });
167
+ }
168
+ }
169
+ }
170
+
171
+ protected generateTriggerManagerConfig(): TriggerManagerConfig {
172
+ return {
173
+ // We need to share hold information between tabs for web
174
+ claimManager: NAVIGATOR_TRIGGER_CLAIM_MANAGER
175
+ };
176
+ }
148
177
 
149
178
  protected openDBAdapter(options: WebPowerSyncDatabaseOptionsWithSettings): DBAdapter {
150
179
  const defaultFactory = new WASQLiteOpenFactory({
@@ -13,7 +13,7 @@ import {
13
13
  } from '@powersync/common';
14
14
  import { getNavigatorLocks } from '../../shared/navigator.js';
15
15
  import { AsyncDatabaseConnection } from './AsyncDatabaseConnection.js';
16
- import { SharedConnectionWorker, WebDBAdapter } from './WebDBAdapter.js';
16
+ import { SharedConnectionWorker, WebDBAdapter, WebDBAdapterConfiguration } from './WebDBAdapter.js';
17
17
  import { WorkerWrappedAsyncDatabaseConnection } from './WorkerWrappedAsyncDatabaseConnection.js';
18
18
  import { WASQLiteVFS } from './wa-sqlite/WASQLiteConnection.js';
19
19
  import { ResolvedWASQLiteOpenFactoryOptions } from './wa-sqlite/WASQLiteOpenFactory.js';
@@ -181,11 +181,15 @@ export class LockedAsyncDatabaseAdapter
181
181
  this.iterateListeners((cb) => cb.initialized?.());
182
182
  }
183
183
 
184
- getConfiguration(): ResolvedWebSQLOpenOptions {
184
+ getConfiguration(): WebDBAdapterConfiguration {
185
185
  if (!this._config) {
186
186
  throw new Error(`Cannot get config before initialization is completed`);
187
187
  }
188
- return this._config;
188
+ return {
189
+ ...this._config,
190
+ // This can be overridden by the adapter later
191
+ requiresPersistentTriggers: false
192
+ };
189
193
  }
190
194
 
191
195
  protected async waitForInitialized() {
@@ -6,6 +6,10 @@ export type SharedConnectionWorker = {
6
6
  port: MessagePort;
7
7
  };
8
8
 
9
+ export type WebDBAdapterConfiguration = ResolvedWebSQLOpenOptions & {
10
+ requiresPersistentTriggers: boolean;
11
+ };
12
+
9
13
  export interface WebDBAdapter extends DBAdapter {
10
14
  /**
11
15
  * Get a MessagePort which can be used to share the internals of this connection.
@@ -16,5 +20,5 @@ export interface WebDBAdapter extends DBAdapter {
16
20
  * Get the config options used to open this connection.
17
21
  * This is useful for sharing connections.
18
22
  */
19
- getConfiguration(): ResolvedWebSQLOpenOptions;
23
+ getConfiguration(): WebDBAdapterConfiguration;
20
24
  }
@@ -0,0 +1,23 @@
1
+ import { LockedAsyncDatabaseAdapter } from '../LockedAsyncDatabaseAdapter.js';
2
+ import { WebDBAdapterConfiguration } from '../WebDBAdapter.js';
3
+ import { WASQLiteVFS } from './WASQLiteConnection.js';
4
+ import { ResolvedWASQLiteOpenFactoryOptions } from './WASQLiteOpenFactory.js';
5
+
6
+ /**
7
+ * @internal
8
+ * An intermediary implementation of WASQLiteDBAdapter, which takes the same
9
+ * constructor arguments as {@link LockedAsyncDatabaseAdapter}, but provides some
10
+ * basic WA-SQLite specific functionality.
11
+ * This base class is used to avoid requiring overloading the constructor of {@link WASQLiteDBAdapter}
12
+ */
13
+ export class InternalWASQLiteDBAdapter extends LockedAsyncDatabaseAdapter {
14
+ getConfiguration(): WebDBAdapterConfiguration {
15
+ // This is valid since we only handle WASQLite connections
16
+ const baseConfig = super.getConfiguration() as unknown as ResolvedWASQLiteOpenFactoryOptions;
17
+ return {
18
+ ...super.getConfiguration(),
19
+ requiresPersistentTriggers:
20
+ baseConfig.vfs == WASQLiteVFS.OPFSCoopSyncVFS || baseConfig.vfs == WASQLiteVFS.AccessHandlePoolVFS
21
+ };
22
+ }
23
+ }
@@ -2,7 +2,6 @@ import { type PowerSyncOpenFactoryOptions } from '@powersync/common';
2
2
  import * as Comlink from 'comlink';
3
3
  import { resolveWebPowerSyncFlags } from '../../PowerSyncDatabase.js';
4
4
  import { OpenAsyncDatabaseConnection } from '../AsyncDatabaseConnection.js';
5
- import { LockedAsyncDatabaseAdapter } from '../LockedAsyncDatabaseAdapter.js';
6
5
  import {
7
6
  DEFAULT_CACHE_SIZE_KB,
8
7
  ResolvedWebSQLOpenOptions,
@@ -10,6 +9,7 @@ import {
10
9
  WebSQLFlags
11
10
  } from '../web-sql-flags.js';
12
11
  import { WorkerWrappedAsyncDatabaseConnection } from '../WorkerWrappedAsyncDatabaseConnection.js';
12
+ import { InternalWASQLiteDBAdapter } from './InternalWASQLiteDBAdapter.js';
13
13
  import { WASQLiteVFS } from './WASQLiteConnection.js';
14
14
  import { WASQLiteOpenFactory } from './WASQLiteOpenFactory.js';
15
15
 
@@ -44,7 +44,7 @@ export interface WASQLiteDBAdapterOptions extends Omit<PowerSyncOpenFactoryOptio
44
44
  /**
45
45
  * Adapter for WA-SQLite SQLite connections.
46
46
  */
47
- export class WASQLiteDBAdapter extends LockedAsyncDatabaseAdapter {
47
+ export class WASQLiteDBAdapter extends InternalWASQLiteDBAdapter {
48
48
  constructor(options: WASQLiteDBAdapterOptions) {
49
49
  super({
50
50
  name: options.dbFilename,
@@ -3,7 +3,6 @@ import * as Comlink from 'comlink';
3
3
  import { openWorkerDatabasePort, resolveWorkerDatabasePortFactory } from '../../../worker/db/open-worker-database.js';
4
4
  import { AbstractWebSQLOpenFactory } from '../AbstractWebSQLOpenFactory.js';
5
5
  import { AsyncDatabaseConnection, OpenAsyncDatabaseConnection } from '../AsyncDatabaseConnection.js';
6
- import { LockedAsyncDatabaseAdapter } from '../LockedAsyncDatabaseAdapter.js';
7
6
  import { WorkerWrappedAsyncDatabaseConnection } from '../WorkerWrappedAsyncDatabaseConnection.js';
8
7
  import {
9
8
  DEFAULT_CACHE_SIZE_KB,
@@ -11,6 +10,7 @@ import {
11
10
  TemporaryStorageOption,
12
11
  WebSQLOpenFactoryOptions
13
12
  } from '../web-sql-flags.js';
13
+ import { InternalWASQLiteDBAdapter } from './InternalWASQLiteDBAdapter.js';
14
14
  import { WASQLiteVFS, WASqliteConnection } from './WASQLiteConnection.js';
15
15
 
16
16
  export interface WASQLiteOpenFactoryOptions extends WebSQLOpenFactoryOptions {
@@ -41,7 +41,7 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
41
41
  }
42
42
 
43
43
  protected openAdapter(): DBAdapter {
44
- return new LockedAsyncDatabaseAdapter({
44
+ return new InternalWASQLiteDBAdapter({
45
45
  name: this.options.dbFilename,
46
46
  openConnection: () => this.openConnection(),
47
47
  debugMode: this.options.debugMode,
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from '@powersync/common';
2
+ export * from './attachments/IndexDBFileSystemAdapter.js';
2
3
  export * from './db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.js';
3
4
  export * from './db/adapters/AbstractWebSQLOpenFactory.js';
4
5
  export * from './db/adapters/AsyncDatabaseConnection.js';