@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.
Files changed (92) hide show
  1. package/CHANGELOG.md +450 -0
  2. package/README.md +1754 -156
  3. package/bin/capa-codegen.js +192 -5
  4. package/bin/capa.js +235 -0
  5. package/bin/graphql-project.js +142 -0
  6. package/bin/project-env.js +58 -0
  7. package/dist/client.d.ts +5 -0
  8. package/dist/client.js +17 -0
  9. package/dist/codegen.d.ts +55 -0
  10. package/dist/codegen.js +320 -39
  11. package/dist/config.d.ts +5 -36
  12. package/dist/config.js +47 -1
  13. package/dist/esm/image/index.d.ts +120 -0
  14. package/dist/esm/image/index.js +250 -0
  15. package/dist/esm/image/shared-params.generated.d.ts +190 -0
  16. package/dist/esm/image/shared-params.generated.js +461 -0
  17. package/dist/esm/nextjs/image-loader.d.ts +60 -0
  18. package/dist/esm/nextjs/image-loader.js +67 -0
  19. package/dist/esm/nextjs/overlay.d.ts +30 -0
  20. package/dist/esm/nextjs/overlay.js +75 -0
  21. package/dist/esm/overlay/index.d.ts +32 -0
  22. package/dist/esm/overlay/index.js +576 -0
  23. package/dist/esm/overlay/protocol.d.ts +187 -0
  24. package/dist/esm/overlay/protocol.js +240 -0
  25. package/dist/esm/package.json +4 -0
  26. package/dist/graphql-codegen.d.ts +117 -0
  27. package/dist/graphql-codegen.js +705 -0
  28. package/dist/http.js +1 -1
  29. package/dist/image/index.d.ts +120 -0
  30. package/dist/image/index.js +257 -0
  31. package/dist/image/shared-params.generated.d.ts +190 -0
  32. package/dist/image/shared-params.generated.js +471 -0
  33. package/dist/index.d.ts +2 -2
  34. package/dist/index.js +2 -1
  35. package/dist/next/attrs.d.ts +98 -0
  36. package/dist/next/attrs.js +125 -0
  37. package/dist/next/client.d.ts +176 -32
  38. package/dist/next/client.js +212 -90
  39. package/dist/next/entry-fields.d.ts +162 -0
  40. package/dist/next/entry-fields.js +2 -0
  41. package/dist/next/errors.d.ts +136 -0
  42. package/dist/next/errors.js +214 -0
  43. package/dist/next/field-names.d.ts +37 -0
  44. package/dist/next/field-names.js +145 -0
  45. package/dist/next/graphql/build.d.ts +27 -0
  46. package/dist/next/graphql/build.js +98 -0
  47. package/dist/next/graphql/documents.d.ts +67 -0
  48. package/dist/next/graphql/documents.js +35 -0
  49. package/dist/next/graphql/edit-mode.d.ts +16 -0
  50. package/dist/next/graphql/edit-mode.js +93 -0
  51. package/dist/next/graphql/filter-values.d.ts +34 -0
  52. package/dist/next/graphql/filter-values.js +96 -0
  53. package/dist/next/graphql/introspection.d.ts +89 -0
  54. package/dist/next/graphql/introspection.js +102 -0
  55. package/dist/next/graphql/plan.d.ts +115 -0
  56. package/dist/next/graphql/plan.js +531 -0
  57. package/dist/next/graphql/request.d.ts +228 -0
  58. package/dist/next/graphql/request.js +283 -0
  59. package/dist/next/graphql/rest.d.ts +66 -0
  60. package/dist/next/graphql/rest.js +502 -0
  61. package/dist/next/graphql/selection.d.ts +55 -0
  62. package/dist/next/graphql/selection.js +212 -0
  63. package/dist/next/graphql/sha256.d.ts +13 -0
  64. package/dist/next/graphql/sha256.js +86 -0
  65. package/dist/next/graphql/summary.d.ts +83 -0
  66. package/dist/next/graphql/summary.js +151 -0
  67. package/dist/next/graphql/tree-layout.d.ts +36 -0
  68. package/dist/next/graphql/tree-layout.js +20 -0
  69. package/dist/next/graphql/tree.d.ts +171 -0
  70. package/dist/next/graphql/tree.js +249 -0
  71. package/dist/next/graphql/typed.d.ts +261 -0
  72. package/dist/next/graphql/typed.js +146 -0
  73. package/dist/next/index.d.ts +30 -3
  74. package/dist/next/index.js +34 -1
  75. package/dist/next/inflate.d.ts +51 -0
  76. package/dist/next/inflate.js +243 -0
  77. package/dist/next/key-family.d.ts +31 -0
  78. package/dist/next/key-family.js +66 -0
  79. package/dist/next/select-types.d.ts +58 -5
  80. package/dist/next/system-keys.d.ts +27 -0
  81. package/dist/next/system-keys.js +42 -0
  82. package/dist/nextjs/image-loader.d.ts +60 -0
  83. package/dist/nextjs/image-loader.js +71 -0
  84. package/dist/nextjs/index.d.ts +484 -5
  85. package/dist/nextjs/index.js +704 -9
  86. package/dist/nextjs/overlay.d.ts +30 -0
  87. package/dist/nextjs/overlay.js +78 -0
  88. package/dist/overlay/index.d.ts +32 -0
  89. package/dist/overlay/index.js +596 -0
  90. package/dist/overlay/protocol.d.ts +187 -0
  91. package/dist/overlay/protocol.js +253 -0
  92. package/package.json +70 -15
package/CHANGELOG.md ADDED
@@ -0,0 +1,450 @@
1
+ # @capacms/sdk changelog
2
+
3
+ ## Unreleased
4
+
5
+ - Live preview shows the text being typed, in 1.0.0-next.10. The overlay's
6
+ `ready` lists `features: ["patch"]`, and it takes the admin's `patch`
7
+ message: the unsaved value of a plain text, number or option field, with the
8
+ saved value the page was rendered from. It writes the value into a tagged
9
+ element's one text node while that text reads the saved value or what the
10
+ overlay last wrote there, keeping the whitespace around it, so text the
11
+ site formats is left alone until the save. Text only, through the node's
12
+ `data`, and only from the admin that said hello. A patch with more than 500
13
+ fields or a value over 100,000 characters is refused whole. New exports:
14
+ `patchedText`, `textAround`, `SITE_FEATURES`, `PATCH_MAX_FIELDS`,
15
+ `PATCH_MAX_LENGTH` and the `PatchField` type. The admin sends patches only
16
+ where live preview is on (`CAPA_LIVE_PREVIEW`).
17
+ - `capa` hands every command other than `codegen` and `persist` to
18
+ `@capacms/cli` when the project has that package, with the same arguments
19
+ and its exit code. Both packages ship a `capa` bin, so whichever one is
20
+ linked, every command works. Without `@capacms/cli` nothing changes: an
21
+ unknown command prints the same usage and exits 1.
22
+ - 1.0.0-next.10. Image helpers, all new; nothing that was there changes.
23
+ `@capacms/sdk/image` exports `imageUrl(image, options)`, `srcSet(image,
24
+ widths, options)` and `sizes(breakpoints)`, for any framework. They take a
25
+ media value from `/api/` (`{ id, url, alt, type, width, height }`), the
26
+ legacy `CapaImage`, a CDN URL or a file key, and the options `width`,
27
+ `height`, `fit`, `quality`, `dpr`, `blur` and `format`. Every URL is in the
28
+ order and spelling Capa's CDN serves without a 301, defaults left out.
29
+ When the image's own width is known, nothing is enlarged past it. A URL
30
+ that is not a Capa image comes back unchanged. `format=auto` is sent only
31
+ when passed, and the CDN currently returns JPEG for it, so the README
32
+ recommends `format: "webp"`.
33
+ A stored `https://api.capacms.com/files/...` URL names the same file and is
34
+ built on `cdn.capacms.com`, where resized copies are cached. A media value
35
+ with a null or empty `url` (the legacy API's unset image) gives
36
+ `undefined`, so `imageUrl` of a media value is typed `string | undefined`.
37
+ `isCapaImageUrl(src)` says whether a URL is a Capa file.
38
+ - Next.js: `capaImageLoader` and `createCapaImageLoader({ format, quality })`,
39
+ a `next/image` loader that lets Capa's CDN resize in place of Next's image
40
+ optimization. `@capacms/sdk/nextjs/image-loader` default-exports it for
41
+ `images.loaderFile` and carries nothing else, since Next runs the loader in
42
+ the browser; `@capacms/sdk/nextjs` exports both too. It resizes only a full
43
+ Capa URL; any other `src`, a bare `logo.png` included, goes back unchanged.
44
+ It never throws, since a throw fails the page's render: a `src` or prop the
45
+ CDN would refuse gets the `src` back, and a call with no props gives
46
+ `undefined`.
47
+ - 1.0.0-next.9. Live preview on sites in production.
48
+ `@capacms/sdk/nextjs/overlay` and `@capacms/sdk/overlay` reach a bundler as
49
+ ES modules (the `import` condition, built to `dist/esm`) and stay CommonJS
50
+ for `require`. The CommonJS overlay's `require("next/navigation")` made
51
+ webpack keep every `next/navigation` export in the module that re-exports
52
+ them, which sits in chunks a visitor loads, so loading the overlay, even
53
+ lazily, changed a site's visitor chunks (17 on one site). An ES
54
+ `import { useRouter }` leaves them as they were.
55
+ - `<CapaOverlay />`: a save's `router.refresh()` runs in a React transition,
56
+ and while it is pending the overlay makes a no-op state update every 300 ms.
57
+ Under React 19.2 and Next 15.5 a draft refresh could stay suspended after
58
+ its whole response had arrived, inside a Suspense boundary already on
59
+ screen (a root `loading.tsx` makes one), and show nothing until some other
60
+ state update. A refresh that has not landed within `refreshTimeoutMs`
61
+ (10 seconds) reloads the page instead, at the same scroll position.
62
+ `refresh="reload"` reloads on every save.
63
+ - `startOverlay`: `onRefresh` may return a promise. The overlay waits for it
64
+ up to `refreshTimeoutMs` and reloads if it rejects or is still pending; a
65
+ function that returns nothing counts as done at once, as before. A reload
66
+ after a failed refresh now keeps the scroll position too. The position is
67
+ held while a refresh is pending, and an editor who scrolls meanwhile is not
68
+ pulled back. `REFRESH_TIMEOUT_MS` is exported.
69
+ - Inside the editor's frame, a ⌘-click (Ctrl-click on Windows and Linux) on a
70
+ tagged element that is a link, or sits inside one, follows the link in the
71
+ frame instead of selecting the field, and the hover label says so. A plain
72
+ click still selects, and an untagged link navigates as before. No message
73
+ between the overlay and the editor changed.
74
+ - Docs: load the overlay lazily from a small client component, since a direct
75
+ import from the root layout puts its code in the layout's chunk, which every
76
+ visitor downloads; and skip a page transition's `FrozenRoute` in draft mode,
77
+ or a save fetches the draft and shows nothing.
78
+ - 1.0.0-next.8. Preview on a live site, in draft mode on its production
79
+ domain. `preview()` takes the legacy key a site already holds (`pk_`, `sk_`
80
+ or unprefixed), as the API does: Capa checks the token with any key that
81
+ reads the project, and refuses a token made for another project. It used to
82
+ throw a `TypeError` for a legacy key before any request. Draft reads through
83
+ the SDK still take a `cap_` key only, and the legacy-key warning says so.
84
+ - Next.js: `createPreviewRoute` and `exitPreviewRoute` take Next's `cookies`.
85
+ Given it, the draft cookie is re-set
86
+ `HttpOnly; Secure; SameSite=None; Partitioned; Path=/; Max-Age=3600`
87
+ (`maxAge` changes the hour), so Safari 26.2 and later send it inside the
88
+ editor's frame and it no longer lasts until the browser closes. Exit and a
89
+ bad link delete it partitioned, which is the only deletion that reaches
90
+ it. `redirect` is optional now: left out, each route answers with its own
91
+ 307, marked `X-Robots-Tag: noindex, nofollow`,
92
+ `Referrer-Policy: no-referrer` and `Cache-Control: private, no-store`, and
93
+ the preview route's carries the editor's `frame-ancestors`. Given
94
+ `redirect`, a route calls it as before.
95
+ - Next.js: `capaHeaders({ adminOrigins })` returns rules for `next.config`'s
96
+ `headers()` that send `X-Robots-Tag: noindex, nofollow` and
97
+ `Content-Security-Policy: frame-ancestors 'self' https://app.capacms.com`
98
+ on a request carrying the draft cookie or a `capa-preview`, `capa-edit` or
99
+ `capa-view` query, and on nothing else, so a visitor's response is
100
+ unchanged. Also exported: `draftHeaders`, `frameAncestors` (it throws for a
101
+ value that is not a bare origin), `frameDraftCookie`, `clearDraftCookie`,
102
+ `CAPA_ADMIN_ORIGIN`, `DRAFT_ROBOTS_TAG`, `DRAFT_COOKIE_MAX_AGE`,
103
+ `PREVIEW_PARAM` and `VIEW_PARAM`.
104
+ - Next.js: `capaMiddleware` marks every edit-mode response, and every request
105
+ carrying `capa-edit` or `capa-view`, `X-Robots-Tag: noindex, nofollow`, and
106
+ `resolveEditRequest` returns that value as `robotsTag`.
107
+ - Docs: "Add preview to a live site without changing it", the steps for a
108
+ site that keeps its own Capa reads, and why `editMode()` and
109
+ `getCapaClient()` make a static page dynamic.
110
+ - 1.0.0-next.7. Browsers: the `/api/` and legacy clients called the platform `fetch` as a
111
+ method of their config, which a browser refuses ("Failed to execute 'fetch'
112
+ on 'Window': Illegal invocation"), so every read from a browser failed unless
113
+ `fetch` was passed in. The default fetch is now called through `globalThis`
114
+ on each request, which also picks up a fetch a framework patches in later.
115
+ - `next` is no longer a peer dependency. No range matches every Next canary,
116
+ so a site on `next@16.3.0-canary.39` could not `npm install` the SDK without
117
+ `--legacy-peer-deps`. `@capacms/sdk/nextjs/overlay` still imports `next`,
118
+ which only a Next site loads.
119
+ - Docs: the edit mark is dropped wherever an entry is serialized to the
120
+ browser (Pages Router props, SvelteKit and Remix loaders, Nuxt payload);
121
+ pass the edit flag to `capaAttrs` there. Found testing preview on nine stacks.
122
+ - Next.js: a GraphQL read that gives neither `tags` nor `revalidate`, through
123
+ `getCapaClient().graphql()`, its builder or `graphql()`, is no longer kept
124
+ in Next's data cache. It is sent as `entries.list` and `entries.get` send
125
+ theirs, with no `cache` and no `next`, so a publish shows on the next render
126
+ whether the page reads by REST or by GraphQL. It used to be kept under
127
+ `capa:graphql` until a webhook revalidated it, so a site with no webhook
128
+ route kept serving the old answer by GraphQL while REST showed the new one.
129
+ A read that gives `tags` or a `revalidate` is kept as before. To keep a read
130
+ with no tags as it was kept, pass `revalidate: false`, which keeps it under
131
+ `capa:graphql` until the next publish. `tags: []` now counts as no tags.
132
+ - 1.0.0-next.6. GraphQL, on `@capacms/sdk/next` and `@capacms/sdk/nextjs`,
133
+ and two commands, `capa-codegen --graphql` and `capa persist`. Additive:
134
+ every call that existed behaves as before. `graphql` is an optional peer
135
+ dependency that only the two commands load. The package's types now need
136
+ TypeScript 5.0 or later, with `strict` on or off.
137
+
138
+ Calls and errors. `client.graphql(document, variables?, options?)` runs a
139
+ query against `/api/graphql` and resolves with
140
+ `{ data, errors, extensions, cacheTags }` once the API has run it, even
141
+ when `errors` is not empty: a root field that failed is `null` and the
142
+ others keep their data. A request the API refuses as a whole, `errors` and
143
+ no `data`, throws `CapaError` whatever its status: a 4xx, or the 200 that
144
+ GraphQL over HTTP sends on `application/json`, thrown with `status` 400.
145
+ 401, 402, 403, 404, 405 and 429 keep their own. `CapaError` carries every
146
+ error of the refusal in `graphqlErrors`. Each error is a
147
+ `CapaGraphQLError`, a plain object: `message`, `locations`, `path` and
148
+ `extensions` as the spec writes them, with `code`, `hint`, `docs`, `param`
149
+ and `type` lifted out, so `Response.json(result)` and a client component's
150
+ props keep every field. `isCapaGraphQLError` checks one by its shape.
151
+ Where GraphQL is switched off (`CAPA_API_GRAPHQL=off`) the call throws
152
+ "This Capa deployment does not serve GraphQL." with a hint to read over
153
+ REST meanwhile. `extensions.cost` (`requestedQueryCost`,
154
+ `actualQueryCost`, and `budget`, whose `counted` is what the 5,000-entry
155
+ limit checks) and `extensions.deprecations` (`{ coordinate, reason }` for
156
+ each deprecated member a document used) are typed.
157
+
158
+ Sending. A read is a GET whenever its URL fits the API's 8,192-byte limit,
159
+ so a production key's read is cached by the CDN and the API with no option
160
+ set, and a POST when it does not; no document is refused for its length.
161
+ `method: "POST"` always sends a POST. A host that serves GraphQL by POST
162
+ only (the admin host) answers a GET as a path it does not serve; the read
163
+ is repeated as a POST, and that host is read by POST for five minutes. A
164
+ 429 (over the API's read limits: 4 running per project, 3 of them per
165
+ client, and 64 waiting per client or 256 per key, with `/api/entries`
166
+ reads counted in the same limits) is sent again after its `Retry-After`
167
+ plus up to 250 ms, up to 3 times; `retries: 0` throws it, and a thrown 429
168
+ carries `retryAfter` in seconds, on REST calls too. `createClient` takes the legacy key a site holds for
169
+ every read, `pk_`, `sk_` or unprefixed as older tenants were minted, and
170
+ warns once per process without printing any of it; `preview()` and draft
171
+ reads (`draftClient`, `CAPA_DRAFT_KEY`) take a `cap_` key only.
172
+
173
+ Persisted queries. `persisted: true` sends the document's sha256 as a
174
+ cacheable GET (a POST when the variables are too long for a URL), and on
175
+ `PersistedQueryNotFound` one POST with the document. The API stores a
176
+ document only for a development key; a hash a host declined is
177
+ remembered for five minutes and sent by POST meanwhile. `capa persist`
178
+ registers a project's documents at build time, with the development key
179
+ in `CAPA_DRAFT_KEY`, on `CAPA_API_URL` (`CAPA_ADMIN_URL` for a self-hosted
180
+ stack whose read host stores nothing), and refuses a production key
181
+ before it sends anything. Every document is pinned (`Capa-Persist: pin`),
182
+ so documents registered in the Explorer or a preview never evict it, and
183
+ one stored without its pin fails the run. A refusal is reported with its
184
+ code. `--release` and `--env` name the build its pins belong to
185
+ (`Capa-Persist: pin; release=<id>; env=<env>`), read from Vercel's and
186
+ Netlify's build variables when not given, so the API keeps the latest
187
+ production releases' documents before a preview's.
188
+
189
+ Typed documents. `capa-codegen --graphql` checks the project's `.graphql`
190
+ files and its `#graphql`, `/* capa */` and gql`` literals against the
191
+ key's schema, and writes one module: the schema's types, `CapaQuery`, a
192
+ `TypedDocument` per operation with `<Name>Models`, the models it reads,
193
+ beside it, a `<Name>Fragment` per fragment and `capaTreeLayout`.
194
+ `tagsFor({ namespace: <Name>Models })` tags a Next.js read with every model
195
+ its query reads, relations and filters through them included, so no model
196
+ is left out by hand, and with `capa:media`, which `revalidateFromWebhook`
197
+ revalidates when a file in the media library is edited. `client.graphql(doc, vars)` then infers data and
198
+ variables with no cast, a literal is typed by its own text, and a
199
+ document's required variables are required in the call. A literal codegen
200
+ has not read yet does not compile (`RunCapaCodegen`). A fragment in a
201
+ literal of its own, spread with `${FRAGMENT}` into a query kept
202
+ `as const`, works as in Hydrogen. A field, argument or value a later
203
+ `Capa-Version` phases out is `@deprecated`, and each use is printed with
204
+ its file, line and reason. `--watch`, `--check`, `--save-schema` and
205
+ `--schema` are supported. Both commands read `CAPA_API_URL` and `CAPA_KEY`
206
+ (`CAPA_BASE_URL` and `CAPA_API_KEY` still work) from the shell or the
207
+ project's `.env` files, as `next dev` does.
208
+
209
+ REST types. `capa-codegen` without `--graphql` takes a `cap_` key: it
210
+ writes the model interfaces the `/v2` schema gives a legacy key from the
211
+ key's GraphQL schema, keyed by namespace, with no tenant id, and from a
212
+ saved schema with `--schema`. Every field is optional there, an enum is a
213
+ `string` and a field GraphQL leaves out is `unknown`, since GraphQL does
214
+ not say more; no `CAPA_SCHEMA_CHECKSUM` is written. Whichever key reads
215
+ it, the module now parses for any namespace: a field that is not an
216
+ identifier is quoted (`"am/pm_indicator"?: string;`), a model whose
217
+ PascalCase name is not one is named as GraphQL names it (`2024_events` is
218
+ `_2024Events`, its references and `Select` and `Attrs` aliases too), and an
219
+ enum's values are escaped. A read's `fields` is typed as the API returns
220
+ it: `entries.list<Articles, typeof select>` types exactly the fields the
221
+ select names, each always there and `null` when it was never filled, an
222
+ expanded relation as the entry (with `fields`) or
223
+ `{ id, model, missing: true }`, one it does not expand as `{ id, model }`,
224
+ a relation list as `{ items, pageInfo }` and media as
225
+ `{ id, url, alt, type, width, height }` (`EntryFields`). A select the
226
+ compiler cannot read, a string or one typed `Select<T>`, types every field
227
+ and each relation as any of the three. A flat read's relations are
228
+ references, `included` holds partial entries, and `inflate` returns the
229
+ tree read's type. BREAKING for code that compiled against the stored
230
+ shape (`fields.author.name`, `fields.coauthors[0]`), which read
231
+ `undefined` or threw at run time. A relation may be named alone in a
232
+ typed select, as a reference.
233
+
234
+ The typed builder. `client.graphql.query(selection)` builds the document
235
+ from an object and, with `createClient<CapaQuery>()`, types the result from
236
+ exactly what was selected. A misspelled field, argument, filter field or
237
+ operator, an argument of the wrong type, a sort value outside the enum
238
+ and a missing required argument (`article` without `args: { id }`) do not
239
+ compile, and the compiler's error names the key and where it was written
240
+ (`SelectionError<"titel is not a field of articles.nodes">`).
241
+ `NodeOf<CapaQuery, typeof selection, "articles">` is one entry of a read,
242
+ for a component's props, and `QueryResult` the whole of its `data`. An alias reads a field again in the same request:
243
+ `{ latest: { __aliasFor: "articles", args, nodes } }` is sent as
244
+ `latest: articles(...)`, and typed and checked as `articles`. A `Date` in
245
+ `args` is sent as its ISO text. `selectionToDocument` returns the text a
246
+ selection sends. It takes no `persisted`, and throws a `TypeError` for it
247
+ before any request: its document is printed when it runs, so `capa persist`
248
+ never stored it, and it is already a cacheable GET.
249
+
250
+ Paging, tags and descriptions. The builders page back as the API does:
251
+ REST's `before` with a `limit` is sent as `last` with `before`, and
252
+ `before=end` as `last` alone, the end of the list. A model's filter takes
253
+ `_tags`, as REST's `where` takes `$tags`, with the operators the key's
254
+ schema declares for it. `graphqlSchema()`, the builders and codegen read a
255
+ field's `Capa field ...` tag from the last line of its description, after
256
+ the field's label, which is where the API writes it.
257
+
258
+ REST's shape. `toTree(data, selection, layout)` turns a builder result into
259
+ REST's `shape=tree` data for the same read, typed from the selection:
260
+ system keys beside `fields`, each field under its namespace, media values
261
+ in REST's public shape and order (`id`, `url`, `alt`, `type`, `width`,
262
+ `height`), and each entry's `model` from the layout. A relation list
263
+ without `pageInfo` selected is `{ items }`, and a missing item of one is
264
+ left out, where REST keeps a `{ id, model, missing: true }` slot. The layout
265
+ is the `capaTreeLayout` constant codegen writes, so a server reads no schema
266
+ for it, or a schema read with `client.graphqlSchema()`. A root alias
267
+ converts as the field it names; an alias below a root is refused, since
268
+ REST reads each field once. `selectToSelection` writes a REST read as a
269
+ builder selection, and `graphqlToSelect` takes a selection back to REST
270
+ as it takes a tool spec.
271
+
272
+ The tool spec. `buildGraphQLQuery`, `graphqlToSelect` and `selectToGraphQL`
273
+ move between the spec the Explorer and `@capacms/mcp` share, GraphQL text and
274
+ the equal REST request, written exactly as the API writes it in
275
+ `extensions.capa.rest`: the filter as `where` JSON, and a system key a
276
+ field shadows as `$tags`, `$createdAt` or `author.$id`. They nest at most 4
277
+ relations below the root entry, which is the API's 5 levels of entries,
278
+ and send each filter value as the type its filter input declares, so
279
+ `has: "true"` on a list of true/false values goes as `true`. They check a
280
+ filter's names through `and`, `or`, `not` and one hop, refuse a system
281
+ field GraphQL does not filter (`_version`) and a hop through a relation the
282
+ key cannot read without naming the hidden model, and refuse a key a spec
283
+ does not take (`frist`) with the keys it does. `selectToGraphQL` selects
284
+ what REST returns: all six media fields, and for `*` or no select the
285
+ system keys, every field and each relation list as a connection of ids.
286
+ `inflate` and `Select<T>` read `$tags`, `$createdAt` and the other `$`
287
+ names as system keys (`SystemKey`). The schema summary lists the models
288
+ GraphQL leaves out in `restOnly`, and the builder refuses one by naming
289
+ its REST read, never as an unknown model with another model suggested.
290
+ A read of one entry pages a relation list from a cursor: `after` on the
291
+ relation's spec, `after:` in a select, `args.after` in a selection. A list
292
+ read refuses it, as the API does. `sort` takes one value or a list, at the
293
+ root too. A spec written as REST writes a read (`author.name`,
294
+ `author(name)`, `*`, a sort of `-publishedAt`) throws with what to write
295
+ instead.
296
+
297
+ Next.js. `graphql()` on `@capacms/sdk/nextjs` reads `CAPA_API_URL`,
298
+ `CAPA_KEY` and `CAPA_API_VERSION` (a setting in `config` wins), works out
299
+ draft and edit mode from `{ draftMode, headers }` as `getCapaClient` does,
300
+ and keeps a published read that answered with no `errors` in Next's data
301
+ cache, through `unstable_cache`, under its `tags`: until one of them is
302
+ revalidated, or for `revalidate` seconds. A read with no `tags` is tagged
303
+ `capa:graphql` (`GRAPHQL_TAG`), which `revalidateFromWebhook` revalidates
304
+ on every content and media event; `tagsFor({ namespace })` tags a read by
305
+ the models it reads. Drafts are read uncached, by GET. It is not
306
+ persisted by default. `draftClient`, `getCapaClient` and
307
+ `getPublishedClient` take `CapaQuery` like `createClient`, and
308
+ `getCapaClient` keeps its GraphQL reads, a document or the typed builder,
309
+ in Next's data cache the same way, under the `tags` and `revalidate` each
310
+ call gives.
311
+
312
+ The package root. `createClient` from `@capacms/sdk`, the legacy `/v2/api`
313
+ client, refuses a `cap_` key with a `TypeError` that names
314
+ `@capacms/sdk/next`, before it asks for a tenant id or sends anything,
315
+ where every read failed as a 401 "Invalid API key". Its `CapaError`
316
+ message joins the body with a colon. The README opens with which import is
317
+ which and links the API reference, and `homepage` is
318
+ https://docs.capacms.com/api.
319
+
320
+ Edit mode. A GraphQL read in edit mode is marked like a REST read: each
321
+ object that selected `id` and `model` is an entry, and
322
+ `capaAttrs(node, field)` and `fieldAttrs(node)` tag a field GraphQL renamed
323
+ by its namespace. To know which fields those are, the client reads the
324
+ key's type and field names once a minute, beside the page's read rather
325
+ than after it; a document that never says `model` reads none. `toTree`
326
+ keeps the mark, and `markGraphQLEntries` is exported. With next.5's
327
+ `FieldAttrs<T>`: `fieldAttrs` also takes a GraphQL node and, for a REST
328
+ entry typed with `Model`, still returns exactly `FieldAttrs<Model>`, so the
329
+ `<Model>Attrs` aliases codegen writes keep working. `TaggableField<E>` is
330
+ exported beside it.
331
+
332
+ - 1.0.0-next.5. `<CapaOverlay adminOrigins={[...]} />` from the new entry
333
+ point `@capacms/sdk/nextjs/overlay`: the live preview overlay as one Next.js
334
+ client component, refreshing with `router.refresh()` on save (M6). `react`
335
+ and `next` are optional peer dependencies, needed only for that entry. The
336
+ `FieldAttrs<T>` type is exported from `@capacms/sdk/next`, and `capa-codegen`
337
+ writes a `<Model>Attrs` alias beside each `<Model>Select`, so
338
+ `const a: ArticleAttrs = fieldAttrs(entry)` catches a wrong field name at
339
+ compile time (M5).
340
+ - 1.0.0-next.4. Edit mode. BREAKING for sites that call `capaAttrs(entry,
341
+ field)` with no third argument: it now tags only entries read in edit mode, so
342
+ a published page ships no `data-capa-` attributes. Create the client with
343
+ `editMode: true` (work it out with `editMode({ draftMode, headers })` from
344
+ `@capacms/sdk/nextjs`) and every entry it reads, related entries included, is
345
+ marked. Sites that pass a flag keep working unchanged. New in
346
+ `@capacms/sdk/nextjs`: `editMode`, `resolveEditRequest` for middleware (checks
347
+ a `capa-edit` token with Capa, strips a forged `x-capa-edit`, returns the
348
+ `private, no-store` Cache-Control to set), and the constants `EDIT_PARAM`,
349
+ `EDIT_HEADER`, `DRAFT_COOKIE`, `EDIT_CACHE_CONTROL`. New on the client: a
350
+ `path` option (config and per call) sent as `Capa-Path` beside `Capa-Page`, so
351
+ Capa can list the concrete URLs an entry appears on. `markEditEntries`,
352
+ `isEditEntry` and `CAPA_EDIT` are exported from `@capacms/sdk/next`.
353
+ Five-minute integration (M6) in `@capacms/sdk/nextjs`: `getCapaClient`
354
+ (keys and edit mode from env), `getPublishedClient`, `createPreviewRoute`,
355
+ `exitPreviewRoute` and `capaMiddleware` (preview links, the Published view,
356
+ edit mode and `no-store` in one line). Typed `fieldAttrs(entry).title` on
357
+ `@capacms/sdk/next` (M5): a wrong field name is a compile error.
358
+ Flat responses: `entries.list` and `entries.get` take `shape: "flat"`, which
359
+ sends `?shape=flat` and returns every relation as a `{ id, model }`
360
+ reference with each expanded entry once in `included`, typed by the select.
361
+ The result carries the select it sent. `inflate(result)` turns it back into
362
+ the tree result, deep-equal to a `shape=tree` read of the same request; it
363
+ returns copies and stops where the select stops, so cycles end. New types:
364
+ `FlatPage`, `FlatSingle`, `FlatListOptions`, `FlatGetOptions`, `Included`,
365
+ `ExpandedTargets`, `ResponseShape`, `FlatResponse`. Additive: a read without
366
+ `shape` sends the same URL and returns the same result as before.
367
+ - 1.0.0-next.3. Reads made from a layout are no longer charged to `/`.
368
+ `routeOf` returned the route a file sits at, so `app/layout.tsx` came out as
369
+ `/` and every Site singleton or nav read in a root layout was recorded as a
370
+ read of the home page, on every page of the site. `routeOf` now returns
371
+ `"(layout)"` (exported as `LAYOUT_PAGE` from `@capacms/sdk/next` and
372
+ `@capacms/sdk/nextjs`) for an app-router `layout.*` or `template.*`, and a
373
+ read whose page is `"(layout)"` sends no `Capa-Page`, even when the client was
374
+ created with a `page`. The return type is still `string`, so layout code that
375
+ already passes `routeOf(import.meta.url)` needs no change: upgrading fixes it.
376
+ No API change: the API already records nothing for a read without the
377
+ header. In the pages router, `pages/layout.tsx` is now the page `/layout`
378
+ rather than `/`.
379
+ - 1.0.0-next.2. The overlay reports `visible { entryId, field }`, the tagged
380
+ element at the centre of the viewport, while the page scrolls: at most every
381
+ 150ms and only when it changes (the topmost visible element at the very top
382
+ of a page, the bottommost at the very bottom). The Capa editor's "Follow the
383
+ page" scrolls the form to match. Additive and still protocol `v: 1`, so an older admin
384
+ ignores it and an older overlay simply never sends it. `pickCentred` and
385
+ `scrollEdge`, the pure choice behind it, and `visibleMessage` are exported
386
+ for tests.
387
+ - 1.0.0-next.1. Live preview. `capaAttrs(entry, field, enabled)` on
388
+ `@capacms/sdk/next` tags an element with the entry and field it renders,
389
+ typed so `field` is one of the entry's data keys. The new entry point
390
+ `@capacms/sdk/overlay` exports `startOverlay({ adminOrigins, onRefresh })`,
391
+ which, inside the Capa editor's preview frame, outlines the field being
392
+ edited, reports clicks on tagged elements back to the editor and re-renders
393
+ the draft after a save. It does nothing outside a frame and has no
394
+ dependencies. `acceptMessage` is exported for tests. See "Live preview" in
395
+ the README.
396
+ - `routeOf(import.meta.url)` now decodes the file URL. A real `import.meta.url`
397
+ percent-encodes brackets, so a dynamic route such as `app/blog/[slug]/page.tsx`
398
+ came out as `/blog/%5Bslug%5D` and every read with that `page` threw a
399
+ `TypeError`.
400
+ - The package is published as `@capacms/sdk`. The `capa` scope on npm was
401
+ already taken, so the org is `capacms`; entry points are `@capacms/sdk`
402
+ (legacy `/v2` client), `@capacms/sdk/next` and `@capacms/sdk/nextjs`.
403
+ Nothing else about the package changed. Earlier notes below that named
404
+ `@capa/sdk` were written before the first publish and mean this package.
405
+ - Added `page` to `CapaNextConfig` and to every call's options, sending the
406
+ `Capa-Page` header so Capa can report which of a site's pages read which
407
+ entries. Telemetry only: it does not change a response, a cache key or an
408
+ `ETag`. A malformed value throws a `TypeError`, because the API ignores a
409
+ header it cannot store and a typo should surface where it is written.
410
+ - Added `client.pages.list()` and `client.pages.get(page)` for the page list and
411
+ one page's detail. `list({ entry })` narrows to the pages that read one entry
412
+ and adds `entryReads` to each row; `get` returns `null` for an unknown page.
413
+ - Added `client.preview(token)`, which verifies a preview token minted by the
414
+ Capa admin and returns the claim, or `null` when the token is invalid or
415
+ expired. Every other failure throws.
416
+ - Added `schemaChecksum` to `CapaNextConfig`, sending the `Capa-Schema` header on
417
+ every call so Capa can tell a site built against the current models from one
418
+ built against an older set. Telemetry only, like `page`: it changes no
419
+ response, cache key or `ETag`. A malformed value throws a `TypeError`, because
420
+ a stamp the API drops in silence looks exactly like a site that is up to date.
421
+ - `capa-codegen` now writes `export const CAPA_SCHEMA_CHECKSUM` beside the
422
+ checksum comment it already wrote, so the value can be imported and handed to
423
+ `createClient`. That constant is the only new byte in the generated file.
424
+ - `client.pages.get(page)` now carries `insights`, the suggestions Capa draws
425
+ from that page's own reads (`overfetch`, `fanout`, `cache`, `drift`), each
426
+ with the numbers behind it and a copyable rewrite where there is one, and
427
+ every `queries[]` row carries `selection`, the parsed Selection IR of its
428
+ `select`, or `selectionError` when it no longer parses.
429
+ - Every `queries[]` row on `client.pages.get(page)` also carries `url`, the
430
+ request that group most often made, so a caller can print the real call with
431
+ its filters, sort and limit instead of reconstructing one from `select`. It is
432
+ `null` once the group has been folded into the daily rollup, which keeps
433
+ counts rather than requests. The detail itself gains `lastReadAt`.
434
+ - `client.pages.list()` now carries `meta.insights`, the tenant-wide `unused`
435
+ rows: entries no page has read in 30 days and nobody has edited in 90.
436
+ - Added `routeOf(file)`, `preview(token, client)` and `pagesFor(client)` to
437
+ `@capacms/sdk/nextjs`. `routeOf` turns a Next route file into a page string,
438
+ dropping route groups, parallel slots, leaf file names and extensions, and
439
+ throws rather than guessing for files Next does not route.
440
+
441
+ ## 1.0.0-next.0
442
+
443
+ - Added `@capacms/sdk/next`, the dependency-free `/api/` read client for `cap_`
444
+ keys, typed selects, cursor iteration, structured `/api/` errors, and
445
+ surrogate cache tags.
446
+ - Added `@capacms/sdk/nextjs` helpers for Next fetch caching, surrogate tag
447
+ construction, webhook revalidation, and server-only draft client selection.
448
+ - Kept the legacy `/v2/api` client as the root export.
449
+ - Added relation-aware `capa-codegen` output while preserving byte-identical
450
+ output for schemas without relations.