rests
Version:
Easily generate API client's SDK — organize and simplify API Requests.
302 lines (257 loc) • 9.43 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const path = require("path");
const { capitalize, dent, mergeOptions } = require("./helpers");
/**
* Generate typescript interface
*/
function generateTypes(schema, options) {
let types =
`/*!
* Made with Rests
* github.com/el1s7/rests
*/
type json =
| string
| number
| boolean
| null
| json[]
| {[key: string]: json};
interface FormData {
[Symbol.iterator](): IterableIterator<[string, File | string]>;
/** Returns an array of key, value pairs for every entry in the list. */
entries(): IterableIterator<[string, File | string]>;
/** Returns a list of keys in the list. */
keys(): IterableIterator<string>;
/** Returns a list of values in the list. */
values(): IterableIterator<File | string>;
}
interface ResponseObject {
statusCode: number,
statusText: string,
headers: Headers,
type: "basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect" ,
ok: boolean,
json?: any,
text?: string,
formData?: FormData,
blob?: Blob,
message?: string
}
type HookRequest = {
/**
* Fetch URL
*/
url: string,
/**
* Fetch Options
*/
options: any,
/**
* The parameters supplied for this request
*/
params: any
/**
* Rests instance
*/
instance: any
/**
* Endpoint Key, e.g "user.login"
*/
key: string
};
interface Hooks {
/**
* A global hook function that is called on request.
*
*/
on_request?: (request: HookRequest) => any,
/**
* A hook function that is called on successful response, you can also modify and return a different response.
*/
on_success?: (response: ResponseObject, request?: HookRequest) => any,
/**
* A hook function that is called on errors.
*
*
* To return a different error:
*/
on_error?: (error: ResponseObject | unknown, request?: HookRequest) => any,
}
interface Params {
[name: string]:{
/** The parameter HTTP name */
name?: string,
/** Required or not */
required?: boolean,
/** A help message to throw in case of errors */
help?: string,
/** Param type (default: any)*/
type?: "string" | "number" | "array" | "object" | "boolean" | "any",
/** Example value */
example?: any,
/** Format functions that accepts supplied value and returns formatted value. */
format?: (value: any)=>any,
/** Regex validation */
validate?: RegExp | string,
/** Array validation */
in?: any[],
max?: number,
min?: number,
/** Default value */
default?: any,
/** HTTP Location */
location?: "body" | "headers" | "query" | "path",
}
}
interface Options extends Hooks {
base?: string,
sandboxBase?: string,
headers?: any,
params?: Params,
/**
* Set default values for parameters
*/
values?: {
[param_name: string]: any
}
/**
* Node-Fetch option for adding a proxy
*/
fetch_agent?: any,
}
interface newCategoryOptions {
/**
* Override global options for this category
*/
$options: Options;
}
interface newCategoryWithOptions extends newCategoryOptions {
[param: string]: any | Options;
}
type newCategoryValues = {
[param: string]: any
} | newCategoryWithOptions;
declare class HideFuncProps<T>{
private name;
private apply;
private bind;
private arguments;
private call;
private caller;
private length;
private prototype;
private toString;
//public set: (values: newCategoryValues) => T;
}
interface updateOptions<X> extends HideFuncProps<X>{
set: (values: newCategoryValues) => X
}
interface newCategory<T> extends HideFuncProps<T> {
new(values: newCategoryValues): T & updateOptions<T>;
}
`;
let config = Object.assign({ output: null, includeExamples: true, template: null }, options);
if (config.template) {
types = fs.readFileSync(path.join(process.cwd(), config.template), 'utf-8');
}
const getJSDoc = (string, tabs = 0, tabFirst = false) => {
let padding = '\t'.repeat(tabs);
return (`${tabFirst ? padding : ''}/**
${padding} * ${string.replace(/^(\s|\r?\n)/, '').replace(/\r?\n/g, `\n${padding} * `)}
${padding} */`);
};
function makeTypes(tree, parent, categoryOptions = {}, parentTreeKey) {
let category_help;
for (var category in tree) {
var category_tree = tree[category];
if ((category == 'help' || category == '$help') && typeof category_tree === "string") {
//@ts-ignore
category_help = tree[category];
}
if (!category_tree || typeof category_tree !== 'object') {
continue;
}
let helpMessage = '';
let treeKey = parentTreeKey ? parentTreeKey + capitalize(category) : capitalize(category);
//Is Endpoint
if (category_tree.hasOwnProperty('path')) {
let endpoint = category_tree;
let endpointParams = Object.assign(Object.assign({}, categoryOptions === null || categoryOptions === void 0 ? void 0 : categoryOptions.params), endpoint.params);
let parseEndpointParams = Object.keys(endpointParams || {})
.sort((b, a) => {
var _a, _b, _c, _d;
return (endpointParams[a].required && !endpointParams[b].required ? 1 : (!endpointParams[a].required && endpointParams[b].required ? -1 : (!((_a = endpoint === null || endpoint === void 0 ? void 0 : endpoint.params) === null || _a === void 0 ? void 0 : _a[a]) && ((_b = endpoint === null || endpoint === void 0 ? void 0 : endpoint.params) === null || _b === void 0 ? void 0 : _b[b]) ? -1 : (!((_c = endpoint === null || endpoint === void 0 ? void 0 : endpoint.params) === null || _c === void 0 ? void 0 : _c[b]) && ((_d = endpoint === null || endpoint === void 0 ? void 0 : endpoint.params) === null || _d === void 0 ? void 0 : _d[a]) ? 1 : 0))));
})
.map((param) => {
var _a, _b, _c;
let ps = endpointParams[param];
if (ps['$initsOnly']) {
return null;
}
let help = ps.help ? ps.help : '', exampleValue = (_b = (_a = ps.example) !== null && _a !== void 0 ? _a : ps.default) !== null && _b !== void 0 ? _b : (_c = categoryOptions === null || categoryOptions === void 0 ? void 0 : categoryOptions.values) === null || _c === void 0 ? void 0 : _c[param], helpWithExample = config.includeExamples && exampleValue ? dent(`
${help}
@example
\`${JSON.stringify(exampleValue)}\`
`, 7) : '', required = ps.required && false ? '' : '?', type = (ps.type || "any").replace(/("|')/g, '').replace("array", "any[]").replace("object", "json");
return dent(`
${getJSDoc(helpWithExample || help, 1, true)}
${param}${required}: ${type}
`, 6);
}).filter(p => p).join('\n');
let endpointParamsType = parseEndpointParams ? `params?: {${parseEndpointParams}\n} | FormData` : '';
helpMessage = endpoint.help || `${capitalize(category)} - ${((endpoint === null || endpoint === void 0 ? void 0 : endpoint.method) || 'get').toUpperCase()} request`;
let endpoint_type = dent(`
${getJSDoc(helpMessage, 1)}
${category}: (${endpointParamsType}) => Promise<ResponseObject>;
`, 4);
parent.push(endpoint_type);
}
//Is Category, recursion
else {
// Skip Special Object (i.e Options)
if (category.substr(0, 1) === '$') {
continue;
}
let subcategory = category_tree;
let nextOptions = categoryOptions;
if (subcategory.$options) {
nextOptions = mergeOptions(categoryOptions, subcategory.$options);
}
helpMessage = category_help || `${capitalize(category)} Endpoints Category`;
let category_type = dent(`
export interface ${treeKey} extends newCategory<${treeKey}> {
`, 5);
let children = [];
makeTypes(subcategory, children, nextOptions, treeKey);
category_type += children.join('\n');
category_type += '\n}\n';
types += category_type;
parent.push(dent(` ${getJSDoc(helpMessage, 1)}
${category}: ${treeKey}
`, 5));
}
}
return types;
}
let rootType = dent(`export interface API extends updateOptions<API> {
`, 2);
let rootChildren = [];
const generated = makeTypes(schema, rootChildren, (schema === null || schema === void 0 ? void 0 : schema.$options) || {}, "API");
rootType += rootChildren.join('\n');
rootType += '\n}';
types += dent(`
${rootType}
declare const API: API;
export default API;
`, 2);
if (fs && fs.writeFileSync && options.output) {
fs.writeFileSync(config.output, types, {
encoding: "utf8"
});
}
return types;
}
module.exports = generateTypes;