lambder 1.0.128 → 1.0.129
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.
- package/OUTPUT_TYPE_ENFORCEMENT_SUMMARY.md +90 -0
- package/dist/Lambder.d.ts +5 -4
- package/dist/Lambder.js +2 -2
- package/dist/LambderResolver.d.ts +17 -15
- package/dist/LambderResolver.js +4 -0
- package/dist/LambderResponseBuilder.d.ts +2 -1
- package/dist/LambderSessionController.d.ts +1 -0
- package/dist/LambderSessionController.js +10 -0
- package/dist/LambderSessionManager.d.ts +6 -1
- package/dist/LambderSessionManager.js +43 -12
- package/docs/DYNAMODB_SETUP.md +96 -0
- package/docs/TYPE_SAFE_QUICK_START.md +48 -4
- package/examples/output-type-enforcement-example.ts +218 -0
- package/examples/secure-session-example.ts +191 -0
- package/examples/test-output-type-enforcement.ts +101 -0
- package/package.json +1 -1
- package/src/Lambder.ts +4 -4
- package/src/LambderResolver.ts +32 -15
- package/src/LambderResponseBuilder.ts +2 -1
- package/src/LambderSessionController.ts +9 -0
- package/src/LambderSessionManager.ts +51 -10
- package/tests/output-type-runtime.test.ts +365 -0
- package/tests/type-safety.test.ts +312 -0
|
@@ -197,6 +197,318 @@ describe('Complex Types', () => {
|
|
|
197
197
|
});
|
|
198
198
|
});
|
|
199
199
|
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// Test 6: Output Type Enforcement
|
|
202
|
+
// ============================================================================
|
|
203
|
+
|
|
204
|
+
describe('Output Type Enforcement', () => {
|
|
205
|
+
it('should enforce output types in resolver.api()', () => {
|
|
206
|
+
const lambder = new Lambder<TestApiContract>({
|
|
207
|
+
publicPath: './public',
|
|
208
|
+
apiPath: '/api'
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// ✅ CORRECT: Return correct output type
|
|
212
|
+
lambder.addApi('getUser', async (ctx, resolver) => {
|
|
213
|
+
const userId = ctx.apiPayload.userId;
|
|
214
|
+
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) => {
|
|
224
|
+
return resolver.api(true);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// ✅ CORRECT: Array output
|
|
228
|
+
lambder.addApi('listUsers', async (ctx, resolver) => {
|
|
229
|
+
return resolver.api([
|
|
230
|
+
{ id: '1', name: 'User 1' },
|
|
231
|
+
{ id: '2', name: 'User 2' }
|
|
232
|
+
]);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// ✅ CORRECT: Empty array
|
|
236
|
+
lambder.addApi('listUsers', async (ctx, resolver) => {
|
|
237
|
+
return resolver.api([]);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
expect(lambder).toBeDefined();
|
|
241
|
+
|
|
242
|
+
// ❌ These would cause TypeScript errors (commented out):
|
|
243
|
+
|
|
244
|
+
// Wrong output type:
|
|
245
|
+
// lambder.addApi('getUser', async (ctx, resolver) => {
|
|
246
|
+
// return resolver.api("wrong"); // Error: string not assignable to { id: string, name: string }
|
|
247
|
+
// });
|
|
248
|
+
|
|
249
|
+
// Missing required property:
|
|
250
|
+
// lambder.addApi('getUser', async (ctx, resolver) => {
|
|
251
|
+
// return resolver.api({ id: '123' }); // Error: missing 'name' property
|
|
252
|
+
// });
|
|
253
|
+
|
|
254
|
+
// Wrong property type:
|
|
255
|
+
// lambder.addApi('getUser', async (ctx, resolver) => {
|
|
256
|
+
// return resolver.api({ id: 123, name: 'Test' }); // Error: id should be string
|
|
257
|
+
// });
|
|
258
|
+
|
|
259
|
+
// Wrong boolean output:
|
|
260
|
+
// lambder.addApi('deleteUser', async (ctx, resolver) => {
|
|
261
|
+
// return resolver.api("true"); // Error: string not assignable to boolean
|
|
262
|
+
// });
|
|
263
|
+
|
|
264
|
+
// Wrong array element type:
|
|
265
|
+
// lambder.addApi('listUsers', async (ctx, resolver) => {
|
|
266
|
+
// return resolver.api([{ id: '1' }]); // Error: missing 'name' property
|
|
267
|
+
// });
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should enforce output types in resolver.die.api()', () => {
|
|
271
|
+
const lambder = new Lambder<TestApiContract>({
|
|
272
|
+
publicPath: './public',
|
|
273
|
+
apiPath: '/api'
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// ✅ CORRECT: die.api also enforces types
|
|
277
|
+
lambder.addApi('getUser', async (ctx, resolver) => {
|
|
278
|
+
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) => {
|
|
283
|
+
return resolver.die.api(true);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
expect(lambder).toBeDefined();
|
|
287
|
+
|
|
288
|
+
// ❌ These would cause TypeScript errors (commented out):
|
|
289
|
+
// lambder.addApi('getUser', async (ctx, resolver) => {
|
|
290
|
+
// return resolver.die.api("wrong"); // Error: wrong type
|
|
291
|
+
// });
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should enforce output types in addSessionApi()', () => {
|
|
295
|
+
const lambder = new Lambder<TestApiContract>({
|
|
296
|
+
publicPath: './public',
|
|
297
|
+
apiPath: '/api'
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// ✅ CORRECT: Session APIs also enforce output types
|
|
301
|
+
lambder.addSessionApi('createUser', async (ctx, resolver) => {
|
|
302
|
+
const { name, email } = ctx.apiPayload;
|
|
303
|
+
return resolver.api({ id: '1', name, email });
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// ✅ CORRECT: Session API with null
|
|
307
|
+
lambder.addSessionApi('deleteUser', async (ctx, resolver) => {
|
|
308
|
+
return resolver.api(true);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
expect(lambder).toBeDefined();
|
|
312
|
+
|
|
313
|
+
// ❌ These would cause TypeScript errors (commented out):
|
|
314
|
+
// lambder.addSessionApi('createUser', async (ctx, resolver) => {
|
|
315
|
+
// return resolver.api({ id: '1' }); // Error: missing name and email
|
|
316
|
+
// });
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// ============================================================================
|
|
321
|
+
// Test 7: Complex Output Types
|
|
322
|
+
// ============================================================================
|
|
323
|
+
|
|
324
|
+
type ComplexOutputContract = ApiContract<{
|
|
325
|
+
// Primitive outputs
|
|
326
|
+
getCount: { input: void, output: number },
|
|
327
|
+
getMessage: { input: void, output: string },
|
|
328
|
+
isActive: { input: void, output: boolean },
|
|
329
|
+
|
|
330
|
+
// Nested objects
|
|
331
|
+
getStats: {
|
|
332
|
+
input: void,
|
|
333
|
+
output: {
|
|
334
|
+
users: { total: number, active: number },
|
|
335
|
+
products: { total: number, inStock: number }
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
// Union types
|
|
340
|
+
findUser: {
|
|
341
|
+
input: { email: string },
|
|
342
|
+
output: { id: string, name: string } | null
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
// Array of complex objects
|
|
346
|
+
getOrders: {
|
|
347
|
+
input: { userId: string },
|
|
348
|
+
output: Array<{
|
|
349
|
+
id: string,
|
|
350
|
+
items: Array<{ name: string, price: number }>,
|
|
351
|
+
total: number
|
|
352
|
+
}>
|
|
353
|
+
}
|
|
354
|
+
}>;
|
|
355
|
+
|
|
356
|
+
describe('Complex Output Types', () => {
|
|
357
|
+
it('should enforce primitive output types', () => {
|
|
358
|
+
const lambder = new Lambder<ComplexOutputContract>({
|
|
359
|
+
publicPath: './public',
|
|
360
|
+
apiPath: '/api'
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// ✅ CORRECT: Primitive outputs
|
|
364
|
+
lambder.addApi('getCount', async (ctx, resolver) => {
|
|
365
|
+
return resolver.api(42);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
lambder.addApi('getMessage', async (ctx, resolver) => {
|
|
369
|
+
return resolver.api("Hello World");
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
lambder.addApi('isActive', async (ctx, resolver) => {
|
|
373
|
+
return resolver.api(true);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
expect(lambder).toBeDefined();
|
|
377
|
+
|
|
378
|
+
// ❌ These would cause TypeScript errors (commented out):
|
|
379
|
+
// lambder.addApi('getCount', async (ctx, resolver) => {
|
|
380
|
+
// return resolver.api("42"); // Error: string not assignable to number
|
|
381
|
+
// });
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('should enforce nested object types', () => {
|
|
385
|
+
const lambder = new Lambder<ComplexOutputContract>({
|
|
386
|
+
publicPath: './public',
|
|
387
|
+
apiPath: '/api'
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// ✅ CORRECT: Nested structure
|
|
391
|
+
lambder.addApi('getStats', async (ctx, resolver) => {
|
|
392
|
+
return resolver.api({
|
|
393
|
+
users: { total: 100, active: 75 },
|
|
394
|
+
products: { total: 50, inStock: 40 }
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
expect(lambder).toBeDefined();
|
|
399
|
+
|
|
400
|
+
// ❌ These would cause TypeScript errors (commented out):
|
|
401
|
+
// lambder.addApi('getStats', async (ctx, resolver) => {
|
|
402
|
+
// return resolver.api({
|
|
403
|
+
// users: { total: 100 } // Error: missing 'active'
|
|
404
|
+
// });
|
|
405
|
+
// });
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('should enforce union types correctly', () => {
|
|
409
|
+
const lambder = new Lambder<ComplexOutputContract>({
|
|
410
|
+
publicPath: './public',
|
|
411
|
+
apiPath: '/api'
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// ✅ CORRECT: Return object
|
|
415
|
+
lambder.addApi('findUser', async (ctx, resolver) => {
|
|
416
|
+
return resolver.api({ id: '123', name: 'John' });
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// ✅ CORRECT: Return null (part of union)
|
|
420
|
+
lambder.addApi('findUser', async (ctx, resolver) => {
|
|
421
|
+
return resolver.api(null);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
expect(lambder).toBeDefined();
|
|
425
|
+
|
|
426
|
+
// ❌ These would cause TypeScript errors (commented out):
|
|
427
|
+
// lambder.addApi('findUser', async (ctx, resolver) => {
|
|
428
|
+
// return resolver.api(undefined); // Error: undefined not in union
|
|
429
|
+
// });
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('should enforce complex nested array types', () => {
|
|
433
|
+
const lambder = new Lambder<ComplexOutputContract>({
|
|
434
|
+
publicPath: './public',
|
|
435
|
+
apiPath: '/api'
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// ✅ CORRECT: Complex nested array
|
|
439
|
+
lambder.addApi('getOrders', async (ctx, resolver) => {
|
|
440
|
+
return resolver.api([
|
|
441
|
+
{
|
|
442
|
+
id: '1',
|
|
443
|
+
items: [
|
|
444
|
+
{ name: 'Item 1', price: 10 },
|
|
445
|
+
{ name: 'Item 2', price: 20 }
|
|
446
|
+
],
|
|
447
|
+
total: 30
|
|
448
|
+
}
|
|
449
|
+
]);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
expect(lambder).toBeDefined();
|
|
453
|
+
|
|
454
|
+
// ❌ These would cause TypeScript errors (commented out):
|
|
455
|
+
// lambder.addApi('getOrders', async (ctx, resolver) => {
|
|
456
|
+
// return resolver.api([
|
|
457
|
+
// {
|
|
458
|
+
// id: '1',
|
|
459
|
+
// items: [{ name: 'Item 1' }], // Error: missing 'price'
|
|
460
|
+
// total: 30
|
|
461
|
+
// }
|
|
462
|
+
// ]);
|
|
463
|
+
// });
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// ============================================================================
|
|
468
|
+
// Test 8: Type Inference
|
|
469
|
+
// ============================================================================
|
|
470
|
+
|
|
471
|
+
describe('Type Inference', () => {
|
|
472
|
+
it('should infer types correctly from contract', () => {
|
|
473
|
+
const lambder = new Lambder<TestApiContract>({
|
|
474
|
+
publicPath: './public',
|
|
475
|
+
apiPath: '/api'
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// Test that TypeScript correctly infers the types
|
|
479
|
+
lambder.addApi('getUser', async (ctx, resolver) => {
|
|
480
|
+
// ctx.apiPayload type should be inferred as { userId: string }
|
|
481
|
+
const userId: string = ctx.apiPayload.userId;
|
|
482
|
+
|
|
483
|
+
// Variable with explicit type matching contract
|
|
484
|
+
const user: { id: string, name: string } = {
|
|
485
|
+
id: userId,
|
|
486
|
+
name: 'Test User'
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
// Should accept the correctly typed variable
|
|
490
|
+
return resolver.api(user);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
lambder.addApi('createUser', async (ctx, resolver) => {
|
|
494
|
+
// Input type inference
|
|
495
|
+
const name: string = ctx.apiPayload.name;
|
|
496
|
+
const email: string = ctx.apiPayload.email;
|
|
497
|
+
|
|
498
|
+
// Output type inference
|
|
499
|
+
const result: { id: string, name: string, email: string } = {
|
|
500
|
+
id: '123',
|
|
501
|
+
name,
|
|
502
|
+
email
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
return resolver.api(result);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
expect(lambder).toBeDefined();
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
200
512
|
// ============================================================================
|
|
201
513
|
// If this file compiles without errors, the type system is working! ✅
|
|
202
514
|
// ============================================================================
|