wellcrafted
Version:
Delightful TypeScript patterns for elegant, type-safe applications
184 lines (182 loc) • 9.28 kB
TypeScript
import { Result } from "../result-_socO0Ud.js";
import "../tap-err-C0xfVXtz.js";
import "../index-D0f5JWiT.js";
import { DefaultError, MutationKey, MutationObserverOptions, QueryClient, QueryFunction, QueryKey, QueryObserverOptions } from "@tanstack/query-core";
//#region src/query/utils.d.ts
/**
* Input for `resultQueryOptions` and `defineQuery`.
*
* Mirrors TanStack Query's `QueryObserverOptions` but expects `queryFn` to
* return a Wellcrafted `Result`. The Result is unwrapped into TanStack's
* throwing data/error contract by `resultQueryOptions`.
*
* @template TQueryFnData - The success type produced by `queryFn`
* @template TError - The error type carried by the Result
* @template TData - The type seen by consumers after `select`
* @template TQueryData - The type stored in the cache (usually `TQueryFnData`)
* @template TQueryKey - The literal query key tuple
*/
type QueryOptionsInput<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey> = Omit<QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>, "queryFn"> & {
queryKey: TQueryKey;
queryFn: QueryFunction<Result<TQueryFnData, TError>, TQueryKey>;
};
/**
* Input for `resultMutationOptions` and `defineMutation`.
*
* Mirrors TanStack Query's `MutationObserverOptions` but expects `mutationFn`
* to return a Wellcrafted `Result`. The Result is unwrapped into TanStack's
* throwing data/error contract by `resultMutationOptions`.
*
* @template TData - The success type produced by `mutationFn`
* @template TError - The error type carried by the Result
* @template TVariables - The variables passed to `mutationFn`
* @template TContext - The context type for optimistic updates
* @template TMutationKey - The literal mutation key tuple
*/
type MutationOptionsInput<TData, TError, TVariables = void, TContext = unknown, TMutationKey extends MutationKey = MutationKey> = Omit<MutationObserverOptions<TData, TError, TVariables, TContext>, "mutationFn"> & {
mutationKey: TMutationKey;
mutationFn: (variables: TVariables) => Result<TData, TError> | Promise<Result<TData, TError>>;
};
/**
* Adapter from a Result-returning query function to TanStack Query options.
*
* This is the single canonical place where `Result<TQueryFnData, TError>`
* is converted into TanStack's contract: `Ok(data)` resolves with `data`,
* `Err(error)` throws `error` into the query error channel.
*
* Use this directly with framework hooks when you do not need the
* `QueryClient`-bound imperative helpers from `defineQuery`:
*
* ```ts
* const query = createQuery(() => resultQueryOptions({
* queryKey: ['user', userId],
* queryFn: () => services.getUser(userId),
* }));
* ```
*
* `defineQuery` composes through this helper, so the `.options` it returns
* is the same shape `resultQueryOptions` produces.
*
* @param input - Result-aware query configuration
* @returns TanStack Query `QueryObserverOptions` with `queryFn` rewired to
* resolve `Ok` and throw `Err`
*/
declare function resultQueryOptions<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryData = TQueryFnData, const TQueryKey extends QueryKey = QueryKey>(input: QueryOptionsInput<TQueryFnData, TError, TData, TQueryData, TQueryKey>): QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>;
/**
* Adapter from a Result-returning mutation function to TanStack Query options.
*
* This is the single canonical place where `Result<TData, TError>` is
* converted into TanStack's contract: `Ok(data)` resolves with `data`,
* `Err(error)` throws `error` into the mutation error channel.
*
* Use this directly with framework hooks when you do not need the
* `QueryClient`-bound imperative helpers from `defineMutation`:
*
* ```ts
* const save = createMutation(() => resultMutationOptions({
* mutationKey: ['saveUser'],
* mutationFn: (input: SaveUserInput) => services.saveUser(input),
* }));
* ```
*
* `defineMutation` composes through this helper, so the `.options` it
* returns is the same shape `resultMutationOptions` produces.
*
* @param input - Result-aware mutation configuration
* @returns TanStack Query `MutationObserverOptions` with `mutationFn` rewired
* to resolve `Ok` and throw `Err`
*/
declare function resultMutationOptions<TData, TError, TVariables = void, TContext = unknown, const TMutationKey extends MutationKey = MutationKey>(input: MutationOptionsInput<TData, TError, TVariables, TContext, TMutationKey>): MutationObserverOptions<TData, TError, TVariables, TContext>;
/**
* Output of `defineQuery`.
*
* Query imperative reads require an explicit cache policy.
*
* - `options`: Options shape produced by `resultQueryOptions`, ready for hooks.
* - `fetch()`: Always evaluates freshness; refetches if stale.
* - `ensure()`: Prefers cached data; fetches only when missing.
*/
type DefineQueryOutput<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey> = {
options: QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>;
fetch: () => Promise<Result<TQueryData, TError>>;
ensure: () => Promise<Result<TQueryData, TError>>;
};
/**
* Output of `defineMutation`.
*
* The returned function directly executes the mutation.
*
* - `(variables)` (callable): Imperatively runs the mutation, returning a Result.
* - `options`: Options shape produced by `resultMutationOptions`, ready for hooks.
*/
type DefineMutationOutput<TData, TError, TVariables = void, TContext = unknown> = ((variables: TVariables) => Promise<Result<TData, TError>>) & {
options: MutationObserverOptions<TData, TError, TVariables, TContext>;
};
/**
* Creates `defineQuery` and `defineMutation` bound to a specific `QueryClient`.
*
* Use this when you want a reusable query/mutation definition that carries
* its own imperative query helpers (`.fetch`, `.ensure`) and callable mutation
* execution powered by a specific client. For local one-shot options that only need
* to flow into a framework hook, prefer `resultQueryOptions` / `resultMutationOptions`
* directly: those are platform-agnostic and do not require a `QueryClient`.
*
* Both `defineQuery` and `defineMutation` compose through `resultQueryOptions` and
* `resultMutationOptions`, so there is exactly one place that unwraps `Result`
* into TanStack's throwing contract.
*
* @param queryClient - The TanStack `QueryClient` to bind imperative helpers to
*
* @example
* ```ts
* const queryClient = new QueryClient();
* const { defineQuery, defineMutation } = createQueryFactories(queryClient);
*
* const userQuery = defineQuery({
* queryKey: ['user', userId],
* queryFn: () => services.getUser(userId),
* });
*
* // Reactive
* const query = createQuery(() => userQuery.options);
*
* // Imperative
* const { data, error } = await userQuery.fetch();
* ```
*/
declare function createQueryFactories(queryClient: QueryClient): {
defineQuery: <TQueryFnData = unknown, TError = Error, TData = TQueryFnData, TQueryData = TQueryFnData, const TQueryKey extends QueryKey = readonly unknown[]>(input: QueryOptionsInput<TQueryFnData, TError, TData, TQueryData, TQueryKey>) => DefineQueryOutput<TQueryFnData, TError, TData, TQueryData, TQueryKey>;
defineMutation: <TData, TError, TVariables = void, TContext = unknown, const TMutationKey extends MutationKey = readonly unknown[]>(input: MutationOptionsInput<TData, TError, TVariables, TContext, TMutationKey>) => DefineMutationOutput<TData, TError, TVariables, TContext>;
};
/**
* Identity helper for declaring a TanStack Query key map while preserving
* tuple types.
*
* - **Static entries** like `['users', 'active']` are narrowed to readonly
* tuples with full literal precision (e.g. `readonly ['users', 'active']`)
* via the `const` type parameter modifier. No per-line `as const` needed.
*
* - **Factory entries** like `(id: string) => ['users', id]` are narrowed to
* tuple SHAPE (e.g. `[string, string]` with correct arity), not widened to
* `string[]`. This happens because the strict tuple constraint provides
* contextual typing into the function body. Literal positions still widen
* without `as const` (TS does not propagate literal narrowing through
* contextual typing). Add `as const` to the body when you need the literal:
* `(id) => ['users', id] as const` -> `readonly ['users', string]`.
*
* Empty arrays and non-key values are rejected at the type level.
*
* @example
* ```ts
* const userKeys = defineKeys({
* all: ['users'], // readonly ['users']
* active: ['users', 'active'], // readonly ['users', 'active']
* detail: (id: string) => ['users', id], // [string, string] (tuple shape kept)
* page: (n: number) => ['users', n] as const, // readonly ['users', number]
* });
* ```
*/
declare function defineKeys<const TKeys extends Record<string, readonly [unknown, ...unknown[]] | ((...args: never[]) => readonly [unknown, ...unknown[]])>>(keys: TKeys): TKeys;
//#endregion
export { createQueryFactories, defineKeys, resultMutationOptions, resultQueryOptions };
//# sourceMappingURL=index.d.ts.map