@cmssy/core 12.9.0 → 12.11.0

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.
@@ -1,6 +1,6 @@
1
- export { C as CMSSY_LOCALE_HEADER, a as localeForPath, b as localeForPathname, l as localizeHref, c as resolveSiteLocales, s as splitCmssyLocale, d as splitLocaleFromPath } from '../locale-BM2fwE3O.cjs';
1
+ export { C as CMSSY_LOCALE_HEADER, a as localeForPath, b as localeForPathname, l as localizeHref, c as resolveSiteLocales, s as splitCmssyLocale, d as splitLocaleFromPath } from '../locale-JyBHqpAQ.cjs';
2
2
  export { CmssySiteLocales } from '@cmssy/types';
3
- import '../content-client-COeyYJl1.cjs';
3
+ import '../content-client-65N8ZRq9.cjs';
4
4
 
5
5
  declare function localizedPath(slug: string, locale: string, defaultLocale: string): string;
6
6
 
@@ -1,6 +1,6 @@
1
- export { C as CMSSY_LOCALE_HEADER, a as localeForPath, b as localeForPathname, l as localizeHref, c as resolveSiteLocales, s as splitCmssyLocale, d as splitLocaleFromPath } from '../locale-jxsO_8YF.js';
1
+ export { C as CMSSY_LOCALE_HEADER, a as localeForPath, b as localeForPathname, l as localizeHref, c as resolveSiteLocales, s as splitCmssyLocale, d as splitLocaleFromPath } from '../locale-D9KI4bND.js';
2
2
  export { CmssySiteLocales } from '@cmssy/types';
3
- import '../content-client-COeyYJl1.js';
3
+ import '../content-client-65N8ZRq9.js';
4
4
 
5
5
  declare function localizedPath(slug: string, locale: string, defaultLocale: string): string;
6
6
 
@@ -2,15 +2,64 @@
2
2
  var CmssyRequestError = class extends Error {
3
3
  status;
4
4
  retryAfterMs;
5
- constructor(message, status, retryAfterMs2) {
5
+ waitedMs;
6
+ constructor(message, status, retryAfterMs2, waitedMs) {
6
7
  super(message);
7
8
  this.name = "CmssyRequestError";
8
9
  this.status = status;
9
10
  if (retryAfterMs2 !== void 0) this.retryAfterMs = retryAfterMs2;
11
+ if (waitedMs !== void 0) this.waitedMs = waitedMs;
10
12
  }
11
13
  };
12
- var DEFAULT_RETRY_STATUSES = [429, 503];
14
+ var THROTTLE_STATUS = 429;
13
15
  var CMSSY_RATE_LIMIT_WINDOW_MS = 6e4;
16
+ var CMSSY_RETRY_MODES = {
17
+ build: {
18
+ maxRetries: 4,
19
+ baseDelayMs: 300,
20
+ throttleBaseDelayMs: 1e3,
21
+ maxDelayMs: 2e4,
22
+ maxRetryAfterMs: CMSSY_RATE_LIMIT_WINDOW_MS,
23
+ maxTotalWaitMs: 18e4,
24
+ retryStatuses: Object.freeze([429, 503])
25
+ },
26
+ interactive: {
27
+ maxRetries: 2,
28
+ baseDelayMs: 50,
29
+ throttleBaseDelayMs: 500,
30
+ maxDelayMs: 1e3,
31
+ maxRetryAfterMs: 1e3,
32
+ maxTotalWaitMs: 2e3,
33
+ retryStatuses: Object.freeze([429, 503])
34
+ }
35
+ };
36
+ Object.freeze(CMSSY_RETRY_MODES.build);
37
+ Object.freeze(CMSSY_RETRY_MODES.interactive);
38
+ Object.freeze(CMSSY_RETRY_MODES);
39
+ function resolveRetryPolicy(retry) {
40
+ if (retry === false || retry === void 0) return null;
41
+ if (typeof retry === "string") {
42
+ const mode = CMSSY_RETRY_MODES[retry];
43
+ if (!mode) {
44
+ throw new Error(
45
+ `cmssy: unknown retry mode "${retry}" - use ${Object.keys(
46
+ CMSSY_RETRY_MODES
47
+ ).map((name) => `"${name}"`).join(" or ")}, false, or a retry policy object`
48
+ );
49
+ }
50
+ return mode;
51
+ }
52
+ const base = CMSSY_RETRY_MODES.build;
53
+ return {
54
+ maxRetries: retry.maxRetries ?? base.maxRetries,
55
+ baseDelayMs: retry.baseDelayMs ?? base.baseDelayMs,
56
+ throttleBaseDelayMs: retry.throttleBaseDelayMs ?? base.throttleBaseDelayMs,
57
+ maxDelayMs: retry.maxDelayMs ?? base.maxDelayMs,
58
+ maxRetryAfterMs: retry.maxRetryAfterMs ?? base.maxRetryAfterMs,
59
+ maxTotalWaitMs: retry.maxTotalWaitMs ?? base.maxTotalWaitMs,
60
+ retryStatuses: retry.retryStatuses ?? base.retryStatuses
61
+ };
62
+ }
14
63
  function retryAfterMs(response) {
15
64
  const raw = response.headers?.get("retry-after");
16
65
  if (!raw) return null;
@@ -37,28 +86,40 @@ function sleep(ms, signal) {
37
86
  signal?.addEventListener("abort", onAbort, { once: true });
38
87
  });
39
88
  }
40
- async function fetchWithRetry(doFetch, url, init, retry) {
41
- if (retry === false || retry === void 0) {
42
- return doFetch(url, init);
89
+ async function fetchWithRetry(doFetch, url, init, retry, label) {
90
+ const policy = resolveRetryPolicy(retry);
91
+ if (policy === null) {
92
+ return { response: await doFetch(url, init), waitedMs: 0 };
43
93
  }
44
- const maxRetries = retry.maxRetries ?? 3;
45
- const baseDelayMs = retry.baseDelayMs ?? 300;
46
- const maxDelayMs = retry.maxDelayMs ?? 3e3;
47
- const maxRetryAfterMs = retry.maxRetryAfterMs ?? CMSSY_RATE_LIMIT_WINDOW_MS;
48
- const retryStatuses = retry.retryStatuses ?? DEFAULT_RETRY_STATUSES;
49
94
  let response = await doFetch(url, init);
50
- for (let attempt = 0; attempt < maxRetries; attempt++) {
51
- if (response.ok || !retryStatuses.includes(response.status)) {
52
- return response;
95
+ let waitedMs = 0;
96
+ for (let attempt = 0; attempt < policy.maxRetries; attempt++) {
97
+ if (response.ok || !policy.retryStatuses.includes(response.status)) {
98
+ return { response, waitedMs };
53
99
  }
100
+ const throttled = response.status === THROTTLE_STATUS;
54
101
  const asked = retryAfterMs(response);
55
- if (asked !== null && asked > maxRetryAfterMs) return response;
56
- const backoff = baseDelayMs * 2 ** attempt;
57
- const wait = asked !== null ? asked : Math.min(backoff, maxDelayMs);
102
+ if (asked !== null && asked > policy.maxRetryAfterMs) {
103
+ return { response, waitedMs };
104
+ }
105
+ const base = throttled ? policy.throttleBaseDelayMs : policy.baseDelayMs;
106
+ const wait = Math.round(
107
+ asked !== null ? Math.min(
108
+ asked + Math.random() * policy.throttleBaseDelayMs,
109
+ policy.maxRetryAfterMs
110
+ ) : Math.random() * Math.min(base * 2 ** attempt, policy.maxDelayMs)
111
+ );
112
+ if (waitedMs + wait > policy.maxTotalWaitMs) {
113
+ return { response, waitedMs };
114
+ }
115
+ console.warn(
116
+ `[cmssy] ${label} got ${response.status}, retrying in ${wait}ms (attempt ${attempt + 1}/${policy.maxRetries})`
117
+ );
58
118
  await sleep(wait, init.signal);
119
+ waitedMs += wait;
59
120
  response = await doFetch(url, init);
60
121
  }
61
- return response;
122
+ return { response, waitedMs };
62
123
  }
63
124
  async function postGraphql(url, query, variables, options) {
64
125
  const doFetch = options.fetch ?? globalThis.fetch;
@@ -67,7 +128,7 @@ async function postGraphql(url, query, variables, options) {
67
128
  "cmssy: no fetch implementation available - pass options.fetch"
68
129
  );
69
130
  }
70
- const response = await fetchWithRetry(
131
+ const { response, waitedMs } = await fetchWithRetry(
71
132
  doFetch,
72
133
  url,
73
134
  {
@@ -76,7 +137,8 @@ async function postGraphql(url, query, variables, options) {
76
137
  body: JSON.stringify({ query, variables }),
77
138
  signal: options.signal
78
139
  },
79
- options.retry
140
+ options.retry,
141
+ options.label
80
142
  );
81
143
  if (!response.ok) {
82
144
  let detail = "";
@@ -90,10 +152,12 @@ async function postGraphql(url, query, variables, options) {
90
152
  }
91
153
  const asked = retryAfterMs(response);
92
154
  const wait = asked !== null ? ` - retry after ${Math.ceil(asked / 1e3)}s` : "";
155
+ const spent = waitedMs > 0 ? ` - gave up after waiting ${waitedMs}ms` : "";
93
156
  throw new CmssyRequestError(
94
- `cmssy: ${options.label} failed (${response.status})${detail}${wait}`,
157
+ `cmssy: ${options.label} failed (${response.status})${detail}${wait}${spent}`,
95
158
  response.status,
96
- asked ?? void 0
159
+ asked ?? void 0,
160
+ waitedMs > 0 ? waitedMs : void 0
97
161
  );
98
162
  }
99
163
  let json;
@@ -182,7 +246,7 @@ async function loadSiteLocales(config, options) {
182
246
  config,
183
247
  SITE_CONFIG_QUERY,
184
248
  { workspaceSlug: config.workspaceSlug },
185
- { ...options, public: true, retry: options?.retry ?? {} },
249
+ { ...options, public: true, retry: options?.retry ?? "build" },
186
250
  "site config"
187
251
  );
188
252
  value = localesFromSiteConfig(data.public?.siteConfig ?? null);
package/dist/internal.cjs CHANGED
@@ -4,15 +4,64 @@
4
4
  var CmssyRequestError = class extends Error {
5
5
  status;
6
6
  retryAfterMs;
7
- constructor(message, status, retryAfterMs2) {
7
+ waitedMs;
8
+ constructor(message, status, retryAfterMs2, waitedMs) {
8
9
  super(message);
9
10
  this.name = "CmssyRequestError";
10
11
  this.status = status;
11
12
  if (retryAfterMs2 !== void 0) this.retryAfterMs = retryAfterMs2;
13
+ if (waitedMs !== void 0) this.waitedMs = waitedMs;
12
14
  }
13
15
  };
14
- var DEFAULT_RETRY_STATUSES = [429, 503];
16
+ var THROTTLE_STATUS = 429;
15
17
  var CMSSY_RATE_LIMIT_WINDOW_MS = 6e4;
18
+ var CMSSY_RETRY_MODES = {
19
+ build: {
20
+ maxRetries: 4,
21
+ baseDelayMs: 300,
22
+ throttleBaseDelayMs: 1e3,
23
+ maxDelayMs: 2e4,
24
+ maxRetryAfterMs: CMSSY_RATE_LIMIT_WINDOW_MS,
25
+ maxTotalWaitMs: 18e4,
26
+ retryStatuses: Object.freeze([429, 503])
27
+ },
28
+ interactive: {
29
+ maxRetries: 2,
30
+ baseDelayMs: 50,
31
+ throttleBaseDelayMs: 500,
32
+ maxDelayMs: 1e3,
33
+ maxRetryAfterMs: 1e3,
34
+ maxTotalWaitMs: 2e3,
35
+ retryStatuses: Object.freeze([429, 503])
36
+ }
37
+ };
38
+ Object.freeze(CMSSY_RETRY_MODES.build);
39
+ Object.freeze(CMSSY_RETRY_MODES.interactive);
40
+ Object.freeze(CMSSY_RETRY_MODES);
41
+ function resolveRetryPolicy(retry) {
42
+ if (retry === false || retry === void 0) return null;
43
+ if (typeof retry === "string") {
44
+ const mode = CMSSY_RETRY_MODES[retry];
45
+ if (!mode) {
46
+ throw new Error(
47
+ `cmssy: unknown retry mode "${retry}" - use ${Object.keys(
48
+ CMSSY_RETRY_MODES
49
+ ).map((name2) => `"${name2}"`).join(" or ")}, false, or a retry policy object`
50
+ );
51
+ }
52
+ return mode;
53
+ }
54
+ const base = CMSSY_RETRY_MODES.build;
55
+ return {
56
+ maxRetries: retry.maxRetries ?? base.maxRetries,
57
+ baseDelayMs: retry.baseDelayMs ?? base.baseDelayMs,
58
+ throttleBaseDelayMs: retry.throttleBaseDelayMs ?? base.throttleBaseDelayMs,
59
+ maxDelayMs: retry.maxDelayMs ?? base.maxDelayMs,
60
+ maxRetryAfterMs: retry.maxRetryAfterMs ?? base.maxRetryAfterMs,
61
+ maxTotalWaitMs: retry.maxTotalWaitMs ?? base.maxTotalWaitMs,
62
+ retryStatuses: retry.retryStatuses ?? base.retryStatuses
63
+ };
64
+ }
16
65
  function retryAfterMs(response) {
17
66
  const raw = response.headers?.get("retry-after");
18
67
  if (!raw) return null;
@@ -39,28 +88,40 @@ function sleep(ms, signal) {
39
88
  signal?.addEventListener("abort", onAbort, { once: true });
40
89
  });
41
90
  }
42
- async function fetchWithRetry(doFetch, url, init, retry) {
43
- if (retry === false || retry === void 0) {
44
- return doFetch(url, init);
91
+ async function fetchWithRetry(doFetch, url, init, retry, label) {
92
+ const policy = resolveRetryPolicy(retry);
93
+ if (policy === null) {
94
+ return { response: await doFetch(url, init), waitedMs: 0 };
45
95
  }
46
- const maxRetries = retry.maxRetries ?? 3;
47
- const baseDelayMs = retry.baseDelayMs ?? 300;
48
- const maxDelayMs = retry.maxDelayMs ?? 3e3;
49
- const maxRetryAfterMs = retry.maxRetryAfterMs ?? CMSSY_RATE_LIMIT_WINDOW_MS;
50
- const retryStatuses = retry.retryStatuses ?? DEFAULT_RETRY_STATUSES;
51
96
  let response = await doFetch(url, init);
52
- for (let attempt = 0; attempt < maxRetries; attempt++) {
53
- if (response.ok || !retryStatuses.includes(response.status)) {
54
- return response;
97
+ let waitedMs = 0;
98
+ for (let attempt = 0; attempt < policy.maxRetries; attempt++) {
99
+ if (response.ok || !policy.retryStatuses.includes(response.status)) {
100
+ return { response, waitedMs };
55
101
  }
102
+ const throttled = response.status === THROTTLE_STATUS;
56
103
  const asked = retryAfterMs(response);
57
- if (asked !== null && asked > maxRetryAfterMs) return response;
58
- const backoff = baseDelayMs * 2 ** attempt;
59
- const wait = asked !== null ? asked : Math.min(backoff, maxDelayMs);
104
+ if (asked !== null && asked > policy.maxRetryAfterMs) {
105
+ return { response, waitedMs };
106
+ }
107
+ const base = throttled ? policy.throttleBaseDelayMs : policy.baseDelayMs;
108
+ const wait = Math.round(
109
+ asked !== null ? Math.min(
110
+ asked + Math.random() * policy.throttleBaseDelayMs,
111
+ policy.maxRetryAfterMs
112
+ ) : Math.random() * Math.min(base * 2 ** attempt, policy.maxDelayMs)
113
+ );
114
+ if (waitedMs + wait > policy.maxTotalWaitMs) {
115
+ return { response, waitedMs };
116
+ }
117
+ console.warn(
118
+ `[cmssy] ${label} got ${response.status}, retrying in ${wait}ms (attempt ${attempt + 1}/${policy.maxRetries})`
119
+ );
60
120
  await sleep(wait, init.signal);
121
+ waitedMs += wait;
61
122
  response = await doFetch(url, init);
62
123
  }
63
- return response;
124
+ return { response, waitedMs };
64
125
  }
65
126
  async function postGraphql(url, query, variables, options) {
66
127
  const doFetch = options.fetch ?? globalThis.fetch;
@@ -69,7 +130,7 @@ async function postGraphql(url, query, variables, options) {
69
130
  "cmssy: no fetch implementation available - pass options.fetch"
70
131
  );
71
132
  }
72
- const response = await fetchWithRetry(
133
+ const { response, waitedMs } = await fetchWithRetry(
73
134
  doFetch,
74
135
  url,
75
136
  {
@@ -78,7 +139,8 @@ async function postGraphql(url, query, variables, options) {
78
139
  body: JSON.stringify({ query, variables }),
79
140
  signal: options.signal
80
141
  },
81
- options.retry
142
+ options.retry,
143
+ options.label
82
144
  );
83
145
  if (!response.ok) {
84
146
  let detail = "";
@@ -92,10 +154,12 @@ async function postGraphql(url, query, variables, options) {
92
154
  }
93
155
  const asked = retryAfterMs(response);
94
156
  const wait = asked !== null ? ` - retry after ${Math.ceil(asked / 1e3)}s` : "";
157
+ const spent = waitedMs > 0 ? ` - gave up after waiting ${waitedMs}ms` : "";
95
158
  throw new CmssyRequestError(
96
- `cmssy: ${options.label} failed (${response.status})${detail}${wait}`,
159
+ `cmssy: ${options.label} failed (${response.status})${detail}${wait}${spent}`,
97
160
  response.status,
98
- asked ?? void 0
161
+ asked ?? void 0,
162
+ waitedMs > 0 ? waitedMs : void 0
99
163
  );
100
164
  }
101
165
  let json;
@@ -232,7 +296,7 @@ async function fetchPage(config, path, options = {}) {
232
296
  fetch: options.fetch,
233
297
  signal: options.signal,
234
298
  headers,
235
- retry: options.retry ?? {},
299
+ retry: options.retry ?? "build",
236
300
  label: "page fetch"
237
301
  }
238
302
  );
@@ -255,7 +319,7 @@ async function fetchPageById(config, pageId, options = {}) {
255
319
  {
256
320
  fetch: options.fetch,
257
321
  signal: options.signal,
258
- retry: options.retry ?? {},
322
+ retry: options.retry ?? "build",
259
323
  label: "page-by-id fetch"
260
324
  }
261
325
  );
@@ -276,7 +340,7 @@ async function fetchPages(config, options = {}) {
276
340
  {
277
341
  fetch: options.fetch,
278
342
  signal: options.signal,
279
- retry: options.retry ?? {},
343
+ retry: options.retry ?? "build",
280
344
  label: "pages fetch"
281
345
  }
282
346
  );
@@ -291,7 +355,7 @@ async function fetchPageMeta(config, path, options = {}) {
291
355
  {
292
356
  fetch: options.fetch,
293
357
  signal: options.signal,
294
- retry: options.retry ?? {},
358
+ retry: options.retry ?? "build",
295
359
  label: "page meta fetch"
296
360
  }
297
361
  );
@@ -308,7 +372,7 @@ async function fetchLayouts(config, path, options = {}) {
308
372
  {
309
373
  fetch: options.fetch,
310
374
  signal: options.signal,
311
- retry: options.retry ?? {},
375
+ retry: options.retry ?? "build",
312
376
  label: "layouts fetch"
313
377
  }
314
378
  );
@@ -434,7 +498,7 @@ async function fetchSiteConfig(config, options = {}) {
434
498
  config,
435
499
  SITE_CONFIG_QUERY,
436
500
  { workspaceSlug: config.workspaceSlug },
437
- { ...options, public: true, retry: options.retry ?? {} },
501
+ { ...options, public: true, retry: options.retry ?? "build" },
438
502
  "site config query"
439
503
  );
440
504
  return data.public?.siteConfig ?? null;
@@ -972,7 +1036,7 @@ async function loadSiteLocales(config, options) {
972
1036
  config,
973
1037
  SITE_CONFIG_QUERY,
974
1038
  { workspaceSlug: config.workspaceSlug },
975
- { ...options, public: true, retry: options?.retry ?? {} },
1039
+ { ...options, public: true, retry: options?.retry ?? "build" },
976
1040
  "site config"
977
1041
  );
978
1042
  value = localesFromSiteConfig(data.public?.siteConfig ?? null);
@@ -1,10 +1,10 @@
1
- export { f as fetchLayouts, d as fetchPage, e as fetchPageById, g as fetchPageMeta, h as fetchPages, r as resolveApiUrl } from './content-client-COeyYJl1.cjs';
1
+ export { g as fetchLayouts, h as fetchPage, i as fetchPageById, j as fetchPageMeta, k as fetchPages, r as resolveApiUrl } from './content-client-65N8ZRq9.cjs';
2
2
  import { CmssyFormDefinition, BuildBlockContextExtra, CmssyBlockContext, CmssyClientConfig, CmssySiteConfig, FieldDefinition, RawBlock } from '@cmssy/types';
3
3
  export { CmssySiteLocales } from '@cmssy/types';
4
- import { G as GraphqlRequestOptions } from './locale-BM2fwE3O.cjs';
5
- export { C as CMSSY_LOCALE_HEADER, a as localeForPath, b as localeForPathname, l as localizeHref, c as resolveSiteLocales, s as splitCmssyLocale, d as splitLocaleFromPath } from './locale-BM2fwE3O.cjs';
6
- import { Q as QueryScopedOptions } from './csp-BMP9bAZ9.cjs';
7
- export { u as cmssyCspHeaders, v as isDevelopment, w as resolveInitialTarget, x as toCspOrigin } from './csp-BMP9bAZ9.cjs';
4
+ import { G as GraphqlRequestOptions } from './locale-JyBHqpAQ.cjs';
5
+ export { C as CMSSY_LOCALE_HEADER, a as localeForPath, b as localeForPathname, l as localizeHref, c as resolveSiteLocales, s as splitCmssyLocale, d as splitLocaleFromPath } from './locale-JyBHqpAQ.cjs';
6
+ import { Q as QueryScopedOptions } from './csp-RaStdD2i.cjs';
7
+ export { u as cmssyCspHeaders, v as isDevelopment, w as resolveInitialTarget, x as toCspOrigin } from './csp-RaStdD2i.cjs';
8
8
  export { localizedPath } from './internal/locale.cjs';
9
9
 
10
10
  declare const SITE_CONFIG_QUERY = "query PublicSiteConfig($workspaceSlug: String!) {\n public {\n siteConfig(workspaceSlug: $workspaceSlug) {\n id\n workspaceId\n siteName\n defaultLanguage\n enabledLanguages\n enabledFeatures\n notFoundPageId\n previewUrl\n branding {\n brandName\n logoUrl\n logoDarkUrl\n faviconUrl\n ogImageUrl\n }\n }\n }\n}";
@@ -1,10 +1,10 @@
1
- export { f as fetchLayouts, d as fetchPage, e as fetchPageById, g as fetchPageMeta, h as fetchPages, r as resolveApiUrl } from './content-client-COeyYJl1.js';
1
+ export { g as fetchLayouts, h as fetchPage, i as fetchPageById, j as fetchPageMeta, k as fetchPages, r as resolveApiUrl } from './content-client-65N8ZRq9.js';
2
2
  import { CmssyFormDefinition, BuildBlockContextExtra, CmssyBlockContext, CmssyClientConfig, CmssySiteConfig, FieldDefinition, RawBlock } from '@cmssy/types';
3
3
  export { CmssySiteLocales } from '@cmssy/types';
4
- import { G as GraphqlRequestOptions } from './locale-jxsO_8YF.js';
5
- export { C as CMSSY_LOCALE_HEADER, a as localeForPath, b as localeForPathname, l as localizeHref, c as resolveSiteLocales, s as splitCmssyLocale, d as splitLocaleFromPath } from './locale-jxsO_8YF.js';
6
- import { Q as QueryScopedOptions } from './csp-BDE0cAZL.js';
7
- export { u as cmssyCspHeaders, v as isDevelopment, w as resolveInitialTarget, x as toCspOrigin } from './csp-BDE0cAZL.js';
4
+ import { G as GraphqlRequestOptions } from './locale-D9KI4bND.js';
5
+ export { C as CMSSY_LOCALE_HEADER, a as localeForPath, b as localeForPathname, l as localizeHref, c as resolveSiteLocales, s as splitCmssyLocale, d as splitLocaleFromPath } from './locale-D9KI4bND.js';
6
+ import { Q as QueryScopedOptions } from './csp-BnOiGUhR.js';
7
+ export { u as cmssyCspHeaders, v as isDevelopment, w as resolveInitialTarget, x as toCspOrigin } from './csp-BnOiGUhR.js';
8
8
  export { localizedPath } from './internal/locale.js';
9
9
 
10
10
  declare const SITE_CONFIG_QUERY = "query PublicSiteConfig($workspaceSlug: String!) {\n public {\n siteConfig(workspaceSlug: $workspaceSlug) {\n id\n workspaceId\n siteName\n defaultLanguage\n enabledLanguages\n enabledFeatures\n notFoundPageId\n previewUrl\n branding {\n brandName\n logoUrl\n logoDarkUrl\n faviconUrl\n ogImageUrl\n }\n }\n }\n}";
package/dist/internal.js CHANGED
@@ -2,15 +2,64 @@
2
2
  var CmssyRequestError = class extends Error {
3
3
  status;
4
4
  retryAfterMs;
5
- constructor(message, status, retryAfterMs2) {
5
+ waitedMs;
6
+ constructor(message, status, retryAfterMs2, waitedMs) {
6
7
  super(message);
7
8
  this.name = "CmssyRequestError";
8
9
  this.status = status;
9
10
  if (retryAfterMs2 !== void 0) this.retryAfterMs = retryAfterMs2;
11
+ if (waitedMs !== void 0) this.waitedMs = waitedMs;
10
12
  }
11
13
  };
12
- var DEFAULT_RETRY_STATUSES = [429, 503];
14
+ var THROTTLE_STATUS = 429;
13
15
  var CMSSY_RATE_LIMIT_WINDOW_MS = 6e4;
16
+ var CMSSY_RETRY_MODES = {
17
+ build: {
18
+ maxRetries: 4,
19
+ baseDelayMs: 300,
20
+ throttleBaseDelayMs: 1e3,
21
+ maxDelayMs: 2e4,
22
+ maxRetryAfterMs: CMSSY_RATE_LIMIT_WINDOW_MS,
23
+ maxTotalWaitMs: 18e4,
24
+ retryStatuses: Object.freeze([429, 503])
25
+ },
26
+ interactive: {
27
+ maxRetries: 2,
28
+ baseDelayMs: 50,
29
+ throttleBaseDelayMs: 500,
30
+ maxDelayMs: 1e3,
31
+ maxRetryAfterMs: 1e3,
32
+ maxTotalWaitMs: 2e3,
33
+ retryStatuses: Object.freeze([429, 503])
34
+ }
35
+ };
36
+ Object.freeze(CMSSY_RETRY_MODES.build);
37
+ Object.freeze(CMSSY_RETRY_MODES.interactive);
38
+ Object.freeze(CMSSY_RETRY_MODES);
39
+ function resolveRetryPolicy(retry) {
40
+ if (retry === false || retry === void 0) return null;
41
+ if (typeof retry === "string") {
42
+ const mode = CMSSY_RETRY_MODES[retry];
43
+ if (!mode) {
44
+ throw new Error(
45
+ `cmssy: unknown retry mode "${retry}" - use ${Object.keys(
46
+ CMSSY_RETRY_MODES
47
+ ).map((name2) => `"${name2}"`).join(" or ")}, false, or a retry policy object`
48
+ );
49
+ }
50
+ return mode;
51
+ }
52
+ const base = CMSSY_RETRY_MODES.build;
53
+ return {
54
+ maxRetries: retry.maxRetries ?? base.maxRetries,
55
+ baseDelayMs: retry.baseDelayMs ?? base.baseDelayMs,
56
+ throttleBaseDelayMs: retry.throttleBaseDelayMs ?? base.throttleBaseDelayMs,
57
+ maxDelayMs: retry.maxDelayMs ?? base.maxDelayMs,
58
+ maxRetryAfterMs: retry.maxRetryAfterMs ?? base.maxRetryAfterMs,
59
+ maxTotalWaitMs: retry.maxTotalWaitMs ?? base.maxTotalWaitMs,
60
+ retryStatuses: retry.retryStatuses ?? base.retryStatuses
61
+ };
62
+ }
14
63
  function retryAfterMs(response) {
15
64
  const raw = response.headers?.get("retry-after");
16
65
  if (!raw) return null;
@@ -37,28 +86,40 @@ function sleep(ms, signal) {
37
86
  signal?.addEventListener("abort", onAbort, { once: true });
38
87
  });
39
88
  }
40
- async function fetchWithRetry(doFetch, url, init, retry) {
41
- if (retry === false || retry === void 0) {
42
- return doFetch(url, init);
89
+ async function fetchWithRetry(doFetch, url, init, retry, label) {
90
+ const policy = resolveRetryPolicy(retry);
91
+ if (policy === null) {
92
+ return { response: await doFetch(url, init), waitedMs: 0 };
43
93
  }
44
- const maxRetries = retry.maxRetries ?? 3;
45
- const baseDelayMs = retry.baseDelayMs ?? 300;
46
- const maxDelayMs = retry.maxDelayMs ?? 3e3;
47
- const maxRetryAfterMs = retry.maxRetryAfterMs ?? CMSSY_RATE_LIMIT_WINDOW_MS;
48
- const retryStatuses = retry.retryStatuses ?? DEFAULT_RETRY_STATUSES;
49
94
  let response = await doFetch(url, init);
50
- for (let attempt = 0; attempt < maxRetries; attempt++) {
51
- if (response.ok || !retryStatuses.includes(response.status)) {
52
- return response;
95
+ let waitedMs = 0;
96
+ for (let attempt = 0; attempt < policy.maxRetries; attempt++) {
97
+ if (response.ok || !policy.retryStatuses.includes(response.status)) {
98
+ return { response, waitedMs };
53
99
  }
100
+ const throttled = response.status === THROTTLE_STATUS;
54
101
  const asked = retryAfterMs(response);
55
- if (asked !== null && asked > maxRetryAfterMs) return response;
56
- const backoff = baseDelayMs * 2 ** attempt;
57
- const wait = asked !== null ? asked : Math.min(backoff, maxDelayMs);
102
+ if (asked !== null && asked > policy.maxRetryAfterMs) {
103
+ return { response, waitedMs };
104
+ }
105
+ const base = throttled ? policy.throttleBaseDelayMs : policy.baseDelayMs;
106
+ const wait = Math.round(
107
+ asked !== null ? Math.min(
108
+ asked + Math.random() * policy.throttleBaseDelayMs,
109
+ policy.maxRetryAfterMs
110
+ ) : Math.random() * Math.min(base * 2 ** attempt, policy.maxDelayMs)
111
+ );
112
+ if (waitedMs + wait > policy.maxTotalWaitMs) {
113
+ return { response, waitedMs };
114
+ }
115
+ console.warn(
116
+ `[cmssy] ${label} got ${response.status}, retrying in ${wait}ms (attempt ${attempt + 1}/${policy.maxRetries})`
117
+ );
58
118
  await sleep(wait, init.signal);
119
+ waitedMs += wait;
59
120
  response = await doFetch(url, init);
60
121
  }
61
- return response;
122
+ return { response, waitedMs };
62
123
  }
63
124
  async function postGraphql(url, query, variables, options) {
64
125
  const doFetch = options.fetch ?? globalThis.fetch;
@@ -67,7 +128,7 @@ async function postGraphql(url, query, variables, options) {
67
128
  "cmssy: no fetch implementation available - pass options.fetch"
68
129
  );
69
130
  }
70
- const response = await fetchWithRetry(
131
+ const { response, waitedMs } = await fetchWithRetry(
71
132
  doFetch,
72
133
  url,
73
134
  {
@@ -76,7 +137,8 @@ async function postGraphql(url, query, variables, options) {
76
137
  body: JSON.stringify({ query, variables }),
77
138
  signal: options.signal
78
139
  },
79
- options.retry
140
+ options.retry,
141
+ options.label
80
142
  );
81
143
  if (!response.ok) {
82
144
  let detail = "";
@@ -90,10 +152,12 @@ async function postGraphql(url, query, variables, options) {
90
152
  }
91
153
  const asked = retryAfterMs(response);
92
154
  const wait = asked !== null ? ` - retry after ${Math.ceil(asked / 1e3)}s` : "";
155
+ const spent = waitedMs > 0 ? ` - gave up after waiting ${waitedMs}ms` : "";
93
156
  throw new CmssyRequestError(
94
- `cmssy: ${options.label} failed (${response.status})${detail}${wait}`,
157
+ `cmssy: ${options.label} failed (${response.status})${detail}${wait}${spent}`,
95
158
  response.status,
96
- asked ?? void 0
159
+ asked ?? void 0,
160
+ waitedMs > 0 ? waitedMs : void 0
97
161
  );
98
162
  }
99
163
  let json;
@@ -230,7 +294,7 @@ async function fetchPage(config, path, options = {}) {
230
294
  fetch: options.fetch,
231
295
  signal: options.signal,
232
296
  headers,
233
- retry: options.retry ?? {},
297
+ retry: options.retry ?? "build",
234
298
  label: "page fetch"
235
299
  }
236
300
  );
@@ -253,7 +317,7 @@ async function fetchPageById(config, pageId, options = {}) {
253
317
  {
254
318
  fetch: options.fetch,
255
319
  signal: options.signal,
256
- retry: options.retry ?? {},
320
+ retry: options.retry ?? "build",
257
321
  label: "page-by-id fetch"
258
322
  }
259
323
  );
@@ -274,7 +338,7 @@ async function fetchPages(config, options = {}) {
274
338
  {
275
339
  fetch: options.fetch,
276
340
  signal: options.signal,
277
- retry: options.retry ?? {},
341
+ retry: options.retry ?? "build",
278
342
  label: "pages fetch"
279
343
  }
280
344
  );
@@ -289,7 +353,7 @@ async function fetchPageMeta(config, path, options = {}) {
289
353
  {
290
354
  fetch: options.fetch,
291
355
  signal: options.signal,
292
- retry: options.retry ?? {},
356
+ retry: options.retry ?? "build",
293
357
  label: "page meta fetch"
294
358
  }
295
359
  );
@@ -306,7 +370,7 @@ async function fetchLayouts(config, path, options = {}) {
306
370
  {
307
371
  fetch: options.fetch,
308
372
  signal: options.signal,
309
- retry: options.retry ?? {},
373
+ retry: options.retry ?? "build",
310
374
  label: "layouts fetch"
311
375
  }
312
376
  );
@@ -432,7 +496,7 @@ async function fetchSiteConfig(config, options = {}) {
432
496
  config,
433
497
  SITE_CONFIG_QUERY,
434
498
  { workspaceSlug: config.workspaceSlug },
435
- { ...options, public: true, retry: options.retry ?? {} },
499
+ { ...options, public: true, retry: options.retry ?? "build" },
436
500
  "site config query"
437
501
  );
438
502
  return data.public?.siteConfig ?? null;
@@ -970,7 +1034,7 @@ async function loadSiteLocales(config, options) {
970
1034
  config,
971
1035
  SITE_CONFIG_QUERY,
972
1036
  { workspaceSlug: config.workspaceSlug },
973
- { ...options, public: true, retry: options?.retry ?? {} },
1037
+ { ...options, public: true, retry: options?.retry ?? "build" },
974
1038
  "site config"
975
1039
  );
976
1040
  value = localesFromSiteConfig(data.public?.siteConfig ?? null);
@@ -1,12 +1,12 @@
1
1
  import { CmssyClientConfig, CmssyLocaleContext, CmssySiteLocales } from '@cmssy/types';
2
- import { F as FetchLike, R as RetryPolicy } from './content-client-COeyYJl1.js';
2
+ import { F as FetchLike, R as RetryOption } from './content-client-65N8ZRq9.js';
3
3
 
4
4
  interface GraphqlRequestOptions {
5
5
  fetch?: FetchLike;
6
6
  signal?: AbortSignal;
7
7
  headers?: Record<string, string>;
8
8
  public?: boolean;
9
- retry?: RetryPolicy | false;
9
+ retry?: RetryOption;
10
10
  }
11
11
  declare function graphqlRequest<T>(config: CmssyClientConfig, query: string, variables: Record<string, unknown>, options?: GraphqlRequestOptions, label?: string): Promise<T>;
12
12