zlient 1.0.9 → 1.0.11
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/dist/endpoint/base-endpoint.d.ts +33 -11
- package/dist/endpoint/base-endpoint.d.ts.map +1 -1
- package/dist/http/http-client.d.ts +13 -0
- package/dist/http/http-client.d.ts.map +1 -1
- package/dist/index.cjs +79 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +79 -41
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/metrics.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["z","s","opts: { header?: string; query?: string; value: string }","getToken: () => Promise<string> | string","minLevel: LogLevel","logger: Logger","k: string","err: unknown","init: RequestInit & { __urlOverride?: string }","timeoutId: NodeJS.Timeout | number | undefined","client: HttpClient","z"],"sources":["../lib/types.ts","../lib/auth.ts","../lib/validation.ts","../lib/logger.ts","../lib/metrics.ts","../lib/http/http-client.ts","../lib/endpoint/base-endpoint.ts","../lib/schemas/common.ts"],"sourcesContent":["import { z, ZodError } from 'zod';\nimport { AuthProvider } from './auth';\nimport { Logger } from './logger';\nimport { MetricsCollector } from './metrics';\n\nexport type Dictionary<T = unknown> = Record<string, T>;\n\nexport type FetchLike = (input: string | Request | URL, init?: RequestInit) => Promise<Response>;\n\n/**\n * Map of base URLs for different services.\n * The 'default' key is required and used when no specific key is provided.\n * \n * @example\n * ```ts\n * {\n * default: 'https://api.example.com',\n * auth: 'https://auth.example.com',\n * cdn: 'https://cdn.example.com'\n * }\n * ```\n */\nexport type BaseUrlMap = {\n default: string;\n [service: string]: string;\n};\n\n/**\n * Configuration for retry behavior on failed requests.\n * Implements exponential backoff with optional jitter.\n * \n * @example\n * ```ts\n * {\n * maxRetries: 3,\n * baseDelayMs: 1000,\n * jitter: 0.2,\n * retryMethods: ['GET', 'HEAD', 'PUT']\n * }\n * ```\n */\nexport type RetryStrategy = {\n /** Maximum number of retry attempts */\n maxRetries: number;\n /** Base delay in milliseconds (will be exponentially increased) */\n baseDelayMs: number;\n /** Jitter factor 0..1 to randomize delays and prevent thundering herd */\n jitter?: number;\n /** HTTP methods eligible for retry */\n retryMethods?: (keyof typeof HTTPMethod)[];\n /** Custom function to determine if a request should be retried */\n shouldRetry?: (ctx: { attempt: number; error?: unknown; response?: Response }) => boolean;\n};\n\nexport const HTTPMethod = {\n GET: 'GET',\n POST: 'POST',\n PUT: 'PUT',\n PATCH: 'PATCH',\n DELETE: 'DELETE',\n HEAD: 'HEAD',\n OPTIONS: 'OPTIONS',\n} as const;\n\nexport type HttpMethod = keyof typeof HTTPMethod;\n\n/**\n * Hook called after a response is received and parsed.\n * Useful for logging, metrics, or global error handling.\n */\nexport type AfterResponseHook = (ctx: {\n request: Request;\n response: Response;\n parsed?: unknown;\n}) => Promise<void> | void;\n\n/**\n * Hook called before a request is sent.\n * Useful for logging, adding headers, or modifying the request.\n */\nexport type BeforeRequestHook = (ctx: { url: string; init: RequestInit }) => Promise<void> | void;\n\nexport interface Interceptors {\n /** Hooks executed before each request is sent */\n beforeRequest?: BeforeRequestHook[];\n /** Hooks executed after each response is received */\n afterResponse?: AfterResponseHook[];\n}\n\nexport interface TimeoutOptions {\n /** Request timeout in milliseconds */\n requestTimeoutMs?: number;\n}\n\n/**\n * Configuration options for the HTTP client.\n * \n * @example\n * ```ts\n * const options: ClientOptions = {\n * baseUrls: { default: 'https://api.example.com' },\n * headers: { 'X-API-Version': '1.0' },\n * retry: { maxRetries: 3, baseDelayMs: 1000 },\n * timeout: { requestTimeoutMs: 30000 }\n * }\n * ```\n */\nexport interface ClientOptions {\n /** Map of base URLs for different services */\n baseUrls: BaseUrlMap;\n /** Custom fetch implementation (defaults to globalThis.fetch) */\n fetch?: FetchLike;\n /** Default headers applied to all requests */\n headers?: Record<string, string>;\n /** Retry strategy configuration */\n retry?: RetryStrategy;\n /** Request/response interceptors */\n interceptors?: Interceptors;\n /** Timeout configuration */\n timeout?: TimeoutOptions;\n /** Authentication provider */\n auth?: AuthProvider;\n /** Logger instance */\n logger?: Logger;\n /** Metrics collector */\n metrics?: MetricsCollector;\n}\n\nexport type SafeParseResult<T> = { success: true; data: T } | { success: false; error: ZodError };\n\n/**\n * Custom error class for API-related errors.\n * Includes HTTP status codes, response details, and validation errors.\n * \n * @example\n * ```ts\n * throw new ApiError('Invalid request', {\n * status: 400,\n * details: { field: 'email', message: 'Invalid format' }\n * });\n * ```\n */\nexport class ApiError extends Error {\n public status?: number;\n public details?: unknown;\n public zodError?: ZodError;\n\n constructor(\n message: string,\n options?: { status?: number; cause?: unknown; details?: unknown; zodError?: ZodError }\n ) {\n super(message);\n this.name = 'ApiError';\n this.status = options?.status;\n this.details = options?.details;\n this.cause = options?.cause as any;\n this.zodError = options?.zodError;\n\n // Maintains proper stack trace for where error was thrown\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, ApiError);\n }\n }\n\n /**\n * Check if this is a validation error (has zodError)\n */\n isValidationError(): boolean {\n return !!this.zodError;\n }\n\n /**\n * Check if this is a client error (4xx status)\n */\n isClientError(): boolean {\n return !!this.status && this.status >= 400 && this.status < 500;\n }\n\n /**\n * Check if this is a server error (5xx status)\n */\n isServerError(): boolean {\n return !!this.status && this.status >= 500;\n }\n\n /**\n * Get a formatted error message with all available details\n */\n toJSON() {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n details: this.details,\n zodError: this.zodError?.issues,\n stack: this.stack,\n };\n }\n}\n\nexport type Paginated<T> = {\n items: T[];\n total: number;\n page: number;\n pageSize: number;\n};\n\n/**\n * Schema for paginated responses\n */\nexport const PaginationSchema = z.object({\n items: z.array(z.unknown()),\n total: z.number().int().nonnegative(),\n page: z.number().int().nonnegative(),\n pageSize: z.number().int().positive(),\n});\n\n/**\n * Options that can be passed to individual requests to override defaults.\n * \n * @example\n * ```ts\n * await endpoint.call(data, {\n * baseUrlKey: 'v2',\n * headers: { 'X-Custom': 'value' },\n * query: { filter: 'active' }\n * });\n * ```\n */\nexport type RequestOptions = {\n /** Override base URL for a single call */\n baseUrlKey?: keyof BaseUrlMap;\n /** Additional headers for this call only */\n headers?: Record<string, string>;\n /** Abort controller signal for cancellation */\n signal?: AbortSignal;\n /** Custom query params */\n query?: URLSearchParams | Record<string, string | number | boolean | undefined>;\n};\n\n/**\n * Converts query parameters to a URL query string.\n * Filters out undefined values automatically.\n * \n * @param q - Query parameters as URLSearchParams or object\n * @returns Query string with leading '?' or empty string\n * \n * @example\n * ```ts\n * toQueryString({ page: 1, filter: 'active' }) // \"?page=1&filter=active\"\n * toQueryString({ optional: undefined }) // \"\"\n * ```\n */\nexport function toQueryString(q?: RequestOptions['query']): string {\n if (!q) return '';\n if (q instanceof URLSearchParams) {\n const s = q.toString();\n return s ? `?${s}` : '';\n }\n const params = new URLSearchParams();\n Object.entries(q).forEach(([k, v]) => {\n if (v !== undefined) {\n params.append(k, String(v));\n }\n });\n const s = params.toString();\n return s ? `?${s}` : '';\n}\n","import type { RequestOptions as ReqOpts } from './types';\n\n/**\n * Extended RequestInit with URL override capability for query-based auth.\n */\nexport interface AuthContext {\n url: string;\n init: RequestInit & { __urlOverride?: string };\n options?: ReqOpts;\n}\n\n/**\n * Interface for authentication providers.\n * Implement this to create custom authentication strategies.\n * \n * @example\n * ```ts\n * class CustomAuth implements AuthProvider {\n * async apply({ init }) {\n * init.headers = { ...init.headers, 'X-Custom-Auth': 'token' };\n * }\n * }\n * ```\n */\nexport interface AuthProvider {\n /**\n * Apply authentication to the outgoing request.\n * Called after SDK headers are assembled, but before request is sent.\n * \n * @param req - Request context including URL, init, and options\n */\n apply(req: AuthContext): Promise<void> | void;\n}\n\n/**\n * No-op authentication provider (no authentication applied).\n * Use this when you don't need authentication.\n */\nexport class NoAuth implements AuthProvider {\n async apply() {\n /* no-op */\n }\n}\n\n/**\n * API Key authentication provider.\n * Supports both header-based and query parameter-based authentication.\n * \n * @example\n * ```ts\n * // Header-based\n * const auth = new ApiKeyAuth({ header: 'X-API-Key', value: 'secret' });\n * \n * // Query parameter-based\n * const auth = new ApiKeyAuth({ query: 'apiKey', value: 'secret' });\n * ```\n */\nexport class ApiKeyAuth implements AuthProvider {\n constructor(private opts: { header?: string; query?: string; value: string }) {\n if (!opts.header && !opts.query) {\n throw new Error('ApiKeyAuth requires either \"header\" or \"query\" option');\n }\n if (opts.header && opts.query) {\n throw new Error('ApiKeyAuth cannot use both \"header\" and \"query\" options');\n }\n }\n apply({ url, init }: AuthContext) {\n if (this.opts.header) {\n init.headers = { ...(init.headers as any), [this.opts.header]: this.opts.value };\n } else if (this.opts.query) {\n const u = new URL(url);\n u.searchParams.set(this.opts.query, this.opts.value);\n init.__urlOverride = u.toString();\n }\n }\n}\n\n/**\n * Bearer token authentication provider.\n * Supports both static tokens and dynamic token fetching (e.g., for OAuth2 refresh).\n * \n * @example\n * ```ts\n * // Static token\n * const auth = new BearerTokenAuth(() => 'my-token');\n * \n * // Dynamic token with refresh\n * const auth = new BearerTokenAuth(async () => {\n * return await refreshAccessToken();\n * });\n * ```\n */\nexport class BearerTokenAuth implements AuthProvider {\n constructor(private getToken: () => Promise<string> | string) { }\n async apply({ init }: AuthContext) {\n const token = await this.getToken();\n if (!token) {\n throw new Error('BearerTokenAuth: token is empty or undefined');\n }\n init.headers = { ...(init.headers as any), Authorization: `Bearer ${token}` };\n }\n}\n","import { z } from 'zod';\nimport { ApiError, SafeParseResult } from './types';\n\n/**\n * Safely parse data with a Zod schema without throwing.\n * Returns a result object with success status and data or error.\n * \n * @param schema - Zod schema to validate against\n * @param data - Data to validate\n * @returns Result object with success flag and data/error\n * \n * @example\n * ```ts\n * const result = safeParse(UserSchema, userData);\n * if (result.success) {\n * console.log(result.data);\n * } else {\n * console.error(result.error);\n * }\n * ```\n */\nexport function safeParse<T>(schema: z.ZodType, data: unknown): SafeParseResult<T> {\n const res = schema.safeParse(data);\n if (res.success) return { success: true, data: res.data as T };\n return { success: false, error: res.error };\n}\n\n/**\n * Parse data with a Zod schema, throwing an ApiError on validation failure.\n * Use this when you want to fail fast on invalid data.\n * \n * @param schema - Zod schema to validate against\n * @param data - Data to validate\n * @returns Validated and typed data\n * @throws {ApiError} If validation fails\n * \n * @example\n * ```ts\n * try {\n * const user = parseOrThrow(UserSchema, userData);\n * console.log(user);\n * } catch (error) {\n * if (error instanceof ApiError && error.zodError) {\n * console.error('Validation failed:', error.zodError.issues);\n * }\n * }\n * ```\n */\nexport function parseOrThrow<T>(schema: z.ZodType, data: unknown): T {\n const res = schema.safeParse(data);\n if (!res.success) {\n throw new ApiError('Response validation failed', { zodError: res.error });\n }\n return res.data as T;\n}\n","/**\n * Log levels for structured logging.\n */\nexport enum LogLevel {\n DEBUG = 'debug',\n INFO = 'info',\n WARN = 'warn',\n ERROR = 'error',\n}\n\n/**\n * Structured log entry with metadata.\n */\nexport interface LogEntry {\n level: LogLevel;\n message: string;\n timestamp: string;\n context?: Record<string, unknown>;\n error?: Error;\n}\n\n/**\n * Logger interface for custom logger implementations.\n * Implement this to integrate with your logging infrastructure.\n * \n * @example\n * ```ts\n * class ConsoleLogger implements Logger {\n * log(entry: LogEntry) {\n * console.log(JSON.stringify(entry));\n * }\n * }\n * ```\n */\nexport interface Logger {\n log(entry: LogEntry): void;\n}\n\n/**\n * Default console logger implementation.\n * Formats log entries as JSON for easy parsing.\n */\nexport class ConsoleLogger implements Logger {\n constructor(private minLevel: LogLevel = LogLevel.INFO) { }\n\n log(entry: LogEntry): void {\n const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR];\n const entryLevelIndex = levels.indexOf(entry.level);\n const minLevelIndex = levels.indexOf(this.minLevel);\n\n if (entryLevelIndex < minLevelIndex) return;\n\n const output = {\n ...entry,\n error: entry.error\n ? {\n message: entry.error.message,\n stack: entry.error.stack,\n name: entry.error.name,\n }\n : undefined,\n };\n\n switch (entry.level) {\n case LogLevel.DEBUG:\n console.debug(JSON.stringify(output));\n break;\n case LogLevel.INFO:\n console.info(JSON.stringify(output));\n break;\n case LogLevel.WARN:\n console.warn(JSON.stringify(output));\n break;\n case LogLevel.ERROR:\n console.error(JSON.stringify(output));\n break;\n }\n }\n}\n\n/**\n * No-op logger that discards all log entries.\n * Use this in production if you don't want any logging.\n */\nexport class NoOpLogger implements Logger {\n log(_entry: LogEntry): void {\n // no-op\n }\n}\n\n/**\n * Utility class for creating structured log entries.\n */\nexport class LoggerUtil {\n constructor(private logger: Logger) { }\n\n debug(message: string, context?: Record<string, unknown>): void {\n this.logger.log({\n level: LogLevel.DEBUG,\n message,\n timestamp: new Date().toISOString(),\n context,\n });\n }\n\n info(message: string, context?: Record<string, unknown>): void {\n this.logger.log({\n level: LogLevel.INFO,\n message,\n timestamp: new Date().toISOString(),\n context,\n });\n }\n\n warn(message: string, context?: Record<string, unknown>): void {\n this.logger.log({\n level: LogLevel.WARN,\n message,\n timestamp: new Date().toISOString(),\n context,\n });\n }\n\n error(message: string, error?: Error, context?: Record<string, unknown>): void {\n this.logger.log({\n level: LogLevel.ERROR,\n message,\n timestamp: new Date().toISOString(),\n context,\n error,\n });\n }\n}\n","/**\n * Metrics data for HTTP requests.\n */\nexport interface RequestMetrics {\n method: string;\n path: string;\n status?: number;\n durationMs: number;\n timestamp: string;\n success: boolean;\n error?: string;\n}\n\n/**\n * Interface for metrics collectors.\n * Implement this to integrate with your monitoring infrastructure (DataDog, Prometheus, etc.).\n * \n * @example\n * ```ts\n * class DataDogMetrics implements MetricsCollector {\n * collect(metrics: RequestMetrics) {\n * dogstatsd.histogram('http.request.duration', metrics.durationMs, {\n * method: metrics.method,\n * status: String(metrics.status)\n * });\n * }\n * }\n * ```\n */\nexport interface MetricsCollector {\n collect(metrics: RequestMetrics): void;\n}\n\n/**\n * No-op metrics collector that discards all metrics.\n */\nexport class NoOpMetricsCollector implements MetricsCollector {\n collect(_metrics: RequestMetrics): void {\n // no-op\n }\n}\n\n/**\n * In-memory metrics collector for testing and development.\n * Stores metrics in memory with configurable retention.\n */\nexport class InMemoryMetricsCollector implements MetricsCollector {\n private metrics: RequestMetrics[] = [];\n private readonly maxEntries: number;\n\n constructor(maxEntries = 1000) {\n this.maxEntries = maxEntries;\n }\n\n collect(metrics: RequestMetrics): void {\n this.metrics.push(metrics);\n if (this.metrics.length > this.maxEntries) {\n this.metrics.shift();\n }\n }\n\n /**\n * Get all collected metrics.\n */\n getMetrics(): RequestMetrics[] {\n return [...this.metrics];\n }\n\n /**\n * Get metrics summary statistics.\n */\n getSummary(): {\n total: number;\n successful: number;\n failed: number;\n avgDurationMs: number;\n minDurationMs: number;\n maxDurationMs: number;\n } {\n if (this.metrics.length === 0) {\n return {\n total: 0,\n successful: 0,\n failed: 0,\n avgDurationMs: 0,\n minDurationMs: 0,\n maxDurationMs: 0,\n };\n }\n\n const successful = this.metrics.filter((m) => m.success).length;\n const durations = this.metrics.map((m) => m.durationMs);\n const sum = durations.reduce((a, b) => a + b, 0);\n\n return {\n total: this.metrics.length,\n successful,\n failed: this.metrics.length - successful,\n avgDurationMs: sum / this.metrics.length,\n minDurationMs: Math.min(...durations),\n maxDurationMs: Math.max(...durations),\n };\n }\n\n /**\n * Clear all collected metrics.\n */\n clear(): void {\n this.metrics = [];\n }\n}\n\n/**\n * Console-based metrics collector for debugging.\n */\nexport class ConsoleMetricsCollector implements MetricsCollector {\n collect(metrics: RequestMetrics): void {\n console.log('[METRICS]', JSON.stringify(metrics));\n }\n}\n","import type { AuthProvider } from '../auth';\nimport { NoAuth } from '../auth';\nimport { LoggerUtil, NoOpLogger } from '../logger';\nimport { MetricsCollector, NoOpMetricsCollector } from '../metrics';\nimport {\n ApiError,\n ClientOptions,\n FetchLike,\n HTTPMethod,\n Interceptors,\n RequestOptions,\n RetryStrategy,\n toQueryString,\n} from '../types';\n\n/**\n * HTTP client with built-in retry logic, authentication, and interceptors.\n * Supports multiple base URLs, type-safe requests, and comprehensive error handling.\n * \n * @example\n * ```ts\n * const client = new HttpClient({\n * baseUrls: { default: 'https://api.example.com' },\n * headers: { 'Content-Type': 'application/json' },\n * retry: { maxRetries: 3, baseDelayMs: 1000 },\n * timeout: { requestTimeoutMs: 30000 }\n * });\n * \n * const { data } = await client.request('GET', '/users', undefined, { query: { page: 1 } });\n * ```\n */\nexport class HttpClient {\n private fetchImpl: FetchLike;\n private baseUrls: ClientOptions['baseUrls'];\n private headers: Record<string, string>;\n private interceptors: Interceptors;\n private retry: RetryStrategy;\n private timeoutMs?: number;\n private auth: AuthProvider;\n private logger: LoggerUtil;\n private metrics: MetricsCollector;\n\n /**\n * Creates a new HTTP client instance.\n * \n * @param opts - Client configuration options\n * @throws {Error} If no fetch implementation is available\n */\n constructor(opts: ClientOptions) {\n this.fetchImpl = opts.fetch ?? (globalThis.fetch?.bind(globalThis) as FetchLike);\n if (!this.fetchImpl)\n throw new Error('No fetch implementation found. Pass one via options.fetch.');\n\n // Validate baseUrls configuration\n if (!opts.baseUrls || typeof opts.baseUrls !== 'object') {\n throw new Error('baseUrls must be provided and must be an object');\n }\n if (!opts.baseUrls.default) {\n throw new Error('baseUrls must include a \"default\" key');\n }\n\n this.baseUrls = opts.baseUrls;\n this.headers = opts.headers ?? { 'Content-Type': 'application/json' };\n this.interceptors = opts.interceptors ?? {};\n this.retry = opts.retry ?? {\n maxRetries: 2,\n baseDelayMs: 250,\n jitter: 0.2,\n retryMethods: ['GET', 'HEAD'],\n };\n\n // Validate retry configuration\n if (this.retry.maxRetries < 0) {\n throw new Error('retry.maxRetries must be non-negative');\n }\n if (this.retry.baseDelayMs < 0) {\n throw new Error('retry.baseDelayMs must be non-negative');\n }\n if (this.retry.jitter !== undefined && (this.retry.jitter < 0 || this.retry.jitter > 1)) {\n throw new Error('retry.jitter must be between 0 and 1');\n }\n\n this.timeoutMs = opts.timeout?.requestTimeoutMs;\n if (this.timeoutMs !== undefined && this.timeoutMs < 0) {\n throw new Error('timeout.requestTimeoutMs must be non-negative');\n }\n\n this.auth = opts['auth'] ?? new NoAuth();\n this.logger = new LoggerUtil(opts.logger ?? new NoOpLogger());\n this.metrics = opts.metrics ?? new NoOpMetricsCollector();\n }\n\n /**\n * Set or update the authentication provider.\n * \n * @param auth - Authentication provider instance\n * @example\n * ```ts\n * client.setAuth(new BearerTokenAuth(() => getToken()));\n * ```\n */\n setAuth(auth: AuthProvider) {\n this.auth = auth;\n }\n\n private resolveBaseUrl(key?: keyof typeof this.baseUrls) {\n const k: string = (key as string) || 'default';\n const url = this.baseUrls[k];\n if (!url) {\n const availableKeys = Object.keys(this.baseUrls).join(', ');\n throw new Error(`Unknown baseUrl key: \"${k}\". Available keys: ${availableKeys}`);\n }\n return url.replace(/\\/$/, '');\n }\n\n /**\n * Sleep for a specified duration (used for retry backoff).\n * @private\n */\n private sleep(ms: number) {\n return new Promise((res) => setTimeout(res, ms));\n }\n\n /**\n * Execute a function with retry logic and exponential backoff.\n * @private\n */\n private async withRetry<T>(\n fn: () => Promise<T>,\n canRetry: (ctx: { attempt: number; error?: unknown; response?: Response }) => boolean\n ): Promise<T> {\n let attempt = 0;\n const { maxRetries, baseDelayMs, jitter = 0.2 } = this.retry;\n while (true) {\n try {\n return await fn();\n } catch (err: unknown) {\n if (attempt >= maxRetries || !canRetry({ attempt, error: err })) throw err;\n const backoff = baseDelayMs * 2 ** attempt;\n const j = 1 + (Math.random() * 2 - 1) * jitter;\n await this.sleep(backoff * j);\n attempt++;\n }\n }\n }\n\n /**\n * Run all registered before-request hooks.\n * @private\n */\n private async runBeforeHooks(url: string, init: RequestInit & { __urlOverride?: string }) {\n for (const h of this.interceptors.beforeRequest ?? []) {\n await h({ url, init });\n }\n }\n\n /**\n * Run all registered after-response hooks.\n * @private\n */\n private async runAfterHooks(req: Request, res: Response, parsed?: unknown) {\n for (const h of this.interceptors.afterResponse ?? []) {\n await h({ request: req, response: res, parsed });\n }\n }\n\n /**\n * Make an HTTP request with automatic retry, authentication, and validation.\n * \n * @param method - HTTP method (GET, POST, PUT, etc.)\n * @param path - Request path (will be appended to base URL)\n * @param body - Request body (will be JSON.stringify'd if Content-Type is json)\n * @param options - Additional request options (headers, query params, etc.)\n * @returns Promise resolving to response data and Response object\n * @throws {ApiError} If request fails or response validation fails\n * \n * @example\n * ```ts\n * const { data, response } = await client.request('GET', '/users', undefined, {\n * query: { page: 1, limit: 10 },\n * headers: { 'X-Custom': 'value' }\n * });\n * ```\n */\n async request<T = unknown>(\n method: keyof typeof HTTPMethod,\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<{ data: T; response: Response }> {\n const startTime = Date.now();\n const base = this.resolveBaseUrl(options?.baseUrlKey);\n let url = `${base}${path}${toQueryString(options?.query)}`;\n\n this.logger.debug('HTTP request initiated', {\n method,\n path,\n baseUrlKey: options?.baseUrlKey,\n hasBody: body !== undefined,\n });\n\n const headers = { ...this.headers, ...(options?.headers ?? {}) };\n\n const controller = new AbortController();\n const signal = options?.signal ?? controller.signal;\n const init: RequestInit & { __urlOverride?: string } = {\n method,\n headers,\n body:\n body != null\n ? headers['Content-Type']?.includes('json')\n ? JSON.stringify(body)\n : String(body)\n : undefined,\n signal,\n };\n\n await this.auth.apply({ url, init, options });\n if (init.__urlOverride) url = init.__urlOverride;\n await this.runBeforeHooks(url, init);\n\n const doFetch = async () => {\n // Apply timeout if configured\n let timeoutId: NodeJS.Timeout | number | undefined;\n if (this.timeoutMs && !options?.signal) {\n timeoutId = setTimeout(() => {\n const timeoutError = new Error('Request timeout');\n timeoutError.name = 'TimeoutError';\n controller.abort(timeoutError);\n }, this.timeoutMs);\n }\n\n try {\n const req = new Request(url, init);\n\n const res = await this.fetchImpl(req);\n if (!res.ok) {\n // Read response safely\n let text = '';\n try {\n text = await res.text();\n } catch (readError) {\n text = `Failed to read response: ${readError instanceof Error ? readError.message : String(readError)}`;\n }\n throw new ApiError(`HTTP ${res.status}: ${res.statusText}`, {\n status: res.status,\n details: text,\n });\n }\n const contentType = res.headers.get('content-type') || '';\n const data = contentType.includes('json') ? await res.json() : await res.text();\n await this.runAfterHooks(new Request(url, init), res, data);\n\n const duration = Date.now() - startTime;\n this.logger.info('HTTP request successful', {\n method,\n url,\n status: res.status,\n durationMs: duration,\n });\n\n this.metrics.collect({\n method,\n path,\n status: res.status,\n durationMs: duration,\n timestamp: new Date().toISOString(),\n success: true,\n });\n\n return { data: data as T, response: res };\n } catch (error) {\n const duration = Date.now() - startTime;\n this.logger.error('HTTP request failed', error as Error, {\n method,\n url,\n durationMs: duration,\n });\n\n this.metrics.collect({\n method,\n path,\n status: error instanceof ApiError ? error.status : undefined,\n durationMs: duration,\n timestamp: new Date().toISOString(),\n success: false,\n error: error instanceof Error ? error.message : String(error),\n });\n\n throw error;\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n }\n };\n\n const canRetry = ({\n response,\n error,\n }: {\n response?: Response;\n error?: unknown;\n attempt: number;\n }) => {\n // Don't retry timeouts or aborts\n if (error && typeof error === 'object' && 'name' in error) {\n const errorName = (error as { name?: string }).name;\n if (errorName === 'AbortError' || errorName === 'TimeoutError') return false;\n }\n // Retry on network errors or 5xx\n if (error instanceof ApiError && error.status && error.status >= 500) return true;\n if (error && !response) return true; // network error\n return false;\n };\n\n if (!this.retry.retryMethods?.includes(method)) {\n return doFetch();\n }\n return this.withRetry(doFetch, canRetry);\n }\n\n /**\n * Convenience method for GET requests.\n * \n * @example\n * ```ts\n * const { data } = await client.get('/users', { query: { page: 1 } });\n * ```\n */\n async get<T = unknown>(path: string, options?: RequestOptions): Promise<{ data: T; response: Response }> {\n return this.request<T>('GET', path, undefined, options);\n }\n\n /**\n * Convenience method for POST requests.\n * \n * @example\n * ```ts\n * const { data } = await client.post('/users', { name: 'John' });\n * ```\n */\n async post<T = unknown>(path: string, body?: unknown, options?: RequestOptions): Promise<{ data: T; response: Response }> {\n return this.request<T>('POST', path, body, options);\n }\n\n /**\n * Convenience method for PUT requests.\n * \n * @example\n * ```ts\n * const { data } = await client.put('/users/1', { name: 'John Updated' });\n * ```\n */\n async put<T = unknown>(path: string, body?: unknown, options?: RequestOptions): Promise<{ data: T; response: Response }> {\n return this.request<T>('PUT', path, body, options);\n }\n\n /**\n * Convenience method for PATCH requests.\n * \n * @example\n * ```ts\n * const { data } = await client.patch('/users/1', { name: 'John' });\n * ```\n */\n async patch<T = unknown>(path: string, body?: unknown, options?: RequestOptions): Promise<{ data: T; response: Response }> {\n return this.request<T>('PATCH', path, body, options);\n }\n\n /**\n * Convenience method for DELETE requests.\n * \n * @example\n * ```ts\n * const { data } = await client.delete('/users/1');\n * ```\n */\n async delete<T = unknown>(path: string, options?: RequestOptions): Promise<{ data: T; response: Response }> {\n return this.request<T>('DELETE', path, undefined, options);\n }\n}\n","import { z } from 'zod';\nimport { HttpClient } from '../http/http-client';\nimport { HTTPMethod } from '../types';\nimport { parseOrThrow } from '../validation';\n\n/**\n * Request configuration for endpoint calls.\n * Wrapper object containing all request parameters.\n *\n * @template ReqSchema - Zod schema for request validation\n */\nexport type EndpointCallConfig<ReqSchema extends z.ZodType> = {\n /** Request body data (for POST, PUT, PATCH, etc.) or request args */\n data?: z.infer<ReqSchema>;\n /** Path parameters for dynamic path construction */\n pathParams?: Record<string, string | number>;\n /** Query string parameters */\n query?: Record<string, string | number | boolean | undefined> | URLSearchParams;\n /** Request headers */\n headers?: Record<string, string>;\n /** Override base URL for this call */\n baseUrlKey?: string;\n /** Abort controller signal for cancellation */\n signal?: AbortSignal;\n};\n\n/**\n * Generic, strongly-typed endpoint with Zod schemas for request and response validation.\n * Extend this class to create type-safe API endpoints.\n * \n * @template ReqSchema - Zod schema for request validation\n * @template ResSchema - Zod schema for response validation\n * \n * @example\n * ```ts\n * const UserSchema = z.object({ id: z.number(), name: z.string() });\n * const CreateUserSchema = z.object({ name: z.string() });\n * \n * class GetUser extends BaseEndpoint<typeof CreateUserSchema, typeof UserSchema> {\n * protected method = 'GET' as const;\n * protected path = (args: z.infer<typeof CreateUserSchema>) => `/users/${args.id}`;\n * \n * constructor(client: HttpClient) {\n * super(client, { \n * requestSchema: CreateUserSchema,\n * responseSchema: UserSchema \n * });\n * }\n * }\n * ```\n */\nexport abstract class BaseEndpoint<ReqSchema extends z.ZodType, ResSchema extends z.ZodType> {\n /** HTTP method for this endpoint */\n protected abstract readonly method: keyof typeof HTTPMethod;\n /** URL path (can be a function for dynamic paths) */\n protected abstract readonly path: string | ((params: z.infer<ReqSchema>) => string);\n /** Additional options for the request */\n protected readonly options?: {\n /** Override base URL for this call */\n baseUrlKey?: string;\n };\n /** Optional request schema for validation */\n protected readonly requestSchema?: ReqSchema;\n /** Response schema for validation */\n protected readonly responseSchema: ResSchema;\n\n /**\n * @param client - HttpClient instance\n * @param cfg - Configuration with request and response schemas\n */\n constructor(\n protected client: HttpClient,\n cfg: { requestSchema?: ReqSchema; responseSchema: ResSchema }\n ) {\n this.requestSchema = cfg.requestSchema;\n this.responseSchema = cfg.responseSchema;\n }\n\n /**\n * Call the endpoint with strong typing derived from schemas.\n * Validates request data before sending and response data after receiving.\n * \n * @param config - Request configuration object containing all parameters\n * @returns Promise resolving to validated response data (typed by ResSchema)\n * @throws {ZodError} If request validation fails\n * @throws {ApiError} If response validation fails or request fails\n * \n * @example\n * ```ts\n * const endpoint = new GetUser(client);\n * const user = await endpoint.call({ data: { id: 1 } });\n * // With additional options:\n * const user = await endpoint.call({ \n * data: { id: 1 },\n * headers: { 'X-Custom': 'value' },\n * query: { include: 'posts' }\n * });\n * ```\n */\n async call(config: EndpointCallConfig<ReqSchema> = {}): Promise<z.infer<ResSchema>> {\n const { data, query, headers, baseUrlKey, signal, pathParams } = config;\n\n // Validate request body/params before sending (when schema provided)\n if (this.requestSchema && data !== undefined) {\n const parsed = this.requestSchema.safeParse(data);\n if (!parsed.success) throw parsed.error;\n }\n\n // Build path - use pathParams if provided, otherwise use data\n const pathArgs = pathParams ?? data;\n const path = typeof this.path === 'function' ? this.path(pathArgs as z.infer<ReqSchema>) : this.path;\n\n // For GET/HEAD methods, don't send body\n const shouldHaveBody = this.method !== 'GET' && this.method !== 'HEAD';\n const body = shouldHaveBody ? data : undefined;\n\n const { data: responseData } = await this.client.request(this.method, path, body, {\n query,\n headers,\n baseUrlKey: baseUrlKey ?? this.options?.baseUrlKey,\n signal,\n });\n\n return parseOrThrow<z.infer<ResSchema>>(this.responseSchema, responseData);\n }\n}\n","import { z } from 'zod';\n\n/**\n * Common ID type that supports strings, numbers, or UUIDs.\n * Use this for entity identifiers in your schemas.\n * \n * @example\n * ```ts\n * const UserSchema = z.object({ id: Id, name: z.string() });\n * ```\n */\nexport const Id = z.union([\n z.string().min(1),\n z.number(),\n z.uuid({\n version: 'v4',\n }),\n]);\nexport type IdType = z.infer<typeof Id>;\n\n/**\n * Common timestamp fields for entities.\n * Use this for database models with creation/update tracking.\n * \n * @example\n * ```ts\n * const UserSchema = z.object({\n * id: Id,\n * name: z.string(),\n * ...Timestamps.shape\n * });\n * ```\n */\nexport const Timestamps = z.object({\n createdAt: z.iso.datetime(),\n updatedAt: z.iso.datetime(),\n});\n\n/**\n * Metadata information typically included in API responses.\n * Contains request tracking and debugging information.\n */\nexport const Meta = z.object({\n requestId: z.string().optional(),\n timestamp: z.iso.datetime().optional(),\n traceId: z.string().optional(),\n});\n\n/**\n * Detailed error information for a specific field or path.\n */\nexport const ErrorDetail = z.object({\n path: z.string().optional(),\n message: z.string(),\n});\n\n/**\n * Standard API error response schema.\n * Use this for consistent error handling across your API.\n */\nexport const ApiErrorSchema = z.object({\n code: z.string(),\n message: z.string(),\n details: z.array(ErrorDetail).optional(),\n});\n\n/**\n * Generic envelope wrapper for API responses.\n * Provides consistent structure with success flag, data, error, and metadata.\n * \n * @param inner - Zod schema for the response data\n * @returns Envelope schema wrapping the inner schema\n * \n * @example\n * ```ts\n * const UserResponseSchema = Envelope(z.object({ id: Id, name: z.string() }));\n * \n * // Response structure:\n * // {\n * // success: true,\n * // data: { id: 1, name: 'John' },\n * // meta: { requestId: '...' }\n * // }\n * ```\n */\nexport const Envelope = <T extends z.ZodType>(inner: T) =>\n z.object({\n success: z.boolean(),\n data: inner.optional().nullable(),\n error: ApiErrorSchema.optional(),\n meta: Meta.optional(),\n });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["z","s","opts: { header?: string; query?: string; value: string }","getToken: () => Promise<string> | string","minLevel: LogLevel","logger: Logger","k: string","err: unknown","init: RequestInit & { __urlOverride?: string }","timeoutId: NodeJS.Timeout | number | undefined","client: HttpClient","z"],"sources":["../lib/types.ts","../lib/auth.ts","../lib/validation.ts","../lib/logger.ts","../lib/metrics.ts","../lib/http/http-client.ts","../lib/endpoint/base-endpoint.ts","../lib/schemas/common.ts"],"sourcesContent":["import { z, ZodError } from 'zod';\nimport { AuthProvider } from './auth';\nimport { Logger } from './logger';\nimport { MetricsCollector } from './metrics';\n\nexport type Dictionary<T = unknown> = Record<string, T>;\n\nexport type FetchLike = (input: string | Request | URL, init?: RequestInit) => Promise<Response>;\n\n/**\n * Map of base URLs for different services.\n * The 'default' key is required and used when no specific key is provided.\n *\n * @example\n * ```ts\n * {\n * default: 'https://api.example.com',\n * auth: 'https://auth.example.com',\n * cdn: 'https://cdn.example.com'\n * }\n * ```\n */\nexport type BaseUrlMap = {\n default: string;\n [service: string]: string;\n};\n\n/**\n * Configuration for retry behavior on failed requests.\n * Implements exponential backoff with optional jitter.\n *\n * @example\n * ```ts\n * {\n * maxRetries: 3,\n * baseDelayMs: 1000,\n * jitter: 0.2,\n * retryMethods: ['GET', 'HEAD', 'PUT']\n * }\n * ```\n */\nexport type RetryStrategy = {\n /** Maximum number of retry attempts */\n maxRetries: number;\n /** Base delay in milliseconds (will be exponentially increased) */\n baseDelayMs: number;\n /** Jitter factor 0..1 to randomize delays and prevent thundering herd */\n jitter?: number;\n /** HTTP methods eligible for retry */\n retryMethods?: (keyof typeof HTTPMethod)[];\n /** Custom function to determine if a request should be retried */\n shouldRetry?: (ctx: { attempt: number; error?: unknown; response?: Response }) => boolean;\n};\n\nexport const HTTPMethod = {\n GET: 'GET',\n POST: 'POST',\n PUT: 'PUT',\n PATCH: 'PATCH',\n DELETE: 'DELETE',\n HEAD: 'HEAD',\n OPTIONS: 'OPTIONS',\n} as const;\n\nexport type HttpMethod = keyof typeof HTTPMethod;\n\n/**\n * Hook called after a response is received and parsed.\n * Useful for logging, metrics, or global error handling.\n */\nexport type AfterResponseHook = (ctx: {\n request: Request;\n response: Response;\n parsed?: unknown;\n}) => Promise<void> | void;\n\n/**\n * Hook called before a request is sent.\n * Useful for logging, adding headers, or modifying the request.\n */\nexport type BeforeRequestHook = (ctx: { url: string; init: RequestInit }) => Promise<void> | void;\n\nexport interface Interceptors {\n /** Hooks executed before each request is sent */\n beforeRequest?: BeforeRequestHook[];\n /** Hooks executed after each response is received */\n afterResponse?: AfterResponseHook[];\n}\n\nexport interface TimeoutOptions {\n /** Request timeout in milliseconds */\n requestTimeoutMs?: number;\n}\n\n/**\n * Configuration options for the HTTP client.\n *\n * @example\n * ```ts\n * const options: ClientOptions = {\n * baseUrls: { default: 'https://api.example.com' },\n * headers: { 'X-API-Version': '1.0' },\n * retry: { maxRetries: 3, baseDelayMs: 1000 },\n * timeout: { requestTimeoutMs: 30000 }\n * }\n * ```\n */\nexport interface ClientOptions {\n /** Map of base URLs for different services */\n baseUrls: BaseUrlMap;\n /** Custom fetch implementation (defaults to globalThis.fetch) */\n fetch?: FetchLike;\n /** Default headers applied to all requests */\n headers?: Record<string, string>;\n /** Retry strategy configuration */\n retry?: RetryStrategy;\n /** Request/response interceptors */\n interceptors?: Interceptors;\n /** Timeout configuration */\n timeout?: TimeoutOptions;\n /** Authentication provider */\n auth?: AuthProvider;\n /** Logger instance */\n logger?: Logger;\n /** Metrics collector */\n metrics?: MetricsCollector;\n}\n\nexport type SafeParseResult<T> = { success: true; data: T } | { success: false; error: ZodError };\n\n/**\n * Custom error class for API-related errors.\n * Includes HTTP status codes, response details, and validation errors.\n *\n * @example\n * ```ts\n * throw new ApiError('Invalid request', {\n * status: 400,\n * details: { field: 'email', message: 'Invalid format' }\n * });\n * ```\n */\nexport class ApiError extends Error {\n public status?: number;\n public details?: unknown;\n public zodError?: ZodError;\n\n constructor(\n message: string,\n options?: { status?: number; cause?: unknown; details?: unknown; zodError?: ZodError }\n ) {\n super(message);\n this.name = 'ApiError';\n this.status = options?.status;\n this.details = options?.details;\n this.cause = options?.cause as any;\n this.zodError = options?.zodError;\n\n // Maintains proper stack trace for where error was thrown\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, ApiError);\n }\n }\n\n /**\n * Check if this is a validation error (has zodError)\n */\n isValidationError(): boolean {\n return !!this.zodError;\n }\n\n /**\n * Check if this is a client error (4xx status)\n */\n isClientError(): boolean {\n return !!this.status && this.status >= 400 && this.status < 500;\n }\n\n /**\n * Check if this is a server error (5xx status)\n */\n isServerError(): boolean {\n return !!this.status && this.status >= 500;\n }\n\n /**\n * Get a formatted error message with all available details\n */\n toJSON() {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n details: this.details,\n zodError: this.zodError?.issues,\n stack: this.stack,\n };\n }\n}\n\nexport type Paginated<T> = {\n items: T[];\n total: number;\n page: number;\n pageSize: number;\n};\n\n/**\n * Schema for paginated responses\n */\nexport const PaginationSchema = z.object({\n items: z.array(z.unknown()),\n total: z.number().int().nonnegative(),\n page: z.number().int().nonnegative(),\n pageSize: z.number().int().positive(),\n});\n\n/**\n * Options that can be passed to individual requests to override defaults.\n *\n * @example\n * ```ts\n * await endpoint.call(data, {\n * baseUrlKey: 'v2',\n * headers: { 'X-Custom': 'value' },\n * query: { filter: 'active' }\n * });\n * ```\n */\nexport type RequestOptions = {\n /** Override base URL for a single call */\n baseUrlKey?: keyof BaseUrlMap;\n /** Additional headers for this call only */\n headers?: Record<string, string>;\n /** Abort controller signal for cancellation */\n signal?: AbortSignal;\n /** Custom query params */\n query?: URLSearchParams | Record<string, string | number | boolean | undefined>;\n};\n\n/**\n * Converts query parameters to a URL query string.\n * Filters out undefined values automatically.\n *\n * @param q - Query parameters as URLSearchParams or object\n * @returns Query string with leading '?' or empty string\n *\n * @example\n * ```ts\n * toQueryString({ page: 1, filter: 'active' }) // \"?page=1&filter=active\"\n * toQueryString({ optional: undefined }) // \"\"\n * ```\n */\nexport function toQueryString(q?: RequestOptions['query']): string {\n if (!q) return '';\n if (q instanceof URLSearchParams) {\n const s = q.toString();\n return s ? `?${s}` : '';\n }\n const params = new URLSearchParams();\n Object.entries(q).forEach(([k, v]) => {\n if (v !== undefined) {\n params.append(k, String(v));\n }\n });\n const s = params.toString();\n return s ? `?${s}` : '';\n}\n","import type { RequestOptions as ReqOpts } from './types';\n\n/**\n * Extended RequestInit with URL override capability for query-based auth.\n */\nexport interface AuthContext {\n url: string;\n init: RequestInit & { __urlOverride?: string };\n options?: ReqOpts;\n}\n\n/**\n * Interface for authentication providers.\n * Implement this to create custom authentication strategies.\n *\n * @example\n * ```ts\n * class CustomAuth implements AuthProvider {\n * async apply({ init }) {\n * init.headers = { ...init.headers, 'X-Custom-Auth': 'token' };\n * }\n * }\n * ```\n */\nexport interface AuthProvider {\n /**\n * Apply authentication to the outgoing request.\n * Called after SDK headers are assembled, but before request is sent.\n *\n * @param req - Request context including URL, init, and options\n */\n apply(req: AuthContext): Promise<void> | void;\n}\n\n/**\n * No-op authentication provider (no authentication applied).\n * Use this when you don't need authentication.\n */\nexport class NoAuth implements AuthProvider {\n async apply() {\n /* no-op */\n }\n}\n\n/**\n * API Key authentication provider.\n * Supports both header-based and query parameter-based authentication.\n *\n * @example\n * ```ts\n * // Header-based\n * const auth = new ApiKeyAuth({ header: 'X-API-Key', value: 'secret' });\n *\n * // Query parameter-based\n * const auth = new ApiKeyAuth({ query: 'apiKey', value: 'secret' });\n * ```\n */\nexport class ApiKeyAuth implements AuthProvider {\n constructor(private opts: { header?: string; query?: string; value: string }) {\n if (!opts.header && !opts.query) {\n throw new Error('ApiKeyAuth requires either \"header\" or \"query\" option');\n }\n if (opts.header && opts.query) {\n throw new Error('ApiKeyAuth cannot use both \"header\" and \"query\" options');\n }\n }\n apply({ url, init }: AuthContext) {\n if (this.opts.header) {\n init.headers = { ...(init.headers as any), [this.opts.header]: this.opts.value };\n } else if (this.opts.query) {\n const u = new URL(url);\n u.searchParams.set(this.opts.query, this.opts.value);\n init.__urlOverride = u.toString();\n }\n }\n}\n\n/**\n * Bearer token authentication provider.\n * Supports both static tokens and dynamic token fetching (e.g., for OAuth2 refresh).\n *\n * @example\n * ```ts\n * // Static token\n * const auth = new BearerTokenAuth(() => 'my-token');\n *\n * // Dynamic token with refresh\n * const auth = new BearerTokenAuth(async () => {\n * return await refreshAccessToken();\n * });\n * ```\n */\nexport class BearerTokenAuth implements AuthProvider {\n constructor(private getToken: () => Promise<string> | string) {}\n async apply({ init }: AuthContext) {\n const token = await this.getToken();\n if (!token) {\n throw new Error('BearerTokenAuth: token is empty or undefined');\n }\n init.headers = { ...(init.headers as any), Authorization: `Bearer ${token}` };\n }\n}\n","import { z } from 'zod';\nimport { ApiError, SafeParseResult } from './types';\n\n/**\n * Safely parse data with a Zod schema without throwing.\n * Returns a result object with success status and data or error.\n *\n * @param schema - Zod schema to validate against\n * @param data - Data to validate\n * @returns Result object with success flag and data/error\n *\n * @example\n * ```ts\n * const result = safeParse(UserSchema, userData);\n * if (result.success) {\n * console.log(result.data);\n * } else {\n * console.error(result.error);\n * }\n * ```\n */\nexport function safeParse<T>(schema: z.ZodType, data: unknown): SafeParseResult<T> {\n const res = schema.safeParse(data);\n if (res.success) return { success: true, data: res.data as T };\n return { success: false, error: res.error };\n}\n\n/**\n * Parse data with a Zod schema, throwing an ApiError on validation failure.\n * Use this when you want to fail fast on invalid data.\n *\n * @param schema - Zod schema to validate against\n * @param data - Data to validate\n * @returns Validated and typed data\n * @throws {ApiError} If validation fails\n *\n * @example\n * ```ts\n * try {\n * const user = parseOrThrow(UserSchema, userData);\n * console.log(user);\n * } catch (error) {\n * if (error instanceof ApiError && error.zodError) {\n * console.error('Validation failed:', error.zodError.issues);\n * }\n * }\n * ```\n */\nexport function parseOrThrow<T>(schema: z.ZodType, data: unknown): T {\n const res = schema.safeParse(data);\n if (!res.success) {\n throw new ApiError('Response validation failed', { zodError: res.error });\n }\n return res.data as T;\n}\n","/**\n * Log levels for structured logging.\n */\nexport enum LogLevel {\n DEBUG = 'debug',\n INFO = 'info',\n WARN = 'warn',\n ERROR = 'error',\n}\n\n/**\n * Structured log entry with metadata.\n */\nexport interface LogEntry {\n level: LogLevel;\n message: string;\n timestamp: string;\n context?: Record<string, unknown>;\n error?: Error;\n}\n\n/**\n * Logger interface for custom logger implementations.\n * Implement this to integrate with your logging infrastructure.\n *\n * @example\n * ```ts\n * class ConsoleLogger implements Logger {\n * log(entry: LogEntry) {\n * console.log(JSON.stringify(entry));\n * }\n * }\n * ```\n */\nexport interface Logger {\n log(entry: LogEntry): void;\n}\n\n/**\n * Default console logger implementation.\n * Formats log entries as JSON for easy parsing.\n */\nexport class ConsoleLogger implements Logger {\n constructor(private minLevel: LogLevel = LogLevel.INFO) {}\n\n log(entry: LogEntry): void {\n const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR];\n const entryLevelIndex = levels.indexOf(entry.level);\n const minLevelIndex = levels.indexOf(this.minLevel);\n\n if (entryLevelIndex < minLevelIndex) return;\n\n const output = {\n ...entry,\n error: entry.error\n ? {\n message: entry.error.message,\n stack: entry.error.stack,\n name: entry.error.name,\n }\n : undefined,\n };\n\n switch (entry.level) {\n case LogLevel.DEBUG:\n console.debug(JSON.stringify(output));\n break;\n case LogLevel.INFO:\n console.info(JSON.stringify(output));\n break;\n case LogLevel.WARN:\n console.warn(JSON.stringify(output));\n break;\n case LogLevel.ERROR:\n console.error(JSON.stringify(output));\n break;\n }\n }\n}\n\n/**\n * No-op logger that discards all log entries.\n * Use this in production if you don't want any logging.\n */\nexport class NoOpLogger implements Logger {\n log(_entry: LogEntry): void {\n // no-op\n }\n}\n\n/**\n * Utility class for creating structured log entries.\n */\nexport class LoggerUtil {\n constructor(private logger: Logger) {}\n\n debug(message: string, context?: Record<string, unknown>): void {\n this.logger.log({\n level: LogLevel.DEBUG,\n message,\n timestamp: new Date().toISOString(),\n context,\n });\n }\n\n info(message: string, context?: Record<string, unknown>): void {\n this.logger.log({\n level: LogLevel.INFO,\n message,\n timestamp: new Date().toISOString(),\n context,\n });\n }\n\n warn(message: string, context?: Record<string, unknown>): void {\n this.logger.log({\n level: LogLevel.WARN,\n message,\n timestamp: new Date().toISOString(),\n context,\n });\n }\n\n error(message: string, error?: Error, context?: Record<string, unknown>): void {\n this.logger.log({\n level: LogLevel.ERROR,\n message,\n timestamp: new Date().toISOString(),\n context,\n error,\n });\n }\n}\n","/**\n * Metrics data for HTTP requests.\n */\nexport interface RequestMetrics {\n method: string;\n path: string;\n status?: number;\n durationMs: number;\n timestamp: string;\n success: boolean;\n error?: string;\n}\n\n/**\n * Interface for metrics collectors.\n * Implement this to integrate with your monitoring infrastructure (DataDog, Prometheus, etc.).\n *\n * @example\n * ```ts\n * class DataDogMetrics implements MetricsCollector {\n * collect(metrics: RequestMetrics) {\n * dogstatsd.histogram('http.request.duration', metrics.durationMs, {\n * method: metrics.method,\n * status: String(metrics.status)\n * });\n * }\n * }\n * ```\n */\nexport interface MetricsCollector {\n collect(metrics: RequestMetrics): void;\n}\n\n/**\n * No-op metrics collector that discards all metrics.\n */\nexport class NoOpMetricsCollector implements MetricsCollector {\n collect(_metrics: RequestMetrics): void {\n // no-op\n }\n}\n\n/**\n * In-memory metrics collector for testing and development.\n * Stores metrics in memory with configurable retention.\n */\nexport class InMemoryMetricsCollector implements MetricsCollector {\n private metrics: RequestMetrics[] = [];\n private readonly maxEntries: number;\n\n constructor(maxEntries = 1000) {\n this.maxEntries = maxEntries;\n }\n\n collect(metrics: RequestMetrics): void {\n this.metrics.push(metrics);\n if (this.metrics.length > this.maxEntries) {\n this.metrics.shift();\n }\n }\n\n /**\n * Get all collected metrics.\n */\n getMetrics(): RequestMetrics[] {\n return [...this.metrics];\n }\n\n /**\n * Get metrics summary statistics.\n */\n getSummary(): {\n total: number;\n successful: number;\n failed: number;\n avgDurationMs: number;\n minDurationMs: number;\n maxDurationMs: number;\n } {\n if (this.metrics.length === 0) {\n return {\n total: 0,\n successful: 0,\n failed: 0,\n avgDurationMs: 0,\n minDurationMs: 0,\n maxDurationMs: 0,\n };\n }\n\n const successful = this.metrics.filter((m) => m.success).length;\n const durations = this.metrics.map((m) => m.durationMs);\n const sum = durations.reduce((a, b) => a + b, 0);\n\n return {\n total: this.metrics.length,\n successful,\n failed: this.metrics.length - successful,\n avgDurationMs: sum / this.metrics.length,\n minDurationMs: Math.min(...durations),\n maxDurationMs: Math.max(...durations),\n };\n }\n\n /**\n * Clear all collected metrics.\n */\n clear(): void {\n this.metrics = [];\n }\n}\n\n/**\n * Console-based metrics collector for debugging.\n */\nexport class ConsoleMetricsCollector implements MetricsCollector {\n collect(metrics: RequestMetrics): void {\n console.log('[METRICS]', JSON.stringify(metrics));\n }\n}\n","import type { AuthProvider } from '../auth';\nimport { NoAuth } from '../auth';\nimport { LoggerUtil, NoOpLogger } from '../logger';\nimport { MetricsCollector, NoOpMetricsCollector } from '../metrics';\nimport {\n ApiError,\n ClientOptions,\n FetchLike,\n HTTPMethod,\n Interceptors,\n RequestOptions,\n RetryStrategy,\n toQueryString,\n} from '../types';\n\n/**\n * HTTP client with built-in retry logic, authentication, and interceptors.\n * Supports multiple base URLs, type-safe requests, and comprehensive error handling.\n *\n * @example\n * ```ts\n * const client = new HttpClient({\n * baseUrls: { default: 'https://api.example.com' },\n * headers: { 'Content-Type': 'application/json' },\n * retry: { maxRetries: 3, baseDelayMs: 1000 },\n * timeout: { requestTimeoutMs: 30000 }\n * });\n *\n * const { data } = await client.request('GET', '/users', undefined, { query: { page: 1 } });\n * ```\n */\nexport class HttpClient {\n private fetchImpl: FetchLike;\n private baseUrls: ClientOptions['baseUrls'];\n private headers: Record<string, string>;\n private interceptors: Interceptors;\n private retry: RetryStrategy;\n private timeoutMs?: number;\n private auth: AuthProvider;\n private logger: LoggerUtil;\n private metrics: MetricsCollector;\n\n /**\n * Creates a new HTTP client instance.\n *\n * @param opts - Client configuration options\n * @throws {Error} If no fetch implementation is available\n */\n constructor(opts: ClientOptions) {\n this.fetchImpl = opts.fetch ?? (globalThis.fetch?.bind(globalThis) as FetchLike);\n if (!this.fetchImpl)\n throw new Error('No fetch implementation found. Pass one via options.fetch.');\n\n // Validate baseUrls configuration\n if (!opts.baseUrls || typeof opts.baseUrls !== 'object') {\n throw new Error('baseUrls must be provided and must be an object');\n }\n if (!opts.baseUrls.default) {\n throw new Error('baseUrls must include a \"default\" key');\n }\n\n this.baseUrls = opts.baseUrls;\n this.headers = opts.headers ?? { 'Content-Type': 'application/json' };\n this.interceptors = opts.interceptors ?? {};\n this.retry = opts.retry ?? {\n maxRetries: 2,\n baseDelayMs: 250,\n jitter: 0.2,\n retryMethods: ['GET', 'HEAD'],\n };\n\n // Validate retry configuration\n if (this.retry.maxRetries < 0) {\n throw new Error('retry.maxRetries must be non-negative');\n }\n if (this.retry.baseDelayMs < 0) {\n throw new Error('retry.baseDelayMs must be non-negative');\n }\n if (this.retry.jitter !== undefined && (this.retry.jitter < 0 || this.retry.jitter > 1)) {\n throw new Error('retry.jitter must be between 0 and 1');\n }\n\n this.timeoutMs = opts.timeout?.requestTimeoutMs;\n if (this.timeoutMs !== undefined && this.timeoutMs < 0) {\n throw new Error('timeout.requestTimeoutMs must be non-negative');\n }\n\n this.auth = opts['auth'] ?? new NoAuth();\n this.logger = new LoggerUtil(opts.logger ?? new NoOpLogger());\n this.metrics = opts.metrics ?? new NoOpMetricsCollector();\n }\n\n /**\n * Set or update the authentication provider.\n *\n * @param auth - Authentication provider instance\n * @example\n * ```ts\n * client.setAuth(new BearerTokenAuth(() => getToken()));\n * ```\n */\n setAuth(auth: AuthProvider) {\n this.auth = auth;\n }\n\n private resolveBaseUrl(key?: keyof typeof this.baseUrls) {\n const k: string = (key as string) || 'default';\n const url = this.baseUrls[k];\n if (!url) {\n const availableKeys = Object.keys(this.baseUrls).join(', ');\n throw new Error(`Unknown baseUrl key: \"${k}\". Available keys: ${availableKeys}`);\n }\n return url.replace(/\\/$/, '');\n }\n\n /**\n * Sleep for a specified duration (used for retry backoff).\n * @private\n */\n private sleep(ms: number) {\n return new Promise((res) => setTimeout(res, ms));\n }\n\n /**\n * Execute a function with retry logic and exponential backoff.\n * @private\n */\n private async withRetry<T>(\n fn: () => Promise<T>,\n canRetry: (ctx: { attempt: number; error?: unknown; response?: Response }) => boolean\n ): Promise<T> {\n let attempt = 0;\n const { maxRetries, baseDelayMs, jitter = 0.2 } = this.retry;\n while (true) {\n try {\n return await fn();\n } catch (err: unknown) {\n if (attempt >= maxRetries || !canRetry({ attempt, error: err })) throw err;\n const backoff = baseDelayMs * 2 ** attempt;\n const j = 1 + (Math.random() * 2 - 1) * jitter;\n await this.sleep(backoff * j);\n attempt++;\n }\n }\n }\n\n /**\n * Run all registered before-request hooks.\n * @private\n */\n private async runBeforeHooks(url: string, init: RequestInit & { __urlOverride?: string }) {\n for (const h of this.interceptors.beforeRequest ?? []) {\n await h({ url, init });\n }\n }\n\n /**\n * Run all registered after-response hooks.\n * @private\n */\n private async runAfterHooks(req: Request, res: Response, parsed?: unknown) {\n for (const h of this.interceptors.afterResponse ?? []) {\n await h({ request: req, response: res, parsed });\n }\n }\n\n /**\n * Get all configured base URLs.\n *\n * @returns Object mapping base URL keys to their resolved URLs\n */\n public getBaseUrls() {\n return this.baseUrls;\n }\n\n /**\n * Get the resolved base URL for a given key.\n *\n * @param key - Base URL key (defaults to 'default' if not provided)\n * @returns Resolved base URL string\n */\n public getBaseUrl(key: string) {\n return this.resolveBaseUrl(key);\n }\n\n /**\n * Make an HTTP request with automatic retry, authentication, and validation.\n *\n * @param method - HTTP method (GET, POST, PUT, etc.)\n * @param path - Request path (will be appended to base URL)\n * @param body - Request body (will be JSON.stringify'd if Content-Type is json)\n * @param options - Additional request options (headers, query params, etc.)\n * @returns Promise resolving to response data and Response object\n * @throws {ApiError} If request fails or response validation fails\n *\n * @example\n * ```ts\n * const { data, response } = await client.request('GET', '/users', undefined, {\n * query: { page: 1, limit: 10 },\n * headers: { 'X-Custom': 'value' }\n * });\n * ```\n */\n async request<T = unknown>(\n method: keyof typeof HTTPMethod,\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<{ data: T; response: Response }> {\n const startTime = Date.now();\n const base = this.resolveBaseUrl(options?.baseUrlKey);\n let url = `${base}${path}${toQueryString(options?.query)}`;\n\n this.logger.debug('HTTP request initiated', {\n method,\n path,\n baseUrlKey: options?.baseUrlKey,\n hasBody: body !== undefined,\n });\n\n const headers = { ...this.headers, ...(options?.headers ?? {}) };\n\n const controller = new AbortController();\n const signal = options?.signal ?? controller.signal;\n const init: RequestInit & { __urlOverride?: string } = {\n method,\n headers,\n body:\n body != null\n ? headers['Content-Type']?.includes('json')\n ? JSON.stringify(body)\n : String(body)\n : undefined,\n signal,\n };\n\n await this.auth.apply({ url, init, options });\n if (init.__urlOverride) url = init.__urlOverride;\n await this.runBeforeHooks(url, init);\n\n const doFetch = async () => {\n // Apply timeout if configured\n let timeoutId: NodeJS.Timeout | number | undefined;\n if (this.timeoutMs && !options?.signal) {\n timeoutId = setTimeout(() => {\n const timeoutError = new Error('Request timeout');\n timeoutError.name = 'TimeoutError';\n controller.abort(timeoutError);\n }, this.timeoutMs);\n }\n\n try {\n const req = new Request(url, init);\n\n const res = await this.fetchImpl(req);\n if (!res.ok) {\n // Read response safely\n let text = '';\n try {\n text = await res.text();\n } catch (readError) {\n text = `Failed to read response: ${readError instanceof Error ? readError.message : String(readError)}`;\n }\n throw new ApiError(`HTTP ${res.status}: ${res.statusText}`, {\n status: res.status,\n details: text,\n });\n }\n const contentType = res.headers.get('content-type') || '';\n const data = contentType.includes('json') ? await res.json() : await res.text();\n await this.runAfterHooks(new Request(url, init), res, data);\n\n const duration = Date.now() - startTime;\n this.logger.info('HTTP request successful', {\n method,\n url,\n status: res.status,\n durationMs: duration,\n });\n\n this.metrics.collect({\n method,\n path,\n status: res.status,\n durationMs: duration,\n timestamp: new Date().toISOString(),\n success: true,\n });\n\n return { data: data as T, response: res };\n } catch (error) {\n const duration = Date.now() - startTime;\n this.logger.error('HTTP request failed', error as Error, {\n method,\n url,\n durationMs: duration,\n });\n\n this.metrics.collect({\n method,\n path,\n status: error instanceof ApiError ? error.status : undefined,\n durationMs: duration,\n timestamp: new Date().toISOString(),\n success: false,\n error: error instanceof Error ? error.message : String(error),\n });\n\n throw error;\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n }\n };\n\n const canRetry = ({\n response,\n error,\n }: {\n response?: Response;\n error?: unknown;\n attempt: number;\n }) => {\n // Don't retry timeouts or aborts\n if (error && typeof error === 'object' && 'name' in error) {\n const errorName = (error as { name?: string }).name;\n if (errorName === 'AbortError' || errorName === 'TimeoutError') return false;\n }\n // Retry on network errors or 5xx\n if (error instanceof ApiError && error.status && error.status >= 500) return true;\n if (error && !response) return true; // network error\n return false;\n };\n\n if (!this.retry.retryMethods?.includes(method)) {\n return doFetch();\n }\n return this.withRetry(doFetch, canRetry);\n }\n\n /**\n * Convenience method for GET requests.\n *\n * @example\n * ```ts\n * const { data } = await client.get('/users', { query: { page: 1 } });\n * ```\n */\n async get<T = unknown>(\n path: string,\n options?: RequestOptions\n ): Promise<{ data: T; response: Response }> {\n return this.request<T>('GET', path, undefined, options);\n }\n\n /**\n * Convenience method for POST requests.\n *\n * @example\n * ```ts\n * const { data } = await client.post('/users', { name: 'John' });\n * ```\n */\n async post<T = unknown>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<{ data: T; response: Response }> {\n return this.request<T>('POST', path, body, options);\n }\n\n /**\n * Convenience method for PUT requests.\n *\n * @example\n * ```ts\n * const { data } = await client.put('/users/1', { name: 'John Updated' });\n * ```\n */\n async put<T = unknown>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<{ data: T; response: Response }> {\n return this.request<T>('PUT', path, body, options);\n }\n\n /**\n * Convenience method for PATCH requests.\n *\n * @example\n * ```ts\n * const { data } = await client.patch('/users/1', { name: 'John' });\n * ```\n */\n async patch<T = unknown>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<{ data: T; response: Response }> {\n return this.request<T>('PATCH', path, body, options);\n }\n\n /**\n * Convenience method for DELETE requests.\n *\n * @example\n * ```ts\n * const { data } = await client.delete('/users/1');\n * ```\n */\n async delete<T = unknown>(\n path: string,\n options?: RequestOptions\n ): Promise<{ data: T; response: Response }> {\n return this.request<T>('DELETE', path, undefined, options);\n }\n}\n","import { z } from 'zod';\nimport { HttpClient } from '../http/http-client';\nimport { HTTPMethod } from '../types';\nimport { parseOrThrow } from '../validation';\n\n/**\n * Request configuration for endpoint calls.\n * Wrapper object containing all request parameters.\n *\n * @template ReqSchema - Zod schema for request validation\n * @template PathParams - Type for path parameters\n * @template QueryParams - Type for query parameters\n */\nexport type EndpointCallConfig<\n ReqSchema extends z.ZodType,\n PathParams = never,\n QueryParams = never,\n> = {\n /** Request body data (for POST, PUT, PATCH, etc.) or request args */\n data?: z.infer<ReqSchema>;\n /** Path parameters for dynamic path construction */\n pathParams?: PathParams;\n /** Query string parameters */\n query?: QueryParams;\n /** Request headers */\n headers?: Record<string, string>;\n /** Override base URL for this call */\n baseUrlKey?: string;\n /** Abort controller signal for cancellation */\n signal?: globalThis.AbortSignal;\n};\n\n/**\n * Generic, strongly-typed endpoint with Zod schemas for request and response validation.\n * Extend this class to create type-safe API endpoints.\n *\n * @template ReqSchema - Zod schema for request validation\n * @template ResSchema - Zod schema for response validation\n * @template PathParams - Type for path parameters (optional)\n * @template QueryParams - Type for query parameters (optional)\n *\n * @example\n * ```ts\n * const UserSchema = z.object({ id: z.number(), name: z.string() });\n * const CreateUserSchema = z.object({ name: z.string() });\n *\n * type UserPathParams = { id: string };\n * type UserQueryParams = { include?: string; limit?: number };\n *\n * class GetUser extends BaseEndpoint<\n * typeof CreateUserSchema,\n * typeof UserSchema,\n * UserPathParams,\n * UserQueryParams\n * > {\n * protected method = 'GET' as const;\n * protected path = (params: UserPathParams) => `/users/${params.id}`;\n *\n * constructor(client: HttpClient) {\n * super(client, {\n * requestSchema: CreateUserSchema,\n * responseSchema: UserSchema\n * });\n * }\n * }\n *\n * // Usage:\n * const user = await endpoint.call({\n * pathParams: { id: '123' },\n * query: { include: 'posts', limit: 10 }\n * });\n * ```\n */\nexport abstract class BaseEndpoint<\n ReqSchema extends z.ZodType,\n ResSchema extends z.ZodType,\n PathParams = never,\n QueryParams = never,\n> {\n /** HTTP method for this endpoint */\n protected abstract readonly method: keyof typeof HTTPMethod;\n /** URL path (can be a function for dynamic paths) */\n protected abstract readonly path: string | ((params: PathParams) => string);\n /** Additional options for the request */\n protected readonly options?: {\n /** Override base URL for this call */\n baseUrlKey?: string;\n };\n /** Optional request schema for validation */\n protected readonly requestSchema?: ReqSchema;\n /** Response schema for validation */\n protected readonly responseSchema: ResSchema;\n\n /**\n * @param client - HttpClient instance\n * @param cfg - Configuration with request and response schemas\n */\n constructor(\n protected client: HttpClient,\n cfg: {\n requestSchema?: ReqSchema;\n responseSchema: ResSchema;\n }\n ) {\n this.requestSchema = cfg.requestSchema;\n this.responseSchema = cfg.responseSchema;\n }\n\n /**\n * Call the endpoint with strong typing derived from schemas.\n * Validates request data before sending and response data after receiving.\n *\n * @param config - Request configuration object containing all parameters\n * @returns Promise resolving to validated response data (typed by ResSchema)\n * @throws {ZodError} If request validation fails\n * @throws {ApiError} If response validation fails or request fails\n *\n * @example\n * ```ts\n * const endpoint = new GetUser(client);\n * const user = await endpoint.call({\n * pathParams: { id: '123' },\n * query: { include: 'posts' }\n * });\n * // With additional options:\n * const user = await endpoint.call({\n * data: { name: 'John' },\n * pathParams: { id: '123' },\n * headers: { 'X-Custom': 'value' },\n * query: { include: 'posts' }\n * });\n * ```\n */\n async call(\n config: EndpointCallConfig<ReqSchema, PathParams, QueryParams> = {} as EndpointCallConfig<\n ReqSchema,\n PathParams,\n QueryParams\n >\n ): Promise<z.infer<ResSchema>> {\n const { data, query, headers, baseUrlKey, signal, pathParams } = config;\n\n // Validate request body/params before sending (when schema provided)\n if (this.requestSchema && data !== undefined) {\n const parsed = this.requestSchema.safeParse(data);\n if (!parsed.success) throw parsed.error;\n }\n\n // Build path - use pathParams if provided, otherwise use data for backwards compatibility\n const pathArgs = (pathParams ?? data) as PathParams;\n const path = typeof this.path === 'function' ? this.path(pathArgs) : this.path;\n\n // For GET/HEAD methods, don't send body\n const shouldHaveBody = this.method !== 'GET' && this.method !== 'HEAD';\n const body = shouldHaveBody ? data : undefined;\n\n // Convert query params to the format expected by http-client\n const queryForRequest = query as\n | Record<string, string | number | boolean | undefined>\n | URLSearchParams\n | undefined;\n\n const { data: responseData } = await this.client.request(this.method, path, body, {\n query: queryForRequest,\n headers,\n baseUrlKey: baseUrlKey ?? this.options?.baseUrlKey,\n signal,\n });\n\n return parseOrThrow<z.infer<ResSchema>>(this.responseSchema, responseData);\n }\n}\n","import { z } from 'zod';\n\n/**\n * Common ID type that supports strings, numbers, or UUIDs.\n * Use this for entity identifiers in your schemas.\n *\n * @example\n * ```ts\n * const UserSchema = z.object({ id: Id, name: z.string() });\n * ```\n */\nexport const Id = z.union([\n z.string().min(1),\n z.number(),\n z.uuid({\n version: 'v4',\n }),\n]);\nexport type IdType = z.infer<typeof Id>;\n\n/**\n * Common timestamp fields for entities.\n * Use this for database models with creation/update tracking.\n *\n * @example\n * ```ts\n * const UserSchema = z.object({\n * id: Id,\n * name: z.string(),\n * ...Timestamps.shape\n * });\n * ```\n */\nexport const Timestamps = z.object({\n createdAt: z.iso.datetime(),\n updatedAt: z.iso.datetime(),\n});\n\n/**\n * Metadata information typically included in API responses.\n * Contains request tracking and debugging information.\n */\nexport const Meta = z.object({\n requestId: z.string().optional(),\n timestamp: z.iso.datetime().optional(),\n traceId: z.string().optional(),\n});\n\n/**\n * Detailed error information for a specific field or path.\n */\nexport const ErrorDetail = z.object({\n path: z.string().optional(),\n message: z.string(),\n});\n\n/**\n * Standard API error response schema.\n * Use this for consistent error handling across your API.\n */\nexport const ApiErrorSchema = z.object({\n code: z.string(),\n message: z.string(),\n details: z.array(ErrorDetail).optional(),\n});\n\n/**\n * Generic envelope wrapper for API responses.\n * Provides consistent structure with success flag, data, error, and metadata.\n *\n * @param inner - Zod schema for the response data\n * @returns Envelope schema wrapping the inner schema\n *\n * @example\n * ```ts\n * const UserResponseSchema = Envelope(z.object({ id: Id, name: z.string() }));\n *\n * // Response structure:\n * // {\n * // success: true,\n * // data: { id: 1, name: 'John' },\n * // meta: { requestId: '...' }\n * // }\n * ```\n */\nexport const Envelope = <T extends z.ZodType>(inner: T) =>\n z.object({\n success: z.boolean(),\n data: inner.optional().nullable(),\n error: ApiErrorSchema.optional(),\n meta: Meta.optional(),\n });\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ const HTTPMethod = {
|
|
|
13
13
|
/**
|
|
14
14
|
* Custom error class for API-related errors.
|
|
15
15
|
* Includes HTTP status codes, response details, and validation errors.
|
|
16
|
-
*
|
|
16
|
+
*
|
|
17
17
|
* @example
|
|
18
18
|
* ```ts
|
|
19
19
|
* throw new ApiError('Invalid request', {
|
|
@@ -79,10 +79,10 @@ const PaginationSchema = z.object({
|
|
|
79
79
|
/**
|
|
80
80
|
* Converts query parameters to a URL query string.
|
|
81
81
|
* Filters out undefined values automatically.
|
|
82
|
-
*
|
|
82
|
+
*
|
|
83
83
|
* @param q - Query parameters as URLSearchParams or object
|
|
84
84
|
* @returns Query string with leading '?' or empty string
|
|
85
|
-
*
|
|
85
|
+
*
|
|
86
86
|
* @example
|
|
87
87
|
* ```ts
|
|
88
88
|
* toQueryString({ page: 1, filter: 'active' }) // "?page=1&filter=active"
|
|
@@ -115,12 +115,12 @@ var NoAuth = class {
|
|
|
115
115
|
/**
|
|
116
116
|
* API Key authentication provider.
|
|
117
117
|
* Supports both header-based and query parameter-based authentication.
|
|
118
|
-
*
|
|
118
|
+
*
|
|
119
119
|
* @example
|
|
120
120
|
* ```ts
|
|
121
121
|
* // Header-based
|
|
122
122
|
* const auth = new ApiKeyAuth({ header: 'X-API-Key', value: 'secret' });
|
|
123
|
-
*
|
|
123
|
+
*
|
|
124
124
|
* // Query parameter-based
|
|
125
125
|
* const auth = new ApiKeyAuth({ query: 'apiKey', value: 'secret' });
|
|
126
126
|
* ```
|
|
@@ -146,12 +146,12 @@ var ApiKeyAuth = class {
|
|
|
146
146
|
/**
|
|
147
147
|
* Bearer token authentication provider.
|
|
148
148
|
* Supports both static tokens and dynamic token fetching (e.g., for OAuth2 refresh).
|
|
149
|
-
*
|
|
149
|
+
*
|
|
150
150
|
* @example
|
|
151
151
|
* ```ts
|
|
152
152
|
* // Static token
|
|
153
153
|
* const auth = new BearerTokenAuth(() => 'my-token');
|
|
154
|
-
*
|
|
154
|
+
*
|
|
155
155
|
* // Dynamic token with refresh
|
|
156
156
|
* const auth = new BearerTokenAuth(async () => {
|
|
157
157
|
* return await refreshAccessToken();
|
|
@@ -177,11 +177,11 @@ var BearerTokenAuth = class {
|
|
|
177
177
|
/**
|
|
178
178
|
* Safely parse data with a Zod schema without throwing.
|
|
179
179
|
* Returns a result object with success status and data or error.
|
|
180
|
-
*
|
|
180
|
+
*
|
|
181
181
|
* @param schema - Zod schema to validate against
|
|
182
182
|
* @param data - Data to validate
|
|
183
183
|
* @returns Result object with success flag and data/error
|
|
184
|
-
*
|
|
184
|
+
*
|
|
185
185
|
* @example
|
|
186
186
|
* ```ts
|
|
187
187
|
* const result = safeParse(UserSchema, userData);
|
|
@@ -206,12 +206,12 @@ function safeParse(schema, data) {
|
|
|
206
206
|
/**
|
|
207
207
|
* Parse data with a Zod schema, throwing an ApiError on validation failure.
|
|
208
208
|
* Use this when you want to fail fast on invalid data.
|
|
209
|
-
*
|
|
209
|
+
*
|
|
210
210
|
* @param schema - Zod schema to validate against
|
|
211
211
|
* @param data - Data to validate
|
|
212
212
|
* @returns Validated and typed data
|
|
213
213
|
* @throws {ApiError} If validation fails
|
|
214
|
-
*
|
|
214
|
+
*
|
|
215
215
|
* @example
|
|
216
216
|
* ```ts
|
|
217
217
|
* try {
|
|
@@ -404,7 +404,7 @@ var ConsoleMetricsCollector = class {
|
|
|
404
404
|
/**
|
|
405
405
|
* HTTP client with built-in retry logic, authentication, and interceptors.
|
|
406
406
|
* Supports multiple base URLs, type-safe requests, and comprehensive error handling.
|
|
407
|
-
*
|
|
407
|
+
*
|
|
408
408
|
* @example
|
|
409
409
|
* ```ts
|
|
410
410
|
* const client = new HttpClient({
|
|
@@ -413,7 +413,7 @@ var ConsoleMetricsCollector = class {
|
|
|
413
413
|
* retry: { maxRetries: 3, baseDelayMs: 1000 },
|
|
414
414
|
* timeout: { requestTimeoutMs: 30000 }
|
|
415
415
|
* });
|
|
416
|
-
*
|
|
416
|
+
*
|
|
417
417
|
* const { data } = await client.request('GET', '/users', undefined, { query: { page: 1 } });
|
|
418
418
|
* ```
|
|
419
419
|
*/
|
|
@@ -429,7 +429,7 @@ var HttpClient = class {
|
|
|
429
429
|
metrics;
|
|
430
430
|
/**
|
|
431
431
|
* Creates a new HTTP client instance.
|
|
432
|
-
*
|
|
432
|
+
*
|
|
433
433
|
* @param opts - Client configuration options
|
|
434
434
|
* @throws {Error} If no fetch implementation is available
|
|
435
435
|
*/
|
|
@@ -458,7 +458,7 @@ var HttpClient = class {
|
|
|
458
458
|
}
|
|
459
459
|
/**
|
|
460
460
|
* Set or update the authentication provider.
|
|
461
|
-
*
|
|
461
|
+
*
|
|
462
462
|
* @param auth - Authentication provider instance
|
|
463
463
|
* @example
|
|
464
464
|
* ```ts
|
|
@@ -526,15 +526,32 @@ var HttpClient = class {
|
|
|
526
526
|
});
|
|
527
527
|
}
|
|
528
528
|
/**
|
|
529
|
+
* Get all configured base URLs.
|
|
530
|
+
*
|
|
531
|
+
* @returns Object mapping base URL keys to their resolved URLs
|
|
532
|
+
*/
|
|
533
|
+
getBaseUrls() {
|
|
534
|
+
return this.baseUrls;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Get the resolved base URL for a given key.
|
|
538
|
+
*
|
|
539
|
+
* @param key - Base URL key (defaults to 'default' if not provided)
|
|
540
|
+
* @returns Resolved base URL string
|
|
541
|
+
*/
|
|
542
|
+
getBaseUrl(key) {
|
|
543
|
+
return this.resolveBaseUrl(key);
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
529
546
|
* Make an HTTP request with automatic retry, authentication, and validation.
|
|
530
|
-
*
|
|
547
|
+
*
|
|
531
548
|
* @param method - HTTP method (GET, POST, PUT, etc.)
|
|
532
549
|
* @param path - Request path (will be appended to base URL)
|
|
533
550
|
* @param body - Request body (will be JSON.stringify'd if Content-Type is json)
|
|
534
551
|
* @param options - Additional request options (headers, query params, etc.)
|
|
535
552
|
* @returns Promise resolving to response data and Response object
|
|
536
553
|
* @throws {ApiError} If request fails or response validation fails
|
|
537
|
-
*
|
|
554
|
+
*
|
|
538
555
|
* @example
|
|
539
556
|
* ```ts
|
|
540
557
|
* const { data, response } = await client.request('GET', '/users', undefined, {
|
|
@@ -649,7 +666,7 @@ var HttpClient = class {
|
|
|
649
666
|
}
|
|
650
667
|
/**
|
|
651
668
|
* Convenience method for GET requests.
|
|
652
|
-
*
|
|
669
|
+
*
|
|
653
670
|
* @example
|
|
654
671
|
* ```ts
|
|
655
672
|
* const { data } = await client.get('/users', { query: { page: 1 } });
|
|
@@ -660,7 +677,7 @@ var HttpClient = class {
|
|
|
660
677
|
}
|
|
661
678
|
/**
|
|
662
679
|
* Convenience method for POST requests.
|
|
663
|
-
*
|
|
680
|
+
*
|
|
664
681
|
* @example
|
|
665
682
|
* ```ts
|
|
666
683
|
* const { data } = await client.post('/users', { name: 'John' });
|
|
@@ -671,7 +688,7 @@ var HttpClient = class {
|
|
|
671
688
|
}
|
|
672
689
|
/**
|
|
673
690
|
* Convenience method for PUT requests.
|
|
674
|
-
*
|
|
691
|
+
*
|
|
675
692
|
* @example
|
|
676
693
|
* ```ts
|
|
677
694
|
* const { data } = await client.put('/users/1', { name: 'John Updated' });
|
|
@@ -682,7 +699,7 @@ var HttpClient = class {
|
|
|
682
699
|
}
|
|
683
700
|
/**
|
|
684
701
|
* Convenience method for PATCH requests.
|
|
685
|
-
*
|
|
702
|
+
*
|
|
686
703
|
* @example
|
|
687
704
|
* ```ts
|
|
688
705
|
* const { data } = await client.patch('/users/1', { name: 'John' });
|
|
@@ -693,7 +710,7 @@ var HttpClient = class {
|
|
|
693
710
|
}
|
|
694
711
|
/**
|
|
695
712
|
* Convenience method for DELETE requests.
|
|
696
|
-
*
|
|
713
|
+
*
|
|
697
714
|
* @example
|
|
698
715
|
* ```ts
|
|
699
716
|
* const { data } = await client.delete('/users/1');
|
|
@@ -709,26 +726,42 @@ var HttpClient = class {
|
|
|
709
726
|
/**
|
|
710
727
|
* Generic, strongly-typed endpoint with Zod schemas for request and response validation.
|
|
711
728
|
* Extend this class to create type-safe API endpoints.
|
|
712
|
-
*
|
|
729
|
+
*
|
|
713
730
|
* @template ReqSchema - Zod schema for request validation
|
|
714
731
|
* @template ResSchema - Zod schema for response validation
|
|
715
|
-
*
|
|
732
|
+
* @template PathParams - Type for path parameters (optional)
|
|
733
|
+
* @template QueryParams - Type for query parameters (optional)
|
|
734
|
+
*
|
|
716
735
|
* @example
|
|
717
736
|
* ```ts
|
|
718
737
|
* const UserSchema = z.object({ id: z.number(), name: z.string() });
|
|
719
738
|
* const CreateUserSchema = z.object({ name: z.string() });
|
|
720
|
-
*
|
|
721
|
-
*
|
|
739
|
+
*
|
|
740
|
+
* type UserPathParams = { id: string };
|
|
741
|
+
* type UserQueryParams = { include?: string; limit?: number };
|
|
742
|
+
*
|
|
743
|
+
* class GetUser extends BaseEndpoint<
|
|
744
|
+
* typeof CreateUserSchema,
|
|
745
|
+
* typeof UserSchema,
|
|
746
|
+
* UserPathParams,
|
|
747
|
+
* UserQueryParams
|
|
748
|
+
* > {
|
|
722
749
|
* protected method = 'GET' as const;
|
|
723
|
-
* protected path = (
|
|
724
|
-
*
|
|
750
|
+
* protected path = (params: UserPathParams) => `/users/${params.id}`;
|
|
751
|
+
*
|
|
725
752
|
* constructor(client: HttpClient) {
|
|
726
|
-
* super(client, {
|
|
753
|
+
* super(client, {
|
|
727
754
|
* requestSchema: CreateUserSchema,
|
|
728
|
-
* responseSchema: UserSchema
|
|
755
|
+
* responseSchema: UserSchema
|
|
729
756
|
* });
|
|
730
757
|
* }
|
|
731
758
|
* }
|
|
759
|
+
*
|
|
760
|
+
* // Usage:
|
|
761
|
+
* const user = await endpoint.call({
|
|
762
|
+
* pathParams: { id: '123' },
|
|
763
|
+
* query: { include: 'posts', limit: 10 }
|
|
764
|
+
* });
|
|
732
765
|
* ```
|
|
733
766
|
*/
|
|
734
767
|
var BaseEndpoint = class {
|
|
@@ -750,19 +783,23 @@ var BaseEndpoint = class {
|
|
|
750
783
|
/**
|
|
751
784
|
* Call the endpoint with strong typing derived from schemas.
|
|
752
785
|
* Validates request data before sending and response data after receiving.
|
|
753
|
-
*
|
|
786
|
+
*
|
|
754
787
|
* @param config - Request configuration object containing all parameters
|
|
755
788
|
* @returns Promise resolving to validated response data (typed by ResSchema)
|
|
756
789
|
* @throws {ZodError} If request validation fails
|
|
757
790
|
* @throws {ApiError} If response validation fails or request fails
|
|
758
|
-
*
|
|
791
|
+
*
|
|
759
792
|
* @example
|
|
760
793
|
* ```ts
|
|
761
794
|
* const endpoint = new GetUser(client);
|
|
762
|
-
* const user = await endpoint.call({
|
|
795
|
+
* const user = await endpoint.call({
|
|
796
|
+
* pathParams: { id: '123' },
|
|
797
|
+
* query: { include: 'posts' }
|
|
798
|
+
* });
|
|
763
799
|
* // With additional options:
|
|
764
|
-
* const user = await endpoint.call({
|
|
765
|
-
* data: {
|
|
800
|
+
* const user = await endpoint.call({
|
|
801
|
+
* data: { name: 'John' },
|
|
802
|
+
* pathParams: { id: '123' },
|
|
766
803
|
* headers: { 'X-Custom': 'value' },
|
|
767
804
|
* query: { include: 'posts' }
|
|
768
805
|
* });
|
|
@@ -777,8 +814,9 @@ var BaseEndpoint = class {
|
|
|
777
814
|
const pathArgs = pathParams ?? data;
|
|
778
815
|
const path = typeof this.path === "function" ? this.path(pathArgs) : this.path;
|
|
779
816
|
const body = this.method !== "GET" && this.method !== "HEAD" ? data : void 0;
|
|
817
|
+
const queryForRequest = query;
|
|
780
818
|
const { data: responseData } = await this.client.request(this.method, path, body, {
|
|
781
|
-
query,
|
|
819
|
+
query: queryForRequest,
|
|
782
820
|
headers,
|
|
783
821
|
baseUrlKey: baseUrlKey ?? this.options?.baseUrlKey,
|
|
784
822
|
signal
|
|
@@ -792,7 +830,7 @@ var BaseEndpoint = class {
|
|
|
792
830
|
/**
|
|
793
831
|
* Common ID type that supports strings, numbers, or UUIDs.
|
|
794
832
|
* Use this for entity identifiers in your schemas.
|
|
795
|
-
*
|
|
833
|
+
*
|
|
796
834
|
* @example
|
|
797
835
|
* ```ts
|
|
798
836
|
* const UserSchema = z.object({ id: Id, name: z.string() });
|
|
@@ -806,7 +844,7 @@ const Id = z.union([
|
|
|
806
844
|
/**
|
|
807
845
|
* Common timestamp fields for entities.
|
|
808
846
|
* Use this for database models with creation/update tracking.
|
|
809
|
-
*
|
|
847
|
+
*
|
|
810
848
|
* @example
|
|
811
849
|
* ```ts
|
|
812
850
|
* const UserSchema = z.object({
|
|
@@ -848,14 +886,14 @@ const ApiErrorSchema = z.object({
|
|
|
848
886
|
/**
|
|
849
887
|
* Generic envelope wrapper for API responses.
|
|
850
888
|
* Provides consistent structure with success flag, data, error, and metadata.
|
|
851
|
-
*
|
|
889
|
+
*
|
|
852
890
|
* @param inner - Zod schema for the response data
|
|
853
891
|
* @returns Envelope schema wrapping the inner schema
|
|
854
|
-
*
|
|
892
|
+
*
|
|
855
893
|
* @example
|
|
856
894
|
* ```ts
|
|
857
895
|
* const UserResponseSchema = Envelope(z.object({ id: Id, name: z.string() }));
|
|
858
|
-
*
|
|
896
|
+
*
|
|
859
897
|
* // Response structure:
|
|
860
898
|
* // {
|
|
861
899
|
* // success: true,
|