Comparing version 1.0.2 to 1.0.3
@@ -22,5 +22,7 @@ /** Type-safe wrapper around Express router for REST APIs */ | ||
constructor(router: express.Router, apiSchema?: any); | ||
get<Path extends keyof API, Spec extends SafeKey<API[Path], 'get'> = SafeKey<API[Path], 'get'>>(route: Path, handler: (params: Spec extends AnyEndpoint ? ExtractRouteParams<Path & string> : never, request: express.Request, response: express.Response) => Promise<Spec extends AnyEndpoint ? Spec['response'] : never>): void; | ||
get<Path extends keyof API & string, Spec extends SafeKey<API[Path], 'get'> = SafeKey<API[Path], 'get'>>(route: Path, handler: (params: ExtractRouteParams<Path>, request: express.Request<ExtractRouteParams<Path>, SafeKey<Spec, 'response'>>, response: express.Response<SafeKey<Spec, 'response'>>) => Promise<Spec extends AnyEndpoint ? Spec['response'] : never>): void; | ||
/** Register a handler on the router for the given path and verb */ | ||
registerEndpoint<Path extends keyof API, Method extends keyof API[Path] & HTTPVerb, Spec extends API[Path][Method] = API[Path][Method]>(method: Method, route: Path, handler: (params: Spec extends AnyEndpoint ? ExtractRouteParams<Path & string> : never, body: Spec extends AnyEndpoint ? Spec['request'] : never, request: express.Request, response: express.Response) => Promise<Spec extends AnyEndpoint ? Spec['response'] : never>): void; | ||
registerEndpoint<Path extends keyof API & string, Method extends keyof API[Path] & HTTPVerb, Spec extends API[Path][Method] = API[Path][Method]>(method: Method, route: Path, handler: (params: ExtractRouteParams<Path>, body: SafeKey<Spec, 'request'>, request: express.Request<ExtractRouteParams<Path>, SafeKey<Spec, 'response'>, SafeKey<Spec, 'request'>>, response: express.Response<SafeKey<Spec, 'response'>>) => Promise<Spec extends AnyEndpoint ? Spec['response'] : never>): void; | ||
/** Get a validation function for request bodies for the endpoint, or null if not applicable. */ | ||
getValidator(route: string, method: HTTPVerb): Ajv.ValidateFunction | null; | ||
/** Throw if any routes declared in the API spec have not been implemented. */ | ||
@@ -27,0 +29,0 @@ assertAllRoutesRegistered(): void; |
@@ -52,38 +52,3 @@ "use strict"; | ||
var _this = this; | ||
var apiSchema = this.apiSchema; | ||
var validate; | ||
if (apiSchema) { | ||
var apiDef = apiSchema.properties; | ||
if (!apiDef[route]) { | ||
throw new Error("API JSONSchema is missing entry for " + route); | ||
} | ||
var refSchema = apiDef[route].properties[method].$ref; | ||
var endpoint = refSchema.slice('#/definitions/'.length); | ||
var endpointTypes = apiSchema.definitions[endpoint].properties; | ||
var requestType = endpointTypes.request; | ||
if (requestType.$ref) { | ||
requestType = requestType.$ref; // allow either references or inline types | ||
} | ||
else if (requestType.type && requestType.type === 'null') { | ||
requestType = null; // no request body, no validation | ||
} | ||
else if (requestType.allOf) { | ||
// TODO(danvk): figure out how to make ajv understand these. | ||
throw new Error('Intersection types in APIs are not supported yet.'); | ||
} | ||
if (requestType && this.ajv) { | ||
if (typeof requestType === 'string') { | ||
validate = this.ajv.getSchema(requestType); | ||
} | ||
else { | ||
// Create a new AJV validate for inline object types. | ||
// This assumes these will never reference other type definitions. | ||
var requestAjv = new ajv_1.default(); | ||
validate = requestAjv.compile(requestType); | ||
} | ||
if (!validate) { | ||
throw new Error("Unable to get schema for '" + requestType + "'"); | ||
} | ||
} | ||
} | ||
var validate = this.getValidator(route, method); | ||
this.registrations.push({ path: route, method: method }); | ||
@@ -131,2 +96,45 @@ this.router[method](route, function () { | ||
}; | ||
/** Get a validation function for request bodies for the endpoint, or null if not applicable. */ | ||
TypedRouter.prototype.getValidator = function (route, method) { | ||
var _a; | ||
var apiSchema = this.apiSchema; | ||
if (!apiSchema) { | ||
return null; | ||
} | ||
var apiDef = apiSchema.properties; | ||
if (!apiDef[route]) { | ||
throw new Error("API JSONSchema is missing entry for " + route); | ||
} | ||
var refSchema = apiDef[route].properties[method].$ref; | ||
var endpoint = refSchema.slice('#/definitions/'.length); | ||
var endpointTypes = apiSchema.definitions[endpoint].properties; | ||
var requestType = endpointTypes.request; | ||
if (requestType.$ref) { | ||
requestType = requestType.$ref; // allow either references or inline types | ||
} | ||
else if (requestType.type && requestType.type === 'null') { | ||
requestType = null; // no request body, no validation | ||
} | ||
else if (requestType.allOf) { | ||
// TODO(danvk): figure out how to make ajv understand these. | ||
throw new Error('Intersection types in APIs are not supported yet.'); | ||
} | ||
if (requestType && this.ajv) { | ||
var validate = void 0; | ||
if (typeof requestType === 'string') { | ||
validate = (_a = this.ajv.getSchema(requestType)) !== null && _a !== void 0 ? _a : null; | ||
} | ||
else { | ||
// Create a new AJV validate for inline object types. | ||
// This assumes these will never reference other type definitions. | ||
var requestAjv = new ajv_1.default(); | ||
validate = requestAjv.compile(requestType); | ||
} | ||
if (!validate) { | ||
throw new Error("Unable to get schema for '" + requestType + "'"); | ||
} | ||
return validate; | ||
} | ||
return null; | ||
}; | ||
/** Throw if any routes declared in the API spec have not been implemented. */ | ||
@@ -133,0 +141,0 @@ TypedRouter.prototype.assertAllRoutesRegistered = function () { |
{ | ||
"name": "crosswalk", | ||
"version": "1.0.2", | ||
"version": "1.0.3", | ||
"description": "Type-safe express routing with TypeScript", | ||
@@ -13,2 +13,15 @@ "main": "dist/index.js", | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/danvk/crosswalk.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/danvk/crosswalk/issues" | ||
}, | ||
"keywords": [ | ||
"express", | ||
"router", | ||
"jsonschema", | ||
"typescript" | ||
], | ||
"devDependencies": { | ||
@@ -18,4 +31,6 @@ "@types/jest": "^26.0.15", | ||
"@types/swagger-ui-express": "^4.1.2", | ||
"body-parser": "^1.19.0", | ||
"install-peers-cli": "^2.2.0", | ||
"jest": "^26.6.3", | ||
"spec.ts": "^1.1.3", | ||
"supertest": "^6.0.1", | ||
@@ -32,4 +47,4 @@ "ts-jest": "^26.4.4", | ||
"express": "^4", | ||
"typescript": "^4.1.1-rc" | ||
"typescript": "^4.1" | ||
} | ||
} |
@@ -252,2 +252,11 @@ # Crosswalk: safe routes for Express and TypeScript | ||
If you get errors about `Type 'any' is not assignable to type 'never'.`, it | ||
might be because you're using an old version of TypeScript, either in your | ||
project or in your editor. | ||
**What's with the name?** | ||
A crosswalk is a _safe route_ across a road. Also a nod to [Sidewalk Labs][swl], | ||
where this project was originally developed. | ||
## Development setup | ||
@@ -282,3 +291,7 @@ | ||
- [ ] Options for request logging | ||
- [ ] Narrow types of request.params, request.body in handlers | ||
- [ ] Set up prettier (doesn't support TS 4.1 yet), eslint, CI | ||
- [ ] Add an option for more express-like callbacks (w/ only request, response) | ||
- [ ] Support fancier paths | ||
- [x] Set up better type tests | ||
- [x] Narrow types of request.params, request.body in handlers | ||
- [x] Write unit tests | ||
@@ -304,3 +317,4 @@ - [x] Decide on a name | ||
[suie]: https://github.com/scottie1984/swagger-ui-express | ||
[ts41]: https://devblogs.microsoft.com/typescript/announcing-typescript-4-1-rc/ | ||
[ts41]: https://devblogs.microsoft.com/typescript/announcing-typescript-4-1 | ||
[tweet]: https://twitter.com/danvdk/status/1301707026507198464 | ||
[swl]: https://sidewalklabs.com/ |
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
176505
31
1153
0
318
1
10