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,102 +1,102 @@
1
- import {
2
- matchPath,
3
- patternToRegex,
4
- hasPathParameters,
5
- normalizePath
6
- } from '../src/utils/path-matcher';
7
-
8
- describe('patternToRegex', () => {
9
- it('should convert simple pattern without parameters', () => {
10
- const { regex, paramNames } = patternToRegex('/users');
11
- expect(paramNames).toEqual([]);
12
- expect(regex.test('/users')).toBe(true);
13
- expect(regex.test('/users/')).toBe(false);
14
- expect(regex.test('/other')).toBe(false);
15
- });
16
-
17
- it('should extract single parameter name', () => {
18
- const { regex, paramNames } = patternToRegex('/users/{id}');
19
- expect(paramNames).toEqual(['id']);
20
- expect(regex.test('/users/123')).toBe(true);
21
- expect(regex.test('/users/abc-def')).toBe(true);
22
- expect(regex.test('/users/')).toBe(false);
23
- });
24
-
25
- it('should extract multiple parameter names', () => {
26
- const { regex, paramNames } = patternToRegex('/users/{userId}/posts/{postId}');
27
- expect(paramNames).toEqual(['userId', 'postId']);
28
- expect(regex.test('/users/123/posts/456')).toBe(true);
29
- });
30
-
31
- it('should escape special regex characters', () => {
32
- const { regex } = patternToRegex('/api/v1.0/users');
33
- expect(regex.test('/api/v1.0/users')).toBe(true);
34
- expect(regex.test('/api/v1X0/users')).toBe(false);
35
- });
36
- });
37
-
38
- describe('matchPath', () => {
39
- it('should return null for non-matching paths', () => {
40
- expect(matchPath('/users', '/posts')).toBeNull();
41
- expect(matchPath('/users/{id}', '/posts/123')).toBeNull();
42
- });
43
-
44
- it('should return empty params for exact match without parameters', () => {
45
- const result = matchPath('/users', '/users');
46
- expect(result).toEqual({});
47
- });
48
-
49
- it('should extract single path parameter', () => {
50
- const result = matchPath('/users/{id}', '/users/123');
51
- expect(result).toEqual({ id: '123' });
52
- });
53
-
54
- it('should extract multiple path parameters', () => {
55
- const result = matchPath('/users/{userId}/posts/{postId}', '/users/abc/posts/xyz');
56
- expect(result).toEqual({ userId: 'abc', postId: 'xyz' });
57
- });
58
-
59
- it('should handle parameters with special characters', () => {
60
- const result = matchPath('/users/{id}', '/users/user-123_abc');
61
- expect(result).toEqual({ id: 'user-123_abc' });
62
- });
63
-
64
- it('should not match partial paths', () => {
65
- expect(matchPath('/users/{id}', '/users/123/extra')).toBeNull();
66
- expect(matchPath('/users/{id}/posts', '/users/123')).toBeNull();
67
- });
68
- });
69
-
70
- describe('hasPathParameters', () => {
71
- it('should return true for patterns with parameters', () => {
72
- expect(hasPathParameters('/users/{id}')).toBe(true);
73
- expect(hasPathParameters('/users/{userId}/posts/{postId}')).toBe(true);
74
- });
75
-
76
- it('should return false for patterns without parameters', () => {
77
- expect(hasPathParameters('/users')).toBe(false);
78
- expect(hasPathParameters('/users/list')).toBe(false);
79
- });
80
- });
81
-
82
- describe('normalizePath', () => {
83
- it('should add leading slash if missing', () => {
84
- expect(normalizePath('users')).toBe('/users');
85
- });
86
-
87
- it('should remove trailing slash', () => {
88
- expect(normalizePath('/users/')).toBe('/users');
89
- });
90
-
91
- it('should keep root path as is', () => {
92
- expect(normalizePath('/')).toBe('/');
93
- });
94
-
95
- it('should handle empty string', () => {
96
- expect(normalizePath('')).toBe('/');
97
- });
98
-
99
- it('should normalize complex paths', () => {
100
- expect(normalizePath('users/123/')).toBe('/users/123');
101
- });
102
- });
1
+ import {
2
+ matchPath,
3
+ patternToRegex,
4
+ hasPathParameters,
5
+ normalizePath
6
+ } from '../src/utils/path-matcher';
7
+
8
+ describe('patternToRegex', () => {
9
+ it('should convert simple pattern without parameters', () => {
10
+ const { regex, paramNames } = patternToRegex('/users');
11
+ expect(paramNames).toEqual([]);
12
+ expect(regex.test('/users')).toBe(true);
13
+ expect(regex.test('/users/')).toBe(false);
14
+ expect(regex.test('/other')).toBe(false);
15
+ });
16
+
17
+ it('should extract single parameter name', () => {
18
+ const { regex, paramNames } = patternToRegex('/users/{id}');
19
+ expect(paramNames).toEqual(['id']);
20
+ expect(regex.test('/users/123')).toBe(true);
21
+ expect(regex.test('/users/abc-def')).toBe(true);
22
+ expect(regex.test('/users/')).toBe(false);
23
+ });
24
+
25
+ it('should extract multiple parameter names', () => {
26
+ const { regex, paramNames } = patternToRegex('/users/{userId}/posts/{postId}');
27
+ expect(paramNames).toEqual(['userId', 'postId']);
28
+ expect(regex.test('/users/123/posts/456')).toBe(true);
29
+ });
30
+
31
+ it('should escape special regex characters', () => {
32
+ const { regex } = patternToRegex('/api/v1.0/users');
33
+ expect(regex.test('/api/v1.0/users')).toBe(true);
34
+ expect(regex.test('/api/v1X0/users')).toBe(false);
35
+ });
36
+ });
37
+
38
+ describe('matchPath', () => {
39
+ it('should return null for non-matching paths', () => {
40
+ expect(matchPath('/users', '/posts')).toBeNull();
41
+ expect(matchPath('/users/{id}', '/posts/123')).toBeNull();
42
+ });
43
+
44
+ it('should return empty params for exact match without parameters', () => {
45
+ const result = matchPath('/users', '/users');
46
+ expect(result).toEqual({});
47
+ });
48
+
49
+ it('should extract single path parameter', () => {
50
+ const result = matchPath('/users/{id}', '/users/123');
51
+ expect(result).toEqual({ id: '123' });
52
+ });
53
+
54
+ it('should extract multiple path parameters', () => {
55
+ const result = matchPath('/users/{userId}/posts/{postId}', '/users/abc/posts/xyz');
56
+ expect(result).toEqual({ userId: 'abc', postId: 'xyz' });
57
+ });
58
+
59
+ it('should handle parameters with special characters', () => {
60
+ const result = matchPath('/users/{id}', '/users/user-123_abc');
61
+ expect(result).toEqual({ id: 'user-123_abc' });
62
+ });
63
+
64
+ it('should not match partial paths', () => {
65
+ expect(matchPath('/users/{id}', '/users/123/extra')).toBeNull();
66
+ expect(matchPath('/users/{id}/posts', '/users/123')).toBeNull();
67
+ });
68
+ });
69
+
70
+ describe('hasPathParameters', () => {
71
+ it('should return true for patterns with parameters', () => {
72
+ expect(hasPathParameters('/users/{id}')).toBe(true);
73
+ expect(hasPathParameters('/users/{userId}/posts/{postId}')).toBe(true);
74
+ });
75
+
76
+ it('should return false for patterns without parameters', () => {
77
+ expect(hasPathParameters('/users')).toBe(false);
78
+ expect(hasPathParameters('/users/list')).toBe(false);
79
+ });
80
+ });
81
+
82
+ describe('normalizePath', () => {
83
+ it('should add leading slash if missing', () => {
84
+ expect(normalizePath('users')).toBe('/users');
85
+ });
86
+
87
+ it('should remove trailing slash', () => {
88
+ expect(normalizePath('/users/')).toBe('/users');
89
+ });
90
+
91
+ it('should keep root path as is', () => {
92
+ expect(normalizePath('/')).toBe('/');
93
+ });
94
+
95
+ it('should handle empty string', () => {
96
+ expect(normalizePath('')).toBe('/');
97
+ });
98
+
99
+ it('should normalize complex paths', () => {
100
+ expect(normalizePath('users/123/')).toBe('/users/123');
101
+ });
102
+ });
@@ -1,155 +1,155 @@
1
- import {
2
- HttpStatus,
3
- DefaultResponseCode,
4
- createStandardResponse,
5
- successResponse,
6
- createdResponse,
7
- badRequestResponse,
8
- unauthorizedResponse,
9
- forbiddenResponse,
10
- notFoundResponse,
11
- conflictResponse,
12
- validationErrorResponse,
13
- internalErrorResponse,
14
- customErrorResponse
15
- } from '../src/http/response';
16
-
17
- describe('createStandardResponse', () => {
18
- it('should create response with data', () => {
19
- const response = createStandardResponse(200, { user: { id: 1 } });
20
- const body = JSON.parse(response.body);
21
-
22
- expect(response.statusCode).toBe(200);
23
- expect(body.status).toBe(200);
24
- expect(body.code).toBe(DefaultResponseCode.SUCCESS);
25
- expect(body.data).toEqual({ user: { id: 1 } });
26
- });
27
-
28
- it('should create response with custom code', () => {
29
- const response = createStandardResponse(200, null, 'CUSTOM_CODE');
30
- const body = JSON.parse(response.body);
31
-
32
- expect(body.code).toBe('CUSTOM_CODE');
33
- });
34
-
35
- it('should create response with message', () => {
36
- const response = createStandardResponse(400, undefined, undefined, 'Error message');
37
- const body = JSON.parse(response.body);
38
-
39
- expect(body.message).toBe('Error message');
40
- });
41
-
42
- it('should include custom headers', () => {
43
- const response = createStandardResponse(200, null, undefined, undefined, {
44
- 'X-Custom-Header': 'value'
45
- });
46
-
47
- expect(response.headers?.['X-Custom-Header']).toBe('value');
48
- expect(response.headers?.['Content-Type']).toBe('application/json');
49
- });
50
- });
51
-
52
- describe('successResponse', () => {
53
- it('should return 200 with data', () => {
54
- const response = successResponse({ items: [1, 2, 3] });
55
- const body = JSON.parse(response.body);
56
-
57
- expect(response.statusCode).toBe(200);
58
- expect(body.code).toBe(DefaultResponseCode.SUCCESS);
59
- expect(body.data).toEqual({ items: [1, 2, 3] });
60
- });
61
-
62
- it('should return 200 without data', () => {
63
- const response = successResponse();
64
- expect(response.statusCode).toBe(200);
65
- });
66
- });
67
-
68
- describe('createdResponse', () => {
69
- it('should return 201 with data', () => {
70
- const response = createdResponse({ id: 123 });
71
- const body = JSON.parse(response.body);
72
-
73
- expect(response.statusCode).toBe(201);
74
- expect(body.code).toBe(DefaultResponseCode.CREATED);
75
- expect(body.data).toEqual({ id: 123 });
76
- });
77
- });
78
-
79
- describe('error responses', () => {
80
- it('badRequestResponse should return 400', () => {
81
- const response = badRequestResponse('Invalid input');
82
- const body = JSON.parse(response.body);
83
-
84
- expect(response.statusCode).toBe(400);
85
- expect(body.code).toBe(DefaultResponseCode.BAD_REQUEST);
86
- expect(body.message).toBe('Invalid input');
87
- });
88
-
89
- it('unauthorizedResponse should return 401', () => {
90
- const response = unauthorizedResponse('Token expired');
91
-
92
- expect(response.statusCode).toBe(401);
93
- });
94
-
95
- it('forbiddenResponse should return 403', () => {
96
- const response = forbiddenResponse('Access denied');
97
-
98
- expect(response.statusCode).toBe(403);
99
- });
100
-
101
- it('notFoundResponse should return 404', () => {
102
- const response = notFoundResponse('User not found');
103
-
104
- expect(response.statusCode).toBe(404);
105
- });
106
-
107
- it('conflictResponse should return 409', () => {
108
- const response = conflictResponse('Email already exists');
109
-
110
- expect(response.statusCode).toBe(409);
111
- });
112
-
113
- it('validationErrorResponse should return 422', () => {
114
- const response = validationErrorResponse('Invalid email format');
115
-
116
- expect(response.statusCode).toBe(422);
117
- });
118
-
119
- it('internalErrorResponse should return 500', () => {
120
- const response = internalErrorResponse('Something went wrong');
121
-
122
- expect(response.statusCode).toBe(500);
123
- });
124
- });
125
-
126
- describe('customErrorResponse', () => {
127
- enum MyErrorCodes {
128
- USER_SUSPENDED = 'USER_SUSPENDED',
129
- QUOTA_EXCEEDED = 'QUOTA_EXCEEDED'
130
- }
131
-
132
- const codeToStatus: Record<MyErrorCodes, HttpStatus> = {
133
- [MyErrorCodes.USER_SUSPENDED]: HttpStatus.FORBIDDEN,
134
- [MyErrorCodes.QUOTA_EXCEEDED]: HttpStatus.UNPROCESSABLE_ENTITY
135
- };
136
-
137
- it('should map custom code to correct status', () => {
138
- const response = customErrorResponse(
139
- MyErrorCodes.USER_SUSPENDED,
140
- 'Account suspended',
141
- codeToStatus
142
- );
143
- const body = JSON.parse(response.body);
144
-
145
- expect(response.statusCode).toBe(403);
146
- expect(body.code).toBe('USER_SUSPENDED');
147
- expect(body.message).toBe('Account suspended');
148
- });
149
-
150
- it('should default to 500 for unmapped codes', () => {
151
- const response = customErrorResponse('UNKNOWN_CODE', 'Unknown error');
152
-
153
- expect(response.statusCode).toBe(500);
154
- });
155
- });
1
+ import {
2
+ HttpStatus,
3
+ DefaultResponseCode,
4
+ createStandardResponse,
5
+ successResponse,
6
+ createdResponse,
7
+ badRequestResponse,
8
+ unauthorizedResponse,
9
+ forbiddenResponse,
10
+ notFoundResponse,
11
+ conflictResponse,
12
+ validationErrorResponse,
13
+ internalErrorResponse,
14
+ customErrorResponse
15
+ } from '../src/http/response';
16
+
17
+ describe('createStandardResponse', () => {
18
+ it('should create response with data', () => {
19
+ const response = createStandardResponse(200, { user: { id: 1 } });
20
+ const body = JSON.parse(response.body);
21
+
22
+ expect(response.statusCode).toBe(200);
23
+ expect(body.status).toBe(200);
24
+ expect(body.code).toBe(DefaultResponseCode.SUCCESS);
25
+ expect(body.data).toEqual({ user: { id: 1 } });
26
+ });
27
+
28
+ it('should create response with custom code', () => {
29
+ const response = createStandardResponse(200, null, 'CUSTOM_CODE');
30
+ const body = JSON.parse(response.body);
31
+
32
+ expect(body.code).toBe('CUSTOM_CODE');
33
+ });
34
+
35
+ it('should create response with message', () => {
36
+ const response = createStandardResponse(400, undefined, undefined, 'Error message');
37
+ const body = JSON.parse(response.body);
38
+
39
+ expect(body.message).toBe('Error message');
40
+ });
41
+
42
+ it('should include custom headers', () => {
43
+ const response = createStandardResponse(200, null, undefined, undefined, {
44
+ 'X-Custom-Header': 'value'
45
+ });
46
+
47
+ expect(response.headers?.['X-Custom-Header']).toBe('value');
48
+ expect(response.headers?.['Content-Type']).toBe('application/json');
49
+ });
50
+ });
51
+
52
+ describe('successResponse', () => {
53
+ it('should return 200 with data', () => {
54
+ const response = successResponse({ items: [1, 2, 3] });
55
+ const body = JSON.parse(response.body);
56
+
57
+ expect(response.statusCode).toBe(200);
58
+ expect(body.code).toBe(DefaultResponseCode.SUCCESS);
59
+ expect(body.data).toEqual({ items: [1, 2, 3] });
60
+ });
61
+
62
+ it('should return 200 without data', () => {
63
+ const response = successResponse();
64
+ expect(response.statusCode).toBe(200);
65
+ });
66
+ });
67
+
68
+ describe('createdResponse', () => {
69
+ it('should return 201 with data', () => {
70
+ const response = createdResponse({ id: 123 });
71
+ const body = JSON.parse(response.body);
72
+
73
+ expect(response.statusCode).toBe(201);
74
+ expect(body.code).toBe(DefaultResponseCode.CREATED);
75
+ expect(body.data).toEqual({ id: 123 });
76
+ });
77
+ });
78
+
79
+ describe('error responses', () => {
80
+ it('badRequestResponse should return 400', () => {
81
+ const response = badRequestResponse('Invalid input');
82
+ const body = JSON.parse(response.body);
83
+
84
+ expect(response.statusCode).toBe(400);
85
+ expect(body.code).toBe(DefaultResponseCode.BAD_REQUEST);
86
+ expect(body.message).toBe('Invalid input');
87
+ });
88
+
89
+ it('unauthorizedResponse should return 401', () => {
90
+ const response = unauthorizedResponse('Token expired');
91
+
92
+ expect(response.statusCode).toBe(401);
93
+ });
94
+
95
+ it('forbiddenResponse should return 403', () => {
96
+ const response = forbiddenResponse('Access denied');
97
+
98
+ expect(response.statusCode).toBe(403);
99
+ });
100
+
101
+ it('notFoundResponse should return 404', () => {
102
+ const response = notFoundResponse('User not found');
103
+
104
+ expect(response.statusCode).toBe(404);
105
+ });
106
+
107
+ it('conflictResponse should return 409', () => {
108
+ const response = conflictResponse('Email already exists');
109
+
110
+ expect(response.statusCode).toBe(409);
111
+ });
112
+
113
+ it('validationErrorResponse should return 422', () => {
114
+ const response = validationErrorResponse('Invalid email format');
115
+
116
+ expect(response.statusCode).toBe(422);
117
+ });
118
+
119
+ it('internalErrorResponse should return 500', () => {
120
+ const response = internalErrorResponse('Something went wrong');
121
+
122
+ expect(response.statusCode).toBe(500);
123
+ });
124
+ });
125
+
126
+ describe('customErrorResponse', () => {
127
+ enum MyErrorCodes {
128
+ USER_SUSPENDED = 'USER_SUSPENDED',
129
+ QUOTA_EXCEEDED = 'QUOTA_EXCEEDED'
130
+ }
131
+
132
+ const codeToStatus: Record<MyErrorCodes, HttpStatus> = {
133
+ [MyErrorCodes.USER_SUSPENDED]: HttpStatus.FORBIDDEN,
134
+ [MyErrorCodes.QUOTA_EXCEEDED]: HttpStatus.UNPROCESSABLE_ENTITY
135
+ };
136
+
137
+ it('should map custom code to correct status', () => {
138
+ const response = customErrorResponse(
139
+ MyErrorCodes.USER_SUSPENDED,
140
+ 'Account suspended',
141
+ codeToStatus
142
+ );
143
+ const body = JSON.parse(response.body);
144
+
145
+ expect(response.statusCode).toBe(403);
146
+ expect(body.code).toBe('USER_SUSPENDED');
147
+ expect(body.message).toBe('Account suspended');
148
+ });
149
+
150
+ it('should default to 500 for unmapped codes', () => {
151
+ const response = customErrorResponse('UNKNOWN_CODE', 'Unknown error');
152
+
153
+ expect(response.statusCode).toBe(500);
154
+ });
155
+ });