Integrations
Using Effect
The cache is written in Effect. Return Effects from queryFns for typed errors, services and interruption, run loaders and actions as Effects, and read the same queries from React.
raf-query's cache is written in Effect. Using raf-query doesn't take any Effect: queryFns return promises, and QueryClient, the hooks and the integrations speak promises. Apps that do use Effect get the same cache as Effects, with typed errors and services.
effect (^4.0.0) is a peer dependency of both packages, so an app that uses Effect shares one copy with raf-query.
The whole picture
An issue page on TanStack Start, from the API to the form that renames the issue. The Effect example app is the same thing, in full, with Effect RPC.
The API: errors and a service
Failures are tagged errors, and the API is a service. Its methods say how they can fail:
export class class IssueNotFoundIssueNotFound extends import SchemaSchema.const TaggedError: <IssueNotFound, {}>(identifier?: string) => {
<Tag, Fields>(tag: Tag, fields: Fields, annotations?: Schema.Annotations.Declaration<IssueNotFound, readonly [Schema.TaggedStruct<Tag, Fields>]> | undefined): Schema.Class<IssueNotFound, Schema.TaggedStruct<Tag, Fields>, YieldableError>;
<Tag, S>(tag: Tag, schema: S, annotations?: Schema.Annotations.Declaration<IssueNotFound, readonly [Schema.Struct<{ [K in keyof ({
readonly _tag: Schema.tag<Tag>;
} & S["fields"])]: ({
readonly _tag: Schema.tag<Tag>;
} & S["fields"])[K]; }>]> | undefined): Schema.Class<...>;
}
TaggedError<class IssueNotFoundIssueNotFound>()(
"IssueNotFound",
{ id: Schema.Numberid: import SchemaSchema.const Number: Schema.NumberNumber },
) {}
export class class UnauthorizedUnauthorized extends import SchemaSchema.const TaggedError: <Unauthorized, {}>(identifier?: string) => {
<Tag, Fields>(tag: Tag, fields: Fields, annotations?: Schema.Annotations.Declaration<Unauthorized, readonly [Schema.TaggedStruct<Tag, Fields>]> | undefined): Schema.Class<Unauthorized, Schema.TaggedStruct<Tag, Fields>, YieldableError>;
<Tag, S>(tag: Tag, schema: S, annotations?: Schema.Annotations.Declaration<Unauthorized, readonly [Schema.Struct<{ [K in keyof ({
readonly _tag: Schema.tag<Tag>;
} & S["fields"])]: ({
readonly _tag: Schema.tag<Tag>;
} & S["fields"])[K]; }>]> | undefined): Schema.Class<...>;
}
TaggedError<class UnauthorizedUnauthorized>()(
"Unauthorized",
{},
) {}
export class class EmptyTitleEmptyTitle extends import SchemaSchema.const TaggedError: <EmptyTitle, {}>(identifier?: string) => {
<Tag, Fields>(tag: Tag, fields: Fields, annotations?: Schema.Annotations.Declaration<EmptyTitle, readonly [Schema.TaggedStruct<Tag, Fields>]> | undefined): Schema.Class<EmptyTitle, Schema.TaggedStruct<Tag, Fields>, YieldableError>;
<Tag, S>(tag: Tag, schema: S, annotations?: Schema.Annotations.Declaration<EmptyTitle, readonly [Schema.Struct<{ [K in keyof ({
readonly _tag: Schema.tag<Tag>;
} & S["fields"])]: ({
readonly _tag: Schema.tag<Tag>;
} & S["fields"])[K]; }>]> | undefined): Schema.Class<...>;
}
TaggedError<class EmptyTitleEmptyTitle>()(
"EmptyTitle",
{},
) {}
export class class IssueApiIssueApi extends import ContextContext.const Service: <IssueApi, {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect.Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>;
}>() => <Identifier, E, R, Args>(id: Identifier, options?: {
readonly make?: ((...args: Args) => Effect.Effect<{
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect.Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>;
}, E, R>) | Effect.Effect<...> | undefined;
} | undefined) => Context.ServiceClass<...> & ([...] extends [...] ? unknown : {
...;
}) (+2 overloads)
Service<
class IssueApiIssueApi,
{
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>get: (
id: numberid: number,
) => import EffectEffect.interface Effect<out A, out E = never, out R = never>Effect<Issue, class IssueNotFoundIssueNotFound | class UnauthorizedUnauthorized>;
readonly assignee: (issueId: number) => Effect.Effect<User, IssueNotFound | Unauthorized>assignee: (
issueId: numberissueId: number,
) => import EffectEffect.interface Effect<out A, out E = never, out R = never>Effect<User, class IssueNotFoundIssueNotFound | class UnauthorizedUnauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>
rename: (input: {
readonly id: number;
readonly title: string;
}
input: {
readonly id: numberid: number;
readonly title: stringtitle: string;
}) => import EffectEffect.interface Effect<out A, out E = never, out R = never>Effect<Issue, class EmptyTitleEmptyTitle | class UnauthorizedUnauthorized>;
}
>()("app/IssueApi") {}
IssueApiLive, a Layer<IssueApi>, implements it: an HTTP client, an RPC client, a database.
Queries
A queryFn returns an Effect that uses the service. The factory's type carries the service and the errors — ErrorOf and ServicesOf name them, and every cache verb fails or requires them:
import { type type ErrorOf<TFactory extends QueryFactory> = ReturnType<TFactory> extends InfiniteQueryOptions<infer TTypes extends InfiniteQueryTypes> ? ErrorIn<TTypes> : ReturnType<TFactory> extends QueryOptions<infer TTypes extends QueryTypes> ? ErrorIn<TTypes> : unknownErrorOf, function queryOptions<TData, TError = unknown, TServices = never, const TKey extends QueryKey = QueryKey, TThrow extends boolean | undefined = undefined>(options: Omit<QueryOptions<{
data: TData;
error: TError;
key: TKey;
services: TServices;
}>, "kind" | "throwOnError"> & ThrowOnErrorField<TThrow>): QueryOptions<Flat<{
data: TData;
key: TKey;
} & ErrorPart<TError> & ServicesPart<TServices> & ThrowPart<TThrow>>>
queryOptions, type type ServicesOf<TFactory extends QueryFactory> = ReturnType<TFactory> extends InfiniteQueryOptions<infer TTypes extends InfiniteQueryTypes> ? ServicesIn<TTypes> : ReturnType<TFactory> extends QueryOptions<infer TTypes extends QueryTypes> ? ServicesIn<TTypes> : neverServicesOf } from "@raf-query/react";
import { class IssueApiIssueApi } from "./issues.ts";
export function function issueQuery(input: {
id: number;
}): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
issueQuery(input: {
id: number;
}
input: { id: numberid: number }) {
return queryOptions<Issue, IssueNotFound | Unauthorized, IssueApi, readonly ["issue", number], undefined>(options: Omit<QueryOptions<{
data: Issue;
error: IssueNotFound | Unauthorized;
key: readonly ["issue", number];
services: IssueApi;
}>, "kind" | "throwOnError"> & ThrowOnErrorField<undefined>): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
queryOptions({
queryKey: readonly ["issue", number]queryKey: ["issue", input: {
id: number;
}
input.id: numberid],
function queryFn(context: QueryFunctionContext): QueryFnResult<{
data: Issue;
error: IssueNotFound | Unauthorized;
key: readonly ["issue", number];
services: IssueApi;
}>
queryFn: () => class IssueApiIssueApi.Service<IssueApi, { readonly get: (id: number) => Effect<Issue, IssueNotFound | Unauthorized>; readonly assignee: (issueId: number) => Effect<User, IssueNotFound | Unauthorized>; readonly rename: (input: { readonly id: number; readonly title: string; }) => Effect<Issue, EmptyTitle | Unauthorized>; }>.use<Issue, IssueNotFound | Unauthorized, never>(f: (service: {
readonly get: (id: number) => Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect<Issue, EmptyTitle | Unauthorized>;
}) => Effect<...>): Effect<...>
use((api: {
readonly get: (id: number) => Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect<Issue, EmptyTitle | Unauthorized>;
}
api) => api: {
readonly get: (id: number) => Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect<Issue, EmptyTitle | Unauthorized>;
}
api.get: (id: number) => Effect<Issue, IssueNotFound | Unauthorized>get(input: {
id: number;
}
input.id: numberid)),
});
}
export function function assigneeQuery(input: {
issueId: number;
}): QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
assigneeQuery(input: {
issueId: number;
}
input: { issueId: numberissueId: number }) {
return queryOptions<User, IssueNotFound | Unauthorized, IssueApi, readonly ["issue", number, "assignee"], undefined>(options: Omit<QueryOptions<{
data: User;
error: IssueNotFound | Unauthorized;
key: readonly ["issue", number, "assignee"];
services: IssueApi;
}>, "kind" | "throwOnError"> & ThrowOnErrorField<undefined>): QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
...;
}>>
queryOptions({
queryKey: readonly ["issue", number, "assignee"]queryKey: ["issue", input: {
issueId: number;
}
input.issueId: numberissueId, "assignee"],
function queryFn(context: QueryFunctionContext): QueryFnResult<{
data: User;
error: IssueNotFound | Unauthorized;
key: readonly ["issue", number, "assignee"];
services: IssueApi;
}>
queryFn: () => class IssueApiIssueApi.Service<IssueApi, { readonly get: (id: number) => Effect<Issue, IssueNotFound | Unauthorized>; readonly assignee: (issueId: number) => Effect<User, IssueNotFound | Unauthorized>; readonly rename: (input: { readonly id: number; readonly title: string; }) => Effect<Issue, EmptyTitle | Unauthorized>; }>.use<User, IssueNotFound | Unauthorized, never>(f: (service: {
readonly get: (id: number) => Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect<Issue, EmptyTitle | Unauthorized>;
}) => Effect<...>): Effect<...>
use((api: {
readonly get: (id: number) => Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect<Issue, EmptyTitle | Unauthorized>;
}
api) => api: {
readonly get: (id: number) => Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect<Issue, EmptyTitle | Unauthorized>;
}
api.assignee: (issueId: number) => Effect<User, IssueNotFound | Unauthorized>assignee(input: {
issueId: number;
}
input.issueId: numberissueId)),
});
}
export type IssueError = type ErrorOf<TFactory extends QueryFactory> = ReturnType<TFactory> extends InfiniteQueryOptions<infer TTypes extends InfiniteQueryTypes> ? ErrorIn<TTypes> : ReturnType<TFactory> extends QueryOptions<infer TTypes extends QueryTypes> ? ErrorIn<TTypes> : unknownErrorOf<typeof function issueQuery(input: {
id: number;
}): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
issueQuery>;export type AssigneeError = type ErrorOf<TFactory extends QueryFactory> = ReturnType<TFactory> extends InfiniteQueryOptions<infer TTypes extends InfiniteQueryTypes> ? ErrorIn<TTypes> : ReturnType<TFactory> extends QueryOptions<infer TTypes extends QueryTypes> ? ErrorIn<TTypes> : unknownErrorOf<typeof function assigneeQuery(input: {
issueId: number;
}): QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
assigneeQuery>;export type Services = type ServicesOf<TFactory extends QueryFactory> = ReturnType<TFactory> extends InfiniteQueryOptions<infer TTypes extends InfiniteQueryTypes> ? ServicesIn<TTypes> : ReturnType<TFactory> extends QueryOptions<infer TTypes extends QueryTypes> ? ServicesIn<TTypes> : neverServicesOf<typeof function issueQuery(input: {
id: number;
}): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
issueQuery>;The client
The QueryClient's layer provides the services, and the router context carries the client with their type, so loaders can only start queries the layer can run:
import { class QueryClient<TServices = never>QueryClient, function QueryClientProvider(props: {
readonly children?: React.ReactNode;
readonly client: QueryClient<unknown>;
}): React.JSX.Element
QueryClientProvider } from "@raf-query/react";
import { function setupRouterIntegration<TRouter extends AnyRouter>(options: RouterIntegrationOptions<TRouter>): voidsetupRouterIntegration } from "@raf-query/react/tanstack-router";
import { const createRouter: CreateRouterFncreateRouter } from "@tanstack/react-router";
import { const IssueApiLive: Layer<IssueApi, never, never>IssueApiLive } from "./issues.ts";
import { const routeTree: Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>
routeTree } from "./routeTree.gen.ts";
export function function getRouter(): RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>
getRouter() {
const const queryClient: QueryClient<IssueApi>queryClient = new new QueryClient<IssueApi>(config?: QueryClientConfig<IssueApi>): QueryClient<IssueApi>QueryClient({
Config.defaults?: QueryBehavior | undefineddefaults: { QueryBehavior.staleTime?: number | undefinedstaleTime: 5000 },
QueryClientConfig<IssueApi>.layer?: Layer<IssueApi, never, never> | undefinedlayer: const IssueApiLive: Layer<IssueApi, never, never>IssueApiLive,
});
const const router: RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>
router = createRouter<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>(options: RouterConstructorOptions<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, ... 7 more ..., undefined>, "never", false, RouterHistory, Record<...>>): RouterCore<...>
createRouter({
context: {
queryClient: QueryClient<IssueApi>;
}
context: { queryClient: QueryClient<IssueApi>queryClient },
// The query cache decides what's fresh; loaders always run and dedupe there.
defaultPreloadStaleTime?: number | undefineddefaultPreloadStaleTime: 0,
routeTree?: Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined> | undefined
routeTree,
Wrap?: ((props: {
children: any;
}) => React.JSX.Element) | undefined
Wrap: (props: {
children: any;
}
props) => (
<function QueryClientProvider(props: {
readonly children?: React.ReactNode;
readonly client: QueryClient<unknown>;
}): React.JSX.Element
QueryClientProvider client: QueryClient<unknown>client={const queryClient: QueryClient<IssueApi>queryClient}>
{props: {
children: any;
}
props.children: anychildren}
</function QueryClientProvider(props: {
readonly children?: React.ReactNode;
readonly client: QueryClient<unknown>;
}): React.JSX.Element
QueryClientProvider>
),
});
setupRouterIntegration<RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>>(options: RouterIntegrationOptions<RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, ... 8 more ..., undefined>, "never", false, RouterHistory, Record<...>>>): void
setupRouterIntegration({ RouterIntegrationOptions<TRouter extends AnyRouter>.queryClient: QueryClient<never>queryClient, RouterIntegrationOptions<RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, { queryClient: QueryClient<IssueApi>; }, AnyContext, ... 7 more ..., undefined>, "never", false, RouterHistory, Record<...>>>.router: RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>
router });
return const router: RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>
router;
}import type { class QueryClient<TServices = never>QueryClient } from "@raf-query/react";
import { function createRootRouteWithContext<TRouterContext extends {}>(): <TRegister = Register, TRouteContextFn = AnyContext, TBeforeLoadFn = AnyContext, TSearchValidator = undefined, TLoaderDeps extends Record<string, any> = {}, TLoaderFn = undefined, TSSR = unknown, TServerMiddlewares = unknown>(options?: RootRouteOptions<TRegister, TSearchValidator, TRouterContext, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, TSSR, TServerMiddlewares>) => RootRoute<TRegister, TSearchValidator, TRouterContext, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, unknown, unknown, TSSR, TServerMiddlewares, undefined>createRootRouteWithContext } from "@tanstack/react-router";
import type { class IssueApiIssueApi } from "~/issues.ts";
export const const Route: RootRoute<Register, undefined, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>
Route = createRootRouteWithContext<{
queryClient: QueryClient<IssueApi>;
}>(): <TRegister, TRouteContextFn, TBeforeLoadFn, TSearchValidator, TLoaderDeps, TLoaderFn, TSSR, TServerMiddlewares>(options?: RootRouteOptions<TRegister, TSearchValidator, {
queryClient: QueryClient<IssueApi>;
}, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, TSSR, TServerMiddlewares, undefined> | undefined) => RootRoute<...>
createRootRouteWithContext<{
queryClient: QueryClient<IssueApi>queryClient: class QueryClient<TServices = never>QueryClient<class IssueApiIssueApi>;
}>()({});The route
Loaders use the same Promise verbs as a non-Effect app. Prefetch is sync; ensure returns a Promise. A loader that needs a typed catch is an Effect.fn, like a form action: it yield*s the QueryCache service, starts its reads together with Effect.all, and catches what it expects. runPromise runs it with the layer's services — the same shape as Effect's ManagedRuntime:
import { import QueryCacheQueryCache, type interface QueryHandle<TFactory extends QueryFactory = (input: unknown) => FactoryResult>QueryHandle, function useQuery<TFactory extends QueryFactory, const TThrow extends boolean | undefined = undefined>(factory: TFactory, handle: QueryHandleWithData<TFactory>, options?: UseQueryOptions & UseQueryThrowOptions<TFactory> & {
readonly throwOnError?: TThrow;
}): UseQueryResultWithData<TFactory, TThrow> (+1 overload)
useQuery } from "@raf-query/react";
import { function createFileRoute<TFilePath extends keyof FileRoutesByPath, TParentRoute extends AnyRoute = FileRoutesByPath[TFilePath]["parentRoute"], TId extends RouteConstraints["TId"] = FileRoutesByPath[TFilePath]["id"], TPath extends RouteConstraints["TPath"] = FileRoutesByPath[TFilePath]["path"], TFullPath extends RouteConstraints["TFullPath"] = FileRoutesByPath[TFilePath]["fullPath"]>(path?: TFilePath): FileRoute<TFilePath, TParentRoute, TId, TPath, TFullPath>["createRoute"]createFileRoute, function notFound(options?: NotFoundError): NotFoundErrornotFound } from "@tanstack/react-router";
import { import EffectEffect, import ResultResult } from "effect";
import { const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense, function use<T>(usable: React.Usable<T>): Tuse } from "react";
import { function RenameForm(props: {
issue: Issue;
}): React.JSX.Element
RenameForm } from "~/components/rename-form.tsx";
import { function assigneeQuery(input: {
issueId: number;
}): QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
assigneeQuery, function issueQuery(input: {
id: number;
}): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
issueQuery } from "~/queries.ts";
const const loadIssue: (id: number) => Effect.Effect<{
assignee: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
issue: QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
}, NotFoundError | ... 1 more ... | CancelledError, IssueApi | QueryCache.QueryCache>
loadIssue = import EffectEffect.const fn: (name: string, options?: SpanOptionsNoTrace) => Effect.fn.Traced (+63 overloads)fn("loadIssue")(
function* (id: numberid: number) {
const const cache: QueryCache.QueryCachecache = yield* import QueryCacheQueryCache.const QueryCache: Service<QueryCache.QueryCache, QueryCache.QueryCache>QueryCache;
return yield* import EffectEffect.const all: <{
readonly assignee: Effect.Effect<QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>, never, IssueApi>;
readonly issue: Loaded<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
}, {
...;
}>(arg: {
readonly assignee: Effect.Effect<QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>, never, IssueApi>;
readonly issue: Loaded<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
}, options?: {
...;
} | undefined) => Effect.Effect<...>
all(
{
assignee: Effect.Effect<QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>, never, IssueApi>
assignee: const cache: QueryCache.QueryCachecache.QueryCache.prefetch<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>(factory: (input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>, input: {
issueId: number;
}): Effect.Effect<QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>, never, IssueApi>
prefetch(function assigneeQuery(input: {
issueId: number;
}): QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
assigneeQuery, { issueId: numberissueId: id: numberid }),
issue: Loaded<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
issue: const cache: QueryCache.QueryCachecache.QueryCache.ensure<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>(factory: (input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>, input: {
id: number;
}): Loaded<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>> (+1 overload)
ensure(function issueQuery(input: {
id: number;
}): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
issueQuery, { id: numberid }),
},
{ concurrency: "unbounded"concurrency: "unbounded" },
);
},
import EffectEffect.const catchTag: <"IssueNotFound", IssueNotFound | Unauthorized | CancelledError, never, NotFoundError, never, unassigned, never, never>(k: "IssueNotFound", f: (e: IssueNotFound) => Effect.Effect<never, NotFoundError, never>, orElse?: ((e: Unauthorized | CancelledError) => Effect.Effect<unassigned, never, never>) | undefined) => <A, R>(self: Effect.Effect<A, IssueNotFound | Unauthorized | CancelledError, R>) => Effect.Effect<...> (+1 overload)catchTag("IssueNotFound", () => import EffectEffect.const fail: <NotFoundError>(error: NotFoundError) => Effect.Effect<never, NotFoundError, never>fail(function notFound(options?: NotFoundError): NotFoundErrornotFound())),
);
export const const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/issues/$issueId", "/issues/$issueId", "/issues/$issueId", "/issues/$issueId", undefined, ResolveParams<"/issues/$issueId">, AnyContext, AnyContext, AnyContext, {}, (args: LoaderFnContext<Register, RootRoute<Register, undefined, ... 9 more ..., undefined>, ... 7 more ..., undefined>) => Promise<...>, ... 4 more ..., undefined>
Route = createFileRoute<"/issues/$issueId", RootRoute<Register, undefined, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/issues/$issueId", "/issues/$issueId", "/issues/$issueId">(path?: "/issues/$issueId" | undefined): <TRegister, TSearchValidator, TParams, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, TChildren, TSSR, TMiddlewares, THandlers>(options?: (ParamsOptions<...> & ... 1 more ... & UpdatableRouteOptions<...>) | undefined) => Route<...>
createFileRoute("/issues/$issueId")({
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient<IssueApi>; }, AnyContext, ... 7 more ..., undefined>, ... 12 more ..., undefined>.loader?: Constrain<(args: LoaderFnContext<Register, RootRoute<Register, undefined, ... 9 more ..., undefined>, ... 7 more ..., undefined>) => Promise<...>, RouteLoaderFn<...> | RouteLoaderObject<...>> | undefinedloader: (args: LoaderFnContext<Register, RootRoute<Register, undefined, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/issues/$issueId", ResolveParams<"/issues/$issueId">, {}, AnyContext, AnyContext, AnyContext, unknown, undefined>
args) =>
args: LoaderFnContext<Register, RootRoute<Register, undefined, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/issues/$issueId", ResolveParams<"/issues/$issueId">, {}, AnyContext, AnyContext, AnyContext, unknown, undefined>
args.LoaderFnContext<Register, RootRoute<Register, undefined, { queryClient: QueryClient<IssueApi>; }, AnyContext, ... 7 more ..., undefined>, ... 7 more ..., undefined>.context: {
queryClient: QueryClient<IssueApi>;
}
context.queryClient.QueryClient<IssueApi>.runPromise<{
assignee: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
issue: QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
}, NotFoundError | ... 1 more ... | CancelledError>(effect: Effect.Effect<...>): Promise<...>
runPromise(const loadIssue: (id: number) => Effect.Effect<{
assignee: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
issue: QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
}, NotFoundError | ... 1 more ... | CancelledError, IssueApi | QueryCache.QueryCache>
loadIssue(var Number: NumberConstructor
(value?: any) => number
Number(args: LoaderFnContext<Register, RootRoute<Register, undefined, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/issues/$issueId", ResolveParams<"/issues/$issueId">, {}, AnyContext, AnyContext, AnyContext, unknown, undefined>
args.LoaderFnContext<Register, RootRoute<Register, undefined, { queryClient: QueryClient<IssueApi>; }, AnyContext, ... 7 more ..., undefined>, ... 7 more ..., undefined>.params: {
issueId: string;
}
params.issueId: stringissueId))), UpdatableRouteOptionsExtensions.component?: RouteComponent | undefinedcomponent: function IssuePage(): React.JSX.ElementIssuePage,
UpdatableRouteOptionsExtensions.errorComponent?: false | ErrorRouteComponent | null | undefinederrorComponent: (props: ErrorComponentPropsprops) => (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div React.HTMLAttributes<HTMLDivElement>.role?: React.AriaRole | undefinedrole="alert">
<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>Couldn't load the issue.</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
<React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={props: ErrorComponentPropsprops.reset: () => voidreset}>Try again</React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
),
});
function function IssuePage(): React.JSX.ElementIssuePage() {
const const loaderData: {
assignee: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
issue: QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
}
loaderData = const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/issues/$issueId", "/issues/$issueId", "/issues/$issueId", "/issues/$issueId", undefined, ResolveParams<"/issues/$issueId">, AnyContext, AnyContext, AnyContext, {}, (args: LoaderFnContext<Register, RootRoute<Register, undefined, ... 9 more ..., undefined>, ... 7 more ..., undefined>) => Promise<...>, ... 4 more ..., undefined>
Route.RouteExtensions<"/issues/$issueId", "/issues/$issueId">.useLoaderData: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>, unknown, boolean>(opts?: (UseLoaderDataBaseOptions<RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, ... 8 more ..., undefined>, "never", false, RouterHistory, Record<...>>, "/issues/$issueId", true, unknown, boolean> & OptionalStructuralSharing<...>) | undefined) => {
...;
}
useLoaderData();
// Loader handled IssueNotFound; throw any other failure to errorComponent.
const [const issue: Issueissue] = useQuery<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>, true>(factory: (input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>, handle: QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>, options?: (Options & ... 1 more ... & {
...;
}) | undefined): readonly [data: ...] (+1 overload)
useQuery(function issueQuery(input: {
id: number;
}): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
issueQuery, const loaderData: {
assignee: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
issue: QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
}
loaderData.issue: QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
issue, {
throwOnError?: true | undefinedthrowOnError: true,
});
return (
<React.JSX.IntrinsicElements.article: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>article>
<React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1>{const issue: Issueissue.Issue.title: stringtitle}</React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1>
<function RenameForm(props: {
issue: Issue;
}): React.JSX.Element
RenameForm issue: Issueissue={const issue: Issueissue} />
<const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense React.SuspenseProps.fallback?: React.ReactNodefallback={<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>Loading assignee…</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>}>
<function Assignee(props: {
handle: QueryHandle<typeof assigneeQuery>;
}): React.JSX.Element
Assignee handle: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
handle={const loaderData: {
assignee: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
issue: QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>;
}
loaderData.assignee: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
assignee} />
</const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense>
</React.JSX.IntrinsicElements.article: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>article>
);
}
function function Assignee(props: {
handle: QueryHandle<typeof assigneeQuery>;
}): React.JSX.Element
Assignee(props: {
handle: QueryHandle<typeof assigneeQuery>;
}
props: { handle: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
handle: interface QueryHandle<TFactory extends QueryFactory = (input: unknown) => FactoryResult>QueryHandle<typeof function assigneeQuery(input: {
issueId: number;
}): QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
assigneeQuery> }) {
const [const assigneePromise: Thenable<Result.Result<User, IssueNotFound | Unauthorized>>assigneePromise, const controls: QueryControls<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
controls] = useQuery<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>, undefined>(factory: (input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>, handle: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>, options?: (Options & ... 1 more ... & {
...;
}) | undefined): readonly [promise: ...] (+1 overload)
useQuery(function assigneeQuery(input: {
issueId: number;
}): QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
assigneeQuery, props: {
handle: QueryHandle<typeof assigneeQuery>;
}
props.handle: QueryHandle<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
handle);
const assignee = use<Result.Result<User, IssueNotFound | Unauthorized>>(usable: React.Usable<Result.Result<User, IssueNotFound | Unauthorized>>): Result.Result<User, IssueNotFound | Unauthorized>use(const assigneePromise: Thenable<Result.Result<User, IssueNotFound | Unauthorized>>assigneePromise); return import ResultResult.const match: <User, IssueNotFound | Unauthorized, React.JSX.Element, React.JSX.Element>(self: Result.Result<User, IssueNotFound | Unauthorized>, options: {
readonly onFailure: (error: IssueNotFound | Unauthorized) => React.JSX.Element;
readonly onSuccess: (ok: User) => React.JSX.Element;
}) => React.JSX.Element (+1 overload)
match(const assignee: Result.Result<User, IssueNotFound | Unauthorized>assignee, {
onFailure: (error: IssueNotFound | Unauthorized) => React.JSX.ElementonFailure: (error: IssueNotFound | Unauthorizederror) =>
error: IssueNotFound | Unauthorizederror._tag: "IssueNotFound" | "Unauthorized"_tag === "IssueNotFound" ? (
<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>Unassigned</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
) : (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div React.HTMLAttributes<HTMLDivElement>.role?: React.AriaRole | undefinedrole="alert">
<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>Couldn't load the assignee.</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
<React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={() => void const controls: QueryControls<(input: {
issueId: number;
}) => QueryOptions<Flat<{
data: User;
key: readonly ["issue", number, "assignee"];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
controls.QueryControls<(input: { issueId: number; }) => QueryOptions<Flat<{ data: User; key: readonly ["issue", number, "assignee"]; } & { error: IssueNotFound | Unauthorized; } & { ...; }>>>.refetch(): Promise<void>refetch()}>Try again</React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
),
onSuccess: (ok: User) => React.JSX.ElementonSuccess: (user: Useruser) => <React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>{user: Useruser.User.name: stringname}</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>,
});
}With a layer, the client defaults to throwOnError: false: Effect useQuery returns a Result so you can match failures inline (Typed errors). Pass throwOnError: true on the call, or set it in the query's options, when you want a rejecting promise instead.
A list page that only prefetches never needs runPromise:
loader: (args) => {
const qc = args.context.queryClient;
return {
counts: qc.prefetch(issueCountsQuery),
issues: qc.prefetch(issueListQuery, args.deps),
};
},What the loader doesn't catch, like Unauthorized, rejects into errorComponent. The components never see the services: the cache runs each queryFn with the layer's, whoever starts it.
A form action
The action is an Effect.fn that renames and then waits for the refetched issue. It catches every failure rename declares into a message, so the Effect can't fail, and the form shows the message:
import { import QueryCacheQueryCache } from "@raf-query/react";
import { function getRouteApi<const TId, TRouter extends AnyRouter = RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>>(id: ConstrainLiteral<TId, RouteIds<TRouter["routeTree"]>>): RouteApi<TId, TRouter>
getRouteApi } from "@tanstack/react-router";
import { import EffectEffect } from "effect";
import { function useActionState<State>(action: (state: Awaited<State>) => State | Promise<State>, initialState: Awaited<State>, permalink?: string): [state: Awaited<State>, dispatch: () => void, isPending: boolean] (+1 overload)useActionState } from "react";
import { type Issue, class IssueApiIssueApi } from "~/issues.ts";
const const Route: RouteApi<"/issues/$issueId", RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>>
Route = getRouteApi<"/issues/$issueId", RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>>(id: ConstrainLiteral<"/issues/$issueId", "__root__" | "/issues/$issueId">): RouteApi<...>
getRouteApi("/issues/$issueId");
const const rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<string | null, never, IssueApi | QueryCache.QueryCache>
rename = import EffectEffect.const fn: (name: string, options?: SpanOptionsNoTrace) => Effect.fn.Traced (+63 overloads)fn("rename")(
function* (input: {
readonly id: number;
readonly title: string;
}
input: { readonly id: numberid: number; readonly title: stringtitle: string }) {
const const api: {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect.Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>;
}
api = yield* class IssueApiIssueApi;
const const cache: QueryCache.QueryCachecache = yield* import QueryCacheQueryCache.const QueryCache: Service<QueryCache.QueryCache, QueryCache.QueryCache>QueryCache;
yield* const api: {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect.Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>;
}
api.rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>
rename(input: {
readonly id: number;
readonly title: string;
}
input);
yield* const cache: QueryCache.QueryCachecache.QueryCache.invalidate<QueryFactory>(...target: QueryCache.Target<QueryFactory>): Effect.Effect<void>invalidate({ Filter.queryKey?: QueryKey | undefinedqueryKey: ["issue", input: {
readonly id: number;
readonly title: string;
}
input.id: numberid] });
return null;
},
import EffectEffect.const catchTags: <Unauthorized | EmptyTitle, {
EmptyTitle: () => Effect.Effect<string, never, never>;
Unauthorized: () => Effect.Effect<string, never, never>;
}, unassigned, never, never>(cases: {
EmptyTitle: () => Effect.Effect<string, never, never>;
Unauthorized: () => Effect.Effect<string, never, never>;
}, orElse?: ((e: never) => Effect.Effect<unassigned, never, never>) | undefined) => <A, R>(self: Effect.Effect<A, Unauthorized | EmptyTitle, R>) => Effect.Effect<...> (+1 overload)
catchTags({
type EmptyTitle: () => Effect.Effect<string, never, never>EmptyTitle: () => import EffectEffect.const succeed: <string>(value: string) => Effect.Effect<string, never, never>succeed("Title can't be empty"),
type Unauthorized: () => Effect.Effect<string, never, never>Unauthorized: () => import EffectEffect.const succeed: <string>(value: string) => Effect.Effect<string, never, never>succeed("Sign in to rename issues"),
}),
);
export function function RenameForm(props: {
issue: Issue;
}): React.JSX.Element
RenameForm(props: {
issue: Issue;
}
props: { issue: Issueissue: Issue }) {
const const queryClient: QueryClient<IssueApi>queryClient = const Route: RouteApi<"/issues/$issueId", RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>>
Route.RouteApi<"/issues/$issueId", RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, { queryClient: QueryClient<IssueApi>; }, ... 8 more ..., undefined>, "never", false, RouterHistory, Record<...>>>.useRouteContext: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>, unknown>(opts?: UseRouteContextBaseOptions<RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient<IssueApi>;
}, ... 8 more ..., undefined>, "never", false, RouterHistory, Record<...>>, "/issues/$issueId", true, unknown> | undefined) => {
...;
}
useRouteContext().queryClient: QueryClient<IssueApi>queryClient;
const [const message: string | nullmessage, const action: (payload: FormData) => voidaction, const isPending: booleanisPending] = useActionState<string | null, FormData>(action: (state: string | null, payload: FormData) => string | Promise<string | null> | null, initialState: string | null, permalink?: string): [state: string | null, dispatch: (payload: FormData) => void, isPending: boolean] (+1 overload)useActionState(
(_previous: string | null_previous: string | null, formData: FormDataformData: FormData) =>
const queryClient: QueryClient<IssueApi>queryClient.QueryClient<IssueApi>.runPromise<string | null, never>(effect: Effect.Effect<string | null, never, IssueApi | QueryCache.QueryCache>): Promise<string | null>runPromise(
const rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<string | null, never, IssueApi | QueryCache.QueryCache>
rename({ id: numberid: props: {
issue: Issue;
}
props.issue: Issueissue.Issue.id: numberid, title: stringtitle: var String: StringConstructor
(value?: any) => string
String(formData: FormDataformData.FormData.get(name: string): FormDataEntryValue | nullget("title")) }),
),
null,
);
return (
<React.JSX.IntrinsicElements.form: React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>form React.FormHTMLAttributes<HTMLFormElement>.action?: string | ((formData: FormData) => void | Promise<void>) | undefinedaction={const action: (payload: FormData) => voidaction}>
<React.JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>input React.HTMLAttributes<HTMLInputElement>.defaultValue?: string | number | readonly string[] | undefineddefaultValue={props: {
issue: Issue;
}
props.issue: Issueissue.Issue.title: stringtitle} React.InputHTMLAttributes<HTMLInputElement>.name?: string | undefinedname="title" />
<React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.ButtonHTMLAttributes<HTMLButtonElement>.disabled?: boolean | undefineddisabled={const isPending: booleanisPending}>Rename</React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
{const message: string | nullmessage === null ? null : <React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p React.HTMLAttributes<HTMLParagraphElement>.role?: React.AriaRole | undefinedrole="alert">{const message: stringmessage}</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>}
</React.JSX.IntrinsicElements.form: React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>form>
);
}The client comes from the router context, not useQueryClient(): that one is a QueryClient<unknown>, so it can't check the action's services (Services). Typed errors covers where else to catch failures, and what React sees when you don't.
Effect queryFns
What an Effect queryFn adds over a promise:
- Typed errors. The Effect's error type is the query's:
qc.effect.fetch(issueQuery, input)fails withIssueNotFound | Unauthorized | Query.CancelledError.ErrorOf<TFactory>names it; it'sunknownfor promises. See Typed errors. - Services. What the Effect requires,
ServicesOf<TFactory>, every verb that can start the query requires. See Services. - Defects aren't retried. A
queryFnthat throws instead of returning, or an Effect that dies, is a defect: it fails the request without retrying. - Interruption. Cancelling the request interrupts the fiber, so its finalizers run. No
AbortSignalneeded. - Schedules.
retryaccepts aScheduleas well as a count or predicate:
export function function issueQuery(input: {
id: number;
}): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
issueQuery(input: {
id: number;
}
input: { id: numberid: number }) {
return queryOptions<Issue, IssueNotFound | Unauthorized, IssueApi, readonly ["issue", number], undefined>(options: Omit<QueryOptions<{
data: Issue;
error: IssueNotFound | Unauthorized;
key: readonly ["issue", number];
services: IssueApi;
}>, "kind" | "throwOnError"> & ThrowOnErrorField<undefined>): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
queryOptions({
queryKey: readonly ["issue", number]queryKey: ["issue", input: {
id: number;
}
input.id: numberid],
function queryFn(context: QueryFunctionContext): QueryFnResult<{
data: Issue;
error: IssueNotFound | Unauthorized;
key: readonly ["issue", number];
services: IssueApi;
}>
queryFn: () => class IssueApiIssueApi.Service<IssueApi, { readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>; readonly assignee: (issueId: number) => Effect.Effect<User, IssueNotFound | Unauthorized>; readonly rename: (input: { readonly id: number; readonly title: string; }) => Effect.Effect<Issue, EmptyTitle | Unauthorized>; }>.use<Issue, IssueNotFound | Unauthorized, never>(f: (service: {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect.Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>;
}) => Effect.Effect<...>): Effect.Effect<...>
use((api: {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect.Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>;
}
api) => api: {
readonly get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>;
readonly assignee: (issueId: number) => Effect.Effect<User, IssueNotFound | Unauthorized>;
readonly rename: (input: {
readonly id: number;
readonly title: string;
}) => Effect.Effect<Issue, EmptyTitle | Unauthorized>;
}
api.get: (id: number) => Effect.Effect<Issue, IssueNotFound | Unauthorized>get(input: {
id: number;
}
input.id: numberid)),
retry?: RetryPolicy | undefinedretry: import ScheduleSchedule.const exponential: (base: Input, factor?: number) => Schedule.Schedule<Duration>exponential("200 millis").Pipeable.pipe<Schedule.Schedule<Duration, unknown, never, never>, Schedule.Schedule<Duration, unknown, never, never>>(this: Schedule.Schedule<Duration, unknown, never, never>, ab: (_: Schedule.Schedule<Duration, unknown, never, never>) => Schedule.Schedule<Duration, unknown, never, never>): Schedule.Schedule<Duration, unknown, never, never> (+21 overloads)pipe(import ScheduleSchedule.const upTo: (options: {
readonly duration?: Input | undefined;
readonly times?: number | undefined;
}) => <Output, Input, Error, Env>(self: Schedule.Schedule<Output, Input, Error, Env>) => Schedule.Schedule<Output, Input, Error, Env> (+1 overload)
upTo({ times?: number | undefinedtimes: 3 })),
});
}The cache as an Effect service
QueryClient is a Promise wrapper around a QueryCache service. queryClient.runPromise hands it to loaders and actions, as above, and qc.effect is the same service as a value. Effect code outside React builds its own with QueryCache.make(config) (scoped: closing the scope interrupts every request and timer) or QueryCache.layer(config):
import { import QueryCacheQueryCache } from "@raf-query/core";
import { import StreamStream } from "effect";
const const program: Effect.Effect<void, CancelledError | IssueNotFound | Unauthorized, QueryCache.QueryCache | IssueApi>program = import EffectEffect.const gen: <Effect.Effect<QueryCache.QueryCache, never, QueryCache.QueryCache> | Effect.Effect<QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>, CancelledError | IssueNotFound | Unauthorized, IssueApi> | Effect.Effect<void, never, IssueApi>, void>(f: () => Generator<...>) => Effect.Effect<...> (+1 overload)
gen(function* () {
const const cache: QueryCache.QueryCachecache = yield* import QueryCacheQueryCache.const QueryCache: Service<QueryCache.QueryCache, QueryCache.QueryCache>QueryCache;
const const issue: QueryHandleWithData<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
issue = yield* const cache: QueryCache.QueryCachecache.QueryCache.fetch<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>(factory: (input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>, input: {
id: number;
}): Loaded<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>> (+1 overload)
fetch(function issueQuery(input: {
id: number;
}): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
issueQuery, { id: numberid: 1 });
yield* const cache: QueryCache.QueryCachecache.QueryCache.watch<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>(factory: (input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>, input: {
id: number;
}): Stream.Stream<State<Issue>, never, IssueApi>
watch(function issueQuery(input: {
id: number;
}): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
issueQuery, { id: numberid: 1 }).Pipeable.pipe<Stream.Stream<State<Issue>, never, IssueApi>, Stream.Stream<string | undefined, never, IssueApi>, Effect.Effect<void, never, IssueApi>>(this: Stream.Stream<State<Issue>, never, IssueApi>, ab: (_: Stream.Stream<State<Issue>, never, IssueApi>) => Stream.Stream<string | undefined, never, IssueApi>, bc: (_: Stream.Stream<string | undefined, never, IssueApi>) => Effect.Effect<void, never, IssueApi>): Effect.Effect<...> (+21 overloads)pipe(
import StreamStream.const map: <State<Issue>, string | undefined>(f: (a: State<Issue>, i: number) => string | undefined) => <E, R>(self: Stream.Stream<State<Issue>, E, R>) => Stream.Stream<string | undefined, E, R> (+1 overload)map((state: State<Issue>state) => state: State<Issue>state.State<Issue>.data: Issue | undefineddata?.Issue.title: string | undefinedtitle),
import StreamStream.const runForEach: <string | undefined, void, never, never>(f: (a: string | undefined) => Effect.Effect<void, never, never>) => <E, R>(self: Stream.Stream<string | undefined, E, R>) => Effect.Effect<void, E, R> (+1 overload)runForEach((title: string | undefinedtitle) => import EffectEffect.const log: (...message: ReadonlyArray<any>) => Effect.Effect<void>log(title: string | undefinedtitle)),
);
});
import EffectEffect.const runFork: <void, CancelledError | IssueNotFound | Unauthorized>(effect: Effect.Effect<void, CancelledError | IssueNotFound | Unauthorized, never>, options?: Effect.RunOptions | undefined) => Fiber<void, CancelledError | IssueNotFound | Unauthorized>runFork(
const program: Effect.Effect<void, CancelledError | IssueNotFound | Unauthorized, QueryCache.QueryCache | IssueApi>program.Pipeable.pipe<Effect.Effect<void, CancelledError | IssueNotFound | Unauthorized, QueryCache.QueryCache | IssueApi>, Effect.Effect<void, CancelledError | IssueNotFound | Unauthorized, IssueApi>, Effect.Effect<void, CancelledError | IssueNotFound | Unauthorized, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, CancelledError | IssueNotFound | Unauthorized, QueryCache.QueryCache | IssueApi>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)pipe(
import EffectEffect.const provide: <QueryCache.QueryCache, never, never>(layer: Layer<QueryCache.QueryCache, never, never>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, QueryCache.QueryCache>> (+5 overloads)
provide(import QueryCacheQueryCache.const layer: (config?: QueryCache.Config) => Layer<QueryCache.QueryCache>layer()),
import EffectEffect.const provide: <IssueApi, never, never>(layer: Layer<IssueApi, never, never>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, IssueApi>> (+5 overloads)
provide(const IssueApiLive: Layer<IssueApi, never, never>IssueApiLive),
),
);The cache has every verb as an Effect: prefetch, ensure, fetch, invalidate, reset, remove, setData, getData (an Option), isFetching (a count), dehydrate and hydrate. watch is a Stream of an entry's state, and events a Stream of cache events.
watch counts as a reader while it runs, so invalidation and window focus refetch the entry. Outside React, cache.mount (Effect<void, never, Scope>) refetches read, stale entries on window focus and reconnect while its scope is open.
Entries and observers
Entries are a data type with module functions, like Effect's own modules:
import { import QueryQuery, import QueryCacheQueryCache } from "@raf-query/core";
const const program: Effect.Effect<Query.State<Issue>, IssueNotFound | Unauthorized | CancelledError, QueryCache.QueryCache | IssueApi>program = import EffectEffect.const gen: <Effect.Effect<QueryCache.QueryCache, never, QueryCache.QueryCache> | Effect.Effect<Query.Query<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>, never, IssueApi> | Effect.Effect<Issue, IssueNotFound | Unauthorized | CancelledError, IssueApi> | Effect.Effect<...>, Query.State<...>>(f: () => Generator<...>) => Effect.Effect<...> (+1 overload)
gen(function* () {
const const cache: QueryCache.QueryCachecache = yield* import QueryCacheQueryCache.const QueryCache: Service<QueryCache.QueryCache, QueryCache.QueryCache>QueryCache;
const const query: Query.Query<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
query = yield* const cache: QueryCache.QueryCachecache.QueryCache.getQuery<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>(factory: (input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>, input: {
id: number;
}): Effect.Effect<Query.Query<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>, never, IssueApi>
getQuery(function issueQuery(input: {
id: number;
}): QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>
issueQuery, { id: numberid: 1 });
yield* import QueryQuery.const fetch: <(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>(self: Query.Query<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>, request?: Query.FetchRequest) => Effect.Effect<Issue, IssueNotFound | ... 1 more ... | CancelledError, IssueApi> (+1 overload)
fetch(const query: Query.Query<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
query);
yield* import QueryQuery.function invalidate<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>(self: Query.Query<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>): Effect.Effect<void>
invalidate(const query: Query.Query<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
query);
return import QueryQuery.function getUnsafe<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>(self: Query.Query<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>): Query.State<Issue>
getUnsafe(const query: Query.Query<(input: {
id: number;
}) => QueryOptions<Flat<{
data: Issue;
key: readonly ["issue", number];
} & {
error: IssueNotFound | Unauthorized;
} & {
services: IssueApi;
}>>>
query); // synchronous read
});Query.changes(query) streams the entry's state, and Query.subscribeUnsafe(query, listener) is the synchronous version.
QueryObserver is one reader's view of a query, as useQuery sees it: the controls and the promise, with whilePending, refetchInterval and the focus and reconnect options. QueryObserver.make(query, options) creates one, and QueryObserver.changes(observer) streams its results. While it runs, the query counts as read and polls on refetchInterval. It's the building block for bindings to other frameworks.
SSR streaming
The SSR integrations are thin wrappers over Effect APIs:
dehydrateStream(options)(scoped) returns the cache'sinitialstate and aStreamofupdates: entries that start or settle later, each sent once, batched per microtask. The stream ends when the scope closes.hydrateStream(stream)hydrates each state as it arrives.Stream.toReadableStreamandStream.fromReadableStreamcarry it over a web stream.
The sent option (a Set of hashes) makes separate dehydrate calls of one request skip entries already sent.
Time and the page
Time comes from Effect's Clock, and the page from the Platform service (focus, visibility, navigator.onLine; the default reads the globals). Provide TestClock or another Platform to test staleness, garbage collection and offline retries without a browser. See Testing.