3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
15 * This file is generated from kinto.js - do not modify directly.
18 import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";
21 * Version 15.0.0 - c8775d9
24 import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs";
26 /******************************************************************************
27 Copyright (c) Microsoft Corporation.
29 Permission to use, copy, modify, and/or distribute this software for any
30 purpose with or without fee is hereby granted.
32 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
33 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
34 AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
35 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
36 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
37 OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
38 PERFORMANCE OF THIS SOFTWARE.
39 ***************************************************************************** */
40 /* global Reflect, Promise */
42 function __decorate(decorators, target, key, desc) {
43 var c = arguments.length,
48 ? (desc = Object.getOwnPropertyDescriptor(target, key))
51 if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
52 r = Reflect.decorate(decorators, target, key, desc);
54 for (var i = decorators.length - 1; i >= 0; i--)
55 if ((d = decorators[i]))
56 r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
57 return c > 3 && r && Object.defineProperty(target, key, r), r;
61 * Chunks an array into n pieces.
64 * @param {Array} array
68 function partition(array, n) {
72 return array.reduce((acc, x, i) => {
73 if (i === 0 || i % n === 0) {
76 acc[acc.length - 1].push(x);
82 * Returns a Promise always resolving after the specified amount in milliseconds.
84 * @return Promise<void>
87 return new Promise(resolve => setTimeout(resolve, ms));
90 * Always returns a resource data object from the provided argument.
93 * @param {Object|String} resource
96 function toDataBody(resource) {
97 if (isObject(resource)) {
100 if (typeof resource === "string") {
101 return { id: resource };
103 throw new Error("Invalid argument.");
106 * Transforms an object into an URL query string, stripping out any undefined
109 * @param {Object} obj
112 function qsify(obj) {
114 encodeURIComponent(typeof v === "boolean" ? String(v) : v);
115 const stripped = cleanUndefinedProperties(obj);
116 return Object.keys(stripped)
118 const ks = encode(k) + "=";
119 if (Array.isArray(stripped[k])) {
120 return ks + stripped[k].map(v => encode(v)).join(",");
122 return ks + encode(stripped[k]);
127 * Checks if a version is within the provided range.
129 * @param {String} version The version to check.
130 * @param {String} minVersion The minimum supported version (inclusive).
131 * @param {String} maxVersion The minimum supported version (exclusive).
132 * @throws {Error} If the version is outside of the provided range.
134 function checkVersion(version, minVersion, maxVersion) {
135 const extract = str => str.split(".").map(x => parseInt(x, 10));
136 const [verMajor, verMinor] = extract(version);
137 const [minMajor, minMinor] = extract(minVersion);
138 const [maxMajor, maxMinor] = extract(maxVersion);
141 verMajor === minMajor && verMinor < minMinor,
143 verMajor === maxMajor && verMinor >= maxMinor,
145 if (checks.some(x => x)) {
147 `Version ${version} doesn't satisfy ${minVersion} <= x < ${maxVersion}`
152 * Generates a decorator function ensuring a version check is performed against
153 * the provided requirements before executing it.
155 * @param {String} min The required min version (inclusive).
156 * @param {String} max The required max version (inclusive).
159 function support(min, max) {
166 const fn = descriptor.value;
170 const wrappedMethod = (...args) => {
171 // "this" is the current instance which its method is decorated.
172 const client = this.client ? this.client : this;
174 .fetchHTTPApiVersion()
175 .then(version => checkVersion(version, min, max))
176 .then(() => fn.apply(this, args));
178 Object.defineProperty(this, key, {
179 value: wrappedMethod,
183 return wrappedMethod;
189 * Generates a decorator function ensuring that the specified capabilities are
190 * available on the server before executing it.
192 * @param {Array<String>} capabilities The required capabilities.
195 function capable(capabilities) {
202 const fn = descriptor.value;
206 const wrappedMethod = (...args) => {
207 // "this" is the current instance which its method is decorated.
208 const client = this.client ? this.client : this;
210 .fetchServerCapabilities()
212 const missing = capabilities.filter(c => !(c in available));
213 if (missing.length) {
214 const missingStr = missing.join(", ");
216 `Required capabilities ${missingStr} not present on server`
220 .then(() => fn.apply(this, args));
222 Object.defineProperty(this, key, {
223 value: wrappedMethod,
227 return wrappedMethod;
233 * Generates a decorator function ensuring an operation is not performed from
234 * within a batch request.
236 * @param {String} message The error message to throw.
239 function nobatch(message) {
246 const fn = descriptor.value;
250 const wrappedMethod = (...args) => {
251 // "this" is the current instance which its method is decorated.
253 throw new Error(message);
255 return fn.apply(this, args);
257 Object.defineProperty(this, key, {
258 value: wrappedMethod,
262 return wrappedMethod;
268 * Returns true if the specified value is an object (i.e. not an array nor null).
269 * @param {Object} thing The value to inspect.
272 function isObject(thing) {
273 return typeof thing === "object" && thing !== null && !Array.isArray(thing);
277 * @param {String} dataURL The data url.
280 function parseDataURL(dataURL) {
281 const regex = /^data:(.*);base64,(.*)/;
282 const match = dataURL.match(regex);
284 throw new Error(`Invalid data-url: ${String(dataURL).substring(0, 32)}...`);
286 const props = match[1];
287 const base64 = match[2];
288 const [type, ...rawParams] = props.split(";");
289 const params = rawParams.reduce((acc, param) => {
290 const [key, value] = param.split("=");
291 return { ...acc, [key]: value };
293 return { ...params, type, base64 };
296 * Extracts file information from a data url.
297 * @param {String} dataURL The data url.
300 function extractFileInfo(dataURL) {
301 const { name, type, base64 } = parseDataURL(dataURL);
302 const binary = atob(base64);
304 for (let i = 0; i < binary.length; i++) {
305 array.push(binary.charCodeAt(i));
307 const blob = new Blob([new Uint8Array(array)], { type });
308 return { blob, name };
311 * Creates a FormData instance from a data url and an existing JSON response
313 * @param {String} dataURL The data url.
314 * @param {Object} body The response body.
315 * @param {Object} [options={}] The options object.
316 * @param {Object} [options.filename] Force attachment file name.
319 function createFormData(dataURL, body, options = {}) {
320 const { filename = "untitled" } = options;
321 const { blob, name } = extractFileInfo(dataURL);
322 const formData = new FormData();
323 formData.append("attachment", blob, name || filename);
324 for (const property in body) {
325 if (typeof body[property] !== "undefined") {
326 formData.append(property, JSON.stringify(body[property]));
332 * Clones an object with all its undefined keys removed.
335 function cleanUndefinedProperties(obj) {
337 for (const key in obj) {
338 if (typeof obj[key] !== "undefined") {
339 result[key] = obj[key];
345 * Handle common query parameters for Kinto requests.
347 * @param {String} [path] The endpoint base path.
348 * @param {Array} [options.fields] Fields to limit the
350 * @param {Object} [options.query={}] Additional query arguments.
352 function addEndpointOptions(path, options = {}) {
353 const query = { ...options.query };
354 if (options.fields) {
355 query._fields = options.fields;
357 const queryString = qsify(query);
359 return path + "?" + queryString;
364 * Replace authorization header with an obscured version
366 function obscureAuthorizationHeader(headers) {
367 const h = new Headers(headers);
368 if (h.has("authorization")) {
369 h.set("authorization", "**** (suppressed)");
371 const obscuredHeaders = {};
372 for (const [header, value] of h.entries()) {
373 obscuredHeaders[header] = value;
375 return obscuredHeaders;
379 * Kinto server error code descriptors.
381 const ERROR_CODES = {
382 104: "Missing Authorization Token",
383 105: "Invalid Authorization Token",
384 106: "Request body was not valid JSON",
385 107: "Invalid request parameter",
386 108: "Missing request parameter",
387 109: "Invalid posted data",
388 110: "Invalid Token / id",
389 111: "Missing Token / id",
390 112: "Content-Length header was not provided",
391 113: "Request body too large",
392 114: "Resource was created, updated or deleted meanwhile",
393 115: "Method not allowed on this end point (hint: server may be readonly)",
394 116: "Requested version not available on this server",
395 117: "Client has sent too many requests",
396 121: "Resource access is forbidden for this user",
397 122: "Another resource violates constraint",
398 201: "Service Temporary unavailable due to high load",
399 202: "Service deprecated",
400 999: "Internal Server Error",
402 class NetworkTimeoutError extends Error {
403 constructor(url, options) {
405 `Timeout while trying to access ${url} with ${JSON.stringify(options)}`
407 if (Error.captureStackTrace) {
408 Error.captureStackTrace(this, NetworkTimeoutError);
411 this.options = options;
414 class UnparseableResponseError extends Error {
415 constructor(response, body, error) {
416 const { status } = response;
418 `Response from server unparseable (HTTP ${
420 }; ${error}): ${body}`
422 if (Error.captureStackTrace) {
423 Error.captureStackTrace(this, UnparseableResponseError);
425 this.status = status;
426 this.response = response;
427 this.stack = error.stack;
432 * "Error" subclass representing a >=400 response from the server.
434 * Whether or not this is an error depends on your application.
436 * The `json` field can be undefined if the server responded with an
437 * empty response body. This shouldn't generally happen. Most "bad"
438 * responses come with a JSON error description, or (if they're
439 * fronted by a CDN or nginx or something) occasionally non-JSON
440 * responses (which become UnparseableResponseErrors, above).
442 class ServerResponse extends Error {
443 constructor(response, json) {
444 const { status } = response;
445 let { statusText } = response;
448 // Try to fill in information from the JSON error.
449 statusText = json.error || statusText;
450 // Take errnoMsg from either ERROR_CODES or json.message.
451 if (json.errno && json.errno in ERROR_CODES) {
452 errnoMsg = ERROR_CODES[json.errno];
453 } else if (json.message) {
454 errnoMsg = json.message;
456 // If we had both ERROR_CODES and json.message, and they differ,
458 if (errnoMsg && json.message && json.message !== errnoMsg) {
459 errnoMsg += ` (${json.message})`;
462 let message = `HTTP ${status} ${statusText}`;
464 message += `: ${errnoMsg}`;
466 super(message.trim());
467 if (Error.captureStackTrace) {
468 Error.captureStackTrace(this, ServerResponse);
470 this.response = response;
475 var errors = /*#__PURE__*/ Object.freeze({
479 UnparseableResponseError,
480 default: ERROR_CODES,
484 * Enhanced HTTP client for the Kinto protocol.
489 * Default HTTP request headers applied to each outgoing request.
493 static get DEFAULT_REQUEST_HEADERS() {
495 Accept: "application/json",
496 "Content-Type": "application/json",
504 static get defaultOptions() {
505 return { timeout: null, requestMode: "cors" };
510 * @param {EventEmitter} events The event handler.
511 * @param {Object} [options={}} The options object.
512 * @param {Number} [options.timeout=null] The request timeout in ms, if any (default: `null`).
513 * @param {String} [options.requestMode="cors"] The HTTP request mode (default: `"cors"`).
515 constructor(events, options = {}) {
518 * The event emitter instance.
519 * @type {EventEmitter}
521 this.events = events;
524 * @see https://fetch.spec.whatwg.org/#requestmode
527 this.requestMode = options.requestMode || HTTP.defaultOptions.requestMode;
529 * The request timeout.
532 this.timeout = options.timeout || HTTP.defaultOptions.timeout;
534 * The fetch() function.
537 this.fetchFunc = options.fetchFunc || globalThis.fetch.bind(globalThis);
542 timedFetch(url, options) {
543 let hasTimedout = false;
544 return new Promise((resolve, reject) => {
545 // Detect if a request has timed out.
548 _timeoutId = setTimeout(() => {
550 if (options && options.headers) {
553 headers: obscureAuthorizationHeader(options.headers),
556 reject(new NetworkTimeoutError(url, options));
559 function proceedWithHandler(fn) {
563 clearTimeout(_timeoutId);
569 this.fetchFunc(url, options)
570 .then(proceedWithHandler(resolve))
571 .catch(proceedWithHandler(reject));
577 async processResponse(response) {
578 const { status, headers } = response;
579 const text = await response.text();
580 // Check if we have a body; if so parse it as JSON.
582 if (text.length !== 0) {
584 json = JSON.parse(text);
586 throw new UnparseableResponseError(response, text, err);
590 throw new ServerResponse(response, json);
592 return { status, json: json, headers };
597 async retry(url, retryAfter, request, options) {
598 await delay(retryAfter);
599 return this.request(url, request, {
601 retry: options.retry - 1,
605 * Performs an HTTP request to the Kinto server.
607 * Resolves with an objet containing the following HTTP response properties:
608 * - `{Number} status` The HTTP status code.
609 * - `{Object} json` The JSON response body.
610 * - `{Headers} headers` The response headers object; see the ES6 fetch() spec.
612 * @param {String} url The URL.
613 * @param {Object} [request={}] The request object, passed to
614 * fetch() as its options object.
615 * @param {Object} [request.headers] The request headers object (default: {})
616 * @param {Object} [options={}] Options for making the
618 * @param {Number} [options.retry] Number of retries (default: 0)
621 async request(url, request = { headers: {} }, options = { retry: 0 }) {
622 // Ensure default request headers are always set
623 request.headers = { ...HTTP.DEFAULT_REQUEST_HEADERS, ...request.headers };
624 // If a multipart body is provided, remove any custom Content-Type header as
625 // the fetch() implementation will add the correct one for us.
626 if (request.body && request.body instanceof FormData) {
627 if (request.headers instanceof Headers) {
628 request.headers.delete("Content-Type");
629 } else if (!Array.isArray(request.headers)) {
630 delete request.headers["Content-Type"];
633 request.mode = this.requestMode;
634 const response = await this.timedFetch(url, request);
635 const { headers } = response;
636 this._checkForDeprecationHeader(headers);
637 this._checkForBackoffHeader(headers);
638 // Check if the server summons the client to retry after a while.
639 const retryAfter = this._checkForRetryAfterHeader(headers);
640 // If number of allowed of retries is not exhausted, retry the same request.
641 if (retryAfter && options.retry > 0) {
642 return this.retry(url, retryAfter, request, options);
644 return this.processResponse(response);
646 _checkForDeprecationHeader(headers) {
647 const alertHeader = headers.get("Alert");
653 alert = JSON.parse(alertHeader);
655 console.warn("Unable to parse Alert header message", alertHeader);
658 console.warn(alert.message, alert.url);
660 this.events.emit("deprecated", alert);
663 _checkForBackoffHeader(headers) {
665 const backoffHeader = headers.get("Backoff");
666 const backoffSeconds = backoffHeader ? parseInt(backoffHeader, 10) : 0;
667 if (backoffSeconds > 0) {
668 backoffMs = new Date().getTime() + backoffSeconds * 1000;
673 this.events.emit("backoff", backoffMs);
676 _checkForRetryAfterHeader(headers) {
677 const retryAfter = headers.get("Retry-After");
681 const delay = parseInt(retryAfter, 10) * 1000;
682 const tryAgainAfter = new Date().getTime() + delay;
684 this.events.emit("retry-after", tryAgainAfter);
691 * Endpoints templates.
696 batch: () => "/batch",
697 permissions: () => "/permissions",
698 bucket: bucket => "/buckets" + (bucket ? `/${bucket}` : ""),
699 history: bucket => `${ENDPOINTS.bucket(bucket)}/history`,
700 collection: (bucket, coll) =>
701 `${ENDPOINTS.bucket(bucket)}/collections` + (coll ? `/${coll}` : ""),
702 group: (bucket, group) =>
703 `${ENDPOINTS.bucket(bucket)}/groups` + (group ? `/${group}` : ""),
704 record: (bucket, coll, id) =>
705 `${ENDPOINTS.collection(bucket, coll)}/records` + (id ? `/${id}` : ""),
706 attachment: (bucket, coll, id) =>
707 `${ENDPOINTS.record(bucket, coll, id)}/attachment`,
710 const requestDefaults = {
712 // check if we should set default content type here
719 function safeHeader(safe, last_modified) {
724 return { "If-Match": `"${last_modified}"` };
726 return { "If-None-Match": "*" };
731 function createRequest(path, { data, permissions }, options = {}) {
732 const { headers, safe } = {
736 const method = options.method || (data && data.id) ? "PUT" : "POST";
740 headers: { ...headers, ...safeHeader(safe) },
741 body: { data, permissions },
747 function updateRequest(path, { data, permissions }, options = {}) {
748 const { headers, safe, patch } = { ...requestDefaults, ...options };
749 const { last_modified } = { ...data, ...options };
752 Object.keys(data).filter(k => k !== "id" && k !== "last_modified")
758 method: patch ? "PATCH" : "PUT",
760 headers: { ...headers, ...safeHeader(safe, last_modified) },
761 body: { data, permissions },
767 function jsonPatchPermissionsRequest(path, permissions, opType, options = {}) {
768 const { headers, safe, last_modified } = { ...requestDefaults, ...options };
770 for (const [type, principals] of Object.entries(permissions)) {
772 for (const principal of principals) {
775 path: `/permissions/${type}/${principal}`,
785 ...safeHeader(safe, last_modified),
786 "Content-Type": "application/json-patch+json",
794 function deleteRequest(path, options = {}) {
795 const { headers, safe, last_modified } = {
799 if (safe && !last_modified) {
800 throw new Error("Safe concurrency check requires a last_modified value.");
805 headers: { ...headers, ...safeHeader(safe, last_modified) },
811 function addAttachmentRequest(
814 { data, permissions } = {},
817 const { headers, safe, gzipped } = { ...requestDefaults, ...options };
818 const { last_modified } = { ...data, ...options };
819 const body = { data, permissions };
820 const formData = createFormData(dataURI, body, options);
821 const customPath = `${path}${
822 gzipped !== null ? "?gzipped=" + (gzipped ? "true" : "false") : ""
827 headers: { ...headers, ...safeHeader(safe, last_modified) },
833 * Exports batch responses as a result object.
836 * @param {Array} responses The batch subrequest responses.
837 * @param {Array} requests The initial issued requests.
840 function aggregate(responses = [], requests = []) {
841 if (responses.length !== requests.length) {
842 throw new Error("Responses length should match requests one.");
850 return responses.reduce((acc, response, index) => {
851 const { status } = response;
852 const request = requests[index];
853 if (status >= 200 && status < 400) {
854 acc.published.push(response.body);
855 } else if (status === 404) {
856 // Extract the id manually from request path while waiting for Kinto/kinto#818
857 const regex = /(buckets|groups|collections|records)\/([^/]+)$/;
858 const extracts = request.path.match(regex);
859 const id = extracts && extracts.length === 3 ? extracts[2] : undefined;
863 error: response.body,
865 } else if (status === 412) {
867 // XXX: specifying the type is probably superfluous
871 (response.body.details && response.body.details.existing) || null,
877 error: response.body,
884 // Unique ID creation requires a high quality random # generator. In the browser we therefore
885 // require the crypto API and do not support built-in fallback to lower quality random number
886 // generators (like Math.random()).
888 const rnds8 = new Uint8Array(16);
890 // lazy load so that environments that need to polyfill have a chance to do so
891 if (!getRandomValues) {
892 // getRandomValues needs to be invoked in a context where "this" is a Crypto implementation.
894 typeof crypto !== "undefined" &&
895 crypto.getRandomValues &&
896 crypto.getRandomValues.bind(crypto);
898 if (!getRandomValues) {
900 "crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported"
905 return getRandomValues(rnds8);
909 * Convert array of 16 byte values to UUID string format of the form:
910 * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
913 const byteToHex = [];
915 for (let i = 0; i < 256; ++i) {
916 byteToHex.push((i + 0x100).toString(16).slice(1));
919 function unsafeStringify(arr, offset = 0) {
920 // Note: Be careful editing this code! It's been tuned for performance
921 // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434
923 byteToHex[arr[offset + 0]] +
924 byteToHex[arr[offset + 1]] +
925 byteToHex[arr[offset + 2]] +
926 byteToHex[arr[offset + 3]] +
928 byteToHex[arr[offset + 4]] +
929 byteToHex[arr[offset + 5]] +
931 byteToHex[arr[offset + 6]] +
932 byteToHex[arr[offset + 7]] +
934 byteToHex[arr[offset + 8]] +
935 byteToHex[arr[offset + 9]] +
937 byteToHex[arr[offset + 10]] +
938 byteToHex[arr[offset + 11]] +
939 byteToHex[arr[offset + 12]] +
940 byteToHex[arr[offset + 13]] +
941 byteToHex[arr[offset + 14]] +
942 byteToHex[arr[offset + 15]]
947 typeof crypto !== "undefined" &&
949 crypto.randomUUID.bind(crypto);
954 function v4(options, buf, offset) {
955 if (native.randomUUID && !buf && !options) {
956 return native.randomUUID();
959 options = options || {};
960 const rnds = options.random || (options.rng || rng)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
962 rnds[6] = (rnds[6] & 0x0f) | 0x40;
963 rnds[8] = (rnds[8] & 0x3f) | 0x80; // Copy bytes to buffer, if provided
966 offset = offset || 0;
968 for (let i = 0; i < 16; ++i) {
969 buf[offset + i] = rnds[i];
975 return unsafeStringify(rnds);
979 * Abstract representation of a selected collection.
986 * @param {KintoClient} client The client instance.
987 * @param {Bucket} bucket The bucket instance.
988 * @param {String} name The collection name.
989 * @param {Object} [options={}] The options object.
990 * @param {Object} [options.headers] The headers object option.
991 * @param {Boolean} [options.safe] The safe option.
992 * @param {Number} [options.retry] The retry option.
993 * @param {Boolean} [options.batch] (Private) Whether this
994 * Collection is operating as part of a batch.
996 constructor(client, bucket, name, options = {}) {
1000 this.client = client;
1004 this.bucket = bucket;
1006 * The collection name.
1010 this._endpoints = client.endpoints;
1014 this._retry = options.retry || 0;
1015 this._safe = !!options.safe;
1016 // FIXME: This is kind of ugly; shouldn't the bucket be responsible
1017 // for doing the merge?
1019 ...this.bucket.headers,
1024 return this.client.execute.bind(this.client);
1027 * Get the value of "headers" for a given request, merging the
1028 * per-request headers with our own "default" headers.
1032 _getHeaders(options) {
1039 * Get the value of "safe" for a given request, using the
1040 * per-request option if present or falling back to our default
1044 * @param {Object} options The options for a request.
1045 * @returns {Boolean}
1048 return { safe: this._safe, ...options }.safe;
1051 * As _getSafe, but for "retry".
1055 _getRetry(options) {
1056 return { retry: this._retry, ...options }.retry;
1059 * Retrieves the total number of records in this collection.
1061 * @param {Object} [options={}] The options object.
1062 * @param {Object} [options.headers] The headers object option.
1063 * @param {Number} [options.retry=0] Number of retries to make
1064 * when faced with transient errors.
1065 * @return {Promise<Number, Error>}
1067 async getTotalRecords(options = {}) {
1068 const path = this._endpoints.record(this.bucket.name, this.name);
1070 headers: this._getHeaders(options),
1074 const { headers } = await this.client.execute(request, {
1076 retry: this._getRetry(options),
1078 return parseInt(headers.get("Total-Records"), 10);
1081 * Retrieves the ETag of the records list, for use with the `since` filtering option.
1083 * @param {Object} [options={}] The options object.
1084 * @param {Object} [options.headers] The headers object option.
1085 * @param {Number} [options.retry=0] Number of retries to make
1086 * when faced with transient errors.
1087 * @return {Promise<String, Error>}
1089 async getRecordsTimestamp(options = {}) {
1090 const path = this._endpoints.record(this.bucket.name, this.name);
1092 headers: this._getHeaders(options),
1096 const { headers } = await this.client.execute(request, {
1098 retry: this._getRetry(options),
1100 return headers.get("ETag");
1103 * Retrieves collection data.
1105 * @param {Object} [options={}] The options object.
1106 * @param {Object} [options.headers] The headers object option.
1107 * @param {Object} [options.query] Query parameters to pass in
1108 * the request. This might be useful for features that aren't
1109 * yet supported by this library.
1110 * @param {Array} [options.fields] Limit response to
1112 * @param {Number} [options.retry=0] Number of retries to make
1113 * when faced with transient errors.
1114 * @return {Promise<Object, Error>}
1116 async getData(options = {}) {
1117 const path = this._endpoints.collection(this.bucket.name, this.name);
1118 const request = { headers: this._getHeaders(options), path };
1119 const { data } = await this.client.execute(request, {
1120 retry: this._getRetry(options),
1121 query: options.query,
1122 fields: options.fields,
1127 * Set collection data.
1128 * @param {Object} data The collection data object.
1129 * @param {Object} [options={}] The options object.
1130 * @param {Object} [options.headers] The headers object option.
1131 * @param {Number} [options.retry=0] Number of retries to make
1132 * when faced with transient errors.
1133 * @param {Boolean} [options.safe] The safe option.
1134 * @param {Boolean} [options.patch] The patch option.
1135 * @param {Number} [options.last_modified] The last_modified option.
1136 * @return {Promise<Object, Error>}
1138 async setData(data, options = {}) {
1139 if (!isObject(data)) {
1140 throw new Error("A collection object is required.");
1142 const { patch, permissions } = options;
1143 const { last_modified } = { ...data, ...options };
1144 const path = this._endpoints.collection(this.bucket.name, this.name);
1145 const request = updateRequest(
1147 { data, permissions },
1151 headers: this._getHeaders(options),
1152 safe: this._getSafe(options),
1155 return this.client.execute(request, {
1156 retry: this._getRetry(options),
1160 * Retrieves the list of permissions for this collection.
1162 * @param {Object} [options={}] The options object.
1163 * @param {Object} [options.headers] The headers object option.
1164 * @param {Number} [options.retry=0] Number of retries to make
1165 * when faced with transient errors.
1166 * @return {Promise<Object, Error>}
1168 async getPermissions(options = {}) {
1169 const path = this._endpoints.collection(this.bucket.name, this.name);
1170 const request = { headers: this._getHeaders(options), path };
1171 const { permissions } = await this.client.execute(request, {
1172 retry: this._getRetry(options),
1177 * Replaces all existing collection permissions with the ones provided.
1179 * @param {Object} permissions The permissions object.
1180 * @param {Object} [options={}] The options object
1181 * @param {Object} [options.headers] The headers object option.
1182 * @param {Number} [options.retry=0] Number of retries to make
1183 * when faced with transient errors.
1184 * @param {Boolean} [options.safe] The safe option.
1185 * @param {Number} [options.last_modified] The last_modified option.
1186 * @return {Promise<Object, Error>}
1188 async setPermissions(permissions, options = {}) {
1189 if (!isObject(permissions)) {
1190 throw new Error("A permissions object is required.");
1192 const path = this._endpoints.collection(this.bucket.name, this.name);
1193 const data = { last_modified: options.last_modified };
1194 const request = updateRequest(
1196 { data, permissions },
1198 headers: this._getHeaders(options),
1199 safe: this._getSafe(options),
1202 return this.client.execute(request, {
1203 retry: this._getRetry(options),
1207 * Append principals to the collection permissions.
1209 * @param {Object} permissions The permissions object.
1210 * @param {Object} [options={}] The options object
1211 * @param {Boolean} [options.safe] The safe option.
1212 * @param {Object} [options.headers] The headers object option.
1213 * @param {Number} [options.retry=0] Number of retries to make
1214 * when faced with transient errors.
1215 * @param {Object} [options.last_modified] The last_modified option.
1216 * @return {Promise<Object, Error>}
1218 async addPermissions(permissions, options = {}) {
1219 if (!isObject(permissions)) {
1220 throw new Error("A permissions object is required.");
1222 const path = this._endpoints.collection(this.bucket.name, this.name);
1223 const { last_modified } = options;
1224 const request = jsonPatchPermissionsRequest(path, permissions, "add", {
1226 headers: this._getHeaders(options),
1227 safe: this._getSafe(options),
1229 return this.client.execute(request, {
1230 retry: this._getRetry(options),
1234 * Remove principals from the collection permissions.
1236 * @param {Object} permissions The permissions object.
1237 * @param {Object} [options={}] The options object
1238 * @param {Boolean} [options.safe] The safe option.
1239 * @param {Object} [options.headers] The headers object option.
1240 * @param {Number} [options.retry=0] Number of retries to make
1241 * when faced with transient errors.
1242 * @param {Object} [options.last_modified] The last_modified option.
1243 * @return {Promise<Object, Error>}
1245 async removePermissions(permissions, options = {}) {
1246 if (!isObject(permissions)) {
1247 throw new Error("A permissions object is required.");
1249 const path = this._endpoints.collection(this.bucket.name, this.name);
1250 const { last_modified } = options;
1251 const request = jsonPatchPermissionsRequest(path, permissions, "remove", {
1253 headers: this._getHeaders(options),
1254 safe: this._getSafe(options),
1256 return this.client.execute(request, {
1257 retry: this._getRetry(options),
1261 * Creates a record in current collection.
1263 * @param {Object} record The record to create.
1264 * @param {Object} [options={}] The options object.
1265 * @param {Object} [options.headers] The headers object option.
1266 * @param {Number} [options.retry=0] Number of retries to make
1267 * when faced with transient errors.
1268 * @param {Boolean} [options.safe] The safe option.
1269 * @param {Object} [options.permissions] The permissions option.
1270 * @return {Promise<Object, Error>}
1272 async createRecord(record, options = {}) {
1273 const { permissions } = options;
1274 const path = this._endpoints.record(this.bucket.name, this.name, record.id);
1275 const request = createRequest(
1277 { data: record, permissions },
1279 headers: this._getHeaders(options),
1280 safe: this._getSafe(options),
1283 return this.client.execute(request, {
1284 retry: this._getRetry(options),
1288 * Adds an attachment to a record, creating the record when it doesn't exist.
1290 * @param {String} dataURL The data url.
1291 * @param {Object} [record={}] The record data.
1292 * @param {Object} [options={}] The options object.
1293 * @param {Object} [options.headers] The headers object option.
1294 * @param {Number} [options.retry=0] Number of retries to make
1295 * when faced with transient errors.
1296 * @param {Boolean} [options.safe] The safe option.
1297 * @param {Number} [options.last_modified] The last_modified option.
1298 * @param {Object} [options.permissions] The permissions option.
1299 * @param {String} [options.filename] Force the attachment filename.
1300 * @param {String} [options.gzipped] Force the attachment to be gzipped or not.
1301 * @return {Promise<Object, Error>}
1303 async addAttachment(dataURI, record = {}, options = {}) {
1304 const { permissions } = options;
1305 const id = record.id || v4();
1306 const path = this._endpoints.attachment(this.bucket.name, this.name, id);
1307 const { last_modified } = { ...record, ...options };
1308 const addAttachmentRequest$1 = addAttachmentRequest(
1311 { data: record, permissions },
1314 filename: options.filename,
1315 gzipped: options.gzipped,
1316 headers: this._getHeaders(options),
1317 safe: this._getSafe(options),
1320 await this.client.execute(addAttachmentRequest$1, {
1322 retry: this._getRetry(options),
1324 return this.getRecord(id);
1327 * Removes an attachment from a given record.
1329 * @param {Object} recordId The record id.
1330 * @param {Object} [options={}] The options object.
1331 * @param {Object} [options.headers] The headers object option.
1332 * @param {Number} [options.retry=0] Number of retries to make
1333 * when faced with transient errors.
1334 * @param {Boolean} [options.safe] The safe option.
1335 * @param {Number} [options.last_modified] The last_modified option.
1337 async removeAttachment(recordId, options = {}) {
1338 const { last_modified } = options;
1339 const path = this._endpoints.attachment(
1344 const request = deleteRequest(path, {
1346 headers: this._getHeaders(options),
1347 safe: this._getSafe(options),
1349 return this.client.execute(request, {
1350 retry: this._getRetry(options),
1354 * Updates a record in current collection.
1356 * @param {Object} record The record to update.
1357 * @param {Object} [options={}] The options object.
1358 * @param {Object} [options.headers] The headers object option.
1359 * @param {Number} [options.retry=0] Number of retries to make
1360 * when faced with transient errors.
1361 * @param {Boolean} [options.safe] The safe option.
1362 * @param {Number} [options.last_modified] The last_modified option.
1363 * @param {Object} [options.permissions] The permissions option.
1364 * @return {Promise<Object, Error>}
1366 async updateRecord(record, options = {}) {
1367 if (!isObject(record)) {
1368 throw new Error("A record object is required.");
1371 throw new Error("A record id is required.");
1373 const { permissions } = options;
1374 const { last_modified } = { ...record, ...options };
1375 const path = this._endpoints.record(this.bucket.name, this.name, record.id);
1376 const request = updateRequest(
1378 { data: record, permissions },
1380 headers: this._getHeaders(options),
1381 safe: this._getSafe(options),
1383 patch: !!options.patch,
1386 return this.client.execute(request, {
1387 retry: this._getRetry(options),
1391 * Deletes a record from the current collection.
1393 * @param {Object|String} record The record to delete.
1394 * @param {Object} [options={}] The options object.
1395 * @param {Object} [options.headers] The headers object option.
1396 * @param {Number} [options.retry=0] Number of retries to make
1397 * when faced with transient errors.
1398 * @param {Boolean} [options.safe] The safe option.
1399 * @param {Number} [options.last_modified] The last_modified option.
1400 * @return {Promise<Object, Error>}
1402 async deleteRecord(record, options = {}) {
1403 const recordObj = toDataBody(record);
1404 if (!recordObj.id) {
1405 throw new Error("A record id is required.");
1407 const { id } = recordObj;
1408 const { last_modified } = { ...recordObj, ...options };
1409 const path = this._endpoints.record(this.bucket.name, this.name, id);
1410 const request = deleteRequest(path, {
1412 headers: this._getHeaders(options),
1413 safe: this._getSafe(options),
1415 return this.client.execute(request, {
1416 retry: this._getRetry(options),
1420 * Deletes records from the current collection.
1422 * Sorting is done by passing a `sort` string option:
1424 * - The field to order the results by, prefixed with `-` for descending.
1425 * Default: `-last_modified`.
1427 * @see http://kinto.readthedocs.io/en/stable/api/1.x/sorting.html
1429 * Filtering is done by passing a `filters` option object:
1431 * - `{fieldname: "value"}`
1432 * - `{min_fieldname: 4000}`
1433 * - `{in_fieldname: "1,2,3"}`
1434 * - `{not_fieldname: 0}`
1435 * - `{exclude_fieldname: "0,1"}`
1437 * @see http://kinto.readthedocs.io/en/stable/api/1.x/filtering.html
1439 * @param {Object} [options={}] The options object.
1440 * @param {Object} [options.headers] The headers object option.
1441 * @param {Number} [options.retry=0] Number of retries to make
1442 * when faced with transient errors.
1443 * @param {Object} [options.filters={}] The filters object.
1444 * @param {String} [options.sort="-last_modified"] The sort field.
1445 * @param {String} [options.at] The timestamp to get a snapshot at.
1446 * @param {String} [options.limit=null] The limit field.
1447 * @param {String} [options.pages=1] The number of result pages to aggregate.
1448 * @param {Number} [options.since=null] Only retrieve records modified since the provided timestamp.
1449 * @param {Array} [options.fields] Limit response to just some fields.
1450 * @return {Promise<Object, Error>}
1452 async deleteRecords(options = {}) {
1453 const path = this._endpoints.record(this.bucket.name, this.name);
1454 return this.client.paginatedDelete(path, options, {
1455 headers: this._getHeaders(options),
1456 retry: this._getRetry(options),
1460 * Retrieves a record from the current collection.
1462 * @param {String} id The record id to retrieve.
1463 * @param {Object} [options={}] The options object.
1464 * @param {Object} [options.headers] The headers object option.
1465 * @param {Object} [options.query] Query parameters to pass in
1466 * the request. This might be useful for features that aren't
1467 * yet supported by this library.
1468 * @param {Array} [options.fields] Limit response to
1470 * @param {Number} [options.retry=0] Number of retries to make
1471 * when faced with transient errors.
1472 * @return {Promise<Object, Error>}
1474 async getRecord(id, options = {}) {
1475 const path = this._endpoints.record(this.bucket.name, this.name, id);
1476 const request = { headers: this._getHeaders(options), path };
1477 return this.client.execute(request, {
1478 retry: this._getRetry(options),
1479 query: options.query,
1480 fields: options.fields,
1484 * Lists records from the current collection.
1486 * Sorting is done by passing a `sort` string option:
1488 * - The field to order the results by, prefixed with `-` for descending.
1489 * Default: `-last_modified`.
1491 * @see http://kinto.readthedocs.io/en/stable/api/1.x/sorting.html
1493 * Filtering is done by passing a `filters` option object:
1495 * - `{fieldname: "value"}`
1496 * - `{min_fieldname: 4000}`
1497 * - `{in_fieldname: "1,2,3"}`
1498 * - `{not_fieldname: 0}`
1499 * - `{exclude_fieldname: "0,1"}`
1501 * @see http://kinto.readthedocs.io/en/stable/api/1.x/filtering.html
1503 * Paginating is done by passing a `limit` option, then calling the `next()`
1504 * method from the resolved result object to fetch the next page, if any.
1506 * @param {Object} [options={}] The options object.
1507 * @param {Object} [options.headers] The headers object option.
1508 * @param {Number} [options.retry=0] Number of retries to make
1509 * when faced with transient errors.
1510 * @param {Object} [options.filters={}] The filters object.
1511 * @param {String} [options.sort="-last_modified"] The sort field.
1512 * @param {String} [options.at] The timestamp to get a snapshot at.
1513 * @param {String} [options.limit=null] The limit field.
1514 * @param {String} [options.pages=1] The number of result pages to aggregate.
1515 * @param {Number} [options.since=null] Only retrieve records modified since the provided timestamp.
1516 * @param {Array} [options.fields] Limit response to just some fields.
1517 * @return {Promise<Object, Error>}
1519 async listRecords(options = {}) {
1520 const path = this._endpoints.record(this.bucket.name, this.name);
1522 return this.getSnapshot(options.at);
1524 return this.client.paginatedList(path, options, {
1525 headers: this._getHeaders(options),
1526 retry: this._getRetry(options),
1532 async isHistoryComplete() {
1533 // We consider that if we have the collection creation event part of the
1534 // history, then all records change events have been tracked.
1536 data: [oldestHistoryEntry],
1537 } = await this.bucket.listHistory({
1541 resource_name: "collection",
1542 collection_id: this.name,
1545 return !!oldestHistoryEntry;
1550 async getSnapshot(at) {
1551 if (!at || !Number.isInteger(at) || at <= 0) {
1552 throw new Error("Invalid argument, expected a positive integer.");
1554 // Retrieve history and check it covers the required time range.
1555 // Ensure we have enough history data to retrieve the complete list of
1557 if (!(await this.isHistoryComplete())) {
1559 "Computing a snapshot is only possible when the full history for a " +
1560 "collection is available. Here, the history plugin seems to have " +
1561 "been enabled after the creation of the collection."
1564 // Because of https://github.com/Kinto/kinto-http.js/issues/963
1565 // we cannot simply rely on the history endpoint.
1566 // Our strategy here is to clean-up the history entries from the
1567 // records that were deleted via the plural endpoint.
1568 // We will detect them by comparing the current state of the collection
1569 // and the full history of the collection since its genesis.
1570 // List full history of collection.
1571 const { data: fullHistory } = await this.bucket.listHistory({
1573 sort: "last_modified",
1575 resource_name: "record",
1576 collection_id: this.name,
1579 // Keep latest entry ever, and latest within snapshot window.
1580 // (history is sorted chronologically)
1581 const latestEver = new Map();
1582 const latestInSnapshot = new Map();
1583 for (const entry of fullHistory) {
1584 if (entry.target.data.last_modified <= at) {
1585 // Snapshot includes changes right on timestamp.
1586 latestInSnapshot.set(entry.record_id, entry);
1588 latestEver.set(entry.record_id, entry);
1590 // Current records ids in the collection.
1591 const { data: current } = await this.listRecords({
1593 fields: ["id"], // we don't need attributes.
1595 const currentIds = new Set(current.map(record => record.id));
1596 // If a record is not in the current collection, and its
1597 // latest history entry isn't a delete then this means that
1598 // it was deleted via the plural endpoint (and that we lost track
1599 // of this deletion because of bug #963)
1600 const deletedViaPlural = new Set();
1601 for (const entry of latestEver.values()) {
1602 if (entry.action != "delete" && !currentIds.has(entry.record_id)) {
1603 deletedViaPlural.add(entry.record_id);
1606 // Now reconstruct the collection based on latest version in snapshot
1607 // filtering all deleted records.
1608 const reconstructed = [];
1609 for (const entry of latestInSnapshot.values()) {
1610 if (entry.action != "delete" && !deletedViaPlural.has(entry.record_id)) {
1611 reconstructed.push(entry.target.data);
1615 last_modified: String(at),
1616 data: Array.from(reconstructed).sort(
1617 (a, b) => b.last_modified - a.last_modified
1620 throw new Error("Snapshots don't support pagination");
1623 totalRecords: reconstructed.length,
1627 * Performs batch operations at the current collection level.
1629 * @param {Function} fn The batch operation function.
1630 * @param {Object} [options={}] The options object.
1631 * @param {Object} [options.headers] The headers object option.
1632 * @param {Boolean} [options.safe] The safe option.
1633 * @param {Number} [options.retry] The retry option.
1634 * @param {Boolean} [options.aggregate] Produces a grouped result object.
1635 * @return {Promise<Object, Error>}
1637 async batch(fn, options = {}) {
1638 return this.client.batch(fn, {
1639 bucket: this.bucket.name,
1640 collection: this.name,
1641 headers: this._getHeaders(options),
1642 retry: this._getRetry(options),
1643 safe: this._getSafe(options),
1644 aggregate: !!options.aggregate,
1649 [capable(["attachments"])],
1650 Collection.prototype,
1655 [capable(["attachments"])],
1656 Collection.prototype,
1660 __decorate([capable(["history"])], Collection.prototype, "getSnapshot", null);
1663 * Abstract representation of a selected bucket.
1670 * @param {KintoClient} client The client instance.
1671 * @param {String} name The bucket name.
1672 * @param {Object} [options={}] The headers object option.
1673 * @param {Object} [options.headers] The headers object option.
1674 * @param {Boolean} [options.safe] The safe option.
1675 * @param {Number} [options.retry] The retry option.
1677 constructor(client, name, options = {}) {
1681 this.client = client;
1687 this._endpoints = client.endpoints;
1691 this._headers = options.headers || {};
1692 this._retry = options.retry || 0;
1693 this._safe = !!options.safe;
1696 return this.client.execute.bind(this.client);
1699 return this._headers;
1702 * Get the value of "headers" for a given request, merging the
1703 * per-request headers with our own "default" headers.
1707 _getHeaders(options) {
1714 * Get the value of "safe" for a given request, using the
1715 * per-request option if present or falling back to our default
1719 * @param {Object} options The options for a request.
1720 * @returns {Boolean}
1723 return { safe: this._safe, ...options }.safe;
1726 * As _getSafe, but for "retry".
1730 _getRetry(options) {
1731 return { retry: this._retry, ...options }.retry;
1734 * Selects a collection.
1736 * @param {String} name The collection name.
1737 * @param {Object} [options={}] The options object.
1738 * @param {Object} [options.headers] The headers object option.
1739 * @param {Boolean} [options.safe] The safe option.
1740 * @return {Collection}
1742 collection(name, options = {}) {
1743 return new Collection(this.client, this, name, {
1744 headers: this._getHeaders(options),
1745 retry: this._getRetry(options),
1746 safe: this._getSafe(options),
1750 * Retrieves the ETag of the collection list, for use with the `since` filtering option.
1752 * @param {Object} [options={}] The options object.
1753 * @param {Object} [options.headers] The headers object option.
1754 * @param {Number} [options.retry=0] Number of retries to make
1755 * when faced with transient errors.
1756 * @return {Promise<String, Error>}
1758 async getCollectionsTimestamp(options = {}) {
1759 const path = this._endpoints.collection(this.name);
1761 headers: this._getHeaders(options),
1765 const { headers } = await this.client.execute(request, {
1767 retry: this._getRetry(options),
1769 return headers.get("ETag");
1772 * Retrieves the ETag of the group list, for use with the `since` filtering option.
1774 * @param {Object} [options={}] The options object.
1775 * @param {Object} [options.headers] The headers object option.
1776 * @param {Number} [options.retry=0] Number of retries to make
1777 * when faced with transient errors.
1778 * @return {Promise<String, Error>}
1780 async getGroupsTimestamp(options = {}) {
1781 const path = this._endpoints.group(this.name);
1783 headers: this._getHeaders(options),
1787 const { headers } = await this.client.execute(request, {
1789 retry: this._getRetry(options),
1791 return headers.get("ETag");
1794 * Retrieves bucket data.
1796 * @param {Object} [options={}] The options object.
1797 * @param {Object} [options.headers] The headers object option.
1798 * @param {Object} [options.query] Query parameters to pass in
1799 * the request. This might be useful for features that aren't
1800 * yet supported by this library.
1801 * @param {Array} [options.fields] Limit response to
1803 * @param {Number} [options.retry=0] Number of retries to make
1804 * when faced with transient errors.
1805 * @return {Promise<Object, Error>}
1807 async getData(options = {}) {
1808 const path = this._endpoints.bucket(this.name);
1810 headers: this._getHeaders(options),
1813 const { data } = await this.client.execute(request, {
1814 retry: this._getRetry(options),
1815 query: options.query,
1816 fields: options.fields,
1822 * @param {Object} data The bucket data object.
1823 * @param {Object} [options={}] The options object.
1824 * @param {Object} [options.headers={}] The headers object option.
1825 * @param {Boolean} [options.safe] The safe option.
1826 * @param {Number} [options.retry=0] Number of retries to make
1827 * when faced with transient errors.
1828 * @param {Boolean} [options.patch] The patch option.
1829 * @param {Number} [options.last_modified] The last_modified option.
1830 * @return {Promise<Object, Error>}
1832 async setData(data, options = {}) {
1833 if (!isObject(data)) {
1834 throw new Error("A bucket object is required.");
1840 // For default bucket, we need to drop the id from the data object.
1841 // Bug in Kinto < 3.1.1
1842 const bucketId = bucket.id;
1843 if (bucket.id === "default") {
1846 const path = this._endpoints.bucket(bucketId);
1847 const { patch, permissions } = options;
1848 const { last_modified } = { ...data, ...options };
1849 const request = updateRequest(
1851 { data: bucket, permissions },
1855 headers: this._getHeaders(options),
1856 safe: this._getSafe(options),
1859 return this.client.execute(request, {
1860 retry: this._getRetry(options),
1864 * Retrieves the list of history entries in the current bucket.
1866 * @param {Object} [options={}] The options object.
1867 * @param {Object} [options.headers] The headers object option.
1868 * @param {Number} [options.retry=0] Number of retries to make
1869 * when faced with transient errors.
1870 * @return {Promise<Array<Object>, Error>}
1872 async listHistory(options = {}) {
1873 const path = this._endpoints.history(this.name);
1874 return this.client.paginatedList(path, options, {
1875 headers: this._getHeaders(options),
1876 retry: this._getRetry(options),
1880 * Retrieves the list of collections in the current bucket.
1882 * @param {Object} [options={}] The options object.
1883 * @param {Object} [options.filters={}] The filters object.
1884 * @param {Object} [options.headers] The headers object option.
1885 * @param {Number} [options.retry=0] Number of retries to make
1886 * when faced with transient errors.
1887 * @param {Array} [options.fields] Limit response to
1889 * @return {Promise<Array<Object>, Error>}
1891 async listCollections(options = {}) {
1892 const path = this._endpoints.collection(this.name);
1893 return this.client.paginatedList(path, options, {
1894 headers: this._getHeaders(options),
1895 retry: this._getRetry(options),
1899 * Creates a new collection in current bucket.
1901 * @param {String|undefined} id The collection id.
1902 * @param {Object} [options={}] The options object.
1903 * @param {Boolean} [options.safe] The safe option.
1904 * @param {Object} [options.headers] The headers object option.
1905 * @param {Number} [options.retry=0] Number of retries to make
1906 * when faced with transient errors.
1907 * @param {Object} [options.permissions] The permissions object.
1908 * @param {Object} [options.data] The data object.
1909 * @return {Promise<Object, Error>}
1911 async createCollection(id, options = {}) {
1912 const { permissions, data = {} } = options;
1914 const path = this._endpoints.collection(this.name, id);
1915 const request = createRequest(
1917 { data, permissions },
1919 headers: this._getHeaders(options),
1920 safe: this._getSafe(options),
1923 return this.client.execute(request, {
1924 retry: this._getRetry(options),
1928 * Deletes a collection from the current bucket.
1930 * @param {Object|String} collection The collection to delete.
1931 * @param {Object} [options={}] The options object.
1932 * @param {Object} [options.headers] The headers object option.
1933 * @param {Number} [options.retry=0] Number of retries to make
1934 * when faced with transient errors.
1935 * @param {Boolean} [options.safe] The safe option.
1936 * @param {Number} [options.last_modified] The last_modified option.
1937 * @return {Promise<Object, Error>}
1939 async deleteCollection(collection, options = {}) {
1940 const collectionObj = toDataBody(collection);
1941 if (!collectionObj.id) {
1942 throw new Error("A collection id is required.");
1944 const { id } = collectionObj;
1945 const { last_modified } = { ...collectionObj, ...options };
1946 const path = this._endpoints.collection(this.name, id);
1947 const request = deleteRequest(path, {
1949 headers: this._getHeaders(options),
1950 safe: this._getSafe(options),
1952 return this.client.execute(request, {
1953 retry: this._getRetry(options),
1957 * Deletes collections from the current bucket.
1959 * @param {Object} [options={}] The options object.
1960 * @param {Object} [options.filters={}] The filters object.
1961 * @param {Object} [options.headers] The headers object option.
1962 * @param {Number} [options.retry=0] Number of retries to make
1963 * when faced with transient errors.
1964 * @param {Array} [options.fields] Limit response to
1966 * @return {Promise<Array<Object>, Error>}
1968 async deleteCollections(options = {}) {
1969 const path = this._endpoints.collection(this.name);
1970 return this.client.paginatedDelete(path, options, {
1971 headers: this._getHeaders(options),
1972 retry: this._getRetry(options),
1976 * Retrieves the list of groups in the current bucket.
1978 * @param {Object} [options={}] The options object.
1979 * @param {Object} [options.filters={}] The filters object.
1980 * @param {Object} [options.headers] The headers object option.
1981 * @param {Number} [options.retry=0] Number of retries to make
1982 * when faced with transient errors.
1983 * @param {Array} [options.fields] Limit response to
1985 * @return {Promise<Array<Object>, Error>}
1987 async listGroups(options = {}) {
1988 const path = this._endpoints.group(this.name);
1989 return this.client.paginatedList(path, options, {
1990 headers: this._getHeaders(options),
1991 retry: this._getRetry(options),
1995 * Fetches a group in current bucket.
1997 * @param {String} id The group id.
1998 * @param {Object} [options={}] The options object.
1999 * @param {Object} [options.headers] The headers object option.
2000 * @param {Number} [options.retry=0] Number of retries to make
2001 * when faced with transient errors.
2002 * @param {Object} [options.query] Query parameters to pass in
2003 * the request. This might be useful for features that aren't
2004 * yet supported by this library.
2005 * @param {Array} [options.fields] Limit response to
2007 * @return {Promise<Object, Error>}
2009 async getGroup(id, options = {}) {
2010 const path = this._endpoints.group(this.name, id);
2012 headers: this._getHeaders(options),
2015 return this.client.execute(request, {
2016 retry: this._getRetry(options),
2017 query: options.query,
2018 fields: options.fields,
2022 * Creates a new group in current bucket.
2024 * @param {String|undefined} id The group id.
2025 * @param {Array<String>} [members=[]] The list of principals.
2026 * @param {Object} [options={}] The options object.
2027 * @param {Object} [options.data] The data object.
2028 * @param {Object} [options.permissions] The permissions object.
2029 * @param {Boolean} [options.safe] The safe option.
2030 * @param {Object} [options.headers] The headers object option.
2031 * @param {Number} [options.retry=0] Number of retries to make
2032 * when faced with transient errors.
2033 * @return {Promise<Object, Error>}
2035 async createGroup(id, members = [], options = {}) {
2041 const path = this._endpoints.group(this.name, id);
2042 const { permissions } = options;
2043 const request = createRequest(
2045 { data, permissions },
2047 headers: this._getHeaders(options),
2048 safe: this._getSafe(options),
2051 return this.client.execute(request, {
2052 retry: this._getRetry(options),
2056 * Updates an existing group in current bucket.
2058 * @param {Object} group The group object.
2059 * @param {Object} [options={}] The options object.
2060 * @param {Object} [options.data] The data object.
2061 * @param {Object} [options.permissions] The permissions object.
2062 * @param {Boolean} [options.safe] The safe option.
2063 * @param {Object} [options.headers] The headers object option.
2064 * @param {Number} [options.retry=0] Number of retries to make
2065 * when faced with transient errors.
2066 * @param {Number} [options.last_modified] The last_modified option.
2067 * @return {Promise<Object, Error>}
2069 async updateGroup(group, options = {}) {
2070 if (!isObject(group)) {
2071 throw new Error("A group object is required.");
2074 throw new Error("A group id is required.");
2080 const path = this._endpoints.group(this.name, group.id);
2081 const { patch, permissions } = options;
2082 const { last_modified } = { ...data, ...options };
2083 const request = updateRequest(
2085 { data, permissions },
2089 headers: this._getHeaders(options),
2090 safe: this._getSafe(options),
2093 return this.client.execute(request, {
2094 retry: this._getRetry(options),
2098 * Deletes a group from the current bucket.
2100 * @param {Object|String} group The group to delete.
2101 * @param {Object} [options={}] The options object.
2102 * @param {Object} [options.headers] The headers object option.
2103 * @param {Number} [options.retry=0] Number of retries to make
2104 * when faced with transient errors.
2105 * @param {Boolean} [options.safe] The safe option.
2106 * @param {Number} [options.last_modified] The last_modified option.
2107 * @return {Promise<Object, Error>}
2109 async deleteGroup(group, options = {}) {
2110 const groupObj = toDataBody(group);
2111 const { id } = groupObj;
2112 const { last_modified } = { ...groupObj, ...options };
2113 const path = this._endpoints.group(this.name, id);
2114 const request = deleteRequest(path, {
2116 headers: this._getHeaders(options),
2117 safe: this._getSafe(options),
2119 return this.client.execute(request, {
2120 retry: this._getRetry(options),
2124 * Deletes groups from the current bucket.
2126 * @param {Object} [options={}] The options object.
2127 * @param {Object} [options.filters={}] The filters object.
2128 * @param {Object} [options.headers] The headers object option.
2129 * @param {Number} [options.retry=0] Number of retries to make
2130 * when faced with transient errors.
2131 * @param {Array} [options.fields] Limit response to
2133 * @return {Promise<Array<Object>, Error>}
2135 async deleteGroups(options = {}) {
2136 const path = this._endpoints.group(this.name);
2137 return this.client.paginatedDelete(path, options, {
2138 headers: this._getHeaders(options),
2139 retry: this._getRetry(options),
2143 * Retrieves the list of permissions for this bucket.
2145 * @param {Object} [options={}] The options object.
2146 * @param {Object} [options.headers] The headers object option.
2147 * @param {Number} [options.retry=0] Number of retries to make
2148 * when faced with transient errors.
2149 * @return {Promise<Object, Error>}
2151 async getPermissions(options = {}) {
2153 headers: this._getHeaders(options),
2154 path: this._endpoints.bucket(this.name),
2156 const { permissions } = await this.client.execute(request, {
2157 retry: this._getRetry(options),
2162 * Replaces all existing bucket permissions with the ones provided.
2164 * @param {Object} permissions The permissions object.
2165 * @param {Object} [options={}] The options object
2166 * @param {Boolean} [options.safe] The safe option.
2167 * @param {Object} [options.headers={}] The headers object option.
2168 * @param {Number} [options.retry=0] Number of retries to make
2169 * when faced with transient errors.
2170 * @param {Object} [options.last_modified] The last_modified option.
2171 * @return {Promise<Object, Error>}
2173 async setPermissions(permissions, options = {}) {
2174 if (!isObject(permissions)) {
2175 throw new Error("A permissions object is required.");
2177 const path = this._endpoints.bucket(this.name);
2178 const { last_modified } = options;
2179 const data = { last_modified };
2180 const request = updateRequest(
2182 { data, permissions },
2184 headers: this._getHeaders(options),
2185 safe: this._getSafe(options),
2188 return this.client.execute(request, {
2189 retry: this._getRetry(options),
2193 * Append principals to the bucket permissions.
2195 * @param {Object} permissions The permissions object.
2196 * @param {Object} [options={}] The options object
2197 * @param {Boolean} [options.safe] The safe option.
2198 * @param {Object} [options.headers] The headers object option.
2199 * @param {Number} [options.retry=0] Number of retries to make
2200 * when faced with transient errors.
2201 * @param {Object} [options.last_modified] The last_modified option.
2202 * @return {Promise<Object, Error>}
2204 async addPermissions(permissions, options = {}) {
2205 if (!isObject(permissions)) {
2206 throw new Error("A permissions object is required.");
2208 const path = this._endpoints.bucket(this.name);
2209 const { last_modified } = options;
2210 const request = jsonPatchPermissionsRequest(path, permissions, "add", {
2212 headers: this._getHeaders(options),
2213 safe: this._getSafe(options),
2215 return this.client.execute(request, {
2216 retry: this._getRetry(options),
2220 * Remove principals from the bucket permissions.
2222 * @param {Object} permissions The permissions object.
2223 * @param {Object} [options={}] The options object
2224 * @param {Boolean} [options.safe] The safe option.
2225 * @param {Object} [options.headers] The headers object option.
2226 * @param {Number} [options.retry=0] Number of retries to make
2227 * when faced with transient errors.
2228 * @param {Object} [options.last_modified] The last_modified option.
2229 * @return {Promise<Object, Error>}
2231 async removePermissions(permissions, options = {}) {
2232 if (!isObject(permissions)) {
2233 throw new Error("A permissions object is required.");
2235 const path = this._endpoints.bucket(this.name);
2236 const { last_modified } = options;
2237 const request = jsonPatchPermissionsRequest(path, permissions, "remove", {
2239 headers: this._getHeaders(options),
2240 safe: this._getSafe(options),
2242 return this.client.execute(request, {
2243 retry: this._getRetry(options),
2247 * Performs batch operations at the current bucket level.
2249 * @param {Function} fn The batch operation function.
2250 * @param {Object} [options={}] The options object.
2251 * @param {Object} [options.headers] The headers object option.
2252 * @param {Boolean} [options.safe] The safe option.
2253 * @param {Number} [options.retry=0] The retry option.
2254 * @param {Boolean} [options.aggregate] Produces a grouped result object.
2255 * @return {Promise<Object, Error>}
2257 async batch(fn, options = {}) {
2258 return this.client.batch(fn, {
2260 headers: this._getHeaders(options),
2261 retry: this._getRetry(options),
2262 safe: this._getSafe(options),
2263 aggregate: !!options.aggregate,
2267 __decorate([capable(["history"])], Bucket.prototype, "listHistory", null);
2270 * Currently supported protocol version.
2273 const SUPPORTED_PROTOCOL_VERSION = "v1";
2275 * High level HTTP client for the Kinto API.
2278 * const client = new KintoClient("https://demo.kinto-storage.org/v1");
2279 * client.bucket("default")
2280 * .collection("my-blog")
2281 * .createRecord({title: "First article"})
2282 * .then(console.log.bind(console))
2283 * .catch(console.error.bind(console));
2285 class KintoClientBase {
2289 * @param {String} remote The remote URL.
2290 * @param {Object} [options={}] The options object.
2291 * @param {Boolean} [options.safe=true] Adds concurrency headers to every requests.
2292 * @param {EventEmitter} [options.events=EventEmitter] The events handler instance.
2293 * @param {Object} [options.headers={}] The key-value headers to pass to each request.
2294 * @param {Object} [options.retry=0] Number of retries when request fails (default: 0)
2295 * @param {String} [options.bucket="default"] The default bucket to use.
2296 * @param {String} [options.requestMode="cors"] The HTTP request mode (from ES6 fetch spec).
2297 * @param {Number} [options.timeout=null] The request timeout in ms, if any.
2298 * @param {Function} [options.fetchFunc=fetch] The function to be used to execute HTTP requests.
2300 constructor(remote, options) {
2301 if (typeof remote !== "string" || !remote.length) {
2302 throw new Error("Invalid remote URL: " + remote);
2304 if (remote[remote.length - 1] === "/") {
2305 remote = remote.slice(0, -1);
2307 this._backoffReleaseTime = null;
2308 this._requests = [];
2309 this._isBatch = !!options.batch;
2310 this._retry = options.retry || 0;
2311 this._safe = !!options.safe;
2312 this._headers = options.headers || {};
2313 // public properties
2315 * The remote server base URL.
2318 this.remote = remote;
2320 * Current server information.
2322 * @type {Object|null}
2324 this.serverInfo = null;
2326 * The event emitter instance. Should comply with the `EventEmitter`
2331 this.events = options.events;
2332 this.endpoints = ENDPOINTS;
2333 const { fetchFunc, requestMode, timeout } = options;
2335 * The HTTP instance.
2339 this.http = new HTTP(this.events, { fetchFunc, requestMode, timeout });
2340 this._registerHTTPEvents();
2343 * The remote endpoint base URL. Setting the value will also extract and
2344 * validate the version.
2348 return this._remote;
2356 version = url.match(/\/(v\d+)\/?$/)[1];
2358 throw new Error("The remote URL must contain the version: " + url);
2360 if (version !== SUPPORTED_PROTOCOL_VERSION) {
2361 throw new Error(`Unsupported protocol version: ${version}`);
2364 this._version = version;
2367 * The current server protocol version, eg. `v1`.
2371 return this._version;
2374 * Backoff remaining time, in milliseconds. Defaults to zero if no backoff is
2380 const currentTime = new Date().getTime();
2381 if (this._backoffReleaseTime && currentTime < this._backoffReleaseTime) {
2382 return this._backoffReleaseTime - currentTime;
2387 * Registers HTTP events.
2390 _registerHTTPEvents() {
2391 // Prevent registering event from a batch client instance
2392 if (!this._isBatch && this.events) {
2393 this.events.on("backoff", backoffMs => {
2394 this._backoffReleaseTime = backoffMs;
2399 * Retrieve a bucket object to perform operations on it.
2401 * @param {String} name The bucket name.
2402 * @param {Object} [options={}] The request options.
2403 * @param {Boolean} [options.safe] The resulting safe option.
2404 * @param {Number} [options.retry] The resulting retry option.
2405 * @param {Object} [options.headers] The extended headers object option.
2408 bucket(name, options = {}) {
2409 return new Bucket(this, name, {
2410 headers: this._getHeaders(options),
2411 safe: this._getSafe(options),
2412 retry: this._getRetry(options),
2416 * Set client "headers" for every request, updating previous headers (if any).
2418 * @param {Object} headers The headers to merge with existing ones.
2420 setHeaders(headers) {
2425 this.serverInfo = null;
2428 * Get the value of "headers" for a given request, merging the
2429 * per-request headers with our own "default" headers.
2431 * Note that unlike other options, headers aren't overridden, but
2435 * @param {Object} options The options for a request.
2438 _getHeaders(options) {
2445 * Get the value of "safe" for a given request, using the
2446 * per-request option if present or falling back to our default
2450 * @param {Object} options The options for a request.
2451 * @returns {Boolean}
2454 return { safe: this._safe, ...options }.safe;
2457 * As _getSafe, but for "retry".
2461 _getRetry(options) {
2462 return { retry: this._retry, ...options }.retry;
2465 * Retrieves the server's "hello" endpoint. This endpoint reveals
2466 * server capabilities and settings as well as telling the client
2467 * "who they are" according to their given authorization headers.
2470 * @param {Object} [options={}] The request options.
2471 * @param {Object} [options.headers={}] Headers to use when making
2473 * @param {Number} [options.retry=0] Number of retries to make
2474 * when faced with transient errors.
2475 * @return {Promise<Object, Error>}
2477 async _getHello(options = {}) {
2478 const path = this.remote + ENDPOINTS.root();
2479 const { json } = await this.http.request(
2481 { headers: this._getHeaders(options) },
2482 { retry: this._getRetry(options) }
2487 * Retrieves server information and persist them locally. This operation is
2488 * usually performed a single time during the instance lifecycle.
2490 * @param {Object} [options={}] The request options.
2491 * @param {Number} [options.retry=0] Number of retries to make
2492 * when faced with transient errors.
2493 * @return {Promise<Object, Error>}
2495 async fetchServerInfo(options = {}) {
2496 if (this.serverInfo) {
2497 return this.serverInfo;
2499 this.serverInfo = await this._getHello({ retry: this._getRetry(options) });
2500 return this.serverInfo;
2503 * Retrieves Kinto server settings.
2505 * @param {Object} [options={}] The request options.
2506 * @param {Number} [options.retry=0] Number of retries to make
2507 * when faced with transient errors.
2508 * @return {Promise<Object, Error>}
2510 async fetchServerSettings(options = {}) {
2511 const { settings } = await this.fetchServerInfo(options);
2515 * Retrieve server capabilities information.
2517 * @param {Object} [options={}] The request options.
2518 * @param {Number} [options.retry=0] Number of retries to make
2519 * when faced with transient errors.
2520 * @return {Promise<Object, Error>}
2522 async fetchServerCapabilities(options = {}) {
2523 const { capabilities } = await this.fetchServerInfo(options);
2524 return capabilities;
2527 * Retrieve authenticated user information.
2529 * @param {Object} [options={}] The request options.
2530 * @param {Object} [options.headers={}] Headers to use when making
2532 * @param {Number} [options.retry=0] Number of retries to make
2533 * when faced with transient errors.
2534 * @return {Promise<Object, Error>}
2536 async fetchUser(options = {}) {
2537 const { user } = await this._getHello(options);
2541 * Retrieve authenticated user information.
2543 * @param {Object} [options={}] The request options.
2544 * @param {Number} [options.retry=0] Number of retries to make
2545 * when faced with transient errors.
2546 * @return {Promise<Object, Error>}
2548 async fetchHTTPApiVersion(options = {}) {
2549 const { http_api_version } = await this.fetchServerInfo(options);
2550 return http_api_version;
2553 * Process batch requests, chunking them according to the batch_max_requests
2554 * server setting when needed.
2556 * @param {Array} requests The list of batch subrequests to perform.
2557 * @param {Object} [options={}] The options object.
2558 * @return {Promise<Object, Error>}
2560 async _batchRequests(requests, options = {}) {
2561 const headers = this._getHeaders(options);
2562 if (!requests.length) {
2565 const serverSettings = await this.fetchServerSettings({
2566 retry: this._getRetry(options),
2568 const maxRequests = serverSettings.batch_max_requests;
2569 if (maxRequests && requests.length > maxRequests) {
2570 const chunks = partition(requests, maxRequests);
2572 for (const chunk of chunks) {
2573 const result = await this._batchRequests(chunk, options);
2574 results.push(...result);
2578 const { responses } = await this.execute(
2580 // FIXME: is this really necessary, since it's also present in
2583 path: ENDPOINTS.batch(),
2586 defaults: { headers },
2590 { retry: this._getRetry(options) }
2595 * Sends batch requests to the remote server.
2597 * Note: Reserved for internal use only.
2600 * @param {Function} fn The function to use for describing batch ops.
2601 * @param {Object} [options={}] The options object.
2602 * @param {Boolean} [options.safe] The safe option.
2603 * @param {Number} [options.retry] The retry option.
2604 * @param {String} [options.bucket] The bucket name option.
2605 * @param {String} [options.collection] The collection name option.
2606 * @param {Object} [options.headers] The headers object option.
2607 * @param {Boolean} [options.aggregate=false] Produces an aggregated result object.
2608 * @return {Promise<Object, Error>}
2610 async batch(fn, options = {}) {
2611 const rootBatch = new KintoClientBase(this.remote, {
2612 events: this.events,
2614 safe: this._getSafe(options),
2615 retry: this._getRetry(options),
2617 if (options.bucket && options.collection) {
2618 fn(rootBatch.bucket(options.bucket).collection(options.collection));
2619 } else if (options.bucket) {
2620 fn(rootBatch.bucket(options.bucket));
2624 const responses = await this._batchRequests(rootBatch._requests, options);
2625 if (options.aggregate) {
2626 return aggregate(responses, rootBatch._requests);
2630 async execute(request, options = {}) {
2631 const { raw = false, stringify = true } = options;
2632 // If we're within a batch, add the request to the stack to send at once.
2633 if (this._isBatch) {
2634 this._requests.push(request);
2635 // Resolve with a message in case people attempt at consuming the result
2636 // from within a batch operation.
2638 "This result is generated from within a batch " +
2639 "operation and should not be consumed.";
2640 return raw ? { status: 0, json: msg, headers: new Headers() } : msg;
2642 const uri = this.remote + addEndpointOptions(request.path, options);
2643 const result = await this.http.request(
2645 cleanUndefinedProperties({
2646 // Limit requests to only those parts that would be allowed in
2647 // a batch request -- don't pass through other fancy fetch()
2648 // options like integrity, redirect, mode because they will
2649 // break on a batch request. A batch request only allows
2650 // headers, method, path (above), and body.
2651 method: request.method,
2652 headers: request.headers,
2653 body: stringify ? JSON.stringify(request.body) : request.body,
2655 { retry: this._getRetry(options) }
2657 return raw ? result : result.json;
2660 * Perform an operation with a given HTTP method on some pages from
2661 * a paginated list, following the `next-page` header automatically
2662 * until we have processed the requested number of pages. Return a
2663 * response with a `.next()` method that can be called to perform
2664 * the requested HTTP method on more results.
2667 * @param {String} path
2668 * The path to make the request to.
2669 * @param {Object} params
2670 * The parameters to use when making the request.
2671 * @param {String} [params.sort="-last_modified"]
2672 * The sorting order to use when doing operation on pages.
2673 * @param {Object} [params.filters={}]
2674 * The filters to send in the request.
2675 * @param {Number} [params.limit=undefined]
2676 * The limit to send in the request. Undefined means no limit.
2677 * @param {Number} [params.pages=undefined]
2678 * The number of pages to operate on. Undefined means one page. Pass
2679 * Infinity to operate on everything.
2680 * @param {String} [params.since=undefined]
2681 * The ETag from which to start doing operation on pages.
2682 * @param {Array} [params.fields]
2683 * Limit response to just some fields.
2684 * @param {Object} [options={}]
2685 * Additional request-level parameters to use in all requests.
2686 * @param {Object} [options.headers={}]
2687 * Headers to use during all requests.
2688 * @param {Number} [options.retry=0]
2689 * Number of times to retry each request if the server responds
2691 * @param {String} [options.method="GET"]
2692 * The method to use in the request.
2694 async paginatedOperation(path, params = {}, options = {}) {
2695 // FIXME: this is called even in batch requests, which doesn't
2696 // make any sense (since all batch requests get a "dummy"
2697 // response; see execute() above).
2698 const { sort, filters, limit, pages, since, fields } = {
2699 sort: "-last_modified",
2702 // Safety/Consistency check on ETag value.
2703 if (since && typeof since !== "string") {
2705 `Invalid value for since (${since}), should be ETag value.`
2715 query._fields = fields;
2717 const querystring = qsify(query);
2720 const next = async function (nextPage) {
2722 throw new Error("Pagination exhausted.");
2724 return processNextPage(nextPage);
2726 const processNextPage = async nextPage => {
2727 const { headers } = options;
2728 return handleResponse(await this.http.request(nextPage, { headers }));
2730 const pageResults = (results, nextPage, etag) => {
2731 // ETag string is supposed to be opaque and stored «as-is».
2732 // ETag header values are quoted (because of * and W/"foo").
2734 last_modified: etag ? etag.replace(/"/g, "") : etag,
2736 next: next.bind(null, nextPage),
2737 hasNextPage: !!nextPage,
2741 const handleResponse = async function ({
2742 headers = new Headers(),
2745 const nextPage = headers.get("Next-Page");
2746 const etag = headers.get("ETag");
2748 return pageResults(json.data, nextPage, etag);
2750 // Aggregate new results with previous ones
2751 results = results.concat(json.data);
2753 if (current >= pages || !nextPage) {
2754 // Pagination exhausted
2755 return pageResults(results, nextPage, etag);
2758 return processNextPage(nextPage);
2760 return handleResponse(
2762 // N.B.: This doesn't use _getHeaders, because all calls to
2763 // `paginatedList` are assumed to come from calls that already
2764 // have headers merged at e.g. the bucket or collection level.
2766 headers: options.headers ? options.headers : {},
2767 path: path + "?" + querystring,
2768 method: options.method,
2770 // N.B. This doesn't use _getRetry, because all calls to
2771 // `paginatedList` are assumed to come from calls that already
2772 // used `_getRetry` at e.g. the bucket or collection level.
2773 { raw: true, retry: options.retry || 0 }
2778 * Fetch some pages from a paginated list, following the `next-page`
2779 * header automatically until we have fetched the requested number
2780 * of pages. Return a response with a `.next()` method that can be
2781 * called to fetch more results.
2784 * @param {String} path
2785 * The path to make the request to.
2786 * @param {Object} params
2787 * The parameters to use when making the request.
2788 * @param {String} [params.sort="-last_modified"]
2789 * The sorting order to use when fetching.
2790 * @param {Object} [params.filters={}]
2791 * The filters to send in the request.
2792 * @param {Number} [params.limit=undefined]
2793 * The limit to send in the request. Undefined means no limit.
2794 * @param {Number} [params.pages=undefined]
2795 * The number of pages to fetch. Undefined means one page. Pass
2796 * Infinity to fetch everything.
2797 * @param {String} [params.since=undefined]
2798 * The ETag from which to start fetching.
2799 * @param {Array} [params.fields]
2800 * Limit response to just some fields.
2801 * @param {Object} [options={}]
2802 * Additional request-level parameters to use in all requests.
2803 * @param {Object} [options.headers={}]
2804 * Headers to use during all requests.
2805 * @param {Number} [options.retry=0]
2806 * Number of times to retry each request if the server responds
2809 async paginatedList(path, params = {}, options = {}) {
2810 return this.paginatedOperation(path, params, options);
2813 * Delete multiple objects, following the pagination if the number of
2814 * objects exceeds the page limit until we have deleted the requested
2815 * number of pages. Return a response with a `.next()` method that can
2816 * be called to delete more results.
2819 * @param {String} path
2820 * The path to make the request to.
2821 * @param {Object} params
2822 * The parameters to use when making the request.
2823 * @param {String} [params.sort="-last_modified"]
2824 * The sorting order to use when deleting.
2825 * @param {Object} [params.filters={}]
2826 * The filters to send in the request.
2827 * @param {Number} [params.limit=undefined]
2828 * The limit to send in the request. Undefined means no limit.
2829 * @param {Number} [params.pages=undefined]
2830 * The number of pages to delete. Undefined means one page. Pass
2831 * Infinity to delete everything.
2832 * @param {String} [params.since=undefined]
2833 * The ETag from which to start deleting.
2834 * @param {Array} [params.fields]
2835 * Limit response to just some fields.
2836 * @param {Object} [options={}]
2837 * Additional request-level parameters to use in all requests.
2838 * @param {Object} [options.headers={}]
2839 * Headers to use during all requests.
2840 * @param {Number} [options.retry=0]
2841 * Number of times to retry each request if the server responds
2844 paginatedDelete(path, params = {}, options = {}) {
2845 const { headers, safe, last_modified } = options;
2846 const deleteRequest$1 = deleteRequest(path, {
2848 safe: safe ? safe : false,
2851 return this.paginatedOperation(path, params, {
2853 headers: deleteRequest$1.headers,
2858 * Lists all permissions.
2860 * @param {Object} [options={}] The options object.
2861 * @param {Object} [options.headers={}] Headers to use when making
2863 * @param {Number} [options.retry=0] Number of retries to make
2864 * when faced with transient errors.
2865 * @return {Promise<Object[], Error>}
2867 async listPermissions(options = {}) {
2868 const path = ENDPOINTS.permissions();
2869 // Ensure the default sort parameter is something that exists in permissions
2870 // entries, as `last_modified` doesn't; here, we pick "id".
2871 const paginationOptions = { sort: "id", ...options };
2872 return this.paginatedList(path, paginationOptions, {
2873 headers: this._getHeaders(options),
2874 retry: this._getRetry(options),
2878 * Retrieves the list of buckets.
2880 * @param {Object} [options={}] The options object.
2881 * @param {Object} [options.headers={}] Headers to use when making
2883 * @param {Number} [options.retry=0] Number of retries to make
2884 * when faced with transient errors.
2885 * @param {Object} [options.filters={}] The filters object.
2886 * @param {Array} [options.fields] Limit response to
2888 * @return {Promise<Object[], Error>}
2890 async listBuckets(options = {}) {
2891 const path = ENDPOINTS.bucket();
2892 return this.paginatedList(path, options, {
2893 headers: this._getHeaders(options),
2894 retry: this._getRetry(options),
2898 * Creates a new bucket on the server.
2900 * @param {String|null} id The bucket name (optional).
2901 * @param {Object} [options={}] The options object.
2902 * @param {Boolean} [options.data] The bucket data option.
2903 * @param {Boolean} [options.safe] The safe option.
2904 * @param {Object} [options.headers] The headers object option.
2905 * @param {Number} [options.retry=0] Number of retries to make
2906 * when faced with transient errors.
2907 * @return {Promise<Object, Error>}
2909 async createBucket(id, options = {}) {
2910 const { data, permissions } = options;
2911 const _data = { ...data, id: id ? id : undefined };
2912 const path = _data.id ? ENDPOINTS.bucket(_data.id) : ENDPOINTS.bucket();
2913 return this.execute(
2916 { data: _data, permissions },
2918 headers: this._getHeaders(options),
2919 safe: this._getSafe(options),
2922 { retry: this._getRetry(options) }
2926 * Deletes a bucket from the server.
2929 * @param {Object|String} bucket The bucket to delete.
2930 * @param {Object} [options={}] The options object.
2931 * @param {Boolean} [options.safe] The safe option.
2932 * @param {Object} [options.headers] The headers object option.
2933 * @param {Number} [options.retry=0] Number of retries to make
2934 * when faced with transient errors.
2935 * @param {Number} [options.last_modified] The last_modified option.
2936 * @return {Promise<Object, Error>}
2938 async deleteBucket(bucket, options = {}) {
2939 const bucketObj = toDataBody(bucket);
2940 if (!bucketObj.id) {
2941 throw new Error("A bucket id is required.");
2943 const path = ENDPOINTS.bucket(bucketObj.id);
2944 const { last_modified } = { ...bucketObj, ...options };
2945 return this.execute(
2946 deleteRequest(path, {
2948 headers: this._getHeaders(options),
2949 safe: this._getSafe(options),
2951 { retry: this._getRetry(options) }
2957 * @param {Object} [options={}] The options object.
2958 * @param {Boolean} [options.safe] The safe option.
2959 * @param {Object} [options.headers={}] Headers to use when making
2961 * @param {Number} [options.retry=0] Number of retries to make
2962 * when faced with transient errors.
2963 * @param {Object} [options.filters={}] The filters object.
2964 * @param {Array} [options.fields] Limit response to
2966 * @param {Number} [options.last_modified] The last_modified option.
2967 * @return {Promise<Object[], Error>}
2969 async deleteBuckets(options = {}) {
2970 const path = ENDPOINTS.bucket();
2971 return this.paginatedDelete(path, options, {
2972 headers: this._getHeaders(options),
2973 retry: this._getRetry(options),
2975 last_modified: options.last_modified,
2978 async createAccount(username, password) {
2979 return this.execute(
2981 `/accounts/${username}`,
2982 { data: { password } },
2989 [nobatch("This operation is not supported within a batch operation.")],
2990 KintoClientBase.prototype,
2991 "fetchServerSettings",
2995 [nobatch("This operation is not supported within a batch operation.")],
2996 KintoClientBase.prototype,
2997 "fetchServerCapabilities",
3001 [nobatch("This operation is not supported within a batch operation.")],
3002 KintoClientBase.prototype,
3007 [nobatch("This operation is not supported within a batch operation.")],
3008 KintoClientBase.prototype,
3009 "fetchHTTPApiVersion",
3013 [nobatch("Can't use batch within a batch!")],
3014 KintoClientBase.prototype,
3019 [capable(["permissions_endpoint"])],
3020 KintoClientBase.prototype,
3025 [support("1.4", "2.0")],
3026 KintoClientBase.prototype,
3031 [capable(["accounts"])],
3032 KintoClientBase.prototype,
3039 * Licensed under the Apache License, Version 2.0 (the "License");
3040 * you may not use this file except in compliance with the License.
3041 * You may obtain a copy of the License at
3043 * http://www.apache.org/licenses/LICENSE-2.0
3045 * Unless required by applicable law or agreed to in writing, software
3046 * distributed under the License is distributed on an "AS IS" BASIS,
3047 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3048 * See the License for the specific language governing permissions and
3049 * limitations under the License.
3052 class KintoHttpClient extends KintoClientBase {
3053 constructor(remote, options = {}) {
3055 EventEmitter.decorate(events);
3056 super(remote, { events: events, ...options });
3059 KintoHttpClient.errors = errors;
3061 export { KintoHttpClient };