box-node-sdk
Version:
Official SDK for Box Plaform APIs
247 lines • 9.65 kB
JavaScript
"use strict";
/**
* @fileoverview Iterator for paged responses
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
const qs = __importStar(require("querystring"));
const bluebird_1 = require("bluebird");
const PromiseQueue = require("promise-queue");
// -----------------------------------------------------------------------------
// Typedefs
// -----------------------------------------------------------------------------
/**
* The iterator response object
* @typedef {Object} IteratorData
* @property {Array} [value] - The next set of values from the iterator
* @property {boolean} done - Whether the iterator is completed
*/
/**
* Iterator callback
* @callback IteratorCallback
* @param {?Error} err - An error if the iterator encountered one
* @param {IteratorData} [data] - New data from the iterator
* @returns {void}
*/
// -----------------------------------------------------------------------------
// Requirements
// -----------------------------------------------------------------------------
const errors = require('./errors');
PromiseQueue.configure(bluebird_1.Promise);
// -----------------------------------------------------------------------------
// Private
// -----------------------------------------------------------------------------
const PAGING_MODES = Object.freeze({
MARKER: 'marker',
OFFSET: 'offset',
});
// -----------------------------------------------------------------------------
// Public
// -----------------------------------------------------------------------------
/**
* Asynchronous iterator for paged collections
*/
class PagingIterator {
/**
* Determine if a response is iterable
* @param {Object} response - The API response
* @returns {boolean} Whether the response is iterable
*/
static isIterable(response /* FIXME */) {
// POST responses for uploading a file are explicitly excluded here because, while the response is iterable,
// it always contains only a single entry and historically has not been handled as iterable in the SDK.
// This behavior is being preserved here to avoid a breaking change.
let UPLOAD_PATTERN = /.*upload\.box\.com.*\/content/;
var isGetOrPostRequest = response.request &&
(response.request.method === 'GET' ||
(response.request.method === 'POST' &&
!UPLOAD_PATTERN.test(response.request.uri.href))), hasEntries = response.body && Array.isArray(response.body.entries), notEventStream = response.body && !response.body.next_stream_position;
return Boolean(isGetOrPostRequest && hasEntries && notEventStream);
}
/**
* @constructor
* @param {Object} response - The original API response
* @param {BoxClient} client - An API client to make further requests
* @returns {void}
* @throws {Error} Will throw when collection cannot be paged
*/
constructor(response /* FIXME */, client /* FIXME */) {
if (!PagingIterator.isIterable(response)) {
throw new Error('Cannot create paging iterator for non-paged response!');
}
var data = response.body;
if (Number.isSafeInteger(data.offset)) {
this.nextField = PAGING_MODES.OFFSET;
this.nextValue = data.offset;
}
else if (typeof data.next_marker === 'undefined') {
// Default to a finished marker collection when there's no field present,
// since some endpoints indicate completed paging this way
this.nextField = PAGING_MODES.MARKER;
this.nextValue = null;
}
else {
this.nextField = PAGING_MODES.MARKER;
this.nextValue = data.next_marker;
}
this.limit = data.limit || data.entries.length;
this.done = false;
var href = response.request.href.split('?')[0];
this.options = {
headers: response.request.headers,
qs: qs.parse(response.request.uri.query),
};
if (response.request.body) {
if (Object.prototype.toString.call(response.request.body) ===
'[object Object]') {
this.options.body = response.request.body;
}
else {
this.options.body = JSON.parse(response.request.body);
}
}
// querystring.parse() makes everything a string, ensure numeric params are the correct type
if (this.options.qs.limit) {
this.options.qs.limit = parseInt(this.options.qs.limit, 10);
}
if (this.options.qs.offset) {
this.options.qs.offset = parseInt(this.options.qs.offset, 10);
}
delete this.options.headers.Authorization;
if (response.request.method === 'GET') {
this.fetch = client.get.bind(client, href);
}
if (response.request.method === 'POST') {
this.fetch = client.post.bind(client, href);
}
this.buffer = response.body.entries;
this.queue = new PromiseQueue(1, Infinity);
this._updatePaging(response);
}
/**
* Update the paging parameters for the iterator
* @private
* @param {Object} response - The latest API response
* @returns {void}
*/
_updatePaging(response /* FIXME */) {
var data = response.body;
if (this.nextField === PAGING_MODES.OFFSET) {
this.nextValue += this.limit;
if (Number.isSafeInteger(data.total_count)) {
this.done = data.offset + this.limit >= data.total_count;
}
else {
this.done = data.entries.length === 0;
}
}
else if (this.nextField === PAGING_MODES.MARKER) {
if (data.next_marker) {
this.nextValue = data.next_marker;
}
else {
this.nextValue = null;
this.done = true;
}
}
if (response.request.method === 'GET') {
this.options.qs[this.nextField] = this.nextValue;
}
else if (response.request.method === 'POST') {
if (!this.options.body) {
this.options.body = {};
}
this.options.body[this.nextField] = this.nextValue;
let bodyString = JSON.stringify(this.options.body);
this.options.headers['content-length'] = bodyString.length;
}
}
/**
* Fetch the next page of results
* @returns {Promise} Promise resolving to iterator state
*/
_getData() {
return this.fetch(this.options).then((response /* FIXME */) => {
if (response.statusCode !== 200) {
throw errors.buildUnexpectedResponseError(response);
}
this._updatePaging(response);
this.buffer = this.buffer.concat(response.body.entries);
if (this.buffer.length === 0) {
if (this.done) {
return {
value: undefined,
done: true,
};
}
// If we didn't get any data in this page, but the paging
// parameters indicate that there is more data, attempt
// to fetch more. This occurs in multiple places in the API
return this._getData();
}
return {
value: this.buffer.shift(),
done: false,
};
});
}
/**
* Fetch the next page of the collection
* @returns {Promise} Promise resolving to iterator state
*/
next() {
if (this.buffer.length > 0) {
return bluebird_1.Promise.resolve({
value: this.buffer.shift(),
done: false,
});
}
if (this.done) {
return bluebird_1.Promise.resolve({
value: undefined,
done: true,
});
}
return this.queue.add(this._getData.bind(this));
}
/**
* Fetch the next marker
* @returns {string|int} String that is the next marker or int that is the next offset
*/
getNextMarker() {
return this.nextValue;
}
}
module.exports = PagingIterator;
//# sourceMappingURL=paging-iterator.js.map