lambder 1.0.41 → 1.0.44
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 +12 -9
- package/dist/Lambder.js +68 -53
- package/dist/Resolver.d.ts +2 -2
- package/package.json +2 -1
- package/src/Lambder.ts +78 -72
- package/src/Resolver.ts +2 -2
package/dist/Lambder.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, Context } from "aws-lambda";
|
|
2
2
|
import Resolver from "./Resolver.js";
|
|
3
3
|
import ResponseBuilder, { ResolverResponse } from "./ResponseBuilder.js";
|
|
4
|
-
type
|
|
4
|
+
type Session = {
|
|
5
5
|
userId: string;
|
|
6
6
|
userIdHash: string;
|
|
7
7
|
sessionHash: string;
|
|
@@ -11,10 +11,13 @@ type DDBSession = {
|
|
|
11
11
|
data: {
|
|
12
12
|
[key: string]: any;
|
|
13
13
|
};
|
|
14
|
-
}
|
|
14
|
+
};
|
|
15
15
|
type RenderContext = {
|
|
16
16
|
host: string;
|
|
17
|
-
|
|
17
|
+
path: string;
|
|
18
|
+
pathParams: {
|
|
19
|
+
[key: string]: any;
|
|
20
|
+
} | null;
|
|
18
21
|
method: string;
|
|
19
22
|
get: {
|
|
20
23
|
[key: string]: any;
|
|
@@ -26,16 +29,16 @@ type RenderContext = {
|
|
|
26
29
|
[key: string]: any;
|
|
27
30
|
};
|
|
28
31
|
headers: APIGatewayProxyEventHeaders;
|
|
29
|
-
session:
|
|
32
|
+
session: Session | null;
|
|
30
33
|
lambdaContext: Context;
|
|
31
34
|
};
|
|
32
|
-
type ConditionFunction = (ctx: RenderContext) => boolean
|
|
35
|
+
type ConditionFunction = (ctx: RenderContext) => boolean;
|
|
33
36
|
type ActionFunction = (ctx: RenderContext, resolver: Resolver) => ResolverResponse | Promise<ResolverResponse>;
|
|
34
37
|
type HookCreatedFunction = (lambderInstance: Lambder) => Promise<void>;
|
|
35
38
|
type HookBeforeRenderFunction = (ctx: RenderContext, resolver: Resolver) => RenderContext | Promise<RenderContext>;
|
|
36
|
-
type HookAfterRenderFunction = (ctx: RenderContext, resolver: Resolver,
|
|
39
|
+
type HookAfterRenderFunction = (ctx: RenderContext, resolver: Resolver, response: ResolverResponse) => ResolverResponse | Promise<ResolverResponse>;
|
|
37
40
|
type HookFallbackFunction = (ctx: RenderContext, resolver: Resolver) => ResolverResponse | Promise<ResolverResponse>;
|
|
38
|
-
type HookCompletedFunction = (
|
|
41
|
+
type HookCompletedFunction = (response: ResolverResponse) => ResolverResponse | Promise<ResolverResponse>;
|
|
39
42
|
type GlobalErrorHandlerFunction = (err: Error, ctx: RenderContext | null, response: ResponseBuilder) => ResolverResponse | Promise<ResolverResponse>;
|
|
40
43
|
type RouteFallbackHandlerFunction = (ctx: RenderContext, resolver: Resolver) => ResolverResponse;
|
|
41
44
|
type ApiFallbackHandlerFunction = (ctx: RenderContext, resolver: Resolver) => ResolverResponse;
|
|
@@ -45,8 +48,8 @@ export default class Lambder {
|
|
|
45
48
|
private apiVersion;
|
|
46
49
|
private isCorsEnabled;
|
|
47
50
|
private publicPath;
|
|
48
|
-
private
|
|
49
|
-
private
|
|
51
|
+
private actionList;
|
|
52
|
+
private hookList;
|
|
50
53
|
private globalErrorHandler;
|
|
51
54
|
private routeFallbackHandler;
|
|
52
55
|
private apiFallbackHandler;
|
package/dist/Lambder.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import querystring from "querystring";
|
|
2
2
|
import cookieParser from "cookie";
|
|
3
|
+
import { match } from "path-to-regexp";
|
|
3
4
|
import Resolver from "./Resolver.js";
|
|
4
5
|
import ResponseBuilder from "./ResponseBuilder.js";
|
|
5
6
|
export const createContext = (event, lambdaContext) => {
|
|
6
7
|
const host = event.headers.Host || event.headers.host || "";
|
|
7
|
-
const
|
|
8
|
+
const path = event.path;
|
|
9
|
+
const pathParams = null;
|
|
8
10
|
const get = event.queryStringParameters || {};
|
|
9
11
|
const method = event.httpMethod;
|
|
10
12
|
const cookie = cookieParser.parse(event.headers.Cookie || event.headers.cookie || "");
|
|
@@ -24,15 +26,15 @@ export const createContext = (event, lambdaContext) => {
|
|
|
24
26
|
if (/^(((\d+\.)+\d+)|(([a-f\d]+:)+[a-f\d]+))$/.test(host)) {
|
|
25
27
|
throw new Error("[404] Host cannot be an ip address: " + host);
|
|
26
28
|
}
|
|
27
|
-
return { host,
|
|
29
|
+
return { host, path, pathParams, method, get, post, cookie, headers, session, lambdaContext };
|
|
28
30
|
};
|
|
29
31
|
export default class Lambder {
|
|
30
32
|
apiPath;
|
|
31
33
|
apiVersion;
|
|
32
34
|
isCorsEnabled;
|
|
33
35
|
publicPath;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
actionList;
|
|
37
|
+
hookList;
|
|
36
38
|
globalErrorHandler = null;
|
|
37
39
|
routeFallbackHandler = null;
|
|
38
40
|
apiFallbackHandler = null;
|
|
@@ -41,8 +43,8 @@ export default class Lambder {
|
|
|
41
43
|
this.apiPath = apiPath ?? "/api";
|
|
42
44
|
this.apiVersion = apiVersion ?? null;
|
|
43
45
|
this.isCorsEnabled = isCorsEnabled ?? false;
|
|
44
|
-
this.
|
|
45
|
-
this.
|
|
46
|
+
this.actionList = [];
|
|
47
|
+
this.hookList = {
|
|
46
48
|
"beforeRender": [],
|
|
47
49
|
"afterRender": [],
|
|
48
50
|
"fallback": [],
|
|
@@ -72,62 +74,80 @@ export default class Lambder {
|
|
|
72
74
|
}
|
|
73
75
|
;
|
|
74
76
|
async handleNoMatchedAction(ctx, resolver) {
|
|
75
|
-
for (const hook of this.
|
|
77
|
+
for (const hook of this.hookList["fallback"]) {
|
|
76
78
|
await hook.hookFn(ctx, resolver);
|
|
77
79
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
80
|
+
const isAPI = ctx.path === this.apiPath;
|
|
81
|
+
if (isAPI && this.apiFallbackHandler) {
|
|
82
|
+
resolver.resolve(await this.apiFallbackHandler(ctx, resolver));
|
|
83
|
+
}
|
|
84
|
+
else if (isAPI) {
|
|
85
|
+
resolver.resolve({ statusCode: 204, body: "API handler not set.", });
|
|
85
86
|
}
|
|
86
87
|
else if (this.routeFallbackHandler) {
|
|
87
88
|
resolver.resolve(await this.routeFallbackHandler(ctx, resolver));
|
|
88
89
|
}
|
|
89
90
|
else {
|
|
90
|
-
resolver.resolve({ statusCode: 204, body: "
|
|
91
|
+
resolver.resolve({ statusCode: 204, body: "Route handler not set.", });
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
async addModule(moduleFn) {
|
|
94
95
|
await moduleFn(this);
|
|
95
96
|
}
|
|
96
97
|
addRoute(condition, actionFn) {
|
|
97
|
-
this.
|
|
98
|
-
conditionFn:
|
|
99
|
-
((typeof condition === "string" && ctx.
|
|
100
|
-
(typeof condition === "function" &&
|
|
101
|
-
(condition?.constructor == RegExp && condition.test(ctx.
|
|
102
|
-
actionFn: async (ctx, resolver) =>
|
|
98
|
+
this.actionList.push({
|
|
99
|
+
conditionFn: (ctx) => (ctx.method === "GET" &&
|
|
100
|
+
((typeof condition === "string" && ctx.path === condition) ||
|
|
101
|
+
(typeof condition === "function" && condition(ctx)) ||
|
|
102
|
+
(condition?.constructor == RegExp && condition.test(ctx.path)))),
|
|
103
|
+
actionFn: async (ctx, resolver) => {
|
|
104
|
+
if (typeof condition === "string") {
|
|
105
|
+
ctx.pathParams = (match(condition))(ctx.path) || {};
|
|
106
|
+
}
|
|
107
|
+
else if (condition?.constructor == RegExp) {
|
|
108
|
+
ctx.pathParams = ctx.path.match(condition);
|
|
109
|
+
}
|
|
110
|
+
return await actionFn(ctx, resolver);
|
|
111
|
+
}
|
|
103
112
|
});
|
|
104
113
|
}
|
|
105
114
|
;
|
|
106
115
|
addSessionRoute(condition, actionFn) {
|
|
107
|
-
this.
|
|
108
|
-
conditionFn:
|
|
109
|
-
((typeof condition === "string" && ctx.
|
|
110
|
-
(typeof condition === "function" &&
|
|
111
|
-
(condition?.constructor == RegExp && condition.test(ctx.
|
|
112
|
-
actionFn: async (ctx, resolver) =>
|
|
116
|
+
this.actionList.push({
|
|
117
|
+
conditionFn: (ctx) => (ctx.method === "GET" &&
|
|
118
|
+
((typeof condition === "string" && ctx.path === condition) ||
|
|
119
|
+
(typeof condition === "function" && condition(ctx)) ||
|
|
120
|
+
(condition?.constructor == RegExp && condition.test(ctx.path)))),
|
|
121
|
+
actionFn: async (ctx, resolver) => {
|
|
122
|
+
const isSessionValid = this.validateSession(ctx.session);
|
|
123
|
+
if (!isSessionValid)
|
|
124
|
+
throw new Error("Session not found");
|
|
125
|
+
if (typeof condition === "string") {
|
|
126
|
+
ctx.pathParams = (match(condition))(ctx.path) || {};
|
|
127
|
+
}
|
|
128
|
+
else if (condition?.constructor == RegExp) {
|
|
129
|
+
ctx.pathParams = ctx.path.match(condition);
|
|
130
|
+
}
|
|
131
|
+
return await actionFn(ctx, resolver);
|
|
132
|
+
}
|
|
113
133
|
});
|
|
114
134
|
}
|
|
115
135
|
;
|
|
116
136
|
addApi(condition, actionFn) {
|
|
117
|
-
this.
|
|
118
|
-
conditionFn:
|
|
137
|
+
this.actionList.push({
|
|
138
|
+
conditionFn: (ctx) => (ctx.method === "POST" && ctx.path === this.apiPath &&
|
|
119
139
|
((typeof condition === "string" && ctx.post?.api === condition) ||
|
|
120
|
-
(typeof condition === "function" &&
|
|
140
|
+
(typeof condition === "function" && condition(ctx)) ||
|
|
121
141
|
(condition?.constructor == RegExp && condition.test(ctx.post?.api)))),
|
|
122
|
-
actionFn: async (ctx, resolver) => await actionFn(ctx, resolver)
|
|
142
|
+
actionFn: async (ctx, resolver) => await actionFn(ctx, resolver),
|
|
123
143
|
});
|
|
124
144
|
}
|
|
125
145
|
;
|
|
126
146
|
addSessionApi(condition, actionFn) {
|
|
127
|
-
this.
|
|
128
|
-
conditionFn:
|
|
147
|
+
this.actionList.push({
|
|
148
|
+
conditionFn: (ctx) => (ctx.method === "POST" && ctx.path === this.apiPath &&
|
|
129
149
|
((typeof condition === "string" && ctx.post?.api === condition) ||
|
|
130
|
-
(typeof condition === "function" &&
|
|
150
|
+
(typeof condition === "function" && condition(ctx)) ||
|
|
131
151
|
(condition?.constructor == RegExp && condition.test(ctx.post?.api)))),
|
|
132
152
|
actionFn: async (ctx, resolver) => {
|
|
133
153
|
const isSessionValid = this.validateSession(ctx.session);
|
|
@@ -143,8 +163,8 @@ export default class Lambder {
|
|
|
143
163
|
await hookFn(this);
|
|
144
164
|
}
|
|
145
165
|
else {
|
|
146
|
-
this.
|
|
147
|
-
this.
|
|
166
|
+
this.hookList[hookEvent].push({ priority, hookFn });
|
|
167
|
+
this.hookList[hookEvent].sort((a, b) => a.priority - b.priority);
|
|
148
168
|
}
|
|
149
169
|
}
|
|
150
170
|
getResponseBuilder() {
|
|
@@ -173,36 +193,31 @@ export default class Lambder {
|
|
|
173
193
|
const resolver = this.getResolver(resolve, reject);
|
|
174
194
|
if (ctx.method === "OPTIONS")
|
|
175
195
|
return resolver.cors();
|
|
176
|
-
const firstMatchedAction =
|
|
177
|
-
for (const action of this._actionList) {
|
|
178
|
-
if (await action.conditionFn(ctx))
|
|
179
|
-
return action;
|
|
180
|
-
}
|
|
181
|
-
})();
|
|
196
|
+
const firstMatchedAction = this.actionList.find(action => action.conditionFn(ctx));
|
|
182
197
|
if (firstMatchedAction) {
|
|
183
|
-
for (const hook of this.
|
|
198
|
+
for (const hook of this.hookList["beforeRender"]) {
|
|
184
199
|
ctx = await hook.hookFn(ctx, resolver);
|
|
185
200
|
}
|
|
186
|
-
let
|
|
187
|
-
for (const hook of this.
|
|
188
|
-
|
|
201
|
+
let response = await firstMatchedAction.actionFn(ctx, resolver);
|
|
202
|
+
for (const hook of this.hookList["afterRender"]) {
|
|
203
|
+
response = await hook.hookFn(ctx, resolver, response);
|
|
189
204
|
}
|
|
190
|
-
resolve(
|
|
205
|
+
resolve(response);
|
|
191
206
|
}
|
|
192
207
|
else {
|
|
193
208
|
return this.handleNoMatchedAction(ctx, resolver);
|
|
194
209
|
}
|
|
195
|
-
}).then(async (
|
|
196
|
-
for (const hook of this.
|
|
197
|
-
|
|
210
|
+
}).then(async (response) => {
|
|
211
|
+
for (const hook of this.hookList["completed"]) {
|
|
212
|
+
response = await hook.hookFn(response);
|
|
198
213
|
}
|
|
199
|
-
return
|
|
214
|
+
return response;
|
|
200
215
|
});
|
|
201
216
|
}
|
|
202
217
|
catch (err) {
|
|
203
|
-
const responseBuilder = this.getResponseBuilder();
|
|
204
|
-
const wrappedError = err instanceof Error ? err : new Error("Error: " + String(err));
|
|
205
218
|
if (this.globalErrorHandler) {
|
|
219
|
+
const wrappedError = err instanceof Error ? err : new Error("Error: " + String(err));
|
|
220
|
+
const responseBuilder = this.getResponseBuilder();
|
|
206
221
|
return this.globalErrorHandler(wrappedError, eventRenderContext, responseBuilder);
|
|
207
222
|
}
|
|
208
223
|
return { statusCode: 500, body: "Internal Server Error.", };
|
package/dist/Resolver.d.ts
CHANGED
|
@@ -13,14 +13,14 @@ interface DieResolverMethods {
|
|
|
13
13
|
api: MethodType<ResponseBuilder, 'api'>;
|
|
14
14
|
}
|
|
15
15
|
export default class Resolver extends ResponseBuilder {
|
|
16
|
-
resolve: (
|
|
16
|
+
resolve: (response: ResolverResponse) => void;
|
|
17
17
|
reject: (err: Error) => void;
|
|
18
18
|
die: DieResolverMethods;
|
|
19
19
|
constructor({ isCorsEnabled, publicPath, apiVersion, resolve, reject }: {
|
|
20
20
|
isCorsEnabled: boolean;
|
|
21
21
|
publicPath: string;
|
|
22
22
|
apiVersion?: string | null;
|
|
23
|
-
resolve: (
|
|
23
|
+
resolve: (response: ResolverResponse) => void;
|
|
24
24
|
reject: (err: Error) => void;
|
|
25
25
|
});
|
|
26
26
|
private autoResolve;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lambder",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.44",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"@types/js-cookie": "^3.0.6",
|
|
25
25
|
"@types/mime-types": "^2.1.4",
|
|
26
26
|
"@types/node": "^20.11.27",
|
|
27
|
+
"@types/path-to-regexp": "^1.7.0",
|
|
27
28
|
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
|
28
29
|
"@typescript-eslint/parser": "^7.2.0",
|
|
29
30
|
"eslint": "^8.57.0",
|
package/src/Lambder.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import querystring from "querystring";
|
|
2
2
|
import cookieParser from "cookie";
|
|
3
|
+
import { match } from "path-to-regexp";
|
|
3
4
|
|
|
4
5
|
import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, Context } from "aws-lambda";
|
|
5
6
|
import Resolver from "./Resolver.js";
|
|
6
7
|
import ResponseBuilder, { ResolverResponse } from "./ResponseBuilder.js";
|
|
7
8
|
|
|
8
|
-
type
|
|
9
|
+
type Session = {
|
|
9
10
|
userId: string,
|
|
10
11
|
userIdHash: string;
|
|
11
12
|
sessionHash: string;
|
|
@@ -13,29 +14,30 @@ type DDBSession = {
|
|
|
13
14
|
createdTimeStamp: number;
|
|
14
15
|
expiresTimeStamp: number;
|
|
15
16
|
data: { [key:string]: any };
|
|
16
|
-
}
|
|
17
|
+
};
|
|
17
18
|
|
|
18
19
|
type RenderContext = {
|
|
19
20
|
host: string;
|
|
20
|
-
|
|
21
|
+
path: string;
|
|
22
|
+
pathParams: { [key: string]: any } | null;
|
|
21
23
|
method: string;
|
|
22
24
|
get: { [key: string]: any };
|
|
23
25
|
post: { [key: string]: any };
|
|
24
26
|
cookie: { [key: string]: any };
|
|
25
27
|
headers: APIGatewayProxyEventHeaders;
|
|
26
|
-
session:
|
|
28
|
+
session: Session|null;
|
|
27
29
|
lambdaContext: Context;
|
|
28
30
|
};
|
|
29
31
|
|
|
30
|
-
type ConditionFunction = (ctx: RenderContext) => boolean
|
|
32
|
+
type ConditionFunction = (ctx: RenderContext) => boolean;
|
|
31
33
|
type ActionFunction = (ctx: RenderContext, resolver: Resolver) => ResolverResponse|Promise<ResolverResponse>;
|
|
32
|
-
type
|
|
34
|
+
type ActionObject = { conditionFn: ConditionFunction, actionFn: ActionFunction };
|
|
33
35
|
|
|
34
36
|
type HookCreatedFunction = (lambderInstance: Lambder) => Promise<void>;
|
|
35
37
|
type HookBeforeRenderFunction = (ctx: RenderContext, resolver: Resolver) => RenderContext|Promise<RenderContext>;
|
|
36
|
-
type HookAfterRenderFunction = (ctx: RenderContext, resolver: Resolver,
|
|
38
|
+
type HookAfterRenderFunction = (ctx: RenderContext, resolver: Resolver, response: ResolverResponse) => ResolverResponse|Promise<ResolverResponse>;
|
|
37
39
|
type HookFallbackFunction = (ctx: RenderContext, resolver: Resolver) => ResolverResponse|Promise<ResolverResponse>;
|
|
38
|
-
type HookCompletedFunction = (
|
|
40
|
+
type HookCompletedFunction = (response: ResolverResponse) => ResolverResponse|Promise<ResolverResponse>;
|
|
39
41
|
|
|
40
42
|
type GlobalErrorHandlerFunction = (err: Error, ctx: RenderContext|null, response: ResponseBuilder) => ResolverResponse|Promise<ResolverResponse>;
|
|
41
43
|
type RouteFallbackHandlerFunction = (ctx:RenderContext, resolver: Resolver) => ResolverResponse;
|
|
@@ -52,7 +54,8 @@ export const createContext = (
|
|
|
52
54
|
lambdaContext: Context,
|
|
53
55
|
):RenderContext => {
|
|
54
56
|
const host = event.headers.Host || event.headers.host || "";
|
|
55
|
-
const
|
|
57
|
+
const path = event.path;
|
|
58
|
+
const pathParams = null;
|
|
56
59
|
const get: { [key:string]: any } = event.queryStringParameters || {};
|
|
57
60
|
const method = event.httpMethod;
|
|
58
61
|
const cookie = cookieParser.parse(event.headers.Cookie || event.headers.cookie || "");
|
|
@@ -65,7 +68,7 @@ export const createContext = (
|
|
|
65
68
|
catch(e){ post = querystring.parse(decodedBody) || {}; }
|
|
66
69
|
}catch(e){}
|
|
67
70
|
if(/^(((\d+\.)+\d+)|(([a-f\d]+:)+[a-f\d]+))$/.test(host)){ throw new Error("[404] Host cannot be an ip address: " + host); }
|
|
68
|
-
return { host,
|
|
71
|
+
return { host, path, pathParams, method, get, post, cookie, headers, session, lambdaContext };
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
|
|
@@ -75,8 +78,8 @@ export default class Lambder {
|
|
|
75
78
|
private isCorsEnabled: boolean;
|
|
76
79
|
private publicPath: string;
|
|
77
80
|
|
|
78
|
-
private
|
|
79
|
-
private
|
|
81
|
+
private actionList: ActionObject[];
|
|
82
|
+
private hookList: {
|
|
80
83
|
"beforeRender": { priority: number, hookFn: HookBeforeRenderFunction }[],
|
|
81
84
|
"afterRender": { priority: number, hookFn: HookAfterRenderFunction }[],
|
|
82
85
|
"fallback": { priority: number, hookFn: HookFallbackFunction }[],
|
|
@@ -95,8 +98,8 @@ export default class Lambder {
|
|
|
95
98
|
this.apiVersion = apiVersion ?? null;
|
|
96
99
|
this.isCorsEnabled = isCorsEnabled ?? false;
|
|
97
100
|
|
|
98
|
-
this.
|
|
99
|
-
this.
|
|
101
|
+
this.actionList = [];
|
|
102
|
+
this.hookList = {
|
|
100
103
|
"beforeRender": [],
|
|
101
104
|
"afterRender": [],
|
|
102
105
|
"fallback": [],
|
|
@@ -114,7 +117,7 @@ export default class Lambder {
|
|
|
114
117
|
this.globalErrorHandler = globalErrorHandler;
|
|
115
118
|
}
|
|
116
119
|
|
|
117
|
-
private async validateSession (session:
|
|
120
|
+
private async validateSession (session: Session|null): Promise<boolean>{
|
|
118
121
|
if(!session) return false;
|
|
119
122
|
if(!session.userId || !session.userIdHash || !session.sessionHash || !session.csrfToken) return false;
|
|
120
123
|
if(!session.createdTimeStamp || !session.expiresTimeStamp) return false;
|
|
@@ -124,19 +127,17 @@ export default class Lambder {
|
|
|
124
127
|
};
|
|
125
128
|
|
|
126
129
|
private async handleNoMatchedAction(ctx: RenderContext, resolver: Resolver){
|
|
127
|
-
for(const hook of this.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
resolver.resolve({ statusCode: 204, body: "API handler not set.", })
|
|
135
|
-
}
|
|
130
|
+
for(const hook of this.hookList["fallback"]){ await hook.hookFn(ctx, resolver); }
|
|
131
|
+
|
|
132
|
+
const isAPI = ctx.path === this.apiPath;
|
|
133
|
+
if(isAPI && this.apiFallbackHandler){
|
|
134
|
+
resolver.resolve(await this.apiFallbackHandler(ctx, resolver));
|
|
135
|
+
}else if(isAPI){
|
|
136
|
+
resolver.resolve({ statusCode: 204, body: "API handler not set.", })
|
|
136
137
|
}else if(this.routeFallbackHandler){
|
|
137
138
|
resolver.resolve(await this.routeFallbackHandler(ctx, resolver));
|
|
138
139
|
}else{
|
|
139
|
-
resolver.resolve({ statusCode: 204, body: "
|
|
140
|
+
resolver.resolve({ statusCode: 204, body: "Route handler not set.", })
|
|
140
141
|
}
|
|
141
142
|
}
|
|
142
143
|
|
|
@@ -144,54 +145,70 @@ export default class Lambder {
|
|
|
144
145
|
await moduleFn(this);
|
|
145
146
|
}
|
|
146
147
|
addRoute(condition: string|ConditionFunction|RegExp, actionFn: ActionFunction):void{
|
|
147
|
-
this.
|
|
148
|
-
conditionFn:
|
|
148
|
+
this.actionList.push({
|
|
149
|
+
conditionFn: (ctx:RenderContext) => (
|
|
149
150
|
ctx.method === "GET" &&
|
|
150
151
|
(
|
|
151
|
-
(typeof condition === "string" && ctx.
|
|
152
|
-
(typeof condition === "function" &&
|
|
153
|
-
(condition?.constructor == RegExp && condition.test(ctx.
|
|
152
|
+
(typeof condition === "string" && ctx.path === condition) ||
|
|
153
|
+
(typeof condition === "function" && condition(ctx)) ||
|
|
154
|
+
(condition?.constructor == RegExp && condition.test(ctx.path))
|
|
154
155
|
)
|
|
155
156
|
),
|
|
156
|
-
actionFn: async (ctx:RenderContext, resolver: Resolver) =>
|
|
157
|
+
actionFn: async (ctx:RenderContext, resolver: Resolver) => {
|
|
158
|
+
if(typeof condition === "string"){
|
|
159
|
+
ctx.pathParams = (match(condition))(ctx.path) || {};
|
|
160
|
+
}else if(condition?.constructor == RegExp){
|
|
161
|
+
ctx.pathParams = ctx.path.match(condition);
|
|
162
|
+
}
|
|
163
|
+
return await actionFn(ctx, resolver);
|
|
164
|
+
}
|
|
157
165
|
});
|
|
158
166
|
};
|
|
159
167
|
|
|
160
168
|
addSessionRoute(condition: string|ConditionFunction|RegExp, actionFn: ActionFunction):void{
|
|
161
|
-
this.
|
|
162
|
-
conditionFn:
|
|
163
|
-
ctx.method === "GET" &&
|
|
169
|
+
this.actionList.push({
|
|
170
|
+
conditionFn: (ctx:RenderContext) => (
|
|
171
|
+
ctx.method === "GET" &&
|
|
164
172
|
(
|
|
165
|
-
(typeof condition === "string" && ctx.
|
|
166
|
-
(typeof condition === "function" &&
|
|
167
|
-
(condition?.constructor == RegExp && condition.test(ctx.
|
|
173
|
+
(typeof condition === "string" && ctx.path === condition) ||
|
|
174
|
+
(typeof condition === "function" && condition(ctx)) ||
|
|
175
|
+
(condition?.constructor == RegExp && condition.test(ctx.path))
|
|
168
176
|
)
|
|
169
177
|
),
|
|
170
|
-
actionFn: async (ctx:RenderContext, resolver: Resolver) =>
|
|
178
|
+
actionFn: async (ctx:RenderContext, resolver: Resolver) => {
|
|
179
|
+
const isSessionValid = this.validateSession(ctx.session);
|
|
180
|
+
if(!isSessionValid) throw new Error("Session not found");
|
|
181
|
+
if(typeof condition === "string"){
|
|
182
|
+
ctx.pathParams = (match(condition))(ctx.path) || {};
|
|
183
|
+
}else if(condition?.constructor == RegExp){
|
|
184
|
+
ctx.pathParams = ctx.path.match(condition);
|
|
185
|
+
}
|
|
186
|
+
return await actionFn(ctx, resolver);
|
|
187
|
+
}
|
|
171
188
|
});
|
|
172
189
|
};
|
|
173
190
|
|
|
174
191
|
addApi(condition: string|ConditionFunction|RegExp, actionFn: ActionFunction):void{
|
|
175
|
-
this.
|
|
176
|
-
conditionFn:
|
|
177
|
-
ctx.method === "POST" && ctx.
|
|
192
|
+
this.actionList.push({
|
|
193
|
+
conditionFn: (ctx:RenderContext) => (
|
|
194
|
+
ctx.method === "POST" && ctx.path === this.apiPath &&
|
|
178
195
|
(
|
|
179
196
|
(typeof condition === "string" && ctx.post?.api === condition) ||
|
|
180
|
-
(typeof condition === "function" &&
|
|
197
|
+
(typeof condition === "function" && condition(ctx)) ||
|
|
181
198
|
(condition?.constructor == RegExp && condition.test(ctx.post?.api))
|
|
182
199
|
)
|
|
183
200
|
),
|
|
184
|
-
actionFn: async (ctx:RenderContext, resolver: Resolver) => await actionFn(ctx, resolver)
|
|
201
|
+
actionFn: async (ctx:RenderContext, resolver: Resolver) => await actionFn(ctx, resolver),
|
|
185
202
|
});
|
|
186
203
|
};
|
|
187
204
|
|
|
188
205
|
addSessionApi(condition: string|ConditionFunction|RegExp, actionFn: ActionFunction):void{
|
|
189
|
-
this.
|
|
190
|
-
conditionFn:
|
|
191
|
-
ctx.method === "POST" && ctx.
|
|
206
|
+
this.actionList.push({
|
|
207
|
+
conditionFn: (ctx:RenderContext) => (
|
|
208
|
+
ctx.method === "POST" && ctx.path === this.apiPath &&
|
|
192
209
|
(
|
|
193
210
|
(typeof condition === "string" && ctx.post?.api === condition) ||
|
|
194
|
-
(typeof condition === "function" &&
|
|
211
|
+
(typeof condition === "function" && condition(ctx)) ||
|
|
195
212
|
(condition?.constructor == RegExp && condition.test(ctx.post?.api))
|
|
196
213
|
)
|
|
197
214
|
),
|
|
@@ -217,8 +234,8 @@ export default class Lambder {
|
|
|
217
234
|
if(hookEvent === "created"){
|
|
218
235
|
await hookFn(this);
|
|
219
236
|
}else{
|
|
220
|
-
this.
|
|
221
|
-
this.
|
|
237
|
+
this.hookList[hookEvent].push({ priority, hookFn });
|
|
238
|
+
this.hookList[hookEvent].sort((a, b) => a.priority - b.priority);
|
|
222
239
|
}
|
|
223
240
|
}
|
|
224
241
|
|
|
@@ -230,7 +247,7 @@ export default class Lambder {
|
|
|
230
247
|
});
|
|
231
248
|
};
|
|
232
249
|
|
|
233
|
-
private getResolver(resolve: (
|
|
250
|
+
private getResolver(resolve: (response: ResolverResponse) => void, reject: (err: Error) => void){
|
|
234
251
|
return new Resolver({
|
|
235
252
|
isCorsEnabled: this.isCorsEnabled,
|
|
236
253
|
publicPath: this.publicPath,
|
|
@@ -250,40 +267,29 @@ export default class Lambder {
|
|
|
250
267
|
eventRenderContext = ctx;
|
|
251
268
|
|
|
252
269
|
return await new Promise(async (
|
|
253
|
-
resolve:(
|
|
270
|
+
resolve:(response: ResolverResponse)=>void,
|
|
254
271
|
reject:(err: Error)=>void
|
|
255
272
|
)=> {
|
|
256
273
|
const resolver = this.getResolver(resolve, reject);
|
|
257
274
|
if(ctx.method === "OPTIONS") return resolver.cors();
|
|
258
275
|
|
|
259
|
-
const firstMatchedAction =
|
|
260
|
-
for(const action of this._actionList){
|
|
261
|
-
if(await action.conditionFn(ctx)) return action;
|
|
262
|
-
}
|
|
263
|
-
})();
|
|
264
|
-
|
|
276
|
+
const firstMatchedAction = this.actionList.find(action => action.conditionFn(ctx));
|
|
265
277
|
if(firstMatchedAction){
|
|
266
|
-
for(const hook of this.
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
for(const hook of this._hookList["afterRender"]){
|
|
271
|
-
renderResult = await hook.hookFn(ctx, resolver, renderResult);
|
|
272
|
-
}
|
|
273
|
-
resolve(renderResult);
|
|
278
|
+
for(const hook of this.hookList["beforeRender"]){ ctx = await hook.hookFn(ctx, resolver); }
|
|
279
|
+
let response = await firstMatchedAction.actionFn(ctx, resolver);
|
|
280
|
+
for(const hook of this.hookList["afterRender"]){ response = await hook.hookFn(ctx, resolver, response); }
|
|
281
|
+
resolve(response);
|
|
274
282
|
}else{
|
|
275
283
|
return this.handleNoMatchedAction(ctx, resolver);
|
|
276
284
|
}
|
|
277
|
-
}).then(async (
|
|
278
|
-
for(const hook of this.
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
return renderResult;
|
|
285
|
+
}).then(async (response: ResolverResponse):Promise<ResolverResponse> => {
|
|
286
|
+
for(const hook of this.hookList["completed"]){ response = await hook.hookFn(response); }
|
|
287
|
+
return response;
|
|
282
288
|
});
|
|
283
289
|
}catch(err){
|
|
284
|
-
const responseBuilder = this.getResponseBuilder();
|
|
285
|
-
const wrappedError = err instanceof Error ? err : new Error("Error: " + String(err));
|
|
286
290
|
if(this.globalErrorHandler){
|
|
291
|
+
const wrappedError = err instanceof Error ? err : new Error("Error: " + String(err));
|
|
292
|
+
const responseBuilder = this.getResponseBuilder();
|
|
287
293
|
return this.globalErrorHandler(wrappedError, eventRenderContext, responseBuilder);
|
|
288
294
|
}
|
|
289
295
|
return { statusCode: 500, body: "Internal Server Error.", }
|
package/src/Resolver.ts
CHANGED
|
@@ -16,7 +16,7 @@ interface DieResolverMethods {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export default class Resolver extends ResponseBuilder {
|
|
19
|
-
public resolve: (
|
|
19
|
+
public resolve: (response: ResolverResponse) => void;
|
|
20
20
|
public reject: (err: Error) => void;
|
|
21
21
|
public die: DieResolverMethods;
|
|
22
22
|
|
|
@@ -26,7 +26,7 @@ export default class Resolver extends ResponseBuilder {
|
|
|
26
26
|
isCorsEnabled: boolean,
|
|
27
27
|
publicPath: string,
|
|
28
28
|
apiVersion?: string|null,
|
|
29
|
-
resolve: (
|
|
29
|
+
resolve: (response: ResolverResponse) => void,
|
|
30
30
|
reject: (err: Error) => void,
|
|
31
31
|
}
|
|
32
32
|
){
|