@capacms/sdk 1.0.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +678 -0
- package/bin/capa-codegen.js +57 -0
- package/dist/client.d.ts +413 -0
- package/dist/client.js +288 -0
- package/dist/codegen.d.ts +69 -0
- package/dist/codegen.js +188 -0
- package/dist/config.d.ts +60 -0
- package/dist/config.js +15 -0
- package/dist/http.d.ts +67 -0
- package/dist/http.js +144 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +21 -0
- package/dist/next/client.d.ts +294 -0
- package/dist/next/client.js +408 -0
- package/dist/next/index.d.ts +3 -0
- package/dist/next/index.js +9 -0
- package/dist/next/select-types.d.ts +39 -0
- package/dist/next/select-types.js +2 -0
- package/dist/nextjs/index.d.ts +53 -0
- package/dist/nextjs/index.js +165 -0
- package/dist/webhook-signature.d.ts +88 -0
- package/dist/webhook-signature.js +161 -0
- package/dist/webhooks.d.ts +244 -0
- package/dist/webhooks.js +130 -0
- package/package.json +69 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CapaError = void 0;
|
|
4
|
+
exports.isCapaError = isCapaError;
|
|
5
|
+
exports.resolveNextConfig = resolveNextConfig;
|
|
6
|
+
exports.serializeSelect = serializeSelect;
|
|
7
|
+
exports.createClient = createClient;
|
|
8
|
+
/**
|
|
9
|
+
* What a `Capa-Schema` value may look like: hex, 8 to 64 characters.
|
|
10
|
+
*
|
|
11
|
+
* COPIED, DELIBERATELY, from `SCHEMA_CHECKSUM` in
|
|
12
|
+
* `apps/api/src/api-next/page-header.ts`, for the same reason `PAGE_ID` below
|
|
13
|
+
* is copied: this package ships with no runtime dependencies at all. If you
|
|
14
|
+
* change one, change the other. The API silently ignores a stamp it cannot
|
|
15
|
+
* parse, so a drift would show up as a `drift` insight that quietly never
|
|
16
|
+
* appears rather than as an error.
|
|
17
|
+
*/
|
|
18
|
+
const SCHEMA_CHECKSUM = /^[a-f0-9]{8,64}$/;
|
|
19
|
+
class CapaError extends Error {
|
|
20
|
+
status;
|
|
21
|
+
type;
|
|
22
|
+
code;
|
|
23
|
+
param;
|
|
24
|
+
hint;
|
|
25
|
+
requestId;
|
|
26
|
+
docs;
|
|
27
|
+
constructor(input) {
|
|
28
|
+
super(input.message);
|
|
29
|
+
this.name = "CapaError";
|
|
30
|
+
this.status = input.status;
|
|
31
|
+
this.type = input.type;
|
|
32
|
+
this.code = input.code;
|
|
33
|
+
this.param = input.param;
|
|
34
|
+
this.hint = input.hint;
|
|
35
|
+
this.requestId = input.requestId;
|
|
36
|
+
this.docs = input.docs;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.CapaError = CapaError;
|
|
40
|
+
function isCapaError(error) {
|
|
41
|
+
return error instanceof CapaError;
|
|
42
|
+
}
|
|
43
|
+
function resolveNextConfig(config) {
|
|
44
|
+
const value = (config ?? {});
|
|
45
|
+
const missing = ["baseUrl", "apiKey", "version"].filter((key) => !value[key]);
|
|
46
|
+
if (missing.length) {
|
|
47
|
+
throw new Error(`@capacms/sdk/next: missing ${missing.join(", ")}. ` +
|
|
48
|
+
"createClient needs baseUrl, apiKey and version.");
|
|
49
|
+
}
|
|
50
|
+
if (!value.apiKey.startsWith("cap_")) {
|
|
51
|
+
throw new Error("@capacms/sdk/next: apiKey must start with cap_. Use the legacy @capacms/sdk client for pk_ and sk_ keys.");
|
|
52
|
+
}
|
|
53
|
+
if (value.contract !== undefined && value.contract !== 1) {
|
|
54
|
+
throw new Error("@capacms/sdk/next: contract must be 1 when provided.");
|
|
55
|
+
}
|
|
56
|
+
const fetchImpl = value.fetch ?? globalThis.fetch;
|
|
57
|
+
if (typeof fetchImpl !== "function") {
|
|
58
|
+
throw new Error("@capacms/sdk/next: no fetch available. Pass one via config.fetch.");
|
|
59
|
+
}
|
|
60
|
+
// Checked here as well as per call, so a bad page on the config throws where
|
|
61
|
+
// the client is built rather than on whichever read happens to run first.
|
|
62
|
+
resolvePage(value.page, undefined);
|
|
63
|
+
// THROWS rather than dropping, the same rule `page` follows and for the same
|
|
64
|
+
// reason: the API must never 400 a running site over a telemetry header, so
|
|
65
|
+
// it ignores what it cannot store, and the SDK is the place a wrong value
|
|
66
|
+
// should surface while somebody is still writing the code. A checksum that
|
|
67
|
+
// is silently dropped is a `drift` insight that never appears, which looks
|
|
68
|
+
// exactly like a site that is up to date.
|
|
69
|
+
if (value.schemaChecksum !== undefined && value.schemaChecksum !== null) {
|
|
70
|
+
if (typeof value.schemaChecksum !== "string" || !SCHEMA_CHECKSUM.test(value.schemaChecksum)) {
|
|
71
|
+
throw new TypeError(`@capacms/sdk/next: schemaChecksum must be the hex checksum from your generated types, for example CAPA_SCHEMA_CHECKSUM. Got ${JSON.stringify(value.schemaChecksum)}.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { ...value, baseUrl: value.baseUrl.replace(/\/+$/, ""), fetchImpl };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* What a `Capa-Page` value may look like.
|
|
78
|
+
*
|
|
79
|
+
* COPIED, DELIBERATELY, from `PAGE_ID_PATTERN` in
|
|
80
|
+
* `packages/shared/src/pages/route-pattern.ts`, which is the one definition the
|
|
81
|
+
* API and the admin both read. It is copied rather than imported because this
|
|
82
|
+
* package ships with no runtime dependencies at all, and `@capa/shared` is a
|
|
83
|
+
* devDependency here. If you change one, change the other: the API silently
|
|
84
|
+
* drops a header it cannot store, so a drift would show up as telemetry that
|
|
85
|
+
* quietly stops arriving rather than as an error.
|
|
86
|
+
*/
|
|
87
|
+
const PAGE_ID = /^\/[A-Za-z0-9._\-[\]/]{0,199}$/;
|
|
88
|
+
/**
|
|
89
|
+
* The page for one call: the call's own value, else the client's, else none.
|
|
90
|
+
*
|
|
91
|
+
* THROWS on a malformed value, and that is the difference between this and the
|
|
92
|
+
* API. A running site must never 400 because a page identity was mangled, so
|
|
93
|
+
* the API ignores what it cannot store. The SDK is the place a typo should
|
|
94
|
+
* surface, while you are writing the code and can still fix it.
|
|
95
|
+
*/
|
|
96
|
+
function resolvePage(configPage, callPage) {
|
|
97
|
+
const value = callPage !== undefined ? callPage : configPage;
|
|
98
|
+
if (value === undefined || value === null)
|
|
99
|
+
return undefined;
|
|
100
|
+
if (typeof value !== "string" || !PAGE_ID.test(value)) {
|
|
101
|
+
throw new TypeError(`@capacms/sdk/next: page must be a path such as "/blog/[slug]" or "/blog/hello". Got ${JSON.stringify(value)}.`);
|
|
102
|
+
}
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
const NAME = /^[A-Za-z0-9_-]+$/;
|
|
106
|
+
function selectName(value, context) {
|
|
107
|
+
if (typeof value !== "string" || !NAME.test(value)) {
|
|
108
|
+
throw new TypeError(`@capacms/sdk/next: ${context} must be a field name.`);
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
function serializeSelectItems(items) {
|
|
113
|
+
if (items.length === 0)
|
|
114
|
+
throw new TypeError("@capacms/sdk/next: select cannot be empty.");
|
|
115
|
+
return items
|
|
116
|
+
.map((item) => {
|
|
117
|
+
if (typeof item === "string") {
|
|
118
|
+
return item === "*" ? item : selectName(item, "select item");
|
|
119
|
+
}
|
|
120
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
121
|
+
throw new TypeError("@capacms/sdk/next: each select item must be a field name or relation object.");
|
|
122
|
+
}
|
|
123
|
+
const entries = Object.entries(item);
|
|
124
|
+
if (entries.length !== 1) {
|
|
125
|
+
throw new TypeError("@capacms/sdk/next: a relation select object must name exactly one field.");
|
|
126
|
+
}
|
|
127
|
+
const [rawName, value] = entries[0];
|
|
128
|
+
const name = selectName(rawName, "relation select key");
|
|
129
|
+
if (value === "*")
|
|
130
|
+
return `${name}(*)`;
|
|
131
|
+
if (Array.isArray(value))
|
|
132
|
+
return `${name}(${serializeSelectItems(value)})`;
|
|
133
|
+
if (!value || typeof value !== "object") {
|
|
134
|
+
throw new TypeError(`@capacms/sdk/next: ${name} must select fields, "*", or select options.`);
|
|
135
|
+
}
|
|
136
|
+
const options = value;
|
|
137
|
+
const allowed = new Set(["select", "limit", "sort", "after"]);
|
|
138
|
+
for (const key of Object.keys(options)) {
|
|
139
|
+
if (!allowed.has(key)) {
|
|
140
|
+
throw new TypeError(`@capacms/sdk/next: ${key} is not a relation select option.`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const inner = options.select === "*"
|
|
144
|
+
? "*"
|
|
145
|
+
: Array.isArray(options.select)
|
|
146
|
+
? serializeSelectItems(options.select)
|
|
147
|
+
: (() => {
|
|
148
|
+
throw new TypeError(`@capacms/sdk/next: ${name}.select must be an array or "*".`);
|
|
149
|
+
})();
|
|
150
|
+
const args = [inner];
|
|
151
|
+
if (options.limit !== undefined) {
|
|
152
|
+
if (!Number.isInteger(options.limit) || Number(options.limit) < 1 || Number(options.limit) > 200) {
|
|
153
|
+
throw new TypeError(`@capacms/sdk/next: ${name}.limit must be an integer from 1 to 200.`);
|
|
154
|
+
}
|
|
155
|
+
args.push(`limit:${options.limit}`);
|
|
156
|
+
}
|
|
157
|
+
if (options.sort !== undefined) {
|
|
158
|
+
if (typeof options.sort !== "string" || !/^-?[A-Za-z0-9_-]+$/.test(options.sort)) {
|
|
159
|
+
throw new TypeError(`@capacms/sdk/next: ${name}.sort must be one field name.`);
|
|
160
|
+
}
|
|
161
|
+
args.push(`sort:${options.sort}`);
|
|
162
|
+
}
|
|
163
|
+
if (options.after !== undefined) {
|
|
164
|
+
if (typeof options.after !== "string" || options.after === "") {
|
|
165
|
+
throw new TypeError(`@capacms/sdk/next: ${name}.after must be a non-empty cursor.`);
|
|
166
|
+
}
|
|
167
|
+
args.push(`after:${options.after}`);
|
|
168
|
+
}
|
|
169
|
+
return `${name}(${args.join(",")})`;
|
|
170
|
+
})
|
|
171
|
+
.join(",");
|
|
172
|
+
}
|
|
173
|
+
/** Serialize the SDK object form into the canonical `/api/entries` grammar. */
|
|
174
|
+
function serializeSelect(select) {
|
|
175
|
+
if (typeof select === "string")
|
|
176
|
+
return select;
|
|
177
|
+
if (!Array.isArray(select)) {
|
|
178
|
+
throw new TypeError("@capacms/sdk/next: select must be a grammar string or an array.");
|
|
179
|
+
}
|
|
180
|
+
return serializeSelectItems(select);
|
|
181
|
+
}
|
|
182
|
+
const FILTER_OPERATORS = new Set([
|
|
183
|
+
"eq",
|
|
184
|
+
"ne",
|
|
185
|
+
"in",
|
|
186
|
+
"nin",
|
|
187
|
+
"lt",
|
|
188
|
+
"lte",
|
|
189
|
+
"gt",
|
|
190
|
+
"gte",
|
|
191
|
+
"contains",
|
|
192
|
+
"startsWith",
|
|
193
|
+
"endsWith",
|
|
194
|
+
"has",
|
|
195
|
+
"hasAny",
|
|
196
|
+
"hasAll",
|
|
197
|
+
"exists",
|
|
198
|
+
"null",
|
|
199
|
+
]);
|
|
200
|
+
const ARRAY_OPERATORS = new Set(["in", "nin", "hasAny", "hasAll"]);
|
|
201
|
+
function filterValue(operator, value) {
|
|
202
|
+
if (Array.isArray(value)) {
|
|
203
|
+
if (!ARRAY_OPERATORS.has(operator)) {
|
|
204
|
+
throw new TypeError(`@capacms/sdk/next: filter operator ${operator} does not accept an array.`);
|
|
205
|
+
}
|
|
206
|
+
return value.map(String).join(",");
|
|
207
|
+
}
|
|
208
|
+
return String(value);
|
|
209
|
+
}
|
|
210
|
+
function listQuery(options) {
|
|
211
|
+
const query = new URLSearchParams();
|
|
212
|
+
if (options.select !== undefined)
|
|
213
|
+
query.set("select", serializeSelect(options.select));
|
|
214
|
+
if (options.filter !== undefined) {
|
|
215
|
+
for (const [path, operations] of Object.entries(options.filter)) {
|
|
216
|
+
for (const [rawOperator, value] of Object.entries(operations)) {
|
|
217
|
+
if (!FILTER_OPERATORS.has(rawOperator)) {
|
|
218
|
+
throw new TypeError(`@capacms/sdk/next: unknown filter operator ${rawOperator}.`);
|
|
219
|
+
}
|
|
220
|
+
if (value === undefined)
|
|
221
|
+
continue;
|
|
222
|
+
const operator = rawOperator;
|
|
223
|
+
query.set(`filter[${path}][${operator}]`, filterValue(operator, value));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (options.where !== undefined)
|
|
228
|
+
query.set("where", JSON.stringify(options.where));
|
|
229
|
+
if (options.sort !== undefined)
|
|
230
|
+
query.set("sort", options.sort.join(","));
|
|
231
|
+
if (options.limit !== undefined)
|
|
232
|
+
query.set("limit", String(options.limit));
|
|
233
|
+
if (options.count !== undefined)
|
|
234
|
+
query.set("count", String(options.count));
|
|
235
|
+
if (options.after !== undefined)
|
|
236
|
+
query.set("after", options.after);
|
|
237
|
+
if (options.before !== undefined)
|
|
238
|
+
query.set("before", options.before);
|
|
239
|
+
return query;
|
|
240
|
+
}
|
|
241
|
+
function optionalString(value) {
|
|
242
|
+
return typeof value === "string" ? value : undefined;
|
|
243
|
+
}
|
|
244
|
+
function unparseable(status, requestId) {
|
|
245
|
+
return new CapaError({
|
|
246
|
+
status,
|
|
247
|
+
type: "api_error",
|
|
248
|
+
code: "unparseable_response",
|
|
249
|
+
message: "Capa returned a response that was not a valid /api/ JSON envelope.",
|
|
250
|
+
requestId,
|
|
251
|
+
docs: "",
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function errorFromEnvelope(status, body, fallbackRequestId) {
|
|
255
|
+
const detail = body.error;
|
|
256
|
+
if (!detail ||
|
|
257
|
+
typeof detail.type !== "string" ||
|
|
258
|
+
typeof detail.code !== "string" ||
|
|
259
|
+
typeof detail.message !== "string" ||
|
|
260
|
+
typeof detail.docs !== "string") {
|
|
261
|
+
return unparseable(status, fallbackRequestId);
|
|
262
|
+
}
|
|
263
|
+
return new CapaError({
|
|
264
|
+
status,
|
|
265
|
+
type: detail.type,
|
|
266
|
+
code: detail.code,
|
|
267
|
+
message: detail.message,
|
|
268
|
+
param: optionalString(detail.param),
|
|
269
|
+
hint: optionalString(detail.hint),
|
|
270
|
+
requestId: optionalString(body.meta?.requestId) ?? fallbackRequestId,
|
|
271
|
+
docs: detail.docs,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function cacheTags(response) {
|
|
275
|
+
const value = response.headers?.get?.("Surrogate-Key") ?? "";
|
|
276
|
+
return value.split(/\s+/).filter(Boolean);
|
|
277
|
+
}
|
|
278
|
+
function createRequester(config) {
|
|
279
|
+
return async function request(path, query = new URLSearchParams(), signal, page) {
|
|
280
|
+
const url = new URL(config.baseUrl + path);
|
|
281
|
+
query.forEach((value, key) => url.searchParams.append(key, value));
|
|
282
|
+
const headers = {
|
|
283
|
+
Accept: "application/json",
|
|
284
|
+
"x-api-key": config.apiKey,
|
|
285
|
+
"Capa-Version": config.version,
|
|
286
|
+
};
|
|
287
|
+
if (config.contract !== undefined)
|
|
288
|
+
headers["Capa-Contract"] = String(config.contract);
|
|
289
|
+
// Absent unless a page was named, so a client that never sets one sends
|
|
290
|
+
// byte-identical requests to the ones it sent before this option existed.
|
|
291
|
+
if (page !== undefined)
|
|
292
|
+
headers["Capa-Page"] = page;
|
|
293
|
+
// Same rule, and on EVERY call rather than only the entries reads: the
|
|
294
|
+
// stamp describes the build, so a page whose only Capa call is `me()`
|
|
295
|
+
// still reports which schema it was generated from.
|
|
296
|
+
if (config.schemaChecksum !== undefined)
|
|
297
|
+
headers["Capa-Schema"] = config.schemaChecksum;
|
|
298
|
+
const response = await config.fetchImpl(url.toString(), { method: "GET", headers, signal });
|
|
299
|
+
const requestId = response.headers?.get?.("X-Request-Id") ?? "";
|
|
300
|
+
const text = await response.text();
|
|
301
|
+
let body;
|
|
302
|
+
try {
|
|
303
|
+
body = JSON.parse(text);
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
throw unparseable(response.status, requestId);
|
|
307
|
+
}
|
|
308
|
+
if (!response.ok)
|
|
309
|
+
throw errorFromEnvelope(response.status, body, requestId);
|
|
310
|
+
if (!body || typeof body !== "object")
|
|
311
|
+
throw unparseable(response.status, requestId);
|
|
312
|
+
return { body: body, cacheTags: cacheTags(response) };
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function createClient(config) {
|
|
316
|
+
const resolved = resolveNextConfig(config);
|
|
317
|
+
const request = createRequester(resolved);
|
|
318
|
+
const entries = {
|
|
319
|
+
async list(namespace, options = {}) {
|
|
320
|
+
const result = await request(`/api/entries/${encodeURIComponent(namespace)}`, listQuery(options), options.signal, resolvePage(resolved.page, options.page));
|
|
321
|
+
return { ...result.body, cacheTags: result.cacheTags };
|
|
322
|
+
},
|
|
323
|
+
async get(namespace, id, options = {}) {
|
|
324
|
+
const query = new URLSearchParams();
|
|
325
|
+
if (options.select !== undefined)
|
|
326
|
+
query.set("select", serializeSelect(options.select));
|
|
327
|
+
try {
|
|
328
|
+
const result = await request(`/api/entries/${encodeURIComponent(namespace)}/${encodeURIComponent(id)}`, query, options.signal, resolvePage(resolved.page, options.page));
|
|
329
|
+
return { ...result.body, cacheTags: result.cacheTags };
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
if (error instanceof CapaError &&
|
|
333
|
+
error.status === 404 &&
|
|
334
|
+
error.type === "not_found" &&
|
|
335
|
+
error.code === "entry_not_found") {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
async *iterate(namespace, options = {}) {
|
|
342
|
+
let after = options.after;
|
|
343
|
+
for (;;) {
|
|
344
|
+
const page = await entries.list(namespace, { ...options, after });
|
|
345
|
+
for (const entry of page.data)
|
|
346
|
+
yield entry;
|
|
347
|
+
if (!page.page.hasNext || !page.page.next)
|
|
348
|
+
return;
|
|
349
|
+
after = page.page.next;
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
const pages = {
|
|
354
|
+
async list(options = {}) {
|
|
355
|
+
const query = new URLSearchParams();
|
|
356
|
+
if (options.entry !== undefined)
|
|
357
|
+
query.set("entry", options.entry);
|
|
358
|
+
return (await request("/api/pages", query, options.signal)).body;
|
|
359
|
+
},
|
|
360
|
+
async get(page, options = {}) {
|
|
361
|
+
try {
|
|
362
|
+
return (await request(
|
|
363
|
+
// A page identity contains slashes, so it is encoded whole into one
|
|
364
|
+
// path segment. The API decodes it back before looking it up.
|
|
365
|
+
`/api/pages/${encodeURIComponent(page)}`, new URLSearchParams(), options.signal)).body;
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
// Same shape as `entries.get`: a page nobody has declared or read is a
|
|
369
|
+
// null, not a throw, because "is this page known to Capa" is a question
|
|
370
|
+
// a caller asks on purpose.
|
|
371
|
+
if (error instanceof CapaError &&
|
|
372
|
+
error.status === 404 &&
|
|
373
|
+
error.code === "page_not_found") {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
return {
|
|
381
|
+
entries,
|
|
382
|
+
pages,
|
|
383
|
+
async preview(token, options = {}) {
|
|
384
|
+
const query = new URLSearchParams();
|
|
385
|
+
query.set("token", token);
|
|
386
|
+
try {
|
|
387
|
+
return (await request("/api/preview", query, options.signal)).body.data;
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
// Invalid and expired both mean "do not enable draft mode", and a
|
|
391
|
+
// preview route has the same fallback for either: render the published
|
|
392
|
+
// page. Anything else (a 500, a network failure, a missing scope) is a
|
|
393
|
+
// real problem and must not be mistaken for a stale link.
|
|
394
|
+
if (error instanceof CapaError &&
|
|
395
|
+
(error.code === "preview_token_invalid" || error.code === "preview_token_expired")) {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
async me(options = {}) {
|
|
402
|
+
return (await request("/api/me", new URLSearchParams(), options.signal, resolvePage(resolved.page, options.page))).body;
|
|
403
|
+
},
|
|
404
|
+
async versions(options = {}) {
|
|
405
|
+
return (await request("/api/versions", new URLSearchParams(), options.signal, resolvePage(resolved.page, options.page))).body;
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { CapaError, createClient, isCapaError, resolveNextConfig, serializeSelect, } from "./client";
|
|
2
|
+
export type { CallOptions, CapaNextClient, CapaNextConfig, EntriesResource, Entry, Filter, FilterOperator, FilterScalar, FilterValue, GetOptions, ListOptions, Page, PageDetail, PageInfo, PageSummary, PagesListOptions, PagesResource, PreviewClaim, ResponseMeta, Single, } from "./client";
|
|
3
|
+
export type { CapaRelation, CapaRelationList, RelationSelectOptions, Select, SelectItem, SelectSort, } from "./select-types";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serializeSelect = exports.resolveNextConfig = exports.isCapaError = exports.createClient = exports.CapaError = void 0;
|
|
4
|
+
var client_1 = require("./client");
|
|
5
|
+
Object.defineProperty(exports, "CapaError", { enumerable: true, get: function () { return client_1.CapaError; } });
|
|
6
|
+
Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return client_1.createClient; } });
|
|
7
|
+
Object.defineProperty(exports, "isCapaError", { enumerable: true, get: function () { return client_1.isCapaError; } });
|
|
8
|
+
Object.defineProperty(exports, "resolveNextConfig", { enumerable: true, get: function () { return client_1.resolveNextConfig; } });
|
|
9
|
+
Object.defineProperty(exports, "serializeSelect", { enumerable: true, get: function () { return client_1.serializeSelect; } });
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** A single relation field emitted by `capa-codegen`. */
|
|
2
|
+
export type CapaRelation<T> = T & {
|
|
3
|
+
readonly __capaRelation: "one";
|
|
4
|
+
readonly __capaRelationTarget: T;
|
|
5
|
+
};
|
|
6
|
+
/** An array relation field emitted by `capa-codegen`. */
|
|
7
|
+
export type CapaRelationList<T> = T[] & {
|
|
8
|
+
readonly __capaRelation: "many";
|
|
9
|
+
readonly __capaRelationTarget: T;
|
|
10
|
+
};
|
|
11
|
+
type StringKey<T> = Extract<keyof T, string>;
|
|
12
|
+
type RelationValue = {
|
|
13
|
+
readonly __capaRelation: "one" | "many";
|
|
14
|
+
readonly __capaRelationTarget: unknown;
|
|
15
|
+
};
|
|
16
|
+
type RelationKeys<T> = {
|
|
17
|
+
[K in StringKey<T>]-?: NonNullable<T[K]> extends RelationValue ? K : never;
|
|
18
|
+
}[StringKey<T>];
|
|
19
|
+
type ScalarKeys<T> = Exclude<StringKey<T>, RelationKeys<T>>;
|
|
20
|
+
type RelationTarget<T> = NonNullable<T> extends {
|
|
21
|
+
readonly __capaRelationTarget: infer R;
|
|
22
|
+
} ? R : never;
|
|
23
|
+
export type SelectSort<T> = ScalarKeys<T> | `-${ScalarKeys<T>}`;
|
|
24
|
+
export interface RelationSelectOptions<T> {
|
|
25
|
+
select: Select<T> | "*";
|
|
26
|
+
limit?: number;
|
|
27
|
+
sort?: SelectSort<T>;
|
|
28
|
+
/** An opaque relation cursor returned by the API. */
|
|
29
|
+
after?: string;
|
|
30
|
+
}
|
|
31
|
+
type RelationSelect<T, K extends RelationKeys<T>> = {
|
|
32
|
+
[P in K]: Select<RelationTarget<T[P]>> | "*" | RelationSelectOptions<RelationTarget<T[P]>>;
|
|
33
|
+
};
|
|
34
|
+
export type SelectItem<T> = "*" | ScalarKeys<T> | {
|
|
35
|
+
[K in RelationKeys<T>]: RelationSelect<T, K>;
|
|
36
|
+
}[RelationKeys<T>];
|
|
37
|
+
/** A typed form of the `/api/entries` `select` grammar. */
|
|
38
|
+
export type Select<T> = ReadonlyArray<SelectItem<T>>;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type CapaNextClient, type CapaNextConfig, type PagesResource, type PreviewClaim } from "../next";
|
|
2
|
+
export interface CacheOptions {
|
|
3
|
+
tags?: string[];
|
|
4
|
+
revalidate?: number | false;
|
|
5
|
+
}
|
|
6
|
+
/** Add Next.js fetch-cache options without importing `next/*`. */
|
|
7
|
+
export declare function withCache(fetchImpl: typeof fetch, options: CacheOptions): typeof fetch;
|
|
8
|
+
export declare function tagsFor(input: {
|
|
9
|
+
model?: string;
|
|
10
|
+
entry?: string;
|
|
11
|
+
key?: string;
|
|
12
|
+
tenant?: string;
|
|
13
|
+
}): string[];
|
|
14
|
+
export interface WebhookPayload {
|
|
15
|
+
data?: {
|
|
16
|
+
instanceId?: unknown;
|
|
17
|
+
modelId?: unknown;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
};
|
|
20
|
+
instanceId?: unknown;
|
|
21
|
+
entryId?: unknown;
|
|
22
|
+
modelId?: unknown;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
/** Revalidate the concrete entry and model identities carried by a webhook. */
|
|
26
|
+
export declare function revalidateFromWebhook(input: {
|
|
27
|
+
payload: WebhookPayload;
|
|
28
|
+
revalidateTag: (tag: string) => void | Promise<void>;
|
|
29
|
+
}): Promise<string[]>;
|
|
30
|
+
/** Select a client using server-only draft state supplied by the caller. */
|
|
31
|
+
export declare function draftClient(input: {
|
|
32
|
+
production: CapaNextConfig;
|
|
33
|
+
draft: CapaNextConfig;
|
|
34
|
+
isDraft: () => boolean | Promise<boolean>;
|
|
35
|
+
}): Promise<CapaNextClient>;
|
|
36
|
+
export declare function routeOf(file: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Verify a preview token from a Next route handler.
|
|
39
|
+
*
|
|
40
|
+
* A thin pass-through to `client.preview`, here so that a preview route reads
|
|
41
|
+
* as one import from `@capacms/sdk/nextjs` alongside the rest of the Next
|
|
42
|
+
* helpers. Null means "do not enable draft mode".
|
|
43
|
+
*/
|
|
44
|
+
export declare function preview(token: string, client: CapaNextClient): Promise<PreviewClaim | null>;
|
|
45
|
+
/**
|
|
46
|
+
* The page list, as a standalone value.
|
|
47
|
+
*
|
|
48
|
+
* `pagesFor(client)` is `client.pages` and exists for symmetry with the other
|
|
49
|
+
* helpers in this module: a build script that only wants the page list can take
|
|
50
|
+
* this and never hold the whole client.
|
|
51
|
+
*/
|
|
52
|
+
export declare function pagesFor(client: CapaNextClient): PagesResource;
|
|
53
|
+
export type { PreviewClaim };
|