directus-sdk-live
v0.1.0
Published
Experimental realtime query client for Directus with live subscriptions, caching, and framework integrations
Readme
directus-sdk-live
Experimental — This package is under active development and not yet stable. APIs may change without notice. Use at your own risk.
Realtime query client for Directus with live subscriptions, caching, and framework integrations.
Install
pnpm add directus-sdk-live @directus/sdkQuick Start
Create a typed client:
import { createLiveClient } from "directus-sdk-live";
const client = createLiveClient<MySchema>("http://localhost:8055", {
auth: { mode: "session" },
});Use it in a React component — data updates automatically when records change in Directus:
import { useLiveQuery } from "directus-sdk-live/react";
function Posts() {
const { data, isPending, error } = useLiveQuery(client.posts, {
fields: ["id", "title", "status"],
filter: { status: { _eq: "published" } },
sort: ["-date_created"],
limit: 10,
});
if (isPending) return <p>Loading...</p>;
return data?.map((post) => <div key={post.id}>{post.title}</div>);
}Or in Vue / Nuxt:
<script setup>
const client = useDirectus();
const { data, isPending } = useLiveQuery(client.posts, {
fields: ["id", "title"],
sort: ["-date_created"],
limit: 10,
});
</script>Don't need live updates? Use useQuery instead — same API, REST only, no WebSocket overhead.
Authentication
Four auth modes: session, cookie, memory, static. See Auth Modes for details.
// Login / logout / refresh
await client.auth.login({ email: "[email protected]", password: "secret" });
await client.auth.refresh();
await client.auth.logout();
// Current user — supports field selection
const user = await client.auth.me();
const userWithRole = await client.auth.me({ fields: ["id", "email", "role.*"] });me() results are cached for 30 seconds per unique query. Cache clears on login, logout, and token changes.
useLiveQuery
Subscribe to real-time collection changes via WebSocket. When records are created, updated, or deleted, the result set updates automatically.
React:
import { useLiveQuery } from "directus-sdk-live/react";
const { data, isPending, error, refresh, liveActive } = useLiveQuery(client.posts, {
fields: ["id", "title", "status"],
filter: { status: { _eq: "published" } },
sort: ["-date_created"],
limit: 10,
});Vue:
import { useLiveQuery } from "directus-sdk-live/vue";
const { data, isPending, error, isReady, isEmpty, status, refresh } = useLiveQuery(client.posts, {
fields: ["id", "title"],
limit: 10,
});Options:
| Option | Type | Default | Description |
| --------- | --------- | ------- | ------------------------------------------- |
| enabled | boolean | true | Disable the query without removing the hook |
| live | boolean | true | Set false for REST-only (no WebSocket) |
Return values:
| Field | Description |
| ------------ | -------------------------------------------- |
| data | The current result set |
| isPending | true during initial fetch |
| error | Error object if the query failed |
| refresh() | Manually re-fetch |
| liveActive | true when WebSocket subscription is active |
| meta | Query metadata (total count, etc.) |
Singletons work the same way:
const { data: settings } = useLiveQuery(client.directus_settings);useQuery
Same API as useLiveQuery but without WebSocket subscriptions. Use when you don't need live updates.
React:
import { useQuery } from "directus-sdk-live/react";
const { data, isPending, error, refresh } = useQuery(client.posts, {
fields: ["id", "title"],
limit: 10,
});Vue:
import { useQuery } from "directus-sdk-live/vue";
const { data, isPending, error, isReady, isEmpty, status, refresh } = useQuery(client.posts, {
fields: ["id", "title"],
limit: 10,
});Queries re-fetch automatically when the query object changes. Call refresh() to manually re-fetch.
SSR Prefetch (React / Next.js)
Prefetch data on the server, then hydrate on the client without a double-fetch.
// Server component
import { createLiveClient, prefetchQuery, dehydrateClient } from "directus-sdk-live";
const client = createLiveClient<MySchema>("http://localhost:8055");
await prefetchQuery(() => client.posts.readItems({ fields: ["id", "title"] }));
const dehydratedState = dehydrateClient(client);
// Pass dehydratedState to LiveQueryProvider// Client component
import { LiveQueryProvider } from "directus-sdk-live/react";
<LiveQueryProvider client={client} dehydratedState={dehydratedState}>
<Posts />
</LiveQueryProvider>;prefetchQuery silently swallows auth errors (FORBIDDEN, UNAUTHORIZED, TOKEN_EXPIRED) so unauthenticated server renders don't crash.
React
import {
LiveQueryProvider,
useDirectus,
useDirectusAuth,
useLiveQuery,
useQuery,
useMutation,
} from "directus-sdk-live/react";
function App() {
return (
<LiveQueryProvider client={client}>
<Posts />
</LiveQueryProvider>
);
}useDirectus() returns the typed client. useDirectusAuth() provides reactive auth state (authenticated | anonymous) plus login, logout, and refresh methods.
Vue
import { useLiveQuery, useQuery, useMutation } from "directus-sdk-live/vue";Vue composables accept reactive refs for query arguments — the query re-subscribes automatically when refs change.
Nuxt Module
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["directus-sdk-live/nuxt"],
directus: {
url: "http://localhost:8055",
auth: {
mode: "session",
middleware: "global",
loginRoute: "/login",
forbiddenRoute: "/",
},
devtools: true,
},
});Auto-imported composables
| Composable | Description |
| --------------------------- | ------------------------------------------------ |
| useDirectus() | Access the typed LiveClient |
| useDirectusAuth() | Auth state, login, logout, refresh |
| useDirectusUser(options?) | Reactive current user with SSR support |
| useLiveQuery(ref, query) | Reactive live query with WebSocket subscriptions |
| useQuery(ref, query) | Reactive query via REST (no WebSocket) |
| useMutation(ref) | Create, update, delete mutations |
Auth Middleware
The module registers a directus-auth route middleware. Use it per-page or globally.
Per-page:
<script setup>
definePageMeta({ middleware: "directus-auth" });
</script>Global mode — set auth.middleware: 'global' in the module config. Opt out per-page:
<script setup>
definePageMeta({ directusAuth: false });
</script>Role / policy validation:
<script setup>
definePageMeta({
middleware: "directus-auth",
directusAuth: {
fields: ["id", "role.*"],
validate: (user) => user.role?.name === "Administrator",
},
});
</script>useDirectusUser
Reactive wrapper around client.auth.me() with SSR hydration via useAsyncData.
<script setup>
const { user, loading, error, refresh } = useDirectusUser({
fields: ["id", "email", "first_name", "role.*", "policies.*"],
});
</script>In Nuxt, useQuery and useLiveQuery integrate with useAsyncData — data fetched on the server is transferred to the client without a double-fetch.
Codegen
Generate TypeScript types from your Directus schema:
npx directus-sdk-live generate --url http://localhost:8055 --token YOUR_TOKEN --output ./app/.generated/directus.tsFlags:
| Flag | Description |
| ----------------- | ----------------------------------------- |
| --url | Directus instance URL |
| --token | Admin access token |
| --output | Output file path (single-file mode) |
| --types-output | Types output path (split mode) |
| --client-output | Client factory output path (split mode) |
| --zod | Generate Zod schemas |
| --zod-output | Zod output path |
| --singleton | Mark collection as singleton (repeatable) |
| --watch | Watch for schema changes |
| --nuxt | Nuxt-compatible output |
| --env | Load env file for token/url |
Devtools
Visual debug panel showing subscriptions, cache state, and live events.
Standalone:
import { mountDevtools } from "directus-sdk-live/devtools";
const unmount = mountDevtools(client);Nuxt: Set devtools: true in the module config — panel mounts automatically in dev mode.
Development Hooks
Dev-only event bus for instrumentation (resolves to undefined in production):
import { __devBus } from "directus-sdk-live";
const stop = __devBus?.subscribe((event) => {
console.debug(event.type, event);
});Events: subscription:open, subscription:close, subscription:event, subscription:malformed, cache:update.
Docs
License
MIT
