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

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 (68) hide show
  1. package/CHANGELOG.md +323 -0
  2. package/README.md +1163 -186
  3. package/bin/capa-codegen.js +192 -5
  4. package/bin/capa.js +208 -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 -1
  12. package/dist/graphql-codegen.d.ts +117 -0
  13. package/dist/graphql-codegen.js +705 -0
  14. package/dist/http.js +1 -1
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.js +2 -1
  17. package/dist/next/attrs.d.ts +84 -10
  18. package/dist/next/attrs.js +119 -2
  19. package/dist/next/client.d.ts +160 -31
  20. package/dist/next/client.js +178 -89
  21. package/dist/next/entry-fields.d.ts +162 -0
  22. package/dist/next/entry-fields.js +2 -0
  23. package/dist/next/errors.d.ts +136 -0
  24. package/dist/next/errors.js +214 -0
  25. package/dist/next/field-names.d.ts +37 -0
  26. package/dist/next/field-names.js +145 -0
  27. package/dist/next/graphql/build.d.ts +27 -0
  28. package/dist/next/graphql/build.js +98 -0
  29. package/dist/next/graphql/documents.d.ts +67 -0
  30. package/dist/next/graphql/documents.js +35 -0
  31. package/dist/next/graphql/edit-mode.d.ts +16 -0
  32. package/dist/next/graphql/edit-mode.js +93 -0
  33. package/dist/next/graphql/filter-values.d.ts +34 -0
  34. package/dist/next/graphql/filter-values.js +96 -0
  35. package/dist/next/graphql/introspection.d.ts +89 -0
  36. package/dist/next/graphql/introspection.js +102 -0
  37. package/dist/next/graphql/plan.d.ts +115 -0
  38. package/dist/next/graphql/plan.js +531 -0
  39. package/dist/next/graphql/request.d.ts +228 -0
  40. package/dist/next/graphql/request.js +283 -0
  41. package/dist/next/graphql/rest.d.ts +66 -0
  42. package/dist/next/graphql/rest.js +502 -0
  43. package/dist/next/graphql/selection.d.ts +55 -0
  44. package/dist/next/graphql/selection.js +212 -0
  45. package/dist/next/graphql/sha256.d.ts +13 -0
  46. package/dist/next/graphql/sha256.js +86 -0
  47. package/dist/next/graphql/summary.d.ts +83 -0
  48. package/dist/next/graphql/summary.js +151 -0
  49. package/dist/next/graphql/tree-layout.d.ts +36 -0
  50. package/dist/next/graphql/tree-layout.js +20 -0
  51. package/dist/next/graphql/tree.d.ts +171 -0
  52. package/dist/next/graphql/tree.js +249 -0
  53. package/dist/next/graphql/typed.d.ts +261 -0
  54. package/dist/next/graphql/typed.js +146 -0
  55. package/dist/next/index.d.ts +29 -4
  56. package/dist/next/index.js +31 -1
  57. package/dist/next/inflate.d.ts +51 -0
  58. package/dist/next/inflate.js +243 -0
  59. package/dist/next/key-family.d.ts +34 -0
  60. package/dist/next/key-family.js +74 -0
  61. package/dist/next/select-types.d.ts +58 -5
  62. package/dist/next/system-keys.d.ts +27 -0
  63. package/dist/next/system-keys.js +42 -0
  64. package/dist/nextjs/index.d.ts +333 -6
  65. package/dist/nextjs/index.js +450 -4
  66. package/dist/nextjs/overlay.d.ts +5 -0
  67. package/dist/nextjs/overlay.js +35 -0
  68. package/package.json +35 -13
@@ -1,20 +1,54 @@
1
- import { LAYOUT_PAGE, type CapaNextClient, type CapaNextConfig, type PagesResource, type PreviewClaim } from "../next";
1
+ import { gql, LAYOUT_PAGE, type CapaDocumentResult, type CapaDocuments, type CapaDocumentVariables, type CapaNextClient, type CapaNextConfig, type GraphQLCallOptions, type GraphQLResult, type NotARecordedDocument, type PagesResource, type PreviewClaim, type TypedDocument, type UntypedQuery, type VariablesThenOptions } from "../next";
2
2
  export interface CacheOptions {
3
3
  tags?: string[];
4
4
  revalidate?: number | false;
5
5
  }
6
6
  /** Add Next.js fetch-cache options without importing `next/*`. */
7
7
  export declare function withCache(fetchImpl: typeof fetch, options: CacheOptions): typeof fetch;
8
+ /**
9
+ * The Next.js cache tag for every read of a model, by its namespace. A GraphQL
10
+ * query names its models by namespace (`articles`), not by id, so this is the
11
+ * tag a GraphQL read is cached under, and `revalidateFromWebhook` revalidates
12
+ * it when an entry of that model changes.
13
+ */
14
+ export declare function modelTag(namespace: string): string;
15
+ /**
16
+ * The tag `graphql()` 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.
21
+ */
22
+ export declare const GRAPHQL_TAG = "capa:graphql";
23
+ /**
24
+ * The tag `tagsFor({ namespace })` adds beside its model tags, and that
25
+ * `revalidateFromWebhook` revalidates on a media event: a GraphQL read shows
26
+ * a file's URL and alt text whichever models it names, so editing a file in
27
+ * the media library refreshes it.
28
+ */
29
+ export declare const MEDIA_TAG = "capa:media";
30
+ /**
31
+ * Next.js cache tags for a read. `model`, `entry`, `key` and `tenant` are the
32
+ * API's surrogate keys (`m:`, `e:`, `k:`, `t:`), by id; `namespace` is one
33
+ * `capa:model:<namespace>` tag per model a GraphQL query reads, which
34
+ * `capa-codegen --graphql` lists as `<Name>Models`, and `MEDIA_TAG`, since
35
+ * the query may show a file from the media library. Each pairs with
36
+ * `revalidateFromWebhook`.
37
+ */
8
38
  export declare function tagsFor(input: {
9
39
  model?: string;
10
40
  entry?: string;
11
41
  key?: string;
12
42
  tenant?: string;
43
+ namespace?: string | readonly string[];
13
44
  }): string[];
14
45
  export interface WebhookPayload {
46
+ type?: unknown;
15
47
  data?: {
16
48
  instanceId?: unknown;
17
49
  modelId?: unknown;
50
+ modelNamespace?: unknown;
51
+ namespace?: unknown;
18
52
  [key: string]: unknown;
19
53
  };
20
54
  instanceId?: unknown;
@@ -22,17 +56,34 @@ export interface WebhookPayload {
22
56
  modelId?: unknown;
23
57
  [key: string]: unknown;
24
58
  }
25
- /** Revalidate the concrete entry and model identities carried by a webhook. */
59
+ /**
60
+ * Revalidate every tag a webhook's entry and model can be cached under: the
61
+ * entry (`e:`), the model by id (`m:`) and by namespace (`capa:model:`, the
62
+ * tag a GraphQL read uses). The namespace is `data.modelNamespace` on
63
+ * `instance.published`, `instance.unpublished` and `model.published`, and
64
+ * `data.namespace` on the other events (docs/WEBHOOKS.md). Any of those, and
65
+ * a `media.` event, also revalidates `GRAPHQL_TAG`, the tag of a `graphql()`
66
+ * read that named no tags. A `media.` event (a file's alt text, name or
67
+ * visibility changed, or the file went) revalidates the file's own key
68
+ * (`f:<fileId>`, as REST's `Surrogate-Key` names it) and `MEDIA_TAG`, which
69
+ * every read tagged by namespace carries, since it may show that file.
70
+ */
26
71
  export declare function revalidateFromWebhook(input: {
27
72
  payload: WebhookPayload;
28
73
  revalidateTag: (tag: string) => void | Promise<void>;
29
74
  }): Promise<string[]>;
30
- /** Select a client using server-only draft state supplied by the caller. */
31
- export declare function draftClient(input: {
75
+ /**
76
+ * Select a client using server-only draft state supplied by the caller. Pass
77
+ * codegen's `CapaQuery` to type the builder, as with `createClient`:
78
+ * `draftClient<CapaQuery>({ ... })`. `production` may hold the legacy key a
79
+ * site already has; `draft` needs a `cap_` key, and a legacy one throws a
80
+ * `TypeError` when draft mode selects it.
81
+ */
82
+ export declare function draftClient<Q = UntypedQuery>(input: {
32
83
  production: CapaNextConfig;
33
84
  draft: CapaNextConfig;
34
85
  isDraft: () => boolean | Promise<boolean>;
35
- }): Promise<CapaNextClient>;
86
+ }): Promise<CapaNextClient<Q>>;
36
87
  export declare function routeOf(file: string): string;
37
88
  /**
38
89
  * Verify a preview token from a Next route handler.
@@ -50,5 +101,281 @@ export declare function preview(token: string, client: CapaNextClient): Promise<
50
101
  * this and never hold the whole client.
51
102
  */
52
103
  export declare function pagesFor(client: CapaNextClient): PagesResource;
53
- export { LAYOUT_PAGE };
104
+ export { gql, LAYOUT_PAGE };
54
105
  export type { PreviewClaim };
106
+ /**
107
+ * The query parameter that turns edit mode on for one request without draft
108
+ * content: `?capa-edit=<token>`, where the token is a Capa preview token. The
109
+ * Capa editor's Published view sends it so the published page is still
110
+ * clickable. It never switches the site to draft data.
111
+ */
112
+ export declare const EDIT_PARAM = "capa-edit";
113
+ /**
114
+ * The request header `resolveEditRequest` sets once a `capa-edit` token has
115
+ * been verified, for `editMode()` to read. Any copy a browser sent is removed
116
+ * first, so it cannot be forged from outside.
117
+ */
118
+ export declare const EDIT_HEADER = "x-capa-edit";
119
+ /** The cookie Next's `draftMode().enable()` sets. */
120
+ export declare const DRAFT_COOKIE = "__prerender_bypass";
121
+ /** What every edit-mode response must send: never cached, never shared. */
122
+ export declare const EDIT_CACHE_CONTROL = "private, no-store";
123
+ interface HeaderReader {
124
+ get(name: string): string | null;
125
+ }
126
+ /**
127
+ * Whether this request renders in edit mode: Next draft mode is on, or the
128
+ * request carried a verified `capa-edit` token (see `resolveEditRequest`).
129
+ *
130
+ * Pass Next's own functions; this package imports nothing from `next`:
131
+ *
132
+ * import { draftMode, headers } from "next/headers";
133
+ * const edit = await editMode({ draftMode, headers });
134
+ * const client = createClient({ ...config, editMode: edit });
135
+ *
136
+ * A client built with `editMode: edit` marks what it reads, and `capaAttrs`
137
+ * then tags those entries and only those.
138
+ */
139
+ export declare function editMode(input: {
140
+ draftMode: () => {
141
+ isEnabled: boolean;
142
+ } | Promise<{
143
+ isEnabled: boolean;
144
+ }>;
145
+ headers: () => HeaderReader | Promise<HeaderReader>;
146
+ }): Promise<boolean>;
147
+ export interface EditRequest {
148
+ /** True when the page must render in edit mode. */
149
+ edit: boolean;
150
+ /** True when a `capa-edit` token was presented and Capa accepted it. */
151
+ verified: boolean;
152
+ /**
153
+ * The request headers to forward: `x-capa-edit` removed, then set to `1`
154
+ * when the token verified. Hand them to `NextResponse.next({ request: { headers } })`.
155
+ */
156
+ headers: Headers;
157
+ /** `EDIT_CACHE_CONTROL` in edit mode, otherwise null. Set it on the response. */
158
+ cacheControl: string | null;
159
+ }
160
+ /**
161
+ * The middleware half of edit mode.
162
+ *
163
+ * export async function middleware(request: NextRequest) {
164
+ * const edit = await resolveEditRequest(request, publishedClient());
165
+ * const response = NextResponse.next({ request: { headers: edit.headers } });
166
+ * if (edit.cacheControl) response.headers.set("Cache-Control", edit.cacheControl);
167
+ * return response;
168
+ * }
169
+ *
170
+ * The token is checked with Capa (`client.preview`), exactly as a preview link
171
+ * is: the site never holds the signing key. A bad, expired or unverifiable
172
+ * token means not in edit mode; it never throws, because a broken edit link
173
+ * must still render the public page.
174
+ */
175
+ export declare function resolveEditRequest(request: {
176
+ url: string | URL;
177
+ headers: Headers;
178
+ cookies?: {
179
+ has(name: string): boolean;
180
+ };
181
+ }, client: Pick<CapaNextClient, "preview">): Promise<EditRequest>;
182
+ /**
183
+ * Where the env-driven helpers read their settings (M6): the one pair of
184
+ * names the whole product uses (the MCP server, `capa-codegen`, `capa
185
+ * persist`, every curl example in the API docs).
186
+ */
187
+ export declare const CAPA_ENV: {
188
+ readonly baseUrl: "CAPA_API_URL";
189
+ readonly apiKey: "CAPA_KEY";
190
+ readonly draftKey: "CAPA_DRAFT_KEY";
191
+ readonly version: "CAPA_API_VERSION";
192
+ };
193
+ /** Older names that still work, read only when the name above is unset. */
194
+ export declare const CAPA_ENV_ALIASES: Readonly<Record<string, string>>;
195
+ export declare const DEFAULT_API_VERSION = "2026-10-01";
196
+ type DraftModeFn = () => {
197
+ isEnabled: boolean;
198
+ enable?: () => void;
199
+ disable?: () => void;
200
+ } | Promise<{
201
+ isEnabled: boolean;
202
+ enable?: () => void;
203
+ disable?: () => void;
204
+ }>;
205
+ /**
206
+ * The published-key client from env: what verifying a token needs. `overrides`
207
+ * win over env, and pass codegen's `CapaQuery` to type the builder:
208
+ * `getPublishedClient<CapaQuery>()`.
209
+ */
210
+ export declare function getPublishedClient<Q = UntypedQuery>(overrides?: Partial<CapaNextConfig>): CapaNextClient<Q>;
211
+ /**
212
+ * The client for this request, from env: the draft key under draft mode,
213
+ * otherwise the published key, and `editMode` worked out for you.
214
+ *
215
+ * import { draftMode, headers } from "next/headers";
216
+ * const capa = await getCapaClient<CapaQuery>({ draftMode, headers });
217
+ * const { data } = await capa.graphql.query(selection, { tags: tagsFor({ namespace: "articles" }) });
218
+ *
219
+ * `CapaQuery` (from `capa-codegen --graphql`) types the builder, as with
220
+ * `createClient`; leave it out for an untyped client. `config` wins over env.
221
+ *
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.
226
+ */
227
+ export declare function getCapaClient<Q = UntypedQuery>(input: {
228
+ draftMode: DraftModeFn;
229
+ headers: () => HeaderReader | Promise<HeaderReader>;
230
+ config?: Partial<CapaNextConfig>;
231
+ /** Next's `unstable_cache`, as `graphql()` takes it. Leave it out: Next's own is loaded. */
232
+ unstable_cache?: UnstableCache;
233
+ }): Promise<CapaNextClient<Q, NextCacheOptions>>;
234
+ /** Where a GraphQL read in Next keeps its answer: Next's data cache, as `graphql()` and `getCapaClient` keep it. */
235
+ export interface NextCacheOptions extends GraphQLCallOptions {
236
+ /**
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.
242
+ */
243
+ tags?: string[];
244
+ /** Seconds to cache, or `false` to cache until a tag is revalidated. */
245
+ revalidate?: number | false;
246
+ }
247
+ export interface NextGraphQLOptions extends NextCacheOptions {
248
+ /**
249
+ * Next's `draftMode` and `headers`, to work out draft and edit mode as
250
+ * `getCapaClient` does: drafts under draft mode, and in edit mode every
251
+ * entry the query reads marked for `capaAttrs`.
252
+ */
253
+ draftMode?: DraftModeFn;
254
+ headers?: () => HeaderReader | Promise<HeaderReader>;
255
+ /**
256
+ * Read drafts with `CAPA_DRAFT_KEY`, uncached. Worked out from `draftMode`
257
+ * when that is passed; set it to decide yourself.
258
+ */
259
+ draft?: boolean;
260
+ /** Overrides for the env-derived client (base URL, key, version, edit mode, fetch). Each one given is not read from env. */
261
+ config?: Partial<CapaNextConfig>;
262
+ /**
263
+ * 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
267
+ * cache is not used for it: that cache keeps any 200, and a GraphQL error
268
+ * is a 200 (a root field that timed out), and in Next 15 it keeps nothing
269
+ * without a `revalidate`, tags or not. Pass it only to supply another.
270
+ */
271
+ unstable_cache?: UnstableCache;
272
+ }
273
+ /**
274
+ * Next's `unstable_cache` (`import { unstable_cache } from "next/cache"`),
275
+ * typed here so this package never imports `next/*`.
276
+ */
277
+ export type UnstableCache = <T extends (...args: any[]) => Promise<any>>(cb: T, keyParts?: string[], options?: {
278
+ revalidate?: number | false;
279
+ tags?: string[];
280
+ }) => T;
281
+ /**
282
+ * GraphQL in a server component, one line, from env:
283
+ *
284
+ * import { draftMode, headers } from "next/headers";
285
+ * import { graphql, tagsFor } from "@capacms/sdk/nextjs";
286
+ * import { LatestModels } from "./capa-graphql";
287
+ * const LATEST = `#graphql
288
+ * query Latest($first: Int) { articles(first: $first) { nodes { id model title } } }
289
+ * `;
290
+ * const { data } = await graphql(LATEST, { first: 5 }, { draftMode, headers, tags: tagsFor({ namespace: LatestModels }) });
291
+ *
292
+ * Once `capa-codegen --graphql` has seen the literal, `data` and the
293
+ * variables are typed from it, as they are for a `<Name>Document` it writes,
294
+ * and `LatestModels` lists the models it reads.
295
+ *
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
+ * `cap_` key, and bypasses the cache (the API answers a development key's GET
303
+ * `no-store`), the same split `getCapaClient` makes for REST, and in edit mode
304
+ * each entry that selects `id` and `model` is marked for `capaAttrs`, uncached.
305
+ * Outside a Next request (a script, a test) the read is simply sent.
306
+ *
307
+ * `persisted: true` sends only the hash, and pays off once the documents are
308
+ * stored with `capa persist`, which takes a development key: a production
309
+ * key never stores one. Until then a
310
+ * hash misses, and the client remembers the miss for five minutes and sends
311
+ * the document by POST meanwhile, so it costs one extra GET per five minutes.
312
+ */
313
+ export declare function graphql<D extends keyof CapaDocuments>(document: D, ...rest: VariablesThenOptions<CapaDocumentVariables<D>, NextGraphQLOptions>): Promise<GraphQLResult<CapaDocumentResult<D>>>;
314
+ export declare function graphql<TData = Record<string, unknown>, TVariables = Record<string, unknown>, D extends string = string>(document: NotARecordedDocument<D, TypedDocument<TData, TVariables> | D>, ...rest: VariablesThenOptions<TVariables, NextGraphQLOptions>): Promise<GraphQLResult<TData>>;
315
+ /** Only a path on this site: never `//elsewhere.example` or a full URL. */
316
+ export declare function safeSitePath(value: string | null | undefined): string;
317
+ /**
318
+ * `app/api/capa/preview/route.ts`:
319
+ *
320
+ * import { draftMode } from "next/headers";
321
+ * import { redirect } from "next/navigation";
322
+ * export const GET = createPreviewRoute({ draftMode, redirect });
323
+ *
324
+ * Checks the token with Capa (the site never holds the signing key), turns
325
+ * draft mode on and lands on the entry's page. A bad or expired token lands on
326
+ * the page without draft mode and `?preview=expired`; Capa unreachable gives
327
+ * `?preview=unavailable`.
328
+ */
329
+ export declare function createPreviewRoute(input: {
330
+ draftMode: DraftModeFn;
331
+ redirect: (url: string) => never | void;
332
+ client?: () => Pick<CapaNextClient, "preview">;
333
+ /** Runs after draft mode is enabled, before the redirect (cookie tweaks). */
334
+ onEnable?: () => void | Promise<void>;
335
+ }): (request: Request) => Promise<Response | void>;
336
+ /** `app/api/capa/exit/route.ts`: `export const GET = exitPreviewRoute({ draftMode, redirect });` */
337
+ export declare function exitPreviewRoute(input: {
338
+ draftMode: DraftModeFn;
339
+ redirect: (url: string) => never | void;
340
+ }): (request: Request) => Promise<Response | void>;
341
+ interface MiddlewareRequest {
342
+ url: string;
343
+ headers: Headers;
344
+ nextUrl: URL & {
345
+ clone(): URL;
346
+ };
347
+ cookies: {
348
+ getAll(): Array<{
349
+ name: string;
350
+ value: string;
351
+ }>;
352
+ };
353
+ }
354
+ interface NextResponseLike {
355
+ next(init?: {
356
+ request?: {
357
+ headers?: Headers;
358
+ };
359
+ }): {
360
+ headers: Headers;
361
+ };
362
+ rewrite(url: URL): unknown;
363
+ }
364
+ /**
365
+ * `middleware.ts` in one line:
366
+ *
367
+ * import { NextResponse } from "next/server";
368
+ * export const middleware = capaMiddleware({ NextResponse });
369
+ *
370
+ * - `?capa-preview=<token>` on any page goes to `previewRoute`, which turns
371
+ * draft mode on and comes back;
372
+ * - `?capa-view=published` renders without the draft cookie, for the editor's
373
+ * Published view;
374
+ * - `?capa-edit=<token>` turns edit mode on (ids and overlay, published data);
375
+ * - every edit-mode response is `private, no-store`.
376
+ */
377
+ export declare function capaMiddleware(input: {
378
+ NextResponse: NextResponseLike;
379
+ previewRoute?: string;
380
+ client?: () => Pick<CapaNextClient, "preview">;
381
+ }): (request: MiddlewareRequest) => Promise<unknown>;