@rebasepro/cli 0.6.1 → 0.7.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/README.md +0 -1
- package/dist/commands/api-keys.d.ts +1 -0
- package/dist/commands/generate_sdk.d.ts +1 -1
- package/dist/index.es.js +399 -57
- package/dist/index.es.js.map +1 -1
- package/package.json +20 -15
- package/templates/template/backend/package.json +0 -1
- package/templates/template/backend/src/index.ts +1 -1
- package/templates/template/config/collections/posts.ts +1 -3
- package/templates/template/config/collections/tags.ts +1 -3
- package/templates/template/config/collections/users.ts +1 -4
- package/templates/template/package.json +0 -1
- package/templates/template/scripts/example.ts +4 -1
- package/skills/rebase-admin/SKILL.md +0 -710
- package/skills/rebase-api/SKILL.md +0 -662
- package/skills/rebase-api/references/.gitkeep +0 -3
- package/skills/rebase-auth/SKILL.md +0 -1143
- package/skills/rebase-auth/references/.gitkeep +0 -3
- package/skills/rebase-backend-postgres/SKILL.md +0 -633
- package/skills/rebase-backend-postgres/references/.gitkeep +0 -3
- package/skills/rebase-basics/SKILL.md +0 -749
- package/skills/rebase-basics/references/.gitkeep +0 -3
- package/skills/rebase-collections/SKILL.md +0 -1328
- package/skills/rebase-collections/references/.gitkeep +0 -3
- package/skills/rebase-cron-jobs/SKILL.md +0 -699
- package/skills/rebase-cron-jobs/references/.gitkeep +0 -1
- package/skills/rebase-custom-functions/SKILL.md +0 -233
- package/skills/rebase-deployment/SKILL.md +0 -885
- package/skills/rebase-deployment/references/.gitkeep +0 -3
- package/skills/rebase-design-language/SKILL.md +0 -692
- package/skills/rebase-email/SKILL.md +0 -701
- package/skills/rebase-email/references/.gitkeep +0 -1
- package/skills/rebase-entity-history/SKILL.md +0 -485
- package/skills/rebase-entity-history/references/.gitkeep +0 -1
- package/skills/rebase-local-env-setup/SKILL.md +0 -189
- package/skills/rebase-local-env-setup/references/.gitkeep +0 -3
- package/skills/rebase-realtime/SKILL.md +0 -755
- package/skills/rebase-realtime/references/.gitkeep +0 -3
- package/skills/rebase-sdk/SKILL.md +0 -594
- package/skills/rebase-sdk/references/.gitkeep +0 -0
- package/skills/rebase-storage/SKILL.md +0 -765
- package/skills/rebase-storage/references/.gitkeep +0 -3
- package/skills/rebase-studio/SKILL.md +0 -746
- package/skills/rebase-studio/references/.gitkeep +0 -3
- package/skills/rebase-ui-components/SKILL.md +0 -1488
- package/skills/rebase-ui-components/references/.gitkeep +0 -3
- package/skills/rebase-webhooks/SKILL.md +0 -623
- package/skills/rebase-webhooks/references/.gitkeep +0 -1
- package/templates/template/backend/drizzle.config.ts +0 -51
|
@@ -1,765 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: rebase-storage
|
|
3
|
-
description: Guide for setting up and using file storage in Rebase. Use this skill when the user needs to configure S3 or local file storage, handle file uploads, TUS resumable uploads, image transformations, or integrate the media manager.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Rebase Storage
|
|
7
|
-
|
|
8
|
-
Rebase provides built-in file storage with support for local filesystem and S3-compatible services, TUS v1.0.0 resumable uploads, on-the-fly image transformation, and a multi-backend registry.
|
|
9
|
-
|
|
10
|
-
> **IMPORTANT FOR AGENTS:** Always read the `rebase-basics` skill first before using this skill. Storage requires a running Rebase backend with `initializeRebaseBackend()`.
|
|
11
|
-
|
|
12
|
-
## Storage Configuration
|
|
13
|
-
|
|
14
|
-
The `storage` option in `initializeRebaseBackend()` accepts three forms:
|
|
15
|
-
|
|
16
|
-
| Form | Type | Description |
|
|
17
|
-
|------|------|-------------|
|
|
18
|
-
| Single config | `BackendStorageConfig` | `{ type: 'local' | 's3', ... }` — creates a single `(default)` backend |
|
|
19
|
-
| Single controller | `StorageController` | A custom controller instance — registered as `(default)` |
|
|
20
|
-
| Multi-backend map | `Record<string, BackendStorageConfig \| StorageController>` | Named backends, first becomes `(default)` if no `(default)` key |
|
|
21
|
-
|
|
22
|
-
### LocalStorageConfig
|
|
23
|
-
|
|
24
|
-
| Property | Type | Default | Description |
|
|
25
|
-
|----------|------|---------|-------------|
|
|
26
|
-
| `type` | `"local"` | — | **Required.** Must be `"local"` |
|
|
27
|
-
| `basePath` | `string` | — | **Required.** Base directory for file storage (e.g., `"./uploads"`) |
|
|
28
|
-
| `maxFileSize` | `number` | `52428800` (50 MB) | Maximum file size in bytes |
|
|
29
|
-
| `allowedMimeTypes` | `string[]` | `undefined` (all allowed) | Restrict uploads to these MIME types |
|
|
30
|
-
| `baseUrl` | `string` | Auto-detected | Base URL for generating download URLs |
|
|
31
|
-
|
|
32
|
-
### S3StorageConfig
|
|
33
|
-
|
|
34
|
-
| Property | Type | Default | Description |
|
|
35
|
-
|----------|------|---------|-------------|
|
|
36
|
-
| `type` | `"s3"` | — | **Required.** Must be `"s3"` |
|
|
37
|
-
| `bucket` | `string` | — | **Required.** S3 bucket name |
|
|
38
|
-
| `accessKeyId` | `string` | — | **Required.** AWS access key ID |
|
|
39
|
-
| `secretAccessKey` | `string` | — | **Required.** AWS secret access key |
|
|
40
|
-
| `region` | `string` | `"us-east-1"` | AWS region |
|
|
41
|
-
| `endpoint` | `string` | `undefined` | Custom endpoint URL (required for MinIO, R2, Hetzner, etc.) |
|
|
42
|
-
| `forcePathStyle` | `boolean` | Auto-enabled when `endpoint` is set | Use path-style URLs (required for MinIO) |
|
|
43
|
-
| `maxFileSize` | `number` | `52428800` (50 MB) | Maximum file size in bytes |
|
|
44
|
-
| `allowedMimeTypes` | `string[]` | `undefined` (all allowed) | Restrict uploads to these MIME types |
|
|
45
|
-
| `signedUrlExpiration` | `number` | `3600` | Signed URL expiry in seconds |
|
|
46
|
-
|
|
47
|
-
### StorageRoutesConfig
|
|
48
|
-
|
|
49
|
-
These options are set internally when mounting routes but are derived from the backend config:
|
|
50
|
-
|
|
51
|
-
| Property | Type | Default | Description |
|
|
52
|
-
|----------|------|---------|-------------|
|
|
53
|
-
| `controller` | `StorageController` | — | **Required.** The storage controller instance |
|
|
54
|
-
| `basePath` | `string` | `"/api/storage"` | Base path for storage routes |
|
|
55
|
-
| `requireAuth` | `boolean` | `true` | Require authentication for write operations |
|
|
56
|
-
| `publicRead` | `boolean` | `false` | Allow unauthenticated read access to stored files |
|
|
57
|
-
|
|
58
|
-
## Storage Providers
|
|
59
|
-
|
|
60
|
-
### Local Storage
|
|
61
|
-
|
|
62
|
-
Store files on the local filesystem. Best for development and simple single-server deployments.
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
import { initializeRebaseBackend } from "@rebasepro/server-core";
|
|
66
|
-
|
|
67
|
-
const backend = await initializeRebaseBackend({
|
|
68
|
-
server, app,
|
|
69
|
-
bootstrappers: [/* ... */],
|
|
70
|
-
storage: {
|
|
71
|
-
type: "local",
|
|
72
|
-
basePath: "./uploads",
|
|
73
|
-
maxFileSize: 50 * 1024 * 1024, // 50MB (default)
|
|
74
|
-
allowedMimeTypes: ["image/jpeg", "image/png", "application/pdf"],
|
|
75
|
-
baseUrl: "http://localhost:3001/api/storage",
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
> **WARNING FOR AGENTS:** In production, the backend logs a warning if local storage is used. Local files are lost on container restart. Set `FORCE_LOCAL_STORAGE=true` to suppress the warning, or use S3 for production.
|
|
81
|
-
|
|
82
|
-
Local storage uses a `{basePath}/{bucket}/{path}` directory structure. The default bucket is `"default"`. Every uploaded file gets a sidecar `.metadata.json` file containing:
|
|
83
|
-
|
|
84
|
-
```json
|
|
85
|
-
{
|
|
86
|
-
"contentType": "image/jpeg",
|
|
87
|
-
"size": 204800,
|
|
88
|
-
"uploadedAt": "2026-01-15T10:30:00.000Z"
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Local storage includes **path traversal protection** — any resolved path that escapes the bucket directory throws an error.
|
|
93
|
-
|
|
94
|
-
### S3-Compatible Storage
|
|
95
|
-
|
|
96
|
-
Works with AWS S3, MinIO, DigitalOcean Spaces, Cloudflare R2, Hetzner Object Storage, Backblaze B2, and any S3-compatible service. GCS also works via its S3-compatible interoperability API.
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
const backend = await initializeRebaseBackend({
|
|
100
|
-
server, app,
|
|
101
|
-
bootstrappers: [/* ... */],
|
|
102
|
-
storage: {
|
|
103
|
-
type: "s3",
|
|
104
|
-
bucket: process.env.S3_BUCKET!,
|
|
105
|
-
region: process.env.S3_REGION,
|
|
106
|
-
accessKeyId: process.env.S3_ACCESS_KEY_ID!,
|
|
107
|
-
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
|
|
108
|
-
endpoint: process.env.S3_ENDPOINT, // For MinIO, R2, etc.
|
|
109
|
-
forcePathStyle: true, // Required for MinIO
|
|
110
|
-
signedUrlExpiration: 3600, // URL expiry in seconds
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
The S3 controller:
|
|
116
|
-
- Auto-enables `forcePathStyle` when a custom `endpoint` is set
|
|
117
|
-
- Maps the logical bucket name `"default"` to the configured S3 bucket
|
|
118
|
-
- Supports `s3://` and `gs://` URL schemes in key parameters for backward compatibility
|
|
119
|
-
- Flattens nested metadata to string values (S3 requirement)
|
|
120
|
-
|
|
121
|
-
### Multiple Storage Backends
|
|
122
|
-
|
|
123
|
-
Rebase supports multiple storage backends simultaneously via the `StorageRegistry`:
|
|
124
|
-
|
|
125
|
-
```typescript
|
|
126
|
-
const backend = await initializeRebaseBackend({
|
|
127
|
-
server, app,
|
|
128
|
-
bootstrappers: [/* ... */],
|
|
129
|
-
storage: {
|
|
130
|
-
"(default)": { type: "local", basePath: "./uploads" },
|
|
131
|
-
"media": {
|
|
132
|
-
type: "s3",
|
|
133
|
-
bucket: "my-media-bucket",
|
|
134
|
-
accessKeyId: "...",
|
|
135
|
-
secretAccessKey: "...",
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
> **IMPORTANT FOR AGENTS:** If no `"(default)"` key is provided, the first entry is automatically registered as the default (with a console warning). The REST API routes always use the default controller. Use `storageRegistry.get("media")` or `storageRegistry.getOrDefault("media")` to access named backends programmatically.
|
|
142
|
-
|
|
143
|
-
### StorageRegistry API
|
|
144
|
-
|
|
145
|
-
The `StorageRegistry` interface:
|
|
146
|
-
|
|
147
|
-
| Method | Signature | Description |
|
|
148
|
-
|--------|-----------|-------------|
|
|
149
|
-
| `register` | `(id: string, controller: StorageController) => void` | Register a controller with an ID |
|
|
150
|
-
| `getDefault` | `() => StorageController` | Get the `(default)` controller (throws if none) |
|
|
151
|
-
| `get` | `(id: string \| undefined \| null) => StorageController \| undefined` | Get by ID, returns `undefined` if not found |
|
|
152
|
-
| `getOrDefault` | `(id: string \| undefined \| null) => StorageController` | Get by ID with fallback to default (throws if neither exists) |
|
|
153
|
-
| `has` | `(id: string) => boolean` | Check if a storage ID exists |
|
|
154
|
-
| `list` | `() => string[]` | List all registered storage IDs |
|
|
155
|
-
| `size` | `() => number` | Number of registered controllers |
|
|
156
|
-
|
|
157
|
-
### Custom Storage Providers
|
|
158
|
-
|
|
159
|
-
Implement the `StorageController` interface for unsupported providers (Azure Blob, native GCS SDK, etc.):
|
|
160
|
-
|
|
161
|
-
```typescript
|
|
162
|
-
interface StorageController {
|
|
163
|
-
putObject(props: UploadFileProps): Promise<UploadFileResult>;
|
|
164
|
-
getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig>;
|
|
165
|
-
getObject(key: string, bucket?: string): Promise<File | null>;
|
|
166
|
-
deleteObject(key: string, bucket?: string): Promise<void>;
|
|
167
|
-
listObjects(prefix: string, options?: {
|
|
168
|
-
bucket?: string;
|
|
169
|
-
maxResults?: number;
|
|
170
|
-
pageToken?: string;
|
|
171
|
-
}): Promise<StorageListResult>;
|
|
172
|
-
getType(): string; // e.g. "gcs", "azure"
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
Pass the instance directly to `storage`:
|
|
177
|
-
|
|
178
|
-
```typescript
|
|
179
|
-
storage: new MyGCSStorageController({ projectId: "...", bucket: "..." }),
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
## Environment Variables
|
|
183
|
-
|
|
184
|
-
The backend validates storage-related environment variables via a Zod schema:
|
|
185
|
-
|
|
186
|
-
| Variable | Type | Default | Description |
|
|
187
|
-
|----------|------|---------|-------------|
|
|
188
|
-
| `STORAGE_TYPE` | `"local" \| "s3"` | `"local"` | Storage provider type |
|
|
189
|
-
| `STORAGE_PATH` | `string` | `"./uploads"` | Base path for local storage / TUS temp directory |
|
|
190
|
-
| `FORCE_LOCAL_STORAGE` | `"true" \| "false"` | `false` | Suppress the production warning for local storage |
|
|
191
|
-
| `S3_BUCKET` | `string` | — | S3 bucket name |
|
|
192
|
-
| `S3_REGION` | `string` | — | S3 region |
|
|
193
|
-
| `S3_ACCESS_KEY_ID` | `string` | — | S3 access key ID |
|
|
194
|
-
| `S3_SECRET_ACCESS_KEY` | `string` | — | S3 secret access key |
|
|
195
|
-
| `S3_ENDPOINT` | `string` (URL) | — | Custom S3-compatible endpoint |
|
|
196
|
-
| `S3_FORCE_PATH_STYLE` | `"true" \| "false"` | — | Enable path-style URLs |
|
|
197
|
-
|
|
198
|
-
```env
|
|
199
|
-
STORAGE_TYPE=s3
|
|
200
|
-
S3_BUCKET=my-bucket
|
|
201
|
-
S3_REGION=us-east-1
|
|
202
|
-
S3_ACCESS_KEY_ID=AKIA...
|
|
203
|
-
S3_SECRET_ACCESS_KEY=...
|
|
204
|
-
S3_ENDPOINT=https://minio.example.com # Optional: for MinIO, R2, etc.
|
|
205
|
-
S3_FORCE_PATH_STYLE=true # Optional: for MinIO
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
## REST API Endpoints
|
|
209
|
-
|
|
210
|
-
All storage routes are mounted at `/api/storage` by default. Write operations require authentication when `requireAuth` is `true` (the default). Read operations also require authentication unless `publicRead` is `true`.
|
|
211
|
-
|
|
212
|
-
### Standard Endpoints
|
|
213
|
-
|
|
214
|
-
| Method | Endpoint | Auth | Description |
|
|
215
|
-
|--------|----------|------|-------------|
|
|
216
|
-
| `POST` | `/api/storage/upload` | Write | Upload a file (multipart/form-data) |
|
|
217
|
-
| `GET` | `/api/storage/file/*` | Read | Download / serve a file (supports image transforms) |
|
|
218
|
-
| `DELETE` | `/api/storage/file/*` | Write | Delete a file |
|
|
219
|
-
| `GET` | `/api/storage/metadata/*` | Read | Get file metadata |
|
|
220
|
-
| `GET` | `/api/storage/list` | Write | List files in a prefix |
|
|
221
|
-
| `POST` | `/api/storage/folder` | Write | Create a new folder |
|
|
222
|
-
|
|
223
|
-
### TUS Resumable Upload Endpoints
|
|
224
|
-
|
|
225
|
-
| Method | Endpoint | Auth | Description |
|
|
226
|
-
|--------|----------|------|-------------|
|
|
227
|
-
| `OPTIONS` | `/api/storage/tus` | None | TUS capability discovery |
|
|
228
|
-
| `POST` | `/api/storage/tus` | Write | Create a new resumable upload |
|
|
229
|
-
| `HEAD` | `/api/storage/tus/:id` | Read | Query upload progress |
|
|
230
|
-
| `PATCH` | `/api/storage/tus/:id` | Write | Append data to an upload |
|
|
231
|
-
| `DELETE` | `/api/storage/tus/:id` | Write | Cancel and remove an upload |
|
|
232
|
-
|
|
233
|
-
### POST /api/storage/upload
|
|
234
|
-
|
|
235
|
-
Upload a file via `multipart/form-data`.
|
|
236
|
-
|
|
237
|
-
**Request body fields:**
|
|
238
|
-
|
|
239
|
-
| Field | Type | Required | Description |
|
|
240
|
-
|-------|------|----------|-------------|
|
|
241
|
-
| `file` | `File` | Yes | The file to upload |
|
|
242
|
-
| `key` | `string` | No | Storage key/path. Falls back to original filename, then `"unnamed"` |
|
|
243
|
-
| `bucket` | `string` | No | Target bucket name |
|
|
244
|
-
| `metadata_*` | `string` | No | Custom metadata keys prefixed with `metadata_` |
|
|
245
|
-
|
|
246
|
-
**Response** (201):
|
|
247
|
-
|
|
248
|
-
```json
|
|
249
|
-
{
|
|
250
|
-
"success": true,
|
|
251
|
-
"data": {
|
|
252
|
-
"key": "products/images/photo.jpg",
|
|
253
|
-
"bucket": "default",
|
|
254
|
-
"storageUrl": "local://default/products/images/photo.jpg"
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
The `storageUrl` format is `local://{bucket}/{key}` for local storage and `s3://{bucket}/{key}` for S3.
|
|
260
|
-
|
|
261
|
-
**Error responses:**
|
|
262
|
-
|
|
263
|
-
| Status | Condition |
|
|
264
|
-
|--------|-----------|
|
|
265
|
-
| 400 | No file provided in request body |
|
|
266
|
-
| 413 | File exceeds `maxFileSize` (body limit middleware) |
|
|
267
|
-
|
|
268
|
-
### GET /api/storage/file/{path}
|
|
269
|
-
|
|
270
|
-
Download or serve a file. The path can include a bucket prefix (e.g., `/file/default/images/photo.jpg`) or omit it to use the default bucket.
|
|
271
|
-
|
|
272
|
-
For **local storage**, files are served directly from disk with content type read from the `.metadata.json` sidecar file.
|
|
273
|
-
|
|
274
|
-
For **S3 storage**, files are proxied through the backend (not redirected to signed URLs). This avoids mixed-content issues and unreachable internal VPC endpoints.
|
|
275
|
-
|
|
276
|
-
**Image transformation query parameters** can be appended — see [Image Transformation](#image-transformation).
|
|
277
|
-
|
|
278
|
-
**Response headers:**
|
|
279
|
-
- `Content-Type` — from metadata or inferred
|
|
280
|
-
- `Cache-Control: public, max-age=31536000, immutable` (when image transforms are applied)
|
|
281
|
-
- `Cache-Control: public, max-age=3600, immutable` (for S3-proxied files without transforms)
|
|
282
|
-
|
|
283
|
-
### GET /api/storage/metadata/{path}
|
|
284
|
-
|
|
285
|
-
Get metadata for a file without downloading it.
|
|
286
|
-
|
|
287
|
-
**Response** (200):
|
|
288
|
-
|
|
289
|
-
```json
|
|
290
|
-
{
|
|
291
|
-
"success": true,
|
|
292
|
-
"data": {
|
|
293
|
-
"bucket": "default",
|
|
294
|
-
"fullPath": "products/images/photo.jpg",
|
|
295
|
-
"name": "photo.jpg",
|
|
296
|
-
"size": 204800,
|
|
297
|
-
"contentType": "image/jpeg",
|
|
298
|
-
"customMetadata": {}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
### GET /api/storage/list
|
|
304
|
-
|
|
305
|
-
List files and folders in a given prefix.
|
|
306
|
-
|
|
307
|
-
**Query parameters:**
|
|
308
|
-
|
|
309
|
-
| Parameter | Type | Default | Description |
|
|
310
|
-
|-----------|------|---------|-------------|
|
|
311
|
-
| `prefix` | `string` | `""` | Path prefix to list (also accepts `path` for backward compat) |
|
|
312
|
-
| `bucket` | `string` | `"default"` (local) | Bucket name |
|
|
313
|
-
| `maxResults` | `number` | `1000` | Maximum number of results |
|
|
314
|
-
| `pageToken` | `string` | — | Pagination token from previous response |
|
|
315
|
-
|
|
316
|
-
**Response** (200):
|
|
317
|
-
|
|
318
|
-
```json
|
|
319
|
-
{
|
|
320
|
-
"success": true,
|
|
321
|
-
"data": {
|
|
322
|
-
"items": [
|
|
323
|
-
{ "bucket": "default", "fullPath": "images/photo.jpg", "name": "photo.jpg" }
|
|
324
|
-
],
|
|
325
|
-
"prefixes": [
|
|
326
|
-
{ "bucket": "default", "fullPath": "images/thumbnails", "name": "thumbnails" }
|
|
327
|
-
],
|
|
328
|
-
"nextPageToken": "25"
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
- `items` — files in the prefix
|
|
334
|
-
- `prefixes` — subdirectories/folders in the prefix
|
|
335
|
-
- `nextPageToken` — pass to the next request for pagination (only present if more results exist)
|
|
336
|
-
|
|
337
|
-
For S3, listing uses the `Delimiter: "/"` for folder-like behavior via `ListObjectsV2Command`.
|
|
338
|
-
|
|
339
|
-
### POST /api/storage/folder
|
|
340
|
-
|
|
341
|
-
Create a new folder.
|
|
342
|
-
|
|
343
|
-
**Request body:**
|
|
344
|
-
|
|
345
|
-
```json
|
|
346
|
-
{
|
|
347
|
-
"path": "products/images/thumbnails",
|
|
348
|
-
"bucket": "default"
|
|
349
|
-
}
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
For **local storage**, creates the directory on disk. For **S3**, creates a zero-byte marker object with a trailing `/` and content type `application/x-directory`.
|
|
353
|
-
|
|
354
|
-
**Response** (201):
|
|
355
|
-
|
|
356
|
-
```json
|
|
357
|
-
{ "success": true, "message": "Folder created" }
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
### DELETE /api/storage/file/{path}
|
|
361
|
-
|
|
362
|
-
Delete a file. For local storage, also deletes the `.metadata.json` sidecar file. Directories can only be deleted if empty (ENOTEMPTY errors are silently ignored).
|
|
363
|
-
|
|
364
|
-
**Response** (200):
|
|
365
|
-
|
|
366
|
-
```json
|
|
367
|
-
{ "success": true, "message": "File deleted" }
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
## TUS Resumable Uploads
|
|
371
|
-
|
|
372
|
-
Rebase implements [TUS v1.0.0](https://tus.io/protocols/resumable-upload) with the **Creation** and **Termination** extensions. This allows reliable upload of large files (up to 5 GB) with automatic resume on network failure.
|
|
373
|
-
|
|
374
|
-
### TUS Configuration
|
|
375
|
-
|
|
376
|
-
| Setting | Value |
|
|
377
|
-
|---------|-------|
|
|
378
|
-
| TUS version | `1.0.0` |
|
|
379
|
-
| Extensions | `creation`, `termination` |
|
|
380
|
-
| Max upload size | 5 GB (`5 * 1024 * 1024 * 1024` bytes) |
|
|
381
|
-
| Stale upload expiry | 24 hours |
|
|
382
|
-
| Cleanup interval | Every 60 seconds |
|
|
383
|
-
| Temp directory | `{storageBasePath}/.tus-uploads` |
|
|
384
|
-
|
|
385
|
-
TUS uploads are stored in a temporary directory and automatically moved to the final storage backend (via `putObject`) when the upload completes.
|
|
386
|
-
|
|
387
|
-
### TUS Protocol Flow
|
|
388
|
-
|
|
389
|
-
```
|
|
390
|
-
1. OPTIONS /api/storage/tus → Discover capabilities
|
|
391
|
-
2. POST /api/storage/tus → Create upload, get Location header
|
|
392
|
-
3. PATCH /api/storage/tus/:id → Send chunks (repeat until done)
|
|
393
|
-
4. (auto) Upload finalized → moved to storage controller
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
### OPTIONS /api/storage/tus
|
|
397
|
-
|
|
398
|
-
Returns TUS server capabilities.
|
|
399
|
-
|
|
400
|
-
**Response headers:**
|
|
401
|
-
|
|
402
|
-
```
|
|
403
|
-
Tus-Resumable: 1.0.0
|
|
404
|
-
Tus-Version: 1.0.0
|
|
405
|
-
Tus-Extension: creation,termination
|
|
406
|
-
Tus-Max-Size: 5368709120
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
### POST /api/storage/tus (Create)
|
|
410
|
-
|
|
411
|
-
Create a new resumable upload.
|
|
412
|
-
|
|
413
|
-
**Required request headers:**
|
|
414
|
-
|
|
415
|
-
| Header | Description |
|
|
416
|
-
|--------|-------------|
|
|
417
|
-
| `Upload-Length` | Total file size in bytes (must be > 0 and ≤ 5 GB) |
|
|
418
|
-
| `Upload-Metadata` | TUS metadata as `key base64value,key2 base64value2` |
|
|
419
|
-
|
|
420
|
-
**Supported metadata keys:**
|
|
421
|
-
|
|
422
|
-
| Key | Description |
|
|
423
|
-
|-----|-------------|
|
|
424
|
-
| `filename` | Original file name (used as storage key if no `key` provided) |
|
|
425
|
-
| `key` | Explicit storage key/path |
|
|
426
|
-
| `bucket` | Target bucket |
|
|
427
|
-
| `contentType` | File MIME type |
|
|
428
|
-
| `filetype` | Alternative MIME type key (fallback) |
|
|
429
|
-
|
|
430
|
-
**Response** (201):
|
|
431
|
-
|
|
432
|
-
```
|
|
433
|
-
Location: http://localhost:3001/api/storage/tus/550e8400-e29b-41d4-a716-446655440000
|
|
434
|
-
Tus-Resumable: 1.0.0
|
|
435
|
-
Upload-Offset: 0
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
### HEAD /api/storage/tus/:id (Progress)
|
|
439
|
-
|
|
440
|
-
Query the current upload progress.
|
|
441
|
-
|
|
442
|
-
**Response** (200):
|
|
443
|
-
|
|
444
|
-
```
|
|
445
|
-
Tus-Resumable: 1.0.0
|
|
446
|
-
Upload-Offset: 1048576
|
|
447
|
-
Upload-Length: 10485760
|
|
448
|
-
Cache-Control: no-store
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
### PATCH /api/storage/tus/:id (Append)
|
|
452
|
-
|
|
453
|
-
Append data to an in-progress upload.
|
|
454
|
-
|
|
455
|
-
**Required request headers:**
|
|
456
|
-
|
|
457
|
-
| Header | Value |
|
|
458
|
-
|--------|-------|
|
|
459
|
-
| `Content-Type` | `application/offset+octet-stream` |
|
|
460
|
-
| `Upload-Offset` | Must match the server's current offset |
|
|
461
|
-
|
|
462
|
-
**Error responses:**
|
|
463
|
-
|
|
464
|
-
| Status | Condition |
|
|
465
|
-
|--------|-----------|
|
|
466
|
-
| 400 | Missing `Upload-Offset` header, or upload already completed |
|
|
467
|
-
| 409 | Offset mismatch (client offset ≠ server offset) |
|
|
468
|
-
| 413 | Chunk would exceed declared `Upload-Length` |
|
|
469
|
-
| 415 | Wrong `Content-Type` (must be `application/offset+octet-stream`) |
|
|
470
|
-
|
|
471
|
-
When `Upload-Offset` reaches `Upload-Length`, the upload is **automatically finalized**: the temp file is read, a `File` object is created with the metadata-derived MIME type, `putObject` is called on the storage controller, and the temp file is cleaned up.
|
|
472
|
-
|
|
473
|
-
### DELETE /api/storage/tus/:id (Terminate)
|
|
474
|
-
|
|
475
|
-
Cancel and remove an in-progress upload. Deletes the temp file and removes the upload from the in-memory registry.
|
|
476
|
-
|
|
477
|
-
### TUS Client Example
|
|
478
|
-
|
|
479
|
-
```typescript
|
|
480
|
-
// Using tus-js-client (npm install tus-js-client)
|
|
481
|
-
import * as tus from "tus-js-client";
|
|
482
|
-
|
|
483
|
-
const file = document.querySelector<HTMLInputElement>("#fileInput")!.files![0];
|
|
484
|
-
|
|
485
|
-
const upload = new tus.Upload(file, {
|
|
486
|
-
endpoint: "http://localhost:3001/api/storage/tus",
|
|
487
|
-
retryDelays: [0, 1000, 3000, 5000],
|
|
488
|
-
metadata: {
|
|
489
|
-
filename: file.name,
|
|
490
|
-
filetype: file.type,
|
|
491
|
-
key: `uploads/${file.name}`,
|
|
492
|
-
bucket: "default",
|
|
493
|
-
},
|
|
494
|
-
headers: {
|
|
495
|
-
Authorization: `Bearer ${accessToken}`,
|
|
496
|
-
},
|
|
497
|
-
onError: (error) => console.error("Upload failed:", error),
|
|
498
|
-
onProgress: (bytesUploaded, bytesTotal) => {
|
|
499
|
-
const pct = ((bytesUploaded / bytesTotal) * 100).toFixed(1);
|
|
500
|
-
console.log(`${pct}%`);
|
|
501
|
-
},
|
|
502
|
-
onSuccess: () => console.log("Upload complete:", upload.url),
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
upload.start();
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
## Image Transformation
|
|
509
|
-
|
|
510
|
-
Rebase provides on-the-fly image resize, crop, format conversion, and quality adjustment. Transformations are applied by adding query parameters to the `GET /api/storage/file/*` endpoint.
|
|
511
|
-
|
|
512
|
-
> **IMPORTANT FOR AGENTS:** Image transformation requires the `sharp` npm package as an **optional peer dependency**. Install it: `pnpm add sharp`. If `sharp` is not installed, requesting a transform will throw an error.
|
|
513
|
-
|
|
514
|
-
### Transform Query Parameters
|
|
515
|
-
|
|
516
|
-
| Parameter | Type | Range | Default | Description |
|
|
517
|
-
|-----------|------|-------|---------|-------------|
|
|
518
|
-
| `width` | `number` | 1–4096 | — | Target width in pixels |
|
|
519
|
-
| `height` | `number` | 1–4096 | — | Target height in pixels |
|
|
520
|
-
| `quality` | `number` | 1–100 | `80` | Output quality |
|
|
521
|
-
| `format` | `string` | `webp`, `avif`, `jpeg`, `png` | `webp` | Output format |
|
|
522
|
-
| `fit` | `string` | `cover`, `contain`, `fill`, `inside`, `outside` | `cover` | Resize fit mode |
|
|
523
|
-
|
|
524
|
-
All dimensions are capped at **4096 px** to prevent abuse. `withoutEnlargement` is enabled — images are never upscaled.
|
|
525
|
-
|
|
526
|
-
### Transform Examples
|
|
527
|
-
|
|
528
|
-
```
|
|
529
|
-
# Resize to 300px wide, auto height, WebP format
|
|
530
|
-
GET /api/storage/file/default/images/photo.jpg?width=300
|
|
531
|
-
|
|
532
|
-
# Resize to 800x600, JPEG at 90% quality
|
|
533
|
-
GET /api/storage/file/default/images/photo.jpg?width=800&height=600&format=jpeg&quality=90
|
|
534
|
-
|
|
535
|
-
# Convert to AVIF with contain fit
|
|
536
|
-
GET /api/storage/file/default/images/photo.jpg?format=avif&fit=contain&width=500&height=500
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
### Transformable Image Types
|
|
540
|
-
|
|
541
|
-
Only raster images are transformable. SVG and GIF are **excluded**:
|
|
542
|
-
|
|
543
|
-
```
|
|
544
|
-
✅ image/jpeg, image/png, image/webp, image/bmp, image/tiff
|
|
545
|
-
❌ image/svg+xml, image/gif
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
### Transform Cache
|
|
549
|
-
|
|
550
|
-
Transformed images are cached in an **LRU in-memory cache** to avoid redundant processing:
|
|
551
|
-
|
|
552
|
-
| Setting | Value |
|
|
553
|
-
|---------|-------|
|
|
554
|
-
| Max entries | 500 |
|
|
555
|
-
| TTL | 1 hour (3,600,000 ms) |
|
|
556
|
-
| Cache key | `{filePath}::{JSON.stringify(options)}` |
|
|
557
|
-
| Eviction policy | LRU (oldest entry evicted when at capacity) |
|
|
558
|
-
|
|
559
|
-
Transformed responses include `Cache-Control: public, max-age=31536000, immutable`.
|
|
560
|
-
|
|
561
|
-
## Client SDK Methods
|
|
562
|
-
|
|
563
|
-
The `@rebasepro/client` package provides a `StorageSource` interface via `createStorage(transport)`. These methods are available on `client.storage` (or through the React/Studio hooks).
|
|
564
|
-
|
|
565
|
-
### putObject
|
|
566
|
-
|
|
567
|
-
Upload a file.
|
|
568
|
-
|
|
569
|
-
```typescript
|
|
570
|
-
const result = await client.storage.putObject({
|
|
571
|
-
file: myFile, // File object
|
|
572
|
-
key: "products/images/photo.jpg", // Storage key/path
|
|
573
|
-
bucket: "default", // Optional bucket
|
|
574
|
-
metadata: { category: "product" }, // Optional custom metadata
|
|
575
|
-
});
|
|
576
|
-
// result: { key: "products/images/photo.jpg", bucket: "default", storageUrl: "local://..." }
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
The SDK sends a `multipart/form-data` request to `POST /api/storage/upload`. Custom metadata keys are prefixed with `metadata_` in the form data.
|
|
580
|
-
|
|
581
|
-
### getSignedUrl
|
|
582
|
-
|
|
583
|
-
Get a download URL and metadata for a file. Results are cached in-memory on the client.
|
|
584
|
-
|
|
585
|
-
```typescript
|
|
586
|
-
const config = await client.storage.getSignedUrl(
|
|
587
|
-
"products/images/photo.jpg", // key or storageUrl (local:// or s3://)
|
|
588
|
-
"default" // optional bucket
|
|
589
|
-
);
|
|
590
|
-
|
|
591
|
-
if (config.fileNotFound) {
|
|
592
|
-
console.log("File does not exist");
|
|
593
|
-
} else {
|
|
594
|
-
console.log(config.url); // Full URL with auth token appended
|
|
595
|
-
console.log(config.metadata); // { bucket, fullPath, name, size, contentType, customMetadata }
|
|
596
|
-
}
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
The URL is constructed as `{baseUrl}/api/storage/file/{path}?token={accessToken}`.
|
|
600
|
-
|
|
601
|
-
Protocol prefixes (`local://`, `s3://`) are automatically stripped from the key.
|
|
602
|
-
|
|
603
|
-
### getObject
|
|
604
|
-
|
|
605
|
-
Download a file as a `File` object.
|
|
606
|
-
|
|
607
|
-
```typescript
|
|
608
|
-
const file = await client.storage.getObject(
|
|
609
|
-
"products/images/photo.jpg", // key or storageUrl
|
|
610
|
-
"default" // optional bucket
|
|
611
|
-
);
|
|
612
|
-
|
|
613
|
-
if (file) {
|
|
614
|
-
console.log(file.name); // "photo.jpg"
|
|
615
|
-
console.log(file.type); // "image/jpeg"
|
|
616
|
-
const blob = new Blob([file]);
|
|
617
|
-
// use blob for display, further processing, etc.
|
|
618
|
-
}
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
> **IMPORTANT FOR AGENTS:** `getObject` uses raw `fetch` (not the JSON transport) because the response is a binary blob, not JSON.
|
|
622
|
-
|
|
623
|
-
### deleteObject
|
|
624
|
-
|
|
625
|
-
Delete a file. Silently ignores 404 errors (file already deleted).
|
|
626
|
-
|
|
627
|
-
```typescript
|
|
628
|
-
await client.storage.deleteObject(
|
|
629
|
-
"products/images/photo.jpg", // key or storageUrl
|
|
630
|
-
"default" // optional bucket
|
|
631
|
-
);
|
|
632
|
-
```
|
|
633
|
-
|
|
634
|
-
### listObjects
|
|
635
|
-
|
|
636
|
-
List files and folders in a prefix with pagination.
|
|
637
|
-
|
|
638
|
-
```typescript
|
|
639
|
-
const result = await client.storage.listObjects("products/images", {
|
|
640
|
-
bucket: "default",
|
|
641
|
-
maxResults: 50,
|
|
642
|
-
pageToken: undefined, // from previous result.nextPageToken
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
console.log(result.items); // files: StorageReference[]
|
|
646
|
-
console.log(result.prefixes); // folders: StorageReference[]
|
|
647
|
-
console.log(result.nextPageToken); // for next page, or undefined
|
|
648
|
-
```
|
|
649
|
-
|
|
650
|
-
## Collection File Upload Properties
|
|
651
|
-
|
|
652
|
-
Define file upload fields in your collections using the `storage` option on string properties:
|
|
653
|
-
|
|
654
|
-
```typescript
|
|
655
|
-
import { PostgresCollection } from "@rebasepro/types";
|
|
656
|
-
|
|
657
|
-
const productsCollection: PostgresCollection = {
|
|
658
|
-
name: "Products",
|
|
659
|
-
table: "products",
|
|
660
|
-
properties: {
|
|
661
|
-
image: {
|
|
662
|
-
name: "Product Image",
|
|
663
|
-
type: "string",
|
|
664
|
-
storage: {
|
|
665
|
-
storagePath: "products/images",
|
|
666
|
-
acceptedFiles: ["image/*"],
|
|
667
|
-
maxSize: 5 * 1024 * 1024, // 5MB
|
|
668
|
-
}
|
|
669
|
-
},
|
|
670
|
-
documents: {
|
|
671
|
-
name: "Documents",
|
|
672
|
-
type: "array",
|
|
673
|
-
of: {
|
|
674
|
-
type: "string",
|
|
675
|
-
storage: {
|
|
676
|
-
storagePath: "products/documents",
|
|
677
|
-
acceptedFiles: ["application/pdf", "application/msword"],
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
};
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
## Storage Browser (Studio)
|
|
686
|
-
|
|
687
|
-
The `@rebasepro/studio` package includes a built-in `StorageView` component:
|
|
688
|
-
- Browse uploaded files and folders with a tree sidebar
|
|
689
|
-
- Drag-and-drop file uploads
|
|
690
|
-
- Image, video, and audio previews with metadata
|
|
691
|
-
- File search and filtering
|
|
692
|
-
- Grid and list view modes
|
|
693
|
-
|
|
694
|
-
## Validation and Error Handling
|
|
695
|
-
|
|
696
|
-
### File Size Validation
|
|
697
|
-
|
|
698
|
-
File size is validated at two levels:
|
|
699
|
-
|
|
700
|
-
1. **Hono body limit middleware** — returns `413` with `PAYLOAD_TOO_LARGE` before the file reaches the controller. The limit is derived from `maxFileSize` in the storage config (default 50 MB).
|
|
701
|
-
2. **Controller-level validation** — the controller's `validateFile()` checks `file.size` against `maxFileSize` and throws an `Error` if exceeded.
|
|
702
|
-
|
|
703
|
-
### MIME Type Validation
|
|
704
|
-
|
|
705
|
-
When `allowedMimeTypes` is set in the storage config, uploads with disallowed MIME types are rejected with an error:
|
|
706
|
-
|
|
707
|
-
```
|
|
708
|
-
File type application/zip is not allowed. Allowed types: image/jpeg, image/png
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
### Common MIME Type Constants
|
|
712
|
-
|
|
713
|
-
The storage module exports two convenience arrays:
|
|
714
|
-
|
|
715
|
-
```typescript
|
|
716
|
-
import { IMAGE_MIME_TYPES, DOCUMENT_MIME_TYPES } from "@rebasepro/server-core";
|
|
717
|
-
|
|
718
|
-
// IMAGE_MIME_TYPES:
|
|
719
|
-
// ["image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml", "image/bmp", "image/tiff"]
|
|
720
|
-
|
|
721
|
-
// DOCUMENT_MIME_TYPES:
|
|
722
|
-
// ["application/pdf", "application/msword",
|
|
723
|
-
// "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
724
|
-
// "application/vnd.ms-excel",
|
|
725
|
-
// "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
726
|
-
// "application/vnd.ms-powerpoint",
|
|
727
|
-
// "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
728
|
-
// "text/plain", "text/csv"]
|
|
729
|
-
```
|
|
730
|
-
|
|
731
|
-
### TUS Error Codes
|
|
732
|
-
|
|
733
|
-
| HTTP Status | Condition |
|
|
734
|
-
|-------------|-----------|
|
|
735
|
-
| 400 | Missing/invalid `Upload-Length` header, missing `Upload-Offset`, upload already completed |
|
|
736
|
-
| 404 | Upload ID not found |
|
|
737
|
-
| 409 | `Upload-Offset` mismatch (client offset ≠ server offset) |
|
|
738
|
-
| 413 | `Upload-Length` exceeds 5 GB max, or chunk exceeds declared length |
|
|
739
|
-
| 415 | `Content-Type` is not `application/offset+octet-stream` |
|
|
740
|
-
|
|
741
|
-
## Metadata Sidecar Files (Local Storage)
|
|
742
|
-
|
|
743
|
-
For local storage, every uploaded file has a companion `.metadata.json` file saved alongside it. This sidecar file stores:
|
|
744
|
-
|
|
745
|
-
```json
|
|
746
|
-
{
|
|
747
|
-
"contentType": "image/jpeg",
|
|
748
|
-
"size": 204800,
|
|
749
|
-
"uploadedAt": "2026-01-15T10:30:00.000Z",
|
|
750
|
-
"category": "product"
|
|
751
|
-
}
|
|
752
|
-
```
|
|
753
|
-
|
|
754
|
-
- `contentType` and `size` are always present
|
|
755
|
-
- Custom metadata from the upload request is merged in
|
|
756
|
-
- The content type from this file is used when serving files via `GET /file/*`
|
|
757
|
-
- When a file is deleted, its `.metadata.json` is also deleted
|
|
758
|
-
- Metadata files are skipped when listing directory contents (filtered out by name pattern)
|
|
759
|
-
|
|
760
|
-
## References
|
|
761
|
-
|
|
762
|
-
- **Documentation:** [rebase.pro/docs](https://rebase.pro/docs)
|
|
763
|
-
- **GitHub:** [github.com/rebasepro/rebase](https://github.com/rebasepro/rebase)
|
|
764
|
-
- **TUS Protocol:** [tus.io/protocols/resumable-upload](https://tus.io/protocols/resumable-upload)
|
|
765
|
-
- **Sharp (image transforms):** [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com)
|