lambder 1.0.147 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/Readme.md +355 -410
  2. package/dist/Lambder.d.ts +47 -21
  3. package/dist/Lambder.js +79 -24
  4. package/dist/LambderApiContract.d.ts +10 -42
  5. package/dist/LambderApiContract.js +2 -22
  6. package/dist/LambderCaller.js +2 -2
  7. package/dist/LambderMSW.js +0 -4
  8. package/dist/LambderResolver.d.ts +16 -17
  9. package/dist/LambderResponseBuilder.d.ts +2 -3
  10. package/dist/LambderResponseBuilder.js +1 -3
  11. package/dist/LambderUtils.js +1 -3
  12. package/dist/index.d.ts +1 -1
  13. package/docs/LAMBDER_MSW.md +6 -6
  14. package/docs/TYPE_SAFE_QUICK_START.md +54 -177
  15. package/examples/msw-testing-example.ts +36 -33
  16. package/examples/secure-session-example.ts +50 -34
  17. package/examples/zod-chained-api-example.ts +63 -0
  18. package/package.json +3 -2
  19. package/src/Lambder.ts +124 -83
  20. package/src/LambderApiContract.ts +7 -50
  21. package/src/LambderCaller.ts +2 -2
  22. package/src/LambderMSW.ts +0 -7
  23. package/src/LambderResolver.ts +21 -24
  24. package/src/LambderResponseBuilder.ts +4 -7
  25. package/src/LambderUtils.ts +1 -3
  26. package/src/index.ts +0 -3
  27. package/tests/UNTESTED_FEATURES.md +263 -0
  28. package/tests/error-handling.test.ts +585 -0
  29. package/tests/hooks.test.ts +561 -0
  30. package/tests/output-type-runtime.test.ts +80 -64
  31. package/tests/routes.test.ts +542 -0
  32. package/tests/session.test.ts +38 -24
  33. package/tests/type-safety.test.ts +147 -97
  34. package/tests/use-plugin.test.ts +437 -0
  35. package/OUTPUT_TYPE_ENFORCEMENT_SUMMARY.md +0 -90
  36. package/examples/output-type-enforcement-example.ts +0 -218
  37. package/examples/simplified-typed-api-example.ts +0 -365
  38. package/examples/test-output-type-enforcement.ts +0 -101
  39. package/test-type-enforcement.ts +0 -111
package/dist/Lambder.d.ts CHANGED
@@ -1,10 +1,11 @@
1
+ import { z } from "zod";
1
2
  import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, Context } from "aws-lambda";
2
3
  import LambderResolver from "./LambderResolver.js";
3
4
  import LambderResponseBuilder, { LambderResolverResponse } from "./LambderResponseBuilder.js";
4
5
  import LambderUtils from "./LambderUtils.js";
5
6
  import { type LambderSessionContext } from "./LambderSessionManager.js";
6
7
  import LambderSessionController from "./LambderSessionController.js";
7
- import type { ApiContractShape } from "./LambderApiContract.js";
8
+ import type { MergeContract } from "./LambderApiContract.js";
8
9
  type Path = `/${string}`;
9
10
  export type LambderRenderContext<TApiPayload = any> = {
10
11
  host: string;
@@ -37,7 +38,6 @@ export type LambderRenderContext<TApiPayload = any> = {
37
38
  export type LambderSessionRenderContext<TApiPayload = any, SessionData = any> = Omit<LambderRenderContext<TApiPayload>, 'session'> & {
38
39
  session: LambderSessionContext<SessionData>;
39
40
  };
40
- type LambderModuleFunction = (lambderInstance: Lambder) => void | Promise<void>;
41
41
  type ConditionFunction = (ctx: LambderRenderContext<any>) => boolean;
42
42
  type ActionFunction = (ctx: LambderRenderContext<any>, resolver: LambderResolver) => LambderResolverResponse | Promise<LambderResolverResponse>;
43
43
  type SessionActionFunction<SessionData = any> = (ctx: LambderSessionRenderContext<any, SessionData>, resolver: LambderResolver) => LambderResolverResponse | Promise<LambderResolverResponse>;
@@ -49,12 +49,38 @@ type GlobalErrorHandlerFunction = (err: Error, ctx: LambderRenderContext<any> |
49
49
  type RouteFallbackHandlerFunction = (ctx: LambderRenderContext<any>, resolver: LambderResolver) => LambderResolverResponse;
50
50
  type ApiFallbackHandlerFunction = (ctx: LambderRenderContext<any>, resolver: LambderResolver) => LambderResolverResponse;
51
51
  export declare const createContext: (event: APIGatewayProxyEvent, lambdaContext: Context, apiPath: string) => LambderRenderContext<any>;
52
- export default class Lambder<TContract extends ApiContractShape = any, TSessionData = any> {
52
+ /**
53
+ * Main Lambder class for building type-safe serverless APIs
54
+ *
55
+ * @typeParam TSessionData - Type of session data stored in DynamoDB
56
+ * @typeParam _TContract - @internal Accumulates API contract during chaining (do not pass manually)
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * interface SessionData { userId: string; role: string; }
61
+ *
62
+ * const lambder = new Lambder<SessionData>({ apiPath: '/api' })
63
+ * .addApi('getUser', { input: z.object({...}), output: z.object({...}) }, handler)
64
+ * .addApi('createUser', { input: z.object({...}), output: z.object({...}) }, handler);
65
+ * ```
66
+ */
67
+ export default class Lambder<TSessionData = any, _TContract extends Record<string, any> = {}> {
53
68
  apiPath: string;
54
69
  apiVersion: null | string;
55
70
  isCorsEnabled: boolean;
56
71
  publicPath: string;
57
72
  ejsPath: string;
73
+ /**
74
+ * Type property for extracting the API contract
75
+ * Use this to export your API types to the frontend
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * const lambder = new Lambder().addApi(...).addApi(...);
80
+ * export type AppContract = typeof lambder.ApiContractType;
81
+ * ```
82
+ */
83
+ readonly ApiContractType: _TContract;
58
84
  private actionList;
59
85
  private hookList;
60
86
  private globalErrorHandler;
@@ -70,7 +96,7 @@ export default class Lambder<TContract extends ApiContractShape = any, TSessionD
70
96
  ejsPath?: string;
71
97
  apiVersion?: string;
72
98
  });
73
- enableCors(isCorsEnabled: boolean): void;
99
+ enableCors(isCorsEnabled: boolean): this;
74
100
  enableDdbSession({ tableName, tableRegion, sessionSalt, enableSlidingExpiration }: {
75
101
  tableName: string;
76
102
  tableRegion: string;
@@ -79,26 +105,25 @@ export default class Lambder<TContract extends ApiContractShape = any, TSessionD
79
105
  }, { partitionKey, sortKey }?: {
80
106
  partitionKey: string;
81
107
  sortKey: string;
82
- }): void;
83
- setSessionCookieKey(sessionTokenCookieKey: string, sessionCsrfCookieKey: string): void;
84
- setRouteFallbackHandler(routeFallbackHandler: RouteFallbackHandlerFunction): void;
85
- setApiFallbackHandler(apiFallbackHandler: ApiFallbackHandlerFunction): void;
86
- setGlobalErrorHandler(globalErrorHandler: GlobalErrorHandlerFunction): void;
108
+ }): this;
109
+ setSessionCookieKey(sessionTokenCookieKey: string, sessionCsrfCookieKey: string): this;
110
+ setRouteFallbackHandler(routeFallbackHandler: RouteFallbackHandlerFunction): this;
111
+ setApiFallbackHandler(apiFallbackHandler: ApiFallbackHandlerFunction): this;
112
+ setGlobalErrorHandler(globalErrorHandler: GlobalErrorHandlerFunction): this;
87
113
  private getPatternMatch;
88
114
  private testPatternMatch;
89
115
  private handleNoMatchedAction;
90
- addModule(moduleFn: LambderModuleFunction): Promise<void>;
91
- importModule(moduleImport: Promise<{
92
- default: LambderModuleFunction;
93
- }>): Promise<void>;
94
- addRoute(condition: Path | ConditionFunction | RegExp, actionFn: ActionFunction): void;
95
- addSessionRoute(condition: Path | ConditionFunction | RegExp, actionFn: SessionActionFunction<TSessionData>): void;
96
- addApi(apiName: ConditionFunction | RegExp, actionFn: ActionFunction): void;
97
- addApi<TApiName extends keyof TContract & string>(apiName: TApiName, actionFn: (ctx: LambderRenderContext<TContract[TApiName]['input']>, resolver: LambderResolver<TContract, TApiName>) => LambderResolverResponse | Promise<LambderResolverResponse>): void;
98
- addApi(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;
116
+ addRoute(condition: Path | ConditionFunction | RegExp, actionFn: ActionFunction): this;
117
+ addSessionRoute(condition: Path | ConditionFunction | RegExp, actionFn: SessionActionFunction<TSessionData>): this;
118
+ use<_TNewContract extends Record<string, any>>(plugin: (lambder: Lambder<TSessionData, _TContract>) => Lambder<TSessionData, _TNewContract>): Lambder<TSessionData, _TNewContract>;
119
+ addApi<TName extends string, TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny>(name: TName, schema: {
120
+ input: TInput;
121
+ output: TOutput;
122
+ }, handler: (ctx: LambderRenderContext<z.infer<TInput>>, resolver: LambderResolver<z.infer<TOutput>>) => LambderResolverResponse | Promise<LambderResolverResponse>): Lambder<TSessionData, MergeContract<_TContract, TName, z.infer<TInput>, z.infer<TOutput>>>;
123
+ addSessionApi<TName extends string, TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny>(name: TName, schema: {
124
+ input: TInput;
125
+ output: TOutput;
126
+ }, handler: (ctx: LambderSessionRenderContext<z.infer<TInput>, TSessionData>, resolver: LambderResolver<z.infer<TOutput>>) => LambderResolverResponse | Promise<LambderResolverResponse>): Lambder<TSessionData, MergeContract<_TContract, TName, z.infer<TInput>, z.infer<TOutput>>>;
102
127
  addHook(hookEvent: 'created', hookFn: HookCreatedFunction, priority?: number): Promise<void>;
103
128
  addHook(hookEvent: 'beforeRender', hookFn: HookBeforeRenderFunction, priority?: number): Promise<void>;
104
129
  addHook(hookEvent: 'afterRender', hookFn: HookAfterRenderFunction, priority?: number): Promise<void>;
@@ -106,6 +131,7 @@ export default class Lambder<TContract extends ApiContractShape = any, TSessionD
106
131
  getSessionController(ctx: LambderRenderContext<any> | LambderSessionRenderContext<any, TSessionData>): LambderSessionController<TSessionData>;
107
132
  getResponseBuilder(): LambderResponseBuilder<any>;
108
133
  private getResolver;
134
+ getHandler(): (event: APIGatewayProxyEvent, context: Context) => Promise<LambderResolverResponse>;
109
135
  render(event: APIGatewayProxyEvent, lambdaContext: Context): Promise<LambderResolverResponse>;
110
136
  }
111
137
  export {};
package/dist/Lambder.js CHANGED
@@ -48,12 +48,38 @@ export const createContext = (event, lambdaContext, apiPath) => {
48
48
  }
49
49
  };
50
50
  };
51
+ /**
52
+ * Main Lambder class for building type-safe serverless APIs
53
+ *
54
+ * @typeParam TSessionData - Type of session data stored in DynamoDB
55
+ * @typeParam _TContract - @internal Accumulates API contract during chaining (do not pass manually)
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * interface SessionData { userId: string; role: string; }
60
+ *
61
+ * const lambder = new Lambder<SessionData>({ apiPath: '/api' })
62
+ * .addApi('getUser', { input: z.object({...}), output: z.object({...}) }, handler)
63
+ * .addApi('createUser', { input: z.object({...}), output: z.object({...}) }, handler);
64
+ * ```
65
+ */
51
66
  export default class Lambder {
52
67
  apiPath;
53
68
  apiVersion;
54
69
  isCorsEnabled = false;
55
70
  publicPath;
56
71
  ejsPath;
72
+ /**
73
+ * Type property for extracting the API contract
74
+ * Use this to export your API types to the frontend
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const lambder = new Lambder().addApi(...).addApi(...);
79
+ * export type AppContract = typeof lambder.ApiContractType;
80
+ * ```
81
+ */
82
+ ApiContractType;
57
83
  actionList;
58
84
  hookList;
59
85
  globalErrorHandler = null;
@@ -78,24 +104,30 @@ export default class Lambder {
78
104
  }
79
105
  enableCors(isCorsEnabled) {
80
106
  this.isCorsEnabled = isCorsEnabled;
107
+ return this;
81
108
  }
82
109
  enableDdbSession({ tableName, tableRegion, sessionSalt, enableSlidingExpiration }, { partitionKey, sortKey } = { partitionKey: "pk", sortKey: "sk" }) {
83
110
  this.lambderSessionManager = new LambderSessionManager({
84
111
  tableName, tableRegion, partitionKey, sortKey, sessionSalt, enableSlidingExpiration
85
112
  });
113
+ return this;
86
114
  }
87
115
  setSessionCookieKey(sessionTokenCookieKey, sessionCsrfCookieKey) {
88
116
  this.sessionTokenCookieKey = sessionTokenCookieKey;
89
117
  this.sessionCsrfCookieKey = sessionCsrfCookieKey;
118
+ return this;
90
119
  }
91
120
  setRouteFallbackHandler(routeFallbackHandler) {
92
121
  this.routeFallbackHandler = routeFallbackHandler;
122
+ return this;
93
123
  }
94
124
  setApiFallbackHandler(apiFallbackHandler) {
95
125
  this.apiFallbackHandler = apiFallbackHandler;
126
+ return this;
96
127
  }
97
128
  setGlobalErrorHandler(globalErrorHandler) {
98
129
  this.globalErrorHandler = globalErrorHandler;
130
+ return this;
99
131
  }
100
132
  getPatternMatch(pattern, path) {
101
133
  const result = (match(pattern, { decode: decodeURIComponent }))(path);
@@ -124,12 +156,6 @@ export default class Lambder {
124
156
  resolver.resolve({ statusCode: 204, body: "Route handler not set.", });
125
157
  }
126
158
  }
127
- async addModule(moduleFn) {
128
- await moduleFn(this);
129
- }
130
- async importModule(moduleImport) {
131
- await this.addModule((await moduleImport).default);
132
- }
133
159
  addRoute(condition, actionFn) {
134
160
  this.actionList.push({
135
161
  conditionFn: (ctx) => (ctx.method === "GET" &&
@@ -141,13 +167,14 @@ export default class Lambder {
141
167
  ctx.pathParams = this.getPatternMatch(condition, ctx.path);
142
168
  }
143
169
  else if (condition?.constructor == RegExp) {
144
- ctx.pathParams = ctx.path.match(condition);
170
+ const match = ctx.path.match(condition);
171
+ ctx.pathParams = match ? (match.groups || match) : {};
145
172
  }
146
173
  return await actionFn(ctx, resolver);
147
174
  }
148
175
  });
176
+ return this;
149
177
  }
150
- ;
151
178
  addSessionRoute(condition, actionFn) {
152
179
  this.actionList.push({
153
180
  conditionFn: (ctx) => (ctx.method === "GET" &&
@@ -159,7 +186,8 @@ export default class Lambder {
159
186
  ctx.pathParams = this.getPatternMatch(condition, ctx.path);
160
187
  }
161
188
  else if (condition?.constructor == RegExp) {
162
- ctx.pathParams = ctx.path.match(condition);
189
+ const match = ctx.path.match(condition);
190
+ ctx.pathParams = match ? (match.groups || match) : {};
163
191
  }
164
192
  const sessionCtx = ctx;
165
193
  await this.getSessionController(ctx).fetchSession();
@@ -169,35 +197,59 @@ export default class Lambder {
169
197
  return await actionFn(sessionCtx, resolver);
170
198
  }
171
199
  });
200
+ return this;
172
201
  }
173
- ;
174
- // Implementation
175
- addApi(apiName, actionFn) {
202
+ // Plugin system
203
+ use(plugin) {
204
+ return plugin(this);
205
+ }
206
+ // Typed API with Zod
207
+ addApi(name, schema, handler) {
176
208
  this.actionList.push({
177
- conditionFn: (ctx) => (!!ctx.apiName && ((typeof apiName === "string" && ctx.apiName === apiName) ||
178
- (typeof apiName === "function" && apiName(ctx)) ||
179
- (apiName?.constructor == RegExp && apiName.test(ctx.apiName)))),
180
- actionFn: async (ctx, resolver) => await actionFn(ctx, resolver),
209
+ conditionFn: (ctx) => ctx.apiName === name,
210
+ actionFn: async (ctx, resolver) => {
211
+ // Validate Input
212
+ const inputResult = schema.input.safeParse(ctx.apiPayload);
213
+ if (!inputResult.success) {
214
+ return resolver.raw({
215
+ statusCode: 400,
216
+ body: JSON.stringify({ error: "Input validation failed", zodError: inputResult.error }),
217
+ multiValueHeaders: { "Content-Type": ["application/json"] }
218
+ });
219
+ }
220
+ // Run Handler with validated data
221
+ ctx.apiPayload = inputResult.data;
222
+ return await handler(ctx, resolver);
223
+ },
181
224
  });
225
+ return this;
182
226
  }
183
- ;
184
- // Implementation
185
- addSessionApi(apiName, actionFn) {
227
+ // Typed Session API with Zod
228
+ addSessionApi(name, schema, handler) {
186
229
  this.actionList.push({
187
- conditionFn: (ctx) => (!!ctx.apiName && ((typeof apiName === "string" && ctx.apiName === apiName) ||
188
- (typeof apiName === "function" && apiName(ctx)) ||
189
- (apiName?.constructor == RegExp && apiName.test(ctx.apiName)))),
230
+ conditionFn: (ctx) => ctx.apiName === name,
190
231
  actionFn: async (ctx, resolver) => {
191
232
  const sessionCtx = ctx;
192
233
  await this.getSessionController(ctx).fetchSession();
193
234
  if (!sessionCtx.session) {
194
235
  throw new Error("Session not found.");
195
236
  }
196
- return await actionFn(sessionCtx, resolver);
237
+ // Validate Input
238
+ const inputResult = schema.input.safeParse(ctx.apiPayload);
239
+ if (!inputResult.success) {
240
+ return resolver.raw({
241
+ statusCode: 400,
242
+ body: JSON.stringify({ error: "Input validation failed", zodError: inputResult.error }),
243
+ multiValueHeaders: { "Content-Type": ["application/json"] }
244
+ });
245
+ }
246
+ // Run Handler with validated data
247
+ ctx.apiPayload = inputResult.data;
248
+ return await handler(sessionCtx, resolver);
197
249
  }
198
250
  });
251
+ return this;
199
252
  }
200
- ;
201
253
  async addHook(hookEvent, hookFn, priority = 0) {
202
254
  if (hookEvent === "created") {
203
255
  await hookFn(this);
@@ -236,6 +288,9 @@ export default class Lambder {
236
288
  });
237
289
  }
238
290
  ;
291
+ getHandler() {
292
+ return (event, context) => this.render(event, context);
293
+ }
239
294
  async render(event, lambdaContext) {
240
295
  let eventRenderContext = null;
241
296
  try {
@@ -1,53 +1,21 @@
1
1
  /**
2
- * Type-safe API Contract System
2
+ * Lambder API Contract System
3
3
  *
4
- * Define your API contract as a TypeScript type to get full type safety
5
- * across frontend and backend with no runtime overhead.
6
- *
7
- * Example:
8
- *
9
- * export type MyApiContract = {
10
- * getUserById: { input: { userId: string }, output: User },
11
- * createUser: { input: CreateUserInput, output: User },
12
- * listUsers: { input: void, output: User[] }
13
- * }
14
- *
15
- * Frontend:
16
- * const caller = new LambderCaller<MyApiContract>({ ... });
17
- * const user = await caller.api('getUserById', { userId: '123' }); // typed!
18
- *
19
- * Backend:
20
- * const lambder = new Lambder<MyApiContract>({ ... });
21
- * lambder.addApi('getUserById', async (ctx, resolver) => {
22
- * // ctx.apiPayload is typed as { userId: string }
23
- * return resolver.api(user); // user is typed as User
24
- * });
4
+ * Contracts are built via method chaining and inferred using typeof lambder.ApiContractType
25
5
  */
26
6
  /**
27
- * Base type for API contracts
28
- *
29
- * Use this as a constraint when defining your API contract:
30
- *
31
- * export type MyApiContract = {
32
- * echo: { input: { message: string }, output: { echo: string } }
33
- * } satisfies ApiContract;
34
- *
35
- * Or for backward compatibility without satisfies:
36
- *
37
- * export type MyApiContract = ApiContract & {
38
- * echo: { input: { message: string }, output: { echo: string } }
39
- * }
7
+ * Base shape for API contracts - used by LambderCaller and LambderMSW
40
8
  */
41
9
  export type ApiContractShape = Record<string, {
42
10
  input: any;
43
11
  output: any;
44
12
  }>;
45
- export type ApiContract<T extends ApiContractShape> = T;
46
- /**
47
- * Extract input type from contract for a specific API
48
- */
49
- export type ApiInput<TContract extends ApiContractShape, TApiName extends keyof TContract> = TContract[TApiName]['input'];
50
13
  /**
51
- * Extract output type from contract for a specific API
14
+ * Helper type for merging new API into existing contract during chaining
52
15
  */
53
- export type ApiOutput<TContract extends ApiContractShape, TApiName extends keyof TContract> = TContract[TApiName]['output'];
16
+ export type MergeContract<Old, Name extends string, In, Out> = Old & {
17
+ [K in Name]: {
18
+ input: In;
19
+ output: Out;
20
+ };
21
+ };
@@ -1,26 +1,6 @@
1
1
  /**
2
- * Type-safe API Contract System
2
+ * Lambder API Contract System
3
3
  *
4
- * Define your API contract as a TypeScript type to get full type safety
5
- * across frontend and backend with no runtime overhead.
6
- *
7
- * Example:
8
- *
9
- * export type MyApiContract = {
10
- * getUserById: { input: { userId: string }, output: User },
11
- * createUser: { input: CreateUserInput, output: User },
12
- * listUsers: { input: void, output: User[] }
13
- * }
14
- *
15
- * Frontend:
16
- * const caller = new LambderCaller<MyApiContract>({ ... });
17
- * const user = await caller.api('getUserById', { userId: '123' }); // typed!
18
- *
19
- * Backend:
20
- * const lambder = new Lambder<MyApiContract>({ ... });
21
- * lambder.addApi('getUserById', async (ctx, resolver) => {
22
- * // ctx.apiPayload is typed as { userId: string }
23
- * return resolver.api(user); // user is typed as User
24
- * });
4
+ * Contracts are built via method chaining and inferred using typeof lambder.ApiContractType
25
5
  */
26
6
  export {};
@@ -71,9 +71,9 @@ export default class LambderCaller {
71
71
  activeFetchList: this.fetchTrackerList.filter(v => !v.done),
72
72
  });
73
73
  }
74
- if (data && data.logList?.length) {
74
+ if (data && data.logList) {
75
75
  for (const record of data.logList) {
76
- console.log("LogToApiResponse:", record);
76
+ // Log to API response for debugging
77
77
  }
78
78
  }
79
79
  if (data && data.versionExpired) {
@@ -1,5 +1,3 @@
1
- import { createRequire } from 'module';
2
- const require = createRequire(import.meta.url);
3
1
  export default class LambderMSW {
4
2
  apiPath;
5
3
  apiVersion;
@@ -48,7 +46,6 @@ export default class LambderMSW {
48
46
  if (body.apiName !== apiName) {
49
47
  return;
50
48
  }
51
- console.log("LambderMSW called for:", body.apiName);
52
49
  try {
53
50
  // Add artificial delay if specified
54
51
  if (options?.delay) {
@@ -56,7 +53,6 @@ export default class LambderMSW {
56
53
  }
57
54
  // Call the handler with the payload from the request
58
55
  const payload = await handler(body.payload);
59
- console.log("Matched! Returning payload for:", apiName);
60
56
  const response = {
61
57
  apiVersion: this.apiVersion,
62
58
  payload,
@@ -1,26 +1,25 @@
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";
5
4
  type MethodType<T, M extends keyof T> = T[M] extends (...args: any[]) => any ? T[M] : never;
6
- interface DieResolverMethods<TContract extends ApiContractShape, TApiName extends keyof TContract & string> {
7
- raw: MethodType<LambderResponseBuilder<TContract>, 'raw'>;
8
- json: MethodType<LambderResponseBuilder<TContract>, 'json'>;
9
- xml: MethodType<LambderResponseBuilder<TContract>, 'xml'>;
10
- html: MethodType<LambderResponseBuilder<TContract>, 'html'>;
11
- status301: MethodType<LambderResponseBuilder<TContract>, 'status301'>;
12
- status404: MethodType<LambderResponseBuilder<TContract>, 'status404'>;
13
- cors: MethodType<LambderResponseBuilder<TContract>, 'cors'>;
14
- fileBase64: MethodType<LambderResponseBuilder<TContract>, 'fileBase64'>;
15
- file: MethodType<LambderResponseBuilder<TContract>, 'file'>;
16
- ejsFile: MethodType<LambderResponseBuilder<TContract>, 'ejsFile'>;
17
- ejsTemplate: MethodType<LambderResponseBuilder<TContract>, 'ejsTemplate'>;
18
- api: (payload: ApiOutput<TContract, TApiName> | null, config?: Parameters<LambderResponseBuilder<TContract>['api']>[1], headers?: Parameters<LambderResponseBuilder<TContract>['api']>[2]) => LambderResolverResponse;
5
+ interface DieResolverMethods<TOutput> {
6
+ raw: MethodType<LambderResponseBuilder, 'raw'>;
7
+ json: MethodType<LambderResponseBuilder, 'json'>;
8
+ xml: MethodType<LambderResponseBuilder, 'xml'>;
9
+ html: MethodType<LambderResponseBuilder, 'html'>;
10
+ status301: MethodType<LambderResponseBuilder, 'status301'>;
11
+ status404: MethodType<LambderResponseBuilder, 'status404'>;
12
+ cors: MethodType<LambderResponseBuilder, 'cors'>;
13
+ fileBase64: MethodType<LambderResponseBuilder, 'fileBase64'>;
14
+ file: MethodType<LambderResponseBuilder, 'file'>;
15
+ ejsFile: MethodType<LambderResponseBuilder, 'ejsFile'>;
16
+ ejsTemplate: MethodType<LambderResponseBuilder, 'ejsTemplate'>;
17
+ api: (payload: TOutput | null, config?: Parameters<LambderResponseBuilder['api']>[1], headers?: Parameters<LambderResponseBuilder['api']>[2]) => LambderResolverResponse;
19
18
  }
20
- export default class LambderResolver<TContract extends ApiContractShape = any, TApiName extends keyof TContract & string = any> extends LambderResponseBuilder<TContract> {
19
+ export default class LambderResolver<TOutput = any> extends LambderResponseBuilder<TOutput> {
21
20
  resolve: (response: LambderResolverResponse) => void;
22
21
  reject: (err: Error) => void;
23
- die: DieResolverMethods<TContract, TApiName>;
22
+ die: DieResolverMethods<TOutput>;
24
23
  constructor({ isCorsEnabled, publicPath, apiVersion, lambderUtils, ctx, resolve, reject }: {
25
24
  isCorsEnabled: boolean;
26
25
  publicPath: string;
@@ -30,7 +29,7 @@ export default class LambderResolver<TContract extends ApiContractShape = any, T
30
29
  resolve: (response: LambderResolverResponse) => void;
31
30
  reject: (err: Error) => void;
32
31
  });
33
- api(payload: ApiOutput<TContract, TApiName> | null, config?: Parameters<LambderResponseBuilder<TContract>['api']>[1], headers?: Parameters<LambderResponseBuilder<TContract>['api']>[2]): LambderResolverResponse;
32
+ api(payload: TOutput | null, config?: Parameters<LambderResponseBuilder['api']>[1], headers?: Parameters<LambderResponseBuilder['api']>[2]): LambderResolverResponse;
34
33
  private autoResolve;
35
34
  private autoResolvePromise;
36
35
  }
@@ -1,6 +1,5 @@
1
1
  import LambderUtils from "./LambderUtils.js";
2
2
  import { LambderRenderContext } from "./Lambder.js";
3
- import type { ApiContractShape } from "./LambderApiContract.js";
4
3
  export type LambderResolverResponse = {
5
4
  statusCode: number;
6
5
  multiValueHeaders?: Record<string, string[]>;
@@ -18,7 +17,7 @@ export type LambderApiResponseConfig = {
18
17
  export type LambderApiResponse<T> = LambderApiResponseConfig & {
19
18
  payload?: T | null;
20
19
  };
21
- export default class LambderResponseBuilder<TContract extends ApiContractShape = any> {
20
+ export default class LambderResponseBuilder<TResponse = any> {
22
21
  private isCorsEnabled;
23
22
  private publicPath;
24
23
  private apiVersion;
@@ -48,6 +47,6 @@ export default class LambderResponseBuilder<TContract extends ApiContractShape =
48
47
  file(filePath: string, headers?: Record<string, string | string[]>, fallbackFilePath?: string): Promise<LambderResolverResponse>;
49
48
  ejsTemplate(template: string, pageData: Record<string, any>, headers?: Record<string, string | string[]>): Promise<LambderResolverResponse>;
50
49
  ejsFile(filePath: string, pageData: Record<string, any>, headers?: Record<string, string | string[]>): Promise<LambderResolverResponse>;
51
- api<T = any>(payload: T | null, { versionExpired, sessionExpired, notAuthorized, message, errorMessage, logList, }?: LambderApiResponseConfig, headers?: Record<string, string | string[]>): LambderResolverResponse;
50
+ api(payload: TResponse | null, { versionExpired, sessionExpired, notAuthorized, message, errorMessage, logList, }?: LambderApiResponseConfig, headers?: Record<string, string | string[]>): LambderResolverResponse;
52
51
  apiBinary<T = any>(payload: T | null, { versionExpired, sessionExpired, notAuthorized, message, errorMessage, logList, }?: LambderApiResponseConfig, headers?: Record<string, string | string[]>): LambderResolverResponse;
53
52
  }
@@ -29,7 +29,6 @@ export default class LambderResponseBuilder {
29
29
  }
30
30
  const publicPath = path.resolve(this.publicPath);
31
31
  const absolutePath = path.resolve(publicPath, filePath);
32
- console.log("readPublicFileSync", { filePath, publicPath, absolutePath });
33
32
  if (!absolutePath.startsWith(publicPath)) {
34
33
  return "forbidden-public-path";
35
34
  }
@@ -44,7 +43,6 @@ export default class LambderResponseBuilder {
44
43
  }
45
44
  const publicPath = path.resolve(this.publicPath);
46
45
  const absolutePath = path.resolve(publicPath, filePath);
47
- console.log("checkPublicFileExist", { filePath, publicPath, absolutePath });
48
46
  if (!absolutePath.startsWith(publicPath)) {
49
47
  return false;
50
48
  }
@@ -166,7 +164,7 @@ export default class LambderResponseBuilder {
166
164
  const mimeType = mimeTypeResolver.lookup(filePath);
167
165
  const body = await this.readPublicFileSync(filePath);
168
166
  if (body === "forbidden-public-path") {
169
- throw { error: "Forbidden public path: " + filePath };
167
+ throw new Error("Forbidden public path: " + filePath);
170
168
  }
171
169
  const bodyBuffer = Buffer.isBuffer(body) ? body : Buffer.from(body);
172
170
  const bodyBase64 = bodyBuffer.toString("base64");
@@ -17,11 +17,10 @@ export default class LambderUtils {
17
17
  }
18
18
  const ejsPath = path.resolve(this.ejsPath);
19
19
  const absolutePath = path.resolve(ejsPath, filePath);
20
- console.log("readEjsFileSync", { filePath, ejsPath, absolutePath });
21
20
  if (!absolutePath.startsWith(ejsPath)) {
22
21
  return "forbidden-ejs-path";
23
22
  }
24
- return String(await fs.promises.readFile(absolutePath));
23
+ return await fs.promises.readFile(absolutePath, 'utf-8');
25
24
  }
26
25
  ;
27
26
  async checkEjsFileExist(filePath) {
@@ -35,7 +34,6 @@ export default class LambderUtils {
35
34
  }
36
35
  const ejsPath = path.resolve(this.ejsPath);
37
36
  const absolutePath = path.resolve(ejsPath, filePath);
38
- console.log("checkEjsFileExist", { filePath, ejsPath, absolutePath });
39
37
  if (!absolutePath.startsWith(ejsPath)) {
40
38
  return false;
41
39
  }
package/dist/index.d.ts CHANGED
@@ -5,4 +5,4 @@ export { default as LambderResponseBuilder } from "./LambderResponseBuilder.js";
5
5
  export { default as LambderResolver } from "./LambderResolver.js";
6
6
  export { default as LambderSessionManager } from "./LambderSessionManager.js";
7
7
  export { default as LambderMSW } from "./LambderMSW.js";
8
- export { type ApiContractShape, type ApiContract, type ApiInput, type ApiOutput, } from "./LambderApiContract.js";
8
+ export { type ApiContractShape, } from "./LambderApiContract.js";
@@ -248,14 +248,14 @@ afterEach(() => server.resetHandlers());
248
248
  afterAll(() => server.close());
249
249
 
250
250
  // Setup LambderCaller
251
- const caller = new LambderCaller<MyApiContract>({
251
+ const lambderCaller = new LambderCaller<MyApiContract>({
252
252
  apiPath: '/secure',
253
253
  isCorsEnabled: false
254
254
  });
255
255
 
256
256
  describe('User APIs', () => {
257
257
  it('should fetch user by id', async () => {
258
- const result = await caller.api('getUserById', { userId: '123' });
258
+ const result = await lambderCaller.api('getUserById', { userId: '123' });
259
259
 
260
260
  expect(result).toEqual({
261
261
  id: '123',
@@ -265,7 +265,7 @@ describe('User APIs', () => {
265
265
  });
266
266
 
267
267
  it('should create a new user', async () => {
268
- const result = await caller.api('createUser', {
268
+ const result = await lambderCaller.api('createUser', {
269
269
  name: 'Jane Smith',
270
270
  email: 'jane@example.com'
271
271
  });
@@ -277,7 +277,7 @@ describe('User APIs', () => {
277
277
 
278
278
  it('should handle session expired', async () => {
279
279
  try {
280
- await caller.api('getProtectedData', {});
280
+ await lambderCaller.api('getProtectedData', {});
281
281
  // Should not reach here
282
282
  expect(true).toBe(false);
283
283
  } catch (error: any) {
@@ -287,7 +287,7 @@ describe('User APIs', () => {
287
287
 
288
288
  it('should handle errors', async () => {
289
289
  try {
290
- await caller.api('failingApi', {});
290
+ await lambderCaller.api('failingApi', {});
291
291
  // Should not reach here
292
292
  expect(true).toBe(false);
293
293
  } catch (error: any) {
@@ -386,7 +386,7 @@ it('should handle specific user', async () => {
386
386
  })
387
387
  );
388
388
 
389
- const result = await caller.api('getUserById', { userId: '999' });
389
+ const result = await lambderCaller.api('getUserById', { userId: '999' });
390
390
  expect(result?.name).toBe('Special User');
391
391
  });
392
392
  ```