UNPKG

@follow-app/client-sdk

Version:

TypeScript client SDK for Follow RSS Server API

164 lines (148 loc) 3.89 kB
/* eslint-disable @typescript-eslint/no-empty-object-type */ import type { HTTPMethod, RequestContentType, ResponseContentType, } from "../types" /** * Route definition with type annotations */ export interface RouteDefinition<TInput = any, TResponse = any> { method: HTTPMethod path: string params?: readonly string[] /** Fields that should be sent as query parameters */ query?: readonly string[] /** Fields that should be sent as request body */ body?: readonly string[] input?: TInput response?: TResponse requestType?: RequestContentType responseType?: ResponseContentType } /** * Nested routes structure - supports any depth of nesting */ export type NestedRoutes = { [key: string]: RouteDefinition | NestedRoutes } /** * Module definition interface */ export interface ModuleDefinition<TRoutes extends NestedRoutes> { name: string prefix?: string routes: TRoutes api: ModuleAPI<TRoutes> } /** * Fetch options for route requests */ interface FetchOptions { headers?: Record<string, string> timeout?: number signal?: AbortSignal } /** * Check if arguments are required */ type IsRequired<TInput> = [TInput] extends [never] ? false : {} extends TInput ? false : true /** * Route function with proper argument requirements */ export type RouteFunction<TInput, TResponse> = IsRequired<TInput> extends true ? (args: TInput, options?: FetchOptions) => Promise<TResponse> : (args?: TInput, options?: FetchOptions) => Promise<TResponse> /** * Generate API types from route structure */ export type ModuleAPI<TRoutes> = { [K in keyof TRoutes]: TRoutes[K] extends RouteDefinition< infer TInput, infer TResponse > ? RouteFunction<TInput, TResponse> : TRoutes[K] extends NestedRoutes ? ModuleAPI<TRoutes[K]> : never; } /** * Legacy route args interface for backward compatibility */ export interface LegacyRouteArgs { params?: Record<string, string> query?: Record<string, string | number | boolean> body?: unknown headers?: Record<string, string> timeout?: number signal?: AbortSignal } /** * Helper function to define a single route with proper type inference */ export function defineRoute<TInput = never, TResponse = never>( method: HTTPMethod, path: string, options: { /** Fields that should be sent as query parameters */ query?: readonly string[] /** Fields that should be sent as request body */ body?: readonly string[] input?: TInput response?: TResponse requestType?: RequestContentType responseType?: ResponseContentType } = {}, ): RouteDefinition<TInput, TResponse> { // Extract parameters from path string const params = extractParamsFromPath(path) return { method, path, params: params.length > 0 ? params : undefined, query: options.query, body: options.body, input: options.input, response: options.response, requestType: options.requestType, responseType: options.responseType, } } /** * Extract parameter names from a path string * e.g. "/users/{userId}/posts" -> ["userId"] */ function extractParamsFromPath(path: string): string[] { const paramRegex = /\{([^}]+)\}/g const params: string[] = [] let match while ((match = paramRegex.exec(path)) !== null) { params.push(match[1]) } return params } /** * Helper type to infer params from path string */ export type InferParams<T extends string> = T extends `${string}{${infer Param}}${infer Rest}` ? { [K in Param]: string } & InferParams<Rest> : {} /** * Define a module with routes and metadata */ export function defineModule<TRoutes extends NestedRoutes>(definition: { name: string prefix?: string routes: TRoutes }): ModuleDefinition<TRoutes> { return { ...definition, api: {} as ModuleAPI<TRoutes>, // Will be created by proxy } }