@microlink/function 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -177,945 +177,945 @@
177
177
 
178
178
  var require$$2 = /*@__PURE__*/ getAugmentedNamespace(qss_m)
179
179
 
180
- var ky$1 = { exports: {} }
181
-
182
- ;(function (module, exports) {
183
- ;(function (global, factory) {
184
- module.exports = factory()
185
- })(commonjsGlobal, function () {
186
- /*! MIT License © Sindre Sorhus */
180
+ var lib = { exports: {} }
187
181
 
188
- const isObject = value => value !== null && typeof value === 'object'
189
- const supportsAbortController =
190
- typeof globalThis.AbortController === 'function'
191
- const supportsStreams = typeof globalThis.ReadableStream === 'function'
192
- const supportsFormData = typeof globalThis.FormData === 'function'
182
+ var _nodeResolve_empty = {}
193
183
 
194
- const mergeHeaders = (source1, source2) => {
195
- const result = new globalThis.Headers(source1 || {})
196
- const isHeadersInstance = source2 instanceof globalThis.Headers
197
- const source = new globalThis.Headers(source2 || {})
184
+ var _nodeResolve_empty$1 = /*#__PURE__*/ Object.freeze({
185
+ __proto__: null,
186
+ default: _nodeResolve_empty
187
+ })
198
188
 
199
- for (const [key, value] of source) {
200
- if (
201
- (isHeadersInstance && value === 'undefined') ||
202
- value === undefined
203
- ) {
204
- result.delete(key)
205
- } else {
206
- result.set(key, value)
207
- }
208
- }
189
+ var require$$0 = /*@__PURE__*/ getAugmentedNamespace(_nodeResolve_empty$1)
209
190
 
210
- return result
211
- }
191
+ const os = require$$0
212
192
 
213
- const deepMerge = (...sources) => {
214
- let returnValue = {}
215
- let headers = {}
193
+ const extractPathRegex = /\s+at.*(?:\(|\s)(.*)\)?/
194
+ const pathRegex = /^(?:(?:(?:node|(?:internal\/[\w/]*|.*node_modules\/(?:babel-polyfill|pirates)\/.*)?\w+)\.js:\d+:\d+)|native)/
195
+ const homeDir = typeof os.homedir === 'undefined' ? '' : os.homedir()
216
196
 
217
- for (const source of sources) {
218
- if (Array.isArray(source)) {
219
- if (!Array.isArray(returnValue)) {
220
- returnValue = []
221
- }
197
+ var cleanStack$1 = (stack, options) => {
198
+ options = Object.assign({ pretty: false }, options)
222
199
 
223
- returnValue = [...returnValue, ...source]
224
- } else if (isObject(source)) {
225
- for (let [key, value] of Object.entries(source)) {
226
- if (isObject(value) && key in returnValue) {
227
- value = deepMerge(returnValue[key], value)
228
- }
200
+ return stack
201
+ .replace(/\\/g, '/')
202
+ .split('\n')
203
+ .filter(line => {
204
+ const pathMatches = line.match(extractPathRegex)
205
+ if (pathMatches === null || !pathMatches[1]) {
206
+ return true
207
+ }
229
208
 
230
- returnValue = { ...returnValue, [key]: value }
231
- }
209
+ const match = pathMatches[1]
232
210
 
233
- if (isObject(source.headers)) {
234
- headers = mergeHeaders(headers, source.headers)
235
- }
236
- }
211
+ // Electron
212
+ if (
213
+ match.includes('.app/Contents/Resources/electron.asar') ||
214
+ match.includes('.app/Contents/Resources/default_app.asar')
215
+ ) {
216
+ return false
217
+ }
237
218
 
238
- returnValue.headers = headers
219
+ return !pathRegex.test(match)
220
+ })
221
+ .filter(line => line.trim() !== '')
222
+ .map(line => {
223
+ if (options.pretty) {
224
+ return line.replace(extractPathRegex, (m, p1) =>
225
+ m.replace(p1, p1.replace(homeDir, '~'))
226
+ )
239
227
  }
240
228
 
241
- return returnValue
242
- }
229
+ return line
230
+ })
231
+ .join('\n')
232
+ }
243
233
 
244
- const requestMethods = ['get', 'post', 'put', 'patch', 'head', 'delete']
234
+ const copyProperty = (to, from, property, ignoreNonConfigurable) => {
235
+ // `Function#length` should reflect the parameters of `to` not `from` since we keep its body.
236
+ // `Function#prototype` is non-writable and non-configurable so can never be modified.
237
+ if (property === 'length' || property === 'prototype') {
238
+ return
239
+ }
245
240
 
246
- const responseTypes = {
247
- json: 'application/json',
248
- text: 'text/*',
249
- formData: 'multipart/form-data',
250
- arrayBuffer: '*/*',
251
- blob: '*/*'
252
- }
241
+ const toDescriptor = Object.getOwnPropertyDescriptor(to, property)
242
+ const fromDescriptor = Object.getOwnPropertyDescriptor(from, property)
253
243
 
254
- const retryMethods = ['get', 'put', 'head', 'delete', 'options', 'trace']
244
+ if (
245
+ !canCopyProperty(toDescriptor, fromDescriptor) &&
246
+ ignoreNonConfigurable
247
+ ) {
248
+ return
249
+ }
255
250
 
256
- const retryStatusCodes = [408, 413, 429, 500, 502, 503, 504]
251
+ Object.defineProperty(to, property, fromDescriptor)
252
+ }
257
253
 
258
- const retryAfterStatusCodes = [413, 429, 503]
254
+ // `Object.defineProperty()` throws if the property exists, is not configurable and either:
255
+ // - one its descriptors is changed
256
+ // - it is non-writable and its value is changed
257
+ const canCopyProperty = function (toDescriptor, fromDescriptor) {
258
+ return (
259
+ toDescriptor === undefined ||
260
+ toDescriptor.configurable ||
261
+ (toDescriptor.writable === fromDescriptor.writable &&
262
+ toDescriptor.enumerable === fromDescriptor.enumerable &&
263
+ toDescriptor.configurable === fromDescriptor.configurable &&
264
+ (toDescriptor.writable || toDescriptor.value === fromDescriptor.value))
265
+ )
266
+ }
259
267
 
260
- const stop = Symbol('stop')
268
+ const changePrototype = (to, from) => {
269
+ const fromPrototype = Object.getPrototypeOf(from)
270
+ if (fromPrototype === Object.getPrototypeOf(to)) {
271
+ return
272
+ }
261
273
 
262
- class HTTPError extends Error {
263
- constructor (response, request, options) {
264
- // Set the message to the status text, such as Unauthorized,
265
- // with some fallbacks. This message should never be undefined.
266
- super(
267
- response.statusText ||
268
- String(
269
- response.status === 0 || response.status
270
- ? response.status
271
- : 'Unknown response error'
272
- )
273
- )
274
- this.name = 'HTTPError'
275
- this.response = response
276
- this.request = request
277
- this.options = options
278
- }
279
- }
274
+ Object.setPrototypeOf(to, fromPrototype)
275
+ }
280
276
 
281
- class TimeoutError extends Error {
282
- constructor (request) {
283
- super('Request timed out')
284
- this.name = 'TimeoutError'
285
- this.request = request
286
- }
287
- }
277
+ const wrappedToString = (withName, fromBody) =>
278
+ `/* Wrapped ${withName}*/\n${fromBody}`
288
279
 
289
- const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
280
+ const toStringDescriptor = Object.getOwnPropertyDescriptor(
281
+ Function.prototype,
282
+ 'toString'
283
+ )
284
+ const toStringName = Object.getOwnPropertyDescriptor(
285
+ Function.prototype.toString,
286
+ 'name'
287
+ )
290
288
 
291
- // `Promise.race()` workaround (#91)
292
- const timeout = (request, abortController, options) =>
293
- new Promise((resolve, reject) => {
294
- const timeoutID = setTimeout(() => {
295
- if (abortController) {
296
- abortController.abort()
297
- }
289
+ // We call `from.toString()` early (not lazily) to ensure `from` can be garbage collected.
290
+ // We use `bind()` instead of a closure for the same reason.
291
+ // Calling `from.toString()` early also allows caching it in case `to.toString()` is called several times.
292
+ const changeToString = (to, from, name) => {
293
+ const withName = name === '' ? '' : `with ${name.trim()}() `
294
+ const newToString = wrappedToString.bind(null, withName, from.toString())
295
+ // Ensure `to.toString.toString` is non-enumerable and has the same `same`
296
+ Object.defineProperty(newToString, 'name', toStringName)
297
+ Object.defineProperty(to, 'toString', {
298
+ ...toStringDescriptor,
299
+ value: newToString
300
+ })
301
+ }
298
302
 
299
- reject(new TimeoutError(request))
300
- }, options.timeout)
303
+ const mimicFn$2 = (to, from, { ignoreNonConfigurable = false } = {}) => {
304
+ const { name } = to
301
305
 
302
- /* eslint-disable promise/prefer-await-to-then */
303
- options
304
- .fetch(request)
305
- .then(resolve)
306
- .catch(reject)
307
- .then(() => {
308
- clearTimeout(timeoutID)
309
- })
310
- /* eslint-enable promise/prefer-await-to-then */
311
- })
306
+ for (const property of Reflect.ownKeys(from)) {
307
+ copyProperty(to, from, property, ignoreNonConfigurable)
308
+ }
312
309
 
313
- const normalizeRequestMethod = input =>
314
- requestMethods.includes(input) ? input.toUpperCase() : input
310
+ changePrototype(to, from)
311
+ changeToString(to, from, name)
315
312
 
316
- const defaultRetryOptions = {
317
- limit: 2,
318
- methods: retryMethods,
319
- statusCodes: retryStatusCodes,
320
- afterStatusCodes: retryAfterStatusCodes
321
- }
313
+ return to
314
+ }
322
315
 
323
- const normalizeRetryOptions = (retry = {}) => {
324
- if (typeof retry === 'number') {
325
- return {
326
- ...defaultRetryOptions,
327
- limit: retry
328
- }
329
- }
316
+ var mimicFn_1 = mimicFn$2
330
317
 
331
- if (retry.methods && !Array.isArray(retry.methods)) {
332
- throw new Error('retry.methods must be an array')
318
+ var helpers = {
319
+ isFunction: obj => typeof obj === 'function',
320
+ isString: obj => typeof obj === 'string',
321
+ composeErrorMessage: (code, description) => `${code}, ${description}`,
322
+ inherits: (ctor, superCtor) => {
323
+ ctor.super_ = superCtor
324
+ ctor.prototype = Object.create(superCtor.prototype, {
325
+ constructor: {
326
+ value: ctor,
327
+ enumerable: false,
328
+ writable: true,
329
+ configurable: true
333
330
  }
331
+ })
332
+ }
333
+ }
334
334
 
335
- if (retry.statusCodes && !Array.isArray(retry.statusCodes)) {
336
- throw new Error('retry.statusCodes must be an array')
337
- }
335
+ const { isFunction, composeErrorMessage } = helpers
338
336
 
339
- return {
340
- ...defaultRetryOptions,
341
- ...retry,
342
- afterStatusCodes: retryAfterStatusCodes
343
- }
344
- }
337
+ function interfaceObject (error, ...props) {
338
+ Object.assign(error, ...props)
345
339
 
346
- // The maximum value of a 32bit int (see issue #117)
347
- const maxSafeTimeout = 2147483647
340
+ error.description = isFunction(error.message)
341
+ ? error.message(error)
342
+ : error.message
348
343
 
349
- class Ky {
350
- constructor (input, options = {}) {
351
- this._retryCount = 0
352
- this._input = input
353
- this._options = {
354
- // TODO: credentials can be removed when the spec change is implemented in all browsers. Context: https://www.chromestatus.com/feature/4539473312350208
355
- credentials: this._input.credentials || 'same-origin',
356
- ...options,
357
- headers: mergeHeaders(this._input.headers, options.headers),
358
- hooks: deepMerge(
359
- {
360
- beforeRequest: [],
361
- beforeRetry: [],
362
- afterResponse: []
363
- },
364
- options.hooks
365
- ),
366
- method: normalizeRequestMethod(
367
- options.method || this._input.method
368
- ),
369
- prefixUrl: String(options.prefixUrl || ''),
370
- retry: normalizeRetryOptions(options.retry),
371
- throwHttpErrors: options.throwHttpErrors !== false,
372
- timeout:
373
- typeof options.timeout === 'undefined' ? 10000 : options.timeout,
374
- fetch: options.fetch || globalThis.fetch.bind(globalThis)
375
- }
344
+ error.message = error.code
345
+ ? composeErrorMessage(error.code, error.description)
346
+ : error.description
347
+ }
376
348
 
377
- if (
378
- typeof this._input !== 'string' &&
379
- !(
380
- this._input instanceof URL ||
381
- this._input instanceof globalThis.Request
382
- )
383
- ) {
384
- throw new TypeError('`input` must be a string, URL, or Request')
385
- }
349
+ var addErrorProps$1 = interfaceObject
386
350
 
387
- if (this._options.prefixUrl && typeof this._input === 'string') {
388
- if (this._input.startsWith('/')) {
389
- throw new Error(
390
- '`input` must not begin with a slash when using `prefixUrl`'
391
- )
392
- }
351
+ const cleanStack = cleanStack$1
352
+ const mimicFn$1 = mimicFn_1
393
353
 
394
- if (!this._options.prefixUrl.endsWith('/')) {
395
- this._options.prefixUrl += '/'
396
- }
354
+ const addErrorProps = addErrorProps$1
355
+ const { isString } = helpers
397
356
 
398
- this._input = this._options.prefixUrl + this._input
399
- }
357
+ function createExtendError$1 (ErrorClass, classProps) {
358
+ function ExtendError (props) {
359
+ const error = new ErrorClass()
360
+ const errorProps = isString(props) ? { message: props } : props
361
+ addErrorProps(error, classProps, errorProps)
400
362
 
401
- if (supportsAbortController) {
402
- this.abortController = new globalThis.AbortController()
403
- if (this._options.signal) {
404
- this._options.signal.addEventListener('abort', () => {
405
- this.abortController.abort()
406
- })
407
- }
363
+ error.stack = cleanStack(error.stack)
364
+ return error
365
+ }
408
366
 
409
- this._options.signal = this.abortController.signal
410
- }
367
+ ExtendError.prototype = ErrorClass.prototype
368
+ mimicFn$1(ExtendError, ErrorClass)
411
369
 
412
- this.request = new globalThis.Request(this._input, this._options)
370
+ return ExtendError
371
+ }
413
372
 
414
- if (this._options.searchParams) {
415
- const textSearchParams =
416
- typeof this._options.searchParams === 'string'
417
- ? this._options.searchParams.replace(/^\?/, '')
418
- : new URLSearchParams(this._options.searchParams).toString()
419
- const searchParams = '?' + textSearchParams
420
- const url = this.request.url.replace(
421
- /(?:\?.*?)?(?=#|$)/,
422
- searchParams
423
- )
373
+ var createExtendError_1 = createExtendError$1
424
374
 
425
- // To provide correct form boundary, Content-Type header should be deleted each time when new Request instantiated from another one
426
- if (
427
- ((supportsFormData &&
428
- this._options.body instanceof globalThis.FormData) ||
429
- this._options.body instanceof URLSearchParams) &&
430
- !(this._options.headers && this._options.headers['content-type'])
431
- ) {
432
- this.request.headers.delete('content-type')
433
- }
375
+ const { inherits } = helpers
376
+ const mimicFn = mimicFn_1
434
377
 
435
- this.request = new globalThis.Request(
436
- new globalThis.Request(url, this.request),
437
- this._options
438
- )
439
- }
378
+ const REGEX_CLASS_NAME = /[^0-9a-zA-Z_$]/
440
379
 
441
- if (this._options.json !== undefined) {
442
- this._options.body = JSON.stringify(this._options.json)
443
- this.request.headers.set('content-type', 'application/json')
444
- this.request = new globalThis.Request(this.request, {
445
- body: this._options.body
446
- })
447
- }
380
+ function createError$1 (className) {
381
+ if (typeof className !== 'string') {
382
+ throw new TypeError('Expected className to be a string')
383
+ }
448
384
 
449
- const fn = async () => {
450
- if (this._options.timeout > maxSafeTimeout) {
451
- throw new RangeError(
452
- `The \`timeout\` option cannot be greater than ${maxSafeTimeout}`
453
- )
454
- }
385
+ if (REGEX_CLASS_NAME.test(className)) {
386
+ throw new Error('className contains invalid characters')
387
+ }
455
388
 
456
- await delay(1)
457
- let response = await this._fetch()
389
+ function ErrorClass () {
390
+ Object.defineProperty(this, 'name', {
391
+ configurable: true,
392
+ value: className,
393
+ writable: true
394
+ })
458
395
 
459
- for (const hook of this._options.hooks.afterResponse) {
460
- // eslint-disable-next-line no-await-in-loop
461
- const modifiedResponse = await hook(
462
- this.request,
463
- this._options,
464
- this._decorateResponse(response.clone())
465
- )
396
+ Error.captureStackTrace(this, this.constructor)
397
+ }
466
398
 
467
- if (modifiedResponse instanceof globalThis.Response) {
468
- response = modifiedResponse
469
- }
470
- }
399
+ inherits(ErrorClass, Error)
400
+ mimicFn(ErrorClass, Error)
401
+ return ErrorClass
402
+ }
471
403
 
472
- this._decorateResponse(response)
404
+ var createError_1 = createError$1
473
405
 
474
- if (!response.ok && this._options.throwHttpErrors) {
475
- throw new HTTPError(response, this.request, this._options)
476
- }
406
+ const createExtendError = createExtendError_1
407
+ const createError = createError_1
477
408
 
478
- // If `onDownloadProgress` is passed, it uses the stream API internally
479
- /* istanbul ignore next */
480
- if (this._options.onDownloadProgress) {
481
- if (typeof this._options.onDownloadProgress !== 'function') {
482
- throw new TypeError(
483
- 'The `onDownloadProgress` option must be a function'
484
- )
485
- }
409
+ const createErrorClass = ErrorClass => (className, props) => {
410
+ const errorClass = createError(className || ErrorClass.name)
411
+ return createExtendError(errorClass, props)
412
+ }
486
413
 
487
- if (!supportsStreams) {
488
- throw new Error(
489
- 'Streams are not supported in your environment. `ReadableStream` is missing.'
490
- )
491
- }
414
+ lib.exports = createErrorClass(Error)
415
+ lib.exports.type = createErrorClass(TypeError)
416
+ lib.exports.range = createErrorClass(RangeError)
417
+ lib.exports.eval = createErrorClass(EvalError)
418
+ lib.exports.syntax = createErrorClass(SyntaxError)
419
+ lib.exports.reference = createErrorClass(ReferenceError)
420
+ lib.exports.uri = createErrorClass(URIError)
492
421
 
493
- return this._stream(
494
- response.clone(),
495
- this._options.onDownloadProgress
496
- )
497
- }
422
+ const ENDPOINT = {
423
+ FREE: 'https://api.microlink.io',
424
+ PRO: 'https://pro.microlink.io'
425
+ }
498
426
 
499
- return response
500
- }
427
+ const isObject = input => input !== null && typeof input === 'object'
501
428
 
502
- const isRetriableMethod = this._options.retry.methods.includes(
503
- this.request.method.toLowerCase()
504
- )
505
- const result = isRetriableMethod ? this._retry(fn) : fn()
429
+ const parseBody = (input, error, url) => {
430
+ try {
431
+ return JSON.parse(input)
432
+ } catch (_) {
433
+ const message = input || error.message
506
434
 
507
- for (const [type, mimeType] of Object.entries(responseTypes)) {
508
- result[type] = async () => {
509
- this.request.headers.set(
510
- 'accept',
511
- this.request.headers.get('accept') || mimeType
512
- )
435
+ return {
436
+ status: 'error',
437
+ data: { url: message },
438
+ more: 'https://microlink.io/efatalclient',
439
+ code: 'EFATALCLIENT',
440
+ message,
441
+ url
442
+ }
443
+ }
444
+ }
513
445
 
514
- const response = (await result).clone()
446
+ const factory$1 = ({
447
+ VERSION,
448
+ MicrolinkError,
449
+ isUrlHttp,
450
+ stringify,
451
+ got,
452
+ flatten
453
+ }) => {
454
+ const assertUrl = (url = '') => {
455
+ if (!isUrlHttp(url)) {
456
+ const message = `The \`url\` as \`${url}\` is not valid. Ensure it has protocol (http or https) and hostname.`
457
+ throw new MicrolinkError({
458
+ status: 'fail',
459
+ data: { url: message },
460
+ more: 'https://microlink.io/docs/api/api-parameters/url',
461
+ code: 'EINVALURLCLIENT',
462
+ message,
463
+ url
464
+ })
465
+ }
466
+ }
515
467
 
516
- if (type === 'json') {
517
- if (response.status === 204) {
518
- return ''
519
- }
468
+ const mapRules = rules => {
469
+ if (!isObject(rules)) return
470
+ const flatRules = flatten(rules)
471
+ return Object.keys(flatRules).reduce(
472
+ (acc, key) => ({ ...acc, [`data.${key}`]: flatRules[key].toString() }),
473
+ {}
474
+ )
475
+ }
520
476
 
521
- if (options.parseJson) {
522
- return options.parseJson(await response.text())
523
- }
524
- }
477
+ const fetchFromApi = async (apiUrl, opts = {}, retryCount = 0) => {
478
+ try {
479
+ const response = await got(apiUrl, opts)
480
+ return opts.responseType === 'buffer'
481
+ ? { body: response.body, response }
482
+ : { ...response.body, response }
483
+ } catch (err) {
484
+ const { response = {} } = err
485
+ const {
486
+ statusCode,
487
+ body: rawBody,
488
+ headers,
489
+ url: uri = apiUrl
490
+ } = response
491
+ const isBuffer = Buffer.isBuffer(rawBody)
525
492
 
526
- return response[type]()
527
- }
528
- }
493
+ const body =
494
+ isObject(rawBody) && !isBuffer
495
+ ? rawBody
496
+ : parseBody(isBuffer ? rawBody.toString() : rawBody, err, uri)
529
497
 
530
- return result
498
+ if (body.code === 'EFATALCLIENT' && retryCount++ < 2) {
499
+ return fetchFromApi(apiUrl, opts, retryCount)
531
500
  }
532
501
 
533
- _calculateRetryDelay (error) {
534
- this._retryCount++
535
-
536
- if (
537
- this._retryCount < this._options.retry.limit &&
538
- !(error instanceof TimeoutError)
539
- ) {
540
- if (error instanceof HTTPError) {
541
- if (
542
- !this._options.retry.statusCodes.includes(error.response.status)
543
- ) {
544
- return 0
545
- }
546
-
547
- const retryAfter = error.response.headers.get('Retry-After')
548
- if (
549
- retryAfter &&
550
- this._options.retry.afterStatusCodes.includes(
551
- error.response.status
552
- )
553
- ) {
554
- let after = Number(retryAfter)
555
- if (Number.isNaN(after)) {
556
- after = Date.parse(retryAfter) - Date.now()
557
- } else {
558
- after *= 1000
559
- }
502
+ throw MicrolinkError({
503
+ ...body,
504
+ message: body.message,
505
+ url: uri,
506
+ statusCode,
507
+ headers
508
+ })
509
+ }
510
+ }
560
511
 
561
- if (
562
- typeof this._options.retry.maxRetryAfter !== 'undefined' &&
563
- after > this._options.retry.maxRetryAfter
564
- ) {
565
- return 0
566
- }
512
+ const getApiUrl = (
513
+ url,
514
+ { data, apiKey, endpoint, retry, cache, ...opts } = {},
515
+ { responseType = 'json', headers: gotHeaders, ...gotOpts } = {}
516
+ ) => {
517
+ const isPro = !!apiKey
518
+ const apiEndpoint = endpoint || ENDPOINT[isPro ? 'PRO' : 'FREE']
567
519
 
568
- return after
569
- }
520
+ const apiUrl = `${apiEndpoint}?${stringify({
521
+ url,
522
+ ...mapRules(data),
523
+ ...flatten(opts)
524
+ })}`
570
525
 
571
- if (error.response.status === 413) {
572
- return 0
573
- }
574
- }
526
+ const headers = isPro
527
+ ? { ...gotHeaders, 'x-api-key': apiKey }
528
+ : { ...gotHeaders }
529
+ return [apiUrl, { ...gotOpts, responseType, cache, retry, headers }]
530
+ }
575
531
 
576
- const BACKOFF_FACTOR = 0.3
577
- return BACKOFF_FACTOR * 2 ** (this._retryCount - 1) * 1000
578
- }
532
+ const createMql = defaultOpts => async (url, opts, gotOpts) => {
533
+ assertUrl(url)
534
+ const [apiUrl, fetchOpts] = getApiUrl(url, opts, {
535
+ ...defaultOpts,
536
+ ...gotOpts
537
+ })
538
+ return fetchFromApi(apiUrl, fetchOpts)
539
+ }
579
540
 
580
- return 0
581
- }
541
+ const mql = createMql()
542
+ mql.MicrolinkError = MicrolinkError
543
+ mql.getApiUrl = getApiUrl
544
+ mql.fetchFromApi = fetchFromApi
545
+ mql.mapRules = mapRules
546
+ mql.version = VERSION
547
+ mql.stream = got.stream
548
+ mql.buffer = createMql({ responseType: 'buffer' })
582
549
 
583
- _decorateResponse (response) {
584
- if (this._options.parseJson) {
585
- response.json = async () => {
586
- return this._options.parseJson(await response.text())
587
- }
588
- }
550
+ return mql
551
+ }
589
552
 
590
- return response
591
- }
553
+ var factory_1 = factory$1
592
554
 
593
- async _retry (fn) {
594
- try {
595
- return await fn()
596
- } catch (error) {
597
- const ms = Math.min(
598
- this._calculateRetryDelay(error),
599
- maxSafeTimeout
600
- )
601
- if (ms !== 0 && this._retryCount > 0) {
602
- await delay(ms)
555
+ var ky$1 = { exports: {} }
603
556
 
604
- for (const hook of this._options.hooks.beforeRetry) {
605
- // eslint-disable-next-line no-await-in-loop
606
- const hookResult = await hook({
607
- request: this.request,
608
- options: this._options,
609
- error,
610
- retryCount: this._retryCount
611
- })
557
+ ;(function (module, exports) {
558
+ ;(function (global, factory) {
559
+ module.exports = factory()
560
+ })(commonjsGlobal, function () {
561
+ /*! MIT License © Sindre Sorhus */
612
562
 
613
- // If `stop` is returned from the hook, the retry process is stopped
614
- if (hookResult === stop) {
615
- return
616
- }
617
- }
563
+ const isObject = value => value !== null && typeof value === 'object'
564
+ const supportsAbortController =
565
+ typeof globalThis.AbortController === 'function'
566
+ const supportsStreams = typeof globalThis.ReadableStream === 'function'
567
+ const supportsFormData = typeof globalThis.FormData === 'function'
618
568
 
619
- return this._retry(fn)
620
- }
569
+ const mergeHeaders = (source1, source2) => {
570
+ const result = new globalThis.Headers(source1 || {})
571
+ const isHeadersInstance = source2 instanceof globalThis.Headers
572
+ const source = new globalThis.Headers(source2 || {})
621
573
 
622
- if (this._options.throwHttpErrors) {
623
- throw error
624
- }
574
+ for (const [key, value] of source) {
575
+ if (
576
+ (isHeadersInstance && value === 'undefined') ||
577
+ value === undefined
578
+ ) {
579
+ result.delete(key)
580
+ } else {
581
+ result.set(key, value)
625
582
  }
626
583
  }
627
584
 
628
- async _fetch () {
629
- for (const hook of this._options.hooks.beforeRequest) {
630
- // eslint-disable-next-line no-await-in-loop
631
- const result = await hook(this.request, this._options)
585
+ return result
586
+ }
632
587
 
633
- if (result instanceof Request) {
634
- this.request = result
635
- break
588
+ const deepMerge = (...sources) => {
589
+ let returnValue = {}
590
+ let headers = {}
591
+
592
+ for (const source of sources) {
593
+ if (Array.isArray(source)) {
594
+ if (!Array.isArray(returnValue)) {
595
+ returnValue = []
636
596
  }
637
597
 
638
- if (result instanceof Response) {
639
- return result
598
+ returnValue = [...returnValue, ...source]
599
+ } else if (isObject(source)) {
600
+ for (let [key, value] of Object.entries(source)) {
601
+ if (isObject(value) && key in returnValue) {
602
+ value = deepMerge(returnValue[key], value)
603
+ }
604
+
605
+ returnValue = { ...returnValue, [key]: value }
640
606
  }
641
- }
642
607
 
643
- if (this._options.timeout === false) {
644
- return this._options.fetch(this.request.clone())
608
+ if (isObject(source.headers)) {
609
+ headers = mergeHeaders(headers, source.headers)
610
+ }
645
611
  }
646
612
 
647
- return timeout(
648
- this.request.clone(),
649
- this.abortController,
650
- this._options
651
- )
613
+ returnValue.headers = headers
652
614
  }
653
615
 
654
- /* istanbul ignore next */
655
- _stream (response, onDownloadProgress) {
656
- const totalBytes = Number(response.headers.get('content-length')) || 0
657
- let transferredBytes = 0
616
+ return returnValue
617
+ }
618
+
619
+ const requestMethods = ['get', 'post', 'put', 'patch', 'head', 'delete']
658
620
 
659
- return new globalThis.Response(
660
- new globalThis.ReadableStream({
661
- async start (controller) {
662
- const reader = response.body.getReader()
621
+ const responseTypes = {
622
+ json: 'application/json',
623
+ text: 'text/*',
624
+ formData: 'multipart/form-data',
625
+ arrayBuffer: '*/*',
626
+ blob: '*/*'
627
+ }
663
628
 
664
- if (onDownloadProgress) {
665
- onDownloadProgress(
666
- { percent: 0, transferredBytes: 0, totalBytes },
667
- new Uint8Array()
668
- )
669
- }
629
+ const retryMethods = ['get', 'put', 'head', 'delete', 'options', 'trace']
670
630
 
671
- async function read () {
672
- const { done, value } = await reader.read()
673
- if (done) {
674
- controller.close()
675
- return
676
- }
631
+ const retryStatusCodes = [408, 413, 429, 500, 502, 503, 504]
677
632
 
678
- if (onDownloadProgress) {
679
- transferredBytes += value.byteLength
680
- const percent =
681
- totalBytes === 0 ? 0 : transferredBytes / totalBytes
682
- onDownloadProgress(
683
- { percent, transferredBytes, totalBytes },
684
- value
685
- )
686
- }
633
+ const retryAfterStatusCodes = [413, 429, 503]
687
634
 
688
- controller.enqueue(value)
689
- await read()
690
- }
635
+ const stop = Symbol('stop')
691
636
 
692
- await read()
693
- }
694
- })
637
+ class HTTPError extends Error {
638
+ constructor (response, request, options) {
639
+ // Set the message to the status text, such as Unauthorized,
640
+ // with some fallbacks. This message should never be undefined.
641
+ super(
642
+ response.statusText ||
643
+ String(
644
+ response.status === 0 || response.status
645
+ ? response.status
646
+ : 'Unknown response error'
647
+ )
695
648
  )
649
+ this.name = 'HTTPError'
650
+ this.response = response
651
+ this.request = request
652
+ this.options = options
696
653
  }
697
654
  }
698
655
 
699
- const validateAndMerge = (...sources) => {
700
- for (const source of sources) {
701
- if (
702
- (!isObject(source) || Array.isArray(source)) &&
703
- typeof source !== 'undefined'
704
- ) {
705
- throw new TypeError('The `options` argument must be an object')
706
- }
656
+ class TimeoutError extends Error {
657
+ constructor (request) {
658
+ super('Request timed out')
659
+ this.name = 'TimeoutError'
660
+ this.request = request
707
661
  }
708
-
709
- return deepMerge({}, ...sources)
710
662
  }
711
663
 
712
- const createInstance = defaults => {
713
- const ky = (input, options) =>
714
- new Ky(input, validateAndMerge(defaults, options))
664
+ const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
715
665
 
716
- for (const method of requestMethods) {
717
- ky[method] = (input, options) =>
718
- new Ky(input, validateAndMerge(defaults, options, { method }))
719
- }
666
+ // `Promise.race()` workaround (#91)
667
+ const timeout = (request, abortController, options) =>
668
+ new Promise((resolve, reject) => {
669
+ const timeoutID = setTimeout(() => {
670
+ if (abortController) {
671
+ abortController.abort()
672
+ }
720
673
 
721
- ky.HTTPError = HTTPError
722
- ky.TimeoutError = TimeoutError
723
- ky.create = newDefaults => createInstance(validateAndMerge(newDefaults))
724
- ky.extend = newDefaults =>
725
- createInstance(validateAndMerge(defaults, newDefaults))
726
- ky.stop = stop
674
+ reject(new TimeoutError(request))
675
+ }, options.timeout)
727
676
 
728
- return ky
729
- }
677
+ /* eslint-disable promise/prefer-await-to-then */
678
+ options
679
+ .fetch(request)
680
+ .then(resolve)
681
+ .catch(reject)
682
+ .then(() => {
683
+ clearTimeout(timeoutID)
684
+ })
685
+ /* eslint-enable promise/prefer-await-to-then */
686
+ })
730
687
 
731
- const ky = createInstance()
688
+ const normalizeRequestMethod = input =>
689
+ requestMethods.includes(input) ? input.toUpperCase() : input
732
690
 
733
- return ky
734
- })
735
- })(ky$1)
691
+ const defaultRetryOptions = {
692
+ limit: 2,
693
+ methods: retryMethods,
694
+ statusCodes: retryStatusCodes,
695
+ afterStatusCodes: retryAfterStatusCodes
696
+ }
736
697
 
737
- var lib = { exports: {} }
698
+ const normalizeRetryOptions = (retry = {}) => {
699
+ if (typeof retry === 'number') {
700
+ return {
701
+ ...defaultRetryOptions,
702
+ limit: retry
703
+ }
704
+ }
738
705
 
739
- var _nodeResolve_empty = {}
706
+ if (retry.methods && !Array.isArray(retry.methods)) {
707
+ throw new Error('retry.methods must be an array')
708
+ }
740
709
 
741
- var _nodeResolve_empty$1 = /*#__PURE__*/ Object.freeze({
742
- __proto__: null,
743
- default: _nodeResolve_empty
744
- })
710
+ if (retry.statusCodes && !Array.isArray(retry.statusCodes)) {
711
+ throw new Error('retry.statusCodes must be an array')
712
+ }
745
713
 
746
- var require$$0 = /*@__PURE__*/ getAugmentedNamespace(_nodeResolve_empty$1)
714
+ return {
715
+ ...defaultRetryOptions,
716
+ ...retry,
717
+ afterStatusCodes: retryAfterStatusCodes
718
+ }
719
+ }
747
720
 
748
- const os = require$$0
721
+ // The maximum value of a 32bit int (see issue #117)
722
+ const maxSafeTimeout = 2147483647
749
723
 
750
- const extractPathRegex = /\s+at.*(?:\(|\s)(.*)\)?/
751
- const pathRegex = /^(?:(?:(?:node|(?:internal\/[\w/]*|.*node_modules\/(?:babel-polyfill|pirates)\/.*)?\w+)\.js:\d+:\d+)|native)/
752
- const homeDir = typeof os.homedir === 'undefined' ? '' : os.homedir()
724
+ class Ky {
725
+ constructor (input, options = {}) {
726
+ this._retryCount = 0
727
+ this._input = input
728
+ this._options = {
729
+ // TODO: credentials can be removed when the spec change is implemented in all browsers. Context: https://www.chromestatus.com/feature/4539473312350208
730
+ credentials: this._input.credentials || 'same-origin',
731
+ ...options,
732
+ headers: mergeHeaders(this._input.headers, options.headers),
733
+ hooks: deepMerge(
734
+ {
735
+ beforeRequest: [],
736
+ beforeRetry: [],
737
+ afterResponse: []
738
+ },
739
+ options.hooks
740
+ ),
741
+ method: normalizeRequestMethod(
742
+ options.method || this._input.method
743
+ ),
744
+ prefixUrl: String(options.prefixUrl || ''),
745
+ retry: normalizeRetryOptions(options.retry),
746
+ throwHttpErrors: options.throwHttpErrors !== false,
747
+ timeout:
748
+ typeof options.timeout === 'undefined' ? 10000 : options.timeout,
749
+ fetch: options.fetch || globalThis.fetch.bind(globalThis)
750
+ }
753
751
 
754
- var cleanStack$1 = (stack, options) => {
755
- options = Object.assign({ pretty: false }, options)
752
+ if (
753
+ typeof this._input !== 'string' &&
754
+ !(
755
+ this._input instanceof URL ||
756
+ this._input instanceof globalThis.Request
757
+ )
758
+ ) {
759
+ throw new TypeError('`input` must be a string, URL, or Request')
760
+ }
756
761
 
757
- return stack
758
- .replace(/\\/g, '/')
759
- .split('\n')
760
- .filter(line => {
761
- const pathMatches = line.match(extractPathRegex)
762
- if (pathMatches === null || !pathMatches[1]) {
763
- return true
764
- }
762
+ if (this._options.prefixUrl && typeof this._input === 'string') {
763
+ if (this._input.startsWith('/')) {
764
+ throw new Error(
765
+ '`input` must not begin with a slash when using `prefixUrl`'
766
+ )
767
+ }
765
768
 
766
- const match = pathMatches[1]
769
+ if (!this._options.prefixUrl.endsWith('/')) {
770
+ this._options.prefixUrl += '/'
771
+ }
767
772
 
768
- // Electron
769
- if (
770
- match.includes('.app/Contents/Resources/electron.asar') ||
771
- match.includes('.app/Contents/Resources/default_app.asar')
772
- ) {
773
- return false
774
- }
773
+ this._input = this._options.prefixUrl + this._input
774
+ }
775
775
 
776
- return !pathRegex.test(match)
777
- })
778
- .filter(line => line.trim() !== '')
779
- .map(line => {
780
- if (options.pretty) {
781
- return line.replace(extractPathRegex, (m, p1) =>
782
- m.replace(p1, p1.replace(homeDir, '~'))
783
- )
784
- }
776
+ if (supportsAbortController) {
777
+ this.abortController = new globalThis.AbortController()
778
+ if (this._options.signal) {
779
+ this._options.signal.addEventListener('abort', () => {
780
+ this.abortController.abort()
781
+ })
782
+ }
785
783
 
786
- return line
787
- })
788
- .join('\n')
789
- }
784
+ this._options.signal = this.abortController.signal
785
+ }
790
786
 
791
- const copyProperty = (to, from, property, ignoreNonConfigurable) => {
792
- // `Function#length` should reflect the parameters of `to` not `from` since we keep its body.
793
- // `Function#prototype` is non-writable and non-configurable so can never be modified.
794
- if (property === 'length' || property === 'prototype') {
795
- return
796
- }
787
+ this.request = new globalThis.Request(this._input, this._options)
797
788
 
798
- const toDescriptor = Object.getOwnPropertyDescriptor(to, property)
799
- const fromDescriptor = Object.getOwnPropertyDescriptor(from, property)
789
+ if (this._options.searchParams) {
790
+ const textSearchParams =
791
+ typeof this._options.searchParams === 'string'
792
+ ? this._options.searchParams.replace(/^\?/, '')
793
+ : new URLSearchParams(this._options.searchParams).toString()
794
+ const searchParams = '?' + textSearchParams
795
+ const url = this.request.url.replace(
796
+ /(?:\?.*?)?(?=#|$)/,
797
+ searchParams
798
+ )
800
799
 
801
- if (
802
- !canCopyProperty(toDescriptor, fromDescriptor) &&
803
- ignoreNonConfigurable
804
- ) {
805
- return
806
- }
800
+ // To provide correct form boundary, Content-Type header should be deleted each time when new Request instantiated from another one
801
+ if (
802
+ ((supportsFormData &&
803
+ this._options.body instanceof globalThis.FormData) ||
804
+ this._options.body instanceof URLSearchParams) &&
805
+ !(this._options.headers && this._options.headers['content-type'])
806
+ ) {
807
+ this.request.headers.delete('content-type')
808
+ }
807
809
 
808
- Object.defineProperty(to, property, fromDescriptor)
809
- }
810
+ this.request = new globalThis.Request(
811
+ new globalThis.Request(url, this.request),
812
+ this._options
813
+ )
814
+ }
810
815
 
811
- // `Object.defineProperty()` throws if the property exists, is not configurable and either:
812
- // - one its descriptors is changed
813
- // - it is non-writable and its value is changed
814
- const canCopyProperty = function (toDescriptor, fromDescriptor) {
815
- return (
816
- toDescriptor === undefined ||
817
- toDescriptor.configurable ||
818
- (toDescriptor.writable === fromDescriptor.writable &&
819
- toDescriptor.enumerable === fromDescriptor.enumerable &&
820
- toDescriptor.configurable === fromDescriptor.configurable &&
821
- (toDescriptor.writable || toDescriptor.value === fromDescriptor.value))
822
- )
823
- }
816
+ if (this._options.json !== undefined) {
817
+ this._options.body = JSON.stringify(this._options.json)
818
+ this.request.headers.set('content-type', 'application/json')
819
+ this.request = new globalThis.Request(this.request, {
820
+ body: this._options.body
821
+ })
822
+ }
824
823
 
825
- const changePrototype = (to, from) => {
826
- const fromPrototype = Object.getPrototypeOf(from)
827
- if (fromPrototype === Object.getPrototypeOf(to)) {
828
- return
829
- }
824
+ const fn = async () => {
825
+ if (this._options.timeout > maxSafeTimeout) {
826
+ throw new RangeError(
827
+ `The \`timeout\` option cannot be greater than ${maxSafeTimeout}`
828
+ )
829
+ }
830
830
 
831
- Object.setPrototypeOf(to, fromPrototype)
832
- }
831
+ await delay(1)
832
+ let response = await this._fetch()
833
833
 
834
- const wrappedToString = (withName, fromBody) =>
835
- `/* Wrapped ${withName}*/\n${fromBody}`
834
+ for (const hook of this._options.hooks.afterResponse) {
835
+ // eslint-disable-next-line no-await-in-loop
836
+ const modifiedResponse = await hook(
837
+ this.request,
838
+ this._options,
839
+ this._decorateResponse(response.clone())
840
+ )
836
841
 
837
- const toStringDescriptor = Object.getOwnPropertyDescriptor(
838
- Function.prototype,
839
- 'toString'
840
- )
841
- const toStringName = Object.getOwnPropertyDescriptor(
842
- Function.prototype.toString,
843
- 'name'
844
- )
842
+ if (modifiedResponse instanceof globalThis.Response) {
843
+ response = modifiedResponse
844
+ }
845
+ }
845
846
 
846
- // We call `from.toString()` early (not lazily) to ensure `from` can be garbage collected.
847
- // We use `bind()` instead of a closure for the same reason.
848
- // Calling `from.toString()` early also allows caching it in case `to.toString()` is called several times.
849
- const changeToString = (to, from, name) => {
850
- const withName = name === '' ? '' : `with ${name.trim()}() `
851
- const newToString = wrappedToString.bind(null, withName, from.toString())
852
- // Ensure `to.toString.toString` is non-enumerable and has the same `same`
853
- Object.defineProperty(newToString, 'name', toStringName)
854
- Object.defineProperty(to, 'toString', {
855
- ...toStringDescriptor,
856
- value: newToString
857
- })
858
- }
847
+ this._decorateResponse(response)
859
848
 
860
- const mimicFn$2 = (to, from, { ignoreNonConfigurable = false } = {}) => {
861
- const { name } = to
849
+ if (!response.ok && this._options.throwHttpErrors) {
850
+ throw new HTTPError(response, this.request, this._options)
851
+ }
862
852
 
863
- for (const property of Reflect.ownKeys(from)) {
864
- copyProperty(to, from, property, ignoreNonConfigurable)
865
- }
853
+ // If `onDownloadProgress` is passed, it uses the stream API internally
854
+ /* istanbul ignore next */
855
+ if (this._options.onDownloadProgress) {
856
+ if (typeof this._options.onDownloadProgress !== 'function') {
857
+ throw new TypeError(
858
+ 'The `onDownloadProgress` option must be a function'
859
+ )
860
+ }
866
861
 
867
- changePrototype(to, from)
868
- changeToString(to, from, name)
862
+ if (!supportsStreams) {
863
+ throw new Error(
864
+ 'Streams are not supported in your environment. `ReadableStream` is missing.'
865
+ )
866
+ }
869
867
 
870
- return to
871
- }
868
+ return this._stream(
869
+ response.clone(),
870
+ this._options.onDownloadProgress
871
+ )
872
+ }
872
873
 
873
- var mimicFn_1 = mimicFn$2
874
+ return response
875
+ }
874
876
 
875
- var helpers = {
876
- isFunction: obj => typeof obj === 'function',
877
- isString: obj => typeof obj === 'string',
878
- composeErrorMessage: (code, description) => `${code}, ${description}`,
879
- inherits: (ctor, superCtor) => {
880
- ctor.super_ = superCtor
881
- ctor.prototype = Object.create(superCtor.prototype, {
882
- constructor: {
883
- value: ctor,
884
- enumerable: false,
885
- writable: true,
886
- configurable: true
887
- }
888
- })
889
- }
890
- }
877
+ const isRetriableMethod = this._options.retry.methods.includes(
878
+ this.request.method.toLowerCase()
879
+ )
880
+ const result = isRetriableMethod ? this._retry(fn) : fn()
891
881
 
892
- const { isFunction, composeErrorMessage } = helpers
882
+ for (const [type, mimeType] of Object.entries(responseTypes)) {
883
+ result[type] = async () => {
884
+ this.request.headers.set(
885
+ 'accept',
886
+ this.request.headers.get('accept') || mimeType
887
+ )
893
888
 
894
- function interfaceObject (error, ...props) {
895
- Object.assign(error, ...props)
889
+ const response = (await result).clone()
896
890
 
897
- error.description = isFunction(error.message)
898
- ? error.message(error)
899
- : error.message
891
+ if (type === 'json') {
892
+ if (response.status === 204) {
893
+ return ''
894
+ }
900
895
 
901
- error.message = error.code
902
- ? composeErrorMessage(error.code, error.description)
903
- : error.description
904
- }
896
+ if (options.parseJson) {
897
+ return options.parseJson(await response.text())
898
+ }
899
+ }
905
900
 
906
- var addErrorProps$1 = interfaceObject
901
+ return response[type]()
902
+ }
903
+ }
907
904
 
908
- const cleanStack = cleanStack$1
909
- const mimicFn$1 = mimicFn_1
905
+ return result
906
+ }
910
907
 
911
- const addErrorProps = addErrorProps$1
912
- const { isString } = helpers
908
+ _calculateRetryDelay (error) {
909
+ this._retryCount++
913
910
 
914
- function createExtendError$1 (ErrorClass, classProps) {
915
- function ExtendError (props) {
916
- const error = new ErrorClass()
917
- const errorProps = isString(props) ? { message: props } : props
918
- addErrorProps(error, classProps, errorProps)
911
+ if (
912
+ this._retryCount < this._options.retry.limit &&
913
+ !(error instanceof TimeoutError)
914
+ ) {
915
+ if (error instanceof HTTPError) {
916
+ if (
917
+ !this._options.retry.statusCodes.includes(error.response.status)
918
+ ) {
919
+ return 0
920
+ }
919
921
 
920
- error.stack = cleanStack(error.stack)
921
- return error
922
- }
922
+ const retryAfter = error.response.headers.get('Retry-After')
923
+ if (
924
+ retryAfter &&
925
+ this._options.retry.afterStatusCodes.includes(
926
+ error.response.status
927
+ )
928
+ ) {
929
+ let after = Number(retryAfter)
930
+ if (Number.isNaN(after)) {
931
+ after = Date.parse(retryAfter) - Date.now()
932
+ } else {
933
+ after *= 1000
934
+ }
923
935
 
924
- ExtendError.prototype = ErrorClass.prototype
925
- mimicFn$1(ExtendError, ErrorClass)
936
+ if (
937
+ typeof this._options.retry.maxRetryAfter !== 'undefined' &&
938
+ after > this._options.retry.maxRetryAfter
939
+ ) {
940
+ return 0
941
+ }
926
942
 
927
- return ExtendError
928
- }
943
+ return after
944
+ }
929
945
 
930
- var createExtendError_1 = createExtendError$1
946
+ if (error.response.status === 413) {
947
+ return 0
948
+ }
949
+ }
931
950
 
932
- const { inherits } = helpers
933
- const mimicFn = mimicFn_1
951
+ const BACKOFF_FACTOR = 0.3
952
+ return BACKOFF_FACTOR * 2 ** (this._retryCount - 1) * 1000
953
+ }
934
954
 
935
- const REGEX_CLASS_NAME = /[^0-9a-zA-Z_$]/
955
+ return 0
956
+ }
936
957
 
937
- function createError$1 (className) {
938
- if (typeof className !== 'string') {
939
- throw new TypeError('Expected className to be a string')
940
- }
958
+ _decorateResponse (response) {
959
+ if (this._options.parseJson) {
960
+ response.json = async () => {
961
+ return this._options.parseJson(await response.text())
962
+ }
963
+ }
941
964
 
942
- if (REGEX_CLASS_NAME.test(className)) {
943
- throw new Error('className contains invalid characters')
944
- }
965
+ return response
966
+ }
945
967
 
946
- function ErrorClass () {
947
- Object.defineProperty(this, 'name', {
948
- configurable: true,
949
- value: className,
950
- writable: true
951
- })
968
+ async _retry (fn) {
969
+ try {
970
+ return await fn()
971
+ } catch (error) {
972
+ const ms = Math.min(
973
+ this._calculateRetryDelay(error),
974
+ maxSafeTimeout
975
+ )
976
+ if (ms !== 0 && this._retryCount > 0) {
977
+ await delay(ms)
952
978
 
953
- Error.captureStackTrace(this, this.constructor)
954
- }
979
+ for (const hook of this._options.hooks.beforeRetry) {
980
+ // eslint-disable-next-line no-await-in-loop
981
+ const hookResult = await hook({
982
+ request: this.request,
983
+ options: this._options,
984
+ error,
985
+ retryCount: this._retryCount
986
+ })
955
987
 
956
- inherits(ErrorClass, Error)
957
- mimicFn(ErrorClass, Error)
958
- return ErrorClass
959
- }
988
+ // If `stop` is returned from the hook, the retry process is stopped
989
+ if (hookResult === stop) {
990
+ return
991
+ }
992
+ }
960
993
 
961
- var createError_1 = createError$1
994
+ return this._retry(fn)
995
+ }
962
996
 
963
- const createExtendError = createExtendError_1
964
- const createError = createError_1
997
+ if (this._options.throwHttpErrors) {
998
+ throw error
999
+ }
1000
+ }
1001
+ }
965
1002
 
966
- const createErrorClass = ErrorClass => (className, props) => {
967
- const errorClass = createError(className || ErrorClass.name)
968
- return createExtendError(errorClass, props)
969
- }
1003
+ async _fetch () {
1004
+ for (const hook of this._options.hooks.beforeRequest) {
1005
+ // eslint-disable-next-line no-await-in-loop
1006
+ const result = await hook(this.request, this._options)
970
1007
 
971
- lib.exports = createErrorClass(Error)
972
- lib.exports.type = createErrorClass(TypeError)
973
- lib.exports.range = createErrorClass(RangeError)
974
- lib.exports.eval = createErrorClass(EvalError)
975
- lib.exports.syntax = createErrorClass(SyntaxError)
976
- lib.exports.reference = createErrorClass(ReferenceError)
977
- lib.exports.uri = createErrorClass(URIError)
1008
+ if (result instanceof Request) {
1009
+ this.request = result
1010
+ break
1011
+ }
978
1012
 
979
- const ENDPOINT = {
980
- FREE: 'https://api.microlink.io',
981
- PRO: 'https://pro.microlink.io'
982
- }
1013
+ if (result instanceof Response) {
1014
+ return result
1015
+ }
1016
+ }
983
1017
 
984
- const isObject = input => typeof input === 'object'
1018
+ if (this._options.timeout === false) {
1019
+ return this._options.fetch(this.request.clone())
1020
+ }
985
1021
 
986
- const pickBy = obj => {
987
- Object.keys(obj).forEach(key => obj[key] == null && delete obj[key])
988
- return obj
989
- }
1022
+ return timeout(
1023
+ this.request.clone(),
1024
+ this.abortController,
1025
+ this._options
1026
+ )
1027
+ }
990
1028
 
991
- const parseBody = (input, error, url) => {
992
- try {
993
- return JSON.parse(input)
994
- } catch (_) {
995
- const message = input || error.message
1029
+ /* istanbul ignore next */
1030
+ _stream (response, onDownloadProgress) {
1031
+ const totalBytes = Number(response.headers.get('content-length')) || 0
1032
+ let transferredBytes = 0
996
1033
 
997
- return {
998
- status: 'error',
999
- data: { url: message },
1000
- more: 'https://microlink.io/efatal',
1001
- code: 'EFATAL',
1002
- message,
1003
- url
1004
- }
1005
- }
1006
- }
1034
+ return new globalThis.Response(
1035
+ new globalThis.ReadableStream({
1036
+ async start (controller) {
1037
+ const reader = response.body.getReader()
1007
1038
 
1008
- const factory$1 = ({
1009
- VERSION,
1010
- MicrolinkError,
1011
- isUrlHttp,
1012
- stringify,
1013
- got,
1014
- flatten
1015
- }) => {
1016
- const assertUrl = (url = '') => {
1017
- if (!isUrlHttp(url)) {
1018
- const message = `The \`url\` as \`${url}\` is not valid. Ensure it has protocol (http or https) and hostname.`
1019
- throw new MicrolinkError({
1020
- status: 'fail',
1021
- data: { url: message },
1022
- more: 'https://microlink.io/docs/api/api-parameters/url',
1023
- code: 'EINVALURLCLIENT',
1024
- message,
1025
- url
1026
- })
1027
- }
1028
- }
1039
+ if (onDownloadProgress) {
1040
+ onDownloadProgress(
1041
+ { percent: 0, transferredBytes: 0, totalBytes },
1042
+ new Uint8Array()
1043
+ )
1044
+ }
1029
1045
 
1030
- const mapRules = rules => {
1031
- if (!isObject(rules)) return
1032
- const flatRules = flatten(rules)
1033
- return Object.keys(flatRules).reduce(
1034
- (acc, key) => ({ ...acc, [`data.${key}`]: flatRules[key] }),
1035
- {}
1036
- )
1037
- }
1046
+ async function read () {
1047
+ const { done, value } = await reader.read()
1048
+ if (done) {
1049
+ controller.close()
1050
+ return
1051
+ }
1038
1052
 
1039
- const fetchFromApi = async (apiUrl, opts = {}) => {
1040
- try {
1041
- const response = await got(apiUrl, opts)
1042
- return opts.responseType === 'buffer'
1043
- ? { body: response.body, response }
1044
- : { ...response.body, response }
1045
- } catch (err) {
1046
- const { response = {} } = err
1047
- const {
1048
- statusCode,
1049
- body: rawBody,
1050
- headers,
1051
- url: uri = apiUrl
1052
- } = response
1053
+ if (onDownloadProgress) {
1054
+ transferredBytes += value.byteLength
1055
+ const percent =
1056
+ totalBytes === 0 ? 0 : transferredBytes / totalBytes
1057
+ onDownloadProgress(
1058
+ { percent, transferredBytes, totalBytes },
1059
+ value
1060
+ )
1061
+ }
1053
1062
 
1054
- const body =
1055
- isObject(rawBody) && !Buffer.isBuffer(rawBody)
1056
- ? rawBody
1057
- : parseBody(rawBody, err, uri)
1063
+ controller.enqueue(value)
1064
+ await read()
1065
+ }
1058
1066
 
1059
- throw MicrolinkError({
1060
- ...body,
1061
- message: body.message,
1062
- url: uri,
1063
- statusCode,
1064
- headers
1065
- })
1067
+ await read()
1068
+ }
1069
+ })
1070
+ )
1071
+ }
1066
1072
  }
1067
- }
1068
1073
 
1069
- const getApiUrl = (
1070
- url,
1071
- { data, apiKey, endpoint, retry, cache, ...opts } = {},
1072
- { responseType = 'json', headers: gotHeaders, ...gotOpts } = {}
1073
- ) => {
1074
- const isPro = !!apiKey
1075
- const apiEndpoint = endpoint || ENDPOINT[isPro ? 'PRO' : 'FREE']
1074
+ const validateAndMerge = (...sources) => {
1075
+ for (const source of sources) {
1076
+ if (
1077
+ (!isObject(source) || Array.isArray(source)) &&
1078
+ typeof source !== 'undefined'
1079
+ ) {
1080
+ throw new TypeError('The `options` argument must be an object')
1081
+ }
1082
+ }
1076
1083
 
1077
- const apiUrl = `${apiEndpoint}?${stringify({
1078
- url,
1079
- ...mapRules(data),
1080
- ...flatten(pickBy(opts))
1081
- })}`
1084
+ return deepMerge({}, ...sources)
1085
+ }
1082
1086
 
1083
- const headers = isPro
1084
- ? { ...gotHeaders, 'x-api-key': apiKey }
1085
- : { ...gotHeaders }
1086
- return [apiUrl, { ...gotOpts, responseType, cache, retry, headers }]
1087
- }
1087
+ const createInstance = defaults => {
1088
+ const ky = (input, options) =>
1089
+ new Ky(input, validateAndMerge(defaults, options))
1088
1090
 
1089
- const createMql = defaultOpts => async (url, opts, gotOpts) => {
1090
- assertUrl(url)
1091
- const [apiUrl, fetchOpts] = getApiUrl(url, opts, {
1092
- ...defaultOpts,
1093
- ...gotOpts
1094
- })
1095
- return fetchFromApi(apiUrl, fetchOpts)
1096
- }
1091
+ for (const method of requestMethods) {
1092
+ ky[method] = (input, options) =>
1093
+ new Ky(input, validateAndMerge(defaults, options, { method }))
1094
+ }
1097
1095
 
1098
- const mql = createMql()
1099
- mql.MicrolinkError = MicrolinkError
1100
- mql.getApiUrl = getApiUrl
1101
- mql.fetchFromApi = fetchFromApi
1102
- mql.mapRules = mapRules
1103
- mql.version = VERSION
1104
- mql.stream = got.stream
1105
- mql.buffer = createMql({ responseType: 'buffer' })
1096
+ ky.HTTPError = HTTPError
1097
+ ky.TimeoutError = TimeoutError
1098
+ ky.create = newDefaults => createInstance(validateAndMerge(newDefaults))
1099
+ ky.extend = newDefaults =>
1100
+ createInstance(validateAndMerge(defaults, newDefaults))
1101
+ ky.stop = stop
1106
1102
 
1107
- return mql
1108
- }
1103
+ return ky
1104
+ }
1109
1105
 
1110
- var factory_1 = factory$1
1106
+ const ky = createInstance()
1107
+
1108
+ return ky
1109
+ })
1110
+ })(ky$1)
1111
1111
 
1112
1112
  const isUrlHttp = lightweight
1113
1113
  const { flattie: flatten } = dist
1114
1114
  const { encode: stringify } = require$$2
1115
- const ky = ky$1.exports
1116
1115
  const whoops = lib.exports
1117
1116
 
1118
1117
  const factory = factory_1
1118
+ const ky = ky$1.exports
1119
1119
 
1120
1120
  const MicrolinkError = whoops('MicrolinkError')
1121
1121
 
@@ -1153,13 +1153,13 @@
1153
1153
  stringify,
1154
1154
  got,
1155
1155
  flatten,
1156
- VERSION: '0.9.3'
1156
+ VERSION: '0.10.13'
1157
1157
  })
1158
1158
 
1159
1159
  var browser = factory_1$1({
1160
1160
  mql: browser$1,
1161
1161
  toCompress: code => code.toString(),
1162
- VERSION: '0.1.1'
1162
+ VERSION: '0.1.2'
1163
1163
  })
1164
1164
 
1165
1165
  return browser