@datrix/api-upload 0.1.0
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/LICENSE +21 -0
- package/README.md +180 -0
- package/dist/index.js +736 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +702 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 datrix Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# @datrix/api-upload
|
|
2
|
+
|
|
3
|
+
File upload extension for `@datrix/api`. Provides storage-agnostic file handling, image format conversion, resolution variants, and automatic media schema injection. `sharp` is only loaded when this package is installed — the core API package stays dependency-free.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @datrix/api-upload
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires `sharp` for image processing (format conversion and resolution variants).
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
Pass an `Upload` instance to `ApiPlugin` via the `upload` option:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { Upload, LocalStorageProvider } from "@datrix/api-upload"
|
|
19
|
+
import { ApiPlugin } from "@datrix/api"
|
|
20
|
+
|
|
21
|
+
new ApiPlugin({
|
|
22
|
+
upload: new Upload({
|
|
23
|
+
provider: new LocalStorageProvider({
|
|
24
|
+
basePath: "./uploads",
|
|
25
|
+
baseUrl: "https://example.com/uploads",
|
|
26
|
+
}),
|
|
27
|
+
}),
|
|
28
|
+
})
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The plugin automatically registers a `media` schema and exposes `/api/upload` endpoints.
|
|
32
|
+
|
|
33
|
+
## Endpoints
|
|
34
|
+
|
|
35
|
+
| Method | Path | Description |
|
|
36
|
+
| ---------- | ----------------- | ----------------------------------------------- |
|
|
37
|
+
| `POST` | `/api/upload` | Upload a file (`multipart/form-data`, field: `file`) |
|
|
38
|
+
| `DELETE` | `/api/upload/:id` | Delete a media record and all its variant files |
|
|
39
|
+
| `GET` | `/api/upload` | List media records (pagination, filtering, populate) |
|
|
40
|
+
| `GET` | `/api/upload/:id` | Get a single media record |
|
|
41
|
+
|
|
42
|
+
`GET` requests fall through to the standard CRUD handler — filtering, pagination, and populate work out of the box.
|
|
43
|
+
|
|
44
|
+
## Storage providers
|
|
45
|
+
|
|
46
|
+
### Local
|
|
47
|
+
|
|
48
|
+
Stores files on the local filesystem.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
new LocalStorageProvider({
|
|
52
|
+
basePath: "./uploads", // directory to write files into
|
|
53
|
+
baseUrl: "https://example.com/uploads", // public URL prefix
|
|
54
|
+
ensureDirectory: true, // create basePath if missing — default: true
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### S3
|
|
59
|
+
|
|
60
|
+
Stores files in any S3-compatible object storage (AWS S3, Cloudflare R2, MinIO, etc.). No AWS SDK — uses native HTTPS with AWS Signature V4.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { S3StorageProvider } from "@datrix/api-upload"
|
|
64
|
+
|
|
65
|
+
new S3StorageProvider({
|
|
66
|
+
bucket: "my-bucket",
|
|
67
|
+
region: "us-east-1",
|
|
68
|
+
accessKeyId: "...",
|
|
69
|
+
secretAccessKey: "...",
|
|
70
|
+
endpoint: "https://...", // optional — custom endpoint for R2 / MinIO
|
|
71
|
+
pathPrefix: "uploads/", // optional key prefix
|
|
72
|
+
})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Custom provider
|
|
76
|
+
|
|
77
|
+
Implement the `StorageProvider` interface to add any backend (GCS, Azure Blob, etc.):
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import type { StorageProvider, UploadFile, UploadResult } from "@datrix/api"
|
|
81
|
+
|
|
82
|
+
class GCSProvider implements StorageProvider {
|
|
83
|
+
readonly name = "gcs"
|
|
84
|
+
|
|
85
|
+
async upload(file: UploadFile): Promise<UploadResult> { ... }
|
|
86
|
+
async delete(key: string): Promise<void> { ... }
|
|
87
|
+
getUrl(key: string): string { ... }
|
|
88
|
+
async exists(key: string): Promise<boolean> { ... }
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## URL design
|
|
93
|
+
|
|
94
|
+
Only the storage `key` is persisted in the database. The `url` field is **never stored** — it is derived at response time by calling `provider.getUrl(key)`. This means changing your domain, CDN, or switching providers never requires a database migration: update the provider config and all URLs resolve correctly immediately.
|
|
95
|
+
|
|
96
|
+
The same applies to variants — each variant stores its `key`, and `url` is injected on the way out.
|
|
97
|
+
|
|
98
|
+
## Format conversion
|
|
99
|
+
|
|
100
|
+
Convert every uploaded image to a target format before storage. The original file is discarded — only the converted version is stored.
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
new Upload({
|
|
104
|
+
provider,
|
|
105
|
+
format: "webp", // "webp" | "jpeg" | "png" | "avif"
|
|
106
|
+
quality: 80, // 1–100, applies to jpeg / webp / avif — default: 80
|
|
107
|
+
})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Resolution variants
|
|
111
|
+
|
|
112
|
+
Generate named resized copies of every uploaded image. Each variant is uploaded through the same provider.
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
new Upload({
|
|
116
|
+
provider,
|
|
117
|
+
format: "webp",
|
|
118
|
+
resolutions: {
|
|
119
|
+
thumbnail: { width: 150, height: 150, fit: "cover" },
|
|
120
|
+
small: { width: 320 }, // height omitted — preserves aspect ratio
|
|
121
|
+
medium: { width: 640 },
|
|
122
|
+
},
|
|
123
|
+
})
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`fit` values (when both `width` and `height` are set): `cover`, `contain`, `fill`, `inside`, `outside`.
|
|
127
|
+
|
|
128
|
+
## Validation
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
new Upload({
|
|
132
|
+
provider,
|
|
133
|
+
maxSize: 5 * 1024 * 1024, // 5 MB limit
|
|
134
|
+
allowedMimeTypes: ["image/*", "application/pdf"], // wildcards supported
|
|
135
|
+
})
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Media schema
|
|
139
|
+
|
|
140
|
+
`ApiPlugin` automatically registers a `media` schema with the following fields:
|
|
141
|
+
|
|
142
|
+
| Field | Type | Description |
|
|
143
|
+
| -------------- | -------- | ------------------------------------------------------------------------ |
|
|
144
|
+
| `filename` | `string` | Generated unique filename |
|
|
145
|
+
| `originalName` | `string` | Original filename from the upload |
|
|
146
|
+
| `mimeType` | `string` | MIME type after any conversion |
|
|
147
|
+
| `size` | `number` | File size in bytes |
|
|
148
|
+
| `key` | `string` | Storage key — stored in DB, used to build URLs and delete files |
|
|
149
|
+
| `url` | `string` | **Not stored.** Injected at response time via `provider.getUrl(key)` |
|
|
150
|
+
| `variants` | `json` | Map of resolution name → `MediaVariant` (each stores `key`, not `url`) |
|
|
151
|
+
|
|
152
|
+
Use `modelName` in `UploadOptions` to change the schema/table name from the default `"media"`.
|
|
153
|
+
|
|
154
|
+
## Configuration reference
|
|
155
|
+
|
|
156
|
+
| Option | Type | Default | Description |
|
|
157
|
+
| ------------------ | --------------------------------- | ---------- | ------------------------------------------------ |
|
|
158
|
+
| `provider` | `StorageProvider` | — | **Required.** Storage backend instance. |
|
|
159
|
+
| `modelName` | `string` | `"media"` | Schema and table name for media records. |
|
|
160
|
+
| `format` | `"webp" \| "jpeg" \| "png" \| "avif"` | — | Convert all uploaded images to this format. |
|
|
161
|
+
| `quality` | `number` | `80` | Compression quality (1–100). |
|
|
162
|
+
| `maxSize` | `number` | — | Maximum file size in bytes. |
|
|
163
|
+
| `allowedMimeTypes` | `string[]` | — | Allowed MIME types. Supports wildcards. |
|
|
164
|
+
| `resolutions` | `Record<string, ResolutionConfig>` | — | Named resolution variants to generate. |
|
|
165
|
+
| `permission` | `SchemaPermission` | — | Permission config for the injected media schema. |
|
|
166
|
+
|
|
167
|
+
## Architecture
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
src/
|
|
171
|
+
├── upload.ts # Upload class — implements IUpload, owns injectUrls traversal
|
|
172
|
+
├── handler.ts # POST /upload and DELETE /upload/:id request handlers
|
|
173
|
+
├── processor.ts # sharp-based format conversion and variant generation
|
|
174
|
+
├── schema.ts # Media schema factory
|
|
175
|
+
├── types.ts # UploadOptions, ResolutionConfig, ImageFormat, ResizeFit
|
|
176
|
+
├── index.ts # Public exports
|
|
177
|
+
└── providers/
|
|
178
|
+
├── local.ts # LocalStorageProvider — filesystem storage
|
|
179
|
+
└── s3.ts # S3StorageProvider — AWS S3 / R2 / MinIO (no SDK)
|
|
180
|
+
```
|