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

@api-ts/typed-express-router

Package Overview
Dependencies
Maintainers
2
Versions
36
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@api-ts/typed-express-router - npm Package Compare versions

Comparing version 0.1.1-beta.1 to 0.2.0-beta.1

6

dist/src/index.d.ts

@@ -1,7 +0,7 @@

import { ApiSpec, HttpRoute } from '@api-ts/io-ts-http';
import { ApiSpec } from '@api-ts/io-ts-http';
import express from 'express';
import { WrappedRouteOptions, WrappedRouter, WrappedRouterOptions } from './types';
export type { AfterEncodedResponseSentFn, OnDecodeErrorFn, OnEncodeErrorFn, TypedRequestHandler, WrappedRouter, WrappedRouteOptions, WrappedRouterOptions, WrappedRequest, WrappedResponse, } from './types';
export type { AfterEncodedResponseSentFn, OnDecodeErrorFn, OnEncodeErrorFn, TypedRequestHandler, UncheckedRequestHandler, WrappedRouter, WrappedRouteOptions, WrappedRouterOptions, WrappedRequest, WrappedResponse, } from './types';
export declare function createRouter<Spec extends ApiSpec>(spec: Spec, { onDecodeError, onEncodeError, afterEncodedResponseSent, ...options }?: WrappedRouterOptions): WrappedRouter<Spec>;
export declare function wrapRouter<Spec extends ApiSpec>(router: express.Router, spec: Spec, { onDecodeError, onEncodeError, afterEncodedResponseSent, }: WrappedRouteOptions<HttpRoute>): WrappedRouter<Spec>;
export declare function wrapRouter<Spec extends ApiSpec>(router: express.Router, spec: Spec, { onDecodeError, onEncodeError, afterEncodedResponseSent, }: WrappedRouteOptions): WrappedRouter<Spec>;
//# sourceMappingURL=index.d.ts.map

@@ -46,36 +46,48 @@ "use strict";

function wrapRouter(router, spec, { onDecodeError = errors_1.defaultOnDecodeError, onEncodeError = errors_1.defaultOnEncodeError, afterEncodedResponseSent = () => { }, }) {
function makeAddAliasRoute(method) {
return (path, apiName, handlers, options) => {
const routerMiddleware = [];
function makeAddUncheckedRoute(method) {
return (apiName, handlers, options) => {
var _a;
const route = spec[apiName][method];
const wrapReqAndRes = (req, res, next) => {
(0, function_1.pipe)(route.request.decode(req), E.matchW((errs) => { var _a; return ((_a = options === null || options === void 0 ? void 0 : options.onDecodeError) !== null && _a !== void 0 ? _a : onDecodeError)(errs, req, res); }, (decoded) => {
req.decoded = decoded;
res.sendEncoded = (status, payload) => {
var _a, _b;
try {
const codec = route.response[status];
if (!codec) {
throw new Error(`no codec defined for response status ${status}`);
}
const statusCode = typeof status === 'number'
? status
: io_ts_http_1.KeyToHttpStatus[status];
if (statusCode === undefined) {
throw new Error(`unknown HTTP status code for key ${status}`);
}
else if (!codec.is(payload)) {
throw new Error(`response does not match expected type ${codec.name}`);
}
const encoded = codec.encode(payload);
res.status(statusCode).json(encoded).end();
((_a = options === null || options === void 0 ? void 0 : options.afterEncodedResponseSent) !== null && _a !== void 0 ? _a : afterEncodedResponseSent)(status, payload, req, res);
const decoded = route.request.decode(req);
req.decoded = decoded;
req.apiName = apiName;
req.httpRoute = route;
res.sendEncoded = (status, payload) => {
var _a, _b;
try {
const codec = route.response[status];
if (!codec) {
throw new Error(`no codec defined for response status ${status}`);
}
catch (err) {
((_b = options === null || options === void 0 ? void 0 : options.onEncodeError) !== null && _b !== void 0 ? _b : onEncodeError)(err, req, res);
const statusCode = typeof status === 'number'
? status
: io_ts_http_1.KeyToHttpStatus[status];
if (statusCode === undefined) {
throw new Error(`unknown HTTP status code for key ${status}`);
}
};
next();
}));
else if (!codec.is(payload)) {
throw new Error(`response does not match expected type ${codec.name}`);
}
const encoded = codec.encode(payload);
res.status(statusCode).json(encoded).end();
((_a = options === null || options === void 0 ? void 0 : options.afterEncodedResponseSent) !== null && _a !== void 0 ? _a : afterEncodedResponseSent)(statusCode, payload, req, res);
}
catch (err) {
((_b = options === null || options === void 0 ? void 0 : options.onEncodeError) !== null && _b !== void 0 ? _b : onEncodeError)(err, req, res);
}
};
next();
};
router[method](path, [wrapReqAndRes, ...handlers]);
const middlewareChain = [
wrapReqAndRes,
...routerMiddleware,
...handlers,
];
const path = spec[apiName][method].path;
router[method]((0, path_1.apiTsPathToExpress)(path), middlewareChain);
(_a = options === null || options === void 0 ? void 0 : options.routeAliases) === null || _a === void 0 ? void 0 : _a.forEach((alias) => {
router[method](alias, middlewareChain);
});
};

@@ -85,4 +97,12 @@ }

return (apiName, handlers, options) => {
const path = spec[apiName][method].path;
return makeAddAliasRoute(method)((0, path_1.apiTsPathToExpress)(path), apiName, handlers, options);
const validateMiddleware = (req, res, next) => {
(0, function_1.pipe)(req.decoded, E.matchW((errs) => {
var _a;
((_a = options === null || options === void 0 ? void 0 : options.onDecodeError) !== null && _a !== void 0 ? _a : onDecodeError)(errs, req, res);
}, (value) => {
req.decoded = value;
next();
}));
};
return makeAddUncheckedRoute(method)(apiName, [validateMiddleware, ...handlers], options);
};

@@ -96,10 +116,9 @@ }

delete: makeAddRoute('delete'),
getAlias: makeAddAliasRoute('get'),
postAlias: makeAddAliasRoute('post'),
putAlias: makeAddAliasRoute('put'),
deleteAlias: makeAddAliasRoute('delete'),
getUnchecked: router.get,
postUnchecked: router.post,
putUnchecked: router.put,
deleteUnchecked: router.delete,
getUnchecked: makeAddUncheckedRoute('get'),
postUnchecked: makeAddUncheckedRoute('post'),
putUnchecked: makeAddUncheckedRoute('put'),
deleteUnchecked: makeAddUncheckedRoute('delete'),
use: (middleware) => {
routerMiddleware.push(middleware);
},
});

@@ -106,0 +125,0 @@ Object.setPrototypeOf(result, Object.getPrototypeOf(router));

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

import { ApiSpec, HttpRoute, Method as HttpMethod, RequestType } from '@api-ts/io-ts-http';
import { ApiSpec, HttpRoute, Method as HttpMethod } from '@api-ts/io-ts-http';
import express from 'express';

@@ -6,24 +6,31 @@ import * as t from 'io-ts';

export declare type RouteAt<Spec extends ApiSpec, ApiName extends keyof Spec, Method extends keyof Spec[ApiName]> = Spec[ApiName][Method] extends HttpRoute ? Spec[ApiName][Method] : never;
export declare type WrappedRequest<Spec extends ApiSpec, ApiName extends keyof Spec, Method extends keyof Spec[ApiName]> = express.Request & {
decoded: RequestType<RouteAt<Spec, ApiName, Method>>;
export declare type WrappedRequest<Decoded = unknown> = express.Request & {
decoded: Decoded;
apiName: string;
httpRoute: HttpRoute;
};
export declare type WrappedResponse<Spec extends ApiSpec, ApiName extends keyof Spec, Method extends keyof Spec[ApiName]> = express.Response & {
sendEncoded: <Status extends keyof RouteAt<Spec, ApiName, Method>['response']>(status: Status, payload: t.TypeOf<RouteAt<Spec, ApiName, Method>['response'][Status]>) => void;
export declare type WrappedResponse<Responses extends {} = Record<string | number, unknown>> = express.Response & {
sendEncoded: <Status extends keyof Responses>(status: Status, payload: Responses[Status]) => void;
};
export declare type OnDecodeErrorFn = (errs: t.Errors, req: express.Request, res: express.Response) => void;
export declare type OnEncodeErrorFn = (err: unknown, req: express.Request, res: express.Response) => void;
export declare type AfterEncodedResponseSentFn<Route extends HttpRoute> = <Status extends keyof Route['response']>(status: Status, payload: Route['response'][Status], req: express.Request, res: express.Response) => void;
export declare type WrappedRouteOptions<Route extends HttpRoute> = {
onDecodeError?: OnDecodeErrorFn;
export declare type OnEncodeErrorFn = (err: unknown, req: WrappedRequest, res: WrappedResponse) => void;
export declare type AfterEncodedResponseSentFn = (status: number, payload: unknown, req: WrappedRequest, res: WrappedResponse) => void;
export declare type UncheckedWrappedRouteOptions = {
onEncodeError?: OnEncodeErrorFn;
afterEncodedResponseSent?: AfterEncodedResponseSentFn<Route>;
afterEncodedResponseSent?: AfterEncodedResponseSentFn;
routeAliases?: string[];
};
export declare type WrappedRouterOptions = express.RouterOptions & WrappedRouteOptions<HttpRoute>;
export declare type TypedRequestHandler<Spec extends ApiSpec, ApiName extends keyof Spec, Method extends keyof Spec[ApiName]> = (req: WrappedRequest<Spec, ApiName, Method>, res: WrappedResponse<Spec, ApiName, Method>, next: express.NextFunction) => void;
export declare type WrappedRouteOptions = UncheckedWrappedRouteOptions & {
onDecodeError?: OnDecodeErrorFn;
};
export declare type WrappedRouterOptions = express.RouterOptions & WrappedRouteOptions;
export declare type TypedRequestHandler<Spec extends ApiSpec = ApiSpec, ApiName extends keyof Spec = string, Method extends keyof Spec[ApiName] = string> = (req: WrappedRequest<t.TypeOf<RouteAt<Spec, ApiName, Method>['request']>>, res: WrappedResponse<t.TypeOfProps<RouteAt<Spec, ApiName, Method>['response']>>, next: express.NextFunction) => void;
export declare type UncheckedRequestHandler<Spec extends ApiSpec = ApiSpec, ApiName extends keyof Spec = string, Method extends keyof Spec[ApiName] = string> = (req: WrappedRequest<ReturnType<RouteAt<Spec, ApiName, Method>['request']['decode']>>, res: WrappedResponse<t.TypeOfProps<RouteAt<Spec, ApiName, Method>['response']>>, next: express.NextFunction) => void;
declare type ApiNamesWithMethod<Spec extends ApiSpec, Method extends Methods> = {
[K in keyof Spec]: Method extends keyof Spec[K] ? K : never;
}[keyof Spec];
export declare type AddRouteHandler<Spec extends ApiSpec, Method extends Methods> = <ApiName extends ApiNamesWithMethod<Spec, Method>>(apiName: ApiName, handlers: TypedRequestHandler<Spec, ApiName, Method>[], options?: WrappedRouteOptions<Spec[ApiName][Method]>) => void;
export declare type AddAliasRouteHandler<Spec extends ApiSpec, Method extends Methods> = <ApiName extends ApiNamesWithMethod<Spec, Method>>(path: string, apiName: ApiName, handlers: TypedRequestHandler<Spec, ApiName, Method>[], options?: WrappedRouteOptions<Spec[ApiName][Method]>) => void;
export declare type WrappedRouter<Spec extends ApiSpec> = Omit<express.Router, 'get' | 'post' | 'put' | 'delete'> & express.RequestHandler & {
[K in keyof Spec & string]: Method extends keyof Spec[K] ? K : never;
}[keyof Spec & string];
export declare type AddRouteHandler<Spec extends ApiSpec, Method extends Methods> = <ApiName extends ApiNamesWithMethod<Spec, Method>>(apiName: ApiName, handlers: TypedRequestHandler<Spec, ApiName, Method>[], options?: WrappedRouteOptions) => void;
export declare type AddUncheckedRouteHandler<Spec extends ApiSpec, Method extends Methods> = <ApiName extends ApiNamesWithMethod<Spec, Method>>(apiName: ApiName, handlers: UncheckedRequestHandler<Spec, ApiName, Method>[], options?: UncheckedWrappedRouteOptions) => void;
export declare type WrappedRouter<Spec extends ApiSpec> = Omit<express.Router, 'get' | 'post' | 'put' | 'delete' | 'use'> & express.RequestHandler & {
use: (middleware: UncheckedRequestHandler<ApiSpec, string, string>) => void;
get: AddRouteHandler<Spec, 'get'>;

@@ -33,12 +40,8 @@ post: AddRouteHandler<Spec, 'post'>;

delete: AddRouteHandler<Spec, 'delete'>;
getAlias: AddAliasRouteHandler<Spec, 'get'>;
postAlias: AddAliasRouteHandler<Spec, 'post'>;
putAlias: AddAliasRouteHandler<Spec, 'put'>;
deleteAlias: AddAliasRouteHandler<Spec, 'delete'>;
getUnchecked: express.Router['get'];
postUnchecked: express.Router['post'];
putUnchecked: express.Router['put'];
deleteUnchecked: express.Router['delete'];
getUnchecked: AddUncheckedRouteHandler<Spec, 'get'>;
postUnchecked: AddUncheckedRouteHandler<Spec, 'post'>;
putUnchecked: AddUncheckedRouteHandler<Spec, 'put'>;
deleteUnchecked: AddUncheckedRouteHandler<Spec, 'delete'>;
};
export {};
//# sourceMappingURL=types.d.ts.map
{
"name": "@api-ts/typed-express-router",
"version": "0.1.1-beta.1",
"version": "0.2.0-beta.1",
"description": "Implement an HTTP specification with Express",

@@ -5,0 +5,0 @@ "author": "Patrick McLaughlin <patrickmclaughlin@bitgo.com>",

@@ -8,8 +8,9 @@ # @api-ts/typed-express-router

- Define Express routes that are associated with routes in an api-ts `apiSpec`
- Augment the existing Express request with the decoded request object
- Augment the existing Express request with the decoded request object and api-ts route
metadata
- Augment the existing Express response with a type-checked `encode` function
- Allow customization of what to do on decode/encode errors, per-route if desired
- Allow action to be performed after an encoded response is sent, per-route if desired
- Allow routes to be defined with path that is different than the one specified in the
`httpRoute` (e.g. for aliases)
- Allow routes to define alias routes with path that is different than the one specified
in the `httpRoute`
- Follow the express router api as closely as possible otherwise

@@ -57,21 +58,11 @@

### Aliased routes
### Route aliases
If more flexibility is needed in the route path, the `getAlias`-style route functions
may be used. They take a path that is directly interpreted by Express, but otherwise
work like the regular route methods:
If more flexibility is needed in the route path, a `routeAliases` function may be
provided to match multiple paths. These paths may use the full Express matching syntax,
but take care to preserve any path parameters or else you will likely get decode errors.
```ts
typedRouter.getAlias('/oldDeprecatedHelloWorld', 'hello.world', [HelloWorldHandler]);
```
### Unchecked routes
For convenience, the original router's `get`/`post`/`put`/`delete` methods can still be
used via `getUnchecked` (or similar):
```ts
// Just a normal express route
typedRouter.getUnchecked('/api/foo/bar', (req, res) => {
res.send(200).end();
typedRouter.get('hello.world', [HelloWorldHandler], {
routeAliases: ['/oldDeprecatedHelloWorld'],
});

@@ -110,2 +101,27 @@ ```

### Unchecked routes
If you need custom behavior on decode errors that is more involved than just sending an
error response, then the unchecked variant of the router functions can be used. They do
not fail and call `onDecodeError` when a request is invalid. Instead, they will still
populate `req.decoded`, except this time it'll contain the
`Either<Errors, DecodedRequest>` type for route handlers to inspect.
```ts
// Just a normal express route
typedRouter.getUnchecked('hello.world', (req, res) => {
if (E.isLeft(req.decoded)) {
console.warn('Route failed to decode! Continuing anyway');
})
res.send(200).end();
});
```
### Router middleware
Middleware added with `typedRouter.use()` is ran just after the request is decoded but
before it is validated, even on checked routes. It'll have access to `req.decoded` in
the same way that unchecked routes do.
### Other usage

@@ -112,0 +128,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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