Quick start
Choose either the unified package or a standalone package for stricter dependency separation. Both options expose the same API.
Install
pnpm add omni-async
pnpm add omni-async @omni-async/react react
pnpm add omni-async @omni-async/vue @vue/runtime-core vue
pnpm add omni-async @omni-async/svelte svelte
pnpm add @omni-async/core
pnpm add @omni-async/react
pnpm add @omni-async/vue @vue/runtime-core
pnpm add @omni-async/svelte
The unified package installs only core by default. It exposes core from both omni-async and omni-async/core, plus framework subpaths such as omni-async/react when the corresponding optional adapter is installed. Standalone packages use the corresponding @omni-async/* import.
Create an operation
import { createAsync } from "@omni-async/core";
// Unified: import { createAsync } from "omni-async";
// Explicit equivalent: import { createAsync } from "omni-async/core";
const user = createAsync(
async ({ signal }, id: string) => {
const response = await fetch(`/api/users/${id}`, { signal: signal ?? undefined });
if (!response.ok) throw new Error("Unable to load user");
return response.json() as Promise<{ id: string; name: string }>;
},
{
abortable: true,
concurrency: "latest",
},
);
const unsubscribe = user.subscribe(() => {
console.log(user.getSnapshot());
});
await user.execute("42");
unsubscribe();
The snapshot moves from idle to loading, then to success or error. Calling reset() restores the initial snapshot; calling abort() invalidates active requests.
Use vanilla JavaScript
createAsync works without TypeScript or a UI framework. Subscribe to changes and update the DOM from the current snapshot:
import { createAsync } from "@omni-async/core";
const output = document.querySelector("#result");
const search = createAsync(async (_context, term) => {
const response = await fetch(`/api/search?q=${encodeURIComponent(term)}`);
if (!response.ok) throw new Error("Search failed");
return response.json();
});
const unsubscribe = search.subscribe(() => {
const { data, error, isLoading } = search.getSnapshot();
output.textContent = isLoading ? "Loading…" : error ? "Search failed" : JSON.stringify(data);
});
void search.execute("async").catch(() => {}); // The snapshot holds the error for display.
// Call unsubscribe() when the view is removed.
Use a framework adapter
The adapters use the same request rules but own their state through native reactive values. For example, React exposes a trigger and independent state fields:
import { useQuery } from "@omni-async/react";
// Unified equivalent: import { useQuery } from "omni-async/react";
function UserSearch() {
const user = useQuery(async (id: string) => {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error("Unable to load user");
return response.json() as Promise<{ name: string }>;
});
return (
<button disabled={user.loading} onClick={() => void user.trigger("42")}>
{user.data?.name ?? "Load user"}
</button>
);
}
See framework adapters for Vue and Svelte examples.