@flusys/nestjs-storage 7.0.2 → 8.0.0-beta.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.
Files changed (202) hide show
  1. package/config/index.d.ts +3 -3
  2. package/controllers/file-manager.controller.d.ts +4 -4
  3. package/controllers/folder.controller.d.ts +2 -2
  4. package/controllers/index.d.ts +4 -4
  5. package/controllers/storage-config.controller.d.ts +2 -2
  6. package/controllers/upload.controller.d.ts +3 -3
  7. package/docs/index.d.ts +1 -1
  8. package/docs/storage-swagger.config.d.ts +2 -2
  9. package/dtos/index.d.ts +4 -4
  10. package/entities/file-manager-with-company.entity.d.ts +2 -2
  11. package/entities/file-manager.entity.d.ts +1 -1
  12. package/entities/folder-with-company.entity.d.ts +1 -1
  13. package/entities/index.d.ts +15 -15
  14. package/entities/storage-config-with-company.entity.d.ts +1 -1
  15. package/enums/index.d.ts +1 -1
  16. package/fesm/493.js +2768 -0
  17. package/fesm/537.js +1157 -0
  18. package/fesm/562.js +786 -0
  19. package/fesm/{providers/azure-provider.optional.js → chunks/190.js} +33 -30
  20. package/fesm/chunks/252.js +12 -0
  21. package/fesm/chunks/327.js +12 -0
  22. package/fesm/chunks/385.js +12 -0
  23. package/fesm/{providers/s3-provider.optional.js → chunks/591.js} +37 -34
  24. package/fesm/chunks/746.js +12 -0
  25. package/fesm/chunks/798.js +12 -0
  26. package/fesm/{providers/sftp-provider.optional.js → chunks/918.js} +35 -31
  27. package/fesm/config/index.js +119 -3
  28. package/fesm/controllers/index.js +16 -4
  29. package/fesm/docs/index.js +206 -1
  30. package/fesm/dtos/index.js +879 -4
  31. package/fesm/entities/index.js +463 -18
  32. package/fesm/enums/index.js +36 -1
  33. package/fesm/index.js +398 -12
  34. package/fesm/interfaces/index.js +19 -5
  35. package/fesm/modules/index.js +16 -1
  36. package/fesm/providers/index.js +364 -3
  37. package/fesm/runtime.js +252 -0
  38. package/fesm/services/index.js +16 -6
  39. package/fesm/utils/index.js +149 -1
  40. package/index.d.ts +11 -11
  41. package/interfaces/file-manager.interface.d.ts +1 -1
  42. package/interfaces/folder.interface.d.ts +1 -1
  43. package/interfaces/index.d.ts +5 -5
  44. package/interfaces/storage-config.interface.d.ts +1 -1
  45. package/interfaces/storage-module-options.interface.d.ts +3 -3
  46. package/interfaces/storage-provider.interface.d.ts +1 -1
  47. package/middlewares/file-serve.middleware.d.ts +4 -3
  48. package/middlewares/index.d.ts +1 -1
  49. package/modules/index.d.ts +1 -1
  50. package/modules/storage.module.d.ts +2 -2
  51. package/package.json +30 -41
  52. package/providers/azure-provider.optional.d.ts +2 -2
  53. package/providers/index.d.ts +3 -3
  54. package/providers/local-provider.d.ts +2 -2
  55. package/providers/s3-provider.optional.d.ts +2 -2
  56. package/providers/sftp-provider.optional.d.ts +2 -2
  57. package/providers/storage-factory.service.d.ts +3 -3
  58. package/providers/storage-provider.registry.d.ts +2 -2
  59. package/services/file-manager.service.d.ts +9 -8
  60. package/services/folder.service.d.ts +6 -6
  61. package/services/index.d.ts +6 -6
  62. package/services/storage-config.service.d.ts +3 -3
  63. package/services/storage-datasource.provider.d.ts +3 -3
  64. package/services/storage-provider-config.service.d.ts +7 -7
  65. package/services/upload.service.d.ts +6 -6
  66. package/utils/image-compressor.util.d.ts +1 -1
  67. package/utils/index.d.ts +1 -1
  68. package/cjs/config/event-actions.js +0 -34
  69. package/cjs/config/index.js +0 -20
  70. package/cjs/config/message-keys.js +0 -46
  71. package/cjs/config/storage.constants.js +0 -28
  72. package/cjs/controllers/file-manager.controller.js +0 -152
  73. package/cjs/controllers/file-manager.controller.spec.js +0 -135
  74. package/cjs/controllers/folder.controller.js +0 -102
  75. package/cjs/controllers/folder.controller.spec.js +0 -86
  76. package/cjs/controllers/index.js +0 -21
  77. package/cjs/controllers/storage-config.controller.js +0 -102
  78. package/cjs/controllers/storage-config.controller.spec.js +0 -85
  79. package/cjs/controllers/upload.controller.js +0 -234
  80. package/cjs/controllers/upload.controller.spec.js +0 -132
  81. package/cjs/docs/index.js +0 -11
  82. package/cjs/docs/storage-swagger.config.js +0 -188
  83. package/cjs/docs/storage-swagger.config.spec.js +0 -72
  84. package/cjs/dtos/file-manager.dto.js +0 -327
  85. package/cjs/dtos/folder.dto.js +0 -105
  86. package/cjs/dtos/index.js +0 -21
  87. package/cjs/dtos/storage-config.dto.js +0 -170
  88. package/cjs/dtos/upload.dto.js +0 -272
  89. package/cjs/entities/file-manager-with-company.entity.js +0 -61
  90. package/cjs/entities/file-manager.entity.js +0 -122
  91. package/cjs/entities/folder-with-company.entity.js +0 -55
  92. package/cjs/entities/folder.entity.js +0 -64
  93. package/cjs/entities/index.js +0 -63
  94. package/cjs/entities/index.spec.js +0 -18
  95. package/cjs/entities/storage-config-with-company.entity.js +0 -51
  96. package/cjs/entities/storage-config.entity.js +0 -96
  97. package/cjs/enums/file-location.enum.js +0 -17
  98. package/cjs/enums/index.js +0 -18
  99. package/cjs/index.js +0 -29
  100. package/cjs/interfaces/file-manager.interface.js +0 -4
  101. package/cjs/interfaces/folder.interface.js +0 -4
  102. package/cjs/interfaces/index.js +0 -22
  103. package/cjs/interfaces/storage-config.interface.js +0 -4
  104. package/cjs/interfaces/storage-module-options.interface.js +0 -4
  105. package/cjs/interfaces/storage-provider.interface.js +0 -4
  106. package/cjs/middlewares/file-serve.middleware.js +0 -241
  107. package/cjs/middlewares/file-serve.middleware.spec.js +0 -211
  108. package/cjs/middlewares/index.js +0 -18
  109. package/cjs/modules/index.js +0 -18
  110. package/cjs/modules/storage.module.js +0 -165
  111. package/cjs/modules/storage.module.spec.js +0 -184
  112. package/cjs/providers/azure-provider.optional.js +0 -155
  113. package/cjs/providers/azure-provider.optional.spec.js +0 -228
  114. package/cjs/providers/index.js +0 -26
  115. package/cjs/providers/local-provider.js +0 -230
  116. package/cjs/providers/local-provider.spec.js +0 -239
  117. package/cjs/providers/s3-provider.optional.js +0 -175
  118. package/cjs/providers/s3-provider.optional.spec.js +0 -243
  119. package/cjs/providers/sftp-provider.optional.js +0 -203
  120. package/cjs/providers/sftp-provider.optional.spec.js +0 -217
  121. package/cjs/providers/storage-factory.service.js +0 -175
  122. package/cjs/providers/storage-factory.service.spec.js +0 -253
  123. package/cjs/providers/storage-provider.registry.js +0 -41
  124. package/cjs/providers/storage-provider.registry.spec.js +0 -67
  125. package/cjs/services/file-manager.service.js +0 -387
  126. package/cjs/services/file-manager.service.spec.js +0 -580
  127. package/cjs/services/folder.service.js +0 -101
  128. package/cjs/services/folder.service.spec.js +0 -167
  129. package/cjs/services/index.js +0 -23
  130. package/cjs/services/storage-config.service.js +0 -129
  131. package/cjs/services/storage-config.service.spec.js +0 -257
  132. package/cjs/services/storage-datasource.provider.js +0 -137
  133. package/cjs/services/storage-datasource.provider.spec.js +0 -217
  134. package/cjs/services/storage-provider-config.service.js +0 -171
  135. package/cjs/services/storage-provider-config.service.spec.js +0 -265
  136. package/cjs/services/upload.service.js +0 -376
  137. package/cjs/services/upload.service.spec.js +0 -426
  138. package/cjs/utils/file-validator.util.js +0 -440
  139. package/cjs/utils/file-validator.util.spec.js +0 -259
  140. package/cjs/utils/image-compressor.util.js +0 -162
  141. package/cjs/utils/image-compressor.util.spec.js +0 -166
  142. package/cjs/utils/index.js +0 -18
  143. package/fesm/config/event-actions.js +0 -13
  144. package/fesm/config/message-keys.js +0 -22
  145. package/fesm/config/storage.constants.js +0 -7
  146. package/fesm/controllers/file-manager.controller.js +0 -142
  147. package/fesm/controllers/file-manager.controller.spec.js +0 -131
  148. package/fesm/controllers/folder.controller.js +0 -92
  149. package/fesm/controllers/folder.controller.spec.js +0 -82
  150. package/fesm/controllers/storage-config.controller.js +0 -92
  151. package/fesm/controllers/storage-config.controller.spec.js +0 -81
  152. package/fesm/controllers/upload.controller.js +0 -224
  153. package/fesm/controllers/upload.controller.spec.js +0 -128
  154. package/fesm/docs/storage-swagger.config.js +0 -181
  155. package/fesm/docs/storage-swagger.config.spec.js +0 -68
  156. package/fesm/dtos/file-manager.dto.js +0 -300
  157. package/fesm/dtos/folder.dto.js +0 -84
  158. package/fesm/dtos/storage-config.dto.js +0 -149
  159. package/fesm/dtos/upload.dto.js +0 -245
  160. package/fesm/entities/file-manager-with-company.entity.js +0 -51
  161. package/fesm/entities/file-manager.entity.js +0 -112
  162. package/fesm/entities/folder-with-company.entity.js +0 -45
  163. package/fesm/entities/folder.entity.js +0 -54
  164. package/fesm/entities/index.spec.js +0 -14
  165. package/fesm/entities/storage-config-with-company.entity.js +0 -41
  166. package/fesm/entities/storage-config.entity.js +0 -86
  167. package/fesm/enums/file-location.enum.js +0 -7
  168. package/fesm/interfaces/file-manager.interface.js +0 -1
  169. package/fesm/interfaces/folder.interface.js +0 -1
  170. package/fesm/interfaces/storage-config.interface.js +0 -1
  171. package/fesm/interfaces/storage-module-options.interface.js +0 -3
  172. package/fesm/interfaces/storage-provider.interface.js +0 -4
  173. package/fesm/middlewares/file-serve.middleware.js +0 -190
  174. package/fesm/middlewares/file-serve.middleware.spec.js +0 -207
  175. package/fesm/middlewares/index.js +0 -1
  176. package/fesm/modules/storage.module.js +0 -156
  177. package/fesm/modules/storage.module.spec.js +0 -180
  178. package/fesm/providers/azure-provider.optional.spec.js +0 -224
  179. package/fesm/providers/local-provider.js +0 -182
  180. package/fesm/providers/local-provider.spec.js +0 -194
  181. package/fesm/providers/s3-provider.optional.spec.js +0 -239
  182. package/fesm/providers/sftp-provider.optional.spec.js +0 -213
  183. package/fesm/providers/storage-factory.service.js +0 -124
  184. package/fesm/providers/storage-factory.service.spec.js +0 -249
  185. package/fesm/providers/storage-provider.registry.js +0 -34
  186. package/fesm/providers/storage-provider.registry.spec.js +0 -63
  187. package/fesm/services/file-manager.service.js +0 -336
  188. package/fesm/services/file-manager.service.spec.js +0 -576
  189. package/fesm/services/folder.service.js +0 -91
  190. package/fesm/services/folder.service.spec.js +0 -163
  191. package/fesm/services/storage-config.service.js +0 -119
  192. package/fesm/services/storage-config.service.spec.js +0 -253
  193. package/fesm/services/storage-datasource.provider.js +0 -86
  194. package/fesm/services/storage-datasource.provider.spec.js +0 -172
  195. package/fesm/services/storage-provider-config.service.js +0 -161
  196. package/fesm/services/storage-provider-config.service.spec.js +0 -261
  197. package/fesm/services/upload.service.js +0 -366
  198. package/fesm/services/upload.service.spec.js +0 -422
  199. package/fesm/utils/file-validator.util.js +0 -433
  200. package/fesm/utils/file-validator.util.spec.js +0 -255
  201. package/fesm/utils/image-compressor.util.js +0 -114
  202. package/fesm/utils/image-compressor.util.spec.js +0 -162
@@ -1,213 +0,0 @@
1
- function _define_property(obj, key, value) {
2
- if (key in obj) {
3
- Object.defineProperty(obj, key, {
4
- value: value,
5
- enumerable: true,
6
- configurable: true,
7
- writable: true
8
- });
9
- } else {
10
- obj[key] = value;
11
- }
12
- return obj;
13
- }
14
- const mockConnect = jest.fn();
15
- const mockMkdir = jest.fn();
16
- const mockPut = jest.fn();
17
- const mockDelete = jest.fn();
18
- const mockEnd = jest.fn();
19
- let MockSftpClient = class MockSftpClient {
20
- constructor(){
21
- _define_property(this, "connect", mockConnect);
22
- _define_property(this, "mkdir", mockMkdir);
23
- _define_property(this, "put", mockPut);
24
- _define_property(this, "delete", mockDelete);
25
- _define_property(this, "end", mockEnd);
26
- }
27
- };
28
- jest.mock('ssh2-sftp-client', ()=>({
29
- __esModule: true,
30
- default: MockSftpClient
31
- }), {
32
- virtual: true
33
- });
34
- jest.mock('../utils/image-compressor.util', ()=>({
35
- ImageCompressor: {
36
- compress: jest.fn()
37
- }
38
- }));
39
- import { SftpProvider } from './sftp-provider.optional';
40
- import { ImageCompressor } from '../utils/image-compressor.util';
41
- function buildMulterFile(overrides = {}) {
42
- return {
43
- fieldname: 'file',
44
- originalname: 'photo.jpg',
45
- encoding: '7bit',
46
- mimetype: 'image/jpeg',
47
- buffer: Buffer.from('file-bytes'),
48
- size: 10,
49
- stream: undefined,
50
- destination: '',
51
- filename: '',
52
- path: '',
53
- ...overrides
54
- };
55
- }
56
- describe('SftpProvider', ()=>{
57
- let provider;
58
- beforeEach(async ()=>{
59
- jest.clearAllMocks();
60
- mockConnect.mockResolvedValue(undefined);
61
- mockMkdir.mockResolvedValue(undefined);
62
- mockPut.mockResolvedValue(undefined);
63
- mockDelete.mockResolvedValue(undefined);
64
- mockEnd.mockResolvedValue(undefined);
65
- provider = new SftpProvider();
66
- await provider.initialize({
67
- host: 'sftp.example.com',
68
- username: 'deploy',
69
- password: 'secret',
70
- basePath: '/uploads'
71
- });
72
- });
73
- describe('initialize', ()=>{
74
- it('connects and ensures the base directory exists', ()=>{
75
- expect(mockConnect).toHaveBeenCalledWith(expect.objectContaining({
76
- host: 'sftp.example.com',
77
- username: 'deploy',
78
- password: 'secret'
79
- }));
80
- expect(mockMkdir).toHaveBeenCalledWith('/uploads', true);
81
- });
82
- it('defaults port to 22 and basePath to /uploads when not provided', async ()=>{
83
- jest.clearAllMocks();
84
- mockConnect.mockResolvedValue(undefined);
85
- mockMkdir.mockResolvedValue(undefined);
86
- const p = new SftpProvider();
87
- await p.initialize({
88
- host: 'h',
89
- username: 'u'
90
- });
91
- expect(mockConnect).toHaveBeenCalledWith(expect.objectContaining({
92
- port: 22
93
- }));
94
- expect(mockMkdir).toHaveBeenCalledWith('/uploads', true);
95
- });
96
- it('swallows mkdir errors (directory might already exist)', async ()=>{
97
- jest.clearAllMocks();
98
- mockConnect.mockResolvedValue(undefined);
99
- mockMkdir.mockRejectedValue(new Error('EEXIST'));
100
- const p = new SftpProvider();
101
- await expect(p.initialize({
102
- host: 'h',
103
- username: 'u',
104
- basePath: '/data'
105
- })).resolves.toBeUndefined();
106
- });
107
- it('throws InternalServerErrorException when the SFTP client cannot connect during initialize', async ()=>{
108
- mockConnect.mockRejectedValueOnce(new Error('ECONNREFUSED'));
109
- const brokenProvider = new SftpProvider();
110
- await expect(brokenProvider.initialize({
111
- host: 'unreachable',
112
- username: 'u'
113
- })).rejects.toThrow('SFTP client not found');
114
- });
115
- });
116
- describe('uploadFile', ()=>{
117
- it('uploads the file under basePath and returns metadata', async ()=>{
118
- const file = buildMulterFile();
119
- const result = await provider.uploadFile(file, {});
120
- expect(mockPut).toHaveBeenCalledWith(file.buffer, expect.stringContaining('/uploads/'));
121
- expect(result.name).toMatch(/photo\.jpg$/);
122
- expect(result.key).toContain('/uploads/');
123
- });
124
- it('nests the remote path under folderPath when provided', async ()=>{
125
- const file = buildMulterFile();
126
- const result = await provider.uploadFile(file, {
127
- folderPath: 'images'
128
- });
129
- expect(result.key).toContain('/uploads/images/');
130
- });
131
- it('compresses image files when options.compress is true', async ()=>{
132
- ImageCompressor.compress.mockResolvedValue({
133
- buffer: Buffer.from('small'),
134
- format: 'image/webp'
135
- });
136
- const file = buildMulterFile();
137
- const result = await provider.uploadFile(file, {
138
- compress: true
139
- });
140
- expect(ImageCompressor.compress).toHaveBeenCalled();
141
- expect(result.contentType).toBe('image/webp');
142
- });
143
- it('reconnects if not already connected before uploading', async ()=>{
144
- const file = buildMulterFile();
145
- mockConnect.mockClear();
146
- await provider.uploadFile(file, {});
147
- // already connected from initialize(); connect() should be a no-op guarded by `connected` flag
148
- expect(mockConnect).not.toHaveBeenCalled();
149
- });
150
- });
151
- describe('uploadMultipleFiles', ()=>{
152
- it('uploads all files', async ()=>{
153
- const files = [
154
- buildMulterFile({
155
- originalname: 'a.jpg'
156
- }),
157
- buildMulterFile({
158
- originalname: 'b.jpg'
159
- })
160
- ];
161
- const results = await provider.uploadMultipleFiles(files, {});
162
- expect(results).toHaveLength(2);
163
- expect(mockPut).toHaveBeenCalledTimes(2);
164
- });
165
- });
166
- describe('deleteFile', ()=>{
167
- it('deletes the file at the given remote path', async ()=>{
168
- await provider.deleteFile('/uploads/photo.jpg');
169
- expect(mockDelete).toHaveBeenCalledWith('/uploads/photo.jpg');
170
- });
171
- it('silently swallows delete errors', async ()=>{
172
- mockDelete.mockRejectedValue(new Error('not found'));
173
- await expect(provider.deleteFile('/uploads/missing.jpg')).resolves.toBeUndefined();
174
- });
175
- });
176
- describe('deleteMultipleFiles', ()=>{
177
- it('deletes every provided key', async ()=>{
178
- await provider.deleteMultipleFiles([
179
- '/uploads/a.jpg',
180
- '/uploads/b.jpg'
181
- ]);
182
- expect(mockDelete).toHaveBeenCalledTimes(2);
183
- });
184
- });
185
- describe('generatePresignedUrl', ()=>{
186
- it('returns the key unchanged since SFTP has no presigned URL support', async ()=>{
187
- const url = await provider.generatePresignedUrl('/uploads/photo.jpg', 3600);
188
- expect(url).toBe('/uploads/photo.jpg');
189
- });
190
- });
191
- describe('healthCheck', ()=>{
192
- it('returns true when connected', async ()=>{
193
- await expect(provider.healthCheck()).resolves.toBe(true);
194
- });
195
- it('returns false when reconnecting fails', async ()=>{
196
- // Disconnect first so healthCheck() must attempt to reconnect via this.connect()
197
- await provider.close();
198
- mockConnect.mockRejectedValueOnce(new Error('unreachable'));
199
- await expect(provider.healthCheck()).resolves.toBe(false);
200
- });
201
- });
202
- describe('close', ()=>{
203
- it('ends the connection when connected', async ()=>{
204
- await provider.close();
205
- expect(mockEnd).toHaveBeenCalled();
206
- });
207
- it('does nothing when not connected', async ()=>{
208
- const freshProvider = new SftpProvider();
209
- await expect(freshProvider.close()).resolves.toBeUndefined();
210
- expect(mockEnd).not.toHaveBeenCalled();
211
- });
212
- });
213
- });
@@ -1,124 +0,0 @@
1
- function _define_property(obj, key, value) {
2
- if (key in obj) {
3
- Object.defineProperty(obj, key, {
4
- value: value,
5
- enumerable: true,
6
- configurable: true,
7
- writable: true
8
- });
9
- } else {
10
- obj[key] = value;
11
- }
12
- return obj;
13
- }
14
- function _ts_decorate(decorators, target, key, desc) {
15
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17
- else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
18
- return c > 3 && r && Object.defineProperty(target, key, r), r;
19
- }
20
- function _ts_metadata(k, v) {
21
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
- }
23
- function _ts_param(paramIndex, decorator) {
24
- return function(target, key) {
25
- decorator(target, key, paramIndex);
26
- };
27
- }
28
- import { SYSTEM_MESSAGES } from '@flusys/nestjs-shared/constants';
29
- import { Inject, Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
30
- import * as crypto from 'crypto';
31
- import { StorageConfigService } from '../services';
32
- import { StorageProviderRegistry } from './storage-provider.registry';
33
- export class StorageFactoryService {
34
- async createProvider(config) {
35
- const cacheKey = this.generateCacheKey(config);
36
- const cached = this.cache.get(cacheKey);
37
- if (cached) return cached;
38
- const ProviderClass = StorageProviderRegistry.get(config.provider);
39
- if (!ProviderClass) {
40
- throw new NotFoundException({
41
- message: `Storage provider '${config.provider}' not available. Available: ${StorageProviderRegistry.getAll().join(', ')}`,
42
- messageKey: SYSTEM_MESSAGES.SERVICE_NOT_AVAILABLE,
43
- messageVariables: {
44
- provider: config.provider,
45
- available: StorageProviderRegistry.getAll().join(', ')
46
- }
47
- });
48
- }
49
- try {
50
- const instance = new ProviderClass();
51
- await this.initializeProvider(instance, config);
52
- this.cache.set(cacheKey, instance);
53
- return instance;
54
- } catch (error) {
55
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
56
- throw new InternalServerErrorException({
57
- message: `Failed to initialize '${config.provider}': ${errorMessage}`,
58
- messageKey: SYSTEM_MESSAGES.INTERNAL_ERROR,
59
- messageVariables: {
60
- provider: config.provider,
61
- error: errorMessage
62
- }
63
- });
64
- }
65
- }
66
- async getProvider(providerName) {
67
- const cachedKey = Array.from(this.cache.keys()).find((k)=>k.startsWith(providerName));
68
- if (cachedKey) return this.cache.get(cachedKey);
69
- return this.createProvider({
70
- provider: providerName,
71
- config: {}
72
- });
73
- }
74
- getLocalProviderBasePath() {
75
- const localKey = Array.from(this.cache.keys()).find((k)=>k.startsWith('local'));
76
- const provider = localKey ? this.cache.get(localKey) : null;
77
- return provider && 'basePath' in provider ? provider.basePath : null;
78
- }
79
- clearCache() {
80
- this.cache.clear();
81
- }
82
- async onModuleDestroy() {
83
- const closePromises = Array.from(this.cache.entries()).filter(([, p])=>'close' in p && typeof p.close === 'function').map(([, p])=>p.close().catch(()=>{}));
84
- await Promise.allSettled(closePromises);
85
- this.cache.clear();
86
- }
87
- // ─── Private Helpers ──────────────────────────────────────────────────────────
88
- generateCacheKey(config) {
89
- const sortedKeys = Object.keys(config.config || {}).sort();
90
- const hash = crypto.createHash('sha256').update(JSON.stringify(config.config, sortedKeys)).digest('hex').substring(0, 16);
91
- return `${config.provider}-${hash}`;
92
- }
93
- async initializeProvider(instance, config) {
94
- if (!('initialize' in instance) || typeof instance.initialize !== 'function') {
95
- return;
96
- }
97
- let initConfig = config.config;
98
- // Inject appUrl as baseUrl fallback for local provider
99
- if (config.provider === 'local' && !config.config?.baseUrl) {
100
- const appUrl = this.configService.getAppUrl();
101
- if (appUrl) {
102
- initConfig = {
103
- ...config.config,
104
- baseUrl: appUrl
105
- };
106
- }
107
- }
108
- await instance.initialize(initConfig);
109
- }
110
- constructor(configService){
111
- _define_property(this, "configService", void 0);
112
- _define_property(this, "cache", void 0);
113
- this.configService = configService;
114
- this.cache = new Map();
115
- }
116
- }
117
- StorageFactoryService = _ts_decorate([
118
- Injectable(),
119
- _ts_param(0, Inject(StorageConfigService)),
120
- _ts_metadata("design:type", Function),
121
- _ts_metadata("design:paramtypes", [
122
- typeof StorageConfigService === "undefined" ? Object : StorageConfigService
123
- ])
124
- ], StorageFactoryService);
@@ -1,249 +0,0 @@
1
- function _define_property(obj, key, value) {
2
- if (key in obj) {
3
- Object.defineProperty(obj, key, {
4
- value: value,
5
- enumerable: true,
6
- configurable: true,
7
- writable: true
8
- });
9
- } else {
10
- obj[key] = value;
11
- }
12
- return obj;
13
- }
14
- import { NotFoundException, InternalServerErrorException } from '@nestjs/common';
15
- import { StorageFactoryService } from './storage-factory.service';
16
- import { StorageProviderRegistry } from './storage-provider.registry';
17
- let FakeProvider = class FakeProvider {
18
- async initialize(config) {
19
- this.initializeCalledWith = config;
20
- this.basePath = config?.basePath;
21
- }
22
- async uploadFile() {
23
- return {};
24
- }
25
- async uploadMultipleFiles() {
26
- return [];
27
- }
28
- async deleteFile() {}
29
- async deleteMultipleFiles() {}
30
- async generatePresignedUrl() {
31
- return '';
32
- }
33
- async healthCheck() {
34
- return true;
35
- }
36
- constructor(){
37
- _define_property(this, "initializeCalledWith", undefined);
38
- _define_property(this, "basePath", void 0);
39
- _define_property(this, "close", jest.fn().mockResolvedValue(undefined));
40
- }
41
- };
42
- let NoInitProvider = class NoInitProvider {
43
- async uploadFile() {
44
- return {};
45
- }
46
- async uploadMultipleFiles() {
47
- return [];
48
- }
49
- async deleteFile() {}
50
- async deleteMultipleFiles() {}
51
- async generatePresignedUrl() {
52
- return '';
53
- }
54
- async healthCheck() {
55
- return true;
56
- }
57
- };
58
- describe('StorageFactoryService', ()=>{
59
- let service;
60
- let mockConfigService;
61
- beforeEach(()=>{
62
- StorageProviderRegistry.clear();
63
- StorageProviderRegistry.register('fake', FakeProvider);
64
- StorageProviderRegistry.register('local', FakeProvider);
65
- StorageProviderRegistry.register('noinit', NoInitProvider);
66
- mockConfigService = {
67
- getAppUrl: jest.fn().mockReturnValue('http://localhost:2002')
68
- };
69
- service = new StorageFactoryService(mockConfigService);
70
- });
71
- afterEach(()=>{
72
- StorageProviderRegistry.clear();
73
- });
74
- describe('createProvider', ()=>{
75
- it('creates and initializes a provider instance from the registry', async ()=>{
76
- const provider = await service.createProvider({
77
- provider: 'fake',
78
- config: {
79
- bucket: 'x'
80
- }
81
- });
82
- expect(provider).toBeInstanceOf(FakeProvider);
83
- expect(provider.initializeCalledWith).toEqual({
84
- bucket: 'x'
85
- });
86
- });
87
- it('caches providers by a hash of their config and reuses the cached instance', async ()=>{
88
- const config = {
89
- provider: 'fake',
90
- config: {
91
- bucket: 'x'
92
- }
93
- };
94
- const first = await service.createProvider(config);
95
- const second = await service.createProvider(config);
96
- expect(first).toBe(second);
97
- });
98
- it('creates distinct instances for different configs of the same provider', async ()=>{
99
- const first = await service.createProvider({
100
- provider: 'fake',
101
- config: {
102
- bucket: 'a'
103
- }
104
- });
105
- const second = await service.createProvider({
106
- provider: 'fake',
107
- config: {
108
- bucket: 'b'
109
- }
110
- });
111
- expect(first).not.toBe(second);
112
- });
113
- it('throws NotFoundException when the requested provider is not registered', async ()=>{
114
- await expect(service.createProvider({
115
- provider: 'unregistered',
116
- config: {}
117
- })).rejects.toThrow(NotFoundException);
118
- });
119
- it('skips calling initialize() when the provider does not implement it', async ()=>{
120
- const provider = await service.createProvider({
121
- provider: 'noinit',
122
- config: {}
123
- });
124
- expect(provider).toBeInstanceOf(NoInitProvider);
125
- });
126
- it('wraps unexpected initialization errors in InternalServerErrorException', async ()=>{
127
- let ThrowingProvider = class ThrowingProvider {
128
- async initialize() {
129
- throw new Error('connection refused');
130
- }
131
- async uploadFile() {
132
- return {};
133
- }
134
- async uploadMultipleFiles() {
135
- return [];
136
- }
137
- async deleteFile() {}
138
- async deleteMultipleFiles() {}
139
- async generatePresignedUrl() {
140
- return '';
141
- }
142
- async healthCheck() {
143
- return true;
144
- }
145
- };
146
- StorageProviderRegistry.register('throwing', ThrowingProvider);
147
- await expect(service.createProvider({
148
- provider: 'throwing',
149
- config: {}
150
- })).rejects.toThrow(InternalServerErrorException);
151
- });
152
- it('injects appUrl as baseUrl fallback for the local provider when not explicitly configured', async ()=>{
153
- const provider = await service.createProvider({
154
- provider: 'local',
155
- config: {
156
- basePath: './uploads'
157
- }
158
- });
159
- expect(provider.initializeCalledWith).toEqual({
160
- basePath: './uploads',
161
- baseUrl: 'http://localhost:2002'
162
- });
163
- });
164
- it('does not override an explicitly configured baseUrl for the local provider', async ()=>{
165
- const provider = await service.createProvider({
166
- provider: 'local',
167
- config: {
168
- basePath: './uploads',
169
- baseUrl: 'https://cdn.example.com'
170
- }
171
- });
172
- expect(provider.initializeCalledWith.baseUrl).toBe('https://cdn.example.com');
173
- });
174
- });
175
- describe('getProvider', ()=>{
176
- it('returns a cached provider whose cache key starts with the given name', async ()=>{
177
- const created = await service.createProvider({
178
- provider: 'fake',
179
- config: {
180
- bucket: 'x'
181
- }
182
- });
183
- const result = await service.getProvider('fake');
184
- expect(result).toBe(created);
185
- });
186
- it('creates a new provider with empty config if none is cached yet', async ()=>{
187
- const result = await service.getProvider('fake');
188
- expect(result).toBeInstanceOf(FakeProvider);
189
- });
190
- });
191
- describe('getLocalProviderBasePath', ()=>{
192
- it('returns null when no local provider has been created yet', ()=>{
193
- expect(service.getLocalProviderBasePath()).toBeNull();
194
- });
195
- it("returns the cached local provider's basePath", async ()=>{
196
- await service.createProvider({
197
- provider: 'local',
198
- config: {
199
- basePath: './my-uploads'
200
- }
201
- });
202
- expect(service.getLocalProviderBasePath()).toBe('./my-uploads');
203
- });
204
- });
205
- describe('clearCache', ()=>{
206
- it('empties the provider cache', async ()=>{
207
- await service.createProvider({
208
- provider: 'fake',
209
- config: {
210
- bucket: 'x'
211
- }
212
- });
213
- service.clearCache();
214
- expect(service.getLocalProviderBasePath()).toBeNull();
215
- });
216
- });
217
- describe('onModuleDestroy', ()=>{
218
- it('calls close() on cached providers that implement it', async ()=>{
219
- const provider = await service.createProvider({
220
- provider: 'fake',
221
- config: {
222
- bucket: 'x'
223
- }
224
- });
225
- await service.onModuleDestroy();
226
- expect(provider.close).toHaveBeenCalled();
227
- });
228
- it('does not throw when a provider close() call rejects', async ()=>{
229
- const provider = await service.createProvider({
230
- provider: 'fake',
231
- config: {
232
- bucket: 'y'
233
- }
234
- });
235
- provider.close.mockRejectedValue(new Error('close failed'));
236
- await expect(service.onModuleDestroy()).resolves.toBeUndefined();
237
- });
238
- it('clears the cache after destroy', async ()=>{
239
- await service.createProvider({
240
- provider: 'fake',
241
- config: {
242
- bucket: 'x'
243
- }
244
- });
245
- await service.onModuleDestroy();
246
- expect(service.getLocalProviderBasePath()).toBeNull();
247
- });
248
- });
249
- });
@@ -1,34 +0,0 @@
1
- function _define_property(obj, key, value) {
2
- if (key in obj) {
3
- Object.defineProperty(obj, key, {
4
- value: value,
5
- enumerable: true,
6
- configurable: true,
7
- writable: true
8
- });
9
- } else {
10
- obj[key] = value;
11
- }
12
- return obj;
13
- }
14
- /**
15
- * Static registry for storage provider classes
16
- * Allows dynamic registration at runtime
17
- */ export class StorageProviderRegistry {
18
- static register(name, providerClass) {
19
- this.providers.set(name.toLowerCase(), providerClass);
20
- }
21
- static get(name) {
22
- return this.providers.get(name.toLowerCase());
23
- }
24
- static has(name) {
25
- return this.providers.has(name.toLowerCase());
26
- }
27
- static getAll() {
28
- return Array.from(this.providers.keys());
29
- }
30
- static clear() {
31
- this.providers.clear();
32
- }
33
- }
34
- _define_property(StorageProviderRegistry, "providers", new Map());
@@ -1,63 +0,0 @@
1
- import { StorageProviderRegistry } from './storage-provider.registry';
2
- let FakeProvider = class FakeProvider {
3
- async uploadFile() {
4
- return {};
5
- }
6
- async uploadMultipleFiles() {
7
- return [];
8
- }
9
- async deleteFile() {}
10
- async deleteMultipleFiles() {}
11
- async generatePresignedUrl() {
12
- return '';
13
- }
14
- async healthCheck() {
15
- return true;
16
- }
17
- };
18
- let AnotherFakeProvider = class AnotherFakeProvider extends FakeProvider {
19
- };
20
- describe('StorageProviderRegistry', ()=>{
21
- afterEach(()=>{
22
- StorageProviderRegistry.clear();
23
- });
24
- it('registers and retrieves a provider by name', ()=>{
25
- StorageProviderRegistry.register('local', FakeProvider);
26
- expect(StorageProviderRegistry.get('local')).toBe(FakeProvider);
27
- });
28
- it('normalizes provider names to lowercase on register and lookup', ()=>{
29
- StorageProviderRegistry.register('LOCAL', FakeProvider);
30
- expect(StorageProviderRegistry.get('local')).toBe(FakeProvider);
31
- expect(StorageProviderRegistry.has('Local')).toBe(true);
32
- });
33
- it('returns undefined for a provider that was never registered', ()=>{
34
- expect(StorageProviderRegistry.get('nonexistent')).toBeUndefined();
35
- });
36
- it('has() reports false for an unregistered provider', ()=>{
37
- expect(StorageProviderRegistry.has('s3')).toBe(false);
38
- });
39
- it('has() reports true once a provider is registered', ()=>{
40
- StorageProviderRegistry.register('s3', FakeProvider);
41
- expect(StorageProviderRegistry.has('s3')).toBe(true);
42
- });
43
- it('getAll() lists every registered provider name', ()=>{
44
- StorageProviderRegistry.register('local', FakeProvider);
45
- StorageProviderRegistry.register('s3', AnotherFakeProvider);
46
- expect(StorageProviderRegistry.getAll().sort()).toEqual([
47
- 'local',
48
- 's3'
49
- ]);
50
- });
51
- it('overwrites an existing registration when registering the same name again', ()=>{
52
- StorageProviderRegistry.register('local', FakeProvider);
53
- StorageProviderRegistry.register('local', AnotherFakeProvider);
54
- expect(StorageProviderRegistry.get('local')).toBe(AnotherFakeProvider);
55
- });
56
- it('clear() removes all registered providers', ()=>{
57
- StorageProviderRegistry.register('local', FakeProvider);
58
- StorageProviderRegistry.register('s3', AnotherFakeProvider);
59
- StorageProviderRegistry.clear();
60
- expect(StorageProviderRegistry.getAll()).toEqual([]);
61
- expect(StorageProviderRegistry.has('local')).toBe(false);
62
- });
63
- });