unnbound-events 1.0.18 → 1.0.20
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/LICENSE +21 -0
- package/README.md +71 -16
- package/dist/events/src/index.d.ts +1 -1
- package/dist/events/src/lib/client.js +10 -62
- package/dist/events/src/lib/types.d.ts +0 -13
- package/dist/lib/client.js +53 -56
- package/dist/lib/types.d.ts +1 -0
- package/dist/logger/src/axios.d.ts +2 -4
- package/dist/logger/src/axios.js +6 -11
- package/dist/logger/src/middleware.d.ts +2 -9
- package/dist/logger/src/middleware.js +8 -12
- package/dist/logger/src/types.d.ts +1 -2
- package/package.json +23 -26
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Unnbound Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -132,6 +132,56 @@ API:
|
|
|
132
132
|
- `use(matcher, middleware)` where matcher is `string | RegExp | (path) => boolean`
|
|
133
133
|
- `middleware(req, next) -> Promise<EventResponse>`
|
|
134
134
|
|
|
135
|
+
## Enqueue Events
|
|
136
|
+
|
|
137
|
+
The `enqueue` function allows you to send events to the queue by using the async endpoint.
|
|
138
|
+
|
|
139
|
+
### Basic Usage
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { createEventsClient } from 'unnbound-events-sdk';
|
|
143
|
+
|
|
144
|
+
const client = createEventsClient();
|
|
145
|
+
|
|
146
|
+
client.post('/process-job', async (req) => {
|
|
147
|
+
console.log('Handle job:', req.body);
|
|
148
|
+
|
|
149
|
+
// Split job into multiple tasks
|
|
150
|
+
const tasks = [...];
|
|
151
|
+
|
|
152
|
+
tasks.map(async (task) => {
|
|
153
|
+
await client.enqueue({
|
|
154
|
+
method: 'POST',
|
|
155
|
+
path: '/process-task',
|
|
156
|
+
body: { jobId: '123', data: 'important data' }
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return { status: 200, body: { processed: true } };
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
client.post('/process-task', async (req) => {
|
|
164
|
+
console.log('Handle task:', req.body);
|
|
165
|
+
|
|
166
|
+
return { status: 200, body: { processed: true } };
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await client.start();
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Scheduled Jobs
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
// Trigger scheduled processing
|
|
176
|
+
setInterval(async () => {
|
|
177
|
+
await client.enqueue({
|
|
178
|
+
method: 'POST',
|
|
179
|
+
path: '/cleanup',
|
|
180
|
+
body: { timestamp: Date.now() },
|
|
181
|
+
});
|
|
182
|
+
}, 60000); // Every minute
|
|
183
|
+
```
|
|
184
|
+
|
|
135
185
|
## Message envelope
|
|
136
186
|
|
|
137
187
|
The SQS record `body` should be a JSON string matching this shape:
|
|
@@ -160,7 +210,17 @@ interface QueueEnvelope {
|
|
|
160
210
|
|
|
161
211
|
The SDK supports automatic handling of large payloads (>256KB) stored in S3, which is necessary since SQS has a 256KB message size limit.
|
|
162
212
|
|
|
163
|
-
|
|
213
|
+
The S3 SDK (`@aws-sdk/client-s3`) is included as a dependency, so no additional installation is required.
|
|
214
|
+
|
|
215
|
+
### Automatic Configuration
|
|
216
|
+
|
|
217
|
+
The SDK will automatically enable S3 support when environment variables are set:
|
|
218
|
+
|
|
219
|
+
- `UNNBOUND_S3_PAYLOAD_BUCKET` or `S3_BUCKET` - S3 bucket name for payload storage
|
|
220
|
+
|
|
221
|
+
No code changes needed! Just set the environment variable and the SDK will handle S3 payloads automatically.
|
|
222
|
+
|
|
223
|
+
### Manual Configuration
|
|
164
224
|
|
|
165
225
|
Configure S3 support when starting the client:
|
|
166
226
|
|
|
@@ -191,14 +251,19 @@ await client.sqsListen({
|
|
|
191
251
|
The SDK supports two S3 payload formats:
|
|
192
252
|
|
|
193
253
|
1. **ExtendedSQSClient Pointer Format** (AWS Extended Client Library compatible):
|
|
254
|
+
|
|
194
255
|
```json
|
|
195
|
-
[
|
|
196
|
-
"
|
|
197
|
-
|
|
198
|
-
|
|
256
|
+
[
|
|
257
|
+
"software.amazon.payloadoffloading.PayloadS3Pointer",
|
|
258
|
+
{
|
|
259
|
+
"s3BucketName": "bucket-name",
|
|
260
|
+
"s3Key": "key-path"
|
|
261
|
+
}
|
|
262
|
+
]
|
|
199
263
|
```
|
|
200
264
|
|
|
201
265
|
2. **Envelope-based S3 Payloads** (Unnbound format):
|
|
266
|
+
|
|
202
267
|
```json
|
|
203
268
|
{
|
|
204
269
|
"payload": {
|
|
@@ -220,6 +285,7 @@ The SDK supports two S3 payload formats:
|
|
|
220
285
|
### Compression Support
|
|
221
286
|
|
|
222
287
|
The SDK automatically decompresses payloads with:
|
|
288
|
+
|
|
223
289
|
- `brotli` compression
|
|
224
290
|
- `gzip` compression
|
|
225
291
|
|
|
@@ -272,14 +338,3 @@ For S3 payload support, configure:
|
|
|
272
338
|
export UNNBOUND_S3_PAYLOAD_BUCKET="my-payload-bucket"
|
|
273
339
|
export AWS_REGION="us-west-2"
|
|
274
340
|
```
|
|
275
|
-
|
|
276
|
-
### AWS Credentials
|
|
277
|
-
|
|
278
|
-
When running in ECS with a Task Role, no additional credentials are needed. The AWS SDK will automatically use the ECS Task Role credentials.
|
|
279
|
-
|
|
280
|
-
For local development or other environments, set:
|
|
281
|
-
|
|
282
|
-
```bash
|
|
283
|
-
export AWS_ACCESS_KEY_ID="your-access-key"
|
|
284
|
-
export AWS_SECRET_ACCESS_KEY="your-secret-key"
|
|
285
|
-
```
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { createEventsClient } from './lib/client';
|
|
2
|
-
export type { EventsClient, EventRequest, EventResponse, RouteHandler, RouteMatcher, RegisteredRoute, SqsBatchEvent,
|
|
2
|
+
export type { EventsClient, EventRequest, EventResponse, RouteHandler, RouteMatcher, RegisteredRoute, SqsBatchEvent, } from './lib/types';
|
|
3
3
|
export { createExpressMiddleware } from './lib/adapters/express';
|
|
4
4
|
export { createSqsConsumer } from './lib/adapters/sqs';
|
|
@@ -183,16 +183,16 @@ function createEventsClient() {
|
|
|
183
183
|
};
|
|
184
184
|
},
|
|
185
185
|
sqsListen(options) {
|
|
186
|
-
const
|
|
186
|
+
const AWS = (() => {
|
|
187
187
|
try {
|
|
188
|
-
return require('
|
|
188
|
+
return require('aws-sdk');
|
|
189
189
|
}
|
|
190
190
|
catch {
|
|
191
191
|
return null;
|
|
192
192
|
}
|
|
193
193
|
})();
|
|
194
|
-
if (!
|
|
195
|
-
throw new Error('
|
|
194
|
+
if (!AWS) {
|
|
195
|
+
throw new Error('aws-sdk is required to use sqsListen');
|
|
196
196
|
}
|
|
197
197
|
const env = globalThis
|
|
198
198
|
.process?.env ?? {};
|
|
@@ -205,7 +205,8 @@ function createEventsClient() {
|
|
|
205
205
|
const waitTimeSeconds = options?.waitTimeSeconds ?? 10;
|
|
206
206
|
const maxMessages = options?.maxMessages ?? 10;
|
|
207
207
|
const visibilityTimeout = options?.visibilityTimeoutSeconds;
|
|
208
|
-
const
|
|
208
|
+
const AWSLib = AWS;
|
|
209
|
+
const sqs = new AWSLib.SQS({ region, endpoint });
|
|
209
210
|
let stopped = false;
|
|
210
211
|
const consume = this.sqs();
|
|
211
212
|
async function loop() {
|
|
@@ -215,9 +216,10 @@ function createEventsClient() {
|
|
|
215
216
|
QueueUrl: queueUrl,
|
|
216
217
|
MaxNumberOfMessages: maxMessages,
|
|
217
218
|
WaitTimeSeconds: waitTimeSeconds,
|
|
218
|
-
...(visibilityTimeout && { VisibilityTimeout: visibilityTimeout }),
|
|
219
219
|
};
|
|
220
|
-
|
|
220
|
+
if (visibilityTimeout)
|
|
221
|
+
params.VisibilityTimeout = visibilityTimeout;
|
|
222
|
+
const resp = await sqs.receiveMessage(params).promise();
|
|
221
223
|
const messages = (resp.Messages ?? []).map((m) => ({
|
|
222
224
|
id: m.MessageId ?? '',
|
|
223
225
|
body: String(m.Body ?? ''),
|
|
@@ -235,7 +237,7 @@ function createEventsClient() {
|
|
|
235
237
|
for (let i = 0; i < toDelete.length; i += 10) {
|
|
236
238
|
const chunk = toDelete.slice(i, i + 10);
|
|
237
239
|
if (chunk.length > 0) {
|
|
238
|
-
await sqs.
|
|
240
|
+
await sqs.deleteMessageBatch({ QueueUrl: queueUrl, Entries: chunk }).promise();
|
|
239
241
|
}
|
|
240
242
|
}
|
|
241
243
|
}
|
|
@@ -254,59 +256,5 @@ function createEventsClient() {
|
|
|
254
256
|
},
|
|
255
257
|
});
|
|
256
258
|
},
|
|
257
|
-
async start(options) {
|
|
258
|
-
// Default to starting both HTTP and SQS if no options provided
|
|
259
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
260
|
-
const httpOptions = options?.http ?? { port: 3000 };
|
|
261
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
262
|
-
const sqsOptions = options?.sqs ?? {};
|
|
263
|
-
// Start HTTP server
|
|
264
|
-
let httpServer;
|
|
265
|
-
try {
|
|
266
|
-
httpServer = await this.http(httpOptions);
|
|
267
|
-
}
|
|
268
|
-
catch (error) {
|
|
269
|
-
unnbound_logger_sdk_1.logger.warn({ error }, 'Failed to start HTTP server, continuing without it');
|
|
270
|
-
}
|
|
271
|
-
// Start SQS listener
|
|
272
|
-
let sqsListener;
|
|
273
|
-
try {
|
|
274
|
-
sqsListener = await this.sqsListen(sqsOptions);
|
|
275
|
-
}
|
|
276
|
-
catch (error) {
|
|
277
|
-
unnbound_logger_sdk_1.logger.warn({ error }, 'Failed to start SQS listener, continuing without it');
|
|
278
|
-
}
|
|
279
|
-
// If neither HTTP nor SQS started successfully, throw an error
|
|
280
|
-
if (!httpServer && !sqsListener) {
|
|
281
|
-
throw new Error('Failed to start any transport. Please check your configuration and try again.');
|
|
282
|
-
}
|
|
283
|
-
// Set up graceful shutdown
|
|
284
|
-
const g = globalThis;
|
|
285
|
-
const shutdown = async () => {
|
|
286
|
-
unnbound_logger_sdk_1.logger.info('Received shutdown signal, closing servers...');
|
|
287
|
-
const promises = [];
|
|
288
|
-
if (httpServer)
|
|
289
|
-
promises.push(httpServer.close());
|
|
290
|
-
if (sqsListener)
|
|
291
|
-
promises.push(sqsListener.stop());
|
|
292
|
-
await Promise.all(promises);
|
|
293
|
-
unnbound_logger_sdk_1.logger.info('All servers closed');
|
|
294
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
295
|
-
g.process?.exit?.(0);
|
|
296
|
-
};
|
|
297
|
-
// Handle SIGINT and SIGTERM
|
|
298
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
299
|
-
g.process?.on?.('SIGINT', shutdown);
|
|
300
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
301
|
-
g.process?.on?.('SIGTERM', shutdown);
|
|
302
|
-
// Log startup information
|
|
303
|
-
const services = [];
|
|
304
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
305
|
-
if (httpServer)
|
|
306
|
-
services.push(`HTTP on port ${httpOptions?.port ?? 3000}`);
|
|
307
|
-
if (sqsListener)
|
|
308
|
-
services.push('SQS listener');
|
|
309
|
-
unnbound_logger_sdk_1.logger.info({ services: services.join(' and ') }, 'Services started. Press Ctrl+C to stop.');
|
|
310
|
-
},
|
|
311
259
|
};
|
|
312
260
|
}
|
|
@@ -26,18 +26,6 @@ export type SqsRecord = {
|
|
|
26
26
|
export interface SqsBatchEvent {
|
|
27
27
|
Records: SqsRecord[];
|
|
28
28
|
}
|
|
29
|
-
export interface StartOptions {
|
|
30
|
-
http?: {
|
|
31
|
-
port?: number;
|
|
32
|
-
};
|
|
33
|
-
sqs?: {
|
|
34
|
-
queueUrl?: string;
|
|
35
|
-
region?: string;
|
|
36
|
-
waitTimeSeconds?: number;
|
|
37
|
-
maxMessages?: number;
|
|
38
|
-
visibilityTimeoutSeconds?: number;
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
29
|
export interface EventsClient {
|
|
42
30
|
on: (method: HttpMethod, matcher: RouteMatcher, handler: RouteHandler<any, any>) => void;
|
|
43
31
|
handle: (request: EventRequest<any>) => Promise<EventResponse<any>>;
|
|
@@ -69,5 +57,4 @@ export interface EventsClient {
|
|
|
69
57
|
}) => Promise<{
|
|
70
58
|
stop: () => Promise<void>;
|
|
71
59
|
}>;
|
|
72
|
-
start: (options?: StartOptions) => Promise<void>;
|
|
73
60
|
}
|
package/dist/lib/client.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.defaultIgnoreTraceRoutes = exports.shouldIgnorePath = void 0;
|
|
4
7
|
exports.createEventsClient = createEventsClient;
|
|
5
8
|
const unnbound_logger_sdk_1 = require("unnbound-logger-sdk");
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
})();
|
|
9
|
+
const internal_1 = require("unnbound-logger-sdk/dist/internal");
|
|
10
|
+
const axios_1 = __importDefault(require("axios"));
|
|
11
|
+
const TRACE_HEADER_KEY = 'x-unnbound-trace-id';
|
|
12
|
+
const MESSAGE_HEADER_KEY = 'x-message-id';
|
|
13
|
+
const http = require('http');
|
|
14
14
|
function matchPath(matcher, path) {
|
|
15
15
|
if (typeof matcher === 'string') {
|
|
16
16
|
return matcher === path;
|
|
@@ -23,15 +23,13 @@ function matchPath(matcher, path) {
|
|
|
23
23
|
function isMiddleware(value) {
|
|
24
24
|
return typeof value === 'function' && value.length >= 2;
|
|
25
25
|
}
|
|
26
|
-
const TRACE_HEADER_KEY = 'x-unnbound-trace-id';
|
|
27
|
-
const MESSAGE_HEADER_KEY = 'x-message-id';
|
|
28
26
|
const getHeaderValue = (headers, name) => {
|
|
29
27
|
const needle = name.toLowerCase();
|
|
30
28
|
for (const [key, value] of Object.entries(headers)) {
|
|
31
29
|
if (key.toLowerCase() === needle)
|
|
32
30
|
return value;
|
|
33
31
|
}
|
|
34
|
-
return
|
|
32
|
+
return;
|
|
35
33
|
};
|
|
36
34
|
const shouldIgnorePath = (path, patterns) => {
|
|
37
35
|
return patterns.some((pattern) => {
|
|
@@ -48,17 +46,7 @@ exports.shouldIgnorePath = shouldIgnorePath;
|
|
|
48
46
|
exports.defaultIgnoreTraceRoutes = ['/health', '/healthcheck'];
|
|
49
47
|
// Helper function to create S3 client
|
|
50
48
|
function createS3Client(options) {
|
|
51
|
-
const awsS3 = (
|
|
52
|
-
try {
|
|
53
|
-
return require('@aws-sdk/client-s3');
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
})();
|
|
59
|
-
if (!awsS3) {
|
|
60
|
-
throw new Error('@aws-sdk/client-s3 is required for S3 payload support');
|
|
61
|
-
}
|
|
49
|
+
const awsS3 = require('@aws-sdk/client-s3');
|
|
62
50
|
const env = globalThis.process
|
|
63
51
|
?.env ?? {};
|
|
64
52
|
const region = options.region || env.AWS_REGION || 'us-east-1';
|
|
@@ -76,7 +64,7 @@ async function fetchS3Payload(s3Client, bucket, key, versionId) {
|
|
|
76
64
|
params.VersionId = versionId;
|
|
77
65
|
}
|
|
78
66
|
try {
|
|
79
|
-
const response = await s3Client.send(new awsS3.GetObjectCommand(params));
|
|
67
|
+
const response = (await s3Client.send(new awsS3.GetObjectCommand(params)));
|
|
80
68
|
const body = response.Body;
|
|
81
69
|
// Convert stream to string
|
|
82
70
|
const chunks = [];
|
|
@@ -94,17 +82,7 @@ async function fetchS3Payload(s3Client, bucket, key, versionId) {
|
|
|
94
82
|
}
|
|
95
83
|
// Helper function to decompress payload
|
|
96
84
|
async function decompressPayload(compressed, compressionType) {
|
|
97
|
-
const zlib = (
|
|
98
|
-
try {
|
|
99
|
-
return require('zlib');
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
})();
|
|
105
|
-
if (!zlib) {
|
|
106
|
-
throw new Error('zlib is required for decompression');
|
|
107
|
-
}
|
|
85
|
+
const zlib = require('zlib');
|
|
108
86
|
return new Promise((resolve, reject) => {
|
|
109
87
|
const buffer = Buffer.from(compressed);
|
|
110
88
|
const callback = (err, result) => {
|
|
@@ -126,6 +104,11 @@ async function decompressPayload(compressed, compressionType) {
|
|
|
126
104
|
}
|
|
127
105
|
});
|
|
128
106
|
}
|
|
107
|
+
const getWorkspaceId = () => process.env.UNNBOUND_WORKFLOW_URL.replace(/^https?:\/\//, '').replace(/\.unnbound\.ai$/, '');
|
|
108
|
+
const getEnvironment = () => process.env.UNNBOUND_WORKFLOW_URL.includes('stg.unnbound.ai') ? 'stg' : 'prod';
|
|
109
|
+
const getWorkflowDomain = () => {
|
|
110
|
+
return getEnvironment() === 'stg' ? 'workflow.stg.unnbound.ai' : 'workflow.unnbound.ai';
|
|
111
|
+
};
|
|
129
112
|
function createEventsClient(ignoreTraceRoutes = exports.defaultIgnoreTraceRoutes) {
|
|
130
113
|
const routes = [];
|
|
131
114
|
const middlewares = [];
|
|
@@ -194,6 +177,7 @@ function createEventsClient(ignoreTraceRoutes = exports.defaultIgnoreTraceRoutes
|
|
|
194
177
|
});
|
|
195
178
|
};
|
|
196
179
|
middlewares.push({ fn: requestLoggerMiddleware });
|
|
180
|
+
const enqueueClient = (0, unnbound_logger_sdk_1.traceAxios)(axios_1.default.create({ baseURL: `https://${getWorkflowDomain()}/async/${getWorkspaceId()}` }), { getPayload: internal_1.internal });
|
|
197
181
|
return {
|
|
198
182
|
on(method, matcher, handler) {
|
|
199
183
|
routes.push({ method, matcher, handler });
|
|
@@ -353,7 +337,8 @@ function createEventsClient(ignoreTraceRoutes = exports.defaultIgnoreTraceRoutes
|
|
|
353
337
|
try {
|
|
354
338
|
const parsed = JSON.parse(recordBody);
|
|
355
339
|
// Detect S3 pointer format: ["software.amazon.payloadoffloading.PayloadS3Pointer", {...}]
|
|
356
|
-
if (Array.isArray(parsed) &&
|
|
340
|
+
if (Array.isArray(parsed) &&
|
|
341
|
+
parsed.length === 2 &&
|
|
357
342
|
parsed[0] === 'software.amazon.payloadoffloading.PayloadS3Pointer') {
|
|
358
343
|
const pointer = parsed[1];
|
|
359
344
|
if (!s3Client) {
|
|
@@ -373,7 +358,8 @@ function createEventsClient(ignoreTraceRoutes = exports.defaultIgnoreTraceRoutes
|
|
|
373
358
|
throw error;
|
|
374
359
|
}
|
|
375
360
|
// Check if envelope payload is stored in S3
|
|
376
|
-
if (envelope.payload.type === 's3-single' ||
|
|
361
|
+
if (envelope.payload.type === 's3-single' ||
|
|
362
|
+
envelope.payload.type === 's3-multipart') {
|
|
377
363
|
if (!envelope.payload.location) {
|
|
378
364
|
throw new Error(`Payload type is ${envelope.payload.type} but no location specified`);
|
|
379
365
|
}
|
|
@@ -383,7 +369,7 @@ function createEventsClient(ignoreTraceRoutes = exports.defaultIgnoreTraceRoutes
|
|
|
383
369
|
unnbound_logger_sdk_1.logger.info({
|
|
384
370
|
type: envelope.payload.type,
|
|
385
371
|
bucket: envelope.payload.location.bucket,
|
|
386
|
-
key: envelope.payload.location.key
|
|
372
|
+
key: envelope.payload.location.key,
|
|
387
373
|
}, 'Fetching S3 envelope payload');
|
|
388
374
|
let payloadData = await fetchS3Payload(s3Client, envelope.payload.location.bucket, envelope.payload.location.key, envelope.payload.location.versionId);
|
|
389
375
|
// Handle decompression if needed
|
|
@@ -487,24 +473,14 @@ function createEventsClient(ignoreTraceRoutes = exports.defaultIgnoreTraceRoutes
|
|
|
487
473
|
}
|
|
488
474
|
}
|
|
489
475
|
}
|
|
490
|
-
const awsSqs = (
|
|
491
|
-
try {
|
|
492
|
-
return require('@aws-sdk/client-sqs');
|
|
493
|
-
}
|
|
494
|
-
catch {
|
|
495
|
-
return null;
|
|
496
|
-
}
|
|
497
|
-
})();
|
|
498
|
-
if (!awsSqs) {
|
|
499
|
-
throw new Error('@aws-sdk/client-sqs is required to use sqsListen');
|
|
500
|
-
}
|
|
476
|
+
const awsSqs = require('@aws-sdk/client-sqs');
|
|
501
477
|
const queueUrl = options?.queueUrl || env.UNNBOUND_SQS_QUEUE_URL || env.SQS_QUEUE_URL || env.QUEUE_URL;
|
|
502
478
|
if (!queueUrl || queueUrl === undefined) {
|
|
503
479
|
throw new Error('SQS queue URL not provided. Set UNNBOUND_SQS_QUEUE_URL, SQS_QUEUE_URL env or pass options.queueUrl');
|
|
504
480
|
}
|
|
505
481
|
const region = options?.region || env.AWS_REGION || 'us-east-1';
|
|
506
482
|
const endpoint = env.AWS_SQS_ENDPOINT || env.AWS_ENDPOINT_URL || undefined;
|
|
507
|
-
const waitTimeSeconds = options?.waitTimeSeconds ??
|
|
483
|
+
const waitTimeSeconds = options?.waitTimeSeconds ?? 20;
|
|
508
484
|
const maxMessages = options?.maxMessages ?? 10;
|
|
509
485
|
const visibilityTimeout = options?.visibilityTimeoutSeconds;
|
|
510
486
|
const sqs = new awsSqs.SQSClient({ region, endpoint });
|
|
@@ -552,12 +528,12 @@ function createEventsClient(ignoreTraceRoutes = exports.defaultIgnoreTraceRoutes
|
|
|
552
528
|
return new Promise((resolve, reject) => {
|
|
553
529
|
const testConnection = async () => {
|
|
554
530
|
try {
|
|
555
|
-
const
|
|
531
|
+
const command = new awsSqs.ReceiveMessageCommand({
|
|
556
532
|
QueueUrl: queueUrl,
|
|
557
533
|
MaxNumberOfMessages: 1,
|
|
558
|
-
WaitTimeSeconds: 0,
|
|
559
|
-
};
|
|
560
|
-
await sqs.send(
|
|
534
|
+
WaitTimeSeconds: 0,
|
|
535
|
+
});
|
|
536
|
+
await sqs.send(command);
|
|
561
537
|
resolve({
|
|
562
538
|
stop() {
|
|
563
539
|
stopped = true;
|
|
@@ -574,14 +550,35 @@ function createEventsClient(ignoreTraceRoutes = exports.defaultIgnoreTraceRoutes
|
|
|
574
550
|
setTimeout(testConnection, 100);
|
|
575
551
|
});
|
|
576
552
|
},
|
|
553
|
+
async enqueue(event) {
|
|
554
|
+
if (!event.path.startsWith('/'))
|
|
555
|
+
throw new Error('Path must be relative.');
|
|
556
|
+
const config = {
|
|
557
|
+
headers: {
|
|
558
|
+
...event.headers,
|
|
559
|
+
[TRACE_HEADER_KEY]: event.metadata?.traceId,
|
|
560
|
+
[MESSAGE_HEADER_KEY]: event.metadata?.messageId,
|
|
561
|
+
},
|
|
562
|
+
params: event.query,
|
|
563
|
+
};
|
|
564
|
+
const method = event.method.toLowerCase();
|
|
565
|
+
switch (method) {
|
|
566
|
+
case 'get':
|
|
567
|
+
case 'delete':
|
|
568
|
+
return await enqueueClient[method](event.path, config).then(() => void 0);
|
|
569
|
+
case 'post':
|
|
570
|
+
case 'put':
|
|
571
|
+
case 'patch':
|
|
572
|
+
return await enqueueClient[method](event.path, event.body, config).then(() => void 0);
|
|
573
|
+
}
|
|
574
|
+
throw new Error(`Unsupported method: ${event.method}`);
|
|
575
|
+
},
|
|
577
576
|
async start(options) {
|
|
578
577
|
// Default to starting both HTTP and SQS if no options provided
|
|
579
578
|
const httpOptions = options?.http ?? { port: 3000 };
|
|
580
579
|
const sqsOptions = options?.sqs ?? {};
|
|
581
580
|
// Pass S3 options through to SQS listener if provided
|
|
582
|
-
const sqsListenOptions = options?.s3
|
|
583
|
-
? { ...sqsOptions, s3: options.s3 }
|
|
584
|
-
: sqsOptions;
|
|
581
|
+
const sqsListenOptions = options?.s3 ? { ...sqsOptions, s3: options.s3 } : sqsOptions;
|
|
585
582
|
// Start HTTP server
|
|
586
583
|
let httpServer;
|
|
587
584
|
try {
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { Axios
|
|
1
|
+
import { Axios } from 'axios';
|
|
2
2
|
import { HttpOptions } from './types';
|
|
3
|
-
type GetPayload = (config: AxiosRequestConfig, res?: AxiosResponse) => object;
|
|
4
3
|
/**
|
|
5
4
|
* Wraps an axios instance to add tracing and span tracking
|
|
6
5
|
* @param axios - The axios instance to wrap
|
|
7
6
|
* @param options - Configuration options for HTTP tracing
|
|
8
7
|
* @returns The wrapped axios instance with span tracking
|
|
9
8
|
*/
|
|
10
|
-
export declare const traceAxios: (client: Axios, { ignoreTraceRoutes, traceHeaderKey,
|
|
11
|
-
export {};
|
|
9
|
+
export declare const traceAxios: (client: Axios, { ignoreTraceRoutes, traceHeaderKey, }?: HttpOptions) => Axios;
|
package/dist/logger/src/axios.js
CHANGED
|
@@ -25,17 +25,15 @@ const buildOutgoingHttpPayload = (config, res) => ({
|
|
|
25
25
|
: undefined,
|
|
26
26
|
},
|
|
27
27
|
});
|
|
28
|
-
const getNoopPayload = () => ({});
|
|
29
28
|
/**
|
|
30
29
|
* Wraps an axios instance to add tracing and span tracking
|
|
31
30
|
* @param axios - The axios instance to wrap
|
|
32
31
|
* @param options - Configuration options for HTTP tracing
|
|
33
32
|
* @returns The wrapped axios instance with span tracking
|
|
34
33
|
*/
|
|
35
|
-
const traceAxios = (client, { ignoreTraceRoutes = types_1.defaultIgnoreTraceRoutes, traceHeaderKey = types_1.defaultTraceHeaderKey,
|
|
34
|
+
const traceAxios = (client, { ignoreTraceRoutes = types_1.defaultIgnoreTraceRoutes, traceHeaderKey = types_1.defaultTraceHeaderKey, } = {
|
|
36
35
|
ignoreTraceRoutes: types_1.defaultIgnoreTraceRoutes,
|
|
37
36
|
traceHeaderKey: types_1.defaultTraceHeaderKey,
|
|
38
|
-
getPayload: getNoopPayload,
|
|
39
37
|
}) => {
|
|
40
38
|
const createSpanWrappedRequest = (originalMethod, method) => {
|
|
41
39
|
const { headers: defaultHeaders, ...partialDefaultConfig } = client.defaults;
|
|
@@ -52,14 +50,11 @@ const traceAxios = (client, { ignoreTraceRoutes = types_1.defaultIgnoreTraceRout
|
|
|
52
50
|
config = { ...config, headers: { ...config.headers, [traceHeaderKey]: traceId } };
|
|
53
51
|
// Execute the request within a span
|
|
54
52
|
return (0, span_1.startSpan)(`Outgoing HTTP request`, () => originalMethod(config), (options) => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
: options.result
|
|
61
|
-
: undefined;
|
|
62
|
-
return { ...buildOutgoingHttpPayload(config, response), ...getPayload(config, response) };
|
|
53
|
+
if (!options)
|
|
54
|
+
return buildOutgoingHttpPayload(config);
|
|
55
|
+
if (options.error)
|
|
56
|
+
return buildOutgoingHttpPayload(config, (0, axios_1.isAxiosError)(options.error) ? options.error.response : undefined);
|
|
57
|
+
return buildOutgoingHttpPayload(config, options.result);
|
|
63
58
|
});
|
|
64
59
|
};
|
|
65
60
|
};
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RequestHandler } from 'express';
|
|
2
2
|
import { HttpOptions } from './types';
|
|
3
|
-
|
|
4
|
-
status: number;
|
|
5
|
-
headers?: Record<string, string | undefined>;
|
|
6
|
-
body?: unknown;
|
|
7
|
-
}
|
|
8
|
-
type GetPayload = (config: Request, res?: Response) => object;
|
|
9
|
-
export declare const traceMiddleware: ({ ignoreTraceRoutes, traceHeaderKey, getPayload, }?: HttpOptions<GetPayload>) => RequestHandler;
|
|
10
|
-
export {};
|
|
3
|
+
export declare const traceMiddleware: ({ ignoreTraceRoutes, traceHeaderKey, }?: HttpOptions) => RequestHandler;
|
|
@@ -19,7 +19,7 @@ const getFullUrl = (req) => {
|
|
|
19
19
|
const host = req.get('host') || req.get('x-forwarded-host') || 'localhost';
|
|
20
20
|
return `${protocol}://${host}${url}`;
|
|
21
21
|
};
|
|
22
|
-
const buildIncomingHttpPayload = (req, res) => ({
|
|
22
|
+
const buildIncomingHttpPayload = (req, res, body) => ({
|
|
23
23
|
type: 'http',
|
|
24
24
|
http: {
|
|
25
25
|
url: getFullUrl(req),
|
|
@@ -32,18 +32,16 @@ const buildIncomingHttpPayload = (req, res) => ({
|
|
|
32
32
|
},
|
|
33
33
|
response: res
|
|
34
34
|
? {
|
|
35
|
-
headers: res
|
|
36
|
-
status: res.
|
|
37
|
-
body: (0, utils_1.safeJsonParse)(
|
|
35
|
+
headers: res?.headers,
|
|
36
|
+
status: res.statusCode,
|
|
37
|
+
body: (0, utils_1.safeJsonParse)(body),
|
|
38
38
|
}
|
|
39
39
|
: undefined,
|
|
40
40
|
},
|
|
41
41
|
});
|
|
42
|
-
const
|
|
43
|
-
const traceMiddleware = ({ ignoreTraceRoutes = types_1.defaultIgnoreTraceRoutes, traceHeaderKey = types_1.defaultTraceHeaderKey, getPayload = getNoopPayload, } = {
|
|
42
|
+
const traceMiddleware = ({ ignoreTraceRoutes = types_1.defaultIgnoreTraceRoutes, traceHeaderKey = types_1.defaultTraceHeaderKey, } = {
|
|
44
43
|
ignoreTraceRoutes: types_1.defaultIgnoreTraceRoutes,
|
|
45
44
|
traceHeaderKey: types_1.defaultTraceHeaderKey,
|
|
46
|
-
getPayload: getNoopPayload,
|
|
47
45
|
}) => async (req, res, next) => {
|
|
48
46
|
if ((0, utils_1.shouldIgnorePath)(req.path, ignoreTraceRoutes))
|
|
49
47
|
return next();
|
|
@@ -62,14 +60,12 @@ const traceMiddleware = ({ ignoreTraceRoutes = types_1.defaultIgnoreTraceRoutes,
|
|
|
62
60
|
return next();
|
|
63
61
|
});
|
|
64
62
|
}, (o) => {
|
|
65
|
-
|
|
63
|
+
return buildIncomingHttpPayload(req, o
|
|
66
64
|
? {
|
|
67
|
-
|
|
65
|
+
statusCode: res.statusCode,
|
|
68
66
|
headers: res.getHeaders(),
|
|
69
|
-
body: res.locals.body,
|
|
70
67
|
}
|
|
71
|
-
: undefined;
|
|
72
|
-
return { ...buildIncomingHttpPayload(req, response), ...getPayload(req, response) };
|
|
68
|
+
: undefined, res.locals.body);
|
|
73
69
|
});
|
|
74
70
|
}, { traceId });
|
|
75
71
|
};
|
|
@@ -53,10 +53,9 @@ export interface SftpPayload {
|
|
|
53
53
|
content?: string;
|
|
54
54
|
exists?: string | false;
|
|
55
55
|
}
|
|
56
|
-
export interface HttpOptions
|
|
56
|
+
export interface HttpOptions {
|
|
57
57
|
ignoreTraceRoutes?: string[];
|
|
58
58
|
traceHeaderKey?: string;
|
|
59
|
-
getPayload?: G;
|
|
60
59
|
}
|
|
61
60
|
export declare const defaultIgnoreTraceRoutes: string[];
|
|
62
61
|
export declare const defaultTraceHeaderKey = "x-unnbound-trace-id";
|
package/package.json
CHANGED
|
@@ -1,25 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unnbound-events",
|
|
3
3
|
"description": "Unified events SDK to handle HTTP routes and SQS messages with a single routing API.",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.20",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"test": "jest",
|
|
10
|
-
"test:coverage": "jest --coverage",
|
|
11
|
-
"test:watch": "jest --watch",
|
|
12
|
-
"typecheck": "tsc -noEmit",
|
|
13
|
-
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .",
|
|
14
|
-
"lint:fix": "pnpm lint --fix",
|
|
15
|
-
"format": "prettier --write .",
|
|
16
|
-
"format:check": "prettier --check .",
|
|
17
|
-
"prepublishOnly": "pnpm build",
|
|
18
|
-
"start:example:express": "npx --yes tsx --watch examples/express.ts",
|
|
19
|
-
"start:example:sqs": "npx --yes tsx --watch examples/sqs.ts",
|
|
20
|
-
"start:example:http-and-sqs": "npx --yes tsx --watch examples/http-and-sqs.ts",
|
|
21
|
-
"version:bump": "npm version patch"
|
|
22
|
-
},
|
|
23
7
|
"author": "Unnbound Team",
|
|
24
8
|
"license": "MIT",
|
|
25
9
|
"repository": {
|
|
@@ -31,20 +15,18 @@
|
|
|
31
15
|
"url": "https://github.com/unnbounddev/unnbound-sdks/issues"
|
|
32
16
|
},
|
|
33
17
|
"dependencies": {
|
|
34
|
-
"
|
|
18
|
+
"@aws-sdk/client-s3": "^3.0.0",
|
|
35
19
|
"@aws-sdk/client-sqs": "^3.0.0",
|
|
36
|
-
"
|
|
20
|
+
"axios": "^1.12.2",
|
|
21
|
+
"express": "^4.0.0 || ^5.0.0",
|
|
22
|
+
"unnbound-logger-sdk": "3.0.22"
|
|
37
23
|
},
|
|
38
24
|
"peerDependencies": {
|
|
39
|
-
"express": "^4.0.0 || ^5.0.0"
|
|
40
|
-
"@aws-sdk/client-s3": "^3.0.0"
|
|
25
|
+
"express": "^4.0.0 || ^5.0.0"
|
|
41
26
|
},
|
|
42
27
|
"peerDependenciesMeta": {
|
|
43
28
|
"express": {
|
|
44
29
|
"optional": true
|
|
45
|
-
},
|
|
46
|
-
"@aws-sdk/client-s3": {
|
|
47
|
-
"optional": true
|
|
48
30
|
}
|
|
49
31
|
},
|
|
50
32
|
"devDependencies": {
|
|
@@ -60,5 +42,20 @@
|
|
|
60
42
|
"engines": {
|
|
61
43
|
"node": ">=22"
|
|
62
44
|
},
|
|
63
|
-
"sideEffects": false
|
|
64
|
-
|
|
45
|
+
"sideEffects": false,
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsc",
|
|
48
|
+
"test": "jest",
|
|
49
|
+
"test:coverage": "jest --coverage",
|
|
50
|
+
"test:watch": "jest --watch",
|
|
51
|
+
"typecheck": "tsc -noEmit",
|
|
52
|
+
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .",
|
|
53
|
+
"lint:fix": "pnpm lint --fix",
|
|
54
|
+
"format": "prettier --write .",
|
|
55
|
+
"format:check": "prettier --check .",
|
|
56
|
+
"start:example:express": "npx --yes tsx --watch examples/express.ts",
|
|
57
|
+
"start:example:sqs": "npx --yes tsx --watch examples/sqs.ts",
|
|
58
|
+
"start:example:http-and-sqs": "npx --yes tsx --watch examples/http-and-sqs.ts",
|
|
59
|
+
"version:bump": "npm version patch"
|
|
60
|
+
}
|
|
61
|
+
}
|