@valentinkolb/filegate 2.4.0 → 2.5.3

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.
Files changed (88) hide show
  1. package/README.md +109 -512
  2. package/dist/activity.d.ts +15 -0
  3. package/dist/activity.d.ts.map +1 -0
  4. package/dist/activity.js +21 -0
  5. package/dist/activity.js.map +1 -0
  6. package/dist/capabilities.d.ts +9 -0
  7. package/dist/capabilities.d.ts.map +1 -0
  8. package/dist/capabilities.js +11 -0
  9. package/dist/capabilities.js.map +1 -0
  10. package/dist/client.d.ts +37 -0
  11. package/dist/client.d.ts.map +1 -0
  12. package/dist/client.js +77 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/core.d.ts +26 -0
  15. package/dist/core.d.ts.map +1 -0
  16. package/dist/core.js +58 -0
  17. package/dist/core.js.map +1 -0
  18. package/dist/downloads.d.ts +9 -0
  19. package/dist/downloads.d.ts.map +1 -0
  20. package/dist/downloads.js +11 -0
  21. package/dist/downloads.js.map +1 -0
  22. package/dist/errors.d.ts +18 -0
  23. package/dist/errors.d.ts.map +1 -0
  24. package/dist/errors.js +42 -0
  25. package/dist/errors.js.map +1 -0
  26. package/dist/index-client.d.ts +17 -0
  27. package/dist/index-client.d.ts.map +1 -0
  28. package/dist/index-client.js +29 -0
  29. package/dist/index-client.js.map +1 -0
  30. package/dist/index.d.ts +14 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +7 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/nodes.d.ts +27 -0
  35. package/dist/nodes.d.ts.map +1 -0
  36. package/dist/nodes.js +71 -0
  37. package/dist/nodes.js.map +1 -0
  38. package/dist/paths.d.ts +45 -0
  39. package/dist/paths.d.ts.map +1 -0
  40. package/dist/paths.js +71 -0
  41. package/dist/paths.js.map +1 -0
  42. package/dist/search.d.ts +17 -0
  43. package/dist/search.d.ts.map +1 -0
  44. package/dist/search.js +25 -0
  45. package/dist/search.js.map +1 -0
  46. package/dist/stats.d.ts +9 -0
  47. package/dist/stats.d.ts.map +1 -0
  48. package/dist/stats.js +11 -0
  49. package/dist/stats.js.map +1 -0
  50. package/dist/transfers.d.ts +9 -0
  51. package/dist/transfers.d.ts.map +1 -0
  52. package/dist/transfers.js +14 -0
  53. package/dist/transfers.js.map +1 -0
  54. package/dist/types.d.ts +285 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +2 -0
  57. package/dist/types.js.map +1 -0
  58. package/dist/uploads.d.ts +246 -0
  59. package/dist/uploads.d.ts.map +1 -0
  60. package/dist/uploads.js +580 -0
  61. package/dist/uploads.js.map +1 -0
  62. package/dist/utils.d.ts +25 -0
  63. package/dist/utils.d.ts.map +1 -0
  64. package/dist/utils.js +41 -0
  65. package/dist/utils.js.map +1 -0
  66. package/dist/versions.d.ts +76 -0
  67. package/dist/versions.d.ts.map +1 -0
  68. package/dist/versions.js +82 -0
  69. package/dist/versions.js.map +1 -0
  70. package/package.json +36 -40
  71. package/LICENSE +0 -21
  72. package/src/client.ts +0 -436
  73. package/src/config.ts +0 -41
  74. package/src/handlers/files.ts +0 -696
  75. package/src/handlers/indexHandler.ts +0 -107
  76. package/src/handlers/search.ts +0 -144
  77. package/src/handlers/thumbnail.ts +0 -174
  78. package/src/handlers/upload.ts +0 -401
  79. package/src/index.ts +0 -131
  80. package/src/lib/index.ts +0 -325
  81. package/src/lib/openapi.ts +0 -48
  82. package/src/lib/ownership.ts +0 -133
  83. package/src/lib/path.ts +0 -128
  84. package/src/lib/response.ts +0 -10
  85. package/src/lib/scanner.ts +0 -121
  86. package/src/lib/validator.ts +0 -21
  87. package/src/schemas.ts +0 -376
  88. package/src/utils.ts +0 -282
package/src/client.ts DELETED
@@ -1,436 +0,0 @@
1
- import type { z } from "zod";
2
- import type {
3
- FileInfoSchema,
4
- DirInfoSchema,
5
- SearchResponseSchema,
6
- UploadStartResponseSchema,
7
- UploadChunkResponseSchema,
8
- ErrorSchema,
9
- } from "./schemas";
10
-
11
- // ============================================================================
12
- // Types
13
- // ============================================================================
14
-
15
- export type FileInfo = z.infer<typeof FileInfoSchema>;
16
- export type DirInfo = z.infer<typeof DirInfoSchema>;
17
- export type SearchResponse = z.infer<typeof SearchResponseSchema>;
18
- export type UploadStartResponse = z.infer<typeof UploadStartResponseSchema>;
19
- export type UploadChunkResponse = z.infer<typeof UploadChunkResponseSchema>;
20
- export type ApiError = z.infer<typeof ErrorSchema>;
21
-
22
- export type FileProxyResponse<T> = { ok: true; data: T } | { ok: false; error: string; status: number };
23
-
24
- type Headers = Record<string, string>;
25
-
26
- export interface ClientOptions {
27
- url: string;
28
- token: string;
29
- fetch?: typeof fetch;
30
- }
31
-
32
- // --- Info ---
33
- export interface InfoOptions {
34
- path: string;
35
- showHidden?: boolean;
36
- /** If true, compute recursive sizes for directories (slower, default: false) */
37
- computeSizes?: boolean;
38
- }
39
-
40
- // --- Download ---
41
- export interface DownloadOptions {
42
- path: string;
43
- /** If true, sets Content-Disposition: inline (view in browser) instead of attachment (download) */
44
- inline?: boolean;
45
- }
46
-
47
- // --- Upload Single ---
48
- export interface UploadSingleOptions {
49
- path: string;
50
- filename: string;
51
- data: Blob | ArrayBuffer | Uint8Array;
52
- uid?: number;
53
- gid?: number;
54
- mode?: string;
55
- /** Directory mode for auto-created parent directories (e.g. "755"). If not set, derived from mode. */
56
- dirMode?: string;
57
- }
58
-
59
- // --- Upload Chunked Start ---
60
- export interface UploadChunkedStartOptions {
61
- path: string;
62
- filename: string;
63
- size: number;
64
- checksum: string;
65
- chunkSize: number;
66
- uid?: number;
67
- gid?: number;
68
- mode?: string;
69
- /** Directory mode for auto-created parent directories (e.g. "755"). If not set, derived from mode. */
70
- dirMode?: string;
71
- }
72
-
73
- // --- Upload Chunked Send ---
74
- export interface UploadChunkedSendOptions {
75
- uploadId: string;
76
- index: number;
77
- data: Blob | ArrayBuffer | Uint8Array;
78
- checksum?: string;
79
- }
80
-
81
- // --- Mkdir ---
82
- export interface MkdirOptions {
83
- path: string;
84
- uid?: number;
85
- gid?: number;
86
- mode?: string;
87
- }
88
-
89
- // --- Delete ---
90
- export interface DeleteOptions {
91
- path: string;
92
- }
93
-
94
- // --- Transfer (Move/Copy) ---
95
- export interface TransferOptions {
96
- from: string;
97
- to: string;
98
- mode: "move" | "copy";
99
- /** If true (default), appends -01, -02, etc. to avoid overwriting existing files */
100
- ensureUniqueName?: boolean;
101
- /** Owner UID - required for cross-base copy */
102
- uid?: number;
103
- /** Owner GID - required for cross-base copy */
104
- gid?: number;
105
- /** File mode (e.g. "644") - required for cross-base copy */
106
- fileMode?: string;
107
- /** Directory mode (e.g. "755") - if not set, derived from fileMode */
108
- dirMode?: string;
109
- }
110
-
111
- // --- Glob (Search) ---
112
- export interface GlobOptions {
113
- paths: string[];
114
- pattern: string;
115
- showHidden?: boolean;
116
- limit?: number;
117
- /** Include files in results (default: true) */
118
- files?: boolean;
119
- /** Include directories in results (default: false) */
120
- directories?: boolean;
121
- }
122
-
123
- // --- Thumbnail ---
124
- export type ThumbnailFit = "cover" | "contain" | "fill" | "inside" | "outside";
125
- export type ThumbnailPosition = "center" | "top" | "bottom" | "left" | "right" | "entropy" | "attention";
126
- export type ThumbnailFormat = "webp" | "jpeg" | "png" | "avif";
127
-
128
- export interface ImageThumbnailOptions {
129
- path: string;
130
- /** Width in pixels (default: 200, max: 2000) */
131
- width?: number;
132
- /** Height in pixels (default: 200, max: 2000) */
133
- height?: number;
134
- /** Scaling mode (default: cover) */
135
- fit?: ThumbnailFit;
136
- /** Crop position for cover fit (default: center) */
137
- position?: ThumbnailPosition;
138
- /** Output format (default: webp) */
139
- format?: ThumbnailFormat;
140
- /** Quality 1-100 (default: 80) */
141
- quality?: number;
142
- }
143
-
144
- // ============================================================================
145
- // Thumbnail Namespace Class
146
- // ============================================================================
147
-
148
- class ThumbnailClient {
149
- constructor(
150
- private readonly url: string,
151
- private readonly hdrs: () => Headers,
152
- private readonly _fetch: typeof fetch,
153
- ) {}
154
-
155
- async image(opts: ImageThumbnailOptions): Promise<FileProxyResponse<Response>> {
156
- const params = new URLSearchParams({ path: opts.path });
157
- if (opts.width !== undefined) params.set("width", String(opts.width));
158
- if (opts.height !== undefined) params.set("height", String(opts.height));
159
- if (opts.fit) params.set("fit", opts.fit);
160
- if (opts.position) params.set("position", opts.position);
161
- if (opts.format) params.set("format", opts.format);
162
- if (opts.quality !== undefined) params.set("quality", String(opts.quality));
163
-
164
- const res = await this._fetch(`${this.url}/files/thumbnail/image?${params}`, {
165
- headers: this.hdrs(),
166
- });
167
-
168
- if (!res.ok) {
169
- const body = (await res.json().catch(() => ({ error: "unknown error" }))) as ApiError;
170
- return { ok: false, error: body.error || "unknown error", status: res.status };
171
- }
172
-
173
- return { ok: true, data: res };
174
- }
175
- }
176
-
177
- // ============================================================================
178
- // Upload Namespace Class
179
- // ============================================================================
180
-
181
- class UploadClient {
182
- constructor(
183
- private readonly url: string,
184
- private readonly hdrs: () => Headers,
185
- private readonly jsonHdrs: () => Headers,
186
- private readonly _fetch: typeof fetch,
187
- private readonly handleResponse: <T>(res: Response) => Promise<FileProxyResponse<T>>,
188
- ) {}
189
-
190
- async single(opts: UploadSingleOptions): Promise<FileProxyResponse<FileInfo>> {
191
- const uploadHdrs: Headers = {
192
- ...this.hdrs(),
193
- "X-File-Path": opts.path,
194
- "X-File-Name": opts.filename,
195
- };
196
- if (opts.uid !== undefined) uploadHdrs["X-Owner-UID"] = String(opts.uid);
197
- if (opts.gid !== undefined) uploadHdrs["X-Owner-GID"] = String(opts.gid);
198
- if (opts.mode) uploadHdrs["X-File-Mode"] = opts.mode;
199
- if (opts.dirMode) uploadHdrs["X-Dir-Mode"] = opts.dirMode;
200
-
201
- const res = await this._fetch(`${this.url}/files/content`, {
202
- method: "PUT",
203
- headers: uploadHdrs,
204
- body: opts.data,
205
- });
206
- return this.handleResponse(res);
207
- }
208
-
209
- readonly chunked = {
210
- start: async (opts: UploadChunkedStartOptions): Promise<FileProxyResponse<UploadStartResponse>> => {
211
- const body = {
212
- path: opts.path,
213
- filename: opts.filename,
214
- size: opts.size,
215
- checksum: opts.checksum,
216
- chunkSize: opts.chunkSize,
217
- ownerUid: opts.uid,
218
- ownerGid: opts.gid,
219
- mode: opts.mode,
220
- dirMode: opts.dirMode,
221
- };
222
-
223
- const res = await this._fetch(`${this.url}/files/upload/start`, {
224
- method: "POST",
225
- headers: this.jsonHdrs(),
226
- body: JSON.stringify(body),
227
- });
228
- return this.handleResponse(res);
229
- },
230
-
231
- send: async (opts: UploadChunkedSendOptions): Promise<FileProxyResponse<UploadChunkResponse>> => {
232
- const headers: Headers = {
233
- ...this.hdrs(),
234
- "X-Upload-Id": opts.uploadId,
235
- "X-Chunk-Index": String(opts.index),
236
- };
237
- if (opts.checksum) {
238
- headers["X-Chunk-Checksum"] = opts.checksum;
239
- }
240
-
241
- const res = await this._fetch(`${this.url}/files/upload/chunk`, {
242
- method: "POST",
243
- headers,
244
- body: opts.data,
245
- });
246
- return this.handleResponse(res);
247
- },
248
- };
249
- }
250
-
251
- // ============================================================================
252
- // Client Class
253
- // ============================================================================
254
-
255
- export class Filegate {
256
- private readonly url: string;
257
- private readonly token: string;
258
- private readonly _fetch: typeof fetch;
259
-
260
- readonly upload: UploadClient;
261
- readonly thumbnail: ThumbnailClient;
262
-
263
- constructor(opts: ClientOptions) {
264
- this.url = opts.url.replace(/\/$/, "");
265
- this.token = opts.token;
266
- this._fetch = opts.fetch ?? fetch;
267
-
268
- this.upload = new UploadClient(
269
- this.url,
270
- () => this.hdrs(),
271
- () => this.jsonHdrs(),
272
- this._fetch,
273
- (res) => this.handleResponse(res),
274
- );
275
-
276
- this.thumbnail = new ThumbnailClient(this.url, () => this.hdrs(), this._fetch);
277
- }
278
-
279
- private hdrs(): Headers {
280
- return { Authorization: `Bearer ${this.token}` };
281
- }
282
-
283
- private jsonHdrs(): Headers {
284
- return { ...this.hdrs(), "Content-Type": "application/json" };
285
- }
286
-
287
- private async handleResponse<T>(res: Response): Promise<FileProxyResponse<T>> {
288
- if (!res.ok) {
289
- const body = (await res.json().catch(() => ({ error: "unknown error" }))) as ApiError;
290
- return { ok: false, error: body.error || "unknown error", status: res.status };
291
- }
292
- const data = (await res.json()) as T;
293
- return { ok: true, data };
294
- }
295
-
296
- // ==========================================================================
297
- // Info
298
- // ==========================================================================
299
-
300
- async info(opts: InfoOptions): Promise<FileProxyResponse<FileInfo | DirInfo>> {
301
- const params = new URLSearchParams({
302
- path: opts.path,
303
- showHidden: String(opts.showHidden ?? false),
304
- });
305
- if (opts.computeSizes) params.set("computeSizes", "true");
306
- const res = await this._fetch(`${this.url}/files/info?${params}`, { headers: this.hdrs() });
307
- return this.handleResponse(res);
308
- }
309
-
310
- // ==========================================================================
311
- // Download
312
- // ==========================================================================
313
-
314
- async download(opts: DownloadOptions): Promise<FileProxyResponse<Response>> {
315
- const params = new URLSearchParams({ path: opts.path });
316
- if (opts.inline) params.set("inline", "true");
317
- const res = await this._fetch(`${this.url}/files/content?${params}`, { headers: this.hdrs() });
318
- if (!res.ok) {
319
- const body = (await res.json().catch(() => ({ error: "unknown error" }))) as ApiError;
320
- return { ok: false, error: body.error || "unknown error", status: res.status };
321
- }
322
- return { ok: true, data: res };
323
- }
324
-
325
- // ==========================================================================
326
- // Directory Operations
327
- // ==========================================================================
328
-
329
- async mkdir(opts: MkdirOptions): Promise<FileProxyResponse<FileInfo>> {
330
- const body: Record<string, unknown> = { path: opts.path };
331
- if (opts.uid !== undefined) body.ownerUid = opts.uid;
332
- if (opts.gid !== undefined) body.ownerGid = opts.gid;
333
- if (opts.mode) body.mode = opts.mode;
334
-
335
- const res = await this._fetch(`${this.url}/files/mkdir`, {
336
- method: "POST",
337
- headers: this.jsonHdrs(),
338
- body: JSON.stringify(body),
339
- });
340
- return this.handleResponse(res);
341
- }
342
-
343
- // ==========================================================================
344
- // Delete
345
- // ==========================================================================
346
-
347
- async delete(opts: DeleteOptions): Promise<FileProxyResponse<void>> {
348
- const params = new URLSearchParams({ path: opts.path });
349
- const res = await this._fetch(`${this.url}/files/delete?${params}`, {
350
- method: "DELETE",
351
- headers: this.hdrs(),
352
- });
353
- if (!res.ok) {
354
- const body = (await res.json().catch(() => ({ error: "unknown error" }))) as ApiError;
355
- return { ok: false, error: body.error || "unknown error", status: res.status };
356
- }
357
- return { ok: true, data: undefined };
358
- }
359
-
360
- // ==========================================================================
361
- // Transfer (Move/Copy)
362
- // ==========================================================================
363
-
364
- async transfer(opts: TransferOptions): Promise<FileProxyResponse<FileInfo>> {
365
- const body: Record<string, unknown> = {
366
- from: opts.from,
367
- to: opts.to,
368
- mode: opts.mode,
369
- };
370
- if (opts.ensureUniqueName !== undefined) body.ensureUniqueName = opts.ensureUniqueName;
371
- if (opts.uid !== undefined) body.ownerUid = opts.uid;
372
- if (opts.gid !== undefined) body.ownerGid = opts.gid;
373
- if (opts.fileMode) body.fileMode = opts.fileMode;
374
- if (opts.dirMode) body.dirMode = opts.dirMode;
375
-
376
- const res = await this._fetch(`${this.url}/files/transfer`, {
377
- method: "POST",
378
- headers: this.jsonHdrs(),
379
- body: JSON.stringify(body),
380
- });
381
- return this.handleResponse(res);
382
- }
383
-
384
- // ==========================================================================
385
- // Glob (Search)
386
- // ==========================================================================
387
-
388
- async glob(opts: GlobOptions): Promise<FileProxyResponse<SearchResponse>> {
389
- const params = new URLSearchParams({
390
- paths: opts.paths.join(","),
391
- pattern: opts.pattern,
392
- });
393
- if (opts.showHidden) params.set("showHidden", "true");
394
- if (opts.limit) params.set("limit", String(opts.limit));
395
- if (opts.files === false) params.set("files", "false");
396
- if (opts.directories) params.set("directories", "true");
397
-
398
- const res = await this._fetch(`${this.url}/files/search?${params}`, { headers: this.hdrs() });
399
- return this.handleResponse(res);
400
- }
401
- }
402
-
403
- // ============================================================================
404
- // Default Instance (server-side only)
405
- // ============================================================================
406
-
407
- const createDefaultInstance = (): Filegate => {
408
- const url = process.env.FILEGATE_URL;
409
- const token = process.env.FILEGATE_TOKEN;
410
-
411
- if (!url || !token) {
412
- throw new Error(
413
- "FILEGATE_URL and FILEGATE_TOKEN environment variables are required.\n" +
414
- "Either set these variables or create an instance manually:\n\n" +
415
- ' import { Filegate } from "filegate/client";\n' +
416
- ' const client = new Filegate({ url: "...", token: "..." });',
417
- );
418
- }
419
-
420
- return new Filegate({ url, token });
421
- };
422
-
423
- let _instance: Filegate | null = null;
424
-
425
- export const filegate: Filegate = new Proxy({} as Filegate, {
426
- get(_target, prop) {
427
- if (_instance === null) {
428
- _instance = createDefaultInstance();
429
- }
430
- const value = _instance[prop as keyof Filegate];
431
- if (typeof value === "function") {
432
- return value.bind(_instance);
433
- }
434
- return value;
435
- },
436
- });
package/src/config.ts DELETED
@@ -1,41 +0,0 @@
1
- const required = (key: string): string => {
2
- const value = process.env[key];
3
- if (!value) throw new Error(`Missing required env: ${key}`);
4
- return value;
5
- };
6
-
7
- const int = (key: string, def: number): number => parseInt(process.env[key] ?? "", 10) || def;
8
-
9
- const optionalInt = (key: string): number | undefined => {
10
- const v = process.env[key];
11
- return v ? parseInt(v, 10) : undefined;
12
- };
13
-
14
- const list = (key: string): string[] =>
15
- required(key)
16
- .split(",")
17
- .map((s) => s.trim())
18
- .filter(Boolean);
19
-
20
- export const config = {
21
- token: required("FILE_PROXY_TOKEN"),
22
- allowedPaths: list("ALLOWED_BASE_PATHS"),
23
- port: int("PORT", 4000),
24
- maxUploadBytes: int("MAX_UPLOAD_MB", 500) * 1024 * 1024,
25
- maxDownloadBytes: int("MAX_DOWNLOAD_MB", 5000) * 1024 * 1024,
26
- maxChunkBytes: int("MAX_CHUNK_SIZE_MB", 50) * 1024 * 1024,
27
- searchMaxResults: int("SEARCH_MAX_RESULTS", 100),
28
- searchMaxRecursiveWildcards: int("SEARCH_MAX_RECURSIVE_WILDCARDS", 10),
29
- uploadExpirySecs: int("UPLOAD_EXPIRY_HOURS", 24) * 60 * 60,
30
- uploadTempDir: process.env.UPLOAD_TEMP_DIR ?? "/tmp/filegate-uploads",
31
- diskCleanupIntervalMs: int("DISK_CLEANUP_INTERVAL_HOURS", 6) * 60 * 60 * 1000,
32
- indexEnabled: process.env.ENABLE_INDEX !== "false",
33
- indexDatabaseUrl: process.env.INDEX_DATABASE_URL ?? "sqlite://:memory:",
34
- indexRescanIntervalMs: int("INDEX_RESCAN_INTERVAL_MINUTES", 30) * 60 * 1000,
35
- indexScanConcurrency: int("INDEX_SCAN_CONCURRENCY", 4),
36
- devUid: optionalInt("DEV_UID_OVERRIDE"),
37
- devGid: optionalInt("DEV_GID_OVERRIDE"),
38
- get isDev() {
39
- return this.devUid !== undefined || this.devGid !== undefined;
40
- },
41
- } as const;