@corpusctl/client
v0.2.0
Published
Typed SDK for Corpusctl headless CMS — edge-first ReadClient, ManagementClient and framework-agnostic block rendering (toRenderTree, renderToHtml)
Maintainers
Readme
@corpusctl/client
Typed SDK for Corpusctl — an API-first, self-hosted headless CMS. Ships four things:
ReadClient— reads published content from the edge. Carries no token, safe in the browser. Two cached HTTP requests per lookup, cost does not grow with content count.ManagementClient— writes to the API with a secret token. Server-side only. The two classes are separate on purpose: a single class would make leaking the management token into a browser bundle a one-line mistake.toRenderTree— framework-agnostic block renderer: nests flat list items into real<ul>/<ol>trees, nests marks (boldinsidelink), generates heading anchors. Never throws; broken blocks are skipped.renderToHtml— universal HTML output for Astro, Eleventy, Svelte, Solid, e-mail templates… anywhere JSX is not available.
Install
pnpm add @corpusctl/clientRead published content
import { ReadClient } from '@corpusctl/client'
const client = new ReadClient({
tenantId: '3f2b…',
edgeUrl: 'https://cdn.example.com',
})
const doc = await client.getBySlug('blog/hello-world')
// undefined if not published — unpublished content is a normal state, not an exceptionOther methods: getSummaryBySlug, getManyBySlugs, resolve,
listShardRoutes, clearCache, ReadClient.shardOf.
Manage content (server-side)
import { ManagementClient, ManagementError } from '@corpusctl/client'
const cms = new ManagementClient({
apiUrl: 'https://api.example.com',
token: process.env.CORPUSCTL_TOKEN!, // never ship to the client
})
const doc = await cms.createDocument({ type: 'post' })
await cms.saveDraft(doc.id, { title: 'Hello' })
await cms.publish(doc.id)Errors carry stable codes (CORPUS_E_*), status, field-level details and
requestId. 429/5xx are retried with exponential backoff + jitter and
respect Retry-After.
Render blocks to HTML
import { toRenderTree, renderToHtml } from '@corpusctl/client'
const tree = toRenderTree(doc.fields.body)
// Unstyled default: <p>…</p><h2>…</h2><ul><li>…</li></ul>
const html = renderToHtml(tree)
// Styling is entirely yours, via overrides:
const styled = renderToHtml(tree, {
headingAnchors: true,
components: {
paragraph: (n, render) => `<p class="prose">${render(n.children)}</p>`,
types: {
gallery: (n) => `<div class="gallery">${(n.value.images ?? []).length}</div>`,
},
},
})The default output contains zero class, style or id attributes. All
text and attributes are escaped; javascript:/vbscript: URLs are never
emitted as links. There is deliberately no rawHtml option.
React and Vue adapters that consume the same tree: @corpusctl/react,
@corpusctl/vue. Next.js draft-preview/revalidation helpers: @corpusctl/next.
License
MIT
