@lacrous/omnicloud 0.2.31 → 0.2.33
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.cjs +91 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +91 -3
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/server/index.js +2 -2
- package/server/web/apple-touch-icon.png +0 -0
- package/server/web/favicon.svg +15 -7
- package/server/web/index.html +1 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../../shared/src/mime.ts","../../shared/src/query.ts","../../shared/src/index.ts","../../core/src/errors.ts","../../core/src/storage/engine.ts","../../core/src/storage/retry.ts","../../core/src/utils/hash.ts","../../core/src/services/query-resolver.ts","../../core/src/services/auth-session-service.ts","../../core/src/services/version-retention.ts","../../core/src/services/upload-operation.ts","../../core/src/utils/operation-log.ts","../../core/src/services/activity-service.ts","../../core/src/utils/filename.ts","../../core/src/utils/request-fingerprint.ts","../../core/src/services/file-service.ts","../../core/src/utils/folder-order.ts","../../core/src/services/folder-service.ts","../../core/src/services/search-service.ts","../../core/src/services/stats-service.ts","../../core/src/services/trash-service.ts","../../core/src/services/integrity-service.ts","../../core/src/services/recent-service.ts","../../core/src/utils/spool.ts","../../core/src/utils/secret-box.ts","../../telegram/src/client.ts","../../telegram/src/errors.ts","../../telegram/src/provider.ts","../../telegram/src/connection.ts","../../telegram/src/brand/channel-logo.ts"],"sourcesContent":["import type {\n ActivityPageDTO,\n BatchResultDTO,\n FolderMutationResultDTO,\n EmptyTrashResultDTO,\n FileDTO,\n FileVersionDTO,\n FoldersPageDTO,\n FilesPageDTO,\n FolderDTO,\n HealthDTO,\n IntegrityReportDTO,\n ListQuery,\n RecentPageDTO,\n SearchResultDTO,\n SessionInfo,\n StorageDTO,\n StorageHealthDTO,\n StorageStatsDTO,\n TelegramVerifyResponse,\n TrashPageDTO,\n UserDTO,\n} from \"@omnicloud/shared\";\n\n/**\n * Errors surfaced by the SDK. `code` mirrors the server's stable error codes\n * and `requestId` correlates with server logs.\n */\nexport class OmniCloudError extends Error {\n constructor(\n public readonly status: number,\n public readonly code: string,\n message: string,\n public readonly requestId: string | undefined = undefined,\n public readonly details: unknown = undefined,\n ) {\n super(message);\n this.name = \"OmniCloudError\";\n }\n}\n\nexport interface RetryOptions {\n /** Total attempts including the first. Default 3. */\n attempts?: number;\n /** Base backoff in ms (doubles per retry). Default 300. */\n baseDelayMs?: number;\n}\n\nexport interface OmniCloudClientOptions {\n /** Base URL of an OmniCloud server (e.g. \"https://cloud.example\"). */\n baseUrl?: string;\n /** Bearer token, when the deployment uses token auth instead of cookies. */\n token?: string;\n /** Custom fetch implementation (tests, polyfills). */\n fetchImpl?: typeof fetch;\n /** Retry policy for transient failures. */\n retry?: RetryOptions;\n /** Extra headers sent with every request. */\n headers?: Record<string, string>;\n}\n\nexport interface UploadProgress {\n loaded: number;\n total: number;\n percent: number;\n}\n\nexport interface UploadOptions {\n folderId?: string | null;\n onProgress?: (progress: UploadProgress) => void;\n /** Aborts the upload. */\n signal?: AbortSignal;\n /** Number of retry attempts for failed uploads. Default 2. */\n retry?: number;\n}\n\nexport interface UploadInput {\n /**\n * File content: a Blob/File, a Uint8Array/Buffer, a web ReadableStream, or (in\n * Node) a Readable stream. Streams are read in chunks; the caller never has to\n * build one large buffer.\n */\n data?: Blob | Uint8Array | ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>;\n /** Node only: a path to read the file from. Use instead of `data`. */\n path?: string;\n /** Filename; also used to derive the MIME type server-side. */\n name: string;\n}\n\nexport interface DownloadProgress {\n loaded: number;\n total: number | null;\n percent: number | null;\n}\n\nexport interface StreamedDownload {\n stream: ReadableStream<Uint8Array>;\n contentType: string;\n /** Declared byte length, or null when the server did not send one. */\n size: number | null;\n sha256: string | null;\n}\n\nexport interface DownloadResult {\n data: Buffer | Uint8Array;\n contentType: string;\n size: number;\n sha256: string | null;\n integrityVerified: boolean | null;\n}\n\ntype Query = Record<string, string | number | boolean | undefined | null>;\n\nconst DEFAULT_RETRY: Required<RetryOptions> = { attempts: 3, baseDelayMs: 300 };\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n reject(new OmniCloudError(0, \"ABORTED\", \"Request aborted\"));\n },\n { once: true },\n );\n });\n}\n\n/** HTTP methods that may be repeated without changing the outcome. */\nconst RETRY_SAFE_METHODS = new Set([\"GET\", \"HEAD\", \"PUT\", \"DELETE\", \"OPTIONS\"]);\n\nfunction isRetryableStatus(status: number): boolean {\n return status === 408 || status === 429 || status >= 500;\n}\n\n/** Builds a query string, skipping undefined/null/empty values. */\nfunction buildQuery(query: Query | ListQuery | undefined): string {\n const params = new URLSearchParams();\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null || value === \"\") continue;\n params.set(key, String(value));\n }\n }\n const encoded = params.toString();\n return encoded ? `?${encoded}` : \"\";\n}\n\nfunction safeJsonParse(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return null;\n }\n}\n\n/**\n * Naming: every endpoint group is a namespaced sub-client so calls read like\n *\n * cloud.files.list({ folderId })\n * cloud.trash.empty()\n * cloud.storage.stats()\n *\n * Works in browsers (cookie sessions, XHR upload progress) and Node\n * (fetch, Buffer downloads).\n */\nexport class OmniCloudClient {\n readonly files: FilesApi;\n readonly folders: FoldersApi;\n readonly trash: TrashApi;\n readonly storage: StorageApi;\n readonly search: SearchApi;\n readonly recent: RecentApi;\n readonly activity: ActivityApi;\n readonly auth: AuthApi;\n\n /** @internal */\n readonly baseUrl: string;\n /** @internal */\n readonly fetchImpl: typeof fetch;\n /** @internal */\n readonly retry: Required<RetryOptions>;\n /** @internal */\n readonly defaultHeaders: Record<string, string>;\n\n constructor(options: OmniCloudClientOptions = {}) {\n this.baseUrl = (options.baseUrl ?? \"\").replace(/\\/$/, \"\");\n this.fetchImpl = options.fetchImpl ?? fetch.bind(globalThis);\n this.retry = { ...DEFAULT_RETRY, ...options.retry };\n this.defaultHeaders = {\n ...(options.token ? { Authorization: `Bearer ${options.token}` } : {}),\n ...options.headers,\n };\n\n this.files = new FilesApi(this);\n this.folders = new FoldersApi(this);\n this.trash = new TrashApi(this);\n this.storage = new StorageApi(this);\n this.search = new SearchApi(this);\n this.recent = new RecentApi(this);\n this.activity = new ActivityApi(this);\n this.auth = new AuthApi(this);\n }\n\n /** Low-level request helper (retries transient failures). */\n async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n // Only methods that are safe to repeat are retried automatically. A POST or PATCH\n // may already have changed state when the server fails, so it is not repeated\n // unless the caller opts in with an idempotency key.\n if (!RETRY_SAFE_METHODS.has(method.toUpperCase())) {\n return this.rawRequest<T>(method, path, body);\n }\n return this.withRetry(async () => this.rawRequest<T>(method, path, body));\n }\n\n /** @internal */\n async rawRequest<T>(method: string, path: string, body?: unknown): Promise<T> {\n const isForm = typeof FormData !== \"undefined\" && body instanceof FormData;\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n method,\n headers: {\n ...this.defaultHeaders,\n ...(body !== undefined && !isForm ? { \"Content-Type\": \"application/json\" } : {}),\n },\n body: body === undefined ? undefined : isForm ? (body as FormData) : JSON.stringify(body),\n credentials: \"include\",\n });\n\n if (response.status === 204) return undefined as T;\n\n const text = await response.text();\n const data: unknown = text ? safeJsonParse(text) : null;\n\n if (!response.ok) {\n const error = (\n data as {\n error?: { code: string; message: string; requestId?: string; details?: unknown };\n } | null\n )?.error;\n throw new OmniCloudError(\n response.status,\n error?.code ?? \"INTERNAL_ERROR\",\n error?.message ?? `Request failed (${response.status})`,\n error?.requestId,\n error?.details,\n );\n }\n return data as T;\n }\n\n /** @internal */\n async withRetry<T>(operation: () => Promise<T>): Promise<T> {\n const { attempts, baseDelayMs } = this.retry;\n let lastError: unknown;\n for (let attempt = 1; attempt <= Math.max(1, attempts); attempt += 1) {\n try {\n return await operation();\n } catch (error) {\n lastError = error;\n const retryable = error instanceof OmniCloudError && isRetryableStatus(error.status);\n if (!retryable || attempt >= attempts) break;\n await sleep(baseDelayMs * 2 ** (attempt - 1));\n }\n }\n throw lastError;\n }\n\n /** @internal */\n /**\n * Opens a binary response as a stream. Bytes are delivered as they arrive, so\n * memory does not grow with the file. The stream errors with DOWNLOAD_INCOMPLETE\n * if the body ends before Content-Length.\n */\n async requestStream(\n path: string,\n onProgress?: (p: DownloadProgress) => void,\n ): Promise<StreamedDownload> {\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n headers: this.defaultHeaders,\n credentials: \"include\",\n });\n\n if (!response.ok) {\n const text = await response.text();\n const error = (safeJsonParse(text) as { error?: { code: string; message: string } } | null)\n ?.error;\n throw new OmniCloudError(\n response.status,\n error?.code ?? \"DOWNLOAD_FAILED\",\n error?.message ?? `Download failed (${response.status})`,\n );\n }\n\n const total = Number(response.headers.get(\"content-length\") ?? 0) || null;\n const contentType = response.headers.get(\"content-type\") ?? \"application/octet-stream\";\n const sha256 = response.headers.get(\"x-content-sha256\");\n const source = response.body;\n if (!source) {\n throw new OmniCloudError(0, \"DOWNLOAD_FAILED\", \"The response has no body to stream\");\n }\n\n let loaded = 0;\n const reader = source.getReader();\n const stream = new ReadableStream<Uint8Array>({\n async pull(controller) {\n const { done, value } = await reader.read();\n if (done) {\n if (total !== null && loaded !== total) {\n controller.error(\n new OmniCloudError(\n 0,\n \"DOWNLOAD_INCOMPLETE\",\n `Download ended after ${loaded} of ${total} bytes`,\n ),\n );\n return;\n }\n controller.close();\n return;\n }\n loaded += value.byteLength;\n onProgress?.({\n loaded,\n total,\n percent: total ? Math.round((loaded / total) * 100) : null,\n });\n controller.enqueue(value);\n },\n cancel(reason) {\n return reader.cancel(reason);\n },\n });\n\n return { stream, contentType, size: total, sha256 };\n }\n\n async requestBinary(\n path: string,\n onProgress?: (p: DownloadProgress) => void,\n ): Promise<DownloadResult> {\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n headers: this.defaultHeaders,\n credentials: \"include\",\n });\n\n if (!response.ok) {\n const text = await response.text();\n const error = (safeJsonParse(text) as { error?: { code: string; message: string } } | null)\n ?.error;\n throw new OmniCloudError(\n response.status,\n error?.code ?? \"DOWNLOAD_FAILED\",\n error?.message ?? `Download failed (${response.status})`,\n );\n }\n\n const total = Number(response.headers.get(\"content-length\") ?? 0) || null;\n const contentType = response.headers.get(\"content-type\") ?? \"application/octet-stream\";\n const sha256 = response.headers.get(\"x-content-sha256\");\n const integrityHeader = response.headers.get(\"x-integrity-verified\");\n const integrityVerified = integrityHeader === null ? null : integrityHeader === \"true\";\n\n const data = await readBody(response, total, onProgress);\n return { data, contentType, size: data.byteLength, sha256, integrityVerified };\n }\n}\n\n/**\n * Turns any supported upload source into a Blob. Streams are read chunk by chunk\n * into parts, so the source is never copied through an intermediate buffer.\n */\nasync function toBlob(input: UploadInput): Promise<Blob> {\n if (input.path !== undefined) {\n const { readFile } = await import(\"node:fs/promises\");\n return new Blob([new Uint8Array(await readFile(input.path))]);\n }\n const data = input.data;\n if (data === undefined) throw new OmniCloudError(0, \"INVALID_REQUEST\", \"No upload content given\");\n if (typeof Blob !== \"undefined\" && data instanceof Blob) return data;\n if (data instanceof Uint8Array) return new Blob([new Uint8Array(data)]);\n\n const parts: Uint8Array[] = [];\n if (typeof (data as ReadableStream<Uint8Array>).getReader === \"function\") {\n const reader = (data as ReadableStream<Uint8Array>).getReader();\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n parts.push(value);\n }\n } else {\n for await (const chunk of data as AsyncIterable<Uint8Array>) parts.push(chunk);\n }\n return new Blob(parts as BlobPart[]);\n}\n\n/** Reads a response body, reporting progress when the platform exposes it. */\nasync function readBody(\n response: Response,\n total: number | null,\n onProgress?: (p: DownloadProgress) => void,\n): Promise<Buffer | Uint8Array> {\n const body = response.body;\n if (!body) {\n const buffer = new Uint8Array(await response.arrayBuffer());\n onProgress?.({ loaded: buffer.byteLength, total: buffer.byteLength, percent: 100 });\n return buffer;\n }\n\n const chunks: Uint8Array[] = [];\n let loaded = 0;\n const reader = body.getReader();\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n chunks.push(value);\n loaded += value.byteLength;\n onProgress?.({\n loaded,\n total,\n percent: total ? Math.round((loaded / total) * 100) : null,\n });\n }\n }\n\n if (total !== null && loaded !== total) {\n throw new OmniCloudError(\n 0,\n \"DOWNLOAD_INCOMPLETE\",\n `Download ended after ${loaded} of ${total} bytes`,\n );\n }\n\n const merged = new Uint8Array(loaded);\n let offset = 0;\n for (const chunk of chunks) {\n merged.set(chunk, offset);\n offset += chunk.byteLength;\n }\n // Prefer Buffer in Node so callers can use Buffer APIs.\n const BufferCtor = (globalThis as { Buffer?: typeof Buffer }).Buffer;\n return BufferCtor ? BufferCtor.from(merged) : merged;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Namespaced APIs\n// ─────────────────────────────────────────────────────────────────────────────\n\nclass FilesApi {\n constructor(private readonly client: OmniCloudClient) {}\n\n /** `folderId: null` = root; omit it to list every active file. */\n async list(query: ListQuery = {}): Promise<FilesPageDTO> {\n return this.client.request<FilesPageDTO>(\"GET\", `/api/files${buildQuery(query)}`);\n }\n\n async get(id: string): Promise<FileDTO> {\n const { file } = await this.client.request<{ file: FileDTO }>(\"GET\", `/api/files/${id}`);\n return file;\n }\n\n async versions(id: string): Promise<FileVersionDTO[]> {\n const { versions } = await this.client.request<{ versions: FileVersionDTO[] }>(\n \"GET\",\n `/api/files/${id}/versions`,\n );\n return versions;\n }\n\n /**\n * Uploads a file with progress and retry support. Uses XMLHttpRequest when\n * available (browsers) so progress and cancellation work; falls back to\n * fetch elsewhere.\n */\n async upload(input: UploadInput, options: UploadOptions = {}): Promise<FileDTO> {\n const attempts = Math.max(1, (options.retry ?? 2) + 1);\n let lastError: unknown;\n // Convert the source once. A stream can be read only once, so every attempt\n // must send these same bytes rather than re-reading an exhausted source.\n const blob = await toBlob(input);\n\n for (let attempt = 1; attempt <= attempts; attempt += 1) {\n try {\n return await this.uploadOnce(blob, input.name, options);\n } catch (error) {\n lastError = error;\n if (options.signal?.aborted) throw error;\n if (error instanceof OmniCloudError && !isRetryableStatus(error.status)) throw error;\n if (attempt < attempts) await sleep(300 * 2 ** (attempt - 1), options.signal);\n }\n }\n throw lastError;\n }\n\n private async uploadOnce(blob: Blob, name: string, options: UploadOptions): Promise<FileDTO> {\n const form = new FormData();\n form.append(\"file\", blob, name);\n if (options.folderId) form.append(\"folderId\", options.folderId);\n\n if (typeof XMLHttpRequest === \"function\") {\n return this.uploadViaXhr(form, options);\n }\n return this.client\n .request<{ file: FileDTO }>(\"POST\", \"/api/files\", form)\n .then((response) => response.file);\n }\n\n private uploadViaXhr(form: FormData, options: UploadOptions): Promise<FileDTO> {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open(\"POST\", `${this.client.baseUrl}/api/files`);\n xhr.responseType = \"json\";\n xhr.withCredentials = true;\n\n const onAbort = () => xhr.abort();\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n xhr.upload.onprogress = (event) => {\n options.onProgress?.({\n loaded: event.loaded,\n total: event.total,\n percent: event.total > 0 ? Math.round((event.loaded / event.total) * 100) : 0,\n });\n };\n xhr.onload = () => {\n options.signal?.removeEventListener(\"abort\", onAbort);\n const body = xhr.response as {\n file?: FileDTO;\n error?: { code: string; message: string; requestId?: string };\n } | null;\n if (xhr.status >= 200 && xhr.status < 300 && body?.file) {\n options.onProgress?.({ loaded: 1, total: 1, percent: 100 });\n resolve(body.file);\n return;\n }\n reject(\n new OmniCloudError(\n xhr.status,\n body?.error?.code ?? \"UPLOAD_FAILED\",\n body?.error?.message ?? `Upload failed (${xhr.status})`,\n body?.error?.requestId,\n ),\n );\n };\n xhr.onerror = () =>\n reject(new OmniCloudError(0, \"NETWORK_ERROR\", \"Network error during upload\"));\n xhr.onabort = () => reject(new OmniCloudError(0, \"ABORTED\", \"Upload aborted\"));\n xhr.send(form);\n });\n }\n\n /** Uploads a new version of an existing file. */\n async replace(id: string, input: UploadInput): Promise<FileDTO> {\n const form = new FormData();\n const blob = await toBlob(input);\n form.append(\"file\", blob, input.name);\n const { file } = await this.client.request<{ file: FileDTO }>(\n \"POST\",\n `/api/files/${id}/replace`,\n form,\n );\n return file;\n }\n\n /**\n * Buffers the entire file in memory. Use `downloadToFile` or `downloadStream` for\n * large files.\n *\n * @deprecated Loads the whole file into memory. Use `downloadToFile` (Node) or\n * `downloadStream` (any runtime) for large files.\n */\n async download(id: string, onProgress?: (p: DownloadProgress) => void): Promise<DownloadResult> {\n return this.client.requestBinary(`/api/files/${id}/download`, onProgress);\n }\n\n /**\n * Streams a file's bytes without holding the whole file in memory. Use this for\n * large files. Read `stream` to the end: a truncated transfer errors instead of\n * ending quietly.\n */\n async downloadStream(\n id: string,\n onProgress?: (p: DownloadProgress) => void,\n ): Promise<StreamedDownload> {\n return this.client.requestStream(`/api/files/${id}/download`, onProgress);\n }\n\n /**\n * Node only. Streams a file to `targetPath` through a `.part` file that is\n * renamed into place only after the full length is verified. A truncated or\n * failed transfer leaves nothing at `targetPath`.\n */\n async downloadToFile(\n id: string,\n targetPath: string,\n onProgress?: (p: DownloadProgress) => void,\n ): Promise<{ path: string; bytes: number; sha256: string | null }> {\n const { createWriteStream } = await import(\"node:fs\");\n const { rename, rm } = await import(\"node:fs/promises\");\n const { Readable } = await import(\"node:stream\");\n const { pipeline } = await import(\"node:stream/promises\");\n\n const download = await this.downloadStream(id, onProgress);\n const partPath = `${targetPath}.part`;\n let bytes = 0;\n try {\n await pipeline(\n Readable.fromWeb(download.stream as never),\n async function* (source: AsyncIterable<Uint8Array>) {\n for await (const chunk of source) {\n bytes += chunk.byteLength;\n yield chunk;\n }\n },\n createWriteStream(partPath),\n );\n await rename(partPath, targetPath);\n } catch (error) {\n await rm(partPath, { force: true });\n throw error;\n }\n return { path: targetPath, bytes, sha256: download.sha256 };\n }\n\n /** URL suitable for direct download (anchor href). */\n downloadUrl(id: string): string {\n return `${this.client.baseUrl}/api/files/${id}/download`;\n }\n\n async rename(id: string, name: string): Promise<FileDTO> {\n const { file } = await this.client.request<{ file: FileDTO }>(\"PATCH\", `/api/files/${id}`, {\n name,\n });\n return file;\n }\n\n async star(id: string, starred = true): Promise<FileDTO> {\n const { file } = await this.client.request<{ file: FileDTO }>(\"PATCH\", `/api/files/${id}`, {\n starred,\n });\n return file;\n }\n\n async move(id: string, folderId: string | null): Promise<FileDTO> {\n const { file } = await this.client.request<{ file: FileDTO }>(\"POST\", `/api/files/${id}/move`, {\n folderId,\n });\n return file;\n }\n\n async trash(id: string): Promise<FileDTO> {\n const { file } = await this.client.request<{ file: FileDTO }>(\"POST\", `/api/files/${id}/trash`);\n return file;\n }\n\n async restore(id: string): Promise<FileDTO> {\n const { file } = await this.client.request<{ file: FileDTO }>(\n \"POST\",\n `/api/files/${id}/restore`,\n );\n return file;\n }\n\n /** Permanently deletes a file and its Telegram objects. */\n async delete(id: string): Promise<void> {\n await this.client.request<void>(\"DELETE\", `/api/files/${id}`);\n }\n\n /** Batch operation: trash | restore | delete | star | unstar | move. */\n async batch(\n operation: \"trash\" | \"restore\" | \"delete\" | \"star\" | \"unstar\" | \"move\",\n ids: string[],\n options: { folderId?: string | null } = {},\n ): Promise<BatchResultDTO> {\n return this.client.request<BatchResultDTO>(\"POST\", \"/api/files/batch\", {\n operation,\n ids,\n ...(operation === \"move\" ? { folderId: options.folderId ?? null } : {}),\n });\n }\n}\n\nclass FoldersApi {\n constructor(private readonly client: OmniCloudClient) {}\n\n async list(query: ListQuery & { parentId?: string | null } = {}): Promise<FoldersPageDTO> {\n const { parentId, ...rest } = query;\n // Keep the paging and sort fields when filtering by parent, so page 2 is not\n // silently answered with page 1.\n const base = buildQuery(rest);\n if (parentId === undefined)\n return this.client.request<FoldersPageDTO>(\"GET\", `/api/folders${base}`);\n // The server treats an empty parentId as the root, so it must be sent explicitly.\n const separator = base === \"\" ? \"?\" : \"&\";\n const path = `/api/folders${base}${separator}parentId=${encodeURIComponent(parentId ?? \"\")}`;\n return this.client.request<FoldersPageDTO>(\"GET\", path);\n }\n\n /** The full active folder tree (for move dialogs). */\n async tree(): Promise<FolderDTO[]> {\n const { folders } = await this.client.request<{ folders: FolderDTO[] }>(\n \"GET\",\n \"/api/folders/tree\",\n );\n return folders;\n }\n\n async create(name: string, parentId: string | null = null): Promise<FolderDTO> {\n const { folder } = await this.client.request<{ folder: FolderDTO }>(\"POST\", \"/api/folders\", {\n name,\n parentId,\n });\n return folder;\n }\n\n async rename(id: string, name: string): Promise<FolderDTO> {\n const { folder } = await this.client.request<{ folder: FolderDTO }>(\n \"PATCH\",\n `/api/folders/${id}`,\n { name },\n );\n return folder;\n }\n\n async star(id: string, starred = true): Promise<FolderDTO> {\n const { folder } = await this.client.request<{ folder: FolderDTO }>(\n \"PATCH\",\n `/api/folders/${id}`,\n { starred },\n );\n return folder;\n }\n\n async move(id: string, parentId: string | null): Promise<FolderDTO> {\n const { folder } = await this.client.request<{ folder: FolderDTO }>(\n \"POST\",\n `/api/folders/${id}/move`,\n { parentId },\n );\n return folder;\n }\n\n async trash(id: string): Promise<FolderMutationResultDTO> {\n return this.client.request<FolderMutationResultDTO>(\"POST\", `/api/folders/${id}/trash`);\n }\n\n async restore(id: string): Promise<FolderMutationResultDTO> {\n return this.client.request<FolderMutationResultDTO>(\"POST\", `/api/folders/${id}/restore`);\n }\n\n /** Permanently deletes the folder subtree and its Telegram objects. */\n async delete(id: string): Promise<FolderMutationResultDTO> {\n return this.client.request<FolderMutationResultDTO>(\"DELETE\", `/api/folders/${id}`);\n }\n\n async batch(\n operation: \"trash\" | \"restore\" | \"delete\" | \"star\" | \"unstar\" | \"move\",\n ids: string[],\n options: { parentId?: string | null } = {},\n ): Promise<BatchResultDTO> {\n return this.client.request<BatchResultDTO>(\"POST\", \"/api/folders/batch\", {\n operation,\n ids,\n ...(operation === \"move\" ? { parentId: options.parentId ?? null } : {}),\n });\n }\n}\n\nclass TrashApi {\n constructor(private readonly client: OmniCloudClient) {}\n\n async list(query: ListQuery = {}): Promise<TrashPageDTO> {\n return this.client.request<TrashPageDTO>(\"GET\", `/api/trash${buildQuery(query)}`);\n }\n\n /** Permanently removes everything in the Trash. */\n async empty(): Promise<EmptyTrashResultDTO> {\n return this.client.request<EmptyTrashResultDTO>(\"POST\", \"/api/trash/empty\");\n }\n}\n\nclass StorageApi {\n constructor(private readonly client: OmniCloudClient) {}\n\n /** Creates the private storage channel if it does not exist yet. */\n async ensure(): Promise<StorageDTO> {\n const { storage } = await this.client.request<{ storage: StorageDTO }>(\n \"POST\",\n \"/api/storage/ensure\",\n );\n return storage;\n }\n\n async stats(): Promise<StorageStatsDTO> {\n const { stats } = await this.client.request<{ stats: StorageStatsDTO }>(\n \"GET\",\n \"/api/storage/stats\",\n );\n return stats;\n }\n\n async health(): Promise<{ health: StorageHealthDTO; stats: StorageStatsDTO }> {\n return this.client.request(\"GET\", \"/api/storage/health\");\n }\n\n /** Probes the storage backend; `deep` performs a real Telegram round-trip. */\n async check(deep = false): Promise<StorageHealthDTO> {\n const { health } = await this.client.request<{ health: StorageHealthDTO }>(\n \"POST\",\n \"/api/storage/health/check\",\n { deep },\n );\n return health;\n }\n\n /** Read-only integrity scan; nothing is repaired or deleted. */\n async integrityCheck(options: { deep?: boolean } = {}): Promise<IntegrityReportDTO> {\n const { report } = await this.client.request<{ report: IntegrityReportDTO }>(\n \"POST\",\n \"/api/storage/integrity/check\",\n { deep: options.deep ?? false },\n );\n return report;\n }\n\n /** Public server health (no authentication required). */\n async serverHealth(): Promise<HealthDTO> {\n return this.client.request<HealthDTO>(\"GET\", \"/api/health\");\n }\n}\n\nclass SearchApi {\n constructor(private readonly client: OmniCloudClient) {}\n\n /**\n * Searches with the v0.2 query language:\n * `type:pdf`, `size:>100MB`, `folder:Projects`, `starred:true`, `after:2026-01-01`\n */\n async query(q: string, page: { page?: number; limit?: number } = {}): Promise<SearchResultDTO> {\n return this.client.request<SearchResultDTO>(\n \"GET\",\n `/api/search${buildQuery({ q, ...page } as Query)}`,\n );\n }\n}\n\nclass RecentApi {\n constructor(private readonly client: OmniCloudClient) {}\n\n async list(page: { page?: number; limit?: number } = {}): Promise<RecentPageDTO> {\n return this.client.request<RecentPageDTO>(\"GET\", `/api/recent${buildQuery(page as Query)}`);\n }\n}\n\nclass ActivityApi {\n constructor(private readonly client: OmniCloudClient) {}\n\n async list(page: { page?: number; limit?: number } = {}): Promise<ActivityPageDTO> {\n return this.client.request<ActivityPageDTO>(\"GET\", `/api/activity${buildQuery(page as Query)}`);\n }\n}\n\nclass AuthApi {\n constructor(private readonly client: OmniCloudClient) {}\n\n async me(): Promise<SessionInfo> {\n return this.client.request<SessionInfo>(\"GET\", \"/api/auth/me\");\n }\n\n async startTelegramLogin(phone: string): Promise<void> {\n await this.client.request(\"POST\", \"/api/auth/telegram/start\", { phone });\n }\n\n async verifyTelegramCode(phone: string, code: string): Promise<TelegramVerifyResponse> {\n return this.client.request<TelegramVerifyResponse>(\"POST\", \"/api/auth/telegram/verify\", {\n phone,\n code,\n });\n }\n\n async submitTelegramPassword(\n phone: string,\n password: string,\n ): Promise<{ status: \"ok\"; user: UserDTO }> {\n return this.client.request(\"POST\", \"/api/auth/telegram/password\", { phone, password });\n }\n\n async logout(): Promise<void> {\n await this.client.request(\"POST\", \"/api/auth/logout\");\n }\n}\n","/**\n * Filename-extension based MIME type detection.\n *\n * MIME types supplied by clients are never trusted: the API derives the MIME\n * type from the filename extension server-side. Shared so the web UI can use\n * the same logic for icons.\n */\n\nconst MIME_BY_EXTENSION: Record<string, string> = {\n // Documents\n pdf: \"application/pdf\",\n doc: \"application/msword\",\n docx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n xls: \"application/vnd.ms-excel\",\n xlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n ppt: \"application/vnd.ms-powerpoint\",\n pptx: \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n odt: \"application/vnd.oasis.opendocument.text\",\n ods: \"application/vnd.oasis.opendocument.spreadsheet\",\n txt: \"text/plain\",\n md: \"text/markdown\",\n csv: \"text/csv\",\n rtf: \"application/rtf\",\n html: \"text/html\",\n htm: \"text/html\",\n css: \"text/css\",\n xml: \"application/xml\",\n yaml: \"application/yaml\",\n yml: \"application/yaml\",\n json: \"application/json\",\n epub: \"application/epub+zip\",\n\n // Images\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n svg: \"image/svg+xml\",\n bmp: \"image/bmp\",\n ico: \"image/x-icon\",\n tiff: \"image/tiff\",\n tif: \"image/tiff\",\n avif: \"image/avif\",\n heic: \"image/heic\",\n\n // Audio\n mp3: \"audio/mpeg\",\n wav: \"audio/wav\",\n ogg: \"audio/ogg\",\n flac: \"audio/flac\",\n m4a: \"audio/mp4\",\n aac: \"audio/aac\",\n opus: \"audio/opus\",\n\n // Video\n mp4: \"video/mp4\",\n mkv: \"video/x-matroska\",\n webm: \"video/webm\",\n avi: \"video/x-msvideo\",\n mov: \"video/quicktime\",\n wmv: \"video/x-ms-wmv\",\n flv: \"video/x-flv\",\n\n // Archives\n zip: \"application/zip\",\n rar: \"application/vnd.rar\",\n \"7z\": \"application/x-7z-compressed\",\n tar: \"application/x-tar\",\n gz: \"application/gzip\",\n bz2: \"application/x-bzip2\",\n xz: \"application/x-xz\",\n\n // Other\n apk: \"application/vnd.android.package-archive\",\n exe: \"application/x-msdownload\",\n dmg: \"application/x-apple-diskimage\",\n iso: \"application/x-iso9660-image\",\n bin: \"application/octet-stream\",\n};\n\nexport const DEFAULT_MIME_TYPE = \"application/octet-stream\";\n\n/** Returns the file extension without the leading dot, lowercased. */\nexport function fileExtension(filename: string): string {\n const base = filename.split(/[/\\\\]/).pop() ?? \"\";\n const dot = base.lastIndexOf(\".\");\n if (dot <= 0 || dot === base.length - 1) return \"\";\n return base.slice(dot + 1).toLowerCase();\n}\n\n/** Derives a MIME type from the filename extension. Never trusts client input. */\nexport function mimeFromFilename(filename: string): string {\n return MIME_BY_EXTENSION[fileExtension(filename)] ?? DEFAULT_MIME_TYPE;\n}\n\nexport type FileCategory =\n \"image\" | \"video\" | \"audio\" | \"pdf\" | \"archive\" | \"document\" | \"text\" | \"other\";\n\nconst PREFIXED_CATEGORIES: Array<[string, FileCategory]> = [\n [\"image/\", \"image\"],\n [\"video/\", \"video\"],\n [\"audio/\", \"audio\"],\n [\"application/pdf\", \"pdf\"],\n [\"application/zip\", \"archive\"],\n [\"application/x-tar\", \"archive\"],\n [\"application/gzip\", \"archive\"],\n [\"application/x-7z-compressed\", \"archive\"],\n [\"application/vnd.rar\", \"archive\"],\n [\"application/x-bzip2\", \"archive\"],\n [\"application/x-xz\", \"archive\"],\n [\"text/\", \"text\"],\n [\"application/json\", \"text\"],\n [\"application/xml\", \"text\"],\n [\"application/yaml\", \"text\"],\n [\"application/javascript\", \"text\"],\n [\"text/markdown\", \"text\"],\n];\n\nconst OFFICE_MIMES = new Set([\n \"application/msword\",\n \"application/vnd.ms-excel\",\n \"application/vnd.ms-powerpoint\",\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n \"application/vnd.oasis.opendocument.text\",\n \"application/vnd.oasis.opendocument.spreadsheet\",\n \"application/rtf\",\n \"application/epub+zip\",\n]);\n\n/** Coarse category used for icon selection in the UI. */\nexport function fileCategory(mimeType: string): FileCategory {\n for (const [prefix, category] of PREFIXED_CATEGORIES) {\n if (mimeType.startsWith(prefix)) return category;\n }\n if (OFFICE_MIMES.has(mimeType)) return \"document\";\n return \"other\";\n}\n\nexport const FILE_CATEGORIES: readonly FileCategory[] = [\n \"image\",\n \"video\",\n \"audio\",\n \"pdf\",\n \"archive\",\n \"document\",\n \"text\",\n \"other\",\n] as const;\n\n/** MIME prefixes belonging to each category, for repository-level filtering. */\nconst CATEGORY_PREFIXES: Record<FileCategory, readonly string[]> = {\n image: [\"image/\"],\n video: [\"video/\"],\n audio: [\"audio/\"],\n pdf: [\"application/pdf\"],\n archive: [\n \"application/zip\",\n \"application/x-tar\",\n \"application/gzip\",\n \"application/x-7z-compressed\",\n \"application/vnd.rar\",\n \"application/x-bzip2\",\n \"application/x-xz\",\n ],\n text: [\n \"text/\",\n \"application/json\",\n \"application/xml\",\n \"application/yaml\",\n \"application/javascript\",\n ],\n document: [...OFFICE_MIMES],\n other: [],\n};\n\nexport function isFileCategory(value: string): value is FileCategory {\n return (FILE_CATEGORIES as readonly string[]).includes(value);\n}\n\n/**\n * Expands a `type:` filter into a set of MIME matchers.\n *\n * Accepts either a coarse category (\"image\", \"pdf\") or an exact MIME type\n * (\"application/pdf\"); the repository layer turns these into prefix/equality\n * filters. Returns null when the value matches nothing usable.\n */\nexport function mimeMatchersForType(value: string): readonly string[] | null {\n const normalized = value.toLowerCase().trim();\n if (!normalized) return null;\n if (isFileCategory(normalized)) {\n const prefixes = CATEGORY_PREFIXES[normalized];\n return prefixes.length > 0 ? prefixes : null;\n }\n // Exact MIME type — match it as an exact value.\n return [normalized];\n}\n","/**\n * Search query parsing, pagination and sorting primitives.\n *\n * Self-contained (no runtime imports from the package root) so the API, web\n * app and SDK can all share one query language without import cycles.\n *\n * Query language (v0.2):\n * report → name contains \"report\"\n * name:report → same, explicit\n * type:pdf type:image type:application/zip\n * ext:zip → file extension (no dot)\n * size:>100MB size:<1KB size:5mb\n * folder:Projects → folder-name filter\n * starred:true trashed:true\n * after:2026-01-01 before:2026-06-01\n */\n\nexport const SORT_FIELDS = [\"name\", \"size\", \"createdAt\", \"updatedAt\", \"type\"] as const;\nexport type SortField = (typeof SORT_FIELDS)[number];\n\nexport const SORT_ORDERS = [\"asc\", \"desc\"] as const;\nexport type SortOrder = (typeof SORT_ORDERS)[number];\n\nexport const ITEM_STATUSES = [\"active\", \"trashed\", \"all\"] as const;\nexport type ItemStatus = (typeof ITEM_STATUSES)[number];\n\nexport const DEFAULT_PAGE_SIZE = 50;\nexport const MAX_PAGE_SIZE = 200;\n\n/** Query parameters accepted by the file/folder listing endpoints. */\nexport interface ListQuery {\n folderId?: string | null;\n page?: number;\n limit?: number;\n sort?: SortField;\n order?: SortOrder;\n /** MIME type or coarse category (image, video, pdf, document, …). */\n type?: string;\n /** File extension without the dot. */\n ext?: string;\n starred?: boolean;\n status?: ItemStatus;\n /** Name substring filter. */\n q?: string;\n /** Folder-name filter (search parser); resolved to ids by the caller. */\n folderName?: string;\n minSize?: number;\n maxSize?: number;\n /** Inclusive date bounds (ISO strings). */\n from?: string;\n to?: string;\n}\n\nconst SIZE_UNITS: Record<string, number> = {\n b: 1,\n kb: 1024,\n mb: 1024 ** 2,\n gb: 1024 ** 3,\n tb: 1024 ** 4,\n};\n\n/** Parses \"100MB\", \"1.5gb\", \"512\" into bytes. Returns null when unparseable. */\nexport function parseSize(value: string): number | null {\n const match = /^\\s*([0-9]+(?:\\.[0-9]+)?)\\s*([a-zA-Z]*)\\s*$/.exec(value);\n if (!match) return null;\n const amount = Number(match[1]);\n if (!Number.isFinite(amount) || amount < 0) return null;\n const unit = (match[2] ?? \"\").toLowerCase() || \"b\";\n const factor = SIZE_UNITS[unit];\n if (factor === undefined) return null;\n return Math.round(amount * factor);\n}\n\n/** Parses a size comparison such as \">100MB\", \"<=2gb\", \"5mb\". */\nexport function parseSizeComparison(value: string): { min?: number; max?: number } | null {\n const match = /^\\s*(>=|<=|>|<|=)?\\s*(.+)$/.exec(value);\n if (!match) return null;\n const operator = match[1] ?? \"=\";\n const bytes = parseSize(match[2]!);\n if (bytes === null) return null;\n\n switch (operator) {\n case \">\":\n return { min: bytes + 1 };\n case \">=\":\n return { min: bytes };\n case \"<\":\n return { max: bytes - 1 };\n case \"<=\":\n return { max: bytes };\n default:\n return { min: bytes, max: bytes };\n }\n}\n\nfunction parseBoolean(value: string): boolean | null {\n const normalized = value.toLowerCase();\n if ([\"true\", \"1\", \"yes\", \"y\", \"on\"].includes(normalized)) return true;\n if ([\"false\", \"0\", \"no\", \"n\", \"off\"].includes(normalized)) return false;\n return null;\n}\n\nfunction parseDate(value: string, endOfDay: boolean): Date | null {\n const trimmed = value.trim();\n // Plain dates are interpreted as the start (or end) of that UTC day so\n // `after:2026-01-01` and `before:2026-01-01` behave intuitively.\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(trimmed)) {\n const date = new Date(`${trimmed}T${endOfDay ? \"23:59:59.999\" : \"00:00:00.000\"}Z`);\n return Number.isNaN(date.getTime()) ? null : date;\n }\n const date = new Date(trimmed);\n return Number.isNaN(date.getTime()) ? null : date;\n}\n\n/** Parses a raw user query string into structured filters. */\nexport function parseSearchQuery(\n input: string,\n base: ListQuery = {},\n): { query: ListQuery; freeText: string } {\n const query: ListQuery = { ...base };\n const terms: string[] = [];\n\n for (const rawToken of input.trim().split(/\\s+/)) {\n if (!rawToken) continue;\n\n const separator = rawToken.indexOf(\":\");\n if (separator <= 0) {\n terms.push(rawToken);\n continue;\n }\n\n const field = rawToken.slice(0, separator).toLowerCase();\n const value = rawToken.slice(separator + 1);\n if (!value) {\n terms.push(rawToken);\n continue;\n }\n\n switch (field) {\n case \"name\":\n case \"filename\":\n terms.push(value);\n break;\n case \"type\":\n // Accept either a MIME type (\"application/pdf\") or a coarse category.\n query.type = value.toLowerCase();\n break;\n case \"ext\":\n case \"extension\":\n query.ext = value.replace(/^\\./, \"\").toLowerCase();\n break;\n case \"size\": {\n const comparison = parseSizeComparison(value);\n if (!comparison) {\n terms.push(rawToken);\n break;\n }\n if (comparison.min !== undefined) query.minSize = comparison.min;\n if (comparison.max !== undefined) query.maxSize = comparison.max;\n break;\n }\n case \"folder\":\n query.folderName = value;\n break;\n case \"starred\": {\n const parsed = parseBoolean(value);\n if (parsed === null) terms.push(rawToken);\n else query.starred = parsed;\n break;\n }\n case \"trashed\":\n case \"deleted\": {\n const parsed = parseBoolean(value);\n if (parsed === null) terms.push(rawToken);\n else query.status = parsed ? \"trashed\" : \"active\";\n break;\n }\n case \"after\":\n case \"since\": {\n const date = parseDate(value, false);\n if (!date) terms.push(rawToken);\n else query.from = date.toISOString();\n break;\n }\n case \"before\": {\n const date = parseDate(value, true);\n if (!date) terms.push(rawToken);\n else query.to = date.toISOString();\n break;\n }\n default:\n terms.push(rawToken);\n break;\n }\n }\n\n const freeText = terms.join(\" \").trim();\n if (freeText) query.q = freeText;\n return { query, freeText };\n}\n\nexport function normalizePage(value: unknown): number {\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed < 1) return 1;\n return parsed;\n}\n\nexport function normalizeLimit(value: unknown): number {\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed < 1) return DEFAULT_PAGE_SIZE;\n return Math.min(parsed, MAX_PAGE_SIZE);\n}\n\nexport function normalizeSort(value: unknown): SortField | undefined {\n if (typeof value !== \"string\") return undefined;\n return (SORT_FIELDS as readonly string[]).includes(value) ? (value as SortField) : undefined;\n}\n\nexport function normalizeOrder(value: unknown): SortOrder | undefined {\n if (typeof value !== \"string\") return undefined;\n return (SORT_ORDERS as readonly string[]).includes(value) ? (value as SortOrder) : undefined;\n}\n\n/** Builds the pagination envelope for a result page. */\nexport function paginationMeta(page: number, limit: number, total: number) {\n return { page, limit, total, hasMore: page * limit < total };\n}\n","/**\n * Data transfer objects, constants and error codes shared between the API,\n * the web app, and the @lacrous/omnicloud SDK.\n *\n * Telegram identifiers that may exceed JavaScript's safe integer range are\n * transported as strings across the API boundary.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Identity & storage\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface UserDTO {\n id: string;\n username: string | null;\n firstName: string | null;\n lastName: string | null;\n}\n\nexport interface StorageDTO {\n id: string;\n provider: string;\n title: string;\n createdAt: string;\n}\n\n/** Connection state of the underlying storage backend. */\nexport type ConnectionState =\n \"DISCONNECTED\" | \"CONNECTING\" | \"CONNECTED\" | \"RECONNECTING\" | \"ERROR\" | \"AUTH_REQUIRED\";\n\nexport type HealthStatus = \"healthy\" | \"degraded\" | \"unavailable\" | \"unknown\";\n\nexport interface StorageHealthDTO {\n provider: string;\n state: ConnectionState;\n status: HealthStatus;\n latencyMs: number | null;\n message: string | null;\n channelTitle: string | null;\n checkedAt: string;\n}\n\n/** Returned by GET /api/auth/me. `user` is null when not signed in. */\nexport interface SessionInfo {\n user: UserDTO | null;\n storage: StorageDTO | null;\n health: StorageHealthDTO | null;\n}\n\n/** POST /api/auth/telegram/verify response. */\nexport type TelegramVerifyResponse =\n { status: \"ok\"; user: UserDTO } | { status: \"password_required\" };\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Files & folders\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface FileDTO {\n id: string;\n name: string;\n size: number;\n mimeType: string;\n sha256: string;\n folderId: string | null;\n starred: boolean;\n trashed: boolean;\n deletedAt: string | null;\n versionCount: number;\n currentVersionId: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface FolderDTO {\n id: string;\n name: string;\n parentId: string | null;\n starred: boolean;\n trashed: boolean;\n deletedAt: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface FileVersionDTO {\n id: string;\n fileId: string;\n versionNumber: number;\n size: number;\n mimeType: string;\n sha256: string;\n telegramMessageId: number;\n isCurrent: boolean;\n createdAt: string;\n}\n\nexport interface SearchResultDTO {\n files: FileDTO[];\n folders: FolderDTO[];\n pagination: PaginationDTO;\n}\n\n/** Result of a recursive folder deletion / trash operation. */\nexport interface FolderMutationResultDTO {\n affectedFolders: number;\n affectedFiles: number;\n}\n\nexport interface EmptyTrashResultDTO {\n deletedFiles: number;\n deletedFolders: number;\n failedFiles: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Pagination, sorting & filtering\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { ListQuery } from \"./query\";\n\nexport type { ListQuery, SortField, SortOrder, ItemStatus } from \"./query\";\n\nexport interface PaginationDTO {\n page: number;\n limit: number;\n total: number;\n hasMore: boolean;\n}\n\nexport interface FilesPageDTO {\n files: FileDTO[];\n pagination: PaginationDTO;\n}\n\nexport interface FoldersPageDTO {\n folders: FolderDTO[];\n pagination: PaginationDTO;\n}\n\nexport interface TrashPageDTO {\n files: FileDTO[];\n folders: FolderDTO[];\n pagination: PaginationDTO;\n}\n\n/** Alias so callers can name the query shape as query parameters. */\nexport type QueryParams = ListQuery;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Recent & activity\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface RecentItemDTO {\n file: FileDTO;\n lastAction: ActivityAction;\n lastActionAt: string;\n}\n\nexport interface RecentPageDTO {\n items: RecentItemDTO[];\n pagination: PaginationDTO;\n}\n\nexport const ACTIVITY_ACTIONS = [\n \"upload\",\n \"download\",\n \"open\",\n \"rename\",\n \"move\",\n \"star\",\n \"unstar\",\n \"trash\",\n \"restore\",\n \"delete\",\n \"create_folder\",\n \"replace\",\n] as const;\nexport type ActivityAction = (typeof ACTIVITY_ACTIONS)[number];\n\nexport const RESOURCE_TYPES = [\"file\", \"folder\", \"storage\"] as const;\nexport type ResourceType = (typeof RESOURCE_TYPES)[number];\n\nexport interface ActivityEventDTO {\n id: string;\n action: ActivityAction;\n resourceType: ResourceType;\n resourceId: string;\n resourceName: string | null;\n metadata: Record<string, unknown> | null;\n createdAt: string;\n}\n\nexport interface ActivityPageDTO {\n events: ActivityEventDTO[];\n pagination: PaginationDTO;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Storage statistics\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface StorageTypeBreakdownDTO {\n category: string;\n bytes: number;\n count: number;\n}\n\nexport interface StorageStatsDTO {\n fileCount: number;\n folderCount: number;\n totalBytes: number;\n trashBytes: number;\n trashFileCount: number;\n starredCount: number;\n byType: StorageTypeBreakdownDTO[];\n largestFiles: FileDTO[];\n quotaBytes: number | null;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Health & integrity\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface HealthDTO {\n status: HealthStatus;\n database: HealthStatus;\n storage: HealthStatus;\n uptimeSeconds: number;\n version: string;\n}\n\nexport interface IntegrityIssueDTO {\n fileId: string;\n /** Set when the issue is with a historical version rather than the current object. */\n versionId?: string;\n name: string;\n kind: \"missing\" | \"size_mismatch\" | \"unreadable\" | \"hash_mismatch\";\n detail: string | null;\n}\n\nexport interface IntegrityReportDTO {\n checkedAt: string;\n filesChecked: number;\n healthy: number;\n missing: number;\n inconsistent: number;\n unreadable: number;\n issues: IntegrityIssueDTO[];\n durationMs: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Batch operations\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const BATCH_FILE_OPERATIONS = [\n \"trash\",\n \"restore\",\n \"delete\",\n \"star\",\n \"unstar\",\n \"move\",\n] as const;\nexport type BatchFileOperation = (typeof BATCH_FILE_OPERATIONS)[number];\n\nexport const BATCH_FOLDER_OPERATIONS = [\n \"trash\",\n \"restore\",\n \"delete\",\n \"star\",\n \"unstar\",\n \"move\",\n] as const;\nexport type BatchFolderOperation = (typeof BATCH_FOLDER_OPERATIONS)[number];\n\nexport interface BatchResultDTO {\n requested: number;\n succeeded: number;\n failed: number;\n errors: { id: string; code: ErrorCode; message: string }[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Errors\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Stable machine-readable error codes returned in API error bodies. */\nexport const ERROR_CODES = {\n AUTH_REQUIRED: \"AUTH_REQUIRED\",\n AUTH_INVALID: \"AUTH_INVALID\",\n PERMISSION_DENIED: \"PERMISSION_DENIED\",\n INVALID_REQUEST: \"INVALID_REQUEST\",\n NOT_FOUND: \"NOT_FOUND\",\n FILE_NOT_FOUND: \"FILE_NOT_FOUND\",\n FOLDER_NOT_FOUND: \"FOLDER_NOT_FOUND\",\n CONFLICT: \"CONFLICT\",\n PAYLOAD_TOO_LARGE: \"PAYLOAD_TOO_LARGE\",\n RATE_LIMITED: \"RATE_LIMITED\",\n STORAGE_NOT_INITIALIZED: \"STORAGE_NOT_INITIALIZED\",\n STORAGE_UNAVAILABLE: \"STORAGE_UNAVAILABLE\",\n TELEGRAM_AUTH_REQUIRED: \"TELEGRAM_AUTH_REQUIRED\",\n TELEGRAM_CONNECTION_FAILED: \"TELEGRAM_CONNECTION_FAILED\",\n TELEGRAM_FILE_NOT_FOUND: \"TELEGRAM_FILE_NOT_FOUND\",\n UPLOAD_FAILED: \"UPLOAD_FAILED\",\n DOWNLOAD_FAILED: \"DOWNLOAD_FAILED\",\n INTEGRITY_CHECK_FAILED: \"INTEGRITY_CHECK_FAILED\",\n INTERNAL: \"INTERNAL_ERROR\",\n} as const;\n\nexport type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];\n\nexport interface ApiErrorBody {\n error: {\n code: ErrorCode;\n message: string;\n details?: unknown;\n requestId?: string;\n };\n}\n\nexport const SESSION_COOKIE = \"omnicloud_session\";\nexport const REQUEST_ID_HEADER = \"x-request-id\";\n\nexport * from \"./mime\";\nexport * from \"./query\";\n","import { ERROR_CODES } from \"@omnicloud/shared\";\nimport type { ErrorCode } from \"@omnicloud/shared\";\n\n/**\n * Base class for all domain errors. The API maps these to structured HTTP\n * responses; everything else becomes a 500.\n */\nexport class DomainError extends Error {\n readonly code: ErrorCode;\n readonly status: number;\n readonly details?: unknown;\n /** Whether repeating the same operation may succeed. Defaults to false. */\n readonly retryable: boolean = false;\n /** Server-requested delay before a retry, in seconds, when the provider gave one. */\n readonly retryAfterSeconds?: number;\n\n constructor(\n code: ErrorCode,\n status: number,\n message: string,\n details?: unknown,\n retry?: { retryable?: boolean; retryAfterSeconds?: number },\n ) {\n super(message);\n this.name = new.target.name;\n this.code = code;\n this.status = status;\n this.details = details;\n this.retryable = retry?.retryable ?? false;\n this.retryAfterSeconds = retry?.retryAfterSeconds;\n }\n}\n\n/** Invalid request payload — 400. */\nexport class ValidationError extends DomainError {\n constructor(message: string, details?: unknown) {\n super(ERROR_CODES.INVALID_REQUEST, 400, message, details);\n }\n}\n\n/** Missing browser session — 401. */\nexport class UnauthorizedError extends DomainError {\n constructor(message = \"Authentication required\") {\n super(ERROR_CODES.AUTH_REQUIRED, 401, message);\n }\n}\n\n/** Invalid credentials or expired session token — 401. */\nexport class AuthInvalidError extends DomainError {\n constructor(message = \"Your session is no longer valid — please sign in again\") {\n super(ERROR_CODES.AUTH_INVALID, 401, message);\n }\n}\n\n/** Authenticated but not allowed — 403. */\nexport class ForbiddenError extends DomainError {\n constructor(message = \"You do not have access to this resource\") {\n super(ERROR_CODES.PERMISSION_DENIED, 403, message);\n }\n}\n\n/**\n * Used for missing resources as well as resources owned by someone else, to\n * avoid leaking existence. `resource` narrows the error code.\n */\nexport class NotFoundError extends DomainError {\n constructor(message = \"Resource not found\", resource?: \"file\" | \"folder\") {\n super(\n resource === \"file\"\n ? ERROR_CODES.FILE_NOT_FOUND\n : resource === \"folder\"\n ? ERROR_CODES.FOLDER_NOT_FOUND\n : ERROR_CODES.NOT_FOUND,\n 404,\n message,\n );\n }\n}\n\nexport class ConflictError extends DomainError {\n constructor(message: string) {\n super(ERROR_CODES.CONFLICT, 409, message);\n }\n}\n\nexport class PayloadTooLargeError extends DomainError {\n constructor(message = \"File is too large\") {\n super(ERROR_CODES.PAYLOAD_TOO_LARGE, 413, message);\n }\n}\n\nexport class RateLimitedError extends DomainError {\n constructor(\n message = \"Too many requests, please retry later\",\n options: { retryAfterSeconds?: number } = {},\n ) {\n super(ERROR_CODES.RATE_LIMITED, 429, message, undefined, {\n retryable: true,\n retryAfterSeconds: options.retryAfterSeconds,\n });\n }\n}\n\n/** The user has no storage backend set up yet — 409. */\nexport class StorageNotInitializedError extends DomainError {\n constructor() {\n super(ERROR_CODES.STORAGE_NOT_INITIALIZED, 409, \"Storage is not initialized for this account\");\n }\n}\n\n/** The storage backend is reachable but failing — 503. */\nexport class StorageUnavailableError extends DomainError {\n constructor(message = \"Storage is temporarily unavailable\", details?: unknown) {\n super(ERROR_CODES.STORAGE_UNAVAILABLE, 503, message, details);\n }\n}\n\n/** Telegram needs re-authentication — 401. */\nexport class TelegramAuthRequiredError extends DomainError {\n constructor(message = \"Your Telegram session has expired — please reconnect\") {\n super(ERROR_CODES.TELEGRAM_AUTH_REQUIRED, 401, message);\n }\n}\n\n/** Telegram connection could not be established — 502. */\nexport class TelegramConnectionError extends DomainError {\n constructor(message = \"Could not reach Telegram\", details?: unknown) {\n super(ERROR_CODES.TELEGRAM_CONNECTION_FAILED, 502, message, details, { retryable: true });\n }\n}\n\n/** The stored object is missing from Telegram — 404. */\nexport class TelegramFileNotFoundError extends DomainError {\n constructor(message = \"The stored file is missing from Telegram\") {\n super(ERROR_CODES.TELEGRAM_FILE_NOT_FOUND, 404, message);\n }\n}\n\n/** Upload failed — 502. */\nexport class UploadFailedError extends DomainError {\n constructor(message = \"Upload failed\", details?: unknown) {\n super(ERROR_CODES.UPLOAD_FAILED, 502, message, details);\n }\n}\n\n/** Download failed — 502. */\nexport class DownloadFailedError extends DomainError {\n constructor(message = \"Download failed\", details?: unknown) {\n super(ERROR_CODES.DOWNLOAD_FAILED, 502, message, details);\n }\n}\n\n/** Integrity verification failed — 502. */\nexport class IntegrityCheckError extends DomainError {\n constructor(message = \"Integrity check failed\", details?: unknown) {\n super(ERROR_CODES.INTEGRITY_CHECK_FAILED, 502, message, details);\n }\n}\n\n/** Operation cancelled by the client — 499 (non-standard but conventional). */\nexport class OperationCancelledError extends DomainError {\n constructor(message = \"Operation cancelled\") {\n super(ERROR_CODES.INVALID_REQUEST, 499, message);\n }\n}\n\n/** @deprecated Use the specific storage errors. Kept for provider compatibility. */\nexport class StorageProviderError extends DomainError {\n constructor(message: string, details?: unknown) {\n super(ERROR_CODES.TELEGRAM_CONNECTION_FAILED, 502, message, details);\n }\n}\n\n/** True when the value is one of our structured domain errors. */\nexport function isDomainError(error: unknown): error is DomainError {\n return error instanceof DomainError;\n}\n\n/**\n * Wraps an arbitrary provider failure as an UploadFailedError (the default\n * storage-operation error), preserving domain errors untouched.\n */\nexport function mapProviderError(message: string, error: unknown): DomainError {\n if (error instanceof DomainError) return error;\n const detail = error instanceof Error ? error.message : String(error);\n return new UploadFailedError(detail ? `${message}: ${detail}` : message, error);\n}\n","import { createHash } from \"node:crypto\";\nimport { Readable, Transform } from \"node:stream\";\nimport type {\n StorageHealth,\n StorageProvider,\n StorageUploadInput,\n StoredObject,\n StoredRef,\n TransferControl,\n} from \"./provider\";\nimport {\n IntegrityCheckError,\n OperationCancelledError,\n UploadFailedError,\n mapProviderError,\n} from \"../errors\";\nimport { isRetryableError, retryDelayMs } from \"./retry\";\n\nexport { sha256Hex } from \"../utils/hash\";\nimport { sha256Hex } from \"../utils/hash\";\n\nexport interface EngineUploadResult {\n stored: StoredObject;\n sha256: string;\n size: number;\n}\n\nexport interface EngineRetryPolicy {\n /** Total attempts, including the first. */\n attempts: number;\n /** Base delay in ms; doubles per retry. */\n baseDelayMs: number;\n}\n\nexport const DEFAULT_RETRY_POLICY: EngineRetryPolicy = { attempts: 3, baseDelayMs: 400 };\n\nfunction delay(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new OperationCancelledError());\n return;\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n function onAbort() {\n clearTimeout(timer);\n reject(new OperationCancelledError());\n }\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\nfunction throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted) throw new OperationCancelledError();\n}\n\n/** Converts a Buffer into a single-chunk Readable. */\nfunction bufferToStream(data: Buffer): Readable {\n return Readable.from([data]);\n}\n\n/**\n * Sits between the application and the raw StorageProvider and adds the\n * cross-cutting guarantees: SHA-256 integrity, streaming, progress reporting,\n * cancellation and bounded retries.\n *\n * The active provider is chosen per user by the host application (each user\n * has their own Telegram storage channel).\n */\nexport class StorageEngine {\n constructor(\n private readonly provider: StorageProvider,\n private readonly retryPolicy: EngineRetryPolicy = DEFAULT_RETRY_POLICY,\n ) {}\n\n get providerName(): string {\n return this.provider.name;\n }\n\n /**\n * Uploads an object, computing its SHA-256 as it goes and reporting\n * progress. Retries transient failures with exponential backoff.\n */\n async upload(\n input: StorageUploadInput,\n control: TransferControl = {},\n ): Promise<EngineUploadResult> {\n if (input.data) {\n const sha256 = sha256Hex(input.data);\n const size = input.data.byteLength;\n control.onProgress?.({ transferred: 0, total: size, percent: 0 });\n const stored = await this.withRetry(\n () => this.provider.put(input, control),\n control,\n (error) => new UploadFailedError(\"Upload failed\", error),\n );\n control.onProgress?.({ transferred: size, total: size, percent: 100 });\n return { stored, sha256, size };\n }\n\n const { path, size, sha256 } = input;\n if (!path || size === undefined || !sha256) {\n throw new UploadFailedError(\"Upload input has neither data nor a complete spooled file\");\n }\n control.onProgress?.({ transferred: 0, total: size, percent: 0 });\n const stored = await this.withRetry(\n () => this.provider.put(input, control),\n control,\n (error) => new UploadFailedError(\"Upload failed\", error),\n );\n control.onProgress?.({ transferred: size, total: size, percent: 100 });\n return { stored, sha256, size };\n }\n\n async download(ref: StoredRef, control: TransferControl = {}): Promise<Buffer> {\n return this.withRetry(\n () => this.provider.get(ref, control),\n control,\n (error) => mapProviderError(\"Download failed\", error),\n );\n }\n\n /**\n * Streams the object and verifies its SHA-256 against `expectedSha256`.\n *\n * The last chunk is held back until the digest of everything before it has\n * been checked, so a corrupt object errors the stream before its final bytes\n * are released. A consumer therefore never sees a complete-looking body that\n * fails the checksum. Memory stays bounded to about two provider chunks.\n */\n async downloadStream(\n ref: StoredRef,\n expectedSha256: string,\n control: TransferControl = {},\n ): Promise<Readable> {\n const source = await this.withRetry(\n () => this.provider.getStream(ref, control),\n control,\n (error) => mapProviderError(\"Download failed\", error),\n );\n\n const hash = createHash(\"sha256\");\n let held: Buffer | null = null;\n const gate = new Transform({\n transform(chunk: Buffer, _encoding, callback) {\n hash.update(chunk);\n if (held) this.push(held);\n held = chunk;\n callback();\n },\n flush(callback) {\n if (hash.digest(\"hex\") !== expectedSha256) {\n callback(new IntegrityCheckError(\"Downloaded content does not match its checksum\"));\n return;\n }\n if (held) this.push(held);\n callback();\n },\n });\n source.once(\"error\", (error) => gate.destroy(error));\n source.pipe(gate);\n return gate;\n }\n\n async remove(ref: StoredRef): Promise<void> {\n await this.withRetry(\n () => this.provider.delete(ref),\n {},\n (error) => mapProviderError(\"Delete failed\", error),\n );\n }\n\n async exists(ref: StoredRef): Promise<boolean> {\n try {\n return await this.provider.exists(ref);\n } catch (error) {\n throw mapProviderError(\"Storage lookup failed\", error);\n }\n }\n\n /** Read-only listing of stored objects, for reconciliation. Never modifies anything. */\n async list(limit: number): Promise<StoredObject[]> {\n try {\n return await this.provider.listObjects(limit);\n } catch (error) {\n throw mapProviderError(\"Storage listing failed\", error);\n }\n }\n\n /** Read-only search for stored objects with this content hash and size. */\n async findByCaption(caption: string, size: number): Promise<StoredObject[] | null> {\n if (!this.provider.findByCaption) return null;\n try {\n return await this.provider.findByCaption(caption, size);\n } catch (error) {\n throw mapProviderError(\"Storage lookup failed\", error);\n }\n }\n\n async stat(ref: StoredRef): Promise<StoredObject | null> {\n try {\n return await this.provider.stat(ref);\n } catch (error) {\n throw mapProviderError(\"Storage lookup failed\", error);\n }\n }\n\n async health(): Promise<StorageHealth> {\n try {\n return await this.provider.healthCheck();\n } catch (error) {\n return {\n healthy: false,\n latencyMs: null,\n message: error instanceof Error ? error.message : \"Storage health check failed\",\n targetTitle: null,\n };\n }\n }\n\n async verifyIntegrity(data: Buffer, expectedSha256: string): Promise<boolean> {\n return sha256Hex(data) === expectedSha256;\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private async withRetry<T>(\n operation: () => Promise<T>,\n control: TransferControl,\n wrapError: (error: unknown) => Error,\n ): Promise<T> {\n const { attempts, baseDelayMs } = this.retryPolicy;\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= Math.max(1, attempts); attempt += 1) {\n throwIfAborted(control.signal);\n try {\n return await operation();\n } catch (error) {\n lastError = error;\n if (attempt >= attempts || !isRetryableError(error)) break;\n await delay(retryDelayMs(error, attempt, baseDelayMs), control.signal);\n }\n }\n throw wrapError(lastError);\n }\n}\n\nexport { bufferToStream };\n","import { DomainError, OperationCancelledError } from \"../errors\";\n\n/**\n * Retry decisions come from the error's declared contract, not from its message\n * text. A DomainError says whether it is retryable; anything else is not retried.\n * Cancellation is never retried.\n */\nexport function isRetryableError(error: unknown): boolean {\n if (error instanceof OperationCancelledError) return false;\n if (error instanceof DomainError) return error.retryable;\n return false;\n}\n\n/**\n * How long to wait before the next attempt. A provider-supplied flood wait is\n * honoured exactly; otherwise exponential backoff with full jitter.\n */\nexport function retryDelayMs(\n error: unknown,\n attempt: number,\n baseDelayMs: number,\n random: () => number = Math.random,\n): number {\n if (error instanceof DomainError && error.retryAfterSeconds !== undefined) {\n return error.retryAfterSeconds * 1000;\n }\n const ceiling = baseDelayMs * 2 ** (attempt - 1);\n return Math.floor(random() * ceiling);\n}\n","import { createHash } from \"node:crypto\";\n\nexport function sha256Hex(data: Uint8Array): string {\n return createHash(\"sha256\").update(data).digest(\"hex\");\n}\n","import { mimeMatchersForType, paginationMeta } from \"@omnicloud/shared\";\nimport type { ListQuery, PaginationDTO } from \"@omnicloud/shared\";\nimport type { FolderRepository } from \"../repos\";\nimport type { ItemQuery } from \"../types\";\n\n/**\n * Translates the shared, user-facing ListQuery into the repository-ready\n * ItemQuery: validates/normalizes sort and date bounds, expands MIME\n * categories, and resolves `folder:Name` filters to concrete folder ids.\n */\nexport async function resolveItemQuery(\n folders: FolderRepository,\n userId: string,\n input: ListQuery,\n): Promise<ItemQuery> {\n const query: ItemQuery = {};\n\n // `folderId: null` means root; `undefined` means \"any folder\". Distinguish\n // them by presence of a defined value rather than the key itself.\n if (input.folderId !== undefined) query.folderId = input.folderId;\n if (input.q && input.q.trim()) query.nameContains = input.q.trim();\n if (input.ext) query.ext = input.ext.replace(/^\\./, \"\").toLowerCase();\n if (input.starred !== undefined) query.starred = input.starred;\n if (input.minSize !== undefined) query.minSize = input.minSize;\n if (input.maxSize !== undefined) query.maxSize = input.maxSize;\n if (input.sort) query.sort = input.sort;\n if (input.order) query.order = input.order;\n\n switch (input.status) {\n case \"active\":\n query.trashed = false;\n break;\n case \"trashed\":\n query.trashed = true;\n break;\n default:\n query.trashed = undefined;\n break;\n }\n\n if (input.type) {\n const matchers = mimeMatchersForType(input.type);\n if (matchers) query.mimeMatchers = matchers;\n }\n\n if (input.from) {\n const from = new Date(input.from);\n if (!Number.isNaN(from.getTime())) query.from = from;\n }\n if (input.to) {\n const to = new Date(input.to);\n if (!Number.isNaN(to.getTime())) query.to = to;\n }\n\n if (input.folderName) {\n query.folderIds = await folders.findIdsByName(userId, input.folderName);\n }\n\n return query;\n}\n\n/** Builds the pagination envelope shared by every listing endpoint. */\nexport function toPagination(page: number, limit: number, total: number): PaginationDTO {\n return paginationMeta(page, limit, total);\n}\n\nexport { paginationMeta };\n","import { createHash, randomBytes } from \"node:crypto\";\nimport type { BrowserSessionRecord, BrowserSessionRepository } from \"../repos\";\nimport { UnauthorizedError } from \"../errors\";\n\n/**\n * Browser sessions: opaque random tokens held in an HttpOnly cookie. The\n * server stores only a SHA-256 of each token, so a database read does not give\n * an attacker usable session cookies, and logout takes effect immediately\n * because every request is checked against the stored, revocable record.\n */\n\nexport interface IssuedSession {\n /** The raw token. Set it in the cookie and never store or log it. */\n token: string;\n session: BrowserSessionRecord;\n}\n\nexport interface SessionServiceOptions {\n /** Lifetime of a session from issue, in days. */\n ttlDays?: number;\n now?: () => Date;\n}\n\nexport const DEFAULT_SESSION_TTL_DAYS = 30;\n\nexport function hashToken(token: string): string {\n return createHash(\"sha256\").update(token, \"utf8\").digest(\"hex\");\n}\n\nexport class AuthSessionService {\n private readonly ttlMs: number;\n private readonly now: () => Date;\n\n constructor(\n private readonly repo: BrowserSessionRepository,\n options: SessionServiceOptions = {},\n ) {\n this.ttlMs = (options.ttlDays ?? DEFAULT_SESSION_TTL_DAYS) * 24 * 60 * 60 * 1000;\n this.now = options.now ?? (() => new Date());\n }\n\n async issue(\n userId: string,\n context: { userAgent?: string | null; ip?: string | null } = {},\n ): Promise<IssuedSession> {\n const token = randomBytes(32).toString(\"base64url\");\n const session = await this.repo.create({\n userId,\n tokenHash: hashToken(token),\n expiresAt: new Date(this.now().getTime() + this.ttlMs),\n userAgent: truncate(context.userAgent ?? null, 256),\n ip: context.ip ?? null,\n });\n return { token, session };\n }\n\n /**\n * Resolves a raw token to its active session. Returns null for an unknown,\n * expired, or revoked token. Successful lookups refresh `lastUsedAt`.\n */\n async resolve(token: string | undefined): Promise<BrowserSessionRecord | null> {\n if (!token || token.length < 16 || token.length > 128) return null;\n const session = await this.repo.findByTokenHash(hashToken(token));\n if (!session) return null;\n const now = this.now();\n if (session.revokedAt !== null || session.expiresAt.getTime() <= now.getTime()) {\n return null;\n }\n await this.repo.touch(session.id, now);\n return session;\n }\n\n /** Revokes the session behind a token. Unknown tokens are a no-op. */\n async revoke(token: string | undefined): Promise<void> {\n if (!token) return;\n const session = await this.repo.findByTokenHash(hashToken(token));\n if (session && session.revokedAt === null) {\n await this.repo.revoke(session.id, this.now());\n }\n }\n\n /** Revokes every session for a user, for \"sign out everywhere\". */\n async revokeAll(userId: string): Promise<number> {\n return this.repo.revokeAllForUser(userId, this.now());\n }\n\n listActive(userId: string): Promise<BrowserSessionRecord[]> {\n return this.repo.listActiveForUser(userId, this.now());\n }\n\n /** Housekeeping: removes sessions that expired more than `graceDays` ago. */\n async pruneExpired(graceDays = 7): Promise<number> {\n const cutoff = new Date(this.now().getTime() - graceDays * 24 * 60 * 60 * 1000);\n return this.repo.deleteExpiredBefore(cutoff);\n }\n}\n\nfunction truncate(value: string | null, max: number): string | null {\n return value === null ? null : value.slice(0, max);\n}\n\nexport function requireSession(session: BrowserSessionRecord | null): BrowserSessionRecord {\n if (!session) throw new UnauthorizedError();\n return session;\n}\n","import type { FileVersionRecord } from \"../types\";\n\n/**\n * Which historical versions to keep. The current version is never a candidate,\n * whatever the policy says. The default keeps everything, so no version is ever\n * removed unless an operator opts in.\n */\nexport type VersionRetentionPolicy =\n | { kind: \"KEEP_ALL\" }\n | { kind: \"KEEP_LATEST_N\"; count: number }\n | { kind: \"KEEP_FOR_DAYS\"; days: number };\n\nexport const DEFAULT_RETENTION: VersionRetentionPolicy = { kind: \"KEEP_ALL\" };\n\n/**\n * Returns the historical versions the policy would remove. Pure: it decides\n * nothing is deleted, the caller must delete the remote objects first.\n */\nexport function versionsToPrune(\n versions: FileVersionRecord[],\n currentVersionId: string | null,\n policy: VersionRetentionPolicy,\n now: Date,\n): FileVersionRecord[] {\n const historical = versions\n .filter((version) => version.id !== currentVersionId)\n .sort((a, b) => b.versionNumber - a.versionNumber);\n\n switch (policy.kind) {\n case \"KEEP_ALL\":\n return [];\n case \"KEEP_LATEST_N\": {\n const keep = Math.max(0, policy.count);\n return historical.slice(keep);\n }\n case \"KEEP_FOR_DAYS\": {\n const cutoff = now.getTime() - policy.days * 24 * 60 * 60 * 1000;\n return historical.filter((version) => version.createdAt.getTime() < cutoff);\n }\n }\n}\n","/**\n * Upload operations are the durable record of one logical upload. They exist so\n * that a retried request (same operation id) can resume or replay instead of\n * writing a second Telegram object and a second file record.\n *\n * PENDING accepted, Telegram write not started\n * UPLOADING Telegram write in progress or its outcome unknown\n * COMMITTING the stored object is being turned into a file record by exactly one\n * request, which holds this state as an exclusive claim\n * COMPLETED Telegram object stored and file record committed; result is final\n * UNKNOWN the Telegram write may have stored the object but the outcome is not\n * known; it is resolved, never silently re-uploaded\n * FAILED Telegram write definitively failed; the operation may be retried\n * under a new id\n */\nexport type UploadOperationStatus =\n \"PENDING\" | \"UPLOADING\" | \"COMMITTING\" | \"UNKNOWN\" | \"COMPLETED\" | \"FAILED\";\n\nconst TRANSITIONS: Record<UploadOperationStatus, readonly UploadOperationStatus[]> = {\n PENDING: [\"UPLOADING\", \"FAILED\"],\n UPLOADING: [\"COMMITTING\", \"FAILED\", \"UNKNOWN\"],\n // UNKNOWN is resolved, never re-uploaded: the object is either found (committed) or\n // confirmed absent (failed). It is not a state a new request may claim.\n UNKNOWN: [\"COMMITTING\", \"FAILED\"],\n COMMITTING: [\"COMPLETED\", \"UPLOADING\"],\n COMPLETED: [],\n FAILED: [],\n};\n\nexport function canTransition(from: UploadOperationStatus, to: UploadOperationStatus): boolean {\n return TRANSITIONS[from].includes(to);\n}\n\n/** A completed operation is final: replays return its result and never re-upload. */\nexport function isTerminal(status: UploadOperationStatus): boolean {\n return status === \"COMPLETED\" || status === \"FAILED\";\n}\n\n/** Operation ids are client-supplied; accept only a bounded, URL-safe token. */\nexport function isValidOperationId(id: string): boolean {\n return /^[A-Za-z0-9_-]{8,64}$/.test(id);\n}\n\n/** Raised when an operation with this id already exists for the user (a create race). */\nexport class OperationAlreadyExistsError extends Error {\n constructor() {\n super(\"An upload with this operation id already exists\");\n this.name = \"OperationAlreadyExistsError\";\n }\n}\n","/**\n * One structured record per storage operation: what happened, how long it took,\n * and whether it worked. Fields are operational only. The operation never\n * receives file contents, names of secrets, or session material, so none can\n * be logged from here.\n */\nexport interface OperationRecord {\n operation: \"upload\" | \"download\" | \"delete\" | \"replace\";\n userId: string;\n resourceId: string | null;\n sizeBytes: number | null;\n durationMs: number;\n status: \"ok\" | \"error\";\n errorCode: string | null;\n}\n\nexport interface OperationSink {\n info(record: Record<string, unknown>, message: string): void;\n warn(record: Record<string, unknown>, message: string): void;\n}\n\n/**\n * Times an operation and emits one record for it. The original error is always\n * rethrown unchanged, and a failure to write the log never fails the operation.\n */\nexport async function timedOperation<T>(\n sink: OperationSink | null,\n base: {\n operation: OperationRecord[\"operation\"];\n userId: string;\n resourceId?: string | null;\n sizeBytes?: number | null;\n },\n run: () => Promise<T>,\n now: () => number = Date.now,\n): Promise<T> {\n const started = now();\n const emit = (status: \"ok\" | \"error\", errorCode: string | null) => {\n if (!sink) return;\n const record: OperationRecord = {\n operation: base.operation,\n userId: base.userId,\n resourceId: base.resourceId ?? null,\n sizeBytes: base.sizeBytes ?? null,\n durationMs: now() - started,\n status,\n errorCode,\n };\n try {\n if (status === \"ok\") sink.info({ ...record }, `${base.operation}.completed`);\n else sink.warn({ ...record }, `${base.operation}.failed`);\n } catch {\n // Logging must never change the outcome of the operation.\n }\n };\n try {\n const result = await run();\n emit(\"ok\", null);\n return result;\n } catch (error) {\n const code = (error as { code?: string }).code ?? \"INTERNAL_ERROR\";\n emit(\"error\", code);\n throw error;\n }\n}\n","import type { ActivityAction, ResourceType } from \"@omnicloud/shared\";\nimport type { ActivityRepository } from \"../repos\";\nimport type { ActivityEventRecord, PageRequest, Paged } from \"../types\";\n\nexport interface RecordActivityInput {\n userId: string;\n action: ActivityAction;\n resourceType: ResourceType;\n resourceId: string;\n resourceName?: string | null;\n metadata?: Record<string, unknown> | null;\n}\n\n/**\n * The narrow interface other services depend on for activity logging, so a\n * no-op recorder can be substituted in tests and activity failures never\n * break the operation being logged.\n */\nexport interface ActivityRecorder {\n record(input: RecordActivityInput): Promise<void>;\n}\n\n/** Activity log with best-effort recording and retention pruning. */\nexport class ActivityService implements ActivityRecorder {\n constructor(\n private readonly activity: ActivityRepository,\n /** Days to retain events; older rows are pruned on demand. */\n private readonly retentionDays = 90,\n ) {}\n\n /**\n * Records an event. Never throws — a logging failure must not fail the\n * user-facing operation that produced it.\n */\n async record(input: RecordActivityInput): Promise<void> {\n try {\n await this.activity.record({\n userId: input.userId,\n action: input.action,\n resourceType: input.resourceType,\n resourceId: input.resourceId,\n resourceName: input.resourceName ?? null,\n metadata: sanitizeMetadata(input.metadata),\n });\n } catch {\n // Swallow: activity logging is observability, not a transaction.\n }\n }\n\n async list(userId: string, page: PageRequest): Promise<Paged<ActivityEventRecord>> {\n return this.activity.list(userId, page);\n }\n\n /** Most recent distinct files for the given actions, for the Recent view. */\n async recentFiles(\n userId: string,\n actions: readonly ActivityAction[],\n limit: number,\n ): Promise<ActivityEventRecord[]> {\n return this.activity.recentFiles(userId, actions, limit);\n }\n\n async prune(): Promise<number> {\n const cutoff = new Date(Date.now() - this.retentionDays * 24 * 60 * 60 * 1000);\n return this.activity.pruneOlderThan(cutoff);\n }\n}\n\nconst SENSITIVE_KEY = /(password|secret|token|session|hash|credential)/i;\n\n/**\n * Strips credential-shaped keys so the activity log can never become a place\n * where secrets leak.\n */\nfunction sanitizeMetadata(\n metadata: Record<string, unknown> | null | undefined,\n): Record<string, unknown> | null {\n if (!metadata) return null;\n const clean: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(metadata)) {\n if (SENSITIVE_KEY.test(key)) continue;\n if (value === undefined) continue;\n clean[key] = value;\n }\n return Object.keys(clean).length > 0 ? clean : null;\n}\n\n/** A recorder that discards events (used where activity is not wired up). */\nexport const noopActivityRecorder: ActivityRecorder = {\n async record() {\n /* intentionally empty */\n },\n};\n","/**\n * Sanitizes a client-supplied filename into a safe, single path component.\n * Returns an empty string when nothing usable remains.\n */\nexport function sanitizeFileName(name: string): string {\n // Drop any path components — prevents path traversal.\n const base = name.split(/[/\\\\]/).pop() ?? name;\n // Strip control characters and characters that are unsafe across filesystems.\n const cleaned = base\n // eslint-disable-next-line no-control-regex\n .replace(/[\\u0000-\\u001f\\u007f-\\u009f]/g, \"\")\n // Bidirectional overrides and isolates, and zero-width characters, can make a\n // name display with a different extension than the one stored.\n .replace(/[\\u200B-\\u200F\\u202A-\\u202E\\u2066-\\u2069\\uFEFF]/g, \"\")\n .replace(/[<>:\"|?*]/g, \"_\")\n .trim();\n if (!cleaned || cleaned === \".\" || cleaned === \"..\") return \"\";\n return cleaned.slice(0, 255);\n}\n","import { createHash } from \"node:crypto\";\nimport type { FileUploadInput } from \"../services/file-service\";\nimport { sha256Hex } from \"./hash\";\nimport { sanitizeFileName } from \"./filename\";\n\n/**\n * A digest of everything that determines what an upload creates: the sanitized\n * name, the target folder, the file being replaced, and the content. A replay\n * under the same operation id must present the same fingerprint, otherwise it is\n * a different request reusing a key.\n */\nexport function uploadRequestFingerprint(input: FileUploadInput): string {\n const contentHash = input.spooled?.sha256 ?? sha256Hex(input.data ?? Buffer.alloc(0));\n const canonical = JSON.stringify({\n name: sanitizeFileName(input.name),\n folderId: input.folderId ?? null,\n replaceFileId: input.replaceFileId ?? null,\n contentHash,\n size: input.spooled?.size ?? input.data?.byteLength ?? 0,\n });\n return createHash(\"sha256\").update(canonical).digest(\"hex\");\n}\n","import type { Readable } from \"node:stream\";\nimport type { ListQuery } from \"@omnicloud/shared\";\nimport { mimeFromFilename } from \"@omnicloud/shared\";\nimport {\n ConflictError,\n NotFoundError,\n TelegramConnectionError,\n UploadFailedError,\n ValidationError,\n} from \"../errors\";\nimport { sanitizeFileName } from \"../utils/filename\";\nimport type { FileRecord, FileVersionRecord, ItemQuery, PageRequest, Paged } from \"../types\";\nimport type {\n FileRepository,\n FolderRepository,\n UploadOperationRecord,\n UploadOperationRepository,\n} from \"../repos\";\nimport { OperationAlreadyExistsError, isValidOperationId } from \"./upload-operation\";\nimport { sha256Hex } from \"../utils/hash\";\nimport { timedOperation, type OperationSink } from \"../utils/operation-log\";\nimport { uploadRequestFingerprint } from \"../utils/request-fingerprint\";\nimport type { StorageEngine } from \"../storage/engine\";\nimport type { StoredObject } from \"../storage/provider\";\nimport type { TransferControl, TransferProgress } from \"../storage/provider\";\nimport type { ActivityRecorder } from \"./activity-service\";\nimport { noopActivityRecorder } from \"./activity-service\";\nimport { resolveItemQuery } from \"./query-resolver\";\nimport { versionsToPrune, type VersionRetentionPolicy } from \"./version-retention\";\n\nexport interface FileDownload {\n record: FileRecord;\n data: Buffer;\n integrityVerified: boolean;\n}\n\nexport interface FileStreamDownload {\n record: FileRecord;\n /** Errors with IntegrityCheckError instead of ending if the bytes do not match. */\n stream: Readable;\n}\n\nexport type EngineResolver = (userId: string) => Promise<StorageEngine>;\n\nexport interface FileUploadInput {\n folderId: string | null;\n name: string;\n /** In-memory content. Use `spooled` for anything large. */\n data?: Buffer;\n /** A file already written to disk by `spoolToFile`, with its checksum. */\n spooled?: { path: string; size: number; sha256: string };\n /** When set, uploads a new version of this file instead of creating one. */\n replaceFileId?: string;\n /** Client-supplied id that makes the upload idempotent across retries. */\n operationId?: string;\n /** Progress callback forwarded from the transport layer. */\n onProgress?: (progress: TransferProgress) => void;\n signal?: AbortSignal;\n}\n\nexport interface FileListResult {\n items: FileRecord[];\n total: number;\n}\n\n/**\n * File metadata operations. Every method verifies ownership: records\n * belonging to other users are reported as missing.\n *\n * Reliability rules honored here:\n * - metadata is written only after the storage upload succeeds;\n * - permanent deletion removes the remote object before the metadata;\n * - client-supplied ownership is never trusted (userId is server-derived).\n */\n/**\n * True when a failure was a Telegram connection problem, possibly after the write landed.\n * The storage engine wraps upload failures, keeping the original error as the cause.\n */\nfunction contentSha256(input: FileUploadInput): string {\n return input.spooled?.sha256 ?? sha256Hex(input.data ?? Buffer.alloc(0));\n}\n\n/**\n * The message caption for one upload attempt. It carries the operation id, so two\n * uploads of identical bytes get different captions and a retry finds only its own message.\n */\nexport function uploadCaption(sha256: string, operationId: string): string {\n return `${sha256}:${operationId}`;\n}\n\nfunction contentSize(input: FileUploadInput): number {\n return input.spooled?.size ?? input.data?.byteLength ?? 0;\n}\n\nfunction connectionCausedFailure(error: unknown): boolean {\n if (error instanceof TelegramConnectionError) return true;\n if (error instanceof UploadFailedError) {\n return error.details instanceof TelegramConnectionError;\n }\n return false;\n}\n\nexport class FileService {\n constructor(\n private readonly files: FileRepository,\n private readonly folders: FolderRepository,\n private readonly engineFor: EngineResolver,\n private readonly activity: ActivityRecorder = noopActivityRecorder,\n private readonly operations: UploadOperationRepository | null = null,\n private readonly log: OperationSink | null = null,\n ) {}\n\n // ── Upload ───────────────────────────────────────────────────────────────\n\n async upload(userId: string, input: FileUploadInput): Promise<FileRecord> {\n return timedOperation(\n this.log,\n {\n operation: \"upload\",\n userId,\n resourceId: input.replaceFileId ?? null,\n sizeBytes: input.spooled?.size ?? input.data?.byteLength ?? null,\n },\n () =>\n input.operationId && this.operations\n ? this.uploadOnce(userId, input, input.operationId, this.operations)\n : this.performUpload(userId, input),\n );\n }\n\n /**\n * Idempotent upload keyed by a client-supplied operation id. A retry never\n * writes a second Telegram object: a completed operation returns its file, and\n * an operation whose object was already stored is committed without uploading.\n */\n private async uploadOnce(\n userId: string,\n input: FileUploadInput,\n operationId: string,\n operations: UploadOperationRepository,\n ): Promise<FileRecord> {\n if (!isValidOperationId(operationId)) {\n throw new ValidationError(\"Invalid upload operation id\");\n }\n const fingerprint = uploadRequestFingerprint(input);\n let op = await operations.findByOperationId(userId, operationId);\n if (op?.requestFingerprint && op.requestFingerprint !== fingerprint) {\n throw new ConflictError(\n \"This upload key was already used for a different file. Use a new key for a new upload.\",\n );\n }\n\n if (op?.status === \"COMPLETED\" && op.fileId) {\n return this.get(userId, op.fileId);\n }\n if (op?.status === \"UPLOADING\" && op.telegramMessageId !== null) {\n return this.commitStoredObject(userId, input, operations, op);\n }\n if (op?.status === \"UNKNOWN\") {\n return this.resolveUnknown(userId, input, operations, op);\n }\n if (!op) {\n try {\n op = await operations.create({ userId, operationId, requestFingerprint: fingerprint });\n } catch (error) {\n if (!(error instanceof OperationAlreadyExistsError)) throw error;\n // Another request with the same key won the create race. Follow its state\n // instead of failing: a completed winner returns its file, an in-flight one\n // is joined through the same recovery path a crashed upload uses.\n const outcome = await this.followWinner(userId, input, operationId, operations);\n if (outcome) return outcome;\n throw new ConflictError(\"A concurrent upload with this operation id is still in progress\");\n }\n }\n // Only the request that wins this compare-and-set uploads. A PENDING operation\n // or a FAILED one (a retry after a failed attempt) can be claimed; any other\n // request that finds it already in flight follows the winner.\n const claimed =\n (await operations.claim(op.id, \"PENDING\", \"UPLOADING\")) ||\n (await operations.claim(op.id, \"FAILED\", \"UPLOADING\"));\n if (!claimed) {\n // Lost the claim to a concurrent request: follow the winner instead of\n // uploading. A bounded wait keeps this from spinning.\n const outcome = await this.followWinner(userId, input, operationId, operations);\n if (outcome) return outcome;\n throw new ConflictError(\"A concurrent upload with this operation id is still in progress\");\n }\n\n try {\n const record = await this.performUpload(userId, input, {\n caption: uploadCaption(contentSha256(input), op.operationId),\n onStored: async (stored) => {\n await operations.update(op!.id, { telegramMessageId: Number(stored.messageId) });\n // Take the exclusive commit claim as soon as the object is stored, so a\n // follower that sees UPLOADING with a message id cannot commit a second record.\n await operations.claim(op!.id, \"UPLOADING\", \"COMMITTING\");\n },\n });\n await operations.update(op.id, { status: \"COMPLETED\", fileId: record.id });\n return record;\n } catch (error) {\n // A connection failure may come after Telegram stored the object, so the outcome is\n // unknown rather than failed. Only a failure known to precede the write is FAILED.\n const status = connectionCausedFailure(error) ? \"UNKNOWN\" : \"FAILED\";\n await operations.update(op.id, {\n status,\n sha256: contentSha256(input),\n size: contentSize(input),\n error: error instanceof Error ? error.message.slice(0, 200) : \"upload failed\",\n });\n throw error;\n }\n }\n\n /**\n * Resolves an operation whose Telegram outcome is unknown. The channel is searched\n * for the object by its content hash and size. Exactly one match is adopted, zero\n * matches means nothing was stored so the upload is sent again, and anything\n * ambiguous stays UNKNOWN so that no duplicate is written.\n */\n private async resolveUnknown(\n userId: string,\n input: FileUploadInput,\n operations: UploadOperationRepository,\n op: UploadOperationRecord,\n ): Promise<FileRecord> {\n if (op.sha256 === null || op.size === null) {\n throw new ConflictError(\n \"This upload's outcome is unknown and cannot be checked automatically. Ask an administrator to reconcile it.\",\n );\n }\n const engine = await this.engineFor(userId);\n const matches = await engine.findByCaption(uploadCaption(op.sha256, op.operationId), op.size);\n if (matches === null) {\n throw new ConflictError(\n \"This upload's outcome is unknown and the storage backend cannot search for it.\",\n );\n }\n if (matches.length > 1) {\n throw new ConflictError(\n \"This upload's outcome is unknown and more than one matching object exists. Nothing was written.\",\n );\n }\n if (matches.length === 1) {\n const stored = matches[0]!;\n await operations.update(op.id, { telegramMessageId: Number(stored.messageId) });\n await operations.claim(op.id, \"UNKNOWN\", \"COMMITTING\");\n const record = await this.commitRecord(userId, input, {\n name: sanitizeFileName(input.name),\n mimeType: mimeFromFilename(sanitizeFileName(input.name)),\n size: op.size,\n sha256: op.sha256,\n telegramMessageId: Number(stored.messageId),\n });\n await operations.update(op.id, { status: \"COMPLETED\", fileId: record.id });\n return record;\n }\n await operations.claim(op.id, \"UNKNOWN\", \"FAILED\");\n return this.uploadOnce(userId, input, op.operationId, operations);\n }\n\n /**\n * Follows an operation another request is driving, for a bounded time. Returns\n * the finished file if the winner completes, or the stored object's commit if\n * it has stored one. Returns null when the winner is still in progress after the\n * budget, so the caller can report a conflict instead of waiting forever.\n */\n private async followWinner(\n userId: string,\n input: FileUploadInput,\n operationId: string,\n operations: UploadOperationRepository,\n ): Promise<FileRecord | null> {\n for (let attempt = 0; attempt < 50; attempt += 1) {\n const current = await operations.findByOperationId(userId, operationId);\n if (current?.status === \"COMPLETED\" && current.fileId) {\n return this.get(userId, current.fileId);\n }\n if (current?.status === \"FAILED\") {\n // A failed attempt may be retried under the same id: return control so the\n // caller can claim it again rather than waiting on it.\n return null;\n }\n if (current?.status === \"UPLOADING\" && current.telegramMessageId !== null) {\n return this.commitStoredObject(userId, input, operations, current);\n }\n await new Promise((resolve) => setTimeout(resolve, 10));\n }\n return null;\n }\n\n /** Commits metadata for an object that is already in Telegram. */\n private async commitStoredObject(\n userId: string,\n input: FileUploadInput,\n operations: UploadOperationRepository,\n op: UploadOperationRecord,\n ): Promise<FileRecord> {\n const name = sanitizeFileName(input.name);\n const mimeType = mimeFromFilename(name);\n const size = op.size ?? input.spooled?.size ?? input.data?.byteLength ?? 0;\n const sha256 = op.sha256 ?? input.spooled?.sha256 ?? sha256Hex(input.data ?? Buffer.alloc(0));\n\n // Exactly one request may write the file record for a stored object. The claim\n // is an atomic compare-and-set, so a request that loses it must not commit\n // another record; it follows the winner's result instead.\n const claimed = await operations.claim(op.id, \"UPLOADING\", \"COMMITTING\");\n if (!claimed) {\n const current = await operations.findByOperationId(userId, op.operationId);\n if (current?.status === \"COMPLETED\" && current.fileId) {\n return this.get(userId, current.fileId);\n }\n throw new ConflictError(\"This upload is being committed by another request. Retry shortly.\");\n }\n\n let record: FileRecord;\n try {\n record = await this.commitRecord(userId, input, {\n name,\n mimeType,\n size,\n sha256,\n telegramMessageId: op.telegramMessageId!,\n });\n } catch (error) {\n await operations.claim(op.id, \"COMMITTING\", \"UPLOADING\");\n throw error;\n }\n await operations.update(op.id, { status: \"COMPLETED\", fileId: record.id });\n return record;\n }\n\n private async performUpload(\n userId: string,\n input: FileUploadInput,\n hooks: { caption?: string; onStored?: (stored: StoredObject) => Promise<void> } = {},\n ): Promise<FileRecord> {\n const name = sanitizeFileName(input.name);\n if (!name) throw new ValidationError(\"Invalid file name\");\n\n let folderId = input.folderId;\n if (input.replaceFileId) {\n const existing = await this.get(userId, input.replaceFileId);\n folderId = existing.folderId;\n } else if (folderId !== null) {\n await this.assertFolder(userId, folderId);\n }\n\n const mimeType = mimeFromFilename(name);\n const engine = await this.engineFor(userId);\n\n const control: TransferControl = {\n onProgress: input.onProgress,\n signal: input.signal,\n };\n\n if (!input.data && !input.spooled) {\n throw new ValidationError(\"An upload needs content\");\n }\n // Upload first; metadata is only persisted once the provider succeeds.\n const { stored, sha256, size } = await engine.upload(\n input.spooled\n ? {\n name,\n mimeType,\n path: input.spooled.path,\n size: input.spooled.size,\n sha256: input.spooled.sha256,\n caption: hooks.caption,\n }\n : { name, mimeType, data: input.data, caption: hooks.caption },\n control,\n );\n await hooks.onStored?.(stored);\n\n try {\n return await this.commitRecord(userId, input, {\n name,\n mimeType,\n size,\n sha256,\n telegramMessageId: Number(stored.messageId),\n });\n } catch (error) {\n // The object is in Telegram but no record will point at it. Remove it so\n // a failed commit does not leave an unreferenced object behind. If that\n // removal also fails, the original error is still the one reported.\n await engine.remove({ messageId: String(stored.messageId) }).catch(() => undefined);\n throw error;\n }\n }\n\n /** Persists the file (or the new version of a file) for an object already stored. */\n private async commitRecord(\n userId: string,\n input: FileUploadInput,\n next: {\n name: string;\n mimeType: string;\n size: number;\n sha256: string;\n telegramMessageId: number;\n },\n ): Promise<FileRecord> {\n const { name, mimeType, size, sha256, telegramMessageId } = next;\n\n if (input.replaceFileId) {\n return this.replaceVersion(userId, input.replaceFileId, {\n name,\n size,\n mimeType,\n sha256,\n telegramMessageId,\n });\n }\n\n const folderId = input.folderId;\n if (folderId !== null) {\n await this.assertFolder(userId, folderId);\n }\n\n const record = await this.files.create({\n userId,\n folderId,\n name,\n size,\n mimeType,\n sha256,\n telegramMessageId,\n });\n\n // Establish version 1 immediately so the version history is complete from\n // the first upload (the foundation for v0.3's version UI).\n const version = await this.files.createVersion({\n fileId: record.id,\n userId,\n size,\n mimeType,\n sha256,\n telegramMessageId,\n });\n const withVersion = await this.files.update(record.id, {\n currentVersionId: version.id,\n versionCount: 1,\n });\n\n await this.activity.record({\n userId,\n action: \"upload\",\n resourceType: \"file\",\n resourceId: withVersion.id,\n resourceName: withVersion.name,\n metadata: { size, mimeType },\n });\n\n return withVersion;\n }\n\n /** Uploads a new version of an existing file and makes it current. */\n private async replaceVersion(\n userId: string,\n fileId: string,\n next: {\n name: string;\n size: number;\n mimeType: string;\n sha256: string;\n telegramMessageId: number;\n },\n ): Promise<FileRecord> {\n const existing = await this.get(userId, fileId);\n\n const version = await this.files.createVersion({\n fileId,\n userId,\n size: next.size,\n mimeType: next.mimeType,\n sha256: next.sha256,\n telegramMessageId: next.telegramMessageId,\n });\n const count = await this.files.countVersions(fileId);\n\n const updated = await this.files.update(fileId, {\n name: next.name,\n size: next.size,\n mimeType: next.mimeType,\n sha256: next.sha256,\n telegramMessageId: next.telegramMessageId,\n currentVersionId: version.id,\n versionCount: count,\n });\n\n await this.activity.record({\n userId,\n action: \"replace\",\n resourceType: \"file\",\n resourceId: fileId,\n resourceName: updated.name,\n metadata: { previousSha256: existing.sha256, size: next.size },\n });\n\n return updated;\n }\n\n // ── Reads ────────────────────────────────────────────────────────────────\n\n async list(userId: string, folderId: string | null): Promise<FileRecord[]> {\n if (folderId !== null) await this.assertFolder(userId, folderId);\n return this.files.listByFolder(userId, folderId);\n }\n\n async query(userId: string, input: ListQuery, page: PageRequest): Promise<Paged<FileRecord>> {\n if (input.folderId != null) await this.assertFolder(userId, input.folderId);\n const query: ItemQuery = await resolveItemQuery(this.folders, userId, input);\n return this.files.query(userId, query, page);\n }\n\n async get(userId: string, id: string): Promise<FileRecord> {\n const record = await this.files.findById(id);\n if (!record || record.userId !== userId) throw new NotFoundError(\"File not found\", \"file\");\n return record;\n }\n\n /** Like get(), but rejects trashed files (for download/open). */\n async getActive(userId: string, id: string): Promise<FileRecord> {\n const record = await this.get(userId, id);\n if (record.deletedAt) throw new NotFoundError(\"File not found\", \"file\");\n return record;\n }\n\n async listVersions(userId: string, id: string): Promise<FileVersionRecord[]> {\n await this.get(userId, id);\n return this.files.listVersions(id);\n }\n\n // ── Mutations ────────────────────────────────────────────────────────────\n\n async rename(userId: string, id: string, name: string): Promise<FileRecord> {\n const existing = await this.get(userId, id);\n const clean = sanitizeFileName(name);\n if (!clean) throw new ValidationError(\"Invalid file name\");\n const updated = await this.files.update(id, { name: clean });\n await this.activity.record({\n userId,\n action: \"rename\",\n resourceType: \"file\",\n resourceId: id,\n resourceName: clean,\n metadata: { previousName: existing.name },\n });\n return updated;\n }\n\n async move(userId: string, id: string, folderId: string | null): Promise<FileRecord> {\n const existing = await this.get(userId, id);\n if (folderId !== null) await this.assertFolder(userId, folderId);\n const updated = await this.files.update(id, { folderId });\n await this.activity.record({\n userId,\n action: \"move\",\n resourceType: \"file\",\n resourceId: id,\n resourceName: updated.name,\n metadata: { from: existing.folderId, to: folderId },\n });\n return updated;\n }\n\n async setStarred(userId: string, id: string, starred: boolean): Promise<FileRecord> {\n await this.get(userId, id);\n const updated = await this.files.update(id, { starred });\n await this.activity.record({\n userId,\n action: starred ? \"star\" : \"unstar\",\n resourceType: \"file\",\n resourceId: id,\n resourceName: updated.name,\n });\n return updated;\n }\n\n /**\n * Moves a file to Trash (soft delete). The remote object is untouched, so\n * restore is lossless.\n */\n async trash(userId: string, id: string): Promise<FileRecord> {\n const record = await this.get(userId, id);\n if (record.deletedAt) return record;\n const updated = await this.files.update(id, {\n deletedAt: new Date(),\n trashBatchId: `solo-${id}`,\n });\n await this.activity.record({\n userId,\n action: \"trash\",\n resourceType: \"file\",\n resourceId: id,\n resourceName: updated.name,\n });\n return updated;\n }\n\n /** Restores a trashed file to its previous folder (or root). */\n async restore(userId: string, id: string): Promise<FileRecord> {\n const record = await this.get(userId, id);\n\n // If the original folder is gone or itself trashed, fall back to root so a\n // restore can never produce an orphaned file.\n let folderId = record.folderId;\n if (folderId !== null) {\n const folder = await this.folders.findById(folderId);\n if (!folder || folder.userId !== userId || folder.deletedAt) folderId = null;\n }\n\n const updated = await this.files.update(id, { deletedAt: null, trashBatchId: null, folderId });\n await this.activity.record({\n userId,\n action: \"restore\",\n resourceType: \"file\",\n resourceId: id,\n resourceName: updated.name,\n });\n return updated;\n }\n\n /**\n * Permanently deletes a file: the Telegram object is removed first, then the\n * metadata. If the remote delete fails we keep the metadata so the user can\n * retry, rather than silently orphaning the storage object.\n */\n async deletePermanently(userId: string, id: string): Promise<void> {\n return timedOperation(this.log, { operation: \"delete\", userId, resourceId: id }, () =>\n this.performPermanentDelete(userId, id),\n );\n }\n\n private async performPermanentDelete(userId: string, id: string): Promise<void> {\n const record = await this.get(userId, id);\n const engine = await this.engineFor(userId);\n\n // Every remote object (current and historical) must be gone before any\n // metadata is dropped. A failure here throws and leaves all pointers in\n // place, so a retry can finish the job without orphaning a Telegram message.\n const versions = await this.files.listVersions(id);\n const messageIds = new Set<string>([String(record.telegramMessageId)]);\n for (const version of versions) messageIds.add(String(version.telegramMessageId));\n for (const messageId of messageIds) {\n await engine.remove({ messageId });\n }\n\n await this.files.deleteVersionsByFileIds([id]);\n await this.files.deleteMany([id]);\n await this.activity.record({\n userId,\n action: \"delete\",\n resourceType: \"file\",\n resourceId: id,\n resourceName: record.name,\n });\n }\n\n /**\n * v0.1-compatible hard delete used by the current API: permanently removes\n * the file. Retained so existing clients keep working.\n */\n async delete(userId: string, id: string): Promise<void> {\n await this.deletePermanently(userId, id);\n }\n\n // ── Download ─────────────────────────────────────────────────────────────\n\n async download(userId: string, id: string, control: TransferControl = {}): Promise<FileDownload> {\n const record = await this.getActive(userId, id);\n const engine = await this.engineFor(userId);\n const data = await engine.download({ messageId: String(record.telegramMessageId) }, control);\n const integrityVerified = await engine.verifyIntegrity(data, record.sha256);\n await this.activity.record({\n userId,\n action: \"download\",\n resourceType: \"file\",\n resourceId: id,\n resourceName: record.name,\n metadata: { size: record.size, integrityVerified },\n });\n return { record, data, integrityVerified };\n }\n\n /**\n * Streaming download for large files. The caller must consume `stream` to\n * the end and then await `verified` to learn whether the bytes match the\n * stored checksum. The download activity is recorded when the stream opens.\n */\n async downloadStreamed(\n userId: string,\n id: string,\n control: TransferControl = {},\n ): Promise<FileStreamDownload> {\n return timedOperation(this.log, { operation: \"download\", userId, resourceId: id }, () =>\n this.performStreamedDownload(userId, id, control),\n );\n }\n\n private async performStreamedDownload(\n userId: string,\n id: string,\n control: TransferControl,\n ): Promise<FileStreamDownload> {\n const record = await this.getActive(userId, id);\n const engine = await this.engineFor(userId);\n const stream = await engine.downloadStream(\n { messageId: String(record.telegramMessageId) },\n record.sha256,\n control,\n );\n await this.activity.record({\n userId,\n action: \"download\",\n resourceType: \"file\",\n resourceId: id,\n resourceName: record.name,\n metadata: { size: record.size, streamed: true },\n });\n return { record, stream };\n }\n\n /** Records an \"open\" event (the UI opening the details panel, say). */\n async touch(userId: string, id: string, action: \"open\" = \"open\"): Promise<void> {\n const record = await this.get(userId, id);\n await this.activity.record({\n userId,\n action,\n resourceType: \"file\",\n resourceId: id,\n resourceName: record.name,\n });\n }\n\n // ── Batch operations ─────────────────────────────────────────────────────\n\n /**\n * Applies an operation to many files, collecting per-item failures instead\n * of aborting the whole batch.\n */\n async batch(\n userId: string,\n ids: string[],\n operation: \"trash\" | \"restore\" | \"delete\" | \"star\" | \"unstar\" | \"move\",\n options: { folderId?: string | null; operationId?: string } = {},\n ): Promise<{\n operationId?: string;\n requested: number;\n succeeded: number;\n failed: number;\n errors: { id: string; message: string }[];\n }> {\n const errors: { id: string; message: string }[] = [];\n let succeeded = 0;\n\n if (ids.length === 0) throw new ValidationError(\"No items selected\");\n\n // Validate the destination once, so a bad folder fails the whole batch\n // rather than producing N identical errors.\n if (operation === \"move\") {\n const target = options.folderId ?? null;\n if (target !== null) await this.assertFolder(userId, target);\n }\n\n for (const id of ids) {\n try {\n switch (operation) {\n case \"trash\":\n await this.trash(userId, id);\n break;\n case \"restore\":\n await this.restore(userId, id);\n break;\n case \"delete\":\n await this.deletePermanently(userId, id);\n break;\n case \"star\":\n await this.setStarred(userId, id, true);\n break;\n case \"unstar\":\n await this.setStarred(userId, id, false);\n break;\n case \"move\":\n await this.move(userId, id, options.folderId ?? null);\n break;\n }\n succeeded += 1;\n } catch (error) {\n errors.push({ id, message: error instanceof Error ? error.message : \"Operation failed\" });\n }\n }\n\n return {\n ...(options.operationId ? { operationId: options.operationId } : {}),\n requested: ids.length,\n succeeded,\n failed: errors.length,\n errors,\n };\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n /**\n * Removes historical versions the policy no longer keeps. For each version the\n * Telegram message is deleted first, and its row only after that succeeds, the\n * same order as a permanent delete. A failed remote delete leaves that version\n * in place. The current version is never removed. Returns how many were removed.\n */\n async pruneVersions(\n userId: string,\n fileId: string,\n policy: VersionRetentionPolicy,\n now: Date = new Date(),\n ): Promise<number> {\n const record = await this.get(userId, fileId);\n const versions = await this.files.listVersions(fileId);\n const doomed = versionsToPrune(versions, record.currentVersionId ?? null, policy, now);\n if (doomed.length === 0) return 0;\n\n const engine = await this.engineFor(userId);\n let removed = 0;\n for (const version of doomed) {\n await engine.remove({ messageId: String(version.telegramMessageId) });\n await this.files.deleteVersion(version.id);\n removed += 1;\n }\n await this.activity.record({\n userId,\n action: \"delete\",\n resourceType: \"file\",\n resourceId: fileId,\n resourceName: `${record.name} (${removed} old version${removed === 1 ? \"\" : \"s\"})`,\n });\n return removed;\n }\n\n private async assertFolder(userId: string, folderId: string): Promise<void> {\n const folder = await this.folders.findById(folderId);\n if (!folder || folder.userId !== userId) {\n throw new NotFoundError(\"Folder not found\", \"folder\");\n }\n if (folder.deletedAt) throw new ConflictError(\"The target folder is in the Trash\");\n }\n}\n","import type { FolderRecord } from \"../types\";\n\n/**\n * Orders folder ids so every child comes before its parent. Deleting in this\n * order satisfies the database's `onDelete: Restrict` rule on `parentId`.\n */\nexport function deepestFirst(folderIds: string[], folders: FolderRecord[]): string[] {\n const byId = new Map(folders.map((folder) => [folder.id, folder]));\n const depthOf = (id: string): number => {\n let depth = 0;\n const seen = new Set<string>();\n let cursor = byId.get(id)?.parentId ?? null;\n while (cursor !== null && !seen.has(cursor)) {\n seen.add(cursor);\n depth += 1;\n cursor = byId.get(cursor)?.parentId ?? null;\n }\n return depth;\n };\n return [...folderIds].sort((a, b) => depthOf(b) - depthOf(a));\n}\n","import type { ListQuery } from \"@omnicloud/shared\";\nimport { ConflictError, NotFoundError, ValidationError } from \"../errors\";\nimport { sanitizeFileName } from \"../utils/filename\";\nimport { deepestFirst } from \"../utils/folder-order\";\nimport type { FolderRecord, ItemQuery, PageRequest, Paged } from \"../types\";\nimport type { FileRepository, FolderRepository } from \"../repos\";\nimport type { EngineResolver } from \"./file-service\";\nimport type { ActivityRecorder } from \"./activity-service\";\nimport { noopActivityRecorder } from \"./activity-service\";\nimport { resolveItemQuery } from \"./query-resolver\";\n\nexport interface FolderMutationResult {\n affectedFolders: number;\n affectedFiles: number;\n}\n\nexport interface FolderTree {\n folders: FolderRecord[];\n}\n\n/**\n * Folder lifecycle (v0.2):\n *\n * - Folders are virtual OmniCloud objects with a parent-child hierarchy.\n * - Trashing a folder trashes its whole subtree (folders + files) without\n * touching Telegram, so restore is lossless.\n * - Restoring a folder restores only the nodes that were trashed together\n * with it; anything the user trashed separately stays in the Trash.\n * - Permanent deletion removes remote objects first, then metadata.\n */\nexport class FolderService {\n constructor(\n private readonly folders: FolderRepository,\n private readonly files: FileRepository,\n private readonly engineFor: EngineResolver,\n private readonly activity: ActivityRecorder = noopActivityRecorder,\n ) {}\n\n // ── Create / read ────────────────────────────────────────────────────────\n\n async create(\n userId: string,\n input: { name: string; parentId: string | null },\n ): Promise<FolderRecord> {\n const name = sanitizeFileName(input.name);\n if (!name) throw new ValidationError(\"Invalid folder name\");\n if (input.parentId !== null) {\n const parent = await this.get(userId, input.parentId);\n if (parent.deletedAt) throw new ConflictError(\"The parent folder is in the Trash\");\n }\n const folder = await this.folders.create({ userId, parentId: input.parentId, name });\n await this.activity.record({\n userId,\n action: \"create_folder\",\n resourceType: \"folder\",\n resourceId: folder.id,\n resourceName: folder.name,\n });\n return folder;\n }\n\n /** Active folders directly inside a folder (null = root). */\n async listChildren(userId: string, parentId: string | null): Promise<FolderRecord[]> {\n if (parentId !== null) await this.get(userId, parentId);\n return this.folders.listChildren(userId, parentId);\n }\n\n async query(userId: string, input: ListQuery, page: PageRequest): Promise<Paged<FolderRecord>> {\n const query: ItemQuery = await resolveItemQuery(this.folders, userId, input);\n return this.folders.query(userId, query, page);\n }\n\n /** Every active folder for the user (used to build trees). */\n async tree(userId: string): Promise<FolderRecord[]> {\n const all = await this.folders.listByUser(userId);\n return all.filter((folder) => folder.deletedAt === null);\n }\n\n async get(userId: string, id: string): Promise<FolderRecord> {\n const record = await this.folders.findById(id);\n if (!record || record.userId !== userId) throw new NotFoundError(\"Folder not found\", \"folder\");\n return record;\n }\n\n // ── Mutations ────────────────────────────────────────────────────────────\n\n async rename(userId: string, id: string, name: string): Promise<FolderRecord> {\n const existing = await this.get(userId, id);\n const clean = sanitizeFileName(name);\n if (!clean) throw new ValidationError(\"Invalid folder name\");\n const updated = await this.folders.update(id, { name: clean });\n await this.activity.record({\n userId,\n action: \"rename\",\n resourceType: \"folder\",\n resourceId: id,\n resourceName: clean,\n metadata: { previousName: existing.name },\n });\n return updated;\n }\n\n async move(userId: string, id: string, newParentId: string | null): Promise<FolderRecord> {\n const existing = await this.get(userId, id);\n\n if (newParentId === id) throw new ValidationError(\"Cannot move a folder into itself\");\n\n if (newParentId !== null) {\n const target = await this.get(userId, newParentId);\n if (target.deletedAt) throw new ConflictError(\"The target folder is in the Trash\");\n\n // Walk up from the new parent; hitting the folder being moved means it\n // would become its own ancestor.\n let cursor: string | null = newParentId;\n while (cursor !== null) {\n if (cursor === id) {\n throw new ValidationError(\"Cannot move a folder into one of its subfolders\");\n }\n const parent = await this.folders.findById(cursor);\n if (!parent || parent.userId !== userId)\n throw new NotFoundError(\"Folder not found\", \"folder\");\n cursor = parent.parentId;\n }\n }\n\n // The repository re-checks the ancestor chain and writes atomically, so a\n // concurrent move cannot slip a cycle in between the check above and the write.\n const updated = await this.folders.moveSafely(id, newParentId);\n await this.activity.record({\n userId,\n action: \"move\",\n resourceType: \"folder\",\n resourceId: id,\n resourceName: updated.name,\n metadata: { from: existing.parentId, to: newParentId },\n });\n return updated;\n }\n\n async setStarred(userId: string, id: string, starred: boolean): Promise<FolderRecord> {\n await this.get(userId, id);\n const updated = await this.folders.update(id, { starred });\n await this.activity.record({\n userId,\n action: starred ? \"star\" : \"unstar\",\n resourceType: \"folder\",\n resourceId: id,\n resourceName: updated.name,\n });\n return updated;\n }\n\n // ── Trash lifecycle ──────────────────────────────────────────────────────\n\n /**\n * Moves a folder and its entire subtree to the Trash. Nothing is deleted\n * from Telegram, so restore is lossless.\n */\n async trash(userId: string, id: string): Promise<FolderMutationResult> {\n const record = await this.get(userId, id);\n if (record.deletedAt) {\n return { affectedFolders: 1, affectedFiles: 0 };\n }\n\n const subtree = await this.collectSubtree(userId, id);\n const fileIds = await this.collectFileIds(userId, subtree);\n\n // One batch id stamps everything this operation trashes, so restore can\n // bring back exactly this set without relying on timestamp equality.\n const batchId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n const stampedAt = new Date();\n // One atomic write: a failure cannot trash the folders and leave their files active.\n await this.folders.trashSubtree(\n subtree.map((folder) => folder.id),\n fileIds,\n { deletedAt: stampedAt, trashBatchId: batchId },\n );\n\n await this.activity.record({\n userId,\n action: \"trash\",\n resourceType: \"folder\",\n resourceId: id,\n resourceName: record.name,\n metadata: { folders: subtree.length, files: fileIds.length },\n });\n\n return { affectedFolders: subtree.length, affectedFiles: fileIds.length };\n }\n\n /**\n * Restores a folder subtree. Only nodes trashed at the same instant as the\n * folder itself are restored, so items the user trashed separately stay put.\n */\n async restore(userId: string, id: string): Promise<FolderMutationResult> {\n const record = await this.get(userId, id);\n\n // If the parent chain is still trashed, restore to the root instead of\n // creating a folder the user cannot see.\n let parentId = record.parentId;\n if (parentId !== null) {\n const parent = await this.folders.findById(parentId);\n if (!parent || parent.userId !== userId || parent.deletedAt) parentId = null;\n }\n\n const subtree = await this.collectSubtree(userId, id, { includeTrashed: true });\n const batchId = record.trashBatchId;\n\n // Prefer the batch marker; fall back to timestamp equality for rows\n // created before the marker existed (v0.2 upgrade path).\n const belongsToBatch = (deletedAt: Date | null, marker: string | null): boolean =>\n deletedAt !== null &&\n (batchId !== null\n ? marker === batchId\n : record.deletedAt !== null && deletedAt.getTime() === record.deletedAt.getTime());\n\n const restorableFolders = subtree.filter((folder) =>\n belongsToBatch(folder.deletedAt, folder.trashBatchId),\n );\n const restorableFolderIds = new Set(restorableFolders.map((folder) => folder.id));\n\n const allFiles = await this.files.listByUser(userId);\n const restorableFiles = allFiles.filter(\n (file) =>\n file.folderId !== null &&\n restorableFolderIds.has(file.folderId) &&\n belongsToBatch(file.deletedAt, file.trashBatchId),\n );\n\n const restorableFileIds = restorableFiles.map((file) => file.id);\n // One atomic write: a failure cannot restore the folders and leave their files in Trash.\n if (restorableFolders.length > 0) {\n await this.folders.restoreSubtree(\n restorableFolders.map((folder) => folder.id),\n restorableFileIds,\n { rootId: id, reparent: { parentId } },\n );\n }\n\n await this.activity.record({\n userId,\n action: \"restore\",\n resourceType: \"folder\",\n resourceId: id,\n resourceName: record.name,\n metadata: { folders: restorableFolders.length, files: restorableFiles.length },\n });\n\n return { affectedFolders: restorableFolders.length, affectedFiles: restorableFiles.length };\n }\n\n /**\n * Permanently deletes a folder subtree: remote objects first, then\n * metadata. Files whose remote delete fails are left in the Trash so the\n * user can retry rather than losing data silently.\n */\n async deletePermanently(userId: string, id: string): Promise<FolderMutationResult> {\n await this.get(userId, id);\n\n const subtree = await this.collectSubtree(userId, id, { includeTrashed: true });\n const folderIds = subtree.map((folder) => folder.id);\n\n const allFiles = await this.files.listByUser(userId);\n const affected = allFiles.filter(\n (file) => file.folderId !== null && folderIds.includes(file.folderId),\n );\n\n const engine = await this.engineFor(userId);\n const removedFileIds: string[] = [];\n // Every Telegram object a file owns, current and historical, must go before its\n // metadata: the version rows are the only record of the older messages.\n const versionsByFile = await this.files.listVersionsForFiles(affected.map((file) => file.id));\n for (const file of affected) {\n try {\n const messageIds = new Set<string>([String(file.telegramMessageId)]);\n for (const version of versionsByFile.get(file.id) ?? []) {\n messageIds.add(String(version.telegramMessageId));\n }\n for (const messageId of messageIds) {\n await engine.remove({ messageId });\n }\n removedFileIds.push(file.id);\n } catch {\n // Keep the metadata so the operation can be retried.\n }\n }\n\n if (removedFileIds.length > 0) {\n await this.files.deleteVersionsByFileIds(removedFileIds);\n await this.files.deleteMany(removedFileIds);\n }\n\n // Only delete folders whose files are all gone, plus empty leaves.\n const remainingFolderIds = new Set(\n affected.filter((file) => !removedFileIds.includes(file.id)).map((file) => file.folderId),\n );\n const deletable = deepestFirst(\n folderIds.filter((folderId) => !remainingFolderIds.has(folderId)),\n subtree,\n );\n if (deletable.length > 0) {\n await this.folders.deleteMany(deletable);\n }\n\n await this.activity.record({\n userId,\n action: \"delete\",\n resourceType: \"folder\",\n resourceId: id,\n resourceName: subtree[0]?.name ?? \"folder\",\n metadata: { folders: deletable.length, files: removedFileIds.length },\n });\n\n return { affectedFolders: deletable.length, affectedFiles: removedFileIds.length };\n }\n\n /**\n * v0.1-compatible recursive delete. Now routes through the v0.2 permanent\n * delete so remote cleanup and orphan prevention are consistent.\n */\n async delete(userId: string, id: string): Promise<FolderMutationResult> {\n return this.deletePermanently(userId, id);\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n /** The folder plus every descendant (BFS), optionally including trashed. */\n private async collectSubtree(\n userId: string,\n rootId: string,\n options: { includeTrashed?: boolean } = {},\n ): Promise<FolderRecord[]> {\n const all = await this.folders.listByUser(userId);\n const childrenOf = new Map<string | null, FolderRecord[]>();\n for (const folder of all) {\n const list = childrenOf.get(folder.parentId) ?? [];\n list.push(folder);\n childrenOf.set(folder.parentId, list);\n }\n\n const root = all.find((folder) => folder.id === rootId);\n if (!root) throw new NotFoundError(\"Folder not found\", \"folder\");\n\n const collected: FolderRecord[] = [root];\n const queue: string[] = [rootId];\n while (queue.length > 0) {\n const current = queue.shift()!;\n for (const child of childrenOf.get(current) ?? []) {\n if (!options.includeTrashed && child.deletedAt) continue;\n collected.push(child);\n queue.push(child.id);\n }\n }\n return collected;\n }\n\n /** Ids of files inside the given folders that are not already trashed. */\n private async collectFileIds(userId: string, folders: FolderRecord[]): Promise<string[]> {\n const ids = new Set(folders.map((folder) => folder.id));\n const allFiles = await this.files.listByUser(userId);\n return allFiles\n .filter((file) => file.deletedAt === null && file.folderId !== null && ids.has(file.folderId))\n .map((file) => file.id);\n }\n}\n","import { paginationMeta, parseSearchQuery } from \"@omnicloud/shared\";\nimport type { ListQuery, PaginationDTO, SearchResultDTO } from \"@omnicloud/shared\";\nimport { ValidationError } from \"../errors\";\nimport type { FileRecord, FolderRecord, PageRequest } from \"../types\";\nimport type { FileRepository, FolderRepository } from \"../repos\";\nimport { resolveItemQuery } from \"./query-resolver\";\n\nconst MAX_QUERY_LENGTH = 200;\n\nexport interface SearchResult {\n files: FileRecord[];\n folders: FolderRecord[];\n pagination: PaginationDTO;\n}\n\n/**\n * PostgreSQL-backed search (no Elasticsearch, per the v0.2 scope).\n *\n * The user query is parsed into structured filters (`type:`, `size:`,\n * `folder:`, …) and evaluated by the repository layer, so results respect\n * ownership and stay safely parameterized.\n */\nexport class SearchService {\n constructor(\n private readonly files: FileRepository,\n private readonly folders: FolderRepository,\n ) {}\n\n async search(\n userId: string,\n rawQuery: string,\n page: PageRequest,\n overrides: ListQuery = {},\n ): Promise<SearchResult> {\n const trimmed = rawQuery.trim();\n if (!trimmed) throw new ValidationError(\"Search query must not be empty\");\n if (trimmed.length > MAX_QUERY_LENGTH) {\n throw new ValidationError(`Search query must be at most ${MAX_QUERY_LENGTH} characters`);\n }\n\n const { query } = parseSearchQuery(trimmed, overrides);\n // Search covers both active and trashed items unless the caller narrows it.\n if (query.status === undefined) query.status = \"all\";\n\n const itemQuery = await resolveItemQuery(this.folders, userId, query);\n\n const [filePage, folderPage] = await Promise.all([\n this.files.query(userId, itemQuery, page),\n this.folders.query(userId, itemQuery, page),\n ]);\n\n // The pagination envelope describes the combined result count so the UI\n // can render a single \"load more\" affordance.\n const total = filePage.total + folderPage.total;\n return {\n files: filePage.items,\n folders: folderPage.items,\n pagination: paginationMeta(page.page, page.limit, total),\n };\n }\n\n /** Convenience wrapper returning the shared DTO shape. */\n async searchDTO(\n userId: string,\n rawQuery: string,\n page: PageRequest,\n ): Promise<Omit<SearchResultDTO, \"files\" | \"folders\"> & SearchResult> {\n return this.search(userId, rawQuery, page);\n }\n}\n","import { fileCategory } from \"@omnicloud/shared\";\nimport type { StorageStats } from \"../types\";\nimport type { FileRepository, FolderRepository } from \"../repos\";\n\nconst LARGEST_FILES = 10;\n\n/**\n * Storage dashboard statistics, computed from PostgreSQL metadata only —\n * no Telegram round-trips, so the page is fast and works offline.\n */\nexport class StatsService {\n constructor(\n private readonly files: FileRepository,\n private readonly folders: FolderRepository,\n ) {}\n\n async stats(userId: string, quotaBytes: number | null = null): Promise<StorageStats> {\n const [counters, allFiles] = await Promise.all([\n this.files.statsByUser(userId),\n // Metadata-only aggregation; v0.2 datasets fit comfortably in memory.\n this.files.listByUser(userId),\n ]);\n\n const active = allFiles.filter((file) => file.deletedAt === null);\n const categoryBytes = new Map<string, { bytes: number; count: number }>();\n\n for (const file of active) {\n const category = fileCategory(file.mimeType);\n const bucket = categoryBytes.get(category) ?? { bytes: 0, count: 0 };\n bucket.bytes += file.size;\n bucket.count += 1;\n categoryBytes.set(category, bucket);\n }\n\n const byType = [...categoryBytes.entries()]\n .map(([category, bucket]) => ({ category, bytes: bucket.bytes, count: bucket.count }))\n .sort((a, b) => b.bytes - a.bytes);\n\n const largestFiles = [...active].sort((a, b) => b.size - a.size).slice(0, LARGEST_FILES);\n\n return {\n fileCount: counters.fileCount,\n folderCount: counters.folderCount,\n totalBytes: counters.totalBytes,\n trashBytes: counters.trashBytes,\n trashFileCount: counters.trashFileCount,\n starredCount: counters.starredCount,\n byType,\n largestFiles,\n quotaBytes,\n };\n }\n}\n","import { deepestFirst } from \"../utils/folder-order\";\nimport { paginationMeta } from \"@omnicloud/shared\";\nimport type { ListQuery, PaginationDTO } from \"@omnicloud/shared\";\nimport type { FileRecord, FolderRecord, PageRequest } from \"../types\";\nimport type { FileRepository, FolderRepository } from \"../repos\";\nimport { NotFoundError } from \"../errors\";\nimport type { EngineResolver } from \"./file-service\";\nimport { resolveItemQuery } from \"./query-resolver\";\n\nexport interface TrashListing {\n files: FileRecord[];\n folders: FolderRecord[];\n pagination: PaginationDTO;\n}\n\nexport interface EmptyTrashResult {\n deletedFiles: number;\n deletedFolders: number;\n failedFiles: number;\n}\n\n/**\n * Trash is a virtual view over soft-deleted rows. Listing shows only the\n * explicitly trashed items; emptying removes their remote objects before the\n * metadata, and reports failures instead of hiding them.\n */\nexport class TrashService {\n constructor(\n private readonly files: FileRepository,\n private readonly folders: FolderRepository,\n private readonly engineFor: EngineResolver,\n ) {}\n\n async list(userId: string, page: PageRequest, extra: ListQuery = {}): Promise<TrashListing> {\n const itemQuery = await resolveItemQuery(this.folders, userId, {\n ...extra,\n status: \"trashed\",\n folderId: undefined,\n });\n\n const [filePage, folderPage] = await Promise.all([\n this.files.query(userId, itemQuery, page),\n this.folders.query(userId, itemQuery, page),\n ]);\n\n const total = filePage.total + folderPage.total;\n return {\n files: filePage.items,\n folders: folderPage.items,\n pagination: paginationMeta(page.page, page.limit, total),\n };\n }\n\n /**\n * Permanently removes everything in the Trash for a user. Folders are\n * processed root-first so removing a parent also removes its contents.\n */\n async empty(userId: string): Promise<EmptyTrashResult> {\n const engine = await this.engineFor(userId);\n const allFiles = await this.files.listByUser(userId);\n const trashedFiles = allFiles.filter((file) => file.deletedAt !== null);\n\n let deletedFiles = 0;\n let failedFiles = 0;\n const deletedFileIds: string[] = [];\n\n for (const file of trashedFiles) {\n try {\n await engine.remove({ messageId: String(file.telegramMessageId) });\n deletedFileIds.push(file.id);\n deletedFiles += 1;\n } catch {\n failedFiles += 1;\n }\n }\n\n if (deletedFileIds.length > 0) {\n await this.files.deleteVersionsByFileIds(deletedFileIds);\n await this.files.deleteMany(deletedFileIds);\n }\n\n // Remove trashed folders, shallowest first, and only those with no\n // remaining files inside.\n const allFolders = await this.folders.listByUser(userId);\n const trashedFolders = allFolders.filter((folder) => folder.deletedAt !== null);\n const orderedIds = deepestFirst(\n trashedFolders.map((folder) => folder.id),\n allFolders,\n );\n\n let deletedFolders = 0;\n for (const folderId of orderedIds) {\n const folder = trashedFolders.find((candidate) => candidate.id === folderId)!;\n // Skip folders that still contain files we could not delete.\n const stillHasFiles = await this.files.query(\n userId,\n { folderId: folder.id, trashed: true },\n { page: 1, limit: 1 },\n );\n if (stillHasFiles.total > 0) continue;\n await this.folders.deleteMany([folder.id]);\n deletedFolders += 1;\n }\n\n return { deletedFiles, deletedFolders, failedFiles };\n }\n\n /** Restores a single trashed item (used by the batch restore endpoint). */\n async verifyTrashedFile(userId: string, id: string): Promise<FileRecord> {\n const file = await this.files.findById(id);\n if (!file || file.userId !== userId || file.deletedAt === null) {\n throw new NotFoundError(\"File is not in the Trash\", \"file\");\n }\n return file;\n }\n}\n","import type { IntegrityIssueDTO, IntegrityReportDTO } from \"@omnicloud/shared\";\nimport type { FileRecord } from \"../types\";\nimport type { FileRepository } from \"../repos\";\nimport type { EngineResolver } from \"./file-service\";\nimport { IntegrityCheckError } from \"../errors\";\n\nexport interface IntegrityCheckOptions {\n /** Re-download and re-hash objects (slow but authoritative). */\n deep?: boolean;\n /** Maximum files to inspect in one pass. */\n limit?: number;\n}\n\n/**\n * Detects drift between PostgreSQL metadata and Telegram storage:\n *\n * - `missing` — metadata exists, the Telegram message is gone\n * - `size_mismatch` — the stored object's size differs from the metadata\n * - `hash_mismatch` — deep mode only: downloaded bytes don't match sha256\n * - `unreadable` — the provider errored while inspecting the object\n *\n * The checker is strictly read-only: repairs are a separate, explicit step so\n * nothing is ever deleted silently (see the v0.2 reliability rules).\n */\nexport class IntegrityService {\n constructor(\n private readonly files: FileRepository,\n private readonly engineFor: EngineResolver,\n ) {}\n\n async check(userId: string, options: IntegrityCheckOptions = {}): Promise<IntegrityReportDTO> {\n const startedAt = Date.now();\n const all = await this.files.listByUser(userId);\n const active = all.filter((file) => file.deletedAt === null);\n const targets = options.limit ? active.slice(0, options.limit) : active;\n\n const engine = await this.engineFor(userId);\n const issues: IntegrityIssueDTO[] = [];\n let healthy = 0;\n const versionsByFile = await this.files.listVersionsForFiles(targets.map((file) => file.id));\n\n for (const file of targets) {\n const issue = await this.inspect(file, options.deep ?? false, engine);\n if (issue) issues.push(issue);\n else healthy += 1;\n\n for (const version of versionsByFile.get(file.id) ?? []) {\n if (version.telegramMessageId === file.telegramMessageId) continue;\n const versionIssue = await this.inspectObject(\n {\n id: file.id,\n name: file.name,\n telegramMessageId: version.telegramMessageId,\n size: version.size,\n sha256: version.sha256,\n },\n options.deep ?? false,\n engine,\n version.id,\n );\n if (versionIssue) issues.push(versionIssue);\n else healthy += 1;\n }\n }\n\n const missing = issues.filter((issue) => issue.kind === \"missing\").length;\n const inconsistent = issues.filter(\n (issue) => issue.kind === \"size_mismatch\" || issue.kind === \"hash_mismatch\",\n ).length;\n const unreadable = issues.filter((issue) => issue.kind === \"unreadable\").length;\n\n return {\n checkedAt: new Date().toISOString(),\n filesChecked: targets.length,\n healthy,\n missing,\n inconsistent,\n unreadable,\n issues,\n durationMs: Date.now() - startedAt,\n };\n }\n\n private async inspect(\n file: FileRecord,\n deep: boolean,\n engine: Awaited<ReturnType<EngineResolver>>,\n ): Promise<IntegrityIssueDTO | null> {\n return this.inspectObject(\n {\n id: file.id,\n name: file.name,\n telegramMessageId: file.telegramMessageId,\n size: file.size,\n sha256: file.sha256,\n },\n deep,\n engine,\n undefined,\n );\n }\n\n private async inspectObject(\n target: {\n id: string;\n name: string;\n telegramMessageId: number;\n size: number;\n sha256: string;\n },\n deep: boolean,\n engine: Awaited<ReturnType<EngineResolver>>,\n versionId: string | undefined,\n ): Promise<IntegrityIssueDTO | null> {\n const ref = { messageId: String(target.telegramMessageId) };\n const withVersion = (issue: IntegrityIssueDTO): IntegrityIssueDTO =>\n versionId ? { ...issue, versionId } : issue;\n\n let stat: Awaited<ReturnType<typeof engine.stat>>;\n try {\n stat = await engine.stat(ref);\n } catch (error) {\n return withVersion({\n fileId: target.id,\n name: target.name,\n kind: \"unreadable\",\n detail: error instanceof Error ? error.message : \"Storage lookup failed\",\n });\n }\n\n if (!stat) {\n return withVersion({\n fileId: target.id,\n name: target.name,\n kind: \"missing\",\n detail: \"No message with this id exists in the storage channel\",\n });\n }\n\n if (stat.size !== target.size) {\n return withVersion({\n fileId: target.id,\n name: target.name,\n kind: \"size_mismatch\",\n detail: `metadata=${target.size} storage=${stat.size}`,\n });\n }\n\n if (deep) {\n try {\n // Streamed and hashed without holding the object in memory. A checksum\n // mismatch surfaces as IntegrityCheckError from the stream.\n const stream = await engine.downloadStream(ref, target.sha256);\n for await (const _chunk of stream) {\n // Consumed only to drive the verification; the bytes are discarded.\n }\n } catch (error) {\n if (error instanceof IntegrityCheckError) {\n return withVersion({\n fileId: target.id,\n name: target.name,\n kind: \"hash_mismatch\",\n detail: \"Downloaded bytes do not match the stored SHA-256\",\n });\n }\n return withVersion({\n fileId: target.id,\n name: target.name,\n kind: \"unreadable\",\n detail: error instanceof Error ? error.message : \"Download failed\",\n });\n }\n }\n\n return null;\n }\n}\n","import { paginationMeta } from \"@omnicloud/shared\";\nimport type { ActivityAction, RecentItemDTO, RecentPageDTO } from \"@omnicloud/shared\";\nimport type { PageRequest } from \"../types\";\nimport type { ActivityRepository, FileRepository } from \"../repos\";\n\n/** Actions that count as meaningful \"recent\" activity for a file. */\nconst RECENT_ACTIONS: ActivityAction[] = [\n \"upload\",\n \"download\",\n \"open\",\n \"rename\",\n \"move\",\n \"replace\",\n];\n\n/**\n * Recent view: the most recently touched active files, newest activity first.\n * Trashed and permanently-deleted files are filtered out so the view never\n * surfaces items the user cannot open.\n */\nexport class RecentService {\n constructor(\n private readonly activity: ActivityRepository,\n private readonly files: FileRepository,\n ) {}\n\n async list(userId: string, page: PageRequest): Promise<RecentPageDTO> {\n // Pull a bounded window of recent *meaningful* events (the repository\n // dedups by file), then resolve the distinct files.\n const window = Math.min(page.page * page.limit * 3, 500);\n const events = await this.activity.recentFiles(userId, RECENT_ACTIONS, window);\n\n const seen = new Set<string>();\n for (const event of events) {\n seen.add(event.resourceId);\n }\n\n const fileIds = [...seen];\n const records = fileIds.length > 0 ? await this.files.listByIds(userId, fileIds) : [];\n const byId = new Map(records.map((record) => [record.id, record]));\n\n const items: RecentItemDTO[] = [];\n for (const fileId of fileIds) {\n const record = byId.get(fileId);\n if (!record || record.deletedAt !== null) continue;\n const event = events.find((candidate) => candidate.resourceId === fileId)!;\n items.push({\n file: {\n id: record.id,\n name: record.name,\n size: record.size,\n mimeType: record.mimeType,\n sha256: record.sha256,\n folderId: record.folderId,\n starred: record.starred,\n trashed: false,\n deletedAt: null,\n versionCount: record.versionCount,\n currentVersionId: record.currentVersionId,\n createdAt: record.createdAt.toISOString(),\n updatedAt: record.updatedAt.toISOString(),\n },\n lastAction: event.action,\n lastActionAt: event.createdAt.toISOString(),\n });\n }\n\n const start = (page.page - 1) * page.limit;\n const pageItems = items.slice(start, start + page.limit);\n\n return {\n items: pageItems,\n pagination: paginationMeta(page.page, page.limit, items.length),\n };\n }\n}\n","import { createHash } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { mkdtemp, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { Transform, type Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport { PayloadTooLargeError } from \"../errors\";\n\nexport interface SpooledFile {\n /** Path to the temporary copy. The caller must call `discard()` when done. */\n path: string;\n size: number;\n sha256: string;\n discard: () => Promise<void>;\n}\n\n/**\n * Writes an upload stream to a temporary file while computing its SHA-256 and\n * byte count. Memory use is bounded by the stream's chunk size, not the file\n * size. The size cap is enforced while writing, so an oversized upload is\n * rejected without being stored in full.\n */\nconst SPOOL_PREFIX = \"omnicloud-upload-\";\n\nexport async function spoolToFile(source: Readable, maxBytes: number): Promise<SpooledFile> {\n const dir = await mkdtemp(join(tmpdir(), `${SPOOL_PREFIX}${process.pid}-`));\n const path = join(dir, \"payload\");\n const discard = () => rm(dir, { recursive: true, force: true });\n\n const hash = createHash(\"sha256\");\n let size = 0;\n const meter = new Transform({\n transform(chunk: Buffer, _encoding, callback) {\n size += chunk.byteLength;\n if (size > maxBytes) {\n callback(\n new PayloadTooLargeError(\n `Files larger than ${Math.floor(maxBytes / (1024 * 1024))} MB are not supported`,\n ),\n );\n return;\n }\n hash.update(chunk);\n callback(null, chunk);\n },\n });\n\n try {\n await pipeline(source, meter, createWriteStream(path));\n } catch (error) {\n await discard();\n throw error;\n }\n\n return { path, size, sha256: hash.digest(\"hex\"), discard };\n}\n\n/** True when a process with this PID exists and is not just a stale record. */\nfunction isAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/**\n * Removes spool directories left by processes that are no longer running,\n * which happens when a process is killed mid-upload and `finally` cannot run.\n * Directories owned by a live process are never touched, so a concurrent\n * upload's bytes are not deleted.\n */\nexport async function sweepStaleSpools(): Promise<number> {\n const { readdir } = await import(\"node:fs/promises\");\n const entries = await readdir(tmpdir()).catch(() => [] as string[]);\n let removed = 0;\n for (const name of entries) {\n if (!name.startsWith(SPOOL_PREFIX)) continue;\n const pid = Number(name.slice(SPOOL_PREFIX.length).split(\"-\")[0]);\n if (!Number.isInteger(pid) || pid <= 0 || isAlive(pid)) continue;\n await rm(join(tmpdir(), name), { recursive: true, force: true });\n removed += 1;\n }\n return removed;\n}\n","import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from \"node:crypto\";\nimport { ValidationError } from \"../errors\";\n\n/**\n * Authenticated encryption for values stored at rest (Telegram session strings).\n *\n * AES-256-GCM with a fresh random 96-bit IV per seal. The stored envelope is\n * `v<keyVersion>:<iv>:<tag>:<ciphertext>` (base64url), so a key can be rotated\n * later by keeping old versions available for decryption.\n */\n\nconst ALGORITHM = \"aes-256-gcm\";\nconst IV_BYTES = 12;\nconst KEY_BYTES = 32;\n\nexport interface SecretBoxKey {\n version: number;\n key: Buffer;\n}\n\nexport class SecretBox {\n private readonly keys: Map<number, Buffer>;\n private readonly currentVersion: number;\n\n /**\n * @param keys key versions available for decryption; the highest version is\n * used for new seals.\n */\n constructor(keys: SecretBoxKey[]) {\n if (keys.length === 0) {\n throw new ValidationError(\"At least one encryption key is required\");\n }\n this.keys = new Map();\n for (const { version, key } of keys) {\n if (key.byteLength !== KEY_BYTES) {\n throw new ValidationError(`Encryption key version ${version} must be 32 bytes`);\n }\n this.keys.set(version, key);\n }\n this.currentVersion = Math.max(...this.keys.keys());\n }\n\n seal(plaintext: string): string {\n const key = this.keys.get(this.currentVersion)!;\n const iv = randomBytes(IV_BYTES);\n const cipher = createCipheriv(ALGORITHM, key, iv);\n const ciphertext = Buffer.concat([cipher.update(plaintext, \"utf8\"), cipher.final()]);\n const tag = cipher.getAuthTag();\n return [\n `v${this.currentVersion}`,\n iv.toString(\"base64url\"),\n tag.toString(\"base64url\"),\n ciphertext.toString(\"base64url\"),\n ].join(\":\");\n }\n\n open(envelope: string): string {\n const parts = envelope.split(\":\");\n if (parts.length !== 4 || !parts[0]!.startsWith(\"v\")) {\n throw new SecretBoxError(\"Malformed sealed value\");\n }\n const version = Number(parts[0]!.slice(1));\n const key = this.keys.get(version);\n if (!key) throw new SecretBoxError(`No encryption key for version ${version}`);\n\n try {\n const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(parts[1]!, \"base64url\"));\n decipher.setAuthTag(Buffer.from(parts[2]!, \"base64url\"));\n const plaintext = Buffer.concat([\n decipher.update(Buffer.from(parts[3]!, \"base64url\")),\n decipher.final(),\n ]);\n return plaintext.toString(\"utf8\");\n } catch {\n throw new SecretBoxError(\"Sealed value failed authentication\");\n }\n }\n\n get currentKeyVersion(): number {\n return this.currentVersion;\n }\n}\n\n/** Raised for corrupt, tampered, or undecryptable values. Never includes the value itself. */\nexport class SecretBoxError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SecretBoxError\";\n }\n}\n\n/** Fixed salt: the same secret must derive the same key on every start. */\nconst KDF_SALT = \"omnicloud-secret-box-v1\";\n\n/**\n * Derives a 256-bit key from an operator-supplied secret. Accepts either a\n * 64-character hex string (preferred: `openssl rand -hex 32`, used as-is) or a\n * passphrase of at least 32 characters, stretched with scrypt so a weak\n * passphrase is expensive to brute-force.\n */\nexport function deriveKey(secret: string): Buffer {\n if (/^[0-9a-f]{64}$/i.test(secret)) return Buffer.from(secret, \"hex\");\n if (secret.length < 32) {\n throw new ValidationError(\n \"The encryption key must be 64 hex characters or at least 32 characters\",\n );\n }\n return scryptSync(secret, KDF_SALT, KEY_BYTES);\n}\n","import { Api, TelegramClient } from \"telegram\";\nimport { StringSession } from \"telegram/sessions/index.js\";\nimport {\n StorageUnavailableError,\n TelegramAuthRequiredError,\n TelegramConnectionError,\n UnauthorizedError,\n} from \"@omnicloud/core\";\nimport type { ConnectionState, SessionRepository, StorageHealth } from \"@omnicloud/core\";\nimport { mapTelegramError } from \"./errors\";\n\nexport interface TelegramCredentials {\n apiId: number;\n apiHash: string;\n}\n\ninterface ManagedConnection {\n client: TelegramClient;\n state: ConnectionState;\n /** In-flight connect/reconnect promise, so callers can await it. */\n pending: Promise<TelegramClient> | null;\n lastError: string | null;\n lastCheckAt: number | null;\n lastCheckOk: boolean | null;\n}\n\nexport interface ConnectionStatus {\n state: ConnectionState;\n lastError: string | null;\n lastCheckAt: Date | null;\n healthy: boolean | null;\n}\n\n/** How long a health probe is cached before re-probing. */\nconst HEALTH_TTL_MS = 30_000;\n/** Minimum gap between reconnect attempts for the same user. */\nconst RECONNECT_COOLDOWN_MS = 5_000;\n\n/**\n * Owns one Telegram (MTProto) connection per authenticated user and tracks an\n * explicit lifecycle state for each:\n *\n * DISCONNECTED → CONNECTING → CONNECTED\n * ↘ RECONNECTING → CONNECTED\n * ↘ ERROR | AUTH_REQUIRED\n *\n * Responsibilities: connect, disconnect, reconnect, validateSession,\n * healthCheck, refreshConnection. Sessions never leave the server.\n */\nexport class TelegramClientManager {\n private connections = new Map<string, ManagedConnection>();\n private lastReconnectAt = new Map<string, number>();\n\n constructor(\n private readonly credentials: TelegramCredentials,\n private readonly sessions: SessionRepository,\n ) {}\n\n /** Returns a connected client, (re)connecting when needed. */\n async getForUser(userId: string): Promise<TelegramClient> {\n const existing = this.connections.get(userId);\n\n if (existing) {\n if (existing.state === \"CONNECTED\" && this.isConnected(existing.client)) {\n return existing.client;\n }\n if (existing.pending) return existing.pending;\n if (existing.state === \"AUTH_REQUIRED\") {\n throw new TelegramAuthRequiredError();\n }\n return this.reconnect(userId, existing);\n }\n\n return this.connectUser(userId);\n }\n\n /** Connection status for health reporting and the storage dashboard. */\n statusForUser(userId: string): ConnectionStatus {\n const connection = this.connections.get(userId);\n if (!connection) {\n return { state: \"DISCONNECTED\", lastError: null, lastCheckAt: null, healthy: null };\n }\n return {\n state: connection.state,\n lastError: connection.lastError,\n lastCheckAt: connection.lastCheckAt ? new Date(connection.lastCheckAt) : null,\n healthy: connection.lastCheckOk,\n };\n }\n\n /**\n * Probes a user's connection. Cheap by default (connection state only);\n * `deep` performs a real Telegram round-trip to validate the session.\n */\n async healthCheck(userId: string, deep = false): Promise<StorageHealth> {\n const connection = this.connections.get(userId);\n\n if (!connection) {\n try {\n await this.getForUser(userId);\n } catch (error) {\n const state: ConnectionState =\n error instanceof TelegramAuthRequiredError ? \"AUTH_REQUIRED\" : \"ERROR\";\n const current = this.connections.get(userId);\n if (current) current.state = state;\n return {\n healthy: false,\n latencyMs: null,\n message: error instanceof Error ? error.message : \"Telegram is not connected\",\n targetTitle: null,\n };\n }\n }\n\n const active = this.connections.get(userId)!;\n\n if (!deep && active.lastCheckAt && Date.now() - active.lastCheckAt < HEALTH_TTL_MS) {\n return {\n healthy: active.lastCheckOk ?? false,\n latencyMs: null,\n message: active.lastError,\n targetTitle: null,\n };\n }\n\n const startedAt = Date.now();\n try {\n await active.client.getMe();\n active.lastCheckAt = Date.now();\n active.lastCheckOk = true;\n active.lastError = null;\n active.state = \"CONNECTED\";\n return {\n healthy: true,\n latencyMs: Date.now() - startedAt,\n message: null,\n targetTitle: null,\n };\n } catch (error) {\n const mapped = mapTelegramError(error, \"Telegram health check failed\");\n active.lastCheckAt = Date.now();\n active.lastCheckOk = false;\n active.lastError = mapped.message;\n active.state =\n mapped instanceof TelegramAuthRequiredError || mapped instanceof UnauthorizedError\n ? \"AUTH_REQUIRED\"\n : \"ERROR\";\n return {\n healthy: false,\n latencyMs: Date.now() - startedAt,\n message: mapped.message,\n targetTitle: null,\n };\n }\n }\n\n /** Registers an already-authenticated client (from the login flow). */\n cacheClient(userId: string, client: TelegramClient): void {\n this.connections.set(userId, {\n client,\n state: \"CONNECTED\",\n pending: null,\n lastError: null,\n lastCheckAt: Date.now(),\n lastCheckOk: true,\n });\n }\n\n /** Drops a user's connection (e.g. after logout) without touching storage. */\n async disconnectUser(userId: string): Promise<void> {\n const connection = this.connections.get(userId);\n if (!connection) return;\n this.connections.delete(userId);\n try {\n await connection.client.disconnect();\n } catch {\n // best effort\n }\n }\n\n async disconnectAll(): Promise<void> {\n const ids = [...this.connections.keys()];\n await Promise.all(ids.map((userId) => this.disconnectUser(userId)));\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private isConnected(client: TelegramClient): boolean {\n try {\n return client.connected === true;\n } catch {\n return false;\n }\n }\n\n private async connectUser(userId: string): Promise<TelegramClient> {\n const session = await this.sessions.get(userId);\n if (!session) {\n throw new TelegramAuthRequiredError(\"Telegram account is not connected\");\n }\n\n const connection: ManagedConnection = {\n client: null as unknown as TelegramClient,\n state: \"CONNECTING\",\n pending: null,\n lastError: null,\n lastCheckAt: null,\n lastCheckOk: null,\n };\n\n const attempt = this.establish(session.stringSession).then((client) => {\n connection.client = client;\n connection.state = \"CONNECTED\";\n connection.pending = null;\n connection.lastError = null;\n return client;\n });\n connection.pending = attempt;\n this.connections.set(userId, connection);\n\n try {\n return await attempt;\n } catch (error) {\n connection.pending = null;\n const mapped = mapTelegramError(error, \"Could not connect to Telegram\");\n connection.state =\n mapped instanceof TelegramAuthRequiredError || mapped instanceof UnauthorizedError\n ? \"AUTH_REQUIRED\"\n : \"ERROR\";\n connection.lastError = mapped.message;\n throw mapped;\n }\n }\n\n private async reconnect(userId: string, connection: ManagedConnection): Promise<TelegramClient> {\n const lastAttempt = this.lastReconnectAt.get(userId) ?? 0;\n if (Date.now() - lastAttempt < RECONNECT_COOLDOWN_MS && connection.pending) {\n return connection.pending;\n }\n this.lastReconnectAt.set(userId, Date.now());\n\n connection.state = \"RECONNECTING\";\n const session = await this.sessions.get(userId);\n if (!session) {\n connection.state = \"AUTH_REQUIRED\";\n throw new TelegramAuthRequiredError(\"Telegram account is not connected\");\n }\n\n const attempt = (async () => {\n try {\n await connection.client.disconnect().catch(() => undefined);\n } catch {\n // ignore\n }\n return this.establish(session.stringSession);\n })()\n .then((client) => {\n connection.client = client;\n connection.state = \"CONNECTED\";\n connection.pending = null;\n connection.lastError = null;\n return client;\n })\n .catch((error: unknown) => {\n connection.pending = null;\n const mapped = mapTelegramError(error, \"Could not reconnect to Telegram\");\n connection.state =\n mapped instanceof TelegramAuthRequiredError || mapped instanceof UnauthorizedError\n ? \"AUTH_REQUIRED\"\n : \"ERROR\";\n connection.lastError = mapped.message;\n throw mapped;\n });\n\n connection.pending = attempt;\n return attempt;\n }\n\n private async establish(sessionString: string): Promise<TelegramClient> {\n const client = new TelegramClient(\n new StringSession(sessionString),\n this.credentials.apiId,\n this.credentials.apiHash,\n { connectionRetries: 3 },\n );\n try {\n await client.connect();\n // Validate the session immediately so expired sessions surface as\n // AUTH_REQUIRED instead of a generic failure on the first operation.\n if (!client.connected) {\n throw new TelegramConnectionError(\"Telegram client did not connect\");\n }\n return client;\n } catch (error) {\n throw mapTelegramError(error, \"Could not connect to Telegram\");\n }\n }\n}\n\nexport function newTelegramClient(credentials: TelegramCredentials): TelegramClient {\n return new TelegramClient(new StringSession(\"\"), credentials.apiId, credentials.apiHash, {\n connectionRetries: 3,\n });\n}\n\n/**\n * Extracts the freshly created broadcast channel from a\n * channels.CreateChannel response.\n */\nexport function extractCreatedChannel(result: Api.TypeUpdates): Api.Channel {\n const chats =\n result instanceof Api.Updates || result instanceof Api.UpdatesCombined ? result.chats : [];\n for (const chat of chats) {\n if (chat instanceof Api.Channel) return chat;\n }\n throw new TelegramConnectionError(\"Telegram did not return the created storage channel\");\n}\n\nexport { StorageUnavailableError };\n","import { RPCError } from \"telegram/errors/index.js\";\nimport {\n DomainError,\n ForbiddenError,\n RateLimitedError,\n StorageNotInitializedError,\n TelegramAuthRequiredError,\n TelegramConnectionError,\n TelegramFileNotFoundError,\n UnauthorizedError,\n ValidationError,\n} from \"@omnicloud/core\";\n\nexport function isTelegramRpcError(error: unknown): error is RPCError {\n return error instanceof RPCError;\n}\n\n/** True when the error signals that 2FA password auth is required. */\nexport function isPasswordRequiredError(error: unknown): boolean {\n return isTelegramRpcError(error) && error.errorMessage === \"SESSION_PASSWORD_NEEDED\";\n}\n\n/**\n * Maps a Telegram (GramJS) failure to a structured domain error with an\n * appropriate HTTP status and a client-facing message that never leaks\n * credentials or session material.\n */\nexport function mapTelegramError(error: unknown, fallbackMessage: string): DomainError {\n if (error instanceof DomainError) return error;\n\n if (isTelegramRpcError(error)) {\n const msg = error.errorMessage ?? \"\";\n\n // GramJS reports a flood wait as errorMessage \"FLOOD\" and carries the server's\n // delay in `seconds`, so read that first. The prefix check only covers raw text.\n const seconds = (error as unknown as { seconds?: unknown }).seconds;\n const floodSeconds =\n typeof seconds === \"number\"\n ? seconds\n : msg.startsWith(\"FLOOD_WAIT_\")\n ? Number(msg.slice(\"FLOOD_WAIT_\".length))\n : null;\n if (msg.startsWith(\"FLOOD\") && floodSeconds !== null && floodSeconds > 0) {\n return new RateLimitedError(\n `Telegram rate limit reached — retry in ${floodSeconds} seconds`,\n {\n retryAfterSeconds: floodSeconds,\n },\n );\n }\n\n switch (msg) {\n case \"PHONE_NUMBER_INVALID\":\n return new ValidationError(\"This phone number is not valid\");\n case \"PHONE_NUMBER_UNOCCUPIED\":\n return new ValidationError(\"No Telegram account exists for this phone number\");\n case \"PHONE_CODE_INVALID\":\n return new ValidationError(\"Invalid confirmation code\");\n case \"PHONE_CODE_EXPIRED\":\n return new ValidationError(\"The confirmation code has expired — request a new one\");\n case \"PHONE_CODE_EMPTY\":\n return new ValidationError(\"Please enter the confirmation code\");\n case \"SESSION_PASSWORD_NEEDED\":\n return new UnauthorizedError(\"Two-factor authentication is enabled on this account\");\n case \"PASSWORD_HASH_INVALID\":\n return new ValidationError(\"Incorrect two-factor password\");\n case \"PHONE_NUMBER_BANNED\":\n return new ForbiddenError(\"This phone number is banned from Telegram\");\n case \"AUTH_KEY_UNREGISTERED\":\n case \"AUTH_KEY_DUPLICATED\":\n case \"SESSION_REVOKED\":\n case \"SESSION_EXPIRED\":\n case \"USER_DEACTIVATED\":\n case \"USER_DEACTIVATED_BAN\":\n return new TelegramAuthRequiredError();\n case \"CHANNEL_PRIVATE\":\n case \"CHANNEL_INVALID\":\n case \"CHAT_ID_INVALID\":\n return new StorageNotInitializedError();\n // Permanent: the account cannot write to the channel. Retrying cannot help.\n case \"CHAT_WRITE_FORBIDDEN\":\n case \"CHAT_ADMIN_REQUIRED\":\n return new ForbiddenError(\"OmniCloud cannot write to the storage channel for this account\");\n // Permanent: the stored channel reference is stale. Retrying with the same reference fails too.\n case \"PEER_ID_INVALID\":\n return new StorageNotInitializedError();\n case \"MSG_ID_INVALID\":\n case \"MESSAGE_ID_INVALID\":\n return new TelegramFileNotFoundError();\n case \"FILE_REFERENCE_EXPIRED\":\n case \"FILE_REFERENCE_INVALID\":\n return new TelegramFileNotFoundError(\n \"The Telegram file reference expired — re-upload the file to refresh it\",\n );\n default:\n return new TelegramConnectionError(fallbackMessage, {\n code: error.code,\n telegramError: msg,\n });\n }\n }\n\n const detail = error instanceof Error ? error.message : String(error);\n // Network-shaped failures get the connection error code so the UI can offer\n // a retry rather than implying the request was invalid.\n if (/timeout|ECONNRESET|ENOTFOUND|socket|network|connect/i.test(detail)) {\n return new TelegramConnectionError(`${fallbackMessage} (network error)`, error);\n }\n return new TelegramConnectionError(\n detail ? `${fallbackMessage}: ${detail}` : fallbackMessage,\n error,\n );\n}\n","import { Readable } from \"node:stream\";\nimport { Api, TelegramClient } from \"telegram\";\nimport bigInt from \"big-integer\";\nimport { CustomFile } from \"telegram/client/uploads.js\";\nimport {\n OperationCancelledError,\n TelegramAuthRequiredError,\n TelegramConnectionError,\n TelegramFileNotFoundError,\n UploadFailedError,\n type StorageHealth,\n type StorageProvider,\n type StorageUploadInput,\n type StoredObject,\n type StoredRef,\n type TransferControl,\n} from \"@omnicloud/core\";\nimport { mapTelegramError } from \"./errors\";\n\nexport interface TelegramStorageOptions {\n /** Raw channel id of the user's private storage channel. */\n chatId: string;\n /** Channel access hash required to build the input peer. */\n accessHash: string;\n /** Human-readable channel title, surfaced in health reporting. */\n title?: string;\n}\n\n/** Telegram's own limits for a single uploaded document. */\nconst MAX_DOCUMENT_BYTES = 2 * 1024 * 1024 * 1024; // 2 GB\n\n/** Largest single `upload.getFile` request Telegram accepts. */\nconst DOWNLOAD_CHUNK_BYTES = 512 * 1024;\n\n/**\n * StorageProvider implementation backed by the user's private Telegram\n * channel. Files are sent as force-downloaded documents (no compression), so\n * every upload is a document message in the channel.\n *\n * v0.2 adds progress reporting, cancellation, streamed downloads and a\n * health check, all translated into the provider-agnostic core vocabulary.\n */\nexport class TelegramStorageProvider implements StorageProvider {\n readonly name = \"telegram\";\n private readonly peer: Api.InputPeerChannel;\n private readonly title: string | null;\n\n constructor(\n private readonly client: TelegramClient,\n options: TelegramStorageOptions,\n ) {\n this.peer = new Api.InputPeerChannel({\n channelId: bigInt(options.chatId),\n accessHash: bigInt(options.accessHash),\n });\n this.title = options.title ?? null;\n }\n\n // ── Upload ───────────────────────────────────────────────────────────────\n\n async put(input: StorageUploadInput, control: TransferControl = {}): Promise<StoredObject> {\n const total = input.data ? input.data.byteLength : input.size;\n if (total === undefined || (!input.data && !input.path)) {\n throw new UploadFailedError(\"Upload input has neither data nor a spooled file\");\n }\n if (total > MAX_DOCUMENT_BYTES) {\n throw new UploadFailedError(\n `Telegram does not accept documents larger than ${MAX_DOCUMENT_BYTES / (1024 * 1024 * 1024)} GB`,\n );\n }\n this.throwIfAborted(control.signal);\n\n control.onProgress?.({ transferred: 0, total, percent: 0 });\n\n try {\n // A spooled file is handed to GramJS by path, so it reads the parts it\n // needs from disk; an in-memory buffer is passed through unchanged.\n const file = input.data\n ? new CustomFile(input.name, total, \"\", input.data)\n : new CustomFile(input.name, total, input.path!);\n const message = await this.client.sendFile(this.peer, {\n file,\n forceDocument: true,\n workers: 1,\n caption: input.caption,\n // Surface transport-level progress to the caller. GramJS reports a\n // 0..1 fraction. `isCanceled` lets us abort an in-flight upload.\n progressCallback: Object.assign(\n (fraction: number) => {\n const clamped = Math.max(0, Math.min(1, fraction));\n control.onProgress?.({\n transferred: Math.round(clamped * total),\n total,\n percent: Math.round(clamped * 100),\n });\n },\n {\n get isCanceled(): boolean {\n return control.signal?.aborted === true;\n },\n },\n ),\n });\n\n if (!(message instanceof Api.Message)) {\n throw new UploadFailedError(\"Telegram returned an unexpected upload response\");\n }\n\n control.onProgress?.({ transferred: total, total, percent: 100 });\n return {\n messageId: message.id.toString(),\n name: input.name,\n size: total,\n mimeType: input.mimeType,\n };\n } catch (error) {\n if (error instanceof OperationCancelledError) throw error;\n throw mapTelegramError(error, \"Telegram upload failed\");\n }\n }\n\n // ── Download ─────────────────────────────────────────────────────────────\n\n async get(ref: StoredRef, control: TransferControl = {}): Promise<Buffer> {\n const message = await this.requireMessage(ref);\n this.throwIfAborted(control.signal);\n\n const size = this.documentSize(message);\n control.onProgress?.({ transferred: 0, total: size, percent: 0 });\n\n let data: Buffer | string | undefined;\n try {\n data = await this.client.downloadMedia(message, {\n progressCallback: (downloaded: bigInt.BigInteger, total: bigInt.BigInteger) => {\n const transferred = Number(downloaded.toString());\n const totalBytes = Number(total.toString());\n control.onProgress?.({\n transferred,\n total: totalBytes,\n percent: totalBytes > 0 ? Math.round((transferred / totalBytes) * 100) : null,\n });\n },\n });\n } catch (error) {\n throw mapTelegramError(error, \"Telegram download failed\");\n }\n\n if (!data || typeof data === \"string\") {\n throw new TelegramFileNotFoundError(\"Telegram did not return file content\");\n }\n const buffer = Buffer.from(data);\n control.onProgress?.({\n transferred: buffer.byteLength,\n total: buffer.byteLength,\n percent: 100,\n });\n return buffer;\n }\n\n /**\n * Streams the object in Telegram-sized chunks, so memory use stays bounded\n * by one chunk regardless of file size. The first request happens here so\n * that a missing message fails before the caller starts consuming bytes.\n */\n async getStream(ref: StoredRef, control: TransferControl = {}): Promise<Readable> {\n const message = await this.requireMessage(ref);\n this.throwIfAborted(control.signal);\n\n const size = this.documentSize(message);\n const media = message.media;\n if (size === null || !media) {\n throw new TelegramFileNotFoundError(\"Telegram did not return file content\");\n }\n\n const iterator = this.client.iterDownload({\n file: media,\n fileSize: bigInt(size),\n requestSize: DOWNLOAD_CHUNK_BYTES,\n });\n\n let transferred = 0;\n control.onProgress?.({ transferred: 0, total: size, percent: 0 });\n\n const signal = control.signal;\n return Readable.from(\n (async function* () {\n try {\n for await (const chunk of iterator) {\n if (signal?.aborted) throw new OperationCancelledError();\n const buffer = Buffer.from(chunk);\n transferred += buffer.byteLength;\n control.onProgress?.({\n transferred,\n total: size,\n percent: size > 0 ? Math.round((transferred / size) * 100) : null,\n });\n yield buffer;\n }\n } catch (error) {\n if (error instanceof OperationCancelledError) throw error;\n throw mapTelegramError(error, \"Telegram download failed\");\n }\n })(),\n );\n }\n\n // ── Lifecycle ────────────────────────────────────────────────────────────\n\n async delete(ref: StoredRef): Promise<void> {\n try {\n await this.client.deleteMessages(this.peer, [Number(ref.messageId)], { revoke: true });\n } catch (error) {\n // A message that is already gone is already deleted, which the storage contract\n // allows. Any other failure is still reported, so a real problem is not hidden.\n const mapped = mapTelegramError(error, \"Telegram delete failed\");\n if (mapped instanceof TelegramFileNotFoundError) return;\n throw mapped;\n }\n }\n\n async exists(ref: StoredRef): Promise<boolean> {\n try {\n return (await this.stat(ref)) !== null;\n } catch (error) {\n throw mapTelegramError(error, \"Telegram lookup failed\");\n }\n }\n\n /**\n * Read-only listing of stored documents in the channel, newest first, up to\n * `limit`. Only messages that carry a document are returned; text messages and\n * service messages are skipped. Nothing is modified.\n */\n async listObjects(limit: number): Promise<StoredObject[]> {\n const objects: StoredObject[] = [];\n try {\n for await (const message of this.client.iterMessages(this.peer, { limit: limit * 2 })) {\n if (objects.length >= limit) break;\n if (!(message instanceof Api.Message)) continue;\n const document = message.document;\n if (!(document instanceof Api.Document)) continue;\n let fileName: string | undefined;\n for (const attr of document.attributes) {\n if (attr instanceof Api.DocumentAttributeFilename) {\n fileName = attr.fileName;\n break;\n }\n }\n objects.push({\n messageId: message.id.toString(),\n name: fileName ?? \"file\",\n size: Number(document.size.toString()),\n mimeType: document.mimeType,\n });\n }\n } catch (error) {\n throw mapTelegramError(error, \"Telegram listing failed\");\n }\n return objects;\n }\n\n /**\n * Finds stored documents whose caption equals the given caption and whose size\n * matches. Used to recover an upload whose outcome was unknown.\n */\n async findByCaption(caption: string, size: number): Promise<StoredObject[]> {\n const matches: StoredObject[] = [];\n try {\n for await (const message of this.client.iterMessages(this.peer, {\n search: caption,\n limit: 20,\n })) {\n if (!(message instanceof Api.Message)) continue;\n if (message.message !== caption) continue;\n const document = message.document;\n if (!(document instanceof Api.Document)) continue;\n if (Number(document.size.toString()) !== size) continue;\n let fileName: string | undefined;\n for (const attr of document.attributes) {\n if (attr instanceof Api.DocumentAttributeFilename) {\n fileName = attr.fileName;\n break;\n }\n }\n matches.push({\n messageId: message.id.toString(),\n name: fileName ?? \"file\",\n size,\n mimeType: document.mimeType,\n });\n }\n } catch (error) {\n throw mapTelegramError(error, \"Telegram lookup failed\");\n }\n return matches;\n }\n\n async stat(ref: StoredRef): Promise<StoredObject | null> {\n const message = await this.getMessage(ref.messageId);\n if (!message?.media) return null;\n\n const document = message.document;\n if (!(document instanceof Api.Document)) return null;\n\n let fileName: string | undefined;\n for (const attr of document.attributes) {\n if (attr instanceof Api.DocumentAttributeFilename) {\n fileName = attr.fileName;\n break;\n }\n }\n return {\n messageId: ref.messageId,\n name: fileName ?? \"file\",\n size: Number(document.size.toString()),\n mimeType: document.mimeType,\n };\n }\n\n // ── Health ───────────────────────────────────────────────────────────────\n\n /**\n * Validates that the client is connected and the storage channel is\n * reachable (a real Telegram round-trip on the channel peer).\n */\n async healthCheck(): Promise<StorageHealth> {\n const startedAt = Date.now();\n try {\n if (!this.client.connected) {\n return {\n healthy: false,\n latencyMs: null,\n message: \"Telegram client is not connected\",\n targetTitle: this.title,\n };\n }\n const result = await this.client.invoke(\n new Api.channels.GetFullChannel({ channel: this.peer }),\n );\n const title =\n result instanceof Api.messages.ChatFull && result.chats[0] instanceof Api.Channel\n ? result.chats[0].title\n : this.title;\n return {\n healthy: true,\n latencyMs: Date.now() - startedAt,\n message: null,\n targetTitle: title ?? null,\n };\n } catch (error) {\n const mapped = mapTelegramError(error, \"Telegram storage health check failed\");\n return {\n healthy: false,\n latencyMs: Date.now() - startedAt,\n message: mapped.message,\n targetTitle: this.title,\n };\n }\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted) throw new OperationCancelledError();\n if (!this.client.connected) {\n throw new TelegramConnectionError(\"Telegram client is not connected\");\n }\n }\n\n private documentSize(message: Api.Message): number | null {\n const document = message.document;\n if (document instanceof Api.Document) return Number(document.size.toString());\n return null;\n }\n\n private async requireMessage(ref: StoredRef): Promise<Api.Message> {\n const message = await this.getMessage(ref.messageId);\n if (!message?.media) {\n throw new TelegramFileNotFoundError();\n }\n return message;\n }\n\n private async getMessage(id: string): Promise<Api.Message | undefined> {\n try {\n const messages = await this.client.getMessages(this.peer, { ids: [Number(id)] });\n const first = messages[0];\n return first instanceof Api.Message ? first : undefined;\n } catch (error) {\n if (error instanceof TelegramAuthRequiredError) throw error;\n throw mapTelegramError(error, \"Telegram lookup failed\");\n }\n }\n}\n","import { randomUUID } from \"node:crypto\";\nimport { Api, TelegramClient } from \"telegram\";\nimport { CustomFile } from \"telegram/client/uploads.js\";\nimport { computeCheck } from \"telegram/Password.js\";\nimport {\n NotFoundError,\n TelegramConnectionError,\n ValidationError,\n type Repos,\n type StorageRecord,\n type UserRecord,\n} from \"@omnicloud/core\";\nimport {\n extractCreatedChannel,\n newTelegramClient,\n type TelegramClientManager,\n type TelegramCredentials,\n} from \"./client\";\nimport { isPasswordRequiredError, mapTelegramError } from \"./errors\";\nimport { CHANNEL_LOGO_PNG_BASE64 } from \"./brand/channel-logo\";\n\nexport const TELEGRAM_PROVIDER = \"telegram\";\n\n/**\n * A Telegram login in progress: a connected client waiting for the\n * confirmation code (and possibly the 2FA password) to be supplied.\n */\ninterface LoginFlow {\n phone: string;\n client: TelegramClient;\n phoneCodeHash: string;\n createdAt: number;\n}\n\nconst LOGIN_FLOW_TTL_MS = 5 * 60 * 1000;\n\n/** A QR sign-in in progress: a connected client waiting for the phone to approve the token. */\ninterface QrFlow {\n client: TelegramClient;\n createdAt: number;\n /** Set once the approval update arrives, so the next poll completes the login. */\n approved: boolean;\n /** Set when the account needs its two-factor password before sign-in completes. */\n needsPassword: boolean;\n /** Pending error from the background wait, reported on the next poll. */\n failure: string | null;\n}\n\nexport interface QrLoginToken {\n /** The `tg://login?token=…` link to render as a QR code. */\n url: string;\n /** Unix time in milliseconds when this token stops being valid. */\n expiresAt: number;\n}\n\nexport type QrStatus =\n | { status: \"waiting\"; token: QrLoginToken }\n | { status: \"approved\"; user: UserRecord }\n | { status: \"password_required\" }\n | { status: \"expired\" };\n\nconst QR_FLOW_TTL_MS = 5 * 60 * 1000;\nconst QR_APPROVAL_POLL_MS = 2_000;\n\nexport type VerifyCodeResult = { status: \"ok\"; user: UserRecord } | { status: \"password_required\" };\n\n/**\n * Orchestrates the full Telegram account connection flow:\n * phone → code (→ 2FA password) → session persisted → user upserted →\n * private storage channel created.\n */\nexport class TelegramConnectionService {\n private flows = new Map<string, LoginFlow>();\n private qrFlows = new Map<string, QrFlow>();\n\n constructor(\n private readonly repos: Repos,\n private readonly clientManager: TelegramClientManager,\n private readonly credentials: TelegramCredentials,\n ) {}\n\n /** Sends the Telegram confirmation code for the phone number. */\n async startLogin(phone: string): Promise<void> {\n this.sweepExpiredFlows();\n await this.cancelLogin(phone);\n\n const client = newTelegramClient(this.credentials);\n try {\n await client.connect();\n const { phoneCodeHash } = await client.sendCode(\n { apiId: this.credentials.apiId, apiHash: this.credentials.apiHash },\n phone,\n );\n this.flows.set(phone, { phone, client, phoneCodeHash, createdAt: Date.now() });\n } catch (error) {\n await this.disconnectQuietly(client);\n throw mapTelegramError(error, \"Could not send the Telegram confirmation code\");\n }\n }\n\n /** Verifies the confirmation code. Returns `password_required` for 2FA accounts. */\n async verifyCode(phone: string, code: string): Promise<VerifyCodeResult> {\n const flow = this.requireFlow(phone);\n try {\n await flow.client.invoke(\n new Api.auth.SignIn({\n phoneNumber: flow.phone,\n phoneCodeHash: flow.phoneCodeHash,\n phoneCode: code,\n }),\n );\n } catch (error) {\n if (isPasswordRequiredError(error)) {\n return { status: \"password_required\" };\n }\n throw mapTelegramError(error, \"Telegram sign-in failed\");\n }\n return this.finalizeLogin(flow);\n }\n\n /** Completes sign-in for accounts with two-factor authentication enabled. */\n async verifyPassword(phone: string, password: string): Promise<VerifyCodeResult> {\n const flow = this.requireFlow(phone);\n try {\n const passwordInfo = await flow.client.invoke(new Api.account.GetPassword());\n const check = await computeCheck(passwordInfo, password);\n await flow.client.invoke(new Api.auth.CheckPassword({ password: check }));\n } catch (error) {\n throw mapTelegramError(error, \"Telegram sign-in failed\");\n }\n return this.finalizeLogin(flow);\n }\n\n /**\n * Starts a QR sign-in. Returns a flow id and the first login token. The phone\n * approves the token in the Telegram app; `pollQrLogin` reports the result.\n */\n async startQrLogin(): Promise<{ flowId: string; token: QrLoginToken }> {\n this.sweepExpiredQrFlows();\n const client = newTelegramClient(this.credentials);\n try {\n await client.connect();\n const flow: QrFlow = {\n client,\n createdAt: Date.now(),\n approved: false,\n needsPassword: false,\n failure: null,\n };\n const flowId = randomUUID();\n const token = await this.exportQrToken(client);\n this.qrFlows.set(flowId, flow);\n this.watchQrApproval(flowId, flow);\n return { flowId, token };\n } catch (error) {\n await this.disconnectQuietly(client);\n throw mapTelegramError(error, \"Could not start Telegram QR sign-in\");\n }\n }\n\n /**\n * Reports where a QR sign-in stands. While waiting, returns a fresh token\n * (Telegram rotates them every few seconds). On approval, completes the login.\n */\n async pollQrLogin(flowId: string): Promise<QrStatus> {\n const flow = this.requireQrFlow(flowId);\n if (flow.failure) {\n this.qrFlows.delete(flowId);\n await this.disconnectQuietly(flow.client);\n throw new TelegramConnectionError(flow.failure);\n }\n if (flow.needsPassword) return { status: \"password_required\" };\n if (!flow.approved) {\n const token = await this.exportQrToken(flow.client).catch((error: unknown) => {\n this.qrFlows.delete(flowId);\n void this.disconnectQuietly(flow.client);\n throw mapTelegramError(error, \"Could not refresh the Telegram QR code\");\n });\n return { status: \"waiting\", token };\n }\n return this.completeQrLogin(flowId, flow);\n }\n\n /** Completes a QR sign-in for an account with two-factor authentication. */\n async submitQrPassword(\n flowId: string,\n password: string,\n ): Promise<{ status: \"approved\"; user: UserRecord }> {\n const flow = this.requireQrFlow(flowId);\n try {\n const passwordInfo = await flow.client.invoke(new Api.account.GetPassword());\n const check = await computeCheck(passwordInfo, password);\n await flow.client.invoke(new Api.auth.CheckPassword({ password: check }));\n } catch (error) {\n throw mapTelegramError(error, \"Telegram sign-in failed\");\n }\n return this.completeQrLogin(flowId, flow);\n }\n\n private async exportQrToken(client: TelegramClient): Promise<QrLoginToken> {\n const result = await client.invoke(\n new Api.auth.ExportLoginToken({\n apiId: this.credentials.apiId,\n apiHash: this.credentials.apiHash,\n exceptIds: [],\n }),\n );\n if (!(result instanceof Api.auth.LoginToken)) {\n throw new TelegramConnectionError(\"Telegram did not issue a QR login token\");\n }\n const tokenBase64 = Buffer.from(result.token).toString(\"base64url\");\n return {\n url: `tg://login?token=${tokenBase64}`,\n expiresAt: result.expires * 1000,\n };\n }\n\n /** Waits for the phone to approve the token, then marks the flow approved. */\n private watchQrApproval(flowId: string, flow: QrFlow): void {\n const timer = setInterval(() => {\n if (Date.now() - flow.createdAt > QR_FLOW_TTL_MS) {\n clearInterval(timer);\n this.qrFlows.delete(flowId);\n void this.disconnectQuietly(flow.client);\n return;\n }\n if (flow.approved || flow.needsPassword || flow.failure) {\n clearInterval(timer);\n return;\n }\n this.probeQrApproval(flow).then(\n (state) => {\n if (state === \"approved\") {\n flow.approved = true;\n clearInterval(timer);\n } else if (state === \"password\") {\n flow.needsPassword = true;\n clearInterval(timer);\n }\n },\n (error: unknown) => {\n flow.failure = mapTelegramError(error, \"Telegram QR sign-in failed\").message;\n clearInterval(timer);\n },\n );\n }, QR_APPROVAL_POLL_MS);\n timer.unref();\n }\n\n /**\n * Asks Telegram whether the token was approved. Approval is observed by\n * `ExportLoginToken` returning `LoginTokenSuccess` (or a DC migration).\n */\n private async probeQrApproval(flow: QrFlow): Promise<\"pending\" | \"approved\" | \"password\"> {\n try {\n const result = await flow.client.invoke(\n new Api.auth.ExportLoginToken({\n apiId: this.credentials.apiId,\n apiHash: this.credentials.apiHash,\n exceptIds: [],\n }),\n );\n if (result instanceof Api.auth.LoginTokenSuccess) return \"approved\";\n if (result instanceof Api.auth.LoginTokenMigrateTo) {\n await flow.client._switchDC(result.dcId);\n const migrated = await flow.client.invoke(\n new Api.auth.ImportLoginToken({ token: result.token }),\n );\n if (migrated instanceof Api.auth.LoginTokenSuccess) return \"approved\";\n }\n return \"pending\";\n } catch (error) {\n if (isPasswordRequiredError(error)) return \"password\";\n throw error;\n }\n }\n\n private async completeQrLogin(\n flowId: string,\n flow: QrFlow,\n ): Promise<{ status: \"approved\"; user: UserRecord }> {\n this.qrFlows.delete(flowId);\n const user = await this.finalizeConnectedClient(flow.client, null);\n return { status: \"approved\", user };\n }\n\n private requireQrFlow(flowId: string): QrFlow {\n const flow = this.qrFlows.get(flowId);\n if (!flow) {\n throw new NotFoundError(\"This Telegram QR sign-in has ended. Start again.\");\n }\n if (Date.now() - flow.createdAt > QR_FLOW_TTL_MS) {\n this.qrFlows.delete(flowId);\n void this.disconnectQuietly(flow.client);\n throw new ValidationError(\"The QR sign-in expired. Start again.\");\n }\n return flow;\n }\n\n private sweepExpiredQrFlows(): void {\n const now = Date.now();\n for (const [flowId, flow] of this.qrFlows) {\n if (now - flow.createdAt > QR_FLOW_TTL_MS) {\n this.qrFlows.delete(flowId);\n void this.disconnectQuietly(flow.client);\n }\n }\n }\n\n /** Aborts a pending login flow (disconnects the client). */\n async cancelLogin(phone: string): Promise<void> {\n const flow = this.flows.get(phone);\n if (flow) {\n this.flows.delete(phone);\n await this.disconnectQuietly(flow.client);\n }\n }\n\n /**\n * Ensures the user has a registered private storage channel, creating it\n * on first use. Idempotent.\n */\n async ensureStorage(userId: string): Promise<StorageRecord> {\n const existing = await this.repos.storages.findByUserAndProvider(userId, TELEGRAM_PROVIDER);\n if (existing) return existing;\n\n const client = await this.clientManager.getForUser(userId);\n return this.ensureStorageForClient(userId, client);\n }\n\n async ensureStorageForClient(userId: string, client: TelegramClient): Promise<StorageRecord> {\n const existing = await this.repos.storages.findByUserAndProvider(userId, TELEGRAM_PROVIDER);\n if (existing) return existing;\n\n let channel: Api.Channel;\n try {\n const result = await client.invoke(\n new Api.channels.CreateChannel({\n title: \"OmniCloud Storage\",\n about:\n \"Private storage channel created by OmniCloud (https://github.com/Lacrous/OmniCloud)\",\n megagroup: false,\n }),\n );\n channel = extractCreatedChannel(result);\n } catch (error) {\n throw mapTelegramError(error, \"Could not create the private storage channel\");\n }\n\n const accessHash = channel.accessHash?.toString();\n if (!accessHash) {\n throw new TelegramConnectionError(\"Telegram did not provide the channel access hash\");\n }\n\n await this.brandChannel(client, channel);\n\n return this.repos.storages.create({\n userId,\n provider: TELEGRAM_PROVIDER,\n title: channel.title ?? \"OmniCloud Storage\",\n telegramChatId: channel.id.toString(),\n telegramAccessHash: accessHash,\n });\n }\n\n /**\n * Sets the OmniCloud logo as the channel photo and moves the channel to the\n * archive folder. Both are cosmetic: a failure is reported in the result and\n * never blocks the storage record.\n */\n private async brandChannel(\n client: TelegramClient,\n channel: Api.Channel,\n ): Promise<{ logo: boolean; archived: boolean }> {\n const result = { logo: false, archived: false };\n const peer = new Api.InputChannel({\n channelId: channel.id,\n accessHash: channel.accessHash!,\n });\n\n try {\n const file = await client.uploadFile({\n file: new CustomFile(\n \"omnicloud.png\",\n Buffer.from(CHANNEL_LOGO_PNG_BASE64, \"base64\").byteLength,\n \"\",\n Buffer.from(CHANNEL_LOGO_PNG_BASE64, \"base64\"),\n ),\n workers: 1,\n });\n await client.invoke(\n new Api.channels.EditPhoto({\n channel: peer,\n photo: new Api.InputChatUploadedPhoto({ file }),\n }),\n );\n result.logo = true;\n } catch {\n /* the logo is cosmetic: the channel still works without it */\n }\n\n try {\n await client.invoke(\n new Api.folders.EditPeerFolders({\n folderPeers: [\n new Api.InputFolderPeer({\n peer: new Api.InputPeerChannel({\n channelId: channel.id,\n accessHash: channel.accessHash!,\n }),\n folderId: 1,\n }),\n ],\n }),\n );\n result.archived = true;\n } catch {\n /* archiving is cosmetic: the channel still works when not archived */\n }\n\n return result;\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private requireFlow(phone: string): LoginFlow {\n const flow = this.flows.get(phone);\n if (!flow) {\n throw new NotFoundError(\"No pending Telegram login for this phone number\");\n }\n if (Date.now() - flow.createdAt > LOGIN_FLOW_TTL_MS) {\n this.flows.delete(phone);\n void this.disconnectQuietly(flow.client);\n throw new ValidationError(\"The login session expired — please start again\");\n }\n return flow;\n }\n\n private async finalizeLogin(flow: LoginFlow): Promise<VerifyCodeResult> {\n this.flows.delete(flow.phone);\n const user = await this.finalizeConnectedClient(flow.client, flow.phone);\n return { status: \"ok\", user };\n }\n\n /**\n * Saves an authorized client's session, upserts the user and creates storage.\n * `phone` is the number the user typed; QR sign-in passes null and relies on\n * the number Telegram reports.\n */\n private async finalizeConnectedClient(\n client: TelegramClient,\n phone: string | null,\n ): Promise<UserRecord> {\n let stringSession: string;\n let user: UserRecord;\n try {\n stringSession = client.session.save() as unknown as string;\n const me = await client.getMe();\n if (!(me instanceof Api.User)) {\n throw new Error(\"Telegram did not return the authenticated user\");\n }\n user = await this.repos.users.upsertFromTelegram({\n telegramUserId: me.id.toString(),\n username: me.username ?? null,\n firstName: me.firstName ?? null,\n lastName: me.lastName ?? null,\n phone: me.phone ?? phone ?? \"\",\n });\n } catch (error) {\n await this.disconnectQuietly(client);\n throw mapTelegramError(error, \"Could not complete the Telegram connection\");\n }\n\n await this.repos.sessions.save(user.id, stringSession);\n await this.ensureStorageForClient(user.id, client);\n this.clientManager.cacheClient(user.id, client);\n\n return user;\n }\n\n private sweepExpiredFlows(): void {\n const now = Date.now();\n for (const [phone, flow] of this.flows) {\n if (now - flow.createdAt > LOGIN_FLOW_TTL_MS) {\n this.flows.delete(phone);\n void this.disconnectQuietly(flow.client);\n }\n }\n }\n\n private async disconnectQuietly(client: TelegramClient): Promise<void> {\n try {\n await client.disconnect();\n } catch {\n // best effort\n }\n }\n}\n","/** The OmniCloud channel logo, embedded so it ships with the compiled package. */\nexport const CHANNEL_LOGO_PNG_BASE64 =\n \"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\";\n"],"mappings":"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","names":["createWriteStream","rm","Readable","pipeline","date","createHash","sha256","size","stored","createHash","createHash","createHash","createHash","createHash","Transform","randomBytes","Readable","Api","Api","Readable","Api","CustomFile","Api","CustomFile"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../../shared/src/mime.ts","../../shared/src/query.ts","../../shared/src/index.ts","../../core/src/errors.ts","../../core/src/storage/engine.ts","../../core/src/storage/retry.ts","../../core/src/utils/hash.ts","../../core/src/services/query-resolver.ts","../../core/src/services/auth-session-service.ts","../../core/src/services/version-retention.ts","../../core/src/services/upload-operation.ts","../../core/src/utils/operation-log.ts","../../core/src/services/activity-service.ts","../../core/src/utils/filename.ts","../../core/src/utils/request-fingerprint.ts","../../core/src/services/file-service.ts","../../core/src/utils/folder-order.ts","../../core/src/services/folder-service.ts","../../core/src/services/search-service.ts","../../core/src/services/stats-service.ts","../../core/src/services/trash-service.ts","../../core/src/services/integrity-service.ts","../../core/src/services/recent-service.ts","../../core/src/utils/spool.ts","../../core/src/utils/secret-box.ts","../../telegram/src/client.ts","../../telegram/src/errors.ts","../../telegram/src/provider.ts","../../telegram/src/connection.ts","../../telegram/src/brand/channel-logo.ts"],"sourcesContent":["import type {\n ActivityPageDTO,\n BatchResultDTO,\n FolderMutationResultDTO,\n EmptyTrashResultDTO,\n FileDTO,\n FileVersionDTO,\n FoldersPageDTO,\n FilesPageDTO,\n FolderDTO,\n HealthDTO,\n IntegrityReportDTO,\n ListQuery,\n RecentPageDTO,\n SearchResultDTO,\n SessionInfo,\n StorageDTO,\n StorageHealthDTO,\n StorageStatsDTO,\n TelegramVerifyResponse,\n TrashPageDTO,\n UserDTO,\n} from \"@omnicloud/shared\";\n\n/**\n * Errors surfaced by the SDK. `code` mirrors the server's stable error codes\n * and `requestId` correlates with server logs.\n */\nexport class OmniCloudError extends Error {\n constructor(\n public readonly status: number,\n public readonly code: string,\n message: string,\n public readonly requestId: string | undefined = undefined,\n public readonly details: unknown = undefined,\n ) {\n super(message);\n this.name = \"OmniCloudError\";\n }\n}\n\nexport interface RetryOptions {\n /** Total attempts including the first. Default 3. */\n attempts?: number;\n /** Base backoff in ms (doubles per retry). Default 300. */\n baseDelayMs?: number;\n}\n\nexport interface OmniCloudClientOptions {\n /** Base URL of an OmniCloud server (e.g. \"https://cloud.example\"). */\n baseUrl?: string;\n /** Bearer token, when the deployment uses token auth instead of cookies. */\n token?: string;\n /** Custom fetch implementation (tests, polyfills). */\n fetchImpl?: typeof fetch;\n /** Retry policy for transient failures. */\n retry?: RetryOptions;\n /** Extra headers sent with every request. */\n headers?: Record<string, string>;\n}\n\nexport interface UploadProgress {\n loaded: number;\n total: number;\n percent: number;\n}\n\nexport interface UploadOptions {\n folderId?: string | null;\n onProgress?: (progress: UploadProgress) => void;\n /** Aborts the upload. */\n signal?: AbortSignal;\n /** Number of retry attempts for failed uploads. Default 2. */\n retry?: number;\n}\n\nexport interface UploadInput {\n /**\n * File content: a Blob/File, a Uint8Array/Buffer, a web ReadableStream, or (in\n * Node) a Readable stream. Streams are read in chunks; the caller never has to\n * build one large buffer.\n */\n data?: Blob | Uint8Array | ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>;\n /** Node only: a path to read the file from. Use instead of `data`. */\n path?: string;\n /** Filename; also used to derive the MIME type server-side. */\n name: string;\n}\n\nexport interface DownloadProgress {\n loaded: number;\n total: number | null;\n percent: number | null;\n}\n\nexport interface StreamedDownload {\n stream: ReadableStream<Uint8Array>;\n contentType: string;\n /** Declared byte length, or null when the server did not send one. */\n size: number | null;\n sha256: string | null;\n}\n\nexport interface DownloadResult {\n data: Buffer | Uint8Array;\n contentType: string;\n size: number;\n sha256: string | null;\n integrityVerified: boolean | null;\n}\n\ntype Query = Record<string, string | number | boolean | undefined | null>;\n\nconst DEFAULT_RETRY: Required<RetryOptions> = { attempts: 3, baseDelayMs: 300 };\n\ntype UploadSource = { kind: \"path\"; path: string } | { kind: \"blob\"; blob: Blob };\n\nfunction randomId(): string {\n return globalThis.crypto.randomUUID().replace(/-/g, \"\");\n}\n\n/** The header part of an RFC 7578 multipart body: fields, then the file part's header. */\nfunction multipartHead(boundary: string, fields: [string, string][], fileName: string): string {\n const parts = fields.map(\n ([name, value]) =>\n `--${boundary}\\r\\nContent-Disposition: form-data; name=\"${name}\"\\r\\n\\r\\n${value}\\r\\n`,\n );\n const safeName = fileName.replace(/[\"\\\\\\r\\n]/g, \"_\");\n parts.push(\n `--${boundary}\\r\\nContent-Disposition: form-data; name=\"file\"; filename=\"${safeName}\"\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\n`,\n );\n return parts.join(\"\");\n}\n\n/** Writes head, then the file's bytes read in chunks, then tail, as one stream. */\nfunction nodeMultipartStream(\n head: string,\n file: AsyncIterable<Uint8Array | string>,\n tail: string,\n): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder();\n const iterator = file[Symbol.asyncIterator]();\n let sentHead = false;\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n if (!sentHead) {\n sentHead = true;\n controller.enqueue(encoder.encode(head));\n return;\n }\n const { done, value } = await iterator.next();\n if (done) {\n controller.enqueue(encoder.encode(tail));\n controller.close();\n return;\n }\n controller.enqueue(typeof value === \"string\" ? encoder.encode(value) : new Uint8Array(value));\n },\n async cancel(reason) {\n await iterator.return?.(reason);\n },\n });\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n reject(new OmniCloudError(0, \"ABORTED\", \"Request aborted\"));\n },\n { once: true },\n );\n });\n}\n\n/** HTTP methods that may be repeated without changing the outcome. */\nconst RETRY_SAFE_METHODS = new Set([\"GET\", \"HEAD\", \"PUT\", \"DELETE\", \"OPTIONS\"]);\n\nfunction isRetryableStatus(status: number): boolean {\n return status === 408 || status === 429 || status >= 500;\n}\n\n/** Builds a query string, skipping undefined/null/empty values. */\nfunction buildQuery(query: Query | ListQuery | undefined): string {\n const params = new URLSearchParams();\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null || value === \"\") continue;\n params.set(key, String(value));\n }\n }\n const encoded = params.toString();\n return encoded ? `?${encoded}` : \"\";\n}\n\nfunction safeJsonParse(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return null;\n }\n}\n\n/**\n * Naming: every endpoint group is a namespaced sub-client so calls read like\n *\n * cloud.files.list({ folderId })\n * cloud.trash.empty()\n * cloud.storage.stats()\n *\n * Works in browsers (cookie sessions, XHR upload progress) and Node\n * (fetch, Buffer downloads).\n */\nexport class OmniCloudClient {\n readonly files: FilesApi;\n readonly folders: FoldersApi;\n readonly trash: TrashApi;\n readonly storage: StorageApi;\n readonly search: SearchApi;\n readonly recent: RecentApi;\n readonly activity: ActivityApi;\n readonly auth: AuthApi;\n\n /** @internal */\n readonly baseUrl: string;\n /** @internal */\n readonly fetchImpl: typeof fetch;\n /** @internal */\n readonly retry: Required<RetryOptions>;\n /** @internal */\n readonly defaultHeaders: Record<string, string>;\n\n constructor(options: OmniCloudClientOptions = {}) {\n this.baseUrl = (options.baseUrl ?? \"\").replace(/\\/$/, \"\");\n this.fetchImpl = options.fetchImpl ?? fetch.bind(globalThis);\n this.retry = { ...DEFAULT_RETRY, ...options.retry };\n this.defaultHeaders = {\n ...(options.token ? { Authorization: `Bearer ${options.token}` } : {}),\n ...options.headers,\n };\n\n this.files = new FilesApi(this);\n this.folders = new FoldersApi(this);\n this.trash = new TrashApi(this);\n this.storage = new StorageApi(this);\n this.search = new SearchApi(this);\n this.recent = new RecentApi(this);\n this.activity = new ActivityApi(this);\n this.auth = new AuthApi(this);\n }\n\n /** Low-level request helper (retries transient failures). */\n async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n // Only methods that are safe to repeat are retried automatically. A POST or PATCH\n // may already have changed state when the server fails, so it is not repeated\n // unless the caller opts in with an idempotency key.\n if (!RETRY_SAFE_METHODS.has(method.toUpperCase())) {\n return this.rawRequest<T>(method, path, body);\n }\n return this.withRetry(async () => this.rawRequest<T>(method, path, body));\n }\n\n /** @internal */\n async rawRequest<T>(method: string, path: string, body?: unknown): Promise<T> {\n const isForm = typeof FormData !== \"undefined\" && body instanceof FormData;\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n method,\n headers: {\n ...this.defaultHeaders,\n ...(body !== undefined && !isForm ? { \"Content-Type\": \"application/json\" } : {}),\n },\n body: body === undefined ? undefined : isForm ? (body as FormData) : JSON.stringify(body),\n credentials: \"include\",\n });\n\n if (response.status === 204) return undefined as T;\n\n const text = await response.text();\n const data: unknown = text ? safeJsonParse(text) : null;\n\n if (!response.ok) {\n const error = (\n data as {\n error?: { code: string; message: string; requestId?: string; details?: unknown };\n } | null\n )?.error;\n throw new OmniCloudError(\n response.status,\n error?.code ?? \"INTERNAL_ERROR\",\n error?.message ?? `Request failed (${response.status})`,\n error?.requestId,\n error?.details,\n );\n }\n return data as T;\n }\n\n /** @internal */\n async withRetry<T>(operation: () => Promise<T>): Promise<T> {\n const { attempts, baseDelayMs } = this.retry;\n let lastError: unknown;\n for (let attempt = 1; attempt <= Math.max(1, attempts); attempt += 1) {\n try {\n return await operation();\n } catch (error) {\n lastError = error;\n const retryable = error instanceof OmniCloudError && isRetryableStatus(error.status);\n if (!retryable || attempt >= attempts) break;\n await sleep(baseDelayMs * 2 ** (attempt - 1));\n }\n }\n throw lastError;\n }\n\n /** @internal */\n /**\n * Opens a binary response as a stream. Bytes are delivered as they arrive, so\n * memory does not grow with the file. The stream errors with DOWNLOAD_INCOMPLETE\n * if the body ends before Content-Length.\n */\n async requestStream(\n path: string,\n onProgress?: (p: DownloadProgress) => void,\n ): Promise<StreamedDownload> {\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n headers: this.defaultHeaders,\n credentials: \"include\",\n });\n\n if (!response.ok) {\n const text = await response.text();\n const error = (safeJsonParse(text) as { error?: { code: string; message: string } } | null)\n ?.error;\n throw new OmniCloudError(\n response.status,\n error?.code ?? \"DOWNLOAD_FAILED\",\n error?.message ?? `Download failed (${response.status})`,\n );\n }\n\n const total = Number(response.headers.get(\"content-length\") ?? 0) || null;\n const contentType = response.headers.get(\"content-type\") ?? \"application/octet-stream\";\n const sha256 = response.headers.get(\"x-content-sha256\");\n const source = response.body;\n if (!source) {\n throw new OmniCloudError(0, \"DOWNLOAD_FAILED\", \"The response has no body to stream\");\n }\n\n let loaded = 0;\n const reader = source.getReader();\n const stream = new ReadableStream<Uint8Array>({\n async pull(controller) {\n const { done, value } = await reader.read();\n if (done) {\n if (total !== null && loaded !== total) {\n controller.error(\n new OmniCloudError(\n 0,\n \"DOWNLOAD_INCOMPLETE\",\n `Download ended after ${loaded} of ${total} bytes`,\n ),\n );\n return;\n }\n controller.close();\n return;\n }\n loaded += value.byteLength;\n onProgress?.({\n loaded,\n total,\n percent: total ? Math.round((loaded / total) * 100) : null,\n });\n controller.enqueue(value);\n },\n cancel(reason) {\n return reader.cancel(reason);\n },\n });\n\n return { stream, contentType, size: total, sha256 };\n }\n\n async requestBinary(\n path: string,\n onProgress?: (p: DownloadProgress) => void,\n ): Promise<DownloadResult> {\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n headers: this.defaultHeaders,\n credentials: \"include\",\n });\n\n if (!response.ok) {\n const text = await response.text();\n const error = (safeJsonParse(text) as { error?: { code: string; message: string } } | null)\n ?.error;\n throw new OmniCloudError(\n response.status,\n error?.code ?? \"DOWNLOAD_FAILED\",\n error?.message ?? `Download failed (${response.status})`,\n );\n }\n\n const total = Number(response.headers.get(\"content-length\") ?? 0) || null;\n const contentType = response.headers.get(\"content-type\") ?? \"application/octet-stream\";\n const sha256 = response.headers.get(\"x-content-sha256\");\n const integrityHeader = response.headers.get(\"x-integrity-verified\");\n const integrityVerified = integrityHeader === null ? null : integrityHeader === \"true\";\n\n const data = await readBody(response, total, onProgress);\n return { data, contentType, size: data.byteLength, sha256, integrityVerified };\n }\n}\n\n/**\n * Turns any supported upload source into a Blob. Streams are read chunk by chunk\n * into parts, so the source is never copied through an intermediate buffer.\n */\nasync function toBlob(input: UploadInput): Promise<Blob> {\n if (input.path !== undefined) {\n const { readFile } = await import(\"node:fs/promises\");\n return new Blob([new Uint8Array(await readFile(input.path))]);\n }\n const data = input.data;\n if (data === undefined) throw new OmniCloudError(0, \"INVALID_REQUEST\", \"No upload content given\");\n if (typeof Blob !== \"undefined\" && data instanceof Blob) return data;\n if (data instanceof Uint8Array) return new Blob([new Uint8Array(data)]);\n\n const parts: Uint8Array[] = [];\n if (typeof (data as ReadableStream<Uint8Array>).getReader === \"function\") {\n const reader = (data as ReadableStream<Uint8Array>).getReader();\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n parts.push(value);\n }\n } else {\n for await (const chunk of data as AsyncIterable<Uint8Array>) parts.push(chunk);\n }\n return new Blob(parts as BlobPart[]);\n}\n\n/** Reads a response body, reporting progress when the platform exposes it. */\nasync function readBody(\n response: Response,\n total: number | null,\n onProgress?: (p: DownloadProgress) => void,\n): Promise<Buffer | Uint8Array> {\n const body = response.body;\n if (!body) {\n const buffer = new Uint8Array(await response.arrayBuffer());\n onProgress?.({ loaded: buffer.byteLength, total: buffer.byteLength, percent: 100 });\n return buffer;\n }\n\n const chunks: Uint8Array[] = [];\n let loaded = 0;\n const reader = body.getReader();\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n chunks.push(value);\n loaded += value.byteLength;\n onProgress?.({\n loaded,\n total,\n percent: total ? Math.round((loaded / total) * 100) : null,\n });\n }\n }\n\n if (total !== null && loaded !== total) {\n throw new OmniCloudError(\n 0,\n \"DOWNLOAD_INCOMPLETE\",\n `Download ended after ${loaded} of ${total} bytes`,\n );\n }\n\n const merged = new Uint8Array(loaded);\n let offset = 0;\n for (const chunk of chunks) {\n merged.set(chunk, offset);\n offset += chunk.byteLength;\n }\n // Prefer Buffer in Node so callers can use Buffer APIs.\n const BufferCtor = (globalThis as { Buffer?: typeof Buffer }).Buffer;\n return BufferCtor ? BufferCtor.from(merged) : merged;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Namespaced APIs\n// ─────────────────────────────────────────────────────────────────────────────\n\nclass FilesApi {\n constructor(private readonly client: OmniCloudClient) {}\n\n /** `folderId: null` = root; omit it to list every active file. */\n async list(query: ListQuery = {}): Promise<FilesPageDTO> {\n return this.client.request<FilesPageDTO>(\"GET\", `/api/files${buildQuery(query)}`);\n }\n\n async get(id: string): Promise<FileDTO> {\n const { file } = await this.client.request<{ file: FileDTO }>(\"GET\", `/api/files/${id}`);\n return file;\n }\n\n async versions(id: string): Promise<FileVersionDTO[]> {\n const { versions } = await this.client.request<{ versions: FileVersionDTO[] }>(\n \"GET\",\n `/api/files/${id}/versions`,\n );\n return versions;\n }\n\n /**\n * Uploads a file with progress and retry support. Uses XMLHttpRequest when\n * available (browsers) so progress and cancellation work; falls back to\n * fetch elsewhere.\n */\n async upload(input: UploadInput, options: UploadOptions = {}): Promise<FileDTO> {\n const attempts = Math.max(1, (options.retry ?? 2) + 1);\n let lastError: unknown;\n // A file path is re-read on every attempt, so its body streams from disk. A\n // stream can be read only once, so it is converted once and every attempt sends\n // those same bytes.\n const source: UploadSource =\n input.path !== undefined && this.canStreamFromDisk()\n ? { kind: \"path\", path: input.path }\n : { kind: \"blob\", blob: await toBlob(input) };\n\n for (let attempt = 1; attempt <= attempts; attempt += 1) {\n try {\n if (source.kind === \"path\") {\n return await this.uploadPathOnce(source.path, input.name, options);\n }\n return await this.uploadOnce(source.blob, input.name, options);\n } catch (error) {\n lastError = error;\n if (options.signal?.aborted) throw error;\n if (error instanceof OmniCloudError && !isRetryableStatus(error.status)) throw error;\n if (attempt < attempts) await sleep(300 * 2 ** (attempt - 1), options.signal);\n }\n }\n throw lastError;\n }\n\n private canStreamFromDisk(): boolean {\n return typeof XMLHttpRequest !== \"function\" && typeof ReadableStream === \"function\";\n }\n\n /**\n * Node: streams a file from disk as a multipart body. The file is re-opened on\n * every attempt, so a retry sends the same bytes without holding them in memory.\n */\n private async uploadPathOnce(\n path: string,\n name: string,\n options: UploadOptions,\n ): Promise<FileDTO> {\n const { createReadStream, statSync } = await import(\"node:fs\");\n const size = statSync(path).size;\n const boundary = `omnicloud-${randomId()}`;\n const fields: [string, string][] = [];\n if (options.folderId) fields.push([\"folderId\", options.folderId]);\n const head = multipartHead(boundary, fields, name);\n const tail = `\\r\\n--${boundary}--\\r\\n`;\n\n const body = nodeMultipartStream(head, createReadStream(path), tail);\n const response = await this.client.fetchImpl(`${this.client.baseUrl}/api/files`, {\n method: \"POST\",\n headers: {\n ...this.client.defaultHeaders,\n \"Content-Type\": `multipart/form-data; boundary=${boundary}`,\n \"Content-Length\": String(Buffer.byteLength(head) + size + Buffer.byteLength(tail)),\n },\n body,\n credentials: \"include\",\n duplex: \"half\",\n } as RequestInit);\n\n const text = await response.text();\n if (!response.ok) {\n const error = (safeJsonParse(text) as { error?: { code: string; message: string } } | null)\n ?.error;\n throw new OmniCloudError(\n response.status,\n error?.code ?? \"UPLOAD_FAILED\",\n error?.message ?? `Upload failed (${response.status})`,\n );\n }\n return (JSON.parse(text) as { file: FileDTO }).file;\n }\n\n private async uploadOnce(blob: Blob, name: string, options: UploadOptions): Promise<FileDTO> {\n const form = new FormData();\n form.append(\"file\", blob, name);\n if (options.folderId) form.append(\"folderId\", options.folderId);\n\n if (typeof XMLHttpRequest === \"function\") {\n return this.uploadViaXhr(form, options);\n }\n return this.client\n .request<{ file: FileDTO }>(\"POST\", \"/api/files\", form)\n .then((response) => response.file);\n }\n\n private uploadViaXhr(form: FormData, options: UploadOptions): Promise<FileDTO> {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open(\"POST\", `${this.client.baseUrl}/api/files`);\n xhr.responseType = \"json\";\n xhr.withCredentials = true;\n\n const onAbort = () => xhr.abort();\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n xhr.upload.onprogress = (event) => {\n options.onProgress?.({\n loaded: event.loaded,\n total: event.total,\n percent: event.total > 0 ? Math.round((event.loaded / event.total) * 100) : 0,\n });\n };\n xhr.onload = () => {\n options.signal?.removeEventListener(\"abort\", onAbort);\n const body = xhr.response as {\n file?: FileDTO;\n error?: { code: string; message: string; requestId?: string };\n } | null;\n if (xhr.status >= 200 && xhr.status < 300 && body?.file) {\n options.onProgress?.({ loaded: 1, total: 1, percent: 100 });\n resolve(body.file);\n return;\n }\n reject(\n new OmniCloudError(\n xhr.status,\n body?.error?.code ?? \"UPLOAD_FAILED\",\n body?.error?.message ?? `Upload failed (${xhr.status})`,\n body?.error?.requestId,\n ),\n );\n };\n xhr.onerror = () =>\n reject(new OmniCloudError(0, \"NETWORK_ERROR\", \"Network error during upload\"));\n xhr.onabort = () => reject(new OmniCloudError(0, \"ABORTED\", \"Upload aborted\"));\n xhr.send(form);\n });\n }\n\n /** Uploads a new version of an existing file. */\n async replace(id: string, input: UploadInput): Promise<FileDTO> {\n const form = new FormData();\n const blob = await toBlob(input);\n form.append(\"file\", blob, input.name);\n const { file } = await this.client.request<{ file: FileDTO }>(\n \"POST\",\n `/api/files/${id}/replace`,\n form,\n );\n return file;\n }\n\n /**\n * Buffers the entire file in memory. Use `downloadToFile` or `downloadStream` for\n * large files.\n *\n * @deprecated Loads the whole file into memory. Use `downloadToFile` (Node) or\n * `downloadStream` (any runtime) for large files.\n */\n async download(id: string, onProgress?: (p: DownloadProgress) => void): Promise<DownloadResult> {\n return this.client.requestBinary(`/api/files/${id}/download`, onProgress);\n }\n\n /**\n * Streams a file's bytes without holding the whole file in memory. Use this for\n * large files. Read `stream` to the end: a truncated transfer errors instead of\n * ending quietly.\n */\n async downloadStream(\n id: string,\n onProgress?: (p: DownloadProgress) => void,\n ): Promise<StreamedDownload> {\n return this.client.requestStream(`/api/files/${id}/download`, onProgress);\n }\n\n /**\n * Node only. Streams a file to `targetPath` through a `.part` file that is\n * renamed into place only after the full length is verified. A truncated or\n * failed transfer leaves nothing at `targetPath`.\n */\n async downloadToFile(\n id: string,\n targetPath: string,\n onProgress?: (p: DownloadProgress) => void,\n ): Promise<{ path: string; bytes: number; sha256: string | null }> {\n const { createWriteStream } = await import(\"node:fs\");\n const { rename, rm } = await import(\"node:fs/promises\");\n const { Readable } = await import(\"node:stream\");\n const { pipeline } = await import(\"node:stream/promises\");\n\n const download = await this.downloadStream(id, onProgress);\n const partPath = `${targetPath}.part`;\n let bytes = 0;\n try {\n await pipeline(\n Readable.fromWeb(download.stream as never),\n async function* (source: AsyncIterable<Uint8Array>) {\n for await (const chunk of source) {\n bytes += chunk.byteLength;\n yield chunk;\n }\n },\n createWriteStream(partPath),\n );\n await rename(partPath, targetPath);\n } catch (error) {\n await rm(partPath, { force: true });\n throw error;\n }\n return { path: targetPath, bytes, sha256: download.sha256 };\n }\n\n /** URL suitable for direct download (anchor href). */\n downloadUrl(id: string): string {\n return `${this.client.baseUrl}/api/files/${id}/download`;\n }\n\n async rename(id: string, name: string): Promise<FileDTO> {\n const { file } = await this.client.request<{ file: FileDTO }>(\"PATCH\", `/api/files/${id}`, {\n name,\n });\n return file;\n }\n\n async star(id: string, starred = true): Promise<FileDTO> {\n const { file } = await this.client.request<{ file: FileDTO }>(\"PATCH\", `/api/files/${id}`, {\n starred,\n });\n return file;\n }\n\n async move(id: string, folderId: string | null): Promise<FileDTO> {\n const { file } = await this.client.request<{ file: FileDTO }>(\"POST\", `/api/files/${id}/move`, {\n folderId,\n });\n return file;\n }\n\n async trash(id: string): Promise<FileDTO> {\n const { file } = await this.client.request<{ file: FileDTO }>(\"POST\", `/api/files/${id}/trash`);\n return file;\n }\n\n async restore(id: string): Promise<FileDTO> {\n const { file } = await this.client.request<{ file: FileDTO }>(\n \"POST\",\n `/api/files/${id}/restore`,\n );\n return file;\n }\n\n /** Permanently deletes a file and its Telegram objects. */\n async delete(id: string): Promise<void> {\n await this.client.request<void>(\"DELETE\", `/api/files/${id}`);\n }\n\n /** Batch operation: trash | restore | delete | star | unstar | move. */\n async batch(\n operation: \"trash\" | \"restore\" | \"delete\" | \"star\" | \"unstar\" | \"move\",\n ids: string[],\n options: { folderId?: string | null } = {},\n ): Promise<BatchResultDTO> {\n return this.client.request<BatchResultDTO>(\"POST\", \"/api/files/batch\", {\n operation,\n ids,\n ...(operation === \"move\" ? { folderId: options.folderId ?? null } : {}),\n });\n }\n}\n\nclass FoldersApi {\n constructor(private readonly client: OmniCloudClient) {}\n\n async list(query: ListQuery & { parentId?: string | null } = {}): Promise<FoldersPageDTO> {\n const { parentId, ...rest } = query;\n // Keep the paging and sort fields when filtering by parent, so page 2 is not\n // silently answered with page 1.\n const base = buildQuery(rest);\n if (parentId === undefined)\n return this.client.request<FoldersPageDTO>(\"GET\", `/api/folders${base}`);\n // The server treats an empty parentId as the root, so it must be sent explicitly.\n const separator = base === \"\" ? \"?\" : \"&\";\n const path = `/api/folders${base}${separator}parentId=${encodeURIComponent(parentId ?? \"\")}`;\n return this.client.request<FoldersPageDTO>(\"GET\", path);\n }\n\n /** The full active folder tree (for move dialogs). */\n async tree(): Promise<FolderDTO[]> {\n const { folders } = await this.client.request<{ folders: FolderDTO[] }>(\n \"GET\",\n \"/api/folders/tree\",\n );\n return folders;\n }\n\n async create(name: string, parentId: string | null = null): Promise<FolderDTO> {\n const { folder } = await this.client.request<{ folder: FolderDTO }>(\"POST\", \"/api/folders\", {\n name,\n parentId,\n });\n return folder;\n }\n\n async rename(id: string, name: string): Promise<FolderDTO> {\n const { folder } = await this.client.request<{ folder: FolderDTO }>(\n \"PATCH\",\n `/api/folders/${id}`,\n { name },\n );\n return folder;\n }\n\n async star(id: string, starred = true): Promise<FolderDTO> {\n const { folder } = await this.client.request<{ folder: FolderDTO }>(\n \"PATCH\",\n `/api/folders/${id}`,\n { starred },\n );\n return folder;\n }\n\n async move(id: string, parentId: string | null): Promise<FolderDTO> {\n const { folder } = await this.client.request<{ folder: FolderDTO }>(\n \"POST\",\n `/api/folders/${id}/move`,\n { parentId },\n );\n return folder;\n }\n\n async trash(id: string): Promise<FolderMutationResultDTO> {\n return this.client.request<FolderMutationResultDTO>(\"POST\", `/api/folders/${id}/trash`);\n }\n\n async restore(id: string): Promise<FolderMutationResultDTO> {\n return this.client.request<FolderMutationResultDTO>(\"POST\", `/api/folders/${id}/restore`);\n }\n\n /** Permanently deletes the folder subtree and its Telegram objects. */\n async delete(id: string): Promise<FolderMutationResultDTO> {\n return this.client.request<FolderMutationResultDTO>(\"DELETE\", `/api/folders/${id}`);\n }\n\n async batch(\n operation: \"trash\" | \"restore\" | \"delete\" | \"star\" | \"unstar\" | \"move\",\n ids: string[],\n options: { parentId?: string | null } = {},\n ): Promise<BatchResultDTO> {\n return this.client.request<BatchResultDTO>(\"POST\", \"/api/folders/batch\", {\n operation,\n ids,\n ...(operation === \"move\" ? { parentId: options.parentId ?? null } : {}),\n });\n }\n}\n\nclass TrashApi {\n constructor(private readonly client: OmniCloudClient) {}\n\n async list(query: ListQuery = {}): Promise<TrashPageDTO> {\n return this.client.request<TrashPageDTO>(\"GET\", `/api/trash${buildQuery(query)}`);\n }\n\n /** Permanently removes everything in the Trash. */\n async empty(): Promise<EmptyTrashResultDTO> {\n return this.client.request<EmptyTrashResultDTO>(\"POST\", \"/api/trash/empty\");\n }\n}\n\nclass StorageApi {\n constructor(private readonly client: OmniCloudClient) {}\n\n /** Creates the private storage channel if it does not exist yet. */\n async ensure(): Promise<StorageDTO> {\n const { storage } = await this.client.request<{ storage: StorageDTO }>(\n \"POST\",\n \"/api/storage/ensure\",\n );\n return storage;\n }\n\n async stats(): Promise<StorageStatsDTO> {\n const { stats } = await this.client.request<{ stats: StorageStatsDTO }>(\n \"GET\",\n \"/api/storage/stats\",\n );\n return stats;\n }\n\n async health(): Promise<{ health: StorageHealthDTO; stats: StorageStatsDTO }> {\n return this.client.request(\"GET\", \"/api/storage/health\");\n }\n\n /** Probes the storage backend; `deep` performs a real Telegram round-trip. */\n async check(deep = false): Promise<StorageHealthDTO> {\n const { health } = await this.client.request<{ health: StorageHealthDTO }>(\n \"POST\",\n \"/api/storage/health/check\",\n { deep },\n );\n return health;\n }\n\n /** Read-only integrity scan; nothing is repaired or deleted. */\n async integrityCheck(options: { deep?: boolean } = {}): Promise<IntegrityReportDTO> {\n const { report } = await this.client.request<{ report: IntegrityReportDTO }>(\n \"POST\",\n \"/api/storage/integrity/check\",\n { deep: options.deep ?? false },\n );\n return report;\n }\n\n /** Public server health (no authentication required). */\n async serverHealth(): Promise<HealthDTO> {\n return this.client.request<HealthDTO>(\"GET\", \"/api/health\");\n }\n}\n\nclass SearchApi {\n constructor(private readonly client: OmniCloudClient) {}\n\n /**\n * Searches with the v0.2 query language:\n * `type:pdf`, `size:>100MB`, `folder:Projects`, `starred:true`, `after:2026-01-01`\n */\n async query(q: string, page: { page?: number; limit?: number } = {}): Promise<SearchResultDTO> {\n return this.client.request<SearchResultDTO>(\n \"GET\",\n `/api/search${buildQuery({ q, ...page } as Query)}`,\n );\n }\n}\n\nclass RecentApi {\n constructor(private readonly client: OmniCloudClient) {}\n\n async list(page: { page?: number; limit?: number } = {}): Promise<RecentPageDTO> {\n return this.client.request<RecentPageDTO>(\"GET\", `/api/recent${buildQuery(page as Query)}`);\n }\n}\n\nclass ActivityApi {\n constructor(private readonly client: OmniCloudClient) {}\n\n async list(page: { page?: number; limit?: number } = {}): Promise<ActivityPageDTO> {\n return this.client.request<ActivityPageDTO>(\"GET\", `/api/activity${buildQuery(page as Query)}`);\n }\n}\n\nclass AuthApi {\n constructor(private readonly client: OmniCloudClient) {}\n\n async me(): Promise<SessionInfo> {\n return this.client.request<SessionInfo>(\"GET\", \"/api/auth/me\");\n }\n\n async startTelegramLogin(phone: string): Promise<void> {\n await this.client.request(\"POST\", \"/api/auth/telegram/start\", { phone });\n }\n\n async verifyTelegramCode(phone: string, code: string): Promise<TelegramVerifyResponse> {\n return this.client.request<TelegramVerifyResponse>(\"POST\", \"/api/auth/telegram/verify\", {\n phone,\n code,\n });\n }\n\n async submitTelegramPassword(\n phone: string,\n password: string,\n ): Promise<{ status: \"ok\"; user: UserDTO }> {\n return this.client.request(\"POST\", \"/api/auth/telegram/password\", { phone, password });\n }\n\n async logout(): Promise<void> {\n await this.client.request(\"POST\", \"/api/auth/logout\");\n }\n}\n","/**\n * Filename-extension based MIME type detection.\n *\n * MIME types supplied by clients are never trusted: the API derives the MIME\n * type from the filename extension server-side. Shared so the web UI can use\n * the same logic for icons.\n */\n\nconst MIME_BY_EXTENSION: Record<string, string> = {\n // Documents\n pdf: \"application/pdf\",\n doc: \"application/msword\",\n docx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n xls: \"application/vnd.ms-excel\",\n xlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n ppt: \"application/vnd.ms-powerpoint\",\n pptx: \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n odt: \"application/vnd.oasis.opendocument.text\",\n ods: \"application/vnd.oasis.opendocument.spreadsheet\",\n txt: \"text/plain\",\n md: \"text/markdown\",\n csv: \"text/csv\",\n rtf: \"application/rtf\",\n html: \"text/html\",\n htm: \"text/html\",\n css: \"text/css\",\n xml: \"application/xml\",\n yaml: \"application/yaml\",\n yml: \"application/yaml\",\n json: \"application/json\",\n epub: \"application/epub+zip\",\n\n // Images\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n svg: \"image/svg+xml\",\n bmp: \"image/bmp\",\n ico: \"image/x-icon\",\n tiff: \"image/tiff\",\n tif: \"image/tiff\",\n avif: \"image/avif\",\n heic: \"image/heic\",\n\n // Audio\n mp3: \"audio/mpeg\",\n wav: \"audio/wav\",\n ogg: \"audio/ogg\",\n flac: \"audio/flac\",\n m4a: \"audio/mp4\",\n aac: \"audio/aac\",\n opus: \"audio/opus\",\n\n // Video\n mp4: \"video/mp4\",\n mkv: \"video/x-matroska\",\n webm: \"video/webm\",\n avi: \"video/x-msvideo\",\n mov: \"video/quicktime\",\n wmv: \"video/x-ms-wmv\",\n flv: \"video/x-flv\",\n\n // Archives\n zip: \"application/zip\",\n rar: \"application/vnd.rar\",\n \"7z\": \"application/x-7z-compressed\",\n tar: \"application/x-tar\",\n gz: \"application/gzip\",\n bz2: \"application/x-bzip2\",\n xz: \"application/x-xz\",\n\n // Other\n apk: \"application/vnd.android.package-archive\",\n exe: \"application/x-msdownload\",\n dmg: \"application/x-apple-diskimage\",\n iso: \"application/x-iso9660-image\",\n bin: \"application/octet-stream\",\n};\n\nexport const DEFAULT_MIME_TYPE = \"application/octet-stream\";\n\n/** Returns the file extension without the leading dot, lowercased. */\nexport function fileExtension(filename: string): string {\n const base = filename.split(/[/\\\\]/).pop() ?? \"\";\n const dot = base.lastIndexOf(\".\");\n if (dot <= 0 || dot === base.length - 1) return \"\";\n return base.slice(dot + 1).toLowerCase();\n}\n\n/** Derives a MIME type from the filename extension. Never trusts client input. */\nexport function mimeFromFilename(filename: string): string {\n return MIME_BY_EXTENSION[fileExtension(filename)] ?? DEFAULT_MIME_TYPE;\n}\n\nexport type FileCategory =\n \"image\" | \"video\" | \"audio\" | \"pdf\" | \"archive\" | \"document\" | \"text\" | \"other\";\n\nconst PREFIXED_CATEGORIES: Array<[string, FileCategory]> = [\n [\"image/\", \"image\"],\n [\"video/\", \"video\"],\n [\"audio/\", \"audio\"],\n [\"application/pdf\", \"pdf\"],\n [\"application/zip\", \"archive\"],\n [\"application/x-tar\", \"archive\"],\n [\"application/gzip\", \"archive\"],\n [\"application/x-7z-compressed\", \"archive\"],\n [\"application/vnd.rar\", \"archive\"],\n [\"application/x-bzip2\", \"archive\"],\n [\"application/x-xz\", \"archive\"],\n [\"text/\", \"text\"],\n [\"application/json\", \"text\"],\n [\"application/xml\", \"text\"],\n [\"application/yaml\", \"text\"],\n [\"application/javascript\", \"text\"],\n [\"text/markdown\", \"text\"],\n];\n\nconst OFFICE_MIMES = new Set([\n \"application/msword\",\n \"application/vnd.ms-excel\",\n \"application/vnd.ms-powerpoint\",\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n \"application/vnd.oasis.opendocument.text\",\n \"application/vnd.oasis.opendocument.spreadsheet\",\n \"application/rtf\",\n \"application/epub+zip\",\n]);\n\n/** Coarse category used for icon selection in the UI. */\nexport function fileCategory(mimeType: string): FileCategory {\n for (const [prefix, category] of PREFIXED_CATEGORIES) {\n if (mimeType.startsWith(prefix)) return category;\n }\n if (OFFICE_MIMES.has(mimeType)) return \"document\";\n return \"other\";\n}\n\nexport const FILE_CATEGORIES: readonly FileCategory[] = [\n \"image\",\n \"video\",\n \"audio\",\n \"pdf\",\n \"archive\",\n \"document\",\n \"text\",\n \"other\",\n] as const;\n\n/** MIME prefixes belonging to each category, for repository-level filtering. */\nconst CATEGORY_PREFIXES: Record<FileCategory, readonly string[]> = {\n image: [\"image/\"],\n video: [\"video/\"],\n audio: [\"audio/\"],\n pdf: [\"application/pdf\"],\n archive: [\n \"application/zip\",\n \"application/x-tar\",\n \"application/gzip\",\n \"application/x-7z-compressed\",\n \"application/vnd.rar\",\n \"application/x-bzip2\",\n \"application/x-xz\",\n ],\n text: [\n \"text/\",\n \"application/json\",\n \"application/xml\",\n \"application/yaml\",\n \"application/javascript\",\n ],\n document: [...OFFICE_MIMES],\n other: [],\n};\n\nexport function isFileCategory(value: string): value is FileCategory {\n return (FILE_CATEGORIES as readonly string[]).includes(value);\n}\n\n/**\n * Expands a `type:` filter into a set of MIME matchers.\n *\n * Accepts either a coarse category (\"image\", \"pdf\") or an exact MIME type\n * (\"application/pdf\"); the repository layer turns these into prefix/equality\n * filters. Returns null when the value matches nothing usable.\n */\nexport function mimeMatchersForType(value: string): readonly string[] | null {\n const normalized = value.toLowerCase().trim();\n if (!normalized) return null;\n if (isFileCategory(normalized)) {\n const prefixes = CATEGORY_PREFIXES[normalized];\n return prefixes.length > 0 ? prefixes : null;\n }\n // Exact MIME type — match it as an exact value.\n return [normalized];\n}\n","/**\n * Search query parsing, pagination and sorting primitives.\n *\n * Self-contained (no runtime imports from the package root) so the API, web\n * app and SDK can all share one query language without import cycles.\n *\n * Query language (v0.2):\n * report → name contains \"report\"\n * name:report → same, explicit\n * type:pdf type:image type:application/zip\n * ext:zip → file extension (no dot)\n * size:>100MB size:<1KB size:5mb\n * folder:Projects → folder-name filter\n * starred:true trashed:true\n * after:2026-01-01 before:2026-06-01\n */\n\nexport const SORT_FIELDS = [\"name\", \"size\", \"createdAt\", \"updatedAt\", \"type\"] as const;\nexport type SortField = (typeof SORT_FIELDS)[number];\n\nexport const SORT_ORDERS = [\"asc\", \"desc\"] as const;\nexport type SortOrder = (typeof SORT_ORDERS)[number];\n\nexport const ITEM_STATUSES = [\"active\", \"trashed\", \"all\"] as const;\nexport type ItemStatus = (typeof ITEM_STATUSES)[number];\n\nexport const DEFAULT_PAGE_SIZE = 50;\nexport const MAX_PAGE_SIZE = 200;\n\n/** Query parameters accepted by the file/folder listing endpoints. */\nexport interface ListQuery {\n folderId?: string | null;\n page?: number;\n limit?: number;\n sort?: SortField;\n order?: SortOrder;\n /** MIME type or coarse category (image, video, pdf, document, …). */\n type?: string;\n /** File extension without the dot. */\n ext?: string;\n starred?: boolean;\n status?: ItemStatus;\n /** Name substring filter. */\n q?: string;\n /** Folder-name filter (search parser); resolved to ids by the caller. */\n folderName?: string;\n minSize?: number;\n maxSize?: number;\n /** Inclusive date bounds (ISO strings). */\n from?: string;\n to?: string;\n}\n\nconst SIZE_UNITS: Record<string, number> = {\n b: 1,\n kb: 1024,\n mb: 1024 ** 2,\n gb: 1024 ** 3,\n tb: 1024 ** 4,\n};\n\n/** Parses \"100MB\", \"1.5gb\", \"512\" into bytes. Returns null when unparseable. */\nexport function parseSize(value: string): number | null {\n const match = /^\\s*([0-9]+(?:\\.[0-9]+)?)\\s*([a-zA-Z]*)\\s*$/.exec(value);\n if (!match) return null;\n const amount = Number(match[1]);\n if (!Number.isFinite(amount) || amount < 0) return null;\n const unit = (match[2] ?? \"\").toLowerCase() || \"b\";\n const factor = SIZE_UNITS[unit];\n if (factor === undefined) return null;\n return Math.round(amount * factor);\n}\n\n/** Parses a size comparison such as \">100MB\", \"<=2gb\", \"5mb\". */\nexport function parseSizeComparison(value: string): { min?: number; max?: number } | null {\n const match = /^\\s*(>=|<=|>|<|=)?\\s*(.+)$/.exec(value);\n if (!match) return null;\n const operator = match[1] ?? \"=\";\n const bytes = parseSize(match[2]!);\n if (bytes === null) return null;\n\n switch (operator) {\n case \">\":\n return { min: bytes + 1 };\n case \">=\":\n return { min: bytes };\n case \"<\":\n return { max: bytes - 1 };\n case \"<=\":\n return { max: bytes };\n default:\n return { min: bytes, max: bytes };\n }\n}\n\nfunction parseBoolean(value: string): boolean | null {\n const normalized = value.toLowerCase();\n if ([\"true\", \"1\", \"yes\", \"y\", \"on\"].includes(normalized)) return true;\n if ([\"false\", \"0\", \"no\", \"n\", \"off\"].includes(normalized)) return false;\n return null;\n}\n\nfunction parseDate(value: string, endOfDay: boolean): Date | null {\n const trimmed = value.trim();\n // Plain dates are interpreted as the start (or end) of that UTC day so\n // `after:2026-01-01` and `before:2026-01-01` behave intuitively.\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(trimmed)) {\n const date = new Date(`${trimmed}T${endOfDay ? \"23:59:59.999\" : \"00:00:00.000\"}Z`);\n return Number.isNaN(date.getTime()) ? null : date;\n }\n const date = new Date(trimmed);\n return Number.isNaN(date.getTime()) ? null : date;\n}\n\n/** Parses a raw user query string into structured filters. */\nexport function parseSearchQuery(\n input: string,\n base: ListQuery = {},\n): { query: ListQuery; freeText: string } {\n const query: ListQuery = { ...base };\n const terms: string[] = [];\n\n for (const rawToken of input.trim().split(/\\s+/)) {\n if (!rawToken) continue;\n\n const separator = rawToken.indexOf(\":\");\n if (separator <= 0) {\n terms.push(rawToken);\n continue;\n }\n\n const field = rawToken.slice(0, separator).toLowerCase();\n const value = rawToken.slice(separator + 1);\n if (!value) {\n terms.push(rawToken);\n continue;\n }\n\n switch (field) {\n case \"name\":\n case \"filename\":\n terms.push(value);\n break;\n case \"type\":\n // Accept either a MIME type (\"application/pdf\") or a coarse category.\n query.type = value.toLowerCase();\n break;\n case \"ext\":\n case \"extension\":\n query.ext = value.replace(/^\\./, \"\").toLowerCase();\n break;\n case \"size\": {\n const comparison = parseSizeComparison(value);\n if (!comparison) {\n terms.push(rawToken);\n break;\n }\n if (comparison.min !== undefined) query.minSize = comparison.min;\n if (comparison.max !== undefined) query.maxSize = comparison.max;\n break;\n }\n case \"folder\":\n query.folderName = value;\n break;\n case \"starred\": {\n const parsed = parseBoolean(value);\n if (parsed === null) terms.push(rawToken);\n else query.starred = parsed;\n break;\n }\n case \"trashed\":\n case \"deleted\": {\n const parsed = parseBoolean(value);\n if (parsed === null) terms.push(rawToken);\n else query.status = parsed ? \"trashed\" : \"active\";\n break;\n }\n case \"after\":\n case \"since\": {\n const date = parseDate(value, false);\n if (!date) terms.push(rawToken);\n else query.from = date.toISOString();\n break;\n }\n case \"before\": {\n const date = parseDate(value, true);\n if (!date) terms.push(rawToken);\n else query.to = date.toISOString();\n break;\n }\n default:\n terms.push(rawToken);\n break;\n }\n }\n\n const freeText = terms.join(\" \").trim();\n if (freeText) query.q = freeText;\n return { query, freeText };\n}\n\nexport function normalizePage(value: unknown): number {\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed < 1) return 1;\n return parsed;\n}\n\nexport function normalizeLimit(value: unknown): number {\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed < 1) return DEFAULT_PAGE_SIZE;\n return Math.min(parsed, MAX_PAGE_SIZE);\n}\n\nexport function normalizeSort(value: unknown): SortField | undefined {\n if (typeof value !== \"string\") return undefined;\n return (SORT_FIELDS as readonly string[]).includes(value) ? (value as SortField) : undefined;\n}\n\nexport function normalizeOrder(value: unknown): SortOrder | undefined {\n if (typeof value !== \"string\") return undefined;\n return (SORT_ORDERS as readonly string[]).includes(value) ? (value as SortOrder) : undefined;\n}\n\n/** Builds the pagination envelope for a result page. */\nexport function paginationMeta(page: number, limit: number, total: number) {\n return { page, limit, total, hasMore: page * limit < total };\n}\n","/**\n * Data transfer objects, constants and error codes shared between the API,\n * the web app, and the @lacrous/omnicloud SDK.\n *\n * Telegram identifiers that may exceed JavaScript's safe integer range are\n * transported as strings across the API boundary.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Identity & storage\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface UserDTO {\n id: string;\n username: string | null;\n firstName: string | null;\n lastName: string | null;\n}\n\nexport interface StorageDTO {\n id: string;\n provider: string;\n title: string;\n createdAt: string;\n}\n\n/** Connection state of the underlying storage backend. */\nexport type ConnectionState =\n \"DISCONNECTED\" | \"CONNECTING\" | \"CONNECTED\" | \"RECONNECTING\" | \"ERROR\" | \"AUTH_REQUIRED\";\n\nexport type HealthStatus = \"healthy\" | \"degraded\" | \"unavailable\" | \"unknown\";\n\nexport interface StorageHealthDTO {\n provider: string;\n state: ConnectionState;\n status: HealthStatus;\n latencyMs: number | null;\n message: string | null;\n channelTitle: string | null;\n checkedAt: string;\n}\n\n/** Returned by GET /api/auth/me. `user` is null when not signed in. */\nexport interface SessionInfo {\n user: UserDTO | null;\n storage: StorageDTO | null;\n health: StorageHealthDTO | null;\n}\n\n/** POST /api/auth/telegram/verify response. */\nexport type TelegramVerifyResponse =\n { status: \"ok\"; user: UserDTO } | { status: \"password_required\" };\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Files & folders\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface FileDTO {\n id: string;\n name: string;\n size: number;\n mimeType: string;\n sha256: string;\n folderId: string | null;\n starred: boolean;\n trashed: boolean;\n deletedAt: string | null;\n versionCount: number;\n currentVersionId: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface FolderDTO {\n id: string;\n name: string;\n parentId: string | null;\n starred: boolean;\n trashed: boolean;\n deletedAt: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface FileVersionDTO {\n id: string;\n fileId: string;\n versionNumber: number;\n size: number;\n mimeType: string;\n sha256: string;\n telegramMessageId: number;\n isCurrent: boolean;\n createdAt: string;\n}\n\nexport interface SearchResultDTO {\n files: FileDTO[];\n folders: FolderDTO[];\n pagination: PaginationDTO;\n}\n\n/** Result of a recursive folder deletion / trash operation. */\nexport interface FolderMutationResultDTO {\n affectedFolders: number;\n affectedFiles: number;\n}\n\nexport interface EmptyTrashResultDTO {\n deletedFiles: number;\n deletedFolders: number;\n failedFiles: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Pagination, sorting & filtering\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { ListQuery } from \"./query\";\n\nexport type { ListQuery, SortField, SortOrder, ItemStatus } from \"./query\";\n\nexport interface PaginationDTO {\n page: number;\n limit: number;\n total: number;\n hasMore: boolean;\n}\n\nexport interface FilesPageDTO {\n files: FileDTO[];\n pagination: PaginationDTO;\n}\n\nexport interface FoldersPageDTO {\n folders: FolderDTO[];\n pagination: PaginationDTO;\n}\n\nexport interface TrashPageDTO {\n files: FileDTO[];\n folders: FolderDTO[];\n pagination: PaginationDTO;\n}\n\n/** Alias so callers can name the query shape as query parameters. */\nexport type QueryParams = ListQuery;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Recent & activity\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface RecentItemDTO {\n file: FileDTO;\n lastAction: ActivityAction;\n lastActionAt: string;\n}\n\nexport interface RecentPageDTO {\n items: RecentItemDTO[];\n pagination: PaginationDTO;\n}\n\nexport const ACTIVITY_ACTIONS = [\n \"upload\",\n \"download\",\n \"open\",\n \"rename\",\n \"move\",\n \"star\",\n \"unstar\",\n \"trash\",\n \"restore\",\n \"delete\",\n \"create_folder\",\n \"replace\",\n] as const;\nexport type ActivityAction = (typeof ACTIVITY_ACTIONS)[number];\n\nexport const RESOURCE_TYPES = [\"file\", \"folder\", \"storage\"] as const;\nexport type ResourceType = (typeof RESOURCE_TYPES)[number];\n\nexport interface ActivityEventDTO {\n id: string;\n action: ActivityAction;\n resourceType: ResourceType;\n resourceId: string;\n resourceName: string | null;\n metadata: Record<string, unknown> | null;\n createdAt: string;\n}\n\nexport interface ActivityPageDTO {\n events: ActivityEventDTO[];\n pagination: PaginationDTO;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Storage statistics\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface StorageTypeBreakdownDTO {\n category: string;\n bytes: number;\n count: number;\n}\n\nexport interface StorageStatsDTO {\n fileCount: number;\n folderCount: number;\n totalBytes: number;\n trashBytes: number;\n trashFileCount: number;\n starredCount: number;\n byType: StorageTypeBreakdownDTO[];\n largestFiles: FileDTO[];\n quotaBytes: number | null;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Health & integrity\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface HealthDTO {\n status: HealthStatus;\n database: HealthStatus;\n storage: HealthStatus;\n uptimeSeconds: number;\n version: string;\n}\n\nexport interface IntegrityIssueDTO {\n fileId: string;\n /** Set when the issue is with a historical version rather than the current object. */\n versionId?: string;\n name: string;\n kind: \"missing\" | \"size_mismatch\" | \"unreadable\" | \"hash_mismatch\";\n detail: string | null;\n}\n\nexport interface IntegrityReportDTO {\n checkedAt: string;\n filesChecked: number;\n healthy: number;\n missing: number;\n inconsistent: number;\n unreadable: number;\n issues: IntegrityIssueDTO[];\n durationMs: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Batch operations\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const BATCH_FILE_OPERATIONS = [\n \"trash\",\n \"restore\",\n \"delete\",\n \"star\",\n \"unstar\",\n \"move\",\n] as const;\nexport type BatchFileOperation = (typeof BATCH_FILE_OPERATIONS)[number];\n\nexport const BATCH_FOLDER_OPERATIONS = [\n \"trash\",\n \"restore\",\n \"delete\",\n \"star\",\n \"unstar\",\n \"move\",\n] as const;\nexport type BatchFolderOperation = (typeof BATCH_FOLDER_OPERATIONS)[number];\n\nexport interface BatchResultDTO {\n requested: number;\n succeeded: number;\n failed: number;\n errors: { id: string; code: ErrorCode; message: string }[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Errors\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Stable machine-readable error codes returned in API error bodies. */\nexport const ERROR_CODES = {\n AUTH_REQUIRED: \"AUTH_REQUIRED\",\n AUTH_INVALID: \"AUTH_INVALID\",\n PERMISSION_DENIED: \"PERMISSION_DENIED\",\n INVALID_REQUEST: \"INVALID_REQUEST\",\n NOT_FOUND: \"NOT_FOUND\",\n FILE_NOT_FOUND: \"FILE_NOT_FOUND\",\n FOLDER_NOT_FOUND: \"FOLDER_NOT_FOUND\",\n CONFLICT: \"CONFLICT\",\n PAYLOAD_TOO_LARGE: \"PAYLOAD_TOO_LARGE\",\n RATE_LIMITED: \"RATE_LIMITED\",\n STORAGE_NOT_INITIALIZED: \"STORAGE_NOT_INITIALIZED\",\n STORAGE_UNAVAILABLE: \"STORAGE_UNAVAILABLE\",\n TELEGRAM_AUTH_REQUIRED: \"TELEGRAM_AUTH_REQUIRED\",\n TELEGRAM_CONNECTION_FAILED: \"TELEGRAM_CONNECTION_FAILED\",\n TELEGRAM_FILE_NOT_FOUND: \"TELEGRAM_FILE_NOT_FOUND\",\n UPLOAD_FAILED: \"UPLOAD_FAILED\",\n DOWNLOAD_FAILED: \"DOWNLOAD_FAILED\",\n INTEGRITY_CHECK_FAILED: \"INTEGRITY_CHECK_FAILED\",\n INTERNAL: \"INTERNAL_ERROR\",\n} as const;\n\nexport type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];\n\nexport interface ApiErrorBody {\n error: {\n code: ErrorCode;\n message: string;\n details?: unknown;\n requestId?: string;\n };\n}\n\nexport const SESSION_COOKIE = \"omnicloud_session\";\nexport const REQUEST_ID_HEADER = \"x-request-id\";\n\nexport * from \"./mime\";\nexport * from \"./query\";\n","import { ERROR_CODES } from \"@omnicloud/shared\";\nimport type { ErrorCode } from \"@omnicloud/shared\";\n\n/**\n * Base class for all domain errors. The API maps these to structured HTTP\n * responses; everything else becomes a 500.\n */\nexport class DomainError extends Error {\n readonly code: ErrorCode;\n readonly status: number;\n readonly details?: unknown;\n /** Whether repeating the same operation may succeed. Defaults to false. */\n readonly retryable: boolean = false;\n /** Server-requested delay before a retry, in seconds, when the provider gave one. */\n readonly retryAfterSeconds?: number;\n\n constructor(\n code: ErrorCode,\n status: number,\n message: string,\n details?: unknown,\n retry?: { retryable?: boolean; retryAfterSeconds?: number },\n ) {\n super(message);\n this.name = new.target.name;\n this.code = code;\n this.status = status;\n this.details = details;\n this.retryable = retry?.retryable ?? false;\n this.retryAfterSeconds = retry?.retryAfterSeconds;\n }\n}\n\n/** Invalid request payload — 400. */\nexport class ValidationError extends DomainError {\n constructor(message: string, details?: unknown) {\n super(ERROR_CODES.INVALID_REQUEST, 400, message, details);\n }\n}\n\n/** Missing browser session — 401. */\nexport class UnauthorizedError extends DomainError {\n constructor(message = \"Authentication required\") {\n super(ERROR_CODES.AUTH_REQUIRED, 401, message);\n }\n}\n\n/** Invalid credentials or expired session token — 401. */\nexport class AuthInvalidError extends DomainError {\n constructor(message = \"Your session is no longer valid — please sign in again\") {\n super(ERROR_CODES.AUTH_INVALID, 401, message);\n }\n}\n\n/** Authenticated but not allowed — 403. */\nexport class ForbiddenError extends DomainError {\n constructor(message = \"You do not have access to this resource\") {\n super(ERROR_CODES.PERMISSION_DENIED, 403, message);\n }\n}\n\n/**\n * Used for missing resources as well as resources owned by someone else, to\n * avoid leaking existence. `resource` narrows the error code.\n */\nexport class NotFoundError extends DomainError {\n constructor(message = \"Resource not found\", resource?: \"file\" | \"folder\") {\n super(\n resource === \"file\"\n ? ERROR_CODES.FILE_NOT_FOUND\n : resource === \"folder\"\n ? ERROR_CODES.FOLDER_NOT_FOUND\n : ERROR_CODES.NOT_FOUND,\n 404,\n message,\n );\n }\n}\n\nexport class ConflictError extends DomainError {\n constructor(message: string) {\n super(ERROR_CODES.CONFLICT, 409, message);\n }\n}\n\nexport class PayloadTooLargeError extends DomainError {\n constructor(message = \"File is too large\") {\n super(ERROR_CODES.PAYLOAD_TOO_LARGE, 413, message);\n }\n}\n\nexport class RateLimitedError extends DomainError {\n constructor(\n message = \"Too many requests, please retry later\",\n options: { retryAfterSeconds?: number } = {},\n ) {\n super(ERROR_CODES.RATE_LIMITED, 429, message, undefined, {\n retryable: true,\n retryAfterSeconds: options.retryAfterSeconds,\n });\n }\n}\n\n/** The user has no storage backend set up yet — 409. */\nexport class StorageNotInitializedError extends DomainError {\n constructor() {\n super(ERROR_CODES.STORAGE_NOT_INITIALIZED, 409, \"Storage is not initialized for this account\");\n }\n}\n\n/** The storage backend is reachable but failing — 503. */\nexport class StorageUnavailableError extends DomainError {\n constructor(message = \"Storage is temporarily unavailable\", details?: unknown) {\n super(ERROR_CODES.STORAGE_UNAVAILABLE, 503, message, details);\n }\n}\n\n/** Telegram needs re-authentication — 401. */\nexport class TelegramAuthRequiredError extends DomainError {\n constructor(message = \"Your Telegram session has expired — please reconnect\") {\n super(ERROR_CODES.TELEGRAM_AUTH_REQUIRED, 401, message);\n }\n}\n\n/** Telegram connection could not be established — 502. */\nexport class TelegramConnectionError extends DomainError {\n constructor(message = \"Could not reach Telegram\", details?: unknown) {\n super(ERROR_CODES.TELEGRAM_CONNECTION_FAILED, 502, message, details, { retryable: true });\n }\n}\n\n/** The stored object is missing from Telegram — 404. */\nexport class TelegramFileNotFoundError extends DomainError {\n constructor(message = \"The stored file is missing from Telegram\") {\n super(ERROR_CODES.TELEGRAM_FILE_NOT_FOUND, 404, message);\n }\n}\n\n/** Upload failed — 502. */\nexport class UploadFailedError extends DomainError {\n constructor(message = \"Upload failed\", details?: unknown) {\n super(ERROR_CODES.UPLOAD_FAILED, 502, message, details);\n }\n}\n\n/** Download failed — 502. */\nexport class DownloadFailedError extends DomainError {\n constructor(message = \"Download failed\", details?: unknown) {\n super(ERROR_CODES.DOWNLOAD_FAILED, 502, message, details);\n }\n}\n\n/** Integrity verification failed — 502. */\nexport class IntegrityCheckError extends DomainError {\n constructor(message = \"Integrity check failed\", details?: unknown) {\n super(ERROR_CODES.INTEGRITY_CHECK_FAILED, 502, message, details);\n }\n}\n\n/** Operation cancelled by the client — 499 (non-standard but conventional). */\nexport class OperationCancelledError extends DomainError {\n constructor(message = \"Operation cancelled\") {\n super(ERROR_CODES.INVALID_REQUEST, 499, message);\n }\n}\n\n/** @deprecated Use the specific storage errors. Kept for provider compatibility. */\nexport class StorageProviderError extends DomainError {\n constructor(message: string, details?: unknown) {\n super(ERROR_CODES.TELEGRAM_CONNECTION_FAILED, 502, message, details);\n }\n}\n\n/** True when the value is one of our structured domain errors. */\nexport function isDomainError(error: unknown): error is DomainError {\n return error instanceof DomainError;\n}\n\n/**\n * Wraps an arbitrary provider failure as an UploadFailedError (the default\n * storage-operation error), preserving domain errors untouched.\n */\nexport function mapProviderError(message: string, error: unknown): DomainError {\n if (error instanceof DomainError) return error;\n const detail = error instanceof Error ? error.message : String(error);\n return new UploadFailedError(detail ? `${message}: ${detail}` : message, error);\n}\n","import { createHash } from \"node:crypto\";\nimport { Readable, Transform } from \"node:stream\";\nimport type {\n StorageHealth,\n StorageProvider,\n StorageUploadInput,\n StoredObject,\n StoredRef,\n TransferControl,\n} from \"./provider\";\nimport {\n IntegrityCheckError,\n OperationCancelledError,\n UploadFailedError,\n mapProviderError,\n} from \"../errors\";\nimport { isRetryableError, retryDelayMs } from \"./retry\";\n\nexport { sha256Hex } from \"../utils/hash\";\nimport { sha256Hex } from \"../utils/hash\";\n\nexport interface EngineUploadResult {\n stored: StoredObject;\n sha256: string;\n size: number;\n}\n\nexport interface EngineRetryPolicy {\n /** Total attempts, including the first. */\n attempts: number;\n /** Base delay in ms; doubles per retry. */\n baseDelayMs: number;\n}\n\nexport const DEFAULT_RETRY_POLICY: EngineRetryPolicy = { attempts: 3, baseDelayMs: 400 };\n\nfunction delay(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new OperationCancelledError());\n return;\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n function onAbort() {\n clearTimeout(timer);\n reject(new OperationCancelledError());\n }\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\nfunction throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted) throw new OperationCancelledError();\n}\n\n/** Converts a Buffer into a single-chunk Readable. */\nfunction bufferToStream(data: Buffer): Readable {\n return Readable.from([data]);\n}\n\n/**\n * Sits between the application and the raw StorageProvider and adds the\n * cross-cutting guarantees: SHA-256 integrity, streaming, progress reporting,\n * cancellation and bounded retries.\n *\n * The active provider is chosen per user by the host application (each user\n * has their own Telegram storage channel).\n */\nexport class StorageEngine {\n constructor(\n private readonly provider: StorageProvider,\n private readonly retryPolicy: EngineRetryPolicy = DEFAULT_RETRY_POLICY,\n ) {}\n\n get providerName(): string {\n return this.provider.name;\n }\n\n /**\n * Uploads an object, computing its SHA-256 as it goes and reporting\n * progress. Retries transient failures with exponential backoff.\n */\n async upload(\n input: StorageUploadInput,\n control: TransferControl = {},\n ): Promise<EngineUploadResult> {\n if (input.data) {\n const sha256 = sha256Hex(input.data);\n const size = input.data.byteLength;\n control.onProgress?.({ transferred: 0, total: size, percent: 0 });\n const stored = await this.withRetry(\n () => this.provider.put(input, control),\n control,\n (error) => new UploadFailedError(\"Upload failed\", error),\n );\n control.onProgress?.({ transferred: size, total: size, percent: 100 });\n return { stored, sha256, size };\n }\n\n const { path, size, sha256 } = input;\n if (!path || size === undefined || !sha256) {\n throw new UploadFailedError(\"Upload input has neither data nor a complete spooled file\");\n }\n control.onProgress?.({ transferred: 0, total: size, percent: 0 });\n const stored = await this.withRetry(\n () => this.provider.put(input, control),\n control,\n (error) => new UploadFailedError(\"Upload failed\", error),\n );\n control.onProgress?.({ transferred: size, total: size, percent: 100 });\n return { stored, sha256, size };\n }\n\n async download(ref: StoredRef, control: TransferControl = {}): Promise<Buffer> {\n return this.withRetry(\n () => this.provider.get(ref, control),\n control,\n (error) => mapProviderError(\"Download failed\", error),\n );\n }\n\n /**\n * Streams the object and verifies its SHA-256 against `expectedSha256`.\n *\n * The last chunk is held back until the digest of everything before it has\n * been checked, so a corrupt object errors the stream before its final bytes\n * are released. A consumer therefore never sees a complete-looking body that\n * fails the checksum. Memory stays bounded to about two provider chunks.\n */\n async downloadStream(\n ref: StoredRef,\n expectedSha256: string,\n control: TransferControl = {},\n ): Promise<Readable> {\n const source = await this.withRetry(\n () => this.provider.getStream(ref, control),\n control,\n (error) => mapProviderError(\"Download failed\", error),\n );\n\n const hash = createHash(\"sha256\");\n let held: Buffer | null = null;\n const gate = new Transform({\n transform(chunk: Buffer, _encoding, callback) {\n hash.update(chunk);\n if (held) this.push(held);\n held = chunk;\n callback();\n },\n flush(callback) {\n if (hash.digest(\"hex\") !== expectedSha256) {\n callback(new IntegrityCheckError(\"Downloaded content does not match its checksum\"));\n return;\n }\n if (held) this.push(held);\n callback();\n },\n });\n source.once(\"error\", (error) => gate.destroy(error));\n source.pipe(gate);\n return gate;\n }\n\n async remove(ref: StoredRef): Promise<void> {\n await this.withRetry(\n () => this.provider.delete(ref),\n {},\n (error) => mapProviderError(\"Delete failed\", error),\n );\n }\n\n async exists(ref: StoredRef): Promise<boolean> {\n try {\n return await this.provider.exists(ref);\n } catch (error) {\n throw mapProviderError(\"Storage lookup failed\", error);\n }\n }\n\n /** Read-only listing of stored objects, for reconciliation. Never modifies anything. */\n async list(limit: number): Promise<StoredObject[]> {\n try {\n return await this.provider.listObjects(limit);\n } catch (error) {\n throw mapProviderError(\"Storage listing failed\", error);\n }\n }\n\n /** Read-only search for stored objects with this content hash and size. */\n async findByCaption(caption: string, size: number): Promise<StoredObject[] | null> {\n if (!this.provider.findByCaption) return null;\n try {\n return await this.provider.findByCaption(caption, size);\n } catch (error) {\n throw mapProviderError(\"Storage lookup failed\", error);\n }\n }\n\n async stat(ref: StoredRef): Promise<StoredObject | null> {\n try {\n return await this.provider.stat(ref);\n } catch (error) {\n throw mapProviderError(\"Storage lookup failed\", error);\n }\n }\n\n async health(): Promise<StorageHealth> {\n try {\n return await this.provider.healthCheck();\n } catch (error) {\n return {\n healthy: false,\n latencyMs: null,\n message: error instanceof Error ? error.message : \"Storage health check failed\",\n targetTitle: null,\n };\n }\n }\n\n async verifyIntegrity(data: Buffer, expectedSha256: string): Promise<boolean> {\n return sha256Hex(data) === expectedSha256;\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private async withRetry<T>(\n operation: () => Promise<T>,\n control: TransferControl,\n wrapError: (error: unknown) => Error,\n ): Promise<T> {\n const { attempts, baseDelayMs } = this.retryPolicy;\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= Math.max(1, attempts); attempt += 1) {\n throwIfAborted(control.signal);\n try {\n return await operation();\n } catch (error) {\n lastError = error;\n if (attempt >= attempts || !isRetryableError(error)) break;\n await delay(retryDelayMs(error, attempt, baseDelayMs), control.signal);\n }\n }\n throw wrapError(lastError);\n }\n}\n\nexport { bufferToStream };\n","import { DomainError, OperationCancelledError } from \"../errors\";\n\n/**\n * Retry decisions come from the error's declared contract, not from its message\n * text. A DomainError says whether it is retryable; anything else is not retried.\n * Cancellation is never retried.\n */\nexport function isRetryableError(error: unknown): boolean {\n if (error instanceof OperationCancelledError) return false;\n if (error instanceof DomainError) return error.retryable;\n return false;\n}\n\n/**\n * How long to wait before the next attempt. A provider-supplied flood wait is\n * honoured exactly; otherwise exponential backoff with full jitter.\n */\nexport function retryDelayMs(\n error: unknown,\n attempt: number,\n baseDelayMs: number,\n random: () => number = Math.random,\n): number {\n if (error instanceof DomainError && error.retryAfterSeconds !== undefined) {\n return error.retryAfterSeconds * 1000;\n }\n const ceiling = baseDelayMs * 2 ** (attempt - 1);\n return Math.floor(random() * ceiling);\n}\n","import { createHash } from \"node:crypto\";\n\nexport function sha256Hex(data: Uint8Array): string {\n return createHash(\"sha256\").update(data).digest(\"hex\");\n}\n","import { mimeMatchersForType, paginationMeta } from \"@omnicloud/shared\";\nimport type { ListQuery, PaginationDTO } from \"@omnicloud/shared\";\nimport type { FolderRepository } from \"../repos\";\nimport type { ItemQuery } from \"../types\";\n\n/**\n * Translates the shared, user-facing ListQuery into the repository-ready\n * ItemQuery: validates/normalizes sort and date bounds, expands MIME\n * categories, and resolves `folder:Name` filters to concrete folder ids.\n */\nexport async function resolveItemQuery(\n folders: FolderRepository,\n userId: string,\n input: ListQuery,\n): Promise<ItemQuery> {\n const query: ItemQuery = {};\n\n // `folderId: null` means root; `undefined` means \"any folder\". Distinguish\n // them by presence of a defined value rather than the key itself.\n if (input.folderId !== undefined) query.folderId = input.folderId;\n if (input.q && input.q.trim()) query.nameContains = input.q.trim();\n if (input.ext) query.ext = input.ext.replace(/^\\./, \"\").toLowerCase();\n if (input.starred !== undefined) query.starred = input.starred;\n if (input.minSize !== undefined) query.minSize = input.minSize;\n if (input.maxSize !== undefined) query.maxSize = input.maxSize;\n if (input.sort) query.sort = input.sort;\n if (input.order) query.order = input.order;\n\n switch (input.status) {\n case \"active\":\n query.trashed = false;\n break;\n case \"trashed\":\n query.trashed = true;\n break;\n default:\n query.trashed = undefined;\n break;\n }\n\n if (input.type) {\n const matchers = mimeMatchersForType(input.type);\n if (matchers) query.mimeMatchers = matchers;\n }\n\n if (input.from) {\n const from = new Date(input.from);\n if (!Number.isNaN(from.getTime())) query.from = from;\n }\n if (input.to) {\n const to = new Date(input.to);\n if (!Number.isNaN(to.getTime())) query.to = to;\n }\n\n if (input.folderName) {\n query.folderIds = await folders.findIdsByName(userId, input.folderName);\n }\n\n return query;\n}\n\n/** Builds the pagination envelope shared by every listing endpoint. */\nexport function toPagination(page: number, limit: number, total: number): PaginationDTO {\n return paginationMeta(page, limit, total);\n}\n\nexport { paginationMeta };\n","import { createHash, randomBytes } from \"node:crypto\";\nimport type { BrowserSessionRecord, BrowserSessionRepository } from \"../repos\";\nimport { UnauthorizedError } from \"../errors\";\n\n/**\n * Browser sessions: opaque random tokens held in an HttpOnly cookie. The\n * server stores only a SHA-256 of each token, so a database read does not give\n * an attacker usable session cookies, and logout takes effect immediately\n * because every request is checked against the stored, revocable record.\n */\n\nexport interface IssuedSession {\n /** The raw token. Set it in the cookie and never store or log it. */\n token: string;\n session: BrowserSessionRecord;\n}\n\nexport interface SessionServiceOptions {\n /** Lifetime of a session from issue, in days. */\n ttlDays?: number;\n now?: () => Date;\n}\n\nexport const DEFAULT_SESSION_TTL_DAYS = 30;\n\nexport function hashToken(token: string): string {\n return createHash(\"sha256\").update(token, \"utf8\").digest(\"hex\");\n}\n\nexport class AuthSessionService {\n private readonly ttlMs: number;\n private readonly now: () => Date;\n\n constructor(\n private readonly repo: BrowserSessionRepository,\n options: SessionServiceOptions = {},\n ) {\n this.ttlMs = (options.ttlDays ?? DEFAULT_SESSION_TTL_DAYS) * 24 * 60 * 60 * 1000;\n this.now = options.now ?? (() => new Date());\n }\n\n async issue(\n userId: string,\n context: { userAgent?: string | null; ip?: string | null } = {},\n ): Promise<IssuedSession> {\n const token = randomBytes(32).toString(\"base64url\");\n const session = await this.repo.create({\n userId,\n tokenHash: hashToken(token),\n expiresAt: new Date(this.now().getTime() + this.ttlMs),\n userAgent: truncate(context.userAgent ?? null, 256),\n ip: context.ip ?? null,\n });\n return { token, session };\n }\n\n /**\n * Resolves a raw token to its active session. Returns null for an unknown,\n * expired, or revoked token. Successful lookups refresh `lastUsedAt`.\n */\n async resolve(token: string | undefined): Promise<BrowserSessionRecord | null> {\n if (!token || token.length < 16 || token.length > 128) return null;\n const session = await this.repo.findByTokenHash(hashToken(token));\n if (!session) return null;\n const now = this.now();\n if (session.revokedAt !== null || session.expiresAt.getTime() <= now.getTime()) {\n return null;\n }\n await this.repo.touch(session.id, now);\n return session;\n }\n\n /** Revokes the session behind a token. Unknown tokens are a no-op. */\n async revoke(token: string | undefined): Promise<void> {\n if (!token) return;\n const session = await this.repo.findByTokenHash(hashToken(token));\n if (session && session.revokedAt === null) {\n await this.repo.revoke(session.id, this.now());\n }\n }\n\n /** Revokes every session for a user, for \"sign out everywhere\". */\n async revokeAll(userId: string): Promise<number> {\n return this.repo.revokeAllForUser(userId, this.now());\n }\n\n listActive(userId: string): Promise<BrowserSessionRecord[]> {\n return this.repo.listActiveForUser(userId, this.now());\n }\n\n /** Housekeeping: removes sessions that expired more than `graceDays` ago. */\n async pruneExpired(graceDays = 7): Promise<number> {\n const cutoff = new Date(this.now().getTime() - graceDays * 24 * 60 * 60 * 1000);\n return this.repo.deleteExpiredBefore(cutoff);\n }\n}\n\nfunction truncate(value: string | null, max: number): string | null {\n return value === null ? null : value.slice(0, max);\n}\n\nexport function requireSession(session: BrowserSessionRecord | null): BrowserSessionRecord {\n if (!session) throw new UnauthorizedError();\n return session;\n}\n","import type { FileVersionRecord } from \"../types\";\n\n/**\n * Which historical versions to keep. The current version is never a candidate,\n * whatever the policy says. The default keeps everything, so no version is ever\n * removed unless an operator opts in.\n */\nexport type VersionRetentionPolicy =\n | { kind: \"KEEP_ALL\" }\n | { kind: \"KEEP_LATEST_N\"; count: number }\n | { kind: \"KEEP_FOR_DAYS\"; days: number };\n\nexport const DEFAULT_RETENTION: VersionRetentionPolicy = { kind: \"KEEP_ALL\" };\n\n/**\n * Returns the historical versions the policy would remove. Pure: it decides\n * nothing is deleted, the caller must delete the remote objects first.\n */\nexport function versionsToPrune(\n versions: FileVersionRecord[],\n currentVersionId: string | null,\n policy: VersionRetentionPolicy,\n now: Date,\n): FileVersionRecord[] {\n const historical = versions\n .filter((version) => version.id !== currentVersionId)\n .sort((a, b) => b.versionNumber - a.versionNumber);\n\n switch (policy.kind) {\n case \"KEEP_ALL\":\n return [];\n case \"KEEP_LATEST_N\": {\n const keep = Math.max(0, policy.count);\n return historical.slice(keep);\n }\n case \"KEEP_FOR_DAYS\": {\n const cutoff = now.getTime() - policy.days * 24 * 60 * 60 * 1000;\n return historical.filter((version) => version.createdAt.getTime() < cutoff);\n }\n }\n}\n","/**\n * Upload operations are the durable record of one logical upload. They exist so\n * that a retried request (same operation id) can resume or replay instead of\n * writing a second Telegram object and a second file record.\n *\n * PENDING accepted, Telegram write not started\n * UPLOADING Telegram write in progress or its outcome unknown\n * COMMITTING the stored object is being turned into a file record by exactly one\n * request, which holds this state as an exclusive claim\n * COMPLETED Telegram object stored and file record committed; result is final\n * UNKNOWN the Telegram write may have stored the object but the outcome is not\n * known; it is resolved, never silently re-uploaded\n * FAILED Telegram write definitively failed; the operation may be retried\n * under a new id\n */\nexport type UploadOperationStatus =\n \"PENDING\" | \"UPLOADING\" | \"COMMITTING\" | \"UNKNOWN\" | \"COMPLETED\" | \"FAILED\";\n\nconst TRANSITIONS: Record<UploadOperationStatus, readonly UploadOperationStatus[]> = {\n PENDING: [\"UPLOADING\", \"FAILED\"],\n UPLOADING: [\"COMMITTING\", \"FAILED\", \"UNKNOWN\"],\n // UNKNOWN is resolved, never re-uploaded: the object is either found (committed) or\n // confirmed absent (failed). It is not a state a new request may claim.\n UNKNOWN: [\"COMMITTING\", \"FAILED\"],\n COMMITTING: [\"COMPLETED\", \"UPLOADING\"],\n COMPLETED: [],\n FAILED: [],\n};\n\nexport function canTransition(from: UploadOperationStatus, to: UploadOperationStatus): boolean {\n return TRANSITIONS[from].includes(to);\n}\n\n/** A completed operation is final: replays return its result and never re-upload. */\nexport function isTerminal(status: UploadOperationStatus): boolean {\n return status === \"COMPLETED\" || status === \"FAILED\";\n}\n\n/** Operation ids are client-supplied; accept only a bounded, URL-safe token. */\nexport function isValidOperationId(id: string): boolean {\n return /^[A-Za-z0-9_-]{8,64}$/.test(id);\n}\n\n/** Raised when an operation with this id already exists for the user (a create race). */\nexport class OperationAlreadyExistsError extends Error {\n constructor() {\n super(\"An upload with this operation id already exists\");\n this.name = \"OperationAlreadyExistsError\";\n }\n}\n","/**\n * One structured record per storage operation: what happened, how long it took,\n * and whether it worked. Fields are operational only. The operation never\n * receives file contents, names of secrets, or session material, so none can\n * be logged from here.\n */\nexport interface OperationRecord {\n operation: \"upload\" | \"download\" | \"delete\" | \"replace\";\n userId: string;\n resourceId: string | null;\n sizeBytes: number | null;\n durationMs: number;\n status: \"ok\" | \"error\";\n errorCode: string | null;\n}\n\nexport interface OperationSink {\n info(record: Record<string, unknown>, message: string): void;\n warn(record: Record<string, unknown>, message: string): void;\n}\n\n/**\n * Times an operation and emits one record for it. The original error is always\n * rethrown unchanged, and a failure to write the log never fails the operation.\n */\nexport async function timedOperation<T>(\n sink: OperationSink | null,\n base: {\n operation: OperationRecord[\"operation\"];\n userId: string;\n resourceId?: string | null;\n sizeBytes?: number | null;\n },\n run: () => Promise<T>,\n now: () => number = Date.now,\n): Promise<T> {\n const started = now();\n const emit = (status: \"ok\" | \"error\", errorCode: string | null) => {\n if (!sink) return;\n const record: OperationRecord = {\n operation: base.operation,\n userId: base.userId,\n resourceId: base.resourceId ?? null,\n sizeBytes: base.sizeBytes ?? null,\n durationMs: now() - started,\n status,\n errorCode,\n };\n try {\n if (status === \"ok\") sink.info({ ...record }, `${base.operation}.completed`);\n else sink.warn({ ...record }, `${base.operation}.failed`);\n } catch {\n // Logging must never change the outcome of the operation.\n }\n };\n try {\n const result = await run();\n emit(\"ok\", null);\n return result;\n } catch (error) {\n const code = (error as { code?: string }).code ?? \"INTERNAL_ERROR\";\n emit(\"error\", code);\n throw error;\n }\n}\n","import type { ActivityAction, ResourceType } from \"@omnicloud/shared\";\nimport type { ActivityRepository } from \"../repos\";\nimport type { ActivityEventRecord, PageRequest, Paged } from \"../types\";\n\nexport interface RecordActivityInput {\n userId: string;\n action: ActivityAction;\n resourceType: ResourceType;\n resourceId: string;\n resourceName?: string | null;\n metadata?: Record<string, unknown> | null;\n}\n\n/**\n * The narrow interface other services depend on for activity logging, so a\n * no-op recorder can be substituted in tests and activity failures never\n * break the operation being logged.\n */\nexport interface ActivityRecorder {\n record(input: RecordActivityInput): Promise<void>;\n}\n\n/** Activity log with best-effort recording and retention pruning. */\nexport class ActivityService implements ActivityRecorder {\n constructor(\n private readonly activity: ActivityRepository,\n /** Days to retain events; older rows are pruned on demand. */\n private readonly retentionDays = 90,\n ) {}\n\n /**\n * Records an event. Never throws — a logging failure must not fail the\n * user-facing operation that produced it.\n */\n async record(input: RecordActivityInput): Promise<void> {\n try {\n await this.activity.record({\n userId: input.userId,\n action: input.action,\n resourceType: input.resourceType,\n resourceId: input.resourceId,\n resourceName: input.resourceName ?? null,\n metadata: sanitizeMetadata(input.metadata),\n });\n } catch {\n // Swallow: activity logging is observability, not a transaction.\n }\n }\n\n async list(userId: string, page: PageRequest): Promise<Paged<ActivityEventRecord>> {\n return this.activity.list(userId, page);\n }\n\n /** Most recent distinct files for the given actions, for the Recent view. */\n async recentFiles(\n userId: string,\n actions: readonly ActivityAction[],\n limit: number,\n ): Promise<ActivityEventRecord[]> {\n return this.activity.recentFiles(userId, actions, limit);\n }\n\n async prune(): Promise<number> {\n const cutoff = new Date(Date.now() - this.retentionDays * 24 * 60 * 60 * 1000);\n return this.activity.pruneOlderThan(cutoff);\n }\n}\n\nconst SENSITIVE_KEY = /(password|secret|token|session|hash|credential)/i;\n\n/**\n * Strips credential-shaped keys so the activity log can never become a place\n * where secrets leak.\n */\nfunction sanitizeMetadata(\n metadata: Record<string, unknown> | null | undefined,\n): Record<string, unknown> | null {\n if (!metadata) return null;\n const clean: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(metadata)) {\n if (SENSITIVE_KEY.test(key)) continue;\n if (value === undefined) continue;\n clean[key] = value;\n }\n return Object.keys(clean).length > 0 ? clean : null;\n}\n\n/** A recorder that discards events (used where activity is not wired up). */\nexport const noopActivityRecorder: ActivityRecorder = {\n async record() {\n /* intentionally empty */\n },\n};\n","/**\n * Sanitizes a client-supplied filename into a safe, single path component.\n * Returns an empty string when nothing usable remains.\n */\nexport function sanitizeFileName(name: string): string {\n // Drop any path components — prevents path traversal.\n const base = name.split(/[/\\\\]/).pop() ?? name;\n // Strip control characters and characters that are unsafe across filesystems.\n const cleaned = base\n // eslint-disable-next-line no-control-regex\n .replace(/[\\u0000-\\u001f\\u007f-\\u009f]/g, \"\")\n // Bidirectional overrides and isolates, and zero-width characters, can make a\n // name display with a different extension than the one stored.\n .replace(/[\\u200B-\\u200F\\u202A-\\u202E\\u2066-\\u2069\\uFEFF]/g, \"\")\n .replace(/[<>:\"|?*]/g, \"_\")\n .trim();\n if (!cleaned || cleaned === \".\" || cleaned === \"..\") return \"\";\n return cleaned.slice(0, 255);\n}\n","import { createHash } from \"node:crypto\";\nimport type { FileUploadInput } from \"../services/file-service\";\nimport { sha256Hex } from \"./hash\";\nimport { sanitizeFileName } from \"./filename\";\n\n/**\n * A digest of everything that determines what an upload creates: the sanitized\n * name, the target folder, the file being replaced, and the content. A replay\n * under the same operation id must present the same fingerprint, otherwise it is\n * a different request reusing a key.\n */\nexport function uploadRequestFingerprint(input: FileUploadInput): string {\n const contentHash = input.spooled?.sha256 ?? sha256Hex(input.data ?? Buffer.alloc(0));\n const canonical = JSON.stringify({\n name: sanitizeFileName(input.name),\n folderId: input.folderId ?? null,\n replaceFileId: input.replaceFileId ?? null,\n contentHash,\n size: input.spooled?.size ?? input.data?.byteLength ?? 0,\n });\n return createHash(\"sha256\").update(canonical).digest(\"hex\");\n}\n","import type { Readable } from \"node:stream\";\nimport type { ListQuery } from \"@omnicloud/shared\";\nimport { mimeFromFilename } from \"@omnicloud/shared\";\nimport {\n ConflictError,\n NotFoundError,\n TelegramConnectionError,\n UploadFailedError,\n ValidationError,\n} from \"../errors\";\nimport { sanitizeFileName } from \"../utils/filename\";\nimport type { FileRecord, FileVersionRecord, ItemQuery, PageRequest, Paged } from \"../types\";\nimport type {\n FileRepository,\n FolderRepository,\n UploadOperationRecord,\n UploadOperationRepository,\n} from \"../repos\";\nimport { OperationAlreadyExistsError, isValidOperationId } from \"./upload-operation\";\nimport { sha256Hex } from \"../utils/hash\";\nimport { timedOperation, type OperationSink } from \"../utils/operation-log\";\nimport { uploadRequestFingerprint } from \"../utils/request-fingerprint\";\nimport type { StorageEngine } from \"../storage/engine\";\nimport type { StoredObject } from \"../storage/provider\";\nimport type { TransferControl, TransferProgress } from \"../storage/provider\";\nimport type { ActivityRecorder } from \"./activity-service\";\nimport { noopActivityRecorder } from \"./activity-service\";\nimport { resolveItemQuery } from \"./query-resolver\";\nimport { versionsToPrune, type VersionRetentionPolicy } from \"./version-retention\";\n\nexport interface FileDownload {\n record: FileRecord;\n data: Buffer;\n integrityVerified: boolean;\n}\n\nexport interface FileStreamDownload {\n record: FileRecord;\n /** Errors with IntegrityCheckError instead of ending if the bytes do not match. */\n stream: Readable;\n}\n\nexport type EngineResolver = (userId: string) => Promise<StorageEngine>;\n\nexport interface FileUploadInput {\n folderId: string | null;\n name: string;\n /** In-memory content. Use `spooled` for anything large. */\n data?: Buffer;\n /** A file already written to disk by `spoolToFile`, with its checksum. */\n spooled?: { path: string; size: number; sha256: string };\n /** When set, uploads a new version of this file instead of creating one. */\n replaceFileId?: string;\n /** Client-supplied id that makes the upload idempotent across retries. */\n operationId?: string;\n /** Progress callback forwarded from the transport layer. */\n onProgress?: (progress: TransferProgress) => void;\n signal?: AbortSignal;\n}\n\nexport interface FileListResult {\n items: FileRecord[];\n total: number;\n}\n\n/**\n * File metadata operations. Every method verifies ownership: records\n * belonging to other users are reported as missing.\n *\n * Reliability rules honored here:\n * - metadata is written only after the storage upload succeeds;\n * - permanent deletion removes the remote object before the metadata;\n * - client-supplied ownership is never trusted (userId is server-derived).\n */\n/**\n * True when a failure was a Telegram connection problem, possibly after the write landed.\n * The storage engine wraps upload failures, keeping the original error as the cause.\n */\nfunction contentSha256(input: FileUploadInput): string {\n return input.spooled?.sha256 ?? sha256Hex(input.data ?? Buffer.alloc(0));\n}\n\n/**\n * The message caption for one upload attempt. It carries the operation id, so two\n * uploads of identical bytes get different captions and a retry finds only its own message.\n */\nexport function uploadCaption(sha256: string, operationId: string): string {\n return `${sha256}:${operationId}`;\n}\n\nfunction contentSize(input: FileUploadInput): number {\n return input.spooled?.size ?? input.data?.byteLength ?? 0;\n}\n\nfunction connectionCausedFailure(error: unknown): boolean {\n if (error instanceof TelegramConnectionError) return true;\n if (error instanceof UploadFailedError) {\n return error.details instanceof TelegramConnectionError;\n }\n return false;\n}\n\nexport class FileService {\n constructor(\n private readonly files: FileRepository,\n private readonly folders: FolderRepository,\n private readonly engineFor: EngineResolver,\n private readonly activity: ActivityRecorder = noopActivityRecorder,\n private readonly operations: UploadOperationRepository | null = null,\n private readonly log: OperationSink | null = null,\n ) {}\n\n // ── Upload ───────────────────────────────────────────────────────────────\n\n async upload(userId: string, input: FileUploadInput): Promise<FileRecord> {\n return timedOperation(\n this.log,\n {\n operation: \"upload\",\n userId,\n resourceId: input.replaceFileId ?? null,\n sizeBytes: input.spooled?.size ?? input.data?.byteLength ?? null,\n },\n () =>\n input.operationId && this.operations\n ? this.uploadOnce(userId, input, input.operationId, this.operations)\n : this.performUpload(userId, input),\n );\n }\n\n /**\n * Idempotent upload keyed by a client-supplied operation id. A retry never\n * writes a second Telegram object: a completed operation returns its file, and\n * an operation whose object was already stored is committed without uploading.\n */\n private async uploadOnce(\n userId: string,\n input: FileUploadInput,\n operationId: string,\n operations: UploadOperationRepository,\n ): Promise<FileRecord> {\n if (!isValidOperationId(operationId)) {\n throw new ValidationError(\"Invalid upload operation id\");\n }\n const fingerprint = uploadRequestFingerprint(input);\n let op = await operations.findByOperationId(userId, operationId);\n if (op?.requestFingerprint && op.requestFingerprint !== fingerprint) {\n throw new ConflictError(\n \"This upload key was already used for a different file. Use a new key for a new upload.\",\n );\n }\n\n if (op?.status === \"COMPLETED\" && op.fileId) {\n return this.get(userId, op.fileId);\n }\n if (op?.status === \"UPLOADING\" && op.telegramMessageId !== null) {\n return this.commitStoredObject(userId, input, operations, op);\n }\n if (op?.status === \"UNKNOWN\") {\n return this.resolveUnknown(userId, input, operations, op);\n }\n if (!op) {\n try {\n op = await operations.create({ userId, operationId, requestFingerprint: fingerprint });\n } catch (error) {\n if (!(error instanceof OperationAlreadyExistsError)) throw error;\n // Another request with the same key won the create race. Follow its state\n // instead of failing: a completed winner returns its file, an in-flight one\n // is joined through the same recovery path a crashed upload uses.\n const outcome = await this.followWinner(userId, input, operationId, operations);\n if (outcome) return outcome;\n throw new ConflictError(\"A concurrent upload with this operation id is still in progress\");\n }\n }\n // Only the request that wins this compare-and-set uploads. A PENDING operation\n // or a FAILED one (a retry after a failed attempt) can be claimed; any other\n // request that finds it already in flight follows the winner.\n const claimed =\n (await operations.claim(op.id, \"PENDING\", \"UPLOADING\")) ||\n (await operations.claim(op.id, \"FAILED\", \"UPLOADING\"));\n if (!claimed) {\n // Lost the claim to a concurrent request: follow the winner instead of\n // uploading. A bounded wait keeps this from spinning.\n const outcome = await this.followWinner(userId, input, operationId, operations);\n if (outcome) return outcome;\n throw new ConflictError(\"A concurrent upload with this operation id is still in progress\");\n }\n\n try {\n const record = await this.performUpload(userId, input, {\n caption: uploadCaption(contentSha256(input), op.operationId),\n onStored: async (stored) => {\n await operations.update(op!.id, { telegramMessageId: Number(stored.messageId) });\n // Take the exclusive commit claim as soon as the object is stored, so a\n // follower that sees UPLOADING with a message id cannot commit a second record.\n await operations.claim(op!.id, \"UPLOADING\", \"COMMITTING\");\n },\n });\n await operations.update(op.id, { status: \"COMPLETED\", fileId: record.id });\n return record;\n } catch (error) {\n // A connection failure may come after Telegram stored the object, so the outcome is\n // unknown rather than failed. Only a failure known to precede the write is FAILED.\n const status = connectionCausedFailure(error) ? \"UNKNOWN\" : \"FAILED\";\n await operations.update(op.id, {\n status,\n sha256: contentSha256(input),\n size: contentSize(input),\n error: error instanceof Error ? error.message.slice(0, 200) : \"upload failed\",\n });\n throw error;\n }\n }\n\n /**\n * Resolves an operation whose Telegram outcome is unknown. The channel is searched\n * for the object by its content hash and size. Exactly one match is adopted, zero\n * matches means nothing was stored so the upload is sent again, and anything\n * ambiguous stays UNKNOWN so that no duplicate is written.\n */\n private async resolveUnknown(\n userId: string,\n input: FileUploadInput,\n operations: UploadOperationRepository,\n op: UploadOperationRecord,\n ): Promise<FileRecord> {\n if (op.sha256 === null || op.size === null) {\n throw new ConflictError(\n \"This upload's outcome is unknown and cannot be checked automatically. Ask an administrator to reconcile it.\",\n );\n }\n const engine = await this.engineFor(userId);\n const matches = await engine.findByCaption(uploadCaption(op.sha256, op.operationId), op.size);\n if (matches === null) {\n throw new ConflictError(\n \"This upload's outcome is unknown and the storage backend cannot search for it.\",\n );\n }\n if (matches.length > 1) {\n throw new ConflictError(\n \"This upload's outcome is unknown and more than one matching object exists. Nothing was written.\",\n );\n }\n if (matches.length === 1) {\n const stored = matches[0]!;\n await operations.update(op.id, { telegramMessageId: Number(stored.messageId) });\n await operations.claim(op.id, \"UNKNOWN\", \"COMMITTING\");\n const record = await this.commitRecord(userId, input, {\n name: sanitizeFileName(input.name),\n mimeType: mimeFromFilename(sanitizeFileName(input.name)),\n size: op.size,\n sha256: op.sha256,\n telegramMessageId: Number(stored.messageId),\n });\n await operations.update(op.id, { status: \"COMPLETED\", fileId: record.id });\n return record;\n }\n await operations.claim(op.id, \"UNKNOWN\", \"FAILED\");\n return this.uploadOnce(userId, input, op.operationId, operations);\n }\n\n /**\n * Follows an operation another request is driving, for a bounded time. Returns\n * the finished file if the winner completes, or the stored object's commit if\n * it has stored one. Returns null when the winner is still in progress after the\n * budget, so the caller can report a conflict instead of waiting forever.\n */\n private async followWinner(\n userId: string,\n input: FileUploadInput,\n operationId: string,\n operations: UploadOperationRepository,\n ): Promise<FileRecord | null> {\n for (let attempt = 0; attempt < 50; attempt += 1) {\n const current = await operations.findByOperationId(userId, operationId);\n if (current?.status === \"COMPLETED\" && current.fileId) {\n return this.get(userId, current.fileId);\n }\n if (current?.status === \"FAILED\") {\n // A failed attempt may be retried under the same id: return control so the\n // caller can claim it again rather than waiting on it.\n return null;\n }\n if (current?.status === \"UPLOADING\" && current.telegramMessageId !== null) {\n return this.commitStoredObject(userId, input, operations, current);\n }\n await new Promise((resolve) => setTimeout(resolve, 10));\n }\n return null;\n }\n\n /** Commits metadata for an object that is already in Telegram. */\n private async commitStoredObject(\n userId: string,\n input: FileUploadInput,\n operations: UploadOperationRepository,\n op: UploadOperationRecord,\n ): Promise<FileRecord> {\n const name = sanitizeFileName(input.name);\n const mimeType = mimeFromFilename(name);\n const size = op.size ?? input.spooled?.size ?? input.data?.byteLength ?? 0;\n const sha256 = op.sha256 ?? input.spooled?.sha256 ?? sha256Hex(input.data ?? Buffer.alloc(0));\n\n // Exactly one request may write the file record for a stored object. The claim\n // is an atomic compare-and-set, so a request that loses it must not commit\n // another record; it follows the winner's result instead.\n const claimed = await operations.claim(op.id, \"UPLOADING\", \"COMMITTING\");\n if (!claimed) {\n const current = await operations.findByOperationId(userId, op.operationId);\n if (current?.status === \"COMPLETED\" && current.fileId) {\n return this.get(userId, current.fileId);\n }\n throw new ConflictError(\"This upload is being committed by another request. Retry shortly.\");\n }\n\n let record: FileRecord;\n try {\n record = await this.commitRecord(userId, input, {\n name,\n mimeType,\n size,\n sha256,\n telegramMessageId: op.telegramMessageId!,\n });\n } catch (error) {\n await operations.claim(op.id, \"COMMITTING\", \"UPLOADING\");\n throw error;\n }\n await operations.update(op.id, { status: \"COMPLETED\", fileId: record.id });\n return record;\n }\n\n private async performUpload(\n userId: string,\n input: FileUploadInput,\n hooks: { caption?: string; onStored?: (stored: StoredObject) => Promise<void> } = {},\n ): Promise<FileRecord> {\n const name = sanitizeFileName(input.name);\n if (!name) throw new ValidationError(\"Invalid file name\");\n\n let folderId = input.folderId;\n if (input.replaceFileId) {\n const existing = await this.get(userId, input.replaceFileId);\n folderId = existing.folderId;\n } else if (folderId !== null) {\n await this.assertFolder(userId, folderId);\n }\n\n const mimeType = mimeFromFilename(name);\n const engine = await this.engineFor(userId);\n\n const control: TransferControl = {\n onProgress: input.onProgress,\n signal: input.signal,\n };\n\n if (!input.data && !input.spooled) {\n throw new ValidationError(\"An upload needs content\");\n }\n // Upload first; metadata is only persisted once the provider succeeds.\n const { stored, sha256, size } = await engine.upload(\n input.spooled\n ? {\n name,\n mimeType,\n path: input.spooled.path,\n size: input.spooled.size,\n sha256: input.spooled.sha256,\n caption: hooks.caption,\n }\n : { name, mimeType, data: input.data, caption: hooks.caption },\n control,\n );\n await hooks.onStored?.(stored);\n\n try {\n return await this.commitRecord(userId, input, {\n name,\n mimeType,\n size,\n sha256,\n telegramMessageId: Number(stored.messageId),\n });\n } catch (error) {\n // The object is in Telegram but no record will point at it. Remove it so\n // a failed commit does not leave an unreferenced object behind. If that\n // removal also fails, the original error is still the one reported.\n await engine.remove({ messageId: String(stored.messageId) }).catch(() => undefined);\n throw error;\n }\n }\n\n /** Persists the file (or the new version of a file) for an object already stored. */\n private async commitRecord(\n userId: string,\n input: FileUploadInput,\n next: {\n name: string;\n mimeType: string;\n size: number;\n sha256: string;\n telegramMessageId: number;\n },\n ): Promise<FileRecord> {\n const { name, mimeType, size, sha256, telegramMessageId } = next;\n\n if (input.replaceFileId) {\n return this.replaceVersion(userId, input.replaceFileId, {\n name,\n size,\n mimeType,\n sha256,\n telegramMessageId,\n });\n }\n\n const folderId = input.folderId;\n if (folderId !== null) {\n await this.assertFolder(userId, folderId);\n }\n\n const record = await this.files.create({\n userId,\n folderId,\n name,\n size,\n mimeType,\n sha256,\n telegramMessageId,\n });\n\n // Establish version 1 immediately so the version history is complete from\n // the first upload (the foundation for v0.3's version UI).\n const version = await this.files.createVersion({\n fileId: record.id,\n userId,\n size,\n mimeType,\n sha256,\n telegramMessageId,\n });\n const withVersion = await this.files.update(record.id, {\n currentVersionId: version.id,\n versionCount: 1,\n });\n\n await this.activity.record({\n userId,\n action: \"upload\",\n resourceType: \"file\",\n resourceId: withVersion.id,\n resourceName: withVersion.name,\n metadata: { size, mimeType },\n });\n\n return withVersion;\n }\n\n /** Uploads a new version of an existing file and makes it current. */\n private async replaceVersion(\n userId: string,\n fileId: string,\n next: {\n name: string;\n size: number;\n mimeType: string;\n sha256: string;\n telegramMessageId: number;\n },\n ): Promise<FileRecord> {\n const existing = await this.get(userId, fileId);\n\n const version = await this.files.createVersion({\n fileId,\n userId,\n size: next.size,\n mimeType: next.mimeType,\n sha256: next.sha256,\n telegramMessageId: next.telegramMessageId,\n });\n const count = await this.files.countVersions(fileId);\n\n const updated = await this.files.update(fileId, {\n name: next.name,\n size: next.size,\n mimeType: next.mimeType,\n sha256: next.sha256,\n telegramMessageId: next.telegramMessageId,\n currentVersionId: version.id,\n versionCount: count,\n });\n\n await this.activity.record({\n userId,\n action: \"replace\",\n resourceType: \"file\",\n resourceId: fileId,\n resourceName: updated.name,\n metadata: { previousSha256: existing.sha256, size: next.size },\n });\n\n return updated;\n }\n\n // ── Reads ────────────────────────────────────────────────────────────────\n\n async list(userId: string, folderId: string | null): Promise<FileRecord[]> {\n if (folderId !== null) await this.assertFolder(userId, folderId);\n return this.files.listByFolder(userId, folderId);\n }\n\n async query(userId: string, input: ListQuery, page: PageRequest): Promise<Paged<FileRecord>> {\n if (input.folderId != null) await this.assertFolder(userId, input.folderId);\n const query: ItemQuery = await resolveItemQuery(this.folders, userId, input);\n return this.files.query(userId, query, page);\n }\n\n async get(userId: string, id: string): Promise<FileRecord> {\n const record = await this.files.findById(id);\n if (!record || record.userId !== userId) throw new NotFoundError(\"File not found\", \"file\");\n return record;\n }\n\n /** Like get(), but rejects trashed files (for download/open). */\n async getActive(userId: string, id: string): Promise<FileRecord> {\n const record = await this.get(userId, id);\n if (record.deletedAt) throw new NotFoundError(\"File not found\", \"file\");\n return record;\n }\n\n async listVersions(userId: string, id: string): Promise<FileVersionRecord[]> {\n await this.get(userId, id);\n return this.files.listVersions(id);\n }\n\n // ── Mutations ────────────────────────────────────────────────────────────\n\n async rename(userId: string, id: string, name: string): Promise<FileRecord> {\n const existing = await this.get(userId, id);\n const clean = sanitizeFileName(name);\n if (!clean) throw new ValidationError(\"Invalid file name\");\n const updated = await this.files.update(id, { name: clean });\n await this.activity.record({\n userId,\n action: \"rename\",\n resourceType: \"file\",\n resourceId: id,\n resourceName: clean,\n metadata: { previousName: existing.name },\n });\n return updated;\n }\n\n async move(userId: string, id: string, folderId: string | null): Promise<FileRecord> {\n const existing = await this.get(userId, id);\n if (folderId !== null) await this.assertFolder(userId, folderId);\n const updated = await this.files.update(id, { folderId });\n await this.activity.record({\n userId,\n action: \"move\",\n resourceType: \"file\",\n resourceId: id,\n resourceName: updated.name,\n metadata: { from: existing.folderId, to: folderId },\n });\n return updated;\n }\n\n async setStarred(userId: string, id: string, starred: boolean): Promise<FileRecord> {\n await this.get(userId, id);\n const updated = await this.files.update(id, { starred });\n await this.activity.record({\n userId,\n action: starred ? \"star\" : \"unstar\",\n resourceType: \"file\",\n resourceId: id,\n resourceName: updated.name,\n });\n return updated;\n }\n\n /**\n * Moves a file to Trash (soft delete). The remote object is untouched, so\n * restore is lossless.\n */\n async trash(userId: string, id: string): Promise<FileRecord> {\n const record = await this.get(userId, id);\n if (record.deletedAt) return record;\n const updated = await this.files.update(id, {\n deletedAt: new Date(),\n trashBatchId: `solo-${id}`,\n });\n await this.activity.record({\n userId,\n action: \"trash\",\n resourceType: \"file\",\n resourceId: id,\n resourceName: updated.name,\n });\n return updated;\n }\n\n /** Restores a trashed file to its previous folder (or root). */\n async restore(userId: string, id: string): Promise<FileRecord> {\n const record = await this.get(userId, id);\n\n // If the original folder is gone or itself trashed, fall back to root so a\n // restore can never produce an orphaned file.\n let folderId = record.folderId;\n if (folderId !== null) {\n const folder = await this.folders.findById(folderId);\n if (!folder || folder.userId !== userId || folder.deletedAt) folderId = null;\n }\n\n const updated = await this.files.update(id, { deletedAt: null, trashBatchId: null, folderId });\n await this.activity.record({\n userId,\n action: \"restore\",\n resourceType: \"file\",\n resourceId: id,\n resourceName: updated.name,\n });\n return updated;\n }\n\n /**\n * Permanently deletes a file: the Telegram object is removed first, then the\n * metadata. If the remote delete fails we keep the metadata so the user can\n * retry, rather than silently orphaning the storage object.\n */\n async deletePermanently(userId: string, id: string): Promise<void> {\n return timedOperation(this.log, { operation: \"delete\", userId, resourceId: id }, () =>\n this.performPermanentDelete(userId, id),\n );\n }\n\n private async performPermanentDelete(userId: string, id: string): Promise<void> {\n const record = await this.get(userId, id);\n const engine = await this.engineFor(userId);\n\n // Every remote object (current and historical) must be gone before any\n // metadata is dropped. A failure here throws and leaves all pointers in\n // place, so a retry can finish the job without orphaning a Telegram message.\n const versions = await this.files.listVersions(id);\n const messageIds = new Set<string>([String(record.telegramMessageId)]);\n for (const version of versions) messageIds.add(String(version.telegramMessageId));\n for (const messageId of messageIds) {\n await engine.remove({ messageId });\n }\n\n await this.files.deleteVersionsByFileIds([id]);\n await this.files.deleteMany([id]);\n await this.activity.record({\n userId,\n action: \"delete\",\n resourceType: \"file\",\n resourceId: id,\n resourceName: record.name,\n });\n }\n\n /**\n * v0.1-compatible hard delete used by the current API: permanently removes\n * the file. Retained so existing clients keep working.\n */\n async delete(userId: string, id: string): Promise<void> {\n await this.deletePermanently(userId, id);\n }\n\n // ── Download ─────────────────────────────────────────────────────────────\n\n async download(userId: string, id: string, control: TransferControl = {}): Promise<FileDownload> {\n const record = await this.getActive(userId, id);\n const engine = await this.engineFor(userId);\n const data = await engine.download({ messageId: String(record.telegramMessageId) }, control);\n const integrityVerified = await engine.verifyIntegrity(data, record.sha256);\n await this.activity.record({\n userId,\n action: \"download\",\n resourceType: \"file\",\n resourceId: id,\n resourceName: record.name,\n metadata: { size: record.size, integrityVerified },\n });\n return { record, data, integrityVerified };\n }\n\n /**\n * Streaming download for large files. The caller must consume `stream` to\n * the end and then await `verified` to learn whether the bytes match the\n * stored checksum. The download activity is recorded when the stream opens.\n */\n async downloadStreamed(\n userId: string,\n id: string,\n control: TransferControl = {},\n ): Promise<FileStreamDownload> {\n return timedOperation(this.log, { operation: \"download\", userId, resourceId: id }, () =>\n this.performStreamedDownload(userId, id, control),\n );\n }\n\n private async performStreamedDownload(\n userId: string,\n id: string,\n control: TransferControl,\n ): Promise<FileStreamDownload> {\n const record = await this.getActive(userId, id);\n const engine = await this.engineFor(userId);\n const stream = await engine.downloadStream(\n { messageId: String(record.telegramMessageId) },\n record.sha256,\n control,\n );\n await this.activity.record({\n userId,\n action: \"download\",\n resourceType: \"file\",\n resourceId: id,\n resourceName: record.name,\n metadata: { size: record.size, streamed: true },\n });\n return { record, stream };\n }\n\n /** Records an \"open\" event (the UI opening the details panel, say). */\n async touch(userId: string, id: string, action: \"open\" = \"open\"): Promise<void> {\n const record = await this.get(userId, id);\n await this.activity.record({\n userId,\n action,\n resourceType: \"file\",\n resourceId: id,\n resourceName: record.name,\n });\n }\n\n // ── Batch operations ─────────────────────────────────────────────────────\n\n /**\n * Applies an operation to many files, collecting per-item failures instead\n * of aborting the whole batch.\n */\n async batch(\n userId: string,\n ids: string[],\n operation: \"trash\" | \"restore\" | \"delete\" | \"star\" | \"unstar\" | \"move\",\n options: { folderId?: string | null; operationId?: string } = {},\n ): Promise<{\n operationId?: string;\n requested: number;\n succeeded: number;\n failed: number;\n errors: { id: string; message: string }[];\n }> {\n const errors: { id: string; message: string }[] = [];\n let succeeded = 0;\n\n if (ids.length === 0) throw new ValidationError(\"No items selected\");\n\n // Validate the destination once, so a bad folder fails the whole batch\n // rather than producing N identical errors.\n if (operation === \"move\") {\n const target = options.folderId ?? null;\n if (target !== null) await this.assertFolder(userId, target);\n }\n\n for (const id of ids) {\n try {\n switch (operation) {\n case \"trash\":\n await this.trash(userId, id);\n break;\n case \"restore\":\n await this.restore(userId, id);\n break;\n case \"delete\":\n await this.deletePermanently(userId, id);\n break;\n case \"star\":\n await this.setStarred(userId, id, true);\n break;\n case \"unstar\":\n await this.setStarred(userId, id, false);\n break;\n case \"move\":\n await this.move(userId, id, options.folderId ?? null);\n break;\n }\n succeeded += 1;\n } catch (error) {\n errors.push({ id, message: error instanceof Error ? error.message : \"Operation failed\" });\n }\n }\n\n return {\n ...(options.operationId ? { operationId: options.operationId } : {}),\n requested: ids.length,\n succeeded,\n failed: errors.length,\n errors,\n };\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n /**\n * Removes historical versions the policy no longer keeps. For each version the\n * Telegram message is deleted first, and its row only after that succeeds, the\n * same order as a permanent delete. A failed remote delete leaves that version\n * in place. The current version is never removed. Returns how many were removed.\n */\n async pruneVersions(\n userId: string,\n fileId: string,\n policy: VersionRetentionPolicy,\n now: Date = new Date(),\n ): Promise<number> {\n const record = await this.get(userId, fileId);\n const versions = await this.files.listVersions(fileId);\n const doomed = versionsToPrune(versions, record.currentVersionId ?? null, policy, now);\n if (doomed.length === 0) return 0;\n\n const engine = await this.engineFor(userId);\n let removed = 0;\n for (const version of doomed) {\n await engine.remove({ messageId: String(version.telegramMessageId) });\n await this.files.deleteVersion(version.id);\n removed += 1;\n }\n await this.activity.record({\n userId,\n action: \"delete\",\n resourceType: \"file\",\n resourceId: fileId,\n resourceName: `${record.name} (${removed} old version${removed === 1 ? \"\" : \"s\"})`,\n });\n return removed;\n }\n\n private async assertFolder(userId: string, folderId: string): Promise<void> {\n const folder = await this.folders.findById(folderId);\n if (!folder || folder.userId !== userId) {\n throw new NotFoundError(\"Folder not found\", \"folder\");\n }\n if (folder.deletedAt) throw new ConflictError(\"The target folder is in the Trash\");\n }\n}\n","import type { FolderRecord } from \"../types\";\n\n/**\n * Orders folder ids so every child comes before its parent. Deleting in this\n * order satisfies the database's `onDelete: Restrict` rule on `parentId`.\n */\nexport function deepestFirst(folderIds: string[], folders: FolderRecord[]): string[] {\n const byId = new Map(folders.map((folder) => [folder.id, folder]));\n const depthOf = (id: string): number => {\n let depth = 0;\n const seen = new Set<string>();\n let cursor = byId.get(id)?.parentId ?? null;\n while (cursor !== null && !seen.has(cursor)) {\n seen.add(cursor);\n depth += 1;\n cursor = byId.get(cursor)?.parentId ?? null;\n }\n return depth;\n };\n return [...folderIds].sort((a, b) => depthOf(b) - depthOf(a));\n}\n","import type { ListQuery } from \"@omnicloud/shared\";\nimport { ConflictError, NotFoundError, ValidationError } from \"../errors\";\nimport { sanitizeFileName } from \"../utils/filename\";\nimport { deepestFirst } from \"../utils/folder-order\";\nimport type { FolderRecord, ItemQuery, PageRequest, Paged } from \"../types\";\nimport type { FileRepository, FolderRepository } from \"../repos\";\nimport type { EngineResolver } from \"./file-service\";\nimport type { ActivityRecorder } from \"./activity-service\";\nimport { noopActivityRecorder } from \"./activity-service\";\nimport { resolveItemQuery } from \"./query-resolver\";\n\nexport interface FolderMutationResult {\n affectedFolders: number;\n affectedFiles: number;\n}\n\nexport interface FolderTree {\n folders: FolderRecord[];\n}\n\n/**\n * Folder lifecycle (v0.2):\n *\n * - Folders are virtual OmniCloud objects with a parent-child hierarchy.\n * - Trashing a folder trashes its whole subtree (folders + files) without\n * touching Telegram, so restore is lossless.\n * - Restoring a folder restores only the nodes that were trashed together\n * with it; anything the user trashed separately stays in the Trash.\n * - Permanent deletion removes remote objects first, then metadata.\n */\nexport class FolderService {\n constructor(\n private readonly folders: FolderRepository,\n private readonly files: FileRepository,\n private readonly engineFor: EngineResolver,\n private readonly activity: ActivityRecorder = noopActivityRecorder,\n ) {}\n\n // ── Create / read ────────────────────────────────────────────────────────\n\n async create(\n userId: string,\n input: { name: string; parentId: string | null },\n ): Promise<FolderRecord> {\n const name = sanitizeFileName(input.name);\n if (!name) throw new ValidationError(\"Invalid folder name\");\n if (input.parentId !== null) {\n const parent = await this.get(userId, input.parentId);\n if (parent.deletedAt) throw new ConflictError(\"The parent folder is in the Trash\");\n }\n const folder = await this.folders.create({ userId, parentId: input.parentId, name });\n await this.activity.record({\n userId,\n action: \"create_folder\",\n resourceType: \"folder\",\n resourceId: folder.id,\n resourceName: folder.name,\n });\n return folder;\n }\n\n /** Active folders directly inside a folder (null = root). */\n async listChildren(userId: string, parentId: string | null): Promise<FolderRecord[]> {\n if (parentId !== null) await this.get(userId, parentId);\n return this.folders.listChildren(userId, parentId);\n }\n\n async query(userId: string, input: ListQuery, page: PageRequest): Promise<Paged<FolderRecord>> {\n const query: ItemQuery = await resolveItemQuery(this.folders, userId, input);\n return this.folders.query(userId, query, page);\n }\n\n /** Every active folder for the user (used to build trees). */\n async tree(userId: string): Promise<FolderRecord[]> {\n const all = await this.folders.listByUser(userId);\n return all.filter((folder) => folder.deletedAt === null);\n }\n\n async get(userId: string, id: string): Promise<FolderRecord> {\n const record = await this.folders.findById(id);\n if (!record || record.userId !== userId) throw new NotFoundError(\"Folder not found\", \"folder\");\n return record;\n }\n\n // ── Mutations ────────────────────────────────────────────────────────────\n\n async rename(userId: string, id: string, name: string): Promise<FolderRecord> {\n const existing = await this.get(userId, id);\n const clean = sanitizeFileName(name);\n if (!clean) throw new ValidationError(\"Invalid folder name\");\n const updated = await this.folders.update(id, { name: clean });\n await this.activity.record({\n userId,\n action: \"rename\",\n resourceType: \"folder\",\n resourceId: id,\n resourceName: clean,\n metadata: { previousName: existing.name },\n });\n return updated;\n }\n\n async move(userId: string, id: string, newParentId: string | null): Promise<FolderRecord> {\n const existing = await this.get(userId, id);\n\n if (newParentId === id) throw new ValidationError(\"Cannot move a folder into itself\");\n\n if (newParentId !== null) {\n const target = await this.get(userId, newParentId);\n if (target.deletedAt) throw new ConflictError(\"The target folder is in the Trash\");\n\n // Walk up from the new parent; hitting the folder being moved means it\n // would become its own ancestor.\n let cursor: string | null = newParentId;\n while (cursor !== null) {\n if (cursor === id) {\n throw new ValidationError(\"Cannot move a folder into one of its subfolders\");\n }\n const parent = await this.folders.findById(cursor);\n if (!parent || parent.userId !== userId)\n throw new NotFoundError(\"Folder not found\", \"folder\");\n cursor = parent.parentId;\n }\n }\n\n // The repository re-checks the ancestor chain and writes atomically, so a\n // concurrent move cannot slip a cycle in between the check above and the write.\n const updated = await this.folders.moveSafely(id, newParentId);\n await this.activity.record({\n userId,\n action: \"move\",\n resourceType: \"folder\",\n resourceId: id,\n resourceName: updated.name,\n metadata: { from: existing.parentId, to: newParentId },\n });\n return updated;\n }\n\n async setStarred(userId: string, id: string, starred: boolean): Promise<FolderRecord> {\n await this.get(userId, id);\n const updated = await this.folders.update(id, { starred });\n await this.activity.record({\n userId,\n action: starred ? \"star\" : \"unstar\",\n resourceType: \"folder\",\n resourceId: id,\n resourceName: updated.name,\n });\n return updated;\n }\n\n // ── Trash lifecycle ──────────────────────────────────────────────────────\n\n /**\n * Moves a folder and its entire subtree to the Trash. Nothing is deleted\n * from Telegram, so restore is lossless.\n */\n async trash(userId: string, id: string): Promise<FolderMutationResult> {\n const record = await this.get(userId, id);\n if (record.deletedAt) {\n return { affectedFolders: 1, affectedFiles: 0 };\n }\n\n const subtree = await this.collectSubtree(userId, id);\n const fileIds = await this.collectFileIds(userId, subtree);\n\n // One batch id stamps everything this operation trashes, so restore can\n // bring back exactly this set without relying on timestamp equality.\n const batchId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n const stampedAt = new Date();\n // One atomic write: a failure cannot trash the folders and leave their files active.\n await this.folders.trashSubtree(\n subtree.map((folder) => folder.id),\n fileIds,\n { deletedAt: stampedAt, trashBatchId: batchId },\n );\n\n await this.activity.record({\n userId,\n action: \"trash\",\n resourceType: \"folder\",\n resourceId: id,\n resourceName: record.name,\n metadata: { folders: subtree.length, files: fileIds.length },\n });\n\n return { affectedFolders: subtree.length, affectedFiles: fileIds.length };\n }\n\n /**\n * Restores a folder subtree. Only nodes trashed at the same instant as the\n * folder itself are restored, so items the user trashed separately stay put.\n */\n async restore(userId: string, id: string): Promise<FolderMutationResult> {\n const record = await this.get(userId, id);\n\n // If the parent chain is still trashed, restore to the root instead of\n // creating a folder the user cannot see.\n let parentId = record.parentId;\n if (parentId !== null) {\n const parent = await this.folders.findById(parentId);\n if (!parent || parent.userId !== userId || parent.deletedAt) parentId = null;\n }\n\n const subtree = await this.collectSubtree(userId, id, { includeTrashed: true });\n const batchId = record.trashBatchId;\n\n // Prefer the batch marker; fall back to timestamp equality for rows\n // created before the marker existed (v0.2 upgrade path).\n const belongsToBatch = (deletedAt: Date | null, marker: string | null): boolean =>\n deletedAt !== null &&\n (batchId !== null\n ? marker === batchId\n : record.deletedAt !== null && deletedAt.getTime() === record.deletedAt.getTime());\n\n const restorableFolders = subtree.filter((folder) =>\n belongsToBatch(folder.deletedAt, folder.trashBatchId),\n );\n const restorableFolderIds = new Set(restorableFolders.map((folder) => folder.id));\n\n const allFiles = await this.files.listByUser(userId);\n const restorableFiles = allFiles.filter(\n (file) =>\n file.folderId !== null &&\n restorableFolderIds.has(file.folderId) &&\n belongsToBatch(file.deletedAt, file.trashBatchId),\n );\n\n const restorableFileIds = restorableFiles.map((file) => file.id);\n // One atomic write: a failure cannot restore the folders and leave their files in Trash.\n if (restorableFolders.length > 0) {\n await this.folders.restoreSubtree(\n restorableFolders.map((folder) => folder.id),\n restorableFileIds,\n { rootId: id, reparent: { parentId } },\n );\n }\n\n await this.activity.record({\n userId,\n action: \"restore\",\n resourceType: \"folder\",\n resourceId: id,\n resourceName: record.name,\n metadata: { folders: restorableFolders.length, files: restorableFiles.length },\n });\n\n return { affectedFolders: restorableFolders.length, affectedFiles: restorableFiles.length };\n }\n\n /**\n * Permanently deletes a folder subtree: remote objects first, then\n * metadata. Files whose remote delete fails are left in the Trash so the\n * user can retry rather than losing data silently.\n */\n async deletePermanently(userId: string, id: string): Promise<FolderMutationResult> {\n await this.get(userId, id);\n\n const subtree = await this.collectSubtree(userId, id, { includeTrashed: true });\n const folderIds = subtree.map((folder) => folder.id);\n\n const allFiles = await this.files.listByUser(userId);\n const affected = allFiles.filter(\n (file) => file.folderId !== null && folderIds.includes(file.folderId),\n );\n\n const engine = await this.engineFor(userId);\n const removedFileIds: string[] = [];\n // Every Telegram object a file owns, current and historical, must go before its\n // metadata: the version rows are the only record of the older messages.\n const versionsByFile = await this.files.listVersionsForFiles(affected.map((file) => file.id));\n for (const file of affected) {\n try {\n const messageIds = new Set<string>([String(file.telegramMessageId)]);\n for (const version of versionsByFile.get(file.id) ?? []) {\n messageIds.add(String(version.telegramMessageId));\n }\n for (const messageId of messageIds) {\n await engine.remove({ messageId });\n }\n removedFileIds.push(file.id);\n } catch {\n // Keep the metadata so the operation can be retried.\n }\n }\n\n if (removedFileIds.length > 0) {\n await this.files.deleteVersionsByFileIds(removedFileIds);\n await this.files.deleteMany(removedFileIds);\n }\n\n // Only delete folders whose files are all gone, plus empty leaves.\n const remainingFolderIds = new Set(\n affected.filter((file) => !removedFileIds.includes(file.id)).map((file) => file.folderId),\n );\n const deletable = deepestFirst(\n folderIds.filter((folderId) => !remainingFolderIds.has(folderId)),\n subtree,\n );\n if (deletable.length > 0) {\n await this.folders.deleteMany(deletable);\n }\n\n await this.activity.record({\n userId,\n action: \"delete\",\n resourceType: \"folder\",\n resourceId: id,\n resourceName: subtree[0]?.name ?? \"folder\",\n metadata: { folders: deletable.length, files: removedFileIds.length },\n });\n\n return { affectedFolders: deletable.length, affectedFiles: removedFileIds.length };\n }\n\n /**\n * v0.1-compatible recursive delete. Now routes through the v0.2 permanent\n * delete so remote cleanup and orphan prevention are consistent.\n */\n async delete(userId: string, id: string): Promise<FolderMutationResult> {\n return this.deletePermanently(userId, id);\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n /** The folder plus every descendant (BFS), optionally including trashed. */\n private async collectSubtree(\n userId: string,\n rootId: string,\n options: { includeTrashed?: boolean } = {},\n ): Promise<FolderRecord[]> {\n const all = await this.folders.listByUser(userId);\n const childrenOf = new Map<string | null, FolderRecord[]>();\n for (const folder of all) {\n const list = childrenOf.get(folder.parentId) ?? [];\n list.push(folder);\n childrenOf.set(folder.parentId, list);\n }\n\n const root = all.find((folder) => folder.id === rootId);\n if (!root) throw new NotFoundError(\"Folder not found\", \"folder\");\n\n const collected: FolderRecord[] = [root];\n const queue: string[] = [rootId];\n while (queue.length > 0) {\n const current = queue.shift()!;\n for (const child of childrenOf.get(current) ?? []) {\n if (!options.includeTrashed && child.deletedAt) continue;\n collected.push(child);\n queue.push(child.id);\n }\n }\n return collected;\n }\n\n /** Ids of files inside the given folders that are not already trashed. */\n private async collectFileIds(userId: string, folders: FolderRecord[]): Promise<string[]> {\n const ids = new Set(folders.map((folder) => folder.id));\n const allFiles = await this.files.listByUser(userId);\n return allFiles\n .filter((file) => file.deletedAt === null && file.folderId !== null && ids.has(file.folderId))\n .map((file) => file.id);\n }\n}\n","import { paginationMeta, parseSearchQuery } from \"@omnicloud/shared\";\nimport type { ListQuery, PaginationDTO, SearchResultDTO } from \"@omnicloud/shared\";\nimport { ValidationError } from \"../errors\";\nimport type { FileRecord, FolderRecord, PageRequest } from \"../types\";\nimport type { FileRepository, FolderRepository } from \"../repos\";\nimport { resolveItemQuery } from \"./query-resolver\";\n\nconst MAX_QUERY_LENGTH = 200;\n\nexport interface SearchResult {\n files: FileRecord[];\n folders: FolderRecord[];\n pagination: PaginationDTO;\n}\n\n/**\n * PostgreSQL-backed search (no Elasticsearch, per the v0.2 scope).\n *\n * The user query is parsed into structured filters (`type:`, `size:`,\n * `folder:`, …) and evaluated by the repository layer, so results respect\n * ownership and stay safely parameterized.\n */\nexport class SearchService {\n constructor(\n private readonly files: FileRepository,\n private readonly folders: FolderRepository,\n ) {}\n\n async search(\n userId: string,\n rawQuery: string,\n page: PageRequest,\n overrides: ListQuery = {},\n ): Promise<SearchResult> {\n const trimmed = rawQuery.trim();\n if (!trimmed) throw new ValidationError(\"Search query must not be empty\");\n if (trimmed.length > MAX_QUERY_LENGTH) {\n throw new ValidationError(`Search query must be at most ${MAX_QUERY_LENGTH} characters`);\n }\n\n const { query } = parseSearchQuery(trimmed, overrides);\n // Search covers both active and trashed items unless the caller narrows it.\n if (query.status === undefined) query.status = \"all\";\n\n const itemQuery = await resolveItemQuery(this.folders, userId, query);\n\n const [filePage, folderPage] = await Promise.all([\n this.files.query(userId, itemQuery, page),\n this.folders.query(userId, itemQuery, page),\n ]);\n\n // The pagination envelope describes the combined result count so the UI\n // can render a single \"load more\" affordance.\n const total = filePage.total + folderPage.total;\n return {\n files: filePage.items,\n folders: folderPage.items,\n pagination: paginationMeta(page.page, page.limit, total),\n };\n }\n\n /** Convenience wrapper returning the shared DTO shape. */\n async searchDTO(\n userId: string,\n rawQuery: string,\n page: PageRequest,\n ): Promise<Omit<SearchResultDTO, \"files\" | \"folders\"> & SearchResult> {\n return this.search(userId, rawQuery, page);\n }\n}\n","import { fileCategory } from \"@omnicloud/shared\";\nimport type { StorageStats } from \"../types\";\nimport type { FileRepository, FolderRepository } from \"../repos\";\n\nconst LARGEST_FILES = 10;\n\n/**\n * Storage dashboard statistics, computed from PostgreSQL metadata only —\n * no Telegram round-trips, so the page is fast and works offline.\n */\nexport class StatsService {\n constructor(\n private readonly files: FileRepository,\n private readonly folders: FolderRepository,\n ) {}\n\n async stats(userId: string, quotaBytes: number | null = null): Promise<StorageStats> {\n const [counters, allFiles] = await Promise.all([\n this.files.statsByUser(userId),\n // Metadata-only aggregation; v0.2 datasets fit comfortably in memory.\n this.files.listByUser(userId),\n ]);\n\n const active = allFiles.filter((file) => file.deletedAt === null);\n const categoryBytes = new Map<string, { bytes: number; count: number }>();\n\n for (const file of active) {\n const category = fileCategory(file.mimeType);\n const bucket = categoryBytes.get(category) ?? { bytes: 0, count: 0 };\n bucket.bytes += file.size;\n bucket.count += 1;\n categoryBytes.set(category, bucket);\n }\n\n const byType = [...categoryBytes.entries()]\n .map(([category, bucket]) => ({ category, bytes: bucket.bytes, count: bucket.count }))\n .sort((a, b) => b.bytes - a.bytes);\n\n const largestFiles = [...active].sort((a, b) => b.size - a.size).slice(0, LARGEST_FILES);\n\n return {\n fileCount: counters.fileCount,\n folderCount: counters.folderCount,\n totalBytes: counters.totalBytes,\n trashBytes: counters.trashBytes,\n trashFileCount: counters.trashFileCount,\n starredCount: counters.starredCount,\n byType,\n largestFiles,\n quotaBytes,\n };\n }\n}\n","import { deepestFirst } from \"../utils/folder-order\";\nimport { paginationMeta } from \"@omnicloud/shared\";\nimport type { ListQuery, PaginationDTO } from \"@omnicloud/shared\";\nimport type { FileRecord, FolderRecord, PageRequest } from \"../types\";\nimport type { FileRepository, FolderRepository } from \"../repos\";\nimport { NotFoundError } from \"../errors\";\nimport type { EngineResolver } from \"./file-service\";\nimport { resolveItemQuery } from \"./query-resolver\";\n\nexport interface TrashListing {\n files: FileRecord[];\n folders: FolderRecord[];\n pagination: PaginationDTO;\n}\n\nexport interface EmptyTrashResult {\n deletedFiles: number;\n deletedFolders: number;\n failedFiles: number;\n}\n\n/**\n * Trash is a virtual view over soft-deleted rows. Listing shows only the\n * explicitly trashed items; emptying removes their remote objects before the\n * metadata, and reports failures instead of hiding them.\n */\nexport class TrashService {\n constructor(\n private readonly files: FileRepository,\n private readonly folders: FolderRepository,\n private readonly engineFor: EngineResolver,\n ) {}\n\n async list(userId: string, page: PageRequest, extra: ListQuery = {}): Promise<TrashListing> {\n const itemQuery = await resolveItemQuery(this.folders, userId, {\n ...extra,\n status: \"trashed\",\n folderId: undefined,\n });\n\n const [filePage, folderPage] = await Promise.all([\n this.files.query(userId, itemQuery, page),\n this.folders.query(userId, itemQuery, page),\n ]);\n\n const total = filePage.total + folderPage.total;\n return {\n files: filePage.items,\n folders: folderPage.items,\n pagination: paginationMeta(page.page, page.limit, total),\n };\n }\n\n /**\n * Permanently removes everything in the Trash for a user. Folders are\n * processed root-first so removing a parent also removes its contents.\n */\n async empty(userId: string): Promise<EmptyTrashResult> {\n const engine = await this.engineFor(userId);\n const allFiles = await this.files.listByUser(userId);\n const trashedFiles = allFiles.filter((file) => file.deletedAt !== null);\n\n let deletedFiles = 0;\n let failedFiles = 0;\n const deletedFileIds: string[] = [];\n\n for (const file of trashedFiles) {\n try {\n await engine.remove({ messageId: String(file.telegramMessageId) });\n deletedFileIds.push(file.id);\n deletedFiles += 1;\n } catch {\n failedFiles += 1;\n }\n }\n\n if (deletedFileIds.length > 0) {\n await this.files.deleteVersionsByFileIds(deletedFileIds);\n await this.files.deleteMany(deletedFileIds);\n }\n\n // Remove trashed folders, shallowest first, and only those with no\n // remaining files inside.\n const allFolders = await this.folders.listByUser(userId);\n const trashedFolders = allFolders.filter((folder) => folder.deletedAt !== null);\n const orderedIds = deepestFirst(\n trashedFolders.map((folder) => folder.id),\n allFolders,\n );\n\n let deletedFolders = 0;\n for (const folderId of orderedIds) {\n const folder = trashedFolders.find((candidate) => candidate.id === folderId)!;\n // Skip folders that still contain files we could not delete.\n const stillHasFiles = await this.files.query(\n userId,\n { folderId: folder.id, trashed: true },\n { page: 1, limit: 1 },\n );\n if (stillHasFiles.total > 0) continue;\n await this.folders.deleteMany([folder.id]);\n deletedFolders += 1;\n }\n\n return { deletedFiles, deletedFolders, failedFiles };\n }\n\n /** Restores a single trashed item (used by the batch restore endpoint). */\n async verifyTrashedFile(userId: string, id: string): Promise<FileRecord> {\n const file = await this.files.findById(id);\n if (!file || file.userId !== userId || file.deletedAt === null) {\n throw new NotFoundError(\"File is not in the Trash\", \"file\");\n }\n return file;\n }\n}\n","import type { IntegrityIssueDTO, IntegrityReportDTO } from \"@omnicloud/shared\";\nimport type { FileRecord } from \"../types\";\nimport type { FileRepository } from \"../repos\";\nimport type { EngineResolver } from \"./file-service\";\nimport { IntegrityCheckError } from \"../errors\";\n\nexport interface IntegrityCheckOptions {\n /** Re-download and re-hash objects (slow but authoritative). */\n deep?: boolean;\n /** Maximum files to inspect in one pass. */\n limit?: number;\n}\n\n/**\n * Detects drift between PostgreSQL metadata and Telegram storage:\n *\n * - `missing` — metadata exists, the Telegram message is gone\n * - `size_mismatch` — the stored object's size differs from the metadata\n * - `hash_mismatch` — deep mode only: downloaded bytes don't match sha256\n * - `unreadable` — the provider errored while inspecting the object\n *\n * The checker is strictly read-only: repairs are a separate, explicit step so\n * nothing is ever deleted silently (see the v0.2 reliability rules).\n */\nexport class IntegrityService {\n constructor(\n private readonly files: FileRepository,\n private readonly engineFor: EngineResolver,\n ) {}\n\n async check(userId: string, options: IntegrityCheckOptions = {}): Promise<IntegrityReportDTO> {\n const startedAt = Date.now();\n const all = await this.files.listByUser(userId);\n const active = all.filter((file) => file.deletedAt === null);\n const targets = options.limit ? active.slice(0, options.limit) : active;\n\n const engine = await this.engineFor(userId);\n const issues: IntegrityIssueDTO[] = [];\n let healthy = 0;\n const versionsByFile = await this.files.listVersionsForFiles(targets.map((file) => file.id));\n\n for (const file of targets) {\n const issue = await this.inspect(file, options.deep ?? false, engine);\n if (issue) issues.push(issue);\n else healthy += 1;\n\n for (const version of versionsByFile.get(file.id) ?? []) {\n if (version.telegramMessageId === file.telegramMessageId) continue;\n const versionIssue = await this.inspectObject(\n {\n id: file.id,\n name: file.name,\n telegramMessageId: version.telegramMessageId,\n size: version.size,\n sha256: version.sha256,\n },\n options.deep ?? false,\n engine,\n version.id,\n );\n if (versionIssue) issues.push(versionIssue);\n else healthy += 1;\n }\n }\n\n const missing = issues.filter((issue) => issue.kind === \"missing\").length;\n const inconsistent = issues.filter(\n (issue) => issue.kind === \"size_mismatch\" || issue.kind === \"hash_mismatch\",\n ).length;\n const unreadable = issues.filter((issue) => issue.kind === \"unreadable\").length;\n\n return {\n checkedAt: new Date().toISOString(),\n filesChecked: targets.length,\n healthy,\n missing,\n inconsistent,\n unreadable,\n issues,\n durationMs: Date.now() - startedAt,\n };\n }\n\n private async inspect(\n file: FileRecord,\n deep: boolean,\n engine: Awaited<ReturnType<EngineResolver>>,\n ): Promise<IntegrityIssueDTO | null> {\n return this.inspectObject(\n {\n id: file.id,\n name: file.name,\n telegramMessageId: file.telegramMessageId,\n size: file.size,\n sha256: file.sha256,\n },\n deep,\n engine,\n undefined,\n );\n }\n\n private async inspectObject(\n target: {\n id: string;\n name: string;\n telegramMessageId: number;\n size: number;\n sha256: string;\n },\n deep: boolean,\n engine: Awaited<ReturnType<EngineResolver>>,\n versionId: string | undefined,\n ): Promise<IntegrityIssueDTO | null> {\n const ref = { messageId: String(target.telegramMessageId) };\n const withVersion = (issue: IntegrityIssueDTO): IntegrityIssueDTO =>\n versionId ? { ...issue, versionId } : issue;\n\n let stat: Awaited<ReturnType<typeof engine.stat>>;\n try {\n stat = await engine.stat(ref);\n } catch (error) {\n return withVersion({\n fileId: target.id,\n name: target.name,\n kind: \"unreadable\",\n detail: error instanceof Error ? error.message : \"Storage lookup failed\",\n });\n }\n\n if (!stat) {\n return withVersion({\n fileId: target.id,\n name: target.name,\n kind: \"missing\",\n detail: \"No message with this id exists in the storage channel\",\n });\n }\n\n if (stat.size !== target.size) {\n return withVersion({\n fileId: target.id,\n name: target.name,\n kind: \"size_mismatch\",\n detail: `metadata=${target.size} storage=${stat.size}`,\n });\n }\n\n if (deep) {\n try {\n // Streamed and hashed without holding the object in memory. A checksum\n // mismatch surfaces as IntegrityCheckError from the stream.\n const stream = await engine.downloadStream(ref, target.sha256);\n for await (const _chunk of stream) {\n // Consumed only to drive the verification; the bytes are discarded.\n }\n } catch (error) {\n if (error instanceof IntegrityCheckError) {\n return withVersion({\n fileId: target.id,\n name: target.name,\n kind: \"hash_mismatch\",\n detail: \"Downloaded bytes do not match the stored SHA-256\",\n });\n }\n return withVersion({\n fileId: target.id,\n name: target.name,\n kind: \"unreadable\",\n detail: error instanceof Error ? error.message : \"Download failed\",\n });\n }\n }\n\n return null;\n }\n}\n","import { paginationMeta } from \"@omnicloud/shared\";\nimport type { ActivityAction, RecentItemDTO, RecentPageDTO } from \"@omnicloud/shared\";\nimport type { PageRequest } from \"../types\";\nimport type { ActivityRepository, FileRepository } from \"../repos\";\n\n/** Actions that count as meaningful \"recent\" activity for a file. */\nconst RECENT_ACTIONS: ActivityAction[] = [\n \"upload\",\n \"download\",\n \"open\",\n \"rename\",\n \"move\",\n \"replace\",\n];\n\n/**\n * Recent view: the most recently touched active files, newest activity first.\n * Trashed and permanently-deleted files are filtered out so the view never\n * surfaces items the user cannot open.\n */\nexport class RecentService {\n constructor(\n private readonly activity: ActivityRepository,\n private readonly files: FileRepository,\n ) {}\n\n async list(userId: string, page: PageRequest): Promise<RecentPageDTO> {\n // Pull a bounded window of recent *meaningful* events (the repository\n // dedups by file), then resolve the distinct files.\n const window = Math.min(page.page * page.limit * 3, 500);\n const events = await this.activity.recentFiles(userId, RECENT_ACTIONS, window);\n\n const seen = new Set<string>();\n for (const event of events) {\n seen.add(event.resourceId);\n }\n\n const fileIds = [...seen];\n const records = fileIds.length > 0 ? await this.files.listByIds(userId, fileIds) : [];\n const byId = new Map(records.map((record) => [record.id, record]));\n\n const items: RecentItemDTO[] = [];\n for (const fileId of fileIds) {\n const record = byId.get(fileId);\n if (!record || record.deletedAt !== null) continue;\n const event = events.find((candidate) => candidate.resourceId === fileId)!;\n items.push({\n file: {\n id: record.id,\n name: record.name,\n size: record.size,\n mimeType: record.mimeType,\n sha256: record.sha256,\n folderId: record.folderId,\n starred: record.starred,\n trashed: false,\n deletedAt: null,\n versionCount: record.versionCount,\n currentVersionId: record.currentVersionId,\n createdAt: record.createdAt.toISOString(),\n updatedAt: record.updatedAt.toISOString(),\n },\n lastAction: event.action,\n lastActionAt: event.createdAt.toISOString(),\n });\n }\n\n const start = (page.page - 1) * page.limit;\n const pageItems = items.slice(start, start + page.limit);\n\n return {\n items: pageItems,\n pagination: paginationMeta(page.page, page.limit, items.length),\n };\n }\n}\n","import { createHash } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { mkdtemp, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { Transform, type Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport { PayloadTooLargeError } from \"../errors\";\n\nexport interface SpooledFile {\n /** Path to the temporary copy. The caller must call `discard()` when done. */\n path: string;\n size: number;\n sha256: string;\n discard: () => Promise<void>;\n}\n\n/**\n * Writes an upload stream to a temporary file while computing its SHA-256 and\n * byte count. Memory use is bounded by the stream's chunk size, not the file\n * size. The size cap is enforced while writing, so an oversized upload is\n * rejected without being stored in full.\n */\nconst SPOOL_PREFIX = \"omnicloud-upload-\";\n\nexport async function spoolToFile(source: Readable, maxBytes: number): Promise<SpooledFile> {\n const dir = await mkdtemp(join(tmpdir(), `${SPOOL_PREFIX}${process.pid}-`));\n const path = join(dir, \"payload\");\n const discard = () => rm(dir, { recursive: true, force: true });\n\n const hash = createHash(\"sha256\");\n let size = 0;\n const meter = new Transform({\n transform(chunk: Buffer, _encoding, callback) {\n size += chunk.byteLength;\n if (size > maxBytes) {\n callback(\n new PayloadTooLargeError(\n `Files larger than ${Math.floor(maxBytes / (1024 * 1024))} MB are not supported`,\n ),\n );\n return;\n }\n hash.update(chunk);\n callback(null, chunk);\n },\n });\n\n try {\n await pipeline(source, meter, createWriteStream(path));\n } catch (error) {\n await discard();\n throw error;\n }\n\n return { path, size, sha256: hash.digest(\"hex\"), discard };\n}\n\n/** True when a process with this PID exists and is not just a stale record. */\nfunction isAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/**\n * Removes spool directories left by processes that are no longer running,\n * which happens when a process is killed mid-upload and `finally` cannot run.\n * Directories owned by a live process are never touched, so a concurrent\n * upload's bytes are not deleted.\n */\nexport async function sweepStaleSpools(): Promise<number> {\n const { readdir } = await import(\"node:fs/promises\");\n const entries = await readdir(tmpdir()).catch(() => [] as string[]);\n let removed = 0;\n for (const name of entries) {\n if (!name.startsWith(SPOOL_PREFIX)) continue;\n const pid = Number(name.slice(SPOOL_PREFIX.length).split(\"-\")[0]);\n if (!Number.isInteger(pid) || pid <= 0 || isAlive(pid)) continue;\n await rm(join(tmpdir(), name), { recursive: true, force: true });\n removed += 1;\n }\n return removed;\n}\n","import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from \"node:crypto\";\nimport { ValidationError } from \"../errors\";\n\n/**\n * Authenticated encryption for values stored at rest (Telegram session strings).\n *\n * AES-256-GCM with a fresh random 96-bit IV per seal. The stored envelope is\n * `v<keyVersion>:<iv>:<tag>:<ciphertext>` (base64url), so a key can be rotated\n * later by keeping old versions available for decryption.\n */\n\nconst ALGORITHM = \"aes-256-gcm\";\nconst IV_BYTES = 12;\nconst KEY_BYTES = 32;\n\nexport interface SecretBoxKey {\n version: number;\n key: Buffer;\n}\n\nexport class SecretBox {\n private readonly keys: Map<number, Buffer>;\n private readonly currentVersion: number;\n\n /**\n * @param keys key versions available for decryption; the highest version is\n * used for new seals.\n */\n constructor(keys: SecretBoxKey[]) {\n if (keys.length === 0) {\n throw new ValidationError(\"At least one encryption key is required\");\n }\n this.keys = new Map();\n for (const { version, key } of keys) {\n if (key.byteLength !== KEY_BYTES) {\n throw new ValidationError(`Encryption key version ${version} must be 32 bytes`);\n }\n this.keys.set(version, key);\n }\n this.currentVersion = Math.max(...this.keys.keys());\n }\n\n seal(plaintext: string): string {\n const key = this.keys.get(this.currentVersion)!;\n const iv = randomBytes(IV_BYTES);\n const cipher = createCipheriv(ALGORITHM, key, iv);\n const ciphertext = Buffer.concat([cipher.update(plaintext, \"utf8\"), cipher.final()]);\n const tag = cipher.getAuthTag();\n return [\n `v${this.currentVersion}`,\n iv.toString(\"base64url\"),\n tag.toString(\"base64url\"),\n ciphertext.toString(\"base64url\"),\n ].join(\":\");\n }\n\n open(envelope: string): string {\n const parts = envelope.split(\":\");\n if (parts.length !== 4 || !parts[0]!.startsWith(\"v\")) {\n throw new SecretBoxError(\"Malformed sealed value\");\n }\n const version = Number(parts[0]!.slice(1));\n const key = this.keys.get(version);\n if (!key) throw new SecretBoxError(`No encryption key for version ${version}`);\n\n try {\n const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(parts[1]!, \"base64url\"));\n decipher.setAuthTag(Buffer.from(parts[2]!, \"base64url\"));\n const plaintext = Buffer.concat([\n decipher.update(Buffer.from(parts[3]!, \"base64url\")),\n decipher.final(),\n ]);\n return plaintext.toString(\"utf8\");\n } catch {\n throw new SecretBoxError(\"Sealed value failed authentication\");\n }\n }\n\n get currentKeyVersion(): number {\n return this.currentVersion;\n }\n}\n\n/** Raised for corrupt, tampered, or undecryptable values. Never includes the value itself. */\nexport class SecretBoxError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SecretBoxError\";\n }\n}\n\n/** Fixed salt: the same secret must derive the same key on every start. */\nconst KDF_SALT = \"omnicloud-secret-box-v1\";\n\n/**\n * Derives a 256-bit key from an operator-supplied secret. Accepts either a\n * 64-character hex string (preferred: `openssl rand -hex 32`, used as-is) or a\n * passphrase of at least 32 characters, stretched with scrypt so a weak\n * passphrase is expensive to brute-force.\n */\nexport function deriveKey(secret: string): Buffer {\n if (/^[0-9a-f]{64}$/i.test(secret)) return Buffer.from(secret, \"hex\");\n if (secret.length < 32) {\n throw new ValidationError(\n \"The encryption key must be 64 hex characters or at least 32 characters\",\n );\n }\n return scryptSync(secret, KDF_SALT, KEY_BYTES);\n}\n","import { Api, TelegramClient } from \"telegram\";\nimport { StringSession } from \"telegram/sessions/index.js\";\nimport {\n StorageUnavailableError,\n TelegramAuthRequiredError,\n TelegramConnectionError,\n UnauthorizedError,\n} from \"@omnicloud/core\";\nimport type { ConnectionState, SessionRepository, StorageHealth } from \"@omnicloud/core\";\nimport { mapTelegramError } from \"./errors\";\n\nexport interface TelegramCredentials {\n apiId: number;\n apiHash: string;\n}\n\ninterface ManagedConnection {\n client: TelegramClient;\n state: ConnectionState;\n /** In-flight connect/reconnect promise, so callers can await it. */\n pending: Promise<TelegramClient> | null;\n lastError: string | null;\n lastCheckAt: number | null;\n lastCheckOk: boolean | null;\n}\n\nexport interface ConnectionStatus {\n state: ConnectionState;\n lastError: string | null;\n lastCheckAt: Date | null;\n healthy: boolean | null;\n}\n\n/** How long a health probe is cached before re-probing. */\nconst HEALTH_TTL_MS = 30_000;\n/** Minimum gap between reconnect attempts for the same user. */\nconst RECONNECT_COOLDOWN_MS = 5_000;\n\n/**\n * Owns one Telegram (MTProto) connection per authenticated user and tracks an\n * explicit lifecycle state for each:\n *\n * DISCONNECTED → CONNECTING → CONNECTED\n * ↘ RECONNECTING → CONNECTED\n * ↘ ERROR | AUTH_REQUIRED\n *\n * Responsibilities: connect, disconnect, reconnect, validateSession,\n * healthCheck, refreshConnection. Sessions never leave the server.\n */\nexport class TelegramClientManager {\n private connections = new Map<string, ManagedConnection>();\n private lastReconnectAt = new Map<string, number>();\n\n constructor(\n private readonly credentials: TelegramCredentials,\n private readonly sessions: SessionRepository,\n ) {}\n\n /** Returns a connected client, (re)connecting when needed. */\n async getForUser(userId: string): Promise<TelegramClient> {\n const existing = this.connections.get(userId);\n\n if (existing) {\n if (existing.state === \"CONNECTED\" && this.isConnected(existing.client)) {\n return existing.client;\n }\n if (existing.pending) return existing.pending;\n if (existing.state === \"AUTH_REQUIRED\") {\n throw new TelegramAuthRequiredError();\n }\n return this.reconnect(userId, existing);\n }\n\n return this.connectUser(userId);\n }\n\n /** Connection status for health reporting and the storage dashboard. */\n statusForUser(userId: string): ConnectionStatus {\n const connection = this.connections.get(userId);\n if (!connection) {\n return { state: \"DISCONNECTED\", lastError: null, lastCheckAt: null, healthy: null };\n }\n return {\n state: connection.state,\n lastError: connection.lastError,\n lastCheckAt: connection.lastCheckAt ? new Date(connection.lastCheckAt) : null,\n healthy: connection.lastCheckOk,\n };\n }\n\n /**\n * Probes a user's connection. Cheap by default (connection state only);\n * `deep` performs a real Telegram round-trip to validate the session.\n */\n async healthCheck(userId: string, deep = false): Promise<StorageHealth> {\n const connection = this.connections.get(userId);\n\n if (!connection) {\n try {\n await this.getForUser(userId);\n } catch (error) {\n const state: ConnectionState =\n error instanceof TelegramAuthRequiredError ? \"AUTH_REQUIRED\" : \"ERROR\";\n const current = this.connections.get(userId);\n if (current) current.state = state;\n return {\n healthy: false,\n latencyMs: null,\n message: error instanceof Error ? error.message : \"Telegram is not connected\",\n targetTitle: null,\n };\n }\n }\n\n const active = this.connections.get(userId)!;\n\n if (!deep && active.lastCheckAt && Date.now() - active.lastCheckAt < HEALTH_TTL_MS) {\n return {\n healthy: active.lastCheckOk ?? false,\n latencyMs: null,\n message: active.lastError,\n targetTitle: null,\n };\n }\n\n const startedAt = Date.now();\n try {\n await active.client.getMe();\n active.lastCheckAt = Date.now();\n active.lastCheckOk = true;\n active.lastError = null;\n active.state = \"CONNECTED\";\n return {\n healthy: true,\n latencyMs: Date.now() - startedAt,\n message: null,\n targetTitle: null,\n };\n } catch (error) {\n const mapped = mapTelegramError(error, \"Telegram health check failed\");\n active.lastCheckAt = Date.now();\n active.lastCheckOk = false;\n active.lastError = mapped.message;\n active.state =\n mapped instanceof TelegramAuthRequiredError || mapped instanceof UnauthorizedError\n ? \"AUTH_REQUIRED\"\n : \"ERROR\";\n return {\n healthy: false,\n latencyMs: Date.now() - startedAt,\n message: mapped.message,\n targetTitle: null,\n };\n }\n }\n\n /** Registers an already-authenticated client (from the login flow). */\n cacheClient(userId: string, client: TelegramClient): void {\n this.connections.set(userId, {\n client,\n state: \"CONNECTED\",\n pending: null,\n lastError: null,\n lastCheckAt: Date.now(),\n lastCheckOk: true,\n });\n }\n\n /** Drops a user's connection (e.g. after logout) without touching storage. */\n async disconnectUser(userId: string): Promise<void> {\n const connection = this.connections.get(userId);\n if (!connection) return;\n this.connections.delete(userId);\n try {\n await connection.client.disconnect();\n } catch {\n // best effort\n }\n }\n\n async disconnectAll(): Promise<void> {\n const ids = [...this.connections.keys()];\n await Promise.all(ids.map((userId) => this.disconnectUser(userId)));\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private isConnected(client: TelegramClient): boolean {\n try {\n return client.connected === true;\n } catch {\n return false;\n }\n }\n\n private async connectUser(userId: string): Promise<TelegramClient> {\n const session = await this.sessions.get(userId);\n if (!session) {\n throw new TelegramAuthRequiredError(\"Telegram account is not connected\");\n }\n\n const connection: ManagedConnection = {\n client: null as unknown as TelegramClient,\n state: \"CONNECTING\",\n pending: null,\n lastError: null,\n lastCheckAt: null,\n lastCheckOk: null,\n };\n\n const attempt = this.establish(session.stringSession).then((client) => {\n connection.client = client;\n connection.state = \"CONNECTED\";\n connection.pending = null;\n connection.lastError = null;\n return client;\n });\n connection.pending = attempt;\n this.connections.set(userId, connection);\n\n try {\n return await attempt;\n } catch (error) {\n connection.pending = null;\n const mapped = mapTelegramError(error, \"Could not connect to Telegram\");\n connection.state =\n mapped instanceof TelegramAuthRequiredError || mapped instanceof UnauthorizedError\n ? \"AUTH_REQUIRED\"\n : \"ERROR\";\n connection.lastError = mapped.message;\n throw mapped;\n }\n }\n\n private async reconnect(userId: string, connection: ManagedConnection): Promise<TelegramClient> {\n const lastAttempt = this.lastReconnectAt.get(userId) ?? 0;\n if (Date.now() - lastAttempt < RECONNECT_COOLDOWN_MS && connection.pending) {\n return connection.pending;\n }\n this.lastReconnectAt.set(userId, Date.now());\n\n connection.state = \"RECONNECTING\";\n const session = await this.sessions.get(userId);\n if (!session) {\n connection.state = \"AUTH_REQUIRED\";\n throw new TelegramAuthRequiredError(\"Telegram account is not connected\");\n }\n\n const attempt = (async () => {\n try {\n await connection.client.disconnect().catch(() => undefined);\n } catch {\n // ignore\n }\n return this.establish(session.stringSession);\n })()\n .then((client) => {\n connection.client = client;\n connection.state = \"CONNECTED\";\n connection.pending = null;\n connection.lastError = null;\n return client;\n })\n .catch((error: unknown) => {\n connection.pending = null;\n const mapped = mapTelegramError(error, \"Could not reconnect to Telegram\");\n connection.state =\n mapped instanceof TelegramAuthRequiredError || mapped instanceof UnauthorizedError\n ? \"AUTH_REQUIRED\"\n : \"ERROR\";\n connection.lastError = mapped.message;\n throw mapped;\n });\n\n connection.pending = attempt;\n return attempt;\n }\n\n private async establish(sessionString: string): Promise<TelegramClient> {\n const client = new TelegramClient(\n new StringSession(sessionString),\n this.credentials.apiId,\n this.credentials.apiHash,\n { connectionRetries: 3 },\n );\n try {\n await client.connect();\n // Validate the session immediately so expired sessions surface as\n // AUTH_REQUIRED instead of a generic failure on the first operation.\n if (!client.connected) {\n throw new TelegramConnectionError(\"Telegram client did not connect\");\n }\n return client;\n } catch (error) {\n throw mapTelegramError(error, \"Could not connect to Telegram\");\n }\n }\n}\n\nexport function newTelegramClient(credentials: TelegramCredentials): TelegramClient {\n return new TelegramClient(new StringSession(\"\"), credentials.apiId, credentials.apiHash, {\n connectionRetries: 3,\n });\n}\n\n/**\n * Extracts the freshly created broadcast channel from a\n * channels.CreateChannel response.\n */\nexport function extractCreatedChannel(result: Api.TypeUpdates): Api.Channel {\n const chats =\n result instanceof Api.Updates || result instanceof Api.UpdatesCombined ? result.chats : [];\n for (const chat of chats) {\n if (chat instanceof Api.Channel) return chat;\n }\n throw new TelegramConnectionError(\"Telegram did not return the created storage channel\");\n}\n\nexport { StorageUnavailableError };\n","import { RPCError } from \"telegram/errors/index.js\";\nimport {\n DomainError,\n ForbiddenError,\n RateLimitedError,\n StorageNotInitializedError,\n TelegramAuthRequiredError,\n TelegramConnectionError,\n TelegramFileNotFoundError,\n UnauthorizedError,\n ValidationError,\n} from \"@omnicloud/core\";\n\nexport function isTelegramRpcError(error: unknown): error is RPCError {\n return error instanceof RPCError;\n}\n\n/** True when the error signals that 2FA password auth is required. */\nexport function isPasswordRequiredError(error: unknown): boolean {\n return isTelegramRpcError(error) && error.errorMessage === \"SESSION_PASSWORD_NEEDED\";\n}\n\n/**\n * Maps a Telegram (GramJS) failure to a structured domain error with an\n * appropriate HTTP status and a client-facing message that never leaks\n * credentials or session material.\n */\nexport function mapTelegramError(error: unknown, fallbackMessage: string): DomainError {\n if (error instanceof DomainError) return error;\n\n if (isTelegramRpcError(error)) {\n const msg = error.errorMessage ?? \"\";\n\n // GramJS reports a flood wait as errorMessage \"FLOOD\" and carries the server's\n // delay in `seconds`, so read that first. The prefix check only covers raw text.\n const seconds = (error as unknown as { seconds?: unknown }).seconds;\n const floodSeconds =\n typeof seconds === \"number\"\n ? seconds\n : msg.startsWith(\"FLOOD_WAIT_\")\n ? Number(msg.slice(\"FLOOD_WAIT_\".length))\n : null;\n if (msg.startsWith(\"FLOOD\") && floodSeconds !== null && floodSeconds > 0) {\n return new RateLimitedError(\n `Telegram rate limit reached — retry in ${floodSeconds} seconds`,\n {\n retryAfterSeconds: floodSeconds,\n },\n );\n }\n\n switch (msg) {\n case \"PHONE_NUMBER_INVALID\":\n return new ValidationError(\"This phone number is not valid\");\n case \"PHONE_NUMBER_UNOCCUPIED\":\n return new ValidationError(\"No Telegram account exists for this phone number\");\n case \"PHONE_CODE_INVALID\":\n return new ValidationError(\"Invalid confirmation code\");\n case \"PHONE_CODE_EXPIRED\":\n return new ValidationError(\"The confirmation code has expired — request a new one\");\n case \"PHONE_CODE_EMPTY\":\n return new ValidationError(\"Please enter the confirmation code\");\n case \"SESSION_PASSWORD_NEEDED\":\n return new UnauthorizedError(\"Two-factor authentication is enabled on this account\");\n case \"PASSWORD_HASH_INVALID\":\n return new ValidationError(\"Incorrect two-factor password\");\n case \"PHONE_NUMBER_BANNED\":\n return new ForbiddenError(\"This phone number is banned from Telegram\");\n case \"AUTH_KEY_UNREGISTERED\":\n case \"AUTH_KEY_DUPLICATED\":\n case \"SESSION_REVOKED\":\n case \"SESSION_EXPIRED\":\n case \"USER_DEACTIVATED\":\n case \"USER_DEACTIVATED_BAN\":\n return new TelegramAuthRequiredError();\n case \"CHANNEL_PRIVATE\":\n case \"CHANNEL_INVALID\":\n case \"CHAT_ID_INVALID\":\n return new StorageNotInitializedError();\n // Permanent: the account cannot write to the channel. Retrying cannot help.\n case \"CHAT_WRITE_FORBIDDEN\":\n case \"CHAT_ADMIN_REQUIRED\":\n return new ForbiddenError(\"OmniCloud cannot write to the storage channel for this account\");\n // Permanent: the stored channel reference is stale. Retrying with the same reference fails too.\n case \"PEER_ID_INVALID\":\n return new StorageNotInitializedError();\n case \"MSG_ID_INVALID\":\n case \"MESSAGE_ID_INVALID\":\n return new TelegramFileNotFoundError();\n case \"FILE_REFERENCE_EXPIRED\":\n case \"FILE_REFERENCE_INVALID\":\n return new TelegramFileNotFoundError(\n \"The Telegram file reference expired — re-upload the file to refresh it\",\n );\n default:\n return new TelegramConnectionError(fallbackMessage, {\n code: error.code,\n telegramError: msg,\n });\n }\n }\n\n const detail = error instanceof Error ? error.message : String(error);\n // Network-shaped failures get the connection error code so the UI can offer\n // a retry rather than implying the request was invalid.\n if (/timeout|ECONNRESET|ENOTFOUND|socket|network|connect/i.test(detail)) {\n return new TelegramConnectionError(`${fallbackMessage} (network error)`, error);\n }\n return new TelegramConnectionError(\n detail ? `${fallbackMessage}: ${detail}` : fallbackMessage,\n error,\n );\n}\n","import { Readable } from \"node:stream\";\nimport { Api, TelegramClient } from \"telegram\";\nimport bigInt from \"big-integer\";\nimport { CustomFile } from \"telegram/client/uploads.js\";\nimport {\n OperationCancelledError,\n TelegramAuthRequiredError,\n TelegramConnectionError,\n TelegramFileNotFoundError,\n UploadFailedError,\n type StorageHealth,\n type StorageProvider,\n type StorageUploadInput,\n type StoredObject,\n type StoredRef,\n type TransferControl,\n} from \"@omnicloud/core\";\nimport { mapTelegramError } from \"./errors\";\n\nexport interface TelegramStorageOptions {\n /** Raw channel id of the user's private storage channel. */\n chatId: string;\n /** Channel access hash required to build the input peer. */\n accessHash: string;\n /** Human-readable channel title, surfaced in health reporting. */\n title?: string;\n}\n\n/** Telegram's own limits for a single uploaded document. */\nconst MAX_DOCUMENT_BYTES = 2 * 1024 * 1024 * 1024; // 2 GB\n\n/** Largest single `upload.getFile` request Telegram accepts. */\nconst DOWNLOAD_CHUNK_BYTES = 512 * 1024;\n\n/**\n * StorageProvider implementation backed by the user's private Telegram\n * channel. Files are sent as force-downloaded documents (no compression), so\n * every upload is a document message in the channel.\n *\n * v0.2 adds progress reporting, cancellation, streamed downloads and a\n * health check, all translated into the provider-agnostic core vocabulary.\n */\nexport class TelegramStorageProvider implements StorageProvider {\n readonly name = \"telegram\";\n private readonly peer: Api.InputPeerChannel;\n private readonly title: string | null;\n\n constructor(\n private readonly client: TelegramClient,\n options: TelegramStorageOptions,\n ) {\n this.peer = new Api.InputPeerChannel({\n channelId: bigInt(options.chatId),\n accessHash: bigInt(options.accessHash),\n });\n this.title = options.title ?? null;\n }\n\n // ── Upload ───────────────────────────────────────────────────────────────\n\n async put(input: StorageUploadInput, control: TransferControl = {}): Promise<StoredObject> {\n const total = input.data ? input.data.byteLength : input.size;\n if (total === undefined || (!input.data && !input.path)) {\n throw new UploadFailedError(\"Upload input has neither data nor a spooled file\");\n }\n if (total > MAX_DOCUMENT_BYTES) {\n throw new UploadFailedError(\n `Telegram does not accept documents larger than ${MAX_DOCUMENT_BYTES / (1024 * 1024 * 1024)} GB`,\n );\n }\n this.throwIfAborted(control.signal);\n\n control.onProgress?.({ transferred: 0, total, percent: 0 });\n\n try {\n // A spooled file is handed to GramJS by path, so it reads the parts it\n // needs from disk; an in-memory buffer is passed through unchanged.\n const file = input.data\n ? new CustomFile(input.name, total, \"\", input.data)\n : new CustomFile(input.name, total, input.path!);\n const message = await this.client.sendFile(this.peer, {\n file,\n forceDocument: true,\n workers: 1,\n caption: input.caption,\n // Surface transport-level progress to the caller. GramJS reports a\n // 0..1 fraction. `isCanceled` lets us abort an in-flight upload.\n progressCallback: Object.assign(\n (fraction: number) => {\n const clamped = Math.max(0, Math.min(1, fraction));\n control.onProgress?.({\n transferred: Math.round(clamped * total),\n total,\n percent: Math.round(clamped * 100),\n });\n },\n {\n get isCanceled(): boolean {\n return control.signal?.aborted === true;\n },\n },\n ),\n });\n\n if (!(message instanceof Api.Message)) {\n throw new UploadFailedError(\"Telegram returned an unexpected upload response\");\n }\n\n control.onProgress?.({ transferred: total, total, percent: 100 });\n return {\n messageId: message.id.toString(),\n name: input.name,\n size: total,\n mimeType: input.mimeType,\n };\n } catch (error) {\n if (error instanceof OperationCancelledError) throw error;\n throw mapTelegramError(error, \"Telegram upload failed\");\n }\n }\n\n // ── Download ─────────────────────────────────────────────────────────────\n\n async get(ref: StoredRef, control: TransferControl = {}): Promise<Buffer> {\n const message = await this.requireMessage(ref);\n this.throwIfAborted(control.signal);\n\n const size = this.documentSize(message);\n control.onProgress?.({ transferred: 0, total: size, percent: 0 });\n\n let data: Buffer | string | undefined;\n try {\n data = await this.client.downloadMedia(message, {\n progressCallback: (downloaded: bigInt.BigInteger, total: bigInt.BigInteger) => {\n const transferred = Number(downloaded.toString());\n const totalBytes = Number(total.toString());\n control.onProgress?.({\n transferred,\n total: totalBytes,\n percent: totalBytes > 0 ? Math.round((transferred / totalBytes) * 100) : null,\n });\n },\n });\n } catch (error) {\n throw mapTelegramError(error, \"Telegram download failed\");\n }\n\n if (!data || typeof data === \"string\") {\n throw new TelegramFileNotFoundError(\"Telegram did not return file content\");\n }\n const buffer = Buffer.from(data);\n control.onProgress?.({\n transferred: buffer.byteLength,\n total: buffer.byteLength,\n percent: 100,\n });\n return buffer;\n }\n\n /**\n * Streams the object in Telegram-sized chunks, so memory use stays bounded\n * by one chunk regardless of file size. The first request happens here so\n * that a missing message fails before the caller starts consuming bytes.\n */\n async getStream(ref: StoredRef, control: TransferControl = {}): Promise<Readable> {\n const message = await this.requireMessage(ref);\n this.throwIfAborted(control.signal);\n\n const size = this.documentSize(message);\n const media = message.media;\n if (size === null || !media) {\n throw new TelegramFileNotFoundError(\"Telegram did not return file content\");\n }\n\n const iterator = this.client.iterDownload({\n file: media,\n fileSize: bigInt(size),\n requestSize: DOWNLOAD_CHUNK_BYTES,\n });\n\n let transferred = 0;\n control.onProgress?.({ transferred: 0, total: size, percent: 0 });\n\n const signal = control.signal;\n return Readable.from(\n (async function* () {\n try {\n for await (const chunk of iterator) {\n if (signal?.aborted) throw new OperationCancelledError();\n const buffer = Buffer.from(chunk);\n transferred += buffer.byteLength;\n control.onProgress?.({\n transferred,\n total: size,\n percent: size > 0 ? Math.round((transferred / size) * 100) : null,\n });\n yield buffer;\n }\n } catch (error) {\n if (error instanceof OperationCancelledError) throw error;\n throw mapTelegramError(error, \"Telegram download failed\");\n }\n })(),\n );\n }\n\n // ── Lifecycle ────────────────────────────────────────────────────────────\n\n async delete(ref: StoredRef): Promise<void> {\n try {\n await this.client.deleteMessages(this.peer, [Number(ref.messageId)], { revoke: true });\n } catch (error) {\n // A message that is already gone is already deleted, which the storage contract\n // allows. Any other failure is still reported, so a real problem is not hidden.\n const mapped = mapTelegramError(error, \"Telegram delete failed\");\n if (mapped instanceof TelegramFileNotFoundError) return;\n throw mapped;\n }\n }\n\n async exists(ref: StoredRef): Promise<boolean> {\n try {\n return (await this.stat(ref)) !== null;\n } catch (error) {\n throw mapTelegramError(error, \"Telegram lookup failed\");\n }\n }\n\n /**\n * Read-only listing of stored documents in the channel, newest first, up to\n * `limit`. Only messages that carry a document are returned; text messages and\n * service messages are skipped. Nothing is modified.\n */\n async listObjects(limit: number): Promise<StoredObject[]> {\n const objects: StoredObject[] = [];\n try {\n for await (const message of this.client.iterMessages(this.peer, { limit: limit * 2 })) {\n if (objects.length >= limit) break;\n if (!(message instanceof Api.Message)) continue;\n const document = message.document;\n if (!(document instanceof Api.Document)) continue;\n let fileName: string | undefined;\n for (const attr of document.attributes) {\n if (attr instanceof Api.DocumentAttributeFilename) {\n fileName = attr.fileName;\n break;\n }\n }\n objects.push({\n messageId: message.id.toString(),\n name: fileName ?? \"file\",\n size: Number(document.size.toString()),\n mimeType: document.mimeType,\n });\n }\n } catch (error) {\n throw mapTelegramError(error, \"Telegram listing failed\");\n }\n return objects;\n }\n\n /**\n * Finds stored documents whose caption equals the given caption and whose size\n * matches. Used to recover an upload whose outcome was unknown.\n */\n async findByCaption(caption: string, size: number): Promise<StoredObject[]> {\n const matches: StoredObject[] = [];\n try {\n for await (const message of this.client.iterMessages(this.peer, {\n search: caption,\n limit: 20,\n })) {\n if (!(message instanceof Api.Message)) continue;\n if (message.message !== caption) continue;\n const document = message.document;\n if (!(document instanceof Api.Document)) continue;\n if (Number(document.size.toString()) !== size) continue;\n let fileName: string | undefined;\n for (const attr of document.attributes) {\n if (attr instanceof Api.DocumentAttributeFilename) {\n fileName = attr.fileName;\n break;\n }\n }\n matches.push({\n messageId: message.id.toString(),\n name: fileName ?? \"file\",\n size,\n mimeType: document.mimeType,\n });\n }\n } catch (error) {\n throw mapTelegramError(error, \"Telegram lookup failed\");\n }\n return matches;\n }\n\n async stat(ref: StoredRef): Promise<StoredObject | null> {\n const message = await this.getMessage(ref.messageId);\n if (!message?.media) return null;\n\n const document = message.document;\n if (!(document instanceof Api.Document)) return null;\n\n let fileName: string | undefined;\n for (const attr of document.attributes) {\n if (attr instanceof Api.DocumentAttributeFilename) {\n fileName = attr.fileName;\n break;\n }\n }\n return {\n messageId: ref.messageId,\n name: fileName ?? \"file\",\n size: Number(document.size.toString()),\n mimeType: document.mimeType,\n };\n }\n\n // ── Health ───────────────────────────────────────────────────────────────\n\n /**\n * Validates that the client is connected and the storage channel is\n * reachable (a real Telegram round-trip on the channel peer).\n */\n async healthCheck(): Promise<StorageHealth> {\n const startedAt = Date.now();\n try {\n if (!this.client.connected) {\n return {\n healthy: false,\n latencyMs: null,\n message: \"Telegram client is not connected\",\n targetTitle: this.title,\n };\n }\n const result = await this.client.invoke(\n new Api.channels.GetFullChannel({ channel: this.peer }),\n );\n const title =\n result instanceof Api.messages.ChatFull && result.chats[0] instanceof Api.Channel\n ? result.chats[0].title\n : this.title;\n return {\n healthy: true,\n latencyMs: Date.now() - startedAt,\n message: null,\n targetTitle: title ?? null,\n };\n } catch (error) {\n const mapped = mapTelegramError(error, \"Telegram storage health check failed\");\n return {\n healthy: false,\n latencyMs: Date.now() - startedAt,\n message: mapped.message,\n targetTitle: this.title,\n };\n }\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted) throw new OperationCancelledError();\n if (!this.client.connected) {\n throw new TelegramConnectionError(\"Telegram client is not connected\");\n }\n }\n\n private documentSize(message: Api.Message): number | null {\n const document = message.document;\n if (document instanceof Api.Document) return Number(document.size.toString());\n return null;\n }\n\n private async requireMessage(ref: StoredRef): Promise<Api.Message> {\n const message = await this.getMessage(ref.messageId);\n if (!message?.media) {\n throw new TelegramFileNotFoundError();\n }\n return message;\n }\n\n private async getMessage(id: string): Promise<Api.Message | undefined> {\n try {\n const messages = await this.client.getMessages(this.peer, { ids: [Number(id)] });\n const first = messages[0];\n return first instanceof Api.Message ? first : undefined;\n } catch (error) {\n if (error instanceof TelegramAuthRequiredError) throw error;\n throw mapTelegramError(error, \"Telegram lookup failed\");\n }\n }\n}\n","import { randomUUID } from \"node:crypto\";\nimport { Api, TelegramClient } from \"telegram\";\nimport { CustomFile } from \"telegram/client/uploads.js\";\nimport { computeCheck } from \"telegram/Password.js\";\nimport {\n NotFoundError,\n TelegramConnectionError,\n ValidationError,\n type Repos,\n type StorageRecord,\n type UserRecord,\n} from \"@omnicloud/core\";\nimport {\n extractCreatedChannel,\n newTelegramClient,\n type TelegramClientManager,\n type TelegramCredentials,\n} from \"./client\";\nimport { isPasswordRequiredError, mapTelegramError } from \"./errors\";\nimport { CHANNEL_LOGO_PNG_BASE64 } from \"./brand/channel-logo\";\n\nexport const TELEGRAM_PROVIDER = \"telegram\";\n\n/**\n * A Telegram login in progress: a connected client waiting for the\n * confirmation code (and possibly the 2FA password) to be supplied.\n */\ninterface LoginFlow {\n phone: string;\n client: TelegramClient;\n phoneCodeHash: string;\n createdAt: number;\n}\n\nconst LOGIN_FLOW_TTL_MS = 5 * 60 * 1000;\n\n/** A QR sign-in in progress: a connected client waiting for the phone to approve the token. */\ninterface QrFlow {\n client: TelegramClient;\n createdAt: number;\n /** Set once the approval update arrives, so the next poll completes the login. */\n approved: boolean;\n /** Set when the account needs its two-factor password before sign-in completes. */\n needsPassword: boolean;\n /** Pending error from the background wait, reported on the next poll. */\n failure: string | null;\n}\n\nexport interface QrLoginToken {\n /** The `tg://login?token=…` link to render as a QR code. */\n url: string;\n /** Unix time in milliseconds when this token stops being valid. */\n expiresAt: number;\n}\n\nexport type QrStatus =\n | { status: \"waiting\"; token: QrLoginToken }\n | { status: \"approved\"; user: UserRecord }\n | { status: \"password_required\" }\n | { status: \"expired\" };\n\nconst QR_FLOW_TTL_MS = 5 * 60 * 1000;\nconst QR_APPROVAL_POLL_MS = 2_000;\n\nexport type VerifyCodeResult = { status: \"ok\"; user: UserRecord } | { status: \"password_required\" };\n\n/**\n * Orchestrates the full Telegram account connection flow:\n * phone → code (→ 2FA password) → session persisted → user upserted →\n * private storage channel created.\n */\nexport class TelegramConnectionService {\n private flows = new Map<string, LoginFlow>();\n private qrFlows = new Map<string, QrFlow>();\n\n constructor(\n private readonly repos: Repos,\n private readonly clientManager: TelegramClientManager,\n private readonly credentials: TelegramCredentials,\n ) {}\n\n /** Sends the Telegram confirmation code for the phone number. */\n async startLogin(phone: string): Promise<void> {\n this.sweepExpiredFlows();\n await this.cancelLogin(phone);\n\n const client = newTelegramClient(this.credentials);\n try {\n await client.connect();\n const { phoneCodeHash } = await client.sendCode(\n { apiId: this.credentials.apiId, apiHash: this.credentials.apiHash },\n phone,\n );\n this.flows.set(phone, { phone, client, phoneCodeHash, createdAt: Date.now() });\n } catch (error) {\n await this.disconnectQuietly(client);\n throw mapTelegramError(error, \"Could not send the Telegram confirmation code\");\n }\n }\n\n /** Verifies the confirmation code. Returns `password_required` for 2FA accounts. */\n async verifyCode(phone: string, code: string): Promise<VerifyCodeResult> {\n const flow = this.requireFlow(phone);\n try {\n await flow.client.invoke(\n new Api.auth.SignIn({\n phoneNumber: flow.phone,\n phoneCodeHash: flow.phoneCodeHash,\n phoneCode: code,\n }),\n );\n } catch (error) {\n if (isPasswordRequiredError(error)) {\n return { status: \"password_required\" };\n }\n throw mapTelegramError(error, \"Telegram sign-in failed\");\n }\n return this.finalizeLogin(flow);\n }\n\n /** Completes sign-in for accounts with two-factor authentication enabled. */\n async verifyPassword(phone: string, password: string): Promise<VerifyCodeResult> {\n const flow = this.requireFlow(phone);\n try {\n const passwordInfo = await flow.client.invoke(new Api.account.GetPassword());\n const check = await computeCheck(passwordInfo, password);\n await flow.client.invoke(new Api.auth.CheckPassword({ password: check }));\n } catch (error) {\n throw mapTelegramError(error, \"Telegram sign-in failed\");\n }\n return this.finalizeLogin(flow);\n }\n\n /**\n * Starts a QR sign-in. Returns a flow id and the first login token. The phone\n * approves the token in the Telegram app; `pollQrLogin` reports the result.\n */\n async startQrLogin(): Promise<{ flowId: string; token: QrLoginToken }> {\n this.sweepExpiredQrFlows();\n const client = newTelegramClient(this.credentials);\n try {\n await client.connect();\n const flow: QrFlow = {\n client,\n createdAt: Date.now(),\n approved: false,\n needsPassword: false,\n failure: null,\n };\n const flowId = randomUUID();\n const token = await this.exportQrToken(client);\n this.qrFlows.set(flowId, flow);\n this.watchQrApproval(flowId, flow);\n return { flowId, token };\n } catch (error) {\n await this.disconnectQuietly(client);\n throw mapTelegramError(error, \"Could not start Telegram QR sign-in\");\n }\n }\n\n /**\n * Reports where a QR sign-in stands. While waiting, returns a fresh token\n * (Telegram rotates them every few seconds). On approval, completes the login.\n */\n async pollQrLogin(flowId: string): Promise<QrStatus> {\n const flow = this.requireQrFlow(flowId);\n if (flow.failure) {\n this.qrFlows.delete(flowId);\n await this.disconnectQuietly(flow.client);\n throw new TelegramConnectionError(flow.failure);\n }\n if (flow.needsPassword) return { status: \"password_required\" };\n if (!flow.approved) {\n const token = await this.exportQrToken(flow.client).catch((error: unknown) => {\n this.qrFlows.delete(flowId);\n void this.disconnectQuietly(flow.client);\n throw mapTelegramError(error, \"Could not refresh the Telegram QR code\");\n });\n return { status: \"waiting\", token };\n }\n return this.completeQrLogin(flowId, flow);\n }\n\n /** Completes a QR sign-in for an account with two-factor authentication. */\n async submitQrPassword(\n flowId: string,\n password: string,\n ): Promise<{ status: \"approved\"; user: UserRecord }> {\n const flow = this.requireQrFlow(flowId);\n try {\n const passwordInfo = await flow.client.invoke(new Api.account.GetPassword());\n const check = await computeCheck(passwordInfo, password);\n await flow.client.invoke(new Api.auth.CheckPassword({ password: check }));\n } catch (error) {\n throw mapTelegramError(error, \"Telegram sign-in failed\");\n }\n return this.completeQrLogin(flowId, flow);\n }\n\n private async exportQrToken(client: TelegramClient): Promise<QrLoginToken> {\n const result = await client.invoke(\n new Api.auth.ExportLoginToken({\n apiId: this.credentials.apiId,\n apiHash: this.credentials.apiHash,\n exceptIds: [],\n }),\n );\n if (!(result instanceof Api.auth.LoginToken)) {\n throw new TelegramConnectionError(\"Telegram did not issue a QR login token\");\n }\n const tokenBase64 = Buffer.from(result.token).toString(\"base64url\");\n return {\n url: `tg://login?token=${tokenBase64}`,\n expiresAt: result.expires * 1000,\n };\n }\n\n /** Waits for the phone to approve the token, then marks the flow approved. */\n private watchQrApproval(flowId: string, flow: QrFlow): void {\n const timer = setInterval(() => {\n if (Date.now() - flow.createdAt > QR_FLOW_TTL_MS) {\n clearInterval(timer);\n this.qrFlows.delete(flowId);\n void this.disconnectQuietly(flow.client);\n return;\n }\n if (flow.approved || flow.needsPassword || flow.failure) {\n clearInterval(timer);\n return;\n }\n this.probeQrApproval(flow).then(\n (state) => {\n if (state === \"approved\") {\n flow.approved = true;\n clearInterval(timer);\n } else if (state === \"password\") {\n flow.needsPassword = true;\n clearInterval(timer);\n }\n },\n (error: unknown) => {\n flow.failure = mapTelegramError(error, \"Telegram QR sign-in failed\").message;\n clearInterval(timer);\n },\n );\n }, QR_APPROVAL_POLL_MS);\n timer.unref();\n }\n\n /**\n * Asks Telegram whether the token was approved. Approval is observed by\n * `ExportLoginToken` returning `LoginTokenSuccess` (or a DC migration).\n */\n private async probeQrApproval(flow: QrFlow): Promise<\"pending\" | \"approved\" | \"password\"> {\n try {\n const result = await flow.client.invoke(\n new Api.auth.ExportLoginToken({\n apiId: this.credentials.apiId,\n apiHash: this.credentials.apiHash,\n exceptIds: [],\n }),\n );\n if (result instanceof Api.auth.LoginTokenSuccess) return \"approved\";\n if (result instanceof Api.auth.LoginTokenMigrateTo) {\n await flow.client._switchDC(result.dcId);\n const migrated = await flow.client.invoke(\n new Api.auth.ImportLoginToken({ token: result.token }),\n );\n if (migrated instanceof Api.auth.LoginTokenSuccess) return \"approved\";\n }\n return \"pending\";\n } catch (error) {\n if (isPasswordRequiredError(error)) return \"password\";\n throw error;\n }\n }\n\n private async completeQrLogin(\n flowId: string,\n flow: QrFlow,\n ): Promise<{ status: \"approved\"; user: UserRecord }> {\n this.qrFlows.delete(flowId);\n const user = await this.finalizeConnectedClient(flow.client, null);\n return { status: \"approved\", user };\n }\n\n private requireQrFlow(flowId: string): QrFlow {\n const flow = this.qrFlows.get(flowId);\n if (!flow) {\n throw new NotFoundError(\"This Telegram QR sign-in has ended. Start again.\");\n }\n if (Date.now() - flow.createdAt > QR_FLOW_TTL_MS) {\n this.qrFlows.delete(flowId);\n void this.disconnectQuietly(flow.client);\n throw new ValidationError(\"The QR sign-in expired. Start again.\");\n }\n return flow;\n }\n\n private sweepExpiredQrFlows(): void {\n const now = Date.now();\n for (const [flowId, flow] of this.qrFlows) {\n if (now - flow.createdAt > QR_FLOW_TTL_MS) {\n this.qrFlows.delete(flowId);\n void this.disconnectQuietly(flow.client);\n }\n }\n }\n\n /** Aborts a pending login flow (disconnects the client). */\n async cancelLogin(phone: string): Promise<void> {\n const flow = this.flows.get(phone);\n if (flow) {\n this.flows.delete(phone);\n await this.disconnectQuietly(flow.client);\n }\n }\n\n /**\n * Ensures the user has a registered private storage channel, creating it\n * on first use. Idempotent.\n */\n async ensureStorage(userId: string): Promise<StorageRecord> {\n const existing = await this.repos.storages.findByUserAndProvider(userId, TELEGRAM_PROVIDER);\n if (existing) return existing;\n\n const client = await this.clientManager.getForUser(userId);\n return this.ensureStorageForClient(userId, client);\n }\n\n async ensureStorageForClient(userId: string, client: TelegramClient): Promise<StorageRecord> {\n const existing = await this.repos.storages.findByUserAndProvider(userId, TELEGRAM_PROVIDER);\n if (existing) return existing;\n\n let channel: Api.Channel;\n try {\n const result = await client.invoke(\n new Api.channels.CreateChannel({\n title: \"OmniCloud Storage\",\n about:\n \"Private storage channel created by OmniCloud (https://github.com/Lacrous/OmniCloud)\",\n megagroup: false,\n }),\n );\n channel = extractCreatedChannel(result);\n } catch (error) {\n throw mapTelegramError(error, \"Could not create the private storage channel\");\n }\n\n const accessHash = channel.accessHash?.toString();\n if (!accessHash) {\n throw new TelegramConnectionError(\"Telegram did not provide the channel access hash\");\n }\n\n await this.brandChannel(client, channel);\n\n return this.repos.storages.create({\n userId,\n provider: TELEGRAM_PROVIDER,\n title: channel.title ?? \"OmniCloud Storage\",\n telegramChatId: channel.id.toString(),\n telegramAccessHash: accessHash,\n });\n }\n\n /**\n * Sets the OmniCloud logo as the channel photo and moves the channel to the\n * archive folder. Both are cosmetic: a failure is reported in the result and\n * never blocks the storage record.\n */\n private async brandChannel(\n client: TelegramClient,\n channel: Api.Channel,\n ): Promise<{ logo: boolean; archived: boolean }> {\n const result = { logo: false, archived: false };\n const peer = new Api.InputChannel({\n channelId: channel.id,\n accessHash: channel.accessHash!,\n });\n\n try {\n const file = await client.uploadFile({\n file: new CustomFile(\n \"omnicloud.png\",\n Buffer.from(CHANNEL_LOGO_PNG_BASE64, \"base64\").byteLength,\n \"\",\n Buffer.from(CHANNEL_LOGO_PNG_BASE64, \"base64\"),\n ),\n workers: 1,\n });\n await client.invoke(\n new Api.channels.EditPhoto({\n channel: peer,\n photo: new Api.InputChatUploadedPhoto({ file }),\n }),\n );\n result.logo = true;\n } catch {\n /* the logo is cosmetic: the channel still works without it */\n }\n\n try {\n await client.invoke(\n new Api.folders.EditPeerFolders({\n folderPeers: [\n new Api.InputFolderPeer({\n peer: new Api.InputPeerChannel({\n channelId: channel.id,\n accessHash: channel.accessHash!,\n }),\n folderId: 1,\n }),\n ],\n }),\n );\n result.archived = true;\n } catch {\n /* archiving is cosmetic: the channel still works when not archived */\n }\n\n return result;\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private requireFlow(phone: string): LoginFlow {\n const flow = this.flows.get(phone);\n if (!flow) {\n throw new NotFoundError(\"No pending Telegram login for this phone number\");\n }\n if (Date.now() - flow.createdAt > LOGIN_FLOW_TTL_MS) {\n this.flows.delete(phone);\n void this.disconnectQuietly(flow.client);\n throw new ValidationError(\"The login session expired — please start again\");\n }\n return flow;\n }\n\n private async finalizeLogin(flow: LoginFlow): Promise<VerifyCodeResult> {\n this.flows.delete(flow.phone);\n const user = await this.finalizeConnectedClient(flow.client, flow.phone);\n return { status: \"ok\", user };\n }\n\n /**\n * Saves an authorized client's session, upserts the user and creates storage.\n * `phone` is the number the user typed; QR sign-in passes null and relies on\n * the number Telegram reports.\n */\n private async finalizeConnectedClient(\n client: TelegramClient,\n phone: string | null,\n ): Promise<UserRecord> {\n let stringSession: string;\n let user: UserRecord;\n try {\n stringSession = client.session.save() as unknown as string;\n const me = await client.getMe();\n if (!(me instanceof Api.User)) {\n throw new Error(\"Telegram did not return the authenticated user\");\n }\n user = await this.repos.users.upsertFromTelegram({\n telegramUserId: me.id.toString(),\n username: me.username ?? null,\n firstName: me.firstName ?? null,\n lastName: me.lastName ?? null,\n phone: me.phone ?? phone ?? \"\",\n });\n } catch (error) {\n await this.disconnectQuietly(client);\n throw mapTelegramError(error, \"Could not complete the Telegram connection\");\n }\n\n await this.repos.sessions.save(user.id, stringSession);\n await this.ensureStorageForClient(user.id, client);\n this.clientManager.cacheClient(user.id, client);\n\n return user;\n }\n\n private sweepExpiredFlows(): void {\n const now = Date.now();\n for (const [phone, flow] of this.flows) {\n if (now - flow.createdAt > LOGIN_FLOW_TTL_MS) {\n this.flows.delete(phone);\n void this.disconnectQuietly(flow.client);\n }\n }\n }\n\n private async disconnectQuietly(client: TelegramClient): Promise<void> {\n try {\n await client.disconnect();\n } catch {\n // best effort\n }\n }\n}\n","/** The OmniCloud channel logo, embedded so it ships with the compiled package. */\nexport const CHANNEL_LOGO_PNG_BASE64 =\n \"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\";\n"],"mappings":"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","names":["createWriteStream","rm","Readable","pipeline","date","createHash","sha256","size","stored","createHash","createHash","createHash","createHash","createHash","Transform","randomBytes","Readable","Api","Api","Readable","Api","CustomFile","Api","CustomFile"]}
|