@springbrand/http 0.1.0-alpha.0 → 0.1.0-alpha.1

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.
@@ -10,7 +10,7 @@ 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 cancellation recognition |
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 |
@@ -19,15 +19,17 @@ Read the installed declarations and README before configuring a client. Public e
19
19
 
20
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.
21
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.
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
 
24
26
  ## Error handling
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.
29
31
 
30
- Use the guarded onMutationSuccess(meta) hook for application-owned cache invalidation. The SDK passes metadata without owning business query keys; callback failures preserve successful mutation results.
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.
31
33
 
32
34
  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
35
 
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、服务鉴权与安全诊断 |
@@ -68,7 +68,10 @@ const api = createHttpClient({
68
68
  feedbackOwner: 'query',
69
69
  notify: notice => showNotice(notice),
70
70
  });
71
- const queryClient = createHttpQueryClient({ handleError: api.handleError });
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,6 +107,18 @@ const api = createHttpClient({
106
107
 
107
108
  返回 true 只接管提示,原请求仍然拒绝。inline、silent 和后台场景不打开全局业务窗口。业务、通知或诊断回调自身异常不替换原始请求结果。
108
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
+
109
122
  主动写入的网络失败会提示结果暂时无法确认,不自动重发。取消保持取消语义,不包装成 NETWORK_ERROR。超时为 REQUEST_TIMEOUT,格式错误为 RESPONSE_INVALID,内容超限为 RESPONSE_TOO_LARGE。
110
123
 
111
124
  默认仅 401 且 AUTH_UNAUTHENTICATED 触发失效回调。旧身份的响应不清理新身份,同一 Token 并发失效仅处理一次;网络错误、其他 401 和 403 不退出登录。默认自动处理针对实例携带的 Bearer 身份,Cookie 会话的恢复流程由消费者掌握。
package/dist/errors.d.ts CHANGED
@@ -35,3 +35,5 @@ export declare class ApiError<T = unknown> extends Error {
35
35
  }
36
36
  export declare function isRequestCancelled(error: unknown): boolean;
37
37
  export declare const isApiError: (error: unknown) => error is ApiError;
38
+ /** Temporary transport or service failures; this does not authorize replaying a write. */
39
+ export declare function isTransientHttpError(error: unknown): error is ApiError;
package/dist/errors.js CHANGED
@@ -37,9 +37,13 @@ function isRequestCancelled(error) {
37
37
  return "name" in error && error.name === "AbortError" || "code" in error && error.code === "ERR_CANCELED";
38
38
  }
39
39
  const isApiError = (error) => error instanceof ApiError;
40
+ function isTransientHttpError(error) {
41
+ return error instanceof ApiError && (error.kind === "network" || error.kind === "timeout" || error.kind === "http" && [429, 502, 503, 504].includes(error.status));
42
+ }
40
43
  export {
41
44
  ApiError,
42
45
  errorDefinitions,
43
46
  isApiError,
44
- isRequestCancelled
47
+ isRequestCancelled,
48
+ isTransientHttpError
45
49
  };
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 error.kind === "network" || error.kind === "timeout" || error.kind === "http" && [429, 502, 503, 504].includes(error.status);
7
+ return isTransientHttpError(error);
8
8
  }
9
9
  function requestRetryDelay(attempt, error) {
10
10
  const requested = error instanceof ApiError ? error.retryAfterMs : void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springbrand/http",
3
- "version": "0.1.0-alpha.0",
3
+ "version": "0.1.0-alpha.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "publishConfig": {