lambder 2.0.18 → 3.1.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 (69) hide show
  1. package/Readme.md +184 -41
  2. package/dist/Lambder.d.ts +154 -46
  3. package/dist/Lambder.js +312 -166
  4. package/dist/LambderCaller.js +6 -3
  5. package/dist/LambderContext.d.ts +20 -9
  6. package/dist/LambderContext.js +57 -17
  7. package/dist/LambderCors.d.ts +12 -0
  8. package/dist/LambderCors.js +30 -0
  9. package/dist/LambderDdbCache.d.ts +65 -0
  10. package/dist/LambderDdbCache.js +480 -0
  11. package/dist/LambderHtml.d.ts +33 -0
  12. package/dist/LambderHtml.js +62 -0
  13. package/dist/LambderMSW.d.ts +16 -1
  14. package/dist/LambderMSW.js +5 -9
  15. package/dist/LambderPublicFiles.d.ts +47 -0
  16. package/dist/LambderPublicFiles.js +108 -0
  17. package/dist/LambderResolver.d.ts +30 -31
  18. package/dist/LambderResolver.js +29 -43
  19. package/dist/LambderResponse.d.ts +71 -0
  20. package/dist/LambderResponse.js +196 -0
  21. package/dist/LambderResponseBuilder.d.ts +58 -33
  22. package/dist/LambderResponseBuilder.js +114 -167
  23. package/dist/LambderRouting.d.ts +23 -0
  24. package/dist/LambderRouting.js +67 -0
  25. package/dist/LambderSessionController.d.ts +13 -1
  26. package/dist/LambderSessionController.js +33 -10
  27. package/dist/LambderSessionManager.d.ts +3 -1
  28. package/dist/LambderSessionManager.js +15 -6
  29. package/dist/LambderTemplatingEngine.d.ts +87 -0
  30. package/dist/LambderTemplatingEngine.js +156 -0
  31. package/dist/index.d.ts +16 -2
  32. package/dist/index.js +12 -1
  33. package/dist/node-polyfills.d.ts +4 -2
  34. package/dist/node-polyfills.js +28 -0
  35. package/package.json +8 -5
  36. package/.eslintrc.cjs +0 -26
  37. package/.vscode/settings.json +0 -26
  38. package/deploy +0 -22
  39. package/dist/LambderUtils.d.ts +0 -10
  40. package/dist/LambderUtils.js +0 -70
  41. package/docs/DYNAMODB_SETUP.md +0 -96
  42. package/docs/LAMBDER_MSW.md +0 -409
  43. package/docs/TYPE_SAFE_QUICK_START.md +0 -77
  44. package/examples/msw-testing-example.ts +0 -280
  45. package/examples/secure-session-example.ts +0 -207
  46. package/examples/zod-chained-api-example.ts +0 -63
  47. package/src/Lambder.ts +0 -430
  48. package/src/LambderApiContract.ts +0 -20
  49. package/src/LambderCaller.ts +0 -238
  50. package/src/LambderContext.ts +0 -78
  51. package/src/LambderMSW.ts +0 -180
  52. package/src/LambderResolver.ts +0 -101
  53. package/src/LambderResponseBuilder.ts +0 -332
  54. package/src/LambderSessionController.ts +0 -114
  55. package/src/LambderSessionManager.ts +0 -217
  56. package/src/LambderUtils.ts +0 -75
  57. package/src/index.ts +0 -17
  58. package/src/node-polyfills.ts +0 -27
  59. package/tests/error-handling.test.ts +0 -585
  60. package/tests/file-serving.test.ts +0 -194
  61. package/tests/fixtures/public/index.html +0 -1
  62. package/tests/fixtures/public/main.css +0 -1
  63. package/tests/hooks.test.ts +0 -561
  64. package/tests/output-type-runtime.test.ts +0 -381
  65. package/tests/redirect.test.ts +0 -88
  66. package/tests/routes.test.ts +0 -543
  67. package/tests/session.test.ts +0 -1083
  68. package/tests/use-plugin.test.ts +0 -460
  69. package/tsconfig.json +0 -24
@@ -1,381 +0,0 @@
1
- /**
2
- * Output Type Enforcement Runtime Tests
3
- *
4
- * These tests verify that the typed APIs work correctly at runtime
5
- * while maintaining type safety at compile time.
6
- */
7
-
8
- import { describe, it, expect } from 'vitest';
9
- import { APIGatewayProxyEvent, Context } from 'aws-lambda';
10
- import { z } from 'zod';
11
- import Lambder from '../src/Lambder.js';
12
-
13
- // Mock AWS Lambda event and context
14
- const createMockEvent = (apiName: string, payload: any): APIGatewayProxyEvent => ({
15
- body: JSON.stringify({ apiName, payload }),
16
- headers: { Host: 'localhost' },
17
- multiValueHeaders: {},
18
- httpMethod: 'POST',
19
- isBase64Encoded: false,
20
- path: '/api',
21
- pathParameters: null,
22
- queryStringParameters: null,
23
- multiValueQueryStringParameters: null,
24
- stageVariables: null,
25
- requestContext: {} as any,
26
- resource: '',
27
- });
28
-
29
- const createMockContext = (): Context => ({
30
- callbackWaitsForEmptyEventLoop: false,
31
- functionName: 'test',
32
- functionVersion: '1',
33
- invokedFunctionArn: 'arn',
34
- memoryLimitInMB: '128',
35
- awsRequestId: '123',
36
- logGroupName: 'group',
37
- logStreamName: 'stream',
38
- getRemainingTimeInMillis: () => 1000,
39
- done: () => {},
40
- fail: () => {},
41
- succeed: () => {},
42
- });
43
-
44
- // ============================================================================
45
- // Runtime Tests
46
- // ============================================================================
47
-
48
- describe('Output Type Enforcement - Runtime', () => {
49
- it('should return correct primitive types', async () => {
50
- const lambder = new Lambder({
51
- publicPath: './public',
52
- apiPath: '/api',
53
- })
54
- .addApi('add', {
55
- input: z.object({ a: z.number(), b: z.number() }),
56
- output: z.number()
57
- }, async (ctx, resolver) => {
58
- const { a, b } = ctx.apiPayload;
59
- const sum = a + b;
60
- return resolver.api(sum);
61
- });
62
-
63
- const event = createMockEvent('add', { a: 5, b: 3 });
64
- const context = createMockContext();
65
- const response = await lambder.render(event, context);
66
-
67
- expect(response.statusCode).toBe(200);
68
- const body = JSON.parse(response.body || '{}');
69
- expect(body.payload).toBe(8);
70
- });
71
-
72
- it('should return correct object types', async () => {
73
- const lambder = new Lambder({
74
- publicPath: './public',
75
- apiPath: '/api',
76
- })
77
- .addApi('getUser', {
78
- input: z.object({ userId: z.string() }),
79
- output: z.object({ id: z.string(), name: z.string(), age: z.number() })
80
- }, async (ctx, resolver) => {
81
- const userId = ctx.apiPayload.userId;
82
- return resolver.api({
83
- id: userId,
84
- name: 'John Doe',
85
- age: 30
86
- });
87
- });
88
-
89
- const event = createMockEvent('getUser', { userId: '123' });
90
- const context = createMockContext();
91
- const response = await lambder.render(event, context);
92
-
93
- expect(response.statusCode).toBe(200);
94
- const body = JSON.parse(response.body || '{}');
95
-
96
- expect(body.payload).toEqual({
97
- id: '123',
98
- name: 'John Doe',
99
- age: 30
100
- });
101
- });
102
-
103
- it('should return correct array types', async () => {
104
- const lambder = new Lambder({
105
- publicPath: './public',
106
- apiPath: '/api',
107
- })
108
- .addApi('listUsers', {
109
- input: z.void(),
110
- output: z.array(z.object({ id: z.string(), name: z.string() }))
111
- }, async (ctx, resolver) => {
112
- return resolver.api([
113
- { id: '1', name: 'Alice' },
114
- { id: '2', name: 'Bob' }
115
- ]);
116
- });
117
-
118
- const event = createMockEvent('listUsers', undefined);
119
- const context = createMockContext();
120
- const response = await lambder.render(event, context);
121
-
122
- expect(response.statusCode).toBe(200);
123
- const body = JSON.parse(response.body || '{}');
124
- expect(body.payload).toEqual([
125
- { id: '1', name: 'Alice' },
126
- { id: '2', name: 'Bob' }
127
- ]);
128
- });
129
-
130
- it('should handle null returns correctly', async () => {
131
- const lambder = new Lambder({
132
- publicPath: './public',
133
- apiPath: '/api',
134
- })
135
- .addApi('findUser', {
136
- input: z.object({ email: z.string() }),
137
- output: z.object({ id: z.string(), name: z.string() }).nullable()
138
- }, async (ctx, resolver) => {
139
- const email = ctx.apiPayload.email;
140
- if (email === 'notfound@example.com') {
141
- return resolver.api(null);
142
- }
143
- return resolver.api({ id: '1', name: 'Found User' });
144
- });
145
-
146
- // Test null case
147
- const event1 = createMockEvent('findUser', { email: 'notfound@example.com' });
148
- const context1 = createMockContext();
149
- const response1 = await lambder.render(event1, context1);
150
-
151
- expect(response1.statusCode).toBe(200);
152
- const body1 = JSON.parse(response1.body || '{}');
153
- expect(body1.payload).toBeNull();
154
-
155
- // Test found case
156
- const event2 = createMockEvent('findUser', { email: 'found@example.com' });
157
- const context2 = createMockContext();
158
- const response2 = await lambder.render(event2, context2);
159
-
160
- expect(response2.statusCode).toBe(200);
161
- const body2 = JSON.parse(response2.body || '{}');
162
- expect(body2.payload).toEqual({ id: '1', name: 'Found User' });
163
- });
164
-
165
- it('should return boolean types correctly', async () => {
166
- const lambder = new Lambder({
167
- publicPath: './public',
168
- apiPath: '/api',
169
- })
170
- .addApi('deleteUser', {
171
- input: z.object({ userId: z.string() }),
172
- output: z.boolean()
173
- }, async (ctx, resolver) => {
174
- const userId = ctx.apiPayload.userId;
175
- // Mock deletion
176
- const success = userId !== '';
177
- return resolver.api(success);
178
- });
179
-
180
- const event = createMockEvent('deleteUser', { userId: '123' });
181
- const context = createMockContext();
182
- const response = await lambder.render(event, context);
183
-
184
- expect(response.statusCode).toBe(200);
185
- const body = JSON.parse(response.body || '{}');
186
- expect(body.payload).toBe(true);
187
- });
188
-
189
- it('should work with die.api()', async () => {
190
- const lambder = new Lambder({
191
- publicPath: './public',
192
- apiPath: '/api',
193
- })
194
- .addApi('echo', {
195
- input: z.object({ message: z.string() }),
196
- output: z.object({ echo: z.string() })
197
- }, async (ctx, resolver) => {
198
- return resolver.die.api({ echo: ctx.apiPayload.message });
199
- });
200
-
201
- const event = createMockEvent('echo', { message: 'Hello!' });
202
- const context = createMockContext();
203
- const response = await lambder.render(event, context);
204
-
205
- expect(response.statusCode).toBe(200);
206
- const body = JSON.parse(response.body || '{}');
207
- expect(body.payload).toEqual({ echo: 'Hello!' });
208
- });
209
-
210
- it('should work with session APIs', async () => {
211
- const lambder = new Lambder({
212
- publicPath: './public',
213
- apiPath: '/api',
214
- })
215
- .addSessionApi('getUser', {
216
- input: z.object({ userId: z.string() }),
217
- output: z.object({ id: z.string(), name: z.string(), age: z.number() })
218
- }, async (ctx, resolver) => {
219
- return resolver.api({
220
- id: ctx.apiPayload.userId,
221
- name: 'Session User',
222
- age: 25
223
- });
224
- });
225
-
226
- // This will return 500 error because session is not configured
227
- const event = createMockEvent('getUser', { userId: '123' });
228
- const context = createMockContext();
229
-
230
- const response = await lambder.render(event, context);
231
-
232
- // Should return 500 error when session is not configured
233
- expect(response.statusCode).toBe(500);
234
- expect(response.body).toBe('Internal Server Error.');
235
- });
236
- });
237
-
238
- // ============================================================================
239
- // Input Type Tests
240
- // ============================================================================
241
-
242
- describe('Input Type Enforcement - Runtime', () => {
243
- it('should receive correctly typed input', async () => {
244
- const lambder = new Lambder({
245
- publicPath: './public',
246
- apiPath: '/api',
247
- })
248
- .addApi('echo', {
249
- input: z.object({ message: z.string() }),
250
- output: z.object({ echo: z.string() })
251
- }, async (ctx, resolver) => {
252
- // Verify input is correctly typed at runtime
253
- const message: string = ctx.apiPayload.message;
254
- expect(typeof message).toBe('string');
255
- return resolver.api({ echo: message });
256
- });
257
-
258
- const event = createMockEvent('echo', { message: 'Test Message' });
259
- const context = createMockContext();
260
- await lambder.render(event, context);
261
- });
262
-
263
- it('should handle void input correctly', async () => {
264
- const lambder = new Lambder({
265
- publicPath: './public',
266
- apiPath: '/api',
267
- })
268
- .addApi('listUsers', {
269
- input: z.void(),
270
- output: z.array(z.any())
271
- }, async (ctx, resolver) => {
272
- // apiPayload should be undefined for void input
273
- expect(ctx.apiPayload).toBeUndefined();
274
- return resolver.api([]);
275
- });
276
-
277
- const event = createMockEvent('listUsers', undefined);
278
- const context = createMockContext();
279
- await lambder.render(event, context);
280
- });
281
-
282
- it('should handle complex input objects', async () => {
283
- const lambder = new Lambder({
284
- publicPath: './public',
285
- apiPath: '/api',
286
- })
287
- .addApi('add', {
288
- input: z.object({ a: z.number(), b: z.number() }),
289
- output: z.number()
290
- }, async (ctx, resolver) => {
291
- const { a, b } = ctx.apiPayload;
292
- expect(typeof a).toBe('number');
293
- expect(typeof b).toBe('number');
294
- return resolver.api(a + b);
295
- });
296
-
297
- const event = createMockEvent('add', { a: 10, b: 20 });
298
- const context = createMockContext();
299
- const response = await lambder.render(event, context);
300
-
301
- const body = JSON.parse(response.body || '{}');
302
- expect(body.payload).toBe(30);
303
- });
304
- });
305
-
306
- // ============================================================================
307
- // Edge Cases
308
- // ============================================================================
309
-
310
- describe('Edge Cases', () => {
311
- it('should handle empty arrays', async () => {
312
- const lambder = new Lambder({
313
- publicPath: './public',
314
- apiPath: '/api',
315
- })
316
- .addApi('listUsers', {
317
- input: z.void(),
318
- output: z.array(z.any())
319
- }, async (ctx, resolver) => {
320
- return resolver.api([]);
321
- });
322
-
323
- const event = createMockEvent('listUsers', undefined);
324
- const context = createMockContext();
325
- const response = await lambder.render(event, context);
326
-
327
- expect(response.statusCode).toBe(200);
328
- const body = JSON.parse(response.body || '{}');
329
- expect(body.payload).toEqual([]);
330
- });
331
-
332
- it('should handle zero as a valid number', async () => {
333
- const lambder = new Lambder({
334
- publicPath: './public',
335
- apiPath: '/api',
336
- })
337
- .addApi('add', {
338
- input: z.object({ a: z.number(), b: z.number() }),
339
- output: z.number()
340
- }, async (ctx, resolver) => {
341
- return resolver.api(0);
342
- });
343
-
344
- const event = createMockEvent('add', { a: 0, b: 0 });
345
- const context = createMockContext();
346
- const response = await lambder.render(event, context);
347
-
348
- expect(response.statusCode).toBe(200);
349
- const body = JSON.parse(response.body || '{}');
350
- expect(body.payload).toBe(0);
351
- });
352
-
353
- it('should handle empty strings in objects', async () => {
354
- const lambder = new Lambder({
355
- publicPath: './public',
356
- apiPath: '/api',
357
- })
358
- .addApi('getUser', {
359
- input: z.object({ userId: z.string() }),
360
- output: z.object({ id: z.string(), name: z.string(), age: z.number() })
361
- }, async (ctx, resolver) => {
362
- return resolver.api({
363
- id: '',
364
- name: '',
365
- age: 0
366
- });
367
- });
368
-
369
- const event = createMockEvent('getUser', { userId: '' });
370
- const context = createMockContext();
371
- const response = await lambder.render(event, context);
372
-
373
- expect(response.statusCode).toBe(200);
374
- const body = JSON.parse(response.body || '{}');
375
- expect(body.payload).toEqual({
376
- id: '',
377
- name: '',
378
- age: 0
379
- });
380
- });
381
- });
@@ -1,88 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import Lambder from '../src/Lambder.js';
3
- import type { APIGatewayProxyEvent, Context } from 'aws-lambda';
4
-
5
- const createMockEvent = (path: string, method: string = 'GET'): APIGatewayProxyEvent => ({
6
- body: null,
7
- headers: {
8
- Host: 'localhost',
9
- },
10
- multiValueHeaders: {},
11
- httpMethod: method,
12
- isBase64Encoded: false,
13
- path,
14
- pathParameters: null,
15
- queryStringParameters: null,
16
- multiValueQueryStringParameters: null,
17
- stageVariables: null,
18
- requestContext: {} as any,
19
- resource: '',
20
- });
21
-
22
- const createMockContext = (): Context => ({
23
- callbackWaitsForEmptyEventLoop: false,
24
- functionName: 'test',
25
- functionVersion: '1',
26
- invokedFunctionArn: 'arn',
27
- memoryLimitInMB: '128',
28
- awsRequestId: '123',
29
- logGroupName: 'group',
30
- logStreamName: 'stream',
31
- getRemainingTimeInMillis: () => 1000,
32
- done: () => {},
33
- fail: () => {},
34
- succeed: () => {},
35
- });
36
-
37
- describe('Redirect Response', () => {
38
- it('should redirect with default status code 302', async () => {
39
- const lambder = new Lambder({
40
- publicPath: './public',
41
- apiPath: '/api'
42
- })
43
- .addRoute('/old-path', (ctx, res) => {
44
- return res.redirect('/new-path');
45
- });
46
-
47
- const handler = lambder.getHandler();
48
- const event = createMockEvent('/old-path');
49
- const result = await handler(event, createMockContext());
50
-
51
- expect(result.statusCode).toBe(302);
52
- expect(result.multiValueHeaders?.Location).toEqual(['/new-path']);
53
- });
54
-
55
- it('should redirect with custom status code', async () => {
56
- const lambder = new Lambder({
57
- publicPath: './public',
58
- apiPath: '/api'
59
- })
60
- .addRoute('/moved-permanently', (ctx, res) => {
61
- return res.redirect('/new-location', 301);
62
- });
63
-
64
- const handler = lambder.getHandler();
65
- const event = createMockEvent('/moved-permanently');
66
- const result = await handler(event, createMockContext());
67
-
68
- expect(result.statusCode).toBe(301);
69
- expect(result.multiValueHeaders?.Location).toEqual(['/new-location']);
70
- });
71
-
72
- it('should redirect using die.redirect', async () => {
73
- const lambder = new Lambder({
74
- publicPath: './public',
75
- apiPath: '/api'
76
- })
77
- .addRoute('/die-redirect', (ctx, res) => {
78
- return res.die.redirect('/somewhere-else');
79
- });
80
-
81
- const handler = lambder.getHandler();
82
- const event = createMockEvent('/die-redirect');
83
- const result = await handler(event, createMockContext());
84
-
85
- expect(result.statusCode).toBe(302);
86
- expect(result.multiValueHeaders?.Location).toEqual(['/somewhere-else']);
87
- });
88
- });