@capacms/sdk 1.0.0-next.0 → 1.0.0-next.10
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/CHANGELOG.md +450 -0
- package/README.md +1754 -156
- package/bin/capa-codegen.js +192 -5
- package/bin/capa.js +235 -0
- package/bin/graphql-project.js +142 -0
- package/bin/project-env.js +58 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.js +17 -0
- package/dist/codegen.d.ts +55 -0
- package/dist/codegen.js +320 -39
- package/dist/config.d.ts +5 -36
- package/dist/config.js +47 -1
- package/dist/esm/image/index.d.ts +120 -0
- package/dist/esm/image/index.js +250 -0
- package/dist/esm/image/shared-params.generated.d.ts +190 -0
- package/dist/esm/image/shared-params.generated.js +461 -0
- package/dist/esm/nextjs/image-loader.d.ts +60 -0
- package/dist/esm/nextjs/image-loader.js +67 -0
- package/dist/esm/nextjs/overlay.d.ts +30 -0
- package/dist/esm/nextjs/overlay.js +75 -0
- package/dist/esm/overlay/index.d.ts +32 -0
- package/dist/esm/overlay/index.js +576 -0
- package/dist/esm/overlay/protocol.d.ts +187 -0
- package/dist/esm/overlay/protocol.js +240 -0
- package/dist/esm/package.json +4 -0
- package/dist/graphql-codegen.d.ts +117 -0
- package/dist/graphql-codegen.js +705 -0
- package/dist/http.js +1 -1
- package/dist/image/index.d.ts +120 -0
- package/dist/image/index.js +257 -0
- package/dist/image/shared-params.generated.d.ts +190 -0
- package/dist/image/shared-params.generated.js +471 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -1
- package/dist/next/attrs.d.ts +98 -0
- package/dist/next/attrs.js +125 -0
- package/dist/next/client.d.ts +176 -32
- package/dist/next/client.js +212 -90
- package/dist/next/entry-fields.d.ts +162 -0
- package/dist/next/entry-fields.js +2 -0
- package/dist/next/errors.d.ts +136 -0
- package/dist/next/errors.js +214 -0
- package/dist/next/field-names.d.ts +37 -0
- package/dist/next/field-names.js +145 -0
- package/dist/next/graphql/build.d.ts +27 -0
- package/dist/next/graphql/build.js +98 -0
- package/dist/next/graphql/documents.d.ts +67 -0
- package/dist/next/graphql/documents.js +35 -0
- package/dist/next/graphql/edit-mode.d.ts +16 -0
- package/dist/next/graphql/edit-mode.js +93 -0
- package/dist/next/graphql/filter-values.d.ts +34 -0
- package/dist/next/graphql/filter-values.js +96 -0
- package/dist/next/graphql/introspection.d.ts +89 -0
- package/dist/next/graphql/introspection.js +102 -0
- package/dist/next/graphql/plan.d.ts +115 -0
- package/dist/next/graphql/plan.js +531 -0
- package/dist/next/graphql/request.d.ts +228 -0
- package/dist/next/graphql/request.js +283 -0
- package/dist/next/graphql/rest.d.ts +66 -0
- package/dist/next/graphql/rest.js +502 -0
- package/dist/next/graphql/selection.d.ts +55 -0
- package/dist/next/graphql/selection.js +212 -0
- package/dist/next/graphql/sha256.d.ts +13 -0
- package/dist/next/graphql/sha256.js +86 -0
- package/dist/next/graphql/summary.d.ts +83 -0
- package/dist/next/graphql/summary.js +151 -0
- package/dist/next/graphql/tree-layout.d.ts +36 -0
- package/dist/next/graphql/tree-layout.js +20 -0
- package/dist/next/graphql/tree.d.ts +171 -0
- package/dist/next/graphql/tree.js +249 -0
- package/dist/next/graphql/typed.d.ts +261 -0
- package/dist/next/graphql/typed.js +146 -0
- package/dist/next/index.d.ts +30 -3
- package/dist/next/index.js +34 -1
- package/dist/next/inflate.d.ts +51 -0
- package/dist/next/inflate.js +243 -0
- package/dist/next/key-family.d.ts +31 -0
- package/dist/next/key-family.js +66 -0
- package/dist/next/select-types.d.ts +58 -5
- package/dist/next/system-keys.d.ts +27 -0
- package/dist/next/system-keys.js +42 -0
- package/dist/nextjs/image-loader.d.ts +60 -0
- package/dist/nextjs/image-loader.js +71 -0
- package/dist/nextjs/index.d.ts +484 -5
- package/dist/nextjs/index.js +704 -9
- package/dist/nextjs/overlay.d.ts +30 -0
- package/dist/nextjs/overlay.js +78 -0
- package/dist/overlay/index.d.ts +32 -0
- package/dist/overlay/index.js +596 -0
- package/dist/overlay/protocol.d.ts +187 -0
- package/dist/overlay/protocol.js +253 -0
- package/package.json +70 -15
package/dist/nextjs/index.d.ts
CHANGED
|
@@ -1,20 +1,55 @@
|
|
|
1
|
-
import { type CapaNextClient, type CapaNextConfig, type PagesResource, type PreviewClaim } from "../next";
|
|
1
|
+
import { gql, LAYOUT_PAGE, type CapaDocumentResult, type CapaDocuments, type CapaDocumentVariables, type CapaNextClient, type CapaNextConfig, type GraphQLCallOptions, type GraphQLResult, type NotARecordedDocument, type PagesResource, type PreviewClaim, type TypedDocument, type UntypedQuery, type VariablesThenOptions } from "../next";
|
|
2
2
|
export interface CacheOptions {
|
|
3
3
|
tags?: string[];
|
|
4
4
|
revalidate?: number | false;
|
|
5
5
|
}
|
|
6
6
|
/** Add Next.js fetch-cache options without importing `next/*`. */
|
|
7
7
|
export declare function withCache(fetchImpl: typeof fetch, options: CacheOptions): typeof fetch;
|
|
8
|
+
/**
|
|
9
|
+
* The Next.js cache tag for every read of a model, by its namespace. A GraphQL
|
|
10
|
+
* query names its models by namespace (`articles`), not by id, so this is the
|
|
11
|
+
* tag a GraphQL read is cached under, and `revalidateFromWebhook` revalidates
|
|
12
|
+
* it when an entry of that model changes.
|
|
13
|
+
*/
|
|
14
|
+
export declare function modelTag(namespace: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* The tag `graphql()` keeps a read under when it gives a `revalidate` and no
|
|
17
|
+
* `tags`, and that `revalidateFromWebhook` revalidates on every content
|
|
18
|
+
* change: such a page is never stale after a publish, at the price of
|
|
19
|
+
* refreshing on any publish. Name the models it reads with
|
|
20
|
+
* `tagsFor({ namespace })` to refresh it only when one of those changes. A
|
|
21
|
+
* read with neither `tags` nor `revalidate` is not kept at all.
|
|
22
|
+
*/
|
|
23
|
+
export declare const GRAPHQL_TAG = "capa:graphql";
|
|
24
|
+
/**
|
|
25
|
+
* The tag `tagsFor({ namespace })` adds beside its model tags, and that
|
|
26
|
+
* `revalidateFromWebhook` revalidates on a media event: a GraphQL read shows
|
|
27
|
+
* a file's URL and alt text whichever models it names, so editing a file in
|
|
28
|
+
* the media library refreshes it.
|
|
29
|
+
*/
|
|
30
|
+
export declare const MEDIA_TAG = "capa:media";
|
|
31
|
+
/**
|
|
32
|
+
* Next.js cache tags for a read. `model`, `entry`, `key` and `tenant` are the
|
|
33
|
+
* API's surrogate keys (`m:`, `e:`, `k:`, `t:`), by id; `namespace` is one
|
|
34
|
+
* `capa:model:<namespace>` tag per model a GraphQL query reads, which
|
|
35
|
+
* `capa-codegen --graphql` lists as `<Name>Models`, and `MEDIA_TAG`, since
|
|
36
|
+
* the query may show a file from the media library. Each pairs with
|
|
37
|
+
* `revalidateFromWebhook`.
|
|
38
|
+
*/
|
|
8
39
|
export declare function tagsFor(input: {
|
|
9
40
|
model?: string;
|
|
10
41
|
entry?: string;
|
|
11
42
|
key?: string;
|
|
12
43
|
tenant?: string;
|
|
44
|
+
namespace?: string | readonly string[];
|
|
13
45
|
}): string[];
|
|
14
46
|
export interface WebhookPayload {
|
|
47
|
+
type?: unknown;
|
|
15
48
|
data?: {
|
|
16
49
|
instanceId?: unknown;
|
|
17
50
|
modelId?: unknown;
|
|
51
|
+
modelNamespace?: unknown;
|
|
52
|
+
namespace?: unknown;
|
|
18
53
|
[key: string]: unknown;
|
|
19
54
|
};
|
|
20
55
|
instanceId?: unknown;
|
|
@@ -22,17 +57,34 @@ export interface WebhookPayload {
|
|
|
22
57
|
modelId?: unknown;
|
|
23
58
|
[key: string]: unknown;
|
|
24
59
|
}
|
|
25
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Revalidate every tag a webhook's entry and model can be cached under: the
|
|
62
|
+
* entry (`e:`), the model by id (`m:`) and by namespace (`capa:model:`, the
|
|
63
|
+
* tag a GraphQL read uses). The namespace is `data.modelNamespace` on
|
|
64
|
+
* `instance.published`, `instance.unpublished` and `model.published`, and
|
|
65
|
+
* `data.namespace` on the other events (docs/WEBHOOKS.md). Any of those, and
|
|
66
|
+
* a `media.` event, also revalidates `GRAPHQL_TAG`, the tag of a `graphql()`
|
|
67
|
+
* read that named no tags. A `media.` event (a file's alt text, name or
|
|
68
|
+
* visibility changed, or the file went) revalidates the file's own key
|
|
69
|
+
* (`f:<fileId>`, as REST's `Surrogate-Key` names it) and `MEDIA_TAG`, which
|
|
70
|
+
* every read tagged by namespace carries, since it may show that file.
|
|
71
|
+
*/
|
|
26
72
|
export declare function revalidateFromWebhook(input: {
|
|
27
73
|
payload: WebhookPayload;
|
|
28
74
|
revalidateTag: (tag: string) => void | Promise<void>;
|
|
29
75
|
}): Promise<string[]>;
|
|
30
|
-
/**
|
|
31
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Select a client using server-only draft state supplied by the caller. Pass
|
|
78
|
+
* codegen's `CapaQuery` to type the builder, as with `createClient`:
|
|
79
|
+
* `draftClient<CapaQuery>({ ... })`. `production` may hold the legacy key a
|
|
80
|
+
* site already has; `draft` needs a `cap_` key, and a legacy one throws a
|
|
81
|
+
* `TypeError` when draft mode selects it.
|
|
82
|
+
*/
|
|
83
|
+
export declare function draftClient<Q = UntypedQuery>(input: {
|
|
32
84
|
production: CapaNextConfig;
|
|
33
85
|
draft: CapaNextConfig;
|
|
34
86
|
isDraft: () => boolean | Promise<boolean>;
|
|
35
|
-
}): Promise<CapaNextClient
|
|
87
|
+
}): Promise<CapaNextClient<Q>>;
|
|
36
88
|
export declare function routeOf(file: string): string;
|
|
37
89
|
/**
|
|
38
90
|
* Verify a preview token from a Next route handler.
|
|
@@ -50,4 +102,431 @@ export declare function preview(token: string, client: CapaNextClient): Promise<
|
|
|
50
102
|
* this and never hold the whole client.
|
|
51
103
|
*/
|
|
52
104
|
export declare function pagesFor(client: CapaNextClient): PagesResource;
|
|
105
|
+
export { gql, LAYOUT_PAGE };
|
|
53
106
|
export type { PreviewClaim };
|
|
107
|
+
export { capaImageLoader, createCapaImageLoader } from "./image-loader";
|
|
108
|
+
export type { CapaImageLoader, CapaImageLoaderOptions, CapaImageLoaderProps } from "./image-loader";
|
|
109
|
+
/**
|
|
110
|
+
* The query parameter that turns edit mode on for one request without draft
|
|
111
|
+
* content: `?capa-edit=<token>`, where the token is a Capa preview token. The
|
|
112
|
+
* Capa editor's Published view sends it so the published page is still
|
|
113
|
+
* clickable. It never switches the site to draft data.
|
|
114
|
+
*/
|
|
115
|
+
export declare const EDIT_PARAM = "capa-edit";
|
|
116
|
+
/** The query parameter a Capa preview link carries: `?capa-preview=<token>`. */
|
|
117
|
+
export declare const PREVIEW_PARAM = "capa-preview";
|
|
118
|
+
/** The query parameter of the editor's Published view: `?capa-view=published`. */
|
|
119
|
+
export declare const VIEW_PARAM = "capa-view";
|
|
120
|
+
/**
|
|
121
|
+
* The request header `resolveEditRequest` sets once a `capa-edit` token has
|
|
122
|
+
* been verified, for `editMode()` to read. Any copy a browser sent is removed
|
|
123
|
+
* first, so it cannot be forged from outside.
|
|
124
|
+
*/
|
|
125
|
+
export declare const EDIT_HEADER = "x-capa-edit";
|
|
126
|
+
/** The cookie Next's `draftMode().enable()` sets. */
|
|
127
|
+
export declare const DRAFT_COOKIE = "__prerender_bypass";
|
|
128
|
+
/** What every edit-mode response must send: never cached, never shared. */
|
|
129
|
+
export declare const EDIT_CACHE_CONTROL = "private, no-store";
|
|
130
|
+
interface HeaderReader {
|
|
131
|
+
get(name: string): string | null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Whether this request renders in edit mode: Next draft mode is on, or the
|
|
135
|
+
* request carried a verified `capa-edit` token (see `resolveEditRequest`).
|
|
136
|
+
*
|
|
137
|
+
* Pass Next's own functions; this package imports nothing from `next`:
|
|
138
|
+
*
|
|
139
|
+
* import { draftMode, headers } from "next/headers";
|
|
140
|
+
* const edit = await editMode({ draftMode, headers });
|
|
141
|
+
* const client = createClient({ ...config, editMode: edit });
|
|
142
|
+
*
|
|
143
|
+
* A client built with `editMode: edit` marks what it reads, and `capaAttrs`
|
|
144
|
+
* then tags those entries and only those.
|
|
145
|
+
*/
|
|
146
|
+
export declare function editMode(input: {
|
|
147
|
+
draftMode: () => {
|
|
148
|
+
isEnabled: boolean;
|
|
149
|
+
} | Promise<{
|
|
150
|
+
isEnabled: boolean;
|
|
151
|
+
}>;
|
|
152
|
+
headers: () => HeaderReader | Promise<HeaderReader>;
|
|
153
|
+
}): Promise<boolean>;
|
|
154
|
+
export interface EditRequest {
|
|
155
|
+
/** True when the page must render in edit mode. */
|
|
156
|
+
edit: boolean;
|
|
157
|
+
/** True when a `capa-edit` token was presented and Capa accepted it. */
|
|
158
|
+
verified: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* The request headers to forward: `x-capa-edit` removed, then set to `1`
|
|
161
|
+
* when the token verified. Hand them to `NextResponse.next({ request: { headers } })`.
|
|
162
|
+
*/
|
|
163
|
+
headers: Headers;
|
|
164
|
+
/** `EDIT_CACHE_CONTROL` in edit mode, otherwise null. Set it on the response. */
|
|
165
|
+
cacheControl: string | null;
|
|
166
|
+
/** `DRAFT_ROBOTS_TAG` in edit mode, otherwise null. Set it on the response as `X-Robots-Tag`. */
|
|
167
|
+
robotsTag: string | null;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The middleware half of edit mode.
|
|
171
|
+
*
|
|
172
|
+
* export async function middleware(request: NextRequest) {
|
|
173
|
+
* const edit = await resolveEditRequest(request, publishedClient());
|
|
174
|
+
* const response = NextResponse.next({ request: { headers: edit.headers } });
|
|
175
|
+
* if (edit.cacheControl) response.headers.set("Cache-Control", edit.cacheControl);
|
|
176
|
+
* if (edit.robotsTag) response.headers.set("X-Robots-Tag", edit.robotsTag);
|
|
177
|
+
* return response;
|
|
178
|
+
* }
|
|
179
|
+
*
|
|
180
|
+
* The token is checked with Capa (`client.preview`), exactly as a preview link
|
|
181
|
+
* is: the site never holds the signing key. A bad, expired or unverifiable
|
|
182
|
+
* token means not in edit mode; it never throws, because a broken edit link
|
|
183
|
+
* must still render the public page.
|
|
184
|
+
*/
|
|
185
|
+
export declare function resolveEditRequest(request: {
|
|
186
|
+
url: string | URL;
|
|
187
|
+
headers: Headers;
|
|
188
|
+
cookies?: {
|
|
189
|
+
has(name: string): boolean;
|
|
190
|
+
};
|
|
191
|
+
}, client: Pick<CapaNextClient, "preview">): Promise<EditRequest>;
|
|
192
|
+
/**
|
|
193
|
+
* Where the env-driven helpers read their settings (M6): the one pair of
|
|
194
|
+
* names the whole product uses (the MCP server, `capa-codegen`, `capa
|
|
195
|
+
* persist`, every curl example in the API docs).
|
|
196
|
+
*/
|
|
197
|
+
export declare const CAPA_ENV: {
|
|
198
|
+
readonly baseUrl: "CAPA_API_URL";
|
|
199
|
+
readonly apiKey: "CAPA_KEY";
|
|
200
|
+
readonly draftKey: "CAPA_DRAFT_KEY";
|
|
201
|
+
readonly version: "CAPA_API_VERSION";
|
|
202
|
+
};
|
|
203
|
+
/** Older names that still work, read only when the name above is unset. */
|
|
204
|
+
export declare const CAPA_ENV_ALIASES: Readonly<Record<string, string>>;
|
|
205
|
+
export declare const DEFAULT_API_VERSION = "2026-10-01";
|
|
206
|
+
type DraftModeFn = () => {
|
|
207
|
+
isEnabled: boolean;
|
|
208
|
+
enable?: () => void;
|
|
209
|
+
disable?: () => void;
|
|
210
|
+
} | Promise<{
|
|
211
|
+
isEnabled: boolean;
|
|
212
|
+
enable?: () => void;
|
|
213
|
+
disable?: () => void;
|
|
214
|
+
}>;
|
|
215
|
+
/**
|
|
216
|
+
* The published-key client from env: what verifying a token needs. `overrides`
|
|
217
|
+
* win over env, and pass codegen's `CapaQuery` to type the builder:
|
|
218
|
+
* `getPublishedClient<CapaQuery>()`.
|
|
219
|
+
*/
|
|
220
|
+
export declare function getPublishedClient<Q = UntypedQuery>(overrides?: Partial<CapaNextConfig>): CapaNextClient<Q>;
|
|
221
|
+
/**
|
|
222
|
+
* The client for this request, from env: the draft key under draft mode,
|
|
223
|
+
* otherwise the published key, and `editMode` worked out for you.
|
|
224
|
+
*
|
|
225
|
+
* import { draftMode, headers } from "next/headers";
|
|
226
|
+
* const capa = await getCapaClient<CapaQuery>({ draftMode, headers });
|
|
227
|
+
* const { data } = await capa.graphql.query(selection, { tags: tagsFor({ namespace: "articles" }) });
|
|
228
|
+
*
|
|
229
|
+
* `CapaQuery` (from `capa-codegen --graphql`) types the builder, as with
|
|
230
|
+
* `createClient`; leave it out for an untyped client. `config` wins over env.
|
|
231
|
+
*
|
|
232
|
+
* Its REST reads are sent as `createClient` sends them, which Next does not
|
|
233
|
+
* keep. Its GraphQL reads, a document or the builder, are sent the same way
|
|
234
|
+
* unless a call gives `tags` or `revalidate`, so a publish shows up on both
|
|
235
|
+
* alike. Given either, a read is kept in Next's data cache exactly as
|
|
236
|
+
* `graphql()` keeps it: a published read with no errors, never a draft or an
|
|
237
|
+
* edit-mode page.
|
|
238
|
+
*/
|
|
239
|
+
export declare function getCapaClient<Q = UntypedQuery>(input: {
|
|
240
|
+
draftMode: DraftModeFn;
|
|
241
|
+
headers: () => HeaderReader | Promise<HeaderReader>;
|
|
242
|
+
config?: Partial<CapaNextConfig>;
|
|
243
|
+
/** Next's `unstable_cache`, as `graphql()` takes it. Leave it out: Next's own is loaded. */
|
|
244
|
+
unstable_cache?: UnstableCache;
|
|
245
|
+
}): Promise<CapaNextClient<Q, NextCacheOptions>>;
|
|
246
|
+
/** Where a GraphQL read in Next keeps its answer: Next's data cache, as `graphql()` and `getCapaClient` keep it. */
|
|
247
|
+
export interface NextCacheOptions extends GraphQLCallOptions {
|
|
248
|
+
/**
|
|
249
|
+
* Next.js cache tags to keep this read under. `tagsFor({ namespace:
|
|
250
|
+
* <Name>Models })`, with the list `capa-codegen --graphql` writes beside
|
|
251
|
+
* each document, tags it with every model the query reads, which
|
|
252
|
+
* `revalidateFromWebhook` revalidates on a publish. Given a `revalidate`
|
|
253
|
+
* and no tags, the read is kept under `GRAPHQL_TAG`, which every publish
|
|
254
|
+
* revalidates. Given neither, the read is not kept: it is sent as a REST
|
|
255
|
+
* read is, on every render.
|
|
256
|
+
*/
|
|
257
|
+
tags?: string[];
|
|
258
|
+
/** Seconds to cache, `false` to cache until a tag is revalidated, or 0 not to cache. */
|
|
259
|
+
revalidate?: number | false;
|
|
260
|
+
}
|
|
261
|
+
export interface NextGraphQLOptions extends NextCacheOptions {
|
|
262
|
+
/**
|
|
263
|
+
* Next's `draftMode` and `headers`, to work out draft and edit mode as
|
|
264
|
+
* `getCapaClient` does: drafts under draft mode, and in edit mode every
|
|
265
|
+
* entry the query reads marked for `capaAttrs`.
|
|
266
|
+
*/
|
|
267
|
+
draftMode?: DraftModeFn;
|
|
268
|
+
headers?: () => HeaderReader | Promise<HeaderReader>;
|
|
269
|
+
/**
|
|
270
|
+
* Read drafts with `CAPA_DRAFT_KEY`, uncached. Worked out from `draftMode`
|
|
271
|
+
* when that is passed; set it to decide yourself.
|
|
272
|
+
*/
|
|
273
|
+
draft?: boolean;
|
|
274
|
+
/** Overrides for the env-derived client (base URL, key, version, edit mode, fetch). Each one given is not read from env. */
|
|
275
|
+
config?: Partial<CapaNextConfig>;
|
|
276
|
+
/**
|
|
277
|
+
* Next's `unstable_cache`, from `next/cache`. Leave it out: `graphql()`
|
|
278
|
+
* loads Next's own. A published read given `tags` or a `revalidate` is kept
|
|
279
|
+
* in Next's data cache only when it answered with no `errors`, under `tags`
|
|
280
|
+
* for `revalidate` seconds, or with no `revalidate` until one of its tags is
|
|
281
|
+
* revalidated. Next's fetch
|
|
282
|
+
* cache is not used for it: that cache keeps any 200, and a GraphQL error
|
|
283
|
+
* is a 200 (a root field that timed out), and in Next 15 it keeps nothing
|
|
284
|
+
* without a `revalidate`, tags or not. Pass it only to supply another.
|
|
285
|
+
*/
|
|
286
|
+
unstable_cache?: UnstableCache;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Next's `unstable_cache` (`import { unstable_cache } from "next/cache"`),
|
|
290
|
+
* typed here so this package never imports `next/*`.
|
|
291
|
+
*/
|
|
292
|
+
export type UnstableCache = <T extends (...args: any[]) => Promise<any>>(cb: T, keyParts?: string[], options?: {
|
|
293
|
+
revalidate?: number | false;
|
|
294
|
+
tags?: string[];
|
|
295
|
+
}) => T;
|
|
296
|
+
/**
|
|
297
|
+
* GraphQL in a server component, one line, from env:
|
|
298
|
+
*
|
|
299
|
+
* import { draftMode, headers } from "next/headers";
|
|
300
|
+
* import { graphql, tagsFor } from "@capacms/sdk/nextjs";
|
|
301
|
+
* import { LatestModels } from "./capa-graphql";
|
|
302
|
+
* const LATEST = `#graphql
|
|
303
|
+
* query Latest($first: Int) { articles(first: $first) { nodes { id model title } } }
|
|
304
|
+
* `;
|
|
305
|
+
* const { data } = await graphql(LATEST, { first: 5 }, { draftMode, headers, tags: tagsFor({ namespace: LatestModels }) });
|
|
306
|
+
*
|
|
307
|
+
* Once `capa-codegen --graphql` has seen the literal, `data` and the
|
|
308
|
+
* variables are typed from it, as they are for a `<Name>Document` it writes,
|
|
309
|
+
* and `LatestModels` lists the models it reads.
|
|
310
|
+
*
|
|
311
|
+
* With no `tags` and no `revalidate` a read is sent as the REST reads are,
|
|
312
|
+
* and Next keeps nothing: the page shows a publish on its next render, as a
|
|
313
|
+
* page reading by REST does. Given either, a published read is kept in Next's
|
|
314
|
+
* data cache (`unstable_cache`) when it answered with no errors: under `tags`,
|
|
315
|
+
* for `revalidate` seconds, or with no `revalidate` until
|
|
316
|
+
* `revalidateFromWebhook` revalidates one of its tags on a publish. It goes
|
|
317
|
+
* as a GET, which the CDN and the API also cache for the published key; a
|
|
318
|
+
* document too long for a GET URL goes as a POST, which only Next's data
|
|
319
|
+
* cache keeps. Under draft mode the read uses `CAPA_DRAFT_KEY`, a
|
|
320
|
+
* `cap_` key, and bypasses the cache (the API answers a development key's GET
|
|
321
|
+
* `no-store`), the same split `getCapaClient` makes for REST, and in edit mode
|
|
322
|
+
* each entry that selects `id` and `model` is marked for `capaAttrs`, uncached.
|
|
323
|
+
* Outside a Next request (a script, a test) the read is simply sent.
|
|
324
|
+
*
|
|
325
|
+
* `persisted: true` sends only the hash, and pays off once the documents are
|
|
326
|
+
* stored with `capa persist`, which takes a development key: a production
|
|
327
|
+
* key never stores one. Until then a
|
|
328
|
+
* hash misses, and the client remembers the miss for five minutes and sends
|
|
329
|
+
* the document by POST meanwhile, so it costs one extra GET per five minutes.
|
|
330
|
+
*/
|
|
331
|
+
export declare function graphql<D extends keyof CapaDocuments>(document: D, ...rest: VariablesThenOptions<CapaDocumentVariables<D>, NextGraphQLOptions>): Promise<GraphQLResult<CapaDocumentResult<D>>>;
|
|
332
|
+
export declare function graphql<TData = Record<string, unknown>, TVariables = Record<string, unknown>, D extends string = string>(document: NotARecordedDocument<D, TypedDocument<TData, TVariables> | D>, ...rest: VariablesThenOptions<TVariables, NextGraphQLOptions>): Promise<GraphQLResult<TData>>;
|
|
333
|
+
/** Only a path on this site: never `//elsewhere.example` or a full URL. */
|
|
334
|
+
export declare function safeSitePath(value: string | null | undefined): string;
|
|
335
|
+
/** The Capa admin's origin: what frames a draft in the editor, unless a site names another. */
|
|
336
|
+
export declare const CAPA_ADMIN_ORIGIN = "https://app.capacms.com";
|
|
337
|
+
/** `X-Robots-Tag` on every draft and edit-mode response: a draft is never indexed, nor its links followed. */
|
|
338
|
+
export declare const DRAFT_ROBOTS_TAG = "noindex, nofollow";
|
|
339
|
+
/** How long the draft cookie lasts, in seconds: one hour, as a preview token does. */
|
|
340
|
+
export declare const DRAFT_COOKIE_MAX_AGE = 3600;
|
|
341
|
+
/**
|
|
342
|
+
* `frame-ancestors 'self' <origins>`, the CSP directive that lets the Capa
|
|
343
|
+
* editor frame a draft and nothing else frame it. Each origin is a scheme and
|
|
344
|
+
* a host, with a port when it has one, and is written as the URL parser
|
|
345
|
+
* normalises it, so no `;` or quote can reach the policy. Anything else, a
|
|
346
|
+
* path or a query included, throws a `TypeError`.
|
|
347
|
+
*/
|
|
348
|
+
export declare function frameAncestors(adminOrigins?: readonly string[]): string;
|
|
349
|
+
/**
|
|
350
|
+
* The headers every draft response carries: `X-Robots-Tag: noindex, nofollow`,
|
|
351
|
+
* and `Content-Security-Policy: frame-ancestors 'self' https://app.capacms.com`
|
|
352
|
+
* (or the `adminOrigins` given). Throws for an origin that is not one.
|
|
353
|
+
*/
|
|
354
|
+
export declare function draftHeaders(options?: {
|
|
355
|
+
adminOrigins?: readonly string[];
|
|
356
|
+
}): Record<string, string>;
|
|
357
|
+
/** One rule of `next.config`'s `headers()`, in the shape Next takes. */
|
|
358
|
+
export interface NextHeaderRule {
|
|
359
|
+
source: string;
|
|
360
|
+
has: Array<{
|
|
361
|
+
type: "cookie" | "query";
|
|
362
|
+
key: string;
|
|
363
|
+
}>;
|
|
364
|
+
headers: Array<{
|
|
365
|
+
key: string;
|
|
366
|
+
value: string;
|
|
367
|
+
}>;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Rules for `next.config`'s `headers()` that send `draftHeaders` on draft
|
|
371
|
+
* responses only:
|
|
372
|
+
*
|
|
373
|
+
* // next.config.mjs
|
|
374
|
+
* import { capaHeaders } from "@capacms/sdk/nextjs";
|
|
375
|
+
* export default { async headers() { return [...capaHeaders()]; } };
|
|
376
|
+
*
|
|
377
|
+
* A rule matches a request carrying the draft cookie, or a `capa-preview`,
|
|
378
|
+
* `capa-edit` or `capa-view` query. A visitor's request carries none of
|
|
379
|
+
* them, so its response, cached or not, is exactly what it was. Next checks
|
|
380
|
+
* the cookie by name, not by value, so a forged cookie only adds these
|
|
381
|
+
* headers to the forger's own response.
|
|
382
|
+
*
|
|
383
|
+
* A site that sends its own `frame-ancestors` or `X-Frame-Options` must leave
|
|
384
|
+
* them off draft responses (`missing: [{ type: "cookie", key: DRAFT_COOKIE }]`
|
|
385
|
+
* on its rule): a browser applies every CSP it is sent, so the strictest wins.
|
|
386
|
+
*/
|
|
387
|
+
export declare function capaHeaders(options?: {
|
|
388
|
+
adminOrigins?: readonly string[];
|
|
389
|
+
}): NextHeaderRule[];
|
|
390
|
+
/** The part of Next's `cookies()` store (`next/headers`) the draft-cookie helpers use. */
|
|
391
|
+
export interface DraftCookieJar {
|
|
392
|
+
get(name: string): {
|
|
393
|
+
value: string;
|
|
394
|
+
} | undefined;
|
|
395
|
+
set(cookie: {
|
|
396
|
+
name: string;
|
|
397
|
+
value: string;
|
|
398
|
+
path: string;
|
|
399
|
+
httpOnly: boolean;
|
|
400
|
+
secure: boolean;
|
|
401
|
+
sameSite: "none";
|
|
402
|
+
partitioned: boolean;
|
|
403
|
+
maxAge?: number;
|
|
404
|
+
expires?: Date;
|
|
405
|
+
}): unknown;
|
|
406
|
+
}
|
|
407
|
+
/** Next's `cookies` from `next/headers`, or anything shaped like it. */
|
|
408
|
+
export type CookiesFn = () => DraftCookieJar | Promise<DraftCookieJar>;
|
|
409
|
+
/**
|
|
410
|
+
* Re-set the cookie `draftMode().enable()` just set, so the Capa editor can
|
|
411
|
+
* use it: `HttpOnly; Secure; SameSite=None; Partitioned; Path=/` and
|
|
412
|
+
* `Max-Age` one hour (`maxAge` seconds). Next sets it with no `Max-Age`, so
|
|
413
|
+
* it unlocks every draft on the site until the browser closes, and without
|
|
414
|
+
* `Partitioned`, which Safari 26.2 and later need to send a cookie into a
|
|
415
|
+
* cross-site frame. Call it after `enable()`, in the same route handler.
|
|
416
|
+
* Resolves false, and sets nothing, when there is no draft cookie to re-set.
|
|
417
|
+
*/
|
|
418
|
+
export declare function frameDraftCookie(cookies: CookiesFn, options?: {
|
|
419
|
+
maxAge?: number;
|
|
420
|
+
}): Promise<boolean>;
|
|
421
|
+
/**
|
|
422
|
+
* Delete the draft cookie `frameDraftCookie` set. A partitioned cookie is
|
|
423
|
+
* deleted only by a `Set-Cookie` that is partitioned too, which
|
|
424
|
+
* `draftMode().disable()` is not. Call it after `disable()`; it replaces
|
|
425
|
+
* `disable()`'s own deletion, since a response sets one cookie per name. A
|
|
426
|
+
* draft cookie set without `Partitioned`, before a site used
|
|
427
|
+
* `frameDraftCookie`, ends when the browser closes, as it always did.
|
|
428
|
+
*/
|
|
429
|
+
export declare function clearDraftCookie(cookies: CookiesFn): Promise<void>;
|
|
430
|
+
/**
|
|
431
|
+
* `app/api/capa/preview/route.ts`:
|
|
432
|
+
*
|
|
433
|
+
* import { cookies, draftMode } from "next/headers";
|
|
434
|
+
* export const GET = createPreviewRoute({ draftMode, cookies });
|
|
435
|
+
*
|
|
436
|
+
* Checks the token with Capa (the site never holds the signing key), turns
|
|
437
|
+
* draft mode on and lands on the entry's page. A bad or expired token lands on
|
|
438
|
+
* the page without draft mode and `?preview=expired`; Capa unreachable gives
|
|
439
|
+
* `?preview=unavailable`. The token is checked with any key the site holds,
|
|
440
|
+
* its legacy key included (`getPublishedClient`, from `CAPA_KEY`).
|
|
441
|
+
*
|
|
442
|
+
* Given `cookies`, the draft cookie is re-set by `frameDraftCookie`: one hour,
|
|
443
|
+
* and sent inside the editor's frame. With no `redirect`, the route answers
|
|
444
|
+
* with its own 307, marked `X-Robots-Tag: noindex, nofollow`,
|
|
445
|
+
* `Referrer-Policy: no-referrer` (the token is in the URL),
|
|
446
|
+
* `Cache-Control: private, no-store` and the editor's `frame-ancestors`.
|
|
447
|
+
* Given Next's `redirect`, it is called instead, as before, and the redirect
|
|
448
|
+
* carries none of those.
|
|
449
|
+
*/
|
|
450
|
+
export declare function createPreviewRoute(input: {
|
|
451
|
+
draftMode: DraftModeFn;
|
|
452
|
+
/** Next's `cookies`. Given, the draft cookie lasts `maxAge` seconds and works in the editor's frame. */
|
|
453
|
+
cookies?: CookiesFn;
|
|
454
|
+
/** Next's `redirect`. Leave it out for a redirect that carries the draft headers. */
|
|
455
|
+
redirect?: (url: string) => never | void;
|
|
456
|
+
client?: () => Pick<CapaNextClient, "preview">;
|
|
457
|
+
/** Runs after draft mode is enabled and the cookie re-set, before the redirect (cookie tweaks). */
|
|
458
|
+
onEnable?: () => void | Promise<void>;
|
|
459
|
+
/** The origins allowed to frame a draft. `[CAPA_ADMIN_ORIGIN]` when left out. */
|
|
460
|
+
adminOrigins?: readonly string[];
|
|
461
|
+
/** Seconds the draft cookie lasts, given `cookies`. `DRAFT_COOKIE_MAX_AGE` (one hour) when left out. */
|
|
462
|
+
maxAge?: number;
|
|
463
|
+
}): (request: Request) => Promise<Response | void>;
|
|
464
|
+
/**
|
|
465
|
+
* `app/api/capa/exit/route.ts`:
|
|
466
|
+
*
|
|
467
|
+
* import { cookies, draftMode } from "next/headers";
|
|
468
|
+
* export const GET = exitPreviewRoute({ draftMode, cookies });
|
|
469
|
+
*
|
|
470
|
+
* Turns draft mode off and lands on `?path=`. Given `cookies`, the framed
|
|
471
|
+
* cookie `createPreviewRoute` set is deleted too. With no `redirect`, the route
|
|
472
|
+
* answers with its own 307, marked noindex and never cached.
|
|
473
|
+
*/
|
|
474
|
+
export declare function exitPreviewRoute(input: {
|
|
475
|
+
draftMode: DraftModeFn;
|
|
476
|
+
cookies?: CookiesFn;
|
|
477
|
+
redirect?: (url: string) => never | void;
|
|
478
|
+
}): (request: Request) => Promise<Response | void>;
|
|
479
|
+
interface MiddlewareRequest {
|
|
480
|
+
url: string;
|
|
481
|
+
headers: Headers;
|
|
482
|
+
nextUrl: URL & {
|
|
483
|
+
clone(): URL;
|
|
484
|
+
};
|
|
485
|
+
cookies: {
|
|
486
|
+
getAll(): Array<{
|
|
487
|
+
name: string;
|
|
488
|
+
value: string;
|
|
489
|
+
}>;
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
interface NextResponseLike {
|
|
493
|
+
next(init?: {
|
|
494
|
+
request?: {
|
|
495
|
+
headers?: Headers;
|
|
496
|
+
};
|
|
497
|
+
}): {
|
|
498
|
+
headers: Headers;
|
|
499
|
+
};
|
|
500
|
+
rewrite(url: URL): unknown;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* `middleware.ts` in one line:
|
|
504
|
+
*
|
|
505
|
+
* import { NextResponse } from "next/server";
|
|
506
|
+
* export const middleware = capaMiddleware({ NextResponse });
|
|
507
|
+
*
|
|
508
|
+
* - `?capa-preview=<token>` on any page goes to `previewRoute`, which turns
|
|
509
|
+
* draft mode on and comes back;
|
|
510
|
+
* - `?capa-view=published` renders without the draft cookie, for the editor's
|
|
511
|
+
* Published view;
|
|
512
|
+
* - `?capa-edit=<token>` turns edit mode on (ids and overlay, published data);
|
|
513
|
+
* - every edit-mode response is `private, no-store`, and it and every request
|
|
514
|
+
* carrying `capa-edit` or `capa-view` is `X-Robots-Tag: noindex, nofollow`.
|
|
515
|
+
*
|
|
516
|
+
* Give it a `matcher` so a visitor's request never runs it (Next reads
|
|
517
|
+
* `config` from the file itself, so it is written out there):
|
|
518
|
+
*
|
|
519
|
+
* export const config = {
|
|
520
|
+
* matcher: [
|
|
521
|
+
* { source: "/:path*", has: [{ type: "query", key: "capa-preview" }] },
|
|
522
|
+
* { source: "/:path*", has: [{ type: "query", key: "capa-edit" }] },
|
|
523
|
+
* { source: "/:path*", has: [{ type: "query", key: "capa-view" }] },
|
|
524
|
+
* { source: "/:path*", has: [{ type: "cookie", key: "__prerender_bypass" }] },
|
|
525
|
+
* ],
|
|
526
|
+
* };
|
|
527
|
+
*/
|
|
528
|
+
export declare function capaMiddleware(input: {
|
|
529
|
+
NextResponse: NextResponseLike;
|
|
530
|
+
previewRoute?: string;
|
|
531
|
+
client?: () => Pick<CapaNextClient, "preview">;
|
|
532
|
+
}): (request: MiddlewareRequest) => Promise<unknown>;
|