@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lacrous/omnicloud",
3
- "version": "0.2.21",
3
+ "version": "0.2.23",
4
4
  "description": "SDK and core building blocks for OmniCloud — self-hosted cloud storage powered by your Telegram account.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -48,9 +48,9 @@
48
48
  "prisma": "^6.2.0"
49
49
  },
50
50
  "devDependencies": {
51
- "@omnicloud/core": "0.2.21",
52
- "@omnicloud/shared": "0.2.21",
53
- "@omnicloud/telegram": "0.2.21",
51
+ "@omnicloud/core": "0.2.23",
52
+ "@omnicloud/shared": "0.2.23",
53
+ "@omnicloud/telegram": "0.2.23",
54
54
  "@types/node": "^22.10.0",
55
55
  "esbuild": "0.25.12",
56
56
  "tsup": "^8.3.0",
package/server/index.js CHANGED
@@ -1760,11 +1760,11 @@ var FolderService = class {
1760
1760
  const fileIds = await this.collectFileIds(userId, subtree);
1761
1761
  const batchId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
1762
1762
  const stampedAt = /* @__PURE__ */ new Date();
1763
- await this.folders.updateMany(
1763
+ await this.folders.trashSubtree(
1764
1764
  subtree.map((folder) => folder.id),
1765
+ fileIds,
1765
1766
  { deletedAt: stampedAt, trashBatchId: batchId }
1766
1767
  );
1767
- await this.files.updateMany(fileIds, { deletedAt: stampedAt, trashBatchId: batchId });
1768
1768
  await this.activity.record({
1769
1769
  userId,
1770
1770
  action: "trash",
@@ -1833,9 +1833,16 @@ var FolderService = class {
1833
1833
  );
1834
1834
  const engine = await this.engineFor(userId);
1835
1835
  const removedFileIds = [];
1836
+ const versionsByFile = await this.files.listVersionsForFiles(affected.map((file) => file.id));
1836
1837
  for (const file of affected) {
1837
1838
  try {
1838
- await engine.remove({ messageId: String(file.telegramMessageId) });
1839
+ const messageIds = /* @__PURE__ */ new Set([String(file.telegramMessageId)]);
1840
+ for (const version of versionsByFile.get(file.id) ?? []) {
1841
+ messageIds.add(String(version.telegramMessageId));
1842
+ }
1843
+ for (const messageId of messageIds) {
1844
+ await engine.remove({ messageId });
1845
+ }
1839
1846
  removedFileIds.push(file.id);
1840
1847
  } catch {
1841
1848
  }
@@ -2440,11 +2447,15 @@ function mapTelegramError(error, fallbackMessage) {
2440
2447
  if (error instanceof DomainError) return error;
2441
2448
  if (isTelegramRpcError(error)) {
2442
2449
  const msg = error.errorMessage ?? "";
2443
- if (msg.startsWith("FLOOD_WAIT_")) {
2444
- const seconds = Number(msg.slice("FLOOD_WAIT_".length)) || 60;
2445
- return new RateLimitedError(`Telegram rate limit reached \u2014 retry in ${seconds} seconds`, {
2446
- retryAfterSeconds: seconds
2447
- });
2450
+ const seconds = error.seconds;
2451
+ const floodSeconds = typeof seconds === "number" ? seconds : msg.startsWith("FLOOD_WAIT_") ? Number(msg.slice("FLOOD_WAIT_".length)) : null;
2452
+ if (msg.startsWith("FLOOD") && floodSeconds !== null && floodSeconds > 0) {
2453
+ return new RateLimitedError(
2454
+ `Telegram rate limit reached \u2014 retry in ${floodSeconds} seconds`,
2455
+ {
2456
+ retryAfterSeconds: floodSeconds
2457
+ }
2458
+ );
2448
2459
  }
2449
2460
  switch (msg) {
2450
2461
  case "PHONE_NUMBER_INVALID":
@@ -2474,6 +2485,13 @@ function mapTelegramError(error, fallbackMessage) {
2474
2485
  case "CHANNEL_INVALID":
2475
2486
  case "CHAT_ID_INVALID":
2476
2487
  return new StorageNotInitializedError();
2488
+ // Permanent: the account cannot write to the channel. Retrying cannot help.
2489
+ case "CHAT_WRITE_FORBIDDEN":
2490
+ case "CHAT_ADMIN_REQUIRED":
2491
+ return new ForbiddenError("OmniCloud cannot write to the storage channel for this account");
2492
+ // Permanent: the stored channel reference is stale. Retrying with the same reference fails too.
2493
+ case "PEER_ID_INVALID":
2494
+ return new StorageNotInitializedError();
2477
2495
  case "MSG_ID_INVALID":
2478
2496
  case "MESSAGE_ID_INVALID":
2479
2497
  return new TelegramFileNotFoundError();
@@ -2875,7 +2893,9 @@ var TelegramStorageProvider = class {
2875
2893
  try {
2876
2894
  await this.client.deleteMessages(this.peer, [Number(ref.messageId)], { revoke: true });
2877
2895
  } catch (error) {
2878
- throw mapTelegramError(error, "Telegram delete failed");
2896
+ const mapped = mapTelegramError(error, "Telegram delete failed");
2897
+ if (mapped instanceof TelegramFileNotFoundError) return;
2898
+ throw mapped;
2879
2899
  }
2880
2900
  }
2881
2901
  async exists(ref) {
@@ -3778,6 +3798,22 @@ function createFolderRepo(prisma) {
3778
3798
  });
3779
3799
  return mapFolder(folder);
3780
3800
  },
3801
+ async trashSubtree(folderIds, fileIds, stamp) {
3802
+ await prisma.$transaction(async (tx) => {
3803
+ if (folderIds.length > 0) {
3804
+ await tx.folder.updateMany({
3805
+ where: { id: { in: folderIds } },
3806
+ data: { deletedAt: stamp.deletedAt, trashBatchId: stamp.trashBatchId }
3807
+ });
3808
+ }
3809
+ if (fileIds.length > 0) {
3810
+ await tx.file.updateMany({
3811
+ where: { id: { in: fileIds } },
3812
+ data: { deletedAt: stamp.deletedAt, trashBatchId: stamp.trashBatchId }
3813
+ });
3814
+ }
3815
+ });
3816
+ },
3781
3817
  async updateMany(ids, patch) {
3782
3818
  const result = await prisma.folder.updateMany({
3783
3819
  where: { id: { in: ids } },
@@ -5032,7 +5068,7 @@ function encryptionCheck(configured) {
5032
5068
  }
5033
5069
 
5034
5070
  // ../../apps/api/src/routes/storage.ts
5035
- var VERSION = "0.2.21";
5071
+ var VERSION = "0.2.23";
5036
5072
  function registerStorageRoutes(app2, container2) {
5037
5073
  app2.get("/api/health", async () => {
5038
5074
  const database = await probeDatabase(container2);