@opensaas/stack-storage 0.1.1
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/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +11 -0
- package/CLAUDE.md +426 -0
- package/LICENSE +21 -0
- package/README.md +425 -0
- package/dist/config/index.d.ts +19 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +25 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +113 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/fields/index.d.ts +111 -0
- package/dist/fields/index.d.ts.map +1 -0
- package/dist/fields/index.js +237 -0
- package/dist/fields/index.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +2 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/local.d.ts +22 -0
- package/dist/providers/local.d.ts.map +1 -0
- package/dist/providers/local.js +64 -0
- package/dist/providers/local.js.map +1 -0
- package/dist/runtime/index.d.ts +75 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +157 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/utils/image.d.ts +18 -0
- package/dist/utils/image.d.ts.map +1 -0
- package/dist/utils/image.js +82 -0
- package/dist/utils/image.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/upload.d.ts +56 -0
- package/dist/utils/upload.d.ts.map +1 -0
- package/dist/utils/upload.js +74 -0
- package/dist/utils/upload.js.map +1 -0
- package/package.json +50 -0
- package/src/config/index.ts +30 -0
- package/src/config/types.ts +127 -0
- package/src/fields/index.ts +347 -0
- package/src/index.ts +14 -0
- package/src/providers/index.ts +1 -0
- package/src/providers/local.ts +85 -0
- package/src/runtime/index.ts +243 -0
- package/src/utils/image.ts +111 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/upload.ts +122 -0
- package/tests/image-utils.test.ts +498 -0
- package/tests/local-provider.test.ts +349 -0
- package/tests/upload-utils.test.ts +313 -0
- package/tsconfig.json +9 -0
- package/vitest.config.ts +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# @opensaas/stack-storage
|
|
2
|
+
|
|
3
|
+
File and image upload field types with pluggable storage providers for OpenSaas Stack.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 📁 **File Upload Field** - Generic file uploads with metadata storage
|
|
8
|
+
- 🖼️ **Image Upload Field** - Image uploads with automatic transformations
|
|
9
|
+
- 🔌 **Pluggable Storage** - Multiple named storage providers (local, S3, Vercel Blob)
|
|
10
|
+
- 🎨 **Image Transformations** - Automatic thumbnail/variant generation with sharp
|
|
11
|
+
- ✅ **Validation** - File size, MIME type, and extension validation
|
|
12
|
+
- 📦 **JSON Storage** - Metadata stored as JSON in your database
|
|
13
|
+
- 🎯 **Type-Safe** - Full TypeScript support
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add @opensaas/stack-storage sharp
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For S3 storage:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add @opensaas/stack-storage-s3
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For Vercel Blob storage:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm add @opensaas/stack-storage-vercel
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Basic Usage
|
|
34
|
+
|
|
35
|
+
### 1. Configure Storage Providers
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// opensaas.config.ts
|
|
39
|
+
import { config, list } from '@opensaas/stack-core'
|
|
40
|
+
import { localStorage } from '@opensaas/stack-storage'
|
|
41
|
+
import { s3Storage } from '@opensaas/stack-storage-s3'
|
|
42
|
+
import { file, image } from '@opensaas/stack-storage/fields'
|
|
43
|
+
|
|
44
|
+
export default config({
|
|
45
|
+
storage: {
|
|
46
|
+
// Local filesystem storage
|
|
47
|
+
documents: localStorage({
|
|
48
|
+
uploadDir: './public/uploads/documents',
|
|
49
|
+
serveUrl: '/uploads/documents',
|
|
50
|
+
}),
|
|
51
|
+
|
|
52
|
+
// S3 storage for images
|
|
53
|
+
avatars: s3Storage({
|
|
54
|
+
bucket: 'my-avatars',
|
|
55
|
+
region: 'us-east-1',
|
|
56
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
57
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
lists: {
|
|
62
|
+
User: list({
|
|
63
|
+
fields: {
|
|
64
|
+
// Image field with transformations
|
|
65
|
+
avatar: image({
|
|
66
|
+
storage: 'avatars',
|
|
67
|
+
transformations: {
|
|
68
|
+
thumbnail: { width: 100, height: 100, fit: 'cover' },
|
|
69
|
+
profile: { width: 400, height: 400, fit: 'cover' },
|
|
70
|
+
},
|
|
71
|
+
validation: {
|
|
72
|
+
maxFileSize: 5 * 1024 * 1024, // 5MB
|
|
73
|
+
acceptedMimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
|
|
77
|
+
// File field
|
|
78
|
+
resume: file({
|
|
79
|
+
storage: 'documents',
|
|
80
|
+
validation: {
|
|
81
|
+
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
82
|
+
acceptedMimeTypes: ['application/pdf'],
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 2. Create Upload API Route
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// app/api/upload/route.ts
|
|
95
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
96
|
+
import config from '@/opensaas.config'
|
|
97
|
+
import { uploadFile, uploadImage, parseFileFromFormData } from '@opensaas/stack-storage/runtime'
|
|
98
|
+
|
|
99
|
+
export async function POST(request: NextRequest) {
|
|
100
|
+
try {
|
|
101
|
+
const formData = await request.formData()
|
|
102
|
+
const storageProvider = formData.get('storage') as string
|
|
103
|
+
const fieldType = formData.get('fieldType') as 'file' | 'image'
|
|
104
|
+
|
|
105
|
+
// Parse file from FormData
|
|
106
|
+
const fileData = await parseFileFromFormData(formData, 'file')
|
|
107
|
+
if (!fileData) {
|
|
108
|
+
return NextResponse.json({ error: 'No file provided' }, { status: 400 })
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Upload based on field type
|
|
112
|
+
if (fieldType === 'image') {
|
|
113
|
+
// Get transformations from config if needed
|
|
114
|
+
const metadata = await uploadImage(config, storageProvider, fileData, {
|
|
115
|
+
validation: {
|
|
116
|
+
maxFileSize: 5 * 1024 * 1024,
|
|
117
|
+
acceptedMimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
|
|
118
|
+
},
|
|
119
|
+
transformations: {
|
|
120
|
+
thumbnail: { width: 100, height: 100, fit: 'cover' },
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
return NextResponse.json(metadata)
|
|
124
|
+
} else {
|
|
125
|
+
const metadata = await uploadFile(config, storageProvider, fileData, {
|
|
126
|
+
validation: {
|
|
127
|
+
maxFileSize: 10 * 1024 * 1024,
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
return NextResponse.json(metadata)
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error('Upload error:', error)
|
|
134
|
+
return NextResponse.json(
|
|
135
|
+
{ error: error instanceof Error ? error.message : 'Upload failed' },
|
|
136
|
+
{ status: 500 },
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 3. Use in Admin UI
|
|
143
|
+
|
|
144
|
+
The file and image fields work automatically in the admin UI. For custom forms, provide an `onUpload` handler:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { FileField, ImageField } from '@opensaas/stack-ui/fields'
|
|
148
|
+
|
|
149
|
+
function CustomForm() {
|
|
150
|
+
const [avatar, setAvatar] = useState<ImageMetadata | null>(null)
|
|
151
|
+
|
|
152
|
+
const handleUpload = async (file: File) => {
|
|
153
|
+
const formData = new FormData()
|
|
154
|
+
formData.append('file', file)
|
|
155
|
+
formData.append('storage', 'avatars')
|
|
156
|
+
formData.append('fieldType', 'image')
|
|
157
|
+
|
|
158
|
+
const response = await fetch('/api/upload', {
|
|
159
|
+
method: 'POST',
|
|
160
|
+
body: formData,
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
if (!response.ok) {
|
|
164
|
+
throw new Error('Upload failed')
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return await response.json()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<ImageField
|
|
172
|
+
name="avatar"
|
|
173
|
+
value={avatar}
|
|
174
|
+
onChange={setAvatar}
|
|
175
|
+
label="Avatar"
|
|
176
|
+
onUpload={handleUpload}
|
|
177
|
+
/>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Storage Providers
|
|
183
|
+
|
|
184
|
+
### Local Filesystem
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { localStorage } from '@opensaas/stack-storage'
|
|
188
|
+
|
|
189
|
+
storage: {
|
|
190
|
+
documents: localStorage({
|
|
191
|
+
uploadDir: './public/uploads/documents',
|
|
192
|
+
serveUrl: '/uploads/documents',
|
|
193
|
+
generateUniqueFilenames: true, // default
|
|
194
|
+
}),
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### AWS S3
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { s3Storage } from '@opensaas/stack-storage-s3'
|
|
202
|
+
|
|
203
|
+
storage: {
|
|
204
|
+
avatars: s3Storage({
|
|
205
|
+
bucket: 'my-bucket',
|
|
206
|
+
region: 'us-east-1',
|
|
207
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
208
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
209
|
+
pathPrefix: 'avatars', // optional
|
|
210
|
+
acl: 'public-read', // default: 'private'
|
|
211
|
+
customDomain: 'https://cdn.example.com', // optional
|
|
212
|
+
}),
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### S3-Compatible Services (MinIO, Backblaze, etc.)
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
storage: {
|
|
220
|
+
files: s3Storage({
|
|
221
|
+
bucket: 'my-bucket',
|
|
222
|
+
region: 'us-east-1',
|
|
223
|
+
endpoint: 'https://s3.backblazeb2.com',
|
|
224
|
+
forcePathStyle: true,
|
|
225
|
+
accessKeyId: process.env.B2_KEY_ID,
|
|
226
|
+
secretAccessKey: process.env.B2_APPLICATION_KEY,
|
|
227
|
+
}),
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Vercel Blob
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { vercelBlobStorage } from '@opensaas/stack-storage-vercel'
|
|
235
|
+
|
|
236
|
+
storage: {
|
|
237
|
+
images: vercelBlobStorage({
|
|
238
|
+
token: process.env.BLOB_READ_WRITE_TOKEN,
|
|
239
|
+
pathPrefix: 'images',
|
|
240
|
+
public: true, // default
|
|
241
|
+
}),
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Image Transformations
|
|
246
|
+
|
|
247
|
+
Generate multiple image variants automatically:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
avatar: image({
|
|
251
|
+
storage: 'avatars',
|
|
252
|
+
transformations: {
|
|
253
|
+
thumbnail: {
|
|
254
|
+
width: 100,
|
|
255
|
+
height: 100,
|
|
256
|
+
fit: 'cover', // crop to fill
|
|
257
|
+
format: 'webp',
|
|
258
|
+
quality: 80,
|
|
259
|
+
},
|
|
260
|
+
large: {
|
|
261
|
+
width: 1200,
|
|
262
|
+
height: 1200,
|
|
263
|
+
fit: 'inside', // scale to fit within bounds
|
|
264
|
+
format: 'jpeg',
|
|
265
|
+
quality: 90,
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
})
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Available fit modes:
|
|
272
|
+
|
|
273
|
+
- `cover` - Crop to fill dimensions
|
|
274
|
+
- `contain` - Scale to fit within dimensions (letterbox)
|
|
275
|
+
- `fill` - Stretch to fill dimensions
|
|
276
|
+
- `inside` - Scale down to fit within dimensions
|
|
277
|
+
- `outside` - Scale up to cover dimensions
|
|
278
|
+
|
|
279
|
+
## Validation
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
file({
|
|
283
|
+
storage: 'documents',
|
|
284
|
+
validation: {
|
|
285
|
+
maxFileSize: 10 * 1024 * 1024, // 10MB in bytes
|
|
286
|
+
acceptedMimeTypes: ['application/pdf', 'application/msword'],
|
|
287
|
+
acceptedExtensions: ['.pdf', '.doc', '.docx'],
|
|
288
|
+
},
|
|
289
|
+
})
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Metadata Storage
|
|
293
|
+
|
|
294
|
+
Files and images store metadata as JSON in your database:
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// FileMetadata
|
|
298
|
+
{
|
|
299
|
+
filename: "1234567890-abc123.pdf",
|
|
300
|
+
originalFilename: "resume.pdf",
|
|
301
|
+
url: "https://bucket.s3.amazonaws.com/...",
|
|
302
|
+
mimeType: "application/pdf",
|
|
303
|
+
size: 245678,
|
|
304
|
+
uploadedAt: "2025-10-31T12:00:00Z",
|
|
305
|
+
storageProvider: "documents",
|
|
306
|
+
metadata: { /* custom metadata */ }
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ImageMetadata (extends FileMetadata)
|
|
310
|
+
{
|
|
311
|
+
filename: "1234567890-abc123.jpg",
|
|
312
|
+
originalFilename: "avatar.jpg",
|
|
313
|
+
url: "https://bucket.s3.amazonaws.com/...",
|
|
314
|
+
mimeType: "image/jpeg",
|
|
315
|
+
size: 123456,
|
|
316
|
+
width: 1200,
|
|
317
|
+
height: 800,
|
|
318
|
+
uploadedAt: "2025-10-31T12:00:00Z",
|
|
319
|
+
storageProvider: "avatars",
|
|
320
|
+
transformations: {
|
|
321
|
+
thumbnail: {
|
|
322
|
+
url: "https://...",
|
|
323
|
+
width: 100,
|
|
324
|
+
height: 100,
|
|
325
|
+
size: 5678
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Runtime Utilities
|
|
332
|
+
|
|
333
|
+
### Upload Helpers
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { uploadFile, uploadImage, deleteFile, deleteImage } from '@opensaas/stack-storage/runtime'
|
|
337
|
+
|
|
338
|
+
// Upload file
|
|
339
|
+
const metadata = await uploadFile(
|
|
340
|
+
config,
|
|
341
|
+
'documents',
|
|
342
|
+
{ file, buffer },
|
|
343
|
+
{
|
|
344
|
+
validation: { maxFileSize: 10 * 1024 * 1024 },
|
|
345
|
+
},
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
// Upload image with transformations
|
|
349
|
+
const imageMetadata = await uploadImage(
|
|
350
|
+
config,
|
|
351
|
+
'avatars',
|
|
352
|
+
{ file, buffer },
|
|
353
|
+
{
|
|
354
|
+
validation: { maxFileSize: 5 * 1024 * 1024 },
|
|
355
|
+
transformations: {
|
|
356
|
+
thumbnail: { width: 100, height: 100, fit: 'cover' },
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
// Delete file
|
|
362
|
+
await deleteFile(config, 'documents', metadata.filename)
|
|
363
|
+
|
|
364
|
+
// Delete image (includes all transformations)
|
|
365
|
+
await deleteImage(config, imageMetadata)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Validation Utilities
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
import { validateFile, formatFileSize, getMimeType } from '@opensaas/stack-storage/utils'
|
|
372
|
+
|
|
373
|
+
const validation = validateFile(
|
|
374
|
+
{ size: file.size, name: file.name, type: file.type },
|
|
375
|
+
{ maxFileSize: 10 * 1024 * 1024, acceptedMimeTypes: ['application/pdf'] },
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
if (!validation.valid) {
|
|
379
|
+
console.error(validation.error)
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## Custom Storage Providers
|
|
384
|
+
|
|
385
|
+
Implement the `StorageProvider` interface:
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
import type { StorageProvider, UploadOptions, UploadResult } from '@opensaas/stack-storage'
|
|
389
|
+
|
|
390
|
+
export class CustomStorageProvider implements StorageProvider {
|
|
391
|
+
async upload(
|
|
392
|
+
file: Buffer | Uint8Array,
|
|
393
|
+
filename: string,
|
|
394
|
+
options?: UploadOptions,
|
|
395
|
+
): Promise<UploadResult> {
|
|
396
|
+
// Your upload logic
|
|
397
|
+
return {
|
|
398
|
+
filename: 'generated-filename.jpg',
|
|
399
|
+
url: 'https://your-cdn.com/file.jpg',
|
|
400
|
+
size: file.length,
|
|
401
|
+
contentType: options?.contentType || 'application/octet-stream',
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async download(filename: string): Promise<Buffer> {
|
|
406
|
+
// Your download logic
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async delete(filename: string): Promise<void> {
|
|
410
|
+
// Your delete logic
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
getUrl(filename: string): string {
|
|
414
|
+
return `https://your-cdn.com/${filename}`
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async getSignedUrl(filename: string, expiresIn?: number): Promise<string> {
|
|
418
|
+
// Optional: signed URLs for private files
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
## License
|
|
424
|
+
|
|
425
|
+
MIT
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { LocalStorageConfig } from './types.js';
|
|
2
|
+
export * from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a local filesystem storage configuration
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* const config = config({
|
|
9
|
+
* storage: {
|
|
10
|
+
* documents: localStorage({
|
|
11
|
+
* uploadDir: './uploads/documents',
|
|
12
|
+
* serveUrl: '/api/files',
|
|
13
|
+
* }),
|
|
14
|
+
* },
|
|
15
|
+
* })
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function localStorage(config: Pick<LocalStorageConfig, 'uploadDir' | 'serveUrl'> & Partial<Pick<LocalStorageConfig, 'generateUniqueFilenames'>>): LocalStorageConfig;
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD,cAAc,YAAY,CAAA;AAE1B;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,WAAW,GAAG,UAAU,CAAC,GACxD,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,yBAAyB,CAAC,CAAC,GAC7D,kBAAkB,CAOpB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a local filesystem storage configuration
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* const config = config({
|
|
8
|
+
* storage: {
|
|
9
|
+
* documents: localStorage({
|
|
10
|
+
* uploadDir: './uploads/documents',
|
|
11
|
+
* serveUrl: '/api/files',
|
|
12
|
+
* }),
|
|
13
|
+
* },
|
|
14
|
+
* })
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function localStorage(config) {
|
|
18
|
+
return {
|
|
19
|
+
type: 'local',
|
|
20
|
+
uploadDir: config.uploadDir,
|
|
21
|
+
serveUrl: config.serveUrl,
|
|
22
|
+
generateUniqueFilenames: config.generateUniqueFilenames ?? true,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAEA,cAAc,YAAY,CAAA;AAE1B;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,YAAY,CAC1B,MAC8D;IAE9D,OAAO;QACL,IAAI,EAAE,OAAO;QACb,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,uBAAuB,EAAE,MAAM,CAAC,uBAAuB,IAAI,IAAI;KAChE,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage provider interface that all storage backends must implement.
|
|
3
|
+
* This allows pluggable storage solutions (local, S3, Vercel Blob, etc.)
|
|
4
|
+
*/
|
|
5
|
+
export interface StorageProvider {
|
|
6
|
+
/**
|
|
7
|
+
* Uploads a file to the storage provider
|
|
8
|
+
* @param file - File data as Buffer or Uint8Array
|
|
9
|
+
* @param filename - Desired filename (may be transformed by provider)
|
|
10
|
+
* @param options - Additional upload options (contentType, metadata, etc.)
|
|
11
|
+
* @returns Upload result with URL and metadata
|
|
12
|
+
*/
|
|
13
|
+
upload(file: Buffer | Uint8Array, filename: string, options?: UploadOptions): Promise<UploadResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Downloads a file from the storage provider
|
|
16
|
+
* @param filename - Filename to download
|
|
17
|
+
* @returns File data as Buffer
|
|
18
|
+
*/
|
|
19
|
+
download(filename: string): Promise<Buffer>;
|
|
20
|
+
/**
|
|
21
|
+
* Deletes a file from the storage provider
|
|
22
|
+
* @param filename - Filename to delete
|
|
23
|
+
*/
|
|
24
|
+
delete(filename: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Gets the public URL for a file
|
|
27
|
+
* @param filename - Filename to get URL for
|
|
28
|
+
* @returns Public URL string
|
|
29
|
+
*/
|
|
30
|
+
getUrl(filename: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Optional: Gets a signed URL for private files
|
|
33
|
+
* @param filename - Filename to get signed URL for
|
|
34
|
+
* @param expiresIn - Expiration time in seconds
|
|
35
|
+
* @returns Signed URL string
|
|
36
|
+
*/
|
|
37
|
+
getSignedUrl?(filename: string, expiresIn?: number): Promise<string>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Options for uploading a file
|
|
41
|
+
*/
|
|
42
|
+
export interface UploadOptions {
|
|
43
|
+
/** MIME type of the file */
|
|
44
|
+
contentType?: string;
|
|
45
|
+
/** Custom metadata to store with the file */
|
|
46
|
+
metadata?: Record<string, string>;
|
|
47
|
+
/** Whether the file should be publicly accessible */
|
|
48
|
+
public?: boolean;
|
|
49
|
+
/** Cache control header */
|
|
50
|
+
cacheControl?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Result from uploading a file
|
|
54
|
+
*/
|
|
55
|
+
export interface UploadResult {
|
|
56
|
+
/** Generated filename (may differ from input) */
|
|
57
|
+
filename: string;
|
|
58
|
+
/** Public URL to access the file */
|
|
59
|
+
url: string;
|
|
60
|
+
/** File size in bytes */
|
|
61
|
+
size: number;
|
|
62
|
+
/** MIME type */
|
|
63
|
+
contentType: string;
|
|
64
|
+
/** Additional provider-specific metadata */
|
|
65
|
+
metadata?: Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Configuration for local filesystem storage
|
|
69
|
+
*/
|
|
70
|
+
export interface LocalStorageConfig {
|
|
71
|
+
type: 'local';
|
|
72
|
+
/** Directory to store uploaded files */
|
|
73
|
+
uploadDir: string;
|
|
74
|
+
/** Base URL for serving files (e.g., '/api/files' or 'https://cdn.example.com') */
|
|
75
|
+
serveUrl: string;
|
|
76
|
+
/** Whether to generate unique filenames (default: true) */
|
|
77
|
+
generateUniqueFilenames?: boolean;
|
|
78
|
+
/** Allow additional properties */
|
|
79
|
+
[key: string]: unknown;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Base configuration shared by all storage providers
|
|
83
|
+
*/
|
|
84
|
+
export interface BaseStorageConfig {
|
|
85
|
+
type: string;
|
|
86
|
+
[key: string]: unknown;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Storage configuration - maps names to storage provider configs
|
|
90
|
+
* Example: { avatars: s3Config, documents: localConfig }
|
|
91
|
+
*/
|
|
92
|
+
export type StorageConfig = Record<string, BaseStorageConfig | LocalStorageConfig>;
|
|
93
|
+
/**
|
|
94
|
+
* Re-export metadata types from core package
|
|
95
|
+
* These types are now defined in @opensaas/stack-core to avoid circular dependencies
|
|
96
|
+
*/
|
|
97
|
+
export type { FileMetadata, ImageMetadata, ImageTransformationResult } from '@opensaas/stack-core';
|
|
98
|
+
/**
|
|
99
|
+
* Configuration for image transformations
|
|
100
|
+
*/
|
|
101
|
+
export interface ImageTransformationConfig {
|
|
102
|
+
/** Target width in pixels */
|
|
103
|
+
width?: number;
|
|
104
|
+
/** Target height in pixels */
|
|
105
|
+
height?: number;
|
|
106
|
+
/** Fit mode: cover (crop), contain (letterbox), fill (stretch) */
|
|
107
|
+
fit?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
|
|
108
|
+
/** Output format (default: original format) */
|
|
109
|
+
format?: 'jpeg' | 'png' | 'webp' | 'avif';
|
|
110
|
+
/** Quality 1-100 (default: 80) */
|
|
111
|
+
quality?: number;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,MAAM,CACJ,IAAI,EAAE,MAAM,GAAG,UAAU,EACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,YAAY,CAAC,CAAA;IAExB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAE3C;;;OAGG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEvC;;;;OAIG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;IAEhC;;;;;OAKG;IACH,YAAY,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACrE;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACjC,qDAAqD;IACrD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAA;IAChB,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAA;IACX,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,gBAAgB;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAA;IACb,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAA;IACjB,mFAAmF;IACnF,QAAQ,EAAE,MAAM,CAAA;IAChB,2DAA2D;IAC3D,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,kCAAkC;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,kBAAkB,CAAC,CAAA;AAElF;;;GAGG;AACH,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAA;AAElG;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,GAAG,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;IACzD,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAA;IACzC,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":""}
|