@basenative/fetch

中文文档
3.0.0发布于 2026/10/31Apache-2.0TypeScriptES Module构建来源已验证

Signal-based resource fetching with caching and SSR preload support

原始使用文档
文档目录13 节
  1. Install
  2. Quick Start
  3. API client
  4. API
  5. createResource(fetcher, options?)
  6. createMutation(mutationFn, options?)
  7. createCache(options?)
  8. fetchJson(url, options?)
  9. createApiClient(options)
  10. ApiError / isApiError(value)
  11. isApiResponse(value) / isApiErrorEnvelope(value) / unwrap(e…
  12. serializeQuery(params) / joinUrl(base, path)
  13. License

@basenative/fetch

Signal-based async resource fetching with caching, mutations, request deduplication, and a typed API client

Part of the BaseNative ecosystem — a signal-based web runtime over native HTML.

Install

npm install @basenative/fetch

Quick Start

import { createResource, createMutation, createCache, fetchJson } from '@basenative/fetch';

// Fetch and expose reactive state
const users = createResource(() => fetchJson('/api/users'));

import { effect } from '@basenative/runtime';

effect(() => {
  if (users.loading()) console.log('Loading...');
  if (users.error()) console.error(users.error());
  if (users.data()) console.log(users.data());
});

// Refetch with params
await users.refetch({ page: 2 });

// Optimistic mutation
const createUser = createMutation(
  (data) => fetchJson('/api/users', { method: 'POST', body: data }),
  { onSuccess: () => users.refetch() }
);

await createUser.mutate({ name: 'Alice' });

API client

import { createApiClient, isApiError, unwrap } from '@basenative/fetch';

const api = createApiClient({
  baseUrl: 'https://api.example.com',
  headers: { 'x-app': 'demo' },
  timeoutMs: 10_000,
  onUnauthorized: () => location.assign('/login'),
});

// Bodies are JSON-encoded, responses JSON-parsed; the envelope comes back as is
const page = await api.get('/api/leads', { page: 2, status: ['new', 'open'] });
page.data; // Lead[]
page.meta; // { total, page, perPage }

// unwrap() when you only need `data`
const lead = await unwrap(api.post('/api/leads', { firstName: 'Jane' }));

try {
  await api.delete(`/api/leads/${lead.id}`);
} catch (err) {
  if (isApiError(err)) console.error(err.status, err.code, err.message);
}

Every failure — a non-2xx status, an { error: { code, message } } envelope in a 2xx body, a timeout, or a network error — rejects with an ApiError. A caller's own AbortSignal abort is rethrown untouched, so createResource(() => api.get(...)) keeps ignoring cancellations.

API

createResource(fetcher, options?)

Creates a signal-based async resource that fetches data reactively. Options:

  • initialData — Value for data before the first fetch.
  • immediate — Whether to fetch immediately on creation (default: true).
  • key — Cache key string for deduplication.

Returns: { data, loading, error, status, fetch, refetch, mutate } — all reactive signals plus imperative methods.

  • status — Computed signal: 'idle', 'loading', 'success', or 'error'.
  • fetch(params?) — Triggers a new fetch, aborting any in-flight request.
  • refetch(params?) — Alias for fetch.
  • mutate(value) — Directly updates data without re-fetching. Accepts a value or an updater function.

createMutation(mutationFn, options?)

Creates a signal-based mutation for write operations. Options: onSuccess(result, params), onError(err, params).

Returns: { data, loading, error, status, mutate, reset }.

  • mutate(params) — Executes the mutation function.
  • reset() — Clears data, error, and sets status back to 'idle'.

createCache(options?)

Creates a request cache for deduplication. Options: maxAge (default: 5 minutes), maxSize (default: 100 entries).

Returns: { get(key), set(key, data), invalidate(key?), has(key), size }.

fetchJson(url, options?)

A fetch wrapper that serializes the request body as JSON, sets Content-Type: application/json, and throws a typed Error with .status and .response on non-2xx responses.

createApiClient(options)

Creates a typed fetch wrapper bound to a base URL. Options:

  • baseUrl — string, or a function resolved on every request.
  • headers — headers sent with every request; per-request headers win.
  • fetch — implementation to use (default: globalThis.fetch).
  • credentials — default credentials mode (default: 'include').
  • timeoutMs — abort requests that take longer; they reject with an ApiError whose code is 'timeout'.
  • onRequest(ctx), onResponse(ctx), onUnauthorized(error, ctx) (401 only, before onError), onError(error, ctx) — awaited hooks for auth redirects, telemetry and error tracking.

Returns an ApiClient:

  • get(path, params?, init?), post(path, body?, init?), put(path, body?, init?), patch(path, body?, init?), delete(path, init?) — resolve with the parsed response body (undefined for 204). init accepts headers, signal, credentials, raw and (for delete) params/body.
  • request({ path, method?, params?, body?, headers?, signal?, credentials?, raw? }) — the general form; raw: true resolves with the Response itself.
  • resolveUrl(path, params?) — the URL a request would hit.

Plain-object bodies are JSON-encoded with content-type: application/json unless you set one; string, FormData, URLSearchParams, Blob, ArrayBuffer, typed arrays and streams pass through untouched.

ApiError / isApiError(value)

Thrown by the client and by unwrap(). Fields: status (0 when no response was received), code (the envelope's error.code, else http_<status>, network_error or timeout), message, field (when the envelope names one), url, body (the parsed response body) and response (the Response, body already consumed). isApiError(value) is an instanceof guard.

isApiResponse(value) / isApiErrorEnvelope(value) / unwrap(envelope)

Helpers for the response envelope — { data, meta? } on success, { error: { code, message, field? } } on failure. unwrap(envelope) returns data from an envelope or from a promise resolving to one, and throws ApiError if it is an error envelope.

serializeQuery(params) / joinUrl(base, path)

serializeQuery({ b: 1, a: ['x', 'y'], c: null }) → ?a=x&a=y&b=1 — keys sorted (stable cache keys), arrays repeated, null/undefined skipped, empty string when nothing survives. joinUrl(base, path) joins with exactly one /; an empty base returns path, an absolute http(s):// path is returned as is.

License

Apache-2.0

版本与发布记录

@basenative/fetch 共发布 11 个版本,首次发布于 2026/03/20;过去 12 个月发布了 11 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
3.0.0latest2026/10/312 天前
2.4.02026/10/268 天前
2.3.22026/10/2212 天前
2.3.12026/10/2212 天前
2.3.02026/10/2212 天前
2.2.02026/10/2113 天前

关于 @basenative/fetch 的常见问题

@basenative/fetch 是什么?

基于信号的资源获取,支持缓存和SSR预加载

如何安装 @basenative/fetch?

在项目目录运行 npm install @basenative/fetch,或使用 pnpm add / yarn add / bun add。

@basenative/fetch 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@basenative/fetch 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@basenative/fetch 可以商用吗?

@basenative/fetch 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@basenative/fetch 还在维护吗?

最新版本 3.0.0 发布于 2026/10/31(2 天前);至今共发布 11 个版本,其中过去 12 个月发布了 11 个;首次发布于 2026/03/20。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。