@ackplus/nest-file-storage 1.1.23 → 2.0.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/CHANGELOG.md +72 -0
- package/MIGRATION.md +220 -0
- package/README.md +311 -557
- package/dist/index.d.ts +1 -4
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/constants.d.ts +3 -1
- package/dist/lib/constants.js +4 -2
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/driver-registry.d.ts +34 -0
- package/dist/lib/driver-registry.js +118 -0
- package/dist/lib/driver-registry.js.map +1 -0
- package/dist/lib/drivers/azure.driver.d.ts +21 -0
- package/dist/lib/drivers/azure.driver.js +91 -0
- package/dist/lib/drivers/azure.driver.js.map +1 -0
- package/dist/lib/drivers/driver.interface.d.ts +40 -0
- package/dist/lib/drivers/driver.interface.js +3 -0
- package/dist/lib/drivers/driver.interface.js.map +1 -0
- package/dist/lib/drivers/driver.util.d.ts +2 -0
- package/dist/lib/drivers/driver.util.js +15 -0
- package/dist/lib/drivers/driver.util.js.map +1 -0
- package/dist/lib/drivers/index.d.ts +7 -0
- package/dist/lib/drivers/index.js +39 -0
- package/dist/lib/drivers/index.js.map +1 -0
- package/dist/lib/drivers/local.driver.d.ts +15 -0
- package/dist/lib/drivers/local.driver.js +110 -0
- package/dist/lib/drivers/local.driver.js.map +1 -0
- package/dist/lib/drivers/s3.driver.d.ts +22 -0
- package/dist/lib/drivers/s3.driver.js +103 -0
- package/dist/lib/drivers/s3.driver.js.map +1 -0
- package/dist/lib/file-storage.service.d.ts +16 -5
- package/dist/lib/file-storage.service.js +60 -22
- package/dist/lib/file-storage.service.js.map +1 -1
- package/dist/lib/index.d.ts +9 -2
- package/dist/lib/index.js +15 -2
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/interceptor/file-storage.interceptor.d.ts +7 -10
- package/dist/lib/interceptor/file-storage.interceptor.js +117 -112
- package/dist/lib/interceptor/file-storage.interceptor.js.map +1 -1
- package/dist/lib/multer/driver-multer-engine.d.ts +18 -0
- package/dist/lib/multer/driver-multer-engine.js +91 -0
- package/dist/lib/multer/driver-multer-engine.js.map +1 -0
- package/dist/lib/nest-file-storage.module.d.ts +3 -3
- package/dist/lib/nest-file-storage.module.js +81 -44
- package/dist/lib/nest-file-storage.module.js.map +1 -1
- package/dist/lib/registry-holder.d.ts +6 -0
- package/dist/lib/registry-holder.js +26 -0
- package/dist/lib/registry-holder.js.map +1 -0
- package/dist/lib/tenant/tenant-from.d.ts +14 -0
- package/dist/lib/tenant/tenant-from.js +71 -0
- package/dist/lib/tenant/tenant-from.js.map +1 -0
- package/dist/lib/tenant/tenant.types.d.ts +20 -0
- package/dist/lib/tenant/tenant.types.js +3 -0
- package/dist/lib/tenant/tenant.types.js.map +1 -0
- package/dist/lib/types.d.ts +45 -35
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/validation.d.ts +22 -0
- package/dist/lib/validation.js +98 -0
- package/dist/lib/validation.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/examples/1-basic-local-storage.example.ts +11 -7
- package/examples/10-testing.example.ts +60 -196
- package/examples/11-custom-driver.example.ts +82 -0
- package/examples/12-multi-tenant.example.ts +93 -0
- package/examples/2-s3-storage.example.ts +18 -16
- package/examples/3-azure-storage.example.ts +14 -12
- package/examples/4-upload-controller.example.ts +20 -55
- package/examples/5-custom-configuration.example.ts +37 -57
- package/examples/6-file-service.example.ts +34 -91
- package/examples/7-user-avatar.example.ts +37 -92
- package/examples/8-document-management.example.ts +45 -196
- package/examples/9-dynamic-storage.example.ts +29 -147
- package/examples/README.md +25 -107
- package/package.json +17 -4
- package/dist/lib/storage/azure.storage.d.ts +0 -18
- package/dist/lib/storage/azure.storage.js +0 -210
- package/dist/lib/storage/azure.storage.js.map +0 -1
- package/dist/lib/storage/local.storage.d.ts +0 -20
- package/dist/lib/storage/local.storage.js +0 -212
- package/dist/lib/storage/local.storage.js.map +0 -1
- package/dist/lib/storage/s3.storage.d.ts +0 -19
- package/dist/lib/storage/s3.storage.js +0 -241
- package/dist/lib/storage/s3.storage.js.map +0 -1
- package/dist/lib/storage.factory.d.ts +0 -8
- package/dist/lib/storage.factory.js +0 -46
- package/dist/lib/storage.factory.js.map +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@ackplus/nest-file-storage` are documented here. This project adheres to
|
|
4
|
+
[Semantic Versioning](https://semver.org/) and the [Keep a Changelog](https://keepachangelog.com/) format.
|
|
5
|
+
|
|
6
|
+
## [2.0.0] - 2026-06-13
|
|
7
|
+
|
|
8
|
+
A redesign around a **driver registry**. Custom storage providers now work everywhere, storage can be
|
|
9
|
+
chosen per request (including multi-tenant), validation is declarative, and the service is injectable.
|
|
10
|
+
See [MIGRATION.md](./MIGRATION.md) for the full upgrade path.
|
|
11
|
+
|
|
12
|
+
> Most v1 apps keep booting: the old `forRoot({ storage, *Config })` config is auto-translated and the
|
|
13
|
+
> static `FileStorageService.getStorage()` still works, both with deprecation warnings. The shims are removed in v3.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **Driver registry.** Register named drivers with `localDriver()`, `s3Driver()`, `azureDriver()`, or a custom
|
|
18
|
+
one with `defineDriver(MyDriver, opts)` — all in a single `drivers` map with a `default`.
|
|
19
|
+
- **First-class custom storage.** A custom `StorageDriver` works in both the interceptor and the service
|
|
20
|
+
(the v1 `storageFactory` only worked in the service and crashed in the interceptor).
|
|
21
|
+
- **Multi-tenant / per-request storage.** A `tenant` block resolves the tenant per request and routes to a
|
|
22
|
+
shared driver + key prefix or a dedicated driver, with a per-tenant driver cache (TTL/LRU) and invalidation.
|
|
23
|
+
- **Composable tenant resolvers** — `tenantFrom.jwt/header/subdomain/param/query/first`.
|
|
24
|
+
- **Declarative validation** — `validation: { maxSize, allowedMimeTypes, allowedExtensions, maxFiles, fileFilter }`
|
|
25
|
+
at module and route level, with typed exceptions `FileTooLargeException`, `InvalidFileTypeException`,
|
|
26
|
+
`TooManyFilesException`.
|
|
27
|
+
- **Injectable `FileStorageService`** with `getDriver()`, `getTenantDriver()`, and convenience delegations
|
|
28
|
+
(`putFile`/`getFile`/`deleteFile`/`copyFile`/`getUrl`/`getSignedUrl`). The module is `global`.
|
|
29
|
+
- **`prefix`** is now implemented as a real per-route and per-tenant key prefix.
|
|
30
|
+
- **Driver instance caching** — drivers are built once and reused (no per-request client churn).
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **Module options** are now `{ default, drivers, validation?, tenant? }` instead of the
|
|
35
|
+
`{ storage, localConfig | s3Config | azureConfig }` discriminated union.
|
|
36
|
+
- **Interceptor options**: `storageType` + `storageOptions` → `driver` (a registered name or `(req) => name`).
|
|
37
|
+
- **`UploadedFile`** is the single canonical result shape across all providers; `fileDist` is always relative.
|
|
38
|
+
- **`Storage` interface → `StorageDriver`** (drivers no longer implement Multer's `StorageEngine`; a shared
|
|
39
|
+
`DriverMulterEngine` adapts any driver). A deprecated `Storage` type alias remains.
|
|
40
|
+
- **DI tokens** are now `Symbol`s (`FILE_STORAGE_OPTIONS`, `FILE_STORAGE_REGISTRY`).
|
|
41
|
+
- `noImplicitAny` is enabled; public types are tightened.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- **S3 signed URLs** now honor `expiresIn` (it was silently dropped into `GetObjectCommand` and ignored).
|
|
46
|
+
- **Per-request SDK-client churn and an unbounded cache `Map`** — drivers are cached once now.
|
|
47
|
+
- **S3 `putFile`** no longer makes an extra `HeadObject` round-trip per upload (uses the buffer length).
|
|
48
|
+
- **S3 client** is constructed with only `{ region, endpoint, credentials, clientOptions }` (v1 spread the whole
|
|
49
|
+
options object, including callback functions, into the client).
|
|
50
|
+
- **Azure `deleteFile`** rethrows on failure (v1 swallowed the error and reported success).
|
|
51
|
+
- **Local multi-file rollback** (`_removeFile`) reads the stored key instead of a non-existent `file.path`.
|
|
52
|
+
|
|
53
|
+
### Deprecated
|
|
54
|
+
|
|
55
|
+
- `FileStorageService.getStorage()` / `setOptions()` / `getOptions()` (static) — inject the service instead.
|
|
56
|
+
- The v1 `forRoot({ storage, *Config })` configuration shape — use `{ default, drivers }`.
|
|
57
|
+
- `FileStorageEnum` — use driver names (strings).
|
|
58
|
+
- `Storage`, `LocalStorageOptions`, `S3StorageOptions`, `AzureStorageOptions` type aliases — use
|
|
59
|
+
`StorageDriver`, `LocalDriverOptions`, `S3DriverOptions`, `AzureDriverOptions`.
|
|
60
|
+
|
|
61
|
+
### Removed
|
|
62
|
+
|
|
63
|
+
- `storageFactory` / `FileStorageClassOptions` — replaced by `drivers` + `defineDriver`.
|
|
64
|
+
- `transformUploadedFileObject` — use the interceptor's `mapToRequestBody`.
|
|
65
|
+
- The interceptor `multerOptions` callback — use `validation`.
|
|
66
|
+
- `UploadedFile.fieldname` (the lowercase duplicate) — use `fieldName`.
|
|
67
|
+
- The implicit `AZURE_CDN_DOMAIN_NAME` env var — use `azureDriver({ cdnUrl })`.
|
|
68
|
+
|
|
69
|
+
## [1.1.23] and earlier
|
|
70
|
+
|
|
71
|
+
See the Git history. v1 supported Local, S3, and Azure storage with a single active provider, the
|
|
72
|
+
`FileStorageInterceptor`, and a static `FileStorageService`.
|
package/MIGRATION.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Migration Guide: v1 → v2
|
|
2
|
+
|
|
3
|
+
v2 is a redesign around a **driver registry**. The big wins: custom storage providers that work everywhere, per-request / multi-tenant storage, declarative validation, and an injectable service. This guide maps every v1 concept to its v2 replacement.
|
|
4
|
+
|
|
5
|
+
> **Good news:** your v1 module config still boots. `NestFileStorageModule.forRoot({ storage, localConfig })` is auto-translated to the v2 shape with a one-time deprecation warning, and the static `FileStorageService.getStorage()` still works. You can upgrade incrementally. The compatibility shims are removed in **v3**.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## TL;DR
|
|
10
|
+
|
|
11
|
+
| Area | v1 | v2 |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| Module config | `{ storage: FileStorageEnum.LOCAL, localConfig }` | `{ default: 'local', drivers: { local: localDriver(...) } }` |
|
|
14
|
+
| Custom storage | `storageFactory` (interceptor-broken) | `drivers: { my: defineDriver(MyDriver, opts) }` |
|
|
15
|
+
| Per-request / tenant | not possible | `tenant: { resolve, driver, cache }` |
|
|
16
|
+
| Interceptor backend | `{ storageType, storageOptions }` | `{ driver: 's3' }` or `{ driver: (req) => '...' }` |
|
|
17
|
+
| Validation | thrown inside `fileName()` / `multerOptions` | `{ validation: { maxSize, allowedMimeTypes, ... } }` |
|
|
18
|
+
| Service access | `FileStorageService.getStorage()` (static) | inject `FileStorageService` → `getDriver()` |
|
|
19
|
+
| Result shaping | `transformUploadedFileObject` | `mapToRequestBody` |
|
|
20
|
+
| Azure CDN | `AZURE_CDN_DOMAIN_NAME` env var | `azureDriver({ cdnUrl })` |
|
|
21
|
+
| `prefix` | present but ignored | implemented (per-route + per-tenant) |
|
|
22
|
+
| Removed | — | `transformUploadedFileObject`, `multerOptions`, `fieldname` |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 1. Module configuration
|
|
27
|
+
|
|
28
|
+
**v1**
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
NestFileStorageModule.forRoot({
|
|
32
|
+
storage: FileStorageEnum.LOCAL,
|
|
33
|
+
localConfig: { rootPath: './uploads', baseUrl: 'http://localhost:3000/uploads' },
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**v2** — register named drivers and pick a default:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { NestFileStorageModule, localDriver } from '@ackplus/nest-file-storage';
|
|
41
|
+
|
|
42
|
+
NestFileStorageModule.forRoot({
|
|
43
|
+
default: 'local',
|
|
44
|
+
drivers: {
|
|
45
|
+
local: localDriver({ rootPath: './uploads', baseUrl: 'http://localhost:3000/uploads' }),
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
S3 and Azure are the same idea with `s3Driver({...})` / `azureDriver({...})`. You can register **several at once** and choose per route:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
NestFileStorageModule.forRoot({
|
|
54
|
+
default: 'local',
|
|
55
|
+
drivers: {
|
|
56
|
+
local: localDriver({ rootPath: './uploads', baseUrl: 'http://localhost:3000/uploads' }),
|
|
57
|
+
s3: s3Driver({ accessKeyId, secretAccessKey, region, bucket }),
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
> The v1 shape still works (with a deprecation warning) — migrate when convenient.
|
|
63
|
+
|
|
64
|
+
## 2. Custom storage providers
|
|
65
|
+
|
|
66
|
+
This is the headline change. In v1, `storageFactory` only worked through `FileStorageService` and **crashed with the interceptor**. In v2, a custom driver is just another entry in `drivers` and works **everywhere**.
|
|
67
|
+
|
|
68
|
+
**v1 (remove this)**
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
NestFileStorageModule.forRoot({
|
|
72
|
+
storageFactory: () => MyStorageClass,
|
|
73
|
+
options: { /* ... */ },
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**v2**
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { defineDriver, StorageDriver } from '@ackplus/nest-file-storage';
|
|
81
|
+
|
|
82
|
+
class GcsDriver implements StorageDriver {
|
|
83
|
+
constructor(private opts: { bucket: string }) {}
|
|
84
|
+
async putFile(content, key) { /* ... */ }
|
|
85
|
+
async getFile(key) { /* ... */ }
|
|
86
|
+
async deleteFile(key) { /* ... */ }
|
|
87
|
+
async copyFile(src, dest) { /* ... */ }
|
|
88
|
+
getUrl(key) { return `https://storage.googleapis.com/${this.opts.bucket}/${key}`; }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
NestFileStorageModule.forRoot({
|
|
92
|
+
default: 'gcs',
|
|
93
|
+
drivers: { gcs: defineDriver(GcsDriver, { bucket: 'my-bucket' }) },
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For async setup, pass a plain factory instead of `defineDriver`: `drivers: { gcs: () => new GcsDriver(...) }`.
|
|
98
|
+
|
|
99
|
+
## 3. Interceptor — choosing the backend
|
|
100
|
+
|
|
101
|
+
**v1**
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
FileStorageInterceptor('avatar', {
|
|
105
|
+
storageType: FileStorageEnum.S3,
|
|
106
|
+
storageOptions: { bucket: 'uploads', region: 'us-east-1', /* full creds */ },
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**v2** — credentials live in the module's `drivers`; the route just names one:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
FileStorageInterceptor('avatar', { driver: 's3' });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
You can also choose dynamically: `{ driver: (req) => req.user.plan === 'pro' ? 's3' : 'local' }`.
|
|
117
|
+
|
|
118
|
+
## 4. Validation
|
|
119
|
+
|
|
120
|
+
Stop validating inside `fileName()`. v2 has a declarative `validation` block (module-level default + per-route override) that throws typed `400`s.
|
|
121
|
+
|
|
122
|
+
**v1**
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
FileStorageInterceptor('image', {
|
|
126
|
+
fileName: (file) => {
|
|
127
|
+
if (!['image/png', 'image/jpeg'].includes(file.mimetype)) {
|
|
128
|
+
throw new BadRequestException('Invalid type');
|
|
129
|
+
}
|
|
130
|
+
return `${Date.now()}-${file.originalname}`;
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**v2**
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
FileStorageInterceptor('image', {
|
|
139
|
+
validation: {
|
|
140
|
+
allowedMimeTypes: ['image/png', 'image/jpeg'], // also supports 'image/*'
|
|
141
|
+
maxSize: 5 * 1024 * 1024,
|
|
142
|
+
allowedExtensions: ['.png', '.jpg'],
|
|
143
|
+
maxFiles: 10,
|
|
144
|
+
},
|
|
145
|
+
fileName: (file) => `${Date.now()}-${file.originalname}`, // naming only
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Rejections throw `InvalidFileTypeException`, `FileTooLargeException`, or `TooManyFilesException` (all extend `BadRequestException`).
|
|
150
|
+
|
|
151
|
+
> The v1 `multerOptions` callback is removed. `limits` → `validation.maxSize`/`maxFiles`; `fileFilter` → `validation.fileFilter` (escape hatch) or the declarative fields above.
|
|
152
|
+
|
|
153
|
+
## 5. Using the service
|
|
154
|
+
|
|
155
|
+
The service is now a normal injectable provider (the module is global). The static API still works but is deprecated.
|
|
156
|
+
|
|
157
|
+
**v1**
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
const storage = await FileStorageService.getStorage();
|
|
161
|
+
await storage.putFile(buffer, key);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**v2**
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
@Injectable()
|
|
168
|
+
class MyService {
|
|
169
|
+
constructor(private readonly fileStorage: FileStorageService) {}
|
|
170
|
+
|
|
171
|
+
async save(buffer: Buffer, key: string) {
|
|
172
|
+
const driver = await this.fileStorage.getDriver(); // default driver
|
|
173
|
+
return driver.putFile(buffer, key);
|
|
174
|
+
// or the shortcut: this.fileStorage.putFile(buffer, key)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`FileStorageService.getStorage(name?)` still resolves the active driver if you can't inject yet. `getStorage(FileStorageEnum.S3)` keeps working because the enum value (`'s3'`) is the driver name.
|
|
180
|
+
|
|
181
|
+
## 6. Shaping the upload result
|
|
182
|
+
|
|
183
|
+
`transformUploadedFileObject` (a driver-level hook) is removed. Use the interceptor's `mapToRequestBody`, which controls what lands in `request.body[field]`.
|
|
184
|
+
|
|
185
|
+
**v1**
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
localConfig: {
|
|
189
|
+
transformUploadedFileObject: (file) => ({ key: file.key, url: file.url }),
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**v2**
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
FileStorageInterceptor('file', {
|
|
197
|
+
mapToRequestBody: (file) => ({ key: file.key, url: file.url }),
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## 7. Azure CDN
|
|
202
|
+
|
|
203
|
+
**v1** read `process.env.AZURE_CDN_DOMAIN_NAME` implicitly. **v2** is an explicit option:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
azureDriver({ account, accountKey, container, cdnUrl: 'https://cdn.example.com' });
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## 8. Other breaking changes
|
|
210
|
+
|
|
211
|
+
- **`UploadedFile.fieldname`** (lowercase duplicate) is removed — use `fieldName`.
|
|
212
|
+
- **`prefix`** is now actually applied (it was ignored in v1). If you set `prefix` expecting it to do nothing, remove it.
|
|
213
|
+
- **`Storage` interface** is renamed to **`StorageDriver`** (a deprecated `Storage` alias remains). A driver no longer implements Multer's `StorageEngine` — the shared engine adapts it.
|
|
214
|
+
- **S3 signed URLs** now honor `expiresIn` (it was silently ignored in v1).
|
|
215
|
+
|
|
216
|
+
## New in v2 (worth adopting)
|
|
217
|
+
|
|
218
|
+
- **Multi-tenant storage** — route uploads to a tenant's bucket/folder per request. See the README's *Multi-tenant* section.
|
|
219
|
+
- **`tenantFrom`** resolvers — `jwt`, `header`, `subdomain`, `param`, `query`, `first(...)`.
|
|
220
|
+
- **Driver registry APIs** — `registerDriver(name, factory)`, `invalidate(name)`, `invalidateTenant(id)` via `fileStorage.getRegistry()`.
|