@tstdl/base 0.90.32 → 0.90.33
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/.eslintrc.json +1 -0
- package/api/server/error-handler.d.ts +2 -2
- package/api/server/error-handler.js +1 -3
- package/api/server/gateway.d.ts +6 -5
- package/api/server/gateway.js +54 -60
- package/api/server/middlewares/allowed-methods.middleware.d.ts +2 -5
- package/api/server/middlewares/allowed-methods.middleware.js +8 -13
- package/api/server/middlewares/catch-error.middleware.d.ts +1 -1
- package/api/server/middlewares/catch-error.middleware.js +4 -5
- package/api/server/middlewares/content-type.middleware.d.ts +2 -0
- package/api/server/middlewares/content-type.middleware.js +15 -0
- package/api/server/middlewares/cors.middleware.js +3 -3
- package/api/server/middlewares/index.d.ts +1 -0
- package/api/server/middlewares/index.js +1 -0
- package/api/server/middlewares/response-time.middleware.d.ts +2 -3
- package/api/server/middlewares/response-time.middleware.js +3 -4
- package/authentication/client/http-client.middleware.js +3 -3
- package/examples/api/basic-overview.d.ts +1 -1
- package/examples/api/basic-overview.js +1 -0
- package/examples/http/client.d.ts +1 -1
- package/examples/http/client.js +1 -0
- package/http/client/http-client.d.ts +4 -4
- package/http/client/http-client.js +69 -59
- package/http/client/middleware.d.ts +8 -4
- package/package.json +1 -1
- package/utils/middleware.d.ts +8 -10
- package/utils/middleware.js +16 -16
package/.eslintrc.json
CHANGED
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
"capitalized-comments": "off",
|
|
87
87
|
"class-methods-use-this": "off",
|
|
88
88
|
"complexity": "off",
|
|
89
|
+
"consistent-return": ["error", { "treatUndefinedAsUnspecified": true }],
|
|
89
90
|
"dot-location": ["error", "property"],
|
|
90
91
|
"eqeqeq": "off",
|
|
91
92
|
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HttpServerResponse } from '../../http/server/index.js';
|
|
1
|
+
import type { HttpServerResponse } from '../../http/server/index.js';
|
|
2
2
|
import type { Logger } from '../../logger/index.js';
|
|
3
3
|
import type { Type } from '../../types.js';
|
|
4
|
-
export declare function handleApiError(error: unknown, supressedErrors: Set<Type<Error>>, logger: Logger): HttpServerResponse;
|
|
4
|
+
export declare function handleApiError(error: unknown, response: HttpServerResponse, supressedErrors: Set<Type<Error>>, logger: Logger): HttpServerResponse;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { HttpServerResponse } from '../../http/server/index.js';
|
|
2
1
|
import { formatError } from '../../utils/format-error.js';
|
|
3
2
|
import { createErrorResponse, getErrorStatusCode, hasErrorHandler } from '../response.js';
|
|
4
|
-
export function handleApiError(error, supressedErrors, logger) {
|
|
5
|
-
const response = new HttpServerResponse();
|
|
3
|
+
export function handleApiError(error, response, supressedErrors, logger) {
|
|
6
4
|
if (error instanceof Error) {
|
|
7
5
|
const errorConstructor = error.constructor;
|
|
8
6
|
const supressed = supressedErrors.has(errorConstructor);
|
package/api/server/gateway.d.ts
CHANGED
|
@@ -15,9 +15,11 @@ export type ApiGatewayMiddlewareContext = {
|
|
|
15
15
|
/** can be undefined if used before allowedMethods middleware */
|
|
16
16
|
endpoint: GatewayEndpoint;
|
|
17
17
|
resourcePatternResult: URLPatternResult;
|
|
18
|
+
request: HttpServerRequest;
|
|
19
|
+
response: HttpServerResponse;
|
|
18
20
|
};
|
|
19
|
-
export type ApiGatewayMiddlewareNext = AsyncMiddlewareNext
|
|
20
|
-
export type ApiGatewayMiddleware = AsyncMiddleware<
|
|
21
|
+
export type ApiGatewayMiddlewareNext = AsyncMiddlewareNext;
|
|
22
|
+
export type ApiGatewayMiddleware = AsyncMiddleware<ApiGatewayMiddlewareContext>;
|
|
21
23
|
export declare abstract class ApiGatewayOptions {
|
|
22
24
|
/**
|
|
23
25
|
* Api prefix. Pass `null` to disable prefix.
|
|
@@ -64,7 +66,7 @@ export declare class ApiGateway implements Resolvable<ApiGatewayOptions> {
|
|
|
64
66
|
private readonly supressedErrors;
|
|
65
67
|
private readonly catchErrorMiddleware;
|
|
66
68
|
private readonly options;
|
|
67
|
-
private
|
|
69
|
+
private composedMiddleware;
|
|
68
70
|
readonly [resolveArgumentType]: ApiGatewayOptions;
|
|
69
71
|
constructor(requestTokenProvider: ApiRequestTokenProvider, logger: Logger, options?: ApiGatewayOptions);
|
|
70
72
|
addMiddleware(middleware: ApiGatewayMiddleware): void;
|
|
@@ -73,6 +75,5 @@ export declare class ApiGateway implements Resolvable<ApiGatewayOptions> {
|
|
|
73
75
|
handleHttpServerRequestContext({ request, respond, close }: HttpServerRequestContext): Promise<void>;
|
|
74
76
|
getApiMetadata(resource: URL): ApiMetadata;
|
|
75
77
|
private updateMiddleware;
|
|
76
|
-
private
|
|
77
|
-
private getBody;
|
|
78
|
+
private endpointMiddleware;
|
|
78
79
|
}
|
package/api/server/gateway.js
CHANGED
|
@@ -29,7 +29,7 @@ import { normalizedApiDefinitionEndpointsEntries } from '../types.js';
|
|
|
29
29
|
import { getFullApiEndpointResource } from '../utils.js';
|
|
30
30
|
import { ApiRequestTokenProvider } from './api-request-token.provider.js';
|
|
31
31
|
import { handleApiError } from './error-handler.js';
|
|
32
|
-
import { allowedMethodsMiddleware,
|
|
32
|
+
import { allowedMethodsMiddleware, getCatchErrorMiddleware, contentTypeMiddleware, corsMiddleware, responseTimeMiddleware } from './middlewares/index.js';
|
|
33
33
|
import { API_MODULE_OPTIONS } from './tokens.js';
|
|
34
34
|
const defaultMaxBytes = 10 * mebibyte;
|
|
35
35
|
export class ApiGatewayOptions {
|
|
@@ -63,7 +63,7 @@ let ApiGateway = class ApiGateway {
|
|
|
63
63
|
supressedErrors;
|
|
64
64
|
catchErrorMiddleware;
|
|
65
65
|
options;
|
|
66
|
-
|
|
66
|
+
composedMiddleware;
|
|
67
67
|
constructor(requestTokenProvider, logger, options = {}) {
|
|
68
68
|
this.requestTokenProvider = requestTokenProvider;
|
|
69
69
|
this.logger = logger;
|
|
@@ -72,7 +72,7 @@ let ApiGateway = class ApiGateway {
|
|
|
72
72
|
this.apis = new Map();
|
|
73
73
|
this.middlewares = options.middlewares ?? [];
|
|
74
74
|
this.supressedErrors = new Set(options.supressedErrors);
|
|
75
|
-
this.catchErrorMiddleware =
|
|
75
|
+
this.catchErrorMiddleware = getCatchErrorMiddleware(this.supressedErrors, logger);
|
|
76
76
|
this.updateMiddleware();
|
|
77
77
|
}
|
|
78
78
|
addMiddleware(middleware) {
|
|
@@ -121,16 +121,18 @@ let ApiGateway = class ApiGateway {
|
|
|
121
121
|
}
|
|
122
122
|
async handleHttpServerRequestContext({ request, respond, close }) {
|
|
123
123
|
let responded = false;
|
|
124
|
+
const response = new HttpServerResponse();
|
|
124
125
|
try {
|
|
125
126
|
const { api, patternResult } = this.getApiMetadata(request.url);
|
|
126
127
|
const endpoint = api.endpoints.get(request.method);
|
|
127
|
-
const
|
|
128
|
+
const context = { api, resourcePatternResult: patternResult, endpoint, request, response };
|
|
129
|
+
await this.composedMiddleware(context);
|
|
128
130
|
responded = true;
|
|
129
|
-
await respond(response);
|
|
131
|
+
await respond(context.response);
|
|
130
132
|
}
|
|
131
133
|
catch (error) {
|
|
132
134
|
try {
|
|
133
|
-
|
|
135
|
+
handleApiError(error, response, this.supressedErrors, this.logger);
|
|
134
136
|
if (responded) {
|
|
135
137
|
await close();
|
|
136
138
|
}
|
|
@@ -160,80 +162,44 @@ let ApiGateway = class ApiGateway {
|
|
|
160
162
|
throw new NotFoundError(`Resource ${resource.pathname} not available.`);
|
|
161
163
|
}
|
|
162
164
|
updateMiddleware() {
|
|
163
|
-
const middlewares = [responseTimeMiddleware, corsMiddleware(this.options.cors), allowedMethodsMiddleware, this.
|
|
164
|
-
this.
|
|
165
|
+
const middlewares = [responseTimeMiddleware, contentTypeMiddleware, this.catchErrorMiddleware, corsMiddleware(this.options.cors), allowedMethodsMiddleware, ...this.middlewares, async (context, next) => this.endpointMiddleware(context, next)];
|
|
166
|
+
this.composedMiddleware = composeAsyncMiddleware(middlewares);
|
|
165
167
|
}
|
|
166
|
-
async
|
|
168
|
+
async endpointMiddleware(context, next) {
|
|
167
169
|
const readBodyOptions = { maxBytes: context.endpoint.definition.maxBytes ?? this.options.defaultMaxBytes ?? defaultMaxBytes };
|
|
168
170
|
const body = isDefined(context.endpoint.definition.body)
|
|
169
|
-
? await
|
|
171
|
+
? await getBody(context.request, readBodyOptions, context.endpoint.definition.body)
|
|
170
172
|
: undefined;
|
|
171
|
-
const bodyAsParameters = (isUndefined(context.endpoint.definition.body) && (request.headers.contentType?.includes('json') == true))
|
|
172
|
-
? await request.body.readAsJson(readBodyOptions)
|
|
173
|
+
const bodyAsParameters = (isUndefined(context.endpoint.definition.body) && (context.request.headers.contentType?.includes('json') == true))
|
|
174
|
+
? await context.request.body.readAsJson(readBodyOptions)
|
|
173
175
|
: undefined;
|
|
174
176
|
if (isDefined(bodyAsParameters) && !isObject(bodyAsParameters)) {
|
|
175
177
|
throw new BadRequestError('Expected json object as body.');
|
|
176
178
|
}
|
|
177
179
|
const decodedUrlParameters = mapObjectValues(context.resourcePatternResult.pathname.groups, (value) => isDefined(value) ? decodeURIComponent(value) : undefined);
|
|
178
|
-
const parameters = { ...request.query.asObject(), ...bodyAsParameters, ...decodedUrlParameters };
|
|
180
|
+
const parameters = { ...context.request.query.asObject(), ...bodyAsParameters, ...decodedUrlParameters };
|
|
179
181
|
const validatedParameters = isDefined(context.endpoint.definition.parameters)
|
|
180
182
|
? Schema.parse(context.endpoint.definition.parameters, parameters)
|
|
181
183
|
: parameters;
|
|
182
184
|
const requestContext = {
|
|
183
185
|
parameters: validatedParameters,
|
|
184
186
|
body,
|
|
185
|
-
request,
|
|
187
|
+
request: context.request,
|
|
186
188
|
getToken: async () => this.requestTokenProvider.getToken(requestContext)
|
|
187
189
|
};
|
|
188
190
|
const result = await context.endpoint.implementation(requestContext);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
: new HttpServerResponse({
|
|
192
|
-
body: isUint8Array(result) ? { buffer: result }
|
|
193
|
-
: isBlob(result) ? { stream: result.stream() }
|
|
194
|
-
: isReadableStream(result) ? { stream: result }
|
|
195
|
-
: (result instanceof ServerSentEventsSource) ? { events: result }
|
|
196
|
-
: (context.endpoint.definition.result == String) ? { text: result }
|
|
197
|
-
: { json: result }
|
|
198
|
-
});
|
|
199
|
-
if (isUndefined(response.headers.contentType)) {
|
|
200
|
-
response.headers.contentType =
|
|
201
|
-
(isDefined(response.body?.json)) ? 'application/json; charset=utf-8'
|
|
202
|
-
: (isDefined(response.body?.text)) ? 'text/plain; charset=utf-8'
|
|
203
|
-
: (isDefined(response.body?.buffer)) ? 'application/octet-stream'
|
|
204
|
-
: (isDefined(response.body?.stream)) ? 'application/octet-stream'
|
|
205
|
-
: (isDefined(response.body?.events)) ? 'text/event-stream'
|
|
206
|
-
: undefined;
|
|
191
|
+
if (result instanceof HttpServerResponse) {
|
|
192
|
+
context.response = result; // eslint-disable-line require-atomic-updates
|
|
207
193
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
else if (schema == Uint8Array) {
|
|
217
|
-
body = await request.body.readAsBuffer(options);
|
|
218
|
-
}
|
|
219
|
-
else if (schema == Blob) {
|
|
220
|
-
const buffer = await request.body.readAsBuffer(options);
|
|
221
|
-
body = new Blob([buffer], { type: request.headers.contentType });
|
|
222
|
-
}
|
|
223
|
-
else if (schema == String) {
|
|
224
|
-
body = await request.body.readAsText(options);
|
|
225
|
-
}
|
|
226
|
-
else if (request.headers.contentType?.startsWith('text') == true) {
|
|
227
|
-
body = await request.body.readAsText(options);
|
|
228
|
-
}
|
|
229
|
-
else if (request.headers.contentType?.includes('json') == true) {
|
|
230
|
-
body = await request.body.readAsJson(options);
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
body = await request.body.readAsBuffer(options);
|
|
234
|
-
}
|
|
194
|
+
else {
|
|
195
|
+
context.response.body = isUint8Array(result) ? { buffer: result } // eslint-disable-line require-atomic-updates
|
|
196
|
+
: isBlob(result) ? { stream: result.stream() }
|
|
197
|
+
: isReadableStream(result) ? { stream: result }
|
|
198
|
+
: (result instanceof ServerSentEventsSource) ? { events: result }
|
|
199
|
+
: (context.endpoint.definition.result == String) ? { text: result }
|
|
200
|
+
: { json: result };
|
|
235
201
|
}
|
|
236
|
-
|
|
202
|
+
await next();
|
|
237
203
|
}
|
|
238
204
|
};
|
|
239
205
|
ApiGateway = __decorate([
|
|
@@ -245,3 +211,31 @@ ApiGateway = __decorate([
|
|
|
245
211
|
__metadata("design:paramtypes", [ApiRequestTokenProvider, Logger, ApiGatewayOptions])
|
|
246
212
|
], ApiGateway);
|
|
247
213
|
export { ApiGateway };
|
|
214
|
+
async function getBody(request, options, schema) {
|
|
215
|
+
let body;
|
|
216
|
+
if (request.hasBody) {
|
|
217
|
+
if (schema == ReadableStream) {
|
|
218
|
+
body = request.body.readAsBinaryStream(options);
|
|
219
|
+
}
|
|
220
|
+
else if (schema == Uint8Array) {
|
|
221
|
+
body = await request.body.readAsBuffer(options);
|
|
222
|
+
}
|
|
223
|
+
else if (schema == Blob) {
|
|
224
|
+
const buffer = await request.body.readAsBuffer(options);
|
|
225
|
+
body = new Blob([buffer], { type: request.headers.contentType });
|
|
226
|
+
}
|
|
227
|
+
else if (schema == String) {
|
|
228
|
+
body = await request.body.readAsText(options);
|
|
229
|
+
}
|
|
230
|
+
else if (request.headers.contentType?.startsWith('text') == true) {
|
|
231
|
+
body = await request.body.readAsText(options);
|
|
232
|
+
}
|
|
233
|
+
else if (request.headers.contentType?.includes('json') == true) {
|
|
234
|
+
body = await request.body.readAsJson(options);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
body = await request.body.readAsBuffer(options);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return Schema.parse(schema, body);
|
|
241
|
+
}
|
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
import type { AsyncMiddlewareNext } from '../../../utils/middleware.js';
|
|
4
|
-
import type { ApiGatewayMiddlewareContext } from '../gateway.js';
|
|
5
|
-
export declare function allowedMethodsMiddleware(request: HttpServerRequest, next: AsyncMiddlewareNext<HttpServerRequest, HttpServerResponse>, context: ApiGatewayMiddlewareContext): Promise<HttpServerResponse>;
|
|
1
|
+
import type { ApiGatewayMiddlewareContext, ApiGatewayMiddlewareNext } from '../gateway.js';
|
|
2
|
+
export declare function allowedMethodsMiddleware({ endpoint, api, request, response }: ApiGatewayMiddlewareContext, next: ApiGatewayMiddlewareNext): Promise<void>;
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import { MethodNotAllowedError } from '../../../errors/method-not-allowed.error.js';
|
|
2
|
-
import { HttpServerResponse } from '../../../http/server/index.js';
|
|
3
2
|
import { isUndefined } from '../../../utils/type-guards.js';
|
|
4
|
-
export async function allowedMethodsMiddleware(request,
|
|
3
|
+
export async function allowedMethodsMiddleware({ endpoint, api, request, response }, next) {
|
|
5
4
|
if (request.method != 'OPTIONS') {
|
|
6
|
-
if (isUndefined(
|
|
7
|
-
throw new MethodNotAllowedError(`Method ${request.method} for resource ${
|
|
5
|
+
if (isUndefined(endpoint)) {
|
|
6
|
+
throw new MethodNotAllowedError(`Method ${request.method} for resource ${api.resource} not available.`);
|
|
8
7
|
}
|
|
9
|
-
return next(
|
|
8
|
+
return next();
|
|
10
9
|
}
|
|
11
|
-
const allowMethods = [...
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
headers: {
|
|
16
|
-
Allow: allowMethods
|
|
17
|
-
}
|
|
18
|
-
});
|
|
10
|
+
const allowMethods = [...api.endpoints.keys()].join(', ');
|
|
11
|
+
response.statusCode = 204;
|
|
12
|
+
response.statusMessage = 'No Content';
|
|
13
|
+
response.headers.setIfMissing('Allow', allowMethods);
|
|
19
14
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { Logger } from '../../../logger/index.js';
|
|
2
2
|
import type { Type } from '../../../types.js';
|
|
3
3
|
import type { ApiGatewayMiddleware } from '../gateway.js';
|
|
4
|
-
export declare function
|
|
4
|
+
export declare function getCatchErrorMiddleware(supressedErrors: Set<Type<Error>>, logger: Logger): ApiGatewayMiddleware;
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { handleApiError } from '../error-handler.js';
|
|
2
|
-
export function
|
|
2
|
+
export function getCatchErrorMiddleware(supressedErrors, logger) {
|
|
3
3
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
4
|
-
async function catchErrorMiddleware(
|
|
4
|
+
async function catchErrorMiddleware(context, next) {
|
|
5
5
|
try {
|
|
6
|
-
|
|
7
|
-
return response;
|
|
6
|
+
await next();
|
|
8
7
|
}
|
|
9
8
|
catch (error) {
|
|
10
|
-
|
|
9
|
+
handleApiError(error, context.response, supressedErrors, logger);
|
|
11
10
|
}
|
|
12
11
|
}
|
|
13
12
|
return catchErrorMiddleware;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { isDefined } from '../../../utils/type-guards.js';
|
|
2
|
+
export async function contentTypeMiddleware(context, next) {
|
|
3
|
+
await next();
|
|
4
|
+
const { response } = context;
|
|
5
|
+
if (isDefined(response.headers.contentType)) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
response.headers.contentType =
|
|
9
|
+
(isDefined(response.body?.json)) ? 'application/json; charset=utf-8'
|
|
10
|
+
: (isDefined(response.body?.text)) ? 'text/plain; charset=utf-8'
|
|
11
|
+
: (isDefined(response.body?.buffer)) ? 'application/octet-stream'
|
|
12
|
+
: (isDefined(response.body?.stream)) ? 'application/octet-stream'
|
|
13
|
+
: (isDefined(response.body?.events)) ? 'text/event-stream'
|
|
14
|
+
: undefined;
|
|
15
|
+
}
|
|
@@ -3,8 +3,9 @@ import { toArray } from '../../../utils/array/array.js';
|
|
|
3
3
|
import { isDefined } from '../../../utils/type-guards.js';
|
|
4
4
|
export function corsMiddleware(options = {}) {
|
|
5
5
|
// eslint-disable-next-line max-statements, @typescript-eslint/no-shadow
|
|
6
|
-
async function corsMiddleware(
|
|
7
|
-
|
|
6
|
+
async function corsMiddleware(context, next) {
|
|
7
|
+
await next();
|
|
8
|
+
const { request, response } = context;
|
|
8
9
|
const requestMethod = request.headers.tryGetSingle('Access-Control-Request-Method') ?? request.method;
|
|
9
10
|
const isOptions = (request.method == 'OPTIONS');
|
|
10
11
|
const endpointDefinition = context.api.endpoints.get(requestMethod)?.definition;
|
|
@@ -45,7 +46,6 @@ export function corsMiddleware(options = {}) {
|
|
|
45
46
|
response.headers.setIfMissing('Access-Control-Allow-Origin', origin);
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
|
-
return response;
|
|
49
49
|
}
|
|
50
50
|
return corsMiddleware;
|
|
51
51
|
}
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
export declare function responseTimeMiddleware(request: HttpServerRequest, next: ApiGatewayMiddlewareNext): Promise<HttpServerResponse>;
|
|
1
|
+
import type { ApiGatewayMiddlewareContext, ApiGatewayMiddlewareNext } from '../gateway.js';
|
|
2
|
+
export declare function responseTimeMiddleware({ response }: ApiGatewayMiddlewareContext, next: ApiGatewayMiddlewareNext): Promise<void>;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { round } from '../../../utils/math.js';
|
|
2
2
|
import { Timer } from '../../../utils/timer.js';
|
|
3
|
-
export async function responseTimeMiddleware(
|
|
4
|
-
const timer =
|
|
5
|
-
|
|
3
|
+
export async function responseTimeMiddleware({ response }, next) {
|
|
4
|
+
const timer = Timer.startNew();
|
|
5
|
+
await next();
|
|
6
6
|
const time = round(timer.milliseconds, 2);
|
|
7
7
|
response.headers.set('X-Response-Time', `${time}ms`);
|
|
8
|
-
return response;
|
|
9
8
|
}
|
|
@@ -4,15 +4,15 @@ import { dontWaitForValidToken } from '../authentication.api.js';
|
|
|
4
4
|
export function waitForAuthenticationCredentialsMiddleware(authenticationServiceOrProvider) {
|
|
5
5
|
const getAuthenticationService = cacheAsyncValueOrProvider(authenticationServiceOrProvider);
|
|
6
6
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
7
|
-
async function waitForAuthenticationCredentialsMiddleware(request, next
|
|
8
|
-
const endpoint = context?.endpoint;
|
|
7
|
+
async function waitForAuthenticationCredentialsMiddleware({ request }, next) {
|
|
8
|
+
const endpoint = request.context?.endpoint;
|
|
9
9
|
if ((endpoint?.credentials == true) && (endpoint.data?.[dontWaitForValidToken] != true)) {
|
|
10
10
|
const authenticationService = await getAuthenticationService();
|
|
11
11
|
while (!authenticationService.hasValidToken) {
|
|
12
12
|
await firstValueFrom(authenticationService.validToken$);
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
return next(
|
|
15
|
+
return next();
|
|
16
16
|
}
|
|
17
17
|
return waitForAuthenticationCredentialsMiddleware;
|
|
18
18
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import '../../polyfills.js';
|
|
@@ -8,6 +8,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
10
|
/* eslint-disable max-classes-per-file */
|
|
11
|
+
import '../../polyfills.js';
|
|
11
12
|
import { compileClient } from '../../api/client/index.js';
|
|
12
13
|
import { defineApi } from '../../api/index.js';
|
|
13
14
|
import { apiController, configureApiServer } from '../../api/server/index.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import '../../polyfills.js';
|
package/examples/http/client.js
CHANGED
|
@@ -13,8 +13,9 @@ export declare class HttpClient implements Resolvable<HttpClientArgument> {
|
|
|
13
13
|
private readonly adapter;
|
|
14
14
|
private readonly middleware;
|
|
15
15
|
private readonly headers;
|
|
16
|
-
private readonly
|
|
17
|
-
private
|
|
16
|
+
private readonly internalStartMiddleware;
|
|
17
|
+
private readonly internalEndMiddleware;
|
|
18
|
+
private composedMiddleware;
|
|
18
19
|
readonly options: HttpClientOptions;
|
|
19
20
|
readonly [resolveArgumentType]: HttpClientOptions;
|
|
20
21
|
constructor();
|
|
@@ -51,6 +52,5 @@ export declare class HttpClient implements Resolvable<HttpClientArgument> {
|
|
|
51
52
|
deleteStream(url: string, options?: HttpClientRequestOptions): ReadableStream<Uint8Array>;
|
|
52
53
|
request(method: HttpMethod, url: string, options?: HttpClientRequestOptions): Promise<HttpClientResponse>;
|
|
53
54
|
rawRequest(request: HttpClientRequest): Promise<HttpClientResponse>;
|
|
54
|
-
private
|
|
55
|
-
private prepareRequest;
|
|
55
|
+
private updateMiddleware;
|
|
56
56
|
}
|
|
@@ -15,7 +15,7 @@ import { encodeUtf8 } from '../../utils/encoding.js';
|
|
|
15
15
|
import { composeAsyncMiddleware } from '../../utils/middleware.js';
|
|
16
16
|
import { objectEntries } from '../../utils/object/object.js';
|
|
17
17
|
import { readableStreamFromPromise } from '../../utils/stream/readable-stream-from-promise.js';
|
|
18
|
-
import { isArray, isDefined, isObject, isUndefined } from '../../utils/type-guards.js';
|
|
18
|
+
import { assertDefined, isArray, isDefined, isObject, isUndefined } from '../../utils/type-guards.js';
|
|
19
19
|
import { buildUrl } from '../../utils/url-builder.js';
|
|
20
20
|
import { HttpHeaders } from '../http-headers.js';
|
|
21
21
|
import { HttpError, HttpErrorReason } from '../http.error.js';
|
|
@@ -28,20 +28,24 @@ let HttpClient = class HttpClient {
|
|
|
28
28
|
adapter = inject(HttpClientAdapter);
|
|
29
29
|
middleware = injectAll(HTTP_CLIENT_MIDDLEWARE, undefined, { optional: true });
|
|
30
30
|
headers = new HttpHeaders();
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
internalStartMiddleware;
|
|
32
|
+
internalEndMiddleware;
|
|
33
|
+
composedMiddleware;
|
|
33
34
|
options = injectArgument(this, { optional: true }) ?? inject(HttpClientOptions, undefined, { optional: true }) ?? {};
|
|
34
35
|
constructor() {
|
|
35
|
-
this.
|
|
36
|
+
this.internalStartMiddleware = [
|
|
36
37
|
getBuildRequestUrlMiddleware(this.options.baseUrl),
|
|
37
|
-
addRequestHeadersMiddleware,
|
|
38
38
|
...((this.options.enableErrorHandling ?? true) ? [errorMiddleware] : [])
|
|
39
39
|
];
|
|
40
|
-
this.
|
|
40
|
+
this.internalEndMiddleware = [
|
|
41
|
+
getAddRequestHeadersMiddleware(this.headers),
|
|
42
|
+
getAdapterCallMiddleware(this.adapter)
|
|
43
|
+
];
|
|
44
|
+
this.updateMiddleware();
|
|
41
45
|
}
|
|
42
46
|
addMiddleware(middleware) {
|
|
43
47
|
this.middleware.push(middleware);
|
|
44
|
-
this.
|
|
48
|
+
this.updateMiddleware();
|
|
45
49
|
}
|
|
46
50
|
setDefaultHeader(name, value) {
|
|
47
51
|
this.headers.set(name, value);
|
|
@@ -174,17 +178,13 @@ let HttpClient = class HttpClient {
|
|
|
174
178
|
return this.rawRequest(request);
|
|
175
179
|
}
|
|
176
180
|
async rawRequest(request) {
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
this.callHandler = composeAsyncMiddleware([...this.middleware, ...this.internalMiddleware], async (request) => this.adapter.call(request), { allowMultipleNextCalls: true });
|
|
181
|
+
const context = { request };
|
|
182
|
+
await this.composedMiddleware(context);
|
|
183
|
+
assertDefined(context.response);
|
|
184
|
+
return context.response;
|
|
182
185
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
clone.headers = new HttpHeaders(this.headers);
|
|
186
|
-
clone.headers.setMany(request.headers);
|
|
187
|
-
return clone;
|
|
186
|
+
updateMiddleware() {
|
|
187
|
+
this.composedMiddleware = composeAsyncMiddleware([...this.internalStartMiddleware, ...this.middleware, ...this.internalEndMiddleware], { allowMultipleNextCalls: true });
|
|
188
188
|
}
|
|
189
189
|
};
|
|
190
190
|
HttpClient = __decorate([
|
|
@@ -193,57 +193,62 @@ HttpClient = __decorate([
|
|
|
193
193
|
], HttpClient);
|
|
194
194
|
export { HttpClient };
|
|
195
195
|
function getBuildRequestUrlMiddleware(baseUrl) {
|
|
196
|
-
async function buildUrlParametersMiddleware(request, next) {
|
|
196
|
+
async function buildUrlParametersMiddleware({ request }, next) {
|
|
197
197
|
if (!request.mapParameters) {
|
|
198
|
-
return next(
|
|
198
|
+
return next();
|
|
199
199
|
}
|
|
200
|
-
|
|
201
|
-
return next(
|
|
200
|
+
mapParameters(request, baseUrl);
|
|
201
|
+
return next();
|
|
202
202
|
}
|
|
203
203
|
return buildUrlParametersMiddleware;
|
|
204
204
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
else if (isDefined(body.text)) {
|
|
213
|
-
clone.headers.contentType = 'text/plain';
|
|
214
|
-
}
|
|
215
|
-
else if (isDefined(body.form)) {
|
|
216
|
-
clone.headers.contentType = 'application/x-www-form-urlencoded';
|
|
217
|
-
}
|
|
218
|
-
else if (isDefined(body.blob)) {
|
|
219
|
-
clone.headers.contentType = (body.blob.type.length > 0) ? body.blob.type : 'application/octet-stream';
|
|
205
|
+
function getAddRequestHeadersMiddleware(defaultHeaders) {
|
|
206
|
+
async function addRequestHeadersMiddleware({ request }, next) {
|
|
207
|
+
await next();
|
|
208
|
+
const { body, authorization } = request;
|
|
209
|
+
for (const [key, value] of defaultHeaders) {
|
|
210
|
+
request.headers.setIfMissing(key, value);
|
|
220
211
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
212
|
+
if (isDefined(body) && isUndefined(request.headers.contentType)) {
|
|
213
|
+
if (isDefined(body.json)) {
|
|
214
|
+
request.headers.contentType = 'application/json';
|
|
215
|
+
}
|
|
216
|
+
else if (isDefined(body.text)) {
|
|
217
|
+
request.headers.contentType = 'text/plain';
|
|
218
|
+
}
|
|
219
|
+
else if (isDefined(body.form)) {
|
|
220
|
+
request.headers.contentType = 'application/x-www-form-urlencoded';
|
|
221
|
+
}
|
|
222
|
+
else if (isDefined(body.blob)) {
|
|
223
|
+
request.headers.contentType = (body.blob.type.length > 0) ? body.blob.type : 'application/octet-stream';
|
|
224
|
+
}
|
|
225
|
+
else if (isDefined(body.stream) || isDefined(body.buffer)) {
|
|
226
|
+
request.headers.contentType = 'application/octet-stream';
|
|
227
|
+
}
|
|
232
228
|
}
|
|
233
|
-
|
|
234
|
-
|
|
229
|
+
if (isDefined(authorization) && isUndefined(request.headers.authorization)) {
|
|
230
|
+
if (isDefined(authorization.basic)) {
|
|
231
|
+
const base64 = encodeBase64(encodeUtf8(`${authorization.basic.username}:${authorization.basic.password}`));
|
|
232
|
+
request.headers.authorization = `Basic ${base64}`;
|
|
233
|
+
}
|
|
234
|
+
else if (isDefined(authorization.bearer)) {
|
|
235
|
+
request.headers.authorization = `Bearer ${authorization.bearer}`;
|
|
236
|
+
}
|
|
237
|
+
else if (isDefined(authorization.token)) {
|
|
238
|
+
request.headers.authorization = `Token ${authorization.token}`;
|
|
239
|
+
}
|
|
235
240
|
}
|
|
236
241
|
}
|
|
237
|
-
return
|
|
242
|
+
return addRequestHeadersMiddleware;
|
|
238
243
|
}
|
|
239
|
-
async function errorMiddleware(
|
|
244
|
+
async function errorMiddleware(context, next) {
|
|
240
245
|
try {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
246
|
+
await next();
|
|
247
|
+
assertDefined(context.response);
|
|
248
|
+
if (context.request.throwOnNon200 && ((context.response.statusCode < 200) || (context.response.statusCode >= 400))) {
|
|
249
|
+
const httpError = await HttpError.create(HttpErrorReason.StatusCode, context.request, context.response, `Status code ${context.response.statusCode}.`);
|
|
244
250
|
throw httpError;
|
|
245
251
|
}
|
|
246
|
-
return response;
|
|
247
252
|
}
|
|
248
253
|
catch (error) {
|
|
249
254
|
if (!(error instanceof HttpError) || (error.responseInstance?.headers.contentType?.includes('json') == false)) {
|
|
@@ -263,7 +268,6 @@ async function errorMiddleware(request, next) {
|
|
|
263
268
|
}
|
|
264
269
|
// eslint-disable-next-line max-statements, max-lines-per-function, complexity
|
|
265
270
|
function mapParameters(request, baseUrl) {
|
|
266
|
-
const clone = request.clone();
|
|
267
271
|
const isGetOrHead = (request.method == 'GET') || (request.method == 'HEAD');
|
|
268
272
|
let url;
|
|
269
273
|
const filteredParameterEntries = objectEntries(request.parameters ?? {}).filter(([_, value]) => isDefined(value));
|
|
@@ -278,7 +282,7 @@ function mapParameters(request, baseUrl) {
|
|
|
278
282
|
parameterEntries = new Set(objectEntries(parametersRest));
|
|
279
283
|
}
|
|
280
284
|
if (request.mapParametersToBody && !isGetOrHead && isUndefined(request.body)) {
|
|
281
|
-
|
|
285
|
+
request.body = { json: Object.fromEntries(parameterEntries) };
|
|
282
286
|
parameterEntries.clear();
|
|
283
287
|
}
|
|
284
288
|
if (request.mapParametersToQuery) {
|
|
@@ -306,6 +310,12 @@ function mapParameters(request, baseUrl) {
|
|
|
306
310
|
}
|
|
307
311
|
}
|
|
308
312
|
}
|
|
309
|
-
|
|
310
|
-
|
|
313
|
+
request.url = url.href;
|
|
314
|
+
}
|
|
315
|
+
function getAdapterCallMiddleware(adapter) {
|
|
316
|
+
async function adapterCallMiddleware(context, next) {
|
|
317
|
+
context.response = await adapter.call(context.request); // eslint-disable-line require-atomic-updates
|
|
318
|
+
return next();
|
|
319
|
+
}
|
|
320
|
+
return adapterCallMiddleware;
|
|
311
321
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import type { AsyncMiddleware,
|
|
1
|
+
import type { AsyncMiddleware, AsyncMiddlewareNext, ComposedAsyncMiddleware } from '../../utils/middleware.js';
|
|
2
2
|
import type { HttpClientRequest } from './http-client-request.js';
|
|
3
3
|
import type { HttpClientResponse } from './http-client-response.js';
|
|
4
|
-
export type
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
export type HttpClientMiddlewareContext = {
|
|
5
|
+
request: HttpClientRequest;
|
|
6
|
+
response?: HttpClientResponse;
|
|
7
|
+
};
|
|
8
|
+
export type HttpClientMiddleware = AsyncMiddleware<HttpClientMiddlewareContext>;
|
|
9
|
+
export type HttpClientMiddlewareNext = AsyncMiddlewareNext;
|
|
10
|
+
export type ComposedHttpClientMiddleware = ComposedAsyncMiddleware<HttpClientMiddlewareContext>;
|
package/package.json
CHANGED
package/utils/middleware.d.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
export type ComposedMiddleware<
|
|
2
|
-
export type
|
|
3
|
-
export type
|
|
4
|
-
export type
|
|
5
|
-
export type
|
|
6
|
-
export type
|
|
7
|
-
export type AsyncMiddlewareNext<TIn, TOut> = (value: TIn) => TOut | Promise<TOut>;
|
|
8
|
-
export type AsyncMiddleware<TIn, TOut, Context = unknown> = (value: TIn, next: AsyncMiddlewareNext<TIn, TOut>, context: Context) => TOut | Promise<TOut>;
|
|
1
|
+
export type ComposedMiddleware<Context = unknown> = (context: Context) => void;
|
|
2
|
+
export type MiddlewareNext = () => void;
|
|
3
|
+
export type Middleware<Context = unknown> = (context: Context, next: MiddlewareNext) => void;
|
|
4
|
+
export type ComposedAsyncMiddleware<Context = unknown> = (context: Context) => Promise<void>;
|
|
5
|
+
export type AsyncMiddlewareNext = () => void | Promise<void>;
|
|
6
|
+
export type AsyncMiddleware<Context = unknown> = (context: Context, next: AsyncMiddlewareNext) => void | Promise<void>;
|
|
9
7
|
export type MiddlewareOptions = {
|
|
10
8
|
allowMultipleNextCalls?: boolean;
|
|
11
9
|
};
|
|
12
|
-
export declare function composeMiddleware<
|
|
13
|
-
export declare function composeAsyncMiddleware<
|
|
10
|
+
export declare function composeMiddleware<Context = unknown>(middlewares: Middleware<Context>[], options?: MiddlewareOptions): ComposedMiddleware<Context>;
|
|
11
|
+
export declare function composeAsyncMiddleware<Context>(middlewares: AsyncMiddleware<Context>[], options?: MiddlewareOptions): ComposedAsyncMiddleware<Context>;
|
package/utils/middleware.js
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
export function composeMiddleware(middlewares,
|
|
2
|
-
function composedMiddleware(
|
|
1
|
+
export function composeMiddleware(middlewares, options = {}) {
|
|
2
|
+
function composedMiddleware(context) {
|
|
3
3
|
let currentIndex = -1;
|
|
4
|
-
function dispatch(index
|
|
4
|
+
function dispatch(index) {
|
|
5
5
|
if (index == middlewares.length) {
|
|
6
|
-
return
|
|
6
|
+
return;
|
|
7
7
|
}
|
|
8
8
|
const middleware = middlewares[index];
|
|
9
9
|
currentIndex = index;
|
|
10
|
-
function next(
|
|
10
|
+
function next() {
|
|
11
11
|
if ((index < currentIndex) && (options.allowMultipleNextCalls != true)) {
|
|
12
12
|
throw new Error('next() called multiple times');
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
dispatch(index + 1);
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
middleware(context, next);
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
dispatch(0);
|
|
19
19
|
}
|
|
20
20
|
return composedMiddleware;
|
|
21
21
|
}
|
|
22
|
-
export function composeAsyncMiddleware(middlewares,
|
|
23
|
-
async function composedMiddleware(
|
|
22
|
+
export function composeAsyncMiddleware(middlewares, options = {}) {
|
|
23
|
+
async function composedMiddleware(context) {
|
|
24
24
|
let currentIndex = -1;
|
|
25
|
-
async function dispatch(index
|
|
25
|
+
async function dispatch(index) {
|
|
26
26
|
if (index == middlewares.length) {
|
|
27
|
-
return
|
|
27
|
+
return;
|
|
28
28
|
}
|
|
29
29
|
const middleware = middlewares[index];
|
|
30
30
|
currentIndex = index;
|
|
31
|
-
async function next(
|
|
31
|
+
async function next() {
|
|
32
32
|
if ((index < currentIndex) && (options.allowMultipleNextCalls != true)) {
|
|
33
33
|
throw new Error('next() called multiple times');
|
|
34
34
|
}
|
|
35
|
-
return dispatch(index + 1
|
|
35
|
+
return dispatch(index + 1);
|
|
36
36
|
}
|
|
37
|
-
return middleware(
|
|
37
|
+
return middleware(context, next); // eslint-disable-line consistent-return
|
|
38
38
|
}
|
|
39
|
-
return dispatch(0
|
|
39
|
+
return dispatch(0);
|
|
40
40
|
}
|
|
41
41
|
return composedMiddleware;
|
|
42
42
|
}
|