@well-prado/blok-typed-sdk
v1.0.1
Published
Type-safe React SDK for Blok Framework with tRPC-like developer experience
Downloads
8
Maintainers
Readme
@blok-framework/typed-sdk
Type-safe React SDK for Blok Framework with tRPC-like developer experience
Features
- ✅ Perfect Type Inference - tRPC-like autocomplete and type safety
- ✅ React Query Integration - Built-in caching and state management
- ✅ Zero Configuration - Works seamlessly with generated types
- ✅ Three Hooks -
useWorkflow,useWorkflowQuery,useWorkflowMutation - ✅ Automatic Type Safety - Compile-time validation
- ✅ Developer Experience - Beautiful autocomplete in your IDE
Installation
npm install @blok-framework/typed-sdk @tanstack/react-queryQuick Start
1. Generate Types (Automatic)
Types are generated automatically when you run npm run dev or npm run build:
npm run dev # Types auto-generated!2. Use in Components
import { useAdminLogs } from "@/blok-types"; // Auto-generated hook!
function AdminLogsPage() {
const { data, loading, error, execute } = useAdminLogs();
// ^? AdminLogsOutput | null - Perfect types!
const fetchLogs = async () => {
await execute({
page: 1, // ✅ Autocomplete
limit: 50, // ✅ Type checked
sortBy: "date", // ✅ Enum suggestions
});
};
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{data?.logs.map((log) => (
<div key={log.id}>{log.message}</div>
))}
</div>
);
}API
useWorkflow
Basic hook for any workflow. Works for both queries and mutations.
import { useWorkflow } from "@blok-framework/typed-sdk";
const { data, loading, error, execute, reset } = useWorkflow<
InputType,
OutputType
>("workflow-key", {
method: "POST",
requiresAuth: true,
requiredRole: "ADMIN",
});
// Execute the workflow
await execute({
/* input data */
});
// Reset state
reset();useWorkflowQuery
React Query integration for GET-like workflows. Provides caching and automatic refetching.
import { useWorkflowQuery } from "@blok-framework/typed-sdk";
const { data, isLoading, error, refetch } = useWorkflowQuery<
InputType,
OutputType
>({
workflowKey: "get-users",
input: { page: 1, limit: 50 },
staleTime: 5 * 60 * 1000, // 5 minutes
refetchInterval: 30000, // Refetch every 30 seconds
});useWorkflowMutation
React Query integration for POST/PUT/DELETE workflows. Provides optimistic updates and cache invalidation.
import { useWorkflowMutation } from "@blok-framework/typed-sdk";
import { useQueryClient } from "@tanstack/react-query";
const queryClient = useQueryClient();
const { mutate, mutateAsync, isLoading } = useWorkflowMutation<
InputType,
OutputType
>({
workflowKey: "create-user",
workflowOptions: { method: "POST" },
onSuccess: (data) => {
// Invalidate and refetch
queryClient.invalidateQueries(["get-users"]);
},
onError: (error) => {
console.error("Failed:", error);
},
});
// Trigger mutation
mutate({ name: "John", email: "[email protected]" });
// Or use async version
await mutateAsync({ name: "John", email: "[email protected]" });Examples
Basic Query
import { useWorkflowQuery } from "@blok-framework/typed-sdk";
function UserList() {
const { data, isLoading } = useWorkflowQuery({
workflowKey: "get-users",
input: { limit: 10 },
});
if (isLoading) return <div>Loading...</div>;
return (
<ul>
{data?.users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}Mutation with Optimistic Updates
import { useWorkflowMutation } from "@blok-framework/typed-sdk";
import { useQueryClient } from "@tanstack/react-query";
function CreateUserForm() {
const queryClient = useQueryClient();
const { mutate, isLoading } = useWorkflowMutation({
workflowKey: "create-user",
onMutate: async (newUser) => {
// Cancel outgoing refetches
await queryClient.cancelQueries(["get-users"]);
// Snapshot previous value
const previous = queryClient.getQueryData(["get-users"]);
// Optimistically update
queryClient.setQueryData(["get-users"], (old: any) => ({
...old,
users: [...old.users, newUser],
}));
return { previous };
},
onError: (err, variables, context) => {
// Rollback on error
queryClient.setQueryData(["get-users"], context?.previous);
},
onSettled: () => {
// Refetch after error or success
queryClient.invalidateQueries(["get-users"]);
},
});
const handleSubmit = (data: any) => {
mutate(data);
};
return <form onSubmit={handleSubmit}>...</form>;
}Dependent Queries
import { useWorkflowQuery } from "@blok-framework/typed-sdk";
function UserProfile({ userId }: { userId: string }) {
// First query
const { data: user } = useWorkflowQuery({
workflowKey: "get-user",
input: { id: userId },
});
// Second query depends on first
const { data: posts } = useWorkflowQuery({
workflowKey: "get-user-posts",
input: { userId: user?.id },
enabled: !!user?.id, // Only run when user is loaded
});
return <div>...</div>;
}Pagination
import { useWorkflowQuery } from "@blok-framework/typed-sdk";
import { useState } from "react";
function PaginatedList() {
const [page, setPage] = useState(1);
const { data, isLoading } = useWorkflowQuery({
workflowKey: "get-users",
input: { page, limit: 10 },
keepPreviousData: true, // Keep old data while fetching new
});
return (
<div>
{data?.users.map((user) => (
<div key={user.id}>{user.name}</div>
))}
<button onClick={() => setPage((p) => p - 1)} disabled={page === 1}>
Previous
</button>
<button onClick={() => setPage((p) => p + 1)} disabled={!data?.hasNext}>
Next
</button>
</div>
);
}Type Safety
Compile-Time Validation
const { execute } = useWorkflow<AdminLogsInput, AdminLogsOutput>("admin-logs");
// ✅ Valid - TypeScript knows the shape
await execute({
page: 1,
limit: 50,
sortBy: "date",
});
// ❌ TypeScript Error: Type 'string' is not assignable to type 'number'
await execute({
page: "1", // Error!
limit: 50,
});
// ❌ TypeScript Error: Type "invalid" is not assignable to type 'date' | 'user' | 'action'
await execute({
page: 1,
sortBy: "invalid", // Error!
});Perfect Autocomplete
When you type data., your IDE shows:
- ✅ All available properties
- ✅ Correct types for each property
- ✅ JSDoc documentation
- ✅ Nested object structures
Configuration
Base URL
const { execute } = useWorkflow("my-workflow", {
baseUrl: "https://api.example.com",
});Custom Headers
const { execute } = useWorkflow("my-workflow", {
headers: {
"X-Custom-Header": "value",
},
});Authentication
The SDK automatically includes cookies for authentication:
const { execute } = useWorkflow("protected-workflow", {
requiresAuth: true,
requiredRole: "ADMIN",
});Best Practices
1. Use Query for GET operations
// ✅ Good - Uses caching
const { data } = useWorkflowQuery({ workflowKey: "get-users" });
// ❌ Avoid - No caching, manual refetching
const { data, execute } = useWorkflow("get-users");
useEffect(() => {
execute();
}, []);2. Use Mutation for POST/PUT/DELETE
// ✅ Good - Proper mutation handling
const { mutate } = useWorkflowMutation({ workflowKey: "create-user" });
// ❌ Avoid - Using query for mutations
const { refetch } = useWorkflowQuery({ workflowKey: "create-user" });3. Invalidate Queries After Mutations
const { mutate } = useWorkflowMutation({
workflowKey: "create-user",
onSuccess: () => {
queryClient.invalidateQueries(["get-users"]);
},
});4. Handle Loading and Error States
const { data, isLoading, error } = useWorkflowQuery({ ... });
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
if (!data) return null;
return <YourComponent data={data} />;License
MIT
