@travetto/web-upload 7.0.0-rc.1 → 7.0.0-rc.3
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/package.json +4 -4
- package/src/decorator.ts +16 -15
- package/src/interceptor.ts +2 -2
- package/src/util.ts +18 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/web-upload",
|
|
3
|
-
"version": "7.0.0-rc.
|
|
3
|
+
"version": "7.0.0-rc.3",
|
|
4
4
|
"description": "Provides integration between the travetto asset and web module.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web",
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@fastify/busboy": "^3.2.0",
|
|
29
|
-
"@travetto/config": "^7.0.0-rc.
|
|
30
|
-
"@travetto/web": "^7.0.0-rc.
|
|
29
|
+
"@travetto/config": "^7.0.0-rc.2",
|
|
30
|
+
"@travetto/web": "^7.0.0-rc.3",
|
|
31
31
|
"file-type": "^21.1.1",
|
|
32
32
|
"mime": "^4.1.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@travetto/test": "^7.0.0-rc.
|
|
35
|
+
"@travetto/test": "^7.0.0-rc.2"
|
|
36
36
|
},
|
|
37
37
|
"peerDependenciesMeta": {
|
|
38
38
|
"@travetto/test": {
|
package/src/decorator.ts
CHANGED
|
@@ -19,29 +19,30 @@ const FileMapContract = toConcrete<FileMap>();
|
|
|
19
19
|
*/
|
|
20
20
|
export function Upload(
|
|
21
21
|
param: Partial<EndpointParameterConfig> & UploadConfig = {},
|
|
22
|
-
): (instance: ClassInstance, property: string
|
|
22
|
+
): (instance: ClassInstance, property: string, idx: number) => void {
|
|
23
23
|
|
|
24
|
-
const
|
|
24
|
+
const finalConfig = { ...param };
|
|
25
25
|
|
|
26
|
-
return (instance: ClassInstance, property: string
|
|
26
|
+
return (instance: ClassInstance, property: string, idx: number): void => {
|
|
27
27
|
// Register field
|
|
28
|
-
const cls =
|
|
29
|
-
const
|
|
28
|
+
const cls = getClass(instance);
|
|
29
|
+
const adapter = ControllerRegistryIndex.getForRegister(cls);
|
|
30
|
+
const getName = (): string => SchemaRegistryIndex.get(cls).getMethod(property).parameters[idx].name!;
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
adapter.registerFinalizeHandler(() => {
|
|
33
|
+
adapter.registerEndpointInterceptorConfig(
|
|
33
34
|
property,
|
|
34
35
|
WebUploadInterceptor,
|
|
35
36
|
{
|
|
36
37
|
applies: true,
|
|
37
|
-
maxSize:
|
|
38
|
-
types:
|
|
39
|
-
cleanupFiles:
|
|
38
|
+
maxSize: finalConfig.maxSize,
|
|
39
|
+
types: finalConfig.types,
|
|
40
|
+
cleanupFiles: finalConfig.cleanupFiles,
|
|
40
41
|
uploads: {
|
|
41
42
|
[getName()]: {
|
|
42
|
-
maxSize:
|
|
43
|
-
types:
|
|
44
|
-
cleanupFiles:
|
|
43
|
+
maxSize: finalConfig.maxSize,
|
|
44
|
+
types: finalConfig.types,
|
|
45
|
+
cleanupFiles: finalConfig.cleanupFiles
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
}
|
|
@@ -49,9 +50,9 @@ export function Upload(
|
|
|
49
50
|
});
|
|
50
51
|
|
|
51
52
|
return Param('body', {
|
|
52
|
-
...
|
|
53
|
+
...finalConfig,
|
|
53
54
|
extract: (request) => {
|
|
54
|
-
const input = SchemaRegistryIndex.
|
|
55
|
+
const input = SchemaRegistryIndex.get(cls).getMethod(property).parameters[idx];
|
|
55
56
|
|
|
56
57
|
if (!input) {
|
|
57
58
|
throw new AppError(`Unknown field type, ensure you are using ${Blob.name}, ${File.name} or ${FileMapContract.name}`);
|
package/src/interceptor.ts
CHANGED
|
@@ -24,8 +24,8 @@ export class WebUploadInterceptor implements WebInterceptor<WebUploadConfig> {
|
|
|
24
24
|
finalizeConfig({ config: base }: WebInterceptorContext<WebUploadConfig>, inputs: Partial<WebUploadConfig>[]): WebUploadConfig {
|
|
25
25
|
base.uploads ??= {};
|
|
26
26
|
// Override the uploads object with all the data from the inputs
|
|
27
|
-
for (const [
|
|
28
|
-
Object.assign(base.uploads[
|
|
27
|
+
for (const [key, config] of inputs.flatMap(inputConfig => Object.entries(inputConfig.uploads ?? {}))) {
|
|
28
|
+
Object.assign(base.uploads[key] ??= {}, config);
|
|
29
29
|
}
|
|
30
30
|
return base;
|
|
31
31
|
}
|
package/src/util.ts
CHANGED
|
@@ -64,9 +64,11 @@ export class WebUploadUtil {
|
|
|
64
64
|
const contentType = WebHeaderUtil.parseHeaderSegment(request.headers.get('Content-Type'));
|
|
65
65
|
|
|
66
66
|
if (MULTIPART.has(contentType.value)) {
|
|
67
|
-
const fileMaxes = Object.values(config.uploads ?? {})
|
|
67
|
+
const fileMaxes = Object.values(config.uploads ?? {})
|
|
68
|
+
.map(uploadConfig => uploadConfig.maxSize)
|
|
69
|
+
.filter(uploadConfig => uploadConfig !== undefined);
|
|
68
70
|
const largestMax = fileMaxes.length ? Math.max(...fileMaxes) : config.maxSize;
|
|
69
|
-
const
|
|
71
|
+
const queue = new AsyncQueue<UploadItem>();
|
|
70
72
|
|
|
71
73
|
// Upload
|
|
72
74
|
bodyStream.pipe(busboy({
|
|
@@ -80,12 +82,12 @@ export class WebUploadUtil {
|
|
|
80
82
|
},
|
|
81
83
|
limits: { fileSize: largestMax }
|
|
82
84
|
})
|
|
83
|
-
.on('file', (field, stream, filename) =>
|
|
84
|
-
.on('limit', field =>
|
|
85
|
-
.on('finish', () =>
|
|
86
|
-
.on('error', (
|
|
85
|
+
.on('file', (field, stream, filename) => queue.add({ stream, filename, field }))
|
|
86
|
+
.on('limit', field => queue.throw(new AppError(`File size exceeded for ${field}`, { category: 'data' })))
|
|
87
|
+
.on('finish', () => queue.close())
|
|
88
|
+
.on('error', (error) => queue.throw(error instanceof Error ? error : new Error(`${error}`))));
|
|
87
89
|
|
|
88
|
-
yield*
|
|
90
|
+
yield* queue;
|
|
89
91
|
} else {
|
|
90
92
|
const filename = WebHeaderUtil.parseHeaderSegment(request.headers.get('Content-Disposition')).parameters.filename;
|
|
91
93
|
yield { stream: bodyStream, filename, field: 'file' };
|
|
@@ -96,12 +98,12 @@ export class WebUploadUtil {
|
|
|
96
98
|
* Convert an UploadItem to a File
|
|
97
99
|
*/
|
|
98
100
|
static async toFile({ stream, filename, field }: UploadItem, config: Partial<WebUploadConfig>): Promise<File> {
|
|
99
|
-
const
|
|
100
|
-
await fs.mkdir(
|
|
101
|
+
const uniqueDirectory = path.resolve(os.tmpdir(), `file_${Date.now()}_${Util.uuid(5)}`);
|
|
102
|
+
await fs.mkdir(uniqueDirectory, { recursive: true });
|
|
101
103
|
|
|
102
104
|
filename = filename ? path.basename(filename) : `unknown_${Date.now()}`;
|
|
103
105
|
|
|
104
|
-
const location = path.resolve(
|
|
106
|
+
const location = path.resolve(uniqueDirectory, filename);
|
|
105
107
|
const remove = (): Promise<void> => fs.rm(location).catch(() => { });
|
|
106
108
|
const mimeCheck = config.matcher ??= WebCommonUtil.mimeTypeMatcher(config.types);
|
|
107
109
|
|
|
@@ -132,9 +134,9 @@ export class WebUploadUtil {
|
|
|
132
134
|
Object.assign(file, { [RawFileSymbol]: location });
|
|
133
135
|
|
|
134
136
|
return file;
|
|
135
|
-
} catch (
|
|
137
|
+
} catch (error) {
|
|
136
138
|
await remove();
|
|
137
|
-
throw
|
|
139
|
+
throw error;
|
|
138
140
|
}
|
|
139
141
|
}
|
|
140
142
|
|
|
@@ -146,14 +148,14 @@ export class WebUploadUtil {
|
|
|
146
148
|
const { fromStream } = await import('strtok3');
|
|
147
149
|
|
|
148
150
|
const parser = new FileTypeParser();
|
|
149
|
-
let
|
|
151
|
+
let token: ReturnType<typeof fromStream> | undefined;
|
|
150
152
|
let matched: FileType | undefined;
|
|
151
153
|
|
|
152
154
|
try {
|
|
153
|
-
|
|
154
|
-
matched = await parser.fromTokenizer(
|
|
155
|
+
token = await fromStream(typeof input === 'string' ? createReadStream(input) : input);
|
|
156
|
+
matched = await parser.fromTokenizer(token);
|
|
155
157
|
} finally {
|
|
156
|
-
await
|
|
158
|
+
await token?.close();
|
|
157
159
|
}
|
|
158
160
|
|
|
159
161
|
if (!matched && typeof input === 'string') {
|