@cmssy/core 12.10.0 → 12.12.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.
@@ -3,16 +3,23 @@ import { CmssyClientConfig, CmssyLayoutGroup, CmssyPageData, CmssyPageMeta, Cmss
3
3
  declare class CmssyRequestError extends Error {
4
4
  readonly status: number;
5
5
  readonly retryAfterMs?: number;
6
- constructor(message: string, status: number, retryAfterMs?: number);
6
+ readonly waitedMs?: number;
7
+ constructor(message: string, status: number, retryAfterMs?: number, waitedMs?: number);
7
8
  }
8
9
  interface RetryPolicy {
9
10
  maxRetries?: number;
10
11
  baseDelayMs?: number;
12
+ throttleBaseDelayMs?: number;
11
13
  maxDelayMs?: number;
12
14
  maxRetryAfterMs?: number;
13
- retryStatuses?: number[];
15
+ maxTotalWaitMs?: number;
16
+ retryStatuses?: readonly number[];
14
17
  }
18
+ type CmssyRetryMode = "build" | "interactive";
19
+ type RetryOption = CmssyRetryMode | RetryPolicy | false;
15
20
  declare const CMSSY_RATE_LIMIT_WINDOW_MS = 60000;
21
+ type ResolvedRetryPolicy = Readonly<Required<RetryPolicy>>;
22
+ declare const CMSSY_RETRY_MODES: Record<CmssyRetryMode, ResolvedRetryPolicy>;
16
23
 
17
24
  declare const DEFAULT_CMSSY_API_URL = "https://api.cmssy.io/graphql";
18
25
  declare function resolveApiUrl(apiUrl: string | undefined): string;
@@ -37,7 +44,7 @@ interface FetchPageOptions {
37
44
  workspaceId?: string;
38
45
  fetch?: FetchLike;
39
46
  signal?: AbortSignal;
40
- retry?: RetryPolicy | false;
47
+ retry?: RetryOption;
41
48
  }
42
49
  declare function fetchPage(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyPageData | null>;
43
50
  declare function fetchPageById(config: CmssyClientConfig, pageId: string, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageData | null>;
@@ -45,4 +52,4 @@ declare function fetchPages(config: CmssyClientConfig, options?: Pick<FetchPageO
45
52
  declare function fetchPageMeta(config: CmssyClientConfig, path: string | string[] | undefined, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageMeta | null>;
46
53
  declare function fetchLayouts(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyLayoutGroup[]>;
47
54
 
48
- export { CMSSY_RATE_LIMIT_WINDOW_MS as C, DEFAULT_CMSSY_API_URL as D, type FetchLike as F, type RetryPolicy as R, CmssyRequestError as a, type FetchLikeResponse as b, type FetchPageOptions as c, fetchPage as d, fetchPageById as e, fetchLayouts as f, fetchPageMeta as g, fetchPages as h, resolveApiUrl as r };
55
+ export { CMSSY_RATE_LIMIT_WINDOW_MS as C, DEFAULT_CMSSY_API_URL as D, type FetchLike as F, type RetryOption as R, CMSSY_RETRY_MODES as a, CmssyRequestError as b, type CmssyRetryMode as c, type FetchLikeResponse as d, type FetchPageOptions as e, type RetryPolicy as f, fetchLayouts as g, fetchPage as h, fetchPageById as i, fetchPageMeta as j, fetchPages as k, resolveApiUrl as r };
@@ -3,16 +3,23 @@ import { CmssyClientConfig, CmssyLayoutGroup, CmssyPageData, CmssyPageMeta, Cmss
3
3
  declare class CmssyRequestError extends Error {
4
4
  readonly status: number;
5
5
  readonly retryAfterMs?: number;
6
- constructor(message: string, status: number, retryAfterMs?: number);
6
+ readonly waitedMs?: number;
7
+ constructor(message: string, status: number, retryAfterMs?: number, waitedMs?: number);
7
8
  }
8
9
  interface RetryPolicy {
9
10
  maxRetries?: number;
10
11
  baseDelayMs?: number;
12
+ throttleBaseDelayMs?: number;
11
13
  maxDelayMs?: number;
12
14
  maxRetryAfterMs?: number;
13
- retryStatuses?: number[];
15
+ maxTotalWaitMs?: number;
16
+ retryStatuses?: readonly number[];
14
17
  }
18
+ type CmssyRetryMode = "build" | "interactive";
19
+ type RetryOption = CmssyRetryMode | RetryPolicy | false;
15
20
  declare const CMSSY_RATE_LIMIT_WINDOW_MS = 60000;
21
+ type ResolvedRetryPolicy = Readonly<Required<RetryPolicy>>;
22
+ declare const CMSSY_RETRY_MODES: Record<CmssyRetryMode, ResolvedRetryPolicy>;
16
23
 
17
24
  declare const DEFAULT_CMSSY_API_URL = "https://api.cmssy.io/graphql";
18
25
  declare function resolveApiUrl(apiUrl: string | undefined): string;
@@ -37,7 +44,7 @@ interface FetchPageOptions {
37
44
  workspaceId?: string;
38
45
  fetch?: FetchLike;
39
46
  signal?: AbortSignal;
40
- retry?: RetryPolicy | false;
47
+ retry?: RetryOption;
41
48
  }
42
49
  declare function fetchPage(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyPageData | null>;
43
50
  declare function fetchPageById(config: CmssyClientConfig, pageId: string, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageData | null>;
@@ -45,4 +52,4 @@ declare function fetchPages(config: CmssyClientConfig, options?: Pick<FetchPageO
45
52
  declare function fetchPageMeta(config: CmssyClientConfig, path: string | string[] | undefined, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageMeta | null>;
46
53
  declare function fetchLayouts(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyLayoutGroup[]>;
47
54
 
48
- export { CMSSY_RATE_LIMIT_WINDOW_MS as C, DEFAULT_CMSSY_API_URL as D, type FetchLike as F, type RetryPolicy as R, CmssyRequestError as a, type FetchLikeResponse as b, type FetchPageOptions as c, fetchPage as d, fetchPageById as e, fetchLayouts as f, fetchPageMeta as g, fetchPages as h, resolveApiUrl as r };
55
+ export { CMSSY_RATE_LIMIT_WINDOW_MS as C, DEFAULT_CMSSY_API_URL as D, type FetchLike as F, type RetryOption as R, CMSSY_RETRY_MODES as a, CmssyRequestError as b, type CmssyRetryMode as c, type FetchLikeResponse as d, type FetchPageOptions as e, type RetryPolicy as f, fetchLayouts as g, fetchPage as h, fetchPageById as i, fetchPageMeta as j, fetchPages as k, resolveApiUrl as r };
@@ -1,5 +1,5 @@
1
1
  import { CmssyClientConfig, BlockRect, BlockSchema, BlockMeta } from '@cmssy/types';
2
- import { G as GraphqlRequestOptions } from './locale-jxsO_8YF.js';
2
+ import { G as GraphqlRequestOptions } from './locale-D9KI4bND.js';
3
3
 
4
4
  declare const DEFAULT_CMSSY_EDITOR_ORIGINS: string[];
5
5
  declare function isDevelopment(): boolean;
@@ -1,5 +1,5 @@
1
1
  import { CmssyClientConfig, BlockRect, BlockSchema, BlockMeta } from '@cmssy/types';
2
- import { G as GraphqlRequestOptions } from './locale-BM2fwE3O.cjs';
2
+ import { G as GraphqlRequestOptions } from './locale-JyBHqpAQ.cjs';
3
3
 
4
4
  declare const DEFAULT_CMSSY_EDITOR_ORIGINS: string[];
5
5
  declare function isDevelopment(): boolean;
package/dist/index.cjs CHANGED
@@ -77,15 +77,64 @@ Set the listed environment variables (e.g. in .env.local) and restart the dev se
77
77
  var CmssyRequestError = class extends Error {
78
78
  status;
79
79
  retryAfterMs;
80
- constructor(message, status, retryAfterMs2) {
80
+ waitedMs;
81
+ constructor(message, status, retryAfterMs2, waitedMs) {
81
82
  super(message);
82
83
  this.name = "CmssyRequestError";
83
84
  this.status = status;
84
85
  if (retryAfterMs2 !== void 0) this.retryAfterMs = retryAfterMs2;
86
+ if (waitedMs !== void 0) this.waitedMs = waitedMs;
85
87
  }
86
88
  };
87
- var DEFAULT_RETRY_STATUSES = [429, 503];
89
+ var THROTTLE_STATUS = 429;
88
90
  var CMSSY_RATE_LIMIT_WINDOW_MS = 6e4;
91
+ var CMSSY_RETRY_MODES = {
92
+ build: {
93
+ maxRetries: 4,
94
+ baseDelayMs: 300,
95
+ throttleBaseDelayMs: 1e3,
96
+ maxDelayMs: 2e4,
97
+ maxRetryAfterMs: CMSSY_RATE_LIMIT_WINDOW_MS,
98
+ maxTotalWaitMs: 18e4,
99
+ retryStatuses: Object.freeze([429, 503])
100
+ },
101
+ interactive: {
102
+ maxRetries: 2,
103
+ baseDelayMs: 50,
104
+ throttleBaseDelayMs: 500,
105
+ maxDelayMs: 1e3,
106
+ maxRetryAfterMs: 1e3,
107
+ maxTotalWaitMs: 2e3,
108
+ retryStatuses: Object.freeze([429, 503])
109
+ }
110
+ };
111
+ Object.freeze(CMSSY_RETRY_MODES.build);
112
+ Object.freeze(CMSSY_RETRY_MODES.interactive);
113
+ Object.freeze(CMSSY_RETRY_MODES);
114
+ function resolveRetryPolicy(retry) {
115
+ if (retry === false || retry === void 0) return null;
116
+ if (typeof retry === "string") {
117
+ const mode = CMSSY_RETRY_MODES[retry];
118
+ if (!mode) {
119
+ throw new Error(
120
+ `cmssy: unknown retry mode "${retry}" - use ${Object.keys(
121
+ CMSSY_RETRY_MODES
122
+ ).map((name2) => `"${name2}"`).join(" or ")}, false, or a retry policy object`
123
+ );
124
+ }
125
+ return mode;
126
+ }
127
+ const base = CMSSY_RETRY_MODES.build;
128
+ return {
129
+ maxRetries: retry.maxRetries ?? base.maxRetries,
130
+ baseDelayMs: retry.baseDelayMs ?? base.baseDelayMs,
131
+ throttleBaseDelayMs: retry.throttleBaseDelayMs ?? base.throttleBaseDelayMs,
132
+ maxDelayMs: retry.maxDelayMs ?? base.maxDelayMs,
133
+ maxRetryAfterMs: retry.maxRetryAfterMs ?? base.maxRetryAfterMs,
134
+ maxTotalWaitMs: retry.maxTotalWaitMs ?? base.maxTotalWaitMs,
135
+ retryStatuses: retry.retryStatuses ?? base.retryStatuses
136
+ };
137
+ }
89
138
  function retryAfterMs(response) {
90
139
  const raw = response.headers?.get("retry-after");
91
140
  if (!raw) return null;
@@ -112,28 +161,40 @@ function sleep(ms, signal) {
112
161
  signal?.addEventListener("abort", onAbort, { once: true });
113
162
  });
114
163
  }
115
- async function fetchWithRetry(doFetch, url, init, retry) {
116
- if (retry === false || retry === void 0) {
117
- return doFetch(url, init);
164
+ async function fetchWithRetry(doFetch, url, init, retry, label) {
165
+ const policy = resolveRetryPolicy(retry);
166
+ if (policy === null) {
167
+ return { response: await doFetch(url, init), waitedMs: 0 };
118
168
  }
119
- const maxRetries = retry.maxRetries ?? 3;
120
- const baseDelayMs = retry.baseDelayMs ?? 300;
121
- const maxDelayMs = retry.maxDelayMs ?? 3e3;
122
- const maxRetryAfterMs = retry.maxRetryAfterMs ?? CMSSY_RATE_LIMIT_WINDOW_MS;
123
- const retryStatuses = retry.retryStatuses ?? DEFAULT_RETRY_STATUSES;
124
169
  let response = await doFetch(url, init);
125
- for (let attempt = 0; attempt < maxRetries; attempt++) {
126
- if (response.ok || !retryStatuses.includes(response.status)) {
127
- return response;
170
+ let waitedMs = 0;
171
+ for (let attempt = 0; attempt < policy.maxRetries; attempt++) {
172
+ if (response.ok || !policy.retryStatuses.includes(response.status)) {
173
+ return { response, waitedMs };
128
174
  }
175
+ const throttled = response.status === THROTTLE_STATUS;
129
176
  const asked = retryAfterMs(response);
130
- if (asked !== null && asked > maxRetryAfterMs) return response;
131
- const backoff = baseDelayMs * 2 ** attempt;
132
- const wait = asked !== null ? asked : Math.min(backoff, maxDelayMs);
177
+ if (asked !== null && asked > policy.maxRetryAfterMs) {
178
+ return { response, waitedMs };
179
+ }
180
+ const base = throttled ? policy.throttleBaseDelayMs : policy.baseDelayMs;
181
+ const wait = Math.round(
182
+ asked !== null ? Math.min(
183
+ asked + Math.random() * policy.throttleBaseDelayMs,
184
+ policy.maxRetryAfterMs
185
+ ) : Math.random() * Math.min(base * 2 ** attempt, policy.maxDelayMs)
186
+ );
187
+ if (waitedMs + wait > policy.maxTotalWaitMs) {
188
+ return { response, waitedMs };
189
+ }
190
+ console.warn(
191
+ `[cmssy] ${label} got ${response.status}, retrying in ${wait}ms (attempt ${attempt + 1}/${policy.maxRetries})`
192
+ );
133
193
  await sleep(wait, init.signal);
194
+ waitedMs += wait;
134
195
  response = await doFetch(url, init);
135
196
  }
136
- return response;
197
+ return { response, waitedMs };
137
198
  }
138
199
  async function postGraphql(url, query, variables, options) {
139
200
  const doFetch = options.fetch ?? globalThis.fetch;
@@ -142,7 +203,7 @@ async function postGraphql(url, query, variables, options) {
142
203
  "cmssy: no fetch implementation available - pass options.fetch"
143
204
  );
144
205
  }
145
- const response = await fetchWithRetry(
206
+ const { response, waitedMs } = await fetchWithRetry(
146
207
  doFetch,
147
208
  url,
148
209
  {
@@ -151,7 +212,8 @@ async function postGraphql(url, query, variables, options) {
151
212
  body: JSON.stringify({ query, variables }),
152
213
  signal: options.signal
153
214
  },
154
- options.retry
215
+ options.retry,
216
+ options.label
155
217
  );
156
218
  if (!response.ok) {
157
219
  let detail = "";
@@ -165,10 +227,12 @@ async function postGraphql(url, query, variables, options) {
165
227
  }
166
228
  const asked = retryAfterMs(response);
167
229
  const wait = asked !== null ? ` - retry after ${Math.ceil(asked / 1e3)}s` : "";
230
+ const spent = waitedMs > 0 ? ` - gave up after waiting ${waitedMs}ms` : "";
168
231
  throw new CmssyRequestError(
169
- `cmssy: ${options.label} failed (${response.status})${detail}${wait}`,
232
+ `cmssy: ${options.label} failed (${response.status})${detail}${wait}${spent}`,
170
233
  response.status,
171
- asked ?? void 0
234
+ asked ?? void 0,
235
+ waitedMs > 0 ? waitedMs : void 0
172
236
  );
173
237
  }
174
238
  let json;
@@ -387,7 +451,7 @@ async function fetchSiteConfig(config, options = {}) {
387
451
  config,
388
452
  SITE_CONFIG_QUERY,
389
453
  { workspaceSlug: config.workspaceSlug },
390
- { ...options, public: true, retry: options.retry ?? {} },
454
+ { ...options, public: true, retry: options.retry ?? "build" },
391
455
  "site config query"
392
456
  );
393
457
  return data.public?.siteConfig ?? null;
@@ -575,7 +639,7 @@ async function loadSiteLocales(config, options) {
575
639
  config,
576
640
  SITE_CONFIG_QUERY,
577
641
  { workspaceSlug: config.workspaceSlug },
578
- { ...options, public: true, retry: options?.retry ?? {} },
642
+ { ...options, public: true, retry: options?.retry ?? "build" },
579
643
  "site config"
580
644
  );
581
645
  value = localesFromSiteConfig(data.public?.siteConfig ?? null);
@@ -902,6 +966,7 @@ exports.CMSSY_EDIT_HEADER = CMSSY_EDIT_HEADER;
902
966
  exports.CMSSY_EDIT_QUERY_PARAM = CMSSY_EDIT_QUERY_PARAM;
903
967
  exports.CMSSY_LOCALE_HEADER = CMSSY_LOCALE_HEADER;
904
968
  exports.CMSSY_RATE_LIMIT_WINDOW_MS = CMSSY_RATE_LIMIT_WINDOW_MS;
969
+ exports.CMSSY_RETRY_MODES = CMSSY_RETRY_MODES;
905
970
  exports.CMSSY_SECRET_QUERY_PARAM = CMSSY_SECRET_QUERY_PARAM;
906
971
  exports.CmssyRequestError = CmssyRequestError;
907
972
  exports.CmssyWebhookError = CmssyWebhookError;
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- export { A as AppToEditorMessage, B as BoundsMessage, C as ClickMessage, a as CmssyClient, b as CmssyConfig, c as CmssyCspOptions, d as CmssyEnvConfig, e as CmssyTypedDocument, D as DEFAULT_CMSSY_EDITOR_ORIGINS, E as EditorToAppMessage, I as InvisibleBlock, f as InvisibleBlocksMessage, P as PROTOCOL_VERSION, g as ParentReadyMessage, h as PatchMessage, i as PostTarget, Q as QueryScopedOptions, R as ReadyMessage, S as SHORTCUT_ACTIONS, j as SelectMessage, k as ShortcutAction, l as ShortcutMessage, V as ViewportMessage, m as applyCmssyCsp, n as createCmssyClient, o as defineCmssyConfig, p as isProtocolCompatible, q as normalizeOrigin, r as parseEditorMessage, s as postToEditor, t as resolveEditorOrigin } from './csp-BMP9bAZ9.cjs';
2
- export { C as CMSSY_LOCALE_HEADER, G as GraphqlRequestOptions, g as graphqlRequest, l as localizeHref, r as resolveCmssyLocale } from './locale-BM2fwE3O.cjs';
3
- export { C as CMSSY_RATE_LIMIT_WINDOW_MS, a as CmssyRequestError, D as DEFAULT_CMSSY_API_URL, F as FetchLike, b as FetchLikeResponse, c as FetchPageOptions, R as RetryPolicy } from './content-client-COeyYJl1.cjs';
1
+ export { A as AppToEditorMessage, B as BoundsMessage, C as ClickMessage, a as CmssyClient, b as CmssyConfig, c as CmssyCspOptions, d as CmssyEnvConfig, e as CmssyTypedDocument, D as DEFAULT_CMSSY_EDITOR_ORIGINS, E as EditorToAppMessage, I as InvisibleBlock, f as InvisibleBlocksMessage, P as PROTOCOL_VERSION, g as ParentReadyMessage, h as PatchMessage, i as PostTarget, Q as QueryScopedOptions, R as ReadyMessage, S as SHORTCUT_ACTIONS, j as SelectMessage, k as ShortcutAction, l as ShortcutMessage, V as ViewportMessage, m as applyCmssyCsp, n as createCmssyClient, o as defineCmssyConfig, p as isProtocolCompatible, q as normalizeOrigin, r as parseEditorMessage, s as postToEditor, t as resolveEditorOrigin } from './csp-RaStdD2i.cjs';
2
+ export { C as CMSSY_LOCALE_HEADER, G as GraphqlRequestOptions, g as graphqlRequest, l as localizeHref, r as resolveCmssyLocale } from './locale-JyBHqpAQ.cjs';
3
+ export { C as CMSSY_RATE_LIMIT_WINDOW_MS, a as CMSSY_RETRY_MODES, b as CmssyRequestError, c as CmssyRetryMode, D as DEFAULT_CMSSY_API_URL, F as FetchLike, d as FetchLikeResponse, e as FetchPageOptions, R as RetryOption, f as RetryPolicy } from './content-client-65N8ZRq9.cjs';
4
4
  import * as _cmssy_types from '@cmssy/types';
5
5
  import { FieldOptions, TypedField, ResolvedMediaValue, PageRef, BlockPropsSchema, InferBlockContent, RelationMode, CmssyModelRecord, ResolvedMedia, VerifyCmssyWebhookOptions, CmssyWebhookEvent } from '@cmssy/types';
6
6
  export { BlockMeta, BlockPropsSchema, BlockRect, BlockSchema, BuildBlockContextExtra, CmssyBlockAuthContext, CmssyBlockContext, CmssyBlockMember, CmssyBlockPage, CmssyBlockWorkspace, CmssyBranding, CmssyClientConfig, CmssyFormDefinition, CmssyFormField, CmssyFormSettings, CmssyFormSubmitResponse, CmssyLayoutGroup, CmssyLayoutSettings, CmssyLocaleContext, CmssyLocalizedValue, CmssyModelDefinition, CmssyModelRecord, CmssyPageData, CmssyPageMeta, CmssyPageSummary, CmssyRecordList, CmssySiteConfig, CmssyWebhookEvent, CmssyWebhookOrder, FieldCondition, FieldConditionGroup, FieldConditionLogic, FieldControl, FieldDefinition, FieldOptions, FieldType, InferBlockContent, LayoutPosition, RawBlock, RawLayoutBlock, SubmitFormInput, TypedField, VerifyCmssyWebhookOptions, evaluateFieldConditionGroup, layoutPositionValues } from '@cmssy/types';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { A as AppToEditorMessage, B as BoundsMessage, C as ClickMessage, a as CmssyClient, b as CmssyConfig, c as CmssyCspOptions, d as CmssyEnvConfig, e as CmssyTypedDocument, D as DEFAULT_CMSSY_EDITOR_ORIGINS, E as EditorToAppMessage, I as InvisibleBlock, f as InvisibleBlocksMessage, P as PROTOCOL_VERSION, g as ParentReadyMessage, h as PatchMessage, i as PostTarget, Q as QueryScopedOptions, R as ReadyMessage, S as SHORTCUT_ACTIONS, j as SelectMessage, k as ShortcutAction, l as ShortcutMessage, V as ViewportMessage, m as applyCmssyCsp, n as createCmssyClient, o as defineCmssyConfig, p as isProtocolCompatible, q as normalizeOrigin, r as parseEditorMessage, s as postToEditor, t as resolveEditorOrigin } from './csp-BDE0cAZL.js';
2
- export { C as CMSSY_LOCALE_HEADER, G as GraphqlRequestOptions, g as graphqlRequest, l as localizeHref, r as resolveCmssyLocale } from './locale-jxsO_8YF.js';
3
- export { C as CMSSY_RATE_LIMIT_WINDOW_MS, a as CmssyRequestError, D as DEFAULT_CMSSY_API_URL, F as FetchLike, b as FetchLikeResponse, c as FetchPageOptions, R as RetryPolicy } from './content-client-COeyYJl1.js';
1
+ export { A as AppToEditorMessage, B as BoundsMessage, C as ClickMessage, a as CmssyClient, b as CmssyConfig, c as CmssyCspOptions, d as CmssyEnvConfig, e as CmssyTypedDocument, D as DEFAULT_CMSSY_EDITOR_ORIGINS, E as EditorToAppMessage, I as InvisibleBlock, f as InvisibleBlocksMessage, P as PROTOCOL_VERSION, g as ParentReadyMessage, h as PatchMessage, i as PostTarget, Q as QueryScopedOptions, R as ReadyMessage, S as SHORTCUT_ACTIONS, j as SelectMessage, k as ShortcutAction, l as ShortcutMessage, V as ViewportMessage, m as applyCmssyCsp, n as createCmssyClient, o as defineCmssyConfig, p as isProtocolCompatible, q as normalizeOrigin, r as parseEditorMessage, s as postToEditor, t as resolveEditorOrigin } from './csp-BnOiGUhR.js';
2
+ export { C as CMSSY_LOCALE_HEADER, G as GraphqlRequestOptions, g as graphqlRequest, l as localizeHref, r as resolveCmssyLocale } from './locale-D9KI4bND.js';
3
+ export { C as CMSSY_RATE_LIMIT_WINDOW_MS, a as CMSSY_RETRY_MODES, b as CmssyRequestError, c as CmssyRetryMode, D as DEFAULT_CMSSY_API_URL, F as FetchLike, d as FetchLikeResponse, e as FetchPageOptions, R as RetryOption, f as RetryPolicy } from './content-client-65N8ZRq9.js';
4
4
  import * as _cmssy_types from '@cmssy/types';
5
5
  import { FieldOptions, TypedField, ResolvedMediaValue, PageRef, BlockPropsSchema, InferBlockContent, RelationMode, CmssyModelRecord, ResolvedMedia, VerifyCmssyWebhookOptions, CmssyWebhookEvent } from '@cmssy/types';
6
6
  export { BlockMeta, BlockPropsSchema, BlockRect, BlockSchema, BuildBlockContextExtra, CmssyBlockAuthContext, CmssyBlockContext, CmssyBlockMember, CmssyBlockPage, CmssyBlockWorkspace, CmssyBranding, CmssyClientConfig, CmssyFormDefinition, CmssyFormField, CmssyFormSettings, CmssyFormSubmitResponse, CmssyLayoutGroup, CmssyLayoutSettings, CmssyLocaleContext, CmssyLocalizedValue, CmssyModelDefinition, CmssyModelRecord, CmssyPageData, CmssyPageMeta, CmssyPageSummary, CmssyRecordList, CmssySiteConfig, CmssyWebhookEvent, CmssyWebhookOrder, FieldCondition, FieldConditionGroup, FieldConditionLogic, FieldControl, FieldDefinition, FieldOptions, FieldType, InferBlockContent, LayoutPosition, RawBlock, RawLayoutBlock, SubmitFormInput, TypedField, VerifyCmssyWebhookOptions, evaluateFieldConditionGroup, layoutPositionValues } from '@cmssy/types';
package/dist/index.js CHANGED
@@ -75,15 +75,64 @@ Set the listed environment variables (e.g. in .env.local) and restart the dev se
75
75
  var CmssyRequestError = class extends Error {
76
76
  status;
77
77
  retryAfterMs;
78
- constructor(message, status, retryAfterMs2) {
78
+ waitedMs;
79
+ constructor(message, status, retryAfterMs2, waitedMs) {
79
80
  super(message);
80
81
  this.name = "CmssyRequestError";
81
82
  this.status = status;
82
83
  if (retryAfterMs2 !== void 0) this.retryAfterMs = retryAfterMs2;
84
+ if (waitedMs !== void 0) this.waitedMs = waitedMs;
83
85
  }
84
86
  };
85
- var DEFAULT_RETRY_STATUSES = [429, 503];
87
+ var THROTTLE_STATUS = 429;
86
88
  var CMSSY_RATE_LIMIT_WINDOW_MS = 6e4;
89
+ var CMSSY_RETRY_MODES = {
90
+ build: {
91
+ maxRetries: 4,
92
+ baseDelayMs: 300,
93
+ throttleBaseDelayMs: 1e3,
94
+ maxDelayMs: 2e4,
95
+ maxRetryAfterMs: CMSSY_RATE_LIMIT_WINDOW_MS,
96
+ maxTotalWaitMs: 18e4,
97
+ retryStatuses: Object.freeze([429, 503])
98
+ },
99
+ interactive: {
100
+ maxRetries: 2,
101
+ baseDelayMs: 50,
102
+ throttleBaseDelayMs: 500,
103
+ maxDelayMs: 1e3,
104
+ maxRetryAfterMs: 1e3,
105
+ maxTotalWaitMs: 2e3,
106
+ retryStatuses: Object.freeze([429, 503])
107
+ }
108
+ };
109
+ Object.freeze(CMSSY_RETRY_MODES.build);
110
+ Object.freeze(CMSSY_RETRY_MODES.interactive);
111
+ Object.freeze(CMSSY_RETRY_MODES);
112
+ function resolveRetryPolicy(retry) {
113
+ if (retry === false || retry === void 0) return null;
114
+ if (typeof retry === "string") {
115
+ const mode = CMSSY_RETRY_MODES[retry];
116
+ if (!mode) {
117
+ throw new Error(
118
+ `cmssy: unknown retry mode "${retry}" - use ${Object.keys(
119
+ CMSSY_RETRY_MODES
120
+ ).map((name2) => `"${name2}"`).join(" or ")}, false, or a retry policy object`
121
+ );
122
+ }
123
+ return mode;
124
+ }
125
+ const base = CMSSY_RETRY_MODES.build;
126
+ return {
127
+ maxRetries: retry.maxRetries ?? base.maxRetries,
128
+ baseDelayMs: retry.baseDelayMs ?? base.baseDelayMs,
129
+ throttleBaseDelayMs: retry.throttleBaseDelayMs ?? base.throttleBaseDelayMs,
130
+ maxDelayMs: retry.maxDelayMs ?? base.maxDelayMs,
131
+ maxRetryAfterMs: retry.maxRetryAfterMs ?? base.maxRetryAfterMs,
132
+ maxTotalWaitMs: retry.maxTotalWaitMs ?? base.maxTotalWaitMs,
133
+ retryStatuses: retry.retryStatuses ?? base.retryStatuses
134
+ };
135
+ }
87
136
  function retryAfterMs(response) {
88
137
  const raw = response.headers?.get("retry-after");
89
138
  if (!raw) return null;
@@ -110,28 +159,40 @@ function sleep(ms, signal) {
110
159
  signal?.addEventListener("abort", onAbort, { once: true });
111
160
  });
112
161
  }
113
- async function fetchWithRetry(doFetch, url, init, retry) {
114
- if (retry === false || retry === void 0) {
115
- return doFetch(url, init);
162
+ async function fetchWithRetry(doFetch, url, init, retry, label) {
163
+ const policy = resolveRetryPolicy(retry);
164
+ if (policy === null) {
165
+ return { response: await doFetch(url, init), waitedMs: 0 };
116
166
  }
117
- const maxRetries = retry.maxRetries ?? 3;
118
- const baseDelayMs = retry.baseDelayMs ?? 300;
119
- const maxDelayMs = retry.maxDelayMs ?? 3e3;
120
- const maxRetryAfterMs = retry.maxRetryAfterMs ?? CMSSY_RATE_LIMIT_WINDOW_MS;
121
- const retryStatuses = retry.retryStatuses ?? DEFAULT_RETRY_STATUSES;
122
167
  let response = await doFetch(url, init);
123
- for (let attempt = 0; attempt < maxRetries; attempt++) {
124
- if (response.ok || !retryStatuses.includes(response.status)) {
125
- return response;
168
+ let waitedMs = 0;
169
+ for (let attempt = 0; attempt < policy.maxRetries; attempt++) {
170
+ if (response.ok || !policy.retryStatuses.includes(response.status)) {
171
+ return { response, waitedMs };
126
172
  }
173
+ const throttled = response.status === THROTTLE_STATUS;
127
174
  const asked = retryAfterMs(response);
128
- if (asked !== null && asked > maxRetryAfterMs) return response;
129
- const backoff = baseDelayMs * 2 ** attempt;
130
- const wait = asked !== null ? asked : Math.min(backoff, maxDelayMs);
175
+ if (asked !== null && asked > policy.maxRetryAfterMs) {
176
+ return { response, waitedMs };
177
+ }
178
+ const base = throttled ? policy.throttleBaseDelayMs : policy.baseDelayMs;
179
+ const wait = Math.round(
180
+ asked !== null ? Math.min(
181
+ asked + Math.random() * policy.throttleBaseDelayMs,
182
+ policy.maxRetryAfterMs
183
+ ) : Math.random() * Math.min(base * 2 ** attempt, policy.maxDelayMs)
184
+ );
185
+ if (waitedMs + wait > policy.maxTotalWaitMs) {
186
+ return { response, waitedMs };
187
+ }
188
+ console.warn(
189
+ `[cmssy] ${label} got ${response.status}, retrying in ${wait}ms (attempt ${attempt + 1}/${policy.maxRetries})`
190
+ );
131
191
  await sleep(wait, init.signal);
192
+ waitedMs += wait;
132
193
  response = await doFetch(url, init);
133
194
  }
134
- return response;
195
+ return { response, waitedMs };
135
196
  }
136
197
  async function postGraphql(url, query, variables, options) {
137
198
  const doFetch = options.fetch ?? globalThis.fetch;
@@ -140,7 +201,7 @@ async function postGraphql(url, query, variables, options) {
140
201
  "cmssy: no fetch implementation available - pass options.fetch"
141
202
  );
142
203
  }
143
- const response = await fetchWithRetry(
204
+ const { response, waitedMs } = await fetchWithRetry(
144
205
  doFetch,
145
206
  url,
146
207
  {
@@ -149,7 +210,8 @@ async function postGraphql(url, query, variables, options) {
149
210
  body: JSON.stringify({ query, variables }),
150
211
  signal: options.signal
151
212
  },
152
- options.retry
213
+ options.retry,
214
+ options.label
153
215
  );
154
216
  if (!response.ok) {
155
217
  let detail = "";
@@ -163,10 +225,12 @@ async function postGraphql(url, query, variables, options) {
163
225
  }
164
226
  const asked = retryAfterMs(response);
165
227
  const wait = asked !== null ? ` - retry after ${Math.ceil(asked / 1e3)}s` : "";
228
+ const spent = waitedMs > 0 ? ` - gave up after waiting ${waitedMs}ms` : "";
166
229
  throw new CmssyRequestError(
167
- `cmssy: ${options.label} failed (${response.status})${detail}${wait}`,
230
+ `cmssy: ${options.label} failed (${response.status})${detail}${wait}${spent}`,
168
231
  response.status,
169
- asked ?? void 0
232
+ asked ?? void 0,
233
+ waitedMs > 0 ? waitedMs : void 0
170
234
  );
171
235
  }
172
236
  let json;
@@ -385,7 +449,7 @@ async function fetchSiteConfig(config, options = {}) {
385
449
  config,
386
450
  SITE_CONFIG_QUERY,
387
451
  { workspaceSlug: config.workspaceSlug },
388
- { ...options, public: true, retry: options.retry ?? {} },
452
+ { ...options, public: true, retry: options.retry ?? "build" },
389
453
  "site config query"
390
454
  );
391
455
  return data.public?.siteConfig ?? null;
@@ -573,7 +637,7 @@ async function loadSiteLocales(config, options) {
573
637
  config,
574
638
  SITE_CONFIG_QUERY,
575
639
  { workspaceSlug: config.workspaceSlug },
576
- { ...options, public: true, retry: options?.retry ?? {} },
640
+ { ...options, public: true, retry: options?.retry ?? "build" },
577
641
  "site config"
578
642
  );
579
643
  value = localesFromSiteConfig(data.public?.siteConfig ?? null);
@@ -888,4 +952,4 @@ async function verifyCmssyWebhook(options) {
888
952
  return parsed;
889
953
  }
890
954
 
891
- export { CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_RATE_LIMIT_WINDOW_MS, CMSSY_SECRET_QUERY_PARAM, CmssyRequestError, CmssyWebhookError, DEFAULT_CMSSY_API_URL, DEFAULT_CMSSY_EDITOR_ORIGINS, PROTOCOL_VERSION, SHORTCUT_ACTIONS, applyCmssyCsp, createCmssyClient, defineCmssyConfig, fields, graphqlRequest, isProtocolCompatible, isVerifiedEditUrl, localizeHref, mediaAlt, mediaUrl, mediaUrls, normalizeOrigin, parseEditorMessage, postToEditor, resolveCmssyLocale, resolveEditorOrigin, verifyCmssyWebhook };
955
+ export { CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_RATE_LIMIT_WINDOW_MS, CMSSY_RETRY_MODES, CMSSY_SECRET_QUERY_PARAM, CmssyRequestError, CmssyWebhookError, DEFAULT_CMSSY_API_URL, DEFAULT_CMSSY_EDITOR_ORIGINS, PROTOCOL_VERSION, SHORTCUT_ACTIONS, applyCmssyCsp, createCmssyClient, defineCmssyConfig, fields, graphqlRequest, isProtocolCompatible, isVerifiedEditUrl, localizeHref, mediaAlt, mediaUrl, mediaUrls, normalizeOrigin, parseEditorMessage, postToEditor, resolveCmssyLocale, resolveEditorOrigin, verifyCmssyWebhook };
@@ -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((name) => `"${name}"`).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;
@@ -184,7 +248,7 @@ async function loadSiteLocales(config, options) {
184
248
  config,
185
249
  SITE_CONFIG_QUERY,
186
250
  { workspaceSlug: config.workspaceSlug },
187
- { ...options, public: true, retry: options?.retry ?? {} },
251
+ { ...options, public: true, retry: options?.retry ?? "build" },
188
252
  "site config"
189
253
  );
190
254
  value = localesFromSiteConfig(data.public?.siteConfig ?? null);