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/src/Lambder.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import cookieParser from "cookie";
2
2
  import { match } from "path-to-regexp";
3
+ import { z } from "zod";
3
4
 
4
5
  import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, Context } from "aws-lambda";
5
6
  import LambderResolver from "./LambderResolver.js";
@@ -7,7 +8,7 @@ import LambderResponseBuilder, { LambderResolverResponse } from "./LambderRespon
7
8
  import LambderUtils from "./LambderUtils.js";
8
9
  import LambderSessionManager, { type LambderSessionContext } from "./LambderSessionManager.js";
9
10
  import LambderSessionController from "./LambderSessionController.js";
10
- import type { ApiContractShape } from "./LambderApiContract.js";
11
+ import type { MergeContract } from "./LambderApiContract.js";
11
12
 
12
13
  type Path = `/${string}`;
13
14
 
@@ -38,8 +39,6 @@ export type LambderSessionRenderContext<
38
39
  TApiPayload = any, SessionData = any
39
40
  > = Omit<LambderRenderContext<TApiPayload>, 'session'> & { session: LambderSessionContext<SessionData> };
40
41
 
41
- type LambderModuleFunction = (lambderInstance: Lambder) => void | Promise<void>;
42
-
43
42
  type ConditionFunction = (ctx: LambderRenderContext<any>) => boolean;
44
43
  type ActionFunction = (ctx: LambderRenderContext<any>, resolver: LambderResolver) => LambderResolverResponse|Promise<LambderResolverResponse>;
45
44
  type SessionActionFunction<SessionData = any> = (ctx: LambderSessionRenderContext<any, SessionData>, resolver: LambderResolver) => LambderResolverResponse|Promise<LambderResolverResponse>;
@@ -105,14 +104,40 @@ export const createContext = (
105
104
  };
106
105
  }
107
106
 
108
-
109
- export default class Lambder<TContract extends ApiContractShape = any, TSessionData = any> {
107
+ /**
108
+ * Main Lambder class for building type-safe serverless APIs
109
+ *
110
+ * @typeParam TSessionData - Type of session data stored in DynamoDB
111
+ * @typeParam _TContract - @internal Accumulates API contract during chaining (do not pass manually)
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * interface SessionData { userId: string; role: string; }
116
+ *
117
+ * const lambder = new Lambder<SessionData>({ apiPath: '/api' })
118
+ * .addApi('getUser', { input: z.object({...}), output: z.object({...}) }, handler)
119
+ * .addApi('createUser', { input: z.object({...}), output: z.object({...}) }, handler);
120
+ * ```
121
+ */
122
+ export default class Lambder<TSessionData = any, _TContract extends Record<string, any> = {}> {
110
123
  public apiPath: string;
111
124
  public apiVersion: null|string;
112
125
  public isCorsEnabled: boolean = false;
113
126
  public publicPath: string;
114
127
  public ejsPath: string;
115
128
 
129
+ /**
130
+ * Type property for extracting the API contract
131
+ * Use this to export your API types to the frontend
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * const lambder = new Lambder().addApi(...).addApi(...);
136
+ * export type AppContract = typeof lambder.ApiContractType;
137
+ * ```
138
+ */
139
+ public readonly ApiContractType!: _TContract;
140
+
116
141
  private actionList: ActionObject[];
117
142
  private hookList: {
118
143
  "beforeRender": { priority: number, hookFn: HookBeforeRenderFunction }[],
@@ -148,32 +173,38 @@ export default class Lambder<TContract extends ApiContractShape = any, TSessionD
148
173
  this.utils = new LambderUtils({ ejsPath });
149
174
  }
150
175
 
151
- enableCors(isCorsEnabled: boolean){
176
+ enableCors(isCorsEnabled: boolean): this {
152
177
  this.isCorsEnabled = isCorsEnabled;
178
+ return this;
153
179
  }
154
180
 
155
181
  enableDdbSession(
156
182
  { tableName, tableRegion, sessionSalt, enableSlidingExpiration }: { tableName: string; tableRegion: string; sessionSalt: string; enableSlidingExpiration?: boolean; },
157
183
  { partitionKey, sortKey }: { partitionKey: string, sortKey: string } = { partitionKey: "pk", sortKey: "sk" }
158
- ){
184
+ ): this {
159
185
  this.lambderSessionManager = new LambderSessionManager({
160
186
  tableName, tableRegion, partitionKey, sortKey, sessionSalt, enableSlidingExpiration
161
187
  });
188
+ return this;
162
189
  }
163
190
 
164
- setSessionCookieKey(sessionTokenCookieKey: string, sessionCsrfCookieKey: string){
191
+ setSessionCookieKey(sessionTokenCookieKey: string, sessionCsrfCookieKey: string): this {
165
192
  this.sessionTokenCookieKey = sessionTokenCookieKey;
166
193
  this.sessionCsrfCookieKey = sessionCsrfCookieKey;
194
+ return this;
167
195
  }
168
196
 
169
- setRouteFallbackHandler(routeFallbackHandler: RouteFallbackHandlerFunction){
197
+ setRouteFallbackHandler(routeFallbackHandler: RouteFallbackHandlerFunction): this {
170
198
  this.routeFallbackHandler = routeFallbackHandler;
199
+ return this;
171
200
  }
172
- setApiFallbackHandler(apiFallbackHandler: ApiFallbackHandlerFunction){
201
+ setApiFallbackHandler(apiFallbackHandler: ApiFallbackHandlerFunction): this {
173
202
  this.apiFallbackHandler = apiFallbackHandler;
203
+ return this;
174
204
  }
175
- setGlobalErrorHandler(globalErrorHandler: GlobalErrorHandlerFunction){
205
+ setGlobalErrorHandler(globalErrorHandler: GlobalErrorHandlerFunction): this {
176
206
  this.globalErrorHandler = globalErrorHandler;
207
+ return this;
177
208
  }
178
209
  private getPatternMatch(pattern: string, path: string): Record<string, any> {
179
210
  const result = (match(pattern, { decode: decodeURIComponent }))(path);
@@ -199,15 +230,7 @@ export default class Lambder<TContract extends ApiContractShape = any, TSessionD
199
230
  }
200
231
  }
201
232
 
202
- async addModule(moduleFn: LambderModuleFunction): Promise<void>{
203
- await moduleFn(this);
204
- }
205
-
206
- async importModule(moduleImport: Promise<{ default: LambderModuleFunction }>): Promise<void>{
207
- await this.addModule((await moduleImport).default);
208
- }
209
-
210
- addRoute(condition: Path|ConditionFunction|RegExp, actionFn: ActionFunction):void{
233
+ addRoute(condition: Path|ConditionFunction|RegExp, actionFn: ActionFunction): this {
211
234
  this.actionList.push({
212
235
  conditionFn: (ctx:LambderRenderContext<any>) => (
213
236
  ctx.method === "GET" &&
@@ -221,15 +244,17 @@ export default class Lambder<TContract extends ApiContractShape = any, TSessionD
221
244
  if(typeof condition === "string"){
222
245
  ctx.pathParams = this.getPatternMatch(condition, ctx.path);
223
246
  }else if(condition?.constructor == RegExp){
224
- ctx.pathParams = ctx.path.match(condition);
247
+ const match = ctx.path.match(condition);
248
+ ctx.pathParams = match ? (match.groups || (match as unknown as Record<string, any>)) : {};
225
249
  }
226
250
 
227
251
  return await actionFn(ctx, resolver);
228
252
  }
229
253
  });
230
- };
254
+ return this;
255
+ }
231
256
 
232
- addSessionRoute(condition: Path|ConditionFunction|RegExp, actionFn: SessionActionFunction<TSessionData>):void{
257
+ addSessionRoute(condition: Path|ConditionFunction|RegExp, actionFn: SessionActionFunction<TSessionData>): this {
233
258
  this.actionList.push({
234
259
  conditionFn: (ctx:LambderRenderContext<any>) => (
235
260
  ctx.method === "GET" &&
@@ -243,7 +268,8 @@ export default class Lambder<TContract extends ApiContractShape = any, TSessionD
243
268
  if(typeof condition === "string"){
244
269
  ctx.pathParams = this.getPatternMatch(condition, ctx.path);
245
270
  }else if(condition?.constructor == RegExp){
246
- ctx.pathParams = ctx.path.match(condition);
271
+ const match = ctx.path.match(condition);
272
+ ctx.pathParams = match ? (match.groups || (match as unknown as Record<string, any>)) : {};
247
273
  }
248
274
 
249
275
  const sessionCtx = ctx as unknown as LambderSessionRenderContext<any, TSessionData>;
@@ -253,76 +279,87 @@ export default class Lambder<TContract extends ApiContractShape = any, TSessionD
253
279
  return await actionFn(sessionCtx, resolver);
254
280
  }
255
281
  });
256
- };
282
+ return this;
283
+ }
257
284
 
258
- // Overload for untyped API with RegExp or function
259
- addApi(
260
- apiName: ConditionFunction|RegExp,
261
- actionFn: ActionFunction
262
- ):void;
263
- // Overload for typed API with string name (must come before untyped string overload)
264
- addApi<TApiName extends keyof TContract & string>(
265
- apiName: TApiName,
266
- actionFn: (
267
- ctx: LambderRenderContext<TContract[TApiName]['input']>,
268
- resolver: LambderResolver<TContract, TApiName>
269
- ) => LambderResolverResponse|Promise<LambderResolverResponse>
270
- ):void;
271
- // Overload for untyped API with string (backward compatibility, must be last)
272
- addApi(
273
- apiName: string,
274
- actionFn: ActionFunction
275
- ):void;
276
- // Implementation
277
- addApi(apiName: string|ConditionFunction|RegExp, actionFn: ActionFunction):void{
285
+ // Plugin system
286
+ public use<_TNewContract extends Record<string, any>>(
287
+ plugin: (lambder: Lambder<TSessionData, _TContract>) => Lambder<TSessionData, _TNewContract>
288
+ ): Lambder<TSessionData, _TNewContract> {
289
+ return plugin(this);
290
+ }
291
+
292
+ // Typed API with Zod
293
+ public addApi<
294
+ TName extends string,
295
+ TInput extends z.ZodTypeAny,
296
+ TOutput extends z.ZodTypeAny
297
+ >(
298
+ name: TName,
299
+ schema: { input: TInput, output: TOutput },
300
+ handler: (
301
+ ctx: LambderRenderContext<z.infer<TInput>>,
302
+ resolver: LambderResolver<z.infer<TOutput>>
303
+ ) => LambderResolverResponse | Promise<LambderResolverResponse>
304
+ ): Lambder<TSessionData, MergeContract<_TContract, TName, z.infer<TInput>, z.infer<TOutput>>> {
278
305
  this.actionList.push({
279
- conditionFn: (ctx:LambderRenderContext<any>) => (
280
- !!ctx.apiName && (
281
- (typeof apiName === "string" && ctx.apiName === apiName) ||
282
- (typeof apiName === "function" && apiName(ctx)) ||
283
- (apiName?.constructor == RegExp && apiName.test(ctx.apiName))
284
- )
285
- ),
286
- actionFn: async (ctx:LambderRenderContext<any>, resolver: LambderResolver) => await actionFn(ctx, resolver),
306
+ conditionFn: (ctx:LambderRenderContext<any>) => ctx.apiName === name,
307
+ actionFn: async (ctx:LambderRenderContext<any>, resolver: LambderResolver) => {
308
+ // Validate Input
309
+ const inputResult = schema.input.safeParse(ctx.apiPayload);
310
+ if (!inputResult.success) {
311
+ return resolver.raw({
312
+ statusCode: 400,
313
+ body: JSON.stringify({ error: "Input validation failed", zodError: inputResult.error }),
314
+ multiValueHeaders: { "Content-Type": ["application/json"] }
315
+ });
316
+ }
317
+
318
+ // Run Handler with validated data
319
+ ctx.apiPayload = inputResult.data;
320
+ return await handler(ctx, resolver);
321
+ },
287
322
  });
288
- };
323
+ return this as any;
324
+ }
289
325
 
290
- // Overload for untyped session API with RegExp or function
291
- addSessionApi(
292
- apiName: ConditionFunction|RegExp,
293
- actionFn: SessionActionFunction<TSessionData>
294
- ):void;
295
- // Overload for typed session API with string name (must come before untyped string overload)
296
- addSessionApi<TApiName extends keyof TContract & string>(
297
- apiName: TApiName,
298
- actionFn: (
299
- ctx: LambderSessionRenderContext<TContract[TApiName]['input'], TSessionData>,
300
- resolver: LambderResolver<TContract, TApiName>
301
- ) => LambderResolverResponse|Promise<LambderResolverResponse>
302
- ):void;
303
- // Overload for untyped session API with string (backward compatibility, must be last)
304
- addSessionApi(
305
- apiName: string,
306
- actionFn: SessionActionFunction<TSessionData>
307
- ):void;
308
- // Implementation
309
- addSessionApi(apiName: string|ConditionFunction|RegExp, actionFn: SessionActionFunction<TSessionData>):void{
326
+ // Typed Session API with Zod
327
+ public addSessionApi<
328
+ TName extends string,
329
+ TInput extends z.ZodTypeAny,
330
+ TOutput extends z.ZodTypeAny
331
+ >(
332
+ name: TName,
333
+ schema: { input: TInput, output: TOutput },
334
+ handler: (
335
+ ctx: LambderSessionRenderContext<z.infer<TInput>, TSessionData>,
336
+ resolver: LambderResolver<z.infer<TOutput>>
337
+ ) => LambderResolverResponse | Promise<LambderResolverResponse>
338
+ ): Lambder<TSessionData, MergeContract<_TContract, TName, z.infer<TInput>, z.infer<TOutput>>> {
310
339
  this.actionList.push({
311
- conditionFn: (ctx:LambderRenderContext<any>) => (
312
- !!ctx.apiName && (
313
- (typeof apiName === "string" && ctx.apiName === apiName) ||
314
- (typeof apiName === "function" && apiName(ctx)) ||
315
- (apiName?.constructor == RegExp && apiName.test(ctx.apiName))
316
- )
317
- ),
340
+ conditionFn: (ctx:LambderRenderContext<any>) => ctx.apiName === name,
318
341
  actionFn: async (ctx:LambderRenderContext<any>, resolver: LambderResolver) => {
319
342
  const sessionCtx = ctx as unknown as LambderSessionRenderContext<any, TSessionData>;
320
343
  await this.getSessionController(ctx).fetchSession();
321
344
  if(!sessionCtx.session){ throw new Error("Session not found."); }
322
- return await actionFn(sessionCtx, resolver);
345
+
346
+ // Validate Input
347
+ const inputResult = schema.input.safeParse(ctx.apiPayload);
348
+ if (!inputResult.success) {
349
+ return resolver.raw({
350
+ statusCode: 400,
351
+ body: JSON.stringify({ error: "Input validation failed", zodError: inputResult.error }),
352
+ multiValueHeaders: { "Content-Type": ["application/json"] }
353
+ });
354
+ }
355
+
356
+ // Run Handler with validated data
357
+ ctx.apiPayload = inputResult.data;
358
+ return await handler(sessionCtx, resolver);
323
359
  }
324
360
  });
325
- };
361
+ return this as any;
362
+ }
326
363
 
327
364
  async addHook(hookEvent: 'created', hookFn: HookCreatedFunction, priority?: number): Promise<void>;
328
365
  async addHook(hookEvent: 'beforeRender', hookFn: HookBeforeRenderFunction, priority?: number): Promise<void>;
@@ -375,6 +412,10 @@ export default class Lambder<TContract extends ApiContractShape = any, TSessionD
375
412
  });
376
413
  };
377
414
 
415
+ getHandler() {
416
+ return (event: APIGatewayProxyEvent, context: Context) => this.render(event, context);
417
+ }
418
+
378
419
  async render(
379
420
  event: APIGatewayProxyEvent,
380
421
  lambdaContext: Context
@@ -1,63 +1,20 @@
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
7
  /**
28
- * Base type for API contracts
29
- *
30
- * Use this as a constraint when defining your API contract:
31
- *
32
- * export type MyApiContract = {
33
- * echo: { input: { message: string }, output: { echo: string } }
34
- * } satisfies ApiContract;
35
- *
36
- * Or for backward compatibility without satisfies:
37
- *
38
- * export type MyApiContract = ApiContract & {
39
- * echo: { input: { message: string }, output: { echo: string } }
40
- * }
8
+ * Base shape for API contracts - used by LambderCaller and LambderMSW
41
9
  */
42
10
  export type ApiContractShape = Record<string, {
43
11
  input: any;
44
12
  output: any;
45
- }>
46
-
47
- export type ApiContract<T extends ApiContractShape> = T;
13
+ }>;
48
14
 
49
15
  /**
50
- * Extract input type from contract for a specific API
16
+ * Helper type for merging new API into existing contract during chaining
51
17
  */
52
- export type ApiInput<
53
- TContract extends ApiContractShape,
54
- TApiName extends keyof TContract
55
- > = TContract[TApiName]['input'];
18
+ export type MergeContract<Old, Name extends string, In, Out> =
19
+ Old & { [K in Name]: { input: In, output: Out } };
56
20
 
57
- /**
58
- * Extract output type from contract for a specific API
59
- */
60
- export type ApiOutput<
61
- TContract extends ApiContractShape,
62
- TApiName extends keyof TContract
63
- > = TContract[TApiName]['output'];
@@ -143,9 +143,9 @@ export default class LambderCaller<TContract extends ApiContractShape = any> {
143
143
  activeFetchList: this.fetchTrackerList.filter(v=>!v.done),
144
144
  });
145
145
  }
146
- if(data && data.logList?.length){
146
+ if(data && data.logList){
147
147
  for(const record of data.logList){
148
- console.log("LogToApiResponse:", record);
148
+ // Log to API response for debugging
149
149
  }
150
150
  }
151
151
  if(data && data.versionExpired){
package/src/LambderMSW.ts CHANGED
@@ -1,9 +1,6 @@
1
- import { createRequire } from 'module';
2
1
  import type { ApiContractShape } from './LambderApiContract';
3
2
  import type { LambderApiResponse } from './LambderResponseBuilder';
4
3
 
5
- const require = createRequire(import.meta.url);
6
-
7
4
  // MSW types - these will be resolved at runtime when msw is installed
8
5
  type RequestHandler = any;
9
6
 
@@ -80,8 +77,6 @@ export default class LambderMSW<TContract extends ApiContractShape = any> {
80
77
  if (!body || typeof body.apiName !== 'string') { return; }
81
78
  if (body.apiName !== apiName) { return; }
82
79
 
83
- console.log("LambderMSW called for:", body.apiName);
84
-
85
80
  try {
86
81
  // Add artificial delay if specified
87
82
  if (options?.delay) {
@@ -91,8 +86,6 @@ export default class LambderMSW<TContract extends ApiContractShape = any> {
91
86
  // Call the handler with the payload from the request
92
87
  const payload = await handler(body.payload as TContract[TApiName]['input']);
93
88
 
94
- console.log("Matched! Returning payload for:", apiName);
95
-
96
89
  const response: MockApiResponse<TContract[TApiName]['output']> = {
97
90
  apiVersion: this.apiVersion,
98
91
  payload,
@@ -1,36 +1,32 @@
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
 
6
5
  type MethodType<T, M extends keyof T> = T[M] extends (...args: any[]) => any ? T[M] : never;
7
6
 
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'>;
7
+ interface DieResolverMethods<TOutput> {
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'>;
20
19
  api: (
21
- payload: ApiOutput<TContract, TApiName> | null,
22
- config?: Parameters<LambderResponseBuilder<TContract>['api']>[1],
23
- headers?: Parameters<LambderResponseBuilder<TContract>['api']>[2]
20
+ payload: TOutput | null,
21
+ config?: Parameters<LambderResponseBuilder['api']>[1],
22
+ headers?: Parameters<LambderResponseBuilder['api']>[2]
24
23
  ) => LambderResolverResponse;
25
24
  }
26
25
 
27
- export default class LambderResolver<
28
- TContract extends ApiContractShape = any,
29
- TApiName extends keyof TContract & string = any
30
- > extends LambderResponseBuilder<TContract> {
26
+ export default class LambderResolver<TOutput = any> extends LambderResponseBuilder<TOutput> {
31
27
  public resolve: (response: LambderResolverResponse) => void;
32
28
  public reject: (err: Error) => void;
33
- public die: DieResolverMethods<TContract, TApiName>;
29
+ public die: DieResolverMethods<TOutput>;
34
30
 
35
31
  constructor(
36
32
  { isCorsEnabled, publicPath, apiVersion, lambderUtils, ctx, resolve, reject }:
@@ -66,13 +62,14 @@ export default class LambderResolver<
66
62
 
67
63
  // Override api method with proper typing
68
64
  api(
69
- payload: ApiOutput<TContract, TApiName> | null,
70
- config?: Parameters<LambderResponseBuilder<TContract>['api']>[1],
71
- headers?: Parameters<LambderResponseBuilder<TContract>['api']>[2]
65
+ payload: TOutput | null,
66
+ config?: Parameters<LambderResponseBuilder['api']>[1],
67
+ headers?: Parameters<LambderResponseBuilder['api']>[2]
72
68
  ): LambderResolverResponse {
73
69
  return super.api(payload, config, headers);
74
70
  }
75
71
 
72
+
76
73
  private autoResolve<
77
74
  T extends (...args: any[]) => LambderResolverResponse
78
75
  >(method: T): (...funcArgs: Parameters<T>) => LambderResolverResponse {
@@ -1,7 +1,6 @@
1
1
  import mimeTypeResolver from "mime-types";
2
2
  import LambderUtils from "./LambderUtils.js";
3
3
  import { LambderRenderContext } from "./Lambder.js";
4
- import type { ApiContractShape } from "./LambderApiContract.js";
5
4
  import { getFS, getPath } from "./node-polyfills.js";
6
5
 
7
6
  const convertToMultiHeader = (
@@ -37,7 +36,7 @@ export type LambderApiResponse<T> = LambderApiResponseConfig & {
37
36
  payload?: T | null;
38
37
  }
39
38
 
40
- export default class LambderResponseBuilder<TContract extends ApiContractShape = any> {
39
+ export default class LambderResponseBuilder<TResponse = any> {
41
40
  private isCorsEnabled: boolean;
42
41
  private publicPath: string;
43
42
  private apiVersion: string|null;
@@ -71,7 +70,6 @@ export default class LambderResponseBuilder<TContract extends ApiContractShape =
71
70
 
72
71
  const publicPath = path.resolve(this.publicPath);
73
72
  const absolutePath = path.resolve(publicPath, filePath);
74
- console.log("readPublicFileSync", { filePath, publicPath, absolutePath });
75
73
  if(!absolutePath.startsWith(publicPath)){ return "forbidden-public-path"; }
76
74
  return await fs.promises.readFile(absolutePath);
77
75
  };
@@ -84,7 +82,6 @@ export default class LambderResponseBuilder<TContract extends ApiContractShape =
84
82
 
85
83
  const publicPath = path.resolve(this.publicPath);
86
84
  const absolutePath = path.resolve(publicPath, filePath);
87
- console.log("checkPublicFileExist", { filePath, publicPath, absolutePath });
88
85
  if(!absolutePath.startsWith(publicPath)){ return false; }
89
86
  try {
90
87
  const stat = await fs.promises.stat(absolutePath);
@@ -224,7 +221,7 @@ export default class LambderResponseBuilder<TContract extends ApiContractShape =
224
221
  const mimeType = mimeTypeResolver.lookup(filePath);
225
222
  const body = await this.readPublicFileSync(filePath);
226
223
  if (body === "forbidden-public-path") {
227
- throw { error: "Forbidden public path: " + filePath };
224
+ throw new Error("Forbidden public path: " + filePath);
228
225
  }
229
226
  const bodyBuffer: Buffer = Buffer.isBuffer(body) ? body : Buffer.from(body);
230
227
  const bodyBase64 = bodyBuffer.toString("base64");
@@ -262,8 +259,8 @@ export default class LambderResponseBuilder<TContract extends ApiContractShape =
262
259
  });
263
260
  };
264
261
 
265
- api<T=any>(
266
- payload: T | null,
262
+ api(
263
+ payload: TResponse | null,
267
264
  {
268
265
  versionExpired, sessionExpired, notAuthorized,
269
266
  message, errorMessage, logList,
@@ -22,9 +22,8 @@ export default class LambderUtils {
22
22
  if(!this.ejsPath){ return "EJS PATH NOT SET!"; }
23
23
  const ejsPath = path.resolve(this.ejsPath);
24
24
  const absolutePath = path.resolve(ejsPath, filePath);
25
- console.log("readEjsFileSync", { filePath, ejsPath, absolutePath });
26
25
  if(!absolutePath.startsWith(ejsPath)){ return "forbidden-ejs-path"; }
27
- return String(await fs.promises.readFile(absolutePath));
26
+ return await fs.promises.readFile(absolutePath, 'utf-8');
28
27
  };
29
28
 
30
29
  private async checkEjsFileExist(filePath: string){
@@ -37,7 +36,6 @@ export default class LambderUtils {
37
36
  if(!this.ejsPath){ return "EJS PATH NOT SET!"; }
38
37
  const ejsPath = path.resolve(this.ejsPath);
39
38
  const absolutePath = path.resolve(ejsPath, filePath);
40
- console.log("checkEjsFileExist", { filePath, ejsPath, absolutePath });
41
39
  if(!absolutePath.startsWith(ejsPath)){ return false; }
42
40
  try {
43
41
  const stat = await fs.promises.stat(absolutePath);
package/src/index.ts CHANGED
@@ -10,7 +10,4 @@ export { default as LambderMSW } from "./LambderMSW.js";
10
10
  // Type-safe API contract utilities
11
11
  export {
12
12
  type ApiContractShape,
13
- type ApiContract,
14
- type ApiInput,
15
- type ApiOutput,
16
13
  } from "./LambderApiContract.js";