@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 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);
@@ -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<HttpServerRequest, HttpServerResponse>;
20
- export type ApiGatewayMiddleware = AsyncMiddleware<HttpServerRequest, HttpServerResponse, ApiGatewayMiddlewareContext>;
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 handler;
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 middlewareHandler;
77
- private getBody;
78
+ private endpointMiddleware;
78
79
  }
@@ -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, catchErrorMiddleware, corsMiddleware, responseTimeMiddleware } from './middlewares/index.js';
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
- handler;
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 = catchErrorMiddleware(this.supressedErrors, logger);
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 response = await this.handler(request, { api, resourcePatternResult: patternResult, endpoint });
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
- const response = handleApiError(error, this.supressedErrors, this.logger);
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.catchErrorMiddleware, ...this.middlewares];
164
- this.handler = composeAsyncMiddleware(middlewares, async (request, context) => this.middlewareHandler(request, context));
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 middlewareHandler(request, context) {
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 this.getBody(request, readBodyOptions, context.endpoint.definition.body)
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
- const response = (result instanceof HttpServerResponse)
190
- ? result
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
- return response;
209
- }
210
- async getBody(request, options, schema) {
211
- let body;
212
- if (request.hasBody) {
213
- if (schema == ReadableStream) {
214
- body = request.body.readAsBinaryStream(options);
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
- return Schema.parse(schema, body);
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 { HttpServerRequest } from '../../../http/server/index.js';
2
- import { HttpServerResponse } from '../../../http/server/index.js';
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, next, context) {
3
+ export async function allowedMethodsMiddleware({ endpoint, api, request, response }, next) {
5
4
  if (request.method != 'OPTIONS') {
6
- if (isUndefined(context.endpoint)) {
7
- throw new MethodNotAllowedError(`Method ${request.method} for resource ${context.api.resource} not available.`);
5
+ if (isUndefined(endpoint)) {
6
+ throw new MethodNotAllowedError(`Method ${request.method} for resource ${api.resource} not available.`);
8
7
  }
9
- return next(request);
8
+ return next();
10
9
  }
11
- const allowMethods = [...context.api.endpoints.keys()].join(', ');
12
- return HttpServerResponse.fromObject({
13
- statusCode: 204,
14
- statusMessage: 'No Content',
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 catchErrorMiddleware(supressedErrors: Set<Type<Error>>, logger: Logger): ApiGatewayMiddleware;
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 catchErrorMiddleware(supressedErrors, logger) {
2
+ export function getCatchErrorMiddleware(supressedErrors, logger) {
3
3
  // eslint-disable-next-line @typescript-eslint/no-shadow
4
- async function catchErrorMiddleware(request, next) {
4
+ async function catchErrorMiddleware(context, next) {
5
5
  try {
6
- const response = await next(request);
7
- return response;
6
+ await next();
8
7
  }
9
8
  catch (error) {
10
- return handleApiError(error, supressedErrors, logger);
9
+ handleApiError(error, context.response, supressedErrors, logger);
11
10
  }
12
11
  }
13
12
  return catchErrorMiddleware;
@@ -0,0 +1,2 @@
1
+ import type { ApiGatewayMiddlewareContext, ApiGatewayMiddlewareNext } from '../gateway.js';
2
+ export declare function contentTypeMiddleware(context: ApiGatewayMiddlewareContext, next: ApiGatewayMiddlewareNext): Promise<void>;
@@ -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(request, next, context) {
7
- const response = await next(request);
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,4 +1,5 @@
1
1
  export * from './allowed-methods.middleware.js';
2
2
  export * from './catch-error.middleware.js';
3
+ export * from './content-type.middleware.js';
3
4
  export * from './cors.middleware.js';
4
5
  export * from './response-time.middleware.js';
@@ -1,4 +1,5 @@
1
1
  export * from './allowed-methods.middleware.js';
2
2
  export * from './catch-error.middleware.js';
3
+ export * from './content-type.middleware.js';
3
4
  export * from './cors.middleware.js';
4
5
  export * from './response-time.middleware.js';
@@ -1,3 +1,2 @@
1
- import type { HttpServerRequest, HttpServerResponse } from '../../../http/server/index.js';
2
- import type { ApiGatewayMiddlewareNext } from '../gateway.js';
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(request, next) {
4
- const timer = new Timer(true);
5
- const response = await next(request);
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, context) {
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(request);
15
+ return next();
16
16
  }
17
17
  return waitForAuthenticationCredentialsMiddleware;
18
18
  }
@@ -1 +1 @@
1
- export {};
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
- export {};
1
+ import '../../polyfills.js';
@@ -1,3 +1,4 @@
1
+ import '../../polyfills.js';
1
2
  import { Application } from '../../application/application.js';
2
3
  import { configureUndiciHttpClientAdapter } from '../../http/client/adapters/undici.adapter.js';
3
4
  import { HttpClient } from '../../http/client/index.js';
@@ -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 internalMiddleware;
17
- private callHandler;
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 updateHandlers;
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
- internalMiddleware;
32
- callHandler;
31
+ internalStartMiddleware;
32
+ internalEndMiddleware;
33
+ composedMiddleware;
33
34
  options = injectArgument(this, { optional: true }) ?? inject(HttpClientOptions, undefined, { optional: true }) ?? {};
34
35
  constructor() {
35
- this.internalMiddleware = [
36
+ this.internalStartMiddleware = [
36
37
  getBuildRequestUrlMiddleware(this.options.baseUrl),
37
- addRequestHeadersMiddleware,
38
38
  ...((this.options.enableErrorHandling ?? true) ? [errorMiddleware] : [])
39
39
  ];
40
- this.updateHandlers();
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.updateHandlers();
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 preparedRequest = this.prepareRequest(request);
178
- return this.callHandler(preparedRequest, request.context);
179
- }
180
- updateHandlers() {
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
- prepareRequest(request) {
184
- const clone = request.clone();
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(request);
198
+ return next();
199
199
  }
200
- const modifiedRequest = mapParameters(request, baseUrl);
201
- return next(modifiedRequest);
200
+ mapParameters(request, baseUrl);
201
+ return next();
202
202
  }
203
203
  return buildUrlParametersMiddleware;
204
204
  }
205
- async function addRequestHeadersMiddleware(request, next) {
206
- const clone = request.clone();
207
- const { body, authorization } = clone;
208
- if (isDefined(body) && isUndefined(clone.headers.contentType)) {
209
- if (isDefined(body.json)) {
210
- clone.headers.contentType = 'application/json';
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
- else if (isDefined(body.stream) || isDefined(body.buffer)) {
222
- clone.headers.contentType = 'application/octet-stream';
223
- }
224
- }
225
- if (isDefined(authorization) && isUndefined(clone.headers.authorization)) {
226
- if (isDefined(authorization.basic)) {
227
- const base64 = encodeBase64(encodeUtf8(`${authorization.basic.username}:${authorization.basic.password}`));
228
- clone.headers.authorization = `Basic ${base64}`;
229
- }
230
- else if (isDefined(authorization.bearer)) {
231
- clone.headers.authorization = `Bearer ${authorization.bearer}`;
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
- else if (isDefined(authorization.token)) {
234
- clone.headers.authorization = `Token ${authorization.token}`;
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 next(clone);
242
+ return addRequestHeadersMiddleware;
238
243
  }
239
- async function errorMiddleware(request, next) {
244
+ async function errorMiddleware(context, next) {
240
245
  try {
241
- const response = await next(request);
242
- if (request.throwOnNon200 && ((response.statusCode < 200) || (response.statusCode >= 400))) {
243
- const httpError = await HttpError.create(HttpErrorReason.StatusCode, request, response, `Status code ${response.statusCode}.`);
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
- clone.body = { json: Object.fromEntries(parameterEntries) };
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
- clone.url = url.href;
310
- return clone;
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, AsyncMiddlewareHandler, AsyncMiddlewareNext } from '../../utils/middleware.js';
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 HttpClientHandler = AsyncMiddlewareHandler<HttpClientRequest, HttpClientResponse>;
5
- export type HttpClientMiddleware = AsyncMiddleware<HttpClientRequest, HttpClientResponse>;
6
- export type HttpClientMiddlewareNext = AsyncMiddlewareNext<HttpClientRequest, HttpClientResponse>;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.90.32",
3
+ "version": "0.90.33",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,13 +1,11 @@
1
- export type ComposedMiddleware<TIn, TOut, Context = unknown> = (value: TIn, context: Context) => TOut;
2
- export type MiddlewareHandler<TIn, TOut, Context = unknown> = (value: TIn, context: Context) => TOut;
3
- export type MiddlewareNext<TIn, TOut> = (value: TIn) => TOut;
4
- export type Middleware<TIn, TOut, Context = unknown> = (value: TIn, next: MiddlewareNext<TIn, TOut>, context: Context) => TOut | TOut;
5
- export type ComposedAsyncMiddleware<TIn, TOut, Context = unknown> = (value: TIn, context: Context) => Promise<TOut>;
6
- export type AsyncMiddlewareHandler<TIn, TOut, Context = unknown> = (value: TIn, context: Context) => TOut | Promise<TOut>;
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<TIn, TOut, Context = unknown>(middlewares: Middleware<TIn, TOut, Context>[], handler: MiddlewareHandler<TIn, TOut, Context>, options?: MiddlewareOptions): ComposedMiddleware<TIn, TOut, Context>;
13
- export declare function composeAsyncMiddleware<TIn, TOut, Context>(middlewares: AsyncMiddleware<TIn, TOut, Context>[], handler: AsyncMiddlewareHandler<TIn, TOut, Context>, options?: MiddlewareOptions): ComposedAsyncMiddleware<TIn, TOut, Context>;
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>;
@@ -1,42 +1,42 @@
1
- export function composeMiddleware(middlewares, handler, options = {}) {
2
- function composedMiddleware(value, context) {
1
+ export function composeMiddleware(middlewares, options = {}) {
2
+ function composedMiddleware(context) {
3
3
  let currentIndex = -1;
4
- function dispatch(index, dispatchedValue) {
4
+ function dispatch(index) {
5
5
  if (index == middlewares.length) {
6
- return handler(dispatchedValue, context);
6
+ return;
7
7
  }
8
8
  const middleware = middlewares[index];
9
9
  currentIndex = index;
10
- function next(nextValue) {
10
+ function next() {
11
11
  if ((index < currentIndex) && (options.allowMultipleNextCalls != true)) {
12
12
  throw new Error('next() called multiple times');
13
13
  }
14
- return dispatch(index + 1, nextValue);
14
+ dispatch(index + 1);
15
15
  }
16
- return middleware(dispatchedValue, next, context);
16
+ middleware(context, next);
17
17
  }
18
- return dispatch(0, value);
18
+ dispatch(0);
19
19
  }
20
20
  return composedMiddleware;
21
21
  }
22
- export function composeAsyncMiddleware(middlewares, handler, options = {}) {
23
- async function composedMiddleware(value, context) {
22
+ export function composeAsyncMiddleware(middlewares, options = {}) {
23
+ async function composedMiddleware(context) {
24
24
  let currentIndex = -1;
25
- async function dispatch(index, dispatchedValue) {
25
+ async function dispatch(index) {
26
26
  if (index == middlewares.length) {
27
- return handler(dispatchedValue, context);
27
+ return;
28
28
  }
29
29
  const middleware = middlewares[index];
30
30
  currentIndex = index;
31
- async function next(nextValue) {
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, nextValue);
35
+ return dispatch(index + 1);
36
36
  }
37
- return middleware(dispatchedValue, next, context);
37
+ return middleware(context, next); // eslint-disable-line consistent-return
38
38
  }
39
- return dispatch(0, value);
39
+ return dispatch(0);
40
40
  }
41
41
  return composedMiddleware;
42
42
  }