lambder 2.0.16 → 3.0.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 (67) hide show
  1. package/Readme.md +162 -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/LambderHtml.d.ts +33 -0
  10. package/dist/LambderHtml.js +62 -0
  11. package/dist/LambderMSW.d.ts +16 -1
  12. package/dist/LambderMSW.js +5 -9
  13. package/dist/LambderPublicFiles.d.ts +47 -0
  14. package/dist/LambderPublicFiles.js +108 -0
  15. package/dist/LambderResolver.d.ts +30 -31
  16. package/dist/LambderResolver.js +29 -43
  17. package/dist/LambderResponse.d.ts +71 -0
  18. package/dist/LambderResponse.js +196 -0
  19. package/dist/LambderResponseBuilder.d.ts +58 -33
  20. package/dist/LambderResponseBuilder.js +114 -167
  21. package/dist/LambderRouting.d.ts +23 -0
  22. package/dist/LambderRouting.js +67 -0
  23. package/dist/LambderSessionController.d.ts +13 -1
  24. package/dist/LambderSessionController.js +33 -10
  25. package/dist/LambderSessionManager.d.ts +3 -1
  26. package/dist/LambderSessionManager.js +15 -6
  27. package/dist/LambderTemplatingEngine.d.ts +87 -0
  28. package/dist/LambderTemplatingEngine.js +156 -0
  29. package/dist/index.d.ts +14 -2
  30. package/dist/index.js +10 -1
  31. package/dist/node-polyfills.d.ts +4 -2
  32. package/dist/node-polyfills.js +28 -0
  33. package/package.json +7 -5
  34. package/.eslintrc.cjs +0 -26
  35. package/.vscode/settings.json +0 -26
  36. package/deploy +0 -22
  37. package/dist/LambderUtils.d.ts +0 -10
  38. package/dist/LambderUtils.js +0 -70
  39. package/docs/DYNAMODB_SETUP.md +0 -96
  40. package/docs/LAMBDER_MSW.md +0 -409
  41. package/docs/TYPE_SAFE_QUICK_START.md +0 -77
  42. package/examples/msw-testing-example.ts +0 -280
  43. package/examples/secure-session-example.ts +0 -207
  44. package/examples/zod-chained-api-example.ts +0 -63
  45. package/src/Lambder.ts +0 -430
  46. package/src/LambderApiContract.ts +0 -20
  47. package/src/LambderCaller.ts +0 -238
  48. package/src/LambderContext.ts +0 -78
  49. package/src/LambderMSW.ts +0 -180
  50. package/src/LambderResolver.ts +0 -101
  51. package/src/LambderResponseBuilder.ts +0 -332
  52. package/src/LambderSessionController.ts +0 -114
  53. package/src/LambderSessionManager.ts +0 -217
  54. package/src/LambderUtils.ts +0 -75
  55. package/src/index.ts +0 -17
  56. package/src/node-polyfills.ts +0 -27
  57. package/tests/error-handling.test.ts +0 -585
  58. package/tests/file-serving.test.ts +0 -194
  59. package/tests/fixtures/public/index.html +0 -1
  60. package/tests/fixtures/public/main.css +0 -1
  61. package/tests/hooks.test.ts +0 -561
  62. package/tests/output-type-runtime.test.ts +0 -381
  63. package/tests/redirect.test.ts +0 -88
  64. package/tests/routes.test.ts +0 -543
  65. package/tests/session.test.ts +0 -1083
  66. package/tests/use-plugin.test.ts +0 -460
  67. package/tsconfig.json +0 -24
@@ -1,460 +0,0 @@
1
- /**
2
- * Plugin System (.use) Tests
3
- *
4
- * This file tests the plugin system that allows modular API composition
5
- */
6
-
7
- import { describe, it, expect, expectTypeOf } from 'vitest';
8
- import { z } from 'zod';
9
- import Lambder from '../src/Lambder.js';
10
- import LambderCaller from '../src/LambderCaller.js';
11
- import { APIGatewayProxyEvent, Context } from 'aws-lambda';
12
-
13
- // Mock AWS Lambda event and context helpers
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
- // Test 1: Basic Plugin Usage
46
- // ============================================================================
47
-
48
- describe('Plugin System - Basic Usage', () => {
49
- it('should allow adding APIs via plugin', async () => {
50
- // Define a simple plugin
51
- const userPlugin = <T>(lambder: Lambder<T>) => {
52
- return lambder
53
- .addApi('getUser', {
54
- input: z.object({ userId: z.string() }),
55
- output: z.object({ id: z.string(), name: z.string() })
56
- }, async (ctx, res) => {
57
- return res.api({ id: ctx.apiPayload.userId, name: 'John Doe' });
58
- });
59
- };
60
-
61
- // Use the plugin
62
- const lambder = new Lambder({
63
- publicPath: './public',
64
- apiPath: '/api'
65
- }).use(userPlugin);
66
-
67
- // Test runtime execution
68
- const handler = lambder.getHandler();
69
- const event = createMockEvent('getUser', { userId: '123' });
70
- const result = await handler(event, createMockContext());
71
-
72
- expect(result.statusCode).toBe(200);
73
- const body = JSON.parse(result.body || '{}');
74
- expect(body.payload).toEqual({ id: '123', name: 'John Doe' });
75
- });
76
-
77
- it('should preserve type contract after using plugin', () => {
78
- const userPlugin = <T>(lambder: Lambder<T>) => {
79
- return lambder
80
- .addApi('getUser', {
81
- input: z.object({ userId: z.string() }),
82
- output: z.object({ id: z.string(), name: z.string() })
83
- }, async (ctx, res) => {
84
- return res.api({ id: ctx.apiPayload.userId, name: 'Test' });
85
- });
86
- };
87
-
88
- const lambder = new Lambder({
89
- publicPath: './public',
90
- apiPath: '/api'
91
- }).use(userPlugin);
92
-
93
- type AppContract = typeof lambder.ApiContract;
94
-
95
- const caller = new LambderCaller<AppContract>({
96
- apiPath: '/api',
97
- isCorsEnabled: false
98
- });
99
-
100
- // Type check - if this compiles, types are correct
101
- type GetUserInput = Parameters<typeof caller.api<'getUser'>>[1];
102
-
103
- expect(caller).toBeDefined();
104
- });
105
- });
106
-
107
- // ============================================================================
108
- // Test 2: Multiple Plugins
109
- // ============================================================================
110
-
111
- describe('Plugin System - Multiple Plugins', () => {
112
- it('should allow chaining multiple plugins', async () => {
113
- const userPlugin = <T>(lambder: Lambder<T>) => {
114
- return lambder
115
- .addApi('getUser', {
116
- input: z.object({ userId: z.string() }),
117
- output: z.object({ id: z.string(), name: z.string() })
118
- }, async (ctx, res) => {
119
- return res.api({ id: ctx.apiPayload.userId, name: 'John' });
120
- });
121
- };
122
-
123
- const productPlugin = <T>(lambder: Lambder<T>) => {
124
- return lambder
125
- .addApi('getProduct', {
126
- input: z.object({ productId: z.string() }),
127
- output: z.object({ id: z.string(), title: z.string(), price: z.number() })
128
- }, async (ctx, res) => {
129
- return res.api({
130
- id: ctx.apiPayload.productId,
131
- title: 'Test Product',
132
- price: 99.99
133
- });
134
- });
135
- };
136
-
137
- const orderPlugin = <T>(lambder: Lambder<T>) => {
138
- return lambder
139
- .addApi('createOrder', {
140
- input: z.object({ userId: z.string(), productId: z.string() }),
141
- output: z.object({ orderId: z.string(), status: z.string() })
142
- }, async (ctx, res) => {
143
- return res.api({
144
- orderId: 'order-123',
145
- status: 'pending'
146
- });
147
- });
148
- };
149
-
150
- // Chain multiple plugins
151
- const lambder = new Lambder({
152
- publicPath: './public',
153
- apiPath: '/api'
154
- })
155
- .use(userPlugin)
156
- .use(productPlugin)
157
- .use(orderPlugin);
158
-
159
- // Test each API works
160
- const handler = lambder.getHandler();
161
-
162
- // Test user API
163
- const userEvent = createMockEvent('getUser', { userId: '123' });
164
- const userResult = await handler(userEvent, createMockContext());
165
- expect(userResult.statusCode).toBe(200);
166
- expect(JSON.parse(userResult.body || '{}').payload.name).toBe('John');
167
-
168
- // Test product API
169
- const productEvent = createMockEvent('getProduct', { productId: 'prod-456' });
170
- const productResult = await handler(productEvent, createMockContext());
171
- expect(productResult.statusCode).toBe(200);
172
- expect(JSON.parse(productResult.body || '{}').payload.title).toBe('Test Product');
173
-
174
- // Test order API
175
- const orderEvent = createMockEvent('createOrder', { userId: '123', productId: 'prod-456' });
176
- const orderResult = await handler(orderEvent, createMockContext());
177
- expect(orderResult.statusCode).toBe(200);
178
- expect(JSON.parse(orderResult.body || '{}').payload.orderId).toBe('order-123');
179
- });
180
-
181
- it('should accumulate types from multiple plugins', () => {
182
- const userPlugin = <T>(lambder: Lambder<T>) => {
183
- return lambder
184
- .addApi('getUser', {
185
- input: z.object({ userId: z.string() }),
186
- output: z.object({ id: z.string(), name: z.string() })
187
- }, async (ctx, res) => {
188
- return res.api({ id: ctx.apiPayload.userId, name: 'Test' });
189
- });
190
- };
191
-
192
- const productPlugin = <T>(lambder: Lambder<T>) => {
193
- return lambder
194
- .addApi('getProduct', {
195
- input: z.object({ productId: z.string() }),
196
- output: z.object({ id: z.string(), title: z.string() })
197
- }, async (ctx, res) => {
198
- return res.api({ id: ctx.apiPayload.productId, title: 'Test' });
199
- });
200
- };
201
-
202
- const lambder = new Lambder({
203
- publicPath: './public',
204
- apiPath: '/api'
205
- })
206
- .use(userPlugin)
207
- .use(productPlugin);
208
-
209
- type AppContract = typeof lambder.ApiContract;
210
-
211
- const caller = new LambderCaller<AppContract>({
212
- apiPath: '/api',
213
- isCorsEnabled: false
214
- });
215
-
216
- // Type check - both APIs should be available
217
- type GetUserInput = Parameters<typeof caller.api<'getUser'>>[1];
218
- type GetProductInput = Parameters<typeof caller.api<'getProduct'>>[1];
219
-
220
- expect(caller).toBeDefined();
221
- });
222
- });
223
-
224
- // ============================================================================
225
- // Test 3: Plugin with Additional APIs
226
- // ============================================================================
227
-
228
- describe('Plugin System - Mixed Usage', () => {
229
- it('should allow mixing direct API addition and plugins', async () => {
230
- const userPlugin = <T>(lambder: Lambder<T>) => {
231
- return lambder
232
- .addApi('getUser', {
233
- input: z.object({ userId: z.string() }),
234
- output: z.object({ id: z.string(), name: z.string() })
235
- }, async (ctx, res) => {
236
- return res.api({ id: ctx.apiPayload.userId, name: 'John' });
237
- });
238
- };
239
-
240
- const lambder = new Lambder({
241
- publicPath: './public',
242
- apiPath: '/api'
243
- })
244
- // Direct API
245
- .addApi('healthCheck', {
246
- input: z.void(),
247
- output: z.object({ status: z.string() })
248
- }, async (ctx, res) => {
249
- return res.api({ status: 'ok' });
250
- })
251
- // Plugin
252
- .use(userPlugin)
253
- // Another direct API
254
- .addApi('getVersion', {
255
- input: z.void(),
256
- output: z.object({ version: z.string() })
257
- }, async (ctx, res) => {
258
- return res.api({ version: '2.0' });
259
- });
260
-
261
- const handler = lambder.getHandler();
262
-
263
- // Test direct API before plugin
264
- const healthEvent = createMockEvent('healthCheck', undefined);
265
- const healthResult = await handler(healthEvent, createMockContext());
266
- expect(JSON.parse(healthResult.body || '{}').payload.status).toBe('ok');
267
-
268
- // Test plugin API
269
- const userEvent = createMockEvent('getUser', { userId: '123' });
270
- const userResult = await handler(userEvent, createMockContext());
271
- expect(JSON.parse(userResult.body || '{}').payload.name).toBe('John');
272
-
273
- // Test direct API after plugin
274
- const versionEvent = createMockEvent('getVersion', undefined);
275
- const versionResult = await handler(versionEvent, createMockContext());
276
- expect(JSON.parse(versionResult.body || '{}').payload.version).toBe('2.0');
277
- });
278
- });
279
-
280
- // ============================================================================
281
- // Test 4: Plugin with Routes
282
- // ============================================================================
283
-
284
- describe('Plugin System - Routes', () => {
285
- it('should allow plugins to add routes', async () => {
286
- const healthPlugin = <T>(lambder: Lambder<T>) => {
287
- // Now addRoute is chainable!
288
- return lambder
289
- .addRoute('/health', (ctx, res) => {
290
- return res.json({ status: 'healthy' });
291
- })
292
- .addRoute('/version', (ctx, res) => {
293
- return res.json({ version: '2.0' });
294
- });
295
- };
296
-
297
- const lambder = new Lambder({
298
- publicPath: './public',
299
- apiPath: '/api'
300
- }).use(healthPlugin);
301
-
302
- const handler = lambder.getHandler();
303
-
304
- // Create GET request event
305
- const healthEvent: APIGatewayProxyEvent = {
306
- ...createMockEvent('', {}),
307
- httpMethod: 'GET',
308
- path: '/health'
309
- };
310
-
311
- const result = await handler(healthEvent, createMockContext());
312
- expect(result.statusCode).toBe(200);
313
- expect(JSON.parse(result.body || '{}').status).toBe('healthy');
314
- });
315
- });
316
-
317
- // ============================================================================
318
- // Test 5: Complex Plugin Composition
319
- // ============================================================================
320
-
321
- describe('Plugin System - Complex Composition', () => {
322
- it('should support nested plugins (plugin that uses another plugin)', async () => {
323
- const basePlugin = <T>(lambder: Lambder<T>) => {
324
- return lambder
325
- .addApi('base', {
326
- input: z.void(),
327
- output: z.object({ value: z.string() })
328
- }, async (ctx, res) => {
329
- return res.api({ value: 'base' });
330
- });
331
- };
332
-
333
- const extendedPlugin = <T>(lambder: Lambder<T>) => {
334
- return lambder
335
- .use(basePlugin)
336
- .addApi('extended', {
337
- input: z.void(),
338
- output: z.object({ value: z.string() })
339
- }, async (ctx, res) => {
340
- return res.api({ value: 'extended' });
341
- });
342
- };
343
-
344
- const lambder = new Lambder({
345
- publicPath: './public',
346
- apiPath: '/api'
347
- }).use(extendedPlugin);
348
-
349
- const handler = lambder.getHandler();
350
-
351
- // Both base and extended APIs should work
352
- const baseEvent = createMockEvent('base', undefined);
353
- const baseResult = await handler(baseEvent, createMockContext());
354
- expect(JSON.parse(baseResult.body || '{}').payload.value).toBe('base');
355
-
356
- const extendedEvent = createMockEvent('extended', undefined);
357
- const extendedResult = await handler(extendedEvent, createMockContext());
358
- expect(JSON.parse(extendedResult.body || '{}').payload.value).toBe('extended');
359
- });
360
-
361
- it('should allow plugins to be reusable across different lambder instances', async () => {
362
- const sharedPlugin = <T>(lambder: Lambder<T>) => {
363
- return lambder
364
- .addApi('shared', {
365
- input: z.object({ id: z.string() }),
366
- output: z.object({ id: z.string(), source: z.string() })
367
- }, async (ctx, res) => {
368
- return res.api({ id: ctx.apiPayload.id, source: 'shared-plugin' });
369
- });
370
- };
371
-
372
- // Use same plugin in two different instances
373
- const lambder1 = new Lambder({
374
- publicPath: './public',
375
- apiPath: '/api'
376
- }).use(sharedPlugin);
377
-
378
- const lambder2 = new Lambder({
379
- publicPath: './public',
380
- apiPath: '/api'
381
- }).use(sharedPlugin);
382
-
383
- // Both should work independently
384
- const event = createMockEvent('shared', { id: 'test-123' });
385
-
386
- const result1 = await lambder1.getHandler()(event, createMockContext());
387
- const result2 = await lambder2.getHandler()(event, createMockContext());
388
-
389
- expect(JSON.parse(result1.body || '{}').payload.source).toBe('shared-plugin');
390
- expect(JSON.parse(result2.body || '{}').payload.source).toBe('shared-plugin');
391
- });
392
- });
393
-
394
- // ============================================================================
395
- // Test 6: Plugin Type Safety Edge Cases
396
- // ============================================================================
397
-
398
- describe('Plugin System - Type Safety', () => {
399
- it('should maintain type safety through plugin chain', () => {
400
- const plugin1 = <T>(lambder: Lambder<T>) => {
401
- return lambder
402
- .addApi('api1', {
403
- input: z.object({ value: z.string() }),
404
- output: z.object({ result: z.string() })
405
- }, async (ctx, res) => {
406
- return res.api({ result: ctx.apiPayload.value });
407
- });
408
- };
409
-
410
- const plugin2 = <T>(lambder: Lambder<T>) => {
411
- return lambder
412
- .addApi('api2', {
413
- input: z.object({ count: z.number() }),
414
- output: z.object({ total: z.number() })
415
- }, async (ctx, res) => {
416
- return res.api({ total: ctx.apiPayload.count * 2 });
417
- });
418
- };
419
-
420
- const lambder = new Lambder({
421
- publicPath: './public',
422
- apiPath: '/api'
423
- })
424
- .use(plugin1)
425
- .use(plugin2);
426
-
427
- type Contract = typeof lambder.ApiContract;
428
-
429
- // Type assertions - both api1 and api2 should be in the contract
430
- // We test this at runtime by creating a caller
431
- const caller = new LambderCaller<Contract>({
432
- apiPath: '/api',
433
- isCorsEnabled: false
434
- });
435
- expect(caller).toBeDefined();
436
- });
437
- });
438
-
439
- // ============================================================================
440
- // Test 7: Non-Generic Plugin Type Accumulation
441
- // ============================================================================
442
-
443
- describe('Plugin System - Non-Generic Plugins', () => {
444
- it('should accumulate types correctly when using non-generic plugins', () => {
445
- const plugin1 = (l: Lambder) => l.addApi('api1', { input: z.void(), output: z.void() }, async (ctx, res) => res.raw({ statusCode: 200, body: '' }));
446
- const plugin2 = (l: Lambder) => l.addApi('api2', { input: z.void(), output: z.void() }, async (ctx, res) => res.raw({ statusCode: 200, body: '' }));
447
-
448
- const lambder = new Lambder({ publicPath: '', apiPath: '' })
449
- .addApi('initialApi', { input: z.void(), output: z.void() }, async (ctx, res) => res.raw({ statusCode: 200, body: '' }))
450
- .use(plugin1)
451
- .use(plugin2);
452
-
453
- type Contract = typeof lambder.ApiContract;
454
-
455
- // Check if both api1 and api2 exist in Contract
456
- expectTypeOf<Contract>().toHaveProperty('initialApi');
457
- expectTypeOf<Contract>().toHaveProperty('api1');
458
- expectTypeOf<Contract>().toHaveProperty('api2');
459
- });
460
- });
package/tsconfig.json DELETED
@@ -1,24 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext", // Compile to a modern ECMAScript version that supports modules
4
- "module": "ESNext", // Use ESNext as module code generation to support ESM
5
- "declaration": true, // Generate corresponding '.d.ts' file.
6
- "outDir": "./dist", // Redirect output structure to the 'dist' directory
7
- "strict": true, // Enable all strict type-checking options
8
- "esModuleInterop": true, // Enables compatibility with Babel imports
9
- "skipLibCheck": true, // Skip type checking of all declaration files (*.d.ts)
10
- "forceConsistentCasingInFileNames": true,
11
- "moduleResolution": "node", // Module resolution strategy to use
12
- "resolveJsonModule": true, // Allow importing of '.json' files
13
- "isolatedModules": true, // Ensure each file can be safely transpiled without relying on other imports
14
- "baseUrl": ".", // Base directory to resolve non-relative module names
15
- "paths": { // Specify paths for imports
16
- "*": ["node_modules/*"]
17
- },
18
- "lib": ["ESNext", "DOM"], // Specify library files to be included in the compilation
19
- "types": ["node"] // Type definitions to be included
20
- },
21
- "include": ["src/**/*"], // Include all files in the src directory
22
- "exclude": ["node_modules", "dist"] // Exclude 'node_modules' directory and 'dist' directory
23
- }
24
-