@capacms/sdk 1.0.0-next.2 → 1.0.0-next.4
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 +140 -7
- package/dist/next/attrs.d.ts +38 -3
- package/dist/next/attrs.js +64 -1
- package/dist/next/client.d.ts +71 -1
- package/dist/next/client.js +81 -8
- package/dist/next/index.d.ts +6 -4
- package/dist/next/index.js +8 -1
- package/dist/next/inflate.d.ts +33 -0
- package/dist/next/inflate.js +229 -0
- package/dist/next/select-types.d.ts +17 -0
- package/dist/nextjs/index.d.ts +176 -1
- package/dist/nextjs/index.js +241 -3
- package/package.json +2 -2
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** The flat body, or an SDK result wrapping one. */
|
|
2
|
+
export interface FlatResponse {
|
|
3
|
+
data: unknown;
|
|
4
|
+
included: Record<string, Record<string, unknown>>;
|
|
5
|
+
/** The select the request sent, when the SDK made it. */
|
|
6
|
+
select?: string;
|
|
7
|
+
}
|
|
8
|
+
/** One level of a select: `*` or not, and the names it wrote, in order. */
|
|
9
|
+
interface Level {
|
|
10
|
+
star: boolean;
|
|
11
|
+
items: Array<{
|
|
12
|
+
name: string;
|
|
13
|
+
expand: Level | null;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The select grammar, read only as far as `inflate` needs it: names, nesting
|
|
18
|
+
* and `*`. Modifiers (`limit:`, `sort:`, `after:`) decide which rows the API
|
|
19
|
+
* returned, which the response already reflects, so they are skipped. The API
|
|
20
|
+
* validated the select before answering, so this parser trusts its shape and
|
|
21
|
+
* throws a `TypeError` only on text it cannot split at all.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseSelectLevels(select: string): Level;
|
|
24
|
+
/**
|
|
25
|
+
* The tree shape of a flat response: `data` with every expansion nested back
|
|
26
|
+
* in, `included` and `select` removed, everything else (`page`, `meta`,
|
|
27
|
+
* `cacheTags`) carried over as it was.
|
|
28
|
+
*
|
|
29
|
+
* `select` is what the request sent; it defaults to `response.select`, which
|
|
30
|
+
* an SDK flat read fills in. A request with no select expanded nothing.
|
|
31
|
+
*/
|
|
32
|
+
export declare function inflate<R extends FlatResponse>(response: R, select?: string): Omit<R, "included" | "select">;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseSelectLevels = parseSelectLevels;
|
|
4
|
+
exports.inflate = inflate;
|
|
5
|
+
/**
|
|
6
|
+
* inflate.ts — turn a `shape=flat` response back into the `shape=tree` one.
|
|
7
|
+
*
|
|
8
|
+
* A flat response carries every expanded entry once, in `included`, keyed by
|
|
9
|
+
* model namespace and then id, and every relation as a `{ id, model }`
|
|
10
|
+
* reference. `inflate` walks the SAME select the request sent and puts each
|
|
11
|
+
* referenced entry back where the tree would have nested it, holding exactly
|
|
12
|
+
* the system keys and fields that path selected. The result is deep-equal to
|
|
13
|
+
* the `shape=tree` body for the same request (amendment 16 of the api-next spec
|
|
14
|
+
* names the one exception).
|
|
15
|
+
*
|
|
16
|
+
* WHY IT NEEDS THE SELECT. An included entry holds the UNION of every path that
|
|
17
|
+
* reached it, and an entry in `data` also carries what any relation path asked
|
|
18
|
+
* of it. Only the select says which of those fields belong at which place in
|
|
19
|
+
* the tree, and which references were expanded. A result from this SDK's
|
|
20
|
+
* `shape: "flat"` read carries the select it sent (`response.select`), so
|
|
21
|
+
* `inflate(response)` is enough; for a body fetched by hand, pass the select
|
|
22
|
+
* you sent as the second argument. A request that sent no select expanded
|
|
23
|
+
* nothing, and `inflate` returns its data unchanged.
|
|
24
|
+
*
|
|
25
|
+
* COPIES, NEVER SHARED INSTANCES. Every entry in the result is a new object,
|
|
26
|
+
* and so is every value inside it: the same author reached from twenty
|
|
27
|
+
* articles comes back as twenty equal, independent objects, exactly as the
|
|
28
|
+
* tree response parses. Mutating one never changes another, the input is never
|
|
29
|
+
* modified, and `JSON.stringify` of the result cannot meet a cycle.
|
|
30
|
+
*
|
|
31
|
+
* CYCLES END WHERE THE SELECT ENDS. `a` related to `b` related to `a` is
|
|
32
|
+
* inflated to the depth the select wrote (at most four levels, the API's cap)
|
|
33
|
+
* and no further: the walk follows the select, never the references, so it
|
|
34
|
+
* cannot loop. A reference the select did not expand stays a reference.
|
|
35
|
+
*/
|
|
36
|
+
const attrs_1 = require("./attrs");
|
|
37
|
+
/** The system keys, in the order the API prints them. */
|
|
38
|
+
const SYSTEM_KEYS = [
|
|
39
|
+
"id",
|
|
40
|
+
"model",
|
|
41
|
+
"status",
|
|
42
|
+
"createdAt",
|
|
43
|
+
"updatedAt",
|
|
44
|
+
"publishedAt",
|
|
45
|
+
"version",
|
|
46
|
+
"folder",
|
|
47
|
+
"tags",
|
|
48
|
+
];
|
|
49
|
+
const ALWAYS_KEYS = new Set(["id", "model", "status"]);
|
|
50
|
+
const SYSTEM_KEY_SET = new Set(SYSTEM_KEYS);
|
|
51
|
+
/**
|
|
52
|
+
* The select grammar, read only as far as `inflate` needs it: names, nesting
|
|
53
|
+
* and `*`. Modifiers (`limit:`, `sort:`, `after:`) decide which rows the API
|
|
54
|
+
* returned, which the response already reflects, so they are skipped. The API
|
|
55
|
+
* validated the select before answering, so this parser trusts its shape and
|
|
56
|
+
* throws a `TypeError` only on text it cannot split at all.
|
|
57
|
+
*/
|
|
58
|
+
function parseSelectLevels(select) {
|
|
59
|
+
let pos = 0;
|
|
60
|
+
const fail = () => {
|
|
61
|
+
throw new TypeError(`@capacms/sdk/next: inflate could not read the select ${JSON.stringify(select)}.`);
|
|
62
|
+
};
|
|
63
|
+
const level = () => {
|
|
64
|
+
const out = { star: false, items: [] };
|
|
65
|
+
for (;;) {
|
|
66
|
+
const start = pos;
|
|
67
|
+
while (pos < select.length && !",()".includes(select[pos]))
|
|
68
|
+
pos += 1;
|
|
69
|
+
const token = select.slice(start, pos);
|
|
70
|
+
if (select[pos] === "(") {
|
|
71
|
+
if (token === "")
|
|
72
|
+
fail();
|
|
73
|
+
pos += 1;
|
|
74
|
+
const inner = level();
|
|
75
|
+
if (select[pos] !== ")")
|
|
76
|
+
fail();
|
|
77
|
+
pos += 1;
|
|
78
|
+
out.items.push({ name: token, expand: inner });
|
|
79
|
+
}
|
|
80
|
+
else if (token === "*") {
|
|
81
|
+
out.star = true;
|
|
82
|
+
}
|
|
83
|
+
else if (token.includes(":")) {
|
|
84
|
+
// A modifier: `limit:2`, `sort:-name`, `after:<cursor>`.
|
|
85
|
+
}
|
|
86
|
+
else if (token !== "") {
|
|
87
|
+
out.items.push({ name: token, expand: null });
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
fail();
|
|
91
|
+
}
|
|
92
|
+
if (select[pos] !== ",")
|
|
93
|
+
return out;
|
|
94
|
+
pos += 1;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const root = level();
|
|
98
|
+
if (pos !== select.length)
|
|
99
|
+
fail();
|
|
100
|
+
return root;
|
|
101
|
+
}
|
|
102
|
+
// ----------------------------------------------------------------- walk ----
|
|
103
|
+
function clone(value) {
|
|
104
|
+
if (Array.isArray(value))
|
|
105
|
+
return value.map(clone);
|
|
106
|
+
if (value && typeof value === "object") {
|
|
107
|
+
const out = {};
|
|
108
|
+
for (const [key, inner] of Object.entries(value))
|
|
109
|
+
out[key] = clone(inner);
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
function isReference(value) {
|
|
115
|
+
return (!!value &&
|
|
116
|
+
typeof value === "object" &&
|
|
117
|
+
!Array.isArray(value) &&
|
|
118
|
+
typeof value.id === "string" &&
|
|
119
|
+
!("fields" in value));
|
|
120
|
+
}
|
|
121
|
+
function resolve(index, ref) {
|
|
122
|
+
if (ref.missing)
|
|
123
|
+
return null;
|
|
124
|
+
const fromIncluded = ref.model ? index.included[ref.model]?.[ref.id] : undefined;
|
|
125
|
+
if (fromIncluded && typeof fromIncluded === "object")
|
|
126
|
+
return fromIncluded;
|
|
127
|
+
return index.data.get(ref.id) ?? null;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* One entry as the tree holds it at a place whose select is `level`. `null`
|
|
131
|
+
* is "no select was sent", which is every key and every field, unexpanded.
|
|
132
|
+
*/
|
|
133
|
+
function project(entry, level, index) {
|
|
134
|
+
const fields = (entry.fields ?? {});
|
|
135
|
+
const all = level === null || level.star;
|
|
136
|
+
// A name is a FIELD when the entry has a field of that name, otherwise a
|
|
137
|
+
// system key: a model field shadows a system key of the same name (spec
|
|
138
|
+
// 3.3), and a path that selected the field is exactly what put it here.
|
|
139
|
+
const wanted = new Set(ALWAYS_KEYS);
|
|
140
|
+
if (level) {
|
|
141
|
+
for (const item of level.items) {
|
|
142
|
+
if (!(item.name in fields) && SYSTEM_KEY_SET.has(item.name))
|
|
143
|
+
wanted.add(item.name);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const out = {};
|
|
147
|
+
for (const key of SYSTEM_KEYS) {
|
|
148
|
+
if (!(key in entry))
|
|
149
|
+
continue;
|
|
150
|
+
if (all || wanted.has(key))
|
|
151
|
+
out[key] = clone(entry[key]);
|
|
152
|
+
}
|
|
153
|
+
const expansions = new Map();
|
|
154
|
+
const names = [];
|
|
155
|
+
if (level) {
|
|
156
|
+
for (const item of level.items) {
|
|
157
|
+
if (item.expand)
|
|
158
|
+
expansions.set(item.name, item.expand);
|
|
159
|
+
if (item.name in fields && !all)
|
|
160
|
+
names.push(item.name);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (all)
|
|
164
|
+
names.push(...Object.keys(fields));
|
|
165
|
+
const outFields = {};
|
|
166
|
+
for (const name of names) {
|
|
167
|
+
const expand = expansions.get(name);
|
|
168
|
+
outFields[name] = expand ? expandValue(fields[name], expand, index) : clone(fields[name]);
|
|
169
|
+
}
|
|
170
|
+
out.fields = outFields;
|
|
171
|
+
if ((0, attrs_1.isEditEntry)(entry)) {
|
|
172
|
+
Object.defineProperty(out, attrs_1.CAPA_EDIT, { value: true, enumerable: false, configurable: true });
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
/** A reference, or an array relation's `{ items, pageInfo }`, put back in place. */
|
|
177
|
+
function expandValue(value, level, index) {
|
|
178
|
+
if (isReference(value)) {
|
|
179
|
+
const target = resolve(index, value);
|
|
180
|
+
return target ? project(target, level, index) : clone(value);
|
|
181
|
+
}
|
|
182
|
+
if (value && typeof value === "object" && Array.isArray(value.items)) {
|
|
183
|
+
const list = value;
|
|
184
|
+
const out = {};
|
|
185
|
+
for (const [key, inner] of Object.entries(list)) {
|
|
186
|
+
out[key] =
|
|
187
|
+
key === "items"
|
|
188
|
+
? list.items.map((item) => expandValue(item, level, index))
|
|
189
|
+
: clone(inner);
|
|
190
|
+
}
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
return clone(value);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* The tree shape of a flat response: `data` with every expansion nested back
|
|
197
|
+
* in, `included` and `select` removed, everything else (`page`, `meta`,
|
|
198
|
+
* `cacheTags`) carried over as it was.
|
|
199
|
+
*
|
|
200
|
+
* `select` is what the request sent; it defaults to `response.select`, which
|
|
201
|
+
* an SDK flat read fills in. A request with no select expanded nothing.
|
|
202
|
+
*/
|
|
203
|
+
function inflate(response, select) {
|
|
204
|
+
if (!response || typeof response !== "object" || !("included" in response)) {
|
|
205
|
+
throw new TypeError("@capacms/sdk/next: inflate takes a shape=flat response, which carries included.");
|
|
206
|
+
}
|
|
207
|
+
const text = select ?? response.select;
|
|
208
|
+
const level = text === undefined || text === null ? null : parseSelectLevels(text);
|
|
209
|
+
const rows = Array.isArray(response.data) ? response.data : [response.data];
|
|
210
|
+
const index = { included: response.included ?? {}, data: new Map() };
|
|
211
|
+
for (const row of rows) {
|
|
212
|
+
if (row && typeof row === "object" && typeof row.id === "string") {
|
|
213
|
+
index.data.set(row.id, row);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const inflateRow = (row) => row && typeof row === "object" ? project(row, level, index) : row;
|
|
217
|
+
const out = {};
|
|
218
|
+
for (const [key, value] of Object.entries(response)) {
|
|
219
|
+
if (key === "included" || key === "select")
|
|
220
|
+
continue;
|
|
221
|
+
if (key === "data") {
|
|
222
|
+
out.data = Array.isArray(value) ? value.map(inflateRow) : inflateRow(value);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
out[key] = value;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
@@ -36,4 +36,21 @@ export type SelectItem<T> = "*" | ScalarKeys<T> | {
|
|
|
36
36
|
}[RelationKeys<T>];
|
|
37
37
|
/** A typed form of the `/api/entries` `select` grammar. */
|
|
38
38
|
export type Select<T> = ReadonlyArray<SelectItem<T>>;
|
|
39
|
+
type Depth = [never, 0, 1, 2, 3, 4];
|
|
40
|
+
/** The target types one select item expands, and everything below them. */
|
|
41
|
+
type ItemTargets<T, I, D extends number> = [D] extends [never] ? never : I extends string ? never : {
|
|
42
|
+
[K in Extract<keyof I, RelationKeys<T>>]: RelationTarget<T[K]> | ValueTargets<RelationTarget<T[K]>, I[K], Depth[D]>;
|
|
43
|
+
}[Extract<keyof I, RelationKeys<T>>];
|
|
44
|
+
type ValueTargets<R, V, D extends number> = V extends "*" ? never : V extends {
|
|
45
|
+
select: infer S;
|
|
46
|
+
} ? ExpandedTargetsAt<R, S, D> : ExpandedTargetsAt<R, V, D>;
|
|
47
|
+
type ExpandedTargetsAt<T, S, D extends number> = S extends ReadonlyArray<infer I> ? ItemTargets<T, I, D> : never;
|
|
48
|
+
/**
|
|
49
|
+
* Every entry type a select EXPANDS, at any depth: what `included` can hold
|
|
50
|
+
* for a `shape: "flat"` read. `[{ author: ["name"] }]` on an article is
|
|
51
|
+
* `Author`; a select given as a string cannot be read by the type system and is
|
|
52
|
+
* `Record<string, unknown>`. Bounded at the API's four levels, so a model that
|
|
53
|
+
* relates to itself does not recurse forever.
|
|
54
|
+
*/
|
|
55
|
+
export type ExpandedTargets<T, S> = S extends string ? Record<string, unknown> : [ExpandedTargetsAt<T, S, 4>] extends [never] ? never : ExpandedTargetsAt<T, S, 4>;
|
|
39
56
|
export {};
|
package/dist/nextjs/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CapaNextClient, type CapaNextConfig, type PagesResource, type PreviewClaim } from "../next";
|
|
1
|
+
import { LAYOUT_PAGE, type CapaNextClient, type CapaNextConfig, type PagesResource, type PreviewClaim } from "../next";
|
|
2
2
|
export interface CacheOptions {
|
|
3
3
|
tags?: string[];
|
|
4
4
|
revalidate?: number | false;
|
|
@@ -50,4 +50,179 @@ export declare function preview(token: string, client: CapaNextClient): Promise<
|
|
|
50
50
|
* this and never hold the whole client.
|
|
51
51
|
*/
|
|
52
52
|
export declare function pagesFor(client: CapaNextClient): PagesResource;
|
|
53
|
+
export { LAYOUT_PAGE };
|
|
53
54
|
export type { PreviewClaim };
|
|
55
|
+
/**
|
|
56
|
+
* The query parameter that turns edit mode on for one request without draft
|
|
57
|
+
* content: `?capa-edit=<token>`, where the token is a Capa preview token. The
|
|
58
|
+
* Capa editor's Published view sends it so the published page is still
|
|
59
|
+
* clickable. It never switches the site to draft data.
|
|
60
|
+
*/
|
|
61
|
+
export declare const EDIT_PARAM = "capa-edit";
|
|
62
|
+
/**
|
|
63
|
+
* The request header `resolveEditRequest` sets once a `capa-edit` token has
|
|
64
|
+
* been verified, for `editMode()` to read. Any copy a browser sent is removed
|
|
65
|
+
* first, so it cannot be forged from outside.
|
|
66
|
+
*/
|
|
67
|
+
export declare const EDIT_HEADER = "x-capa-edit";
|
|
68
|
+
/** The cookie Next's `draftMode().enable()` sets. */
|
|
69
|
+
export declare const DRAFT_COOKIE = "__prerender_bypass";
|
|
70
|
+
/** What every edit-mode response must send: never cached, never shared. */
|
|
71
|
+
export declare const EDIT_CACHE_CONTROL = "private, no-store";
|
|
72
|
+
interface HeaderReader {
|
|
73
|
+
get(name: string): string | null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Whether this request renders in edit mode: Next draft mode is on, or the
|
|
77
|
+
* request carried a verified `capa-edit` token (see `resolveEditRequest`).
|
|
78
|
+
*
|
|
79
|
+
* Pass Next's own functions; this package imports nothing from `next`:
|
|
80
|
+
*
|
|
81
|
+
* import { draftMode, headers } from "next/headers";
|
|
82
|
+
* const edit = await editMode({ draftMode, headers });
|
|
83
|
+
* const client = createClient({ ...config, editMode: edit });
|
|
84
|
+
*
|
|
85
|
+
* A client built with `editMode: edit` marks what it reads, and `capaAttrs`
|
|
86
|
+
* then tags those entries and only those.
|
|
87
|
+
*/
|
|
88
|
+
export declare function editMode(input: {
|
|
89
|
+
draftMode: () => {
|
|
90
|
+
isEnabled: boolean;
|
|
91
|
+
} | Promise<{
|
|
92
|
+
isEnabled: boolean;
|
|
93
|
+
}>;
|
|
94
|
+
headers: () => HeaderReader | Promise<HeaderReader>;
|
|
95
|
+
}): Promise<boolean>;
|
|
96
|
+
export interface EditRequest {
|
|
97
|
+
/** True when the page must render in edit mode. */
|
|
98
|
+
edit: boolean;
|
|
99
|
+
/** True when a `capa-edit` token was presented and Capa accepted it. */
|
|
100
|
+
verified: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* The request headers to forward: `x-capa-edit` removed, then set to `1`
|
|
103
|
+
* when the token verified. Hand them to `NextResponse.next({ request: { headers } })`.
|
|
104
|
+
*/
|
|
105
|
+
headers: Headers;
|
|
106
|
+
/** `EDIT_CACHE_CONTROL` in edit mode, otherwise null. Set it on the response. */
|
|
107
|
+
cacheControl: string | null;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The middleware half of edit mode.
|
|
111
|
+
*
|
|
112
|
+
* export async function middleware(request: NextRequest) {
|
|
113
|
+
* const edit = await resolveEditRequest(request, publishedClient());
|
|
114
|
+
* const response = NextResponse.next({ request: { headers: edit.headers } });
|
|
115
|
+
* if (edit.cacheControl) response.headers.set("Cache-Control", edit.cacheControl);
|
|
116
|
+
* return response;
|
|
117
|
+
* }
|
|
118
|
+
*
|
|
119
|
+
* The token is checked with Capa (`client.preview`), exactly as a preview link
|
|
120
|
+
* is: the site never holds the signing key. A bad, expired or unverifiable
|
|
121
|
+
* token means not in edit mode; it never throws, because a broken edit link
|
|
122
|
+
* must still render the public page.
|
|
123
|
+
*/
|
|
124
|
+
export declare function resolveEditRequest(request: {
|
|
125
|
+
url: string | URL;
|
|
126
|
+
headers: Headers;
|
|
127
|
+
cookies?: {
|
|
128
|
+
has(name: string): boolean;
|
|
129
|
+
};
|
|
130
|
+
}, client: Pick<CapaNextClient, "preview">): Promise<EditRequest>;
|
|
131
|
+
/** Where the env-driven helpers read their settings (M6). */
|
|
132
|
+
export declare const CAPA_ENV: {
|
|
133
|
+
readonly baseUrl: "CAPA_API_URL";
|
|
134
|
+
readonly apiKey: "CAPA_KEY";
|
|
135
|
+
readonly draftKey: "CAPA_DRAFT_KEY";
|
|
136
|
+
readonly version: "CAPA_API_VERSION";
|
|
137
|
+
};
|
|
138
|
+
export declare const DEFAULT_API_VERSION = "2026-10-01";
|
|
139
|
+
type DraftModeFn = () => {
|
|
140
|
+
isEnabled: boolean;
|
|
141
|
+
enable?: () => void;
|
|
142
|
+
disable?: () => void;
|
|
143
|
+
} | Promise<{
|
|
144
|
+
isEnabled: boolean;
|
|
145
|
+
enable?: () => void;
|
|
146
|
+
disable?: () => void;
|
|
147
|
+
}>;
|
|
148
|
+
/** The published-key client from env: what verifying a token needs. */
|
|
149
|
+
export declare function getPublishedClient(overrides?: Partial<CapaNextConfig>): CapaNextClient;
|
|
150
|
+
/**
|
|
151
|
+
* The client for this request, from env: the draft key under draft mode,
|
|
152
|
+
* otherwise the published key, and `editMode` worked out for you.
|
|
153
|
+
*
|
|
154
|
+
* import { draftMode, headers } from "next/headers";
|
|
155
|
+
* const capa = await getCapaClient({ draftMode, headers });
|
|
156
|
+
*/
|
|
157
|
+
export declare function getCapaClient(input: {
|
|
158
|
+
draftMode: DraftModeFn;
|
|
159
|
+
headers: () => HeaderReader | Promise<HeaderReader>;
|
|
160
|
+
config?: Partial<CapaNextConfig>;
|
|
161
|
+
}): Promise<CapaNextClient>;
|
|
162
|
+
/** Only a path on this site: never `//elsewhere.example` or a full URL. */
|
|
163
|
+
export declare function safeSitePath(value: string | null | undefined): string;
|
|
164
|
+
/**
|
|
165
|
+
* `app/api/capa/preview/route.ts`:
|
|
166
|
+
*
|
|
167
|
+
* import { draftMode } from "next/headers";
|
|
168
|
+
* import { redirect } from "next/navigation";
|
|
169
|
+
* export const GET = createPreviewRoute({ draftMode, redirect });
|
|
170
|
+
*
|
|
171
|
+
* Checks the token with Capa (the site never holds the signing key), turns
|
|
172
|
+
* draft mode on and lands on the entry's page. A bad or expired token lands on
|
|
173
|
+
* the page without draft mode and `?preview=expired`; Capa unreachable gives
|
|
174
|
+
* `?preview=unavailable`.
|
|
175
|
+
*/
|
|
176
|
+
export declare function createPreviewRoute(input: {
|
|
177
|
+
draftMode: DraftModeFn;
|
|
178
|
+
redirect: (url: string) => never | void;
|
|
179
|
+
client?: () => Pick<CapaNextClient, "preview">;
|
|
180
|
+
/** Runs after draft mode is enabled, before the redirect (cookie tweaks). */
|
|
181
|
+
onEnable?: () => void | Promise<void>;
|
|
182
|
+
}): (request: Request) => Promise<Response | void>;
|
|
183
|
+
/** `app/api/capa/exit/route.ts`: `export const GET = exitPreviewRoute({ draftMode, redirect });` */
|
|
184
|
+
export declare function exitPreviewRoute(input: {
|
|
185
|
+
draftMode: DraftModeFn;
|
|
186
|
+
redirect: (url: string) => never | void;
|
|
187
|
+
}): (request: Request) => Promise<Response | void>;
|
|
188
|
+
interface MiddlewareRequest {
|
|
189
|
+
url: string;
|
|
190
|
+
headers: Headers;
|
|
191
|
+
nextUrl: URL & {
|
|
192
|
+
clone(): URL;
|
|
193
|
+
};
|
|
194
|
+
cookies: {
|
|
195
|
+
getAll(): Array<{
|
|
196
|
+
name: string;
|
|
197
|
+
value: string;
|
|
198
|
+
}>;
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
interface NextResponseLike {
|
|
202
|
+
next(init?: {
|
|
203
|
+
request?: {
|
|
204
|
+
headers?: Headers;
|
|
205
|
+
};
|
|
206
|
+
}): {
|
|
207
|
+
headers: Headers;
|
|
208
|
+
};
|
|
209
|
+
rewrite(url: URL): unknown;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* `middleware.ts` in one line:
|
|
213
|
+
*
|
|
214
|
+
* import { NextResponse } from "next/server";
|
|
215
|
+
* export const middleware = capaMiddleware({ NextResponse });
|
|
216
|
+
*
|
|
217
|
+
* - `?capa-preview=<token>` on any page goes to `previewRoute`, which turns
|
|
218
|
+
* draft mode on and comes back;
|
|
219
|
+
* - `?capa-view=published` renders without the draft cookie, for the editor's
|
|
220
|
+
* Published view;
|
|
221
|
+
* - `?capa-edit=<token>` turns edit mode on (ids and overlay, published data);
|
|
222
|
+
* - every edit-mode response is `private, no-store`.
|
|
223
|
+
*/
|
|
224
|
+
export declare function capaMiddleware(input: {
|
|
225
|
+
NextResponse: NextResponseLike;
|
|
226
|
+
previewRoute?: string;
|
|
227
|
+
client?: () => Pick<CapaNextClient, "preview">;
|
|
228
|
+
}): (request: MiddlewareRequest) => Promise<unknown>;
|