serverless-event-orchestrator 1.0.1

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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +377 -0
  3. package/dist/dispatcher.d.ts +18 -0
  4. package/dist/dispatcher.d.ts.map +1 -0
  5. package/dist/dispatcher.js +345 -0
  6. package/dist/dispatcher.js.map +1 -0
  7. package/dist/http/body-parser.d.ts +27 -0
  8. package/dist/http/body-parser.d.ts.map +1 -0
  9. package/dist/http/body-parser.js +56 -0
  10. package/dist/http/body-parser.js.map +1 -0
  11. package/dist/http/cors.d.ts +32 -0
  12. package/dist/http/cors.d.ts.map +1 -0
  13. package/dist/http/cors.js +69 -0
  14. package/dist/http/cors.js.map +1 -0
  15. package/dist/http/index.d.ts +4 -0
  16. package/dist/http/index.d.ts.map +1 -0
  17. package/dist/http/index.js +20 -0
  18. package/dist/http/index.js.map +1 -0
  19. package/dist/http/response.d.ts +104 -0
  20. package/dist/http/response.d.ts.map +1 -0
  21. package/dist/http/response.js +164 -0
  22. package/dist/http/response.js.map +1 -0
  23. package/dist/identity/extractor.d.ts +39 -0
  24. package/dist/identity/extractor.d.ts.map +1 -0
  25. package/dist/identity/extractor.js +88 -0
  26. package/dist/identity/extractor.js.map +1 -0
  27. package/dist/identity/index.d.ts +2 -0
  28. package/dist/identity/index.d.ts.map +1 -0
  29. package/dist/identity/index.js +18 -0
  30. package/dist/identity/index.js.map +1 -0
  31. package/dist/index.d.ts +17 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +62 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/types/event-type.enum.d.ts +20 -0
  36. package/dist/types/event-type.enum.d.ts.map +1 -0
  37. package/dist/types/event-type.enum.js +25 -0
  38. package/dist/types/event-type.enum.js.map +1 -0
  39. package/dist/types/index.d.ts +3 -0
  40. package/dist/types/index.d.ts.map +1 -0
  41. package/dist/types/index.js +19 -0
  42. package/dist/types/index.js.map +1 -0
  43. package/dist/types/routes.d.ts +163 -0
  44. package/dist/types/routes.d.ts.map +1 -0
  45. package/dist/types/routes.js +3 -0
  46. package/dist/types/routes.js.map +1 -0
  47. package/dist/utils/headers.d.ts +28 -0
  48. package/dist/utils/headers.d.ts.map +1 -0
  49. package/dist/utils/headers.js +61 -0
  50. package/dist/utils/headers.js.map +1 -0
  51. package/dist/utils/index.d.ts +3 -0
  52. package/dist/utils/index.d.ts.map +1 -0
  53. package/dist/utils/index.js +19 -0
  54. package/dist/utils/index.js.map +1 -0
  55. package/dist/utils/path-matcher.d.ts +33 -0
  56. package/dist/utils/path-matcher.d.ts.map +1 -0
  57. package/dist/utils/path-matcher.js +74 -0
  58. package/dist/utils/path-matcher.js.map +1 -0
  59. package/jest.config.js +32 -0
  60. package/package.json +68 -0
  61. package/src/dispatcher.ts +415 -0
  62. package/src/http/body-parser.ts +60 -0
  63. package/src/http/cors.ts +76 -0
  64. package/src/http/index.ts +3 -0
  65. package/src/http/response.ts +194 -0
  66. package/src/identity/extractor.ts +89 -0
  67. package/src/identity/index.ts +1 -0
  68. package/src/index.ts +92 -0
  69. package/src/types/event-type.enum.ts +20 -0
  70. package/src/types/index.ts +2 -0
  71. package/src/types/routes.ts +182 -0
  72. package/src/utils/headers.ts +72 -0
  73. package/src/utils/index.ts +2 -0
  74. package/src/utils/path-matcher.ts +79 -0
  75. package/tests/cors.test.ts +133 -0
  76. package/tests/dispatcher.test.ts +425 -0
  77. package/tests/headers.test.ts +99 -0
  78. package/tests/identity.test.ts +171 -0
  79. package/tests/path-matcher.test.ts +102 -0
  80. package/tests/response.test.ts +155 -0
  81. package/tsconfig.json +24 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
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 ADDED
@@ -0,0 +1,377 @@
1
+ # serverless-event-orchestrator
2
+
3
+ [![npm version](https://badge.fury.io/js/serverless-event-orchestrator.svg)](https://www.npmjs.com/package/serverless-event-orchestrator)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ A lightweight, type-safe event dispatcher and middleware orchestrator for AWS Lambda. Designed for hexagonal architectures with support for segmented routing (public, private, backoffice), Cognito User Pool validation, and built-in infrastructure middlewares.
7
+
8
+ ## Features
9
+
10
+ - **Multi-Trigger Support**: Handle HTTP (API Gateway), SQS, EventBridge, and Lambda invocations with a single handler
11
+ - **Segmented Routing**: Organize routes by security context (`public`, `private`, `backoffice`, `internal`)
12
+ - **Path Parameters**: Built-in support for dynamic routes like `/users/{id}`
13
+ - **Identity Aware**: Automatic Cognito User Pool validation per segment
14
+ - **Middleware Support**: Global and per-segment middleware chains
15
+ - **Zero Config CORS**: Built-in CORS handling with sensible defaults
16
+ - **Response Utilities**: Standardized response helpers (success, error, etc.)
17
+ - **TypeScript First**: Full type safety with exported interfaces
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install serverless-event-orchestrator
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ### Basic Usage (Flat Routes)
28
+
29
+ ```typescript
30
+ import { dispatchEvent, HttpRouter, successResponse } from 'serverless-event-orchestrator';
31
+
32
+ const routes: HttpRouter = {
33
+ get: {
34
+ '/users': {
35
+ handler: async (event) => {
36
+ return successResponse({ users: [] });
37
+ }
38
+ },
39
+ '/users/{id}': {
40
+ handler: async (event) => {
41
+ const userId = event.params.id;
42
+ return successResponse({ id: userId, name: 'John' });
43
+ }
44
+ }
45
+ },
46
+ post: {
47
+ '/users': {
48
+ handler: async (event) => {
49
+ const body = event.payload.body;
50
+ return successResponse({ created: true, data: body });
51
+ }
52
+ }
53
+ }
54
+ };
55
+
56
+ export const handler = async (event: any) => {
57
+ return dispatchEvent(event, { apigateway: routes });
58
+ };
59
+ ```
60
+
61
+ ### Segmented Routes (Recommended)
62
+
63
+ Organize routes by security context for cleaner code and automatic validation:
64
+
65
+ ```typescript
66
+ import {
67
+ dispatchEvent,
68
+ SegmentedHttpRouter,
69
+ successResponse,
70
+ forbiddenResponse
71
+ } from 'serverless-event-orchestrator';
72
+
73
+ const routes: SegmentedHttpRouter = {
74
+ // No authentication required
75
+ public: {
76
+ post: {
77
+ '/auth/login': { handler: loginHandler },
78
+ '/auth/register': { handler: registerHandler }
79
+ }
80
+ },
81
+
82
+ // Requires authenticated user (Client User Pool)
83
+ private: {
84
+ get: {
85
+ '/me': { handler: getProfileHandler },
86
+ '/orders': { handler: getOrdersHandler }
87
+ },
88
+ put: {
89
+ '/me': { handler: updateProfileHandler }
90
+ }
91
+ },
92
+
93
+ // Requires admin user (Backoffice User Pool)
94
+ backoffice: {
95
+ get: {
96
+ '/admin/users': { handler: listAllUsersHandler }
97
+ },
98
+ delete: {
99
+ '/admin/users/{id}': { handler: deleteUserHandler }
100
+ }
101
+ },
102
+
103
+ // Internal Lambda-to-Lambda calls
104
+ internal: {
105
+ post: {
106
+ '/internal/sync': { handler: syncDataHandler }
107
+ }
108
+ }
109
+ };
110
+
111
+ export const handler = async (event: any) => {
112
+ return dispatchEvent(event, { apigateway: routes }, {
113
+ debug: process.env.DEBUG === 'true',
114
+ userPools: {
115
+ private: process.env.USER_POOL_ID,
116
+ backoffice: process.env.ADMIN_POOL_ID
117
+ }
118
+ });
119
+ };
120
+ ```
121
+
122
+ ### With Middleware
123
+
124
+ ```typescript
125
+ import {
126
+ AdvancedSegmentedRouter,
127
+ NormalizedEvent,
128
+ forbiddenResponse
129
+ } from 'serverless-event-orchestrator';
130
+
131
+ // Custom middleware
132
+ const validateAdminRole = async (event: NormalizedEvent) => {
133
+ const groups = event.context.identity?.groups ?? [];
134
+ if (!groups.includes('Admins')) {
135
+ throw forbiddenResponse('Admin role required');
136
+ }
137
+ return event;
138
+ };
139
+
140
+ const routes: AdvancedSegmentedRouter = {
141
+ public: {
142
+ routes: {
143
+ get: { '/health': { handler: healthCheck } }
144
+ }
145
+ },
146
+ backoffice: {
147
+ middleware: [validateAdminRole],
148
+ routes: {
149
+ get: { '/admin/dashboard': { handler: dashboardHandler } }
150
+ }
151
+ }
152
+ };
153
+ ```
154
+
155
+ ## Handling Multiple Event Types
156
+
157
+ ```typescript
158
+ import { dispatchEvent, DispatchRoutes, NormalizedEvent } from 'serverless-event-orchestrator';
159
+
160
+ const routes: DispatchRoutes = {
161
+ // HTTP routes
162
+ apigateway: {
163
+ public: {
164
+ get: { '/status': { handler: statusHandler } }
165
+ }
166
+ },
167
+
168
+ // EventBridge events
169
+ eventbridge: {
170
+ 'user.created': async (event: NormalizedEvent) => {
171
+ console.log('User created:', event.payload.body);
172
+ },
173
+ 'order.completed': async (event: NormalizedEvent) => {
174
+ console.log('Order completed:', event.payload.body);
175
+ },
176
+ default: async (event: NormalizedEvent) => {
177
+ console.log('Unknown event:', event.payload.body);
178
+ }
179
+ },
180
+
181
+ // SQS queues
182
+ sqs: {
183
+ 'notification-queue': async (event: NormalizedEvent) => {
184
+ console.log('Notification:', event.payload.body);
185
+ },
186
+ default: async (event: NormalizedEvent) => {
187
+ console.log('Unknown queue message:', event.payload.body);
188
+ }
189
+ }
190
+ };
191
+
192
+ export const handler = async (event: any) => {
193
+ return dispatchEvent(event, routes);
194
+ };
195
+ ```
196
+
197
+ ## Response Utilities
198
+
199
+ Built-in response helpers for consistent API responses:
200
+
201
+ ```typescript
202
+ import {
203
+ successResponse,
204
+ createdResponse,
205
+ badRequestResponse,
206
+ unauthorizedResponse,
207
+ forbiddenResponse,
208
+ notFoundResponse,
209
+ conflictResponse,
210
+ validationErrorResponse,
211
+ internalErrorResponse,
212
+ customErrorResponse
213
+ } from 'serverless-event-orchestrator';
214
+
215
+ // Success responses
216
+ successResponse({ user: { id: 1 } });
217
+ // { statusCode: 200, body: '{"status":200,"code":"SUCCESS","data":{"user":{"id":1}}}' }
218
+
219
+ createdResponse({ id: 123 });
220
+ // { statusCode: 201, body: '{"status":201,"code":"CREATED","data":{"id":123}}' }
221
+
222
+ // Error responses
223
+ badRequestResponse('Invalid email format');
224
+ notFoundResponse('User not found');
225
+
226
+ // Custom error codes (your domain-specific codes)
227
+ enum MyErrorCodes {
228
+ USER_SUSPENDED = 'USER_SUSPENDED',
229
+ QUOTA_EXCEEDED = 'QUOTA_EXCEEDED'
230
+ }
231
+
232
+ const codeToStatus = {
233
+ [MyErrorCodes.USER_SUSPENDED]: 403,
234
+ [MyErrorCodes.QUOTA_EXCEEDED]: 429
235
+ };
236
+
237
+ customErrorResponse(MyErrorCodes.QUOTA_EXCEEDED, 'API quota exceeded', codeToStatus);
238
+ ```
239
+
240
+ ## Identity & Security
241
+
242
+ Extract and validate Cognito claims:
243
+
244
+ ```typescript
245
+ import {
246
+ extractIdentity,
247
+ hasAnyGroup,
248
+ hasAllGroups,
249
+ validateIssuer
250
+ } from 'serverless-event-orchestrator';
251
+
252
+ const myHandler = async (event: NormalizedEvent) => {
253
+ const identity = event.context.identity;
254
+
255
+ // Access user info
256
+ console.log(identity?.userId); // Cognito sub
257
+ console.log(identity?.email); // User email
258
+ console.log(identity?.groups); // Cognito groups
259
+
260
+ // Check groups
261
+ if (hasAnyGroup(identity, ['Admins', 'Moderators'])) {
262
+ // User has admin or moderator role
263
+ }
264
+
265
+ if (hasAllGroups(identity, ['Premium', 'Verified'])) {
266
+ // User has both premium and verified status
267
+ }
268
+ };
269
+ ```
270
+
271
+ ## CORS Handling
272
+
273
+ ```typescript
274
+ import { withCors, applyCorsHeaders } from 'serverless-event-orchestrator';
275
+
276
+ // Option 1: Wrap handler
277
+ const handler = withCors(async (event) => {
278
+ return successResponse({ data: 'Hello' });
279
+ }, {
280
+ origins: ['https://myapp.com', 'https://admin.myapp.com'],
281
+ credentials: true,
282
+ maxAge: 86400
283
+ });
284
+
285
+ // Option 2: Apply to response
286
+ const response = successResponse({ data: 'Hello' });
287
+ return applyCorsHeaders(response, { origins: '*' });
288
+ ```
289
+
290
+ ## Configuration
291
+
292
+ ```typescript
293
+ import { dispatchEvent, OrchestratorConfig } from 'serverless-event-orchestrator';
294
+
295
+ const config: OrchestratorConfig = {
296
+ // Enable debug logging
297
+ debug: process.env.NODE_ENV !== 'production',
298
+
299
+ // User Pool validation per segment
300
+ userPools: {
301
+ private: 'us-east-1_ABC123',
302
+ backoffice: 'us-east-1_XYZ789'
303
+ },
304
+
305
+ // Global middleware (runs for all routes)
306
+ globalMiddleware: [
307
+ async (event) => {
308
+ console.log('Request:', event.context.requestId);
309
+ return event;
310
+ }
311
+ ],
312
+
313
+ // Custom response handlers
314
+ responses: {
315
+ notFound: () => ({ statusCode: 404, body: JSON.stringify({ error: 'Not found' }) }),
316
+ forbidden: () => ({ statusCode: 403, body: JSON.stringify({ error: 'Access denied' }) })
317
+ }
318
+ };
319
+
320
+ export const handler = async (event: any) => {
321
+ return dispatchEvent(event, routes, config);
322
+ };
323
+ ```
324
+
325
+ ## API Reference
326
+
327
+ ### Core Functions
328
+
329
+ | Function | Description |
330
+ |----------|-------------|
331
+ | `dispatchEvent(event, routes, config?)` | Main dispatcher function |
332
+ | `createOrchestrator(config)` | Creates a pre-configured dispatcher |
333
+ | `detectEventType(event)` | Detects AWS event type |
334
+
335
+ ### Response Helpers
336
+
337
+ | Function | Status Code |
338
+ |----------|-------------|
339
+ | `successResponse(data?, code?)` | 200 |
340
+ | `createdResponse(data?, code?)` | 201 |
341
+ | `badRequestResponse(message?, code?)` | 400 |
342
+ | `unauthorizedResponse(message?, code?)` | 401 |
343
+ | `forbiddenResponse(message?, code?)` | 403 |
344
+ | `notFoundResponse(message?, code?)` | 404 |
345
+ | `conflictResponse(message?, code?)` | 409 |
346
+ | `validationErrorResponse(message?, code?)` | 422 |
347
+ | `internalErrorResponse(message?, code?)` | 500 |
348
+
349
+ ### Identity Functions
350
+
351
+ | Function | Description |
352
+ |----------|-------------|
353
+ | `extractIdentity(event)` | Extracts Cognito claims from event |
354
+ | `validateIssuer(identity, userPoolId)` | Validates token issuer |
355
+ | `hasAnyGroup(identity, groups)` | Checks if user has any of the groups |
356
+ | `hasAllGroups(identity, groups)` | Checks if user has all groups |
357
+
358
+ ## TypeScript Support
359
+
360
+ All types are exported for full TypeScript support:
361
+
362
+ ```typescript
363
+ import type {
364
+ HttpRouter,
365
+ SegmentedHttpRouter,
366
+ AdvancedSegmentedRouter,
367
+ NormalizedEvent,
368
+ IdentityContext,
369
+ RouteConfig,
370
+ OrchestratorConfig,
371
+ MiddlewareFn
372
+ } from 'serverless-event-orchestrator';
373
+ ```
374
+
375
+ ## License
376
+
377
+ MIT 2024
@@ -0,0 +1,18 @@
1
+ import { EventType } from './types/event-type.enum.js';
2
+ import { DispatchRoutes, OrchestratorConfig } from './types/routes.js';
3
+ /**
4
+ * Detects the type of AWS event
5
+ */
6
+ export declare function detectEventType(event: any): EventType;
7
+ /**
8
+ * Main dispatch function with all improvements
9
+ */
10
+ export declare function dispatchEvent(event: any, routes: DispatchRoutes, config?: OrchestratorConfig): Promise<any>;
11
+ /**
12
+ * Creates an orchestrator instance with pre-configured options
13
+ */
14
+ export declare function createOrchestrator(config?: OrchestratorConfig): {
15
+ dispatch: (event: any, routes: DispatchRoutes) => Promise<any>;
16
+ config: OrchestratorConfig;
17
+ };
18
+ //# sourceMappingURL=dispatcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../src/dispatcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAgB,MAAM,4BAA4B,CAAC;AACrE,OAAO,EACL,cAAc,EASd,kBAAkB,EAEnB,MAAM,mBAAmB,CAAC;AAO3B;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,GAAG,GAAG,SAAS,CAMrD;AAyOD;;GAEG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,GAAG,EACV,MAAM,EAAE,cAAc,EACtB,MAAM,GAAE,kBAAuB,GAC9B,OAAO,CAAC,GAAG,CAAC,CAuId;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,GAAE,kBAAuB;sBAE5C,GAAG,UAAU,cAAc;;EAGhD"}