lambder 1.0.147 → 2.0.2

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 (39) hide show
  1. package/Readme.md +355 -410
  2. package/dist/Lambder.d.ts +47 -21
  3. package/dist/Lambder.js +79 -24
  4. package/dist/LambderApiContract.d.ts +10 -42
  5. package/dist/LambderApiContract.js +2 -22
  6. package/dist/LambderCaller.js +2 -2
  7. package/dist/LambderMSW.js +0 -4
  8. package/dist/LambderResolver.d.ts +16 -17
  9. package/dist/LambderResponseBuilder.d.ts +2 -3
  10. package/dist/LambderResponseBuilder.js +1 -3
  11. package/dist/LambderUtils.js +1 -3
  12. package/dist/index.d.ts +1 -1
  13. package/docs/LAMBDER_MSW.md +6 -6
  14. package/docs/TYPE_SAFE_QUICK_START.md +54 -177
  15. package/examples/msw-testing-example.ts +36 -33
  16. package/examples/secure-session-example.ts +50 -34
  17. package/examples/zod-chained-api-example.ts +63 -0
  18. package/package.json +3 -2
  19. package/src/Lambder.ts +124 -83
  20. package/src/LambderApiContract.ts +7 -50
  21. package/src/LambderCaller.ts +2 -2
  22. package/src/LambderMSW.ts +0 -7
  23. package/src/LambderResolver.ts +21 -24
  24. package/src/LambderResponseBuilder.ts +4 -7
  25. package/src/LambderUtils.ts +1 -3
  26. package/src/index.ts +0 -3
  27. package/tests/UNTESTED_FEATURES.md +263 -0
  28. package/tests/error-handling.test.ts +585 -0
  29. package/tests/hooks.test.ts +561 -0
  30. package/tests/output-type-runtime.test.ts +80 -64
  31. package/tests/routes.test.ts +542 -0
  32. package/tests/session.test.ts +38 -24
  33. package/tests/type-safety.test.ts +147 -97
  34. package/tests/use-plugin.test.ts +437 -0
  35. package/OUTPUT_TYPE_ENFORCEMENT_SUMMARY.md +0 -90
  36. package/examples/output-type-enforcement-example.ts +0 -218
  37. package/examples/simplified-typed-api-example.ts +0 -365
  38. package/examples/test-output-type-enforcement.ts +0 -101
  39. package/test-type-enforcement.ts +0 -111
@@ -7,8 +7,8 @@
7
7
 
8
8
  import { describe, it, expect } from 'vitest';
9
9
  import { APIGatewayProxyEvent, Context } from 'aws-lambda';
10
+ import { z } from 'zod';
10
11
  import Lambder from '../src/Lambder.js';
11
- import type { ApiContract } from '../src/index.js';
12
12
 
13
13
  // Mock AWS Lambda event and context
14
14
  const createMockEvent = (apiName: string, payload: any): APIGatewayProxyEvent => ({
@@ -41,28 +41,20 @@ const createMockContext = (): Context => ({
41
41
  succeed: () => {},
42
42
  });
43
43
 
44
- // Test contract
45
- type TestContract = ApiContract<{
46
- echo: { input: { message: string }, output: { echo: string } },
47
- add: { input: { a: number, b: number }, output: number },
48
- getUser: { input: { userId: string }, output: { id: string, name: string, age: number } },
49
- listUsers: { input: void, output: Array<{ id: string, name: string }> },
50
- findUser: { input: { email: string }, output: { id: string, name: string } | null },
51
- deleteUser: { input: { userId: string }, output: boolean },
52
- }>;
53
-
54
44
  // ============================================================================
55
45
  // Runtime Tests
56
46
  // ============================================================================
57
47
 
58
48
  describe('Output Type Enforcement - Runtime', () => {
59
49
  it('should return correct primitive types', async () => {
60
- const lambder = new Lambder<TestContract>({
50
+ const lambder = new Lambder({
61
51
  publicPath: './public',
62
52
  apiPath: '/api',
63
- });
64
-
65
- lambder.addApi('add', async (ctx, resolver) => {
53
+ })
54
+ .addApi('add', {
55
+ input: z.object({ a: z.number(), b: z.number() }),
56
+ output: z.number()
57
+ }, async (ctx, resolver) => {
66
58
  const { a, b } = ctx.apiPayload;
67
59
  const sum = a + b;
68
60
  return resolver.api(sum);
@@ -78,12 +70,14 @@ describe('Output Type Enforcement - Runtime', () => {
78
70
  });
79
71
 
80
72
  it('should return correct object types', async () => {
81
- const lambder = new Lambder<TestContract>({
73
+ const lambder = new Lambder({
82
74
  publicPath: './public',
83
75
  apiPath: '/api',
84
- });
85
-
86
- lambder.addApi('getUser', async (ctx, resolver) => {
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) => {
87
81
  const userId = ctx.apiPayload.userId;
88
82
  return resolver.api({
89
83
  id: userId,
@@ -98,6 +92,7 @@ describe('Output Type Enforcement - Runtime', () => {
98
92
 
99
93
  expect(response.statusCode).toBe(200);
100
94
  const body = JSON.parse(response.body || '{}');
95
+
101
96
  expect(body.payload).toEqual({
102
97
  id: '123',
103
98
  name: 'John Doe',
@@ -106,12 +101,14 @@ describe('Output Type Enforcement - Runtime', () => {
106
101
  });
107
102
 
108
103
  it('should return correct array types', async () => {
109
- const lambder = new Lambder<TestContract>({
104
+ const lambder = new Lambder({
110
105
  publicPath: './public',
111
106
  apiPath: '/api',
112
- });
113
-
114
- lambder.addApi('listUsers', async (ctx, resolver) => {
107
+ })
108
+ .addApi('listUsers', {
109
+ input: z.void(),
110
+ output: z.array(z.object({ id: z.string(), name: z.string() }))
111
+ }, async (ctx, resolver) => {
115
112
  return resolver.api([
116
113
  { id: '1', name: 'Alice' },
117
114
  { id: '2', name: 'Bob' }
@@ -131,12 +128,14 @@ describe('Output Type Enforcement - Runtime', () => {
131
128
  });
132
129
 
133
130
  it('should handle null returns correctly', async () => {
134
- const lambder = new Lambder<TestContract>({
131
+ const lambder = new Lambder({
135
132
  publicPath: './public',
136
133
  apiPath: '/api',
137
- });
138
-
139
- lambder.addApi('findUser', async (ctx, resolver) => {
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) => {
140
139
  const email = ctx.apiPayload.email;
141
140
  if (email === 'notfound@example.com') {
142
141
  return resolver.api(null);
@@ -164,12 +163,14 @@ describe('Output Type Enforcement - Runtime', () => {
164
163
  });
165
164
 
166
165
  it('should return boolean types correctly', async () => {
167
- const lambder = new Lambder<TestContract>({
166
+ const lambder = new Lambder({
168
167
  publicPath: './public',
169
168
  apiPath: '/api',
170
- });
171
-
172
- lambder.addApi('deleteUser', async (ctx, resolver) => {
169
+ })
170
+ .addApi('deleteUser', {
171
+ input: z.object({ userId: z.string() }),
172
+ output: z.boolean()
173
+ }, async (ctx, resolver) => {
173
174
  const userId = ctx.apiPayload.userId;
174
175
  // Mock deletion
175
176
  const success = userId !== '';
@@ -186,12 +187,14 @@ describe('Output Type Enforcement - Runtime', () => {
186
187
  });
187
188
 
188
189
  it('should work with die.api()', async () => {
189
- const lambder = new Lambder<TestContract>({
190
+ const lambder = new Lambder({
190
191
  publicPath: './public',
191
192
  apiPath: '/api',
192
- });
193
-
194
- lambder.addApi('echo', async (ctx, resolver) => {
193
+ })
194
+ .addApi('echo', {
195
+ input: z.object({ message: z.string() }),
196
+ output: z.object({ echo: z.string() })
197
+ }, async (ctx, resolver) => {
195
198
  return resolver.die.api({ echo: ctx.apiPayload.message });
196
199
  });
197
200
 
@@ -205,13 +208,14 @@ describe('Output Type Enforcement - Runtime', () => {
205
208
  });
206
209
 
207
210
  it('should work with session APIs', async () => {
208
- const lambder = new Lambder<TestContract>({
211
+ const lambder = new Lambder({
209
212
  publicPath: './public',
210
213
  apiPath: '/api',
211
- });
212
-
213
- // Note: Session will fail without DynamoDB setup, but we're testing type enforcement
214
- lambder.addSessionApi('getUser', async (ctx, resolver) => {
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) => {
215
219
  return resolver.api({
216
220
  id: ctx.apiPayload.userId,
217
221
  name: 'Session User',
@@ -237,12 +241,14 @@ describe('Output Type Enforcement - Runtime', () => {
237
241
 
238
242
  describe('Input Type Enforcement - Runtime', () => {
239
243
  it('should receive correctly typed input', async () => {
240
- const lambder = new Lambder<TestContract>({
244
+ const lambder = new Lambder({
241
245
  publicPath: './public',
242
246
  apiPath: '/api',
243
- });
244
-
245
- lambder.addApi('echo', async (ctx, resolver) => {
247
+ })
248
+ .addApi('echo', {
249
+ input: z.object({ message: z.string() }),
250
+ output: z.object({ echo: z.string() })
251
+ }, async (ctx, resolver) => {
246
252
  // Verify input is correctly typed at runtime
247
253
  const message: string = ctx.apiPayload.message;
248
254
  expect(typeof message).toBe('string');
@@ -255,12 +261,14 @@ describe('Input Type Enforcement - Runtime', () => {
255
261
  });
256
262
 
257
263
  it('should handle void input correctly', async () => {
258
- const lambder = new Lambder<TestContract>({
264
+ const lambder = new Lambder({
259
265
  publicPath: './public',
260
266
  apiPath: '/api',
261
- });
262
-
263
- lambder.addApi('listUsers', async (ctx, resolver) => {
267
+ })
268
+ .addApi('listUsers', {
269
+ input: z.void(),
270
+ output: z.array(z.any())
271
+ }, async (ctx, resolver) => {
264
272
  // apiPayload should be undefined for void input
265
273
  expect(ctx.apiPayload).toBeUndefined();
266
274
  return resolver.api([]);
@@ -272,12 +280,14 @@ describe('Input Type Enforcement - Runtime', () => {
272
280
  });
273
281
 
274
282
  it('should handle complex input objects', async () => {
275
- const lambder = new Lambder<TestContract>({
283
+ const lambder = new Lambder({
276
284
  publicPath: './public',
277
285
  apiPath: '/api',
278
- });
279
-
280
- lambder.addApi('add', async (ctx, resolver) => {
286
+ })
287
+ .addApi('add', {
288
+ input: z.object({ a: z.number(), b: z.number() }),
289
+ output: z.number()
290
+ }, async (ctx, resolver) => {
281
291
  const { a, b } = ctx.apiPayload;
282
292
  expect(typeof a).toBe('number');
283
293
  expect(typeof b).toBe('number');
@@ -299,12 +309,14 @@ describe('Input Type Enforcement - Runtime', () => {
299
309
 
300
310
  describe('Edge Cases', () => {
301
311
  it('should handle empty arrays', async () => {
302
- const lambder = new Lambder<TestContract>({
312
+ const lambder = new Lambder({
303
313
  publicPath: './public',
304
314
  apiPath: '/api',
305
- });
306
-
307
- lambder.addApi('listUsers', async (ctx, resolver) => {
315
+ })
316
+ .addApi('listUsers', {
317
+ input: z.void(),
318
+ output: z.array(z.any())
319
+ }, async (ctx, resolver) => {
308
320
  return resolver.api([]);
309
321
  });
310
322
 
@@ -318,12 +330,14 @@ describe('Edge Cases', () => {
318
330
  });
319
331
 
320
332
  it('should handle zero as a valid number', async () => {
321
- const lambder = new Lambder<TestContract>({
333
+ const lambder = new Lambder({
322
334
  publicPath: './public',
323
335
  apiPath: '/api',
324
- });
325
-
326
- lambder.addApi('add', async (ctx, resolver) => {
336
+ })
337
+ .addApi('add', {
338
+ input: z.object({ a: z.number(), b: z.number() }),
339
+ output: z.number()
340
+ }, async (ctx, resolver) => {
327
341
  return resolver.api(0);
328
342
  });
329
343
 
@@ -337,12 +351,14 @@ describe('Edge Cases', () => {
337
351
  });
338
352
 
339
353
  it('should handle empty strings in objects', async () => {
340
- const lambder = new Lambder<TestContract>({
354
+ const lambder = new Lambder({
341
355
  publicPath: './public',
342
356
  apiPath: '/api',
343
- });
344
-
345
- lambder.addApi('getUser', async (ctx, resolver) => {
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) => {
346
362
  return resolver.api({
347
363
  id: '',
348
364
  name: '',