useDocyrusInventory
Shared app-utils inventory cache for Docyrus metadata (apps, data sources with fields, enums, views and forms, users, brands, preferences) plus a post-sign-in warm-up hook that drives a loading screen.
Installation
pnpm dlx @docyrus/cli add @docyrus/rn-hooks-use-docyrus-inventorypnpm add @docyrus/app-utils @docyrus/api-clientThe module exports:
getSharedDocyrusInventory(client): returns the process-wide sharedInventoryClientfor aRestApiClient, creating it on first use. Instances are memoized in aWeakMapkeyed by client.nullin givesnullout.useDocyrusInventoryLoader(options): headless hook that runsinventory.load()once per client after sign-in and reports progress, so the app can show a loading screen before the home screen.
Requires @docyrus/app-utils >= 0.20.0 (ships a React Native build; no Metro config needed).
Why this exists
createInventoryClient from @docyrus/app-utils caches the metadata a Docyrus app needs at startup: apps, data sources (with embedded views, forms, fields and field enums), users, brands and tenant preferences. The data sources come from one GET /v1/apps/data-sources?expand=views,forms,fields request.
The cache lives inside the returned client, so everything must share one instance. Then a single warm-up fills the cache, and every inventory-backed Docyrus hook reads from it instead of fetching again on mount. getSharedDocyrusInventory hands out that instance; the Docyrus hooks call it internally.
A new client identity (for example after a tenant switch that rebuilds the client) gets a fresh inventory. If you reuse the same client across a tenant switch, call inventory.refresh().
getSharedDocyrusInventory(client)
import { createDataSourceClient } from '@docyrus/app-utils';
import { getSharedDocyrusInventory } from '@/hooks/docyrus-native/use-docyrus-inventory';
const inventory = getSharedDocyrusInventory(apiClient);
const dataSources = createDataSourceClient(apiClient, { inventory });
// Served from the shared cache (fields, enums, views and forms embedded)
const contacts = await dataSources.getBySlug('crm', 'contacts');| Parameter | Type | Description |
|---|---|---|
client | RestApiClient | null | undefined | The authenticated API client. |
Returns: InventoryClient for a client, null when client is nullish. Overloaded so a non-null client returns a non-null InventoryClient type.
useDocyrusInventoryLoader(options)
Runs inventory.load() once as soon as enabled is true and a client exists. It is StrictMode-safe and re-runs automatically when the client identity changes. Failures do not throw: status becomes 'error' and reload() retries. The app still works on error, because inventory-backed reads fall back to the API.
import { ActivityIndicator, Text, View } from 'react-native';
import { useDocyrusAuth, useDocyrusClient } from '@docyrus/signin/react-native';
import { Button } from '@/components/docyrus-native/button';
import { Progress } from '@/components/docyrus-native/progress';
import { useDocyrusInventoryLoader } from '@/hooks/docyrus-native/use-docyrus-inventory';
const APP_ID = process.env.EXPO_PUBLIC_DOCYRUS_APP_ID;
// Keep array options stable (module scope or useMemo).
const CONFIG_APP_IDS = APP_ID ? [APP_ID] : [];
export function InventoryGate({ children }: { children: React.ReactNode }) {
const { status } = useDocyrusAuth();
const client = useDocyrusClient();
const { status: loadStatus, progress, reload } = useDocyrusInventoryLoader({
client,
enabled: status === 'authenticated',
configAppIds: CONFIG_APP_IDS
});
if (loadStatus === 'ready') return children;
return (
<View className="flex-1 items-center justify-center gap-4 p-6">
{loadStatus === 'error' ? (
<Button onPress={reload}>Retry</Button>
) : (
<>
<ActivityIndicator />
<Text className="text-muted-foreground">{progress?.message ?? 'Loading…'}</Text>
<Progress value={Math.round((progress?.ratio ?? 0) * 100)} />
</>
)}
</View>
);
}Options (UseDocyrusInventoryLoaderOptions)
| Option | Type | Default | Description |
|---|---|---|---|
client | RestApiClient | null | undefined | — | The authenticated API client. The loader waits until it is set. |
enabled | boolean | true | Gate the warm-up (e.g. only once authenticated). A non-null client is still required. |
include | InventoryLoadStepId[] | app-utils default: ['apps', 'data-sources', 'users', 'brands', 'preferences'] | Tenant-wide steps to warm. Drop 'users' when the user may lack the Users.Read.All scope. |
configAppIds | string[] | app-utils default | App ids whose config and user-config to warm. app-utils defaults to the VITE_APP_ID env var, which does not exist under Metro, so pass ids explicitly on native. [] skips config steps. |
force | boolean | app-utils default: true | Invalidate caches before loading so the warm-up makes fresh network calls. |
Result (DocyrusInventoryLoaderState)
| Field | Type | Description |
|---|---|---|
inventory | InventoryClient | null | The shared inventory for the current client. |
status | 'idle' | 'loading' | 'ready' | 'error' | Warm-up lifecycle. |
progress | InventoryLoadProgress | null | Latest progress event. progress.ratio (0 to 1) drives a determinate bar; progress.message is an English label. |
error | unknown | The load error when status === 'error'. |
result | InventoryLoadResult | null | The loaded payload once ready. |
isLoading | boolean | true while warming. |
isReady | boolean | true once warmed successfully. |
reload | () => void | Re-run the warm-up (Retry button, tenant switch). |
Type Exports
| Type | Description |
|---|---|
DocyrusInventoryLoadStatus | 'idle' | 'loading' | 'ready' | 'error'. |
UseDocyrusInventoryLoaderOptions | Options of useDocyrusInventoryLoader. |
DocyrusInventoryLoaderState | Return value of useDocyrusInventoryLoader. |
useDocyrusInstantMessageComposer
Wires the native InstantMessageComposer to the Docyrus SMS and WhatsApp messaging API.
useDocyrusKanban
Kanban board backed by a Docyrus data source. Groups records by an enum, user or date field, and saves a card's move to the API when it is dropped. Enum options marked final become drop zones.