lambder 2.0.18 → 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.
Files changed (67) hide show
  1. package/Readme.md +162 -41
  2. package/dist/Lambder.d.ts +154 -46
  3. package/dist/Lambder.js +312 -166
  4. package/dist/LambderCaller.js +6 -3
  5. package/dist/LambderContext.d.ts +20 -9
  6. package/dist/LambderContext.js +57 -17
  7. package/dist/LambderCors.d.ts +12 -0
  8. package/dist/LambderCors.js +30 -0
  9. package/dist/LambderHtml.d.ts +33 -0
  10. package/dist/LambderHtml.js +62 -0
  11. package/dist/LambderMSW.d.ts +16 -1
  12. package/dist/LambderMSW.js +5 -9
  13. package/dist/LambderPublicFiles.d.ts +47 -0
  14. package/dist/LambderPublicFiles.js +108 -0
  15. package/dist/LambderResolver.d.ts +30 -31
  16. package/dist/LambderResolver.js +29 -43
  17. package/dist/LambderResponse.d.ts +71 -0
  18. package/dist/LambderResponse.js +196 -0
  19. package/dist/LambderResponseBuilder.d.ts +58 -33
  20. package/dist/LambderResponseBuilder.js +114 -167
  21. package/dist/LambderRouting.d.ts +23 -0
  22. package/dist/LambderRouting.js +67 -0
  23. package/dist/LambderSessionController.d.ts +13 -1
  24. package/dist/LambderSessionController.js +33 -10
  25. package/dist/LambderSessionManager.d.ts +3 -1
  26. package/dist/LambderSessionManager.js +15 -6
  27. package/dist/LambderTemplatingEngine.d.ts +87 -0
  28. package/dist/LambderTemplatingEngine.js +156 -0
  29. package/dist/index.d.ts +14 -2
  30. package/dist/index.js +10 -1
  31. package/dist/node-polyfills.d.ts +4 -2
  32. package/dist/node-polyfills.js +28 -0
  33. package/package.json +7 -5
  34. package/.eslintrc.cjs +0 -26
  35. package/.vscode/settings.json +0 -26
  36. package/deploy +0 -22
  37. package/dist/LambderUtils.d.ts +0 -10
  38. package/dist/LambderUtils.js +0 -70
  39. package/docs/DYNAMODB_SETUP.md +0 -96
  40. package/docs/LAMBDER_MSW.md +0 -409
  41. package/docs/TYPE_SAFE_QUICK_START.md +0 -77
  42. package/examples/msw-testing-example.ts +0 -280
  43. package/examples/secure-session-example.ts +0 -207
  44. package/examples/zod-chained-api-example.ts +0 -63
  45. package/src/Lambder.ts +0 -430
  46. package/src/LambderApiContract.ts +0 -20
  47. package/src/LambderCaller.ts +0 -238
  48. package/src/LambderContext.ts +0 -78
  49. package/src/LambderMSW.ts +0 -180
  50. package/src/LambderResolver.ts +0 -101
  51. package/src/LambderResponseBuilder.ts +0 -332
  52. package/src/LambderSessionController.ts +0 -114
  53. package/src/LambderSessionManager.ts +0 -217
  54. package/src/LambderUtils.ts +0 -75
  55. package/src/index.ts +0 -17
  56. package/src/node-polyfills.ts +0 -27
  57. package/tests/error-handling.test.ts +0 -585
  58. package/tests/file-serving.test.ts +0 -194
  59. package/tests/fixtures/public/index.html +0 -1
  60. package/tests/fixtures/public/main.css +0 -1
  61. package/tests/hooks.test.ts +0 -561
  62. package/tests/output-type-runtime.test.ts +0 -381
  63. package/tests/redirect.test.ts +0 -88
  64. package/tests/routes.test.ts +0 -543
  65. package/tests/session.test.ts +0 -1083
  66. package/tests/use-plugin.test.ts +0 -460
  67. 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;