lambder 1.0.31 → 1.0.36

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 CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, Context } from "aws-lambda";
2
- import Resolver, { ResolverResponse } from "./Resolver.js";
2
+ import Resolver from "./Resolver.js";
3
+ import ResolverLight, { ResolverResponse } from "./ResolverLight.js";
3
4
  type DDBSession = {
4
5
  userId: string;
5
6
  userIdHash: string;
@@ -35,7 +36,7 @@ type HookBeforeRenderFunction = (renderContext: RenderContext, resolver: Resolve
35
36
  type HookAfterRenderFunction = (renderContext: RenderContext, resolver: Resolver, renderResult: ResolverResponse) => ResolverResponse | Promise<ResolverResponse>;
36
37
  type HookFallbackFunction = (renderContext: RenderContext, resolver: Resolver) => ResolverResponse | Promise<ResolverResponse>;
37
38
  type HookCompletedFunction = (renderResult: ResolverResponse) => ResolverResponse | Promise<ResolverResponse>;
38
- type GlobalErrorHandlerFunction = (err: Error, renderContext: RenderContext | null) => ResolverResponse | Promise<ResolverResponse>;
39
+ type GlobalErrorHandlerFunction = (err: Error, renderContext: RenderContext | null, resolver: ResolverLight) => ResolverResponse | Promise<ResolverResponse>;
39
40
  type RouteFallbackHandlerFunction = (renderContext: RenderContext, resolver: Resolver) => ResolverResponse;
40
41
  type ApiFallbackHandlerFunction = (renderContext: RenderContext, resolver: Resolver) => ResolverResponse;
41
42
  export declare const createContext: (event: APIGatewayProxyEvent, lambdaContext: Context) => RenderContext;
package/dist/Lambder.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import querystring from "querystring";
2
2
  import cookieParser from "cookie";
3
3
  import Resolver from "./Resolver.js";
4
+ import ResolverLight from "./ResolverLight.js";
4
5
  export const createContext = (event, lambdaContext) => {
5
6
  const host = event.headers.Host || event.headers.host || "";
6
7
  const url = event.path;
@@ -148,6 +149,11 @@ export default class Lambder {
148
149
  }
149
150
  async render(event, lambdaContext) {
150
151
  let eventRenderContext = null;
152
+ const resolverLight = new ResolverLight({
153
+ isCorsEnabled: this.isCorsEnabled,
154
+ publicPath: this.publicPath,
155
+ apiVersion: this.apiVersion,
156
+ });
151
157
  try {
152
158
  let renderContext = createContext(event, lambdaContext);
153
159
  eventRenderContext = renderContext;
@@ -156,7 +162,7 @@ export default class Lambder {
156
162
  isCorsEnabled: this.isCorsEnabled,
157
163
  publicPath: this.publicPath,
158
164
  apiVersion: this.apiVersion,
159
- resolve, reject,
165
+ resolve, reject
160
166
  });
161
167
  if (renderContext.method === "OPTIONS")
162
168
  return resolver.cors();
@@ -189,7 +195,7 @@ export default class Lambder {
189
195
  catch (err) {
190
196
  const wrappedError = err instanceof Error ? err : new Error("Error: " + String(err));
191
197
  if (this.globalErrorHandler) {
192
- return this.globalErrorHandler(wrappedError, eventRenderContext);
198
+ return this.globalErrorHandler(wrappedError, eventRenderContext, resolverLight);
193
199
  }
194
200
  return { statusCode: 500, body: "Internal Server Error.", };
195
201
  }
@@ -1,12 +1,4 @@
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
- };
1
+ import ResolverLight, { ResolverResponse } from "./ResolverLight";
10
2
  interface IResolverMethods {
11
3
  raw(param: {
12
4
  statusCode: number;
@@ -15,29 +7,29 @@ interface IResolverMethods {
15
7
  multiValueHeaders: {
16
8
  [key: string]: string[];
17
9
  };
18
- }): any;
10
+ }): ResolverResponse;
19
11
  json(data: {
20
12
  [key: string]: any;
21
13
  }, headers?: {
22
14
  [key: string]: string | string[];
23
- }): any;
24
- xml(data: string): any;
15
+ }): ResolverResponse;
16
+ xml(data: string): ResolverResponse;
25
17
  html(data: string, headers?: {
26
18
  [key: string]: string | string[];
27
- }): any;
19
+ }): ResolverResponse;
28
20
  status301(url: string, headers?: {
29
21
  [key: string]: string | string[];
30
- }): any;
22
+ }): ResolverResponse;
31
23
  status404(data: string, headers?: {
32
24
  [key: string]: string | string[];
33
- }): any;
34
- cors(): any;
25
+ }): ResolverResponse;
26
+ cors(): ResolverResponse;
35
27
  fileBase64(fileBase64: string, mimeType: string, headers?: {
36
28
  [key: string]: string | string[];
37
- }): any;
29
+ }): ResolverResponse;
38
30
  file(filePath: string, headers?: {
39
31
  [key: string]: string | string[];
40
- }): any;
32
+ }): ResolverResponse;
41
33
  api({ payload, versionExpired, sessionExpired, notAuthorized, message, errorMessage, ...other }: {
42
34
  payload?: any;
43
35
  versionExpired?: boolean;
@@ -47,14 +39,11 @@ interface IResolverMethods {
47
39
  errorMessage?: any;
48
40
  }, headers?: {
49
41
  [key: string]: string | string[];
50
- }): any;
42
+ }): ResolverResponse;
51
43
  }
52
- export default class Resolver {
53
- private isCorsEnabled;
54
- private publicPath;
55
- private apiVersion;
56
- resolve: Function;
57
- reject: Function;
44
+ export default class Resolver extends ResolverLight {
45
+ resolve: (renderResult: ResolverResponse) => void;
46
+ reject: (err: Error) => void;
58
47
  die: IResolverMethods;
59
48
  constructor({ isCorsEnabled, publicPath, apiVersion, resolve, reject }: {
60
49
  isCorsEnabled: boolean;
@@ -63,40 +52,6 @@ export default class Resolver {
63
52
  resolve: (renderResult: ResolverResponse) => void;
64
53
  reject: (err: Error) => void;
65
54
  });
66
- private readFileSync;
67
- private checkFileExist;
68
- raw(param: ResolverResponse): ResolverResponse;
69
- json(data: {
70
- [key: string]: any;
71
- }, headers?: {
72
- [key: string]: string | string[];
73
- }): ResolverResponse;
74
- xml(data: string): ResolverResponse;
75
- html(data: string, headers?: {
76
- [key: string]: string | string[];
77
- }): ResolverResponse;
78
- status301(url: string, headers?: {
79
- [key: string]: string | string[];
80
- }): ResolverResponse;
81
- status404(data: string, headers?: {
82
- [key: string]: string | string[];
83
- }): ResolverResponse;
84
- cors(): ResolverResponse;
85
- fileBase64(fileBase64: string, mimeType: string, headers?: {
86
- [key: string]: string | string[];
87
- }): ResolverResponse;
88
- file(filePath: string, headers?: {
89
- [key: string]: string | string[];
90
- }, fallbackFilePath?: string): ResolverResponse;
91
- api({ payload, versionExpired, sessionExpired, notAuthorized, message, errorMessage, ...other }: {
92
- payload?: any;
93
- versionExpired?: boolean;
94
- sessionExpired?: boolean;
95
- notAuthorized?: boolean;
96
- message?: any;
97
- errorMessage?: any;
98
- }, headers?: {
99
- [key: string]: string | string[];
100
- }): ResolverResponse;
55
+ private autoResolve;
101
56
  }
102
57
  export {};
package/dist/Resolver.js CHANGED
@@ -1,153 +1,32 @@
1
- import fs from "fs";
2
- import * as path 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;
1
+ import ResolverLight from "./ResolverLight";
2
+ export default class Resolver extends ResolverLight {
15
3
  resolve;
16
4
  reject;
17
5
  die;
18
6
  constructor({ isCorsEnabled, publicPath, apiVersion, resolve, reject }) {
19
- this.isCorsEnabled = isCorsEnabled;
20
- this.publicPath = publicPath;
21
- this.apiVersion = apiVersion ?? null;
7
+ super({ isCorsEnabled, publicPath, apiVersion });
22
8
  this.resolve = resolve;
23
9
  this.reject = reject;
10
+ // Bind ResolverLight methods to `this` for `die` and automatically resolve
24
11
  this.die = {
25
- raw: this.raw,
26
- json: this.json,
27
- xml: this.xml,
28
- html: this.html,
29
- status301: this.status301,
30
- status404: this.status404,
31
- cors: this.cors,
32
- fileBase64: this.fileBase64,
33
- file: this.file,
34
- api: this.api,
12
+ raw: this.autoResolve(this.raw),
13
+ json: this.autoResolve(this.json),
14
+ xml: this.autoResolve(this.xml),
15
+ html: this.autoResolve(this.html),
16
+ status301: this.autoResolve(this.status301),
17
+ status404: this.autoResolve(this.status404),
18
+ cors: this.autoResolve(this.cors),
19
+ fileBase64: this.autoResolve(this.fileBase64),
20
+ file: this.autoResolve(this.file),
21
+ api: this.autoResolve(this.api),
35
22
  };
36
23
  }
37
- ;
38
- readFileSync(filePath) {
39
- const publicPath = path.resolve(this.publicPath);
40
- const absolutePath = path.join(publicPath, filePath);
41
- console.log("readFileSync", { filePath, publicPath, absolutePath });
42
- if (!absolutePath.includes(publicPath)) {
43
- return "forbiddenpath";
44
- }
45
- return fs.readFileSync(absolutePath);
46
- }
47
- ;
48
- checkFileExist(filePath) {
49
- const publicPath = path.resolve(this.publicPath);
50
- const absolutePath = path.join(this.publicPath, filePath);
51
- console.log("checkFileExist", { filePath, publicPath, absolutePath });
52
- if (!absolutePath.includes(publicPath)) {
53
- return false;
54
- }
55
- return fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile();
56
- }
57
- ;
58
- raw(param) {
59
- return param;
60
- }
61
- ;
62
- json(data, headers) {
63
- return this.raw({
64
- statusCode: 200,
65
- multiValueHeaders: {
66
- "Content-Type": ["application/json; charset=utf-8"],
67
- ...(this.isCorsEnabled ? CORS_HEADERS : {}),
68
- ...convertToMultiHeader(headers)
69
- },
70
- body: JSON.stringify(data),
71
- });
72
- }
73
- xml(data) {
74
- return this.raw({
75
- statusCode: 200,
76
- isBase64Encoded: true,
77
- multiValueHeaders: { "Content-Type": ["application/xml; charset=utf-8"] },
78
- body: Buffer.from(data).toString("base64"),
79
- });
80
- }
81
- ;
82
- html(data, headers) {
83
- return this.raw({
84
- statusCode: 200,
85
- isBase64Encoded: true,
86
- multiValueHeaders: { "Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers) },
87
- body: Buffer.from(data).toString("base64"),
88
- });
89
- }
90
- ;
91
- status301(url, headers) {
92
- return this.raw({
93
- statusCode: 301,
94
- multiValueHeaders: { "Location": [url], ...convertToMultiHeader(headers) },
95
- body: null
96
- });
97
- }
98
- ;
99
- status404(data, headers) {
100
- return this.raw({
101
- statusCode: 404,
102
- isBase64Encoded: true,
103
- multiValueHeaders: { "Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers) },
104
- body: Buffer.from(data).toString("base64"),
105
- });
106
- }
107
- ;
108
- cors() {
109
- return this.raw({
110
- statusCode: 200,
111
- multiValueHeaders: this.isCorsEnabled ? CORS_HEADERS : {},
112
- body: JSON.stringify(""),
113
- });
114
- }
115
- ;
116
- fileBase64(fileBase64, mimeType, headers) {
117
- return this.raw({
118
- statusCode: 200,
119
- isBase64Encoded: true,
120
- multiValueHeaders: { "Content-Type": [mimeType || "text/html"], ...convertToMultiHeader(headers) },
121
- body: fileBase64,
122
- });
123
- }
124
- ;
125
- file(filePath, headers, fallbackFilePath) {
126
- if (!this.checkFileExist(filePath)) {
127
- if (fallbackFilePath && this.checkFileExist(fallbackFilePath)) {
128
- return this.file(fallbackFilePath, headers);
129
- }
130
- return this.json({ error: "File not found: " + filePath });
131
- }
132
- const mimeType = mimeTypeResolver.lookup(filePath);
133
- const body = this.readFileSync(filePath);
134
- const bodyBase64 = Buffer.from(body).toString("base64");
135
- console.log("bodyBase64.length", bodyBase64.length);
136
- return this.fileBase64(bodyBase64, mimeType || "", headers);
137
- }
138
- ;
139
- api({ payload, versionExpired, sessionExpired, notAuthorized, message = null, errorMessage = null, ...other }, headers) {
140
- if (Object.keys(other).length)
141
- console.log("ERROR: Unexpected key in result: " + Object.keys(other).join(", "));
142
- return this.json({
143
- apiVersion: this.apiVersion,
144
- payload,
145
- ...(versionExpired ? { versionExpired } : {}),
146
- ...(sessionExpired ? { sessionExpired } : {}),
147
- ...(notAuthorized ? { notAuthorized } : {}),
148
- ...(message ? { message } : {}),
149
- ...(errorMessage ? { errorMessage } : {}),
150
- }, headers);
24
+ autoResolve(method) {
25
+ return (...args) => {
26
+ const result = method.apply(this, args);
27
+ this.resolve(result);
28
+ return result;
29
+ };
151
30
  }
152
- ;
153
31
  }
32
+ ;
@@ -0,0 +1,54 @@
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
+ export default class ResolverLight {
11
+ private isCorsEnabled;
12
+ private publicPath;
13
+ private apiVersion;
14
+ constructor({ isCorsEnabled, publicPath, apiVersion }: {
15
+ isCorsEnabled: boolean;
16
+ publicPath: string;
17
+ apiVersion?: string | null;
18
+ });
19
+ private readFileSync;
20
+ private checkFileExist;
21
+ raw(param: ResolverResponse): ResolverResponse;
22
+ json(data: {
23
+ [key: string]: any;
24
+ }, headers?: {
25
+ [key: string]: string | string[];
26
+ }): ResolverResponse;
27
+ xml(data: string): ResolverResponse;
28
+ html(data: string, headers?: {
29
+ [key: string]: string | string[];
30
+ }): ResolverResponse;
31
+ status301(url: string, headers?: {
32
+ [key: string]: string | string[];
33
+ }): ResolverResponse;
34
+ status404(data: string, headers?: {
35
+ [key: string]: string | string[];
36
+ }): ResolverResponse;
37
+ cors(): ResolverResponse;
38
+ fileBase64(fileBase64: string, mimeType: string, headers?: {
39
+ [key: string]: string | string[];
40
+ }): ResolverResponse;
41
+ file(filePath: string, headers?: {
42
+ [key: string]: string | string[];
43
+ }, fallbackFilePath?: string): ResolverResponse;
44
+ api({ payload, versionExpired, sessionExpired, notAuthorized, message, errorMessage, ...other }: {
45
+ payload?: any;
46
+ versionExpired?: boolean;
47
+ sessionExpired?: boolean;
48
+ notAuthorized?: boolean;
49
+ message?: any;
50
+ errorMessage?: any;
51
+ }, headers?: {
52
+ [key: string]: string | string[];
53
+ }): ResolverResponse;
54
+ }
@@ -0,0 +1,137 @@
1
+ import fs from "fs";
2
+ import * as path 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 ResolverLight {
12
+ isCorsEnabled;
13
+ publicPath;
14
+ apiVersion;
15
+ constructor({ isCorsEnabled, publicPath, apiVersion }) {
16
+ this.isCorsEnabled = isCorsEnabled;
17
+ this.publicPath = publicPath;
18
+ this.apiVersion = apiVersion ?? null;
19
+ }
20
+ ;
21
+ readFileSync(filePath) {
22
+ const publicPath = path.resolve(this.publicPath);
23
+ const absolutePath = path.join(publicPath, filePath);
24
+ console.log("readFileSync", { filePath, publicPath, absolutePath });
25
+ if (!absolutePath.includes(publicPath)) {
26
+ return "forbiddenpath";
27
+ }
28
+ return fs.readFileSync(absolutePath);
29
+ }
30
+ ;
31
+ checkFileExist(filePath) {
32
+ const publicPath = path.resolve(this.publicPath);
33
+ const absolutePath = path.join(this.publicPath, filePath);
34
+ console.log("checkFileExist", { filePath, publicPath, absolutePath });
35
+ if (!absolutePath.includes(publicPath)) {
36
+ return false;
37
+ }
38
+ return fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile();
39
+ }
40
+ ;
41
+ raw(param) {
42
+ return param;
43
+ }
44
+ ;
45
+ json(data, headers) {
46
+ return this.raw({
47
+ statusCode: 200,
48
+ multiValueHeaders: {
49
+ "Content-Type": ["application/json; charset=utf-8"],
50
+ ...(this.isCorsEnabled ? CORS_HEADERS : {}),
51
+ ...convertToMultiHeader(headers)
52
+ },
53
+ body: JSON.stringify(data),
54
+ });
55
+ }
56
+ xml(data) {
57
+ return this.raw({
58
+ statusCode: 200,
59
+ isBase64Encoded: true,
60
+ multiValueHeaders: { "Content-Type": ["application/xml; charset=utf-8"] },
61
+ body: Buffer.from(data).toString("base64"),
62
+ });
63
+ }
64
+ ;
65
+ html(data, headers) {
66
+ return this.raw({
67
+ statusCode: 200,
68
+ isBase64Encoded: true,
69
+ multiValueHeaders: { "Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers) },
70
+ body: Buffer.from(data).toString("base64"),
71
+ });
72
+ }
73
+ ;
74
+ status301(url, headers) {
75
+ return this.raw({
76
+ statusCode: 301,
77
+ multiValueHeaders: { "Location": [url], ...convertToMultiHeader(headers) },
78
+ body: null
79
+ });
80
+ }
81
+ ;
82
+ status404(data, headers) {
83
+ return this.raw({
84
+ statusCode: 404,
85
+ isBase64Encoded: true,
86
+ multiValueHeaders: { "Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers) },
87
+ body: Buffer.from(data).toString("base64"),
88
+ });
89
+ }
90
+ ;
91
+ cors() {
92
+ return this.raw({
93
+ statusCode: 200,
94
+ multiValueHeaders: this.isCorsEnabled ? CORS_HEADERS : {},
95
+ body: JSON.stringify(""),
96
+ });
97
+ }
98
+ ;
99
+ fileBase64(fileBase64, mimeType, headers) {
100
+ return this.raw({
101
+ statusCode: 200,
102
+ isBase64Encoded: true,
103
+ multiValueHeaders: { "Content-Type": [mimeType || "text/html"], ...convertToMultiHeader(headers) },
104
+ body: fileBase64,
105
+ });
106
+ }
107
+ ;
108
+ file(filePath, headers, fallbackFilePath) {
109
+ if (!this.checkFileExist(filePath)) {
110
+ if (fallbackFilePath && this.checkFileExist(fallbackFilePath)) {
111
+ return this.file(fallbackFilePath, headers);
112
+ }
113
+ return this.json({ error: "File not found: " + filePath });
114
+ }
115
+ const mimeType = mimeTypeResolver.lookup(filePath);
116
+ const body = this.readFileSync(filePath);
117
+ const bodyBase64 = Buffer.from(body).toString("base64");
118
+ console.log("bodyBase64.length", bodyBase64.length);
119
+ return this.fileBase64(bodyBase64, mimeType || "", headers);
120
+ }
121
+ ;
122
+ api({ payload, versionExpired, sessionExpired, notAuthorized, message = null, errorMessage = null, ...other }, headers) {
123
+ if (Object.keys(other).length)
124
+ console.log("ERROR: Unexpected key in result: " + Object.keys(other).join(", "));
125
+ return this.json({
126
+ apiVersion: this.apiVersion,
127
+ payload,
128
+ ...(versionExpired ? { versionExpired } : {}),
129
+ ...(sessionExpired ? { sessionExpired } : {}),
130
+ ...(notAuthorized ? { notAuthorized } : {}),
131
+ ...(message ? { message } : {}),
132
+ ...(errorMessage ? { errorMessage } : {}),
133
+ }, headers);
134
+ }
135
+ ;
136
+ }
137
+ ;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  import Lambder from './Lambder.js';
2
2
  export default Lambder;
3
3
  export { default as LambderCaller } from "./LambderCaller.js";
4
+ export { default as ResolverLight } from "./ResolverLight.js";
5
+ export { default as Resolver } from "./Resolver.js";
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
1
  import Lambder from './Lambder.js';
2
2
  export default Lambder;
3
3
  export { default as LambderCaller } from "./LambderCaller.js";
4
+ export { default as ResolverLight } from "./ResolverLight.js";
5
+ export { default as Resolver } from "./Resolver.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambder",
3
- "version": "1.0.31",
3
+ "version": "1.0.36",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/Lambder.ts CHANGED
@@ -2,7 +2,8 @@ import querystring from "querystring";
2
2
  import cookieParser from "cookie";
3
3
 
4
4
  import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, Context } from "aws-lambda";
5
- import Resolver, { ResolverResponse } from "./Resolver.js";
5
+ import Resolver from "./Resolver.js";
6
+ import ResolverLight, { ResolverResponse } from "./ResolverLight.js";
6
7
 
7
8
  type DDBSession = {
8
9
  userId: string,
@@ -36,7 +37,7 @@ type HookAfterRenderFunction = (renderContext: RenderContext, resolver: Resolver
36
37
  type HookFallbackFunction = (renderContext: RenderContext, resolver: Resolver) => ResolverResponse|Promise<ResolverResponse>;
37
38
  type HookCompletedFunction = (renderResult: ResolverResponse) => ResolverResponse|Promise<ResolverResponse>;
38
39
 
39
- type GlobalErrorHandlerFunction = (err: Error, renderContext: RenderContext|null) => ResolverResponse|Promise<ResolverResponse>;
40
+ type GlobalErrorHandlerFunction = (err: Error, renderContext: RenderContext|null, resolver: ResolverLight) => ResolverResponse|Promise<ResolverResponse>;
40
41
  type RouteFallbackHandlerFunction = (renderContext:RenderContext, resolver: Resolver) => ResolverResponse;
41
42
  type ApiFallbackHandlerFunction = (renderContext:RenderContext, resolver: Resolver) => ResolverResponse;
42
43
 
@@ -227,6 +228,11 @@ export default class Lambder {
227
228
  lambdaContext: Context
228
229
  ): Promise<ResolverResponse>{
229
230
  let eventRenderContext:RenderContext|null = null;
231
+ const resolverLight = new ResolverLight({
232
+ isCorsEnabled: this.isCorsEnabled,
233
+ publicPath: this.publicPath,
234
+ apiVersion: this.apiVersion,
235
+ })
230
236
 
231
237
  try {
232
238
  let renderContext = createContext(event, lambdaContext);
@@ -236,11 +242,11 @@ export default class Lambder {
236
242
  resolve:(renderResult: ResolverResponse)=>void,
237
243
  reject:(err: Error)=>void
238
244
  )=> {
239
- const resolver = new Resolver({
245
+ const resolver = new Resolver({
240
246
  isCorsEnabled: this.isCorsEnabled,
241
247
  publicPath: this.publicPath,
242
248
  apiVersion: this.apiVersion,
243
- resolve, reject,
249
+ resolve, reject
244
250
  });
245
251
  if(renderContext.method === "OPTIONS") return resolver.cors();
246
252
 
@@ -271,7 +277,7 @@ export default class Lambder {
271
277
  }catch(err){
272
278
  const wrappedError = err instanceof Error ? err : new Error("Error: " + String(err));
273
279
  if(this.globalErrorHandler){
274
- return this.globalErrorHandler(wrappedError, eventRenderContext);
280
+ return this.globalErrorHandler(wrappedError, eventRenderContext, resolverLight);
275
281
  }
276
282
  return { statusCode: 500, body: "Internal Server Error.", }
277
283
 
package/src/Resolver.ts CHANGED
@@ -1,27 +1,4 @@
1
- import fs from "fs";
2
- import * as path from "path";
3
- import mimeTypeResolver from "mime-types";
4
-
5
- const convertToMultiHeader = (
6
- headers: { [key:string]: string|string[] } | undefined
7
- ):{ [key:string]: string[] } =>
8
- Object.fromEntries(Object.entries(headers||{}).map(([k,v])=>[ k, Array.isArray(v) ? v : [v] ]));
9
-
10
- const CORS_HEADERS = convertToMultiHeader({
11
- "Access-Control-Allow-Credentials": "true",
12
- "Access-Control-Allow-Headers" : "Origin, X-Requested-With, Content-Type, Accept",
13
- "Access-Control-Allow-Origin": "*",
14
- "Access-Control-Allow-Methods": "OPTIONS,POST",
15
- });
16
-
17
-
18
- export type ResolverResponse = {
19
- statusCode: number,
20
- multiValueHeaders?: { [key:string]: string[] },
21
- body: string | null,
22
- isBase64Encoded?: boolean,
23
- [key:string]: any,
24
- };
1
+ import ResolverLight, { ResolverResponse } from "./ResolverLight";
25
2
 
26
3
 
27
4
  interface IResolverMethods {
@@ -30,15 +7,15 @@ interface IResolverMethods {
30
7
  isBase64Encoded?: boolean,
31
8
  body: null | string,
32
9
  multiValueHeaders: { [key: string]: string[] },
33
- }): any;
34
- json(data: { [key: string]: any }, headers?: { [key: string]: string | string[] }): any;
35
- xml(data: string): any;
36
- html(data: string, headers?: { [key: string]: string | string[] }): any;
37
- status301(url: string, headers?: { [key: string]: string | string[] }): any;
38
- status404(data: string, headers?: { [key: string]: string | string[] }): any;
39
- cors(): any;
40
- fileBase64(fileBase64: string, mimeType: string, headers?: { [key: string]: string | string[] }): any;
41
- file(filePath: string, headers?: { [key: string]: string | string[] }): any;
10
+ }): ResolverResponse;
11
+ json(data: { [key: string]: any }, headers?: { [key: string]: string | string[] }): ResolverResponse;
12
+ xml(data: string): ResolverResponse;
13
+ html(data: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
14
+ status301(url: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
15
+ status404(data: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
16
+ cors(): ResolverResponse;
17
+ fileBase64(fileBase64: string, mimeType: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
18
+ file(filePath: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
42
19
  api({
43
20
  payload,
44
21
  versionExpired, sessionExpired, notAuthorized,
@@ -53,17 +30,14 @@ interface IResolverMethods {
53
30
  errorMessage?: any;
54
31
  },
55
32
  headers?: { [key:string]: string|string[] }
56
- ): any;
33
+ ): ResolverResponse;
57
34
  }
58
35
 
59
36
 
60
- export default class Resolver {
61
- private isCorsEnabled: boolean;
62
- private publicPath: string;
63
- private apiVersion: string|null;
64
- resolve: Function;
65
- reject: Function;
66
- die: IResolverMethods;
37
+ export default class Resolver extends ResolverLight {
38
+ public resolve: (renderResult: ResolverResponse) => void;
39
+ public reject: (err: Error) => void;
40
+ public die: IResolverMethods;
67
41
 
68
42
  constructor(
69
43
  { isCorsEnabled, publicPath, apiVersion, resolve, reject }:
@@ -71,173 +45,37 @@ export default class Resolver {
71
45
  isCorsEnabled: boolean,
72
46
  publicPath: string,
73
47
  apiVersion?: string|null,
74
- resolve:(renderResult: ResolverResponse)=>void,
75
- reject:(err: Error)=>void
48
+ resolve: (renderResult: ResolverResponse) => void,
49
+ reject: (err: Error) => void,
76
50
  }
77
51
  ){
78
- this.isCorsEnabled = isCorsEnabled;
79
- this.publicPath = publicPath;
80
- this.apiVersion = apiVersion ?? null;
52
+ super({ isCorsEnabled, publicPath, apiVersion });
81
53
  this.resolve = resolve;
82
54
  this.reject = reject;
83
- this.die = {
84
- raw: this.raw,
85
- json: this.json,
86
- xml: this.xml,
87
- html: this.html,
88
- status301: this.status301,
89
- status404: this.status404,
90
- cors: this.cors,
91
- fileBase64: this.fileBase64,
92
- file: this.file,
93
- api: this.api,
94
- }
95
- };
96
-
97
- private readFileSync(filePath: string){
98
- const publicPath = path.resolve(this.publicPath);
99
- const absolutePath = path.join(publicPath, filePath);
100
- console.log("readFileSync", { filePath, publicPath, absolutePath });
101
- if(!absolutePath.includes(publicPath)){ return "forbiddenpath"; }
102
- return fs.readFileSync(absolutePath);
103
- };
104
-
105
- private checkFileExist(filePath: string){
106
- const publicPath = path.resolve(this.publicPath);
107
- const absolutePath = path.join(this.publicPath, filePath);
108
- console.log("checkFileExist", { filePath, publicPath, absolutePath });
109
- if(!absolutePath.includes(publicPath)){ return false; }
110
- return fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile();
111
- };
112
55
 
113
- raw(param: ResolverResponse){
114
- return param;
115
- };
116
-
117
- json(
118
- data: { [key:string]: any },
119
- headers?: { [key:string]: string|string[] }
120
- ):ResolverResponse{
121
- return this.raw({
122
- statusCode: 200,
123
- multiValueHeaders: {
124
- "Content-Type": ["application/json; charset=utf-8"],
125
- ...(this.isCorsEnabled ? CORS_HEADERS : {}),
126
- ...convertToMultiHeader(headers)
127
- },
128
- body: JSON.stringify(data),
129
- });
56
+ // Bind ResolverLight methods to `this` for `die` and automatically resolve
57
+ this.die = {
58
+ raw: this.autoResolve(this.raw),
59
+ json: this.autoResolve(this.json),
60
+ xml: this.autoResolve(this.xml),
61
+ html: this.autoResolve(this.html),
62
+ status301: this.autoResolve(this.status301),
63
+ status404: this.autoResolve(this.status404),
64
+ cors: this.autoResolve(this.cors),
65
+ fileBase64: this.autoResolve(this.fileBase64),
66
+ file: this.autoResolve(this.file),
67
+ api: this.autoResolve(this.api),
68
+ };
130
69
  }
131
70
 
132
- xml(data: string):ResolverResponse{
133
- return this.raw({
134
- statusCode: 200,
135
- isBase64Encoded: true,
136
- multiValueHeaders: { "Content-Type": ["application/xml; charset=utf-8"]},
137
- body: Buffer.from(data).toString("base64"),
138
- });
139
- };
140
-
141
- html(
142
- data: string,
143
- headers?: { [key:string]: string|string[] },
144
- ):ResolverResponse{
145
- return this.raw({
146
- statusCode: 200,
147
- isBase64Encoded: true,
148
- multiValueHeaders: {"Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers)},
149
- body: Buffer.from(data).toString("base64"),
150
- });
151
- };
152
-
153
- status301(
154
- url: string,
155
- headers?: { [key:string]: string|string[] },
156
- ):ResolverResponse{
157
- return this.raw({
158
- statusCode: 301,
159
- multiValueHeaders: { "Location" : [url], ...convertToMultiHeader(headers) },
160
- body:null
161
- });
162
- };
163
-
164
- status404(
165
- data: string,
166
- headers?: { [key:string]: string|string[] },
167
- ):ResolverResponse{
168
- return this.raw({
169
- statusCode: 404,
170
- isBase64Encoded: true,
171
- multiValueHeaders: {"Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers)},
172
- body: Buffer.from(data).toString("base64"),
173
- });
174
- };
175
-
176
- cors():ResolverResponse{
177
- return this.raw({
178
- statusCode: 200,
179
- multiValueHeaders: this.isCorsEnabled ? CORS_HEADERS: {},
180
- body: JSON.stringify(""),
181
- });
182
- };
183
-
184
- fileBase64 (
185
- fileBase64: string,
186
- mimeType: string,
187
- headers?: { [key:string]: string|string[] },
188
- ):ResolverResponse{
189
- return this.raw({
190
- statusCode: 200,
191
- isBase64Encoded: true,
192
- multiValueHeaders: { "Content-Type": [mimeType || "text/html"], ...convertToMultiHeader(headers) },
193
- body: fileBase64,
194
- });
195
- };
196
-
197
- file(
198
- filePath: string,
199
- headers?: { [key:string]: string|string[] },
200
- fallbackFilePath?: string,
201
- ):ResolverResponse{
202
- if(!this.checkFileExist(filePath)){
203
- if(fallbackFilePath && this.checkFileExist(fallbackFilePath)){
204
- return this.file(fallbackFilePath, headers);
205
- }
206
- return this.json({ error: "File not found: " + filePath })
207
- }
208
- const mimeType = mimeTypeResolver.lookup(filePath);
209
- const body = this.readFileSync(filePath);
210
- const bodyBase64 = Buffer.from(body).toString("base64");
211
- console.log("bodyBase64.length",bodyBase64.length);
212
- return this.fileBase64(bodyBase64, mimeType || "", headers);
213
- };
71
+ private autoResolve<
72
+ T extends (...args: any[]) => ResolverResponse
73
+ >(method: T): (...funcArgs: Parameters<T>) => ResolverResponse {
74
+ return (...args: Parameters<T>): ResolverResponse => {
75
+ const result: ResolverResponse = method.apply(this, args);
76
+ this.resolve(result);
77
+ return result;
78
+ };
79
+ }
214
80
 
215
- api(
216
- {
217
- payload,
218
- versionExpired, sessionExpired, notAuthorized,
219
- message = null, errorMessage = null,
220
- ...other
221
- }: {
222
- payload?: any;
223
- versionExpired?: boolean;
224
- sessionExpired?: boolean;
225
- notAuthorized?: boolean;
226
- message?: any;
227
- errorMessage?: any;
228
- },
229
- headers?: { [key:string]: string|string[] },
230
- ): ResolverResponse {
231
- if(Object.keys(other).length) console.log("ERROR: Unexpected key in result: " + Object.keys(other).join(", "));
232
- return this.json({
233
- apiVersion: this.apiVersion,
234
- payload,
235
- ...(versionExpired ? {versionExpired} : {}),
236
- ...(sessionExpired ? {sessionExpired} : {}),
237
- ...(notAuthorized ? {notAuthorized} : {}),
238
- ...(message ? {message} : {}),
239
- ...(errorMessage ? {errorMessage} : {}),
240
- }, headers);
241
- };
242
-
243
- }
81
+ };
@@ -0,0 +1,224 @@
1
+ import fs from "fs";
2
+ import * as path from "path";
3
+ import mimeTypeResolver from "mime-types";
4
+
5
+ const convertToMultiHeader = (
6
+ headers: { [key:string]: string|string[] } | undefined
7
+ ):{ [key:string]: string[] } =>
8
+ Object.fromEntries(Object.entries(headers||{}).map(([k,v])=>[ k, Array.isArray(v) ? v : [v] ]));
9
+
10
+ const CORS_HEADERS = convertToMultiHeader({
11
+ "Access-Control-Allow-Credentials": "true",
12
+ "Access-Control-Allow-Headers" : "Origin, X-Requested-With, Content-Type, Accept",
13
+ "Access-Control-Allow-Origin": "*",
14
+ "Access-Control-Allow-Methods": "OPTIONS,POST",
15
+ });
16
+
17
+ export type ResolverResponse = {
18
+ statusCode: number,
19
+ multiValueHeaders?: { [key:string]: string[] },
20
+ body: string | null,
21
+ isBase64Encoded?: boolean,
22
+ [key:string]: any,
23
+ };
24
+
25
+
26
+ export default class ResolverLight {
27
+ private isCorsEnabled: boolean;
28
+ private publicPath: string;
29
+ private apiVersion: string|null;
30
+
31
+ constructor(
32
+ { isCorsEnabled, publicPath, apiVersion }:
33
+ {
34
+ isCorsEnabled: boolean,
35
+ publicPath: string,
36
+ apiVersion?: string|null,
37
+ }
38
+ ){
39
+ this.isCorsEnabled = isCorsEnabled;
40
+ this.publicPath = publicPath;
41
+ this.apiVersion = apiVersion ?? null;
42
+ };
43
+
44
+ private readFileSync(filePath: string){
45
+ const publicPath = path.resolve(this.publicPath);
46
+ const absolutePath = path.join(publicPath, filePath);
47
+ console.log("readFileSync", { filePath, publicPath, absolutePath });
48
+ if(!absolutePath.includes(publicPath)){ return "forbiddenpath"; }
49
+ return fs.readFileSync(absolutePath);
50
+ };
51
+
52
+ private checkFileExist(filePath: string){
53
+ const publicPath = path.resolve(this.publicPath);
54
+ const absolutePath = path.join(this.publicPath, filePath);
55
+ console.log("checkFileExist", { filePath, publicPath, absolutePath });
56
+ if(!absolutePath.includes(publicPath)){ return false; }
57
+ return fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile();
58
+ };
59
+
60
+ raw(param: ResolverResponse){
61
+ return param;
62
+ };
63
+
64
+ json(
65
+ data: { [key:string]: any },
66
+ headers?: { [key:string]: string|string[] }
67
+ ):ResolverResponse{
68
+ return this.raw({
69
+ statusCode: 200,
70
+ multiValueHeaders: {
71
+ "Content-Type": ["application/json; charset=utf-8"],
72
+ ...(this.isCorsEnabled ? CORS_HEADERS : {}),
73
+ ...convertToMultiHeader(headers)
74
+ },
75
+ body: JSON.stringify(data),
76
+ });
77
+ }
78
+
79
+ xml(data: string):ResolverResponse{
80
+ return this.raw({
81
+ statusCode: 200,
82
+ isBase64Encoded: true,
83
+ multiValueHeaders: { "Content-Type": ["application/xml; charset=utf-8"]},
84
+ body: Buffer.from(data).toString("base64"),
85
+ });
86
+ };
87
+
88
+ html(
89
+ data: string,
90
+ headers?: { [key:string]: string|string[] },
91
+ ):ResolverResponse{
92
+ return this.raw({
93
+ statusCode: 200,
94
+ isBase64Encoded: true,
95
+ multiValueHeaders: {"Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers)},
96
+ body: Buffer.from(data).toString("base64"),
97
+ });
98
+ };
99
+
100
+ status301(
101
+ url: string,
102
+ headers?: { [key:string]: string|string[] },
103
+ ):ResolverResponse{
104
+ return this.raw({
105
+ statusCode: 301,
106
+ multiValueHeaders: { "Location" : [url], ...convertToMultiHeader(headers) },
107
+ body:null
108
+ });
109
+ };
110
+
111
+ status404(
112
+ data: string,
113
+ headers?: { [key:string]: string|string[] },
114
+ ):ResolverResponse{
115
+ return this.raw({
116
+ statusCode: 404,
117
+ isBase64Encoded: true,
118
+ multiValueHeaders: {"Content-Type": ["text/html; charset=utf-8"], ...convertToMultiHeader(headers)},
119
+ body: Buffer.from(data).toString("base64"),
120
+ });
121
+ };
122
+
123
+ cors():ResolverResponse{
124
+ return this.raw({
125
+ statusCode: 200,
126
+ multiValueHeaders: this.isCorsEnabled ? CORS_HEADERS: {},
127
+ body: JSON.stringify(""),
128
+ });
129
+ };
130
+
131
+ fileBase64 (
132
+ fileBase64: string,
133
+ mimeType: string,
134
+ headers?: { [key:string]: string|string[] },
135
+ ):ResolverResponse{
136
+ return this.raw({
137
+ statusCode: 200,
138
+ isBase64Encoded: true,
139
+ multiValueHeaders: { "Content-Type": [mimeType || "text/html"], ...convertToMultiHeader(headers) },
140
+ body: fileBase64,
141
+ });
142
+ };
143
+
144
+ file(
145
+ filePath: string,
146
+ headers?: { [key:string]: string|string[] },
147
+ fallbackFilePath?: string,
148
+ ):ResolverResponse{
149
+ if(!this.checkFileExist(filePath)){
150
+ if(fallbackFilePath && this.checkFileExist(fallbackFilePath)){
151
+ return this.file(fallbackFilePath, headers);
152
+ }
153
+ return this.json({ error: "File not found: " + filePath })
154
+ }
155
+ const mimeType = mimeTypeResolver.lookup(filePath);
156
+ const body = this.readFileSync(filePath);
157
+ const bodyBase64 = Buffer.from(body).toString("base64");
158
+ console.log("bodyBase64.length",bodyBase64.length);
159
+ return this.fileBase64(bodyBase64, mimeType || "", headers);
160
+ };
161
+
162
+ api(
163
+ {
164
+ payload,
165
+ versionExpired, sessionExpired, notAuthorized,
166
+ message = null, errorMessage = null,
167
+ ...other
168
+ }: {
169
+ payload?: any;
170
+ versionExpired?: boolean;
171
+ sessionExpired?: boolean;
172
+ notAuthorized?: boolean;
173
+ message?: any;
174
+ errorMessage?: any;
175
+ },
176
+ headers?: { [key:string]: string|string[] },
177
+ ): ResolverResponse {
178
+ if(Object.keys(other).length) console.log("ERROR: Unexpected key in result: " + Object.keys(other).join(", "));
179
+ return this.json({
180
+ apiVersion: this.apiVersion,
181
+ payload,
182
+ ...(versionExpired ? {versionExpired} : {}),
183
+ ...(sessionExpired ? {sessionExpired} : {}),
184
+ ...(notAuthorized ? {notAuthorized} : {}),
185
+ ...(message ? {message} : {}),
186
+ ...(errorMessage ? {errorMessage} : {}),
187
+ }, headers);
188
+ };
189
+
190
+ };
191
+
192
+
193
+
194
+ interface IResolverMethods {
195
+ raw(param: {
196
+ statusCode: number,
197
+ isBase64Encoded?: boolean,
198
+ body: null | string,
199
+ multiValueHeaders: { [key: string]: string[] },
200
+ }): ResolverResponse;
201
+ json(data: { [key: string]: any }, headers?: { [key: string]: string | string[] }): ResolverResponse;
202
+ xml(data: string): ResolverResponse;
203
+ html(data: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
204
+ status301(url: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
205
+ status404(data: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
206
+ cors(): ResolverResponse;
207
+ fileBase64(fileBase64: string, mimeType: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
208
+ file(filePath: string, headers?: { [key: string]: string | string[] }): ResolverResponse;
209
+ api({
210
+ payload,
211
+ versionExpired, sessionExpired, notAuthorized,
212
+ message, errorMessage,
213
+ ...other
214
+ }: {
215
+ payload?: any;
216
+ versionExpired?: boolean;
217
+ sessionExpired?: boolean;
218
+ notAuthorized?: boolean;
219
+ message?: any;
220
+ errorMessage?: any;
221
+ },
222
+ headers?: { [key:string]: string|string[] }
223
+ ): ResolverResponse;
224
+ }
package/src/index.ts CHANGED
@@ -2,3 +2,5 @@ import Lambder from './Lambder.js';
2
2
 
3
3
  export default Lambder;
4
4
  export { default as LambderCaller } from "./LambderCaller.js";
5
+ export { default as ResolverLight } from "./ResolverLight.js";
6
+ export { default as Resolver } from "./Resolver.js";