UNPKG

rests

Version:

Easily generate API client's SDK — organize and simplify API Requests.

432 lines (431 loc) 22.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); ; function Rests(endpoints, options) { const fetch = (typeof window !== 'undefined') ? window === null || window === void 0 ? void 0 : window.fetch : require("node-fetch"), FormData = (typeof window !== 'undefined') ? window === null || window === void 0 ? void 0 : window.FormData : require("form-data"); if (!fetch) { throw new Error("Fetch API is not installed. If you are using Node please run `npm install node-fetch`"); } if (!FormData) { throw new Error("FormData is not installed. If you are using Node please run `npm install form-data`"); } const copyOptions = (o) => (Object.assign(Object.assign({}, o), { headers: Object.assign({}, o.headers), params: Object.assign({}, o.params), values: Object.assign({}, o.values) })); const parseSet = (values) => { var _a, _b; if (((_b = (_a = values === null || values === void 0 ? void 0 : values.__proto__) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.name) != "Object") { throw new Error("Invalid $options object."); } let saveOptions = copyOptions(values.$options || {}); delete values['$options']; return Object.assign(Object.assign({}, saveOptions), { values: Object.assign(Object.assign({}, saveOptions.values), values) }); }; const mergeOptions = (prevOptions, currentOptions, mutate = false //Mutate the previous options? ) => { let firstOptions = mutate ? prevOptions || {} : copyOptions(prevOptions || {}); let secondOptions = copyOptions(currentOptions || {}); secondOptions.headers = Object.assign(Object.assign({}, firstOptions.headers), secondOptions.headers); secondOptions.params = Object.assign(Object.assign({}, firstOptions.params), secondOptions.params); secondOptions.values = Object.assign(Object.assign({}, firstOptions.values), secondOptions.values); Object.assign(firstOptions, secondOptions); return firstOptions; }; endpoints = Object.assign({}, endpoints); let global_options = { base: "", headers: { 'User-Agent': 'Rests JS (v1.1.1)' }, params: {}, values: {}, on_error: void 0, on_success: void 0, on_request: void 0, fetch_agent: null, }; mergeOptions(global_options, ((endpoints === null || endpoints === void 0 ? void 0 : endpoints.$options) || {}), true); mergeOptions(global_options, options, true); const def_param_enctypes = { "json": "application/json", "form": "multipart/form-data", "urlencoded": "application/x-www-form-urlencoded", "text": "text/plain" }, allowed_param_enctypes = Object.values(def_param_enctypes), allowed_param_locations = ["headers", "body", "query", "path"], def_param_locations = { 'POST': 'body', 'GET': 'query', }; const serializers = { 'multipart/form-data': (function () { var formData = new FormData(); formData.toString = function () { return this; }; return formData; }), 'application/x-www-form-urlencoded': (function () { return new URLSearchParams(); }), 'application/json': (function () { return { append: function (key, value) { this.data = this.data || {}; this.data[key] = value; }, toString: function () { return JSON.stringify(this.data); }, isEmpty: function () { return (!this.data || Object.keys(this.data).length == 0); } }; }), 'text/plain': (function () { return { append: function (_, value) { this.data = this.data || []; return this.data.push(value); }, toString: function () { return this.data.join(''); }, isEmpty: function () { return (!this.data || this.data.length == 0); } }; }) }; const isNull = (value) => { return value === null || value === undefined; }; const isEmptyIterable = (iterable) => { for (var _ of iterable) { return false; } return true; }; const escapeRegExp = (string) => { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }; const capitalize = (string) => { return string.substr(0, 1).toUpperCase() + string.substr(1, string.length); }; const formToJson = (f) => { return Object.fromEntries(Array.from(f.keys(), (k) => (k.endsWith('[]') ? [k.slice(0, -2), f.getAll(k)] : [k, f.get(k)]))); }; const get = (t, path) => (path.split(".").reduce((r, k) => r === null || r === void 0 ? void 0 : r[k], t)); const getOne = (...args) => { for (var i = 0; i < args.length; i++) { if (args[i] !== null && args[i] !== undefined) { return args[i]; } } return null; }; /** * Fetch API */ const sendRequest = (url, options, currentOptions, requestInfo) => __awaiter(this, void 0, void 0, function* () { return fetch(url, options) .then(function (res) { var _a; return __awaiter(this, void 0, void 0, function* () { try { var contentType = res.headers.get('Content-Type') || ''; let corsType; try { corsType = res.type; } catch (err) { //prevent errors on cloudflare workers } let formattedResponse = { statusCode: res.status, statusText: res.statusText, headers: res.headers, type: corsType, ok: res.ok }; let responseTypes = { 'application\/json': 'json', 'text\/plain': 'text', '(multipart\/form\-data|application\/x\-www\-form\-urlencoded)': 'formData', 'blob': 'blob', 'text': 'text' }; let currentResponseType = Object.keys(responseTypes).find((responseType) => (new RegExp(responseType)).test(contentType)) || "text"; formattedResponse[responseTypes[currentResponseType]] = yield res[responseTypes[currentResponseType]](); formattedResponse['message'] = ((_a = formattedResponse === null || formattedResponse === void 0 ? void 0 : formattedResponse.json) === null || _a === void 0 ? void 0 : _a.message) || (res.ok ? "Success." : "Something went wrong."); if (!res.ok) { throw formattedResponse; } if (currentOptions.on_success) { let successCallbackRes = currentOptions.on_success(formattedResponse, requestInfo); if (successCallbackRes !== undefined) { return successCallbackRes; } } return formattedResponse; } catch (err) { if (currentOptions.on_error) { let errorCallbackRes = currentOptions.on_error(err, requestInfo); if (errorCallbackRes !== undefined) { return errorCallbackRes; } } return Promise.reject(err); } }); }); }); /** * Request Wrapper */ function wrap(endpoint, categoryOptions, categoryKey) { endpoint.method = (endpoint.method || "get").toUpperCase(), endpoint.params = endpoint.params || {}; const sender = function (params) { var _a, _b, _c, _d, _e, _f; return __awaiter(this, void 0, void 0, function* () { if (this instanceof sender) { throw new Error("This is an endpoint, you can't initialize this."); } var currentOptions = mergeOptions(global_options, categoryOptions); currentOptions.on_request = endpoint.on_request || currentOptions.on_request; currentOptions.on_success = endpoint.on_success || currentOptions.on_success; currentOptions.on_error = endpoint.on_error || currentOptions.on_error; var url = `${currentOptions.base}${endpoint.path}`; if ((params === null || params === void 0 ? void 0 : params['$sandbox']) || ((_a = currentOptions === null || currentOptions === void 0 ? void 0 : currentOptions.values) === null || _a === void 0 ? void 0 : _a['$sandbox'])) { url = `${currentOptions.sandboxBase || currentOptions.base}${endpoint.path}`; } var options = { method: endpoint.method, headers: Object.assign({}, currentOptions.headers), agent: currentOptions.fetch_agent }; var enctype = allowed_param_enctypes.includes(endpoint.enctype) ? endpoint.enctype : def_param_enctypes[endpoint.enctype || "json"]; var request_params = Object.assign({}, currentOptions.params, endpoint.params); var bodySerializer = serializers[enctype](), querySerializer = new URLSearchParams(); /** * Parse Params */ if (((_b = params === null || params === void 0 ? void 0 : params.constructor) === null || _b === void 0 ? void 0 : _b.name) == 'FormData') { params = formToJson(params); } else { params = params || {}; } for (var param_name in request_params) { var param = request_params[param_name]; var current_param_value = params[param_name], options_param_value = (_c = currentOptions === null || currentOptions === void 0 ? void 0 : currentOptions.values) === null || _c === void 0 ? void 0 : _c[param_name], default_param_value = param.default, example_param_value = ((params === null || params === void 0 ? void 0 : params['$sandbox']) || ((_d = currentOptions === null || currentOptions === void 0 ? void 0 : currentOptions.values) === null || _d === void 0 ? void 0 : _d['$sandbox'])) ? param.example : undefined; var param_value = getOne(current_param_value, options_param_value, example_param_value, default_param_value); var param_dest = param.name || param_name; var param_error = param.help || `The '${param_name}' field is invalid.`; //Required Param or not if (param.required && isNull(param_value)) { var error = new Error(param_error); error.field = param_name; throw error; } //Skip, not required if (isNull(param_value)) continue; //Formatter function? if (typeof param.format === "function") { try { param_value = param.format(param_value); } catch (e) { var error = new Error(e.message || param_error); error.field = param_name; throw error; } } //Type if (param.type && param.type !== "any") { var error = new Error(param_error); error.field = param_name; if ((["string", "boolean", "number"].includes(param.type) && typeof param_value != param.type) || (param.type == "array" && !Array.isArray(param_value)) || (param.type == "object" && (!param_value || param_value.__proto__.constructor.name !== "Object"))) { throw error; } } //Validate if (param.validate) { if (((_f = (_e = param.validate) === null || _e === void 0 ? void 0 : _e.constructor) === null || _f === void 0 ? void 0 : _f.name) == "RegExp") { param.validate['toJSON'] = function () { return param.validate.toString().replace(/^\//g, '').replace(/\/$/g, ''); }; } if (!(new RegExp(param.validate).test(param_value))) { var error = new Error(param_error); error.field = param_name; throw error; } } //Max/Min if (param.type == "number") { if (param.hasOwnProperty('max') && !isNaN(param.max) && Number(param_value) > Number(param.max)) { var error = new Error(`The maximum allowed value allowed for the ${param_dest} parameter is ${param.max}`); error.field = param_name; throw error; } if (param.hasOwnProperty('min') && !isNaN(param.min) && Number(param_value) < Number(param.min)) { var error = new Error(`The minimum allowed value allowed for the ${param_dest} parameter is ${param.min}`); error.field = param_name; throw error; } } //In if (param.in && Array.isArray(param.in) && !param.in.includes(param_value)) { var error = new Error(`The ${param_dest} parameter should be one of these values: ${param.in}`); error.field = param_name; throw error; } //Location var param_location = (typeof param.location === "string" ? param.location.toLowerCase() : def_param_locations[options.method]); if (!param_location || !allowed_param_locations.includes(param_location)) { throw new Error(`Invalid location for '${param_name}' field.`); } if (param_location == "headers") { options['headers'] = options['headers'] || {}; options['headers'][param_dest] = param_value; continue; } if (param_location == "body") { bodySerializer.append(param_dest, param_value); continue; } if (param_location == "query") { querySerializer.append(param_dest, param_value); continue; } if (param_location == "path") { url = url.replace(new RegExp(`\{${escapeRegExp(param_dest).trim()}\}`), param_value); } } //Set Query var hasQuery = querySerializer.toString(); if (hasQuery) { url = `${url}?${hasQuery}`; } //Set Body var isEmptyBody = (bodySerializer.keys && isEmptyIterable(bodySerializer.keys())) || (bodySerializer.getLengthSync && bodySerializer.getLengthSync() == 0) || (bodySerializer.isEmpty && bodySerializer.isEmpty()); if (!isEmptyBody) { options['body'] = bodySerializer.toString(); } //Set content-type header, (not set for multipart/form-data because it overrides the automatically generated multipart key) if (options['body'] && enctype !== 'multipart/form-data') { options['headers'] = options['headers'] || {}; options['headers']['Content-Type'] = enctype; } let requestInfo = { url: url, options: options, params: params, key: categoryKey, instance: global_options['__$root_instance__'], self: wrap(endpoint, categoryOptions, categoryKey) }; //Pre-Request Middleware if (currentOptions.on_request) { var requestCallbackRes = yield Promise.resolve(currentOptions.on_request(requestInfo)); if (requestCallbackRes) { if ((requestCallbackRes === null || requestCallbackRes === void 0 ? void 0 : requestCallbackRes.url) || (requestCallbackRes === null || requestCallbackRes === void 0 ? void 0 : requestCallbackRes.options)) { url = requestCallbackRes.url || url; options = requestCallbackRes.options || options; } else { return requestCallbackRes; } } if (requestCallbackRes === false) { return false; } } return sendRequest(url, options, currentOptions, requestInfo); }); }; return sender; } /** * Initalize a category and set values for it's endpoints. (Full category options can be updated with special $options key) */ function newCategory(name, categoryOptions, categoryName, isRoot) { name = name || "Rests"; var New = { [name]: (function (values) { if (!(this instanceof New[name])) { throw new Error("This is a category, you can initalize this category to update values using 'new' command."); } if (isRoot) { throw new Error("This is already initialized, you can use 'set' instead."); } let currentOptions = mergeOptions(global_options, categoryOptions); let updateOptions = parseSet(values); let newOptions = mergeOptions(currentOptions, updateOptions); return Rests(categoryName ? get(endpoints, categoryName) : endpoints, newOptions); }) }; if (isRoot) { /** * Root object can update it's options */ New[name]['set'] = function (values) { if (this instanceof New[name]['set']) { throw new Error("The set object can't be initialized."); } let updateOptions = parseSet(values); //Mutate Global Options mergeOptions(global_options, updateOptions, true); return New[name]; }; } return New[name]; } /** * Recursive loop on schema and make wrappers */ function traverse(root, schema, categoryOptions, categoryKey) { for (var category in schema) { var tree = schema[category]; if (!tree || typeof tree !== 'object') { continue; } //Skip duplicate keys in main object root if (typeof root[category] !== "undefined") { console.warn(`Skipping ${category} as it confilicts with another key in the object`); continue; } let categoryName = `${categoryKey ? categoryKey + '.' : ''}${category}`; //Is Endpoint if (tree.hasOwnProperty('path')) { var endpoint = tree; root[category] = wrap(endpoint, categoryOptions, categoryName); } //Is Category, recursion else { // Don't make category for special objects if (category.substr(0, 1) === '$') { continue; } let nextOptions = categoryOptions; if (tree === null || tree === void 0 ? void 0 : tree['$options']) { nextOptions = mergeOptions(categoryOptions, tree === null || tree === void 0 ? void 0 : tree['$options']); } root[category] = traverse(newCategory(category, nextOptions, categoryName), tree, nextOptions, categoryName); } } return root; } const rootCategory = Object.defineProperty(newCategory("Rests", global_options, undefined, true), '__schema__', { value: { schema: endpoints, options: global_options }, writable: false, enumerable: false }); global_options['__$root_instance__'] = rootCategory; return traverse(rootCategory, endpoints, {}); } Rests.default = Rests; exports.default = Rests; module.exports = Rests;