@powersync/web 0.0.0-dev-20251127205344 → 0.0.0-dev-20251201150812
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/1807036ae51c10ee4d23.wasm +0 -0
- package/dist/{10072fe45f0a8fab0a0e.wasm → 307d8ce2280e3bae09d5.wasm} +0 -0
- package/dist/{6e435e51534839845554.wasm → cd8b9e8f4c87bf81c169.wasm} +0 -0
- package/dist/e797080f5ed0b5324166.wasm +0 -0
- package/dist/index.umd.js +336 -177
- package/dist/index.umd.js.map +1 -1
- package/dist/worker/SharedSyncImplementation.umd.js +310 -143
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
- package/dist/worker/WASQLiteDB.umd.js +19 -5
- package/dist/worker/WASQLiteDB.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js +2 -2
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js +2 -2
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js +2 -2
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +2 -2
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js +20 -23
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js.map +1 -1
- package/lib/src/db/PowerSyncDatabase.d.ts +1 -1
- package/lib/src/db/PowerSyncDatabase.js +4 -4
- package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +8 -0
- package/lib/src/db/adapters/AsyncDatabaseConnection.js +11 -1
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +14 -0
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +61 -8
- package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +5 -1
- package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +15 -7
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +1 -4
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +45 -31
- package/lib/src/worker/sync/MockSyncService.d.ts +2 -0
- package/lib/src/worker/sync/MockSyncService.js +3 -0
- package/lib/src/worker/sync/MockSyncServiceTypes.d.ts +101 -0
- package/lib/src/worker/sync/MockSyncServiceTypes.js +1 -0
- package/lib/src/worker/sync/MockSyncServiceWorker.d.ts +56 -0
- package/lib/src/worker/sync/MockSyncServiceWorker.js +369 -0
- package/lib/src/worker/sync/SharedSyncImplementation.d.ts +18 -8
- package/lib/src/worker/sync/SharedSyncImplementation.js +174 -108
- package/lib/src/worker/sync/SharedSyncImplementation.worker.js +1 -1
- package/lib/src/worker/sync/WorkerClient.d.ts +4 -5
- package/lib/src/worker/sync/WorkerClient.js +5 -7
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/db/PowerSyncDatabase.ts +13 -15
- package/src/db/adapters/AsyncDatabaseConnection.ts +12 -0
- package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +71 -9
- package/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts +19 -6
- package/src/db/sync/SharedWebStreamingSyncImplementation.ts +56 -40
- package/src/worker/sync/MockSyncService.ts +3 -0
- package/src/worker/sync/MockSyncServiceTypes.ts +71 -0
- package/src/worker/sync/MockSyncServiceWorker.ts +406 -0
- package/src/worker/sync/SharedSyncImplementation.ts +201 -120
- package/src/worker/sync/SharedSyncImplementation.worker.ts +1 -1
- package/src/worker/sync/WorkerClient.ts +6 -11
- package/dist/a730f7ca717b02234beb.wasm +0 -0
- package/dist/aa2f408d64445fed090e.wasm +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BaseObserver, createLogger } from '@powersync/common';
|
|
2
2
|
import { getNavigatorLocks } from '../..//shared/navigator';
|
|
3
|
+
import { ConnectionClosedError } from './AsyncDatabaseConnection';
|
|
3
4
|
import { WorkerWrappedAsyncDatabaseConnection } from './WorkerWrappedAsyncDatabaseConnection';
|
|
4
5
|
import { WASQLiteVFS } from './wa-sqlite/WASQLiteConnection';
|
|
5
6
|
/**
|
|
@@ -19,6 +20,7 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver {
|
|
|
19
20
|
_config = null;
|
|
20
21
|
pendingAbortControllers;
|
|
21
22
|
requiresHolds;
|
|
23
|
+
databaseOpenPromise = null;
|
|
22
24
|
closing;
|
|
23
25
|
closed;
|
|
24
26
|
constructor(options) {
|
|
@@ -68,17 +70,46 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver {
|
|
|
68
70
|
async init() {
|
|
69
71
|
return this.initPromise;
|
|
70
72
|
}
|
|
71
|
-
async
|
|
73
|
+
async openInternalDB() {
|
|
74
|
+
// Dispose any previous table change listener.
|
|
75
|
+
this._disposeTableChangeListener?.();
|
|
76
|
+
this._disposeTableChangeListener = null;
|
|
77
|
+
const isReOpen = !!this._db;
|
|
72
78
|
this._db = await this.options.openConnection();
|
|
73
79
|
await this._db.init();
|
|
74
80
|
this._config = await this._db.getConfig();
|
|
75
81
|
await this.registerOnChangeListener(this._db);
|
|
76
|
-
|
|
82
|
+
if (isReOpen) {
|
|
83
|
+
this.iterateListeners((cb) => cb.databaseReOpened?.());
|
|
84
|
+
}
|
|
77
85
|
/**
|
|
78
86
|
* This is only required for the long-lived shared IndexedDB connections.
|
|
79
87
|
*/
|
|
80
88
|
this.requiresHolds = this._config.vfs == WASQLiteVFS.IDBBatchAtomicVFS;
|
|
81
89
|
}
|
|
90
|
+
_reOpen() {
|
|
91
|
+
this.databaseOpenPromise = this.openInternalDB().finally(() => {
|
|
92
|
+
this.databaseOpenPromise = null;
|
|
93
|
+
});
|
|
94
|
+
return this.databaseOpenPromise;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Re-opens the underlying database.
|
|
98
|
+
* Returns a pending operation if one is already in progress.
|
|
99
|
+
*/
|
|
100
|
+
async reOpenInternalDB() {
|
|
101
|
+
if (!this.options.reOpenOnConnectionClosed) {
|
|
102
|
+
throw new Error(`Cannot re-open underlying database, reOpenOnConnectionClosed is not enabled`);
|
|
103
|
+
}
|
|
104
|
+
if (this.databaseOpenPromise) {
|
|
105
|
+
return this.databaseOpenPromise;
|
|
106
|
+
}
|
|
107
|
+
return this._reOpen();
|
|
108
|
+
}
|
|
109
|
+
async _init() {
|
|
110
|
+
await this.openInternalDB();
|
|
111
|
+
this.iterateListeners((cb) => cb.initialized?.());
|
|
112
|
+
}
|
|
82
113
|
getConfiguration() {
|
|
83
114
|
if (!this._config) {
|
|
84
115
|
throw new Error(`Cannot get config before initialization is completed`);
|
|
@@ -144,13 +175,13 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver {
|
|
|
144
175
|
async readLock(fn, options) {
|
|
145
176
|
await this.waitForInitialized();
|
|
146
177
|
return this.acquireLock(async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })), {
|
|
147
|
-
timeoutMs: options?.timeoutMs
|
|
178
|
+
timeoutMs: options?.timeoutMs ?? this.options.defaultLockTimeoutMs
|
|
148
179
|
});
|
|
149
180
|
}
|
|
150
181
|
async writeLock(fn, options) {
|
|
151
182
|
await this.waitForInitialized();
|
|
152
183
|
return this.acquireLock(async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })), {
|
|
153
|
-
timeoutMs: options?.timeoutMs
|
|
184
|
+
timeoutMs: options?.timeoutMs ?? this.options.defaultLockTimeoutMs
|
|
154
185
|
});
|
|
155
186
|
}
|
|
156
187
|
async acquireLock(callback, options) {
|
|
@@ -161,7 +192,7 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver {
|
|
|
161
192
|
const abortController = new AbortController();
|
|
162
193
|
this.pendingAbortControllers.add(abortController);
|
|
163
194
|
const { timeoutMs } = options ?? {};
|
|
164
|
-
const
|
|
195
|
+
const timeoutId = timeoutMs
|
|
165
196
|
? setTimeout(() => {
|
|
166
197
|
abortController.abort(`Timeout after ${timeoutMs}ms`);
|
|
167
198
|
this.pendingAbortControllers.delete(abortController);
|
|
@@ -169,13 +200,35 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver {
|
|
|
169
200
|
: null;
|
|
170
201
|
return getNavigatorLocks().request(`db-lock-${this._dbIdentifier}`, { signal: abortController.signal }, async () => {
|
|
171
202
|
this.pendingAbortControllers.delete(abortController);
|
|
172
|
-
if (
|
|
173
|
-
clearTimeout(
|
|
203
|
+
if (timeoutId) {
|
|
204
|
+
clearTimeout(timeoutId);
|
|
174
205
|
}
|
|
175
|
-
|
|
206
|
+
let holdId = null;
|
|
176
207
|
try {
|
|
208
|
+
// The database is being opened in the background. Wait for it here.
|
|
209
|
+
if (this.databaseOpenPromise) {
|
|
210
|
+
try {
|
|
211
|
+
await this.databaseOpenPromise;
|
|
212
|
+
}
|
|
213
|
+
catch (ex) {
|
|
214
|
+
// This will cause a retry of opening the database.
|
|
215
|
+
const wrappedError = new ConnectionClosedError('Could not open database');
|
|
216
|
+
wrappedError.cause = ex;
|
|
217
|
+
throw wrappedError;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
holdId = this.requiresHolds ? await this.baseDB.markHold() : null;
|
|
177
221
|
return await callback();
|
|
178
222
|
}
|
|
223
|
+
catch (ex) {
|
|
224
|
+
if (ex instanceof ConnectionClosedError) {
|
|
225
|
+
if (this.options.reOpenOnConnectionClosed && !this.databaseOpenPromise && !this.closing) {
|
|
226
|
+
// Immediately re-open the database. We need to miss as little table updates as possible.
|
|
227
|
+
this.reOpenInternalDB();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
throw ex;
|
|
231
|
+
}
|
|
179
232
|
finally {
|
|
180
233
|
if (holdId) {
|
|
181
234
|
await this.baseDB.releaseHold(holdId);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BaseObserver } from '@powersync/common';
|
|
1
2
|
import * as Comlink from 'comlink';
|
|
2
3
|
import { AsyncDatabaseConnection, OnTableChangeCallback, OpenAsyncDatabaseConnection, ProxiedQueryResult } from './AsyncDatabaseConnection';
|
|
3
4
|
import { ResolvedWebSQLOpenOptions } from './web-sql-flags';
|
|
@@ -15,11 +16,14 @@ export type WrappedWorkerConnectionOptions<Config extends ResolvedWebSQLOpenOpti
|
|
|
15
16
|
remote: Comlink.Remote<OpenAsyncDatabaseConnection<Config>>;
|
|
16
17
|
onClose?: () => void;
|
|
17
18
|
};
|
|
19
|
+
export type WorkerWrappedAsyncDatabaseConnectionListener = {
|
|
20
|
+
closing: () => void;
|
|
21
|
+
};
|
|
18
22
|
/**
|
|
19
23
|
* Wraps a provided instance of {@link AsyncDatabaseConnection}, providing necessary proxy
|
|
20
24
|
* functions for worker listeners.
|
|
21
25
|
*/
|
|
22
|
-
export declare class WorkerWrappedAsyncDatabaseConnection<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions> implements AsyncDatabaseConnection {
|
|
26
|
+
export declare class WorkerWrappedAsyncDatabaseConnection<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions> extends BaseObserver<WorkerWrappedAsyncDatabaseConnectionListener> implements AsyncDatabaseConnection {
|
|
23
27
|
protected options: WrappedWorkerConnectionOptions<Config>;
|
|
24
28
|
protected lockAbortController: AbortController;
|
|
25
29
|
protected notifyRemoteClosed: AbortController | undefined;
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import { BaseObserver } from '@powersync/common';
|
|
1
2
|
import * as Comlink from 'comlink';
|
|
3
|
+
import { ConnectionClosedError } from './AsyncDatabaseConnection';
|
|
2
4
|
/**
|
|
3
5
|
* Wraps a provided instance of {@link AsyncDatabaseConnection}, providing necessary proxy
|
|
4
6
|
* functions for worker listeners.
|
|
5
7
|
*/
|
|
6
|
-
export class WorkerWrappedAsyncDatabaseConnection {
|
|
8
|
+
export class WorkerWrappedAsyncDatabaseConnection extends BaseObserver {
|
|
7
9
|
options;
|
|
8
10
|
lockAbortController = new AbortController();
|
|
9
11
|
notifyRemoteClosed;
|
|
10
12
|
constructor(options) {
|
|
13
|
+
super();
|
|
11
14
|
this.options = options;
|
|
12
15
|
if (options.remoteCanCloseUnexpectedly) {
|
|
13
16
|
this.notifyRemoteClosed = new AbortController();
|
|
@@ -40,17 +43,20 @@ export class WorkerWrappedAsyncDatabaseConnection {
|
|
|
40
43
|
isAutoCommit() {
|
|
41
44
|
return this.withRemote(() => this.baseConnection.isAutoCommit());
|
|
42
45
|
}
|
|
43
|
-
withRemote(workerPromise) {
|
|
46
|
+
withRemote(workerPromise, fireActionOnAbort = false) {
|
|
44
47
|
const controller = this.notifyRemoteClosed;
|
|
45
48
|
if (controller) {
|
|
46
49
|
return new Promise((resolve, reject) => {
|
|
47
50
|
if (controller.signal.aborted) {
|
|
48
|
-
reject(new
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
reject(new ConnectionClosedError('Called operation on closed remote'));
|
|
52
|
+
if (!fireActionOnAbort) {
|
|
53
|
+
// Don't run the operation if we're going to reject
|
|
54
|
+
// We might want to fire-and-forget the operation in some cases (like a close operation)
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
51
57
|
}
|
|
52
58
|
function handleAbort() {
|
|
53
|
-
reject(new
|
|
59
|
+
reject(new ConnectionClosedError('Remote peer closed with request in flight'));
|
|
54
60
|
}
|
|
55
61
|
function completePromise(action) {
|
|
56
62
|
controller.signal.removeEventListener('abort', handleAbort);
|
|
@@ -118,11 +124,13 @@ export class WorkerWrappedAsyncDatabaseConnection {
|
|
|
118
124
|
// Abort any pending lock requests.
|
|
119
125
|
this.lockAbortController.abort();
|
|
120
126
|
try {
|
|
121
|
-
|
|
127
|
+
// fire and forget the close operation
|
|
128
|
+
await this.withRemote(() => this.baseConnection.close(), true);
|
|
122
129
|
}
|
|
123
130
|
finally {
|
|
124
131
|
this.options.remote[Comlink.releaseProxy]();
|
|
125
132
|
this.options.onClose?.();
|
|
133
|
+
this.iterateListeners((l) => l.closing?.());
|
|
126
134
|
}
|
|
127
135
|
}
|
|
128
136
|
execute(sql, params) {
|
|
@@ -41,6 +41,7 @@ export declare class SharedWebStreamingSyncImplementation extends WebStreamingSy
|
|
|
41
41
|
protected dbAdapter: WebDBAdapter;
|
|
42
42
|
private abortOnClose;
|
|
43
43
|
constructor(options: SharedWebStreamingSyncImplementationOptions);
|
|
44
|
+
protected _init(): Promise<void>;
|
|
44
45
|
/**
|
|
45
46
|
* Starts the sync process, this effectively acts as a call to
|
|
46
47
|
* `connect` if not yet connected.
|
|
@@ -52,9 +53,5 @@ export declare class SharedWebStreamingSyncImplementation extends WebStreamingSy
|
|
|
52
53
|
dispose(): Promise<void>;
|
|
53
54
|
waitForReady(): Promise<void>;
|
|
54
55
|
updateSubscriptions(subscriptions: SubscribedStream[]): void;
|
|
55
|
-
/**
|
|
56
|
-
* Used in tests to force a connection states
|
|
57
|
-
*/
|
|
58
|
-
private _testUpdateStatus;
|
|
59
56
|
}
|
|
60
57
|
export {};
|
|
@@ -119,7 +119,19 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
|
|
|
119
119
|
type: 'module'
|
|
120
120
|
}).port;
|
|
121
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Pass along any sync status updates to this listener
|
|
124
|
+
*/
|
|
125
|
+
this.clientProvider = new SharedSyncClientProvider(this.webOptions, (status) => {
|
|
126
|
+
this.updateSyncStatus(status);
|
|
127
|
+
}, options.db);
|
|
122
128
|
this.syncManager = Comlink.wrap(this.messagePort);
|
|
129
|
+
/**
|
|
130
|
+
* The sync worker will call this client provider when it needs
|
|
131
|
+
* to fetch credentials or upload data.
|
|
132
|
+
* This performs bi-directional method calling.
|
|
133
|
+
*/
|
|
134
|
+
Comlink.expose(this.clientProvider, this.messagePort);
|
|
123
135
|
this.syncManager.setLogLevel(this.logger.getLevel());
|
|
124
136
|
this.triggerCrudUpload = this.syncManager.triggerCrudUpload;
|
|
125
137
|
/**
|
|
@@ -128,22 +140,43 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
|
|
|
128
140
|
* DB worker, but a port to the DB worker can be transferred to the
|
|
129
141
|
* sync worker.
|
|
130
142
|
*/
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
143
|
+
this.isInitialized = this._init();
|
|
144
|
+
}
|
|
145
|
+
async _init() {
|
|
146
|
+
/**
|
|
147
|
+
* The general flow of initialization is:
|
|
148
|
+
* - The client requests a unique navigator lock.
|
|
149
|
+
* - Once the lock is acquired, we register the lock with the shared worker.
|
|
150
|
+
* - The shared worker can then request the same lock. The client has been closed if the shared worker can acquire the lock.
|
|
151
|
+
* - Once the shared worker knows the client's lock, we can guarentee that the shared worker will detect if the client has been closed.
|
|
152
|
+
* - This makes the client safe for the shared worker to use.
|
|
153
|
+
* - The client is only added to the SharedSyncImplementation once the lock has been registered.
|
|
154
|
+
* This ensures we don't ever keep track of dead clients (tabs that closed before the lock was registered).
|
|
155
|
+
* - The client side lock is held until the client is disposed.
|
|
156
|
+
* - We resolve the top-level promise after the lock has been registered with the shared worker.
|
|
157
|
+
* - The client sends the params to the shared worker after locks have been registered.
|
|
158
|
+
*/
|
|
159
|
+
await new Promise((resolve) => {
|
|
160
|
+
// Request a random lock until this client is disposed. The name of the lock is sent to the shared worker, which
|
|
161
|
+
// will also attempt to acquire it. Since the lock is returned when the tab is closed, this allows the share worker
|
|
162
|
+
// to free resources associated with this tab.
|
|
163
|
+
// We take hold of this lock as soon-as-possible in order to cater for potentially closed tabs.
|
|
164
|
+
getNavigatorLocks().request(`tab-close-signal-${crypto.randomUUID()}`, async (lock) => {
|
|
165
|
+
if (this.abortOnClose.signal.aborted) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
139
168
|
// Awaiting here ensures the worker is waiting for the lock
|
|
140
169
|
await this.syncManager.addLockBasedCloseSignal(lock.name);
|
|
170
|
+
// The lock has been registered, we can continue with the initialization
|
|
171
|
+
resolve();
|
|
141
172
|
await new Promise((r) => {
|
|
142
173
|
this.abortOnClose.signal.onabort = () => r();
|
|
143
174
|
});
|
|
144
|
-
}
|
|
175
|
+
});
|
|
145
176
|
});
|
|
146
|
-
|
|
177
|
+
const { crudUploadThrottleMs, identifier, retryDelayMs } = this.options;
|
|
178
|
+
const flags = { ...this.webOptions.flags, workers: undefined };
|
|
179
|
+
await this.syncManager.setParams({
|
|
147
180
|
dbParams: this.dbAdapter.getConfiguration(),
|
|
148
181
|
streamOptions: {
|
|
149
182
|
crudUploadThrottleMs,
|
|
@@ -151,19 +184,7 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
|
|
|
151
184
|
retryDelayMs,
|
|
152
185
|
flags: flags
|
|
153
186
|
}
|
|
154
|
-
}, options.subscriptions);
|
|
155
|
-
/**
|
|
156
|
-
* Pass along any sync status updates to this listener
|
|
157
|
-
*/
|
|
158
|
-
this.clientProvider = new SharedSyncClientProvider(this.webOptions, (status) => {
|
|
159
|
-
this.iterateListeners((l) => this.updateSyncStatus(status));
|
|
160
|
-
}, options.db);
|
|
161
|
-
/**
|
|
162
|
-
* The sync worker will call this client provider when it needs
|
|
163
|
-
* to fetch credentials or upload data.
|
|
164
|
-
* This performs bi-directional method calling.
|
|
165
|
-
*/
|
|
166
|
-
Comlink.expose(this.clientProvider, this.messagePort);
|
|
187
|
+
}, this.options.subscriptions);
|
|
167
188
|
}
|
|
168
189
|
/**
|
|
169
190
|
* Starts the sync process, this effectively acts as a call to
|
|
@@ -186,7 +207,6 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
|
|
|
186
207
|
}
|
|
187
208
|
async dispose() {
|
|
188
209
|
await this.waitForReady();
|
|
189
|
-
await super.dispose();
|
|
190
210
|
await new Promise((resolve) => {
|
|
191
211
|
// Listen for the close acknowledgment from the worker
|
|
192
212
|
this.messagePort.addEventListener('message', (event) => {
|
|
@@ -202,6 +222,7 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
|
|
|
202
222
|
};
|
|
203
223
|
this.messagePort.postMessage(closeMessagePayload);
|
|
204
224
|
});
|
|
225
|
+
await super.dispose();
|
|
205
226
|
this.abortOnClose.abort();
|
|
206
227
|
// Release the proxy
|
|
207
228
|
this.syncManager[Comlink.releaseProxy]();
|
|
@@ -213,11 +234,4 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
|
|
|
213
234
|
updateSubscriptions(subscriptions) {
|
|
214
235
|
this.syncManager.updateSubscriptions(subscriptions);
|
|
215
236
|
}
|
|
216
|
-
/**
|
|
217
|
-
* Used in tests to force a connection states
|
|
218
|
-
*/
|
|
219
|
-
async _testUpdateStatus(status) {
|
|
220
|
-
await this.isInitialized;
|
|
221
|
-
return this.syncManager._testUpdateAllStatuses(status.toJSON());
|
|
222
|
-
}
|
|
223
237
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Representation of a pending request
|
|
3
|
+
*/
|
|
4
|
+
export interface PendingRequest {
|
|
5
|
+
id: string;
|
|
6
|
+
url: string;
|
|
7
|
+
method: string;
|
|
8
|
+
headers: Record<string, string>;
|
|
9
|
+
body: any;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Automatic response configuration
|
|
13
|
+
*/
|
|
14
|
+
export interface AutomaticResponseConfig {
|
|
15
|
+
status: number;
|
|
16
|
+
headers: Record<string, string>;
|
|
17
|
+
bodyLines?: any[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Message types for communication via MessagePort
|
|
21
|
+
*/
|
|
22
|
+
export type MockSyncServiceMessage = {
|
|
23
|
+
type: 'getPendingRequests';
|
|
24
|
+
requestId: string;
|
|
25
|
+
} | {
|
|
26
|
+
type: 'createResponse';
|
|
27
|
+
requestId: string;
|
|
28
|
+
pendingRequestId: string;
|
|
29
|
+
status: number;
|
|
30
|
+
headers: Record<string, string>;
|
|
31
|
+
} | {
|
|
32
|
+
type: 'pushBodyData';
|
|
33
|
+
requestId: string;
|
|
34
|
+
pendingRequestId: string;
|
|
35
|
+
data: string | ArrayBuffer | Uint8Array;
|
|
36
|
+
} | {
|
|
37
|
+
type: 'completeResponse';
|
|
38
|
+
requestId: string;
|
|
39
|
+
pendingRequestId: string;
|
|
40
|
+
} | {
|
|
41
|
+
type: 'setAutomaticResponse';
|
|
42
|
+
requestId: string;
|
|
43
|
+
config: AutomaticResponseConfig | null;
|
|
44
|
+
} | {
|
|
45
|
+
type: 'replyToAllPendingRequests';
|
|
46
|
+
requestId: string;
|
|
47
|
+
};
|
|
48
|
+
export type MockSyncServiceResponse = {
|
|
49
|
+
type: 'getPendingRequests';
|
|
50
|
+
requestId: string;
|
|
51
|
+
requests: PendingRequest[];
|
|
52
|
+
} | {
|
|
53
|
+
type: 'createResponse';
|
|
54
|
+
requestId: string;
|
|
55
|
+
success: boolean;
|
|
56
|
+
} | {
|
|
57
|
+
type: 'pushBodyData';
|
|
58
|
+
requestId: string;
|
|
59
|
+
success: boolean;
|
|
60
|
+
} | {
|
|
61
|
+
type: 'completeResponse';
|
|
62
|
+
requestId: string;
|
|
63
|
+
success: boolean;
|
|
64
|
+
} | {
|
|
65
|
+
type: 'setAutomaticResponse';
|
|
66
|
+
requestId: string;
|
|
67
|
+
success: boolean;
|
|
68
|
+
} | {
|
|
69
|
+
type: 'replyToAllPendingRequests';
|
|
70
|
+
requestId: string;
|
|
71
|
+
success: boolean;
|
|
72
|
+
count: number;
|
|
73
|
+
} | {
|
|
74
|
+
type: 'error';
|
|
75
|
+
requestId?: string;
|
|
76
|
+
error: string;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Internal representation of a pending request with response promise
|
|
80
|
+
*/
|
|
81
|
+
export interface PendingRequestInternal {
|
|
82
|
+
id: string;
|
|
83
|
+
url: string;
|
|
84
|
+
method: string;
|
|
85
|
+
headers: Record<string, string>;
|
|
86
|
+
body: any;
|
|
87
|
+
responsePromise: {
|
|
88
|
+
resolve: (response: Response) => void;
|
|
89
|
+
reject: (error: Error) => void;
|
|
90
|
+
};
|
|
91
|
+
streamController?: ReadableStreamDefaultController<Uint8Array>;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Internal representation of an active response
|
|
95
|
+
*/
|
|
96
|
+
export interface ActiveResponse {
|
|
97
|
+
id: string;
|
|
98
|
+
status: number;
|
|
99
|
+
headers: Record<string, string>;
|
|
100
|
+
stream: ReadableStreamDefaultController<Uint8Array>;
|
|
101
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { AutomaticResponseConfig, PendingRequest } from './MockSyncServiceTypes';
|
|
2
|
+
/**
|
|
3
|
+
* Mock sync service implementation for shared worker environments.
|
|
4
|
+
* This allows tests to mock sync responses when using enableMultipleTabs: true.
|
|
5
|
+
* Requests are kept pending until a client explicitly creates a response.
|
|
6
|
+
*/
|
|
7
|
+
export declare class MockSyncService {
|
|
8
|
+
private pendingRequests;
|
|
9
|
+
private activeResponses;
|
|
10
|
+
private nextId;
|
|
11
|
+
private automaticResponse;
|
|
12
|
+
/**
|
|
13
|
+
* A Static instance of the mock sync service.
|
|
14
|
+
* This can be used directly for non-worker environments.
|
|
15
|
+
* A proxy is required for worker environments.
|
|
16
|
+
*/
|
|
17
|
+
static readonly GLOBAL_INSTANCE: MockSyncService;
|
|
18
|
+
/**
|
|
19
|
+
* Register a new pending request (called by WebRemote when a sync stream is requested).
|
|
20
|
+
* Returns a promise that resolves when a client creates a response for this request.
|
|
21
|
+
*/
|
|
22
|
+
registerPendingRequest(url: string, method: string, headers: Record<string, string>, body: any, signal?: AbortSignal): Promise<Response>;
|
|
23
|
+
/**
|
|
24
|
+
* Get all pending requests
|
|
25
|
+
*/
|
|
26
|
+
getPendingRequestsSync(): PendingRequest[];
|
|
27
|
+
/**
|
|
28
|
+
* Create a response for a pending request.
|
|
29
|
+
* This resolves the response promise and allows pushing body lines.
|
|
30
|
+
*/
|
|
31
|
+
createResponse(pendingRequestId: string, status: number, headers: Record<string, string>): void;
|
|
32
|
+
/**
|
|
33
|
+
* Push body data to an active response.
|
|
34
|
+
* Accepts either text (string) or binary data (ArrayBuffer or Uint8Array).
|
|
35
|
+
* All data is encoded to Uint8Array before enqueueing (required by ReadableStream<Uint8Array>).
|
|
36
|
+
*/
|
|
37
|
+
pushBodyData(pendingRequestId: string, data: string | ArrayBuffer | Uint8Array): void;
|
|
38
|
+
/**
|
|
39
|
+
* Complete an active response (close the stream)
|
|
40
|
+
*/
|
|
41
|
+
completeResponse(pendingRequestId: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* Set the automatic response configuration.
|
|
44
|
+
* When set, this will be used to automatically reply to all pending requests.
|
|
45
|
+
*/
|
|
46
|
+
setAutomaticResponse(config: AutomaticResponseConfig | null): void;
|
|
47
|
+
/**
|
|
48
|
+
* Automatically reply to all pending requests using the automatic response configuration.
|
|
49
|
+
* Returns the number of requests that were replied to.
|
|
50
|
+
*/
|
|
51
|
+
replyToAllPendingRequests(): number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Set up message handler for the mock service on a MessagePort
|
|
55
|
+
*/
|
|
56
|
+
export declare function setupMockServiceMessageHandler(port: MessagePort): void;
|