lambder 2.0.15 → 2.0.16

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 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: 400,
178
+ statusCode: 422,
179
179
  body: JSON.stringify({ error: "Input validation failed", zodError: inputResult.error }),
180
180
  multiValueHeaders: { "Content-Type": ["application/json"] }
181
181
  });
@@ -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;
@@ -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: " + String(err));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambder",
3
- "version": "2.0.15",
3
+ "version": "2.0.16",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
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: 400,
243
+ statusCode: 422,
244
244
  body: JSON.stringify({ error: "Input validation failed", zodError: inputResult.error }),
245
245
  multiValueHeaders: { "Content-Type": ["application/json"] }
246
246
  });
@@ -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: " + String(err));
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(400);
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(400);
470
+ expect(result.statusCode).toBe(422);
471
471
  });
472
472
  });
473
473