@riverbankcms/sdk 0.7.0 → 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 (151) 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-7FIJSGHU.mjs → chunk-5JT452F2.mjs} +537 -76
  28. package/dist/server/chunk-5JT452F2.mjs.map +1 -0
  29. package/dist/server/chunk-AET56TQX.mjs +45 -0
  30. package/dist/server/chunk-AET56TQX.mjs.map +1 -0
  31. package/dist/server/{chunk-P7UVAMK6.js → chunk-HMENX4Y7.js} +543 -82
  32. package/dist/server/chunk-HMENX4Y7.js.map +1 -0
  33. package/dist/server/chunk-LQUKXIW7.mjs +13 -0
  34. package/dist/server/chunk-LQUKXIW7.mjs.map +1 -0
  35. package/dist/server/chunk-VODFQMUW.js +45 -0
  36. package/dist/server/chunk-VODFQMUW.js.map +1 -0
  37. package/dist/server/chunk-WYNEYDXO.js +13 -0
  38. package/dist/server/chunk-WYNEYDXO.js.map +1 -0
  39. package/dist/server/{components-CICSJyp_.d.ts → components--LT61IKp.d.ts} +2 -2
  40. package/dist/server/{components-CMMwDXTW.d.mts → components-RPzRQve6.d.mts} +2 -2
  41. package/dist/server/components.d.mts +4 -4
  42. package/dist/server/components.d.ts +4 -4
  43. package/dist/server/components.js +0 -1
  44. package/dist/server/components.js.map +1 -1
  45. package/dist/server/components.mjs +0 -1
  46. package/dist/server/config-validation.js +0 -1
  47. package/dist/server/config-validation.js.map +1 -1
  48. package/dist/server/config-validation.mjs +0 -1
  49. package/dist/server/config.js +0 -1
  50. package/dist/server/config.js.map +1 -1
  51. package/dist/server/config.mjs +0 -1
  52. package/dist/server/config.mjs.map +1 -1
  53. package/dist/server/data.d.mts +1 -1
  54. package/dist/server/data.d.ts +1 -1
  55. package/dist/server/data.js +0 -1
  56. package/dist/server/data.js.map +1 -1
  57. package/dist/server/data.mjs +0 -1
  58. package/dist/server/env.d.mts +23 -0
  59. package/dist/server/env.d.ts +23 -0
  60. package/dist/server/env.js +7 -0
  61. package/dist/server/env.js.map +1 -0
  62. package/dist/server/env.mjs +7 -0
  63. package/dist/server/{index-DI_qlYx3.d.mts → index-BL66CU6d.d.mts} +2 -2
  64. package/dist/server/{index-Bucs6UqG.d.mts → index-Bkva0WAj.d.mts} +1 -1
  65. package/dist/server/{index-BTwWvSBu.d.ts → index-CJk9iQQW.d.ts} +2 -2
  66. package/dist/server/{index-Cp7tJuRt.d.ts → index-CSBWKA3r.d.ts} +1 -1
  67. package/dist/server/index.d.mts +3 -3
  68. package/dist/server/index.d.ts +3 -3
  69. package/dist/server/index.js +2 -3
  70. package/dist/server/index.js.map +1 -1
  71. package/dist/server/index.mjs +1 -2
  72. package/dist/server/index.mjs.map +1 -1
  73. package/dist/server/{loadContent-DmgpFcFC.d.ts → loadContent-CXUWMuzY.d.ts} +2 -2
  74. package/dist/server/{loadContent-C-YYUKQa.d.mts → loadContent-F_tAS0Nl.d.mts} +2 -2
  75. package/dist/server/{loadPage-IDGVDFBB.js → loadPage-6I7F6GRF.js} +1 -2
  76. package/dist/server/loadPage-6I7F6GRF.js.map +1 -0
  77. package/dist/server/{loadPage-B8mQUUSo.d.mts → loadPage-CxlYLe5K.d.mts} +1 -1
  78. package/dist/server/{loadPage-DNQTTRHL.mjs → loadPage-JI2SML4M.mjs} +1 -2
  79. package/dist/server/{loadPage-DP3nrHBi.d.ts → loadPage-i2r-X5b9.d.ts} +1 -1
  80. package/dist/server/metadata.d.mts +3 -3
  81. package/dist/server/metadata.d.ts +3 -3
  82. package/dist/server/metadata.js +0 -1
  83. package/dist/server/metadata.js.map +1 -1
  84. package/dist/server/metadata.mjs +0 -1
  85. package/dist/server/navigation.js +0 -1
  86. package/dist/server/navigation.js.map +1 -1
  87. package/dist/server/navigation.mjs +0 -1
  88. package/dist/server/next/revalidate.d.mts +66 -0
  89. package/dist/server/next/revalidate.d.ts +66 -0
  90. package/dist/server/next/revalidate.js +59 -0
  91. package/dist/server/next/revalidate.js.map +1 -0
  92. package/dist/server/next/revalidate.mjs +59 -0
  93. package/dist/server/next/revalidate.mjs.map +1 -0
  94. package/dist/server/next/tags.d.mts +78 -0
  95. package/dist/server/next/tags.d.ts +78 -0
  96. package/dist/server/next/tags.js +34 -0
  97. package/dist/server/next/tags.js.map +1 -0
  98. package/dist/server/next/tags.mjs +34 -0
  99. package/dist/server/next/tags.mjs.map +1 -0
  100. package/dist/server/next.d.mts +163 -5
  101. package/dist/server/next.d.ts +163 -5
  102. package/dist/server/next.js +78 -11
  103. package/dist/server/next.js.map +1 -1
  104. package/dist/server/next.mjs +75 -8
  105. package/dist/server/next.mjs.map +1 -1
  106. package/dist/server/rendering/server.d.mts +3 -3
  107. package/dist/server/rendering/server.d.ts +3 -3
  108. package/dist/server/rendering/server.js +0 -1
  109. package/dist/server/rendering/server.js.map +1 -1
  110. package/dist/server/rendering/server.mjs +0 -1
  111. package/dist/server/rendering.d.mts +6 -6
  112. package/dist/server/rendering.d.ts +6 -6
  113. package/dist/server/rendering.js +2 -3
  114. package/dist/server/rendering.js.map +1 -1
  115. package/dist/server/rendering.mjs +3 -4
  116. package/dist/server/routing.d.mts +2 -2
  117. package/dist/server/routing.d.ts +2 -2
  118. package/dist/server/routing.js +2 -4
  119. package/dist/server/routing.js.map +1 -1
  120. package/dist/server/routing.mjs +1 -3
  121. package/dist/server/routing.mjs.map +1 -1
  122. package/dist/server/server.d.mts +4 -4
  123. package/dist/server/server.d.ts +4 -4
  124. package/dist/server/server.js +4 -5
  125. package/dist/server/server.js.map +1 -1
  126. package/dist/server/server.mjs +4 -5
  127. package/dist/server/theme-bridge.js +0 -1
  128. package/dist/server/theme-bridge.js.map +1 -1
  129. package/dist/server/theme-bridge.mjs +0 -1
  130. package/dist/server/theme-bridge.mjs.map +1 -1
  131. package/dist/server/theme.js +1 -3
  132. package/dist/server/theme.js.map +1 -1
  133. package/dist/server/theme.mjs +0 -2
  134. package/dist/server/theme.mjs.map +1 -1
  135. package/dist/server/{types-BvcJU7zk.d.ts → types-DnkRh0UL.d.ts} +118 -9
  136. package/dist/server/{types-1cLz0vnq.d.mts → types-MF2AWoKv.d.mts} +118 -9
  137. package/dist/server/webhooks.d.mts +75 -0
  138. package/dist/server/webhooks.d.ts +75 -0
  139. package/dist/server/webhooks.js +11 -0
  140. package/dist/server/webhooks.js.map +1 -0
  141. package/dist/server/webhooks.mjs +11 -0
  142. package/dist/server/webhooks.mjs.map +1 -0
  143. package/package.json +23 -3
  144. package/dist/server/chunk-7FIJSGHU.mjs.map +0 -1
  145. package/dist/server/chunk-BJTO5JO5.mjs +0 -11
  146. package/dist/server/chunk-DGUM43GV.js +0 -11
  147. package/dist/server/chunk-DGUM43GV.js.map +0 -1
  148. package/dist/server/chunk-P7UVAMK6.js.map +0 -1
  149. package/dist/server/loadPage-IDGVDFBB.js.map +0 -1
  150. /package/dist/server/{chunk-BJTO5JO5.mjs.map → env.mjs.map} +0 -0
  151. /package/dist/server/{loadPage-DNQTTRHL.mjs.map → loadPage-JI2SML4M.mjs.map} +0 -0
@@ -17332,10 +17332,10 @@ function resolveApiBaseUrl() {
17332
17332
  var revalidateTag = null;
17333
17333
  if (typeof window === "undefined") {
17334
17334
  try {
17335
- const nextCache = require("next/cache");
17336
- revalidateTag = nextCache.revalidateTag;
17335
+ const dynamicRequire = new Function("modulePath", "return require(modulePath)");
17336
+ const nextCache = dynamicRequire("next/cache");
17337
+ revalidateTag = nextCache.revalidateTag ?? null;
17337
17338
  } catch {
17338
- revalidateTag = null;
17339
17339
  }
17340
17340
  }
17341
17341
  var sdkVersion;
@@ -17364,8 +17364,22 @@ var ApiRequestError = class extends Error {
17364
17364
  this.body = options.body;
17365
17365
  this.cause = options.cause;
17366
17366
  this.errorCode = options.errorCode;
17367
+ this.retryAfterMs = options.retryAfterMs;
17367
17368
  }
17368
17369
  };
17370
+ function parseRetryAfterHeader(headerValue) {
17371
+ if (!headerValue) return void 0;
17372
+ if (/^\d+$/.test(headerValue)) {
17373
+ const seconds = parseInt(headerValue, 10);
17374
+ return seconds * 1e3;
17375
+ }
17376
+ const date = new Date(headerValue);
17377
+ if (!isNaN(date.getTime())) {
17378
+ const delayMs = date.getTime() - Date.now();
17379
+ return delayMs > 0 ? delayMs : void 0;
17380
+ }
17381
+ return void 0;
17382
+ }
17369
17383
  function buildEndpointURL(baseURL, endpoint) {
17370
17384
  return baseURL + API_ENDPOINTS[endpoint].path;
17371
17385
  }
@@ -17582,6 +17596,7 @@ function createParsedClient(rawClient) {
17582
17596
  if (!response.ok) {
17583
17597
  const body = await parseErrorBody(response);
17584
17598
  const requestId = response.headers.get("x-request-id") ?? void 0;
17599
+ const retryAfterMs = parseRetryAfterHeader(response.headers.get("retry-after"));
17585
17600
  throw new ApiRequestError(
17586
17601
  `Request to ${String(endpoint)} failed with status ${response.status}`,
17587
17602
  {
@@ -17590,7 +17605,8 @@ function createParsedClient(rawClient) {
17590
17605
  method: config.method,
17591
17606
  auth,
17592
17607
  requestId,
17593
- body
17608
+ body,
17609
+ retryAfterMs
17594
17610
  }
17595
17611
  );
17596
17612
  }
@@ -17638,38 +17654,117 @@ var SimpleCache = class {
17638
17654
  this.cache = /* @__PURE__ */ new Map();
17639
17655
  this.maxSize = options.maxSize ?? 100;
17640
17656
  this.ttl = options.ttl ?? 3e5;
17657
+ this.staleTtl = options.staleTtl ?? 3e5;
17658
+ }
17659
+ /**
17660
+ * Get a fresh value (within TTL)
17661
+ * @returns The value if fresh, null otherwise
17662
+ */
17663
+ getFresh(key) {
17664
+ const entry = this.cache.get(key);
17665
+ if (!entry) return null;
17666
+ const now = Date.now();
17667
+ if (now <= entry.freshUntil) {
17668
+ return entry.value;
17669
+ }
17670
+ return null;
17641
17671
  }
17642
- get(key) {
17672
+ /**
17673
+ * Get a value that may be stale (past TTL but within staleTtl)
17674
+ * @returns Object with value and stale age, or null if expired
17675
+ */
17676
+ getStale(key) {
17643
17677
  const entry = this.cache.get(key);
17644
- if (!entry) return void 0;
17645
- if (Date.now() > entry.expires) {
17678
+ if (!entry) return null;
17679
+ const now = Date.now();
17680
+ if (now > entry.staleUntil) {
17646
17681
  this.cache.delete(key);
17647
- return void 0;
17682
+ return null;
17648
17683
  }
17649
- return entry.value;
17684
+ const staleAgeSec = now <= entry.freshUntil ? 0 : Math.floor((now - entry.freshUntil) / 1e3);
17685
+ return {
17686
+ value: entry.value,
17687
+ staleAgeSec
17688
+ };
17650
17689
  }
17651
- set(key, value) {
17652
- if (this.cache.size >= this.maxSize) {
17653
- const firstKey = this.cache.keys().next().value;
17654
- if (firstKey) {
17655
- this.cache.delete(firstKey);
17656
- }
17690
+ /**
17691
+ * Store a value with TTL and stale window
17692
+ */
17693
+ set(key, value, options) {
17694
+ const ttl = options?.ttl ?? this.ttl;
17695
+ const staleTtl = options?.staleTtl ?? this.staleTtl;
17696
+ const now = Date.now();
17697
+ if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
17698
+ this.evictOne(now);
17657
17699
  }
17658
17700
  this.cache.set(key, {
17659
17701
  value,
17660
- expires: Date.now() + this.ttl
17702
+ createdAt: now,
17703
+ freshUntil: now + ttl,
17704
+ staleUntil: now + ttl + staleTtl
17661
17705
  });
17662
17706
  }
17707
+ /**
17708
+ * Evict one entry to make room for a new one
17709
+ * Priority: oldest stale entry, then oldest fresh entry
17710
+ */
17711
+ evictOne(now) {
17712
+ let oldestStaleKey = null;
17713
+ let oldestStaleTime = Infinity;
17714
+ let oldestFreshKey = null;
17715
+ let oldestFreshTime = Infinity;
17716
+ for (const [key, entry] of this.cache) {
17717
+ if (now > entry.freshUntil) {
17718
+ if (entry.createdAt < oldestStaleTime) {
17719
+ oldestStaleTime = entry.createdAt;
17720
+ oldestStaleKey = key;
17721
+ }
17722
+ } else {
17723
+ if (entry.createdAt < oldestFreshTime) {
17724
+ oldestFreshTime = entry.createdAt;
17725
+ oldestFreshKey = key;
17726
+ }
17727
+ }
17728
+ }
17729
+ const keyToEvict = oldestStaleKey ?? oldestFreshKey;
17730
+ if (keyToEvict) {
17731
+ this.cache.delete(keyToEvict);
17732
+ }
17733
+ }
17734
+ /**
17735
+ * Remove all fully expired entries (past staleUntil)
17736
+ */
17737
+ prune() {
17738
+ const now = Date.now();
17739
+ for (const [key, entry] of this.cache) {
17740
+ if (now > entry.staleUntil) {
17741
+ this.cache.delete(key);
17742
+ }
17743
+ }
17744
+ }
17745
+ /**
17746
+ * Clear all entries
17747
+ */
17663
17748
  clear() {
17664
17749
  this.cache.clear();
17665
17750
  }
17751
+ /**
17752
+ * Check if a key exists and is not fully expired
17753
+ */
17666
17754
  has(key) {
17667
- return this.get(key) !== void 0;
17755
+ const entry = this.cache.get(key);
17756
+ if (!entry) return false;
17757
+ const now = Date.now();
17758
+ if (now > entry.staleUntil) {
17759
+ this.cache.delete(key);
17760
+ return false;
17761
+ }
17762
+ return now <= entry.freshUntil;
17668
17763
  }
17669
17764
  };
17670
17765
 
17671
17766
  // src/version.ts
17672
- var SDK_VERSION = "0.7.0";
17767
+ var SDK_VERSION = "0.7.2";
17673
17768
 
17674
17769
  // src/client/error.ts
17675
17770
  var RiverbankApiError = class _RiverbankApiError extends Error {
@@ -17681,8 +17776,31 @@ var RiverbankApiError = class _RiverbankApiError extends Error {
17681
17776
  this.status = apiError.status;
17682
17777
  this.fieldErrors = apiError.fieldErrors;
17683
17778
  this.timestamp = apiError.timestamp;
17779
+ this.retryAfterMs = "retryAfterMs" in apiError ? apiError.retryAfterMs : void 0;
17780
+ this.isRetryable = this.computeRetryable();
17684
17781
  Object.setPrototypeOf(this, _RiverbankApiError.prototype);
17685
17782
  }
17783
+ /**
17784
+ * Compute whether this error is retryable based on HTTP status code.
17785
+ * - 0 (network errors - no HTTP response): retryable
17786
+ * - 429 (rate limit): retryable
17787
+ * - 5xx (server errors): retryable
17788
+ * - 4xx (client errors, except 429): NOT retryable
17789
+ */
17790
+ computeRetryable() {
17791
+ if (this.status === 0) return true;
17792
+ if (this.status === 429) return true;
17793
+ if (this.status >= 500) return true;
17794
+ return false;
17795
+ }
17796
+ /**
17797
+ * Check if this is a network error (no HTTP response received)
17798
+ *
17799
+ * Matches: network:connection_error, network:timeout, network:dns_error
17800
+ */
17801
+ isNetworkError() {
17802
+ return this.code.startsWith("network:");
17803
+ }
17686
17804
  /**
17687
17805
  * Check if this error matches a specific error code
17688
17806
  *
@@ -17741,9 +17859,215 @@ var RiverbankApiError = class _RiverbankApiError extends Error {
17741
17859
  }
17742
17860
  };
17743
17861
 
17862
+ // src/client/resilience.ts
17863
+ var DEFAULT_RETRY_CONFIG = {
17864
+ maxAttempts: 3,
17865
+ baseDelayMs: 200,
17866
+ maxDelayMs: 2e3,
17867
+ jitter: "full"
17868
+ };
17869
+ var DEFAULT_CIRCUIT_BREAKER_CONFIG = {
17870
+ failureThreshold: 5,
17871
+ resetTimeoutMs: 3e4,
17872
+ halfOpenMaxRequests: 2
17873
+ };
17874
+ function isTransientError(error) {
17875
+ if (error instanceof RiverbankApiError) {
17876
+ if (error.status === 0) return true;
17877
+ if (error.status === 429) return true;
17878
+ if (error.status >= 500) return true;
17879
+ return false;
17880
+ }
17881
+ return true;
17882
+ }
17883
+ function calculateBackoff(attempt, config) {
17884
+ const baseDelayMs = config.baseDelayMs ?? DEFAULT_RETRY_CONFIG.baseDelayMs;
17885
+ const maxDelayMs = config.maxDelayMs ?? DEFAULT_RETRY_CONFIG.maxDelayMs;
17886
+ const jitter = config.jitter ?? DEFAULT_RETRY_CONFIG.jitter;
17887
+ const exponential = baseDelayMs * Math.pow(2, attempt - 1);
17888
+ const capped = Math.min(exponential, maxDelayMs);
17889
+ if (jitter === "full") {
17890
+ return Math.random() * capped;
17891
+ }
17892
+ return capped;
17893
+ }
17894
+ var CircuitBreaker = class {
17895
+ constructor(config) {
17896
+ this.state = "closed";
17897
+ this.failureCount = 0;
17898
+ this.successCount = 0;
17899
+ this.openUntil = 0;
17900
+ this.halfOpenRequests = 0;
17901
+ this.config = {
17902
+ failureThreshold: config?.failureThreshold ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.failureThreshold,
17903
+ resetTimeoutMs: config?.resetTimeoutMs ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.resetTimeoutMs,
17904
+ halfOpenMaxRequests: config?.halfOpenMaxRequests ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.halfOpenMaxRequests
17905
+ };
17906
+ }
17907
+ /**
17908
+ * Check if circuit is open (requests should be blocked)
17909
+ * Also handles automatic transition from open to half-open after timeout
17910
+ */
17911
+ isOpen() {
17912
+ if (this.state === "open" && Date.now() >= this.openUntil) {
17913
+ this.transitionTo("half-open");
17914
+ }
17915
+ return this.state === "open";
17916
+ }
17917
+ /**
17918
+ * Check if a request can be attempted
17919
+ * - closed: always yes
17920
+ * - open: always no
17921
+ * - half-open: limited number of probes
17922
+ */
17923
+ canAttempt() {
17924
+ if (this.state === "closed") return true;
17925
+ if (this.state === "open") return false;
17926
+ return this.halfOpenRequests < this.config.halfOpenMaxRequests;
17927
+ }
17928
+ /**
17929
+ * Increment half-open request counter (call before making request in half-open)
17930
+ */
17931
+ incrementHalfOpenRequests() {
17932
+ if (this.state === "half-open") {
17933
+ this.halfOpenRequests++;
17934
+ }
17935
+ }
17936
+ /**
17937
+ * Record a successful request
17938
+ */
17939
+ recordSuccess() {
17940
+ if (this.state === "half-open") {
17941
+ this.successCount++;
17942
+ if (this.successCount >= this.config.halfOpenMaxRequests) {
17943
+ this.transitionTo("closed");
17944
+ }
17945
+ } else {
17946
+ this.failureCount = 0;
17947
+ }
17948
+ }
17949
+ /**
17950
+ * Record a failed request
17951
+ * Only counts transient failures toward circuit breaker threshold
17952
+ */
17953
+ recordFailure(error) {
17954
+ if (!isTransientError(error)) return;
17955
+ this.failureCount++;
17956
+ if (this.state === "half-open") {
17957
+ this.transitionTo("open");
17958
+ } else if (this.failureCount >= this.config.failureThreshold) {
17959
+ this.transitionTo("open");
17960
+ }
17961
+ }
17962
+ /**
17963
+ * Get current circuit state
17964
+ */
17965
+ getState() {
17966
+ return {
17967
+ state: this.state,
17968
+ failureCount: this.failureCount,
17969
+ openUntil: this.state === "open" ? this.openUntil : void 0
17970
+ };
17971
+ }
17972
+ /**
17973
+ * Transition to a new state
17974
+ */
17975
+ transitionTo(newState) {
17976
+ this.state = newState;
17977
+ if (newState === "open") {
17978
+ this.openUntil = Date.now() + this.config.resetTimeoutMs;
17979
+ } else if (newState === "half-open") {
17980
+ this.halfOpenRequests = 0;
17981
+ this.successCount = 0;
17982
+ } else if (newState === "closed") {
17983
+ this.failureCount = 0;
17984
+ this.successCount = 0;
17985
+ this.halfOpenRequests = 0;
17986
+ }
17987
+ }
17988
+ };
17989
+ async function fetchWithTimeoutAndRetry(fetcher, config) {
17990
+ const maxAttempts = config.maxAttempts ?? DEFAULT_RETRY_CONFIG.maxAttempts;
17991
+ const requestTimeoutMs = config.requestTimeoutMs ?? 8e3;
17992
+ let lastError;
17993
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
17994
+ try {
17995
+ const controller = new AbortController();
17996
+ const timeoutId = setTimeout(() => controller.abort(), requestTimeoutMs);
17997
+ try {
17998
+ const result = await fetcher(controller.signal);
17999
+ return result;
18000
+ } finally {
18001
+ clearTimeout(timeoutId);
18002
+ }
18003
+ } catch (error) {
18004
+ lastError = error;
18005
+ const shouldRetry = shouldRetryError(error, config.retryOn);
18006
+ if (!shouldRetry) {
18007
+ throw error;
18008
+ }
18009
+ if (attempt < maxAttempts) {
18010
+ const delay = getRetryDelay(error, attempt, config);
18011
+ await sleep(delay);
18012
+ }
18013
+ }
18014
+ }
18015
+ throw lastError;
18016
+ }
18017
+ function shouldRetryError(error, customRetryOn) {
18018
+ if (customRetryOn) {
18019
+ const statusCode = error instanceof RiverbankApiError ? error.status : void 0;
18020
+ return customRetryOn(error, statusCode);
18021
+ }
18022
+ return isTransientError(error);
18023
+ }
18024
+ function getRetryDelay(error, attempt, config) {
18025
+ if (error instanceof RiverbankApiError && error.retryAfterMs) {
18026
+ return error.retryAfterMs;
18027
+ }
18028
+ return calculateBackoff(attempt, config);
18029
+ }
18030
+ function sleep(ms) {
18031
+ return new Promise((resolve) => setTimeout(resolve, ms));
18032
+ }
18033
+ var CircuitOpenError = class extends Error {
18034
+ constructor(state) {
18035
+ super("Circuit breaker is open");
18036
+ this.name = "CircuitOpenError";
18037
+ this.circuitState = state;
18038
+ }
18039
+ };
18040
+
17744
18041
  // src/client/index.ts
17745
18042
  setSdkVersion(SDK_VERSION);
18043
+ var DEFAULT_BROWSER_TIMEOUT_MS = 5e3;
18044
+ var DEFAULT_SERVER_TIMEOUT_MS = 8e3;
18045
+ function generateRequestId2() {
18046
+ return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
18047
+ }
18048
+ function isAbortError(error) {
18049
+ if (error instanceof DOMException && error.name === "AbortError") {
18050
+ return true;
18051
+ }
18052
+ if (error instanceof Error && error.name === "AbortError") {
18053
+ return true;
18054
+ }
18055
+ return false;
18056
+ }
18057
+ function getNetworkErrorCode(error) {
18058
+ const message = error.message.toLowerCase();
18059
+ if (message.includes("timeout") || message.includes("timed out")) {
18060
+ return "network:timeout";
18061
+ }
18062
+ if (message.includes("dns") || message.includes("getaddrinfo") || message.includes("enotfound")) {
18063
+ return "network:dns_error";
18064
+ }
18065
+ return "network:connection_error";
18066
+ }
17746
18067
  function convertToTypedError(error) {
18068
+ if (isAbortError(error)) {
18069
+ throw error;
18070
+ }
17747
18071
  if (error instanceof ApiEnvelopeError) {
17748
18072
  throw new RiverbankApiError({
17749
18073
  code: error.code,
@@ -17757,15 +18081,30 @@ function convertToTypedError(error) {
17757
18081
  if (error instanceof ApiRequestError && error.body && typeof error.body === "object") {
17758
18082
  const body = error.body;
17759
18083
  if (isApiError(body)) {
17760
- throw new RiverbankApiError(body.error);
18084
+ const envelopeError = body.error;
18085
+ throw new RiverbankApiError({
18086
+ ...envelopeError,
18087
+ retryAfterMs: error.retryAfterMs
18088
+ });
17761
18089
  }
17762
18090
  }
18091
+ if (error instanceof TypeError || error instanceof Error && !("status" in error)) {
18092
+ const networkError = error;
18093
+ throw new RiverbankApiError({
18094
+ code: getNetworkErrorCode(networkError),
18095
+ message: networkError.message || "Network request failed",
18096
+ requestId: `local-${Date.now()}`,
18097
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
18098
+ status: 0
18099
+ // No HTTP response received
18100
+ });
18101
+ }
17763
18102
  throw error;
17764
18103
  }
17765
18104
  function createRiverbankClient(config) {
17766
18105
  if (!config.baseUrl) {
17767
18106
  throw new Error(
17768
- "baseUrl is required when creating a Builder client. Expected format: https://dashboard.example.com/api (must include /api path)"
18107
+ "baseUrl is required when creating a Riverbank client. Expected format: https://dashboard.example.com/api (must include /api path)"
17769
18108
  );
17770
18109
  }
17771
18110
  if (!config.baseUrl.endsWith("/api")) {
@@ -17776,59 +18115,179 @@ function createRiverbankClient(config) {
17776
18115
  const cacheEnabled = config.cache?.enabled ?? true;
17777
18116
  const cacheTTL = (config.cache?.ttl ?? 300) * 1e3;
17778
18117
  const cacheMaxSize = config.cache?.maxSize ?? 100;
18118
+ const resilienceEnabled = config.resilience?.enabled ?? true;
18119
+ const staleIfError = config.resilience?.staleIfError ?? true;
18120
+ const staleTtlMs = (config.resilience?.staleTtlSec ?? 300) * 1e3;
18121
+ const requestTimeoutMs = config.resilience?.requestTimeoutMs ?? (typeof window !== "undefined" ? DEFAULT_BROWSER_TIMEOUT_MS : DEFAULT_SERVER_TIMEOUT_MS);
18122
+ const retryConfig = {
18123
+ maxAttempts: config.resilience?.retry?.maxAttempts ?? DEFAULT_RETRY_CONFIG.maxAttempts,
18124
+ baseDelayMs: config.resilience?.retry?.baseDelayMs ?? DEFAULT_RETRY_CONFIG.baseDelayMs,
18125
+ maxDelayMs: config.resilience?.retry?.maxDelayMs ?? DEFAULT_RETRY_CONFIG.maxDelayMs,
18126
+ jitter: config.resilience?.retry?.jitter ?? DEFAULT_RETRY_CONFIG.jitter,
18127
+ retryOn: config.resilience?.retry?.retryOn
18128
+ };
18129
+ const circuitBreakerConfig = {
18130
+ failureThreshold: config.resilience?.circuitBreaker?.failureThreshold ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.failureThreshold,
18131
+ resetTimeoutMs: config.resilience?.circuitBreaker?.resetTimeoutMs ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.resetTimeoutMs,
18132
+ halfOpenMaxRequests: config.resilience?.circuitBreaker?.halfOpenMaxRequests ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.halfOpenMaxRequests
18133
+ };
17779
18134
  const apiClient = createBearerAPIClient(config.apiKey, config.baseUrl);
17780
18135
  const cache = new SimpleCache({
17781
18136
  maxSize: cacheMaxSize,
17782
- ttl: cacheTTL
18137
+ ttl: cacheTTL,
18138
+ staleTtl: staleTtlMs
17783
18139
  });
17784
- async function cachedFetch(cacheKey, fetcher, options) {
17785
- if (cacheEnabled && !options?.force) {
17786
- const cached = cache.get(cacheKey);
17787
- if (cached !== void 0) {
17788
- return cached;
18140
+ const circuitBreaker = new CircuitBreaker(circuitBreakerConfig);
18141
+ let lastStatus = null;
18142
+ let isDegraded = false;
18143
+ function emitStatus(source, data, details) {
18144
+ const status = {
18145
+ source,
18146
+ isPreview: details.isPreview,
18147
+ cacheKey: details.cacheKey,
18148
+ error: details.error,
18149
+ staleAgeSec: details.staleAgeSec,
18150
+ circuit: circuitBreaker.getState(),
18151
+ requestId: details.requestId,
18152
+ durationMs: details.durationMs
18153
+ };
18154
+ lastStatus = status;
18155
+ config.resilience?.onStatusChange?.(status);
18156
+ const nowDegraded = source === "stale" || source === "error";
18157
+ if (nowDegraded !== isDegraded) {
18158
+ isDegraded = nowDegraded;
18159
+ config.resilience?.onDegradedMode?.(nowDegraded, status);
18160
+ }
18161
+ return data;
18162
+ }
18163
+ async function resilientFetch(cacheKey, fetcher, options) {
18164
+ const requestId = generateRequestId2();
18165
+ const startTime = Date.now();
18166
+ const isPreview = options.preview ?? false;
18167
+ const statusDetails = (extra = {}) => ({
18168
+ requestId,
18169
+ cacheKey,
18170
+ isPreview,
18171
+ durationMs: Date.now() - startTime,
18172
+ ...extra
18173
+ });
18174
+ if (cacheEnabled && !options.force) {
18175
+ const fresh = cache.getFresh(cacheKey);
18176
+ if (fresh !== null) {
18177
+ return emitStatus("cache", fresh, statusDetails());
18178
+ }
18179
+ }
18180
+ if (resilienceEnabled && circuitBreaker.isOpen()) {
18181
+ if (!isPreview && staleIfError) {
18182
+ const stale = cache.getStale(cacheKey);
18183
+ if (stale) {
18184
+ return emitStatus("stale", stale.value, statusDetails({
18185
+ staleAgeSec: stale.staleAgeSec,
18186
+ error: { code: "circuit_open", message: "Circuit breaker is open" }
18187
+ }));
18188
+ }
18189
+ }
18190
+ if (!options.force) {
18191
+ const circuitState = circuitBreaker.getState();
18192
+ emitStatus("error", null, statusDetails({
18193
+ error: { code: "circuit_open", message: "Circuit breaker is open" }
18194
+ }));
18195
+ throw new CircuitOpenError(circuitState);
17789
18196
  }
17790
18197
  }
17791
- let data;
17792
18198
  try {
17793
- const response = await fetcher();
17794
- data = unwrapResponse(response);
18199
+ let data;
18200
+ if (resilienceEnabled) {
18201
+ if (circuitBreaker.getState().state === "half-open") {
18202
+ circuitBreaker.incrementHalfOpenRequests();
18203
+ }
18204
+ data = await fetchWithTimeoutAndRetry(
18205
+ async (timeoutSignal) => {
18206
+ const combinedSignal = options.signal ? combineAbortSignals(timeoutSignal, options.signal) : timeoutSignal;
18207
+ try {
18208
+ const response = await fetcher(combinedSignal);
18209
+ return unwrapResponse(response);
18210
+ } catch (error) {
18211
+ convertToTypedError(error);
18212
+ }
18213
+ },
18214
+ {
18215
+ ...retryConfig,
18216
+ requestTimeoutMs
18217
+ }
18218
+ );
18219
+ circuitBreaker.recordSuccess();
18220
+ } else {
18221
+ try {
18222
+ const response = await fetcher(options.signal ?? new AbortController().signal);
18223
+ data = unwrapResponse(response);
18224
+ } catch (error) {
18225
+ convertToTypedError(error);
18226
+ }
18227
+ }
18228
+ if (cacheEnabled) {
18229
+ cache.set(cacheKey, data);
18230
+ }
18231
+ return emitStatus("live", data, statusDetails());
17795
18232
  } catch (error) {
17796
- convertToTypedError(error);
18233
+ if (resilienceEnabled && error instanceof Error) {
18234
+ circuitBreaker.recordFailure(error);
18235
+ }
18236
+ if (!isPreview && staleIfError && cacheEnabled) {
18237
+ const stale = cache.getStale(cacheKey);
18238
+ if (stale) {
18239
+ const errorInfo2 = error instanceof RiverbankApiError ? { code: error.code, message: error.message } : { message: error.message };
18240
+ return emitStatus("stale", stale.value, statusDetails({
18241
+ staleAgeSec: stale.staleAgeSec,
18242
+ error: errorInfo2
18243
+ }));
18244
+ }
18245
+ }
18246
+ const errorInfo = error instanceof RiverbankApiError ? { code: error.code, message: error.message } : { message: error.message };
18247
+ emitStatus("error", null, statusDetails({ error: errorInfo }));
18248
+ throw error;
17797
18249
  }
17798
- if (cacheEnabled) {
17799
- cache.set(cacheKey, data);
18250
+ }
18251
+ function combineAbortSignals(...signals) {
18252
+ const controller = new AbortController();
18253
+ for (const signal of signals) {
18254
+ if (signal.aborted) {
18255
+ controller.abort(signal.reason);
18256
+ break;
18257
+ }
18258
+ signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
17800
18259
  }
17801
- return data;
18260
+ return controller.signal;
17802
18261
  }
17803
18262
  return {
17804
18263
  async getSite(params) {
17805
- const { slug, domain, id } = params;
18264
+ const { slug, domain, id, signal } = params;
17806
18265
  if (!slug && !domain && !id) {
17807
18266
  throw new Error(
17808
18267
  `getSite() requires at least one identifier: slug, domain, or id. Received: ${JSON.stringify(params)}`
17809
18268
  );
17810
18269
  }
17811
18270
  const cacheKey = `site:${slug || domain || id}`;
17812
- return cachedFetch(cacheKey, async () => {
18271
+ return resilientFetch(cacheKey, async (sig) => {
17813
18272
  const apiParams = {};
17814
18273
  if (params.slug) apiParams.slug = params.slug;
17815
18274
  if (params.domain) apiParams.domain = params.domain;
17816
18275
  if (params.id) apiParams.id = params.id;
17817
- return await apiClient({ endpoint: "getSite", params: apiParams });
17818
- });
18276
+ return await apiClient({ endpoint: "getSite", params: apiParams, options: { signal: sig } });
18277
+ }, { signal });
17819
18278
  },
17820
18279
  async getPage(params) {
17821
- const { siteId, path, preview = false } = params;
18280
+ const { siteId, path, preview = false, signal } = params;
17822
18281
  const cacheKey = `page:${siteId}:${path}:${preview}`;
17823
- return cachedFetch(cacheKey, async () => {
17824
- return await apiClient({ endpoint: "getContentByPath", params: { siteId }, body: { path, preview } });
17825
- });
18282
+ return resilientFetch(cacheKey, async (sig) => {
18283
+ return await apiClient({ endpoint: "getContentByPath", params: { siteId }, body: { path, preview }, options: { signal: sig } });
18284
+ }, { preview, signal });
17826
18285
  },
17827
18286
  async getEntries(params) {
17828
- const { siteId, contentType, limit, offset, order, preview = false, mode, entryIds, includeMeta } = params;
18287
+ const { siteId, contentType, limit, offset, order, preview = false, mode, entryIds, includeMeta, signal } = params;
17829
18288
  const entryIdsCacheKey = mode === "manual" && entryIds?.length ? entryIds.join(",") : "";
17830
18289
  const cacheKey = `entries:${siteId}:${contentType}:${limit ?? ""}:${offset ?? ""}:${order ?? ""}:${preview}:${mode ?? ""}:${entryIdsCacheKey}:${includeMeta ?? ""}`;
17831
- return cachedFetch(cacheKey, async () => {
18290
+ return resilientFetch(cacheKey, async (sig) => {
17832
18291
  let orderParam;
17833
18292
  if (order === "newest") {
17834
18293
  orderParam = "published_at.desc";
@@ -17850,47 +18309,47 @@ function createRiverbankClient(config) {
17850
18309
  entryIds: JSON.stringify(entryIds)
17851
18310
  }
17852
18311
  };
17853
- return await apiClient({ endpoint: "listPublishedEntries", params: apiParams });
17854
- });
18312
+ return await apiClient({ endpoint: "listPublishedEntries", params: apiParams, options: { signal: sig } });
18313
+ }, { preview, signal });
17855
18314
  },
17856
18315
  async getEntry(params) {
17857
- const { siteId, contentType, slug } = params;
18316
+ const { siteId, contentType, slug, signal } = params;
17858
18317
  const cacheKey = `entry:${siteId}:${contentType}:${slug}`;
17859
- return cachedFetch(cacheKey, async () => {
17860
- return await apiClient({ endpoint: "getPublishedEntryPreview", params: { siteId, type: contentType, slug } });
17861
- });
18318
+ return resilientFetch(cacheKey, async (sig) => {
18319
+ return await apiClient({ endpoint: "getPublishedEntryPreview", params: { siteId, type: contentType, slug }, options: { signal: sig } });
18320
+ }, { signal });
17862
18321
  },
17863
18322
  async getPublicFormById(params) {
17864
- const { formId } = params;
18323
+ const { formId, signal } = params;
17865
18324
  if (!formId) {
17866
18325
  throw new Error("getPublicFormById() requires formId");
17867
18326
  }
17868
18327
  const cacheKey = `public-form:${formId}`;
17869
- return cachedFetch(cacheKey, async () => {
17870
- return await apiClient({ endpoint: "getPublicFormById", params: { formId } });
17871
- });
18328
+ return resilientFetch(cacheKey, async (sig) => {
18329
+ return await apiClient({ endpoint: "getPublicFormById", params: { formId }, options: { signal: sig } });
18330
+ }, { signal });
17872
18331
  },
17873
18332
  async getPublicBookingServices(params) {
17874
- const { siteId, ids } = params;
18333
+ const { siteId, ids, signal } = params;
17875
18334
  if (!siteId) {
17876
18335
  throw new Error("getPublicBookingServices() requires siteId");
17877
18336
  }
17878
18337
  const cacheKey = `public-booking-services:${siteId}:${ids ?? ""}`;
17879
- return cachedFetch(cacheKey, async () => {
18338
+ return resilientFetch(cacheKey, async (sig) => {
17880
18339
  const apiParams = {
17881
18340
  siteId,
17882
18341
  ...ids && { ids }
17883
18342
  };
17884
- return await apiClient({ endpoint: "getPublicBookingServices", params: apiParams });
17885
- });
18343
+ return await apiClient({ endpoint: "getPublicBookingServices", params: apiParams, options: { signal: sig } });
18344
+ }, { signal });
17886
18345
  },
17887
18346
  async listPublicEvents(params) {
17888
- const { siteId, limit, from, to, stage } = params;
18347
+ const { siteId, limit, from, to, stage, signal } = params;
17889
18348
  if (!siteId) {
17890
18349
  throw new Error("listPublicEvents() requires siteId");
17891
18350
  }
17892
18351
  const cacheKey = `public-events:${siteId}:${limit ?? ""}:${from ?? ""}:${to ?? ""}:${stage ?? ""}`;
17893
- return cachedFetch(cacheKey, async () => {
18352
+ return resilientFetch(cacheKey, async (sig) => {
17894
18353
  const apiParams = {
17895
18354
  siteId,
17896
18355
  ...typeof limit === "number" && { limit: String(limit) },
@@ -17898,40 +18357,46 @@ function createRiverbankClient(config) {
17898
18357
  ...to && { to },
17899
18358
  ...stage && { stage }
17900
18359
  };
17901
- return await apiClient({ endpoint: "listPublicEvents", params: apiParams });
17902
- });
18360
+ return await apiClient({ endpoint: "listPublicEvents", params: apiParams, options: { signal: sig } });
18361
+ }, { signal });
17903
18362
  },
17904
18363
  async resolveEventOccurrence(params) {
17905
- const { siteId, entryId, segment } = params;
18364
+ const { siteId, entryId, segment, signal } = params;
17906
18365
  if (!siteId || !entryId || !segment) {
17907
18366
  throw new Error("resolveEventOccurrence() requires siteId, entryId, and segment");
17908
18367
  }
17909
18368
  const cacheKey = `event-occurrence:${siteId}:${entryId}:${segment}`;
17910
- return cachedFetch(cacheKey, async () => {
18369
+ return resilientFetch(cacheKey, async (sig) => {
17911
18370
  return await apiClient({
17912
18371
  endpoint: "resolveEventOccurrence",
17913
- params: { siteId, entryId, segment }
18372
+ params: { siteId, entryId, segment },
18373
+ options: { signal: sig }
17914
18374
  });
17915
- });
18375
+ }, { signal });
17916
18376
  },
17917
18377
  async checkRedirect(params) {
17918
- const { siteId, path } = params;
18378
+ const { siteId, path, signal } = params;
17919
18379
  if (!siteId || !path) {
17920
18380
  throw new Error("checkRedirect() requires siteId and path");
17921
18381
  }
17922
18382
  const cacheKey = `redirect:${siteId}:${path}`;
17923
- return cachedFetch(cacheKey, async () => {
18383
+ return resilientFetch(cacheKey, async (sig) => {
17924
18384
  return await apiClient({
17925
18385
  endpoint: "checkRedirect",
17926
- params: { site: siteId, path }
18386
+ params: { site: siteId, path },
18387
+ options: { signal: sig }
17927
18388
  });
17928
- });
18389
+ }, { signal });
17929
18390
  },
17930
18391
  clearCache() {
17931
18392
  cache.clear();
18393
+ },
18394
+ getLastEmittedStatus() {
18395
+ return lastStatus;
18396
+ },
18397
+ getCircuitState() {
18398
+ return circuitBreaker.getState();
17932
18399
  }
17933
- // Cast to RiverbankClient to satisfy overloaded getEntries signature
17934
- // The implementation correctly returns the right type based on includeMeta
17935
18400
  };
17936
18401
  }
17937
18402
  // Annotate the CommonJS export names for ESM import in node: