@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.
- package/dist/0b19af1befc07ce338dd.wasm +0 -0
- package/dist/2632c3bda9473da74fd5.wasm +0 -0
- package/dist/64f5351ba3784bfe2f3e.wasm +0 -0
- package/dist/9318ca94aac4d0fe0135.wasm +0 -0
- package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js +1878 -0
- package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js.map +1 -0
- package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-2530150.index.umd.js +555 -0
- package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-2530150.index.umd.js.map +1 -0
- package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-2530151.index.umd.js +555 -0
- package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-2530151.index.umd.js.map +1 -0
- package/dist/index.umd.js +8267 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/worker/SharedSyncImplementation.umd.js +19059 -0
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -0
- package/dist/worker/WASQLiteDB.umd.js +17736 -0
- package/dist/worker/WASQLiteDB.umd.js.map +1 -0
- package/dist/worker/node_modules_pnpm_bson_6_10_4_node_modules_bson_lib_bson_mjs.umd.js +4646 -0
- package/dist/worker/node_modules_pnpm_bson_6_10_4_node_modules_bson_lib_bson_mjs.umd.js.map +1 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-0d2437.umd.js +2478 -0
- 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
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-1d4e74.umd.js +1820 -0
- 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
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-3622cf.umd.js +1681 -0
- 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
- package/lib/package.json +95 -0
- package/lib/src/attachments/IndexDBFileSystemAdapter.d.ts +25 -0
- package/lib/src/attachments/IndexDBFileSystemAdapter.js +104 -0
- package/lib/src/db/NavigatorTriggerClaimManager.d.ts +6 -0
- package/lib/src/db/NavigatorTriggerClaimManager.js +20 -0
- package/lib/src/db/PowerSyncDatabase.d.ts +79 -0
- package/lib/src/db/PowerSyncDatabase.js +166 -0
- package/lib/src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.d.ts +23 -0
- package/lib/src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.js +26 -0
- package/lib/src/db/adapters/AbstractWebSQLOpenFactory.d.ts +17 -0
- package/lib/src/db/adapters/AbstractWebSQLOpenFactory.js +33 -0
- package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +49 -0
- package/lib/src/db/adapters/AsyncDatabaseConnection.js +1 -0
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +109 -0
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +401 -0
- package/lib/src/db/adapters/SSRDBAdapter.d.ts +31 -0
- package/lib/src/db/adapters/SSRDBAdapter.js +69 -0
- package/lib/src/db/adapters/WebDBAdapter.d.ts +20 -0
- package/lib/src/db/adapters/WebDBAdapter.js +1 -0
- package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +59 -0
- package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +147 -0
- package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.d.ts +12 -0
- package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.js +19 -0
- package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.d.ts +155 -0
- package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js +401 -0
- package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.d.ts +32 -0
- package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.js +49 -0
- package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +23 -0
- package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +96 -0
- package/lib/src/db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.d.ts +15 -0
- package/lib/src/db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.js +21 -0
- package/lib/src/db/adapters/web-sql-flags.d.ts +87 -0
- package/lib/src/db/adapters/web-sql-flags.js +36 -0
- package/lib/src/db/sync/SSRWebStreamingSyncImplementation.d.ts +48 -0
- package/lib/src/db/sync/SSRWebStreamingSyncImplementation.js +65 -0
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +57 -0
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +237 -0
- package/lib/src/db/sync/WebRemote.d.ts +9 -0
- package/lib/src/db/sync/WebRemote.js +44 -0
- package/lib/src/db/sync/WebStreamingSyncImplementation.d.ts +13 -0
- package/lib/src/db/sync/WebStreamingSyncImplementation.js +18 -0
- package/lib/src/db/sync/userAgent.d.ts +17 -0
- package/lib/src/db/sync/userAgent.js +64 -0
- package/lib/src/index.d.ts +15 -0
- package/lib/src/index.js +15 -0
- package/lib/src/shared/navigator.d.ts +1 -0
- package/lib/src/shared/navigator.js +6 -0
- package/lib/src/worker/db/SharedWASQLiteConnection.d.ts +42 -0
- package/lib/src/worker/db/SharedWASQLiteConnection.js +90 -0
- package/lib/src/worker/db/WASQLiteDB.worker.d.ts +4 -0
- package/lib/src/worker/db/WASQLiteDB.worker.js +69 -0
- package/lib/src/worker/db/WorkerWASQLiteConnection.d.ts +9 -0
- package/lib/src/worker/db/WorkerWASQLiteConnection.js +12 -0
- package/lib/src/worker/db/open-worker-database.d.ts +14 -0
- package/lib/src/worker/db/open-worker-database.js +52 -0
- package/lib/src/worker/sync/AbstractSharedSyncClientProvider.d.ts +19 -0
- package/lib/src/worker/sync/AbstractSharedSyncClientProvider.js +5 -0
- package/lib/src/worker/sync/BroadcastLogger.d.ts +47 -0
- package/lib/src/worker/sync/BroadcastLogger.js +128 -0
- package/lib/src/worker/sync/SharedSyncImplementation.d.ts +137 -0
- package/lib/src/worker/sync/SharedSyncImplementation.js +495 -0
- package/lib/src/worker/sync/SharedSyncImplementation.worker.d.ts +1 -0
- package/lib/src/worker/sync/SharedSyncImplementation.worker.js +11 -0
- package/lib/src/worker/sync/WorkerClient.d.ts +31 -0
- package/lib/src/worker/sync/WorkerClient.js +84 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +3 -3
- 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';
|
package/lib/src/index.js
ADDED
|
@@ -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,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,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,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
|
+
}
|