@myco-dev/sdk
v0.1.14
Published
TypeScript SDK for building apps on the Myco platform.
Readme
@myco/sdk
TypeScript SDK for building apps on the Myco platform.
Installation
npm install @myco/sdkPeer Dependencies
npm install react better-auth swrQuick Start
import { MycoSDK } from "@myco/sdk";
// Optional: import generated types for type-safe entity access
import type { EntityTypes } from "./types/myco.generated";
const myco = new MycoSDK<EntityTypes>("your-app-key");
// Fetch records - automatically waits for SDK to be ready
const { records } = await myco.data.getRecords("property");Client SDK
Initialization
import { MycoSDK } from "@myco/sdk";
const myco = new MycoSDK<EntityTypes>("your-app-key", {
// Optional: override the base URL
baseUrl: "https://api.myco.com",
// Optional: disable auto-redirect on unauthenticated (default: true)
redirectOnUnauth: false,
});
// Optional: await ready if you need to know when initialization completes
// All API calls automatically wait for ready internally
await myco.ready;The SDK automatically:
- Detects local vs production environment
- Handles workspace initialization via the
/api/app/joinendpoint - Persists workspace ID in localStorage
- Redirects to login if unauthenticated (configurable)
Auth Namespace
myco.auth.login(returnTo?)
Redirects to the federated login page.
// Redirect to login, return to current page after
myco.auth.login();
// Redirect to login, return to specific URL after
myco.auth.login("/dashboard");myco.auth.logout()
Signs out the current user and clears the session.
await myco.auth.logout();myco.auth.useSession()
React hook for session state.
function MyComponent() {
const { data: session, isPending, error } = myco.auth.useSession();
if (isPending) return <div>Loading...</div>;
if (!session) return <div>Not logged in</div>;
return <div>Hello, {session.user.name}</div>;
}myco.auth.getSession()
Get session data outside of React components.
const session = await myco.auth.getSession();Workspace Namespace
The workspace namespace handles initialization automatically — no manual ensure() needed.
myco.workspace.ready
Promise that resolves when the workspace is initialized. All other SDK namespaces wait for this internally.
await myco.workspace.ready;myco.workspace.list()
List all workspaces the user has access to.
const workspaces = await myco.workspace.list();myco.workspace.current()
Get the current workspace details.
const workspace = await myco.workspace.current();
console.log(workspace.name);myco.workspace.switch(workspaceId)
Switch to a different workspace.
myco.workspace.switch("ws_abc123");myco.workspace.update(data)
Update the current workspace.
await myco.workspace.update({ name: "Renamed Workspace" });myco.workspace.useWorkspace()
React hook for the current workspace.
function WorkspaceHeader() {
const { data: workspace, isPending, error } = myco.workspace.useWorkspace();
if (isPending) return <div>Loading...</div>;
return <h1>{workspace?.name}</h1>;
}myco.workspace.members.get()
Get members of the current workspace.
const members = await myco.workspace.members.get();myco.workspace.members.add(userId, role?)
Add a member to the current workspace.
await myco.workspace.members.add("user_123", "admin");
await myco.workspace.members.add("user_456"); // defaults to "member"myco.workspace.members.remove(userId)
Remove a member from the current workspace.
await myco.workspace.members.remove("user_123");Data Namespace
Entities
myco.data.getEntities()
Get all entities for the current app.
const entities = await myco.data.getEntities();myco.data.getEntity(entitySlug)
Get a single entity by slug.
const entity = await myco.data.getEntity("property");
console.log(entity.fields);Records (Async Methods)
myco.data.getRecords(entitySlug, options?)
Get paginated records for an entity.
const { records, pagination } = await myco.data.getRecords("property", {
page: 1,
pageSize: 20,
sort: "createdAt",
order: "desc",
filter: { status: "active" },
});myco.data.getRecord(entitySlug, recordId)
Get a single record by ID.
const record = await myco.data.getRecord("property", "rec_123");myco.data.createRecord(entitySlug, data)
Create a new record.
const record = await myco.data.createRecord("property", {
title: "Beach House",
price: 500000,
});myco.data.updateRecord(entitySlug, recordId, data)
Update an existing record.
const record = await myco.data.updateRecord("property", "rec_123", {
price: 550000,
});myco.data.deleteRecord(entitySlug, recordId)
Delete a record.
await myco.data.deleteRecord("property", "rec_123");myco.data.batchGetRecords(entitySlug, ids)
Batch get multiple records by IDs.
const records = await myco.data.batchGetRecords("property", [
"rec_123",
"rec_456",
]);Records (React Hooks)
myco.data.useRecords(entitySlug, options?)
SWR hook for fetching records with built-in pagination.
function PropertyList() {
const {
data: properties,
isLoading,
error,
// Pagination
page,
hasNextPage,
hasPreviousPage,
nextPage,
previousPage,
setPage,
} = myco.data.useRecords("property", {
pageSize: 10,
initialPage: 1,
sort: "createdAt",
order: "desc",
filter: { status: "active" },
// Called when page changes (for URL sync, analytics, etc.)
onPageChange: (page) => {
router.push(`?page=${page}`);
},
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{properties?.map((property) => (
<div key={property.id}>{property.data.title}</div>
))}
<button onClick={previousPage} disabled={!hasPreviousPage}>
Previous
</button>
<span>Page {page}</span>
<button onClick={nextPage} disabled={!hasNextPage}>
Next
</button>
</div>
);
}Options:
| Option | Type | Description |
|--------|------|-------------|
| initialPage | number | Starting page (default: 1) |
| pageSize | number | Records per page |
| sort | string | Field to sort by |
| order | "asc" \| "desc" | Sort direction |
| filter | object | Filter criteria |
| onPageChange | (page: number) => void | Callback when page changes |
Return value extends SWR response with:
| Property | Type | Description |
|----------|------|-------------|
| page | number | Current page number |
| hasNextPage | boolean | Whether there's a next page |
| hasPreviousPage | boolean | Whether there's a previous page |
| nextPage | () => void | Go to next page |
| previousPage | () => void | Go to previous page |
| setPage | (page: number) => void | Jump to specific page |
myco.data.useRecord(entitySlug, recordId)
SWR hook for fetching a single record.
function PropertyDetail({ id }: { id: string }) {
const { data: property, isLoading } = myco.data.useRecord("property", id);
if (isLoading) return <div>Loading...</div>;
return <div>{property?.data.title}</div>;
}Supports conditional fetching by passing null or undefined:
// Only fetch when selectedId is set
const { data } = myco.data.useRecord("property", selectedId ?? null);Server SDK
For server-side rendering and API routes (e.g., Remix loaders/actions).
import { createServerSDK } from "@myco/sdk/server";
export async function loader({ request }: LoaderArgs) {
const myco = createServerSDK(
{ MYCO_APP_KEY: process.env.MYCO_APP_KEY! },
request
);
const { user } = await myco.auth.getVerifiedUser();
if (!user) {
return redirect(myco.auth.getLoginUrl(request.url));
}
const { records } = await myco.data.getRecords("property");
return json({ records });
}Server Auth
myco.auth.getVerifiedUser()
Verify the JWT from cookies and return the user.
const { user, jwt } = await myco.auth.getVerifiedUser();
if (!user) {
// Not authenticated
}myco.auth.getLoginUrl(returnTo)
Get the federated login URL.
const loginUrl = myco.auth.getLoginUrl("https://myapp.com/dashboard");
return redirect(loginUrl);Server Workspace
Same API as client SDK (minus switch, useWorkspace, and ready):
myco.workspace.list()myco.workspace.current()myco.workspace.update(data)myco.workspace.members.get()myco.workspace.members.add(userId, role?)myco.workspace.members.remove(userId)
Server Data
Same API as client SDK (async methods only, no hooks):
myco.data.getEntities()myco.data.getEntity(entitySlug)myco.data.getRecords(entitySlug, options?)myco.data.getRecord(entitySlug, recordId)myco.data.createRecord(entitySlug, data)myco.data.updateRecord(entitySlug, recordId, data)myco.data.deleteRecord(entitySlug, recordId)myco.data.batchGetRecords(recordIds)
Server Fetch
For custom API calls:
const result = await myco.fetch<MyType>("/api/custom-endpoint", {
method: "POST",
body: JSON.stringify({ data }),
});Error Handling
The SDK exports an ApiError class for handling API errors:
import { ApiError } from "@myco/sdk";
try {
await myco.data.getRecord("property", "invalid-id");
} catch (error) {
if (error instanceof ApiError) {
console.log(error.status); // HTTP status code
console.log(error.body); // Response body
console.log(error.path); // Request path
}
}TypeScript
Generated Types
For type-safe entity access, generate types from your Myco schema:
// types/myco.generated.ts
export interface EntityTypes {
property: {
title: string;
price: number;
bedrooms: number;
status: "active" | "sold";
};
agent: {
name: string;
email: string;
};
}Then pass the type to MycoSDK:
import type { EntityTypes } from "./types/myco.generated";
const myco = new MycoSDK<EntityTypes>("your-app-key");
// Now fully typed!
const { records } = await myco.data.getRecords("property");
// records is TypedEntityRecord<{ title: string; price: number; ... }>[]Exported Types
import type {
MycoSDKConfig,
JoinResponse,
VerifiedUser,
Workspace,
WorkspaceMember,
Entity,
EntityField,
EntityRecord,
PaginatedResponse,
GetRecordsOptions,
TypedEntityRecord,
} from "@myco/sdk";