Skip to Content
GuidesEntities and Data

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 list

View entity details

myco entity get contact

Update 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 typegen

Schema 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

OptionTypeDefaultDescription
pageSizenumber20Records per page
initialPagenumber1Starting page
sortstringField to sort by
order"asc" | "desc"Sort direction
filterRecord<string, unknown>Filter criteria
onPageChange(page: number) => voidCallback 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:

FieldTypeDescription
_idstringUnique record ID
_createdAtstringISO timestamp
_updatedAtstringISO timestamp

These are added by the RecordEntry<T> wrapper type. Your entity fields (like name, email) sit alongside them.