@kiyasov/platform-hono 1.0.6 → 1.0.8

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 (235) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/dist/cjs/index.d.ts +1 -0
  3. package/dist/cjs/index.js +1 -0
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/src/adapters/hono-adapter.d.ts +11 -11
  6. package/dist/cjs/src/adapters/hono-adapter.js +24 -24
  7. package/dist/cjs/src/adapters/hono-adapter.js.map +1 -1
  8. package/dist/cjs/src/drivers/constants/apollo.constants.d.ts +1 -0
  9. package/dist/cjs/src/drivers/constants/apollo.constants.js +5 -0
  10. package/dist/cjs/src/drivers/constants/apollo.constants.js.map +1 -0
  11. package/dist/cjs/src/drivers/constants/index.d.ts +1 -0
  12. package/dist/cjs/src/drivers/constants/index.js +18 -0
  13. package/dist/cjs/src/drivers/constants/index.js.map +1 -0
  14. package/dist/cjs/src/drivers/graphql.driver.d.ts +7 -3
  15. package/dist/cjs/src/drivers/graphql.driver.js +32 -12
  16. package/dist/cjs/src/drivers/graphql.driver.js.map +1 -1
  17. package/dist/cjs/src/drivers/services/plugins-explorer.service.d.ts +9 -0
  18. package/dist/cjs/src/drivers/services/plugins-explorer.service.js +25 -0
  19. package/dist/cjs/src/drivers/services/plugins-explorer.service.js.map +1 -0
  20. package/dist/cjs/src/multer/crypto/index.d.ts +2 -0
  21. package/dist/cjs/src/multer/crypto/index.js +7 -0
  22. package/dist/cjs/src/multer/crypto/index.js.map +1 -0
  23. package/dist/cjs/src/multer/decorators/index.d.ts +2 -0
  24. package/dist/cjs/src/multer/decorators/index.js +19 -0
  25. package/dist/cjs/src/multer/decorators/index.js.map +1 -0
  26. package/dist/cjs/src/multer/decorators/uploaded-file-decorator.d.ts +1 -0
  27. package/dist/cjs/src/multer/decorators/uploaded-file-decorator.js +10 -0
  28. package/dist/cjs/src/multer/decorators/uploaded-file-decorator.js.map +1 -0
  29. package/dist/cjs/src/multer/decorators/uploaded-files-decorator.d.ts +1 -0
  30. package/dist/cjs/src/multer/decorators/uploaded-files-decorator.js +10 -0
  31. package/dist/cjs/src/multer/decorators/uploaded-files-decorator.js.map +1 -0
  32. package/dist/cjs/src/multer/fs/index.d.ts +2 -0
  33. package/dist/cjs/src/multer/fs/index.js +23 -0
  34. package/dist/cjs/src/multer/fs/index.js.map +1 -0
  35. package/dist/cjs/src/multer/index.d.ts +4 -0
  36. package/dist/cjs/src/multer/index.js +21 -0
  37. package/dist/cjs/src/multer/index.js.map +1 -0
  38. package/dist/cjs/src/multer/interceptors/any-files-interceptor.d.ts +3 -0
  39. package/dist/cjs/src/multer/interceptors/any-files-interceptor.js +27 -0
  40. package/dist/cjs/src/multer/interceptors/any-files-interceptor.js.map +1 -0
  41. package/dist/cjs/src/multer/interceptors/file-fields-interceptor.d.ts +4 -0
  42. package/dist/cjs/src/multer/interceptors/file-fields-interceptor.js +28 -0
  43. package/dist/cjs/src/multer/interceptors/file-fields-interceptor.js.map +1 -0
  44. package/dist/cjs/src/multer/interceptors/file-interceptor.d.ts +3 -0
  45. package/dist/cjs/src/multer/interceptors/file-interceptor.js +27 -0
  46. package/dist/cjs/src/multer/interceptors/file-interceptor.js.map +1 -0
  47. package/dist/cjs/src/multer/interceptors/files-interceptor.d.ts +3 -0
  48. package/dist/cjs/src/multer/interceptors/files-interceptor.js +27 -0
  49. package/dist/cjs/src/multer/interceptors/files-interceptor.js.map +1 -0
  50. package/dist/cjs/src/multer/interceptors/index.d.ts +4 -0
  51. package/dist/cjs/src/multer/interceptors/index.js +21 -0
  52. package/dist/cjs/src/multer/interceptors/index.js.map +1 -0
  53. package/dist/cjs/src/multer/multipart/exceptions.d.ts +1 -0
  54. package/dist/cjs/src/multer/multipart/exceptions.js +22 -0
  55. package/dist/cjs/src/multer/multipart/exceptions.js.map +1 -0
  56. package/dist/cjs/src/multer/multipart/file.d.ts +10 -0
  57. package/dist/cjs/src/multer/multipart/file.js +10 -0
  58. package/dist/cjs/src/multer/multipart/file.js.map +1 -0
  59. package/dist/cjs/src/multer/multipart/filter.d.ts +6 -0
  60. package/dist/cjs/src/multer/multipart/filter.js +22 -0
  61. package/dist/cjs/src/multer/multipart/filter.js.map +1 -0
  62. package/dist/cjs/src/multer/multipart/handlers/any-files.d.ts +8 -0
  63. package/dist/cjs/src/multer/multipart/handlers/any-files.js +33 -0
  64. package/dist/cjs/src/multer/multipart/handlers/any-files.js.map +1 -0
  65. package/dist/cjs/src/multer/multipart/handlers/file-fields.d.ts +14 -0
  66. package/dist/cjs/src/multer/multipart/handlers/file-fields.js +57 -0
  67. package/dist/cjs/src/multer/multipart/handlers/file-fields.js.map +1 -0
  68. package/dist/cjs/src/multer/multipart/handlers/index.d.ts +1 -0
  69. package/dist/cjs/src/multer/multipart/handlers/index.js +3 -0
  70. package/dist/cjs/src/multer/multipart/handlers/index.js.map +1 -0
  71. package/dist/cjs/src/multer/multipart/handlers/multiple-files.d.ts +8 -0
  72. package/dist/cjs/src/multer/multipart/handlers/multiple-files.js +40 -0
  73. package/dist/cjs/src/multer/multipart/handlers/multiple-files.js.map +1 -0
  74. package/dist/cjs/src/multer/multipart/handlers/single-file.d.ts +8 -0
  75. package/dist/cjs/src/multer/multipart/handlers/single-file.js +45 -0
  76. package/dist/cjs/src/multer/multipart/handlers/single-file.js.map +1 -0
  77. package/dist/cjs/src/multer/multipart/index.d.ts +3 -0
  78. package/dist/cjs/src/multer/multipart/index.js +19 -0
  79. package/dist/cjs/src/multer/multipart/index.js.map +1 -0
  80. package/dist/cjs/src/multer/multipart/options.d.ts +22 -0
  81. package/dist/cjs/src/multer/multipart/options.js +23 -0
  82. package/dist/cjs/src/multer/multipart/options.js.map +1 -0
  83. package/dist/cjs/src/multer/multipart/request.d.ts +16 -0
  84. package/dist/cjs/src/multer/multipart/request.js +25 -0
  85. package/dist/cjs/src/multer/multipart/request.js.map +1 -0
  86. package/dist/cjs/src/multer/storage/disk-storage.d.ts +31 -0
  87. package/dist/cjs/src/multer/storage/disk-storage.js +62 -0
  88. package/dist/cjs/src/multer/storage/disk-storage.js.map +1 -0
  89. package/dist/cjs/src/multer/storage/index.d.ts +3 -0
  90. package/dist/cjs/src/multer/storage/index.js +20 -0
  91. package/dist/cjs/src/multer/storage/index.js.map +1 -0
  92. package/dist/cjs/src/multer/storage/memory-storage.d.ts +17 -0
  93. package/dist/cjs/src/multer/storage/memory-storage.js +21 -0
  94. package/dist/cjs/src/multer/storage/memory-storage.js.map +1 -0
  95. package/dist/cjs/src/multer/storage/storage.d.ts +13 -0
  96. package/dist/cjs/src/multer/storage/storage.js +3 -0
  97. package/dist/cjs/src/multer/storage/storage.js.map +1 -0
  98. package/dist/cjs/src/multer/stream/index.d.ts +3 -0
  99. package/dist/cjs/src/multer/stream/index.js +7 -0
  100. package/dist/cjs/src/multer/stream/index.js.map +1 -0
  101. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  102. package/dist/esm/index.d.ts +1 -0
  103. package/dist/esm/index.js +1 -0
  104. package/dist/esm/index.js.map +1 -1
  105. package/dist/esm/src/adapters/hono-adapter.d.ts +11 -11
  106. package/dist/esm/src/adapters/hono-adapter.js +35 -35
  107. package/dist/esm/src/adapters/hono-adapter.js.map +1 -1
  108. package/dist/esm/src/drivers/constants/apollo.constants.d.ts +1 -0
  109. package/dist/esm/src/drivers/constants/apollo.constants.js +2 -0
  110. package/dist/esm/src/drivers/constants/apollo.constants.js.map +1 -0
  111. package/dist/esm/src/drivers/constants/index.d.ts +1 -0
  112. package/dist/esm/src/drivers/constants/index.js +2 -0
  113. package/dist/esm/src/drivers/constants/index.js.map +1 -0
  114. package/dist/esm/src/drivers/graphql.driver.d.ts +7 -3
  115. package/dist/esm/src/drivers/graphql.driver.js +36 -16
  116. package/dist/esm/src/drivers/graphql.driver.js.map +1 -1
  117. package/dist/esm/src/drivers/services/plugins-explorer.service.d.ts +9 -0
  118. package/dist/esm/src/drivers/services/plugins-explorer.service.js +21 -0
  119. package/dist/esm/src/drivers/services/plugins-explorer.service.js.map +1 -0
  120. package/dist/esm/src/multer/crypto/index.d.ts +2 -0
  121. package/dist/esm/src/multer/crypto/index.js +4 -0
  122. package/dist/esm/src/multer/crypto/index.js.map +1 -0
  123. package/dist/esm/src/multer/decorators/index.d.ts +2 -0
  124. package/dist/esm/src/multer/decorators/index.js +3 -0
  125. package/dist/esm/src/multer/decorators/index.js.map +1 -0
  126. package/dist/esm/src/multer/decorators/uploaded-file-decorator.d.ts +1 -0
  127. package/dist/esm/src/multer/decorators/uploaded-file-decorator.js +7 -0
  128. package/dist/esm/src/multer/decorators/uploaded-file-decorator.js.map +1 -0
  129. package/dist/esm/src/multer/decorators/uploaded-files-decorator.d.ts +1 -0
  130. package/dist/esm/src/multer/decorators/uploaded-files-decorator.js +7 -0
  131. package/dist/esm/src/multer/decorators/uploaded-files-decorator.js.map +1 -0
  132. package/dist/esm/src/multer/fs/index.d.ts +2 -0
  133. package/dist/esm/src/multer/fs/index.js +18 -0
  134. package/dist/esm/src/multer/fs/index.js.map +1 -0
  135. package/dist/esm/src/multer/index.d.ts +4 -0
  136. package/dist/esm/src/multer/index.js +5 -0
  137. package/dist/esm/src/multer/index.js.map +1 -0
  138. package/dist/esm/src/multer/interceptors/any-files-interceptor.d.ts +3 -0
  139. package/dist/esm/src/multer/interceptors/any-files-interceptor.js +23 -0
  140. package/dist/esm/src/multer/interceptors/any-files-interceptor.js.map +1 -0
  141. package/dist/esm/src/multer/interceptors/file-fields-interceptor.d.ts +4 -0
  142. package/dist/esm/src/multer/interceptors/file-fields-interceptor.js +24 -0
  143. package/dist/esm/src/multer/interceptors/file-fields-interceptor.js.map +1 -0
  144. package/dist/esm/src/multer/interceptors/file-interceptor.d.ts +3 -0
  145. package/dist/esm/src/multer/interceptors/file-interceptor.js +23 -0
  146. package/dist/esm/src/multer/interceptors/file-interceptor.js.map +1 -0
  147. package/dist/esm/src/multer/interceptors/files-interceptor.d.ts +3 -0
  148. package/dist/esm/src/multer/interceptors/files-interceptor.js +23 -0
  149. package/dist/esm/src/multer/interceptors/files-interceptor.js.map +1 -0
  150. package/dist/esm/src/multer/interceptors/index.d.ts +4 -0
  151. package/dist/esm/src/multer/interceptors/index.js +5 -0
  152. package/dist/esm/src/multer/interceptors/index.js.map +1 -0
  153. package/dist/esm/src/multer/multipart/exceptions.d.ts +1 -0
  154. package/dist/esm/src/multer/multipart/exceptions.js +18 -0
  155. package/dist/esm/src/multer/multipart/exceptions.js.map +1 -0
  156. package/dist/esm/src/multer/multipart/file.d.ts +10 -0
  157. package/dist/esm/src/multer/multipart/file.js +6 -0
  158. package/dist/esm/src/multer/multipart/file.js.map +1 -0
  159. package/dist/esm/src/multer/multipart/filter.d.ts +6 -0
  160. package/dist/esm/src/multer/multipart/filter.js +18 -0
  161. package/dist/esm/src/multer/multipart/filter.js.map +1 -0
  162. package/dist/esm/src/multer/multipart/handlers/any-files.d.ts +8 -0
  163. package/dist/esm/src/multer/multipart/handlers/any-files.js +29 -0
  164. package/dist/esm/src/multer/multipart/handlers/any-files.js.map +1 -0
  165. package/dist/esm/src/multer/multipart/handlers/file-fields.d.ts +14 -0
  166. package/dist/esm/src/multer/multipart/handlers/file-fields.js +52 -0
  167. package/dist/esm/src/multer/multipart/handlers/file-fields.js.map +1 -0
  168. package/dist/esm/src/multer/multipart/handlers/index.d.ts +1 -0
  169. package/dist/esm/src/multer/multipart/handlers/index.js +2 -0
  170. package/dist/esm/src/multer/multipart/handlers/index.js.map +1 -0
  171. package/dist/esm/src/multer/multipart/handlers/multiple-files.d.ts +8 -0
  172. package/dist/esm/src/multer/multipart/handlers/multiple-files.js +36 -0
  173. package/dist/esm/src/multer/multipart/handlers/multiple-files.js.map +1 -0
  174. package/dist/esm/src/multer/multipart/handlers/single-file.d.ts +8 -0
  175. package/dist/esm/src/multer/multipart/handlers/single-file.js +41 -0
  176. package/dist/esm/src/multer/multipart/handlers/single-file.js.map +1 -0
  177. package/dist/esm/src/multer/multipart/index.d.ts +3 -0
  178. package/dist/esm/src/multer/multipart/index.js +3 -0
  179. package/dist/esm/src/multer/multipart/index.js.map +1 -0
  180. package/dist/esm/src/multer/multipart/options.d.ts +22 -0
  181. package/dist/esm/src/multer/multipart/options.js +19 -0
  182. package/dist/esm/src/multer/multipart/options.js.map +1 -0
  183. package/dist/esm/src/multer/multipart/request.d.ts +16 -0
  184. package/dist/esm/src/multer/multipart/request.js +20 -0
  185. package/dist/esm/src/multer/multipart/request.js.map +1 -0
  186. package/dist/esm/src/multer/storage/disk-storage.d.ts +31 -0
  187. package/dist/esm/src/multer/storage/disk-storage.js +58 -0
  188. package/dist/esm/src/multer/storage/disk-storage.js.map +1 -0
  189. package/dist/esm/src/multer/storage/index.d.ts +3 -0
  190. package/dist/esm/src/multer/storage/index.js +4 -0
  191. package/dist/esm/src/multer/storage/index.js.map +1 -0
  192. package/dist/esm/src/multer/storage/memory-storage.d.ts +17 -0
  193. package/dist/esm/src/multer/storage/memory-storage.js +17 -0
  194. package/dist/esm/src/multer/storage/memory-storage.js.map +1 -0
  195. package/dist/esm/src/multer/storage/storage.d.ts +13 -0
  196. package/dist/esm/src/multer/storage/storage.js +2 -0
  197. package/dist/esm/src/multer/storage/storage.js.map +1 -0
  198. package/dist/esm/src/multer/stream/index.d.ts +3 -0
  199. package/dist/esm/src/multer/stream/index.js +4 -0
  200. package/dist/esm/src/multer/stream/index.js.map +1 -0
  201. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  202. package/index.ts +1 -0
  203. package/package.json +3 -1
  204. package/src/adapters/hono-adapter.ts +51 -50
  205. package/src/drivers/constants/apollo.constants.ts +1 -0
  206. package/src/drivers/constants/index.ts +1 -0
  207. package/src/drivers/graphql.driver.ts +52 -21
  208. package/src/drivers/services/plugins-explorer.service.ts +27 -0
  209. package/src/multer/crypto/index.ts +4 -0
  210. package/src/multer/decorators/index.ts +2 -0
  211. package/src/multer/decorators/uploaded-file-decorator.ts +12 -0
  212. package/src/multer/decorators/uploaded-files-decorator.ts +15 -0
  213. package/src/multer/fs/index.ts +22 -0
  214. package/src/multer/index.ts +4 -0
  215. package/src/multer/interceptors/any-files-interceptor.ts +46 -0
  216. package/src/multer/interceptors/file-fields-interceptor.ts +56 -0
  217. package/src/multer/interceptors/file-interceptor.ts +48 -0
  218. package/src/multer/interceptors/files-interceptor.ts +50 -0
  219. package/src/multer/interceptors/index.ts +4 -0
  220. package/src/multer/multipart/exceptions.ts +25 -0
  221. package/src/multer/multipart/file.ts +18 -0
  222. package/src/multer/multipart/filter.ts +38 -0
  223. package/src/multer/multipart/handlers/any-files.ts +39 -0
  224. package/src/multer/multipart/handlers/file-fields.ts +89 -0
  225. package/src/multer/multipart/handlers/index.ts +1 -0
  226. package/src/multer/multipart/handlers/multiple-files.ts +55 -0
  227. package/src/multer/multipart/handlers/single-file.ts +56 -0
  228. package/src/multer/multipart/index.ts +3 -0
  229. package/src/multer/multipart/options.ts +29 -0
  230. package/src/multer/multipart/request.ts +44 -0
  231. package/src/multer/storage/disk-storage.ts +107 -0
  232. package/src/multer/storage/index.ts +3 -0
  233. package/src/multer/storage/memory-storage.ts +25 -0
  234. package/src/multer/storage/storage.ts +15 -0
  235. package/src/multer/stream/index.ts +4 -0
@@ -0,0 +1,50 @@
1
+ import { Observable, tap } from 'rxjs';
2
+ import {
3
+ CallHandler,
4
+ ExecutionContext,
5
+ mixin,
6
+ NestInterceptor,
7
+ Type,
8
+ } from '@nestjs/common';
9
+
10
+ import { getMultipartRequest } from '../multipart/request';
11
+ import { transformUploadOptions, UploadOptions } from '../multipart/options';
12
+ import { handleMultipartMultipleFiles } from '../multipart/handlers/multiple-files';
13
+
14
+ export function FilesInterceptor(
15
+ fieldname: string,
16
+ maxCount = 1,
17
+ options?: UploadOptions,
18
+ ): Type<NestInterceptor> {
19
+ class MixinInterceptor implements NestInterceptor {
20
+ private readonly options: UploadOptions;
21
+
22
+ constructor() {
23
+ this.options = transformUploadOptions(options);
24
+ }
25
+
26
+ async intercept(
27
+ context: ExecutionContext,
28
+ next: CallHandler,
29
+ ): Promise<Observable<any>> {
30
+ const ctx = context.switchToHttp();
31
+ const req = getMultipartRequest(ctx);
32
+
33
+ const { body, files, remove } = await handleMultipartMultipleFiles(
34
+ req,
35
+ fieldname,
36
+ maxCount,
37
+ this.options,
38
+ );
39
+
40
+ req.body = body;
41
+ req.storageFiles = files;
42
+
43
+ return next.handle().pipe(tap(remove));
44
+ }
45
+ }
46
+
47
+ const Interceptor = mixin(MixinInterceptor);
48
+
49
+ return Interceptor;
50
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./file-fields-interceptor";
2
+ export * from "./file-interceptor";
3
+ export * from "./any-files-interceptor";
4
+ export * from "./files-interceptor";
@@ -0,0 +1,25 @@
1
+ import {
2
+ BadRequestException,
3
+ HttpException,
4
+ PayloadTooLargeException,
5
+ } from '@nestjs/common';
6
+
7
+ export const transformException = (err: Error | undefined) => {
8
+ if (!err || err instanceof HttpException) {
9
+ return err;
10
+ }
11
+
12
+ const code: string = (err as any).code;
13
+
14
+ switch (code) {
15
+ case 'REQ_FILE_TOO_LARGE':
16
+ return new PayloadTooLargeException();
17
+ case 'PARTS_LIMIT':
18
+ case 'FILES_LIMIT':
19
+ case 'PROTO_VIOLATION':
20
+ case 'INVALID_MULTIPART_CONTENT_TYPE':
21
+ return new BadRequestException(err.message);
22
+ }
23
+
24
+ return err;
25
+ };
@@ -0,0 +1,18 @@
1
+ import { Readable } from 'stream';
2
+ import { Storage, StorageFile } from '../storage';
3
+
4
+ export type MultipartFile = Omit<File[], 'file'> & {
5
+ value?: any;
6
+ file: Readable & { truncated?: boolean };
7
+ };
8
+
9
+ export const removeStorageFiles = async (
10
+ storage: Storage,
11
+ files?: (StorageFile | undefined)[],
12
+ force?: boolean,
13
+ ) => {
14
+ if (files == null) return;
15
+ await Promise.all(
16
+ files.map((file) => file && storage.removeFile(file, force)),
17
+ );
18
+ };
@@ -0,0 +1,38 @@
1
+ import { BadRequestException } from '@nestjs/common';
2
+ import { UploadOptions } from '.';
3
+
4
+ import { DiskStorageFile, MemoryStorageFile, StorageFile } from '../storage';
5
+ import { HonoRequest } from 'hono';
6
+
7
+ export type UploadFilterFile =
8
+ | DiskStorageFile
9
+ | MemoryStorageFile
10
+ | StorageFile;
11
+
12
+ export type UploadFilterHandler = (
13
+ req: HonoRequest,
14
+ file: UploadFilterFile,
15
+ ) => Promise<boolean | string> | boolean | string;
16
+
17
+ export const filterUpload = async (
18
+ uploadOptions: UploadOptions,
19
+ req: HonoRequest,
20
+ file: UploadFilterFile,
21
+ ): Promise<boolean> => {
22
+ if (uploadOptions.filter == null) {
23
+ return true;
24
+ }
25
+
26
+ try {
27
+ const res = await uploadOptions.filter(req, file);
28
+
29
+ if (typeof res === 'string') {
30
+ throw new BadRequestException(res);
31
+ }
32
+
33
+ return res;
34
+ } catch (error) {
35
+ await uploadOptions.storage!.removeFile(file, true);
36
+ throw error;
37
+ }
38
+ };
@@ -0,0 +1,39 @@
1
+ import { UploadOptions } from '../options';
2
+ import { StorageFile } from '../../storage';
3
+ import { THonoRequest, getParts } from '../request';
4
+ import { removeStorageFiles } from '../file';
5
+ import { filterUpload } from '../filter';
6
+
7
+ export const handleMultipartAnyFiles = async (
8
+ req: THonoRequest,
9
+ options: UploadOptions,
10
+ ) => {
11
+ const parts = await getParts(req, options);
12
+
13
+ const body: Record<string, any> = {};
14
+
15
+ const files: StorageFile[] = [];
16
+
17
+ const removeFiles = async (error?: boolean) => {
18
+ return await removeStorageFiles(options.storage!, files, error);
19
+ };
20
+
21
+ try {
22
+ for await (const [partFieldName, part] of Object.entries(parts)) {
23
+ if (!(part instanceof File)) {
24
+ body[partFieldName] = part;
25
+ continue;
26
+ }
27
+ const file = await options.storage!.handleFile(part, req, partFieldName);
28
+
29
+ if (await filterUpload(options, req, file)) {
30
+ files.push(file);
31
+ }
32
+ }
33
+ } catch (error) {
34
+ await removeFiles(true);
35
+ throw error;
36
+ }
37
+
38
+ return { body, files, remove: () => removeFiles() };
39
+ };
@@ -0,0 +1,89 @@
1
+ import { BadRequestException } from '@nestjs/common';
2
+
3
+ import { UploadOptions } from '../options';
4
+ import { StorageFile } from '../../storage/storage';
5
+ import { THonoRequest, getParts } from '../request';
6
+ import { removeStorageFiles } from '../file';
7
+ import { filterUpload } from '../filter';
8
+ import { HonoRequest } from 'hono';
9
+
10
+ export interface UploadField {
11
+ /**
12
+ * Field name
13
+ */
14
+ name: string;
15
+ /**
16
+ * Max number of files in this field
17
+ */
18
+ maxCount?: number;
19
+ }
20
+
21
+ export type UploadFieldMapEntry = Required<Pick<UploadField, 'maxCount'>>;
22
+
23
+ export const uploadFieldsToMap = (uploadFields: UploadField[]) => {
24
+ const map = new Map<string, UploadFieldMapEntry>();
25
+
26
+ uploadFields.forEach(({ name, ...opts }) => {
27
+ map.set(name, { maxCount: 1, ...opts });
28
+ });
29
+
30
+ return map;
31
+ };
32
+
33
+ export const handleMultipartFileFields = async (
34
+ req: THonoRequest,
35
+ fieldsMap: Map<string, UploadFieldMapEntry>,
36
+ options: UploadOptions,
37
+ ) => {
38
+ const parts = await getParts(req, options);
39
+ const body: Record<string, any> = {};
40
+
41
+ const files: Record<string, StorageFile[]> = {};
42
+
43
+ const removeFiles = async (error?: boolean) => {
44
+ const allFiles = ([] as StorageFile[]).concat(...Object.values(files));
45
+ return await removeStorageFiles(options.storage!, allFiles, error);
46
+ };
47
+
48
+ try {
49
+ for await (const [fieldName, part] of Object.entries(parts)) {
50
+ if (!(part instanceof File)) {
51
+ body[fieldName] = part;
52
+ continue;
53
+ }
54
+
55
+ const fieldOptions = fieldsMap.get(fieldName);
56
+
57
+ if (fieldOptions == null) {
58
+ throw new BadRequestException(
59
+ `Field ${fieldName} doesn't accept files`,
60
+ );
61
+ }
62
+
63
+ if (files[fieldName] == null) {
64
+ files[fieldName] = [];
65
+ }
66
+
67
+ if (files[fieldName].length + 1 > fieldOptions.maxCount) {
68
+ throw new BadRequestException(
69
+ `Field ${fieldName} accepts max ${fieldOptions.maxCount} files`,
70
+ );
71
+ }
72
+
73
+ const file = await options.storage!.handleFile(part, req, fieldName);
74
+
75
+ if (await filterUpload(options, req, file)) {
76
+ files[fieldName].push(file);
77
+ }
78
+ }
79
+ } catch (error) {
80
+ await removeFiles(true);
81
+ throw error;
82
+ }
83
+
84
+ return {
85
+ body,
86
+ files,
87
+ remove: () => removeFiles(),
88
+ };
89
+ };
@@ -0,0 +1 @@
1
+ export { UploadField } from "./file-fields";
@@ -0,0 +1,55 @@
1
+ import { BadRequestException } from '@nestjs/common';
2
+
3
+ import { UploadOptions } from '../options';
4
+ import { StorageFile } from '../../storage';
5
+ import { removeStorageFiles } from '../file';
6
+ import { THonoRequest, getParts } from '../request';
7
+ import { filterUpload } from '../filter';
8
+
9
+ export const handleMultipartMultipleFiles = async (
10
+ req: THonoRequest,
11
+ fieldname: string,
12
+ maxCount: number,
13
+ options: UploadOptions,
14
+ ) => {
15
+ const parts = await getParts(req, options);
16
+ const body: Record<string, any> = {};
17
+
18
+ const files: StorageFile[] = [];
19
+
20
+ const removeFiles = async (error?: boolean) => {
21
+ return await removeStorageFiles(options.storage!, files, error);
22
+ };
23
+
24
+ try {
25
+ for await (const [partFieldName, part] of Object.entries(parts)) {
26
+ if (!(part instanceof File)) {
27
+ body[partFieldName] = part;
28
+ continue;
29
+ }
30
+
31
+ if (partFieldName !== fieldname) {
32
+ throw new BadRequestException(
33
+ `Field ${partFieldName} doesn't accept files`,
34
+ );
35
+ }
36
+
37
+ if (files.length + 1 > maxCount) {
38
+ throw new BadRequestException(
39
+ `Field ${partFieldName} accepts max ${maxCount} files`,
40
+ );
41
+ }
42
+
43
+ const file = await options.storage!.handleFile(part, req, partFieldName);
44
+
45
+ if (await filterUpload(options, req, file)) {
46
+ files.push(file);
47
+ }
48
+ }
49
+ } catch (error) {
50
+ await removeFiles(error);
51
+ throw error;
52
+ }
53
+
54
+ return { body, files, remove: () => removeFiles() };
55
+ };
@@ -0,0 +1,56 @@
1
+ import { BadRequestException } from '@nestjs/common';
2
+
3
+ import { UploadOptions } from '../options';
4
+ import { StorageFile } from '../../storage';
5
+ import { THonoRequest, getParts } from '../request';
6
+ import { filterUpload } from '../filter';
7
+
8
+ export const handleMultipartSingleFile = async (
9
+ req: THonoRequest,
10
+ fieldname: string,
11
+ options: UploadOptions,
12
+ ) => {
13
+ const parts = await getParts(req, options);
14
+ const body: Record<string, any> = {};
15
+
16
+ let file: StorageFile | undefined = undefined;
17
+
18
+ const removeFiles = async (error?: boolean) => {
19
+ if (file == null) return;
20
+ await options.storage!.removeFile(file, error);
21
+ };
22
+
23
+ try {
24
+ for await (const [partFieldName, part] of Object.entries(parts)) {
25
+ if (!(part instanceof File)) {
26
+ body[partFieldName] = part;
27
+ continue;
28
+ }
29
+
30
+ if (partFieldName !== fieldname) {
31
+ throw new BadRequestException(
32
+ `Field ${partFieldName} doesn't accept file`,
33
+ );
34
+ } else if (file != null) {
35
+ throw new BadRequestException(
36
+ `Field ${fieldname} accepts only one file`,
37
+ );
38
+ }
39
+
40
+ const _file = await options.storage!.handleFile(part, req, partFieldName);
41
+
42
+ if (await filterUpload(options, req, _file)) {
43
+ file = _file;
44
+ }
45
+ }
46
+ } catch (error) {
47
+ await removeFiles(true);
48
+ throw error;
49
+ }
50
+
51
+ return {
52
+ body,
53
+ file,
54
+ remove: () => removeFiles(),
55
+ };
56
+ };
@@ -0,0 +1,3 @@
1
+ export * from "./options";
2
+ export * from "./filter";
3
+ export { UploadFilterFile, UploadFilterHandler } from "./filter";
@@ -0,0 +1,29 @@
1
+ import busboy from 'busboy';
2
+ import { DiskStorage, MemoryStorage, Storage } from '../storage';
3
+ import { UploadFilterHandler } from './filter';
4
+
5
+ export type UploadOptions = busboy.BusboyConfig & {
6
+ dest?: string;
7
+ storage?: Storage;
8
+ filter?: UploadFilterHandler;
9
+ };
10
+
11
+ export const DEFAULT_UPLOAD_OPTIONS: Partial<UploadOptions> = {
12
+ storage: new MemoryStorage(),
13
+ };
14
+
15
+ export const transformUploadOptions = (opts?: UploadOptions) => {
16
+ if (opts == null) return DEFAULT_UPLOAD_OPTIONS;
17
+
18
+ if (opts.dest != null) {
19
+ return {
20
+ ...opts,
21
+ storage: new DiskStorage({
22
+ dest: opts.dest,
23
+ ...opts.storage?.options,
24
+ }),
25
+ };
26
+ }
27
+
28
+ return { ...DEFAULT_UPLOAD_OPTIONS, ...opts };
29
+ };
@@ -0,0 +1,44 @@
1
+ import { BadRequestException } from "@nestjs/common";
2
+ import { HttpArgumentsHost } from "@nestjs/common/interfaces";
3
+
4
+ import { UploadOptions } from "./options";
5
+ import { StorageFile } from "../storage";
6
+ import { MultipartFile } from "./file";
7
+ import { HonoRequest } from "hono";
8
+
9
+ export type THonoRequest = HonoRequest & {
10
+ files: Record<string, File[]>;
11
+ body: Record<string, any>;
12
+ storageFile?: StorageFile;
13
+ storageFiles?: StorageFile[] | Record<string, StorageFile[]>;
14
+ };
15
+
16
+ export const getMultipartRequest = (ctx: HttpArgumentsHost) => {
17
+ const req = ctx.getRequest<THonoRequest>();
18
+
19
+ if (!req.header("content-type")?.startsWith("multipart/form-data")) {
20
+ throw new BadRequestException("Not a multipart request");
21
+ }
22
+
23
+ return req;
24
+ };
25
+
26
+ export const getParts = async (req: THonoRequest, options: UploadOptions) => {
27
+ const files = await req.parseBody({ all: true });
28
+
29
+ for (const [key, file] of Object.entries(files)) {
30
+ if (
31
+ file instanceof File &&
32
+ options?.limits?.fileSize &&
33
+ file.size > options.limits.fileSize
34
+ ) {
35
+ throw new BadRequestException(
36
+ `File ${key} is too large. Maximum size is ${options.limits.fileSize} bytes`
37
+ );
38
+ }
39
+ }
40
+
41
+ return files;
42
+ };
43
+
44
+ export type MultipartsIterator = AsyncIterableIterator<MultipartFile>;
@@ -0,0 +1,107 @@
1
+ import { tmpdir } from 'os';
2
+ import { createWriteStream } from 'fs';
3
+ import { mkdir, unlink } from 'fs/promises';
4
+ import { join } from 'path';
5
+
6
+ import { StorageFile, Storage } from './storage';
7
+ import { getUniqueFilename, pathExists } from '../fs';
8
+ import { pump } from '../stream';
9
+ import { HonoRequest } from 'hono';
10
+ import { Readable } from 'node:stream';
11
+
12
+ export interface DiskStorageFile extends StorageFile {
13
+ dest: string;
14
+ filename: string;
15
+ path: string;
16
+ }
17
+
18
+ type DiskStorageOptionHandler =
19
+ | ((file: File, req: HonoRequest) => Promise<string> | string)
20
+ | string;
21
+
22
+ export interface DiskStorageOptions {
23
+ dest?: DiskStorageOptionHandler;
24
+ filename?: DiskStorageOptionHandler;
25
+ removeAfter?: boolean;
26
+ }
27
+
28
+ const excecuteStorageHandler = (
29
+ file: File,
30
+ req: HonoRequest,
31
+ obj?: DiskStorageOptionHandler,
32
+ ) => {
33
+ if (typeof obj === 'function') {
34
+ return obj(file, req);
35
+ }
36
+
37
+ if (obj != null) return obj;
38
+
39
+ return null;
40
+ };
41
+
42
+ const ENV_TESTS_STORAGE_TMP_PATH = process.env.__TESTS_TMP_PATH__;
43
+ export class DiskStorage
44
+ implements Storage<DiskStorageFile, DiskStorageOptions>
45
+ {
46
+ public readonly options?: DiskStorageOptions;
47
+
48
+ constructor(options?: DiskStorageOptions) {
49
+ this.options = options;
50
+
51
+ if (ENV_TESTS_STORAGE_TMP_PATH != null) {
52
+ this.options = { ...this.options, dest: ENV_TESTS_STORAGE_TMP_PATH };
53
+ }
54
+ }
55
+
56
+ public async handleFile(file: File, req: HonoRequest, fieldName: string) {
57
+ const filename = await this.getFilename(file, req, this.options?.filename);
58
+ const dest = await this.getFileDestination(file, req, this.options?.dest);
59
+
60
+ if (!(await pathExists(dest))) {
61
+ await mkdir(dest, { recursive: true });
62
+ }
63
+
64
+ const path = join(dest, filename);
65
+ const stream = createWriteStream(path);
66
+
67
+ const buffer = await file.arrayBuffer();
68
+ const readableStream = Readable.from(Buffer.from(buffer));
69
+
70
+ await pump(readableStream, stream);
71
+
72
+ return {
73
+ size: stream.bytesWritten,
74
+ dest,
75
+ filename,
76
+ originalFilename: file.name,
77
+ path,
78
+ mimetype: file.type,
79
+ encoding: 'utf-8',
80
+ fieldname: fieldName,
81
+ };
82
+ }
83
+
84
+ public async removeFile(file: DiskStorageFile, force?: boolean) {
85
+ if (!this.options?.removeAfter && !force) return;
86
+
87
+ await unlink(file.path);
88
+ }
89
+
90
+ protected async getFilename(
91
+ file: File,
92
+ req: HonoRequest,
93
+ obj?: DiskStorageOptionHandler,
94
+ ): Promise<string> {
95
+ return (
96
+ excecuteStorageHandler(file, req, obj) ?? getUniqueFilename(file.name)
97
+ );
98
+ }
99
+
100
+ protected async getFileDestination(
101
+ file: File,
102
+ req: HonoRequest,
103
+ obj?: DiskStorageOptionHandler,
104
+ ): Promise<string> {
105
+ return excecuteStorageHandler(file, req, obj) ?? tmpdir();
106
+ }
107
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./disk-storage";
2
+ export * from "./memory-storage";
3
+ export * from "./storage";
@@ -0,0 +1,25 @@
1
+ import { StorageFile, Storage } from './storage';
2
+ import { HonoRequest } from 'hono';
3
+
4
+ export interface MemoryStorageFile extends StorageFile {
5
+ buffer: Buffer;
6
+ }
7
+
8
+ export class MemoryStorage implements Storage<MemoryStorageFile> {
9
+ public async handleFile(file: File, _req: HonoRequest, fieldName: string) {
10
+ const buffer = await file.arrayBuffer().then(Buffer.from);
11
+
12
+ return {
13
+ buffer,
14
+ size: buffer.length,
15
+ encoding: 'utf-8',
16
+ mimetype: file.type,
17
+ fieldname: fieldName,
18
+ originalFilename: file.name,
19
+ };
20
+ }
21
+
22
+ public async removeFile(file: MemoryStorageFile) {
23
+ delete file.buffer;
24
+ }
25
+ }
@@ -0,0 +1,15 @@
1
+ import { HonoRequest } from 'hono';
2
+
3
+ export interface StorageFile {
4
+ size: number;
5
+ fieldname: string;
6
+ encoding: string;
7
+ mimetype: string;
8
+ originalFilename: string;
9
+ }
10
+
11
+ export interface Storage<T extends StorageFile = StorageFile, K = any> {
12
+ handleFile: (file: File, req: HonoRequest, fieldName: string) => Promise<T>;
13
+ removeFile: (file: T, force?: boolean) => Promise<void> | void;
14
+ options?: K;
15
+ }
@@ -0,0 +1,4 @@
1
+ import { promisify } from 'node:util';
2
+ import { pipeline } from 'node:stream';
3
+
4
+ export const pump = promisify(pipeline);