Comparing version 0.2.15 to 0.3.0
@@ -1,2 +0,2 @@ | ||
import { TBodyJson, TBodyOptions } from '../types'; | ||
export default function normalizeBody(body: TBodyJson, options: TBodyOptions): TBodyJson; | ||
import { TBodyJson, TGetBodyOptions } from '../types'; | ||
export default function normalizeBody(body: TBodyJson, options: TGetBodyOptions): TBodyJson; |
/// <reference types="node" /> | ||
import { ServerResponse } from 'http'; | ||
export default function sendFile(res: ServerResponse, asset: string, mime?: string): Promise<void>; | ||
import { TPathname } from '../../types'; | ||
export default function sendFile(res: ServerResponse, asset: TPathname, mime?: string): Promise<void>; |
@@ -8,3 +8,4 @@ import { IKequapp } from './types'; | ||
export { default as sendFile } from './built-in/helpers/send-file'; | ||
export { default as staticFiles } from './built-in/helpers/static-files'; | ||
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'; | ||
@@ -11,0 +12,0 @@ export { default as inject } from './util/tools/inject'; |
@@ -20,4 +20,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.createApp = exports.inject = exports.Ex = exports.staticFiles = exports.sendFile = exports.createRoute = exports.createRenderer = exports.createHandle = exports.createErrorHandler = exports.createBranch = void 0; | ||
const create_get_body_1 = __importDefault(require("./body/create-get-body")); | ||
exports.createApp = exports.inject = exports.Ex = exports.staticDirectory = exports.staticFile = exports.sendFile = exports.createRoute = exports.createRenderer = exports.createHandle = exports.createErrorHandler = exports.createBranch = void 0; | ||
const error_handler_1 = __importDefault(require("./built-in/error-handler")); | ||
@@ -43,4 +42,6 @@ const json_renderer_1 = __importDefault(require("./built-in/json-renderer")); | ||
Object.defineProperty(exports, "sendFile", { enumerable: true, get: function () { return __importDefault(send_file_1).default; } }); | ||
var static_files_1 = require("./built-in/helpers/static-files"); | ||
Object.defineProperty(exports, "staticFiles", { enumerable: true, get: function () { return __importDefault(static_files_1).default; } }); | ||
var static_file_1 = require("./built-in/helpers/static-file"); | ||
Object.defineProperty(exports, "staticFile", { enumerable: true, get: function () { return __importDefault(static_file_1).default; } }); | ||
var static_directory_1 = require("./built-in/helpers/static-directory"); | ||
Object.defineProperty(exports, "staticDirectory", { enumerable: true, get: function () { return __importDefault(static_directory_1).default; } }); | ||
var ex_1 = require("./util/tools/ex"); | ||
@@ -52,3 +53,3 @@ Object.defineProperty(exports, "Ex", { enumerable: true, get: function () { return __importDefault(ex_1).default; } }); | ||
const DEFAULT_CONFIG = { | ||
silent: false, | ||
logger: console, | ||
autoHead: true | ||
@@ -60,16 +61,10 @@ }; | ||
const branch = (0, create_branch_1.default)(...handles).add(error_handler_1.default, json_renderer_1.default, text_renderer_1.default); | ||
let router; | ||
let router = (0, create_router_1.default)(config, branch()); | ||
validateConfig(config); | ||
function app(req, res) { | ||
if (!router) | ||
router = (0, create_router_1.default)(branch()); | ||
(0, request_processor_1.default)(router, config, { | ||
req, | ||
res, | ||
url: new URL(req.url || '/', `${req.headers.protocol}://${req.headers.host}`), | ||
getBody: (0, create_get_body_1.default)(req) | ||
}); | ||
(0, request_processor_1.default)(router, config, req, res); | ||
} | ||
function add(...params) { | ||
branch.add(...params); | ||
router = (0, create_router_1.default)(config, branch()); | ||
return app; | ||
@@ -82,4 +77,17 @@ } | ||
function validateConfig(config) { | ||
(0, validate_1.validateType)(config.silent, 'Config silent', 'boolean'); | ||
if (typeof config.logger === 'boolean') { | ||
config.logger = config.logger ? DEFAULT_CONFIG.logger : { | ||
debug() { }, | ||
log() { }, | ||
warn() { }, | ||
error() { } | ||
}; | ||
} | ||
(0, validate_1.validateExists)(config.logger, 'Config logger'); | ||
(0, validate_1.validateObject)(config.logger, 'Config logger', 'function'); | ||
(0, validate_1.validateExists)(config.logger.debug, 'Config logger debug'); | ||
(0, validate_1.validateExists)(config.logger.log, 'Config logger log'); | ||
(0, validate_1.validateExists)(config.logger.warn, 'Config logger warn'); | ||
(0, validate_1.validateExists)(config.logger.error, 'Config logger error'); | ||
(0, validate_1.validateType)(config.autoHead, 'Config autoHead', 'boolean'); | ||
} |
@@ -1,3 +0,3 @@ | ||
import { TAddableData, TBundle, TConfig, TRouteData } from '../types'; | ||
export declare function renderRoute(collection: TAddableData, bundle: TBundle, route: TRouteData, config: TConfig): Promise<void>; | ||
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>; |
@@ -15,3 +15,3 @@ "use strict"; | ||
const ex_1 = require("../util/tools/ex"); | ||
function renderRoute(collection, bundle, route, config) { | ||
function renderRoute(collection, bundle, route) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
@@ -54,3 +54,3 @@ const { renderers } = collection; | ||
if (res.statusCode === 500) { | ||
console.error(error); | ||
bundle.logger.error(error); | ||
} | ||
@@ -57,0 +57,0 @@ }); |
@@ -1,2 +0,2 @@ | ||
import { IRouter, TAddableData } from '../types'; | ||
export default function createRouter(branchData: TAddableData): IRouter; | ||
import { IRouter, TAddableData, TConfig } from '../types'; | ||
export default function createRouter(config: TConfig, branchData: TAddableData): IRouter; |
@@ -5,7 +5,7 @@ "use strict"; | ||
const extract_1 = require("../util/extract"); | ||
function createRouter(branchData) { | ||
function createRouter(config, branchData) { | ||
const routes = [...branchData.routes].sort(priority); | ||
const renderers = [...branchData.renderers].sort(priority); | ||
const errorHandlers = [...branchData.errorHandlers].sort(priority); | ||
warnDuplicates(routes); | ||
warnDuplicates(config, routes); | ||
function router(pathname) { | ||
@@ -62,3 +62,3 @@ if (pathname) { | ||
} | ||
function warnDuplicates(routes) { | ||
function warnDuplicates(config, routes) { | ||
const checked = []; | ||
@@ -68,3 +68,3 @@ for (const route of routes) { | ||
if (exists) { | ||
console.warn('Duplicate route detected', { | ||
config.logger.warn('Duplicate route detected', { | ||
method: route.method, | ||
@@ -71,0 +71,0 @@ url: `/${route.parts.join('/')}`, |
@@ -5,7 +5,2 @@ "use strict"; | ||
const validate_1 = require("../../util/validate"); | ||
const OPTIONS = { | ||
parts: ['**'], | ||
handles: [], | ||
method: 'OPTIONS' | ||
}; | ||
exports.default = createBranch; | ||
@@ -22,5 +17,5 @@ function createBranch(...params) { | ||
return { | ||
routes: [...routes, OPTIONS].map(route => (Object.assign(Object.assign({}, route), { parts: [...parts, ...route.parts], handles: [...handles, ...route.handles] }))), | ||
routes: routes.map(route => (Object.assign(Object.assign({}, route), { parts: [...parts, ...route.parts], handles: [...handles, ...route.handles] }))), | ||
renderers: renderers.map(renderer => (Object.assign(Object.assign({}, renderer), { parts: [...parts, ...renderer.parts] }))), | ||
errorHandlers: errorHandlers.map(errorHandler => (Object.assign(Object.assign({}, errorHandler), { parts: [...parts, ...errorHandler.parts] }))) | ||
errorHandlers: errorHandlers.map(errorHandler => (Object.assign(Object.assign({}, errorHandler), { parts: [...parts, ...errorHandler.parts] }))), | ||
}; | ||
@@ -27,0 +22,0 @@ } |
@@ -1,2 +0,4 @@ | ||
import { IRouter, TConfig, TRawBundle } from '../types'; | ||
export default function requestProcessor(router: IRouter, config: TConfig, raw: TRawBundle): Promise<void>; | ||
/// <reference types="node" /> | ||
import { IncomingMessage, ServerResponse } from 'http'; | ||
import { IRouter, TConfig } from '../types'; | ||
export default function requestProcessor(router: IRouter, config: TConfig, req: IncomingMessage, res: ServerResponse): Promise<void>; |
@@ -18,5 +18,6 @@ "use strict"; | ||
const extract_1 = require("../util/extract"); | ||
function requestProcessor(router, config, raw) { | ||
const create_get_body_1 = __importDefault(require("../body/create-get-body")); | ||
function requestProcessor(router, config, req, res) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { req, res, url } = raw; | ||
const url = new URL(req.url || '/', `${req.headers.protocol}://${req.headers.host}`); | ||
const method = req.method || 'GET'; | ||
@@ -26,3 +27,12 @@ const pathname = url.pathname; | ||
const route = findRoute(config, collection.routes, method); | ||
const bundle = Object.freeze(Object.assign(Object.assign({}, raw), { params: (0, extract_1.getParams)(pathname, route), methods: getMethods(config, collection.routes), context: {} })); | ||
const bundle = Object.freeze({ | ||
req, | ||
res, | ||
url, | ||
context: {}, | ||
params: (0, extract_1.getParams)(pathname, route), | ||
methods: getMethods(config, collection.routes), | ||
getBody: (0, create_get_body_1.default)(req), | ||
logger: config.logger | ||
}); | ||
try { | ||
@@ -32,3 +42,3 @@ if (!route) { | ||
} | ||
yield (0, actions_1.renderRoute)(collection, bundle, route, config); | ||
yield (0, actions_1.renderRoute)(collection, bundle, route); | ||
cleanup(res); | ||
@@ -42,9 +52,7 @@ } | ||
res.statusCode = 500; | ||
console.error(fatalError); | ||
config.logger.error(fatalError); | ||
} | ||
cleanup(res); | ||
} | ||
if (!config.silent) { | ||
console.debug(res.statusCode, method, pathname); | ||
} | ||
config.logger.debug(res.statusCode, method, pathname); | ||
}); | ||
@@ -56,3 +64,3 @@ } | ||
if (config.autoHead && !route && method === 'HEAD') { | ||
return findRoute(config, routes, 'GET'); | ||
return routes.find(route => route.method === 'GET'); | ||
} | ||
@@ -59,0 +67,0 @@ return route; |
@@ -7,3 +7,3 @@ /// <reference types="node" /> | ||
export interface IKequapp extends RequestListener { | ||
(config: TConfig, ...handles: THandle[]): IKequapp; | ||
(config: TConfigInput, ...handles: THandle[]): IKequapp; | ||
(...handles: THandle[]): IKequapp; | ||
@@ -13,6 +13,16 @@ add(...routers: IAddable[]): IKequapp; | ||
} | ||
export declare type TConfigInput = { | ||
logger: TLogger | boolean; | ||
autoHead: boolean; | ||
}; | ||
export declare type TConfig = { | ||
silent: boolean; | ||
logger: TLogger; | ||
autoHead: boolean; | ||
}; | ||
export declare type TLogger = { | ||
log: (...params: unknown[]) => void; | ||
error: (...params: unknown[]) => void; | ||
warn: (...params: unknown[]) => void; | ||
debug: (...params: unknown[]) => void; | ||
}; | ||
export interface IAddable { | ||
@@ -73,4 +83,4 @@ (): Partial<TAddableData>; | ||
getBody: IGetBody; | ||
logger: TLogger; | ||
}; | ||
export declare type TRawBundle = Omit<TBundle, 'params' | 'context' | 'methods'>; | ||
export declare type TBundleContext = { | ||
@@ -80,19 +90,19 @@ [k: string]: unknown; | ||
export interface IGetBody { | ||
(format: TBodyOptions & { | ||
(format: TGetBodyOptions & { | ||
raw: true; | ||
multipart: true; | ||
}): Promise<TRawPart[]>; | ||
(format: TBodyOptions & { | ||
(format: TGetBodyOptions & { | ||
raw: true; | ||
}): Promise<Buffer>; | ||
(format: TBodyOptions & { | ||
(format: TGetBodyOptions & { | ||
multipart: true; | ||
}): Promise<[TBodyJson, TFilePart[]]>; | ||
(format?: TBodyOptions): Promise<TBodyJson>; | ||
<T>(format: TBodyOptions & { | ||
(format?: TGetBodyOptions): Promise<TBodyJson>; | ||
<T>(format: TGetBodyOptions & { | ||
multipart: true; | ||
}): Promise<[T, TFilePart[]]>; | ||
<T>(format?: TBodyOptions): Promise<T>; | ||
<T>(format?: TGetBodyOptions): Promise<T>; | ||
} | ||
export declare type TBodyOptions = { | ||
export declare type TGetBodyOptions = { | ||
raw?: boolean; | ||
@@ -99,0 +109,0 @@ multipart?: boolean; |
{ | ||
"name": "kequapp", | ||
"version": "0.2.15", | ||
"version": "0.3.0", | ||
"description": "Versatile, non-intrusive webapp framework", | ||
@@ -5,0 +5,0 @@ "main": "dist/main.js", |
@@ -318,3 +318,3 @@ <img alt="kequapp" src="https://github.com/Kequc/kequapp/blob/0.2-wip/logo.png?raw=true" width="142" height="85" /> | ||
createApp({ | ||
silent: true, | ||
logger: false, | ||
autoHead: false | ||
@@ -324,3 +324,3 @@ }); | ||
Setting `silent` to true disables all logging in the framework. | ||
The `logger` can disable logging by setting `false`. Alternatively a custom logger can be set, by default it is `console`. It must be an object containing methods for `log`, `error`, `warn`, and `debug`. | ||
@@ -413,2 +413,6 @@ Disabling `autoHead` will mean that the framework doesn't automatically use `GET` routes for `HEAD` requests, as described in [more detail](#head-requests) later. | ||
* **`logger`** | ||
The logger being used by the application. | ||
* **`getBody()`** | ||
@@ -680,52 +684,83 @@ | ||
The following helper tools [`sendFile()`](#-sendfile), and [`staticFiles()`](#-staticfiles) are included to make development of common features easier. | ||
The following helper tools [`staticDirectory()`](#-staticdirectory), [`staticFile()`](#-sendfile), and [`sendFile()`](#-sendfile) are included to make file delivery easier. | ||
# # sendFile() | ||
# # staticDirectory() | ||
```javascript | ||
import { sendFile } from 'kequapp'; | ||
import { staticDirectory } from 'kequapp'; | ||
``` | ||
``` | ||
# sendFile(res: Res, asset: string, mime: string): void; | ||
# sendFile(res: Res, asset: string): void; | ||
# staticDirectory(url: Pathname, options: Options): Route; | ||
# staticDirectory(options: Options): Route; | ||
# staticDirectory(url: Pathname): Route; | ||
# staticDirectory(): Route; | ||
``` | ||
Send a file and finalize the response. | ||
Pair a `url` and a set of `options` with a directory. | ||
This is asyncronous and must be awaited otherwise the application might get confused as it continues processing the request. If a mime type is not provided a `'Content-Type'` header is guessed from the file extension. | ||
```javascript | ||
// staticDirectory | ||
app.add( | ||
staticDirectory('/assets/**', { | ||
dir: '/my-assets-dir', | ||
exclude: ['/my-assets-dir/private'], | ||
mime: { | ||
'.3gp': 'audio/3gpp' | ||
} | ||
}) | ||
); | ||
``` | ||
The `url` must be wild if provided, meaning it ends in `'/**'` capturing all possible paths at the given location. | ||
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 file's extension. If there are files in the directory with unusual file extensions then additional `mime` types can be added. | ||
# # staticFile() | ||
```javascript | ||
// sendFile | ||
import { staticFile } from 'kequapp'; | ||
``` | ||
createRoute('/db.json', async ({ res }) => { | ||
await sendFile(res, '/db/my-db.json'); | ||
}); | ||
``` | ||
# staticFile(url: Pathname, asset: Pathname, mime: string): Route; | ||
# staticFile(url: Pathname, asset: Pathname): Route; | ||
``` | ||
# # staticFiles() | ||
Pair a `url` and an `asset`. This asset will be delivered to the client when accessed. | ||
```javascript | ||
import { staticFiles } from 'kequapp'; | ||
// staticFile | ||
app.add( | ||
staticFile('/db.json', '/db/my-db.json') | ||
); | ||
``` | ||
If a mime type is not provided a `'Content-Type'` header is guessed from the file extension. | ||
# # sendFile() | ||
```javascript | ||
import { sendFile } from 'kequapp'; | ||
``` | ||
# staticFiles(url: Pathname, options = Options): Route; | ||
# staticFiles(options: Options): Route; | ||
# staticFiles(url: Pathname): Route; | ||
# staticFiles(): Route; | ||
``` | ||
# sendFile(res: Res, asset: Pathname, mime: string): void; | ||
# sendFile(res: Res, asset: Pathname): void; | ||
``` | ||
Pair a `url` and a given set of `options` with a directory. | ||
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. If a mime type is not provided a `'Content-Type'` header is guessed from the file extension. | ||
The following example is the same as the `staticFile()` example above. | ||
```javascript | ||
// staticFiles | ||
// sendFile | ||
app.add( | ||
staticFiles('/assets', { | ||
dir: '/my-assets-dir', | ||
exclude: ['/my-assets-dir/private'], | ||
mime: { | ||
'.3gp': 'audio/3gpp' | ||
} | ||
createRoute('/db.json', async ({ res }) => { | ||
await sendFile(res, '/db/my-db.json'); | ||
}) | ||
@@ -735,6 +770,2 @@ ); | ||
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 the file extension. If there are files in the directory with unusual file extensions then additional `mime` types can be added. | ||
# Utilities | ||
@@ -741,0 +772,0 @@ |
110397
65
2024
880