@twin.org/api-processors 0.0.3-next.2 → 0.0.3-next.21

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # TWIN API Processors
2
2
 
3
- Route processors for use with API servers.
3
+ This package provides reusable request and route processors for logging, context handling, and content negotiation.
4
4
 
5
5
  ## Installation
6
6
 
@@ -36,9 +36,11 @@ export class RestRouteProcessor {
36
36
  * @param response The outgoing response.
37
37
  * @param route The route to process.
38
38
  * @param processorState The state handed through the processors.
39
- * @param loggingComponentType The logging component type for the request.
39
+ * @param componentTypes The component types for the request.
40
+ * @param componentTypes.loggingComponentType The logging component type.
41
+ * @param componentTypes.hostingComponentType The hosting component type.
40
42
  */
41
- async process(request, response, route, processorState, loggingComponentType) {
43
+ async process(request, response, route, processorState, componentTypes) {
42
44
  // Don't handle the route if another processor has already set the response
43
45
  // status code e.g. from an auth processor
44
46
  if (Is.empty(response.statusCode)) {
@@ -54,6 +56,7 @@ export class RestRouteProcessor {
54
56
  else {
55
57
  try {
56
58
  const req = {
59
+ headers: request.headers,
57
60
  pathParams: request.pathParams,
58
61
  query: request.query,
59
62
  body: request.body
@@ -61,10 +64,20 @@ export class RestRouteProcessor {
61
64
  const restRouteResponse = await route.handler({
62
65
  serverRequest: request,
63
66
  processorState,
64
- loggingComponentType
67
+ loggingComponentType: componentTypes?.loggingComponentType,
68
+ hostingComponentType: componentTypes?.hostingComponentType
65
69
  }, req);
66
70
  let statusCode = restRouteResponse.statusCode ?? response.statusCode ?? HttpStatusCode.ok;
67
71
  const headers = restRouteResponse?.headers ?? {};
72
+ const location = headers[HeaderTypes.Location];
73
+ if (restRouteResponse.statusCode === HttpStatusCode.created &&
74
+ Is.stringValue(location) &&
75
+ !location.includes("/")) {
76
+ // If this was a create with a location header and its a plain id
77
+ // then make sure it is encoded to avoid problems such as embedded colons
78
+ // if it contains slashes then we assume it is already encoded correctly
79
+ headers[HeaderTypes.Location] = encodeURIComponent(location);
80
+ }
68
81
  if (Is.empty(restRouteResponse?.body)) {
69
82
  // If there is no custom status code and the body is empty
70
83
  // use the no content response and set the length to 0
@@ -1 +1 @@
1
- {"version":3,"file":"restRouteProcessor.js","sourceRoot":"","sources":["../../../src/data/restRouteProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport {\n\tHttpErrorHelper,\n\ttype IHttpRequest,\n\ttype IHttpResponse,\n\ttype IHttpServerRequest,\n\ttype IRestRoute,\n\ttype IRestRouteProcessor,\n\ttype IRestRouteResponseOptions\n} from \"@twin.org/api-models\";\nimport { Is, NotFoundError } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { HeaderTypes, HttpStatusCode, MimeTypes } from \"@twin.org/web\";\nimport type { IRestRouteProcessorConstructorOptions } from \"../models/IRestRouteProcessorConstructorOptions.js\";\n\n/**\n * Process the REST request and hands it on to the route handler.\n */\nexport class RestRouteProcessor implements IRestRouteProcessor {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<RestRouteProcessor>();\n\n\t/**\n\t * Include the stack with errors.\n\t * @internal\n\t */\n\tprivate readonly _includeErrorStack: boolean;\n\n\t/**\n\t * Create a new instance of RouteProcessor.\n\t * @param options Options for the processor.\n\t */\n\tconstructor(options?: IRestRouteProcessorConstructorOptions) {\n\t\tthis._includeErrorStack = options?.config?.includeErrorStack ?? false;\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn RestRouteProcessor.CLASS_NAME;\n\t}\n\n\t/**\n\t * Process the REST request for the specified route.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param processorState The state handed through the processors.\n\t * @param loggingComponentType The logging component type for the request.\n\t */\n\tpublic async process(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IRestRoute | undefined,\n\t\tprocessorState: { [id: string]: unknown },\n\t\tloggingComponentType?: string\n\t): Promise<void> {\n\t\t// Don't handle the route if another processor has already set the response\n\t\t// status code e.g. from an auth processor\n\t\tif (Is.empty(response.statusCode)) {\n\t\t\tif (Is.empty(route)) {\n\t\t\t\tHttpErrorHelper.buildResponse(\n\t\t\t\t\tresponse,\n\t\t\t\t\t{\n\t\t\t\t\t\tname: NotFoundError.CLASS_NAME,\n\t\t\t\t\t\tmessage: `${RestRouteProcessor.CLASS_NAME}.routeNotFound`,\n\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\tnotFoundId: request.url\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tHttpStatusCode.notFound\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\tconst req: IHttpRequest = {\n\t\t\t\t\t\tpathParams: request.pathParams,\n\t\t\t\t\t\tquery: request.query,\n\t\t\t\t\t\tbody: request.body\n\t\t\t\t\t};\n\n\t\t\t\t\tconst restRouteResponse: IHttpResponse & IRestRouteResponseOptions = await route.handler(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tserverRequest: request,\n\t\t\t\t\t\t\tprocessorState,\n\t\t\t\t\t\t\tloggingComponentType\n\t\t\t\t\t\t},\n\t\t\t\t\t\treq\n\t\t\t\t\t);\n\n\t\t\t\t\tlet statusCode: HttpStatusCode =\n\t\t\t\t\t\trestRouteResponse.statusCode ?? response.statusCode ?? HttpStatusCode.ok;\n\n\t\t\t\t\tconst headers = restRouteResponse?.headers ?? {};\n\n\t\t\t\t\tif (Is.empty(restRouteResponse?.body)) {\n\t\t\t\t\t\t// If there is no custom status code and the body is empty\n\t\t\t\t\t\t// use the no content response and set the length to 0\n\t\t\t\t\t\theaders[HeaderTypes.ContentLength] = \"0\";\n\t\t\t\t\t\t// Only change to no content if the status code is ok\n\t\t\t\t\t\t// This could be something like a created status code\n\t\t\t\t\t\t// which is successful but has no content\n\t\t\t\t\t\tif (statusCode === HttpStatusCode.ok) {\n\t\t\t\t\t\t\tstatusCode = HttpStatusCode.noContent;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Only set the content type if there is a body\n\t\t\t\t\t\t// If there are custom response types for the route then use them\n\t\t\t\t\t\t// instead of the default application/json\n\t\t\t\t\t\theaders[HeaderTypes.ContentType] =\n\t\t\t\t\t\t\trestRouteResponse?.attachment?.mimeType ??\n\t\t\t\t\t\t\trestRouteResponse.headers?.[HeaderTypes.ContentType] ??\n\t\t\t\t\t\t\t`${MimeTypes.Json}; charset=utf-8`;\n\n\t\t\t\t\t\t// If there are filename or inline options set then add the content disposition\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tIs.stringValue(restRouteResponse?.attachment?.filename) ||\n\t\t\t\t\t\t\tIs.boolean(restRouteResponse?.attachment?.inline)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tlet filename = \"\";\n\t\t\t\t\t\t\tif (Is.stringValue(restRouteResponse?.attachment?.filename)) {\n\t\t\t\t\t\t\t\tfilename = `; filename=\"${restRouteResponse?.attachment?.filename}\"`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\theaders[HeaderTypes.ContentDisposition] =\n\t\t\t\t\t\t\t\t`${restRouteResponse?.attachment?.inline ? \"inline\" : \"attachment\"}${filename}`;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// If this is a binary response then set the content length\n\t\t\t\t\t\tif (Is.uint8Array(restRouteResponse?.body)) {\n\t\t\t\t\t\t\tconst contentLength = restRouteResponse.body.length;\n\t\t\t\t\t\t\theaders[HeaderTypes.ContentLength] = contentLength.toString();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tresponse.body = restRouteResponse?.body;\n\t\t\t\t\t}\n\n\t\t\t\t\tresponse.headers = headers;\n\t\t\t\t\tresponse.statusCode = statusCode;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst { error, httpStatusCode } = HttpErrorHelper.processError(\n\t\t\t\t\t\terr,\n\t\t\t\t\t\tthis._includeErrorStack\n\t\t\t\t\t);\n\n\t\t\t\t\tHttpErrorHelper.buildResponse(response, error, httpStatusCode);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"restRouteProcessor.js","sourceRoot":"","sources":["../../../src/data/restRouteProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport {\n\tHttpErrorHelper,\n\ttype IHttpRequest,\n\ttype IHttpResponse,\n\ttype IHttpServerRequest,\n\ttype IRestRoute,\n\ttype IRestRouteProcessor,\n\ttype IRestRouteResponseOptions\n} from \"@twin.org/api-models\";\nimport { Is, NotFoundError } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { HeaderTypes, HttpStatusCode, MimeTypes } from \"@twin.org/web\";\nimport type { IRestRouteProcessorConstructorOptions } from \"../models/IRestRouteProcessorConstructorOptions.js\";\n\n/**\n * Process the REST request and hands it on to the route handler.\n */\nexport class RestRouteProcessor implements IRestRouteProcessor {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<RestRouteProcessor>();\n\n\t/**\n\t * Include the stack with errors.\n\t * @internal\n\t */\n\tprivate readonly _includeErrorStack: boolean;\n\n\t/**\n\t * Create a new instance of RouteProcessor.\n\t * @param options Options for the processor.\n\t */\n\tconstructor(options?: IRestRouteProcessorConstructorOptions) {\n\t\tthis._includeErrorStack = options?.config?.includeErrorStack ?? false;\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn RestRouteProcessor.CLASS_NAME;\n\t}\n\n\t/**\n\t * Process the REST request for the specified route.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param processorState The state handed through the processors.\n\t * @param componentTypes The component types for the request.\n\t * @param componentTypes.loggingComponentType The logging component type.\n\t * @param componentTypes.hostingComponentType The hosting component type.\n\t */\n\tpublic async process(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IRestRoute | undefined,\n\t\tprocessorState: { [id: string]: unknown },\n\t\tcomponentTypes?: {\n\t\t\tloggingComponentType?: string;\n\t\t\thostingComponentType?: string;\n\t\t}\n\t): Promise<void> {\n\t\t// Don't handle the route if another processor has already set the response\n\t\t// status code e.g. from an auth processor\n\t\tif (Is.empty(response.statusCode)) {\n\t\t\tif (Is.empty(route)) {\n\t\t\t\tHttpErrorHelper.buildResponse(\n\t\t\t\t\tresponse,\n\t\t\t\t\t{\n\t\t\t\t\t\tname: NotFoundError.CLASS_NAME,\n\t\t\t\t\t\tmessage: `${RestRouteProcessor.CLASS_NAME}.routeNotFound`,\n\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\tnotFoundId: request.url\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tHttpStatusCode.notFound\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\tconst req: IHttpRequest = {\n\t\t\t\t\t\theaders: request.headers,\n\t\t\t\t\t\tpathParams: request.pathParams,\n\t\t\t\t\t\tquery: request.query,\n\t\t\t\t\t\tbody: request.body\n\t\t\t\t\t};\n\n\t\t\t\t\tconst restRouteResponse: IHttpResponse & IRestRouteResponseOptions = await route.handler(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tserverRequest: request,\n\t\t\t\t\t\t\tprocessorState,\n\t\t\t\t\t\t\tloggingComponentType: componentTypes?.loggingComponentType,\n\t\t\t\t\t\t\thostingComponentType: componentTypes?.hostingComponentType\n\t\t\t\t\t\t},\n\t\t\t\t\t\treq\n\t\t\t\t\t);\n\n\t\t\t\t\tlet statusCode: HttpStatusCode =\n\t\t\t\t\t\trestRouteResponse.statusCode ?? response.statusCode ?? HttpStatusCode.ok;\n\n\t\t\t\t\tconst headers = restRouteResponse?.headers ?? {};\n\n\t\t\t\t\tconst location = headers[HeaderTypes.Location];\n\t\t\t\t\tif (\n\t\t\t\t\t\trestRouteResponse.statusCode === HttpStatusCode.created &&\n\t\t\t\t\t\tIs.stringValue(location) &&\n\t\t\t\t\t\t!location.includes(\"/\")\n\t\t\t\t\t) {\n\t\t\t\t\t\t// If this was a create with a location header and its a plain id\n\t\t\t\t\t\t// then make sure it is encoded to avoid problems such as embedded colons\n\t\t\t\t\t\t// if it contains slashes then we assume it is already encoded correctly\n\t\t\t\t\t\theaders[HeaderTypes.Location] = encodeURIComponent(location);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (Is.empty(restRouteResponse?.body)) {\n\t\t\t\t\t\t// If there is no custom status code and the body is empty\n\t\t\t\t\t\t// use the no content response and set the length to 0\n\t\t\t\t\t\theaders[HeaderTypes.ContentLength] = \"0\";\n\t\t\t\t\t\t// Only change to no content if the status code is ok\n\t\t\t\t\t\t// This could be something like a created status code\n\t\t\t\t\t\t// which is successful but has no content\n\t\t\t\t\t\tif (statusCode === HttpStatusCode.ok) {\n\t\t\t\t\t\t\tstatusCode = HttpStatusCode.noContent;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Only set the content type if there is a body\n\t\t\t\t\t\t// If there are custom response types for the route then use them\n\t\t\t\t\t\t// instead of the default application/json\n\t\t\t\t\t\theaders[HeaderTypes.ContentType] =\n\t\t\t\t\t\t\trestRouteResponse?.attachment?.mimeType ??\n\t\t\t\t\t\t\trestRouteResponse.headers?.[HeaderTypes.ContentType] ??\n\t\t\t\t\t\t\t`${MimeTypes.Json}; charset=utf-8`;\n\n\t\t\t\t\t\t// If there are filename or inline options set then add the content disposition\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tIs.stringValue(restRouteResponse?.attachment?.filename) ||\n\t\t\t\t\t\t\tIs.boolean(restRouteResponse?.attachment?.inline)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tlet filename = \"\";\n\t\t\t\t\t\t\tif (Is.stringValue(restRouteResponse?.attachment?.filename)) {\n\t\t\t\t\t\t\t\tfilename = `; filename=\"${restRouteResponse?.attachment?.filename}\"`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\theaders[HeaderTypes.ContentDisposition] =\n\t\t\t\t\t\t\t\t`${restRouteResponse?.attachment?.inline ? \"inline\" : \"attachment\"}${filename}`;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// If this is a binary response then set the content length\n\t\t\t\t\t\tif (Is.uint8Array(restRouteResponse?.body)) {\n\t\t\t\t\t\t\tconst contentLength = restRouteResponse.body.length;\n\t\t\t\t\t\t\theaders[HeaderTypes.ContentLength] = contentLength.toString();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tresponse.body = restRouteResponse?.body;\n\t\t\t\t\t}\n\n\t\t\t\t\tresponse.headers = headers;\n\t\t\t\t\tresponse.statusCode = statusCode;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst { error, httpStatusCode } = HttpErrorHelper.processError(\n\t\t\t\t\t\terr,\n\t\t\t\t\t\tthis._includeErrorStack\n\t\t\t\t\t);\n\n\t\t\t\t\tHttpErrorHelper.buildResponse(response, error, httpStatusCode);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
@@ -72,10 +72,14 @@ export class LoggingProcessor {
72
72
  level: "info",
73
73
  source: LoggingProcessor.CLASS_NAME,
74
74
  ts: Date.now(),
75
- message: `===> ${request.method} ${requestUrl}`,
76
- data: this._includeBody && isJson
77
- ? this.processJson("body", ObjectHelper.clone(request?.body))
78
- : undefined
75
+ message: "requestMessage",
76
+ data: {
77
+ method: request.method,
78
+ requestUrl,
79
+ body: this._includeBody && isJson
80
+ ? this.processJson("body", ObjectHelper.clone(request?.body))
81
+ : undefined
82
+ }
79
83
  });
80
84
  }
81
85
  /**
@@ -126,15 +130,36 @@ export class LoggingProcessor {
126
130
  requestUrl = request.url;
127
131
  }
128
132
  }
129
- await this._logging?.log({
130
- level: Is.number(response.statusCode) && response.statusCode >= HttpStatusCode.badRequest
131
- ? "error"
132
- : "info",
133
- source: LoggingProcessor.CLASS_NAME,
134
- ts: Date.now(),
135
- message: `<=== ${response.statusCode ?? ""} ${request.method} ${requestUrl} duration: ${elapsedMicroSeconds}µs`,
136
- data
137
- });
133
+ if (Is.number(response.statusCode) && response.statusCode >= HttpStatusCode.badRequest) {
134
+ await this._logging?.log({
135
+ level: "error",
136
+ source: LoggingProcessor.CLASS_NAME,
137
+ ts: Date.now(),
138
+ message: "responseMessage",
139
+ data: {
140
+ statusCode: response.statusCode,
141
+ method: request.method,
142
+ requestUrl: requestUrl ?? "",
143
+ elapsedMicroSeconds,
144
+ ...data
145
+ }
146
+ });
147
+ }
148
+ else {
149
+ await this._logging?.log({
150
+ level: "info",
151
+ source: LoggingProcessor.CLASS_NAME,
152
+ ts: Date.now(),
153
+ message: "responseMessage",
154
+ data: {
155
+ statusCode: response.statusCode,
156
+ method: request.method,
157
+ requestUrl: requestUrl ?? "",
158
+ elapsedMicroSeconds,
159
+ ...data
160
+ }
161
+ });
162
+ }
138
163
  }
139
164
  /**
140
165
  * Process the JSON.
@@ -1 +1 @@
1
- {"version":3,"file":"loggingProcessor.js","sourceRoot":"","sources":["../../../src/logging/loggingProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIBaseRoute,\n\tIBaseRouteProcessor,\n\tIHttpResponse,\n\tIHttpServerRequest\n} from \"@twin.org/api-models\";\nimport type { IContextIds } from \"@twin.org/context\";\nimport { Coerce, ComponentFactory, Is, ObjectHelper } from \"@twin.org/core\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { HeaderTypes, HttpStatusCode, MimeTypes } from \"@twin.org/web\";\nimport type { ILoggingProcessorConstructorOptions } from \"../models/ILoggingProcessorConstructorOptions.js\";\n\n/**\n * Process the REST request and log its information.\n */\nexport class LoggingProcessor implements IBaseRouteProcessor {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<LoggingProcessor>();\n\n\t/**\n\t * The component for logging the information.\n\t * @internal\n\t */\n\tprivate readonly _logging?: ILoggingComponent;\n\n\t/**\n\t * Include the body objects when logging the information.\n\t * @internal\n\t */\n\tprivate readonly _includeBody: boolean;\n\n\t/**\n\t * Show the full base64 content for data, default to abbreviate.\n\t * @internal\n\t */\n\tprivate readonly _fullBase64: boolean;\n\n\t/**\n\t * List of property names to obfuscate, defaults to \"password\".\n\t * @internal\n\t */\n\tprivate readonly _obfuscateProperties: string[];\n\n\t/**\n\t * Create a new instance of LoggingProcessor.\n\t * @param options Options for the processor.\n\t */\n\tconstructor(options?: ILoggingProcessorConstructorOptions) {\n\t\tthis._logging = ComponentFactory.getIfExists(options?.loggingComponentType ?? \"logging\");\n\t\tthis._includeBody = options?.config?.includeBody ?? false;\n\t\tthis._fullBase64 = options?.config?.fullBase64 ?? false;\n\t\tthis._obfuscateProperties = options?.config?.obfuscateProperties ?? [\"password\"];\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn LoggingProcessor.CLASS_NAME;\n\t}\n\n\t/**\n\t * Pre process the REST request for the specified route.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t */\n\tpublic async pre(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IBaseRoute | undefined,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<void> {\n\t\tconst now = process.hrtime.bigint();\n\t\tprocessorState.requestStart = now;\n\n\t\tconst contentType = request.headers?.[HeaderTypes.ContentType];\n\t\tconst isJson = this.isMimeJson(contentType);\n\n\t\tlet requestUrl = \"\";\n\t\tif (Is.stringValue(request.url)) {\n\t\t\t// Socket paths do not have a prefix so just use the whole url.\n\t\t\tif (request.url.startsWith(\"http\")) {\n\t\t\t\trequestUrl = new URL(request.url).pathname;\n\t\t\t} else {\n\t\t\t\trequestUrl = request.url;\n\t\t\t}\n\t\t}\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: LoggingProcessor.CLASS_NAME,\n\t\t\tts: Date.now(),\n\t\t\tmessage: `===> ${request.method} ${requestUrl}`,\n\t\t\tdata:\n\t\t\t\tthis._includeBody && isJson\n\t\t\t\t\t? (this.processJson(\"body\", ObjectHelper.clone(request?.body)) as {\n\t\t\t\t\t\t\t[key: string]: unknown;\n\t\t\t\t\t\t})\n\t\t\t\t\t: undefined\n\t\t});\n\t}\n\n\t/**\n\t * Post process the REST request for the specified route.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t */\n\tpublic async post(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IBaseRoute | undefined,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<void> {\n\t\tlet data: { [id: string]: unknown } | undefined;\n\t\tif (this._includeBody) {\n\t\t\tconst contentType = response.headers?.[HeaderTypes.ContentType];\n\t\t\tconst isJson = this.isMimeJson(contentType);\n\t\t\tconst contentLength = response.headers?.[HeaderTypes.ContentLength];\n\t\t\tif (isJson) {\n\t\t\t\tdata = {\n\t\t\t\t\tbody: this.processJson(\"body\", ObjectHelper.clone(response.body))\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\tconst dataParts: { [id: string]: string } = {};\n\t\t\t\tif (Is.stringValue(contentType)) {\n\t\t\t\t\tdataParts[\"Content Type\"] = contentType;\n\t\t\t\t}\n\t\t\t\tif (Is.stringValue(contentLength)) {\n\t\t\t\t\tdataParts[\"Content Length\"] = contentLength;\n\t\t\t\t}\n\t\t\t\tif (Object.keys(dataParts).length > 0) {\n\t\t\t\t\tdata = {\n\t\t\t\t\t\theaders: dataParts\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst now = process.hrtime.bigint();\n\t\tconst start = Coerce.bigint(processorState.requestStart) ?? now;\n\t\tconst elapsed = now - start;\n\t\tconst elapsedMicroSeconds = Math.floor(Number(elapsed) / 1000);\n\n\t\tlet requestUrl = \"\";\n\t\tif (Is.stringValue(request.url)) {\n\t\t\t// Socket paths do not have a prefix so just use the whole url.\n\t\t\tif (request.url.startsWith(\"http\")) {\n\t\t\t\trequestUrl = new URL(request.url).pathname;\n\t\t\t} else {\n\t\t\t\trequestUrl = request.url;\n\t\t\t}\n\t\t}\n\n\t\tawait this._logging?.log({\n\t\t\tlevel:\n\t\t\t\tIs.number(response.statusCode) && response.statusCode >= HttpStatusCode.badRequest\n\t\t\t\t\t? \"error\"\n\t\t\t\t\t: \"info\",\n\t\t\tsource: LoggingProcessor.CLASS_NAME,\n\t\t\tts: Date.now(),\n\t\t\tmessage: `<=== ${response.statusCode ?? \"\"} ${request.method} ${requestUrl} duration: ${elapsedMicroSeconds}µs`,\n\t\t\tdata\n\t\t});\n\t}\n\n\t/**\n\t * Process the JSON.\n\t * @param propValue The property to process.\n\t * @returns The processed property.\n\t * @internal\n\t */\n\tprivate processJson(propName: string, propValue: unknown): unknown {\n\t\tif (Is.array(propValue)) {\n\t\t\tfor (let i = 0; i < propValue.length; i++) {\n\t\t\t\tpropValue[i] = this.processJson(`${i}`, propValue[i]);\n\t\t\t}\n\t\t} else if (Is.object(propValue)) {\n\t\t\tfor (const key of Object.keys(propValue)) {\n\t\t\t\tpropValue[key] = this.processJson(key, propValue[key]);\n\t\t\t}\n\t\t} else if (!this._fullBase64 && Is.stringBase64(propValue) && propValue.length > 256) {\n\t\t\tpropValue = \"<base64>\";\n\t\t} else if (\n\t\t\tIs.string(propValue) &&\n\t\t\tthis._obfuscateProperties.some(op => new RegExp(op).test(propName))\n\t\t) {\n\t\t\tpropValue = \"**************\";\n\t\t}\n\n\t\treturn propValue;\n\t}\n\n\t/**\n\t * Check if the content type is JSON.\n\t * @param contentType The content type to check.\n\t * @returns True if the content type is JSON, false otherwise.\n\t * @internal\n\t */\n\tprivate isMimeJson(contentType: string | string[] | undefined): boolean {\n\t\treturn Is.stringValue(contentType)\n\t\t\t? contentType.includes(MimeTypes.Json) || contentType.includes(MimeTypes.JsonLd)\n\t\t\t: false;\n\t}\n}\n"]}
1
+ {"version":3,"file":"loggingProcessor.js","sourceRoot":"","sources":["../../../src/logging/loggingProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIBaseRoute,\n\tIBaseRouteProcessor,\n\tIHttpResponse,\n\tIHttpServerRequest\n} from \"@twin.org/api-models\";\nimport type { IContextIds } from \"@twin.org/context\";\nimport { Coerce, ComponentFactory, Is, ObjectHelper } from \"@twin.org/core\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { HeaderTypes, HttpStatusCode, MimeTypes } from \"@twin.org/web\";\nimport type { ILoggingProcessorConstructorOptions } from \"../models/ILoggingProcessorConstructorOptions.js\";\n\n/**\n * Process the REST request and log its information.\n */\nexport class LoggingProcessor implements IBaseRouteProcessor {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<LoggingProcessor>();\n\n\t/**\n\t * The component for logging the information.\n\t * @internal\n\t */\n\tprivate readonly _logging?: ILoggingComponent;\n\n\t/**\n\t * Include the body objects when logging the information.\n\t * @internal\n\t */\n\tprivate readonly _includeBody: boolean;\n\n\t/**\n\t * Show the full base64 content for data, default to abbreviate.\n\t * @internal\n\t */\n\tprivate readonly _fullBase64: boolean;\n\n\t/**\n\t * List of property names to obfuscate, defaults to \"password\".\n\t * @internal\n\t */\n\tprivate readonly _obfuscateProperties: string[];\n\n\t/**\n\t * Create a new instance of LoggingProcessor.\n\t * @param options Options for the processor.\n\t */\n\tconstructor(options?: ILoggingProcessorConstructorOptions) {\n\t\tthis._logging = ComponentFactory.getIfExists(options?.loggingComponentType ?? \"logging\");\n\t\tthis._includeBody = options?.config?.includeBody ?? false;\n\t\tthis._fullBase64 = options?.config?.fullBase64 ?? false;\n\t\tthis._obfuscateProperties = options?.config?.obfuscateProperties ?? [\"password\"];\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn LoggingProcessor.CLASS_NAME;\n\t}\n\n\t/**\n\t * Pre process the REST request for the specified route.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t */\n\tpublic async pre(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IBaseRoute | undefined,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<void> {\n\t\tconst now = process.hrtime.bigint();\n\t\tprocessorState.requestStart = now;\n\n\t\tconst contentType = request.headers?.[HeaderTypes.ContentType];\n\t\tconst isJson = this.isMimeJson(contentType);\n\n\t\tlet requestUrl = \"\";\n\t\tif (Is.stringValue(request.url)) {\n\t\t\t// Socket paths do not have a prefix so just use the whole url.\n\t\t\tif (request.url.startsWith(\"http\")) {\n\t\t\t\trequestUrl = new URL(request.url).pathname;\n\t\t\t} else {\n\t\t\t\trequestUrl = request.url;\n\t\t\t}\n\t\t}\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: LoggingProcessor.CLASS_NAME,\n\t\t\tts: Date.now(),\n\t\t\tmessage: \"requestMessage\",\n\t\t\tdata: {\n\t\t\t\tmethod: request.method,\n\t\t\t\trequestUrl,\n\t\t\t\tbody:\n\t\t\t\t\tthis._includeBody && isJson\n\t\t\t\t\t\t? this.processJson(\"body\", ObjectHelper.clone(request?.body))\n\t\t\t\t\t\t: undefined\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Post process the REST request for the specified route.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t */\n\tpublic async post(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IBaseRoute | undefined,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<void> {\n\t\tlet data: { [id: string]: unknown } | undefined;\n\t\tif (this._includeBody) {\n\t\t\tconst contentType = response.headers?.[HeaderTypes.ContentType];\n\t\t\tconst isJson = this.isMimeJson(contentType);\n\t\t\tconst contentLength = response.headers?.[HeaderTypes.ContentLength];\n\t\t\tif (isJson) {\n\t\t\t\tdata = {\n\t\t\t\t\tbody: this.processJson(\"body\", ObjectHelper.clone(response.body))\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\tconst dataParts: { [id: string]: string } = {};\n\t\t\t\tif (Is.stringValue(contentType)) {\n\t\t\t\t\tdataParts[\"Content Type\"] = contentType;\n\t\t\t\t}\n\t\t\t\tif (Is.stringValue(contentLength)) {\n\t\t\t\t\tdataParts[\"Content Length\"] = contentLength;\n\t\t\t\t}\n\t\t\t\tif (Object.keys(dataParts).length > 0) {\n\t\t\t\t\tdata = {\n\t\t\t\t\t\theaders: dataParts\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst now = process.hrtime.bigint();\n\t\tconst start = Coerce.bigint(processorState.requestStart) ?? now;\n\t\tconst elapsed = now - start;\n\t\tconst elapsedMicroSeconds = Math.floor(Number(elapsed) / 1000);\n\n\t\tlet requestUrl = \"\";\n\t\tif (Is.stringValue(request.url)) {\n\t\t\t// Socket paths do not have a prefix so just use the whole url.\n\t\t\tif (request.url.startsWith(\"http\")) {\n\t\t\t\trequestUrl = new URL(request.url).pathname;\n\t\t\t} else {\n\t\t\t\trequestUrl = request.url;\n\t\t\t}\n\t\t}\n\n\t\tif (Is.number(response.statusCode) && response.statusCode >= HttpStatusCode.badRequest) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: LoggingProcessor.CLASS_NAME,\n\t\t\t\tts: Date.now(),\n\t\t\t\tmessage: \"responseMessage\",\n\t\t\t\tdata: {\n\t\t\t\t\tstatusCode: response.statusCode,\n\t\t\t\t\tmethod: request.method,\n\t\t\t\t\trequestUrl: requestUrl ?? \"\",\n\t\t\t\t\telapsedMicroSeconds,\n\t\t\t\t\t...data\n\t\t\t\t}\n\t\t\t});\n\t\t} else {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: LoggingProcessor.CLASS_NAME,\n\t\t\t\tts: Date.now(),\n\t\t\t\tmessage: \"responseMessage\",\n\t\t\t\tdata: {\n\t\t\t\t\tstatusCode: response.statusCode,\n\t\t\t\t\tmethod: request.method,\n\t\t\t\t\trequestUrl: requestUrl ?? \"\",\n\t\t\t\t\telapsedMicroSeconds,\n\t\t\t\t\t...data\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Process the JSON.\n\t * @param propValue The property to process.\n\t * @returns The processed property.\n\t * @internal\n\t */\n\tprivate processJson(propName: string, propValue: unknown): unknown {\n\t\tif (Is.array(propValue)) {\n\t\t\tfor (let i = 0; i < propValue.length; i++) {\n\t\t\t\tpropValue[i] = this.processJson(`${i}`, propValue[i]);\n\t\t\t}\n\t\t} else if (Is.object(propValue)) {\n\t\t\tfor (const key of Object.keys(propValue)) {\n\t\t\t\tpropValue[key] = this.processJson(key, propValue[key]);\n\t\t\t}\n\t\t} else if (!this._fullBase64 && Is.stringBase64(propValue) && propValue.length > 256) {\n\t\t\tpropValue = \"<base64>\";\n\t\t} else if (\n\t\t\tIs.string(propValue) &&\n\t\t\tthis._obfuscateProperties.some(op => new RegExp(op).test(propName))\n\t\t) {\n\t\t\tpropValue = \"**************\";\n\t\t}\n\n\t\treturn propValue;\n\t}\n\n\t/**\n\t * Check if the content type is JSON.\n\t * @param contentType The content type to check.\n\t * @returns True if the content type is JSON, false otherwise.\n\t * @internal\n\t */\n\tprivate isMimeJson(contentType: string | string[] | undefined): boolean {\n\t\treturn Is.stringValue(contentType)\n\t\t\t? contentType.includes(MimeTypes.Json) || contentType.includes(MimeTypes.JsonLd)\n\t\t\t: false;\n\t}\n}\n"]}
@@ -24,9 +24,14 @@ export declare class RestRouteProcessor implements IRestRouteProcessor {
24
24
  * @param response The outgoing response.
25
25
  * @param route The route to process.
26
26
  * @param processorState The state handed through the processors.
27
- * @param loggingComponentType The logging component type for the request.
27
+ * @param componentTypes The component types for the request.
28
+ * @param componentTypes.loggingComponentType The logging component type.
29
+ * @param componentTypes.hostingComponentType The hosting component type.
28
30
  */
29
31
  process(request: IHttpServerRequest, response: IHttpResponse, route: IRestRoute | undefined, processorState: {
30
32
  [id: string]: unknown;
31
- }, loggingComponentType?: string): Promise<void>;
33
+ }, componentTypes?: {
34
+ loggingComponentType?: string;
35
+ hostingComponentType?: string;
36
+ }): Promise<void>;
32
37
  }
package/docs/changelog.md CHANGED
@@ -1,4 +1,305 @@
1
- # @twin.org/api-processors - Changelog
1
+ # Changelog
2
+
3
+ ## [0.0.3-next.21](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.20...api-processors-v0.0.3-next.21) (2026-03-11)
4
+
5
+
6
+ ### Miscellaneous Chores
7
+
8
+ * **api-processors:** Synchronize repo versions
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @twin.org/api-models bumped from 0.0.3-next.20 to 0.0.3-next.21
16
+
17
+ ## [0.0.3-next.20](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.19...api-processors-v0.0.3-next.20) (2026-02-09)
18
+
19
+
20
+ ### Features
21
+
22
+ * location encoding ([#79](https://github.com/twinfoundation/api/issues/79)) ([c684465](https://github.com/twinfoundation/api/commit/c684465f2a871376152472bdecb6aa230b1101a1))
23
+
24
+
25
+ ### Dependencies
26
+
27
+ * The following workspace dependencies were updated
28
+ * dependencies
29
+ * @twin.org/api-models bumped from 0.0.3-next.19 to 0.0.3-next.20
30
+
31
+ ## [0.0.3-next.19](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.18...api-processors-v0.0.3-next.19) (2026-02-06)
32
+
33
+
34
+ ### Miscellaneous Chores
35
+
36
+ * **api-processors:** Synchronize repo versions
37
+
38
+
39
+ ### Dependencies
40
+
41
+ * The following workspace dependencies were updated
42
+ * dependencies
43
+ * @twin.org/api-models bumped from 0.0.3-next.18 to 0.0.3-next.19
44
+
45
+ ## [0.0.3-next.18](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.17...api-processors-v0.0.3-next.18) (2026-02-04)
46
+
47
+
48
+ ### Miscellaneous Chores
49
+
50
+ * **api-processors:** Synchronize repo versions
51
+
52
+
53
+ ### Dependencies
54
+
55
+ * The following workspace dependencies were updated
56
+ * dependencies
57
+ * @twin.org/api-models bumped from 0.0.3-next.17 to 0.0.3-next.18
58
+
59
+ ## [0.0.3-next.17](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.16...api-processors-v0.0.3-next.17) (2026-01-26)
60
+
61
+
62
+ ### Miscellaneous Chores
63
+
64
+ * **api-processors:** Synchronize repo versions
65
+
66
+
67
+ ### Dependencies
68
+
69
+ * The following workspace dependencies were updated
70
+ * dependencies
71
+ * @twin.org/api-models bumped from 0.0.3-next.16 to 0.0.3-next.17
72
+
73
+ ## [0.0.3-next.16](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.15...api-processors-v0.0.3-next.16) (2026-01-26)
74
+
75
+
76
+ ### Features
77
+
78
+ * public base url ([#70](https://github.com/twinfoundation/api/issues/70)) ([5b958cd](https://github.com/twinfoundation/api/commit/5b958cd91e8a38cdae2835ff5f2356c7e48d37c3))
79
+
80
+
81
+ ### Dependencies
82
+
83
+ * The following workspace dependencies were updated
84
+ * dependencies
85
+ * @twin.org/api-models bumped from 0.0.3-next.15 to 0.0.3-next.16
86
+
87
+ ## [0.0.3-next.15](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.14...api-processors-v0.0.3-next.15) (2026-01-22)
88
+
89
+
90
+ ### Miscellaneous Chores
91
+
92
+ * **api-processors:** Synchronize repo versions
93
+
94
+
95
+ ### Dependencies
96
+
97
+ * The following workspace dependencies were updated
98
+ * dependencies
99
+ * @twin.org/api-models bumped from 0.0.3-next.14 to 0.0.3-next.15
100
+
101
+ ## [0.0.3-next.14](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.13...api-processors-v0.0.3-next.14) (2026-01-20)
102
+
103
+
104
+ ### Miscellaneous Chores
105
+
106
+ * **api-processors:** Synchronize repo versions
107
+
108
+
109
+ ### Dependencies
110
+
111
+ * The following workspace dependencies were updated
112
+ * dependencies
113
+ * @twin.org/api-models bumped from 0.0.3-next.13 to 0.0.3-next.14
114
+
115
+ ## [0.0.3-next.13](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.12...api-processors-v0.0.3-next.13) (2026-01-19)
116
+
117
+
118
+ ### Miscellaneous Chores
119
+
120
+ * **api-processors:** Synchronize repo versions
121
+
122
+
123
+ ### Dependencies
124
+
125
+ * The following workspace dependencies were updated
126
+ * dependencies
127
+ * @twin.org/api-models bumped from 0.0.3-next.12 to 0.0.3-next.13
128
+
129
+ ## [0.0.3-next.12](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.11...api-processors-v0.0.3-next.12) (2026-01-12)
130
+
131
+
132
+ ### Bug Fixes
133
+
134
+ * add http headers to rest requests ([#64](https://github.com/twinfoundation/api/issues/64)) ([d4a46b9](https://github.com/twinfoundation/api/commit/d4a46b97b59c116a703f136f99aa4aba0cbbf545))
135
+
136
+
137
+ ### Dependencies
138
+
139
+ * The following workspace dependencies were updated
140
+ * dependencies
141
+ * @twin.org/api-models bumped from 0.0.3-next.11 to 0.0.3-next.12
142
+
143
+ ## [0.0.3-next.11](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.10...api-processors-v0.0.3-next.11) (2026-01-08)
144
+
145
+
146
+ ### Miscellaneous Chores
147
+
148
+ * **api-processors:** Synchronize repo versions
149
+
150
+
151
+ ### Dependencies
152
+
153
+ * The following workspace dependencies were updated
154
+ * dependencies
155
+ * @twin.org/api-models bumped from 0.0.3-next.10 to 0.0.3-next.11
156
+
157
+ ## [0.0.3-next.10](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.9...api-processors-v0.0.3-next.10) (2026-01-05)
158
+
159
+
160
+ ### Miscellaneous Chores
161
+
162
+ * **api-processors:** Synchronize repo versions
163
+
164
+
165
+ ### Dependencies
166
+
167
+ * The following workspace dependencies were updated
168
+ * dependencies
169
+ * @twin.org/api-models bumped from 0.0.3-next.9 to 0.0.3-next.10
170
+
171
+ ## [0.0.3-next.9](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.8...api-processors-v0.0.3-next.9) (2026-01-05)
172
+
173
+
174
+ ### Features
175
+
176
+ * add context id features ([#42](https://github.com/twinfoundation/api/issues/42)) ([0186055](https://github.com/twinfoundation/api/commit/0186055c48afde842a4254b4df9ac9249c40fe40))
177
+ * add json-ld mime type processor and auth admin component ([8861791](https://github.com/twinfoundation/api/commit/88617916e23bfbca023dbae1976fe421983a02ff))
178
+ * add logging component type to request contexts ([210de1b](https://github.com/twinfoundation/api/commit/210de1b9e1c91079b59a2b90ddd57569668d647d))
179
+ * add root, favicon routes ([71da1c3](https://github.com/twinfoundation/api/commit/71da1c3a93c349588aff7084d1d8d6a29a277da8))
180
+ * add socket id, connect and disconnect ([20b0d0e](https://github.com/twinfoundation/api/commit/20b0d0ec279cab46141fee09de2c4a7087cdce16))
181
+ * add validate-locales ([cdba610](https://github.com/twinfoundation/api/commit/cdba610a0acb5022d2e3ce729732e6646a297e5e))
182
+ * eslint migration to flat config ([0dd5820](https://github.com/twinfoundation/api/commit/0dd5820e3af97350fd08b8d226f4a6c1a9246805))
183
+ * logging naming consistency ([a4a6ef2](https://github.com/twinfoundation/api/commit/a4a6ef2de5049045589eb78b177ff62e744bde9d))
184
+ * remove unused namespace ([08478f2](https://github.com/twinfoundation/api/commit/08478f27efda9beb0271fdb22f6972e918361965))
185
+ * update dependencies ([1171dc4](https://github.com/twinfoundation/api/commit/1171dc416a9481737f6a640e3cf30145768f37e9))
186
+ * update framework core ([d8eebf2](https://github.com/twinfoundation/api/commit/d8eebf267fa2a0abaa84e58590496e9d20490cfa))
187
+ * update IComponent signatures ([915ce37](https://github.com/twinfoundation/api/commit/915ce37712326ab4aa6869c350eabaa4622e8430))
188
+ * use shared store mechanism ([#19](https://github.com/twinfoundation/api/issues/19)) ([32116df](https://github.com/twinfoundation/api/commit/32116df3b4380a30137f5056f242a5c99afa2df9))
189
+
190
+
191
+ ### Bug Fixes
192
+
193
+ * error handling make sure primary error takes precedence ([84b61f2](https://github.com/twinfoundation/api/commit/84b61f27fe5e4919c0c9f9a1edc8ff46dc45c1f7))
194
+ * locales ([1b84d8e](https://github.com/twinfoundation/api/commit/1b84d8eb4dbe2302897e184e6389892b7ba12608))
195
+
196
+
197
+ ### Dependencies
198
+
199
+ * The following workspace dependencies were updated
200
+ * dependencies
201
+ * @twin.org/api-models bumped from 0.0.3-next.8 to 0.0.3-next.9
202
+
203
+ ## [0.0.3-next.8](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.7...api-processors-v0.0.3-next.8) (2025-12-17)
204
+
205
+
206
+ ### Miscellaneous Chores
207
+
208
+ * **api-processors:** Synchronize repo versions
209
+
210
+
211
+ ### Dependencies
212
+
213
+ * The following workspace dependencies were updated
214
+ * dependencies
215
+ * @twin.org/api-models bumped from 0.0.3-next.7 to 0.0.3-next.8
216
+
217
+ ## [0.0.3-next.7](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.6...api-processors-v0.0.3-next.7) (2025-11-26)
218
+
219
+
220
+ ### Bug Fixes
221
+
222
+ * error handling make sure primary error takes precedence ([84b61f2](https://github.com/twinfoundation/api/commit/84b61f27fe5e4919c0c9f9a1edc8ff46dc45c1f7))
223
+
224
+
225
+ ### Dependencies
226
+
227
+ * The following workspace dependencies were updated
228
+ * dependencies
229
+ * @twin.org/api-models bumped from 0.0.3-next.6 to 0.0.3-next.7
230
+
231
+ ## [0.0.3-next.6](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.5...api-processors-v0.0.3-next.6) (2025-11-20)
232
+
233
+
234
+ ### Miscellaneous Chores
235
+
236
+ * **api-processors:** Synchronize repo versions
237
+
238
+
239
+ ### Dependencies
240
+
241
+ * The following workspace dependencies were updated
242
+ * dependencies
243
+ * @twin.org/api-models bumped from 0.0.3-next.5 to 0.0.3-next.6
244
+
245
+ ## [0.0.3-next.5](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.4...api-processors-v0.0.3-next.5) (2025-11-14)
246
+
247
+
248
+ ### Miscellaneous Chores
249
+
250
+ * **api-processors:** Synchronize repo versions
251
+
252
+
253
+ ### Dependencies
254
+
255
+ * The following workspace dependencies were updated
256
+ * dependencies
257
+ * @twin.org/api-models bumped from 0.0.3-next.4 to 0.0.3-next.5
258
+
259
+ ## [0.0.3-next.4](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.3...api-processors-v0.0.3-next.4) (2025-11-14)
260
+
261
+
262
+ ### Features
263
+
264
+ * add context id features ([#42](https://github.com/twinfoundation/api/issues/42)) ([0186055](https://github.com/twinfoundation/api/commit/0186055c48afde842a4254b4df9ac9249c40fe40))
265
+ * add json-ld mime type processor and auth admin component ([8861791](https://github.com/twinfoundation/api/commit/88617916e23bfbca023dbae1976fe421983a02ff))
266
+ * add logging component type to request contexts ([210de1b](https://github.com/twinfoundation/api/commit/210de1b9e1c91079b59a2b90ddd57569668d647d))
267
+ * add root, favicon routes ([71da1c3](https://github.com/twinfoundation/api/commit/71da1c3a93c349588aff7084d1d8d6a29a277da8))
268
+ * add socket id, connect and disconnect ([20b0d0e](https://github.com/twinfoundation/api/commit/20b0d0ec279cab46141fee09de2c4a7087cdce16))
269
+ * add validate-locales ([cdba610](https://github.com/twinfoundation/api/commit/cdba610a0acb5022d2e3ce729732e6646a297e5e))
270
+ * eslint migration to flat config ([0dd5820](https://github.com/twinfoundation/api/commit/0dd5820e3af97350fd08b8d226f4a6c1a9246805))
271
+ * logging naming consistency ([a4a6ef2](https://github.com/twinfoundation/api/commit/a4a6ef2de5049045589eb78b177ff62e744bde9d))
272
+ * remove unused namespace ([08478f2](https://github.com/twinfoundation/api/commit/08478f27efda9beb0271fdb22f6972e918361965))
273
+ * update dependencies ([1171dc4](https://github.com/twinfoundation/api/commit/1171dc416a9481737f6a640e3cf30145768f37e9))
274
+ * update framework core ([d8eebf2](https://github.com/twinfoundation/api/commit/d8eebf267fa2a0abaa84e58590496e9d20490cfa))
275
+ * update IComponent signatures ([915ce37](https://github.com/twinfoundation/api/commit/915ce37712326ab4aa6869c350eabaa4622e8430))
276
+ * use shared store mechanism ([#19](https://github.com/twinfoundation/api/issues/19)) ([32116df](https://github.com/twinfoundation/api/commit/32116df3b4380a30137f5056f242a5c99afa2df9))
277
+
278
+
279
+ ### Bug Fixes
280
+
281
+ * locales ([1b84d8e](https://github.com/twinfoundation/api/commit/1b84d8eb4dbe2302897e184e6389892b7ba12608))
282
+
283
+
284
+ ### Dependencies
285
+
286
+ * The following workspace dependencies were updated
287
+ * dependencies
288
+ * @twin.org/api-models bumped from 0.0.3-next.3 to 0.0.3-next.4
289
+
290
+ ## [0.0.3-next.3](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.2...api-processors-v0.0.3-next.3) (2025-11-14)
291
+
292
+
293
+ ### Miscellaneous Chores
294
+
295
+ * **api-processors:** Synchronize repo versions
296
+
297
+
298
+ ### Dependencies
299
+
300
+ * The following workspace dependencies were updated
301
+ * dependencies
302
+ * @twin.org/api-models bumped from 0.0.3-next.2 to 0.0.3-next.3
2
303
 
3
304
  ## [0.0.3-next.2](https://github.com/twinfoundation/api/compare/api-processors-v0.0.3-next.1...api-processors-v0.0.3-next.2) (2025-11-12)
4
305
 
package/docs/examples.md CHANGED
@@ -1 +1,189 @@
1
- # @twin.org/api-processors - Examples
1
+ # Processors Examples
2
+
3
+ These snippets demonstrate request and response processors that can be composed into REST, socket, and MIME handling pipelines.
4
+
5
+ ## SocketRouteProcessor
6
+
7
+ ```typescript
8
+ import { SocketRouteProcessor } from '@twin.org/api-processors';
9
+
10
+ const socketProcessor = new SocketRouteProcessor();
11
+
12
+ console.log(socketProcessor.className()); // SocketRouteProcessor
13
+
14
+ const socketRoute = {
15
+ event: 'tenant.updated',
16
+ handler: async () => ({
17
+ body: {
18
+ ok: true
19
+ }
20
+ })
21
+ };
22
+
23
+ const socketRequest = {
24
+ socketId: 'conn-1',
25
+ url: '/tenant.updated',
26
+ pathParams: {},
27
+ query: {},
28
+ body: {
29
+ tenantId: '0123456789abcdef0123456789abcdef'
30
+ }
31
+ };
32
+
33
+ const socketResponse = {};
34
+ const socketProcessorState = {};
35
+
36
+ await socketProcessor.connected(socketRequest, socketRoute);
37
+
38
+ await socketProcessor.process(
39
+ socketRequest,
40
+ socketResponse,
41
+ socketRoute,
42
+ socketProcessorState,
43
+ async (topic, response) => {
44
+ console.log(topic); // tenant.updated
45
+ console.log(response.statusCode ?? 200); // 200
46
+ }
47
+ );
48
+
49
+ await socketProcessor.disconnected(socketRequest, socketRoute);
50
+ ```
51
+
52
+ ## ContextIdProcessor
53
+
54
+ ```typescript
55
+ import { ContextIdProcessor } from '@twin.org/api-processors';
56
+
57
+ const contextProcessor = new ContextIdProcessor({
58
+ config: {
59
+ key: 'node'
60
+ }
61
+ });
62
+
63
+ await contextProcessor.start('default');
64
+ console.log(contextProcessor.className()); // ContextIdProcessor
65
+
66
+ const contextIds = {};
67
+ await contextProcessor.pre(
68
+ { method: 'get', url: '/health', headers: {} },
69
+ {},
70
+ undefined,
71
+ contextIds,
72
+ {}
73
+ );
74
+ console.log(typeof contextIds.node); // string
75
+ ```
76
+
77
+ ## LoggingProcessor
78
+
79
+ ```typescript
80
+ import { LoggingProcessor } from '@twin.org/api-processors';
81
+
82
+ const logging = new LoggingProcessor();
83
+
84
+ console.log(logging.className()); // LoggingProcessor
85
+
86
+ const request = {
87
+ method: 'get',
88
+ url: 'http://localhost:3000/health',
89
+ headers: {}
90
+ };
91
+
92
+ const response = {
93
+ statusCode: 200,
94
+ headers: {}
95
+ };
96
+
97
+ const processorState: { [id: string]: unknown } = {};
98
+
99
+ await logging.pre(request, response, undefined, {}, processorState);
100
+ await logging.post(request, response, undefined, {}, processorState);
101
+ ```
102
+
103
+ ## JsonLdMimeTypeProcessor
104
+
105
+ ```typescript
106
+ import { JsonLdMimeTypeProcessor } from '@twin.org/api-processors';
107
+
108
+ const jsonLd = new JsonLdMimeTypeProcessor();
109
+
110
+ const mediaTypes = jsonLd.getTypes();
111
+ const body = new TextEncoder().encode('{"@context":"https://schema.org","name":"Tenant"}');
112
+ const handled = await jsonLd.handle(body);
113
+
114
+ console.log(mediaTypes[0]); // application/ld+json
115
+ console.log(typeof handled); // object
116
+ ```
117
+
118
+ ## JwtMimeTypeProcessor
119
+
120
+ ```typescript
121
+ import { JwtMimeTypeProcessor } from '@twin.org/api-processors';
122
+
123
+ const jwt = new JwtMimeTypeProcessor();
124
+
125
+ const mediaTypes = jwt.getTypes();
126
+ const body = new TextEncoder().encode('eyJhbGciOiJIUzI1NiJ9.payload.signature');
127
+ const handled = await jwt.handle(body);
128
+
129
+ console.log(mediaTypes[0]); // application/jwt
130
+ console.log(typeof handled); // string
131
+ ```
132
+
133
+ ## RestRouteProcessor
134
+
135
+ ```typescript
136
+ import { RestRouteProcessor } from '@twin.org/api-processors';
137
+
138
+ const restProcessor = new RestRouteProcessor();
139
+
140
+ console.log(restProcessor.className()); // RestRouteProcessor
141
+
142
+ const request = {
143
+ method: 'get',
144
+ url: '/info',
145
+ headers: {},
146
+ pathParams: {},
147
+ query: {}
148
+ };
149
+
150
+ const response = {};
151
+
152
+ const route = {
153
+ method: 'get',
154
+ path: '/info',
155
+ handler: async () => ({
156
+ body: {
157
+ name: 'Twin API'
158
+ }
159
+ })
160
+ };
161
+
162
+ await restProcessor.process(request, response, route, {});
163
+ console.log(response.statusCode ?? 200); // 200
164
+ ```
165
+
166
+ ## StaticContextIdProcessor
167
+
168
+ ```typescript
169
+ import { StaticContextIdProcessor } from '@twin.org/api-processors';
170
+
171
+ const staticContext = new StaticContextIdProcessor({
172
+ config: {
173
+ key: 'environment',
174
+ value: 'test'
175
+ }
176
+ });
177
+
178
+ console.log(staticContext.className()); // StaticContextIdProcessor
179
+
180
+ const contextIds = {};
181
+ await staticContext.pre(
182
+ { method: 'get', url: '/health', headers: {} },
183
+ {},
184
+ undefined,
185
+ contextIds,
186
+ {}
187
+ );
188
+ console.log(contextIds.environment); // test
189
+ ```
@@ -56,7 +56,7 @@ The class name of the component.
56
56
 
57
57
  ### process()
58
58
 
59
- > **process**(`request`, `response`, `route`, `processorState`, `loggingComponentType?`): `Promise`\<`void`\>
59
+ > **process**(`request`, `response`, `route`, `processorState`, `componentTypes?`): `Promise`\<`void`\>
60
60
 
61
61
  Process the REST request for the specified route.
62
62
 
@@ -84,11 +84,21 @@ The route to process.
84
84
 
85
85
  The state handed through the processors.
86
86
 
87
- ##### loggingComponentType?
87
+ ##### componentTypes?
88
+
89
+ The component types for the request.
90
+
91
+ ###### loggingComponentType?
92
+
93
+ `string`
94
+
95
+ The logging component type.
96
+
97
+ ###### hostingComponentType?
88
98
 
89
99
  `string`
90
100
 
91
- The logging component type for the request.
101
+ The hosting component type.
92
102
 
93
103
  #### Returns
94
104
 
package/locales/en.json CHANGED
@@ -8,6 +8,15 @@
8
8
  },
9
9
  "jsonLdMimeTypeProcessor": {
10
10
  "invalidJsonLd": "The JSON-LD content is invalid or missing '@context'."
11
+ },
12
+ "loggingProcessor": {
13
+ "responseMessage": "<=== {statusCode} {method} {requestUrl} duration: {elapsedMicroSeconds}µs"
14
+ }
15
+ },
16
+ "info": {
17
+ "loggingProcessor": {
18
+ "requestMessage": "===> {method} {requestUrl}",
19
+ "responseMessage": "<=== {statusCode} {method} {requestUrl} duration: {elapsedMicroSeconds}µs"
11
20
  }
12
21
  }
13
22
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@twin.org/api-processors",
3
- "version": "0.0.3-next.2",
4
- "description": "Route processors for use with API servers",
3
+ "version": "0.0.3-next.21",
4
+ "description": "Reusable request and route processors for logging, context handling, and content negotiation.",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/twinfoundation/api.git",
@@ -14,7 +14,7 @@
14
14
  "node": ">=20.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "@twin.org/api-models": "0.0.3-next.2",
17
+ "@twin.org/api-models": "0.0.3-next.21",
18
18
  "@twin.org/context": "next",
19
19
  "@twin.org/core": "next",
20
20
  "@twin.org/logging-models": "next",