@fhir-dsl/tanstack-query
v1.2.2
Published
TanStack Query bindings for fhir-dsl: queryOptions(builder) + mutationOptions(factory).
Downloads
11
Maintainers
Readme
@fhir-dsl/tanstack-query
TanStack Query bindings for @fhir-dsl/*. Hands you typed queryOptions(builder) and mutationOptions(factory) helpers that wrap any fhir-dsl terminal builder — derive a stable queryKey from compile(), run the call via execute(), and propagate FhirDslError through the error channel.
import { useMutation, useQuery } from "@tanstack/react-query";
import { mutationOptions, queryOptions } from "@fhir-dsl/tanstack-query";
import { createClient } from "./fhir/r4"; // generated by fhir-gen
const fhir = createClient({ baseUrl: "https://hapi.fhir.org/baseR4" });
// Query — types and AbortSignal flow through automatically.
const result = useQuery(queryOptions(fhir.read("Patient", "123")));
// result.data: Patient | undefined
// result.error: FhirDslError | null
// Mutation — factory takes the per-call input.
const m = useMutation(mutationOptions((p: Patient) => fhir.update(p)));
m.mutate(updatedPatient);
// m.error: FhirDslError | null → switch (m.error?.kind) { case "core.request": ... }
// Bound mutation — no per-call input (e.g. delete by id).
const del = useMutation(mutationOptionsBound(fhir.delete("Patient", "123")));
del.mutate();Install
npm install @fhir-dsl/tanstack-query @tanstack/react-query@tanstack/react-query (and its @tanstack/query-core peer) is an optional peer dependency — install it alongside this package.
API
| Export | Returns | When |
|---|---|---|
| queryOptions(builder, overrides?) | TanStack UseQueryOptions | Wrap any compile() + execute() builder for useQuery / useSuspenseQuery / prefetchQuery / queryClient.fetchQuery. |
| mutationOptions(factory, overrides?) | TanStack UseMutationOptions | Wrap a per-input builder factory for useMutation. The factory is called fresh on every invocation. |
| mutationOptionsBound(builder, overrides?) | TanStack UseMutationOptions<TOutput, FhirDslError, void> | Wrap a builder that's already bound (e.g. fhir.delete("Patient", id)); the resulting mutation takes no input. |
| isFhirDslError | type guard | Re-exported from @fhir-dsl/utils so consumers don't need a second import for the standard catch pattern. |
queryKey derivation
The derived key is structurally stable and JSON-comparable — same compiled query, same key. It always begins with "fhir" so consumers can scope invalidation:
queryClient.invalidateQueries({ queryKey: ["fhir"] }); // every fhir-dsl query
queryClient.invalidateQueries({ queryKey: ["fhir", "GET", "Patient/123"] }); // one resourceThe key shape:
type FhirQueryKey = readonly [
"fhir",
string, // method (GET / POST / PUT / …)
string, // path ("Patient/123", "Observation", …)
ReadonlyArray<readonly [name: string, value: string | number, prefix?: string, modifier?: string]>,
];Params are sorted by name so query order doesn't break cache reuse.
Error channel
The error channel is typed as FhirDslError end-to-end. Pattern-match on kind instead of parsing .message:
const result = useQuery(queryOptions(fhir.read("Patient", id)));
if (result.error) {
switch (result.error.kind) {
case "core.request":
console.error(result.error.context.status, result.error.context.statusText);
break;
case "smart.auth":
// Redirect to login
break;
}
}See @fhir-dsl/utils for the full FhirDslError contract and the Result<T, E> toolkit.
Overrides
Both helpers accept an overrides object that's spread into the underlying TanStack options — pass any standard enabled, staleTime, refetchOnWindowFocus, onSuccess, etc.
useQuery(queryOptions(fhir.read("Patient", id), { staleTime: 30_000, enabled: id !== "" }));
useMutation(mutationOptions((p: Patient) => fhir.update(p), {
onSuccess: (next) => queryClient.setQueryData(["fhir", "GET", `Patient/${next.id}`], next),
onError: (err) => err.kind === "core.request" && toast.error(err.context.statusText),
}));