@telecrypt-io/storage 0.5.21 → 0.5.23

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.
@@ -123,6 +123,7 @@ export interface CreateFromOidcOptions {
123
123
  * If cancellation arrives after the mutation starts, the outcome is explicitly
124
124
  * unknown: the caller must reconcile and may safely retry idempotent work. */
125
125
  export declare function withMatrixMutationAbort<T>(_client: MatrixClient, operation: () => Promise<T>, signal?: AbortSignal, operationName?: string): Promise<T>;
126
+ export declare function boundedMatrixFetch(fetchFn: typeof fetch): typeof fetch;
126
127
  export interface MatrixRequestOptions {
127
128
  signal?: AbortSignal;
128
129
  timeoutMs?: number;
@@ -78,6 +78,23 @@ async function readBoundedMatrixResponse(response, maxBytes, signal, abortError)
78
78
  throw new Error("Matrix response body is too large");
79
79
  if (!response.body)
80
80
  return response;
81
+ // Fetch forbids a response body for 204, 205, and 304 responses. Some
82
+ // browser implementations nevertheless expose an empty response stream for
83
+ // a 204 from a proxy. Reconstructing that response with even an empty
84
+ // Uint8Array then throws before matrix-js-sdk can accept the successful
85
+ // response. Preserve the bodyless status after boundedly consuming the
86
+ // stream so callers such as FetchHttpApi can still read an empty Blob.
87
+ const bodylessStatus = response.status === 204 || response.status === 205 || response.status === 304;
88
+ if (bodylessStatus) {
89
+ if (body.bytes.byteLength !== 0) {
90
+ throw new Error("Matrix response body is invalid for a bodyless status");
91
+ }
92
+ return new Response(null, {
93
+ status: response.status,
94
+ statusText: response.statusText,
95
+ headers: response.headers,
96
+ });
97
+ }
81
98
  return new Response(body.bytes, {
82
99
  status: response.status,
83
100
  statusText: response.statusText,
@@ -92,7 +109,7 @@ function isMatrixStateOrMembershipUrl(input) {
92
109
  : new URL(input.url, "https://matrix.invalid");
93
110
  return /\/rooms\/[^/]+\/(?:state|members)(?:\/|$)/.test(url.pathname);
94
111
  }
95
- function boundedMatrixFetch(fetchFn) {
112
+ export function boundedMatrixFetch(fetchFn) {
96
113
  const wrapped = ((input, init) => {
97
114
  const pending = (async () => {
98
115
  const controller = new AbortController();
@@ -31,6 +31,7 @@ const RATE_LIMIT_DEFAULT_DELAY_MS = 15_000;
31
31
  const RATE_LIMIT_MAX_DELAY_MS = 30_000;
32
32
  const RATE_LIMIT_MAX_TOTAL_DELAY_MS = 90_000;
33
33
  const MAX_DELETION_ROOMS = 4096;
34
+ const DELETION_REFRESH_CONCURRENCY = 8;
34
35
  const MAX_DELETION_DEPTH = 128;
35
36
  const MAX_FILE_VERSION_CHAIN = 128;
36
37
  const MAX_LIST_ITEMS = 10000;
@@ -249,6 +250,7 @@ async function refreshDeletionRooms(storage, signal) {
249
250
  if (roomIds.length > MAX_DELETION_ROOMS)
250
251
  throw new StorageError("delete graph is too large");
251
252
  const joined = new Set(roomIds);
253
+ const roomIdsToRefresh = [];
252
254
  // Matrix has no bounded endpoint that enumerates every invited/non-joined
253
255
  // room carrying a space relation. Refresh every locally visible room and
254
256
  // fail closed for an invite/knock/unknown membership; a joined external
@@ -258,14 +260,34 @@ async function refreshDeletionRooms(storage, signal) {
258
260
  const membership = room.getMyMembership?.();
259
261
  if (membership !== "join")
260
262
  throw new StorageError("delete graph inventory is incomplete");
261
- await storage.refreshRoomState(room.roomId, { signal });
263
+ roomIdsToRefresh.push(room.roomId);
262
264
  }
263
265
  }
264
- for (const roomId of roomIds) {
265
- if (signal?.aborted)
266
- throw new StorageError("operation cancelled");
267
- await storage.refreshRoomState(roomId, { signal });
268
- }
266
+ roomIdsToRefresh.push(...roomIds);
267
+ let nextRoom = 0;
268
+ let failed = false;
269
+ let firstError;
270
+ const refreshWorker = async () => {
271
+ while (!failed && !signal?.aborted) {
272
+ const roomId = roomIdsToRefresh[nextRoom++];
273
+ if (roomId === undefined)
274
+ return;
275
+ try {
276
+ await storage.refreshRoomState(roomId, { signal });
277
+ }
278
+ catch (error) {
279
+ failed = true;
280
+ firstError = error;
281
+ return;
282
+ }
283
+ }
284
+ };
285
+ const workerCount = Math.min(DELETION_REFRESH_CONCURRENCY, roomIdsToRefresh.length);
286
+ await Promise.all(Array.from({ length: workerCount }, () => refreshWorker()));
287
+ if (signal?.aborted)
288
+ throw new StorageError("operation cancelled");
289
+ if (failed)
290
+ throw firstError;
269
291
  }
270
292
  async function refreshTreeSpaces(storage, spaces, signal) {
271
293
  for (const space of spaces) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telecrypt-io/storage",
3
- "version": "0.5.21",
3
+ "version": "0.5.23",
4
4
  "license": "BUSL-1.1",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.19.0",