lambder 1.0.0 → 1.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.
- package/deploy.sh +11 -0
- package/dist/Resolver.d.ts +94 -0
- package/dist/Resolver.js +135 -0
- package/dist/index.d.ts +38 -16
- package/dist/index.js +49 -86
- package/package.json +1 -1
package/deploy.sh
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export type ResolverResponse = {
|
|
2
|
+
statusCode: number;
|
|
3
|
+
multiValueHeaders?: {
|
|
4
|
+
[key: string]: string[];
|
|
5
|
+
};
|
|
6
|
+
body: string | null;
|
|
7
|
+
isBase64Encoded?: boolean;
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
};
|
|
10
|
+
interface IResolverMethods {
|
|
11
|
+
raw(param: {
|
|
12
|
+
statusCode: number;
|
|
13
|
+
isBase64Encoded?: boolean;
|
|
14
|
+
body: null | string;
|
|
15
|
+
multiValueHeaders: {
|
|
16
|
+
[key: string]: string[];
|
|
17
|
+
};
|
|
18
|
+
}): any;
|
|
19
|
+
json(data: {
|
|
20
|
+
[key: string]: any;
|
|
21
|
+
}, headers?: {
|
|
22
|
+
[key: string]: string | string[];
|
|
23
|
+
}): any;
|
|
24
|
+
xml(data: string): any;
|
|
25
|
+
html(data: string, headers?: {
|
|
26
|
+
[key: string]: string | string[];
|
|
27
|
+
}): any;
|
|
28
|
+
status301(url: string, headers?: {
|
|
29
|
+
[key: string]: string | string[];
|
|
30
|
+
}): any;
|
|
31
|
+
status404(data: string, headers?: {
|
|
32
|
+
[key: string]: string | string[];
|
|
33
|
+
}): any;
|
|
34
|
+
cors(): any;
|
|
35
|
+
fileBase64(fileBase64: string, mimeType: string, headers?: {
|
|
36
|
+
[key: string]: string | string[];
|
|
37
|
+
}): any;
|
|
38
|
+
file(filePath: string, headers?: {
|
|
39
|
+
[key: string]: string | string[];
|
|
40
|
+
}): any;
|
|
41
|
+
api({ payload, error, ...other }: {
|
|
42
|
+
payload?: any;
|
|
43
|
+
error?: any;
|
|
44
|
+
[key: string]: any;
|
|
45
|
+
}, headers?: {
|
|
46
|
+
[key: string]: string | string[];
|
|
47
|
+
}): any;
|
|
48
|
+
}
|
|
49
|
+
export default class Resolver {
|
|
50
|
+
private isCorsEnabled;
|
|
51
|
+
private publicPath;
|
|
52
|
+
private apiVersion;
|
|
53
|
+
resolve: Function;
|
|
54
|
+
reject: Function;
|
|
55
|
+
die: Partial<IResolverMethods>;
|
|
56
|
+
constructor({ isCorsEnabled, publicPath, apiVersion, resolve, reject }: {
|
|
57
|
+
isCorsEnabled: boolean;
|
|
58
|
+
publicPath: string;
|
|
59
|
+
apiVersion?: string | null;
|
|
60
|
+
resolve: (renderResult: ResolverResponse) => void;
|
|
61
|
+
reject: (err: Error) => void;
|
|
62
|
+
});
|
|
63
|
+
private readFileSync;
|
|
64
|
+
raw(param: ResolverResponse): ResolverResponse;
|
|
65
|
+
json(data: {
|
|
66
|
+
[key: string]: any;
|
|
67
|
+
}, headers?: {
|
|
68
|
+
[key: string]: string | string[];
|
|
69
|
+
}): ResolverResponse;
|
|
70
|
+
xml(data: string): ResolverResponse;
|
|
71
|
+
html(data: string, headers?: {
|
|
72
|
+
[key: string]: string | string[];
|
|
73
|
+
}): ResolverResponse;
|
|
74
|
+
status301(url: string, headers?: {
|
|
75
|
+
[key: string]: string | string[];
|
|
76
|
+
}): ResolverResponse;
|
|
77
|
+
status404(data: string, headers?: {
|
|
78
|
+
[key: string]: string | string[];
|
|
79
|
+
}): ResolverResponse;
|
|
80
|
+
cors(): ResolverResponse;
|
|
81
|
+
fileBase64(fileBase64: string, mimeType: string, headers?: {
|
|
82
|
+
[key: string]: string | string[];
|
|
83
|
+
}): ResolverResponse;
|
|
84
|
+
file(filePath: string, headers?: {
|
|
85
|
+
[key: string]: string | string[];
|
|
86
|
+
}): ResolverResponse;
|
|
87
|
+
api({ payload, error, ...other }: {
|
|
88
|
+
payload?: any;
|
|
89
|
+
error?: any;
|
|
90
|
+
}, headers?: {
|
|
91
|
+
[key: string]: string | string[];
|
|
92
|
+
}): ResolverResponse;
|
|
93
|
+
}
|
|
94
|
+
export {};
|
package/dist/Resolver.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import * as pathLibrary from "path";
|
|
3
|
+
import mimeTypeResolver from "mime-types";
|
|
4
|
+
const convertToMultiHeader = (headers) => Object.fromEntries(Object.entries(headers || {}).map(([k, v]) => [k, Array.isArray(v) ? v : [v]]));
|
|
5
|
+
const CORS_HEADERS = convertToMultiHeader({
|
|
6
|
+
"Access-Control-Allow-Credentials": "true",
|
|
7
|
+
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
|
|
8
|
+
"Access-Control-Allow-Origin": "*",
|
|
9
|
+
"Access-Control-Allow-Methods": "OPTIONS,POST",
|
|
10
|
+
});
|
|
11
|
+
export default class Resolver {
|
|
12
|
+
isCorsEnabled;
|
|
13
|
+
publicPath;
|
|
14
|
+
apiVersion;
|
|
15
|
+
resolve;
|
|
16
|
+
reject;
|
|
17
|
+
die;
|
|
18
|
+
constructor({ isCorsEnabled, publicPath, apiVersion, resolve, reject }) {
|
|
19
|
+
this.isCorsEnabled = isCorsEnabled;
|
|
20
|
+
this.publicPath = publicPath;
|
|
21
|
+
this.apiVersion = apiVersion ?? null;
|
|
22
|
+
this.resolve = resolve;
|
|
23
|
+
this.reject = reject;
|
|
24
|
+
this.die = new Proxy(this, {
|
|
25
|
+
get: (target, prop) => {
|
|
26
|
+
const origProp = target[prop];
|
|
27
|
+
if (typeof origProp === 'function') {
|
|
28
|
+
return (...args) => {
|
|
29
|
+
this.resolve(origProp.apply(this, args));
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return origProp;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
readFileSync(filePath) {
|
|
37
|
+
const __dirname = pathLibrary.resolve(pathLibrary.dirname(""));
|
|
38
|
+
const absolutePath = pathLibrary.resolve(filePath);
|
|
39
|
+
const publicPath = pathLibrary.resolve(this.publicPath);
|
|
40
|
+
if (!absolutePath.includes(publicPath)) {
|
|
41
|
+
return "forbiddenpath";
|
|
42
|
+
}
|
|
43
|
+
return fs.readFileSync(filePath);
|
|
44
|
+
}
|
|
45
|
+
;
|
|
46
|
+
raw(param) {
|
|
47
|
+
return param;
|
|
48
|
+
}
|
|
49
|
+
;
|
|
50
|
+
json(data, headers) {
|
|
51
|
+
return this.raw({
|
|
52
|
+
statusCode: 200,
|
|
53
|
+
multiValueHeaders: {
|
|
54
|
+
"Content-Type": ["application/json; charset=utf-8"],
|
|
55
|
+
...(this.isCorsEnabled ? CORS_HEADERS : {}),
|
|
56
|
+
...convertToMultiHeader(headers)
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify(data),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
xml(data) {
|
|
62
|
+
return this.raw({
|
|
63
|
+
statusCode: 200,
|
|
64
|
+
isBase64Encoded: true,
|
|
65
|
+
multiValueHeaders: { "Content-Type": ["application/xml; charset=utf-8"] },
|
|
66
|
+
body: Buffer.from(data).toString("base64"),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
;
|
|
70
|
+
html(data, headers) {
|
|
71
|
+
return this.raw({
|
|
72
|
+
statusCode: 200,
|
|
73
|
+
isBase64Encoded: true,
|
|
74
|
+
multiValueHeaders: { "Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers) },
|
|
75
|
+
body: Buffer.from(data).toString("base64"),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
;
|
|
79
|
+
status301(url, headers) {
|
|
80
|
+
return this.raw({
|
|
81
|
+
statusCode: 301,
|
|
82
|
+
multiValueHeaders: { "Location": [url], ...convertToMultiHeader(headers) },
|
|
83
|
+
body: null
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
;
|
|
87
|
+
status404(data, headers) {
|
|
88
|
+
return this.raw({
|
|
89
|
+
statusCode: 404,
|
|
90
|
+
isBase64Encoded: true,
|
|
91
|
+
multiValueHeaders: { "Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers) },
|
|
92
|
+
body: Buffer.from(data).toString("base64"),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
;
|
|
96
|
+
cors() {
|
|
97
|
+
return this.raw({
|
|
98
|
+
statusCode: 200,
|
|
99
|
+
multiValueHeaders: this.isCorsEnabled ? CORS_HEADERS : {},
|
|
100
|
+
body: JSON.stringify(""),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
;
|
|
104
|
+
fileBase64(fileBase64, mimeType, headers) {
|
|
105
|
+
return this.raw({
|
|
106
|
+
statusCode: 200,
|
|
107
|
+
isBase64Encoded: true,
|
|
108
|
+
multiValueHeaders: { "Content-Type": [mimeType || "text/html"], ...convertToMultiHeader(headers) },
|
|
109
|
+
body: fileBase64,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
;
|
|
113
|
+
file(filePath, headers) {
|
|
114
|
+
if (!fs.existsSync(filePath))
|
|
115
|
+
return this.json({ error: "File not found: " + filePath });
|
|
116
|
+
const mimeType = mimeTypeResolver.lookup(filePath);
|
|
117
|
+
const body = this.readFileSync(filePath);
|
|
118
|
+
const bodyBase64 = Buffer.from(body).toString("base64");
|
|
119
|
+
console.log("bodyBase64.length", bodyBase64.length);
|
|
120
|
+
return this.fileBase64(bodyBase64, mimeType || "", headers);
|
|
121
|
+
}
|
|
122
|
+
;
|
|
123
|
+
api({ payload, error = null, ...other }, headers) {
|
|
124
|
+
if (error)
|
|
125
|
+
console.log("ERROR: ", error);
|
|
126
|
+
if (Object.keys(other).length)
|
|
127
|
+
console.log("ERROR: Unexpected key in result: " + Object.keys(other).join(", "));
|
|
128
|
+
return this.json({
|
|
129
|
+
apiVersion: this.apiVersion,
|
|
130
|
+
payload,
|
|
131
|
+
...(error ? { error } : {}),
|
|
132
|
+
}, headers);
|
|
133
|
+
}
|
|
134
|
+
;
|
|
135
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { APIGatewayProxyEvent, Context } from "aws-lambda";
|
|
2
|
-
|
|
1
|
+
import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, Context } from "aws-lambda";
|
|
2
|
+
import Resolver, { ResolverResponse } from "./Resolver";
|
|
3
|
+
type RawResolvedContext = {
|
|
3
4
|
host: string;
|
|
4
|
-
realHost: string;
|
|
5
5
|
url: string;
|
|
6
6
|
method: string;
|
|
7
7
|
get: {
|
|
@@ -10,27 +10,49 @@ export declare const resolveEvent: (event: APIGatewayProxyEvent, resolve: any, r
|
|
|
10
10
|
post: {
|
|
11
11
|
[key: string]: any;
|
|
12
12
|
};
|
|
13
|
-
cookie:
|
|
14
|
-
|
|
13
|
+
cookie: {
|
|
14
|
+
[key: string]: any;
|
|
15
|
+
};
|
|
16
|
+
headers: APIGatewayProxyEventHeaders;
|
|
17
|
+
};
|
|
18
|
+
type RenderContext = RawResolvedContext & {
|
|
19
|
+
runContext: Context;
|
|
20
|
+
resolver: Resolver;
|
|
15
21
|
};
|
|
16
|
-
|
|
17
|
-
type
|
|
22
|
+
type ConditionFunction = (renderContext: RenderContext) => boolean | Promise<boolean>;
|
|
23
|
+
type ActionFunction = (renderContext: RenderContext) => ResolverResponse | Promise<ResolverResponse>;
|
|
24
|
+
type HookCreatedFunction = (lambderInstance: Lambder) => Promise<void>;
|
|
25
|
+
type HookBeforeRenderFunction = (renderContext: RenderContext) => Promise<void>;
|
|
26
|
+
type HookAfterRenderFunction = (renderContext: RenderContext, renderResult: ResolverResponse) => ResolverResponse | Promise<ResolverResponse>;
|
|
27
|
+
type HookFallbackFunction = (renderContext: RenderContext) => ResolverResponse | Promise<ResolverResponse>;
|
|
28
|
+
type HookCompletedFunction = (renderResult: ResolverResponse) => ResolverResponse | Promise<ResolverResponse>;
|
|
29
|
+
export declare const resolveEvent: (event: APIGatewayProxyEvent, resolve: Function, reject: Function) => RawResolvedContext;
|
|
18
30
|
export default class Lambder {
|
|
19
|
-
apiPath
|
|
20
|
-
|
|
31
|
+
private apiPath;
|
|
32
|
+
private apiVersion;
|
|
33
|
+
private isCorsEnabled;
|
|
34
|
+
private publicPath;
|
|
21
35
|
private _actionList;
|
|
22
36
|
private _hookList;
|
|
23
|
-
|
|
37
|
+
private globalErrorHandler;
|
|
38
|
+
constructor({ apiPath, apiVersion, isCorsEnabled, publicPath }: {
|
|
24
39
|
apiPath?: string;
|
|
25
|
-
|
|
40
|
+
apiVersion?: string;
|
|
41
|
+
isCorsEnabled?: boolean;
|
|
42
|
+
publicPath: string;
|
|
26
43
|
});
|
|
27
44
|
private apiHandler;
|
|
28
45
|
private pathHandler;
|
|
29
46
|
private fallbackHandler;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
47
|
+
setGlobalErrorHandler(globalErrorHandler: (err: Error) => ResolverResponse): void;
|
|
48
|
+
addApi(condition: string | ConditionFunction | RegExp, actionFn: ActionFunction): void;
|
|
49
|
+
addPath(condition: string | ConditionFunction | RegExp, actionFn: ActionFunction): void;
|
|
50
|
+
addHook(hookEvent: 'created', hookFn: HookCreatedFunction, priority?: number): Promise<void>;
|
|
51
|
+
addHook(hookEvent: 'beforeRender', hookFn: HookBeforeRenderFunction, priority?: number): Promise<void>;
|
|
52
|
+
addHook(hookEvent: 'afterRender', hookFn: HookAfterRenderFunction, priority?: number): Promise<void>;
|
|
53
|
+
addHook(hookEvent: 'fallback', hookFn: HookFallbackFunction, priority?: number): Promise<void>;
|
|
54
|
+
addHook(hookEvent: 'completed', hookFn: HookCompletedFunction, priority?: number): Promise<void>;
|
|
55
|
+
addModule(moduleFn: Function): Promise<void>;
|
|
56
|
+
render(event: APIGatewayProxyEvent, runContext: Context): Promise<ResolverResponse>;
|
|
35
57
|
}
|
|
36
58
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,29 +1,14 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import * as pathLibrary from "path";
|
|
3
1
|
import querystring from "querystring";
|
|
4
2
|
import cookieParser from "cookie";
|
|
5
|
-
|
|
6
|
-
const __dirname = pathLibrary.resolve(pathLibrary.dirname(""));
|
|
7
|
-
const absolutePath = pathLibrary.resolve(filePath);
|
|
8
|
-
const publicPath = pathLibrary.resolve(`${__dirname}/www/`);
|
|
9
|
-
if (!absolutePath.includes(publicPath)) {
|
|
10
|
-
return "forbiddenpath";
|
|
11
|
-
}
|
|
12
|
-
return fs.readFileSync(filePath);
|
|
13
|
-
};
|
|
14
|
-
// export const millisecondCounter = (startTime = 0) => {
|
|
15
|
-
// const time = process.hrtime();
|
|
16
|
-
// return Number((time[0] + (time[1] / 1e9) - startTime).toFixed(3));
|
|
17
|
-
// };
|
|
3
|
+
import Resolver from "./Resolver";
|
|
18
4
|
export const resolveEvent = (event, resolve, reject) => {
|
|
19
|
-
const
|
|
20
|
-
const host = realHost.replace(/^(\S+)--([a-z]+)\.beta00\.com$/, "$1.$2.com").replace(".beta00.com", ".com");
|
|
5
|
+
const host = event.headers.Host || event.headers.host || "";
|
|
21
6
|
const url = event.path;
|
|
22
7
|
const get = event.queryStringParameters || {};
|
|
23
8
|
const method = event.httpMethod;
|
|
24
9
|
const cookie = cookieParser.parse(event.headers.Cookie || event.headers.cookie || "");
|
|
25
10
|
const headers = event.headers;
|
|
26
|
-
let post;
|
|
11
|
+
let post = {};
|
|
27
12
|
try {
|
|
28
13
|
const decodedBody = event.isBase64Encoded ? (event.body ? Buffer.from(event.body, "base64").toString() : "{}") : (event.body || "{}");
|
|
29
14
|
try {
|
|
@@ -33,64 +18,25 @@ export const resolveEvent = (event, resolve, reject) => {
|
|
|
33
18
|
post = querystring.parse(decodedBody) || {};
|
|
34
19
|
}
|
|
35
20
|
}
|
|
36
|
-
catch (e) {
|
|
37
|
-
post = {};
|
|
38
|
-
}
|
|
21
|
+
catch (e) { }
|
|
39
22
|
if (/^(((\d+\.)+\d+)|(([a-f\d]+:)+[a-f\d]+))$/.test(host)) {
|
|
40
|
-
reject("[404] Host cannot be an ip address: " + host);
|
|
23
|
+
reject(new Error("[404] Host cannot be an ip address: " + host));
|
|
41
24
|
}
|
|
42
|
-
return { host,
|
|
25
|
+
return { host, url, method, get, post, cookie, headers };
|
|
43
26
|
};
|
|
44
|
-
// const parseHeader = (
|
|
45
|
-
// headers: { [key:string]: string|string[] } | undefined
|
|
46
|
-
// ):{ [key:string]: string[] } =>
|
|
47
|
-
// Object.fromEntries(Object.entries(headers||{}).map(([k,v])=>[ k, Array.isArray(v) ? v : [v] ]));
|
|
48
|
-
// const CORS_HEADERS = parseHeader({
|
|
49
|
-
// "Access-Control-Allow-Credentials": "true",
|
|
50
|
-
// "Access-Control-Allow-Headers" : "Origin, X-Requested-With, Content-Type, Accept",
|
|
51
|
-
// "Access-Control-Allow-Origin": "*",
|
|
52
|
-
// "Access-Control-Allow-Methods": "OPTIONS,POST",
|
|
53
|
-
// });
|
|
54
|
-
// export const dieRaw = (param: { body: null|string, statusCode: number, multiValueHeaders: {[key:string]: string[] }, isBase64Encoded?: boolean }) => { console.log("HandlerEnd: " + millisecondCounter() + " Size: " + (param.body || {}).length ); return param; };
|
|
55
|
-
// export const dieJson = (data: { [key:string]: any }|string, headers?: { [key:string]: string|string[] }) => dieRaw({statusCode: 200,multiValueHeaders: { "Content-Type": ["application/json; charset=utf-8"], ...CORS_HEADERS, ...parseHeader(headers)},body: JSON.stringify(data),});
|
|
56
|
-
// export const dieXml = (data: string) => dieRaw({statusCode: 200,isBase64Encoded: true,multiValueHeaders: { "Content-Type": ["application/xml; charset=utf-8"]}, body: Buffer.from(data).toString("base64"),});
|
|
57
|
-
// export const dieHtml = (data: string, headers?: { [key:string]: string|string[] }) => dieRaw({statusCode: 200,isBase64Encoded: true,multiValueHeaders: {"Content-Type": ["text/html; charset=utf-8"], ...parseHeader(headers)},body: Buffer.from(data).toString("base64"),});
|
|
58
|
-
// export const die301 = (url: string, headers?: { [key:string]: string|string[] }) => dieRaw({statusCode: 301, multiValueHeaders: { "Location" : [url], ...parseHeader(headers) },body:null});
|
|
59
|
-
// export const die404 = (data: string,headers?: { [key:string]: string|string[] }) => dieRaw({statusCode: 404,isBase64Encoded: true,multiValueHeaders: {"Content-Type": ["text/html; charset=utf-8"], ...parseHeader(headers)},body: Buffer.from(data).toString("base64"),});
|
|
60
|
-
// export const dieCors = () => dieRaw({ statusCode: 200,multiValueHeaders: CORS_HEADERS,body: JSON.stringify(""),});
|
|
61
|
-
// export const dieFileBase64 = async (fileBase64: string, mimeType: string, headers?: { [key:string]: string|string[] }) => dieRaw({ statusCode: 200, isBase64Encoded: true, multiValueHeaders: { "Content-Type": [mimeType || "text/html"], ...parseHeader(headers) }, body: fileBase64, });
|
|
62
|
-
// export const dieFile = async (file: string, headers?: { [key:string]: string|string[] }) => {
|
|
63
|
-
// if(!fs.existsSync(file)) return dieJson("File not found: " + file );
|
|
64
|
-
// const mimeType = mimeTypeResolver.lookup(file);
|
|
65
|
-
// const body = readFileSync(file);
|
|
66
|
-
// const bodyBase64 = Buffer.from(body).toString("base64");
|
|
67
|
-
// console.log("bodyBase64.length",bodyBase64.length);
|
|
68
|
-
// return dieFileBase64(bodyBase64, mimeType || "", headers);
|
|
69
|
-
// };
|
|
70
|
-
// export const dieApi = ({
|
|
71
|
-
// payload, error=null,
|
|
72
|
-
// ...other
|
|
73
|
-
// }: {
|
|
74
|
-
// payload?: any;
|
|
75
|
-
// error?: any;
|
|
76
|
-
// }, headers?: { [key:string]: string|string[] })=>{
|
|
77
|
-
// if(error) console.log("ERROR: ", error);
|
|
78
|
-
// if(Object.keys(other).length) console.log("ERROR: Unexpected key in result: " + Object.keys(other).join(", "));
|
|
79
|
-
// return dieJson({
|
|
80
|
-
// version: lambdaConfig.version,
|
|
81
|
-
// payload,
|
|
82
|
-
// ...(error ? {error} : {}),
|
|
83
|
-
// },headers);
|
|
84
|
-
// }
|
|
85
|
-
const hookTypeList = ["beforeRender", "afterRender", "created", "completed", "fallback"];
|
|
86
27
|
export default class Lambder {
|
|
87
28
|
apiPath;
|
|
88
|
-
|
|
29
|
+
apiVersion;
|
|
30
|
+
isCorsEnabled;
|
|
31
|
+
publicPath;
|
|
89
32
|
_actionList;
|
|
90
33
|
_hookList;
|
|
91
|
-
|
|
34
|
+
globalErrorHandler = null;
|
|
35
|
+
constructor({ apiPath, apiVersion, isCorsEnabled, publicPath }) {
|
|
92
36
|
this.apiPath = apiPath ?? "/api";
|
|
93
|
-
this.
|
|
37
|
+
this.apiVersion = apiVersion ?? null;
|
|
38
|
+
this.isCorsEnabled = isCorsEnabled ?? false;
|
|
39
|
+
this.publicPath = publicPath || "/incorrect-path-not-found";
|
|
94
40
|
this._actionList = [];
|
|
95
41
|
this._hookList = {
|
|
96
42
|
"beforeRender": [],
|
|
@@ -99,16 +45,19 @@ export default class Lambder {
|
|
|
99
45
|
"completed": []
|
|
100
46
|
};
|
|
101
47
|
}
|
|
102
|
-
async apiHandler(
|
|
48
|
+
async apiHandler(renderContext, actionFn) {
|
|
103
49
|
// Do context stuff;
|
|
104
|
-
return await actionFn(
|
|
50
|
+
return await actionFn(renderContext);
|
|
105
51
|
}
|
|
106
|
-
async pathHandler(
|
|
52
|
+
async pathHandler(renderContext, actionFn) {
|
|
107
53
|
// Do context stuff;
|
|
108
|
-
return await actionFn(
|
|
54
|
+
return await actionFn(renderContext);
|
|
109
55
|
}
|
|
110
|
-
async fallbackHandler(
|
|
111
|
-
return
|
|
56
|
+
async fallbackHandler(renderContext) {
|
|
57
|
+
return renderContext.resolver.file("index.html");
|
|
58
|
+
}
|
|
59
|
+
setGlobalErrorHandler(globalErrorHandler) {
|
|
60
|
+
this.globalErrorHandler = globalErrorHandler;
|
|
112
61
|
}
|
|
113
62
|
addApi(condition, actionFn) {
|
|
114
63
|
let conditionFn = (renderContext) => false;
|
|
@@ -116,7 +65,7 @@ export default class Lambder {
|
|
|
116
65
|
conditionFn = (renderContext) => renderContext.url === this.apiPath && (renderContext.post?.api) === condition;
|
|
117
66
|
}
|
|
118
67
|
else if (typeof condition === "function") {
|
|
119
|
-
conditionFn = (renderContext) => renderContext.url === this.apiPath && condition(renderContext);
|
|
68
|
+
conditionFn = async (renderContext) => renderContext.url === this.apiPath && (await condition(renderContext));
|
|
120
69
|
}
|
|
121
70
|
else if (condition?.constructor == RegExp) {
|
|
122
71
|
conditionFn = (renderContext) => renderContext.url === this.apiPath && condition.test(renderContext.post?.api);
|
|
@@ -124,7 +73,7 @@ export default class Lambder {
|
|
|
124
73
|
else {
|
|
125
74
|
throw "Unsupported API Condition";
|
|
126
75
|
}
|
|
127
|
-
|
|
76
|
+
this._actionList.push({
|
|
128
77
|
conditionFn,
|
|
129
78
|
actionFn: async (renderContext) => await this.apiHandler(renderContext, actionFn)
|
|
130
79
|
});
|
|
@@ -135,7 +84,7 @@ export default class Lambder {
|
|
|
135
84
|
conditionFn = (renderContext) => renderContext.url === condition;
|
|
136
85
|
}
|
|
137
86
|
else if (typeof condition === "function") {
|
|
138
|
-
conditionFn = (renderContext) => condition(renderContext);
|
|
87
|
+
conditionFn = async (renderContext) => (await condition(renderContext));
|
|
139
88
|
}
|
|
140
89
|
else if (condition?.constructor == RegExp) {
|
|
141
90
|
conditionFn = (renderContext) => condition.test(renderContext.url);
|
|
@@ -143,18 +92,18 @@ export default class Lambder {
|
|
|
143
92
|
else {
|
|
144
93
|
throw "Unsupported Path Condition";
|
|
145
94
|
}
|
|
146
|
-
|
|
95
|
+
this._actionList.push({
|
|
147
96
|
conditionFn,
|
|
148
97
|
actionFn: async (renderContext) => await this.pathHandler(renderContext, actionFn)
|
|
149
98
|
});
|
|
150
99
|
}
|
|
151
|
-
async addHook(
|
|
152
|
-
if (
|
|
100
|
+
async addHook(hookEvent, hookFn, priority = 0) {
|
|
101
|
+
if (hookEvent === "created") {
|
|
153
102
|
await hookFn(this);
|
|
154
103
|
}
|
|
155
104
|
else {
|
|
156
|
-
this._hookList[
|
|
157
|
-
this._hookList[
|
|
105
|
+
this._hookList[hookEvent].push({ priority, hookFn });
|
|
106
|
+
this._hookList[hookEvent].sort((a, b) => a.priority - b.priority);
|
|
158
107
|
}
|
|
159
108
|
}
|
|
160
109
|
async addModule(moduleFn) {
|
|
@@ -162,10 +111,16 @@ export default class Lambder {
|
|
|
162
111
|
}
|
|
163
112
|
async render(event, runContext) {
|
|
164
113
|
return await new Promise(async (resolve, reject) => {
|
|
114
|
+
const resolver = new Resolver({
|
|
115
|
+
isCorsEnabled: this.isCorsEnabled,
|
|
116
|
+
publicPath: this.publicPath,
|
|
117
|
+
apiVersion: this.apiVersion,
|
|
118
|
+
resolve, reject,
|
|
119
|
+
});
|
|
165
120
|
const eventContext = resolveEvent(event, resolve, reject);
|
|
166
|
-
const renderContext = { ...eventContext, runContext,
|
|
121
|
+
const renderContext = { ...eventContext, runContext, resolver };
|
|
167
122
|
if (renderContext.method === "OPTIONS")
|
|
168
|
-
return
|
|
123
|
+
return resolver.cors();
|
|
169
124
|
let firstMatchedAction = null;
|
|
170
125
|
for (const action of this._actionList) {
|
|
171
126
|
const isConditionMet = await action.conditionFn(renderContext);
|
|
@@ -189,8 +144,16 @@ export default class Lambder {
|
|
|
189
144
|
const renderResult = await this.fallbackHandler(renderContext);
|
|
190
145
|
resolve(renderResult);
|
|
191
146
|
}
|
|
147
|
+
}).then(async (renderResult) => {
|
|
148
|
+
for (const hook of this._hookList["completed"]) {
|
|
149
|
+
renderResult = await hook.hookFn(renderResult);
|
|
150
|
+
}
|
|
151
|
+
return renderResult;
|
|
152
|
+
}).catch(async (err) => {
|
|
153
|
+
if (this.globalErrorHandler) {
|
|
154
|
+
return this.globalErrorHandler(err);
|
|
155
|
+
}
|
|
156
|
+
return { statusCode: 501, body: "Unknown Server Error", };
|
|
192
157
|
});
|
|
193
|
-
for (const hook of this._hookList["completed"])
|
|
194
|
-
await hook.hookFn(promiseResult);
|
|
195
158
|
}
|
|
196
159
|
}
|