@lacrous/omnicloud 0.2.21 → 0.2.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.
package/dist/index.d.cts CHANGED
@@ -909,6 +909,14 @@ interface FolderRepository {
909
909
  deletedAt?: Date | null;
910
910
  trashBatchId?: string | null;
911
911
  }): Promise<number>;
912
+ /**
913
+ * Trashes a folder subtree and the files inside it in one atomic step, so a
914
+ * failure cannot leave the folders trashed while their files stay active.
915
+ */
916
+ trashSubtree(folderIds: string[], fileIds: string[], stamp: {
917
+ deletedAt: Date;
918
+ trashBatchId: string;
919
+ }): Promise<void>;
912
920
  deleteMany(ids: string[]): Promise<void>;
913
921
  countByUser(userId: string): Promise<number>;
914
922
  }
package/dist/index.d.ts CHANGED
@@ -909,6 +909,14 @@ interface FolderRepository {
909
909
  deletedAt?: Date | null;
910
910
  trashBatchId?: string | null;
911
911
  }): Promise<number>;
912
+ /**
913
+ * Trashes a folder subtree and the files inside it in one atomic step, so a
914
+ * failure cannot leave the folders trashed while their files stay active.
915
+ */
916
+ trashSubtree(folderIds: string[], fileIds: string[], stamp: {
917
+ deletedAt: Date;
918
+ trashBatchId: string;
919
+ }): Promise<void>;
912
920
  deleteMany(ids: string[]): Promise<void>;
913
921
  countByUser(userId: string): Promise<number>;
914
922
  }
package/dist/index.js CHANGED
@@ -2114,11 +2114,11 @@ var FolderService = class {
2114
2114
  const fileIds = await this.collectFileIds(userId, subtree);
2115
2115
  const batchId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
2116
2116
  const stampedAt = /* @__PURE__ */ new Date();
2117
- await this.folders.updateMany(
2117
+ await this.folders.trashSubtree(
2118
2118
  subtree.map((folder) => folder.id),
2119
+ fileIds,
2119
2120
  { deletedAt: stampedAt, trashBatchId: batchId }
2120
2121
  );
2121
- await this.files.updateMany(fileIds, { deletedAt: stampedAt, trashBatchId: batchId });
2122
2122
  await this.activity.record({
2123
2123
  userId,
2124
2124
  action: "trash",
@@ -2187,9 +2187,16 @@ var FolderService = class {
2187
2187
  );
2188
2188
  const engine = await this.engineFor(userId);
2189
2189
  const removedFileIds = [];
2190
+ const versionsByFile = await this.files.listVersionsForFiles(affected.map((file) => file.id));
2190
2191
  for (const file of affected) {
2191
2192
  try {
2192
- await engine.remove({ messageId: String(file.telegramMessageId) });
2193
+ const messageIds = /* @__PURE__ */ new Set([String(file.telegramMessageId)]);
2194
+ for (const version of versionsByFile.get(file.id) ?? []) {
2195
+ messageIds.add(String(version.telegramMessageId));
2196
+ }
2197
+ for (const messageId of messageIds) {
2198
+ await engine.remove({ messageId });
2199
+ }
2193
2200
  removedFileIds.push(file.id);
2194
2201
  } catch {
2195
2202
  }
@@ -2626,11 +2633,15 @@ function mapTelegramError(error, fallbackMessage) {
2626
2633
  if (error instanceof DomainError) return error;
2627
2634
  if (isTelegramRpcError(error)) {
2628
2635
  const msg = error.errorMessage ?? "";
2629
- if (msg.startsWith("FLOOD_WAIT_")) {
2630
- const seconds = Number(msg.slice("FLOOD_WAIT_".length)) || 60;
2631
- return new RateLimitedError(`Telegram rate limit reached \u2014 retry in ${seconds} seconds`, {
2632
- retryAfterSeconds: seconds
2633
- });
2636
+ const seconds = error.seconds;
2637
+ const floodSeconds = typeof seconds === "number" ? seconds : msg.startsWith("FLOOD_WAIT_") ? Number(msg.slice("FLOOD_WAIT_".length)) : null;
2638
+ if (msg.startsWith("FLOOD") && floodSeconds !== null && floodSeconds > 0) {
2639
+ return new RateLimitedError(
2640
+ `Telegram rate limit reached \u2014 retry in ${floodSeconds} seconds`,
2641
+ {
2642
+ retryAfterSeconds: floodSeconds
2643
+ }
2644
+ );
2634
2645
  }
2635
2646
  switch (msg) {
2636
2647
  case "PHONE_NUMBER_INVALID":
@@ -2660,6 +2671,13 @@ function mapTelegramError(error, fallbackMessage) {
2660
2671
  case "CHANNEL_INVALID":
2661
2672
  case "CHAT_ID_INVALID":
2662
2673
  return new StorageNotInitializedError();
2674
+ // Permanent: the account cannot write to the channel. Retrying cannot help.
2675
+ case "CHAT_WRITE_FORBIDDEN":
2676
+ case "CHAT_ADMIN_REQUIRED":
2677
+ return new ForbiddenError("OmniCloud cannot write to the storage channel for this account");
2678
+ // Permanent: the stored channel reference is stale. Retrying with the same reference fails too.
2679
+ case "PEER_ID_INVALID":
2680
+ return new StorageNotInitializedError();
2663
2681
  case "MSG_ID_INVALID":
2664
2682
  case "MESSAGE_ID_INVALID":
2665
2683
  return new TelegramFileNotFoundError();
@@ -3064,7 +3082,9 @@ var TelegramStorageProvider = class {
3064
3082
  try {
3065
3083
  await this.client.deleteMessages(this.peer, [Number(ref.messageId)], { revoke: true });
3066
3084
  } catch (error) {
3067
- throw mapTelegramError(error, "Telegram delete failed");
3085
+ const mapped = mapTelegramError(error, "Telegram delete failed");
3086
+ if (mapped instanceof TelegramFileNotFoundError) return;
3087
+ throw mapped;
3068
3088
  }
3069
3089
  }
3070
3090
  async exists(ref) {