@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 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` also keeps its GraphQL reads in
291
- Next's data cache, as `graphql()` does, under the `tags` and `revalidate`
292
- each call gives (see The typed builder).
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 data cache, through Next's own
339
- `unstable_cache`, under the tags you give. `BlogIndexModels` lists the models
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 no `revalidate`, until one of its tags is revalidated. With the
352
- webhook route, that is until the next publish of a model it reads.
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. This is the only thing it adds.
355
- - With `revalidate: 0`, not at all: every render reads the API.
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` (`GRAPHQL_TAG`), which
358
- `revalidateFromWebhook` revalidates on every content change, so it is never
359
- stale after a publish; tag it with its models to refresh the page only when
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
- for a published read with no errors, a POST included: kept under its `tags`
518
- until one is revalidated, or for `revalidate` seconds when you give it, and
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`. Its reads are
561
- kept in Next's data cache, under the tags each call names:
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. `capa.graphql(document)` on the same client takes them too.
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, in edit mode: <CapaOverlay adminOrigins={[...]} /> from @capacms/sdk/nextjs/overlay
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 ?? globalThis.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
  }
@@ -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` through
391
- * Next's data cache, with the same refusals.
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> {
@@ -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` through
39
- * Next's data cache, with the same refusals.
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 ?? globalThis.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
  }
@@ -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()` gives a read that names no `tags`, and that
17
- * `revalidateFromWebhook` revalidates on every content change: such a page is
18
- * never stale after a publish, at the price of refreshing on any publish.
19
- * Name the models it reads with `tagsFor({ namespace })` to refresh it only
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 GraphQL reads, a document or the builder, are kept in Next's data cache
223
- * exactly as `graphql()` keeps them, each under the `tags` and `revalidate`
224
- * it is called with: a published read with no errors, never a draft or an
225
- * edit-mode page. Its REST reads are sent as `createClient` sends them.
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 for this read. `tagsFor({ namespace: <Name>Models })`,
238
- * with the list `capa-codegen --graphql` writes beside each document, tags
239
- * it with every model the query reads, which `revalidateFromWebhook`
240
- * revalidates on a publish. Without tags the read is tagged `GRAPHQL_TAG`,
241
- * which every publish revalidates.
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, or `false` to cache until a tag is revalidated. */
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 is kept in Next's data cache only when
265
- * it answered with no `errors`, under `tags` for `revalidate` seconds, or
266
- * with no `revalidate` until one of its tags is revalidated. Next's fetch
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
- * A published read is kept in Next's data cache (`unstable_cache`) when it
297
- * answered with no errors: under `tags`, for `revalidate` seconds, or with no
298
- * `revalidate` until `revalidateFromWebhook` revalidates one of its tags on a
299
- * publish. It goes as a GET, which the CDN and the API also cache for the
300
- * published key; a document too long for a GET URL goes as a POST, which only
301
- * Next's data cache keeps. Under draft mode the read uses `CAPA_DRAFT_KEY`, a
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.
@@ -47,11 +47,12 @@ function modelTag(namespace) {
47
47
  return `capa:model:${namespace}`;
48
48
  }
49
49
  /**
50
- * The tag `graphql()` gives a read that names no `tags`, and that
51
- * `revalidateFromWebhook` revalidates on every content change: such a page is
52
- * never stale after a publish, at the price of refreshing on any publish.
53
- * Name the models it reads with `tagsFor({ namespace })` to refresh it only
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 GraphQL reads, a document or the builder, are kept in Next's data cache
427
- * exactly as `graphql()` keeps them, each under the `tags` and `revalidate`
428
- * it is called with: a published read with no errors, never a draft or an
429
- * edit-mode page. Its REST reads are sent as `createClient` sends them.
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
- * One GraphQL read in Next, for `graphql()` and `getCapaClient`. Next's data
497
- * cache holds only a published read with no errors, and the fetch under it is
498
- * never cached. A draft, an edit-mode read (its entries carry marks a cached
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
- async function readInNext(read, document, variables, call, { tags, revalidate }) {
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.6",
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
  }