@unisource/sdk 1.0.0 → 1.1.0-beta.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/src/client.ts DELETED
@@ -1,700 +0,0 @@
1
- import type { ApiError, UploadDestination, UploadStatus } from './primitives';
2
- import type {
3
- UploadR2InitRequest,
4
- UploadR2InitResponse,
5
- UploadAppwriteInitRequest,
6
- UploadAppwriteInitResponse,
7
- UploadLifecycleRequest,
8
- UploadCompleteResponse,
9
- UploadFailResponse,
10
- UploadsListResponse,
11
- UploadRecordDetailResponse,
12
- MultipartCreateRequest,
13
- MultipartCreateResponse,
14
- MultipartSignPartResponse,
15
- MultipartListPartsResponse,
16
- MultipartCompleteRequest,
17
- MultipartCompleteResponse,
18
- MultipartAbortResponse,
19
- } from './uploads';
20
- import type {
21
- FileRecord,
22
- FileRecordsListQuery,
23
- FileRecordsListResponse,
24
- FileRecordDetailResponse,
25
- FileMoveRequest,
26
- FileDownloadUrlResponse,
27
- FileDeleteResponse,
28
- FileRestoreResponse,
29
- FileUpdateRequest,
30
- FileUpdateResponse,
31
- } from './fileRecords';
32
- import type {
33
- FolderListQuery,
34
- FolderListResponse,
35
- FolderDetailResponse,
36
- FolderCreateRequest,
37
- FolderCreateResponse,
38
- FolderUpdateRequest,
39
- FolderUpdateResponse,
40
- FolderDeleteResponse,
41
- FolderRestoreResponse,
42
- } from './folders';
43
- import type {
44
- FileRecordsListV2Query,
45
- FolderListV2Query,
46
- BulkFileIds,
47
- BulkFileMoveRequest,
48
- BulkFolderIds,
49
- BulkFolderMoveRequest,
50
- BulkOperationResponse,
51
- FolderBreadcrumbsResponse,
52
- } from './v2';
53
- import type {
54
- ServiceDetailResponse,
55
- ServiceUsageResponse,
56
- AdminServiceUpdateRequest,
57
- AdminServiceUpdateResponse,
58
- AdminServiceSettingsRequest,
59
- AdminServiceSettingsResponse,
60
- AuditLogListQuery,
61
- AuditLogListResponse,
62
- AdminUserListResponse,
63
- AdminUserUpdateRequest,
64
- AdminUserUpdateResponse,
65
- AdminUserPasswordResetRequest,
66
- AdminUserPasswordResetResponse,
67
- AdminUserRoleUpdateRequest,
68
- AdminUserStorageLimitUpdateRequest,
69
- } from './services';
70
- import type {
71
- ShareLinkCreateRequest,
72
- ShareLinkCreateResponse,
73
- ShareLinkListResponse,
74
- ShareLinkDetailResponse,
75
- SharesCreateRequest,
76
- PublicFileAccessResponse,
77
- PublicFileLockedResponse,
78
- ShareLinkUpdateRequest,
79
- ShareLinkUpdateResponse,
80
- ShareLinkDeleteResponse,
81
- } from './shareLinks';
82
- import type {
83
- MainStorageDetailResponse,
84
- MainStorageListQuery,
85
- MainStorageListResponse,
86
- MainStorageRenameResponse,
87
- MainStorageDeleteResponse,
88
- MainStorageRestoreResponse,
89
- } from './mainStorage';
90
- import type {
91
- ReleaseDTO,
92
- ReleaseUploadInitRequest,
93
- ReleaseUploadInitResponse,
94
- ReleaseUploadCompleteRequest,
95
- ReleaseUploadCompleteResponse,
96
- ReleaseUploadFailResponse,
97
- ReleasesListQuery,
98
- ReleasesListResponse,
99
- ReleaseUpdateRequest,
100
- ReleaseDeleteResponse,
101
- ReleaseSyncRequest,
102
- ReleaseSyncResponse,
103
- ReleaseMultipartCreateRequest,
104
- ReleaseMultipartCreateResponse,
105
- ReleaseMultipartSignPartResponse,
106
- ReleaseMultipartListPartsResponse,
107
- ReleaseMultipartCompleteRequest,
108
- ReleaseMultipartCompleteResponse,
109
- ReleaseMultipartAbortResponse,
110
- AppReleaseLatestResponse,
111
- } from './releases';
112
-
113
- // ─── SDK Error classes ────────────────────────────────────────────────────────
114
-
115
- export class UnisourceError extends Error {
116
- constructor(
117
- message: string,
118
- public readonly status: number,
119
- public readonly body: ApiError
120
- ) {
121
- super(message);
122
- this.name = 'UnisourceError';
123
- }
124
- }
125
-
126
- export class UnisourceNetworkError extends Error {
127
- constructor(
128
- message: string,
129
- public readonly cause: unknown
130
- ) {
131
- super(message);
132
- this.name = 'UnisourceNetworkError';
133
- }
134
- }
135
-
136
- // ─── Config ───────────────────────────────────────────────────────────────────
137
-
138
- export interface UnisourceClientConfig {
139
- /** Base URL of the UniSource API, e.g. https://api.usrc.dev */
140
- baseUrl: string;
141
- /** Service identifier — tells the backend which service this client belongs to */
142
- serviceId: string;
143
- /**
144
- * Returns a fresh JWT or API key for each request.
145
- * Return null/undefined to send unauthenticated requests.
146
- */
147
- getToken: () => string | null | undefined | Promise<string | null | undefined>;
148
- }
149
-
150
- // ─── Internal fetch helper ────────────────────────────────────────────────────
151
-
152
- async function fetchApi<T>(
153
- baseUrl: string,
154
- method: string,
155
- path: string,
156
- options: {
157
- body?: unknown;
158
- query?: Record<string, string | number | boolean | undefined | null>;
159
- signal?: AbortSignal;
160
- authHeaders?: Record<string, string>;
161
- } = {}
162
- ): Promise<T> {
163
- const url = new URL(path, baseUrl);
164
- if (options.query) {
165
- for (const [key, value] of Object.entries(options.query)) {
166
- if (value !== undefined && value !== null) {
167
- url.searchParams.set(key, String(value));
168
- }
169
- }
170
- }
171
-
172
- const headers: Record<string, string> = { ...options.authHeaders };
173
- if (options.body !== undefined) {
174
- headers['Content-Type'] = 'application/json';
175
- }
176
-
177
- let response: Response;
178
- try {
179
- response = await fetch(url.toString(), {
180
- method,
181
- headers,
182
- body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
183
- signal: options.signal,
184
- });
185
- } catch (err) {
186
- throw new UnisourceNetworkError('Network request failed', err);
187
- }
188
-
189
- if (!response.ok) {
190
- let body: ApiError;
191
- try {
192
- body = (await response.json()) as ApiError;
193
- } catch {
194
- body = { error: 'Unknown', message: response.statusText };
195
- }
196
- throw new UnisourceError(body.message, response.status, body);
197
- }
198
-
199
- return response.json() as Promise<T>;
200
- }
201
-
202
- async function apiRequest<T>(
203
- config: UnisourceClientConfig,
204
- method: string,
205
- path: string,
206
- options: { body?: unknown; query?: Record<string, string | number | boolean | undefined | null>; signal?: AbortSignal; extraHeaders?: Record<string, string> } = {}
207
- ): Promise<T> {
208
- const token = await config.getToken();
209
- const authHeaders: Record<string, string> = {
210
- ...(options.extraHeaders ?? {}),
211
- 'X-Service-ID': config.serviceId,
212
- };
213
- if (token) {
214
- authHeaders['Authorization'] = `Bearer ${token}`;
215
- }
216
- return fetchApi<T>(config.baseUrl, method, path, { ...options, authHeaders });
217
- }
218
-
219
- // ─── Client class ─────────────────────────────────────────────────────────────
220
-
221
- export async function getPublicFileInfo(
222
- baseUrl: string,
223
- slug: string,
224
- signal?: AbortSignal
225
- ): Promise<PublicFileAccessResponse | PublicFileLockedResponse> {
226
- return fetchApi(baseUrl, 'GET', `/public/${encodeURIComponent(slug)}`, { signal });
227
- }
228
-
229
- export async function unlockPublicFile(
230
- baseUrl: string,
231
- slug: string,
232
- password: string,
233
- signal?: AbortSignal
234
- ): Promise<PublicFileAccessResponse | PublicFileLockedResponse> {
235
- return fetchApi(baseUrl, 'POST', `/public/${encodeURIComponent(slug)}/unlock`, {
236
- body: { password },
237
- signal,
238
- });
239
- }
240
-
241
- export class UnisourceClient {
242
- private config: UnisourceClientConfig;
243
-
244
- constructor(config: UnisourceClientConfig) {
245
- this.config = config;
246
- }
247
-
248
- private request<T>(
249
- method: string,
250
- path: string,
251
- options: { body?: unknown; query?: Record<string, string | number | boolean | undefined | null>; signal?: AbortSignal; extraHeaders?: Record<string, string> } = {}
252
- ): Promise<T> {
253
- return apiRequest<T>(this.config, method, path, options);
254
- }
255
-
256
- private withAsUser(options?: { asUser?: string }): Record<string, string> {
257
- return options?.asUser ? { 'X-Target-User-ID': options.asUser } : {};
258
- }
259
-
260
- // ─── Upload ────────────────────────────────────────────────────────────────
261
-
262
- readonly upload = {
263
- /** Initiate an R2 upload — returns a presigned PUT URL */
264
- r2Init: (body: UploadR2InitRequest, signal?: AbortSignal): Promise<UploadR2InitResponse> =>
265
- apiRequest(this.config, 'POST', '/upload/r2/init', { body, signal }),
266
-
267
- /** Initiate an Appwrite upload — returns credentials for direct SDK upload */
268
- appwriteInit: (body: UploadAppwriteInitRequest, signal?: AbortSignal): Promise<UploadAppwriteInitResponse> =>
269
- apiRequest(this.config, 'POST', '/upload/appwrite/init', { body, signal }),
270
-
271
- /** Confirm that the file was successfully uploaded to storage */
272
- complete: (body: UploadLifecycleRequest, signal?: AbortSignal): Promise<UploadCompleteResponse> =>
273
- apiRequest(this.config, 'POST', '/upload/complete', { body, signal }),
274
-
275
- /** Mark an upload as failed and release reserved quota */
276
- fail: (body: UploadLifecycleRequest, signal?: AbortSignal): Promise<UploadFailResponse> =>
277
- apiRequest(this.config, 'POST', '/upload/fail', { body, signal }),
278
-
279
- /** Multipart upload helpers — direct browser → R2 via S3 presigned URLs */
280
- multipart: {
281
- /** Create a multipart upload — returns UploadId + storage key */
282
- create: (body: MultipartCreateRequest, signal?: AbortSignal): Promise<MultipartCreateResponse> =>
283
- apiRequest(this.config, 'POST', '/upload/r2/multipart/create', { body, signal }),
284
-
285
- /** Short-lived presigned PUT URL for a single part */
286
- signPart: (uploadId: string, partNumber: number, signal?: AbortSignal): Promise<MultipartSignPartResponse> =>
287
- apiRequest(this.config, 'GET', '/upload/r2/multipart/sign-part', {
288
- query: { upload_id: uploadId, part_number: partNumber },
289
- signal,
290
- }),
291
-
292
- /** List parts already uploaded — feeds Golden Retriever's resume logic */
293
- listParts: (uploadId: string, signal?: AbortSignal): Promise<MultipartListPartsResponse> =>
294
- apiRequest(this.config, 'GET', '/upload/r2/multipart/list-parts', {
295
- query: { upload_id: uploadId },
296
- signal,
297
- }),
298
-
299
- /** Finalize the multipart upload. Parts must include PartNumber + ETag. */
300
- complete: (body: MultipartCompleteRequest, signal?: AbortSignal): Promise<MultipartCompleteResponse> =>
301
- apiRequest(this.config, 'POST', '/upload/r2/multipart/complete', { body, signal }),
302
-
303
- /** Abort the multipart upload and release reserved quota */
304
- abort: (uploadId: string, signal?: AbortSignal): Promise<MultipartAbortResponse> =>
305
- apiRequest(this.config, 'DELETE', '/upload/r2/multipart/abort', {
306
- body: { upload_id: uploadId },
307
- signal,
308
- }),
309
- },
310
- };
311
-
312
- // ─── My Files ─────────────────────────────────────────────────────────────
313
-
314
- readonly myFiles = {
315
- /** List files owned by the authenticated user */
316
- list: (query?: FileRecordsListQuery, signal?: AbortSignal, options?: { asUser?: string }): Promise<FileRecordsListResponse> =>
317
- apiRequest(this.config, 'GET', '/my-files', { query, signal, extraHeaders: this.withAsUser(options) }),
318
-
319
- /** List files in the trash */
320
- trash: (query?: { cursor?: string; limit?: number }, signal?: AbortSignal, options?: { asUser?: string }): Promise<FileRecordsListResponse> =>
321
- apiRequest(this.config, 'GET', '/my-files/trash', { query, signal, extraHeaders: this.withAsUser(options) }),
322
-
323
- /** Get a single file record */
324
- get: (id: string, signal?: AbortSignal, options?: { asUser?: string }): Promise<FileRecordDetailResponse> =>
325
- apiRequest(this.config, 'GET', `/my-files/${id}`, { signal, extraHeaders: this.withAsUser(options) }),
326
-
327
- /** Get a time-limited download URL (never cached by proxy) */
328
- downloadUrl: (id: string, signal?: AbortSignal, options?: { asUser?: string }): Promise<FileDownloadUrlResponse> =>
329
- apiRequest(this.config, 'GET', `/my-files/${id}/download-url`, { signal, extraHeaders: this.withAsUser(options) }),
330
-
331
- /** Move file to a different folder (null = root) */
332
- move: (id: string, body: FileMoveRequest, signal?: AbortSignal, options?: { asUser?: string }): Promise<{ file: FileRecord }> =>
333
- apiRequest(this.config, 'PATCH', `/my-files/${id}/move`, { body, signal, extraHeaders: this.withAsUser(options) }),
334
-
335
- /** Soft-delete (move to trash) or permanently delete */
336
- delete: (id: string, query?: { permanent?: boolean }, signal?: AbortSignal, options?: { asUser?: string }): Promise<FileDeleteResponse> =>
337
- apiRequest(this.config, 'DELETE', `/my-files/${id}`, { query, signal, extraHeaders: this.withAsUser(options) }),
338
-
339
- /** Restore a file from trash */
340
- restore: (id: string, signal?: AbortSignal, options?: { asUser?: string }): Promise<FileRestoreResponse> =>
341
- apiRequest(this.config, 'POST', `/my-files/${id}/restore`, { signal, extraHeaders: this.withAsUser(options) }),
342
-
343
- /** Rename a file */
344
- update: (id: string, body: FileUpdateRequest, signal?: AbortSignal, options?: { asUser?: string }): Promise<FileUpdateResponse> =>
345
- apiRequest(this.config, 'PATCH', `/my-files/${id}`, { body, signal, extraHeaders: this.withAsUser(options) }),
346
- };
347
-
348
- // ─── Folders ──────────────────────────────────────────────────────────────
349
-
350
- readonly folders = {
351
- /** List folders owned by the authenticated user */
352
- list: (query?: FolderListQuery, signal?: AbortSignal, options?: { asUser?: string }): Promise<FolderListResponse> =>
353
- apiRequest(this.config, 'GET', '/folders', { query, signal, extraHeaders: this.withAsUser(options) }),
354
-
355
- /** Get a single folder by ID */
356
- get: (id: string, signal?: AbortSignal, options?: { asUser?: string }): Promise<FolderDetailResponse> =>
357
- apiRequest(this.config, 'GET', `/folders/${id}`, { signal, extraHeaders: this.withAsUser(options) }),
358
-
359
- /** Create a new folder */
360
- create: (body: FolderCreateRequest, signal?: AbortSignal, options?: { asUser?: string }): Promise<FolderCreateResponse> =>
361
- apiRequest(this.config, 'POST', '/folders', { body, signal, extraHeaders: this.withAsUser(options) }),
362
-
363
- /** Update folder name or color tag */
364
- update: (id: string, body: FolderUpdateRequest, signal?: AbortSignal, options?: { asUser?: string }): Promise<FolderUpdateResponse> =>
365
- apiRequest(this.config, 'PATCH', `/folders/${id}`, { body, signal, extraHeaders: this.withAsUser(options) }),
366
-
367
- /** Soft-delete or permanently delete a folder and its contents */
368
- delete: (id: string, query?: { permanent?: boolean }, signal?: AbortSignal, options?: { asUser?: string }): Promise<FolderDeleteResponse> =>
369
- apiRequest(this.config, 'DELETE', `/folders/${id}`, { query, signal, extraHeaders: this.withAsUser(options) }),
370
-
371
- /** Restore a folder from trash */
372
- restore: (id: string, signal?: AbortSignal, options?: { asUser?: string }): Promise<FolderRestoreResponse> =>
373
- apiRequest(this.config, 'POST', `/folders/${id}/restore`, { signal, extraHeaders: this.withAsUser(options) }),
374
- };
375
-
376
- // ─── Main Storage ─────────────────────────────────────────────────────────
377
-
378
- readonly mainStorage = {
379
- /** List files in the main storage pool */
380
- list: (query?: MainStorageListQuery): Promise<MainStorageListResponse> =>
381
- this.request('GET', '/main', { query }),
382
-
383
- /** Get a single main-storage file record */
384
- get: (fileId: string): Promise<MainStorageDetailResponse> =>
385
- this.request('GET', `/main/${fileId}`),
386
-
387
- /** Rename a main-storage file */
388
- rename: (fileId: string, filename: string): Promise<MainStorageRenameResponse> =>
389
- this.request('PATCH', `/main/${fileId}`, { body: { filename } }),
390
-
391
- /** Delete a main-storage file (soft by default; pass permanent=true for hard delete) */
392
- delete: (fileId: string, permanent = false): Promise<MainStorageDeleteResponse> =>
393
- this.request('DELETE', `/main/${fileId}`, { query: { permanent: permanent || undefined } }),
394
-
395
- /** Restore a soft-deleted main-storage file */
396
- restore: (fileId: string): Promise<MainStorageRestoreResponse> =>
397
- this.request('POST', `/main/${fileId}/restore`),
398
-
399
- upload: {
400
- /** Initiate an R2 upload targeting main storage */
401
- r2Init: (input: Omit<UploadR2InitRequest, 'is_main_storage'>): Promise<UploadR2InitResponse> =>
402
- this.request('POST', '/upload/r2/init', { body: { ...input, is_main_storage: true } }),
403
-
404
- /** Initiate an Appwrite upload targeting main storage */
405
- appwriteInit: (input: Omit<UploadAppwriteInitRequest, 'is_main_storage'>): Promise<UploadAppwriteInitResponse> =>
406
- this.request('POST', '/upload/appwrite/init', { body: { ...input, is_main_storage: true } }),
407
-
408
- /** Confirm a main-storage upload completed */
409
- complete: (uploadId: string): Promise<UploadCompleteResponse> =>
410
- this.request('POST', '/upload/complete', { body: { upload_id: uploadId, is_main_storage: true } }),
411
-
412
- /** Mark a main-storage upload as failed */
413
- fail: (uploadId: string): Promise<UploadFailResponse> =>
414
- this.request('POST', '/upload/fail', { body: { upload_id: uploadId } }),
415
- },
416
- };
417
-
418
- // ─── Releases ─────────────────────────────────────────────────────────────
419
-
420
- readonly releases = {
421
- upload: {
422
- /** Initiate a release upload — returns a presigned PUT URL */
423
- init: (body: ReleaseUploadInitRequest, signal?: AbortSignal): Promise<ReleaseUploadInitResponse> =>
424
- apiRequest(this.config, 'POST', '/releases/upload/init', { body, signal }),
425
-
426
- /** Confirm that a release object was successfully uploaded */
427
- complete: (body: ReleaseUploadCompleteRequest, signal?: AbortSignal): Promise<ReleaseUploadCompleteResponse> =>
428
- apiRequest(this.config, 'POST', '/releases/upload/complete', { body, signal }),
429
-
430
- /** Mark a release upload as failed */
431
- fail: (releaseId: string, signal?: AbortSignal): Promise<ReleaseUploadFailResponse> =>
432
- apiRequest(this.config, 'POST', '/releases/upload/fail', { body: { release_id: releaseId }, signal }),
433
-
434
- /** Multipart upload helpers — direct browser → R2 via S3 presigned URLs */
435
- multipart: {
436
- /** Create a multipart release upload — returns the R2 UploadId + storage key */
437
- create: (body: ReleaseMultipartCreateRequest, signal?: AbortSignal): Promise<ReleaseMultipartCreateResponse> =>
438
- apiRequest(this.config, 'POST', '/releases/upload/multipart/create', { body, signal }),
439
-
440
- /** Short-lived presigned PUT URL for a single part */
441
- signPart: (uploadId: string, partNumber: number, signal?: AbortSignal): Promise<ReleaseMultipartSignPartResponse> =>
442
- apiRequest(this.config, 'GET', '/releases/upload/multipart/sign-part', {
443
- query: { upload_id: uploadId, part_number: partNumber },
444
- signal,
445
- }),
446
-
447
- /** List parts already uploaded — feeds Golden Retriever's resume logic */
448
- listParts: (uploadId: string, signal?: AbortSignal): Promise<ReleaseMultipartListPartsResponse> =>
449
- apiRequest(this.config, 'GET', '/releases/upload/multipart/list-parts', {
450
- query: { upload_id: uploadId },
451
- signal,
452
- }),
453
-
454
- /** Finalize the multipart upload — backend reads object size from R2 (no client size). */
455
- complete: (body: ReleaseMultipartCompleteRequest, signal?: AbortSignal): Promise<ReleaseMultipartCompleteResponse> =>
456
- apiRequest(this.config, 'POST', '/releases/upload/multipart/complete', { body, signal }),
457
-
458
- /** Abort an in-flight multipart upload and mark the release as failed */
459
- abort: (uploadId: string, signal?: AbortSignal): Promise<ReleaseMultipartAbortResponse> =>
460
- apiRequest(this.config, 'DELETE', '/releases/upload/multipart/abort', {
461
- body: { upload_id: uploadId },
462
- signal,
463
- }),
464
- },
465
- },
466
-
467
- /** List releases for the configured service */
468
- list: (query?: ReleasesListQuery, signal?: AbortSignal): Promise<ReleasesListResponse> =>
469
- apiRequest(this.config, 'GET', '/releases', { query, signal }),
470
-
471
- /** Get a single release */
472
- get: (releaseId: string, signal?: AbortSignal): Promise<ReleaseDTO> =>
473
- apiRequest(this.config, 'GET', `/releases/${releaseId}`, { signal }),
474
-
475
- /** Get the latest completed release */
476
- latest: (signal?: AbortSignal): Promise<ReleaseDTO> =>
477
- apiRequest(this.config, 'GET', '/releases/latest', { signal }),
478
-
479
- /** Update release metadata */
480
- update: (releaseId: string, body: ReleaseUpdateRequest, signal?: AbortSignal): Promise<ReleaseDTO> =>
481
- apiRequest(this.config, 'PATCH', `/releases/${releaseId}`, { body, signal }),
482
-
483
- /** Delete a release and its completed object, when applicable */
484
- delete: (releaseId: string, signal?: AbortSignal): Promise<ReleaseDeleteResponse> =>
485
- apiRequest(this.config, 'DELETE', `/releases/${releaseId}`, { signal }),
486
-
487
- /** Sync existing release manifests into the backend */
488
- sync: (body: ReleaseSyncRequest, signal?: AbortSignal): Promise<ReleaseSyncResponse> =>
489
- apiRequest(this.config, 'POST', '/releases/sync', { body, signal }),
490
- };
491
-
492
- // ─── Admin ────────────────────────────────────────────────────────────────
493
-
494
- readonly admin = {
495
- /** Get service info and quota limits */
496
- serviceDetail: (signal?: AbortSignal): Promise<ServiceDetailResponse> =>
497
- apiRequest(this.config, 'GET', '/admin/service', { signal }),
498
-
499
- /** Update custom service-wide limits */
500
- updateService: (body: AdminServiceUpdateRequest, signal?: AbortSignal): Promise<AdminServiceUpdateResponse> =>
501
- apiRequest(this.config, 'PATCH', '/admin/service', { body, signal }),
502
-
503
- /** Update service-wide settings (e.g. recommended upload destination) */
504
- updateServiceSettings: (
505
- body: AdminServiceSettingsRequest,
506
- signal?: AbortSignal
507
- ): Promise<AdminServiceSettingsResponse> =>
508
- apiRequest(this.config, 'PATCH', '/admin/service/settings', { body, signal }),
509
-
510
- /** Get real-time storage usage for the service */
511
- usage: (signal?: AbortSignal): Promise<ServiceUsageResponse> =>
512
- apiRequest(this.config, 'GET', '/admin/service/usage', { signal }),
513
-
514
- /** List all uploads (pending/completed/failed) for this service */
515
- listUploads: (
516
- query?: { destination?: UploadDestination; status?: UploadStatus; cursor?: string; limit?: number },
517
- signal?: AbortSignal
518
- ): Promise<UploadsListResponse> =>
519
- apiRequest(this.config, 'GET', '/admin/files', { query, signal }),
520
-
521
- /** Get a single upload for this service */
522
- getUpload: (id: string, signal?: AbortSignal): Promise<UploadRecordDetailResponse> =>
523
- apiRequest(this.config, 'GET', `/admin/files/${id}`, { signal }),
524
-
525
- /** Get a time-limited download URL for an upload owned by this service */
526
- downloadUploadUrl: (id: string, signal?: AbortSignal): Promise<FileDownloadUrlResponse> =>
527
- apiRequest(this.config, 'GET', `/admin/files/${id}/download-url`, { signal }),
528
-
529
- /** Permanently delete an upload owned by this service */
530
- deleteUpload: (id: string, signal?: AbortSignal): Promise<FileDeleteResponse> =>
531
- apiRequest(this.config, 'DELETE', `/admin/files/${id}`, { signal }),
532
-
533
- /** List audit log events for this service */
534
- auditLog: (query?: AuditLogListQuery, signal?: AbortSignal): Promise<AuditLogListResponse> =>
535
- apiRequest(this.config, 'GET', '/admin/audit-log', { query, signal }),
536
-
537
- /** List Appwrite users together with service-specific metadata */
538
- listUsers: (
539
- query?: { search?: string; offset?: number; limit?: number },
540
- signal?: AbortSignal
541
- ): Promise<AdminUserListResponse> => apiRequest(this.config, 'GET', '/admin/users', { query, signal }),
542
-
543
- /** Update Appwrite user properties and service-specific quota/role */
544
- updateUser: (
545
- userId: string,
546
- body: AdminUserUpdateRequest,
547
- signal?: AbortSignal
548
- ): Promise<AdminUserUpdateResponse> =>
549
- apiRequest(this.config, 'PATCH', `/admin/users/${userId}`, { body, signal }),
550
-
551
- /** Overwrite a user's password and revoke active sessions */
552
- resetUserPassword: (
553
- userId: string,
554
- body: AdminUserPasswordResetRequest,
555
- signal?: AbortSignal
556
- ): Promise<AdminUserPasswordResetResponse> =>
557
- apiRequest(this.config, 'POST', `/admin/users/${userId}/password`, { body, signal }),
558
-
559
- /** Update a user's role */
560
- updateUserRole: (
561
- userId: string,
562
- body: AdminUserRoleUpdateRequest,
563
- signal?: AbortSignal
564
- ): Promise<AdminUserUpdateResponse> =>
565
- apiRequest(this.config, 'PATCH', `/admin/users/${userId}/role`, { body, signal }),
566
-
567
- /** Update a user's storage limit */
568
- updateUserStorageLimit: (
569
- userId: string,
570
- body: AdminUserStorageLimitUpdateRequest,
571
- signal?: AbortSignal
572
- ): Promise<AdminUserUpdateResponse> =>
573
- apiRequest(this.config, 'PATCH', `/admin/users/${userId}/storage-limit`, { body, signal }),
574
- };
575
-
576
- // ─── Share Links (legacy — per-file endpoints) ────────────────────────────────
577
-
578
- readonly shareLinks = {
579
- /** Create a public share link for a file */
580
- create: (fileId: string, body: ShareLinkCreateRequest, signal?: AbortSignal): Promise<ShareLinkCreateResponse> =>
581
- apiRequest(this.config, 'POST', `/my-files/${fileId}/share-links`, { body, signal }),
582
-
583
- /** List all share links for a file */
584
- list: (fileId: string, signal?: AbortSignal): Promise<ShareLinkListResponse> =>
585
- apiRequest(this.config, 'GET', `/my-files/${fileId}/share-links`, { signal }),
586
-
587
- /** Update a share link (rename, toggle, change password/expiry) */
588
- update: (linkId: string, body: ShareLinkUpdateRequest, signal?: AbortSignal): Promise<ShareLinkUpdateResponse> =>
589
- apiRequest(this.config, 'PATCH', `/share-links/${linkId}`, { body, signal }),
590
-
591
- /** Permanently delete a share link */
592
- delete: (linkId: string, signal?: AbortSignal): Promise<ShareLinkDeleteResponse> =>
593
- apiRequest(this.config, 'DELETE', `/share-links/${linkId}`, { signal }),
594
- };
595
-
596
- // ─── Files (Plan 2 contract — /files/:id) ────────────────────────────────────
597
-
598
- readonly files = {
599
- /** Get a single file record */
600
- get: (id: string, signal?: AbortSignal, options?: { asUser?: string }): Promise<FileRecordDetailResponse> =>
601
- apiRequest(this.config, 'GET', `/files/${id}`, { signal, extraHeaders: this.withAsUser(options) }),
602
-
603
- /** Rename or update a file */
604
- update: (id: string, body: FileUpdateRequest, signal?: AbortSignal, options?: { asUser?: string }): Promise<FileUpdateResponse> =>
605
- apiRequest(this.config, 'PATCH', `/files/${id}`, { body, signal, extraHeaders: this.withAsUser(options) }),
606
-
607
- /** Soft-delete or permanently delete a file */
608
- delete: (id: string, query?: { permanent?: boolean }, signal?: AbortSignal, options?: { asUser?: string }): Promise<FileDeleteResponse> =>
609
- apiRequest(this.config, 'DELETE', `/files/${id}`, { query, signal, extraHeaders: this.withAsUser(options) }),
610
-
611
- /** Restore a file from trash */
612
- restore: (id: string, signal?: AbortSignal, options?: { asUser?: string }): Promise<FileRestoreResponse> =>
613
- apiRequest(this.config, 'POST', `/files/${id}/restore`, { signal, extraHeaders: this.withAsUser(options) }),
614
-
615
- /** Get a time-limited download URL */
616
- downloadUrl: (id: string, signal?: AbortSignal, options?: { asUser?: string }): Promise<FileDownloadUrlResponse> =>
617
- apiRequest(this.config, 'GET', `/files/${id}/download-url`, { signal, extraHeaders: this.withAsUser(options) }),
618
- };
619
-
620
- // ─── Shares (Plan 2 contract — /shares) ──────────────────────────────────────
621
-
622
- readonly shares = {
623
- /** List all share links for the authenticated user */
624
- list: (signal?: AbortSignal): Promise<ShareLinkListResponse> =>
625
- apiRequest(this.config, 'GET', '/shares', { signal }),
626
-
627
- /** Create a share link (file_id in body) */
628
- create: (body: SharesCreateRequest, signal?: AbortSignal): Promise<ShareLinkCreateResponse> =>
629
- apiRequest(this.config, 'POST', '/shares', { body, signal }),
630
-
631
- /** Get a single share link by ID */
632
- get: (id: string, signal?: AbortSignal): Promise<ShareLinkDetailResponse> =>
633
- apiRequest(this.config, 'GET', `/shares/${id}`, { signal }),
634
-
635
- /** Delete a share link */
636
- delete: (id: string, signal?: AbortSignal): Promise<ShareLinkDeleteResponse> =>
637
- apiRequest(this.config, 'DELETE', `/shares/${id}`, { signal }),
638
- };
639
-
640
- // ─── V2 API Endpoints ───────────────────────────────────────────────────────
641
-
642
- readonly v2 = {
643
- files: {
644
- /** List files with advanced search, sorting, and filtering capabilities */
645
- list: (query?: FileRecordsListV2Query, signal?: AbortSignal, options?: { asUser?: string }): Promise<FileRecordsListResponse> =>
646
- apiRequest(this.config, 'GET', '/v2/files', { query, signal, extraHeaders: this.withAsUser(options) }),
647
-
648
- /** Move multiple files at once */
649
- bulkMove: (body: BulkFileMoveRequest, signal?: AbortSignal, options?: { asUser?: string }): Promise<BulkOperationResponse> =>
650
- apiRequest(this.config, 'POST', '/v2/files/bulk-move', { body, signal, extraHeaders: this.withAsUser(options) }),
651
-
652
- /** Move multiple files to trash */
653
- bulkTrash: (body: BulkFileIds, signal?: AbortSignal, options?: { asUser?: string }): Promise<BulkOperationResponse> =>
654
- apiRequest(this.config, 'POST', '/v2/files/bulk-trash', { body, signal, extraHeaders: this.withAsUser(options) }),
655
-
656
- /** Restore multiple files from trash */
657
- bulkRestore: (body: BulkFileIds, signal?: AbortSignal, options?: { asUser?: string }): Promise<BulkOperationResponse> =>
658
- apiRequest(this.config, 'POST', '/v2/files/bulk-restore', { body, signal, extraHeaders: this.withAsUser(options) }),
659
- },
660
-
661
- folders: {
662
- /** List folders with advanced search, sorting, and filtering capabilities */
663
- list: (query?: FolderListV2Query, signal?: AbortSignal, options?: { asUser?: string }): Promise<FolderListResponse> =>
664
- apiRequest(this.config, 'GET', '/v2/folders', { query, signal, extraHeaders: this.withAsUser(options) }),
665
-
666
- /** Get the breadcrumb hierarchy for a specific folder */
667
- breadcrumbs: (id: string, signal?: AbortSignal, options?: { asUser?: string }): Promise<FolderBreadcrumbsResponse> =>
668
- apiRequest(this.config, 'GET', `/v2/folders/${id}/breadcrumbs`, { signal, extraHeaders: this.withAsUser(options) }),
669
-
670
- /** Move multiple folders at once */
671
- bulkMove: (body: BulkFolderMoveRequest, signal?: AbortSignal, options?: { asUser?: string }): Promise<BulkOperationResponse> =>
672
- apiRequest(this.config, 'POST', '/v2/folders/bulk-move', { body, signal, extraHeaders: this.withAsUser(options) }),
673
-
674
- /** Move multiple folders to trash */
675
- bulkTrash: (body: BulkFolderIds, signal?: AbortSignal, options?: { asUser?: string }): Promise<BulkOperationResponse> =>
676
- apiRequest(this.config, 'POST', '/v2/folders/bulk-trash', { body, signal, extraHeaders: this.withAsUser(options) }),
677
-
678
- /** Restore multiple folders from trash */
679
- bulkRestore: (body: BulkFolderIds, signal?: AbortSignal, options?: { asUser?: string }): Promise<BulkOperationResponse> =>
680
- apiRequest(this.config, 'POST', '/v2/folders/bulk-restore', { body, signal, extraHeaders: this.withAsUser(options) }),
681
- }
682
- };
683
-
684
- // ─── App-facing endpoints (/app/*) — public release distribution ──────────────
685
-
686
- readonly app = {
687
- releases: {
688
- /**
689
- * Get the latest completed release for a channel (e.g. `stable`, `beta`).
690
- * Backed by `GET /app/releases/latest`. Returns the release metadata plus
691
- * a short-lived presigned download URL.
692
- */
693
- latest: (
694
- channel: string = 'stable',
695
- signal?: AbortSignal
696
- ): Promise<AppReleaseLatestResponse> =>
697
- apiRequest(this.config, 'GET', '/app/releases/latest', { query: { channel }, signal }),
698
- },
699
- };
700
- }