prismic-javascript
Version:
JavaScript development kit for prismic.io
1,243 lines (1,226 loc) • 51.6 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('cross-fetch')) :
typeof define === 'function' && define.amd ? define(['cross-fetch'], factory) :
(global = global || self, global.PrismicJS = factory(global.fetch));
}(this, (function (crossFetch) { 'use strict';
crossFetch = crossFetch && Object.prototype.hasOwnProperty.call(crossFetch, 'default') ? crossFetch['default'] : crossFetch;
/*! *****************************************************************************
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);
};
var Variation = /** @class */ (function () {
function Variation(data) {
this.data = {};
this.data = data;
}
Variation.prototype.id = function () {
return this.data.id;
};
Variation.prototype.ref = function () {
return this.data.ref;
};
Variation.prototype.label = function () {
return this.data.label;
};
return Variation;
}());
var Experiment = /** @class */ (function () {
function Experiment(data) {
this.data = {};
this.data = data;
this.variations = (data.variations || []).map(function (v) {
return new Variation(v);
});
}
Experiment.prototype.id = function () {
return this.data.id;
};
Experiment.prototype.googleId = function () {
return this.data.googleId;
};
Experiment.prototype.name = function () {
return this.data.name;
};
return Experiment;
}());
var Experiments = /** @class */ (function () {
function Experiments(data) {
if (data) {
this.drafts = (data.drafts || []).map(function (exp) {
return new Experiment(exp);
});
this.running = (data.running || []).map(function (exp) {
return new Experiment(exp);
});
}
}
Experiments.prototype.current = function () {
if (this.running.length > 0) {
return this.running[0];
}
else {
return null;
}
};
Experiments.prototype.refFromCookie = function (cookie) {
if (!cookie || cookie.trim() === '')
return null;
var splitted = cookie.trim().split(' ');
if (splitted.length < 2)
return null;
var expId = splitted[0];
var varIndex = parseInt(splitted[1], 10);
var exp = this.running.filter(function (exp) {
return exp.googleId() === expId && exp.variations.length > varIndex;
})[0];
return exp ? exp.variations[varIndex].ref() : null;
};
return Experiments;
}());
var LazySearchForm = /** @class */ (function () {
function LazySearchForm(id, api) {
this.id = id;
this.api = api;
this.fields = {};
}
LazySearchForm.prototype.set = function (key, value) {
this.fields[key] = value;
return this;
};
LazySearchForm.prototype.ref = function (ref) {
return this.set('ref', ref);
};
LazySearchForm.prototype.query = function (query) {
return this.set('q', query);
};
LazySearchForm.prototype.pageSize = function (size) {
return this.set('pageSize', size);
};
LazySearchForm.prototype.fetch = function (fields) {
console.warn('Warning: Using Fetch is deprecated. Use the property `graphQuery` instead.');
return this.set('fetch', fields);
};
LazySearchForm.prototype.fetchLinks = function (fields) {
console.warn('Warning: Using FetchLinks is deprecated. Use the property `graphQuery` instead.');
return this.set('fetchLinks', fields);
};
LazySearchForm.prototype.graphQuery = function (query) {
return this.set('graphQuery', query);
};
LazySearchForm.prototype.lang = function (langCode) {
return this.set('lang', langCode);
};
LazySearchForm.prototype.page = function (p) {
return this.set('page', p);
};
LazySearchForm.prototype.after = function (documentId) {
return this.set('after', documentId);
};
LazySearchForm.prototype.orderings = function (orderings) {
return this.set('orderings', orderings);
};
LazySearchForm.prototype.url = function () {
var _this = this;
return this.api.get().then(function (api) {
return LazySearchForm.toSearchForm(_this, api).url();
});
};
LazySearchForm.prototype.submit = function (cb) {
var _this = this;
return this.api.get().then(function (api) {
return LazySearchForm.toSearchForm(_this, api).submit(cb);
});
};
LazySearchForm.toSearchForm = function (lazyForm, api) {
var form = api.form(lazyForm.id);
if (form) {
return Object.keys(lazyForm.fields).reduce(function (form, fieldKey) {
var fieldValue = lazyForm.fields[fieldKey];
if (fieldKey === 'q') {
return form.query(fieldValue);
}
else if (fieldKey === 'pageSize') {
return form.pageSize(fieldValue);
}
else if (fieldKey === 'fetch') {
return form.fetch(fieldValue);
}
else if (fieldKey === 'fetchLinks') {
return form.fetchLinks(fieldValue);
}
else if (fieldKey === 'graphQuery') {
return form.graphQuery(fieldValue);
}
else if (fieldKey === 'lang') {
return form.lang(fieldValue);
}
else if (fieldKey === 'page') {
return form.page(fieldValue);
}
else if (fieldKey === 'after') {
return form.after(fieldValue);
}
else if (fieldKey === 'orderings') {
return form.orderings(fieldValue);
}
else {
return form.set(fieldKey, fieldValue);
}
}, form);
}
else {
throw new Error("Unable to access to form " + lazyForm.id);
}
};
return LazySearchForm;
}());
var SearchForm = /** @class */ (function () {
function SearchForm(form, httpClient) {
this.httpClient = httpClient;
this.form = form;
this.data = {};
for (var field in form.fields) {
if (form.fields[field]['default']) {
this.data[field] = [form.fields[field]['default']];
}
}
}
SearchForm.prototype.set = function (field, value) {
var fieldDesc = this.form.fields[field];
if (!fieldDesc)
throw new Error('Unknown field ' + field);
var checkedValue = value === '' || value === undefined ? null : value;
var values = this.data[field] || [];
if (fieldDesc.multiple) {
values = checkedValue ? values.concat([checkedValue]) : values;
}
else {
values = checkedValue ? [checkedValue] : values;
}
this.data[field] = values;
return this;
};
/**
* Sets a ref to query on for this SearchForm. This is a mandatory
* method to call before calling submit(), and api.form('everything').submit()
* will not work.
*/
SearchForm.prototype.ref = function (ref) {
return this.set('ref', ref);
};
/**
* Sets a predicate-based query for this SearchForm. This is where you
* paste what you compose in your prismic.io API browser.
*/
SearchForm.prototype.query = function (query) {
if (typeof query === 'string') {
return this.query([query]);
}
else if (Array.isArray(query)) {
return this.set('q', "[" + query.join('') + "]");
}
else {
throw new Error("Invalid query : " + query);
}
};
/**
* Sets a page size to query for this SearchForm. This is an optional method.
*
* @param {number} size - The page size
* @returns {SearchForm} - The SearchForm itself
*/
SearchForm.prototype.pageSize = function (size) {
return this.set('pageSize', size);
};
/**
* Restrict the results document to the specified fields
*/
SearchForm.prototype.fetch = function (fields) {
console.warn('Warning: Using Fetch is deprecated. Use the property `graphQuery` instead.');
var strFields = Array.isArray(fields) ? fields.join(',') : fields;
return this.set('fetch', strFields);
};
/**
* Include the requested fields in the DocumentLink instances in the result
*/
SearchForm.prototype.fetchLinks = function (fields) {
console.warn('Warning: Using FetchLinks is deprecated. Use the property `graphQuery` instead.');
var strFields = Array.isArray(fields) ? fields.join(',') : fields;
return this.set('fetchLinks', strFields);
};
/**
* Sets the graphquery to query for this SearchForm. This is an optional method.
*/
SearchForm.prototype.graphQuery = function (query) {
return this.set('graphQuery', query);
};
/**
* Sets the language to query for this SearchForm. This is an optional method.
*/
SearchForm.prototype.lang = function (langCode) {
return this.set('lang', langCode);
};
/**
* Sets the page number to query for this SearchForm. This is an optional method.
*/
SearchForm.prototype.page = function (p) {
return this.set('page', p);
};
/**
* Remove all the documents except for those after the specified document in the list. This is an optional method.
*/
SearchForm.prototype.after = function (documentId) {
return this.set('after', documentId);
};
/**
* Sets the orderings to query for this SearchForm. This is an optional method.
*/
SearchForm.prototype.orderings = function (orderings) {
if (!orderings) {
return this;
}
else {
return this.set('orderings', "[" + orderings.join(',') + "]");
}
};
/**
* Build the URL to query
*/
SearchForm.prototype.url = function () {
var url = this.form.action;
if (this.data) {
var sep = (url.indexOf('?') > -1 ? '&' : '?');
for (var key in this.data) {
if (Object.prototype.hasOwnProperty.call(this.data, key)) {
var values = this.data[key];
if (values) {
for (var i = 0; i < values.length; i++) {
url += sep + key + '=' + encodeURIComponent(values[i]);
sep = '&';
}
}
}
}
}
return url;
};
/**
* Submits the query, and calls the callback function.
*/
SearchForm.prototype.submit = function (cb) {
return this.httpClient.cachedRequest(this.url()).then(function (response) {
cb && cb(null, response);
return response;
}).catch(function (error) {
cb && cb(error);
throw error;
});
};
return SearchForm;
}());
var OPERATOR = {
at: 'at',
not: 'not',
missing: 'missing',
has: 'has',
any: 'any',
in: 'in',
fulltext: 'fulltext',
similar: 'similar',
numberGt: 'number.gt',
numberLt: 'number.lt',
numberInRange: 'number.inRange',
dateBefore: 'date.before',
dateAfter: 'date.after',
dateBetween: 'date.between',
dateDayOfMonth: 'date.day-of-month',
dateDayOfMonthAfter: 'date.day-of-month-after',
dateDayOfMonthBefore: 'date.day-of-month-before',
dateDayOfWeek: 'date.day-of-week',
dateDayOfWeekAfter: 'date.day-of-week-after',
dateDayOfWeekBefore: 'date.day-of-week-before',
dateMonth: 'date.month',
dateMonthBefore: 'date.month-before',
dateMonthAfter: 'date.month-after',
dateYear: 'date.year',
dateHour: 'date.hour',
dateHourBefore: 'date.hour-before',
dateHourAfter: 'date.hour-after',
GeopointNear: 'geopoint.near',
};
function encode(value) {
if (typeof value === 'string') {
return "\"" + value + "\"";
}
else if (typeof value === 'number') {
return value.toString();
}
else if (value instanceof Date) {
return value.getTime().toString();
}
else if (Array.isArray(value)) {
return "[" + value.map(function (v) { return encode(v); }).join(',') + "]";
}
else if (typeof value === "boolean") {
return value.toString();
}
else {
throw new Error("Unable to encode " + value + " of type " + typeof value);
}
}
var geopoint = {
near: function (fragment, latitude, longitude, radius) {
return "[" + OPERATOR.GeopointNear + "(" + fragment + ", " + latitude + ", " + longitude + ", " + radius + ")]";
},
};
var date = {
before: function (fragment, before) {
return "[" + OPERATOR.dateBefore + "(" + fragment + ", " + encode(before) + ")]";
},
after: function (fragment, after) {
return "[" + OPERATOR.dateAfter + "(" + fragment + ", " + encode(after) + ")]";
},
between: function (fragment, before, after) {
return "[" + OPERATOR.dateBetween + "(" + fragment + ", " + encode(before) + ", " + encode(after) + ")]";
},
dayOfMonth: function (fragment, day) {
return "[" + OPERATOR.dateDayOfMonth + "(" + fragment + ", " + day + ")]";
},
dayOfMonthAfter: function (fragment, day) {
return "[" + OPERATOR.dateDayOfMonthAfter + "(" + fragment + ", " + day + ")]";
},
dayOfMonthBefore: function (fragment, day) {
return "[" + OPERATOR.dateDayOfMonthBefore + "(" + fragment + ", " + day + ")]";
},
dayOfWeek: function (fragment, day) {
return "[" + OPERATOR.dateDayOfWeek + "(" + fragment + ", " + encode(day) + ")]";
},
dayOfWeekAfter: function (fragment, day) {
return "[" + OPERATOR.dateDayOfWeekAfter + "(" + fragment + ", " + encode(day) + ")]";
},
dayOfWeekBefore: function (fragment, day) {
return "[" + OPERATOR.dateDayOfWeekBefore + "(" + fragment + ", " + encode(day) + ")]";
},
month: function (fragment, month) {
return "[" + OPERATOR.dateMonth + "(" + fragment + ", " + encode(month) + ")]";
},
monthBefore: function (fragment, month) {
return "[" + OPERATOR.dateMonthBefore + "(" + fragment + ", " + encode(month) + ")]";
},
monthAfter: function (fragment, month) {
return "[" + OPERATOR.dateMonthAfter + "(" + fragment + ", " + encode(month) + ")]";
},
year: function (fragment, year) {
return "[" + OPERATOR.dateYear + "(" + fragment + ", " + year + ")]";
},
hour: function (fragment, hour) {
return "[" + OPERATOR.dateHour + "(" + fragment + ", " + hour + ")]";
},
hourBefore: function (fragment, hour) {
return "[" + OPERATOR.dateHourBefore + "(" + fragment + ", " + hour + ")]";
},
hourAfter: function (fragment, hour) {
return "[" + OPERATOR.dateHourAfter + "(" + fragment + ", " + hour + ")]";
},
};
var number = {
gt: function (fragment, value) {
return "[" + OPERATOR.numberGt + "(" + fragment + ", " + value + ")]";
},
lt: function (fragment, value) {
return "[" + OPERATOR.numberLt + "(" + fragment + ", " + value + ")]";
},
inRange: function (fragment, before, after) {
return "[" + OPERATOR.numberInRange + "(" + fragment + ", " + before + ", " + after + ")]";
},
};
var Predicates = {
at: function (fragment, value) {
return "[" + OPERATOR.at + "(" + fragment + ", " + encode(value) + ")]";
},
not: function (fragment, value) {
return "[" + OPERATOR.not + "(" + fragment + ", " + encode(value) + ")]";
},
missing: function (fragment) {
return "[" + OPERATOR.missing + "(" + fragment + ")]";
},
has: function (fragment) {
return "[" + OPERATOR.has + "(" + fragment + ")]";
},
any: function (fragment, values) {
return "[" + OPERATOR.any + "(" + fragment + ", " + encode(values) + ")]";
},
in: function (fragment, values) {
return "[" + OPERATOR.in + "(" + fragment + ", " + encode(values) + ")]";
},
fulltext: function (fragment, value) {
return "[" + OPERATOR.fulltext + "(" + fragment + ", " + encode(value) + ")]";
},
similar: function (documentId, maxResults) {
return "[" + OPERATOR.similar + "(\"" + documentId + "\", " + maxResults + ")]";
},
date: date,
dateBefore: date.before,
dateAfter: date.after,
dateBetween: date.between,
dayOfMonth: date.dayOfMonth,
dayOfMonthAfter: date.dayOfMonthAfter,
dayOfMonthBefore: date.dayOfMonthBefore,
dayOfWeek: date.dayOfWeek,
dayOfWeekAfter: date.dayOfWeekAfter,
dayOfWeekBefore: date.dayOfWeekBefore,
month: date.month,
monthBefore: date.monthBefore,
monthAfter: date.monthAfter,
year: date.year,
hour: date.hour,
hourBefore: date.hourBefore,
hourAfter: date.hourAfter,
number: number,
gt: number.gt,
lt: number.lt,
inRange: number.inRange,
near: geopoint.near,
geopoint: geopoint,
};
/* eslint-disable */
// Some portions of code from https://github.com/jshttp/cookie
var decode = decodeURIComponent;
function tryDecode(str, decode) {
try {
return decode(str);
}
catch (e) {
return str;
}
}
function parse(str, options) {
if (typeof str !== 'string') {
throw new TypeError('argument str must be a string');
}
var obj = {};
var opt = options || {};
var pairs = str.split(/; */);
var dec = opt.decode || decode;
pairs.forEach(function (pair) {
var eq_idx = pair.indexOf('=');
// skip things that don't look like key=value
if (eq_idx < 0) {
return;
}
var key = pair.substr(0, eq_idx).trim();
var val = pair.substr(++eq_idx, pair.length).trim();
// quoted values
if ('"' == val[0]) {
val = val.slice(1, -1);
}
// only assign once
if (undefined == obj[key]) {
obj[key] = tryDecode(val, dec);
}
});
return obj;
}
var Cookies = { parse: parse };
function createPreviewResolver(token, documentId, getDocByID) {
var resolve = function (linkResolver, defaultUrl, cb) {
if (documentId && getDocByID) {
return getDocByID(documentId, { ref: token }).then(function (document) {
if (!document) {
cb && cb(null, defaultUrl);
return defaultUrl;
}
else {
var url = (linkResolver && linkResolver(document)) || document.url || defaultUrl;
cb && cb(null, url);
return url;
}
});
}
else {
return Promise.resolve(defaultUrl);
}
};
return { token: token, documentId: documentId, resolve: resolve };
}
var PREVIEW_COOKIE = 'io.prismic.preview';
var EXPERIMENT_COOKIE = 'io.prismic.experiment';
var ResolvedApi = /** @class */ (function () {
function ResolvedApi(data, httpClient, options) {
this.data = data;
this.masterRef = data.refs.filter(function (ref) { return ref.isMasterRef; })[0];
this.experiments = new Experiments(data.experiments);
this.bookmarks = data.bookmarks;
this.httpClient = httpClient;
this.options = options;
this.refs = data.refs;
this.tags = data.tags;
this.types = data.types;
this.languages = data.languages;
}
/**
* Returns a useable form from its id, as described in the RESTful description of the API.
* For instance: api.form("everything") works on every repository (as "everything" exists by default)
* You can then chain the calls: api.form("everything").query('[[:d = at(document.id, "UkL0gMuvzYUANCpf")]]').ref(ref).submit()
*/
ResolvedApi.prototype.form = function (formId) {
var form = this.data.forms[formId];
if (form) {
return new SearchForm(form, this.httpClient);
}
return null;
};
ResolvedApi.prototype.everything = function () {
var f = this.form('everything');
if (!f)
throw new Error('Missing everything form');
return f;
};
/**
* The ID of the master ref on this prismic.io API.
* Do not use like this: searchForm.ref(api.master()).
* Instead, set your ref once in a variable, and call it when you need it; this will allow to change the ref you're viewing easily for your entire page.
*/
ResolvedApi.prototype.master = function () {
return this.masterRef.ref;
};
/**
* Returns the ref ID for a given ref's label.
* Do not use like this: searchForm.ref(api.ref("Future release label")).
* Instead, set your ref once in a variable, and call it when you need it; this will allow to change the ref you're viewing easily for your entire page.
*/
ResolvedApi.prototype.ref = function (label) {
var ref = this.data.refs.filter(function (ref) { return ref.label === label; })[0];
return ref ? ref.ref : null;
};
ResolvedApi.prototype.currentExperiment = function () {
return this.experiments.current();
};
/**
* Query the repository
*/
ResolvedApi.prototype.query = function (q, optionsOrCallback, cb) {
if (cb === void 0) { cb = function () { }; }
var _a = typeof optionsOrCallback === 'function'
? { options: {}, callback: optionsOrCallback }
: { options: optionsOrCallback || {}, callback: cb }, options = _a.options, callback = _a.callback;
var form = this.everything();
for (var key in options) {
form = form.set(key, options[key]);
}
if (!options.ref) {
// Look in cookies if we have a ref (preview or experiment)
var cookieString = '';
if (this.options.req) { // NodeJS
cookieString = this.options.req.headers['cookie'] || '';
}
else if (typeof window !== 'undefined' && window.document) { // Browser
cookieString = window.document.cookie || '';
}
var cookies = Cookies.parse(cookieString);
var previewRef = cookies[PREVIEW_COOKIE];
var experimentRef = this.experiments.refFromCookie(cookies[EXPERIMENT_COOKIE]);
form = form.ref(previewRef || experimentRef || this.masterRef.ref);
}
if (q) {
form.query(q);
}
return form.submit(callback);
};
/**
* Retrieve the document returned by the given query
* @param {string|array|Predicate} the query
* @param {object} additional parameters. In NodeJS, pass the request as 'req'.
* @param {function} callback(err, doc)
*/
ResolvedApi.prototype.queryFirst = function (q, optionsOrCallback, cb) {
var _a = typeof optionsOrCallback === 'function'
? { options: {}, callback: optionsOrCallback }
: { options: optionsOrCallback || {}, callback: cb || (function () { }) }, options = _a.options, callback = _a.callback;
options.page = 1;
options.pageSize = 1;
return this.query(q, options).then(function (response) {
var document = response && response.results && response.results[0];
callback(null, document);
return document;
}).catch(function (error) {
callback(error);
throw error;
});
};
/**
* Retrieve the document with the given id
*/
ResolvedApi.prototype.getByID = function (id, maybeOptions, cb) {
var options = maybeOptions ? __assign({}, maybeOptions) : {};
if (!options.lang)
options.lang = '*';
return this.queryFirst(Predicates.at('document.id', id), options, cb);
};
/**
* Retrieve multiple documents from an array of id
*/
ResolvedApi.prototype.getByIDs = function (ids, maybeOptions, cb) {
var options = maybeOptions ? __assign({}, maybeOptions) : {};
if (!options.lang)
options.lang = '*';
return this.query(Predicates.in('document.id', ids), options, cb);
};
/**
* Retrieve the document with the given uid
*/
ResolvedApi.prototype.getByUID = function (type, uid, maybeOptions, cb) {
var options = maybeOptions ? __assign({}, maybeOptions) : {};
if (options.lang === '*')
throw new Error("FORBIDDEN. You can't use getByUID with *, use the predicates instead.");
if (!options.page)
options.page = 1;
return this.queryFirst(Predicates.at("my." + type + ".uid", uid), options, cb);
};
/**
* Retrieve the singleton document with the given type
*/
ResolvedApi.prototype.getSingle = function (type, maybeOptions, cb) {
var options = maybeOptions ? __assign({}, maybeOptions) : {};
return this.queryFirst(Predicates.at('document.type', type), options, cb);
};
/**
* Retrieve the document with the given bookmark
*/
ResolvedApi.prototype.getBookmark = function (bookmark, maybeOptions, cb) {
var id = this.data.bookmarks[bookmark];
if (id) {
return this.getByID(id, maybeOptions, cb);
}
else {
return Promise.reject('Error retrieving bookmarked id');
}
};
ResolvedApi.prototype.getPreviewResolver = function (token, documentId) {
return createPreviewResolver(token, documentId, this.getByID.bind(this));
};
ResolvedApi.prototype.previewSession = function (token, linkResolver, defaultUrl, cb) {
var _this = this;
console.warn('previewSession function is deprecated in favor of getPreviewResolver function.');
return new Promise(function (resolve, reject) {
_this.httpClient.request(token, function (e, result) {
if (e) {
cb && cb(e);
reject(e);
}
else if (result) {
if (!result.mainDocument) {
cb && cb(null, defaultUrl);
resolve(defaultUrl);
}
else {
return _this.getByID(result.mainDocument, { ref: token }).then(function (document) {
if (!document) {
cb && cb(null, defaultUrl);
resolve(defaultUrl);
}
else {
var url = (linkResolver && linkResolver(document)) || document.url || defaultUrl;
cb && cb(null, url);
resolve(url);
}
}).catch(reject);
}
}
});
});
};
return ResolvedApi;
}());
/* eslint-disable */
/**
* A doubly linked list-based Least Recently Used (LRU) cache. Will keep most
* recently used items while discarding least recently used items when its limit
* is reached.
*
* Licensed under MIT. Copyright (c) 2010 Rasmus Andersson <http://hunch.se/>
* Typescript-ified by Oleksandr Nikitin <https://tvori.info>
*
* Illustration of the design:
*
* entry entry entry entry
* ______ ______ ______ ______
* | head |.newer => | |.newer => | |.newer => | tail |
* | A | | B | | C | | D |
* |______| <= older.|______| <= older.|______| <= older.|______|
*
* removed <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- added
*/
function MakeLRUCache(limit) {
return new LRUCache(limit);
}
function LRUCache(limit) {
// Current size of the cache. (Read-only).
this.size = 0;
// Maximum number of items this cache can hold.
this.limit = limit;
this._keymap = {};
}
/**
* Put <value> into the cache associated with <key>. Returns the entry which was
* removed to make room for the new entry. Otherwise undefined is returned
* (i.e. if there was enough room already).
*/
LRUCache.prototype.put = function (key, value) {
var entry = { key: key, value: value };
// Note: No protection agains replacing, and thus orphan entries. By design.
this._keymap[key] = entry;
if (this.tail) {
// link previous tail to the new tail (entry)
this.tail.newer = entry;
entry.older = this.tail;
}
else {
// we're first in -- yay
this.head = entry;
}
// add new entry to the end of the linked list -- it's now the freshest entry.
this.tail = entry;
if (this.size === this.limit) {
// we hit the limit -- remove the head
return this.shift();
}
else {
// increase the size counter
this.size++;
}
};
/**
* Purge the least recently used (oldest) entry from the cache. Returns the
* removed entry or undefined if the cache was empty.
*
* If you need to perform any form of finalization of purged items, this is a
* good place to do it. Simply override/replace this function:
*
* var c = new LRUCache(123);
* c.shift = function() {
* var entry = LRUCache.prototype.shift.call(this);
* doSomethingWith(entry);
* return entry;
* }
*/
LRUCache.prototype.shift = function () {
// todo: handle special case when limit == 1
var entry = this.head;
if (entry) {
if (this.head.newer) {
this.head = this.head.newer;
this.head.older = undefined;
}
else {
this.head = undefined;
}
// Remove last strong reference to <entry> and remove links from the purged
// entry being returned:
entry.newer = entry.older = undefined;
// delete is slow, but we need to do this to avoid uncontrollable growth:
delete this._keymap[entry.key];
}
console.log('purging ', entry.key);
return entry;
};
/**
* Get and register recent use of <key>. Returns the value associated with <key>
* or undefined if not in cache.
*/
LRUCache.prototype.get = function (key, returnEntry) {
// First, find our cache entry
var entry = this._keymap[key];
if (entry === undefined)
return; // Not cached. Sorry.
// As <key> was found in the cache, register it as being requested recently
if (entry === this.tail) {
// Already the most recently used entry, so no need to update the list
return returnEntry ? entry : entry.value;
}
// HEAD--------------TAIL
// <.older .newer>
// <--- add direction --
// A B C <D> E
if (entry.newer) {
if (entry === this.head)
this.head = entry.newer;
entry.newer.older = entry.older; // C <-- E.
}
if (entry.older)
entry.older.newer = entry.newer; // C. --> E
entry.newer = undefined; // D --x
entry.older = this.tail; // D. --> E
if (this.tail)
this.tail.newer = entry; // E. <-- D
this.tail = entry;
return returnEntry ? entry : entry.value;
};
// ----------------------------------------------------------------------------
// Following code is optional and can be removed without breaking the core
// functionality.
/**
* Check if <key> is in the cache without registering recent use. Feasible if
* you do not want to chage the state of the cache, but only "peek" at it.
* Returns the entry associated with <key> if found, or undefined if not found.
*/
LRUCache.prototype.find = function (key) {
return this._keymap[key];
};
/**
* Update the value of entry with <key>. Returns the old value, or undefined if
* entry was not in the cache.
*/
LRUCache.prototype.set = function (key, value) {
var oldvalue;
var entry = this.get(key, true);
if (entry) {
oldvalue = entry.value;
entry.value = value;
}
else {
oldvalue = this.put(key, value);
if (oldvalue)
oldvalue = oldvalue.value;
}
return oldvalue;
};
/**
* Remove entry <key> from cache and return its value. Returns undefined if not
* found.
*/
LRUCache.prototype.remove = function (key) {
var entry = this._keymap[key];
if (!entry)
return;
delete this._keymap[entry.key]; // need to do delete unfortunately
if (entry.newer && entry.older) {
// relink the older entry with the newer entry
entry.older.newer = entry.newer;
entry.newer.older = entry.older;
}
else if (entry.newer) {
// remove the link to us
entry.newer.older = undefined;
// link the newer entry to head
this.head = entry.newer;
}
else if (entry.older) {
// remove the link to us
entry.older.newer = undefined;
// link the newer entry to head
this.tail = entry.older;
}
else { // if(entry.older === undefined && entry.newer === undefined) {
this.head = this.tail = undefined;
}
this.size--;
return entry.value;
};
/** Removes all entries */
LRUCache.prototype.removeAll = function () {
// This should be safe, as we never expose strong refrences to the outside
this.head = this.tail = undefined;
this.size = 0;
this._keymap = {};
};
/**
* Return an array containing all keys of entries stored in the cache object, in
* arbitrary order.
*/
if (typeof Object.keys === 'function') {
LRUCache.prototype.keys = function () { return Object.keys(this._keymap); };
}
else {
LRUCache.prototype.keys = function () {
var keys = [];
for (var k in this._keymap)
keys.push(k);
return keys;
};
}
/**
* Call `fun` for each entry. Starting with the newest entry if `desc` is a true
* value, otherwise starts with the oldest (head) enrty and moves towards the
* tail.
*
* `fun` is called with 3 arguments in the context `context`:
* `fun.call(context, Object key, Object value, LRUCache self)`
*/
LRUCache.prototype.forEach = function (fun, context, desc) {
var entry;
if (context === true) {
desc = true;
context = undefined;
}
else if (typeof context !== 'object')
context = this;
if (desc) {
entry = this.tail;
while (entry) {
fun.call(context, entry.key, entry.value, this);
entry = entry.older;
}
}
else {
entry = this.head;
while (entry) {
fun.call(context, entry.key, entry.value, this);
entry = entry.newer;
}
}
};
/** Returns a JSON (array) representation */
//LRUCache.prototype.toJSON = function () {
// var s: IEntry[] = [], entry = this.head;
// while (entry) {
// s.push({ key: entry.key.toJSON(), value: entry.value.toJSON() });
// entry = entry.newer;
// }
// return s;
//};
/** Returns a String representation */
LRUCache.prototype.toString = function () {
var s = '', entry = this.head;
while (entry) {
s += String(entry.key) + ':' + entry.value;
entry = entry.newer;
if (entry)
s += ' < ';
}
return s;
};
// Export ourselves
//if (typeof this === 'object') this.LRUCache = LRUCache;
var DefaultApiCache = /** @class */ (function () {
function DefaultApiCache(limit) {
if (limit === void 0) { limit = 1000; }
this.lru = MakeLRUCache(limit);
}
DefaultApiCache.prototype.isExpired = function (key) {
var value = this.lru.get(key, false);
if (value) {
return value.expiredIn !== 0 && value.expiredIn < Date.now();
}
else {
return false;
}
};
DefaultApiCache.prototype.get = function (key, cb) {
var value = this.lru.get(key, false);
if (value && !this.isExpired(key)) {
cb(null, value.data);
}
else {
cb && cb(null);
}
};
DefaultApiCache.prototype.set = function (key, value, ttl, cb) {
this.lru.remove(key);
this.lru.put(key, {
data: value,
expiredIn: ttl ? (Date.now() + (ttl * 1000)) : 0,
});
cb && cb(null);
};
DefaultApiCache.prototype.remove = function (key, cb) {
this.lru.remove(key);
cb && cb(null);
};
DefaultApiCache.prototype.clear = function (cb) {
this.lru.removeAll();
cb && cb(null);
};
return DefaultApiCache;
}());
function fetchRequest(url, options, callback) {
var fetchOptions = {
headers: {
Accept: 'application/json',
},
};
if (options && options.proxyAgent) {
fetchOptions.agent = options.proxyAgent;
}
// can't use number because of NodeJS globals included
var timeoutId;
var fetchPromise = crossFetch(url, fetchOptions);
var promise = options.timeoutInMs ? Promise.race([
fetchPromise,
new Promise(function (_, reject) {
timeoutId = setTimeout(function () { return reject(new Error(url + " response timeout")); }, options.timeoutInMs);
})
]) : fetchPromise;
promise.then(function (resp) {
clearTimeout(timeoutId);
if (~~(resp.status / 100 !== 2)) {
/**
* @description
* drain the resp before throwing an error to prevent memory leaks
* @link https://github.com/bitinn/node-fetch/issues/83
*/
return resp.text().then(function () {
var e = new Error("Unexpected status code [" + resp.status + "] on URL " + url);
e.status = resp.status;
throw e;
});
}
return resp.json().then(function (result) {
var cacheControl = resp.headers.get('cache-control');
var parsedCacheControl = cacheControl ? /max-age=(\d+)/.exec(cacheControl) : null;
var ttl = parsedCacheControl ? parseInt(parsedCacheControl[1], 10) : undefined;
callback(null, result, resp, ttl);
});
}).catch(function (err) {
clearTimeout(timeoutId);
callback(err);
});
}
var DefaultRequestHandler = /** @class */ (function () {
function DefaultRequestHandler(options) {
this.options = options || {};
}
DefaultRequestHandler.prototype.request = function (url, callback) {
fetchRequest(url, this.options, callback);
};
return DefaultRequestHandler;
}());
var HttpClient = /** @class */ (function () {
function HttpClient(requestHandler, cache, proxyAgent, timeoutInMs) {
this.requestHandler = requestHandler || new DefaultRequestHandler({ proxyAgent: proxyAgent, timeoutInMs: timeoutInMs });
this.cache = cache || new DefaultApiCache();
}
HttpClient.prototype.request = function (url, callback) {
this.requestHandler.request(url, function (err, result, xhr, ttl) {
if (err) {
callback && callback(err, null, xhr, ttl);
}
else if (result) {
callback && callback(null, result, xhr, ttl);
}
});
};
/**
* Fetch a URL corresponding to a query, and parse the response as a Response object
*/
HttpClient.prototype.cachedRequest = function (url, maybeOptions) {
var _this = this;
var options = maybeOptions || {};
var run = function (cb) {
var cacheKey = options.cacheKey || url;
_this.cache.get(cacheKey, function (cacheGetError, cacheGetValue) {
if (cacheGetError || cacheGetValue) {
cb(cacheGetError, cacheGetValue);
}
else {
_this.request(url, function (fetchError, fetchValue, _, ttlReq) {
if (fetchError) {
cb(fetchError, null);
}
else {
var ttl = ttlReq || options.ttl;
if (ttl) {
_this.cache.set(cacheKey, fetchValue, ttl, cb);
}
cb(null, fetchValue);
}
});
}
});
};
return new Promise(function (resolve, reject) {
run(function (err, value) {
if (err)
reject(err);
if (value)
resolve(value);
});
});
};
return HttpClient;
}());
function separator(url) {
return url.indexOf('?') > -1 ? '&' : '?';
}
var Api = /** @class */ (function () {
function Api(url, options) {
this.options = options || {};
this.url = url;
if (this.options.accessToken) {
var accessTokenParam = "access_token=" + this.options.accessToken;
this.url += separator(url) + accessTokenParam;
}
if (this.options.routes) {
this.url += separator(url) + ("routes=" + encodeURIComponent(JSON.stringify(this.options.routes)));
}
this.apiDataTTL = this.options.apiDataTTL || 5;
this.httpClient = new HttpClient(this.options.requestHandler, this.options.apiCache, this.options.proxyAgent, this.options.timeoutInMs);
}
/**
* Fetches data used to construct the api client, from cache if it's
* present, otherwise from calling the prismic api endpoint (which is
* then cached).
*/
Api.prototype.get = function (cb) {
var _this = this;
return this.httpClient.cachedRequest(this.url, { ttl: this.apiDataTTL }).then(function (data) {
var resolvedApi = new ResolvedApi(data, _this.httpClient, _this.options);
cb && cb(null, resolvedApi);
return resolvedApi;
}).catch(function (error) {
cb && cb(error);
throw error;
});
};
return Api;
}());
var DefaultClient = /** @class */ (function () {
function DefaultClient(url, options) {
this.api = new Api(url, options);
}
DefaultClient.prototype.getApi = function () {
return this.api.get();
};
DefaultClient.prototype.everything = function () {
return this.form('everything');
};
DefaultClient.prototype.form = function (formId) {
return new LazySearchForm(formId, this.api);
};
DefaultClient.prototype.query = function (q, optionsOrCallback, cb) {
return this.getApi().then(function (api) { return api.query(q, optionsOrCallback, cb); });
};
DefaultClient.prototype.queryFirst = function (q, optionsOrCallback, cb) {
return this.getApi().then(function (api) { return api.queryFirst(q, optionsOrCallback, cb); });
};
DefaultClient.prototype.getByID = function (id, options, cb) {
return this.getApi().then(function (api) { return api.getByID(id, options, cb); });
};
DefaultClient.prototype.getByIDs = function (ids, options, cb) {
return this.getApi().then(function (api) { return api.getByIDs(ids, options, cb); });
};
DefaultClient.prototype.getByUID = function (type, uid, options, cb) {
return this.getApi().then(function (api) { return api.getByUID(type, uid, options, cb); });
};
DefaultClient.prototype.getSingle = function (type, options, cb) {
return this.getApi().then(function (api) { return api.getSingle(type, options, cb); });
};
DefaultClient.prototype.getBookmark