/** * REST resource composable for CRUD operations with optimistic updates, * form submission, and reactive caching built on the bQuery fetch layer. * * @module bquery/reactive */ import { Signal } from './core'; import { type AsyncDataStatus, type UseFetchOptions } from './async-data'; import { type HttpClient, type HttpRequestConfig, type HttpResponse } from './http'; /** HTTP method shortcuts available on a resource. */ export interface ResourceActions { /** Fetch the resource (GET). */ fetch: () => Promise; /** Create a new item (POST). */ create: (body: Partial | Record) => Promise; /** Replace the resource (PUT). */ update: (body: Partial | Record) => Promise; /** Partially update the resource (PATCH). */ patch: (body: Partial | Record) => Promise; /** Delete the resource (DELETE). */ remove: () => Promise; } /** Options for `useResource()`. */ export interface UseResourceOptions extends Omit, 'method' | 'body'> { /** Enable optimistic updates for mutating operations (default: false). */ optimistic?: boolean; /** Called after any successful mutation (create / update / patch / remove). */ onMutationSuccess?: (data: T | undefined, action: string) => void; /** Called after a failed mutation, receives the error and action name. */ onMutationError?: (error: Error, action: string) => void; } /** Return value of `useResource()`. */ export interface UseResourceReturn { /** Reactive resource data. */ data: Signal; /** Last error. */ error: Signal; /** Lifecycle status for the initial fetch. */ status: Signal; /** Whether the initial fetch is pending. */ pending: { readonly value: boolean; peek(): boolean; }; /** Whether any mutation is in progress. */ isMutating: { readonly value: boolean; peek(): boolean; }; /** CRUD actions. */ actions: ResourceActions; /** Refresh the resource (re-GET). */ refresh: () => Promise; /** Clear data, error, and status. */ clear: () => void; /** Dispose all reactive state and prevent future operations. */ dispose: () => void; } /** * Reactive REST resource composable providing CRUD operations. * * Binds a base URL to a resource and exposes `fetch`, `create`, `update`, * `patch`, and `remove` helpers with optional optimistic updates. * * @template T - Resource data type * @param url - Resource endpoint URL or getter * @param options - Fetch and resource options * @returns Reactive resource state with CRUD actions * * @example * ```ts * import { useResource } from '@bquery/bquery/reactive'; * * const user = useResource('/api/users/1', { * baseUrl: 'https://api.example.com', * optimistic: true, * }); * * // Read * await user.actions.fetch(); * * // Update * await user.actions.patch({ name: 'Ada' }); * * // Delete * await user.actions.remove(); * ``` */ export declare const useResource: (url: string | URL | (() => string | URL), options?: UseResourceOptions) => UseResourceReturn; /** Options for `useSubmit()`. */ export interface UseSubmitOptions extends Omit, 'body' | 'immediate'> { /** HTTP method (default: `'POST'`). */ method?: string; } /** Return value of `useSubmit()`. */ export interface UseSubmitReturn { /** Last response data. */ data: Signal; /** Last error. */ error: Signal; /** Current status. */ status: Signal; /** Whether the submission is pending. */ pending: { readonly value: boolean; peek(): boolean; }; /** Submit data to the endpoint. */ submit: (body: Record | FormData | BodyInit) => Promise; /** Reset state. */ clear: () => void; } /** * Reactive form submission composable. * * Provides a `submit()` function that sends data to an endpoint with * reactive status, data, and error signals. * * @template TResponse - Response data type * @param url - Submission endpoint URL * @param options - Fetch options (method defaults to POST) * @returns Reactive submission state with `submit()` and `clear()` * * @example * ```ts * import { useSubmit } from '@bquery/bquery/reactive'; * * const form = useSubmit<{ id: number }>('/api/users', { * baseUrl: 'https://api.example.com', * headers: { 'x-csrf': token }, * }); * * const result = await form.submit({ name: 'Ada', email: 'ada@example.com' }); * console.log(form.status.value); // 'success' * ``` */ export declare const useSubmit: (url: string | URL, options?: UseSubmitOptions) => UseSubmitReturn; /** Typed CRUD methods for a REST endpoint. */ export interface RestClient { /** GET all items. */ list: (config?: HttpRequestConfig) => Promise>; /** GET a single item by ID. */ get: (id: string | number, config?: HttpRequestConfig) => Promise>; /** POST a new item. */ create: (body: Partial | Record, config?: HttpRequestConfig) => Promise>; /** PUT (full replace) an item by ID. */ update: (id: string | number, body: Partial | Record, config?: HttpRequestConfig) => Promise>; /** PATCH (partial update) an item by ID. */ patch: (id: string | number, body: Partial | Record, config?: HttpRequestConfig) => Promise>; /** DELETE an item by ID. */ remove: (id: string | number, config?: HttpRequestConfig) => Promise>; /** The underlying HttpClient instance. */ http: HttpClient; } /** * Create a typed REST client for a specific API resource. * * Wraps `createHttp()` and maps standard CRUD operations to their * conventional REST endpoints (`GET /`, `GET /:id`, `POST /`, `PUT /:id`, * `PATCH /:id`, `DELETE /:id`). * * @template T - Resource item type * @param baseUrl - Base URL of the resource (e.g. `https://api.example.com/users`) * @param defaults - Default request configuration merged into every call * @returns Typed REST client with `list`, `get`, `create`, `update`, `patch`, `remove` * * @example * ```ts * import { createRestClient } from '@bquery/bquery/reactive'; * * interface User { id: number; name: string; email: string } * * const users = createRestClient('https://api.example.com/users', { * headers: { authorization: '******' }, * timeout: 10_000, * }); * * const { data: allUsers } = await users.list(); * const { data: user } = await users.get(1); * const { data: created } = await users.create({ name: 'Ada' }); * await users.update(1, { name: 'Ada', email: 'ada@example.com' }); * await users.patch(1, { email: 'new@example.com' }); * await users.remove(1); * ``` */ export declare const createRestClient: (baseUrl: string, defaults?: HttpRequestConfig) => RestClient; /** Extract a unique identifier from an item. */ export type IdExtractor = (item: T) => string | number; /** Options for `useResourceList()`. */ export interface UseResourceListOptions extends Omit, 'method' | 'body'> { /** Extract the unique ID from each item (default: `item.id`). */ getId?: IdExtractor; /** Enable optimistic list mutations (default: false). */ optimistic?: boolean; /** Called after a successful list mutation. */ onMutationSuccess?: (action: string) => void; /** Called after a failed list mutation. */ onMutationError?: (error: Error, action: string) => void; } /** CRUD actions for a list resource. */ export interface ResourceListActions { /** Refresh the list (GET). */ fetch: () => Promise; /** Add a new item to the list (POST). */ add: (body: Partial | Record) => Promise; /** Update an existing item (PUT) by ID. */ update: (id: string | number, body: Partial | Record) => Promise; /** Partially update an existing item (PATCH) by ID. */ patch: (id: string | number, body: Partial | Record) => Promise; /** Remove an item from the list (DELETE) by ID. */ remove: (id: string | number) => Promise; } /** Return value of `useResourceList()`. */ export interface UseResourceListReturn { /** Reactive list data. */ data: Signal; /** Last error. */ error: Signal; /** Lifecycle status. */ status: Signal; /** Whether the list fetch is pending. */ pending: { readonly value: boolean; peek(): boolean; }; /** Whether any mutation is in progress. */ isMutating: { readonly value: boolean; peek(): boolean; }; /** CRUD actions. */ actions: ResourceListActions; /** Refresh the list. */ refresh: () => Promise; /** Clear data, error, and status. */ clear: () => void; /** Dispose all reactive state. */ dispose: () => void; } /** * Reactive list/collection CRUD composable with optimistic add, remove, and update. * * Fetches a list of items and provides typed CRUD helpers that update the * reactive array optimistically or after server confirmation. * * @template T - Item type * @param url - List endpoint URL or getter * @param options - Fetch and list options * @returns Reactive list state with CRUD actions * * @example * ```ts * import { useResourceList } from '@bquery/bquery/reactive'; * * interface Todo { id: number; title: string; done: boolean } * * const todos = useResourceList('/api/todos', { * baseUrl: 'https://api.example.com', * optimistic: true, * getId: (t) => t.id, * }); * * await todos.actions.add({ title: 'Buy milk', done: false }); * await todos.actions.patch(1, { done: true }); * await todos.actions.remove(1); * ``` */ export declare const useResourceList: (url: string | URL | (() => string | URL), options?: UseResourceListOptions) => UseResourceListReturn; /** * Deduplicate identical in-flight requests or operations keyed by `key`. * * If an operation with the same key is already in flight, reuse its promise * instead of starting a new one. Once the operation completes, the entry is removed. * * @param key - Cache key for the in-flight operation (for HTTP, typically URL + serialized query) * @param execute - The operation function to run if no duplicate is in flight * @returns The shared result promise for callers using the same key concurrently * * @example * ```ts * import { deduplicateRequest, createHttp } from '@bquery/bquery/reactive'; * * const api = createHttp({ baseUrl: 'https://api.example.com' }); * * // Both calls share the same in-flight operation * const [a, b] = await Promise.all([ * deduplicateRequest('/users', () => api.get('/users')), * deduplicateRequest('/users', () => api.get('/users')), * ]); * ``` */ export declare function deduplicateRequest(key: string, execute: () => Promise): Promise; //# sourceMappingURL=rest.d.ts.map