@valentinkolb/filegate 2.4.0 → 2.5.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.
Files changed (88) hide show
  1. package/dist/activity.d.ts +15 -0
  2. package/dist/activity.d.ts.map +1 -0
  3. package/dist/activity.js +21 -0
  4. package/dist/activity.js.map +1 -0
  5. package/dist/capabilities.d.ts +9 -0
  6. package/dist/capabilities.d.ts.map +1 -0
  7. package/dist/capabilities.js +11 -0
  8. package/dist/capabilities.js.map +1 -0
  9. package/dist/client.d.ts +37 -0
  10. package/dist/client.d.ts.map +1 -0
  11. package/dist/client.js +77 -0
  12. package/dist/client.js.map +1 -0
  13. package/dist/core.d.ts +26 -0
  14. package/dist/core.d.ts.map +1 -0
  15. package/dist/core.js +58 -0
  16. package/dist/core.js.map +1 -0
  17. package/dist/downloads.d.ts +9 -0
  18. package/dist/downloads.d.ts.map +1 -0
  19. package/dist/downloads.js +11 -0
  20. package/dist/downloads.js.map +1 -0
  21. package/dist/errors.d.ts +18 -0
  22. package/dist/errors.d.ts.map +1 -0
  23. package/dist/errors.js +42 -0
  24. package/dist/errors.js.map +1 -0
  25. package/dist/index-client.d.ts +17 -0
  26. package/dist/index-client.d.ts.map +1 -0
  27. package/dist/index-client.js +29 -0
  28. package/dist/index-client.js.map +1 -0
  29. package/dist/index.d.ts +14 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +7 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/nodes.d.ts +27 -0
  34. package/dist/nodes.d.ts.map +1 -0
  35. package/dist/nodes.js +71 -0
  36. package/dist/nodes.js.map +1 -0
  37. package/dist/paths.d.ts +45 -0
  38. package/dist/paths.d.ts.map +1 -0
  39. package/dist/paths.js +71 -0
  40. package/dist/paths.js.map +1 -0
  41. package/dist/search.d.ts +17 -0
  42. package/dist/search.d.ts.map +1 -0
  43. package/dist/search.js +25 -0
  44. package/dist/search.js.map +1 -0
  45. package/dist/stats.d.ts +9 -0
  46. package/dist/stats.d.ts.map +1 -0
  47. package/dist/stats.js +11 -0
  48. package/dist/stats.js.map +1 -0
  49. package/dist/transfers.d.ts +9 -0
  50. package/dist/transfers.d.ts.map +1 -0
  51. package/dist/transfers.js +14 -0
  52. package/dist/transfers.js.map +1 -0
  53. package/dist/types.d.ts +285 -0
  54. package/dist/types.d.ts.map +1 -0
  55. package/dist/types.js +2 -0
  56. package/dist/types.js.map +1 -0
  57. package/dist/uploads.d.ts +246 -0
  58. package/dist/uploads.d.ts.map +1 -0
  59. package/dist/uploads.js +580 -0
  60. package/dist/uploads.js.map +1 -0
  61. package/dist/utils.d.ts +25 -0
  62. package/dist/utils.d.ts.map +1 -0
  63. package/dist/utils.js +41 -0
  64. package/dist/utils.js.map +1 -0
  65. package/dist/versions.d.ts +76 -0
  66. package/dist/versions.d.ts.map +1 -0
  67. package/dist/versions.js +82 -0
  68. package/dist/versions.js.map +1 -0
  69. package/package.json +36 -41
  70. package/LICENSE +0 -21
  71. package/README.md +0 -569
  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/README.md DELETED
@@ -1,569 +0,0 @@
1
- # Filegate
2
-
3
- Secure file proxy for building custom file management systems. Streaming uploads, chunked transfers, Unix permissions.
4
-
5
- ```
6
- Browser/App Your Backend Filegate Filesystem
7
- | | | |
8
- | upload request | | |
9
- |------------------->| | |
10
- | | proxy to filegate | |
11
- | |-------------------->| |
12
- | | | write file |
13
- | | |------------------->|
14
- | | | |
15
- | |<--------------------|<-------------------|
16
- |<-------------------| | |
17
- ```
18
-
19
- Filegate runs behind your backend, not as a public-facing service. Your backend handles authentication and authorization, then proxies requests to Filegate. You control access logic - Filegate handles file operations.
20
-
21
- ## Features
22
-
23
- - Streaming uploads and downloads (no memory buffering)
24
- - Resumable chunked uploads with SHA-256 verification
25
- - Directory downloads as TAR archives
26
- - Unix file permissions (uid, gid, mode)
27
- - Glob-based file search
28
- - Type-safe client with full TypeScript support
29
- - OpenAPI documentation
30
- - Minimal dependencies (Hono, Zod - no database required)
31
-
32
- ## Quick Start
33
-
34
- ### 1. Start Filegate with Docker
35
-
36
- ```bash
37
- docker run -d \
38
- --name filegate \
39
- -p 4000:4000 \
40
- -e FILE_PROXY_TOKEN=your-secret-token \
41
- -e ALLOWED_BASE_PATHS=/data \
42
- -v /path/to/your/files:/data \
43
- ghcr.io/valentinkolb/filegate:latest
44
- ```
45
-
46
- ### 2. Install the Client
47
-
48
- ```bash
49
- npm install @valentinkolb/filegate
50
- ```
51
-
52
- ### 3. Configure Environment
53
-
54
- ```bash
55
- export FILEGATE_URL=http://localhost:4000
56
- export FILEGATE_TOKEN=your-secret-token
57
- ```
58
-
59
- ### 4. Upload a File
60
-
61
- ```typescript
62
- import { filegate } from "@valentinkolb/filegate/client";
63
-
64
- const result = await filegate.upload.single({
65
- path: "/data/uploads",
66
- filename: "document.pdf",
67
- data: fileBuffer,
68
- });
69
-
70
- if (result.ok) {
71
- console.log("Uploaded:", result.data.path);
72
- }
73
- ```
74
-
75
- ### 5. Download a File
76
-
77
- ```typescript
78
- import { filegate } from "@valentinkolb/filegate/client";
79
-
80
- const result = await filegate.download({ path: "/data/uploads/document.pdf" });
81
-
82
- if (result.ok) {
83
- const blob = await result.data.blob();
84
- }
85
-
86
- // Open in browser instead of downloading
87
- const preview = await filegate.download({
88
- path: "/data/uploads/image.png",
89
- inline: true,
90
- });
91
- ```
92
-
93
- ## Core Concepts
94
-
95
- ### Base Paths
96
-
97
- Filegate restricts all file operations to explicitly allowed directories called "base paths". This is a security boundary - files outside these paths cannot be accessed.
98
-
99
- ```bash
100
- ALLOWED_BASE_PATHS=/data/uploads,/data/exports
101
- ```
102
-
103
- With this configuration:
104
- - `/data/uploads/file.txt` - allowed
105
- - `/data/exports/report.pdf` - allowed
106
- - `/home/user/file.txt` - forbidden
107
- - `/data/../etc/passwd` - forbidden (path traversal blocked)
108
-
109
- Symlinks that point outside base paths are also blocked.
110
-
111
- ### Auto-Create Parent Directories
112
-
113
- When uploading files, Filegate automatically creates any missing parent directories. This simplifies folder uploads where nested structures need to be created on-the-fly:
114
-
115
- ```typescript
116
- // Parent directories /data/new/nested/path will be created automatically
117
- await client.upload.single({
118
- path: "/data/new/nested/path",
119
- filename: "file.txt",
120
- data: buffer,
121
- });
122
- ```
123
-
124
- This applies to both simple uploads and chunked uploads.
125
-
126
- ### File Ownership
127
-
128
- Filegate can set Unix file ownership on uploaded files:
129
-
130
- ```typescript
131
- await client.upload.single({
132
- path: "/data/uploads",
133
- filename: "file.txt",
134
- data: buffer,
135
- uid: 1000, // Owner user ID
136
- gid: 1000, // Owner group ID
137
- mode: "644", // Unix permissions (rw-r--r--)
138
- });
139
- ```
140
-
141
- If ownership is not specified, files are created with the user running Filegate (typically root in Docker).
142
-
143
- Filegate does not validate whether the specified uid/gid exists on the system, nor does it verify that the requesting user matches the specified ownership. Your backend is responsible for this validation.
144
-
145
- This feature is intended for scenarios like NFS shares exposed through Filegate, where preserving the original permission structure is required.
146
-
147
- ### Transfer (Move/Copy)
148
-
149
- The `transfer` endpoint handles both moving and copying files or directories:
150
-
151
- ```typescript
152
- // Move (rename) a file - same base path only
153
- await client.transfer({
154
- from: "/data/old-name.txt",
155
- to: "/data/new-name.txt",
156
- mode: "move",
157
- });
158
-
159
- // Copy within same base path - no ownership required
160
- await client.transfer({
161
- from: "/data/file.txt",
162
- to: "/data/backup/file.txt",
163
- mode: "copy",
164
- });
165
-
166
- // Copy across different base paths - ownership required
167
- await client.transfer({
168
- from: "/data/file.txt",
169
- to: "/backup/file.txt",
170
- mode: "copy",
171
- uid: 1000,
172
- gid: 1000,
173
- fileMode: "644",
174
- });
175
-
176
- // Allow overwriting existing files (default: false)
177
- await client.transfer({
178
- from: "/data/new-file.txt",
179
- to: "/data/existing-file.txt",
180
- mode: "copy",
181
- ensureUniqueName: false, // Overwrite if target exists
182
- });
183
- ```
184
-
185
- **Rules:**
186
- - `mode: "move"` - Only within the same base path (uses filesystem rename)
187
- - `mode: "copy"` without ownership - Only within the same base path
188
- - `mode: "copy"` with ownership - Allows cross-base copying (ownership is applied recursively)
189
- - Both operations work recursively on directories
190
- - `ensureUniqueName: true` (default) - Appends `-01`, `-02`, etc. if target exists
191
- - `ensureUniqueName: false` - Overwrites existing target file
192
-
193
- ### Thumbnails
194
-
195
- Filegate can generate image thumbnails on-the-fly using Sharp. No caching - thumbnails are generated per request (typically 5-20ms).
196
-
197
- ```typescript
198
- // Get a 200x200 cover thumbnail (default)
199
- const result = await client.thumbnail.image({
200
- path: "/data/photos/vacation.jpg",
201
- });
202
-
203
- if (result.ok) {
204
- const blob = await result.data.blob();
205
- // Use as image source
206
- }
207
-
208
- // Customized thumbnail
209
- const result = await client.thumbnail.image({
210
- path: "/data/photos/vacation.jpg",
211
- width: 400,
212
- height: 300,
213
- fit: "contain", // Fit within bounds, preserve aspect ratio
214
- format: "webp", // Output format
215
- quality: 90, // Higher quality
216
- });
217
-
218
- // Smart cropping with attention detection
219
- const result = await client.thumbnail.image({
220
- path: "/data/photos/portrait.jpg",
221
- width: 150,
222
- height: 150,
223
- fit: "cover",
224
- position: "attention", // Focus on interesting areas
225
- });
226
- ```
227
-
228
- **Options:**
229
-
230
- | Parameter | Default | Description |
231
- |-----------|---------|-------------|
232
- | `width` | 200 | Width in pixels (max 2000) |
233
- | `height` | 200 | Height in pixels (max 2000) |
234
- | `fit` | `cover` | `cover`, `contain`, `fill`, `inside`, `outside` |
235
- | `position` | `center` | Crop position: `center`, `top`, `bottom`, `left`, `right`, `entropy`, `attention` |
236
- | `format` | `webp` | Output: `webp`, `jpeg`, `png`, `avif` |
237
- | `quality` | 80 | Quality 1-100 |
238
-
239
- **Fit modes:**
240
- - `cover` - Fill the box, crop excess (best for uniform grids)
241
- - `contain` - Fit within box, preserve aspect ratio (may have letterboxing)
242
- - `fill` - Stretch to exact size (distorts)
243
- - `inside` - Like contain, but never upscale
244
- - `outside` - Like cover, but never downscale
245
-
246
- **Supported input formats:** JPEG, PNG, WebP, AVIF, TIFF, GIF, SVG
247
-
248
- **Caching:** Thumbnails include `ETag`, `Last-Modified`, and `Cache-Control: immutable` headers. Simply pass through the response:
249
-
250
- ```typescript
251
- const result = await client.thumbnail.image({ path: "/data/photo.jpg" });
252
- if (!result.ok) return c.json({ error: result.error }, result.status);
253
-
254
- return result.data; // Response with all headers
255
- ```
256
-
257
- ### Chunked Uploads
258
-
259
- For large files, use chunked uploads. They support:
260
- - Resume after connection failure
261
- - Progress tracking
262
- - Per-chunk checksum verification
263
- - Automatic assembly when complete
264
-
265
- The [Browser Utilities](#browser-utilities) help with checksum calculation, chunking, and progress tracking. They work both in the browser and on the server.
266
-
267
- ```typescript
268
- // Start or resume upload
269
- const start = await client.upload.chunked.start({
270
- path: "/data/uploads",
271
- filename: "large-file.zip",
272
- size: file.size,
273
- checksum: "sha256:abc123...", // Checksum of entire file
274
- chunkSize: 5 * 1024 * 1024, // 5MB chunks
275
- });
276
-
277
- // Upload each chunk
278
- for (let i = 0; i < start.data.totalChunks; i++) {
279
- if (start.data.uploadedChunks.includes(i)) continue; // Skip already uploaded
280
-
281
- await client.upload.chunked.send({
282
- uploadId: start.data.uploadId,
283
- index: i,
284
- data: chunkData,
285
- });
286
- }
287
- ```
288
-
289
- Uploads automatically expire after 24 hours of inactivity.
290
-
291
- ## Configuration
292
-
293
- All configuration is done through environment variables.
294
-
295
- | Variable | Required | Default | Description |
296
- |----------|----------|---------|-------------|
297
- | `FILE_PROXY_TOKEN` | Yes | - | Bearer token for API authentication |
298
- | `ALLOWED_BASE_PATHS` | Yes | - | Comma-separated list of allowed directories |
299
- | `PORT` | No | 4000 | Server port |
300
- | `MAX_UPLOAD_MB` | No | 500 | Maximum upload size in MB |
301
- | `MAX_DOWNLOAD_MB` | No | 5000 | Maximum download size in MB |
302
- | `MAX_CHUNK_SIZE_MB` | No | 50 | Maximum chunk size in MB |
303
- | `SEARCH_MAX_RESULTS` | No | 100 | Maximum search results returned |
304
- | `SEARCH_MAX_RECURSIVE_WILDCARDS` | No | 10 | Maximum `**` wildcards in glob patterns |
305
- | `UPLOAD_EXPIRY_HOURS` | No | 24 | Hours until incomplete uploads expire |
306
- | `UPLOAD_TEMP_DIR` | No | /tmp/filegate-uploads | Directory for temporary chunk storage |
307
- | `DISK_CLEANUP_INTERVAL_HOURS` | No | 6 | Interval for cleaning orphaned chunks |
308
-
309
- ### Development Mode
310
-
311
- For local development without root permissions, you can override file ownership:
312
-
313
- ```bash
314
- DEV_UID_OVERRIDE=1000
315
- DEV_GID_OVERRIDE=1000
316
- ```
317
-
318
- This applies the specified uid/gid instead of the requested values. Do not use in production.
319
-
320
- ## Docker Compose Example
321
-
322
- ```yaml
323
- services:
324
- filegate:
325
- image: ghcr.io/valentinkolb/filegate:latest
326
- ports:
327
- - "4000:4000"
328
- environment:
329
- FILE_PROXY_TOKEN: ${FILE_PROXY_TOKEN}
330
- ALLOWED_BASE_PATHS: /data
331
- volumes:
332
- - ./data:/data
333
- - filegate-chunks:/tmp/filegate-uploads
334
-
335
- volumes:
336
- filegate-chunks:
337
- ```
338
-
339
- ## Client API
340
-
341
- The client provides a type-safe interface for all Filegate operations.
342
-
343
- ### Installation
344
-
345
- ```bash
346
- npm install @valentinkolb/filegate
347
- ```
348
-
349
- ### Default Instance
350
-
351
- Set `FILEGATE_URL` and `FILEGATE_TOKEN` environment variables, then import the pre-configured client:
352
-
353
- ```typescript
354
- import { filegate } from "@valentinkolb/filegate/client";
355
-
356
- await filegate.info({ path: "/data/uploads" });
357
- ```
358
-
359
- ### Custom Instance
360
-
361
- For more control or multiple Filegate servers, create instances manually:
362
-
363
- ```typescript
364
- import { Filegate } from "@valentinkolb/filegate/client";
365
-
366
- const client = new Filegate({
367
- url: "http://localhost:4000",
368
- token: "your-secret-token",
369
- });
370
- ```
371
-
372
- ### Methods
373
-
374
- ```typescript
375
- // Get file or directory info
376
- await client.info({ path: "/data/file.txt", showHidden: false });
377
-
378
- // Get directory info with recursive sizes (slower)
379
- await client.info({ path: "/data/uploads", computeSizes: true });
380
-
381
- // Download file (returns streaming Response)
382
- await client.download({ path: "/data/file.txt" });
383
-
384
- // Download and open in browser (inline)
385
- await client.download({ path: "/data/image.png", inline: true });
386
-
387
- // Download directory as TAR archive
388
- await client.download({ path: "/data/folder" });
389
-
390
- // Simple upload
391
- await client.upload.single({
392
- path: "/data/uploads",
393
- filename: "file.txt",
394
- data: buffer,
395
- uid: 1000,
396
- gid: 1000,
397
- mode: "644",
398
- });
399
-
400
- // Chunked upload
401
- await client.upload.chunked.start({ ... });
402
- await client.upload.chunked.send({ ... });
403
-
404
- // Create directory
405
- await client.mkdir({ path: "/data/new-folder", mode: "755" });
406
-
407
- // Delete file or directory
408
- await client.delete({ path: "/data/old-file.txt" });
409
-
410
- // Transfer: Move or copy files/directories
411
- await client.transfer({
412
- from: "/data/old.txt",
413
- to: "/data/new.txt",
414
- mode: "move", // or "copy"
415
- });
416
-
417
- // Transfer with ownership (required for cross-base copy)
418
- await client.transfer({
419
- from: "/data/file.txt",
420
- to: "/backup/file.txt",
421
- mode: "copy",
422
- uid: 1000,
423
- gid: 1000,
424
- fileMode: "644",
425
- dirMode: "755",
426
- ensureUniqueName: true, // default: append -01, -02 if target exists
427
- });
428
-
429
- // Search files with glob patterns
430
- await client.glob({
431
- paths: ["/data/uploads"],
432
- pattern: "**/*.pdf",
433
- limit: 50,
434
- });
435
-
436
- // Search directories only
437
- await client.glob({
438
- paths: ["/data"],
439
- pattern: "**/*",
440
- files: false,
441
- directories: true,
442
- });
443
-
444
- // Search both files and directories
445
- await client.glob({
446
- paths: ["/data"],
447
- pattern: "**/*",
448
- directories: true,
449
- });
450
-
451
- // Generate image thumbnail
452
- await client.thumbnail.image({
453
- path: "/data/photo.jpg",
454
- width: 200,
455
- height: 200,
456
- fit: "cover",
457
- position: "center",
458
- format: "webp",
459
- quality: 80,
460
- });
461
- ```
462
-
463
- ### Response Format
464
-
465
- All methods return a discriminated union:
466
-
467
- ```typescript
468
- type Response<T> =
469
- | { ok: true; data: T }
470
- | { ok: false; error: string; status: number };
471
-
472
- const result = await client.info({ path: "/data/file.txt" });
473
-
474
- if (result.ok) {
475
- console.log(result.data.size);
476
- } else {
477
- console.error(result.error); // "not found", "path not allowed", etc.
478
- }
479
- ```
480
-
481
- ## Browser Utilities
482
-
483
- Utilities for chunked uploads that work both in the browser and on the server. They handle file chunking, SHA-256 checksum calculation, progress tracking, and retry logic.
484
-
485
- ```typescript
486
- import { chunks, formatBytes } from "@valentinkolb/filegate/utils";
487
-
488
- // Prepare a file for chunked upload
489
- const upload = await chunks.prepare({
490
- file: fileInput.files[0],
491
- chunkSize: 5 * 1024 * 1024,
492
- });
493
-
494
- console.log(upload.checksum); // "sha256:..."
495
- console.log(upload.totalChunks); // Number of chunks
496
- console.log(formatBytes({ bytes: upload.fileSize })); // "52.4 MB"
497
-
498
- // Subscribe to progress updates
499
- upload.subscribe((state) => {
500
- console.log(`${state.percent}% - ${state.status}`);
501
- });
502
-
503
- // Upload all chunks with retry and concurrency
504
- await upload.sendAll({
505
- skip: alreadyUploadedChunks,
506
- retries: 3,
507
- concurrency: 3,
508
- fn: async ({ index, data }) => {
509
- await fetch("/api/upload/chunk", {
510
- method: "POST",
511
- headers: {
512
- "X-Upload-Id": uploadId,
513
- "X-Chunk-Index": String(index),
514
- },
515
- body: data,
516
- });
517
- },
518
- });
519
- ```
520
-
521
- ## API Endpoints
522
-
523
- All `/files/*` endpoints require `Authorization: Bearer <token>`.
524
-
525
- | Method | Path | Description |
526
- |--------|------|-------------|
527
- | GET | `/health` | Health check |
528
- | GET | `/docs` | OpenAPI documentation (Scalar UI) |
529
- | GET | `/openapi.json` | OpenAPI specification |
530
- | GET | `/llms.txt` | LLM-friendly markdown documentation |
531
- | GET | `/files/info` | Get file or directory info. Use `?computeSizes=true` for recursive dir sizes |
532
- | GET | `/files/content` | Download file or directory (TAR). Use `?inline=true` to view in browser |
533
- | PUT | `/files/content` | Upload file |
534
- | POST | `/files/mkdir` | Create directory |
535
- | DELETE | `/files/delete` | Delete file or directory |
536
- | POST | `/files/transfer` | Move or copy file/directory. Cross-base copy requires ownership |
537
- | GET | `/files/search` | Search with glob pattern. Use `?directories=true` to include folders |
538
- | POST | `/files/upload/start` | Start or resume chunked upload |
539
- | POST | `/files/upload/chunk` | Upload a chunk |
540
- | GET | `/files/thumbnail/image` | Generate image thumbnail on-the-fly |
541
-
542
- ## Security
543
-
544
- Filegate implements multiple security layers:
545
-
546
- - **Path validation**: All paths are validated against allowed base paths
547
- - **Symlink protection**: Symlinks pointing outside base paths are blocked
548
- - **Path traversal prevention**: Sequences like `../` are normalized and checked
549
- - **Size limits**: Configurable limits for uploads, downloads, and chunks
550
- - **Search limits**: Glob pattern complexity is limited to prevent DoS
551
- - **Secure headers**: X-Frame-Options, X-Content-Type-Options, etc.
552
-
553
- ## Development
554
-
555
- ```bash
556
- # Install dependencies
557
- bun install
558
-
559
- # Run server
560
- FILE_PROXY_TOKEN=dev ALLOWED_BASE_PATHS=/tmp bun run src/index.ts
561
-
562
- # Run tests
563
- bun run test:unit
564
- bun run test:integration:run
565
- ```
566
-
567
- ## License
568
-
569
- MIT