@deepkit/http 1.0.1-alpha.16 → 1.0.1-alpha.160

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 (124) hide show
  1. package/CHANGELOG.md +236 -0
  2. package/dist/cjs/index.d.ts +14 -10
  3. package/dist/cjs/index.js +19 -11
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/src/controllers.d.ts +10 -3
  6. package/dist/cjs/src/controllers.js +7 -3
  7. package/dist/cjs/src/controllers.js.map +1 -1
  8. package/dist/cjs/src/decorator.d.ts +144 -70
  9. package/dist/cjs/src/decorator.js +277 -102
  10. package/dist/cjs/src/decorator.js.map +1 -1
  11. package/dist/cjs/src/filter.d.ts +49 -0
  12. package/dist/cjs/src/filter.js +215 -0
  13. package/dist/cjs/src/filter.js.map +1 -0
  14. package/dist/cjs/src/http-app.d.ts +8 -0
  15. package/dist/cjs/src/http-app.js +16 -0
  16. package/dist/cjs/src/http-app.js.map +1 -0
  17. package/dist/cjs/src/http.d.ts +131 -32
  18. package/dist/cjs/src/http.js +523 -122
  19. package/dist/cjs/src/http.js.map +1 -1
  20. package/dist/cjs/src/kernel.d.ts +50 -9
  21. package/dist/cjs/src/kernel.js +109 -98
  22. package/dist/cjs/src/kernel.js.map +1 -1
  23. package/dist/cjs/src/logger.d.ts +3 -3
  24. package/dist/cjs/src/logger.js +9 -12
  25. package/dist/cjs/src/logger.js.map +1 -1
  26. package/dist/cjs/src/middleware.d.ts +61 -0
  27. package/dist/cjs/src/middleware.js +95 -0
  28. package/dist/cjs/src/middleware.js.map +1 -0
  29. package/dist/cjs/src/model.d.ts +270 -4
  30. package/dist/cjs/src/model.js +401 -3
  31. package/dist/cjs/src/model.js.map +1 -1
  32. package/dist/cjs/src/module.config.d.ts +149 -4
  33. package/dist/cjs/src/module.config.js +11 -6
  34. package/dist/cjs/src/module.config.js.map +1 -1
  35. package/dist/cjs/src/module.d.ts +14 -32
  36. package/dist/cjs/src/module.js +122 -7
  37. package/dist/cjs/src/module.js.map +1 -1
  38. package/dist/cjs/src/request-parser.d.ts +37 -0
  39. package/dist/cjs/src/request-parser.js +382 -0
  40. package/dist/cjs/src/request-parser.js.map +1 -0
  41. package/dist/cjs/src/router.d.ts +174 -75
  42. package/dist/cjs/src/router.js +469 -288
  43. package/dist/cjs/src/router.js.map +1 -1
  44. package/dist/cjs/src/static-serving.d.ts +32 -4
  45. package/dist/cjs/src/static-serving.js +115 -71
  46. package/dist/cjs/src/static-serving.js.map +1 -1
  47. package/dist/cjs/src/utils.js +2 -2
  48. package/dist/cjs/src/utils.js.map +1 -1
  49. package/dist/cjs/tsconfig.tsbuildinfo +1 -2916
  50. package/dist/esm/index.d.ts +14 -10
  51. package/dist/esm/index.js +14 -10
  52. package/dist/esm/index.js.map +1 -1
  53. package/dist/esm/src/controllers.d.ts +10 -3
  54. package/dist/esm/src/controllers.js +7 -3
  55. package/dist/esm/src/controllers.js.map +1 -1
  56. package/dist/esm/src/decorator.d.ts +144 -70
  57. package/dist/esm/src/decorator.js +272 -99
  58. package/dist/esm/src/decorator.js.map +1 -1
  59. package/dist/esm/src/filter.d.ts +49 -0
  60. package/dist/esm/src/filter.js +209 -0
  61. package/dist/esm/src/filter.js.map +1 -0
  62. package/dist/esm/src/http-app.d.ts +8 -0
  63. package/dist/esm/src/http-app.js +12 -0
  64. package/dist/esm/src/http-app.js.map +1 -0
  65. package/dist/esm/src/http.d.ts +131 -32
  66. package/dist/esm/src/http.js +504 -121
  67. package/dist/esm/src/http.js.map +1 -1
  68. package/dist/esm/src/kernel.d.ts +50 -9
  69. package/dist/esm/src/kernel.js +110 -100
  70. package/dist/esm/src/kernel.js.map +1 -1
  71. package/dist/esm/src/logger.d.ts +3 -3
  72. package/dist/esm/src/logger.js +7 -11
  73. package/dist/esm/src/logger.js.map +1 -1
  74. package/dist/esm/src/middleware.d.ts +61 -0
  75. package/dist/esm/src/middleware.js +90 -0
  76. package/dist/esm/src/middleware.js.map +1 -0
  77. package/dist/esm/src/model.d.ts +270 -4
  78. package/dist/esm/src/model.js +357 -1
  79. package/dist/esm/src/model.js.map +1 -1
  80. package/dist/esm/src/module.config.d.ts +149 -4
  81. package/dist/esm/src/module.config.js +9 -5
  82. package/dist/esm/src/module.config.js.map +1 -1
  83. package/dist/esm/src/module.d.ts +14 -32
  84. package/dist/esm/src/module.js +121 -7
  85. package/dist/esm/src/module.js.map +1 -1
  86. package/dist/esm/src/request-parser.d.ts +37 -0
  87. package/dist/esm/src/request-parser.js +372 -0
  88. package/dist/esm/src/request-parser.js.map +1 -0
  89. package/dist/esm/src/router.d.ts +174 -75
  90. package/dist/esm/src/router.js +463 -285
  91. package/dist/esm/src/router.js.map +1 -1
  92. package/dist/esm/src/static-serving.d.ts +32 -4
  93. package/dist/esm/src/static-serving.js +107 -65
  94. package/dist/esm/src/static-serving.js.map +1 -1
  95. package/dist/esm/src/utils.js +1 -0
  96. package/dist/esm/src/utils.js.map +1 -1
  97. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -2916
  98. package/index.ts +14 -10
  99. package/package.json +30 -19
  100. package/src/controllers.ts +4 -3
  101. package/src/decorator.ts +242 -104
  102. package/src/filter.ts +224 -0
  103. package/src/http-app.ts +11 -0
  104. package/src/http.ts +501 -100
  105. package/src/kernel.ts +128 -80
  106. package/src/logger.ts +4 -6
  107. package/src/middleware.ts +116 -0
  108. package/src/model.ts +516 -5
  109. package/src/module.config.ts +168 -5
  110. package/src/module.ts +120 -7
  111. package/src/request-parser.ts +430 -0
  112. package/src/router.ts +546 -317
  113. package/src/static-serving.ts +120 -50
  114. package/src/utils.ts +0 -2
  115. package/tsconfig.esm.json +7 -7
  116. package/tsconfig.json +18 -11
  117. package/tsconfig.spec.json +8 -0
  118. package/dist/cjs/tests/router.spec.d.ts +0 -1
  119. package/dist/cjs/tests/router.spec.js +0 -481
  120. package/dist/cjs/tests/router.spec.js.map +0 -1
  121. package/dist/esm/tests/router.spec.d.ts +0 -1
  122. package/dist/esm/tests/router.spec.js +0 -479
  123. package/dist/esm/tests/router.spec.js.map +0 -1
  124. package/tests/router.spec.ts +0 -347
package/src/model.ts CHANGED
@@ -8,7 +8,12 @@
8
8
  * You should have received a copy of the MIT License along with this program.
9
9
  */
10
10
 
11
- import { IncomingMessage, ServerResponse } from 'http';
11
+ import { IncomingMessage, OutgoingHttpHeader, OutgoingHttpHeaders, ServerResponse } from 'http';
12
+ import { UploadedFile } from './router.js';
13
+ import * as querystring from 'querystring';
14
+ import { Writable } from 'stream';
15
+ import { ReflectionKind, Type, typeAnnotation, ValidationErrorItem } from '@deepkit/type';
16
+ import { asyncOperation, isArray, TypeAnnotation } from '@deepkit/core';
12
17
 
13
18
  export class HttpResponse extends ServerResponse {
14
19
  status(code: number) {
@@ -17,14 +22,414 @@ export class HttpResponse extends ServerResponse {
17
22
  }
18
23
  }
19
24
 
20
- export type HttpRequestQuery = {[name: string]: string};
21
- export type HttpRequestResolvedParameters = {[name: string]: any};
25
+ /**
26
+ * Reads the body of the request and returns it as a Buffer.
27
+ * The result will be cached in the request object as `request.body`.
28
+ *
29
+ * Deepkit's router will automatically use `request.body` if available.
30
+ */
31
+ export function readBody(request: IncomingMessage): Promise<Buffer> {
32
+ return asyncOperation((resolve, reject) => {
33
+ const chunks: Buffer[] = [];
34
+ request.on('data', (chunk: Buffer) => {
35
+ chunks.push(chunk);
36
+ });
37
+ request.on('end', () => {
38
+ const body = Buffer.concat(chunks);
39
+ //the router uses this now instead of trying to read the body again
40
+ (request as HttpRequest).body = body;
41
+ resolve(body);
42
+ });
43
+ request.on('error', (error: Error) => {
44
+ reject(error);
45
+ });
46
+ });
47
+ }
48
+
49
+ export function createRequestWithCachedBody(request: Partial<IncomingMessage>, body: Buffer): HttpRequest {
50
+ const headers = request.headers;
51
+ const method = request.method;
52
+ const url = request.url;
53
+
54
+ const writable = new Writable({
55
+ write(chunk, encoding, callback) {
56
+ callback();
57
+ },
58
+ writev(chunks, callback) {
59
+ callback();
60
+ }
61
+ });
62
+ return new (class extends HttpRequest {
63
+ url = url;
64
+ method = method;
65
+ position = 0;
66
+
67
+ headers = headers as any;
68
+
69
+ _read(size: number) {
70
+ if (this.complete) {
71
+ this.push(null);
72
+ } else {
73
+ //in Node 18 this seems to be necessary to not trigger the abort event
74
+ this.complete = true;
75
+ this.push(body);
76
+ }
77
+ }
78
+ })(writable as any);
79
+ }
80
+
81
+ export type HttpRequestQuery = { [name: string]: string };
82
+ export type HttpRequestResolvedParameters = { [name: string]: any };
83
+ export type HttpRequestPositionedParameters = { arguments: any[], parameters: HttpRequestResolvedParameters };
84
+
85
+ export class BodyValidationError {
86
+ constructor(
87
+ public readonly errors: ValidationErrorItem[] = []
88
+ ) {
89
+ }
90
+
91
+ hasErrors(prefix?: string): boolean {
92
+ return this.getErrors(prefix).length > 0;
93
+ }
94
+
95
+ getErrors(prefix?: string): ValidationErrorItem[] {
96
+ if (prefix) return this.errors.filter(v => v.path.startsWith(prefix));
97
+
98
+ return this.errors;
99
+ }
100
+
101
+ getErrorsForPath(path: string): ValidationErrorItem[] {
102
+ return this.errors.filter(v => v.path === path);
103
+ }
104
+
105
+ getErrorMessageForPath(path: string): string {
106
+ return this.getErrorsForPath(path).map(v => v.toString()).join(', ');
107
+ }
108
+ }
109
+
110
+ export class ValidatedBody<T> {
111
+ constructor(public error: BodyValidationError, public value?: T) {
112
+ }
113
+
114
+ valid(): this is { value: T } {
115
+ return this.value !== undefined;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Marks a parameter as HTTP body and reads the value from the request body.
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * class Controller {
125
+ * @http.GET('/api')
126
+ * route(body: HttpBody<{username: string}>) {
127
+ * //body is {username: string} and required
128
+ * //could also reference an interface or type alias via `HttpBody<MyInterface>`
129
+ * }
130
+ * }
131
+ *
132
+ * // curl /api -d '{"username":"Peter"}'
133
+ */
134
+ export type HttpBody<T> = T & TypeAnnotation<'httpBody'>;
135
+ export type HttpBodyValidation<T> = ValidatedBody<T> & TypeAnnotation<'httpBodyValidation'>;
136
+
137
+ export interface HttpRequestParserOptions {
138
+ withPath?: boolean;
139
+ withBody?: boolean;
140
+ withQuery?: boolean;
141
+ withHeader?: boolean;
142
+ }
143
+
144
+ /**
145
+ * Delays the parsing of the path/body/query/header to the very last moment, when the parameter is actually used.
146
+ *
147
+ * If no options are provided, the parser will receive data from path, header, body, and query, in this order.
148
+ * This basically allows to fetch data from all possible HTTP sources in one go.
149
+ *
150
+ * You can disable various sources by providing the options, e.g. `{withBody: false}` to disable body parsing.
151
+ * Or `{withQuery: false}` to disable query parsing. Or `{withHeader: false}` to disable header parsing.
152
+ * To only parse the body, use `{withQuery: false, withHeader: false}`.
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * async route(parser: HttpRequestParser<{authorization: string}>) {
157
+ * const data = await parser();
158
+ * console.log(data.authorization);
159
+ * }
160
+ * ```
161
+ *
162
+ * Note that the parsers is based on all defined parameters (e.g. `userId: HttpQuery<string>` => {userId: string}),
163
+ * and then starts from there applying header, body, and then query values.
164
+ * This means you also get access to defined path parameters, like:
165
+ *
166
+ * ```typescript
167
+ * @http.GET('teams/:teamId')
168
+ * async route(teamId: string) {
169
+ * //teamId is string
170
+ * }
171
+ *
172
+ * httpWorkflow.onController.listen((event, parser: HttpRequestParser<{teamId: string}>) => {
173
+ * const data = await parser();
174
+ * console.log(data.teamId);
175
+ * });
176
+ * ```
177
+ *
178
+ * HttpRequestParser is necessary in event listeners, since they are instantiated synchronously,
179
+ * but body is parsed asynchronously. So use in event listeners HttpRequestParser instead of HttpBody.
180
+ */
181
+ export type HttpRequestParser<T> = ((options?: HttpRequestParserOptions) => Promise<T>) & TypeAnnotation<'httpRequestParser', T>;
182
+
183
+ /**
184
+ * Marks a parameter as HTTP path and reads the value from the request path.
185
+ * This is normally not requires since the parameter name automatically maps to the path parameter,
186
+ * but in case of requesting the path parameter in for example listeners, this is required.
187
+ * The name option can be used to change the parameter name.
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * app.listen(httpWorkflow.onController, (event, request: HttpRequest, groupId: HttpPath<number>) => {
192
+ * console.log(groupId); //123
193
+ * });
194
+ *
195
+ * class Controller {
196
+ * @http.GET('/api/:groupId')
197
+ * route(groupId: number) {
198
+ * //groupId is number and required
199
+ * //same as `groupId: HttpPath<number>`
200
+ * }
201
+ * }
202
+ * ```
203
+ */
204
+ export type HttpPath<T, Options extends { name?: string } = {}> = T & TypeAnnotation<'httpPath', Options>;
205
+
206
+ /**
207
+ * Marks a parameter as HTTP header and reads the value from the request header.
208
+ *
209
+ * @example
210
+ * ```typescript
211
+ * class Controller {
212
+ * @http.GET('/api')
213
+ * route(authorization: HttpHeader<string>) {
214
+ * //authorization is string and required
215
+ * //use `authorization?: HttpHeader<string>` to make it optional
216
+ * }
217
+ * }
218
+ *
219
+ * // curl /api -H 'Authorization: 123'
220
+ * ```
221
+ *
222
+ * To change the header name, use `param: HttpHeader<string, {name: 'X-My-Header'}>`.
223
+ */
224
+ export type HttpHeader<T, Options extends { name?: string } = {}> = T & TypeAnnotation<'httpHeader', Options>;
225
+
226
+ /**
227
+ * Marks a parameter as HTTP query and reads the value from the request query string.
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * class Controller {
232
+ * @http.GET('/api')
233
+ * route(limit: HttpQuery<number>) {
234
+ * //limit is number and required
235
+ * //use `limit?: HttpQuery<number>` to make it optional
236
+ * }
237
+ * }
238
+ *
239
+ * // curl /api?limit=10
240
+ * ```
241
+ */
242
+ export type HttpQuery<T, Options extends { name?: string } = {}> = T & TypeAnnotation<'httpQuery', Options>;
243
+
244
+ /**
245
+ * Marks a parameter as HTTP query objects and reads multiple values from the request query string into an object.
246
+ *
247
+ * @example
248
+ * ```typescript
249
+ * interface Query {
250
+ * limit?: number;
251
+ * offset?: number;
252
+ * sort?: 'asc' | 'desc';
253
+ * }
254
+ *
255
+ * class Controller {
256
+ * @http.GET('/api')
257
+ * route(query: HttpQueries<Query>) {
258
+ * //query is an object containing limit, offset, and sort
259
+ * }
260
+ *
261
+ * // curl /api?limit=10&offset=20&sort=asc
262
+ */
263
+ export type HttpQueries<T, Options extends { name?: string } = {}> = T & TypeAnnotation<'httpQueries', Options>;
264
+
265
+ /**
266
+ * For all parameters used in the URL path, a regular expression of /[^/]+/ is used. To change that, use getRegExp.
267
+ *
268
+ * @example
269
+ * ```typescript
270
+ *
271
+ * class Controller {
272
+ * @http.GET('/user/:username')
273
+ * route(username: HttpRegExp<string, '.*'>) {
274
+ * //username is string
275
+ * }
276
+ * }
277
+ * ```
278
+ */
279
+ export type HttpRegExp<T, Pattern extends string | RegExp> = T & TypeAnnotation<'httpRegExp', Pattern>;
280
+
281
+ export function getRegExp(type: Type): string | RegExp | undefined {
282
+ const options = typeAnnotation.getType(type, 'httpRegExp');
283
+ if (!options) return;
284
+ if (options.kind === ReflectionKind.literal && 'string' === typeof options.literal) return options.literal;
285
+ if (options.kind === ReflectionKind.literal && options.literal instanceof RegExp) return options.literal;
286
+ return;
287
+ }
288
+
289
+ export class RequestBuilder {
290
+ protected contentBuffer: Buffer = Buffer.alloc(0);
291
+ protected _headers: { [name: string]: string } = {};
292
+ protected queryPath?: string;
293
+
294
+ constructor(
295
+ protected path: string,
296
+ protected method: string = 'GET',
297
+ ) {
298
+ }
299
+
300
+ getUrl() {
301
+ if (this.queryPath) {
302
+ return this.path + '?' + this.queryPath;
303
+ }
304
+ return this.path;
305
+ }
306
+
307
+ build(): HttpRequest {
308
+ return createRequestWithCachedBody({
309
+ method: this.method,
310
+ url: this.getUrl(),
311
+ headers: this._headers,
312
+ }, this.contentBuffer);
313
+ }
314
+
315
+ headers(headers: { [name: string]: string }): this {
316
+ this._headers = headers;
317
+ return this;
318
+ }
319
+
320
+ header(name: string, value: string | number): this {
321
+ this._headers[name] = String(value);
322
+ return this;
323
+ }
324
+
325
+ json(body: object): this {
326
+ this.contentBuffer = Buffer.from(JSON.stringify(body), 'utf8');
327
+ this._headers['content-type'] = 'application/json; charset=utf-8';
328
+ this._headers['content-length'] = String(this.contentBuffer.byteLength);
329
+ return this;
330
+ }
331
+
332
+ multiPart(items: { name: string, file?: Uint8Array, fileName?: string, json?: any }[]): this {
333
+ const boundary = '--------------------------' + Math.random().toString(36).substr(2, 10);
334
+ this._headers['content-type'] = 'multipart/form-data; boundary=' + boundary;
335
+ const parts = items.map(item => {
336
+ if (item.file) {
337
+ const header = Buffer.from(`--${boundary}\r
338
+ Content-Disposition: form-data; name="${item.name}"; filename="${item.fileName || 'file'}"\r
339
+ Content-Type: application/octet-stream\r
340
+ \r\n`, 'utf8');
341
+ return Buffer.concat([header, item.file, Buffer.from('\r\n', 'utf8')]);
342
+ } else if (item.json) {
343
+ const header = Buffer.from(`--${boundary}\r
344
+ Content-Disposition: form-data; name="${item.name}"\r
345
+ Content-Type: application/json\r
346
+ \r\n`, 'utf8');
347
+ return Buffer.concat([header, Buffer.from(JSON.stringify(item.json) + '\r\n', 'utf8')]);
348
+ } else {
349
+ throw new Error('Invalid multiPart item');
350
+ }
351
+ });
352
+ parts.push(Buffer.from(`--${boundary}--`, 'utf8'));
353
+ this.contentBuffer = Buffer.concat(parts);
354
+ this._headers['content-length'] = String(this.contentBuffer.byteLength);
355
+ return this;
356
+ }
357
+
358
+ body(body: string | Buffer): this {
359
+ if ('string' === typeof body) {
360
+ this.contentBuffer = Buffer.from(body, 'utf8');
361
+ } else {
362
+ this.contentBuffer = body;
363
+ }
364
+ this._headers['content-length'] = String(this.contentBuffer.byteLength);
365
+ return this;
366
+ }
367
+
368
+ query(query: any | string): this {
369
+ if ('string' === typeof query) {
370
+ this.queryPath = query;
371
+ } else {
372
+ this.queryPath = querystring.stringify(query);
373
+ }
374
+ return this;
375
+ }
376
+ }
22
377
 
23
378
  export class HttpRequest extends IncomingMessage {
24
379
  /**
25
380
  * A store that can be used to transport data from guards/listeners to ParameterResolvers/controllers.
26
381
  */
27
- public store: {[name: string]: any} = {};
382
+ public store: { [name: string]: any } = {};
383
+
384
+ public uploadedFiles: { [name: string]: UploadedFile } = {};
385
+
386
+ public throwErrorOnNotFound: boolean = false;
387
+
388
+ /**
389
+ * The router sets the body when it was read.
390
+ */
391
+ public body?: Buffer;
392
+
393
+ async readBody(): Promise<Buffer> {
394
+ if (this.body) return this.body;
395
+ return await readBody(this);
396
+ }
397
+
398
+ async readBodyText(): Promise<string> {
399
+ return (await this.readBody()).toString('utf8');
400
+ }
401
+
402
+ static GET(path: string): RequestBuilder {
403
+ return new RequestBuilder(path);
404
+ }
405
+
406
+ static POST(path: string): RequestBuilder {
407
+ return new RequestBuilder(path, 'POST');
408
+ }
409
+
410
+ static OPTIONS(path: string): RequestBuilder {
411
+ return new RequestBuilder(path, 'OPTIONS');
412
+ }
413
+
414
+ static TRACE(path: string): RequestBuilder {
415
+ return new RequestBuilder(path, 'TRACE');
416
+ }
417
+
418
+ static HEAD(path: string): RequestBuilder {
419
+ return new RequestBuilder(path, 'HEAD');
420
+ }
421
+
422
+ static PATCH(path: string): RequestBuilder {
423
+ return new RequestBuilder(path, 'PATCH');
424
+ }
425
+
426
+ static PUT(path: string): RequestBuilder {
427
+ return new RequestBuilder(path, 'PUT');
428
+ }
429
+
430
+ static DELETE(path: string): RequestBuilder {
431
+ return new RequestBuilder(path, 'DELETE');
432
+ }
28
433
 
29
434
  getUrl(): string {
30
435
  return this.url || '/';
@@ -35,6 +440,112 @@ export class HttpRequest extends IncomingMessage {
35
440
  }
36
441
 
37
442
  getRemoteAddress(): string {
38
- return this.connection.remoteAddress || '';
443
+ return this.socket.remoteAddress || '';
444
+ }
445
+ }
446
+
447
+ export function incomingMessageToHttpRequest(request: IncomingMessage): HttpRequest {
448
+ if (request instanceof HttpRequest) return request;
449
+ Object.setPrototypeOf(request, HttpRequest.prototype);
450
+ HttpRequest.constructor.call(request);
451
+ return request as HttpRequest;
452
+ }
453
+
454
+ export function serverResponseToHttpResponse(response: ServerResponse): HttpResponse {
455
+ if (response instanceof HttpResponse) return response;
456
+ Object.setPrototypeOf(response, HttpResponse.prototype);
457
+ MemoryHttpResponse.constructor.call(response);
458
+ return response as HttpResponse;
459
+ }
460
+
461
+ export class MemoryHttpResponse extends HttpResponse {
462
+ public body: Buffer = Buffer.alloc(0);
463
+ public headers: { [name: string]: number | string | string[] | undefined } = Object.create(null);
464
+
465
+ setHeader(name: string, value: number | string | ReadonlyArray<string>): this {
466
+ this.headers[name] = value as any;
467
+ super.setHeader(name, value);
468
+ return this;
469
+ }
470
+
471
+ removeHeader(name: string) {
472
+ delete this.headers[name];
473
+ super.removeHeader(name);
474
+ }
475
+
476
+ getHeader(name: string) {
477
+ return this.headers[name];
478
+ }
479
+
480
+ getHeaders(): OutgoingHttpHeaders {
481
+ return this.headers;
482
+ }
483
+
484
+ writeHead(statusCode: number, headersOrReasonPhrase?: string | OutgoingHttpHeaders | OutgoingHttpHeader[], headers?: OutgoingHttpHeaders | OutgoingHttpHeader[]): this {
485
+ headers = typeof headersOrReasonPhrase === 'string' ? headers : headersOrReasonPhrase;
486
+ if (headers && !isArray(headers)) this.headers = headers;
487
+
488
+ if (typeof headersOrReasonPhrase === 'string') return super.writeHead(statusCode, headersOrReasonPhrase, headers);
489
+ return super.writeHead(statusCode, headers);
490
+ }
491
+
492
+ get json(): any {
493
+ const json = this.bodyString;
494
+ try {
495
+ return JSON.parse(json);
496
+ } catch (error: any) {
497
+ throw new Error(`Could not parse JSON: ${error.message}, body: ${json}`);
498
+ }
499
+ }
500
+
501
+ get text(): string {
502
+ return this.bodyString;
503
+ }
504
+
505
+ get bodyString(): string {
506
+ return this.body.toString('utf8');
507
+ }
508
+
509
+ write(
510
+ chunk: any,
511
+ encoding: any,
512
+ callback?: any
513
+ ): any {
514
+ if (typeof encoding === 'function') {
515
+ callback = encoding;
516
+ encoding = null;
517
+ }
518
+
519
+ if (chunk) {
520
+ if ('string' === typeof chunk) {
521
+ chunk = Buffer.from(chunk, encoding || 'utf8');
522
+ }
523
+ this.body = Buffer.concat([this.body, chunk]);
524
+ }
525
+
526
+ if (callback) callback();
527
+ return true;
528
+ }
529
+
530
+ end(chunk: any, encoding?: any, callback?: any): any {
531
+ if (typeof chunk === 'function') {
532
+ callback = chunk;
533
+ chunk = null;
534
+ encoding = null;
535
+ } else if (typeof encoding === 'function') {
536
+ callback = encoding;
537
+ encoding = null;
538
+ }
539
+
540
+ if (chunk) {
541
+ if ('string' === typeof chunk) {
542
+ chunk = Buffer.from(chunk, encoding || 'utf8');
543
+ }
544
+ this.body = Buffer.concat([this.body, chunk]);
545
+ }
546
+ this.emit('finish');
547
+ super.end(chunk, encoding, callback);
548
+ this.emit('close');
549
+ return this;
39
550
  }
40
551
  }