tempest-react-sdk 0.43.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +12 -5
  2. package/dist/app/AppProviders.cjs +1 -1
  3. package/dist/app/AppProviders.js +6 -6
  4. package/dist/audio/sfx-pool.cjs +2 -0
  5. package/dist/audio/sfx-pool.cjs.map +1 -0
  6. package/dist/audio/sfx-pool.js +62 -0
  7. package/dist/audio/sfx-pool.js.map +1 -0
  8. package/dist/audio/use-sfx-pool.cjs +2 -0
  9. package/dist/audio/use-sfx-pool.cjs.map +1 -0
  10. package/dist/audio/use-sfx-pool.js +18 -0
  11. package/dist/audio/use-sfx-pool.js.map +1 -0
  12. package/dist/auth/create-tempest-auth.cjs +1 -1
  13. package/dist/auth/create-tempest-auth.cjs.map +1 -1
  14. package/dist/auth/create-tempest-auth.js +47 -44
  15. package/dist/auth/create-tempest-auth.js.map +1 -1
  16. package/dist/auth/lazy-with-retry.cjs +1 -1
  17. package/dist/auth/lazy-with-retry.cjs.map +1 -1
  18. package/dist/auth/lazy-with-retry.js +8 -1
  19. package/dist/auth/lazy-with-retry.js.map +1 -1
  20. package/dist/components/AppBar/AppBar.cjs +1 -1
  21. package/dist/components/AppBar/AppBar.cjs.map +1 -1
  22. package/dist/components/AppBar/AppBar.js +27 -25
  23. package/dist/components/AppBar/AppBar.js.map +1 -1
  24. package/dist/components/AppBar/use-sticky-body-warning.cjs +2 -0
  25. package/dist/components/AppBar/use-sticky-body-warning.cjs.map +1 -0
  26. package/dist/components/AppBar/use-sticky-body-warning.js +16 -0
  27. package/dist/components/AppBar/use-sticky-body-warning.js.map +1 -0
  28. package/dist/components/BarList/BarList.cjs +2 -0
  29. package/dist/components/BarList/BarList.cjs.map +1 -0
  30. package/dist/components/BarList/BarList.js +42 -0
  31. package/dist/components/BarList/BarList.js.map +1 -0
  32. package/dist/components/BarList/BarList.module.cjs +2 -0
  33. package/dist/components/BarList/BarList.module.cjs.map +1 -0
  34. package/dist/components/BarList/BarList.module.js +15 -0
  35. package/dist/components/BarList/BarList.module.js.map +1 -0
  36. package/dist/components/BarList/bar-list-model.cjs +2 -0
  37. package/dist/components/BarList/bar-list-model.cjs.map +1 -0
  38. package/dist/components/BarList/bar-list-model.js +18 -0
  39. package/dist/components/BarList/bar-list-model.js.map +1 -0
  40. package/dist/components/DataTable/DataTable.cjs +1 -1
  41. package/dist/components/DataTable/DataTable.cjs.map +1 -1
  42. package/dist/components/DataTable/DataTable.js +141 -108
  43. package/dist/components/DataTable/DataTable.js.map +1 -1
  44. package/dist/components/DataTable/DataTable.module.cjs +1 -1
  45. package/dist/components/DataTable/DataTable.module.cjs.map +1 -1
  46. package/dist/components/DataTable/DataTable.module.js +13 -10
  47. package/dist/components/DataTable/DataTable.module.js.map +1 -1
  48. package/dist/components/DataTable/LoadingRows.cjs +2 -0
  49. package/dist/components/DataTable/LoadingRows.cjs.map +1 -0
  50. package/dist/components/DataTable/LoadingRows.js +18 -0
  51. package/dist/components/DataTable/LoadingRows.js.map +1 -0
  52. package/dist/components/DataTable/use-dev-warnings.cjs +2 -0
  53. package/dist/components/DataTable/use-dev-warnings.cjs.map +1 -0
  54. package/dist/components/DataTable/use-dev-warnings.js +17 -0
  55. package/dist/components/DataTable/use-dev-warnings.js.map +1 -0
  56. package/dist/components/FilterBar/filter-apply.cjs +2 -0
  57. package/dist/components/FilterBar/filter-apply.cjs.map +1 -0
  58. package/dist/components/FilterBar/filter-apply.js +76 -0
  59. package/dist/components/FilterBar/filter-apply.js.map +1 -0
  60. package/dist/components/FilterBar/filter-query.cjs +2 -0
  61. package/dist/components/FilterBar/filter-query.cjs.map +1 -0
  62. package/dist/components/FilterBar/filter-query.js +38 -0
  63. package/dist/components/FilterBar/filter-query.js.map +1 -0
  64. package/dist/hooks/use-countdown.cjs +2 -0
  65. package/dist/hooks/use-countdown.cjs.map +1 -0
  66. package/dist/hooks/use-countdown.js +18 -0
  67. package/dist/hooks/use-countdown.js.map +1 -0
  68. package/dist/hooks/use-latest-ref.cjs +2 -0
  69. package/dist/hooks/use-latest-ref.cjs.map +1 -0
  70. package/dist/hooks/use-latest-ref.js +10 -0
  71. package/dist/hooks/use-latest-ref.js.map +1 -0
  72. package/dist/hooks/use-typewriter.cjs +2 -0
  73. package/dist/hooks/use-typewriter.cjs.map +1 -0
  74. package/dist/hooks/use-typewriter.js +28 -0
  75. package/dist/hooks/use-typewriter.js.map +1 -0
  76. package/dist/http/api-client.cjs +1 -1
  77. package/dist/http/api-client.cjs.map +1 -1
  78. package/dist/http/api-client.js +42 -43
  79. package/dist/http/api-client.js.map +1 -1
  80. package/dist/http/build-url.cjs +2 -0
  81. package/dist/http/build-url.cjs.map +1 -0
  82. package/dist/http/build-url.js +26 -0
  83. package/dist/http/build-url.js.map +1 -0
  84. package/dist/http/describe-api-error.cjs +2 -0
  85. package/dist/http/describe-api-error.cjs.map +1 -0
  86. package/dist/http/describe-api-error.js +22 -0
  87. package/dist/http/describe-api-error.js.map +1 -0
  88. package/dist/http/use-describe-api-error.cjs +2 -0
  89. package/dist/http/use-describe-api-error.cjs.map +1 -0
  90. package/dist/http/use-describe-api-error.js +15 -0
  91. package/dist/http/use-describe-api-error.js.map +1 -0
  92. package/dist/i18n/I18nProvider.cjs +1 -1
  93. package/dist/i18n/I18nProvider.cjs.map +1 -1
  94. package/dist/i18n/I18nProvider.js +4 -1
  95. package/dist/i18n/I18nProvider.js.map +1 -1
  96. package/dist/query/QueryProvider.cjs +1 -1
  97. package/dist/query/QueryProvider.cjs.map +1 -1
  98. package/dist/query/QueryProvider.js +13 -12
  99. package/dist/query/QueryProvider.js.map +1 -1
  100. package/dist/query/retry-policy.cjs +2 -0
  101. package/dist/query/retry-policy.cjs.map +1 -0
  102. package/dist/query/retry-policy.js +10 -0
  103. package/dist/query/retry-policy.js.map +1 -0
  104. package/dist/styles.css +1 -1
  105. package/dist/tempest-react-sdk.cjs +1 -1
  106. package/dist/tempest-react-sdk.d.ts +867 -10
  107. package/dist/tempest-react-sdk.js +276 -261
  108. package/dist/utils/compressed-storage.cjs +2 -0
  109. package/dist/utils/compressed-storage.cjs.map +1 -0
  110. package/dist/utils/compressed-storage.js +52 -0
  111. package/dist/utils/compressed-storage.js.map +1 -0
  112. package/dist/utils/csv.cjs +4 -0
  113. package/dist/utils/csv.cjs.map +1 -0
  114. package/dist/utils/csv.js +25 -0
  115. package/dist/utils/csv.js.map +1 -0
  116. package/dist/utils/format.cjs +1 -1
  117. package/dist/utils/format.cjs.map +1 -1
  118. package/dist/utils/format.js +11 -4
  119. package/dist/utils/format.js.map +1 -1
  120. package/dist/utils/numbers.cjs +1 -1
  121. package/dist/utils/numbers.cjs.map +1 -1
  122. package/dist/utils/numbers.js +5 -2
  123. package/dist/utils/numbers.js.map +1 -1
  124. package/dist/vite/tempest-pwa-dev-sw.cjs +1 -1
  125. package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
  126. package/dist/vite/tempest-pwa-dev-sw.js +10 -5
  127. package/dist/vite/tempest-pwa-dev-sw.js.map +1 -1
  128. package/dist/vite/tempest-pwa-manifest.cjs +2 -2
  129. package/dist/vite/tempest-pwa-manifest.cjs.map +1 -1
  130. package/dist/vite/tempest-pwa-manifest.js +17 -13
  131. package/dist/vite/tempest-pwa-manifest.js.map +1 -1
  132. package/dist/vite.d.ts +19 -2
  133. package/package.json +1 -1
@@ -1,32 +1,28 @@
1
1
  import { randomId as e } from "../utils/ids.js";
2
- import { TempestApiError as t, buildApiError as n } from "./errors.js";
3
- import { retry as r } from "./retry.js";
2
+ import { buildApiUrl as t } from "./build-url.js";
3
+ import { TempestApiError as n, buildApiError as r } from "./errors.js";
4
+ import { retry as i } from "./retry.js";
4
5
  //#region src/http/api-client.ts
5
- var i = /* @__PURE__ */ new Set([
6
+ var a = /* @__PURE__ */ new Set([
6
7
  "GET",
7
8
  "HEAD",
8
9
  "OPTIONS"
9
- ]), a = /* @__PURE__ */ new Set([
10
+ ]), o = /* @__PURE__ */ new Set([
10
11
  0,
11
12
  408,
12
13
  425,
13
14
  429
14
15
  ]);
15
- function o(e, n) {
16
- return !i.has(n) || !(e instanceof t) ? !1 : a.has(e.status) || e.status >= 500;
16
+ function s(e, t) {
17
+ return !a.has(t) || !(e instanceof n) ? !1 : o.has(e.status) || e.status >= 500;
17
18
  }
18
- function s(e) {
19
+ function c(e) {
19
20
  return e ? e === !0 ? {} : e : null;
20
21
  }
21
- function c(e, t, n) {
22
- let r = new URL(t, e.endsWith("/") ? e : `${e}/`);
23
- if (n) for (let [e, t] of Object.entries(n)) t != null && r.searchParams.set(e, String(t));
24
- return r.toString();
25
- }
26
22
  function l(e) {
27
23
  return typeof FormData < "u" && e instanceof FormData;
28
24
  }
29
- async function u(e, r) {
25
+ async function u(e, t) {
30
26
  let i;
31
27
  try {
32
28
  i = await e.clone().json();
@@ -37,51 +33,54 @@ async function u(e, r) {
37
33
  i = null;
38
34
  }
39
35
  }
40
- return new t(n(e.status, i, e.headers, r));
36
+ return new n(r(e.status, i, e.headers, t));
41
37
  }
42
- function d(t) {
43
- let n = t.fetcher ?? globalThis.fetch.bind(globalThis);
44
- function i() {
45
- let e = t.getToken?.();
38
+ function d(n) {
39
+ let r = n.fetcher ?? globalThis.fetch.bind(globalThis);
40
+ function a() {
41
+ let e = n.getToken?.();
46
42
  return e ? { Authorization: `Bearer ${e}` } : {};
47
43
  }
48
- async function a(e, r, a) {
49
- let { body: o, params: s, headers: u, ...d } = r, f = l(o), p = {
44
+ async function o(e, i, o) {
45
+ let { body: s, params: c, headers: u, ...d } = i, f = l(s), p = {
50
46
  ...f ? {} : { "Content-Type": "application/json" },
51
- ...a ? { "X-Request-ID": a } : {},
52
- ...t.headers,
53
- ...i(),
47
+ ...o ? { "X-Request-ID": o } : {},
48
+ ...n.headers,
49
+ ...a(),
54
50
  ...u
55
51
  }, m = {
56
52
  ...d,
57
53
  headers: p,
58
- credentials: t.withCredentials ? "include" : d.credentials,
59
- body: o == null ? void 0 : f ? o : JSON.stringify(o)
54
+ credentials: n.withCredentials ? "include" : d.credentials,
55
+ body: s == null ? void 0 : f ? s : JSON.stringify(s)
60
56
  };
61
- return n(c(t.baseURL, e, s), m);
57
+ return r(t(n.baseURL, e, {
58
+ prefix: n.prefix,
59
+ params: c
60
+ }), m);
62
61
  }
63
- async function d(n, r) {
64
- let i = t.requestId ? t.requestId() : e(), o = await a(n, r, i);
65
- if (o.status === 401) {
66
- if (t.refresh) {
62
+ async function d(t, r) {
63
+ let i = n.requestId ? n.requestId() : e(), a = await o(t, r, i);
64
+ if (a.status === 401) {
65
+ if (n.refresh) {
67
66
  try {
68
- await t.refresh(), o = await a(n, r, i);
67
+ await n.refresh(), a = await o(t, r, i);
69
68
  } catch {
70
- throw await t.onUnauthorized?.(o), await u(o, i);
69
+ throw await n.onUnauthorized?.(a), await u(a, i);
71
70
  }
72
- o.status === 401 && await t.onUnauthorized?.(o);
73
- } else await t.onUnauthorized?.(o);
71
+ a.status === 401 && await n.onUnauthorized?.(a);
72
+ } else await n.onUnauthorized?.(a);
74
73
  }
75
- if (!o.ok) throw await u(o, i);
76
- if (o.status !== 204) return (o.headers.get("content-type") ?? "").includes("application/json") ? await o.json() : await o.text();
74
+ if (!a.ok) throw await u(a, i);
75
+ if (a.status !== 204) return (a.headers.get("content-type") ?? "").includes("application/json") ? await a.json() : await a.text();
77
76
  }
78
- async function f(e, n = {}) {
79
- let i = s(t.retry);
80
- if (!i) return d(e, n);
81
- let a = (n.method ?? "GET").toUpperCase();
82
- return r(() => d(e, n), {
83
- ...i,
84
- shouldRetry: i.shouldRetry ?? ((e) => o(e, a))
77
+ async function f(e, t = {}) {
78
+ let r = c(n.retry);
79
+ if (!r) return d(e, t);
80
+ let a = (t.method ?? "GET").toUpperCase();
81
+ return i(() => d(e, t), {
82
+ ...r,
83
+ shouldRetry: r.shouldRetry ?? ((e) => s(e, a))
85
84
  });
86
85
  }
87
86
  async function p(e, t, n = "POST") {
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.js","names":[],"sources":["../../src/http/api-client.ts"],"sourcesContent":["/**\n * @tempest-limits function-lines — createApiClient is over the limit and every line\n * is a request-lifecycle concern the client cannot delegate: base URL joining, the\n * auth header, the 401 refresh-and-replay, the opt-in retry wrapper and the response\n * parsing that turns a failure into a typed error.\n */\nimport { randomId } from \"../utils\";\nimport { buildApiError, TempestApiError } from \"./errors\";\nimport { retry as retryWithBackoff } from \"./retry\";\nimport type { RetryOptions } from \"./retry\";\nimport type { ApiClient, ApiClientConfig, RequestOptions } from \"./types\";\n\n/**\n * Methods the built-in retry policy will replay.\n *\n * `PUT` and `DELETE` are idempotent on paper but stay out: a backend that logs,\n * bills, or fires a webhook per call still sees two, so replaying them is a\n * decision the caller makes through `shouldRetry`, not a default.\n */\nconst IDEMPOTENT_METHODS: ReadonlySet<string> = new Set([\"GET\", \"HEAD\", \"OPTIONS\"]);\n\n/**\n * Sub-500 statuses worth a second attempt: a network failure (status `0`), a\n * request timeout, a too-early replay, and a rate limit — which usually carries\n * the `Retry-After` the backoff already honours.\n */\nconst RETRIABLE_STATUSES: ReadonlySet<number> = new Set([0, 408, 425, 429]);\n\n/**\n * The built-in retry policy, used when `retry` is `true` or is options carrying\n * no `shouldRetry` of their own.\n *\n * Conservative on purpose. Replaying a write can duplicate it, and replaying a\n * `400` or a `403` cannot fix a bad payload or a permission the caller does not\n * have — it only spends the user's time before showing the same error.\n *\n * @param error - Whatever the attempt threw.\n * @param method - The upper-cased HTTP method of the request.\n * @returns Whether the client should try again.\n */\nfunction isRetriableFailure(error: unknown, method: string): boolean {\n if (!IDEMPOTENT_METHODS.has(method)) return false;\n if (!(error instanceof TempestApiError)) return false;\n return RETRIABLE_STATUSES.has(error.status) || error.status >= 500;\n}\n\n/**\n * Normalize the `retry` config into options, or `null` when retrying is off.\n *\n * @param config - The `retry` field as the caller wrote it.\n * @returns Retry options to use, or `null` to run a single attempt.\n */\nfunction resolveRetry(config: boolean | RetryOptions | undefined): RetryOptions | null {\n if (!config) return null;\n return config === true ? {} : config;\n}\n\nfunction buildUrl(baseURL: string, path: string, params?: RequestOptions[\"params\"]): string {\n const url = new URL(path, baseURL.endsWith(\"/\") ? baseURL : `${baseURL}/`);\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n return url.toString();\n}\n\nfunction isFormData(body: unknown): body is FormData {\n return typeof FormData !== \"undefined\" && body instanceof FormData;\n}\n\nasync function parseError(response: Response, sentRequestId?: string): Promise<TempestApiError> {\n let body: unknown;\n try {\n body = await response.clone().json();\n } catch {\n try {\n body = await response.text();\n } catch {\n body = null;\n }\n }\n return new TempestApiError(\n buildApiError(response.status, body, response.headers, sentRequestId),\n );\n}\n\n/**\n * Create a typed HTTP client backed by `fetch`.\n *\n * Handles JSON serialization, query params, bearer auth via `getToken`, uploads\n * via `FormData`, and throws a typed `ApiError` on any non-2xx response.\n *\n * **Expired sessions.** A `401` with `refresh` configured awaits the refresh and\n * replays the request once. `onUnauthorized` fires whenever that path ends\n * unauthorized anyway — the refresh threw, or the replay came back `401` — which\n * is the signal to clear the session. Without `refresh`, the first `401` calls\n * it directly.\n *\n * **Retries** are off unless you set `retry`. See {@link ApiClientConfig.retry}\n * for the built-in policy; it never replays a write.\n *\n * @example\n * const api = createApiClient({\n * baseURL: import.meta.env.VITE_API_URL,\n * getToken: () => useAuthStore.getState().token,\n * refresh,\n * onUnauthorized: () => useAuthStore.getState().logout(),\n * retry: true,\n * });\n *\n * @param config - Base URL plus the optional auth, retry and fetch hooks.\n * @returns A client with `request`/`get`/`post`/`put`/`patch`/`delete`/`upload`.\n */\nexport function createApiClient(config: ApiClientConfig): ApiClient {\n const fetcher = config.fetcher ?? globalThis.fetch.bind(globalThis);\n\n function authHeaders(): Record<string, string> {\n const token = config.getToken?.();\n return token ? { Authorization: `Bearer ${token}` } : {};\n }\n\n async function rawRequest(\n path: string,\n options: RequestOptions,\n requestId?: string,\n ): Promise<Response> {\n const { body, params, headers, ...rest } = options;\n const isForm = isFormData(body);\n\n const finalHeaders: Record<string, string> = {\n ...(isForm ? {} : { \"Content-Type\": \"application/json\" }),\n ...(requestId ? { \"X-Request-ID\": requestId } : {}),\n ...config.headers,\n ...authHeaders(),\n ...(headers as Record<string, string> | undefined),\n };\n\n const init: RequestInit = {\n ...rest,\n headers: finalHeaders,\n credentials: config.withCredentials ? \"include\" : rest.credentials,\n body:\n body === undefined || body === null\n ? undefined\n : isForm\n ? (body as FormData)\n : JSON.stringify(body),\n };\n\n return fetcher(buildUrl(config.baseURL, path, params), init);\n }\n\n async function attempt<T>(path: string, options: RequestOptions): Promise<T> {\n const requestId = config.requestId ? config.requestId() : randomId();\n let response = await rawRequest(path, options, requestId);\n\n if (response.status === 401) {\n if (config.refresh) {\n try {\n await config.refresh();\n response = await rawRequest(path, options, requestId);\n } catch {\n await config.onUnauthorized?.(response);\n throw await parseError(response, requestId);\n }\n if (response.status === 401) {\n await config.onUnauthorized?.(response);\n }\n } else {\n await config.onUnauthorized?.(response);\n }\n }\n\n if (!response.ok) {\n throw await parseError(response, requestId);\n }\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"application/json\")) {\n return (await response.json()) as T;\n }\n return (await response.text()) as unknown as T;\n }\n\n async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {\n const retryOptions = resolveRetry(config.retry);\n if (!retryOptions) return attempt<T>(path, options);\n\n const method = (options.method ?? \"GET\").toUpperCase();\n return retryWithBackoff(() => attempt<T>(path, options), {\n ...retryOptions,\n shouldRetry:\n retryOptions.shouldRetry ?? ((error: unknown) => isRetriableFailure(error, method)),\n });\n }\n\n async function upload<T>(\n path: string,\n formData: FormData,\n method: \"POST\" | \"PUT\" | \"PATCH\" = \"POST\",\n ): Promise<T> {\n return request<T>(path, { method, body: formData });\n }\n\n return {\n request,\n get: <T>(path: string, options?: RequestOptions) =>\n request<T>(path, { ...options, method: \"GET\" }),\n post: <T>(path: string, options?: RequestOptions) =>\n request<T>(path, { ...options, method: \"POST\" }),\n put: <T>(path: string, options?: RequestOptions) =>\n request<T>(path, { ...options, method: \"PUT\" }),\n patch: <T>(path: string, options?: RequestOptions) =>\n request<T>(path, { ...options, method: \"PATCH\" }),\n delete: <T>(path: string, options?: RequestOptions) =>\n request<T>(path, { ...options, method: \"DELETE\" }),\n upload,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"api-client.js","names":[],"sources":["../../src/http/api-client.ts"],"sourcesContent":["/**\n * @tempest-limits function-lines — createApiClient is over the limit and every line\n * is a request-lifecycle concern the client cannot delegate: base URL joining, the\n * auth header, the 401 refresh-and-replay, the opt-in retry wrapper and the response\n * parsing that turns a failure into a typed error.\n */\nimport { randomId } from \"../utils\";\nimport { buildApiUrl } from \"./build-url\";\nimport { buildApiError, TempestApiError } from \"./errors\";\nimport { retry as retryWithBackoff } from \"./retry\";\nimport type { RetryOptions } from \"./retry\";\nimport type { ApiClient, ApiClientConfig, RequestOptions } from \"./types\";\n\n/**\n * Methods the built-in retry policy will replay.\n *\n * `PUT` and `DELETE` are idempotent on paper but stay out: a backend that logs,\n * bills, or fires a webhook per call still sees two, so replaying them is a\n * decision the caller makes through `shouldRetry`, not a default.\n */\nconst IDEMPOTENT_METHODS: ReadonlySet<string> = new Set([\"GET\", \"HEAD\", \"OPTIONS\"]);\n\n/**\n * Sub-500 statuses worth a second attempt: a network failure (status `0`), a\n * request timeout, a too-early replay, and a rate limit — which usually carries\n * the `Retry-After` the backoff already honours.\n */\nconst RETRIABLE_STATUSES: ReadonlySet<number> = new Set([0, 408, 425, 429]);\n\n/**\n * The built-in retry policy, used when `retry` is `true` or is options carrying\n * no `shouldRetry` of their own.\n *\n * Conservative on purpose. Replaying a write can duplicate it, and replaying a\n * `400` or a `403` cannot fix a bad payload or a permission the caller does not\n * have — it only spends the user's time before showing the same error.\n *\n * @param error - Whatever the attempt threw.\n * @param method - The upper-cased HTTP method of the request.\n * @returns Whether the client should try again.\n */\nfunction isRetriableFailure(error: unknown, method: string): boolean {\n if (!IDEMPOTENT_METHODS.has(method)) return false;\n if (!(error instanceof TempestApiError)) return false;\n return RETRIABLE_STATUSES.has(error.status) || error.status >= 500;\n}\n\n/**\n * Normalize the `retry` config into options, or `null` when retrying is off.\n *\n * @param config - The `retry` field as the caller wrote it.\n * @returns Retry options to use, or `null` to run a single attempt.\n */\nfunction resolveRetry(config: boolean | RetryOptions | undefined): RetryOptions | null {\n if (!config) return null;\n return config === true ? {} : config;\n}\n\nfunction isFormData(body: unknown): body is FormData {\n return typeof FormData !== \"undefined\" && body instanceof FormData;\n}\n\nasync function parseError(response: Response, sentRequestId?: string): Promise<TempestApiError> {\n let body: unknown;\n try {\n body = await response.clone().json();\n } catch {\n try {\n body = await response.text();\n } catch {\n body = null;\n }\n }\n return new TempestApiError(\n buildApiError(response.status, body, response.headers, sentRequestId),\n );\n}\n\n/**\n * Create a typed HTTP client backed by `fetch`.\n *\n * Handles JSON serialization, query params, bearer auth via `getToken`, uploads\n * via `FormData`, and throws a typed `ApiError` on any non-2xx response.\n *\n * **Expired sessions.** A `401` with `refresh` configured awaits the refresh and\n * replays the request once. `onUnauthorized` fires whenever that path ends\n * unauthorized anyway — the refresh threw, or the replay came back `401` — which\n * is the signal to clear the session. Without `refresh`, the first `401` calls\n * it directly.\n *\n * **Retries** are off unless you set `retry`. See {@link ApiClientConfig.retry}\n * for the built-in policy; it never replays a write.\n *\n * @example\n * const api = createApiClient({\n * baseURL: import.meta.env.VITE_API_URL,\n * getToken: () => useAuthStore.getState().token,\n * refresh,\n * onUnauthorized: () => useAuthStore.getState().logout(),\n * retry: true,\n * });\n *\n * @param config - Base URL plus the optional auth, retry and fetch hooks.\n * @returns A client with `request`/`get`/`post`/`put`/`patch`/`delete`/`upload`.\n */\nexport function createApiClient(config: ApiClientConfig): ApiClient {\n const fetcher = config.fetcher ?? globalThis.fetch.bind(globalThis);\n\n function authHeaders(): Record<string, string> {\n const token = config.getToken?.();\n return token ? { Authorization: `Bearer ${token}` } : {};\n }\n\n async function rawRequest(\n path: string,\n options: RequestOptions,\n requestId?: string,\n ): Promise<Response> {\n const { body, params, headers, ...rest } = options;\n const isForm = isFormData(body);\n\n const finalHeaders: Record<string, string> = {\n ...(isForm ? {} : { \"Content-Type\": \"application/json\" }),\n ...(requestId ? { \"X-Request-ID\": requestId } : {}),\n ...config.headers,\n ...authHeaders(),\n ...(headers as Record<string, string> | undefined),\n };\n\n const init: RequestInit = {\n ...rest,\n headers: finalHeaders,\n credentials: config.withCredentials ? \"include\" : rest.credentials,\n body:\n body === undefined || body === null\n ? undefined\n : isForm\n ? (body as FormData)\n : JSON.stringify(body),\n };\n\n return fetcher(buildApiUrl(config.baseURL, path, { prefix: config.prefix, params }), init);\n }\n\n async function attempt<T>(path: string, options: RequestOptions): Promise<T> {\n const requestId = config.requestId ? config.requestId() : randomId();\n let response = await rawRequest(path, options, requestId);\n\n if (response.status === 401) {\n if (config.refresh) {\n try {\n await config.refresh();\n response = await rawRequest(path, options, requestId);\n } catch {\n await config.onUnauthorized?.(response);\n throw await parseError(response, requestId);\n }\n if (response.status === 401) {\n await config.onUnauthorized?.(response);\n }\n } else {\n await config.onUnauthorized?.(response);\n }\n }\n\n if (!response.ok) {\n throw await parseError(response, requestId);\n }\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"application/json\")) {\n return (await response.json()) as T;\n }\n return (await response.text()) as unknown as T;\n }\n\n async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {\n const retryOptions = resolveRetry(config.retry);\n if (!retryOptions) return attempt<T>(path, options);\n\n const method = (options.method ?? \"GET\").toUpperCase();\n return retryWithBackoff(() => attempt<T>(path, options), {\n ...retryOptions,\n shouldRetry:\n retryOptions.shouldRetry ?? ((error: unknown) => isRetriableFailure(error, method)),\n });\n }\n\n async function upload<T>(\n path: string,\n formData: FormData,\n method: \"POST\" | \"PUT\" | \"PATCH\" = \"POST\",\n ): Promise<T> {\n return request<T>(path, { method, body: formData });\n }\n\n return {\n request,\n get: <T>(path: string, options?: RequestOptions) =>\n request<T>(path, { ...options, method: \"GET\" }),\n post: <T>(path: string, options?: RequestOptions) =>\n request<T>(path, { ...options, method: \"POST\" }),\n put: <T>(path: string, options?: RequestOptions) =>\n request<T>(path, { ...options, method: \"PUT\" }),\n patch: <T>(path: string, options?: RequestOptions) =>\n request<T>(path, { ...options, method: \"PATCH\" }),\n delete: <T>(path: string, options?: RequestOptions) =>\n request<T>(path, { ...options, method: \"DELETE\" }),\n upload,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ var e=/^[a-z][a-z\d+\-.]*:/i;function t(e){return e.split(`/`).filter(Boolean)}function n(e,t){return t.length===0||t.length>e.length?!1:t.every((t,n)=>e[n]===t)}function r(e){let t=globalThis.location?.origin;if(!t)throw TypeError(`createApiClient: baseURL "${e}" is relative and there is no location to resolve it against. Pass an absolute URL (https://api.example.com) outside the browser.`);return t}function i(t,n,r={}){let{prefix:i,params:o}=r,s=e.test(n)?new URL(n):new URL(a(t,n,i));if(o)for(let[e,t]of Object.entries(o))t!=null&&s.searchParams.set(e,String(t));return s.toString()}function a(i,a,o){let s=e.test(i)?new URL(i):new URL(i,r(i)),c=a.indexOf(`?`),l=c===-1?a:a.slice(0,c),u=c===-1?``:a.slice(c),d=[...t(s.pathname),...t(o??``)],f=t(l),p=n(f,d)?f:[...d,...f],m=l.endsWith(`/`)?`/`:``,h=p.length>0?`/${p.join(`/`)}${m}`:`/`;return`${s.origin}${h}${u}`}exports.buildApiUrl=i;
2
+ //# sourceMappingURL=build-url.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-url.cjs","names":[],"sources":["../../src/http/build-url.ts"],"sourcesContent":["/**\n * Matches a path that already carries its own scheme (`https:`, `blob:`), which\n * makes it an absolute URL rather than something to resolve against the base.\n */\nconst ABSOLUTE_URL = /^[a-z][a-z\\d+\\-.]*:/i;\n\n/** Split a path into its non-empty segments, dropping surrounding slashes. */\nfunction segments(path: string): string[] {\n return path.split(\"/\").filter(Boolean);\n}\n\n/**\n * Whether `path` already starts with every segment of `prefix`.\n *\n * Compared segment by segment, so a `/api` prefix is not considered present in\n * `/api-keys` — a plain `startsWith` would swallow the resource.\n *\n * @param path - Segments of the request path.\n * @param prefix - Segments of the base path.\n * @returns Whether the prefix is already applied.\n */\nfunction startsWithSegments(path: string[], prefix: string[]): boolean {\n if (prefix.length === 0 || prefix.length > path.length) return false;\n return prefix.every((segment, index) => path[index] === segment);\n}\n\n/**\n * Resolve the origin a relative `baseURL` hangs off.\n *\n * A base such as `\"/api\"` is the shape you get behind a dev-server proxy or a\n * reverse proxy that serves app and API from one host. It only means something\n * in a browsing context, so this throws with the config to fix rather than\n * letting `new URL` report `Invalid base URL` from three frames deeper.\n *\n * @param baseURL - The base as the caller wrote it.\n * @returns The origin to resolve against.\n * @throws When there is no `location` to borrow an origin from.\n */\nfunction currentOrigin(baseURL: string): string {\n const origin = globalThis.location?.origin;\n if (!origin) {\n throw new TypeError(\n `createApiClient: baseURL \"${baseURL}\" is relative and there is no location to resolve it against. Pass an absolute URL (https://api.example.com) outside the browser.`,\n );\n }\n return origin;\n}\n\n/** Options accepted by {@link buildApiUrl}. */\nexport interface BuildApiUrlOptions {\n /**\n * Path segment every request is nested under, such as `\"/api\"`. Joined\n * after the path the `baseURL` already carries.\n */\n prefix?: string;\n /** Query params to append. `undefined` and `null` values are skipped. */\n params?: Record<string, string | number | boolean | undefined | null>;\n}\n\n/**\n * Join a base URL, an optional prefix and a request path into an absolute URL.\n *\n * `new URL(path, base)` on its own is wrong for an API client. It follows the\n * URL spec, where a path starting with `/` is absolute against the *origin* and\n * therefore discards whatever path the base carried: a client on\n * `https://api.example.com/api` asked for `/auth/login` reaches\n * `https://api.example.com/auth/login`, and every request 404s with nothing in\n * the config that looks wrong. This function resolves the path against the base\n * *path* instead, so the leading slash is a matter of taste rather than a\n * silent 404, and `baseURL` + `prefix` are interchangeable ways to say the same\n * thing.\n *\n * The prefix is applied at most once: a path that already opens with it — say\n * `\"/api/auth/login\"` under a `\"/api\"` prefix — is left alone, so a codebase\n * migrating to `prefix` can move its call sites one at a time. The check is per\n * segment, so `/api-keys` is not mistaken for an already-prefixed path.\n *\n * A path that is itself an absolute URL wins over all of this, which is how a\n * client reaches a second host (a signed upload endpoint, a CDN) without a\n * second client.\n *\n * @example\n * buildApiUrl(\"https://api.example.com\", \"/auth/login\", { prefix: \"/api\" });\n * // \"https://api.example.com/api/auth/login\"\n *\n * buildApiUrl(\"https://api.example.com/api\", \"auth/login\");\n * // \"https://api.example.com/api/auth/login\"\n *\n * @param baseURL - Absolute base URL, or a path relative to the current origin.\n * @param path - The request path, or an absolute URL to use as-is.\n * @param options - Optional prefix and query params.\n * @returns The absolute URL to fetch.\n * @throws When `baseURL` is relative and there is no `location` to resolve it.\n */\nexport function buildApiUrl(\n baseURL: string,\n path: string,\n options: BuildApiUrlOptions = {},\n): string {\n const { prefix, params } = options;\n const url = ABSOLUTE_URL.test(path) ? new URL(path) : new URL(resolve(baseURL, path, prefix));\n\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n return url.toString();\n}\n\n/**\n * Build the absolute URL string for a path that is not already absolute.\n *\n * Kept separate from {@link buildApiUrl} so the query-param loop is not nested\n * inside the joining rules.\n *\n * @param baseURL - Absolute base URL, or a path relative to the current origin.\n * @param path - The request path.\n * @param prefix - Optional segment every request is nested under.\n * @returns The joined absolute URL.\n */\nfunction resolve(baseURL: string, path: string, prefix?: string): string {\n const base = ABSOLUTE_URL.test(baseURL)\n ? new URL(baseURL)\n : new URL(baseURL, currentOrigin(baseURL));\n\n const queryStart = path.indexOf(\"?\");\n const rawPath = queryStart === -1 ? path : path.slice(0, queryStart);\n const query = queryStart === -1 ? \"\" : path.slice(queryStart);\n\n const basePath = [...segments(base.pathname), ...segments(prefix ?? \"\")];\n const requested = segments(rawPath);\n const joined = startsWithSegments(requested, basePath)\n ? requested\n : [...basePath, ...requested];\n\n const trailing = rawPath.endsWith(\"/\") ? \"/\" : \"\";\n const pathname = joined.length > 0 ? `/${joined.join(\"/\")}${trailing}` : \"/\";\n return `${base.origin}${pathname}${query}`;\n}\n"],"mappings":"AAIA,IAAM,EAAe,uBAGrB,SAAS,EAAS,EAAwB,CACtC,OAAO,EAAK,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO,CACzC,CAYA,SAAS,EAAmB,EAAgB,EAA2B,CAEnE,OADI,EAAO,SAAW,GAAK,EAAO,OAAS,EAAK,OAAe,GACxD,EAAO,OAAO,EAAS,IAAU,EAAK,KAAW,CAAO,CACnE,CAcA,SAAS,EAAc,EAAyB,CAC5C,IAAM,EAAS,WAAW,UAAU,OACpC,GAAI,CAAC,EACD,MAAU,UACN,6BAA6B,EAAQ,kIACzC,EAEJ,OAAO,CACX,CAgDA,SAAgB,EACZ,EACA,EACA,EAA8B,CAAC,EACzB,CACN,GAAM,CAAE,SAAQ,UAAW,EACrB,EAAM,EAAa,KAAK,CAAI,EAAI,IAAI,IAAI,CAAI,EAAI,IAAI,IAAI,EAAQ,EAAS,EAAM,CAAM,CAAC,EAE5F,GAAI,EACK,IAAA,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAM,EACxC,GAAiC,MACjC,EAAI,aAAa,IAAI,EAAK,OAAO,CAAK,CAAC,EAInD,OAAO,EAAI,SAAS,CACxB,CAaA,SAAS,EAAQ,EAAiB,EAAc,EAAyB,CACrE,IAAM,EAAO,EAAa,KAAK,CAAO,EAChC,IAAI,IAAI,CAAO,EACf,IAAI,IAAI,EAAS,EAAc,CAAO,CAAC,EAEvC,EAAa,EAAK,QAAQ,GAAG,EAC7B,EAAU,IAAe,GAAK,EAAO,EAAK,MAAM,EAAG,CAAU,EAC7D,EAAQ,IAAe,GAAK,GAAK,EAAK,MAAM,CAAU,EAEtD,EAAW,CAAC,GAAG,EAAS,EAAK,QAAQ,EAAG,GAAG,EAAS,GAAU,EAAE,CAAC,EACjE,EAAY,EAAS,CAAO,EAC5B,EAAS,EAAmB,EAAW,CAAQ,EAC/C,EACA,CAAC,GAAG,EAAU,GAAG,CAAS,EAE1B,EAAW,EAAQ,SAAS,GAAG,EAAI,IAAM,GACzC,EAAW,EAAO,OAAS,EAAI,IAAI,EAAO,KAAK,GAAG,IAAI,IAAa,IACzE,MAAO,GAAG,EAAK,SAAS,IAAW,GACvC"}
@@ -0,0 +1,26 @@
1
+ //#region src/http/build-url.ts
2
+ var e = /^[a-z][a-z\d+\-.]*:/i;
3
+ function t(e) {
4
+ return e.split("/").filter(Boolean);
5
+ }
6
+ function n(e, t) {
7
+ return t.length === 0 || t.length > e.length ? !1 : t.every((t, n) => e[n] === t);
8
+ }
9
+ function r(e) {
10
+ let t = globalThis.location?.origin;
11
+ if (!t) throw TypeError(`createApiClient: baseURL "${e}" is relative and there is no location to resolve it against. Pass an absolute URL (https://api.example.com) outside the browser.`);
12
+ return t;
13
+ }
14
+ function i(t, n, r = {}) {
15
+ let { prefix: i, params: o } = r, s = e.test(n) ? new URL(n) : new URL(a(t, n, i));
16
+ if (o) for (let [e, t] of Object.entries(o)) t != null && s.searchParams.set(e, String(t));
17
+ return s.toString();
18
+ }
19
+ function a(i, a, o) {
20
+ let s = e.test(i) ? new URL(i) : new URL(i, r(i)), c = a.indexOf("?"), l = c === -1 ? a : a.slice(0, c), u = c === -1 ? "" : a.slice(c), d = [...t(s.pathname), ...t(o ?? "")], f = t(l), p = n(f, d) ? f : [...d, ...f], m = l.endsWith("/") ? "/" : "", h = p.length > 0 ? `/${p.join("/")}${m}` : "/";
21
+ return `${s.origin}${h}${u}`;
22
+ }
23
+ //#endregion
24
+ export { i as buildApiUrl };
25
+
26
+ //# sourceMappingURL=build-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-url.js","names":[],"sources":["../../src/http/build-url.ts"],"sourcesContent":["/**\n * Matches a path that already carries its own scheme (`https:`, `blob:`), which\n * makes it an absolute URL rather than something to resolve against the base.\n */\nconst ABSOLUTE_URL = /^[a-z][a-z\\d+\\-.]*:/i;\n\n/** Split a path into its non-empty segments, dropping surrounding slashes. */\nfunction segments(path: string): string[] {\n return path.split(\"/\").filter(Boolean);\n}\n\n/**\n * Whether `path` already starts with every segment of `prefix`.\n *\n * Compared segment by segment, so a `/api` prefix is not considered present in\n * `/api-keys` — a plain `startsWith` would swallow the resource.\n *\n * @param path - Segments of the request path.\n * @param prefix - Segments of the base path.\n * @returns Whether the prefix is already applied.\n */\nfunction startsWithSegments(path: string[], prefix: string[]): boolean {\n if (prefix.length === 0 || prefix.length > path.length) return false;\n return prefix.every((segment, index) => path[index] === segment);\n}\n\n/**\n * Resolve the origin a relative `baseURL` hangs off.\n *\n * A base such as `\"/api\"` is the shape you get behind a dev-server proxy or a\n * reverse proxy that serves app and API from one host. It only means something\n * in a browsing context, so this throws with the config to fix rather than\n * letting `new URL` report `Invalid base URL` from three frames deeper.\n *\n * @param baseURL - The base as the caller wrote it.\n * @returns The origin to resolve against.\n * @throws When there is no `location` to borrow an origin from.\n */\nfunction currentOrigin(baseURL: string): string {\n const origin = globalThis.location?.origin;\n if (!origin) {\n throw new TypeError(\n `createApiClient: baseURL \"${baseURL}\" is relative and there is no location to resolve it against. Pass an absolute URL (https://api.example.com) outside the browser.`,\n );\n }\n return origin;\n}\n\n/** Options accepted by {@link buildApiUrl}. */\nexport interface BuildApiUrlOptions {\n /**\n * Path segment every request is nested under, such as `\"/api\"`. Joined\n * after the path the `baseURL` already carries.\n */\n prefix?: string;\n /** Query params to append. `undefined` and `null` values are skipped. */\n params?: Record<string, string | number | boolean | undefined | null>;\n}\n\n/**\n * Join a base URL, an optional prefix and a request path into an absolute URL.\n *\n * `new URL(path, base)` on its own is wrong for an API client. It follows the\n * URL spec, where a path starting with `/` is absolute against the *origin* and\n * therefore discards whatever path the base carried: a client on\n * `https://api.example.com/api` asked for `/auth/login` reaches\n * `https://api.example.com/auth/login`, and every request 404s with nothing in\n * the config that looks wrong. This function resolves the path against the base\n * *path* instead, so the leading slash is a matter of taste rather than a\n * silent 404, and `baseURL` + `prefix` are interchangeable ways to say the same\n * thing.\n *\n * The prefix is applied at most once: a path that already opens with it — say\n * `\"/api/auth/login\"` under a `\"/api\"` prefix — is left alone, so a codebase\n * migrating to `prefix` can move its call sites one at a time. The check is per\n * segment, so `/api-keys` is not mistaken for an already-prefixed path.\n *\n * A path that is itself an absolute URL wins over all of this, which is how a\n * client reaches a second host (a signed upload endpoint, a CDN) without a\n * second client.\n *\n * @example\n * buildApiUrl(\"https://api.example.com\", \"/auth/login\", { prefix: \"/api\" });\n * // \"https://api.example.com/api/auth/login\"\n *\n * buildApiUrl(\"https://api.example.com/api\", \"auth/login\");\n * // \"https://api.example.com/api/auth/login\"\n *\n * @param baseURL - Absolute base URL, or a path relative to the current origin.\n * @param path - The request path, or an absolute URL to use as-is.\n * @param options - Optional prefix and query params.\n * @returns The absolute URL to fetch.\n * @throws When `baseURL` is relative and there is no `location` to resolve it.\n */\nexport function buildApiUrl(\n baseURL: string,\n path: string,\n options: BuildApiUrlOptions = {},\n): string {\n const { prefix, params } = options;\n const url = ABSOLUTE_URL.test(path) ? new URL(path) : new URL(resolve(baseURL, path, prefix));\n\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n return url.toString();\n}\n\n/**\n * Build the absolute URL string for a path that is not already absolute.\n *\n * Kept separate from {@link buildApiUrl} so the query-param loop is not nested\n * inside the joining rules.\n *\n * @param baseURL - Absolute base URL, or a path relative to the current origin.\n * @param path - The request path.\n * @param prefix - Optional segment every request is nested under.\n * @returns The joined absolute URL.\n */\nfunction resolve(baseURL: string, path: string, prefix?: string): string {\n const base = ABSOLUTE_URL.test(baseURL)\n ? new URL(baseURL)\n : new URL(baseURL, currentOrigin(baseURL));\n\n const queryStart = path.indexOf(\"?\");\n const rawPath = queryStart === -1 ? path : path.slice(0, queryStart);\n const query = queryStart === -1 ? \"\" : path.slice(queryStart);\n\n const basePath = [...segments(base.pathname), ...segments(prefix ?? \"\")];\n const requested = segments(rawPath);\n const joined = startsWithSegments(requested, basePath)\n ? requested\n : [...basePath, ...requested];\n\n const trailing = rawPath.endsWith(\"/\") ? \"/\" : \"\";\n const pathname = joined.length > 0 ? `/${joined.join(\"/\")}${trailing}` : \"/\";\n return `${base.origin}${pathname}${query}`;\n}\n"],"mappings":";AAIA,IAAM,IAAe;AAGrB,SAAS,EAAS,GAAwB;CACtC,OAAO,EAAK,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO;AACzC;AAYA,SAAS,EAAmB,GAAgB,GAA2B;CAEnE,OADI,EAAO,WAAW,KAAK,EAAO,SAAS,EAAK,SAAe,KACxD,EAAO,OAAO,GAAS,MAAU,EAAK,OAAW,CAAO;AACnE;AAcA,SAAS,EAAc,GAAyB;CAC5C,IAAM,IAAS,WAAW,UAAU;CACpC,IAAI,CAAC,GACD,MAAU,UACN,6BAA6B,EAAQ,kIACzC;CAEJ,OAAO;AACX;AAgDA,SAAgB,EACZ,GACA,GACA,IAA8B,CAAC,GACzB;CACN,IAAM,EAAE,WAAQ,cAAW,GACrB,IAAM,EAAa,KAAK,CAAI,IAAI,IAAI,IAAI,CAAI,IAAI,IAAI,IAAI,EAAQ,GAAS,GAAM,CAAM,CAAC;CAE5F,IAAI,GACK,KAAA,IAAM,CAAC,GAAK,MAAU,OAAO,QAAQ,CAAM,GAC5C,AAAI,KAAiC,QACjC,EAAI,aAAa,IAAI,GAAK,OAAO,CAAK,CAAC;CAInD,OAAO,EAAI,SAAS;AACxB;AAaA,SAAS,EAAQ,GAAiB,GAAc,GAAyB;CACrE,IAAM,IAAO,EAAa,KAAK,CAAO,IAChC,IAAI,IAAI,CAAO,IACf,IAAI,IAAI,GAAS,EAAc,CAAO,CAAC,GAEvC,IAAa,EAAK,QAAQ,GAAG,GAC7B,IAAU,MAAe,KAAK,IAAO,EAAK,MAAM,GAAG,CAAU,GAC7D,IAAQ,MAAe,KAAK,KAAK,EAAK,MAAM,CAAU,GAEtD,IAAW,CAAC,GAAG,EAAS,EAAK,QAAQ,GAAG,GAAG,EAAS,KAAU,EAAE,CAAC,GACjE,IAAY,EAAS,CAAO,GAC5B,IAAS,EAAmB,GAAW,CAAQ,IAC/C,IACA,CAAC,GAAG,GAAU,GAAG,CAAS,GAE1B,IAAW,EAAQ,SAAS,GAAG,IAAI,MAAM,IACzC,IAAW,EAAO,SAAS,IAAI,IAAI,EAAO,KAAK,GAAG,IAAI,MAAa;CACzE,OAAO,GAAG,EAAK,SAAS,IAAW;AACvC"}
@@ -0,0 +1,2 @@
1
+ const e=require("./errors.cjs");var t={offline:`Sem conexão com o servidor. Verifique sua internet e tente de novo.`},n=`tempest.error.offline`;function r(e){return`Erro ${e}`}function i(){return typeof navigator<`u`&&navigator.onLine===!1}function a(n,a,o){let s=o?.offline??t.offline;if(e.isApiError(n)){if(n.status===0)return s;let e=n.detail.trim();return e!==``&&e!==r(n.status)?e:`${a} (HTTP ${n.status})`}return i()?s:a}exports.API_ERROR_OFFLINE_KEY=n,exports.DEFAULT_API_ERROR_STRINGS=t,exports.describeApiError=a;
2
+ //# sourceMappingURL=describe-api-error.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"describe-api-error.cjs","names":[],"sources":["../../src/http/describe-api-error.ts"],"sourcesContent":["// The last mile of error handling: a typed error is what code reads, a sentence\n// is what a person reads, and every app was writing the funnel between the two.\n// The case everyone forgets is the request that never reached the server, which\n// without special handling renders as the nonsense \"erro 0\".\n\nimport { isApiError } from \"./errors\";\n\n/** The two fixed sentences {@link describeApiError} may need. */\nexport interface ApiErrorStrings {\n /** Shown when the request never reached the server. */\n offline: string;\n}\n\n/**\n * PT-BR defaults, used when no strings are supplied and no catalog answers.\n *\n * The SDK's copy is pt-BR everywhere else (`FilterBar`, `DataTable`, `Chat`), so\n * the default here matches rather than introducing an English string that only\n * shows up on a network failure.\n */\nexport const DEFAULT_API_ERROR_STRINGS: ApiErrorStrings = {\n offline: \"Sem conexão com o servidor. Verifique sua internet e tente de novo.\",\n};\n\n/**\n * Translation key the {@link useDescribeApiError} hook looks up.\n *\n * A catalog that does not define it falls back to\n * {@link DEFAULT_API_ERROR_STRINGS}, because `t` returns the key itself when the\n * lookup misses and printing `tempest.error.offline` at the user would be worse\n * than printing pt-BR at them.\n */\nexport const API_ERROR_OFFLINE_KEY = \"tempest.error.offline\";\n\n/**\n * Detail text `buildApiError` synthesises when the response body carries none.\n *\n * Ported from `buildApiError` in `errors.ts`, which falls back to `Erro ${status}`.\n * Recognising it here is what lets the caller's `fallback` win: \"Erro 500\" says\n * strictly less than \"Não foi possível carregar os pedidos\".\n *\n * @param status - The HTTP status of the error.\n * @returns The synthetic detail for that status.\n */\nfunction syntheticDetail(status: number): string {\n return `Erro ${status}`;\n}\n\n/**\n * Whether the browser currently reports itself as offline.\n *\n * `fetch` rejects a network failure with a plain `TypeError` whose message\n * differs per browser (\"Failed to fetch\", \"NetworkError when attempting to fetch\n * resource.\", \"Load failed\"), so sniffing the message is not portable. The online\n * flag is, and it is the signal that matters for the sentence being chosen.\n *\n * @returns `true` only when the environment positively says it is offline.\n */\nfunction browserIsOffline(): boolean {\n return typeof navigator !== \"undefined\" && navigator.onLine === false;\n}\n\n/**\n * Turn any caught value into a sentence worth showing.\n *\n * The funnel, in order:\n *\n * 1. A request that never reached the server — `status === 0`, or a non-API\n * error thrown while the browser reports itself offline — produces the\n * offline sentence. This is the step apps skip, and skipping it renders\n * \"erro 0\" or a raw `TypeError` at the user.\n * 2. The backend's own `detail`, which is the most specific thing available and\n * is already written for a person.\n * 3. `fallback`, with `(HTTP <status>)` appended when a status is known, so the\n * screenshot in the support ticket carries the one fact a developer needs.\n *\n * Pure on purpose: it works in an interceptor, in a logger and anywhere outside\n * the React tree. {@link useDescribeApiError} is the same funnel with the\n * sentences resolved through `I18nProvider`.\n *\n * @example\n * catch (error) {\n * toast(describeApiError(error, \"Não foi possível salvar o pedido\"));\n * }\n *\n * @param error - The caught value, of any shape.\n * @param fallback - What to say when the error carries nothing better.\n * @param strings - Overrides for the fixed sentences.\n * @returns A sentence to show the user.\n */\nexport function describeApiError(\n error: unknown,\n fallback: string,\n strings?: Partial<ApiErrorStrings>,\n): string {\n const offline = strings?.offline ?? DEFAULT_API_ERROR_STRINGS.offline;\n\n if (isApiError(error)) {\n if (error.status === 0) return offline;\n const detail = error.detail.trim();\n if (detail !== \"\" && detail !== syntheticDetail(error.status)) return detail;\n return `${fallback} (HTTP ${error.status})`;\n }\n\n if (browserIsOffline()) return offline;\n\n return fallback;\n}\n"],"mappings":"gCAoBA,IAAa,EAA6C,CACtD,QAAS,qEACb,EAUa,EAAwB,wBAYrC,SAAS,EAAgB,EAAwB,CAC7C,MAAO,QAAQ,GACnB,CAYA,SAAS,GAA4B,CACjC,OAAO,OAAO,UAAc,KAAe,UAAU,SAAW,EACpE,CA8BA,SAAgB,EACZ,EACA,EACA,EACM,CACN,IAAM,EAAU,GAAS,SAAW,EAA0B,QAE9D,GAAI,EAAA,WAAW,CAAK,EAAG,CACnB,GAAI,EAAM,SAAW,EAAG,OAAO,EAC/B,IAAM,EAAS,EAAM,OAAO,KAAK,EAEjC,OADI,IAAW,IAAM,IAAW,EAAgB,EAAM,MAAM,EAAU,EAC/D,GAAG,EAAS,SAAS,EAAM,OAAO,EAC7C,CAIA,OAFI,EAAiB,EAAU,EAExB,CACX"}
@@ -0,0 +1,22 @@
1
+ import { isApiError as e } from "./errors.js";
2
+ //#region src/http/describe-api-error.ts
3
+ var t = { offline: "Sem conexão com o servidor. Verifique sua internet e tente de novo." }, n = "tempest.error.offline";
4
+ function r(e) {
5
+ return `Erro ${e}`;
6
+ }
7
+ function i() {
8
+ return typeof navigator < "u" && navigator.onLine === !1;
9
+ }
10
+ function a(n, a, o) {
11
+ let s = o?.offline ?? t.offline;
12
+ if (e(n)) {
13
+ if (n.status === 0) return s;
14
+ let e = n.detail.trim();
15
+ return e !== "" && e !== r(n.status) ? e : `${a} (HTTP ${n.status})`;
16
+ }
17
+ return i() ? s : a;
18
+ }
19
+ //#endregion
20
+ export { n as API_ERROR_OFFLINE_KEY, t as DEFAULT_API_ERROR_STRINGS, a as describeApiError };
21
+
22
+ //# sourceMappingURL=describe-api-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"describe-api-error.js","names":[],"sources":["../../src/http/describe-api-error.ts"],"sourcesContent":["// The last mile of error handling: a typed error is what code reads, a sentence\n// is what a person reads, and every app was writing the funnel between the two.\n// The case everyone forgets is the request that never reached the server, which\n// without special handling renders as the nonsense \"erro 0\".\n\nimport { isApiError } from \"./errors\";\n\n/** The two fixed sentences {@link describeApiError} may need. */\nexport interface ApiErrorStrings {\n /** Shown when the request never reached the server. */\n offline: string;\n}\n\n/**\n * PT-BR defaults, used when no strings are supplied and no catalog answers.\n *\n * The SDK's copy is pt-BR everywhere else (`FilterBar`, `DataTable`, `Chat`), so\n * the default here matches rather than introducing an English string that only\n * shows up on a network failure.\n */\nexport const DEFAULT_API_ERROR_STRINGS: ApiErrorStrings = {\n offline: \"Sem conexão com o servidor. Verifique sua internet e tente de novo.\",\n};\n\n/**\n * Translation key the {@link useDescribeApiError} hook looks up.\n *\n * A catalog that does not define it falls back to\n * {@link DEFAULT_API_ERROR_STRINGS}, because `t` returns the key itself when the\n * lookup misses and printing `tempest.error.offline` at the user would be worse\n * than printing pt-BR at them.\n */\nexport const API_ERROR_OFFLINE_KEY = \"tempest.error.offline\";\n\n/**\n * Detail text `buildApiError` synthesises when the response body carries none.\n *\n * Ported from `buildApiError` in `errors.ts`, which falls back to `Erro ${status}`.\n * Recognising it here is what lets the caller's `fallback` win: \"Erro 500\" says\n * strictly less than \"Não foi possível carregar os pedidos\".\n *\n * @param status - The HTTP status of the error.\n * @returns The synthetic detail for that status.\n */\nfunction syntheticDetail(status: number): string {\n return `Erro ${status}`;\n}\n\n/**\n * Whether the browser currently reports itself as offline.\n *\n * `fetch` rejects a network failure with a plain `TypeError` whose message\n * differs per browser (\"Failed to fetch\", \"NetworkError when attempting to fetch\n * resource.\", \"Load failed\"), so sniffing the message is not portable. The online\n * flag is, and it is the signal that matters for the sentence being chosen.\n *\n * @returns `true` only when the environment positively says it is offline.\n */\nfunction browserIsOffline(): boolean {\n return typeof navigator !== \"undefined\" && navigator.onLine === false;\n}\n\n/**\n * Turn any caught value into a sentence worth showing.\n *\n * The funnel, in order:\n *\n * 1. A request that never reached the server — `status === 0`, or a non-API\n * error thrown while the browser reports itself offline — produces the\n * offline sentence. This is the step apps skip, and skipping it renders\n * \"erro 0\" or a raw `TypeError` at the user.\n * 2. The backend's own `detail`, which is the most specific thing available and\n * is already written for a person.\n * 3. `fallback`, with `(HTTP <status>)` appended when a status is known, so the\n * screenshot in the support ticket carries the one fact a developer needs.\n *\n * Pure on purpose: it works in an interceptor, in a logger and anywhere outside\n * the React tree. {@link useDescribeApiError} is the same funnel with the\n * sentences resolved through `I18nProvider`.\n *\n * @example\n * catch (error) {\n * toast(describeApiError(error, \"Não foi possível salvar o pedido\"));\n * }\n *\n * @param error - The caught value, of any shape.\n * @param fallback - What to say when the error carries nothing better.\n * @param strings - Overrides for the fixed sentences.\n * @returns A sentence to show the user.\n */\nexport function describeApiError(\n error: unknown,\n fallback: string,\n strings?: Partial<ApiErrorStrings>,\n): string {\n const offline = strings?.offline ?? DEFAULT_API_ERROR_STRINGS.offline;\n\n if (isApiError(error)) {\n if (error.status === 0) return offline;\n const detail = error.detail.trim();\n if (detail !== \"\" && detail !== syntheticDetail(error.status)) return detail;\n return `${fallback} (HTTP ${error.status})`;\n }\n\n if (browserIsOffline()) return offline;\n\n return fallback;\n}\n"],"mappings":";;AAoBA,IAAa,IAA6C,EACtD,SAAS,sEACb,GAUa,IAAwB;AAYrC,SAAS,EAAgB,GAAwB;CAC7C,OAAO,QAAQ;AACnB;AAYA,SAAS,IAA4B;CACjC,OAAO,OAAO,YAAc,OAAe,UAAU,WAAW;AACpE;AA8BA,SAAgB,EACZ,GACA,GACA,GACM;CACN,IAAM,IAAU,GAAS,WAAW,EAA0B;CAE9D,IAAI,EAAW,CAAK,GAAG;EACnB,IAAI,EAAM,WAAW,GAAG,OAAO;EAC/B,IAAM,IAAS,EAAM,OAAO,KAAK;EAEjC,OADI,MAAW,MAAM,MAAW,EAAgB,EAAM,MAAM,IAAU,IAC/D,GAAG,EAAS,SAAS,EAAM,OAAO;CAC7C;CAIA,OAFI,EAAiB,IAAU,IAExB;AACX"}
@@ -0,0 +1,2 @@
1
+ const e=require("./describe-api-error.cjs"),t=require("../i18n/I18nProvider.cjs");let n=require("react");function r(){let r=t.useOptionalI18n()?.t;return(0,n.useCallback)((t,n)=>{let i=r?.(e.API_ERROR_OFFLINE_KEY),a=i&&i!==`tempest.error.offline`?i:e.DEFAULT_API_ERROR_STRINGS.offline;return e.describeApiError(t,n,{offline:a})},[r])}exports.useDescribeApiError=r;
2
+ //# sourceMappingURL=use-describe-api-error.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-describe-api-error.cjs","names":[],"sources":["../../src/http/use-describe-api-error.ts"],"sourcesContent":["import { useCallback } from \"react\";\n\nimport { useOptionalI18n } from \"../i18n/I18nProvider\";\nimport {\n API_ERROR_OFFLINE_KEY,\n DEFAULT_API_ERROR_STRINGS,\n describeApiError,\n} from \"./describe-api-error\";\n\n/**\n * {@link describeApiError} with its fixed sentences resolved through the active\n * `I18nProvider`.\n *\n * The funnel is not duplicated — this hook only supplies the strings and calls\n * the pure function. Which is also why both exist: the pure one runs in an\n * interceptor or a logger, where there is no React tree to read a context from,\n * and this one runs in a component without every caller passing translations\n * down by hand.\n *\n * Works with no provider at all: i18n is opt-in in this SDK, so a missing\n * provider — or a catalog that never defined `tempest.error.offline` — falls\n * back to the pt-BR default rather than crashing or printing the raw key.\n *\n * @example\n * const describe = useDescribeApiError();\n * const { mutate } = useMutation({\n * mutationFn: save,\n * onError: (error) => toast(describe(error, t(\"orders.saveFailed\"))),\n * });\n *\n * @returns A stable `(error, fallback) => string` function.\n */\nexport function useDescribeApiError(): (error: unknown, fallback: string) => string {\n const i18n = useOptionalI18n();\n const translate = i18n?.t;\n\n return useCallback(\n (error: unknown, fallback: string) => {\n const translated = translate?.(API_ERROR_OFFLINE_KEY);\n const offline =\n translated && translated !== API_ERROR_OFFLINE_KEY\n ? translated\n : DEFAULT_API_ERROR_STRINGS.offline;\n return describeApiError(error, fallback, { offline });\n },\n [translate],\n );\n}\n"],"mappings":"yGAgCA,SAAgB,GAAoE,CAEhF,IAAM,EADO,EAAA,gBACK,CAAA,EAAM,EAExB,OAAA,EAAO,EAAA,YAAA,EACF,EAAgB,IAAqB,CAClC,IAAM,EAAa,IAAY,EAAA,qBAAqB,EAC9C,EACF,GAAc,IAAA,wBACR,EACA,EAAA,0BAA0B,QACpC,OAAO,EAAA,iBAAiB,EAAO,EAAU,CAAE,SAAQ,CAAC,CACxD,EACA,CAAC,CAAS,CACd,CACJ"}
@@ -0,0 +1,15 @@
1
+ import { API_ERROR_OFFLINE_KEY as e, DEFAULT_API_ERROR_STRINGS as t, describeApiError as n } from "./describe-api-error.js";
2
+ import { useOptionalI18n as r } from "../i18n/I18nProvider.js";
3
+ import { useCallback as i } from "react";
4
+ //#region src/http/use-describe-api-error.ts
5
+ function a() {
6
+ let a = r()?.t;
7
+ return i((r, i) => {
8
+ let o = a?.(e), s = o && o !== "tempest.error.offline" ? o : t.offline;
9
+ return n(r, i, { offline: s });
10
+ }, [a]);
11
+ }
12
+ //#endregion
13
+ export { a as useDescribeApiError };
14
+
15
+ //# sourceMappingURL=use-describe-api-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-describe-api-error.js","names":[],"sources":["../../src/http/use-describe-api-error.ts"],"sourcesContent":["import { useCallback } from \"react\";\n\nimport { useOptionalI18n } from \"../i18n/I18nProvider\";\nimport {\n API_ERROR_OFFLINE_KEY,\n DEFAULT_API_ERROR_STRINGS,\n describeApiError,\n} from \"./describe-api-error\";\n\n/**\n * {@link describeApiError} with its fixed sentences resolved through the active\n * `I18nProvider`.\n *\n * The funnel is not duplicated — this hook only supplies the strings and calls\n * the pure function. Which is also why both exist: the pure one runs in an\n * interceptor or a logger, where there is no React tree to read a context from,\n * and this one runs in a component without every caller passing translations\n * down by hand.\n *\n * Works with no provider at all: i18n is opt-in in this SDK, so a missing\n * provider — or a catalog that never defined `tempest.error.offline` — falls\n * back to the pt-BR default rather than crashing or printing the raw key.\n *\n * @example\n * const describe = useDescribeApiError();\n * const { mutate } = useMutation({\n * mutationFn: save,\n * onError: (error) => toast(describe(error, t(\"orders.saveFailed\"))),\n * });\n *\n * @returns A stable `(error, fallback) => string` function.\n */\nexport function useDescribeApiError(): (error: unknown, fallback: string) => string {\n const i18n = useOptionalI18n();\n const translate = i18n?.t;\n\n return useCallback(\n (error: unknown, fallback: string) => {\n const translated = translate?.(API_ERROR_OFFLINE_KEY);\n const offline =\n translated && translated !== API_ERROR_OFFLINE_KEY\n ? translated\n : DEFAULT_API_ERROR_STRINGS.offline;\n return describeApiError(error, fallback, { offline });\n },\n [translate],\n );\n}\n"],"mappings":";;;;AAgCA,SAAgB,IAAoE;CAEhF,IAAM,IADO,EACK,CAAA,EAAM;CAExB,OAAO,GACF,GAAgB,MAAqB;EAClC,IAAM,IAAa,IAAY,CAAqB,GAC9C,IACF,KAAc,MAAA,0BACR,IACA,EAA0B;EACpC,OAAO,EAAiB,GAAO,GAAU,EAAE,WAAQ,CAAC;CACxD,GACA,CAAC,CAAS,CACd;AACJ"}
@@ -1,2 +1,2 @@
1
- const e=require("./create-i18n.cjs");let t=require("react"),n=require("react/jsx-runtime");var r=(0,t.createContext)(null);function i(e,t){if(!e||typeof window>`u`)return null;try{let n=window.localStorage.getItem(e);return n&&t.includes(n)?n:null}catch{return null}}function a({children:a,locale:o,fallbackLocale:s,messages:c,storageKey:l=`tempest-locale`}){let u=(0,t.useMemo)(()=>Object.keys(c),[c]),[d,f]=(0,t.useState)(()=>i(l,u)??o);(0,t.useEffect)(()=>{typeof document<`u`&&document.documentElement.setAttribute(`lang`,d)},[d]);let p=(0,t.useCallback)(e=>{if(f(e),l&&typeof window<`u`)try{window.localStorage.setItem(l,e)}catch{}},[l]),m=(0,t.useMemo)(()=>({...e.createI18n({locale:d,fallbackLocale:s,messages:c}),setLocale:p,availableLocales:u}),[d,s,c,p,u]);return(0,n.jsx)(r.Provider,{value:m,children:a})}function o(){let e=(0,t.useContext)(r);if(!e)throw Error(`useI18n must be used inside an <I18nProvider>`);return e}function s(){return o().t}exports.I18nProvider=a,exports.useI18n=o,exports.useTranslate=s;
1
+ const e=require("./create-i18n.cjs");let t=require("react"),n=require("react/jsx-runtime");var r=(0,t.createContext)(null);function i(e,t){if(!e||typeof window>`u`)return null;try{let n=window.localStorage.getItem(e);return n&&t.includes(n)?n:null}catch{return null}}function a({children:a,locale:o,fallbackLocale:s,messages:c,storageKey:l=`tempest-locale`}){let u=(0,t.useMemo)(()=>Object.keys(c),[c]),[d,f]=(0,t.useState)(()=>i(l,u)??o);(0,t.useEffect)(()=>{typeof document<`u`&&document.documentElement.setAttribute(`lang`,d)},[d]);let p=(0,t.useCallback)(e=>{if(f(e),l&&typeof window<`u`)try{window.localStorage.setItem(l,e)}catch{}},[l]),m=(0,t.useMemo)(()=>({...e.createI18n({locale:d,fallbackLocale:s,messages:c}),setLocale:p,availableLocales:u}),[d,s,c,p,u]);return(0,n.jsx)(r.Provider,{value:m,children:a})}function o(){let e=(0,t.useContext)(r);if(!e)throw Error(`useI18n must be used inside an <I18nProvider>`);return e}function s(){return(0,t.useContext)(r)}function c(){return o().t}exports.I18nProvider=a,exports.useI18n=o,exports.useOptionalI18n=s,exports.useTranslate=c;
2
2
  //# sourceMappingURL=I18nProvider.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"I18nProvider.cjs","names":[],"sources":["../../src/i18n/I18nProvider.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useEffect, useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createI18n, type Catalog, type I18n } from \"./create-i18n\";\n\nexport interface I18nContextValue extends I18n {\n /** Switch the active locale. Persisted when `storageKey` is set. */\n setLocale: (locale: string) => void;\n /** Locales available in the catalog. */\n availableLocales: string[];\n}\n\nconst I18nContext = createContext<I18nContextValue | null>(null);\n\nexport interface I18nProviderProps {\n children: ReactNode;\n /** Initial locale. */\n locale: string;\n /** Fallback locale used when a key is missing. */\n fallbackLocale?: string;\n /** Translation catalog. */\n messages: Catalog;\n /** localStorage key for persisting the user choice. Pass `null` to disable. Default: `\"tempest-locale\"`. */\n storageKey?: string | null;\n}\n\nfunction readStored(storageKey: string | null, available: string[]): string | null {\n if (!storageKey || typeof window === \"undefined\") return null;\n try {\n const value = window.localStorage.getItem(storageKey);\n if (value && available.includes(value)) return value;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * React provider for the SDK's lightweight i18n. Exposes the translation\n * helpers and a `setLocale` setter to children.\n *\n * @tempest-limits empty-catch — remembering the chosen locale is best-effort. The\n * switch itself is React state and already happened by the time the write runs, so\n * a storage failure costs the preference on the next load and nothing in this one.\n */\nexport function I18nProvider({\n children,\n locale: initialLocale,\n fallbackLocale,\n messages,\n storageKey = \"tempest-locale\",\n}: I18nProviderProps) {\n const availableLocales = useMemo(() => Object.keys(messages), [messages]);\n const [locale, setLocaleState] = useState<string>(\n () => readStored(storageKey, availableLocales) ?? initialLocale,\n );\n\n useEffect(() => {\n if (typeof document !== \"undefined\") {\n document.documentElement.setAttribute(\"lang\", locale);\n }\n }, [locale]);\n\n const setLocale = useCallback(\n (next: string) => {\n setLocaleState(next);\n if (storageKey && typeof window !== \"undefined\") {\n try {\n window.localStorage.setItem(storageKey, next);\n } catch {\n /* empty */\n }\n }\n },\n [storageKey],\n );\n\n const value = useMemo<I18nContextValue>(() => {\n const i18n = createI18n({ locale, fallbackLocale, messages });\n return { ...i18n, setLocale, availableLocales };\n }, [locale, fallbackLocale, messages, setLocale, availableLocales]);\n\n return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;\n}\n\n/**\n * Access translation helpers. Must be used inside an {@link I18nProvider}.\n */\nexport function useI18n(): I18nContextValue {\n const ctx = useContext(I18nContext);\n if (!ctx) throw new Error(\"useI18n must be used inside an <I18nProvider>\");\n return ctx;\n}\n\n/**\n * Shortcut for components that only need the `t` function — avoids destructuring.\n */\nexport function useTranslate(): I18nContextValue[\"t\"] {\n return useI18n().t;\n}\n"],"mappings":"2FAWA,IAAM,GAAA,EAAc,EAAA,cAAA,CAAuC,IAAI,EAc/D,SAAS,EAAW,EAA2B,EAAoC,CAC/E,GAAI,CAAC,GAAc,OAAO,OAAW,IAAa,OAAO,KACzD,GAAI,CACA,IAAM,EAAQ,OAAO,aAAa,QAAQ,CAAU,EAEpD,OADI,GAAS,EAAU,SAAS,CAAK,EAAU,EACxC,IACX,MAAQ,CACJ,OAAO,IACX,CACJ,CAUA,SAAgB,EAAa,CACzB,WACA,OAAQ,EACR,iBACA,WACA,aAAa,kBACK,CAClB,IAAM,GAAA,EAAmB,EAAA,QAAA,KAAc,OAAO,KAAK,CAAQ,EAAG,CAAC,CAAQ,CAAC,EAClE,CAAC,EAAQ,IAAA,EAAkB,EAAA,SAAA,KACvB,EAAW,EAAY,CAAgB,GAAK,CACtD,GAEA,EAAA,EAAA,UAAA,KAAgB,CACR,OAAO,SAAa,KACpB,SAAS,gBAAgB,aAAa,OAAQ,CAAM,CAE5D,EAAG,CAAC,CAAM,CAAC,EAEX,IAAM,GAAA,EAAY,EAAA,YAAA,CACb,GAAiB,CAEd,GADA,EAAe,CAAI,EACf,GAAc,OAAO,OAAW,IAChC,GAAI,CACA,OAAO,aAAa,QAAQ,EAAY,CAAI,CAChD,MAAQ,CAER,CAER,EACA,CAAC,CAAU,CACf,EAEM,GAAA,EAAQ,EAAA,QAAA,MAEH,CAAE,GADI,EAAA,WAAW,CAAE,SAAQ,iBAAgB,UAAS,CAC/C,EAAM,YAAW,kBAAiB,GAC/C,CAAC,EAAQ,EAAgB,EAAU,EAAW,CAAgB,CAAC,EAElE,OAAO,EAAA,EAAA,IAAA,CAAC,EAAY,SAAb,CAA6B,QAAQ,UAA+B,CAAA,CAC/E,CAKA,SAAgB,GAA4B,CACxC,IAAM,GAAA,EAAM,EAAA,WAAA,CAAW,CAAW,EAClC,GAAI,CAAC,EAAK,MAAU,MAAM,+CAA+C,EACzE,OAAO,CACX,CAKA,SAAgB,GAAsC,CAClD,OAAO,EAAQ,CAAC,CAAC,CACrB"}
1
+ {"version":3,"file":"I18nProvider.cjs","names":[],"sources":["../../src/i18n/I18nProvider.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useEffect, useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createI18n, type Catalog, type I18n } from \"./create-i18n\";\n\nexport interface I18nContextValue extends I18n {\n /** Switch the active locale. Persisted when `storageKey` is set. */\n setLocale: (locale: string) => void;\n /** Locales available in the catalog. */\n availableLocales: string[];\n}\n\nconst I18nContext = createContext<I18nContextValue | null>(null);\n\nexport interface I18nProviderProps {\n children: ReactNode;\n /** Initial locale. */\n locale: string;\n /** Fallback locale used when a key is missing. */\n fallbackLocale?: string;\n /** Translation catalog. */\n messages: Catalog;\n /** localStorage key for persisting the user choice. Pass `null` to disable. Default: `\"tempest-locale\"`. */\n storageKey?: string | null;\n}\n\nfunction readStored(storageKey: string | null, available: string[]): string | null {\n if (!storageKey || typeof window === \"undefined\") return null;\n try {\n const value = window.localStorage.getItem(storageKey);\n if (value && available.includes(value)) return value;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * React provider for the SDK's lightweight i18n. Exposes the translation\n * helpers and a `setLocale` setter to children.\n *\n * @tempest-limits empty-catch — remembering the chosen locale is best-effort. The\n * switch itself is React state and already happened by the time the write runs, so\n * a storage failure costs the preference on the next load and nothing in this one.\n */\nexport function I18nProvider({\n children,\n locale: initialLocale,\n fallbackLocale,\n messages,\n storageKey = \"tempest-locale\",\n}: I18nProviderProps) {\n const availableLocales = useMemo(() => Object.keys(messages), [messages]);\n const [locale, setLocaleState] = useState<string>(\n () => readStored(storageKey, availableLocales) ?? initialLocale,\n );\n\n useEffect(() => {\n if (typeof document !== \"undefined\") {\n document.documentElement.setAttribute(\"lang\", locale);\n }\n }, [locale]);\n\n const setLocale = useCallback(\n (next: string) => {\n setLocaleState(next);\n if (storageKey && typeof window !== \"undefined\") {\n try {\n window.localStorage.setItem(storageKey, next);\n } catch {\n /* empty */\n }\n }\n },\n [storageKey],\n );\n\n const value = useMemo<I18nContextValue>(() => {\n const i18n = createI18n({ locale, fallbackLocale, messages });\n return { ...i18n, setLocale, availableLocales };\n }, [locale, fallbackLocale, messages, setLocale, availableLocales]);\n\n return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;\n}\n\n/**\n * Access translation helpers. Must be used inside an {@link I18nProvider}.\n */\nexport function useI18n(): I18nContextValue {\n const ctx = useContext(I18nContext);\n if (!ctx) throw new Error(\"useI18n must be used inside an <I18nProvider>\");\n return ctx;\n}\n\n/**\n * Access translation helpers when there may be no {@link I18nProvider} above.\n *\n * Returns `null` instead of throwing, which is what a piece of the SDK needs\n * when it wants to be translated where a catalog exists and still work where one\n * does not — i18n is opt-in in this SDK, and a helper that demanded a provider\n * would turn \"you did not configure translations\" into a crash.\n *\n * @returns The context value, or `null` outside a provider.\n */\nexport function useOptionalI18n(): I18nContextValue | null {\n return useContext(I18nContext);\n}\n\n/**\n * Shortcut for components that only need the `t` function — avoids destructuring.\n */\nexport function useTranslate(): I18nContextValue[\"t\"] {\n return useI18n().t;\n}\n"],"mappings":"2FAWA,IAAM,GAAA,EAAc,EAAA,cAAA,CAAuC,IAAI,EAc/D,SAAS,EAAW,EAA2B,EAAoC,CAC/E,GAAI,CAAC,GAAc,OAAO,OAAW,IAAa,OAAO,KACzD,GAAI,CACA,IAAM,EAAQ,OAAO,aAAa,QAAQ,CAAU,EAEpD,OADI,GAAS,EAAU,SAAS,CAAK,EAAU,EACxC,IACX,MAAQ,CACJ,OAAO,IACX,CACJ,CAUA,SAAgB,EAAa,CACzB,WACA,OAAQ,EACR,iBACA,WACA,aAAa,kBACK,CAClB,IAAM,GAAA,EAAmB,EAAA,QAAA,KAAc,OAAO,KAAK,CAAQ,EAAG,CAAC,CAAQ,CAAC,EAClE,CAAC,EAAQ,IAAA,EAAkB,EAAA,SAAA,KACvB,EAAW,EAAY,CAAgB,GAAK,CACtD,GAEA,EAAA,EAAA,UAAA,KAAgB,CACR,OAAO,SAAa,KACpB,SAAS,gBAAgB,aAAa,OAAQ,CAAM,CAE5D,EAAG,CAAC,CAAM,CAAC,EAEX,IAAM,GAAA,EAAY,EAAA,YAAA,CACb,GAAiB,CAEd,GADA,EAAe,CAAI,EACf,GAAc,OAAO,OAAW,IAChC,GAAI,CACA,OAAO,aAAa,QAAQ,EAAY,CAAI,CAChD,MAAQ,CAER,CAER,EACA,CAAC,CAAU,CACf,EAEM,GAAA,EAAQ,EAAA,QAAA,MAEH,CAAE,GADI,EAAA,WAAW,CAAE,SAAQ,iBAAgB,UAAS,CAC/C,EAAM,YAAW,kBAAiB,GAC/C,CAAC,EAAQ,EAAgB,EAAU,EAAW,CAAgB,CAAC,EAElE,OAAO,EAAA,EAAA,IAAA,CAAC,EAAY,SAAb,CAA6B,QAAQ,UAA+B,CAAA,CAC/E,CAKA,SAAgB,GAA4B,CACxC,IAAM,GAAA,EAAM,EAAA,WAAA,CAAW,CAAW,EAClC,GAAI,CAAC,EAAK,MAAU,MAAM,+CAA+C,EACzE,OAAO,CACX,CAYA,SAAgB,GAA2C,CACvD,OAAA,EAAO,EAAA,WAAA,CAAW,CAAW,CACjC,CAKA,SAAgB,GAAsC,CAClD,OAAO,EAAQ,CAAC,CAAC,CACrB"}
@@ -47,9 +47,12 @@ function d() {
47
47
  return e;
48
48
  }
49
49
  function f() {
50
+ return r(c);
51
+ }
52
+ function p() {
50
53
  return d().t;
51
54
  }
52
55
  //#endregion
53
- export { u as I18nProvider, d as useI18n, f as useTranslate };
56
+ export { u as I18nProvider, d as useI18n, f as useOptionalI18n, p as useTranslate };
54
57
 
55
58
  //# sourceMappingURL=I18nProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"I18nProvider.js","names":[],"sources":["../../src/i18n/I18nProvider.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useEffect, useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createI18n, type Catalog, type I18n } from \"./create-i18n\";\n\nexport interface I18nContextValue extends I18n {\n /** Switch the active locale. Persisted when `storageKey` is set. */\n setLocale: (locale: string) => void;\n /** Locales available in the catalog. */\n availableLocales: string[];\n}\n\nconst I18nContext = createContext<I18nContextValue | null>(null);\n\nexport interface I18nProviderProps {\n children: ReactNode;\n /** Initial locale. */\n locale: string;\n /** Fallback locale used when a key is missing. */\n fallbackLocale?: string;\n /** Translation catalog. */\n messages: Catalog;\n /** localStorage key for persisting the user choice. Pass `null` to disable. Default: `\"tempest-locale\"`. */\n storageKey?: string | null;\n}\n\nfunction readStored(storageKey: string | null, available: string[]): string | null {\n if (!storageKey || typeof window === \"undefined\") return null;\n try {\n const value = window.localStorage.getItem(storageKey);\n if (value && available.includes(value)) return value;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * React provider for the SDK's lightweight i18n. Exposes the translation\n * helpers and a `setLocale` setter to children.\n *\n * @tempest-limits empty-catch — remembering the chosen locale is best-effort. The\n * switch itself is React state and already happened by the time the write runs, so\n * a storage failure costs the preference on the next load and nothing in this one.\n */\nexport function I18nProvider({\n children,\n locale: initialLocale,\n fallbackLocale,\n messages,\n storageKey = \"tempest-locale\",\n}: I18nProviderProps) {\n const availableLocales = useMemo(() => Object.keys(messages), [messages]);\n const [locale, setLocaleState] = useState<string>(\n () => readStored(storageKey, availableLocales) ?? initialLocale,\n );\n\n useEffect(() => {\n if (typeof document !== \"undefined\") {\n document.documentElement.setAttribute(\"lang\", locale);\n }\n }, [locale]);\n\n const setLocale = useCallback(\n (next: string) => {\n setLocaleState(next);\n if (storageKey && typeof window !== \"undefined\") {\n try {\n window.localStorage.setItem(storageKey, next);\n } catch {\n /* empty */\n }\n }\n },\n [storageKey],\n );\n\n const value = useMemo<I18nContextValue>(() => {\n const i18n = createI18n({ locale, fallbackLocale, messages });\n return { ...i18n, setLocale, availableLocales };\n }, [locale, fallbackLocale, messages, setLocale, availableLocales]);\n\n return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;\n}\n\n/**\n * Access translation helpers. Must be used inside an {@link I18nProvider}.\n */\nexport function useI18n(): I18nContextValue {\n const ctx = useContext(I18nContext);\n if (!ctx) throw new Error(\"useI18n must be used inside an <I18nProvider>\");\n return ctx;\n}\n\n/**\n * Shortcut for components that only need the `t` function — avoids destructuring.\n */\nexport function useTranslate(): I18nContextValue[\"t\"] {\n return useI18n().t;\n}\n"],"mappings":";;;;AAWA,IAAM,IAAc,EAAuC,IAAI;AAc/D,SAAS,EAAW,GAA2B,GAAoC;CAC/E,IAAI,CAAC,KAAc,OAAO,SAAW,KAAa,OAAO;CACzD,IAAI;EACA,IAAM,IAAQ,OAAO,aAAa,QAAQ,CAAU;EAEpD,OADI,KAAS,EAAU,SAAS,CAAK,IAAU,IACxC;CACX,QAAQ;EACJ,OAAO;CACX;AACJ;AAUA,SAAgB,EAAa,EACzB,aACA,QAAQ,GACR,mBACA,aACA,gBAAa,oBACK;CAClB,IAAM,IAAmB,QAAc,OAAO,KAAK,CAAQ,GAAG,CAAC,CAAQ,CAAC,GAClE,CAAC,GAAQ,KAAkB,QACvB,EAAW,GAAY,CAAgB,KAAK,CACtD;CAEA,QAAgB;EACZ,AAAI,OAAO,WAAa,OACpB,SAAS,gBAAgB,aAAa,QAAQ,CAAM;CAE5D,GAAG,CAAC,CAAM,CAAC;CAEX,IAAM,IAAY,GACb,MAAiB;EAEd,IADA,EAAe,CAAI,GACf,KAAc,OAAO,SAAW,KAChC,IAAI;GACA,OAAO,aAAa,QAAQ,GAAY,CAAI;EAChD,QAAQ,CAER;CAER,GACA,CAAC,CAAU,CACf,GAEM,IAAQ,SAEH;EAAE,GADI,EAAW;GAAE;GAAQ;GAAgB;EAAS,CAC/C;EAAM;EAAW;CAAiB,IAC/C;EAAC;EAAQ;EAAgB;EAAU;EAAW;CAAgB,CAAC;CAElE,OAAO,kBAAC,EAAY,UAAb;EAA6B;EAAQ;CAA+B,CAAA;AAC/E;AAKA,SAAgB,IAA4B;CACxC,IAAM,IAAM,EAAW,CAAW;CAClC,IAAI,CAAC,GAAK,MAAU,MAAM,+CAA+C;CACzE,OAAO;AACX;AAKA,SAAgB,IAAsC;CAClD,OAAO,EAAQ,CAAC,CAAC;AACrB"}
1
+ {"version":3,"file":"I18nProvider.js","names":[],"sources":["../../src/i18n/I18nProvider.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useEffect, useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createI18n, type Catalog, type I18n } from \"./create-i18n\";\n\nexport interface I18nContextValue extends I18n {\n /** Switch the active locale. Persisted when `storageKey` is set. */\n setLocale: (locale: string) => void;\n /** Locales available in the catalog. */\n availableLocales: string[];\n}\n\nconst I18nContext = createContext<I18nContextValue | null>(null);\n\nexport interface I18nProviderProps {\n children: ReactNode;\n /** Initial locale. */\n locale: string;\n /** Fallback locale used when a key is missing. */\n fallbackLocale?: string;\n /** Translation catalog. */\n messages: Catalog;\n /** localStorage key for persisting the user choice. Pass `null` to disable. Default: `\"tempest-locale\"`. */\n storageKey?: string | null;\n}\n\nfunction readStored(storageKey: string | null, available: string[]): string | null {\n if (!storageKey || typeof window === \"undefined\") return null;\n try {\n const value = window.localStorage.getItem(storageKey);\n if (value && available.includes(value)) return value;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * React provider for the SDK's lightweight i18n. Exposes the translation\n * helpers and a `setLocale` setter to children.\n *\n * @tempest-limits empty-catch — remembering the chosen locale is best-effort. The\n * switch itself is React state and already happened by the time the write runs, so\n * a storage failure costs the preference on the next load and nothing in this one.\n */\nexport function I18nProvider({\n children,\n locale: initialLocale,\n fallbackLocale,\n messages,\n storageKey = \"tempest-locale\",\n}: I18nProviderProps) {\n const availableLocales = useMemo(() => Object.keys(messages), [messages]);\n const [locale, setLocaleState] = useState<string>(\n () => readStored(storageKey, availableLocales) ?? initialLocale,\n );\n\n useEffect(() => {\n if (typeof document !== \"undefined\") {\n document.documentElement.setAttribute(\"lang\", locale);\n }\n }, [locale]);\n\n const setLocale = useCallback(\n (next: string) => {\n setLocaleState(next);\n if (storageKey && typeof window !== \"undefined\") {\n try {\n window.localStorage.setItem(storageKey, next);\n } catch {\n /* empty */\n }\n }\n },\n [storageKey],\n );\n\n const value = useMemo<I18nContextValue>(() => {\n const i18n = createI18n({ locale, fallbackLocale, messages });\n return { ...i18n, setLocale, availableLocales };\n }, [locale, fallbackLocale, messages, setLocale, availableLocales]);\n\n return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;\n}\n\n/**\n * Access translation helpers. Must be used inside an {@link I18nProvider}.\n */\nexport function useI18n(): I18nContextValue {\n const ctx = useContext(I18nContext);\n if (!ctx) throw new Error(\"useI18n must be used inside an <I18nProvider>\");\n return ctx;\n}\n\n/**\n * Access translation helpers when there may be no {@link I18nProvider} above.\n *\n * Returns `null` instead of throwing, which is what a piece of the SDK needs\n * when it wants to be translated where a catalog exists and still work where one\n * does not — i18n is opt-in in this SDK, and a helper that demanded a provider\n * would turn \"you did not configure translations\" into a crash.\n *\n * @returns The context value, or `null` outside a provider.\n */\nexport function useOptionalI18n(): I18nContextValue | null {\n return useContext(I18nContext);\n}\n\n/**\n * Shortcut for components that only need the `t` function — avoids destructuring.\n */\nexport function useTranslate(): I18nContextValue[\"t\"] {\n return useI18n().t;\n}\n"],"mappings":";;;;AAWA,IAAM,IAAc,EAAuC,IAAI;AAc/D,SAAS,EAAW,GAA2B,GAAoC;CAC/E,IAAI,CAAC,KAAc,OAAO,SAAW,KAAa,OAAO;CACzD,IAAI;EACA,IAAM,IAAQ,OAAO,aAAa,QAAQ,CAAU;EAEpD,OADI,KAAS,EAAU,SAAS,CAAK,IAAU,IACxC;CACX,QAAQ;EACJ,OAAO;CACX;AACJ;AAUA,SAAgB,EAAa,EACzB,aACA,QAAQ,GACR,mBACA,aACA,gBAAa,oBACK;CAClB,IAAM,IAAmB,QAAc,OAAO,KAAK,CAAQ,GAAG,CAAC,CAAQ,CAAC,GAClE,CAAC,GAAQ,KAAkB,QACvB,EAAW,GAAY,CAAgB,KAAK,CACtD;CAEA,QAAgB;EACZ,AAAI,OAAO,WAAa,OACpB,SAAS,gBAAgB,aAAa,QAAQ,CAAM;CAE5D,GAAG,CAAC,CAAM,CAAC;CAEX,IAAM,IAAY,GACb,MAAiB;EAEd,IADA,EAAe,CAAI,GACf,KAAc,OAAO,SAAW,KAChC,IAAI;GACA,OAAO,aAAa,QAAQ,GAAY,CAAI;EAChD,QAAQ,CAER;CAER,GACA,CAAC,CAAU,CACf,GAEM,IAAQ,SAEH;EAAE,GADI,EAAW;GAAE;GAAQ;GAAgB;EAAS,CAC/C;EAAM;EAAW;CAAiB,IAC/C;EAAC;EAAQ;EAAgB;EAAU;EAAW;CAAgB,CAAC;CAElE,OAAO,kBAAC,EAAY,UAAb;EAA6B;EAAQ;CAA+B,CAAA;AAC/E;AAKA,SAAgB,IAA4B;CACxC,IAAM,IAAM,EAAW,CAAW;CAClC,IAAI,CAAC,GAAK,MAAU,MAAM,+CAA+C;CACzE,OAAO;AACX;AAYA,SAAgB,IAA2C;CACvD,OAAO,EAAW,CAAW;AACjC;AAKA,SAAgB,IAAsC;CAClD,OAAO,EAAQ,CAAC,CAAC;AACrB"}
@@ -1,2 +1,2 @@
1
- const e=require("./constants.cjs");let t=require("react"),n=require("react/jsx-runtime"),r=require("@tanstack/react-query");function i({children:i,client:a,defaultOptions:o}){let[s]=(0,t.useState)(()=>a??new r.QueryClient({defaultOptions:{queries:{staleTime:e.STALE_TIME.DEFAULT,gcTime:e.CACHE_TIME.DEFAULT,retry:1,refetchOnWindowFocus:!1,...o?.queries??{}},mutations:{retry:0,...o?.mutations??{}}}}));return(0,n.jsx)(r.QueryClientProvider,{client:s,children:i})}exports.QueryProvider=i;
1
+ const e=require("./constants.cjs"),t=require("./retry-policy.cjs");let n=require("react"),r=require("react/jsx-runtime"),i=require("@tanstack/react-query");function a({children:a,client:o,defaultOptions:s}){let[c]=(0,n.useState)(()=>o??new i.QueryClient({defaultOptions:{queries:{staleTime:e.STALE_TIME.DEFAULT,gcTime:e.CACHE_TIME.DEFAULT,retry:t.shouldRetryQuery,refetchOnWindowFocus:!1,...s?.queries??{}},mutations:{retry:0,...s?.mutations??{}}}}));return(0,r.jsx)(i.QueryClientProvider,{client:c,children:a})}exports.QueryProvider=a;
2
2
  //# sourceMappingURL=QueryProvider.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"QueryProvider.cjs","names":[],"sources":["../../src/query/QueryProvider.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport type { DefaultOptions } from \"@tanstack/react-query\";\nimport { STALE_TIME, CACHE_TIME } from \"./constants\";\n\nexport interface QueryProviderProps {\n children: ReactNode;\n /** Pass an existing client to share it across roots. */\n client?: QueryClient;\n /** Overrides for the default options applied when no `client` is provided. */\n defaultOptions?: DefaultOptions;\n}\n\n/**\n * Wrapper around `QueryClientProvider` that bootstraps a `QueryClient` with\n * sane SDK defaults (5-minute stale time, 30-minute gc time, 1 retry).\n */\nexport function QueryProvider({ children, client, defaultOptions }: QueryProviderProps) {\n const [internalClient] = useState<QueryClient>(\n () =>\n client ??\n new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: STALE_TIME.DEFAULT,\n gcTime: CACHE_TIME.DEFAULT,\n retry: 1,\n refetchOnWindowFocus: false,\n ...(defaultOptions?.queries ?? {}),\n },\n mutations: {\n retry: 0,\n ...(defaultOptions?.mutations ?? {}),\n },\n },\n }),\n );\n\n return <QueryClientProvider client={internalClient}>{children}</QueryClientProvider>;\n}\n"],"mappings":"4HAkBA,SAAgB,EAAc,CAAE,WAAU,SAAQ,kBAAsC,CACpF,GAAM,CAAC,IAAA,EAAkB,EAAA,SAAA,KAEjB,GACA,IAAI,EAAA,YAAY,CACZ,eAAgB,CACZ,QAAS,CACL,UAAW,EAAA,WAAW,QACtB,OAAQ,EAAA,WAAW,QACnB,MAAO,EACP,qBAAsB,GACtB,GAAI,GAAgB,SAAW,CAAC,CACpC,EACA,UAAW,CACP,MAAO,EACP,GAAI,GAAgB,WAAa,CAAC,CACtC,CACJ,CACJ,CAAC,CACT,EAEA,OAAO,EAAA,EAAA,IAAA,CAAC,EAAA,oBAAD,CAAqB,OAAQ,EAAiB,UAA8B,CAAA,CACvF"}
1
+ {"version":3,"file":"QueryProvider.cjs","names":[],"sources":["../../src/query/QueryProvider.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport type { DefaultOptions } from \"@tanstack/react-query\";\nimport { STALE_TIME, CACHE_TIME } from \"./constants\";\nimport { shouldRetryQuery } from \"./retry-policy\";\n\nexport interface QueryProviderProps {\n children: ReactNode;\n /** Pass an existing client to share it across roots. */\n client?: QueryClient;\n /** Overrides for the default options applied when no `client` is provided. */\n defaultOptions?: DefaultOptions;\n}\n\n/**\n * Wrapper around `QueryClientProvider` that bootstraps a `QueryClient` with\n * sane SDK defaults (5-minute stale time, 30-minute gc time, one retry for\n * failures worth replaying).\n *\n * Retries follow {@link shouldRetryQuery} rather than a flat count: a 4xx other\n * than 408/429 is the server refusing on purpose, and replaying it only doubles\n * the network log while the spinner keeps turning.\n */\nexport function QueryProvider({ children, client, defaultOptions }: QueryProviderProps) {\n const [internalClient] = useState<QueryClient>(\n () =>\n client ??\n new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: STALE_TIME.DEFAULT,\n gcTime: CACHE_TIME.DEFAULT,\n retry: shouldRetryQuery,\n refetchOnWindowFocus: false,\n ...(defaultOptions?.queries ?? {}),\n },\n mutations: {\n retry: 0,\n ...(defaultOptions?.mutations ?? {}),\n },\n },\n }),\n );\n\n return <QueryClientProvider client={internalClient}>{children}</QueryClientProvider>;\n}\n"],"mappings":"4JAwBA,SAAgB,EAAc,CAAE,WAAU,SAAQ,kBAAsC,CACpF,GAAM,CAAC,IAAA,EAAkB,EAAA,SAAA,KAEjB,GACA,IAAI,EAAA,YAAY,CACZ,eAAgB,CACZ,QAAS,CACL,UAAW,EAAA,WAAW,QACtB,OAAQ,EAAA,WAAW,QACnB,MAAO,EAAA,iBACP,qBAAsB,GACtB,GAAI,GAAgB,SAAW,CAAC,CACpC,EACA,UAAW,CACP,MAAO,EACP,GAAI,GAAgB,WAAa,CAAC,CACtC,CACJ,CACJ,CAAC,CACT,EAEA,OAAO,EAAA,EAAA,IAAA,CAAC,EAAA,oBAAD,CAAqB,OAAQ,EAAiB,UAA8B,CAAA,CACvF"}
@@ -1,28 +1,29 @@
1
1
  import { CACHE_TIME as e, STALE_TIME as t } from "./constants.js";
2
- import { useState as n } from "react";
3
- import { jsx as r } from "react/jsx-runtime";
4
- import { QueryClient as i, QueryClientProvider as a } from "@tanstack/react-query";
2
+ import { shouldRetryQuery as n } from "./retry-policy.js";
3
+ import { useState as r } from "react";
4
+ import { jsx as i } from "react/jsx-runtime";
5
+ import { QueryClient as a, QueryClientProvider as o } from "@tanstack/react-query";
5
6
  //#region src/query/QueryProvider.tsx
6
- function o({ children: o, client: s, defaultOptions: c }) {
7
- let [l] = n(() => s ?? new i({ defaultOptions: {
7
+ function s({ children: s, client: c, defaultOptions: l }) {
8
+ let [u] = r(() => c ?? new a({ defaultOptions: {
8
9
  queries: {
9
10
  staleTime: t.DEFAULT,
10
11
  gcTime: e.DEFAULT,
11
- retry: 1,
12
+ retry: n,
12
13
  refetchOnWindowFocus: !1,
13
- ...c?.queries ?? {}
14
+ ...l?.queries ?? {}
14
15
  },
15
16
  mutations: {
16
17
  retry: 0,
17
- ...c?.mutations ?? {}
18
+ ...l?.mutations ?? {}
18
19
  }
19
20
  } }));
20
- return /* @__PURE__ */ r(a, {
21
- client: l,
22
- children: o
21
+ return /* @__PURE__ */ i(o, {
22
+ client: u,
23
+ children: s
23
24
  });
24
25
  }
25
26
  //#endregion
26
- export { o as QueryProvider };
27
+ export { s as QueryProvider };
27
28
 
28
29
  //# sourceMappingURL=QueryProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"QueryProvider.js","names":[],"sources":["../../src/query/QueryProvider.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport type { DefaultOptions } from \"@tanstack/react-query\";\nimport { STALE_TIME, CACHE_TIME } from \"./constants\";\n\nexport interface QueryProviderProps {\n children: ReactNode;\n /** Pass an existing client to share it across roots. */\n client?: QueryClient;\n /** Overrides for the default options applied when no `client` is provided. */\n defaultOptions?: DefaultOptions;\n}\n\n/**\n * Wrapper around `QueryClientProvider` that bootstraps a `QueryClient` with\n * sane SDK defaults (5-minute stale time, 30-minute gc time, 1 retry).\n */\nexport function QueryProvider({ children, client, defaultOptions }: QueryProviderProps) {\n const [internalClient] = useState<QueryClient>(\n () =>\n client ??\n new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: STALE_TIME.DEFAULT,\n gcTime: CACHE_TIME.DEFAULT,\n retry: 1,\n refetchOnWindowFocus: false,\n ...(defaultOptions?.queries ?? {}),\n },\n mutations: {\n retry: 0,\n ...(defaultOptions?.mutations ?? {}),\n },\n },\n }),\n );\n\n return <QueryClientProvider client={internalClient}>{children}</QueryClientProvider>;\n}\n"],"mappings":";;;;;AAkBA,SAAgB,EAAc,EAAE,aAAU,WAAQ,qBAAsC;CACpF,IAAM,CAAC,KAAkB,QAEjB,KACA,IAAI,EAAY,EACZ,gBAAgB;EACZ,SAAS;GACL,WAAW,EAAW;GACtB,QAAQ,EAAW;GACnB,OAAO;GACP,sBAAsB;GACtB,GAAI,GAAgB,WAAW,CAAC;EACpC;EACA,WAAW;GACP,OAAO;GACP,GAAI,GAAgB,aAAa,CAAC;EACtC;CACJ,EACJ,CAAC,CACT;CAEA,OAAO,kBAAC,GAAD;EAAqB,QAAQ;EAAiB;CAA8B,CAAA;AACvF"}
1
+ {"version":3,"file":"QueryProvider.js","names":[],"sources":["../../src/query/QueryProvider.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport type { DefaultOptions } from \"@tanstack/react-query\";\nimport { STALE_TIME, CACHE_TIME } from \"./constants\";\nimport { shouldRetryQuery } from \"./retry-policy\";\n\nexport interface QueryProviderProps {\n children: ReactNode;\n /** Pass an existing client to share it across roots. */\n client?: QueryClient;\n /** Overrides for the default options applied when no `client` is provided. */\n defaultOptions?: DefaultOptions;\n}\n\n/**\n * Wrapper around `QueryClientProvider` that bootstraps a `QueryClient` with\n * sane SDK defaults (5-minute stale time, 30-minute gc time, one retry for\n * failures worth replaying).\n *\n * Retries follow {@link shouldRetryQuery} rather than a flat count: a 4xx other\n * than 408/429 is the server refusing on purpose, and replaying it only doubles\n * the network log while the spinner keeps turning.\n */\nexport function QueryProvider({ children, client, defaultOptions }: QueryProviderProps) {\n const [internalClient] = useState<QueryClient>(\n () =>\n client ??\n new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: STALE_TIME.DEFAULT,\n gcTime: CACHE_TIME.DEFAULT,\n retry: shouldRetryQuery,\n refetchOnWindowFocus: false,\n ...(defaultOptions?.queries ?? {}),\n },\n mutations: {\n retry: 0,\n ...(defaultOptions?.mutations ?? {}),\n },\n },\n }),\n );\n\n return <QueryClientProvider client={internalClient}>{children}</QueryClientProvider>;\n}\n"],"mappings":";;;;;;AAwBA,SAAgB,EAAc,EAAE,aAAU,WAAQ,qBAAsC;CACpF,IAAM,CAAC,KAAkB,QAEjB,KACA,IAAI,EAAY,EACZ,gBAAgB;EACZ,SAAS;GACL,WAAW,EAAW;GACtB,QAAQ,EAAW;GACnB,OAAO;GACP,sBAAsB;GACtB,GAAI,GAAgB,WAAW,CAAC;EACpC;EACA,WAAW;GACP,OAAO;GACP,GAAI,GAAgB,aAAa,CAAC;EACtC;CACJ,EACJ,CAAC,CACT;CAEA,OAAO,kBAAC,GAAD;EAAqB,QAAQ;EAAiB;CAA8B,CAAA;AACvF"}
@@ -0,0 +1,2 @@
1
+ const e=require("../http/errors.cjs");var t=new Set([408,429]),n=1;function r(r,i){return r>=n?!1:!e.isApiError(i)||i.status===0?!0:i.status>=400&&i.status<500?t.has(i.status):!0}exports.shouldRetryQuery=r;
2
+ //# sourceMappingURL=retry-policy.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry-policy.cjs","names":[],"sources":["../../src/query/retry-policy.ts"],"sourcesContent":["import { isApiError } from \"../http/errors\";\n\n/**\n * Statuses in the 4xx range that are still worth replaying.\n *\n * `429` is a refusal that explicitly means \"later\", and `408` is the server\n * saying the request itself timed out — both describe a condition that changes on\n * its own. Every other 4xx is the server refusing on purpose, and repeating it\n * changes nothing.\n */\nconst RETRIABLE_CLIENT_STATUSES: ReadonlySet<number> = new Set([408, 429]);\n\n/** How many times a retriable failure is replayed. */\nconst MAX_RETRIES = 1;\n\n/**\n * Default `retry` for SDK queries: replay transport and server failures, never a\n * deliberate client-side refusal.\n *\n * A flat `retry: 1` replays a 403 on an admin-only listing and a 404 for a record\n * somebody deleted. The server meant both — the second attempt returns the same\n * answer, doubles the network log and holds the spinner on screen for the length\n * of another round trip. A network failure or a 5xx is a different thing: it may\n * well succeed on the next try, so those keep the previous behaviour.\n *\n * @example\n * new QueryClient({ defaultOptions: { queries: { retry: shouldRetryQuery } } });\n *\n * @param failureCount - Attempts already made, as react-query counts them.\n * @param error - The rejection value from the query function.\n * @returns Whether react-query should try again.\n */\nexport function shouldRetryQuery(failureCount: number, error: unknown): boolean {\n if (failureCount >= MAX_RETRIES) return false;\n if (!isApiError(error)) return true;\n if (error.status === 0) return true;\n if (error.status >= 400 && error.status < 500) {\n return RETRIABLE_CLIENT_STATUSES.has(error.status);\n }\n return true;\n}\n"],"mappings":"sCAUA,IAAM,EAAiD,IAAI,IAAI,CAAC,IAAK,GAAG,CAAC,EAGnE,EAAc,EAmBpB,SAAgB,EAAiB,EAAsB,EAAyB,CAO5E,OANI,GAAgB,EAAoB,GACpC,CAAC,EAAA,WAAW,CAAK,GACjB,EAAM,SAAW,EAAU,GAC3B,EAAM,QAAU,KAAO,EAAM,OAAS,IAC/B,EAA0B,IAAI,EAAM,MAAM,EAE9C,EACX"}
@@ -0,0 +1,10 @@
1
+ import { isApiError as e } from "../http/errors.js";
2
+ //#region src/query/retry-policy.ts
3
+ var t = /* @__PURE__ */ new Set([408, 429]), n = 1;
4
+ function r(r, i) {
5
+ return r >= n ? !1 : !e(i) || i.status === 0 ? !0 : i.status >= 400 && i.status < 500 ? t.has(i.status) : !0;
6
+ }
7
+ //#endregion
8
+ export { r as shouldRetryQuery };
9
+
10
+ //# sourceMappingURL=retry-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry-policy.js","names":[],"sources":["../../src/query/retry-policy.ts"],"sourcesContent":["import { isApiError } from \"../http/errors\";\n\n/**\n * Statuses in the 4xx range that are still worth replaying.\n *\n * `429` is a refusal that explicitly means \"later\", and `408` is the server\n * saying the request itself timed out — both describe a condition that changes on\n * its own. Every other 4xx is the server refusing on purpose, and repeating it\n * changes nothing.\n */\nconst RETRIABLE_CLIENT_STATUSES: ReadonlySet<number> = new Set([408, 429]);\n\n/** How many times a retriable failure is replayed. */\nconst MAX_RETRIES = 1;\n\n/**\n * Default `retry` for SDK queries: replay transport and server failures, never a\n * deliberate client-side refusal.\n *\n * A flat `retry: 1` replays a 403 on an admin-only listing and a 404 for a record\n * somebody deleted. The server meant both — the second attempt returns the same\n * answer, doubles the network log and holds the spinner on screen for the length\n * of another round trip. A network failure or a 5xx is a different thing: it may\n * well succeed on the next try, so those keep the previous behaviour.\n *\n * @example\n * new QueryClient({ defaultOptions: { queries: { retry: shouldRetryQuery } } });\n *\n * @param failureCount - Attempts already made, as react-query counts them.\n * @param error - The rejection value from the query function.\n * @returns Whether react-query should try again.\n */\nexport function shouldRetryQuery(failureCount: number, error: unknown): boolean {\n if (failureCount >= MAX_RETRIES) return false;\n if (!isApiError(error)) return true;\n if (error.status === 0) return true;\n if (error.status >= 400 && error.status < 500) {\n return RETRIABLE_CLIENT_STATUSES.has(error.status);\n }\n return true;\n}\n"],"mappings":";;AAUA,IAAM,oBAAiD,IAAI,IAAI,CAAC,KAAK,GAAG,CAAC,GAGnE,IAAc;AAmBpB,SAAgB,EAAiB,GAAsB,GAAyB;CAO5E,OANI,KAAgB,IAAoB,KACpC,CAAC,EAAW,CAAK,KACjB,EAAM,WAAW,IAAU,KAC3B,EAAM,UAAU,OAAO,EAAM,SAAS,MAC/B,EAA0B,IAAI,EAAM,MAAM,IAE9C;AACX"}