@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.
- package/README.md +109 -512
- package/dist/activity.d.ts +15 -0
- package/dist/activity.d.ts.map +1 -0
- package/dist/activity.js +21 -0
- package/dist/activity.js.map +1 -0
- package/dist/capabilities.d.ts +9 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +11 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/client.d.ts +37 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +77 -0
- package/dist/client.js.map +1 -0
- package/dist/core.d.ts +26 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +58 -0
- package/dist/core.js.map +1 -0
- package/dist/downloads.d.ts +9 -0
- package/dist/downloads.d.ts.map +1 -0
- package/dist/downloads.js +11 -0
- package/dist/downloads.js.map +1 -0
- package/dist/errors.d.ts +18 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +42 -0
- package/dist/errors.js.map +1 -0
- package/dist/index-client.d.ts +17 -0
- package/dist/index-client.d.ts.map +1 -0
- package/dist/index-client.js +29 -0
- package/dist/index-client.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/nodes.d.ts +27 -0
- package/dist/nodes.d.ts.map +1 -0
- package/dist/nodes.js +71 -0
- package/dist/nodes.js.map +1 -0
- package/dist/paths.d.ts +45 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +71 -0
- package/dist/paths.js.map +1 -0
- package/dist/search.d.ts +17 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +25 -0
- package/dist/search.js.map +1 -0
- package/dist/stats.d.ts +9 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +11 -0
- package/dist/stats.js.map +1 -0
- package/dist/transfers.d.ts +9 -0
- package/dist/transfers.d.ts.map +1 -0
- package/dist/transfers.js +14 -0
- package/dist/transfers.js.map +1 -0
- package/dist/types.d.ts +285 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/uploads.d.ts +246 -0
- package/dist/uploads.d.ts.map +1 -0
- package/dist/uploads.js +580 -0
- package/dist/uploads.js.map +1 -0
- package/dist/utils.d.ts +25 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +41 -0
- package/dist/utils.js.map +1 -0
- package/dist/versions.d.ts +76 -0
- package/dist/versions.d.ts.map +1 -0
- package/dist/versions.js +82 -0
- package/dist/versions.js.map +1 -0
- package/package.json +36 -40
- package/LICENSE +0 -21
- package/src/client.ts +0 -436
- package/src/config.ts +0 -41
- package/src/handlers/files.ts +0 -696
- package/src/handlers/indexHandler.ts +0 -107
- package/src/handlers/search.ts +0 -144
- package/src/handlers/thumbnail.ts +0 -174
- package/src/handlers/upload.ts +0 -401
- package/src/index.ts +0 -131
- package/src/lib/index.ts +0 -325
- package/src/lib/openapi.ts +0 -48
- package/src/lib/ownership.ts +0 -133
- package/src/lib/path.ts +0 -128
- package/src/lib/response.ts +0 -10
- package/src/lib/scanner.ts +0 -121
- package/src/lib/validator.ts +0 -21
- package/src/schemas.ts +0 -376
- package/src/utils.ts +0 -282
package/README.md
CHANGED
|
@@ -1,569 +1,166 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @valentinkolb/filegate
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript client for Filegate. Use it from trusted server runtimes to call the
|
|
4
|
+
Filegate REST API, and from browsers only with scoped direct upload/download
|
|
5
|
+
URLs minted by your application server.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
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)
|
|
7
|
+
Filegate keeps files as normal Linux files on configured mounts. The client
|
|
8
|
+
covers path and ID lookup, direct URLs, upload sessions, transfers, search,
|
|
9
|
+
activity, and stats.
|
|
31
10
|
|
|
32
|
-
##
|
|
33
|
-
|
|
34
|
-
### 1. Start Filegate with Docker
|
|
11
|
+
## Install
|
|
35
12
|
|
|
36
13
|
```bash
|
|
37
|
-
|
|
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
|
|
14
|
+
npm i @valentinkolb/filegate
|
|
44
15
|
```
|
|
45
16
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
npm install @valentinkolb/filegate
|
|
50
|
-
```
|
|
17
|
+
## Server client
|
|
51
18
|
|
|
52
|
-
|
|
19
|
+
Use the default instance when `FILEGATE_URL` and `FILEGATE_TOKEN` are available
|
|
20
|
+
in the server runtime:
|
|
53
21
|
|
|
54
|
-
```
|
|
55
|
-
export FILEGATE_URL=http://localhost:4000
|
|
56
|
-
export FILEGATE_TOKEN=your-secret-token
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### 4. Upload a File
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
22
|
+
```ts
|
|
62
23
|
import { filegate } from "@valentinkolb/filegate/client";
|
|
63
24
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
filename: "document.pdf",
|
|
67
|
-
data: fileBuffer,
|
|
68
|
-
});
|
|
25
|
+
process.env.FILEGATE_URL = "http://127.0.0.1:8080";
|
|
26
|
+
process.env.FILEGATE_TOKEN = "dev-token";
|
|
69
27
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
28
|
+
const roots = await filegate.paths.get();
|
|
29
|
+
const caps = await filegate.capabilities.get();
|
|
73
30
|
```
|
|
74
31
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
import { filegate } from "@valentinkolb/filegate/client";
|
|
79
|
-
|
|
80
|
-
const result = await filegate.download({ path: "/data/uploads/document.pdf" });
|
|
32
|
+
Use an explicit instance for dependency injection:
|
|
81
33
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
34
|
+
```ts
|
|
35
|
+
import { Filegate } from "@valentinkolb/filegate/client";
|
|
85
36
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
37
|
+
const fg = new Filegate({
|
|
38
|
+
baseUrl: "https://filegate.internal.example",
|
|
39
|
+
token: "<filegate-token>",
|
|
40
|
+
fetchImpl: fetch,
|
|
90
41
|
});
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Core Concepts
|
|
94
42
|
|
|
95
|
-
|
|
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
|
|
43
|
+
const node = await fg.paths.get("photos/2026");
|
|
101
44
|
```
|
|
102
45
|
|
|
103
|
-
|
|
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)
|
|
46
|
+
The client namespaces match the API surface:
|
|
108
47
|
|
|
109
|
-
|
|
48
|
+
| Namespace | Meaning |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `paths` | Path-based lookup, upload, and directory listing. |
|
|
51
|
+
| `nodes` | ID-based metadata, content, mkdir, delete, and metadata edits. |
|
|
52
|
+
| `uploads` | One-shot direct upload URLs and resumable upload sessions. |
|
|
53
|
+
| `downloads` | Scoped direct download URLs. |
|
|
54
|
+
| `transfers` | Move and copy operations. |
|
|
55
|
+
| `search` | Indexed path search. |
|
|
56
|
+
| `index` | Index stats and rescan operations. |
|
|
57
|
+
| `stats` | Runtime, mount, cache, filesystem, and process stats. |
|
|
58
|
+
| `capabilities` | Server upload/download limits for adaptive clients. |
|
|
59
|
+
| `versions` | File version history on supported mounts. |
|
|
60
|
+
| `activity` | Recent API activity from the bounded server ring buffer. |
|
|
110
61
|
|
|
111
|
-
|
|
62
|
+
## Browser uploads
|
|
112
63
|
|
|
113
|
-
|
|
64
|
+
Do not expose the Filegate bearer token to browser code. The browser should ask
|
|
65
|
+
your application server for permission to upload. The application server then
|
|
66
|
+
creates direct Filegate upload URLs or direct upload sessions and returns the
|
|
67
|
+
scoped credentials to the browser.
|
|
114
68
|
|
|
115
|
-
```
|
|
116
|
-
|
|
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:
|
|
69
|
+
```ts
|
|
70
|
+
import { upload } from "@valentinkolb/filegate/client";
|
|
129
71
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
path: "/
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
72
|
+
await upload({
|
|
73
|
+
files,
|
|
74
|
+
path: "photos/inbox",
|
|
75
|
+
allow: async (request) => {
|
|
76
|
+
const response = await fetch("/api/filegate/uploads/allow", {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: { "Content-Type": "application/json" },
|
|
79
|
+
body: JSON.stringify(request),
|
|
80
|
+
});
|
|
81
|
+
return response.json();
|
|
82
|
+
},
|
|
83
|
+
onEvent: (event) => {
|
|
84
|
+
console.log(event.type, event);
|
|
85
|
+
},
|
|
138
86
|
});
|
|
139
87
|
```
|
|
140
88
|
|
|
141
|
-
|
|
89
|
+
The high-level helper uses the transfer shape from the allow response. Small
|
|
90
|
+
files can use direct one-shot URLs. Larger files can use resumable sessions
|
|
91
|
+
with parallel segment uploads. Conflict handling is explicit; `skip-existing`
|
|
92
|
+
is the default.
|
|
142
93
|
|
|
143
|
-
|
|
94
|
+
For a single direct upload URL:
|
|
144
95
|
|
|
145
|
-
|
|
96
|
+
```ts
|
|
97
|
+
import { uploadDirect } from "@valentinkolb/filegate/client";
|
|
146
98
|
|
|
147
|
-
|
|
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
|
|
99
|
+
await uploadDirect(uploadUrlFromYourServer, file, {
|
|
100
|
+
onSuccess: ({ node }) => console.log(node.id),
|
|
182
101
|
});
|
|
183
102
|
```
|
|
184
103
|
|
|
185
|
-
|
|
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
|
|
104
|
+
## Browser downloads
|
|
194
105
|
|
|
195
|
-
|
|
106
|
+
Mint the direct download URL on your application server and redirect the
|
|
107
|
+
browser:
|
|
196
108
|
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
109
|
+
```ts
|
|
110
|
+
const direct = await fg.downloads.createDirectURL({
|
|
111
|
+
nodeId: "<node-id>",
|
|
112
|
+
expiresInSeconds: 5 * 60,
|
|
201
113
|
});
|
|
202
114
|
|
|
203
|
-
|
|
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
|
|
115
|
+
return Response.redirect(direct.downloadUrl, 303);
|
|
255
116
|
```
|
|
256
117
|
|
|
257
|
-
|
|
118
|
+
Direct file downloads support `GET`, `HEAD`, and byte ranges. Directory
|
|
119
|
+
downloads stream tar archives.
|
|
258
120
|
|
|
259
|
-
|
|
260
|
-
- Resume after connection failure
|
|
261
|
-
- Progress tracking
|
|
262
|
-
- Per-chunk checksum verification
|
|
263
|
-
- Automatic assembly when complete
|
|
121
|
+
## Resumable sessions
|
|
264
122
|
|
|
265
|
-
|
|
123
|
+
Use upload sessions when you need resumable, parallel uploads with explicit
|
|
124
|
+
commit:
|
|
266
125
|
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
chunkSize: 5 * 1024 * 1024, // 5MB chunks
|
|
126
|
+
```ts
|
|
127
|
+
const session = await fg.uploads.sessions.create({
|
|
128
|
+
path: "videos/input.mov",
|
|
129
|
+
size,
|
|
130
|
+
checksum,
|
|
131
|
+
segmentSize: 16 * 1024 * 1024,
|
|
132
|
+
onConflict: "error",
|
|
275
133
|
});
|
|
276
134
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
index: i,
|
|
284
|
-
data: chunkData,
|
|
135
|
+
for (const segment of session.segments) {
|
|
136
|
+
await fg.uploads.sessions.segments.put({
|
|
137
|
+
sessionId: session.id,
|
|
138
|
+
index: segment.index,
|
|
139
|
+
body: segmentBody,
|
|
140
|
+
checksum: segmentChecksum,
|
|
285
141
|
});
|
|
286
142
|
}
|
|
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
143
|
|
|
483
|
-
|
|
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
|
-
});
|
|
144
|
+
const committed = await fg.uploads.sessions.commit({ sessionId: session.id });
|
|
145
|
+
console.log(committed.node.id);
|
|
519
146
|
```
|
|
520
147
|
|
|
521
|
-
|
|
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 |
|
|
148
|
+
Segment PUTs are idempotent when the content matches. Commit is explicit and
|
|
149
|
+
safe to retry after success.
|
|
541
150
|
|
|
542
|
-
##
|
|
151
|
+
## Utilities
|
|
543
152
|
|
|
544
|
-
|
|
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
|
|
153
|
+
Pure helpers live under `@valentinkolb/filegate/utils` and do not require a
|
|
154
|
+
Filegate token:
|
|
558
155
|
|
|
559
|
-
|
|
560
|
-
|
|
156
|
+
```ts
|
|
157
|
+
import { uploads } from "@valentinkolb/filegate/utils";
|
|
561
158
|
|
|
562
|
-
|
|
563
|
-
bun run test:unit
|
|
564
|
-
bun run test:integration:run
|
|
159
|
+
const checksum = await uploads.checksum.sha256(file);
|
|
565
160
|
```
|
|
566
161
|
|
|
567
|
-
##
|
|
162
|
+
## Documentation
|
|
568
163
|
|
|
569
|
-
|
|
164
|
+
- Project README: https://github.com/ValentinKolb/filegate#readme
|
|
165
|
+
- TypeScript guide: https://github.com/ValentinKolb/filegate/blob/main/docs/ts-client.md
|
|
166
|
+
- HTTP routes: https://github.com/ValentinKolb/filegate/blob/main/docs/http-routes.md
|