@storyblok/management-api-client 0.1.5 → 0.1.6
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/README.md +2 -2
- package/dist/client/client.js +7 -5
- package/dist/client/client.js.map +1 -1
- package/dist/client/client.mjs +7 -5
- package/dist/client/client.mjs.map +1 -1
- package/dist/utils/calculate-retry-delay.js +20 -0
- package/dist/utils/calculate-retry-delay.js.map +1 -0
- package/dist/utils/calculate-retry-delay.mjs +19 -0
- package/dist/utils/calculate-retry-delay.mjs.map +1 -0
- package/dist/utils/delay.js +7 -0
- package/dist/utils/delay.js.map +1 -0
- package/dist/utils/delay.mjs +6 -0
- package/dist/utils/delay.mjs.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -140,7 +140,7 @@ The client includes built-in retry handling for rate limits and network errors:
|
|
|
140
140
|
|
|
141
141
|
```typescript
|
|
142
142
|
// The client automatically handles retries with these defaults:
|
|
143
|
-
// - maxRetries:
|
|
143
|
+
// - maxRetries: 12
|
|
144
144
|
// - retryDelay: 1000ms
|
|
145
145
|
// - Respects retry-after headers from 429 responses
|
|
146
146
|
|
|
@@ -148,7 +148,7 @@ const stories = await client.stories.list({
|
|
|
148
148
|
path: { space_id: 123456 },
|
|
149
149
|
query: { per_page: 10 }
|
|
150
150
|
});
|
|
151
|
-
// If rate limited, will automatically retry up to
|
|
151
|
+
// If rate limited, will automatically retry up to 12 times
|
|
152
152
|
```
|
|
153
153
|
|
|
154
154
|
## Runtime Configuration
|
package/dist/client/client.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.js');
|
|
2
2
|
const require_utils = require('./utils.js');
|
|
3
|
+
const require_calculate_retry_delay = require('../utils/calculate-retry-delay.js');
|
|
4
|
+
const require_delay = require('../utils/delay.js');
|
|
3
5
|
const __storyblok_region_helper = require_rolldown_runtime.__toESM(require("@storyblok/region-helper"));
|
|
4
6
|
|
|
5
7
|
//#region src/client/client.ts
|
|
@@ -38,7 +40,7 @@ const createClient = (config) => {
|
|
|
38
40
|
for (const fn of interceptors.request._fns) if (fn) request$1 = await fn(request$1, opts);
|
|
39
41
|
const _fetch = opts.fetch;
|
|
40
42
|
let response = await executeWithRetry(_fetch, url, requestInit, {
|
|
41
|
-
maxRetries:
|
|
43
|
+
maxRetries: 12,
|
|
42
44
|
retryDelay: 1e3
|
|
43
45
|
});
|
|
44
46
|
for (const fn of interceptors.response._fns) if (fn) response = await fn(response, request$1, opts);
|
|
@@ -96,15 +98,15 @@ const createClient = (config) => {
|
|
|
96
98
|
const response = await fetchFn(request$1);
|
|
97
99
|
if (response.status === 429 && attempt < retryConfig.maxRetries) {
|
|
98
100
|
const retryAfter = response.headers.get("retry-after");
|
|
99
|
-
const
|
|
100
|
-
await
|
|
101
|
+
const retryDelay = retryAfter ? parseInt(retryAfter) * 1e3 : require_calculate_retry_delay.calculateRetryDelay(attempt, retryConfig.retryDelay);
|
|
102
|
+
await require_delay.delay(retryDelay);
|
|
101
103
|
return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);
|
|
102
104
|
}
|
|
103
105
|
return response;
|
|
104
106
|
} catch (error) {
|
|
105
107
|
if (attempt < retryConfig.maxRetries) {
|
|
106
|
-
const delay = retryConfig.retryDelay;
|
|
107
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
108
|
+
const delay$1 = retryConfig.retryDelay;
|
|
109
|
+
await new Promise((resolve) => setTimeout(resolve, delay$1));
|
|
108
110
|
return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);
|
|
109
111
|
}
|
|
110
112
|
throw error;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["config: Config","mergeConfigs","createConfig","config","createInterceptors","request: Client['request']","mergeHeaders","setAuthParams","buildUrl","requestInit: ReqInit","request","getParseAs","data: any","jsonError: unknown","fetchFn: any","url: string","retryConfig: { maxRetries: number; retryDelay: number }","attempt: number"],"sources":["../../src/client/client.ts"],"sourcesContent":["import { getManagementBaseUrl, getRegion } from '@storyblok/region-helper';\nimport type { Client, Config, RequestOptions } from './types';\nimport {\n buildUrl,\n createConfig,\n createInterceptors,\n getParseAs,\n mergeConfigs,\n mergeHeaders,\n setAuthParams,\n} from './utils';\n\ntype ReqInit = Omit<RequestInit, 'body' | 'headers'> & {\n body?: any;\n headers: ReturnType<typeof mergeHeaders>;\n};\n\nexport const createClient = (config: Config): Client => {\n let _config = mergeConfigs(createConfig(), config);\n\n const getConfig = (): Config => ({ ..._config });\n\n const setConfig = (config: Config): Config => {\n _config = mergeConfigs(_config, config);\n return getConfig();\n };\n\n const interceptors = createInterceptors<\n Request,\n Response,\n unknown,\n RequestOptions\n >();\n\n const request: Client['request'] = async (options) => {\n const opts = {\n ..._config,\n ...options,\n fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,\n headers: mergeHeaders(_config.headers, options.headers),\n };\n\n // If the baseUrl is not set and we have a space_id, we can attempt to infer the region\n if (!_config.baseUrl && options.path?.space_id) {\n const region = getRegion(options.path.space_id as number, opts.region);\n if (region) {\n opts.baseUrl = getManagementBaseUrl(region, 'https');\n }\n } else if (!_config.baseUrl && opts.region) {\n opts.baseUrl = getManagementBaseUrl(opts.region, 'https');\n }\n\n if (opts.security) {\n await setAuthParams({\n ...opts,\n security: opts.security,\n });\n }\n\n if (opts.requestValidator) {\n await opts.requestValidator(opts);\n }\n\n if (opts.body && opts.bodySerializer) {\n opts.body = opts.bodySerializer(opts.body);\n }\n\n // remove Content-Type header if body is empty to avoid sending invalid requests\n if (opts.body === undefined || opts.body === '') {\n opts.headers.delete('Content-Type');\n }\n\n const url = buildUrl(opts);\n const requestInit: ReqInit = {\n redirect: 'follow',\n ...opts,\n };\n\n let request = new Request(url, requestInit);\n\n for (const fn of interceptors.request._fns) {\n if (fn) {\n request = await fn(request, opts);\n }\n }\n\n // fetch must be assigned here, otherwise it would throw the error:\n // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation\n const _fetch = opts.fetch!;\n \n // Execute with retry logic by recreating the request for each attempt\n let response = await executeWithRetry(_fetch, url, requestInit, {\n maxRetries: 3,\n retryDelay: 1000\n });\n\n for (const fn of interceptors.response._fns) {\n if (fn) {\n response = await fn(response, request, opts);\n }\n }\n\n const result = {\n request,\n response,\n };\n\n if (response.ok) {\n if (\n response.status === 204 ||\n response.headers.get('Content-Length') === '0'\n ) {\n return opts.responseStyle === 'data'\n ? {}\n : {\n data: {},\n ...result,\n };\n }\n\n const parseAs =\n (opts.parseAs === 'auto'\n ? getParseAs(response.headers.get('Content-Type'))\n : opts.parseAs) ?? 'json';\n\n let data: any;\n switch (parseAs) {\n case 'arrayBuffer':\n case 'blob':\n case 'formData':\n case 'json':\n case 'text':\n data = await response[parseAs]();\n break;\n case 'stream':\n return opts.responseStyle === 'data'\n ? response.body\n : {\n data: response.body,\n ...result,\n };\n }\n\n if (parseAs === 'json') {\n if (opts.responseValidator) {\n await opts.responseValidator(data);\n }\n\n if (opts.responseTransformer) {\n data = await opts.responseTransformer(data);\n }\n }\n\n return opts.responseStyle === 'data'\n ? data\n : {\n data,\n ...result,\n };\n }\n\n const textError = await response.text();\n let jsonError: unknown;\n\n try {\n jsonError = JSON.parse(textError);\n } catch {\n // noop\n }\n\n const error = jsonError ?? textError;\n let finalError = error;\n\n for (const fn of interceptors.error._fns) {\n if (fn) {\n finalError = (await fn(error, response, request, opts)) as string;\n }\n }\n\n finalError = finalError || ({} as string);\n\n if (opts.throwOnError) {\n throw finalError;\n }\n\n // TODO: we probably want to return error and improve types\n return opts.responseStyle === 'data'\n ? undefined\n : {\n error: finalError,\n ...result,\n };\n };\n\n // Helper function to execute fetch with retry logic\n async function executeWithRetry(\n fetchFn: any,\n url: string,\n requestInit: ReqInit,\n retryConfig: { maxRetries: number; retryDelay: number },\n attempt: number = 0\n ): Promise<Response> {\n try {\n const request = new Request(url, requestInit);\n const response = await fetchFn(request);\n \n if (response.status === 429 && attempt < retryConfig.maxRetries) {\n const retryAfter = response.headers.get('retry-after');\n const delay = retryAfter ? parseInt(retryAfter) * 1000 : retryConfig.retryDelay;\n \n await new Promise(resolve => setTimeout(resolve, delay));\n \n // Use the original unconsumed request for retry\n return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);\n }\n \n return response;\n } catch (error) {\n // If it's a network error and we haven't exceeded retries, try again\n if (attempt < retryConfig.maxRetries) {\n const delay = retryConfig.retryDelay;\n await new Promise(resolve => setTimeout(resolve, delay));\n \n // Use the original unconsumed request for retry\n return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);\n }\n \n throw error;\n }\n }\n\n return {\n buildUrl,\n connect: (options) => request({ ...options, method: 'CONNECT' }),\n delete: (options) => request({ ...options, method: 'DELETE' }),\n get: (options) => request({ ...options, method: 'GET' }),\n getConfig,\n head: (options) => request({ ...options, method: 'HEAD' }),\n interceptors,\n options: (options) => request({ ...options, method: 'OPTIONS' }),\n patch: (options) => request({ ...options, method: 'PATCH' }),\n post: (options) => request({ ...options, method: 'POST' }),\n put: (options) => request({ ...options, method: 'PUT' }),\n request,\n setConfig,\n trace: (options) => request({ ...options, method: 'TRACE' }),\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["config: Config","mergeConfigs","createConfig","config","createInterceptors","request: Client['request']","mergeHeaders","setAuthParams","buildUrl","requestInit: ReqInit","request","getParseAs","data: any","jsonError: unknown","fetchFn: any","url: string","retryConfig: { maxRetries: number; retryDelay: number }","attempt: number","calculateRetryDelay","delay"],"sources":["../../src/client/client.ts"],"sourcesContent":["import { getManagementBaseUrl, getRegion } from '@storyblok/region-helper';\nimport type { Client, Config, RequestOptions } from './types';\nimport {\n buildUrl,\n createConfig,\n createInterceptors,\n getParseAs,\n mergeConfigs,\n mergeHeaders,\n setAuthParams,\n} from './utils';\nimport { calculateRetryDelay } from \"../utils/calculate-retry-delay\";\nimport { delay } from \"../utils/delay\";\n\ntype ReqInit = Omit<RequestInit, 'body' | 'headers'> & {\n body?: any;\n headers: ReturnType<typeof mergeHeaders>;\n};\n\nexport const createClient = (config: Config): Client => {\n let _config = mergeConfigs(createConfig(), config);\n\n const getConfig = (): Config => ({ ..._config });\n\n const setConfig = (config: Config): Config => {\n _config = mergeConfigs(_config, config);\n return getConfig();\n };\n\n const interceptors = createInterceptors<\n Request,\n Response,\n unknown,\n RequestOptions\n >();\n\n const request: Client['request'] = async (options) => {\n const opts = {\n ..._config,\n ...options,\n fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,\n headers: mergeHeaders(_config.headers, options.headers),\n };\n\n // If the baseUrl is not set and we have a space_id, we can attempt to infer the region\n if (!_config.baseUrl && options.path?.space_id) {\n const region = getRegion(options.path.space_id as number, opts.region);\n if (region) {\n opts.baseUrl = getManagementBaseUrl(region, 'https');\n }\n } else if (!_config.baseUrl && opts.region) {\n opts.baseUrl = getManagementBaseUrl(opts.region, 'https');\n }\n\n if (opts.security) {\n await setAuthParams({\n ...opts,\n security: opts.security,\n });\n }\n\n if (opts.requestValidator) {\n await opts.requestValidator(opts);\n }\n\n if (opts.body && opts.bodySerializer) {\n opts.body = opts.bodySerializer(opts.body);\n }\n\n // remove Content-Type header if body is empty to avoid sending invalid requests\n if (opts.body === undefined || opts.body === '') {\n opts.headers.delete('Content-Type');\n }\n\n const url = buildUrl(opts);\n const requestInit: ReqInit = {\n redirect: 'follow',\n ...opts,\n };\n\n let request = new Request(url, requestInit);\n\n for (const fn of interceptors.request._fns) {\n if (fn) {\n request = await fn(request, opts);\n }\n }\n\n // fetch must be assigned here, otherwise it would throw the error:\n // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation\n const _fetch = opts.fetch!;\n \n // Execute with retry logic by recreating the request for each attempt\n let response = await executeWithRetry(_fetch, url, requestInit, {\n maxRetries: 12,\n retryDelay: 1000\n });\n\n for (const fn of interceptors.response._fns) {\n if (fn) {\n response = await fn(response, request, opts);\n }\n }\n\n const result = {\n request,\n response,\n };\n\n if (response.ok) {\n if (\n response.status === 204 ||\n response.headers.get('Content-Length') === '0'\n ) {\n return opts.responseStyle === 'data'\n ? {}\n : {\n data: {},\n ...result,\n };\n }\n\n const parseAs =\n (opts.parseAs === 'auto'\n ? getParseAs(response.headers.get('Content-Type'))\n : opts.parseAs) ?? 'json';\n\n let data: any;\n switch (parseAs) {\n case 'arrayBuffer':\n case 'blob':\n case 'formData':\n case 'json':\n case 'text':\n data = await response[parseAs]();\n break;\n case 'stream':\n return opts.responseStyle === 'data'\n ? response.body\n : {\n data: response.body,\n ...result,\n };\n }\n\n if (parseAs === 'json') {\n if (opts.responseValidator) {\n await opts.responseValidator(data);\n }\n\n if (opts.responseTransformer) {\n data = await opts.responseTransformer(data);\n }\n }\n\n return opts.responseStyle === 'data'\n ? data\n : {\n data,\n ...result,\n };\n }\n\n const textError = await response.text();\n let jsonError: unknown;\n\n try {\n jsonError = JSON.parse(textError);\n } catch {\n // noop\n }\n\n const error = jsonError ?? textError;\n let finalError = error;\n\n for (const fn of interceptors.error._fns) {\n if (fn) {\n finalError = (await fn(error, response, request, opts)) as string;\n }\n }\n\n finalError = finalError || ({} as string);\n\n if (opts.throwOnError) {\n throw finalError;\n }\n\n // TODO: we probably want to return error and improve types\n return opts.responseStyle === 'data'\n ? undefined\n : {\n error: finalError,\n ...result,\n };\n };\n\n // Helper function to execute fetch with retry logic\n async function executeWithRetry(\n fetchFn: any,\n url: string,\n requestInit: ReqInit,\n retryConfig: { maxRetries: number; retryDelay: number },\n attempt: number = 0\n ): Promise<Response> {\n try {\n const request = new Request(url, requestInit);\n const response = await fetchFn(request);\n \n if (response.status === 429 && attempt < retryConfig.maxRetries) {\n const retryAfter = response.headers.get('retry-after');\n const retryDelay = retryAfter ? parseInt(retryAfter) * 1000 : calculateRetryDelay(attempt, retryConfig.retryDelay);\n await delay(retryDelay);\n \n // Use the original unconsumed request for retry\n return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);\n }\n \n return response;\n } catch (error) {\n // If it's a network error and we haven't exceeded retries, try again\n if (attempt < retryConfig.maxRetries) {\n const delay = retryConfig.retryDelay;\n await new Promise(resolve => setTimeout(resolve, delay));\n \n // Use the original unconsumed request for retry\n return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);\n }\n \n throw error;\n }\n }\n\n return {\n buildUrl,\n connect: (options) => request({ ...options, method: 'CONNECT' }),\n delete: (options) => request({ ...options, method: 'DELETE' }),\n get: (options) => request({ ...options, method: 'GET' }),\n getConfig,\n head: (options) => request({ ...options, method: 'HEAD' }),\n interceptors,\n options: (options) => request({ ...options, method: 'OPTIONS' }),\n patch: (options) => request({ ...options, method: 'PATCH' }),\n post: (options) => request({ ...options, method: 'POST' }),\n put: (options) => request({ ...options, method: 'PUT' }),\n request,\n setConfig,\n trace: (options) => request({ ...options, method: 'TRACE' }),\n };\n};\n"],"mappings":"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"}
|
package/dist/client/client.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { buildUrl, createConfig, createInterceptors, getParseAs, mergeConfigs, mergeHeaders, setAuthParams } from "./utils.mjs";
|
|
2
|
+
import { calculateRetryDelay } from "../utils/calculate-retry-delay.mjs";
|
|
3
|
+
import { delay } from "../utils/delay.mjs";
|
|
2
4
|
import { getManagementBaseUrl, getRegion } from "@storyblok/region-helper";
|
|
3
5
|
|
|
4
6
|
//#region src/client/client.ts
|
|
@@ -37,7 +39,7 @@ const createClient = (config) => {
|
|
|
37
39
|
for (const fn of interceptors.request._fns) if (fn) request$1 = await fn(request$1, opts);
|
|
38
40
|
const _fetch = opts.fetch;
|
|
39
41
|
let response = await executeWithRetry(_fetch, url, requestInit, {
|
|
40
|
-
maxRetries:
|
|
42
|
+
maxRetries: 12,
|
|
41
43
|
retryDelay: 1e3
|
|
42
44
|
});
|
|
43
45
|
for (const fn of interceptors.response._fns) if (fn) response = await fn(response, request$1, opts);
|
|
@@ -95,15 +97,15 @@ const createClient = (config) => {
|
|
|
95
97
|
const response = await fetchFn(request$1);
|
|
96
98
|
if (response.status === 429 && attempt < retryConfig.maxRetries) {
|
|
97
99
|
const retryAfter = response.headers.get("retry-after");
|
|
98
|
-
const
|
|
99
|
-
await
|
|
100
|
+
const retryDelay = retryAfter ? parseInt(retryAfter) * 1e3 : calculateRetryDelay(attempt, retryConfig.retryDelay);
|
|
101
|
+
await delay(retryDelay);
|
|
100
102
|
return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);
|
|
101
103
|
}
|
|
102
104
|
return response;
|
|
103
105
|
} catch (error) {
|
|
104
106
|
if (attempt < retryConfig.maxRetries) {
|
|
105
|
-
const delay = retryConfig.retryDelay;
|
|
106
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
107
|
+
const delay$1 = retryConfig.retryDelay;
|
|
108
|
+
await new Promise((resolve) => setTimeout(resolve, delay$1));
|
|
107
109
|
return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);
|
|
108
110
|
}
|
|
109
111
|
throw error;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.mjs","names":["config: Config","config","request: Client['request']","requestInit: ReqInit","request","data: any","jsonError: unknown","fetchFn: any","url: string","retryConfig: { maxRetries: number; retryDelay: number }","attempt: number"],"sources":["../../src/client/client.ts"],"sourcesContent":["import { getManagementBaseUrl, getRegion } from '@storyblok/region-helper';\nimport type { Client, Config, RequestOptions } from './types';\nimport {\n buildUrl,\n createConfig,\n createInterceptors,\n getParseAs,\n mergeConfigs,\n mergeHeaders,\n setAuthParams,\n} from './utils';\n\ntype ReqInit = Omit<RequestInit, 'body' | 'headers'> & {\n body?: any;\n headers: ReturnType<typeof mergeHeaders>;\n};\n\nexport const createClient = (config: Config): Client => {\n let _config = mergeConfigs(createConfig(), config);\n\n const getConfig = (): Config => ({ ..._config });\n\n const setConfig = (config: Config): Config => {\n _config = mergeConfigs(_config, config);\n return getConfig();\n };\n\n const interceptors = createInterceptors<\n Request,\n Response,\n unknown,\n RequestOptions\n >();\n\n const request: Client['request'] = async (options) => {\n const opts = {\n ..._config,\n ...options,\n fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,\n headers: mergeHeaders(_config.headers, options.headers),\n };\n\n // If the baseUrl is not set and we have a space_id, we can attempt to infer the region\n if (!_config.baseUrl && options.path?.space_id) {\n const region = getRegion(options.path.space_id as number, opts.region);\n if (region) {\n opts.baseUrl = getManagementBaseUrl(region, 'https');\n }\n } else if (!_config.baseUrl && opts.region) {\n opts.baseUrl = getManagementBaseUrl(opts.region, 'https');\n }\n\n if (opts.security) {\n await setAuthParams({\n ...opts,\n security: opts.security,\n });\n }\n\n if (opts.requestValidator) {\n await opts.requestValidator(opts);\n }\n\n if (opts.body && opts.bodySerializer) {\n opts.body = opts.bodySerializer(opts.body);\n }\n\n // remove Content-Type header if body is empty to avoid sending invalid requests\n if (opts.body === undefined || opts.body === '') {\n opts.headers.delete('Content-Type');\n }\n\n const url = buildUrl(opts);\n const requestInit: ReqInit = {\n redirect: 'follow',\n ...opts,\n };\n\n let request = new Request(url, requestInit);\n\n for (const fn of interceptors.request._fns) {\n if (fn) {\n request = await fn(request, opts);\n }\n }\n\n // fetch must be assigned here, otherwise it would throw the error:\n // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation\n const _fetch = opts.fetch!;\n \n // Execute with retry logic by recreating the request for each attempt\n let response = await executeWithRetry(_fetch, url, requestInit, {\n maxRetries: 3,\n retryDelay: 1000\n });\n\n for (const fn of interceptors.response._fns) {\n if (fn) {\n response = await fn(response, request, opts);\n }\n }\n\n const result = {\n request,\n response,\n };\n\n if (response.ok) {\n if (\n response.status === 204 ||\n response.headers.get('Content-Length') === '0'\n ) {\n return opts.responseStyle === 'data'\n ? {}\n : {\n data: {},\n ...result,\n };\n }\n\n const parseAs =\n (opts.parseAs === 'auto'\n ? getParseAs(response.headers.get('Content-Type'))\n : opts.parseAs) ?? 'json';\n\n let data: any;\n switch (parseAs) {\n case 'arrayBuffer':\n case 'blob':\n case 'formData':\n case 'json':\n case 'text':\n data = await response[parseAs]();\n break;\n case 'stream':\n return opts.responseStyle === 'data'\n ? response.body\n : {\n data: response.body,\n ...result,\n };\n }\n\n if (parseAs === 'json') {\n if (opts.responseValidator) {\n await opts.responseValidator(data);\n }\n\n if (opts.responseTransformer) {\n data = await opts.responseTransformer(data);\n }\n }\n\n return opts.responseStyle === 'data'\n ? data\n : {\n data,\n ...result,\n };\n }\n\n const textError = await response.text();\n let jsonError: unknown;\n\n try {\n jsonError = JSON.parse(textError);\n } catch {\n // noop\n }\n\n const error = jsonError ?? textError;\n let finalError = error;\n\n for (const fn of interceptors.error._fns) {\n if (fn) {\n finalError = (await fn(error, response, request, opts)) as string;\n }\n }\n\n finalError = finalError || ({} as string);\n\n if (opts.throwOnError) {\n throw finalError;\n }\n\n // TODO: we probably want to return error and improve types\n return opts.responseStyle === 'data'\n ? undefined\n : {\n error: finalError,\n ...result,\n };\n };\n\n // Helper function to execute fetch with retry logic\n async function executeWithRetry(\n fetchFn: any,\n url: string,\n requestInit: ReqInit,\n retryConfig: { maxRetries: number; retryDelay: number },\n attempt: number = 0\n ): Promise<Response> {\n try {\n const request = new Request(url, requestInit);\n const response = await fetchFn(request);\n \n if (response.status === 429 && attempt < retryConfig.maxRetries) {\n const retryAfter = response.headers.get('retry-after');\n const delay = retryAfter ? parseInt(retryAfter) * 1000 : retryConfig.retryDelay;\n \n await new Promise(resolve => setTimeout(resolve, delay));\n \n // Use the original unconsumed request for retry\n return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);\n }\n \n return response;\n } catch (error) {\n // If it's a network error and we haven't exceeded retries, try again\n if (attempt < retryConfig.maxRetries) {\n const delay = retryConfig.retryDelay;\n await new Promise(resolve => setTimeout(resolve, delay));\n \n // Use the original unconsumed request for retry\n return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);\n }\n \n throw error;\n }\n }\n\n return {\n buildUrl,\n connect: (options) => request({ ...options, method: 'CONNECT' }),\n delete: (options) => request({ ...options, method: 'DELETE' }),\n get: (options) => request({ ...options, method: 'GET' }),\n getConfig,\n head: (options) => request({ ...options, method: 'HEAD' }),\n interceptors,\n options: (options) => request({ ...options, method: 'OPTIONS' }),\n patch: (options) => request({ ...options, method: 'PATCH' }),\n post: (options) => request({ ...options, method: 'POST' }),\n put: (options) => request({ ...options, method: 'PUT' }),\n request,\n setConfig,\n trace: (options) => request({ ...options, method: 'TRACE' }),\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.mjs","names":["config: Config","config","request: Client['request']","requestInit: ReqInit","request","data: any","jsonError: unknown","fetchFn: any","url: string","retryConfig: { maxRetries: number; retryDelay: number }","attempt: number","delay"],"sources":["../../src/client/client.ts"],"sourcesContent":["import { getManagementBaseUrl, getRegion } from '@storyblok/region-helper';\nimport type { Client, Config, RequestOptions } from './types';\nimport {\n buildUrl,\n createConfig,\n createInterceptors,\n getParseAs,\n mergeConfigs,\n mergeHeaders,\n setAuthParams,\n} from './utils';\nimport { calculateRetryDelay } from \"../utils/calculate-retry-delay\";\nimport { delay } from \"../utils/delay\";\n\ntype ReqInit = Omit<RequestInit, 'body' | 'headers'> & {\n body?: any;\n headers: ReturnType<typeof mergeHeaders>;\n};\n\nexport const createClient = (config: Config): Client => {\n let _config = mergeConfigs(createConfig(), config);\n\n const getConfig = (): Config => ({ ..._config });\n\n const setConfig = (config: Config): Config => {\n _config = mergeConfigs(_config, config);\n return getConfig();\n };\n\n const interceptors = createInterceptors<\n Request,\n Response,\n unknown,\n RequestOptions\n >();\n\n const request: Client['request'] = async (options) => {\n const opts = {\n ..._config,\n ...options,\n fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,\n headers: mergeHeaders(_config.headers, options.headers),\n };\n\n // If the baseUrl is not set and we have a space_id, we can attempt to infer the region\n if (!_config.baseUrl && options.path?.space_id) {\n const region = getRegion(options.path.space_id as number, opts.region);\n if (region) {\n opts.baseUrl = getManagementBaseUrl(region, 'https');\n }\n } else if (!_config.baseUrl && opts.region) {\n opts.baseUrl = getManagementBaseUrl(opts.region, 'https');\n }\n\n if (opts.security) {\n await setAuthParams({\n ...opts,\n security: opts.security,\n });\n }\n\n if (opts.requestValidator) {\n await opts.requestValidator(opts);\n }\n\n if (opts.body && opts.bodySerializer) {\n opts.body = opts.bodySerializer(opts.body);\n }\n\n // remove Content-Type header if body is empty to avoid sending invalid requests\n if (opts.body === undefined || opts.body === '') {\n opts.headers.delete('Content-Type');\n }\n\n const url = buildUrl(opts);\n const requestInit: ReqInit = {\n redirect: 'follow',\n ...opts,\n };\n\n let request = new Request(url, requestInit);\n\n for (const fn of interceptors.request._fns) {\n if (fn) {\n request = await fn(request, opts);\n }\n }\n\n // fetch must be assigned here, otherwise it would throw the error:\n // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation\n const _fetch = opts.fetch!;\n \n // Execute with retry logic by recreating the request for each attempt\n let response = await executeWithRetry(_fetch, url, requestInit, {\n maxRetries: 12,\n retryDelay: 1000\n });\n\n for (const fn of interceptors.response._fns) {\n if (fn) {\n response = await fn(response, request, opts);\n }\n }\n\n const result = {\n request,\n response,\n };\n\n if (response.ok) {\n if (\n response.status === 204 ||\n response.headers.get('Content-Length') === '0'\n ) {\n return opts.responseStyle === 'data'\n ? {}\n : {\n data: {},\n ...result,\n };\n }\n\n const parseAs =\n (opts.parseAs === 'auto'\n ? getParseAs(response.headers.get('Content-Type'))\n : opts.parseAs) ?? 'json';\n\n let data: any;\n switch (parseAs) {\n case 'arrayBuffer':\n case 'blob':\n case 'formData':\n case 'json':\n case 'text':\n data = await response[parseAs]();\n break;\n case 'stream':\n return opts.responseStyle === 'data'\n ? response.body\n : {\n data: response.body,\n ...result,\n };\n }\n\n if (parseAs === 'json') {\n if (opts.responseValidator) {\n await opts.responseValidator(data);\n }\n\n if (opts.responseTransformer) {\n data = await opts.responseTransformer(data);\n }\n }\n\n return opts.responseStyle === 'data'\n ? data\n : {\n data,\n ...result,\n };\n }\n\n const textError = await response.text();\n let jsonError: unknown;\n\n try {\n jsonError = JSON.parse(textError);\n } catch {\n // noop\n }\n\n const error = jsonError ?? textError;\n let finalError = error;\n\n for (const fn of interceptors.error._fns) {\n if (fn) {\n finalError = (await fn(error, response, request, opts)) as string;\n }\n }\n\n finalError = finalError || ({} as string);\n\n if (opts.throwOnError) {\n throw finalError;\n }\n\n // TODO: we probably want to return error and improve types\n return opts.responseStyle === 'data'\n ? undefined\n : {\n error: finalError,\n ...result,\n };\n };\n\n // Helper function to execute fetch with retry logic\n async function executeWithRetry(\n fetchFn: any,\n url: string,\n requestInit: ReqInit,\n retryConfig: { maxRetries: number; retryDelay: number },\n attempt: number = 0\n ): Promise<Response> {\n try {\n const request = new Request(url, requestInit);\n const response = await fetchFn(request);\n \n if (response.status === 429 && attempt < retryConfig.maxRetries) {\n const retryAfter = response.headers.get('retry-after');\n const retryDelay = retryAfter ? parseInt(retryAfter) * 1000 : calculateRetryDelay(attempt, retryConfig.retryDelay);\n await delay(retryDelay);\n \n // Use the original unconsumed request for retry\n return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);\n }\n \n return response;\n } catch (error) {\n // If it's a network error and we haven't exceeded retries, try again\n if (attempt < retryConfig.maxRetries) {\n const delay = retryConfig.retryDelay;\n await new Promise(resolve => setTimeout(resolve, delay));\n \n // Use the original unconsumed request for retry\n return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);\n }\n \n throw error;\n }\n }\n\n return {\n buildUrl,\n connect: (options) => request({ ...options, method: 'CONNECT' }),\n delete: (options) => request({ ...options, method: 'DELETE' }),\n get: (options) => request({ ...options, method: 'GET' }),\n getConfig,\n head: (options) => request({ ...options, method: 'HEAD' }),\n interceptors,\n options: (options) => request({ ...options, method: 'OPTIONS' }),\n patch: (options) => request({ ...options, method: 'PATCH' }),\n post: (options) => request({ ...options, method: 'POST' }),\n put: (options) => request({ ...options, method: 'PUT' }),\n request,\n setConfig,\n trace: (options) => request({ ...options, method: 'TRACE' }),\n };\n};\n"],"mappings":";;;;;;AAmBA,MAAa,eAAe,CAACA,WAA2B;CACtD,IAAI,UAAU,aAAa,cAAc,EAAE,OAAO;CAElD,MAAM,YAAY,OAAe,EAAE,GAAG,QAAS;CAE/C,MAAM,YAAY,CAACA,aAA2B;EAC5C,UAAU,aAAa,SAASC,SAAO;AACvC,SAAO,WAAW;CACnB;CAED,MAAM,eAAe,oBAKlB;CAEH,MAAMC,UAA6B,OAAO,YAAY;EACpD,MAAM,OAAO;GACX,GAAG;GACH,GAAG;GACH,OAAO,QAAQ,SAAS,QAAQ,SAAS,WAAW;GACpD,SAAS,aAAa,QAAQ,SAAS,QAAQ,QAAQ;EACxD;AAGD,MAAI,CAAC,QAAQ,WAAW,QAAQ,MAAM,UAAU;GAC9C,MAAM,SAAS,UAAU,QAAQ,KAAK,UAAoB,KAAK,OAAO;AACtE,OAAI,QACF,KAAK,UAAU,qBAAqB,QAAQ,QAAQ;EAEvD,WAAU,CAAC,QAAQ,WAAW,KAAK,QAClC,KAAK,UAAU,qBAAqB,KAAK,QAAQ,QAAQ;AAG3D,MAAI,KAAK,UACP,MAAM,cAAc;GAClB,GAAG;GACH,UAAU,KAAK;EAChB,EAAC;AAGJ,MAAI,KAAK,kBACP,MAAM,KAAK,iBAAiB,KAAK;AAGnC,MAAI,KAAK,QAAQ,KAAK,gBACpB,KAAK,OAAO,KAAK,eAAe,KAAK,KAAK;AAI5C,MAAI,KAAK,SAAS,UAAa,KAAK,SAAS,IAC3C,KAAK,QAAQ,OAAO,eAAe;EAGrC,MAAM,MAAM,SAAS,KAAK;EAC1B,MAAMC,cAAuB;GAC3B,UAAU;GACV,GAAG;EACJ;EAED,IAAIC,YAAU,IAAI,QAAQ,KAAK;AAE/B,OAAK,MAAM,MAAM,aAAa,QAAQ,KACpC,KAAI,IACFA,YAAU,MAAM,GAAGA,WAAS,KAAK;EAMrC,MAAM,SAAS,KAAK;EAGpB,IAAI,WAAW,MAAM,iBAAiB,QAAQ,KAAK,aAAa;GAC9D,YAAY;GACZ,YAAY;EACb,EAAC;AAEF,OAAK,MAAM,MAAM,aAAa,SAAS,KACrC,KAAI,IACF,WAAW,MAAM,GAAG,UAAUA,WAAS,KAAK;EAIhD,MAAM,SAAS;GACb;GACA;EACD;AAED,MAAI,SAAS,IAAI;AACf,OACE,SAAS,WAAW,OACpB,SAAS,QAAQ,IAAI,iBAAiB,KAAK,IAE3C,QAAO,KAAK,kBAAkB,SAC1B,CAAE,IACF;IACE,MAAM,CAAE;IACR,GAAG;GACJ;GAGP,MAAM,WACH,KAAK,YAAY,SACd,WAAW,SAAS,QAAQ,IAAI,eAAe,CAAC,GAChD,KAAK,YAAY;GAEvB,IAAIC;AACJ,WAAQ,SAAR;IACE,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;KACH,OAAO,MAAM,SAAS,UAAU;AAChC;IACF,KAAK,SACH,QAAO,KAAK,kBAAkB,SAC1B,SAAS,OACT;KACE,MAAM,SAAS;KACf,GAAG;IACJ;GACR;AAED,OAAI,YAAY,QAAQ;AACtB,QAAI,KAAK,mBACP,MAAM,KAAK,kBAAkB,KAAK;AAGpC,QAAI,KAAK,qBACP,OAAO,MAAM,KAAK,oBAAoB,KAAK;GAE9C;AAED,UAAO,KAAK,kBAAkB,SAC1B,OACA;IACE;IACA,GAAG;GACJ;EACN;EAED,MAAM,YAAY,MAAM,SAAS,MAAM;EACvC,IAAIC;AAEJ,MAAI;GACF,YAAY,KAAK,MAAM,UAAU;EAClC,QAAO,CAEP;EAED,MAAM,QAAQ,aAAa;EAC3B,IAAI,aAAa;AAEjB,OAAK,MAAM,MAAM,aAAa,MAAM,KAClC,KAAI,IACF,aAAc,MAAM,GAAG,OAAO,UAAUF,WAAS,KAAK;EAI1D,aAAa,cAAe,CAAE;AAE9B,MAAI,KAAK,aACP,OAAM;AAIR,SAAO,KAAK,kBAAkB,SAC1B,SACA;GACE,OAAO;GACP,GAAG;EACJ;CACN;CAGD,eAAe,iBACbG,SACAC,KACAL,aACAM,aACAC,UAAkB,GACC;AACnB,MAAI;GACF,MAAMN,YAAU,IAAI,QAAQ,KAAK;GACjC,MAAM,WAAW,MAAM,QAAQA,UAAQ;AAEvC,OAAI,SAAS,WAAW,OAAO,UAAU,YAAY,YAAY;IAC/D,MAAM,aAAa,SAAS,QAAQ,IAAI,cAAc;IACtD,MAAM,aAAa,aAAa,SAAS,WAAW,GAAG,MAAO,oBAAoB,SAAS,YAAY,WAAW;IAClH,MAAM,MAAM,WAAW;AAGvB,WAAO,iBAAiB,SAAS,KAAK,aAAa,aAAa,UAAU,EAAE;GAC7E;AAED,UAAO;EACR,SAAQ,OAAO;AAEd,OAAI,UAAU,YAAY,YAAY;IACpC,MAAMO,UAAQ,YAAY;IAC1B,MAAM,IAAI,QAAQ,aAAW,WAAW,SAASA,QAAM;AAGvD,WAAO,iBAAiB,SAAS,KAAK,aAAa,aAAa,UAAU,EAAE;GAC7E;AAED,SAAM;EACP;CACF;AAED,QAAO;EACL;EACA,SAAS,CAAC,YAAY,QAAQ;GAAE,GAAG;GAAS,QAAQ;EAAW,EAAC;EAChE,QAAQ,CAAC,YAAY,QAAQ;GAAE,GAAG;GAAS,QAAQ;EAAU,EAAC;EAC9D,KAAK,CAAC,YAAY,QAAQ;GAAE,GAAG;GAAS,QAAQ;EAAO,EAAC;EACxD;EACA,MAAM,CAAC,YAAY,QAAQ;GAAE,GAAG;GAAS,QAAQ;EAAQ,EAAC;EAC1D;EACA,SAAS,CAAC,YAAY,QAAQ;GAAE,GAAG;GAAS,QAAQ;EAAW,EAAC;EAChE,OAAO,CAAC,YAAY,QAAQ;GAAE,GAAG;GAAS,QAAQ;EAAS,EAAC;EAC5D,MAAM,CAAC,YAAY,QAAQ;GAAE,GAAG;GAAS,QAAQ;EAAQ,EAAC;EAC1D,KAAK,CAAC,YAAY,QAAQ;GAAE,GAAG;GAAS,QAAQ;EAAO,EAAC;EACxD;EACA;EACA,OAAO,CAAC,YAAY,QAAQ;GAAE,GAAG;GAAS,QAAQ;EAAS,EAAC;CAC7D;AACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/utils/calculate-retry-delay.ts
|
|
3
|
+
/**
|
|
4
|
+
* Calculates the delay for the next retry attempt using exponential backoff with full jitter.
|
|
5
|
+
*
|
|
6
|
+
* @param attempt The current retry attempt number (starting from 0 for the first retry).
|
|
7
|
+
* @param baseDelay The initial delay in milliseconds (e.g., 100).
|
|
8
|
+
* @param maxDelay The maximum possible delay in milliseconds (e.g., 20000).
|
|
9
|
+
* @returns The calculated delay in milliseconds to wait before the next attempt.
|
|
10
|
+
*/
|
|
11
|
+
function calculateRetryDelay(attempt, baseDelay = 100, maxDelay = 2e4) {
|
|
12
|
+
const exponentialBackoff = baseDelay * 2 ** attempt;
|
|
13
|
+
const cappedBackoff = Math.min(exponentialBackoff, maxDelay);
|
|
14
|
+
const jitter = Math.random() * cappedBackoff;
|
|
15
|
+
return jitter;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
exports.calculateRetryDelay = calculateRetryDelay;
|
|
20
|
+
//# sourceMappingURL=calculate-retry-delay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calculate-retry-delay.js","names":["attempt: number","baseDelay: number","maxDelay: number"],"sources":["../../src/utils/calculate-retry-delay.ts"],"sourcesContent":["/**\n * Calculates the delay for the next retry attempt using exponential backoff with full jitter.\n *\n * @param attempt The current retry attempt number (starting from 0 for the first retry).\n * @param baseDelay The initial delay in milliseconds (e.g., 100).\n * @param maxDelay The maximum possible delay in milliseconds (e.g., 20000).\n * @returns The calculated delay in milliseconds to wait before the next attempt.\n */\nexport function calculateRetryDelay(\n attempt: number,\n baseDelay: number = 100,\n maxDelay: number = 20000,\n): number {\n const exponentialBackoff = baseDelay * 2 ** attempt;\n const cappedBackoff = Math.min(exponentialBackoff, maxDelay);\n // Apply full jitter: a random value between 0 and the capped backoff\n const jitter = Math.random() * cappedBackoff;\n\n return jitter;\n}\n"],"mappings":";;;;;;;;;;AAQA,SAAgB,oBACdA,SACAC,YAAoB,KACpBC,WAAmB,KACX;CACR,MAAM,qBAAqB,YAAY,KAAK;CAC5C,MAAM,gBAAgB,KAAK,IAAI,oBAAoB,SAAS;CAE5D,MAAM,SAAS,KAAK,QAAQ,GAAG;AAE/B,QAAO;AACR"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/utils/calculate-retry-delay.ts
|
|
2
|
+
/**
|
|
3
|
+
* Calculates the delay for the next retry attempt using exponential backoff with full jitter.
|
|
4
|
+
*
|
|
5
|
+
* @param attempt The current retry attempt number (starting from 0 for the first retry).
|
|
6
|
+
* @param baseDelay The initial delay in milliseconds (e.g., 100).
|
|
7
|
+
* @param maxDelay The maximum possible delay in milliseconds (e.g., 20000).
|
|
8
|
+
* @returns The calculated delay in milliseconds to wait before the next attempt.
|
|
9
|
+
*/
|
|
10
|
+
function calculateRetryDelay(attempt, baseDelay = 100, maxDelay = 2e4) {
|
|
11
|
+
const exponentialBackoff = baseDelay * 2 ** attempt;
|
|
12
|
+
const cappedBackoff = Math.min(exponentialBackoff, maxDelay);
|
|
13
|
+
const jitter = Math.random() * cappedBackoff;
|
|
14
|
+
return jitter;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { calculateRetryDelay };
|
|
19
|
+
//# sourceMappingURL=calculate-retry-delay.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calculate-retry-delay.mjs","names":["attempt: number","baseDelay: number","maxDelay: number"],"sources":["../../src/utils/calculate-retry-delay.ts"],"sourcesContent":["/**\n * Calculates the delay for the next retry attempt using exponential backoff with full jitter.\n *\n * @param attempt The current retry attempt number (starting from 0 for the first retry).\n * @param baseDelay The initial delay in milliseconds (e.g., 100).\n * @param maxDelay The maximum possible delay in milliseconds (e.g., 20000).\n * @returns The calculated delay in milliseconds to wait before the next attempt.\n */\nexport function calculateRetryDelay(\n attempt: number,\n baseDelay: number = 100,\n maxDelay: number = 20000,\n): number {\n const exponentialBackoff = baseDelay * 2 ** attempt;\n const cappedBackoff = Math.min(exponentialBackoff, maxDelay);\n // Apply full jitter: a random value between 0 and the capped backoff\n const jitter = Math.random() * cappedBackoff;\n\n return jitter;\n}\n"],"mappings":";;;;;;;;;AAQA,SAAgB,oBACdA,SACAC,YAAoB,KACpBC,WAAmB,KACX;CACR,MAAM,qBAAqB,YAAY,KAAK;CAC5C,MAAM,gBAAgB,KAAK,IAAI,oBAAoB,SAAS;CAE5D,MAAM,SAAS,KAAK,QAAQ,GAAG;AAE/B,QAAO;AACR"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delay.js","names":["ms: number"],"sources":["../../src/utils/delay.ts"],"sourcesContent":["export const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));\n"],"mappings":";;AAAA,MAAa,QAAQ,CAACA,OAAe,IAAI,QAAQ,aAAW,WAAW,SAAS,GAAG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delay.mjs","names":["ms: number"],"sources":["../../src/utils/delay.ts"],"sourcesContent":["export const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));\n"],"mappings":";AAAA,MAAa,QAAQ,CAACA,OAAe,IAAI,QAAQ,aAAW,WAAW,SAAS,GAAG"}
|