lambder 2.0.16 → 3.0.0
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 +162 -41
- package/dist/Lambder.d.ts +154 -46
- package/dist/Lambder.js +312 -166
- package/dist/LambderCaller.js +6 -3
- package/dist/LambderContext.d.ts +20 -9
- package/dist/LambderContext.js +57 -17
- package/dist/LambderCors.d.ts +12 -0
- package/dist/LambderCors.js +30 -0
- package/dist/LambderHtml.d.ts +33 -0
- package/dist/LambderHtml.js +62 -0
- package/dist/LambderMSW.d.ts +16 -1
- package/dist/LambderMSW.js +5 -9
- package/dist/LambderPublicFiles.d.ts +47 -0
- package/dist/LambderPublicFiles.js +108 -0
- package/dist/LambderResolver.d.ts +30 -31
- package/dist/LambderResolver.js +29 -43
- package/dist/LambderResponse.d.ts +71 -0
- package/dist/LambderResponse.js +196 -0
- package/dist/LambderResponseBuilder.d.ts +58 -33
- package/dist/LambderResponseBuilder.js +114 -167
- package/dist/LambderRouting.d.ts +23 -0
- package/dist/LambderRouting.js +67 -0
- package/dist/LambderSessionController.d.ts +13 -1
- package/dist/LambderSessionController.js +33 -10
- package/dist/LambderSessionManager.d.ts +3 -1
- package/dist/LambderSessionManager.js +15 -6
- package/dist/LambderTemplatingEngine.d.ts +87 -0
- package/dist/LambderTemplatingEngine.js +156 -0
- package/dist/index.d.ts +14 -2
- package/dist/index.js +10 -1
- package/dist/node-polyfills.d.ts +4 -2
- package/dist/node-polyfills.js +28 -0
- package/package.json +7 -5
- package/.eslintrc.cjs +0 -26
- package/.vscode/settings.json +0 -26
- package/deploy +0 -22
- package/dist/LambderUtils.d.ts +0 -10
- package/dist/LambderUtils.js +0 -70
- package/docs/DYNAMODB_SETUP.md +0 -96
- package/docs/LAMBDER_MSW.md +0 -409
- package/docs/TYPE_SAFE_QUICK_START.md +0 -77
- package/examples/msw-testing-example.ts +0 -280
- package/examples/secure-session-example.ts +0 -207
- package/examples/zod-chained-api-example.ts +0 -63
- package/src/Lambder.ts +0 -430
- package/src/LambderApiContract.ts +0 -20
- package/src/LambderCaller.ts +0 -238
- package/src/LambderContext.ts +0 -78
- package/src/LambderMSW.ts +0 -180
- package/src/LambderResolver.ts +0 -101
- package/src/LambderResponseBuilder.ts +0 -332
- package/src/LambderSessionController.ts +0 -114
- package/src/LambderSessionManager.ts +0 -217
- package/src/LambderUtils.ts +0 -75
- package/src/index.ts +0 -17
- package/src/node-polyfills.ts +0 -27
- package/tests/error-handling.test.ts +0 -585
- package/tests/file-serving.test.ts +0 -194
- package/tests/fixtures/public/index.html +0 -1
- package/tests/fixtures/public/main.css +0 -1
- package/tests/hooks.test.ts +0 -561
- package/tests/output-type-runtime.test.ts +0 -381
- package/tests/redirect.test.ts +0 -88
- package/tests/routes.test.ts +0 -543
- package/tests/session.test.ts +0 -1083
- package/tests/use-plugin.test.ts +0 -460
- package/tsconfig.json +0 -24
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LambderMSW Testing Example
|
|
3
|
-
*
|
|
4
|
-
* This example shows how to use LambderMSW with MSW (Mock Service Worker)
|
|
5
|
-
* to test your Lambder APIs with full type safety.
|
|
6
|
-
*
|
|
7
|
-
* To run this example:
|
|
8
|
-
* 1. Install MSW: npm install msw --save-dev
|
|
9
|
-
* 2. Install a test runner: npm install vitest --save-dev
|
|
10
|
-
* 3. Create this file in your test directory
|
|
11
|
-
*
|
|
12
|
-
* NOTE: This is an example file. Type errors are expected since dependencies
|
|
13
|
-
* may not be installed in the examples directory. Copy this to your project's
|
|
14
|
-
* test directory to use it.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
// @ts-nocheck - Example file, types may not be available
|
|
18
|
-
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
|
|
19
|
-
import { setupServer } from 'msw/node';
|
|
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({ publicPath: './public' })
|
|
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 ApiContractType = typeof lambder.ApiContract;
|
|
44
|
-
|
|
45
|
-
// Setup LambderMSW with type safety
|
|
46
|
-
const lambderMSW = new LambderMSW<ApiContractType>({
|
|
47
|
-
apiPath: '/secure',
|
|
48
|
-
apiVersion: '1.0.0',
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// Create mock handlers
|
|
52
|
-
const handlers = [
|
|
53
|
-
// Mock getUserById - returns user or null
|
|
54
|
-
lambderMSW.mockApi('getUserById', async (payload) => {
|
|
55
|
-
const users: Record<string, { id: string; name: string; email: string }> = {
|
|
56
|
-
'1': { id: '1', name: 'John Doe', email: 'john@example.com' },
|
|
57
|
-
'2': { id: '2', name: 'Jane Smith', email: 'jane@example.com' },
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
return users[payload?.userId || ''] || null;
|
|
61
|
-
}),
|
|
62
|
-
|
|
63
|
-
// Mock createUser - simulates creating a user
|
|
64
|
-
lambderMSW.mockApi('createUser', async (payload) => {
|
|
65
|
-
return {
|
|
66
|
-
id: Math.random().toString(36).substring(7),
|
|
67
|
-
name: payload?.name || '',
|
|
68
|
-
email: payload?.email || '',
|
|
69
|
-
};
|
|
70
|
-
}, {
|
|
71
|
-
delay: 100, // Simulate 100ms network delay
|
|
72
|
-
message: 'User created successfully'
|
|
73
|
-
}),
|
|
74
|
-
|
|
75
|
-
// Mock getUsers - returns a list of users
|
|
76
|
-
lambderMSW.mockApi('getUsers', async (payload) => {
|
|
77
|
-
const allUsers = [
|
|
78
|
-
{ id: '1', name: 'John Doe', email: 'john@example.com' },
|
|
79
|
-
{ id: '2', name: 'Jane Smith', email: 'jane@example.com' },
|
|
80
|
-
{ id: '3', name: 'Bob Johnson', email: 'bob@example.com' },
|
|
81
|
-
];
|
|
82
|
-
|
|
83
|
-
const limit = payload?.limit || 10;
|
|
84
|
-
return allUsers.slice(0, limit);
|
|
85
|
-
}),
|
|
86
|
-
|
|
87
|
-
// Mock deleteUser - simulate authorization error
|
|
88
|
-
lambderMSW.mockNotAuthorized('deleteUser'),
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
// Setup MSW server
|
|
92
|
-
const server = setupServer(...handlers);
|
|
93
|
-
|
|
94
|
-
// Setup LambderCaller
|
|
95
|
-
const lambderCaller = new LambderCaller<ApiContractType>({
|
|
96
|
-
apiPath: '/secure',
|
|
97
|
-
isCorsEnabled: false,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// Test suite
|
|
101
|
-
describe('LambderMSW Testing Example', () => {
|
|
102
|
-
beforeAll(() => {
|
|
103
|
-
server.listen({ onUnhandledRequest: 'error' });
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
afterEach(() => {
|
|
107
|
-
server.resetHandlers();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
afterAll(() => {
|
|
111
|
-
server.close();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('should fetch user by id', async () => {
|
|
115
|
-
const user = await lambderCaller.api('getUserById', { userId: '1' });
|
|
116
|
-
|
|
117
|
-
expect(user).toEqual({
|
|
118
|
-
id: '1',
|
|
119
|
-
name: 'John Doe',
|
|
120
|
-
email: 'john@example.com',
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should return null for non-existent user', async () => {
|
|
125
|
-
const user = await lambderCaller.api('getUserById', { userId: '999' });
|
|
126
|
-
|
|
127
|
-
expect(user).toBeNull();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('should create a new user with delay', async () => {
|
|
131
|
-
const startTime = Date.now();
|
|
132
|
-
|
|
133
|
-
const newUser = await lambderCaller.api('createUser', {
|
|
134
|
-
name: 'Alice Wonder',
|
|
135
|
-
email: 'alice@example.com',
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
const endTime = Date.now();
|
|
139
|
-
|
|
140
|
-
expect(newUser?.name).toBe('Alice Wonder');
|
|
141
|
-
expect(newUser?.email).toBe('alice@example.com');
|
|
142
|
-
expect(newUser?.id).toBeDefined();
|
|
143
|
-
|
|
144
|
-
// Check that delay was applied (at least 100ms)
|
|
145
|
-
expect(endTime - startTime).toBeGreaterThanOrEqual(100);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('should fetch list of users', async () => {
|
|
149
|
-
const users = await lambderCaller.api('getUsers', { limit: 2 });
|
|
150
|
-
|
|
151
|
-
expect(users).toHaveLength(2);
|
|
152
|
-
expect(users?.[0]?.name).toBe('John Doe');
|
|
153
|
-
expect(users?.[1]?.name).toBe('Jane Smith');
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('should handle authorization error', async () => {
|
|
157
|
-
let errorCaught = false;
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
await lambderCaller.api('deleteUser', { userId: '1' });
|
|
161
|
-
} catch (error: any) {
|
|
162
|
-
errorCaught = true;
|
|
163
|
-
expect(error.notAuthorized).toBe(true);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
expect(errorCaught).toBe(true);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('should override handler for specific test', async () => {
|
|
170
|
-
// Override the getUserById handler for this test only
|
|
171
|
-
server.use(
|
|
172
|
-
lambderMSW.mockApi('getUserById', async (payload) => {
|
|
173
|
-
return {
|
|
174
|
-
id: payload?.userId || '',
|
|
175
|
-
name: 'Override User',
|
|
176
|
-
email: 'override@example.com',
|
|
177
|
-
};
|
|
178
|
-
})
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
const user = await lambderCaller.api('getUserById', { userId: '999' });
|
|
182
|
-
|
|
183
|
-
expect(user?.name).toBe('Override User');
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('should simulate session expired', async () => {
|
|
187
|
-
// Add a handler that simulates session expiration
|
|
188
|
-
server.use(
|
|
189
|
-
lambderMSW.mockSessionExpired('getUserById')
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
let errorCaught = false;
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
await lambderCaller.api('getUserById', { userId: '1' });
|
|
196
|
-
} catch (error: any) {
|
|
197
|
-
errorCaught = true;
|
|
198
|
-
expect(error.sessionExpired).toBe(true);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
expect(errorCaught).toBe(true);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('should simulate custom error', async () => {
|
|
205
|
-
server.use(
|
|
206
|
-
lambderMSW.mockError('createUser', 'Email already exists')
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
let errorCaught = false;
|
|
210
|
-
|
|
211
|
-
try {
|
|
212
|
-
await lambderCaller.api('createUser', {
|
|
213
|
-
name: 'Duplicate',
|
|
214
|
-
email: 'john@example.com',
|
|
215
|
-
});
|
|
216
|
-
} catch (error: any) {
|
|
217
|
-
errorCaught = true;
|
|
218
|
-
expect(error.errorMessage).toBe('Email already exists');
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
expect(errorCaught).toBe(true);
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
// Example: Testing with dynamic responses
|
|
226
|
-
describe('Dynamic Response Testing', () => {
|
|
227
|
-
beforeAll(() => server.listen());
|
|
228
|
-
afterEach(() => server.resetHandlers());
|
|
229
|
-
afterAll(() => server.close());
|
|
230
|
-
|
|
231
|
-
it('should handle different query parameters', async () => {
|
|
232
|
-
server.use(
|
|
233
|
-
lambderMSW.mockApi('getUsers', async (payload) => {
|
|
234
|
-
const limit = payload?.limit || 10;
|
|
235
|
-
|
|
236
|
-
// Generate mock users based on limit
|
|
237
|
-
return Array.from({ length: limit }, (_, i) => ({
|
|
238
|
-
id: `${i + 1}`,
|
|
239
|
-
name: `User ${i + 1}`,
|
|
240
|
-
email: `user${i + 1}@example.com`,
|
|
241
|
-
}));
|
|
242
|
-
})
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
const users3 = await lambderCaller.api('getUsers', { limit: 3 });
|
|
246
|
-
expect(users3).toHaveLength(3);
|
|
247
|
-
|
|
248
|
-
const users5 = await lambderCaller.api('getUsers', { limit: 5 });
|
|
249
|
-
expect(users5).toHaveLength(5);
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
// Example: Testing error conditions
|
|
254
|
-
describe('Error Handling', () => {
|
|
255
|
-
beforeAll(() => server.listen());
|
|
256
|
-
afterEach(() => server.resetHandlers());
|
|
257
|
-
afterAll(() => server.close());
|
|
258
|
-
|
|
259
|
-
it('should handle handler throwing error', async () => {
|
|
260
|
-
server.use(
|
|
261
|
-
lambderMSW.mockApi('getUserById', async () => {
|
|
262
|
-
throw new Error('Database connection failed');
|
|
263
|
-
})
|
|
264
|
-
);
|
|
265
|
-
|
|
266
|
-
let errorCaught = false;
|
|
267
|
-
|
|
268
|
-
try {
|
|
269
|
-
await lambderCaller.api('getUserById', { userId: '1' });
|
|
270
|
-
} catch (error: any) {
|
|
271
|
-
errorCaught = true;
|
|
272
|
-
expect(error.errorMessage).toBe('Database connection failed');
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
expect(errorCaught).toBe(true);
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
console.log('✅ LambderMSW example tests configured!');
|
|
280
|
-
console.log('📖 See docs/LAMBDER_MSW.md for more information');
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import Lambder from "../src/Lambder.js";
|
|
3
|
-
|
|
4
|
-
// Example: Secure session handling with all security fixes applied
|
|
5
|
-
|
|
6
|
-
const lambder = new Lambder({
|
|
7
|
-
publicPath: "/public",
|
|
8
|
-
apiPath: "/api",
|
|
9
|
-
ejsPath: "/views",
|
|
10
|
-
})
|
|
11
|
-
// Enable session management with sliding expiration
|
|
12
|
-
.enableDdbSession(
|
|
13
|
-
{
|
|
14
|
-
tableName: process.env.SESSION_TABLE || "sessions",
|
|
15
|
-
tableRegion: process.env.AWS_REGION || "us-east-1",
|
|
16
|
-
sessionSalt: process.env.SESSION_SALT || "change-this-to-a-secure-random-string",
|
|
17
|
-
enableSlidingExpiration: true, // Sessions extend on each access
|
|
18
|
-
},
|
|
19
|
-
{ partitionKey: "pk", sortKey: "sk" }
|
|
20
|
-
)
|
|
21
|
-
// Example: Login API with session regeneration
|
|
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) => {
|
|
26
|
-
const { username, password } = ctx.apiPayload;
|
|
27
|
-
|
|
28
|
-
// Validate credentials (implement your own logic)
|
|
29
|
-
const user = await authenticateUser(username, password);
|
|
30
|
-
if (!user) {
|
|
31
|
-
return resolver.api({ success: false, error: "Invalid credentials" });
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Create new session
|
|
35
|
-
const sessionController = lambder.getSessionController(ctx);
|
|
36
|
-
const session = await sessionController.createSession(user.id, {
|
|
37
|
-
userId: user.id,
|
|
38
|
-
username: user.username,
|
|
39
|
-
role: user.role,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
return resolver.api({
|
|
43
|
-
success: true,
|
|
44
|
-
csrfToken: session.csrfToken,
|
|
45
|
-
});
|
|
46
|
-
})
|
|
47
|
-
// Example: Protected API that requires session
|
|
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) => {
|
|
52
|
-
// Session is automatically fetched and validated
|
|
53
|
-
const sessionData = ctx.session.data;
|
|
54
|
-
|
|
55
|
-
return resolver.api({
|
|
56
|
-
userId: sessionData.userId,
|
|
57
|
-
username: sessionData.username,
|
|
58
|
-
role: sessionData.role,
|
|
59
|
-
});
|
|
60
|
-
})
|
|
61
|
-
// Example: Sensitive operation that regenerates session
|
|
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) => {
|
|
66
|
-
const { oldPassword, newPassword } = ctx.apiPayload;
|
|
67
|
-
const sessionController = lambder.getSessionController(ctx);
|
|
68
|
-
|
|
69
|
-
// Validate old password
|
|
70
|
-
const isValid = await validatePassword(
|
|
71
|
-
ctx.session.data.userId,
|
|
72
|
-
oldPassword
|
|
73
|
-
);
|
|
74
|
-
if (!isValid) {
|
|
75
|
-
return resolver.api({ success: false, error: "Invalid password" });
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Update password
|
|
79
|
-
await updatePassword(ctx.session.data.userId, newPassword);
|
|
80
|
-
|
|
81
|
-
// IMPORTANT: Regenerate session after password change to prevent session fixation
|
|
82
|
-
const newSession = await sessionController.regenerateSession();
|
|
83
|
-
|
|
84
|
-
// OPTIONAL: End all other sessions for this user
|
|
85
|
-
await sessionController.endSessionAll();
|
|
86
|
-
|
|
87
|
-
return resolver.api({
|
|
88
|
-
success: true,
|
|
89
|
-
message: "Password changed successfully",
|
|
90
|
-
csrfToken: newSession.csrfToken, // Send new CSRF token
|
|
91
|
-
});
|
|
92
|
-
})
|
|
93
|
-
// Example: Update session data
|
|
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) => {
|
|
98
|
-
const { theme, language } = ctx.apiPayload;
|
|
99
|
-
const sessionController = lambder.getSessionController(ctx);
|
|
100
|
-
|
|
101
|
-
// Update session data (also extends expiration if sliding is enabled)
|
|
102
|
-
await sessionController.updateSessionData({
|
|
103
|
-
...ctx.session.data,
|
|
104
|
-
preferences: { theme, language },
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
return resolver.api({
|
|
108
|
-
success: true,
|
|
109
|
-
message: "Preferences updated",
|
|
110
|
-
});
|
|
111
|
-
})
|
|
112
|
-
// Example: Logout
|
|
113
|
-
.addSessionApi("user.logout", {
|
|
114
|
-
input: z.void(),
|
|
115
|
-
output: z.object({ success: z.boolean(), message: z.string() })
|
|
116
|
-
}, async (ctx, resolver) => {
|
|
117
|
-
const sessionController = lambder.getSessionController(ctx);
|
|
118
|
-
|
|
119
|
-
// End current session
|
|
120
|
-
await sessionController.endSession();
|
|
121
|
-
|
|
122
|
-
return resolver.api({
|
|
123
|
-
success: true,
|
|
124
|
-
message: "Logged out successfully",
|
|
125
|
-
});
|
|
126
|
-
})
|
|
127
|
-
// Example: Logout from all devices
|
|
128
|
-
.addSessionApi("user.logoutAll", {
|
|
129
|
-
input: z.void(),
|
|
130
|
-
output: z.object({ success: z.boolean(), message: z.string() })
|
|
131
|
-
}, async (ctx, resolver) => {
|
|
132
|
-
const sessionController = lambder.getSessionController(ctx);
|
|
133
|
-
|
|
134
|
-
// End all sessions for this user (same sessionKey)
|
|
135
|
-
await sessionController.endSessionAll();
|
|
136
|
-
|
|
137
|
-
return resolver.api({
|
|
138
|
-
success: true,
|
|
139
|
-
message: "Logged out from all devices",
|
|
140
|
-
});
|
|
141
|
-
})
|
|
142
|
-
// Example: Optional session (check if logged in)
|
|
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) => {
|
|
151
|
-
const sessionController = lambder.getSessionController(ctx);
|
|
152
|
-
|
|
153
|
-
// Try to fetch session without throwing error
|
|
154
|
-
const session = await sessionController.fetchSessionIfExists();
|
|
155
|
-
|
|
156
|
-
if (session) {
|
|
157
|
-
return resolver.api({
|
|
158
|
-
authenticated: true,
|
|
159
|
-
userId: session.data.userId,
|
|
160
|
-
username: session.data.username,
|
|
161
|
-
});
|
|
162
|
-
} else {
|
|
163
|
-
return resolver.api({
|
|
164
|
-
authenticated: false,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
})
|
|
168
|
-
// Example: Route with session
|
|
169
|
-
.addSessionRoute("/dashboard", async (ctx, resolver) => {
|
|
170
|
-
// Session is automatically fetched and validated
|
|
171
|
-
const userData = ctx.session.data;
|
|
172
|
-
|
|
173
|
-
return resolver.ejsFile("dashboard.ejs", {
|
|
174
|
-
user: userData,
|
|
175
|
-
csrfToken: ctx.session.csrfToken,
|
|
176
|
-
});
|
|
177
|
-
})
|
|
178
|
-
// Example: Route with optional session
|
|
179
|
-
.addRoute("/", async (ctx, resolver) => {
|
|
180
|
-
const sessionController = lambder.getSessionController(ctx);
|
|
181
|
-
const session = await sessionController.fetchSessionIfExists();
|
|
182
|
-
|
|
183
|
-
return resolver.ejsFile("home.ejs", {
|
|
184
|
-
isLoggedIn: !!session,
|
|
185
|
-
user: session?.data,
|
|
186
|
-
csrfToken: session?.csrfToken,
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// Dummy functions (implement these)
|
|
191
|
-
async function authenticateUser(username: string, password: string) {
|
|
192
|
-
// Implement your authentication logic
|
|
193
|
-
return { id: "user123", username, role: "user" };
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async function validatePassword(userId: string, password: string) {
|
|
197
|
-
// Implement password validation
|
|
198
|
-
return true;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
async function updatePassword(userId: string, newPassword: string) {
|
|
202
|
-
// Implement password update
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export const handler = async (event: any, context: any) => {
|
|
206
|
-
return await lambder.render(event, context);
|
|
207
|
-
};
|
|
@@ -1,63 +0,0 @@
|
|
|
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 use
|
|
49
|
-
export type ApiContractType = typeof lambder.ApiContract;
|
|
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.ApiContract;
|