@spacefast/content
v0.2.2
Published
Typed collections and batched content client for Spacefast.
Readme
@spacefast/content
Typed collections and a batched content client for the WordPress content kernel built into every Spacefast Space. The package works in browsers, Next.js server code, and Spacefast Zero.
import { collection, createContentClient, defineContent, fields } from "@spacefast/content";
const schema = defineContent({
collections: {
events: collection({
name: "events",
// Keep this durable identifier unchanged if the collection is renamed.
resourceId: "events_01k4t7x8",
label: "Events",
singularLabel: "Event",
fields: {
venue: fields.text({ label: "Venue" }),
starts_at: fields.date({ label: "Starts at", required: true }),
},
}),
},
});
const content = createContentClient({ url: "https://example.spacefast.app", schema });
const result = await content.batch({
events: content.collections.events.query({ limit: 10 }),
posts: content.posts.query({ limit: 3 }),
media: content.media.query({ where: [{ field: "id", operator: "in", value: [12, 18] }] }),
});defineContent() gives your queries and results real types. It does not create collections — that's
the next section's job.
The content model lives in your files
Your Space's content model is a file, not an API call. Put a content.config.ts at the Space root
and any modules it imports under content/:
// content.config.ts
export default {
collections: [
{
slug: "events",
fields: [
{ name: "venue", type: "text" },
{ name: "starts_at", type: "date", required: true },
],
},
],
globals: [],
};It's a Payload-style config, and it compiles when you publish the Space. The compiled model is bound to that version, so a rollback rolls the content model back with it — the version you serve and the shape of its content are never out of sync.
That's the only way the model changes. There is no endpoint that edits it behind publish's back, so what you read in Git is what's running. Two limits to stay under: 64 modules and 2 MiB of source in total.
Document writes
Documents are a different story — they change all the time, so they go over the API. Point the same client at Spacefast with a scoped API key:
const content = createContentClient({
apiUrl: "https://api.spacefast.com",
spaceId: "spc_...",
token: process.env.SPACEFAST_TOKEN,
schema,
});
await content.syncMarkdown({
source: "content/hello.md",
slug: "hello",
title: "Hello",
markdown: "# Hello",
status: "publish",
});That writes a document into an existing collection; it never adds or reshapes one. batch() sends
one request to the Space content endpoint. Writes require a Spacefast management token; public reads
never expose WordPress credentials.
Inside a Zero capsule, use the runtime-owned client instead of making an HTTP request:
import { capsule, query } from "@spacefast/zero/server";
const homeContent = query((ctx) =>
ctx.content.batch({
posts: { collection: "posts", limit: 3 },
events: { collection: "events", limit: 10 },
}),
);
export default capsule({ queries: { homeContent } });Zero forwards that call as one bounded batch to the Space's WordPress content kernel. WordPress credentials and endpoint selection remain runtime-owned.
