type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; type WriteMethod = "POST" | "PUT" | "PATCH" | "DELETE"; type Simplify = { [K in keyof T]: T[K]; } & {}; type QueryField = [TQuery] extends [never] ? object : { query: TQuery; }; type BodyField = [TBody] extends [never] ? object : { body: TBody; }; type ParamsField = [TParamNames] extends [never] ? object : { params: Record; }; type InputFields = QueryField & BodyField & ParamsField; type InputFieldWrapper = [ TQuery, TBody, TParamNames ] extends [never, never, never] ? object : { input: InputFields; }; type SpooshResponse = ({ status: number; data: TData; headers?: Headers; error?: undefined; aborted?: false; readonly __requestOptions?: TRequestOptions; } & InputFieldWrapper) | ({ status: number; data?: undefined; headers?: Headers; error: TError; aborted?: boolean; readonly __requestOptions?: TRequestOptions; } & InputFieldWrapper); /** * Opaque type representing a transformed body. Create using `form()`, `json()`, or `urlencoded()` helpers. * Do not create this type manually - use the helper functions instead. * * @example * ```ts * import { form, json, urlencoded } from "@spoosh/core"; * * // Use helpers to create SpooshBody * trigger({ body: form({ file: myFile }) }); * trigger({ body: json({ data: "value" }) }); * trigger({ body: urlencoded({ key: "value" }) }); * ``` */ declare const __spooshBodyBrand: unique symbol; type SpooshBody = { readonly [__spooshBodyBrand]: T; }; declare function isSpooshBody(value: unknown): value is SpooshBody; declare function form(value: T): SpooshBody; declare function json(value: T): SpooshBody; declare function urlencoded(value: T): SpooshBody; declare function resolveRequestBody(rawBody: unknown): { body: BodyInit; headers?: Record; removeHeaders?: string[]; } | undefined; interface TransportResponse { ok: boolean; status: number; headers: Headers; data: unknown; } type Transport = (url: string, init: RequestInit, options?: TOptions) => Promise; /** * Transport layer used for requests. * * - `"fetch"` — Uses the Fetch API (default). * - `"xhr"` — Uses XMLHttpRequest. Required for upload/download progress tracking. */ type TransportOption = "fetch" | "xhr"; interface TransportOptionsMap { fetch: never; } type HeadersInitOrGetter = HeadersInit | (() => HeadersInit | Promise); type SpooshOptions = Omit & { headers?: HeadersInitOrGetter; /** Default transport for all requests. */ transport?: TransportOption; }; type FetchOnlyInitKeys = "mode" | "cache" | "integrity" | "keepalive" | "next" | "priority" | "redirect" | "referrer" | "referrerPolicy" | "window"; type SharedSpooshOptions = Omit & { headers?: HeadersInitOrGetter; }; type SpooshFetchOptions = SharedSpooshOptions & Pick> & { transport?: "fetch"; }; type SpooshXhrOptions = SharedSpooshOptions & { transport: "xhr"; }; /** * Constructor-level options with transport-aware type narrowing. * * When `transport` is `"xhr"`, fetch-only fields (e.g. `mode`, `cache`, `redirect`) are * excluded from autocomplete since they have no effect on XMLHttpRequest. */ type SpooshOptionsInput = SpooshFetchOptions | SpooshXhrOptions; type BaseRequestOptions$1 = { headers?: HeadersInitOrGetter; cache?: RequestCache; signal?: AbortSignal; }; type BodyOption$1 = [TBody] extends [never] ? object : undefined extends TBody ? { body?: Exclude | SpooshBody>; } : { body: TBody | SpooshBody; }; type QueryOption$1 = [TQuery] extends [never] ? object : undefined extends TQuery ? { query?: Exclude; } : { query: TQuery; }; type RequestOptions$1 = BaseRequestOptions$1 & BodyOption$1 & QueryOption$1; type AnyRequestOptions = BaseRequestOptions$1 & { body?: unknown; query?: Record; params?: Record; signal?: AbortSignal; /** Per-request transport override. */ transport?: TransportOption; /** Transport-specific options passed through to the transport function. */ transportOptions?: unknown; }; type DynamicParamsOption = { params?: Record; }; type CoreRequestOptionsBase = { __hasDynamicParams?: DynamicParamsOption; }; type MethodOptionsMap = { GET: TQueryOptions; POST: TMutationOptions; PUT: TMutationOptions; PATCH: TMutationOptions; DELETE: TMutationOptions; }; type ExtractMethodOptions = TOptionsMap extends MethodOptionsMap ? TMethod extends "GET" ? TQuery : TMutation : TOptionsMap; type FetchExecutor = (baseUrl: string, path: string[], method: HttpMethod, defaultOptions: TOptions, requestOptions?: TRequestOptions, nextTags?: boolean) => Promise>; type TypedParamsOption = [TParamNames] extends [ never ] ? object : { params?: Record; }; type ComputeRequestOptions = "__hasDynamicParams" extends keyof TRequestOptionsBase ? [TParamNames] extends [never] ? Omit : Omit & TypedParamsOption : TRequestOptionsBase & TypedParamsOption; type EventCallback = (payload: T) => void; /** * Built-in event map. Maps event names to their payload types. * * Third-party plugins can extend this via declaration merging: * @example * ```ts * declare module '@spoosh/core' { * interface BuiltInEvents { * "my-custom-event": MyPayloadType; * } * } * ``` */ interface BuiltInEvents { refetch: RefetchEvent; invalidate: string[]; refetchAll: void; } type EventEmitter = { /** * Subscribe to an event. Built-in events have type-safe payloads. * * @example * ```ts * // Built-in event - payload is typed as RefetchEvent * eventEmitter.on("refetch", (event) => { * console.log(event.queryKey, event.reason); * }); * * // Custom event - specify type explicitly * eventEmitter.on("my-event", (payload) => { ... }); * ``` */ on(event: E, callback: EventCallback): () => void; on(event: string, callback: EventCallback): () => void; /** * Emit an event. Built-in events have type-safe payloads. * * @example * ```ts * // Built-in event - payload is type-checked * eventEmitter.emit("refetch", { queryKey: "...", reason: "focus" }); * * // Custom event * eventEmitter.emit("my-event", myPayload); * ``` */ emit(event: E, payload: BuiltInEvents[E]): void; emit(event: string, payload: T): void; off: (event: string, callback: EventCallback) => void; clear: () => void; }; declare function createEventEmitter(): EventEmitter; type Subscriber = () => void; type DataChangeCallback = (key: string, oldData: unknown, newData: unknown) => void; type CacheEntryWithKey = { key: string; entry: CacheEntry; }; type StateManager = { createQueryKey: (params: { path: string; method: string; options?: unknown; }) => string; getCache: (key: string) => CacheEntry | undefined; setCache: (key: string, entry: Partial>) => void; deleteCache: (key: string) => void; subscribeCache: (key: string, callback: Subscriber) => () => void; getCacheByTags: (tags: string[]) => CacheEntry | undefined; getCacheEntriesByTags: (tags: string[]) => CacheEntryWithKey[]; getCacheEntriesBySelfTag: (selfTag: string) => CacheEntryWithKey[]; setMeta: (key: string, data: Record) => void; /** Mark all cache entries with matching tags as stale */ markStale: (tags: string[]) => void; /** Get all cache entries */ getAllCacheEntries: () => CacheEntryWithKey[]; /** Get the number of cache entries */ getSize: () => number; /** Get the number of active subscribers for a cache key */ getSubscribersCount: (key: string) => number; /** Set a pending promise for a query key (for deduplication) */ setPendingPromise: (key: string, promise: Promise | undefined) => void; /** Get a pending promise for a query key */ getPendingPromise: (key: string) => Promise | undefined; /** * Register a callback to be invoked when cache data changes. * @returns Unsubscribe function */ onDataChange: (callback: DataChangeCallback) => () => void; clear: () => void; }; declare function createStateManager(): StateManager; declare function createInitialState(): OperationState; declare function generateSelfTagFromKey(key: string): string | undefined; /** * Devtool-related types for tracing and debugging. * These types are used by the devtool plugin and plugins that emit trace events. */ /** * Stage of plugin execution for tracing. */ type TraceStage = "return" | "log" | "skip" | "fetch"; /** * Color hint for devtools visualization. */ type TraceColor = "success" | "warning" | "error" | "info" | "muted"; /** * Structured trace event emitted by plugins. * Plugins self-report what they did and why. */ type TraceEvent = { /** Plugin name */ plugin: string; /** Execution stage */ stage: TraceStage; /** Human-readable explanation of what happened */ reason?: string; /** Color hint for devtools (success=green, warning=yellow, error=red, info=blue) */ color?: TraceColor; /** Before/after diff with optional label */ diff?: { before: unknown; after: unknown; label?: string; }; /** Structured information to display (e.g., invalidated tags, cache keys) */ info?: Array<{ label?: string; value: unknown; }>; }; /** * Trace API available to plugins via ctx.trace. * Plugins emit structured events; devtools renders them. * * @example * ```ts * middleware: async (ctx, next) => { * const cached = getCache(ctx.queryKey); * if (cached) { * ctx.trace?.step({ * plugin: "cache", * stage: "skip", * meta: { reason: "Cache hit (TTL valid)" } * }); * return cached; * } * * ctx.trace?.step({ * plugin: "cache", * stage: "before", * intent: "read", * }); * * const result = await next(); * * ctx.trace?.step({ * plugin: "cache", * stage: "after", * meta: { * reason: "Stored in cache", * diff: { before: null, after: result.data } * } * }); * * return result; * } * ``` */ type Trace = { /** * Emit a trace event. Lazy evaluation - only computed when devtools is active. * * @param event - Trace event or function that returns trace event (for lazy evaluation) */ step: (event: TraceEvent | (() => TraceEvent)) => void; }; /** * Listener for trace events emitted by plugins. */ type TraceListener = (event: TraceEvent & { queryKey: string; timestamp: number; }) => void; /** * Standalone event not tied to a request lifecycle. * Used for polling, debounce, gc, and other background activities. */ type StandaloneEvent = { /** Plugin name */ plugin: string; /** Human-readable message */ message: string; /** Color hint for devtools */ color?: TraceColor; /** Related query key (for filtering) */ queryKey?: string; /** Additional metadata */ meta?: Record; /** Timestamp when event occurred */ timestamp: number; }; type EventListener = (event: StandaloneEvent) => void; type TraceInfo = { label?: string; value: unknown; }; type TraceOptions = { color?: TraceColor; diff?: { before: unknown; after: unknown; label?: string; }; info?: TraceInfo[]; }; type EventOptions = { color?: TraceColor; /** Query key this event relates to (for filtering) */ queryKey?: string; /** Additional metadata to display */ meta?: Record; }; /** * Request-bound tracer API for plugins. * Created via `context.tracer?.(pluginName)`. * Automatically bound to the request's queryKey for accurate devtool tracing. * * @example * ```ts * const t = context.tracer?.("my-plugin"); * t?.return("Cache hit", { color: "success" }); * t?.log("Transformed", { color: "info", diff: { before, after } }); * t?.skip("Nothing to do", { color: "muted" }); * ``` */ interface RequestTracer { /** Returned early without calling next() */ return(msg: string, options?: TraceOptions): void; /** Did something (any activity worth noting) */ log(msg: string, options?: TraceOptions): void; /** Nothing to do, passed through */ skip(msg: string, options?: TraceOptions): void; } /** * Event emitted after all afterResponse hooks complete. * Used by devtools to capture meta snapshots. */ interface RequestCompleteEvent { context: PluginContext; queryKey: string; } /** Event emitted when a subscription starts connecting */ interface SubscriptionConnectEvent { subscriptionId: string; channel: string; transport: string; connectionUrl: string; queryKey: string; timestamp: number; /** Event types being listened to. Empty or ["*"] means all events. */ listenedEvents?: string[]; } /** Event emitted when a subscription successfully connects */ interface SubscriptionConnectedEvent { subscriptionId: string; timestamp: number; } /** Event emitted when a subscription receives a message */ interface SubscriptionMessageEvent { subscriptionId: string; messageId: string; eventType: string; rawData: unknown; accumulatedData: Record; timestamp: number; } /** Event emitted when a subscription encounters an error */ interface SubscriptionErrorEvent { subscriptionId: string; error: Error; retryCount: number; timestamp: number; } /** Event emitted when a subscription disconnects */ interface SubscriptionDisconnectEvent { subscriptionId: string; reason: string; timestamp: number; } /** Event emitted when hook-level accumulation updates data */ interface SubscriptionAccumulateEvent { queryKey: string; eventType: string; accumulatedData: Record; timestamp: number; } /** * Internal events used by core and devtools. Not for public use. * @internal */ interface DevtoolEvents { "spoosh:devtool-event": StandaloneEvent; "spoosh:request-complete": RequestCompleteEvent; "spoosh:subscription:connect": SubscriptionConnectEvent; "spoosh:subscription:connected": SubscriptionConnectedEvent; "spoosh:subscription:message": SubscriptionMessageEvent; "spoosh:subscription:error": SubscriptionErrorEvent; "spoosh:subscription:disconnect": SubscriptionDisconnectEvent; "spoosh:subscription:accumulate": SubscriptionAccumulateEvent; } /** * Event tracer API for standalone events not tied to a request lifecycle. * Created via `context.eventTracer?.(pluginName)`. * Use for async callbacks like polling, debounce completion, gc, etc. * * @example * ```ts * const et = context.eventTracer?.("my-plugin"); * et?.emit("Poll triggered", { queryKey, color: "success" }); * et?.emit("GC cleaned 5 entries", { color: "info", meta: { count: 5 } }); * ``` */ interface EventTracer { /** Emit a standalone event not tied to a request */ emit(msg: string, options?: EventOptions): void; } interface SpooshTransport { name: string; operationType: OperationType; connect(url: string, options?: TOptions): Promise; disconnect(): Promise; subscribe(channel: string, callback: (message: TMessage) => void): () => void; send(channel: string, message: TMessage): Promise; isConnected(): boolean; } interface SpooshTransportRegistry { } type TransportName = keyof SpooshTransportRegistry; interface SubscriptionContext extends PluginContext { channel: string; message?: unknown; onData?: (data: TData) => void; onError?: (error: TError) => void; onDisconnect?: () => void; registerUnsubscribers?: (unsubscribers: Array<() => void>) => void; } interface SubscriptionAdapter { subscribe(context: SubscriptionContext): Promise>; emit(context: SubscriptionContext): Promise<{ success: boolean; error?: TError; }>; } interface SubscriptionHandle { unsubscribe(): void; getData(): TData | undefined; getError(): TError | undefined; onData(callback: (data: TData) => void): () => void; onError(callback: (error: TError) => void): () => void; } type OperationType = "read" | "write" | "pages" | "queue" | (string & {}); type LifecyclePhase = "onMount" | "onUnmount" | "onUpdate"; type OperationState = { data: TData | undefined; error: TError | undefined; timestamp: number; }; type CacheEntry = { state: OperationState; tags: string[]; /** Plugin-contributed result data (e.g., isOptimistic, isStale). Merged into hook result. */ meta: Map; /** The original path-derived tag (e.g., "posts/1/comments"). Used for exact matching in cache */ selfTag?: string; previousData?: TData; /** Cache was invalidated while no subscriber was listening. Triggers refetch on next mount. */ stale?: boolean; }; /** RequestOptions in plugin context have headers already resolved to Record */ type PluginRequestOptions = Omit & { headers: Record; }; /** * Registry for extending PluginContext with custom properties. * Third-party plugins can extend this interface via declaration merging. * * @example * ```ts * // In your plugin's types file: * declare module '@spoosh/core' { * interface PluginContextExtensions { * myCustomProperty?: MyCustomType; * } * } * ``` */ interface PluginContextExtensions { } type PluginContextBase = { readonly operationType: OperationType; readonly path: string; readonly method: HttpMethod; readonly queryKey: string; readonly tags: string[]; readonly requestTimestamp: number; /** Unique identifier for this usage instance. Persists across queryKey changes. */ readonly instanceId?: string; /** Request options with resolved headers. Modify to customize the request (e.g., add headers, signal, body). */ request: PluginRequestOptions; /** Temporary storage for inter-plugin communication during a single request lifecycle. Data stored here doesn't persist beyond the request. */ temp: Map; /** State manager for accessing and modifying cache entries. */ stateManager: StateManager; /** Event emitter for triggering refetch, invalidation, and other events. */ eventEmitter: EventEmitter; /** Access other plugins' exported APIs */ plugins: PluginAccessor; /** Plugin-specific options passed from hooks (useRead/useWrite/usePages) */ pluginOptions?: unknown; /** Force a network request even if cached data exists. Used by plugins to communicate intent. */ forceRefetch?: boolean; /** * Creates a request-bound tracer for devtools debugging. * Automatically bound to this request's queryKey. * Only available when devtools plugin is active. * * @example * ```ts * const t = ctx.tracer?.("my-plugin"); * t?.return("Cache hit", { color: "success" }); * t?.log("Processing", { color: "info" }); * t?.skip("Nothing to do", { color: "muted" }); * ``` */ tracer?: (plugin: string) => RequestTracer; /** * Creates an event tracer for standalone events not tied to a request lifecycle. * Use for async callbacks like polling, debounce completion, gc, etc. * Only available when devtools plugin is active. * * @example * ```ts * const et = ctx.eventTracer?.("my-plugin"); * et?.emit("Poll triggered", { queryKey, color: "success" }); * ``` */ eventTracer?: (plugin: string) => EventTracer; }; /** * Plugin context with extensions from third-party plugins. * Plugins can extend this via PluginContextExtensions declaration merging. */ type PluginContext = PluginContextBase & PluginContextExtensions; /** Input type for creating PluginContext (without injected properties) */ type PluginContextInput = Omit; /** * Middleware function that wraps the fetch flow. * Plugins use this for full control over request/response handling. * * @param context - The plugin context with request info and utilities * @param next - Call this to continue to the next middleware or actual fetch * @returns The response (either from next() or early return) * * @example * ```ts * // Cache middleware - return cached data or continue * middleware: async (context, next) => { * const cached = context.stateManager.getCache(context.queryKey); * if (cached?.state?.data && !isStale(cached)) { * return { data: cached.state.data, status: 200 }; * } * return next(); * } * * // Auth middleware - add authentication headers * middleware: async (context, next) => { * context.request.headers['Authorization'] = `Bearer ${getToken()}`; * return next(); * } * ``` */ type PluginMiddleware = (context: PluginContext, next: () => Promise>) => Promise>; type PluginHandler = (context: PluginContext) => void | Promise; type PluginUpdateHandler = (context: PluginContext, previousContext: PluginContext) => void | Promise; /** * Handler called after every response, regardless of early returns from middleware. * Can return a new response to transform it, or void for side effects only. * Returned responses are chained through plugins in order. */ type PluginResponseHandler = (context: PluginContext, response: SpooshResponse) => SpooshResponse | void | Promise | void>; type PluginLifecycle = { /** Called on component mount */ onMount?: PluginHandler; /** Called when options/query changes. Receives both new and previous context. */ onUpdate?: PluginUpdateHandler; /** Called on component unmount */ onUnmount?: PluginHandler; }; /** * Configuration object for plugin type definitions. * Use this to specify which options and results your plugin provides. * * @example * ```ts * // Plugin with read options only * SpooshPlugin<{ readOptions: MyReadOptions }> * * // Plugin with read/write options and results * SpooshPlugin<{ * readOptions: MyReadOptions; * writeOptions: MyWriteOptions; * readResult: MyReadResult; * }> * * // Plugin with instance-level API * SpooshPlugin<{ * api: { prefetch: (selector: Selector) => Promise }; * }> * ``` */ type PluginTypeConfig = { readOptions?: object; writeOptions?: object; pagesOptions?: object; writeTriggerOptions?: object; queueOptions?: object; queueTriggerOptions?: object; subscribeOptions?: object; subscribeTriggerOptions?: object; readResult?: object; writeResult?: object; queueResult?: object; subscribeResult?: object; api?: object; }; /** * Base interface for Spoosh plugins. * * Plugins can implement: * - `middleware`: Wraps the fetch flow for full control (intercept, transform, modify) * - `afterResponse`: Called after every response, regardless of early returns * - `lifecycle`: Component lifecycle hooks (onMount, onUpdate, onUnmount) * - `internal`: Functions/variables accessible to other plugins * * @typeParam T - Plugin type configuration object. Specify only the types your plugin needs. * * @example * ```ts * function myPlugin(): SpooshPlugin<{ * readOptions: { cacheTime: number }; * readResult: { isFromCache: boolean }; * }> { * return { * name: "my-plugin", * operations: ["read"], * middleware: async (context, next) => { * // Full control over fetch flow * const result = await next(); * return result; * }, * afterResponse(context, response) { * // Always runs after response * }, * lifecycle: { * onMount(context) { }, * onUpdate(context, previousContext) { }, * onUnmount(context) { }, * }, * }; * } * ``` */ interface SpooshPlugin { name: string; operations: OperationType[]; /** Middleware for controlling the fetch flow. Called in plugin order, composing a chain. */ middleware?: PluginMiddleware; /** * Called after middleware chain completes, regardless of early returns. * Return a new response to transform it, or void for side effects only. */ afterResponse?: PluginResponseHandler; /** Component lifecycle hooks (setup, cleanup, option changes) */ lifecycle?: PluginLifecycle; /** Expose functions/variables for other plugins to access via `context.plugins.get(name)` */ internal?: (context: PluginContext) => object; /** * One-time initialization when the Spoosh instance is created. * Use for setting up timers, event listeners, or other side effects. * Runs before api is called. * * @example * ```ts * setup: ({ stateManager, eventEmitter, pluginExecutor }) => { * // Set up interval timer * const intervalId = setInterval(() => { * // periodic cleanup * }, 60000); * * // Register context enhancer * pluginExecutor.registerContextEnhancer((context) => { * context.myProperty = myValue; * }); * * // Set up event listener * eventEmitter.on("invalidate", (tags) => { * // handle invalidation * }); * } * ``` */ setup?: (context: SetupContext) => void; /** * Expose functions/properties on the framework adapter return value (e.g., create). * Unlike `internal`, these are accessible directly from the adapter, not just within plugin context. * Should be pure - use `setup` for side effects like timers or event listeners. * * @example * ```ts * api: ({ spopiosh, stateManager }) => ({ * prefetch: async (selector) => { ... }, * invalidateAll: () => { ... }, * }) * ``` */ api?: (context: ApiContext) => T extends { api: infer A; } ? A : object; /** * Wrap a subscription adapter to add plugin behavior (e.g., caching, retry, logging). * Called for subscription operations to compose adapter functionality. * * @example * ```ts * wrapAdapter: (inner) => ({ * subscribe: async (ctx) => { * // Add caching logic * return inner.subscribe(ctx); * }, * emit: inner.emit, * }) * ``` */ wrapAdapter?: (inner: SubscriptionAdapter) => SubscriptionAdapter; /** * Plugin execution priority. Lower numbers run first, higher numbers run last. * Default: 0 * * @example * ```ts * // Cache plugin runs early (checks cache before other plugins) * { name: "cache", priority: -10, ... } * * // Throttle plugin runs last (blocks all requests including force fetches) * { name: "throttle", priority: 100, ... } * * // Most plugins use default priority * { name: "retry", priority: 0, ... } // or omit priority * ``` */ priority?: number; /** * List of plugin names that this plugin depends on. * Used to validate that required plugins are registered. * Does not affect execution order - use `priority` for that. * * @example * ```ts * { * name: "spoosh:optimistic", * dependencies: ["spoosh:invalidation"], * // ... * } * ``` */ dependencies?: string[]; /** @internal Type carrier for inference - do not use directly */ readonly _types?: T; } /** * Helper type for creating plugin factory functions. * * @typeParam TConfig - Configuration object type (use `void` for no config) * @typeParam TTypes - Plugin type configuration object * * @example * ```ts * // Factory with no config * const myPlugin: PluginFactory = () => ({ ... }); * * // Factory with config * const myPlugin: PluginFactory = (config) => ({ ... }); * ``` */ type PluginFactory = TConfig extends void ? () => SpooshPlugin : (config?: TConfig) => SpooshPlugin; /** * Marker type for callbacks that need TData/TError from useRead/useWrite. * Third-party plugins should use this for data-aware callback options. * * @example * ```ts * interface MyPluginReadOptions { * onDataChange?: DataAwareCallback; // (data, error) => boolean * transform?: DataAwareTransform; // (data, error) => data * } * ``` */ type DataAwareCallback = (data: TData | undefined, error: TError | undefined) => TReturn; /** * Marker type for transform functions that receive and return TData. */ type DataAwareTransform = (data: TData | undefined, error: TError | undefined) => TData | undefined; /** * Context object containing all type information available for resolution. * 3rd party plugins can access any combination of these types. */ type ResolverContext = { schema: TSchema; data: TData; error: TError; input: { query: TQuery; body: TBody; params: TParams; }; }; /** * Unified resolver registry for plugin type resolution. * * 3rd party plugins can extend this interface via TypeScript declaration * merging to register their own type-aware options: * * @example * ```ts * // In your plugin's types file: * declare module '@spoosh/core' { * interface PluginResolvers { * // Access schema * mySchemaCallback: MyFn | undefined; * * // Access data/error * myDataTransform: (data: TContext['data']) => TContext['data']; * * // Access request input * myDebounce: (prev: { prevQuery: TContext['input']['query'] }) => number; * * // Access multiple contexts at once * myComplexOption: ComplexFn; * } * } * ``` */ interface PluginResolvers { } /** * Registry for plugin result type resolution based on options. * Extend via declaration merging to provide type inference for hook results. * * Unlike PluginResolvers which receives the full context, this receives * the OPTIONS type so plugins can infer result types from what the user passes. * * @example * ```ts * // In your plugin's types file: * declare module '@spoosh/core' { * interface PluginResultResolvers { * transformedData: TOptions extends { transform: { response: (...args: never[]) => infer R } } * ? Awaited | undefined * : never; * } * } * ``` */ interface PluginResultResolvers { } /** * Registry for plugin internal APIs. Extend via declaration merging for type-safe access. * * Plugins can expose functions and variables that other plugins can access * via `context.plugins.get("plugin-name")`. * * @example * ```ts * // In your plugin's types file: * declare module '@spoosh/core' { * interface PluginInternalRegistry { * "my-plugin": { myMethod: () => void } * } * } * ``` */ interface PluginInternalRegistry { } /** * Registry for public API type resolution. Extend via declaration merging. * * Plugins that expose schema-aware public APIs should extend this interface * to get proper type inference when the API is used. * * @example * ```ts * // In your plugin's types file: * declare module '@spoosh/core' { * interface ApiResolvers { * myFunction: MyFn; * } * } * ``` */ interface ApiResolvers { } /** * Accessor for plugin internal APIs with type-safe lookup. */ type PluginAccessor = { /** Get a plugin's internal API by name. Returns undefined if plugin not found. */ get(name: K): PluginInternalRegistry[K] | undefined; get(name: string): unknown; }; type RefetchEventReason = "focus" | "reconnect" | "polling" | "invalidate"; /** * Event emitted by plugins to request a refetch. * Hooks subscribe to this event and trigger controller.execute(). */ type RefetchEvent = { queryKey: string; reason: RefetchEventReason | Omit; }; /** * Minimal PluginExecutor interface for ApiContext. * Avoids circular dependency with executor.ts. */ type ApiPluginExecutor = { executeMiddleware: (operationType: OperationType, context: PluginContext, coreFetch: () => Promise>) => Promise>; createContext: (input: PluginContextInput) => PluginContext; getPlugins: () => readonly SpooshPlugin[]; /** * Register a function to enhance every PluginContext during creation. * Call this during plugin setup to inject properties into all request contexts. */ registerContextEnhancer: (enhancer: (context: PluginContext) => void) => void; }; /** * Context provided to plugin's api function. * Used for creating framework-agnostic APIs exposed on the adapter. */ type ApiContext = { spoosh: TApi; stateManager: StateManager; eventEmitter: EventEmitter; pluginExecutor: ApiPluginExecutor; /** * Creates an event tracer for standalone events. * Only available when devtools plugin is active. */ eventTracer?: (plugin: string) => EventTracer; }; /** * Context provided to plugin's setup function. * Used for one-time initialization when the Spoosh instance is created. */ type SetupContext = { stateManager: StateManager; eventEmitter: EventEmitter; pluginExecutor: ApiPluginExecutor; /** * Creates an event tracer for standalone events. * Only available when devtools plugin is active. */ eventTracer?: (plugin: string) => EventTracer; }; type PluginExecutor = { /** Execute lifecycle hooks for onMount or onUnmount */ executeLifecycle: (phase: "onMount" | "onUnmount", operationType: OperationType, context: PluginContext) => Promise; /** Execute onUpdate lifecycle with previous context */ executeUpdateLifecycle: (operationType: OperationType, context: PluginContext, previousContext: PluginContext) => Promise; executeMiddleware: (operationType: OperationType, context: PluginContext, coreFetch: () => Promise>) => Promise>; getPlugins: () => readonly SpooshPlugin[]; /** Get plugins that support a specific operation type */ getPluginsForOperation: (operationType: OperationType) => readonly SpooshPlugin[]; /** Creates a full PluginContext with plugins accessor */ createContext: (input: PluginContextInput) => PluginContext; /** * Register a function to enhance every PluginContext during creation. * Call this during plugin setup to inject properties into all request contexts. */ registerContextEnhancer: (enhancer: (context: PluginContext) => void) => void; }; declare function createPluginExecutor(initialPlugins?: SpooshPlugin[]): PluginExecutor; /** * Resolves plugin option types based on the full context. * * This is the single entry point for all type resolution. It receives * the full ResolverContext containing schema, data, error, and input types, * and resolves each option key accordingly. * * Plugins extend PluginResolvers via declaration merging to add their own * resolved option types. * * @example * ```ts * // In your plugin's types.ts: * declare module "@spoosh/core" { * interface PluginResolvers { * myOption: MyResolvedType | undefined; * } * } * * // Type resolution: * type ResolvedOptions = ResolveTypes< * MergePluginOptions["read"], * { * schema: ApiSchema; * data: Post[]; * error: Error; * input: { query: { page: number }; body: never; params: never; formData: never }; * } * >; * ``` */ type ResolveTypes = { [K in keyof TOptions]: K extends keyof PluginResolvers ? PluginResolvers[K] : TOptions[K] extends DataAwareCallback | undefined ? DataAwareCallback | undefined : TOptions[K] extends DataAwareTransform | undefined ? DataAwareTransform | undefined : TOptions[K]; }; /** * Resolves schema-aware types in plugin options. * This is a simplified resolver for write operations that only need schema context. */ type ResolveSchemaTypes = ResolveTypes>; /** * Resolves plugin result types based on the options passed to the hook. * * This allows plugins to infer result types from the options. For example, * the transform plugin can infer `transformedData` type from the response * transformer's return type. * * @example * ```ts * // Usage in hooks: * type ResolvedResults = ResolveResultTypes; * // If TReadOpts has { transform: { response: (d) => { count: number } } } * // Then transformedData will be { count: number } | undefined * ``` */ type ResolveResultTypes = TResults & PluginResultResolvers; /** * Resolves public API types with schema awareness. * Maps each key in TApi to its resolved type from resolvers. * * Plugins extend ApiResolvers via declaration merging to add their own * resolved API types. * * @example * ```ts * // In your plugin's types.ts: * declare module "@spoosh/core" { * interface ApiResolvers { * prefetch: PrefetchFn; * } * } * ``` */ type ResolveApi = { [K in keyof TApi]: K extends keyof ApiResolvers ? ApiResolvers[K] : TApi[K]; }; type ExtractReadOptions = T extends SpooshPlugin ? Types extends { readOptions: infer R; } ? R : object : object; type ExtractWriteOptions = T extends SpooshPlugin ? Types extends { writeOptions: infer W; } ? W : object : object; type ExtractPagesOptions = T extends SpooshPlugin ? Types extends { pagesOptions: infer I; } ? I : object : object; type ExtractWriteTriggerOptions = T extends SpooshPlugin ? Types extends { writeTriggerOptions: infer W; } ? W : object : object; type ExtractQueueOptions = T extends SpooshPlugin ? Types extends { queueOptions: infer Q; } ? Q : object : object; type ExtractQueueTriggerOptions = T extends SpooshPlugin ? Types extends { queueTriggerOptions: infer Q; } ? Q : object : object; type ExtractQueueResult = T extends SpooshPlugin ? Types extends { queueResult: infer Q; } ? Q : object : object; type ExtractSubscribeResult = T extends SpooshPlugin ? Types extends { subscribeResult: infer S; } ? S : object : object; type ExtractReadResult = T extends SpooshPlugin ? Types extends { readResult: infer R; } ? R : object : object; type ExtractWriteResult = T extends SpooshPlugin ? Types extends { writeResult: infer W; } ? W : object : object; type ExtractApi = T extends SpooshPlugin ? Types extends { api: infer A; } ? A : object : object; type UnionToIntersection$1 = (U extends unknown ? (x: U) => void : never) extends (x: infer I) => void ? I : never; type MergePluginOptions[]> = { read: UnionToIntersection$1>; write: UnionToIntersection$1>; pages: UnionToIntersection$1>; writeTrigger: UnionToIntersection$1>; queue: UnionToIntersection$1>; queueTrigger: UnionToIntersection$1>; }; type MergePluginResults[]> = { read: UnionToIntersection$1>; write: UnionToIntersection$1>; queue: UnionToIntersection$1>; subscribe: UnionToIntersection$1>; }; type MergePluginApi[], TSchema = unknown> = ResolveApi>, TSchema, MergePluginOptions["read"]>; type PluginRegistry[]> = { plugins: TPlugins; _options: MergePluginOptions; }; declare function createPluginRegistry[]>(plugins: [...TPlugins]): PluginRegistry; /** * Registry for subscription methods. Transports extend via module augmentation. */ interface SpooshSubscriptionMethodRegistry { } type SubscriptionMethod = keyof SpooshSubscriptionMethodRegistry; type AnyMethod = HttpMethod | SubscriptionMethod; /** * An API schema where routes are defined as string keys with path patterns. * Define data, body, query, and error directly on each method. * * For subscription endpoints (SSE, WebSocket), define the `events` field instead of `data`. * The transport to use is determined by the hook (useSSE, useWebSocket), not the path. * * @example * ```ts * type ApiSchema = { * "posts": { * GET: { data: Post[] }; * POST: { data: Post; body: CreatePostBody }; * }; * "posts/:id": { * GET: { data: Post }; * PUT: { data: Post; body: UpdatePostBody }; * DELETE: { data: void }; * }; * "notifications": { * GET: { * events: { * alert: { data: Alert }; * message: { data: Message }; * }; * query?: { userId: string }; * }; * }; * }; * ``` */ /** * HTTP endpoint method config. */ type HttpMethodConfig = { data?: unknown; body?: unknown; query?: unknown; error?: unknown; }; /** * Subscription endpoint method config (SSE, WebSocket, etc.). * Has events field instead of data. */ type SubscriptionMethodConfig = { events?: Record; body?: unknown; query?: unknown; error?: unknown; }; /** * Endpoint config - supports both HTTP and subscription methods. * Subscription endpoints are those with an `events` field, HTTP endpoints have `data`. */ type EndpointConfig = { [M in HttpMethod | SubscriptionMethod]?: HttpMethodConfig | SubscriptionMethodConfig; }; /** * Base API schema type. */ type ApiSchema = { [path: string]: { [method: string]: unknown; }; }; /** * Helper type for defining API schemas with proper autocomplete. * Use `events` field for subscription endpoints, `data` field for HTTP endpoints. * * @example * ```ts * type MyApi = SpooshSchema<{ * "posts": { GET: { data: Post[] } }; * "chat": { GET: { events: { message: { data: string } } } }; * }>; * ``` */ type SpooshSchema = T; /** * Extract data type from an endpoint. */ type ExtractData = T extends { data: infer D; } ? D : void; /** * Extract body type from an endpoint. */ type ExtractBody$1 = T extends { body: infer B; } ? B : never; /** * Extract query type from an endpoint. */ type ExtractQuery$1 = T extends { query: infer Q; } ? Q : never; /** * Extract error type from an endpoint. */ type ExtractError = T extends { error: infer E; } ? E : TDefault; /** * Convert a route pattern like "posts/:id" to a path matcher pattern like `posts/${string}`. * This enables TypeScript to match actual paths like "posts/123" to their schema definitions. * * @example * ```ts * type A = RouteToPath<"posts/:id">; // "posts/${string}" * type B = RouteToPath<"posts/:id/comments/:cid">; // "posts/${string}/comments/${string}" * type C = RouteToPath<"posts">; // "posts" * ``` */ type RouteToPath = T extends `/${infer Rest}` ? `/${RouteToPath}` : T extends `${infer Segment}/${infer Rest}` ? Segment extends `:${string}` ? `${string}/${RouteToPath}` : `${Segment}/${RouteToPath}` : T extends `:${string}` ? string : T; /** * Find which schema key matches a given path. * First checks for exact match, then checks pattern matches. * * @example * ```ts * type Schema = { "posts": {...}; "posts/:id": {...} }; * type A = FindMatchingKey; // "posts" (exact match) * type B = FindMatchingKey; // "posts/:id" (pattern match) * ``` */ type FindMatchingKey = TPath extends keyof TSchema ? TPath : { [K in keyof TSchema]: TPath extends RouteToPath ? K : never; }[keyof TSchema]; /** * Extract parameter names from a route pattern. * * @example * ```ts * type A = ExtractParamNames<"posts/:id">; // "id" * type B = ExtractParamNames<"posts/:id/comments/:cid">; // "id" | "cid" * type C = ExtractParamNames<"posts">; // never * ``` */ type ExtractParamNames = T extends `${string}:${infer Param}/${infer Rest}` ? Param | ExtractParamNames : T extends `${string}:${infer Param}` ? Param : never; /** * Check if a route pattern has any parameters. * * @example * ```ts * type A = HasParams<"posts/:id">; // true * type B = HasParams<"posts">; // false * ``` */ type HasParams = T extends `${string}:${string}` ? true : false; /** * Extract paths that have GET methods. */ type ReadPaths = { [K in keyof TSchema & string]: "GET" extends keyof TSchema[K] ? K : never; }[keyof TSchema & string]; /** * Extract paths that have write methods (POST, PUT, PATCH, DELETE). */ type WritePaths = { [K in keyof TSchema & string]: Extract extends never ? never : K; }[keyof TSchema & string]; /** * Check if a schema path has a GET method. */ type HasReadMethod = FindMatchingKey extends infer TKey ? TKey extends keyof TSchema ? "GET" extends keyof TSchema[TKey] ? true : false : false : false; /** * Check if a schema path has any write methods. */ type HasWriteMethod = FindMatchingKey extends infer TKey ? TKey extends keyof TSchema ? WriteMethod extends never ? false : Extract extends never ? false : true : false : false; /** * Extract paths that have methods with events (subscription endpoints). */ type SubscriptionPaths = { [K in keyof TSchema & string]: { [M in keyof TSchema[K]]: TSchema[K][M] extends { events: unknown; } ? K : never; }[keyof TSchema[K]] extends never ? never : K; }[keyof TSchema & string]; /** * Check if a schema path has any method with events field. */ type HasSubscriptionMethod = FindMatchingKey extends infer TKey ? TKey extends keyof TSchema ? { [M in keyof TSchema[TKey]]: TSchema[TKey][M] extends { events: unknown; } ? true : never; }[keyof TSchema[TKey]] extends never ? false : true : false : false; type NormalizePrefix = T extends `/${infer Rest}` ? NormalizePrefix : T extends `${infer Rest}/` ? NormalizePrefix : T; type StripPrefixFromPath = TPath extends TPrefix ? "" : TPath extends `${TPrefix}/${infer Rest}` ? Rest : TPath; /** * Strips a prefix from all path keys in a schema. * Works with any schema (Elysia, Hono, or manual). * * @example * ```ts * type FullSchema = { * "api": { GET: { data: string } }; * "api/users": { GET: { data: User[] } }; * "api/posts/:id": { GET: { data: Post } }; * "api/health": { GET: { data: { status: string } } }; * }; * * type ApiSchema = StripPrefix; * // { * // "/": { GET: { data: string } }; * // "users": { GET: { data: User[] } }; * // "posts/:id": { GET: { data: Post } }; * // "health": { GET: { data: { status: string } } }; * // } * ``` */ type StripPrefix = TPrefix extends "" ? TSchema : { [K in keyof TSchema as K extends string ? StripPrefixFromPath> : K]: TSchema[K]; }; type IsNever = [T] extends [never] ? true : false; type EndpointRequestOptions = (IsNever> extends true ? object : { body: ExtractBody$1; }) & (IsNever> extends true ? object : { query: ExtractQuery$1; }) & (HasParams extends true ? { params: Record, string | number>; } : object); type EndpointMethodFn = (options?: Simplify>) => Promise, unknown, EndpointRequestOptions>>; type ReadPathMethods$1 = FindMatchingKey extends infer TKey ? TKey extends keyof TSchema ? "GET" extends keyof TSchema[TKey] ? Simplify<{ GET: EndpointMethodFn; }> : never : never : never; /** * Schema navigation helper for plugins that need type-safe API schema access. * * This type transforms the API schema into a callable function where: * - Path strings are used to select endpoints * - Only GET methods are exposed (for query operations) * * Use this in plugin option types that need to reference API endpoints: * * @example * ```ts * // Define your plugin's callback type * type MyCallbackFn = ( * api: ReadSchemaHelper * ) => unknown; * * // Usage in plugin options * interface MyPluginWriteOptions { * myCallback?: MyCallbackFn; * } * * // Register for schema resolution * declare module '@spoosh/core' { * interface SchemaResolvers { * myCallback: MyCallbackFn | undefined; * } * } * ``` * * @example * ```ts * // User's code - paths are type-checked! * trigger({ * myCallback: (api) => [ * api("posts").GET, // ✓ Valid * api("posts/:id").GET, // ✓ Dynamic segment * api("nonexistent").GET, // ✗ Type error * ], * }); * ``` */ type ReadSchemaHelper = | (string & {})>(path: TPath) => HasReadMethod extends true ? ReadPathMethods$1 : never; type WritePathMethods$1 = FindMatchingKey extends infer TKey ? TKey extends keyof TSchema ? Simplify<{ [M in WriteMethod as M extends keyof TSchema[TKey] ? M : never]: M extends keyof TSchema[TKey] ? EndpointMethodFn : never; }> : never : never; /** * Schema navigation helper for plugins that need type-safe API schema access for mutations. * * Similar to ReadSchemaHelper but exposes write methods (POST, PUT, PATCH, DELETE). */ type WriteSchemaHelper = | (string & {})>(path: TPath) => HasWriteMethod extends true ? WritePathMethods$1 : never; /** * Check for exact type equality using function type assignability. * Two types are equal if functions returning them are mutually assignable. */ type Equals = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? true : false; /** * Convert bare `object` type to `never` to exclude it from the union. * Preserves all other types including interfaces and types with actual properties. */ type FilterObjectType = Equals extends true ? never : T; /** * Convert a union type to an intersection type. * This merges all properties from all types in the union. */ type UnionToIntersection = (U extends unknown ? (arg: U) => void : never) extends (arg: infer I) => void ? I : never; /** * Extract all option types from plugin configuration and create a union, then intersect. * This allows middleware to access all properties from all option types. * object types are converted to unknown which doesn't affect the intersection. */ type ExtractPluginOptionsUnion = UnionToIntersection | FilterObjectType | FilterObjectType | FilterObjectType | FilterObjectType | FilterObjectType>; /** * Plugin context with typed pluginOptions based on plugin configuration. */ type TypedPluginContext = Omit & { pluginOptions?: ExtractPluginOptionsUnion; }; /** * Plugin definition with typed context for middleware and handlers. */ type TypedPluginDefinition = Omit, "middleware" | "afterResponse" | "lifecycle"> & { middleware?: (context: TypedPluginContext, next: () => Promise) => Promise; afterResponse?: (context: TypedPluginContext, response: any) => any; lifecycle?: { onMount?: (context: TypedPluginContext) => void | Promise; onUpdate?: (context: TypedPluginContext, previousContext: TypedPluginContext) => void | Promise; onUnmount?: (context: TypedPluginContext) => void | Promise; }; }; /** * Helper to create a Spoosh plugin with automatic type inference for plugin options. * * This eliminates the need for manual type assertions in middleware by automatically * intersecting all option types, making all properties accessible: * * ```ts * // Before: * const pluginOptions = context.pluginOptions as CacheReadOptions | undefined; * const staleTime = pluginOptions?.staleTime ?? defaultStaleTime; * * // After (with createSpooshPlugin): * const staleTime = context.pluginOptions?.staleTime ?? defaultStaleTime; * ``` * * @typeParam T - Plugin type configuration (readOptions, writeOptions, etc.) * @param definition - Plugin definition with typed context * @returns Typed Spoosh plugin * * @example * ```ts * export const cachePlugin = (config: CachePluginConfig = {}) => * createSpooshPlugin<{ * readOptions: CacheReadOptions; * writeOptions: CacheWriteOptions; * pagesOptions: CachePagesOptions; * }>({ * name: "spoosh:cache", * operations: ["read", "write", "pages"], * middleware: async (context, next) => { * // context.pluginOptions is automatically typed as an intersection: * // CacheReadOptions & CachePagesOptions (CacheWriteOptions filtered as it's just 'object') * // All properties from all option types are accessible: * const staleTime = context.pluginOptions?.staleTime ?? defaultStaleTime; * return next(); * }, * }); * ``` */ declare function createSpooshPlugin(definition: TypedPluginDefinition): SpooshPlugin; /** * Base request options available on all methods. */ type BaseRequestOptions = { headers?: HeadersInitOrGetter; signal?: AbortSignal; }; /** * Extract body type from method config. */ type ExtractBody = T extends { body?: infer B; } ? B : never; /** * Extract query type from method config. */ type ExtractQuery = T extends { query?: infer Q; } ? Q : never; /** * Check if body is required (not optional). */ type IsBodyRequired = T extends { body: infer B; } ? undefined extends B ? false : true : false; /** * Check if query is required (not optional). */ type IsQueryRequired = T extends { query: infer Q; } ? undefined extends Q ? false : true : false; /** * Build the options type for a method. */ type BodyOption = [ExtractBody] extends [never] ? {} : IsBodyRequired extends true ? { body: ExtractBody | SpooshBody>; } : { body?: ExtractBody | SpooshBody>; }; type QueryOption = [ExtractQuery] extends [never] ? {} : IsQueryRequired extends true ? { query: ExtractQuery; } : { query?: ExtractQuery; }; type ParamsOption = HasParams extends true ? { params: Record, string | number>; } : {}; type RequestOptions = Simplify & QueryOption & ParamsOption>; /** * Check if options argument is required. */ type IsOptionsRequired = IsBodyRequired extends true ? true : IsQueryRequired extends true ? true : HasParams extends true ? true : false; /** * Build response type for a method call. */ type MethodResponse = SpooshResponse, ExtractError, RequestOptions, ExtractQuery, ExtractBody, ExtractParamNames>; /** * Create a method function type. * Direct lookup: Schema[Path][Method] → method config → build function type */ type MethodFn = IsOptionsRequired extends true ? (options: RequestOptions) => Promise> : (options?: RequestOptions) => Promise>; /** * HTTP methods available on a path. * Direct lookup: Schema[Path] → route config → map methods */ type HttpMethods = { [M in HttpMethod as M extends keyof TRoute ? M : never]: M extends keyof TRoute ? MethodFn : never; }; /** * Build the return type when calling a client with a path. * Uses FindMatchingKey for pattern matching (e.g., "posts/123" → "posts/:id") */ type PathMethods = FindMatchingKey extends infer TKey ? TKey extends keyof TSchema ? Simplify> : never : never; /** * Extract all valid paths from a schema (for autocomplete). */ type SchemaPaths = keyof TSchema & string; /** * An API interface that uses path strings instead of chained property access. * Methods use HTTP names directly: GET, POST, PUT, PATCH, DELETE. * * @example * ```ts * type ApiSchema = { * "posts": { GET: { data: Post[] } }; * "posts/:id": { GET: { data: Post } }; * }; * * const api = createClient({ baseUrl: "/api" }); * * // Call with exact path (autocomplete available) * await api("posts").GET(); * * // Call with embedded params (matches "posts/:id") * await api("posts/123").GET(); * * // Call with explicit params * await api("posts/:id").GET({ params: { id: 123 } }); * ``` */ type SpooshClient = | (string & {})>(path: TPath) => PathMethods; /** * Read-only client type that only exposes GET methods. * Used by useRead/injectRead hooks to ensure only query operations are selected. */ type ReadPathMethods = FindMatchingKey extends infer TKey ? TKey extends keyof TSchema ? "GET" extends keyof TSchema[TKey] ? Simplify<{ GET: MethodFn; }> : never : never : never; /** * A read-only API interface that only exposes GET methods. * Used by useRead and injectRead hooks. */ type ReadClient = | (string & {})>(path: TPath) => HasReadMethod extends true ? ReadPathMethods : never; /** * Write-only client type that only exposes mutation methods (POST, PUT, PATCH, DELETE). * Used by useWrite/injectWrite hooks to ensure only mutation operations are selected. */ type WritePathMethods = FindMatchingKey extends infer TKey ? TKey extends keyof TSchema ? Simplify<{ [M in WriteMethod as M extends keyof TSchema[TKey] ? M : never]: M extends keyof TSchema[TKey] ? MethodFn : never; }> : never : never; /** * A write-only API interface that only exposes mutation methods (POST, PUT, PATCH, DELETE). * Used by useWrite and injectWrite hooks. */ type WriteClient = | (string & {})>(path: TPath) => HasWriteMethod extends true ? WritePathMethods : never; /** * Method function type for write selectors - accepts no arguments. * All input (body, query, params) is passed to trigger() instead. */ type WriteSelectorMethodFn = () => Promise>; /** * Write selector path methods - methods accept no arguments. */ type WriteSelectorPathMethods = FindMatchingKey extends infer TKey ? TKey extends keyof TSchema ? Simplify<{ [M in WriteMethod as M extends keyof TSchema[TKey] ? M : never]: M extends keyof TSchema[TKey] ? WriteSelectorMethodFn : never; }> : never : never; /** * Write selector client - methods accept no arguments. * Used by useWrite for selecting endpoints. All input goes to trigger(). */ type WriteSelectorClient = | (string & {})>(path: TPath) => HasWriteMethod extends true ? WriteSelectorPathMethods : never; /** * Method function type for queue selectors - accepts no arguments. * All input (body, query, params) is passed to trigger() instead. */ type QueueSelectorMethodFn = () => Promise>; /** * Queue selector path methods - all HTTP methods, accepting no arguments. */ type QueueSelectorPathMethods = FindMatchingKey extends infer TKey ? TKey extends keyof TSchema ? Simplify<{ [M in HttpMethod as M extends keyof TSchema[TKey] ? M : never]: M extends keyof TSchema[TKey] ? QueueSelectorMethodFn : never; }> : never : never; /** * Queue selector client - all HTTP methods, accepting no arguments. * Used by useQueue for selecting endpoints. All input goes to trigger(). */ type QueueSelectorClient = | (string & {})>(path: TPath) => QueueSelectorPathMethods; /** * Extract events type from a method config. */ type ExtractEvents = T extends { events: infer E; } ? E : never; /** * Transport-specific options for subscriptions. */ type SubscriptionTransportOptions = { /** Keep connection alive when browser tab is hidden. Defaults to true. */ openWhenHidden?: boolean; }; /** * Strict subscription request options (for GET method). * Body/query are required if schema requires them. * Note: Transport options (maxRetries, retryDelay, events, parse, accumulate) * are now configured at the hook level (useSSE). */ type StrictSubscriptionRequestOptions)[] = readonly (keyof ExtractEvents)[]> = Simplify & BodyOption & ParamsOption & SubscriptionTransportOptions>; /** * Loose subscription request options (for POST/PUT/etc methods). * Body/query are always optional since they're provided via trigger(). * Note: Transport options (maxRetries, retryDelay, events, parse, accumulate) * are now configured at the hook level (useSSE). */ type LooseSubscriptionRequestOptions)[] = readonly (keyof ExtractEvents)[]> = Simplify; } & { body?: ExtractBody | SpooshBody>; } & ParamsOption & SubscriptionTransportOptions>; /** * Check if strict subscription options are required (for GET method). */ type IsStrictSubscriptionOptionsRequired = IsBodyRequired extends true ? true : IsQueryRequired extends true ? true : HasParams extends true ? true : false; /** * Subscription response type that carries event types. */ type SubscriptionResponse)[] = readonly (keyof ExtractEvents)[]> = { _subscription: true; events: ExtractEvents; requestedEvents: TRequestedEvents; query: ExtractQuery; body: ExtractBody; params: HasParams extends true ? Record, string | number> : never; error: ExtractError; }; type BaseSubscriptionResponse = Record, TError = unknown> = { _subscription: true; events: TEvents; requestedEvents: readonly string[]; query: unknown; body: unknown; params: unknown; error: TError; }; /** * Subscription method function type - methods with events field. * GET: Strict typing (body/query required if schema requires) * POST/PUT/etc: Loose typing (body/query always optional, passed to trigger) */ type SubscriptionMethodFn = TMethod extends "GET" ? IsStrictSubscriptionOptionsRequired extends true ? )[]>(options: StrictSubscriptionRequestOptions) => SubscriptionResponse : )[]>(options?: StrictSubscriptionRequestOptions) => SubscriptionResponse : )[]>(options?: LooseSubscriptionRequestOptions) => SubscriptionResponse; /** * Subscription path methods - only methods with events field. */ type SubscriptionPathMethods = FindMatchingKey extends infer TKey ? TKey extends keyof TSchema ? Simplify<{ [M in HttpMethod as M extends keyof TSchema[TKey] ? TSchema[TKey][M] extends { events: unknown; } ? M : never : never]: M extends keyof TSchema[TKey] ? SubscriptionMethodFn : never; }> : never : never; /** * A subscription API interface that only exposes methods with events field. * Used by useSubscription hook for real-time data streams. */ type SubscriptionClient = | (string & {})>(path: TPath) => HasSubscriptionMethod extends true ? SubscriptionPathMethods : never; type PluginArray = readonly SpooshPlugin[]; interface SpooshConfig { baseUrl: string; defaultOptions?: SpooshOptions; plugins?: TPlugins; } type SpooshInstance = { api: SpooshClient; stateManager: StateManager; eventEmitter: EventEmitter; pluginExecutor: PluginExecutor; transports: Map; config: { baseUrl: string; defaultOptions: SpooshOptions; }; _types: { schema: TSchema; defaultError: TDefaultError; plugins: TPlugins; transports: TTransports; }; }; type ExtractTriggerQuery = I extends { query: infer Q; } ? { query?: Q; } : unknown; type ExtractTriggerBody = I extends { body: infer B; } ? { body?: B; } : unknown; type ExtractTriggerParams = I extends { params: infer P; } ? { params?: P; } : unknown; /** * Class-based builder for creating Spoosh instances with type-safe plugin inference. * * @template TSchema - The API schema type defining endpoints and their types * @template TError - Default error type for all requests (defaults to unknown) * @template TPlugins - A const tuple of plugin instances for type inference (defaults to empty array) * * @example Basic usage * ```ts * const spoosh = new Spoosh('/api') * .use([cachePlugin(), devtool()]); * * const { api } = client; * const response = await api("posts").GET(); * ``` * * @example With default options * ```ts * const spoosh = new Spoosh('/api', { * headers: { 'Authorization': 'Bearer token' } * }).use([cachePlugin()]); * ``` * * @example With React hooks * ```ts * import { create } from '@spoosh/react'; * * const spoosh = new Spoosh('/api') * .use([cachePlugin(), prefetchPlugin()]); * * const { useRead, useWrite } = create(client); * * // In component * const { data } = useRead((api) => api("posts").GET()); * const { trigger } = useWrite((api) => api("posts").POST()); * ``` * * @since 0.1.0 */ declare class Spoosh { private baseUrl; private defaultOptions; private _plugins; private _transports; /** * Creates a new Spoosh instance. * * @param baseUrl - The base URL for all API requests (e.g., '/api' or 'https://api.example.com') * @param defaultOptions - Optional default options applied to all requests (headers, credentials, transport, etc.) * @param plugins - Internal parameter used by the `.use()` method. Do not pass directly. * * @example * ```ts * // Simple usage * const spoosh = new Spoosh('/api'); * * // With default headers * const spoosh = new Spoosh('/api', { * headers: { 'X-API-Key': 'secret' } * }); * * // With XHR transport (narrows available options to XHR-compatible fields) * const spoosh = new Spoosh('/api', { * transport: 'xhr', * credentials: 'include', * }); * ``` */ constructor(baseUrl: string, defaultOptions?: SpooshOptionsInput, plugins?: TPlugins, transports?: Map); /** * Adds plugins to the Spoosh instance. * * Returns a configured Spoosh instance with the specified plugins. * Can only be called once - the returned instance does not have `.use()`. * * @template TNewPlugins - The const tuple type of the new plugins array * @param plugins - Array of plugin instances to use * @returns A configured Spoosh instance (without `.use()` method) * * ```ts * const spoosh = new Spoosh('/api').use([ * cachePlugin({ staleTime: 5000 }), * invalidationPlugin(), * prefetchPlugin(), * ]); * ``` */ use(plugins: TNewPlugins): Omit, "use">; /** * Registers transport implementations for real-time operations. * * @param transports - Array of transport instances to register * @returns This Spoosh instance for method chaining * * @example * ```ts * import { sse } from '@spoosh/transport-sse'; * * const spoosh = new Spoosh('/api') * .withTransports([sse()]) * .use([...]); * ``` */ withTransports(transports: T): Spoosh; /** * Cached instance of the underlying SpooshInstance. * Created lazily on first property access. * @private */ private _instance?; /** * Gets or creates the underlying SpooshInstance. * Uses lazy initialization for optimal performance. * @private */ private getInstance; /** * The type-safe API interface for making requests. * * Provides a proxy-based interface for accessing endpoints defined in your schema. * * @example * ```ts * const spoosh = new Spoosh('/api').use([...]); * const { api } = client; * * // GET request * const { data } = await api("posts").GET(); * * // POST request with body * const { data } = await api("posts").POST({ body: { title: 'Hello' } }); * * // Dynamic path parameters * const { data } = await api("posts/:id").GET({ params: { id: postId } }); * ``` */ get api(): SpooshClient; /** * State manager for cache and state operations. * * Provides methods for managing cached data, invalidating entries, and retrieving state. * * @example * ```ts * const { stateManager } = client; * * // Get cached data * const cache = stateManager.getCache('posts.GET'); * * // Invalidate cache by tag * stateManager.invalidate(['posts']); * * // Clear all cache * stateManager.clearCache(); * ``` */ get stateManager(): StateManager; /** * Event emitter for subscribing to refetch and invalidation events. * * Used internally by plugins and hooks to trigger re-fetches. * * @example * ```ts * const { eventEmitter } = client; * * // Subscribe to refetch events * eventEmitter.on('refetch', ({ queryKey, reason }) => { * console.log(`Refetching ${queryKey} due to ${reason}`); * }); * ``` */ get eventEmitter(): EventEmitter; /** * Plugin executor that manages plugin lifecycle and middleware. * * Provides access to registered plugins and their execution context. * * @example * ```ts * const { pluginExecutor } = client; * * // Get all registered plugins * const plugins = pluginExecutor.getPlugins(); * * // Check if a plugin is registered * const hasCache = plugins.some(p => p.name === 'cache'); * ``` */ get pluginExecutor(): PluginExecutor; /** * Configuration object containing baseUrl and defaultOptions. * * @example * ```ts * const { config } = client; * console.log(config.baseUrl); // '/api' * console.log(config.defaultOptions); // { headers: {...} } * ``` */ get config(): { baseUrl: string; defaultOptions: SpooshOptions; }; /** * Type information carrier for generic type inference. * Used internally by TypeScript for type resolution. * * @internal */ get _types(): { schema: TSchema; defaultError: TError; plugins: TPlugins; transports: TTransports; }; /** * Map of registered transport implementations. * * @example * ```ts * const { transports } = client; * const sseTransport = transports.get('sse'); * ``` */ get transports(): Map>; } /** * Creates a lightweight type-safe API instance for vanilla JavaScript/TypeScript usage. * * This is a simpler alternative to `Spoosh` for users who don't need * the full plugin system, state management, or React integration. * * @param baseUrl - Base URL for all API requests * @param defaultOptions - Default fetch options (headers, credentials, etc.) * @returns Type-safe API instance * * @example * ```ts * type ApiSchema = SpooshSchema<{ * "posts": { * GET: { data: Post[] }; * POST: { data: Post; body: CreatePostBody }; * }; * "posts/:id": { * GET: { data: Post }; * PUT: { data: Post; body: UpdatePostBody }; * DELETE: { data: void }; * }; * }>; * * type ApiError = { * message: string; * } * * const api = createClient("/api"); * * // Type-safe API calls with path strings * const { data } = await api("posts").GET(); * const { data: post } = await api("posts/123").GET(); * await api("posts/:id").GET({ params: { id: 123 } }); * * // With custom options * const api = createClient("/api", { * headers: { Authorization: "Bearer token" }, * transport: "xhr", * }); * ``` */ declare function createClient(baseUrl: string, defaultOptions?: SpooshOptionsInput): SpooshClient; declare function buildUrl(baseUrl: string, path: string[], query?: Record): string; declare function __DEV__(): boolean; /** * Generate cache tags from URL path segments. * e.g., ['posts', '1'] → ['posts', 'posts/1'] */ declare function generateTags(path: string[]): string[]; declare function containsFile(value: unknown): boolean; declare function isJsonBody(body: unknown): body is Record | unknown[]; /** * Resolves HeadersInitOrGetter to a plain Record. * Handles functions, Headers objects, arrays, and plain objects. */ declare function resolveHeadersToRecord(headers?: HeadersInitOrGetter): Promise>; declare function mergeHeaders(defaultHeaders?: HeadersInitOrGetter, requestHeaders?: HeadersInitOrGetter): Promise; declare function setHeaders(requestOptions: { headers?: HeadersInitOrGetter; }, newHeaders: Record): void; /** * Extracts the Content-Type header value from HeadersInit. * Returns undefined if no Content-Type is set. */ declare function getContentType(headers?: HeadersInit): string | undefined; declare function removeHeaderKeys(headers: HeadersInit | undefined, keysToRemove: string[]): HeadersInit | undefined; declare function objectToFormData(obj: Record): FormData; declare function objectToUrlEncoded(obj: Record): string; declare function sortObjectKeys(obj: unknown, seen?: WeakSet): unknown; /** * Common tag options used across plugins and operations. */ type TagOptions = { /** * Custom tags to use instead of auto-generated tag. * Can be a single tag string or an array of tags. */ tags?: string | string[]; }; /** * Resolves tags for a cache entry. * Returns a single tag by default (the joined path), or custom tags if provided. * All tags are normalized (leading "/" removed) for consistency. * * @param options - Tag options containing optional custom tags * @param resolvedPath - The resolved path segments * @returns Array of normalized tags */ declare function resolveTags(options: TagOptions | undefined, resolvedPath: string[]): string[]; declare function resolvePath(path: string[], params: Record | undefined): string[]; /** * Resolves dynamic path parameters in a string path. * Unlike `resolvePath`, this works with string paths and doesn't throw on missing params. * * @param path - The path string with dynamic segments (e.g., "products/:id/comments") * @param params - The params object containing values to substitute * @returns The resolved path string (e.g., "products/1/comments") * * @example * ```ts * resolvePathString("products/:id/comments", { id: 1 }) * // => "products/1/comments" * ``` */ declare function resolvePathString(path: string, params: Record | undefined): string; /** * Normalizes a tag or pattern by removing leading slashes. * Ensures consistency between "/hello" and "hello". */ declare function normalizeTag(tag: string): string; /** * Checks if an entry's tag matches a given invalidation pattern. * Both tag and pattern are normalized (leading "/" removed) before comparison. * * @param entryTag - The tag from a cache entry (e.g., "posts", "posts/1", "posts/1/comments") * @param pattern - The pattern to match against: * - "posts" - Exact match only * - "posts/*" - Children only (matches posts/1, posts/1/comments, but NOT posts) * @returns true if the entry tag matches the pattern */ declare function matchTag(entryTag: string, pattern: string): boolean; /** * Checks if an entry's tag matches any of the given invalidation patterns. * * @param entryTag - The tag from a cache entry * @param patterns - Array of patterns to match against * @returns true if the entry tag matches any of the patterns */ declare function matchTags(entryTag: string, patterns: string[]): boolean; declare const isNetworkError: (err: unknown) => boolean; declare const isAbortError: (err: unknown) => boolean; declare function clone(value: T, seen?: WeakMap): T; /** * Creates a request-bound tracer for a plugin. * Use for middleware, afterResponse, and lifecycle hooks. * * @example * ```ts * const t = createTracer("spoosh:cache", context.trace); * t.return("Cache hit", { color: "success" }); * t.log("Cached response", { color: "info", diff: { before, after } }); * t.skip("No query params", { color: "muted" }); * ``` */ declare function createTracer(plugin: string, trace: Trace | undefined): RequestTracer; /** * Generates a UUID v4 string. * Uses crypto.randomUUID() when available (secure contexts), * falls back to Math.random() for non-secure contexts (HTTP). */ declare function generateUUID(): string; type ProxyHandlerConfig = { baseUrl: string; defaultOptions: TOptions; fetchExecutor?: FetchExecutor; nextTags?: boolean; }; /** * Creates an API proxy that uses path strings instead of chained property access. * Methods use HTTP names directly: GET, POST, PUT, PATCH, DELETE. * * @param config - Proxy handler configuration * @returns A function that takes a path and returns an object with HTTP method functions * * @example * ```ts * const api = createProxyHandler({ baseUrl: '/api', defaultOptions: {} }); * * // Call with path string * await api("posts").GET(); * await api("posts/123").GET(); * await api("posts/:id").GET({ params: { id: 123 } }); * ``` */ declare function createProxyHandler(config: ProxyHandlerConfig): SpooshClient; /** All supported HTTP method keys used in the API toolkit */ declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "PATCH", "DELETE"]; /** Union type of all HTTP method keys */ type HttpMethodKey = (typeof HTTP_METHODS)[number]; /** * A function returned by `createSelectorProxy` that stores the selected path and method. * * Does not execute any request - only captures the API endpoint selection. */ type SelectorFunction = (() => Promise<{ data: undefined; }>) & { /** The path string for this endpoint (e.g., 'posts', 'posts/:id') */ __selectorPath?: string; /** The HTTP method selected (e.g., 'GET', 'POST') */ __selectorMethod?: string; }; /** * Represents a fully captured API call with path, method, and options. * * Captured when an HTTP method is invoked with options. */ type CapturedCall = { /** Path string to the endpoint (e.g., 'posts/:id') */ path: string; /** HTTP method called (e.g., 'GET', 'POST') */ method: string; /** Request options passed to the method (query, body, params, etc.) */ options: unknown; }; /** * Represents the selected endpoint (path and method) without options. * * Captured when an HTTP method is accessed but not yet called. */ type SelectedEndpoint = { /** Path string to the endpoint */ path: string; /** HTTP method selected */ method: string; }; /** * Result from the selector proxy callback. * * Contains either: * - `call`: Full call details when method is invoked with options (for useRead) * - `selector`: Just path/method when method is accessed (for useWrite) */ type SelectorResult = { /** Full call details when method is invoked with options */ call: CapturedCall | null; /** Endpoint selection when method is accessed but not called */ selector: SelectedEndpoint | null; }; /** * Creates a proxy for selecting API endpoints without executing requests. * * Used by plugins to let users specify which cache entries to target * using a type-safe API selector syntax. * * @returns A proxy typed as TSchema for endpoint selection * * @example * ```ts * const proxy = createSelectorProxy(); * * // Select an endpoint * const endpoint = proxy("posts").GET; * * // Extract path for cache operations * const path = extractPathFromSelector(endpoint); // 'posts' * const method = extractMethodFromSelector(endpoint); // 'GET' * ``` * * @internal onCapture - Used internally by framework adapters */ declare function createSelectorProxy(onCapture?: (result: SelectorResult) => void): TSchema; /** * Extracts the path from a SelectorFunction. * * @param fn - A SelectorFunction returned from `createSelectorProxy` * @returns The path string (e.g., 'posts', 'posts/:id') * * @example * ```ts * const proxy = createSelectorProxy(); * const path = extractPathFromSelector(proxy("posts").GET); * // path = 'posts' * ``` */ declare function extractPathFromSelector(fn: unknown): string; /** * Extracts the HTTP method from a SelectorFunction. * * @param fn - A SelectorFunction returned from `createSelectorProxy` * @returns The HTTP method string (e.g., 'GET', 'POST') or undefined * * @example * ```ts * const proxy = createSelectorProxy(); * const method = extractMethodFromSelector(proxy("posts").POST()); * // method = 'POST' * ``` */ declare function extractMethodFromSelector(fn: unknown): string | undefined; declare const fetchTransport: Transport; interface XhrTransportOptions { /** Called on upload and download progress events. */ onProgress?: (event: ProgressEvent, xhr: XMLHttpRequest) => void; } declare module "./types" { interface TransportOptionsMap { xhr: XhrTransportOptions; } } declare const xhrTransport: Transport; declare function composeAdapter(baseAdapter: SubscriptionAdapter, plugins: readonly SpooshPlugin[]): SubscriptionAdapter; declare function executeFetch(baseUrl: string, path: string[], method: HttpMethod, defaultOptions: SpooshOptions, requestOptions?: AnyRequestOptions, nextTags?: boolean): Promise>; type ExecuteOptions = { force?: boolean; }; type OperationController = { execute: (options?: AnyRequestOptions, executeOptions?: ExecuteOptions) => Promise>; getState: () => OperationState; subscribe: (callback: () => void) => () => void; abort: () => void; refetch: () => Promise>; /** Called once when hook first mounts */ mount: () => void; /** Called once when hook finally unmounts */ unmount: () => void; /** Called when options/query changes. Pass previous context for cleanup. */ update: (previousContext: PluginContext) => void; /** Get current context (for passing to update as previousContext) */ getContext: () => PluginContext; setPluginOptions: (options: unknown) => void; setMetadata: (key: string, value: unknown) => void; }; type CreateOperationOptions = { operationType: OperationType; path: string; method: HttpMethod; tags: string[]; requestOptions?: AnyRequestOptions; stateManager: StateManager; eventEmitter: EventEmitter; pluginExecutor: PluginExecutor; fetchFn: (options: AnyRequestOptions) => Promise>; /** Unique identifier for the hook instance. Persists across queryKey changes. */ instanceId?: string; }; declare function createOperationController(options: CreateOperationOptions): OperationController; type InfiniteRequestOptions = { query?: Record; params?: Record; body?: unknown; }; type InfinitePageStatus = "pending" | "loading" | "success" | "error" | "stale"; interface InfinitePage { status: InfinitePageStatus; data?: TData; error?: TError; meta?: TMeta; input?: InfiniteRequestOptions; } type InfiniteNextContext = { lastPage: InfinitePage | undefined; pages: InfinitePage[]; request: TRequest; }; type InfinitePrevContext = { firstPage: InfinitePage | undefined; pages: InfinitePage[]; request: TRequest; }; type FetchDirection = "next" | "prev"; type InfiniteTriggerOptions = Partial & { /** Bypass cache and force refetch. Default: true */ force?: boolean; }; type InfiniteReadState> = { data: TItem[] | undefined; pages: InfinitePage[]; canFetchNext: boolean; canFetchPrev: boolean; error: TError | undefined; }; type InfiniteReadController> = { getState: () => InfiniteReadState; getFetchingDirection: () => FetchDirection | null; subscribe: (callback: () => void) => () => void; fetchNext: () => Promise; fetchPrev: () => Promise; trigger: (options?: InfiniteTriggerOptions) => Promise; refetch: () => Promise; abort: () => void; mount: () => void; unmount: () => void; update: (previousContext: PluginContext) => void; getContext: () => PluginContext; setPluginOptions: (options: unknown) => void; }; type CreateInfiniteReadOptions> = { path: string; method: HttpMethod; tags: string[]; initialRequest: InfiniteRequestOptions; canFetchNext?: (ctx: InfiniteNextContext) => boolean; canFetchPrev?: (ctx: InfinitePrevContext) => boolean; nextPageRequest?: (ctx: InfiniteNextContext) => Partial; prevPageRequest?: (ctx: InfinitePrevContext) => Partial; merger: (pages: InfinitePage[]) => TItem[]; stateManager: StateManager; eventEmitter: EventEmitter; pluginExecutor: PluginExecutor; fetchFn: (options: InfiniteRequestOptions, signal: AbortSignal) => Promise>; /** Unique identifier for the hook instance. Persists across queryKey changes. */ instanceId?: string; }; declare function createInfiniteReadController>(options: CreateInfiniteReadOptions): InfiniteReadController; /** * Status of an item in the queue. */ type QueueItemStatus = "pending" | "running" | "success" | "error" | "aborted"; /** * Represents a single item in the queue. */ interface QueueItem> { /** Unique identifier for this queue item */ id: string; /** Current status of the item */ status: QueueItemStatus; /** Response data on success */ data?: TData; /** Error on failure */ error?: TError; /** Original trigger input */ input?: { body?: unknown; query?: unknown; params?: Record; }; /** Plugin-contributed metadata (e.g., progress, transformedData) */ meta?: TMeta; } /** * Statistics information for the queue. */ interface QueueStats { /** Number of pending items waiting to run */ pending: number; /** Number of currently running items */ running: number; /** Number of settled items (success, error, or aborted) */ settled: number; /** Number of successful items */ success: number; /** Number of failed items (error or aborted) */ failed: number; /** Total number of items in queue */ total: number; /** Completion percentage (0-100) */ percentage: number; } /** * Input type for queue trigger. */ interface QueueTriggerInput { /** Custom ID for this queue item. If not provided, one will be auto-generated. */ id?: string; body?: unknown; query?: unknown; params?: Record; } /** * Queue controller instance. * Framework-agnostic - can be used directly in Angular, Vue, etc. */ interface QueueController> { /** Add item to queue and execute. Returns promise that resolves when item completes. */ trigger: (input: QueueTriggerInput) => Promise>; /** Get current queue state */ getQueue: () => QueueItem[]; /** Get queue statistics */ getStats: () => QueueStats; /** Subscribe to queue state changes */ subscribe: (callback: () => void) => () => void; /** Abort item by ID, or all items if no ID provided */ abort: (id?: string) => void; /** Retry failed/aborted item by ID, or all failed items if no ID provided */ retry: (id?: string) => Promise; /** Remove specific item by ID (aborts if active) */ remove: (id: string) => void; /** Remove all settled items (success, error, aborted). Keeps pending/running. */ removeSettled: () => void; /** Abort all and clear entire queue */ clear: () => void; /** Update the concurrency limit */ setConcurrency: (concurrency: number) => void; } /** * Configuration for creating a queue controller. */ interface QueueControllerConfig { /** API path */ path: string; /** HTTP method */ method: string; /** Maximum concurrent operations. Defaults to 3. */ concurrency?: number; /** Operation type for plugin middleware */ operationType: "read" | "write" | "queue"; /** Hook-level plugin options (e.g., progress, retries) */ hookOptions?: Record; } interface QueueControllerContext { api: unknown; stateManager: StateManager; eventEmitter: EventEmitter; pluginExecutor: ApiPluginExecutor; } declare function createQueueController>(config: QueueControllerConfig, context: QueueControllerContext): QueueController; /** * Semaphore for controlling concurrent access. * Used to limit the number of concurrent operations in the queue. */ declare class Semaphore { private max; private current; private waiting; constructor(max: number); acquire(): Promise; release(): void; setConcurrency(max: number): void; reset(): void; getCurrent(): number; getWaitingCount(): number; } interface SubscriptionController { subscribe(): Promise>; subscribe(callback: () => void): () => void; emit(message: unknown): Promise<{ success: boolean; error?: TError; }>; unsubscribe(): void; getState(): { data: TData | undefined; error: TError | undefined; isConnected: boolean; }; mount(): void; unmount(): void; setDisconnected(): void; } interface CreateSubscriptionControllerOptions { channel: string; baseAdapter: SubscriptionAdapter; stateManager: StateManager; eventEmitter: EventEmitter; pluginExecutor: PluginExecutor; queryKey: string; operationType: OperationType; path: string; method: string; instanceId?: string; } declare function createSubscriptionController(options: CreateSubscriptionControllerOptions): SubscriptionController; export { type AnyMethod, type AnyRequestOptions, type ApiContext, type ApiPluginExecutor, type ApiResolvers, type ApiSchema, type BaseSubscriptionResponse, type BuiltInEvents, type CacheEntry, type CacheEntryWithKey, type CapturedCall, type ComputeRequestOptions, type CoreRequestOptionsBase, type CreateInfiniteReadOptions, type CreateOperationOptions, type CreateSubscriptionControllerOptions, type DataAwareCallback, type DataAwareTransform, type DataChangeCallback, type DevtoolEvents, type EventEmitter, type EventListener, type EventOptions, type EventTracer, type ExecuteOptions, type ExtractBody$1 as ExtractBody, type ExtractData, type ExtractError, type ExtractMethodOptions, type ExtractParamNames, type ExtractQuery$1 as ExtractQuery, type ExtractTriggerBody, type ExtractTriggerParams, type ExtractTriggerQuery, type FetchDirection, type FetchExecutor, type FindMatchingKey, HTTP_METHODS, type HasParams, type HasReadMethod, type HasSubscriptionMethod, type HasWriteMethod, type HeadersInitOrGetter, type HttpMethod, type HttpMethodKey, type InfiniteNextContext, type InfinitePage, type InfinitePageStatus, type InfinitePrevContext, type InfiniteReadController, type InfiniteReadState, type InfiniteRequestOptions, type InfiniteTriggerOptions, type LifecyclePhase, type MergePluginApi, type MergePluginOptions, type MergePluginResults, type MethodOptionsMap, type OperationController, type OperationState, type OperationType, type PluginAccessor, type PluginArray, type PluginContext, type PluginContextBase, type PluginContextExtensions, type PluginContextInput, type PluginExecutor, type PluginFactory, type PluginHandler, type PluginInternalRegistry, type PluginLifecycle, type PluginMiddleware, type PluginRegistry, type PluginRequestOptions, type PluginResolvers, type PluginResponseHandler, type PluginResultResolvers, type PluginTypeConfig, type PluginUpdateHandler, type QueueController, type QueueControllerConfig, type QueueControllerContext, type QueueItem, type QueueItemStatus, type QueueSelectorClient, type QueueStats, type QueueTriggerInput, type ReadClient, type ReadPaths, type ReadSchemaHelper, type RefetchEvent, type RequestCompleteEvent, type RequestOptions$1 as RequestOptions, type RequestTracer, type ResolveApi, type ResolveResultTypes, type ResolveSchemaTypes, type ResolveTypes, type ResolverContext, type SchemaPaths, type SelectedEndpoint, type SelectorFunction, type SelectorResult, Semaphore, type SetupContext, type Simplify, Spoosh, type SpooshBody, type SpooshClient, type SpooshConfig, type SpooshInstance, type SpooshOptions, type SpooshOptionsInput, type SpooshPlugin, type SpooshResponse, type SpooshSchema, type SpooshSubscriptionMethodRegistry, type SpooshTransport, type SpooshTransportRegistry, type StandaloneEvent, type StateManager, type StripPrefix, type Subscriber, type SubscriptionAccumulateEvent, type SubscriptionAdapter, type SubscriptionClient, type SubscriptionConnectEvent, type SubscriptionConnectedEvent, type SubscriptionContext, type SubscriptionController, type SubscriptionDisconnectEvent, type SubscriptionErrorEvent, type SubscriptionHandle, type SubscriptionMessageEvent, type SubscriptionMethod, type SubscriptionPaths, type TagOptions, type Trace, type TraceColor, type TraceEvent, type TraceInfo, type TraceListener, type TraceOptions, type TraceStage, type Transport, type TransportName, type TransportOption, type TransportOptionsMap, type TransportResponse, type TypedPluginContext, type TypedPluginDefinition, type WriteClient, type WriteMethod, type WritePaths, type WriteSchemaHelper, type WriteSelectorClient, __DEV__, buildUrl, clone, composeAdapter, containsFile, createClient, createEventEmitter, createInfiniteReadController, createInitialState, createOperationController, createPluginExecutor, createPluginRegistry, createProxyHandler, createQueueController, createSelectorProxy, createSpooshPlugin, createStateManager, createSubscriptionController, createTracer, executeFetch, extractMethodFromSelector, extractPathFromSelector, fetchTransport, form, generateSelfTagFromKey, generateTags, generateUUID, getContentType, isAbortError, isJsonBody, isNetworkError, isSpooshBody, json, matchTag, matchTags, mergeHeaders, normalizeTag, objectToFormData, objectToUrlEncoded, removeHeaderKeys, resolveHeadersToRecord, resolvePath, resolvePathString, resolveRequestBody, resolveTags, setHeaders, sortObjectKeys, urlencoded, xhrTransport };