@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 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
+ ```