unnbound-events 2.0.20 → 2.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export { createEnqueuer } from './lib/enqueue';
2
2
  export type { ServerErrorOptions } from './lib/error';
3
3
  export { ServerError } from './lib/error';
4
4
  export type { EventMetadata, EventSource, IncomingEvent, IncomingRequest } from './lib/event';
5
+ export type { As2Prepared, As2RawRequest } from './lib/routing/as2';
6
+ export { as2Endpoint } from './lib/routing/as2';
5
7
  export type { Handler } from './lib/routing/endpoint';
6
8
  export type { MiddlewareHandler } from './lib/routing/middleware';
7
9
  export type { EventVariables, MaybePromise } from './lib/routing/types';
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createServer = exports.ServerError = exports.createEnqueuer = void 0;
3
+ exports.createServer = exports.as2Endpoint = exports.ServerError = exports.createEnqueuer = void 0;
4
4
  var enqueue_1 = require("./lib/enqueue");
5
5
  Object.defineProperty(exports, "createEnqueuer", { enumerable: true, get: function () { return enqueue_1.createEnqueuer; } });
6
6
  var error_1 = require("./lib/error");
7
7
  Object.defineProperty(exports, "ServerError", { enumerable: true, get: function () { return error_1.ServerError; } });
8
+ var as2_1 = require("./lib/routing/as2");
9
+ Object.defineProperty(exports, "as2Endpoint", { enumerable: true, get: function () { return as2_1.as2Endpoint; } });
8
10
  var server_1 = require("./lib/server");
9
11
  Object.defineProperty(exports, "createServer", { enumerable: true, get: function () { return server_1.createServer; } });
@@ -25,6 +25,12 @@ export interface IncomingEvent<R extends IncomingRequest = IncomingRequest> {
25
25
  export interface StrictEventHandlerResult {
26
26
  status: StatusCode;
27
27
  headers?: Record<string, string>;
28
- body?: object | undefined | null;
28
+ /**
29
+ * Response body. Objects are JSON-serialized; `Uint8Array` (including Buffer)
30
+ * and `ArrayBuffer` are sent verbatim as binary — required for protocols like
31
+ * AS2 whose responses (signed S/MIME MDNs) must not be JSON-mangled. Set the
32
+ * `content-type` header explicitly when returning binary.
33
+ */
34
+ body?: object | Uint8Array | ArrayBuffer | undefined | null;
29
35
  }
30
36
  export type EventHandlerResult = Partial<StrictEventHandlerResult> | undefined | null | undefined;
@@ -0,0 +1,37 @@
1
+ import type { EventHandlerResult } from '../event';
2
+ import { type Handler } from './endpoint';
3
+ import type { EventVariables, MaybePromise } from './types';
4
+ /** Raw wire request handed to the platform verification call, byte-exact. */
5
+ export interface As2RawRequest {
6
+ headers: Record<string, string>;
7
+ body: ArrayBuffer | Uint8Array | string | undefined;
8
+ }
9
+ /**
10
+ * Result of the platform verification call. `as2.receive` results carry the exchange's
11
+ * canonical customer trace (recovered from the ledger for a loopback, server-minted for
12
+ * an external inbound); `as2.confirm` results deliberately do not — an async MDN
13
+ * callback is its own execution and stays on its own trace, linked by Message-ID.
14
+ */
15
+ export interface As2Prepared {
16
+ customerTraceId?: string | null;
17
+ }
18
+ /**
19
+ * Verify-then-bind AS2 endpoint. The public `x-unnbound-trace-id` header is never AS2
20
+ * continuation authority: the route starts on a fresh trace, runs `prepare` (the hidden
21
+ * `as2.receive`/`as2.confirm` platform call) before any customer request span exists,
22
+ * and only a verified ledger-backed trace may re-bind the request. Unverified traffic
23
+ * fails on its own sanitized trace and can assert nothing. Request/response headers are
24
+ * logged through the AS2 allowlist — Receipt-Delivery-Option and Subject never appear.
25
+ *
26
+ * ```ts
27
+ * server.post('/as2/inbound', as2Endpoint(
28
+ * ({ headers, body }) => as2.receive({ headers, body: Buffer.from(body as ArrayBuffer), partner: 'AS2_PARTNER' }),
29
+ * async (result) => {
30
+ * if (result.kind !== 'received') return result.response;
31
+ * await processDocument(result.payload);
32
+ * return (await result.acknowledge()).response;
33
+ * },
34
+ * ));
35
+ * ```
36
+ */
37
+ export declare const as2Endpoint: <V extends object, P extends object>(prepare: (request: As2RawRequest, event: EventVariables<V>) => Promise<P>, handler: (prepared: P, event: EventVariables<V>) => MaybePromise<EventHandlerResult>) => Handler<EventVariables<V>>;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.as2Endpoint = void 0;
4
+ const unnbound_logger_sdk_1 = require("unnbound-logger-sdk");
5
+ const error_1 = require("../error");
6
+ const utils_1 = require("../utils");
7
+ const endpoint_1 = require("./endpoint");
8
+ /**
9
+ * Verify-then-bind AS2 endpoint. The public `x-unnbound-trace-id` header is never AS2
10
+ * continuation authority: the route starts on a fresh trace, runs `prepare` (the hidden
11
+ * `as2.receive`/`as2.confirm` platform call) before any customer request span exists,
12
+ * and only a verified ledger-backed trace may re-bind the request. Unverified traffic
13
+ * fails on its own sanitized trace and can assert nothing. Request/response headers are
14
+ * logged through the AS2 allowlist — Receipt-Delivery-Option and Subject never appear.
15
+ *
16
+ * ```ts
17
+ * server.post('/as2/inbound', as2Endpoint(
18
+ * ({ headers, body }) => as2.receive({ headers, body: Buffer.from(body as ArrayBuffer), partner: 'AS2_PARTNER' }),
19
+ * async (result) => {
20
+ * if (result.kind !== 'received') return result.response;
21
+ * await processDocument(result.payload);
22
+ * return (await result.acknowledge()).response;
23
+ * },
24
+ * ));
25
+ * ```
26
+ */
27
+ // P is constrained to object, not As2Prepared: prepared results are unions whose
28
+ // members don't all carry customerTraceId (e.g. inFlight/parked), and TypeScript's
29
+ // weak-type check would reject those. The As2Prepared contract is read defensively.
30
+ const as2Endpoint = (prepare, handler) => {
31
+ return (0, endpoint_1.markSelfTraced)(async (event) => {
32
+ const requestTraceId = (0, unnbound_logger_sdk_1.getTraceId)();
33
+ let prepared;
34
+ try {
35
+ prepared = await (0, unnbound_logger_sdk_1.withTrace)(() => prepare({ headers: event.request.headers, body: event.request.body }, event), {
36
+ traceId: requestTraceId,
37
+ });
38
+ }
39
+ catch (error) {
40
+ return (0, unnbound_logger_sdk_1.withTrace)(async () => {
41
+ const failure = error_1.ServerError.fromError(error);
42
+ // Bookend the request on its own failure trace: the span records the sanitized
43
+ // AS2 payload and rethrows; the wire response carries no protocol claims.
44
+ await (0, unnbound_logger_sdk_1.startSpan)('Event request', () => Promise.reject(failure), (o) => (0, utils_1.buildAs2IncomingPayload)(event, o)).catch(() => undefined);
45
+ const result = failure.toJson();
46
+ return { ...result, headers: (0, endpoint_1.attachTraceHeaders)(result.headers) };
47
+ }, { traceId: requestTraceId });
48
+ }
49
+ const verified = prepared.customerTraceId;
50
+ const traceId = (0, utils_1.isCanonicalTraceId)(verified) ? verified : requestTraceId;
51
+ return (0, unnbound_logger_sdk_1.withTrace)(async () => {
52
+ // Response trace headers must be attached inside this scope: respondWith runs after
53
+ // the re-bound trace exits and would otherwise advertise the pre-bind trace.
54
+ try {
55
+ const result = await (0, unnbound_logger_sdk_1.startSpan)('Event request', async () => {
56
+ const result = await handler(prepared, event);
57
+ return { status: result?.status ?? 204, headers: result?.headers, body: result?.body };
58
+ }, (o) => (0, utils_1.buildAs2IncomingPayload)(event, o));
59
+ return { ...result, headers: (0, endpoint_1.attachTraceHeaders)(result.headers) };
60
+ }
61
+ catch (error) {
62
+ // The span above already logged the failure on the bound trace; respond with the
63
+ // sanitized shape instead of rethrowing into the header-derived middleware trace.
64
+ const result = error_1.ServerError.fromError(error).toJson();
65
+ return { ...result, headers: (0, endpoint_1.attachTraceHeaders)(result.headers) };
66
+ }
67
+ }, { traceId });
68
+ });
69
+ };
70
+ exports.as2Endpoint = as2Endpoint;
@@ -3,17 +3,18 @@ import type { EventHandlerResult, StrictEventHandlerResult } from '../event';
3
3
  import { type MiddlewareHandler } from './middleware';
4
4
  import type { EventEnvironment, EventVariables, MaybePromise } from './types';
5
5
  export type Handler<I, R = EventHandlerResult> = (input: I) => MaybePromise<R>;
6
+ export declare const markSelfTraced: <I>(handler: (input: I) => Promise<StrictEventHandlerResult>) => Handler<I>;
6
7
  export interface Endpoint<I> {
7
8
  (path: string, handler: Handler<I>): MaybePromise<void>;
8
9
  (path: string, middleware: MiddlewareHandler<I>, handler: Handler<I>): MaybePromise<void>;
9
10
  }
10
11
  export type EndpointArgs<I> = [path: string, handler: Handler<I>] | [path: string, middleware: MiddlewareHandler<I>, handler: Handler<I>];
11
- export declare const buildEndpointArguments: (...args: EndpointArgs<EventVariables<{}>>) => (string | MiddlewareHandler<Context<EventEnvironment<{}>, any, {}>> | Handler<Context<EventEnvironment<{}>, any, {}>, Response>)[];
12
+ export declare const buildEndpointArguments: (...args: EndpointArgs<EventVariables<{}>>) => (string | Handler<Context<EventEnvironment<{}>, any, {}>, Response> | MiddlewareHandler<Context<EventEnvironment<{}>, any, {}>>)[];
12
13
  export declare const parseEndpointArguments: (...args: EndpointArgs<EventVariables<{}>>) => {
13
14
  path: string;
14
15
  middlewares: MiddlewareHandler<EventVariables<{}>>[];
15
16
  handler: Handler<EventVariables<{}>, EventHandlerResult>;
16
17
  };
17
18
  export declare const patchEndpoint: <V extends object>(handler: Handler<EventVariables<V>>) => Handler<Context<EventEnvironment<V>>, Response>;
18
- export declare const respondWith: (context: Context, result: StrictEventHandlerResult) => (Response & import("hono").TypedResponse<null, 100 | 101 | 102 | 103 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | -1, "body">) | (Response & import("hono").TypedResponse<never, 100 | 102 | 103 | 200 | 201 | 202 | 203 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | -1, "json">);
19
+ export declare const respondWith: (context: Context, result: StrictEventHandlerResult) => (Response & import("hono").TypedResponse<never, -1 | 100 | 102 | 103 | 200 | 201 | 202 | 203 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511, "json">) | (Response & import("hono").TypedResponse<null, -1 | 100 | 101 | 102 | 103 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511, "body">) | (Response & import("hono").TypedResponse<ArrayBuffer, -1 | 100 | 102 | 103 | 200 | 201 | 202 | 203 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511, "body">);
19
20
  export declare const attachTraceHeaders: (headers?: Record<string, string>) => Record<string, string> | undefined;
@@ -1,9 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.attachTraceHeaders = exports.respondWith = exports.patchEndpoint = exports.parseEndpointArguments = exports.buildEndpointArguments = void 0;
3
+ exports.attachTraceHeaders = exports.respondWith = exports.patchEndpoint = exports.parseEndpointArguments = exports.buildEndpointArguments = exports.markSelfTraced = void 0;
4
4
  const unnbound_logger_sdk_1 = require("unnbound-logger-sdk");
5
5
  const utils_1 = require("../utils");
6
6
  const middleware_1 = require("./middleware");
7
+ /**
8
+ * Marks a handler that owns its customer trace and request span. The generic endpoint
9
+ * wrapper binds the header-derived trace before running a handler; AS2 endpoints must
10
+ * instead verify the protocol exchange first and only then choose the trace, so their
11
+ * wrapper (see routing/as2.ts) emits the request span itself and returns a strict result.
12
+ */
13
+ const selfTraced = Symbol.for('unnbound-events.selfTraced');
14
+ const markSelfTraced = (handler) => {
15
+ return Object.assign(handler, { [selfTraced]: true });
16
+ };
17
+ exports.markSelfTraced = markSelfTraced;
18
+ const isSelfTraced = (handler) => selfTraced in handler;
7
19
  const buildEndpointArguments = (...args) => {
8
20
  const { path, middlewares, handler } = (0, exports.parseEndpointArguments)(...args);
9
21
  const patchedHandler = (0, exports.patchEndpoint)(handler);
@@ -21,10 +33,12 @@ const patchEndpoint = (handler) => {
21
33
  const state = context.get('state');
22
34
  // We have to recalculate the params because the handler path might contain url params
23
35
  state.request.params = { ...state.request.params, ...context.req.param() };
24
- const result = await (0, unnbound_logger_sdk_1.startSpan)('Event request', async () => {
25
- const result = await handler(state);
26
- return { status: result?.status ?? 204, headers: result?.headers, body: result?.body };
27
- }, (o) => (0, utils_1.buildIncomingPayload)(state, o));
36
+ const result = isSelfTraced(handler)
37
+ ? await handler(state)
38
+ : await (0, unnbound_logger_sdk_1.startSpan)('Event request', async () => {
39
+ const result = await handler(state);
40
+ return { status: result?.status ?? 204, headers: result?.headers, body: result?.body };
41
+ }, (o) => (0, utils_1.buildIncomingPayload)(state, o));
28
42
  return (0, exports.respondWith)(context, result);
29
43
  };
30
44
  };
@@ -33,6 +47,11 @@ const respondWith = (context, result) => {
33
47
  const headers = (0, exports.attachTraceHeaders)(result.headers);
34
48
  if (!result.body)
35
49
  return context.body(null, result.status, headers);
50
+ // Signed protocol responses such as AS2 MDNs must remain byte-exact.
51
+ if (result.body instanceof Uint8Array || result.body instanceof ArrayBuffer) {
52
+ const bytes = result.body instanceof ArrayBuffer ? result.body : Uint8Array.from(result.body).buffer;
53
+ return context.body(bytes, result.status, headers);
54
+ }
36
55
  return context.json(result.body, result.status, headers);
37
56
  };
38
57
  exports.respondWith = respondWith;
@@ -3,6 +3,7 @@ import { type Maybe } from 'unnbound-logger-sdk/dist/types';
3
3
  import type { EventHandlerResult, EventMetadata, EventSource, IncomingEvent, IncomingRequest, MatchedIncomingRequest, StrictEventHandlerResult } from './event';
4
4
  export declare const normalizeHeaders: (headers: Record<string, string | string[]>) => Record<string, string>;
5
5
  export declare const getIncomingEvent: (event: HonoRequest) => Promise<IncomingEvent<MatchedIncomingRequest>>;
6
+ export declare const isCanonicalTraceId: (value: string | undefined | null) => value is string;
6
7
  export declare const getMetadataFromRequest: (request: IncomingRequest, source: EventSource) => EventMetadata;
7
8
  interface ForwardedMetadata {
8
9
  source: EventSource;
@@ -17,7 +18,24 @@ export declare const buildIncomingPayload: (event: IncomingEvent<MatchedIncoming
17
18
  source: EventSource;
18
19
  delay: number | undefined;
19
20
  http: {
21
+ url: string;
22
+ method: string;
23
+ query: Record<string, any>;
24
+ params: Record<string, string> | undefined;
25
+ ip: string | undefined;
26
+ incoming: boolean;
27
+ request: {
28
+ headers: Record<string, string>;
29
+ body: object;
30
+ };
20
31
  response?: StrictEventHandlerResult | undefined;
32
+ };
33
+ };
34
+ export declare const buildAs2IncomingPayload: (event: IncomingEvent<MatchedIncomingRequest>, result?: Maybe<EventHandlerResult>) => {
35
+ type: string;
36
+ source: EventSource;
37
+ delay: number | undefined;
38
+ http: {
21
39
  url: string;
22
40
  method: string;
23
41
  query: Record<string, any>;
@@ -26,8 +44,9 @@ export declare const buildIncomingPayload: (event: IncomingEvent<MatchedIncoming
26
44
  incoming: boolean;
27
45
  request: {
28
46
  headers: Record<string, string>;
29
- body: any;
47
+ body: object;
30
48
  };
49
+ response?: StrictEventHandlerResult | undefined;
31
50
  };
32
51
  };
33
52
  export declare const batchArray: <T>(array: T[], size: number) => T[][];
package/dist/lib/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.batchArray = exports.buildIncomingPayload = exports.serializeMetadata = exports.getMetadataFromRequest = exports.getIncomingEvent = exports.normalizeHeaders = void 0;
3
+ exports.batchArray = exports.buildAs2IncomingPayload = exports.buildIncomingPayload = exports.serializeMetadata = exports.getMetadataFromRequest = exports.isCanonicalTraceId = exports.getIncomingEvent = exports.normalizeHeaders = void 0;
4
4
  const unnbound_logger_sdk_1 = require("unnbound-logger-sdk");
5
5
  const trace_1 = require("unnbound-logger-sdk/dist/trace");
6
6
  const types_1 = require("unnbound-logger-sdk/dist/types");
@@ -61,11 +61,22 @@ const parseBody = async (request, headers) => {
61
61
  return undefined;
62
62
  }
63
63
  };
64
- const getMetadataFromRequest = (request, source) => ({
65
- traceId: request.headers[types_1.defaultTraceHeaderKey] ?? (0, unnbound_logger_sdk_1.getTraceId)(),
66
- messageId: request.headers[types_1.defaultMessageHeaderKey] ?? (0, trace_1.getMessageId)(),
67
- source,
68
- });
64
+ /**
65
+ * The customer trace ID the product groups logs by — a logger-SDK UUID. Public callers
66
+ * can set the trace header, so anything non-canonical (oversized values, W3C
67
+ * traceparents, arbitrary strings) is ignored and a fresh trace is minted instead.
68
+ */
69
+ const CANONICAL_TRACE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
70
+ const isCanonicalTraceId = (value) => typeof value === 'string' && CANONICAL_TRACE_ID.test(value);
71
+ exports.isCanonicalTraceId = isCanonicalTraceId;
72
+ const getMetadataFromRequest = (request, source) => {
73
+ const headerTraceId = request.headers[types_1.defaultTraceHeaderKey];
74
+ return {
75
+ traceId: (0, exports.isCanonicalTraceId)(headerTraceId) ? headerTraceId : (0, unnbound_logger_sdk_1.getTraceId)(),
76
+ messageId: request.headers[types_1.defaultMessageHeaderKey] ?? (0, trace_1.getMessageId)(),
77
+ source,
78
+ };
79
+ };
69
80
  exports.getMetadataFromRequest = getMetadataFromRequest;
70
81
  const sourceKey = 'x-unnbound-source';
71
82
  const timestampKey = 'x-unnbound-timestamp';
@@ -76,17 +87,61 @@ const serializeMetadata = (metadata) => ({
76
87
  exports.serializeMetadata = serializeMetadata;
77
88
  const deserializeMetadata = (request) => ({
78
89
  source: request.headers[sourceKey],
79
- timestamp: request.headers[timestampKey] ? Number.parseInt(request.headers[timestampKey]) : undefined,
90
+ timestamp: request.headers[timestampKey] ? Number.parseInt(request.headers[timestampKey], 10) : undefined,
80
91
  });
81
- const buildResponsePayload = (result) => ({
92
+ const describeBody = (body) => {
93
+ if (body instanceof Uint8Array || body instanceof ArrayBuffer) {
94
+ return { binary: true, byteLength: body.byteLength };
95
+ }
96
+ return (0, utils_1.safeJsonParse)(body);
97
+ };
98
+ // Trace payloads are shipped to the logging pipeline; credentials must never ride along
99
+ // regardless of body shape (JSON, text, or binary — e.g. AS2's signed/encrypted MIME).
100
+ const REDACTED_TRACE_HEADERS = new Set([
101
+ 'authorization',
102
+ 'proxy-authorization',
103
+ 'cookie',
104
+ 'set-cookie',
105
+ 'x-service-token',
106
+ 'x-api-key',
107
+ 'x-auth-token',
108
+ ]);
109
+ const headersForTrace = (headers) => Object.fromEntries(Object.entries(headers).map(([name, value]) => [
110
+ name,
111
+ REDACTED_TRACE_HEADERS.has(name.toLowerCase()) ? '[redacted]' : value,
112
+ ]));
113
+ // AS2 routes carry partner-controlled protocol headers, so logging flips from a
114
+ // redaction denylist to an allowlist: protocol identity/framing and Temper correlation
115
+ // headers pass; everything else is redacted. Receipt-Delivery-Option (its URL may embed
116
+ // query credentials) and Subject (business metadata) are deliberately NOT listed.
117
+ const AS2_TRACE_HEADER_ALLOWLIST = new Set([
118
+ 'as2-from',
119
+ 'as2-to',
120
+ 'as2-version',
121
+ 'content-length',
122
+ 'content-transfer-encoding',
123
+ 'content-type',
124
+ 'date',
125
+ 'disposition-notification-options',
126
+ 'disposition-notification-to',
127
+ 'message-id',
128
+ 'mime-version',
129
+ 'x-message-id',
130
+ 'x-unnbound-trace-id',
131
+ ]);
132
+ const as2HeadersForTrace = (headers) => Object.fromEntries(Object.entries(headers).map(([name, value]) => [
133
+ name,
134
+ AS2_TRACE_HEADER_ALLOWLIST.has(name.toLowerCase()) ? value : '[redacted]',
135
+ ]));
136
+ const buildResponsePayload = (headersPolicy, result) => ({
82
137
  status: result?.status ?? 204,
83
- headers: {
138
+ headers: headersPolicy({
84
139
  'content-type': 'application/json; charset=utf-8',
85
140
  ...(result?.headers ? (0, exports.normalizeHeaders)(result.headers) : undefined),
86
- },
87
- body: result?.body ? (0, utils_1.safeJsonParse)(result.body) : undefined,
141
+ }),
142
+ body: result?.body ? describeBody(result.body) : undefined,
88
143
  });
89
- const buildIncomingPayload = (event, result) => {
144
+ const buildIncomingPayloadWith = (headersPolicy) => (event, result) => {
90
145
  return {
91
146
  type: 'http',
92
147
  source: event.metadata.source,
@@ -101,18 +156,19 @@ const buildIncomingPayload = (event, result) => {
101
156
  event.request.headers['x-real-ip']),
102
157
  incoming: true,
103
158
  request: {
104
- headers: event.request.headers,
105
- body: (0, utils_1.safeJsonParse)(event.request.body),
159
+ headers: headersPolicy(event.request.headers),
160
+ body: describeBody(event.request.body),
106
161
  },
107
162
  ...(result && {
108
163
  response: result.error
109
- ? buildResponsePayload(error_1.ServerError.fromError(result.error).toJson())
110
- : buildResponsePayload(result.result),
164
+ ? buildResponsePayload(headersPolicy, error_1.ServerError.fromError(result.error).toJson())
165
+ : buildResponsePayload(headersPolicy, result.result),
111
166
  }),
112
167
  },
113
168
  };
114
169
  };
115
- exports.buildIncomingPayload = buildIncomingPayload;
170
+ exports.buildIncomingPayload = buildIncomingPayloadWith(headersForTrace);
171
+ exports.buildAs2IncomingPayload = buildIncomingPayloadWith(as2HeadersForTrace);
116
172
  const batchArray = (array, size) => {
117
173
  return [...Array(Math.ceil(array.length / size))].map((_, i) => array.slice(i * size, i * size + size));
118
174
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "unnbound-events",
3
3
  "description": "Unified events SDK to handle HTTP routes and queued messages with a single routing API.",
4
- "version": "2.0.20",
4
+ "version": "2.1.0-beta.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "author": "Unnbound Team",
@@ -20,12 +20,11 @@
20
20
  "@hono/node-server": "^1.19.11",
21
21
  "axios": "1.16.0",
22
22
  "hono": "^4.12.21",
23
- "unnbound-logger-sdk": "^3.0.34"
23
+ "unnbound-logger-sdk": "3.1.0-beta.0"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/jest": "^29.5.12",
27
27
  "@types/node": "^24.12.2",
28
- "typescript": "^5.8.3",
29
28
  "vitest": "^4.0.15"
30
29
  },
31
30
  "files": [
@@ -40,7 +39,7 @@
40
39
  "scripts": {
41
40
  "build": "tsc",
42
41
  "test": "vitest run",
43
- "typecheck": "tsgo --noEmit",
42
+ "typecheck": "tsc --noEmit",
44
43
  "format": "biome format --write .",
45
44
  "format:check": "biome format .",
46
45
  "start:example": "tsx watch examples/node-server.ts",