@kiyasov/platform-hono 1.6.1 → 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/.claude/settings.local.json +7 -1
- package/dist/cjs/src/adapters/hono-adapter.d.ts +1 -0
- package/dist/cjs/src/adapters/hono-adapter.js +14 -3
- package/dist/cjs/src/adapters/hono-adapter.js.map +1 -1
- package/dist/cjs/src/drivers/graphQLUpload/Upload.d.ts +5 -7
- package/dist/cjs/src/drivers/graphQLUpload/Upload.js.map +1 -1
- package/dist/cjs/src/drivers/graphQLUpload/fs-capacitor.d.ts +20 -8
- package/dist/cjs/src/drivers/graphQLUpload/fs-capacitor.js +111 -58
- package/dist/cjs/src/drivers/graphQLUpload/fs-capacitor.js.map +1 -1
- package/dist/cjs/src/drivers/graphQLUpload/index.d.ts +9 -3
- package/dist/cjs/src/drivers/graphQLUpload/index.js +21 -3
- package/dist/cjs/src/drivers/graphQLUpload/index.js.map +1 -1
- package/dist/cjs/src/drivers/graphQLUpload/processRequest.d.ts +8 -1
- package/dist/cjs/src/drivers/graphQLUpload/processRequest.js +43 -37
- package/dist/cjs/src/drivers/graphQLUpload/processRequest.js.map +1 -1
- package/dist/cjs/src/drivers/graphQLUpload/storage/capacitor-storage.d.ts +15 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/capacitor-storage.js +47 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/capacitor-storage.js.map +1 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/index.d.ts +3 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/index.js +20 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/index.js.map +1 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/memory-storage.d.ts +13 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/memory-storage.js +31 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/memory-storage.js.map +1 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/storage.d.ts +17 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/storage.js +3 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/storage.js.map +1 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/file.d.ts +6 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/file.js +62 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/file.js.map +1 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/index.d.ts +2 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/index.js +19 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/index.js.map +1 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/validators.d.ts +18 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/validators.js +171 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/validators.js.map +1 -0
- package/dist/cjs/src/multer/index.d.ts +1 -0
- package/dist/cjs/src/multer/index.js +1 -0
- package/dist/cjs/src/multer/index.js.map +1 -1
- package/dist/cjs/src/multer/interceptors/any-files-interceptor.d.ts +2 -2
- package/dist/cjs/src/multer/interceptors/any-files-interceptor.js +6 -23
- package/dist/cjs/src/multer/interceptors/any-files-interceptor.js.map +1 -1
- package/dist/cjs/src/multer/interceptors/base-interceptor.d.ts +6 -0
- package/dist/cjs/src/multer/interceptors/base-interceptor.js +26 -0
- package/dist/cjs/src/multer/interceptors/base-interceptor.js.map +1 -0
- package/dist/cjs/src/multer/interceptors/file-fields-interceptor.d.ts +2 -2
- package/dist/cjs/src/multer/interceptors/file-fields-interceptor.js +7 -24
- package/dist/cjs/src/multer/interceptors/file-fields-interceptor.js.map +1 -1
- package/dist/cjs/src/multer/interceptors/file-interceptor.d.ts +2 -2
- package/dist/cjs/src/multer/interceptors/file-interceptor.js +6 -23
- package/dist/cjs/src/multer/interceptors/file-interceptor.js.map +1 -1
- package/dist/cjs/src/multer/interceptors/files-interceptor.d.ts +2 -2
- package/dist/cjs/src/multer/interceptors/files-interceptor.js +6 -23
- package/dist/cjs/src/multer/interceptors/files-interceptor.js.map +1 -1
- package/dist/cjs/src/multer/interceptors/index.d.ts +1 -0
- package/dist/cjs/src/multer/interceptors/index.js +1 -0
- package/dist/cjs/src/multer/interceptors/index.js.map +1 -1
- package/dist/cjs/src/multer/multipart/handlers/any-files.d.ts +2 -8
- package/dist/cjs/src/multer/multipart/handlers/any-files.js +12 -25
- package/dist/cjs/src/multer/multipart/handlers/any-files.js.map +1 -1
- package/dist/cjs/src/multer/multipart/handlers/base-handler.d.ts +42 -0
- package/dist/cjs/src/multer/multipart/handlers/base-handler.js +106 -0
- package/dist/cjs/src/multer/multipart/handlers/base-handler.js.map +1 -0
- package/dist/cjs/src/multer/multipart/handlers/file-fields.d.ts +3 -10
- package/dist/cjs/src/multer/multipart/handlers/file-fields.js +19 -33
- package/dist/cjs/src/multer/multipart/handlers/file-fields.js.map +1 -1
- package/dist/cjs/src/multer/multipart/handlers/index.d.ts +6 -1
- package/dist/cjs/src/multer/multipart/handlers/index.js +13 -0
- package/dist/cjs/src/multer/multipart/handlers/index.js.map +1 -1
- package/dist/cjs/src/multer/multipart/handlers/multiple-files.d.ts +2 -8
- package/dist/cjs/src/multer/multipart/handlers/multiple-files.js +18 -36
- package/dist/cjs/src/multer/multipart/handlers/multiple-files.js.map +1 -1
- package/dist/cjs/src/multer/multipart/handlers/single-file.d.ts +2 -8
- package/dist/cjs/src/multer/multipart/handlers/single-file.js +11 -33
- package/dist/cjs/src/multer/multipart/handlers/single-file.js.map +1 -1
- package/dist/cjs/src/multer/multipart/index.d.ts +1 -1
- package/dist/cjs/src/multer/multipart/options.d.ts +10 -16
- package/dist/cjs/src/multer/multipart/options.js.map +1 -1
- package/dist/cjs/src/multer/multipart/request.js +14 -3
- package/dist/cjs/src/multer/multipart/request.js.map +1 -1
- package/dist/cjs/src/multer/storage/disk-storage.d.ts +2 -1
- package/dist/cjs/src/multer/storage/disk-storage.js +2 -1
- package/dist/cjs/src/multer/storage/disk-storage.js.map +1 -1
- package/dist/cjs/src/multer/storage/memory-storage.d.ts +2 -11
- package/dist/cjs/src/multer/storage/memory-storage.js +6 -4
- package/dist/cjs/src/multer/storage/memory-storage.js.map +1 -1
- package/dist/cjs/src/multer/storage/storage.d.ts +6 -5
- package/dist/cjs/src/multer/utils/file.d.ts +6 -0
- package/dist/cjs/src/multer/utils/file.js +62 -0
- package/dist/cjs/src/multer/utils/file.js.map +1 -0
- package/dist/cjs/src/multer/utils/index.d.ts +2 -0
- package/dist/cjs/src/multer/utils/index.js +19 -0
- package/dist/cjs/src/multer/utils/index.js.map +1 -0
- package/dist/cjs/src/multer/utils/validators.d.ts +18 -0
- package/dist/cjs/src/multer/utils/validators.js +171 -0
- package/dist/cjs/src/multer/utils/validators.js.map +1 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/adapters/hono-adapter.d.ts +1 -0
- package/dist/esm/src/adapters/hono-adapter.js +14 -3
- package/dist/esm/src/adapters/hono-adapter.js.map +1 -1
- package/dist/esm/src/drivers/graphQLUpload/Upload.d.ts +5 -7
- package/dist/esm/src/drivers/graphQLUpload/Upload.js.map +1 -1
- package/dist/esm/src/drivers/graphQLUpload/fs-capacitor.d.ts +20 -8
- package/dist/esm/src/drivers/graphQLUpload/fs-capacitor.js +112 -59
- package/dist/esm/src/drivers/graphQLUpload/fs-capacitor.js.map +1 -1
- package/dist/esm/src/drivers/graphQLUpload/index.d.ts +9 -3
- package/dist/esm/src/drivers/graphQLUpload/index.js +7 -3
- package/dist/esm/src/drivers/graphQLUpload/index.js.map +1 -1
- package/dist/esm/src/drivers/graphQLUpload/processRequest.d.ts +8 -1
- package/dist/esm/src/drivers/graphQLUpload/processRequest.js +42 -36
- package/dist/esm/src/drivers/graphQLUpload/processRequest.js.map +1 -1
- package/dist/esm/src/drivers/graphQLUpload/storage/capacitor-storage.d.ts +15 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/capacitor-storage.js +43 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/capacitor-storage.js.map +1 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/index.d.ts +3 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/index.js +4 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/index.js.map +1 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/memory-storage.d.ts +13 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/memory-storage.js +27 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/memory-storage.js.map +1 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/storage.d.ts +17 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/storage.js +2 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/storage.js.map +1 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/file.d.ts +6 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/file.js +54 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/file.js.map +1 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/index.d.ts +2 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/index.js +3 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/index.js.map +1 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/validators.d.ts +18 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/validators.js +167 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/validators.js.map +1 -0
- package/dist/esm/src/multer/index.d.ts +1 -0
- package/dist/esm/src/multer/index.js +1 -0
- package/dist/esm/src/multer/index.js.map +1 -1
- package/dist/esm/src/multer/interceptors/any-files-interceptor.d.ts +2 -2
- package/dist/esm/src/multer/interceptors/any-files-interceptor.js +6 -23
- package/dist/esm/src/multer/interceptors/any-files-interceptor.js.map +1 -1
- package/dist/esm/src/multer/interceptors/base-interceptor.d.ts +6 -0
- package/dist/esm/src/multer/interceptors/base-interceptor.js +23 -0
- package/dist/esm/src/multer/interceptors/base-interceptor.js.map +1 -0
- package/dist/esm/src/multer/interceptors/file-fields-interceptor.d.ts +2 -2
- package/dist/esm/src/multer/interceptors/file-fields-interceptor.js +7 -24
- package/dist/esm/src/multer/interceptors/file-fields-interceptor.js.map +1 -1
- package/dist/esm/src/multer/interceptors/file-interceptor.d.ts +2 -2
- package/dist/esm/src/multer/interceptors/file-interceptor.js +6 -23
- package/dist/esm/src/multer/interceptors/file-interceptor.js.map +1 -1
- package/dist/esm/src/multer/interceptors/files-interceptor.d.ts +2 -2
- package/dist/esm/src/multer/interceptors/files-interceptor.js +6 -23
- package/dist/esm/src/multer/interceptors/files-interceptor.js.map +1 -1
- package/dist/esm/src/multer/interceptors/index.d.ts +1 -0
- package/dist/esm/src/multer/interceptors/index.js +1 -0
- package/dist/esm/src/multer/interceptors/index.js.map +1 -1
- package/dist/esm/src/multer/multipart/handlers/any-files.d.ts +2 -8
- package/dist/esm/src/multer/multipart/handlers/any-files.js +12 -25
- package/dist/esm/src/multer/multipart/handlers/any-files.js.map +1 -1
- package/dist/esm/src/multer/multipart/handlers/base-handler.d.ts +42 -0
- package/dist/esm/src/multer/multipart/handlers/base-handler.js +102 -0
- package/dist/esm/src/multer/multipart/handlers/base-handler.js.map +1 -0
- package/dist/esm/src/multer/multipart/handlers/file-fields.d.ts +3 -10
- package/dist/esm/src/multer/multipart/handlers/file-fields.js +19 -33
- package/dist/esm/src/multer/multipart/handlers/file-fields.js.map +1 -1
- package/dist/esm/src/multer/multipart/handlers/index.d.ts +6 -1
- package/dist/esm/src/multer/multipart/handlers/index.js +6 -1
- package/dist/esm/src/multer/multipart/handlers/index.js.map +1 -1
- package/dist/esm/src/multer/multipart/handlers/multiple-files.d.ts +2 -8
- package/dist/esm/src/multer/multipart/handlers/multiple-files.js +18 -36
- package/dist/esm/src/multer/multipart/handlers/multiple-files.js.map +1 -1
- package/dist/esm/src/multer/multipart/handlers/single-file.d.ts +2 -8
- package/dist/esm/src/multer/multipart/handlers/single-file.js +11 -33
- package/dist/esm/src/multer/multipart/handlers/single-file.js.map +1 -1
- package/dist/esm/src/multer/multipart/index.d.ts +1 -1
- package/dist/esm/src/multer/multipart/options.d.ts +10 -16
- package/dist/esm/src/multer/multipart/options.js.map +1 -1
- package/dist/esm/src/multer/multipart/request.js +14 -3
- package/dist/esm/src/multer/multipart/request.js.map +1 -1
- package/dist/esm/src/multer/storage/disk-storage.d.ts +2 -1
- package/dist/esm/src/multer/storage/disk-storage.js +2 -1
- package/dist/esm/src/multer/storage/disk-storage.js.map +1 -1
- package/dist/esm/src/multer/storage/memory-storage.d.ts +2 -11
- package/dist/esm/src/multer/storage/memory-storage.js +6 -4
- package/dist/esm/src/multer/storage/memory-storage.js.map +1 -1
- package/dist/esm/src/multer/storage/storage.d.ts +6 -5
- package/dist/esm/src/multer/utils/file.d.ts +6 -0
- package/dist/esm/src/multer/utils/file.js +54 -0
- package/dist/esm/src/multer/utils/file.js.map +1 -0
- package/dist/esm/src/multer/utils/index.d.ts +2 -0
- package/dist/esm/src/multer/utils/index.js +3 -0
- package/dist/esm/src/multer/utils/index.js.map +1 -0
- package/dist/esm/src/multer/utils/validators.d.ts +18 -0
- package/dist/esm/src/multer/utils/validators.js +167 -0
- package/dist/esm/src/multer/utils/validators.js.map +1 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +6 -4
- package/src/adapters/hono-adapter.ts +18 -3
- package/src/drivers/graphQLUpload/Upload.ts +21 -14
- package/src/drivers/graphQLUpload/fs-capacitor.ts +240 -116
- package/src/drivers/graphQLUpload/index.ts +37 -3
- package/src/drivers/graphQLUpload/processRequest.ts +92 -38
- package/src/drivers/graphQLUpload/storage/capacitor-storage.ts +86 -0
- package/src/drivers/graphQLUpload/storage/index.ts +3 -0
- package/src/drivers/graphQLUpload/storage/memory-storage.ts +62 -0
- package/src/drivers/graphQLUpload/storage/storage.ts +52 -0
- package/src/drivers/graphQLUpload/utils/file.ts +109 -0
- package/src/drivers/graphQLUpload/utils/index.ts +2 -0
- package/src/drivers/graphQLUpload/utils/validators.ts +219 -0
- package/src/multer/index.ts +1 -0
- package/src/multer/interceptors/any-files-interceptor.ts +12 -43
- package/src/multer/interceptors/base-interceptor.ts +54 -0
- package/src/multer/interceptors/file-fields-interceptor.ts +14 -48
- package/src/multer/interceptors/file-interceptor.ts +12 -44
- package/src/multer/interceptors/files-interceptor.ts +13 -45
- package/src/multer/interceptors/index.ts +1 -0
- package/src/multer/multipart/handlers/any-files.ts +14 -32
- package/src/multer/multipart/handlers/base-handler.ts +204 -0
- package/src/multer/multipart/handlers/file-fields.ts +29 -57
- package/src/multer/multipart/handlers/index.ts +11 -1
- package/src/multer/multipart/handlers/multiple-files.ts +23 -54
- package/src/multer/multipart/handlers/single-file.ts +14 -47
- package/src/multer/multipart/index.ts +1 -1
- package/src/multer/multipart/options.ts +26 -8
- package/src/multer/multipart/request.ts +19 -3
- package/src/multer/storage/disk-storage.ts +2 -1
- package/src/multer/storage/memory-storage.ts +13 -6
- package/src/multer/storage/storage.ts +12 -5
- package/src/multer/utils/file.ts +109 -0
- package/src/multer/utils/index.ts +2 -0
- package/src/multer/utils/validators.ts +219 -0
- package/test/README.md +247 -0
- package/test/graphql-upload.test.ts +509 -0
- package/test/helpers.ts +70 -0
- package/test/integration.test.ts +197 -0
- package/test/interceptors-e2e.test.ts +362 -0
- package/test/multipart-upload.test.ts +354 -0
- package/test/smoke.test.ts +227 -0
|
@@ -1,47 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CallHandler,
|
|
3
|
-
ExecutionContext,
|
|
4
|
-
mixin,
|
|
5
|
-
NestInterceptor,
|
|
6
|
-
Type,
|
|
7
|
-
} from '@nestjs/common';
|
|
8
|
-
import { finalize } from 'rxjs';
|
|
9
|
-
|
|
10
1
|
import { handleMultipartAnyFiles } from '../multipart/handlers/any-files';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
2
|
+
import { UploadOptions } from '../multipart/options';
|
|
3
|
+
import { createInterceptor } from './base-interceptor';
|
|
13
4
|
|
|
14
5
|
export function AnyFilesInterceptor(
|
|
15
|
-
|
|
16
|
-
):
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const ctx = context.switchToHttp();
|
|
26
|
-
const req = getMultipartRequest(ctx);
|
|
27
|
-
|
|
28
|
-
if (!req.header('content-type')?.startsWith('multipart/form-data')) {
|
|
29
|
-
return next.handle();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const { body, files, remove } = await handleMultipartAnyFiles(
|
|
33
|
-
req,
|
|
34
|
-
this.options,
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
req.body = body;
|
|
38
|
-
req.storageFiles = files;
|
|
39
|
-
|
|
40
|
-
return next.handle().pipe(finalize(remove));
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const Interceptor = mixin(MixinInterceptor);
|
|
45
|
-
|
|
46
|
-
return Interceptor;
|
|
6
|
+
localOptions?: UploadOptions,
|
|
7
|
+
): ReturnType<typeof createInterceptor> {
|
|
8
|
+
return createInterceptor(
|
|
9
|
+
localOptions ?? {},
|
|
10
|
+
(req, options) => handleMultipartAnyFiles(req, options),
|
|
11
|
+
(req, result) => {
|
|
12
|
+
req.body = result.body;
|
|
13
|
+
req.storageFiles = result.files;
|
|
14
|
+
},
|
|
15
|
+
);
|
|
47
16
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CallHandler,
|
|
3
|
+
ExecutionContext,
|
|
4
|
+
mixin,
|
|
5
|
+
NestInterceptor,
|
|
6
|
+
Type,
|
|
7
|
+
} from '@nestjs/common';
|
|
8
|
+
import { finalize } from 'rxjs';
|
|
9
|
+
import { Observable } from 'rxjs/internal/Observable';
|
|
10
|
+
|
|
11
|
+
import { FileProcessResult } from '../multipart/handlers/base-handler';
|
|
12
|
+
import { transformUploadOptions, UploadOptions } from '../multipart/options';
|
|
13
|
+
import { getMultipartRequest } from '../multipart/request';
|
|
14
|
+
|
|
15
|
+
export type HandlerFunction<T extends FileProcessResult> = (
|
|
16
|
+
req: ReturnType<typeof getMultipartRequest>,
|
|
17
|
+
options: UploadOptions,
|
|
18
|
+
) => Promise<T>;
|
|
19
|
+
|
|
20
|
+
export function createInterceptor<T extends FileProcessResult>(
|
|
21
|
+
rawOptions: UploadOptions,
|
|
22
|
+
handlerFn: HandlerFunction<T>,
|
|
23
|
+
resultProcessor: (
|
|
24
|
+
req: ReturnType<typeof getMultipartRequest>,
|
|
25
|
+
result: T,
|
|
26
|
+
) => void,
|
|
27
|
+
): Type<NestInterceptor> {
|
|
28
|
+
class MixinInterceptor implements NestInterceptor {
|
|
29
|
+
private readonly options: UploadOptions;
|
|
30
|
+
|
|
31
|
+
constructor() {
|
|
32
|
+
this.options = transformUploadOptions(rawOptions);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async intercept(
|
|
36
|
+
context: ExecutionContext,
|
|
37
|
+
next: CallHandler,
|
|
38
|
+
): Promise<Observable<unknown>> {
|
|
39
|
+
const ctx = context.switchToHttp();
|
|
40
|
+
const req = getMultipartRequest(ctx);
|
|
41
|
+
|
|
42
|
+
if (!req.header('content-type')?.startsWith('multipart/form-data')) {
|
|
43
|
+
return next.handle();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = await handlerFn(req, this.options);
|
|
47
|
+
resultProcessor(req, result);
|
|
48
|
+
|
|
49
|
+
return next.handle().pipe(finalize(result.remove));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return mixin(MixinInterceptor);
|
|
54
|
+
}
|
|
@@ -1,57 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CallHandler,
|
|
3
|
-
ExecutionContext,
|
|
4
|
-
mixin,
|
|
5
|
-
NestInterceptor,
|
|
6
|
-
Type,
|
|
7
|
-
} from '@nestjs/common';
|
|
8
|
-
import { finalize } from 'rxjs';
|
|
9
|
-
|
|
10
1
|
import {
|
|
11
2
|
handleMultipartFileFields,
|
|
12
3
|
UploadField,
|
|
13
|
-
UploadFieldMapEntry,
|
|
14
4
|
uploadFieldsToMap,
|
|
15
5
|
} from '../multipart/handlers/file-fields';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
6
|
+
import { UploadOptions } from '../multipart/options';
|
|
7
|
+
import { createInterceptor } from './base-interceptor';
|
|
18
8
|
|
|
19
9
|
export function FileFieldsInterceptor(
|
|
20
10
|
uploadFields: UploadField[],
|
|
21
|
-
|
|
22
|
-
):
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async intercept(context: ExecutionContext, next: CallHandler) {
|
|
34
|
-
const ctx = context.switchToHttp();
|
|
35
|
-
const req = getMultipartRequest(ctx);
|
|
36
|
-
|
|
37
|
-
if (!req.header('content-type')?.startsWith('multipart/form-data')) {
|
|
38
|
-
return next.handle();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const { body, files, remove } = await handleMultipartFileFields(
|
|
42
|
-
req,
|
|
43
|
-
this.fieldsMap,
|
|
44
|
-
this.options,
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
req.body = body;
|
|
48
|
-
req.storageFiles = files;
|
|
49
|
-
|
|
50
|
-
return next.handle().pipe(finalize(remove));
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const Interceptor = mixin(MixinInterceptor);
|
|
55
|
-
|
|
56
|
-
return Interceptor;
|
|
11
|
+
localOptions?: UploadOptions,
|
|
12
|
+
): ReturnType<typeof createInterceptor> {
|
|
13
|
+
const fieldsMap = uploadFieldsToMap(uploadFields);
|
|
14
|
+
|
|
15
|
+
return createInterceptor(
|
|
16
|
+
localOptions ?? {},
|
|
17
|
+
(req, options) => handleMultipartFileFields(req, fieldsMap, options),
|
|
18
|
+
(req, result) => {
|
|
19
|
+
req.body = result.body;
|
|
20
|
+
req.storageFiles = result.files;
|
|
21
|
+
},
|
|
22
|
+
);
|
|
57
23
|
}
|
|
@@ -1,49 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CallHandler,
|
|
3
|
-
ExecutionContext,
|
|
4
|
-
mixin,
|
|
5
|
-
NestInterceptor,
|
|
6
|
-
Type,
|
|
7
|
-
} from '@nestjs/common';
|
|
8
|
-
import { finalize } from 'rxjs';
|
|
9
|
-
|
|
10
1
|
import { handleMultipartSingleFile } from '../multipart/handlers/single-file';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
2
|
+
import { UploadOptions } from '../multipart/options';
|
|
3
|
+
import { createInterceptor } from './base-interceptor';
|
|
13
4
|
|
|
14
5
|
export function FileInterceptor(
|
|
15
6
|
fieldname: string,
|
|
16
|
-
|
|
17
|
-
):
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const ctx = context.switchToHttp();
|
|
27
|
-
const req = getMultipartRequest(ctx);
|
|
28
|
-
|
|
29
|
-
if (!req.header('content-type')?.startsWith('multipart/form-data')) {
|
|
30
|
-
return next.handle();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const { file, body, remove } = await handleMultipartSingleFile(
|
|
34
|
-
req,
|
|
35
|
-
fieldname,
|
|
36
|
-
this.options,
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
req.body = body;
|
|
40
|
-
req.storageFile = file;
|
|
41
|
-
|
|
42
|
-
return next.handle().pipe(finalize(remove));
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const Interceptor = mixin(MixinInterceptor);
|
|
47
|
-
|
|
48
|
-
return Interceptor;
|
|
7
|
+
localOptions?: UploadOptions,
|
|
8
|
+
): ReturnType<typeof createInterceptor> {
|
|
9
|
+
return createInterceptor(
|
|
10
|
+
localOptions ?? {},
|
|
11
|
+
(req, options) => handleMultipartSingleFile(req, fieldname, options),
|
|
12
|
+
(req, result) => {
|
|
13
|
+
req.body = result.body;
|
|
14
|
+
req.storageFile = result.file;
|
|
15
|
+
},
|
|
16
|
+
);
|
|
49
17
|
}
|
|
@@ -1,51 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CallHandler,
|
|
3
|
-
ExecutionContext,
|
|
4
|
-
mixin,
|
|
5
|
-
NestInterceptor,
|
|
6
|
-
Type,
|
|
7
|
-
} from '@nestjs/common';
|
|
8
|
-
import { finalize } from 'rxjs';
|
|
9
|
-
|
|
10
1
|
import { handleMultipartMultipleFiles } from '../multipart/handlers/multiple-files';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
2
|
+
import { UploadOptions } from '../multipart/options';
|
|
3
|
+
import { createInterceptor } from './base-interceptor';
|
|
13
4
|
|
|
14
5
|
export function FilesInterceptor(
|
|
15
6
|
fieldname: string,
|
|
16
7
|
maxCount = 1,
|
|
17
|
-
|
|
18
|
-
):
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const req = getMultipartRequest(ctx);
|
|
29
|
-
|
|
30
|
-
if (!req.header('content-type')?.startsWith('multipart/form-data')) {
|
|
31
|
-
return next.handle();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const { body, files, remove } = await handleMultipartMultipleFiles(
|
|
35
|
-
req,
|
|
36
|
-
fieldname,
|
|
37
|
-
maxCount,
|
|
38
|
-
this.options,
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
req.body = body;
|
|
42
|
-
req.storageFiles = files;
|
|
43
|
-
|
|
44
|
-
return next.handle().pipe(finalize(remove));
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const Interceptor = mixin(MixinInterceptor);
|
|
49
|
-
|
|
50
|
-
return Interceptor;
|
|
8
|
+
localOptions?: UploadOptions,
|
|
9
|
+
): ReturnType<typeof createInterceptor> {
|
|
10
|
+
return createInterceptor(
|
|
11
|
+
localOptions ?? {},
|
|
12
|
+
(req, options) =>
|
|
13
|
+
handleMultipartMultipleFiles(req, fieldname, maxCount, options),
|
|
14
|
+
(req, result) => {
|
|
15
|
+
req.body = result.body;
|
|
16
|
+
req.storageFiles = result.files;
|
|
17
|
+
},
|
|
18
|
+
);
|
|
51
19
|
}
|
|
@@ -1,41 +1,23 @@
|
|
|
1
|
-
import { BodyData } from 'hono/utils/body';
|
|
2
|
-
|
|
3
|
-
import { StorageFile } from '../../storage';
|
|
4
|
-
import { removeStorageFiles } from '../file';
|
|
5
|
-
import { filterUpload } from '../filter';
|
|
6
1
|
import { UploadOptions } from '../options';
|
|
7
|
-
import { THonoRequest
|
|
2
|
+
import { THonoRequest } from '../request';
|
|
3
|
+
import { FileHandler, MultipleFilesResult } from './base-handler';
|
|
8
4
|
|
|
9
5
|
export const handleMultipartAnyFiles = async (
|
|
10
6
|
req: THonoRequest,
|
|
11
7
|
options: UploadOptions,
|
|
12
|
-
) => {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const body: BodyData = {};
|
|
8
|
+
): Promise<MultipleFilesResult> => {
|
|
9
|
+
const handler = new FileHandler(req, options);
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
const file = await options.storage!.handleFile(part, req, partFieldName);
|
|
30
|
-
|
|
31
|
-
if (await filterUpload(options, req, file)) {
|
|
32
|
-
files.push(file);
|
|
33
|
-
}
|
|
11
|
+
await handler.process(async (fieldName, part) => {
|
|
12
|
+
const storageFile = await handler.handleSingleFile(fieldName, part);
|
|
13
|
+
if (storageFile) {
|
|
14
|
+
handler.addFile(fieldName, storageFile);
|
|
34
15
|
}
|
|
35
|
-
}
|
|
36
|
-
await removeFiles(true);
|
|
37
|
-
throw error;
|
|
38
|
-
}
|
|
16
|
+
});
|
|
39
17
|
|
|
40
|
-
return {
|
|
18
|
+
return {
|
|
19
|
+
body: handler.getBody(),
|
|
20
|
+
files: handler.getFiles(),
|
|
21
|
+
remove: handler.createRemoveFunction(),
|
|
22
|
+
};
|
|
41
23
|
};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { BadRequestException } from '@nestjs/common';
|
|
2
|
+
import { BodyData } from 'hono/utils/body';
|
|
3
|
+
|
|
4
|
+
import { StorageFile } from '../../storage/storage';
|
|
5
|
+
import { removeStorageFiles } from '../file';
|
|
6
|
+
import { filterUpload } from '../filter';
|
|
7
|
+
import { UploadOptions } from '../options';
|
|
8
|
+
import { THonoRequest, getParts } from '../request';
|
|
9
|
+
|
|
10
|
+
export interface ProcessedFile {
|
|
11
|
+
file: StorageFile;
|
|
12
|
+
fieldName: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface FileProcessResult {
|
|
16
|
+
body: BodyData;
|
|
17
|
+
remove: () => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SingleFileResult extends FileProcessResult {
|
|
21
|
+
file?: StorageFile;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface MultipleFilesResult extends FileProcessResult {
|
|
25
|
+
files: StorageFile[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface FileFieldsResult extends FileProcessResult {
|
|
29
|
+
files: Record<string, StorageFile[]>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class FileHandler {
|
|
33
|
+
private readonly body: BodyData = {};
|
|
34
|
+
private readonly files: StorageFile[] = [];
|
|
35
|
+
private readonly filesByField: Record<string, StorageFile[]> = {};
|
|
36
|
+
private processed = false;
|
|
37
|
+
private cleanedUp = false;
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
private readonly req: THonoRequest,
|
|
41
|
+
private readonly options: UploadOptions,
|
|
42
|
+
) {}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Processes all parts in the multipart request.
|
|
46
|
+
*/
|
|
47
|
+
async process(
|
|
48
|
+
processor: (fieldName: string, file: File) => Promise<void>,
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
if (this.processed) {
|
|
51
|
+
throw new Error('Request already processed');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.processed = true;
|
|
55
|
+
const parts = getParts(this.req, this.options);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
for await (const [fieldName, part] of Object.entries(parts)) {
|
|
59
|
+
// Handle array of files (for multiple file uploads with same field name)
|
|
60
|
+
if (Array.isArray(part) && part.every((item) => item instanceof File)) {
|
|
61
|
+
for (const file of part) {
|
|
62
|
+
await processor(fieldName, file);
|
|
63
|
+
}
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!(part instanceof File)) {
|
|
68
|
+
this.body[fieldName] = part;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await processor(fieldName, part);
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
await this.cleanup(true);
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Handles a single file upload for a specific field.
|
|
82
|
+
*/
|
|
83
|
+
async handleSingleFile(
|
|
84
|
+
fieldName: string,
|
|
85
|
+
file: File,
|
|
86
|
+
): Promise<StorageFile | undefined> {
|
|
87
|
+
const storageFile = await this.options.storage!.handleFile(
|
|
88
|
+
file,
|
|
89
|
+
this.req,
|
|
90
|
+
fieldName,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (await filterUpload(this.options, this.req, storageFile)) {
|
|
94
|
+
return storageFile;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// If file was filtered out, remove it from storage
|
|
98
|
+
await this.options.storage!.removeFile(storageFile, true);
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Validates that the field name matches the expected field name.
|
|
104
|
+
*/
|
|
105
|
+
validateFieldName(fieldName: string, expectedFieldName: string): void {
|
|
106
|
+
if (fieldName !== expectedFieldName) {
|
|
107
|
+
throw new BadRequestException(
|
|
108
|
+
`Field "${fieldName}" doesn't accept file.`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Validates that only one file is uploaded for a field.
|
|
115
|
+
*/
|
|
116
|
+
validateSingleFile(currentFile: StorageFile | undefined): void {
|
|
117
|
+
if (currentFile) {
|
|
118
|
+
throw new BadRequestException('Field accepts only one file.');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Validates the maximum number of files for a field.
|
|
124
|
+
*/
|
|
125
|
+
validateMaxCount(
|
|
126
|
+
fieldName: string,
|
|
127
|
+
currentCount: number,
|
|
128
|
+
maxCount: number,
|
|
129
|
+
): void {
|
|
130
|
+
if (currentCount >= maxCount) {
|
|
131
|
+
throw new BadRequestException(
|
|
132
|
+
`Field "${fieldName}" accepts max ${maxCount} files.`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Adds a file to the files collection.
|
|
139
|
+
*/
|
|
140
|
+
addFile(fieldName: string, file: StorageFile): void {
|
|
141
|
+
this.files.push(file);
|
|
142
|
+
|
|
143
|
+
if (!this.filesByField[fieldName]) {
|
|
144
|
+
this.filesByField[fieldName] = [];
|
|
145
|
+
}
|
|
146
|
+
this.filesByField[fieldName].push(file);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Returns all processed files.
|
|
151
|
+
*/
|
|
152
|
+
getFiles(): StorageFile[] {
|
|
153
|
+
return [...this.files];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Returns files grouped by field name.
|
|
158
|
+
*/
|
|
159
|
+
getFilesByField(): Record<string, StorageFile[]> {
|
|
160
|
+
return { ...this.filesByField };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Returns the request body.
|
|
165
|
+
*/
|
|
166
|
+
getBody(): BodyData {
|
|
167
|
+
return { ...this.body };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Cleans up all uploaded files.
|
|
172
|
+
* Prevents multiple cleanups and clears references to avoid memory leaks.
|
|
173
|
+
*/
|
|
174
|
+
async cleanup(error?: boolean): Promise<void> {
|
|
175
|
+
if (this.cleanedUp) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.cleanedUp = true;
|
|
180
|
+
|
|
181
|
+
await removeStorageFiles(this.options.storage!, this.files, error);
|
|
182
|
+
|
|
183
|
+
// Clear references to allow garbage collection
|
|
184
|
+
this.files.length = 0;
|
|
185
|
+
for (const key of Object.keys(this.filesByField)) {
|
|
186
|
+
delete this.filesByField[key];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Creates a remove function for cleanup that prevents multiple calls.
|
|
192
|
+
*/
|
|
193
|
+
createRemoveFunction(): () => Promise<void> {
|
|
194
|
+
let called = false;
|
|
195
|
+
|
|
196
|
+
return async () => {
|
|
197
|
+
if (called) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
called = true;
|
|
201
|
+
await this.cleanup();
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|