Entities and Data
Define schemas, store records, and query data with the SDK.
Overview
Myco data is organized into entities (schemas) and records (rows). You define entities via the CLI, and the SDK gives you hooks and methods to work with records in your app.
Entity lifecycle
Create an entity
myco entity create \
--name "Contact" \
--slug contact \
--description "A CRM contact" \
--schema '{"properties":{"name":{"type":"string"},"email":{"type":"string"},"company":{"type":"string"}}}'List entities
myco entity listView entity details
myco entity get contactUpdate an entity
myco entity update contact \
--schema '{"properties":{"name":{"type":"string"},"email":{"type":"string"},"company":{"type":"string"},"phone":{"type":"string"}}}'Regenerate types
After any schema change:
myco entity typegenSchema format
{
"properties": {
"title": { "type": "string" },
"amount": { "type": "number" },
"active": { "type": "boolean" }
}
}Supported types: string, number, boolean.
SDK hooks
All data hooks are accessed through the myco.data namespace.
useRecords — fetch a list
import { useMyco } from "@myco-dev/sdk";
import type { EntityTypes } from "@/types/myco.generated";
function ContactList() {
const myco = useMyco<EntityTypes>();
const {
data: contacts,
status,
error,
// Pagination
page,
hasNextPage,
hasPreviousPage,
nextPage,
previousPage,
setPage,
// Cache mutation
mutate,
mutateOptimistic,
} = myco.data.useRecords("contact", {
pageSize: 20,
sort: "createdAt",
order: "desc",
});
if (status === "pending") return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<>
{contacts?.map(c => <div key={c._id}>{c.name}</div>)}
<button onClick={previousPage} disabled={!hasPreviousPage}>Previous</button>
<span>Page {page}</span>
<button onClick={nextPage} disabled={!hasNextPage}>Next</button>
</>
);
}Options
| Option | Type | Default | Description |
|---|---|---|---|
pageSize | number | 20 | Records per page |
initialPage | number | 1 | Starting page |
sort | string | — | Field to sort by |
order | "asc" | "desc" | — | Sort direction |
filter | Record<string, unknown> | — | Filter criteria |
onPageChange | (page: number) => void | — | Callback when page changes |
useRecord — fetch a single record
const { data: contact, status } = myco.data.useRecord("contact", contactId);Pass null or undefined to disable fetching:
const { data } = myco.data.useRecord("contact", selectedId ?? null);Mutations
Use React Query’s useMutation for write operations:
import { useMutation } from "@tanstack/react-query";
function CreateContactButton() {
const myco = useMyco<EntityTypes>();
const create = useMutation({
mutationFn: (data: Partial<Contact>) =>
myco.data.createRecord("contact", data),
onSuccess: () =>
myco.queryClient.invalidateQueries({ queryKey: ["records", "contact"] }),
});
return (
<button onClick={() => create.mutate({ name: "Jane", email: "jane@co.com" })}>
Add Contact
</button>
);
}Optimistic updates
The mutate and mutateOptimistic functions on useRecords and useRecord let you update the cache directly:
const { data: contacts, mutateOptimistic } = myco.data.useRecords("contact");
// Optimistic delete with automatic rollback on error
async function handleDelete(id: string) {
await mutateOptimistic(
(current) => (current ?? []).filter(c => c._id !== id),
() => myco.data.deleteRecord("contact", id)
);
}Direct API calls
When hooks can’t be used (event handlers, utilities), use the async methods:
// CRUD methods
await myco.data.getRecords("contact", { page: 1, pageSize: 50 });
await myco.data.getRecord("contact", recordId);
await myco.data.createRecord("contact", { name: "Jane" });
await myco.data.updateRecord("contact", recordId, { company: "Acme" });
await myco.data.deleteRecord("contact", recordId);
// Batch fetch
await myco.data.batchGetRecords("contact", [id1, id2, id3]);
// Entity metadata
await myco.data.getEntities();
await myco.data.getEntity("contact");Record metadata
Every record returned by the SDK includes metadata fields:
| Field | Type | Description |
|---|---|---|
_id | string | Unique record ID |
_createdAt | string | ISO timestamp |
_updatedAt | string | ISO timestamp |
These are added by the RecordEntry<T> wrapper type. Your entity fields (like name, email) sit alongside them.