unnbound-events 2.0.12 → 2.0.14
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/lib/adapters/extended-sqs-client.d.ts +10 -3
- package/dist/lib/adapters/extended-sqs-client.js +45 -16
- package/dist/lib/adapters/queue-adapter.d.ts +0 -2
- package/dist/lib/adapters/queue-adapter.js +16 -42
- package/dist/lib/routing/middleware.d.ts +1 -1
- package/dist/lib/routing/middleware.js +1 -1
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.js +6 -3
- package/package.json +1 -1
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { S3Client } from '@aws-sdk/client-s3';
|
|
2
|
-
import {
|
|
2
|
+
import { Message, ReceiveMessageCommand, SQSClient } from '@aws-sdk/client-sqs';
|
|
3
|
+
import { IncomingEvent, IncomingRequest } from '../event';
|
|
3
4
|
export interface S3Pointer {
|
|
4
5
|
bucket: string;
|
|
5
6
|
key: string;
|
|
6
7
|
}
|
|
7
8
|
export type S3PointerMessage = ['software.amazon.payloadoffloading.PayloadS3Pointer', S3Pointer];
|
|
8
9
|
type SQSClientConfig = ConstructorParameters<typeof SQSClient>[0];
|
|
10
|
+
export interface ExtendedSqsMessage {
|
|
11
|
+
message: Message;
|
|
12
|
+
event: IncomingEvent<IncomingRequest>;
|
|
13
|
+
}
|
|
9
14
|
export type ExtendedSQSClientConfig = SQSClientConfig & {
|
|
10
15
|
s3: S3Client;
|
|
11
16
|
};
|
|
@@ -13,8 +18,10 @@ export declare class ExtendedSQSClient extends SQSClient {
|
|
|
13
18
|
private readonly s3;
|
|
14
19
|
private readonly S3_POINTER_CLASS;
|
|
15
20
|
constructor(config: ExtendedSQSClientConfig);
|
|
16
|
-
|
|
21
|
+
receive(command: ReceiveMessageCommand, options?: Parameters<SQSClient['send']>[1]): Promise<ExtendedSqsMessage[]>;
|
|
17
22
|
private retrieveMessage;
|
|
18
|
-
|
|
23
|
+
private isSqsMessage;
|
|
24
|
+
private isS3PointerMessage;
|
|
25
|
+
private parseEvent;
|
|
19
26
|
}
|
|
20
27
|
export {};
|
|
@@ -5,6 +5,7 @@ const client_s3_1 = require("@aws-sdk/client-s3");
|
|
|
5
5
|
const client_sqs_1 = require("@aws-sdk/client-sqs");
|
|
6
6
|
const unnbound_logger_sdk_1 = require("unnbound-logger-sdk");
|
|
7
7
|
const internal_1 = require("unnbound-logger-sdk/dist/internal");
|
|
8
|
+
const utils_1 = require("../utils");
|
|
8
9
|
class ExtendedSQSClient extends client_sqs_1.SQSClient {
|
|
9
10
|
s3;
|
|
10
11
|
S3_POINTER_CLASS = 'software.amazon.payloadoffloading.PayloadS3Pointer';
|
|
@@ -12,32 +13,60 @@ class ExtendedSQSClient extends client_sqs_1.SQSClient {
|
|
|
12
13
|
super(config);
|
|
13
14
|
this.s3 = config.s3;
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
async receive(command, options) {
|
|
17
|
+
const result = await this.send(command, options);
|
|
18
|
+
return await Promise.all(result.Messages?.map(async (message) => ({
|
|
19
|
+
message,
|
|
20
|
+
event: await this.retrieveMessage(message),
|
|
21
|
+
})) ?? []);
|
|
17
22
|
}
|
|
18
23
|
async retrieveMessage(message) {
|
|
19
24
|
if (!message.Body)
|
|
20
|
-
|
|
21
|
-
if (!message.Body.startsWith(`["${this.S3_POINTER_CLASS}",`))
|
|
22
|
-
return message;
|
|
25
|
+
throw new Error('Queue message body is missing.');
|
|
23
26
|
const parsed = JSON.parse(message.Body);
|
|
24
|
-
if (!this.
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
+
if (!this.isSqsMessage(parsed))
|
|
28
|
+
throw new Error('Invalid queue message format.');
|
|
29
|
+
const payload = parsed.request.body;
|
|
30
|
+
if (!this.isS3PointerMessage(payload))
|
|
31
|
+
return this.parseEvent(parsed);
|
|
32
|
+
const { bucket, key } = payload[1];
|
|
27
33
|
const body = await (0, unnbound_logger_sdk_1.startSpan)('Retrieve message from storage', async () => {
|
|
28
34
|
const payload = await this.s3.send(new client_s3_1.GetObjectCommand({ Bucket: bucket, Key: key }));
|
|
29
|
-
const body = await payload.Body?.
|
|
35
|
+
const body = await payload.Body?.transformToByteArray();
|
|
30
36
|
if (!body)
|
|
31
37
|
throw new Error('Failed to retrieve queue message from storage.');
|
|
32
|
-
return body;
|
|
33
|
-
}, (o) => ({ bucket, key, ...(0, internal_1.internal)(), size: o?.result?.
|
|
34
|
-
return { ...
|
|
38
|
+
return body.buffer;
|
|
39
|
+
}, (o) => ({ bucket, key, ...(0, internal_1.internal)(), size: o?.result?.byteLength }));
|
|
40
|
+
return this.parseEvent({ ...parsed, request: { ...parsed.request, body } });
|
|
35
41
|
}
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
isSqsMessage(message) {
|
|
43
|
+
return (!!message &&
|
|
44
|
+
typeof message === 'object' &&
|
|
45
|
+
'id' in message &&
|
|
46
|
+
'timestamp' in message &&
|
|
47
|
+
'request' in message);
|
|
48
|
+
}
|
|
49
|
+
isS3PointerMessage(body) {
|
|
50
|
+
return Array.isArray(body) && body.length === 2 && body[0] === this.S3_POINTER_CLASS;
|
|
51
|
+
}
|
|
52
|
+
parseEvent(message) {
|
|
53
|
+
const method = message.request.method.toLowerCase();
|
|
54
|
+
const url = new URL(message.request.url, `https://unnbound.ai`);
|
|
55
|
+
const request = {
|
|
56
|
+
url: url.toString(),
|
|
57
|
+
method,
|
|
58
|
+
path: url.pathname,
|
|
59
|
+
headers: (0, utils_1.normalizeHeaders)(message.request.headers),
|
|
60
|
+
query: Object.fromEntries(url.searchParams.entries()),
|
|
61
|
+
body: message.request.body,
|
|
62
|
+
};
|
|
38
63
|
return {
|
|
39
|
-
|
|
40
|
-
|
|
64
|
+
request,
|
|
65
|
+
timestamp: message.timestamp,
|
|
66
|
+
metadata: {
|
|
67
|
+
...message.metadata,
|
|
68
|
+
...(0, utils_1.getMetadataFromRequest)(request, 'queue'),
|
|
69
|
+
},
|
|
41
70
|
};
|
|
42
71
|
}
|
|
43
72
|
}
|
|
@@ -41,10 +41,10 @@ class QueueAdapter {
|
|
|
41
41
|
if (controller.signal.aborted)
|
|
42
42
|
return resolve(void 0);
|
|
43
43
|
try {
|
|
44
|
-
const
|
|
44
|
+
const messages = await this.sqs.receive(this.getReceiveCommand(false), {
|
|
45
45
|
abortSignal: controller.signal,
|
|
46
46
|
});
|
|
47
|
-
if (!messages
|
|
47
|
+
if (!messages.length)
|
|
48
48
|
return;
|
|
49
49
|
const { failed } = await (0, unnbound_logger_sdk_1.startSpan)('Process messages', async () => {
|
|
50
50
|
const results = await Promise.all(messages.map(this.processMessage.bind(this)));
|
|
@@ -77,14 +77,17 @@ class QueueAdapter {
|
|
|
77
77
|
}
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
|
-
async processMessage(message) {
|
|
80
|
+
async processMessage({ message, event, }) {
|
|
81
81
|
try {
|
|
82
|
-
const event = this.parseEvent(message);
|
|
83
82
|
// It forward the request to the HTTP server that will
|
|
84
83
|
// handle it according to the routes the user set up.
|
|
85
84
|
const response = await this.app.request(event.request.url, {
|
|
86
85
|
method: event.request.method,
|
|
87
|
-
body: event.request.body
|
|
86
|
+
body: event.request.body instanceof ArrayBuffer || event.request.body instanceof FormData
|
|
87
|
+
? event.request.body
|
|
88
|
+
: typeof event.request.body === 'object'
|
|
89
|
+
? JSON.stringify(event.request.body)
|
|
90
|
+
: event.request.body,
|
|
88
91
|
headers: {
|
|
89
92
|
...event.request.headers,
|
|
90
93
|
...(0, utils_1.serializeMetadata)({ source: 'queue', timestamp: event.timestamp }),
|
|
@@ -118,44 +121,15 @@ class QueueAdapter {
|
|
|
118
121
|
async delete(messages) {
|
|
119
122
|
if (!messages.length)
|
|
120
123
|
return;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
const queue = this.getQueue();
|
|
125
|
+
// Batch the messages into 10 messages per batch
|
|
126
|
+
const batches = (0, utils_1.batchArray)(messages, 10);
|
|
127
|
+
await Promise.all(batches.map(async (messages) => {
|
|
128
|
+
await this.sqs.send(new client_sqs_1.DeleteMessageBatchCommand({
|
|
129
|
+
QueueUrl: queue,
|
|
130
|
+
Entries: messages.map((m) => ({ Id: m.MessageId, ReceiptHandle: m.ReceiptHandle })),
|
|
131
|
+
}));
|
|
124
132
|
}));
|
|
125
133
|
}
|
|
126
|
-
isSqsMessage(message) {
|
|
127
|
-
return (!!message &&
|
|
128
|
-
typeof message === 'object' &&
|
|
129
|
-
'id' in message &&
|
|
130
|
-
'timestamp' in message &&
|
|
131
|
-
'request' in message);
|
|
132
|
-
}
|
|
133
|
-
parseEvent(message) {
|
|
134
|
-
if (!message.Body)
|
|
135
|
-
throw new Error('Queue message body is missing.');
|
|
136
|
-
if (typeof message.Body !== 'string')
|
|
137
|
-
throw new Error('Queue message body is malformed.');
|
|
138
|
-
const parsed = JSON.parse(message.Body);
|
|
139
|
-
if (!this.isSqsMessage(parsed))
|
|
140
|
-
throw new Error('Invalid queue message format.');
|
|
141
|
-
const method = parsed.request.method.toLowerCase();
|
|
142
|
-
const url = new URL(parsed.request.url, `https://unnbound.ai`);
|
|
143
|
-
const request = {
|
|
144
|
-
url: url.toString(),
|
|
145
|
-
method,
|
|
146
|
-
path: url.pathname,
|
|
147
|
-
headers: (0, utils_1.normalizeHeaders)(parsed.request.headers),
|
|
148
|
-
query: Object.fromEntries(url.searchParams.entries()),
|
|
149
|
-
body: parsed.request.body ? Buffer.from(parsed.request.body, 'base64') : undefined,
|
|
150
|
-
};
|
|
151
|
-
return {
|
|
152
|
-
request,
|
|
153
|
-
timestamp: parsed.timestamp,
|
|
154
|
-
metadata: {
|
|
155
|
-
...parsed.metadata,
|
|
156
|
-
...(0, utils_1.getMetadataFromRequest)(request, 'queue'),
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
134
|
}
|
|
161
135
|
exports.QueueAdapter = QueueAdapter;
|
|
@@ -14,4 +14,4 @@ export declare const parseMiddlewareArguments: (...args: MiddlewareArgs<EventVar
|
|
|
14
14
|
path: undefined;
|
|
15
15
|
middlewares: MiddlewareHandler<EventVariables<{}>>[];
|
|
16
16
|
};
|
|
17
|
-
export declare const buildMiddlewareArguments: (...args: MiddlewareArgs<EventVariables<{}>>) => MiddlewareHandler<
|
|
17
|
+
export declare const buildMiddlewareArguments: (...args: MiddlewareArgs<EventVariables<{}>>) => MiddlewareHandler<Context<EventEnvironment<{}>, any, {}>>[] | readonly [string, ...MiddlewareHandler<Context<EventEnvironment<{}>, any, {}>>[]];
|
|
@@ -14,7 +14,7 @@ exports.parseMiddlewareArguments = parseMiddlewareArguments;
|
|
|
14
14
|
const buildMiddlewareArguments = (...args) => {
|
|
15
15
|
const { path, middlewares } = (0, exports.parseMiddlewareArguments)(...args);
|
|
16
16
|
if (!path)
|
|
17
|
-
return middlewares;
|
|
17
|
+
return middlewares.map(exports.patchMiddleware);
|
|
18
18
|
return [path, ...middlewares.map(exports.patchMiddleware)];
|
|
19
19
|
};
|
|
20
20
|
exports.buildMiddlewareArguments = buildMiddlewareArguments;
|
package/dist/lib/utils.d.ts
CHANGED
package/dist/lib/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildIncomingPayload = exports.serializeMetadata = exports.getMetadataFromRequest = exports.getIncomingEvent = exports.normalizeHeaders = void 0;
|
|
3
|
+
exports.batchArray = exports.buildIncomingPayload = exports.serializeMetadata = exports.getMetadataFromRequest = exports.getIncomingEvent = exports.normalizeHeaders = void 0;
|
|
4
4
|
const unnbound_logger_sdk_1 = require("unnbound-logger-sdk");
|
|
5
5
|
const types_1 = require("unnbound-logger-sdk/dist/types");
|
|
6
6
|
const trace_1 = require("unnbound-logger-sdk/dist/trace");
|
|
@@ -55,8 +55,7 @@ const parseBody = async (request, headers) => {
|
|
|
55
55
|
return await request.parseBody();
|
|
56
56
|
if (headers['content-type']?.includes('text/plain'))
|
|
57
57
|
return await request.text();
|
|
58
|
-
|
|
59
|
-
return undefined;
|
|
58
|
+
return request.arrayBuffer();
|
|
60
59
|
}
|
|
61
60
|
catch {
|
|
62
61
|
return undefined;
|
|
@@ -114,3 +113,7 @@ const buildIncomingPayload = (event, result) => {
|
|
|
114
113
|
};
|
|
115
114
|
};
|
|
116
115
|
exports.buildIncomingPayload = buildIncomingPayload;
|
|
116
|
+
const batchArray = (array, size) => {
|
|
117
|
+
return [...Array(Math.ceil(array.length / size))].map((_, i) => array.slice(i * size, i * size + size));
|
|
118
|
+
};
|
|
119
|
+
exports.batchArray = batchArray;
|
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.
|
|
4
|
+
"version": "2.0.14",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"author": "Unnbound Team",
|