@telecrypt-io/storage 0.6.1 → 0.7.1

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.
@@ -17,7 +17,7 @@ import { Method } from "matrix-js-sdk/lib/http-api/method.js";
17
17
  import { FileTooLargeError, MutationPartialError, MutationOutcomeUnknownError, NonEmptyTreeError, RoomCreationAmbiguousError, RoomCleanupIncompleteError, StorageError, } from "./errors.js";
18
18
  import { ConditionTimeoutError, waitForCondition } from "./poll.js";
19
19
  import { validateName } from "./validation.js";
20
- import { getDeletedTreeIds, isFileDeleted, isTreeDeleted, markFileDeleted, markTreeDeleted, } from "../deletion-markers.js";
20
+ import { isFileDeleted, isTreeDeleted, markFileDeleted, markTreeDeleted, } from "../deletion-markers.js";
21
21
  /**
22
22
  * Production Synapse enforces the built-in rc_messages budget (per-account
23
23
  * burst 10, refill 1/5s) on every room/state mutation the tree operations
@@ -100,9 +100,7 @@ function isRateLimited(error) {
100
100
  function requiresMutationReconciliation(error) {
101
101
  return (error instanceof MutationOutcomeUnknownError ||
102
102
  error instanceof RoomCreationAmbiguousError ||
103
- error instanceof RoomCleanupIncompleteError ||
104
- (error instanceof Error &&
105
- error.cleanupIncomplete === true));
103
+ error instanceof RoomCleanupIncompleteError);
106
104
  }
107
105
  async function withRateLimitRetry(operation, signal) {
108
106
  for (;;) {
@@ -175,13 +173,9 @@ async function resolveTree(storage, treeId, signal) {
175
173
  }
176
174
  }
177
175
  function isMarkedTreeDeleted(storage, treeId) {
178
- if (typeof storage.getClient !== "function")
179
- return false;
180
176
  return isTreeDeleted(storage.getClient(), treeId);
181
177
  }
182
178
  function isMarkedFileDeleted(storage, treeId, fileId) {
183
- if (typeof storage.getClient !== "function")
184
- return false;
185
179
  return isFileDeleted(storage.getClient(), treeId, fileId);
186
180
  }
187
181
  /** As `resolveTree`, but for a specific file within an already-resolved
@@ -210,9 +204,7 @@ async function resolveFile(storage, tree, fileId, signal) {
210
204
  * room. This also prevents a folder/vault delete from silently deleting a
211
205
  * nested shared tree.
212
206
  */
213
- function assertTreeEmptyForDeletion(storage, tree, spaces) {
214
- if (spaces.length !== 1)
215
- throw new NonEmptyTreeError(tree.id);
207
+ function assertTreeEmptyForDeletion(storage, tree) {
216
208
  let files;
217
209
  try {
218
210
  files = tree.listFiles().filter((file) => !isMarkedFileDeleted(storage, tree.id, file.id));
@@ -222,40 +214,74 @@ function assertTreeEmptyForDeletion(storage, tree, spaces) {
222
214
  }
223
215
  if (files.length > 0)
224
216
  throw new NonEmptyTreeError(tree.id);
217
+ const childEvents = readRelationEvents(storage.getClient(), tree.id, EventType.SpaceChild);
218
+ if (childEvents === null)
219
+ throw new StorageError("could not enumerate storage folders safely");
220
+ for (const event of childEvents) {
221
+ if (!isActiveRelationEvent(event))
222
+ continue;
223
+ const childId = relationStateKey(event);
224
+ if (!childId)
225
+ throw new StorageError("could not enumerate storage folders safely");
226
+ if (!isTreeDeleted(storage.getClient(), childId))
227
+ throw new NonEmptyTreeError(tree.id);
228
+ }
225
229
  }
226
230
  async function refreshDeletionRooms(storage, rootId, signal) {
227
231
  const client = storage.getClient();
228
- const http = client.http;
229
- // A real MatrixClient always exposes its authenticated HTTP transport. A
230
- // client without one cannot prove that its local relation graph is current.
231
- if (typeof http?.authedRequest !== "function") {
232
- throw new StorageError("delete graph refresh is unavailable");
233
- }
234
232
  await storage.refreshRoomState(rootId, { signal });
235
233
  if (signal?.aborted)
236
234
  throw new StorageError("operation cancelled");
237
- // The refreshed root owns its parent relation. Verify its one supported
238
- // external parent directly; unrelated local rooms are outside this deletion.
239
235
  const parentEvents = readRelationEvents(client, rootId, EventType.SpaceParent);
240
236
  if (parentEvents === null)
241
- throw new StorageError("delete graph is unsafe");
237
+ throw new StorageError("could not enumerate storage parent safely");
242
238
  const parentIds = new Set();
243
239
  for (const event of parentEvents) {
244
240
  if (!isActiveRelationEvent(event))
245
241
  continue;
246
242
  const parentId = relationStateKey(event);
247
243
  if (!parentId)
248
- throw new StorageError("delete graph is unsafe");
244
+ throw new StorageError("could not enumerate storage parent safely");
249
245
  if (parentId !== rootId)
250
246
  parentIds.add(parentId);
251
247
  }
252
248
  if (parentIds.size > 1)
253
- throw new StorageError("delete graph is unsafe");
254
- const parentId = parentIds.values().next().value;
255
- if (parentId)
256
- await storage.refreshRoomState(parentId, { signal });
249
+ throw new StorageError("storage room has multiple parents");
250
+ const parents = [...parentIds];
251
+ if (parents[0]) {
252
+ await storage.refreshRoomState(parents[0], { signal });
253
+ const childEvents = readRelationEvents(client, parents[0], EventType.SpaceChild, rootId);
254
+ if (childEvents === null ||
255
+ !childEvents.some((event) => isActiveRelationEvent(event) && relationStateKey(event, rootId) === rootId)) {
256
+ throw new StorageError("storage parent relation is inconsistent");
257
+ }
258
+ }
257
259
  if (signal?.aborted)
258
260
  throw new StorageError("operation cancelled");
261
+ return parents;
262
+ }
263
+ function snapshotTreeSpaces(root) {
264
+ const spaces = [];
265
+ const seen = new Set();
266
+ const pending = [root];
267
+ while (pending.length > 0) {
268
+ const tree = pending.pop();
269
+ if (!tree || seen.has(tree.id))
270
+ continue;
271
+ seen.add(tree.id);
272
+ spaces.push(tree);
273
+ let children;
274
+ try {
275
+ children = tree.getDirectories();
276
+ }
277
+ catch (error) {
278
+ if (error instanceof StorageError)
279
+ throw error;
280
+ throw new StorageError("could not enumerate storage folders", { cause: error });
281
+ }
282
+ pending.push(...children);
283
+ }
284
+ return spaces;
259
285
  }
260
286
  async function refreshTreeSpaces(storage, spaces, signal) {
261
287
  for (const space of spaces) {
@@ -286,126 +312,24 @@ function isActiveRelationEvent(event) {
286
312
  function activeTreeDirectories(client, tree) {
287
313
  const childEvents = readRelationEvents(client, tree.id, EventType.SpaceChild);
288
314
  if (childEvents === null)
289
- throw new StorageError("delete graph is unsafe");
315
+ throw new StorageError("storage folder state is unavailable");
290
316
  const activeChildIds = new Set();
291
317
  for (const event of childEvents) {
292
318
  if (!isActiveRelationEvent(event))
293
319
  continue;
294
320
  const childId = relationStateKey(event);
295
321
  if (!childId)
296
- throw new StorageError("delete graph is unsafe");
322
+ throw new StorageError("storage folder state is inconsistent");
297
323
  activeChildIds.add(childId);
298
324
  }
299
325
  const knownChildren = new Map(tree.getDirectories().map((child) => [child.id, child]));
300
326
  return [...activeChildIds].map((childId) => {
301
327
  const child = knownChildren.get(childId);
302
328
  if (!child)
303
- throw new StorageError("delete graph is unsafe");
329
+ throw new StorageError("storage folder state is inconsistent");
304
330
  return child;
305
331
  });
306
332
  }
307
- function snapshotTreeSpaces(root, activeRelationClient) {
308
- const spaces = [];
309
- const seen = new Set();
310
- const pending = [root];
311
- while (pending.length > 0) {
312
- const tree = pending.pop();
313
- if (!tree)
314
- continue;
315
- if (seen.has(tree.id))
316
- continue;
317
- seen.add(tree.id);
318
- spaces.push(tree);
319
- let children;
320
- try {
321
- children = activeRelationClient
322
- ? activeTreeDirectories(activeRelationClient, tree)
323
- : tree.getDirectories();
324
- }
325
- catch (error) {
326
- if (error instanceof StorageError)
327
- throw error;
328
- throw new StorageError("could not enumerate storage space descendants safely", { cause: error });
329
- }
330
- for (const child of children)
331
- pending.push(child);
332
- }
333
- return spaces;
334
- }
335
- /**
336
- * Deletion is intentionally narrower than MSC3089TreeSpace.delete(): a local
337
- * child graph is not proof that the rooms are exclusively owned by this tree.
338
- * Validate every observed edge in both directions and reject any parent or
339
- * child edge outside the exact deletion set before the first mutation.
340
- */
341
- function validateDeletionGraph(client, root, ids, deletedRooms) {
342
- const edges = new Set();
343
- const externalParents = [];
344
- const addEdge = (parentId, childId) => {
345
- if (deletedRooms.has(childId))
346
- return;
347
- if (!ids.has(childId))
348
- throw new StorageError("delete graph is unsafe");
349
- edges.add(`${parentId}\u0000${childId}`);
350
- };
351
- for (const id of ids) {
352
- const tree = client.unstableGetFileTreeSpace(id);
353
- if (!tree)
354
- throw new StorageError("delete graph is unsafe");
355
- const directories = activeTreeDirectories(client, tree);
356
- for (const child of directories)
357
- addEdge(id, child.id);
358
- const childEvents = readRelationEvents(client, id, EventType.SpaceChild);
359
- if (childEvents === null)
360
- throw new StorageError("delete graph is unsafe");
361
- for (const event of childEvents) {
362
- if (!isActiveRelationEvent(event))
363
- continue;
364
- const childId = relationStateKey(event);
365
- if (!childId)
366
- throw new StorageError("delete graph is unsafe");
367
- addEdge(id, childId);
368
- }
369
- }
370
- for (const edge of edges) {
371
- const separator = edge.indexOf("\u0000");
372
- const parentId = edge.slice(0, separator);
373
- const childId = edge.slice(separator + 1);
374
- const childLink = readRelationEvents(client, parentId, EventType.SpaceChild, childId);
375
- const parentLink = readRelationEvents(client, childId, EventType.SpaceParent, parentId);
376
- if (!childLink?.some((event) => isActiveRelationEvent(event) && relationStateKey(event, childId) === childId) ||
377
- !parentLink?.some((event) => isActiveRelationEvent(event) && relationStateKey(event, parentId) === parentId)) {
378
- throw new StorageError("delete graph is unsafe");
379
- }
380
- }
381
- for (const id of ids) {
382
- const parentEvents = readRelationEvents(client, id, EventType.SpaceParent);
383
- if (parentEvents === null)
384
- throw new StorageError("delete graph is unsafe");
385
- for (const event of parentEvents) {
386
- if (!isActiveRelationEvent(event))
387
- continue;
388
- const parentId = relationStateKey(event);
389
- if (!parentId)
390
- throw new StorageError("delete graph is unsafe");
391
- if (!ids.has(parentId)) {
392
- if (id !== root.id || externalParents.includes(parentId)) {
393
- throw new StorageError("delete graph is unsafe");
394
- }
395
- const childLink = readRelationEvents(client, parentId, EventType.SpaceChild, id);
396
- if (!childLink?.some((candidate) => isActiveRelationEvent(candidate) && relationStateKey(candidate, id) === id)) {
397
- throw new StorageError("delete graph is unsafe");
398
- }
399
- externalParents.push(parentId);
400
- }
401
- }
402
- }
403
- if (!ids.has(root.id))
404
- throw new StorageError("delete graph is unsafe");
405
- if (externalParents.length > 1)
406
- throw new StorageError("delete graph is unsafe");
407
- return { externalParents };
408
- }
409
333
  async function unlinkExternalParents(storage, rootId, externalParents, signal) {
410
334
  const client = storage.getClient();
411
335
  let mutationAttempted = false;
@@ -417,10 +341,10 @@ async function unlinkExternalParents(storage, rootId, externalParents, signal) {
417
341
  // is not used here because it can be delayed or rejected independently of
418
342
  // the relation update.
419
343
  mutationAttempted = true;
420
- await withRateLimitRetry(() => withMatrixMutationAbort(client, () => client.sendStateEvent(parentId, EventType.SpaceChild, {}, rootId), signal), signal);
344
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => client.sendStateEvent(parentId, EventType.SpaceChild, {}, rootId), signal), signal);
421
345
  if (signal?.aborted)
422
346
  throw new StorageError("operation cancelled");
423
- await withRateLimitRetry(() => withMatrixMutationAbort(client, () => client.sendStateEvent(rootId, EventType.SpaceParent, {}, parentId), signal), signal);
347
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => client.sendStateEvent(rootId, EventType.SpaceParent, {}, parentId), signal), signal);
424
348
  await storage.refreshRoomState(parentId, { signal });
425
349
  await storage.refreshRoomState(rootId, { signal });
426
350
  const childLink = readRelationEvents(client, parentId, EventType.SpaceChild, rootId);
@@ -461,8 +385,8 @@ async function relinkExternalParents(storage, rootId, externalParents, signal) {
461
385
  for (const parentId of externalParents) {
462
386
  if (signal?.aborted)
463
387
  throw new StorageError("operation cancelled");
464
- await withRateLimitRetry(() => withMatrixMutationAbort(client, () => client.sendStateEvent(parentId, EventType.SpaceChild, { via: [via] }, rootId), signal), signal);
465
- await withRateLimitRetry(() => withMatrixMutationAbort(client, () => client.sendStateEvent(rootId, EventType.SpaceParent, { via: [via] }, parentId), signal), signal);
388
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => client.sendStateEvent(parentId, EventType.SpaceChild, { via: [via] }, rootId), signal), signal);
389
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => client.sendStateEvent(rootId, EventType.SpaceParent, { via: [via] }, parentId), signal), signal);
466
390
  await storage.refreshRoomState(parentId, { signal });
467
391
  await storage.refreshRoomState(rootId, { signal });
468
392
  const childLink = readRelationEvents(client, parentId, EventType.SpaceChild, rootId);
@@ -487,10 +411,10 @@ async function deleteRoomDeterministically(storage, roomId, signal) {
487
411
  const client = storage.getClient();
488
412
  const tree = storage.getTree(roomId);
489
413
  if (!tree)
490
- throw new StorageError("delete graph is unsafe");
414
+ throw new StorageError("storage room is unavailable for deletion");
491
415
  const self = client.getUserId();
492
416
  if (!self)
493
- throw new StorageError("delete graph is unsafe");
417
+ throw new StorageError("Matrix user identity is unavailable");
494
418
  const members = await storage.listMembers(tree, { signal });
495
419
  if (members.some((member) => member.userId !== self && member.role === "owner" &&
496
420
  (member.membership === "join" || member.membership === "invite" || member.membership === "knock"))) {
@@ -503,7 +427,7 @@ async function deleteRoomDeterministically(storage, roomId, signal) {
503
427
  continue;
504
428
  }
505
429
  try {
506
- await withRateLimitRetry(() => withMatrixMutationAbort(client, () => client.kick(roomId, member.userId, "Room deleted"), signal), signal);
430
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => client.kick(roomId, member.userId, "Room deleted"), signal), signal);
507
431
  markRoomMutationComplete();
508
432
  }
509
433
  catch (error) {
@@ -523,7 +447,7 @@ async function deleteRoomDeterministically(storage, roomId, signal) {
523
447
  const ownMembership = await storage.getRoomMembership(roomId, undefined, { signal });
524
448
  if (ownMembership === "join" || ownMembership === "invite" || ownMembership === "knock") {
525
449
  try {
526
- await withRateLimitRetry(() => withMatrixMutationAbort(client, () => client.leave(roomId), signal), signal);
450
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => client.leave(roomId), signal), signal);
527
451
  markRoomMutationComplete();
528
452
  }
529
453
  catch (error) {
@@ -532,7 +456,7 @@ async function deleteRoomDeterministically(storage, roomId, signal) {
532
456
  }
533
457
  }
534
458
  try {
535
- await withRateLimitRetry(() => withMatrixMutationAbort(client, () => client.forget(roomId), signal), signal);
459
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => client.forget(roomId), signal), signal);
536
460
  markRoomMutationComplete();
537
461
  }
538
462
  catch (error) {
@@ -632,7 +556,7 @@ export async function joinVault(storage, vaultId, options) {
632
556
  if (membership === "join")
633
557
  return { vaultId, joined: true };
634
558
  try {
635
- await withRateLimitRetry(() => withMatrixMutationAbort(storage.getClient(), () => storage.getClient().joinRoom(vaultId), signal), signal);
559
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => storage.getClient().joinRoom(vaultId), signal), signal);
636
560
  }
637
561
  catch (err) {
638
562
  if (err instanceof MutationOutcomeUnknownError)
@@ -765,7 +689,7 @@ export async function declineInvite(storage, vaultId, options) {
765
689
  return { vaultId, declined: false };
766
690
  }
767
691
  try {
768
- await withRateLimitRetry(() => withMatrixMutationAbort(client, () => client.leave(vaultId), signal), signal);
692
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => client.leave(vaultId), signal), signal);
769
693
  }
770
694
  catch (error) {
771
695
  let suppress = isGoneError(error);
@@ -777,7 +701,7 @@ export async function declineInvite(storage, vaultId, options) {
777
701
  throw error;
778
702
  }
779
703
  try {
780
- await withRateLimitRetry(() => withMatrixMutationAbort(client, () => client.forget(vaultId), signal), signal);
704
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => client.forget(vaultId), signal), signal);
781
705
  }
782
706
  catch (error) {
783
707
  let suppress = isGoneError(error);
@@ -815,7 +739,6 @@ export async function shareVault(storage, vaultId, userId, role, options) {
815
739
  ensureOperationActive(operation.signal);
816
740
  const tree = await resolveTree(storage, vaultId, operation.signal);
817
741
  const pending = withTreeMutation(storage.getClient(), async () => {
818
- const client = storage.getClient();
819
742
  try {
820
743
  ensureOperationActive(operation.signal);
821
744
  await storage.refreshRoomState(tree.id, { signal: operation.signal });
@@ -839,7 +762,7 @@ export async function shareVault(storage, vaultId, userId, role, options) {
839
762
  if (isActiveMembership(currentMembership ?? ""))
840
763
  continue;
841
764
  try {
842
- await withRateLimitRetry(() => withMatrixMutationAbort(client, () => space.invite(userId), operation.signal), operation.signal);
765
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => space.invite(userId), operation.signal), operation.signal);
843
766
  completedRoomIds.add(space.id);
844
767
  }
845
768
  catch (error) {
@@ -863,7 +786,7 @@ export async function shareVault(storage, vaultId, userId, role, options) {
863
786
  if (members.some((member) => member.userId === userId && member.role === "owner")) {
864
787
  throw new StorageError("share will not demote an existing owner");
865
788
  }
866
- await withRateLimitRetry(() => withMatrixMutationAbort(client, () => space.setPermissions(userId, role), operation.signal), operation.signal);
789
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => space.setPermissions(userId, role), operation.signal), operation.signal);
867
790
  completedRoomIds.add(space.id);
868
791
  }
869
792
  }
@@ -925,7 +848,7 @@ export async function unshareVault(storage, vaultId, userId, options) {
925
848
  if (!isRevocableMembership(membership))
926
849
  continue;
927
850
  try {
928
- await withRateLimitRetry(() => withMatrixMutationAbort(storage.getClient(), () => storage.getClient().kick(space.id, userId, "unshared"), operation.signal), operation.signal);
851
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => storage.getClient().kick(space.id, userId, "unshared"), operation.signal), operation.signal);
929
852
  completedRoomIds.add(space.id);
930
853
  }
931
854
  catch (error) {
@@ -1031,8 +954,7 @@ async function renameTree(storage, treeId, name, options) {
1031
954
  return withOperationDeadline(options, async (signal) => {
1032
955
  const tree = await resolveTree(storage, treeId, signal);
1033
956
  try {
1034
- const client = typeof storage.getClient === "function" ? storage.getClient() : undefined;
1035
- await withRateLimitRetry(() => client ? withMatrixMutationAbort(client, () => tree.setName(name), signal) : tree.setName(name), signal);
957
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => tree.setName(name), signal), signal);
1036
958
  await waitForCondition(async () => {
1037
959
  ensureOperationActive(signal);
1038
960
  const current = storage.getTree(treeId);
@@ -1070,38 +992,20 @@ async function deleteTree(storage, treeId, options) {
1070
992
  if (isTreeDeleted(client, treeId))
1071
993
  return { id: treeId, deleted: true };
1072
994
  const tree = await resolveTree(storage, treeId, operation.signal);
1073
- // Read reconciliation state after this operation reaches the single
1074
- // client queue; a preceding delete may have populated the set while we
1075
- // were waiting for its turn.
1076
- const removedRooms = getDeletedTreeIds(client);
1077
- let graph;
995
+ let externalParents;
1078
996
  try {
1079
- // Re-read the candidate graph and any external parents it identifies
1080
- // before taking the immutable graph snapshot. Unrelated rooms are not
1081
- // part of this operation and must not block deletion merely because
1082
- // their membership is not joined.
1083
- await refreshDeletionRooms(storage, tree.id, operation.signal);
1084
- const spaces = snapshotTreeSpaces(tree, client);
1085
- const activeSpaces = spaces.filter((space) => !removedRooms.has(space.id));
1086
- const ids = new Set(activeSpaces.map((space) => space.id));
1087
- if (!ids.has(tree.id)) {
1088
- if (removedRooms.has(tree.id)) {
1089
- return { id: treeId, deleted: true };
1090
- }
1091
- throw new StorageError("delete graph is unsafe");
1092
- }
1093
- assertTreeEmptyForDeletion(storage, tree, activeSpaces);
1094
- graph = validateDeletionGraph(client, tree, ids, removedRooms);
997
+ // Deletion owns one room. Refresh its current state and its one
998
+ // supported parent before checking files/folders or changing links.
999
+ externalParents = await refreshDeletionRooms(storage, tree.id, operation.signal);
1000
+ assertTreeEmptyForDeletion(storage, tree);
1095
1001
  }
1096
1002
  catch (error) {
1097
1003
  if (error instanceof StorageError)
1098
1004
  throw error;
1099
1005
  throw new StorageError("delete failed", { cause: error });
1100
1006
  }
1101
- // Keep external parent links intact until the room deletion succeeds.
1102
- // Unlinking first would silently detach a live vault on failure.
1103
1007
  try {
1104
- await unlinkExternalParents(storage, tree.id, graph.externalParents, operation.signal);
1008
+ await unlinkExternalParents(storage, tree.id, externalParents, operation.signal);
1105
1009
  }
1106
1010
  catch (error) {
1107
1011
  if (error instanceof StorageError)
@@ -1112,11 +1016,8 @@ async function deleteTree(storage, treeId, options) {
1112
1016
  await deleteRoomDeterministically(storage, tree.id, operation.signal);
1113
1017
  }
1114
1018
  catch (error) {
1115
- // The root is still live when its relation is removed. If deletion then
1116
- // fails, restore both sides before reporting failure; otherwise a live
1117
- // vault would be silently detached from its external parent.
1118
1019
  try {
1119
- await relinkExternalParents(storage, tree.id, graph.externalParents, new AbortController().signal);
1020
+ await relinkExternalParents(storage, tree.id, externalParents, new AbortController().signal);
1120
1021
  }
1121
1022
  catch (cleanupError) {
1122
1023
  throw new StorageError("delete cleanup is incomplete", {
@@ -1149,8 +1050,7 @@ export async function renameFile(storage, treeId, fileId, name, options) {
1149
1050
  const tree = await resolveTree(storage, treeId, signal);
1150
1051
  const branch = await resolveFile(storage, tree, fileId, signal);
1151
1052
  try {
1152
- const client = typeof storage.getClient === "function" ? storage.getClient() : undefined;
1153
- await withRateLimitRetry(() => client ? withMatrixMutationAbort(client, () => branch.setName(name), signal) : branch.setName(name), signal);
1053
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => branch.setName(name), signal), signal);
1154
1054
  // `setName` resolves when the homeserver accepts the state event, but a
1155
1055
  // fresh CLI/UI process can still read the previous local room state for a
1156
1056
  // short time. Do not report success until this client has observed the new
@@ -1181,7 +1081,7 @@ async function deleteFileMedia(storage, mediaIds, signal) {
1181
1081
  const client = storage.getClient();
1182
1082
  const http = requireFileDeletionTransport(client);
1183
1083
  try {
1184
- await withRateLimitRetry(() => withMatrixMutationAbort(client, () => http.authedRequest(Method.Post, "/io.telecrypt.storage/delete_media", undefined, { media_ids: [...mediaIds] }, { prefix: ClientPrefix.Unstable, rawResponseBody: true, abortSignal: signal }), signal, "delete file media"), signal);
1084
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => http.authedRequest(Method.Post, "/io.telecrypt.storage/delete_media", undefined, { media_ids: [...mediaIds] }, { prefix: ClientPrefix.Unstable, rawResponseBody: true, abortSignal: signal }), signal, "delete file media"), signal);
1185
1085
  }
1186
1086
  catch (error) {
1187
1087
  if (error instanceof MutationOutcomeUnknownError)
@@ -1209,11 +1109,9 @@ export async function deleteFile(storage, treeId, fileId, options) {
1209
1109
  await deleteFileMedia(storage, [mediaId], signal);
1210
1110
  const completedIds = [];
1211
1111
  try {
1212
- const client = typeof storage.getClient === "function" ? storage.getClient() : undefined;
1213
- if (!client)
1214
- throw new StorageError("Matrix client unavailable");
1215
- await withRateLimitRetry(() => withMatrixMutationAbort(client, () => client.sendStateEvent(tree.id, UNSTABLE_MSC3089_BRANCH.name, {}, branch.id), signal, "delete file state"), signal);
1216
- await withRateLimitRetry(() => withMatrixMutationAbort(client, () => client.redactEvent(tree.id, branch.id), signal, "delete file event"), signal);
1112
+ const client = storage.getClient();
1113
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => client.sendStateEvent(tree.id, UNSTABLE_MSC3089_BRANCH.name, {}, branch.id), signal, "delete file state"), signal);
1114
+ await withRateLimitRetry(() => withMatrixMutationAbort(() => client.redactEvent(tree.id, branch.id), signal, "delete file event"), signal);
1217
1115
  completedIds.push(branch.id);
1218
1116
  }
1219
1117
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telecrypt-io/storage",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "license": "BUSL-1.1",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.19.0",
@@ -25,12 +25,10 @@
25
25
  "node": "24.20.0"
26
26
  },
27
27
  "publishConfig": {
28
- "access": "public",
29
- "provenance": true
28
+ "access": "public"
30
29
  },
31
30
  "scripts": {
32
31
  "build": "tsc",
33
- "typecheck:tests": "tsc -p tsconfig.test.json",
34
32
  "lint": "eslint src test --ext .ts",
35
33
  "test": "vitest run",
36
34
  "test:unit": "vitest run --config vitest.unit.config.ts",