@powersync/web 0.0.0-dev-20251129133952 → 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.
Files changed (56) hide show
  1. package/dist/1807036ae51c10ee4d23.wasm +0 -0
  2. package/dist/{10072fe45f0a8fab0a0e.wasm → 307d8ce2280e3bae09d5.wasm} +0 -0
  3. package/dist/{6e435e51534839845554.wasm → cd8b9e8f4c87bf81c169.wasm} +0 -0
  4. package/dist/e797080f5ed0b5324166.wasm +0 -0
  5. package/dist/index.umd.js +137 -104
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/worker/SharedSyncImplementation.umd.js +137 -110
  8. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  9. package/dist/worker/WASQLiteDB.umd.js +15 -1
  10. package/dist/worker/WASQLiteDB.umd.js.map +1 -1
  11. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js +2 -2
  12. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js.map +1 -1
  13. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js +2 -2
  14. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js.map +1 -1
  15. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js +2 -2
  16. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js.map +1 -1
  17. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +2 -2
  18. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map +1 -1
  19. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js +20 -23
  20. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js.map +1 -1
  21. package/lib/src/db/PowerSyncDatabase.d.ts +1 -1
  22. package/lib/src/db/PowerSyncDatabase.js +4 -4
  23. package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +5 -0
  24. package/lib/src/db/adapters/AsyncDatabaseConnection.js +5 -0
  25. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +6 -1
  26. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +20 -5
  27. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +5 -1
  28. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +12 -5
  29. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +0 -4
  30. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +3 -8
  31. package/lib/src/worker/sync/MockSyncService.d.ts +2 -0
  32. package/lib/src/worker/sync/MockSyncService.js +3 -0
  33. package/lib/src/worker/sync/MockSyncServiceTypes.d.ts +101 -0
  34. package/lib/src/worker/sync/MockSyncServiceTypes.js +1 -0
  35. package/lib/src/worker/sync/MockSyncServiceWorker.d.ts +56 -0
  36. package/lib/src/worker/sync/MockSyncServiceWorker.js +369 -0
  37. package/lib/src/worker/sync/SharedSyncImplementation.d.ts +6 -11
  38. package/lib/src/worker/sync/SharedSyncImplementation.js +73 -64
  39. package/lib/src/worker/sync/SharedSyncImplementation.worker.js +1 -1
  40. package/lib/src/worker/sync/WorkerClient.d.ts +1 -3
  41. package/lib/src/worker/sync/WorkerClient.js +3 -27
  42. package/lib/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +3 -3
  44. package/src/db/PowerSyncDatabase.ts +13 -15
  45. package/src/db/adapters/AsyncDatabaseConnection.ts +5 -0
  46. package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +22 -5
  47. package/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts +16 -4
  48. package/src/db/sync/SharedWebStreamingSyncImplementation.ts +5 -11
  49. package/src/worker/sync/MockSyncService.ts +3 -0
  50. package/src/worker/sync/MockSyncServiceTypes.ts +71 -0
  51. package/src/worker/sync/MockSyncServiceWorker.ts +406 -0
  52. package/src/worker/sync/SharedSyncImplementation.ts +85 -78
  53. package/src/worker/sync/SharedSyncImplementation.worker.ts +1 -1
  54. package/src/worker/sync/WorkerClient.ts +4 -30
  55. package/dist/a730f7ca717b02234beb.wasm +0 -0
  56. package/dist/aa2f408d64445fed090e.wasm +0 -0
@@ -1,14 +1,16 @@
1
+ import { BaseObserver } from '@powersync/common';
1
2
  import * as Comlink from 'comlink';
2
3
  import { ConnectionClosedError } from './AsyncDatabaseConnection';
3
4
  /**
4
5
  * Wraps a provided instance of {@link AsyncDatabaseConnection}, providing necessary proxy
5
6
  * functions for worker listeners.
6
7
  */
7
- export class WorkerWrappedAsyncDatabaseConnection {
8
+ export class WorkerWrappedAsyncDatabaseConnection extends BaseObserver {
8
9
  options;
9
10
  lockAbortController = new AbortController();
10
11
  notifyRemoteClosed;
11
12
  constructor(options) {
13
+ super();
12
14
  this.options = options;
13
15
  if (options.remoteCanCloseUnexpectedly) {
14
16
  this.notifyRemoteClosed = new AbortController();
@@ -41,14 +43,17 @@ export class WorkerWrappedAsyncDatabaseConnection {
41
43
  isAutoCommit() {
42
44
  return this.withRemote(() => this.baseConnection.isAutoCommit());
43
45
  }
44
- withRemote(workerPromise) {
46
+ withRemote(workerPromise, fireActionOnAbort = false) {
45
47
  const controller = this.notifyRemoteClosed;
46
48
  if (controller) {
47
49
  return new Promise((resolve, reject) => {
48
50
  if (controller.signal.aborted) {
49
51
  reject(new ConnectionClosedError('Called operation on closed remote'));
50
- // Don't run the operation if we're going to reject
51
- return;
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
+ }
52
57
  }
53
58
  function handleAbort() {
54
59
  reject(new ConnectionClosedError('Remote peer closed with request in flight'));
@@ -119,11 +124,13 @@ export class WorkerWrappedAsyncDatabaseConnection {
119
124
  // Abort any pending lock requests.
120
125
  this.lockAbortController.abort();
121
126
  try {
122
- await this.withRemote(() => this.baseConnection.close());
127
+ // fire and forget the close operation
128
+ await this.withRemote(() => this.baseConnection.close(), true);
123
129
  }
124
130
  finally {
125
131
  this.options.remote[Comlink.releaseProxy]();
126
132
  this.options.onClose?.();
133
+ this.iterateListeners((l) => l.closing?.());
127
134
  }
128
135
  }
129
136
  execute(sql, params) {
@@ -53,9 +53,5 @@ export declare class SharedWebStreamingSyncImplementation extends WebStreamingSy
53
53
  dispose(): Promise<void>;
54
54
  waitForReady(): Promise<void>;
55
55
  updateSubscriptions(subscriptions: SubscribedStream[]): void;
56
- /**
57
- * Used in tests to force a connection states
58
- */
59
- private _testUpdateStatus;
60
56
  }
61
57
  export {};
@@ -150,6 +150,8 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
150
150
  * - The shared worker can then request the same lock. The client has been closed if the shared worker can acquire the lock.
151
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
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).
153
155
  * - The client side lock is held until the client is disposed.
154
156
  * - We resolve the top-level promise after the lock has been registered with the shared worker.
155
157
  * - The client sends the params to the shared worker after locks have been registered.
@@ -205,7 +207,6 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
205
207
  }
206
208
  async dispose() {
207
209
  await this.waitForReady();
208
- await super.dispose();
209
210
  await new Promise((resolve) => {
210
211
  // Listen for the close acknowledgment from the worker
211
212
  this.messagePort.addEventListener('message', (event) => {
@@ -221,6 +222,7 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
221
222
  };
222
223
  this.messagePort.postMessage(closeMessagePayload);
223
224
  });
225
+ await super.dispose();
224
226
  this.abortOnClose.abort();
225
227
  // Release the proxy
226
228
  this.syncManager[Comlink.releaseProxy]();
@@ -232,11 +234,4 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
232
234
  updateSubscriptions(subscriptions) {
233
235
  this.syncManager.updateSubscriptions(subscriptions);
234
236
  }
235
- /**
236
- * Used in tests to force a connection states
237
- */
238
- async _testUpdateStatus(status) {
239
- await this.isInitialized;
240
- return this.syncManager._testUpdateAllStatuses(status.toJSON());
241
- }
242
237
  }
@@ -0,0 +1,2 @@
1
+ export * from './MockSyncServiceTypes';
2
+ export * from './MockSyncServiceWorker';
@@ -0,0 +1,3 @@
1
+ // Re-export types and worker-side implementation
2
+ export * from './MockSyncServiceTypes';
3
+ export * from './MockSyncServiceWorker';
@@ -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;
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Mock sync service implementation for shared worker environments.
3
+ * This allows tests to mock sync responses when using enableMultipleTabs: true.
4
+ * Requests are kept pending until a client explicitly creates a response.
5
+ */
6
+ export class MockSyncService {
7
+ pendingRequests = new Map();
8
+ activeResponses = new Map();
9
+ nextId = 0;
10
+ automaticResponse = null;
11
+ /**
12
+ * A Static instance of the mock sync service.
13
+ * This can be used directly for non-worker environments.
14
+ * A proxy is required for worker environments.
15
+ */
16
+ static GLOBAL_INSTANCE = new MockSyncService();
17
+ /**
18
+ * Register a new pending request (called by WebRemote when a sync stream is requested).
19
+ * Returns a promise that resolves when a client creates a response for this request.
20
+ */
21
+ registerPendingRequest(url, method, headers, body, signal) {
22
+ const id = `pending-${++this.nextId}`;
23
+ let resolveResponse;
24
+ let rejectResponse;
25
+ const responsePromise = new Promise((resolve, reject) => {
26
+ resolveResponse = resolve;
27
+ rejectResponse = reject;
28
+ });
29
+ const pendingRequest = {
30
+ id,
31
+ url,
32
+ method,
33
+ headers,
34
+ body,
35
+ responsePromise: {
36
+ resolve: resolveResponse,
37
+ reject: rejectResponse
38
+ }
39
+ };
40
+ this.pendingRequests.set(id, pendingRequest);
41
+ signal?.addEventListener('abort', () => {
42
+ this.pendingRequests.delete(id);
43
+ rejectResponse(new Error('Request aborted'));
44
+ // if already in active responses, remove it
45
+ if (this.activeResponses.has(id)) {
46
+ const response = this.activeResponses.get(id);
47
+ if (response) {
48
+ response.stream.close();
49
+ }
50
+ this.activeResponses.delete(id);
51
+ }
52
+ });
53
+ // If automatic response is configured, apply it immediately
54
+ if (this.automaticResponse) {
55
+ // Use setTimeout to ensure the response is created asynchronously
56
+ // This prevents issues if the response creation happens synchronously
57
+ setTimeout(() => {
58
+ try {
59
+ // Create response with automatic config
60
+ this.createResponse(id, this.automaticResponse.status, this.automaticResponse.headers);
61
+ // Push body lines if provided
62
+ if (this.automaticResponse.bodyLines) {
63
+ for (const line of this.automaticResponse.bodyLines) {
64
+ const lineStr = `${JSON.stringify(line)}\n`;
65
+ const encoder = new TextEncoder();
66
+ this.pushBodyData(id, encoder.encode(lineStr));
67
+ }
68
+ }
69
+ // Complete the response
70
+ this.completeResponse(id);
71
+ }
72
+ catch (e) {
73
+ // If automatic response fails, reject the promise
74
+ rejectResponse(e instanceof Error ? e : new Error(String(e)));
75
+ }
76
+ }, 0);
77
+ }
78
+ // Return the promise - it will resolve when createResponse is called (or immediately if auto-response is set)
79
+ return responsePromise;
80
+ }
81
+ /**
82
+ * Get all pending requests
83
+ */
84
+ getPendingRequestsSync() {
85
+ return Array.from(this.pendingRequests.values()).map((pr) => ({
86
+ id: pr.id,
87
+ url: pr.url,
88
+ method: pr.method,
89
+ headers: pr.headers,
90
+ body: pr.body
91
+ }));
92
+ }
93
+ /**
94
+ * Create a response for a pending request.
95
+ * This resolves the response promise and allows pushing body lines.
96
+ */
97
+ createResponse(pendingRequestId, status, headers) {
98
+ const pendingRequest = this.pendingRequests.get(pendingRequestId);
99
+ if (!pendingRequest) {
100
+ throw new Error(`Pending request ${pendingRequestId} not found`);
101
+ }
102
+ // Create a readable stream that the mock service can control
103
+ // Response.body is always ReadableStream<Uint8Array>, so we use Uint8Array
104
+ const stream = new ReadableStream({
105
+ start: (controller) => {
106
+ // Store the active response once the controller is available
107
+ // The start callback is called synchronously, so this is safe
108
+ const activeResponse = {
109
+ id: pendingRequestId,
110
+ status,
111
+ headers,
112
+ stream: controller
113
+ };
114
+ this.activeResponses.set(pendingRequestId, activeResponse);
115
+ },
116
+ cancel: () => {
117
+ // Remove response when stream is cancelled
118
+ this.activeResponses.delete(pendingRequestId);
119
+ this.pendingRequests.delete(pendingRequestId);
120
+ }
121
+ });
122
+ // Create the Response object
123
+ const response = new Response(stream, {
124
+ status,
125
+ headers
126
+ });
127
+ // Resolve the pending request's promise
128
+ pendingRequest.responsePromise.resolve(response);
129
+ // Remove from pending (it's now active)
130
+ this.pendingRequests.delete(pendingRequestId);
131
+ }
132
+ /**
133
+ * Push body data to an active response.
134
+ * Accepts either text (string) or binary data (ArrayBuffer or Uint8Array).
135
+ * All data is encoded to Uint8Array before enqueueing (required by ReadableStream<Uint8Array>).
136
+ */
137
+ pushBodyData(pendingRequestId, data) {
138
+ const activeResponse = this.activeResponses.get(pendingRequestId);
139
+ if (!activeResponse) {
140
+ throw new Error(`Active response ${pendingRequestId} not found`);
141
+ }
142
+ try {
143
+ let encoded;
144
+ if (typeof data === 'string') {
145
+ // Encode string to Uint8Array (required by ReadableStream<Uint8Array>)
146
+ const encoder = new TextEncoder();
147
+ encoded = encoder.encode(data);
148
+ }
149
+ else if (data instanceof ArrayBuffer) {
150
+ // Convert ArrayBuffer to Uint8Array
151
+ encoded = new Uint8Array(data);
152
+ }
153
+ else {
154
+ // Already Uint8Array, use directly
155
+ encoded = data;
156
+ }
157
+ activeResponse.stream.enqueue(encoded);
158
+ }
159
+ catch (e) {
160
+ // Stream might be closed, remove it
161
+ this.activeResponses.delete(pendingRequestId);
162
+ throw new Error(`Failed to push data to response ${pendingRequestId}: ${e}`);
163
+ }
164
+ }
165
+ /**
166
+ * Complete an active response (close the stream)
167
+ */
168
+ completeResponse(pendingRequestId) {
169
+ const activeResponse = this.activeResponses.get(pendingRequestId);
170
+ if (!activeResponse) {
171
+ throw new Error(`Active response ${pendingRequestId} not found`);
172
+ }
173
+ try {
174
+ activeResponse.stream.close();
175
+ }
176
+ catch (e) {
177
+ // Stream might already be closed
178
+ }
179
+ finally {
180
+ this.activeResponses.delete(pendingRequestId);
181
+ }
182
+ }
183
+ /**
184
+ * Set the automatic response configuration.
185
+ * When set, this will be used to automatically reply to all pending requests.
186
+ */
187
+ setAutomaticResponse(config) {
188
+ this.automaticResponse = config;
189
+ }
190
+ /**
191
+ * Automatically reply to all pending requests using the automatic response configuration.
192
+ * Returns the number of requests that were replied to.
193
+ */
194
+ replyToAllPendingRequests() {
195
+ if (!this.automaticResponse) {
196
+ throw new Error('Automatic response not set. Call setAutomaticResponse first.');
197
+ }
198
+ const pendingRequestIds = Array.from(this.pendingRequests.keys());
199
+ let count = 0;
200
+ for (const requestId of pendingRequestIds) {
201
+ try {
202
+ // Create response with automatic config
203
+ this.createResponse(requestId, this.automaticResponse.status, this.automaticResponse.headers);
204
+ // Push body lines if provided
205
+ if (this.automaticResponse.bodyLines) {
206
+ for (const line of this.automaticResponse.bodyLines) {
207
+ const lineStr = `${JSON.stringify(line)}\n`;
208
+ const encoder = new TextEncoder();
209
+ this.pushBodyData(requestId, encoder.encode(lineStr));
210
+ }
211
+ }
212
+ // Complete the response
213
+ this.completeResponse(requestId);
214
+ count++;
215
+ }
216
+ catch (e) {
217
+ // Skip requests that fail (might already be handled)
218
+ continue;
219
+ }
220
+ }
221
+ return count;
222
+ }
223
+ }
224
+ /**
225
+ * Set up message handler for the mock service on a MessagePort
226
+ */
227
+ export function setupMockServiceMessageHandler(port) {
228
+ port.addEventListener('message', (event) => {
229
+ const message = event.data;
230
+ if (!message || typeof message !== 'object' || !('type' in message)) {
231
+ return;
232
+ }
233
+ const service = MockSyncService.GLOBAL_INSTANCE;
234
+ try {
235
+ switch (message.type) {
236
+ case 'getPendingRequests': {
237
+ try {
238
+ const requests = service.getPendingRequestsSync();
239
+ port.postMessage({
240
+ type: 'getPendingRequests',
241
+ requestId: message.requestId,
242
+ requests
243
+ });
244
+ }
245
+ catch (error) {
246
+ port.postMessage({
247
+ type: 'error',
248
+ requestId: message.requestId,
249
+ error: error instanceof Error ? error.message : String(error)
250
+ });
251
+ }
252
+ break;
253
+ }
254
+ case 'createResponse': {
255
+ try {
256
+ service.createResponse(message.pendingRequestId, message.status, message.headers);
257
+ port.postMessage({
258
+ type: 'createResponse',
259
+ requestId: message.requestId,
260
+ success: true
261
+ });
262
+ }
263
+ catch (error) {
264
+ port.postMessage({
265
+ type: 'error',
266
+ requestId: message.requestId,
267
+ error: error instanceof Error ? error.message : String(error)
268
+ });
269
+ }
270
+ break;
271
+ }
272
+ case 'pushBodyData': {
273
+ try {
274
+ service.pushBodyData(message.pendingRequestId, message.data);
275
+ port.postMessage({
276
+ type: 'pushBodyData',
277
+ requestId: message.requestId,
278
+ success: true
279
+ });
280
+ }
281
+ catch (error) {
282
+ port.postMessage({
283
+ type: 'error',
284
+ requestId: message.requestId,
285
+ error: error instanceof Error ? error.message : String(error)
286
+ });
287
+ }
288
+ break;
289
+ }
290
+ case 'completeResponse': {
291
+ try {
292
+ service.completeResponse(message.pendingRequestId);
293
+ port.postMessage({
294
+ type: 'completeResponse',
295
+ requestId: message.requestId,
296
+ success: true
297
+ });
298
+ }
299
+ catch (error) {
300
+ port.postMessage({
301
+ type: 'error',
302
+ requestId: message.requestId,
303
+ error: error instanceof Error ? error.message : String(error)
304
+ });
305
+ }
306
+ break;
307
+ }
308
+ case 'setAutomaticResponse': {
309
+ try {
310
+ service.setAutomaticResponse(message.config);
311
+ port.postMessage({
312
+ type: 'setAutomaticResponse',
313
+ requestId: message.requestId,
314
+ success: true
315
+ });
316
+ }
317
+ catch (error) {
318
+ port.postMessage({
319
+ type: 'error',
320
+ requestId: message.requestId,
321
+ error: error instanceof Error ? error.message : String(error)
322
+ });
323
+ }
324
+ break;
325
+ }
326
+ case 'replyToAllPendingRequests': {
327
+ try {
328
+ const count = service.replyToAllPendingRequests();
329
+ port.postMessage({
330
+ type: 'replyToAllPendingRequests',
331
+ requestId: message.requestId,
332
+ success: true,
333
+ count
334
+ });
335
+ }
336
+ catch (error) {
337
+ port.postMessage({
338
+ type: 'error',
339
+ requestId: message.requestId,
340
+ error: error instanceof Error ? error.message : String(error)
341
+ });
342
+ }
343
+ break;
344
+ }
345
+ default: {
346
+ const requestId = 'requestId' in message && typeof message === 'object' && message !== null
347
+ ? message.requestId
348
+ : undefined;
349
+ port.postMessage({
350
+ type: 'error',
351
+ requestId,
352
+ error: `Unknown message type: ${message.type}`
353
+ });
354
+ break;
355
+ }
356
+ }
357
+ }
358
+ catch (error) {
359
+ // Fallback for any unexpected errors
360
+ const requestId = 'requestId' in message ? message.requestId : undefined;
361
+ port.postMessage({
362
+ type: 'error',
363
+ requestId,
364
+ error: error instanceof Error ? error.message : String(error)
365
+ });
366
+ }
367
+ });
368
+ port.start();
369
+ }
@@ -46,10 +46,6 @@ export type WrappedSyncPort = {
46
46
  db?: DBAdapter;
47
47
  currentSubscriptions: SubscribedStream[];
48
48
  closeListeners: (() => void | Promise<void>)[];
49
- /**
50
- * If we can use Navigator locks to detect if the client has closed.
51
- */
52
- isProtectedFromClose: boolean;
53
49
  isClosing: boolean;
54
50
  };
55
51
  /**
@@ -84,7 +80,12 @@ export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImp
84
80
  /**
85
81
  * Gets the last client port which we know is safe from unexpected closes.
86
82
  */
87
- protected get lastWrappedPort(): WrappedSyncPort | undefined;
83
+ protected getLastWrappedPort(): Promise<WrappedSyncPort | undefined>;
84
+ /**
85
+ * In some very rare cases a specific tab might not respond to requests.
86
+ * This returns a random port which is not closing.
87
+ */
88
+ protected getRandomWrappedPort(): Promise<WrappedSyncPort | undefined>;
88
89
  waitForStatus(status: SyncStatusOptions): Promise<void>;
89
90
  waitUntilStatusMatches(predicate: (status: SyncStatus) => boolean): Promise<void>;
90
91
  waitForReady(): Promise<void>;
@@ -112,7 +113,6 @@ export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImp
112
113
  clientProvider: Comlink.Remote<AbstractSharedSyncClientProvider>;
113
114
  currentSubscriptions: never[];
114
115
  closeListeners: never[];
115
- isProtectedFromClose: false;
116
116
  isClosing: false;
117
117
  }>;
118
118
  /**
@@ -134,9 +134,4 @@ export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImp
134
134
  * client.
135
135
  */
136
136
  private updateAllStatuses;
137
- /**
138
- * A function only used for unit tests which updates the internal
139
- * sync stream client and all tab client's sync status
140
- */
141
- _testUpdateAllStatuses(status: SyncStatusOptions): Promise<void>;
142
137
  }