lambder 2.0.5 → 2.0.7
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 +5 -34
- package/dist/Lambder.js +12 -44
- package/dist/LambderContext.d.ts +34 -0
- package/dist/LambderContext.js +44 -0
- package/dist/LambderResolver.d.ts +1 -1
- package/dist/LambderResponseBuilder.d.ts +1 -1
- package/dist/LambderResponseBuilder.js +1 -1
- package/dist/LambderSessionController.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
- package/src/Lambder.ts +15 -79
- package/src/LambderContext.ts +78 -0
- package/src/LambderResolver.ts +1 -1
- package/src/LambderResponseBuilder.ts +2 -2
- package/src/LambderSessionController.ts +1 -1
- package/src/index.ts +4 -0
package/dist/Lambder.d.ts
CHANGED
|
@@ -1,43 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import type { APIGatewayProxyEvent,
|
|
2
|
+
import type { APIGatewayProxyEvent, Context } from "aws-lambda";
|
|
3
3
|
import LambderResolver from "./LambderResolver.js";
|
|
4
4
|
import LambderResponseBuilder, { LambderResolverResponse } from "./LambderResponseBuilder.js";
|
|
5
5
|
import LambderUtils from "./LambderUtils.js";
|
|
6
|
-
import { type LambderSessionContext } from "./LambderSessionManager.js";
|
|
7
6
|
import LambderSessionController from "./LambderSessionController.js";
|
|
8
7
|
import type { MergeContract } from "./LambderApiContract.js";
|
|
8
|
+
import { type LambderRenderContext, type LambderSessionRenderContext } from "./LambderContext.js";
|
|
9
9
|
type Path = `/${string}`;
|
|
10
|
-
export type LambderRenderContext<TApiPayload = any> = {
|
|
11
|
-
host: string;
|
|
12
|
-
path: string;
|
|
13
|
-
pathParams: Record<string, any> | null;
|
|
14
|
-
method: string;
|
|
15
|
-
get: Record<string, any>;
|
|
16
|
-
post: Record<string, any>;
|
|
17
|
-
cookie: Record<string, any>;
|
|
18
|
-
session: null;
|
|
19
|
-
apiName: string;
|
|
20
|
-
apiPayload: TApiPayload;
|
|
21
|
-
headers: APIGatewayProxyEventHeaders;
|
|
22
|
-
event: APIGatewayProxyEvent;
|
|
23
|
-
lambdaContext: Context;
|
|
24
|
-
_otherInternal: {
|
|
25
|
-
isApiCall: boolean;
|
|
26
|
-
requestVersion: string | null;
|
|
27
|
-
setHeaderFnAccumulator: {
|
|
28
|
-
key: string;
|
|
29
|
-
value: string | string[];
|
|
30
|
-
}[];
|
|
31
|
-
addHeaderFnAccumulator: {
|
|
32
|
-
key: string;
|
|
33
|
-
value: string;
|
|
34
|
-
}[];
|
|
35
|
-
logToApiResponseAccumulator: any[];
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
export type LambderSessionRenderContext<TApiPayload = any, SessionData = any> = Omit<LambderRenderContext<TApiPayload>, 'session'> & {
|
|
39
|
-
session: LambderSessionContext<SessionData>;
|
|
40
|
-
};
|
|
41
10
|
type ConditionFunction = (ctx: LambderRenderContext<any>) => boolean;
|
|
42
11
|
type ActionFunction = (ctx: LambderRenderContext<any>, resolver: LambderResolver) => LambderResolverResponse | Promise<LambderResolverResponse>;
|
|
43
12
|
type SessionActionFunction<SessionData = any> = (ctx: LambderSessionRenderContext<any, SessionData>, resolver: LambderResolver) => LambderResolverResponse | Promise<LambderResolverResponse>;
|
|
@@ -48,7 +17,7 @@ type HookFallbackFunction = (ctx: LambderRenderContext<any>, resolver: LambderRe
|
|
|
48
17
|
type GlobalErrorHandlerFunction = (err: Error, ctx: LambderRenderContext<any> | null, response: LambderResponseBuilder, logListToApiResponse?: any[]) => LambderResolverResponse | Promise<LambderResolverResponse>;
|
|
49
18
|
type RouteFallbackHandlerFunction = (ctx: LambderRenderContext<any>, resolver: LambderResolver) => LambderResolverResponse;
|
|
50
19
|
type ApiFallbackHandlerFunction = (ctx: LambderRenderContext<any>, resolver: LambderResolver) => LambderResolverResponse;
|
|
51
|
-
|
|
20
|
+
type ApiInputValidationErrorHandlerFunction = (ctx: LambderRenderContext<any>, resolver: LambderResolver, zodError: z.ZodError) => LambderResolverResponse | Promise<LambderResolverResponse>;
|
|
52
21
|
/**
|
|
53
22
|
* Main Lambder class for building type-safe serverless APIs
|
|
54
23
|
*
|
|
@@ -86,6 +55,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
86
55
|
private globalErrorHandler;
|
|
87
56
|
private routeFallbackHandler;
|
|
88
57
|
private apiFallbackHandler;
|
|
58
|
+
private apiInputValidationErrorHandler;
|
|
89
59
|
utils: LambderUtils;
|
|
90
60
|
private lambderSessionManager?;
|
|
91
61
|
private sessionTokenCookieKey;
|
|
@@ -109,6 +79,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
109
79
|
setSessionCookieKey(sessionTokenCookieKey: string, sessionCsrfCookieKey: string): this;
|
|
110
80
|
setRouteFallbackHandler(routeFallbackHandler: RouteFallbackHandlerFunction): this;
|
|
111
81
|
setApiFallbackHandler(apiFallbackHandler: ApiFallbackHandlerFunction): this;
|
|
82
|
+
setApiInputValidationErrorHandler(apiInputValidationErrorHandler: ApiInputValidationErrorHandlerFunction): this;
|
|
112
83
|
setGlobalErrorHandler(globalErrorHandler: GlobalErrorHandlerFunction): this;
|
|
113
84
|
private getPatternMatch;
|
|
114
85
|
private testPatternMatch;
|
package/dist/Lambder.js
CHANGED
|
@@ -1,53 +1,10 @@
|
|
|
1
|
-
import cookieParser from "cookie";
|
|
2
1
|
import { match } from "path-to-regexp";
|
|
3
2
|
import LambderResolver from "./LambderResolver.js";
|
|
4
3
|
import LambderResponseBuilder from "./LambderResponseBuilder.js";
|
|
5
4
|
import LambderUtils from "./LambderUtils.js";
|
|
6
5
|
import LambderSessionManager from "./LambderSessionManager.js";
|
|
7
6
|
import LambderSessionController from "./LambderSessionController.js";
|
|
8
|
-
|
|
9
|
-
const host = event.headers.Host || event.headers.host || "";
|
|
10
|
-
const path = event.path;
|
|
11
|
-
const pathParams = null;
|
|
12
|
-
const get = event.queryStringParameters || {};
|
|
13
|
-
const method = event.httpMethod;
|
|
14
|
-
const cookie = cookieParser.parse(event.headers.Cookie || event.headers.cookie || "");
|
|
15
|
-
const headers = event.headers;
|
|
16
|
-
// Decode body for the post
|
|
17
|
-
let post = {};
|
|
18
|
-
try {
|
|
19
|
-
const decodedBody = event.isBase64Encoded ? (event.body ? Buffer.from(event.body, "base64").toString() : "{}") : (event.body || "{}");
|
|
20
|
-
try {
|
|
21
|
-
post = JSON.parse(decodedBody) || {};
|
|
22
|
-
}
|
|
23
|
-
catch (e) {
|
|
24
|
-
const params = new URLSearchParams(decodedBody);
|
|
25
|
-
post = {};
|
|
26
|
-
for (const [key, value] of params.entries()) {
|
|
27
|
-
post[key] = value;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
catch (e) { }
|
|
32
|
-
// Parse api variables
|
|
33
|
-
const isApiCall = method === "POST" && apiPath && path === apiPath && post.apiName;
|
|
34
|
-
const apiName = isApiCall ? post.apiName : null;
|
|
35
|
-
const apiPayload = isApiCall ? post.payload : null;
|
|
36
|
-
const requestVersion = isApiCall ? post.version : null;
|
|
37
|
-
return {
|
|
38
|
-
host, path, pathParams, method,
|
|
39
|
-
get, post, cookie, event,
|
|
40
|
-
session: null,
|
|
41
|
-
apiName, apiPayload,
|
|
42
|
-
headers, lambdaContext,
|
|
43
|
-
_otherInternal: {
|
|
44
|
-
isApiCall, requestVersion,
|
|
45
|
-
setHeaderFnAccumulator: [],
|
|
46
|
-
addHeaderFnAccumulator: [],
|
|
47
|
-
logToApiResponseAccumulator: [],
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
};
|
|
7
|
+
import { createContext } from "./LambderContext.js";
|
|
51
8
|
/**
|
|
52
9
|
* Main Lambder class for building type-safe serverless APIs
|
|
53
10
|
*
|
|
@@ -85,6 +42,7 @@ export default class Lambder {
|
|
|
85
42
|
globalErrorHandler = null;
|
|
86
43
|
routeFallbackHandler = null;
|
|
87
44
|
apiFallbackHandler = null;
|
|
45
|
+
apiInputValidationErrorHandler = null;
|
|
88
46
|
utils;
|
|
89
47
|
lambderSessionManager;
|
|
90
48
|
sessionTokenCookieKey = "LMDRSESSIONTKID";
|
|
@@ -125,6 +83,10 @@ export default class Lambder {
|
|
|
125
83
|
this.apiFallbackHandler = apiFallbackHandler;
|
|
126
84
|
return this;
|
|
127
85
|
}
|
|
86
|
+
setApiInputValidationErrorHandler(apiInputValidationErrorHandler) {
|
|
87
|
+
this.apiInputValidationErrorHandler = apiInputValidationErrorHandler;
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
128
90
|
setGlobalErrorHandler(globalErrorHandler) {
|
|
129
91
|
this.globalErrorHandler = globalErrorHandler;
|
|
130
92
|
return this;
|
|
@@ -211,6 +173,9 @@ export default class Lambder {
|
|
|
211
173
|
// Validate Input
|
|
212
174
|
const inputResult = schema.input.safeParse(ctx.apiPayload);
|
|
213
175
|
if (!inputResult.success) {
|
|
176
|
+
if (this.apiInputValidationErrorHandler) {
|
|
177
|
+
return await this.apiInputValidationErrorHandler(ctx, resolver, inputResult.error);
|
|
178
|
+
}
|
|
214
179
|
return resolver.raw({
|
|
215
180
|
statusCode: 400,
|
|
216
181
|
body: JSON.stringify({ error: "Input validation failed", zodError: inputResult.error }),
|
|
@@ -237,6 +202,9 @@ export default class Lambder {
|
|
|
237
202
|
// Validate Input
|
|
238
203
|
const inputResult = schema.input.safeParse(ctx.apiPayload);
|
|
239
204
|
if (!inputResult.success) {
|
|
205
|
+
if (this.apiInputValidationErrorHandler) {
|
|
206
|
+
return await this.apiInputValidationErrorHandler(ctx, resolver, inputResult.error);
|
|
207
|
+
}
|
|
240
208
|
return resolver.raw({
|
|
241
209
|
statusCode: 400,
|
|
242
210
|
body: JSON.stringify({ error: "Input validation failed", zodError: inputResult.error }),
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, Context } from "aws-lambda";
|
|
2
|
+
import type { LambderSessionContext } from "./LambderSessionManager.js";
|
|
3
|
+
export type LambderRenderContext<TApiPayload = any> = {
|
|
4
|
+
host: string;
|
|
5
|
+
path: string;
|
|
6
|
+
pathParams: Record<string, any> | null;
|
|
7
|
+
method: string;
|
|
8
|
+
get: Record<string, any>;
|
|
9
|
+
post: Record<string, any>;
|
|
10
|
+
cookie: Record<string, any>;
|
|
11
|
+
session: null;
|
|
12
|
+
apiName: string;
|
|
13
|
+
apiPayload: TApiPayload;
|
|
14
|
+
headers: APIGatewayProxyEventHeaders;
|
|
15
|
+
event: APIGatewayProxyEvent;
|
|
16
|
+
lambdaContext: Context;
|
|
17
|
+
_otherInternal: {
|
|
18
|
+
isApiCall: boolean;
|
|
19
|
+
requestVersion: string | null;
|
|
20
|
+
setHeaderFnAccumulator: {
|
|
21
|
+
key: string;
|
|
22
|
+
value: string | string[];
|
|
23
|
+
}[];
|
|
24
|
+
addHeaderFnAccumulator: {
|
|
25
|
+
key: string;
|
|
26
|
+
value: string;
|
|
27
|
+
}[];
|
|
28
|
+
logToApiResponseAccumulator: any[];
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export type LambderSessionRenderContext<TApiPayload = any, SessionData = any> = Omit<LambderRenderContext<TApiPayload>, 'session'> & {
|
|
32
|
+
session: LambderSessionContext<SessionData>;
|
|
33
|
+
};
|
|
34
|
+
export declare const createContext: (event: APIGatewayProxyEvent, lambdaContext: Context, apiPath: string) => LambderRenderContext<any>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import cookieParser from "cookie";
|
|
2
|
+
export const createContext = (event, lambdaContext, apiPath) => {
|
|
3
|
+
const host = event.headers.Host || event.headers.host || "";
|
|
4
|
+
const path = event.path;
|
|
5
|
+
const pathParams = null;
|
|
6
|
+
const get = event.queryStringParameters || {};
|
|
7
|
+
const method = event.httpMethod;
|
|
8
|
+
const cookie = cookieParser.parse(event.headers.Cookie || event.headers.cookie || "");
|
|
9
|
+
const headers = event.headers;
|
|
10
|
+
// Decode body for the post
|
|
11
|
+
let post = {};
|
|
12
|
+
try {
|
|
13
|
+
const decodedBody = event.isBase64Encoded ? (event.body ? Buffer.from(event.body, "base64").toString() : "{}") : (event.body || "{}");
|
|
14
|
+
try {
|
|
15
|
+
post = JSON.parse(decodedBody) || {};
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
const params = new URLSearchParams(decodedBody);
|
|
19
|
+
post = {};
|
|
20
|
+
for (const [key, value] of params.entries()) {
|
|
21
|
+
post[key] = value;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch (e) { }
|
|
26
|
+
// Parse api variables
|
|
27
|
+
const isApiCall = method === "POST" && apiPath && path === apiPath && post.apiName;
|
|
28
|
+
const apiName = isApiCall ? post.apiName : null;
|
|
29
|
+
const apiPayload = isApiCall ? post.payload : null;
|
|
30
|
+
const requestVersion = isApiCall ? post.version : null;
|
|
31
|
+
return {
|
|
32
|
+
host, path, pathParams, method,
|
|
33
|
+
get, post, cookie, event,
|
|
34
|
+
session: null,
|
|
35
|
+
apiName, apiPayload,
|
|
36
|
+
headers, lambdaContext,
|
|
37
|
+
_otherInternal: {
|
|
38
|
+
isApiCall, requestVersion,
|
|
39
|
+
setHeaderFnAccumulator: [],
|
|
40
|
+
addHeaderFnAccumulator: [],
|
|
41
|
+
logToApiResponseAccumulator: [],
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LambderRenderContext } from "./
|
|
1
|
+
import type { LambderRenderContext } from "./LambderContext.js";
|
|
2
2
|
import LambderResponseBuilder, { LambderResolverResponse } from "./LambderResponseBuilder.js";
|
|
3
3
|
import LambderUtils from "./LambderUtils.js";
|
|
4
4
|
type MethodType<T, M extends keyof T> = T[M] extends (...args: any[]) => any ? T[M] : never;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import LambderUtils from "./LambderUtils.js";
|
|
2
|
-
import { LambderRenderContext } from "./
|
|
2
|
+
import { LambderRenderContext } from "./LambderContext.js";
|
|
3
3
|
export type LambderResolverResponse = {
|
|
4
4
|
statusCode: number;
|
|
5
5
|
multiValueHeaders?: Record<string, string[]>;
|
|
@@ -68,7 +68,7 @@ export default class LambderResponseBuilder {
|
|
|
68
68
|
throw new Error(".setHeader function is not available within this hook");
|
|
69
69
|
else {
|
|
70
70
|
this.ctx._otherInternal.addHeaderFnAccumulator = this.ctx._otherInternal.addHeaderFnAccumulator
|
|
71
|
-
.filter(header => header.key !== key);
|
|
71
|
+
.filter((header) => header.key !== key);
|
|
72
72
|
this.ctx._otherInternal.setHeaderFnAccumulator.push({ key, value });
|
|
73
73
|
}
|
|
74
74
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LambderRenderContext, LambderSessionRenderContext } from "./
|
|
1
|
+
import { LambderRenderContext, LambderSessionRenderContext } from "./LambderContext.js";
|
|
2
2
|
import type LambderSessionManager from "./LambderSessionManager.js";
|
|
3
3
|
import type { LambderSessionContext } from "./LambderSessionManager.js";
|
|
4
4
|
export default class LambderSessionController<TSessionData = any> {
|
package/dist/index.d.ts
CHANGED
|
@@ -6,3 +6,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
8
|
export { type ApiContractShape, } from "./LambderApiContract.js";
|
|
9
|
+
export type { LambderRenderContext, LambderSessionRenderContext } from "./LambderContext.js";
|
|
10
|
+
export { createContext } from "./LambderContext.js";
|
package/dist/index.js
CHANGED
|
@@ -5,3 +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 { createContext } from "./LambderContext.js";
|
package/package.json
CHANGED
package/src/Lambder.ts
CHANGED
|
@@ -1,44 +1,17 @@
|
|
|
1
|
-
import cookieParser from "cookie";
|
|
2
1
|
import { match } from "path-to-regexp";
|
|
3
2
|
import { z } from "zod";
|
|
4
3
|
|
|
5
|
-
import type { APIGatewayProxyEvent,
|
|
4
|
+
import type { APIGatewayProxyEvent, Context } from "aws-lambda";
|
|
6
5
|
import LambderResolver from "./LambderResolver.js";
|
|
7
6
|
import LambderResponseBuilder, { LambderResolverResponse } from "./LambderResponseBuilder.js";
|
|
8
7
|
import LambderUtils from "./LambderUtils.js";
|
|
9
|
-
import LambderSessionManager
|
|
8
|
+
import LambderSessionManager from "./LambderSessionManager.js";
|
|
10
9
|
import LambderSessionController from "./LambderSessionController.js";
|
|
11
10
|
import type { MergeContract } from "./LambderApiContract.js";
|
|
11
|
+
import { createContext, type LambderRenderContext, type LambderSessionRenderContext } from "./LambderContext.js";
|
|
12
12
|
|
|
13
13
|
type Path = `/${string}`;
|
|
14
14
|
|
|
15
|
-
export type LambderRenderContext<TApiPayload = any> = {
|
|
16
|
-
host: string;
|
|
17
|
-
path: string;
|
|
18
|
-
pathParams: Record<string, any> | null;
|
|
19
|
-
method: string;
|
|
20
|
-
get: Record<string, any>;
|
|
21
|
-
post: Record<string, any>;
|
|
22
|
-
cookie: Record<string, any>;
|
|
23
|
-
session: null;
|
|
24
|
-
apiName: string;
|
|
25
|
-
apiPayload: TApiPayload;
|
|
26
|
-
headers: APIGatewayProxyEventHeaders;
|
|
27
|
-
event: APIGatewayProxyEvent;
|
|
28
|
-
lambdaContext: Context;
|
|
29
|
-
_otherInternal: {
|
|
30
|
-
isApiCall: boolean,
|
|
31
|
-
requestVersion: string|null;
|
|
32
|
-
setHeaderFnAccumulator: { key:string, value:string|string[] }[];
|
|
33
|
-
addHeaderFnAccumulator: { key:string, value:string }[];
|
|
34
|
-
logToApiResponseAccumulator: any[];
|
|
35
|
-
};
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export type LambderSessionRenderContext<
|
|
39
|
-
TApiPayload = any, SessionData = any
|
|
40
|
-
> = Omit<LambderRenderContext<TApiPayload>, 'session'> & { session: LambderSessionContext<SessionData> };
|
|
41
|
-
|
|
42
15
|
type ConditionFunction = (ctx: LambderRenderContext<any>) => boolean;
|
|
43
16
|
type ActionFunction = (ctx: LambderRenderContext<any>, resolver: LambderResolver) => LambderResolverResponse|Promise<LambderResolverResponse>;
|
|
44
17
|
type SessionActionFunction<SessionData = any> = (ctx: LambderSessionRenderContext<any, SessionData>, resolver: LambderResolver) => LambderResolverResponse|Promise<LambderResolverResponse>;
|
|
@@ -54,55 +27,7 @@ type HookFallbackFunction = (ctx: LambderRenderContext<any>, resolver: LambderRe
|
|
|
54
27
|
type GlobalErrorHandlerFunction = (err: Error, ctx: LambderRenderContext<any>|null, response: LambderResponseBuilder, logListToApiResponse?: any[]) => LambderResolverResponse|Promise<LambderResolverResponse>;
|
|
55
28
|
type RouteFallbackHandlerFunction = (ctx:LambderRenderContext<any>, resolver: LambderResolver) => LambderResolverResponse;
|
|
56
29
|
type ApiFallbackHandlerFunction = (ctx:LambderRenderContext<any>, resolver: LambderResolver) => LambderResolverResponse;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
export const createContext = (
|
|
60
|
-
event: APIGatewayProxyEvent,
|
|
61
|
-
lambdaContext: Context,
|
|
62
|
-
apiPath: string,
|
|
63
|
-
):LambderRenderContext<any> => {
|
|
64
|
-
const host = event.headers.Host || event.headers.host || "";
|
|
65
|
-
const path = event.path;
|
|
66
|
-
const pathParams = null;
|
|
67
|
-
const get: Record<string, any> = event.queryStringParameters || {};
|
|
68
|
-
const method = event.httpMethod;
|
|
69
|
-
const cookie = cookieParser.parse(event.headers.Cookie || event.headers.cookie || "");
|
|
70
|
-
const headers = event.headers;
|
|
71
|
-
|
|
72
|
-
// Decode body for the post
|
|
73
|
-
let post: Record<string, any> = {};
|
|
74
|
-
try {
|
|
75
|
-
const decodedBody = event.isBase64Encoded ? ( event.body ? Buffer.from(event.body,"base64").toString() : "{}" ) : ( event.body || "{}" );
|
|
76
|
-
try { post = JSON.parse(decodedBody) || {}; }
|
|
77
|
-
catch(e){
|
|
78
|
-
const params = new URLSearchParams(decodedBody);
|
|
79
|
-
post = {};
|
|
80
|
-
for(const [key, value] of params.entries()){
|
|
81
|
-
post[key] = value;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}catch(e){}
|
|
85
|
-
// Parse api variables
|
|
86
|
-
|
|
87
|
-
const isApiCall = method === "POST" && apiPath && path === apiPath && post.apiName;
|
|
88
|
-
const apiName:string = isApiCall ? post.apiName : null;
|
|
89
|
-
const apiPayload:string = isApiCall ? post.payload : null;
|
|
90
|
-
const requestVersion:string = isApiCall ? post.version : null;
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
host, path, pathParams, method,
|
|
94
|
-
get, post, cookie, event,
|
|
95
|
-
session: null,
|
|
96
|
-
apiName, apiPayload,
|
|
97
|
-
headers, lambdaContext,
|
|
98
|
-
_otherInternal: {
|
|
99
|
-
isApiCall, requestVersion,
|
|
100
|
-
setHeaderFnAccumulator: [],
|
|
101
|
-
addHeaderFnAccumulator: [],
|
|
102
|
-
logToApiResponseAccumulator: [],
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
}
|
|
30
|
+
type ApiInputValidationErrorHandlerFunction = (ctx: LambderRenderContext<any>, resolver: LambderResolver, zodError: z.ZodError) => LambderResolverResponse|Promise<LambderResolverResponse>;
|
|
106
31
|
|
|
107
32
|
/**
|
|
108
33
|
* Main Lambder class for building type-safe serverless APIs
|
|
@@ -147,6 +72,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
147
72
|
private globalErrorHandler: GlobalErrorHandlerFunction|null = null;
|
|
148
73
|
private routeFallbackHandler: RouteFallbackHandlerFunction|null = null;
|
|
149
74
|
private apiFallbackHandler: ApiFallbackHandlerFunction|null = null;
|
|
75
|
+
private apiInputValidationErrorHandler: ApiInputValidationErrorHandlerFunction|null = null;
|
|
150
76
|
|
|
151
77
|
public utils: LambderUtils;
|
|
152
78
|
|
|
@@ -202,6 +128,10 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
202
128
|
this.apiFallbackHandler = apiFallbackHandler;
|
|
203
129
|
return this;
|
|
204
130
|
}
|
|
131
|
+
setApiInputValidationErrorHandler(apiInputValidationErrorHandler: ApiInputValidationErrorHandlerFunction): this {
|
|
132
|
+
this.apiInputValidationErrorHandler = apiInputValidationErrorHandler;
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
205
135
|
setGlobalErrorHandler(globalErrorHandler: GlobalErrorHandlerFunction): this {
|
|
206
136
|
this.globalErrorHandler = globalErrorHandler;
|
|
207
137
|
return this;
|
|
@@ -308,6 +238,9 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
308
238
|
// Validate Input
|
|
309
239
|
const inputResult = schema.input.safeParse(ctx.apiPayload);
|
|
310
240
|
if (!inputResult.success) {
|
|
241
|
+
if (this.apiInputValidationErrorHandler) {
|
|
242
|
+
return await this.apiInputValidationErrorHandler(ctx, resolver, inputResult.error);
|
|
243
|
+
}
|
|
311
244
|
return resolver.raw({
|
|
312
245
|
statusCode: 400,
|
|
313
246
|
body: JSON.stringify({ error: "Input validation failed", zodError: inputResult.error }),
|
|
@@ -346,6 +279,9 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
346
279
|
// Validate Input
|
|
347
280
|
const inputResult = schema.input.safeParse(ctx.apiPayload);
|
|
348
281
|
if (!inputResult.success) {
|
|
282
|
+
if (this.apiInputValidationErrorHandler) {
|
|
283
|
+
return await this.apiInputValidationErrorHandler(ctx, resolver, inputResult.error);
|
|
284
|
+
}
|
|
349
285
|
return resolver.raw({
|
|
350
286
|
statusCode: 400,
|
|
351
287
|
body: JSON.stringify({ error: "Input validation failed", zodError: inputResult.error }),
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import cookieParser from "cookie";
|
|
2
|
+
import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, Context } from "aws-lambda";
|
|
3
|
+
import type { LambderSessionContext } from "./LambderSessionManager.js";
|
|
4
|
+
|
|
5
|
+
export type LambderRenderContext<TApiPayload = any> = {
|
|
6
|
+
host: string;
|
|
7
|
+
path: string;
|
|
8
|
+
pathParams: Record<string, any> | null;
|
|
9
|
+
method: string;
|
|
10
|
+
get: Record<string, any>;
|
|
11
|
+
post: Record<string, any>;
|
|
12
|
+
cookie: Record<string, any>;
|
|
13
|
+
session: null;
|
|
14
|
+
apiName: string;
|
|
15
|
+
apiPayload: TApiPayload;
|
|
16
|
+
headers: APIGatewayProxyEventHeaders;
|
|
17
|
+
event: APIGatewayProxyEvent;
|
|
18
|
+
lambdaContext: Context;
|
|
19
|
+
_otherInternal: {
|
|
20
|
+
isApiCall: boolean,
|
|
21
|
+
requestVersion: string|null;
|
|
22
|
+
setHeaderFnAccumulator: { key:string, value:string|string[] }[];
|
|
23
|
+
addHeaderFnAccumulator: { key:string, value:string }[];
|
|
24
|
+
logToApiResponseAccumulator: any[];
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type LambderSessionRenderContext<
|
|
29
|
+
TApiPayload = any, SessionData = any
|
|
30
|
+
> = Omit<LambderRenderContext<TApiPayload>, 'session'> & { session: LambderSessionContext<SessionData> };
|
|
31
|
+
|
|
32
|
+
export const createContext = (
|
|
33
|
+
event: APIGatewayProxyEvent,
|
|
34
|
+
lambdaContext: Context,
|
|
35
|
+
apiPath: string,
|
|
36
|
+
):LambderRenderContext<any> => {
|
|
37
|
+
const host = event.headers.Host || event.headers.host || "";
|
|
38
|
+
const path = event.path;
|
|
39
|
+
const pathParams = null;
|
|
40
|
+
const get: Record<string, any> = event.queryStringParameters || {};
|
|
41
|
+
const method = event.httpMethod;
|
|
42
|
+
const cookie = cookieParser.parse(event.headers.Cookie || event.headers.cookie || "");
|
|
43
|
+
const headers = event.headers;
|
|
44
|
+
|
|
45
|
+
// Decode body for the post
|
|
46
|
+
let post: Record<string, any> = {};
|
|
47
|
+
try {
|
|
48
|
+
const decodedBody = event.isBase64Encoded ? ( event.body ? Buffer.from(event.body,"base64").toString() : "{}" ) : ( event.body || "{}" );
|
|
49
|
+
try { post = JSON.parse(decodedBody) || {}; }
|
|
50
|
+
catch(e){
|
|
51
|
+
const params = new URLSearchParams(decodedBody);
|
|
52
|
+
post = {};
|
|
53
|
+
for(const [key, value] of params.entries()){
|
|
54
|
+
post[key] = value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}catch(e){}
|
|
58
|
+
// Parse api variables
|
|
59
|
+
|
|
60
|
+
const isApiCall = method === "POST" && apiPath && path === apiPath && post.apiName;
|
|
61
|
+
const apiName:string = isApiCall ? post.apiName : null;
|
|
62
|
+
const apiPayload:string = isApiCall ? post.payload : null;
|
|
63
|
+
const requestVersion:string = isApiCall ? post.version : null;
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
host, path, pathParams, method,
|
|
67
|
+
get, post, cookie, event,
|
|
68
|
+
session: null,
|
|
69
|
+
apiName, apiPayload,
|
|
70
|
+
headers, lambdaContext,
|
|
71
|
+
_otherInternal: {
|
|
72
|
+
isApiCall, requestVersion,
|
|
73
|
+
setHeaderFnAccumulator: [],
|
|
74
|
+
addHeaderFnAccumulator: [],
|
|
75
|
+
logToApiResponseAccumulator: [],
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
package/src/LambderResolver.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import mimeTypeResolver from "mime-types";
|
|
2
2
|
import LambderUtils from "./LambderUtils.js";
|
|
3
|
-
import { LambderRenderContext } from "./
|
|
3
|
+
import { LambderRenderContext } from "./LambderContext.js";
|
|
4
4
|
import { getFS, getPath } from "./node-polyfills.js";
|
|
5
5
|
|
|
6
6
|
const convertToMultiHeader = (
|
|
@@ -102,7 +102,7 @@ export default class LambderResponseBuilder<TResponse = any> {
|
|
|
102
102
|
if(!this.ctx) throw new Error(".setHeader function is not available within this hook");
|
|
103
103
|
else{
|
|
104
104
|
this.ctx._otherInternal.addHeaderFnAccumulator = this.ctx._otherInternal.addHeaderFnAccumulator
|
|
105
|
-
.filter(header=>header.key !== key);
|
|
105
|
+
.filter((header: { key: string, value: string }) => header.key !== key);
|
|
106
106
|
this.ctx._otherInternal.setHeaderFnAccumulator.push({ key, value });
|
|
107
107
|
}
|
|
108
108
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LambderRenderContext, LambderSessionRenderContext } from "./
|
|
1
|
+
import { LambderRenderContext, LambderSessionRenderContext } from "./LambderContext.js";
|
|
2
2
|
import type LambderSessionManager from "./LambderSessionManager.js";
|
|
3
3
|
import type { LambderSessionContext } from "./LambderSessionManager.js";
|
|
4
4
|
|
package/src/index.ts
CHANGED
|
@@ -11,3 +11,7 @@ export { default as LambderMSW } from "./LambderMSW.js";
|
|
|
11
11
|
export {
|
|
12
12
|
type ApiContractShape,
|
|
13
13
|
} from "./LambderApiContract.js";
|
|
14
|
+
|
|
15
|
+
// Context types and utilities
|
|
16
|
+
export type { LambderRenderContext, LambderSessionRenderContext } from "./LambderContext.js";
|
|
17
|
+
export { createContext } from "./LambderContext.js";
|