serverless-event-orchestrator 2.0.1 → 2.3.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.
Files changed (52) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +489 -434
  3. package/dist/dispatcher.d.ts +6 -1
  4. package/dist/dispatcher.d.ts.map +1 -1
  5. package/dist/dispatcher.js +66 -7
  6. package/dist/dispatcher.js.map +1 -1
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/types/event-type.enum.d.ts +1 -0
  11. package/dist/types/event-type.enum.d.ts.map +1 -1
  12. package/dist/types/event-type.enum.js +1 -0
  13. package/dist/types/event-type.enum.js.map +1 -1
  14. package/dist/types/routes.d.ts +6 -0
  15. package/dist/types/routes.d.ts.map +1 -1
  16. package/jest.config.js +32 -32
  17. package/package.json +82 -81
  18. package/src/dispatcher.ts +586 -519
  19. package/src/http/body-parser.ts +60 -60
  20. package/src/http/cors.ts +76 -76
  21. package/src/http/index.ts +3 -3
  22. package/src/http/response.ts +209 -209
  23. package/src/identity/extractor.ts +207 -207
  24. package/src/identity/index.ts +2 -2
  25. package/src/identity/jwt-verifier.ts +41 -41
  26. package/src/index.ts +128 -127
  27. package/src/middleware/crm-guard.ts +51 -51
  28. package/src/middleware/index.ts +3 -3
  29. package/src/middleware/init-tenant-context.ts +59 -59
  30. package/src/middleware/tenant-guard.ts +54 -54
  31. package/src/tenant/TenantContext.ts +115 -115
  32. package/src/tenant/helpers.ts +112 -112
  33. package/src/tenant/index.ts +21 -21
  34. package/src/tenant/types.ts +101 -101
  35. package/src/types/event-type.enum.ts +21 -20
  36. package/src/types/index.ts +2 -2
  37. package/src/types/routes.ts +218 -211
  38. package/src/utils/headers.ts +72 -72
  39. package/src/utils/index.ts +2 -2
  40. package/src/utils/path-matcher.ts +84 -84
  41. package/tests/cors.test.ts +133 -133
  42. package/tests/dispatcher.test.ts +795 -715
  43. package/tests/headers.test.ts +99 -99
  44. package/tests/identity.test.ts +301 -301
  45. package/tests/middleware/crm-guard.test.ts +69 -69
  46. package/tests/middleware/init-tenant-context.test.ts +147 -147
  47. package/tests/middleware/tenant-guard.test.ts +100 -100
  48. package/tests/path-matcher.test.ts +102 -102
  49. package/tests/response.test.ts +155 -155
  50. package/tests/tenant/TenantContext.test.ts +134 -134
  51. package/tests/tenant/helpers.test.ts +187 -187
  52. package/tsconfig.json +24 -24
@@ -1,211 +1,218 @@
1
- import { RouteSegment } from './event-type.enum.js';
2
- import type { TenantInfo } from '../tenant/types.js';
3
-
4
- /**
5
- * HTTP methods supported by the router
6
- */
7
- export type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head' | 'options';
8
-
9
- /**
10
- * Middleware function signature
11
- * Returns the modified event or throws an error to halt execution
12
- */
13
- export type MiddlewareFn = (event: NormalizedEvent) => Promise<NormalizedEvent | void>;
14
-
15
- /**
16
- * Route configuration for a single endpoint
17
- */
18
- export interface RouteConfig {
19
- handler: (event: NormalizedEvent) => Promise<any>;
20
- middleware?: MiddlewareFn[];
21
- cors?: boolean | CorsConfig;
22
- rateLimit?: RateLimitConfig;
23
- }
24
-
25
- /**
26
- * CORS configuration options
27
- */
28
- export interface CorsConfig {
29
- origins: string[] | '*';
30
- methods?: string[];
31
- headers?: string[];
32
- credentials?: boolean;
33
- maxAge?: number;
34
- exposedHeaders?: string[];
35
- }
36
-
37
- /**
38
- * Rate limit configuration
39
- */
40
- export interface RateLimitConfig {
41
- burstLimit: number;
42
- rateLimit: number;
43
- }
44
-
45
- /**
46
- * Standard HTTP router structure
47
- * Maps HTTP methods to path-handler pairs
48
- */
49
- export type HttpRouter = {
50
- [K in HttpMethod]?: {
51
- [path: string]: RouteConfig;
52
- };
53
- };
54
-
55
- /**
56
- * Segmented HTTP router for access control categorization
57
- * Allows organizing routes by security context
58
- */
59
- export interface SegmentedHttpRouter {
60
- public?: HttpRouter;
61
- private?: HttpRouter;
62
- backoffice?: HttpRouter;
63
- internal?: HttpRouter;
64
- }
65
-
66
- /**
67
- * Segment configuration with optional middleware
68
- */
69
- export interface SegmentConfig {
70
- routes: HttpRouter;
71
- middleware?: MiddlewareFn[];
72
- }
73
-
74
- /**
75
- * Advanced segmented router with per-segment middleware
76
- */
77
- export interface AdvancedSegmentedRouter {
78
- public?: SegmentConfig | HttpRouter;
79
- private?: SegmentConfig | HttpRouter;
80
- backoffice?: SegmentConfig | HttpRouter;
81
- internal?: SegmentConfig | HttpRouter;
82
- }
83
-
84
- /**
85
- * EventBridge routes configuration
86
- * Maps operation names to handlers
87
- */
88
- export type EventBridgeRoutes = Record<string, (event: NormalizedEvent) => Promise<any>>;
89
-
90
- /**
91
- * Lambda invocation routes
92
- */
93
- export type LambdaRoutes = Record<string, (event: NormalizedEvent) => Promise<any>>;
94
-
95
- /**
96
- * SQS queue routes
97
- * Maps queue names to handlers
98
- */
99
- export type SqsRoutes = Record<string, (event: NormalizedEvent) => Promise<any>>;
100
-
101
- /**
102
- * Complete dispatch routes configuration
103
- */
104
- export interface DispatchRoutes {
105
- apigateway?: HttpRouter | SegmentedHttpRouter | AdvancedSegmentedRouter;
106
- eventbridge?: EventBridgeRoutes;
107
- lambda?: LambdaRoutes;
108
- sqs?: SqsRoutes;
109
- }
110
-
111
- /**
112
- * Identity context extracted from the event
113
- */
114
- export interface IdentityContext {
115
- userId?: string;
116
- email?: string;
117
- groups?: string[];
118
- issuer?: string;
119
- claims?: Record<string, any>;
120
- }
121
-
122
- /**
123
- * Route match result with extracted parameters
124
- */
125
- export interface RouteMatch {
126
- handler: (event: NormalizedEvent) => Promise<any>;
127
- params: Record<string, string>;
128
- segment: RouteSegment;
129
- middleware?: MiddlewareFn[];
130
- config: RouteConfig;
131
- }
132
-
133
- /**
134
- * Normalized event structure passed to handlers
135
- */
136
- export interface NormalizedEvent {
137
- eventRaw: any;
138
- eventType: string;
139
- payload: {
140
- body?: Record<string, any>;
141
- pathParameters?: Record<string, string>;
142
- queryStringParameters?: Record<string, string>;
143
- headers?: Record<string, string>;
144
- };
145
- params: Record<string, string>;
146
- context: {
147
- segment: RouteSegment;
148
- identity?: IdentityContext;
149
- requestId?: string;
150
- tenantInfo?: TenantInfo;
151
- };
152
- }
153
-
154
- /**
155
- * JWT verification configuration for a Cognito User Pool
156
- */
157
- export interface JwtVerificationPoolConfig {
158
- /** Cognito User Pool ID (e.g., "us-east-1_ABC123") */
159
- userPoolId: string;
160
- /** App Client ID(s). Pass null to skip client ID verification. */
161
- clientId?: string | string[] | null;
162
- /** Expected token use. Pass null to accept either. Defaults to null. */
163
- tokenUse?: 'id' | 'access' | null;
164
- }
165
-
166
- /**
167
- * Orchestrator configuration options
168
- */
169
- export interface OrchestratorConfig {
170
- /**
171
- * Enable verbose logging for debugging
172
- */
173
- debug?: boolean;
174
-
175
- /**
176
- * User Pool ID mappings for segment-based validation (issuer string only).
177
- * @deprecated Use jwtVerification instead for cryptographic signature verification.
178
- */
179
- userPools?: {
180
- [K in RouteSegment]?: string;
181
- };
182
-
183
- /**
184
- * Global middleware applied to all routes
185
- */
186
- globalMiddleware?: MiddlewareFn[];
187
-
188
- /**
189
- * Custom response handlers
190
- */
191
- responses?: {
192
- notFound?: () => any;
193
- forbidden?: () => any;
194
- badRequest?: (message?: string) => any;
195
- internalError?: (message?: string) => any;
196
- };
197
-
198
- /**
199
- * Automatically extract identity from Authorization header if no authorizer is present
200
- */
201
- autoExtractIdentity?: boolean;
202
-
203
- /**
204
- * JWT signature verification configuration.
205
- * When provided for a segment, JWTs from the Authorization header
206
- * are cryptographically verified against the Cognito JWKS endpoint.
207
- */
208
- jwtVerification?: {
209
- [K in RouteSegment]?: JwtVerificationPoolConfig;
210
- };
211
- }
1
+ import { RouteSegment } from './event-type.enum.js';
2
+ import type { TenantInfo } from '../tenant/types.js';
3
+
4
+ /**
5
+ * HTTP methods supported by the router
6
+ */
7
+ export type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head' | 'options';
8
+
9
+ /**
10
+ * Middleware function signature
11
+ * Returns the modified event or throws an error to halt execution
12
+ */
13
+ export type MiddlewareFn = (event: NormalizedEvent) => Promise<NormalizedEvent | void>;
14
+
15
+ /**
16
+ * Route configuration for a single endpoint
17
+ */
18
+ export interface RouteConfig {
19
+ handler: (event: NormalizedEvent) => Promise<any>;
20
+ middleware?: MiddlewareFn[];
21
+ cors?: boolean | CorsConfig;
22
+ rateLimit?: RateLimitConfig;
23
+ }
24
+
25
+ /**
26
+ * CORS configuration options
27
+ */
28
+ export interface CorsConfig {
29
+ origins: string[] | '*';
30
+ methods?: string[];
31
+ headers?: string[];
32
+ credentials?: boolean;
33
+ maxAge?: number;
34
+ exposedHeaders?: string[];
35
+ }
36
+
37
+ /**
38
+ * Rate limit configuration
39
+ */
40
+ export interface RateLimitConfig {
41
+ burstLimit: number;
42
+ rateLimit: number;
43
+ }
44
+
45
+ /**
46
+ * Standard HTTP router structure
47
+ * Maps HTTP methods to path-handler pairs
48
+ */
49
+ export type HttpRouter = {
50
+ [K in HttpMethod]?: {
51
+ [path: string]: RouteConfig;
52
+ };
53
+ };
54
+
55
+ /**
56
+ * Segmented HTTP router for access control categorization
57
+ * Allows organizing routes by security context
58
+ */
59
+ export interface SegmentedHttpRouter {
60
+ public?: HttpRouter;
61
+ private?: HttpRouter;
62
+ backoffice?: HttpRouter;
63
+ internal?: HttpRouter;
64
+ }
65
+
66
+ /**
67
+ * Segment configuration with optional middleware
68
+ */
69
+ export interface SegmentConfig {
70
+ routes: HttpRouter;
71
+ middleware?: MiddlewareFn[];
72
+ }
73
+
74
+ /**
75
+ * Advanced segmented router with per-segment middleware
76
+ */
77
+ export interface AdvancedSegmentedRouter {
78
+ public?: SegmentConfig | HttpRouter;
79
+ private?: SegmentConfig | HttpRouter;
80
+ backoffice?: SegmentConfig | HttpRouter;
81
+ internal?: SegmentConfig | HttpRouter;
82
+ }
83
+
84
+ /**
85
+ * EventBridge routes configuration
86
+ * Maps operation names to handlers
87
+ */
88
+ export type EventBridgeRoutes = Record<string, (event: NormalizedEvent) => Promise<any>>;
89
+
90
+ /**
91
+ * Lambda invocation routes
92
+ */
93
+ export type LambdaRoutes = Record<string, (event: NormalizedEvent) => Promise<any>>;
94
+
95
+ /**
96
+ * SQS queue routes
97
+ * Maps queue names to handlers
98
+ */
99
+ export type SqsRoutes = Record<string, (event: NormalizedEvent) => Promise<any>>;
100
+
101
+ /**
102
+ * Scheduled event routes (EventBridge Scheduler / CloudWatch Events rules)
103
+ * Maps rule names to handlers. Use 'default' as fallback.
104
+ */
105
+ export type ScheduledRoutes = Record<string, (event: NormalizedEvent) => Promise<any>>;
106
+
107
+ /**
108
+ * Complete dispatch routes configuration
109
+ */
110
+ export interface DispatchRoutes {
111
+ apigateway?: HttpRouter | SegmentedHttpRouter | AdvancedSegmentedRouter;
112
+ eventbridge?: EventBridgeRoutes;
113
+ lambda?: LambdaRoutes;
114
+ sqs?: SqsRoutes;
115
+ scheduled?: ScheduledRoutes;
116
+ }
117
+
118
+ /**
119
+ * Identity context extracted from the event
120
+ */
121
+ export interface IdentityContext {
122
+ userId?: string;
123
+ email?: string;
124
+ groups?: string[];
125
+ issuer?: string;
126
+ claims?: Record<string, any>;
127
+ }
128
+
129
+ /**
130
+ * Route match result with extracted parameters
131
+ */
132
+ export interface RouteMatch {
133
+ handler: (event: NormalizedEvent) => Promise<any>;
134
+ params: Record<string, string>;
135
+ segment: RouteSegment;
136
+ middleware?: MiddlewareFn[];
137
+ config: RouteConfig;
138
+ }
139
+
140
+ /**
141
+ * Normalized event structure passed to handlers
142
+ */
143
+ export interface NormalizedEvent {
144
+ eventRaw: any;
145
+ eventType: string;
146
+ payload: {
147
+ body?: Record<string, any>;
148
+ pathParameters?: Record<string, string>;
149
+ queryStringParameters?: Record<string, string>;
150
+ headers?: Record<string, string>;
151
+ };
152
+ params: Record<string, string>;
153
+ context: {
154
+ segment: RouteSegment;
155
+ identity?: IdentityContext;
156
+ requestId?: string;
157
+ tenantInfo?: TenantInfo;
158
+ };
159
+ }
160
+
161
+ /**
162
+ * JWT verification configuration for a Cognito User Pool
163
+ */
164
+ export interface JwtVerificationPoolConfig {
165
+ /** Cognito User Pool ID (e.g., "us-east-1_ABC123") */
166
+ userPoolId: string;
167
+ /** App Client ID(s). Pass null to skip client ID verification. */
168
+ clientId?: string | string[] | null;
169
+ /** Expected token use. Pass null to accept either. Defaults to null. */
170
+ tokenUse?: 'id' | 'access' | null;
171
+ }
172
+
173
+ /**
174
+ * Orchestrator configuration options
175
+ */
176
+ export interface OrchestratorConfig {
177
+ /**
178
+ * Enable verbose logging for debugging
179
+ */
180
+ debug?: boolean;
181
+
182
+ /**
183
+ * User Pool ID mappings for segment-based validation (issuer string only).
184
+ * @deprecated Use jwtVerification instead for cryptographic signature verification.
185
+ */
186
+ userPools?: {
187
+ [K in RouteSegment]?: string;
188
+ };
189
+
190
+ /**
191
+ * Global middleware applied to all routes
192
+ */
193
+ globalMiddleware?: MiddlewareFn[];
194
+
195
+ /**
196
+ * Custom response handlers
197
+ */
198
+ responses?: {
199
+ notFound?: () => any;
200
+ forbidden?: () => any;
201
+ badRequest?: (message?: string) => any;
202
+ internalError?: (message?: string) => any;
203
+ };
204
+
205
+ /**
206
+ * Automatically extract identity from Authorization header if no authorizer is present
207
+ */
208
+ autoExtractIdentity?: boolean;
209
+
210
+ /**
211
+ * JWT signature verification configuration.
212
+ * When provided for a segment, JWTs from the Authorization header
213
+ * are cryptographically verified against the Cognito JWKS endpoint.
214
+ */
215
+ jwtVerification?: {
216
+ [K in RouteSegment]?: JwtVerificationPoolConfig;
217
+ };
218
+ }
@@ -1,72 +1,72 @@
1
- /**
2
- * Header normalization utilities
3
- * HTTP headers are case-insensitive, this ensures consistent access
4
- */
5
-
6
- /**
7
- * Normalizes headers to lowercase keys for consistent access
8
- * @param headers - Original headers object
9
- * @returns Headers with lowercase keys
10
- */
11
- export function normalizeHeaders(headers: Record<string, string> | undefined): Record<string, string> {
12
- if (!headers) return {};
13
-
14
- const normalized: Record<string, string> = {};
15
-
16
- for (const [key, value] of Object.entries(headers)) {
17
- normalized[key.toLowerCase()] = value;
18
- }
19
-
20
- return normalized;
21
- }
22
-
23
- /**
24
- * Gets a header value case-insensitively
25
- * @param headers - Headers object
26
- * @param name - Header name to find
27
- * @returns Header value or undefined
28
- */
29
- export function getHeader(headers: Record<string, string> | undefined, name: string): string | undefined {
30
- if (!headers) return undefined;
31
-
32
- const normalizedName = name.toLowerCase();
33
-
34
- for (const [key, value] of Object.entries(headers)) {
35
- if (key.toLowerCase() === normalizedName) {
36
- return value;
37
- }
38
- }
39
-
40
- return undefined;
41
- }
42
-
43
- /**
44
- * Standard CORS headers for preflight responses
45
- */
46
- export function getCorsHeaders(config?: {
47
- origins?: string[] | '*';
48
- methods?: string[];
49
- headers?: string[];
50
- credentials?: boolean;
51
- maxAge?: number;
52
- }): Record<string, string> {
53
- const origin = config?.origins === '*' ? '*' : (config?.origins?.join(', ') || '*');
54
- const methods = config?.methods?.join(', ') || 'GET,POST,PUT,DELETE,PATCH,OPTIONS';
55
- const headers = config?.headers?.join(', ') || 'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token';
56
-
57
- const corsHeaders: Record<string, string> = {
58
- 'Access-Control-Allow-Origin': origin,
59
- 'Access-Control-Allow-Methods': methods,
60
- 'Access-Control-Allow-Headers': headers,
61
- };
62
-
63
- if (config?.credentials) {
64
- corsHeaders['Access-Control-Allow-Credentials'] = 'true';
65
- }
66
-
67
- if (config?.maxAge) {
68
- corsHeaders['Access-Control-Max-Age'] = String(config.maxAge);
69
- }
70
-
71
- return corsHeaders;
72
- }
1
+ /**
2
+ * Header normalization utilities
3
+ * HTTP headers are case-insensitive, this ensures consistent access
4
+ */
5
+
6
+ /**
7
+ * Normalizes headers to lowercase keys for consistent access
8
+ * @param headers - Original headers object
9
+ * @returns Headers with lowercase keys
10
+ */
11
+ export function normalizeHeaders(headers: Record<string, string> | undefined): Record<string, string> {
12
+ if (!headers) return {};
13
+
14
+ const normalized: Record<string, string> = {};
15
+
16
+ for (const [key, value] of Object.entries(headers)) {
17
+ normalized[key.toLowerCase()] = value;
18
+ }
19
+
20
+ return normalized;
21
+ }
22
+
23
+ /**
24
+ * Gets a header value case-insensitively
25
+ * @param headers - Headers object
26
+ * @param name - Header name to find
27
+ * @returns Header value or undefined
28
+ */
29
+ export function getHeader(headers: Record<string, string> | undefined, name: string): string | undefined {
30
+ if (!headers) return undefined;
31
+
32
+ const normalizedName = name.toLowerCase();
33
+
34
+ for (const [key, value] of Object.entries(headers)) {
35
+ if (key.toLowerCase() === normalizedName) {
36
+ return value;
37
+ }
38
+ }
39
+
40
+ return undefined;
41
+ }
42
+
43
+ /**
44
+ * Standard CORS headers for preflight responses
45
+ */
46
+ export function getCorsHeaders(config?: {
47
+ origins?: string[] | '*';
48
+ methods?: string[];
49
+ headers?: string[];
50
+ credentials?: boolean;
51
+ maxAge?: number;
52
+ }): Record<string, string> {
53
+ const origin = config?.origins === '*' ? '*' : (config?.origins?.join(', ') || '*');
54
+ const methods = config?.methods?.join(', ') || 'GET,POST,PUT,DELETE,PATCH,OPTIONS';
55
+ const headers = config?.headers?.join(', ') || 'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token';
56
+
57
+ const corsHeaders: Record<string, string> = {
58
+ 'Access-Control-Allow-Origin': origin,
59
+ 'Access-Control-Allow-Methods': methods,
60
+ 'Access-Control-Allow-Headers': headers,
61
+ };
62
+
63
+ if (config?.credentials) {
64
+ corsHeaders['Access-Control-Allow-Credentials'] = 'true';
65
+ }
66
+
67
+ if (config?.maxAge) {
68
+ corsHeaders['Access-Control-Max-Age'] = String(config.maxAge);
69
+ }
70
+
71
+ return corsHeaders;
72
+ }
@@ -1,2 +1,2 @@
1
- export * from './path-matcher.js';
2
- export * from './headers.js';
1
+ export * from './path-matcher.js';
2
+ export * from './headers.js';