barehttp 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +185 -28
  2. package/lib/context/execution.d.ts +7 -0
  3. package/lib/context/execution.js +14 -0
  4. package/lib/context/index.d.ts +10 -0
  5. package/lib/context/index.js +46 -0
  6. package/lib/env.d.ts +5 -0
  7. package/lib/env.js +5 -0
  8. package/lib/index.d.ts +5 -0
  9. package/lib/index.js +3 -0
  10. package/lib/logger/index.d.ts +16 -0
  11. package/lib/logger/index.js +26 -0
  12. package/lib/logger/serializers.d.ts +28 -0
  13. package/lib/logger/serializers.js +78 -0
  14. package/lib/middlewares/cookies/cookie-manager.d.ts +25 -0
  15. package/lib/middlewares/cookies/cookie-manager.js +68 -0
  16. package/lib/middlewares/cookies/signer.d.ts +8 -0
  17. package/lib/middlewares/cookies/signer.js +25 -0
  18. package/lib/middlewares/cors/cors.d.ts +38 -0
  19. package/lib/middlewares/cors/cors.js +164 -0
  20. package/lib/request.d.ts +84 -0
  21. package/lib/request.js +260 -0
  22. package/lib/schemas/custom-schema.d.ts +32 -0
  23. package/lib/schemas/custom-schema.js +62 -0
  24. package/lib/schemas/dirty-tsm.d.ts +1 -0
  25. package/lib/schemas/dirty-tsm.js +199 -0
  26. package/lib/schemas/generator.d.ts +7 -0
  27. package/lib/schemas/generator.js +179 -0
  28. package/lib/schemas/helpers.d.ts +27 -0
  29. package/lib/schemas/helpers.js +40 -0
  30. package/lib/schemas/json-schema.d.ts +2 -0
  31. package/lib/schemas/json-schema.js +48 -0
  32. package/lib/schemas/openami-schema.d.ts +2 -0
  33. package/lib/schemas/openami-schema.js +59 -0
  34. package/lib/schemas/project.d.ts +1 -0
  35. package/lib/schemas/project.js +1 -0
  36. package/lib/server.d.ts +154 -0
  37. package/lib/server.js +396 -0
  38. package/lib/utils/content-type.d.ts +54 -0
  39. package/lib/utils/content-type.js +54 -0
  40. package/lib/utils/http-methods.d.ts +11 -0
  41. package/lib/utils/http-methods.js +9 -0
  42. package/lib/utils/index.d.ts +4 -0
  43. package/lib/utils/index.js +4 -0
  44. package/lib/utils/safe-json.d.ts +2 -0
  45. package/lib/utils/safe-json.js +18 -0
  46. package/lib/utils/status-codes.d.ts +339 -0
  47. package/lib/utils/status-codes.js +339 -0
  48. package/lib/utils/status-phrases.d.ts +338 -0
  49. package/lib/utils/status-phrases.js +339 -0
  50. package/lib/websocket.d.ts +36 -0
  51. package/lib/websocket.js +176 -0
  52. package/package.json +64 -32
  53. package/.eslintrc.js +0 -47
  54. package/.github/workflows/release.yml +0 -27
  55. package/.jest-setup.js +0 -1
  56. package/jest.config.js +0 -8
  57. package/prettier.config.js +0 -6
  58. package/src/context/context.test.ts +0 -30
  59. package/src/context/execution.ts +0 -17
  60. package/src/context/index.ts +0 -61
  61. package/src/env.ts +0 -5
  62. package/src/examples/bare-http.ts +0 -36
  63. package/src/examples/express.ts +0 -11
  64. package/src/examples/fastify.ts +0 -18
  65. package/src/index.ts +0 -4
  66. package/src/logger/index.ts +0 -67
  67. package/src/logger/serializers.test.ts +0 -186
  68. package/src/logger/serializers.ts +0 -109
  69. package/src/middlewares/cookies/cookie-manager.ts +0 -86
  70. package/src/middlewares/cookies/signer.ts +0 -30
  71. package/src/report.ts +0 -25
  72. package/src/request.test.ts +0 -143
  73. package/src/request.ts +0 -277
  74. package/src/server.integration.test.ts +0 -296
  75. package/src/server.middlewares.test.ts +0 -93
  76. package/src/server.routes.test.ts +0 -71
  77. package/src/server.ts +0 -450
  78. package/src/utils/content-type.ts +0 -59
  79. package/src/utils/index.ts +0 -2
  80. package/src/utils/safe-json.ts +0 -17
  81. package/src/utils/status-codes.ts +0 -339
  82. package/src/utils/status-phrases.ts +0 -339
  83. package/tsconfig.json +0 -24
@@ -0,0 +1,68 @@
1
+ import { serialize } from 'cookie';
2
+ import { secretsOperator } from './signer.js';
3
+ import { logMe } from '../../logger/index.js';
4
+ export class CookiesManager {
5
+ options;
6
+ flow;
7
+ signer;
8
+ constructor(options = {}, flow) {
9
+ this.options = options;
10
+ this.flow = flow;
11
+ const secret = this.options.secret || '';
12
+ const enableRotation = Array.isArray(secret);
13
+ this.signer = typeof secret === 'string' || enableRotation ? secretsOperator(secret) : null;
14
+ }
15
+ setCookie(name, value, options, signer) {
16
+ const localSigner = signer || this.signer;
17
+ const opts = options || this.options;
18
+ const { signed, ...rest } = opts;
19
+ const normalizedExpires = typeof rest.expires === 'number' ? new Date(rest.expires) : rest.expires;
20
+ const serializeOptions = {
21
+ ...rest,
22
+ expires: normalizedExpires,
23
+ };
24
+ if (signed && localSigner) {
25
+ value = localSigner.sign(value);
26
+ }
27
+ const serialized = serialize(name, value, serializeOptions);
28
+ let setCookie = this.flow.getHeader('Set-Cookie');
29
+ if (!setCookie) {
30
+ this.flow.setHeader('Set-Cookie', serialized);
31
+ return;
32
+ }
33
+ if (typeof setCookie === 'string') {
34
+ setCookie = [setCookie];
35
+ }
36
+ setCookie.push(serialized);
37
+ this.flow.setHeader('Set-Cookie', setCookie);
38
+ }
39
+ clearCookie(name, options = {}) {
40
+ const opts = {
41
+ path: '/',
42
+ ...options,
43
+ expires: new Date(1),
44
+ signed: undefined,
45
+ maxAge: undefined,
46
+ };
47
+ return this.setCookie(name, '', opts);
48
+ }
49
+ parseCookie(rawCookie) {
50
+ if (!rawCookie)
51
+ return {};
52
+ const result = {};
53
+ const values = rawCookie?.split(';');
54
+ for (let i = 0; i < values.length - 1; i++) {
55
+ const split = values[i].trim().split('=');
56
+ if (split.length == 2)
57
+ result[split[0]] = split[1];
58
+ }
59
+ return result;
60
+ }
61
+ unsignCookie(value) {
62
+ if (!this.signer) {
63
+ logMe.error('No signer defined for the cookies, unsign wont work');
64
+ return;
65
+ }
66
+ return this.signer.unsign(value);
67
+ }
68
+ }
@@ -0,0 +1,8 @@
1
+ export declare function secretsOperator(secret: string | string[]): {
2
+ sign(value: string): string;
3
+ unsign(signedValue: any): {
4
+ valid: boolean;
5
+ renew: boolean;
6
+ value: string | null;
7
+ };
8
+ };
@@ -0,0 +1,25 @@
1
+ import cookieSignature from 'cookie-signature';
2
+ export function secretsOperator(secret) {
3
+ const secrets = Array.isArray(secret) ? secret : [secret];
4
+ const [signingKey] = secrets;
5
+ return {
6
+ sign(value) {
7
+ return cookieSignature.sign(value, signingKey);
8
+ },
9
+ unsign(signedValue) {
10
+ let valid = false;
11
+ let renew = false;
12
+ let value = null;
13
+ for (const key of secrets) {
14
+ const result = cookieSignature.unsign(signedValue, key);
15
+ if (result !== false) {
16
+ valid = true;
17
+ renew = key !== signingKey;
18
+ value = result;
19
+ break;
20
+ }
21
+ }
22
+ return { valid, renew, value };
23
+ },
24
+ };
25
+ }
@@ -0,0 +1,38 @@
1
+ import type { HttpMethodsUnionUppercase } from '../../utils/index.js';
2
+ import type { BareRequest } from '../../request.js';
3
+ export type CorsOptions = {
4
+ origin?: string | RegExp;
5
+ methods?: Array<HttpMethodsUnionUppercase>;
6
+ preflightContinue?: boolean;
7
+ optionsSuccessStatus?: 200 | 201 | 202 | 203 | 204;
8
+ allowedHeaders?: string | string[];
9
+ exposedHeaders?: string | string[];
10
+ credentials?: boolean;
11
+ maxAge?: number;
12
+ };
13
+ export declare class Cors {
14
+ options: CorsOptions;
15
+ defaults: {
16
+ origin: string;
17
+ methods: string[];
18
+ preflightContinue: boolean;
19
+ optionsSuccessStatus: number;
20
+ };
21
+ computedHeaders: {
22
+ credentials: {};
23
+ methods: {};
24
+ maxAge: {};
25
+ exposedHeaders: {};
26
+ };
27
+ constructor(corsOptions?: CorsOptions);
28
+ private computeStaticHeaders;
29
+ private isString;
30
+ private isOriginAllowed;
31
+ private configureOrigin;
32
+ private configureMethods;
33
+ private configureCredentials;
34
+ private configureAllowedHeaders;
35
+ private configureExposedHeaders;
36
+ private configureMaxAge;
37
+ corsMiddleware(flow: BareRequest): void;
38
+ }
@@ -0,0 +1,164 @@
1
+ 'use strict';
2
+ export class Cors {
3
+ options;
4
+ defaults = {
5
+ origin: '*',
6
+ methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
7
+ preflightContinue: false,
8
+ optionsSuccessStatus: 204,
9
+ };
10
+ computedHeaders = {
11
+ credentials: {},
12
+ methods: {},
13
+ maxAge: {},
14
+ exposedHeaders: {},
15
+ };
16
+ constructor(corsOptions = {}) {
17
+ this.options = Object.assign({}, this.defaults, corsOptions);
18
+ this.computeStaticHeaders();
19
+ }
20
+ computeStaticHeaders() {
21
+ this.computedHeaders = {
22
+ credentials: this.configureCredentials(),
23
+ methods: this.configureMethods(),
24
+ maxAge: this.configureMaxAge(),
25
+ exposedHeaders: this.configureExposedHeaders(),
26
+ };
27
+ }
28
+ isString(s) {
29
+ return typeof s === 'string' || s instanceof String;
30
+ }
31
+ isOriginAllowed(origin, allowedOrigin) {
32
+ if (Array.isArray(allowedOrigin)) {
33
+ for (let i = 0; i < allowedOrigin.length; ++i) {
34
+ if (this.isOriginAllowed(origin, allowedOrigin[i])) {
35
+ return true;
36
+ }
37
+ }
38
+ return false;
39
+ }
40
+ else if (this.isString(allowedOrigin)) {
41
+ return origin === allowedOrigin;
42
+ }
43
+ else if (allowedOrigin instanceof RegExp) {
44
+ return allowedOrigin.test(origin);
45
+ }
46
+ else {
47
+ return !!allowedOrigin;
48
+ }
49
+ }
50
+ configureOrigin(req) {
51
+ const requestOrigin = req.headers.origin;
52
+ const headers = {};
53
+ let isAllowed;
54
+ if (!this.options.origin || this.options.origin === '*') {
55
+ // allow any origin
56
+ Object.assign(headers, { 'Access-Control-Allow-Origin': '*' });
57
+ }
58
+ else if (this.isString(this.options.origin)) {
59
+ // fixed origin
60
+ Object.assign(headers, {
61
+ 'Access-Control-Allow-Origin': this.options.origin,
62
+ Vary: 'Origin',
63
+ });
64
+ }
65
+ else {
66
+ isAllowed = this.isOriginAllowed(requestOrigin, this.options.origin);
67
+ // reflect origin
68
+ Object.assign(headers, {
69
+ 'Access-Control-Allow-Origin': isAllowed ? requestOrigin : false,
70
+ Vary: 'Origin',
71
+ });
72
+ }
73
+ return headers;
74
+ }
75
+ configureMethods() {
76
+ const { methods } = this.options;
77
+ return {
78
+ 'Access-Control-Allow-Methods': methods,
79
+ };
80
+ }
81
+ configureCredentials() {
82
+ if (this.options.credentials === true) {
83
+ return {
84
+ 'Access-Control-Allow-Credentials': 'true',
85
+ };
86
+ }
87
+ return {};
88
+ }
89
+ configureAllowedHeaders(req) {
90
+ let { allowedHeaders } = this.options;
91
+ const headers = {};
92
+ if (!allowedHeaders) {
93
+ allowedHeaders = req.headers['access-control-request-headers']; // .headers wasn't specified, so reflect the request headers
94
+ Object.assign(headers, {
95
+ Vary: 'Access-Control-Request-Headers',
96
+ });
97
+ }
98
+ else if (Array.isArray(allowedHeaders)) {
99
+ allowedHeaders = allowedHeaders.join(','); // .headers is an array, so turn it into a string
100
+ }
101
+ if (allowedHeaders && allowedHeaders.length) {
102
+ Object.assign(headers, {
103
+ 'Access-Control-Allow-Headers': allowedHeaders,
104
+ });
105
+ }
106
+ return headers;
107
+ }
108
+ configureExposedHeaders() {
109
+ let headers = this.options.exposedHeaders;
110
+ if (!headers) {
111
+ return {};
112
+ }
113
+ else if (Array.isArray(headers)) {
114
+ headers = headers.join(','); // .headers is an array, so turn it into a string
115
+ }
116
+ if (headers && headers.length) {
117
+ return {
118
+ 'Access-Control-Expose-Headers': headers,
119
+ };
120
+ }
121
+ return {};
122
+ }
123
+ configureMaxAge() {
124
+ const maxAge = (typeof this.options.maxAge === 'number' || this.options.maxAge) &&
125
+ this.options.maxAge.toString();
126
+ if (maxAge && maxAge.length) {
127
+ return { 'Access-Control-Max-Age': maxAge };
128
+ }
129
+ return {};
130
+ }
131
+ corsMiddleware(flow) {
132
+ let headers = {};
133
+ const req = flow._originalRequest;
134
+ const method = req.method && req.method.toUpperCase && req.method.toUpperCase();
135
+ if (method === 'OPTIONS') {
136
+ // preflight
137
+ headers = {
138
+ ...this.configureOrigin(req),
139
+ ...this.configureAllowedHeaders(req),
140
+ ...this.computedHeaders.credentials,
141
+ ...this.computedHeaders.maxAge,
142
+ ...this.computedHeaders.exposedHeaders,
143
+ ...this.computedHeaders.methods,
144
+ };
145
+ flow.setHeaders(headers);
146
+ if (!this.options.preflightContinue) {
147
+ // Safari (and potentially other browsers) need content-length 0,
148
+ // for 204 or they just hang waiting for a body
149
+ flow.status(this.options.optionsSuccessStatus);
150
+ flow.setHeader('Content-Length', '0');
151
+ flow.send();
152
+ }
153
+ }
154
+ else {
155
+ // actual response
156
+ headers = {
157
+ ...this.configureOrigin(req),
158
+ ...this.computedHeaders.credentials,
159
+ ...this.computedHeaders.exposedHeaders,
160
+ };
161
+ flow.setHeaders(headers);
162
+ }
163
+ }
164
+ }
@@ -0,0 +1,84 @@
1
+ import { StatusCodesUnion } from './utils/index.js';
2
+ import { CookiesManager } from './middlewares/cookies/cookie-manager.js';
3
+ import type { IncomingMessage, ServerResponse } from 'http';
4
+ type Cacheability = 'public' | 'private' | 'no-cache' | 'no-store';
5
+ type ExpirationType = 'max-age' | 's-maxage' | 'max-stale' | 'min-fresh' | 'stale-while-revalidate' | 'stale-if-error';
6
+ type Revalidation = 'must-revalidate' | 'proxy-revalidate' | 'immutable';
7
+ export type CacheOpts = {
8
+ cacheability: Cacheability;
9
+ expirationKind: ExpirationType;
10
+ /**
11
+ * Default 3600
12
+ */
13
+ expirationSeconds?: number;
14
+ revalidation?: Revalidation;
15
+ };
16
+ export declare class BareRequest<H extends {
17
+ [key: string]: string | undefined;
18
+ } = {
19
+ [key: string]: string | undefined;
20
+ }> {
21
+ _originalRequest: IncomingMessage;
22
+ _originalResponse: ServerResponse;
23
+ ID: {
24
+ code: string;
25
+ };
26
+ params: H;
27
+ query: {
28
+ [k: string]: string | undefined;
29
+ };
30
+ remoteIp?: string;
31
+ requestBody?: any;
32
+ requestHeaders: {
33
+ [key: string]: any;
34
+ };
35
+ statusToSend: number;
36
+ cm?: CookiesManager;
37
+ sent: boolean;
38
+ private cache;
39
+ private startTime?;
40
+ private startDate;
41
+ private remoteClient;
42
+ private logging;
43
+ private requestTimeFormat?;
44
+ private headers;
45
+ private cookies;
46
+ private contentType?;
47
+ private timeout?;
48
+ constructor(_originalRequest: IncomingMessage, _originalResponse: ServerResponse, options?: {
49
+ logging?: boolean;
50
+ requestTimeFormat?: 'ms' | 's';
51
+ });
52
+ private readBody;
53
+ private attachCookieManager;
54
+ private populateCookies;
55
+ private classifyRequestBody;
56
+ private setRemoteClient;
57
+ private setRequestTime;
58
+ private cleanHeader;
59
+ private attachTimeout;
60
+ private setParams;
61
+ getHeader(header: string): string | string[];
62
+ getCookie(cookie: string): string;
63
+ getCookies(): {
64
+ [cooke: string]: string;
65
+ };
66
+ disableCache(): void;
67
+ setCache(cacheOpts: CacheOpts): void;
68
+ addHeader(header: string, value: string | number | string[] | number[]): void;
69
+ setHeader(header: string, value: string | number | string[] | number[]): void;
70
+ setHeaders(headers: {
71
+ [header: string]: string | number | string[] | number[];
72
+ }): void;
73
+ addHeaders(headers: {
74
+ [header: string]: string | number | string[] | number[];
75
+ }): void;
76
+ status(status: StatusCodesUnion): this;
77
+ sendStatus(status: StatusCodesUnion): void;
78
+ stream<T extends NodeJS.WritableStream>(stream: T): void;
79
+ json(data: any): void;
80
+ _send(chunk?: string | ArrayBuffer | NodeJS.ArrayBufferView | SharedArrayBuffer): void;
81
+ sendStringifiedJson(data: string): void;
82
+ send(anything?: any): void;
83
+ }
84
+ export {};
package/lib/request.js ADDED
@@ -0,0 +1,260 @@
1
+ import hyperid from 'hyperid';
2
+ import { StatusCodes, StatusPhrases } from './utils/index.js';
3
+ import { JSONParse, JSONStringify } from './utils/safe-json.js';
4
+ import { logHttp, logMe } from './logger/index.js';
5
+ import { CookiesManager } from './middlewares/cookies/cookie-manager.js';
6
+ import { types } from 'util';
7
+ import { Writable } from 'stream';
8
+ import url from 'url';
9
+ const generateId = hyperid();
10
+ const statusTuples = Object.entries(StatusCodes).reduce((acc, [name, status]) => {
11
+ acc[status] = StatusPhrases[name];
12
+ return acc;
13
+ }, {});
14
+ export class BareRequest {
15
+ _originalRequest;
16
+ _originalResponse;
17
+ ID;
18
+ params;
19
+ query = {};
20
+ remoteIp;
21
+ requestBody;
22
+ requestHeaders;
23
+ statusToSend = 200;
24
+ cm;
25
+ sent = false;
26
+ cache = true;
27
+ startTime;
28
+ startDate = new Date();
29
+ remoteClient = '';
30
+ logging = false;
31
+ requestTimeFormat;
32
+ headers = {};
33
+ cookies = {};
34
+ contentType;
35
+ timeout;
36
+ constructor(_originalRequest, _originalResponse, options) {
37
+ this._originalRequest = _originalRequest;
38
+ this._originalResponse = _originalResponse;
39
+ this.params = {};
40
+ this.ID = { code: _originalRequest.headers['x-request-id'] || generateId() };
41
+ this.remoteIp = _originalRequest.socket.remoteAddress;
42
+ this.contentType = this._originalRequest.headers['content-type'];
43
+ this.requestHeaders = this._originalRequest.headers;
44
+ this.logging = options?.logging ?? false;
45
+ // this is a placeholder URL base that we need to make class working
46
+ new url.URL(`http://localhost/${this._originalRequest.url}`).searchParams.forEach((value, name) => (this.query[name] = value));
47
+ // parsed;
48
+ _originalRequest['flow'] = this; // to receive flow object later on in the route handler
49
+ this.addHeaders({
50
+ 'Content-Type': 'text/plain; charset=utf-8',
51
+ 'X-Request-Id': this.ID.code,
52
+ });
53
+ if (options?.requestTimeFormat) {
54
+ this.startTime = process.hrtime();
55
+ this.requestTimeFormat = options.requestTimeFormat;
56
+ }
57
+ }
58
+ readBody() {
59
+ switch (this._originalRequest.method) {
60
+ case 'POST':
61
+ case 'PATCH':
62
+ case 'PUT':
63
+ return new Promise((resolve, reject) => {
64
+ const temp = [];
65
+ this._originalRequest
66
+ .on('data', (chunk) => temp.push(chunk))
67
+ .on('end', () => {
68
+ const parsed = this.classifyRequestBody(temp);
69
+ if (types.isNativeError(parsed))
70
+ return reject(parsed);
71
+ this.requestBody = parsed;
72
+ resolve(parsed);
73
+ })
74
+ .on('error', reject);
75
+ });
76
+ default:
77
+ return;
78
+ }
79
+ }
80
+ attachCookieManager(opts) {
81
+ this.cm = new CookiesManager(opts, this);
82
+ }
83
+ populateCookies() {
84
+ this.cookies = this.cm?.parseCookie(this._originalRequest.headers.cookie) || {};
85
+ }
86
+ classifyRequestBody(data) {
87
+ const wholeChunk = Buffer.concat(data);
88
+ switch (this.contentType) {
89
+ case 'text/plain':
90
+ return wholeChunk.toString();
91
+ case 'application/json':
92
+ return JSONParse(wholeChunk.toString());
93
+ case 'application/x-www-form-urlencoded':
94
+ const store = {};
95
+ for (const curr of wholeChunk.toString().split('&')) {
96
+ const [key, value] = curr.split('=');
97
+ if (!key || !value)
98
+ return null; // form urlencoded is not correct
99
+ store[key] = value;
100
+ }
101
+ return store;
102
+ default:
103
+ return wholeChunk;
104
+ }
105
+ }
106
+ setRemoteClient(remoteClient) {
107
+ this.remoteClient = remoteClient;
108
+ }
109
+ setRequestTime() {
110
+ const diff = process.hrtime(this.startTime);
111
+ const time = diff[0] * (this.requestTimeFormat === 's' ? 1 : 1e3) +
112
+ diff[1] * (this.requestTimeFormat === 's' ? 1e-9 : 1e-6);
113
+ this.setHeaders({
114
+ 'X-Processing-Time': time,
115
+ 'X-Processing-Time-Mode': this.requestTimeFormat === 's' ? 'seconds' : 'milliseconds',
116
+ });
117
+ }
118
+ cleanHeader(header) {
119
+ delete this.headers[header];
120
+ }
121
+ attachTimeout(timeout) {
122
+ if (this.timeout)
123
+ clearTimeout(this.timeout);
124
+ this.timeout = setTimeout(() => {
125
+ this.status(503)._send('Server aborted connection by overtime');
126
+ }, timeout);
127
+ // attach listener to clear the timeout
128
+ this._originalResponse.on('close', () => this.timeout && clearTimeout(this.timeout));
129
+ }
130
+ setParams(params) {
131
+ this.params = params;
132
+ }
133
+ // ======== PUBLIC APIS ========
134
+ getHeader(header) {
135
+ return this.headers[header];
136
+ }
137
+ getCookie(cookie) {
138
+ return this.cookies[cookie];
139
+ }
140
+ getCookies() {
141
+ return { ...this.cookies };
142
+ }
143
+ disableCache() {
144
+ this.cache = false;
145
+ }
146
+ setCache(cacheOpts) {
147
+ const cacheHeader = [];
148
+ const directive = 'Cache-Control';
149
+ if (cacheOpts.cacheability)
150
+ cacheHeader.push(cacheOpts.cacheability);
151
+ if (cacheOpts.expirationKind)
152
+ cacheHeader.push(`${cacheOpts.expirationKind}=${cacheOpts.expirationSeconds ?? 3600}`);
153
+ if (cacheOpts.revalidation)
154
+ cacheHeader.push(cacheOpts.revalidation);
155
+ if (cacheHeader.length > 0)
156
+ this.setHeader(directive, cacheHeader);
157
+ }
158
+ addHeader(header, value) {
159
+ const old = this.headers[header];
160
+ const parsedVal = Array.isArray(value) ? value.join(', ') : '' + value;
161
+ if (old) {
162
+ this.headers[header] += `, ${parsedVal}`;
163
+ }
164
+ else {
165
+ this.headers[header] = parsedVal;
166
+ }
167
+ }
168
+ setHeader(header, value) {
169
+ const parsedVal = Array.isArray(value) ? value.join(', ') : '' + value;
170
+ this.headers[header] = parsedVal;
171
+ }
172
+ setHeaders(headers) {
173
+ for (const [header, value] of Object.entries(headers)) {
174
+ this.setHeader(header, value);
175
+ }
176
+ }
177
+ addHeaders(headers) {
178
+ for (const [header, value] of Object.entries(headers)) {
179
+ this.addHeader(header, value);
180
+ }
181
+ }
182
+ status(status) {
183
+ this.statusToSend = status;
184
+ return this;
185
+ }
186
+ sendStatus(status) {
187
+ this.status(status)._send();
188
+ }
189
+ stream(stream) {
190
+ this._originalResponse.pipe(stream, { end: true });
191
+ }
192
+ json(data) {
193
+ // to generate with fast-json-stringify schema issue #1
194
+ const jsoned = JSONStringify(data);
195
+ this.setHeader('Content-Type', 'application/json');
196
+ this._send(jsoned ? jsoned : undefined);
197
+ }
198
+ _send(chunk) {
199
+ if (this._originalResponse.socket?.destroyed) {
200
+ logMe.error("Tying to send into closed client's stream");
201
+ return;
202
+ }
203
+ if (this._originalResponse.headersSent || this.sent) {
204
+ logMe.error('Trying to send with the headers already sent');
205
+ return;
206
+ }
207
+ // work basic headers
208
+ if (typeof chunk !== 'undefined' && chunk !== null)
209
+ this.setHeader('Content-Length', Buffer.byteLength(chunk, 'utf-8'));
210
+ if (!this.cache)
211
+ this.setHeaders({ 'Cache-Control': 'no-store', Expire: 0, Pragma: 'no-cache' });
212
+ if (this.statusToSend >= 400 && this.statusToSend !== 404 && this.statusToSend !== 410)
213
+ this.cleanHeader('Cache-Control');
214
+ if (this.requestTimeFormat)
215
+ this.setRequestTime();
216
+ // perform sending
217
+ this._originalResponse.writeHead(this.statusToSend, '', this.headers);
218
+ this._originalResponse.end(chunk || statusTuples[this.statusToSend]);
219
+ this.sent = true;
220
+ // call logging section
221
+ if (this.logging === true) {
222
+ logHttp(this.headers, this.startDate, this.remoteClient, this._originalRequest, this._originalResponse);
223
+ }
224
+ }
225
+ sendStringifiedJson(data) {
226
+ this.setHeader('Content-Type', 'application/json');
227
+ this._send(data);
228
+ }
229
+ send(anything) {
230
+ if (this.sent)
231
+ return;
232
+ if (typeof anything === 'undefined' || anything === null)
233
+ return this._send();
234
+ switch (anything.constructor) {
235
+ case Uint8Array:
236
+ case Uint16Array:
237
+ case Uint32Array:
238
+ this._send(Buffer.from(anything.buffer));
239
+ break;
240
+ case Buffer:
241
+ case String:
242
+ this._send(anything);
243
+ break;
244
+ case Boolean:
245
+ case Number:
246
+ this._send('' + anything);
247
+ break;
248
+ case Writable:
249
+ this.stream(anything);
250
+ break;
251
+ case Object:
252
+ case Array:
253
+ this.json(anything);
254
+ break;
255
+ default:
256
+ this._send();
257
+ logMe.warn('Unknown type to send');
258
+ }
259
+ }
260
+ }
@@ -0,0 +1,32 @@
1
+ import { ts, Type } from 'ts-morph';
2
+ export type StringSchemaType = {
3
+ type: 'string';
4
+ nullable: boolean;
5
+ };
6
+ export type NumberSchemaType = {
7
+ type: 'number';
8
+ nullable: boolean;
9
+ };
10
+ export type BooleanSchemaType = {
11
+ type: 'boolean';
12
+ nullable: boolean;
13
+ };
14
+ export type ArraySchemaType = {
15
+ type: 'array';
16
+ items: StringSchemaType | NumberSchemaType | BooleanSchemaType | ArraySchemaType | ObjectSchemaType;
17
+ nullable: boolean;
18
+ };
19
+ export type ObjectSchemaType = {
20
+ type: 'object';
21
+ properties: {
22
+ [key: string]: StringSchemaType | NumberSchemaType | BooleanSchemaType | ArraySchemaType | ObjectSchemaType;
23
+ };
24
+ nullable: boolean;
25
+ };
26
+ export type UnionSchemaType = {
27
+ type: 'union';
28
+ anyOf: CustomSchema[];
29
+ nullable: boolean;
30
+ };
31
+ export type CustomSchema = StringSchemaType | NumberSchemaType | BooleanSchemaType | ArraySchemaType | ObjectSchemaType | UnionSchemaType;
32
+ export declare const generateCustomSchema: (t: Type<ts.Type>) => CustomSchema;