UNPKG

@noodletired/rest-ts-core

Version:

Type-safe rest API definitions to share

253 lines 13 kB
"use strict"; /** * @module rest-ts-core */ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.defineAPI = exports.PATCH = exports.DELETE = exports.POST = exports.PUT = exports.GET = exports.EndpointBuilder = void 0; /** * Builder class to create endpoint definitions. * * This class provides a high-level user friendly interface to create a typed definition of an API endpoint. * * Endpoint definitions are usually grouped together in API definitions, which can be shared between producers * and consumers of the API to create a type-safe communication channel. * * You will generally not need to create this class explicitly. Instead, use the helper methods [[GET]], [[PUT]], * [[PATCH]], [[POST]] or [[DELETE]] to create an instance of EndpointBuilder. */ var EndpointBuilder = /** @class */ (function () { function EndpointBuilder(def) { this.def = def; } /** * Change the HTTP method of this endpoint * @param method Any valid HTTP method. See [[HttpMethod]]. */ EndpointBuilder.prototype.method = function (method) { return new EndpointBuilder(Object.assign({}, this.def, { method: method })); }; /** * Change the type of the response. * * You must provide a real object whose type will be inferred and used as the response type for this endpoint. * Rest.ts offers multiple ways to do this: * * 1. Use a regular object. * For instance, if you need a string, use `'string'`, if you need a number, use `123`. * This also works for complex objects like so: * * GET `/current-user` * .response({ * id: 'string', * kind: 'person' as 'person' | 'robot' | 'cat' * }) * * 2. Some people like to use classes to define their DTOs. If this is your situation, you may just put the * class constructor here, and the instance type will be inferred. * * class CurrentUserDTO { * id: string; * kind: 'person' | 'robot' | 'cat'; * } * * GET `/current-user` * .response(CurrentUserDTO) * * 3. **(Preferred method)** Use a Runtype. [runtypes](https://github.com/pelotom/runtypes) is a * library that allows you to create type definitions with runtime type metadata to ensure that * input data conforms to an expected type. * Rest.ts has first-class support for runtypes: * * const CurrentUserDTO = rt.Record({ * id: rt.String, * kind: rt.Union( * rt.String('person'), * rt.String('robot'), * rt.String('cat') * ) * }); * * GET `/current-user` * .response(CurrentUserDTO) * * @param response type of the response data. */ EndpointBuilder.prototype.response = function (response) { return new EndpointBuilder(Object.assign({}, this.def, { response: response })); }; /** * Change the type of the request body. * * You must provide a real object whose type will be inferred and used as the response type for this endpoint. * Rest.ts offers multiple ways to do this: * * 1. Use a regular object. * For instance, if you need a string, use `'string'`, if you need a number, use `123`. * This also works for complex objects like so: * * POST `/current-user` * .body({ * id: 'string', * kind: 'person' as 'person' | 'robot' | 'cat' * }) * * 2. Some people like to use classes to define their DTOs. If this is your situation, you may just put the * class constructor here, and the instance type will be inferred. * * class CurrentUserDTO { * id: string; * kind: 'person' | 'robot' | 'cat'; * } * * POST `/current-user` * .body(CurrentUserDTO) * * 3. **(Preferred method)** Use a Runtype. [runtypes](https://github.com/pelotom/runtypes) is a * library that allows you to create type definitions with runtime type metadata to ensure that * input data conforms to an expected type. * Rest.ts has first-class support for runtypes: * * const CurrentUserDTO = rt.Record({ * id: rt.String, * kind: rt.Union( * rt.String('person'), * rt.String('robot'), * rt.String('cat') * ) * }); * * POST `/current-user` * .body(CurrentUserDTO) * * rest-ts-express automatically type-checks incoming data when the body of the endpoint definition is a runtype. * * @param response type of the response data. */ EndpointBuilder.prototype.body = function (body) { return new EndpointBuilder(Object.assign({}, this.def, { body: body })); }; /** * Add query parameters. * * Note that query parameters are always optional. * * Example: * * GET `/users/search` * .query({ * 'order': 'string', * 'filter': 'string' * }) * * @param query type of the query parameters. */ // @ts-ignore: possibly very deep type checks but it's safe EndpointBuilder.prototype.query = function (query) { return new EndpointBuilder(Object.assign({}, this.def, { query: query })); }; return EndpointBuilder; }()); exports.EndpointBuilder = EndpointBuilder; function endpoint(pathOrStaticParts) { var params = []; for (var _i = 1; _i < arguments.length; _i++) { params[_i - 1] = arguments[_i]; } if (typeof pathOrStaticParts === 'string') { return new EndpointBuilder({ path: [pathOrStaticParts], params: [], response: undefined }); } return new EndpointBuilder({ path: pathOrStaticParts.slice(), params: params || [], response: undefined }); } function GET(pathOrStaticParts) { var params = []; for (var _i = 1; _i < arguments.length; _i++) { params[_i - 1] = arguments[_i]; } return endpoint.apply(void 0, __spreadArray([pathOrStaticParts], params, false)).method('GET'); } exports.GET = GET; function PUT(pathOrStaticParts) { var params = []; for (var _i = 1; _i < arguments.length; _i++) { params[_i - 1] = arguments[_i]; } return endpoint.apply(void 0, __spreadArray([pathOrStaticParts], params, false)).method('PUT'); } exports.PUT = PUT; function POST(pathOrStaticParts) { var params = []; for (var _i = 1; _i < arguments.length; _i++) { params[_i - 1] = arguments[_i]; } return endpoint.apply(void 0, __spreadArray([pathOrStaticParts], params, false)).method('POST'); } exports.POST = POST; function DELETE(pathOrStaticParts) { var params = []; for (var _i = 1; _i < arguments.length; _i++) { params[_i - 1] = arguments[_i]; } return endpoint.apply(void 0, __spreadArray([pathOrStaticParts], params, false)).method('DELETE'); } exports.DELETE = DELETE; function PATCH(pathOrStaticParts) { var params = []; for (var _i = 1; _i < arguments.length; _i++) { params[_i - 1] = arguments[_i]; } return endpoint.apply(void 0, __spreadArray([pathOrStaticParts], params, false)).method('PATCH'); } exports.PATCH = PATCH; /** * Create an API definition to share across producers and consumers of the API. * * The usual workflow of rest-ts-core goes like this: * * 1. Create an API definition: * * const myAwesomeAPI = defineAPI({ * someEndpoint: GET `/some/path` * .response(SomeResponseDTO) * }); * * 2. Create a server for this API. * rest-ts-express allows you to import the API definition you just created and * turn it into an express router. See the documentation for that package for more details. * * 3. Create a consumer for this API. * rest-ts-axios lets you create a typed instance of [axios](https://github.com/axios/axios) to * perform requests to your API. * * 4. ... Profit! * * * Notice: Unless you are authoring an adapter for Rest.ts, you should always treat the return type of this function * as an opaque type. Use the utilities provided by this library to create the API definition within the brackets * of `defineAPI({ ... })`, and export the resulting symbol to be consumed by your server and client(s). * The type you get from `defineAPI` is very complex, and for a good reason: it encodes all of the type information * of your API! It is pointless to inspect the raw type you get. Instead, we recommend that you feed it directly * to a compatible binding library such as rest-ts-express and rest-ts-axios. These libraries are able to decode * the complex type and make sense out of it. * * @param api */ var defineAPI = function (api) { return api; }; exports.defineAPI = defineAPI; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbGRlci1raXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvYnVpbGRlci1raXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOztHQUVHOzs7Ozs7Ozs7Ozs7QUFLSDs7Ozs7Ozs7OztHQVVHO0FBQ0g7SUFFSSx5QkFBNEIsR0FBZ0I7UUFBaEIsUUFBRyxHQUFILEdBQUcsQ0FBYTtJQUFJLENBQUM7SUFFakQ7OztPQUdHO0lBQ0ksZ0NBQU0sR0FBYixVQUFvQyxNQUFTO1FBQ3pDLE9BQU8sSUFBSSxlQUFlLENBQW9CLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxFQUFFLElBQUksQ0FBQyxHQUFHLEVBQUUsRUFBRSxNQUFNLFFBQUEsRUFBRSxDQUFDLENBQUMsQ0FBQztJQUMzRixDQUFDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztPQTZDRztJQUNJLGtDQUFRLEdBQWYsVUFBMEQsUUFBVztRQUNqRSxPQUFPLElBQUksZUFBZSxDQUFzQixNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsRUFBRSxJQUFJLENBQUMsR0FBRyxFQUFFLEVBQUUsUUFBUSxVQUFBLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDL0YsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztPQStDRztJQUNJLDhCQUFJLEdBQVgsVUFBa0QsSUFBTztRQUNyRCxPQUFPLElBQUksZUFBZSxDQUFrQixNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsRUFBRSxJQUFJLENBQUMsR0FBRyxFQUFFLEVBQUUsSUFBSSxNQUFBLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDdkYsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7OztPQWNHO0lBQ0gsMkRBQTJEO0lBQ3BELCtCQUFLLEdBQVosVUFBb0MsS0FBeUI7UUFDekQsT0FBTyxJQUFJLGVBQWUsQ0FBb0MsTUFBTSxDQUFDLE1BQU0sQ0FBQyxF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