Taruvi Refine frontend
Use @taruvi/refine-providers to connect Refine.dev admin UIs to Taruvi. This skill covers the six providers, how to wire resources, the meta vocabulary that unlocks Taruvi-specific features (populate, aggregate, graph, upsert), and the gotchas that aren't obvious from the package API alone.
This skill is the frontend layer. If you're provisioning backend resources, switch to taruvi-backend-provisioning. If you're writing Python for a Taruvi function body, switch to taruvi-functions.
Core principles
- Resource name = datatable name by default. Override with
meta.tableName when they must differ (e.g., resource: "active_users" queries tableName: "users" with a filter).
- Meta is the control surface. Refine's
meta object is how you reach Taruvi-specific features (populate, aggregate, graph, bucketName, idColumnName, upsert, deleteByFilter). Don't try to push these through filters or query params.
- Multiple providers, one client. Register all six providers against the same
Client instance. Select providers by dataProviderName in hooks.
- Auth is redirect-based. There is no credentials login in the default Refine flow.
authProvider.login() redirects to Taruvi's login endpoint.
- AccessControl batches. Permission checks are debounced 50ms via DataLoader. Tests that don't
await will flake.
Setup
import { Refine } from "@refinedev/core";
import { Client } from "@taruvi/sdk";
import {
dataProvider,
storageDataProvider,
appDataProvider,
userDataProvider,
authProvider,
accessControlProvider,
} from "@taruvi/refine-providers";
const client = new Client({
apiKey: process.env.REACT_APP_TARUVI_KEY!,
appSlug: process.env.REACT_APP_TARUVI_APP!,
apiUrl: process.env.REACT_APP_TARUVI_API_URL!,
});
export function App() {
return (
<Refine
dataProvider={{
default: dataProvider(client),
storage: storageDataProvider(client),
app: appDataProvider(client),
user: userDataProvider(client),
}}
authProvider={authProvider(client)}
accessControlProvider={accessControlProvider(client)}
resources={[
{ name: "posts", list: "/posts", create: "/posts/create", edit: "/posts/edit/:id" },
{ name: "authors" },
]}
>
{/* Routes, layouts, pages */}
</Refine>
);
}
The six providers
1. dataProvider (default)
CRUD against Taruvi datatables. Covers getList, getOne, getMany, create, createMany, update, updateMany, deleteOne, deleteMany, custom.
See [references/data-provider.md](references/data-provider.md) for full API.
Filter operators ([references/filter-operators.md](references/filter-operators.md)): 24 supported — eq, ne, lt/lte/gt/gte, in/nin, contains/containss (case-insensitive), startswith/endswith (plus s suffix for case-insensitive), negated forms (ncontains, nstartswith, etc.), between/nbetween, null/nnull.
2. storageDataProvider
Buckets and objects. Resource name = bucket name (or override via meta.bucketName).
getList — list files in bucket, supports size/date/prefix filters.
getOne — download file as Blob (default) or get metadata (meta.metadata: true).
create — upload files: values: { files: File[], paths?: string[], metadatas?: object[] }.
update — update file metadata.
deleteOne / deleteMany — delete by path.
See [references/provider-quickref.md](references/provider-quickref.md).
3. appDataProvider
App-level resources: roles, settings, secrets; plus useCustom for function and analytics execution.
Supported resources:
getList("roles") — list app roles.
getList("secrets") — batch fetch by meta.keys: string[].
getOne("settings") — app settings.
getOne("secrets", key) — single secret.
useCustom for execution:
meta.kind: "function" → executes url as function slug.
meta.kind: "analytics" → executes url as analytics query slug.
4. userDataProvider
Users, roles (per user), user-apps. resource: "users" with id: "me" fetches current user. resource: "roles" with meta.username fetches that user's roles. resource: "apps" with meta.username fetches accessible apps.
5. authProvider
Refine AuthProvider interface backed by Taruvi's OAuth redirect flow.
login(callbackUrl?) — redirects to Taruvi login endpoint. No credentials flow.
logout(callbackUrl?) — clears cached user, redirects.
check() — token presence check (local); server validation happens on API call failure.
register(callbackUrl?) — redirect to signup.
getIdentity() — current user, cached to _cachedUser.
getPermissions() — reuses _cachedUser to avoid a second call.
onError(error) — 401 → logout + redirect to /login; 403 → surface error only.
See [references/auth-access-control.md](references/auth-access-control.md).
6. accessControlProvider
Cerbos permission checks via Taruvi Policy. DataLoader batches checks within a 50ms window (configurable via options.batchDelayMs).
const { data: canCreate } = useCan({
resource: "posts",
action: "create",
});
Returns { can: boolean, reason?: string }. Falls back gracefully if user is unauthenticated.
The meta vocabulary
See [references/meta-options-cookbook.md](references/meta-options-cookbook.md) for the full list.
Quick reference:
| Meta field |
Purpose |
Example |
populate |
Expand FK relations |
"author,category" or "*" or ["author", "comments"] |
select |
Field projection (return only listed fields) |
["id", "total", "status"] |
headers |
Custom request headers |
{ "X-Request-ID": "..." } |
idColumnName |
Override default "id" PK |
"user_id" |
tableName |
Override resource → table mapping |
"users" (when resource is "active_users") |
bucketName |
Override resource → bucket mapping (storage) |
"user-uploads" |
aggregate |
Aggregate expressions |
["sum(total)", "count(*)"] |
groupBy |
Group-by fields |
["status", "category"] |
having |
Filters on aggregates |
[{field: "sum(total)", operator: "gte", value: 1000}] |
format |
Graph format |
"tree" \ |
"graph" |
include |
Graph direction |
"descendants" \ |
"ancestors" \ |
"both" |
depth |
Graph traversal depth |
3 |
graph_types |
Filter edge types in graph |
["parent_of", "related"] |
upsert |
Use upsert on create |
true |
deleteByFilter |
Delete matching filters |
true (plus meta.filters) |
allowedActions |
Request per-row permissions |
["update", "delete"] |
metadata |
Storage: return metadata vs blob |
true |
kind |
App provider: function vs analytics |
"function" \ |
"analytics" |
keys |
App provider secrets: which to fetch |
["STRIPEKEY", "APIURL"] |
username |
User provider: for roles/apps |
"alice" |
Common patterns
List with populate and filters
const { data, isLoading } = useList({
resource: "posts",
filters: [
{ field: "status", operator: "eq", value: "published" },
{ field: "author_id", operator: "in", value: [1, 2, 3] },
],
sorters: [{ field: "created_at", order: "desc" }],
pagination: { currentPage: 1, pageSize: 20 },
meta: {
populate: ["author", "category"],
},
});
Upsert on create
const { mutate } = useCreate();
mutate({
resource: "users",
values: { id: 42, email: "[email protected]" },
meta: { upsert: true },
});
Graph traversal
const { data } = useList({
resource: "categories",
meta: {
format: "tree",
include: "descendants",
depth: 3,
},
});
Aggregation
const { data } = useList({
resource: "orders",
meta: {
aggregate: ["sum(total)", "count(*)"],
groupBy: ["status"],
having: [{ field: "sum(total)", operator: "gte", value: 1000 }],
},
});
File upload
const { mutate } = useCreate();
mutate({
dataProviderName: "storage",
resource: "user-uploads",
values: {
files: [file1, file2],
paths: [`${userId}/avatar.png`, `${userId}/cover.png`],
metadatas: [{ kind: "avatar" }, { kind: "cover" }],
},
});
Execute a function from the UI
const { refetch: sendEmail } = useCustom({
dataProviderName: "app",
url: "send-welcome-email",
method: "post",
config: { payload: { user_id: userId } },
meta: { kind: "function" },
queryOptions: { enabled: false },
});
Access control on a button
const { data: canDelete } = useCan({
resource: "posts",
action: "delete",
params: { id: postId },
});
return canDelete?.can ? <DeleteButton id={postId} /> : null;
Gotchas
meta.idColumnName defaults to "id". Tables with a different PK must pass idColumnName on every hook that needs it (useOne, useUpdate, useDelete).
- Resource vs table name. Refine's resource name goes straight to Taruvi unless
meta.tableName overrides. If you name your resource active-users but the table is users, you'll get a 404.
_cachedUser staleness. After a role change, the cached user is stale until logout or page refresh. Force re-fetch via useGetIdentity with queryOptions: { refetchOnMount: true } if you need fresh permissions.
- 401 vs 403 onError semantics. 401 triggers logout + redirect; 403 does not. Handle 403 UX yourself (toast, error page).
authProvider.login() without callbackUrl goes to the default callback. Pass one if you want to land back on the originating page.
useCan fires for every row. A list of 100 rows calling useCan per row → 100 batched checks in one request via DataLoader, but that first render is 50ms delayed. Test with await in tests.
useCustom with meta.kind: "function" ignores method. Functions are always POST under the hood.
- Storage
getOne returns a Blob by default. Pass meta.metadata: true to get the file's metadata object instead.
- Graph format="tree" requires
hierarchy.enabled on the table. Graph format requires graph.enabled. See the backend-provisioning skill for schema setup.
user-invocable: false-style skills don't apply here — this is a client-side library, not an agent skill runtime. Don't confuse consumer app configuration with agent skill metadata.
Verification checklist
Before reporting a frontend feature as done:
When you get stuck
- Full API reference for the default provider: [references/data-provider.md](references/data-provider.md).
- Every filter operator: [references/filter-operators.md](references/filter-operators.md).
- All
meta options in context: [references/meta-options-cookbook.md](references/meta-options-cookbook.md).
- Auth + access control details: [references/auth-access-control.md](references/auth-access-control.md).
- Direct
@taruvi/sdk usage when providers don't fit: [references/sdk-primer.md](references/sdk-primer.md).
- Exported types and utility functions: [references/types-and-utilities.md](references/types-and-utilities.md).
- Validate Refine resources match Taruvi datatables:
node scripts/validate-resource-map.js (see script header for usage).