lambder 2.0.13 → 2.0.14

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/Readme.md CHANGED
@@ -27,17 +27,16 @@ yarn add lambder zod
27
27
  ### Basic Setup
28
28
 
29
29
  ```typescript
30
- import Lambder, { InferLambderContract } from 'lambder';
30
+ import Lambder from 'lambder';
31
31
  import { z } from 'zod';
32
32
  import * as path from 'path';
33
33
 
34
34
  const lambder = new Lambder({
35
35
  apiPath: "/api",
36
36
  publicPath: path.resolve(`./public`),
37
- // ejsPath: path.resolve(`./ejs-templates`), // Optional
38
37
  });
39
38
 
40
- // Enable session and CORS - all chainable!
39
+ // Enable session and CORS
41
40
  lambder
42
41
  .enableDdbSession({
43
42
  tableName: "website-session",
@@ -47,7 +46,7 @@ lambder
47
46
  .enableCors(true);
48
47
 
49
48
  // Define type-safe APIs with Zod schemas
50
- const app = lambder
49
+ lambder
51
50
  .addApi("getCompanyPage", {
52
51
  input: z.object({ companyName: z.string() }),
53
52
  output: z.object({ id: z.string(), name: z.string(), description: z.string() })
@@ -70,7 +69,7 @@ const app = lambder
70
69
  });
71
70
 
72
71
  // Export the inferred contract for the frontend
73
- export type AppContract = typeof lambder.ApiContract;
72
+ export type ApiContractType = typeof lambder.ApiContract;
74
73
 
75
74
  // Export the handler
76
75
  export const handler = lambder.getHandler();
@@ -78,8 +77,6 @@ export const handler = lambder.getHandler();
78
77
 
79
78
  ### Adding Routes
80
79
 
81
- Routes are fully chainable for a fluent interface.
82
-
83
80
  ```typescript
84
81
  lambder
85
82
  // Define a simple route
@@ -100,25 +97,6 @@ lambder
100
97
  .addRoute((ctx)=>ctx.path === '/hello-fn-route', (ctx, res) => {
101
98
  return res.html("Hello from a function route");
102
99
  })
103
- // Define a simple route that serves an EJS template file
104
- .addRoute("/product/:productId", async (ctx, res) => {
105
- const product = await getProduct(ctx.pathParams.productId);
106
- // Serve the file from ejsPath defined above.
107
- return await res.ejsFile("productPage.html.ejs", { product });
108
- })
109
- // Serve sitemap using an ejs template
110
- .addRoute("/sitemap", async (ctx, res) => {
111
- const templateString = `
112
- <?xml version="1.0" encoding="UTF-8"?>
113
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
114
- <%~
115
- page.urlList.map(url => \`<url><loc>\${url}</loc></url>\`).join("")
116
- %>
117
- </urlset>
118
- `.trim();
119
- const urlList = await getUrlList();
120
- return await res.ejsTemplate(templateString, { urlList }, { "Content-Type": ["application/xml; charset=utf-8"]});
121
- })
122
100
  // Match all other paths and serve static files from publicPath
123
101
  .addRoute("/(.*)", (ctx, res)=>{
124
102
  return res.file(ctx.path, {}, "index.html");
@@ -127,6 +105,14 @@ lambder
127
105
  .setRouteFallbackHandler((ctx, res) => {
128
106
  return res.status404("Not Found");
129
107
  })
108
+ // Set a fallback handler for unmatched APIs
109
+ .setApiFallbackHandler((ctx, res) => {
110
+ return res.api(null, { errorMessage: "API not found" });
111
+ })
112
+ // Handle Zod validation errors for API inputs
113
+ .setApiInputValidationErrorHandler((ctx, res, zodError) => {
114
+ return res.api(null, { errorMessage: zodError.errors });
115
+ })
130
116
  // Global error handler
131
117
  .setGlobalErrorHandler((err, ctx, res) => {
132
118
  console.error("Error:", err);
@@ -134,35 +120,21 @@ lambder
134
120
  });
135
121
  ```
136
122
 
137
- ### Adding APIs
123
+ ### Session-Protected APIs
138
124
 
139
- All APIs in v2.0 must use Zod schemas for type safety and runtime validation. Use method chaining for a clean API definition.
125
+ Use `addSessionApi` for endpoints that require authentication:
140
126
 
141
127
  ```typescript
142
- import { z } from 'zod';
143
-
144
- lambder
145
- // Add a typed API
146
- .addApi("getUserById", {
147
- input: z.object({ userId: z.string() }),
148
- output: z.object({ id: z.string(), name: z.string(), email: z.string() })
149
- }, async (ctx, res) => {
150
- // ctx.apiPayload is typed as { userId: string }
151
- const user = await db.getUser(ctx.apiPayload.userId);
152
- return res.api(user); // Type-checked against output schema
153
- })
154
- // Session-protected API
155
- .addSessionApi("getProfile", {
156
- input: z.void(),
157
- output: z.object({ userId: z.string(), username: z.string() })
158
- }, async (ctx, res) => {
159
- // Session is automatically fetched and validated
160
- // ctx.session.data contains your session data
161
- return res.api({
162
- userId: ctx.session.data.userId,
163
- username: ctx.session.data.username
164
- });
128
+ lambder.addSessionApi("getProfile", {
129
+ input: z.void(),
130
+ output: z.object({ userId: z.string(), username: z.string() })
131
+ }, async (ctx, res) => {
132
+ // Session is automatically fetched and validated
133
+ return res.api({
134
+ userId: ctx.session.data.userId,
135
+ username: ctx.session.data.username
165
136
  });
137
+ });
166
138
  ```
167
139
 
168
140
  ### Modular APIs with .use()
@@ -193,10 +165,10 @@ export const userApi = <T>(l: Lambder<T>) => {
193
165
  // index.ts
194
166
  import { userApi } from "./user-api";
195
167
 
196
- const lambder = new Lambder()
168
+ const lambder = new Lambder({ publicPath: './public' })
197
169
  .use(userApi);
198
170
 
199
- export type AppContract = typeof lambder.ApiContract;
171
+ export type ApiContractType = typeof lambder.ApiContract;
200
172
  ```
201
173
 
202
174
 
@@ -217,24 +189,28 @@ lambder
217
189
  // Modify response before sending
218
190
  console.log("Response status:", response.statusCode);
219
191
  return response;
192
+ })
193
+ // Fallback hook - runs when no route/API matches
194
+ .addHook("fallback", async (ctx, res) => {
195
+ // Perform cleanup or logging for unmatched requests
196
+ console.log("No handler matched for:", ctx.path);
220
197
  });
221
198
  ```
222
199
 
223
200
  ### Session Management
224
201
 
225
- Enable DynamoDB-based session management - fully chainable:
202
+ Enable DynamoDB-based sessions with `enableDdbSession()`. Optional configuration:
226
203
 
227
204
  ```typescript
228
- // Enable sessions using a DynamoDB session table - chainable!
229
- const lambder = new Lambder({ apiPath: '/api', publicPath: './public' })
205
+ lambder
230
206
  .enableDdbSession({
231
207
  tableName: "website-session",
232
208
  tableRegion: "us-east-1",
233
209
  sessionSalt: "CHANGE-THIS-TO-A-SECURE-RANDOM-STRING",
234
210
  enableSlidingExpiration: true // Optional: extend session on each access
235
211
  })
236
- .enableCors(true)
237
- .addApi(...);
212
+ // Optionally customize session cookie names (defaults: LMDRSESSIONTKID, LMDRSESSIONCSTK)
213
+ .setSessionCookieKey("MY_SESSION_TOKEN", "MY_CSRF_TOKEN");
238
214
  ```
239
215
 
240
216
  #### DynamoDB Session Table Structure
@@ -247,71 +223,17 @@ See [docs/DYNAMODB_SETUP.md](docs/DYNAMODB_SETUP.md) for detailed setup instruct
247
223
 
248
224
  #### Session Controller
249
225
 
250
- After enabling sessions, you can access the session controller:
251
-
252
- ```typescript
253
- const sessionController = lambder.getSessionController(ctx);
254
-
255
- // Available methods:
256
- await sessionController.createSession(sessionKey, data, ttlInSeconds);
257
- // Starts a new session and persists the session data to DDB.
226
+ Access the session controller with `lambder.getSessionController(ctx)`:
258
227
 
259
- await sessionController.fetchSession();
260
- // Fetch and validate if there is an existing session
261
- // This is automatically done for addSessionRoute and addSessionApi
262
- // Throws if session not found
263
-
264
- await sessionController.fetchSessionIfExists();
265
- // Returns session if found, otherwise null
266
-
267
- await sessionController.updateSessionData(updatedData);
268
- // Updates the active session's data and persists it to DDB
269
-
270
- await sessionController.endSession();
271
- // End session and delete from DDB
272
-
273
- await sessionController.endSessionAll();
274
- // Ends and deletes all sessions for this sessionKey across all devices
275
-
276
- await sessionController.regenerateSession();
277
- // Regenerates session token (use after password change, etc.)
278
- ```
279
-
280
- #### Session Examples
281
-
282
- ```typescript
283
- lambder
284
- .addApi("createSession", {
285
- input: z.object({ userId: z.string() }),
286
- output: z.object({ success: z.boolean() })
287
- }, async (ctx, res) => {
288
- // Create a new session
289
- const userId = ctx.apiPayload.userId;
290
- await lambder.getSessionController(ctx)
291
- .createSession(userId, { business: "Session data goes here" });
292
-
293
- console.log(ctx.session?.sessionKey); // userId
294
- console.log(ctx.session?.data?.business); // "Session data goes here"
295
-
296
- return res.api({ success: true });
297
- })
298
- .addSessionApi("updateSession", {
299
- input: z.object({ newData: z.string() }),
300
- output: z.object({ success: z.boolean() })
301
- }, async (ctx, res) => {
302
- // Session is automatically fetched
303
- console.log(ctx.session.sessionKey); // userId
304
-
305
- // Update session data
306
- await lambder.getSessionController(ctx)
307
- .updateSessionData({ business2: ctx.apiPayload.newData });
308
-
309
- console.log(ctx.session.data.business); // undefined
310
- console.log(ctx.session.data.business2); // newData value
311
-
312
- return res.api({ success: true });
313
- });
314
- ```
228
+ | Method | Description |
229
+ |--------|-------------|
230
+ | `createSession(sessionKey, data?, ttlInSeconds?)` | Start new session, persist to DDB |
231
+ | `fetchSession()` | Fetch & validate existing session (throws if not found) |
232
+ | `fetchSessionIfExists()` | Returns session or null |
233
+ | `updateSessionData(newData)` | Update session data in DDB |
234
+ | `endSession()` | End session, delete from DDB |
235
+ | `endSessionAll()` | End all sessions for this sessionKey (all devices) |
236
+ | `regenerateSession()` | Regenerate token (use after password change) |
315
237
 
316
238
  ### EJS Templates
317
239
 
@@ -339,75 +261,52 @@ Example partial:
339
261
 
340
262
  ### Render Context (ctx) Variables
341
263
 
342
- ```typescript
343
- lambder
344
- .addApi("exampleApi", {
345
- input: z.object({ value: z.string() }),
346
- output: z.object({ result: z.string() })
347
- }, async (ctx, res) => {
348
- const {
349
- host, // Request host: "www.example.com"
350
- path, // Request path: "/api"
351
- get, // GET query parameters: { userId: "342" }
352
- post, // POST body (parsed): { userId: "342" }
353
- cookie, // Cookies: { "rememberMe": "true" }
354
- headers, // Request headers
355
- apiName, // API name: "exampleApi"
356
- apiPayload, // Validated input (same as post.payload)
357
- session, // Session (null for addApi, available for addSessionApi)
358
- } = ctx;
359
-
360
- return res.api({ result: ctx.apiPayload.value });
361
- });
362
- ```
264
+ The `ctx` object provides access to request data:
265
+
266
+ | Property | Description | Example |
267
+ |----------|-------------|----------|
268
+ | `host` | Request host | `"www.example.com"` |
269
+ | `path` | Request path | `"/api"` |
270
+ | `pathParams` | Path parameters (routes) | `{ userId: "123" }` |
271
+ | `method` | HTTP method | `"GET"`, `"POST"` |
272
+ | `get` | Query parameters | `{ page: "1" }` |
273
+ | `post` | POST body (parsed) | `{ name: "John" }` |
274
+ | `cookie` | Cookies | `{ rememberMe: "true" }` |
275
+ | `headers` | Request headers | `{ "Content-Type": "..." }` |
276
+ | `event` | Raw APIGatewayProxyEvent | - |
277
+ | `lambdaContext` | AWS Lambda Context | - |
278
+ | `apiName` | API name (for API calls) | `"getUser"` |
279
+ | `apiPayload` | Validated input | `{ userId: "123" }` |
280
+ | `session` | Session data | Available in `addSessionApi` |
363
281
 
364
282
  ### Resolver Methods
365
283
 
366
- Available response methods:
367
-
368
- ```typescript
369
- return res.raw(param);
370
- // Sends a custom HTTP response. Useful for non-standard responses.
371
-
372
- return res.json(data, headers);
373
- // Sends a JSON response with optional headers.
374
-
375
- return res.xml(data);
376
- // Sends an XML response (base64 encoded).
377
-
378
- return res.html(data, headers);
379
- // Sends an HTML response (base64 encoded).
380
-
381
- return res.status301(url, headers);
382
- // Redirects to the specified URL with a 301 status code.
383
-
384
- return res.status404(data, headers);
385
- // Sends a 404 Not Found response.
386
-
387
- return res.cors();
388
- // Sends a 200 OK response with CORS headers (for preflight requests).
389
-
390
- return res.fileBase64(fileBase64, mimeType, headers);
391
- // Sends a file response from base64 content.
392
-
393
- return res.file(filePath, headers, fallbackFilePath);
394
- // Serves a file from the public directory.
395
-
396
- return await res.ejsFile(filePath, pageData, headers);
397
- // Renders and serves an EJS file.
398
-
399
- return await res.ejsTemplate(template, pageData, headers);
400
- // Renders and serves an EJS template string.
401
-
402
- return res.api(payload, config, headers);
403
- // Sends a standardized API response for use with LambderCaller.
404
- // Config: { notAuthorized, message, errorMessage, versionExpired, sessionExpired }
405
-
406
- // res.die.* - Same as res.* but immediately returns and skips afterRender hooks
407
- return res.die.json(data, headers);
408
- return res.die.api(payload, config, headers);
409
- // ... etc
410
- ```
284
+ **Header Manipulation** (call before returning response):
285
+ - `res.addHeader(key, value)` - Adds a header value (can be called multiple times for same key)
286
+ - `res.setHeader(key, value)` - Sets a header (replaces existing values)
287
+ - `res.logToApiResponse(data)` - Adds data to logList in API responses (debugging)
288
+
289
+ **Response Methods**:
290
+
291
+ | Method | Description |
292
+ |--------|-------------|
293
+ | `res.raw(param)` | Custom HTTP response |
294
+ | `res.json(data, headers?)` | JSON response |
295
+ | `res.xml(data)` | XML response (base64 encoded) |
296
+ | `res.html(data, headers?)` | HTML response (base64 encoded) |
297
+ | `res.redirect(url, statusCode?, headers?)` | Redirect (default: 302) |
298
+ | `res.status404(data, headers?)` | 404 Not Found response |
299
+ | `res.cors()` | 200 OK with CORS headers (preflight) |
300
+ | `res.fileBase64(base64, mimeType, headers?)` | File from base64 content |
301
+ | `res.file(path, headers?, fallbackPath?)` | Serve file from public directory |
302
+ | `await res.ejsFile(path, pageData, headers?)` | Render EJS file |
303
+ | `await res.ejsTemplate(template, pageData, headers?)` | Render EJS template string |
304
+ | `res.api(payload, config?, headers?)` | Standardized API response |
305
+ | `res.apiBinary(payload, config?, headers?)` | Gzip-compressed API response |
306
+
307
+ **API Config Options**: `{ notAuthorized, message, errorMessage, versionExpired, sessionExpired, logList }`
308
+
309
+ **Die Methods**: `res.die.*` - Same as above but immediately returns, skipping `afterRender` hooks.
411
310
 
412
311
  ## Frontend Usage with LambderCaller
413
312
 
@@ -417,9 +316,9 @@ LambderCaller is a frontend companion library for Lambder (only 2kb compressed)
417
316
 
418
317
  ```typescript
419
318
  import { LambderCaller } from "lambder";
420
- import type { AppContract } from "./backend/handler"; // Import the inferred contract type
319
+ import type { ApiContractType } from "./backend/handler"; // Import the inferred contract type
421
320
 
422
- const lambderCaller = new LambderCaller<AppContract>({
321
+ const lambderCaller = new LambderCaller<ApiContractType>({
423
322
  apiPath: "/api",
424
323
  isCorsEnabled: false,
425
324
  fetchStartedHandler: ({ fetchParams, activeFetchList }) => {
@@ -441,38 +340,6 @@ const user = await lambderCaller.api("getCompanyPage", { companyName: "Acme" });
441
340
  // - Expected output type
442
341
  ```
443
342
 
444
- ### How Type Safety Works
445
-
446
- 1. **Backend**: Chain your APIs and export the inferred contract
447
- ```typescript
448
- // backend/handler.ts
449
- import Lambder from 'lambder';
450
- import { z } from 'zod';
451
-
452
- const lambder = new Lambder({ apiPath: '/api' })
453
- .addApi('getUser', {
454
- input: z.object({ userId: z.string() }),
455
- output: z.object({ id: z.string(), name: z.string() })
456
- }, async (ctx, res) => {
457
- return res.api({ id: ctx.apiPayload.userId, name: "John" });
458
- });
459
-
460
- export type AppContract = typeof lambder.ApiContract;
461
- export const handler = lambder.getHandler();
462
- ```
463
-
464
- 2. **Frontend**: Import the **type** (not the code) and use it
465
- ```typescript
466
- // frontend/api.ts
467
- import { LambderCaller } from 'lambder';
468
- import type { AppContract } from '../backend/handler'; // Type-only import
469
-
470
- const lambderCaller = new LambderCaller<AppContract>({ apiPath: '/api' });
471
-
472
- // ✅ Fully typed - TypeScript knows all APIs and their input/output types
473
- const user = await lambderCaller.api('getUser', { userId: '123' });
474
- ```
475
-
476
343
  ### Benefits
477
344
 
478
345
  ✅ **No Manual Type Definitions** - Types are inferred from your Zod schemas
@@ -491,9 +358,9 @@ LambderMSW provides seamless integration with [MSW (Mock Service Worker)](https:
491
358
  ```typescript
492
359
  import { LambderMSW } from 'lambder';
493
360
  import { setupServer } from 'msw/node';
494
- import type { AppContract } from './backend/handler';
361
+ import type { ApiContractType } from './backend/handler';
495
362
 
496
- const lambderMSW = new LambderMSW<AppContract>({
363
+ const lambderMSW = new LambderMSW<ApiContractType>({
497
364
  apiPath: '/api',
498
365
  });
499
366
 
package/dist/Lambder.d.ts CHANGED
@@ -46,7 +46,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
46
46
  * @example
47
47
  * ```typescript
48
48
  * const lambder = new Lambder().addApi(...).addApi(...);
49
- * export type AppContract = typeof lambder.ApiContract;
49
+ * export type ApiContractType = typeof lambder.ApiContract;
50
50
  * ```
51
51
  */
52
52
  readonly ApiContract: _TContract;
package/dist/Lambder.js CHANGED
@@ -33,7 +33,7 @@ export default class Lambder {
33
33
  * @example
34
34
  * ```typescript
35
35
  * const lambder = new Lambder().addApi(...).addApi(...);
36
- * export type AppContract = typeof lambder.ApiContract;
36
+ * export type ApiContractType = typeof lambder.ApiContract;
37
37
  * ```
38
38
  */
39
39
  ApiContract;
@@ -31,14 +31,6 @@ const handlers = [
31
31
  name: 'John Doe',
32
32
  email: 'john@example.com'
33
33
  };
34
- }),
35
-
36
- lambderMSW.mockApi('createUser', async (payload) => {
37
- return {
38
- id: '123',
39
- name: payload.name,
40
- email: payload.email
41
- };
42
34
  })
43
35
  ];
44
36
 
@@ -60,7 +52,7 @@ When using TypeScript API contracts, LambderMSW provides full type safety:
60
52
  import { z } from 'zod';
61
53
  import Lambder from 'lambder';
62
54
 
63
- const lambder = new Lambder({ apiPath: '/secure' })
55
+ const lambder = new Lambder({ apiPath: '/secure', publicPath: './public' })
64
56
  .addApi('getUserById', {
65
57
  input: z.object({ userId: z.string() }),
66
58
  output: z.object({ id: z.string(), name: z.string(), email: z.string() })
@@ -77,13 +69,13 @@ const lambder = new Lambder({ apiPath: '/secure' })
77
69
  });
78
70
 
79
71
  // Export the inferred contract type
80
- export type MyApiContract = typeof lambder.ApiContract;
72
+ export type ApiContractType = typeof lambder.ApiContract;
81
73
 
82
74
  // test/setup.ts
83
75
  import { LambderMSW } from 'lambder';
84
- import type { MyApiContract } from '../backend';
76
+ import type { ApiContractType } from '../backend';
85
77
 
86
- const lambderMSW = new LambderMSW<MyApiContract>({
78
+ const lambderMSW = new LambderMSW<ApiContractType>({
87
79
  apiPath: '/secure',
88
80
  apiVersion: '1.0.0'
89
81
  });
@@ -215,12 +207,11 @@ lambderMSW.mockWithMessage('updateProfile', async (payload) => {
215
207
  // test/api.test.ts
216
208
  import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
217
209
  import { setupServer } from 'msw/node';
218
- import { LambderMSW } from 'lambder';
219
- import { LambderCaller } from 'lambder';
220
- import type { MyApiContract } from '../backend'; // Type-only import from your backend
210
+ import { LambderMSW, LambderCaller } from 'lambder';
211
+ import type { ApiContractType } from '../backend'; // Type-only import from your backend
221
212
 
222
213
  // Setup MSW
223
- const lambderMSW = new LambderMSW<MyApiContract>({
214
+ const lambderMSW = new LambderMSW<ApiContractType>({
224
215
  apiPath: '/secure',
225
216
  apiVersion: '1.0.0'
226
217
  });
@@ -257,7 +248,7 @@ afterEach(() => server.resetHandlers());
257
248
  afterAll(() => server.close());
258
249
 
259
250
  // Setup LambderCaller
260
- const lambderCaller = new LambderCaller<MyApiContract>({
251
+ const lambderCaller = new LambderCaller<ApiContractType>({
261
252
  apiPath: '/secure',
262
253
  isCorsEnabled: false
263
254
  });
@@ -314,9 +305,9 @@ LambderMSW also works in browser environments with MSW's browser integration:
314
305
  // test/browser-setup.ts
315
306
  import { setupWorker } from 'msw/browser';
316
307
  import { LambderMSW } from 'lambder';
317
- import type { MyApiContract } from '../backend'; // Type-only import from your backend
308
+ import type { ApiContractType } from '../backend'; // Type-only import from your backend
318
309
 
319
- const lambderMSW = new LambderMSW<MyApiContract>({
310
+ const lambderMSW = new LambderMSW<ApiContractType>({
320
311
  apiPath: '/secure'
321
312
  });
322
313
 
@@ -361,25 +352,6 @@ lambderMSW.mockApi('searchUsers', async (payload) => {
361
352
  });
362
353
  ```
363
354
 
364
- ### Simulating Network Conditions
365
-
366
- ```typescript
367
- // Slow network
368
- lambderMSW.mockApi('slowApi', async () => {
369
- return { data: 'slow response' };
370
- }, { delay: 3000 }); // 3 second delay
371
-
372
- // Intermittent failures
373
- let callCount = 0;
374
- lambderMSW.mockApi('flakeyApi', async () => {
375
- callCount++;
376
- if (callCount % 3 === 0) {
377
- throw new Error('Random failure');
378
- }
379
- return { success: true };
380
- });
381
- ```
382
-
383
355
  ### Override Handlers Per Test
384
356
 
385
357
  ```typescript
@@ -402,12 +374,10 @@ it('should handle specific user', async () => {
402
374
 
403
375
  ## Benefits
404
376
 
405
- ✅ **Type Safety** - Full TypeScript support with API contracts
406
- ✅ **Simple API** - Intuitive methods matching Lambder's API structure
407
- ✅ **Flexible** - Mock success, errors, delays, and custom responses
408
- **Isolated** - Tests run without real backend dependencies
409
- ✅ **Fast** - No network calls, instant test execution
410
- ✅ **Realistic** - Simulate real-world scenarios (delays, errors, etc.)
377
+ - Full TypeScript support with API contracts
378
+ - Intuitive methods matching Lambder's API structure
379
+ - Test isolation without real backend dependencies
380
+ - Simulate real-world scenarios (delays, errors, etc.)
411
381
 
412
382
  ## Troubleshooting
413
383
 
@@ -29,7 +29,7 @@ const lambder = new Lambder({
29
29
  });
30
30
 
31
31
  // Export the inferred contract type
32
- export type AppContract = typeof lambder.ApiContract;
32
+ export type ApiContractType = typeof lambder.ApiContract;
33
33
 
34
34
  export const handler = lambder.getHandler();
35
35
  ```
@@ -40,9 +40,9 @@ Import the type (not the code) and use `LambderCaller`.
40
40
 
41
41
  ```typescript
42
42
  import { LambderCaller } from "lambder";
43
- import type { AppContract } from "./backend"; // Type-only import
43
+ import type { ApiContractType } from "./backend"; // Type-only import
44
44
 
45
- const lambderCaller = new LambderCaller<AppContract>({
45
+ const lambderCaller = new LambderCaller<ApiContractType>({
46
46
  apiPath: "/api"
47
47
  });
48
48
 
@@ -72,6 +72,6 @@ export const userApi = <T>(l: Lambder<T>) => {
72
72
  // index.ts
73
73
  import { userApi } from "./api.user";
74
74
 
75
- const lambder = new Lambder()
75
+ const lambder = new Lambder({ publicPath: './public' })
76
76
  .use(userApi); // Types are preserved!
77
77
  ```
@@ -22,7 +22,7 @@ import { LambderMSW, LambderCaller } from '../src/index.ts';
22
22
  import Lambder from '../src/Lambder.js';
23
23
 
24
24
  // Define your API contract using Lambder chaining
25
- const lambder = new Lambder()
25
+ const lambder = new Lambder({ publicPath: './public' })
26
26
  .addApi('getUserById', {
27
27
  input: z.object({ userId: z.string() }),
28
28
  output: z.object({ id: z.string(), name: z.string(), email: z.string() }).nullable()
@@ -40,10 +40,10 @@ const lambder = new Lambder()
40
40
  output: z.object({ success: z.boolean() })
41
41
  }, async () => ({} as any));
42
42
 
43
- type TestApiContract = typeof lambder.ApiContract;
43
+ type ApiContractType = typeof lambder.ApiContract;
44
44
 
45
45
  // Setup LambderMSW with type safety
46
- const lambderMSW = new LambderMSW<TestApiContract>({
46
+ const lambderMSW = new LambderMSW<ApiContractType>({
47
47
  apiPath: '/secure',
48
48
  apiVersion: '1.0.0',
49
49
  });
@@ -92,7 +92,7 @@ const handlers = [
92
92
  const server = setupServer(...handlers);
93
93
 
94
94
  // Setup LambderCaller
95
- const lambderCaller = new LambderCaller<TestApiContract>({
95
+ const lambderCaller = new LambderCaller<ApiContractType>({
96
96
  apiPath: '/secure',
97
97
  isCorsEnabled: false,
98
98
  });
@@ -45,8 +45,8 @@ const lambder = new Lambder({
45
45
  });
46
46
  });
47
47
 
48
- // 3. Export the inferred contract type for Frontend
49
- export type AppContract = typeof lambder.ApiContract;
48
+ // 3. Export the inferred contract type for frontend use
49
+ export type ApiContractType = typeof lambder.ApiContract;
50
50
 
51
51
  // 4. Modular example using .use()
52
52
  const authApi = <T>(l: Lambder<T>) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambder",
3
- "version": "2.0.13",
3
+ "version": "2.0.14",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/Lambder.ts CHANGED
@@ -58,7 +58,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
58
58
  * @example
59
59
  * ```typescript
60
60
  * const lambder = new Lambder().addApi(...).addApi(...);
61
- * export type AppContract = typeof lambder.ApiContract;
61
+ * export type ApiContractType = typeof lambder.ApiContract;
62
62
  * ```
63
63
  */
64
64
  public readonly ApiContract!: _TContract;
@@ -1,263 +0,0 @@
1
- # Untested Features in Lambder
2
-
3
- This document lists features that currently lack test coverage.
4
-
5
- ## ✅ Currently Tested Features
6
-
7
- Based on existing test files:
8
-
9
- 1. **Type Safety** (`type-safety.test.ts`)
10
- - LambderCaller type inference
11
- - Lambder API type safety
12
- - Input/output type enforcement
13
- - Complex type patterns
14
- - Type inference from contracts
15
-
16
- 2. **Session Management** (`session.test.ts`)
17
- - Session creation, fetching, updating, deletion
18
- - Session regeneration
19
- - Session security (CSRF, validation)
20
- - Session type safety
21
- - Sliding expiration
22
-
23
- 3. **Output Type Runtime** (`output-type-runtime.test.ts`)
24
- - Runtime validation of API outputs
25
- - Zod schema enforcement at runtime
26
- - API response format validation
27
-
28
- 4. **Plugin System** (`use-plugin.test.ts`)
29
- - Basic plugin usage
30
- - Multiple plugin chaining
31
- - Plugin type accumulation
32
- - Nested plugins
33
- - Plugin reusability
34
-
35
- ## ❌ Untested Features (Needs Test Coverage)
36
-
37
- ### 1. Routes (`addRoute`, `addSessionRoute`)
38
- **Priority: HIGH**
39
-
40
- - [x] Basic route matching (string paths)
41
- - [ ] Path parameter extraction (e.g., `/user/:userId`)
42
- - [ ] RegExp route matching
43
- - [ ] Function-based conditional routing
44
- - [ ] Session-protected routes (`addSessionRoute`)
45
- - [ ] Route priority/ordering
46
- - [ ] Wildcard routes
47
-
48
- **Suggested test file:** `tests/routes.test.ts`
49
-
50
- ### 2. Hooks System
51
- **Priority: HIGH**
52
-
53
- - [ ] `beforeRender` hook execution
54
- - [ ] `afterRender` hook execution
55
- - [ ] `fallback` hook execution
56
- - [ ] `created` hook execution
57
- - [ ] Hook priority ordering
58
- - [ ] Multiple hooks of same type
59
- - [ ] Hook error handling
60
- - [ ] Context modification in hooks
61
- - [ ] Response modification in hooks
62
-
63
- **Suggested test file:** `tests/hooks.test.ts`
64
-
65
- ### 3. Error Handling
66
- **Priority: HIGH**
67
-
68
- - [ ] `setGlobalErrorHandler` functionality
69
- - [ ] Error handling with context available
70
- - [ ] Error handling with null context
71
- - [ ] Custom error responses
72
- - [ ] Error in API handlers
73
- - [ ] Error in route handlers
74
- - [ ] Error in hooks
75
- - [ ] Error log accumulation
76
-
77
- **Suggested test file:** `tests/error-handling.test.ts`
78
-
79
- ### 4. Fallback Handlers
80
- **Priority: MEDIUM**
81
-
82
- - [ ] `setRouteFallbackHandler` for unmatched routes
83
- - [ ] `setApiFallbackHandler` for unmatched APIs
84
- - [ ] Default fallback behavior (no handler set)
85
- - [ ] Fallback handler with custom responses
86
-
87
- **Suggested test file:** `tests/fallback-handlers.test.ts`
88
-
89
- ### 5. Response Methods (LambderResolver/LambderResponseBuilder)
90
- **Priority: MEDIUM**
91
-
92
- - [x] `res.api()` - tested in output-type-runtime
93
- - [ ] `res.json()`
94
- - [ ] `res.html()`
95
- - [ ] `res.xml()`
96
- - [ ] `res.raw()`
97
- - [ ] `res.file()` with fallback
98
- - [ ] `res.fileBase64()`
99
- - [ ] `res.ejsFile()`
100
- - [ ] `res.ejsTemplate()`
101
- - [ ] `res.redirect()` (redirects)
102
- - [ ] `res.status404()`
103
- - [ ] `res.cors()`
104
- - [ ] `res.die.*` methods (skip afterRender hooks)
105
- - [ ] Custom headers in responses
106
- - [ ] `setHeader()` and `addHeader()` in context
107
-
108
- **Suggested test file:** `tests/response-methods.test.ts`
109
-
110
- ### 6. CORS Support
111
- **Priority: MEDIUM**
112
-
113
- - [ ] `enableCors(true)` functionality
114
- - [ ] CORS headers in responses
115
- - [ ] OPTIONS request handling (preflight)
116
- - [ ] CORS disabled behavior
117
-
118
- **Suggested test file:** `tests/cors.test.ts`
119
-
120
- ### 7. API Version Management
121
- **Priority: MEDIUM**
122
-
123
- - [ ] Setting `apiVersion` in constructor
124
- - [ ] Version mismatch detection
125
- - [ ] Version expired response
126
- - [ ] Client version validation
127
- - [ ] Version header handling
128
-
129
- **Suggested test file:** `tests/api-versioning.test.ts`
130
-
131
- ### 8. Context Variables
132
- **Priority: LOW**
133
-
134
- - [ ] `ctx.host` extraction
135
- - [ ] `ctx.path` extraction
136
- - [ ] `ctx.pathParams` for route parameters
137
- - [ ] `ctx.get` (query parameters)
138
- - [ ] `ctx.post` (POST body parsing)
139
- - [ ] `ctx.cookie` parsing
140
- - [ ] `ctx.headers` access
141
- - [ ] `ctx.event` (raw Lambda event)
142
- - [ ] `ctx.lambdaContext` (raw Lambda context)
143
- - [ ] Base64 encoded body handling
144
- - [ ] URL-encoded form data parsing
145
-
146
- **Suggested test file:** `tests/context-extraction.test.ts`
147
-
148
- ### 9. LambderCaller Features
149
- **Priority: MEDIUM**
150
-
151
- - [x] Basic API calls - tested in type-safety
152
- - [ ] `apiRaw()` method
153
- - [ ] `fetchStartedHandler` callback
154
- - [ ] `fetchEndedHandler` callback
155
- - [ ] `errorMessageHandler` callback
156
- - [ ] `activeFetchList` tracking
157
- - [ ] Error response handling
158
- - [ ] Session expired handling
159
- - [ ] Version expired handling
160
- - [ ] Not authorized handling
161
- - [ ] Custom headers in requests
162
- - [ ] CORS mode handling
163
-
164
- **Suggested test file:** `tests/lambder-caller.test.ts`
165
-
166
- ### 10. LambderMSW Features
167
- **Priority: MEDIUM**
168
-
169
- - [ ] `mockApi()` basic functionality
170
- - [ ] `mockSessionExpired()` helper
171
- - [ ] `mockNotAuthorized()` helper
172
- - [ ] `mockVersionExpired()` helper
173
- - [ ] Custom mock options (delay, message, errorMessage)
174
- - [ ] Type safety in mocks
175
- - [ ] Multiple API mocks
176
- - [ ] Mock priority/ordering
177
-
178
- **Suggested test file:** `tests/lambder-msw.test.ts`
179
-
180
- ### 11. EJS Template Rendering
181
- **Priority: LOW**
182
-
183
- - [ ] `res.ejsFile()` rendering
184
- - [ ] `res.ejsTemplate()` rendering
185
- - [ ] Template `page` variable
186
- - [ ] Partial `partial` variable
187
- - [ ] `include()` function in templates
188
- - [ ] Template error handling
189
- - [ ] Custom headers with EJS
190
-
191
- **Suggested test file:** `tests/ejs-templates.test.ts`
192
-
193
- ### 12. File Serving
194
- **Priority: LOW**
195
-
196
- - [ ] Static file serving with `res.file()`
197
- - [ ] Fallback file (e.g., index.html)
198
- - [ ] MIME type detection
199
- - [ ] Base64 file responses
200
- - [ ] File not found handling
201
- - [ ] Public path configuration
202
-
203
- **Suggested test file:** `tests/file-serving.test.ts`
204
-
205
- ### 13. Utility Methods
206
- **Priority: LOW**
207
-
208
- - [ ] `lambder.utils.*` methods
209
- - [ ] `getSessionController()` helper
210
- - [ ] `getResponseBuilder()` helper
211
- - [ ] `getHandler()` export
212
-
213
- **Suggested test file:** `tests/utilities.test.ts`
214
-
215
- ### 14. Edge Cases & Integration
216
- **Priority: LOW**
217
-
218
- - [ ] Multiple simultaneous requests
219
- - [ ] Race conditions in session management
220
- - [ ] Large payload handling
221
- - [ ] Invalid JSON in request body
222
- - [ ] Missing required fields
223
- - [ ] Empty/null payloads
224
- - [ ] Very long session data
225
- - [ ] Malformed Lambda events
226
-
227
- **Suggested test file:** `tests/edge-cases.test.ts`
228
-
229
- ## Test Coverage Priorities
230
-
231
- ### Must Have (Before v2.0 stable)
232
- 1. Routes and path parameters
233
- 2. Hooks system
234
- 3. Error handling
235
- 4. Response methods (at least common ones)
236
-
237
- ### Should Have (Before v2.1)
238
- 1. CORS support
239
- 2. API versioning
240
- 3. LambderCaller edge cases
241
- 4. Fallback handlers
242
-
243
- ### Nice to Have (Future releases)
244
- 1. EJS templates
245
- 2. File serving
246
- 3. Edge cases and stress testing
247
- 4. Integration tests
248
-
249
- ## How to Run Tests
250
-
251
- ```bash
252
- # Run all tests
253
- npm run test
254
-
255
- # Run specific test file
256
- npm run test -- tests/routes.test.ts
257
-
258
- # Run tests in watch mode
259
- npm run test -- --watch
260
-
261
- # Run tests with coverage
262
- npm run test -- --coverage
263
- ```