node-server-engine 1.2.5 → 1.2.7

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 (23) hide show
  1. package/README.md +43 -18
  2. package/dist/middleware/fileUploader/fileUploader.d.ts +4 -5
  3. package/dist/middleware/fileUploader/fileUploader.js +68 -255
  4. package/dist/middleware/fileUploader/fileUploader.js.map +1 -1
  5. package/dist/middleware/fileUploader/fileUploader.types.d.ts +13 -32
  6. package/dist/middleware/fileUploader/fileUploader.types.js.map +1 -1
  7. package/package.json +2 -2
  8. package/src/middleware/fileUploader/fileUploader.ts +70 -270
  9. package/src/middleware/fileUploader/fileUploader.types.ts +16 -33
  10. package/test/env.ts +1 -0
  11. package/dist/middleware/fileUploader/compressAvatar/compressAvatar.d.ts +0 -6
  12. package/dist/middleware/fileUploader/compressAvatar/compressAvatar.js +0 -28
  13. package/dist/middleware/fileUploader/compressAvatar/compressAvatar.js.map +0 -1
  14. package/dist/middleware/fileUploader/compressAvatar/compressAvatar.types.d.ts +0 -12
  15. package/dist/middleware/fileUploader/compressAvatar/compressAvatar.types.js +0 -2
  16. package/dist/middleware/fileUploader/compressAvatar/compressAvatar.types.js.map +0 -1
  17. package/dist/middleware/fileUploader/compressAvatar/index.d.ts +0 -2
  18. package/dist/middleware/fileUploader/compressAvatar/index.js +0 -28
  19. package/dist/middleware/fileUploader/compressAvatar/index.js.map +0 -1
  20. package/src/middleware/fileUploader/compressAvatar/compressAvatar.ts +0 -25
  21. package/src/middleware/fileUploader/compressAvatar/compressAvatar.types.ts +0 -12
  22. package/src/middleware/fileUploader/compressAvatar/index.ts +0 -2
  23. package/src/middleware/fileUploader/fileUploader.test.ts +0 -310
@@ -1,303 +1,103 @@
1
- import Busboy from 'busboy';
2
1
  import { Request, Response, NextFunction } from 'express';
3
2
  import { Schema } from 'express-validator';
4
- import { compressAvatar } from './compressAvatar';
3
+ import { WebError } from 'entities/WebError';
4
+ import { reportDebug } from 'utils/report';
5
+ import multer from 'multer';
5
6
  import { FileUploaderConfig } from './fileUploader.types';
6
- import { isMultipart } from './isMultiPart';
7
7
  import { validate } from './validate';
8
- import { Storage } from 'entities/Storage';
9
- import { WebError } from 'entities/WebError';
10
- import { reportError, reportDebug } from 'utils/report';
11
- import { Readable } from 'stream';
12
8
 
13
9
  const namespace = 'engine:middleware:fileUploader';
14
10
 
15
11
  /**
16
- * Generate a custom fileUploader middleware
12
+ * Middleware for handling file uploads.
13
+ * @param filesOptions Configuration options for expected files.
14
+ * @param validator Validation schema for request fields.
17
15
  */
18
16
  export function fileUploader(
19
- options: Array<FileUploaderConfig>,
17
+ filesOptions: Array<FileUploaderConfig>,
20
18
  validator: Schema
21
19
  ) {
22
- return (
20
+ return async (
23
21
  request: Request,
24
22
  response: Response,
25
23
  next: NextFunction
26
- ): Busboy.Busboy =>
27
- fileUploaderMiddleware(request, response, next, options, validator);
28
- }
29
-
30
- /** Handle file upload and generic multi-part parsing */
31
- export function fileUploaderMiddleware(
32
- request: Request,
33
- response: Response,
34
- next: NextFunction,
35
- filesOptions: Array<FileUploaderConfig>,
36
- validator: Schema
37
- ): Busboy.Busboy {
38
- reportDebug({
39
- namespace,
40
- message: 'Received request to upload file',
41
- data: { filesOptions, validator }
42
- });
43
- // Check that the request is a proper multipart request
44
- if (!isMultipart(request))
45
- throw new WebError({
46
- statusCode: 415,
47
- errorCode: 'invalid-content-type',
48
- message: 'Request is not of type multipart/form-data',
49
- data: { contentType: request.get('Content-Type') }
24
+ ): Promise<void> => {
25
+ reportDebug({
26
+ namespace,
27
+ message: 'Received request to upload file',
28
+ data: { filesOptions, validator }
50
29
  });
51
- // If no files to handle ignore
52
- if (!filesOptions.length) next();
53
- // Parse multipart data with Busboy
54
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
- const busboy = new (Busboy as any)({
56
- headers: {
57
- ...request.headers,
58
- 'content-type': request.headers['content-type'] ?? ''
59
- }
60
- });
61
30
 
62
- let hasError = false;
31
+ // Setup multer with memory storage
32
+ const storage = multer.memoryStorage();
33
+ const upload = multer({
34
+ storage,
35
+ limits: { fileSize: 10 * 1024 * 1024 } // Limit file size to 10MB
36
+ }).any();
63
37
 
64
- /**
65
- * Function called when an error happened in the process
66
- * It immediately stops and drains all connection and deletes all files that have already been uploaded
67
- */
68
- function triggerError(error: unknown): void {
69
- pendingWrites--;
70
- if (hasError) return;
71
- hasError = true;
72
- request.unpipe(busboy);
73
- request.on('readable', request.read.bind(request));
74
- busboy.removeAllListeners();
75
- next(error);
76
- if (request.files) {
77
- Object.values(request.files).forEach((files) => {
78
- files.forEach((file) => {
79
- Storage.delete(file.bucket, file.name).catch((error) => {
80
- reportError(error);
81
- });
38
+ upload(request, response, async (err) => {
39
+ if (err) {
40
+ reportDebug({
41
+ namespace,
42
+ message: 'Multer upload error',
43
+ data: { error: err.message }
82
44
  });
83
- });
84
- }
85
- }
86
-
87
- let readFinished = false;
88
- let fieldsValidated = false;
89
- let pendingWrites = 0;
90
-
91
- /** Function called when all the body has been parsed */
92
- function done(): void {
93
- if (readFinished && pendingWrites === 0 && fieldsValidated && !hasError) {
94
- const uploadedFiles = Object.keys(request.files ?? {});
95
- if (
96
- filesOptions.some(
97
- (options) => options.required && !uploadedFiles.includes(options.key)
98
- )
99
- ) {
100
- triggerError(
101
- new WebError({
102
- statusCode: 400,
103
- errorCode: 'missing-field',
104
- message: 'Some required files have not been uploaded',
105
- data: { filesOptions, validator, uploadedFiles }
106
- })
107
- );
108
- return;
45
+ return next(err);
109
46
  }
110
- next();
111
- }
112
- }
113
47
 
114
- /*
115
- * On cancel request, while processing
116
- */
117
- request.connection.on('close', function () {
118
- if (!response.writableEnded) triggerError(new Error('Request cancelled'));
119
- });
48
+ // Ensure `request.files` is properly typed
49
+ let uploadedFiles: Express.Multer.File[] = [];
50
+ if (Array.isArray(request.files)) {
51
+ uploadedFiles = request.files;
52
+ } else if (typeof request.files === 'object') {
53
+ uploadedFiles = Object.values(request.files).flat();
54
+ }
120
55
 
121
- /**
122
- * Once all the fields have been read they are checked against the validation schema
123
- */
124
- async function validateFields(): Promise<void> {
125
- try {
126
- await validate(request, validator);
127
- } catch (error) {
128
- triggerError(error);
129
- }
130
- reportDebug({
131
- namespace,
132
- message: 'Validation processed',
133
- data: { hasError, filesOptions, validator }
134
- });
135
- fieldsValidated = true;
136
- done();
137
- }
56
+ // Log uploaded files
57
+ reportDebug({
58
+ namespace,
59
+ message: 'Files Received',
60
+ data: { uploadedFiles: uploadedFiles.map((file) => file.originalname) }
61
+ });
138
62
 
139
- busboy.on(
140
- 'file',
141
- (
142
- field: string,
143
- file: Readable,
144
- fileName: string,
145
- encoding: unknown,
146
- mimeType: string
147
- ) => {
148
- (async (): Promise<void> => {
149
- reportDebug({
150
- namespace,
151
- message: 'Found file in request',
152
- data: { hasError, field, fileName, encoding, mimeType, pendingWrites }
153
- });
154
- // Do not handle the stream if there has been an error previously
155
- if (hasError) file.destroy();
156
- pendingWrites++;
157
- // Find related file options based on the field name
158
- const options = filesOptions.find(
159
- (fileOption) => fileOption.key === field
160
- );
161
- // Make sure that the field is in the config
162
- if (!options) {
163
- triggerError(
164
- new WebError({
165
- statusCode: 400,
166
- errorCode: 'invalid-file-field',
167
- message: 'Request contains a file at an invalid field',
168
- hint: { field },
169
- data: { field }
170
- })
171
- );
172
- return;
173
- }
63
+ // Validate request body fields
64
+ try {
65
+ await validate(request, validator);
66
+ } catch (error) {
174
67
  reportDebug({
175
68
  namespace,
176
- message: 'Found options for file',
177
- data: {
178
- hasError,
179
- field,
180
- fileName,
181
- encoding,
182
- mimeType,
183
- pendingWrites,
184
- options
185
- }
69
+ message: 'Validation failed',
70
+ data: { error }
186
71
  });
187
- // If a specific MIME type is required check it
188
- if (options.mimeTypes && !options.mimeTypes.includes(mimeType)) {
189
- triggerError(
190
- new WebError({
191
- statusCode: 415,
192
- errorCode: 'invalid-mime-type',
193
- message: 'Request contains a file with an invalid MIME type',
194
- hint: { field },
195
- data: { field, mimeType, expected: options.mimeTypes }
196
- })
197
- );
198
- return;
199
- }
200
- // Generate the destination path of the file and upload
72
+ return next(error);
73
+ }
74
+
75
+ // Extract uploaded field names
76
+ const uploadedFileKeys = uploadedFiles.map((file) => file.fieldname);
77
+
78
+ // Check for missing required files
79
+ const missingFiles = filesOptions.filter(
80
+ (option) => option.required && !uploadedFileKeys.includes(option.key)
81
+ );
82
+
83
+ if (missingFiles.length > 0) {
201
84
  reportDebug({
202
85
  namespace,
203
- message: 'Starting file upload',
204
- data: {
205
- hasError,
206
- field,
207
- fileName,
208
- encoding,
209
- mimeType,
210
- pendingWrites,
211
- options
212
- }
86
+ message: 'Missing required files',
87
+ data: { missingFiles, uploadedFileKeys }
213
88
  });
214
- try {
215
- const fileToUpload = options.image
216
- ? compressAvatar(file, options.image)
217
- : file;
218
-
219
- const uploadedFile = await Storage.upload(
220
- fileToUpload,
221
- options.bucket,
222
- {
223
- directory: options.path,
224
- mime: mimeType,
225
- noExtension: options.noExtension
226
- },
227
- {
228
- metadata: { contentType: mimeType }
229
- },
230
- { maxSize: options.maxSize }
231
- );
232
89
 
233
- reportDebug({
234
- namespace,
235
- message: 'File uploaded',
236
- data: {
237
- hasError,
238
- field,
239
- fileName,
240
- encoding,
241
- mimeType,
242
- pendingWrites,
243
- options,
244
- uploadedFile
245
- }
246
- });
247
- if (hasError) {
248
- // If some other failed before this one terminated
249
- // Delete file
250
- await Storage.delete(uploadedFile.bucket, uploadedFile.name);
251
- } else {
252
- // Handle successful upload
253
- // Add the file's data to the request
254
- pendingWrites--;
255
- if (!request.files) request.files = {};
256
- if (!request.files[field]) request.files[field] = [];
257
- request.files[field].push({
258
- ...uploadedFile,
259
- originalFileName: fileName
260
- });
261
- done();
262
- }
263
- } catch (error) {
264
- triggerError(error);
265
- }
266
- })().catch((error) => {
267
- triggerError(error);
268
- });
269
- }
270
- );
271
-
272
- /**
273
- * Handle non file fields
274
- * These fields will be pushed to the body as array of strings
275
- */
276
- busboy.on('field', (fieldName: string, value: unknown) => {
277
- if (!request.body) request.body = {};
90
+ return next(
91
+ new WebError({
92
+ statusCode: 400,
93
+ errorCode: 'missing-field',
94
+ message: 'Some required files have not been uploaded',
95
+ data: { missingFiles, uploadedFileKeys }
96
+ })
97
+ );
98
+ }
278
99
 
279
- if (request.body[fieldName]) {
280
- request.body[fieldName].push(value);
281
- } else {
282
- request.body[fieldName] = [value];
283
- }
284
- });
285
- busboy.on('error', function (error: unknown) {
286
- triggerError(error);
287
- });
288
- busboy.on('finish', () => {
289
- (async (): Promise<void> => {
290
- reportDebug({
291
- namespace,
292
- message: 'Busboy finished reading',
293
- data: { hasError, filesOptions, validator, pendingWrites }
294
- });
295
- readFinished = true;
296
- await validateFields();
297
- done();
298
- })().catch((error) => {
299
- triggerError(error);
100
+ next();
300
101
  });
301
- });
302
- return request.pipe(busboy);
102
+ };
303
103
  }
@@ -1,52 +1,35 @@
1
- import { CompressionOptions } from './compressAvatar';
2
- import { StorageUploadedFile } from 'entities/Storage';
3
-
4
- // Override the Express request object to add the files
1
+ // Extend Express request object to include `files`
5
2
  declare global {
6
3
  // eslint-disable-next-line @typescript-eslint/no-namespace
7
4
  namespace Express {
8
- /** Express request object */
9
5
  interface Request {
10
- /** Configuration for files that are uploaded with the request */
11
- files?: { [key: string]: Array<UploadedFile> };
6
+ /** Uploaded files mapped by field name */
7
+ files?:
8
+ | { [key: string]: Express.Multer.File[] }
9
+ | Express.Multer.File[]
10
+ | undefined;
12
11
  }
13
12
  }
14
13
  }
15
14
 
16
15
  /** Custom uploaded file metadata */
17
- export interface UploadedFile extends StorageUploadedFile {
18
- /** File name that was given to the file by the client */
16
+ export interface UploadedFile extends Express.Multer.File {
17
+ /** Original file name (use `originalname` from `Express.Multer.File`) */
19
18
  originalFileName: string;
19
+ /** File data buffer (use `buffer` from `Express.Multer.File`) */
20
+ file: Buffer;
20
21
  }
21
22
 
22
- /** Outputs setting for the file upload. Defines where a file should be stored and how it should be processed. */
23
- interface FileUploaderOutputs {
24
- /** Name of the output */
25
- name: string;
26
- /** Path to which this specific output is stored (defaults to default path if not set) */
27
- path?: string;
28
- /** Bucket to which this specific output is stored (defaults to default bucket if not set) */
29
- bucket?: string;
30
- }
31
-
32
- /** Configuration for a file upload */
23
+ /** Configuration for file upload */
33
24
  export interface FileUploaderConfig {
34
- /** Field key for which this configuration applies */
25
+ /** Field name expected in the request */
35
26
  key: string;
36
- /** A list of supported MIME Types for the file */
27
+ /** Allowed MIME types */
37
28
  mimeTypes?: Array<string>;
38
- /** Base path where the file should be stored (default used when no output config is set, will be used as temporary storage when outputs are set) */
39
- path?: string;
40
- /** Bucket in which the file should be stored (will be used as temporary storage when outputs are set) */
41
- bucket: string;
42
- /** Maximum size of the file (ex: 2MB) */
29
+ /** Maximum file size (e.g., "2MB") */
43
30
  maxSize?: string;
44
- /** Indicates that the file must be present */
31
+ /** Whether the file is required */
45
32
  required?: boolean;
46
- /** Do not store with an extension (will simply be a uuid) */
33
+ /** Whether to store without an extension */
47
34
  noExtension?: boolean;
48
- /** Specify multiple outputs with processing */
49
- outputs?: Array<FileUploaderOutputs>;
50
- /** @deprecated compression of images */
51
- image?: CompressionOptions;
52
35
  }
package/test/env.ts CHANGED
@@ -47,5 +47,6 @@ process.env = {
47
47
  SQL_DB: 'testDb',
48
48
  SQL_TYPE: 'mysql',
49
49
  DEFAULT_MESSAGE_AUDIENCE: audience,
50
+ GOOGLE_AI_KEY: 'AIKEY',
50
51
  ...process.env
51
52
  };
@@ -1,6 +0,0 @@
1
- import { Readable } from 'stream';
2
- import { CompressionOptions } from './compressAvatar.types';
3
- /**
4
- * Compress the user's avatar before uploading
5
- */
6
- export declare function compressAvatar(fileStream: Readable, options: CompressionOptions): Readable;
@@ -1,28 +0,0 @@
1
- "use strict";
2
-
3
- var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
- Object.defineProperty(exports, "__esModule", {
5
- value: true
6
- });
7
- exports.compressAvatar = compressAvatar;
8
- var _sharp = _interopRequireDefault(require("sharp"));
9
- /**
10
- * Compress the user's avatar before uploading
11
- */
12
- function compressAvatar(fileStream, options) {
13
- const {
14
- resize: {
15
- height,
16
- width
17
- },
18
- quality
19
- } = options;
20
- const transform = (0, _sharp.default)().resize(width, height, {
21
- fit: 'cover'
22
- }).toFormat('jpeg', {
23
- quality: quality
24
- });
25
- fileStream.pipe(transform);
26
- return fileStream;
27
- }
28
- //# sourceMappingURL=compressAvatar.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"compressAvatar.js","names":["_sharp","_interopRequireDefault","require","compressAvatar","fileStream","options","resize","height","width","quality","transform","sharp","fit","toFormat","pipe"],"sources":["../../../../src/middleware/fileUploader/compressAvatar/compressAvatar.ts"],"sourcesContent":["import { Readable } from 'stream';\nimport sharp from 'sharp';\nimport { CompressionOptions } from './compressAvatar.types';\n\n/**\n * Compress the user's avatar before uploading\n */\nexport function compressAvatar(\n fileStream: Readable,\n options: CompressionOptions\n): Readable {\n const {\n resize: { height, width },\n quality\n } = options;\n const transform = sharp()\n .resize(width, height, {\n fit: 'cover'\n })\n .toFormat('jpeg', {\n quality: quality\n });\n fileStream.pipe(transform);\n return fileStream;\n}\n"],"mappings":";;;;;;;AACA,IAAAA,MAAA,GAAAC,sBAAA,CAAAC,OAAA;AAGA;AACA;AACA;AACO,SAASC,cAAcA,CAC5BC,UAAoB,EACpBC,OAA2B,EACjB;EACV,MAAM;IACJC,MAAM,EAAE;MAAEC,MAAM;MAAEC;IAAM,CAAC;IACzBC;EACF,CAAC,GAAGJ,OAAO;EACX,MAAMK,SAAS,GAAG,IAAAC,cAAK,EAAC,CAAC,CACtBL,MAAM,CAACE,KAAK,EAAED,MAAM,EAAE;IACrBK,GAAG,EAAE;EACP,CAAC,CAAC,CACDC,QAAQ,CAAC,MAAM,EAAE;IAChBJ,OAAO,EAAEA;EACX,CAAC,CAAC;EACJL,UAAU,CAACU,IAAI,CAACJ,SAAS,CAAC;EAC1B,OAAON,UAAU;AACnB","ignoreList":[]}
@@ -1,12 +0,0 @@
1
- /** Options to compress image */
2
- export interface CompressionOptions {
3
- /** Set dimensions in which the image will be resized */
4
- resize: {
5
- /** Height of the resized image */
6
- height: number;
7
- /** Width of the resized image */
8
- width: number;
9
- };
10
- /** JPG quality factor to apply when compressing */
11
- quality: number;
12
- }
@@ -1,2 +0,0 @@
1
- "use strict";
2
- //# sourceMappingURL=compressAvatar.types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"compressAvatar.types.js","names":[],"sources":["../../../../src/middleware/fileUploader/compressAvatar/compressAvatar.types.ts"],"sourcesContent":["/** Options to compress image */\nexport interface CompressionOptions {\n /** Set dimensions in which the image will be resized */\n resize: {\n /** Height of the resized image */\n height: number;\n /** Width of the resized image */\n width: number;\n };\n /** JPG quality factor to apply when compressing */\n quality: number;\n}\n"],"mappings":"","ignoreList":[]}
@@ -1,2 +0,0 @@
1
- export * from './compressAvatar';
2
- export * from './compressAvatar.types';
@@ -1,28 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- var _compressAvatar = require("./compressAvatar");
7
- Object.keys(_compressAvatar).forEach(function (key) {
8
- if (key === "default" || key === "__esModule") return;
9
- if (key in exports && exports[key] === _compressAvatar[key]) return;
10
- Object.defineProperty(exports, key, {
11
- enumerable: true,
12
- get: function () {
13
- return _compressAvatar[key];
14
- }
15
- });
16
- });
17
- var _compressAvatar2 = require("./compressAvatar.types");
18
- Object.keys(_compressAvatar2).forEach(function (key) {
19
- if (key === "default" || key === "__esModule") return;
20
- if (key in exports && exports[key] === _compressAvatar2[key]) return;
21
- Object.defineProperty(exports, key, {
22
- enumerable: true,
23
- get: function () {
24
- return _compressAvatar2[key];
25
- }
26
- });
27
- });
28
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":["_compressAvatar","require","Object","keys","forEach","key","exports","defineProperty","enumerable","get","_compressAvatar2"],"sources":["../../../../src/middleware/fileUploader/compressAvatar/index.ts"],"sourcesContent":["export * from './compressAvatar';\nexport * from './compressAvatar.types';\n"],"mappings":";;;;;AAAA,IAAAA,eAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,eAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAL,eAAA,CAAAK,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAT,eAAA,CAAAK,GAAA;IAAA;EAAA;AAAA;AACA,IAAAK,gBAAA,GAAAT,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAO,gBAAA,EAAAN,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAK,gBAAA,CAAAL,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAC,gBAAA,CAAAL,GAAA;IAAA;EAAA;AAAA","ignoreList":[]}
@@ -1,25 +0,0 @@
1
- import { Readable } from 'stream';
2
- import sharp from 'sharp';
3
- import { CompressionOptions } from './compressAvatar.types';
4
-
5
- /**
6
- * Compress the user's avatar before uploading
7
- */
8
- export function compressAvatar(
9
- fileStream: Readable,
10
- options: CompressionOptions
11
- ): Readable {
12
- const {
13
- resize: { height, width },
14
- quality
15
- } = options;
16
- const transform = sharp()
17
- .resize(width, height, {
18
- fit: 'cover'
19
- })
20
- .toFormat('jpeg', {
21
- quality: quality
22
- });
23
- fileStream.pipe(transform);
24
- return fileStream;
25
- }
@@ -1,12 +0,0 @@
1
- /** Options to compress image */
2
- export interface CompressionOptions {
3
- /** Set dimensions in which the image will be resized */
4
- resize: {
5
- /** Height of the resized image */
6
- height: number;
7
- /** Width of the resized image */
8
- width: number;
9
- };
10
- /** JPG quality factor to apply when compressing */
11
- quality: number;
12
- }
@@ -1,2 +0,0 @@
1
- export * from './compressAvatar';
2
- export * from './compressAvatar.types';