@kiyasov/platform-hono 1.6.1 → 2.0.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.
- 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/GraphQLUpload.d.ts +1 -1
- package/dist/cjs/src/drivers/graphQLUpload/Upload.d.ts +11 -8
- 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/GraphQLUpload.d.ts +1 -1
- package/dist/esm/src/drivers/graphQLUpload/Upload.d.ts +11 -8
- 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 +34 -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 +82 -0
- package/src/drivers/graphQLUpload/storage/index.ts +3 -0
- package/src/drivers/graphQLUpload/storage/memory-storage.ts +58 -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
|
@@ -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
|
+
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { BadRequestException } from '@nestjs/common';
|
|
2
|
-
import { BodyData } from 'hono/utils/body';
|
|
3
2
|
|
|
4
3
|
import { StorageFile } from '../../storage/storage';
|
|
5
|
-
import { removeStorageFiles } from '../file';
|
|
6
|
-
import { filterUpload } from '../filter';
|
|
7
4
|
import { UploadOptions } from '../options';
|
|
8
|
-
import { THonoRequest
|
|
5
|
+
import { THonoRequest } from '../request';
|
|
6
|
+
import { FileHandler, FileFieldsResult } from './base-handler';
|
|
9
7
|
|
|
10
8
|
export interface UploadField {
|
|
11
9
|
name: string;
|
|
@@ -16,74 +14,48 @@ export type UploadFieldMapEntry = Required<Pick<UploadField, 'maxCount'>>;
|
|
|
16
14
|
|
|
17
15
|
/**
|
|
18
16
|
* Converts an array of upload fields into a map for easy lookup.
|
|
19
|
-
* @param {UploadField[]} uploadFields - Array of upload field definitions.
|
|
20
|
-
* @returns {Map<string, UploadFieldMapEntry>} A map of field names to their max count settings.
|
|
21
17
|
*/
|
|
22
|
-
export const uploadFieldsToMap = (
|
|
18
|
+
export const uploadFieldsToMap = (
|
|
19
|
+
uploadFields: UploadField[],
|
|
20
|
+
): Map<string, UploadFieldMapEntry> =>
|
|
23
21
|
new Map(
|
|
24
22
|
uploadFields.map(({ name, ...opts }) => [name, { maxCount: 1, ...opts }]),
|
|
25
23
|
);
|
|
26
24
|
|
|
27
25
|
/**
|
|
28
26
|
* Handles multipart file fields by processing form-data parts.
|
|
29
|
-
* @param {THonoRequest} req - The incoming request object.
|
|
30
|
-
* @param {Map<string, UploadFieldMapEntry>} fieldsMap - A map of allowed upload fields.
|
|
31
|
-
* @param {UploadOptions} options - Upload options including storage handler.
|
|
32
|
-
* @returns {Promise<{ body: BodyData, files: Record<string, StorageFile[]>, remove: () => Promise<void> }>} The parsed body and files with a removal function.
|
|
33
27
|
*/
|
|
34
28
|
export const handleMultipartFileFields = async (
|
|
35
29
|
req: THonoRequest,
|
|
36
30
|
fieldsMap: Map<string, UploadFieldMapEntry>,
|
|
37
31
|
options: UploadOptions,
|
|
38
|
-
): Promise<{
|
|
39
|
-
|
|
40
|
-
files: Record<string, StorageFile[]>;
|
|
41
|
-
remove: () => Promise<void>;
|
|
42
|
-
}> => {
|
|
43
|
-
const parts = getParts(req, options);
|
|
44
|
-
const body: BodyData = {};
|
|
32
|
+
): Promise<FileFieldsResult> => {
|
|
33
|
+
const handler = new FileHandler(req, options);
|
|
45
34
|
const files: Record<string, StorageFile[]> = {};
|
|
46
35
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const removeFiles = async (error?: boolean): Promise<void> => {
|
|
53
|
-
const allFiles = Object.values(files).flat();
|
|
54
|
-
return removeStorageFiles(options.storage!, allFiles, error);
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
for await (const [fieldName, part] of Object.entries(parts)) {
|
|
59
|
-
if (!(part instanceof File)) {
|
|
60
|
-
body[fieldName] = part;
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const fieldOptions = fieldsMap.get(fieldName);
|
|
65
|
-
if (!fieldOptions) {
|
|
66
|
-
throw new BadRequestException(
|
|
67
|
-
`Field ${fieldName} doesn't accept files`,
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
files[fieldName] = files[fieldName] || [];
|
|
72
|
-
if (files[fieldName].length >= fieldOptions.maxCount) {
|
|
73
|
-
throw new BadRequestException(
|
|
74
|
-
`Field ${fieldName} accepts max ${fieldOptions.maxCount} files`,
|
|
75
|
-
);
|
|
76
|
-
}
|
|
36
|
+
await handler.process(async (fieldName, part) => {
|
|
37
|
+
const fieldOptions = fieldsMap.get(fieldName);
|
|
38
|
+
if (!fieldOptions) {
|
|
39
|
+
throw new BadRequestException(`Field ${fieldName} doesn't accept files`);
|
|
40
|
+
}
|
|
77
41
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
42
|
+
files[fieldName] = files[fieldName] || [];
|
|
43
|
+
handler.validateMaxCount(
|
|
44
|
+
fieldName,
|
|
45
|
+
files[fieldName].length,
|
|
46
|
+
fieldOptions.maxCount,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const storageFile = await handler.handleSingleFile(fieldName, part);
|
|
50
|
+
if (storageFile) {
|
|
51
|
+
files[fieldName].push(storageFile);
|
|
52
|
+
handler.addFile(fieldName, storageFile);
|
|
82
53
|
}
|
|
83
|
-
}
|
|
84
|
-
await removeFiles(true);
|
|
85
|
-
throw error;
|
|
86
|
-
}
|
|
54
|
+
});
|
|
87
55
|
|
|
88
|
-
return {
|
|
56
|
+
return {
|
|
57
|
+
body: handler.getBody(),
|
|
58
|
+
files,
|
|
59
|
+
remove: handler.createRemoveFunction(),
|
|
60
|
+
};
|
|
89
61
|
};
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
export { UploadField } from './file-fields';
|
|
1
|
+
export { UploadField, uploadFieldsToMap } from './file-fields';
|
|
2
|
+
export { handleMultipartSingleFile } from './single-file';
|
|
3
|
+
export { handleMultipartMultipleFiles } from './multiple-files';
|
|
4
|
+
export { handleMultipartFileFields } from './file-fields';
|
|
5
|
+
export { handleMultipartAnyFiles } from './any-files';
|
|
6
|
+
export {
|
|
7
|
+
FileHandler,
|
|
8
|
+
SingleFileResult,
|
|
9
|
+
MultipleFilesResult,
|
|
10
|
+
FileFieldsResult,
|
|
11
|
+
} from './base-handler';
|
|
@@ -1,70 +1,39 @@
|
|
|
1
1
|
import { BadRequestException } from '@nestjs/common';
|
|
2
|
-
import { BodyData } from 'hono/utils/body';
|
|
3
2
|
|
|
4
|
-
import { StorageFile } from '../../storage';
|
|
5
|
-
import { removeStorageFiles } from '../file';
|
|
6
|
-
import { filterUpload } from '../filter';
|
|
3
|
+
import { StorageFile } from '../../storage/storage';
|
|
7
4
|
import { UploadOptions } from '../options';
|
|
8
|
-
import { THonoRequest
|
|
5
|
+
import { THonoRequest } from '../request';
|
|
6
|
+
import { FileHandler, MultipleFilesResult } from './base-handler';
|
|
9
7
|
|
|
10
8
|
export const handleMultipartMultipleFiles = async (
|
|
11
9
|
req: THonoRequest,
|
|
12
10
|
fieldname: string,
|
|
13
11
|
maxCount: number,
|
|
14
12
|
options: UploadOptions,
|
|
15
|
-
) => {
|
|
16
|
-
const
|
|
17
|
-
const body: BodyData = {};
|
|
18
|
-
|
|
13
|
+
): Promise<MultipleFilesResult> => {
|
|
14
|
+
const handler = new FileHandler(req, options);
|
|
19
15
|
const files: StorageFile[] = [];
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (!(part instanceof File || Array.isArray(part))) {
|
|
28
|
-
body[partFieldName] = part;
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const partArray = Array.isArray(part) ? part : [part];
|
|
33
|
-
|
|
34
|
-
for (const singlePart of partArray) {
|
|
35
|
-
if (!(singlePart instanceof File)) {
|
|
36
|
-
throw new BadRequestException(
|
|
37
|
-
`Field ${partFieldName} contains invalid file data`,
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (partFieldName !== fieldname) {
|
|
42
|
-
throw new BadRequestException(
|
|
43
|
-
`Field ${partFieldName} doesn't accept files`,
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (files.length >= maxCount) {
|
|
48
|
-
throw new BadRequestException(
|
|
49
|
-
`Field ${partFieldName} accepts max ${maxCount} files`,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
17
|
+
await handler.process(async (fieldName, part) => {
|
|
18
|
+
if (!(part instanceof File)) {
|
|
19
|
+
throw new BadRequestException(
|
|
20
|
+
`Field ${fieldName} contains invalid file data`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
52
23
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
req,
|
|
56
|
-
partFieldName,
|
|
57
|
-
);
|
|
24
|
+
handler.validateFieldName(fieldName, fieldname);
|
|
25
|
+
handler.validateMaxCount(fieldName, files.length, maxCount);
|
|
58
26
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
27
|
+
const storageFile = await handler.handleSingleFile(fieldName, part);
|
|
28
|
+
if (storageFile) {
|
|
29
|
+
files.push(storageFile);
|
|
30
|
+
handler.addFile(fieldName, storageFile);
|
|
63
31
|
}
|
|
64
|
-
}
|
|
65
|
-
await removeFiles(error);
|
|
66
|
-
throw error;
|
|
67
|
-
}
|
|
32
|
+
});
|
|
68
33
|
|
|
69
|
-
return {
|
|
34
|
+
return {
|
|
35
|
+
body: handler.getBody(),
|
|
36
|
+
files,
|
|
37
|
+
remove: handler.createRemoveFunction(),
|
|
38
|
+
};
|
|
70
39
|
};
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { BodyData } from 'hono/utils/body';
|
|
3
|
-
|
|
4
|
-
import { StorageFile } from '../../storage';
|
|
5
|
-
import { filterUpload } from '../filter';
|
|
1
|
+
import { StorageFile } from '../../storage/storage';
|
|
6
2
|
import { UploadOptions } from '../options';
|
|
7
|
-
import { THonoRequest
|
|
3
|
+
import { THonoRequest } from '../request';
|
|
4
|
+
import { FileHandler, SingleFileResult } from './base-handler';
|
|
8
5
|
|
|
9
6
|
/**
|
|
10
7
|
* Handles a single file upload in a multipart request.
|
|
@@ -17,53 +14,23 @@ export const handleMultipartSingleFile = async (
|
|
|
17
14
|
req: THonoRequest,
|
|
18
15
|
fieldname: string,
|
|
19
16
|
options: UploadOptions,
|
|
20
|
-
) => {
|
|
21
|
-
const
|
|
22
|
-
const body: BodyData = {};
|
|
17
|
+
): Promise<SingleFileResult> => {
|
|
18
|
+
const handler = new FileHandler(req, options);
|
|
23
19
|
let file: StorageFile | undefined;
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*/
|
|
29
|
-
const removeFiles = async (error?: boolean) => {
|
|
30
|
-
if (!file) return;
|
|
31
|
-
await options.storage!.removeFile(file, error);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
for await (const [partFieldName, part] of Object.entries(parts)) {
|
|
36
|
-
if (!(part instanceof File)) {
|
|
37
|
-
body[partFieldName] = part;
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (partFieldName !== fieldname) {
|
|
42
|
-
throw new BadRequestException(
|
|
43
|
-
`Field "${partFieldName}" doesn't accept file.`,
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (file) {
|
|
48
|
-
throw new BadRequestException(
|
|
49
|
-
`Field "${fieldname}" accepts only one file.`,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const _file = await options.storage!.handleFile(part, req, partFieldName);
|
|
21
|
+
await handler.process(async (fieldName, part) => {
|
|
22
|
+
handler.validateFieldName(fieldName, fieldname);
|
|
23
|
+
handler.validateSingleFile(file);
|
|
54
24
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
25
|
+
const storageFile = await handler.handleSingleFile(fieldName, part);
|
|
26
|
+
if (storageFile) {
|
|
27
|
+
file = storageFile;
|
|
58
28
|
}
|
|
59
|
-
}
|
|
60
|
-
await removeFiles(true);
|
|
61
|
-
throw error;
|
|
62
|
-
}
|
|
29
|
+
});
|
|
63
30
|
|
|
64
31
|
return {
|
|
65
|
-
body,
|
|
32
|
+
body: handler.getBody(),
|
|
66
33
|
file,
|
|
67
|
-
remove: ()
|
|
34
|
+
remove: handler.createRemoveFunction(),
|
|
68
35
|
};
|
|
69
36
|
};
|
|
@@ -1,20 +1,38 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { DiskStorage, MemoryStorage, Storage } from '../storage';
|
|
1
|
+
import { DiskStorage, MemoryStorage, Storage, StorageFile } from '../storage';
|
|
4
2
|
import { UploadFilterHandler } from './filter';
|
|
5
3
|
|
|
6
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Upload limits configuration
|
|
6
|
+
*/
|
|
7
|
+
export interface UploadLimits {
|
|
8
|
+
/** Maximum file size in bytes */
|
|
9
|
+
fileSize?: number;
|
|
10
|
+
/** Maximum number of files for a field */
|
|
11
|
+
files?: number;
|
|
12
|
+
/** Maximum number of fields (for file fields upload) */
|
|
13
|
+
fields?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* File upload options
|
|
18
|
+
*/
|
|
19
|
+
export type UploadOptions = {
|
|
20
|
+
/** Destination directory for disk storage */
|
|
7
21
|
dest?: string;
|
|
8
|
-
|
|
22
|
+
/** Storage implementation */
|
|
23
|
+
storage?: Storage<StorageFile>;
|
|
24
|
+
/** File filter function */
|
|
9
25
|
filter?: UploadFilterHandler;
|
|
26
|
+
/** Upload limits */
|
|
27
|
+
limits?: UploadLimits;
|
|
10
28
|
};
|
|
11
29
|
|
|
12
30
|
export const DEFAULT_UPLOAD_OPTIONS: Partial<UploadOptions> = {
|
|
13
31
|
storage: new MemoryStorage(),
|
|
14
32
|
};
|
|
15
33
|
|
|
16
|
-
export const transformUploadOptions = (opts?: UploadOptions) => {
|
|
17
|
-
if (opts == null) return DEFAULT_UPLOAD_OPTIONS;
|
|
34
|
+
export const transformUploadOptions = (opts?: UploadOptions): UploadOptions => {
|
|
35
|
+
if (opts == null) return DEFAULT_UPLOAD_OPTIONS as UploadOptions;
|
|
18
36
|
|
|
19
37
|
if (opts.dest != null) {
|
|
20
38
|
return {
|
|
@@ -26,5 +44,5 @@ export const transformUploadOptions = (opts?: UploadOptions) => {
|
|
|
26
44
|
};
|
|
27
45
|
}
|
|
28
46
|
|
|
29
|
-
return { ...DEFAULT_UPLOAD_OPTIONS, ...opts };
|
|
47
|
+
return { ...DEFAULT_UPLOAD_OPTIONS, ...opts } as UploadOptions;
|
|
30
48
|
};
|
|
@@ -36,10 +36,26 @@ export const getParts = (
|
|
|
36
36
|
): BodyData => {
|
|
37
37
|
const parts = req.body ?? {};
|
|
38
38
|
|
|
39
|
-
for (const [key,
|
|
40
|
-
|
|
39
|
+
for (const [key, value] of Object.entries(parts)) {
|
|
40
|
+
// Handle array of files
|
|
41
|
+
if (Array.isArray(value) && value.every((item) => item instanceof File)) {
|
|
41
42
|
const maxSize = options?.limits?.fileSize;
|
|
42
|
-
if (maxSize
|
|
43
|
+
if (maxSize) {
|
|
44
|
+
for (const file of value) {
|
|
45
|
+
if (file.size > maxSize) {
|
|
46
|
+
throw new BadRequestException(
|
|
47
|
+
`File "${key}" is too large. Maximum size is ${maxSize} bytes.`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Handle single file
|
|
56
|
+
if (value instanceof File) {
|
|
57
|
+
const maxSize = options?.limits?.fileSize;
|
|
58
|
+
if (maxSize && value.size > maxSize) {
|
|
43
59
|
throw new BadRequestException(
|
|
44
60
|
`File "${key}" is too large. Maximum size is ${maxSize} bytes.`,
|
|
45
61
|
);
|
|
@@ -8,26 +8,33 @@ export interface MemoryStorageFile extends StorageFile {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export class MemoryStorage implements Storage<MemoryStorageFile> {
|
|
11
|
-
public async handleFile(
|
|
11
|
+
public async handleFile(
|
|
12
|
+
file: File,
|
|
13
|
+
_req: HonoRequest,
|
|
14
|
+
fieldName: string,
|
|
15
|
+
): Promise<MemoryStorageFile> {
|
|
12
16
|
const buffer = await file
|
|
13
17
|
.stream()
|
|
14
18
|
.pipeTo(new WritableStream())
|
|
15
19
|
.then(() => file.arrayBuffer())
|
|
16
|
-
.then((
|
|
20
|
+
.then((buf) => Buffer.from(buf));
|
|
17
21
|
|
|
18
22
|
return {
|
|
19
23
|
buffer,
|
|
20
24
|
size: buffer.length,
|
|
21
25
|
encoding: 'utf-8',
|
|
22
26
|
mimetype: file.type,
|
|
23
|
-
|
|
27
|
+
fieldName: fieldName,
|
|
24
28
|
originalFilename: file.name,
|
|
29
|
+
uploadedAt: new Date().toISOString(),
|
|
25
30
|
stream: () => file.stream(),
|
|
26
|
-
lastModified: file.lastModified,
|
|
27
31
|
};
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
public async removeFile(file:
|
|
31
|
-
|
|
34
|
+
public async removeFile(file: StorageFile): Promise<void> {
|
|
35
|
+
// Check if it's a MemoryStorageFile before deleting buffer
|
|
36
|
+
if ('buffer' in file) {
|
|
37
|
+
delete (file as MemoryStorageFile).buffer;
|
|
38
|
+
}
|
|
32
39
|
}
|
|
33
40
|
}
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import { HonoRequest } from 'hono';
|
|
2
2
|
|
|
3
3
|
export interface StorageFile {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
mimetype: string;
|
|
4
|
+
/** Field name in the multipart form */
|
|
5
|
+
fieldName: string;
|
|
6
|
+
/** Original filename provided by client */
|
|
8
7
|
originalFilename: string;
|
|
8
|
+
/** MIME type of the file */
|
|
9
|
+
mimetype: string;
|
|
10
|
+
/** Encoding type (e.g., '7bit', '8bit', 'binary') */
|
|
11
|
+
encoding: string;
|
|
12
|
+
/** Size of the file in bytes */
|
|
13
|
+
size: number;
|
|
14
|
+
/** Timestamp when file was uploaded (ISO 8601) */
|
|
15
|
+
uploadedAt?: string;
|
|
9
16
|
}
|
|
10
17
|
|
|
11
18
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
19
|
export interface Storage<T extends StorageFile = StorageFile, K = any> {
|
|
13
20
|
handleFile: (file: File, req: HonoRequest, fieldName: string) => Promise<T>;
|
|
14
|
-
removeFile: (file: T, force?: boolean) => Promise<void> | void;
|
|
21
|
+
removeFile: (file: T | StorageFile, force?: boolean) => Promise<void> | void;
|
|
15
22
|
options?: K;
|
|
16
23
|
}
|