@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.
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 +336 -177
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/worker/SharedSyncImplementation.umd.js +310 -143
  8. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  9. package/dist/worker/WASQLiteDB.umd.js +19 -5
  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 +8 -0
  24. package/lib/src/db/adapters/AsyncDatabaseConnection.js +11 -1
  25. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +14 -0
  26. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +61 -8
  27. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +5 -1
  28. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +15 -7
  29. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +1 -4
  30. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +45 -31
  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 +18 -8
  38. package/lib/src/worker/sync/SharedSyncImplementation.js +174 -108
  39. package/lib/src/worker/sync/SharedSyncImplementation.worker.js +1 -1
  40. package/lib/src/worker/sync/WorkerClient.d.ts +4 -5
  41. package/lib/src/worker/sync/WorkerClient.js +5 -7
  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 +12 -0
  46. package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +71 -9
  47. package/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts +19 -6
  48. package/src/db/sync/SharedWebStreamingSyncImplementation.ts +56 -40
  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 +201 -120
  53. package/src/worker/sync/SharedSyncImplementation.worker.ts +1 -1
  54. package/src/worker/sync/WorkerClient.ts +6 -11
  55. package/dist/a730f7ca717b02234beb.wasm +0 -0
  56. package/dist/aa2f408d64445fed090e.wasm +0 -0
@@ -11,7 +11,7 @@ import {
11
11
  type ILogger
12
12
  } from '@powersync/common';
13
13
  import { getNavigatorLocks } from '../..//shared/navigator';
14
- import { AsyncDatabaseConnection } from './AsyncDatabaseConnection';
14
+ import { AsyncDatabaseConnection, ConnectionClosedError } from './AsyncDatabaseConnection';
15
15
  import { SharedConnectionWorker, WebDBAdapter } from './WebDBAdapter';
16
16
  import { WorkerWrappedAsyncDatabaseConnection } from './WorkerWrappedAsyncDatabaseConnection';
17
17
  import { WASQLiteVFS } from './wa-sqlite/WASQLiteConnection';
@@ -26,10 +26,16 @@ export interface LockedAsyncDatabaseAdapterOptions {
26
26
  openConnection: () => Promise<AsyncDatabaseConnection>;
27
27
  debugMode?: boolean;
28
28
  logger?: ILogger;
29
+ defaultLockTimeoutMs?: number;
30
+ reOpenOnConnectionClosed?: boolean;
29
31
  }
30
32
 
31
33
  export type LockedAsyncDatabaseAdapterListener = DBAdapterListener & {
32
34
  initialized?: () => void;
35
+ /**
36
+ * Fired when the database is re-opened after being closed.
37
+ */
38
+ databaseReOpened?: () => void;
33
39
  };
34
40
 
35
41
  /**
@@ -51,6 +57,7 @@ export class LockedAsyncDatabaseAdapter
51
57
  private _config: ResolvedWebSQLOpenOptions | null = null;
52
58
  protected pendingAbortControllers: Set<AbortController>;
53
59
  protected requiresHolds: boolean | null;
60
+ protected databaseOpenPromise: Promise<void> | null = null;
54
61
 
55
62
  closing: boolean;
56
63
  closed: boolean;
@@ -105,18 +112,52 @@ export class LockedAsyncDatabaseAdapter
105
112
  return this.initPromise;
106
113
  }
107
114
 
108
- protected async _init() {
115
+ protected async openInternalDB() {
116
+ // Dispose any previous table change listener.
117
+ this._disposeTableChangeListener?.();
118
+ this._disposeTableChangeListener = null;
119
+
120
+ const isReOpen = !!this._db;
121
+
109
122
  this._db = await this.options.openConnection();
110
123
  await this._db.init();
111
124
  this._config = await this._db.getConfig();
112
125
  await this.registerOnChangeListener(this._db);
113
- this.iterateListeners((cb) => cb.initialized?.());
126
+ if (isReOpen) {
127
+ this.iterateListeners((cb) => cb.databaseReOpened?.());
128
+ }
114
129
  /**
115
130
  * This is only required for the long-lived shared IndexedDB connections.
116
131
  */
117
132
  this.requiresHolds = (this._config as ResolvedWASQLiteOpenFactoryOptions).vfs == WASQLiteVFS.IDBBatchAtomicVFS;
118
133
  }
119
134
 
135
+ protected _reOpen() {
136
+ this.databaseOpenPromise = this.openInternalDB().finally(() => {
137
+ this.databaseOpenPromise = null;
138
+ });
139
+ return this.databaseOpenPromise;
140
+ }
141
+
142
+ /**
143
+ * Re-opens the underlying database.
144
+ * Returns a pending operation if one is already in progress.
145
+ */
146
+ async reOpenInternalDB(): Promise<void> {
147
+ if (!this.options.reOpenOnConnectionClosed) {
148
+ throw new Error(`Cannot re-open underlying database, reOpenOnConnectionClosed is not enabled`);
149
+ }
150
+ if (this.databaseOpenPromise) {
151
+ return this.databaseOpenPromise;
152
+ }
153
+ return this._reOpen();
154
+ }
155
+
156
+ protected async _init() {
157
+ await this.openInternalDB();
158
+ this.iterateListeners((cb) => cb.initialized?.());
159
+ }
160
+
120
161
  getConfiguration(): ResolvedWebSQLOpenOptions {
121
162
  if (!this._config) {
122
163
  throw new Error(`Cannot get config before initialization is completed`);
@@ -196,7 +237,7 @@ export class LockedAsyncDatabaseAdapter
196
237
  return this.acquireLock(
197
238
  async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })),
198
239
  {
199
- timeoutMs: options?.timeoutMs
240
+ timeoutMs: options?.timeoutMs ?? this.options.defaultLockTimeoutMs
200
241
  }
201
242
  );
202
243
  }
@@ -206,7 +247,7 @@ export class LockedAsyncDatabaseAdapter
206
247
  return this.acquireLock(
207
248
  async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })),
208
249
  {
209
- timeoutMs: options?.timeoutMs
250
+ timeoutMs: options?.timeoutMs ?? this.options.defaultLockTimeoutMs
210
251
  }
211
252
  );
212
253
  }
@@ -222,7 +263,7 @@ export class LockedAsyncDatabaseAdapter
222
263
  this.pendingAbortControllers.add(abortController);
223
264
  const { timeoutMs } = options ?? {};
224
265
 
225
- const timoutId = timeoutMs
266
+ const timeoutId = timeoutMs
226
267
  ? setTimeout(() => {
227
268
  abortController.abort(`Timeout after ${timeoutMs}ms`);
228
269
  this.pendingAbortControllers.delete(abortController);
@@ -234,12 +275,33 @@ export class LockedAsyncDatabaseAdapter
234
275
  { signal: abortController.signal },
235
276
  async () => {
236
277
  this.pendingAbortControllers.delete(abortController);
237
- if (timoutId) {
238
- clearTimeout(timoutId);
278
+ if (timeoutId) {
279
+ clearTimeout(timeoutId);
239
280
  }
240
- const holdId = this.requiresHolds ? await this.baseDB.markHold() : null;
281
+ let holdId: string | null = null;
241
282
  try {
283
+ // The database is being opened in the background. Wait for it here.
284
+ if (this.databaseOpenPromise) {
285
+ try {
286
+ await this.databaseOpenPromise;
287
+ } catch (ex) {
288
+ // This will cause a retry of opening the database.
289
+ const wrappedError = new ConnectionClosedError('Could not open database');
290
+ wrappedError.cause = ex;
291
+ throw wrappedError;
292
+ }
293
+ }
294
+
295
+ holdId = this.requiresHolds ? await this.baseDB.markHold() : null;
242
296
  return await callback();
297
+ } catch (ex) {
298
+ if (ex instanceof ConnectionClosedError) {
299
+ if (this.options.reOpenOnConnectionClosed && !this.databaseOpenPromise && !this.closing) {
300
+ // Immediately re-open the database. We need to miss as little table updates as possible.
301
+ this.reOpenInternalDB();
302
+ }
303
+ }
304
+ throw ex;
243
305
  } finally {
244
306
  if (holdId) {
245
307
  await this.baseDB.releaseHold(holdId);
@@ -1,6 +1,8 @@
1
+ import { BaseObserver } from '@powersync/common';
1
2
  import * as Comlink from 'comlink';
2
3
  import {
3
4
  AsyncDatabaseConnection,
5
+ ConnectionClosedError,
4
6
  OnTableChangeCallback,
5
7
  OpenAsyncDatabaseConnection,
6
8
  ProxiedQueryResult
@@ -23,17 +25,23 @@ export type WrappedWorkerConnectionOptions<Config extends ResolvedWebSQLOpenOpti
23
25
  onClose?: () => void;
24
26
  };
25
27
 
28
+ export type WorkerWrappedAsyncDatabaseConnectionListener = {
29
+ closing: () => void;
30
+ };
26
31
  /**
27
32
  * Wraps a provided instance of {@link AsyncDatabaseConnection}, providing necessary proxy
28
33
  * functions for worker listeners.
29
34
  */
30
35
  export class WorkerWrappedAsyncDatabaseConnection<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions>
36
+ extends BaseObserver<WorkerWrappedAsyncDatabaseConnectionListener>
31
37
  implements AsyncDatabaseConnection
32
38
  {
33
39
  protected lockAbortController = new AbortController();
34
40
  protected notifyRemoteClosed: AbortController | undefined;
35
41
 
36
42
  constructor(protected options: WrappedWorkerConnectionOptions<Config>) {
43
+ super();
44
+
37
45
  if (options.remoteCanCloseUnexpectedly) {
38
46
  this.notifyRemoteClosed = new AbortController();
39
47
  }
@@ -72,18 +80,21 @@ export class WorkerWrappedAsyncDatabaseConnection<Config extends ResolvedWebSQLO
72
80
  return this.withRemote(() => this.baseConnection.isAutoCommit());
73
81
  }
74
82
 
75
- private withRemote<T>(workerPromise: () => Promise<T>): Promise<T> {
83
+ private withRemote<T>(workerPromise: () => Promise<T>, fireActionOnAbort = false): Promise<T> {
76
84
  const controller = this.notifyRemoteClosed;
77
85
  if (controller) {
78
86
  return new Promise((resolve, reject) => {
79
87
  if (controller.signal.aborted) {
80
- reject(new Error('Called operation on closed remote'));
81
- // Don't run the operation if we're going to reject
82
- return;
88
+ reject(new ConnectionClosedError('Called operation on closed remote'));
89
+ if (!fireActionOnAbort) {
90
+ // Don't run the operation if we're going to reject
91
+ // We might want to fire-and-forget the operation in some cases (like a close operation)
92
+ return;
93
+ }
83
94
  }
84
95
 
85
96
  function handleAbort() {
86
- reject(new Error('Remote peer closed with request in flight'));
97
+ reject(new ConnectionClosedError('Remote peer closed with request in flight'));
87
98
  }
88
99
 
89
100
  function completePromise(action: () => void) {
@@ -164,10 +175,12 @@ export class WorkerWrappedAsyncDatabaseConnection<Config extends ResolvedWebSQLO
164
175
  // Abort any pending lock requests.
165
176
  this.lockAbortController.abort();
166
177
  try {
167
- await this.withRemote(() => this.baseConnection.close());
178
+ // fire and forget the close operation
179
+ await this.withRemote(() => this.baseConnection.close(), true);
168
180
  } finally {
169
181
  this.options.remote[Comlink.releaseProxy]();
170
182
  this.options.onClose?.();
183
+ this.iterateListeners((l) => l.closing?.());
171
184
  }
172
185
  }
173
186
 
@@ -2,7 +2,6 @@ import {
2
2
  PowerSyncConnectionOptions,
3
3
  PowerSyncCredentials,
4
4
  SubscribedStream,
5
- SyncStatus,
6
5
  SyncStatusOptions
7
6
  } from '@powersync/common';
8
7
  import * as Comlink from 'comlink';
@@ -146,7 +145,25 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
146
145
  ).port;
147
146
  }
148
147
 
148
+ /**
149
+ * Pass along any sync status updates to this listener
150
+ */
151
+ this.clientProvider = new SharedSyncClientProvider(
152
+ this.webOptions,
153
+ (status) => {
154
+ this.updateSyncStatus(status);
155
+ },
156
+ options.db
157
+ );
158
+
149
159
  this.syncManager = Comlink.wrap<WorkerClient>(this.messagePort);
160
+ /**
161
+ * The sync worker will call this client provider when it needs
162
+ * to fetch credentials or upload data.
163
+ * This performs bi-directional method calling.
164
+ */
165
+ Comlink.expose(this.clientProvider, this.messagePort);
166
+
150
167
  this.syncManager.setLogLevel(this.logger.getLevel());
151
168
 
152
169
  this.triggerCrudUpload = this.syncManager.triggerCrudUpload;
@@ -157,25 +174,49 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
157
174
  * DB worker, but a port to the DB worker can be transferred to the
158
175
  * sync worker.
159
176
  */
160
- const { crudUploadThrottleMs, identifier, retryDelayMs } = this.options;
161
- const flags = { ...this.webOptions.flags, workers: undefined };
162
177
 
163
- // Request a random lock until this client is disposed. The name of the lock is sent to the shared worker, which
164
- // will also attempt to acquire it. Since the lock is returned when the tab is closed, this allows the share worker
165
- // to free resources associated with this tab.
166
- // We take hold of this lock as soon-as-possible in order to cater for potentially closed tabs.
167
- getNavigatorLocks().request(`tab-close-signal-${crypto.randomUUID()}`, async (lock) => {
168
- if (!this.abortOnClose.signal.aborted) {
178
+ this.isInitialized = this._init();
179
+ }
180
+
181
+ protected async _init() {
182
+ /**
183
+ * The general flow of initialization is:
184
+ * - The client requests a unique navigator lock.
185
+ * - Once the lock is acquired, we register the lock with the shared worker.
186
+ * - The shared worker can then request the same lock. The client has been closed if the shared worker can acquire the lock.
187
+ * - Once the shared worker knows the client's lock, we can guarentee that the shared worker will detect if the client has been closed.
188
+ * - This makes the client safe for the shared worker to use.
189
+ * - The client is only added to the SharedSyncImplementation once the lock has been registered.
190
+ * This ensures we don't ever keep track of dead clients (tabs that closed before the lock was registered).
191
+ * - The client side lock is held until the client is disposed.
192
+ * - We resolve the top-level promise after the lock has been registered with the shared worker.
193
+ * - The client sends the params to the shared worker after locks have been registered.
194
+ */
195
+ await new Promise<void>((resolve) => {
196
+ // Request a random lock until this client is disposed. The name of the lock is sent to the shared worker, which
197
+ // will also attempt to acquire it. Since the lock is returned when the tab is closed, this allows the share worker
198
+ // to free resources associated with this tab.
199
+ // We take hold of this lock as soon-as-possible in order to cater for potentially closed tabs.
200
+ getNavigatorLocks().request(`tab-close-signal-${crypto.randomUUID()}`, async (lock) => {
201
+ if (this.abortOnClose.signal.aborted) {
202
+ return;
203
+ }
169
204
  // Awaiting here ensures the worker is waiting for the lock
170
205
  await this.syncManager.addLockBasedCloseSignal(lock!.name);
171
206
 
207
+ // The lock has been registered, we can continue with the initialization
208
+ resolve();
209
+
172
210
  await new Promise<void>((r) => {
173
211
  this.abortOnClose.signal.onabort = () => r();
174
212
  });
175
- }
213
+ });
176
214
  });
177
215
 
178
- this.isInitialized = this.syncManager.setParams(
216
+ const { crudUploadThrottleMs, identifier, retryDelayMs } = this.options;
217
+ const flags = { ...this.webOptions.flags, workers: undefined };
218
+
219
+ await this.syncManager.setParams(
179
220
  {
180
221
  dbParams: this.dbAdapter.getConfiguration(),
181
222
  streamOptions: {
@@ -185,26 +226,8 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
185
226
  flags: flags
186
227
  }
187
228
  },
188
- options.subscriptions
189
- );
190
-
191
- /**
192
- * Pass along any sync status updates to this listener
193
- */
194
- this.clientProvider = new SharedSyncClientProvider(
195
- this.webOptions,
196
- (status) => {
197
- this.iterateListeners((l) => this.updateSyncStatus(status));
198
- },
199
- options.db
229
+ this.options.subscriptions
200
230
  );
201
-
202
- /**
203
- * The sync worker will call this client provider when it needs
204
- * to fetch credentials or upload data.
205
- * This performs bi-directional method calling.
206
- */
207
- Comlink.expose(this.clientProvider, this.messagePort);
208
231
  }
209
232
 
210
233
  /**
@@ -233,8 +256,6 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
233
256
  async dispose(): Promise<void> {
234
257
  await this.waitForReady();
235
258
 
236
- await super.dispose();
237
-
238
259
  await new Promise<void>((resolve) => {
239
260
  // Listen for the close acknowledgment from the worker
240
261
  this.messagePort.addEventListener('message', (event) => {
@@ -251,6 +272,9 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
251
272
  };
252
273
  this.messagePort.postMessage(closeMessagePayload);
253
274
  });
275
+
276
+ await super.dispose();
277
+
254
278
  this.abortOnClose.abort();
255
279
 
256
280
  // Release the proxy
@@ -265,12 +289,4 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
265
289
  updateSubscriptions(subscriptions: SubscribedStream[]): void {
266
290
  this.syncManager.updateSubscriptions(subscriptions);
267
291
  }
268
-
269
- /**
270
- * Used in tests to force a connection states
271
- */
272
- private async _testUpdateStatus(status: SyncStatus) {
273
- await this.isInitialized;
274
- return this.syncManager._testUpdateAllStatuses(status.toJSON());
275
- }
276
292
  }
@@ -0,0 +1,3 @@
1
+ // Re-export types and worker-side implementation
2
+ export * from './MockSyncServiceTypes';
3
+ export * from './MockSyncServiceWorker';
@@ -0,0 +1,71 @@
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
+ /**
13
+ * Automatic response configuration
14
+ */
15
+ export interface AutomaticResponseConfig {
16
+ status: number;
17
+ headers: Record<string, string>;
18
+ bodyLines?: any[];
19
+ }
20
+
21
+ /**
22
+ * Message types for communication via MessagePort
23
+ */
24
+ export type MockSyncServiceMessage =
25
+ | { type: 'getPendingRequests'; requestId: string }
26
+ | {
27
+ type: 'createResponse';
28
+ requestId: string;
29
+ pendingRequestId: string;
30
+ status: number;
31
+ headers: Record<string, string>;
32
+ }
33
+ | { type: 'pushBodyData'; requestId: string; pendingRequestId: string; data: string | ArrayBuffer | Uint8Array }
34
+ | { type: 'completeResponse'; requestId: string; pendingRequestId: string }
35
+ | { type: 'setAutomaticResponse'; requestId: string; config: AutomaticResponseConfig | null }
36
+ | { type: 'replyToAllPendingRequests'; requestId: string };
37
+
38
+ export type MockSyncServiceResponse =
39
+ | { type: 'getPendingRequests'; requestId: string; requests: PendingRequest[] }
40
+ | { type: 'createResponse'; requestId: string; success: boolean }
41
+ | { type: 'pushBodyData'; requestId: string; success: boolean }
42
+ | { type: 'completeResponse'; requestId: string; success: boolean }
43
+ | { type: 'setAutomaticResponse'; requestId: string; success: boolean }
44
+ | { type: 'replyToAllPendingRequests'; requestId: string; success: boolean; count: number }
45
+ | { type: 'error'; requestId?: string; error: string };
46
+
47
+ /**
48
+ * Internal representation of a pending request with response promise
49
+ */
50
+ export interface PendingRequestInternal {
51
+ id: string;
52
+ url: string;
53
+ method: string;
54
+ headers: Record<string, string>;
55
+ body: any;
56
+ responsePromise: {
57
+ resolve: (response: Response) => void;
58
+ reject: (error: Error) => void;
59
+ };
60
+ streamController?: ReadableStreamDefaultController<Uint8Array>;
61
+ }
62
+
63
+ /**
64
+ * Internal representation of an active response
65
+ */
66
+ export interface ActiveResponse {
67
+ id: string;
68
+ status: number;
69
+ headers: Record<string, string>;
70
+ stream: ReadableStreamDefaultController<Uint8Array>;
71
+ }