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
@@ -1,200 +1,77 @@
1
- # Type-Safe API Quick Start
1
+ # Type-Safe API Quick Start (v2.0)
2
2
 
3
3
  ## In 3 Simple Steps
4
4
 
5
- ### 1. Define Your Contract Type
5
+ ### 1. Define & Implement APIs (Backend)
6
6
 
7
- ```typescript
8
- // shared/apiContract.ts
9
- import type { ApiContract } from 'lambder';
10
-
11
- export type MyApiContract = ApiContract<{
12
- getUserById: { input: { userId: string }, output: User },
13
- createUser: { input: CreateUserInput, output: User },
14
- listUsers: { input: void, output: User[] }
15
- }>;
16
- ```
17
-
18
- ### 2. Backend - Pass Type to Lambder
7
+ Use Zod schemas to define your API contract inline. Lambder will automatically validate inputs at runtime and infer types for compile-time safety.
19
8
 
20
9
  ```typescript
21
- import Lambder from 'lambder';
22
- import type { MyApiContract } from './shared/apiContract';
23
-
24
- const lambder = new Lambder<MyApiContract>({
25
- publicPath: './public',
26
- apiPath: '/api'
10
+ import { z } from "zod";
11
+ import Lambder from "lambder";
12
+
13
+ // Initialize
14
+ const lambder = new Lambder({
15
+ publicPath: "./public",
16
+ apiPath: "/api"
17
+ })
18
+ // Chain APIs
19
+ .addApi("getUser", {
20
+ input: z.object({ userId: z.string() }),
21
+ output: z.object({ id: z.string(), name: z.string() })
22
+ }, async (ctx, resolver) => {
23
+ // ctx.apiPayload is typed as { userId: string }
24
+ // Runtime validation is already performed!
25
+ return resolver.api({
26
+ id: ctx.apiPayload.userId,
27
+ name: "John Doe"
28
+ });
27
29
  });
28
30
 
29
- // Now addApi is type-safe for both inputs AND outputs!
30
- lambder.addApi('getUserById', async (ctx, resolver) => {
31
- // ✅ ctx.apiPayload is automatically typed as { userId: string }
32
- const user = await db.getUser(ctx.apiPayload.userId);
33
-
34
- // ✅ resolver.api() enforces the output type (User)
35
- return resolver.api(user); // TypeScript checks that user matches User type!
36
- });
37
- ```
38
-
39
- ### 3. Frontend - Pass Type to LambderCaller
40
-
41
- ```typescript
42
- import { LambderCaller } from 'lambder';
43
- import type { MyApiContract } from './shared/apiContract';
44
-
45
- const caller = new LambderCaller<MyApiContract>({
46
- apiPath: '/api',
47
- isCorsEnabled: false
48
- });
49
-
50
- // Now api() is type-safe!
51
- const user = await caller.api('getUserById', { userId: '123' });
52
- // user is typed as User | null | undefined
53
- ```
54
-
55
- ## That's It!
56
-
57
- - ✅ **No wrapper functions needed**
58
- - ✅ **Use existing `api()` and `addApi()` methods**
59
- - ✅ **Full autocomplete in IDE**
60
- - ✅ **Type-safe inputs AND outputs**
61
- - ✅ **Compile-time validation**
62
- - ✅ **Backward compatible**
31
+ // Export the inferred contract type
32
+ export type AppContract = typeof lambder.ApiContractType;
63
33
 
64
- ## Contract Type Format
65
-
66
- ```typescript
67
- type MyApiContract = {
68
- apiName: { input: InputType, output: OutputType }
69
- }
34
+ export const handler = lambder.getHandler();
70
35
  ```
71
36
 
72
- ## Examples
37
+ ### 2. Use in Frontend
73
38
 
74
- ### API with parameters
75
- ```typescript
76
- getUserById: { input: { userId: string }, output: User }
77
- ```
39
+ Import the type (not the code) and use `LambderCaller`.
78
40
 
79
- ### API with no input
80
41
  ```typescript
81
- listAll: { input: void, output: Item[] }
82
- ```
83
-
84
- ### API with complex types
85
- ```typescript
86
- updateUser: {
87
- input: { id: string } & Partial<User>,
88
- output: User
89
- }
90
- ```
91
-
92
- ### API with conditional output
93
- ```typescript
94
- login: {
95
- input: { email: string, password: string },
96
- output: { success: boolean, user?: User, error?: string }
97
- }
98
- ```
42
+ import { LambderCaller } from "lambder";
43
+ import type { AppContract } from "./backend"; // Type-only import
99
44
 
100
- ## Output Type Enforcement
101
-
102
- The type system now enforces that `resolver.api()` returns data matching your contract's output type:
103
-
104
- ```typescript
105
- type MyContract = ApiContract<{
106
- getNumber: { input: void, output: number },
107
- getUser: { input: { id: string }, output: User }
108
- }>;
109
-
110
- const lambder = new Lambder<MyContract>({ ... });
111
-
112
- // ✅ CORRECT
113
- lambder.addApi('getNumber', async (ctx, resolver) => {
114
- return resolver.api(42); // number - matches output type
115
- });
116
-
117
- // ❌ ERROR: Type 'string' is not assignable to type 'number'
118
- lambder.addApi('getNumber', async (ctx, resolver) => {
119
- return resolver.api("wrong"); // TypeScript error!
45
+ const lambderCaller = new LambderCaller<AppContract>({
46
+ apiPath: "/api"
120
47
  });
121
48
 
122
- // CORRECT
123
- lambder.addApi('getUser', async (ctx, resolver) => {
124
- return resolver.api({ id: "123", name: "John", ... }); // User object
125
- });
126
-
127
- // ❌ ERROR: Missing required properties
128
- lambder.addApi('getUser', async (ctx, resolver) => {
129
- return resolver.api({ id: "123" }); // TypeScript error - incomplete User!
130
- });
49
+ // Fully typed!
50
+ // TypeScript knows 'getUser' takes { userId: string } and returns { id: string, name: string }
51
+ const user = await lambderCaller.api("getUser", { userId: "123" });
131
52
  ```
132
53
 
133
- This works with:
134
- - `resolver.api()` - typed return value
135
- - `resolver.die.api()` - typed return value
136
- - `addApi()` - typed for regular APIs
137
- - `addSessionApi()` - typed for session APIs
54
+ ### 3. Modular APIs (Optional)
138
55
 
139
- ## Without Type Safety (Still Works!)
140
-
141
- Don't want type safety? Just don't pass the generic type:
56
+ For larger apps, split your APIs into modules using `.use()`.
142
57
 
143
58
  ```typescript
144
- // Frontend
145
- const caller = new LambderCaller({ ... }); // No generic
146
- await caller.api('anyApi', { anything: true }); // Works, but untyped
147
-
148
- // Backend
149
- const lambder = new Lambder({ ... }); // No generic
150
- lambder.addApi('anyApi', async (ctx, resolver) => {
151
- // ctx.apiPayload is any
152
- });
59
+ // api.user.ts
60
+ import { z } from "zod";
61
+ import Lambder from "lambder";
62
+
63
+ export const userApi = <T>(l: Lambder<T>) => {
64
+ return l.addApi("login", {
65
+ input: z.object({ email: z.string() }),
66
+ output: z.boolean()
67
+ }, async (ctx, resolver) => {
68
+ return resolver.api(true);
69
+ });
70
+ };
71
+
72
+ // index.ts
73
+ import { userApi } from "./api.user";
74
+
75
+ const lambder = new Lambder()
76
+ .use(userApi); // Types are preserved!
153
77
  ```
154
-
155
- ## Full Example
156
-
157
- See [simplified-typed-api-example.ts](../examples/simplified-typed-api-example.ts) for a complete working example.
158
-
159
- ## Testing Your Type-Safe APIs
160
-
161
- LambderMSW provides full type safety for testing your APIs with MSW (Mock Service Worker):
162
-
163
- ```typescript
164
- import { LambderMSW } from 'lambder';
165
- import { setupServer } from 'msw/node';
166
- import type { MyApiContract } from './shared/apiContract';
167
-
168
- // Create type-safe MSW instance
169
- const lambderMSW = new LambderMSW<MyApiContract>({
170
- apiPath: '/api'
171
- });
172
-
173
- // Mock with full type safety! ✨
174
- const handlers = [
175
- lambderMSW.mockApi('getUserById', async (payload) => {
176
- // payload is typed as { userId: string }
177
- // Return value is type-checked against output
178
- return {
179
- id: payload.userId,
180
- name: 'John Doe',
181
- email: 'john@example.com'
182
- };
183
- })
184
- ];
185
-
186
- const server = setupServer(...handlers);
187
- ```
188
-
189
- 📖 See [LAMBDER_MSW.md](./LAMBDER_MSW.md) for complete testing documentation.
190
-
191
- ## Key Points
192
-
193
- - **Contract is just a TypeScript type** - No runtime code!
194
- - **Zero overhead** - All type checking happens at compile time
195
- - **Input AND output validation** - Both sides of your API are type-safe
196
- - **Compile-time safety** - Catch type mismatches before deployment
197
- - **Opt-in** - Use types when you want them
198
- - **Simple** - Just pass type to constructor
199
- - **Autocomplete** - IDE shows available APIs as you type
200
- - **Testing support** - LambderMSW provides type-safe mocking
@@ -17,27 +17,30 @@
17
17
  // @ts-nocheck - Example file, types may not be available
18
18
  import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
19
19
  import { setupServer } from 'msw/node';
20
- import { LambderMSW, LambderCaller, type ApiContract } from '../src/index.ts';
21
-
22
- // Define your API contract
23
- type TestApiContract = ApiContract<{
24
- getUserById: {
25
- input: { userId: string };
26
- output: { id: string; name: string; email: string } | null;
27
- };
28
- createUser: {
29
- input: { name: string; email: string };
30
- output: { id: string; name: string; email: string };
31
- };
32
- getUsers: {
33
- input: { limit?: number };
34
- output: Array<{ id: string; name: string; email: string }>;
35
- };
36
- deleteUser: {
37
- input: { userId: string };
38
- output: { success: boolean };
39
- };
40
- }>;
20
+ import { z } from 'zod';
21
+ import { LambderMSW, LambderCaller } from '../src/index.ts';
22
+ import Lambder from '../src/Lambder.js';
23
+
24
+ // Define your API contract using Lambder chaining
25
+ const lambder = new Lambder()
26
+ .addApi('getUserById', {
27
+ input: z.object({ userId: z.string() }),
28
+ output: z.object({ id: z.string(), name: z.string(), email: z.string() }).nullable()
29
+ }, async () => ({} as any)) // Dummy handler for type inference
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 () => ({} as any))
34
+ .addApi('getUsers', {
35
+ input: z.object({ limit: z.number().optional() }),
36
+ output: z.array(z.object({ id: z.string(), name: z.string(), email: z.string() }))
37
+ }, async () => ({} as any))
38
+ .addApi('deleteUser', {
39
+ input: z.object({ userId: z.string() }),
40
+ output: z.object({ success: z.boolean() })
41
+ }, async () => ({} as any));
42
+
43
+ type TestApiContract = typeof lambder.ApiContractType;
41
44
 
42
45
  // Setup LambderMSW with type safety
43
46
  const lambderMSW = new LambderMSW<TestApiContract>({
@@ -89,7 +92,7 @@ const handlers = [
89
92
  const server = setupServer(...handlers);
90
93
 
91
94
  // Setup LambderCaller
92
- const caller = new LambderCaller<TestApiContract>({
95
+ const lambderCaller = new LambderCaller<TestApiContract>({
93
96
  apiPath: '/secure',
94
97
  isCorsEnabled: false,
95
98
  });
@@ -109,7 +112,7 @@ describe('LambderMSW Testing Example', () => {
109
112
  });
110
113
 
111
114
  it('should fetch user by id', async () => {
112
- const user = await caller.api('getUserById', { userId: '1' });
115
+ const user = await lambderCaller.api('getUserById', { userId: '1' });
113
116
 
114
117
  expect(user).toEqual({
115
118
  id: '1',
@@ -119,7 +122,7 @@ describe('LambderMSW Testing Example', () => {
119
122
  });
120
123
 
121
124
  it('should return null for non-existent user', async () => {
122
- const user = await caller.api('getUserById', { userId: '999' });
125
+ const user = await lambderCaller.api('getUserById', { userId: '999' });
123
126
 
124
127
  expect(user).toBeNull();
125
128
  });
@@ -127,7 +130,7 @@ describe('LambderMSW Testing Example', () => {
127
130
  it('should create a new user with delay', async () => {
128
131
  const startTime = Date.now();
129
132
 
130
- const newUser = await caller.api('createUser', {
133
+ const newUser = await lambderCaller.api('createUser', {
131
134
  name: 'Alice Wonder',
132
135
  email: 'alice@example.com',
133
136
  });
@@ -143,7 +146,7 @@ describe('LambderMSW Testing Example', () => {
143
146
  });
144
147
 
145
148
  it('should fetch list of users', async () => {
146
- const users = await caller.api('getUsers', { limit: 2 });
149
+ const users = await lambderCaller.api('getUsers', { limit: 2 });
147
150
 
148
151
  expect(users).toHaveLength(2);
149
152
  expect(users?.[0]?.name).toBe('John Doe');
@@ -154,7 +157,7 @@ describe('LambderMSW Testing Example', () => {
154
157
  let errorCaught = false;
155
158
 
156
159
  try {
157
- await caller.api('deleteUser', { userId: '1' });
160
+ await lambderCaller.api('deleteUser', { userId: '1' });
158
161
  } catch (error: any) {
159
162
  errorCaught = true;
160
163
  expect(error.notAuthorized).toBe(true);
@@ -175,7 +178,7 @@ describe('LambderMSW Testing Example', () => {
175
178
  })
176
179
  );
177
180
 
178
- const user = await caller.api('getUserById', { userId: '999' });
181
+ const user = await lambderCaller.api('getUserById', { userId: '999' });
179
182
 
180
183
  expect(user?.name).toBe('Override User');
181
184
  });
@@ -189,7 +192,7 @@ describe('LambderMSW Testing Example', () => {
189
192
  let errorCaught = false;
190
193
 
191
194
  try {
192
- await caller.api('getUserById', { userId: '1' });
195
+ await lambderCaller.api('getUserById', { userId: '1' });
193
196
  } catch (error: any) {
194
197
  errorCaught = true;
195
198
  expect(error.sessionExpired).toBe(true);
@@ -206,7 +209,7 @@ describe('LambderMSW Testing Example', () => {
206
209
  let errorCaught = false;
207
210
 
208
211
  try {
209
- await caller.api('createUser', {
212
+ await lambderCaller.api('createUser', {
210
213
  name: 'Duplicate',
211
214
  email: 'john@example.com',
212
215
  });
@@ -239,10 +242,10 @@ describe('Dynamic Response Testing', () => {
239
242
  })
240
243
  );
241
244
 
242
- const users3 = await caller.api('getUsers', { limit: 3 });
245
+ const users3 = await lambderCaller.api('getUsers', { limit: 3 });
243
246
  expect(users3).toHaveLength(3);
244
247
 
245
- const users5 = await caller.api('getUsers', { limit: 5 });
248
+ const users5 = await lambderCaller.api('getUsers', { limit: 5 });
246
249
  expect(users5).toHaveLength(5);
247
250
  });
248
251
  });
@@ -263,7 +266,7 @@ describe('Error Handling', () => {
263
266
  let errorCaught = false;
264
267
 
265
268
  try {
266
- await caller.api('getUserById', { userId: '1' });
269
+ await lambderCaller.api('getUserById', { userId: '1' });
267
270
  } catch (error: any) {
268
271
  errorCaught = true;
269
272
  expect(error.errorMessage).toBe('Database connection failed');
@@ -1,3 +1,4 @@
1
+ import { z } from "zod";
1
2
  import Lambder from "../src/Lambder.js";
2
3
 
3
4
  // Example: Secure session handling with all security fixes applied
@@ -6,10 +7,9 @@ const lambder = new Lambder({
6
7
  publicPath: "/public",
7
8
  apiPath: "/api",
8
9
  ejsPath: "/views",
9
- });
10
-
10
+ })
11
11
  // Enable session management with sliding expiration
12
- lambder.enableDdbSession(
12
+ .enableDdbSession(
13
13
  {
14
14
  tableName: process.env.SESSION_TABLE || "sessions",
15
15
  tableRegion: process.env.AWS_REGION || "us-east-1",
@@ -17,10 +17,12 @@ lambder.enableDdbSession(
17
17
  enableSlidingExpiration: true, // Sessions extend on each access
18
18
  },
19
19
  { partitionKey: "pk", sortKey: "sk" }
20
- );
21
-
20
+ )
22
21
  // Example: Login API with session regeneration
23
- lambder.addApi("user.login", async (ctx, resolver) => {
22
+ .addApi("user.login", {
23
+ input: z.object({ username: z.string(), password: z.string() }),
24
+ output: z.object({ success: z.boolean(), csrfToken: z.string().optional(), error: z.string().optional() })
25
+ }, async (ctx, resolver) => {
24
26
  const { username, password } = ctx.apiPayload;
25
27
 
26
28
  // Validate credentials (implement your own logic)
@@ -41,10 +43,12 @@ lambder.addApi("user.login", async (ctx, resolver) => {
41
43
  success: true,
42
44
  csrfToken: session.csrfToken,
43
45
  });
44
- });
45
-
46
+ })
46
47
  // Example: Protected API that requires session
47
- lambder.addSessionApi("user.profile", async (ctx, resolver) => {
48
+ .addSessionApi("user.profile", {
49
+ input: z.void(),
50
+ output: z.object({ userId: z.string(), username: z.string(), role: z.string() })
51
+ }, async (ctx, resolver) => {
48
52
  // Session is automatically fetched and validated
49
53
  const sessionData = ctx.session.data;
50
54
 
@@ -53,10 +57,12 @@ lambder.addSessionApi("user.profile", async (ctx, resolver) => {
53
57
  username: sessionData.username,
54
58
  role: sessionData.role,
55
59
  });
56
- });
57
-
60
+ })
58
61
  // Example: Sensitive operation that regenerates session
59
- lambder.addSessionApi("user.changePassword", async (ctx, resolver) => {
62
+ .addSessionApi("user.changePassword", {
63
+ input: z.object({ oldPassword: z.string(), newPassword: z.string() }),
64
+ output: z.object({ success: z.boolean(), message: z.string().optional(), csrfToken: z.string().optional(), error: z.string().optional() })
65
+ }, async (ctx, resolver) => {
60
66
  const { oldPassword, newPassword } = ctx.apiPayload;
61
67
  const sessionController = lambder.getSessionController(ctx);
62
68
 
@@ -83,10 +89,12 @@ lambder.addSessionApi("user.changePassword", async (ctx, resolver) => {
83
89
  message: "Password changed successfully",
84
90
  csrfToken: newSession.csrfToken, // Send new CSRF token
85
91
  });
86
- });
87
-
92
+ })
88
93
  // Example: Update session data
89
- lambder.addSessionApi("user.updatePreferences", async (ctx, resolver) => {
94
+ .addSessionApi("user.updatePreferences", {
95
+ input: z.object({ theme: z.string(), language: z.string() }),
96
+ output: z.object({ success: z.boolean(), message: z.string() })
97
+ }, async (ctx, resolver) => {
90
98
  const { theme, language } = ctx.apiPayload;
91
99
  const sessionController = lambder.getSessionController(ctx);
92
100
 
@@ -100,10 +108,12 @@ lambder.addSessionApi("user.updatePreferences", async (ctx, resolver) => {
100
108
  success: true,
101
109
  message: "Preferences updated",
102
110
  });
103
- });
104
-
111
+ })
105
112
  // Example: Logout
106
- lambder.addSessionApi("user.logout", async (ctx, resolver) => {
113
+ .addSessionApi("user.logout", {
114
+ input: z.void(),
115
+ output: z.object({ success: z.boolean(), message: z.string() })
116
+ }, async (ctx, resolver) => {
107
117
  const sessionController = lambder.getSessionController(ctx);
108
118
 
109
119
  // End current session
@@ -113,10 +123,12 @@ lambder.addSessionApi("user.logout", async (ctx, resolver) => {
113
123
  success: true,
114
124
  message: "Logged out successfully",
115
125
  });
116
- });
117
-
126
+ })
118
127
  // Example: Logout from all devices
119
- lambder.addSessionApi("user.logoutAll", async (ctx, resolver) => {
128
+ .addSessionApi("user.logoutAll", {
129
+ input: z.void(),
130
+ output: z.object({ success: z.boolean(), message: z.string() })
131
+ }, async (ctx, resolver) => {
120
132
  const sessionController = lambder.getSessionController(ctx);
121
133
 
122
134
  // End all sessions for this user (same sessionKey)
@@ -126,10 +138,16 @@ lambder.addSessionApi("user.logoutAll", async (ctx, resolver) => {
126
138
  success: true,
127
139
  message: "Logged out from all devices",
128
140
  });
129
- });
130
-
141
+ })
131
142
  // Example: Optional session (check if logged in)
132
- lambder.addApi("user.checkAuth", async (ctx, resolver) => {
143
+ .addApi("user.checkAuth", {
144
+ input: z.object({}),
145
+ output: z.object({
146
+ authenticated: z.boolean(),
147
+ userId: z.string().optional(),
148
+ username: z.string().optional(),
149
+ })
150
+ }, async (ctx, resolver) => {
133
151
  const sessionController = lambder.getSessionController(ctx);
134
152
 
135
153
  // Try to fetch session without throwing error
@@ -141,15 +159,14 @@ lambder.addApi("user.checkAuth", async (ctx, resolver) => {
141
159
  userId: session.data.userId,
142
160
  username: session.data.username,
143
161
  });
162
+ } else {
163
+ return resolver.api({
164
+ authenticated: false,
165
+ });
144
166
  }
145
-
146
- return resolver.api({
147
- authenticated: false,
148
- });
149
- });
150
-
167
+ })
151
168
  // Example: Route with session
152
- lambder.addSessionRoute("/dashboard", async (ctx, resolver) => {
169
+ .addSessionRoute("/dashboard", async (ctx, resolver) => {
153
170
  // Session is automatically fetched and validated
154
171
  const userData = ctx.session.data;
155
172
 
@@ -157,10 +174,9 @@ lambder.addSessionRoute("/dashboard", async (ctx, resolver) => {
157
174
  user: userData,
158
175
  csrfToken: ctx.session.csrfToken,
159
176
  });
160
- });
161
-
177
+ })
162
178
  // Example: Route with optional session
163
- lambder.addRoute("/", async (ctx, resolver) => {
179
+ .addRoute("/", async (ctx, resolver) => {
164
180
  const sessionController = lambder.getSessionController(ctx);
165
181
  const session = await sessionController.fetchSessionIfExists();
166
182
 
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ import Lambder from "../src/index.js";
3
+
4
+ // 1. Define reusable schemas
5
+ const UserSchema = z.object({
6
+ id: z.string(),
7
+ name: z.string(),
8
+ email: z.string().email(),
9
+ });
10
+
11
+ const CreateUserSchema = z.object({
12
+ name: z.string(),
13
+ email: z.string().email(),
14
+ });
15
+
16
+ // 2. Initialize Lambder and chain APIs
17
+ const lambder = new Lambder({
18
+ publicPath: "./public",
19
+ apiPath: "/api"
20
+ })
21
+ .addApi("getUser", {
22
+ input: z.object({ userId: z.string() }),
23
+ output: UserSchema
24
+ }, async (ctx, resolver) => {
25
+ // ctx.apiPayload is typed as { userId: string }
26
+ const { userId } = ctx.apiPayload;
27
+
28
+ return resolver.api({
29
+ id: userId,
30
+ name: "John Doe",
31
+ email: "john@example.com"
32
+ });
33
+ })
34
+ .addApi("createUser", {
35
+ input: CreateUserSchema,
36
+ output: UserSchema
37
+ }, async (ctx, resolver) => {
38
+ // ctx.apiPayload is typed as { name: string, email: string }
39
+ const { name, email } = ctx.apiPayload;
40
+
41
+ return resolver.api({
42
+ id: "123",
43
+ name,
44
+ email
45
+ });
46
+ });
47
+
48
+ // 3. Export the inferred contract type for Frontend
49
+ export type AppContract = typeof lambder.ApiContractType;
50
+
51
+ // 4. Modular example using .use()
52
+ const authApi = <T>(l: Lambder<T>) => {
53
+ return l.addApi("login", {
54
+ input: z.object({ username: z.string(), password: z.string() }),
55
+ output: z.object({ token: z.string() })
56
+ }, async (ctx, resolver) => {
57
+ return resolver.api({ token: "abc-123" });
58
+ });
59
+ };
60
+
61
+ const lambderWithAuth = lambder.use(authApi);
62
+
63
+ export type AuthContract = typeof lambderWithAuth.ApiContractType;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambder",
3
- "version": "1.0.147",
3
+ "version": "2.0.2",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -35,7 +35,8 @@
35
35
  "js-cookie": "^3.0.5",
36
36
  "mime-types": "^2.1.35",
37
37
  "path-to-regexp": "^6.2.1",
38
- "querystring": "^0.2.1"
38
+ "querystring": "^0.2.1",
39
+ "zod": "^4.1.12"
39
40
  },
40
41
  "peerDependencies": {
41
42
  "msw": "^2.0.0"