@storyblok/management-api-client 0.1.4 → 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 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: 3
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 3 times
151
+ // If rate limited, will automatically retry up to 12 times
152
152
  ```
153
153
 
154
154
  ## Runtime Configuration
@@ -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
@@ -21,7 +23,7 @@ const createClient = (config) => {
21
23
  if (!_config.baseUrl && options.path?.space_id) {
22
24
  const region = (0, __storyblok_region_helper.getRegion)(options.path.space_id, opts.region);
23
25
  if (region) opts.baseUrl = (0, __storyblok_region_helper.getManagementBaseUrl)(region, "https");
24
- } else if (opts.region) opts.baseUrl = (0, __storyblok_region_helper.getManagementBaseUrl)(opts.region, "https");
26
+ } else if (!_config.baseUrl && opts.region) opts.baseUrl = (0, __storyblok_region_helper.getManagementBaseUrl)(opts.region, "https");
25
27
  if (opts.security) await require_utils.setAuthParams({
26
28
  ...opts,
27
29
  security: opts.security
@@ -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: 3,
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 delay = retryAfter ? parseInt(retryAfter) * 1e3 : retryConfig.retryDelay;
100
- await new Promise((resolve) => setTimeout(resolve, delay));
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\n // If the baseUrl is not set and we have a space_id, we can attempt toinfer 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 (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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"}
@@ -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
@@ -20,7 +22,7 @@ const createClient = (config) => {
20
22
  if (!_config.baseUrl && options.path?.space_id) {
21
23
  const region = getRegion(options.path.space_id, opts.region);
22
24
  if (region) opts.baseUrl = getManagementBaseUrl(region, "https");
23
- } else if (opts.region) opts.baseUrl = getManagementBaseUrl(opts.region, "https");
25
+ } else if (!_config.baseUrl && opts.region) opts.baseUrl = getManagementBaseUrl(opts.region, "https");
24
26
  if (opts.security) await setAuthParams({
25
27
  ...opts,
26
28
  security: opts.security
@@ -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: 3,
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 delay = retryAfter ? parseInt(retryAfter) * 1e3 : retryConfig.retryDelay;
99
- await new Promise((resolve) => setTimeout(resolve, delay));
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\n // If the baseUrl is not set and we have a space_id, we can attempt toinfer 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 (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":"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"}
@@ -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,7 @@
1
+
2
+ //#region src/utils/delay.ts
3
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
4
+
5
+ //#endregion
6
+ exports.delay = delay;
7
+ //# sourceMappingURL=delay.js.map
@@ -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,6 @@
1
+ //#region src/utils/delay.ts
2
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3
+
4
+ //#endregion
5
+ export { delay };
6
+ //# sourceMappingURL=delay.mjs.map
@@ -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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storyblok/management-api-client",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Storyblok Management API Client",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,