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
@@ -6,17 +6,9 @@
6
6
  */
7
7
 
8
8
  import { describe, it, expect } from 'vitest';
9
+ import { z } from 'zod';
9
10
  import Lambder from '../src/Lambder.js';
10
11
  import LambderCaller from '../src/LambderCaller.js';
11
- import type { ApiContract } from '../src/index.js';
12
-
13
- // Test contract
14
- type TestApiContract = ApiContract<{
15
- getUser: { input: { userId: string }, output: { id: string, name: string } },
16
- createUser: { input: { name: string, email: string }, output: { id: string, name: string, email: string } },
17
- listUsers: { input: void, output: Array<{ id: string, name: string }> },
18
- deleteUser: { input: { userId: string }, output: boolean }
19
- }>;
20
12
 
21
13
  // ============================================================================
22
14
  // Test 1: LambderCaller Type Safety
@@ -24,6 +16,26 @@ type TestApiContract = ApiContract<{
24
16
 
25
17
  describe('LambderCaller Type Safety', () => {
26
18
  it('should have correct types', () => {
19
+ // Define contract via chaining
20
+ const lambder = new Lambder({
21
+ publicPath: './public',
22
+ apiPath: '/api'
23
+ })
24
+ .addApi('getUser', {
25
+ input: z.object({ userId: z.string() }),
26
+ output: z.object({ id: z.string(), name: z.string() })
27
+ }, async (ctx, resolver) => {
28
+ return resolver.api({ id: ctx.apiPayload.userId, name: 'Test' });
29
+ })
30
+ .addApi('createUser', {
31
+ input: z.object({ name: z.string(), email: z.string() }),
32
+ output: z.object({ id: z.string(), name: z.string(), email: z.string() })
33
+ }, async (ctx, resolver) => {
34
+ return resolver.api({ id: '1', ...ctx.apiPayload });
35
+ });
36
+
37
+ type TestApiContract = typeof lambder.ApiContractType;
38
+
27
39
  const caller = new LambderCaller<TestApiContract>({
28
40
  apiPath: '/api',
29
41
  isCorsEnabled: false
@@ -55,13 +67,16 @@ describe('LambderCaller Type Safety', () => {
55
67
 
56
68
  describe('Lambder Type Safety', () => {
57
69
  it('should have correct types', () => {
58
- const lambder = new Lambder<TestApiContract>({
70
+ const lambder = new Lambder({
59
71
  publicPath: './public',
60
72
  apiPath: '/api'
61
73
  });
62
74
 
63
75
  // ✅ Should work: Typed API
64
- lambder.addApi('getUser', async (ctx, resolver) => {
76
+ lambder.addApi('getUser', {
77
+ input: z.object({ userId: z.string() }),
78
+ output: z.object({ id: z.string(), name: z.string() })
79
+ }, async (ctx, resolver) => {
65
80
  // ctx.apiPayload should be typed as { userId: string }
66
81
  const userId: string = ctx.apiPayload.userId; // Should work
67
82
 
@@ -70,7 +85,10 @@ describe('Lambder Type Safety', () => {
70
85
  });
71
86
 
72
87
  // ✅ Should work: Typed session API
73
- lambder.addSessionApi('createUser', async (ctx, resolver) => {
88
+ lambder.addSessionApi('createUser', {
89
+ input: z.object({ name: z.string(), email: z.string() }),
90
+ output: z.object({ id: z.string(), name: z.string(), email: z.string() })
91
+ }, async (ctx, resolver) => {
74
92
  // ctx.apiPayload should be typed as { name: string, email: string }
75
93
  const name: string = ctx.apiPayload.name;
76
94
  const email: string = ctx.apiPayload.email;
@@ -79,7 +97,10 @@ describe('Lambder Type Safety', () => {
79
97
  });
80
98
 
81
99
  // ✅ Should work: Void input API
82
- lambder.addApi('listUsers', async (ctx, resolver) => {
100
+ lambder.addApi('listUsers', {
101
+ input: z.void(),
102
+ output: z.array(z.object({ id: z.string(), name: z.string() }))
103
+ }, async (ctx, resolver) => {
83
104
  // ctx.apiPayload should be void/undefined
84
105
  return resolver.api([
85
106
  { id: '1', name: 'User 1' },
@@ -88,7 +109,10 @@ describe('Lambder Type Safety', () => {
88
109
  });
89
110
 
90
111
  // ✅ Should work: RegExp (untyped)
91
- lambder.addApi(/^admin\./, async (ctx, resolver) => {
112
+ lambder.addApi('admin.anyAction', {
113
+ input: z.any(),
114
+ output: z.any()
115
+ }, async (ctx, resolver) => {
92
116
  // ctx.apiPayload is any (untyped)
93
117
  return resolver.api({});
94
118
  });
@@ -126,7 +150,10 @@ describe('Backward Compatibility (No Contract)', () => {
126
150
  });
127
151
 
128
152
  // Should work - untyped
129
- lambder.addApi('anyApi', async (ctx, resolver) => {
153
+ lambder.addApi('anyApi', {
154
+ input: z.any(),
155
+ output: z.any()
156
+ }, async (ctx, resolver) => {
130
157
  // ctx.apiPayload is any
131
158
  return resolver.api({ anything: ctx.apiPayload });
132
159
  });
@@ -143,6 +170,19 @@ describe('Backward Compatibility (No Contract)', () => {
143
170
 
144
171
  describe('apiRaw Method Type Safety', () => {
145
172
  it('should have correct types for apiRaw', () => {
173
+ const lambder = new Lambder({
174
+ publicPath: './public',
175
+ apiPath: '/api'
176
+ })
177
+ .addApi('getUser', {
178
+ input: z.object({ userId: z.string() }),
179
+ output: z.object({ id: z.string(), name: z.string() })
180
+ }, async (ctx, resolver) => {
181
+ return resolver.api({ id: ctx.apiPayload.userId, name: 'Test' });
182
+ });
183
+
184
+ type TestApiContract = typeof lambder.ApiContractType;
185
+
146
186
  const caller = new LambderCaller<TestApiContract>({
147
187
  apiPath: '/api',
148
188
  isCorsEnabled: false
@@ -203,40 +243,33 @@ describe('Complex Types', () => {
203
243
 
204
244
  describe('Output Type Enforcement', () => {
205
245
  it('should enforce output types in resolver.api()', () => {
206
- const lambder = new Lambder<TestApiContract>({
246
+ const lambder = new Lambder({
207
247
  publicPath: './public',
208
248
  apiPath: '/api'
209
- });
210
-
211
- // CORRECT: Return correct output type
212
- lambder.addApi('getUser', async (ctx, resolver) => {
249
+ })
250
+ .addApi('getUser', {
251
+ input: z.object({ userId: z.string() }),
252
+ output: z.object({ id: z.string(), name: z.string() }).nullable()
253
+ }, async (ctx, resolver) => {
213
254
  const userId = ctx.apiPayload.userId;
214
255
  return resolver.api({ id: userId, name: 'John Doe' });
215
- });
216
-
217
- // CORRECT: Return null is allowed
218
- lambder.addApi('getUser', async (ctx, resolver) => {
219
- return resolver.api(null);
220
- });
221
-
222
- // ✅ CORRECT: Boolean output
223
- lambder.addApi('deleteUser', async (ctx, resolver) => {
256
+ })
257
+ .addApi('deleteUser', {
258
+ input: z.object({ userId: z.string() }),
259
+ output: z.boolean()
260
+ }, async (ctx, resolver) => {
224
261
  return resolver.api(true);
225
- });
226
-
227
- // ✅ CORRECT: Array output
228
- lambder.addApi('listUsers', async (ctx, resolver) => {
262
+ })
263
+ .addApi('listUsers', {
264
+ input: z.void(),
265
+ output: z.array(z.object({ id: z.string(), name: z.string() }))
266
+ }, async (ctx, resolver) => {
229
267
  return resolver.api([
230
268
  { id: '1', name: 'User 1' },
231
269
  { id: '2', name: 'User 2' }
232
270
  ]);
233
271
  });
234
272
 
235
- // ✅ CORRECT: Empty array
236
- lambder.addApi('listUsers', async (ctx, resolver) => {
237
- return resolver.api([]);
238
- });
239
-
240
273
  expect(lambder).toBeDefined();
241
274
 
242
275
  // ❌ These would cause TypeScript errors (commented out):
@@ -268,18 +301,20 @@ describe('Output Type Enforcement', () => {
268
301
  });
269
302
 
270
303
  it('should enforce output types in resolver.die.api()', () => {
271
- const lambder = new Lambder<TestApiContract>({
304
+ const lambder = new Lambder({
272
305
  publicPath: './public',
273
306
  apiPath: '/api'
274
- });
275
-
276
- // CORRECT: die.api also enforces types
277
- lambder.addApi('getUser', async (ctx, resolver) => {
307
+ })
308
+ .addApi('getUser', {
309
+ input: z.object({ userId: z.string() }),
310
+ output: z.object({ id: z.string(), name: z.string() })
311
+ }, async (ctx, resolver) => {
278
312
  return resolver.die.api({ id: ctx.apiPayload.userId, name: 'Test' });
279
- });
280
-
281
- // CORRECT: die.api with null
282
- lambder.addApi('deleteUser', async (ctx, resolver) => {
313
+ })
314
+ .addApi('deleteUser', {
315
+ input: z.object({ userId: z.string() }),
316
+ output: z.boolean()
317
+ }, async (ctx, resolver) => {
283
318
  return resolver.die.api(true);
284
319
  });
285
320
 
@@ -292,19 +327,21 @@ describe('Output Type Enforcement', () => {
292
327
  });
293
328
 
294
329
  it('should enforce output types in addSessionApi()', () => {
295
- const lambder = new Lambder<TestApiContract>({
330
+ const lambder = new Lambder({
296
331
  publicPath: './public',
297
332
  apiPath: '/api'
298
- });
299
-
300
- // CORRECT: Session APIs also enforce output types
301
- lambder.addSessionApi('createUser', async (ctx, resolver) => {
333
+ })
334
+ .addSessionApi('createUser', {
335
+ input: z.object({ name: z.string(), email: z.string() }),
336
+ output: z.object({ id: z.string(), name: z.string(), email: z.string() })
337
+ }, async (ctx, resolver) => {
302
338
  const { name, email } = ctx.apiPayload;
303
339
  return resolver.api({ id: '1', name, email });
304
- });
305
-
306
- // CORRECT: Session API with null
307
- lambder.addSessionApi('deleteUser', async (ctx, resolver) => {
340
+ })
341
+ .addSessionApi('deleteUser', {
342
+ input: z.object({ userId: z.string() }),
343
+ output: z.boolean()
344
+ }, async (ctx, resolver) => {
308
345
  return resolver.api(true);
309
346
  });
310
347
 
@@ -321,7 +358,7 @@ describe('Output Type Enforcement', () => {
321
358
  // Test 7: Complex Output Types
322
359
  // ============================================================================
323
360
 
324
- type ComplexOutputContract = ApiContract<{
361
+ type ComplexOutputContract = {
325
362
  // Primitive outputs
326
363
  getCount: { input: void, output: number },
327
364
  getMessage: { input: void, output: string },
@@ -351,25 +388,30 @@ type ComplexOutputContract = ApiContract<{
351
388
  total: number
352
389
  }>
353
390
  }
354
- }>;
391
+ };
355
392
 
356
393
  describe('Complex Output Types', () => {
357
394
  it('should enforce primitive output types', () => {
358
- const lambder = new Lambder<ComplexOutputContract>({
395
+ const lambder = new Lambder({
359
396
  publicPath: './public',
360
397
  apiPath: '/api'
361
- });
362
-
363
- // ✅ CORRECT: Primitive outputs
364
- lambder.addApi('getCount', async (ctx, resolver) => {
398
+ })
399
+ .addApi('getCount', {
400
+ input: z.void(),
401
+ output: z.number()
402
+ }, async (ctx, resolver) => {
365
403
  return resolver.api(42);
366
- });
367
-
368
- lambder.addApi('getMessage', async (ctx, resolver) => {
404
+ })
405
+ .addApi('getMessage', {
406
+ input: z.void(),
407
+ output: z.string()
408
+ }, async (ctx, resolver) => {
369
409
  return resolver.api("Hello World");
370
- });
371
-
372
- lambder.addApi('isActive', async (ctx, resolver) => {
410
+ })
411
+ .addApi('isActive', {
412
+ input: z.void(),
413
+ output: z.boolean()
414
+ }, async (ctx, resolver) => {
373
415
  return resolver.api(true);
374
416
  });
375
417
 
@@ -382,13 +424,17 @@ describe('Complex Output Types', () => {
382
424
  });
383
425
 
384
426
  it('should enforce nested object types', () => {
385
- const lambder = new Lambder<ComplexOutputContract>({
427
+ const lambder = new Lambder({
386
428
  publicPath: './public',
387
429
  apiPath: '/api'
388
- });
389
-
390
- // ✅ CORRECT: Nested structure
391
- lambder.addApi('getStats', async (ctx, resolver) => {
430
+ })
431
+ .addApi('getStats', {
432
+ input: z.void(),
433
+ output: z.object({
434
+ users: z.object({ total: z.number(), active: z.number() }),
435
+ products: z.object({ total: z.number(), inStock: z.number() })
436
+ })
437
+ }, async (ctx, resolver) => {
392
438
  return resolver.api({
393
439
  users: { total: 100, active: 75 },
394
440
  products: { total: 50, inStock: 40 }
@@ -406,21 +452,17 @@ describe('Complex Output Types', () => {
406
452
  });
407
453
 
408
454
  it('should enforce union types correctly', () => {
409
- const lambder = new Lambder<ComplexOutputContract>({
455
+ const lambder = new Lambder({
410
456
  publicPath: './public',
411
457
  apiPath: '/api'
412
- });
413
-
414
- // CORRECT: Return object
415
- lambder.addApi('findUser', async (ctx, resolver) => {
458
+ })
459
+ .addApi('findUser', {
460
+ input: z.object({ email: z.string() }),
461
+ output: z.object({ id: z.string(), name: z.string() }).nullable()
462
+ }, async (ctx, resolver) => {
416
463
  return resolver.api({ id: '123', name: 'John' });
417
464
  });
418
465
 
419
- // ✅ CORRECT: Return null (part of union)
420
- lambder.addApi('findUser', async (ctx, resolver) => {
421
- return resolver.api(null);
422
- });
423
-
424
466
  expect(lambder).toBeDefined();
425
467
 
426
468
  // ❌ These would cause TypeScript errors (commented out):
@@ -430,13 +472,18 @@ describe('Complex Output Types', () => {
430
472
  });
431
473
 
432
474
  it('should enforce complex nested array types', () => {
433
- const lambder = new Lambder<ComplexOutputContract>({
475
+ const lambder = new Lambder({
434
476
  publicPath: './public',
435
477
  apiPath: '/api'
436
- });
437
-
438
- // CORRECT: Complex nested array
439
- lambder.addApi('getOrders', async (ctx, resolver) => {
478
+ })
479
+ .addApi('getOrders', {
480
+ input: z.object({ userId: z.string() }),
481
+ output: z.array(z.object({
482
+ id: z.string(),
483
+ items: z.array(z.object({ name: z.string(), price: z.number() })),
484
+ total: z.number()
485
+ }))
486
+ }, async (ctx, resolver) => {
440
487
  return resolver.api([
441
488
  {
442
489
  id: '1',
@@ -470,13 +517,14 @@ describe('Complex Output Types', () => {
470
517
 
471
518
  describe('Type Inference', () => {
472
519
  it('should infer types correctly from contract', () => {
473
- const lambder = new Lambder<TestApiContract>({
520
+ const lambder = new Lambder({
474
521
  publicPath: './public',
475
522
  apiPath: '/api'
476
- });
477
-
478
- // Test that TypeScript correctly infers the types
479
- lambder.addApi('getUser', async (ctx, resolver) => {
523
+ })
524
+ .addApi('getUser', {
525
+ input: z.object({ userId: z.string() }),
526
+ output: z.object({ id: z.string(), name: z.string() })
527
+ }, async (ctx, resolver) => {
480
528
  // ctx.apiPayload type should be inferred as { userId: string }
481
529
  const userId: string = ctx.apiPayload.userId;
482
530
 
@@ -488,9 +536,11 @@ describe('Type Inference', () => {
488
536
 
489
537
  // Should accept the correctly typed variable
490
538
  return resolver.api(user);
491
- });
492
-
493
- lambder.addApi('createUser', async (ctx, resolver) => {
539
+ })
540
+ .addApi('createUser', {
541
+ input: z.object({ name: z.string(), email: z.string() }),
542
+ output: z.object({ id: z.string(), name: z.string(), email: z.string() })
543
+ }, async (ctx, resolver) => {
494
544
  // Input type inference
495
545
  const name: string = ctx.apiPayload.name;
496
546
  const email: string = ctx.apiPayload.email;