UNPKG

49.1 kBJavaScriptView Raw
1/* eslint-disable max-classes-per-file */
2const axios = require('./httpclient');
3const config = require('./config');
4const Conditions = require('./conditions');
5const Headers = require('./headers');
6const AccountAPI = require('./api-account.js');
7const AuthAPI = require('./api-auth.js');
8const PurgeAPI = require('./api-purge.js');
9const DomainAPI = require('./api-domain.js');
10const HealthcheckAPI = require('./api-healthcheck');
11
12class RateLimitError extends Error {
13
14}
15
16class Fastly {
17 /**
18 * @typedef {Function} CreateFunction
19 * A function that creates a resource of a specific type. If a resource of that
20 * name already exists, it will reject the returned promise with an error.
21 * @param {string} version - The service config version to operate on. Needs to be checked out.
22 * @param {object} data - The data object describing the resource to be created.
23 * @param {string} data.name - The name of the resource to be created.
24 * @returns {Promise} The response object representing the completion or failure.
25 * @throws {FastlyError}
26 */
27
28 /**
29 * @typedef {Function} UpdateFunction
30 * A function that updates an already existing resource of a specific type.
31 * If no resource of that name exists, it will reject the returned promise with an error.
32 * @param {string} version - The service config version to operate on. Needs to be checked out.
33 * @param {string} name - The name of the resource to be updated. The old name in case of renaming
34 * something.
35 * @param {object} data - The data object describing the resource to be updated.
36 * @param {string} data.name - The new name of the resource to be updated.
37 * @returns {Promise} The response object representing the completion or failure.
38 * @throws {FastlyError}
39 */
40
41 /**
42 * @typedef {Function} ReadFunction
43 * A function that retrieves a representation of a resource of a specific type.
44 * If no resource of that name exists, it will reject the returned promise with an error.
45 * @param {string} version - The service config version to operate on. Needs to be checked out.
46 * @param {string} name - The name of the resource to be retrieved.
47 * @returns {Promise} The response object representing the completion or failure.
48 * @throws {FastlyError}
49 */
50
51 /**
52 * @typedef {Function} ListFunction
53 * A function that retrieves a list of resources of a specific type.
54 * @param {string} version - The service config version to operate on. Needs to be checked out.
55 * @returns {Promise} The response object representing the completion or failure.
56 * @throws {FastlyError}
57 */
58
59 /**
60 * Create a new function that lists all log configurations for a given service
61 * and version. The function can be parametrized with the name of the logging
62 * service.
63 *
64 * @param {string} service - The id of the logging service. Supported services are:
65 * s3, s3canary, azureblob, cloudfiles, digitalocean, ftp, bigquery, gcs, honeycomb,
66 * logshuttle, logentries, loggly, heroku, https, openstack, papertrail, scalyr, splunk,
67 * sumologic, syslog.
68 * @returns {ListFunction} A logging function.
69 */
70 readLogsFn(service) {
71 return async (version) => this.request.get(`/service/${this.service_id}/version/${await this.getVersion(version, 'latest')}/logging/${service}`);
72 }
73
74 /**
75 * Create a new function that returns a named log configuration for a given service
76 * and version. The function can be parametrized with the name of the logging
77 * service.
78 *
79 * @param {string} service - The id of the logging service. Supported services are:
80 * s3, s3canary, azureblob, cloudfiles, digitalocean, ftp, bigquery, gcs, honeycomb,
81 * logshuttle, logentries, loggly, heroku, https, openstack, papertrail, scalyr, splunk,
82 * sumologic, syslog.
83 * @returns {ReadFunction} A logging function.
84 */
85 readLogFn(service) {
86 return async (version, name) => this.request.get(`/service/${this.service_id}/version/${await this.getVersion(version, 'latest')}/logging/${service}/${name}`);
87 }
88
89 /**
90 * Create a new function that creates a named log configuration for a given service
91 * and version. The function can be parametrized with the name of the logging
92 * service.
93 *
94 * @param {string} service - The id of the logging service. Supported services are:
95 * s3, s3canary, azureblob, cloudfiles, digitalocean, ftp, bigquery, gcs, honeycomb,
96 * logshuttle, logentries, loggly, heroku, https, openstack, papertrail, scalyr, splunk,
97 * sumologic, syslog.
98 * @returns {CreateFunction} A logging function.
99 */
100 createLogFn(service) {
101 return async (version, data) => this.request.post(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/logging/${service}`, data);
102 }
103
104 /**
105 * Create a new function that updates a named log configuration for a given service
106 * and version. The function can be parametrized with the name of the logging
107 * service.
108 *
109 * @param {string} service - The id of the logging service. Supported services are:
110 * s3, s3canary, azureblob, cloudfiles, digitalocean, ftp, bigquery, gcs, honeycomb,
111 * logshuttle, logentries, loggly, heroku, https, openstack, papertrail, scalyr, splunk,
112 * sumologic, syslog.
113 * @returns {UpdateFunction} A logging function.
114 */
115 updateLogFn(service) {
116 return async (version, name, data) => this.request.put(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/logging/${service}/${name}`, data);
117 }
118
119 /**
120 * Creates an update-or-create or "safe create" function that will either create
121 * (if it does not exist) or update (if it does) a named resource. The function
122 * will attempt to check if the resource exists first (if a reader function has been
123 * provided), alternatively, it will just blindly create and fall back with an
124 * update.
125 *
126 * @param {CreateFunction} createFn - A function that creates a resource.
127 * @param {UpdateFunction} updateFn - A function that updates a resource.
128 * @param {ReadFunction} readFn - An optional function that checks for the existence
129 * of a resource.
130 * @returns {UpdateFunction} An update function that does not fail on conflict.
131 */
132 upsertFn(createFn, updateFn, readFn) {
133 if (readFn) {
134 // careful
135 return (version, name, data) => readFn.apply(this, [version, name])
136 .then(() => updateFn.apply(this, [version, name, data]))
137 .catch(() => createFn.apply(this, [version, data]));
138 }
139 // stubborn
140 return (version, name, data) => createFn.apply(this, [version, data])
141 .catch(() => updateFn.apply(this, [version, name, data]));
142 }
143
144 /* eslint-disable camelcase */
145 /**
146 * The constructor method for creating a fastly-promises instance.
147 *
148 * @param {string} token - The Fastly API token.
149 * @param {string} service_id - The Fastly service ID.
150 * @param {number} timeout - HTTP timeout for requests to the Fastly API, default: 15 seconds.
151 */
152 constructor(token, service_id, timeout = 15000) {
153 this.service_id = service_id;
154 this.request = axios.create({
155 baseURL: config.mainEntryPoint,
156 timeout,
157 headers: { 'Fastly-Key': token },
158 });
159
160 this.requestmonitor = this.request.monitor;
161
162 this.versions = {
163 current: undefined,
164 active: undefined,
165 latest: undefined,
166 };
167
168 this.readS3Logs = this.readLogsFn('s3');
169 this.readS3canaryLogs = this.readLogsFn('s3canary');
170 this.readAzureblobLogs = this.readLogsFn('azureblob');
171 this.readCloudfilesLogs = this.readLogsFn('cloudfiles');
172 this.readDigitaloceanLogs = this.readLogsFn('digitalocean');
173 this.readFtpLogs = this.readLogsFn('ftp');
174 this.readBigqueryLogs = this.readLogsFn('bigquery');
175 this.readGcsLogs = this.readLogsFn('gcs');
176 this.readHoneycombLogs = this.readLogsFn('honeycomb');
177 this.readLogshuttleLogs = this.readLogsFn('logshuttle');
178 this.readLogentriesLogs = this.readLogsFn('logentries');
179 this.readLogglyLogs = this.readLogsFn('loggly');
180 this.readHerokuLogs = this.readLogsFn('heroku');
181 this.readOpenstackLogs = this.readLogsFn('openstack');
182 this.readPapertrailLogs = this.readLogsFn('papertrail');
183 this.readScalyrLogs = this.readLogsFn('scalyr');
184 this.readSplunkLogs = this.readLogsFn('splunk');
185 this.readSumologicLogs = this.readLogsFn('sumologic');
186 this.readSyslogLogs = this.readLogsFn('syslog');
187 this.readHttpsLogs = this.readLogsFn('https');
188
189 this.readS3 = this.readLogFn('s3');
190 this.readS3canary = this.readLogFn('s3canary');
191 this.readAzureblob = this.readLogFn('azureblob');
192 this.readCloudfiles = this.readLogFn('cloudfiles');
193 this.readDigitalocean = this.readLogFn('digitalocean');
194 this.readFtp = this.readLogFn('ftp');
195 this.readBigquery = this.readLogFn('bigquery');
196 this.readGcs = this.readLogFn('gcs');
197 this.readHoneycomb = this.readLogFn('honeycomb');
198 this.readLogshuttle = this.readLogFn('logshuttle');
199 this.readLogentries = this.readLogFn('logentries');
200 this.readLoggly = this.readLogFn('loggly');
201 this.readHeroku = this.readLogFn('heroku');
202 this.readOpenstack = this.readLogFn('openstack');
203 this.readPapertrail = this.readLogFn('papertrail');
204 this.readScalyr = this.readLogFn('scalyr');
205 this.readSplunk = this.readLogFn('splunk');
206 this.readSumologic = this.readLogFn('sumologic');
207 this.readSyslog = this.readLogFn('syslog');
208 this.readHttps = this.readLogFn('https');
209
210 this.createS3 = this.createLogFn('s3');
211 this.createS3canary = this.createLogFn('s3canary');
212 this.createAzureblob = this.createLogFn('azureblob');
213 this.createCloudfiles = this.createLogFn('cloudfiles');
214 this.createDigitalocean = this.createLogFn('digitalocean');
215 this.createFtp = this.createLogFn('ftp');
216 this.createBigquery = this.createLogFn('bigquery');
217 this.createGcs = this.createLogFn('gcs');
218 this.createHoneycomb = this.createLogFn('honeycomb');
219 this.createLogshuttle = this.createLogFn('logshuttle');
220 this.createLogentries = this.createLogFn('logentries');
221 this.createLoggly = this.createLogFn('loggly');
222 this.createHeroku = this.createLogFn('heroku');
223 this.createOpenstack = this.createLogFn('openstack');
224 this.createPapertrail = this.createLogFn('papertrail');
225 this.createScalyr = this.createLogFn('scalyr');
226 this.createSplunk = this.createLogFn('splunk');
227 this.createSumologic = this.createLogFn('sumologic');
228 this.createSyslog = this.createLogFn('syslog');
229 this.createHttps = this.createLogFn('https');
230
231 this.updateS3 = this.updateLogFn('s3');
232 this.updateS3canary = this.updateLogFn('s3canary');
233 this.updateAzureblob = this.updateLogFn('azureblob');
234 this.updateCloudfiles = this.updateLogFn('cloudfiles');
235 this.updateDigitalocean = this.updateLogFn('digitalocean');
236 this.updateFtp = this.updateLogFn('ftp');
237 this.updateBigquery = this.updateLogFn('bigquery');
238 this.updateGcs = this.updateLogFn('gcs');
239 this.updateHoneycomb = this.updateLogFn('honeycomb');
240 this.updateLogshuttle = this.updateLogFn('logshuttle');
241 this.updateLogentries = this.updateLogFn('logentries');
242 this.updateLoggly = this.updateLogFn('loggly');
243 this.updateHeroku = this.updateLogFn('heroku');
244 this.updateOpenstack = this.updateLogFn('openstack');
245 this.updatePapertrail = this.updateLogFn('papertrail');
246 this.updateScalyr = this.updateLogFn('scalyr');
247 this.updateSplunk = this.updateLogFn('splunk');
248 this.updateSumologic = this.updateLogFn('sumologic');
249 this.updateSyslog = this.updateLogFn('syslog');
250 this.updateHttps = this.updateLogFn('https');
251
252 this.writeS3 = this
253 .upsertFn(this.createS3, this.updateS3, this.readS3);
254 this.writeS3canary = this
255 .upsertFn(this.createS3canary, this.updateS3canary, this.readS3canary);
256 this.writeAzureblob = this
257 .upsertFn(this.createAzureblob, this.updateAzureblob, this.readAzureblob);
258 this.writeCloudfiles = this
259 .upsertFn(this.createCloudfiles, this.updateCloudfiles, this.readCloudfiles);
260 this.writeDigitalocean = this
261 .upsertFn(this.createDigitalocean, this.updateDigitalocean, this.readDigitalocean);
262 this.writeFtp = this
263 .upsertFn(this.createFtp, this.updateFtp, this.readFtp);
264 this.writeBigquery = this
265 .upsertFn(this.createBigquery, this.updateBigquery, this.readBigquery);
266 this.writeGcs = this
267 .upsertFn(this.createGcs, this.updateGcs, this.readGcs);
268 this.writeHoneycomb = this
269 .upsertFn(this.createHoneycomb, this.updateHoneycomb, this.readHoneycomb);
270 this.writeLogshuttle = this
271 .upsertFn(this.createLogshuttle, this.updateLogshuttle, this.readLogshuttle);
272 this.writeLogentries = this
273 .upsertFn(this.createLogentries, this.updateLogentries, this.readLogentries);
274 this.writeLoggly = this
275 .upsertFn(this.createLoggly, this.updateLoggly, this.readLoggly);
276 this.writeHeroku = this
277 .upsertFn(this.createHeroku, this.updateHeroku, this.readHeroku);
278 this.writeOpenstack = this
279 .upsertFn(this.createOpenstack, this.updateOpenstack, this.readOpenstack);
280 this.writePapertrail = this
281 .upsertFn(this.createPapertrail, this.updatePapertrail, this.readPapertrail);
282 this.writeScalyr = this
283 .upsertFn(this.createScalyr, this.updateScalyr, this.readScalyr);
284 this.writeSplunk = this
285 .upsertFn(this.createSplunk, this.updateSplunk, this.readSplunk);
286 this.writeSumologic = this
287 .upsertFn(this.createSumologic, this.updateSumologic, this.readSumologic);
288 this.writeSyslog = this
289 .upsertFn(this.createSyslog, this.updateSyslog, this.readSyslog);
290 this.writeHttps = this
291 .upsertFn(this.createHttps, this.updateHttps, this.readHttps);
292
293 this.writeVCL = this.upsertFn(this.createVCL, this.updateVCL);
294 this.writeSnippet = this.upsertFn(this.createSnippet, this.updateSnippet);
295 this.writeBackend = this.upsertFn(this.createBackend, this.updateBackend);
296
297 this.writeCondition = this.upsertFn(
298 this.createCondition,
299 this.updateCondition,
300 this.readCondition,
301 );
302
303 this.writeHeader = this.upsertFn(
304 this.createHeader,
305 this.updateHeader,
306 this.readHeader,
307 );
308
309 this.conditions = new Conditions(this);
310 this.headers = new Headers(this);
311
312 // bind the methods of the API classes.
313 [AccountAPI, AuthAPI, PurgeAPI, DomainAPI, HealthcheckAPI].forEach((API) => {
314 const api = new API(this);
315 Object.getOwnPropertyNames(API.prototype).forEach((name) => {
316 const prop = api[name];
317 if (typeof prop === 'function' && !name.startsWith('_') && name !== 'constructor') {
318 this[name] = prop.bind(api);
319 }
320 });
321 });
322 }
323
324 /**
325 * @typedef {object} FastlyError
326 * The FastlyError class describes the most common errors that can occur
327 * when working with the Fastly API. Using `error.status`, the underlying
328 * HTTP status code can be retrieved. Known error status codes include:
329 * - 400: attempting to activate invalid VCL
330 * - 401: invalid credentials
331 * - 404: resource not found
332 * - 409: confict when trying to POST a resource that already exists
333 * - 422: attempting to modify a service config that is not checked out
334 * - 429: rate limit exceeded, try again later
335 * @property {number} status The HTTP status code from the server response, e.g. 200.
336 * @property {object} data The parsed body of the HTTP response.
337 * @property {string} code A short error message.
338 * @property {string} message A more detailed error message.
339 */
340
341 /**
342 * @typedef {object} Response
343 * @property {number} status The HTTP status code from the server response, e.g. 200.
344 * @property {string} statusText The HTTP status text, e.g. 'OK'.
345 * @property {object} headers The HTTP headers of the reponse.
346 * @property {object} config The original request configuration used for the HTTP client.
347 * @property {object} request The HTTP request.
348 * @property {object} data The parsed body of the HTTP response.
349 */
350
351 /**
352 * Get a list of all Fastly datacenters.
353 *
354 * @see https://docs.fastly.com/api/tools#datacenter_1c8d3b9dd035e301155b44eae05e0554
355 * @example
356 * instance.dataCenters()
357 .then(res => {
358 console.log(res.data);
359 })
360 .catch(err => {
361 console.log(err.message);
362 });
363 * @returns {Promise} The response object representing the completion or failure.
364 */
365 dataCenters() {
366 return this.request.get('/datacenters');
367 }
368
369 /**
370 * Fastly's services IP ranges.
371 *
372 * @see https://docs.fastly.com/api/tools#public_ip_list_ef2e9900a1c9522b58f5abed92ec785e
373 * @example
374 * instance.publicIpList()
375 .then(res => {
376 console.log(res.data);
377 })
378 .catch(err => {
379 console.log(err.message);
380 });
381 * @returns {Promise} The response object representing the completion or failure.
382 */
383 publicIpList() {
384 return this.request.get('/public-ip-list');
385 }
386
387 /**
388 * Retrieve headers and MD5 hash of the content for a particular URL from each Fastly edge server.
389 *
390 * @see https://docs.fastly.com/api/tools#content_4d2d4548b29c7661e17ebe7098872d6d
391 * @example
392 * instance.edgeCheck('api.example.com')
393 .then(res => {
394 console.log(res.data);
395 })
396 .catch(err => {
397 console.log(err.message);
398 });
399 * @param {string} url - Full URL (host and path) to check on all nodes. If protocol is omitted,
400 http will be assumed.
401 * @returns {Promise} The response object representing the completion or failure.
402 */
403 edgeCheck(url = '') {
404 return this.request.get(`/content/edge_check?url=${url}`);
405 }
406
407 /**
408 * List all services.
409 *
410 * @see https://docs.fastly.com/api/config#service_74d98f7e5d018256e44d1cf820388ef8
411 * @example
412 * instance.readServices()
413 .then(res => {
414 console.log(res.data);
415 })
416 .catch(err => {
417 console.log(err.message);
418 });
419 * @returns {Promise} The response object representing the completion or failure.
420 */
421 readServices() {
422 return this.request.get('/service');
423 }
424
425 /**
426 * Reads the services and returns a data object that contains a map where the service id is
427 * the key.
428 *
429 * @returns {Promise} The response object representing the completion or failure.
430 */
431 async readServicesById() {
432 const ret = await this.request.get('/service');
433 ret.data = ret.data.reduce((dat, service) => Object.assign(dat, { [service.id]: service }), {});
434 return ret;
435 }
436
437 /**
438 * Get a specific service by id.
439 *
440 * @see https://docs.fastly.com/api/config#service_a884a9abd5af9723f6fcbb1ed13ae4cc
441 * @param {string} [serviceId] - The service id.
442 * @returns {Promise} The response object representing the completion or failure.
443 */
444 async readService(serviceId = this.service_id) {
445 return this.request.get(`/service/${serviceId}`);
446 }
447
448 /**
449 * List the versions for a particular service.
450 *
451 * @see https://docs.fastly.com/api/config#version_dfde9093f4eb0aa2497bbfd1d9415987
452 * @example
453 * instance.readVersions()
454 .then(res => {
455 const active = res.data.filter(version => version.active);
456 console.log(active);
457 })
458 .catch(err => {
459 console.log(err.message);
460 });
461 * @returns {Promise} The response object representing the completion or failure.
462 */
463 readVersions() {
464 return this.request.get(`/service/${this.service_id}/version`);
465 }
466
467 /**
468 * @typedef {object} Versions
469 *
470 * Describes the most relevant versions of the service.
471 *
472 * @property {number} latest - The latest version of the service.
473 * @property {number} active - The currently active version number.
474 * @property {number} current - The latest editable version number.
475 */
476 /**
477 * Gets the version footprint for the service.
478 *
479 * @returns {Versions} The latest, current, and active versions of the service.
480 */
481 async getVersions() {
482 if (this.versions.latest) {
483 return this.versions;
484 }
485 const { data } = await this.readVersions();
486 this.versions.initial = 1;
487 this.versions.latest = data
488 .map(({ number }) => number)
489 .pop();
490 this.versions.active = data
491 .filter((version) => version.active)
492 .map(({ number }) => number)
493 .pop();
494 this.versions.current = data
495 .filter((version) => !version.locked)
496 .map(({ number }) => number)
497 .pop();
498
499 return this.versions;
500 }
501
502 async getVersion(version, ...fallbackname) {
503 if (version) {
504 return version;
505 }
506 const versions = await this.getVersions();
507 return fallbackname.map((attempt) => versions[attempt]).filter((e) => e)[0];
508 }
509
510 /**
511 * Clone the current configuration into a new version.
512 *
513 * @param {string} version - The version to be cloned.
514 * @see https://docs.fastly.com/api/config#version_7f4937d0663a27fbb765820d4c76c709
515 * @example
516 * instance.cloneVersion('45')
517 .then(res => {
518 console.log(res.data);
519 })
520 .catch(err => {
521 console.log(err.message);
522 });
523 * @returns {Promise} The response object representing the completion or failure.
524 */
525 async cloneVersion(version) {
526 const versions = await this.request.put(`/service/${this.service_id}/version/${await this.getVersion(version, 'active', 'current', 'latest', 'initial')}/clone`);
527 this.versions.current = versions.data.number;
528 this.versions.latest = versions.data.number;
529 return versions;
530 }
531
532 /**
533 * Activate the current version.
534 *
535 * @param {string} version - The version to be activated.
536 * @see https://docs.fastly.com/api/config#version_0b79ae1ba6aee61d64cc4d43fed1e0d5
537 * @example
538 * instance.activateVersion('23')
539 .then(res => {
540 console.log(res.data);
541 })
542 .catch(err => {
543 console.log(err.message);
544 });
545 * @returns {Promise} The response object representing the completion or failure.
546 */
547 async activateVersion(version) {
548 const versions = await this.request.put(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/activate`);
549 this.versions.active = versions.data.number;
550 this.versions.latest = versions.data.number;
551 return versions;
552 }
553
554 // === start ===
555
556 /**
557 * List all dictionary items for a particular service and version.
558 *
559 * @see https://docs.fastly.com/api/config#dictionary_item_a48de28cd7e76c1ea58523f39bb7204b
560 * @example
561 * instance.readDictItems(1, 'my_dictionary')
562 .then(res => {
563 console.log(res.data);
564 })
565 .catch(err => {
566 console.log(err.message);
567 });
568 * @param {string} version - The version of the dictionary.
569 * @param {string} name - The name of the dictionary.
570 * @returns {Promise} The response object representing the completion or failure.
571 */
572 async readDictItems(version, name) {
573 return this.readDictionary(
574 await this.getVersion(version, 'latest'),
575 name,
576 ).then(({ data }) => this.request.get(`/service/${this.service_id}/dictionary/${data.id}/items`));
577 }
578
579 /**
580 * Get details of a single dictionary item.
581 *
582 * @see https://docs.fastly.com/api/config#dictionary_item_08f090cd03ed4602ae63f131087e2f29
583 * @example
584 * instance.readDictItem('12', 'extensions', 'some_key')
585 .then(res => {
586 console.log(res.data);
587 })
588 .catch(err => {
589 console.log(err.message);
590 });
591 * @param {string} version - The current version of a service.
592 * @param {string} name - Name of the dictionary.
593 * @param {string} key - The key to retrieve values by.
594 * @returns {Promise} The response object representing the completion or failure.
595 */
596 async readDictItem(version, name, key) {
597 return this.readDictionary(
598 await this.getVersion(version, 'latest'),
599 name,
600 ).then(({ data }) => {
601 if (data.write_only) {
602 return {
603 status: 403, // not permitted to read from write-only dicts
604 data: {
605 dictionary_id: data.id,
606 service_id: this.service_id,
607 item_key: key,
608 item_value: undefined,
609 created_at: undefined,
610 deleted_at: undefined,
611 updated_at: undefined,
612 },
613 };
614 }
615 // always use uncached version here
616 return this.request.get.fresh(`/service/${this.service_id}/dictionary/${data.id}/item/${encodeURIComponent(key)}`);
617 });
618 }
619
620 /**
621 * Create a new dictionary item for a particular service and version.
622 *
623 * @see https://docs.fastly.com/api/config#dictionary_item_6ec455c0ba1b21671789e1362bc7fe55
624 * @param {number} version - The version number (current if omitted).
625 * @param {object} name - The dictionary definition.
626 * @param {string} key - The key.
627 * @param {string} value - The value to write.
628 * @returns {Promise} The reponse object.
629 */
630 async createDictItem(version, name, key, value) {
631 return this.readDictionary(
632 await this.getVersion(version, 'latest'),
633 name,
634 ).then(({ data }) => this.request.post(`/service/${this.service_id}/dictionary/${data.id}/item`, {
635 item_key: key,
636 item_value: value,
637 }));
638 }
639
640 /**
641 * @typedef {object} DictUpdate
642 * Specifies a dictionary update operation. In most cases, `upsert` is the best way
643 * to update values, as it will work for existing and non-existing items.
644 * @property {string} op - The operation: `create`, `update`, `delete`, or `upsert`.
645 * @property {string} item_key - The lookup key.
646 * @property {string} item_value - The dictionary value.
647 */
648 /**
649 * Updates multiple dictionary items in bulk.
650 *
651 * @param {number} version - The version numer (current if ommitted).
652 * @param {string} name - Name of the dictionary.
653 * @param {...DictUpdate} items - The dictionary update operations.
654 * @returns {Promise} The response object.
655 * @example
656 * // single item
657 * fastly.bulkUpdateDictItems(1, 'secret_dictionary',
658 * { item_key: 'some_key', item_value: 'some_value', op: 'upsert' });
659 *
660 * // multiple items
661 * fastly.bulkUpdateDictItems(1, 'secret_dictionary',
662 * { item_key: 'some_key', item_value: 'some_value', op: 'update' },
663 * { item_key: 'other_key', item_value: 'other_value', op: 'update' });
664 */
665 async bulkUpdateDictItems(version, name, ...items) {
666 return this.readDictionary(
667 await this.getVersion(version, 'latest'),
668 name,
669 ).then(({ data }) => this.request.patch(`/service/${this.service_id}/dictionary/${data.id}/items`, {
670 items,
671 }));
672 }
673
674 /**
675 * Update a dictionary item value for a particular service and version.
676 *
677 * @see https://docs.fastly.com/api/config#dictionary_item_34c884a7cdce84dfcfd38dac7a0b5bb0
678 * @example
679 * instance.updateDictItem(1, 'extensions', 'html', 'text/html')
680 .then(res => {
681 console.log(res.data);
682 })
683 .catch(err => {
684 console.log(err.message);
685 });
686 * @param {string} version - The current version of a service.
687 * @param {string} name - The name of the dictionary.
688 * @param {string} key - The key to update data under.
689 * @param {string} value - The value to update the dictionary with.
690 * @returns {Promise} The response object representing the completion or failure.
691 */
692 async updateDictItem(version, name, key, value) {
693 return this.readDictionary(
694 await this.getVersion(version, 'latest'),
695 name,
696 ).then(({ data }) => this.request.put(`/service/${this.service_id}/dictionary/${data.id}/item/${encodeURIComponent(key)}`, {
697 item_value: value,
698 }));
699 }
700
701 /**
702 * Delete a dictionary item for a particular service and version.
703 *
704 * @see https://docs.fastly.com/api/config#dictionary_item_664347e743b8eafc9a93c729d9da0427
705 * @example
706 * instance.deleteDictItem('34', 'extensions', 'html')
707 .then(res => {
708 console.log(res.data);
709 })
710 .catch(err => {
711 console.log(err.message);
712 });
713 * @param {string} version - The current version of a service.
714 * @param {string} name - The name of the dictionary.
715 * @param {string} key - The key to update data under.
716 * @returns {Promise} The response object representing the completion or failure.
717 */
718 async deleteDictItem(version, name, key) {
719 return this.readDictionary(
720 await this.getVersion(version, 'latest'),
721 name,
722 ).then(({ data }) => this.request.delete(`/service/${this.service_id}/dictionary/${data.id}/item/${encodeURIComponent(key)}`));
723 }
724
725 /**
726 * Safely create, update or delete a dictionary item in a named dictionary.
727 *
728 * @param {number} version - Service version to use for dictionary lookup.
729 * @param {string} name - Name of the dictionary (not ID).
730 * @param {string} key - Key to create, update or delete.
731 * @param {string} value - Value to update. Empty strings will delete the dictionary entry.
732 * @returns {Promise} The response object representing the completion or failure.
733 */
734 writeDictItem(version, name, key, value) {
735 return this.readDictItem(version, name, key)
736 .then(() => {
737 // the dictionary item already exists
738 if (value) {
739 // update existing value
740 return this.updateDictItem(version, name, key, value);
741 }
742 // value is undefined. Fastly does not support overwriting with empty
743 // values, so we delete the value
744 return this.deleteDictItem(version, name, key);
745 })
746 .catch(() => {
747 // the dictionary item does not exist
748 if (value) {
749 return this.createDictItem(version, name, key, value);
750 }
751 // the item does not exist and there is no data to write, we just pretend it went ok
752 return {
753 status: 200,
754 data: {
755 status: 'ok',
756 },
757 };
758 });
759 }
760
761 // === done ===
762
763 /**
764 * List all dictionaries for a particular service and version.
765 *
766 * @see https://docs.fastly.com/api/config#dictionary_6d2cc293b994eb8c16d93e92e91f3915
767 * @example
768 * instance.readDictionaries('12')
769 .then(res => {
770 console.log(res.data);
771 })
772 .catch(err => {
773 console.log(err.message);
774 });
775 * @param {string} version - The current version of a service.
776 * @returns {Promise} The response object representing the completion or failure.
777 */
778 async readDictionaries(version) {
779 return this.request.get(`/service/${this.service_id}/version/${await this.getVersion(version, 'latest')}/dictionary`);
780 }
781
782 /**
783 * Get details of a single dictionary.
784 *
785 * @see https://docs.fastly.com/api/config#dictionary_0e16df083830ed3b6c30b73dcef64014
786 * @example
787 * instance.readDictionary('12', 'extensions')
788 .then(res => {
789 console.log(res.data);
790 })
791 .catch(err => {
792 console.log(err.message);
793 });
794 * @param {string} version - The current version of a service.
795 * @param {string} name - Name of the dictionary.
796 * @returns {Promise} The response object representing the completion or failure.
797 */
798 async readDictionary(version, name) {
799 return this.request.get(`/service/${this.service_id}/version/${await this.getVersion(version, 'latest')}/dictionary/${name}`);
800 }
801
802 /**
803 * Create a new dictionary for a particular service and version.
804 *
805 * @see https://docs.fastly.com/api/config#dictionary_7d48b87bf82433162a3b209292722125
806 * @param {number} version - The version number (current if omitted).
807 * @param {object} data - The dictionary definition.
808 * @returns {Promise} The reponse object.
809 */
810 async createDictionary(version, data) {
811 return this.request.post(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/dictionary`, data);
812 }
813
814 /**
815 * Update a dictionary for a particular service and version.
816 *
817 * @see https://docs.fastly.com/api/config#dictionary_8c9da370b1591d99e5389143a5589a32
818 * @example
819 * instance.updateDictionary('34', 'old-name', { name: 'new-name' })
820 .then(res => {
821 console.log(res.data);
822 })
823 .catch(err => {
824 console.log(err.message);
825 });
826 * @param {string} version - The current version of a service.
827 * @param {string} name - The name of the dictionary.
828 * @param {object} data - The data to be sent as the request body.
829 * @returns {Promise} The response object representing the completion or failure.
830 */
831 async updateDictionary(version, name, data) {
832 return this.request.put(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/dictionary/${encodeURIComponent(name)}`, data);
833 }
834
835 async writeDictionary(version, name, data) {
836 try {
837 const existing = await this.readDictionary(version, name);
838 // keep the write-only status
839 const mydata = {
840 name: data.name,
841 write_only: existing.data.write_only,
842 };
843 if (mydata.name && mydata.name !== existing.data.name) {
844 return this.updateDictionary(version, name, mydata);
845 }
846 return existing;
847 } catch (e) {
848 return this.createDictionary(version, data);
849 }
850 }
851
852 /**
853 * Delete a dictionary for a particular service and version.
854 *
855 * @see https://docs.fastly.com/api/config#dictionary_8c9da370b1591d99e5389143a5589a32
856 * @example
857 * instance.deleteDictionary('34', 'extensions')
858 .then(res => {
859 console.log(res.data);
860 })
861 .catch(err => {
862 console.log(err.message);
863 });
864 * @param {string} version - The current version of a service.
865 * @param {string} name - The name of the dictionary.
866 * @returns {Promise} The response object representing the completion or failure.
867 */
868 async deleteDictionary(version, name) {
869 return this.request.delete(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/dictionary/${encodeURIComponent(name)}`);
870 }
871
872 /* ==CONDITIONS */
873
874 /**
875 * List all conditions for a particular service and version.
876 *
877 * @see https://docs.fastly.com/api/config#condition_b61196c572f473c89863a81cc5912861
878 * @example
879 * instance.readConditions('12')
880 .then(res => {
881 console.log(res.data);
882 })
883 .catch(err => {
884 console.log(err.message);
885 });
886 * @param {string} version - The current version of a service.
887 * @returns {Promise} The response object representing the completion or failure.
888 */
889 async readConditions(version) {
890 return this.request.get(`/service/${this.service_id}/version/${await this.getVersion(version, 'latest')}/condition`);
891 }
892
893 /**
894 * Get details of a single named condition.
895 *
896 * @see https://docs.fastly.com/api/config#condition_149a2f48485ceb335f70504e5269b77e
897 * @example
898 * instance.readCondition('12', 'returning')
899 .then(res => {
900 console.log(res.data);
901 })
902 .catch(err => {
903 console.log(err.message);
904 });
905 * @param {string} version - The current version of a service.
906 * @param {string} name - Name of the condition.
907 * @returns {Promise} The response object representing the completion or failure.
908 */
909 async readCondition(version, name) {
910 return this.request.get(`/service/${this.service_id}/version/${await this.getVersion(version, 'latest')}/condition/${name}`);
911 }
912
913 /**
914 * Create a new condition for a particular service and version.
915 *
916 * @see https://docs.fastly.com/api/config#condition_551199dbec2271195319b675d8659226
917 * @param {number} version - The version number (current if omitted).
918 * @param {object} data - The condition definition.
919 * @returns {Promise} The reponse object.
920 */
921 async createCondition(version, data) {
922 return this.request.post(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/condition`, data);
923 }
924
925 /**
926 * Update a condition for a particular service and version.
927 *
928 * @see https://docs.fastly.com/api/config#condition_01a2c4e4b44943b541e001013b665deb
929 * @example
930 * instance.updateCondition('34', 'returning', { name: 'returning-visitor' })
931 .then(res => {
932 console.log(res.data);
933 })
934 .catch(err => {
935 console.log(err.message);
936 });
937 * @param {string} version - The current version of a service.
938 * @param {string} name - The name of the condition.
939 * @param {object} data - The data to be sent as the request body.
940 * @returns {Promise} The response object representing the completion or failure.
941 */
942 async updateCondition(version, name, data) {
943 return this.request.put(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/condition/${encodeURIComponent(name)}`, data);
944 }
945
946 /**
947 * Delete a condition for a particular service and version.
948 *
949 * @see https://docs.fastly.com/api/config#condition_2b902b7649c46b4541f00a920d06c94d
950 * @example
951 * instance.deleteCondition('34', 'extensions')
952 .then(res => {
953 console.log(res.data);
954 })
955 .catch(err => {
956 console.log(err.message);
957 });
958 * @param {string} version - The current version of a service.
959 * @param {string} name - The name of the condition.
960 * @returns {Promise} The response object representing the completion or failure.
961 */
962 async deleteCondition(version, name) {
963 return this.request.delete(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/condition/${encodeURIComponent(name)}`);
964 }
965
966 /* == HEADERS */
967
968 /**
969 * List all headers for a particular service and version.
970 *
971 * @see https://docs.fastly.com/api/config#header_dd9da0592b2f1ff8ef0a4c1943f8abff
972 * @example
973 * instance.readHeaders('12')
974 .then(res => {
975 console.log(res.data);
976 })
977 .catch(err => {
978 console.log(err.message);
979 });
980 * @param {string} version - The current version of a service.
981 * @returns {Promise} The response object representing the completion or failure.
982 */
983 async readHeaders(version) {
984 return this.request.get(`/service/${this.service_id}/version/${await this.getVersion(version, 'latest')}/header`);
985 }
986
987 /**
988 * Get details of a single named header.
989 *
990 * @see https://docs.fastly.com/api/config#header_86469e5eba4e5d6b1463e81f82a847e0
991 * @example
992 * instance.readHeader('12', 'returning')
993 .then(res => {
994 console.log(res.data);
995 })
996 .catch(err => {
997 console.log(err.message);
998 });
999 * @param {string} version - The current version of a service.
1000 * @param {string} name - Name of the header.
1001 * @returns {Promise} The response object representing the completion or failure.
1002 */
1003 async readHeader(version, name) {
1004 return this.request.get(`/service/${this.service_id}/version/${await this.getVersion(version, 'latest')}/header/${name}`);
1005 }
1006
1007 /**
1008 * Create a new header for a particular service and version.
1009 *
1010 * @see https://docs.fastly.com/api/config#header_151df4ce647a8e222e730b260287cb39
1011 * @param {number} version - The version number (current if omitted).
1012 * @param {object} data - The header definition.
1013 * @returns {Promise} The reponse object.
1014 */
1015 async createHeader(version, data) {
1016 return this.request.post(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/header`, data);
1017 }
1018
1019 /**
1020 * Update a header for a particular service and version.
1021 *
1022 * @see https://docs.fastly.com/api/config#header_c4257a0fd0eb017ea47b1fbb318fd61c
1023 * @example
1024 * instance.updateHeader('34', 'returning', { name: 'returning-visitor' })
1025 .then(res => {
1026 console.log(res.data);
1027 })
1028 .catch(err => {
1029 console.log(err.message);
1030 });
1031 * @param {string} version - The current version of a service.
1032 * @param {string} name - The name of the header.
1033 * @param {object} data - The data to be sent as the request body.
1034 * @returns {Promise} The response object representing the completion or failure.
1035 */
1036 async updateHeader(version, name, data) {
1037 return this.request.put(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/header/${encodeURIComponent(name)}`, data);
1038 }
1039
1040 /**
1041 * Delete a header for a particular service and version.
1042 *
1043 * @see https://docs.fastly.com/api/config#header_4bbb73fffda4d189bf5a19b474399a83
1044 * @example
1045 * instance.deleteHeader('34', 'extensions')
1046 .then(res => {
1047 console.log(res.data);
1048 })
1049 .catch(err => {
1050 console.log(err.message);
1051 });
1052 * @param {string} version - The current version of a service.
1053 * @param {string} name - The name of the header.
1054 * @returns {Promise} The response object representing the completion or failure.
1055 */
1056 async deleteHeader(version, name) {
1057 return this.request.delete(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/header/${encodeURIComponent(name)}`);
1058 }
1059
1060 /**
1061 * List all backends for a particular service and version.
1062 *
1063 * @see https://docs.fastly.com/api/config#backend_fb0e875c9a7669f071cbf89ca32c7f69
1064 * @example
1065 * instance.readBackends('12')
1066 .then(res => {
1067 console.log(res.data);
1068 })
1069 .catch(err => {
1070 console.log(err.message);
1071 });
1072 * @param {string} version - The current version of a service.
1073 * @returns {Promise} The response object representing the completion or failure.
1074 */
1075 async readBackends(version) {
1076 return this.request.get(`/service/${this.service_id}/version/${await this.getVersion(version, 'latest')}/backend`);
1077 }
1078
1079 /**
1080 * Update the backend for a particular service and version.
1081 *
1082 * @see https://docs.fastly.com/api/config#backend_fb3b3529417c70f57458644f7aec652e
1083 * @example
1084 * instance.updateBackend('34', 'slow-server', { name: 'fast-server' })
1085 .then(res => {
1086 console.log(res.data);
1087 })
1088 .catch(err => {
1089 console.log(err.message);
1090 });
1091 * @param {string} version - The current version of a service.
1092 * @param {string} name - The name of the backend.
1093 * @param {object} data - The data to be sent as the request body.
1094 * @returns {Promise} The response object representing the completion or failure.
1095 */
1096 async updateBackend(version, name, data) {
1097 return this.request.put(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/backend/${encodeURIComponent(name)}`, data);
1098 }
1099
1100 /**
1101 * Create a new backend for a particular service and version.
1102 *
1103 * @see https://docs.fastly.com/api/config#backend_85c170418ee71191dbb3b5046aeb6c2c
1104 * @param {number} version - The version number (current if omitted).
1105 * @param {object} data - The backend definition.
1106 * @returns {Promise} The reponse object.
1107 */
1108 async createBackend(version, data) {
1109 return this.request.post(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/backend`, data);
1110 }
1111 /**
1112 * @typedef {object} Snippet
1113 * @property {string} name The name of the snippet, as visible in the Fastly UI.
1114 * @property {string} content The VCL body of the snippet.
1115 */
1116
1117 /**
1118 * List all snippets for a particular service and version.
1119 *
1120 * @see https://docs.fastly.com/api/config#api-section-snippet
1121 * @example
1122 * instance.readSnippets('12')
1123 .then(res => {
1124 console.log(res.data);
1125 })
1126 .catch(err => {
1127 console.log(err.message);
1128 });
1129 * @param {string} version - The current version of a service.
1130 * @returns {Promise} The response object representing the completion or failure.
1131 */
1132 async readSnippets(version) {
1133 return this.request.get(`/service/${this.service_id}/version/${await this.getVersion(version, 'latest')}/snippet`);
1134 }
1135
1136 /**
1137 * Create a snippet for a particular service and version.
1138 *
1139 * @see https://docs.fastly.com/api/config#snippet_41e0e11c662d4d56adada215e707f30d
1140 * @example
1141 * instance.createSnippet('36', {
1142 name: 'your_snippet',
1143 priority: 10,
1144 dynamic: 1,
1145 content: 'table referer_blacklist {}',
1146 type: 'init'
1147 })
1148 .then(res => {
1149 console.log(res.data);
1150 })
1151 .catch(err => {
1152 console.log(err.message);
1153 });
1154 * @param {string} version - The current version of a service.
1155 * @param {Snippet} data - The data to be sent as the request body.
1156 * @returns {Promise} The response object representing the completion or failure.
1157 */
1158 async createSnippet(version, data) {
1159 return this.request.post(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/snippet`, data);
1160 }
1161
1162 /**
1163 * Update a VCL snippet for a particular service and version.
1164 *
1165 * @param {string} version - The current version of a service.
1166 * @param {string} name - The name of the snippet to update.
1167 * @param {Snippet} data - The data to be sent as the request body.
1168 * @returns {Promise} The response object representing the completion or failure.
1169 */
1170 async updateSnippet(version, name, data) {
1171 return this.request.put(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/snippet/${name}`, data);
1172 }
1173
1174 /**
1175 * @typedef {object} VCL
1176 * @property {string} name The name of the VCL, as visible in the Fastly UI.
1177 * Note: setting the name to 'main' here won't make it the main VCL,
1178 * unless you also call `setMainVCL`.
1179 * @property {string} content The VCL body of the custom VCL.
1180 */
1181
1182 /**
1183 * Create custom VCL for a particular service and version.
1184 *
1185 * @see https://docs.fastly.com/api/config#vcl_7ade6ab5926b903b6acf3335a85060cc
1186 * @param {string} version - The current version of a service.
1187 * @param {VCL} data - The data to be sent as the request body.
1188 * @returns {Promise} The response object representing the completion or failure.
1189 */
1190 async createVCL(version, data) {
1191 return this.request.post(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/vcl`, data);
1192 }
1193
1194 /**
1195 * Update custom VCL for a particular service and version.
1196 *
1197 * @see https://docs.fastly.com/api/config#vcl_0971365908e17086751c5ef2a8053087
1198 * @param {string} version - The current version of a service.
1199 * @param {string} name - The name of the VCL to update.
1200 * @param {VCL} data - The data to be sent as the request body.
1201 * @returns {Promise} The response object representing the completion or failure.
1202 */
1203 async updateVCL(version, name, data) {
1204 if (typeof name === 'object') {
1205 /* eslint-disable no-param-reassign */
1206 data = name;
1207 name = data.name;
1208 /* eslint-enable no-param-reassign */
1209 }
1210 return this.request.put(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/vcl/${name}`, data);
1211 }
1212
1213 /**
1214 * Define a custom VCL to be the main VCL for a particular service and version.
1215 *
1216 * @see https://docs.fastly.com/api/config#vcl_5576c38e7652f5a7261bfcad41c0faf1
1217 * @param {string} version - The current version of a service.
1218 * @param {string} name - The name of the VCL to declare main.
1219 * @returns {Promise} The response object representing the completion or failure.
1220 */
1221 async setMainVCL(version, name) {
1222 return this.request.put(`/service/${this.service_id}/version/${await this.getVersion(version, 'current')}/vcl/${name}/main`, {});
1223 }
1224
1225 /**
1226 * Creates a new version, runs the function `operations` and then
1227 * optionally activates the newly created version. This function
1228 * is useful for making modifications to a service config.
1229 *
1230 * You can provide a `limit` of write operations, which is an estimate
1231 * of the number of write operations that will be attempted. If the
1232 * limit is higher than the number of actions allowed by Fastly's rate
1233 * limits, the function will fail fast after cloning the service config.
1234 *
1235 * @example
1236 * ```javascript
1237 * await fastly.transact(async (newversion) => {
1238 * await fastly.doSomething(newversion);
1239 * });
1240 * // new version has been activated
1241 * ```
1242 * @param {Function} operations - A function that performs changes on the service config.
1243 * @param {boolean} activate - Set to false to prevent automatic activation.
1244 * @param {number} limit - Number of write operations that will be performed in this action.
1245 * @returns {object} The return value of the wrapped function.
1246 */
1247 async transact(operations, activate = true, limit = 0) {
1248 const newversion = (await this.cloneVersion()).data.number;
1249 if (limit > 0 && this.requestmonitor.remaining && this.requestmonitor.remaining < limit) {
1250 throw new RateLimitError(`Insufficient number of requests (${this.requestmonitor.remaining}) remaining for number of scheduled operations (${limit})`);
1251 }
1252 const result = await operations.apply(this, [newversion]);
1253 if (activate) {
1254 await this.activateVersion(newversion);
1255 }
1256 return result;
1257 }
1258
1259 /**
1260 * See `transact`, but this version does not activate the created version.
1261 *
1262 * @see #transact
1263 * @param {Function} operations - The operations that should be applied to the
1264 * cloned service config version.
1265 * @returns {object} Whatever `operations` returns.
1266 */
1267 async dryrun(operations) {
1268 return this.transact(operations, false);
1269 }
1270}
1271
1272/**
1273 * Function to create a new fastly-promises instance.
1274 *
1275 * @param {string} token - The Fastly API token.
1276 * @param {string} service_id - The Fastly service ID.
1277 * @returns {Fastly} The exported module.
1278 */
1279module.exports = (token, service_id) => new Fastly(token, service_id);