@capacms/sdk 1.0.0-next.6 → 1.0.0-next.7
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 +22 -0
- package/README.md +47 -21
- package/dist/config.d.ts +0 -35
- package/dist/config.js +47 -1
- package/dist/next/client.d.ts +3 -2
- package/dist/next/client.js +17 -3
- package/dist/nextjs/index.d.ts +33 -24
- package/dist/nextjs/index.js +37 -14
- package/package.json +1 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- 1.0.0-next.7. Browsers: the `/api/` and legacy clients called the platform `fetch` as a
|
|
6
|
+
method of their config, which a browser refuses ("Failed to execute 'fetch'
|
|
7
|
+
on 'Window': Illegal invocation"), so every read from a browser failed unless
|
|
8
|
+
`fetch` was passed in. The default fetch is now called through `globalThis`
|
|
9
|
+
on each request, which also picks up a fetch a framework patches in later.
|
|
10
|
+
- `next` is no longer a peer dependency. No range matches every Next canary,
|
|
11
|
+
so a site on `next@16.3.0-canary.39` could not `npm install` the SDK without
|
|
12
|
+
`--legacy-peer-deps`. `@capacms/sdk/nextjs/overlay` still imports `next`,
|
|
13
|
+
which only a Next site loads.
|
|
14
|
+
- Docs: the edit mark is dropped wherever an entry is serialized to the
|
|
15
|
+
browser (Pages Router props, SvelteKit and Remix loaders, Nuxt payload);
|
|
16
|
+
pass the edit flag to `capaAttrs` there. Found testing preview on nine stacks.
|
|
17
|
+
- Next.js: a GraphQL read that gives neither `tags` nor `revalidate`, through
|
|
18
|
+
`getCapaClient().graphql()`, its builder or `graphql()`, is no longer kept
|
|
19
|
+
in Next's data cache. It is sent as `entries.list` and `entries.get` send
|
|
20
|
+
theirs, with no `cache` and no `next`, so a publish shows on the next render
|
|
21
|
+
whether the page reads by REST or by GraphQL. It used to be kept under
|
|
22
|
+
`capa:graphql` until a webhook revalidated it, so a site with no webhook
|
|
23
|
+
route kept serving the old answer by GraphQL while REST showed the new one.
|
|
24
|
+
A read that gives `tags` or a `revalidate` is kept as before. To keep a read
|
|
25
|
+
with no tags as it was kept, pass `revalidate: false`, which keeps it under
|
|
26
|
+
`capa:graphql` until the next publish. `tags: []` now counts as no tags.
|
|
5
27
|
- 1.0.0-next.6. GraphQL, on `@capacms/sdk/next` and `@capacms/sdk/nextjs`,
|
|
6
28
|
and two commands, `capa-codegen --graphql` and `capa persist`. Additive:
|
|
7
29
|
every call that existed behaves as before. `graphql` is an optional peer
|
package/README.md
CHANGED
|
@@ -287,9 +287,10 @@ const capa = await draftClient<CapaQuery>({
|
|
|
287
287
|
`CapaQuery` types the GraphQL builder on the client it returns, as it does
|
|
288
288
|
for `createClient<CapaQuery>`; `getCapaClient<CapaQuery>` and
|
|
289
289
|
`getPublishedClient<CapaQuery>` take it the same way. Without it each helper
|
|
290
|
-
returns an untyped client. `getCapaClient`
|
|
291
|
-
|
|
292
|
-
|
|
290
|
+
returns an untyped client. `getCapaClient` sends its GraphQL reads as it
|
|
291
|
+
sends its REST reads, which Next does not keep, unless a call gives `tags` or
|
|
292
|
+
`revalidate`; then it keeps them in Next's data cache as `graphql()` does
|
|
293
|
+
(see The typed builder).
|
|
293
294
|
|
|
294
295
|
## GraphQL
|
|
295
296
|
|
|
@@ -335,8 +336,10 @@ once `capa-codegen --graphql` has read it (see Typed documents). Until then
|
|
|
335
336
|
the call does not compile, and the error says to run it, so a document edited
|
|
336
337
|
since the last run is never silently untyped.
|
|
337
338
|
|
|
338
|
-
`graphql()` keeps a published read in Next's
|
|
339
|
-
`unstable_cache`, under the tags you give.
|
|
339
|
+
Given `tags` or `revalidate`, `graphql()` keeps a published read in Next's
|
|
340
|
+
data cache, through Next's own `unstable_cache`, under the tags you give.
|
|
341
|
+
Given neither, it keeps nothing: the read is sent as a REST read is, and the
|
|
342
|
+
page shows a publish on its next render. `BlogIndexModels` lists the models
|
|
340
343
|
the query reads, which codegen writes beside its types: `articles`, and
|
|
341
344
|
`authors` for `author { name }`. It changes when the query does, so a model
|
|
342
345
|
the query starts reading is never left out. `tagsFor({ namespace })` makes
|
|
@@ -348,16 +351,19 @@ library, its alt text say, changes no entry, so it revalidates `capa:media`
|
|
|
348
351
|
instead, and every read tagged by its models shows the new text. How long a
|
|
349
352
|
read is kept:
|
|
350
353
|
|
|
351
|
-
- With
|
|
352
|
-
|
|
354
|
+
- With neither `tags` nor `revalidate`, not at all: every render reads the
|
|
355
|
+
API, as `entries.list` does, so a site with no webhook route still shows a
|
|
356
|
+
publish on the next request.
|
|
357
|
+
- With `tags` and no `revalidate`, until one of its tags is revalidated.
|
|
358
|
+
With the webhook route, that is until the next publish of a model it reads.
|
|
353
359
|
- With `revalidate: 60`, also at most 60 seconds, so a missed webhook costs
|
|
354
|
-
a minute of stale content at most.
|
|
355
|
-
- With `revalidate: 0`, not at all
|
|
360
|
+
a minute of stale content at most.
|
|
361
|
+
- With `revalidate: 0`, not at all.
|
|
356
362
|
|
|
357
|
-
A read with no `tags` is tagged `capa:graphql`
|
|
358
|
-
`revalidateFromWebhook` revalidates on every content
|
|
359
|
-
stale after a publish; tag it with its models to
|
|
360
|
-
one of them changes.
|
|
363
|
+
A read with a `revalidate` and no `tags` is tagged `capa:graphql`
|
|
364
|
+
(`GRAPHQL_TAG`), which `revalidateFromWebhook` revalidates on every content
|
|
365
|
+
change, so it is never stale after a publish; tag it with its models to
|
|
366
|
+
refresh the page only when one of them changes.
|
|
361
367
|
|
|
362
368
|
A read that answered with `errors` is never kept: a root field that timed
|
|
363
369
|
out is shown once and read again on the next request. Next's fetch cache is
|
|
@@ -513,10 +519,10 @@ it is never cached. The admin host serves GraphQL by POST only and answers a
|
|
|
513
519
|
GET as a path it does not serve; a read that did not ask for GET is then
|
|
514
520
|
repeated as a POST, and that host is read by POST for five minutes. A
|
|
515
521
|
`method: "GET"` read there throws "This Capa host does not serve GraphQL by
|
|
516
|
-
GET." In Next.js, `graphql()` from `/nextjs` adds Next's data cache on top
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
never for a draft.
|
|
522
|
+
GET." In Next.js, `graphql()` from `/nextjs` adds Next's data cache on top
|
|
523
|
+
when a read gives `tags` or `revalidate`, for a published read with no
|
|
524
|
+
errors, a POST included: kept under its `tags` until one is revalidated, or
|
|
525
|
+
for `revalidate` seconds when you give it, and never for a draft.
|
|
520
526
|
|
|
521
527
|
### The typed builder
|
|
522
528
|
|
|
@@ -557,8 +563,8 @@ document is printed when it runs, so `capa persist` never stored it, and it
|
|
|
557
563
|
is already a GET that the API and the CDN cache. To persist a read, write it
|
|
558
564
|
as a `#graphql` literal (see Persisted queries).
|
|
559
565
|
|
|
560
|
-
In a Next.js server component, read through `getCapaClient`.
|
|
561
|
-
kept in Next's data cache
|
|
566
|
+
In a Next.js server component, read through `getCapaClient`. A read that
|
|
567
|
+
names tags is kept in Next's data cache under them:
|
|
562
568
|
|
|
563
569
|
```tsx
|
|
564
570
|
// app/blog/page.tsx
|
|
@@ -579,7 +585,8 @@ export default async function Blog() {
|
|
|
579
585
|
`tags` and `revalidate` work as they do for `graphql()` (see In a Next.js
|
|
580
586
|
server component). A read is kept until a publish of a model its tags name,
|
|
581
587
|
a read with errors is never kept, and a draft or an edit-mode page is read
|
|
582
|
-
uncached.
|
|
588
|
+
uncached. A read with neither is not kept, as the client's REST reads are
|
|
589
|
+
not. `capa.graphql(document)` on the same client takes them too.
|
|
583
590
|
|
|
584
591
|
Read one field twice in one request with an alias: any other key, with
|
|
585
592
|
`__aliasFor` naming the field it reads. A home page's featured and latest
|
|
@@ -1282,7 +1289,8 @@ import { createPreviewRoute } from "@capacms/sdk/nextjs";
|
|
|
1282
1289
|
export const GET = createPreviewRoute({ draftMode, redirect });
|
|
1283
1290
|
|
|
1284
1291
|
// in a page: getCapaClient({ draftMode, headers }), then <h1 {...fieldAttrs(post).title}>
|
|
1285
|
-
// in the root layout
|
|
1292
|
+
// in the root layout: const edit = await editMode({ draftMode, headers }) from @capacms/sdk/nextjs,
|
|
1293
|
+
// then {edit ? <CapaOverlay adminOrigins={[...]} /> : null} from @capacms/sdk/nextjs/overlay
|
|
1286
1294
|
```
|
|
1287
1295
|
|
|
1288
1296
|
In Capa, set the project's preview URL to your site, and editors can click
|
|
@@ -1330,6 +1338,24 @@ The mark does not survive a spread copy or being passed to a client component,
|
|
|
1330
1338
|
so tag in the server component that read the entry. `capaAttrs(entry, field,
|
|
1331
1339
|
true)` still forces the tags on and `false` forces them off.
|
|
1332
1340
|
|
|
1341
|
+
**When the entry crosses to the browser as data, pass the flag.** The mark is
|
|
1342
|
+
a hidden symbol, so anything that serializes the entry drops it silently: the
|
|
1343
|
+
Next Pages Router's `getServerSideProps`, SvelteKit and Remix loaders, Nuxt's
|
|
1344
|
+
payload, or your own JSON endpoint. The page then shows the draft with no
|
|
1345
|
+
`data-capa-` tags, and the editor has nothing to point at. Nothing errors. Send
|
|
1346
|
+
the draft or edit state alongside the entry and hand it to `capaAttrs`:
|
|
1347
|
+
|
|
1348
|
+
```tsx
|
|
1349
|
+
// getServerSideProps, a +page.server.ts load, useAsyncData on the server...
|
|
1350
|
+
return { props: { entry, edit: draft } };
|
|
1351
|
+
|
|
1352
|
+
// in the component
|
|
1353
|
+
<h1 {...capaAttrs(entry, "title", edit)}>{entry.fields.title}</h1>
|
|
1354
|
+
```
|
|
1355
|
+
|
|
1356
|
+
Or re-mark the entries where they arrive with `markEditEntries(data)` when the
|
|
1357
|
+
page is in edit mode. Tested on the Pages Router, SvelteKit and Nuxt.
|
|
1358
|
+
|
|
1333
1359
|
GraphQL reads are marked the same way, from `getCapaClient`, an edit-mode
|
|
1334
1360
|
`createClient` or `graphql()` given `{ draftMode, headers }`. A node is an
|
|
1335
1361
|
entry when it selected `id` and `model`, and `field` is the field as you
|
package/dist/config.d.ts
CHANGED
|
@@ -1,38 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Client configuration, and the one thing about it that is not obvious.
|
|
3
|
-
*
|
|
4
|
-
* PREVIEW IS A KEY, NOT A FLAG
|
|
5
|
-
* Capa gates unpublished content on the API key's `environment` column, not on
|
|
6
|
-
* anything in the request:
|
|
7
|
-
*
|
|
8
|
-
* // apps/api/src/routes/v2/api.ts:203
|
|
9
|
-
* const environment = req.apiKeyEnvironment || "production";
|
|
10
|
-
* const includeDrafted = environment === "production" ? false : true;
|
|
11
|
-
*
|
|
12
|
-
* So there is no `?preview=true` to pass, and `getContent(ns, id, {preview})`
|
|
13
|
-
* CANNOT be implemented against a published key — the server would ignore it.
|
|
14
|
-
* The honest surface is one client per key:
|
|
15
|
-
*
|
|
16
|
-
* const capa = createClient({ ...cfg, apiKey: PUBLISHED_KEY })
|
|
17
|
-
* const preview = createClient({ ...cfg, apiKey: PREVIEW_KEY })
|
|
18
|
-
*
|
|
19
|
-
* Note also that the comparison above is exact and case-sensitive against a
|
|
20
|
-
* free-text column, so ANY environment that is not literally "production"
|
|
21
|
-
* returns drafts — `staging`, `development`, `draft`, and equally a typo like
|
|
22
|
-
* "Production".
|
|
23
|
-
*
|
|
24
|
-
* AND A CLIENT CANNOT FIND OUT WHICH IT HAS.
|
|
25
|
-
* There is deliberately no `includesDrafts()` here, because it cannot be
|
|
26
|
-
* implemented: `apiKeyEnvironment` is set in verifyApiKey.ts:57, consumed
|
|
27
|
-
* internally to compute `includeDrafted`, and returned to the caller by NO
|
|
28
|
-
* endpoint. /v2/schema carries only {models, relations, checksum, generatedAt}.
|
|
29
|
-
*
|
|
30
|
-
* So a site handed a `draft`, `staging` or `development` key serves unpublished
|
|
31
|
-
* content to the public, and has no way to detect it — not at startup, not at
|
|
32
|
-
* runtime, not from any response. In production-shaped data 29 of 74 keys are
|
|
33
|
-
* non-production. Whatever this SDK offers, it cannot make that safe; the fix
|
|
34
|
-
* is for Capa to report the key's environment on a read a client already makes.
|
|
35
|
-
*/
|
|
36
1
|
export interface CapaConfig {
|
|
37
2
|
/** Base URL of the Capa API, e.g. https://api.example.com. No trailing slash required. */
|
|
38
3
|
baseUrl: string;
|
package/dist/config.js
CHANGED
|
@@ -1,13 +1,59 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.resolveConfig = resolveConfig;
|
|
4
|
+
/**
|
|
5
|
+
* Client configuration, and the one thing about it that is not obvious.
|
|
6
|
+
*
|
|
7
|
+
* PREVIEW IS A KEY, NOT A FLAG
|
|
8
|
+
* Capa gates unpublished content on the API key's `environment` column, not on
|
|
9
|
+
* anything in the request:
|
|
10
|
+
*
|
|
11
|
+
* // apps/api/src/routes/v2/api.ts:203
|
|
12
|
+
* const environment = req.apiKeyEnvironment || "production";
|
|
13
|
+
* const includeDrafted = environment === "production" ? false : true;
|
|
14
|
+
*
|
|
15
|
+
* So there is no `?preview=true` to pass, and `getContent(ns, id, {preview})`
|
|
16
|
+
* CANNOT be implemented against a published key — the server would ignore it.
|
|
17
|
+
* The honest surface is one client per key:
|
|
18
|
+
*
|
|
19
|
+
* const capa = createClient({ ...cfg, apiKey: PUBLISHED_KEY })
|
|
20
|
+
* const preview = createClient({ ...cfg, apiKey: PREVIEW_KEY })
|
|
21
|
+
*
|
|
22
|
+
* Note also that the comparison above is exact and case-sensitive against a
|
|
23
|
+
* free-text column, so ANY environment that is not literally "production"
|
|
24
|
+
* returns drafts — `staging`, `development`, `draft`, and equally a typo like
|
|
25
|
+
* "Production".
|
|
26
|
+
*
|
|
27
|
+
* AND A CLIENT CANNOT FIND OUT WHICH IT HAS.
|
|
28
|
+
* There is deliberately no `includesDrafts()` here, because it cannot be
|
|
29
|
+
* implemented: `apiKeyEnvironment` is set in verifyApiKey.ts:57, consumed
|
|
30
|
+
* internally to compute `includeDrafted`, and returned to the caller by NO
|
|
31
|
+
* endpoint. /v2/schema carries only {models, relations, checksum, generatedAt}.
|
|
32
|
+
*
|
|
33
|
+
* So a site handed a `draft`, `staging` or `development` key serves unpublished
|
|
34
|
+
* content to the public, and has no way to detect it — not at startup, not at
|
|
35
|
+
* runtime, not from any response. In production-shaped data 29 of 74 keys are
|
|
36
|
+
* non-production. Whatever this SDK offers, it cannot make that safe; the fix
|
|
37
|
+
* is for Capa to report the key's environment on a read a client already makes.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* The platform fetch, called through `globalThis` on every request. Storing
|
|
41
|
+
* `globalThis.fetch` and calling it as a method of the config object gives it
|
|
42
|
+
* the wrong `this`, and a browser refuses that ("Illegal invocation"). Looking
|
|
43
|
+
* it up per call also picks up a fetch a framework patches in later.
|
|
44
|
+
*/
|
|
45
|
+
function defaultFetch() {
|
|
46
|
+
if (typeof globalThis.fetch !== "function")
|
|
47
|
+
return undefined;
|
|
48
|
+
return ((input, init) => globalThis.fetch(input, init));
|
|
49
|
+
}
|
|
4
50
|
function resolveConfig(config) {
|
|
5
51
|
const missing = ["baseUrl", "apiKey", "tenantId"].filter((k) => !config[k]);
|
|
6
52
|
if (missing.length) {
|
|
7
53
|
throw new Error(`@capacms/sdk: missing ${missing.join(", ")}. ` +
|
|
8
54
|
`createClient needs baseUrl, apiKey and tenantId.`);
|
|
9
55
|
}
|
|
10
|
-
const fetchImpl = config.fetch ??
|
|
56
|
+
const fetchImpl = config.fetch ?? defaultFetch();
|
|
11
57
|
if (typeof fetchImpl !== "function") {
|
|
12
58
|
throw new Error("@capacms/sdk: no fetch available. Pass one via config.fetch on older runtimes.");
|
|
13
59
|
}
|
package/dist/next/client.d.ts
CHANGED
|
@@ -387,8 +387,9 @@ export type BuilderCallOptions<O extends GraphQLCallOptions = GraphQLCallOptions
|
|
|
387
387
|
/**
|
|
388
388
|
* `client.graphql` over one way of reading a document: called with a
|
|
389
389
|
* document, and `query()` printing a selection's document for it. `createClient`
|
|
390
|
-
* reads straight from the API, and `getCapaClient` from `/nextjs`
|
|
391
|
-
* Next's data cache,
|
|
390
|
+
* reads straight from the API, and `getCapaClient` from `/nextjs` the same way
|
|
391
|
+
* unless a call gives `tags` or `revalidate`, then through Next's data cache,
|
|
392
|
+
* with the same refusals.
|
|
392
393
|
*/
|
|
393
394
|
export declare function graphqlClient<Q, O extends GraphQLCallOptions>(read: (document: string, variables: Record<string, unknown> | undefined, options: O | undefined) => Promise<GraphQLResult<unknown>>): GraphQLClient<Q, O>;
|
|
394
395
|
export interface CapaNextClient<Q = UntypedQuery, O extends GraphQLCallOptions = GraphQLCallOptions> {
|
package/dist/next/client.js
CHANGED
|
@@ -18,6 +18,19 @@ const typed_1 = require("./graphql/typed");
|
|
|
18
18
|
var errors_2 = require("./errors");
|
|
19
19
|
Object.defineProperty(exports, "CapaError", { enumerable: true, get: function () { return errors_2.CapaError; } });
|
|
20
20
|
Object.defineProperty(exports, "isCapaError", { enumerable: true, get: function () { return errors_2.isCapaError; } });
|
|
21
|
+
/**
|
|
22
|
+
* The platform fetch, called through `globalThis` on every request. Storing
|
|
23
|
+
* `globalThis.fetch` and calling it as a method of the config object gives it
|
|
24
|
+
* the wrong `this`, and a browser refuses that ("Failed to execute 'fetch' on
|
|
25
|
+
* 'Window': Illegal invocation"); Node does not care, which is why only
|
|
26
|
+
* browser reads broke. Looking it up per call also picks up a fetch a
|
|
27
|
+
* framework patches in after the client is built (Next does).
|
|
28
|
+
*/
|
|
29
|
+
function defaultFetch() {
|
|
30
|
+
if (typeof globalThis.fetch !== "function")
|
|
31
|
+
return undefined;
|
|
32
|
+
return ((input, init) => globalThis.fetch(input, init));
|
|
33
|
+
}
|
|
21
34
|
/**
|
|
22
35
|
* What a `Capa-Schema` value may look like: hex, 8 to 64 characters.
|
|
23
36
|
*
|
|
@@ -35,8 +48,9 @@ const BUILDER_NOT_PERSISTED = "@capacms/sdk/next: graphql.query() cannot send pe
|
|
|
35
48
|
/**
|
|
36
49
|
* `client.graphql` over one way of reading a document: called with a
|
|
37
50
|
* document, and `query()` printing a selection's document for it. `createClient`
|
|
38
|
-
* reads straight from the API, and `getCapaClient` from `/nextjs`
|
|
39
|
-
* Next's data cache,
|
|
51
|
+
* reads straight from the API, and `getCapaClient` from `/nextjs` the same way
|
|
52
|
+
* unless a call gives `tags` or `revalidate`, then through Next's data cache,
|
|
53
|
+
* with the same refusals.
|
|
40
54
|
*/
|
|
41
55
|
function graphqlClient(read) {
|
|
42
56
|
const query = async (selection, options) => {
|
|
@@ -61,7 +75,7 @@ function resolveNextConfig(config) {
|
|
|
61
75
|
if (value.contract !== undefined && value.contract !== 1) {
|
|
62
76
|
throw new Error("@capacms/sdk/next: contract must be 1 when provided.");
|
|
63
77
|
}
|
|
64
|
-
const fetchImpl = value.fetch ??
|
|
78
|
+
const fetchImpl = value.fetch ?? defaultFetch();
|
|
65
79
|
if (typeof fetchImpl !== "function") {
|
|
66
80
|
throw new Error("@capacms/sdk/next: no fetch available. Pass one via config.fetch.");
|
|
67
81
|
}
|
package/dist/nextjs/index.d.ts
CHANGED
|
@@ -13,11 +13,12 @@ export declare function withCache(fetchImpl: typeof fetch, options: CacheOptions
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function modelTag(namespace: string): string;
|
|
15
15
|
/**
|
|
16
|
-
* The tag `graphql()`
|
|
17
|
-
* `revalidateFromWebhook` revalidates on every content
|
|
18
|
-
* never stale after a publish, at the price of
|
|
19
|
-
* Name the models it reads with
|
|
20
|
-
* when one of those changes.
|
|
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.
|
|
21
22
|
*/
|
|
22
23
|
export declare const GRAPHQL_TAG = "capa:graphql";
|
|
23
24
|
/**
|
|
@@ -219,10 +220,12 @@ export declare function getPublishedClient<Q = UntypedQuery>(overrides?: Partial
|
|
|
219
220
|
* `CapaQuery` (from `capa-codegen --graphql`) types the builder, as with
|
|
220
221
|
* `createClient`; leave it out for an untyped client. `config` wins over env.
|
|
221
222
|
*
|
|
222
|
-
* Its
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
223
|
+
* Its REST reads are sent as `createClient` sends them, which Next does not
|
|
224
|
+
* keep. Its GraphQL reads, a document or the builder, are sent the same way
|
|
225
|
+
* unless a call gives `tags` or `revalidate`, so a publish shows up on both
|
|
226
|
+
* alike. Given either, a read is kept in Next's data cache exactly as
|
|
227
|
+
* `graphql()` keeps it: a published read with no errors, never a draft or an
|
|
228
|
+
* edit-mode page.
|
|
226
229
|
*/
|
|
227
230
|
export declare function getCapaClient<Q = UntypedQuery>(input: {
|
|
228
231
|
draftMode: DraftModeFn;
|
|
@@ -234,14 +237,16 @@ export declare function getCapaClient<Q = UntypedQuery>(input: {
|
|
|
234
237
|
/** Where a GraphQL read in Next keeps its answer: Next's data cache, as `graphql()` and `getCapaClient` keep it. */
|
|
235
238
|
export interface NextCacheOptions extends GraphQLCallOptions {
|
|
236
239
|
/**
|
|
237
|
-
* Next.js cache tags
|
|
238
|
-
* with the list `capa-codegen --graphql` writes beside
|
|
239
|
-
* it with every model the query reads, which
|
|
240
|
-
* revalidates on a publish.
|
|
241
|
-
* which every publish
|
|
240
|
+
* Next.js cache tags to keep this read under. `tagsFor({ namespace:
|
|
241
|
+
* <Name>Models })`, with the list `capa-codegen --graphql` writes beside
|
|
242
|
+
* each document, tags it with every model the query reads, which
|
|
243
|
+
* `revalidateFromWebhook` revalidates on a publish. Given a `revalidate`
|
|
244
|
+
* and no tags, the read is kept under `GRAPHQL_TAG`, which every publish
|
|
245
|
+
* revalidates. Given neither, the read is not kept: it is sent as a REST
|
|
246
|
+
* read is, on every render.
|
|
242
247
|
*/
|
|
243
248
|
tags?: string[];
|
|
244
|
-
/** Seconds to cache,
|
|
249
|
+
/** Seconds to cache, `false` to cache until a tag is revalidated, or 0 not to cache. */
|
|
245
250
|
revalidate?: number | false;
|
|
246
251
|
}
|
|
247
252
|
export interface NextGraphQLOptions extends NextCacheOptions {
|
|
@@ -261,9 +266,10 @@ export interface NextGraphQLOptions extends NextCacheOptions {
|
|
|
261
266
|
config?: Partial<CapaNextConfig>;
|
|
262
267
|
/**
|
|
263
268
|
* Next's `unstable_cache`, from `next/cache`. Leave it out: `graphql()`
|
|
264
|
-
* loads Next's own. A published read
|
|
265
|
-
* it answered with no `errors`, under `tags`
|
|
266
|
-
* with no `revalidate` until one of its tags is
|
|
269
|
+
* loads Next's own. A published read given `tags` or a `revalidate` is kept
|
|
270
|
+
* in Next's data cache only when it answered with no `errors`, under `tags`
|
|
271
|
+
* for `revalidate` seconds, or with no `revalidate` until one of its tags is
|
|
272
|
+
* revalidated. Next's fetch
|
|
267
273
|
* cache is not used for it: that cache keeps any 200, and a GraphQL error
|
|
268
274
|
* is a 200 (a root field that timed out), and in Next 15 it keeps nothing
|
|
269
275
|
* without a `revalidate`, tags or not. Pass it only to supply another.
|
|
@@ -293,12 +299,15 @@ export type UnstableCache = <T extends (...args: any[]) => Promise<any>>(cb: T,
|
|
|
293
299
|
* variables are typed from it, as they are for a `<Name>Document` it writes,
|
|
294
300
|
* and `LatestModels` lists the models it reads.
|
|
295
301
|
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
+
* With no `tags` and no `revalidate` a read is sent as the REST reads are,
|
|
303
|
+
* and Next keeps nothing: the page shows a publish on its next render, as a
|
|
304
|
+
* page reading by REST does. Given either, a published read is kept in Next's
|
|
305
|
+
* data cache (`unstable_cache`) when it answered with no errors: under `tags`,
|
|
306
|
+
* for `revalidate` seconds, or with no `revalidate` until
|
|
307
|
+
* `revalidateFromWebhook` revalidates one of its tags on a publish. It goes
|
|
308
|
+
* as a GET, which the CDN and the API also cache for the published key; a
|
|
309
|
+
* document too long for a GET URL goes as a POST, which only Next's data
|
|
310
|
+
* cache keeps. Under draft mode the read uses `CAPA_DRAFT_KEY`, a
|
|
302
311
|
* `cap_` key, and bypasses the cache (the API answers a development key's GET
|
|
303
312
|
* `no-store`), the same split `getCapaClient` makes for REST, and in edit mode
|
|
304
313
|
* each entry that selects `id` and `model` is marked for `capaAttrs`, uncached.
|
package/dist/nextjs/index.js
CHANGED
|
@@ -47,11 +47,12 @@ function modelTag(namespace) {
|
|
|
47
47
|
return `capa:model:${namespace}`;
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
* The tag `graphql()`
|
|
51
|
-
* `revalidateFromWebhook` revalidates on every content
|
|
52
|
-
* never stale after a publish, at the price of
|
|
53
|
-
* Name the models it reads with
|
|
54
|
-
* when one of those changes.
|
|
50
|
+
* The tag `graphql()` keeps a read under when it gives a `revalidate` and no
|
|
51
|
+
* `tags`, and that `revalidateFromWebhook` revalidates on every content
|
|
52
|
+
* change: such a page is never stale after a publish, at the price of
|
|
53
|
+
* refreshing on any publish. Name the models it reads with
|
|
54
|
+
* `tagsFor({ namespace })` to refresh it only when one of those changes. A
|
|
55
|
+
* read with neither `tags` nor `revalidate` is not kept at all.
|
|
55
56
|
*/
|
|
56
57
|
exports.GRAPHQL_TAG = "capa:graphql";
|
|
57
58
|
/**
|
|
@@ -423,10 +424,12 @@ function getPublishedClient(overrides = {}) {
|
|
|
423
424
|
* `CapaQuery` (from `capa-codegen --graphql`) types the builder, as with
|
|
424
425
|
* `createClient`; leave it out for an untyped client. `config` wins over env.
|
|
425
426
|
*
|
|
426
|
-
* Its
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
427
|
+
* Its REST reads are sent as `createClient` sends them, which Next does not
|
|
428
|
+
* keep. Its GraphQL reads, a document or the builder, are sent the same way
|
|
429
|
+
* unless a call gives `tags` or `revalidate`, so a publish shows up on both
|
|
430
|
+
* alike. Given either, a read is kept in Next's data cache exactly as
|
|
431
|
+
* `graphql()` keeps it: a published read with no errors, never a draft or an
|
|
432
|
+
* edit-mode page.
|
|
430
433
|
*/
|
|
431
434
|
async function getCapaClient(input) {
|
|
432
435
|
const draft = (await input.draftMode()).isEnabled === true;
|
|
@@ -493,13 +496,33 @@ async function graphql(document, variables, options = {}) {
|
|
|
493
496
|
return readInNext(read, document, variables, call, { tags, revalidate });
|
|
494
497
|
}
|
|
495
498
|
/**
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
* copy would lose) and `revalidate: 0` are not cached.
|
|
499
|
+
* Whether a read said anything about Next's cache: tags to keep it under, or a
|
|
500
|
+
* `revalidate`. One that said neither is sent as the REST reads are, so a
|
|
501
|
+
* publish shows up on both the same way.
|
|
500
502
|
*/
|
|
501
|
-
|
|
503
|
+
function namesCaching({ tags, revalidate }) {
|
|
504
|
+
return (tags !== undefined && tags.length > 0) || revalidate !== undefined;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* One GraphQL read in Next, for `graphql()` and `getCapaClient`.
|
|
508
|
+
*
|
|
509
|
+
* With no `tags` and no `revalidate` it is sent exactly as `entries.list` and
|
|
510
|
+
* `entries.get` send theirs: a plain fetch with no `cache` and no `next`, which
|
|
511
|
+
* Next does not keep, so the page shows a publish on its next render whether
|
|
512
|
+
* it reads by REST or by GraphQL. Given `tags` or a `revalidate`, Next's data
|
|
513
|
+
* cache holds a published read with no errors, and the fetch under it is never
|
|
514
|
+
* cached. A draft, an edit-mode read (its entries carry marks a cached copy
|
|
515
|
+
* would lose) and `revalidate: 0` are not cached.
|
|
516
|
+
*/
|
|
517
|
+
async function readInNext(read, document, variables, call, options) {
|
|
502
518
|
const { draft, edit, settings, given } = read;
|
|
519
|
+
if (!draft && !edit && !namesCaching(options)) {
|
|
520
|
+
return (0, next_1.createClient)({ editMode: false, ...settings }).graphql(document, variables, call);
|
|
521
|
+
}
|
|
522
|
+
// `tags: []` names no tag, so the read is kept under `GRAPHQL_TAG` like one
|
|
523
|
+
// with no tags: kept under none, nothing could ever revalidate it.
|
|
524
|
+
const tags = options.tags && options.tags.length > 0 ? options.tags : undefined;
|
|
525
|
+
const revalidate = options.revalidate;
|
|
503
526
|
const baseFetch = settings.fetch ?? globalThis.fetch;
|
|
504
527
|
const cache = given ?? nextUnstableCache();
|
|
505
528
|
if (cache && !draft) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capacms/sdk",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.7",
|
|
4
4
|
"description": "The TypeScript SDK for Capa's content API: REST and GraphQL reads typed from your models, Next.js caching and live preview, and codegen.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://docs.capacms.com/api",
|
|
@@ -68,16 +68,12 @@
|
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
70
|
"graphql": "^16.9.0",
|
|
71
|
-
"next": ">=14",
|
|
72
71
|
"react": ">=18"
|
|
73
72
|
},
|
|
74
73
|
"peerDependenciesMeta": {
|
|
75
74
|
"graphql": {
|
|
76
75
|
"optional": true
|
|
77
76
|
},
|
|
78
|
-
"next": {
|
|
79
|
-
"optional": true
|
|
80
|
-
},
|
|
81
77
|
"react": {
|
|
82
78
|
"optional": true
|
|
83
79
|
}
|