@mapled/next 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +289 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,18 +4,215 @@ Read published [Mapled](https://mapled.io) content in a Next.js site — a deliv
|
|
|
4
4
|
|
|
5
5
|
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 the site reads what's published.
|
|
6
6
|
|
|
7
|
+
- [Install](#install)
|
|
8
|
+
- [Integrate by hand](#integrate-by-hand) — the whole integration step by step, no AI agent needed
|
|
9
|
+
- Reference: [Read published content](#read-published-content) • [Rich text](#rich-text) • [Images](#images) • [Refresh on publish](#refresh-on-publish) • [Preview drafts](#preview-drafts) • [Forms](#forms) • [Live data](#live-data) • [Without Next.js: the HTTP API](#without-nextjs-the-http-api) • [API](#api)
|
|
10
|
+
|
|
7
11
|
## Install
|
|
8
12
|
|
|
9
13
|
```bash
|
|
10
14
|
npm install @mapled/next
|
|
11
15
|
```
|
|
12
16
|
|
|
13
|
-
Grab the delivery key in Mapled: project → Integrations → **Your site**, and put it in your site's env:
|
|
17
|
+
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:
|
|
14
18
|
|
|
15
19
|
```bash
|
|
16
20
|
MAPLED_KEY=mk_live_…
|
|
17
21
|
```
|
|
18
22
|
|
|
23
|
+
## Integrate by hand
|
|
24
|
+
|
|
25
|
+
An AI agent connected through [`@mapled/mcp`](https://www.npmjs.com/package/@mapled/mcp) does all of this for you. Nothing in it needs one, though: this is the same integration, step by step — from a Next.js site with hardcoded content to a site its editors run from Mapled. Plan on an hour for a small site.
|
|
26
|
+
|
|
27
|
+
You need a Mapled project where you can change the structure (the builder plan), a Next.js 14+ site on the App Router, and Node 18 or newer. The example is a site with a homepage and a blog; the keys (`homepage`, `articles`) are whatever you choose in step 1.
|
|
28
|
+
|
|
29
|
+
### 1. Model the content
|
|
30
|
+
|
|
31
|
+
In Mapled, open the project and press the wrench in the header (**Structure**) → **Collections**. Create a **single** for every one-off part of the site (`homepage`, `footer`) and a **collection** for every list of alike things (`articles`, `team`), then **Add field** for each text, image and link the code hardcodes today.
|
|
32
|
+
|
|
33
|
+
- Keys are what the code reads — `getRecords("articles")`, `data.title`. A key can be renamed later and the old one keeps working as an alias, but it is a migration: choose them with care.
|
|
34
|
+
- A collection whose records have pages of their own needs a **Slug** field — `getRecordBySlug` finds records by it.
|
|
35
|
+
- Write a **Help text** for every field. Editors see it under the field; write it for someone who has never seen the code.
|
|
36
|
+
- Mark a field **Sensitive** when editors keep it but the site must never receive it.
|
|
37
|
+
|
|
38
|
+
### 2. Fill it in and publish once
|
|
39
|
+
|
|
40
|
+
Open **Content**, move the hardcoded texts and images into records, then **Review & publish**. The site only ever reads published releases: before the first publish the delivery API answers 404 “Nothing published yet.” — `getSingle` and `getRecord` read that as `null`, `getRecords` throws.
|
|
41
|
+
|
|
42
|
+
### 3. Link the repository and generate types
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx @mapled/cli auth login
|
|
46
|
+
npx @mapled/cli project link
|
|
47
|
+
npx @mapled/cli types generate
|
|
48
|
+
npx @mapled/cli schema pull
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Your browser opens Mapled, you pick the project and authorize; `project link` writes `mapled.json`, `types generate` writes `mapled-types.ts` — an interface per collection and single, tied together by `MapledSchema` — and `schema pull` records the schema the site is built against in `mapled/schema.json`, which `schema diff` and `doctor` compare with Mapled later. ([`@mapled/cli`](https://www.npmjs.com/package/@mapled/cli) has the details of every command used here.)
|
|
52
|
+
|
|
53
|
+
### 4. Create the client
|
|
54
|
+
|
|
55
|
+
Install the package and set `MAPLED_KEY` ([Install](#install)) in `.env.local` and in your hosting's environment. Then one client for the whole site:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
// lib/mapled.ts
|
|
59
|
+
import { createClient } from "@mapled/next";
|
|
60
|
+
import type { MapledSchema } from "../mapled-types";
|
|
61
|
+
|
|
62
|
+
export const mapled = createClient<MapledSchema>({ key: process.env.MAPLED_KEY! });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
With `MapledSchema` every read is typed by its key, and a misspelled collection or field is a build error.
|
|
66
|
+
|
|
67
|
+
### 5. Replace the hardcoded content with reads
|
|
68
|
+
|
|
69
|
+
Read in Server Components — no client-side fetching, no loading states. A single:
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
// app/page.tsx
|
|
73
|
+
import { assetUrl } from "@mapled/next";
|
|
74
|
+
import { mapled } from "../lib/mapled";
|
|
75
|
+
|
|
76
|
+
export default async function Home() {
|
|
77
|
+
const home = await mapled.getSingle("homepage");
|
|
78
|
+
if (!home) return null; // nothing published yet
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<main>
|
|
82
|
+
<h1>{home.data.headline}</h1>
|
|
83
|
+
<p>{home.data.intro}</p>
|
|
84
|
+
{home.data.cover ? <img src={assetUrl(home.data.cover, { width: 1280 })} alt="" /> : null}
|
|
85
|
+
</main>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A list:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// app/blog/page.tsx
|
|
94
|
+
import Link from "next/link";
|
|
95
|
+
import { mapled } from "../../lib/mapled";
|
|
96
|
+
|
|
97
|
+
export default async function Blog() {
|
|
98
|
+
const { records } = await mapled.getRecords("articles", { sort: "-date", limit: 20 });
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<ul>
|
|
102
|
+
{records.map((post) => (
|
|
103
|
+
<li key={post.id}>
|
|
104
|
+
<Link href={`/blog/${post.data.slug}`}>{post.data.title}</Link>
|
|
105
|
+
</li>
|
|
106
|
+
))}
|
|
107
|
+
</ul>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
A page per record — new records get their page without a deploy:
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
// app/blog/[slug]/page.tsx
|
|
116
|
+
import { notFound } from "next/navigation";
|
|
117
|
+
import Markdown from "react-markdown";
|
|
118
|
+
import { mapled } from "../../../lib/mapled";
|
|
119
|
+
|
|
120
|
+
export async function generateStaticParams() {
|
|
121
|
+
const { records } = await mapled.getRecords("articles", { fields: ["slug"] });
|
|
122
|
+
return records.map((post) => ({ slug: post.data.slug }));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default async function Article({ params }: { params: Promise<{ slug: string }> }) {
|
|
126
|
+
const { slug } = await params;
|
|
127
|
+
const post = await mapled.getRecordBySlug("articles", slug);
|
|
128
|
+
if (!post) notFound();
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<article>
|
|
132
|
+
<h1>{post.data.title}</h1>
|
|
133
|
+
<Markdown>{post.data.body}</Markdown>
|
|
134
|
+
</article>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
(`params` is a Promise since Next.js 15; on 14 it is the plain object.) Rich text is Markdown — render it with a component such as `react-markdown`, never as raw HTML. Leave in the code what editors shouldn't touch: layout, navigation structure, anything that is logic rather than content.
|
|
140
|
+
|
|
141
|
+
### 6. Refresh the site on publish
|
|
142
|
+
|
|
143
|
+
Reads are cached (`revalidate: 3600`) and tagged; a webhook refreshes them the moment someone publishes.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
// app/api/mapled/revalidate/route.ts
|
|
147
|
+
import { createRevalidateHandler } from "@mapled/next/server";
|
|
148
|
+
|
|
149
|
+
export const POST = createRevalidateHandler({
|
|
150
|
+
secret: process.env.MAPLED_WEBHOOK_SECRET!,
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Deploy, then in Mapled → **Integrations** → **Your site** save `https://your-site.com/api/mapled/revalidate` as the **Revalidation webhook**, copy the **Signing secret** into `MAPLED_WEBHOOK_SECRET` (hosting env and `.env.local`), redeploy, and press **Send test ping**: the delivery shows up under **Recent deliveries** as Delivered. The URL has to be public — Mapled refuses addresses that resolve to a private network.
|
|
155
|
+
|
|
156
|
+
### 7. Preview drafts
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
// app/api/mapled/preview/route.ts
|
|
160
|
+
import { createPreviewHandler } from "@mapled/next/server";
|
|
161
|
+
|
|
162
|
+
export const GET = createPreviewHandler();
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
// app/api/mapled/preview/exit/route.ts
|
|
167
|
+
import { createExitPreviewHandler } from "@mapled/next/server";
|
|
168
|
+
|
|
169
|
+
export const GET = createExitPreviewHandler();
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The **Preview** button in Mapled opens the site at the origin of the webhook URL from step 6, on exactly this path — `/api/mapled/preview` — so keep the route there. It turns on Next.js draft mode, and every read of the client switches to live drafts, uncached, with no changes in your components.
|
|
173
|
+
|
|
174
|
+
### 8. Forms
|
|
175
|
+
|
|
176
|
+
A contact or signup form posts straight to Mapled; submissions land in **Forms**, with e-mail notifications and spam protection. Create the form once:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npx @mapled/cli forms create "Contact"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
and post to it from the site — see [Forms](#forms) for the component.
|
|
183
|
+
|
|
184
|
+
### 9. Live data
|
|
185
|
+
|
|
186
|
+
Orders, bookings, votes — records the site's backend writes and reads immediately, without Publish — go to collections created with the mode **Live data** and are reached with the server key: see [Live data](#live-data).
|
|
187
|
+
|
|
188
|
+
### 10. Record the integration
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
npx @mapled/cli scan --write
|
|
192
|
+
npx @mapled/cli bindings push
|
|
193
|
+
npx @mapled/cli md pull
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
`scan` reads the code and writes `mapled/manifest.json`: which page reads which collection and field. `bindings push` sends it to Mapled, so editors see where a field appears and get a warning before a change would break the site (**Structure** → **Bindings**). `md pull` writes `MAPLED.md`, the guide for whoever works on the site next — person or AI agent. Commit `mapled.json`, `mapled-types.ts`, `mapled/schema.json`, `mapled/manifest.json` and `MAPLED.md`; never commit an env file.
|
|
197
|
+
|
|
198
|
+
### 11. Check everything
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
npx @mapled/cli doctor
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
It checks the integration from both sides — the repository and what Mapled sees of the deployed site. The list below is what an agent's setup has to pass before Mapled calls it complete; `doctor` answers all of it except the last two lines, which are yours to check:
|
|
205
|
+
|
|
206
|
+
- the site reads content from Mapled (“Site reads content — Last read …”);
|
|
207
|
+
- a publish refreshes it (“Publish webhook — Delivered …”);
|
|
208
|
+
- **Preview** in Mapled opens the site with drafts (“Preview on the site — Responds at …”);
|
|
209
|
+
- bindings are pushed and healthy, and the integration is in sync (“Bindings”, “Integration”);
|
|
210
|
+
- no env file or secret is tracked by git (“Secrets in git”);
|
|
211
|
+
- every field has a help text (**Structure** → **Collections**);
|
|
212
|
+
- the site builds.
|
|
213
|
+
|
|
214
|
+
Later, when the structure changes in Mapled, `npx @mapled/cli schema diff` says what changed and whether it breaks the site; after updating the code, run `types generate`, `scan --write`, `bindings push` and `md pull` again.
|
|
215
|
+
|
|
19
216
|
## Read published content
|
|
20
217
|
|
|
21
218
|
```ts
|
|
@@ -114,7 +311,9 @@ export const POST = createRevalidateHandler({
|
|
|
114
311
|
});
|
|
115
312
|
```
|
|
116
313
|
|
|
117
|
-
Then in Mapled (
|
|
314
|
+
Then in Mapled (**Integrations** → **Your site**) set the revalidation webhook to `https://your-site.com/api/mapled/revalidate` and copy the signing secret into `MAPLED_WEBHOOK_SECRET`. Every publish sends a signed ping; the handler verifies the `x-mapled-signature` HMAC and revalidates the `mapled` tags.
|
|
315
|
+
|
|
316
|
+
Each ping is tried up to three times (after 2 and 6 seconds, 5 seconds to answer); **Recent deliveries** keeps them for 30 days, and a failed one can be sent again from there. **Rotate secret** replaces the signing secret at once — deliveries fail until the site has the new one.
|
|
118
317
|
|
|
119
318
|
## Preview drafts
|
|
120
319
|
|
|
@@ -134,6 +333,64 @@ export const GET = createExitPreviewHandler();
|
|
|
134
333
|
|
|
135
334
|
The **Preview** button in Mapled opens your site through a one-time link: the route turns on Next.js draft mode, and every `getRecords`/`getRecord`/`getSingle` call automatically switches to live drafts, uncached. No code changes in your components — the client detects draft mode on its own. Sessions last two hours; `/api/mapled/preview/exit` leaves preview.
|
|
136
335
|
|
|
336
|
+
Mapled finds the site by the revalidation webhook's URL — preview opens at its origin, on `/api/mapled/preview` — so set the webhook up first and keep the route on that path.
|
|
337
|
+
|
|
338
|
+
## Forms
|
|
339
|
+
|
|
340
|
+
A form is created once — `npx @mapled/cli forms create "Contact"`, or by your AI agent — and gets a key (`contact`). The site posts submissions as flat JSON with the delivery key; they show up in Mapled under **Forms** right away, and the form's recipients — the project's owners, unless its settings name others — get an e-mail. Forms never wait for Publish.
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
// app/contact/ContactForm.tsx
|
|
344
|
+
"use client";
|
|
345
|
+
|
|
346
|
+
import { useState, type FormEvent } from "react";
|
|
347
|
+
|
|
348
|
+
const CONSENT = "I agree to be contacted about my request.";
|
|
349
|
+
|
|
350
|
+
export function ContactForm() {
|
|
351
|
+
const [state, setState] = useState<"idle" | "sending" | "sent" | "failed">("idle");
|
|
352
|
+
|
|
353
|
+
async function submit(event: FormEvent<HTMLFormElement>) {
|
|
354
|
+
event.preventDefault();
|
|
355
|
+
setState("sending");
|
|
356
|
+
const form = new FormData(event.currentTarget);
|
|
357
|
+
const res = await fetch("https://api.mapled.io/v1/delivery/forms/contact", {
|
|
358
|
+
method: "POST",
|
|
359
|
+
headers: { "content-type": "application/json", "x-mapled-key": process.env.NEXT_PUBLIC_MAPLED_KEY! },
|
|
360
|
+
body: JSON.stringify({
|
|
361
|
+
name: form.get("name"),
|
|
362
|
+
email: form.get("email"),
|
|
363
|
+
message: form.get("message"),
|
|
364
|
+
_gotcha: form.get("_gotcha"), // the honeypot: people leave it empty
|
|
365
|
+
_consent: form.get("consent") ? CONSENT : undefined,
|
|
366
|
+
_page: window.location.pathname,
|
|
367
|
+
}),
|
|
368
|
+
});
|
|
369
|
+
setState(res.ok ? "sent" : "failed");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (state === "sent") return <p>Thank you — we'll be in touch.</p>;
|
|
373
|
+
return (
|
|
374
|
+
<form onSubmit={submit}>
|
|
375
|
+
<input name="name" required />
|
|
376
|
+
<input name="email" type="email" required />
|
|
377
|
+
<textarea name="message" required />
|
|
378
|
+
<input name="_gotcha" tabIndex={-1} autoComplete="off" aria-hidden="true" style={{ display: "none" }} />
|
|
379
|
+
<label>
|
|
380
|
+
<input name="consent" type="checkbox" required /> {CONSENT}
|
|
381
|
+
</label>
|
|
382
|
+
<button disabled={state === "sending"}>Send</button>
|
|
383
|
+
{state === "failed" ? <p>That didn't go through. Try again in a minute.</p> : null}
|
|
384
|
+
</form>
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
- The endpoint is open to browsers (CORS) and takes the **delivery key** — the one key that is safe in client code. Expose it as `NEXT_PUBLIC_MAPLED_KEY`; never the server key or the webhook secret. To keep even that out of the bundle, post from a Server Action with `MAPLED_KEY` instead — all visitors then share your server's address for the limit below.
|
|
390
|
+
- The body is one flat object: up to 50 fields of text (5,000 characters), numbers and booleans. Keys starting with `_` are instructions, not answers: `_gotcha` (a hidden field bots fill in — such submissions are accepted and filed as spam), `_consent` (the exact label of the consent checkbox the visitor ticked, kept as evidence), `_page` (where the form was), `_test: true` (a test submission: no e-mail, not counted).
|
|
391
|
+
- `201 { "ok": true }` on success. `400 VALIDATION_FAILED` for a body of another shape, `404` for an unknown key or form, `429 RATE_LIMITED` past 30 submissions a minute per form and address, `429 FORMS_LIMIT` when the project's monthly submissions are used up, `410 PROJECT_ARCHIVED` for an archived project.
|
|
392
|
+
- `npx @mapled/cli forms list` shows the project's forms and their URLs.
|
|
393
|
+
|
|
137
394
|
## Live data
|
|
138
395
|
|
|
139
396
|
Operational collections skip the publish cycle — saves take effect immediately. Your site's backend reads **and writes** them through the Application Data API with the project's **server key** (Mapled: Integrations → Your site). Server-side only; the key must never reach the browser.
|
|
@@ -155,12 +412,42 @@ await db.remove("orders", id);
|
|
|
155
412
|
|
|
156
413
|
The server key reaches only operational collections with access class `public`/`server` — editorial content, internal and sensitive collections stay out of reach, and it grants nothing on the Management API.
|
|
157
414
|
|
|
415
|
+
## Without Next.js: the HTTP API
|
|
416
|
+
|
|
417
|
+
The client is a thin layer over a plain HTTP API, so any site — or a build script — can read Mapled. The base URL is `https://api.mapled.io`; every request carries the delivery key in `x-mapled-key`.
|
|
418
|
+
|
|
419
|
+
| Request | Answer |
|
|
420
|
+
| --- | --- |
|
|
421
|
+
| `GET /v1/delivery/collections/{collection}/records` | `{ records: [{ id, data, expanded? }], total, release }` |
|
|
422
|
+
| `GET /v1/delivery/collections/{collection}/records/{id}` | `{ record, release }` — 404 when it isn't in the release |
|
|
423
|
+
| `GET /v1/delivery/collections/{collection}/records/by-slug/{slug}` | `{ record, release }` |
|
|
424
|
+
| `GET /v1/delivery/singles/{single}` | `{ record, release }` — `record` is `null` while the single is empty |
|
|
425
|
+
| `GET /v1/delivery/release` | `{ release, publishedAt }` — the release being served now; `null`s before the first publish |
|
|
426
|
+
| `POST /v1/delivery/forms/{form}` | `201 { ok: true }` — see [Forms](#forms) |
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
curl -H "x-mapled-key: $MAPLED_KEY" \
|
|
430
|
+
"https://api.mapled.io/v1/delivery/collections/articles/records?filter[featured]=true&filter[date][gte]=2026-01-01&sort=-date&limit=10&expand=author&fields=title,slug"
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
- Lists take `filter[field]=value` or `filter[field][op]=value` (`eq`, `ne`, `lt`, `lte`, `gt`, `gte`, `in` — comma-separated, `contains`), `sort=field` / `sort=-field`, `limit` (1–100, default 100) and `offset`. Every read takes `expand` (relation keys, dotted for a second level), `fields` and `release=N` to read a published release by its number. A query the schema can't answer is a 400 `INVALID_QUERY` that says why.
|
|
434
|
+
- Only published content of collections with access **Public** is served; sensitive fields are never in an answer. Image and file values are asset ids — `https://api.mapled.io/files/{id}` is the file, with `?w=`, `h=`, `fit=`, `fmt=`, `q=` for a resized variant.
|
|
435
|
+
- Answers are per key — `Cache-Control: private` — with an `ETag`: send it back as `If-None-Match` and an unchanged answer is a 304. A read pinned with `release=N` never changes and may be cached for good. `GET /v1/delivery/release` is the cheap way to learn that something was published.
|
|
436
|
+
- Errors share one envelope: `{ error: { code, message, requestId } }`. A key gets 1,200 requests a minute; past that, 429 `RATE_LIMITED` with `retry-after`.
|
|
437
|
+
- The publish webhook is a `POST` with a JSON body `{ event, project, release, at, id }` and the headers `x-mapled-event` (`release.published`, `project.restored` or `test`), `x-mapled-event-id` (the same on every retry — use it to deduplicate), `x-mapled-attempt` and `x-mapled-signature`: `sha256=` followed by the hex HMAC-SHA256 of the raw body with the signing secret. Compare in constant time, answer 2xx within 5 seconds. `verifySignature` from `@mapled/next/server` does the check anywhere Web Crypto exists.
|
|
438
|
+
- Live data: `GET` / `POST /v1/app/collections/{collection}/records` and `GET` / `PATCH` / `DELETE /v1/app/collections/{collection}/records/{id}` with the server key in `x-mapled-server-key`; writes send `{ "data": { … } }`.
|
|
439
|
+
|
|
440
|
+
Preview (drafts on the site) is built on Next.js draft mode and ships with this package only.
|
|
441
|
+
|
|
158
442
|
## API
|
|
159
443
|
|
|
160
444
|
- `createClient({ key, apiUrl?, release? })` → `getRecords(collection, opts?)`, `getRecord(collection, id, opts?)`, `getRecordBySlug(collection, slug, opts?)`, `getSingle(key, opts?)`, `getRelease()`; `createClient<MapledSchema>(…)` with the generated `MapledSchema` types every read by its key (`MapledClient<S>`, `MapledAppClient<S>` name the typed clients)
|
|
161
445
|
- list `opts`: `filter` (a value for equality, or `{ eq, ne, lt, lte, gt, gte, in, contains }` — text takes eq/ne/in/contains, numbers and dates comparisons, booleans eq/ne, image and relation ids eq/ne/in; a one-to-many relation matches when it includes the id), `sort` (`"field"` / `"-field"`, not on relations or images), `limit` (≤100), `offset`
|
|
162
446
|
- every read: `expand` (relation keys, dotted for a second level), `fields` (data keys to keep), `release` (pin to a published release), `revalidate`, `tags`; a query the schema can't answer fails with a 400 `INVALID_QUERY` message, and errors carry the API's `code` on `MapledError`
|
|
447
|
+
- `createAppClient({ serverKey, apiUrl? })` → `list(collection, opts?)`, `get(collection, id)`, `create(collection, data)`, `update(collection, id, data)`, `remove(collection, id)`
|
|
448
|
+
- `assetUrl(id, { width?, height?, fit?, format?, quality?, apiUrl? })` — URL of an image or file value
|
|
163
449
|
- `createRevalidateHandler({ secret, tags? })` — App Router `POST` handler
|
|
450
|
+
- `createPreviewHandler({ apiUrl?, appUrl? })`, `createExitPreviewHandler()` — App Router `GET` handlers of the preview routes
|
|
164
451
|
- `verifySignature(secret, body, signature)` — if you'd rather build your own handler
|
|
165
452
|
|
|
166
453
|
The delivery key only reads published, public content — safe to use anywhere.
|
package/package.json
CHANGED