Skip to content
This repository has been archived by the owner on Jan 18, 2023. It is now read-only.

Commit

Permalink
perf(hooks): use hooks (#49)
Browse files Browse the repository at this point in the history
perf: use hooks (BREAKING CHANGE)
  • Loading branch information
nahtnam committed Oct 20, 2019
2 parents daa7381 + 4979fff commit fe46515
Show file tree
Hide file tree
Showing 27 changed files with 304 additions and 470 deletions.
5 changes: 0 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Expand Up @@ -43,7 +43,6 @@
"decache": "^4.5.1",
"emojic": "^1.1.15",
"find-my-way": "^2.1.0",
"is-class": "0.0.8",
"lodash.set": "^4.3.2",
"micro": "^9.3.4",
"micro-boom": "^1.2.0",
Expand Down
13 changes: 7 additions & 6 deletions src/index.ts
@@ -1,13 +1,14 @@
const light = require('./light'); // eslint-disable-line

module.exports = light.default;
exports = light.default;
// if we need a default export, the following code will work
// const light = require('./light'); // eslint-disable-line
// module.exports = light.default;
// exports = light.default;
// export { default, default as light } from './light';

export { default as server } from './server';
export { default as params } from './params';
export { default as query } from './query';
export { default as test } from './test';
export { default as Route, default as route } from './route';
export { default, default as light } from './light';
export { default as route } from './route';
export {
buffer,
text,
Expand Down
90 changes: 0 additions & 90 deletions src/light.ts

This file was deleted.

File renamed without changes.
220 changes: 146 additions & 74 deletions src/route.ts
@@ -1,13 +1,33 @@
import { IncomingMessage, ServerResponse } from 'http';
import AWSServerlessMicro from 'aws-serverless-micro';
import { run } from 'micro';
import { handleErrors } from 'micro-boom';
import pino from 'pino';
import pinoHTTP from 'pino-http';
import Youch from 'youch';
import forTerminal from 'youch-terminal';

import query from './helpers/query';
import pinoPretty from './helpers/pino-pretty';

type IM = IncomingMessage;
type SR = ServerResponse;
type AP = Promise<any>;

interface Route {
handler: (fn: (req: IM, res: SR) => {} | any) => (req: IM, res: SR) => {};
middleware: (fn: any) => void;
plugins: (fn: any) => void;
}

interface Options {
dev?: boolean;
requestLogger?: boolean;
errorHandler?: boolean;
}

const { LIGHT_ENV } = process.env;

// TODO: extra to a different folder
const youchPlugin = (fun: any): any => async (req: IM, res: SR): Promise<void> => {
try {
return await fun(req, res);
Expand All @@ -19,86 +39,138 @@ const youchPlugin = (fun: any): any => async (req: IM, res: SR): Promise<void> =
}
};

type IM = IncomingMessage;
type SR = ServerResponse;
type AP = Promise<any>;

export default class Route {
public disableRequestLogger: boolean = false;

public disableErrorHandler: boolean = false;

public isDev: boolean = false;

public req: IM;

public res: SR;

public logger: any;

public constructor({ req, res, opts }: { req: IM; res: SR; opts?: any }) {
const options = opts || {};
if (options.isDev) {
this.isDev = true;
// TODO: remove extra logger out of here
const loggerPlugin = ({ dev, requestLogger }: Options): any => {
const pinoOptions = dev ? {
prettyPrint: true,
prettifier: pinoPretty,
level: 'trace',
} : {};
const logger = pino((pinoOptions as any));
const pinoHandler = pinoHTTP({
logger,
});
return (fn: any): any => async (req: IM, res: SR): AP => {
if (requestLogger) {
pinoHandler(req, res);
}
return fn(req, res);
};
};

this.req = req;
this.res = res;

const pinoOptions = this.isDev ? {
prettyPrint: true,
prettifier: pinoPretty,
level: 'trace',
} : {};
this.logger = pino((pinoOptions as any));

if (!this.disableRequestLogger && options.disableRequestLogger) {
this.disableRequestLogger = options.disableRequestLogger;
}
if (!this.disableErrorHandler && options.disableErrorHandler) {
this.disableErrorHandler = options.disableErrorHandler;
const errorHandler = ({ errorHandler: errH, dev }: Options): any => {
const pinoOptions = dev ? {
prettyPrint: true,
prettifier: pinoPretty,
level: 'trace',
} : {};
const logger = pino((pinoOptions as any));
return (fun: any): any => async (req: IM, res: SR): Promise<void> => {
try {
return await fun(req, res);
} catch (err) {
if (errH) {
logger.error(err);
}
throw err;
}
}
};
};

public async query(): Promise<any> {
return query(this.req.url || '');
}
const getOptions = (...opts: Options[]): Options => {
const defaultOptions = {
dev: !(process.env.NODE_ENV === 'production'),
requestLogger: true,
errorHandler: true,
};
return Object.assign({}, defaultOptions, ...opts);
};

public static async query(url: string): Promise<any> {
return query(url);
}
export default (opts?: Options): Route => {
// closures
let options = getOptions(opts || {});
const _middleware: any[] = [];
const _plugins: any[] = [];

return {
middleware(...fns: any[]): void {
_middleware.push(...fns.filter((x: any): any => x));
},

plugins(...fns: any[]): void {
_plugins.push(...fns.filter((x: any): any => x));
},

handler(fn: ((req: IM, res: SR) => {} | any)): (req: IM, res: SR) => {} {
if (!fn) {
throw new Error('route is missing');
}
let func: any = fn;
if (func.default) {
func = func.default;
}

const wrapper = async (Req: IM, Res: SR, reqOpts?: Options): AP => {
options = getOptions(options, (reqOpts || {}));

let proxy = async (req: IM, res: SR): AP => {
for (const mw of _middleware) { // eslint-disable-line
await mw(req, res); // eslint-disable-line

if (res.headersSent) {
return null;
}
}

return func(req, res);
};

const plugins = [
loggerPlugin(options),
options.errorHandler ? handleErrors : null,
errorHandler(options),
..._plugins,
].filter((x: any): any => x);

if (options.dev) {
plugins.push(youchPlugin);
}

public _getInternalPlugins(): any[] {
const plugins = [];
if (!this.disableRequestLogger) {
const pinoHandler = pinoHTTP({
logger: this.logger,
});
plugins.push((fn: any): any => async (req: IM, res: SR): AP => {
pinoHandler(req, res);
return fn(req, res);
});
}
proxy = plugins.reverse().reduce((acc: any, val: any): any => val(acc), proxy);

if (!this.disableErrorHandler) {
const errorHandler = (fun: any): any => async (req: IM, res: SR): Promise<void> => {
try {
return await fun(req, res);
} catch (err) {
this.logger.error(err);
throw err;
}
return proxy(Req, Res);
};

// log the error first and then push it to `micro-boom`
plugins.push(handleErrors);
plugins.push(errorHandler);
}

if (this.isDev) {
plugins.push(youchPlugin);
}
// detect if serverless environment
const { env } = process;
const isNetlify = LIGHT_ENV === 'netlify' || env.LIGHT_ENV === 'netlify';
const isAWS = LIGHT_ENV === 'aws' || env.LIGHT_ENV === 'aws';
const isRunKit = LIGHT_ENV === 'runkit' || env.LIGHT_ENV === 'runkit';
const isNow = LIGHT_ENV === 'now' || env.LIGHT_ENV === 'now';

const isServerless = isNetlify || isAWS || isRunKit || isNow;

// transform exports
let handler: any = wrapper;
if (isServerless) {
if (isRunKit || isNow) {
// TODO: test this in runkit and now tests
/* istanbul ignore next */
handler = async (req: IM, res: SR): AP => run(req, res, (wrapper as any));
}
if (isNetlify || isAWS) {
handler = {
handler: AWSServerlessMicro(wrapper),
};
}
if (isRunKit) {
handler = {
endpoint: wrapper,
};
}
}

return plugins;
}
}
return handler;
},
};
};
1 change: 1 addition & 0 deletions src/server.ts
Expand Up @@ -15,6 +15,7 @@ interface Light {
type IM = IncomingMessage;
type SR = ServerResponse;

// TODO: change opts type to the Options type in route.ts
const app = ({
routes,
opts,
Expand Down
3 changes: 2 additions & 1 deletion src/test.ts
Expand Up @@ -5,7 +5,8 @@ import { server } from './index';
export default async (route: any, opts?: any): Promise<any> => {
// generate a server with only the route provided
const options = {
disableRequestLogger: true,
requestLogger: false,
dev: false,
...(opts || {}),
};

Expand Down

0 comments on commit fe46515

Please sign in to comment.