@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.
Files changed (86) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/MIGRATION.md +220 -0
  3. package/README.md +311 -557
  4. package/dist/index.d.ts +1 -4
  5. package/dist/index.js +1 -4
  6. package/dist/index.js.map +1 -1
  7. package/dist/lib/constants.d.ts +3 -1
  8. package/dist/lib/constants.js +4 -2
  9. package/dist/lib/constants.js.map +1 -1
  10. package/dist/lib/driver-registry.d.ts +34 -0
  11. package/dist/lib/driver-registry.js +118 -0
  12. package/dist/lib/driver-registry.js.map +1 -0
  13. package/dist/lib/drivers/azure.driver.d.ts +21 -0
  14. package/dist/lib/drivers/azure.driver.js +91 -0
  15. package/dist/lib/drivers/azure.driver.js.map +1 -0
  16. package/dist/lib/drivers/driver.interface.d.ts +40 -0
  17. package/dist/lib/drivers/driver.interface.js +3 -0
  18. package/dist/lib/drivers/driver.interface.js.map +1 -0
  19. package/dist/lib/drivers/driver.util.d.ts +2 -0
  20. package/dist/lib/drivers/driver.util.js +15 -0
  21. package/dist/lib/drivers/driver.util.js.map +1 -0
  22. package/dist/lib/drivers/index.d.ts +7 -0
  23. package/dist/lib/drivers/index.js +39 -0
  24. package/dist/lib/drivers/index.js.map +1 -0
  25. package/dist/lib/drivers/local.driver.d.ts +15 -0
  26. package/dist/lib/drivers/local.driver.js +110 -0
  27. package/dist/lib/drivers/local.driver.js.map +1 -0
  28. package/dist/lib/drivers/s3.driver.d.ts +22 -0
  29. package/dist/lib/drivers/s3.driver.js +103 -0
  30. package/dist/lib/drivers/s3.driver.js.map +1 -0
  31. package/dist/lib/file-storage.service.d.ts +16 -5
  32. package/dist/lib/file-storage.service.js +60 -22
  33. package/dist/lib/file-storage.service.js.map +1 -1
  34. package/dist/lib/index.d.ts +9 -2
  35. package/dist/lib/index.js +15 -2
  36. package/dist/lib/index.js.map +1 -1
  37. package/dist/lib/interceptor/file-storage.interceptor.d.ts +7 -10
  38. package/dist/lib/interceptor/file-storage.interceptor.js +117 -112
  39. package/dist/lib/interceptor/file-storage.interceptor.js.map +1 -1
  40. package/dist/lib/multer/driver-multer-engine.d.ts +18 -0
  41. package/dist/lib/multer/driver-multer-engine.js +91 -0
  42. package/dist/lib/multer/driver-multer-engine.js.map +1 -0
  43. package/dist/lib/nest-file-storage.module.d.ts +3 -3
  44. package/dist/lib/nest-file-storage.module.js +81 -44
  45. package/dist/lib/nest-file-storage.module.js.map +1 -1
  46. package/dist/lib/registry-holder.d.ts +6 -0
  47. package/dist/lib/registry-holder.js +26 -0
  48. package/dist/lib/registry-holder.js.map +1 -0
  49. package/dist/lib/tenant/tenant-from.d.ts +14 -0
  50. package/dist/lib/tenant/tenant-from.js +71 -0
  51. package/dist/lib/tenant/tenant-from.js.map +1 -0
  52. package/dist/lib/tenant/tenant.types.d.ts +20 -0
  53. package/dist/lib/tenant/tenant.types.js +3 -0
  54. package/dist/lib/tenant/tenant.types.js.map +1 -0
  55. package/dist/lib/types.d.ts +45 -35
  56. package/dist/lib/types.js.map +1 -1
  57. package/dist/lib/validation.d.ts +22 -0
  58. package/dist/lib/validation.js +98 -0
  59. package/dist/lib/validation.js.map +1 -0
  60. package/dist/tsconfig.build.tsbuildinfo +1 -1
  61. package/examples/1-basic-local-storage.example.ts +11 -7
  62. package/examples/10-testing.example.ts +60 -196
  63. package/examples/11-custom-driver.example.ts +82 -0
  64. package/examples/12-multi-tenant.example.ts +93 -0
  65. package/examples/2-s3-storage.example.ts +18 -16
  66. package/examples/3-azure-storage.example.ts +14 -12
  67. package/examples/4-upload-controller.example.ts +20 -55
  68. package/examples/5-custom-configuration.example.ts +37 -57
  69. package/examples/6-file-service.example.ts +34 -91
  70. package/examples/7-user-avatar.example.ts +37 -92
  71. package/examples/8-document-management.example.ts +45 -196
  72. package/examples/9-dynamic-storage.example.ts +29 -147
  73. package/examples/README.md +25 -107
  74. package/package.json +17 -4
  75. package/dist/lib/storage/azure.storage.d.ts +0 -18
  76. package/dist/lib/storage/azure.storage.js +0 -210
  77. package/dist/lib/storage/azure.storage.js.map +0 -1
  78. package/dist/lib/storage/local.storage.d.ts +0 -20
  79. package/dist/lib/storage/local.storage.js +0 -212
  80. package/dist/lib/storage/local.storage.js.map +0 -1
  81. package/dist/lib/storage/s3.storage.d.ts +0 -19
  82. package/dist/lib/storage/s3.storage.js +0 -241
  83. package/dist/lib/storage/s3.storage.js.map +0 -1
  84. package/dist/lib/storage.factory.d.ts +0 -8
  85. package/dist/lib/storage.factory.js +0 -46
  86. 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()`.