Bug 1852754: part 9) Add tests for dynamically loading <link rel="prefetch"> elements...
[gecko.git] / services / common / kinto-http-client.sys.mjs
blob6ccaa520c1d50ac75dd6cf4b0adb21d3370d4619
1 /*
2  *
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
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
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.
14  *
15  * This file is generated from kinto.js - do not modify directly.
16  */
18 import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";
21  * Version 15.0.0 - c8775d9
22  */
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,
44     r =
45       c < 3
46         ? target
47         : desc === null
48         ? (desc = Object.getOwnPropertyDescriptor(target, key))
49         : desc,
50     d;
51   if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
52     r = Reflect.decorate(decorators, target, key, desc);
53   else
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;
60 /**
61  * Chunks an array into n pieces.
62  *
63  * @private
64  * @param  {Array}  array
65  * @param  {Number} n
66  * @return {Array}
67  */
68 function partition(array, n) {
69   if (n <= 0) {
70     return [array];
71   }
72   return array.reduce((acc, x, i) => {
73     if (i === 0 || i % n === 0) {
74       acc.push([x]);
75     } else {
76       acc[acc.length - 1].push(x);
77     }
78     return acc;
79   }, []);
81 /**
82  * Returns a Promise always resolving after the specified amount in milliseconds.
83  *
84  * @return Promise<void>
85  */
86 function delay(ms) {
87   return new Promise(resolve => setTimeout(resolve, ms));
89 /**
90  * Always returns a resource data object from the provided argument.
91  *
92  * @private
93  * @param  {Object|String} resource
94  * @return {Object}
95  */
96 function toDataBody(resource) {
97   if (isObject(resource)) {
98     return resource;
99   }
100   if (typeof resource === "string") {
101     return { id: resource };
102   }
103   throw new Error("Invalid argument.");
106  * Transforms an object into an URL query string, stripping out any undefined
107  * values.
109  * @param  {Object} obj
110  * @return {String}
111  */
112 function qsify(obj) {
113   const encode = v =>
114     encodeURIComponent(typeof v === "boolean" ? String(v) : v);
115   const stripped = cleanUndefinedProperties(obj);
116   return Object.keys(stripped)
117     .map(k => {
118       const ks = encode(k) + "=";
119       if (Array.isArray(stripped[k])) {
120         return ks + stripped[k].map(v => encode(v)).join(",");
121       }
122       return ks + encode(stripped[k]);
123     })
124     .join("&");
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.
133  */
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);
139   const checks = [
140     verMajor < minMajor,
141     verMajor === minMajor && verMinor < minMinor,
142     verMajor > maxMajor,
143     verMajor === maxMajor && verMinor >= maxMinor,
144   ];
145   if (checks.some(x => x)) {
146     throw new Error(
147       `Version ${version} doesn't satisfy ${minVersion} <= x < ${maxVersion}`
148     );
149   }
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).
157  * @return {Function}
158  */
159 function support(min, max) {
160   return function (
161     // @ts-ignore
162     target,
163     key,
164     descriptor
165   ) {
166     const fn = descriptor.value;
167     return {
168       configurable: true,
169       get() {
170         const wrappedMethod = (...args) => {
171           // "this" is the current instance which its method is decorated.
172           const client = this.client ? this.client : this;
173           return client
174             .fetchHTTPApiVersion()
175             .then(version => checkVersion(version, min, max))
176             .then(() => fn.apply(this, args));
177         };
178         Object.defineProperty(this, key, {
179           value: wrappedMethod,
180           configurable: true,
181           writable: true,
182         });
183         return wrappedMethod;
184       },
185     };
186   };
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.
193  * @return {Function}
194  */
195 function capable(capabilities) {
196   return function (
197     // @ts-ignore
198     target,
199     key,
200     descriptor
201   ) {
202     const fn = descriptor.value;
203     return {
204       configurable: true,
205       get() {
206         const wrappedMethod = (...args) => {
207           // "this" is the current instance which its method is decorated.
208           const client = this.client ? this.client : this;
209           return client
210             .fetchServerCapabilities()
211             .then(available => {
212               const missing = capabilities.filter(c => !(c in available));
213               if (missing.length) {
214                 const missingStr = missing.join(", ");
215                 throw new Error(
216                   `Required capabilities ${missingStr} not present on server`
217                 );
218               }
219             })
220             .then(() => fn.apply(this, args));
221         };
222         Object.defineProperty(this, key, {
223           value: wrappedMethod,
224           configurable: true,
225           writable: true,
226         });
227         return wrappedMethod;
228       },
229     };
230   };
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.
237  * @return {Function}
238  */
239 function nobatch(message) {
240   return function (
241     // @ts-ignore
242     target,
243     key,
244     descriptor
245   ) {
246     const fn = descriptor.value;
247     return {
248       configurable: true,
249       get() {
250         const wrappedMethod = (...args) => {
251           // "this" is the current instance which its method is decorated.
252           if (this._isBatch) {
253             throw new Error(message);
254           }
255           return fn.apply(this, args);
256         };
257         Object.defineProperty(this, key, {
258           value: wrappedMethod,
259           configurable: true,
260           writable: true,
261         });
262         return wrappedMethod;
263       },
264     };
265   };
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.
270  * @return {bool}
271  */
272 function isObject(thing) {
273   return typeof thing === "object" && thing !== null && !Array.isArray(thing);
276  * Parses a data url.
277  * @param  {String} dataURL The data url.
278  * @return {Object}
279  */
280 function parseDataURL(dataURL) {
281   const regex = /^data:(.*);base64,(.*)/;
282   const match = dataURL.match(regex);
283   if (!match) {
284     throw new Error(`Invalid data-url: ${String(dataURL).substring(0, 32)}...`);
285   }
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 };
292   }, {});
293   return { ...params, type, base64 };
296  * Extracts file information from a data url.
297  * @param  {String} dataURL The data url.
298  * @return {Object}
299  */
300 function extractFileInfo(dataURL) {
301   const { name, type, base64 } = parseDataURL(dataURL);
302   const binary = atob(base64);
303   const array = [];
304   for (let i = 0; i < binary.length; i++) {
305     array.push(binary.charCodeAt(i));
306   }
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
312  * body.
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.
317  * @return {FormData}
318  */
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]));
327     }
328   }
329   return formData;
332  * Clones an object with all its undefined keys removed.
333  * @private
334  */
335 function cleanUndefinedProperties(obj) {
336   const result = {};
337   for (const key in obj) {
338     if (typeof obj[key] !== "undefined") {
339       result[key] = obj[key];
340     }
341   }
342   return result;
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
349  *   request to.
350  * @param  {Object}  [options.query={}]  Additional query arguments.
351  */
352 function addEndpointOptions(path, options = {}) {
353   const query = { ...options.query };
354   if (options.fields) {
355     query._fields = options.fields;
356   }
357   const queryString = qsify(query);
358   if (queryString) {
359     return path + "?" + queryString;
360   }
361   return path;
364  * Replace authorization header with an obscured version
365  */
366 function obscureAuthorizationHeader(headers) {
367   const h = new Headers(headers);
368   if (h.has("authorization")) {
369     h.set("authorization", "**** (suppressed)");
370   }
371   const obscuredHeaders = {};
372   for (const [header, value] of h.entries()) {
373     obscuredHeaders[header] = value;
374   }
375   return obscuredHeaders;
379  * Kinto server error code descriptors.
380  */
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) {
404     super(
405       `Timeout while trying to access ${url} with ${JSON.stringify(options)}`
406     );
407     if (Error.captureStackTrace) {
408       Error.captureStackTrace(this, NetworkTimeoutError);
409     }
410     this.url = url;
411     this.options = options;
412   }
414 class UnparseableResponseError extends Error {
415   constructor(response, body, error) {
416     const { status } = response;
417     super(
418       `Response from server unparseable (HTTP ${
419         status || 0
420       }; ${error}): ${body}`
421     );
422     if (Error.captureStackTrace) {
423       Error.captureStackTrace(this, UnparseableResponseError);
424     }
425     this.status = status;
426     this.response = response;
427     this.stack = error.stack;
428     this.error = error;
429   }
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).
441  */
442 class ServerResponse extends Error {
443   constructor(response, json) {
444     const { status } = response;
445     let { statusText } = response;
446     let errnoMsg;
447     if (json) {
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;
455       }
456       // If we had both ERROR_CODES and json.message, and they differ,
457       // combine them.
458       if (errnoMsg && json.message && json.message !== errnoMsg) {
459         errnoMsg += ` (${json.message})`;
460       }
461     }
462     let message = `HTTP ${status} ${statusText}`;
463     if (errnoMsg) {
464       message += `: ${errnoMsg}`;
465     }
466     super(message.trim());
467     if (Error.captureStackTrace) {
468       Error.captureStackTrace(this, ServerResponse);
469     }
470     this.response = response;
471     this.data = json;
472   }
475 var errors = /*#__PURE__*/ Object.freeze({
476   __proto__: null,
477   NetworkTimeoutError,
478   ServerResponse,
479   UnparseableResponseError,
480   default: ERROR_CODES,
484  * Enhanced HTTP client for the Kinto protocol.
485  * @private
486  */
487 class HTTP {
488   /**
489    * Default HTTP request headers applied to each outgoing request.
490    *
491    * @type {Object}
492    */
493   static get DEFAULT_REQUEST_HEADERS() {
494     return {
495       Accept: "application/json",
496       "Content-Type": "application/json",
497     };
498   }
499   /**
500    * Default options.
501    *
502    * @type {Object}
503    */
504   static get defaultOptions() {
505     return { timeout: null, requestMode: "cors" };
506   }
507   /**
508    * Constructor.
509    *
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"`).
514    */
515   constructor(events, options = {}) {
516     // public properties
517     /**
518      * The event emitter instance.
519      * @type {EventEmitter}
520      */
521     this.events = events;
522     /**
523      * The request mode.
524      * @see  https://fetch.spec.whatwg.org/#requestmode
525      * @type {String}
526      */
527     this.requestMode = options.requestMode || HTTP.defaultOptions.requestMode;
528     /**
529      * The request timeout.
530      * @type {Number}
531      */
532     this.timeout = options.timeout || HTTP.defaultOptions.timeout;
533     /**
534      * The fetch() function.
535      * @type {Function}
536      */
537     this.fetchFunc = options.fetchFunc || globalThis.fetch.bind(globalThis);
538   }
539   /**
540    * @private
541    */
542   timedFetch(url, options) {
543     let hasTimedout = false;
544     return new Promise((resolve, reject) => {
545       // Detect if a request has timed out.
546       let _timeoutId;
547       if (this.timeout) {
548         _timeoutId = setTimeout(() => {
549           hasTimedout = true;
550           if (options && options.headers) {
551             options = {
552               ...options,
553               headers: obscureAuthorizationHeader(options.headers),
554             };
555           }
556           reject(new NetworkTimeoutError(url, options));
557         }, this.timeout);
558       }
559       function proceedWithHandler(fn) {
560         return arg => {
561           if (!hasTimedout) {
562             if (_timeoutId) {
563               clearTimeout(_timeoutId);
564             }
565             fn(arg);
566           }
567         };
568       }
569       this.fetchFunc(url, options)
570         .then(proceedWithHandler(resolve))
571         .catch(proceedWithHandler(reject));
572     });
573   }
574   /**
575    * @private
576    */
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.
581     let json;
582     if (text.length !== 0) {
583       try {
584         json = JSON.parse(text);
585       } catch (err) {
586         throw new UnparseableResponseError(response, text, err);
587       }
588     }
589     if (status >= 400) {
590       throw new ServerResponse(response, json);
591     }
592     return { status, json: json, headers };
593   }
594   /**
595    * @private
596    */
597   async retry(url, retryAfter, request, options) {
598     await delay(retryAfter);
599     return this.request(url, request, {
600       ...options,
601       retry: options.retry - 1,
602     });
603   }
604   /**
605    * Performs an HTTP request to the Kinto server.
606    *
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.
611    *
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
617    *     request
618    * @param  {Number} [options.retry]   Number of retries (default: 0)
619    * @return {Promise}
620    */
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"];
631       }
632     }
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);
643     }
644     return this.processResponse(response);
645   }
646   _checkForDeprecationHeader(headers) {
647     const alertHeader = headers.get("Alert");
648     if (!alertHeader) {
649       return;
650     }
651     let alert;
652     try {
653       alert = JSON.parse(alertHeader);
654     } catch (err) {
655       console.warn("Unable to parse Alert header message", alertHeader);
656       return;
657     }
658     console.warn(alert.message, alert.url);
659     if (this.events) {
660       this.events.emit("deprecated", alert);
661     }
662   }
663   _checkForBackoffHeader(headers) {
664     let backoffMs;
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;
669     } else {
670       backoffMs = 0;
671     }
672     if (this.events) {
673       this.events.emit("backoff", backoffMs);
674     }
675   }
676   _checkForRetryAfterHeader(headers) {
677     const retryAfter = headers.get("Retry-After");
678     if (!retryAfter) {
679       return null;
680     }
681     const delay = parseInt(retryAfter, 10) * 1000;
682     const tryAgainAfter = new Date().getTime() + delay;
683     if (this.events) {
684       this.events.emit("retry-after", tryAgainAfter);
685     }
686     return delay;
687   }
691  * Endpoints templates.
692  * @type {Object}
693  */
694 const ENDPOINTS = {
695   root: () => "/",
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 = {
711   safe: false,
712   // check if we should set default content type here
713   headers: {},
714   patch: false,
717  * @private
718  */
719 function safeHeader(safe, last_modified) {
720   if (!safe) {
721     return {};
722   }
723   if (last_modified) {
724     return { "If-Match": `"${last_modified}"` };
725   }
726   return { "If-None-Match": "*" };
729  * @private
730  */
731 function createRequest(path, { data, permissions }, options = {}) {
732   const { headers, safe } = {
733     ...requestDefaults,
734     ...options,
735   };
736   const method = options.method || (data && data.id) ? "PUT" : "POST";
737   return {
738     method,
739     path,
740     headers: { ...headers, ...safeHeader(safe) },
741     body: { data, permissions },
742   };
745  * @private
746  */
747 function updateRequest(path, { data, permissions }, options = {}) {
748   const { headers, safe, patch } = { ...requestDefaults, ...options };
749   const { last_modified } = { ...data, ...options };
750   const hasNoData =
751     data &&
752     Object.keys(data).filter(k => k !== "id" && k !== "last_modified")
753       .length === 0;
754   if (hasNoData) {
755     data = undefined;
756   }
757   return {
758     method: patch ? "PATCH" : "PUT",
759     path,
760     headers: { ...headers, ...safeHeader(safe, last_modified) },
761     body: { data, permissions },
762   };
765  * @private
766  */
767 function jsonPatchPermissionsRequest(path, permissions, opType, options = {}) {
768   const { headers, safe, last_modified } = { ...requestDefaults, ...options };
769   const ops = [];
770   for (const [type, principals] of Object.entries(permissions)) {
771     if (principals) {
772       for (const principal of principals) {
773         ops.push({
774           op: opType,
775           path: `/permissions/${type}/${principal}`,
776         });
777       }
778     }
779   }
780   return {
781     method: "PATCH",
782     path,
783     headers: {
784       ...headers,
785       ...safeHeader(safe, last_modified),
786       "Content-Type": "application/json-patch+json",
787     },
788     body: ops,
789   };
792  * @private
793  */
794 function deleteRequest(path, options = {}) {
795   const { headers, safe, last_modified } = {
796     ...requestDefaults,
797     ...options,
798   };
799   if (safe && !last_modified) {
800     throw new Error("Safe concurrency check requires a last_modified value.");
801   }
802   return {
803     method: "DELETE",
804     path,
805     headers: { ...headers, ...safeHeader(safe, last_modified) },
806   };
809  * @private
810  */
811 function addAttachmentRequest(
812   path,
813   dataURI,
814   { data, permissions } = {},
815   options = {}
816 ) {
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") : ""
823   }`;
824   return {
825     method: "POST",
826     path: customPath,
827     headers: { ...headers, ...safeHeader(safe, last_modified) },
828     body: formData,
829   };
833  * Exports batch responses as a result object.
835  * @private
836  * @param  {Array} responses The batch subrequest responses.
837  * @param  {Array} requests  The initial issued requests.
838  * @return {Object}
839  */
840 function aggregate(responses = [], requests = []) {
841   if (responses.length !== requests.length) {
842     throw new Error("Responses length should match requests one.");
843   }
844   const results = {
845     errors: [],
846     published: [],
847     conflicts: [],
848     skipped: [],
849   };
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;
860       acc.skipped.push({
861         id,
862         path: request.path,
863         error: response.body,
864       });
865     } else if (status === 412) {
866       acc.conflicts.push({
867         // XXX: specifying the type is probably superfluous
868         type: "outgoing",
869         local: request.body,
870         remote:
871           (response.body.details && response.body.details.existing) || null,
872       });
873     } else {
874       acc.errors.push({
875         path: request.path,
876         sent: request,
877         error: response.body,
878       });
879     }
880     return acc;
881   }, results);
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()).
887 let getRandomValues;
888 const rnds8 = new Uint8Array(16);
889 function rng() {
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.
893     getRandomValues =
894       typeof crypto !== "undefined" &&
895       crypto.getRandomValues &&
896       crypto.getRandomValues.bind(crypto);
898     if (!getRandomValues) {
899       throw new Error(
900         "crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported"
901       );
902     }
903   }
905   return getRandomValues(rnds8);
909  * Convert array of 16 byte values to UUID string format of the form:
910  * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
911  */
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
922   return (
923     byteToHex[arr[offset + 0]] +
924     byteToHex[arr[offset + 1]] +
925     byteToHex[arr[offset + 2]] +
926     byteToHex[arr[offset + 3]] +
927     "-" +
928     byteToHex[arr[offset + 4]] +
929     byteToHex[arr[offset + 5]] +
930     "-" +
931     byteToHex[arr[offset + 6]] +
932     byteToHex[arr[offset + 7]] +
933     "-" +
934     byteToHex[arr[offset + 8]] +
935     byteToHex[arr[offset + 9]] +
936     "-" +
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]]
943   ).toLowerCase();
946 const randomUUID =
947   typeof crypto !== "undefined" &&
948   crypto.randomUUID &&
949   crypto.randomUUID.bind(crypto);
950 var native = {
951   randomUUID,
954 function v4(options, buf, offset) {
955   if (native.randomUUID && !buf && !options) {
956     return native.randomUUID();
957   }
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
965   if (buf) {
966     offset = offset || 0;
968     for (let i = 0; i < 16; ++i) {
969       buf[offset + i] = rnds[i];
970     }
972     return buf;
973   }
975   return unsafeStringify(rnds);
979  * Abstract representation of a selected collection.
981  */
982 class Collection {
983   /**
984    * Constructor.
985    *
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.
995    */
996   constructor(client, bucket, name, options = {}) {
997     /**
998      * @ignore
999      */
1000     this.client = client;
1001     /**
1002      * @ignore
1003      */
1004     this.bucket = bucket;
1005     /**
1006      * The collection name.
1007      * @type {String}
1008      */
1009     this.name = name;
1010     this._endpoints = client.endpoints;
1011     /**
1012      * @ignore
1013      */
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?
1018     this._headers = {
1019       ...this.bucket.headers,
1020       ...options.headers,
1021     };
1022   }
1023   get execute() {
1024     return this.client.execute.bind(this.client);
1025   }
1026   /**
1027    * Get the value of "headers" for a given request, merging the
1028    * per-request headers with our own "default" headers.
1029    *
1030    * @private
1031    */
1032   _getHeaders(options) {
1033     return {
1034       ...this._headers,
1035       ...options.headers,
1036     };
1037   }
1038   /**
1039    * Get the value of "safe" for a given request, using the
1040    * per-request option if present or falling back to our default
1041    * otherwise.
1042    *
1043    * @private
1044    * @param {Object} options The options for a request.
1045    * @returns {Boolean}
1046    */
1047   _getSafe(options) {
1048     return { safe: this._safe, ...options }.safe;
1049   }
1050   /**
1051    * As _getSafe, but for "retry".
1052    *
1053    * @private
1054    */
1055   _getRetry(options) {
1056     return { retry: this._retry, ...options }.retry;
1057   }
1058   /**
1059    * Retrieves the total number of records in this collection.
1060    *
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>}
1066    */
1067   async getTotalRecords(options = {}) {
1068     const path = this._endpoints.record(this.bucket.name, this.name);
1069     const request = {
1070       headers: this._getHeaders(options),
1071       path,
1072       method: "HEAD",
1073     };
1074     const { headers } = await this.client.execute(request, {
1075       raw: true,
1076       retry: this._getRetry(options),
1077     });
1078     return parseInt(headers.get("Total-Records"), 10);
1079   }
1080   /**
1081    * Retrieves the ETag of the records list, for use with the `since` filtering option.
1082    *
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>}
1088    */
1089   async getRecordsTimestamp(options = {}) {
1090     const path = this._endpoints.record(this.bucket.name, this.name);
1091     const request = {
1092       headers: this._getHeaders(options),
1093       path,
1094       method: "HEAD",
1095     };
1096     const { headers } = await this.client.execute(request, {
1097       raw: true,
1098       retry: this._getRetry(options),
1099     });
1100     return headers.get("ETag");
1101   }
1102   /**
1103    * Retrieves collection data.
1104    *
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
1111    *     just some fields.
1112    * @param  {Number} [options.retry=0] Number of retries to make
1113    *     when faced with transient errors.
1114    * @return {Promise<Object, Error>}
1115    */
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,
1123     });
1124     return data;
1125   }
1126   /**
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>}
1137    */
1138   async setData(data, options = {}) {
1139     if (!isObject(data)) {
1140       throw new Error("A collection object is required.");
1141     }
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(
1146       path,
1147       { data, permissions },
1148       {
1149         last_modified,
1150         patch,
1151         headers: this._getHeaders(options),
1152         safe: this._getSafe(options),
1153       }
1154     );
1155     return this.client.execute(request, {
1156       retry: this._getRetry(options),
1157     });
1158   }
1159   /**
1160    * Retrieves the list of permissions for this collection.
1161    *
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>}
1167    */
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),
1173     });
1174     return permissions;
1175   }
1176   /**
1177    * Replaces all existing collection permissions with the ones provided.
1178    *
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>}
1187    */
1188   async setPermissions(permissions, options = {}) {
1189     if (!isObject(permissions)) {
1190       throw new Error("A permissions object is required.");
1191     }
1192     const path = this._endpoints.collection(this.bucket.name, this.name);
1193     const data = { last_modified: options.last_modified };
1194     const request = updateRequest(
1195       path,
1196       { data, permissions },
1197       {
1198         headers: this._getHeaders(options),
1199         safe: this._getSafe(options),
1200       }
1201     );
1202     return this.client.execute(request, {
1203       retry: this._getRetry(options),
1204     });
1205   }
1206   /**
1207    * Append principals to the collection permissions.
1208    *
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>}
1217    */
1218   async addPermissions(permissions, options = {}) {
1219     if (!isObject(permissions)) {
1220       throw new Error("A permissions object is required.");
1221     }
1222     const path = this._endpoints.collection(this.bucket.name, this.name);
1223     const { last_modified } = options;
1224     const request = jsonPatchPermissionsRequest(path, permissions, "add", {
1225       last_modified,
1226       headers: this._getHeaders(options),
1227       safe: this._getSafe(options),
1228     });
1229     return this.client.execute(request, {
1230       retry: this._getRetry(options),
1231     });
1232   }
1233   /**
1234    * Remove principals from the collection permissions.
1235    *
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>}
1244    */
1245   async removePermissions(permissions, options = {}) {
1246     if (!isObject(permissions)) {
1247       throw new Error("A permissions object is required.");
1248     }
1249     const path = this._endpoints.collection(this.bucket.name, this.name);
1250     const { last_modified } = options;
1251     const request = jsonPatchPermissionsRequest(path, permissions, "remove", {
1252       last_modified,
1253       headers: this._getHeaders(options),
1254       safe: this._getSafe(options),
1255     });
1256     return this.client.execute(request, {
1257       retry: this._getRetry(options),
1258     });
1259   }
1260   /**
1261    * Creates a record in current collection.
1262    *
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>}
1271    */
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(
1276       path,
1277       { data: record, permissions },
1278       {
1279         headers: this._getHeaders(options),
1280         safe: this._getSafe(options),
1281       }
1282     );
1283     return this.client.execute(request, {
1284       retry: this._getRetry(options),
1285     });
1286   }
1287   /**
1288    * Adds an attachment to a record, creating the record when it doesn't exist.
1289    *
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>}
1302    */
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(
1309       path,
1310       dataURI,
1311       { data: record, permissions },
1312       {
1313         last_modified,
1314         filename: options.filename,
1315         gzipped: options.gzipped,
1316         headers: this._getHeaders(options),
1317         safe: this._getSafe(options),
1318       }
1319     );
1320     await this.client.execute(addAttachmentRequest$1, {
1321       stringify: false,
1322       retry: this._getRetry(options),
1323     });
1324     return this.getRecord(id);
1325   }
1326   /**
1327    * Removes an attachment from a given record.
1328    *
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.
1336    */
1337   async removeAttachment(recordId, options = {}) {
1338     const { last_modified } = options;
1339     const path = this._endpoints.attachment(
1340       this.bucket.name,
1341       this.name,
1342       recordId
1343     );
1344     const request = deleteRequest(path, {
1345       last_modified,
1346       headers: this._getHeaders(options),
1347       safe: this._getSafe(options),
1348     });
1349     return this.client.execute(request, {
1350       retry: this._getRetry(options),
1351     });
1352   }
1353   /**
1354    * Updates a record in current collection.
1355    *
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>}
1365    */
1366   async updateRecord(record, options = {}) {
1367     if (!isObject(record)) {
1368       throw new Error("A record object is required.");
1369     }
1370     if (!record.id) {
1371       throw new Error("A record id is required.");
1372     }
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(
1377       path,
1378       { data: record, permissions },
1379       {
1380         headers: this._getHeaders(options),
1381         safe: this._getSafe(options),
1382         last_modified,
1383         patch: !!options.patch,
1384       }
1385     );
1386     return this.client.execute(request, {
1387       retry: this._getRetry(options),
1388     });
1389   }
1390   /**
1391    * Deletes a record from the current collection.
1392    *
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>}
1401    */
1402   async deleteRecord(record, options = {}) {
1403     const recordObj = toDataBody(record);
1404     if (!recordObj.id) {
1405       throw new Error("A record id is required.");
1406     }
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, {
1411       last_modified,
1412       headers: this._getHeaders(options),
1413       safe: this._getSafe(options),
1414     });
1415     return this.client.execute(request, {
1416       retry: this._getRetry(options),
1417     });
1418   }
1419   /**
1420    * Deletes records from the current collection.
1421    *
1422    * Sorting is done by passing a `sort` string option:
1423    *
1424    * - The field to order the results by, prefixed with `-` for descending.
1425    * Default: `-last_modified`.
1426    *
1427    * @see http://kinto.readthedocs.io/en/stable/api/1.x/sorting.html
1428    *
1429    * Filtering is done by passing a `filters` option object:
1430    *
1431    * - `{fieldname: "value"}`
1432    * - `{min_fieldname: 4000}`
1433    * - `{in_fieldname: "1,2,3"}`
1434    * - `{not_fieldname: 0}`
1435    * - `{exclude_fieldname: "0,1"}`
1436    *
1437    * @see http://kinto.readthedocs.io/en/stable/api/1.x/filtering.html
1438    *
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>}
1451    */
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),
1457     });
1458   }
1459   /**
1460    * Retrieves a record from the current collection.
1461    *
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
1469    *     just some fields.
1470    * @param  {Number} [options.retry=0] Number of retries to make
1471    *     when faced with transient errors.
1472    * @return {Promise<Object, Error>}
1473    */
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,
1481     });
1482   }
1483   /**
1484    * Lists records from the current collection.
1485    *
1486    * Sorting is done by passing a `sort` string option:
1487    *
1488    * - The field to order the results by, prefixed with `-` for descending.
1489    * Default: `-last_modified`.
1490    *
1491    * @see http://kinto.readthedocs.io/en/stable/api/1.x/sorting.html
1492    *
1493    * Filtering is done by passing a `filters` option object:
1494    *
1495    * - `{fieldname: "value"}`
1496    * - `{min_fieldname: 4000}`
1497    * - `{in_fieldname: "1,2,3"}`
1498    * - `{not_fieldname: 0}`
1499    * - `{exclude_fieldname: "0,1"}`
1500    *
1501    * @see http://kinto.readthedocs.io/en/stable/api/1.x/filtering.html
1502    *
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.
1505    *
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>}
1518    */
1519   async listRecords(options = {}) {
1520     const path = this._endpoints.record(this.bucket.name, this.name);
1521     if (options.at) {
1522       return this.getSnapshot(options.at);
1523     }
1524     return this.client.paginatedList(path, options, {
1525       headers: this._getHeaders(options),
1526       retry: this._getRetry(options),
1527     });
1528   }
1529   /**
1530    * @private
1531    */
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.
1535     const {
1536       data: [oldestHistoryEntry],
1537     } = await this.bucket.listHistory({
1538       limit: 1,
1539       filters: {
1540         action: "create",
1541         resource_name: "collection",
1542         collection_id: this.name,
1543       },
1544     });
1545     return !!oldestHistoryEntry;
1546   }
1547   /**
1548    * @private
1549    */
1550   async getSnapshot(at) {
1551     if (!at || !Number.isInteger(at) || at <= 0) {
1552       throw new Error("Invalid argument, expected a positive integer.");
1553     }
1554     // Retrieve history and check it covers the required time range.
1555     // Ensure we have enough history data to retrieve the complete list of
1556     // changes.
1557     if (!(await this.isHistoryComplete())) {
1558       throw new Error(
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."
1562       );
1563     }
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({
1572       pages: Infinity,
1573       sort: "last_modified",
1574       filters: {
1575         resource_name: "record",
1576         collection_id: this.name,
1577       },
1578     });
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);
1587       }
1588       latestEver.set(entry.record_id, entry);
1589     }
1590     // Current records ids in the collection.
1591     const { data: current } = await this.listRecords({
1592       pages: Infinity,
1593       fields: ["id"], // we don't need attributes.
1594     });
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);
1604       }
1605     }
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);
1612       }
1613     }
1614     return {
1615       last_modified: String(at),
1616       data: Array.from(reconstructed).sort(
1617         (a, b) => b.last_modified - a.last_modified
1618       ),
1619       next: () => {
1620         throw new Error("Snapshots don't support pagination");
1621       },
1622       hasNextPage: false,
1623       totalRecords: reconstructed.length,
1624     };
1625   }
1626   /**
1627    * Performs batch operations at the current collection level.
1628    *
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>}
1636    */
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,
1645     });
1646   }
1648 __decorate(
1649   [capable(["attachments"])],
1650   Collection.prototype,
1651   "addAttachment",
1652   null
1654 __decorate(
1655   [capable(["attachments"])],
1656   Collection.prototype,
1657   "removeAttachment",
1658   null
1660 __decorate([capable(["history"])], Collection.prototype, "getSnapshot", null);
1663  * Abstract representation of a selected bucket.
1665  */
1666 class Bucket {
1667   /**
1668    * Constructor.
1669    *
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.
1676    */
1677   constructor(client, name, options = {}) {
1678     /**
1679      * @ignore
1680      */
1681     this.client = client;
1682     /**
1683      * The bucket name.
1684      * @type {String}
1685      */
1686     this.name = name;
1687     this._endpoints = client.endpoints;
1688     /**
1689      * @ignore
1690      */
1691     this._headers = options.headers || {};
1692     this._retry = options.retry || 0;
1693     this._safe = !!options.safe;
1694   }
1695   get execute() {
1696     return this.client.execute.bind(this.client);
1697   }
1698   get headers() {
1699     return this._headers;
1700   }
1701   /**
1702    * Get the value of "headers" for a given request, merging the
1703    * per-request headers with our own "default" headers.
1704    *
1705    * @private
1706    */
1707   _getHeaders(options) {
1708     return {
1709       ...this._headers,
1710       ...options.headers,
1711     };
1712   }
1713   /**
1714    * Get the value of "safe" for a given request, using the
1715    * per-request option if present or falling back to our default
1716    * otherwise.
1717    *
1718    * @private
1719    * @param {Object} options The options for a request.
1720    * @returns {Boolean}
1721    */
1722   _getSafe(options) {
1723     return { safe: this._safe, ...options }.safe;
1724   }
1725   /**
1726    * As _getSafe, but for "retry".
1727    *
1728    * @private
1729    */
1730   _getRetry(options) {
1731     return { retry: this._retry, ...options }.retry;
1732   }
1733   /**
1734    * Selects a collection.
1735    *
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}
1741    */
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),
1747     });
1748   }
1749   /**
1750    * Retrieves the ETag of the collection list, for use with the `since` filtering option.
1751    *
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>}
1757    */
1758   async getCollectionsTimestamp(options = {}) {
1759     const path = this._endpoints.collection(this.name);
1760     const request = {
1761       headers: this._getHeaders(options),
1762       path,
1763       method: "HEAD",
1764     };
1765     const { headers } = await this.client.execute(request, {
1766       raw: true,
1767       retry: this._getRetry(options),
1768     });
1769     return headers.get("ETag");
1770   }
1771   /**
1772    * Retrieves the ETag of the group list, for use with the `since` filtering option.
1773    *
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>}
1779    */
1780   async getGroupsTimestamp(options = {}) {
1781     const path = this._endpoints.group(this.name);
1782     const request = {
1783       headers: this._getHeaders(options),
1784       path,
1785       method: "HEAD",
1786     };
1787     const { headers } = await this.client.execute(request, {
1788       raw: true,
1789       retry: this._getRetry(options),
1790     });
1791     return headers.get("ETag");
1792   }
1793   /**
1794    * Retrieves bucket data.
1795    *
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
1802    *     just some fields.
1803    * @param  {Number} [options.retry=0] Number of retries to make
1804    *     when faced with transient errors.
1805    * @return {Promise<Object, Error>}
1806    */
1807   async getData(options = {}) {
1808     const path = this._endpoints.bucket(this.name);
1809     const request = {
1810       headers: this._getHeaders(options),
1811       path,
1812     };
1813     const { data } = await this.client.execute(request, {
1814       retry: this._getRetry(options),
1815       query: options.query,
1816       fields: options.fields,
1817     });
1818     return data;
1819   }
1820   /**
1821    * Set bucket data.
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>}
1831    */
1832   async setData(data, options = {}) {
1833     if (!isObject(data)) {
1834       throw new Error("A bucket object is required.");
1835     }
1836     const bucket = {
1837       ...data,
1838       id: this.name,
1839     };
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") {
1844       delete bucket.id;
1845     }
1846     const path = this._endpoints.bucket(bucketId);
1847     const { patch, permissions } = options;
1848     const { last_modified } = { ...data, ...options };
1849     const request = updateRequest(
1850       path,
1851       { data: bucket, permissions },
1852       {
1853         last_modified,
1854         patch,
1855         headers: this._getHeaders(options),
1856         safe: this._getSafe(options),
1857       }
1858     );
1859     return this.client.execute(request, {
1860       retry: this._getRetry(options),
1861     });
1862   }
1863   /**
1864    * Retrieves the list of history entries in the current bucket.
1865    *
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>}
1871    */
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),
1877     });
1878   }
1879   /**
1880    * Retrieves the list of collections in the current bucket.
1881    *
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
1888    *     just some fields.
1889    * @return {Promise<Array<Object>, Error>}
1890    */
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),
1896     });
1897   }
1898   /**
1899    * Creates a new collection in current bucket.
1900    *
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>}
1910    */
1911   async createCollection(id, options = {}) {
1912     const { permissions, data = {} } = options;
1913     data.id = id;
1914     const path = this._endpoints.collection(this.name, id);
1915     const request = createRequest(
1916       path,
1917       { data, permissions },
1918       {
1919         headers: this._getHeaders(options),
1920         safe: this._getSafe(options),
1921       }
1922     );
1923     return this.client.execute(request, {
1924       retry: this._getRetry(options),
1925     });
1926   }
1927   /**
1928    * Deletes a collection from the current bucket.
1929    *
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>}
1938    */
1939   async deleteCollection(collection, options = {}) {
1940     const collectionObj = toDataBody(collection);
1941     if (!collectionObj.id) {
1942       throw new Error("A collection id is required.");
1943     }
1944     const { id } = collectionObj;
1945     const { last_modified } = { ...collectionObj, ...options };
1946     const path = this._endpoints.collection(this.name, id);
1947     const request = deleteRequest(path, {
1948       last_modified,
1949       headers: this._getHeaders(options),
1950       safe: this._getSafe(options),
1951     });
1952     return this.client.execute(request, {
1953       retry: this._getRetry(options),
1954     });
1955   }
1956   /**
1957    * Deletes collections from the current bucket.
1958    *
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
1965    *     just some fields.
1966    * @return {Promise<Array<Object>, Error>}
1967    */
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),
1973     });
1974   }
1975   /**
1976    * Retrieves the list of groups in the current bucket.
1977    *
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
1984    *     just some fields.
1985    * @return {Promise<Array<Object>, Error>}
1986    */
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),
1992     });
1993   }
1994   /**
1995    * Fetches a group in current bucket.
1996    *
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
2006    *     just some fields.
2007    * @return {Promise<Object, Error>}
2008    */
2009   async getGroup(id, options = {}) {
2010     const path = this._endpoints.group(this.name, id);
2011     const request = {
2012       headers: this._getHeaders(options),
2013       path,
2014     };
2015     return this.client.execute(request, {
2016       retry: this._getRetry(options),
2017       query: options.query,
2018       fields: options.fields,
2019     });
2020   }
2021   /**
2022    * Creates a new group in current bucket.
2023    *
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>}
2034    */
2035   async createGroup(id, members = [], options = {}) {
2036     const data = {
2037       ...options.data,
2038       id,
2039       members,
2040     };
2041     const path = this._endpoints.group(this.name, id);
2042     const { permissions } = options;
2043     const request = createRequest(
2044       path,
2045       { data, permissions },
2046       {
2047         headers: this._getHeaders(options),
2048         safe: this._getSafe(options),
2049       }
2050     );
2051     return this.client.execute(request, {
2052       retry: this._getRetry(options),
2053     });
2054   }
2055   /**
2056    * Updates an existing group in current bucket.
2057    *
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>}
2068    */
2069   async updateGroup(group, options = {}) {
2070     if (!isObject(group)) {
2071       throw new Error("A group object is required.");
2072     }
2073     if (!group.id) {
2074       throw new Error("A group id is required.");
2075     }
2076     const data = {
2077       ...options.data,
2078       ...group,
2079     };
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(
2084       path,
2085       { data, permissions },
2086       {
2087         last_modified,
2088         patch,
2089         headers: this._getHeaders(options),
2090         safe: this._getSafe(options),
2091       }
2092     );
2093     return this.client.execute(request, {
2094       retry: this._getRetry(options),
2095     });
2096   }
2097   /**
2098    * Deletes a group from the current bucket.
2099    *
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>}
2108    */
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, {
2115       last_modified,
2116       headers: this._getHeaders(options),
2117       safe: this._getSafe(options),
2118     });
2119     return this.client.execute(request, {
2120       retry: this._getRetry(options),
2121     });
2122   }
2123   /**
2124    * Deletes groups from the current bucket.
2125    *
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
2132    *     just some fields.
2133    * @return {Promise<Array<Object>, Error>}
2134    */
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),
2140     });
2141   }
2142   /**
2143    * Retrieves the list of permissions for this bucket.
2144    *
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>}
2150    */
2151   async getPermissions(options = {}) {
2152     const request = {
2153       headers: this._getHeaders(options),
2154       path: this._endpoints.bucket(this.name),
2155     };
2156     const { permissions } = await this.client.execute(request, {
2157       retry: this._getRetry(options),
2158     });
2159     return permissions;
2160   }
2161   /**
2162    * Replaces all existing bucket permissions with the ones provided.
2163    *
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>}
2172    */
2173   async setPermissions(permissions, options = {}) {
2174     if (!isObject(permissions)) {
2175       throw new Error("A permissions object is required.");
2176     }
2177     const path = this._endpoints.bucket(this.name);
2178     const { last_modified } = options;
2179     const data = { last_modified };
2180     const request = updateRequest(
2181       path,
2182       { data, permissions },
2183       {
2184         headers: this._getHeaders(options),
2185         safe: this._getSafe(options),
2186       }
2187     );
2188     return this.client.execute(request, {
2189       retry: this._getRetry(options),
2190     });
2191   }
2192   /**
2193    * Append principals to the bucket permissions.
2194    *
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>}
2203    */
2204   async addPermissions(permissions, options = {}) {
2205     if (!isObject(permissions)) {
2206       throw new Error("A permissions object is required.");
2207     }
2208     const path = this._endpoints.bucket(this.name);
2209     const { last_modified } = options;
2210     const request = jsonPatchPermissionsRequest(path, permissions, "add", {
2211       last_modified,
2212       headers: this._getHeaders(options),
2213       safe: this._getSafe(options),
2214     });
2215     return this.client.execute(request, {
2216       retry: this._getRetry(options),
2217     });
2218   }
2219   /**
2220    * Remove principals from the bucket permissions.
2221    *
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>}
2230    */
2231   async removePermissions(permissions, options = {}) {
2232     if (!isObject(permissions)) {
2233       throw new Error("A permissions object is required.");
2234     }
2235     const path = this._endpoints.bucket(this.name);
2236     const { last_modified } = options;
2237     const request = jsonPatchPermissionsRequest(path, permissions, "remove", {
2238       last_modified,
2239       headers: this._getHeaders(options),
2240       safe: this._getSafe(options),
2241     });
2242     return this.client.execute(request, {
2243       retry: this._getRetry(options),
2244     });
2245   }
2246   /**
2247    * Performs batch operations at the current bucket level.
2248    *
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>}
2256    */
2257   async batch(fn, options = {}) {
2258     return this.client.batch(fn, {
2259       bucket: this.name,
2260       headers: this._getHeaders(options),
2261       retry: this._getRetry(options),
2262       safe: this._getSafe(options),
2263       aggregate: !!options.aggregate,
2264     });
2265   }
2267 __decorate([capable(["history"])], Bucket.prototype, "listHistory", null);
2270  * Currently supported protocol version.
2271  * @type {String}
2272  */
2273 const SUPPORTED_PROTOCOL_VERSION = "v1";
2275  * High level HTTP client for the Kinto API.
2277  * @example
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));
2284  */
2285 class KintoClientBase {
2286   /**
2287    * Constructor.
2288    *
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.
2299    */
2300   constructor(remote, options) {
2301     if (typeof remote !== "string" || !remote.length) {
2302       throw new Error("Invalid remote URL: " + remote);
2303     }
2304     if (remote[remote.length - 1] === "/") {
2305       remote = remote.slice(0, -1);
2306     }
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
2314     /**
2315      * The remote server base URL.
2316      * @type {String}
2317      */
2318     this.remote = remote;
2319     /**
2320      * Current server information.
2321      * @ignore
2322      * @type {Object|null}
2323      */
2324     this.serverInfo = null;
2325     /**
2326      * The event emitter instance. Should comply with the `EventEmitter`
2327      * interface.
2328      * @ignore
2329      * @type {Class}
2330      */
2331     this.events = options.events;
2332     this.endpoints = ENDPOINTS;
2333     const { fetchFunc, requestMode, timeout } = options;
2334     /**
2335      * The HTTP instance.
2336      * @ignore
2337      * @type {HTTP}
2338      */
2339     this.http = new HTTP(this.events, { fetchFunc, requestMode, timeout });
2340     this._registerHTTPEvents();
2341   }
2342   /**
2343    * The remote endpoint base URL. Setting the value will also extract and
2344    * validate the version.
2345    * @type {String}
2346    */
2347   get remote() {
2348     return this._remote;
2349   }
2350   /**
2351    * @ignore
2352    */
2353   set remote(url) {
2354     let version;
2355     try {
2356       version = url.match(/\/(v\d+)\/?$/)[1];
2357     } catch (err) {
2358       throw new Error("The remote URL must contain the version: " + url);
2359     }
2360     if (version !== SUPPORTED_PROTOCOL_VERSION) {
2361       throw new Error(`Unsupported protocol version: ${version}`);
2362     }
2363     this._remote = url;
2364     this._version = version;
2365   }
2366   /**
2367    * The current server protocol version, eg. `v1`.
2368    * @type {String}
2369    */
2370   get version() {
2371     return this._version;
2372   }
2373   /**
2374    * Backoff remaining time, in milliseconds. Defaults to zero if no backoff is
2375    * ongoing.
2376    *
2377    * @type {Number}
2378    */
2379   get backoff() {
2380     const currentTime = new Date().getTime();
2381     if (this._backoffReleaseTime && currentTime < this._backoffReleaseTime) {
2382       return this._backoffReleaseTime - currentTime;
2383     }
2384     return 0;
2385   }
2386   /**
2387    * Registers HTTP events.
2388    * @private
2389    */
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;
2395       });
2396     }
2397   }
2398   /**
2399    * Retrieve a bucket object to perform operations on it.
2400    *
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.
2406    * @return {Bucket}
2407    */
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),
2413     });
2414   }
2415   /**
2416    * Set client "headers" for every request, updating previous headers (if any).
2417    *
2418    * @param {Object} headers The headers to merge with existing ones.
2419    */
2420   setHeaders(headers) {
2421     this._headers = {
2422       ...this._headers,
2423       ...headers,
2424     };
2425     this.serverInfo = null;
2426   }
2427   /**
2428    * Get the value of "headers" for a given request, merging the
2429    * per-request headers with our own "default" headers.
2430    *
2431    * Note that unlike other options, headers aren't overridden, but
2432    * merged instead.
2433    *
2434    * @private
2435    * @param {Object} options The options for a request.
2436    * @returns {Object}
2437    */
2438   _getHeaders(options) {
2439     return {
2440       ...this._headers,
2441       ...options.headers,
2442     };
2443   }
2444   /**
2445    * Get the value of "safe" for a given request, using the
2446    * per-request option if present or falling back to our default
2447    * otherwise.
2448    *
2449    * @private
2450    * @param {Object} options The options for a request.
2451    * @returns {Boolean}
2452    */
2453   _getSafe(options) {
2454     return { safe: this._safe, ...options }.safe;
2455   }
2456   /**
2457    * As _getSafe, but for "retry".
2458    *
2459    * @private
2460    */
2461   _getRetry(options) {
2462     return { retry: this._retry, ...options }.retry;
2463   }
2464   /**
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.
2468    *
2469    * @private
2470    * @param  {Object}  [options={}] The request options.
2471    * @param  {Object}  [options.headers={}] Headers to use when making
2472    *     this request.
2473    * @param  {Number}  [options.retry=0]    Number of retries to make
2474    *     when faced with transient errors.
2475    * @return {Promise<Object, Error>}
2476    */
2477   async _getHello(options = {}) {
2478     const path = this.remote + ENDPOINTS.root();
2479     const { json } = await this.http.request(
2480       path,
2481       { headers: this._getHeaders(options) },
2482       { retry: this._getRetry(options) }
2483     );
2484     return json;
2485   }
2486   /**
2487    * Retrieves server information and persist them locally. This operation is
2488    * usually performed a single time during the instance lifecycle.
2489    *
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>}
2494    */
2495   async fetchServerInfo(options = {}) {
2496     if (this.serverInfo) {
2497       return this.serverInfo;
2498     }
2499     this.serverInfo = await this._getHello({ retry: this._getRetry(options) });
2500     return this.serverInfo;
2501   }
2502   /**
2503    * Retrieves Kinto server settings.
2504    *
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>}
2509    */
2510   async fetchServerSettings(options = {}) {
2511     const { settings } = await this.fetchServerInfo(options);
2512     return settings;
2513   }
2514   /**
2515    * Retrieve server capabilities information.
2516    *
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>}
2521    */
2522   async fetchServerCapabilities(options = {}) {
2523     const { capabilities } = await this.fetchServerInfo(options);
2524     return capabilities;
2525   }
2526   /**
2527    * Retrieve authenticated user information.
2528    *
2529    * @param  {Object}  [options={}] The request options.
2530    * @param  {Object}  [options.headers={}] Headers to use when making
2531    *     this request.
2532    * @param  {Number}  [options.retry=0]    Number of retries to make
2533    *     when faced with transient errors.
2534    * @return {Promise<Object, Error>}
2535    */
2536   async fetchUser(options = {}) {
2537     const { user } = await this._getHello(options);
2538     return user;
2539   }
2540   /**
2541    * Retrieve authenticated user information.
2542    *
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>}
2547    */
2548   async fetchHTTPApiVersion(options = {}) {
2549     const { http_api_version } = await this.fetchServerInfo(options);
2550     return http_api_version;
2551   }
2552   /**
2553    * Process batch requests, chunking them according to the batch_max_requests
2554    * server setting when needed.
2555    *
2556    * @param  {Array}  requests     The list of batch subrequests to perform.
2557    * @param  {Object} [options={}] The options object.
2558    * @return {Promise<Object, Error>}
2559    */
2560   async _batchRequests(requests, options = {}) {
2561     const headers = this._getHeaders(options);
2562     if (!requests.length) {
2563       return [];
2564     }
2565     const serverSettings = await this.fetchServerSettings({
2566       retry: this._getRetry(options),
2567     });
2568     const maxRequests = serverSettings.batch_max_requests;
2569     if (maxRequests && requests.length > maxRequests) {
2570       const chunks = partition(requests, maxRequests);
2571       const results = [];
2572       for (const chunk of chunks) {
2573         const result = await this._batchRequests(chunk, options);
2574         results.push(...result);
2575       }
2576       return results;
2577     }
2578     const { responses } = await this.execute(
2579       {
2580         // FIXME: is this really necessary, since it's also present in
2581         // the "defaults"?
2582         headers,
2583         path: ENDPOINTS.batch(),
2584         method: "POST",
2585         body: {
2586           defaults: { headers },
2587           requests,
2588         },
2589       },
2590       { retry: this._getRetry(options) }
2591     );
2592     return responses;
2593   }
2594   /**
2595    * Sends batch requests to the remote server.
2596    *
2597    * Note: Reserved for internal use only.
2598    *
2599    * @ignore
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>}
2609    */
2610   async batch(fn, options = {}) {
2611     const rootBatch = new KintoClientBase(this.remote, {
2612       events: this.events,
2613       batch: true,
2614       safe: this._getSafe(options),
2615       retry: this._getRetry(options),
2616     });
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));
2621     } else {
2622       fn(rootBatch);
2623     }
2624     const responses = await this._batchRequests(rootBatch._requests, options);
2625     if (options.aggregate) {
2626       return aggregate(responses, rootBatch._requests);
2627     }
2628     return responses;
2629   }
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.
2637       const msg =
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;
2641     }
2642     const uri = this.remote + addEndpointOptions(request.path, options);
2643     const result = await this.http.request(
2644       uri,
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,
2654       }),
2655       { retry: this._getRetry(options) }
2656     );
2657     return raw ? result : result.json;
2658   }
2659   /**
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.
2665    *
2666    * @private
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
2690    *     with Retry-After.
2691    * @param  {String}  [options.method="GET"]
2692    *     The method to use in the request.
2693    */
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",
2700       ...params,
2701     };
2702     // Safety/Consistency check on ETag value.
2703     if (since && typeof since !== "string") {
2704       throw new Error(
2705         `Invalid value for since (${since}), should be ETag value.`
2706       );
2707     }
2708     const query = {
2709       ...filters,
2710       _sort: sort,
2711       _limit: limit,
2712       _since: since,
2713     };
2714     if (fields) {
2715       query._fields = fields;
2716     }
2717     const querystring = qsify(query);
2718     let results = [],
2719       current = 0;
2720     const next = async function (nextPage) {
2721       if (!nextPage) {
2722         throw new Error("Pagination exhausted.");
2723       }
2724       return processNextPage(nextPage);
2725     };
2726     const processNextPage = async nextPage => {
2727       const { headers } = options;
2728       return handleResponse(await this.http.request(nextPage, { headers }));
2729     };
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").
2733       return {
2734         last_modified: etag ? etag.replace(/"/g, "") : etag,
2735         data: results,
2736         next: next.bind(null, nextPage),
2737         hasNextPage: !!nextPage,
2738         totalRecords: -1,
2739       };
2740     };
2741     const handleResponse = async function ({
2742       headers = new Headers(),
2743       json = {},
2744     }) {
2745       const nextPage = headers.get("Next-Page");
2746       const etag = headers.get("ETag");
2747       if (!pages) {
2748         return pageResults(json.data, nextPage, etag);
2749       }
2750       // Aggregate new results with previous ones
2751       results = results.concat(json.data);
2752       current += 1;
2753       if (current >= pages || !nextPage) {
2754         // Pagination exhausted
2755         return pageResults(results, nextPage, etag);
2756       }
2757       // Follow next page
2758       return processNextPage(nextPage);
2759     };
2760     return handleResponse(
2761       await this.execute(
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.
2765         {
2766           headers: options.headers ? options.headers : {},
2767           path: path + "?" + querystring,
2768           method: options.method,
2769         },
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 }
2774       )
2775     );
2776   }
2777   /**
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.
2782    *
2783    * @private
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
2807    *     with Retry-After.
2808    */
2809   async paginatedList(path, params = {}, options = {}) {
2810     return this.paginatedOperation(path, params, options);
2811   }
2812   /**
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.
2817    *
2818    * @private
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
2842    *     with Retry-After.
2843    */
2844   paginatedDelete(path, params = {}, options = {}) {
2845     const { headers, safe, last_modified } = options;
2846     const deleteRequest$1 = deleteRequest(path, {
2847       headers,
2848       safe: safe ? safe : false,
2849       last_modified,
2850     });
2851     return this.paginatedOperation(path, params, {
2852       ...options,
2853       headers: deleteRequest$1.headers,
2854       method: "DELETE",
2855     });
2856   }
2857   /**
2858    * Lists all permissions.
2859    *
2860    * @param  {Object} [options={}]      The options object.
2861    * @param  {Object} [options.headers={}] Headers to use when making
2862    *     this request.
2863    * @param  {Number} [options.retry=0]    Number of retries to make
2864    *     when faced with transient errors.
2865    * @return {Promise<Object[], Error>}
2866    */
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),
2875     });
2876   }
2877   /**
2878    * Retrieves the list of buckets.
2879    *
2880    * @param  {Object} [options={}]      The options object.
2881    * @param  {Object} [options.headers={}] Headers to use when making
2882    *     this request.
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
2887    *     just some fields.
2888    * @return {Promise<Object[], Error>}
2889    */
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),
2895     });
2896   }
2897   /**
2898    * Creates a new bucket on the server.
2899    *
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>}
2908    */
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(
2914       createRequest(
2915         path,
2916         { data: _data, permissions },
2917         {
2918           headers: this._getHeaders(options),
2919           safe: this._getSafe(options),
2920         }
2921       ),
2922       { retry: this._getRetry(options) }
2923     );
2924   }
2925   /**
2926    * Deletes a bucket from the server.
2927    *
2928    * @ignore
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>}
2937    */
2938   async deleteBucket(bucket, options = {}) {
2939     const bucketObj = toDataBody(bucket);
2940     if (!bucketObj.id) {
2941       throw new Error("A bucket id is required.");
2942     }
2943     const path = ENDPOINTS.bucket(bucketObj.id);
2944     const { last_modified } = { ...bucketObj, ...options };
2945     return this.execute(
2946       deleteRequest(path, {
2947         last_modified,
2948         headers: this._getHeaders(options),
2949         safe: this._getSafe(options),
2950       }),
2951       { retry: this._getRetry(options) }
2952     );
2953   }
2954   /**
2955    * Deletes buckets.
2956    *
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
2960    *     this request.
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
2965    *     just some fields.
2966    * @param  {Number}  [options.last_modified] The last_modified option.
2967    * @return {Promise<Object[], Error>}
2968    */
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),
2974       safe: options.safe,
2975       last_modified: options.last_modified,
2976     });
2977   }
2978   async createAccount(username, password) {
2979     return this.execute(
2980       createRequest(
2981         `/accounts/${username}`,
2982         { data: { password } },
2983         { method: "PUT" }
2984       )
2985     );
2986   }
2988 __decorate(
2989   [nobatch("This operation is not supported within a batch operation.")],
2990   KintoClientBase.prototype,
2991   "fetchServerSettings",
2992   null
2994 __decorate(
2995   [nobatch("This operation is not supported within a batch operation.")],
2996   KintoClientBase.prototype,
2997   "fetchServerCapabilities",
2998   null
3000 __decorate(
3001   [nobatch("This operation is not supported within a batch operation.")],
3002   KintoClientBase.prototype,
3003   "fetchUser",
3004   null
3006 __decorate(
3007   [nobatch("This operation is not supported within a batch operation.")],
3008   KintoClientBase.prototype,
3009   "fetchHTTPApiVersion",
3010   null
3012 __decorate(
3013   [nobatch("Can't use batch within a batch!")],
3014   KintoClientBase.prototype,
3015   "batch",
3016   null
3018 __decorate(
3019   [capable(["permissions_endpoint"])],
3020   KintoClientBase.prototype,
3021   "listPermissions",
3022   null
3024 __decorate(
3025   [support("1.4", "2.0")],
3026   KintoClientBase.prototype,
3027   "deleteBuckets",
3028   null
3030 __decorate(
3031   [capable(["accounts"])],
3032   KintoClientBase.prototype,
3033   "createAccount",
3034   null
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.
3050  */
3051 /* @ts-ignore */
3052 class KintoHttpClient extends KintoClientBase {
3053   constructor(remote, options = {}) {
3054     const events = {};
3055     EventEmitter.decorate(events);
3056     super(remote, { events: events, ...options });
3057   }
3059 KintoHttpClient.errors = errors;
3061 export { KintoHttpClient };