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

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 (99) 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 +8253 -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 +14 -0
  77. package/lib/src/index.js +14 -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
@@ -0,0 +1,26 @@
1
+ import { AbstractPowerSyncDatabaseOpenFactory } from '@powersync/common';
2
+ import { PowerSyncDatabase, resolveWebPowerSyncFlags } from '../../db/PowerSyncDatabase.js';
3
+ /**
4
+ * Intermediate PowerSync Database Open factory for Web which uses a mock
5
+ * SSR DB Adapter if running on server side.
6
+ * Most SQLite DB implementations only run on client side, this will safely return
7
+ * empty query results in SSR which will allow for generating server partial views.
8
+ */
9
+ export class AbstractWebPowerSyncDatabaseOpenFactory extends AbstractPowerSyncDatabaseOpenFactory {
10
+ options;
11
+ constructor(options) {
12
+ super(options);
13
+ this.options = options;
14
+ }
15
+ generateOptions() {
16
+ return {
17
+ ...this.options,
18
+ database: this.openDB(),
19
+ schema: this.schema,
20
+ flags: resolveWebPowerSyncFlags(this.options.flags)
21
+ };
22
+ }
23
+ generateInstance(options) {
24
+ return new PowerSyncDatabase(options);
25
+ }
26
+ }
@@ -0,0 +1,17 @@
1
+ import { DBAdapter, type ILogger, SQLOpenFactory } from '@powersync/common';
2
+ import { ResolvedWebSQLFlags, WebSQLOpenFactoryOptions } from './web-sql-flags.js';
3
+ export declare abstract class AbstractWebSQLOpenFactory implements SQLOpenFactory {
4
+ protected options: WebSQLOpenFactoryOptions;
5
+ protected resolvedFlags: ResolvedWebSQLFlags;
6
+ protected logger: ILogger;
7
+ constructor(options: WebSQLOpenFactoryOptions);
8
+ /**
9
+ * Opens a DBAdapter if not in SSR mode
10
+ */
11
+ protected abstract openAdapter(): DBAdapter;
12
+ /**
13
+ * Opens a {@link DBAdapter} using resolved flags.
14
+ * A SSR implementation is loaded if SSR mode is detected.
15
+ */
16
+ openDB(): DBAdapter;
17
+ }
@@ -0,0 +1,33 @@
1
+ import { createLogger } from '@powersync/common';
2
+ import { SSRDBAdapter } from './SSRDBAdapter.js';
3
+ import { isServerSide, resolveWebSQLFlags } from './web-sql-flags.js';
4
+ export class AbstractWebSQLOpenFactory {
5
+ options;
6
+ resolvedFlags;
7
+ logger;
8
+ constructor(options) {
9
+ this.options = options;
10
+ this.resolvedFlags = resolveWebSQLFlags(options.flags);
11
+ this.logger = options.logger ?? createLogger(`AbstractWebSQLOpenFactory - ${this.options.dbFilename}`);
12
+ }
13
+ /**
14
+ * Opens a {@link DBAdapter} using resolved flags.
15
+ * A SSR implementation is loaded if SSR mode is detected.
16
+ */
17
+ openDB() {
18
+ const { resolvedFlags: { disableSSRWarning, enableMultiTabs, ssrMode = isServerSide() } } = this;
19
+ if (ssrMode && !disableSSRWarning) {
20
+ this.logger.warn(`
21
+ Running PowerSync in SSR mode.
22
+ Only empty query results will be returned.
23
+ Disable this warning by setting 'disableSSRWarning: true' in options.`);
24
+ }
25
+ if (!enableMultiTabs) {
26
+ this.logger.warn('Multiple tab support is not enabled. Using this site across multiple tabs may not function correctly.');
27
+ }
28
+ if (ssrMode) {
29
+ return new SSRDBAdapter();
30
+ }
31
+ return this.openAdapter();
32
+ }
33
+ }
@@ -0,0 +1,49 @@
1
+ import { BatchedUpdateNotification, QueryResult } from '@powersync/common';
2
+ import { ResolvedWebSQLOpenOptions } from './web-sql-flags.js';
3
+ /**
4
+ * @internal
5
+ * Proxied query result does not contain a function for accessing row values
6
+ */
7
+ export type ProxiedQueryResult = Omit<QueryResult, 'rows'> & {
8
+ rows: {
9
+ _array: any[];
10
+ length: number;
11
+ };
12
+ };
13
+ /**
14
+ * @internal
15
+ */
16
+ export type OnTableChangeCallback = (event: BatchedUpdateNotification) => void;
17
+ /**
18
+ * @internal
19
+ * An async Database connection which provides basic async SQL methods.
20
+ * This is usually a proxied through a web worker.
21
+ */
22
+ export interface AsyncDatabaseConnection<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions> {
23
+ init(): Promise<void>;
24
+ close(): Promise<void>;
25
+ /**
26
+ * Marks the connection as in-use by a certain actor.
27
+ * @returns A hold ID which can be used to release the hold.
28
+ */
29
+ markHold(): Promise<string>;
30
+ /**
31
+ * Releases a hold on the connection.
32
+ * @param holdId The hold ID to release.
33
+ */
34
+ releaseHold(holdId: string): Promise<void>;
35
+ /**
36
+ * Checks if the database connection is in autocommit mode.
37
+ * @returns true if in autocommit mode, false if in a transaction
38
+ */
39
+ isAutoCommit(): Promise<boolean>;
40
+ execute(sql: string, params?: any[]): Promise<ProxiedQueryResult>;
41
+ executeRaw(sql: string, params?: any[]): Promise<any[][]>;
42
+ executeBatch(sql: string, params?: any[]): Promise<ProxiedQueryResult>;
43
+ registerOnTableChange(callback: OnTableChangeCallback): Promise<() => void>;
44
+ getConfig(): Promise<Config>;
45
+ }
46
+ /**
47
+ * @internal
48
+ */
49
+ export type OpenAsyncDatabaseConnection<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions> = (config: Config) => AsyncDatabaseConnection;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,109 @@
1
+ import { BaseObserver, DBAdapterListener, DBLockOptions, LockContext, QueryResult, Transaction, type ILogger } from '@powersync/common';
2
+ import { AsyncDatabaseConnection } from './AsyncDatabaseConnection.js';
3
+ import { SharedConnectionWorker, WebDBAdapter, WebDBAdapterConfiguration } from './WebDBAdapter.js';
4
+ import { ResolvedWebSQLOpenOptions } from './web-sql-flags.js';
5
+ /**
6
+ * @internal
7
+ */
8
+ export interface LockedAsyncDatabaseAdapterOptions {
9
+ name: string;
10
+ openConnection: () => Promise<AsyncDatabaseConnection>;
11
+ debugMode?: boolean;
12
+ logger?: ILogger;
13
+ defaultLockTimeoutMs?: number;
14
+ reOpenOnConnectionClosed?: boolean;
15
+ }
16
+ export type LockedAsyncDatabaseAdapterListener = DBAdapterListener & {
17
+ initialized?: () => void;
18
+ /**
19
+ * Fired when the database is re-opened after being closed.
20
+ */
21
+ databaseReOpened?: () => void;
22
+ };
23
+ /**
24
+ * @internal
25
+ * Wraps a {@link AsyncDatabaseConnection} and provides exclusive locking functions in
26
+ * order to implement {@link DBAdapter}.
27
+ */
28
+ export declare class LockedAsyncDatabaseAdapter extends BaseObserver<LockedAsyncDatabaseAdapterListener> implements WebDBAdapter {
29
+ protected options: LockedAsyncDatabaseAdapterOptions;
30
+ private logger;
31
+ private dbGetHelpers;
32
+ private debugMode;
33
+ private _dbIdentifier;
34
+ protected initPromise: Promise<void>;
35
+ private _db;
36
+ protected _disposeTableChangeListener: (() => void) | null;
37
+ private _config;
38
+ protected pendingAbortControllers: Set<AbortController>;
39
+ protected requiresHolds: boolean | null;
40
+ protected databaseOpenPromise: Promise<void> | null;
41
+ closing: boolean;
42
+ closed: boolean;
43
+ constructor(options: LockedAsyncDatabaseAdapterOptions);
44
+ protected get baseDB(): AsyncDatabaseConnection<ResolvedWebSQLOpenOptions>;
45
+ get name(): string;
46
+ /**
47
+ * Init is automatic, this helps catch errors or explicitly await initialization
48
+ */
49
+ init(): Promise<void>;
50
+ protected openInternalDB(): Promise<any>;
51
+ protected _reOpen(): Promise<void>;
52
+ /**
53
+ * Re-opens the underlying database.
54
+ * Returns a pending operation if one is already in progress.
55
+ */
56
+ reOpenInternalDB(): Promise<void>;
57
+ protected _init(): Promise<void>;
58
+ getConfiguration(): WebDBAdapterConfiguration;
59
+ protected waitForInitialized(): Promise<void>;
60
+ shareConnection(): Promise<SharedConnectionWorker>;
61
+ /**
62
+ * Registers a table change notification callback with the base database.
63
+ * This can be extended by custom implementations in order to handle proxy events.
64
+ */
65
+ protected registerOnChangeListener(db: AsyncDatabaseConnection): Promise<void>;
66
+ /**
67
+ * This is currently a no-op on web
68
+ */
69
+ refreshSchema(): Promise<void>;
70
+ execute(query: string, params?: any[] | undefined): Promise<QueryResult>;
71
+ executeRaw(query: string, params?: any[] | undefined): Promise<any[][]>;
72
+ executeBatch(query: string, params?: any[][]): Promise<QueryResult>;
73
+ /**
74
+ * Attempts to close the connection.
75
+ * Shared workers might not actually close the connection if other
76
+ * tabs are still using it.
77
+ */
78
+ close(): Promise<void>;
79
+ getAll<T>(sql: string, parameters?: any[] | undefined): Promise<T[]>;
80
+ getOptional<T>(sql: string, parameters?: any[] | undefined): Promise<T | null>;
81
+ get<T>(sql: string, parameters?: any[] | undefined): Promise<T>;
82
+ readLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions | undefined): Promise<T>;
83
+ writeLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions | undefined): Promise<T>;
84
+ protected _acquireLock(callback: () => Promise<any>, options?: {
85
+ timeoutMs?: number;
86
+ }): Promise<any>;
87
+ protected acquireLock(callback: () => Promise<any>, options?: {
88
+ timeoutMs?: number;
89
+ }): Promise<any>;
90
+ readTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions | undefined): Promise<T>;
91
+ writeTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions | undefined): Promise<T>;
92
+ private generateDBHelpers;
93
+ /**
94
+ * Wraps a lock context into a transaction context
95
+ */
96
+ private wrapTransaction;
97
+ /**
98
+ * Wraps the worker execute function, awaiting for it to be available
99
+ */
100
+ private _execute;
101
+ /**
102
+ * Wraps the worker executeRaw function, awaiting for it to be available
103
+ */
104
+ private _executeRaw;
105
+ /**
106
+ * Wraps the worker executeBatch function, awaiting for it to be available
107
+ */
108
+ private _executeBatch;
109
+ }
@@ -0,0 +1,401 @@
1
+ import { BaseObserver, ConnectionClosedError, createLogger } from '@powersync/common';
2
+ import { getNavigatorLocks } from '../../shared/navigator.js';
3
+ import { WorkerWrappedAsyncDatabaseConnection } from './WorkerWrappedAsyncDatabaseConnection.js';
4
+ import { WASQLiteVFS } from './wa-sqlite/WASQLiteConnection.js';
5
+ /**
6
+ * @internal
7
+ * Wraps a {@link AsyncDatabaseConnection} and provides exclusive locking functions in
8
+ * order to implement {@link DBAdapter}.
9
+ */
10
+ export class LockedAsyncDatabaseAdapter extends BaseObserver {
11
+ options;
12
+ logger;
13
+ dbGetHelpers;
14
+ debugMode;
15
+ _dbIdentifier;
16
+ initPromise;
17
+ _db = null;
18
+ _disposeTableChangeListener = null;
19
+ _config = null;
20
+ pendingAbortControllers;
21
+ requiresHolds;
22
+ databaseOpenPromise = null;
23
+ closing;
24
+ closed;
25
+ constructor(options) {
26
+ super();
27
+ this.options = options;
28
+ this._dbIdentifier = options.name;
29
+ this.logger = options.logger ?? createLogger(`LockedAsyncDatabaseAdapter - ${this._dbIdentifier}`);
30
+ this.pendingAbortControllers = new Set();
31
+ this.closed = false;
32
+ this.closing = false;
33
+ this.requiresHolds = null;
34
+ // Set the name if provided. We can query for the name if not available yet
35
+ this.debugMode = options.debugMode ?? false;
36
+ if (this.debugMode) {
37
+ const originalExecute = this._execute.bind(this);
38
+ this._execute = async (sql, bindings) => {
39
+ const start = performance.now();
40
+ try {
41
+ const r = await originalExecute(sql, bindings);
42
+ performance.measure(`[SQL] ${sql}`, { start });
43
+ return r;
44
+ }
45
+ catch (e) {
46
+ performance.measure(`[SQL] [ERROR: ${e.message}] ${sql}`, { start });
47
+ throw e;
48
+ }
49
+ };
50
+ }
51
+ this.dbGetHelpers = this.generateDBHelpers({
52
+ execute: (query, params) => this.acquireLock(() => this._execute(query, params)),
53
+ executeRaw: (query, params) => this.acquireLock(() => this._executeRaw(query, params))
54
+ });
55
+ this.initPromise = this._init();
56
+ }
57
+ get baseDB() {
58
+ if (!this._db) {
59
+ throw new Error(`Initialization has not completed yet. Cannot access base db`);
60
+ }
61
+ return this._db;
62
+ }
63
+ get name() {
64
+ return this._dbIdentifier;
65
+ }
66
+ /**
67
+ * Init is automatic, this helps catch errors or explicitly await initialization
68
+ */
69
+ async init() {
70
+ return this.initPromise;
71
+ }
72
+ async openInternalDB() {
73
+ /**
74
+ * Execute opening of the db in a lock in order not to interfere with other operations.
75
+ */
76
+ return this._acquireLock(async () => {
77
+ // Dispose any previous table change listener.
78
+ this._disposeTableChangeListener?.();
79
+ this._disposeTableChangeListener = null;
80
+ this._db?.close().catch((ex) => this.logger.warn(`Error closing database before opening new instance`, ex));
81
+ const isReOpen = !!this._db;
82
+ this._db = null;
83
+ this._db = await this.options.openConnection();
84
+ await this._db.init();
85
+ this._config = await this._db.getConfig();
86
+ await this.registerOnChangeListener(this._db);
87
+ if (isReOpen) {
88
+ this.iterateListeners((cb) => cb.databaseReOpened?.());
89
+ }
90
+ /**
91
+ * This is only required for the long-lived shared IndexedDB connections.
92
+ */
93
+ this.requiresHolds = this._config.vfs == WASQLiteVFS.IDBBatchAtomicVFS;
94
+ });
95
+ }
96
+ _reOpen() {
97
+ this.databaseOpenPromise = this.openInternalDB().finally(() => {
98
+ this.databaseOpenPromise = null;
99
+ });
100
+ return this.databaseOpenPromise;
101
+ }
102
+ /**
103
+ * Re-opens the underlying database.
104
+ * Returns a pending operation if one is already in progress.
105
+ */
106
+ async reOpenInternalDB() {
107
+ if (this.closing || !this.options.reOpenOnConnectionClosed) {
108
+ // No-op
109
+ return;
110
+ }
111
+ else if (this.databaseOpenPromise) {
112
+ // Already busy opening
113
+ return this.databaseOpenPromise;
114
+ }
115
+ else {
116
+ return this._reOpen();
117
+ }
118
+ }
119
+ async _init() {
120
+ /**
121
+ * For OPFS, we can see this open call sometimes fail due to NoModificationAllowedError.
122
+ * We should be able to recover from this by re-opening the database.
123
+ */
124
+ const maxAttempts = 3;
125
+ for (let count = 0; count < maxAttempts; count++) {
126
+ try {
127
+ await this.openInternalDB();
128
+ break;
129
+ }
130
+ catch (ex) {
131
+ if (count == maxAttempts - 1) {
132
+ throw ex;
133
+ }
134
+ this.logger.warn(`Attempt ${count + 1} of ${maxAttempts} to open database failed, retrying in 1 second...`, ex);
135
+ await new Promise((resolve) => setTimeout(resolve, 1000));
136
+ }
137
+ }
138
+ this.iterateListeners((cb) => cb.initialized?.());
139
+ }
140
+ getConfiguration() {
141
+ if (!this._config) {
142
+ throw new Error(`Cannot get config before initialization is completed`);
143
+ }
144
+ return {
145
+ ...this._config,
146
+ // This can be overridden by the adapter later
147
+ requiresPersistentTriggers: false
148
+ };
149
+ }
150
+ async waitForInitialized() {
151
+ // Awaiting this will expose errors on function calls like .execute etc
152
+ await this.initPromise;
153
+ }
154
+ async shareConnection() {
155
+ if (false == this._db instanceof WorkerWrappedAsyncDatabaseConnection) {
156
+ throw new Error(`Only worker connections can be shared`);
157
+ }
158
+ return this._db.shareConnection();
159
+ }
160
+ /**
161
+ * Registers a table change notification callback with the base database.
162
+ * This can be extended by custom implementations in order to handle proxy events.
163
+ */
164
+ async registerOnChangeListener(db) {
165
+ this._disposeTableChangeListener = await db.registerOnTableChange((event) => {
166
+ this.iterateListeners((cb) => cb.tablesUpdated?.(event));
167
+ });
168
+ }
169
+ /**
170
+ * This is currently a no-op on web
171
+ */
172
+ async refreshSchema() { }
173
+ async execute(query, params) {
174
+ return this.writeLock((ctx) => ctx.execute(query, params));
175
+ }
176
+ async executeRaw(query, params) {
177
+ return this.writeLock((ctx) => ctx.executeRaw(query, params));
178
+ }
179
+ async executeBatch(query, params) {
180
+ return this.writeLock((ctx) => this._executeBatch(query, params));
181
+ }
182
+ /**
183
+ * Attempts to close the connection.
184
+ * Shared workers might not actually close the connection if other
185
+ * tabs are still using it.
186
+ */
187
+ async close() {
188
+ this.closing = true;
189
+ /**
190
+ * Note that we obtain a reference to the callback to avoid calling the callback with `this` as the context.
191
+ * This is to avoid Comlink attempting to clone `this` when calling the method.
192
+ */
193
+ const dispose = this._disposeTableChangeListener;
194
+ if (dispose) {
195
+ dispose();
196
+ }
197
+ this.pendingAbortControllers.forEach((controller) => controller.abort('Closed'));
198
+ await this.baseDB?.close?.();
199
+ this.closed = true;
200
+ }
201
+ async getAll(sql, parameters) {
202
+ await this.waitForInitialized();
203
+ return this.dbGetHelpers.getAll(sql, parameters);
204
+ }
205
+ async getOptional(sql, parameters) {
206
+ await this.waitForInitialized();
207
+ return this.dbGetHelpers.getOptional(sql, parameters);
208
+ }
209
+ async get(sql, parameters) {
210
+ await this.waitForInitialized();
211
+ return this.dbGetHelpers.get(sql, parameters);
212
+ }
213
+ async readLock(fn, options) {
214
+ await this.waitForInitialized();
215
+ return this.acquireLock(async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })), {
216
+ timeoutMs: options?.timeoutMs ?? this.options.defaultLockTimeoutMs
217
+ });
218
+ }
219
+ async writeLock(fn, options) {
220
+ await this.waitForInitialized();
221
+ return this.acquireLock(async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })), {
222
+ timeoutMs: options?.timeoutMs ?? this.options.defaultLockTimeoutMs
223
+ });
224
+ }
225
+ async _acquireLock(callback, options) {
226
+ if (this.closing) {
227
+ throw new Error(`Cannot acquire lock, the database is closing`);
228
+ }
229
+ const abortController = new AbortController();
230
+ this.pendingAbortControllers.add(abortController);
231
+ const { timeoutMs } = options ?? {};
232
+ const timeoutId = timeoutMs
233
+ ? setTimeout(() => {
234
+ abortController.abort(`Timeout after ${timeoutMs}ms`);
235
+ this.pendingAbortControllers.delete(abortController);
236
+ }, timeoutMs)
237
+ : null;
238
+ return getNavigatorLocks().request(`db-lock-${this._dbIdentifier}`, { signal: abortController.signal }, async () => {
239
+ this.pendingAbortControllers.delete(abortController);
240
+ if (timeoutId) {
241
+ clearTimeout(timeoutId);
242
+ }
243
+ return await callback();
244
+ });
245
+ }
246
+ async acquireLock(callback, options) {
247
+ await this.waitForInitialized();
248
+ // The database is being (re)opened in the background. Wait for it here.
249
+ if (this.databaseOpenPromise) {
250
+ await this.databaseOpenPromise;
251
+ }
252
+ else if (!this._db) {
253
+ /**
254
+ * The database is not open anymore, we might need to re-open it.
255
+ * Typically, _db, can be `null` if we tried to reOpen the database, but failed to succeed in re-opening.
256
+ * This can happen when disconnecting the client.
257
+ * Note: It is safe to re-enter this method multiple times.
258
+ */
259
+ await this.reOpenInternalDB();
260
+ }
261
+ return this._acquireLock(async () => {
262
+ let holdId = null;
263
+ try {
264
+ /**
265
+ * We can't await this since it uses the same lock as we're in now.
266
+ * If there is a pending open, this call will throw.
267
+ * If there is no pending open, but there is also no database - the open
268
+ * might have failed. We need to re-open the database.
269
+ */
270
+ if (this.databaseOpenPromise || !this._db) {
271
+ throw new ConnectionClosedError('Connection is busy re-opening');
272
+ }
273
+ holdId = this.requiresHolds ? await this.baseDB.markHold() : null;
274
+ return await callback();
275
+ }
276
+ catch (ex) {
277
+ if (ConnectionClosedError.MATCHES(ex)) {
278
+ // Immediately re-open the database. We need to miss as little table updates as possible.
279
+ // Note, don't await this since it uses the same lock as we're in now.
280
+ this.reOpenInternalDB();
281
+ }
282
+ throw ex;
283
+ }
284
+ finally {
285
+ if (holdId) {
286
+ await this.baseDB.releaseHold(holdId);
287
+ }
288
+ }
289
+ }, options);
290
+ }
291
+ async readTransaction(fn, options) {
292
+ return this.readLock(this.wrapTransaction(fn));
293
+ }
294
+ writeTransaction(fn, options) {
295
+ return this.writeLock(this.wrapTransaction(fn, true));
296
+ }
297
+ generateDBHelpers(tx) {
298
+ return {
299
+ ...tx,
300
+ /**
301
+ * Execute a read-only query and return results
302
+ */
303
+ async getAll(sql, parameters) {
304
+ const res = await tx.execute(sql, parameters);
305
+ return res.rows?._array ?? [];
306
+ },
307
+ /**
308
+ * Execute a read-only query and return the first result, or null if the ResultSet is empty.
309
+ */
310
+ async getOptional(sql, parameters) {
311
+ const res = await tx.execute(sql, parameters);
312
+ return res.rows?.item(0) ?? null;
313
+ },
314
+ /**
315
+ * Execute a read-only query and return the first result, error if the ResultSet is empty.
316
+ */
317
+ async get(sql, parameters) {
318
+ const res = await tx.execute(sql, parameters);
319
+ const first = res.rows?.item(0);
320
+ if (!first) {
321
+ throw new Error('Result set is empty');
322
+ }
323
+ return first;
324
+ }
325
+ };
326
+ }
327
+ /**
328
+ * Wraps a lock context into a transaction context
329
+ */
330
+ wrapTransaction(cb, write = false) {
331
+ return async (tx) => {
332
+ await this._execute(write ? 'BEGIN EXCLUSIVE' : 'BEGIN');
333
+ let finalized = false;
334
+ const commit = async () => {
335
+ if (finalized) {
336
+ return { rowsAffected: 0 };
337
+ }
338
+ finalized = true;
339
+ return this._execute('COMMIT');
340
+ };
341
+ const rollback = () => {
342
+ finalized = true;
343
+ return this._execute('ROLLBACK');
344
+ };
345
+ try {
346
+ const result = await cb({
347
+ ...tx,
348
+ commit,
349
+ rollback
350
+ });
351
+ if (!finalized) {
352
+ await commit();
353
+ }
354
+ return result;
355
+ }
356
+ catch (ex) {
357
+ this.logger.debug('Caught ex in transaction', ex);
358
+ try {
359
+ await rollback();
360
+ }
361
+ catch (ex2) {
362
+ // In rare cases, a rollback may fail.
363
+ // Safe to ignore.
364
+ }
365
+ throw ex;
366
+ }
367
+ };
368
+ }
369
+ /**
370
+ * Wraps the worker execute function, awaiting for it to be available
371
+ */
372
+ _execute = async (sql, bindings) => {
373
+ await this.waitForInitialized();
374
+ const result = await this.baseDB.execute(sql, bindings);
375
+ return {
376
+ ...result,
377
+ rows: {
378
+ ...result.rows,
379
+ item: (idx) => result.rows._array[idx]
380
+ }
381
+ };
382
+ };
383
+ /**
384
+ * Wraps the worker executeRaw function, awaiting for it to be available
385
+ */
386
+ _executeRaw = async (sql, bindings) => {
387
+ await this.waitForInitialized();
388
+ return await this.baseDB.executeRaw(sql, bindings);
389
+ };
390
+ /**
391
+ * Wraps the worker executeBatch function, awaiting for it to be available
392
+ */
393
+ _executeBatch = async (query, params) => {
394
+ await this.waitForInitialized();
395
+ const result = await this.baseDB.executeBatch(query, params);
396
+ return {
397
+ ...result,
398
+ rows: undefined
399
+ };
400
+ };
401
+ }
@@ -0,0 +1,31 @@
1
+ import { BaseObserver, DBAdapterListener, DBAdapter, DBLockOptions, LockContext, QueryResult, Transaction } from '@powersync/common';
2
+ import { Mutex } from 'async-mutex';
3
+ /**
4
+ * Implements a Mock DB adapter for use in Server Side Rendering (SSR).
5
+ * This adapter will return empty results for queries, which will allow
6
+ * server rendered views to initially generate scaffolding components
7
+ */
8
+ export declare class SSRDBAdapter extends BaseObserver<DBAdapterListener> implements DBAdapter {
9
+ name: string;
10
+ readMutex: Mutex;
11
+ writeMutex: Mutex;
12
+ constructor();
13
+ close(): void;
14
+ readLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions): Promise<T>;
15
+ readTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions): Promise<T>;
16
+ writeLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions): Promise<T>;
17
+ writeTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions): Promise<T>;
18
+ execute(query: string, params?: any[]): Promise<QueryResult>;
19
+ executeRaw(query: string, params?: any[]): Promise<any[][]>;
20
+ executeBatch(query: string, params?: any[][]): Promise<QueryResult>;
21
+ getAll<T>(sql: string, parameters?: any[]): Promise<T[]>;
22
+ getOptional<T>(sql: string, parameters?: any[] | undefined): Promise<T | null>;
23
+ get<T>(sql: string, parameters?: any[] | undefined): Promise<T>;
24
+ /**
25
+ * Generates a mock context for use in read/write transactions.
26
+ * `this` already mocks most of the API, commit and rollback mocks
27
+ * are added here
28
+ */
29
+ private generateMockTransactionContext;
30
+ refreshSchema(): Promise<void>;
31
+ }