@latellu/atlas-sdk
v0.3.0
Published
Typed delivery + management client for Atlas CMS
Downloads
1,554
Maintainers
Readme
@latellu/atlas-sdk
Typed delivery + management client for Atlas CMS.
Full documentation: https://docs.atlas.latellu.com
- Delivery client — read entries, pages, and media from the Public API (
/api/v1/public/*). - Management client — create, update, publish, and delete content via the Management API (
/api/v1/manage/*).
Server-side only. The management client (and any
atlas_mgmt_*key) grants full write access to a workspace. Never importcreateManagementClient/makeManagementClient, or ship anatlas_mgmt_*key, in browser-bundled code (client components, SPA bundles). Keep it in server components, API routes, or backend services. The delivery client with anatlas_live_*key is read-only and safe for server-side rendering, but still avoid embedding it in client-shipped JS unless the key is meant to be public.Known limitation: draft preview isn't wrapped yet (the preview-token mint endpoint is pending) — see Notes.
Install
npm install @latellu/atlas-sdk
# Generate typed interfaces for your workspace (recommended):
npx @latellu/atlas-cli generate --api-key=atlas_live_xxx --output=./srcRequires Node 18+ (uses global fetch). For older runtimes, pass fetchImpl.
Quick start — Delivery client
import { createClient } from '@latellu/atlas-sdk';
import type { AtlasContentTypes } from './atlas.types'; // from @latellu/atlas-cli
const atlas = createClient<AtlasContentTypes>({
url: 'https://api.atlas.latellu.com',
apiKey: 'atlas_live_xxx',
});
// List entries
const { items } = await atlas.entries('article').list({ locale: 'en', page: 1, limit: 20 });
// Get one entry by slug
const post = await atlas.entries('article').get('hello-world', { locale: 'ja' });
// post.data is typed as Article; locale translation merged over base
// Pages (lightweight list, or full with SEO + blocks)
const pages = await atlas.pages.list();
const home = await atlas.pages.get('home', { locale: 'en' });
// Media
const asset = await atlas.media.get(mediaId);Delivery API reference
atlas.entries(slug)
| Method | Options | Returns |
| --- | --- | --- |
| .list(options?) | locale?, page?, limit?, sort? | { items, total, page, pageSize } |
| .get(slug, options?) | locale? | AtlasEntry<T> \| null |
sort— expression like"created_at:desc"or"title:asc".pagedefaults to 1; pagination metadata intotal/page/pageSize.get()returnsnullfor 404; other failures throwAtlasError.
atlas.pages
| Method | Options | Returns |
| --- | --- | --- |
| .list(options?) | locale?, page?, limit? | { items, total, page, pageSize } of page summaries (no blocks) |
| .get(slug, options?) | locale? | AtlasPage \| null (SEO + blocks resolved for locale) |
atlas.media
| Method | Options | Returns |
| --- | --- | --- |
| .get(id) | — | MediaAsset \| null |
atlas.raw
Low-level requester for delivery endpoints not yet wrapped by a resource:
const { data, meta } = await atlas.raw.get<MyShape>('/some/public/path', { locale: 'en' });Path is relative to /api/v1/public.
Locale merging
For entries and pages, passing locale merges localized fields over the base (default-locale) values. Untranslated fields fall back to the base.
Error handling
get() methods return null for 404. All other failures throw AtlasError with .status, .code, and .message.
import { AtlasError } from '@latellu/atlas-sdk';
try {
await atlas.entries('article').list();
} catch (err) {
if (err instanceof AtlasError) {
console.error(err.status, err.code, err.message);
}
}Quick start — Management client
⚠️ Server-side only — see the warning above. Do not call this from browser-shipped code.
Authenticates with a management key (atlas_mgmt_...). Create one at cms.atlas.latellu.com/dashboard/api-keys (sidebar: Developer → API Keys); see Management API authentication for scopes.
Promise surface (default)
import { createManagementClient, AtlasError } from '@latellu/atlas-sdk/management';
const client = createManagementClient({
url: 'https://api.atlas.latellu.com',
token: process.env.ATLAS_MGMT_KEY!,
});
// Create → publish → update → delete
const entry = await client.entries('article').create({
slug: 'hello-world',
data: { title: 'Hello, world' },
}, { idempotencyKey: crypto.randomUUID() });
await client.entries('article').publish('hello-world');
await client.entries('article').update('hello-world', { data: { title: 'Updated' } });
await client.entries('article').delete('hello-world');Effect-native surface
For codebases built on Effect:
import { makeManagementClient, AtlasError } from '@latellu/atlas-sdk/management/effect';
import { Effect } from 'effect';
const client = makeManagementClient({
url: 'https://api.atlas.latellu.com',
token: process.env.ATLAS_MGMT_KEY!,
});
const program = client.entries('article').publish('hello-world');
await Effect.runPromise(program);Importing @latellu/atlas-sdk (delivery) or @latellu/atlas-sdk/management (Promise) never pulls Effect into your bundle. Effect is only a dependency of .../management/effect.
Management API reference
client.entries(type)
| Method | Description |
| --- | --- |
| .create(input, opts?) | Create a draft entry |
| .update(idOrSlug, input, opts?) | Update an entry |
| .publish(idOrSlug, opts?) | Publish (make visible to delivery API) |
| .unpublish(idOrSlug, opts?) | Unpublish (hide from delivery API) |
| .archive(idOrSlug, opts?) | Archive an entry |
| .schedule(idOrSlug, publishAt, opts?) | Schedule publish at a future timestamp |
| .duplicate(idOrSlug, opts?) | Clone an entry as a new draft |
| .delete(idOrSlug, opts?) | Delete an entry |
| .bulk(operations, opts?) | Batch multiple operations |
Input shapes:
interface CreateEntryInput {
slug: string;
data: Record<string, unknown>;
}
interface UpdateEntryInput {
slug?: string;
data?: Record<string, unknown>;
}client.pages
| Method | Description |
| --- | --- |
| .create(input, opts?) | Create a page |
| .update(slug, input, opts?) | Update a page |
| .delete(slug, opts?) | Delete a page |
| .publish(slug, opts?) | Publish a page |
| .unpublish(slug, opts?) | Unpublish a page |
| .archive(slug, opts?) | Archive a page |
| .schedule(slug, publishAt, opts?) | Schedule publish |
| .blocksReorder(slug, blockIds, opts?) | Reorder page blocks |
client.media
| Method | Description |
| --- | --- |
| .upload(file, meta?, opts?) | Upload a media asset (Blob) |
| .delete(id, opts?) | Delete a media asset |
Idempotency
All write methods accept { idempotencyKey?: string } to prevent duplicates on retry. The client also auto-retries 429 responses with exponential backoff.
Management error handling
Same AtlasError as delivery, with additional .errors array for validation failures:
try {
await client.entries('article').create({ slug: '', data: {} });
} catch (err) {
if (err instanceof AtlasError) {
console.error(err.status, err.code, err.message, err.errors);
// err.errors: [{ field: "slug", message: "slug is required" }]
}
}Passing an atlas_live_ key (instead of atlas_mgmt_) throws ManagementConfigError immediately.
Notes
- Draft preview is not wrapped yet (the preview-token mint endpoint is pending).
entry.dataarrives from the delivery API as a JSON string; the SDK parses it into your typed object.- For
entries().get(slug, { locale }), the matching translation is merged over the base data (untranslated fields fall back to the base value). createClientis generic: without a schema type, slugs are unconstrained anddataisunknown. PassAtlasContentTypesfor full typing.
