@lacrous/omnicloud 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2049 -265
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1035 -121
- package/dist/index.d.ts +1035 -121
- package/dist/index.js +2036 -263
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,84 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
1
2
|
import { TelegramClient } from 'telegram';
|
|
2
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Search query parsing, pagination and sorting primitives.
|
|
6
|
+
*
|
|
7
|
+
* Self-contained (no runtime imports from the package root) so the API, web
|
|
8
|
+
* app and SDK can all share one query language without import cycles.
|
|
9
|
+
*
|
|
10
|
+
* Query language (v0.2):
|
|
11
|
+
* report → name contains "report"
|
|
12
|
+
* name:report → same, explicit
|
|
13
|
+
* type:pdf type:image type:application/zip
|
|
14
|
+
* ext:zip → file extension (no dot)
|
|
15
|
+
* size:>100MB size:<1KB size:5mb
|
|
16
|
+
* folder:Projects → folder-name filter
|
|
17
|
+
* starred:true trashed:true
|
|
18
|
+
* after:2026-01-01 before:2026-06-01
|
|
19
|
+
*/
|
|
20
|
+
declare const SORT_FIELDS: readonly ["name", "size", "createdAt", "updatedAt", "type"];
|
|
21
|
+
type SortField = (typeof SORT_FIELDS)[number];
|
|
22
|
+
declare const SORT_ORDERS: readonly ["asc", "desc"];
|
|
23
|
+
type SortOrder = (typeof SORT_ORDERS)[number];
|
|
24
|
+
declare const ITEM_STATUSES: readonly ["active", "trashed", "all"];
|
|
25
|
+
type ItemStatus = (typeof ITEM_STATUSES)[number];
|
|
26
|
+
/** Query parameters accepted by the file/folder listing endpoints. */
|
|
27
|
+
interface ListQuery {
|
|
28
|
+
folderId?: string | null;
|
|
29
|
+
page?: number;
|
|
30
|
+
limit?: number;
|
|
31
|
+
sort?: SortField;
|
|
32
|
+
order?: SortOrder;
|
|
33
|
+
/** MIME type or coarse category (image, video, pdf, document, …). */
|
|
34
|
+
type?: string;
|
|
35
|
+
/** File extension without the dot. */
|
|
36
|
+
ext?: string;
|
|
37
|
+
starred?: boolean;
|
|
38
|
+
status?: ItemStatus;
|
|
39
|
+
/** Name substring filter. */
|
|
40
|
+
q?: string;
|
|
41
|
+
/** Folder-name filter (search parser); resolved to ids by the caller. */
|
|
42
|
+
folderName?: string;
|
|
43
|
+
minSize?: number;
|
|
44
|
+
maxSize?: number;
|
|
45
|
+
/** Inclusive date bounds (ISO strings). */
|
|
46
|
+
from?: string;
|
|
47
|
+
to?: string;
|
|
48
|
+
}
|
|
49
|
+
/** Parses "100MB", "1.5gb", "512" into bytes. Returns null when unparseable. */
|
|
50
|
+
declare function parseSize(value: string): number | null;
|
|
51
|
+
/** Parses a raw user query string into structured filters. */
|
|
52
|
+
declare function parseSearchQuery(input: string, base?: ListQuery): {
|
|
53
|
+
query: ListQuery;
|
|
54
|
+
freeText: string;
|
|
55
|
+
};
|
|
56
|
+
/** Builds the pagination envelope for a result page. */
|
|
57
|
+
declare function paginationMeta(page: number, limit: number, total: number): {
|
|
58
|
+
page: number;
|
|
59
|
+
limit: number;
|
|
60
|
+
total: number;
|
|
61
|
+
hasMore: boolean;
|
|
62
|
+
};
|
|
63
|
+
|
|
3
64
|
/** Derives a MIME type from the filename extension. Never trusts client input. */
|
|
4
65
|
declare function mimeFromFilename(filename: string): string;
|
|
5
66
|
type FileCategory = "image" | "video" | "audio" | "pdf" | "archive" | "document" | "text" | "other";
|
|
6
67
|
/** Coarse category used for icon selection in the UI. */
|
|
7
68
|
declare function fileCategory(mimeType: string): FileCategory;
|
|
69
|
+
declare function isFileCategory(value: string): value is FileCategory;
|
|
70
|
+
/**
|
|
71
|
+
* Expands a `type:` filter into a set of MIME matchers.
|
|
72
|
+
*
|
|
73
|
+
* Accepts either a coarse category ("image", "pdf") or an exact MIME type
|
|
74
|
+
* ("application/pdf"); the repository layer turns these into prefix/equality
|
|
75
|
+
* filters. Returns null when the value matches nothing usable.
|
|
76
|
+
*/
|
|
77
|
+
declare function mimeMatchersForType(value: string): readonly string[] | null;
|
|
8
78
|
|
|
9
79
|
/**
|
|
10
|
-
* Data transfer objects and
|
|
11
|
-
* and the @lacrous/omnicloud SDK.
|
|
80
|
+
* Data transfer objects, constants and error codes shared between the API,
|
|
81
|
+
* the web app, and the @lacrous/omnicloud SDK.
|
|
12
82
|
*
|
|
13
83
|
* Telegram identifiers that may exceed JavaScript's safe integer range are
|
|
14
84
|
* transported as strings across the API boundary.
|
|
@@ -25,54 +95,197 @@ interface StorageDTO {
|
|
|
25
95
|
title: string;
|
|
26
96
|
createdAt: string;
|
|
27
97
|
}
|
|
98
|
+
/** Connection state of the underlying storage backend. */
|
|
99
|
+
type ConnectionState$1 = "DISCONNECTED" | "CONNECTING" | "CONNECTED" | "RECONNECTING" | "ERROR" | "AUTH_REQUIRED";
|
|
100
|
+
type HealthStatus = "healthy" | "degraded" | "unavailable" | "unknown";
|
|
101
|
+
interface StorageHealthDTO {
|
|
102
|
+
provider: string;
|
|
103
|
+
state: ConnectionState$1;
|
|
104
|
+
status: HealthStatus;
|
|
105
|
+
latencyMs: number | null;
|
|
106
|
+
message: string | null;
|
|
107
|
+
channelTitle: string | null;
|
|
108
|
+
checkedAt: string;
|
|
109
|
+
}
|
|
28
110
|
/** Returned by GET /api/auth/me. `user` is null when not signed in. */
|
|
29
111
|
interface SessionInfo {
|
|
30
112
|
user: UserDTO | null;
|
|
31
113
|
storage: StorageDTO | null;
|
|
114
|
+
health: StorageHealthDTO | null;
|
|
32
115
|
}
|
|
33
|
-
|
|
116
|
+
/** POST /api/auth/telegram/verify response. */
|
|
117
|
+
type TelegramVerifyResponse = {
|
|
118
|
+
status: "ok";
|
|
119
|
+
user: UserDTO;
|
|
120
|
+
} | {
|
|
121
|
+
status: "password_required";
|
|
122
|
+
};
|
|
123
|
+
interface FileDTO$1 {
|
|
34
124
|
id: string;
|
|
35
125
|
name: string;
|
|
36
126
|
size: number;
|
|
37
127
|
mimeType: string;
|
|
38
128
|
sha256: string;
|
|
39
129
|
folderId: string | null;
|
|
130
|
+
starred: boolean;
|
|
131
|
+
trashed: boolean;
|
|
132
|
+
deletedAt: string | null;
|
|
133
|
+
versionCount: number;
|
|
134
|
+
currentVersionId: string | null;
|
|
40
135
|
createdAt: string;
|
|
41
136
|
updatedAt: string;
|
|
42
137
|
}
|
|
43
|
-
interface FolderDTO {
|
|
138
|
+
interface FolderDTO$1 {
|
|
44
139
|
id: string;
|
|
45
140
|
name: string;
|
|
46
141
|
parentId: string | null;
|
|
142
|
+
starred: boolean;
|
|
143
|
+
trashed: boolean;
|
|
144
|
+
deletedAt: string | null;
|
|
47
145
|
createdAt: string;
|
|
48
146
|
updatedAt: string;
|
|
49
147
|
}
|
|
50
|
-
interface
|
|
51
|
-
|
|
52
|
-
|
|
148
|
+
interface FileVersionDTO {
|
|
149
|
+
id: string;
|
|
150
|
+
fileId: string;
|
|
151
|
+
versionNumber: number;
|
|
152
|
+
size: number;
|
|
153
|
+
mimeType: string;
|
|
154
|
+
sha256: string;
|
|
155
|
+
telegramMessageId: number;
|
|
156
|
+
isCurrent: boolean;
|
|
157
|
+
createdAt: string;
|
|
53
158
|
}
|
|
54
|
-
interface
|
|
55
|
-
|
|
159
|
+
interface SearchResultDTO$1 {
|
|
160
|
+
files: FileDTO$1[];
|
|
161
|
+
folders: FolderDTO$1[];
|
|
162
|
+
pagination: PaginationDTO$1;
|
|
163
|
+
}
|
|
164
|
+
/** Result of a recursive folder deletion / trash operation. */
|
|
165
|
+
interface FolderMutationResultDTO {
|
|
166
|
+
affectedFolders: number;
|
|
167
|
+
affectedFiles: number;
|
|
168
|
+
}
|
|
169
|
+
interface EmptyTrashResultDTO {
|
|
56
170
|
deletedFiles: number;
|
|
171
|
+
deletedFolders: number;
|
|
172
|
+
failedFiles: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
interface PaginationDTO$1 {
|
|
176
|
+
page: number;
|
|
177
|
+
limit: number;
|
|
178
|
+
total: number;
|
|
179
|
+
hasMore: boolean;
|
|
180
|
+
}
|
|
181
|
+
interface FilesPageDTO {
|
|
182
|
+
files: FileDTO$1[];
|
|
183
|
+
pagination: PaginationDTO$1;
|
|
184
|
+
}
|
|
185
|
+
interface FoldersPageDTO {
|
|
186
|
+
folders: FolderDTO$1[];
|
|
187
|
+
pagination: PaginationDTO$1;
|
|
188
|
+
}
|
|
189
|
+
interface TrashPageDTO {
|
|
190
|
+
files: FileDTO$1[];
|
|
191
|
+
folders: FolderDTO$1[];
|
|
192
|
+
pagination: PaginationDTO$1;
|
|
193
|
+
}
|
|
194
|
+
interface RecentItemDTO$1 {
|
|
195
|
+
file: FileDTO$1;
|
|
196
|
+
lastAction: ActivityAction$1;
|
|
197
|
+
lastActionAt: string;
|
|
198
|
+
}
|
|
199
|
+
interface RecentPageDTO$1 {
|
|
200
|
+
items: RecentItemDTO$1[];
|
|
201
|
+
pagination: PaginationDTO$1;
|
|
202
|
+
}
|
|
203
|
+
declare const ACTIVITY_ACTIONS$1: readonly ["upload", "download", "open", "rename", "move", "star", "unstar", "trash", "restore", "delete", "create_folder", "replace"];
|
|
204
|
+
type ActivityAction$1 = (typeof ACTIVITY_ACTIONS$1)[number];
|
|
205
|
+
declare const RESOURCE_TYPES$1: readonly ["file", "folder", "storage"];
|
|
206
|
+
type ResourceType$1 = (typeof RESOURCE_TYPES$1)[number];
|
|
207
|
+
interface ActivityEventDTO {
|
|
208
|
+
id: string;
|
|
209
|
+
action: ActivityAction$1;
|
|
210
|
+
resourceType: ResourceType$1;
|
|
211
|
+
resourceId: string;
|
|
212
|
+
resourceName: string | null;
|
|
213
|
+
metadata: Record<string, unknown> | null;
|
|
214
|
+
createdAt: string;
|
|
215
|
+
}
|
|
216
|
+
interface ActivityPageDTO {
|
|
217
|
+
events: ActivityEventDTO[];
|
|
218
|
+
pagination: PaginationDTO$1;
|
|
219
|
+
}
|
|
220
|
+
interface StorageTypeBreakdownDTO {
|
|
221
|
+
category: string;
|
|
222
|
+
bytes: number;
|
|
223
|
+
count: number;
|
|
224
|
+
}
|
|
225
|
+
interface StorageStatsDTO {
|
|
226
|
+
fileCount: number;
|
|
227
|
+
folderCount: number;
|
|
228
|
+
totalBytes: number;
|
|
229
|
+
trashBytes: number;
|
|
230
|
+
trashFileCount: number;
|
|
231
|
+
starredCount: number;
|
|
232
|
+
byType: StorageTypeBreakdownDTO[];
|
|
233
|
+
largestFiles: FileDTO$1[];
|
|
234
|
+
quotaBytes: number | null;
|
|
235
|
+
}
|
|
236
|
+
interface HealthDTO {
|
|
237
|
+
status: HealthStatus;
|
|
238
|
+
database: HealthStatus;
|
|
239
|
+
storage: HealthStatus;
|
|
240
|
+
uptimeSeconds: number;
|
|
241
|
+
version: string;
|
|
242
|
+
}
|
|
243
|
+
interface IntegrityIssueDTO$1 {
|
|
244
|
+
fileId: string;
|
|
245
|
+
name: string;
|
|
246
|
+
kind: "missing" | "size_mismatch" | "unreadable" | "hash_mismatch";
|
|
247
|
+
detail: string | null;
|
|
248
|
+
}
|
|
249
|
+
interface IntegrityReportDTO$1 {
|
|
250
|
+
checkedAt: string;
|
|
251
|
+
filesChecked: number;
|
|
252
|
+
healthy: number;
|
|
253
|
+
missing: number;
|
|
254
|
+
inconsistent: number;
|
|
255
|
+
unreadable: number;
|
|
256
|
+
issues: IntegrityIssueDTO$1[];
|
|
257
|
+
durationMs: number;
|
|
258
|
+
}
|
|
259
|
+
interface BatchResultDTO {
|
|
260
|
+
requested: number;
|
|
261
|
+
succeeded: number;
|
|
262
|
+
failed: number;
|
|
263
|
+
errors: {
|
|
264
|
+
id: string;
|
|
265
|
+
code: ErrorCode$1;
|
|
266
|
+
message: string;
|
|
267
|
+
}[];
|
|
57
268
|
}
|
|
58
|
-
/** POST /api/auth/telegram/verify response. */
|
|
59
|
-
type TelegramVerifyResponse = {
|
|
60
|
-
status: "ok";
|
|
61
|
-
user: UserDTO;
|
|
62
|
-
} | {
|
|
63
|
-
status: "password_required";
|
|
64
|
-
};
|
|
65
269
|
/** Stable machine-readable error codes returned in API error bodies. */
|
|
66
270
|
declare const ERROR_CODES$1: {
|
|
67
|
-
readonly
|
|
68
|
-
readonly
|
|
69
|
-
readonly
|
|
271
|
+
readonly AUTH_REQUIRED: "AUTH_REQUIRED";
|
|
272
|
+
readonly AUTH_INVALID: "AUTH_INVALID";
|
|
273
|
+
readonly PERMISSION_DENIED: "PERMISSION_DENIED";
|
|
274
|
+
readonly INVALID_REQUEST: "INVALID_REQUEST";
|
|
70
275
|
readonly NOT_FOUND: "NOT_FOUND";
|
|
276
|
+
readonly FILE_NOT_FOUND: "FILE_NOT_FOUND";
|
|
277
|
+
readonly FOLDER_NOT_FOUND: "FOLDER_NOT_FOUND";
|
|
71
278
|
readonly CONFLICT: "CONFLICT";
|
|
72
279
|
readonly PAYLOAD_TOO_LARGE: "PAYLOAD_TOO_LARGE";
|
|
73
280
|
readonly RATE_LIMITED: "RATE_LIMITED";
|
|
74
281
|
readonly STORAGE_NOT_INITIALIZED: "STORAGE_NOT_INITIALIZED";
|
|
75
|
-
readonly
|
|
282
|
+
readonly STORAGE_UNAVAILABLE: "STORAGE_UNAVAILABLE";
|
|
283
|
+
readonly TELEGRAM_AUTH_REQUIRED: "TELEGRAM_AUTH_REQUIRED";
|
|
284
|
+
readonly TELEGRAM_CONNECTION_FAILED: "TELEGRAM_CONNECTION_FAILED";
|
|
285
|
+
readonly TELEGRAM_FILE_NOT_FOUND: "TELEGRAM_FILE_NOT_FOUND";
|
|
286
|
+
readonly UPLOAD_FAILED: "UPLOAD_FAILED";
|
|
287
|
+
readonly DOWNLOAD_FAILED: "DOWNLOAD_FAILED";
|
|
288
|
+
readonly INTEGRITY_CHECK_FAILED: "INTEGRITY_CHECK_FAILED";
|
|
76
289
|
readonly INTERNAL: "INTERNAL_ERROR";
|
|
77
290
|
};
|
|
78
291
|
type ErrorCode$1 = (typeof ERROR_CODES$1)[keyof typeof ERROR_CODES$1];
|
|
@@ -81,19 +294,38 @@ interface ApiErrorBody {
|
|
|
81
294
|
code: ErrorCode$1;
|
|
82
295
|
message: string;
|
|
83
296
|
details?: unknown;
|
|
297
|
+
requestId?: string;
|
|
84
298
|
};
|
|
85
299
|
}
|
|
86
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Errors surfaced by the SDK. `code` mirrors the server's stable error codes
|
|
303
|
+
* and `requestId` correlates with server logs.
|
|
304
|
+
*/
|
|
87
305
|
declare class OmniCloudError extends Error {
|
|
88
306
|
readonly status: number;
|
|
89
307
|
readonly code: string;
|
|
90
|
-
|
|
308
|
+
readonly requestId: string | undefined;
|
|
309
|
+
readonly details: unknown;
|
|
310
|
+
constructor(status: number, code: string, message: string, requestId?: string | undefined, details?: unknown);
|
|
311
|
+
}
|
|
312
|
+
interface RetryOptions {
|
|
313
|
+
/** Total attempts including the first. Default 3. */
|
|
314
|
+
attempts?: number;
|
|
315
|
+
/** Base backoff in ms (doubles per retry). Default 300. */
|
|
316
|
+
baseDelayMs?: number;
|
|
91
317
|
}
|
|
92
318
|
interface OmniCloudClientOptions {
|
|
93
|
-
/** Base URL of an OmniCloud server.
|
|
319
|
+
/** Base URL of an OmniCloud server (e.g. "https://cloud.example"). */
|
|
94
320
|
baseUrl?: string;
|
|
95
|
-
/**
|
|
321
|
+
/** Bearer token, when the deployment uses token auth instead of cookies. */
|
|
322
|
+
token?: string;
|
|
323
|
+
/** Custom fetch implementation (tests, polyfills). */
|
|
96
324
|
fetchImpl?: typeof fetch;
|
|
325
|
+
/** Retry policy for transient failures. */
|
|
326
|
+
retry?: RetryOptions;
|
|
327
|
+
/** Extra headers sent with every request. */
|
|
328
|
+
headers?: Record<string, string>;
|
|
97
329
|
}
|
|
98
330
|
interface UploadProgress {
|
|
99
331
|
loaded: number;
|
|
@@ -103,22 +335,175 @@ interface UploadProgress {
|
|
|
103
335
|
interface UploadOptions {
|
|
104
336
|
folderId?: string | null;
|
|
105
337
|
onProgress?: (progress: UploadProgress) => void;
|
|
338
|
+
/** Aborts the upload. */
|
|
339
|
+
signal?: AbortSignal;
|
|
340
|
+
/** Number of retry attempts for failed uploads. Default 2. */
|
|
341
|
+
retry?: number;
|
|
106
342
|
}
|
|
107
343
|
interface UploadInput {
|
|
108
|
-
/** File content:
|
|
344
|
+
/** File content: Blob/File in the browser, Buffer/Uint8Array in Node. */
|
|
109
345
|
data: Blob | Uint8Array;
|
|
346
|
+
/** Filename; also used to derive the MIME type server-side. */
|
|
110
347
|
name: string;
|
|
111
348
|
}
|
|
349
|
+
interface DownloadProgress {
|
|
350
|
+
loaded: number;
|
|
351
|
+
total: number | null;
|
|
352
|
+
percent: number | null;
|
|
353
|
+
}
|
|
354
|
+
interface DownloadResult {
|
|
355
|
+
data: Buffer | Uint8Array;
|
|
356
|
+
contentType: string;
|
|
357
|
+
size: number;
|
|
358
|
+
sha256: string | null;
|
|
359
|
+
integrityVerified: boolean | null;
|
|
360
|
+
}
|
|
112
361
|
/**
|
|
113
|
-
*
|
|
362
|
+
* Naming: every endpoint group is a namespaced sub-client so calls read like
|
|
363
|
+
*
|
|
364
|
+
* cloud.files.list({ folderId })
|
|
365
|
+
* cloud.trash.empty()
|
|
366
|
+
* cloud.storage.stats()
|
|
114
367
|
*
|
|
115
|
-
* Works in browsers (cookie sessions, upload progress
|
|
116
|
-
* (fetch
|
|
368
|
+
* Works in browsers (cookie sessions, XHR upload progress) and Node
|
|
369
|
+
* (fetch, Buffer downloads).
|
|
117
370
|
*/
|
|
118
371
|
declare class OmniCloudClient {
|
|
119
|
-
|
|
120
|
-
|
|
372
|
+
readonly files: FilesApi;
|
|
373
|
+
readonly folders: FoldersApi;
|
|
374
|
+
readonly trash: TrashApi;
|
|
375
|
+
readonly storage: StorageApi;
|
|
376
|
+
readonly search: SearchApi;
|
|
377
|
+
readonly recent: RecentApi;
|
|
378
|
+
readonly activity: ActivityApi;
|
|
379
|
+
readonly auth: AuthApi;
|
|
380
|
+
/** @internal */
|
|
381
|
+
readonly baseUrl: string;
|
|
382
|
+
/** @internal */
|
|
383
|
+
readonly fetchImpl: typeof fetch;
|
|
384
|
+
/** @internal */
|
|
385
|
+
readonly retry: Required<RetryOptions>;
|
|
386
|
+
/** @internal */
|
|
387
|
+
readonly defaultHeaders: Record<string, string>;
|
|
121
388
|
constructor(options?: OmniCloudClientOptions);
|
|
389
|
+
/** Low-level request helper (retries transient failures). */
|
|
390
|
+
request<T>(method: string, path: string, body?: unknown): Promise<T>;
|
|
391
|
+
/** @internal */
|
|
392
|
+
rawRequest<T>(method: string, path: string, body?: unknown): Promise<T>;
|
|
393
|
+
/** @internal */
|
|
394
|
+
withRetry<T>(operation: () => Promise<T>): Promise<T>;
|
|
395
|
+
/** @internal */
|
|
396
|
+
requestBinary(path: string, onProgress?: (p: DownloadProgress) => void): Promise<DownloadResult>;
|
|
397
|
+
}
|
|
398
|
+
declare class FilesApi {
|
|
399
|
+
private readonly client;
|
|
400
|
+
constructor(client: OmniCloudClient);
|
|
401
|
+
/** `folderId: null` = root; omit it to list every active file. */
|
|
402
|
+
list(query?: ListQuery): Promise<FilesPageDTO>;
|
|
403
|
+
get(id: string): Promise<FileDTO$1>;
|
|
404
|
+
versions(id: string): Promise<FileVersionDTO[]>;
|
|
405
|
+
/**
|
|
406
|
+
* Uploads a file with progress and retry support. Uses XMLHttpRequest when
|
|
407
|
+
* available (browsers) so progress and cancellation work; falls back to
|
|
408
|
+
* fetch elsewhere.
|
|
409
|
+
*/
|
|
410
|
+
upload(input: UploadInput, options?: UploadOptions): Promise<FileDTO$1>;
|
|
411
|
+
private uploadOnce;
|
|
412
|
+
private uploadViaXhr;
|
|
413
|
+
/** Uploads a new version of an existing file. */
|
|
414
|
+
replace(id: string, input: UploadInput): Promise<FileDTO$1>;
|
|
415
|
+
download(id: string, onProgress?: (p: DownloadProgress) => void): Promise<DownloadResult>;
|
|
416
|
+
/** URL suitable for direct download (anchor href). */
|
|
417
|
+
downloadUrl(id: string): string;
|
|
418
|
+
rename(id: string, name: string): Promise<FileDTO$1>;
|
|
419
|
+
star(id: string, starred?: boolean): Promise<FileDTO$1>;
|
|
420
|
+
move(id: string, folderId: string | null): Promise<FileDTO$1>;
|
|
421
|
+
trash(id: string): Promise<FileDTO$1>;
|
|
422
|
+
restore(id: string): Promise<FileDTO$1>;
|
|
423
|
+
/** Permanently deletes a file and its Telegram objects. */
|
|
424
|
+
delete(id: string): Promise<void>;
|
|
425
|
+
/** Batch operation: trash | restore | delete | star | unstar | move. */
|
|
426
|
+
batch(operation: "trash" | "restore" | "delete" | "star" | "unstar" | "move", ids: string[], options?: {
|
|
427
|
+
folderId?: string | null;
|
|
428
|
+
}): Promise<BatchResultDTO>;
|
|
429
|
+
}
|
|
430
|
+
declare class FoldersApi {
|
|
431
|
+
private readonly client;
|
|
432
|
+
constructor(client: OmniCloudClient);
|
|
433
|
+
list(query?: ListQuery & {
|
|
434
|
+
parentId?: string | null;
|
|
435
|
+
}): Promise<FoldersPageDTO>;
|
|
436
|
+
/** The full active folder tree (for move dialogs). */
|
|
437
|
+
tree(): Promise<FolderDTO$1[]>;
|
|
438
|
+
create(name: string, parentId?: string | null): Promise<FolderDTO$1>;
|
|
439
|
+
rename(id: string, name: string): Promise<FolderDTO$1>;
|
|
440
|
+
star(id: string, starred?: boolean): Promise<FolderDTO$1>;
|
|
441
|
+
move(id: string, parentId: string | null): Promise<FolderDTO$1>;
|
|
442
|
+
trash(id: string): Promise<FolderMutationResultDTO>;
|
|
443
|
+
restore(id: string): Promise<FolderMutationResultDTO>;
|
|
444
|
+
/** Permanently deletes the folder subtree and its Telegram objects. */
|
|
445
|
+
delete(id: string): Promise<FolderMutationResultDTO>;
|
|
446
|
+
batch(operation: "trash" | "restore" | "delete" | "star" | "unstar" | "move", ids: string[], options?: {
|
|
447
|
+
parentId?: string | null;
|
|
448
|
+
}): Promise<BatchResultDTO>;
|
|
449
|
+
}
|
|
450
|
+
declare class TrashApi {
|
|
451
|
+
private readonly client;
|
|
452
|
+
constructor(client: OmniCloudClient);
|
|
453
|
+
list(query?: ListQuery): Promise<TrashPageDTO>;
|
|
454
|
+
/** Permanently removes everything in the Trash. */
|
|
455
|
+
empty(): Promise<EmptyTrashResultDTO>;
|
|
456
|
+
}
|
|
457
|
+
declare class StorageApi {
|
|
458
|
+
private readonly client;
|
|
459
|
+
constructor(client: OmniCloudClient);
|
|
460
|
+
/** Creates the private storage channel if it does not exist yet. */
|
|
461
|
+
ensure(): Promise<StorageDTO>;
|
|
462
|
+
stats(): Promise<StorageStatsDTO>;
|
|
463
|
+
health(): Promise<{
|
|
464
|
+
health: StorageHealthDTO;
|
|
465
|
+
stats: StorageStatsDTO;
|
|
466
|
+
}>;
|
|
467
|
+
/** Probes the storage backend; `deep` performs a real Telegram round-trip. */
|
|
468
|
+
check(deep?: boolean): Promise<StorageHealthDTO>;
|
|
469
|
+
/** Read-only integrity scan; nothing is repaired or deleted. */
|
|
470
|
+
integrityCheck(options?: {
|
|
471
|
+
deep?: boolean;
|
|
472
|
+
}): Promise<IntegrityReportDTO$1>;
|
|
473
|
+
/** Public server health (no authentication required). */
|
|
474
|
+
serverHealth(): Promise<HealthDTO>;
|
|
475
|
+
}
|
|
476
|
+
declare class SearchApi {
|
|
477
|
+
private readonly client;
|
|
478
|
+
constructor(client: OmniCloudClient);
|
|
479
|
+
/**
|
|
480
|
+
* Searches with the v0.2 query language:
|
|
481
|
+
* `type:pdf`, `size:>100MB`, `folder:Projects`, `starred:true`, `after:2026-01-01`
|
|
482
|
+
*/
|
|
483
|
+
query(q: string, page?: {
|
|
484
|
+
page?: number;
|
|
485
|
+
limit?: number;
|
|
486
|
+
}): Promise<SearchResultDTO$1>;
|
|
487
|
+
}
|
|
488
|
+
declare class RecentApi {
|
|
489
|
+
private readonly client;
|
|
490
|
+
constructor(client: OmniCloudClient);
|
|
491
|
+
list(page?: {
|
|
492
|
+
page?: number;
|
|
493
|
+
limit?: number;
|
|
494
|
+
}): Promise<RecentPageDTO$1>;
|
|
495
|
+
}
|
|
496
|
+
declare class ActivityApi {
|
|
497
|
+
private readonly client;
|
|
498
|
+
constructor(client: OmniCloudClient);
|
|
499
|
+
list(page?: {
|
|
500
|
+
page?: number;
|
|
501
|
+
limit?: number;
|
|
502
|
+
}): Promise<ActivityPageDTO>;
|
|
503
|
+
}
|
|
504
|
+
declare class AuthApi {
|
|
505
|
+
private readonly client;
|
|
506
|
+
constructor(client: OmniCloudClient);
|
|
122
507
|
me(): Promise<SessionInfo>;
|
|
123
508
|
startTelegramLogin(phone: string): Promise<void>;
|
|
124
509
|
verifyTelegramCode(phone: string, code: string): Promise<TelegramVerifyResponse>;
|
|
@@ -126,42 +511,96 @@ declare class OmniCloudClient {
|
|
|
126
511
|
status: "ok";
|
|
127
512
|
user: UserDTO;
|
|
128
513
|
}>;
|
|
129
|
-
ensureStorage(): Promise<StorageDTO>;
|
|
130
514
|
logout(): Promise<void>;
|
|
131
|
-
listFolders(parentId: string | null): Promise<FolderDTO[]>;
|
|
132
|
-
folderTree(): Promise<FolderDTO[]>;
|
|
133
|
-
createFolder(name: string, parentId?: string | null): Promise<FolderDTO>;
|
|
134
|
-
renameFolder(id: string, name: string): Promise<FolderDTO>;
|
|
135
|
-
moveFolder(id: string, parentId: string | null): Promise<FolderDTO>;
|
|
136
|
-
deleteFolder(id: string): Promise<DeleteFolderResultDTO>;
|
|
137
|
-
listFiles(folderId: string | null): Promise<FileDTO[]>;
|
|
138
|
-
getFile(id: string): Promise<FileDTO>;
|
|
139
|
-
renameFile(id: string, name: string): Promise<FileDTO>;
|
|
140
|
-
moveFile(id: string, folderId: string | null): Promise<FileDTO>;
|
|
141
|
-
deleteFile(id: string): Promise<void>;
|
|
142
|
-
/** URL of the download endpoint (usable as href for direct downloads). */
|
|
143
|
-
fileDownloadUrl(id: string): string;
|
|
144
|
-
/**
|
|
145
|
-
* Uploads a file. Uses XMLHttpRequest when available so `onProgress`
|
|
146
|
-
* works (browsers); falls back to fetch without progress in Node.
|
|
147
|
-
*/
|
|
148
|
-
uploadFile(input: UploadInput, options?: UploadOptions): Promise<FileDTO>;
|
|
149
|
-
search(query: string): Promise<SearchResultDTO>;
|
|
150
|
-
private uploadViaXhr;
|
|
151
|
-
private request;
|
|
152
515
|
}
|
|
153
516
|
|
|
517
|
+
/** Connection state of the underlying storage backend. */
|
|
518
|
+
type ConnectionState = "DISCONNECTED" | "CONNECTING" | "CONNECTED" | "RECONNECTING" | "ERROR" | "AUTH_REQUIRED";
|
|
519
|
+
interface FileDTO {
|
|
520
|
+
id: string;
|
|
521
|
+
name: string;
|
|
522
|
+
size: number;
|
|
523
|
+
mimeType: string;
|
|
524
|
+
sha256: string;
|
|
525
|
+
folderId: string | null;
|
|
526
|
+
starred: boolean;
|
|
527
|
+
trashed: boolean;
|
|
528
|
+
deletedAt: string | null;
|
|
529
|
+
versionCount: number;
|
|
530
|
+
currentVersionId: string | null;
|
|
531
|
+
createdAt: string;
|
|
532
|
+
updatedAt: string;
|
|
533
|
+
}
|
|
534
|
+
interface FolderDTO {
|
|
535
|
+
id: string;
|
|
536
|
+
name: string;
|
|
537
|
+
parentId: string | null;
|
|
538
|
+
starred: boolean;
|
|
539
|
+
trashed: boolean;
|
|
540
|
+
deletedAt: string | null;
|
|
541
|
+
createdAt: string;
|
|
542
|
+
updatedAt: string;
|
|
543
|
+
}
|
|
544
|
+
interface SearchResultDTO {
|
|
545
|
+
files: FileDTO[];
|
|
546
|
+
folders: FolderDTO[];
|
|
547
|
+
pagination: PaginationDTO;
|
|
548
|
+
}
|
|
549
|
+
interface PaginationDTO {
|
|
550
|
+
page: number;
|
|
551
|
+
limit: number;
|
|
552
|
+
total: number;
|
|
553
|
+
hasMore: boolean;
|
|
554
|
+
}
|
|
555
|
+
interface RecentItemDTO {
|
|
556
|
+
file: FileDTO;
|
|
557
|
+
lastAction: ActivityAction;
|
|
558
|
+
lastActionAt: string;
|
|
559
|
+
}
|
|
560
|
+
interface RecentPageDTO {
|
|
561
|
+
items: RecentItemDTO[];
|
|
562
|
+
pagination: PaginationDTO;
|
|
563
|
+
}
|
|
564
|
+
declare const ACTIVITY_ACTIONS: readonly ["upload", "download", "open", "rename", "move", "star", "unstar", "trash", "restore", "delete", "create_folder", "replace"];
|
|
565
|
+
type ActivityAction = (typeof ACTIVITY_ACTIONS)[number];
|
|
566
|
+
declare const RESOURCE_TYPES: readonly ["file", "folder", "storage"];
|
|
567
|
+
type ResourceType = (typeof RESOURCE_TYPES)[number];
|
|
568
|
+
interface IntegrityIssueDTO {
|
|
569
|
+
fileId: string;
|
|
570
|
+
name: string;
|
|
571
|
+
kind: "missing" | "size_mismatch" | "unreadable" | "hash_mismatch";
|
|
572
|
+
detail: string | null;
|
|
573
|
+
}
|
|
574
|
+
interface IntegrityReportDTO {
|
|
575
|
+
checkedAt: string;
|
|
576
|
+
filesChecked: number;
|
|
577
|
+
healthy: number;
|
|
578
|
+
missing: number;
|
|
579
|
+
inconsistent: number;
|
|
580
|
+
unreadable: number;
|
|
581
|
+
issues: IntegrityIssueDTO[];
|
|
582
|
+
durationMs: number;
|
|
583
|
+
}
|
|
154
584
|
/** Stable machine-readable error codes returned in API error bodies. */
|
|
155
585
|
declare const ERROR_CODES: {
|
|
156
|
-
readonly
|
|
157
|
-
readonly
|
|
158
|
-
readonly
|
|
586
|
+
readonly AUTH_REQUIRED: "AUTH_REQUIRED";
|
|
587
|
+
readonly AUTH_INVALID: "AUTH_INVALID";
|
|
588
|
+
readonly PERMISSION_DENIED: "PERMISSION_DENIED";
|
|
589
|
+
readonly INVALID_REQUEST: "INVALID_REQUEST";
|
|
159
590
|
readonly NOT_FOUND: "NOT_FOUND";
|
|
591
|
+
readonly FILE_NOT_FOUND: "FILE_NOT_FOUND";
|
|
592
|
+
readonly FOLDER_NOT_FOUND: "FOLDER_NOT_FOUND";
|
|
160
593
|
readonly CONFLICT: "CONFLICT";
|
|
161
594
|
readonly PAYLOAD_TOO_LARGE: "PAYLOAD_TOO_LARGE";
|
|
162
595
|
readonly RATE_LIMITED: "RATE_LIMITED";
|
|
163
596
|
readonly STORAGE_NOT_INITIALIZED: "STORAGE_NOT_INITIALIZED";
|
|
164
|
-
readonly
|
|
597
|
+
readonly STORAGE_UNAVAILABLE: "STORAGE_UNAVAILABLE";
|
|
598
|
+
readonly TELEGRAM_AUTH_REQUIRED: "TELEGRAM_AUTH_REQUIRED";
|
|
599
|
+
readonly TELEGRAM_CONNECTION_FAILED: "TELEGRAM_CONNECTION_FAILED";
|
|
600
|
+
readonly TELEGRAM_FILE_NOT_FOUND: "TELEGRAM_FILE_NOT_FOUND";
|
|
601
|
+
readonly UPLOAD_FAILED: "UPLOAD_FAILED";
|
|
602
|
+
readonly DOWNLOAD_FAILED: "DOWNLOAD_FAILED";
|
|
603
|
+
readonly INTEGRITY_CHECK_FAILED: "INTEGRITY_CHECK_FAILED";
|
|
165
604
|
readonly INTERNAL: "INTERNAL_ERROR";
|
|
166
605
|
};
|
|
167
606
|
type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
|
@@ -176,10 +615,11 @@ declare class DomainError extends Error {
|
|
|
176
615
|
readonly details?: unknown;
|
|
177
616
|
constructor(code: ErrorCode, status: number, message: string, details?: unknown);
|
|
178
617
|
}
|
|
179
|
-
/**
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
618
|
+
/**
|
|
619
|
+
* Wraps an arbitrary provider failure as an UploadFailedError (the default
|
|
620
|
+
* storage-operation error), preserving domain errors untouched.
|
|
621
|
+
*/
|
|
622
|
+
declare function mapProviderError(message: string, error: unknown): DomainError;
|
|
183
623
|
|
|
184
624
|
/**
|
|
185
625
|
* Domain records. Repository implementations (e.g. the Prisma layer) map
|
|
@@ -189,6 +629,7 @@ declare class StorageProviderError extends DomainError {
|
|
|
189
629
|
* Telegram identifiers are represented as strings/numbers that fit in
|
|
190
630
|
* JavaScript's safe integer range; the persistence layer converts as needed.
|
|
191
631
|
*/
|
|
632
|
+
|
|
192
633
|
interface UserRecord {
|
|
193
634
|
id: string;
|
|
194
635
|
telegramUserId: string;
|
|
@@ -216,6 +657,15 @@ interface FolderRecord {
|
|
|
216
657
|
userId: string;
|
|
217
658
|
parentId: string | null;
|
|
218
659
|
name: string;
|
|
660
|
+
starred: boolean;
|
|
661
|
+
/** Soft-delete marker (null = active). */
|
|
662
|
+
deletedAt: Date | null;
|
|
663
|
+
/**
|
|
664
|
+
* Groups everything moved to the Trash by one operation, so restoring a
|
|
665
|
+
* folder restores exactly what it took (and leaves separately-trashed
|
|
666
|
+
* items alone). Null for items trashed individually.
|
|
667
|
+
*/
|
|
668
|
+
trashBatchId: string | null;
|
|
219
669
|
createdAt: Date;
|
|
220
670
|
updatedAt: Date;
|
|
221
671
|
}
|
|
@@ -228,9 +678,83 @@ interface FileRecord {
|
|
|
228
678
|
mimeType: string;
|
|
229
679
|
sha256: string;
|
|
230
680
|
telegramMessageId: number;
|
|
681
|
+
starred: boolean;
|
|
682
|
+
/** Soft-delete marker (null = active). */
|
|
683
|
+
deletedAt: Date | null;
|
|
684
|
+
/** See FolderRecord.trashBatchId. */
|
|
685
|
+
trashBatchId: string | null;
|
|
686
|
+
/** Current version row, if versioning has been initialized for this file. */
|
|
687
|
+
currentVersionId: string | null;
|
|
688
|
+
versionCount: number;
|
|
231
689
|
createdAt: Date;
|
|
232
690
|
updatedAt: Date;
|
|
233
691
|
}
|
|
692
|
+
interface FileVersionRecord {
|
|
693
|
+
id: string;
|
|
694
|
+
fileId: string;
|
|
695
|
+
userId: string;
|
|
696
|
+
versionNumber: number;
|
|
697
|
+
size: number;
|
|
698
|
+
mimeType: string;
|
|
699
|
+
sha256: string;
|
|
700
|
+
telegramMessageId: number;
|
|
701
|
+
createdAt: Date;
|
|
702
|
+
}
|
|
703
|
+
interface ActivityEventRecord {
|
|
704
|
+
id: string;
|
|
705
|
+
userId: string;
|
|
706
|
+
action: ActivityAction;
|
|
707
|
+
resourceType: ResourceType;
|
|
708
|
+
resourceId: string;
|
|
709
|
+
resourceName: string | null;
|
|
710
|
+
metadata: Record<string, unknown> | null;
|
|
711
|
+
createdAt: Date;
|
|
712
|
+
}
|
|
713
|
+
/** Resolved, repository-ready item query (search parser output resolved). */
|
|
714
|
+
interface ItemQuery {
|
|
715
|
+
/** Restrict to a folder (null = root). Undefined = any folder. */
|
|
716
|
+
folderId?: string | null;
|
|
717
|
+
/** Restrict to a set of folder ids (used for `folder:Name` filters). */
|
|
718
|
+
folderIds?: string[];
|
|
719
|
+
nameContains?: string;
|
|
720
|
+
/** MIME matchers: exact values or prefixes ending in "/". */
|
|
721
|
+
mimeMatchers?: readonly string[];
|
|
722
|
+
ext?: string;
|
|
723
|
+
starred?: boolean;
|
|
724
|
+
/** false = active only, true = trashed only, undefined = both. */
|
|
725
|
+
trashed?: boolean;
|
|
726
|
+
minSize?: number;
|
|
727
|
+
maxSize?: number;
|
|
728
|
+
from?: Date;
|
|
729
|
+
to?: Date;
|
|
730
|
+
sort?: SortField;
|
|
731
|
+
order?: SortOrder;
|
|
732
|
+
}
|
|
733
|
+
interface PageRequest {
|
|
734
|
+
page: number;
|
|
735
|
+
limit: number;
|
|
736
|
+
}
|
|
737
|
+
interface Paged<T> {
|
|
738
|
+
items: T[];
|
|
739
|
+
total: number;
|
|
740
|
+
}
|
|
741
|
+
interface FileTypeBucket {
|
|
742
|
+
category: string;
|
|
743
|
+
bytes: number;
|
|
744
|
+
count: number;
|
|
745
|
+
}
|
|
746
|
+
interface StorageStats {
|
|
747
|
+
fileCount: number;
|
|
748
|
+
folderCount: number;
|
|
749
|
+
totalBytes: number;
|
|
750
|
+
trashBytes: number;
|
|
751
|
+
trashFileCount: number;
|
|
752
|
+
starredCount: number;
|
|
753
|
+
byType: FileTypeBucket[];
|
|
754
|
+
largestFiles: FileRecord[];
|
|
755
|
+
/** Optional quota ceiling (null = unlimited, the v0.2 default). */
|
|
756
|
+
quotaBytes: number | null;
|
|
757
|
+
}
|
|
234
758
|
|
|
235
759
|
/**
|
|
236
760
|
* Repository interfaces. The application depends on these, not on a specific
|
|
@@ -263,42 +787,116 @@ interface StorageRepository {
|
|
|
263
787
|
telegramAccessHash: string;
|
|
264
788
|
}): Promise<StorageRecord>;
|
|
265
789
|
}
|
|
790
|
+
interface FolderCreateInput {
|
|
791
|
+
userId: string;
|
|
792
|
+
parentId: string | null;
|
|
793
|
+
name: string;
|
|
794
|
+
}
|
|
266
795
|
interface FolderRepository {
|
|
267
|
-
create(input:
|
|
268
|
-
userId: string;
|
|
269
|
-
parentId: string | null;
|
|
270
|
-
name: string;
|
|
271
|
-
}): Promise<FolderRecord>;
|
|
796
|
+
create(input: FolderCreateInput): Promise<FolderRecord>;
|
|
272
797
|
findById(id: string): Promise<FolderRecord | null>;
|
|
273
|
-
/** Every folder belonging to the user (used for trees and cascade
|
|
798
|
+
/** Every folder belonging to the user (used for trees and cascade logic). */
|
|
274
799
|
listByUser(userId: string): Promise<FolderRecord[]>;
|
|
275
800
|
listChildren(userId: string, parentId: string | null): Promise<FolderRecord[]>;
|
|
801
|
+
/** Filtered, sorted, paginated listing. */
|
|
802
|
+
query(userId: string, query: ItemQuery, page: PageRequest): Promise<Paged<FolderRecord>>;
|
|
803
|
+
/** Folder ids matching a name substring (used to resolve `folder:` filters). */
|
|
804
|
+
findIdsByName(userId: string, name: string): Promise<string[]>;
|
|
276
805
|
update(id: string, patch: {
|
|
277
806
|
name?: string;
|
|
278
807
|
parentId?: string | null;
|
|
808
|
+
starred?: boolean;
|
|
809
|
+
deletedAt?: Date | null;
|
|
810
|
+
trashBatchId?: string | null;
|
|
279
811
|
}): Promise<FolderRecord>;
|
|
812
|
+
/** Bulk trash/restore/star without touching files. */
|
|
813
|
+
updateMany(ids: string[], patch: {
|
|
814
|
+
starred?: boolean;
|
|
815
|
+
deletedAt?: Date | null;
|
|
816
|
+
trashBatchId?: string | null;
|
|
817
|
+
}): Promise<number>;
|
|
280
818
|
deleteMany(ids: string[]): Promise<void>;
|
|
281
|
-
|
|
819
|
+
countByUser(userId: string): Promise<number>;
|
|
820
|
+
}
|
|
821
|
+
interface FileCreateInput {
|
|
822
|
+
userId: string;
|
|
823
|
+
folderId: string | null;
|
|
824
|
+
name: string;
|
|
825
|
+
size: number;
|
|
826
|
+
mimeType: string;
|
|
827
|
+
sha256: string;
|
|
828
|
+
telegramMessageId: number;
|
|
282
829
|
}
|
|
283
830
|
interface FileRepository {
|
|
284
|
-
create(input:
|
|
285
|
-
userId: string;
|
|
286
|
-
folderId: string | null;
|
|
287
|
-
name: string;
|
|
288
|
-
size: number;
|
|
289
|
-
mimeType: string;
|
|
290
|
-
sha256: string;
|
|
291
|
-
telegramMessageId: number;
|
|
292
|
-
}): Promise<FileRecord>;
|
|
831
|
+
create(input: FileCreateInput): Promise<FileRecord>;
|
|
293
832
|
findById(id: string): Promise<FileRecord | null>;
|
|
833
|
+
/** Active files directly inside a folder (null = root). */
|
|
294
834
|
listByFolder(userId: string, folderId: string | null): Promise<FileRecord[]>;
|
|
295
835
|
listByUser(userId: string): Promise<FileRecord[]>;
|
|
836
|
+
listByIds(userId: string, ids: string[]): Promise<FileRecord[]>;
|
|
837
|
+
query(userId: string, query: ItemQuery, page: PageRequest): Promise<Paged<FileRecord>>;
|
|
296
838
|
update(id: string, patch: {
|
|
297
839
|
name?: string;
|
|
298
840
|
folderId?: string | null;
|
|
841
|
+
starred?: boolean;
|
|
842
|
+
deletedAt?: Date | null;
|
|
843
|
+
trashBatchId?: string | null;
|
|
844
|
+
size?: number;
|
|
845
|
+
mimeType?: string;
|
|
846
|
+
sha256?: string;
|
|
847
|
+
telegramMessageId?: number;
|
|
848
|
+
currentVersionId?: string | null;
|
|
849
|
+
versionCount?: number;
|
|
299
850
|
}): Promise<FileRecord>;
|
|
851
|
+
updateMany(ids: string[], patch: {
|
|
852
|
+
folderId?: string | null;
|
|
853
|
+
starred?: boolean;
|
|
854
|
+
deletedAt?: Date | null;
|
|
855
|
+
trashBatchId?: string | null;
|
|
856
|
+
}): Promise<number>;
|
|
857
|
+
/** Aggregate counters used by the storage dashboard. */
|
|
858
|
+
statsByUser(userId: string): Promise<{
|
|
859
|
+
fileCount: number;
|
|
860
|
+
folderCount: number;
|
|
861
|
+
totalBytes: number;
|
|
862
|
+
trashBytes: number;
|
|
863
|
+
trashFileCount: number;
|
|
864
|
+
starredCount: number;
|
|
865
|
+
}>;
|
|
300
866
|
deleteMany(ids: string[]): Promise<void>;
|
|
301
|
-
|
|
867
|
+
createVersion(input: {
|
|
868
|
+
fileId: string;
|
|
869
|
+
userId: string;
|
|
870
|
+
size: number;
|
|
871
|
+
mimeType: string;
|
|
872
|
+
sha256: string;
|
|
873
|
+
telegramMessageId: number;
|
|
874
|
+
}): Promise<FileVersionRecord>;
|
|
875
|
+
listVersions(fileId: string): Promise<FileVersionRecord[]>;
|
|
876
|
+
findVersionById(versionId: string): Promise<FileVersionRecord | null>;
|
|
877
|
+
countVersions(fileId: string): Promise<number>;
|
|
878
|
+
deleteVersionsByFileIds(fileIds: string[]): Promise<void>;
|
|
879
|
+
}
|
|
880
|
+
interface ActivityRepository {
|
|
881
|
+
record(input: {
|
|
882
|
+
userId: string;
|
|
883
|
+
action: ActivityAction;
|
|
884
|
+
resourceType: ResourceType;
|
|
885
|
+
resourceId: string;
|
|
886
|
+
resourceName?: string | null;
|
|
887
|
+
metadata?: Record<string, unknown> | null;
|
|
888
|
+
}): Promise<void>;
|
|
889
|
+
/** Newest-first activity for a user. */
|
|
890
|
+
list(userId: string, page: PageRequest): Promise<Paged<ActivityEventRecord>>;
|
|
891
|
+
/**
|
|
892
|
+
* Most recent distinct files for the given actions, newest first. Only
|
|
893
|
+
* events whose action is listed are considered, so a file whose latest
|
|
894
|
+
* event is (say) a star still surfaces via its last meaningful action.
|
|
895
|
+
*/
|
|
896
|
+
recentFiles(userId: string, actions: readonly ActivityAction[], limit: number): Promise<ActivityEventRecord[]>;
|
|
897
|
+
countByUser(userId: string): Promise<number>;
|
|
898
|
+
/** Housekeeping: drop events older than the retention window. */
|
|
899
|
+
pruneOlderThan(cutoff: Date): Promise<number>;
|
|
302
900
|
}
|
|
303
901
|
interface Repos {
|
|
304
902
|
users: UserRepository;
|
|
@@ -306,11 +904,15 @@ interface Repos {
|
|
|
306
904
|
storages: StorageRepository;
|
|
307
905
|
folders: FolderRepository;
|
|
308
906
|
files: FileRepository;
|
|
907
|
+
activity: ActivityRepository;
|
|
309
908
|
}
|
|
310
909
|
|
|
311
910
|
/**
|
|
312
911
|
* Generic storage abstraction. The application must never depend on
|
|
313
912
|
* Telegram-specific details — it talks to a StorageProvider.
|
|
913
|
+
*
|
|
914
|
+
* v0.2 adds streaming, progress reporting, cancellation and health checks
|
|
915
|
+
* while keeping the abstraction provider-agnostic.
|
|
314
916
|
*/
|
|
315
917
|
/** Reference to an object inside the backing store (opaque to callers). */
|
|
316
918
|
interface StoredRef {
|
|
@@ -321,21 +923,57 @@ interface StoredObject extends StoredRef {
|
|
|
321
923
|
size: number;
|
|
322
924
|
mimeType: string;
|
|
323
925
|
}
|
|
926
|
+
/** Transfer progress emitted during upload/download. */
|
|
927
|
+
interface TransferProgress {
|
|
928
|
+
/** Bytes transferred so far. */
|
|
929
|
+
transferred: number;
|
|
930
|
+
/** Total bytes, when known upfront. */
|
|
931
|
+
total: number | null;
|
|
932
|
+
/** 0–100 when total is known, otherwise null. */
|
|
933
|
+
percent: number | null;
|
|
934
|
+
}
|
|
935
|
+
interface TransferControl {
|
|
936
|
+
/** Aborted when the caller cancels the operation. */
|
|
937
|
+
signal?: AbortSignal;
|
|
938
|
+
onProgress?: (progress: TransferProgress) => void;
|
|
939
|
+
}
|
|
940
|
+
/** Upload input. `data` is a Buffer; `stream` is used for large files. */
|
|
324
941
|
interface StorageUploadInput {
|
|
325
942
|
name: string;
|
|
326
943
|
mimeType: string;
|
|
327
|
-
|
|
944
|
+
/** Whole-object buffer (small files). */
|
|
945
|
+
data?: Buffer;
|
|
946
|
+
/** Streaming source (large files). Either `data` or `stream` is required. */
|
|
947
|
+
stream?: Readable;
|
|
948
|
+
/** Total byte length, required when streaming. */
|
|
949
|
+
size?: number;
|
|
950
|
+
}
|
|
951
|
+
interface StorageHealth {
|
|
952
|
+
/** Reachable and usable. */
|
|
953
|
+
healthy: boolean;
|
|
954
|
+
/** Round-trip latency of the probe in milliseconds, when measured. */
|
|
955
|
+
latencyMs: number | null;
|
|
956
|
+
message: string | null;
|
|
957
|
+
/** Name of the storage channel/target, when available. */
|
|
958
|
+
targetTitle: string | null;
|
|
328
959
|
}
|
|
329
960
|
interface StorageProvider {
|
|
330
961
|
readonly name: string;
|
|
331
|
-
/**
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
962
|
+
/**
|
|
963
|
+
* Uploads an object and returns the stored reference. Reports progress and
|
|
964
|
+
* honors `control.signal` for cancellation.
|
|
965
|
+
*/
|
|
966
|
+
put(input: StorageUploadInput, control?: TransferControl): Promise<StoredObject>;
|
|
967
|
+
/** Downloads the object's content as a Buffer. */
|
|
968
|
+
get(ref: StoredRef, control?: TransferControl): Promise<Buffer>;
|
|
969
|
+
/** Opens a stream for the object (large-file friendly). */
|
|
970
|
+
getStream(ref: StoredRef, control?: TransferControl): Promise<Readable>;
|
|
335
971
|
/** Deletes the object. Deleting an already-missing object must not throw. */
|
|
336
972
|
delete(ref: StoredRef): Promise<void>;
|
|
337
973
|
exists(ref: StoredRef): Promise<boolean>;
|
|
338
974
|
stat(ref: StoredRef): Promise<StoredObject | null>;
|
|
975
|
+
/** Validates that the backing store is reachable and usable. */
|
|
976
|
+
healthCheck(): Promise<StorageHealth>;
|
|
339
977
|
}
|
|
340
978
|
|
|
341
979
|
declare function sha256Hex(data: Uint8Array): string;
|
|
@@ -345,24 +983,82 @@ interface EngineUploadResult {
|
|
|
345
983
|
sha256: string;
|
|
346
984
|
size: number;
|
|
347
985
|
}
|
|
986
|
+
interface EngineRetryPolicy {
|
|
987
|
+
/** Total attempts, including the first. */
|
|
988
|
+
attempts: number;
|
|
989
|
+
/** Base delay in ms; doubles per retry. */
|
|
990
|
+
baseDelayMs: number;
|
|
991
|
+
}
|
|
348
992
|
/**
|
|
349
993
|
* Sits between the application and the raw StorageProvider and adds the
|
|
350
|
-
* cross-cutting guarantees: SHA-256 integrity
|
|
994
|
+
* cross-cutting guarantees: SHA-256 integrity, streaming, progress reporting,
|
|
995
|
+
* cancellation and bounded retries.
|
|
351
996
|
*
|
|
352
997
|
* The active provider is chosen per user by the host application (each user
|
|
353
998
|
* has their own Telegram storage channel).
|
|
354
999
|
*/
|
|
355
1000
|
declare class StorageEngine {
|
|
356
1001
|
private readonly provider;
|
|
357
|
-
|
|
1002
|
+
private readonly retryPolicy;
|
|
1003
|
+
constructor(provider: StorageProvider, retryPolicy?: EngineRetryPolicy);
|
|
358
1004
|
get providerName(): string;
|
|
359
|
-
/**
|
|
360
|
-
|
|
361
|
-
|
|
1005
|
+
/**
|
|
1006
|
+
* Uploads an object, computing its SHA-256 as it goes and reporting
|
|
1007
|
+
* progress. Retries transient failures with exponential backoff.
|
|
1008
|
+
*/
|
|
1009
|
+
upload(input: StorageUploadInput, control?: TransferControl): Promise<EngineUploadResult>;
|
|
1010
|
+
download(ref: StoredRef, control?: TransferControl): Promise<Buffer>;
|
|
1011
|
+
/** Streams the object, computing the checksum on the fly when requested. */
|
|
1012
|
+
downloadStream(ref: StoredRef, control?: TransferControl): Promise<{
|
|
1013
|
+
stream: Readable;
|
|
1014
|
+
hash: () => string | null;
|
|
1015
|
+
}>;
|
|
362
1016
|
remove(ref: StoredRef): Promise<void>;
|
|
1017
|
+
exists(ref: StoredRef): Promise<boolean>;
|
|
1018
|
+
stat(ref: StoredRef): Promise<StoredObject | null>;
|
|
1019
|
+
health(): Promise<StorageHealth>;
|
|
363
1020
|
verifyIntegrity(data: Buffer, expectedSha256: string): Promise<boolean>;
|
|
1021
|
+
/** Materializes an upload input so the checksum can be computed. */
|
|
1022
|
+
private readInput;
|
|
1023
|
+
private withRetry;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
interface RecordActivityInput {
|
|
1027
|
+
userId: string;
|
|
1028
|
+
action: ActivityAction;
|
|
1029
|
+
resourceType: ResourceType;
|
|
1030
|
+
resourceId: string;
|
|
1031
|
+
resourceName?: string | null;
|
|
1032
|
+
metadata?: Record<string, unknown> | null;
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* The narrow interface other services depend on for activity logging, so a
|
|
1036
|
+
* no-op recorder can be substituted in tests and activity failures never
|
|
1037
|
+
* break the operation being logged.
|
|
1038
|
+
*/
|
|
1039
|
+
interface ActivityRecorder {
|
|
1040
|
+
record(input: RecordActivityInput): Promise<void>;
|
|
364
1041
|
}
|
|
365
|
-
|
|
1042
|
+
/** Activity log with best-effort recording and retention pruning. */
|
|
1043
|
+
declare class ActivityService implements ActivityRecorder {
|
|
1044
|
+
private readonly activity;
|
|
1045
|
+
/** Days to retain events; older rows are pruned on demand. */
|
|
1046
|
+
private readonly retentionDays;
|
|
1047
|
+
constructor(activity: ActivityRepository,
|
|
1048
|
+
/** Days to retain events; older rows are pruned on demand. */
|
|
1049
|
+
retentionDays?: number);
|
|
1050
|
+
/**
|
|
1051
|
+
* Records an event. Never throws — a logging failure must not fail the
|
|
1052
|
+
* user-facing operation that produced it.
|
|
1053
|
+
*/
|
|
1054
|
+
record(input: RecordActivityInput): Promise<void>;
|
|
1055
|
+
list(userId: string, page: PageRequest): Promise<Paged<ActivityEventRecord>>;
|
|
1056
|
+
/** Most recent distinct files for the given actions, for the Recent view. */
|
|
1057
|
+
recentFiles(userId: string, actions: readonly ActivityAction[], limit: number): Promise<ActivityEventRecord[]>;
|
|
1058
|
+
prune(): Promise<number>;
|
|
1059
|
+
}
|
|
1060
|
+
/** A recorder that discards events (used where activity is not wired up). */
|
|
1061
|
+
declare const noopActivityRecorder: ActivityRecorder;
|
|
366
1062
|
|
|
367
1063
|
interface FileDownload {
|
|
368
1064
|
record: FileRecord;
|
|
@@ -370,64 +1066,239 @@ interface FileDownload {
|
|
|
370
1066
|
integrityVerified: boolean;
|
|
371
1067
|
}
|
|
372
1068
|
type EngineResolver = (userId: string) => Promise<StorageEngine>;
|
|
1069
|
+
interface FileUploadInput {
|
|
1070
|
+
folderId: string | null;
|
|
1071
|
+
name: string;
|
|
1072
|
+
data: Buffer;
|
|
1073
|
+
/** When set, uploads a new version of this file instead of creating one. */
|
|
1074
|
+
replaceFileId?: string;
|
|
1075
|
+
/** Progress callback forwarded from the transport layer. */
|
|
1076
|
+
onProgress?: (progress: TransferProgress) => void;
|
|
1077
|
+
signal?: AbortSignal;
|
|
1078
|
+
}
|
|
373
1079
|
/**
|
|
374
|
-
*
|
|
1080
|
+
* File metadata operations. Every method verifies ownership: records
|
|
375
1081
|
* belonging to other users are reported as missing.
|
|
1082
|
+
*
|
|
1083
|
+
* Reliability rules honored here:
|
|
1084
|
+
* - metadata is written only after the storage upload succeeds;
|
|
1085
|
+
* - permanent deletion removes the remote object before the metadata;
|
|
1086
|
+
* - client-supplied ownership is never trusted (userId is server-derived).
|
|
376
1087
|
*/
|
|
377
1088
|
declare class FileService {
|
|
378
1089
|
private readonly files;
|
|
379
1090
|
private readonly folders;
|
|
380
1091
|
private readonly engineFor;
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
}): Promise<FileRecord>;
|
|
1092
|
+
private readonly activity;
|
|
1093
|
+
constructor(files: FileRepository, folders: FolderRepository, engineFor: EngineResolver, activity?: ActivityRecorder);
|
|
1094
|
+
upload(userId: string, input: FileUploadInput): Promise<FileRecord>;
|
|
1095
|
+
/** Uploads a new version of an existing file and makes it current. */
|
|
1096
|
+
private replaceVersion;
|
|
387
1097
|
list(userId: string, folderId: string | null): Promise<FileRecord[]>;
|
|
1098
|
+
query(userId: string, input: ListQuery, page: PageRequest): Promise<Paged<FileRecord>>;
|
|
388
1099
|
get(userId: string, id: string): Promise<FileRecord>;
|
|
1100
|
+
/** Like get(), but rejects trashed files (for download/open). */
|
|
1101
|
+
getActive(userId: string, id: string): Promise<FileRecord>;
|
|
1102
|
+
listVersions(userId: string, id: string): Promise<FileVersionRecord[]>;
|
|
389
1103
|
rename(userId: string, id: string, name: string): Promise<FileRecord>;
|
|
390
1104
|
move(userId: string, id: string, folderId: string | null): Promise<FileRecord>;
|
|
1105
|
+
setStarred(userId: string, id: string, starred: boolean): Promise<FileRecord>;
|
|
1106
|
+
/**
|
|
1107
|
+
* Moves a file to Trash (soft delete). The remote object is untouched, so
|
|
1108
|
+
* restore is lossless.
|
|
1109
|
+
*/
|
|
1110
|
+
trash(userId: string, id: string): Promise<FileRecord>;
|
|
1111
|
+
/** Restores a trashed file to its previous folder (or root). */
|
|
1112
|
+
restore(userId: string, id: string): Promise<FileRecord>;
|
|
1113
|
+
/**
|
|
1114
|
+
* Permanently deletes a file: the Telegram object is removed first, then the
|
|
1115
|
+
* metadata. If the remote delete fails we keep the metadata so the user can
|
|
1116
|
+
* retry, rather than silently orphaning the storage object.
|
|
1117
|
+
*/
|
|
1118
|
+
deletePermanently(userId: string, id: string): Promise<void>;
|
|
1119
|
+
/**
|
|
1120
|
+
* v0.1-compatible hard delete used by the current API: permanently removes
|
|
1121
|
+
* the file. Retained so existing clients keep working.
|
|
1122
|
+
*/
|
|
391
1123
|
delete(userId: string, id: string): Promise<void>;
|
|
392
|
-
download(userId: string, id: string): Promise<FileDownload>;
|
|
1124
|
+
download(userId: string, id: string, control?: TransferControl): Promise<FileDownload>;
|
|
1125
|
+
/** Records an "open" event (the UI opening the details panel, say). */
|
|
1126
|
+
touch(userId: string, id: string, action?: "open"): Promise<void>;
|
|
1127
|
+
/**
|
|
1128
|
+
* Applies an operation to many files, collecting per-item failures instead
|
|
1129
|
+
* of aborting the whole batch.
|
|
1130
|
+
*/
|
|
1131
|
+
batch(userId: string, ids: string[], operation: "trash" | "restore" | "delete" | "star" | "unstar" | "move", options?: {
|
|
1132
|
+
folderId?: string | null;
|
|
1133
|
+
}): Promise<{
|
|
1134
|
+
requested: number;
|
|
1135
|
+
succeeded: number;
|
|
1136
|
+
failed: number;
|
|
1137
|
+
errors: {
|
|
1138
|
+
id: string;
|
|
1139
|
+
message: string;
|
|
1140
|
+
}[];
|
|
1141
|
+
}>;
|
|
1142
|
+
private assertFolder;
|
|
393
1143
|
}
|
|
394
1144
|
|
|
395
|
-
interface
|
|
396
|
-
|
|
397
|
-
|
|
1145
|
+
interface FolderMutationResult {
|
|
1146
|
+
affectedFolders: number;
|
|
1147
|
+
affectedFiles: number;
|
|
398
1148
|
}
|
|
399
1149
|
/**
|
|
400
|
-
* Folder
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
1150
|
+
* Folder lifecycle (v0.2):
|
|
1151
|
+
*
|
|
1152
|
+
* - Folders are virtual OmniCloud objects with a parent-child hierarchy.
|
|
1153
|
+
* - Trashing a folder trashes its whole subtree (folders + files) without
|
|
1154
|
+
* touching Telegram, so restore is lossless.
|
|
1155
|
+
* - Restoring a folder restores only the nodes that were trashed together
|
|
1156
|
+
* with it; anything the user trashed separately stays in the Trash.
|
|
1157
|
+
* - Permanent deletion removes remote objects first, then metadata.
|
|
404
1158
|
*/
|
|
405
1159
|
declare class FolderService {
|
|
406
1160
|
private readonly folders;
|
|
407
1161
|
private readonly files;
|
|
408
1162
|
private readonly engineFor;
|
|
409
|
-
|
|
1163
|
+
private readonly activity;
|
|
1164
|
+
constructor(folders: FolderRepository, files: FileRepository, engineFor: EngineResolver, activity?: ActivityRecorder);
|
|
410
1165
|
create(userId: string, input: {
|
|
411
1166
|
name: string;
|
|
412
1167
|
parentId: string | null;
|
|
413
1168
|
}): Promise<FolderRecord>;
|
|
1169
|
+
/** Active folders directly inside a folder (null = root). */
|
|
414
1170
|
listChildren(userId: string, parentId: string | null): Promise<FolderRecord[]>;
|
|
1171
|
+
query(userId: string, input: ListQuery, page: PageRequest): Promise<Paged<FolderRecord>>;
|
|
1172
|
+
/** Every active folder for the user (used to build trees). */
|
|
415
1173
|
tree(userId: string): Promise<FolderRecord[]>;
|
|
416
1174
|
get(userId: string, id: string): Promise<FolderRecord>;
|
|
417
1175
|
rename(userId: string, id: string, name: string): Promise<FolderRecord>;
|
|
418
1176
|
move(userId: string, id: string, newParentId: string | null): Promise<FolderRecord>;
|
|
419
|
-
|
|
1177
|
+
setStarred(userId: string, id: string, starred: boolean): Promise<FolderRecord>;
|
|
1178
|
+
/**
|
|
1179
|
+
* Moves a folder and its entire subtree to the Trash. Nothing is deleted
|
|
1180
|
+
* from Telegram, so restore is lossless.
|
|
1181
|
+
*/
|
|
1182
|
+
trash(userId: string, id: string): Promise<FolderMutationResult>;
|
|
1183
|
+
/**
|
|
1184
|
+
* Restores a folder subtree. Only nodes trashed at the same instant as the
|
|
1185
|
+
* folder itself are restored, so items the user trashed separately stay put.
|
|
1186
|
+
*/
|
|
1187
|
+
restore(userId: string, id: string): Promise<FolderMutationResult>;
|
|
1188
|
+
/**
|
|
1189
|
+
* Permanently deletes a folder subtree: remote objects first, then
|
|
1190
|
+
* metadata. Files whose remote delete fails are left in the Trash so the
|
|
1191
|
+
* user can retry rather than losing data silently.
|
|
1192
|
+
*/
|
|
1193
|
+
deletePermanently(userId: string, id: string): Promise<FolderMutationResult>;
|
|
1194
|
+
/**
|
|
1195
|
+
* v0.1-compatible recursive delete. Now routes through the v0.2 permanent
|
|
1196
|
+
* delete so remote cleanup and orphan prevention are consistent.
|
|
1197
|
+
*/
|
|
1198
|
+
delete(userId: string, id: string): Promise<FolderMutationResult>;
|
|
1199
|
+
/** The folder plus every descendant (BFS), optionally including trashed. */
|
|
1200
|
+
private collectSubtree;
|
|
1201
|
+
/** Ids of files inside the given folders that are not already trashed. */
|
|
1202
|
+
private collectFileIds;
|
|
420
1203
|
}
|
|
421
1204
|
|
|
422
|
-
|
|
1205
|
+
interface SearchResult {
|
|
1206
|
+
files: FileRecord[];
|
|
1207
|
+
folders: FolderRecord[];
|
|
1208
|
+
pagination: PaginationDTO;
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* PostgreSQL-backed search (no Elasticsearch, per the v0.2 scope).
|
|
1212
|
+
*
|
|
1213
|
+
* The user query is parsed into structured filters (`type:`, `size:`,
|
|
1214
|
+
* `folder:`, …) and evaluated by the repository layer, so results respect
|
|
1215
|
+
* ownership and stay safely parameterized.
|
|
1216
|
+
*/
|
|
423
1217
|
declare class SearchService {
|
|
424
1218
|
private readonly files;
|
|
425
1219
|
private readonly folders;
|
|
426
1220
|
constructor(files: FileRepository, folders: FolderRepository);
|
|
427
|
-
search(userId: string,
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
1221
|
+
search(userId: string, rawQuery: string, page: PageRequest, overrides?: ListQuery): Promise<SearchResult>;
|
|
1222
|
+
/** Convenience wrapper returning the shared DTO shape. */
|
|
1223
|
+
searchDTO(userId: string, rawQuery: string, page: PageRequest): Promise<Omit<SearchResultDTO, "files" | "folders"> & SearchResult>;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
/**
|
|
1227
|
+
* Storage dashboard statistics, computed from PostgreSQL metadata only —
|
|
1228
|
+
* no Telegram round-trips, so the page is fast and works offline.
|
|
1229
|
+
*/
|
|
1230
|
+
declare class StatsService {
|
|
1231
|
+
private readonly files;
|
|
1232
|
+
private readonly folders;
|
|
1233
|
+
constructor(files: FileRepository, folders: FolderRepository);
|
|
1234
|
+
stats(userId: string, quotaBytes?: number | null): Promise<StorageStats>;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
interface TrashListing {
|
|
1238
|
+
files: FileRecord[];
|
|
1239
|
+
folders: FolderRecord[];
|
|
1240
|
+
pagination: PaginationDTO;
|
|
1241
|
+
}
|
|
1242
|
+
interface EmptyTrashResult {
|
|
1243
|
+
deletedFiles: number;
|
|
1244
|
+
deletedFolders: number;
|
|
1245
|
+
failedFiles: number;
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Trash is a virtual view over soft-deleted rows. Listing shows only the
|
|
1249
|
+
* explicitly trashed items; emptying removes their remote objects before the
|
|
1250
|
+
* metadata, and reports failures instead of hiding them.
|
|
1251
|
+
*/
|
|
1252
|
+
declare class TrashService {
|
|
1253
|
+
private readonly files;
|
|
1254
|
+
private readonly folders;
|
|
1255
|
+
private readonly engineFor;
|
|
1256
|
+
constructor(files: FileRepository, folders: FolderRepository, engineFor: EngineResolver);
|
|
1257
|
+
list(userId: string, page: PageRequest, extra?: ListQuery): Promise<TrashListing>;
|
|
1258
|
+
/**
|
|
1259
|
+
* Permanently removes everything in the Trash for a user. Folders are
|
|
1260
|
+
* processed root-first so removing a parent also removes its contents.
|
|
1261
|
+
*/
|
|
1262
|
+
empty(userId: string): Promise<EmptyTrashResult>;
|
|
1263
|
+
/** Restores a single trashed item (used by the batch restore endpoint). */
|
|
1264
|
+
verifyTrashedFile(userId: string, id: string): Promise<FileRecord>;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
interface IntegrityCheckOptions {
|
|
1268
|
+
/** Re-download and re-hash objects (slow but authoritative). */
|
|
1269
|
+
deep?: boolean;
|
|
1270
|
+
/** Maximum files to inspect in one pass. */
|
|
1271
|
+
limit?: number;
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Detects drift between PostgreSQL metadata and Telegram storage:
|
|
1275
|
+
*
|
|
1276
|
+
* - `missing` — metadata exists, the Telegram message is gone
|
|
1277
|
+
* - `size_mismatch` — the stored object's size differs from the metadata
|
|
1278
|
+
* - `hash_mismatch` — deep mode only: downloaded bytes don't match sha256
|
|
1279
|
+
* - `unreadable` — the provider errored while inspecting the object
|
|
1280
|
+
*
|
|
1281
|
+
* The checker is strictly read-only: repairs are a separate, explicit step so
|
|
1282
|
+
* nothing is ever deleted silently (see the v0.2 reliability rules).
|
|
1283
|
+
*/
|
|
1284
|
+
declare class IntegrityService {
|
|
1285
|
+
private readonly files;
|
|
1286
|
+
private readonly engineFor;
|
|
1287
|
+
constructor(files: FileRepository, engineFor: EngineResolver);
|
|
1288
|
+
check(userId: string, options?: IntegrityCheckOptions): Promise<IntegrityReportDTO>;
|
|
1289
|
+
private inspect;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/**
|
|
1293
|
+
* Recent view: the most recently touched active files, newest activity first.
|
|
1294
|
+
* Trashed and permanently-deleted files are filtered out so the view never
|
|
1295
|
+
* surfaces items the user cannot open.
|
|
1296
|
+
*/
|
|
1297
|
+
declare class RecentService {
|
|
1298
|
+
private readonly activity;
|
|
1299
|
+
private readonly files;
|
|
1300
|
+
constructor(activity: ActivityRepository, files: FileRepository);
|
|
1301
|
+
list(userId: string, page: PageRequest): Promise<RecentPageDTO>;
|
|
431
1302
|
}
|
|
432
1303
|
|
|
433
1304
|
/**
|
|
@@ -440,26 +1311,53 @@ interface TelegramCredentials {
|
|
|
440
1311
|
apiId: number;
|
|
441
1312
|
apiHash: string;
|
|
442
1313
|
}
|
|
1314
|
+
interface ConnectionStatus {
|
|
1315
|
+
state: ConnectionState;
|
|
1316
|
+
lastError: string | null;
|
|
1317
|
+
lastCheckAt: Date | null;
|
|
1318
|
+
healthy: boolean | null;
|
|
1319
|
+
}
|
|
443
1320
|
/**
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
1321
|
+
* Owns one Telegram (MTProto) connection per authenticated user and tracks an
|
|
1322
|
+
* explicit lifecycle state for each:
|
|
1323
|
+
*
|
|
1324
|
+
* DISCONNECTED → CONNECTING → CONNECTED
|
|
1325
|
+
* ↘ RECONNECTING → CONNECTED
|
|
1326
|
+
* ↘ ERROR | AUTH_REQUIRED
|
|
1327
|
+
*
|
|
1328
|
+
* Responsibilities: connect, disconnect, reconnect, validateSession,
|
|
1329
|
+
* healthCheck, refreshConnection. Sessions never leave the server.
|
|
447
1330
|
*/
|
|
448
1331
|
declare class TelegramClientManager {
|
|
449
1332
|
private readonly credentials;
|
|
450
1333
|
private readonly sessions;
|
|
451
|
-
private
|
|
1334
|
+
private connections;
|
|
1335
|
+
private lastReconnectAt;
|
|
452
1336
|
constructor(credentials: TelegramCredentials, sessions: SessionRepository);
|
|
1337
|
+
/** Returns a connected client, (re)connecting when needed. */
|
|
453
1338
|
getForUser(userId: string): Promise<TelegramClient>;
|
|
454
|
-
/**
|
|
1339
|
+
/** Connection status for health reporting and the storage dashboard. */
|
|
1340
|
+
statusForUser(userId: string): ConnectionStatus;
|
|
1341
|
+
/**
|
|
1342
|
+
* Probes a user's connection. Cheap by default (connection state only);
|
|
1343
|
+
* `deep` performs a real Telegram round-trip to validate the session.
|
|
1344
|
+
*/
|
|
1345
|
+
healthCheck(userId: string, deep?: boolean): Promise<StorageHealth>;
|
|
1346
|
+
/** Registers an already-authenticated client (from the login flow). */
|
|
455
1347
|
cacheClient(userId: string, client: TelegramClient): void;
|
|
1348
|
+
/** Drops a user's connection (e.g. after logout) without touching storage. */
|
|
1349
|
+
disconnectUser(userId: string): Promise<void>;
|
|
456
1350
|
disconnectAll(): Promise<void>;
|
|
457
|
-
private
|
|
1351
|
+
private isConnected;
|
|
1352
|
+
private connectUser;
|
|
1353
|
+
private reconnect;
|
|
1354
|
+
private establish;
|
|
458
1355
|
}
|
|
459
1356
|
|
|
460
1357
|
/**
|
|
461
1358
|
* Maps a Telegram (GramJS) failure to a structured domain error with an
|
|
462
|
-
* appropriate HTTP status and client-facing message
|
|
1359
|
+
* appropriate HTTP status and a client-facing message that never leaks
|
|
1360
|
+
* credentials or session material.
|
|
463
1361
|
*/
|
|
464
1362
|
declare function mapTelegramError(error: unknown, fallbackMessage: string): DomainError;
|
|
465
1363
|
|
|
@@ -468,22 +1366,38 @@ interface TelegramStorageOptions {
|
|
|
468
1366
|
chatId: string;
|
|
469
1367
|
/** Channel access hash required to build the input peer. */
|
|
470
1368
|
accessHash: string;
|
|
1369
|
+
/** Human-readable channel title, surfaced in health reporting. */
|
|
1370
|
+
title?: string;
|
|
471
1371
|
}
|
|
472
1372
|
/**
|
|
473
1373
|
* StorageProvider implementation backed by the user's private Telegram
|
|
474
|
-
* channel. Files are sent as force-downloaded documents (no compression),
|
|
475
|
-
*
|
|
1374
|
+
* channel. Files are sent as force-downloaded documents (no compression), so
|
|
1375
|
+
* every upload is a document message in the channel.
|
|
1376
|
+
*
|
|
1377
|
+
* v0.2 adds progress reporting, cancellation, streamed downloads and a
|
|
1378
|
+
* health check, all translated into the provider-agnostic core vocabulary.
|
|
476
1379
|
*/
|
|
477
1380
|
declare class TelegramStorageProvider implements StorageProvider {
|
|
478
1381
|
private readonly client;
|
|
479
1382
|
readonly name = "telegram";
|
|
480
1383
|
private readonly peer;
|
|
1384
|
+
private readonly title;
|
|
481
1385
|
constructor(client: TelegramClient, options: TelegramStorageOptions);
|
|
482
|
-
put(input: StorageUploadInput): Promise<StoredObject>;
|
|
483
|
-
get(ref: StoredRef): Promise<Buffer>;
|
|
1386
|
+
put(input: StorageUploadInput, control?: TransferControl): Promise<StoredObject>;
|
|
1387
|
+
get(ref: StoredRef, control?: TransferControl): Promise<Buffer>;
|
|
1388
|
+
getStream(ref: StoredRef, control?: TransferControl): Promise<Readable>;
|
|
484
1389
|
delete(ref: StoredRef): Promise<void>;
|
|
485
1390
|
exists(ref: StoredRef): Promise<boolean>;
|
|
486
1391
|
stat(ref: StoredRef): Promise<StoredObject | null>;
|
|
1392
|
+
/**
|
|
1393
|
+
* Validates that the client is connected and the storage channel is
|
|
1394
|
+
* reachable (a real Telegram round-trip on the channel peer).
|
|
1395
|
+
*/
|
|
1396
|
+
healthCheck(): Promise<StorageHealth>;
|
|
1397
|
+
private throwIfAborted;
|
|
1398
|
+
private materialize;
|
|
1399
|
+
private documentSize;
|
|
1400
|
+
private requireMessage;
|
|
487
1401
|
private getMessage;
|
|
488
1402
|
}
|
|
489
1403
|
|
|
@@ -525,4 +1439,4 @@ declare class TelegramConnectionService {
|
|
|
525
1439
|
private disconnectQuietly;
|
|
526
1440
|
}
|
|
527
1441
|
|
|
528
|
-
export { type ApiErrorBody, type
|
|
1442
|
+
export { type ActivityEventDTO, type ActivityEventRecord, type ActivityPageDTO, type ActivityRecorder, ActivityService, type ApiErrorBody, type BatchResultDTO, type ConnectionState, type ConnectionStatus, DomainError, type DownloadProgress, type DownloadResult, ERROR_CODES$1 as ERROR_CODES, type EmptyTrashResultDTO, type EngineResolver, type EngineRetryPolicy, type EngineUploadResult, type ErrorCode$1 as ErrorCode, type FileCategory, type FileDTO$1 as FileDTO, type FileDownload, type FileRecord, FileService, type FileTypeBucket, type FileVersionDTO, type FileVersionRecord, type FilesPageDTO, type FolderDTO$1 as FolderDTO, type FolderMutationResult, type FolderMutationResultDTO, type FolderRecord, FolderService, type FoldersPageDTO, type HealthDTO, type HealthStatus, type IntegrityCheckOptions, type IntegrityIssueDTO$1 as IntegrityIssueDTO, type IntegrityReportDTO$1 as IntegrityReportDTO, IntegrityService, type ItemQuery, type ItemStatus, type ListQuery, OmniCloudClient, type OmniCloudClientOptions, OmniCloudError, type PageRequest, type Paged, type PaginationDTO$1 as PaginationDTO, type RecentItemDTO$1 as RecentItemDTO, type RecentPageDTO$1 as RecentPageDTO, RecentService, type Repos, type RetryOptions, type SearchResultDTO$1 as SearchResultDTO, SearchService, type SessionInfo, type SortField, type SortOrder, StatsService, type StorageDTO, StorageEngine, type StorageHealth, type StorageHealthDTO, type StorageProvider, type StorageRecord, type StorageStats, type StorageStatsDTO, type StorageTypeBreakdownDTO, type StorageUploadInput, type StoredObject, type StoredRef, TELEGRAM_PROVIDER, TelegramClientManager, TelegramConnectionService, type TelegramCredentials, type TelegramStorageOptions, TelegramStorageProvider, type TelegramVerifyResponse, type TransferControl, type TransferProgress, type TrashPageDTO, TrashService, type UploadInput, type UploadOptions, type UploadProgress, type UserDTO, type UserRecord, fileCategory, isFileCategory, mapProviderError, mapTelegramError, mimeFromFilename, mimeMatchersForType, noopActivityRecorder, paginationMeta, parseSearchQuery, parseSize, sanitizeFileName, sha256Hex };
|