@riverbankcms/sdk 0.6.1 → 0.7.2

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.
Files changed (172) hide show
  1. package/README.md +175 -0
  2. package/dist/cli/index.js +42 -95
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/cli/init-docs/content/agents-section.md +50 -0
  5. package/dist/cli/init-docs/content/cli-reference.md +574 -0
  6. package/dist/cli/init-docs/content/content-management.md +384 -0
  7. package/dist/cli/init-docs/content/context-brand.md +125 -0
  8. package/dist/cli/init-docs/content/context-brief.md +77 -0
  9. package/dist/cli/init-docs/content/context-knowledge.md +111 -0
  10. package/dist/cli/init-docs/content/getting-started.md +130 -0
  11. package/dist/cli/init-docs/content/site-workflows-readme.md +96 -0
  12. package/dist/cli/init-docs/content/workflow-add-block.md +228 -0
  13. package/dist/cli/init-docs/content/workflow-create-page.md +193 -0
  14. package/dist/cli/init-docs/content/workflow-publish.md +280 -0
  15. package/dist/client/client.d.mts +2 -2
  16. package/dist/client/client.d.ts +2 -2
  17. package/dist/client/client.js +536 -71
  18. package/dist/client/client.js.map +1 -1
  19. package/dist/client/client.mjs +536 -78
  20. package/dist/client/client.mjs.map +1 -1
  21. package/dist/client/hooks.d.mts +2 -2
  22. package/dist/client/hooks.d.ts +2 -2
  23. package/dist/client/usePage-C9tJpuKa.d.mts +6813 -0
  24. package/dist/client/usePage-Db9kzA41.d.ts +6813 -0
  25. package/dist/server/{Layout-wBtJLTVX.d.ts → Layout-Ce7PU9I5.d.ts} +1 -1
  26. package/dist/server/{Layout-B7cvis7r.d.mts → Layout-WllR8Zug.d.mts} +1 -1
  27. package/dist/server/{chunk-I7ZR2WO3.mjs → chunk-5JT452F2.mjs} +537 -76
  28. package/dist/server/chunk-5JT452F2.mjs.map +1 -0
  29. package/dist/server/{chunk-Z5ZA6Q4D.mjs → chunk-74XUVNOO.mjs} +5 -3
  30. package/dist/server/chunk-74XUVNOO.mjs.map +1 -0
  31. package/dist/server/{chunk-3B364WO2.js → chunk-7BVRA5MY.js} +4 -49
  32. package/dist/server/chunk-7BVRA5MY.js.map +1 -0
  33. package/dist/server/chunk-AET56TQX.mjs +45 -0
  34. package/dist/server/chunk-AET56TQX.mjs.map +1 -0
  35. package/dist/server/chunk-ARNCLSQT.mjs +52 -0
  36. package/dist/server/chunk-ARNCLSQT.mjs.map +1 -0
  37. package/dist/server/chunk-BNQV3PXP.js +69 -0
  38. package/dist/server/chunk-BNQV3PXP.js.map +1 -0
  39. package/dist/server/{chunk-IVHIQFJH.js → chunk-HMENX4Y7.js} +543 -82
  40. package/dist/server/chunk-HMENX4Y7.js.map +1 -0
  41. package/dist/server/{chunk-I2D7KOEA.js → chunk-JWRNMNWI.js} +5 -3
  42. package/dist/server/chunk-JWRNMNWI.js.map +1 -0
  43. package/dist/server/chunk-LQUKXIW7.mjs +13 -0
  44. package/dist/server/chunk-LQUKXIW7.mjs.map +1 -0
  45. package/dist/server/{chunk-XXFF4RVR.mjs → chunk-RBJFXNDM.mjs} +1 -46
  46. package/dist/server/chunk-RBJFXNDM.mjs.map +1 -0
  47. package/dist/server/chunk-SWYWZT3L.mjs +69 -0
  48. package/dist/server/chunk-SWYWZT3L.mjs.map +1 -0
  49. package/dist/server/chunk-T26N3P26.js +52 -0
  50. package/dist/server/chunk-T26N3P26.js.map +1 -0
  51. package/dist/server/chunk-VODFQMUW.js +45 -0
  52. package/dist/server/chunk-VODFQMUW.js.map +1 -0
  53. package/dist/server/chunk-WYNEYDXO.js +13 -0
  54. package/dist/server/chunk-WYNEYDXO.js.map +1 -0
  55. package/dist/server/{components-CICSJyp_.d.ts → components--LT61IKp.d.ts} +2 -2
  56. package/dist/server/{components-CMMwDXTW.d.mts → components-RPzRQve6.d.mts} +2 -2
  57. package/dist/server/components.d.mts +4 -4
  58. package/dist/server/components.d.ts +4 -4
  59. package/dist/server/components.js +5 -4
  60. package/dist/server/components.js.map +1 -1
  61. package/dist/server/components.mjs +5 -4
  62. package/dist/server/config-validation.js +0 -1
  63. package/dist/server/config-validation.js.map +1 -1
  64. package/dist/server/config-validation.mjs +0 -1
  65. package/dist/server/config.js +0 -1
  66. package/dist/server/config.js.map +1 -1
  67. package/dist/server/config.mjs +0 -1
  68. package/dist/server/config.mjs.map +1 -1
  69. package/dist/server/data.d.mts +1 -1
  70. package/dist/server/data.d.ts +1 -1
  71. package/dist/server/data.js +0 -1
  72. package/dist/server/data.js.map +1 -1
  73. package/dist/server/data.mjs +0 -1
  74. package/dist/server/env.d.mts +23 -0
  75. package/dist/server/env.d.ts +23 -0
  76. package/dist/server/env.js +7 -0
  77. package/dist/server/env.js.map +1 -0
  78. package/dist/server/env.mjs +7 -0
  79. package/dist/server/index-BL66CU6d.d.mts +130 -0
  80. package/dist/server/{index-Bucs6UqG.d.mts → index-Bkva0WAj.d.mts} +1 -1
  81. package/dist/server/index-CJk9iQQW.d.ts +130 -0
  82. package/dist/server/{index-Cp7tJuRt.d.ts → index-CSBWKA3r.d.ts} +1 -1
  83. package/dist/server/index.d.mts +3 -3
  84. package/dist/server/index.d.ts +3 -3
  85. package/dist/server/index.js +2 -3
  86. package/dist/server/index.js.map +1 -1
  87. package/dist/server/index.mjs +1 -2
  88. package/dist/server/index.mjs.map +1 -1
  89. package/dist/server/{loadContent-Buvmudee.d.ts → loadContent-CXUWMuzY.d.ts} +11 -3
  90. package/dist/server/{loadContent-BS-3wesN.d.mts → loadContent-F_tAS0Nl.d.mts} +11 -3
  91. package/dist/server/{loadPage-IDGVDFBB.js → loadPage-6I7F6GRF.js} +1 -2
  92. package/dist/server/loadPage-6I7F6GRF.js.map +1 -0
  93. package/dist/server/{loadPage-B8mQUUSo.d.mts → loadPage-CxlYLe5K.d.mts} +1 -1
  94. package/dist/server/{loadPage-DNQTTRHL.mjs → loadPage-JI2SML4M.mjs} +1 -2
  95. package/dist/server/{loadPage-DP3nrHBi.d.ts → loadPage-i2r-X5b9.d.ts} +1 -1
  96. package/dist/server/metadata.d.mts +8 -135
  97. package/dist/server/metadata.d.ts +8 -135
  98. package/dist/server/metadata.js +4 -65
  99. package/dist/server/metadata.js.map +1 -1
  100. package/dist/server/metadata.mjs +4 -65
  101. package/dist/server/metadata.mjs.map +1 -1
  102. package/dist/server/navigation.js +0 -1
  103. package/dist/server/navigation.js.map +1 -1
  104. package/dist/server/navigation.mjs +0 -1
  105. package/dist/server/next/revalidate.d.mts +66 -0
  106. package/dist/server/next/revalidate.d.ts +66 -0
  107. package/dist/server/next/revalidate.js +59 -0
  108. package/dist/server/next/revalidate.js.map +1 -0
  109. package/dist/server/next/revalidate.mjs +59 -0
  110. package/dist/server/next/revalidate.mjs.map +1 -0
  111. package/dist/server/next/tags.d.mts +78 -0
  112. package/dist/server/next/tags.d.ts +78 -0
  113. package/dist/server/next/tags.js +34 -0
  114. package/dist/server/next/tags.js.map +1 -0
  115. package/dist/server/next/tags.mjs +34 -0
  116. package/dist/server/next/tags.mjs.map +1 -0
  117. package/dist/server/next.d.mts +432 -0
  118. package/dist/server/next.d.ts +432 -0
  119. package/dist/server/next.js +217 -0
  120. package/dist/server/next.js.map +1 -0
  121. package/dist/server/next.mjs +217 -0
  122. package/dist/server/next.mjs.map +1 -0
  123. package/dist/server/rendering/server.d.mts +3 -3
  124. package/dist/server/rendering/server.d.ts +3 -3
  125. package/dist/server/rendering/server.js +5 -4
  126. package/dist/server/rendering/server.js.map +1 -1
  127. package/dist/server/rendering/server.mjs +5 -4
  128. package/dist/server/rendering.d.mts +6 -6
  129. package/dist/server/rendering.d.ts +6 -6
  130. package/dist/server/rendering.js +7 -6
  131. package/dist/server/rendering.js.map +1 -1
  132. package/dist/server/rendering.mjs +9 -8
  133. package/dist/server/routing.d.mts +2 -2
  134. package/dist/server/routing.d.ts +2 -2
  135. package/dist/server/routing.js +2 -4
  136. package/dist/server/routing.js.map +1 -1
  137. package/dist/server/routing.mjs +1 -3
  138. package/dist/server/routing.mjs.map +1 -1
  139. package/dist/server/server.d.mts +4 -4
  140. package/dist/server/server.d.ts +4 -4
  141. package/dist/server/server.js +4 -5
  142. package/dist/server/server.js.map +1 -1
  143. package/dist/server/server.mjs +5 -6
  144. package/dist/server/theme-bridge.js +0 -1
  145. package/dist/server/theme-bridge.js.map +1 -1
  146. package/dist/server/theme-bridge.mjs +0 -1
  147. package/dist/server/theme-bridge.mjs.map +1 -1
  148. package/dist/server/theme.js +1 -3
  149. package/dist/server/theme.js.map +1 -1
  150. package/dist/server/theme.mjs +0 -2
  151. package/dist/server/theme.mjs.map +1 -1
  152. package/dist/server/{types-BvcJU7zk.d.ts → types-DnkRh0UL.d.ts} +118 -9
  153. package/dist/server/{types-1cLz0vnq.d.mts → types-MF2AWoKv.d.mts} +118 -9
  154. package/dist/server/webhooks.d.mts +75 -0
  155. package/dist/server/webhooks.d.ts +75 -0
  156. package/dist/server/webhooks.js +11 -0
  157. package/dist/server/webhooks.js.map +1 -0
  158. package/dist/server/webhooks.mjs +11 -0
  159. package/dist/server/webhooks.mjs.map +1 -0
  160. package/package.json +33 -1
  161. package/dist/server/chunk-3B364WO2.js.map +0 -1
  162. package/dist/server/chunk-BJTO5JO5.mjs +0 -11
  163. package/dist/server/chunk-DGUM43GV.js +0 -11
  164. package/dist/server/chunk-DGUM43GV.js.map +0 -1
  165. package/dist/server/chunk-I2D7KOEA.js.map +0 -1
  166. package/dist/server/chunk-I7ZR2WO3.mjs.map +0 -1
  167. package/dist/server/chunk-IVHIQFJH.js.map +0 -1
  168. package/dist/server/chunk-XXFF4RVR.mjs.map +0 -1
  169. package/dist/server/chunk-Z5ZA6Q4D.mjs.map +0 -1
  170. package/dist/server/loadPage-IDGVDFBB.js.map +0 -1
  171. /package/dist/server/{chunk-BJTO5JO5.mjs.map → env.mjs.map} +0 -0
  172. /package/dist/server/{loadPage-DNQTTRHL.mjs.map → loadPage-JI2SML4M.mjs.map} +0 -0
@@ -1,11 +1,4 @@
1
1
  "use client";
2
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
- }) : x)(function(x) {
5
- if (typeof require !== "undefined") return require.apply(this, arguments);
6
- throw Error('Dynamic require of "' + x + '" is not supported');
7
- });
8
-
9
2
  // src/rendering/hooks/usePage.ts
10
3
  import { useState, useEffect } from "react";
11
4
 
@@ -17296,10 +17289,10 @@ function resolveApiBaseUrl() {
17296
17289
  var revalidateTag = null;
17297
17290
  if (typeof window === "undefined") {
17298
17291
  try {
17299
- const nextCache = __require("next/cache");
17300
- revalidateTag = nextCache.revalidateTag;
17292
+ const dynamicRequire = new Function("modulePath", "return require(modulePath)");
17293
+ const nextCache = dynamicRequire("next/cache");
17294
+ revalidateTag = nextCache.revalidateTag ?? null;
17301
17295
  } catch {
17302
- revalidateTag = null;
17303
17296
  }
17304
17297
  }
17305
17298
  var sdkVersion;
@@ -17328,8 +17321,22 @@ var ApiRequestError = class extends Error {
17328
17321
  this.body = options.body;
17329
17322
  this.cause = options.cause;
17330
17323
  this.errorCode = options.errorCode;
17324
+ this.retryAfterMs = options.retryAfterMs;
17331
17325
  }
17332
17326
  };
17327
+ function parseRetryAfterHeader(headerValue) {
17328
+ if (!headerValue) return void 0;
17329
+ if (/^\d+$/.test(headerValue)) {
17330
+ const seconds = parseInt(headerValue, 10);
17331
+ return seconds * 1e3;
17332
+ }
17333
+ const date = new Date(headerValue);
17334
+ if (!isNaN(date.getTime())) {
17335
+ const delayMs = date.getTime() - Date.now();
17336
+ return delayMs > 0 ? delayMs : void 0;
17337
+ }
17338
+ return void 0;
17339
+ }
17333
17340
  function buildEndpointURL(baseURL, endpoint) {
17334
17341
  return baseURL + API_ENDPOINTS[endpoint].path;
17335
17342
  }
@@ -17546,6 +17553,7 @@ function createParsedClient(rawClient) {
17546
17553
  if (!response.ok) {
17547
17554
  const body = await parseErrorBody(response);
17548
17555
  const requestId = response.headers.get("x-request-id") ?? void 0;
17556
+ const retryAfterMs = parseRetryAfterHeader(response.headers.get("retry-after"));
17549
17557
  throw new ApiRequestError(
17550
17558
  `Request to ${String(endpoint)} failed with status ${response.status}`,
17551
17559
  {
@@ -17554,7 +17562,8 @@ function createParsedClient(rawClient) {
17554
17562
  method: config.method,
17555
17563
  auth,
17556
17564
  requestId,
17557
- body
17565
+ body,
17566
+ retryAfterMs
17558
17567
  }
17559
17568
  );
17560
17569
  }
@@ -17602,38 +17611,117 @@ var SimpleCache = class {
17602
17611
  this.cache = /* @__PURE__ */ new Map();
17603
17612
  this.maxSize = options.maxSize ?? 100;
17604
17613
  this.ttl = options.ttl ?? 3e5;
17614
+ this.staleTtl = options.staleTtl ?? 3e5;
17615
+ }
17616
+ /**
17617
+ * Get a fresh value (within TTL)
17618
+ * @returns The value if fresh, null otherwise
17619
+ */
17620
+ getFresh(key) {
17621
+ const entry = this.cache.get(key);
17622
+ if (!entry) return null;
17623
+ const now = Date.now();
17624
+ if (now <= entry.freshUntil) {
17625
+ return entry.value;
17626
+ }
17627
+ return null;
17605
17628
  }
17606
- get(key) {
17629
+ /**
17630
+ * Get a value that may be stale (past TTL but within staleTtl)
17631
+ * @returns Object with value and stale age, or null if expired
17632
+ */
17633
+ getStale(key) {
17607
17634
  const entry = this.cache.get(key);
17608
- if (!entry) return void 0;
17609
- if (Date.now() > entry.expires) {
17635
+ if (!entry) return null;
17636
+ const now = Date.now();
17637
+ if (now > entry.staleUntil) {
17610
17638
  this.cache.delete(key);
17611
- return void 0;
17639
+ return null;
17612
17640
  }
17613
- return entry.value;
17641
+ const staleAgeSec = now <= entry.freshUntil ? 0 : Math.floor((now - entry.freshUntil) / 1e3);
17642
+ return {
17643
+ value: entry.value,
17644
+ staleAgeSec
17645
+ };
17614
17646
  }
17615
- set(key, value) {
17616
- if (this.cache.size >= this.maxSize) {
17617
- const firstKey = this.cache.keys().next().value;
17618
- if (firstKey) {
17619
- this.cache.delete(firstKey);
17620
- }
17647
+ /**
17648
+ * Store a value with TTL and stale window
17649
+ */
17650
+ set(key, value, options) {
17651
+ const ttl = options?.ttl ?? this.ttl;
17652
+ const staleTtl = options?.staleTtl ?? this.staleTtl;
17653
+ const now = Date.now();
17654
+ if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
17655
+ this.evictOne(now);
17621
17656
  }
17622
17657
  this.cache.set(key, {
17623
17658
  value,
17624
- expires: Date.now() + this.ttl
17659
+ createdAt: now,
17660
+ freshUntil: now + ttl,
17661
+ staleUntil: now + ttl + staleTtl
17625
17662
  });
17626
17663
  }
17664
+ /**
17665
+ * Evict one entry to make room for a new one
17666
+ * Priority: oldest stale entry, then oldest fresh entry
17667
+ */
17668
+ evictOne(now) {
17669
+ let oldestStaleKey = null;
17670
+ let oldestStaleTime = Infinity;
17671
+ let oldestFreshKey = null;
17672
+ let oldestFreshTime = Infinity;
17673
+ for (const [key, entry] of this.cache) {
17674
+ if (now > entry.freshUntil) {
17675
+ if (entry.createdAt < oldestStaleTime) {
17676
+ oldestStaleTime = entry.createdAt;
17677
+ oldestStaleKey = key;
17678
+ }
17679
+ } else {
17680
+ if (entry.createdAt < oldestFreshTime) {
17681
+ oldestFreshTime = entry.createdAt;
17682
+ oldestFreshKey = key;
17683
+ }
17684
+ }
17685
+ }
17686
+ const keyToEvict = oldestStaleKey ?? oldestFreshKey;
17687
+ if (keyToEvict) {
17688
+ this.cache.delete(keyToEvict);
17689
+ }
17690
+ }
17691
+ /**
17692
+ * Remove all fully expired entries (past staleUntil)
17693
+ */
17694
+ prune() {
17695
+ const now = Date.now();
17696
+ for (const [key, entry] of this.cache) {
17697
+ if (now > entry.staleUntil) {
17698
+ this.cache.delete(key);
17699
+ }
17700
+ }
17701
+ }
17702
+ /**
17703
+ * Clear all entries
17704
+ */
17627
17705
  clear() {
17628
17706
  this.cache.clear();
17629
17707
  }
17708
+ /**
17709
+ * Check if a key exists and is not fully expired
17710
+ */
17630
17711
  has(key) {
17631
- return this.get(key) !== void 0;
17712
+ const entry = this.cache.get(key);
17713
+ if (!entry) return false;
17714
+ const now = Date.now();
17715
+ if (now > entry.staleUntil) {
17716
+ this.cache.delete(key);
17717
+ return false;
17718
+ }
17719
+ return now <= entry.freshUntil;
17632
17720
  }
17633
17721
  };
17634
17722
 
17635
17723
  // src/version.ts
17636
- var SDK_VERSION = "0.6.1";
17724
+ var SDK_VERSION = "0.7.2";
17637
17725
 
17638
17726
  // src/client/error.ts
17639
17727
  var RiverbankApiError = class _RiverbankApiError extends Error {
@@ -17645,8 +17733,31 @@ var RiverbankApiError = class _RiverbankApiError extends Error {
17645
17733
  this.status = apiError.status;
17646
17734
  this.fieldErrors = apiError.fieldErrors;
17647
17735
  this.timestamp = apiError.timestamp;
17736
+ this.retryAfterMs = "retryAfterMs" in apiError ? apiError.retryAfterMs : void 0;
17737
+ this.isRetryable = this.computeRetryable();
17648
17738
  Object.setPrototypeOf(this, _RiverbankApiError.prototype);
17649
17739
  }
17740
+ /**
17741
+ * Compute whether this error is retryable based on HTTP status code.
17742
+ * - 0 (network errors - no HTTP response): retryable
17743
+ * - 429 (rate limit): retryable
17744
+ * - 5xx (server errors): retryable
17745
+ * - 4xx (client errors, except 429): NOT retryable
17746
+ */
17747
+ computeRetryable() {
17748
+ if (this.status === 0) return true;
17749
+ if (this.status === 429) return true;
17750
+ if (this.status >= 500) return true;
17751
+ return false;
17752
+ }
17753
+ /**
17754
+ * Check if this is a network error (no HTTP response received)
17755
+ *
17756
+ * Matches: network:connection_error, network:timeout, network:dns_error
17757
+ */
17758
+ isNetworkError() {
17759
+ return this.code.startsWith("network:");
17760
+ }
17650
17761
  /**
17651
17762
  * Check if this error matches a specific error code
17652
17763
  *
@@ -17705,9 +17816,215 @@ var RiverbankApiError = class _RiverbankApiError extends Error {
17705
17816
  }
17706
17817
  };
17707
17818
 
17819
+ // src/client/resilience.ts
17820
+ var DEFAULT_RETRY_CONFIG = {
17821
+ maxAttempts: 3,
17822
+ baseDelayMs: 200,
17823
+ maxDelayMs: 2e3,
17824
+ jitter: "full"
17825
+ };
17826
+ var DEFAULT_CIRCUIT_BREAKER_CONFIG = {
17827
+ failureThreshold: 5,
17828
+ resetTimeoutMs: 3e4,
17829
+ halfOpenMaxRequests: 2
17830
+ };
17831
+ function isTransientError(error) {
17832
+ if (error instanceof RiverbankApiError) {
17833
+ if (error.status === 0) return true;
17834
+ if (error.status === 429) return true;
17835
+ if (error.status >= 500) return true;
17836
+ return false;
17837
+ }
17838
+ return true;
17839
+ }
17840
+ function calculateBackoff(attempt, config) {
17841
+ const baseDelayMs = config.baseDelayMs ?? DEFAULT_RETRY_CONFIG.baseDelayMs;
17842
+ const maxDelayMs = config.maxDelayMs ?? DEFAULT_RETRY_CONFIG.maxDelayMs;
17843
+ const jitter = config.jitter ?? DEFAULT_RETRY_CONFIG.jitter;
17844
+ const exponential = baseDelayMs * Math.pow(2, attempt - 1);
17845
+ const capped = Math.min(exponential, maxDelayMs);
17846
+ if (jitter === "full") {
17847
+ return Math.random() * capped;
17848
+ }
17849
+ return capped;
17850
+ }
17851
+ var CircuitBreaker = class {
17852
+ constructor(config) {
17853
+ this.state = "closed";
17854
+ this.failureCount = 0;
17855
+ this.successCount = 0;
17856
+ this.openUntil = 0;
17857
+ this.halfOpenRequests = 0;
17858
+ this.config = {
17859
+ failureThreshold: config?.failureThreshold ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.failureThreshold,
17860
+ resetTimeoutMs: config?.resetTimeoutMs ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.resetTimeoutMs,
17861
+ halfOpenMaxRequests: config?.halfOpenMaxRequests ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.halfOpenMaxRequests
17862
+ };
17863
+ }
17864
+ /**
17865
+ * Check if circuit is open (requests should be blocked)
17866
+ * Also handles automatic transition from open to half-open after timeout
17867
+ */
17868
+ isOpen() {
17869
+ if (this.state === "open" && Date.now() >= this.openUntil) {
17870
+ this.transitionTo("half-open");
17871
+ }
17872
+ return this.state === "open";
17873
+ }
17874
+ /**
17875
+ * Check if a request can be attempted
17876
+ * - closed: always yes
17877
+ * - open: always no
17878
+ * - half-open: limited number of probes
17879
+ */
17880
+ canAttempt() {
17881
+ if (this.state === "closed") return true;
17882
+ if (this.state === "open") return false;
17883
+ return this.halfOpenRequests < this.config.halfOpenMaxRequests;
17884
+ }
17885
+ /**
17886
+ * Increment half-open request counter (call before making request in half-open)
17887
+ */
17888
+ incrementHalfOpenRequests() {
17889
+ if (this.state === "half-open") {
17890
+ this.halfOpenRequests++;
17891
+ }
17892
+ }
17893
+ /**
17894
+ * Record a successful request
17895
+ */
17896
+ recordSuccess() {
17897
+ if (this.state === "half-open") {
17898
+ this.successCount++;
17899
+ if (this.successCount >= this.config.halfOpenMaxRequests) {
17900
+ this.transitionTo("closed");
17901
+ }
17902
+ } else {
17903
+ this.failureCount = 0;
17904
+ }
17905
+ }
17906
+ /**
17907
+ * Record a failed request
17908
+ * Only counts transient failures toward circuit breaker threshold
17909
+ */
17910
+ recordFailure(error) {
17911
+ if (!isTransientError(error)) return;
17912
+ this.failureCount++;
17913
+ if (this.state === "half-open") {
17914
+ this.transitionTo("open");
17915
+ } else if (this.failureCount >= this.config.failureThreshold) {
17916
+ this.transitionTo("open");
17917
+ }
17918
+ }
17919
+ /**
17920
+ * Get current circuit state
17921
+ */
17922
+ getState() {
17923
+ return {
17924
+ state: this.state,
17925
+ failureCount: this.failureCount,
17926
+ openUntil: this.state === "open" ? this.openUntil : void 0
17927
+ };
17928
+ }
17929
+ /**
17930
+ * Transition to a new state
17931
+ */
17932
+ transitionTo(newState) {
17933
+ this.state = newState;
17934
+ if (newState === "open") {
17935
+ this.openUntil = Date.now() + this.config.resetTimeoutMs;
17936
+ } else if (newState === "half-open") {
17937
+ this.halfOpenRequests = 0;
17938
+ this.successCount = 0;
17939
+ } else if (newState === "closed") {
17940
+ this.failureCount = 0;
17941
+ this.successCount = 0;
17942
+ this.halfOpenRequests = 0;
17943
+ }
17944
+ }
17945
+ };
17946
+ async function fetchWithTimeoutAndRetry(fetcher, config) {
17947
+ const maxAttempts = config.maxAttempts ?? DEFAULT_RETRY_CONFIG.maxAttempts;
17948
+ const requestTimeoutMs = config.requestTimeoutMs ?? 8e3;
17949
+ let lastError;
17950
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
17951
+ try {
17952
+ const controller = new AbortController();
17953
+ const timeoutId = setTimeout(() => controller.abort(), requestTimeoutMs);
17954
+ try {
17955
+ const result = await fetcher(controller.signal);
17956
+ return result;
17957
+ } finally {
17958
+ clearTimeout(timeoutId);
17959
+ }
17960
+ } catch (error) {
17961
+ lastError = error;
17962
+ const shouldRetry = shouldRetryError(error, config.retryOn);
17963
+ if (!shouldRetry) {
17964
+ throw error;
17965
+ }
17966
+ if (attempt < maxAttempts) {
17967
+ const delay = getRetryDelay(error, attempt, config);
17968
+ await sleep(delay);
17969
+ }
17970
+ }
17971
+ }
17972
+ throw lastError;
17973
+ }
17974
+ function shouldRetryError(error, customRetryOn) {
17975
+ if (customRetryOn) {
17976
+ const statusCode = error instanceof RiverbankApiError ? error.status : void 0;
17977
+ return customRetryOn(error, statusCode);
17978
+ }
17979
+ return isTransientError(error);
17980
+ }
17981
+ function getRetryDelay(error, attempt, config) {
17982
+ if (error instanceof RiverbankApiError && error.retryAfterMs) {
17983
+ return error.retryAfterMs;
17984
+ }
17985
+ return calculateBackoff(attempt, config);
17986
+ }
17987
+ function sleep(ms) {
17988
+ return new Promise((resolve) => setTimeout(resolve, ms));
17989
+ }
17990
+ var CircuitOpenError = class extends Error {
17991
+ constructor(state) {
17992
+ super("Circuit breaker is open");
17993
+ this.name = "CircuitOpenError";
17994
+ this.circuitState = state;
17995
+ }
17996
+ };
17997
+
17708
17998
  // src/client/index.ts
17709
17999
  setSdkVersion(SDK_VERSION);
18000
+ var DEFAULT_BROWSER_TIMEOUT_MS = 5e3;
18001
+ var DEFAULT_SERVER_TIMEOUT_MS = 8e3;
18002
+ function generateRequestId2() {
18003
+ return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
18004
+ }
18005
+ function isAbortError(error) {
18006
+ if (error instanceof DOMException && error.name === "AbortError") {
18007
+ return true;
18008
+ }
18009
+ if (error instanceof Error && error.name === "AbortError") {
18010
+ return true;
18011
+ }
18012
+ return false;
18013
+ }
18014
+ function getNetworkErrorCode(error) {
18015
+ const message = error.message.toLowerCase();
18016
+ if (message.includes("timeout") || message.includes("timed out")) {
18017
+ return "network:timeout";
18018
+ }
18019
+ if (message.includes("dns") || message.includes("getaddrinfo") || message.includes("enotfound")) {
18020
+ return "network:dns_error";
18021
+ }
18022
+ return "network:connection_error";
18023
+ }
17710
18024
  function convertToTypedError(error) {
18025
+ if (isAbortError(error)) {
18026
+ throw error;
18027
+ }
17711
18028
  if (error instanceof ApiEnvelopeError) {
17712
18029
  throw new RiverbankApiError({
17713
18030
  code: error.code,
@@ -17721,15 +18038,30 @@ function convertToTypedError(error) {
17721
18038
  if (error instanceof ApiRequestError && error.body && typeof error.body === "object") {
17722
18039
  const body = error.body;
17723
18040
  if (isApiError(body)) {
17724
- throw new RiverbankApiError(body.error);
18041
+ const envelopeError = body.error;
18042
+ throw new RiverbankApiError({
18043
+ ...envelopeError,
18044
+ retryAfterMs: error.retryAfterMs
18045
+ });
17725
18046
  }
17726
18047
  }
18048
+ if (error instanceof TypeError || error instanceof Error && !("status" in error)) {
18049
+ const networkError = error;
18050
+ throw new RiverbankApiError({
18051
+ code: getNetworkErrorCode(networkError),
18052
+ message: networkError.message || "Network request failed",
18053
+ requestId: `local-${Date.now()}`,
18054
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
18055
+ status: 0
18056
+ // No HTTP response received
18057
+ });
18058
+ }
17727
18059
  throw error;
17728
18060
  }
17729
18061
  function createRiverbankClient(config) {
17730
18062
  if (!config.baseUrl) {
17731
18063
  throw new Error(
17732
- "baseUrl is required when creating a Builder client. Expected format: https://dashboard.example.com/api (must include /api path)"
18064
+ "baseUrl is required when creating a Riverbank client. Expected format: https://dashboard.example.com/api (must include /api path)"
17733
18065
  );
17734
18066
  }
17735
18067
  if (!config.baseUrl.endsWith("/api")) {
@@ -17740,59 +18072,179 @@ function createRiverbankClient(config) {
17740
18072
  const cacheEnabled = config.cache?.enabled ?? true;
17741
18073
  const cacheTTL = (config.cache?.ttl ?? 300) * 1e3;
17742
18074
  const cacheMaxSize = config.cache?.maxSize ?? 100;
18075
+ const resilienceEnabled = config.resilience?.enabled ?? true;
18076
+ const staleIfError = config.resilience?.staleIfError ?? true;
18077
+ const staleTtlMs = (config.resilience?.staleTtlSec ?? 300) * 1e3;
18078
+ const requestTimeoutMs = config.resilience?.requestTimeoutMs ?? (typeof window !== "undefined" ? DEFAULT_BROWSER_TIMEOUT_MS : DEFAULT_SERVER_TIMEOUT_MS);
18079
+ const retryConfig = {
18080
+ maxAttempts: config.resilience?.retry?.maxAttempts ?? DEFAULT_RETRY_CONFIG.maxAttempts,
18081
+ baseDelayMs: config.resilience?.retry?.baseDelayMs ?? DEFAULT_RETRY_CONFIG.baseDelayMs,
18082
+ maxDelayMs: config.resilience?.retry?.maxDelayMs ?? DEFAULT_RETRY_CONFIG.maxDelayMs,
18083
+ jitter: config.resilience?.retry?.jitter ?? DEFAULT_RETRY_CONFIG.jitter,
18084
+ retryOn: config.resilience?.retry?.retryOn
18085
+ };
18086
+ const circuitBreakerConfig = {
18087
+ failureThreshold: config.resilience?.circuitBreaker?.failureThreshold ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.failureThreshold,
18088
+ resetTimeoutMs: config.resilience?.circuitBreaker?.resetTimeoutMs ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.resetTimeoutMs,
18089
+ halfOpenMaxRequests: config.resilience?.circuitBreaker?.halfOpenMaxRequests ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.halfOpenMaxRequests
18090
+ };
17743
18091
  const apiClient = createBearerAPIClient(config.apiKey, config.baseUrl);
17744
18092
  const cache = new SimpleCache({
17745
18093
  maxSize: cacheMaxSize,
17746
- ttl: cacheTTL
18094
+ ttl: cacheTTL,
18095
+ staleTtl: staleTtlMs
17747
18096
  });
17748
- async function cachedFetch(cacheKey, fetcher, options) {
17749
- if (cacheEnabled && !options?.force) {
17750
- const cached = cache.get(cacheKey);
17751
- if (cached !== void 0) {
17752
- return cached;
18097
+ const circuitBreaker = new CircuitBreaker(circuitBreakerConfig);
18098
+ let lastStatus = null;
18099
+ let isDegraded = false;
18100
+ function emitStatus(source, data, details) {
18101
+ const status = {
18102
+ source,
18103
+ isPreview: details.isPreview,
18104
+ cacheKey: details.cacheKey,
18105
+ error: details.error,
18106
+ staleAgeSec: details.staleAgeSec,
18107
+ circuit: circuitBreaker.getState(),
18108
+ requestId: details.requestId,
18109
+ durationMs: details.durationMs
18110
+ };
18111
+ lastStatus = status;
18112
+ config.resilience?.onStatusChange?.(status);
18113
+ const nowDegraded = source === "stale" || source === "error";
18114
+ if (nowDegraded !== isDegraded) {
18115
+ isDegraded = nowDegraded;
18116
+ config.resilience?.onDegradedMode?.(nowDegraded, status);
18117
+ }
18118
+ return data;
18119
+ }
18120
+ async function resilientFetch(cacheKey, fetcher, options) {
18121
+ const requestId = generateRequestId2();
18122
+ const startTime = Date.now();
18123
+ const isPreview = options.preview ?? false;
18124
+ const statusDetails = (extra = {}) => ({
18125
+ requestId,
18126
+ cacheKey,
18127
+ isPreview,
18128
+ durationMs: Date.now() - startTime,
18129
+ ...extra
18130
+ });
18131
+ if (cacheEnabled && !options.force) {
18132
+ const fresh = cache.getFresh(cacheKey);
18133
+ if (fresh !== null) {
18134
+ return emitStatus("cache", fresh, statusDetails());
18135
+ }
18136
+ }
18137
+ if (resilienceEnabled && circuitBreaker.isOpen()) {
18138
+ if (!isPreview && staleIfError) {
18139
+ const stale = cache.getStale(cacheKey);
18140
+ if (stale) {
18141
+ return emitStatus("stale", stale.value, statusDetails({
18142
+ staleAgeSec: stale.staleAgeSec,
18143
+ error: { code: "circuit_open", message: "Circuit breaker is open" }
18144
+ }));
18145
+ }
18146
+ }
18147
+ if (!options.force) {
18148
+ const circuitState = circuitBreaker.getState();
18149
+ emitStatus("error", null, statusDetails({
18150
+ error: { code: "circuit_open", message: "Circuit breaker is open" }
18151
+ }));
18152
+ throw new CircuitOpenError(circuitState);
17753
18153
  }
17754
18154
  }
17755
- let data;
17756
18155
  try {
17757
- const response = await fetcher();
17758
- data = unwrapResponse(response);
18156
+ let data;
18157
+ if (resilienceEnabled) {
18158
+ if (circuitBreaker.getState().state === "half-open") {
18159
+ circuitBreaker.incrementHalfOpenRequests();
18160
+ }
18161
+ data = await fetchWithTimeoutAndRetry(
18162
+ async (timeoutSignal) => {
18163
+ const combinedSignal = options.signal ? combineAbortSignals(timeoutSignal, options.signal) : timeoutSignal;
18164
+ try {
18165
+ const response = await fetcher(combinedSignal);
18166
+ return unwrapResponse(response);
18167
+ } catch (error) {
18168
+ convertToTypedError(error);
18169
+ }
18170
+ },
18171
+ {
18172
+ ...retryConfig,
18173
+ requestTimeoutMs
18174
+ }
18175
+ );
18176
+ circuitBreaker.recordSuccess();
18177
+ } else {
18178
+ try {
18179
+ const response = await fetcher(options.signal ?? new AbortController().signal);
18180
+ data = unwrapResponse(response);
18181
+ } catch (error) {
18182
+ convertToTypedError(error);
18183
+ }
18184
+ }
18185
+ if (cacheEnabled) {
18186
+ cache.set(cacheKey, data);
18187
+ }
18188
+ return emitStatus("live", data, statusDetails());
17759
18189
  } catch (error) {
17760
- convertToTypedError(error);
18190
+ if (resilienceEnabled && error instanceof Error) {
18191
+ circuitBreaker.recordFailure(error);
18192
+ }
18193
+ if (!isPreview && staleIfError && cacheEnabled) {
18194
+ const stale = cache.getStale(cacheKey);
18195
+ if (stale) {
18196
+ const errorInfo2 = error instanceof RiverbankApiError ? { code: error.code, message: error.message } : { message: error.message };
18197
+ return emitStatus("stale", stale.value, statusDetails({
18198
+ staleAgeSec: stale.staleAgeSec,
18199
+ error: errorInfo2
18200
+ }));
18201
+ }
18202
+ }
18203
+ const errorInfo = error instanceof RiverbankApiError ? { code: error.code, message: error.message } : { message: error.message };
18204
+ emitStatus("error", null, statusDetails({ error: errorInfo }));
18205
+ throw error;
17761
18206
  }
17762
- if (cacheEnabled) {
17763
- cache.set(cacheKey, data);
18207
+ }
18208
+ function combineAbortSignals(...signals) {
18209
+ const controller = new AbortController();
18210
+ for (const signal of signals) {
18211
+ if (signal.aborted) {
18212
+ controller.abort(signal.reason);
18213
+ break;
18214
+ }
18215
+ signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
17764
18216
  }
17765
- return data;
18217
+ return controller.signal;
17766
18218
  }
17767
18219
  return {
17768
18220
  async getSite(params) {
17769
- const { slug, domain, id } = params;
18221
+ const { slug, domain, id, signal } = params;
17770
18222
  if (!slug && !domain && !id) {
17771
18223
  throw new Error(
17772
18224
  `getSite() requires at least one identifier: slug, domain, or id. Received: ${JSON.stringify(params)}`
17773
18225
  );
17774
18226
  }
17775
18227
  const cacheKey = `site:${slug || domain || id}`;
17776
- return cachedFetch(cacheKey, async () => {
18228
+ return resilientFetch(cacheKey, async (sig) => {
17777
18229
  const apiParams = {};
17778
18230
  if (params.slug) apiParams.slug = params.slug;
17779
18231
  if (params.domain) apiParams.domain = params.domain;
17780
18232
  if (params.id) apiParams.id = params.id;
17781
- return await apiClient({ endpoint: "getSite", params: apiParams });
17782
- });
18233
+ return await apiClient({ endpoint: "getSite", params: apiParams, options: { signal: sig } });
18234
+ }, { signal });
17783
18235
  },
17784
18236
  async getPage(params) {
17785
- const { siteId, path, preview = false } = params;
18237
+ const { siteId, path, preview = false, signal } = params;
17786
18238
  const cacheKey = `page:${siteId}:${path}:${preview}`;
17787
- return cachedFetch(cacheKey, async () => {
17788
- return await apiClient({ endpoint: "getContentByPath", params: { siteId }, body: { path, preview } });
17789
- });
18239
+ return resilientFetch(cacheKey, async (sig) => {
18240
+ return await apiClient({ endpoint: "getContentByPath", params: { siteId }, body: { path, preview }, options: { signal: sig } });
18241
+ }, { preview, signal });
17790
18242
  },
17791
18243
  async getEntries(params) {
17792
- const { siteId, contentType, limit, offset, order, preview = false, mode, entryIds, includeMeta } = params;
18244
+ const { siteId, contentType, limit, offset, order, preview = false, mode, entryIds, includeMeta, signal } = params;
17793
18245
  const entryIdsCacheKey = mode === "manual" && entryIds?.length ? entryIds.join(",") : "";
17794
18246
  const cacheKey = `entries:${siteId}:${contentType}:${limit ?? ""}:${offset ?? ""}:${order ?? ""}:${preview}:${mode ?? ""}:${entryIdsCacheKey}:${includeMeta ?? ""}`;
17795
- return cachedFetch(cacheKey, async () => {
18247
+ return resilientFetch(cacheKey, async (sig) => {
17796
18248
  let orderParam;
17797
18249
  if (order === "newest") {
17798
18250
  orderParam = "published_at.desc";
@@ -17814,47 +18266,47 @@ function createRiverbankClient(config) {
17814
18266
  entryIds: JSON.stringify(entryIds)
17815
18267
  }
17816
18268
  };
17817
- return await apiClient({ endpoint: "listPublishedEntries", params: apiParams });
17818
- });
18269
+ return await apiClient({ endpoint: "listPublishedEntries", params: apiParams, options: { signal: sig } });
18270
+ }, { preview, signal });
17819
18271
  },
17820
18272
  async getEntry(params) {
17821
- const { siteId, contentType, slug } = params;
18273
+ const { siteId, contentType, slug, signal } = params;
17822
18274
  const cacheKey = `entry:${siteId}:${contentType}:${slug}`;
17823
- return cachedFetch(cacheKey, async () => {
17824
- return await apiClient({ endpoint: "getPublishedEntryPreview", params: { siteId, type: contentType, slug } });
17825
- });
18275
+ return resilientFetch(cacheKey, async (sig) => {
18276
+ return await apiClient({ endpoint: "getPublishedEntryPreview", params: { siteId, type: contentType, slug }, options: { signal: sig } });
18277
+ }, { signal });
17826
18278
  },
17827
18279
  async getPublicFormById(params) {
17828
- const { formId } = params;
18280
+ const { formId, signal } = params;
17829
18281
  if (!formId) {
17830
18282
  throw new Error("getPublicFormById() requires formId");
17831
18283
  }
17832
18284
  const cacheKey = `public-form:${formId}`;
17833
- return cachedFetch(cacheKey, async () => {
17834
- return await apiClient({ endpoint: "getPublicFormById", params: { formId } });
17835
- });
18285
+ return resilientFetch(cacheKey, async (sig) => {
18286
+ return await apiClient({ endpoint: "getPublicFormById", params: { formId }, options: { signal: sig } });
18287
+ }, { signal });
17836
18288
  },
17837
18289
  async getPublicBookingServices(params) {
17838
- const { siteId, ids } = params;
18290
+ const { siteId, ids, signal } = params;
17839
18291
  if (!siteId) {
17840
18292
  throw new Error("getPublicBookingServices() requires siteId");
17841
18293
  }
17842
18294
  const cacheKey = `public-booking-services:${siteId}:${ids ?? ""}`;
17843
- return cachedFetch(cacheKey, async () => {
18295
+ return resilientFetch(cacheKey, async (sig) => {
17844
18296
  const apiParams = {
17845
18297
  siteId,
17846
18298
  ...ids && { ids }
17847
18299
  };
17848
- return await apiClient({ endpoint: "getPublicBookingServices", params: apiParams });
17849
- });
18300
+ return await apiClient({ endpoint: "getPublicBookingServices", params: apiParams, options: { signal: sig } });
18301
+ }, { signal });
17850
18302
  },
17851
18303
  async listPublicEvents(params) {
17852
- const { siteId, limit, from, to, stage } = params;
18304
+ const { siteId, limit, from, to, stage, signal } = params;
17853
18305
  if (!siteId) {
17854
18306
  throw new Error("listPublicEvents() requires siteId");
17855
18307
  }
17856
18308
  const cacheKey = `public-events:${siteId}:${limit ?? ""}:${from ?? ""}:${to ?? ""}:${stage ?? ""}`;
17857
- return cachedFetch(cacheKey, async () => {
18309
+ return resilientFetch(cacheKey, async (sig) => {
17858
18310
  const apiParams = {
17859
18311
  siteId,
17860
18312
  ...typeof limit === "number" && { limit: String(limit) },
@@ -17862,40 +18314,46 @@ function createRiverbankClient(config) {
17862
18314
  ...to && { to },
17863
18315
  ...stage && { stage }
17864
18316
  };
17865
- return await apiClient({ endpoint: "listPublicEvents", params: apiParams });
17866
- });
18317
+ return await apiClient({ endpoint: "listPublicEvents", params: apiParams, options: { signal: sig } });
18318
+ }, { signal });
17867
18319
  },
17868
18320
  async resolveEventOccurrence(params) {
17869
- const { siteId, entryId, segment } = params;
18321
+ const { siteId, entryId, segment, signal } = params;
17870
18322
  if (!siteId || !entryId || !segment) {
17871
18323
  throw new Error("resolveEventOccurrence() requires siteId, entryId, and segment");
17872
18324
  }
17873
18325
  const cacheKey = `event-occurrence:${siteId}:${entryId}:${segment}`;
17874
- return cachedFetch(cacheKey, async () => {
18326
+ return resilientFetch(cacheKey, async (sig) => {
17875
18327
  return await apiClient({
17876
18328
  endpoint: "resolveEventOccurrence",
17877
- params: { siteId, entryId, segment }
18329
+ params: { siteId, entryId, segment },
18330
+ options: { signal: sig }
17878
18331
  });
17879
- });
18332
+ }, { signal });
17880
18333
  },
17881
18334
  async checkRedirect(params) {
17882
- const { siteId, path } = params;
18335
+ const { siteId, path, signal } = params;
17883
18336
  if (!siteId || !path) {
17884
18337
  throw new Error("checkRedirect() requires siteId and path");
17885
18338
  }
17886
18339
  const cacheKey = `redirect:${siteId}:${path}`;
17887
- return cachedFetch(cacheKey, async () => {
18340
+ return resilientFetch(cacheKey, async (sig) => {
17888
18341
  return await apiClient({
17889
18342
  endpoint: "checkRedirect",
17890
- params: { site: siteId, path }
18343
+ params: { site: siteId, path },
18344
+ options: { signal: sig }
17891
18345
  });
17892
- });
18346
+ }, { signal });
17893
18347
  },
17894
18348
  clearCache() {
17895
18349
  cache.clear();
18350
+ },
18351
+ getLastEmittedStatus() {
18352
+ return lastStatus;
18353
+ },
18354
+ getCircuitState() {
18355
+ return circuitBreaker.getState();
17896
18356
  }
17897
- // Cast to RiverbankClient to satisfy overloaded getEntries signature
17898
- // The implementation correctly returns the right type based on includeMeta
17899
18357
  };
17900
18358
  }
17901
18359
  export {