lambder 1.0.128 → 1.0.129

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Output Type Enforcement Example
3
+ *
4
+ * This example demonstrates how Lambder now enforces output types
5
+ * in addition to input types for type-safe APIs.
6
+ */
7
+
8
+ import Lambder from '../src/Lambder.js';
9
+ import type { ApiContract } from '../src/index.js';
10
+
11
+ // ============================================================================
12
+ // Define Types
13
+ // ============================================================================
14
+
15
+ type User = {
16
+ id: string;
17
+ name: string;
18
+ email: string;
19
+ age: number;
20
+ };
21
+
22
+ type Product = {
23
+ id: string;
24
+ name: string;
25
+ price: number;
26
+ inStock: boolean;
27
+ };
28
+
29
+ type Stats = {
30
+ totalUsers: number;
31
+ totalProducts: number;
32
+ revenue: number;
33
+ };
34
+
35
+ // ============================================================================
36
+ // Define API Contract
37
+ // ============================================================================
38
+
39
+ type MyApiContract = ApiContract<{
40
+ // Simple primitive outputs
41
+ getCount: { input: void, output: number },
42
+ getMessage: { input: { id: string }, output: string },
43
+ isActive: { input: void, output: boolean },
44
+
45
+ // Complex object outputs
46
+ getUser: { input: { userId: string }, output: User },
47
+ getProduct: { input: { productId: string }, output: Product },
48
+
49
+ // Array outputs
50
+ listUsers: { input: void, output: User[] },
51
+ listProducts: { input: { category: string }, output: Product[] },
52
+
53
+ // Complex nested outputs
54
+ getStats: { input: void, output: Stats },
55
+
56
+ // Nullable outputs
57
+ findUser: { input: { email: string }, output: User | null },
58
+ }>;
59
+
60
+ // ============================================================================
61
+ // Setup Backend with Type Enforcement
62
+ // ============================================================================
63
+
64
+ const lambder = new Lambder<MyApiContract>({
65
+ publicPath: './public',
66
+ apiPath: '/api',
67
+ });
68
+
69
+ // ✅ EXAMPLE 1: Simple primitive outputs are enforced
70
+ lambder.addApi('getCount', async (ctx, resolver) => {
71
+ const count = 42;
72
+ return resolver.api(count); // ✅ TypeScript validates this is a number
73
+ });
74
+
75
+ lambder.addApi('getMessage', async (ctx, resolver) => {
76
+ const message = `Message for ${ctx.apiPayload.id}`;
77
+ return resolver.api(message); // ✅ TypeScript validates this is a string
78
+ });
79
+
80
+ lambder.addApi('isActive', async (ctx, resolver) => {
81
+ const active = true;
82
+ return resolver.api(active); // ✅ TypeScript validates this is a boolean
83
+ });
84
+
85
+ // ✅ EXAMPLE 2: Complex objects must match the shape
86
+ lambder.addApi('getUser', async (ctx, resolver) => {
87
+ const user: User = {
88
+ id: ctx.apiPayload.userId,
89
+ name: "John Doe",
90
+ email: "john@example.com",
91
+ age: 30,
92
+ };
93
+ return resolver.api(user); // ✅ All required fields are present
94
+ });
95
+
96
+ lambder.addApi('getProduct', async (ctx, resolver) => {
97
+ const product: Product = {
98
+ id: ctx.apiPayload.productId,
99
+ name: "Widget",
100
+ price: 99.99,
101
+ inStock: true,
102
+ };
103
+ return resolver.api(product); // ✅ Correct shape
104
+ });
105
+
106
+ // ✅ EXAMPLE 3: Arrays are type-checked
107
+ lambder.addApi('listUsers', async (ctx, resolver) => {
108
+ const users: User[] = [
109
+ { id: "1", name: "Alice", email: "alice@example.com", age: 25 },
110
+ { id: "2", name: "Bob", email: "bob@example.com", age: 30 },
111
+ ];
112
+ return resolver.api(users); // ✅ Array of User objects
113
+ });
114
+
115
+ lambder.addApi('listProducts', async (ctx, resolver) => {
116
+ const category = ctx.apiPayload.category;
117
+ const products: Product[] = [
118
+ { id: "1", name: "Item 1", price: 10, inStock: true },
119
+ { id: "2", name: "Item 2", price: 20, inStock: false },
120
+ ];
121
+ return resolver.api(products); // ✅ Array of Product objects
122
+ });
123
+
124
+ // ✅ EXAMPLE 4: Nested objects are validated
125
+ lambder.addApi('getStats', async (ctx, resolver) => {
126
+ const stats: Stats = {
127
+ totalUsers: 100,
128
+ totalProducts: 50,
129
+ revenue: 10000,
130
+ };
131
+ return resolver.api(stats); // ✅ Matches Stats shape
132
+ });
133
+
134
+ // ✅ EXAMPLE 5: Nullable types work correctly
135
+ lambder.addApi('findUser', async (ctx, resolver) => {
136
+ const email = ctx.apiPayload.email;
137
+
138
+ if (email === "john@example.com") {
139
+ // Found user
140
+ const user: User = {
141
+ id: "123",
142
+ name: "John",
143
+ email: email,
144
+ age: 30,
145
+ };
146
+ return resolver.api(user); // ✅ Can return User
147
+ } else {
148
+ // Not found
149
+ return resolver.api(null); // ✅ Can return null
150
+ }
151
+ });
152
+
153
+ // ✅ EXAMPLE 6: Using die.api() also enforces types
154
+ lambder.addApi('getCount', async (ctx, resolver) => {
155
+ return resolver.die.api(42); // ✅ die.api() also type-checks
156
+ });
157
+
158
+ // ✅ EXAMPLE 7: Session APIs also enforce output types
159
+ lambder.addSessionApi('getUser', async (ctx, resolver) => {
160
+ const user: User = {
161
+ id: ctx.apiPayload.userId,
162
+ name: "Jane Doe",
163
+ email: "jane@example.com",
164
+ age: 28,
165
+ };
166
+ return resolver.api(user); // ✅ Type-safe for session APIs too
167
+ });
168
+
169
+ // ============================================================================
170
+ // What TypeScript Prevents (these would cause compile errors)
171
+ // ============================================================================
172
+
173
+ /*
174
+ // ❌ ERROR: Wrong primitive type
175
+ lambder.addApi('getCount', async (ctx, resolver) => {
176
+ return resolver.api("not a number"); // Error: string not assignable to number
177
+ });
178
+
179
+ // ❌ ERROR: Missing required field
180
+ lambder.addApi('getUser', async (ctx, resolver) => {
181
+ return resolver.api({
182
+ id: "123",
183
+ name: "John",
184
+ email: "john@example.com",
185
+ // age is missing! - TypeScript error
186
+ });
187
+ });
188
+
189
+ // ❌ ERROR: Wrong field type
190
+ lambder.addApi('getUser', async (ctx, resolver) => {
191
+ return resolver.api({
192
+ id: "123",
193
+ name: "John",
194
+ email: "john@example.com",
195
+ age: "30", // Error: string not assignable to number
196
+ });
197
+ });
198
+
199
+ // ❌ ERROR: Wrong array element type
200
+ lambder.addApi('listUsers', async (ctx, resolver) => {
201
+ return resolver.api([
202
+ { id: "1", name: "Alice", email: "alice@example.com", age: 25 },
203
+ "not a user object", // Error: string not assignable to User
204
+ ]);
205
+ });
206
+
207
+ // ❌ ERROR: Returning wrong type when null is not allowed
208
+ lambder.addApi('getUser', async (ctx, resolver) => {
209
+ return resolver.api(null); // Error: null not assignable to User (findUser allows null, but getUser doesn't)
210
+ });
211
+ */
212
+
213
+ console.log("✅ All examples demonstrate proper output type enforcement!");
214
+ console.log("✅ TypeScript will catch type mismatches at compile time!");
215
+ console.log("✅ This works with both resolver.api() and resolver.die.api()!");
216
+ console.log("✅ Both addApi() and addSessionApi() enforce output types!");
217
+
218
+ export default lambder;
@@ -0,0 +1,191 @@
1
+ import Lambder from "../src/Lambder.js";
2
+
3
+ // Example: Secure session handling with all security fixes applied
4
+
5
+ const lambder = new Lambder({
6
+ publicPath: "/public",
7
+ apiPath: "/api",
8
+ ejsPath: "/views",
9
+ });
10
+
11
+ // Enable session management with sliding expiration
12
+ lambder.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
+
22
+ // Example: Login API with session regeneration
23
+ lambder.addApi("user.login", async (ctx, resolver) => {
24
+ const { username, password } = ctx.apiPayload;
25
+
26
+ // Validate credentials (implement your own logic)
27
+ const user = await authenticateUser(username, password);
28
+ if (!user) {
29
+ return resolver.api({ success: false, error: "Invalid credentials" });
30
+ }
31
+
32
+ // Create new session
33
+ const sessionController = lambder.getSessionController(ctx);
34
+ await sessionController.createSession(user.id, {
35
+ userId: user.id,
36
+ username: user.username,
37
+ role: user.role,
38
+ });
39
+
40
+ return resolver.api({
41
+ success: true,
42
+ csrfToken: ctx.session?.csrfToken,
43
+ });
44
+ });
45
+
46
+ // Example: Protected API that requires session
47
+ lambder.addSessionApi("user.profile", async (ctx, resolver) => {
48
+ // Session is automatically fetched and validated
49
+ const sessionData = ctx.session?.data;
50
+
51
+ return resolver.api({
52
+ userId: sessionData.userId,
53
+ username: sessionData.username,
54
+ role: sessionData.role,
55
+ });
56
+ });
57
+
58
+ // Example: Sensitive operation that regenerates session
59
+ lambder.addSessionApi("user.changePassword", async (ctx, resolver) => {
60
+ const { oldPassword, newPassword } = ctx.apiPayload;
61
+ const sessionController = lambder.getSessionController(ctx);
62
+
63
+ // Validate old password
64
+ const isValid = await validatePassword(
65
+ ctx.session?.data.userId,
66
+ oldPassword
67
+ );
68
+ if (!isValid) {
69
+ return resolver.api({ success: false, error: "Invalid password" });
70
+ }
71
+
72
+ // Update password
73
+ await updatePassword(ctx.session?.data.userId, newPassword);
74
+
75
+ // IMPORTANT: Regenerate session after password change to prevent session fixation
76
+ await sessionController.regenerateSession();
77
+
78
+ // OPTIONAL: End all other sessions for this user
79
+ await sessionController.endSessionAll();
80
+
81
+ return resolver.api({
82
+ success: true,
83
+ message: "Password changed successfully",
84
+ csrfToken: ctx.session?.csrfToken, // Send new CSRF token
85
+ });
86
+ });
87
+
88
+ // Example: Update session data
89
+ lambder.addSessionApi("user.updatePreferences", async (ctx, resolver) => {
90
+ const { theme, language } = ctx.apiPayload;
91
+ const sessionController = lambder.getSessionController(ctx);
92
+
93
+ // Update session data (also extends expiration if sliding is enabled)
94
+ await sessionController.updateSessionData({
95
+ ...ctx.session?.data,
96
+ preferences: { theme, language },
97
+ });
98
+
99
+ return resolver.api({
100
+ success: true,
101
+ message: "Preferences updated",
102
+ });
103
+ });
104
+
105
+ // Example: Logout
106
+ lambder.addSessionApi("user.logout", async (ctx, resolver) => {
107
+ const sessionController = lambder.getSessionController(ctx);
108
+
109
+ // End current session
110
+ await sessionController.endSession();
111
+
112
+ return resolver.api({
113
+ success: true,
114
+ message: "Logged out successfully",
115
+ });
116
+ });
117
+
118
+ // Example: Logout from all devices
119
+ lambder.addSessionApi("user.logoutAll", async (ctx, resolver) => {
120
+ const sessionController = lambder.getSessionController(ctx);
121
+
122
+ // End all sessions for this user (same sessionKey)
123
+ await sessionController.endSessionAll();
124
+
125
+ return resolver.api({
126
+ success: true,
127
+ message: "Logged out from all devices",
128
+ });
129
+ });
130
+
131
+ // Example: Optional session (check if logged in)
132
+ lambder.addApi("user.checkAuth", async (ctx, resolver) => {
133
+ const sessionController = lambder.getSessionController(ctx);
134
+
135
+ // Try to fetch session without throwing error
136
+ const session = await sessionController.fetchSessionIfExists();
137
+
138
+ if (session) {
139
+ return resolver.api({
140
+ authenticated: true,
141
+ userId: session.data.userId,
142
+ username: session.data.username,
143
+ });
144
+ }
145
+
146
+ return resolver.api({
147
+ authenticated: false,
148
+ });
149
+ });
150
+
151
+ // Example: Route with session
152
+ lambder.addSessionRoute("/dashboard", async (ctx, resolver) => {
153
+ // Session is automatically fetched and validated
154
+ const userData = ctx.session?.data;
155
+
156
+ return resolver.ejsFile("dashboard.ejs", {
157
+ user: userData,
158
+ csrfToken: ctx.session?.csrfToken,
159
+ });
160
+ });
161
+
162
+ // Example: Route with optional session
163
+ lambder.addRoute("/", async (ctx, resolver) => {
164
+ const sessionController = lambder.getSessionController(ctx);
165
+ const session = await sessionController.fetchSessionIfExists();
166
+
167
+ return resolver.ejsFile("home.ejs", {
168
+ isLoggedIn: !!session,
169
+ user: session?.data,
170
+ csrfToken: session?.csrfToken,
171
+ });
172
+ });
173
+
174
+ // Dummy functions (implement these)
175
+ async function authenticateUser(username: string, password: string) {
176
+ // Implement your authentication logic
177
+ return { id: "user123", username, role: "user" };
178
+ }
179
+
180
+ async function validatePassword(userId: string, password: string) {
181
+ // Implement password validation
182
+ return true;
183
+ }
184
+
185
+ async function updatePassword(userId: string, newPassword: string) {
186
+ // Implement password update
187
+ }
188
+
189
+ export const handler = async (event: any, context: any) => {
190
+ return await lambder.render(event, context);
191
+ };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Test file to verify output type enforcement
3
+ * This file should have TypeScript errors if output types are not correct
4
+ */
5
+
6
+ import Lambder from '../src/Lambder.js';
7
+ import type { ApiContract } from '../src/index.js';
8
+
9
+ // Define a simple API contract
10
+ type TestContract = ApiContract<{
11
+ getNumber: { input: void, output: number },
12
+ getString: { input: { id: string }, output: string },
13
+ getUser: { input: { userId: string }, output: { id: string; name: string; age: number } },
14
+ }>;
15
+
16
+ const lambder = new Lambder<TestContract>({
17
+ publicPath: './public',
18
+ apiPath: '/api',
19
+ });
20
+
21
+ // ✅ CORRECT: Returns the right type
22
+ lambder.addApi('getNumber', async (ctx, resolver) => {
23
+ // This should work - returning number
24
+ return resolver.api(42);
25
+ });
26
+
27
+ // ❌ INCORRECT: Should cause TypeScript error - returning wrong type
28
+ lambder.addApi('getNumber', async (ctx, resolver) => {
29
+ // @ts-expect-error - Testing type enforcement: should not accept string when output is number
30
+ return resolver.api("wrong type");
31
+ });
32
+
33
+ // ✅ CORRECT: Returns string
34
+ lambder.addApi('getString', async (ctx, resolver) => {
35
+ const id = ctx.apiPayload.id;
36
+ return resolver.api(`String for ${id}`);
37
+ });
38
+
39
+ // ❌ INCORRECT: Should cause TypeScript error - returning wrong type
40
+ lambder.addApi('getString', async (ctx, resolver) => {
41
+ // @ts-expect-error - Testing type enforcement: should not accept number when output is string
42
+ return resolver.api(123);
43
+ });
44
+
45
+ // ✅ CORRECT: Returns correct object shape
46
+ lambder.addApi('getUser', async (ctx, resolver) => {
47
+ return resolver.api({
48
+ id: ctx.apiPayload.userId,
49
+ name: "John Doe",
50
+ age: 30,
51
+ });
52
+ });
53
+
54
+ // ❌ INCORRECT: Should cause TypeScript error - missing required field
55
+ lambder.addApi('getUser', async (ctx, resolver) => {
56
+ // @ts-expect-error - Testing type enforcement: missing 'age' field
57
+ return resolver.api({
58
+ id: ctx.apiPayload.userId,
59
+ name: "John Doe",
60
+ // age is missing
61
+ });
62
+ });
63
+
64
+ // ❌ INCORRECT: Should cause TypeScript error - wrong field type
65
+ lambder.addApi('getUser', async (ctx, resolver) => {
66
+ return resolver.api({
67
+ id: ctx.apiPayload.userId,
68
+ name: "John Doe",
69
+ // @ts-expect-error - Testing type enforcement: age should be number, not string
70
+ age: "30", // wrong type
71
+ });
72
+ });
73
+
74
+ // ✅ CORRECT: Using null is allowed
75
+ lambder.addApi('getNumber', async (ctx, resolver) => {
76
+ return resolver.api(null);
77
+ });
78
+
79
+ // ✅ CORRECT: Using resolver.die.api also enforces types
80
+ lambder.addApi('getNumber', async (ctx, resolver) => {
81
+ return resolver.die.api(42);
82
+ });
83
+
84
+ // ❌ INCORRECT: resolver.die.api should also enforce types
85
+ lambder.addApi('getNumber', async (ctx, resolver) => {
86
+ // @ts-expect-error - Testing type enforcement: die.api should also check types
87
+ return resolver.die.api("wrong type");
88
+ });
89
+
90
+ // Test with addSessionApi
91
+ lambder.addSessionApi('getString', async (ctx, resolver) => {
92
+ // Should also enforce output type for session APIs
93
+ return resolver.api("correct string");
94
+ });
95
+
96
+ lambder.addSessionApi('getString', async (ctx, resolver) => {
97
+ // @ts-expect-error - Testing type enforcement: session API should also enforce types
98
+ return resolver.api(123);
99
+ });
100
+
101
+ console.log("If this file compiles with @ts-expect-error comments, output type enforcement is working!");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambder",
3
- "version": "1.0.128",
3
+ "version": "1.0.129",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/Lambder.ts CHANGED
@@ -140,11 +140,11 @@ export default class Lambder<TContract extends ApiContractShape = any> {
140
140
  }
141
141
 
142
142
  enableDdbSession(
143
- { tableName, tableRegion, sessionSalt }: { tableName: string; tableRegion: string; sessionSalt: string; },
143
+ { tableName, tableRegion, sessionSalt, enableSlidingExpiration }: { tableName: string; tableRegion: string; sessionSalt: string; enableSlidingExpiration?: boolean; },
144
144
  { partitionKey, sortKey }: { partitionKey: string, sortKey: string } = { partitionKey: "pk", sortKey: "sk" }
145
145
  ){
146
146
  this.lambderSessionManager = new LambderSessionManager({
147
- tableName, tableRegion, partitionKey, sortKey, sessionSalt
147
+ tableName, tableRegion, partitionKey, sortKey, sessionSalt, enableSlidingExpiration
148
148
  });
149
149
  }
150
150
 
@@ -249,7 +249,7 @@ export default class Lambder<TContract extends ApiContractShape = any> {
249
249
  apiName: TApiName,
250
250
  actionFn: (
251
251
  ctx: LambderRenderContext<TContract[TApiName]['input']>,
252
- resolver: LambderResolver
252
+ resolver: LambderResolver<TContract, TApiName>
253
253
  ) => LambderResolverResponse|Promise<LambderResolverResponse>
254
254
  ):void;
255
255
  // Overload for untyped API with string (backward compatibility, must be last)
@@ -281,7 +281,7 @@ export default class Lambder<TContract extends ApiContractShape = any> {
281
281
  apiName: TApiName,
282
282
  actionFn: (
283
283
  ctx: LambderRenderContext<TContract[TApiName]['input']>,
284
- resolver: LambderResolver
284
+ resolver: LambderResolver<TContract, TApiName>
285
285
  ) => LambderResolverResponse|Promise<LambderResolverResponse>
286
286
  ):void;
287
287
  // Overload for untyped session API with string (backward compatibility, must be last)
@@ -1,28 +1,36 @@
1
1
  import type { LambderRenderContext } from "./Lambder.js";
2
2
  import LambderResponseBuilder, { LambderResolverResponse } from "./LambderResponseBuilder.js";
3
3
  import LambderUtils from "./LambderUtils.js";
4
+ import type { ApiContractShape, ApiOutput } from "./LambderApiContract.js";
4
5
 
5
6
  type MethodType<T, M extends keyof T> = T[M] extends (...args: any[]) => any ? T[M] : never;
6
7
 
7
- interface DieResolverMethods {
8
- raw: MethodType<LambderResponseBuilder, 'raw'>;
9
- json: MethodType<LambderResponseBuilder, 'json'>;
10
- xml: MethodType<LambderResponseBuilder, 'xml'>;
11
- html: MethodType<LambderResponseBuilder, 'html'>;
12
- status301: MethodType<LambderResponseBuilder, 'status301'>;
13
- status404: MethodType<LambderResponseBuilder, 'status404'>;
14
- cors: MethodType<LambderResponseBuilder, 'cors'>;
15
- fileBase64: MethodType<LambderResponseBuilder, 'fileBase64'>;
16
- file: MethodType<LambderResponseBuilder, 'file'>;
17
- ejsFile: MethodType<LambderResponseBuilder, 'ejsFile'>;
18
- ejsTemplate: MethodType<LambderResponseBuilder, 'ejsTemplate'>;
19
- api: MethodType<LambderResponseBuilder, 'api'>;
8
+ interface DieResolverMethods<TContract extends ApiContractShape, TApiName extends keyof TContract & string> {
9
+ raw: MethodType<LambderResponseBuilder<TContract>, 'raw'>;
10
+ json: MethodType<LambderResponseBuilder<TContract>, 'json'>;
11
+ xml: MethodType<LambderResponseBuilder<TContract>, 'xml'>;
12
+ html: MethodType<LambderResponseBuilder<TContract>, 'html'>;
13
+ status301: MethodType<LambderResponseBuilder<TContract>, 'status301'>;
14
+ status404: MethodType<LambderResponseBuilder<TContract>, 'status404'>;
15
+ cors: MethodType<LambderResponseBuilder<TContract>, 'cors'>;
16
+ fileBase64: MethodType<LambderResponseBuilder<TContract>, 'fileBase64'>;
17
+ file: MethodType<LambderResponseBuilder<TContract>, 'file'>;
18
+ ejsFile: MethodType<LambderResponseBuilder<TContract>, 'ejsFile'>;
19
+ ejsTemplate: MethodType<LambderResponseBuilder<TContract>, 'ejsTemplate'>;
20
+ api: (
21
+ payload: ApiOutput<TContract, TApiName> | null,
22
+ config?: Parameters<LambderResponseBuilder<TContract>['api']>[1],
23
+ headers?: Parameters<LambderResponseBuilder<TContract>['api']>[2]
24
+ ) => LambderResolverResponse;
20
25
  }
21
26
 
22
- export default class LambderResolver extends LambderResponseBuilder {
27
+ export default class LambderResolver<
28
+ TContract extends ApiContractShape = any,
29
+ TApiName extends keyof TContract & string = any
30
+ > extends LambderResponseBuilder<TContract> {
23
31
  public resolve: (response: LambderResolverResponse) => void;
24
32
  public reject: (err: Error) => void;
25
- public die: DieResolverMethods;
33
+ public die: DieResolverMethods<TContract, TApiName>;
26
34
 
27
35
  constructor(
28
36
  { isCorsEnabled, publicPath, apiVersion, lambderUtils, ctx, resolve, reject }:
@@ -56,6 +64,15 @@ export default class LambderResolver extends LambderResponseBuilder {
56
64
  };
57
65
  }
58
66
 
67
+ // Override api method with proper typing
68
+ api(
69
+ payload: ApiOutput<TContract, TApiName> | null,
70
+ config?: Parameters<LambderResponseBuilder<TContract>['api']>[1],
71
+ headers?: Parameters<LambderResponseBuilder<TContract>['api']>[2]
72
+ ): LambderResolverResponse {
73
+ return super.api(payload, config, headers);
74
+ }
75
+
59
76
  private autoResolve<
60
77
  T extends (...args: any[]) => LambderResolverResponse
61
78
  >(method: T): (...funcArgs: Parameters<T>) => LambderResolverResponse {
@@ -4,6 +4,7 @@ import ejs from "ejs";
4
4
  import mimeTypeResolver from "mime-types";
5
5
  import LambderUtils from "./LambderUtils.js";
6
6
  import { LambderRenderContext } from "./Lambder.js";
7
+ import type { ApiContractShape } from "./LambderApiContract.js";
7
8
 
8
9
  const convertToMultiHeader = (
9
10
  headers: Record<string, string|string[]> | undefined
@@ -38,7 +39,7 @@ export type LambderApiResponse<T> = LambderApiResponseConfig & {
38
39
  payload?: T | null;
39
40
  }
40
41
 
41
- export default class LambderResponseBuilder {
42
+ export default class LambderResponseBuilder<TContract extends ApiContractShape = any> {
42
43
  private isCorsEnabled: boolean;
43
44
  private publicPath: string;
44
45
  private apiVersion: string|null;
@@ -48,6 +48,15 @@ export default class LambderSessionController {
48
48
  return this.ctx.session;
49
49
  };
50
50
 
51
+ async regenerateSession (): Promise<LambderSessionContext> {
52
+ if(!this.ctx.session) throw new Error("Session not found.");
53
+ const newSession = await this.lambderSessionManager.regenerateSession(this.ctx.session);
54
+ this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionTokenCookieKey}=${newSession.sessionToken}; Expires=${new Date(newSession.expiresAt * 1000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure` });
55
+ this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionCsrfCookieKey}=${newSession.csrfToken}; Expires=${new Date(newSession.expiresAt * 1000).toUTCString()}; Path=/; SameSite=Lax; Secure` });
56
+ this.ctx.session = newSession;
57
+ return this.ctx.session;
58
+ };
59
+
51
60
  async fetchSession (): Promise<LambderSessionContext>{
52
61
  if(!this.areRequestSessionTokensValid()){ throw new Error("Session tokens are invalid"); }
53
62