lambder 2.0.15 → 2.0.18
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.js +2 -2
- package/dist/LambderCaller.d.ts +6 -1
- package/dist/LambderCaller.js +15 -2
- package/package.json +1 -1
- package/src/Lambder.ts +2 -2
- package/src/LambderCaller.ts +17 -1
- package/tests/error-handling.test.ts +2 -2
package/dist/Lambder.js
CHANGED
|
@@ -175,7 +175,7 @@ export default class Lambder {
|
|
|
175
175
|
return await this.apiInputValidationErrorHandler(ctx, resolver, inputResult.error);
|
|
176
176
|
}
|
|
177
177
|
return resolver.raw({
|
|
178
|
-
statusCode:
|
|
178
|
+
statusCode: 422,
|
|
179
179
|
body: JSON.stringify({ error: "Input validation failed", zodError: inputResult.error }),
|
|
180
180
|
multiValueHeaders: { "Content-Type": ["application/json"] }
|
|
181
181
|
});
|
|
@@ -267,7 +267,7 @@ export default class Lambder {
|
|
|
267
267
|
try {
|
|
268
268
|
const resolver = this.getResolver(ctx, resolve, reject);
|
|
269
269
|
if (ctx.method === "OPTIONS")
|
|
270
|
-
return resolver.cors();
|
|
270
|
+
return resolve(resolver.cors());
|
|
271
271
|
const firstMatchedAction = this.actionList.find(action => action.conditionFn(ctx));
|
|
272
272
|
if (firstMatchedAction) {
|
|
273
273
|
// Check version if provided by the client and the server
|
package/dist/LambderCaller.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LambderApiResponse } from './LambderResponseBuilder';
|
|
2
2
|
import type { ApiContractShape } from './LambderApiContract';
|
|
3
|
+
import type { z } from "zod";
|
|
3
4
|
type VoidFunction = () => void | Promise<void>;
|
|
4
5
|
type FetchTracker = {
|
|
5
6
|
apiName: string;
|
|
@@ -21,6 +22,7 @@ type FetchEndEventHandler = (params: {
|
|
|
21
22
|
activeFetchList: FetchTracker[];
|
|
22
23
|
}) => void | Promise<void>;
|
|
23
24
|
type ErrorHandler = (err: Error) => void | Promise<void>;
|
|
25
|
+
type ValidationErrorHandler = (zodError: z.ZodError) => (void | false) | Promise<(void | false)>;
|
|
24
26
|
type MessageHandler = (message: any) => void | Promise<void>;
|
|
25
27
|
export default class LambderCaller<TContract extends ApiContractShape = any> {
|
|
26
28
|
private isCorsEnabled;
|
|
@@ -34,11 +36,12 @@ export default class LambderCaller<TContract extends ApiContractShape = any> {
|
|
|
34
36
|
private errorMessageHandler?;
|
|
35
37
|
private notAuthorizedHandler?;
|
|
36
38
|
private errorHandler?;
|
|
39
|
+
private apiInputValidationErrorHandler?;
|
|
37
40
|
private fetchStartedHandler?;
|
|
38
41
|
private fetchEndedHandler?;
|
|
39
42
|
private sessionTokenCookieKey;
|
|
40
43
|
private sessionCsrfCookieKey;
|
|
41
|
-
constructor({ apiPath, apiVersion, isCorsEnabled, versionExpiredHandler, sessionExpiredHandler, messageHandler, errorMessageHandler, notAuthorizedHandler, errorHandler, fetchStartedHandler, fetchEndedHandler, }: {
|
|
44
|
+
constructor({ apiPath, apiVersion, isCorsEnabled, versionExpiredHandler, sessionExpiredHandler, messageHandler, errorMessageHandler, notAuthorizedHandler, errorHandler, fetchStartedHandler, fetchEndedHandler, apiInputValidationErrorHandler, }: {
|
|
42
45
|
apiPath: string;
|
|
43
46
|
apiVersion?: string;
|
|
44
47
|
isCorsEnabled: boolean;
|
|
@@ -50,6 +53,7 @@ export default class LambderCaller<TContract extends ApiContractShape = any> {
|
|
|
50
53
|
errorHandler?: ErrorHandler;
|
|
51
54
|
fetchStartedHandler?: FetchStartEventHandler;
|
|
52
55
|
fetchEndedHandler?: FetchEndEventHandler;
|
|
56
|
+
apiInputValidationErrorHandler?: ValidationErrorHandler;
|
|
53
57
|
});
|
|
54
58
|
setSessionCookieKey(sessionTokenCookieKey: string, sessionCsrfCookieKey: string): void;
|
|
55
59
|
apiRaw<TApiName extends keyof TContract & string = string, TOutput = TApiName extends keyof TContract ? TContract[TApiName]['output'] : any>(apiName: TApiName, payload?: TApiName extends keyof TContract ? TContract[TApiName]['input'] : any, options?: {
|
|
@@ -58,6 +62,7 @@ export default class LambderCaller<TContract extends ApiContractShape = any> {
|
|
|
58
62
|
sessionExpiredHandler?: VoidFunction;
|
|
59
63
|
messageHandler?: MessageHandler;
|
|
60
64
|
errorMessageHandler?: MessageHandler;
|
|
65
|
+
apiInputValidationErrorHandler?: ValidationErrorHandler;
|
|
61
66
|
notAuthorizedHandler?: VoidFunction;
|
|
62
67
|
errorHandler?: ErrorHandler;
|
|
63
68
|
fetchStartedHandler?: FetchStartEventHandler;
|
package/dist/LambderCaller.js
CHANGED
|
@@ -11,11 +11,12 @@ export default class LambderCaller {
|
|
|
11
11
|
errorMessageHandler;
|
|
12
12
|
notAuthorizedHandler;
|
|
13
13
|
errorHandler;
|
|
14
|
+
apiInputValidationErrorHandler;
|
|
14
15
|
fetchStartedHandler;
|
|
15
16
|
fetchEndedHandler;
|
|
16
17
|
sessionTokenCookieKey = "LMDRSESSIONTKID";
|
|
17
18
|
sessionCsrfCookieKey = "LMDRSESSIONCSTK";
|
|
18
|
-
constructor({ apiPath, apiVersion, isCorsEnabled = false, versionExpiredHandler, sessionExpiredHandler, messageHandler, errorMessageHandler, notAuthorizedHandler, errorHandler, fetchStartedHandler, fetchEndedHandler, }) {
|
|
19
|
+
constructor({ apiPath, apiVersion, isCorsEnabled = false, versionExpiredHandler, sessionExpiredHandler, messageHandler, errorMessageHandler, notAuthorizedHandler, errorHandler, fetchStartedHandler, fetchEndedHandler, apiInputValidationErrorHandler, }) {
|
|
19
20
|
this.apiPath = apiPath ?? "/api";
|
|
20
21
|
this.apiVersion = apiVersion;
|
|
21
22
|
this.isCorsEnabled = isCorsEnabled;
|
|
@@ -25,6 +26,7 @@ export default class LambderCaller {
|
|
|
25
26
|
this.errorMessageHandler = errorMessageHandler;
|
|
26
27
|
this.notAuthorizedHandler = notAuthorizedHandler;
|
|
27
28
|
this.errorHandler = errorHandler;
|
|
29
|
+
this.apiInputValidationErrorHandler = apiInputValidationErrorHandler;
|
|
28
30
|
this.fetchStartedHandler = fetchStartedHandler;
|
|
29
31
|
this.fetchEndedHandler = fetchEndedHandler;
|
|
30
32
|
}
|
|
@@ -54,6 +56,17 @@ export default class LambderCaller {
|
|
|
54
56
|
}).then(async (res) => {
|
|
55
57
|
if (res.status >= 500)
|
|
56
58
|
throw new Error("Request failed: " + res.status + " - " + res.statusText);
|
|
59
|
+
if (res.status === 422) {
|
|
60
|
+
const errorData = await res.json();
|
|
61
|
+
if (this.apiInputValidationErrorHandler) {
|
|
62
|
+
await this.apiInputValidationErrorHandler(errorData.zodError);
|
|
63
|
+
}
|
|
64
|
+
else if (this.errorHandler) {
|
|
65
|
+
await this.errorHandler(new Error("API Input Validation Error", { cause: errorData.zodError }));
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
;
|
|
57
70
|
if (res.headers.get("Content-Type")?.includes("application/lambder-json-stream")) {
|
|
58
71
|
const decompressed = res.json();
|
|
59
72
|
return decompressed;
|
|
@@ -114,7 +127,7 @@ export default class LambderCaller {
|
|
|
114
127
|
return data;
|
|
115
128
|
}
|
|
116
129
|
catch (err) {
|
|
117
|
-
const wrappedError = err instanceof Error ? err : new Error("Error: "
|
|
130
|
+
const wrappedError = err instanceof Error ? err : new Error("Error: ", { cause: err });
|
|
118
131
|
fetchTracker.done = true;
|
|
119
132
|
if (!fetchTracker.fetchEndCalled && this.fetchEndedHandler) {
|
|
120
133
|
await this.fetchEndedHandler({
|
package/package.json
CHANGED
package/src/Lambder.ts
CHANGED
|
@@ -240,7 +240,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
240
240
|
return await this.apiInputValidationErrorHandler(ctx, resolver, inputResult.error);
|
|
241
241
|
}
|
|
242
242
|
return resolver.raw({
|
|
243
|
-
statusCode:
|
|
243
|
+
statusCode: 422,
|
|
244
244
|
body: JSON.stringify({ error: "Input validation failed", zodError: inputResult.error }),
|
|
245
245
|
multiValueHeaders: { "Content-Type": ["application/json"] }
|
|
246
246
|
});
|
|
@@ -367,7 +367,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
367
367
|
)=> {
|
|
368
368
|
try{
|
|
369
369
|
const resolver = this.getResolver(ctx, resolve, reject);
|
|
370
|
-
if(ctx.method === "OPTIONS") return resolver.cors();
|
|
370
|
+
if(ctx.method === "OPTIONS") return resolve(resolver.cors());
|
|
371
371
|
|
|
372
372
|
const firstMatchedAction = this.actionList.find(action => action.conditionFn(ctx));
|
|
373
373
|
if(firstMatchedAction){
|
package/src/LambderCaller.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Cookies from 'js-cookie';
|
|
2
2
|
import { LambderApiResponse } from './LambderResponseBuilder';
|
|
3
3
|
import type { ApiContractShape } from './LambderApiContract';
|
|
4
|
+
import type { z } from "zod";
|
|
4
5
|
|
|
5
6
|
type VoidFunction = ()=>void|Promise<void>;
|
|
6
7
|
type FetchTracker = { apiName: string, done: boolean, fetchEndCalled: boolean };
|
|
@@ -22,6 +23,7 @@ type FetchEndEventHandler = (params: {
|
|
|
22
23
|
})=>void|Promise<void>;
|
|
23
24
|
|
|
24
25
|
type ErrorHandler = (err: Error) => void|Promise<void>;
|
|
26
|
+
type ValidationErrorHandler = (zodError: z.ZodError) => (void|false)|Promise<(void|false)>;
|
|
25
27
|
type MessageHandler = (message:any) => void|Promise<void>;
|
|
26
28
|
|
|
27
29
|
export default class LambderCaller<TContract extends ApiContractShape = any> {
|
|
@@ -39,6 +41,7 @@ export default class LambderCaller<TContract extends ApiContractShape = any> {
|
|
|
39
41
|
private errorMessageHandler?: MessageHandler;
|
|
40
42
|
private notAuthorizedHandler?: VoidFunction;
|
|
41
43
|
private errorHandler?: ErrorHandler;
|
|
44
|
+
private apiInputValidationErrorHandler?: ValidationErrorHandler;
|
|
42
45
|
|
|
43
46
|
private fetchStartedHandler?: FetchStartEventHandler;
|
|
44
47
|
private fetchEndedHandler?: FetchEndEventHandler;
|
|
@@ -54,6 +57,7 @@ export default class LambderCaller<TContract extends ApiContractShape = any> {
|
|
|
54
57
|
messageHandler, errorMessageHandler,
|
|
55
58
|
notAuthorizedHandler, errorHandler,
|
|
56
59
|
fetchStartedHandler, fetchEndedHandler,
|
|
60
|
+
apiInputValidationErrorHandler,
|
|
57
61
|
}:
|
|
58
62
|
{
|
|
59
63
|
apiPath: string,
|
|
@@ -67,6 +71,7 @@ export default class LambderCaller<TContract extends ApiContractShape = any> {
|
|
|
67
71
|
errorHandler?: ErrorHandler,
|
|
68
72
|
fetchStartedHandler?: FetchStartEventHandler,
|
|
69
73
|
fetchEndedHandler?: FetchEndEventHandler,
|
|
74
|
+
apiInputValidationErrorHandler?: ValidationErrorHandler,
|
|
70
75
|
}
|
|
71
76
|
){
|
|
72
77
|
this.apiPath = apiPath ?? "/api";
|
|
@@ -80,6 +85,7 @@ export default class LambderCaller<TContract extends ApiContractShape = any> {
|
|
|
80
85
|
this.errorMessageHandler = errorMessageHandler;
|
|
81
86
|
this.notAuthorizedHandler = notAuthorizedHandler;
|
|
82
87
|
this.errorHandler = errorHandler;
|
|
88
|
+
this.apiInputValidationErrorHandler = apiInputValidationErrorHandler;
|
|
83
89
|
|
|
84
90
|
this.fetchStartedHandler = fetchStartedHandler;
|
|
85
91
|
this.fetchEndedHandler = fetchEndedHandler;
|
|
@@ -103,6 +109,7 @@ export default class LambderCaller<TContract extends ApiContractShape = any> {
|
|
|
103
109
|
sessionExpiredHandler?: VoidFunction,
|
|
104
110
|
messageHandler?: MessageHandler,
|
|
105
111
|
errorMessageHandler?: MessageHandler,
|
|
112
|
+
apiInputValidationErrorHandler?: ValidationErrorHandler,
|
|
106
113
|
notAuthorizedHandler?: VoidFunction,
|
|
107
114
|
errorHandler?: ErrorHandler,
|
|
108
115
|
fetchStartedHandler?: FetchStartEventHandler,
|
|
@@ -127,6 +134,15 @@ export default class LambderCaller<TContract extends ApiContractShape = any> {
|
|
|
127
134
|
body: JSON.stringify({ apiName, version, token, siteHost, payload, }),
|
|
128
135
|
}).then(async (res)=>{
|
|
129
136
|
if(res.status >= 500) throw new Error("Request failed: " + res.status + " - " + res.statusText);
|
|
137
|
+
if(res.status === 422){
|
|
138
|
+
const errorData: { error: string, zodError: z.ZodError } = await res.json();
|
|
139
|
+
if(this.apiInputValidationErrorHandler){
|
|
140
|
+
await this.apiInputValidationErrorHandler(errorData.zodError);
|
|
141
|
+
}else if(this.errorHandler){
|
|
142
|
+
await this.errorHandler(new Error("API Input Validation Error", { cause: errorData.zodError }));
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
};
|
|
130
146
|
if(res.headers.get("Content-Type")?.includes("application/lambder-json-stream")){
|
|
131
147
|
const decompressed = res.json();
|
|
132
148
|
return decompressed;
|
|
@@ -182,7 +198,7 @@ export default class LambderCaller<TContract extends ApiContractShape = any> {
|
|
|
182
198
|
}
|
|
183
199
|
return data;
|
|
184
200
|
}catch(err){
|
|
185
|
-
const wrappedError = err instanceof Error ? err : new Error("Error: "
|
|
201
|
+
const wrappedError = err instanceof Error ? err : new Error("Error: ", { cause: err });
|
|
186
202
|
fetchTracker.done = true;
|
|
187
203
|
if(!fetchTracker.fetchEndCalled && this.fetchEndedHandler){
|
|
188
204
|
await this.fetchEndedHandler({
|
|
@@ -441,7 +441,7 @@ describe('Error Handling - Input Validation Errors', () => {
|
|
|
441
441
|
});
|
|
442
442
|
const result = await handler(event, createMockContext());
|
|
443
443
|
|
|
444
|
-
expect(result.statusCode).toBe(
|
|
444
|
+
expect(result.statusCode).toBe(422);
|
|
445
445
|
const body = JSON.parse(result.body || '{}');
|
|
446
446
|
expect(body.error).toBe('Input validation failed');
|
|
447
447
|
expect(body.zodError).toBeDefined();
|
|
@@ -467,7 +467,7 @@ describe('Error Handling - Input Validation Errors', () => {
|
|
|
467
467
|
const event = createMockEvent('/api', 'POST', 'testApi', { value: 'abc' }); // Too short
|
|
468
468
|
const result = await handler(event, createMockContext());
|
|
469
469
|
|
|
470
|
-
expect(result.statusCode).toBe(
|
|
470
|
+
expect(result.statusCode).toBe(422);
|
|
471
471
|
});
|
|
472
472
|
});
|
|
473
473
|
|