@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
@@ -1,7 +1,3 @@
1
- import {
2
- __require
3
- } from "./chunk-BJTO5JO5.mjs";
4
-
5
1
  // ../api/src/endpoints.ts
6
2
  var ENDPOINT_DEFINITIONS = {
7
3
  // AI endpoints - no cache due to dynamic nature
@@ -1659,10 +1655,10 @@ function resolveApiBaseUrl() {
1659
1655
  var revalidateTag = null;
1660
1656
  if (typeof window === "undefined") {
1661
1657
  try {
1662
- const nextCache = __require("next/cache");
1663
- revalidateTag = nextCache.revalidateTag;
1658
+ const dynamicRequire = new Function("modulePath", "return require(modulePath)");
1659
+ const nextCache = dynamicRequire("next/cache");
1660
+ revalidateTag = nextCache.revalidateTag ?? null;
1664
1661
  } catch {
1665
- revalidateTag = null;
1666
1662
  }
1667
1663
  }
1668
1664
  var sdkVersion;
@@ -1691,8 +1687,22 @@ var ApiRequestError = class extends Error {
1691
1687
  this.body = options.body;
1692
1688
  this.cause = options.cause;
1693
1689
  this.errorCode = options.errorCode;
1690
+ this.retryAfterMs = options.retryAfterMs;
1694
1691
  }
1695
1692
  };
1693
+ function parseRetryAfterHeader(headerValue) {
1694
+ if (!headerValue) return void 0;
1695
+ if (/^\d+$/.test(headerValue)) {
1696
+ const seconds = parseInt(headerValue, 10);
1697
+ return seconds * 1e3;
1698
+ }
1699
+ const date = new Date(headerValue);
1700
+ if (!isNaN(date.getTime())) {
1701
+ const delayMs = date.getTime() - Date.now();
1702
+ return delayMs > 0 ? delayMs : void 0;
1703
+ }
1704
+ return void 0;
1705
+ }
1696
1706
  function buildEndpointURL2(baseURL, endpoint) {
1697
1707
  return baseURL + API_ENDPOINTS[endpoint].path;
1698
1708
  }
@@ -1909,6 +1919,7 @@ function createParsedClient(rawClient) {
1909
1919
  if (!response.ok) {
1910
1920
  const body = await parseErrorBody(response);
1911
1921
  const requestId = response.headers.get("x-request-id") ?? void 0;
1922
+ const retryAfterMs = parseRetryAfterHeader(response.headers.get("retry-after"));
1912
1923
  throw new ApiRequestError(
1913
1924
  `Request to ${String(endpoint)} failed with status ${response.status}`,
1914
1925
  {
@@ -1917,7 +1928,8 @@ function createParsedClient(rawClient) {
1917
1928
  method: config.method,
1918
1929
  auth,
1919
1930
  requestId,
1920
- body
1931
+ body,
1932
+ retryAfterMs
1921
1933
  }
1922
1934
  );
1923
1935
  }
@@ -1965,38 +1977,117 @@ var SimpleCache = class {
1965
1977
  this.cache = /* @__PURE__ */ new Map();
1966
1978
  this.maxSize = options.maxSize ?? 100;
1967
1979
  this.ttl = options.ttl ?? 3e5;
1980
+ this.staleTtl = options.staleTtl ?? 3e5;
1968
1981
  }
1969
- get(key) {
1982
+ /**
1983
+ * Get a fresh value (within TTL)
1984
+ * @returns The value if fresh, null otherwise
1985
+ */
1986
+ getFresh(key) {
1970
1987
  const entry = this.cache.get(key);
1971
- if (!entry) return void 0;
1972
- if (Date.now() > entry.expires) {
1988
+ if (!entry) return null;
1989
+ const now = Date.now();
1990
+ if (now <= entry.freshUntil) {
1991
+ return entry.value;
1992
+ }
1993
+ return null;
1994
+ }
1995
+ /**
1996
+ * Get a value that may be stale (past TTL but within staleTtl)
1997
+ * @returns Object with value and stale age, or null if expired
1998
+ */
1999
+ getStale(key) {
2000
+ const entry = this.cache.get(key);
2001
+ if (!entry) return null;
2002
+ const now = Date.now();
2003
+ if (now > entry.staleUntil) {
1973
2004
  this.cache.delete(key);
1974
- return void 0;
2005
+ return null;
1975
2006
  }
1976
- return entry.value;
2007
+ const staleAgeSec = now <= entry.freshUntil ? 0 : Math.floor((now - entry.freshUntil) / 1e3);
2008
+ return {
2009
+ value: entry.value,
2010
+ staleAgeSec
2011
+ };
1977
2012
  }
1978
- set(key, value) {
1979
- if (this.cache.size >= this.maxSize) {
1980
- const firstKey = this.cache.keys().next().value;
1981
- if (firstKey) {
1982
- this.cache.delete(firstKey);
1983
- }
2013
+ /**
2014
+ * Store a value with TTL and stale window
2015
+ */
2016
+ set(key, value, options) {
2017
+ const ttl = options?.ttl ?? this.ttl;
2018
+ const staleTtl = options?.staleTtl ?? this.staleTtl;
2019
+ const now = Date.now();
2020
+ if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
2021
+ this.evictOne(now);
1984
2022
  }
1985
2023
  this.cache.set(key, {
1986
2024
  value,
1987
- expires: Date.now() + this.ttl
2025
+ createdAt: now,
2026
+ freshUntil: now + ttl,
2027
+ staleUntil: now + ttl + staleTtl
1988
2028
  });
1989
2029
  }
2030
+ /**
2031
+ * Evict one entry to make room for a new one
2032
+ * Priority: oldest stale entry, then oldest fresh entry
2033
+ */
2034
+ evictOne(now) {
2035
+ let oldestStaleKey = null;
2036
+ let oldestStaleTime = Infinity;
2037
+ let oldestFreshKey = null;
2038
+ let oldestFreshTime = Infinity;
2039
+ for (const [key, entry] of this.cache) {
2040
+ if (now > entry.freshUntil) {
2041
+ if (entry.createdAt < oldestStaleTime) {
2042
+ oldestStaleTime = entry.createdAt;
2043
+ oldestStaleKey = key;
2044
+ }
2045
+ } else {
2046
+ if (entry.createdAt < oldestFreshTime) {
2047
+ oldestFreshTime = entry.createdAt;
2048
+ oldestFreshKey = key;
2049
+ }
2050
+ }
2051
+ }
2052
+ const keyToEvict = oldestStaleKey ?? oldestFreshKey;
2053
+ if (keyToEvict) {
2054
+ this.cache.delete(keyToEvict);
2055
+ }
2056
+ }
2057
+ /**
2058
+ * Remove all fully expired entries (past staleUntil)
2059
+ */
2060
+ prune() {
2061
+ const now = Date.now();
2062
+ for (const [key, entry] of this.cache) {
2063
+ if (now > entry.staleUntil) {
2064
+ this.cache.delete(key);
2065
+ }
2066
+ }
2067
+ }
2068
+ /**
2069
+ * Clear all entries
2070
+ */
1990
2071
  clear() {
1991
2072
  this.cache.clear();
1992
2073
  }
2074
+ /**
2075
+ * Check if a key exists and is not fully expired
2076
+ */
1993
2077
  has(key) {
1994
- return this.get(key) !== void 0;
2078
+ const entry = this.cache.get(key);
2079
+ if (!entry) return false;
2080
+ const now = Date.now();
2081
+ if (now > entry.staleUntil) {
2082
+ this.cache.delete(key);
2083
+ return false;
2084
+ }
2085
+ return now <= entry.freshUntil;
1995
2086
  }
1996
2087
  };
1997
2088
 
1998
2089
  // src/version.ts
1999
- var SDK_VERSION = "0.7.0";
2090
+ var SDK_VERSION = "0.7.2";
2000
2091
 
2001
2092
  // src/client/error.ts
2002
2093
  var RiverbankApiError = class _RiverbankApiError extends Error {
@@ -2008,8 +2099,31 @@ var RiverbankApiError = class _RiverbankApiError extends Error {
2008
2099
  this.status = apiError.status;
2009
2100
  this.fieldErrors = apiError.fieldErrors;
2010
2101
  this.timestamp = apiError.timestamp;
2102
+ this.retryAfterMs = "retryAfterMs" in apiError ? apiError.retryAfterMs : void 0;
2103
+ this.isRetryable = this.computeRetryable();
2011
2104
  Object.setPrototypeOf(this, _RiverbankApiError.prototype);
2012
2105
  }
2106
+ /**
2107
+ * Compute whether this error is retryable based on HTTP status code.
2108
+ * - 0 (network errors - no HTTP response): retryable
2109
+ * - 429 (rate limit): retryable
2110
+ * - 5xx (server errors): retryable
2111
+ * - 4xx (client errors, except 429): NOT retryable
2112
+ */
2113
+ computeRetryable() {
2114
+ if (this.status === 0) return true;
2115
+ if (this.status === 429) return true;
2116
+ if (this.status >= 500) return true;
2117
+ return false;
2118
+ }
2119
+ /**
2120
+ * Check if this is a network error (no HTTP response received)
2121
+ *
2122
+ * Matches: network:connection_error, network:timeout, network:dns_error
2123
+ */
2124
+ isNetworkError() {
2125
+ return this.code.startsWith("network:");
2126
+ }
2013
2127
  /**
2014
2128
  * Check if this error matches a specific error code
2015
2129
  *
@@ -2068,9 +2182,215 @@ var RiverbankApiError = class _RiverbankApiError extends Error {
2068
2182
  }
2069
2183
  };
2070
2184
 
2185
+ // src/client/resilience.ts
2186
+ var DEFAULT_RETRY_CONFIG = {
2187
+ maxAttempts: 3,
2188
+ baseDelayMs: 200,
2189
+ maxDelayMs: 2e3,
2190
+ jitter: "full"
2191
+ };
2192
+ var DEFAULT_CIRCUIT_BREAKER_CONFIG = {
2193
+ failureThreshold: 5,
2194
+ resetTimeoutMs: 3e4,
2195
+ halfOpenMaxRequests: 2
2196
+ };
2197
+ function isTransientError(error) {
2198
+ if (error instanceof RiverbankApiError) {
2199
+ if (error.status === 0) return true;
2200
+ if (error.status === 429) return true;
2201
+ if (error.status >= 500) return true;
2202
+ return false;
2203
+ }
2204
+ return true;
2205
+ }
2206
+ function calculateBackoff(attempt, config) {
2207
+ const baseDelayMs = config.baseDelayMs ?? DEFAULT_RETRY_CONFIG.baseDelayMs;
2208
+ const maxDelayMs = config.maxDelayMs ?? DEFAULT_RETRY_CONFIG.maxDelayMs;
2209
+ const jitter = config.jitter ?? DEFAULT_RETRY_CONFIG.jitter;
2210
+ const exponential = baseDelayMs * Math.pow(2, attempt - 1);
2211
+ const capped = Math.min(exponential, maxDelayMs);
2212
+ if (jitter === "full") {
2213
+ return Math.random() * capped;
2214
+ }
2215
+ return capped;
2216
+ }
2217
+ var CircuitBreaker = class {
2218
+ constructor(config) {
2219
+ this.state = "closed";
2220
+ this.failureCount = 0;
2221
+ this.successCount = 0;
2222
+ this.openUntil = 0;
2223
+ this.halfOpenRequests = 0;
2224
+ this.config = {
2225
+ failureThreshold: config?.failureThreshold ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.failureThreshold,
2226
+ resetTimeoutMs: config?.resetTimeoutMs ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.resetTimeoutMs,
2227
+ halfOpenMaxRequests: config?.halfOpenMaxRequests ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.halfOpenMaxRequests
2228
+ };
2229
+ }
2230
+ /**
2231
+ * Check if circuit is open (requests should be blocked)
2232
+ * Also handles automatic transition from open to half-open after timeout
2233
+ */
2234
+ isOpen() {
2235
+ if (this.state === "open" && Date.now() >= this.openUntil) {
2236
+ this.transitionTo("half-open");
2237
+ }
2238
+ return this.state === "open";
2239
+ }
2240
+ /**
2241
+ * Check if a request can be attempted
2242
+ * - closed: always yes
2243
+ * - open: always no
2244
+ * - half-open: limited number of probes
2245
+ */
2246
+ canAttempt() {
2247
+ if (this.state === "closed") return true;
2248
+ if (this.state === "open") return false;
2249
+ return this.halfOpenRequests < this.config.halfOpenMaxRequests;
2250
+ }
2251
+ /**
2252
+ * Increment half-open request counter (call before making request in half-open)
2253
+ */
2254
+ incrementHalfOpenRequests() {
2255
+ if (this.state === "half-open") {
2256
+ this.halfOpenRequests++;
2257
+ }
2258
+ }
2259
+ /**
2260
+ * Record a successful request
2261
+ */
2262
+ recordSuccess() {
2263
+ if (this.state === "half-open") {
2264
+ this.successCount++;
2265
+ if (this.successCount >= this.config.halfOpenMaxRequests) {
2266
+ this.transitionTo("closed");
2267
+ }
2268
+ } else {
2269
+ this.failureCount = 0;
2270
+ }
2271
+ }
2272
+ /**
2273
+ * Record a failed request
2274
+ * Only counts transient failures toward circuit breaker threshold
2275
+ */
2276
+ recordFailure(error) {
2277
+ if (!isTransientError(error)) return;
2278
+ this.failureCount++;
2279
+ if (this.state === "half-open") {
2280
+ this.transitionTo("open");
2281
+ } else if (this.failureCount >= this.config.failureThreshold) {
2282
+ this.transitionTo("open");
2283
+ }
2284
+ }
2285
+ /**
2286
+ * Get current circuit state
2287
+ */
2288
+ getState() {
2289
+ return {
2290
+ state: this.state,
2291
+ failureCount: this.failureCount,
2292
+ openUntil: this.state === "open" ? this.openUntil : void 0
2293
+ };
2294
+ }
2295
+ /**
2296
+ * Transition to a new state
2297
+ */
2298
+ transitionTo(newState) {
2299
+ this.state = newState;
2300
+ if (newState === "open") {
2301
+ this.openUntil = Date.now() + this.config.resetTimeoutMs;
2302
+ } else if (newState === "half-open") {
2303
+ this.halfOpenRequests = 0;
2304
+ this.successCount = 0;
2305
+ } else if (newState === "closed") {
2306
+ this.failureCount = 0;
2307
+ this.successCount = 0;
2308
+ this.halfOpenRequests = 0;
2309
+ }
2310
+ }
2311
+ };
2312
+ async function fetchWithTimeoutAndRetry(fetcher, config) {
2313
+ const maxAttempts = config.maxAttempts ?? DEFAULT_RETRY_CONFIG.maxAttempts;
2314
+ const requestTimeoutMs = config.requestTimeoutMs ?? 8e3;
2315
+ let lastError;
2316
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
2317
+ try {
2318
+ const controller = new AbortController();
2319
+ const timeoutId = setTimeout(() => controller.abort(), requestTimeoutMs);
2320
+ try {
2321
+ const result = await fetcher(controller.signal);
2322
+ return result;
2323
+ } finally {
2324
+ clearTimeout(timeoutId);
2325
+ }
2326
+ } catch (error) {
2327
+ lastError = error;
2328
+ const shouldRetry = shouldRetryError(error, config.retryOn);
2329
+ if (!shouldRetry) {
2330
+ throw error;
2331
+ }
2332
+ if (attempt < maxAttempts) {
2333
+ const delay = getRetryDelay(error, attempt, config);
2334
+ await sleep(delay);
2335
+ }
2336
+ }
2337
+ }
2338
+ throw lastError;
2339
+ }
2340
+ function shouldRetryError(error, customRetryOn) {
2341
+ if (customRetryOn) {
2342
+ const statusCode = error instanceof RiverbankApiError ? error.status : void 0;
2343
+ return customRetryOn(error, statusCode);
2344
+ }
2345
+ return isTransientError(error);
2346
+ }
2347
+ function getRetryDelay(error, attempt, config) {
2348
+ if (error instanceof RiverbankApiError && error.retryAfterMs) {
2349
+ return error.retryAfterMs;
2350
+ }
2351
+ return calculateBackoff(attempt, config);
2352
+ }
2353
+ function sleep(ms) {
2354
+ return new Promise((resolve) => setTimeout(resolve, ms));
2355
+ }
2356
+ var CircuitOpenError = class extends Error {
2357
+ constructor(state) {
2358
+ super("Circuit breaker is open");
2359
+ this.name = "CircuitOpenError";
2360
+ this.circuitState = state;
2361
+ }
2362
+ };
2363
+
2071
2364
  // src/client/index.ts
2072
2365
  setSdkVersion(SDK_VERSION);
2366
+ var DEFAULT_BROWSER_TIMEOUT_MS = 5e3;
2367
+ var DEFAULT_SERVER_TIMEOUT_MS = 8e3;
2368
+ function generateRequestId2() {
2369
+ return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
2370
+ }
2371
+ function isAbortError(error) {
2372
+ if (error instanceof DOMException && error.name === "AbortError") {
2373
+ return true;
2374
+ }
2375
+ if (error instanceof Error && error.name === "AbortError") {
2376
+ return true;
2377
+ }
2378
+ return false;
2379
+ }
2380
+ function getNetworkErrorCode(error) {
2381
+ const message = error.message.toLowerCase();
2382
+ if (message.includes("timeout") || message.includes("timed out")) {
2383
+ return "network:timeout";
2384
+ }
2385
+ if (message.includes("dns") || message.includes("getaddrinfo") || message.includes("enotfound")) {
2386
+ return "network:dns_error";
2387
+ }
2388
+ return "network:connection_error";
2389
+ }
2073
2390
  function convertToTypedError(error) {
2391
+ if (isAbortError(error)) {
2392
+ throw error;
2393
+ }
2074
2394
  if (error instanceof ApiEnvelopeError) {
2075
2395
  throw new RiverbankApiError({
2076
2396
  code: error.code,
@@ -2084,15 +2404,30 @@ function convertToTypedError(error) {
2084
2404
  if (error instanceof ApiRequestError && error.body && typeof error.body === "object") {
2085
2405
  const body = error.body;
2086
2406
  if (isApiError(body)) {
2087
- throw new RiverbankApiError(body.error);
2407
+ const envelopeError = body.error;
2408
+ throw new RiverbankApiError({
2409
+ ...envelopeError,
2410
+ retryAfterMs: error.retryAfterMs
2411
+ });
2088
2412
  }
2089
2413
  }
2414
+ if (error instanceof TypeError || error instanceof Error && !("status" in error)) {
2415
+ const networkError = error;
2416
+ throw new RiverbankApiError({
2417
+ code: getNetworkErrorCode(networkError),
2418
+ message: networkError.message || "Network request failed",
2419
+ requestId: `local-${Date.now()}`,
2420
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2421
+ status: 0
2422
+ // No HTTP response received
2423
+ });
2424
+ }
2090
2425
  throw error;
2091
2426
  }
2092
2427
  function createRiverbankClient(config) {
2093
2428
  if (!config.baseUrl) {
2094
2429
  throw new Error(
2095
- "baseUrl is required when creating a Builder client. Expected format: https://dashboard.example.com/api (must include /api path)"
2430
+ "baseUrl is required when creating a Riverbank client. Expected format: https://dashboard.example.com/api (must include /api path)"
2096
2431
  );
2097
2432
  }
2098
2433
  if (!config.baseUrl.endsWith("/api")) {
@@ -2103,59 +2438,179 @@ function createRiverbankClient(config) {
2103
2438
  const cacheEnabled = config.cache?.enabled ?? true;
2104
2439
  const cacheTTL = (config.cache?.ttl ?? 300) * 1e3;
2105
2440
  const cacheMaxSize = config.cache?.maxSize ?? 100;
2441
+ const resilienceEnabled = config.resilience?.enabled ?? true;
2442
+ const staleIfError = config.resilience?.staleIfError ?? true;
2443
+ const staleTtlMs = (config.resilience?.staleTtlSec ?? 300) * 1e3;
2444
+ const requestTimeoutMs = config.resilience?.requestTimeoutMs ?? (typeof window !== "undefined" ? DEFAULT_BROWSER_TIMEOUT_MS : DEFAULT_SERVER_TIMEOUT_MS);
2445
+ const retryConfig = {
2446
+ maxAttempts: config.resilience?.retry?.maxAttempts ?? DEFAULT_RETRY_CONFIG.maxAttempts,
2447
+ baseDelayMs: config.resilience?.retry?.baseDelayMs ?? DEFAULT_RETRY_CONFIG.baseDelayMs,
2448
+ maxDelayMs: config.resilience?.retry?.maxDelayMs ?? DEFAULT_RETRY_CONFIG.maxDelayMs,
2449
+ jitter: config.resilience?.retry?.jitter ?? DEFAULT_RETRY_CONFIG.jitter,
2450
+ retryOn: config.resilience?.retry?.retryOn
2451
+ };
2452
+ const circuitBreakerConfig = {
2453
+ failureThreshold: config.resilience?.circuitBreaker?.failureThreshold ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.failureThreshold,
2454
+ resetTimeoutMs: config.resilience?.circuitBreaker?.resetTimeoutMs ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.resetTimeoutMs,
2455
+ halfOpenMaxRequests: config.resilience?.circuitBreaker?.halfOpenMaxRequests ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.halfOpenMaxRequests
2456
+ };
2106
2457
  const apiClient = createBearerAPIClient(config.apiKey, config.baseUrl);
2107
2458
  const cache = new SimpleCache({
2108
2459
  maxSize: cacheMaxSize,
2109
- ttl: cacheTTL
2460
+ ttl: cacheTTL,
2461
+ staleTtl: staleTtlMs
2110
2462
  });
2111
- async function cachedFetch(cacheKey, fetcher, options) {
2112
- if (cacheEnabled && !options?.force) {
2113
- const cached = cache.get(cacheKey);
2114
- if (cached !== void 0) {
2115
- return cached;
2463
+ const circuitBreaker = new CircuitBreaker(circuitBreakerConfig);
2464
+ let lastStatus = null;
2465
+ let isDegraded = false;
2466
+ function emitStatus(source, data, details) {
2467
+ const status = {
2468
+ source,
2469
+ isPreview: details.isPreview,
2470
+ cacheKey: details.cacheKey,
2471
+ error: details.error,
2472
+ staleAgeSec: details.staleAgeSec,
2473
+ circuit: circuitBreaker.getState(),
2474
+ requestId: details.requestId,
2475
+ durationMs: details.durationMs
2476
+ };
2477
+ lastStatus = status;
2478
+ config.resilience?.onStatusChange?.(status);
2479
+ const nowDegraded = source === "stale" || source === "error";
2480
+ if (nowDegraded !== isDegraded) {
2481
+ isDegraded = nowDegraded;
2482
+ config.resilience?.onDegradedMode?.(nowDegraded, status);
2483
+ }
2484
+ return data;
2485
+ }
2486
+ async function resilientFetch(cacheKey, fetcher, options) {
2487
+ const requestId = generateRequestId2();
2488
+ const startTime = Date.now();
2489
+ const isPreview = options.preview ?? false;
2490
+ const statusDetails = (extra = {}) => ({
2491
+ requestId,
2492
+ cacheKey,
2493
+ isPreview,
2494
+ durationMs: Date.now() - startTime,
2495
+ ...extra
2496
+ });
2497
+ if (cacheEnabled && !options.force) {
2498
+ const fresh = cache.getFresh(cacheKey);
2499
+ if (fresh !== null) {
2500
+ return emitStatus("cache", fresh, statusDetails());
2501
+ }
2502
+ }
2503
+ if (resilienceEnabled && circuitBreaker.isOpen()) {
2504
+ if (!isPreview && staleIfError) {
2505
+ const stale = cache.getStale(cacheKey);
2506
+ if (stale) {
2507
+ return emitStatus("stale", stale.value, statusDetails({
2508
+ staleAgeSec: stale.staleAgeSec,
2509
+ error: { code: "circuit_open", message: "Circuit breaker is open" }
2510
+ }));
2511
+ }
2512
+ }
2513
+ if (!options.force) {
2514
+ const circuitState = circuitBreaker.getState();
2515
+ emitStatus("error", null, statusDetails({
2516
+ error: { code: "circuit_open", message: "Circuit breaker is open" }
2517
+ }));
2518
+ throw new CircuitOpenError(circuitState);
2116
2519
  }
2117
2520
  }
2118
- let data;
2119
2521
  try {
2120
- const response = await fetcher();
2121
- data = unwrapResponse(response);
2522
+ let data;
2523
+ if (resilienceEnabled) {
2524
+ if (circuitBreaker.getState().state === "half-open") {
2525
+ circuitBreaker.incrementHalfOpenRequests();
2526
+ }
2527
+ data = await fetchWithTimeoutAndRetry(
2528
+ async (timeoutSignal) => {
2529
+ const combinedSignal = options.signal ? combineAbortSignals(timeoutSignal, options.signal) : timeoutSignal;
2530
+ try {
2531
+ const response = await fetcher(combinedSignal);
2532
+ return unwrapResponse(response);
2533
+ } catch (error) {
2534
+ convertToTypedError(error);
2535
+ }
2536
+ },
2537
+ {
2538
+ ...retryConfig,
2539
+ requestTimeoutMs
2540
+ }
2541
+ );
2542
+ circuitBreaker.recordSuccess();
2543
+ } else {
2544
+ try {
2545
+ const response = await fetcher(options.signal ?? new AbortController().signal);
2546
+ data = unwrapResponse(response);
2547
+ } catch (error) {
2548
+ convertToTypedError(error);
2549
+ }
2550
+ }
2551
+ if (cacheEnabled) {
2552
+ cache.set(cacheKey, data);
2553
+ }
2554
+ return emitStatus("live", data, statusDetails());
2122
2555
  } catch (error) {
2123
- convertToTypedError(error);
2556
+ if (resilienceEnabled && error instanceof Error) {
2557
+ circuitBreaker.recordFailure(error);
2558
+ }
2559
+ if (!isPreview && staleIfError && cacheEnabled) {
2560
+ const stale = cache.getStale(cacheKey);
2561
+ if (stale) {
2562
+ const errorInfo2 = error instanceof RiverbankApiError ? { code: error.code, message: error.message } : { message: error.message };
2563
+ return emitStatus("stale", stale.value, statusDetails({
2564
+ staleAgeSec: stale.staleAgeSec,
2565
+ error: errorInfo2
2566
+ }));
2567
+ }
2568
+ }
2569
+ const errorInfo = error instanceof RiverbankApiError ? { code: error.code, message: error.message } : { message: error.message };
2570
+ emitStatus("error", null, statusDetails({ error: errorInfo }));
2571
+ throw error;
2124
2572
  }
2125
- if (cacheEnabled) {
2126
- cache.set(cacheKey, data);
2573
+ }
2574
+ function combineAbortSignals(...signals) {
2575
+ const controller = new AbortController();
2576
+ for (const signal of signals) {
2577
+ if (signal.aborted) {
2578
+ controller.abort(signal.reason);
2579
+ break;
2580
+ }
2581
+ signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
2127
2582
  }
2128
- return data;
2583
+ return controller.signal;
2129
2584
  }
2130
2585
  return {
2131
2586
  async getSite(params) {
2132
- const { slug, domain, id } = params;
2587
+ const { slug, domain, id, signal } = params;
2133
2588
  if (!slug && !domain && !id) {
2134
2589
  throw new Error(
2135
2590
  `getSite() requires at least one identifier: slug, domain, or id. Received: ${JSON.stringify(params)}`
2136
2591
  );
2137
2592
  }
2138
2593
  const cacheKey = `site:${slug || domain || id}`;
2139
- return cachedFetch(cacheKey, async () => {
2594
+ return resilientFetch(cacheKey, async (sig) => {
2140
2595
  const apiParams = {};
2141
2596
  if (params.slug) apiParams.slug = params.slug;
2142
2597
  if (params.domain) apiParams.domain = params.domain;
2143
2598
  if (params.id) apiParams.id = params.id;
2144
- return await apiClient({ endpoint: "getSite", params: apiParams });
2145
- });
2599
+ return await apiClient({ endpoint: "getSite", params: apiParams, options: { signal: sig } });
2600
+ }, { signal });
2146
2601
  },
2147
2602
  async getPage(params) {
2148
- const { siteId, path, preview = false } = params;
2603
+ const { siteId, path, preview = false, signal } = params;
2149
2604
  const cacheKey = `page:${siteId}:${path}:${preview}`;
2150
- return cachedFetch(cacheKey, async () => {
2151
- return await apiClient({ endpoint: "getContentByPath", params: { siteId }, body: { path, preview } });
2152
- });
2605
+ return resilientFetch(cacheKey, async (sig) => {
2606
+ return await apiClient({ endpoint: "getContentByPath", params: { siteId }, body: { path, preview }, options: { signal: sig } });
2607
+ }, { preview, signal });
2153
2608
  },
2154
2609
  async getEntries(params) {
2155
- const { siteId, contentType, limit, offset, order, preview = false, mode, entryIds, includeMeta } = params;
2610
+ const { siteId, contentType, limit, offset, order, preview = false, mode, entryIds, includeMeta, signal } = params;
2156
2611
  const entryIdsCacheKey = mode === "manual" && entryIds?.length ? entryIds.join(",") : "";
2157
2612
  const cacheKey = `entries:${siteId}:${contentType}:${limit ?? ""}:${offset ?? ""}:${order ?? ""}:${preview}:${mode ?? ""}:${entryIdsCacheKey}:${includeMeta ?? ""}`;
2158
- return cachedFetch(cacheKey, async () => {
2613
+ return resilientFetch(cacheKey, async (sig) => {
2159
2614
  let orderParam;
2160
2615
  if (order === "newest") {
2161
2616
  orderParam = "published_at.desc";
@@ -2177,47 +2632,47 @@ function createRiverbankClient(config) {
2177
2632
  entryIds: JSON.stringify(entryIds)
2178
2633
  }
2179
2634
  };
2180
- return await apiClient({ endpoint: "listPublishedEntries", params: apiParams });
2181
- });
2635
+ return await apiClient({ endpoint: "listPublishedEntries", params: apiParams, options: { signal: sig } });
2636
+ }, { preview, signal });
2182
2637
  },
2183
2638
  async getEntry(params) {
2184
- const { siteId, contentType, slug } = params;
2639
+ const { siteId, contentType, slug, signal } = params;
2185
2640
  const cacheKey = `entry:${siteId}:${contentType}:${slug}`;
2186
- return cachedFetch(cacheKey, async () => {
2187
- return await apiClient({ endpoint: "getPublishedEntryPreview", params: { siteId, type: contentType, slug } });
2188
- });
2641
+ return resilientFetch(cacheKey, async (sig) => {
2642
+ return await apiClient({ endpoint: "getPublishedEntryPreview", params: { siteId, type: contentType, slug }, options: { signal: sig } });
2643
+ }, { signal });
2189
2644
  },
2190
2645
  async getPublicFormById(params) {
2191
- const { formId } = params;
2646
+ const { formId, signal } = params;
2192
2647
  if (!formId) {
2193
2648
  throw new Error("getPublicFormById() requires formId");
2194
2649
  }
2195
2650
  const cacheKey = `public-form:${formId}`;
2196
- return cachedFetch(cacheKey, async () => {
2197
- return await apiClient({ endpoint: "getPublicFormById", params: { formId } });
2198
- });
2651
+ return resilientFetch(cacheKey, async (sig) => {
2652
+ return await apiClient({ endpoint: "getPublicFormById", params: { formId }, options: { signal: sig } });
2653
+ }, { signal });
2199
2654
  },
2200
2655
  async getPublicBookingServices(params) {
2201
- const { siteId, ids } = params;
2656
+ const { siteId, ids, signal } = params;
2202
2657
  if (!siteId) {
2203
2658
  throw new Error("getPublicBookingServices() requires siteId");
2204
2659
  }
2205
2660
  const cacheKey = `public-booking-services:${siteId}:${ids ?? ""}`;
2206
- return cachedFetch(cacheKey, async () => {
2661
+ return resilientFetch(cacheKey, async (sig) => {
2207
2662
  const apiParams = {
2208
2663
  siteId,
2209
2664
  ...ids && { ids }
2210
2665
  };
2211
- return await apiClient({ endpoint: "getPublicBookingServices", params: apiParams });
2212
- });
2666
+ return await apiClient({ endpoint: "getPublicBookingServices", params: apiParams, options: { signal: sig } });
2667
+ }, { signal });
2213
2668
  },
2214
2669
  async listPublicEvents(params) {
2215
- const { siteId, limit, from, to, stage } = params;
2670
+ const { siteId, limit, from, to, stage, signal } = params;
2216
2671
  if (!siteId) {
2217
2672
  throw new Error("listPublicEvents() requires siteId");
2218
2673
  }
2219
2674
  const cacheKey = `public-events:${siteId}:${limit ?? ""}:${from ?? ""}:${to ?? ""}:${stage ?? ""}`;
2220
- return cachedFetch(cacheKey, async () => {
2675
+ return resilientFetch(cacheKey, async (sig) => {
2221
2676
  const apiParams = {
2222
2677
  siteId,
2223
2678
  ...typeof limit === "number" && { limit: String(limit) },
@@ -2225,40 +2680,46 @@ function createRiverbankClient(config) {
2225
2680
  ...to && { to },
2226
2681
  ...stage && { stage }
2227
2682
  };
2228
- return await apiClient({ endpoint: "listPublicEvents", params: apiParams });
2229
- });
2683
+ return await apiClient({ endpoint: "listPublicEvents", params: apiParams, options: { signal: sig } });
2684
+ }, { signal });
2230
2685
  },
2231
2686
  async resolveEventOccurrence(params) {
2232
- const { siteId, entryId, segment } = params;
2687
+ const { siteId, entryId, segment, signal } = params;
2233
2688
  if (!siteId || !entryId || !segment) {
2234
2689
  throw new Error("resolveEventOccurrence() requires siteId, entryId, and segment");
2235
2690
  }
2236
2691
  const cacheKey = `event-occurrence:${siteId}:${entryId}:${segment}`;
2237
- return cachedFetch(cacheKey, async () => {
2692
+ return resilientFetch(cacheKey, async (sig) => {
2238
2693
  return await apiClient({
2239
2694
  endpoint: "resolveEventOccurrence",
2240
- params: { siteId, entryId, segment }
2695
+ params: { siteId, entryId, segment },
2696
+ options: { signal: sig }
2241
2697
  });
2242
- });
2698
+ }, { signal });
2243
2699
  },
2244
2700
  async checkRedirect(params) {
2245
- const { siteId, path } = params;
2701
+ const { siteId, path, signal } = params;
2246
2702
  if (!siteId || !path) {
2247
2703
  throw new Error("checkRedirect() requires siteId and path");
2248
2704
  }
2249
2705
  const cacheKey = `redirect:${siteId}:${path}`;
2250
- return cachedFetch(cacheKey, async () => {
2706
+ return resilientFetch(cacheKey, async (sig) => {
2251
2707
  return await apiClient({
2252
2708
  endpoint: "checkRedirect",
2253
- params: { site: siteId, path }
2709
+ params: { site: siteId, path },
2710
+ options: { signal: sig }
2254
2711
  });
2255
- });
2712
+ }, { signal });
2256
2713
  },
2257
2714
  clearCache() {
2258
2715
  cache.clear();
2716
+ },
2717
+ getLastEmittedStatus() {
2718
+ return lastStatus;
2719
+ },
2720
+ getCircuitState() {
2721
+ return circuitBreaker.getState();
2259
2722
  }
2260
- // Cast to RiverbankClient to satisfy overloaded getEntries signature
2261
- // The implementation correctly returns the right type based on includeMeta
2262
2723
  };
2263
2724
  }
2264
2725
 
@@ -2267,4 +2728,4 @@ export {
2267
2728
  buildEndpointURL,
2268
2729
  createRiverbankClient
2269
2730
  };
2270
- //# sourceMappingURL=chunk-7FIJSGHU.mjs.map
2731
+ //# sourceMappingURL=chunk-5JT452F2.mjs.map