@microlink/mql 0.15.1 → 0.16.1

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.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@microlink/mql",
3
3
  "description": "Microlink Query Language. The official HTTP client to interact with Microlink API for Node.js, browsers & Deno.",
4
4
  "homepage": "https://microlink.io/mql",
5
- "version": "0.15.1",
5
+ "version": "0.16.1",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {
8
8
  "types": "./dist/index.d.ts",
@@ -49,12 +49,11 @@
49
49
  ],
50
50
  "dependencies": {
51
51
  "flattie": "~1.1.1",
52
- "ky": "~1.14.3"
52
+ "ky": "~2.0.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@commitlint/cli": "latest",
56
56
  "@commitlint/config-conventional": "latest",
57
- "@ksmithut/prettier-standard": "latest",
58
57
  "@rollup/plugin-commonjs": "latest",
59
58
  "@rollup/plugin-node-resolve": "latest",
60
59
  "@rollup/plugin-replace": "latest",
@@ -76,12 +75,11 @@
76
75
  "tsd": "latest"
77
76
  },
78
77
  "engines": {
79
- "node": ">= 18"
78
+ "node": ">= 22"
80
79
  },
81
80
  "files": [
82
81
  "dist",
83
82
  "src/constants.js",
84
- "src/factory.js",
85
83
  "src/index.js",
86
84
  "src/main.mjs"
87
85
  ],
@@ -121,7 +119,7 @@
121
119
  },
122
120
  "nano-staged": {
123
121
  "*.js": [
124
- "prettier-standard",
122
+ "npx -y @kikobeats/prettier-standard",
125
123
  "standard --fix"
126
124
  ],
127
125
  "*.md": [
package/src/constants.js CHANGED
@@ -6,7 +6,13 @@ module.exports = {
6
6
  VERSION,
7
7
  USER_AGENT: `mql/${VERSION}`,
8
8
  /**
9
- * Retry status codes, excluding 429 (too many requests).
9
+ * Based on Ky default retry status codes, excluding 429:
10
+ * https://github.com/sindresorhus/ky/blob/main/source/core/constants.ts
10
11
  */
11
- RETRY_STATUS_CODES: [408, 413, 500, 502, 503, 504, 521, 522, 524]
12
+ RETRY_STATUS_CODES: [408, 413, 500, 502, 503, 504, 521, 522, 524],
13
+ /**
14
+ * Based on Ky default Retry-After status codes, excluding 429:
15
+ * https://github.com/sindresorhus/ky/blob/main/source/core/constants.ts
16
+ */
17
+ RETRY_AFTER_STATUS_CODES: [413, 503]
12
18
  }
package/src/index.js CHANGED
@@ -3,14 +3,59 @@
3
3
  const { flattie: flatten } = require('flattie')
4
4
  const { default: ky } = require('ky')
5
5
 
6
- const { VERSION, USER_AGENT, RETRY_STATUS_CODES } = require('./constants')
6
+ const {
7
+ VERSION,
8
+ USER_AGENT,
9
+ RETRY_STATUS_CODES,
10
+ RETRY_AFTER_STATUS_CODES
11
+ } = require('./constants')
12
+
13
+ const ENDPOINT = {
14
+ FREE: 'https://api.microlink.io/',
15
+ PRO: 'https://pro.microlink.io/'
16
+ }
17
+
18
+ const STREAM_RESPONSE_TYPE = 'arrayBuffer'
7
19
 
8
20
  const kyInstance = ky.extend({
9
21
  headers: { 'user-agent': USER_AGENT },
10
- retry: { statusCodes: RETRY_STATUS_CODES }
22
+ retry: {
23
+ statusCodes: RETRY_STATUS_CODES,
24
+ afterStatusCodes: RETRY_AFTER_STATUS_CODES
25
+ }
11
26
  })
12
27
 
13
- const factory = require('./factory')('arrayBuffer')
28
+ const isObject = input => input !== null && typeof input === 'object'
29
+
30
+ const isBuffer = input =>
31
+ typeof input?.constructor?.isBuffer === 'function' &&
32
+ input.constructor.isBuffer(input)
33
+
34
+ const parseBody = (input, error, url) => {
35
+ try {
36
+ return JSON.parse(input)
37
+ } catch (_) {
38
+ const message = input || error.message
39
+
40
+ return {
41
+ status: 'error',
42
+ data: { url: message },
43
+ more: 'https://microlink.io/efatalclient',
44
+ code: 'EFATALCLIENT',
45
+ message,
46
+ url
47
+ }
48
+ }
49
+ }
50
+
51
+ const isURL = url => {
52
+ try {
53
+ const { protocol } = new URL(url)
54
+ return protocol === 'http:' || protocol === 'https:'
55
+ } catch (_) {
56
+ return false
57
+ }
58
+ }
14
59
 
15
60
  class MicrolinkError extends Error {
16
61
  constructor (props) {
@@ -24,44 +69,130 @@ class MicrolinkError extends Error {
24
69
  }
25
70
  }
26
71
 
27
- const got = async (url, { responseType, ...opts }) => {
72
+ const assertUrl = (url = '') => {
73
+ if (!isURL(url)) {
74
+ const message = `The \`url\` as \`${url}\` is not valid. Ensure it has protocol (http or https) and hostname.`
75
+ throw new MicrolinkError({
76
+ status: 'fail',
77
+ data: { url: message },
78
+ more: 'https://microlink.io/einvalurlclient',
79
+ code: 'EINVALURLCLIENT',
80
+ message,
81
+ url
82
+ })
83
+ }
84
+ }
85
+
86
+ const mapRules = rules => {
87
+ if (!isObject(rules)) return
88
+ return Object.fromEntries(
89
+ Object.entries(flatten(rules)).map(([key, value]) => [
90
+ `data.${key}`,
91
+ value.toString()
92
+ ])
93
+ )
94
+ }
95
+
96
+ const doFetch = async (apiUrl, { responseType, ...opts }) => {
97
+ if (opts.timeout === undefined) opts.timeout = false
98
+ const response = await kyInstance(apiUrl, opts)
99
+ const body = await response[responseType]()
100
+ const { headers, status: statusCode } = response
101
+ return { url: response.url, body, headers, statusCode }
102
+ }
103
+
104
+ const fetchFromApi = async (apiUrl, opts = {}) => {
28
105
  try {
29
- if (opts.timeout === undefined) opts.timeout = false
30
- const response = await kyInstance(url, opts)
31
- const body = await response[responseType]()
32
- const { headers, status: statusCode } = response
33
- return { url: response.url, body, headers, statusCode }
106
+ const response = await doFetch(apiUrl, opts)
107
+ return opts.responseType === STREAM_RESPONSE_TYPE
108
+ ? response
109
+ : { ...response.body, response }
34
110
  } catch (error) {
35
- if (error.response) {
36
- const { response } = error
37
- error.response = {
38
- ...response,
39
- headers: Array.from(response.headers.entries()).reduce(
40
- (acc, [key, value]) => {
41
- acc[key] = value
42
- return acc
43
- },
44
- {}
45
- ),
46
- statusCode: response.status,
47
- body: await response.text()
111
+ const { response = {} } = error
112
+ const { statusCode: responseStatusCode, status } = response
113
+ const {
114
+ body: rawBody,
115
+ headers: responseHeaders,
116
+ url: uri = apiUrl
117
+ } = response
118
+
119
+ const statusCode = responseStatusCode ?? status
120
+ const headers =
121
+ typeof responseHeaders?.entries === 'function'
122
+ ? Object.fromEntries(responseHeaders.entries())
123
+ : responseHeaders || {}
124
+
125
+ let bodyInput = error.data ?? rawBody
126
+ const isBodyReadableStream = typeof bodyInput?.getReader === 'function'
127
+
128
+ if (
129
+ (bodyInput === undefined || isBodyReadableStream) &&
130
+ typeof response.text === 'function'
131
+ ) {
132
+ try {
133
+ bodyInput = await response.text()
134
+ } catch (_) {
135
+ bodyInput = undefined
48
136
  }
49
137
  }
50
- throw error
138
+
139
+ const isBodyBuffer = isBuffer(bodyInput)
140
+ const body =
141
+ isObject(bodyInput) && !isBodyBuffer
142
+ ? bodyInput
143
+ : parseBody(isBodyBuffer ? bodyInput.toString() : bodyInput, error, uri)
144
+
145
+ throw new MicrolinkError({
146
+ ...body,
147
+ url: uri,
148
+ statusCode,
149
+ headers
150
+ })
51
151
  }
52
152
  }
53
153
 
54
- got.stream = (...args) => kyInstance(...args).then(res => res.body)
154
+ const getApiUrl = (
155
+ url,
156
+ { data, apiKey, endpoint, ...opts } = {},
157
+ { responseType = 'json', headers: reqHeaders = {}, ...gotOpts } = {}
158
+ ) => {
159
+ const isPro = !!apiKey
160
+ const apiEndpoint = endpoint || ENDPOINT[isPro ? 'PRO' : 'FREE']
55
161
 
56
- const mql = factory({
57
- MicrolinkError,
58
- got,
59
- flatten,
60
- VERSION
61
- })
162
+ const apiUrl = `${apiEndpoint}?${new URLSearchParams({
163
+ url,
164
+ ...mapRules(data),
165
+ ...flatten(opts)
166
+ })}`
167
+
168
+ const headers = isPro ? { ...reqHeaders, 'x-api-key': apiKey } : reqHeaders
169
+
170
+ if (opts.stream) responseType = STREAM_RESPONSE_TYPE
171
+
172
+ return [apiUrl, { ...gotOpts, responseType, headers }]
173
+ }
174
+
175
+ const createMql = defaultOpts => async (url, opts, gotOpts) => {
176
+ assertUrl(url)
177
+ const [apiUrl, fetchOpts] = getApiUrl(url, opts, {
178
+ ...defaultOpts,
179
+ ...gotOpts
180
+ })
181
+ return fetchFromApi(apiUrl, fetchOpts)
182
+ }
183
+
184
+ const mql = createMql()
185
+
186
+ mql.extend = createMql
187
+ mql.MicrolinkError = MicrolinkError
188
+ mql.getApiUrl = getApiUrl
189
+ mql.fetchFromApi = fetchFromApi
190
+ mql.mapRules = mapRules
191
+ mql.version = VERSION
192
+ mql.stream = (...args) => kyInstance(...args).then(res => res.body)
62
193
 
63
194
  module.exports = mql
64
- module.exports.arrayBuffer = mql.extend({ responseType: 'arrayBuffer' })
195
+ module.exports.arrayBuffer = mql.extend({ responseType: STREAM_RESPONSE_TYPE })
65
196
  module.exports.buffer = module.exports.arrayBuffer
66
197
  module.exports.extend = mql.extend
67
198
  module.exports.fetchFromApi = mql.fetchFromApi
package/src/factory.js DELETED
@@ -1,144 +0,0 @@
1
- const ENDPOINT = {
2
- FREE: 'https://api.microlink.io/',
3
- PRO: 'https://pro.microlink.io/'
4
- }
5
-
6
- const isObject = input => input !== null && typeof input === 'object'
7
-
8
- const isBuffer = input =>
9
- input != null &&
10
- input.constructor != null &&
11
- typeof input.constructor.isBuffer === 'function' &&
12
- input.constructor.isBuffer(input)
13
-
14
- const parseBody = (input, error, url) => {
15
- try {
16
- return JSON.parse(input)
17
- } catch (_) {
18
- const message = input || error.message
19
-
20
- return {
21
- status: 'error',
22
- data: { url: message },
23
- more: 'https://microlink.io/efatalclient',
24
- code: 'EFATALCLIENT',
25
- message,
26
- url
27
- }
28
- }
29
- }
30
-
31
- const isURL = url => {
32
- try {
33
- return /^https?:\/\//i.test(new URL(url).href)
34
- } catch (_) {
35
- return false
36
- }
37
- }
38
-
39
- const factory = streamResponseType => ({
40
- VERSION,
41
- MicrolinkError,
42
- got,
43
- flatten
44
- }) => {
45
- const assertUrl = (url = '') => {
46
- if (!isURL(url)) {
47
- const message = `The \`url\` as \`${url}\` is not valid. Ensure it has protocol (http or https) and hostname.`
48
- throw new MicrolinkError({
49
- status: 'fail',
50
- data: { url: message },
51
- more: 'https://microlink.io/einvalurlclient',
52
- code: 'EINVALURLCLIENT',
53
- message,
54
- url
55
- })
56
- }
57
- }
58
-
59
- const mapRules = rules => {
60
- if (!isObject(rules)) return
61
- const flatRules = flatten(rules)
62
- return Object.keys(flatRules).reduce((acc, key) => {
63
- acc[`data.${key}`] = flatRules[key].toString()
64
- return acc
65
- }, {})
66
- }
67
-
68
- const fetchFromApi = async (apiUrl, opts = {}) => {
69
- try {
70
- const response = await got(apiUrl, opts)
71
- return opts.responseType === streamResponseType
72
- ? response
73
- : { ...response.body, response }
74
- } catch (error) {
75
- const { response = {} } = error
76
- const {
77
- statusCode,
78
- body: rawBody,
79
- headers = {},
80
- url: uri = apiUrl
81
- } = response
82
- const isBodyBuffer = isBuffer(rawBody)
83
-
84
- const body =
85
- isObject(rawBody) && !isBodyBuffer
86
- ? rawBody
87
- : parseBody(isBodyBuffer ? rawBody.toString() : rawBody, error, uri)
88
-
89
- throw new MicrolinkError({
90
- ...body,
91
- message: body.message,
92
- url: uri,
93
- statusCode,
94
- headers
95
- })
96
- }
97
- }
98
-
99
- const getApiUrl = (
100
- url,
101
- { data, apiKey, endpoint, ...opts } = {},
102
- { responseType = 'json', headers: gotHeaders, ...gotOpts } = {}
103
- ) => {
104
- const isPro = !!apiKey
105
- const apiEndpoint = endpoint || ENDPOINT[isPro ? 'PRO' : 'FREE']
106
-
107
- const apiUrl = `${apiEndpoint}?${new URLSearchParams({
108
- url,
109
- ...mapRules(data),
110
- ...flatten(opts)
111
- }).toString()}`
112
-
113
- const headers = isPro
114
- ? { ...gotHeaders, 'x-api-key': apiKey }
115
- : { ...gotHeaders }
116
-
117
- if (opts.stream) {
118
- responseType = streamResponseType
119
- }
120
- return [apiUrl, { ...gotOpts, responseType, headers }]
121
- }
122
-
123
- const createMql = defaultOpts => async (url, opts, gotOpts) => {
124
- assertUrl(url)
125
- const [apiUrl, fetchOpts] = getApiUrl(url, opts, {
126
- ...defaultOpts,
127
- ...gotOpts
128
- })
129
- return fetchFromApi(apiUrl, fetchOpts)
130
- }
131
-
132
- const mql = createMql()
133
- mql.extend = createMql
134
- mql.MicrolinkError = MicrolinkError
135
- mql.getApiUrl = getApiUrl
136
- mql.fetchFromApi = fetchFromApi
137
- mql.mapRules = mapRules
138
- mql.version = VERSION
139
- mql.stream = got.stream
140
-
141
- return mql
142
- }
143
-
144
- module.exports = factory