@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
package/dist/http.js CHANGED
@@ -10,7 +10,7 @@ class CapaError extends Error {
10
10
  status;
11
11
  path;
12
12
  constructor(status, path, body) {
13
- super(`Capa API ${status} on ${path}${body ? ` — ${body.slice(0, 200)}` : ""}`);
13
+ super(`Capa API ${status} on ${path}${body ? `: ${body.slice(0, 200)}` : ""}`);
14
14
  this.name = "CapaError";
15
15
  this.status = status;
16
16
  this.path = path;
package/dist/index.d.ts CHANGED
@@ -6,5 +6,5 @@ export { WEBHOOKS_NEED_ACCESS_TOKEN } from "./webhooks";
6
6
  export type { CreateWebhookEndpointInput, ListWebhookDeliveriesOptions, ResumeWebhookEndpointOptions, ResumeWebhookEndpointResult, UpdateWebhookEndpointInput, WebhookDeliveriesPage, WebhookDeliveriesResource, WebhookDelivery, WebhookDeliveryDetail, WebhookDeliveryStatus, WebhookDisabledReason, WebhookEndpoint, WebhookEndpointDetail, WebhookEndpointsResource, WebhookEndpointWithSecret, WebhookEventCatalogueEntry, WebhookEventGroup, WebhookPagination, WebhooksResource, } from "./webhooks";
7
7
  export { DEFAULT_WEBHOOK_TOLERANCE_SECONDS, parseWebhookSignatureHeader, signWebhookPayload, verifyWebhookSignature, WEBHOOK_SIGNATURE_HEADER, } from "./webhook-signature";
8
8
  export type { ParsedWebhookSignature, VerifyWebhookSignatureInput, } from "./webhook-signature";
9
- export { generate, normalizeTypes, readStampedChecksum, typeNames } from "./codegen";
10
- export type { CodegenResult } from "./codegen";
9
+ export { generate, normalizeTypes, readStampedChecksum, restTypesFromIntrospection, typeNames } from "./codegen";
10
+ export type { CodegenResult, RestTypesResult } from "./codegen";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.typeNames = exports.readStampedChecksum = exports.normalizeTypes = exports.generate = exports.WEBHOOK_SIGNATURE_HEADER = exports.verifyWebhookSignature = exports.signWebhookPayload = exports.parseWebhookSignatureHeader = exports.DEFAULT_WEBHOOK_TOLERANCE_SECONDS = exports.WEBHOOKS_NEED_ACCESS_TOKEN = exports.CapaError = exports.instanceIdOf = exports.createClient = void 0;
3
+ exports.typeNames = exports.restTypesFromIntrospection = exports.readStampedChecksum = exports.normalizeTypes = exports.generate = exports.WEBHOOK_SIGNATURE_HEADER = exports.verifyWebhookSignature = exports.signWebhookPayload = exports.parseWebhookSignatureHeader = exports.DEFAULT_WEBHOOK_TOLERANCE_SECONDS = exports.WEBHOOKS_NEED_ACCESS_TOKEN = exports.CapaError = exports.instanceIdOf = exports.createClient = void 0;
4
4
  var client_1 = require("./client");
5
5
  Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return client_1.createClient; } });
6
6
  Object.defineProperty(exports, "instanceIdOf", { enumerable: true, get: function () { return client_1.instanceIdOf; } });
@@ -18,4 +18,5 @@ var codegen_1 = require("./codegen");
18
18
  Object.defineProperty(exports, "generate", { enumerable: true, get: function () { return codegen_1.generate; } });
19
19
  Object.defineProperty(exports, "normalizeTypes", { enumerable: true, get: function () { return codegen_1.normalizeTypes; } });
20
20
  Object.defineProperty(exports, "readStampedChecksum", { enumerable: true, get: function () { return codegen_1.readStampedChecksum; } });
21
+ Object.defineProperty(exports, "restTypesFromIntrospection", { enumerable: true, get: function () { return codegen_1.restTypesFromIntrospection; } });
21
22
  Object.defineProperty(exports, "typeNames", { enumerable: true, get: function () { return codegen_1.typeNames; } });
@@ -1,15 +1,31 @@
1
1
  /**
2
2
  * The attributes that make a rendered field clickable in Capa's live preview.
3
3
  *
4
- * <h1 {...capaAttrs(entry, "title", isDraft)}>{entry.fields.title}</h1>
4
+ * <h1 {...capaAttrs(entry, "title")}>{entry.fields.title}</h1>
5
+ * <h1 {...capaAttrs(node, "title")}>{node.title}</h1> // a GraphQL node
5
6
  *
6
- * `field` is the field's namespace, which is the key it has in `entry.fields`:
7
- * the API renders every field under its namespace, and the Capa editor tags
8
- * each field row with that same namespace, so one string names the field on
9
- * both sides of the preview frame.
7
+ * For a REST entry, `field` is the field's namespace, which is the key it has
8
+ * in `entry.fields`: the API renders every field under its namespace, and the
9
+ * Capa editor tags each field row with that same namespace, so one string
10
+ * names the field on both sides of the preview frame.
10
11
  *
11
- * Pass `enabled = false` outside draft mode and the element carries nothing, so
12
- * a published page never ships entry ids in its markup.
12
+ * For a GraphQL node (an object that selected `id` and `model`), `field` is
13
+ * the field as selected. A field GraphQL renamed (`hero_image` for
14
+ * `hero-image`, `status_field` for `status`) is tagged by its namespace: a
15
+ * client in edit mode reads the key's schema to know which those are.
16
+ *
17
+ * EDIT MODE DECIDES, NOT THE CALLER. An entry read by a client created with
18
+ * `editMode: true` carries a hidden edit mark (`markEditEntries`), and
19
+ * `capaAttrs` tags only marked entries. So a published page, read with a normal
20
+ * client, ships no entry ids in its markup without the site passing anything,
21
+ * and the same component in the Capa editor is clickable. The mark is a
22
+ * non-enumerable symbol: it does not show up in JSON, logs or a spread copy, and
23
+ * it does not survive being passed to a client component as a prop, which is
24
+ * the safe direction to fail in.
25
+ *
26
+ * Pass `enabled` to override: `true` tags an unmarked entry, `false` tags
27
+ * nothing. Sites written before edit mode passed `isDraft` here and keep
28
+ * working unchanged.
13
29
  */
14
30
  export type CapaAttrs = {
15
31
  "data-capa-entry": string;
@@ -18,7 +34,65 @@ export type CapaAttrs = {
18
34
  "data-capa-entry"?: undefined;
19
35
  "data-capa-field"?: undefined;
20
36
  };
21
- export declare function capaAttrs<T = Record<string, unknown>>(entry: {
37
+ /** `Symbol.for`, so two copies of the SDK in one bundle agree on the mark. */
38
+ export declare const CAPA_EDIT: unique symbol;
39
+ /** Whether an entry was read in edit mode. */
40
+ export declare function isEditEntry(entry: unknown): boolean;
41
+ /**
42
+ * Mark every entry inside `value`, related entries included, so a click on an
43
+ * author inside an article opens the author. Walks arrays and plain objects,
44
+ * never the same object twice. Returns `value` for chaining.
45
+ */
46
+ export declare function markEditEntries<T>(value: T): T;
47
+ /**
48
+ * Mark every entry inside a GraphQL result's `data`: each object that
49
+ * selected `id` and `model`, related entries included. `renamed(model)` gives
50
+ * that model's fields whose GraphQL name is not their namespace, by GraphQL
51
+ * name, so `capaAttrs` tags those by namespace. Returns `data`.
52
+ */
53
+ export declare function markGraphQLEntries<T>(data: T, renamed?: (model: string) => Readonly<Record<string, string>> | undefined): T;
54
+ /** Whether a GraphQL result's `data` holds any entry `markGraphQLEntries` would mark. */
55
+ export declare function hasGraphQLEntry(data: unknown): boolean;
56
+ /** Keep `from`'s edit mark on `to`, for a copy of an entry in another shape (`toTree`). */
57
+ export declare function carryEditMark<T extends object>(from: unknown, to: T): T;
58
+ /** GraphQL's system fields, which name the entry rather than one of its fields. */
59
+ type GraphQLSystemField = "id" | "model" | "status" | "createdAt" | "updatedAt" | "publishedAt" | "_version" | "_tags" | "_folder" | "__typename";
60
+ /**
61
+ * The `field` `capaAttrs` takes for `E`: a key of `fields` for a REST entry;
62
+ * a field as selected for a GraphQL node, which selected `id` and `model`;
63
+ * any name for a bare `{ id }`. A GraphQL node without `model` takes none,
64
+ * and the compiler says why.
65
+ */
66
+ export type TaggableField<E> = unknown extends FieldsOf<E> ? E extends {
67
+ model: string;
68
+ } ? Exclude<Extract<keyof E, string>, GraphQLSystemField> : [Exclude<keyof E, "id">] extends [never] ? string : "select model on this node: capaAttrs tags an entry that selected id and model" : Extract<keyof NonNullable<FieldsOf<E>>, string>;
69
+ /** A REST entry's `fields` type; `unknown` for anything without one (a GraphQL node, a bare `{ id }`). */
70
+ type FieldsOf<E> = "fields" extends keyof E ? E["fields" & keyof E] : unknown;
71
+ export declare function capaAttrs<E extends {
22
72
  id: string;
23
- fields?: T;
24
- }, field: Extract<keyof T, string>, enabled?: boolean): CapaAttrs;
73
+ }>(entry: E, field: TaggableField<E>, enabled?: boolean): CapaAttrs;
74
+ /**
75
+ * The attributes for every field of `T`, one `CapaAttrs` per field name.
76
+ * `capa-codegen` writes `<Model>Attrs = FieldAttrs<Model>` beside each
77
+ * `<Model>Select`, so `const a: ArticleAttrs = fieldAttrs(entry)` fails to
78
+ * compile on a wrong field name (M5). `fieldAttrs` of a REST entry typed with
79
+ * `Model` returns exactly this type.
80
+ */
81
+ export type FieldAttrs<T> = {
82
+ readonly [K in Extract<keyof T, string>]: CapaAttrs;
83
+ };
84
+ /**
85
+ * Typed attributes for every field of one entry (M5), or of one GraphQL node:
86
+ *
87
+ * const a = fieldAttrs(article);
88
+ * <h1 {...a.title}>…</h1> // a.titel is a compile error
89
+ *
90
+ * Same rule as `capaAttrs`: empty unless the entry was read in edit mode, or
91
+ * `enabled` says otherwise.
92
+ */
93
+ export declare function fieldAttrs<E extends {
94
+ id: string;
95
+ }>(entry: E, enabled?: boolean): {
96
+ readonly [K in TaggableField<E>]: CapaAttrs;
97
+ };
98
+ export {};
@@ -1,8 +1,125 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CAPA_EDIT = void 0;
4
+ exports.isEditEntry = isEditEntry;
5
+ exports.markEditEntries = markEditEntries;
6
+ exports.markGraphQLEntries = markGraphQLEntries;
7
+ exports.hasGraphQLEntry = hasGraphQLEntry;
8
+ exports.carryEditMark = carryEditMark;
3
9
  exports.capaAttrs = capaAttrs;
4
- function capaAttrs(entry, field, enabled = true) {
10
+ exports.fieldAttrs = fieldAttrs;
11
+ /** `Symbol.for`, so two copies of the SDK in one bundle agree on the mark. */
12
+ exports.CAPA_EDIT = Symbol.for("capacms.edit");
13
+ /** Whether an entry was read in edit mode. */
14
+ function isEditEntry(entry) {
15
+ return (typeof entry === "object" &&
16
+ entry !== null &&
17
+ entry[exports.CAPA_EDIT] === true);
18
+ }
19
+ /**
20
+ * Where a GraphQL node read in edit mode keeps the namespaces of the fields
21
+ * GraphQL renamed, by GraphQL name. Hidden like the mark itself.
22
+ */
23
+ const CAPA_EDIT_NAMESPACES = Symbol.for("capacms.edit.namespaces");
24
+ /** A REST entry: an id beside `fields`. */
25
+ function looksLikeEntry(value) {
26
+ return typeof value.id === "string" && typeof value.fields === "object" && value.fields !== null;
27
+ }
28
+ /** A GraphQL entry: the `Entry` interface's `id` and `model`, which every model type has. */
29
+ function looksLikeGraphQLEntry(value) {
30
+ return typeof value.id === "string" && typeof value.model === "string";
31
+ }
32
+ /** Define a hidden property: absent from JSON, keys and spreads. */
33
+ function hide(record, key, value) {
34
+ Object.defineProperty(record, key, { value, enumerable: false, configurable: true });
35
+ }
36
+ /** Visit every plain object and array inside `value` once, cycles included. */
37
+ function eachObject(value, visit) {
38
+ const seen = new Set();
39
+ const walk = (node) => {
40
+ if (typeof node !== "object" || node === null || seen.has(node))
41
+ return;
42
+ seen.add(node);
43
+ if (Array.isArray(node)) {
44
+ for (const item of node)
45
+ walk(item);
46
+ return;
47
+ }
48
+ const record = node;
49
+ visit(record);
50
+ for (const key of Object.keys(record))
51
+ walk(record[key]);
52
+ };
53
+ walk(value);
54
+ }
55
+ /**
56
+ * Mark every entry inside `value`, related entries included, so a click on an
57
+ * author inside an article opens the author. Walks arrays and plain objects,
58
+ * never the same object twice. Returns `value` for chaining.
59
+ */
60
+ function markEditEntries(value) {
61
+ eachObject(value, (record) => {
62
+ if (looksLikeEntry(record) && Object.isExtensible(record))
63
+ hide(record, exports.CAPA_EDIT, true);
64
+ });
65
+ return value;
66
+ }
67
+ /**
68
+ * Mark every entry inside a GraphQL result's `data`: each object that
69
+ * selected `id` and `model`, related entries included. `renamed(model)` gives
70
+ * that model's fields whose GraphQL name is not their namespace, by GraphQL
71
+ * name, so `capaAttrs` tags those by namespace. Returns `data`.
72
+ */
73
+ function markGraphQLEntries(data, renamed = () => undefined) {
74
+ eachObject(data, (record) => {
75
+ if (!looksLikeGraphQLEntry(record) || !Object.isExtensible(record))
76
+ return;
77
+ hide(record, exports.CAPA_EDIT, true);
78
+ const namespaces = renamed(record.model);
79
+ if (namespaces)
80
+ hide(record, CAPA_EDIT_NAMESPACES, namespaces);
81
+ });
82
+ return data;
83
+ }
84
+ /** Whether a GraphQL result's `data` holds any entry `markGraphQLEntries` would mark. */
85
+ function hasGraphQLEntry(data) {
86
+ let found = false;
87
+ eachObject(data, (record) => {
88
+ found ||= looksLikeGraphQLEntry(record);
89
+ });
90
+ return found;
91
+ }
92
+ /** Keep `from`'s edit mark on `to`, for a copy of an entry in another shape (`toTree`). */
93
+ function carryEditMark(from, to) {
94
+ if (isEditEntry(from) && Object.isExtensible(to))
95
+ hide(to, exports.CAPA_EDIT, true);
96
+ return to;
97
+ }
98
+ /** The namespace a field of `entry` is tagged with: a renamed GraphQL field's own, else the name given. */
99
+ function namespaceOf(entry, field) {
100
+ const namespaces = entry[CAPA_EDIT_NAMESPACES];
101
+ return namespaces?.[field] ?? field;
102
+ }
103
+ function capaAttrs(entry, field, enabled = isEditEntry(entry)) {
5
104
  if (!enabled)
6
105
  return {};
7
- return { "data-capa-entry": entry.id, "data-capa-field": field };
106
+ return { "data-capa-entry": entry.id, "data-capa-field": namespaceOf(entry, field) };
107
+ }
108
+ /**
109
+ * Typed attributes for every field of one entry (M5), or of one GraphQL node:
110
+ *
111
+ * const a = fieldAttrs(article);
112
+ * <h1 {...a.title}>…</h1> // a.titel is a compile error
113
+ *
114
+ * Same rule as `capaAttrs`: empty unless the entry was read in edit mode, or
115
+ * `enabled` says otherwise.
116
+ */
117
+ function fieldAttrs(entry, enabled = isEditEntry(entry)) {
118
+ return new Proxy({}, {
119
+ get(_target, key) {
120
+ if (typeof key !== "string")
121
+ return undefined;
122
+ return enabled ? { "data-capa-entry": entry.id, "data-capa-field": namespaceOf(entry, key) } : {};
123
+ },
124
+ });
8
125
  }
@@ -1,6 +1,18 @@
1
- import type { Select } from "./select-types";
1
+ import type { CapaDocumentResult, CapaDocuments, CapaDocumentVariables, NotARecordedDocument } from "./graphql/documents";
2
+ import { type GraphQLCallOptions, type GraphQLResult, type TypedDocument, type VariablesThenOptions } from "./graphql/request";
3
+ import { type GraphQLSchemaSummary } from "./graphql/summary";
4
+ import { type ExactSelection, type QueryResult, type QuerySelection, type UntypedQuery } from "./graphql/typed";
5
+ export { CapaError, isCapaError } from "./errors";
6
+ import type { EntryFields, FlatFields, FlatRead, IncludedFields } from "./entry-fields";
7
+ import type { ExpandedTargets, Select } from "./select-types";
2
8
  export interface CapaNextConfig {
3
9
  baseUrl: string;
10
+ /**
11
+ * A `cap_` key, or the legacy key a site already holds: `pk_`, `sk_`, or
12
+ * an older key with no prefix. A legacy key reads through every read call
13
+ * and warns once per process; `preview()` and draft reads take a `cap_` key
14
+ * only.
15
+ */
4
16
  apiKey: string;
5
17
  version: string;
6
18
  contract?: 1;
@@ -34,6 +46,23 @@ export interface CapaNextConfig {
34
46
  * Config only, never per call: one build has one schema.
35
47
  */
36
48
  schemaChecksum?: string;
49
+ /**
50
+ * Read in edit mode: every entry this client returns carries the hidden edit
51
+ * mark, so `capaAttrs` tags it for the Capa editor. Leave it off for visitors
52
+ * and a published page ships no entry ids. `@capacms/sdk/nextjs` works it out
53
+ * per request with `editMode()`.
54
+ *
55
+ * The same request is sent either way. A GraphQL read marks each object
56
+ * that selected `id` and `model`, and to tag a field GraphQL renamed by its
57
+ * namespace, it also reads the key's schema, once a minute.
58
+ */
59
+ editMode?: boolean;
60
+ /**
61
+ * The concrete path being rendered (`/blog/hello`), sent as `Capa-Path`
62
+ * beside `Capa-Page` (`/blog/[slug]`). Telemetry only, like `page`: it is how
63
+ * Capa lists every real URL an entry appears on. Usually set per call.
64
+ */
65
+ path?: string;
37
66
  /** Injected for tests, non-standard runtimes, and framework fetch wrappers. */
38
67
  fetch?: typeof fetch;
39
68
  }
@@ -53,7 +82,18 @@ export interface CallOptions {
53
82
  * writing the code.
54
83
  */
55
84
  page?: string;
85
+ /**
86
+ * The concrete path THIS read renders (`/blog/hello`). Overrides `path` on the
87
+ * config. Sent as `Capa-Path` only alongside a `Capa-Page`, since a path with
88
+ * no page says nothing Capa can group.
89
+ */
90
+ path?: string;
56
91
  }
92
+ /**
93
+ * One entry as `/api/entries` returns it: the system keys, and `fields`. A read
94
+ * types `fields` from its model and select (`EntryFields`), so a relation is a
95
+ * reference or the related entry, and a relation list is `{ items, pageInfo }`.
96
+ */
57
97
  export interface Entry<T = Record<string, unknown>> {
58
98
  id: string;
59
99
  model: string;
@@ -97,23 +137,74 @@ export type FilterOperator = "eq" | "ne" | "in" | "nin" | "lt" | "lte" | "gt" |
97
137
  export type FilterScalar = string | number | boolean | null;
98
138
  export type FilterValue = FilterScalar | readonly FilterScalar[];
99
139
  export type Filter = Record<string, Partial<Record<FilterOperator, FilterValue>>>;
100
- export interface ListOptions<T = Record<string, unknown>> extends CallOptions {
101
- select?: Select<T> | string;
140
+ /**
141
+ * How expanded relations come back (`?shape=`). `tree`, the default, nests each
142
+ * one inline where it was selected. `flat` answers every relation as a
143
+ * `{ id, model }` reference and every expanded entry ONCE, in `included`, so
144
+ * twenty articles by one author carry that author once. `inflate(result)`
145
+ * turns a flat result back into the tree one.
146
+ */
147
+ export type ResponseShape = "tree" | "flat";
148
+ export interface ListOptions<T = Record<string, unknown>, S extends Select<T> | string = Select<T> | string> extends CallOptions {
149
+ select?: S;
150
+ shape?: "tree";
102
151
  filter?: Filter;
103
152
  where?: Record<string, unknown>;
104
153
  sort?: readonly string[];
105
154
  limit?: number;
106
155
  count?: boolean;
156
+ /** `page.next`: the page after it. */
107
157
  after?: string;
158
+ /** `page.prev`: the page before it. Or `"end"`: the last `limit` entries of the list. */
108
159
  before?: string;
109
160
  }
110
- export interface GetOptions<T = Record<string, unknown>> extends CallOptions {
111
- select?: Select<T> | string;
161
+ export interface GetOptions<T = Record<string, unknown>, S extends Select<T> | string = Select<T> | string> extends CallOptions {
162
+ select?: S;
163
+ shape?: "tree";
112
164
  }
165
+ /** `list` with `shape: "flat"`. `S` is the select, which types `included`. */
166
+ export type FlatListOptions<T, S extends Select<T> | string = Select<T>> = Omit<ListOptions<T>, "select" | "shape"> & {
167
+ select?: S;
168
+ shape: "flat";
169
+ };
170
+ /** `get` with `shape: "flat"`. */
171
+ export type FlatGetOptions<T, S extends Select<T> | string = Select<T>> = Omit<GetOptions<T>, "select" | "shape"> & {
172
+ select?: S;
173
+ shape: "flat";
174
+ };
175
+ /**
176
+ * `included` of a flat read: model namespace, then entry id, then the entry.
177
+ * Typed by the select: the union of every entry type it expands, each field
178
+ * of which may be absent, and every relation in it a reference.
179
+ */
180
+ export type Included<I> = Record<string, Record<string, Entry<IncludedFields<I>>>>;
181
+ interface FlatExtras<T, S> extends FlatRead<T, S> {
182
+ included: Included<[ExpandedTargets<T, S>] extends [never] ? never : ExpandedTargets<T, S>>;
183
+ /** The select this read sent, which is what `inflate` walks. Absent when none was sent. */
184
+ select?: string;
185
+ }
186
+ /** A `shape=flat` list: every relation in `data` a reference, each expanded entry once in `included`. */
187
+ export type FlatPage<T, S = Select<T>> = Page<Entry<FlatFields<T, S>>> & FlatExtras<T, S>;
188
+ export type FlatSingle<T, S = Select<T>> = Single<Entry<FlatFields<T, S>>> & FlatExtras<T, S>;
189
+ /**
190
+ * `T` as given, never inferred from the options: a read's model type is
191
+ * written (`list<Article>`), and without one the read is untyped whatever its
192
+ * select names. Inferred from a select's names, `T` would be `{ title: any }`,
193
+ * which refuses the relations the same select expands.
194
+ */
195
+ type Given<T> = [T][T extends unknown ? 0 : never];
196
+ /**
197
+ * Reads of `/api/entries`. `T` is the model type (`list<Articles>`), and `S`
198
+ * the select's own type, for `fields` typed exactly as the read returns them
199
+ * (`list<Articles, typeof select>`); see `EntryFields`.
200
+ */
113
201
  export interface EntriesResource {
114
- list<T = Record<string, unknown>>(namespace: string, options?: ListOptions<T>): Promise<Page<Entry<T>>>;
115
- get<T = Record<string, unknown>>(namespace: string, id: string, options?: GetOptions<T>): Promise<Single<Entry<T>> | null>;
116
- iterate<T = Record<string, unknown>>(namespace: string, options?: ListOptions<T>): AsyncGenerator<Entry<T>, void, undefined>;
202
+ list<T = Record<string, unknown>, S extends Select<T> | string = Select<T>>(namespace: string, options: FlatListOptions<Given<T>, S>): Promise<FlatPage<T, S>>;
203
+ list<T = Record<string, unknown>, S extends Select<T> | string = Select<T> | string>(namespace: string, options?: ListOptions<Given<T>, S>): Promise<Page<Entry<EntryFields<T, S>>>>;
204
+ get<T = Record<string, unknown>, S extends Select<T> | string = Select<T>>(namespace: string, id: string, options: FlatGetOptions<Given<T>, S>): Promise<FlatSingle<T, S> | null>;
205
+ get<T = Record<string, unknown>, S extends Select<T> | string = Select<T> | string>(namespace: string, id: string, options?: GetOptions<Given<T>, S>): Promise<Single<Entry<EntryFields<T, S>>> | null>;
206
+ /** Tree only: it yields entries one at a time, which is what `included` exists to avoid. */
207
+ iterate<T = Record<string, unknown>, S extends Select<T> | string = Select<T> | string>(namespace: string, options?: ListOptions<Given<T>, S>): AsyncGenerator<Entry<EntryFields<T, S>>, void, undefined>;
117
208
  }
118
209
  /**
119
210
  * One suggestion drawn from a page's own reads.
@@ -252,7 +343,55 @@ export interface PreviewClaim {
252
343
  path: string | null;
253
344
  expiresAt: string;
254
345
  }
255
- export interface CapaNextClient {
346
+ /**
347
+ * `client.graphql`: run a document, or build one from a typed selection with
348
+ * `client.graphql.query()`. `Q` is the `CapaQuery` type `capa-codegen
349
+ * --graphql` writes; without it selections and results are untyped. `O` is
350
+ * what a call takes: `getCapaClient` from `/nextjs` adds Next's cache `tags`
351
+ * and `revalidate`.
352
+ */
353
+ export interface GraphQLClient<Q = UntypedQuery, O extends GraphQLCallOptions = GraphQLCallOptions> {
354
+ /**
355
+ * Run a document written as a literal (`#graphql`, `/* capa *\/` or
356
+ * gql(`...`)) that `capa-codegen --graphql` has checked: its text is looked
357
+ * up in `CapaDocuments`, so `data` and `variables` are typed with no cast.
358
+ *
359
+ * Resolves once the API ran the document, even when `errors` is not empty,
360
+ * because the root fields that worked still carry data. Throws `CapaError`
361
+ * when the API refused the request as a whole, with `graphqlErrors` holding
362
+ * every error it sent: `errors` and no `data`, whether sent as a 4xx or, as
363
+ * GraphQL over HTTP sends it on `application/json`, as a 200.
364
+ */
365
+ <D extends keyof CapaDocuments>(document: D, ...rest: VariablesThenOptions<CapaDocumentVariables<D>, O>): Promise<GraphQLResult<CapaDocumentResult<D>>>;
366
+ /**
367
+ * Run a GraphQL document. A `<Name>Document` from `capa-codegen --graphql`
368
+ * types `data` and `variables` by itself; for any other string, pass the
369
+ * data type: `capa.graphql<{ articles: ... }>(query, variables)`.
370
+ */
371
+ <TData = Record<string, unknown>, TVariables = Record<string, unknown>, D extends string = string>(document: NotARecordedDocument<D, TypedDocument<TData, TVariables> | D>, ...rest: VariablesThenOptions<TVariables, O>): Promise<GraphQLResult<TData>>;
372
+ /**
373
+ * Build the document from a selection object and run it. See "Typed
374
+ * builder" in the README. It takes no `persisted`: see `BuilderCallOptions`.
375
+ */
376
+ query<const S extends QuerySelection<Q>>(selection: S & ExactSelection<S, Q>, options?: BuilderCallOptions<O>): Promise<GraphQLResult<QueryResult<Q, S>>>;
377
+ }
378
+ /**
379
+ * What `graphql.query()` takes: a call's options without `persisted`. The
380
+ * builder prints its document when it runs, so `capa persist`, which stores
381
+ * the documents written in a project, never stored it, and a production key
382
+ * never registers one: every read would miss its hash and fall back to a POST,
383
+ * which is never cached. Unpersisted, a builder read is already a GET the API
384
+ * and the CDN cache. To persist a read, write it as a `#graphql` literal.
385
+ */
386
+ export type BuilderCallOptions<O extends GraphQLCallOptions = GraphQLCallOptions> = Omit<O, "persisted">;
387
+ /**
388
+ * `client.graphql` over one way of reading a document: called with a
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.
392
+ */
393
+ 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
+ export interface CapaNextClient<Q = UntypedQuery, O extends GraphQLCallOptions = GraphQLCallOptions> {
256
395
  entries: EntriesResource;
257
396
  pages: PagesResource;
258
397
  /**
@@ -261,31 +400,22 @@ export interface CapaNextClient {
261
400
  * Returns the claim, or NULL when the token is invalid or expired, because
262
401
  * both mean the same thing to a preview route: do not enable draft mode.
263
402
  * Every other failure throws, so a Capa outage does not look like a bad link.
403
+ * Needs a `cap_` key: a client built with a legacy key throws a
404
+ * `TypeError` here before any request.
264
405
  */
265
406
  preview(token: string, options?: CallOptions): Promise<PreviewClaim | null>;
266
407
  me<T = Record<string, unknown>>(options?: CallOptions): Promise<T>;
267
408
  versions<T = Record<string, unknown>>(options?: CallOptions): Promise<T>;
409
+ graphql: GraphQLClient<Q, O>;
410
+ /**
411
+ * Every model this key can read over GraphQL, with its root fields, fields,
412
+ * filter operators and sort values, read from one introspection request.
413
+ * What `buildGraphQLQuery`, `graphqlToSelect` and `toTree` take.
414
+ */
415
+ graphqlSchema(options?: {
416
+ signal?: AbortSignal;
417
+ }): Promise<GraphQLSchemaSummary<Q>>;
268
418
  }
269
- export declare class CapaError extends Error {
270
- readonly status: number;
271
- readonly type: string;
272
- readonly code: string;
273
- readonly param?: string;
274
- readonly hint?: string;
275
- readonly requestId: string;
276
- readonly docs: string;
277
- constructor(input: {
278
- status: number;
279
- type: string;
280
- code: string;
281
- message: string;
282
- param?: string;
283
- hint?: string;
284
- requestId: string;
285
- docs: string;
286
- });
287
- }
288
- export declare function isCapaError(error: unknown): error is CapaError;
289
419
  export declare function resolveNextConfig(config: CapaNextConfig): ResolvedNextConfig;
290
420
  /**
291
421
  * The page a layout (or template) reads for: none.
@@ -304,5 +434,4 @@ export declare const LAYOUT_PAGE = "(layout)";
304
434
  type SelectInput = string | ReadonlyArray<unknown>;
305
435
  /** Serialize the SDK object form into the canonical `/api/entries` grammar. */
306
436
  export declare function serializeSelect(select: SelectInput): string;
307
- export declare function createClient(config: CapaNextConfig): CapaNextClient;
308
- export {};
437
+ export declare function createClient<Q = UntypedQuery>(config: CapaNextConfig): CapaNextClient<Q>;