@spfn/core
Version:
SPFN Framework Core - File-based routing, transactions, repository pattern
213 lines (209 loc) • 5.91 kB
TypeScript
import '../auto-loader-JFaZ9gON.js';
import { a as RouteContract, I as InferContract } from '../types-DYueuoD6.js';
import 'hono';
import 'hono/utils/http-status';
import '@sinclair/typebox';
import '../error-handler-wjLL3v-a.js';
/**
* Contract-Based API Client
*
* Type-safe HTTP client that works with RouteContract for full end-to-end type safety
*/
type RequestInterceptor = (url: string, init: RequestInit) => Promise<RequestInit> | RequestInit;
interface ClientConfig {
/**
* API base URL (e.g., http://localhost:4000)
*/
baseUrl?: string;
/**
* Default headers to include in all requests
*/
headers?: Record<string, string>;
/**
* Request timeout in milliseconds
*/
timeout?: number;
/**
* Custom fetch implementation
*/
fetch?: typeof fetch;
}
interface CallOptions<TContract extends RouteContract> {
params?: InferContract<TContract>['params'];
query?: InferContract<TContract>['query'];
body?: InferContract<TContract>['body'];
headers?: Record<string, string>;
baseUrl?: string;
}
/**
* API Client Error
*/
declare class ApiClientError extends Error {
readonly status: number;
readonly url: string;
readonly response?: unknown | undefined;
readonly errorType?: "timeout" | "network" | "http" | undefined;
constructor(message: string, status: number, url: string, response?: unknown | undefined, errorType?: "timeout" | "network" | "http" | undefined);
}
/**
* Contract-based API Client
*/
declare class ContractClient {
private readonly config;
private readonly interceptors;
constructor(config?: ClientConfig);
/**
* Add request interceptor
*/
use(interceptor: RequestInterceptor): void;
/**
* Make a type-safe API call using a contract
*
* @param contract - Route contract with absolute path
* @param options - Call options (params, query, body, headers)
*/
call<TContract extends RouteContract>(contract: TContract, options?: CallOptions<TContract>): Promise<InferContract<TContract>['response']>;
/**
* Create a new client with merged configuration
*/
withConfig(config: Partial<ClientConfig>): ContractClient;
private static buildUrl;
private static buildQuery;
private static getHttpMethod;
private static isFormData;
}
/**
* Create a new contract-based API client
*/
declare function createClient(config?: ClientConfig): ContractClient;
/**
* Configure the global client instance
*
* Call this in your app initialization to set default configuration
* for all auto-generated API calls.
*
* @example
* ```ts
* // In app initialization (layout.tsx, _app.tsx, etc)
* import { configureClient } from '@spfn/core/client';
*
* configureClient({
* baseUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000',
* timeout: 60000,
* headers: {
* 'X-App-Version': '1.0.0'
* }
* });
*
* // Add interceptors
* import { client } from '@spfn/core/client';
* client.use(async (url, init) => {
* // Add auth header
* return {
* ...init,
* headers: {
* ...init.headers,
* Authorization: `Bearer ${getToken()}`
* }
* };
* });
* ```
*/
declare function configureClient(config: ClientConfig): void;
/**
* Global client singleton with Proxy
*
* This client can be configured using configureClient() before use.
* Used by auto-generated API client code.
*/
declare const client: ContractClient;
/**
* Type guard for timeout errors
*
* @example
* ```ts
* try {
* await api.users.getById({ params: { id: '123' } });
* } catch (error) {
* if (isTimeoutError(error)) {
* console.error('Request timed out, retrying...');
* // Implement retry logic
* }
* }
* ```
*/
declare function isTimeoutError(error: unknown): error is ApiClientError;
/**
* Type guard for network errors
*
* @example
* ```ts
* try {
* await api.users.list();
* } catch (error) {
* if (isNetworkError(error)) {
* showOfflineMessage();
* }
* }
* ```
*/
declare function isNetworkError(error: unknown): error is ApiClientError;
/**
* Type guard for HTTP errors (4xx, 5xx)
*
* @example
* ```ts
* try {
* await api.users.create({ body: userData });
* } catch (error) {
* if (isHttpError(error)) {
* if (error.status === 401) {
* redirectToLogin();
* } else if (error.status === 404) {
* showNotFoundMessage();
* }
* }
* }
* ```
*/
declare function isHttpError(error: unknown): error is ApiClientError;
/**
* Check if error is a specific server error type
*
* @example
* ```ts
* try {
* await api.workflows.getById({ params: { uuid: 'xxx' } });
* } catch (error) {
* if (isServerError(error, 'NotFoundError')) {
* showNotFoundMessage();
* } else if (isServerError(error, 'ValidationError')) {
* showValidationErrors(getServerErrorDetails(error));
* }
* }
* ```
*/
declare function isServerError(error: unknown, errorType: string): error is ApiClientError;
/**
* Get server error type from ApiClientError
*
* @example
* ```ts
* const errorType = getServerErrorType(error);
* // 'NotFoundError', 'ValidationError', 'PaymentFailedError', etc.
* ```
*/
declare function getServerErrorType(error: ApiClientError): string | undefined;
/**
* Get server error details from ApiClientError
*
* @example
* ```ts
* if (isServerError(error, 'PaymentFailedError')) {
* const details = getServerErrorDetails(error);
* console.log('Payment ID:', details.paymentId);
* }
* ```
*/
declare function getServerErrorDetails<T = any>(error: ApiClientError): T | undefined;
export { ApiClientError, type CallOptions, type ClientConfig, ContractClient, type RequestInterceptor, client, configureClient, createClient, getServerErrorDetails, getServerErrorType, isHttpError, isNetworkError, isServerError, isTimeoutError };