@capacms/sdk 1.0.0-next.2 → 1.0.0-next.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -199,6 +199,19 @@ so that moving a folder cannot silently split one page's telemetry in two. Set
199
199
  `page` on the config instead when a client serves exactly one page; a value on
200
200
  the call wins over one on the config.
201
201
 
202
+ A layout is not a page. `app/layout.tsx` (and any nested `layout.*` or
203
+ `template.*`) renders around every page below it, and Next does not tell it
204
+ which one, so its reads cannot be charged to the page being rendered. `routeOf`
205
+ returns `"(layout)"` for these files, exported as `LAYOUT_PAGE`, and a read that
206
+ names it sends no `Capa-Page` header at all, even when the client was created
207
+ with a `page`. So a Site singleton or a nav read in your root layout is simply
208
+ not attributed, instead of making `/` look as if it read everything:
209
+
210
+ ```ts
211
+ // In app/layout.tsx: same call as in a page, and no page is recorded.
212
+ const site = await capa.entries.list("site", { page: routeOf(import.meta.url) });
213
+ ```
214
+
202
215
  A malformed value throws a `TypeError`. Capa itself ignores a header it cannot
203
216
  store, because a mangled page identity must never take a blog down, so the SDK
204
217
  is the place a typo surfaces.
@@ -287,6 +287,20 @@ export declare class CapaError extends Error {
287
287
  }
288
288
  export declare function isCapaError(error: unknown): error is CapaError;
289
289
  export declare function resolveNextConfig(config: CapaNextConfig): ResolvedNextConfig;
290
+ /**
291
+ * The page a layout (or template) reads for: none.
292
+ *
293
+ * A root layout renders around every page on the site, so charging its reads
294
+ * to `/`, the route its file sits at, made the home page look as if it read
295
+ * every Site singleton and nav on the site. `routeOf` returns this for a
296
+ * `layout.*` or `template.*` file, and a read that names it sends NO
297
+ * `Capa-Page` at all, even when the client was built with a `page`.
298
+ *
299
+ * Not sent as a value, because the API would drop it anyway: it is not a
300
+ * `PAGE_ID`, and the API ignores what it cannot store. Staying absent keeps a
301
+ * layout read byte-identical to one from a client that never named a page.
302
+ */
303
+ export declare const LAYOUT_PAGE = "(layout)";
290
304
  type SelectInput = string | ReadonlyArray<unknown>;
291
305
  /** Serialize the SDK object form into the canonical `/api/entries` grammar. */
292
306
  export declare function serializeSelect(select: SelectInput): string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CapaError = void 0;
3
+ exports.LAYOUT_PAGE = exports.CapaError = void 0;
4
4
  exports.isCapaError = isCapaError;
5
5
  exports.resolveNextConfig = resolveNextConfig;
6
6
  exports.serializeSelect = serializeSelect;
@@ -85,6 +85,20 @@ function resolveNextConfig(config) {
85
85
  * quietly stops arriving rather than as an error.
86
86
  */
87
87
  const PAGE_ID = /^\/[A-Za-z0-9._\-[\]/]{0,199}$/;
88
+ /**
89
+ * The page a layout (or template) reads for: none.
90
+ *
91
+ * A root layout renders around every page on the site, so charging its reads
92
+ * to `/`, the route its file sits at, made the home page look as if it read
93
+ * every Site singleton and nav on the site. `routeOf` returns this for a
94
+ * `layout.*` or `template.*` file, and a read that names it sends NO
95
+ * `Capa-Page` at all, even when the client was built with a `page`.
96
+ *
97
+ * Not sent as a value, because the API would drop it anyway: it is not a
98
+ * `PAGE_ID`, and the API ignores what it cannot store. Staying absent keeps a
99
+ * layout read byte-identical to one from a client that never named a page.
100
+ */
101
+ exports.LAYOUT_PAGE = "(layout)";
88
102
  /**
89
103
  * The page for one call: the call's own value, else the client's, else none.
90
104
  *
@@ -97,6 +111,10 @@ function resolvePage(configPage, callPage) {
97
111
  const value = callPage !== undefined ? callPage : configPage;
98
112
  if (value === undefined || value === null)
99
113
  return undefined;
114
+ // A layout's read, named on purpose: no header, and the config's page does
115
+ // not stand in for it either.
116
+ if (value === exports.LAYOUT_PAGE)
117
+ return undefined;
100
118
  if (typeof value !== "string" || !PAGE_ID.test(value)) {
101
119
  throw new TypeError(`@capacms/sdk/next: page must be a path such as "/blog/[slug]" or "/blog/hello". Got ${JSON.stringify(value)}.`);
102
120
  }
@@ -1,4 +1,4 @@
1
- export { CapaError, createClient, isCapaError, resolveNextConfig, serializeSelect, } from "./client";
1
+ export { CapaError, createClient, isCapaError, LAYOUT_PAGE, resolveNextConfig, serializeSelect, } from "./client";
2
2
  export { capaAttrs } from "./attrs";
3
3
  export type { CapaAttrs } from "./attrs";
4
4
  export type { CallOptions, CapaNextClient, CapaNextConfig, EntriesResource, Entry, Filter, FilterOperator, FilterScalar, FilterValue, GetOptions, ListOptions, Page, PageDetail, PageInfo, PageSummary, PagesListOptions, PagesResource, PreviewClaim, ResponseMeta, Single, } from "./client";
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.capaAttrs = exports.serializeSelect = exports.resolveNextConfig = exports.isCapaError = exports.createClient = exports.CapaError = void 0;
3
+ exports.capaAttrs = exports.serializeSelect = exports.resolveNextConfig = exports.LAYOUT_PAGE = exports.isCapaError = exports.createClient = exports.CapaError = void 0;
4
4
  var client_1 = require("./client");
5
5
  Object.defineProperty(exports, "CapaError", { enumerable: true, get: function () { return client_1.CapaError; } });
6
6
  Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return client_1.createClient; } });
7
7
  Object.defineProperty(exports, "isCapaError", { enumerable: true, get: function () { return client_1.isCapaError; } });
8
+ Object.defineProperty(exports, "LAYOUT_PAGE", { enumerable: true, get: function () { return client_1.LAYOUT_PAGE; } });
8
9
  Object.defineProperty(exports, "resolveNextConfig", { enumerable: true, get: function () { return client_1.resolveNextConfig; } });
9
10
  Object.defineProperty(exports, "serializeSelect", { enumerable: true, get: function () { return client_1.serializeSelect; } });
10
11
  var attrs_1 = require("./attrs");
@@ -1,4 +1,4 @@
1
- import { type CapaNextClient, type CapaNextConfig, type PagesResource, type PreviewClaim } from "../next";
1
+ import { LAYOUT_PAGE, type CapaNextClient, type CapaNextConfig, type PagesResource, type PreviewClaim } from "../next";
2
2
  export interface CacheOptions {
3
3
  tags?: string[];
4
4
  revalidate?: number | false;
@@ -50,4 +50,5 @@ export declare function preview(token: string, client: CapaNextClient): Promise<
50
50
  * this and never hold the whole client.
51
51
  */
52
52
  export declare function pagesFor(client: CapaNextClient): PagesResource;
53
+ export { LAYOUT_PAGE };
53
54
  export type { PreviewClaim };
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LAYOUT_PAGE = void 0;
3
4
  exports.withCache = withCache;
4
5
  exports.tagsFor = tagsFor;
5
6
  exports.revalidateFromWebhook = revalidateFromWebhook;
@@ -8,6 +9,7 @@ exports.routeOf = routeOf;
8
9
  exports.preview = preview;
9
10
  exports.pagesFor = pagesFor;
10
11
  const next_1 = require("../next");
12
+ Object.defineProperty(exports, "LAYOUT_PAGE", { enumerable: true, get: function () { return next_1.LAYOUT_PAGE; } });
11
13
  /** Add Next.js fetch-cache options without importing `next/*`. */
12
14
  function withCache(fetchImpl, options) {
13
15
  return (async (input, init = {}) => {
@@ -64,6 +66,16 @@ async function draftClient(input) {
64
66
  * src/app/(marketing)/pricing/page.tsx -> /pricing
65
67
  * pages/blog/[slug].tsx -> /blog/[slug]
66
68
  * app/page.tsx -> /
69
+ * app/layout.tsx, app/blog/template.tsx -> (layout)
70
+ *
71
+ * A LAYOUT IS NOT A PAGE. `layout.*` and `template.*` render around every page
72
+ * below them, and a layout is not told which one: Next hands it no pathname.
73
+ * So its reads cannot be charged to the page being rendered, and charging them
74
+ * to the route its own file sits at is wrong: a root layout's Site singleton
75
+ * and nav would all be recorded as reads of `/`. `routeOf` returns
76
+ * `LAYOUT_PAGE` (`"(layout)"`) for these files instead, and a read that names
77
+ * it sends no `Capa-Page` at all. Pass `routeOf(import.meta.url)` from a layout
78
+ * exactly as from a page and it does the right thing.
67
79
  *
68
80
  * WHAT IS DROPPED, and why each one:
69
81
  *
@@ -74,8 +86,7 @@ async function draftClient(input) {
74
86
  * parallel slots `@modal` the same
75
87
  * the `(.)` intercept marker an intercepting route at the SAME
76
88
  * level renders the segment beside it
77
- * `page.*`, `layout.*`, `route.*`, leaf files, not segments
78
- * `default.*`, `template.*`
89
+ * `page.*`, `route.*`, `default.*` leaf files, not segments
79
90
  * `index` in the pages router the folder IS the route
80
91
  * the extension never in a URL
81
92
  *
@@ -87,7 +98,9 @@ async function draftClient(input) {
87
98
  * case the message says to pass the page string yourself.
88
99
  */
89
100
  const ROUTER_ROOTS = new Set(["app", "pages"]);
90
- const LEAF_FILES = new Set(["page", "layout", "route", "default", "template"]);
101
+ const LEAF_FILES = new Set(["page", "route", "default"]);
102
+ /** Files that render around many routes, so they name none. */
103
+ const LAYOUT_FILES = new Set(["layout", "template"]);
91
104
  /** `(..)photo`, `(..)(..)photo`, `(...)photo`: the URL is not here. */
92
105
  const OUTER_INTERCEPT = /^(\(\.\.\.\)|(\(\.\.\))+)/;
93
106
  function routeOf(file) {
@@ -131,6 +144,9 @@ function routeOf(file) {
131
144
  const dot = part.lastIndexOf(".");
132
145
  if (dot > 0)
133
146
  part = part.slice(0, dot);
147
+ // Only in the app router: `pages/layout.tsx` is a page at `/layout`.
148
+ if (parts[rootIndex] === "app" && LAYOUT_FILES.has(part))
149
+ return next_1.LAYOUT_PAGE;
134
150
  if (LEAF_FILES.has(part))
135
151
  continue;
136
152
  // The pages router: `pages/blog/index.tsx` is `/blog`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capacms/sdk",
3
- "version": "1.0.0-next.2",
3
+ "version": "1.0.0-next.3",
4
4
  "license": "UNLICENSED",
5
5
  "repository": {
6
6
  "type": "git",