New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

kequapp

Package Overview
Dependencies
Maintainers
1
Versions
65
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

kequapp - npm Package Compare versions

Comparing version 0.4.2 to 0.5.0

dist/built-in/tools/ex.d.ts

12

dist/body/create-get-body.js

@@ -44,6 +44,6 @@ "use strict";

const stream_reader_1 = __importDefault(require("./stream-reader"));
const ex_1 = __importDefault(require("../util/tools/ex"));
const ex_1 = __importDefault(require("../built-in/tools/ex"));
const parseBody = (0, create_parse_body_1.default)({
'application/x-www-form-urlencoded': create_parse_body_1.parseUrlEncoded,
'application/json': create_parse_body_1.parseJson,
'application/json': create_parse_body_1.parseJson
});

@@ -53,2 +53,3 @@ function createGetBody(req) {

return function (options = {}) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {

@@ -58,4 +59,4 @@ if (_body === undefined) {

headers: {
'content-type': req.headers['content-type'] || '',
'content-disposition': req.headers['content-disposition'] || ''
'content-type': (_a = req.headers['content-type']) !== null && _a !== void 0 ? _a : '',
'content-disposition': (_b = req.headers['content-disposition']) !== null && _b !== void 0 ? _b : ''
},

@@ -91,3 +92,4 @@ data: yield (0, stream_reader_1.default)(getStream(req), getMaxPayloadSize(options))

function getStream(req) {
const encoding = (req.headers['content-encoding'] || 'identity').toLowerCase();
var _a;
const encoding = ((_a = req.headers['content-encoding']) !== null && _a !== void 0 ? _a : 'identity').toLowerCase();
switch (encoding) {

@@ -94,0 +96,0 @@ case 'br': return req.pipe(zlib_1.default.createBrotliDecompress());

@@ -42,3 +42,3 @@ "use strict";

'text/': ({ data }) => data.toString(),
'application/json': create_parse_body_1.parseJson,
'application/json': create_parse_body_1.parseJson
}, ({ data }) => data);

@@ -48,2 +48,3 @@ function createGetResponse(res) {

return function (options = {}) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {

@@ -54,4 +55,4 @@ if (_body === undefined) {

headers: {
'content-type': String(res.getHeader('Content-Type') || ''),
'content-disposition': String(res.getHeader('Content-Disposition') || '')
'content-type': String((_a = res.getHeader('Content-Type')) !== null && _a !== void 0 ? _a : ''),
'content-disposition': String((_b = res.getHeader('Content-Disposition')) !== null && _b !== void 0 ? _b : '')
},

@@ -58,0 +59,0 @@ data

@@ -7,6 +7,7 @@ "use strict";

exports.parseJson = exports.parseUrlEncoded = void 0;
const ex_1 = __importDefault(require("../util/tools/ex"));
const ex_1 = __importDefault(require("../built-in/tools/ex"));
function createParseBody(parsers, _default) {
return function (body) {
const contentType = body.headers['content-type'] || 'text/plain';
var _a;
const contentType = (_a = body.headers['content-type']) !== null && _a !== void 0 ? _a : 'text/plain';
try {

@@ -13,0 +14,0 @@ for (const key of Object.keys(parsers)) {

@@ -8,2 +8,3 @@ "use strict";

function parseMultipart(parts) {
var _a;
const result = {};

@@ -14,11 +15,11 @@ const files = [];

const { filename, name } = (0, header_attributes_1.default)(part.headers['content-disposition']);
const mime = getMime(part.headers['content-type']);
const isFile = filename || !mime.startsWith('text/');
const contentType = getContentType(part.headers['content-type']);
const isFile = filename !== null && filename !== void 0 ? filename : !contentType.startsWith('text/');
if (isFile) {
files.push(Object.assign(Object.assign({}, part), { mime, name, filename }));
files.push(Object.assign(Object.assign({}, part), { contentType, name, filename }));
continue;
}
const key = name || 'undefined';
const key = name !== null && name !== void 0 ? name : 'undefined';
const value = part.data.toString();
counters[key] = counters[key] || 0;
counters[key] = (_a = counters[key]) !== null && _a !== void 0 ? _a : 0;
counters[key]++;

@@ -38,4 +39,5 @@ if (counters[key] === 2) {

exports.default = parseMultipart;
function getMime(contentType) {
return (contentType === null || contentType === void 0 ? void 0 : contentType.split(';')[0].toLowerCase().trim()) || 'text/plain';
function getContentType(contentType) {
var _a;
return (_a = contentType === null || contentType === void 0 ? void 0 : contentType.split(';')[0].toLowerCase().trim()) !== null && _a !== void 0 ? _a : 'text/plain';
}

@@ -6,3 +6,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const ex_1 = __importDefault(require("../../util/tools/ex"));
const ex_1 = __importDefault(require("../../built-in/tools/ex"));
const header_attributes_1 = __importDefault(require("../../util/header-attributes"));

@@ -9,0 +9,0 @@ const CR = 0x0d;

@@ -6,3 +6,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const ex_1 = __importDefault(require("../util/tools/ex"));
const ex_1 = __importDefault(require("../built-in/tools/ex"));
function normalizeBody(body, options) {

@@ -12,3 +12,3 @@ if (options.skipNormalize === true)

const result = Object.assign({}, body);
const { required = [], arrays = [], numbers = [], booleans = [], validate, postProcess } = options;
const { required = [], arrays = [], numbers = [], booleans = [], validate } = options;
for (const key of arrays) {

@@ -81,8 +81,3 @@ if (!Array.isArray(result[key])) {

}
if (typeof postProcess === 'function') {
return postProcess(result);
}
else {
return result;
}
return result;
}

@@ -89,0 +84,0 @@ exports.default = normalizeBody;

/// <reference types="node" />
/// <reference types="node" />
import { Readable } from 'stream';
export default function streamReader(stream: Readable, maxPayloadSize?: number): Promise<Buffer>;

@@ -6,3 +6,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const ex_1 = __importDefault(require("../util/tools/ex"));
const ex_1 = __importDefault(require("../built-in/tools/ex"));
function streamReader(stream, maxPayloadSize = Infinity) {

@@ -9,0 +9,0 @@ return new Promise(function (resolve, reject) {

@@ -1,2 +0,2 @@

declare const _default: import("../types").IAddable;
declare const _default: import("../types").TErrorHandlerData;
export default _default;
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const create_error_handler_1 = __importDefault(require("../router/modules/create-error-handler"));
exports.default = (0, create_error_handler_1.default)((ex, { res }) => {
var _a;
const error = {
statusCode: ex.statusCode,
message: ex.message
};
if (process.env.NODE_ENV !== 'production') {
error.stack = (_a = ex.stack) === null || _a === void 0 ? void 0 : _a.split(/\r?\n/);
error.info = ex.info;
const modules_1 = require("../router/modules");
exports.default = (0, modules_1.createErrorHandler)({
contentType: '*',
handle(ex, { res }) {
var _a;
const error = {
statusCode: ex.statusCode,
message: ex.message
};
if (process.env.NODE_ENV !== 'production') {
error.stack = (_a = ex.stack) === null || _a === void 0 ? void 0 : _a.split(/\r?\n/);
error.info = ex.info;
}
res.setHeader('Content-Type', 'application/json');
return { error };
}
res.setHeader('Content-Type', 'application/json');
return { error };
});
/// <reference types="node" />
import { IncomingMessage, ServerResponse } from 'http';
import { TPathname } from '../../types';
export default function sendFile(req: IncomingMessage, res: ServerResponse, asset: TPathname, mime?: string): Promise<void>;
export default function sendFile(req: IncomingMessage, res: ServerResponse, asset: TPathname, contentType?: string): Promise<void>;

@@ -17,9 +17,9 @@ "use strict";

const path_1 = __importDefault(require("path"));
const ex_1 = __importDefault(require("../../util/tools/ex"));
const guess_mime_1 = __importDefault(require("../../util/guess-mime"));
function sendFile(req, res, asset, mime) {
const ex_1 = __importDefault(require("../tools/ex"));
const guess_content_type_1 = __importDefault(require("../../util/guess-content-type"));
function sendFile(req, res, asset, contentType) {
return __awaiter(this, void 0, void 0, function* () {
const location = path_1.default.join(process.cwd(), asset);
const stats = getStats(location);
res.setHeader('Content-Type', mime || (0, guess_mime_1.default)(asset));
const stats = yield getStats(location);
res.setHeader('Content-Type', contentType !== null && contentType !== void 0 ? contentType : (0, guess_content_type_1.default)(asset));
res.setHeader('Content-Length', stats.size);

@@ -51,10 +51,12 @@ if (req.method === 'HEAD') {

function getStats(location) {
try {
const stats = fs_1.default.statSync(location);
if (stats.isFile())
return stats;
}
catch (error) {
}
throw ex_1.default.NotFound(undefined, { location });
return __awaiter(this, void 0, void 0, function* () {
try {
const stats = yield fs_1.default.promises.stat(location);
if (stats.isFile())
return stats;
}
catch (error) {
}
throw ex_1.default.NotFound(undefined, { location });
});
}

@@ -1,14 +0,10 @@

import { IAddable, TParams, TPathname, TPathnameWild } from '../../types';
import { TRouteData, TParams, TPathname, TPathnameWild, THandle } from '../../types';
declare type TStaticDirectoryOptions = {
dir: TPathname;
exclude: TPathname[];
mime: TParams;
url?: TPathnameWild;
dir?: TPathname;
exclude?: TPathname[];
contentTypes?: TParams;
handles?: THandle[];
};
interface IStaticDirectory {
(url: TPathnameWild, options: Partial<TStaticDirectoryOptions>): IAddable;
(url: TPathnameWild): IAddable;
(options: Partial<TStaticDirectoryOptions>): IAddable;
(): IAddable;
}
declare const _default: IStaticDirectory;
export default _default;
export default function staticDirectory(options: TStaticDirectoryOptions): TRouteData;
export {};

@@ -17,32 +17,34 @@ "use strict";

const send_file_1 = __importDefault(require("./send-file"));
const create_route_1 = __importDefault(require("../../router/modules/create-route"));
const extract_1 = require("../../util/extract");
const guess_mime_1 = __importDefault(require("../../util/guess-mime"));
const modules_1 = require("../../router/modules");
const guess_content_type_1 = __importDefault(require("../../util/guess-content-type"));
const validate_1 = require("../../util/validate");
const ex_1 = __importDefault(require("../../util/tools/ex"));
const DEFAULT_OPTIONS = {
dir: '/public',
exclude: [],
mime: {}
};
exports.default = staticDirectory;
function staticDirectory(...params) {
const url = (0, extract_1.extractUrl)(params, '/**');
const options = (0, extract_1.extractOptions)(params, DEFAULT_OPTIONS);
(0, validate_1.validatePathname)(url, 'Static directory url', true);
const ex_1 = __importDefault(require("../tools/ex"));
function staticDirectory(options) {
var _a, _b;
validateOptions(options);
return (0, create_route_1.default)(url, ({ req, res, params }) => __awaiter(this, void 0, void 0, function* () {
const asset = path_1.default.join(options.dir, params['**']);
if (isExcluded(options.exclude, asset)) {
const handle = (0, modules_1.createHandle)(({ req, res, params }) => __awaiter(this, void 0, void 0, function* () {
var _c, _d;
const asset = path_1.default.join((_c = options.dir) !== null && _c !== void 0 ? _c : '/public', params.wild);
if (isExcluded((_d = options.exclude) !== null && _d !== void 0 ? _d : [], asset)) {
throw ex_1.default.NotFound();
}
yield (0, send_file_1.default)(req, res, asset, (0, guess_mime_1.default)(asset, options.mime));
yield (0, send_file_1.default)(req, res, asset, (0, guess_content_type_1.default)(asset, options.contentTypes));
}));
return (0, modules_1.createRoute)({
method: 'GET',
url: (_a = options.url) !== null && _a !== void 0 ? _a : '/**',
handles: [...(_b = options.handles) !== null && _b !== void 0 ? _b : [], handle]
});
}
exports.default = staticDirectory;
function validateOptions(options) {
var _a;
(0, validate_1.validateExists)(options, 'Static directory options');
(0, validate_1.validateObject)(options, 'Static directory options');
(0, validate_1.validatePathname)(options.url, 'Static directory options.url', true);
(0, validate_1.validatePathname)(options.dir, 'Static directory options.dir');
(0, validate_1.validateArray)(options.exclude, 'Static directory options.exclude');
(0, validate_1.validateObject)(options.mime, 'Static directory options.mime', 'string');
for (const value of options.exclude || []) {
(0, validate_1.validateObject)(options.contentTypes, 'Static directory options.contentTypes', 'string');
(0, validate_1.validateArray)(options.handles, 'Static directory handles', 'function');
for (const value of (_a = options.exclude) !== null && _a !== void 0 ? _a : []) {
(0, validate_1.validatePathname)(value, 'Static directory options.exclude');

@@ -49,0 +51,0 @@ }

@@ -1,9 +0,9 @@

import { IAddable, TPathname } from '../../types';
interface IStaticFile {
(url: TPathname, asset: TPathname, mime: string): IAddable;
(url: TPathname, asset: TPathname): IAddable;
(asset: TPathname, mime: string): IAddable;
(asset: TPathname): IAddable;
}
declare const _default: IStaticFile;
export default _default;
import { THandle, TPathname, TRouteData } from '../../types';
declare type TStaticFileOptions = {
url?: TPathname;
asset: TPathname;
contentType?: string;
handles?: THandle[];
};
export default function staticFile(options: TStaticFileOptions): TRouteData;
export {};

@@ -15,21 +15,26 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const create_route_1 = __importDefault(require("../../router/modules/create-route"));
const extract_1 = require("../../util/extract");
const modules_1 = require("../../router/modules");
const validate_1 = require("../../util/validate");
const send_file_1 = __importDefault(require("./send-file"));
exports.default = staticFile;
function staticFile(...params) {
let url = (0, extract_1.extractUrl)(params);
let asset = (0, extract_1.extractUrl)(params);
const mime = (0, extract_1.extractContentType)(params, undefined);
if (asset === '/') {
asset = url;
url = '/';
}
(0, validate_1.validatePathname)(url, 'Static file url');
(0, validate_1.validatePathname)(asset, 'Static file asset');
(0, validate_1.validateType)(mime, 'Static file mime', 'string');
return (0, create_route_1.default)(url, ({ req, res }) => __awaiter(this, void 0, void 0, function* () {
yield (0, send_file_1.default)(req, res, asset, mime);
function staticFile(options) {
var _a;
validateOptions(options);
const handle = (0, modules_1.createHandle)(({ req, res }) => __awaiter(this, void 0, void 0, function* () {
yield (0, send_file_1.default)(req, res, options.asset, options.contentType);
}));
return (0, modules_1.createRoute)({
method: 'GET',
url: options.url,
handles: [...(_a = options.handles) !== null && _a !== void 0 ? _a : [], handle]
});
}
exports.default = staticFile;
function validateOptions(options) {
(0, validate_1.validateExists)(options, 'Static file options');
(0, validate_1.validateObject)(options, 'Static file options');
(0, validate_1.validatePathname)(options.url, 'Static file url');
(0, validate_1.validateExists)(options.asset, 'Static file asset');
(0, validate_1.validatePathname)(options.asset, 'Static file asset');
(0, validate_1.validateContentType)(options.contentType, 'Static file contentType');
(0, validate_1.validateArray)(options.handles, 'Static file handles', 'function');
}

@@ -1,2 +0,2 @@

declare const _default: import("../types").IAddable;
declare const _default: import("../types").TRendererData;
export default _default;

@@ -6,13 +6,16 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const create_renderer_1 = __importDefault(require("../router/modules/create-renderer"));
const ex_1 = __importDefault(require("../util/tools/ex"));
exports.default = (0, create_renderer_1.default)('application/json', (payload, { req, res }) => {
const json = generateJson(payload);
res.setHeader('Content-Length', Buffer.byteLength(json));
if (req.method === 'HEAD') {
res.end();
const modules_1 = require("../router/modules");
const ex_1 = __importDefault(require("../built-in/tools/ex"));
exports.default = (0, modules_1.createRenderer)({
contentType: 'application/json',
handle(payload, { req, res }) {
const json = generateJson(payload);
res.setHeader('Content-Length', Buffer.byteLength(json));
if (req.method === 'HEAD') {
res.end();
}
else {
res.end(json);
}
}
else {
res.end(json);
}
});

@@ -19,0 +22,0 @@ function generateJson(payload) {

@@ -1,2 +0,2 @@

declare const _default: import("../types").IAddable;
declare const _default: import("../types").TRendererData;
export default _default;

@@ -6,13 +6,16 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const create_renderer_1 = __importDefault(require("../router/modules/create-renderer"));
const ex_1 = __importDefault(require("../util/tools/ex"));
exports.default = (0, create_renderer_1.default)('text/*', (payload, { req, res }) => {
const text = generateText(payload);
res.setHeader('Content-Length', Buffer.byteLength(text));
if (req.method === 'HEAD') {
res.end();
const modules_1 = require("../router/modules");
const ex_1 = __importDefault(require("../built-in/tools/ex"));
exports.default = (0, modules_1.createRenderer)({
contentType: 'text/*',
handle(payload, { req, res }) {
const text = generateText(payload);
res.setHeader('Content-Length', Buffer.byteLength(text));
if (req.method === 'HEAD') {
res.end();
}
else {
res.end(text);
}
}
else {
res.end(text);
}
});

@@ -19,0 +22,0 @@ function generateText(payload) {

@@ -1,14 +0,10 @@

import { IKequapp } from './types';
export { default as createBranch } from './router/modules/create-branch';
export { default as createConfig } from './router/modules/create-config';
export { default as createErrorHandler } from './router/modules/create-error-handler';
export { default as createHandle } from './router/modules/create-handle';
export { default as createRenderer } from './router/modules/create-renderer';
export { default as createRoute } from './router/modules/create-route';
/// <reference types="node" />
import { RequestListener } from 'http';
import { TBranchData } from './types';
export { default as sendFile } from './built-in/helpers/send-file';
export { default as staticFile } from './built-in/helpers/static-file';
export { default as staticDirectory } from './built-in/helpers/static-directory';
export { default as Ex } from './util/tools/ex';
export { default as inject } from './util/tools/inject';
export * from './types';
export declare function createApp(...params: unknown[]): IKequapp;
export { default as Ex } from './built-in/tools/ex';
export { default as inject } from './built-in/tools/inject';
export * from './router/modules';
export declare function createApp(structure: TBranchData): RequestListener;

@@ -16,2 +16,11 @@ "use strict";

};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {

@@ -21,22 +30,6 @@ return (mod && mod.__esModule) ? mod : { "default": mod };

Object.defineProperty(exports, "__esModule", { value: true });
exports.createApp = exports.inject = exports.Ex = exports.staticDirectory = exports.staticFile = exports.sendFile = exports.createRoute = exports.createRenderer = exports.createHandle = exports.createErrorHandler = exports.createConfig = exports.createBranch = void 0;
const error_handler_1 = __importDefault(require("./built-in/error-handler"));
const json_renderer_1 = __importDefault(require("./built-in/json-renderer"));
const text_renderer_1 = __importDefault(require("./built-in/text-renderer"));
exports.createApp = exports.inject = exports.Ex = exports.staticDirectory = exports.staticFile = exports.sendFile = void 0;
const create_get_body_1 = __importDefault(require("./body/create-get-body"));
const actions_1 = require("./router/actions");
const create_router_1 = __importDefault(require("./router/create-router"));
const create_branch_1 = __importDefault(require("./router/modules/create-branch"));
const request_processor_1 = __importDefault(require("./router/request-processor"));
const extract_1 = require("./util/extract");
var create_branch_2 = require("./router/modules/create-branch");
Object.defineProperty(exports, "createBranch", { enumerable: true, get: function () { return __importDefault(create_branch_2).default; } });
var create_config_1 = require("./router/modules/create-config");
Object.defineProperty(exports, "createConfig", { enumerable: true, get: function () { return __importDefault(create_config_1).default; } });
var create_error_handler_1 = require("./router/modules/create-error-handler");
Object.defineProperty(exports, "createErrorHandler", { enumerable: true, get: function () { return __importDefault(create_error_handler_1).default; } });
var create_handle_1 = require("./router/modules/create-handle");
Object.defineProperty(exports, "createHandle", { enumerable: true, get: function () { return __importDefault(create_handle_1).default; } });
var create_renderer_1 = require("./router/modules/create-renderer");
Object.defineProperty(exports, "createRenderer", { enumerable: true, get: function () { return __importDefault(create_renderer_1).default; } });
var create_route_1 = require("./router/modules/create-route");
Object.defineProperty(exports, "createRoute", { enumerable: true, get: function () { return __importDefault(create_route_1).default; } });
var send_file_1 = require("./built-in/helpers/send-file");

@@ -48,22 +41,50 @@ Object.defineProperty(exports, "sendFile", { enumerable: true, get: function () { return __importDefault(send_file_1).default; } });

Object.defineProperty(exports, "staticDirectory", { enumerable: true, get: function () { return __importDefault(static_directory_1).default; } });
var ex_1 = require("./util/tools/ex");
var ex_1 = require("./built-in/tools/ex");
Object.defineProperty(exports, "Ex", { enumerable: true, get: function () { return __importDefault(ex_1).default; } });
var inject_1 = require("./util/tools/inject");
var inject_1 = require("./built-in/tools/inject");
Object.defineProperty(exports, "inject", { enumerable: true, get: function () { return __importDefault(inject_1).default; } });
__exportStar(require("./types"), exports);
function createApp(...params) {
const handles = (0, extract_1.extractHandles)(params);
const branch = (0, create_branch_1.default)(...handles).add(error_handler_1.default, json_renderer_1.default, text_renderer_1.default);
let router = (0, create_router_1.default)(branch());
__exportStar(require("./router/modules"), exports);
function createApp(structure) {
const router = (0, create_router_1.default)(structure);
function app(req, res) {
(0, request_processor_1.default)(router, req, res);
requestProcessor(router, req, res);
}
function add(...params) {
branch.add(...params);
router = (0, create_router_1.default)(branch());
return app;
}
Object.assign(app, { add });
return app;
}
exports.createApp = createApp;
function requestProcessor(router, req, res) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
const startedAt = Date.now();
const url = new URL((_a = req.url) !== null && _a !== void 0 ? _a : '/', `${req.headers.protocol}://${req.headers.host}`);
const method = (_b = req.method) !== null && _b !== void 0 ? _b : 'GET';
const [route, params, methods] = router(method, url.pathname);
const { logger } = route;
const bundle = Object.freeze({
req,
res,
url,
context: {},
params,
methods,
getBody: (0, create_get_body_1.default)(req),
logger
});
try {
yield (0, actions_1.renderRoute)(route, bundle);
}
catch (error) {
try {
yield (0, actions_1.renderError)(route, bundle, error);
}
catch (fatalError) {
res.statusCode = 500;
logger.error(fatalError);
}
}
if (!res.writableEnded) {
res.end();
}
logger.http(res.statusCode, Date.now() - startedAt, method, url.pathname);
});
}

@@ -1,3 +0,3 @@

import { TAddableData, TBundle, TRouteData } from '../types';
export declare function renderRoute(collection: TAddableData, bundle: TBundle, route: TRouteData): Promise<void>;
export declare function renderError(collection: TAddableData, bundle: TBundle, error: unknown): Promise<void>;
import { TBundle, TRoute } from '../types';
export declare function renderRoute(route: TRoute, bundle: TBundle): Promise<void>;
export declare function renderError(route: TRoute, bundle: TBundle, error: unknown): Promise<void>;

@@ -14,8 +14,7 @@ "use strict";

const find_1 = require("./find");
const ex_1 = require("../util/tools/ex");
function renderRoute(collection, bundle, route) {
const ex_1 = require("../built-in/tools/ex");
function renderRoute(route, bundle) {
return __awaiter(this, void 0, void 0, function* () {
const { renderers } = collection;
const { res, methods } = bundle;
const { handles, method } = route;
const { handles, method, renderers } = route;
let payload = undefined;

@@ -27,3 +26,2 @@ if (methods.includes('OPTIONS')) {

res.statusCode = 204;
res.setHeader('Content-Length', 0);
addOptionsHeaders(bundle);

@@ -45,5 +43,5 @@ }

exports.renderRoute = renderRoute;
function renderError(collection, bundle, error) {
function renderError(route, bundle, error) {
return __awaiter(this, void 0, void 0, function* () {
const { errorHandlers, renderers } = collection;
const { errorHandlers, renderers } = route;
const { res, logger } = bundle;

@@ -62,3 +60,4 @@ const errorHandler = (0, find_1.findErrorHandler)(errorHandlers, getContentType(bundle));

function getContentType({ res }) {
return String(res.getHeader('Content-Type') || 'text/plain');
var _a;
return String((_a = res.getHeader('Content-Type')) !== null && _a !== void 0 ? _a : 'text/plain');
}

@@ -75,2 +74,3 @@ function addOptionsHeaders({ req, res, methods }) {

}
res.setHeader('Content-Length', 0);
}

@@ -77,0 +77,0 @@ function finalize(renderers, bundle, payload) {

@@ -1,2 +0,2 @@

import { IRouter, TAddableData } from '../types';
export default function createRouter(branchData: TAddableData): IRouter;
import { IRouter, TBranchData } from '../types';
export default function createRouter(structure: TBranchData): IRouter;
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const extract_1 = require("../util/extract");
function createRouter(branchData) {
const routes = [...branchData.routes].sort(priority);
const renderers = [...branchData.renderers].sort(priority);
const errorHandlers = [...branchData.errorHandlers].sort(priority);
const configs = [...branchData.configs].sort(priority);
function router(pathname) {
if (pathname) {
const clientParts = (0, extract_1.getParts)(pathname);
return {
routes: routes.filter(item => compare(item.parts, clientParts)),
renderers: renderers.filter(item => compare(item.parts, clientParts)),
errorHandlers: errorHandlers.filter(item => compare(item.parts, clientParts)),
configs: configs.filter(item => compare(item.parts, clientParts))
};
}
return {
routes,
renderers,
errorHandlers,
configs
};
}
return router;
const create_regexp_1 = __importDefault(require("./create-regexp"));
const main_1 = require("../main");
const logger_1 = __importDefault(require("../util/logger"));
const validate_1 = require("../util/validate");
const cacher_1 = require("./util/cacher");
const extract_1 = require("./util/extract");
function createRouter(structure) {
(0, validate_1.validateBranch)(structure);
const routes = (0, cacher_1.cacheRoutes)(structure);
const branches = (0, cacher_1.cacheBranches)(structure);
return function router(method, url) {
var _a;
const matchedRoutes = routes.filter(item => item.regexp.test(url));
const route = (_a = getRoute(matchedRoutes, method)) !== null && _a !== void 0 ? _a : generate404(branches, url, method);
const params = (0, extract_1.matchGroups)(url, route.regexp);
const methods = getMethods(matchedRoutes, route.autoHead);
return [route, params, methods];
};
}
exports.default = createRouter;
function priority(a, b) {
const count = Math.max(a.parts.length, b.parts.length);
for (let i = 0; i < count; i++) {
const aa = a.parts[i];
const bb = b.parts[i];
if (aa === bb)
continue;
if (bb === undefined || aa === '**')
return 1;
if (aa === undefined || bb === '**')
return -1;
const aaa = aa[0] === ':';
const bbb = bb[0] === ':';
if (aaa && bbb)
continue;
if (aaa)
return 1;
if (bbb)
return -1;
return aa.localeCompare(bb);
function getRoute(routes, method) {
const route = routes.find(item => item.method === method);
if (route === undefined && method === 'HEAD') {
return routes.find(item => item.autoHead && item.method === 'GET');
}
if (a.contentType && b.contentType) {
const aa = a.contentType.indexOf('*');
const bb = b.contentType.indexOf('*');
if (aa > -1 && bb > -1)
return bb - aa;
if (aa > -1)
return 1;
if (bb > -1)
return -1;
}
return 0;
return route;
}
function compare(parts, clientParts) {
const isWild = parts.includes('**');
if (!isWild && parts.length !== clientParts.length) {
return false;
}
if (isWild && parts.length - 1 > clientParts.length) {
return false;
}
for (let i = 0; i < parts.length; i++) {
if (parts[i] === '**')
return true;
if (parts[i] !== clientParts[i] && parts[i][0] !== ':')
return false;
}
return true;
function generate404(branches, key, method) {
var _a;
const branch = (_a = branches.find(item => item.regexp.test(key))) !== null && _a !== void 0 ? _a : {
regexp: (0, create_regexp_1.default)('/**'),
handles: [],
errorHandlers: [],
renderers: [],
autoHead: true,
logger: logger_1.default
};
return Object.assign(Object.assign({}, branch), { method, handles: [...branch.handles, () => { throw main_1.Ex.NotFound(); }] });
}
function getMethods(routes, autoHead) {
const set = new Set(routes.map(item => item.method));
if (autoHead && set.has('GET'))
set.add('HEAD');
return [...set];
}

@@ -1,5 +0,3 @@

import { TConfig, TErrorHandler, TErrorHandlerData, TRenderer, TRendererData, TRouteData } from '../types';
import { TErrorHandler, TErrorHandlerData, TRenderer, TRendererData } from '../types';
export declare function findRenderer(renderers: TRendererData[], contentType: string): TRenderer;
export declare function findErrorHandler(errorHandlers: TErrorHandlerData[], contentType: string): TErrorHandler;
export declare function warnDuplicates({ logger }: TConfig, routes: TRouteData[]): void;
export declare function isDuplicate(a: TRouteData, b: TRouteData): boolean;

@@ -6,4 +6,4 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.isDuplicate = exports.warnDuplicates = exports.findErrorHandler = exports.findRenderer = void 0;
const ex_1 = __importDefault(require("../util/tools/ex"));
exports.findErrorHandler = exports.findRenderer = void 0;
const ex_1 = __importDefault(require("../built-in/tools/ex"));
function findRenderer(renderers, contentType) {

@@ -38,33 +38,1 @@ const renderer = renderers.find(renderer => compareContentType(renderer.contentType, contentType));

}
function warnDuplicates({ logger }, routes) {
const checked = [];
for (const route of routes) {
const exists = checked.find(value => isDuplicate(value, route));
if (exists) {
logger.warn('Duplicate route detected', {
method: route.method,
url: `/${route.parts.join('/')}`,
matches: `/${exists.parts.join('/')}`
});
}
checked.push(route);
}
}
exports.warnDuplicates = warnDuplicates;
function isDuplicate(a, b) {
if (a.method !== b.method || a.parts.length !== b.parts.length) {
return false;
}
const count = a.parts.length;
for (let i = 0; i < count; i++) {
const aa = a.parts[i];
const bb = b.parts[i];
if (aa === bb)
continue;
if ((aa === '**' || aa[0] === ':') && (bb === '**' || bb[0] === ':'))
return true;
return false;
}
return true;
}
exports.isDuplicate = isDuplicate;
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import { RequestListener, IncomingMessage, ServerResponse } from 'http';
import { IncomingMessage, ServerResponse } from 'http';
import { Transform } from 'stream';
export interface IKequapp extends RequestListener {
(...handles: THandle[]): IKequapp;
add(...routers: IAddable[]): IKequapp;
}
export declare type TConfigInput = {
logger: TLogger | boolean;
autoHead: boolean;
};
export declare type TConfig = {
logger: TLogger;
autoHead: boolean;
};
export declare type TLoggerLvl = (...params: unknown[]) => void;
export declare type TLogger = {
log: (...params: unknown[]) => void;
error: (...params: unknown[]) => void;
warn: (...params: unknown[]) => void;
debug: (...params: unknown[]) => void;
log: TLoggerLvl;
error: TLoggerLvl;
warn: TLoggerLvl;
info: TLoggerLvl;
http: TLoggerLvl;
verbose: TLoggerLvl;
debug: TLoggerLvl;
silly: TLoggerLvl;
};
export interface IAddable {
(): Partial<TAddableData>;
}
export interface IAddableBranch {
(): TAddableData;
add(...routers: IAddable[]): IAddableBranch;
}
export declare type TAddableData = {
routes: TRouteData[];
renderers: TRendererData[];
errorHandlers: TErrorHandlerData[];
configs: TConfigData[];
};
export interface IRouter {
(pathname?: string): TAddableData;
}
export declare type TRoute = {
parts: string[];
method: string;
lifecycle: (req: IncomingMessage, res: ServerResponse) => void;
};
export declare type TRouteData = {
parts: string[];
handles: THandle[];
method: string;
};
export declare type TRendererData = {
parts: string[];
contentType: string;
handle: TRenderer;
};
export declare type TErrorHandlerData = {
parts: string[];
contentType: string;
handle: TErrorHandler;
};
export declare type TConfigData = {
parts: string[];
config: TConfig;
};
export declare type THandle = (bundle: TBundle) => Promise<unknown> | unknown;

@@ -101,8 +52,8 @@ export declare type TRenderer = (payload: unknown, bundle: TBundle) => Promise<void> | void;

(format?: TGetBodyOptions): Promise<TBodyJson>;
<T>(format: TGetBodyOptions & {
<T>(format: TGetBodyOptions<T> & {
multipart: true;
}): Promise<[T, TFilePart[]]>;
<T>(format?: TGetBodyOptions): Promise<T>;
<T>(format?: TGetBodyOptions<T>): Promise<T>;
}
export declare type TGetBodyOptions = {
export declare type TGetBodyOptions<T = TBodyJson> = {
raw?: boolean;

@@ -116,4 +67,3 @@ multipart?: boolean;

required?: string[];
validate?(body: TBodyJson): string | void;
postProcess?(body: TBodyJson): TBodyJson;
validate?(body: T): string | void;
};

@@ -142,3 +92,3 @@ export interface IGetResponse {

export declare type TFilePart = TRawPart & {
mime?: string;
contentType?: string;
name?: string;

@@ -160,1 +110,46 @@ filename?: string;

};
export interface IRouter {
(method: string, url: string): [TRoute, TParams, string[]];
}
export declare type TRouteData = {
method: string;
url?: TPathname;
handles?: THandle[];
logger?: Partial<TLogger>;
autoHead?: boolean;
};
export declare type TBranchData = Omit<TRouteData, 'method'> & {
routes?: TRouteData[];
branches?: TBranchData[];
errorHandlers?: TErrorHandlerData[];
renderers?: TRendererData[];
};
export declare type TRendererData = {
contentType: string;
handle: TRenderer;
};
export declare type TErrorHandlerData = {
contentType: string;
handle: TErrorHandler;
};
export declare type TCacheBranch = {
url: TPathname;
handles: THandle[];
errorHandlers: TErrorHandlerData[];
renderers: TRendererData[];
autoHead?: boolean;
logger?: Partial<TLogger>;
};
export declare type TCacheRoute = TCacheBranch & {
method: string;
};
export declare type TBranch = Omit<TCacheBranch, 'url'> & {
regexp: RegExp;
autoHead: boolean;
logger: TLogger;
};
export declare type TRoute = Omit<TCacheRoute, 'url'> & {
regexp: RegExp;
autoHead: boolean;
logger: TLogger;
};
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import { OutgoingHttpHeaders } from 'http';

@@ -5,0 +3,0 @@ import { Transform } from 'stream';

@@ -8,2 +8,3 @@ "use strict";

constructor(options) {
var _a, _b;
super();

@@ -13,4 +14,4 @@ for (const key of Object.keys(options)) {

}
this.method = options.method || 'GET';
this.url = options.url || '';
this.method = (_a = options.method) !== null && _a !== void 0 ? _a : 'GET';
this.url = (_b = options.url) !== null && _b !== void 0 ? _b : '';
this.headers = {};

@@ -68,2 +69,3 @@ this.rawHeaders = [];

writeHead(statusCode, statusMessage, headers) {
var _a;
if (statusMessage !== undefined && typeof statusMessage !== 'string') {

@@ -74,3 +76,3 @@ headers = statusMessage;

this.statusCode = statusCode;
this.statusMessage = statusMessage || http_1.STATUS_CODES[statusCode] || 'unknown';
this.statusMessage = (_a = statusMessage !== null && statusMessage !== void 0 ? statusMessage : http_1.STATUS_CODES[statusCode]) !== null && _a !== void 0 ? _a : 'unknown';
if (!headers)

@@ -77,0 +79,0 @@ return;

@@ -0,1 +1,4 @@

import { TBranchData, TErrorHandlerData, TLogger, TRendererData, TRouteData } from '../types';
export declare const PATHNAME_REGEX: RegExp;
export declare const CONTENT_TYPE_REGEX: RegExp;
export declare function validateObject(topic: unknown, name: string, type?: string): void;

@@ -5,2 +8,8 @@ export declare function validateArray(topic: unknown, name: string, type?: string): void;

export declare function validatePathname(topic: unknown, name: string, isWild?: boolean): void;
export declare function validateContentType(topic: unknown, name: string): void;
export declare function validateExists(topic: unknown, name: string): void;
export declare function validateBranch(branch: TBranchData): void;
export declare function validateRoute(route: TRouteData): void;
export declare function validateErrorHandler(errorHandler: TErrorHandlerData): void;
export declare function validateRenderer(renderer: TRendererData): void;
export declare function validateLogger(logger?: Partial<TLogger>): void;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateExists = exports.validatePathname = exports.validateType = exports.validateArray = exports.validateObject = void 0;
exports.validateLogger = exports.validateRenderer = exports.validateErrorHandler = exports.validateRoute = exports.validateBranch = exports.validateExists = exports.validateContentType = exports.validatePathname = exports.validateType = exports.validateArray = exports.validateObject = exports.CONTENT_TYPE_REGEX = exports.PATHNAME_REGEX = void 0;
const extract_1 = require("../router/util/extract");
exports.PATHNAME_REGEX = /^(?:\/:[a-zA-Z_]\w*|\/[\w\-.]*|\/\*{2})+$/;
exports.CONTENT_TYPE_REGEX = /^[a-zA-Z]+\/(?:[a-zA-Z]+|\*)|\*$/;
function validateObject(topic, name, type) {

@@ -41,3 +44,2 @@ if (topic !== undefined) {

exports.validateType = validateType;
const PATHNAME_REGEX = /^(?:\/:[^/: *]+|\/[^/: *]*|\/\*{2})+$/;
function validatePathname(topic, name, isWild = false) {

@@ -52,8 +54,29 @@ if (topic !== undefined) {

}
if (!topic.match(PATHNAME_REGEX)) {
if (!exports.PATHNAME_REGEX.test(topic)) {
throw new Error(`${name} invalid format '${topic}'`);
}
const existing = [];
for (const part of (0, extract_1.getParts)(topic)) {
if (!part.startsWith(':'))
continue;
if (part === ':wild') {
throw new Error(`${name} cannot contain :wild '${topic}'`);
}
if (existing.includes(part)) {
throw new Error(`${name} duplicate ${part} '${topic}'`);
}
existing.push(part);
}
}
}
exports.validatePathname = validatePathname;
function validateContentType(topic, name) {
if (topic !== undefined) {
validateType(topic, name, 'string');
if (!exports.CONTENT_TYPE_REGEX.test(topic)) {
throw new Error(`${name} invalid format '${topic}'`);
}
}
}
exports.validateContentType = validateContentType;
function validateExists(topic, name) {

@@ -65,1 +88,57 @@ if (topic === undefined) {

exports.validateExists = validateExists;
function validateBranch(branch) {
validateExists(branch, 'Branch');
validateObject(branch, 'Branch');
validatePathname(branch.url, 'Branch url');
validateArray(branch.handles, 'Branch handles', 'function');
validateArray(branch.branches, 'Branch handles');
validateArray(branch.routes, 'Branch routes');
validateArray(branch.errorHandlers, 'Branch errorHandlers');
validateArray(branch.renderers, 'Branch renderers');
validateLogger(branch.logger);
validateType(branch.autoHead, 'Branch autoHead', 'boolean');
}
exports.validateBranch = validateBranch;
function validateRoute(route) {
validateExists(route, 'Route');
validateObject(route, 'Route');
validateExists(route.method, 'Route method');
validateType(route.method, 'Route method', 'string');
validatePathname(route.url, 'Route url');
validateArray(route.handles, 'Route handles', 'function');
validateLogger(route.logger);
validateType(route.autoHead, 'Route autoHead', 'boolean');
}
exports.validateRoute = validateRoute;
function validateErrorHandler(errorHandler) {
validateExists(errorHandler, 'Error handler');
validateObject(errorHandler, 'Error handler');
validateExists(errorHandler.contentType, 'Error handler contentType');
validateContentType(errorHandler.contentType, 'Error handler contentType');
validateExists(errorHandler.handle, 'Error handler handle');
validateType(errorHandler.handle, 'Error handler handle', 'function');
}
exports.validateErrorHandler = validateErrorHandler;
function validateRenderer(renderer) {
validateExists(renderer, 'Renderer');
validateObject(renderer, 'Renderer');
validateExists(renderer.contentType, 'Renderer contentType');
validateContentType(renderer.contentType, 'Renderer contentType');
validateExists(renderer.handle, 'Renderer handle');
validateType(renderer.handle, 'Renderer handle', 'function');
}
exports.validateRenderer = validateRenderer;
function validateLogger(logger) {
validateObject(logger, 'Logger');
if (logger !== undefined) {
validateType(logger.error, 'Logger error', 'function');
validateType(logger.warn, 'Logger warn', 'function');
validateType(logger.info, 'Logger info', 'function');
validateType(logger.http, 'Logger http', 'function');
validateType(logger.verbose, 'Logger verbose', 'function');
validateType(logger.debug, 'Logger debug', 'function');
validateType(logger.silly, 'Logger silly', 'function');
validateType(logger.log, 'Logger log', 'function');
}
}
exports.validateLogger = validateLogger;
{
"name": "kequapp",
"version": "0.4.2",
"description": "Versatile, non-intrusive webapp framework",
"version": "0.5.0",
"description": "Non-intrusive Node JavaScript web app framework",
"main": "dist/main.js",

@@ -6,0 +6,0 @@ "types": "dist/main.d.ts",

<img alt="kequapp" src="https://github.com/Kequc/kequapp/blob/0.2-wip/logo.png?raw=true" width="142" height="85" />
Versatile, non-intrusive webapp framework
Non-intrusive Node JavaScript web app framework

@@ -9,5 +9,5 @@ *\ `hek-yü-ap \\*

This framework manages three stages of a request separately. First the route which is broken up into bite sized pieces, then handling errors if they come up, and finally rendering a response to the client. Each step is as non-obtrusive as possible, so that we can focus on creating applications using Node's built in features.
Does the best it can to stay out of the way and leverage Node's built in features. It comes with a great deal of conveniences which makes it easy to structure an application. With regard to modularity, body parsing, testing, handling any request, and returning any response.
Intended to be simple to learn and use. While being powerful and allowing us to interject any time we need.
Intended to be simple, powerful, and allows us to interceed at any time.

@@ -17,11 +17,12 @@ **Features**

* Modern modular framework
* CORS default
* CORS
* Body parsing for multipart requests
* Static file serving
* Handle any HTTP request
* Async await everywhere
* Unit testing tool
* Does not modify Node features or functionality
* Exposes Node features and functionality
* No dependencies <3
# Install
```

@@ -31,24 +32,2 @@ npm i kequapp

# General
#### **handle**
A route is composed of one or more handles which run in sequence. Handles are responsible for all of the heavy lifting and contain most of our application code.
#### **route**
Each route is a self contained collection of handles, these direct the lifecycle of a request at a given url.
#### **branch**
Used for distributing behavior across multiple routes and helping to stay organized during development. We might separate a json api from client facing pages for example, and want different behaviors which are common to either area.
#### **error handler**
An appropriate error handler is invoked whenever a handle throws an exception. They behave much the same as a handle but should not throw an exception.
#### **renderer**
An appropriate renderer is invoked whenever a handle or error handler returns a value apart from `undefined`. These behave much the same as a handle but are always the last step of a request and should deliver a response to the client.
# Hello world!

@@ -60,9 +39,13 @@

import { createServer } from 'http';
import { createApp, createRoute } from 'kequapp';
import { createApp } from 'kequapp';
const app = createApp().add(
createRoute(() => {
return 'Hello world!';
})
);
const app = createApp({
routes: [
{
method: 'GET',
url: '/',
handles: [() => 'Hello world!']
}
]
});

@@ -74,64 +57,118 @@ createServer(app).listen(4000, () => {

This example responds to all `'GET'`, and `'HEAD'` requests made to the base of our application at `'/'`. Otherwise a `404` not found error will be thrown. The reason this responds to requests at `'/'` is that is the default url for new routes.
This example responds to all `'GET'`, and `'HEAD'` requests made to `'/'` otherwise a `404 Not Found` error will be thrown. The framework comes with a built-in error handler and some renderers. We will look at how to create our own, but for now we don't need to worry about it.
The defaults are the same as writing the following:
# # createApp()
```javascript
createRoute('GET', '/', () => {
return 'Hello world!';
});
import { createApp } from 'kequapp';
```
The framework comes with a built-in error handler and some renderers. We will look at how to create our own, but for now we don't need to worry about it.
This prepares our application for use as the event handler in Node's `createServer()` method. It is otherwise identical to the `createBranch()` method.
# # createHandle()
All methods [`createBranch()`](#-createbranch), [`createRoute()`](#-createroute), [`createHandle()`](#-createhandle), [`createErrorHandler()`](#-createerrorhandler), [`createRenderer()`](#-createrenderer) described below are useful for building elements that exist outside of scope. For example in another file. This provides types if we are using TypeScript.
# # createBranch()
```javascript
import { createHandle } from 'kequapp';
import { createBranch } from 'kequapp';
```
```
# createHandle(handle: Handle): Handle;
```
| key | description | default |
| ---- | ---- | ---- |
| **url** | *Pathname* | `'/'` |
| **handles** | *Sequence* | `[]` |
| **logger** | *Logger* | `console` |
| **autoHead** | *HEAD request* | `true` |
| **routes** | *Routes* | `[]` |
| **branches** | *Branches* | `[]` |
| **errorHandlers** | *Error handlers* | `[]` |
| **renderers** | *Renderers* | `[]` |
This is useful for building handles that exist outside of any scope, for example in another file. This provides types if we are using TypeScript.
A branch of the application will distribute the given options, handles, error handlers, and renderers through a section of branches and routes.
```javascript
// createHandle
// createBranch
const json = createHandle(({ res }) => {
res.setHeader('Content-Type', 'application/json');
createBranch({
branches: [
{
url: '/api/users',
handles: [json],
routes: [
{
method: 'GET',
url: '/',
handles: [() => ({ result: [] })]
},
{
method: 'GET',
url: '/:id',
handles: [({ params }) => ({ userId: params.id })]
}
]
}
],
routes: [
{
method: 'GET',
url: '/admin/dashboard',
handles: [loggedIn, ({ context }) => `Hello admin ${context.auth}`]
}
]
});
```
const loggedIn = createHandle(({ req, context }) => {
if (req.headers.authorization !== 'mike') {
throw Ex.Unauthorized();
}
Three routes are defined in the example and therefore our endpoints are the following:
context.auth = req.headers.authorization;
});
```
GET /api/users
GET /api/users/:id
GET /admin/dashboard
```
In these examples the `json` handle sets `'Content-Type'` to `'application/json'`, and the `loggedIn` handle checks for an `authorization` header from the client.
We can define an `'/api'` branch and an `'/admin'` branch, giving us the same result in a more verbose way.
Handles can be asyncronous.
# # createApp()
```javascript
import { createApp } from 'kequapp';
```
// createBranch
createBranch({
branches: [
{
url: '/api',
handles: [json],
branches: [
{
url: '/users',
routes: [
{
method: 'GET',
url: '/',
handles: [() => ({ result: [] })]
},
{
method: 'GET',
url: '/:id',
handles: [({ params }) => ({ userId: params.id })]
}
]
}
]
},
{
url: '/admin',
handles: [loggedIn],
routes: [
{
method: 'GET',
url: '/dashboard',
handles: [({ context }) => `Hello admin ${context.auth}`]
}
]
}
]
});
```
# createApp(...handles: Handle[]): Branch;
```
This creates a branch but it is also the base of our application. Any handles that are specified here will be used with all routes. It is meant to be passed as the event handler into Node's `createServer` method.
All routes and branches can be added in any order, they are rearranged and organized by the framework based on specificity.
# Modules
The following modules [`createRoute()`](#-createroute), [`createBranch()`](#-createbranch), [`createConfig()`](#-createconfig), [`createErrorHandler()`](#-createerrorhandler), and [`createRouter()`](#-createrouter) are added the same way to a branch or the base of the application.
All can be added in any order, they are rearranged and organized by the framework based on specificity.
```

@@ -155,134 +192,49 @@ '/icecream'

```
# createRoute(method: string, url: Pathname, ...handles: Handle[]): Route;
# createRoute(url: Pathname, ...handles: Handle[]): Route;
# createRoute(method: string, ...handles: Handle[]): Route;
# createRoute(...handles: Handle[]): Route;
```
| key | description | default |
| ---- | ---- | ---- |
| **method \*** | *Method* | |
| **url \*** | *Pathname* | |
| **handles** | *Sequence* | `[]` |
| **logger** | *Logger* | `console` |
| **autoHead** | *HEAD request* | `true` |
A route may specify a method (`'GET'`, `'POST'`, etc.) and url, followed by any number of handles. The url is a pathname that the route should respond to.
A route must specify a `method` (`'GET'`, `'POST'`, etc.) and a `url`. The `url` is a pathname that the route should respond to and must always start with `'/'`.
When provided the url must always start with `'/'`.
```javascript
// createRoute
createRoute('POST', '/admin/users', loggedIn, () => {
// do something here
return `User created!`;
createRoute({
method: 'POST',
url: '/admin/users',
handles: [loggedIn, () => 'User created!']
});
```
This example has two handles. One which we defined earlier called `loggedIn` and a second that returns a value that will be sent to the renderer.
This example has two handles. One called `loggedIn()`, then a second that returns a value which is therefore delivered to a renderer.
# # createBranch()
# # createHandle()
```javascript
import { createBranch } from 'kequapp';
import { createHandle } from 'kequapp';
```
```
# createBranch(url: Pathname, ...handles: Handle[]): Branch;
# createBranch(...handles: Handle[]): Branch;
```
A simple wrapper for a handle the purpose of which is to provide types.
A branch of the application will cause routes to adopt the given url and handles.
Every branch of our application exposes `add()`. This is an important function used to extend the branch with functionality. Usually this will be a route, another branch, an error handler, or renderer.
```javascript
// createBranch
// createHandle
createBranch().add(
createBranch('/api', json).add(
createBranch('/users').add(
createRoute(() => {
return { result: [] };
}),
createRoute('/:id', ({ params }) => {
return { userId: params.id };
})
)
),
createBranch('/admin', loggedIn).add(
createRoute('/dashboard', ({ context }) => {
return `Hello admin ${context.auth}!`;
})
)
);
```
const json = createHandle(({ res }) => {
res.setHeader('Content-Type', 'application/json');
});
Routes beginning with `'/api'` are returning `'application/json'` formatted responses, we can see those routes are returning javascript objects.
Routes beginning with `'/admin'` require the user to be logged in. Three routes are defined in the example and therefore our endpoints are the following:
```
GET /api/users
GET /api/users/:id
GET /admin/dashboard
```
The example is verbose. We can omit the `'/api'` branch because it only exposes one branch, and the `'/admin'` branch because it only exposes one route, leaving us the same result with less code.
```javascript
// createBranch
createBranch().add(
createBranch('/api/users', json).add(
createRoute(() => {
return { result: [] };
}),
createRoute('/:id', ({ params }) => {
return { userId: params.id };
})
),
createRoute('/admin/dashboard', loggedIn, ({ context }) => {
return `Hello admin ${context.auth}!`;
})
);
```
# # createConfig()
```javascript
import { createConfig } from 'kequapp';
```
```
# createConfig(url: Pathname, config: Config): Branch;
# createConfig(config: Config): Branch;
# createConfig(): Branch;
```
The options available are only useful for a limited number of features. If provided the url most likely should be wild (ending in `'/**'`) in order to capture the most amount of our routes.
Any config will override all options from lesser priority configs.
```javascript
// createConfig
createConfig({
logger: false,
autoHead: false
const loggedIn = createHandle(({ req, context }) => {
if (req.headers.authorization !== 'mike') {
throw Ex.Unauthorized();
}
context.auth = req.headers.authorization;
});
```
The following options are available:
In these examples the `json()` handle sets the `'Content-Type'` header to `'application/json'`, and the `loggedIn()` handle checks for an `'authorization'` header from the client. Handles can be asyncronous and always run in sequence.
| name | type | default |
| ---- | ---- | ---- |
| **logger** | *Logger / boolean* | `console` |
| **autoHead** | *boolean* | `true` |
#### **`logger`**
If a boolean is provided the app will use either the default logger (`console`) if `true`, or a silent logger. The silent logger ignores all logging inside the application.
Alternatively a custom logger can be set. It must be an object containing methods for `debug`, `log`, `warn`, and `error`.
#### **`autoHead`**
Disabling `autoHead` will mean that the application doesn't automatically use `GET` routes when `HEAD` is requested as described in [more detail](#head-requests) later.
# # createErrorHandler()

@@ -294,22 +246,19 @@

```
# createErrorHandler(contentType: string, url: Pathname, handle: Handle): ErrorHandler;
# createErrorHandler(url: Pathname, handle: Handle): ErrorHandler;
# createErrorHandler(contentType: string, handle: Handle): ErrorHandler;
# createErrorHandler(handle: Handle): ErrorHandler;
```
| key | description | default |
| ---- | ---- | ---- |
| **contentType \*** | *Content type* | |
| **handle \*** | *Handler* | |
If no content type is provided the error handler will be used for all content types.
An appropriate error handler is invoked whenever a handle throws an exception.
Set the url if you only want it to be used for specific routes. For example `/api/**` would mean it is only used for routes in that location. Usually this isn't needed because it is easier to add the error handler to the relevant branch instead.
Error handlers turn an exception into useful information that should be sent to the client. We may return a value to invoke a renderer or finalize the response ourselves directly. The default built-in error handler structures a json formatted response with helpful information for debugging.
Error handlers turn an exception into useful information that should be sent to the client. We may return a value to invoke a renderer or finalize the response directly. The default built-in structures a json formatted response with helpful information for debugging.
The `'Content-Type'` header set by our application determines the correct error handler to use. Error handlers are sorted by the framework in favor of content type and hierarchical specificity. The following is a very simple error handler for text based responses.
The following is a very simple text error handler.
```javascript
// createErrorHandler
createErrorHandler('text/*', (ex, { res }) => {
return `${ex.statusCode} ${ex.message}`;
createErrorHandler({
contentType: 'text/*',
handler: (ex, { res }) => `${ex.statusCode} ${ex.message}`
});

@@ -320,6 +269,4 @@ ```

Error handlers are sorted by the framework in favor of specificity.
For a good example of how to write an error handler see this repo's [`/src/built-in`](https://github.com/Kequc/kequapp/tree/main/src/built-in) directory.
For a good example of how to write error handlers see this repo's [`/src/built-in`](https://github.com/Kequc/kequapp/tree/main/src/built-in) directory.
# # createRenderer()

@@ -331,41 +278,36 @@

```
# createRenderer(contentType: string, url: Pathname, handle: Handle): ErrorHandler;
# createRenderer(url: Pathname, handle: Handle): ErrorHandler;
# createRenderer(contentType: string, handle: Handle): ErrorHandler;
# createRenderer(handle: Handle): ErrorHandler;
```
| key | description | default |
| ---- | ---- | ---- |
| **contentType \*** | *Content type* | |
| **handle \*** | *Handler* | |
If no content type is provided the renderer will be used for all content types. The url is used in the same way as it is in error handlers.
An appropriate renderer is invoked whenever a handle returns a value apart from `undefined`.
Renderers are responsible for finalizing the response to the client. It is the last stage of a request and otherwise an empty `body` will be delivered.
Renderers are responsible for finalizing the response to the client. It is the last stage of a request and without one an empty `body` will be delivered. There are default renderers that come built-in for both `'text/*'` and `'application/json'`, however these can be overridden by defining our own.
There are default renderers that come built-in for both `'text/*'` and `'application/json'`, however these can be overridden by defining our own.
The `'Content-Type'` header set by our application determines the correct renderer to use. Error handlers are sorted by the framework in favor of content type and hierarchical specificity. The following is a simple example of what an html renderer might look like.
The following is a simple example of what an html renderer might look like.
```javascript
// createRenderer
createRenderer('text/html', (payload, { res }) => {
const html = myMarkupRenderer(payload);
createRenderer({
contentType: 'text/html',
handle: (payload, { res }) => {
const html = myMarkupRenderer(payload);
// finalize response
res.end(html);
// finalize response
res.end(html);
}
});
```
Renderers are sorted by the framework in favor of specificity.
For good examples of how to write a renderer see this repo's [`/src/built-in`](https://github.com/Kequc/kequapp/tree/main/src/built-in) directory.
For good examples of how to write renderers see this repo's [`/src/built-in`](https://github.com/Kequc/kequapp/tree/main/src/built-in) directory.
# How to respond to a request
# Responding to a request
Any handle may terminate a request one of three ways:
| action | result |
| ---- | ---- |
| **Return a value** | A renderer is invoked. |
| **Throw an error** | An error handler is invoked. |
| **Finalize the response** | ... |
1. **Throw an error** - An error handler is invoked.
2. **Return a value** - A renderer is invoked.
3. **Finalize the response**

@@ -385,3 +327,3 @@ Finalizing a response is for cases where we need the most control. It allows us to terminate the response any way we want without invoking a renderer.

// finalize response ignore remaining handles
// finalize response to ignore remaining handles
res.end();

@@ -391,17 +333,21 @@ }

createRoute('/api/users', authenticated, json, () => {
// returning a value invokes a renderer
return {
users: [{ name: 'April' }, { name: 'Leo' }]
};
createRoute({
method: 'GET',
url: '/api/users',
handles: [authenticated, json, () => {
// return a value to invoke a renderer
return {
users: [{ name: 'April' }, { name: 'Leo' }]
};
}]
});
```
In this example if the client did not provide an `authorization` header, the `authenticated` handle will finalize the response. This terminates the request and skips all remaining handles. Otherwise the `json` handle sets `'Content-Type'` of the response to `'application/json'`.
In this example if the client did not provide an `'authorization'` header, the `authenticated()` handle will finalize the response. This terminates the request and skips all remaining handles. Otherwise the `json()` handle sets the `'Content-Type'` header of the response to `'application/json'`.
The last remaining handle returns a value. This invokes a renderer best matching the `'Content-Type'` of the response, in this example a renderer matching `'application/json'` will be used. The renderer will finalize the response to the client.
The last remaining handle returns a value. This invokes a renderer best matching the `'Content-Type'` header, in this example a renderer matching `'application/json'` will be used. The appropriate renderer will finalize a response to the client.
# Bundle
# Bundle properties
Properties such as `req`, `res`, and `context` are found throughout the examples above. These properties are generated for every request and are available in every route, renderer, and error handler.
Properties such as `req`, `res`, and `context` are found throughout the examples above. These properties are generated for every request and available in every route, renderer, and error handler.

@@ -420,11 +366,15 @@ #### **`req`**

Useful for examining the querystring for example by digging into it's [`searchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams).
Useful for examining the querystring for example by digging into [`searchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams).
```javascript
createRoute('/hotels', ({ url }) => {
const page = url.searchParams.get('page');
const categories = url.searchParams.getAll('categories');
createRoute({
method: 'GET',
url: '/hotels',
handles: [({ url }) => {
const page = url.searchParams.get('page');
const categories = url.searchParams.getAll('categories');
// page ~= '2'
// categories ~= ['ac', 'hottub']
// page ~= '2'
// categories ~= ['ac', 'hottub']
}]
});

@@ -445,3 +395,3 @@ ```

When defining a route we can specify parameters to extract by prefixing a colon `'/:'` character in the url. If we specify a route such as `'/users/:userId'` we will have a parameter called `'userId'`. Use a double asterix `'/**'` to accept anything for the remainder of the url.
When defining a route we can specify parameters to extract by prefixing a colon `':'` character in the url. If we specify a route such as `'/users/:userId'` we will have a `params` item called `'userId'`. Use a double asterix `'/**'` to accept anything for the remainder of the url, we will have a `params` item called `'wild'`.

@@ -454,3 +404,3 @@ Param values are always a string.

#### **`getBody()`**
#### **`getBody`**

@@ -468,10 +418,14 @@ This method can be used in many ways so the next section will look at it in detail.

createRoute('POST', '/users', async ({ getBody }) => {
const body = await getBody();
createRoute({
method: 'POST',
url: '/users',
handles: [async ({ getBody }) => {
const body = await getBody();
// body ~= {
// name: 'April'
// }
// body ~= {
// name: 'April'
// }
return `User creation ${body.name}!`;
return `User creation ${body.name}!`;
}]
});

@@ -482,5 +436,5 @@ ```

* **`multipart`**
#### **`multipart`**
Causes the function to return both `body` and `files`. If the client didn't send any files, or it wasn't a multipart request the second parameter will be an empty array.
Causes the method to return both `body` and `files`. If the client didn't send any files, or it wasn't a multipart request the second parameter will be an empty array.

@@ -490,26 +444,30 @@ ```javascript

createRoute('POST', '/users', async ({ getBody }) => {
const [body, files] = await getBody({ multipart: true });
createRoute({
method: 'POST',
url: '/users',
handles: [async ({ getBody }) => {
const [body, files] = await getBody({ multipart: true });
// body ~= {
// name: 'April'
// }
// files ~= [{
// headers: {
// 'content-disposition': 'form-data; name="avatar" filename="my-cat.png"',
// 'content-type': 'image/png;'
// },
// mime: 'image/png',
// name: 'avatar',
// filename: 'my-cat.png',
// data: Buffer <...>
// }]
// body ~= {
// name: 'April'
// }
// files ~= [{
// headers: {
// 'content-disposition': 'form-data; name="avatar" filename="my-cat.png"',
// 'content-type': 'image/png;'
// },
// contentType: 'image/png',
// name: 'avatar',
// filename: 'my-cat.png',
// data: Buffer <...>
// }]
return `User creation ${body.name}!`;
return `User creation ${body.name}!`;
}]
});
```
* **`raw`**
#### **`raw`**
The body is processed as minimally as possible and returns a single buffer. When combined with `multipart`, the body is parsed into an array of separate buffers with their respective headers.
Causes the body to be processed as minimally as possible and return a single buffer. When combined with `multipart`, the body is parsed into an array of separate buffers with their respective headers.

@@ -519,23 +477,27 @@ ```javascript

createRoute('POST', '/users', async ({ getBody }) => {
const parts = await getBody({ raw: true, multipart: true });
createRoute({
method: 'POST',
url: '/users',
handles: [async ({ getBody }) => {
const parts = await getBody({ raw: true, multipart: true });
// parts ~= [{
// headers: {
// 'content-disposition': 'form-data; name="name"'
// },
// data: Buffer <...>
// }, {
// headers: {
// 'content-disposition': 'form-data; name="avatar" filename="my-cat.png"',
// 'content-type': 'image/png;'
// },
// data: Buffer <...>
// }]
// parts ~= [{
// headers: {
// 'content-disposition': 'form-data; name="name"'
// },
// data: Buffer <...>
// }, {
// headers: {
// 'content-disposition': 'form-data; name="avatar" filename="my-cat.png"',
// 'content-type': 'image/png;'
// },
// data: Buffer <...>
// }]
return `User creation ${parts[0].data.toString()}!`;
return `User creation ${parts[0].data.toString()}!`;
}]
});
```
* **`skipNormalize`**
#### **`skipNormalize`**

@@ -546,3 +508,3 @@ By default the data received is pushed through some body normalization. This is so that the body we receive is in a format we expect and is therefore easier to work with.

* **`arrays`**
#### **`arrays`**

@@ -556,32 +518,36 @@ The provided list of fields are converted into arrays.

createRoute('POST', '/users', async ({ getBody }) => {
const body = await getBody({
arrays: ['ownedPets']
});
createRoute({
method: 'POST',
url: '/users',
handles: [async ({ getBody }) => {
const body = await getBody({
arrays: ['ownedPets']
});
// body ~= {
// ownedPets: ['cat'],
// age: '23',
// name: 'April'
// }
// body ~= {
// ownedPets: ['cat'],
// age: '23',
// name: 'April'
// }
}]
});
```
* **`required`**
#### **`required`**
The provided list of fields are not `null` or `undefined`. It's a quick way to throw a `422` unprocessable entity error. These fields might still be empty, but at least something was sent and we know we can operate on them. When a `required` field is also an `arrays` field the array is sure to have at least one value.
The provided list of fields are not `null` or `undefined`. It's a quick way to throw a `422 Unprocessable Entity` error. These fields might still be empty, but at least something was sent and we know we can operate on them. When a `required` field is also an `arrays` field the array is sure to have at least one value.
* **`numbers`**
#### **`numbers`**
The provided list of fields will throw a `422` unprocessable entity error if any value is provided which parses into `NaN`. Otherwise they are converted into numbers.
The provided list of fields will throw a `422 Unprocessable Entity` error if any value is provided which parses into `NaN`. Otherwise they are converted into numbers.
When a `numbers` field is also an `arrays` field the array is all numbers.
* **`booleans`**
#### **`booleans`**
The provided list of fields are converted into `false` if the value is falsy, `'0'`, or `'false'`, otherwise `true`. When a `booleans` field is also an `arrays` field the array is all booleans. When a `booleans` field is also a `numbers` field the value is first converted to a number and then to a boolean this is not recommended.
* **`validate`**
#### **`validate`**
After normalization, this method further ensures the validity of the data. Returning anything from this function throws a `422` unprocessable entity error.
After normalization, this method further ensures the validity of the data. Returning anything throws a `422 Unprocessable Entity` error.

@@ -593,67 +559,47 @@ ```javascript

ownedPets: string[];
age?: number;
age: number;
name: string;
};
createRoute('POST', '/users', async ({ getBody }) => {
const body = await getBody<TBody>({
arrays: ['ownedPets'],
numbers: ['age'],
validate (result) {
if (result.ownedPets.length > 99) {
return 'Too many pets';
createRoute({
method: 'POST',
url: '/users',
handles: [async ({ getBody }) => {
const body = await getBody<TBody>({
arrays: ['ownedPets'],
required: ['age', 'name']
numbers: ['age'],
validate (result) {
if (result.ownedPets.length > 99) {
return 'Too many pets';
}
if (result.name.length < 3) {
return 'Name is too short';
}
}
}
});
});
// body ~= {
// ownedPets: ['Maggie', 'Ralph'],
// age: 23
// }
// body ~= {
// ownedPets: ['Maggie', 'Ralph'],
// age: 23,
// name: 'April'
// }
}]
});
```
We know it is safe to use `result.ownedPets.length` in this example because it is listed as an `arrays` field and therefore certain to be an array.
We know it is safe to use `result.ownedPets.length` in this example because it is listed as an `arrays` field and therefore certain to be an array. `result.name` is also safe to use because it is listed as a `required` field and therefore certain to exist.
* **`postProcess`**
#### **`maxPayloadSize`**
After normalization is complete and `validate` has passed, this method further formats the response in any way we need. The returned value will be the final result.
The max payload size is `1e6` (approximately 1mb) by default. If this payload size is exceeded by the client the request will be terminated saving our application both memory and bandwidth. If we are absolutely sure we want to receive a payload of any size then a value of `Infinity` is accepted.
```javascript
// postProcess
# Logger
type TBody = {
ownedPets: string[];
age: number;
name: string;
};
One of the options provided to `createBranch()` is a `logger` parameter. The default logger for the application is a simple object with methods for `error`, `warn`, `info`, `http`, `verbose`, `debug`, `silly`, and `log`. Each mapping roughly to console.
createRoute('POST', '/users', async ({ getBody }) => {
const body = await getBody<TBody>({
arrays: ['ownedPets'],
required: ['age', 'name'],
numbers: ['age'],
postProcess (result) {
return {
...result,
name: result.name.trim()
};
}
});
Overriding this logger requires an object with some or all of the same methods.
// body ~= {
// ownedPets: ['Maggie', 'Ralph'],
// age: 23,
// name: 'April'
// }
});
```
# CORS and OPTIONS requests
We know it is safe to call `result.name.trim()` in this example because it is listed as a `required` field and therefore certain to exist.
* **`maxPayloadSize`**
The max payload size is `1e6` (approximately 1mb) by default. If this payload size is exceeded by the client the request will be terminated saving our application both memory and bandwidth. If we are absolutely sure we want to receive a payload of any size then a value of `Infinity` is accepted.
# CORS and `OPTIONS` requests
CORS behavior is managed by headers as shaped by handles. The framework will automatically add default headers we can use for basic support.

@@ -666,11 +612,14 @@

createApp().add(
createRoute('OPTIONS', '/**')
);
createApp({
routes: [
{
method: 'OPTIONS',
url: '/**'
}
]
});
```
The framework automatically attaches four additional headers to `OPTIONS` responses.
The framework automatically attaches four additional headers to `OPTIONS` responses. `'Valid'` and `'Access-Control-Allow-Methods'` will correctly identify all methods available at the requested url. `'Access-Control-Allow-Headers'` will return headers that the client specified. `'Content-Length'` will be 0.
`'Valid'` and `'Access-Control-Allow-Methods'` will correctly identify all methods available at the requested url. `'Access-Control-Allow-Headers'` will return headers that the client specified. `'Content-Length'` will be 0.
In addition the default response code for `OPTIONS` requests is `204`. To change any of this behavior or add more headers to `OPTIONS` responses we use a handle.

@@ -681,8 +630,14 @@

createApp().add(
createRoute('OPTIONS', '/**', ({ res }) => {
res.setHeader('Access-Control-Max-Age', 86400);
res.setHeader('Vary', 'Access-Control-Request-Headers');
})
);
createApp({
routes: [
{
method: 'OPTIONS',
url: '/**',
handles: [({ res }) => {
res.setHeader('Access-Control-Max-Age', 86400);
res.setHeader('Vary', 'Access-Control-Request-Headers');
}]
}
]
});
```

@@ -692,3 +647,3 @@

The simplest place to override `'Access-Control-Allow-Origin'` is at the base of the application, but we may adjust this as needed. The `createApp` method accepts handles and is a convenient place to set global headers.
The simplest place to override `'Access-Control-Allow-Origin'` is at the base of the application but we may adjust this as needed. The `createApp()` method accepts handles and is a convenient place to set global headers.

@@ -704,3 +659,5 @@ ```javascript

createApp(strictCors);
createApp({
handles: [strictCors]
});
```

@@ -712,13 +669,19 @@

# `HEAD` requests
# HEAD requests
By default if a `HEAD` request has no matching route our application will use a matching `GET` route in it's place. Therefore it is important to keep in mind that `HEAD` requests follow the same flow as `GET` requests in our application.
Occasionally we may need to differentiate between the two as it is generally understood that a `HEAD` request does not modify data. In this case looking at the value of `req.method` can be useful.
```javascript
// HEAD
createRoute('GET', '/api/users', ({ req }) => {
if (req.method === 'HEAD') {
// head request
}
createRoute({
method: 'GET',
url: '/api/users',
handles: [({ req }) => {
if (req.method === 'HEAD') {
// head request
}
}]
});

@@ -729,8 +692,2 @@ ```

Occasionally we may need to differentiate between the two as it is generally understood that a `HEAD` request does not modify data. In this case looking at the value of `req.method` can be useful.
# Helpers
The following helper tools [`staticDirectory()`](#-staticdirectory), [`staticFile()`](#-staticfile), and [`sendFile()`](#-sendfile) are included to make file delivery easier.
# # staticDirectory()

@@ -742,10 +699,11 @@

```
# staticDirectory(url: Pathname, options: Options): Route;
# staticDirectory(options: Options): Route;
# staticDirectory(url: Pathname): Route;
# staticDirectory(): Route;
```
| key | description | default |
| ---- | ---- | ---- |
| **url** | *Pathname* | `'/**'` |
| **dir** | *Local* | `'/public'` |
| **exclude** | *Exclusions* | `[]` |
| **contentTypes** | *Additions* | `{}` |
| **handles** | *Sequence* | `[]` |
Pair a `url` and a set of `options` with a directory.
Pairs a `url` with a static directory.

@@ -755,16 +713,19 @@ ```javascript

app.add(
staticDirectory('/assets/**', {
dir: '/my-assets-dir',
exclude: ['/my-assets-dir/private'],
mime: {
'.3gp': 'audio/3gpp'
}
})
createApp({
routes: [
staticDirectory({
url: '/assets/**',
dir: '/my-assets-dir',
exclude: ['/my-assets-dir/private'],
contentTypes: {
'.3gp': 'audio/3gpp'
}
})
]
);
```
The `url` must be wild if provided, meaning it ends in `'/**'` capturing all possible paths at the given location.
The `url` must end with `'/**'` capturing all possible paths.
If no `dir` is specified then `'/public'` is used by default. Exclusions can be provided if we want to ignore some files or directories using `exclude`. A `'Content-Type'` header is guessed based on every asset's file extension. If there are assets in the directory with unusual file extensions then additional `mime` types can be added.
Exclusions can be provided if we want to ignore some files or directories using `exclude`. A `'Content-Type'` header is guessed based on every asset's file extension. If there are assets in the directory with unusual file extensions then additional `contentTypes` may be provided.

@@ -777,10 +738,10 @@ # # staticFile()

```
# staticFile(url: Pathname, asset: Pathname, mime: string): Route;
# staticFile(url: Pathname, asset: Pathname): Route;
# staticFile(asset: Pathname, mime: string): Route;
# staticFile(asset: Pathname): Route;
```
| key | description | default |
| ---- | ---- | ---- |
| **asset \*** | *Local* | |
| **url** | *Pathname* | `'/'` |
| **contentType** | *Content type* | |
| **handles** | *Sequence* | `[]` |
Pair a `url` and an `asset`. This asset will be delivered to the client.
Pairs a `url` and a local file. This asset will be delivered to the client.

@@ -790,8 +751,13 @@ ```javascript

app.add(
staticFile('/db.json', '/db/my-db.json')
createApp({
routes: [
staticFile({
url: '/db.json',
asset: '/db/my-db.json'
})
]
);
```
If `mime` is not provided a `'Content-Type'` header is guessed from the file extension.
If `contentType` is not provided a `'Content-Type'` header will be guessed from the file extension.

@@ -804,11 +770,6 @@ # # sendFile()

```
# sendFile(req: Req, res: Res, asset: Pathname, mime: string): void;
# sendFile(req: Req, res: Res, asset: Pathname): void;
```
Sends a file and finalizes the response.
Send a file and finalize the response.
This is asyncronous and must be awaited otherwise the application might get confused as it continues processing the request unexpectedly.
This is asyncronous and must be awaited otherwise the application might get confused as it continues processing the request.
The following is the same as the `staticFile()` example above.

@@ -819,17 +780,19 @@

app.add(
createRoute('/db.json', async ({ req, res }) => {
await sendFile(req, res, '/db/my-db.json');
})
createApp({
routes: [
{
method: 'GET',
url: '/db.json'
handles: [async ({ req, res }) => {
await sendFile(req, res, '/db/my-db.json');
}],
}
]
);
```
If `mime` is not provided a `'Content-Type'` header is guessed from the file extension.
A fourth parameter may be provided defining a `'Content-Type'`, this header is otherwise guessed from the file extension.
# Utilities
# # Ex()
The following utilities [`Ex()`](#-ex), and [`inject()`](#-inject) will likely be used throughout our application. These are very useful for building a well working app.
# # Ex.()
```javascript

@@ -839,28 +802,21 @@ import { Ex } from 'kequapp';

```
# Ex.<NAME>(message: string, ...info: unknown[]): new Error;
# Ex.<NAME>(message: string): new Error;
# Ex.<NAME>(): new Error;
An unhandled exception from our application results in a `500 Internal Server Error`. If we would like an error with a different status code there is a helper tool for that.
# Ex.StatusCode(statusCode: number, message: string, ...info: unknown[]): new Error;
# Ex.StatusCode(statusCode: number, message: string): new Error;
# Ex.StatusCode(statusCode: number): new Error;
```
An unhandled exception from our application results in a `500` internal server error. If we would like an error with a different status code there is a helper tool.
```javascript
// Ex
createRoute('/throw-error', () => {
throw Ex.NotFound();
throw Ex.NotFound('Custom message', { extra: 'info' });
// same as
throw Ex.StatusCode(404);
throw Ex.StatusCode(404, 'Custom message', { extra: 'info' });
createRoute({
method: 'GET',
url: '/throw-error',
handles: [() => {
throw Ex.NotFound();
throw Ex.NotFound('Custom message', { extra: 'info' });
// same as
throw Ex.StatusCode(404);
throw Ex.StatusCode(404, 'Custom message', { extra: 'info' });
}]
});
```
This makes it easy to utilize any status code `400` and above. These methods create errors with correct stacktraces there is no reason to use `new`.
This makes it easy to utilize any status code `400` and above. These methods create errors with correct stacktraces we can throw directly without the use of `new`.

@@ -873,16 +829,10 @@ # # inject()

```
# inject(app: Kequapp, options: {}): { req, res, getResponse };
```
We may unit test our application without starting a server by using the `inject()` tool. The first parameter is our app, then options used to populate the request.
The returned `req` value is a simulation of Node's built-in [`ClientRequest`](https://nodejs.org/api/http.html#class-httpclientrequest) object and is a `Transform` stream. The returned `res` value is a simulation of Node's built-in [`ServerResponse`](https://nodejs.org/api/http.html#class-httpserverresponse) object and is also a `Transform` stream.
The returned `req` value is a simulation of Node's built-in [`ClientRequest`](https://nodejs.org/api/http.html#class-httpclientrequest) object and is a `Transform` stream. The returned `res` value is a simulation of Node's built-in [`ServerResponse`](https://nodejs.org/api/http.html#class-httpserverresponse) object and is also a `Transform` stream. The returned `getResponse()` tool waits for our application to finish, and then parses the response. It is very similar to `getBody()` as described earlier. We could inspect what our application is doing using the `req` and `res` objects in realtime instead if that's what we want.
The returned `getResponse()` tool waits for our application to finish, and then parses the response. We could inspect what our application is doing using the `req` and `res` objects in realtime instead if that's what we wanted to do.
```javascript
// inject
it('reads the authorization header', async function () {
it('reads the authorization header', async () => {
const { getResponse, res } = inject(app, {

@@ -889,0 +839,0 @@ url: '/admin/dashboard',

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc