@capacms/sdk 1.0.0-next.4 → 1.0.0-next.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +345 -0
  2. package/README.md +1069 -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 -36
  12. package/dist/config.js +47 -1
  13. package/dist/graphql-codegen.d.ts +117 -0
  14. package/dist/graphql-codegen.js +705 -0
  15. package/dist/http.js +1 -1
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.js +2 -1
  18. package/dist/next/attrs.d.ts +51 -12
  19. package/dist/next/attrs.js +74 -20
  20. package/dist/next/client.d.ts +112 -38
  21. package/dist/next/client.js +131 -83
  22. package/dist/next/entry-fields.d.ts +162 -0
  23. package/dist/next/entry-fields.js +2 -0
  24. package/dist/next/errors.d.ts +136 -0
  25. package/dist/next/errors.js +214 -0
  26. package/dist/next/field-names.d.ts +37 -0
  27. package/dist/next/field-names.js +145 -0
  28. package/dist/next/graphql/build.d.ts +27 -0
  29. package/dist/next/graphql/build.js +98 -0
  30. package/dist/next/graphql/documents.d.ts +67 -0
  31. package/dist/next/graphql/documents.js +35 -0
  32. package/dist/next/graphql/edit-mode.d.ts +16 -0
  33. package/dist/next/graphql/edit-mode.js +93 -0
  34. package/dist/next/graphql/filter-values.d.ts +34 -0
  35. package/dist/next/graphql/filter-values.js +96 -0
  36. package/dist/next/graphql/introspection.d.ts +89 -0
  37. package/dist/next/graphql/introspection.js +102 -0
  38. package/dist/next/graphql/plan.d.ts +115 -0
  39. package/dist/next/graphql/plan.js +531 -0
  40. package/dist/next/graphql/request.d.ts +228 -0
  41. package/dist/next/graphql/request.js +283 -0
  42. package/dist/next/graphql/rest.d.ts +66 -0
  43. package/dist/next/graphql/rest.js +502 -0
  44. package/dist/next/graphql/selection.d.ts +55 -0
  45. package/dist/next/graphql/selection.js +212 -0
  46. package/dist/next/graphql/sha256.d.ts +13 -0
  47. package/dist/next/graphql/sha256.js +86 -0
  48. package/dist/next/graphql/summary.d.ts +83 -0
  49. package/dist/next/graphql/summary.js +151 -0
  50. package/dist/next/graphql/tree-layout.d.ts +36 -0
  51. package/dist/next/graphql/tree-layout.js +20 -0
  52. package/dist/next/graphql/tree.d.ts +171 -0
  53. package/dist/next/graphql/tree.js +249 -0
  54. package/dist/next/graphql/typed.d.ts +261 -0
  55. package/dist/next/graphql/typed.js +146 -0
  56. package/dist/next/index.d.ts +28 -5
  57. package/dist/next/index.js +25 -1
  58. package/dist/next/inflate.d.ts +25 -7
  59. package/dist/next/inflate.js +46 -32
  60. package/dist/next/key-family.d.ts +34 -0
  61. package/dist/next/key-family.js +74 -0
  62. package/dist/next/select-types.d.ts +44 -8
  63. package/dist/next/system-keys.d.ts +27 -0
  64. package/dist/next/system-keys.js +42 -0
  65. package/dist/nextjs/index.d.ts +174 -12
  66. package/dist/nextjs/index.js +270 -23
  67. package/dist/nextjs/overlay.d.ts +5 -0
  68. package/dist/nextjs/overlay.js +35 -0
  69. package/package.json +31 -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; } });
@@ -2,11 +2,17 @@
2
2
  * The attributes that make a rendered field clickable in Capa's live preview.
3
3
  *
4
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.
11
+ *
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.
10
16
  *
11
17
  * EDIT MODE DECIDES, NOT THE CALLER. An entry read by a client created with
12
18
  * `editMode: true` carries a hidden edit mark (`markEditEntries`), and
@@ -38,12 +44,45 @@ export declare function isEditEntry(entry: unknown): boolean;
38
44
  * never the same object twice. Returns `value` for chaining.
39
45
  */
40
46
  export declare function markEditEntries<T>(value: T): T;
41
- export declare function capaAttrs<T = Record<string, unknown>>(entry: {
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 {
42
72
  id: string;
43
- fields?: T;
44
- }, field: Extract<keyof T, string>, enabled?: boolean): CapaAttrs;
73
+ }>(entry: E, field: TaggableField<E>, enabled?: boolean): CapaAttrs;
45
74
  /**
46
- * Typed attributes for every field of one entry (M5):
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:
47
86
  *
48
87
  * const a = fieldAttrs(article);
49
88
  * <h1 {...a.title}>…</h1> // a.titel is a compile error
@@ -51,9 +90,9 @@ export declare function capaAttrs<T = Record<string, unknown>>(entry: {
51
90
  * Same rule as `capaAttrs`: empty unless the entry was read in edit mode, or
52
91
  * `enabled` says otherwise.
53
92
  */
54
- export declare function fieldAttrs<T = Record<string, unknown>>(entry: {
93
+ export declare function fieldAttrs<E extends {
55
94
  id: string;
56
- fields?: T;
57
- }, enabled?: boolean): {
58
- readonly [K in Extract<keyof T, string>]: CapaAttrs;
95
+ }>(entry: E, enabled?: boolean): {
96
+ readonly [K in TaggableField<E>]: CapaAttrs;
59
97
  };
98
+ export {};
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CAPA_EDIT = void 0;
4
4
  exports.isEditEntry = isEditEntry;
5
5
  exports.markEditEntries = markEditEntries;
6
+ exports.markGraphQLEntries = markGraphQLEntries;
7
+ exports.hasGraphQLEntry = hasGraphQLEntry;
8
+ exports.carryEditMark = carryEditMark;
6
9
  exports.capaAttrs = capaAttrs;
7
10
  exports.fieldAttrs = fieldAttrs;
8
11
  /** `Symbol.for`, so two copies of the SDK in one bundle agree on the mark. */
@@ -13,46 +16,97 @@ function isEditEntry(entry) {
13
16
  entry !== null &&
14
17
  entry[exports.CAPA_EDIT] === true);
15
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`. */
16
25
  function looksLikeEntry(value) {
17
26
  return typeof value.id === "string" && typeof value.fields === "object" && value.fields !== null;
18
27
  }
19
- /**
20
- * Mark every entry inside `value`, related entries included, so a click on an
21
- * author inside an article opens the author. Walks arrays and plain objects,
22
- * never the same object twice. Returns `value` for chaining.
23
- */
24
- function markEditEntries(value) {
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) {
25
38
  const seen = new Set();
26
- const visit = (node) => {
39
+ const walk = (node) => {
27
40
  if (typeof node !== "object" || node === null || seen.has(node))
28
41
  return;
29
42
  seen.add(node);
30
43
  if (Array.isArray(node)) {
31
44
  for (const item of node)
32
- visit(item);
45
+ walk(item);
33
46
  return;
34
47
  }
35
48
  const record = node;
36
- if (looksLikeEntry(record) && Object.isExtensible(record)) {
37
- Object.defineProperty(record, exports.CAPA_EDIT, {
38
- value: true,
39
- enumerable: false,
40
- configurable: true,
41
- });
42
- }
49
+ visit(record);
43
50
  for (const key of Object.keys(record))
44
- visit(record[key]);
51
+ walk(record[key]);
45
52
  };
46
- visit(value);
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
+ });
47
65
  return value;
48
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
+ }
49
103
  function capaAttrs(entry, field, enabled = isEditEntry(entry)) {
50
104
  if (!enabled)
51
105
  return {};
52
- return { "data-capa-entry": entry.id, "data-capa-field": field };
106
+ return { "data-capa-entry": entry.id, "data-capa-field": namespaceOf(entry, field) };
53
107
  }
54
108
  /**
55
- * Typed attributes for every field of one entry (M5):
109
+ * Typed attributes for every field of one entry (M5), or of one GraphQL node:
56
110
  *
57
111
  * const a = fieldAttrs(article);
58
112
  * <h1 {...a.title}>…</h1> // a.titel is a compile error
@@ -65,7 +119,7 @@ function fieldAttrs(entry, enabled = isEditEntry(entry)) {
65
119
  get(_target, key) {
66
120
  if (typeof key !== "string")
67
121
  return undefined;
68
- return enabled ? { "data-capa-entry": entry.id, "data-capa-field": key } : {};
122
+ return enabled ? { "data-capa-entry": entry.id, "data-capa-field": namespaceOf(entry, key) } : {};
69
123
  },
70
124
  });
71
125
  }
@@ -1,6 +1,18 @@
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";
1
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;
@@ -40,7 +52,9 @@ export interface CapaNextConfig {
40
52
  * and a published page ships no entry ids. `@capacms/sdk/nextjs` works it out
41
53
  * per request with `editMode()`.
42
54
  *
43
- * Changes nothing on the wire. The same request is sent either way.
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.
44
58
  */
45
59
  editMode?: boolean;
46
60
  /**
@@ -75,6 +89,11 @@ export interface CallOptions {
75
89
  */
76
90
  path?: string;
77
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
+ */
78
97
  export interface Entry<T = Record<string, unknown>> {
79
98
  id: string;
80
99
  model: string;
@@ -126,19 +145,21 @@ export type Filter = Record<string, Partial<Record<FilterOperator, FilterValue>>
126
145
  * turns a flat result back into the tree one.
127
146
  */
128
147
  export type ResponseShape = "tree" | "flat";
129
- export interface ListOptions<T = Record<string, unknown>> extends CallOptions {
130
- select?: Select<T> | string;
148
+ export interface ListOptions<T = Record<string, unknown>, S extends Select<T> | string = Select<T> | string> extends CallOptions {
149
+ select?: S;
131
150
  shape?: "tree";
132
151
  filter?: Filter;
133
152
  where?: Record<string, unknown>;
134
153
  sort?: readonly string[];
135
154
  limit?: number;
136
155
  count?: boolean;
156
+ /** `page.next`: the page after it. */
137
157
  after?: string;
158
+ /** `page.prev`: the page before it. Or `"end"`: the last `limit` entries of the list. */
138
159
  before?: string;
139
160
  }
140
- export interface GetOptions<T = Record<string, unknown>> extends CallOptions {
141
- 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;
142
163
  shape?: "tree";
143
164
  }
144
165
  /** `list` with `shape: "flat"`. `S` is the select, which types `included`. */
@@ -153,23 +174,37 @@ export type FlatGetOptions<T, S extends Select<T> | string = Select<T>> = Omit<G
153
174
  };
154
175
  /**
155
176
  * `included` of a flat read: model namespace, then entry id, then the entry.
156
- * Typed by the select: the union of every entry type it expands.
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.
157
179
  */
158
- export type Included<I> = Record<string, Record<string, Entry<I>>>;
159
- interface FlatExtras<T, S> {
180
+ export type Included<I> = Record<string, Record<string, Entry<IncludedFields<I>>>>;
181
+ interface FlatExtras<T, S> extends FlatRead<T, S> {
160
182
  included: Included<[ExpandedTargets<T, S>] extends [never] ? never : ExpandedTargets<T, S>>;
161
183
  /** The select this read sent, which is what `inflate` walks. Absent when none was sent. */
162
184
  select?: string;
163
185
  }
164
- export type FlatPage<T, S = Select<T>> = Page<Entry<T>> & FlatExtras<T, S>;
165
- export type FlatSingle<T, S = Select<T>> = Single<Entry<T>> & FlatExtras<T, S>;
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
+ */
166
201
  export interface EntriesResource {
167
- list<T = Record<string, unknown>, S extends Select<T> | string = Select<T>>(namespace: string, options: FlatListOptions<T, S>): Promise<FlatPage<T, S>>;
168
- list<T = Record<string, unknown>>(namespace: string, options?: ListOptions<T>): Promise<Page<Entry<T>>>;
169
- get<T = Record<string, unknown>, S extends Select<T> | string = Select<T>>(namespace: string, id: string, options: FlatGetOptions<T, S>): Promise<FlatSingle<T, S> | null>;
170
- get<T = Record<string, unknown>>(namespace: string, id: string, options?: GetOptions<T>): Promise<Single<Entry<T>> | null>;
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>;
171
206
  /** Tree only: it yields entries one at a time, which is what `included` exists to avoid. */
172
- iterate<T = Record<string, unknown>>(namespace: string, options?: ListOptions<T>): AsyncGenerator<Entry<T>, void, undefined>;
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>;
173
208
  }
174
209
  /**
175
210
  * One suggestion drawn from a page's own reads.
@@ -308,7 +343,56 @@ export interface PreviewClaim {
308
343
  path: string | null;
309
344
  expiresAt: string;
310
345
  }
311
- 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` the same way
391
+ * unless a call gives `tags` or `revalidate`, then through Next's data cache,
392
+ * with the same refusals.
393
+ */
394
+ export declare function graphqlClient<Q, O extends GraphQLCallOptions>(read: (document: string, variables: Record<string, unknown> | undefined, options: O | undefined) => Promise<GraphQLResult<unknown>>): GraphQLClient<Q, O>;
395
+ export interface CapaNextClient<Q = UntypedQuery, O extends GraphQLCallOptions = GraphQLCallOptions> {
312
396
  entries: EntriesResource;
313
397
  pages: PagesResource;
314
398
  /**
@@ -317,31 +401,22 @@ export interface CapaNextClient {
317
401
  * Returns the claim, or NULL when the token is invalid or expired, because
318
402
  * both mean the same thing to a preview route: do not enable draft mode.
319
403
  * Every other failure throws, so a Capa outage does not look like a bad link.
404
+ * Needs a `cap_` key: a client built with a legacy key throws a
405
+ * `TypeError` here before any request.
320
406
  */
321
407
  preview(token: string, options?: CallOptions): Promise<PreviewClaim | null>;
322
408
  me<T = Record<string, unknown>>(options?: CallOptions): Promise<T>;
323
409
  versions<T = Record<string, unknown>>(options?: CallOptions): Promise<T>;
410
+ graphql: GraphQLClient<Q, O>;
411
+ /**
412
+ * Every model this key can read over GraphQL, with its root fields, fields,
413
+ * filter operators and sort values, read from one introspection request.
414
+ * What `buildGraphQLQuery`, `graphqlToSelect` and `toTree` take.
415
+ */
416
+ graphqlSchema(options?: {
417
+ signal?: AbortSignal;
418
+ }): Promise<GraphQLSchemaSummary<Q>>;
324
419
  }
325
- export declare class CapaError extends Error {
326
- readonly status: number;
327
- readonly type: string;
328
- readonly code: string;
329
- readonly param?: string;
330
- readonly hint?: string;
331
- readonly requestId: string;
332
- readonly docs: string;
333
- constructor(input: {
334
- status: number;
335
- type: string;
336
- code: string;
337
- message: string;
338
- param?: string;
339
- hint?: string;
340
- requestId: string;
341
- docs: string;
342
- });
343
- }
344
- export declare function isCapaError(error: unknown): error is CapaError;
345
420
  export declare function resolveNextConfig(config: CapaNextConfig): ResolvedNextConfig;
346
421
  /**
347
422
  * The page a layout (or template) reads for: none.
@@ -360,5 +435,4 @@ export declare const LAYOUT_PAGE = "(layout)";
360
435
  type SelectInput = string | ReadonlyArray<unknown>;
361
436
  /** Serialize the SDK object form into the canonical `/api/entries` grammar. */
362
437
  export declare function serializeSelect(select: SelectInput): string;
363
- export declare function createClient(config: CapaNextConfig): CapaNextClient;
364
- export {};
438
+ export declare function createClient<Q = UntypedQuery>(config: CapaNextConfig): CapaNextClient<Q>;