@noodletired/rest-ts-core
Version:
Type-safe rest API definitions to share
253 lines • 13 kB
JavaScript
;
/**
* @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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