UNPKG

odata

Version:

o.js is a isomorphic Odata Javascript library to simplify the request of data. The main goal is to build a standalone, lightweight and easy to understand Odata lib.

675 lines (662 loc) 28.6 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __awaiter(thisArg, _arguments, P, generator) { 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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } var encodeURIComponentStrict = function (str) { return encodeURIComponent(str).replace(/[!'()*]/g, function (c) { return "%" + c.charCodeAt(0).toString(16).toUpperCase(); }); }; var ORequest = /** @class */ (function () { function ORequest(url, config) { this.config = config; if (typeof url === "string") { this.url = new URL(url); } else { this.url = url; } } Object.defineProperty(ORequest.prototype, "fetch", { get: function () { var req = new Request(this.url.href, this.config); return fetch(req, this.config); }, enumerable: false, configurable: true }); ORequest.prototype.applyStringQuery = function (query, configQuery) { if (configQuery === void 0) { configQuery = {}; } var searchParams = new URLSearchParams(query); searchParams.forEach(function (value, key) { if (!query.hasOwnProperty(key)) { configQuery[key] = value; } }); return this.applyQuery(configQuery); }; ORequest.prototype.applyQuery = function (query) { if (query === void 0) { query = {}; } this.url.searchParams.forEach(function (value, key) { if (!query.hasOwnProperty(key)) { query[key] = value; } }); this.url.search = Object.entries(query) .map(function (_a) { var key = _a[0], value = _a[1]; return encodeURIComponentStrict(key) + "=" + encodeURIComponentStrict(value); }) .join("&"); return this; }; return ORequest; }()); var CRLF = "\r\n"; var OBatch = /** @class */ (function () { function OBatch(resources, config, query, changeset) { var _this = this; if (changeset === void 0) { changeset = false; } this.changeset = changeset; // "" here prevents 'undefined' at start of body under some conditions. this.batchBody = ""; this.batchConfig = __assign(__assign({}, config), config.batch); this.batchUid = this.getUid(); this.batchConfig.headers.set("Content-Type", "multipart/mixed; boundary=" + this.batchUid); if (this.batchConfig.batch.useChangset) { resources = this.checkForChangset(resources, query); } else { this.batchBody += "--" + this.batchUid; } resources.forEach(function (req) { return req.config.method === "GET" && req.applyQuery(query); }); var contentId = 0; this.batchBody += resources.map(function (req) { contentId++; return [ "", "Content-Type: application/http", "Content-Transfer-Encoding: binary", "Content-ID: " + contentId, "", req.config.method + " " + _this.getRequestURL(req) + " HTTP/1.1", "" + _this.getHeaders(req), "" + _this.getBody(req) ].join(CRLF); }).join(CRLF + "--" + this.batchUid); this.batchBody += CRLF + "--" + this.batchUid + "--" + CRLF; if (!changeset) { this.batchConfig.headers.set("Content-Type", "multipart/mixed;boundary=" + this.batchUid); } } OBatch.prototype.fetch = function (url) { return __awaiter(this, void 0, void 0, function () { var req, res, data; return __generator(this, function (_a) { switch (_a.label) { case 0: req = new ORequest(url, __assign(__assign({}, this.batchConfig), { body: this.batchBody, method: "POST" })); return [4 /*yield*/, req.fetch]; case 1: res = _a.sent(); if (!res.ok) return [3 /*break*/, 3]; return [4 /*yield*/, res.text()]; case 2: data = _a.sent(); return [2 /*return*/, this.parseResponse(data, res.headers.get("Content-Type"))]; case 3: throw res; } }); }); }; OBatch.prototype.parseResponse = function (responseData, contentTypeHeader) { var _this = this; var headers = contentTypeHeader.split("boundary="); var boundary = headers[headers.length - 1]; var splitData = responseData.split("--" + boundary); splitData.shift(); splitData.pop(); var wasWithChangesetresponse = false; var parsedData = splitData.map(function (data) { var dataSegments = data.trim().split("\r\n\r\n"); if (dataSegments.length === 0) { // we are unable to parse -> return all return data; } else if (dataSegments.length > 3) { var header = dataSegments.find(function (x) { return x.startsWith("Content-Type: ") && x.includes("boundary=changesetresponse_"); }); if (!header) { return data; } dataSegments.shift(); wasWithChangesetresponse = true; return _this.parseResponse(dataSegments.join("\r\n\r\n"), header); } else { var contentIdHeader = dataSegments[0].split("\r\n").find(function (x) { return x.startsWith("Content-ID: "); }); if (contentIdHeader) { try { var contentId = parseInt(contentIdHeader.substring(12), 10); } catch (ex) { } } var status = +dataSegments[1].split(" ")[1]; if (dataSegments.length === 3) { // if length == 3 we have a body, try to parse if JSON and return that! var body; try { var parsed = JSON.parse(dataSegments[2]); var hasFragment = parsed[_this.batchConfig.fragment]; body = hasFragment || parsed; } catch (ex) { body = dataSegments[2]; } } return { contentId: contentId, status: status, body: body }; } }); if (wasWithChangesetresponse) { return parsedData[0]; } return parsedData; }; /** * If we determine a changset (POST, PUT, PATCH) we initalize a new * OBatch instance for it. */ OBatch.prototype.checkForChangset = function (resources, query) { var changeRes = this.getChangeResources(resources); if (this.changeset) { this.batchBody += [ "", "Content-Type: multipart/mixed;boundary=" + this.batchUid, "", "--" + this.batchUid ].join(CRLF); } else if (changeRes.length > 0) { this.batchBody = "--" + this.batchUid; this.batchBody += new OBatch(changeRes, this.batchConfig, query, true).batchBody; resources = this.getGETResources(resources); } else { this.batchBody = "--" + this.batchUid; } return resources; }; OBatch.prototype.getGETResources = function (resources) { return resources.filter(function (req) { return req.config.method === "GET"; }); }; OBatch.prototype.getChangeResources = function (resources) { return resources.filter(function (req) { return req.config.method !== "GET"; }); }; OBatch.prototype.getBody = function (req) { if (req.config.body) { return "" + req.config.body + CRLF + CRLF; } return ""; }; OBatch.prototype.getUid = function () { var d = new Date().getTime(); var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { var r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c === "x" ? r : (r & 0x7) | 0x8).toString(16); }); return "" + (this.changeset ? this.batchConfig.batch.changsetBoundaryPrefix : this.batchConfig.batch.boundaryPrefix) + uuid; }; OBatch.prototype.getHeaders = function (req) { // Request headers can be Headers | string[][] | Record<string, string>. // A new Headers instance around them allows treatment of all three types // to be the same. This also applies security last two could bypass. var headers = new Headers(req.config.headers || undefined); // Convert each header to single string. // Headers is iterable. Array.from is needed instead of Object.keys. var mapped = Array.from(headers).map(function (_a) { var k = _a[0], v = _a[1]; return k + ": " + v; }); if (mapped.length) { // Need to ensure a blank line between HEADERS and BODY. When there are // headers, it must be added here. Otherwise blank is added in ctor. mapped.push(""); } return mapped.join(CRLF); }; OBatch.prototype.getRequestURL = function (req) { var href = req.url.href; if (this.batchConfig.batch.useRelativeURLs) { // Strip away matching root from request. href = href.replace(this.batchConfig.rootUrl.href, ""); } return href; }; return OBatch; }()); var OHandler = /** @class */ (function () { function OHandler(config) { this.config = config; this.requests = []; } /** * Does a fetch request to the given endpoint and request * all resources in sequent. Tries to parse the result logical * so that no further processing is used. If the result is only one * entity a object is returned, otherwise a array of objects. * * @example * ```typescript * const russell = await o('https://services.odata.org/TripPinRESTierService/') * .get('People("russellwhyte")) * .query(); * * console.log(russell); // shows: { FirstName: "Russell", LastName: "Whyte" [...] } * ``` * * If the request fails with an error code higher then 400 it throws the * Response: * * @example * ```typescript * try { * const unknown = await o('https://services.odata.org/TripPinRESTierService/') * .get('People("unknown")) * .query(); * } catch(res) { // Response * console.log(res.status); // 404 * } * ``` * * @param query The URLSearchParams that are added to the question mark on the url. * That are usually the odata queries like $filter, $top, etc... or a string of parameters. * @returns Either an array or a object with the given entities. If multiple * resources are fetched, this method returns a array of array/object. If there * is no content (e.g. for delete) this method returns the Response */ OHandler.prototype.query = function (query) { return __awaiter(this, void 0, void 0, function () { var response, json, ex_1; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 3, 4, 5]); this.config.onStart(this); return [4 /*yield*/, this.getFetch(query)]; case 1: response = _a.sent(); return [4 /*yield*/, Promise.all(response.map(function (res) { return __awaiter(_this, void 0, void 0, function () { var data, ex_2; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!(res.status >= 400)) return [3 /*break*/, 1]; throw res; case 1: if (!(res.ok && res.json)) return [3 /*break*/, 6]; _a.label = 2; case 2: _a.trys.push([2, 4, , 5]); this.config.onFinish(this, res); return [4 /*yield*/, res.json()]; case 3: data = _a.sent(); return [2 /*return*/, data[this.config.fragment] || data]; case 4: ex_2 = _a.sent(); return [2 /*return*/, res]; case 5: return [3 /*break*/, 8]; case 6: return [4 /*yield*/, res.text()]; case 7: return [2 /*return*/, _a.sent()]; case 8: return [2 /*return*/]; } }); }); }))]; case 2: json = _a.sent(); return [2 /*return*/, json.length > 1 ? json : json[0]]; case 3: ex_1 = _a.sent(); this.config.onError(this, ex_1); throw ex_1; case 4: this.requests = []; return [7 /*endfinally*/]; case 5: return [2 /*return*/]; } }); }); }; /** * Request all requests in sequent. Does simply return a Response or Response[] * without any data parsing applied. * * @param query The URLSearchParams that are added to the question mark on the url. * That are usually the odata queries like $filter, $top, etc... */ OHandler.prototype.fetch = function (query) { return __awaiter(this, void 0, void 0, function () { var fetch_1, ex_3; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, 3, 4]); this.config.onStart(this); return [4 /*yield*/, this.getFetch(query)]; case 1: fetch_1 = _a.sent(); return [2 /*return*/, fetch_1.length === 1 ? fetch_1[0] : fetch_1]; case 2: ex_3 = _a.sent(); this.config.onError(this, ex_3); throw ex_3; case 3: this.config.onFinish(this); this.requests = []; return [7 /*endfinally*/]; case 4: return [2 /*return*/]; } }); }); }; /** * Does a batch http-batch request. All request in that sequent are send via one * physically request and afterwards parsed to separate data chunks. * * @param query The URLSearchParams that are added to the question mark on the url. * That are usually the odata queries like $filter, $top, etc... */ OHandler.prototype.batch = function (query) { return __awaiter(this, void 0, void 0, function () { var batch, url, data, ex_4; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, 3, 4]); batch = new OBatch(this.requests, this.config, query); url = this.getUrl(this.config.batch.endpoint); return [4 /*yield*/, batch.fetch(url)]; case 1: data = _a.sent(); return [2 /*return*/, data]; case 2: ex_4 = _a.sent(); this.config.onError(this, ex_4); throw ex_4; case 3: this.requests = []; return [7 /*endfinally*/]; case 4: return [2 /*return*/]; } }); }); }; /** * Gets the data from the endpoint + resource url. * * @param resource The resource to request e.g. People/$value. */ OHandler.prototype.get = function (resource) { if (resource === void 0) { resource = ""; } var url = this.getUrl(resource); var request = new ORequest(url, __assign(__assign({}, this.config), { method: "GET" })); this.requests.push(request); return this; }; /** * Post data to an endpoint + resource. * * @param resource The resource to post to. * @param body The data to post. */ OHandler.prototype.post = function (resource, body) { if (resource === void 0) { resource = ""; } var url = this.getUrl(resource); var request = new ORequest(url, __assign(__assign({}, this.config), { method: "POST", body: this.getBody(body) })); this.requests.push(request); return this; }; /** * Put data to an endpoint + resource. * * @param resource The resource to put to. * @param body The data to put. */ OHandler.prototype.put = function (resource, body) { if (resource === void 0) { resource = ""; } var url = this.getUrl(resource); var request = new ORequest(url, __assign(__assign({}, this.config), { method: "PUT", body: this.getBody(body) })); this.requests.push(request); return this; }; /** * Patch data to an endpoint + resource. * * @param resource The resource to patch to. * @param body The data to patch. */ OHandler.prototype.patch = function (resource, body) { if (resource === void 0) { resource = ""; } var url = this.getUrl(resource); var request = new ORequest(url, __assign(__assign({}, this.config), { body: this.getBody(body), method: "PATCH" })); this.requests.push(request); return this; }; /** * Deletes a resource from the endpoint. * * @param resource The resource to delete e.g. People/1 */ OHandler.prototype.delete = function (resource) { if (resource === void 0) { resource = ""; } var url = this.getUrl(resource); var request = new ORequest(url, __assign(__assign({}, this.config), { method: "DELETE" })); this.requests.push(request); return this; }; /** * Use that method to add any kind of request (e.g. a head request) to * the execution list. * * @example * ```typescript * const req = new ORequest('http://full.url/healt', { method: 'HEAD'}); * const res = await o('http://another.url').request(req).fetch(); * console.log(res.status); // e.g. 200 from http://full.url/healt * ``` * @param req The request to add. */ OHandler.prototype.request = function (req) { this.requests.push(req); return this; }; Object.defineProperty(OHandler.prototype, "pending", { /** * Determines how many request are outstanding. */ get: function () { return this.requests.length; }, enumerable: false, configurable: true }); /** * Returns a URL based on the rootURL + the given resource * @param resource The resource to join. */ OHandler.prototype.getUrl = function (resource) { return new URL(resource, this.config.rootUrl); }; OHandler.prototype.getFetch = function (query) { return __awaiter(this, void 0, void 0, function () { var result, _i, _a, req, request; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!(this.pending > 1)) return [3 /*break*/, 5]; result = []; _i = 0, _a = this.requests; _b.label = 1; case 1: if (!(_i < _a.length)) return [3 /*break*/, 4]; req = _a[_i]; if (typeof query === "string") { req.applyStringQuery(query, this.config.query); } else { req.applyQuery(__assign(__assign({}, this.config.query), query)); } return [4 /*yield*/, req.fetch]; case 2: request = _b.sent(); result.push(request); _b.label = 3; case 3: _i++; return [3 /*break*/, 1]; case 4: return [2 /*return*/, result]; case 5: if (typeof query === "string") { this.requests[0].applyStringQuery(query, this.config.query); } else { this.requests[0].applyQuery(__assign(__assign({}, this.config.query), query)); } return [4 /*yield*/, this.requests[0].fetch]; case 6: return [2 /*return*/, [_b.sent()]]; } }); }); }; OHandler.prototype.getBody = function (body) { if (body instanceof Object) { return JSON.stringify(body); } return body; }; return OHandler; }()); /** * Use the 'o'-function to initialize a request directly or use the returned * handler to store the settings. * * Use o() directly jquery like: * @example * ```typescript * await o('https://rootUrl').get('resource').query(); * ``` * * Or with a handler: * @example * ```typescript * const oHandler = o('https://rootUrl'); * await oHandler.get('resource').query({ $top: 2 }); * ``` * * @param rootUrl The url to query * @param config The odata and fetch configuration. */ function o(rootUrl, config) { if (config === void 0) { config = {}; } // set the default configuration values var defaultConfigValues = { batch: { boundaryPrefix: "batch_", changsetBoundaryPrefix: "changset_", endpoint: "$batch", headers: new Headers({ "Content-Type": "multipart/mixed", }), useChangset: false, useRelativeURLs: false, }, credentials: "omit", fragment: "value", headers: new Headers({ "Content-Type": "application/json", }), mode: "cors", redirect: "follow", referrer: typeof window === "undefined" ? undefined : "client", onStart: function () { return null; }, onError: function () { return null; }, onFinish: function () { return null; }, }; var mergedConfig = __assign(__assign({}, defaultConfigValues), config); if (typeof rootUrl === "string") { try { // we assuming a resource var configUrl = (mergedConfig.rootUrl || window.location.href); rootUrl = new URL(rootUrl, configUrl.endsWith("/") ? configUrl : configUrl + "/"); } catch (ex) { // no window?! rootUrl = new URL(rootUrl, mergedConfig.rootUrl); } } mergedConfig.rootUrl = rootUrl; return new OHandler(mergedConfig); } exports.o = o; exports.OBatch = OBatch; exports.OHandler = OHandler; exports.ORequest = ORequest; //# sourceMappingURL=o.js.map