@powersync/web 0.0.0-dev-20260202162549 → 0.0.0-dev-20260216124709

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 (100) hide show
  1. package/dist/0b19af1befc07ce338dd.wasm +0 -0
  2. package/dist/2632c3bda9473da74fd5.wasm +0 -0
  3. package/dist/64f5351ba3784bfe2f3e.wasm +0 -0
  4. package/dist/9318ca94aac4d0fe0135.wasm +0 -0
  5. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js +1878 -0
  6. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js.map +1 -0
  7. package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-2530150.index.umd.js +555 -0
  8. package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-2530150.index.umd.js.map +1 -0
  9. package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-2530151.index.umd.js +555 -0
  10. package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-2530151.index.umd.js.map +1 -0
  11. package/dist/index.umd.js +8267 -0
  12. package/dist/index.umd.js.map +1 -0
  13. package/dist/worker/SharedSyncImplementation.umd.js +19059 -0
  14. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -0
  15. package/dist/worker/WASQLiteDB.umd.js +17736 -0
  16. package/dist/worker/WASQLiteDB.umd.js.map +1 -0
  17. package/dist/worker/node_modules_pnpm_bson_6_10_4_node_modules_bson_lib_bson_mjs.umd.js +4646 -0
  18. package/dist/worker/node_modules_pnpm_bson_6_10_4_node_modules_bson_lib_bson_mjs.umd.js.map +1 -0
  19. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-3a94cf.umd.js +44 -0
  20. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-3a94cf.umd.js.map +1 -0
  21. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-868779.umd.js +44 -0
  22. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-868779.umd.js.map +1 -0
  23. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-f60d0d.umd.js +44 -0
  24. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-f60d0d.umd.js.map +1 -0
  25. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +44 -0
  26. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map +1 -0
  27. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-0d2437.umd.js +2478 -0
  28. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-0d2437.umd.js.map +1 -0
  29. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-1d4e74.umd.js +1820 -0
  30. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-1d4e74.umd.js.map +1 -0
  31. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-3622cf.umd.js +1681 -0
  32. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-3622cf.umd.js.map +1 -0
  33. package/lib/package.json +95 -0
  34. package/lib/src/attachments/IndexDBFileSystemAdapter.d.ts +25 -0
  35. package/lib/src/attachments/IndexDBFileSystemAdapter.js +104 -0
  36. package/lib/src/db/NavigatorTriggerClaimManager.d.ts +6 -0
  37. package/lib/src/db/NavigatorTriggerClaimManager.js +20 -0
  38. package/lib/src/db/PowerSyncDatabase.d.ts +79 -0
  39. package/lib/src/db/PowerSyncDatabase.js +166 -0
  40. package/lib/src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.d.ts +23 -0
  41. package/lib/src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.js +26 -0
  42. package/lib/src/db/adapters/AbstractWebSQLOpenFactory.d.ts +17 -0
  43. package/lib/src/db/adapters/AbstractWebSQLOpenFactory.js +33 -0
  44. package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +49 -0
  45. package/lib/src/db/adapters/AsyncDatabaseConnection.js +1 -0
  46. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +109 -0
  47. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +401 -0
  48. package/lib/src/db/adapters/SSRDBAdapter.d.ts +31 -0
  49. package/lib/src/db/adapters/SSRDBAdapter.js +69 -0
  50. package/lib/src/db/adapters/WebDBAdapter.d.ts +20 -0
  51. package/lib/src/db/adapters/WebDBAdapter.js +1 -0
  52. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +59 -0
  53. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +147 -0
  54. package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.d.ts +12 -0
  55. package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.js +19 -0
  56. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.d.ts +155 -0
  57. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js +401 -0
  58. package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.d.ts +32 -0
  59. package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.js +49 -0
  60. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +23 -0
  61. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +96 -0
  62. package/lib/src/db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.d.ts +15 -0
  63. package/lib/src/db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.js +21 -0
  64. package/lib/src/db/adapters/web-sql-flags.d.ts +87 -0
  65. package/lib/src/db/adapters/web-sql-flags.js +36 -0
  66. package/lib/src/db/sync/SSRWebStreamingSyncImplementation.d.ts +48 -0
  67. package/lib/src/db/sync/SSRWebStreamingSyncImplementation.js +65 -0
  68. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +57 -0
  69. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +237 -0
  70. package/lib/src/db/sync/WebRemote.d.ts +9 -0
  71. package/lib/src/db/sync/WebRemote.js +44 -0
  72. package/lib/src/db/sync/WebStreamingSyncImplementation.d.ts +13 -0
  73. package/lib/src/db/sync/WebStreamingSyncImplementation.js +18 -0
  74. package/lib/src/db/sync/userAgent.d.ts +17 -0
  75. package/lib/src/db/sync/userAgent.js +64 -0
  76. package/lib/src/index.d.ts +15 -0
  77. package/lib/src/index.js +15 -0
  78. package/lib/src/shared/navigator.d.ts +1 -0
  79. package/lib/src/shared/navigator.js +6 -0
  80. package/lib/src/worker/db/SharedWASQLiteConnection.d.ts +42 -0
  81. package/lib/src/worker/db/SharedWASQLiteConnection.js +90 -0
  82. package/lib/src/worker/db/WASQLiteDB.worker.d.ts +4 -0
  83. package/lib/src/worker/db/WASQLiteDB.worker.js +69 -0
  84. package/lib/src/worker/db/WorkerWASQLiteConnection.d.ts +9 -0
  85. package/lib/src/worker/db/WorkerWASQLiteConnection.js +12 -0
  86. package/lib/src/worker/db/open-worker-database.d.ts +14 -0
  87. package/lib/src/worker/db/open-worker-database.js +52 -0
  88. package/lib/src/worker/sync/AbstractSharedSyncClientProvider.d.ts +19 -0
  89. package/lib/src/worker/sync/AbstractSharedSyncClientProvider.js +5 -0
  90. package/lib/src/worker/sync/BroadcastLogger.d.ts +47 -0
  91. package/lib/src/worker/sync/BroadcastLogger.js +128 -0
  92. package/lib/src/worker/sync/SharedSyncImplementation.d.ts +137 -0
  93. package/lib/src/worker/sync/SharedSyncImplementation.js +495 -0
  94. package/lib/src/worker/sync/SharedSyncImplementation.worker.d.ts +1 -0
  95. package/lib/src/worker/sync/SharedSyncImplementation.worker.js +11 -0
  96. package/lib/src/worker/sync/WorkerClient.d.ts +31 -0
  97. package/lib/src/worker/sync/WorkerClient.js +84 -0
  98. package/lib/tsconfig.tsbuildinfo +1 -0
  99. package/package.json +3 -3
  100. package/src/index.ts +1 -0
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Get a minimal representation of browser, version and operating system.
3
+ *
4
+ * The goal is to get enough environemnt info to reproduce issues, but no
5
+ * more.
6
+ */
7
+ export function getUserAgentInfo(nav) {
8
+ nav ??= navigator;
9
+ const browser = getBrowserInfo(nav);
10
+ const os = getOsInfo(nav);
11
+ // The cast below is to cater for TypeScript < 5.5.0
12
+ return [browser, os].filter((v) => v != null);
13
+ }
14
+ function getBrowserInfo(nav) {
15
+ const brands = nav.userAgentData?.brands;
16
+ if (brands != null) {
17
+ const tests = [
18
+ { name: 'Google Chrome', value: 'Chrome' },
19
+ { name: 'Opera', value: 'Opera' },
20
+ { name: 'Edge', value: 'Edge' },
21
+ { name: 'Chromium', value: 'Chromium' }
22
+ ];
23
+ for (let { name, value } of tests) {
24
+ const brand = brands.find((b) => b.brand == name);
25
+ if (brand != null) {
26
+ return `${value}/${brand.version}`;
27
+ }
28
+ }
29
+ }
30
+ const ua = nav.userAgent;
31
+ const regexps = [
32
+ { re: /(?:firefox|fxios)\/(\d+)/i, value: 'Firefox' },
33
+ { re: /(?:edg|edge|edga|edgios)\/(\d+)/i, value: 'Edge' },
34
+ { re: /opr\/(\d+)/i, value: 'Opera' },
35
+ { re: /(?:chrome|chromium|crios)\/(\d+)/i, value: 'Chrome' },
36
+ { re: /version\/(\d+).*safari/i, value: 'Safari' }
37
+ ];
38
+ for (let { re, value } of regexps) {
39
+ const match = re.exec(ua);
40
+ if (match != null) {
41
+ return `${value}/${match[1]}`;
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+ function getOsInfo(nav) {
47
+ if (nav.userAgentData?.platform != null) {
48
+ return nav.userAgentData.platform.toLowerCase();
49
+ }
50
+ const ua = nav.userAgent;
51
+ const regexps = [
52
+ { re: /windows/i, value: 'windows' },
53
+ { re: /android/i, value: 'android' },
54
+ { re: /linux/i, value: 'linux' },
55
+ { re: /iphone|ipad|ipod/i, value: 'ios' },
56
+ { re: /macintosh|mac os x/i, value: 'macos' }
57
+ ];
58
+ for (let { re, value } of regexps) {
59
+ if (re.test(ua)) {
60
+ return value;
61
+ }
62
+ }
63
+ return null;
64
+ }
@@ -0,0 +1,15 @@
1
+ export * from '@powersync/common';
2
+ export * from './attachments/IndexDBFileSystemAdapter.js';
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';
8
+ export * from './db/adapters/wa-sqlite/WASQLiteOpenFactory.js';
9
+ export * from './db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.js';
10
+ export * from './db/adapters/web-sql-flags.js';
11
+ export * from './db/PowerSyncDatabase.js';
12
+ export * from './db/sync/SharedWebStreamingSyncImplementation.js';
13
+ export * from './db/sync/WebRemote.js';
14
+ export * from './db/sync/WebStreamingSyncImplementation.js';
15
+ export * from './db/adapters/WebDBAdapter.js';
@@ -0,0 +1,15 @@
1
+ export * from '@powersync/common';
2
+ export * from './attachments/IndexDBFileSystemAdapter.js';
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';
8
+ export * from './db/adapters/wa-sqlite/WASQLiteOpenFactory.js';
9
+ export * from './db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.js';
10
+ export * from './db/adapters/web-sql-flags.js';
11
+ export * from './db/PowerSyncDatabase.js';
12
+ export * from './db/sync/SharedWebStreamingSyncImplementation.js';
13
+ export * from './db/sync/WebRemote.js';
14
+ export * from './db/sync/WebStreamingSyncImplementation.js';
15
+ export * from './db/adapters/WebDBAdapter.js';
@@ -0,0 +1 @@
1
+ export declare const getNavigatorLocks: () => LockManager;
@@ -0,0 +1,6 @@
1
+ export const getNavigatorLocks = () => {
2
+ if ('locks' in navigator && navigator.locks) {
3
+ return navigator.locks;
4
+ }
5
+ throw new Error('Navigator locks are not available in an insecure context. Use a secure context such as HTTPS or http://localhost.');
6
+ };
@@ -0,0 +1,42 @@
1
+ import { ILogger } from '@powersync/common';
2
+ import { AsyncDatabaseConnection, OnTableChangeCallback, ProxiedQueryResult } from '../../db/adapters/AsyncDatabaseConnection.js';
3
+ import { ResolvedWebSQLOpenOptions } from '../../db/adapters/web-sql-flags.js';
4
+ /**
5
+ * Keeps track of open DB connections and the clients which
6
+ * are using it.
7
+ */
8
+ export type SharedDBWorkerConnection = {
9
+ clientIds: Set<number>;
10
+ db: AsyncDatabaseConnection;
11
+ };
12
+ export type SharedWASQLiteConnectionOptions = {
13
+ dbMap: Map<string, SharedDBWorkerConnection>;
14
+ dbFilename: string;
15
+ clientId: number;
16
+ logger: ILogger;
17
+ };
18
+ export declare class SharedWASQLiteConnection implements AsyncDatabaseConnection {
19
+ protected options: SharedWASQLiteConnectionOptions;
20
+ protected isClosing: boolean;
21
+ protected activeHoldId: string | null;
22
+ constructor(options: SharedWASQLiteConnectionOptions);
23
+ protected get logger(): ILogger;
24
+ protected get dbEntry(): SharedDBWorkerConnection;
25
+ protected get connection(): AsyncDatabaseConnection<ResolvedWebSQLOpenOptions>;
26
+ protected get clientIds(): Set<number>;
27
+ init(): Promise<void>;
28
+ markHold(): Promise<string>;
29
+ releaseHold(id: string): Promise<void>;
30
+ isAutoCommit(): Promise<boolean>;
31
+ /**
32
+ * Handles closing of a shared connection.
33
+ * The connection is only closed if there are no active clients using it.
34
+ */
35
+ close(): Promise<void>;
36
+ protected withClosing<T>(action: () => Promise<T>): Promise<T>;
37
+ execute(sql: string, params?: any[]): Promise<ProxiedQueryResult>;
38
+ executeRaw(sql: string, params?: any[]): Promise<any[][]>;
39
+ executeBatch(sql: string, params?: any[] | undefined): Promise<ProxiedQueryResult>;
40
+ registerOnTableChange(callback: OnTableChangeCallback): Promise<() => void>;
41
+ getConfig(): Promise<ResolvedWebSQLOpenOptions>;
42
+ }
@@ -0,0 +1,90 @@
1
+ export class SharedWASQLiteConnection {
2
+ options;
3
+ isClosing;
4
+ // Keeps track if this current hold if the shared connection has a hold
5
+ activeHoldId;
6
+ constructor(options) {
7
+ this.options = options;
8
+ // Add this client ID to the set of known clients
9
+ this.clientIds.add(options.clientId);
10
+ this.isClosing = false;
11
+ this.activeHoldId = null;
12
+ }
13
+ get logger() {
14
+ return this.options.logger;
15
+ }
16
+ get dbEntry() {
17
+ return this.options.dbMap.get(this.options.dbFilename);
18
+ }
19
+ get connection() {
20
+ return this.dbEntry.db;
21
+ }
22
+ get clientIds() {
23
+ return this.dbEntry.clientIds;
24
+ }
25
+ async init() {
26
+ // No-op since the connection is already initialized when it was created
27
+ }
28
+ async markHold() {
29
+ this.activeHoldId = await this.connection.markHold();
30
+ return this.activeHoldId;
31
+ }
32
+ async releaseHold(id) {
33
+ try {
34
+ await this.connection.releaseHold(id);
35
+ }
36
+ finally {
37
+ this.activeHoldId = null;
38
+ }
39
+ }
40
+ async isAutoCommit() {
41
+ return this.connection.isAutoCommit();
42
+ }
43
+ /**
44
+ * Handles closing of a shared connection.
45
+ * The connection is only closed if there are no active clients using it.
46
+ */
47
+ async close() {
48
+ // This prevents further statements on this connection from being executed
49
+ this.isClosing = true;
50
+ const { clientIds, logger } = this;
51
+ const { clientId, dbFilename, dbMap } = this.options;
52
+ logger.debug(`Close requested from client ${clientId} of ${[...clientIds]}`);
53
+ clientIds.delete(clientId);
54
+ if (this.activeHoldId) {
55
+ // We can't cleanup here since we're not in a lock context.
56
+ // The cleanup will occur once a new hold is acquired.
57
+ this.logger.info(`Hold ${this.activeHoldId} was still active when the connection was closed. Cleanup will occur once a new hold is acquired.`);
58
+ }
59
+ if (clientIds.size == 0) {
60
+ logger.debug(`Closing connection to ${this.options}.`);
61
+ const connection = this.connection;
62
+ dbMap.delete(dbFilename);
63
+ await connection.close();
64
+ return;
65
+ }
66
+ logger.debug(`Connection to ${dbFilename} not closed yet due to active clients.`);
67
+ return;
68
+ }
69
+ async withClosing(action) {
70
+ if (this.isClosing) {
71
+ throw new Error('Connection is closing');
72
+ }
73
+ return action();
74
+ }
75
+ async execute(sql, params) {
76
+ return this.withClosing(() => this.connection.execute(sql, params));
77
+ }
78
+ async executeRaw(sql, params) {
79
+ return this.withClosing(() => this.connection.executeRaw(sql, params));
80
+ }
81
+ executeBatch(sql, params) {
82
+ return this.withClosing(() => this.connection.executeBatch(sql, params));
83
+ }
84
+ registerOnTableChange(callback) {
85
+ return this.connection.registerOnTableChange(callback);
86
+ }
87
+ getConfig() {
88
+ return this.connection.getConfig();
89
+ }
90
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Supports both shared and dedicated workers, based on how the worker is constructed (new SharedWorker vs new Worker()).
3
+ */
4
+ import '@journeyapps/wa-sqlite';
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Supports both shared and dedicated workers, based on how the worker is constructed (new SharedWorker vs new Worker()).
3
+ */
4
+ import '@journeyapps/wa-sqlite';
5
+ import { createBaseLogger, createLogger } from '@powersync/common';
6
+ import * as Comlink from 'comlink';
7
+ import { getNavigatorLocks } from '../../shared/navigator.js';
8
+ import { SharedWASQLiteConnection } from './SharedWASQLiteConnection.js';
9
+ import { WorkerWASQLiteConnection } from './WorkerWASQLiteConnection.js';
10
+ const baseLogger = createBaseLogger();
11
+ baseLogger.useDefaults();
12
+ const logger = createLogger('db-worker');
13
+ const DBMap = new Map();
14
+ const OPEN_DB_LOCK = 'open-wasqlite-db';
15
+ let nextClientId = 1;
16
+ const openDBShared = async (options) => {
17
+ // Prevent multiple simultaneous opens from causing race conditions
18
+ return getNavigatorLocks().request(OPEN_DB_LOCK, async () => {
19
+ const clientId = nextClientId++;
20
+ const { dbFilename, logLevel } = options;
21
+ logger.setLevel(logLevel);
22
+ if (!DBMap.has(dbFilename)) {
23
+ const clientIds = new Set();
24
+ // This format returns proxy objects for function callbacks
25
+ const connection = new WorkerWASQLiteConnection(options);
26
+ await connection.init();
27
+ connection.registerListener({
28
+ holdOverwritten: async () => {
29
+ /**
30
+ * The previous hold has been overwritten, without being released.
31
+ * we need to cleanup any resources associated with it.
32
+ * We can perform a rollback to release any potential transactions that were started.
33
+ */
34
+ await connection.execute('ROLLBACK').catch(() => { });
35
+ }
36
+ });
37
+ DBMap.set(dbFilename, {
38
+ clientIds,
39
+ db: connection
40
+ });
41
+ }
42
+ // Associates this clientId with the shared connection entry
43
+ const sharedConnection = new SharedWASQLiteConnection({
44
+ dbMap: DBMap,
45
+ dbFilename,
46
+ clientId,
47
+ logger
48
+ });
49
+ return Comlink.proxy(sharedConnection);
50
+ });
51
+ };
52
+ // Check if we're in a SharedWorker context
53
+ if (typeof SharedWorkerGlobalScope !== 'undefined') {
54
+ const _self = self;
55
+ _self.onconnect = function (event) {
56
+ const port = event.ports[0];
57
+ Comlink.expose(openDBShared, port);
58
+ };
59
+ }
60
+ else {
61
+ // A dedicated worker can be shared externally
62
+ Comlink.expose(openDBShared);
63
+ }
64
+ addEventListener('unload', () => {
65
+ Array.from(DBMap.values()).forEach(async (dbConnection) => {
66
+ const { db } = dbConnection;
67
+ db.close?.();
68
+ });
69
+ });
@@ -0,0 +1,9 @@
1
+ import { OnTableChangeCallback } from '../../db/adapters/AsyncDatabaseConnection.js';
2
+ import { WASqliteConnection } from '../../db/adapters/wa-sqlite/WASQLiteConnection.js';
3
+ /**
4
+ * A Small proxy wrapper around the WASqliteConnection.
5
+ * This ensures that certain return types are properly proxied.
6
+ */
7
+ export declare class WorkerWASQLiteConnection extends WASqliteConnection {
8
+ registerOnTableChange(callback: OnTableChangeCallback): Promise<() => void>;
9
+ }
@@ -0,0 +1,12 @@
1
+ import * as Comlink from 'comlink';
2
+ import { WASqliteConnection } from '../../db/adapters/wa-sqlite/WASQLiteConnection.js';
3
+ /**
4
+ * A Small proxy wrapper around the WASqliteConnection.
5
+ * This ensures that certain return types are properly proxied.
6
+ */
7
+ export class WorkerWASQLiteConnection extends WASqliteConnection {
8
+ async registerOnTableChange(callback) {
9
+ // Proxy the callback remove function
10
+ return Comlink.proxy(await super.registerOnTableChange(callback));
11
+ }
12
+ }
@@ -0,0 +1,14 @@
1
+ import * as Comlink from 'comlink';
2
+ import { OpenAsyncDatabaseConnection } from '../../db/adapters/AsyncDatabaseConnection.js';
3
+ import { WASQLiteVFS } from '../../db/adapters/wa-sqlite/WASQLiteConnection.js';
4
+ /**
5
+ * Opens a shared or dedicated worker which exposes opening of database connections
6
+ */
7
+ export declare function openWorkerDatabasePort(workerIdentifier: string, multipleTabs?: boolean, worker?: string | URL, vfs?: WASQLiteVFS): Worker | MessagePort;
8
+ /**
9
+ * @returns A function which allows for opening database connections inside
10
+ * a worker.
11
+ */
12
+ export declare function getWorkerDatabaseOpener(workerIdentifier: string, multipleTabs?: boolean, worker?: string | URL): Comlink.Remote<OpenAsyncDatabaseConnection>;
13
+ export declare function resolveWorkerDatabasePortFactory(worker: () => Worker | SharedWorker): Worker | MessagePort;
14
+ export declare function isSharedWorker(worker: Worker | SharedWorker): worker is SharedWorker;
@@ -0,0 +1,52 @@
1
+ import * as Comlink from 'comlink';
2
+ import { WASQLiteVFS } from '../../db/adapters/wa-sqlite/WASQLiteConnection.js';
3
+ /**
4
+ * Opens a shared or dedicated worker which exposes opening of database connections
5
+ */
6
+ export function openWorkerDatabasePort(workerIdentifier, multipleTabs = true, worker = '', vfs) {
7
+ const needsDedicated = vfs == WASQLiteVFS.AccessHandlePoolVFS || vfs == WASQLiteVFS.OPFSCoopSyncVFS;
8
+ if (worker) {
9
+ return !needsDedicated && multipleTabs
10
+ ? new SharedWorker(`${worker}`, {
11
+ /* @vite-ignore */
12
+ name: `shared-DB-worker-${workerIdentifier}`
13
+ }).port
14
+ : new Worker(`${worker}`, {
15
+ /* @vite-ignore */
16
+ name: `DB-worker-${workerIdentifier}`
17
+ });
18
+ }
19
+ else {
20
+ /**
21
+ * Webpack V5 can bundle the worker automatically if the full Worker constructor syntax is used
22
+ * https://webpack.js.org/guides/web-workers/
23
+ * This enables multi tab support by default, but falls back if SharedWorker is not available
24
+ * (in the case of Android)
25
+ */
26
+ return !needsDedicated && multipleTabs
27
+ ? new SharedWorker(new URL('./WASQLiteDB.worker.js', import.meta.url), {
28
+ /* @vite-ignore */
29
+ name: `shared-DB-worker-${workerIdentifier}`,
30
+ type: 'module'
31
+ }).port
32
+ : new Worker(new URL('./WASQLiteDB.worker.js', import.meta.url), {
33
+ /* @vite-ignore */
34
+ name: `DB-worker-${workerIdentifier}`,
35
+ type: 'module'
36
+ });
37
+ }
38
+ }
39
+ /**
40
+ * @returns A function which allows for opening database connections inside
41
+ * a worker.
42
+ */
43
+ export function getWorkerDatabaseOpener(workerIdentifier, multipleTabs = true, worker = '') {
44
+ return Comlink.wrap(openWorkerDatabasePort(workerIdentifier, multipleTabs, worker));
45
+ }
46
+ export function resolveWorkerDatabasePortFactory(worker) {
47
+ const workerInstance = worker();
48
+ return isSharedWorker(workerInstance) ? workerInstance.port : workerInstance;
49
+ }
50
+ export function isSharedWorker(worker) {
51
+ return 'port' in worker;
52
+ }
@@ -0,0 +1,19 @@
1
+ import type { PowerSyncCredentials, SyncStatusOptions } from '@powersync/common';
2
+ /**
3
+ * The client side port should provide these methods.
4
+ */
5
+ export declare abstract class AbstractSharedSyncClientProvider {
6
+ abstract fetchCredentials(): Promise<PowerSyncCredentials | null>;
7
+ abstract invalidateCredentials(): void;
8
+ abstract uploadCrud(): Promise<void>;
9
+ abstract statusChanged(status: SyncStatusOptions): void;
10
+ abstract getDBWorkerPort(): Promise<MessagePort>;
11
+ abstract trace(...x: any[]): void;
12
+ abstract debug(...x: any[]): void;
13
+ abstract info(...x: any[]): void;
14
+ abstract log(...x: any[]): void;
15
+ abstract warn(...x: any[]): void;
16
+ abstract error(...x: any[]): void;
17
+ abstract time(label: string): void;
18
+ abstract timeEnd(label: string): void;
19
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * The client side port should provide these methods.
3
+ */
4
+ export class AbstractSharedSyncClientProvider {
5
+ }
@@ -0,0 +1,47 @@
1
+ import { type ILogger, type ILogLevel } from '@powersync/common';
2
+ import { type WrappedSyncPort } from './SharedSyncImplementation.js';
3
+ /**
4
+ * Broadcasts logs to all clients
5
+ */
6
+ export declare class BroadcastLogger implements ILogger {
7
+ protected clients: WrappedSyncPort[];
8
+ TRACE: ILogLevel;
9
+ DEBUG: ILogLevel;
10
+ INFO: ILogLevel;
11
+ TIME: ILogLevel;
12
+ WARN: ILogLevel;
13
+ ERROR: ILogLevel;
14
+ OFF: ILogLevel;
15
+ private currentLevel;
16
+ constructor(clients: WrappedSyncPort[]);
17
+ trace(...x: any[]): void;
18
+ debug(...x: any[]): void;
19
+ info(...x: any[]): void;
20
+ log(...x: any[]): void;
21
+ warn(...x: any[]): void;
22
+ error(...x: any[]): void;
23
+ time(label: string): void;
24
+ timeEnd(label: string): void;
25
+ /**
26
+ * Set the global log level.
27
+ */
28
+ setLevel(level: ILogLevel): void;
29
+ /**
30
+ * Get the current log level.
31
+ */
32
+ getLevel(): ILogLevel;
33
+ /**
34
+ * Returns true if the given level is enabled.
35
+ */
36
+ enabledFor(level: ILogLevel): boolean;
37
+ /**
38
+ * Iterates all clients, catches individual client exceptions
39
+ * and proceeds to execute for all clients.
40
+ */
41
+ protected iterateClients(callback: (client: WrappedSyncPort) => Promise<void>): Promise<void>;
42
+ /**
43
+ * Guards against any logging errors.
44
+ * We don't want a logging exception to cause further issues upstream
45
+ */
46
+ protected sanitizeArgs(x: any[]): any[];
47
+ }
@@ -0,0 +1,128 @@
1
+ import { LogLevel } from '@powersync/common';
2
+ /**
3
+ * Broadcasts logs to all clients
4
+ */
5
+ export class BroadcastLogger {
6
+ clients;
7
+ TRACE;
8
+ DEBUG;
9
+ INFO;
10
+ TIME;
11
+ WARN;
12
+ ERROR;
13
+ OFF;
14
+ currentLevel = LogLevel.INFO;
15
+ constructor(clients) {
16
+ this.clients = clients;
17
+ this.TRACE = LogLevel.TRACE;
18
+ this.DEBUG = LogLevel.DEBUG;
19
+ this.INFO = LogLevel.INFO;
20
+ this.TIME = LogLevel.TIME;
21
+ this.WARN = LogLevel.WARN;
22
+ this.ERROR = LogLevel.ERROR;
23
+ this.OFF = LogLevel.OFF;
24
+ }
25
+ trace(...x) {
26
+ if (!this.enabledFor(this.TRACE))
27
+ return;
28
+ console.trace(...x);
29
+ const sanitized = this.sanitizeArgs(x);
30
+ this.iterateClients((client) => client.clientProvider.trace(...sanitized));
31
+ }
32
+ debug(...x) {
33
+ if (!this.enabledFor(this.DEBUG))
34
+ return;
35
+ console.debug(...x);
36
+ const sanitized = this.sanitizeArgs(x);
37
+ this.iterateClients((client) => client.clientProvider.debug(...sanitized));
38
+ }
39
+ info(...x) {
40
+ if (!this.enabledFor(this.INFO))
41
+ return;
42
+ console.info(...x);
43
+ const sanitized = this.sanitizeArgs(x);
44
+ this.iterateClients((client) => client.clientProvider.info(...sanitized));
45
+ }
46
+ log(...x) {
47
+ if (!this.enabledFor(this.INFO))
48
+ return;
49
+ console.log(...x);
50
+ const sanitized = this.sanitizeArgs(x);
51
+ this.iterateClients((client) => client.clientProvider.log(...sanitized));
52
+ }
53
+ warn(...x) {
54
+ if (!this.enabledFor(this.WARN))
55
+ return;
56
+ console.warn(...x);
57
+ const sanitized = this.sanitizeArgs(x);
58
+ this.iterateClients((client) => client.clientProvider.warn(...sanitized));
59
+ }
60
+ error(...x) {
61
+ if (!this.enabledFor(this.ERROR))
62
+ return;
63
+ console.error(...x);
64
+ const sanitized = this.sanitizeArgs(x);
65
+ this.iterateClients((client) => client.clientProvider.error(...sanitized));
66
+ }
67
+ time(label) {
68
+ if (!this.enabledFor(this.TIME))
69
+ return;
70
+ console.time(label);
71
+ this.iterateClients((client) => client.clientProvider.time(label));
72
+ }
73
+ timeEnd(label) {
74
+ if (!this.enabledFor(this.TIME))
75
+ return;
76
+ console.timeEnd(label);
77
+ this.iterateClients((client) => client.clientProvider.timeEnd(label));
78
+ }
79
+ /**
80
+ * Set the global log level.
81
+ */
82
+ setLevel(level) {
83
+ this.currentLevel = level;
84
+ }
85
+ /**
86
+ * Get the current log level.
87
+ */
88
+ getLevel() {
89
+ return this.currentLevel;
90
+ }
91
+ /**
92
+ * Returns true if the given level is enabled.
93
+ */
94
+ enabledFor(level) {
95
+ return level.value >= this.currentLevel.value;
96
+ }
97
+ /**
98
+ * Iterates all clients, catches individual client exceptions
99
+ * and proceeds to execute for all clients.
100
+ */
101
+ async iterateClients(callback) {
102
+ for (const client of this.clients) {
103
+ try {
104
+ await callback(client);
105
+ }
106
+ catch (ex) {
107
+ console.error('Caught exception when iterating client', ex);
108
+ }
109
+ }
110
+ }
111
+ /**
112
+ * Guards against any logging errors.
113
+ * We don't want a logging exception to cause further issues upstream
114
+ */
115
+ sanitizeArgs(x) {
116
+ const sanitizedParams = x.map((param) => {
117
+ try {
118
+ // Try and clone here first. If it fails it won't be passable over a MessagePort
119
+ return structuredClone(param);
120
+ }
121
+ catch (ex) {
122
+ console.error(ex);
123
+ return 'Could not serialize log params. Check shared worker logs for more details.';
124
+ }
125
+ });
126
+ return sanitizedParams;
127
+ }
128
+ }