@archie/react-sdk

中文文档
0.8.0发布于 2026/11/04MITTypeScriptES Module零依赖近 30 天下载 6390

Archie BAAS - React hooks, provider & components

原始使用文档
文档目录26 节
  1. Table of Contents
  2. Installation
  3. Quick Start
  4. ArchieProvider
  5. Hooks
  6. useArchieClient
  7. useAuth
  8. useQuery
  9. useMutation
  10. useSubscription
  11. useFileUpload
  12. useRest
  13. useRestQuery
  14. useRestMutation
  15. useNotifications
  16. useAIChat
  17. Components
  18. AuthGuard
  19. NotificationAction
  20. Re-exported Types
  21. Error Handling
  22. SSR Compatibility
  23. Advanced Client Options
  24. Health Check
  25. TypeScript
  26. License

@archie/react-sdk

React hooks, provider, and components for the Archie BAAS platform.

Built on top of @archie/js-sdk, this package provides a fully reactive React integration with auth state management, declarative data fetching, real-time subscriptions, file uploads with progress tracking, and route guards.


Table of Contents


Installation

npm install @archie/js-sdk @archie/react-sdk
# or
pnpm add @archie/js-sdk @archie/react-sdk

Peer dependencies: react >=18, @archie/js-sdk


Quick Start

import { createClient } from '@archie/js-sdk';
import { ArchieProvider, useAuth, useQuery, AuthGuard } from '@archie/react-sdk';

const archie = createClient({
  projectId: 'your-project-uuid',
  apiKey: 'anon your-api-key',
  apiUrl: 'https://your-project.archiecore.com',
  environment: 'master',
  retries: 3, // automatic retry with backoff (inherited from js-sdk)
  timeout: 15_000, // request timeout in ms
});

function App() {
  return (
    <ArchieProvider client={archie}>
      <AuthGuard fallback={<LoginPage />} loadingComponent={<Spinner />}>
        <Dashboard />
      </AuthGuard>
    </ArchieProvider>
  );
}

function LoginPage() {
  const { signIn } = useAuth();

  const handleSubmit = async (email: string, password: string) => {
    const { session, error } = await signIn({ email, password });
    if (error) console.error(error.message);
  };

  return <form onSubmit={/* ... */}>/* ... */</form>;
}

function Dashboard() {
  const { user, signOut } = useAuth();
  const { data, isLoading } = useQuery<{ orders: Order[] }>('{ orders { id total status } }');

  return (
    <div>
      <p>Welcome, {user?.email}</p>
      <button onClick={signOut}>Sign Out</button>
      {isLoading ? <Spinner /> : <OrderList orders={data?.orders ?? []} />}
    </div>
  );
}

ArchieProvider

Wraps your React app to provide the Archie client and auth session state via context.

import { createClient } from '@archie/js-sdk';
import { ArchieProvider } from '@archie/react-sdk';

const archie = createClient({ projectId: '...', apiKey: 'anon ...' });

function App() {
  return (
    <ArchieProvider client={archie}>
      <MyApp />
    </ArchieProvider>
  );
}

Props:

PropTypeDescription
clientArchieClientClient instance from createClient()
childrenReactNodeChild components

Internals:

  • Creates React context with ArchieClient instance
  • Calls client.auth.waitForInit() on mount to restore persisted session
  • Subscribes to onAuthStateChange and updates session in context
  • Provides { client, session, isLoading } to all child hooks

Hooks

useArchieClient

Returns the raw ArchieClient instance. Useful for advanced use cases outside the provided hooks.

const archie = useArchieClient();

// Direct access to any module
const jwks = await archie.auth.getJWKS();

Throws if used outside <ArchieProvider>.


useAuth

Full authentication state and operations. All async methods return { data/session, error } — they never throw.

const {
  user, // User | null
  session, // Session | null
  isLoading, // boolean — true until initial session check completes
  isAuthenticated, // boolean — shorthand for session !== null
  signIn, // (params) => Promise<{ session, error }>
  signUp, // (params) => Promise<{ data, error }>
  exchangeCode, // ({ code }) => Promise<{ session, error }> — single-use code from a trusted server; session cannot be refreshed
  signOut, // () => Promise<void>
  confirmSignUp, // (params) => Promise<{ session, error }>
  recoverPassword, // (params) => Promise<{ data, error }>
  resetPassword, // (params) => Promise<{ data, error }>
  refreshSession, // () => Promise<{ session, error }>
} = useAuth();

Example — Sign In:

function LoginForm() {
  const { signIn, isLoading } = useAuth();
  const [error, setError] = useState<string | null>(null);

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    const { session, error } = await signIn({ email, password });
    if (error) {
      setError(error.message);
    }
    // session is automatically updated in context — no manual setState needed
  };

  return (
    <form onSubmit={handleSubmit}>
      {error && <p className="error">{error}</p>}
      {/* inputs */}
      <button disabled={isLoading}>Sign In</button>
    </form>
  );
}

Example — Sign Up flow:

const { signUp, confirmSignUp } = useAuth();

// Step 1: Register
const { data, error } = await signUp({ email, password });
// data = { userId: '...', message: 'Check your email' }

// Step 2: Confirm with code from email
const { session, error: confirmError } = await confirmSignUp({
  email,
  code: '123456',
});

Example — Password Recovery:

const { recoverPassword, resetPassword } = useAuth();

// Step 1: Request reset
await recoverPassword({ email });

// Step 2: Reset with code
await resetPassword({ email, code: '123456', newPassword: 'newPass!' });

All methods are memoized with useCallback — safe to pass as props without causing re-renders.


useQuery

Declarative GraphQL queries with caching, stale-while-revalidate, polling, and automatic deduplication.

const { data, error, isLoading, isRefetching, refetch } = useQuery<T>(
  gql,         // GraphQL query string
  variables?,  // Record<string, unknown>
  options?,    // UseQueryOptions
);

Options:

OptionTypeDefaultDescription
enabledbooleantrueSet false to skip fetch (conditional queries)
refetchIntervalnumber—Poll interval in ms
refetchOnWindowFocusbooleanfalseRefetch when window regains focus
onSuccess(data: T) => void—Called on successful fetch
onError(error: ArchieError) => void—Called on error

Return:

FieldTypeDescription
dataT | nullQuery result
errorArchieError | nullError if failed
isLoadingbooleanTrue on initial load
isRefetchingbooleanTrue on background refetch
refetch() => Promise<void>Manually trigger refetch

Behaviors:

  • Stale-while-revalidate: Shows cached data immediately, refetches in background
  • Deduplication: Multiple components with the same query+variables share a single network request
  • Auto-refetch on auth change: When the user's token changes, all active queries refetch automatically
  • Auto-refetch on invalidation: A mounted query re-runs as soon as its key is invalidated (e.g. by a useMutation with invalidateQueries); concurrent consumers of the same key still share one request
  • Latest-response-wins: A response that was already in flight when its key got invalidated is discarded instead of repainting the view with pre-mutation data
  • Errors never blank the view: A failed revalidation sets error but keeps the data already on screen
  • Variable tracking: Deep comparison via JSON.stringify — refetches when variables change
  • SSR safe: No window access when refetchOnWindowFocus is disabled

Examples:

// Basic query
const { data, isLoading } = useQuery<{ users: User[] }>('{ users { id email name } }');

// With variables
const { data } = useQuery<{ user: User }>('query($id: ID!) { user(id: $id) { id email } }', {
  id: userId,
});

// Conditional query (wait for userId)
const { data } = useQuery<{ user: User }>(
  'query($id: ID!) { user(id: $id) { id email } }',
  { id: userId },
  { enabled: !!userId },
);

// Polling every 5 seconds
const { data } = useQuery<{ stats: Stats }>('{ stats { activeUsers requests } }', undefined, {
  refetchInterval: 5000,
});

useMutation

Imperative GraphQL mutations with cache invalidation.

const { mutate, mutateAsync, data, error, isLoading, reset } = useMutation<T>(
  gql,       // GraphQL mutation string
  options?,  // UseMutationOptions
);

Options:

OptionTypeDescription
onSuccess(data: T) => voidCalled after successful mutation
onError(error: ArchieError) => voidCalled on error
onSettled() => voidCalled after success or error
invalidateQueriesstring[]Query substrings to invalidate after success — every mounted useQuery whose key matches refetches

Return:

FieldTypeDescription
mutate(vars?) => voidFire-and-forget mutation
mutateAsync(vars?) => Promise<T | null>Await the result
dataT | nullLast successful result
errorArchieError | nullLast error
isLoadingbooleanTrue while executing
reset() => voidClear data/error/isLoading

Example:

const { mutate, isLoading } = useMutation<{ createUser: User }>(
  'mutation($input: CreateUserInput!) { createUser(input: $input) { id email } }',
  {
    invalidateQueries: ['users'],
    onSuccess: (data) => toast.success(`Created ${data.createUser.email}`),
  },
);

const handleCreate = () => {
  mutate({ input: { email: '[email protected]', name: 'John' } });
};

Matching is by substring, so invalidateQueries: ['id'] would refetch every mounted query whose text contains id — each one a real network request. Prefer patterns distinctive enough to name the entity you mutated. Passing several patterns is cheap: the whole array is applied as one batch, so a query matched by more than one of them still refetches only once.


useSubscription

Real-time GraphQL subscriptions via WebSocket. Subscribes on mount, unsubscribes on unmount.

const { data, error, connectionState } = useSubscription<T>(
  gql,         // GraphQL subscription string
  variables?,  // Record<string, unknown>
  options?,    // UseSubscriptionOptions
);

Options:

OptionTypeDefaultDescription
enabledbooleantrueSet false to skip subscription
onData(data: T) => void—Called on each new value
onError(error: ArchieError) => void—Called on error

Return:

FieldTypeDescription
dataT | nullLatest subscription value
errorArchieError | nullLast error
connectionStateConnectionState'DISCONNECTED' | 'CONNECTING' | 'CONNECTED' | 'RECONNECTING'

Example:

function LiveOrders() {
  const { data, connectionState } = useSubscription<{ orderUpdated: Order }>(
    'subscription { orderUpdated { id status total } }',
  );

  return (
    <div>
      <Badge>{connectionState}</Badge>
      {data && <OrderCard order={data.orderUpdated} />}
    </div>
  );
}

Lifecycle: Reconnection is handled automatically by the underlying js-sdk realtime module (exponential backoff).


useFileUpload

File uploads with progress tracking.

const { upload, progress, isUploading, data, error, retry, abort, reset } = useFileUpload();

Return:

FieldTypeDescription
upload(file: File | Blob, opts?: FileUploadOptions) => Promise<FileUploadResult | null>Start upload; resolves the result, or null on failure/abort
progressnumberReal upload progress, 0..100 (jumps 0 to 100 without XMLHttpRequest)
fractionnumberThe same progress as 0..1
isUploadingbooleanTrue while uploading
dataFileUploadResult | null{ fileId, url, filename, mimetype, size } on success (url is null if the lookup failed)
errorArchieError | nullError if failed
retry() => Promise<FileUploadResult | null>Re-run the last upload() with the same file and options
abort() => voidAbort the upload in flight (error.code is REQUEST_ABORTED)
reset() => voidClear all state

FileUploadOptions: { filename?, contentType?, providerType? } (filename and contentType default to the File's; a plain Blob without a type needs contentType). Also { onProgress?, signal? }. error.code is the stable upload code (FILE_TOO_LARGE, UNSUPPORTED_FILE_TYPE, STORAGE_NOT_ENABLED, UPLOAD_AUTH_REQUIRED, UPLOADS_DISABLED, STORAGE_QUOTA_EXCEEDED, REQUEST_ABORTED, TIMEOUT). Progress is real in browsers (XMLHttpRequest); in Node/SSR it jumps 0 to 1.

Example:

function FileUploader() {
  const { upload, progress, isUploading, data, error, reset } = useFileUpload();

  return (
    <div>
      <input
        type="file"
        onChange={(e) => {
          const file = e.target.files?.[0];
          if (file) upload(file, { contentType: file.type, filename: file.name });
        }}
      />
      {isUploading && <ProgressBar value={progress} />}
      {data && <p>Uploaded: {data.url ?? data.fileId}</p>}
      {error && <p className="error">{error.message}</p>}
      {data && <button onClick={reset}>Upload Another</button>}
    </div>
  );
}

useRest

Returns the REST module directly for imperative REST API calls.

const rest = useRest();

// rest.get<T>(path, options?)
// rest.post<T>(path, body?, options?)
// rest.put<T>(path, body?, options?)
// rest.patch<T>(path, body?, options?)
// rest.delete<T>(path, options?)

Example:

function ProductActions() {
  const rest = useRest();

  const handleExport = async () => {
    const csv = await rest.get<string>('/api/products/export');
    downloadCsv(csv);
  };

  return <button onClick={handleExport}>Export CSV</button>;
}

useRestQuery

Declarative hook for GET requests to custom REST endpoints.

const { data, error, isLoading, refetch } = useRestQuery<T>(path, options?);

Options extend RestRequestOptions ({ headers?, params?, signal? }) with enabled?: boolean.

Return:

FieldTypeDescription
dataT | nullResponse data
errorArchieError | nullError if failed
isLoadingbooleanTrue on initial load
refetch() => Promise<void>Manually trigger refetch

Example:

const { data, isLoading } = useRestQuery<Product[]>('/api/products');
const { data: product } = useRestQuery<Product>(`/api/products/${id}`, { enabled: !!id });

useRestMutation

Imperative hook for POST/PUT/PATCH/DELETE requests.

const { execute, data, error, isLoading, reset } = useRestMutation<T>(path, method?);

Params:

ParamTypeDefaultDescription
pathstring—REST endpoint path
method'POST' | 'PUT' | 'PATCH' | 'DELETE''POST'HTTP method

Return:

FieldTypeDescription
execute(body?, overrideOptions?: RestRequestOptions) => Promise<T | null>Execute the request
dataT | nullLast successful result
errorArchieError | nullLast error
isLoadingbooleanTrue while executing
reset() => voidClear data/error/isLoading

The execute function accepts an optional second parameter overrideOptions (RestRequestOptions) to pass custom headers, params, or signal per request.

Example:

const { execute, isLoading } = useRestMutation<Product>('/api/products', 'POST');

const handleCreate = async () => {
  const product = await execute({ name: 'Widget', price: 9.99 });
  if (product) router.push(`/products/${product.id}`);
};

// With custom headers per request
await execute(payload, { headers: { 'x-idempotency-key': uuid() } });

useNotifications

Subscribes to live in-app notifications and manages read state. Opens a WebSocket channel on mount and closes it on unmount.

const { user } = useAuth();
const receiverId = user?.id ?? '';

const {
  notifications,
  latest,
  unreadCount,
  isLoading,
  error,
  connectionState,
  markAsRead,
  markAllAsRead,
  refetch,
} = useNotifications(receiverId, { enabled: !!user, limit: 20 });

Options:

OptionTypeDefaultDescription
enabledbooleantrueSet false to skip loading and subscription (e.g. while user is null)
unreadOnlybooleanfalseInitial load returns only unread notifications
limitnumber—Max items to load in the initial fetch
onNotification(n: NotificationWithSafeUrl) => void—Called for each new live notification (e.g. for toasts or sounds)

Returns:

FieldTypeDescription
notificationsNotification[]History + live arrivals, most-recent first, deduplicated by id
latestNotification | nullLast notification received live (convenient for toasts)
unreadCountnumberLive unread count — updates automatically
isLoadingbooleanTrue during initial history load
errorArchieError | nullError from the latest failed operation
connectionStateConnectionState'CONNECTING' | 'CONNECTED' | 'DISCONNECTED' | 'RECONNECTING'
markAsRead(id: string) => Promise<void>Mark one notification read; updates local state optimistically
markAllAsRead() => Promise<void>Mark all loaded notifications read; reconciles unreadCount with the server (normally 0)
refetch() => Promise<void>Reload the notification history

Infinite scroll

useNotifications pages backward through history without dropping or duplicating live items:

const { notifications, fetchMore, hasMore, isFetchingMore, error } = useNotifications(user.id, {
  limit: 20, // page size for fetchMore()
});

// e.g. an IntersectionObserver on a sentinel at the bottom of the list.
// Gate on `!error` so a failed page doesn't drive a tight auto-retry loop
// (the hook also suppresses fetchMore internally after an error until refetch).
useEffect(() => {
  if (hasMore && !isFetchingMore && !error) void fetchMore();
}, [hasMore, isFetchingMore, error, fetchMore]);
  • fetchMore() — loads the next older page and appends it (deduped, newest-first). No-op when !hasMore, a fetch is already in flight, the hook is disabled, or a previous fetchMore errored (call refetch() — or change receiverId/unreadOnly — to retry).
  • hasMore — more older records exist for the active filter.
  • isFetchingMore — a fetchMore() call is in flight (drive a bottom "loading older" indicator).

Changing receiverId or unreadOnly resets pagination and re-seeds from the first page.

Example — notification bell with unread badge:

import { useAuth, useNotifications } from '@archie/react-sdk';

function NotificationBell() {
  const { user } = useAuth();
  const receiverId = user?.id ?? '';
  const { unreadCount, connectionState } = useNotifications(receiverId, { enabled: !!user });

  return (
    <button aria-label="Notifications">
      <BellIcon />
      {unreadCount > 0 && <span className="badge">{unreadCount > 99 ? '99+' : unreadCount}</span>}
      {connectionState !== 'CONNECTED' && <span className="dot reconnecting" />}
    </button>
  );
}

Example — notification list with mark-as-read:

function NotificationList() {
  const { user } = useAuth();
  const receiverId = user?.id ?? '';
  const { notifications, isLoading, unreadCount, markAsRead, markAllAsRead } = useNotifications(
    receiverId,
    { enabled: !!user, limit: 20 },
  );

  if (isLoading) return <Spinner />;

  return (
    <div>
      <header>
        <span>{unreadCount} unread</span>
        <button onClick={() => markAllAsRead()} disabled={unreadCount === 0}>
          Mark all read
        </button>
      </header>
      <ul>
        {notifications.map((n) => (
          <li
            key={n.id}
            className={n.isRead ? 'read' : 'unread'}
            onClick={() => !n.isRead && markAsRead(n.id)}
          >
            <p>{n.actionText ?? n.notificationKey}</p>
            <time>{new Date(n.createdAt).toLocaleString()}</time>
          </li>
        ))}
      </ul>
    </div>
  );
}

Always call the hook unconditionally (React rules of hooks). When there is no authenticated user, pass receiverId='' and enabled: false — the hook will not load or subscribe.

Security note. Never render the raw n.actionUrl as an href — it is untrusted server data (javascript:, data:, //evil.com, https://[email protected]). Rows returned by useNotifications carry a pre-sanitized safeActionUrl field (string | null), or use the NotificationAction component. Treat n.metadata and n.params as untrusted too. See the js-sdk README Security section for the exact rules of safeActionUrl.

safeActionUrl on each row. Every item in notifications (and latest) is the original notification plus safeActionUrl: string | null. actionUrl itself is left untouched, so this is purely additive.

{
  notifications.map((n) => (
    <li key={n.id}>
      {n.safeActionUrl ? (
        <a href={n.safeActionUrl} rel="noopener noreferrer">
          Open
        </a>
      ) : null}
    </li>
  ));
}

useAIChat

Live chat over Archie AI (client.ai.stream, REST SSE). For one-shot tasks (summarize, classify, extract) prefer the GraphQL aiChat mutation with useMutation.

import { useAIChat } from '@archie/react-sdk';

function Assistant() {
  const { messages, send, stop, status, usage, error } = useAIChat({
    system: 'You help customers with their orders.',
    tools: [
      {
        name: 'getOrders',
        description: 'List the current user orders',
        parameters: { type: 'object', properties: { limit: { type: 'number' } } },
        execute: async ({ limit }) => fetchOrders(Number(limit ?? 5)),
      },
    ],
    metadata: { feature: 'order-assistant' },
  });
  // render messages; send('Where is my order?'); stop() aborts
}
  • messages are in contract shape and update live while the answer streams.
  • providerState from done is kept on the assistant message and resent on the next turn.
  • Tool loop: each tool_call runs tool.execute(args, { toolCall, signal }) (or onToolCall(call, { signal })), its result (strings as-is, other values as JSON; a throw becomes isError: true) is appended as a tool message and the chat continues automatically, up to maxSteps model calls per send() (default 5).
  • status: idle | submitted | streaming | error; usage accumulates over the conversation; error is an AIError (insufficient_credits, rate_limited, ...).
  • stop(), reset(), a new send() and unmount abort the request in flight; an interrupted turn keeps its text and drops unanswered tool calls, so the next turn is valid.
  • Options: model, provider, system, tools, toolChoice, responseFormat, reasoning, maxTokens, temperature, metadata, providerOptions, initialMessages, maxSteps, onToolCall, onFinish, onError.

Components

AuthGuard

Conditional rendering based on authentication state.

<AuthGuard fallback={<LoginPage />} loadingComponent={<Spinner />} requiredRoles={['admin']}>
  <ProtectedContent />
</AuthGuard>

Props:

PropTypeDefaultDescription
childrenReactNode—Content shown when authorized
fallbackReactNodenullContent shown when NOT authenticated or unauthorized
loadingComponentReactNodenullContent shown while session loads
requiredRolesstring[]—User must have at least one of these roles

Behavior:

  1. While isLoading → render loadingComponent
  2. If not authenticated → render fallback
  3. If requiredRoles specified and user lacks all of them → render fallback
  4. Otherwise → render children

Nested guards:

<AuthGuard fallback={<LoginPage />}>
  <AppLayout>
    <AuthGuard requiredRoles={['admin']} fallback={<Unauthorized />}>
      <AdminPanel />
    </AuthGuard>
  </AppLayout>
</AuthGuard>

NotificationAction

Renders a notification's call-to-action safely.

import { NotificationAction } from '@archie/react-sdk';

<NotificationAction notification={n} className="btn" />
<NotificationAction notification={n}>View details</NotificationAction>

Props:

PropTypeDefaultDescription
notification{ actionText?, actionUrl? } (a Notification)—Source of the label and link
classNamestring—Applied to the <a> (or to the <span> when there is no link)
childrenReactNodeactionTextOverrides the label

Behavior:

  1. The URL goes through safeActionUrl (https:// or same-origin /path only).
  2. Safe → <a href rel="noopener noreferrer">; the label is a plain React text child (escaped, never HTML).
  3. Unsafe or missing URL → the label as a <span> with no link.
  4. No label → renders nothing.

Re-exported Types

For convenience, @archie/react-sdk re-exports all essential types from @archie/js-sdk, so you don't need to import from both packages:

import {
  // Client
  createClient,
  type ArchieClient,
  type ArchieClientOptions,

  // Auth types
  type Session,
  type User,
  type AuthSignUpParams,
  type AuthSignInParams,
  type AuthConfirmParams,
  type AuthRecoverParams,
  type AuthResetPasswordParams,
  type AuthEvent,
  type AuthEventCallback,

  // GraphQL types
  type GraphQLResponse,
  type GraphQLRequestOptions,
  type GraphQLRawRequest,

  // File types
  type FileUploadOptions,
  type FileUploadResult,
  type CsvUploadOptions,

  // Realtime types
  type Subscription,
  type SubscriptionCallbacks,
  type RealtimeEvent,
  type RealtimeChannel,
  type ConnectionState,
  type ConnectionStateCallback,

  // REST types
  type RestRequestOptions,

  // Error classes
  ArchieError,
  AuthError,
  GraphQLError,
  NetworkError,

  // Storage
  type StorageAdapter,
  BrowserLocalStorage,
  MemoryStorage,

  // Interfaces (SOLID)
  type IAuthModule,
  type IGraphQLModule,
  type IFileModule,
  type IRealtimeModule,
  type IRestModule,
  type IHttpClient,
  type Logger,
  type TokenAccessor,
} from '@archie/react-sdk';

// React-specific prop types
import type { ArchieProviderProps, AuthGuardProps } from '@archie/react-sdk';

Error Handling

All useAuth methods return { data/session, error } — they never throw. For useQuery and useMutation, errors are exposed via the error field in the return object.

Error classes from @archie/js-sdk:

ClassWhen
AuthErrorAuthentication failures (sign in, sign up, token refresh)
GraphQLErrorGraphQL response errors
NetworkErrorNetwork failures, timeouts
ArchieErrorBase class for all Archie errors
const { error } = useQuery<{ users: User[] }>('{ users { id } }');

if (error) {
  if (error instanceof AuthError) {
    // Token expired, redirect to login
  } else if (error instanceof NetworkError) {
    // Show offline indicator
  } else {
    // Show generic error
  }
}

SSR Compatibility

All hooks are SSR safe:

  • No window or document access on initial render
  • refetchOnWindowFocus checks typeof window before adding listeners
  • ArchieProvider initializes session asynchronously (no blocking SSR)
  • AuthGuard renders loadingComponent during server render when session is unknown

For frameworks like Next.js, create the client outside the component tree:

// lib/archie.ts
import { createClient } from '@archie/js-sdk';

export const archie = createClient({
  projectId: process.env.NEXT_PUBLIC_ARCHIE_PROJECT_ID!,
  apiKey: process.env.NEXT_PUBLIC_ARCHIE_ANON_KEY!,
});

// app/providers.tsx
('use client');
import { ArchieProvider } from '@archie/react-sdk';
import { archie } from '@/lib/archie';

export function Providers({ children }: { children: React.ReactNode }) {
  return <ArchieProvider client={archie}>{children}</ArchieProvider>;
}

Advanced Client Options

@archie/react-sdk inherits all advanced options from @archie/js-sdk via createClient(). These are configured once at client creation and propagate automatically to all hooks:

import { createClient, consoleLogger } from '@archie/js-sdk';

const archie = createClient({
  projectId: 'your-project-uuid',

  // Retry & timeout
  retries: 3, // retry transient errors (429, 500, 502, 503, 504, network)
  retryDelay: 200, // initial backoff delay in ms (exponential with jitter)
  timeout: 15_000, // abort requests after 15 seconds (0 = disabled)

  // Custom fetch — for testing, proxies, or edge runtimes
  fetch: customFetchImpl,

  // Logger
  logger: consoleLogger, // or a custom Logger implementation
});
OptionTypeDefaultDescription
timeoutnumber30000Request timeout in ms (0 = disabled)
retriesnumber0Max retry attempts for transient errors
retryDelaynumber200Initial backoff delay in ms
fetchtypeof fetchglobalThis.fetchCustom fetch implementation

Health Check

The underlying client also exposes ping():

const archie = useArchieClient();

const isUp = await archie.ping();

See the full @archie/js-sdk README for detailed documentation on retry behavior, backoff strategy, timeout semantics, and custom fetch usage.


TypeScript

All hooks are fully generic:

interface User {
  id: string;
  email: string;
  name: string;
}

// Typed query response
const { data } = useQuery<{ users: User[] }>('{ users { id email name } }');
//     ^? { users: User[] } | null

// Typed mutation response
const { mutateAsync } = useMutation<{ createUser: User }>('mutation ...');
const result = await mutateAsync({ input: { email: '[email protected]' } });
//    ^? { createUser: User } | null

// Typed subscription
const { data } = useSubscription<{ orderUpdated: Order }>('subscription ...');
//     ^? { orderUpdated: Order } | null

// Typed REST
const { data } = useRestQuery<Product[]>('/api/products');
//     ^? Product[] | null

License

MIT

版本与发布记录

@archie/react-sdk 共发布 19 个版本,首次发布于 2026/03/19;过去 12 个月发布了 19 个版本。

版本发布日期距今
0.8.0latest2026/11/044 天前
0.7.02026/10/1721 天前
0.6.02026/10/1325 天前
0.5.22026/10/1325 天前
0.5.12026/10/1325 天前
0.5.02026/10/1325 天前

关于 @archie/react-sdk 的常见问题

@archie/react-sdk 是什么?

Archie BAAS - React hooks, provider & components

如何安装 @archie/react-sdk?

在项目目录运行 npm install @archie/react-sdk,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=18.0.0)、@archie/js-sdk(>=0.7.0 <1.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@archie/react-sdk 支持 TypeScript 吗?

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

@archie/react-sdk 支持 ESM 还是 CommonJS?

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

@archie/react-sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@archie/react-sdk 可以商用吗?

@archie/react-sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@archie/react-sdk 的下载量有多少?

根据 npm 官方下载统计,@archie/react-sdk 在 2026/10/07 – 2026/11/05 这 30 天内被下载 6,390 次,日均约 213 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@archie/react-sdk 还在维护吗?

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