@springbrand/http 0.1.0-alpha.0 → 0.1.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/dune-react-http/SKILL.md +10 -6
- package/README.md +22 -9
- package/dist/client.js +16 -4
- package/dist/errors.d.ts +5 -1
- package/dist/errors.js +7 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -2
- package/dist/query.js +2 -2
- package/dist/server/service-auth.js +4 -1
- package/package.json +5 -3
|
@@ -10,14 +10,16 @@ Read the installed declarations and README before configuring a client. Public e
|
|
|
10
10
|
| Entry point | Responsibility |
|
|
11
11
|
| --- | --- |
|
|
12
12
|
| @springbrand/http | Runtime-neutral contracts and one ApiError class |
|
|
13
|
-
| @springbrand/http/errors | Error definitions and
|
|
13
|
+
| @springbrand/http/errors | Error definitions, cancellation and transient failure classification |
|
|
14
14
|
| @springbrand/http/client | Axios client and standard HTTP error parsing |
|
|
15
15
|
| @springbrand/http/query | Optional TanStack React Query integration |
|
|
16
16
|
| @springbrand/http/server | Standard Response helpers and service authentication |
|
|
17
17
|
|
|
18
18
|
## Client setup
|
|
19
19
|
|
|
20
|
-
Create one client per trusted API origin. Inject getToken; do not copy Token storage or Bearer construction into requests. Relative paths include the configured base path. Foreign origins and URL credentials are rejected. Defaults are Cookie omit, 15-second deadline and 10-MiB response limit.
|
|
20
|
+
Create one client per trusted API origin. Inject getToken; do not copy Token storage or Bearer construction into requests. Relative paths include the configured base path. Foreign origins and URL credentials are rejected. Defaults are Cookie omit, no-store cache, manual redirects, 15-second deadline and 10-MiB response limit. The Fetch adapter supports browsers, Node.js 24 and Cloudflare Workers. Redirects reject with REQUEST_REDIRECT_BLOCKED, including opaque browser redirects; never enable automatic redirects for credential-bearing API calls.
|
|
21
|
+
|
|
22
|
+
Pass params for query serialization and signal/timeoutMs for cancellation/deadlines. Null/undefined query values are omitted; use a string for literal null. Do not duplicate query encoding or timeout controllers in domain clients.
|
|
21
23
|
|
|
22
24
|
Use get/post/put/patch/delete for payloads, request for data plus status and headers. Explicitly choose json, text or blob for endpoints without the standard success envelope. FormData owns its boundary. Pass Query's signal through; do not wrap cancellation as network failure.
|
|
23
25
|
|
|
@@ -25,9 +27,11 @@ Use get/post/put/patch/delete for payloads, request for data plus status and hea
|
|
|
25
27
|
|
|
26
28
|
Errors stay rejected even when presentation is silent or a business callback returns true. Never return fake successful data, match error messages, or serialize an Axios error/config to logs. Business detail validation belongs to the consumer's decoder; default parsing does not expose arbitrary details.
|
|
27
29
|
|
|
28
|
-
Choose one feedback owner: client for direct calls, query for Query/Mutation. Inline pages must render errors. Query integration retries safe reads only, with at most two extra attempts by default; mutations do not retry. Preserve cache after background failure. User refresh can explicitly request user feedback.
|
|
30
|
+
Choose one feedback owner: client for direct calls, query for Query/Mutation. Inline pages must render errors. Query integration retries safe reads only, with at most two extra attempts by default; mutations do not retry. Preserve cache after background failure. Use onMutationSuccess for consumer-owned cache invalidation metadata; do not hard-code business query keys in the SDK. User refresh can explicitly request user feedback.
|
|
31
|
+
|
|
32
|
+
Use isTransientHttpError from the core or errors entry point for cached-view recovery, matching the Query retry classifier. Network, timeout and HTTP 429/502/503/504 are transient; cancellation, decoding, auth and permission errors are not. Classification never authorizes replaying writes.
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
Runtime capability failures use RUNTIME_UNSUPPORTED (kind runtime), distinct from invalid input; neither runtime failures nor blocked redirects are transient or retryable. Raw runtime diagnostics are not exposed.
|
|
31
35
|
|
|
32
36
|
Only confirmed current Bearer session failures trigger onUnauthorized. Never clear sessions on network failures, every 401, or 403. SDK callbacks guard stale Token responses and duplicate feedback. Cookie login/recovery belongs to consumer authentication.
|
|
33
37
|
|
|
@@ -35,8 +39,8 @@ Only confirmed current Bearer session failures trigger onUnauthorized. Never cle
|
|
|
35
39
|
|
|
36
40
|
Return ok or fail through framework-compatible Response handling. Failures use real 4xx/5xx and public error messages. Keep domain errors separate until the HTTP handler translates them. Validate business details before returning them. toErrorResponse hides unknown exceptions.
|
|
37
41
|
|
|
38
|
-
Use service request/verification helpers for service credentials, independently of user authentication. Never forward user Cookie/Authorization or send service tokens to another origin.
|
|
42
|
+
Use service request/verification helpers for service credentials, independently of user authentication. Service requests use manual redirects and no-store; Workers omit the unsupported credentials option. The caller must reject 3xx instead of following them with service credentials. Never forward user Cookie/Authorization or send service tokens to another origin.
|
|
39
43
|
|
|
40
44
|
## Verification
|
|
41
45
|
|
|
42
|
-
Exercise transient read recovery, write uncertainty, field errors, cancellation, Token changes and callback failures. Verify server imports without React Query and packaged public entry points. Use HTTP Storybook examples as behavior references, not copied implementations. Do not add app-local parallel clients or edit installed node_modules.
|
|
46
|
+
Exercise transient read recovery, write uncertainty, field errors, cancellation, Token changes and callback failures. Run the Miniflare/workerd regression with mocked outbound services; Node-only Fetch tests do not prove Worker compatibility. Verify server imports without React Query and packaged public entry points. Use HTTP Storybook examples as behavior references, not copied implementations. Do not add app-local parallel clients or edit installed node_modules.
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ pnpm add @springbrand/http@alpha
|
|
|
15
15
|
| 入口 | 能力 |
|
|
16
16
|
| --- | --- |
|
|
17
17
|
| `@springbrand/http` | 共享类型与 ApiError,无浏览器初始化 |
|
|
18
|
-
| `@springbrand/http/errors` |
|
|
18
|
+
| `@springbrand/http/errors` | 基础错误码、取消识别和临时错误判断 |
|
|
19
19
|
| `@springbrand/http/client` | createHttpClient、parseHttpError |
|
|
20
20
|
| `@springbrand/http/query` | createHttpQueryClient、读取重试策略 |
|
|
21
21
|
| `@springbrand/http/server` | ok、fail、toErrorResponse、服务鉴权与安全诊断 |
|
|
@@ -39,7 +39,7 @@ const items = await api.get<Item[]>('/items');
|
|
|
39
39
|
await api.post('/items', { name: 'Example' });
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Token
|
|
42
|
+
Token 每次发送时读取。实例使用 `cache: no-store` 和 `redirect: manual`,兼容浏览器、Node.js 24 和 Cloudflare Workers。API 重定向(含浏览器 opaque redirect)以 REQUEST_REDIRECT_BLOCKED 拒绝,不跟随跳转;Query 缓存仍由 Query 管理。实例只向绑定来源发送请求;非法地址、带用户名密码的 URL 和其他来源的绝对 URL 会在发送前失败。`baseUrl` 的路径前缀会与相对接口路径拼接。
|
|
43
43
|
|
|
44
44
|
客户端使用 Axios Fetch Adapter,默认 `credentials: 'omit'`。Cookie 登录使用显式 `credentials: 'include'` 的独立实例。`auth: false` 关闭 SDK 的 Bearer 注入,Cookie 策略不随之改变。有 getToken 时,SDK 管理 Authorization,额外请求头不能覆盖它。
|
|
45
45
|
|
|
@@ -68,7 +68,10 @@ const api = createHttpClient({
|
|
|
68
68
|
feedbackOwner: 'query',
|
|
69
69
|
notify: notice => showNotice(notice),
|
|
70
70
|
});
|
|
71
|
-
const queryClient = createHttpQueryClient({
|
|
71
|
+
const queryClient = createHttpQueryClient({
|
|
72
|
+
handleError: api.handleError,
|
|
73
|
+
onMutationSuccess: meta => refreshBusinessQueries(meta),
|
|
74
|
+
});
|
|
72
75
|
|
|
73
76
|
// 传入 QueryClientProvider,业务 queryKey 由功能模块管理。
|
|
74
77
|
useQuery({
|
|
@@ -78,14 +81,12 @@ useQuery({
|
|
|
78
81
|
});
|
|
79
82
|
```
|
|
80
83
|
|
|
81
|
-
默认最多两次额外重试,只针对已确认 GET 或 HEAD 的网络错误、超时和 429、502、503、504。Mutation 默认不重试;POST 即使误放进 queryFn 也不会获得默认重试权限。业务主动覆盖 Query 的 retry 选项后,由消费者承担该操作的重试合同。
|
|
84
|
+
默认最多两次额外重试,只针对已确认 GET 或 HEAD 的网络错误、超时和 429、502、503、504。Mutation 默认不重试;POST 即使误放进 queryFn 也不会获得默认重试权限。业务主动覆盖 Query 的 retry 选项后,由消费者承担该操作的重试合同。onMutationSuccess 传入 Mutation 元数据供消费者更新业务缓存,回调失败不改变原请求结果。
|
|
82
85
|
|
|
83
86
|
恢复可见或网络恢复时,Query 刷新过期数据。已有缓存的后台错误静默,首次加载在页面内展示;SDK 不吞掉失败。用户主动刷新需要在失败后用 `api.handleError(error, { intent: 'read', trigger: 'user' })` 明确反馈。
|
|
84
87
|
|
|
85
88
|
Query 项目中的直接命令式调用使用 `{ feedbackOwner: 'client' }`;经 Mutation 管理的操作继续由 Query 负责最终反馈,避免重复提示。
|
|
86
89
|
|
|
87
|
-
业务缓存失效通过 `createHttpQueryClient` 的 `onMutationSuccess(meta)` 扩展接入。它接收 Mutation 元数据,由消费者解释业务缓存键;回调失败不会改变已经成功的 Mutation 结果。
|
|
88
|
-
|
|
89
90
|
## 错误和业务扩展
|
|
90
91
|
|
|
91
92
|
```ts
|
|
@@ -106,7 +107,19 @@ const api = createHttpClient({
|
|
|
106
107
|
|
|
107
108
|
返回 true 只接管提示,原请求仍然拒绝。inline、silent 和后台场景不打开全局业务窗口。业务、通知或诊断回调自身异常不替换原始请求结果。
|
|
108
109
|
|
|
109
|
-
|
|
110
|
+
`isTransientHttpError(error)` 在核心与 errors 入口公开,识别网络错误、超时及真实 HTTP 429、502、503、504。Query 默认重试和页面保留缓存共用这项判断。格式错误、取消、认证和权限失败不会被认作临时错误;这项分类不授权重试写操作。
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { isTransientHttpError } from '@springbrand/http';
|
|
114
|
+
|
|
115
|
+
if (query.data !== undefined && isTransientHttpError(query.error)) {
|
|
116
|
+
// 保留缓存内容,首次加载失败仍由页面展示。
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
查询参数直接传 `params`,由 SDK 编码:数组发送重复参数,null 和 undefined 省略。需要字面值 null 的业务字段应传字符串 `'null'`。传入 `signal` 和 `timeoutMs` 即可取消或设置超时,无需在业务包再创建超时控制器。
|
|
121
|
+
|
|
122
|
+
主动写入的网络失败会提示结果暂时无法确认,不自动重发。取消保持取消语义,不包装成 NETWORK_ERROR。超时为 REQUEST_TIMEOUT,格式错误为 RESPONSE_INVALID,内容超限为 RESPONSE_TOO_LARGE。请求运行时不支持所需能力时为 RUNTIME_UNSUPPORTED(kind: runtime),与非法调用参数 REQUEST_INVALID_INPUT 分开;运行时和重定向错误不重试、不清理登录。
|
|
110
123
|
|
|
111
124
|
默认仅 401 且 AUTH_UNAUTHENTICATED 触发失效回调。旧身份的响应不清理新身份,同一 Token 并发失效仅处理一次;网络错误、其他 401 和 403 不退出登录。默认自动处理针对实例携带的 Bearer 身份,Cookie 会话的恢复流程由消费者掌握。
|
|
112
125
|
|
|
@@ -136,7 +149,7 @@ ok 支持 200、201、202;无内容成功直接用标准 Response。fail 只
|
|
|
136
149
|
|
|
137
150
|
toErrorResponse 公开标准 ApiError 的安全信息,未知异常返回固定 500 INTERNAL_ERROR。业务错误和 details 在 HTTP 处理器中翻译和验证。
|
|
138
151
|
|
|
139
|
-
createServiceRequest 为绑定来源构造服务请求,清理用户 Authorization 和 Cookie,注入 X-SpringBrand-Service-Token。verifyServiceToken
|
|
152
|
+
createServiceRequest 为绑定来源构造服务请求,清理用户 Authorization 和 Cookie,注入 X-SpringBrand-Service-Token。verifyServiceToken 比较等长摘要。服务请求使用 no-store / manual,Worker 中不设置浏览器专有的 credentials。调用方需拒绝非成功响应,尤其不得自行跟随 3xx 并转发服务凭据;不使用用户 Bearer 的登录失效流程。
|
|
140
153
|
|
|
141
154
|
## 验证与示例
|
|
142
155
|
|
|
@@ -148,6 +161,6 @@ pnpm --filter @springbrand/http build
|
|
|
148
161
|
pnpm build-storybook
|
|
149
162
|
```
|
|
150
163
|
|
|
151
|
-
Storybook 的 HTTP 分类包含成功、后台恢复、首次失败、字段错误、业务扩展、认证、取消、动态 Token 和上传下载。模拟服务仍通过真实 Axios 请求实现。
|
|
164
|
+
Storybook 的 HTTP 分类包含成功、后台恢复、首次失败、字段错误、业务扩展、认证、取消、动态 Token 和上传下载。模拟服务仍通过真实 Axios 请求实现。tests/worker.test.ts 使用 Miniflare/workerd 执行真正的 Worker,验证 JSON、Token、服务凭据及重定向保护;测试不会访问真实供应商。
|
|
152
165
|
|
|
153
166
|
完整设计位于 dune-react 的 trevordocs/axios-request-sdk-architecture.md。预发布版本使用 npm 的 alpha 标签,业务项目的迁移由各项目独立安排。
|
package/dist/client.js
CHANGED
|
@@ -13,7 +13,8 @@ function createHttpClient(config) {
|
|
|
13
13
|
responseType: "stream",
|
|
14
14
|
timeout: 0,
|
|
15
15
|
withCredentials: config.credentials === "same-origin" ? void 0 : config.credentials === "include",
|
|
16
|
-
|
|
16
|
+
// Workers rejects explicit cache: default and redirect: error. Never follow API redirects.
|
|
17
|
+
fetchOptions: { cache: "no-store", redirect: "manual" }
|
|
17
18
|
});
|
|
18
19
|
const handleError = createErrorHandler(config);
|
|
19
20
|
let rejectedToken;
|
|
@@ -75,6 +76,13 @@ function createHttpClient(config) {
|
|
|
75
76
|
const rawHeaders = response.headers instanceof axios.AxiosHeaders ? response.headers.toJSON() : response.headers;
|
|
76
77
|
for (const [key, value] of Object.entries(rawHeaders)) if (typeof value === "string" || typeof value === "number" || Array.isArray(value)) responseHeaders[key.toLowerCase()] = String(value);
|
|
77
78
|
const info = { status: response.status, headers: responseHeaders };
|
|
79
|
+
if (response.status === 0 || response.status >= 300 && response.status < 400) {
|
|
80
|
+
try {
|
|
81
|
+
await response.data?.cancel();
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
throw new ApiError({ status: response.status, code: "REQUEST_REDIRECT_BLOCKED", message: errorDefinitions.REQUEST_REDIRECT_BLOCKED, kind: "response", requestId: responseHeaders["x-request-id"] });
|
|
85
|
+
}
|
|
78
86
|
const chunks = await readBody(response.data, info, limit);
|
|
79
87
|
if (signal.aborted) throw signal.reason;
|
|
80
88
|
if (response.status < 200 || response.status >= 300) {
|
|
@@ -97,11 +105,15 @@ function createHttpClient(config) {
|
|
|
97
105
|
const reason = signal.aborted ? signal.reason : error;
|
|
98
106
|
if (signal.aborted && !(reason instanceof ApiError) && !(reason instanceof Error && reason.name === "TimeoutError")) throw reason;
|
|
99
107
|
if (isRequestCancelled(reason)) throw reason;
|
|
108
|
+
const runtimeUnsupported = axios.isAxiosError(reason) && (reason.code === axios.AxiosError.ERR_NOT_SUPPORT || !reason.request && reason.cause instanceof TypeError);
|
|
109
|
+
const timedOut = reason instanceof Error && reason.name === "TimeoutError";
|
|
110
|
+
const networkFailure = received || axios.isAxiosError(reason) && reason.request instanceof Request;
|
|
111
|
+
const code = timedOut ? "REQUEST_TIMEOUT" : runtimeUnsupported ? "RUNTIME_UNSUPPORTED" : networkFailure ? "NETWORK_ERROR" : "REQUEST_INVALID_INPUT";
|
|
100
112
|
const normalized = reason instanceof ApiError ? reason : new ApiError({
|
|
101
113
|
status: 0,
|
|
102
|
-
code
|
|
103
|
-
message:
|
|
104
|
-
kind:
|
|
114
|
+
code,
|
|
115
|
+
message: errorDefinitions[code],
|
|
116
|
+
kind: timedOut ? "timeout" : runtimeUnsupported ? "runtime" : networkFailure ? "network" : "input"
|
|
105
117
|
});
|
|
106
118
|
const failure = new ApiError({ ...normalized, message: normalized.message, context });
|
|
107
119
|
const unauthorized = config.isUnauthorized ?? ((value) => value.status === 401 && value.code === "AUTH_UNAUTHENTICATED");
|
package/dist/errors.d.ts
CHANGED
|
@@ -5,12 +5,14 @@ export declare const errorDefinitions: {
|
|
|
5
5
|
readonly RESPONSE_INVALID: "服务返回的数据格式异常。";
|
|
6
6
|
readonly RESPONSE_TOO_LARGE: "返回内容超过读取限制。";
|
|
7
7
|
readonly REQUEST_INVALID_INPUT: "请求配置无效。";
|
|
8
|
+
readonly RUNTIME_UNSUPPORTED: "当前运行环境不支持 HTTP 请求所需能力,请升级 SDK 或检查运行时配置。";
|
|
9
|
+
readonly REQUEST_REDIRECT_BLOCKED: "接口返回了重定向,已阻止跳转,请检查 API 地址。";
|
|
8
10
|
readonly AUTH_UNAUTHENTICATED: "登录状态已失效,请重新登录。";
|
|
9
11
|
readonly REQUEST_FORBIDDEN: "没有执行此操作的权限。";
|
|
10
12
|
readonly INTERNAL_ERROR: "服务暂时无法完成请求,请稍后重试。";
|
|
11
13
|
readonly HTTP_ERROR: "请求未能完成,请检查后重试。";
|
|
12
14
|
};
|
|
13
|
-
export type ApiErrorKind = "network" | "timeout" | "http" | "response" | "input";
|
|
15
|
+
export type ApiErrorKind = "network" | "timeout" | "http" | "response" | "input" | "runtime";
|
|
14
16
|
export type ApiErrorOptions<T = unknown> = {
|
|
15
17
|
status: number;
|
|
16
18
|
code: string;
|
|
@@ -35,3 +37,5 @@ export declare class ApiError<T = unknown> extends Error {
|
|
|
35
37
|
}
|
|
36
38
|
export declare function isRequestCancelled(error: unknown): boolean;
|
|
37
39
|
export declare const isApiError: (error: unknown) => error is ApiError;
|
|
40
|
+
/** Temporary transport or service failures; this does not authorize replaying a write. */
|
|
41
|
+
export declare function isTransientHttpError(error: unknown): error is ApiError;
|
package/dist/errors.js
CHANGED
|
@@ -4,6 +4,8 @@ const errorDefinitions = {
|
|
|
4
4
|
RESPONSE_INVALID: "服务返回的数据格式异常。",
|
|
5
5
|
RESPONSE_TOO_LARGE: "返回内容超过读取限制。",
|
|
6
6
|
REQUEST_INVALID_INPUT: "请求配置无效。",
|
|
7
|
+
RUNTIME_UNSUPPORTED: "当前运行环境不支持 HTTP 请求所需能力,请升级 SDK 或检查运行时配置。",
|
|
8
|
+
REQUEST_REDIRECT_BLOCKED: "接口返回了重定向,已阻止跳转,请检查 API 地址。",
|
|
7
9
|
AUTH_UNAUTHENTICATED: "登录状态已失效,请重新登录。",
|
|
8
10
|
REQUEST_FORBIDDEN: "没有执行此操作的权限。",
|
|
9
11
|
INTERNAL_ERROR: "服务暂时无法完成请求,请稍后重试。",
|
|
@@ -37,9 +39,13 @@ function isRequestCancelled(error) {
|
|
|
37
39
|
return "name" in error && error.name === "AbortError" || "code" in error && error.code === "ERR_CANCELED";
|
|
38
40
|
}
|
|
39
41
|
const isApiError = (error) => error instanceof ApiError;
|
|
42
|
+
function isTransientHttpError(error) {
|
|
43
|
+
return error instanceof ApiError && (error.kind === "network" || error.kind === "timeout" || error.kind === "http" && [429, 502, 503, 504].includes(error.status));
|
|
44
|
+
}
|
|
40
45
|
export {
|
|
41
46
|
ApiError,
|
|
42
47
|
errorDefinitions,
|
|
43
48
|
isApiError,
|
|
44
|
-
isRequestCancelled
|
|
49
|
+
isRequestCancelled,
|
|
50
|
+
isTransientHttpError
|
|
45
51
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { ApiError, errorDefinitions, isApiError, isRequestCancelled } from "./errors.js";
|
|
1
|
+
export { ApiError, errorDefinitions, isApiError, isRequestCancelled, isTransientHttpError } from "./errors.js";
|
|
2
2
|
export type { ApiErrorOptions, ApiErrorKind } from "./errors.js";
|
|
3
3
|
export type { ApiSuccessResponse, HttpErrorResponse, FieldError, HttpResult, ErrorContext, RequestNotice, RequestFailure, HttpClientConfig, HttpRequestOptions, HttpMethod, ResponseMode } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { ApiError, errorDefinitions, isApiError, isRequestCancelled } from "./errors.js";
|
|
1
|
+
import { ApiError, errorDefinitions, isApiError, isRequestCancelled, isTransientHttpError } from "./errors.js";
|
|
2
2
|
export {
|
|
3
3
|
ApiError,
|
|
4
4
|
errorDefinitions,
|
|
5
5
|
isApiError,
|
|
6
|
-
isRequestCancelled
|
|
6
|
+
isRequestCancelled,
|
|
7
|
+
isTransientHttpError
|
|
7
8
|
};
|
package/dist/query.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { QueryClient, MutationCache, QueryCache, isCancelledError } from "@tanstack/react-query";
|
|
2
|
-
import { isRequestCancelled, ApiError } from "./errors.js";
|
|
2
|
+
import { isRequestCancelled, ApiError, isTransientHttpError } from "./errors.js";
|
|
3
3
|
import { toSafeDiagnostics } from "./diagnostics.js";
|
|
4
4
|
function shouldRetryRequest(failureCount, error, maxRetries = 2) {
|
|
5
5
|
if (failureCount >= maxRetries || !(error instanceof ApiError)) return false;
|
|
6
6
|
if (error.context?.method !== "GET" && error.context?.method !== "HEAD") return false;
|
|
7
|
-
return
|
|
7
|
+
return isTransientHttpError(error);
|
|
8
8
|
}
|
|
9
9
|
function requestRetryDelay(attempt, error) {
|
|
10
10
|
const requested = error instanceof ApiError ? error.retryAfterMs : void 0;
|
|
@@ -7,7 +7,10 @@ function createServiceRequest(baseUrl, token, path, init = {}) {
|
|
|
7
7
|
headers.delete("Authorization");
|
|
8
8
|
headers.delete("Cookie");
|
|
9
9
|
headers.set(SERVICE_TOKEN_HEADER, token);
|
|
10
|
-
|
|
10
|
+
const options = { ...init, headers, cache: "no-store", redirect: "manual" };
|
|
11
|
+
if ("credentials" in Request.prototype) options.credentials = "omit";
|
|
12
|
+
else delete options.credentials;
|
|
13
|
+
return new Request(url, options);
|
|
11
14
|
}
|
|
12
15
|
async function verifyServiceToken(provided, expected) {
|
|
13
16
|
if (!provided || !expected) return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@springbrand/http",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -65,9 +65,11 @@
|
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@tanstack/react-query": "5.104.1",
|
|
68
|
-
"
|
|
68
|
+
"@types/node": "^20.8.3",
|
|
69
|
+
"esbuild": "0.27.7",
|
|
70
|
+
"miniflare": "5.20260910.0-alpha",
|
|
69
71
|
"typescript": "^5.0.2",
|
|
70
72
|
"vite": "^5.4.19",
|
|
71
|
-
"
|
|
73
|
+
"vitest": "3.2.6"
|
|
72
74
|
}
|
|
73
75
|
}
|