@cmssy/react 2.7.0 → 4.0.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.
package/dist/index.cjs CHANGED
@@ -342,6 +342,108 @@ function parseEditorMessage(data, origin, expectedOrigin) {
342
342
  }
343
343
  }
344
344
 
345
+ // src/data/http.ts
346
+ var CmssyRequestError = class extends Error {
347
+ status;
348
+ constructor(message, status) {
349
+ super(message);
350
+ this.name = "CmssyRequestError";
351
+ this.status = status;
352
+ }
353
+ };
354
+ var DEFAULT_RETRY_STATUSES = [429, 503];
355
+ function retryAfterMs(response) {
356
+ const raw = response.headers?.get("retry-after");
357
+ if (!raw) return null;
358
+ const seconds = Number(raw);
359
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
360
+ const date = Date.parse(raw);
361
+ if (Number.isFinite(date)) return Math.max(0, date - Date.now());
362
+ return null;
363
+ }
364
+ function sleep(ms, signal) {
365
+ return new Promise((resolve, reject) => {
366
+ if (signal?.aborted) {
367
+ reject(new Error("cmssy: request aborted"));
368
+ return;
369
+ }
370
+ const timer = setTimeout(() => {
371
+ signal?.removeEventListener("abort", onAbort);
372
+ resolve();
373
+ }, ms);
374
+ function onAbort() {
375
+ clearTimeout(timer);
376
+ reject(new Error("cmssy: request aborted"));
377
+ }
378
+ signal?.addEventListener("abort", onAbort, { once: true });
379
+ });
380
+ }
381
+ async function fetchWithRetry(doFetch, url, init, retry) {
382
+ if (retry === false || retry === void 0) {
383
+ return doFetch(url, init);
384
+ }
385
+ const maxRetries = retry.maxRetries ?? 3;
386
+ const baseDelayMs = retry.baseDelayMs ?? 300;
387
+ const maxDelayMs = retry.maxDelayMs ?? 3e3;
388
+ const retryStatuses = retry.retryStatuses ?? DEFAULT_RETRY_STATUSES;
389
+ let response = await doFetch(url, init);
390
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
391
+ if (response.ok || !retryStatuses.includes(response.status)) {
392
+ return response;
393
+ }
394
+ const backoff = baseDelayMs * 2 ** attempt;
395
+ const wait = Math.min(retryAfterMs(response) ?? backoff, maxDelayMs);
396
+ await sleep(wait, init.signal);
397
+ response = await doFetch(url, init);
398
+ }
399
+ return response;
400
+ }
401
+ async function postGraphql(url, query, variables, options) {
402
+ const doFetch = options.fetch ?? globalThis.fetch;
403
+ if (typeof doFetch !== "function") {
404
+ throw new Error(
405
+ "cmssy: no fetch implementation available - pass options.fetch"
406
+ );
407
+ }
408
+ const response = await fetchWithRetry(
409
+ doFetch,
410
+ url,
411
+ {
412
+ method: "POST",
413
+ headers: { "content-type": "application/json", ...options.headers },
414
+ body: JSON.stringify({ query, variables }),
415
+ signal: options.signal
416
+ },
417
+ options.retry
418
+ );
419
+ if (!response.ok) {
420
+ let detail = "";
421
+ try {
422
+ const body = await response.json();
423
+ if (body.errors && body.errors.length > 0) {
424
+ detail = ` - ${body.errors.map((error) => error.message ?? "GraphQL error").join("; ")}`;
425
+ }
426
+ } catch {
427
+ detail = "";
428
+ }
429
+ throw new CmssyRequestError(
430
+ `cmssy: ${options.label} failed (${response.status})${detail}`,
431
+ response.status
432
+ );
433
+ }
434
+ let json;
435
+ try {
436
+ json = await response.json();
437
+ } catch {
438
+ throw new Error(`cmssy: invalid JSON response from the ${options.label}`);
439
+ }
440
+ if (json.errors && json.errors.length > 0) {
441
+ const message = json.errors.map((error) => error.message ?? "GraphQL error").join("; ");
442
+ throw new Error(`cmssy: ${options.label} error - ${message}`);
443
+ }
444
+ return json.data;
445
+ }
446
+
345
447
  // src/content/content-client.ts
346
448
  var DEFAULT_CMSSY_API_URL = "https://api.cmssy.io/graphql";
347
449
  function resolveApiUrl(apiUrl) {
@@ -433,251 +535,113 @@ function normalizeSlug(path) {
433
535
  }
434
536
  async function fetchPage(config, path, options = {}) {
435
537
  const slug = normalizeSlug(path);
436
- const doFetch = options.fetch ?? globalThis.fetch;
437
- if (typeof doFetch !== "function") {
438
- throw new Error(
439
- "cmssy: no fetch implementation available - pass options.fetch"
440
- );
441
- }
442
538
  const trimmedSecret = options.previewSecret?.trim();
443
539
  const previewSecret = trimmedSecret ? trimmedSecret : null;
444
540
  const devToken = options.devToken?.trim();
445
541
  const devPreview = Boolean(options.devPreview && devToken);
446
- const headers = {
447
- "content-type": "application/json"
448
- };
542
+ const headers = {};
449
543
  if (devPreview && devToken) {
450
544
  headers["authorization"] = `Bearer ${devToken}`;
451
545
  if (options.workspaceId) {
452
546
  headers["x-workspace-id"] = options.workspaceId;
453
547
  }
454
548
  }
455
- const response = await doFetch(resolvePublicUrl(config), {
456
- method: "POST",
457
- headers,
458
- body: JSON.stringify({
459
- query: devPreview ? PUBLIC_PAGE_DEV_QUERY : PUBLIC_PAGE_QUERY,
460
- variables: {
461
- workspaceSlug: config.workspaceSlug,
462
- slug,
463
- previewSecret,
464
- ...devPreview ? { devPreview: true } : {}
465
- }
466
- }),
467
- signal: options.signal
468
- });
469
- if (!response.ok) {
470
- let detail = "";
471
- try {
472
- const body = await response.json();
473
- if (body.errors && body.errors.length > 0) {
474
- detail = ` - ${body.errors.map((error) => error.message ?? "GraphQL error").join("; ")}`;
475
- }
476
- } catch {
477
- detail = "";
549
+ const data = await postGraphql(
550
+ resolvePublicUrl(config),
551
+ devPreview ? PUBLIC_PAGE_DEV_QUERY : PUBLIC_PAGE_QUERY,
552
+ {
553
+ workspaceSlug: config.workspaceSlug,
554
+ slug,
555
+ previewSecret,
556
+ ...devPreview ? { devPreview: true } : {}
557
+ },
558
+ {
559
+ fetch: options.fetch,
560
+ signal: options.signal,
561
+ headers,
562
+ retry: options.retry ?? {},
563
+ label: "page fetch"
478
564
  }
479
- throw new Error(`cmssy: page fetch failed (${response.status})${detail}`);
480
- }
481
- let json;
482
- try {
483
- json = await response.json();
484
- } catch {
485
- throw new Error("cmssy: invalid JSON response from the page query");
486
- }
487
- if (json.errors && json.errors.length > 0) {
488
- const message = json.errors.map((error) => error.message ?? "GraphQL error").join("; ");
489
- throw new Error(`cmssy: page fetch error - ${message}`);
490
- }
491
- const page = json.data?.public?.page?.get;
565
+ );
566
+ const page = data?.public?.page?.get;
492
567
  if (!page) return null;
493
568
  const draft = previewSecret !== null || devPreview;
494
569
  const blocks = (draft ? page.blocks : page.publishedBlocks) ?? [];
495
570
  return { id: page.id, blocks };
496
571
  }
497
572
  async function fetchPageById(config, pageId, options = {}) {
498
- const doFetch = options.fetch ?? globalThis.fetch;
499
- if (typeof doFetch !== "function") {
500
- throw new Error(
501
- "cmssy: no fetch implementation available - pass options.fetch"
502
- );
503
- }
504
- const response = await doFetch(resolvePublicUrl(config), {
505
- method: "POST",
506
- headers: { "content-type": "application/json" },
507
- body: JSON.stringify({
508
- query: PUBLIC_PAGE_BY_ID_QUERY,
509
- variables: { workspaceSlug: config.workspaceSlug, pageId }
510
- }),
511
- signal: options.signal
512
- });
513
- if (!response.ok) {
514
- let detail = "";
515
- try {
516
- const body = await response.json();
517
- if (body.errors && body.errors.length > 0) {
518
- detail = ` - ${body.errors.map((error) => error.message ?? "GraphQL error").join("; ")}`;
519
- }
520
- } catch {
521
- detail = "";
573
+ const data = await postGraphql(
574
+ resolvePublicUrl(config),
575
+ PUBLIC_PAGE_BY_ID_QUERY,
576
+ { workspaceSlug: config.workspaceSlug, pageId },
577
+ {
578
+ fetch: options.fetch,
579
+ signal: options.signal,
580
+ retry: options.retry ?? {},
581
+ label: "page-by-id fetch"
522
582
  }
523
- throw new Error(
524
- `cmssy: page-by-id fetch failed (${response.status})${detail}`
525
- );
526
- }
527
- let json;
528
- try {
529
- json = await response.json();
530
- } catch {
531
- throw new Error("cmssy: invalid JSON response from the page-by-id query");
532
- }
533
- if (json.errors && json.errors.length > 0) {
534
- const message = json.errors.map((error) => error.message ?? "GraphQL error").join("; ");
535
- throw new Error(`cmssy: page-by-id fetch error - ${message}`);
536
- }
537
- const page = json.data?.public?.page?.getById;
583
+ );
584
+ const page = data?.public?.page?.getById;
538
585
  if (!page) return null;
539
586
  return { id: page.id, blocks: page.publishedBlocks ?? [] };
540
587
  }
541
588
  async function fetchPages(config, options = {}) {
542
- const doFetch = options.fetch ?? globalThis.fetch;
543
- if (typeof doFetch !== "function") {
544
- throw new Error(
545
- "cmssy: no fetch implementation available - pass options.fetch"
546
- );
547
- }
548
- const response = await doFetch(resolvePublicUrl(config), {
549
- method: "POST",
550
- headers: { "content-type": "application/json" },
551
- body: JSON.stringify({
552
- query: PUBLIC_PAGES_QUERY,
553
- variables: { workspaceSlug: config.workspaceSlug }
554
- }),
555
- signal: options.signal
556
- });
557
- if (!response.ok) {
558
- throw new Error(`cmssy: pages fetch failed (${response.status})`);
559
- }
560
- let json;
561
- try {
562
- json = await response.json();
563
- } catch {
564
- throw new Error("cmssy: invalid JSON response from the pages query");
565
- }
566
- if (json.errors && json.errors.length > 0) {
567
- const message = json.errors.map((error) => error.message ?? "GraphQL error").join("; ");
568
- throw new Error(`cmssy: pages fetch error - ${message}`);
569
- }
570
- return json.data?.public?.page?.list ?? [];
589
+ const data = await postGraphql(
590
+ resolvePublicUrl(config),
591
+ PUBLIC_PAGES_QUERY,
592
+ { workspaceSlug: config.workspaceSlug },
593
+ {
594
+ fetch: options.fetch,
595
+ signal: options.signal,
596
+ retry: options.retry ?? {},
597
+ label: "pages fetch"
598
+ }
599
+ );
600
+ return data?.public?.page?.list ?? [];
571
601
  }
572
602
  async function fetchPageMeta(config, path, options = {}) {
573
603
  const slug = normalizeSlug(path);
574
- const doFetch = options.fetch ?? globalThis.fetch;
575
- if (typeof doFetch !== "function") {
576
- throw new Error(
577
- "cmssy: no fetch implementation available - pass options.fetch"
578
- );
579
- }
580
- const response = await doFetch(resolvePublicUrl(config), {
581
- method: "POST",
582
- headers: { "content-type": "application/json" },
583
- body: JSON.stringify({
584
- query: PUBLIC_PAGE_META_QUERY,
585
- variables: { workspaceSlug: config.workspaceSlug, slug }
586
- }),
587
- signal: options.signal
588
- });
589
- if (!response.ok) {
590
- throw new Error(`cmssy: page meta fetch failed (${response.status})`);
591
- }
592
- let json;
593
- try {
594
- json = await response.json();
595
- } catch {
596
- throw new Error("cmssy: invalid JSON response from the page meta query");
597
- }
598
- if (json.errors && json.errors.length > 0) {
599
- const message = json.errors.map((error) => error.message ?? "GraphQL error").join("; ");
600
- throw new Error(`cmssy: page meta fetch error - ${message}`);
601
- }
602
- return json.data?.public?.page?.get ?? null;
604
+ const data = await postGraphql(
605
+ resolvePublicUrl(config),
606
+ PUBLIC_PAGE_META_QUERY,
607
+ { workspaceSlug: config.workspaceSlug, slug },
608
+ {
609
+ fetch: options.fetch,
610
+ signal: options.signal,
611
+ retry: options.retry ?? {},
612
+ label: "page meta fetch"
613
+ }
614
+ );
615
+ return data?.public?.page?.get ?? null;
603
616
  }
604
617
  async function fetchLayouts(config, path, options = {}) {
605
618
  const pageSlug = normalizeSlug(path);
606
- const doFetch = options.fetch ?? globalThis.fetch;
607
- if (typeof doFetch !== "function") {
608
- throw new Error(
609
- "cmssy: no fetch implementation available - pass options.fetch"
610
- );
611
- }
612
619
  const trimmedSecret = options.previewSecret?.trim();
613
620
  const previewSecret = trimmedSecret ? trimmedSecret : null;
614
- const response = await doFetch(resolvePublicUrl(config), {
615
- method: "POST",
616
- headers: { "content-type": "application/json" },
617
- body: JSON.stringify({
618
- query: PUBLIC_PAGE_LAYOUTS_QUERY,
619
- variables: {
620
- workspaceSlug: config.workspaceSlug,
621
- pageSlug,
622
- previewSecret
623
- }
624
- }),
625
- signal: options.signal
626
- });
627
- if (!response.ok) {
628
- throw new Error(`cmssy: layouts fetch failed (${response.status})`);
629
- }
630
- let json;
631
- try {
632
- json = await response.json();
633
- } catch {
634
- throw new Error("cmssy: invalid JSON response from the layouts query");
635
- }
636
- if (json.errors && json.errors.length > 0) {
637
- const message = json.errors.map((error) => error.message ?? "GraphQL error").join("; ");
638
- throw new Error(`cmssy: layouts fetch error - ${message}`);
639
- }
640
- return json.data?.public?.page?.layouts ?? [];
621
+ const data = await postGraphql(
622
+ resolvePublicUrl(config),
623
+ PUBLIC_PAGE_LAYOUTS_QUERY,
624
+ { workspaceSlug: config.workspaceSlug, pageSlug, previewSecret },
625
+ {
626
+ fetch: options.fetch,
627
+ signal: options.signal,
628
+ retry: options.retry ?? {},
629
+ label: "layouts fetch"
630
+ }
631
+ );
632
+ return data?.public?.page?.layouts ?? [];
641
633
  }
642
634
 
643
635
  // src/data/graphql-request.ts
644
636
  async function graphqlRequest(config, query, variables, options = {}, label = "request") {
645
- const doFetch = options.fetch ?? globalThis.fetch;
646
- if (typeof doFetch !== "function") {
647
- throw new Error(
648
- "cmssy: no fetch implementation available - pass options.fetch"
649
- );
650
- }
651
637
  const url = options.public ? resolvePublicUrl(config) : resolveApiUrl(config.apiUrl);
652
- const response = await doFetch(url, {
653
- method: "POST",
654
- headers: { "content-type": "application/json", ...options.headers },
655
- body: JSON.stringify({ query, variables }),
656
- signal: options.signal
638
+ return postGraphql(url, query, variables, {
639
+ fetch: options.fetch,
640
+ signal: options.signal,
641
+ headers: options.headers,
642
+ retry: options.retry,
643
+ label
657
644
  });
658
- if (!response.ok) {
659
- let detail = "";
660
- try {
661
- const body = await response.json();
662
- if (body.errors && body.errors.length > 0) {
663
- detail = ` - ${body.errors.map((error) => error.message ?? "GraphQL error").join("; ")}`;
664
- }
665
- } catch {
666
- detail = "";
667
- }
668
- throw new Error(`cmssy: ${label} failed (${response.status})${detail}`);
669
- }
670
- let json;
671
- try {
672
- json = await response.json();
673
- } catch {
674
- throw new Error(`cmssy: invalid JSON response from the ${label}`);
675
- }
676
- if (json.errors && json.errors.length > 0) {
677
- const message = json.errors.map((error) => error.message ?? "GraphQL error").join("; ");
678
- throw new Error(`cmssy: ${label} error - ${message}`);
679
- }
680
- return json.data;
681
645
  }
682
646
 
683
647
  // src/data/queries.ts
@@ -759,7 +723,7 @@ async function fetchSiteConfig(config, options = {}) {
759
723
  config,
760
724
  SITE_CONFIG_QUERY,
761
725
  { workspaceSlug: config.workspaceSlug },
762
- { ...options, public: true },
726
+ { ...options, public: true, retry: options.retry ?? {} },
763
727
  "site config query"
764
728
  );
765
729
  return data.public?.siteConfig ?? null;
@@ -875,7 +839,7 @@ async function resolveSiteLocales(config, options) {
875
839
  config,
876
840
  SITE_CONFIG_QUERY,
877
841
  { workspaceSlug: config.workspaceSlug },
878
- { ...options, public: true },
842
+ { ...options, public: true, retry: options?.retry ?? {} },
879
843
  "site config"
880
844
  );
881
845
  const siteConfig = data.public?.siteConfig ?? null;
@@ -985,6 +949,7 @@ Object.defineProperty(exports, "evaluateFieldConditionGroup", {
985
949
  get: function () { return types.evaluateFieldConditionGroup; }
986
950
  });
987
951
  exports.CmssyBlock = CmssyBlock;
952
+ exports.CmssyRequestError = CmssyRequestError;
988
953
  exports.CmssyServerLayout = CmssyServerLayout;
989
954
  exports.CmssyServerPage = CmssyServerPage;
990
955
  exports.DEFAULT_CMSSY_API_URL = DEFAULT_CMSSY_API_URL;
package/dist/index.d.cts CHANGED
@@ -5,6 +5,22 @@ export { b as blocksToMeta, c as blocksToSchemas, d as buildBlockMap, e as defin
5
5
  import * as react_jsx_runtime from 'react/jsx-runtime';
6
6
  import 'react';
7
7
 
8
+ /** HTTP-level failure from the cmssy API, with a machine-readable status. */
9
+ declare class CmssyRequestError extends Error {
10
+ readonly status: number;
11
+ constructor(message: string, status: number);
12
+ }
13
+ interface RetryPolicy {
14
+ /** Additional attempts after the first request (default 3). */
15
+ maxRetries?: number;
16
+ /** Exponential backoff base in ms: base * 2^attempt (default 300). */
17
+ baseDelayMs?: number;
18
+ /** Upper bound for any single wait, including Retry-After (default 3000). */
19
+ maxDelayMs?: number;
20
+ /** HTTP statuses that trigger a retry (default [429, 503]). */
21
+ retryStatuses?: number[];
22
+ }
23
+
8
24
  /**
9
25
  * The cmssy cloud GraphQL delivery endpoint. It is the same for every workspace,
10
26
  * so consumers never need to set it - `apiUrl` defaults to this. Self-hosted /
@@ -23,6 +39,9 @@ interface FetchLikeResponse {
23
39
  ok: boolean;
24
40
  status: number;
25
41
  json: () => Promise<unknown>;
42
+ headers?: {
43
+ get: (name: string) => string | null;
44
+ };
26
45
  }
27
46
  type FetchLike = (url: string, init: {
28
47
  method: string;
@@ -37,12 +56,13 @@ interface FetchPageOptions {
37
56
  workspaceId?: string;
38
57
  fetch?: FetchLike;
39
58
  signal?: AbortSignal;
59
+ retry?: RetryPolicy | false;
40
60
  }
41
61
  declare function normalizeSlug(path: string | string[] | undefined): string;
42
62
  declare function fetchPage(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyPageData | null>;
43
- declare function fetchPageById(config: CmssyClientConfig, pageId: string, options?: Pick<FetchPageOptions, "fetch" | "signal">): Promise<CmssyPageData | null>;
44
- declare function fetchPages(config: CmssyClientConfig, options?: Pick<FetchPageOptions, "fetch" | "signal">): Promise<CmssyPageSummary[]>;
45
- declare function fetchPageMeta(config: CmssyClientConfig, path: string | string[] | undefined, options?: Pick<FetchPageOptions, "fetch" | "signal">): Promise<CmssyPageMeta | null>;
63
+ declare function fetchPageById(config: CmssyClientConfig, pageId: string, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageData | null>;
64
+ declare function fetchPages(config: CmssyClientConfig, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageSummary[]>;
65
+ declare function fetchPageMeta(config: CmssyClientConfig, path: string | string[] | undefined, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageMeta | null>;
46
66
  declare function fetchLayouts(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyLayoutGroup[]>;
47
67
 
48
68
  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 faviconUrl\n ogImageUrl\n }\n }\n }\n}";
@@ -220,6 +240,12 @@ interface GraphqlRequestOptions {
220
240
  * resolves the workspace from the URL rather than a global slug lookup.
221
241
  */
222
242
  public?: boolean;
243
+ /**
244
+ * Retry transient HTTP failures (429/503, honoring Retry-After). Off by
245
+ * default: this function also carries mutations (auth, cart, checkout),
246
+ * which must never be blind-retried. Read-only callers opt in with `{}`.
247
+ */
248
+ retry?: RetryPolicy | false;
223
249
  }
224
250
  declare function graphqlRequest<T>(config: CmssyClientConfig, query: string, variables: Record<string, unknown>, options?: GraphqlRequestOptions, label?: string): Promise<T>;
225
251
 
@@ -282,4 +308,4 @@ interface UnknownBlockProps {
282
308
  }
283
309
  declare function UnknownBlock({ type }: UnknownBlockProps): react_jsx_runtime.JSX.Element;
284
310
 
285
- export { type AppToEditorMessage, BlockDefinition, BlockMap, type BoundsMessage, type ClickMessage, CmssyBlock, type CmssyBlockProps, type CmssyClient, CmssyServerLayout, type CmssyServerLayoutProps, CmssyServerPage, type CmssyServerPageProps, DEFAULT_CMSSY_API_URL, type EditorToAppMessage, FORM_QUERY, type FetchLike, type FetchLikeResponse, type FetchPageOptions, type GraphqlRequestOptions, MODEL_DEFINITIONS_QUERY, MODEL_RECORDS_QUERY, PROTOCOL_VERSION, type ParentReadyMessage, type PatchMessage, type PostTarget, type QueryScopedOptions, type ReadyMessage, SITE_CONFIG_QUERY, SUBMIT_FORM_MUTATION, type SelectMessage, UnknownBlock, type UnknownBlockProps, buildBlockContext, buildLocaleSwitchHref, collectFormIds, createCmssyClient, fetchLayouts, fetchPage, fetchPageById, fetchPageMeta, fetchPages, fetchSiteConfig, fields, getBlockContentForLanguage, graphqlRequest, isProtocolCompatible, localizeHref, localizeHtmlLinks, normalizeOrigin, normalizeSlug, parseEditorMessage, postToEditor, resolveApiUrl, resolveForms, resolvePublicUrl, resolveSiteLocales, resolveWorkspaceId, splitLocaleFromPath };
311
+ export { type AppToEditorMessage, BlockDefinition, BlockMap, type BoundsMessage, type ClickMessage, CmssyBlock, type CmssyBlockProps, type CmssyClient, CmssyRequestError, CmssyServerLayout, type CmssyServerLayoutProps, CmssyServerPage, type CmssyServerPageProps, DEFAULT_CMSSY_API_URL, type EditorToAppMessage, FORM_QUERY, type FetchLike, type FetchLikeResponse, type FetchPageOptions, type GraphqlRequestOptions, MODEL_DEFINITIONS_QUERY, MODEL_RECORDS_QUERY, PROTOCOL_VERSION, type ParentReadyMessage, type PatchMessage, type PostTarget, type QueryScopedOptions, type ReadyMessage, type RetryPolicy, SITE_CONFIG_QUERY, SUBMIT_FORM_MUTATION, type SelectMessage, UnknownBlock, type UnknownBlockProps, buildBlockContext, buildLocaleSwitchHref, collectFormIds, createCmssyClient, fetchLayouts, fetchPage, fetchPageById, fetchPageMeta, fetchPages, fetchSiteConfig, fields, getBlockContentForLanguage, graphqlRequest, isProtocolCompatible, localizeHref, localizeHtmlLinks, normalizeOrigin, normalizeSlug, parseEditorMessage, postToEditor, resolveApiUrl, resolveForms, resolvePublicUrl, resolveSiteLocales, resolveWorkspaceId, splitLocaleFromPath };
package/dist/index.d.ts CHANGED
@@ -5,6 +5,22 @@ export { b as blocksToMeta, c as blocksToSchemas, d as buildBlockMap, e as defin
5
5
  import * as react_jsx_runtime from 'react/jsx-runtime';
6
6
  import 'react';
7
7
 
8
+ /** HTTP-level failure from the cmssy API, with a machine-readable status. */
9
+ declare class CmssyRequestError extends Error {
10
+ readonly status: number;
11
+ constructor(message: string, status: number);
12
+ }
13
+ interface RetryPolicy {
14
+ /** Additional attempts after the first request (default 3). */
15
+ maxRetries?: number;
16
+ /** Exponential backoff base in ms: base * 2^attempt (default 300). */
17
+ baseDelayMs?: number;
18
+ /** Upper bound for any single wait, including Retry-After (default 3000). */
19
+ maxDelayMs?: number;
20
+ /** HTTP statuses that trigger a retry (default [429, 503]). */
21
+ retryStatuses?: number[];
22
+ }
23
+
8
24
  /**
9
25
  * The cmssy cloud GraphQL delivery endpoint. It is the same for every workspace,
10
26
  * so consumers never need to set it - `apiUrl` defaults to this. Self-hosted /
@@ -23,6 +39,9 @@ interface FetchLikeResponse {
23
39
  ok: boolean;
24
40
  status: number;
25
41
  json: () => Promise<unknown>;
42
+ headers?: {
43
+ get: (name: string) => string | null;
44
+ };
26
45
  }
27
46
  type FetchLike = (url: string, init: {
28
47
  method: string;
@@ -37,12 +56,13 @@ interface FetchPageOptions {
37
56
  workspaceId?: string;
38
57
  fetch?: FetchLike;
39
58
  signal?: AbortSignal;
59
+ retry?: RetryPolicy | false;
40
60
  }
41
61
  declare function normalizeSlug(path: string | string[] | undefined): string;
42
62
  declare function fetchPage(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyPageData | null>;
43
- declare function fetchPageById(config: CmssyClientConfig, pageId: string, options?: Pick<FetchPageOptions, "fetch" | "signal">): Promise<CmssyPageData | null>;
44
- declare function fetchPages(config: CmssyClientConfig, options?: Pick<FetchPageOptions, "fetch" | "signal">): Promise<CmssyPageSummary[]>;
45
- declare function fetchPageMeta(config: CmssyClientConfig, path: string | string[] | undefined, options?: Pick<FetchPageOptions, "fetch" | "signal">): Promise<CmssyPageMeta | null>;
63
+ declare function fetchPageById(config: CmssyClientConfig, pageId: string, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageData | null>;
64
+ declare function fetchPages(config: CmssyClientConfig, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageSummary[]>;
65
+ declare function fetchPageMeta(config: CmssyClientConfig, path: string | string[] | undefined, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageMeta | null>;
46
66
  declare function fetchLayouts(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyLayoutGroup[]>;
47
67
 
48
68
  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 faviconUrl\n ogImageUrl\n }\n }\n }\n}";
@@ -220,6 +240,12 @@ interface GraphqlRequestOptions {
220
240
  * resolves the workspace from the URL rather than a global slug lookup.
221
241
  */
222
242
  public?: boolean;
243
+ /**
244
+ * Retry transient HTTP failures (429/503, honoring Retry-After). Off by
245
+ * default: this function also carries mutations (auth, cart, checkout),
246
+ * which must never be blind-retried. Read-only callers opt in with `{}`.
247
+ */
248
+ retry?: RetryPolicy | false;
223
249
  }
224
250
  declare function graphqlRequest<T>(config: CmssyClientConfig, query: string, variables: Record<string, unknown>, options?: GraphqlRequestOptions, label?: string): Promise<T>;
225
251
 
@@ -282,4 +308,4 @@ interface UnknownBlockProps {
282
308
  }
283
309
  declare function UnknownBlock({ type }: UnknownBlockProps): react_jsx_runtime.JSX.Element;
284
310
 
285
- export { type AppToEditorMessage, BlockDefinition, BlockMap, type BoundsMessage, type ClickMessage, CmssyBlock, type CmssyBlockProps, type CmssyClient, CmssyServerLayout, type CmssyServerLayoutProps, CmssyServerPage, type CmssyServerPageProps, DEFAULT_CMSSY_API_URL, type EditorToAppMessage, FORM_QUERY, type FetchLike, type FetchLikeResponse, type FetchPageOptions, type GraphqlRequestOptions, MODEL_DEFINITIONS_QUERY, MODEL_RECORDS_QUERY, PROTOCOL_VERSION, type ParentReadyMessage, type PatchMessage, type PostTarget, type QueryScopedOptions, type ReadyMessage, SITE_CONFIG_QUERY, SUBMIT_FORM_MUTATION, type SelectMessage, UnknownBlock, type UnknownBlockProps, buildBlockContext, buildLocaleSwitchHref, collectFormIds, createCmssyClient, fetchLayouts, fetchPage, fetchPageById, fetchPageMeta, fetchPages, fetchSiteConfig, fields, getBlockContentForLanguage, graphqlRequest, isProtocolCompatible, localizeHref, localizeHtmlLinks, normalizeOrigin, normalizeSlug, parseEditorMessage, postToEditor, resolveApiUrl, resolveForms, resolvePublicUrl, resolveSiteLocales, resolveWorkspaceId, splitLocaleFromPath };
311
+ export { type AppToEditorMessage, BlockDefinition, BlockMap, type BoundsMessage, type ClickMessage, CmssyBlock, type CmssyBlockProps, type CmssyClient, CmssyRequestError, CmssyServerLayout, type CmssyServerLayoutProps, CmssyServerPage, type CmssyServerPageProps, DEFAULT_CMSSY_API_URL, type EditorToAppMessage, FORM_QUERY, type FetchLike, type FetchLikeResponse, type FetchPageOptions, type GraphqlRequestOptions, MODEL_DEFINITIONS_QUERY, MODEL_RECORDS_QUERY, PROTOCOL_VERSION, type ParentReadyMessage, type PatchMessage, type PostTarget, type QueryScopedOptions, type ReadyMessage, type RetryPolicy, SITE_CONFIG_QUERY, SUBMIT_FORM_MUTATION, type SelectMessage, UnknownBlock, type UnknownBlockProps, buildBlockContext, buildLocaleSwitchHref, collectFormIds, createCmssyClient, fetchLayouts, fetchPage, fetchPageById, fetchPageMeta, fetchPages, fetchSiteConfig, fields, getBlockContentForLanguage, graphqlRequest, isProtocolCompatible, localizeHref, localizeHtmlLinks, normalizeOrigin, normalizeSlug, parseEditorMessage, postToEditor, resolveApiUrl, resolveForms, resolvePublicUrl, resolveSiteLocales, resolveWorkspaceId, splitLocaleFromPath };
package/dist/index.js CHANGED
@@ -340,6 +340,108 @@ function parseEditorMessage(data, origin, expectedOrigin) {
340
340
  }
341
341
  }
342
342
 
343
+ // src/data/http.ts
344
+ var CmssyRequestError = class extends Error {
345
+ status;
346
+ constructor(message, status) {
347
+ super(message);
348
+ this.name = "CmssyRequestError";
349
+ this.status = status;
350
+ }
351
+ };
352
+ var DEFAULT_RETRY_STATUSES = [429, 503];
353
+ function retryAfterMs(response) {
354
+ const raw = response.headers?.get("retry-after");
355
+ if (!raw) return null;
356
+ const seconds = Number(raw);
357
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
358
+ const date = Date.parse(raw);
359
+ if (Number.isFinite(date)) return Math.max(0, date - Date.now());
360
+ return null;
361
+ }
362
+ function sleep(ms, signal) {
363
+ return new Promise((resolve, reject) => {
364
+ if (signal?.aborted) {
365
+ reject(new Error("cmssy: request aborted"));
366
+ return;
367
+ }
368
+ const timer = setTimeout(() => {
369
+ signal?.removeEventListener("abort", onAbort);
370
+ resolve();
371
+ }, ms);
372
+ function onAbort() {
373
+ clearTimeout(timer);
374
+ reject(new Error("cmssy: request aborted"));
375
+ }
376
+ signal?.addEventListener("abort", onAbort, { once: true });
377
+ });
378
+ }
379
+ async function fetchWithRetry(doFetch, url, init, retry) {
380
+ if (retry === false || retry === void 0) {
381
+ return doFetch(url, init);
382
+ }
383
+ const maxRetries = retry.maxRetries ?? 3;
384
+ const baseDelayMs = retry.baseDelayMs ?? 300;
385
+ const maxDelayMs = retry.maxDelayMs ?? 3e3;
386
+ const retryStatuses = retry.retryStatuses ?? DEFAULT_RETRY_STATUSES;
387
+ let response = await doFetch(url, init);
388
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
389
+ if (response.ok || !retryStatuses.includes(response.status)) {
390
+ return response;
391
+ }
392
+ const backoff = baseDelayMs * 2 ** attempt;
393
+ const wait = Math.min(retryAfterMs(response) ?? backoff, maxDelayMs);
394
+ await sleep(wait, init.signal);
395
+ response = await doFetch(url, init);
396
+ }
397
+ return response;
398
+ }
399
+ async function postGraphql(url, query, variables, options) {
400
+ const doFetch = options.fetch ?? globalThis.fetch;
401
+ if (typeof doFetch !== "function") {
402
+ throw new Error(
403
+ "cmssy: no fetch implementation available - pass options.fetch"
404
+ );
405
+ }
406
+ const response = await fetchWithRetry(
407
+ doFetch,
408
+ url,
409
+ {
410
+ method: "POST",
411
+ headers: { "content-type": "application/json", ...options.headers },
412
+ body: JSON.stringify({ query, variables }),
413
+ signal: options.signal
414
+ },
415
+ options.retry
416
+ );
417
+ if (!response.ok) {
418
+ let detail = "";
419
+ try {
420
+ const body = await response.json();
421
+ if (body.errors && body.errors.length > 0) {
422
+ detail = ` - ${body.errors.map((error) => error.message ?? "GraphQL error").join("; ")}`;
423
+ }
424
+ } catch {
425
+ detail = "";
426
+ }
427
+ throw new CmssyRequestError(
428
+ `cmssy: ${options.label} failed (${response.status})${detail}`,
429
+ response.status
430
+ );
431
+ }
432
+ let json;
433
+ try {
434
+ json = await response.json();
435
+ } catch {
436
+ throw new Error(`cmssy: invalid JSON response from the ${options.label}`);
437
+ }
438
+ if (json.errors && json.errors.length > 0) {
439
+ const message = json.errors.map((error) => error.message ?? "GraphQL error").join("; ");
440
+ throw new Error(`cmssy: ${options.label} error - ${message}`);
441
+ }
442
+ return json.data;
443
+ }
444
+
343
445
  // src/content/content-client.ts
344
446
  var DEFAULT_CMSSY_API_URL = "https://api.cmssy.io/graphql";
345
447
  function resolveApiUrl(apiUrl) {
@@ -431,251 +533,113 @@ function normalizeSlug(path) {
431
533
  }
432
534
  async function fetchPage(config, path, options = {}) {
433
535
  const slug = normalizeSlug(path);
434
- const doFetch = options.fetch ?? globalThis.fetch;
435
- if (typeof doFetch !== "function") {
436
- throw new Error(
437
- "cmssy: no fetch implementation available - pass options.fetch"
438
- );
439
- }
440
536
  const trimmedSecret = options.previewSecret?.trim();
441
537
  const previewSecret = trimmedSecret ? trimmedSecret : null;
442
538
  const devToken = options.devToken?.trim();
443
539
  const devPreview = Boolean(options.devPreview && devToken);
444
- const headers = {
445
- "content-type": "application/json"
446
- };
540
+ const headers = {};
447
541
  if (devPreview && devToken) {
448
542
  headers["authorization"] = `Bearer ${devToken}`;
449
543
  if (options.workspaceId) {
450
544
  headers["x-workspace-id"] = options.workspaceId;
451
545
  }
452
546
  }
453
- const response = await doFetch(resolvePublicUrl(config), {
454
- method: "POST",
455
- headers,
456
- body: JSON.stringify({
457
- query: devPreview ? PUBLIC_PAGE_DEV_QUERY : PUBLIC_PAGE_QUERY,
458
- variables: {
459
- workspaceSlug: config.workspaceSlug,
460
- slug,
461
- previewSecret,
462
- ...devPreview ? { devPreview: true } : {}
463
- }
464
- }),
465
- signal: options.signal
466
- });
467
- if (!response.ok) {
468
- let detail = "";
469
- try {
470
- const body = await response.json();
471
- if (body.errors && body.errors.length > 0) {
472
- detail = ` - ${body.errors.map((error) => error.message ?? "GraphQL error").join("; ")}`;
473
- }
474
- } catch {
475
- detail = "";
547
+ const data = await postGraphql(
548
+ resolvePublicUrl(config),
549
+ devPreview ? PUBLIC_PAGE_DEV_QUERY : PUBLIC_PAGE_QUERY,
550
+ {
551
+ workspaceSlug: config.workspaceSlug,
552
+ slug,
553
+ previewSecret,
554
+ ...devPreview ? { devPreview: true } : {}
555
+ },
556
+ {
557
+ fetch: options.fetch,
558
+ signal: options.signal,
559
+ headers,
560
+ retry: options.retry ?? {},
561
+ label: "page fetch"
476
562
  }
477
- throw new Error(`cmssy: page fetch failed (${response.status})${detail}`);
478
- }
479
- let json;
480
- try {
481
- json = await response.json();
482
- } catch {
483
- throw new Error("cmssy: invalid JSON response from the page query");
484
- }
485
- if (json.errors && json.errors.length > 0) {
486
- const message = json.errors.map((error) => error.message ?? "GraphQL error").join("; ");
487
- throw new Error(`cmssy: page fetch error - ${message}`);
488
- }
489
- const page = json.data?.public?.page?.get;
563
+ );
564
+ const page = data?.public?.page?.get;
490
565
  if (!page) return null;
491
566
  const draft = previewSecret !== null || devPreview;
492
567
  const blocks = (draft ? page.blocks : page.publishedBlocks) ?? [];
493
568
  return { id: page.id, blocks };
494
569
  }
495
570
  async function fetchPageById(config, pageId, options = {}) {
496
- const doFetch = options.fetch ?? globalThis.fetch;
497
- if (typeof doFetch !== "function") {
498
- throw new Error(
499
- "cmssy: no fetch implementation available - pass options.fetch"
500
- );
501
- }
502
- const response = await doFetch(resolvePublicUrl(config), {
503
- method: "POST",
504
- headers: { "content-type": "application/json" },
505
- body: JSON.stringify({
506
- query: PUBLIC_PAGE_BY_ID_QUERY,
507
- variables: { workspaceSlug: config.workspaceSlug, pageId }
508
- }),
509
- signal: options.signal
510
- });
511
- if (!response.ok) {
512
- let detail = "";
513
- try {
514
- const body = await response.json();
515
- if (body.errors && body.errors.length > 0) {
516
- detail = ` - ${body.errors.map((error) => error.message ?? "GraphQL error").join("; ")}`;
517
- }
518
- } catch {
519
- detail = "";
571
+ const data = await postGraphql(
572
+ resolvePublicUrl(config),
573
+ PUBLIC_PAGE_BY_ID_QUERY,
574
+ { workspaceSlug: config.workspaceSlug, pageId },
575
+ {
576
+ fetch: options.fetch,
577
+ signal: options.signal,
578
+ retry: options.retry ?? {},
579
+ label: "page-by-id fetch"
520
580
  }
521
- throw new Error(
522
- `cmssy: page-by-id fetch failed (${response.status})${detail}`
523
- );
524
- }
525
- let json;
526
- try {
527
- json = await response.json();
528
- } catch {
529
- throw new Error("cmssy: invalid JSON response from the page-by-id query");
530
- }
531
- if (json.errors && json.errors.length > 0) {
532
- const message = json.errors.map((error) => error.message ?? "GraphQL error").join("; ");
533
- throw new Error(`cmssy: page-by-id fetch error - ${message}`);
534
- }
535
- const page = json.data?.public?.page?.getById;
581
+ );
582
+ const page = data?.public?.page?.getById;
536
583
  if (!page) return null;
537
584
  return { id: page.id, blocks: page.publishedBlocks ?? [] };
538
585
  }
539
586
  async function fetchPages(config, options = {}) {
540
- const doFetch = options.fetch ?? globalThis.fetch;
541
- if (typeof doFetch !== "function") {
542
- throw new Error(
543
- "cmssy: no fetch implementation available - pass options.fetch"
544
- );
545
- }
546
- const response = await doFetch(resolvePublicUrl(config), {
547
- method: "POST",
548
- headers: { "content-type": "application/json" },
549
- body: JSON.stringify({
550
- query: PUBLIC_PAGES_QUERY,
551
- variables: { workspaceSlug: config.workspaceSlug }
552
- }),
553
- signal: options.signal
554
- });
555
- if (!response.ok) {
556
- throw new Error(`cmssy: pages fetch failed (${response.status})`);
557
- }
558
- let json;
559
- try {
560
- json = await response.json();
561
- } catch {
562
- throw new Error("cmssy: invalid JSON response from the pages query");
563
- }
564
- if (json.errors && json.errors.length > 0) {
565
- const message = json.errors.map((error) => error.message ?? "GraphQL error").join("; ");
566
- throw new Error(`cmssy: pages fetch error - ${message}`);
567
- }
568
- return json.data?.public?.page?.list ?? [];
587
+ const data = await postGraphql(
588
+ resolvePublicUrl(config),
589
+ PUBLIC_PAGES_QUERY,
590
+ { workspaceSlug: config.workspaceSlug },
591
+ {
592
+ fetch: options.fetch,
593
+ signal: options.signal,
594
+ retry: options.retry ?? {},
595
+ label: "pages fetch"
596
+ }
597
+ );
598
+ return data?.public?.page?.list ?? [];
569
599
  }
570
600
  async function fetchPageMeta(config, path, options = {}) {
571
601
  const slug = normalizeSlug(path);
572
- const doFetch = options.fetch ?? globalThis.fetch;
573
- if (typeof doFetch !== "function") {
574
- throw new Error(
575
- "cmssy: no fetch implementation available - pass options.fetch"
576
- );
577
- }
578
- const response = await doFetch(resolvePublicUrl(config), {
579
- method: "POST",
580
- headers: { "content-type": "application/json" },
581
- body: JSON.stringify({
582
- query: PUBLIC_PAGE_META_QUERY,
583
- variables: { workspaceSlug: config.workspaceSlug, slug }
584
- }),
585
- signal: options.signal
586
- });
587
- if (!response.ok) {
588
- throw new Error(`cmssy: page meta fetch failed (${response.status})`);
589
- }
590
- let json;
591
- try {
592
- json = await response.json();
593
- } catch {
594
- throw new Error("cmssy: invalid JSON response from the page meta query");
595
- }
596
- if (json.errors && json.errors.length > 0) {
597
- const message = json.errors.map((error) => error.message ?? "GraphQL error").join("; ");
598
- throw new Error(`cmssy: page meta fetch error - ${message}`);
599
- }
600
- return json.data?.public?.page?.get ?? null;
602
+ const data = await postGraphql(
603
+ resolvePublicUrl(config),
604
+ PUBLIC_PAGE_META_QUERY,
605
+ { workspaceSlug: config.workspaceSlug, slug },
606
+ {
607
+ fetch: options.fetch,
608
+ signal: options.signal,
609
+ retry: options.retry ?? {},
610
+ label: "page meta fetch"
611
+ }
612
+ );
613
+ return data?.public?.page?.get ?? null;
601
614
  }
602
615
  async function fetchLayouts(config, path, options = {}) {
603
616
  const pageSlug = normalizeSlug(path);
604
- const doFetch = options.fetch ?? globalThis.fetch;
605
- if (typeof doFetch !== "function") {
606
- throw new Error(
607
- "cmssy: no fetch implementation available - pass options.fetch"
608
- );
609
- }
610
617
  const trimmedSecret = options.previewSecret?.trim();
611
618
  const previewSecret = trimmedSecret ? trimmedSecret : null;
612
- const response = await doFetch(resolvePublicUrl(config), {
613
- method: "POST",
614
- headers: { "content-type": "application/json" },
615
- body: JSON.stringify({
616
- query: PUBLIC_PAGE_LAYOUTS_QUERY,
617
- variables: {
618
- workspaceSlug: config.workspaceSlug,
619
- pageSlug,
620
- previewSecret
621
- }
622
- }),
623
- signal: options.signal
624
- });
625
- if (!response.ok) {
626
- throw new Error(`cmssy: layouts fetch failed (${response.status})`);
627
- }
628
- let json;
629
- try {
630
- json = await response.json();
631
- } catch {
632
- throw new Error("cmssy: invalid JSON response from the layouts query");
633
- }
634
- if (json.errors && json.errors.length > 0) {
635
- const message = json.errors.map((error) => error.message ?? "GraphQL error").join("; ");
636
- throw new Error(`cmssy: layouts fetch error - ${message}`);
637
- }
638
- return json.data?.public?.page?.layouts ?? [];
619
+ const data = await postGraphql(
620
+ resolvePublicUrl(config),
621
+ PUBLIC_PAGE_LAYOUTS_QUERY,
622
+ { workspaceSlug: config.workspaceSlug, pageSlug, previewSecret },
623
+ {
624
+ fetch: options.fetch,
625
+ signal: options.signal,
626
+ retry: options.retry ?? {},
627
+ label: "layouts fetch"
628
+ }
629
+ );
630
+ return data?.public?.page?.layouts ?? [];
639
631
  }
640
632
 
641
633
  // src/data/graphql-request.ts
642
634
  async function graphqlRequest(config, query, variables, options = {}, label = "request") {
643
- const doFetch = options.fetch ?? globalThis.fetch;
644
- if (typeof doFetch !== "function") {
645
- throw new Error(
646
- "cmssy: no fetch implementation available - pass options.fetch"
647
- );
648
- }
649
635
  const url = options.public ? resolvePublicUrl(config) : resolveApiUrl(config.apiUrl);
650
- const response = await doFetch(url, {
651
- method: "POST",
652
- headers: { "content-type": "application/json", ...options.headers },
653
- body: JSON.stringify({ query, variables }),
654
- signal: options.signal
636
+ return postGraphql(url, query, variables, {
637
+ fetch: options.fetch,
638
+ signal: options.signal,
639
+ headers: options.headers,
640
+ retry: options.retry,
641
+ label
655
642
  });
656
- if (!response.ok) {
657
- let detail = "";
658
- try {
659
- const body = await response.json();
660
- if (body.errors && body.errors.length > 0) {
661
- detail = ` - ${body.errors.map((error) => error.message ?? "GraphQL error").join("; ")}`;
662
- }
663
- } catch {
664
- detail = "";
665
- }
666
- throw new Error(`cmssy: ${label} failed (${response.status})${detail}`);
667
- }
668
- let json;
669
- try {
670
- json = await response.json();
671
- } catch {
672
- throw new Error(`cmssy: invalid JSON response from the ${label}`);
673
- }
674
- if (json.errors && json.errors.length > 0) {
675
- const message = json.errors.map((error) => error.message ?? "GraphQL error").join("; ");
676
- throw new Error(`cmssy: ${label} error - ${message}`);
677
- }
678
- return json.data;
679
643
  }
680
644
 
681
645
  // src/data/queries.ts
@@ -757,7 +721,7 @@ async function fetchSiteConfig(config, options = {}) {
757
721
  config,
758
722
  SITE_CONFIG_QUERY,
759
723
  { workspaceSlug: config.workspaceSlug },
760
- { ...options, public: true },
724
+ { ...options, public: true, retry: options.retry ?? {} },
761
725
  "site config query"
762
726
  );
763
727
  return data.public?.siteConfig ?? null;
@@ -873,7 +837,7 @@ async function resolveSiteLocales(config, options) {
873
837
  config,
874
838
  SITE_CONFIG_QUERY,
875
839
  { workspaceSlug: config.workspaceSlug },
876
- { ...options, public: true },
840
+ { ...options, public: true, retry: options?.retry ?? {} },
877
841
  "site config"
878
842
  );
879
843
  const siteConfig = data.public?.siteConfig ?? null;
@@ -978,4 +942,4 @@ function CmssyBlock({
978
942
  );
979
943
  }
980
944
 
981
- export { CmssyBlock, CmssyServerLayout, CmssyServerPage, DEFAULT_CMSSY_API_URL, FORM_QUERY, MODEL_DEFINITIONS_QUERY, MODEL_RECORDS_QUERY, PROTOCOL_VERSION, SITE_CONFIG_QUERY, SUBMIT_FORM_MUTATION, UnknownBlock, blocksToMeta, blocksToSchemas, buildBlockContext, buildBlockMap, buildLocaleSwitchHref, collectFormIds, createCmssyClient, defineBlock, fetchLayouts, fetchPage, fetchPageById, fetchPageMeta, fetchPages, fetchSiteConfig, fields, getBlockContentForLanguage, graphqlRequest, isProtocolCompatible, localizeHref, localizeHtmlLinks, normalizeOrigin, normalizeSlug, parseEditorMessage, postToEditor, resolveApiUrl, resolveForms, resolvePublicUrl, resolveSiteLocales, resolveWorkspaceId, splitLocaleFromPath };
945
+ export { CmssyBlock, CmssyRequestError, CmssyServerLayout, CmssyServerPage, DEFAULT_CMSSY_API_URL, FORM_QUERY, MODEL_DEFINITIONS_QUERY, MODEL_RECORDS_QUERY, PROTOCOL_VERSION, SITE_CONFIG_QUERY, SUBMIT_FORM_MUTATION, UnknownBlock, blocksToMeta, blocksToSchemas, buildBlockContext, buildBlockMap, buildLocaleSwitchHref, collectFormIds, createCmssyClient, defineBlock, fetchLayouts, fetchPage, fetchPageById, fetchPageMeta, fetchPages, fetchSiteConfig, fields, getBlockContentForLanguage, graphqlRequest, isProtocolCompatible, localizeHref, localizeHtmlLinks, normalizeOrigin, normalizeSlug, parseEditorMessage, postToEditor, resolveApiUrl, resolveForms, resolvePublicUrl, resolveSiteLocales, resolveWorkspaceId, splitLocaleFromPath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmssy/react",
3
- "version": "2.7.0",
3
+ "version": "4.0.0",
4
4
  "description": "React blocks, renderers, data client and editor bridge for cmssy headless sites",
5
5
  "keywords": [
6
6
  "cmssy",