New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@mapled/astro

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mapled/astro

Read published Mapled content in an Astro site — a server-side client with a tag cache, the publish webhook (purge or deploy hook), preview and live updates.

latest
npmnpm
Version
0.1.1
Version published
Weekly downloads
9
-95.41%
Maintainers
1
Weekly downloads
 
Created
Source

@mapled/astro

Read published Mapled content in an Astro site — a server-side client with a tag cache, a publish webhook that purges it (or rebuilds a static site), preview of drafts, and live updates for the tabs that are already open.

Mapled is a hosted headless CMS built for sites created with AI. Your agent (or you) models collections and fills in content; this package is how an Astro site reads what's published. It is @mapled/client with Astro's idioms around it: the same reads, the same types from mapled types generate.

Install

npm install @mapled/astro

Grab the delivery key in Mapled: project settings (the gear in the project's header) → Integrations → Your site, and put it in your site's env. The adapter reads it on the server, so no PUBLIC_ prefix is needed (the delivery key is the one key that may be public; the webhook secret and a server key never are):

MAPLED_KEY=mk_live_…
MAPLED_WEBHOOK_SECRET=whsec_…

Two places hold a site's env, and the adapter looks in both. A .env file (and astro dev) fills the site's own import.meta.env, when the site is built — the snippets below hand those values over. A host's environment variables are the server's process.env, which the adapter reads itself when a request arrives — so a site built without a value still finds it where it runs.

Pages that read Mapled render on demand: output: "server" in astro.config.mjs (with an adapter), or export const prerender = false on those pages. A static build works too — the webhook then asks your host to rebuild (Refresh on publish).

Read published content

One client for the whole site, typed by the schema npx @mapled/cli types generate writes:

// src/lib/mapled.ts
import { mapled } from "@mapled/astro";
import type { MapledSchema } from "../mapled-types";

/** Pass `Astro` (or an endpoint's context) so the request's preview cookie is seen; without it, published content only. */
export const cms = (from?: Request | { request: Request }) => mapled<MapledSchema>(from, { key: import.meta.env.MAPLED_KEY });

mapled() takes the key it is handed — the one of a .env file, named as import.meta.env.MAPLED_KEY so the build keeps it — else MAPLED_KEY of the server's environment (apiUrl and MAPLED_API_URL likewise). In a page's frontmatter:

---
// src/pages/index.astro
import { cms } from "../lib/mapled";
const home = await cms(Astro).getSingle("homepage");
const { records: posts } = await cms(Astro).getRecords("posts", { sort: "-date", limit: 10 });
---
<h1>{home?.data.headline}</h1>
<ul>{posts.map((p) => <li><a href={`/blog/${p.data.slug}`}>{p.data.title}</a></li>)}</ul>
---
// src/pages/blog/[slug].astro
export const prerender = false;
import { cms } from "../../lib/mapled";
const post = await cms(Astro).getRecordBySlug("posts", Astro.params.slug!);
if (!post) return Astro.redirect("/404");
---
<article><h1>{post.data.title}</h1></article>

The reads are those of @mapled/client: getRecords(collection, { filter, sort, limit, offset, fields, expand, search, locale }), getRecord, getRecordBySlug, getSingle, getRelease, isPreview. Each published answer is kept in the adapter's cache — in the server process, under the tags mapled and mapled:<collection> — for an hour, or for as long as you say:

// src/lib/reads.ts
import { cms } from "./mapled";

export async function homepage() {
  // kept until the next publish purges it; `revalidate: 60` keeps it a minute, `revalidate: 0` never
  return cms().getSingle("homepage", { revalidate: false, tags: ["layout"] });
}

A page and its layout asking for the same record during one render make one request. A prerendered page reads published content: mapled(Astro) leaves its request unread (Astro 5+ marks it isPrerendered), so a static build stays quiet. The cache is this process's: a long-running server (the Node adapter) holds one, serverless functions hold one each — keep revalidate short there, or build statically. createClient({ key, apiUrl, draft, cache: false }) makes a client by hand, with the cache off when you say so.

Rich text and images

rich_text values are Markdown — render them with a Markdown component, never as raw HTML. Image values are asset ids: assetUrl(id, { width, format }) gives the URL of a resized variant.

// src/lib/images.ts
import { assetUrl } from "@mapled/astro";

export const cover = (id: string) => assetUrl(id, { width: 1280, format: "webp" });

Refresh on publish

Mount the webhook and point the project at it (Integrations → Your site → Revalidation webhook, or let your agent call configure_revalidation). Every publish is signed with the project's secret; the handler purges the cache, so the next render reads the new release:

// src/pages/api/mapled/revalidate.ts
import { createRevalidateHandler } from "@mapled/astro/server";

export const prerender = false;
export const POST = createRevalidateHandler({ secret: import.meta.env.MAPLED_WEBHOOK_SECRET });

import.meta.env holds the secret of a .env file; on a site built without it the handler reads MAPLED_WEBHOOK_SECRET from the server's environment when the first webhook arrives. Until either has it, it answers 503 and Mapled retries.

A statically built site can't re-render on its own: set MAPLED_DEPLOY_HOOK_URL to your host's build hook (Vercel, Netlify, Cloudflare Pages…) and the same handler asks it for a build. The route itself still runs on a server — output: "static" with an adapter prerenders the pages and keeps the routes marked prerender = false as functions; a host with no functions at all has nothing to take the webhook. The handler calls the hook once per publish — a retry of the same publish doesn't build twice, and a hook that refuses fails the delivery so Mapled retries. The URL is read from the environment on the first webhook and never written anywhere. A handler of your own can verify a request with verifySignature(secret, body, header), drop what it needs with purge(["mapled:posts"]), and answer with the header x-mapled-cache: cacheMark() so live updates follow it.

Preview drafts

Mapled opens the site through a one-time link to the preview route, which trades it for a draft session, keeps the session in the mapled_draft cookie (HttpOnly, Secure, SameSite=Lax) and redirects into the site. From then on mapled(Astro) reads drafts for that visitor, uncached, until the exit route clears the cookie — published content for everyone else.

// src/pages/api/mapled/preview.ts
import { createPreviewHandler } from "@mapled/astro/server";

export const prerender = false;
export const GET = createPreviewHandler();

(It trades the link at MAPLED_API_URL when the site's env has one — the API the reads go to.)

// src/pages/api/mapled/preview/exit.ts
import { createExitPreviewHandler } from "@mapled/astro/server";

export const prerender = false;
export const GET = createExitPreviewHandler();

A draft page must never be cached by your host or a CDN. The middleware marks it private, no-store while the cookie is there:

// src/middleware.ts
export { onRequest } from "@mapled/astro/middleware";

(Next to middleware of your own: sequence(onRequest, yours).) draftOf(request) answers the draft session of a request, or null — what mapled(Astro) uses; pass it as draft to createClient when you build the client yourself. Preview links point at /api/mapled/preview by default; both routes redirect only to a path of the site itself.

Live updates

Open tabs can follow Publish. Mount the release route and render the component once, in the layout — it asks the route every few seconds and reloads the page when a publish lands:

// src/pages/api/mapled/release.ts
import { createReleaseHandler } from "@mapled/astro/server";

export const prerender = false;
export const GET = createReleaseHandler();
---
// src/layouts/Layout.astro
import MapledLive from "@mapled/astro/MapledLive.astro";
---
<html><body><slot /><MapledLive /></body></html>

The route names the release this server last acknowledged through the webhook — never one its cache hasn't been purged for — so a tab that reloads on it renders the new content, not the old one again. It keeps the delivery key on the server and never asks Mapled. A site without a server (a static build) passes the delivery key instead, <MapledLive key={import.meta.env.PUBLIC_MAPLED_KEY} /> — the one key that is safe in a page; a PUBLIC_ variable for it, never for a secret — and the tab asks Mapled directly, once a minute. From a script of your own: startLive({ endpoint }) (or { key }) from @mapled/astro/live reloads on a publish and returns a stop function; mountLive() is what the component's script calls; watchRelease is the client's own, for anything else.

Live data

Operational collections — content the site's backend writes, which takes effect without a publish — are createAppClient({ serverKey }) from the same package, server-side only. Its calls, Site Identity and the data of signed-in people are documented in @mapled/client.

// src/pages/api/orders.ts
import { createAppClient } from "@mapled/astro";

export const prerender = false;
// the .env file's value, else the server's own environment — a build made without the key still finds it where it runs
const db = createAppClient({ serverKey: import.meta.env.MAPLED_SERVER_KEY ?? process.env.MAPLED_SERVER_KEY! });
export async function POST({ request }: { request: Request }) {
  const body = (await request.json()) as { email: string };
  const order = await db.create("orders", { email: body.email });
  return Response.json({ id: order.id }, { status: 201 });
}

API

ImportWhat it is
mapled(from?, { key, apiUrl }?) from @mapled/astroThe site's client from the environment; with Astro, a request or an endpoint's context it reads drafts under the preview cookie
createClient({ key, apiUrl, release, locale, draft, fetch, cache })A client by hand
draftOf(from)The draft session a request carries, or null
assetUrl, createAppClient, MapledError, watchReleaseRe-exported from @mapled/client
createRevalidateHandler({ secret, tags, deployHook, fetch }) from @mapled/astro/serverThe publish webhook: purges the cache, calls the deploy hook, answers with x-mapled-cache; without a secret, MAPLED_WEBHOOK_SECRET of the server
createPreviewHandler({ apiUrl, appUrl }), createExitPreviewHandler()Into preview and out of it
createReleaseHandler({ ttl })The release route open tabs ask
verifySignature, purge, cacheMarkFor a webhook handler of your own
onRequest from @mapled/astro/middlewareA draft page answers private, no-store
startLive, mountLive from @mapled/astro/live; MapledLive.astroOpen tabs follow Publish

Errors are MapledError with the API's status and code. Every handler takes a Request or Astro's context ({ request }) and returns a Response, so it is an Astro endpoint as it is.

Keywords

mapled

FAQs

Package last updated on 07 Oct 2026

Related posts