unnbound-events 2.0.19 → 2.0.21-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/README.md +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +5 -5
- package/dist/lib/enqueue.js +8 -7
- package/dist/lib/event.d.ts +7 -1
- package/dist/lib/routing/endpoint.d.ts +2 -2
- package/dist/lib/routing/endpoint.js +9 -0
- package/dist/lib/server.d.ts +1 -0
- package/dist/lib/server.js +36 -12
- package/dist/lib/utils.d.ts +2 -2
- package/dist/lib/utils.js +9 -2
- package/package.json +10 -13
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ server.start();
|
|
|
30
30
|
The server automatically:
|
|
31
31
|
|
|
32
32
|
- Starts HTTP server on `PORT` (default: `3000`)
|
|
33
|
-
- Starts SQS long-polling if `UNNBOUND_SQS_URL` is set
|
|
33
|
+
- Starts SQS long-polling if `UNNBOUND_SQS_URL` is set. Set `queue.enabled: true` to require queue polling and fail fast when the queue URL is missing.
|
|
34
34
|
- Provides `/healthcheck` endpoint (returns `200 { status: 'ok' }`)
|
|
35
35
|
- Handles graceful shutdown on `SIGINT`/`SIGTERM`
|
|
36
36
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { createEnqueuer } from './lib/enqueue';
|
|
2
2
|
export type { ServerErrorOptions } from './lib/error';
|
|
3
|
-
export
|
|
3
|
+
export { ServerError } from './lib/error';
|
|
4
|
+
export type { EventMetadata, EventSource, IncomingEvent, IncomingRequest } from './lib/event';
|
|
4
5
|
export type { Handler } from './lib/routing/endpoint';
|
|
5
6
|
export type { MiddlewareHandler } from './lib/routing/middleware';
|
|
6
7
|
export type { EventVariables, MaybePromise } from './lib/routing/types';
|
|
8
|
+
export type { EventServer, EventServerOptions } from './lib/server';
|
|
7
9
|
export { createServer } from './lib/server';
|
|
8
|
-
export { ServerError } from './lib/error';
|
|
9
|
-
export { createEnqueuer } from './lib/enqueue';
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
var server_1 = require("./lib/server");
|
|
5
|
-
Object.defineProperty(exports, "createServer", { enumerable: true, get: function () { return server_1.createServer; } });
|
|
6
|
-
var error_1 = require("./lib/error");
|
|
7
|
-
Object.defineProperty(exports, "ServerError", { enumerable: true, get: function () { return error_1.ServerError; } });
|
|
3
|
+
exports.createServer = exports.ServerError = exports.createEnqueuer = void 0;
|
|
8
4
|
var enqueue_1 = require("./lib/enqueue");
|
|
9
5
|
Object.defineProperty(exports, "createEnqueuer", { enumerable: true, get: function () { return enqueue_1.createEnqueuer; } });
|
|
6
|
+
var error_1 = require("./lib/error");
|
|
7
|
+
Object.defineProperty(exports, "ServerError", { enumerable: true, get: function () { return error_1.ServerError; } });
|
|
8
|
+
var server_1 = require("./lib/server");
|
|
9
|
+
Object.defineProperty(exports, "createServer", { enumerable: true, get: function () { return server_1.createServer; } });
|
package/dist/lib/enqueue.js
CHANGED
|
@@ -19,26 +19,26 @@ const types_1 = require("unnbound-logger-sdk/dist/types");
|
|
|
19
19
|
* - Custom domain: proxy.domain.com/{shortId} → {shortId}
|
|
20
20
|
*/
|
|
21
21
|
const getWorkspaceId = (url) => {
|
|
22
|
-
|
|
22
|
+
const urlWithoutProtocol = url.replace(/^https?:\/\//, '');
|
|
23
23
|
// Custom domain: extract path after domain (not gotemper.com or legacy unnbound.ai)
|
|
24
|
-
if (!
|
|
25
|
-
return
|
|
24
|
+
if (!urlWithoutProtocol.includes('gotemper.com') && !urlWithoutProtocol.includes('unnbound.ai')) {
|
|
25
|
+
return urlWithoutProtocol
|
|
26
26
|
.replace(/^.+?\/(.+)$/, '$1')
|
|
27
27
|
.replace(/^(sandbox|staging)\//, '')
|
|
28
28
|
.replace(/^a?sync\//, '')
|
|
29
29
|
.split('/')[0];
|
|
30
30
|
}
|
|
31
31
|
// New path-based format: {env}.workflow.gotemper.com/{sandbox/}{shortId} or workflow.gotemper.com/{sandbox/}{shortId}
|
|
32
|
-
if (
|
|
32
|
+
if (urlWithoutProtocol.includes('.workflow.') || urlWithoutProtocol.startsWith('workflow.')) {
|
|
33
33
|
// Extract everything after the domain, then remove sandbox/ and async/ prefixes
|
|
34
|
-
const path =
|
|
34
|
+
const path = urlWithoutProtocol.replace(/^.+?\/(.+)$/, '$1');
|
|
35
35
|
return path
|
|
36
36
|
.replace(/^(sandbox|staging)\//, '')
|
|
37
37
|
.replace(/^a?sync\//, '')
|
|
38
38
|
.split('/')[0];
|
|
39
39
|
}
|
|
40
40
|
// Legacy hostname format: {shortId}.workspaces.{env}.unnbound.ai
|
|
41
|
-
return
|
|
41
|
+
return urlWithoutProtocol.split('.')[0];
|
|
42
42
|
};
|
|
43
43
|
const TYPE_PATH_PREFIXES = {
|
|
44
44
|
sandbox: '/sandbox',
|
|
@@ -101,6 +101,7 @@ const getWorkflowDomain = (url) => {
|
|
|
101
101
|
// Custom domain - return hostname as-is
|
|
102
102
|
return hostname;
|
|
103
103
|
};
|
|
104
|
+
const getWorkflowProtocol = (url) => (url.startsWith('http://') ? 'http' : 'https');
|
|
104
105
|
/**
|
|
105
106
|
* Create an HTTP-based enqueuer that posts to the ingress service.
|
|
106
107
|
* Used for legacy SQS mode or cross-workflow communication.
|
|
@@ -117,7 +118,7 @@ const createHttpEnqueuer = () => {
|
|
|
117
118
|
const workspaceId = getWorkspaceId(url);
|
|
118
119
|
const domain = getWorkflowDomain(url);
|
|
119
120
|
const workflowType = getWorkflowType(url);
|
|
120
|
-
const baseURL =
|
|
121
|
+
const baseURL = `${getWorkflowProtocol(url)}://${domain}${TYPE_PATH_PREFIXES[workflowType]}/async/${workspaceId}`;
|
|
121
122
|
const client = (0, unnbound_logger_sdk_1.traceAxios)(axios_1.default.create({ baseURL }), { getPayload: internal_1.internal });
|
|
122
123
|
return async (event, metadata) => {
|
|
123
124
|
unnbound_logger_sdk_1.logger.info({ event, metadata }, 'Enqueuing event via HTTP...');
|
package/dist/lib/event.d.ts
CHANGED
|
@@ -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
|
-
|
|
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;
|
|
@@ -8,12 +8,12 @@ export interface Endpoint<I> {
|
|
|
8
8
|
(path: string, middleware: MiddlewareHandler<I>, handler: Handler<I>): MaybePromise<void>;
|
|
9
9
|
}
|
|
10
10
|
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 |
|
|
11
|
+
export declare const buildEndpointArguments: (...args: EndpointArgs<EventVariables<{}>>) => (string | Handler<Context<EventEnvironment<{}>, any, {}>, Response> | MiddlewareHandler<Context<EventEnvironment<{}>, any, {}>>)[];
|
|
12
12
|
export declare const parseEndpointArguments: (...args: EndpointArgs<EventVariables<{}>>) => {
|
|
13
13
|
path: string;
|
|
14
14
|
middlewares: MiddlewareHandler<EventVariables<{}>>[];
|
|
15
15
|
handler: Handler<EventVariables<{}>, EventHandlerResult>;
|
|
16
16
|
};
|
|
17
17
|
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
|
|
18
|
+
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
19
|
export declare const attachTraceHeaders: (headers?: Record<string, string>) => Record<string, string> | undefined;
|
|
@@ -33,6 +33,15 @@ const respondWith = (context, result) => {
|
|
|
33
33
|
const headers = (0, exports.attachTraceHeaders)(result.headers);
|
|
34
34
|
if (!result.body)
|
|
35
35
|
return context.body(null, result.status, headers);
|
|
36
|
+
// Binary bodies (Buffer/Uint8Array/ArrayBuffer) go out verbatim — JSON-serializing
|
|
37
|
+
// them corrupts wire protocols like AS2, whose signed S/MIME MDN response must be
|
|
38
|
+
// byte-exact. Buffers can be views over a pooled ArrayBuffer, so slice to the view.
|
|
39
|
+
if (result.body instanceof Uint8Array || result.body instanceof ArrayBuffer) {
|
|
40
|
+
const bytes = result.body instanceof ArrayBuffer
|
|
41
|
+
? result.body
|
|
42
|
+
: result.body.buffer.slice(result.body.byteOffset, result.body.byteOffset + result.body.byteLength);
|
|
43
|
+
return context.body(bytes, result.status, headers);
|
|
44
|
+
}
|
|
36
45
|
return context.json(result.body, result.status, headers);
|
|
37
46
|
};
|
|
38
47
|
exports.respondWith = respondWith;
|
package/dist/lib/server.d.ts
CHANGED
package/dist/lib/server.js
CHANGED
|
@@ -14,6 +14,7 @@ class HttpQueueServer {
|
|
|
14
14
|
app = new hono_1.Hono();
|
|
15
15
|
http;
|
|
16
16
|
queue;
|
|
17
|
+
queueEnabled;
|
|
17
18
|
dispose;
|
|
18
19
|
constructor(options = {}) {
|
|
19
20
|
this.logger = options.logger ?? unnbound_logger_sdk_1.logger;
|
|
@@ -33,12 +34,14 @@ class HttpQueueServer {
|
|
|
33
34
|
return (0, endpoint_1.respondWith)(c, error.toJson());
|
|
34
35
|
});
|
|
35
36
|
this.http = new http_adapter_1.HttpAdapter({ app: this.app, logger: this.logger, ...options.http });
|
|
36
|
-
|
|
37
|
-
this.queue =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
this.queueEnabled = shouldEnableQueue(options.queue);
|
|
38
|
+
this.queue = this.queueEnabled
|
|
39
|
+
? new queue_adapter_1.QueueAdapter({
|
|
40
|
+
app: this.app,
|
|
41
|
+
logger: this.logger,
|
|
42
|
+
options: options.queue,
|
|
43
|
+
})
|
|
44
|
+
: null;
|
|
42
45
|
}
|
|
43
46
|
get = (...args) => {
|
|
44
47
|
// @ts-expect-error
|
|
@@ -73,17 +76,24 @@ class HttpQueueServer {
|
|
|
73
76
|
process.on('SIGINT', createOnSignal('SIGINT'));
|
|
74
77
|
process.on('SIGTERM', createOnSignal('SIGTERM'));
|
|
75
78
|
this.logger.debug({}, 'Starting server...');
|
|
79
|
+
const disposables = [];
|
|
76
80
|
try {
|
|
77
|
-
|
|
78
|
-
this.
|
|
79
|
-
await
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
disposables.push(await this.http.listen());
|
|
82
|
+
if (this.queue && this.queueEnabled) {
|
|
83
|
+
disposables.push(await this.queue.listen());
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
this.logger.info({
|
|
87
|
+
runtimeTarget: process.env.UNNBOUND_RUNTIME_TARGET ?? 'default',
|
|
88
|
+
queueConfigured: Boolean(process.env.UNNBOUND_SQS_URL),
|
|
89
|
+
}, 'Queue listener disabled for this runtime.');
|
|
90
|
+
}
|
|
91
|
+
this.dispose = () => this.stopDisposables(disposables);
|
|
83
92
|
this.logger.info('Server started.');
|
|
84
93
|
return this.dispose;
|
|
85
94
|
}
|
|
86
95
|
catch (error) {
|
|
96
|
+
await this.stopDisposables(disposables);
|
|
87
97
|
this.logger.error({ err: error }, 'Server failed to start.');
|
|
88
98
|
throw error;
|
|
89
99
|
}
|
|
@@ -95,6 +105,20 @@ class HttpQueueServer {
|
|
|
95
105
|
await this.dispose();
|
|
96
106
|
this.logger.debug({}, 'Server stopped.');
|
|
97
107
|
}
|
|
108
|
+
async stopDisposables(disposables) {
|
|
109
|
+
const results = await Promise.allSettled(disposables.map((dispose) => dispose()));
|
|
110
|
+
for (const result of results) {
|
|
111
|
+
if (result.status === 'rejected') {
|
|
112
|
+
this.logger.error({ err: result.reason }, 'Server disposable failed to stop.');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
98
116
|
}
|
|
99
117
|
const createServer = (options = {}) => new HttpQueueServer(options);
|
|
100
118
|
exports.createServer = createServer;
|
|
119
|
+
function shouldEnableQueue(options) {
|
|
120
|
+
if (options?.enabled !== undefined) {
|
|
121
|
+
return options.enabled;
|
|
122
|
+
}
|
|
123
|
+
return Boolean(process.env.UNNBOUND_SQS_URL);
|
|
124
|
+
}
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -17,7 +17,6 @@ export declare const buildIncomingPayload: (event: IncomingEvent<MatchedIncoming
|
|
|
17
17
|
source: EventSource;
|
|
18
18
|
delay: number | undefined;
|
|
19
19
|
http: {
|
|
20
|
-
response?: StrictEventHandlerResult | undefined;
|
|
21
20
|
url: string;
|
|
22
21
|
method: string;
|
|
23
22
|
query: Record<string, any>;
|
|
@@ -26,8 +25,9 @@ export declare const buildIncomingPayload: (event: IncomingEvent<MatchedIncoming
|
|
|
26
25
|
incoming: boolean;
|
|
27
26
|
request: {
|
|
28
27
|
headers: Record<string, string>;
|
|
29
|
-
body:
|
|
28
|
+
body: unknown;
|
|
30
29
|
};
|
|
30
|
+
response?: StrictEventHandlerResult | undefined;
|
|
31
31
|
};
|
|
32
32
|
};
|
|
33
33
|
export declare const batchArray: <T>(array: T[], size: number) => T[][];
|
package/dist/lib/utils.js
CHANGED
|
@@ -78,13 +78,20 @@ const deserializeMetadata = (request) => ({
|
|
|
78
78
|
source: request.headers[sourceKey],
|
|
79
79
|
timestamp: request.headers[timestampKey] ? Number.parseInt(request.headers[timestampKey]) : undefined,
|
|
80
80
|
});
|
|
81
|
+
/** Trace-safe body representation: binary bodies become a marker, not a byte dump. */
|
|
82
|
+
const describeBody = (body) => {
|
|
83
|
+
if (body instanceof Uint8Array || body instanceof ArrayBuffer) {
|
|
84
|
+
return { binary: true, byteLength: body.byteLength };
|
|
85
|
+
}
|
|
86
|
+
return (0, utils_1.safeJsonParse)(body);
|
|
87
|
+
};
|
|
81
88
|
const buildResponsePayload = (result) => ({
|
|
82
89
|
status: result?.status ?? 204,
|
|
83
90
|
headers: {
|
|
84
91
|
'content-type': 'application/json; charset=utf-8',
|
|
85
92
|
...(result?.headers ? (0, exports.normalizeHeaders)(result.headers) : undefined),
|
|
86
93
|
},
|
|
87
|
-
body: result?.body ? (
|
|
94
|
+
body: result?.body ? describeBody(result.body) : undefined,
|
|
88
95
|
});
|
|
89
96
|
const buildIncomingPayload = (event, result) => {
|
|
90
97
|
return {
|
|
@@ -102,7 +109,7 @@ const buildIncomingPayload = (event, result) => {
|
|
|
102
109
|
incoming: true,
|
|
103
110
|
request: {
|
|
104
111
|
headers: event.request.headers,
|
|
105
|
-
body: (
|
|
112
|
+
body: describeBody(event.request.body),
|
|
106
113
|
},
|
|
107
114
|
...(result && {
|
|
108
115
|
response: result.error
|
package/package.json
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
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.
|
|
4
|
+
"version": "2.0.21-beta.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"build": "
|
|
9
|
-
"test": "
|
|
10
|
-
"typecheck": "tsc
|
|
11
|
-
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .",
|
|
12
|
-
"lint:fix": "bun run lint --fix",
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "vitest run",
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
13
11
|
"format": "biome format --write .",
|
|
14
12
|
"format:check": "biome format .",
|
|
15
|
-
"prepublishOnly": "
|
|
16
|
-
"start:example": "
|
|
13
|
+
"prepublishOnly": "pnpm run build",
|
|
14
|
+
"start:example": "tsx watch examples/node-server.ts",
|
|
17
15
|
"version:bump": "npm version patch"
|
|
18
16
|
},
|
|
19
17
|
"author": "Unnbound Team",
|
|
@@ -30,15 +28,14 @@
|
|
|
30
28
|
"@aws-sdk/client-s3": "^3.0.0",
|
|
31
29
|
"@aws-sdk/client-sqs": "^3.0.0",
|
|
32
30
|
"@hono/node-server": "^1.19.11",
|
|
33
|
-
"axios": "
|
|
34
|
-
"hono": "^4.12.
|
|
35
|
-
"ioredis": "^5.4.1",
|
|
31
|
+
"axios": "1.16.0",
|
|
32
|
+
"hono": "^4.12.21",
|
|
36
33
|
"unnbound-logger-sdk": "^3.0.34"
|
|
37
34
|
},
|
|
38
35
|
"devDependencies": {
|
|
39
36
|
"@types/jest": "^29.5.12",
|
|
40
|
-
"@types/node": "^
|
|
41
|
-
"
|
|
37
|
+
"@types/node": "^24.12.2",
|
|
38
|
+
"vitest": "^4.0.15"
|
|
42
39
|
},
|
|
43
40
|
"files": [
|
|
44
41
|
"dist/**/*",
|