@lacrous/omnicloud 0.2.0 → 0.2.2
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 +610 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1255 -59
- package/dist/index.d.ts +1255 -59
- package/dist/index.js +608 -98
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,80 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import { TelegramClient } from 'telegram';
|
|
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
|
+
|
|
64
|
+
/** Derives a MIME type from the filename extension. Never trusts client input. */
|
|
65
|
+
declare function mimeFromFilename(filename: string): string;
|
|
66
|
+
type FileCategory = "image" | "video" | "audio" | "pdf" | "archive" | "document" | "text" | "other";
|
|
67
|
+
/** Coarse category used for icon selection in the UI. */
|
|
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;
|
|
6
78
|
|
|
7
79
|
/**
|
|
8
80
|
* Data transfer objects, constants and error codes shared between the API,
|
|
@@ -48,7 +120,7 @@ type TelegramVerifyResponse = {
|
|
|
48
120
|
} | {
|
|
49
121
|
status: "password_required";
|
|
50
122
|
};
|
|
51
|
-
interface FileDTO {
|
|
123
|
+
interface FileDTO$1 {
|
|
52
124
|
id: string;
|
|
53
125
|
name: string;
|
|
54
126
|
size: number;
|
|
@@ -63,7 +135,7 @@ interface FileDTO {
|
|
|
63
135
|
createdAt: string;
|
|
64
136
|
updatedAt: string;
|
|
65
137
|
}
|
|
66
|
-
interface FolderDTO {
|
|
138
|
+
interface FolderDTO$1 {
|
|
67
139
|
id: string;
|
|
68
140
|
name: string;
|
|
69
141
|
parentId: string | null;
|
|
@@ -84,10 +156,10 @@ interface FileVersionDTO {
|
|
|
84
156
|
isCurrent: boolean;
|
|
85
157
|
createdAt: string;
|
|
86
158
|
}
|
|
87
|
-
interface SearchResultDTO {
|
|
88
|
-
files: FileDTO[];
|
|
89
|
-
folders: FolderDTO[];
|
|
90
|
-
pagination: PaginationDTO;
|
|
159
|
+
interface SearchResultDTO$1 {
|
|
160
|
+
files: FileDTO$1[];
|
|
161
|
+
folders: FolderDTO$1[];
|
|
162
|
+
pagination: PaginationDTO$1;
|
|
91
163
|
}
|
|
92
164
|
/** Result of a recursive folder deletion / trash operation. */
|
|
93
165
|
interface FolderMutationResultDTO {
|
|
@@ -100,42 +172,42 @@ interface EmptyTrashResultDTO {
|
|
|
100
172
|
failedFiles: number;
|
|
101
173
|
}
|
|
102
174
|
|
|
103
|
-
interface PaginationDTO {
|
|
175
|
+
interface PaginationDTO$1 {
|
|
104
176
|
page: number;
|
|
105
177
|
limit: number;
|
|
106
178
|
total: number;
|
|
107
179
|
hasMore: boolean;
|
|
108
180
|
}
|
|
109
181
|
interface FilesPageDTO {
|
|
110
|
-
files: FileDTO[];
|
|
111
|
-
pagination: PaginationDTO;
|
|
182
|
+
files: FileDTO$1[];
|
|
183
|
+
pagination: PaginationDTO$1;
|
|
112
184
|
}
|
|
113
185
|
interface FoldersPageDTO {
|
|
114
|
-
folders: FolderDTO[];
|
|
115
|
-
pagination: PaginationDTO;
|
|
186
|
+
folders: FolderDTO$1[];
|
|
187
|
+
pagination: PaginationDTO$1;
|
|
116
188
|
}
|
|
117
189
|
interface TrashPageDTO {
|
|
118
|
-
files: FileDTO[];
|
|
119
|
-
folders: FolderDTO[];
|
|
120
|
-
pagination: PaginationDTO;
|
|
190
|
+
files: FileDTO$1[];
|
|
191
|
+
folders: FolderDTO$1[];
|
|
192
|
+
pagination: PaginationDTO$1;
|
|
121
193
|
}
|
|
122
|
-
interface RecentItemDTO {
|
|
123
|
-
file: FileDTO;
|
|
124
|
-
lastAction: ActivityAction;
|
|
194
|
+
interface RecentItemDTO$1 {
|
|
195
|
+
file: FileDTO$1;
|
|
196
|
+
lastAction: ActivityAction$1;
|
|
125
197
|
lastActionAt: string;
|
|
126
198
|
}
|
|
127
|
-
interface RecentPageDTO {
|
|
128
|
-
items: RecentItemDTO[];
|
|
129
|
-
pagination: PaginationDTO;
|
|
199
|
+
interface RecentPageDTO$1 {
|
|
200
|
+
items: RecentItemDTO$1[];
|
|
201
|
+
pagination: PaginationDTO$1;
|
|
130
202
|
}
|
|
131
|
-
declare const ACTIVITY_ACTIONS: readonly ["upload", "download", "open", "rename", "move", "star", "unstar", "trash", "restore", "delete", "create_folder", "replace"];
|
|
132
|
-
type ActivityAction = (typeof ACTIVITY_ACTIONS)[number];
|
|
133
|
-
declare const RESOURCE_TYPES: readonly ["file", "folder", "storage"];
|
|
134
|
-
type ResourceType = (typeof RESOURCE_TYPES)[number];
|
|
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];
|
|
135
207
|
interface ActivityEventDTO {
|
|
136
208
|
id: string;
|
|
137
|
-
action: ActivityAction;
|
|
138
|
-
resourceType: ResourceType;
|
|
209
|
+
action: ActivityAction$1;
|
|
210
|
+
resourceType: ResourceType$1;
|
|
139
211
|
resourceId: string;
|
|
140
212
|
resourceName: string | null;
|
|
141
213
|
metadata: Record<string, unknown> | null;
|
|
@@ -143,7 +215,7 @@ interface ActivityEventDTO {
|
|
|
143
215
|
}
|
|
144
216
|
interface ActivityPageDTO {
|
|
145
217
|
events: ActivityEventDTO[];
|
|
146
|
-
pagination: PaginationDTO;
|
|
218
|
+
pagination: PaginationDTO$1;
|
|
147
219
|
}
|
|
148
220
|
interface StorageTypeBreakdownDTO {
|
|
149
221
|
category: string;
|
|
@@ -158,7 +230,7 @@ interface StorageStatsDTO {
|
|
|
158
230
|
trashFileCount: number;
|
|
159
231
|
starredCount: number;
|
|
160
232
|
byType: StorageTypeBreakdownDTO[];
|
|
161
|
-
largestFiles: FileDTO[];
|
|
233
|
+
largestFiles: FileDTO$1[];
|
|
162
234
|
quotaBytes: number | null;
|
|
163
235
|
}
|
|
164
236
|
interface HealthDTO {
|
|
@@ -168,20 +240,22 @@ interface HealthDTO {
|
|
|
168
240
|
uptimeSeconds: number;
|
|
169
241
|
version: string;
|
|
170
242
|
}
|
|
171
|
-
interface IntegrityIssueDTO {
|
|
243
|
+
interface IntegrityIssueDTO$1 {
|
|
172
244
|
fileId: string;
|
|
245
|
+
/** Set when the issue is with a historical version rather than the current object. */
|
|
246
|
+
versionId?: string;
|
|
173
247
|
name: string;
|
|
174
248
|
kind: "missing" | "size_mismatch" | "unreadable" | "hash_mismatch";
|
|
175
249
|
detail: string | null;
|
|
176
250
|
}
|
|
177
|
-
interface IntegrityReportDTO {
|
|
251
|
+
interface IntegrityReportDTO$1 {
|
|
178
252
|
checkedAt: string;
|
|
179
253
|
filesChecked: number;
|
|
180
254
|
healthy: number;
|
|
181
255
|
missing: number;
|
|
182
256
|
inconsistent: number;
|
|
183
257
|
unreadable: number;
|
|
184
|
-
issues: IntegrityIssueDTO[];
|
|
258
|
+
issues: IntegrityIssueDTO$1[];
|
|
185
259
|
durationMs: number;
|
|
186
260
|
}
|
|
187
261
|
interface BatchResultDTO {
|
|
@@ -190,12 +264,12 @@ interface BatchResultDTO {
|
|
|
190
264
|
failed: number;
|
|
191
265
|
errors: {
|
|
192
266
|
id: string;
|
|
193
|
-
code: ErrorCode;
|
|
267
|
+
code: ErrorCode$1;
|
|
194
268
|
message: string;
|
|
195
269
|
}[];
|
|
196
270
|
}
|
|
197
271
|
/** Stable machine-readable error codes returned in API error bodies. */
|
|
198
|
-
declare const ERROR_CODES: {
|
|
272
|
+
declare const ERROR_CODES$1: {
|
|
199
273
|
readonly AUTH_REQUIRED: "AUTH_REQUIRED";
|
|
200
274
|
readonly AUTH_INVALID: "AUTH_INVALID";
|
|
201
275
|
readonly PERMISSION_DENIED: "PERMISSION_DENIED";
|
|
@@ -216,10 +290,10 @@ declare const ERROR_CODES: {
|
|
|
216
290
|
readonly INTEGRITY_CHECK_FAILED: "INTEGRITY_CHECK_FAILED";
|
|
217
291
|
readonly INTERNAL: "INTERNAL_ERROR";
|
|
218
292
|
};
|
|
219
|
-
type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
|
293
|
+
type ErrorCode$1 = (typeof ERROR_CODES$1)[keyof typeof ERROR_CODES$1];
|
|
220
294
|
interface ApiErrorBody {
|
|
221
295
|
error: {
|
|
222
|
-
code: ErrorCode;
|
|
296
|
+
code: ErrorCode$1;
|
|
223
297
|
message: string;
|
|
224
298
|
details?: unknown;
|
|
225
299
|
requestId?: string;
|
|
@@ -269,8 +343,14 @@ interface UploadOptions {
|
|
|
269
343
|
retry?: number;
|
|
270
344
|
}
|
|
271
345
|
interface UploadInput {
|
|
272
|
-
/**
|
|
273
|
-
|
|
346
|
+
/**
|
|
347
|
+
* File content: a Blob/File, a Uint8Array/Buffer, a web ReadableStream, or (in
|
|
348
|
+
* Node) a Readable stream. Streams are read in chunks; the caller never has to
|
|
349
|
+
* build one large buffer.
|
|
350
|
+
*/
|
|
351
|
+
data?: Blob | Uint8Array | ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>;
|
|
352
|
+
/** Node only: a path to read the file from. Use instead of `data`. */
|
|
353
|
+
path?: string;
|
|
274
354
|
/** Filename; also used to derive the MIME type server-side. */
|
|
275
355
|
name: string;
|
|
276
356
|
}
|
|
@@ -279,6 +359,13 @@ interface DownloadProgress {
|
|
|
279
359
|
total: number | null;
|
|
280
360
|
percent: number | null;
|
|
281
361
|
}
|
|
362
|
+
interface StreamedDownload {
|
|
363
|
+
stream: ReadableStream<Uint8Array>;
|
|
364
|
+
contentType: string;
|
|
365
|
+
/** Declared byte length, or null when the server did not send one. */
|
|
366
|
+
size: number | null;
|
|
367
|
+
sha256: string | null;
|
|
368
|
+
}
|
|
282
369
|
interface DownloadResult {
|
|
283
370
|
data: Buffer | Uint8Array;
|
|
284
371
|
contentType: string;
|
|
@@ -321,6 +408,12 @@ declare class OmniCloudClient {
|
|
|
321
408
|
/** @internal */
|
|
322
409
|
withRetry<T>(operation: () => Promise<T>): Promise<T>;
|
|
323
410
|
/** @internal */
|
|
411
|
+
/**
|
|
412
|
+
* Opens a binary response as a stream. Bytes are delivered as they arrive, so
|
|
413
|
+
* memory does not grow with the file. The stream errors with DOWNLOAD_INCOMPLETE
|
|
414
|
+
* if the body ends before Content-Length.
|
|
415
|
+
*/
|
|
416
|
+
requestStream(path: string, onProgress?: (p: DownloadProgress) => void): Promise<StreamedDownload>;
|
|
324
417
|
requestBinary(path: string, onProgress?: (p: DownloadProgress) => void): Promise<DownloadResult>;
|
|
325
418
|
}
|
|
326
419
|
declare class FilesApi {
|
|
@@ -328,26 +421,42 @@ declare class FilesApi {
|
|
|
328
421
|
constructor(client: OmniCloudClient);
|
|
329
422
|
/** `folderId: null` = root; omit it to list every active file. */
|
|
330
423
|
list(query?: ListQuery): Promise<FilesPageDTO>;
|
|
331
|
-
get(id: string): Promise<FileDTO>;
|
|
424
|
+
get(id: string): Promise<FileDTO$1>;
|
|
332
425
|
versions(id: string): Promise<FileVersionDTO[]>;
|
|
333
426
|
/**
|
|
334
427
|
* Uploads a file with progress and retry support. Uses XMLHttpRequest when
|
|
335
428
|
* available (browsers) so progress and cancellation work; falls back to
|
|
336
429
|
* fetch elsewhere.
|
|
337
430
|
*/
|
|
338
|
-
upload(input: UploadInput, options?: UploadOptions): Promise<FileDTO>;
|
|
431
|
+
upload(input: UploadInput, options?: UploadOptions): Promise<FileDTO$1>;
|
|
339
432
|
private uploadOnce;
|
|
340
433
|
private uploadViaXhr;
|
|
341
434
|
/** Uploads a new version of an existing file. */
|
|
342
|
-
replace(id: string, input: UploadInput): Promise<FileDTO>;
|
|
435
|
+
replace(id: string, input: UploadInput): Promise<FileDTO$1>;
|
|
343
436
|
download(id: string, onProgress?: (p: DownloadProgress) => void): Promise<DownloadResult>;
|
|
437
|
+
/**
|
|
438
|
+
* Streams a file's bytes without holding the whole file in memory. Use this for
|
|
439
|
+
* large files. Read `stream` to the end: a truncated transfer errors instead of
|
|
440
|
+
* ending quietly.
|
|
441
|
+
*/
|
|
442
|
+
downloadStream(id: string, onProgress?: (p: DownloadProgress) => void): Promise<StreamedDownload>;
|
|
443
|
+
/**
|
|
444
|
+
* Node only. Streams a file to `targetPath` through a `.part` file that is
|
|
445
|
+
* renamed into place only after the full length is verified. A truncated or
|
|
446
|
+
* failed transfer leaves nothing at `targetPath`.
|
|
447
|
+
*/
|
|
448
|
+
downloadToFile(id: string, targetPath: string, onProgress?: (p: DownloadProgress) => void): Promise<{
|
|
449
|
+
path: string;
|
|
450
|
+
bytes: number;
|
|
451
|
+
sha256: string | null;
|
|
452
|
+
}>;
|
|
344
453
|
/** URL suitable for direct download (anchor href). */
|
|
345
454
|
downloadUrl(id: string): string;
|
|
346
|
-
rename(id: string, name: string): Promise<FileDTO>;
|
|
347
|
-
star(id: string, starred?: boolean): Promise<FileDTO>;
|
|
348
|
-
move(id: string, folderId: string | null): Promise<FileDTO>;
|
|
349
|
-
trash(id: string): Promise<FileDTO>;
|
|
350
|
-
restore(id: string): Promise<FileDTO>;
|
|
455
|
+
rename(id: string, name: string): Promise<FileDTO$1>;
|
|
456
|
+
star(id: string, starred?: boolean): Promise<FileDTO$1>;
|
|
457
|
+
move(id: string, folderId: string | null): Promise<FileDTO$1>;
|
|
458
|
+
trash(id: string): Promise<FileDTO$1>;
|
|
459
|
+
restore(id: string): Promise<FileDTO$1>;
|
|
351
460
|
/** Permanently deletes a file and its Telegram objects. */
|
|
352
461
|
delete(id: string): Promise<void>;
|
|
353
462
|
/** Batch operation: trash | restore | delete | star | unstar | move. */
|
|
@@ -362,11 +471,11 @@ declare class FoldersApi {
|
|
|
362
471
|
parentId?: string | null;
|
|
363
472
|
}): Promise<FoldersPageDTO>;
|
|
364
473
|
/** The full active folder tree (for move dialogs). */
|
|
365
|
-
tree(): Promise<FolderDTO[]>;
|
|
366
|
-
create(name: string, parentId?: string | null): Promise<FolderDTO>;
|
|
367
|
-
rename(id: string, name: string): Promise<FolderDTO>;
|
|
368
|
-
star(id: string, starred?: boolean): Promise<FolderDTO>;
|
|
369
|
-
move(id: string, parentId: string | null): Promise<FolderDTO>;
|
|
474
|
+
tree(): Promise<FolderDTO$1[]>;
|
|
475
|
+
create(name: string, parentId?: string | null): Promise<FolderDTO$1>;
|
|
476
|
+
rename(id: string, name: string): Promise<FolderDTO$1>;
|
|
477
|
+
star(id: string, starred?: boolean): Promise<FolderDTO$1>;
|
|
478
|
+
move(id: string, parentId: string | null): Promise<FolderDTO$1>;
|
|
370
479
|
trash(id: string): Promise<FolderMutationResultDTO>;
|
|
371
480
|
restore(id: string): Promise<FolderMutationResultDTO>;
|
|
372
481
|
/** Permanently deletes the folder subtree and its Telegram objects. */
|
|
@@ -397,7 +506,7 @@ declare class StorageApi {
|
|
|
397
506
|
/** Read-only integrity scan; nothing is repaired or deleted. */
|
|
398
507
|
integrityCheck(options?: {
|
|
399
508
|
deep?: boolean;
|
|
400
|
-
}): Promise<IntegrityReportDTO>;
|
|
509
|
+
}): Promise<IntegrityReportDTO$1>;
|
|
401
510
|
/** Public server health (no authentication required). */
|
|
402
511
|
serverHealth(): Promise<HealthDTO>;
|
|
403
512
|
}
|
|
@@ -411,7 +520,7 @@ declare class SearchApi {
|
|
|
411
520
|
query(q: string, page?: {
|
|
412
521
|
page?: number;
|
|
413
522
|
limit?: number;
|
|
414
|
-
}): Promise<SearchResultDTO>;
|
|
523
|
+
}): Promise<SearchResultDTO$1>;
|
|
415
524
|
}
|
|
416
525
|
declare class RecentApi {
|
|
417
526
|
private readonly client;
|
|
@@ -419,7 +528,7 @@ declare class RecentApi {
|
|
|
419
528
|
list(page?: {
|
|
420
529
|
page?: number;
|
|
421
530
|
limit?: number;
|
|
422
|
-
}): Promise<RecentPageDTO>;
|
|
531
|
+
}): Promise<RecentPageDTO$1>;
|
|
423
532
|
}
|
|
424
533
|
declare class ActivityApi {
|
|
425
534
|
private readonly client;
|
|
@@ -444,5 +553,1092 @@ declare class AuthApi {
|
|
|
444
553
|
|
|
445
554
|
/** Connection state of the underlying storage backend. */
|
|
446
555
|
type ConnectionState = "DISCONNECTED" | "CONNECTING" | "CONNECTED" | "RECONNECTING" | "ERROR" | "AUTH_REQUIRED";
|
|
556
|
+
interface FileDTO {
|
|
557
|
+
id: string;
|
|
558
|
+
name: string;
|
|
559
|
+
size: number;
|
|
560
|
+
mimeType: string;
|
|
561
|
+
sha256: string;
|
|
562
|
+
folderId: string | null;
|
|
563
|
+
starred: boolean;
|
|
564
|
+
trashed: boolean;
|
|
565
|
+
deletedAt: string | null;
|
|
566
|
+
versionCount: number;
|
|
567
|
+
currentVersionId: string | null;
|
|
568
|
+
createdAt: string;
|
|
569
|
+
updatedAt: string;
|
|
570
|
+
}
|
|
571
|
+
interface FolderDTO {
|
|
572
|
+
id: string;
|
|
573
|
+
name: string;
|
|
574
|
+
parentId: string | null;
|
|
575
|
+
starred: boolean;
|
|
576
|
+
trashed: boolean;
|
|
577
|
+
deletedAt: string | null;
|
|
578
|
+
createdAt: string;
|
|
579
|
+
updatedAt: string;
|
|
580
|
+
}
|
|
581
|
+
interface SearchResultDTO {
|
|
582
|
+
files: FileDTO[];
|
|
583
|
+
folders: FolderDTO[];
|
|
584
|
+
pagination: PaginationDTO;
|
|
585
|
+
}
|
|
586
|
+
interface PaginationDTO {
|
|
587
|
+
page: number;
|
|
588
|
+
limit: number;
|
|
589
|
+
total: number;
|
|
590
|
+
hasMore: boolean;
|
|
591
|
+
}
|
|
592
|
+
interface RecentItemDTO {
|
|
593
|
+
file: FileDTO;
|
|
594
|
+
lastAction: ActivityAction;
|
|
595
|
+
lastActionAt: string;
|
|
596
|
+
}
|
|
597
|
+
interface RecentPageDTO {
|
|
598
|
+
items: RecentItemDTO[];
|
|
599
|
+
pagination: PaginationDTO;
|
|
600
|
+
}
|
|
601
|
+
declare const ACTIVITY_ACTIONS: readonly ["upload", "download", "open", "rename", "move", "star", "unstar", "trash", "restore", "delete", "create_folder", "replace"];
|
|
602
|
+
type ActivityAction = (typeof ACTIVITY_ACTIONS)[number];
|
|
603
|
+
declare const RESOURCE_TYPES: readonly ["file", "folder", "storage"];
|
|
604
|
+
type ResourceType = (typeof RESOURCE_TYPES)[number];
|
|
605
|
+
interface IntegrityIssueDTO {
|
|
606
|
+
fileId: string;
|
|
607
|
+
/** Set when the issue is with a historical version rather than the current object. */
|
|
608
|
+
versionId?: string;
|
|
609
|
+
name: string;
|
|
610
|
+
kind: "missing" | "size_mismatch" | "unreadable" | "hash_mismatch";
|
|
611
|
+
detail: string | null;
|
|
612
|
+
}
|
|
613
|
+
interface IntegrityReportDTO {
|
|
614
|
+
checkedAt: string;
|
|
615
|
+
filesChecked: number;
|
|
616
|
+
healthy: number;
|
|
617
|
+
missing: number;
|
|
618
|
+
inconsistent: number;
|
|
619
|
+
unreadable: number;
|
|
620
|
+
issues: IntegrityIssueDTO[];
|
|
621
|
+
durationMs: number;
|
|
622
|
+
}
|
|
623
|
+
/** Stable machine-readable error codes returned in API error bodies. */
|
|
624
|
+
declare const ERROR_CODES: {
|
|
625
|
+
readonly AUTH_REQUIRED: "AUTH_REQUIRED";
|
|
626
|
+
readonly AUTH_INVALID: "AUTH_INVALID";
|
|
627
|
+
readonly PERMISSION_DENIED: "PERMISSION_DENIED";
|
|
628
|
+
readonly INVALID_REQUEST: "INVALID_REQUEST";
|
|
629
|
+
readonly NOT_FOUND: "NOT_FOUND";
|
|
630
|
+
readonly FILE_NOT_FOUND: "FILE_NOT_FOUND";
|
|
631
|
+
readonly FOLDER_NOT_FOUND: "FOLDER_NOT_FOUND";
|
|
632
|
+
readonly CONFLICT: "CONFLICT";
|
|
633
|
+
readonly PAYLOAD_TOO_LARGE: "PAYLOAD_TOO_LARGE";
|
|
634
|
+
readonly RATE_LIMITED: "RATE_LIMITED";
|
|
635
|
+
readonly STORAGE_NOT_INITIALIZED: "STORAGE_NOT_INITIALIZED";
|
|
636
|
+
readonly STORAGE_UNAVAILABLE: "STORAGE_UNAVAILABLE";
|
|
637
|
+
readonly TELEGRAM_AUTH_REQUIRED: "TELEGRAM_AUTH_REQUIRED";
|
|
638
|
+
readonly TELEGRAM_CONNECTION_FAILED: "TELEGRAM_CONNECTION_FAILED";
|
|
639
|
+
readonly TELEGRAM_FILE_NOT_FOUND: "TELEGRAM_FILE_NOT_FOUND";
|
|
640
|
+
readonly UPLOAD_FAILED: "UPLOAD_FAILED";
|
|
641
|
+
readonly DOWNLOAD_FAILED: "DOWNLOAD_FAILED";
|
|
642
|
+
readonly INTEGRITY_CHECK_FAILED: "INTEGRITY_CHECK_FAILED";
|
|
643
|
+
readonly INTERNAL: "INTERNAL_ERROR";
|
|
644
|
+
};
|
|
645
|
+
type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Base class for all domain errors. The API maps these to structured HTTP
|
|
649
|
+
* responses; everything else becomes a 500.
|
|
650
|
+
*/
|
|
651
|
+
declare class DomainError extends Error {
|
|
652
|
+
readonly code: ErrorCode;
|
|
653
|
+
readonly status: number;
|
|
654
|
+
readonly details?: unknown;
|
|
655
|
+
/** Whether repeating the same operation may succeed. Defaults to false. */
|
|
656
|
+
readonly retryable: boolean;
|
|
657
|
+
/** Server-requested delay before a retry, in seconds, when the provider gave one. */
|
|
658
|
+
readonly retryAfterSeconds?: number;
|
|
659
|
+
constructor(code: ErrorCode, status: number, message: string, details?: unknown, retry?: {
|
|
660
|
+
retryable?: boolean;
|
|
661
|
+
retryAfterSeconds?: number;
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Wraps an arbitrary provider failure as an UploadFailedError (the default
|
|
666
|
+
* storage-operation error), preserving domain errors untouched.
|
|
667
|
+
*/
|
|
668
|
+
declare function mapProviderError(message: string, error: unknown): DomainError;
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Domain records. Repository implementations (e.g. the Prisma layer) map
|
|
672
|
+
* their persistence models onto these records; the rest of the application
|
|
673
|
+
* only ever works with them.
|
|
674
|
+
*
|
|
675
|
+
* Telegram identifiers are represented as strings/numbers that fit in
|
|
676
|
+
* JavaScript's safe integer range; the persistence layer converts as needed.
|
|
677
|
+
*/
|
|
678
|
+
|
|
679
|
+
interface UserRecord {
|
|
680
|
+
id: string;
|
|
681
|
+
telegramUserId: string;
|
|
682
|
+
username: string | null;
|
|
683
|
+
firstName: string | null;
|
|
684
|
+
lastName: string | null;
|
|
685
|
+
phone: string | null;
|
|
686
|
+
createdAt: Date;
|
|
687
|
+
}
|
|
688
|
+
interface TelegramSessionRecord {
|
|
689
|
+
userId: string;
|
|
690
|
+
stringSession: string;
|
|
691
|
+
}
|
|
692
|
+
interface StorageRecord {
|
|
693
|
+
id: string;
|
|
694
|
+
userId: string;
|
|
695
|
+
provider: string;
|
|
696
|
+
title: string;
|
|
697
|
+
telegramChatId: string;
|
|
698
|
+
telegramAccessHash: string;
|
|
699
|
+
createdAt: Date;
|
|
700
|
+
}
|
|
701
|
+
interface FolderRecord {
|
|
702
|
+
id: string;
|
|
703
|
+
userId: string;
|
|
704
|
+
parentId: string | null;
|
|
705
|
+
name: string;
|
|
706
|
+
starred: boolean;
|
|
707
|
+
/** Soft-delete marker (null = active). */
|
|
708
|
+
deletedAt: Date | null;
|
|
709
|
+
/**
|
|
710
|
+
* Groups everything moved to the Trash by one operation, so restoring a
|
|
711
|
+
* folder restores exactly what it took (and leaves separately-trashed
|
|
712
|
+
* items alone). Null for items trashed individually.
|
|
713
|
+
*/
|
|
714
|
+
trashBatchId: string | null;
|
|
715
|
+
createdAt: Date;
|
|
716
|
+
updatedAt: Date;
|
|
717
|
+
}
|
|
718
|
+
interface FileRecord {
|
|
719
|
+
id: string;
|
|
720
|
+
userId: string;
|
|
721
|
+
folderId: string | null;
|
|
722
|
+
name: string;
|
|
723
|
+
size: number;
|
|
724
|
+
mimeType: string;
|
|
725
|
+
sha256: string;
|
|
726
|
+
telegramMessageId: number;
|
|
727
|
+
starred: boolean;
|
|
728
|
+
/** Soft-delete marker (null = active). */
|
|
729
|
+
deletedAt: Date | null;
|
|
730
|
+
/** See FolderRecord.trashBatchId. */
|
|
731
|
+
trashBatchId: string | null;
|
|
732
|
+
/** Current version row, if versioning has been initialized for this file. */
|
|
733
|
+
currentVersionId: string | null;
|
|
734
|
+
versionCount: number;
|
|
735
|
+
createdAt: Date;
|
|
736
|
+
updatedAt: Date;
|
|
737
|
+
}
|
|
738
|
+
interface FileVersionRecord {
|
|
739
|
+
id: string;
|
|
740
|
+
fileId: string;
|
|
741
|
+
userId: string;
|
|
742
|
+
versionNumber: number;
|
|
743
|
+
size: number;
|
|
744
|
+
mimeType: string;
|
|
745
|
+
sha256: string;
|
|
746
|
+
telegramMessageId: number;
|
|
747
|
+
createdAt: Date;
|
|
748
|
+
}
|
|
749
|
+
interface ActivityEventRecord {
|
|
750
|
+
id: string;
|
|
751
|
+
userId: string;
|
|
752
|
+
action: ActivityAction;
|
|
753
|
+
resourceType: ResourceType;
|
|
754
|
+
resourceId: string;
|
|
755
|
+
resourceName: string | null;
|
|
756
|
+
metadata: Record<string, unknown> | null;
|
|
757
|
+
createdAt: Date;
|
|
758
|
+
}
|
|
759
|
+
/** Resolved, repository-ready item query (search parser output resolved). */
|
|
760
|
+
interface ItemQuery {
|
|
761
|
+
/** Restrict to a folder (null = root). Undefined = any folder. */
|
|
762
|
+
folderId?: string | null;
|
|
763
|
+
/** Restrict to a set of folder ids (used for `folder:Name` filters). */
|
|
764
|
+
folderIds?: string[];
|
|
765
|
+
nameContains?: string;
|
|
766
|
+
/** MIME matchers: exact values or prefixes ending in "/". */
|
|
767
|
+
mimeMatchers?: readonly string[];
|
|
768
|
+
ext?: string;
|
|
769
|
+
starred?: boolean;
|
|
770
|
+
/** false = active only, true = trashed only, undefined = both. */
|
|
771
|
+
trashed?: boolean;
|
|
772
|
+
minSize?: number;
|
|
773
|
+
maxSize?: number;
|
|
774
|
+
from?: Date;
|
|
775
|
+
to?: Date;
|
|
776
|
+
sort?: SortField;
|
|
777
|
+
order?: SortOrder;
|
|
778
|
+
}
|
|
779
|
+
interface PageRequest {
|
|
780
|
+
page: number;
|
|
781
|
+
limit: number;
|
|
782
|
+
}
|
|
783
|
+
interface Paged<T> {
|
|
784
|
+
items: T[];
|
|
785
|
+
total: number;
|
|
786
|
+
}
|
|
787
|
+
interface FileTypeBucket {
|
|
788
|
+
category: string;
|
|
789
|
+
bytes: number;
|
|
790
|
+
count: number;
|
|
791
|
+
}
|
|
792
|
+
interface StorageStats {
|
|
793
|
+
fileCount: number;
|
|
794
|
+
folderCount: number;
|
|
795
|
+
totalBytes: number;
|
|
796
|
+
trashBytes: number;
|
|
797
|
+
trashFileCount: number;
|
|
798
|
+
starredCount: number;
|
|
799
|
+
byType: FileTypeBucket[];
|
|
800
|
+
largestFiles: FileRecord[];
|
|
801
|
+
/** Optional quota ceiling (null = unlimited, the v0.2 default). */
|
|
802
|
+
quotaBytes: number | null;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Upload operations are the durable record of one logical upload. They exist so
|
|
807
|
+
* that a retried request (same operation id) can resume or replay instead of
|
|
808
|
+
* writing a second Telegram object and a second file record.
|
|
809
|
+
*
|
|
810
|
+
* PENDING accepted, Telegram write not started
|
|
811
|
+
* UPLOADING Telegram write in progress or its outcome unknown
|
|
812
|
+
* COMPLETED Telegram object stored and file record committed; result is final
|
|
813
|
+
* FAILED Telegram write definitively failed; the operation may be retried
|
|
814
|
+
* under a new id
|
|
815
|
+
*/
|
|
816
|
+
type UploadOperationStatus = "PENDING" | "UPLOADING" | "COMPLETED" | "FAILED";
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Repository interfaces. The application depends on these, not on a specific
|
|
820
|
+
* database — the Prisma implementation lives in @omnicloud/database, and
|
|
821
|
+
* tests use in-memory implementations.
|
|
822
|
+
*/
|
|
823
|
+
interface UserRepository {
|
|
824
|
+
findById(id: string): Promise<UserRecord | null>;
|
|
825
|
+
findByTelegramId(telegramUserId: string): Promise<UserRecord | null>;
|
|
826
|
+
upsertFromTelegram(input: {
|
|
827
|
+
telegramUserId: string;
|
|
828
|
+
username: string | null;
|
|
829
|
+
firstName: string | null;
|
|
830
|
+
lastName: string | null;
|
|
831
|
+
phone: string | null;
|
|
832
|
+
}): Promise<UserRecord>;
|
|
833
|
+
}
|
|
834
|
+
interface SessionRepository {
|
|
835
|
+
get(userId: string): Promise<TelegramSessionRecord | null>;
|
|
836
|
+
save(userId: string, stringSession: string): Promise<void>;
|
|
837
|
+
delete(userId: string): Promise<void>;
|
|
838
|
+
}
|
|
839
|
+
interface BrowserSessionRecord {
|
|
840
|
+
id: string;
|
|
841
|
+
userId: string;
|
|
842
|
+
tokenHash: string;
|
|
843
|
+
createdAt: Date;
|
|
844
|
+
expiresAt: Date;
|
|
845
|
+
revokedAt: Date | null;
|
|
846
|
+
lastUsedAt: Date | null;
|
|
847
|
+
userAgent: string | null;
|
|
848
|
+
ip: string | null;
|
|
849
|
+
}
|
|
850
|
+
/** Server-side browser sessions. Holds only token hashes, never raw tokens. */
|
|
851
|
+
interface BrowserSessionRepository {
|
|
852
|
+
create(input: {
|
|
853
|
+
userId: string;
|
|
854
|
+
tokenHash: string;
|
|
855
|
+
expiresAt: Date;
|
|
856
|
+
userAgent: string | null;
|
|
857
|
+
ip: string | null;
|
|
858
|
+
}): Promise<BrowserSessionRecord>;
|
|
859
|
+
findByTokenHash(tokenHash: string): Promise<BrowserSessionRecord | null>;
|
|
860
|
+
touch(id: string, at: Date): Promise<void>;
|
|
861
|
+
revoke(id: string, at: Date): Promise<void>;
|
|
862
|
+
revokeAllForUser(userId: string, at: Date): Promise<number>;
|
|
863
|
+
listActiveForUser(userId: string, now: Date): Promise<BrowserSessionRecord[]>;
|
|
864
|
+
deleteExpiredBefore(cutoff: Date): Promise<number>;
|
|
865
|
+
}
|
|
866
|
+
interface StorageRepository {
|
|
867
|
+
findByUserAndProvider(userId: string, provider: string): Promise<StorageRecord | null>;
|
|
868
|
+
create(input: {
|
|
869
|
+
userId: string;
|
|
870
|
+
provider: string;
|
|
871
|
+
title: string;
|
|
872
|
+
telegramChatId: string;
|
|
873
|
+
telegramAccessHash: string;
|
|
874
|
+
}): Promise<StorageRecord>;
|
|
875
|
+
}
|
|
876
|
+
interface FolderCreateInput {
|
|
877
|
+
userId: string;
|
|
878
|
+
parentId: string | null;
|
|
879
|
+
name: string;
|
|
880
|
+
}
|
|
881
|
+
interface FolderRepository {
|
|
882
|
+
create(input: FolderCreateInput): Promise<FolderRecord>;
|
|
883
|
+
/**
|
|
884
|
+
* Re-parents a folder only if the result stays acyclic. The ancestor check and
|
|
885
|
+
* the write are one atomic step, so two concurrent moves cannot each pass the
|
|
886
|
+
* check and together create a cycle. Throws ConflictError on a cycle.
|
|
887
|
+
*/
|
|
888
|
+
moveSafely(id: string, newParentId: string | null): Promise<FolderRecord>;
|
|
889
|
+
findById(id: string): Promise<FolderRecord | null>;
|
|
890
|
+
/** Every folder belonging to the user (used for trees and cascade logic). */
|
|
891
|
+
listByUser(userId: string): Promise<FolderRecord[]>;
|
|
892
|
+
listChildren(userId: string, parentId: string | null): Promise<FolderRecord[]>;
|
|
893
|
+
/** Filtered, sorted, paginated listing. */
|
|
894
|
+
query(userId: string, query: ItemQuery, page: PageRequest): Promise<Paged<FolderRecord>>;
|
|
895
|
+
/** Folder ids matching a name substring (used to resolve `folder:` filters). */
|
|
896
|
+
findIdsByName(userId: string, name: string): Promise<string[]>;
|
|
897
|
+
update(id: string, patch: {
|
|
898
|
+
name?: string;
|
|
899
|
+
parentId?: string | null;
|
|
900
|
+
starred?: boolean;
|
|
901
|
+
deletedAt?: Date | null;
|
|
902
|
+
trashBatchId?: string | null;
|
|
903
|
+
}): Promise<FolderRecord>;
|
|
904
|
+
/** Bulk trash/restore/star without touching files. */
|
|
905
|
+
updateMany(ids: string[], patch: {
|
|
906
|
+
starred?: boolean;
|
|
907
|
+
deletedAt?: Date | null;
|
|
908
|
+
trashBatchId?: string | null;
|
|
909
|
+
}): Promise<number>;
|
|
910
|
+
deleteMany(ids: string[]): Promise<void>;
|
|
911
|
+
countByUser(userId: string): Promise<number>;
|
|
912
|
+
}
|
|
913
|
+
interface FileCreateInput {
|
|
914
|
+
userId: string;
|
|
915
|
+
folderId: string | null;
|
|
916
|
+
name: string;
|
|
917
|
+
size: number;
|
|
918
|
+
mimeType: string;
|
|
919
|
+
sha256: string;
|
|
920
|
+
telegramMessageId: number;
|
|
921
|
+
}
|
|
922
|
+
interface UploadOperationRecord {
|
|
923
|
+
id: string;
|
|
924
|
+
userId: string;
|
|
925
|
+
operationId: string;
|
|
926
|
+
status: UploadOperationStatus;
|
|
927
|
+
/** Set once the Telegram object exists, so recovery can commit without re-uploading. */
|
|
928
|
+
telegramMessageId: number | null;
|
|
929
|
+
sha256: string | null;
|
|
930
|
+
size: number | null;
|
|
931
|
+
fileId: string | null;
|
|
932
|
+
error: string | null;
|
|
933
|
+
createdAt: Date;
|
|
934
|
+
updatedAt: Date;
|
|
935
|
+
}
|
|
936
|
+
/** Durable upload attempts, scoped to the owning user. */
|
|
937
|
+
interface UploadOperationRepository {
|
|
938
|
+
findByOperationId(userId: string, operationId: string): Promise<UploadOperationRecord | null>;
|
|
939
|
+
/** Operations for a user in the given states (used by reconciliation). */
|
|
940
|
+
listByStatus(userId: string, status: UploadOperationStatus): Promise<UploadOperationRecord[]>;
|
|
941
|
+
create(input: {
|
|
942
|
+
userId: string;
|
|
943
|
+
operationId: string;
|
|
944
|
+
}): Promise<UploadOperationRecord>;
|
|
945
|
+
/**
|
|
946
|
+
* Atomically moves an operation from `from` to `to` if, and only if, it is
|
|
947
|
+
* still in `from`. Returns false when another request already moved it, so
|
|
948
|
+
* exactly one request performs the upload.
|
|
949
|
+
*/
|
|
950
|
+
claim(id: string, from: UploadOperationStatus, to: UploadOperationStatus): Promise<boolean>;
|
|
951
|
+
update(id: string, patch: Partial<Pick<UploadOperationRecord, "status" | "telegramMessageId" | "sha256" | "size" | "fileId" | "error">>): Promise<UploadOperationRecord>;
|
|
952
|
+
}
|
|
953
|
+
interface FileRepository {
|
|
954
|
+
create(input: FileCreateInput): Promise<FileRecord>;
|
|
955
|
+
findById(id: string): Promise<FileRecord | null>;
|
|
956
|
+
/** Active files directly inside a folder (null = root). */
|
|
957
|
+
listByFolder(userId: string, folderId: string | null): Promise<FileRecord[]>;
|
|
958
|
+
listByUser(userId: string): Promise<FileRecord[]>;
|
|
959
|
+
listByIds(userId: string, ids: string[]): Promise<FileRecord[]>;
|
|
960
|
+
query(userId: string, query: ItemQuery, page: PageRequest): Promise<Paged<FileRecord>>;
|
|
961
|
+
update(id: string, patch: {
|
|
962
|
+
name?: string;
|
|
963
|
+
folderId?: string | null;
|
|
964
|
+
starred?: boolean;
|
|
965
|
+
deletedAt?: Date | null;
|
|
966
|
+
trashBatchId?: string | null;
|
|
967
|
+
size?: number;
|
|
968
|
+
mimeType?: string;
|
|
969
|
+
sha256?: string;
|
|
970
|
+
telegramMessageId?: number;
|
|
971
|
+
currentVersionId?: string | null;
|
|
972
|
+
versionCount?: number;
|
|
973
|
+
}): Promise<FileRecord>;
|
|
974
|
+
updateMany(ids: string[], patch: {
|
|
975
|
+
folderId?: string | null;
|
|
976
|
+
starred?: boolean;
|
|
977
|
+
deletedAt?: Date | null;
|
|
978
|
+
trashBatchId?: string | null;
|
|
979
|
+
}): Promise<number>;
|
|
980
|
+
/** Aggregate counters used by the storage dashboard. */
|
|
981
|
+
statsByUser(userId: string): Promise<{
|
|
982
|
+
fileCount: number;
|
|
983
|
+
folderCount: number;
|
|
984
|
+
totalBytes: number;
|
|
985
|
+
trashBytes: number;
|
|
986
|
+
trashFileCount: number;
|
|
987
|
+
starredCount: number;
|
|
988
|
+
}>;
|
|
989
|
+
deleteMany(ids: string[]): Promise<void>;
|
|
990
|
+
createVersion(input: {
|
|
991
|
+
fileId: string;
|
|
992
|
+
userId: string;
|
|
993
|
+
size: number;
|
|
994
|
+
mimeType: string;
|
|
995
|
+
sha256: string;
|
|
996
|
+
telegramMessageId: number;
|
|
997
|
+
}): Promise<FileVersionRecord>;
|
|
998
|
+
listVersions(fileId: string): Promise<FileVersionRecord[]>;
|
|
999
|
+
/** All versions for many files in one query, grouped by file id. */
|
|
1000
|
+
listVersionsForFiles(fileIds: string[]): Promise<Map<string, FileVersionRecord[]>>;
|
|
1001
|
+
findVersionById(versionId: string): Promise<FileVersionRecord | null>;
|
|
1002
|
+
countVersions(fileId: string): Promise<number>;
|
|
1003
|
+
deleteVersionsByFileIds(fileIds: string[]): Promise<void>;
|
|
1004
|
+
}
|
|
1005
|
+
interface ActivityRepository {
|
|
1006
|
+
record(input: {
|
|
1007
|
+
userId: string;
|
|
1008
|
+
action: ActivityAction;
|
|
1009
|
+
resourceType: ResourceType;
|
|
1010
|
+
resourceId: string;
|
|
1011
|
+
resourceName?: string | null;
|
|
1012
|
+
metadata?: Record<string, unknown> | null;
|
|
1013
|
+
}): Promise<void>;
|
|
1014
|
+
/** Newest-first activity for a user. */
|
|
1015
|
+
list(userId: string, page: PageRequest): Promise<Paged<ActivityEventRecord>>;
|
|
1016
|
+
/**
|
|
1017
|
+
* Most recent distinct files for the given actions, newest first. Only
|
|
1018
|
+
* events whose action is listed are considered, so a file whose latest
|
|
1019
|
+
* event is (say) a star still surfaces via its last meaningful action.
|
|
1020
|
+
*/
|
|
1021
|
+
recentFiles(userId: string, actions: readonly ActivityAction[], limit: number): Promise<ActivityEventRecord[]>;
|
|
1022
|
+
countByUser(userId: string): Promise<number>;
|
|
1023
|
+
/** Housekeeping: drop events older than the retention window. */
|
|
1024
|
+
pruneOlderThan(cutoff: Date): Promise<number>;
|
|
1025
|
+
}
|
|
1026
|
+
interface Repos {
|
|
1027
|
+
users: UserRepository;
|
|
1028
|
+
sessions: SessionRepository;
|
|
1029
|
+
browserSessions: BrowserSessionRepository;
|
|
1030
|
+
uploadOperations: UploadOperationRepository;
|
|
1031
|
+
storages: StorageRepository;
|
|
1032
|
+
folders: FolderRepository;
|
|
1033
|
+
files: FileRepository;
|
|
1034
|
+
activity: ActivityRepository;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* Generic storage abstraction. The application must never depend on
|
|
1039
|
+
* Telegram-specific details — it talks to a StorageProvider.
|
|
1040
|
+
*
|
|
1041
|
+
* v0.2 adds streaming, progress reporting, cancellation and health checks
|
|
1042
|
+
* while keeping the abstraction provider-agnostic.
|
|
1043
|
+
*/
|
|
1044
|
+
/** Reference to an object inside the backing store (opaque to callers). */
|
|
1045
|
+
interface StoredRef {
|
|
1046
|
+
messageId: string;
|
|
1047
|
+
}
|
|
1048
|
+
interface StoredObject extends StoredRef {
|
|
1049
|
+
name: string;
|
|
1050
|
+
size: number;
|
|
1051
|
+
mimeType: string;
|
|
1052
|
+
}
|
|
1053
|
+
/** Transfer progress emitted during upload/download. */
|
|
1054
|
+
interface TransferProgress {
|
|
1055
|
+
/** Bytes transferred so far. */
|
|
1056
|
+
transferred: number;
|
|
1057
|
+
/** Total bytes, when known upfront. */
|
|
1058
|
+
total: number | null;
|
|
1059
|
+
/** 0–100 when total is known, otherwise null. */
|
|
1060
|
+
percent: number | null;
|
|
1061
|
+
}
|
|
1062
|
+
interface TransferControl {
|
|
1063
|
+
/** Aborted when the caller cancels the operation. */
|
|
1064
|
+
signal?: AbortSignal;
|
|
1065
|
+
onProgress?: (progress: TransferProgress) => void;
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Upload input. Exactly one of `data` (small, in memory) or `path` (a spooled
|
|
1069
|
+
* file on disk, read in slices by the provider) must be given.
|
|
1070
|
+
*/
|
|
1071
|
+
interface StorageUploadInput {
|
|
1072
|
+
name: string;
|
|
1073
|
+
mimeType: string;
|
|
1074
|
+
/** Whole-object buffer (small files). */
|
|
1075
|
+
data?: Buffer;
|
|
1076
|
+
/** Path to a spooled file (large files). Its bytes are never fully loaded. */
|
|
1077
|
+
path?: string;
|
|
1078
|
+
/** Total byte length of the object. Required with `path`. */
|
|
1079
|
+
size?: number;
|
|
1080
|
+
/** SHA-256 of the spooled bytes, computed while they were written. Required with `path`. */
|
|
1081
|
+
sha256?: string;
|
|
1082
|
+
}
|
|
1083
|
+
interface StorageHealth {
|
|
1084
|
+
/** Reachable and usable. */
|
|
1085
|
+
healthy: boolean;
|
|
1086
|
+
/** Round-trip latency of the probe in milliseconds, when measured. */
|
|
1087
|
+
latencyMs: number | null;
|
|
1088
|
+
message: string | null;
|
|
1089
|
+
/** Name of the storage channel/target, when available. */
|
|
1090
|
+
targetTitle: string | null;
|
|
1091
|
+
}
|
|
1092
|
+
interface StorageProvider {
|
|
1093
|
+
readonly name: string;
|
|
1094
|
+
/**
|
|
1095
|
+
* Uploads an object and returns the stored reference. Reports progress and
|
|
1096
|
+
* honors `control.signal` for cancellation.
|
|
1097
|
+
*/
|
|
1098
|
+
put(input: StorageUploadInput, control?: TransferControl): Promise<StoredObject>;
|
|
1099
|
+
/** Downloads the object's content as a Buffer. */
|
|
1100
|
+
get(ref: StoredRef, control?: TransferControl): Promise<Buffer>;
|
|
1101
|
+
/** Opens a stream for the object (large-file friendly). */
|
|
1102
|
+
getStream(ref: StoredRef, control?: TransferControl): Promise<Readable>;
|
|
1103
|
+
/** Deletes the object. Deleting an already-missing object must not throw. */
|
|
1104
|
+
delete(ref: StoredRef): Promise<void>;
|
|
1105
|
+
exists(ref: StoredRef): Promise<boolean>;
|
|
1106
|
+
stat(ref: StoredRef): Promise<StoredObject | null>;
|
|
1107
|
+
/**
|
|
1108
|
+
* Lists the objects the backing store holds, newest first, read-only. Used by
|
|
1109
|
+
* reconciliation; it must never modify anything. Implementations page through
|
|
1110
|
+
* the store and stop once `limit` objects are returned.
|
|
1111
|
+
*/
|
|
1112
|
+
listObjects(limit: number): Promise<StoredObject[]>;
|
|
1113
|
+
/** Validates that the backing store is reachable and usable. */
|
|
1114
|
+
healthCheck(): Promise<StorageHealth>;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
declare function sha256Hex(data: Uint8Array): string;
|
|
1118
|
+
|
|
1119
|
+
interface EngineUploadResult {
|
|
1120
|
+
stored: StoredObject;
|
|
1121
|
+
sha256: string;
|
|
1122
|
+
size: number;
|
|
1123
|
+
}
|
|
1124
|
+
interface EngineRetryPolicy {
|
|
1125
|
+
/** Total attempts, including the first. */
|
|
1126
|
+
attempts: number;
|
|
1127
|
+
/** Base delay in ms; doubles per retry. */
|
|
1128
|
+
baseDelayMs: number;
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Sits between the application and the raw StorageProvider and adds the
|
|
1132
|
+
* cross-cutting guarantees: SHA-256 integrity, streaming, progress reporting,
|
|
1133
|
+
* cancellation and bounded retries.
|
|
1134
|
+
*
|
|
1135
|
+
* The active provider is chosen per user by the host application (each user
|
|
1136
|
+
* has their own Telegram storage channel).
|
|
1137
|
+
*/
|
|
1138
|
+
declare class StorageEngine {
|
|
1139
|
+
private readonly provider;
|
|
1140
|
+
private readonly retryPolicy;
|
|
1141
|
+
constructor(provider: StorageProvider, retryPolicy?: EngineRetryPolicy);
|
|
1142
|
+
get providerName(): string;
|
|
1143
|
+
/**
|
|
1144
|
+
* Uploads an object, computing its SHA-256 as it goes and reporting
|
|
1145
|
+
* progress. Retries transient failures with exponential backoff.
|
|
1146
|
+
*/
|
|
1147
|
+
upload(input: StorageUploadInput, control?: TransferControl): Promise<EngineUploadResult>;
|
|
1148
|
+
download(ref: StoredRef, control?: TransferControl): Promise<Buffer>;
|
|
1149
|
+
/**
|
|
1150
|
+
* Streams the object and verifies its SHA-256 against `expectedSha256`.
|
|
1151
|
+
*
|
|
1152
|
+
* The last chunk is held back until the digest of everything before it has
|
|
1153
|
+
* been checked, so a corrupt object errors the stream before its final bytes
|
|
1154
|
+
* are released. A consumer therefore never sees a complete-looking body that
|
|
1155
|
+
* fails the checksum. Memory stays bounded to about two provider chunks.
|
|
1156
|
+
*/
|
|
1157
|
+
downloadStream(ref: StoredRef, expectedSha256: string, control?: TransferControl): Promise<Readable>;
|
|
1158
|
+
remove(ref: StoredRef): Promise<void>;
|
|
1159
|
+
exists(ref: StoredRef): Promise<boolean>;
|
|
1160
|
+
/** Read-only listing of stored objects, for reconciliation. Never modifies anything. */
|
|
1161
|
+
list(limit: number): Promise<StoredObject[]>;
|
|
1162
|
+
stat(ref: StoredRef): Promise<StoredObject | null>;
|
|
1163
|
+
health(): Promise<StorageHealth>;
|
|
1164
|
+
verifyIntegrity(data: Buffer, expectedSha256: string): Promise<boolean>;
|
|
1165
|
+
private withRetry;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
interface OperationSink {
|
|
1169
|
+
info(record: Record<string, unknown>, message: string): void;
|
|
1170
|
+
warn(record: Record<string, unknown>, message: string): void;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
interface RecordActivityInput {
|
|
1174
|
+
userId: string;
|
|
1175
|
+
action: ActivityAction;
|
|
1176
|
+
resourceType: ResourceType;
|
|
1177
|
+
resourceId: string;
|
|
1178
|
+
resourceName?: string | null;
|
|
1179
|
+
metadata?: Record<string, unknown> | null;
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* The narrow interface other services depend on for activity logging, so a
|
|
1183
|
+
* no-op recorder can be substituted in tests and activity failures never
|
|
1184
|
+
* break the operation being logged.
|
|
1185
|
+
*/
|
|
1186
|
+
interface ActivityRecorder {
|
|
1187
|
+
record(input: RecordActivityInput): Promise<void>;
|
|
1188
|
+
}
|
|
1189
|
+
/** Activity log with best-effort recording and retention pruning. */
|
|
1190
|
+
declare class ActivityService implements ActivityRecorder {
|
|
1191
|
+
private readonly activity;
|
|
1192
|
+
/** Days to retain events; older rows are pruned on demand. */
|
|
1193
|
+
private readonly retentionDays;
|
|
1194
|
+
constructor(activity: ActivityRepository,
|
|
1195
|
+
/** Days to retain events; older rows are pruned on demand. */
|
|
1196
|
+
retentionDays?: number);
|
|
1197
|
+
/**
|
|
1198
|
+
* Records an event. Never throws — a logging failure must not fail the
|
|
1199
|
+
* user-facing operation that produced it.
|
|
1200
|
+
*/
|
|
1201
|
+
record(input: RecordActivityInput): Promise<void>;
|
|
1202
|
+
list(userId: string, page: PageRequest): Promise<Paged<ActivityEventRecord>>;
|
|
1203
|
+
/** Most recent distinct files for the given actions, for the Recent view. */
|
|
1204
|
+
recentFiles(userId: string, actions: readonly ActivityAction[], limit: number): Promise<ActivityEventRecord[]>;
|
|
1205
|
+
prune(): Promise<number>;
|
|
1206
|
+
}
|
|
1207
|
+
/** A recorder that discards events (used where activity is not wired up). */
|
|
1208
|
+
declare const noopActivityRecorder: ActivityRecorder;
|
|
1209
|
+
|
|
1210
|
+
interface FileDownload {
|
|
1211
|
+
record: FileRecord;
|
|
1212
|
+
data: Buffer;
|
|
1213
|
+
integrityVerified: boolean;
|
|
1214
|
+
}
|
|
1215
|
+
interface FileStreamDownload {
|
|
1216
|
+
record: FileRecord;
|
|
1217
|
+
/** Errors with IntegrityCheckError instead of ending if the bytes do not match. */
|
|
1218
|
+
stream: Readable;
|
|
1219
|
+
}
|
|
1220
|
+
type EngineResolver = (userId: string) => Promise<StorageEngine>;
|
|
1221
|
+
interface FileUploadInput {
|
|
1222
|
+
folderId: string | null;
|
|
1223
|
+
name: string;
|
|
1224
|
+
/** In-memory content. Use `spooled` for anything large. */
|
|
1225
|
+
data?: Buffer;
|
|
1226
|
+
/** A file already written to disk by `spoolToFile`, with its checksum. */
|
|
1227
|
+
spooled?: {
|
|
1228
|
+
path: string;
|
|
1229
|
+
size: number;
|
|
1230
|
+
sha256: string;
|
|
1231
|
+
};
|
|
1232
|
+
/** When set, uploads a new version of this file instead of creating one. */
|
|
1233
|
+
replaceFileId?: string;
|
|
1234
|
+
/** Client-supplied id that makes the upload idempotent across retries. */
|
|
1235
|
+
operationId?: string;
|
|
1236
|
+
/** Progress callback forwarded from the transport layer. */
|
|
1237
|
+
onProgress?: (progress: TransferProgress) => void;
|
|
1238
|
+
signal?: AbortSignal;
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* File metadata operations. Every method verifies ownership: records
|
|
1242
|
+
* belonging to other users are reported as missing.
|
|
1243
|
+
*
|
|
1244
|
+
* Reliability rules honored here:
|
|
1245
|
+
* - metadata is written only after the storage upload succeeds;
|
|
1246
|
+
* - permanent deletion removes the remote object before the metadata;
|
|
1247
|
+
* - client-supplied ownership is never trusted (userId is server-derived).
|
|
1248
|
+
*/
|
|
1249
|
+
declare class FileService {
|
|
1250
|
+
private readonly files;
|
|
1251
|
+
private readonly folders;
|
|
1252
|
+
private readonly engineFor;
|
|
1253
|
+
private readonly activity;
|
|
1254
|
+
private readonly operations;
|
|
1255
|
+
private readonly log;
|
|
1256
|
+
constructor(files: FileRepository, folders: FolderRepository, engineFor: EngineResolver, activity?: ActivityRecorder, operations?: UploadOperationRepository | null, log?: OperationSink | null);
|
|
1257
|
+
upload(userId: string, input: FileUploadInput): Promise<FileRecord>;
|
|
1258
|
+
/**
|
|
1259
|
+
* Idempotent upload keyed by a client-supplied operation id. A retry never
|
|
1260
|
+
* writes a second Telegram object: a completed operation returns its file, and
|
|
1261
|
+
* an operation whose object was already stored is committed without uploading.
|
|
1262
|
+
*/
|
|
1263
|
+
private uploadOnce;
|
|
1264
|
+
/**
|
|
1265
|
+
* Follows an operation another request is driving, for a bounded time. Returns
|
|
1266
|
+
* the finished file if the winner completes, or the stored object's commit if
|
|
1267
|
+
* it has stored one. Returns null when the winner is still in progress after the
|
|
1268
|
+
* budget, so the caller can report a conflict instead of waiting forever.
|
|
1269
|
+
*/
|
|
1270
|
+
private followWinner;
|
|
1271
|
+
/** Commits metadata for an object that is already in Telegram. */
|
|
1272
|
+
private commitStoredObject;
|
|
1273
|
+
private performUpload;
|
|
1274
|
+
/** Persists the file (or the new version of a file) for an object already stored. */
|
|
1275
|
+
private commitRecord;
|
|
1276
|
+
/** Uploads a new version of an existing file and makes it current. */
|
|
1277
|
+
private replaceVersion;
|
|
1278
|
+
list(userId: string, folderId: string | null): Promise<FileRecord[]>;
|
|
1279
|
+
query(userId: string, input: ListQuery, page: PageRequest): Promise<Paged<FileRecord>>;
|
|
1280
|
+
get(userId: string, id: string): Promise<FileRecord>;
|
|
1281
|
+
/** Like get(), but rejects trashed files (for download/open). */
|
|
1282
|
+
getActive(userId: string, id: string): Promise<FileRecord>;
|
|
1283
|
+
listVersions(userId: string, id: string): Promise<FileVersionRecord[]>;
|
|
1284
|
+
rename(userId: string, id: string, name: string): Promise<FileRecord>;
|
|
1285
|
+
move(userId: string, id: string, folderId: string | null): Promise<FileRecord>;
|
|
1286
|
+
setStarred(userId: string, id: string, starred: boolean): Promise<FileRecord>;
|
|
1287
|
+
/**
|
|
1288
|
+
* Moves a file to Trash (soft delete). The remote object is untouched, so
|
|
1289
|
+
* restore is lossless.
|
|
1290
|
+
*/
|
|
1291
|
+
trash(userId: string, id: string): Promise<FileRecord>;
|
|
1292
|
+
/** Restores a trashed file to its previous folder (or root). */
|
|
1293
|
+
restore(userId: string, id: string): Promise<FileRecord>;
|
|
1294
|
+
/**
|
|
1295
|
+
* Permanently deletes a file: the Telegram object is removed first, then the
|
|
1296
|
+
* metadata. If the remote delete fails we keep the metadata so the user can
|
|
1297
|
+
* retry, rather than silently orphaning the storage object.
|
|
1298
|
+
*/
|
|
1299
|
+
deletePermanently(userId: string, id: string): Promise<void>;
|
|
1300
|
+
private performPermanentDelete;
|
|
1301
|
+
/**
|
|
1302
|
+
* v0.1-compatible hard delete used by the current API: permanently removes
|
|
1303
|
+
* the file. Retained so existing clients keep working.
|
|
1304
|
+
*/
|
|
1305
|
+
delete(userId: string, id: string): Promise<void>;
|
|
1306
|
+
download(userId: string, id: string, control?: TransferControl): Promise<FileDownload>;
|
|
1307
|
+
/**
|
|
1308
|
+
* Streaming download for large files. The caller must consume `stream` to
|
|
1309
|
+
* the end and then await `verified` to learn whether the bytes match the
|
|
1310
|
+
* stored checksum. The download activity is recorded when the stream opens.
|
|
1311
|
+
*/
|
|
1312
|
+
downloadStreamed(userId: string, id: string, control?: TransferControl): Promise<FileStreamDownload>;
|
|
1313
|
+
private performStreamedDownload;
|
|
1314
|
+
/** Records an "open" event (the UI opening the details panel, say). */
|
|
1315
|
+
touch(userId: string, id: string, action?: "open"): Promise<void>;
|
|
1316
|
+
/**
|
|
1317
|
+
* Applies an operation to many files, collecting per-item failures instead
|
|
1318
|
+
* of aborting the whole batch.
|
|
1319
|
+
*/
|
|
1320
|
+
batch(userId: string, ids: string[], operation: "trash" | "restore" | "delete" | "star" | "unstar" | "move", options?: {
|
|
1321
|
+
folderId?: string | null;
|
|
1322
|
+
operationId?: string;
|
|
1323
|
+
}): Promise<{
|
|
1324
|
+
operationId?: string;
|
|
1325
|
+
requested: number;
|
|
1326
|
+
succeeded: number;
|
|
1327
|
+
failed: number;
|
|
1328
|
+
errors: {
|
|
1329
|
+
id: string;
|
|
1330
|
+
message: string;
|
|
1331
|
+
}[];
|
|
1332
|
+
}>;
|
|
1333
|
+
private assertFolder;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
interface FolderMutationResult {
|
|
1337
|
+
affectedFolders: number;
|
|
1338
|
+
affectedFiles: number;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Folder lifecycle (v0.2):
|
|
1342
|
+
*
|
|
1343
|
+
* - Folders are virtual OmniCloud objects with a parent-child hierarchy.
|
|
1344
|
+
* - Trashing a folder trashes its whole subtree (folders + files) without
|
|
1345
|
+
* touching Telegram, so restore is lossless.
|
|
1346
|
+
* - Restoring a folder restores only the nodes that were trashed together
|
|
1347
|
+
* with it; anything the user trashed separately stays in the Trash.
|
|
1348
|
+
* - Permanent deletion removes remote objects first, then metadata.
|
|
1349
|
+
*/
|
|
1350
|
+
declare class FolderService {
|
|
1351
|
+
private readonly folders;
|
|
1352
|
+
private readonly files;
|
|
1353
|
+
private readonly engineFor;
|
|
1354
|
+
private readonly activity;
|
|
1355
|
+
constructor(folders: FolderRepository, files: FileRepository, engineFor: EngineResolver, activity?: ActivityRecorder);
|
|
1356
|
+
create(userId: string, input: {
|
|
1357
|
+
name: string;
|
|
1358
|
+
parentId: string | null;
|
|
1359
|
+
}): Promise<FolderRecord>;
|
|
1360
|
+
/** Active folders directly inside a folder (null = root). */
|
|
1361
|
+
listChildren(userId: string, parentId: string | null): Promise<FolderRecord[]>;
|
|
1362
|
+
query(userId: string, input: ListQuery, page: PageRequest): Promise<Paged<FolderRecord>>;
|
|
1363
|
+
/** Every active folder for the user (used to build trees). */
|
|
1364
|
+
tree(userId: string): Promise<FolderRecord[]>;
|
|
1365
|
+
get(userId: string, id: string): Promise<FolderRecord>;
|
|
1366
|
+
rename(userId: string, id: string, name: string): Promise<FolderRecord>;
|
|
1367
|
+
move(userId: string, id: string, newParentId: string | null): Promise<FolderRecord>;
|
|
1368
|
+
setStarred(userId: string, id: string, starred: boolean): Promise<FolderRecord>;
|
|
1369
|
+
/**
|
|
1370
|
+
* Moves a folder and its entire subtree to the Trash. Nothing is deleted
|
|
1371
|
+
* from Telegram, so restore is lossless.
|
|
1372
|
+
*/
|
|
1373
|
+
trash(userId: string, id: string): Promise<FolderMutationResult>;
|
|
1374
|
+
/**
|
|
1375
|
+
* Restores a folder subtree. Only nodes trashed at the same instant as the
|
|
1376
|
+
* folder itself are restored, so items the user trashed separately stay put.
|
|
1377
|
+
*/
|
|
1378
|
+
restore(userId: string, id: string): Promise<FolderMutationResult>;
|
|
1379
|
+
/**
|
|
1380
|
+
* Permanently deletes a folder subtree: remote objects first, then
|
|
1381
|
+
* metadata. Files whose remote delete fails are left in the Trash so the
|
|
1382
|
+
* user can retry rather than losing data silently.
|
|
1383
|
+
*/
|
|
1384
|
+
deletePermanently(userId: string, id: string): Promise<FolderMutationResult>;
|
|
1385
|
+
/**
|
|
1386
|
+
* v0.1-compatible recursive delete. Now routes through the v0.2 permanent
|
|
1387
|
+
* delete so remote cleanup and orphan prevention are consistent.
|
|
1388
|
+
*/
|
|
1389
|
+
delete(userId: string, id: string): Promise<FolderMutationResult>;
|
|
1390
|
+
/** The folder plus every descendant (BFS), optionally including trashed. */
|
|
1391
|
+
private collectSubtree;
|
|
1392
|
+
/** Ids of files inside the given folders that are not already trashed. */
|
|
1393
|
+
private collectFileIds;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
interface SearchResult {
|
|
1397
|
+
files: FileRecord[];
|
|
1398
|
+
folders: FolderRecord[];
|
|
1399
|
+
pagination: PaginationDTO;
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* PostgreSQL-backed search (no Elasticsearch, per the v0.2 scope).
|
|
1403
|
+
*
|
|
1404
|
+
* The user query is parsed into structured filters (`type:`, `size:`,
|
|
1405
|
+
* `folder:`, …) and evaluated by the repository layer, so results respect
|
|
1406
|
+
* ownership and stay safely parameterized.
|
|
1407
|
+
*/
|
|
1408
|
+
declare class SearchService {
|
|
1409
|
+
private readonly files;
|
|
1410
|
+
private readonly folders;
|
|
1411
|
+
constructor(files: FileRepository, folders: FolderRepository);
|
|
1412
|
+
search(userId: string, rawQuery: string, page: PageRequest, overrides?: ListQuery): Promise<SearchResult>;
|
|
1413
|
+
/** Convenience wrapper returning the shared DTO shape. */
|
|
1414
|
+
searchDTO(userId: string, rawQuery: string, page: PageRequest): Promise<Omit<SearchResultDTO, "files" | "folders"> & SearchResult>;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
/**
|
|
1418
|
+
* Storage dashboard statistics, computed from PostgreSQL metadata only —
|
|
1419
|
+
* no Telegram round-trips, so the page is fast and works offline.
|
|
1420
|
+
*/
|
|
1421
|
+
declare class StatsService {
|
|
1422
|
+
private readonly files;
|
|
1423
|
+
private readonly folders;
|
|
1424
|
+
constructor(files: FileRepository, folders: FolderRepository);
|
|
1425
|
+
stats(userId: string, quotaBytes?: number | null): Promise<StorageStats>;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
interface TrashListing {
|
|
1429
|
+
files: FileRecord[];
|
|
1430
|
+
folders: FolderRecord[];
|
|
1431
|
+
pagination: PaginationDTO;
|
|
1432
|
+
}
|
|
1433
|
+
interface EmptyTrashResult {
|
|
1434
|
+
deletedFiles: number;
|
|
1435
|
+
deletedFolders: number;
|
|
1436
|
+
failedFiles: number;
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Trash is a virtual view over soft-deleted rows. Listing shows only the
|
|
1440
|
+
* explicitly trashed items; emptying removes their remote objects before the
|
|
1441
|
+
* metadata, and reports failures instead of hiding them.
|
|
1442
|
+
*/
|
|
1443
|
+
declare class TrashService {
|
|
1444
|
+
private readonly files;
|
|
1445
|
+
private readonly folders;
|
|
1446
|
+
private readonly engineFor;
|
|
1447
|
+
constructor(files: FileRepository, folders: FolderRepository, engineFor: EngineResolver);
|
|
1448
|
+
list(userId: string, page: PageRequest, extra?: ListQuery): Promise<TrashListing>;
|
|
1449
|
+
/**
|
|
1450
|
+
* Permanently removes everything in the Trash for a user. Folders are
|
|
1451
|
+
* processed root-first so removing a parent also removes its contents.
|
|
1452
|
+
*/
|
|
1453
|
+
empty(userId: string): Promise<EmptyTrashResult>;
|
|
1454
|
+
/** Restores a single trashed item (used by the batch restore endpoint). */
|
|
1455
|
+
verifyTrashedFile(userId: string, id: string): Promise<FileRecord>;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
interface IntegrityCheckOptions {
|
|
1459
|
+
/** Re-download and re-hash objects (slow but authoritative). */
|
|
1460
|
+
deep?: boolean;
|
|
1461
|
+
/** Maximum files to inspect in one pass. */
|
|
1462
|
+
limit?: number;
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Detects drift between PostgreSQL metadata and Telegram storage:
|
|
1466
|
+
*
|
|
1467
|
+
* - `missing` — metadata exists, the Telegram message is gone
|
|
1468
|
+
* - `size_mismatch` — the stored object's size differs from the metadata
|
|
1469
|
+
* - `hash_mismatch` — deep mode only: downloaded bytes don't match sha256
|
|
1470
|
+
* - `unreadable` — the provider errored while inspecting the object
|
|
1471
|
+
*
|
|
1472
|
+
* The checker is strictly read-only: repairs are a separate, explicit step so
|
|
1473
|
+
* nothing is ever deleted silently (see the v0.2 reliability rules).
|
|
1474
|
+
*/
|
|
1475
|
+
declare class IntegrityService {
|
|
1476
|
+
private readonly files;
|
|
1477
|
+
private readonly engineFor;
|
|
1478
|
+
constructor(files: FileRepository, engineFor: EngineResolver);
|
|
1479
|
+
check(userId: string, options?: IntegrityCheckOptions): Promise<IntegrityReportDTO>;
|
|
1480
|
+
private inspect;
|
|
1481
|
+
private inspectObject;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
/**
|
|
1485
|
+
* Recent view: the most recently touched active files, newest activity first.
|
|
1486
|
+
* Trashed and permanently-deleted files are filtered out so the view never
|
|
1487
|
+
* surfaces items the user cannot open.
|
|
1488
|
+
*/
|
|
1489
|
+
declare class RecentService {
|
|
1490
|
+
private readonly activity;
|
|
1491
|
+
private readonly files;
|
|
1492
|
+
constructor(activity: ActivityRepository, files: FileRepository);
|
|
1493
|
+
list(userId: string, page: PageRequest): Promise<RecentPageDTO>;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
/**
|
|
1497
|
+
* Sanitizes a client-supplied filename into a safe, single path component.
|
|
1498
|
+
* Returns an empty string when nothing usable remains.
|
|
1499
|
+
*/
|
|
1500
|
+
declare function sanitizeFileName(name: string): string;
|
|
1501
|
+
|
|
1502
|
+
interface TelegramCredentials {
|
|
1503
|
+
apiId: number;
|
|
1504
|
+
apiHash: string;
|
|
1505
|
+
}
|
|
1506
|
+
interface ConnectionStatus {
|
|
1507
|
+
state: ConnectionState;
|
|
1508
|
+
lastError: string | null;
|
|
1509
|
+
lastCheckAt: Date | null;
|
|
1510
|
+
healthy: boolean | null;
|
|
1511
|
+
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Owns one Telegram (MTProto) connection per authenticated user and tracks an
|
|
1514
|
+
* explicit lifecycle state for each:
|
|
1515
|
+
*
|
|
1516
|
+
* DISCONNECTED → CONNECTING → CONNECTED
|
|
1517
|
+
* ↘ RECONNECTING → CONNECTED
|
|
1518
|
+
* ↘ ERROR | AUTH_REQUIRED
|
|
1519
|
+
*
|
|
1520
|
+
* Responsibilities: connect, disconnect, reconnect, validateSession,
|
|
1521
|
+
* healthCheck, refreshConnection. Sessions never leave the server.
|
|
1522
|
+
*/
|
|
1523
|
+
declare class TelegramClientManager {
|
|
1524
|
+
private readonly credentials;
|
|
1525
|
+
private readonly sessions;
|
|
1526
|
+
private connections;
|
|
1527
|
+
private lastReconnectAt;
|
|
1528
|
+
constructor(credentials: TelegramCredentials, sessions: SessionRepository);
|
|
1529
|
+
/** Returns a connected client, (re)connecting when needed. */
|
|
1530
|
+
getForUser(userId: string): Promise<TelegramClient>;
|
|
1531
|
+
/** Connection status for health reporting and the storage dashboard. */
|
|
1532
|
+
statusForUser(userId: string): ConnectionStatus;
|
|
1533
|
+
/**
|
|
1534
|
+
* Probes a user's connection. Cheap by default (connection state only);
|
|
1535
|
+
* `deep` performs a real Telegram round-trip to validate the session.
|
|
1536
|
+
*/
|
|
1537
|
+
healthCheck(userId: string, deep?: boolean): Promise<StorageHealth>;
|
|
1538
|
+
/** Registers an already-authenticated client (from the login flow). */
|
|
1539
|
+
cacheClient(userId: string, client: TelegramClient): void;
|
|
1540
|
+
/** Drops a user's connection (e.g. after logout) without touching storage. */
|
|
1541
|
+
disconnectUser(userId: string): Promise<void>;
|
|
1542
|
+
disconnectAll(): Promise<void>;
|
|
1543
|
+
private isConnected;
|
|
1544
|
+
private connectUser;
|
|
1545
|
+
private reconnect;
|
|
1546
|
+
private establish;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
/**
|
|
1550
|
+
* Maps a Telegram (GramJS) failure to a structured domain error with an
|
|
1551
|
+
* appropriate HTTP status and a client-facing message that never leaks
|
|
1552
|
+
* credentials or session material.
|
|
1553
|
+
*/
|
|
1554
|
+
declare function mapTelegramError(error: unknown, fallbackMessage: string): DomainError;
|
|
1555
|
+
|
|
1556
|
+
interface TelegramStorageOptions {
|
|
1557
|
+
/** Raw channel id of the user's private storage channel. */
|
|
1558
|
+
chatId: string;
|
|
1559
|
+
/** Channel access hash required to build the input peer. */
|
|
1560
|
+
accessHash: string;
|
|
1561
|
+
/** Human-readable channel title, surfaced in health reporting. */
|
|
1562
|
+
title?: string;
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* StorageProvider implementation backed by the user's private Telegram
|
|
1566
|
+
* channel. Files are sent as force-downloaded documents (no compression), so
|
|
1567
|
+
* every upload is a document message in the channel.
|
|
1568
|
+
*
|
|
1569
|
+
* v0.2 adds progress reporting, cancellation, streamed downloads and a
|
|
1570
|
+
* health check, all translated into the provider-agnostic core vocabulary.
|
|
1571
|
+
*/
|
|
1572
|
+
declare class TelegramStorageProvider implements StorageProvider {
|
|
1573
|
+
private readonly client;
|
|
1574
|
+
readonly name = "telegram";
|
|
1575
|
+
private readonly peer;
|
|
1576
|
+
private readonly title;
|
|
1577
|
+
constructor(client: TelegramClient, options: TelegramStorageOptions);
|
|
1578
|
+
put(input: StorageUploadInput, control?: TransferControl): Promise<StoredObject>;
|
|
1579
|
+
get(ref: StoredRef, control?: TransferControl): Promise<Buffer>;
|
|
1580
|
+
/**
|
|
1581
|
+
* Streams the object in Telegram-sized chunks, so memory use stays bounded
|
|
1582
|
+
* by one chunk regardless of file size. The first request happens here so
|
|
1583
|
+
* that a missing message fails before the caller starts consuming bytes.
|
|
1584
|
+
*/
|
|
1585
|
+
getStream(ref: StoredRef, control?: TransferControl): Promise<Readable>;
|
|
1586
|
+
delete(ref: StoredRef): Promise<void>;
|
|
1587
|
+
exists(ref: StoredRef): Promise<boolean>;
|
|
1588
|
+
/**
|
|
1589
|
+
* Read-only listing of stored documents in the channel, newest first, up to
|
|
1590
|
+
* `limit`. Only messages that carry a document are returned; text messages and
|
|
1591
|
+
* service messages are skipped. Nothing is modified.
|
|
1592
|
+
*/
|
|
1593
|
+
listObjects(limit: number): Promise<StoredObject[]>;
|
|
1594
|
+
stat(ref: StoredRef): Promise<StoredObject | null>;
|
|
1595
|
+
/**
|
|
1596
|
+
* Validates that the client is connected and the storage channel is
|
|
1597
|
+
* reachable (a real Telegram round-trip on the channel peer).
|
|
1598
|
+
*/
|
|
1599
|
+
healthCheck(): Promise<StorageHealth>;
|
|
1600
|
+
private throwIfAborted;
|
|
1601
|
+
private documentSize;
|
|
1602
|
+
private requireMessage;
|
|
1603
|
+
private getMessage;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
declare const TELEGRAM_PROVIDER = "telegram";
|
|
1607
|
+
type VerifyCodeResult = {
|
|
1608
|
+
status: "ok";
|
|
1609
|
+
user: UserRecord;
|
|
1610
|
+
} | {
|
|
1611
|
+
status: "password_required";
|
|
1612
|
+
};
|
|
1613
|
+
/**
|
|
1614
|
+
* Orchestrates the full Telegram account connection flow:
|
|
1615
|
+
* phone → code (→ 2FA password) → session persisted → user upserted →
|
|
1616
|
+
* private storage channel created.
|
|
1617
|
+
*/
|
|
1618
|
+
declare class TelegramConnectionService {
|
|
1619
|
+
private readonly repos;
|
|
1620
|
+
private readonly clientManager;
|
|
1621
|
+
private readonly credentials;
|
|
1622
|
+
private flows;
|
|
1623
|
+
constructor(repos: Repos, clientManager: TelegramClientManager, credentials: TelegramCredentials);
|
|
1624
|
+
/** Sends the Telegram confirmation code for the phone number. */
|
|
1625
|
+
startLogin(phone: string): Promise<void>;
|
|
1626
|
+
/** Verifies the confirmation code. Returns `password_required` for 2FA accounts. */
|
|
1627
|
+
verifyCode(phone: string, code: string): Promise<VerifyCodeResult>;
|
|
1628
|
+
/** Completes sign-in for accounts with two-factor authentication enabled. */
|
|
1629
|
+
verifyPassword(phone: string, password: string): Promise<VerifyCodeResult>;
|
|
1630
|
+
/** Aborts a pending login flow (disconnects the client). */
|
|
1631
|
+
cancelLogin(phone: string): Promise<void>;
|
|
1632
|
+
/**
|
|
1633
|
+
* Ensures the user has a registered private storage channel, creating it
|
|
1634
|
+
* on first use. Idempotent.
|
|
1635
|
+
*/
|
|
1636
|
+
ensureStorage(userId: string): Promise<StorageRecord>;
|
|
1637
|
+
ensureStorageForClient(userId: string, client: TelegramClient): Promise<StorageRecord>;
|
|
1638
|
+
private requireFlow;
|
|
1639
|
+
private finalizeLogin;
|
|
1640
|
+
private sweepExpiredFlows;
|
|
1641
|
+
private disconnectQuietly;
|
|
1642
|
+
}
|
|
447
1643
|
|
|
448
|
-
export { type ActivityEventDTO, type ActivityPageDTO, type ApiErrorBody, type BatchResultDTO, type ConnectionState, type DownloadProgress, type DownloadResult, ERROR_CODES, type EmptyTrashResultDTO, type ErrorCode, type FileDTO, type FileVersionDTO, type FilesPageDTO, type FolderDTO, type FolderMutationResultDTO, type FoldersPageDTO, type HealthDTO, type HealthStatus, type IntegrityIssueDTO, type IntegrityReportDTO, OmniCloudClient, type OmniCloudClientOptions, OmniCloudError, type PaginationDTO, type RecentItemDTO, type RecentPageDTO, type RetryOptions, type SearchResultDTO, type SessionInfo, type StorageDTO, type StorageHealthDTO, type StorageStatsDTO, type StorageTypeBreakdownDTO, type TelegramVerifyResponse, type TrashPageDTO, type UploadInput, type UploadOptions, type UploadProgress, type UserDTO };
|
|
1644
|
+
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, type StreamedDownload, 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 };
|