unnbound-events 1.0.22 → 2.0.2
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 +105 -272
- package/dist/index.d.ts +3 -4
- package/dist/index.js +3 -7
- package/dist/lib/adapters/adapter.d.ts +40 -0
- package/dist/lib/adapters/adapter.js +70 -0
- package/dist/lib/adapters/express.d.ts +2 -1
- package/dist/lib/adapters/express.js +1 -0
- package/dist/lib/adapters/extended-sqs-client.d.ts +20 -0
- package/dist/lib/adapters/extended-sqs-client.js +42 -0
- package/dist/lib/adapters/http-adapter.d.ts +14 -0
- package/dist/lib/adapters/http-adapter.js +38 -0
- package/dist/lib/adapters/queue-adapter.d.ts +37 -0
- package/dist/lib/adapters/queue-adapter.js +130 -0
- package/dist/lib/adapters/sqs.d.ts +3 -10
- package/dist/lib/adapters/sqs.js +1 -0
- package/dist/lib/client.d.ts +41 -4
- package/dist/lib/client.js +137 -606
- package/dist/lib/enqueue.d.ts +2 -0
- package/dist/lib/enqueue.js +44 -0
- package/dist/lib/error.d.ts +15 -0
- package/dist/lib/error.js +28 -0
- package/dist/lib/routing/endpoint.d.ts +17 -0
- package/dist/lib/routing/endpoint.js +43 -0
- package/dist/lib/routing/middleware.d.ts +17 -0
- package/dist/lib/routing/middleware.js +20 -0
- package/dist/lib/routing/types.d.ts +8 -0
- package/dist/lib/routing/types.js +2 -0
- package/dist/lib/server.d.ts +28 -0
- package/dist/lib/server.js +97 -0
- package/dist/lib/types.d.ts +21 -82
- package/dist/lib/utils.d.ts +33 -0
- package/dist/lib/utils.js +101 -0
- package/package.json +6 -16
package/README.md
CHANGED
|
@@ -1,340 +1,173 @@
|
|
|
1
1
|
# Unnbound Events SDK
|
|
2
2
|
|
|
3
|
-
Unified
|
|
3
|
+
Unified HTTP and SQS queue routing with a single API. Register handlers once, handle requests from both transports.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
pnpm add unnbound-events
|
|
8
|
+
pnpm add unnbound-events
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
### Simple Start (Recommended)
|
|
14
|
-
|
|
15
|
-
Register handlers and start with automatic graceful shutdown:
|
|
11
|
+
## Quick Start
|
|
16
12
|
|
|
17
13
|
```ts
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
const client = createEventsClient();
|
|
21
|
-
|
|
22
|
-
// Register handlers
|
|
23
|
-
client.post('/email', async (req) => ({ status: 200, body: { ok: true } }));
|
|
24
|
-
client.get('/health', async () => ({ status: 200, body: { status: 'ok' } }));
|
|
25
|
-
|
|
26
|
-
// Start with defaults (HTTP on port 3000, SQS from environment variables)
|
|
27
|
-
await client.start();
|
|
28
|
-
```
|
|
14
|
+
import { createServer } from 'unnbound-events';
|
|
29
15
|
|
|
30
|
-
|
|
16
|
+
const server = createServer();
|
|
31
17
|
|
|
32
|
-
|
|
18
|
+
// Register route handlers
|
|
19
|
+
server.post('/email/:userId', async (c) => {
|
|
20
|
+
const { userId } = c.request.params;
|
|
21
|
+
const body = c.request.body;
|
|
33
22
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
// HTTP only
|
|
38
|
-
await client.start({ http: { port: 8080 } });
|
|
39
|
-
|
|
40
|
-
// SQS only
|
|
41
|
-
await client.start({
|
|
42
|
-
sqs: { queueUrl: 'https://sqs.us-west-2.amazonaws.com/123456789012/my-queue' },
|
|
23
|
+
return { status: 200, body: { sent: true } };
|
|
43
24
|
});
|
|
44
25
|
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
http: { port: 8080 },
|
|
48
|
-
sqs: {
|
|
49
|
-
queueUrl: 'https://sqs.us-west-2.amazonaws.com/123456789012/my-queue',
|
|
50
|
-
region: 'us-west-2',
|
|
51
|
-
},
|
|
52
|
-
});
|
|
26
|
+
// Start both HTTP and queue listeners
|
|
27
|
+
server.start();
|
|
53
28
|
```
|
|
54
29
|
|
|
55
|
-
|
|
30
|
+
The server automatically:
|
|
56
31
|
|
|
57
|
-
|
|
32
|
+
- Starts HTTP server on `PORT` (default: `3000`)
|
|
33
|
+
- Starts SQS long-polling if `UNNBOUND_SQS_URL` is set
|
|
34
|
+
- Provides `/healthcheck` endpoint (returns `200 { status: 'ok' }`)
|
|
35
|
+
- Handles graceful shutdown on `SIGINT`/`SIGTERM`
|
|
58
36
|
|
|
59
|
-
|
|
60
|
-
import { createEventsClient } from 'unnbound-events-sdk';
|
|
61
|
-
|
|
62
|
-
const client = createEventsClient();
|
|
63
|
-
client.post('/email', async (req) => ({ status: 200, body: { ok: true } }));
|
|
64
|
-
|
|
65
|
-
// Built-in HTTP server
|
|
66
|
-
await client.http({ port: 3000 });
|
|
37
|
+
## Handlers
|
|
67
38
|
|
|
68
|
-
|
|
69
|
-
export const handler = client.sqs();
|
|
70
|
-
|
|
71
|
-
// SQS long-poll listener (env-driven)
|
|
72
|
-
// Env: UNNBOUND_SQS_QUEUE_URL (or SQS_QUEUE_URL), AWS_REGION, AWS_SQS_ENDPOINT (optional)
|
|
73
|
-
await client.sqsListen();
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## Middleware
|
|
77
|
-
|
|
78
|
-
Add cross-cutting logic that runs before/after your handlers. Works for both HTTP and SQS.
|
|
39
|
+
Routes receive a context object with request details:
|
|
79
40
|
|
|
80
41
|
```ts
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
42
|
+
server.post('/users/:id', async (c) => {
|
|
43
|
+
// Access request data
|
|
44
|
+
const { id } = c.request.params;
|
|
45
|
+
const query = c.request.query;
|
|
46
|
+
const headers = c.request.headers;
|
|
47
|
+
const body = c.request.body;
|
|
48
|
+
|
|
49
|
+
// Return response
|
|
89
50
|
return {
|
|
90
|
-
|
|
91
|
-
|
|
51
|
+
status: 200,
|
|
52
|
+
body: { id, ...body },
|
|
53
|
+
headers: { 'x-custom': 'value' },
|
|
92
54
|
};
|
|
93
55
|
});
|
|
94
|
-
|
|
95
|
-
// Path-scoped middleware (string | RegExp | (path)=>boolean)
|
|
96
|
-
client.use(/^\/admin\//, async (req, next) => {
|
|
97
|
-
const token = req.headers['authorization'] || req.headers['Authorization'];
|
|
98
|
-
if (!token || !String(token).startsWith('Bearer ')) {
|
|
99
|
-
return { status: 401, body: { message: 'Unauthorized' } };
|
|
100
|
-
}
|
|
101
|
-
return next(req);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
client.get('/health', async () => ({ status: 200, body: { status: 'ok' } }));
|
|
105
|
-
client.post('/echo', async (req) => ({ status: 200, body: req.body }));
|
|
106
|
-
|
|
107
|
-
await client.start();
|
|
108
56
|
```
|
|
109
57
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
```ts
|
|
113
|
-
const client = createEventsClient();
|
|
114
|
-
|
|
115
|
-
client.use(async (req, next) => {
|
|
116
|
-
const rid = String(req.headers['x-request-id'] ?? crypto.randomUUID());
|
|
117
|
-
return next({
|
|
118
|
-
...req,
|
|
119
|
-
headers: { ...req.headers, 'x-request-id': rid },
|
|
120
|
-
metadata: { ...(req.metadata ?? {}), requestId: rid },
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
client.post('/jobs/process', async (req) => ({ status: 200, body: { ok: true } }));
|
|
125
|
-
|
|
126
|
-
await client.sqsListen();
|
|
127
|
-
```
|
|
58
|
+
**Response format:** `{ status?: number, body?: object, headers?: Record<string, string> }`
|
|
128
59
|
|
|
129
|
-
|
|
60
|
+
Return `undefined`, `null`, or omit properties to default to `204 No Content`.
|
|
130
61
|
|
|
131
|
-
|
|
132
|
-
- `use(matcher, middleware)` where matcher is `string | RegExp | (path) => boolean`
|
|
133
|
-
- `middleware(req, next) -> Promise<EventResponse>`
|
|
134
|
-
|
|
135
|
-
## Enqueue Events
|
|
136
|
-
|
|
137
|
-
The `enqueue` function allows you to send events to the queue by using the async endpoint.
|
|
62
|
+
## Middleware
|
|
138
63
|
|
|
139
|
-
|
|
64
|
+
Add logic that runs before/after handlers. Works for both HTTP and queue requests.
|
|
140
65
|
|
|
141
66
|
```ts
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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 } };
|
|
67
|
+
// Global middleware
|
|
68
|
+
server.use(async (c, next) => {
|
|
69
|
+
console.log('Request:', c.request.method, c.request.path);
|
|
70
|
+
return next();
|
|
161
71
|
});
|
|
162
72
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
73
|
+
// Path-based middleware
|
|
74
|
+
server.use('/admin/*', async (c, next) => {
|
|
75
|
+
const token = c.request.headers['authorization'];
|
|
76
|
+
if (!token?.startsWith('Bearer ')) {
|
|
77
|
+
return { status: 401, body: { error: 'Unauthorized' } };
|
|
78
|
+
}
|
|
79
|
+
return next();
|
|
167
80
|
});
|
|
168
81
|
|
|
169
|
-
|
|
82
|
+
// Handler-specific middleware
|
|
83
|
+
server.post(
|
|
84
|
+
'/email',
|
|
85
|
+
async (c, next) => {
|
|
86
|
+
// Runs only for this handler
|
|
87
|
+
return next();
|
|
88
|
+
},
|
|
89
|
+
async (c) => {
|
|
90
|
+
return { status: 200, body: { ok: true } };
|
|
91
|
+
}
|
|
92
|
+
);
|
|
170
93
|
```
|
|
171
94
|
|
|
172
|
-
|
|
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
|
-
```
|
|
95
|
+
## Configuration
|
|
184
96
|
|
|
185
|
-
|
|
97
|
+
### HTTP Port
|
|
186
98
|
|
|
187
|
-
|
|
99
|
+
Set via `PORT` environment variable (default: `3000`):
|
|
188
100
|
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
id: string;
|
|
192
|
-
timestamp: number;
|
|
193
|
-
version: string;
|
|
194
|
-
workflow: string;
|
|
195
|
-
priority: number;
|
|
196
|
-
payload: { type: string; size: number; compressed: boolean };
|
|
197
|
-
request: {
|
|
198
|
-
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
|
|
199
|
-
path: string;
|
|
200
|
-
headers: Record<string, string>;
|
|
201
|
-
query: Record<string, unknown>;
|
|
202
|
-
body: unknown;
|
|
203
|
-
};
|
|
204
|
-
metadata?: Record<string, unknown>;
|
|
205
|
-
addons?: Record<string, unknown>;
|
|
206
|
-
}
|
|
101
|
+
```bash
|
|
102
|
+
export PORT=8080
|
|
207
103
|
```
|
|
208
104
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
The SDK supports automatic handling of large payloads (>256KB) stored in S3, which is necessary since SQS has a 256KB message size limit.
|
|
105
|
+
### Queue Options
|
|
212
106
|
|
|
213
|
-
|
|
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
|
|
224
|
-
|
|
225
|
-
Configure S3 support when starting the client:
|
|
107
|
+
Configure SQS polling behavior:
|
|
226
108
|
|
|
227
109
|
```ts
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
region: 'us-west-2',
|
|
110
|
+
const server = createServer({
|
|
111
|
+
queue: {
|
|
112
|
+
maxMessages: 10, // Messages per poll (default: 10)
|
|
113
|
+
visibilityTimeout: 30, // Seconds (default: SQS default)
|
|
233
114
|
},
|
|
234
115
|
});
|
|
235
116
|
```
|
|
236
117
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
```ts
|
|
240
|
-
await client.sqsListen({
|
|
241
|
-
queueUrl: 'https://sqs.us-west-2.amazonaws.com/123456789012/my-queue',
|
|
242
|
-
s3: {
|
|
243
|
-
bucketName: 'my-payload-bucket',
|
|
244
|
-
region: 'us-west-2',
|
|
245
|
-
},
|
|
246
|
-
});
|
|
247
|
-
```
|
|
118
|
+
### SQS Environment Variables
|
|
248
119
|
|
|
249
|
-
|
|
120
|
+
The queue adapter reads from:
|
|
250
121
|
|
|
251
|
-
|
|
122
|
+
- `UNNBOUND_SQS_URL` - Queue URL (required for queue listener)
|
|
123
|
+
- `UNNBOUND_AWS_REGION` - AWS region (default: `us-west-1`)
|
|
124
|
+
- `UNNBOUND_SQS_ENDPOINT` - Custom endpoint (optional)
|
|
252
125
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
"software.amazon.payloadoffloading.PayloadS3Pointer",
|
|
258
|
-
{
|
|
259
|
-
"s3BucketName": "bucket-name",
|
|
260
|
-
"s3Key": "key-path"
|
|
261
|
-
}
|
|
262
|
-
]
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
2. **Envelope-based S3 Payloads** (Unnbound format):
|
|
266
|
-
|
|
267
|
-
```json
|
|
268
|
-
{
|
|
269
|
-
"payload": {
|
|
270
|
-
"type": "s3-single",
|
|
271
|
-
"size": 512000,
|
|
272
|
-
"compressed": true,
|
|
273
|
-
"compressionType": "gzip",
|
|
274
|
-
"location": {
|
|
275
|
-
"bucket": "bucket-name",
|
|
276
|
-
"key": "key-path",
|
|
277
|
-
"region": "us-west-2",
|
|
278
|
-
"versionId": "optional-version-id"
|
|
279
|
-
}
|
|
280
|
-
},
|
|
281
|
-
"request": { ... }
|
|
282
|
-
}
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
### Compression Support
|
|
286
|
-
|
|
287
|
-
The SDK automatically decompresses payloads with:
|
|
288
|
-
|
|
289
|
-
- `brotli` compression
|
|
290
|
-
- `gzip` compression
|
|
291
|
-
|
|
292
|
-
When the `compressed` flag is `true` and `compressionType` is specified, the SDK will automatically decompress the payload after fetching from S3.
|
|
293
|
-
|
|
294
|
-
## Environment Variables
|
|
295
|
-
|
|
296
|
-
### SQS Configuration
|
|
126
|
+
```bash
|
|
127
|
+
export UNNBOUND_SQS_URL="https://sqs.us-west-2.amazonaws.com/123/my-queue"
|
|
128
|
+
export UNNBOUND_AWS_REGION="us-west-2"
|
|
129
|
+
```
|
|
297
130
|
|
|
298
|
-
|
|
131
|
+
### S3 Large Payload Support
|
|
299
132
|
|
|
300
|
-
|
|
301
|
-
2. **Environment variables** (in order of preference):
|
|
302
|
-
- `UNNBOUND_SQS_QUEUE_URL` (custom Unnbound variable)
|
|
303
|
-
- `SQS_QUEUE_URL` (standard variable)
|
|
304
|
-
- `QUEUE_URL` (fallback)
|
|
133
|
+
For messages >1MB, payloads can be stored in S3. The SDK automatically fetches and processes them.
|
|
305
134
|
|
|
306
|
-
**
|
|
135
|
+
**Environment variables:**
|
|
307
136
|
|
|
308
|
-
- `
|
|
137
|
+
- `UNNBOUND_S3_ENDPOINT` - Custom S3 endpoint (optional)
|
|
309
138
|
|
|
310
|
-
|
|
139
|
+
The SDK supports AWS Extended Client Library format (S3 pointer messages).
|
|
311
140
|
|
|
312
|
-
|
|
313
|
-
- `AWS_SQS_ENDPOINT` - Custom SQS endpoint (for LocalStack, etc.)
|
|
141
|
+
## Queue Message Format
|
|
314
142
|
|
|
315
|
-
|
|
143
|
+
SQS message body should be JSON:
|
|
316
144
|
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"id": "unique-id",
|
|
148
|
+
"timestamp": 1234567890,
|
|
149
|
+
"request": {
|
|
150
|
+
"method": "POST",
|
|
151
|
+
"url": "https://example.com/path?query=value",
|
|
152
|
+
"headers": { "content-type": "application/json" },
|
|
153
|
+
"body": "base64-encoded-body"
|
|
154
|
+
},
|
|
155
|
+
"metadata": {}
|
|
156
|
+
}
|
|
320
157
|
```
|
|
321
158
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
For S3 payload support, configure:
|
|
325
|
-
|
|
326
|
-
**Required:**
|
|
159
|
+
The adapter extracts `method`, `path`, `headers`, `query`, and `body` to route the message through your handlers.
|
|
327
160
|
|
|
328
|
-
|
|
161
|
+
## Logger Integration
|
|
329
162
|
|
|
330
|
-
|
|
163
|
+
Pass a custom logger:
|
|
331
164
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
**Example:**
|
|
165
|
+
```ts
|
|
166
|
+
import { logger } from 'unnbound-logger-sdk';
|
|
336
167
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
168
|
+
const server = createServer({
|
|
169
|
+
logger,
|
|
170
|
+
});
|
|
340
171
|
```
|
|
172
|
+
|
|
173
|
+
The logger is used for internal events and traces.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export type {
|
|
3
|
-
export {
|
|
4
|
-
export { createSqsConsumer } from './lib/adapters/sqs';
|
|
1
|
+
export type { IncomingEvent, IncomingRequest, EventMetadata, EventSource } from './lib/types';
|
|
2
|
+
export type { EventServerOptions, EventServer } from './lib/server';
|
|
3
|
+
export { createServer } from './lib/server';
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
var
|
|
5
|
-
Object.defineProperty(exports, "
|
|
6
|
-
var express_1 = require("./lib/adapters/express");
|
|
7
|
-
Object.defineProperty(exports, "createExpressMiddleware", { enumerable: true, get: function () { return express_1.createExpressMiddleware; } });
|
|
8
|
-
var sqs_1 = require("./lib/adapters/sqs");
|
|
9
|
-
Object.defineProperty(exports, "createSqsConsumer", { enumerable: true, get: function () { return sqs_1.createSqsConsumer; } });
|
|
3
|
+
exports.createServer = void 0;
|
|
4
|
+
var server_1 = require("./lib/server");
|
|
5
|
+
Object.defineProperty(exports, "createServer", { enumerable: true, get: function () { return server_1.createServer; } });
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { UnnboundLogger } from 'unnbound-logger-sdk';
|
|
2
|
+
import { EventHandlerResult, EventMetadata, EventSource, IncomingEvent, IncomingRequest } from '../types';
|
|
3
|
+
export type EventHandler = (event: IncomingEvent) => Promise<EventHandlerResult>;
|
|
4
|
+
export interface EventAdapterOptions {
|
|
5
|
+
logger?: UnnboundLogger;
|
|
6
|
+
source: EventSource;
|
|
7
|
+
handler: EventHandler;
|
|
8
|
+
}
|
|
9
|
+
export declare abstract class EventAdapter<E> {
|
|
10
|
+
protected readonly logger: UnnboundLogger;
|
|
11
|
+
protected readonly source: EventSource;
|
|
12
|
+
private readonly handler;
|
|
13
|
+
protected constructor(options: EventAdapterOptions);
|
|
14
|
+
protected abstract parseEvent(event: E): IncomingEvent;
|
|
15
|
+
abstract listen(): () => Promise<void>;
|
|
16
|
+
protected getMetadataFromRequest(request: IncomingRequest): EventMetadata;
|
|
17
|
+
protected normalizeHeaders(headers: Record<string, string | string[]>): Record<string, string>;
|
|
18
|
+
protected handle(event: IncomingEvent): Promise<EventHandlerResult>;
|
|
19
|
+
protected buildIncomingPayload(req: IncomingRequest, res?: EventHandlerResult): {
|
|
20
|
+
type: string;
|
|
21
|
+
source: EventSource;
|
|
22
|
+
http: {
|
|
23
|
+
url: string;
|
|
24
|
+
method: string;
|
|
25
|
+
query: Record<string, any>;
|
|
26
|
+
ip: string | undefined;
|
|
27
|
+
incoming: boolean;
|
|
28
|
+
request: {
|
|
29
|
+
headers: Record<string, string>;
|
|
30
|
+
body: any;
|
|
31
|
+
};
|
|
32
|
+
response: {
|
|
33
|
+
status: import("hono/utils/http-status").StatusCode;
|
|
34
|
+
headers: Record<string, string> | undefined;
|
|
35
|
+
body: any;
|
|
36
|
+
} | undefined;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
protected handleError(error: unknown): EventHandlerResult;
|
|
40
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EventAdapter = void 0;
|
|
4
|
+
const unnbound_logger_sdk_1 = require("unnbound-logger-sdk");
|
|
5
|
+
const trace_1 = require("unnbound-logger-sdk/dist/trace");
|
|
6
|
+
const types_1 = require("unnbound-logger-sdk/dist/types");
|
|
7
|
+
const utils_1 = require("unnbound-logger-sdk/dist/utils");
|
|
8
|
+
const error_1 = require("../error");
|
|
9
|
+
class EventAdapter {
|
|
10
|
+
logger;
|
|
11
|
+
source;
|
|
12
|
+
handler;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.logger = options.logger ?? unnbound_logger_sdk_1.logger;
|
|
15
|
+
this.source = options.source;
|
|
16
|
+
this.handler = options.handler;
|
|
17
|
+
}
|
|
18
|
+
getMetadataFromRequest(request) {
|
|
19
|
+
return {
|
|
20
|
+
traceId: request.headers[types_1.defaultTraceHeaderKey] ?? unnbound_logger_sdk_1.getTraceId,
|
|
21
|
+
messageId: request.headers[types_1.defaultMessageHeaderKey] ?? (0, trace_1.getMessageId)(),
|
|
22
|
+
source: this.source,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
normalizeHeaders(headers) {
|
|
26
|
+
return Object.fromEntries(Object.entries(headers).map(([key, value]) => [
|
|
27
|
+
key,
|
|
28
|
+
Array.isArray(value) ? value.join(',') : (value?.toString() ?? ''),
|
|
29
|
+
]));
|
|
30
|
+
}
|
|
31
|
+
async handle(event) {
|
|
32
|
+
return await (0, trace_1.withTrace)(() => this.handler(event), event.metadata);
|
|
33
|
+
}
|
|
34
|
+
buildIncomingPayload(req, res) {
|
|
35
|
+
return {
|
|
36
|
+
type: 'http',
|
|
37
|
+
source: this.source,
|
|
38
|
+
http: {
|
|
39
|
+
url: req.url,
|
|
40
|
+
method: req.method.toLowerCase(),
|
|
41
|
+
query: req.query,
|
|
42
|
+
ip: (0, utils_1.normalizeIp)(req.headers['cf-connecting-ip'] ??
|
|
43
|
+
req.headers['x-forwarded-for'] ??
|
|
44
|
+
req.headers['x-real-ip']),
|
|
45
|
+
incoming: true,
|
|
46
|
+
request: {
|
|
47
|
+
headers: req.headers,
|
|
48
|
+
body: (0, utils_1.safeJsonParse)(req.body),
|
|
49
|
+
},
|
|
50
|
+
response: res
|
|
51
|
+
? {
|
|
52
|
+
status: res.status ?? 204,
|
|
53
|
+
headers: res.headers ? this.normalizeHeaders(res.headers) : undefined,
|
|
54
|
+
body: res.body ? (0, utils_1.safeJsonParse)(res.body) : undefined,
|
|
55
|
+
}
|
|
56
|
+
: undefined,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
handleError(error) {
|
|
61
|
+
if (error instanceof error_1.HandlerError) {
|
|
62
|
+
return {
|
|
63
|
+
status: error.status,
|
|
64
|
+
body: error.toJson(),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return this.handleError(new error_1.HandlerError({ message: 'Internal server error', status: 500, cause: error }));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.EventAdapter = EventAdapter;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { EventsClient } from '../types';
|
|
1
2
|
type Request = {
|
|
3
|
+
url: string;
|
|
2
4
|
method: string;
|
|
3
5
|
path: string;
|
|
4
6
|
headers: Record<string, string | string[] | undefined>;
|
|
@@ -11,6 +13,5 @@ type Response = {
|
|
|
11
13
|
send: (body: unknown) => void;
|
|
12
14
|
end: () => void;
|
|
13
15
|
};
|
|
14
|
-
import type { EventsClient } from '../types';
|
|
15
16
|
export declare function createExpressMiddleware(client: EventsClient): (req: Request, res: Response) => Promise<void>;
|
|
16
17
|
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
import { ReceiveMessageCommand, ReceiveMessageCommandOutput, SQSClient } from '@aws-sdk/client-sqs';
|
|
3
|
+
export interface S3Pointer {
|
|
4
|
+
bucket: string;
|
|
5
|
+
key: string;
|
|
6
|
+
}
|
|
7
|
+
export type S3PointerMessage = ['software.amazon.payloadoffloading.PayloadS3Pointer', S3Pointer];
|
|
8
|
+
type SQSClientConfig = ConstructorParameters<typeof SQSClient>[0];
|
|
9
|
+
export type ExtendedSQSClientConfig = SQSClientConfig & {
|
|
10
|
+
s3: S3Client;
|
|
11
|
+
};
|
|
12
|
+
export declare class ExtendedSQSClient extends SQSClient {
|
|
13
|
+
private readonly s3;
|
|
14
|
+
private readonly S3_POINTER_CLASS;
|
|
15
|
+
constructor(config: ExtendedSQSClientConfig);
|
|
16
|
+
private isS3PointerMessage;
|
|
17
|
+
private retrieveMessage;
|
|
18
|
+
receive(command: ReceiveMessageCommand): Promise<ReceiveMessageCommandOutput>;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExtendedSQSClient = void 0;
|
|
4
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
5
|
+
const client_sqs_1 = require("@aws-sdk/client-sqs");
|
|
6
|
+
const unnbound_logger_sdk_1 = require("unnbound-logger-sdk");
|
|
7
|
+
const internal_1 = require("unnbound-logger-sdk/dist/internal");
|
|
8
|
+
class ExtendedSQSClient extends client_sqs_1.SQSClient {
|
|
9
|
+
s3;
|
|
10
|
+
S3_POINTER_CLASS = 'software.amazon.payloadoffloading.PayloadS3Pointer';
|
|
11
|
+
constructor(config) {
|
|
12
|
+
super(config);
|
|
13
|
+
this.s3 = config.s3;
|
|
14
|
+
}
|
|
15
|
+
isS3PointerMessage(body) {
|
|
16
|
+
return Array.isArray(body) && body.length === 2 && body[0] === this.S3_POINTER_CLASS;
|
|
17
|
+
}
|
|
18
|
+
async retrieveMessage(message) {
|
|
19
|
+
if (!message.Body)
|
|
20
|
+
return message;
|
|
21
|
+
if (!message.Body.startsWith(`["${this.S3_POINTER_CLASS}",`))
|
|
22
|
+
return message;
|
|
23
|
+
const parsed = JSON.parse(message.Body);
|
|
24
|
+
if (!this.isS3PointerMessage(parsed))
|
|
25
|
+
return message;
|
|
26
|
+
const { bucket, key } = parsed[1];
|
|
27
|
+
unnbound_logger_sdk_1.logger.debug({ bucket, key, ...(0, internal_1.internal)() }, 'Retrieving message from storage.');
|
|
28
|
+
const payload = await this.s3.send(new client_s3_1.GetObjectCommand({ Bucket: bucket, Key: key }));
|
|
29
|
+
const body = await payload.Body?.transformToString();
|
|
30
|
+
if (!body)
|
|
31
|
+
throw new Error('Failed to retrieve queue message from storage.');
|
|
32
|
+
return { ...message, Body: body };
|
|
33
|
+
}
|
|
34
|
+
async receive(command) {
|
|
35
|
+
const result = await this.send(command);
|
|
36
|
+
return {
|
|
37
|
+
...result,
|
|
38
|
+
Messages: await Promise.all(result.Messages?.map(this.retrieveMessage.bind(this)) ?? []),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.ExtendedSQSClient = ExtendedSQSClient;
|