lambder 1.0.143 → 1.0.145

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/dist/Lambder.d.ts CHANGED
@@ -14,10 +14,10 @@ export type LambderRenderContext<TApiPayload = any> = {
14
14
  get: Record<string, any>;
15
15
  post: Record<string, any>;
16
16
  cookie: Record<string, any>;
17
+ session: null;
17
18
  apiName: string;
18
19
  apiPayload: TApiPayload;
19
20
  headers: APIGatewayProxyEventHeaders;
20
- session: LambderSessionContext | null;
21
21
  event: APIGatewayProxyEvent;
22
22
  lambdaContext: Context;
23
23
  _otherInternal: {
@@ -34,9 +34,13 @@ export type LambderRenderContext<TApiPayload = any> = {
34
34
  logToApiResponseAccumulator: any[];
35
35
  };
36
36
  };
37
+ export type LambderSessionRenderContext<TApiPayload = any, SessionData = any> = Omit<LambderRenderContext<TApiPayload>, 'session'> & {
38
+ session: LambderSessionContext<SessionData>;
39
+ };
37
40
  type LambderModuleFunction = (lambderInstance: Lambder) => void | Promise<void>;
38
41
  type ConditionFunction = (ctx: LambderRenderContext<any>) => boolean;
39
42
  type ActionFunction = (ctx: LambderRenderContext<any>, resolver: LambderResolver) => LambderResolverResponse | Promise<LambderResolverResponse>;
43
+ type SessionActionFunction<SessionData = any> = (ctx: LambderSessionRenderContext<any, SessionData>, resolver: LambderResolver) => LambderResolverResponse | Promise<LambderResolverResponse>;
40
44
  type HookCreatedFunction = (lambderInstance: Lambder) => Promise<void>;
41
45
  type HookBeforeRenderFunction = (ctx: LambderRenderContext<any>, resolver: LambderResolver) => LambderRenderContext<any> | Error | Promise<LambderRenderContext<any> | Error>;
42
46
  type HookAfterRenderFunction = (ctx: LambderRenderContext<any>, resolver: LambderResolver, response: LambderResolverResponse) => LambderResolverResponse | Error | Promise<LambderResolverResponse | Error>;
@@ -45,7 +49,7 @@ type GlobalErrorHandlerFunction = (err: Error, ctx: LambderRenderContext<any> |
45
49
  type RouteFallbackHandlerFunction = (ctx: LambderRenderContext<any>, resolver: LambderResolver) => LambderResolverResponse;
46
50
  type ApiFallbackHandlerFunction = (ctx: LambderRenderContext<any>, resolver: LambderResolver) => LambderResolverResponse;
47
51
  export declare const createContext: (event: APIGatewayProxyEvent, lambdaContext: Context, apiPath: string) => LambderRenderContext<any>;
48
- export default class Lambder<TContract extends ApiContractShape = any> {
52
+ export default class Lambder<TContract extends ApiContractShape = any, TSessionData = any> {
49
53
  apiPath: string;
50
54
  apiVersion: null | string;
51
55
  isCorsEnabled: boolean;
@@ -88,18 +92,18 @@ export default class Lambder<TContract extends ApiContractShape = any> {
88
92
  default: LambderModuleFunction;
89
93
  }>): Promise<void>;
90
94
  addRoute(condition: Path | ConditionFunction | RegExp, actionFn: ActionFunction): void;
91
- addSessionRoute(condition: Path | ConditionFunction | RegExp, actionFn: ActionFunction): void;
95
+ addSessionRoute(condition: Path | ConditionFunction | RegExp, actionFn: SessionActionFunction<TSessionData>): void;
92
96
  addApi(apiName: ConditionFunction | RegExp, actionFn: ActionFunction): void;
93
97
  addApi<TApiName extends keyof TContract & string>(apiName: TApiName, actionFn: (ctx: LambderRenderContext<TContract[TApiName]['input']>, resolver: LambderResolver<TContract, TApiName>) => LambderResolverResponse | Promise<LambderResolverResponse>): void;
94
98
  addApi(apiName: string, actionFn: ActionFunction): void;
95
- addSessionApi(apiName: ConditionFunction | RegExp, actionFn: ActionFunction): void;
96
- addSessionApi<TApiName extends keyof TContract & string>(apiName: TApiName, actionFn: (ctx: LambderRenderContext<TContract[TApiName]['input']>, resolver: LambderResolver<TContract, TApiName>) => LambderResolverResponse | Promise<LambderResolverResponse>): void;
97
- addSessionApi(apiName: string, actionFn: ActionFunction): void;
99
+ addSessionApi(apiName: ConditionFunction | RegExp, actionFn: SessionActionFunction<TSessionData>): void;
100
+ addSessionApi<TApiName extends keyof TContract & string>(apiName: TApiName, actionFn: (ctx: LambderSessionRenderContext<TContract[TApiName]['input'], TSessionData>, resolver: LambderResolver<TContract, TApiName>) => LambderResolverResponse | Promise<LambderResolverResponse>): void;
101
+ addSessionApi(apiName: string, actionFn: SessionActionFunction<TSessionData>): void;
98
102
  addHook(hookEvent: 'created', hookFn: HookCreatedFunction, priority?: number): Promise<void>;
99
103
  addHook(hookEvent: 'beforeRender', hookFn: HookBeforeRenderFunction, priority?: number): Promise<void>;
100
104
  addHook(hookEvent: 'afterRender', hookFn: HookAfterRenderFunction, priority?: number): Promise<void>;
101
105
  addHook(hookEvent: 'fallback', hookFn: HookFallbackFunction, priority?: number): Promise<void>;
102
- getSessionController(ctx: LambderRenderContext<any>): LambderSessionController;
106
+ getSessionController(ctx: LambderRenderContext<any> | LambderSessionRenderContext<any, TSessionData>): LambderSessionController<TSessionData>;
103
107
  getResponseBuilder(): LambderResponseBuilder<any>;
104
108
  private getResolver;
105
109
  render(event: APIGatewayProxyEvent, lambdaContext: Context): Promise<LambderResolverResponse>;
package/dist/Lambder.js CHANGED
@@ -14,7 +14,6 @@ export const createContext = (event, lambdaContext, apiPath) => {
14
14
  const method = event.httpMethod;
15
15
  const cookie = cookieParser.parse(event.headers.Cookie || event.headers.cookie || "");
16
16
  const headers = event.headers;
17
- const session = null;
18
17
  // Decode body for the post
19
18
  let post = {};
20
19
  try {
@@ -35,8 +34,9 @@ export const createContext = (event, lambdaContext, apiPath) => {
35
34
  return {
36
35
  host, path, pathParams, method,
37
36
  get, post, cookie, event,
37
+ session: null,
38
38
  apiName, apiPayload,
39
- headers, session, lambdaContext,
39
+ headers, lambdaContext,
40
40
  _otherInternal: {
41
41
  isApiCall, requestVersion,
42
42
  setHeaderFnAccumulator: [],
@@ -152,14 +152,18 @@ export default class Lambder {
152
152
  (typeof condition === "function" && condition(ctx)) ||
153
153
  (condition?.constructor == RegExp && condition.test(ctx.path)))),
154
154
  actionFn: async (ctx, resolver) => {
155
- await this.getSessionController(ctx).fetchSession();
156
155
  if (typeof condition === "string") {
157
156
  ctx.pathParams = this.getPatternMatch(condition, ctx.path);
158
157
  }
159
158
  else if (condition?.constructor == RegExp) {
160
159
  ctx.pathParams = ctx.path.match(condition);
161
160
  }
162
- return await actionFn(ctx, resolver);
161
+ const sessionCtx = ctx;
162
+ await this.getSessionController(ctx).fetchSession();
163
+ if (!sessionCtx.session) {
164
+ throw new Error("Session not found.");
165
+ }
166
+ return await actionFn(sessionCtx, resolver);
163
167
  }
164
168
  });
165
169
  }
@@ -181,8 +185,12 @@ export default class Lambder {
181
185
  (typeof apiName === "function" && apiName(ctx)) ||
182
186
  (apiName?.constructor == RegExp && apiName.test(ctx.apiName)))),
183
187
  actionFn: async (ctx, resolver) => {
188
+ const sessionCtx = ctx;
184
189
  await this.getSessionController(ctx).fetchSession();
185
- return await actionFn(ctx, resolver);
190
+ if (!sessionCtx.session) {
191
+ throw new Error("Session not found.");
192
+ }
193
+ return await actionFn(sessionCtx, resolver);
186
194
  }
187
195
  });
188
196
  }
@@ -239,7 +247,7 @@ export default class Lambder {
239
247
  if (firstMatchedAction) {
240
248
  // Check version if provided by the client and the server
241
249
  if (this.apiVersion && ctx._otherInternal.requestVersion) {
242
- if (ctx.post.apiVersion !== this.apiVersion) {
250
+ if (ctx._otherInternal.requestVersion !== this.apiVersion) {
243
251
  const responseBuilder = this.getResponseBuilder();
244
252
  return resolve(responseBuilder.versionExpired());
245
253
  }
@@ -1,21 +1,21 @@
1
- import { LambderRenderContext } from "./Lambder.js";
1
+ import { LambderRenderContext, LambderSessionRenderContext } from "./Lambder.js";
2
2
  import type LambderSessionManager from "./LambderSessionManager.js";
3
3
  import type { LambderSessionContext } from "./LambderSessionManager.js";
4
- export default class LambderSessionController {
4
+ export default class LambderSessionController<TSessionData = any> {
5
5
  lambderSessionManager: LambderSessionManager;
6
6
  sessionTokenCookieKey: string;
7
7
  sessionCsrfCookieKey: string;
8
- ctx: LambderRenderContext<any>;
8
+ ctx: LambderRenderContext<any> | LambderSessionRenderContext<any, TSessionData>;
9
9
  constructor({ lambderSessionManager, sessionTokenCookieKey, sessionCsrfCookieKey, ctx, }: {
10
10
  lambderSessionManager: LambderSessionManager;
11
11
  sessionTokenCookieKey: string;
12
12
  sessionCsrfCookieKey: string;
13
- ctx: LambderRenderContext<any>;
13
+ ctx: LambderRenderContext<any> | LambderSessionRenderContext<any, TSessionData>;
14
14
  });
15
15
  private areRequestSessionTokensValid;
16
- createSession(sessionKey: string, data?: any, ttlInSeconds?: number): Promise<LambderSessionContext>;
17
- regenerateSession(): Promise<LambderSessionContext>;
18
- fetchSession(): Promise<LambderSessionContext>;
16
+ createSession(sessionKey: string, data?: TSessionData, ttlInSeconds?: number): Promise<LambderSessionContext<TSessionData>>;
17
+ regenerateSession(): Promise<LambderSessionContext<TSessionData>>;
18
+ fetchSession(): Promise<LambderSessionContext<TSessionData>>;
19
19
  fetchSessionIfExists(): Promise<LambderSessionContext | null>;
20
20
  isSessionValid(session: any): boolean;
21
21
  updateSessionData(newData: any): Promise<LambderSessionContext>;
@@ -2,7 +2,7 @@ export default class LambderSessionController {
2
2
  lambderSessionManager;
3
3
  sessionTokenCookieKey;
4
4
  sessionCsrfCookieKey;
5
- ctx;
5
+ ctx; // Internal context with mutable session property
6
6
  constructor({ lambderSessionManager, sessionTokenCookieKey, sessionCsrfCookieKey, ctx, }) {
7
7
  this.lambderSessionManager = lambderSessionManager;
8
8
  this.sessionTokenCookieKey = sessionTokenCookieKey;
@@ -1,9 +1,9 @@
1
- export type LambderSessionContext = {
1
+ export type LambderSessionContext<SessionData = any> = {
2
2
  [x: string]: any;
3
3
  sessionToken: string;
4
4
  csrfToken: string;
5
5
  sessionKey: string;
6
- data: any;
6
+ data: SessionData;
7
7
  createdAt: number;
8
8
  expiresAt: number;
9
9
  lastAccessedAt: number;
@@ -31,7 +31,7 @@ lambder.addApi("user.login", async (ctx, resolver) => {
31
31
 
32
32
  // Create new session
33
33
  const sessionController = lambder.getSessionController(ctx);
34
- await sessionController.createSession(user.id, {
34
+ const session = await sessionController.createSession(user.id, {
35
35
  userId: user.id,
36
36
  username: user.username,
37
37
  role: user.role,
@@ -39,14 +39,14 @@ lambder.addApi("user.login", async (ctx, resolver) => {
39
39
 
40
40
  return resolver.api({
41
41
  success: true,
42
- csrfToken: ctx.session?.csrfToken,
42
+ csrfToken: session.csrfToken,
43
43
  });
44
44
  });
45
45
 
46
46
  // Example: Protected API that requires session
47
47
  lambder.addSessionApi("user.profile", async (ctx, resolver) => {
48
48
  // Session is automatically fetched and validated
49
- const sessionData = ctx.session?.data;
49
+ const sessionData = ctx.session.data;
50
50
 
51
51
  return resolver.api({
52
52
  userId: sessionData.userId,
@@ -62,7 +62,7 @@ lambder.addSessionApi("user.changePassword", async (ctx, resolver) => {
62
62
 
63
63
  // Validate old password
64
64
  const isValid = await validatePassword(
65
- ctx.session?.data.userId,
65
+ ctx.session.data.userId,
66
66
  oldPassword
67
67
  );
68
68
  if (!isValid) {
@@ -70,10 +70,10 @@ lambder.addSessionApi("user.changePassword", async (ctx, resolver) => {
70
70
  }
71
71
 
72
72
  // Update password
73
- await updatePassword(ctx.session?.data.userId, newPassword);
73
+ await updatePassword(ctx.session.data.userId, newPassword);
74
74
 
75
75
  // IMPORTANT: Regenerate session after password change to prevent session fixation
76
- await sessionController.regenerateSession();
76
+ const newSession = await sessionController.regenerateSession();
77
77
 
78
78
  // OPTIONAL: End all other sessions for this user
79
79
  await sessionController.endSessionAll();
@@ -81,7 +81,7 @@ lambder.addSessionApi("user.changePassword", async (ctx, resolver) => {
81
81
  return resolver.api({
82
82
  success: true,
83
83
  message: "Password changed successfully",
84
- csrfToken: ctx.session?.csrfToken, // Send new CSRF token
84
+ csrfToken: newSession.csrfToken, // Send new CSRF token
85
85
  });
86
86
  });
87
87
 
@@ -92,7 +92,7 @@ lambder.addSessionApi("user.updatePreferences", async (ctx, resolver) => {
92
92
 
93
93
  // Update session data (also extends expiration if sliding is enabled)
94
94
  await sessionController.updateSessionData({
95
- ...ctx.session?.data,
95
+ ...ctx.session.data,
96
96
  preferences: { theme, language },
97
97
  });
98
98
 
@@ -151,11 +151,11 @@ lambder.addApi("user.checkAuth", async (ctx, resolver) => {
151
151
  // Example: Route with session
152
152
  lambder.addSessionRoute("/dashboard", async (ctx, resolver) => {
153
153
  // Session is automatically fetched and validated
154
- const userData = ctx.session?.data;
154
+ const userData = ctx.session.data;
155
155
 
156
156
  return resolver.ejsFile("dashboard.ejs", {
157
157
  user: userData,
158
- csrfToken: ctx.session?.csrfToken,
158
+ csrfToken: ctx.session.csrfToken,
159
159
  });
160
160
  });
161
161
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambder",
3
- "version": "1.0.143",
3
+ "version": "1.0.145",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -55,6 +55,7 @@
55
55
  "@types/path-to-regexp": "^1.7.0",
56
56
  "@typescript-eslint/eslint-plugin": "^7.2.0",
57
57
  "@typescript-eslint/parser": "^7.2.0",
58
+ "aws-sdk-client-mock": "^4.1.0",
58
59
  "eslint": "^8.57.0",
59
60
  "typescript": "^5.9.3",
60
61
  "vitest": "^3.2.4"
package/src/Lambder.ts CHANGED
@@ -20,10 +20,10 @@ export type LambderRenderContext<TApiPayload = any> = {
20
20
  get: Record<string, any>;
21
21
  post: Record<string, any>;
22
22
  cookie: Record<string, any>;
23
+ session: null;
23
24
  apiName: string;
24
25
  apiPayload: TApiPayload;
25
26
  headers: APIGatewayProxyEventHeaders;
26
- session: LambderSessionContext|null;
27
27
  event: APIGatewayProxyEvent;
28
28
  lambdaContext: Context;
29
29
  _otherInternal: {
@@ -35,11 +35,17 @@ export type LambderRenderContext<TApiPayload = any> = {
35
35
  };
36
36
  };
37
37
 
38
+ export type LambderSessionRenderContext<
39
+ TApiPayload = any, SessionData = any
40
+ > = Omit<LambderRenderContext<TApiPayload>, 'session'> & { session: LambderSessionContext<SessionData> };
41
+
38
42
  type LambderModuleFunction = (lambderInstance: Lambder) => void | Promise<void>;
39
43
 
40
44
  type ConditionFunction = (ctx: LambderRenderContext<any>) => boolean;
41
45
  type ActionFunction = (ctx: LambderRenderContext<any>, resolver: LambderResolver) => LambderResolverResponse|Promise<LambderResolverResponse>;
42
- type ActionObject = { conditionFn: ConditionFunction, actionFn: ActionFunction };
46
+ type SessionActionFunction<SessionData = any> = (ctx: LambderSessionRenderContext<any, SessionData>, resolver: LambderResolver) => LambderResolverResponse|Promise<LambderResolverResponse>;
47
+
48
+ type ActionObject = { conditionFn: ConditionFunction, actionFn: ActionFunction | SessionActionFunction };
43
49
 
44
50
  type HookEventType = "created"|"beforeRender"|"afterRender"|"fallback";
45
51
  type HookCreatedFunction = (lambderInstance: Lambder) => Promise<void>;
@@ -64,7 +70,6 @@ export const createContext = (
64
70
  const method = event.httpMethod;
65
71
  const cookie = cookieParser.parse(event.headers.Cookie || event.headers.cookie || "");
66
72
  const headers = event.headers;
67
- const session = null;
68
73
 
69
74
  // Decode body for the post
70
75
  let post: Record<string, any> = {};
@@ -83,8 +88,9 @@ export const createContext = (
83
88
  return {
84
89
  host, path, pathParams, method,
85
90
  get, post, cookie, event,
91
+ session: null,
86
92
  apiName, apiPayload,
87
- headers, session, lambdaContext,
93
+ headers, lambdaContext,
88
94
  _otherInternal: {
89
95
  isApiCall, requestVersion,
90
96
  setHeaderFnAccumulator: [],
@@ -95,7 +101,7 @@ export const createContext = (
95
101
  }
96
102
 
97
103
 
98
- export default class Lambder<TContract extends ApiContractShape = any> {
104
+ export default class Lambder<TContract extends ApiContractShape = any, TSessionData = any> {
99
105
  public apiPath: string;
100
106
  public apiVersion: null|string;
101
107
  public isCorsEnabled: boolean = false;
@@ -218,7 +224,7 @@ export default class Lambder<TContract extends ApiContractShape = any> {
218
224
  });
219
225
  };
220
226
 
221
- addSessionRoute(condition: Path|ConditionFunction|RegExp, actionFn: ActionFunction):void{
227
+ addSessionRoute(condition: Path|ConditionFunction|RegExp, actionFn: SessionActionFunction<TSessionData>):void{
222
228
  this.actionList.push({
223
229
  conditionFn: (ctx:LambderRenderContext<any>) => (
224
230
  ctx.method === "GET" &&
@@ -229,14 +235,17 @@ export default class Lambder<TContract extends ApiContractShape = any> {
229
235
  )
230
236
  ),
231
237
  actionFn: async (ctx:LambderRenderContext<any>, resolver: LambderResolver) => {
232
- await this.getSessionController(ctx).fetchSession();
233
-
234
238
  if(typeof condition === "string"){
235
239
  ctx.pathParams = this.getPatternMatch(condition, ctx.path);
236
240
  }else if(condition?.constructor == RegExp){
237
241
  ctx.pathParams = ctx.path.match(condition);
238
242
  }
239
- return await actionFn(ctx, resolver);
243
+
244
+ const sessionCtx = ctx as unknown as LambderSessionRenderContext<any, TSessionData>;
245
+ await this.getSessionController(ctx).fetchSession();
246
+ if(!sessionCtx.session){ throw new Error("Session not found."); }
247
+
248
+ return await actionFn(sessionCtx, resolver);
240
249
  }
241
250
  });
242
251
  };
@@ -276,23 +285,23 @@ export default class Lambder<TContract extends ApiContractShape = any> {
276
285
  // Overload for untyped session API with RegExp or function
277
286
  addSessionApi(
278
287
  apiName: ConditionFunction|RegExp,
279
- actionFn: ActionFunction
288
+ actionFn: SessionActionFunction<TSessionData>
280
289
  ):void;
281
290
  // Overload for typed session API with string name (must come before untyped string overload)
282
291
  addSessionApi<TApiName extends keyof TContract & string>(
283
292
  apiName: TApiName,
284
293
  actionFn: (
285
- ctx: LambderRenderContext<TContract[TApiName]['input']>,
294
+ ctx: LambderSessionRenderContext<TContract[TApiName]['input'], TSessionData>,
286
295
  resolver: LambderResolver<TContract, TApiName>
287
296
  ) => LambderResolverResponse|Promise<LambderResolverResponse>
288
297
  ):void;
289
298
  // Overload for untyped session API with string (backward compatibility, must be last)
290
299
  addSessionApi(
291
300
  apiName: string,
292
- actionFn: ActionFunction
301
+ actionFn: SessionActionFunction<TSessionData>
293
302
  ):void;
294
303
  // Implementation
295
- addSessionApi(apiName: string|ConditionFunction|RegExp, actionFn: ActionFunction):void{
304
+ addSessionApi(apiName: string|ConditionFunction|RegExp, actionFn: SessionActionFunction<TSessionData>):void{
296
305
  this.actionList.push({
297
306
  conditionFn: (ctx:LambderRenderContext<any>) => (
298
307
  !!ctx.apiName && (
@@ -302,8 +311,10 @@ export default class Lambder<TContract extends ApiContractShape = any> {
302
311
  )
303
312
  ),
304
313
  actionFn: async (ctx:LambderRenderContext<any>, resolver: LambderResolver) => {
314
+ const sessionCtx = ctx as unknown as LambderSessionRenderContext<any, TSessionData>;
305
315
  await this.getSessionController(ctx).fetchSession();
306
- return await actionFn(ctx, resolver);
316
+ if(!sessionCtx.session){ throw new Error("Session not found."); }
317
+ return await actionFn(sessionCtx, resolver);
307
318
  }
308
319
  });
309
320
  };
@@ -325,10 +336,10 @@ export default class Lambder<TContract extends ApiContractShape = any> {
325
336
  }
326
337
  }
327
338
 
328
- getSessionController(ctx: LambderRenderContext<any>): LambderSessionController{
339
+ getSessionController(ctx: LambderRenderContext<any> | LambderSessionRenderContext<any, TSessionData>): LambderSessionController<TSessionData>{
329
340
  if(!this.lambderSessionManager) throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
330
341
 
331
- return new LambderSessionController({
342
+ return new LambderSessionController<TSessionData>({
332
343
  lambderSessionManager: this.lambderSessionManager,
333
344
  sessionTokenCookieKey: this.sessionTokenCookieKey,
334
345
  sessionCsrfCookieKey: this.sessionCsrfCookieKey,
@@ -381,7 +392,7 @@ export default class Lambder<TContract extends ApiContractShape = any> {
381
392
  if(firstMatchedAction){
382
393
  // Check version if provided by the client and the server
383
394
  if(this.apiVersion && ctx._otherInternal.requestVersion){
384
- if(ctx.post.apiVersion !== this.apiVersion){
395
+ if(ctx._otherInternal.requestVersion !== this.apiVersion){
385
396
  const responseBuilder = this.getResponseBuilder();
386
397
  return resolve(responseBuilder.versionExpired());
387
398
  }
@@ -393,7 +404,7 @@ export default class Lambder<TContract extends ApiContractShape = any> {
393
404
  ctx = hookCtx;
394
405
  }
395
406
  // Run matched action
396
- let response = await firstMatchedAction.actionFn(ctx, resolver);
407
+ let response = await firstMatchedAction.actionFn(ctx as any, resolver);
397
408
  // Run afterRender hooks
398
409
  for(const hook of this.hookList["afterRender"]){
399
410
  const hookResponse = await hook.hookFn(ctx, resolver, response);
@@ -1,12 +1,12 @@
1
- import { LambderRenderContext } from "./Lambder.js";
1
+ import { LambderRenderContext, LambderSessionRenderContext } from "./Lambder.js";
2
2
  import type LambderSessionManager from "./LambderSessionManager.js";
3
3
  import type { LambderSessionContext } from "./LambderSessionManager.js";
4
4
 
5
- export default class LambderSessionController {
5
+ export default class LambderSessionController<TSessionData = any> {
6
6
  lambderSessionManager: LambderSessionManager;
7
7
  sessionTokenCookieKey: string;
8
8
  sessionCsrfCookieKey: string;
9
- ctx: LambderRenderContext<any>;
9
+ ctx: LambderRenderContext<any> | LambderSessionRenderContext<any, TSessionData>; // Internal context with mutable session property
10
10
 
11
11
  constructor(
12
12
  {
@@ -18,7 +18,7 @@ export default class LambderSessionController {
18
18
  lambderSessionManager: LambderSessionManager,
19
19
  sessionTokenCookieKey: string,
20
20
  sessionCsrfCookieKey: string,
21
- ctx: LambderRenderContext<any>,
21
+ ctx: LambderRenderContext<any> | LambderSessionRenderContext<any, TSessionData>,
22
22
  }
23
23
  ){
24
24
  this.lambderSessionManager = lambderSessionManager;
@@ -40,7 +40,7 @@ export default class LambderSessionController {
40
40
  }
41
41
  };
42
42
 
43
- async createSession (sessionKey: string, data?: any, ttlInSeconds?: number): Promise<LambderSessionContext> {
43
+ async createSession (sessionKey: string, data?: TSessionData, ttlInSeconds?: number): Promise<LambderSessionContext<TSessionData>> {
44
44
  const session = await this.lambderSessionManager.createSession(sessionKey, data, ttlInSeconds);
45
45
  this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionTokenCookieKey}=${session.sessionToken}; Expires=${new Date(session.expiresAt * 1000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure` });
46
46
  this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionCsrfCookieKey}=${session.csrfToken}; Expires=${new Date(session.expiresAt * 1000).toUTCString()}; Path=/; SameSite=Lax; Secure` });
@@ -48,7 +48,7 @@ export default class LambderSessionController {
48
48
  return this.ctx.session;
49
49
  };
50
50
 
51
- async regenerateSession (): Promise<LambderSessionContext> {
51
+ async regenerateSession (): Promise<LambderSessionContext<TSessionData>> {
52
52
  if(!this.ctx.session) throw new Error("Session not found.");
53
53
  const newSession = await this.lambderSessionManager.regenerateSession(this.ctx.session);
54
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` });
@@ -57,7 +57,7 @@ export default class LambderSessionController {
57
57
  return this.ctx.session;
58
58
  };
59
59
 
60
- async fetchSession (): Promise<LambderSessionContext>{
60
+ async fetchSession (): Promise<LambderSessionContext<TSessionData>>{
61
61
  if(!this.areRequestSessionTokensValid()){ throw new Error("Session tokens are invalid"); }
62
62
 
63
63
  const sessionToken = this.ctx.cookie?.[this.sessionTokenCookieKey];
@@ -101,7 +101,7 @@ export default class LambderSessionController {
101
101
  await this.lambderSessionManager.deleteSession(this.ctx.session);
102
102
  this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionTokenCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure` });
103
103
  this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionCsrfCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; SameSite=Lax; Secure` });
104
- this.ctx.session = null
104
+ (this.ctx as any).session = null;
105
105
  };
106
106
 
107
107
  async endSessionAll (){
@@ -109,6 +109,6 @@ export default class LambderSessionController {
109
109
  await this.lambderSessionManager.deleteSessionAll(this.ctx.session);
110
110
  this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionTokenCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure` });
111
111
  this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionCsrfCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; SameSite=Lax; Secure` });
112
- this.ctx.session = null
112
+ (this.ctx as any).session = null;
113
113
  };
114
114
  };
@@ -2,12 +2,12 @@ import crypto from "crypto";
2
2
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
3
3
  import { DynamoDBDocumentClient, QueryCommand, DeleteCommand, PutCommand, GetCommand } from "@aws-sdk/lib-dynamodb";
4
4
 
5
- export type LambderSessionContext = {
5
+ export type LambderSessionContext<SessionData = any> = {
6
6
  [x: string]: any;
7
7
  sessionToken: string;
8
8
  csrfToken: string;
9
9
  sessionKey: string;
10
- data: any;
10
+ data: SessionData;
11
11
  createdAt: number;
12
12
  expiresAt: number;
13
13
  lastAccessedAt: number;