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
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Response utilities for consistent HTTP responses
3
+ * Agnostic to domain-specific error codes - allows injection of custom codes
4
+ */
5
+
6
+ /**
7
+ * Standard HTTP status codes
8
+ */
9
+ export enum HttpStatus {
10
+ OK = 200,
11
+ CREATED = 201,
12
+ NO_CONTENT = 204,
13
+ BAD_REQUEST = 400,
14
+ UNAUTHORIZED = 401,
15
+ FORBIDDEN = 403,
16
+ NOT_FOUND = 404,
17
+ CONFLICT = 409,
18
+ UNPROCESSABLE_ENTITY = 422,
19
+ INTERNAL_SERVER_ERROR = 500,
20
+ SERVICE_UNAVAILABLE = 503,
21
+ }
22
+
23
+ /**
24
+ * Standard response structure
25
+ */
26
+ export interface StandardResponse<T = unknown, C = string> {
27
+ status: number;
28
+ code: C;
29
+ data?: T;
30
+ message?: string;
31
+ }
32
+
33
+ /**
34
+ * Lambda HTTP response format
35
+ */
36
+ export interface HttpResponse {
37
+ statusCode: number;
38
+ body: string;
39
+ headers?: Record<string, string>;
40
+ }
41
+
42
+ /**
43
+ * Default response codes for standard HTTP statuses
44
+ */
45
+ export const DefaultResponseCode = {
46
+ SUCCESS: 'SUCCESS',
47
+ CREATED: 'CREATED',
48
+ BAD_REQUEST: 'BAD_REQUEST',
49
+ UNAUTHORIZED: 'UNAUTHORIZED',
50
+ FORBIDDEN: 'FORBIDDEN',
51
+ NOT_FOUND: 'NOT_FOUND',
52
+ CONFLICT: 'CONFLICT',
53
+ VALIDATION_ERROR: 'VALIDATION_ERROR',
54
+ INTERNAL_ERROR: 'INTERNAL_ERROR',
55
+ } as const;
56
+
57
+ /**
58
+ * Creates a standardized HTTP response
59
+ * @param statusCode - HTTP status code
60
+ * @param data - Response payload
61
+ * @param code - Custom response code
62
+ * @param message - Optional message
63
+ * @param headers - Optional headers
64
+ */
65
+ export function createStandardResponse<T, C = string>(
66
+ statusCode: number,
67
+ data?: T,
68
+ code?: C,
69
+ message?: string,
70
+ headers?: Record<string, string>
71
+ ): HttpResponse {
72
+ const responseCode = code ?? getDefaultCodeForStatus(statusCode);
73
+
74
+ const body: StandardResponse<T, C | string> = {
75
+ status: statusCode,
76
+ code: responseCode,
77
+ ...(data !== undefined && { data }),
78
+ ...(message && { message }),
79
+ };
80
+
81
+ return {
82
+ statusCode,
83
+ body: JSON.stringify(body, null, 2),
84
+ headers: {
85
+ 'Content-Type': 'application/json',
86
+ ...headers,
87
+ },
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Gets default response code for a status
93
+ */
94
+ function getDefaultCodeForStatus(status: number): string {
95
+ switch (status) {
96
+ case HttpStatus.OK:
97
+ return DefaultResponseCode.SUCCESS;
98
+ case HttpStatus.CREATED:
99
+ return DefaultResponseCode.CREATED;
100
+ case HttpStatus.BAD_REQUEST:
101
+ return DefaultResponseCode.BAD_REQUEST;
102
+ case HttpStatus.UNAUTHORIZED:
103
+ return DefaultResponseCode.UNAUTHORIZED;
104
+ case HttpStatus.FORBIDDEN:
105
+ return DefaultResponseCode.FORBIDDEN;
106
+ case HttpStatus.NOT_FOUND:
107
+ return DefaultResponseCode.NOT_FOUND;
108
+ case HttpStatus.CONFLICT:
109
+ return DefaultResponseCode.CONFLICT;
110
+ case HttpStatus.UNPROCESSABLE_ENTITY:
111
+ return DefaultResponseCode.VALIDATION_ERROR;
112
+ default:
113
+ return DefaultResponseCode.INTERNAL_ERROR;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Success response (200 OK)
119
+ */
120
+ export function successResponse<T>(data?: T, code?: string, headers?: Record<string, string>): HttpResponse {
121
+ return createStandardResponse(HttpStatus.OK, data, code ?? DefaultResponseCode.SUCCESS, undefined, headers);
122
+ }
123
+
124
+ /**
125
+ * Created response (201 Created)
126
+ */
127
+ export function createdResponse<T>(data?: T, code?: string, headers?: Record<string, string>): HttpResponse {
128
+ return createStandardResponse(HttpStatus.CREATED, data, code ?? DefaultResponseCode.CREATED, undefined, headers);
129
+ }
130
+
131
+ /**
132
+ * Bad request response (400)
133
+ */
134
+ export function badRequestResponse(message?: string, code?: string, headers?: Record<string, string>): HttpResponse {
135
+ return createStandardResponse(HttpStatus.BAD_REQUEST, undefined, code ?? DefaultResponseCode.BAD_REQUEST, message, headers);
136
+ }
137
+
138
+ /**
139
+ * Unauthorized response (401)
140
+ */
141
+ export function unauthorizedResponse(message?: string, code?: string, headers?: Record<string, string>): HttpResponse {
142
+ return createStandardResponse(HttpStatus.UNAUTHORIZED, undefined, code ?? DefaultResponseCode.UNAUTHORIZED, message, headers);
143
+ }
144
+
145
+ /**
146
+ * Forbidden response (403)
147
+ */
148
+ export function forbiddenResponse(message?: string, code?: string, headers?: Record<string, string>): HttpResponse {
149
+ return createStandardResponse(HttpStatus.FORBIDDEN, undefined, code ?? DefaultResponseCode.FORBIDDEN, message, headers);
150
+ }
151
+
152
+ /**
153
+ * Not found response (404)
154
+ */
155
+ export function notFoundResponse(message?: string, code?: string, headers?: Record<string, string>): HttpResponse {
156
+ return createStandardResponse(HttpStatus.NOT_FOUND, undefined, code ?? DefaultResponseCode.NOT_FOUND, message, headers);
157
+ }
158
+
159
+ /**
160
+ * Conflict response (409)
161
+ */
162
+ export function conflictResponse(message?: string, code?: string, headers?: Record<string, string>): HttpResponse {
163
+ return createStandardResponse(HttpStatus.CONFLICT, undefined, code ?? DefaultResponseCode.CONFLICT, message, headers);
164
+ }
165
+
166
+ /**
167
+ * Validation error response (422)
168
+ */
169
+ export function validationErrorResponse(message?: string, code?: string, headers?: Record<string, string>): HttpResponse {
170
+ return createStandardResponse(HttpStatus.UNPROCESSABLE_ENTITY, undefined, code ?? DefaultResponseCode.VALIDATION_ERROR, message, headers);
171
+ }
172
+
173
+ /**
174
+ * Internal server error response (500)
175
+ */
176
+ export function internalErrorResponse(message?: string, code?: string, headers?: Record<string, string>): HttpResponse {
177
+ return createStandardResponse(HttpStatus.INTERNAL_SERVER_ERROR, undefined, code ?? DefaultResponseCode.INTERNAL_ERROR, message, headers);
178
+ }
179
+
180
+ /**
181
+ * Custom error response with automatic status resolution
182
+ * @param customCode - Your domain-specific error code
183
+ * @param message - Error message
184
+ * @param codeToStatusMap - Mapping of custom codes to HTTP statuses
185
+ */
186
+ export function customErrorResponse<C extends string>(
187
+ customCode: C,
188
+ message?: string,
189
+ codeToStatusMap?: Record<C, HttpStatus>,
190
+ headers?: Record<string, string>
191
+ ): HttpResponse {
192
+ const status = codeToStatusMap?.[customCode] ?? HttpStatus.INTERNAL_SERVER_ERROR;
193
+ return createStandardResponse(status, undefined, customCode, message, headers);
194
+ }
@@ -0,0 +1,89 @@
1
+ import { IdentityContext } from '../types/routes.js';
2
+
3
+ /**
4
+ * Extracts identity context from API Gateway authorizer claims
5
+ * Supports Cognito User Pools and custom authorizers
6
+ */
7
+
8
+ /**
9
+ * Extracts identity information from the event's authorizer context
10
+ * @param event - Raw API Gateway event
11
+ * @returns Identity context or undefined if not authenticated
12
+ */
13
+ export function extractIdentity(event: any): IdentityContext | undefined {
14
+ const claims = event?.requestContext?.authorizer?.claims;
15
+
16
+ if (!claims) {
17
+ return undefined;
18
+ }
19
+
20
+ return {
21
+ userId: claims.sub || claims['cognito:username'],
22
+ email: claims.email,
23
+ groups: parseGroups(claims['cognito:groups']),
24
+ issuer: claims.iss,
25
+ claims,
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Parses Cognito groups from claims
31
+ * Groups can come as a string or array depending on configuration
32
+ */
33
+ function parseGroups(groups: string | string[] | undefined): string[] {
34
+ if (!groups) return [];
35
+ if (Array.isArray(groups)) return groups;
36
+
37
+ // Cognito sometimes sends groups as a comma-separated string
38
+ return groups.split(',').map(g => g.trim()).filter(Boolean);
39
+ }
40
+
41
+ /**
42
+ * Extracts the User Pool ID from the issuer URL
43
+ * @param issuer - Cognito issuer URL (e.g., https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxx)
44
+ * @returns User Pool ID or undefined
45
+ */
46
+ export function extractUserPoolId(issuer: string | undefined): string | undefined {
47
+ if (!issuer) return undefined;
48
+
49
+ // Extract the last segment of the issuer URL
50
+ const parts = issuer.split('/');
51
+ return parts[parts.length - 1];
52
+ }
53
+
54
+ /**
55
+ * Validates that the token issuer matches the expected User Pool
56
+ * @param identity - Extracted identity context
57
+ * @param expectedUserPoolId - Expected User Pool ID
58
+ * @returns True if issuer matches
59
+ */
60
+ export function validateIssuer(identity: IdentityContext | undefined, expectedUserPoolId: string): boolean {
61
+ if (!identity?.issuer) return false;
62
+
63
+ const actualUserPoolId = extractUserPoolId(identity.issuer);
64
+ return actualUserPoolId === expectedUserPoolId;
65
+ }
66
+
67
+ /**
68
+ * Checks if the user belongs to any of the specified groups
69
+ * @param identity - Extracted identity context
70
+ * @param allowedGroups - Groups that grant access
71
+ * @returns True if user is in at least one allowed group
72
+ */
73
+ export function hasAnyGroup(identity: IdentityContext | undefined, allowedGroups: string[]): boolean {
74
+ if (!identity?.groups || identity.groups.length === 0) return false;
75
+
76
+ return allowedGroups.some(group => identity.groups?.includes(group));
77
+ }
78
+
79
+ /**
80
+ * Checks if the user belongs to all specified groups
81
+ * @param identity - Extracted identity context
82
+ * @param requiredGroups - Groups required for access
83
+ * @returns True if user is in all required groups
84
+ */
85
+ export function hasAllGroups(identity: IdentityContext | undefined, requiredGroups: string[]): boolean {
86
+ if (!identity?.groups || identity.groups.length === 0) return false;
87
+
88
+ return requiredGroups.every(group => identity.groups?.includes(group));
89
+ }
@@ -0,0 +1 @@
1
+ export * from './extractor.js';
package/src/index.ts ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * serverless-event-orchestrator
3
+ *
4
+ * A lightweight, type-safe event dispatcher and middleware orchestrator for AWS Lambda.
5
+ * Designed for hexagonal architectures with support for segmented routing,
6
+ * Cognito User Pool validation, and built-in infrastructure middlewares.
7
+ */
8
+
9
+ // Core dispatcher
10
+ export { dispatchEvent, detectEventType, createOrchestrator } from './dispatcher.js';
11
+
12
+ // Types
13
+ export {
14
+ EventType,
15
+ RouteSegment,
16
+ } from './types/event-type.enum.js';
17
+
18
+ export {
19
+ HttpMethod,
20
+ MiddlewareFn,
21
+ RouteConfig,
22
+ CorsConfig,
23
+ RateLimitConfig,
24
+ HttpRouter,
25
+ SegmentedHttpRouter,
26
+ SegmentConfig,
27
+ AdvancedSegmentedRouter,
28
+ EventBridgeRoutes,
29
+ LambdaRoutes,
30
+ SqsRoutes,
31
+ DispatchRoutes,
32
+ IdentityContext,
33
+ RouteMatch,
34
+ NormalizedEvent,
35
+ OrchestratorConfig,
36
+ } from './types/routes.js';
37
+
38
+ // HTTP utilities
39
+ export {
40
+ HttpStatus,
41
+ StandardResponse,
42
+ HttpResponse,
43
+ DefaultResponseCode,
44
+ createStandardResponse,
45
+ successResponse,
46
+ createdResponse,
47
+ badRequestResponse,
48
+ unauthorizedResponse,
49
+ forbiddenResponse,
50
+ notFoundResponse,
51
+ conflictResponse,
52
+ validationErrorResponse,
53
+ internalErrorResponse,
54
+ customErrorResponse,
55
+ } from './http/response.js';
56
+
57
+ export {
58
+ parseJsonBody,
59
+ parseQueryParams,
60
+ withJsonBodyParser,
61
+ } from './http/body-parser.js';
62
+
63
+ export {
64
+ isPreflightRequest,
65
+ createPreflightResponse,
66
+ applyCorsHeaders,
67
+ withCors,
68
+ } from './http/cors.js';
69
+
70
+ // Identity utilities
71
+ export {
72
+ extractIdentity,
73
+ extractUserPoolId,
74
+ validateIssuer,
75
+ hasAnyGroup,
76
+ hasAllGroups,
77
+ } from './identity/extractor.js';
78
+
79
+ // Path utilities
80
+ export {
81
+ matchPath,
82
+ patternToRegex,
83
+ hasPathParameters,
84
+ normalizePath,
85
+ } from './utils/path-matcher.js';
86
+
87
+ // Header utilities
88
+ export {
89
+ normalizeHeaders,
90
+ getHeader,
91
+ getCorsHeaders,
92
+ } from './utils/headers.js';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Supported AWS event types for dispatching
3
+ */
4
+ export enum EventType {
5
+ EventBridge = 'eventbridge',
6
+ ApiGateway = 'apigateway',
7
+ Lambda = 'lambda',
8
+ Sqs = 'sqs',
9
+ Unknown = 'unknown',
10
+ }
11
+
12
+ /**
13
+ * Route segments for access control categorization
14
+ */
15
+ export enum RouteSegment {
16
+ Public = 'public',
17
+ Private = 'private',
18
+ Backoffice = 'backoffice',
19
+ Internal = 'internal',
20
+ }
@@ -0,0 +1,2 @@
1
+ export * from './event-type.enum.js';
2
+ export * from './routes.js';
@@ -0,0 +1,182 @@
1
+ import { RouteSegment } from './event-type.enum.js';
2
+
3
+ /**
4
+ * HTTP methods supported by the router
5
+ */
6
+ export type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head' | 'options';
7
+
8
+ /**
9
+ * Middleware function signature
10
+ * Returns the modified event or throws an error to halt execution
11
+ */
12
+ export type MiddlewareFn = (event: NormalizedEvent) => Promise<NormalizedEvent | void>;
13
+
14
+ /**
15
+ * Route configuration for a single endpoint
16
+ */
17
+ export interface RouteConfig {
18
+ handler: (event: NormalizedEvent) => Promise<any>;
19
+ middleware?: MiddlewareFn[];
20
+ cors?: boolean | CorsConfig;
21
+ rateLimit?: RateLimitConfig;
22
+ }
23
+
24
+ /**
25
+ * CORS configuration options
26
+ */
27
+ export interface CorsConfig {
28
+ origins: string[] | '*';
29
+ methods?: string[];
30
+ headers?: string[];
31
+ credentials?: boolean;
32
+ maxAge?: number;
33
+ exposedHeaders?: string[];
34
+ }
35
+
36
+ /**
37
+ * Rate limit configuration
38
+ */
39
+ export interface RateLimitConfig {
40
+ burstLimit: number;
41
+ rateLimit: number;
42
+ }
43
+
44
+ /**
45
+ * Standard HTTP router structure
46
+ * Maps HTTP methods to path-handler pairs
47
+ */
48
+ export type HttpRouter = {
49
+ [K in HttpMethod]?: {
50
+ [path: string]: RouteConfig;
51
+ };
52
+ };
53
+
54
+ /**
55
+ * Segmented HTTP router for access control categorization
56
+ * Allows organizing routes by security context
57
+ */
58
+ export interface SegmentedHttpRouter {
59
+ public?: HttpRouter;
60
+ private?: HttpRouter;
61
+ backoffice?: HttpRouter;
62
+ internal?: HttpRouter;
63
+ }
64
+
65
+ /**
66
+ * Segment configuration with optional middleware
67
+ */
68
+ export interface SegmentConfig {
69
+ routes: HttpRouter;
70
+ middleware?: MiddlewareFn[];
71
+ }
72
+
73
+ /**
74
+ * Advanced segmented router with per-segment middleware
75
+ */
76
+ export interface AdvancedSegmentedRouter {
77
+ public?: SegmentConfig | HttpRouter;
78
+ private?: SegmentConfig | HttpRouter;
79
+ backoffice?: SegmentConfig | HttpRouter;
80
+ internal?: SegmentConfig | HttpRouter;
81
+ }
82
+
83
+ /**
84
+ * EventBridge routes configuration
85
+ * Maps operation names to handlers
86
+ */
87
+ export type EventBridgeRoutes = Record<string, (event: NormalizedEvent) => Promise<any>>;
88
+
89
+ /**
90
+ * Lambda invocation routes
91
+ */
92
+ export type LambdaRoutes = Record<string, (event: NormalizedEvent) => Promise<any>>;
93
+
94
+ /**
95
+ * SQS queue routes
96
+ * Maps queue names to handlers
97
+ */
98
+ export type SqsRoutes = Record<string, (event: NormalizedEvent) => Promise<any>>;
99
+
100
+ /**
101
+ * Complete dispatch routes configuration
102
+ */
103
+ export interface DispatchRoutes {
104
+ apigateway?: HttpRouter | SegmentedHttpRouter | AdvancedSegmentedRouter;
105
+ eventbridge?: EventBridgeRoutes;
106
+ lambda?: LambdaRoutes;
107
+ sqs?: SqsRoutes;
108
+ }
109
+
110
+ /**
111
+ * Identity context extracted from the event
112
+ */
113
+ export interface IdentityContext {
114
+ userId?: string;
115
+ email?: string;
116
+ groups?: string[];
117
+ issuer?: string;
118
+ claims?: Record<string, any>;
119
+ }
120
+
121
+ /**
122
+ * Route match result with extracted parameters
123
+ */
124
+ export interface RouteMatch {
125
+ handler: (event: NormalizedEvent) => Promise<any>;
126
+ params: Record<string, string>;
127
+ segment: RouteSegment;
128
+ middleware?: MiddlewareFn[];
129
+ config: RouteConfig;
130
+ }
131
+
132
+ /**
133
+ * Normalized event structure passed to handlers
134
+ */
135
+ export interface NormalizedEvent {
136
+ eventRaw: any;
137
+ eventType: string;
138
+ payload: {
139
+ body?: Record<string, any>;
140
+ pathParameters?: Record<string, string>;
141
+ queryStringParameters?: Record<string, string>;
142
+ headers?: Record<string, string>;
143
+ };
144
+ params: Record<string, string>;
145
+ context: {
146
+ segment: RouteSegment;
147
+ identity?: IdentityContext;
148
+ requestId?: string;
149
+ };
150
+ }
151
+
152
+ /**
153
+ * Orchestrator configuration options
154
+ */
155
+ export interface OrchestratorConfig {
156
+ /**
157
+ * Enable verbose logging for debugging
158
+ */
159
+ debug?: boolean;
160
+
161
+ /**
162
+ * User Pool ID mappings for segment-based validation
163
+ */
164
+ userPools?: {
165
+ [K in RouteSegment]?: string;
166
+ };
167
+
168
+ /**
169
+ * Global middleware applied to all routes
170
+ */
171
+ globalMiddleware?: MiddlewareFn[];
172
+
173
+ /**
174
+ * Custom response handlers
175
+ */
176
+ responses?: {
177
+ notFound?: () => any;
178
+ forbidden?: () => any;
179
+ badRequest?: (message?: string) => any;
180
+ internalError?: (message?: string) => any;
181
+ };
182
+ }
@@ -0,0 +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
+ }
@@ -0,0 +1,2 @@
1
+ export * from './path-matcher.js';
2
+ export * from './headers.js';