@athenox/talos
Version:
Talos: a backend you describe instead of write. Declare a schema, write the rules in plain language, map URLs to sentences; a small built-in model turns them into query plans.
39 lines (31 loc) • 1.75 kB
Markdown
# Files and images
A column that holds an uploaded file is declared on its table:
```ts
files: {
photo: {
dir: 'uploads/recipes', url: '/uploads/recipes/',
accept: /^image\/(jpeg|png|webp)$/, maxBytes: 12_000_000, message: 'A JPEG, PNG or WebP of 12 MB at most',
required: 'A photo is needed', // on creation
image: { width: 1600, height: 1600, format: 'webp', quality: 82 }, // processed before it is stored
variants: { thumb: { width: 640, height: 480, fit: 'cover', format: 'webp' } }, // more sizes, each in its column
record: { width: 'width', height: 'height', size: 'size', type: 'mime', name: 'original_name' },
},
}
```
A multipart request (`FormData`) to a route writing that table carries the file
under the column's name; its text fields are values like a JSON body. The file
is checked, processed, stored under a random name, and the column set to
`url + name`. Replacing it or deleting the row removes the old file; a request
that fails its rules leaves nothing behind.
Image processing (`sharp`, from your dependencies — `npm install sharp`, or
`useImages(sharp)`): at most `width` × `height` (never enlarged, unless
`enlarge` or `fit: 'cover'`), re-encoded as `format` at `quality`, rotated
upright, and stripped of its metadata — a phone photo carries its GPS position
(`keepMetadata: true` to keep it). `transform(buffer, file)` for anything else.
Serving them back:
```ts
'GET /uploads/recipes/:name': serveFiles('uploads/recipes', { maxAge: 31536000, guard: (name, event) => {} }),
```
The path stays inside the folder, the type follows the extension, and
`nosniff` is set. A route with `download: row => ({ path, name, type })` sends a
row's file instead.