@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
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ResolvedConfig } from "./config";
|
|
2
|
+
export declare class CapaError extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
readonly path: string;
|
|
5
|
+
constructor(status: number, path: string, body: string);
|
|
6
|
+
}
|
|
7
|
+
export type Query = Record<string, string | number | boolean | undefined | null>;
|
|
8
|
+
/**
|
|
9
|
+
* Which credential a call carries.
|
|
10
|
+
*
|
|
11
|
+
* `key` is the tenant API key, which is what every read in this SDK uses.
|
|
12
|
+
* `session` is a logged-in person's access token, and it exists because the
|
|
13
|
+
* webhook routes have no API key mount at all (publishing spec D8): managing an
|
|
14
|
+
* endpoint is a session-only act, so an API key cannot reach those routes and
|
|
15
|
+
* `webhooks.*` says so rather than spending a request on a guaranteed 401.
|
|
16
|
+
*/
|
|
17
|
+
export type Auth = "key" | "session";
|
|
18
|
+
/**
|
|
19
|
+
* The bearer variant.
|
|
20
|
+
*
|
|
21
|
+
* `X-Tenant-Key` still goes along for symmetry with the key variant and for
|
|
22
|
+
* proxies and logs that key on it, but NOTHING on the API reads it on a session
|
|
23
|
+
* call: `validateTenant` only checks that the request already has a tenant, and
|
|
24
|
+
* that tenant is the logged-in user's `currentTenant` off the JWT. So `tenantId`
|
|
25
|
+
* does not select the tenant for `webhooks.*`; the session does.
|
|
26
|
+
*/
|
|
27
|
+
export declare function sessionHeaders(config: ResolvedConfig, extra?: Record<string, string>): {
|
|
28
|
+
Authorization: string;
|
|
29
|
+
"X-Tenant-Key": string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A plain `fetch` with no caching of our own, deliberately.
|
|
33
|
+
*
|
|
34
|
+
* Every framework caches differently and owning it here means reimplementing
|
|
35
|
+
* all of them badly. Returning plain data from a plain fetch lets Next's fetch
|
|
36
|
+
* cache, React Router loaders and everything else work natively. What the host
|
|
37
|
+
* CANNOT know is which surrogate keys a response belongs to for CDN purging —
|
|
38
|
+
* Capa emits those, so `listContent`/`getContent` surface them and the host
|
|
39
|
+
* decides what to do with them.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getJson<T>(config: ResolvedConfig, path: string, query?: Query, auth?: Auth): Promise<{
|
|
42
|
+
body: T;
|
|
43
|
+
cacheTags: string[];
|
|
44
|
+
}>;
|
|
45
|
+
/** A conditional GET. Returns `null` when the server answers 304. */
|
|
46
|
+
export declare function getJsonConditional<T>(config: ResolvedConfig, path: string, etag: string | null, query?: Query): Promise<{
|
|
47
|
+
body: T;
|
|
48
|
+
etag: string | null;
|
|
49
|
+
} | null>;
|
|
50
|
+
/** GET an endpoint that answers with text rather than JSON (e.g. /v2/schema/types). */
|
|
51
|
+
export declare function getText(config: ResolvedConfig, path: string, query?: Query): Promise<string>;
|
|
52
|
+
/**
|
|
53
|
+
* A write verb — POST, PUT, PATCH or DELETE.
|
|
54
|
+
*
|
|
55
|
+
* The SDK was read-only because the surface was: `verifyApiKey` guarded five
|
|
56
|
+
* route files carrying 15 GETs and no write verb at all. The agent surface
|
|
57
|
+
* (#383) and ADMIN_UI_OVERHAUL 0h.4b changed that, and
|
|
58
|
+
* `tenant_api_keys.permission` is now enforced (#4646), so a `read` key gets a
|
|
59
|
+
* 403 here and a `write` key does not. `CapaError` carries the API's own body,
|
|
60
|
+
* so a caller can tell the two apart without parsing a message this file made
|
|
61
|
+
* up.
|
|
62
|
+
*
|
|
63
|
+
* Content writes are still NOT offered. `/v2/agent/model-instances` exists, but
|
|
64
|
+
* an SDK that can silently publish a draft needs a deliberate surface of its
|
|
65
|
+
* own rather than a second use of this function.
|
|
66
|
+
*/
|
|
67
|
+
export declare function writeJson<T>(config: ResolvedConfig, method: "POST" | "PUT" | "PATCH" | "DELETE", path: string, body: unknown, auth?: Auth): Promise<T>;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CapaError = void 0;
|
|
4
|
+
exports.sessionHeaders = sessionHeaders;
|
|
5
|
+
exports.getJson = getJson;
|
|
6
|
+
exports.getJsonConditional = getJsonConditional;
|
|
7
|
+
exports.getText = getText;
|
|
8
|
+
exports.writeJson = writeJson;
|
|
9
|
+
class CapaError extends Error {
|
|
10
|
+
status;
|
|
11
|
+
path;
|
|
12
|
+
constructor(status, path, body) {
|
|
13
|
+
super(`Capa API ${status} on ${path}${body ? ` — ${body.slice(0, 200)}` : ""}`);
|
|
14
|
+
this.name = "CapaError";
|
|
15
|
+
this.status = status;
|
|
16
|
+
this.path = path;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.CapaError = CapaError;
|
|
20
|
+
function buildUrl(config, path, query) {
|
|
21
|
+
const url = new URL(config.baseUrl + path);
|
|
22
|
+
for (const [k, v] of Object.entries(query)) {
|
|
23
|
+
if (v === undefined || v === null || v === "")
|
|
24
|
+
continue;
|
|
25
|
+
url.searchParams.set(k, String(v));
|
|
26
|
+
}
|
|
27
|
+
return url.toString();
|
|
28
|
+
}
|
|
29
|
+
function headers(config, extra = {}, auth = "key") {
|
|
30
|
+
return auth === "session"
|
|
31
|
+
? sessionHeaders(config, extra)
|
|
32
|
+
: { "x-api-key": config.apiKey, "X-Tenant-Key": config.tenantId, ...extra };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The bearer variant.
|
|
36
|
+
*
|
|
37
|
+
* `X-Tenant-Key` still goes along for symmetry with the key variant and for
|
|
38
|
+
* proxies and logs that key on it, but NOTHING on the API reads it on a session
|
|
39
|
+
* call: `validateTenant` only checks that the request already has a tenant, and
|
|
40
|
+
* that tenant is the logged-in user's `currentTenant` off the JWT. So `tenantId`
|
|
41
|
+
* does not select the tenant for `webhooks.*`; the session does.
|
|
42
|
+
*/
|
|
43
|
+
function sessionHeaders(config, extra = {}) {
|
|
44
|
+
return {
|
|
45
|
+
Authorization: `Bearer ${config.accessToken ?? ""}`,
|
|
46
|
+
"X-Tenant-Key": config.tenantId,
|
|
47
|
+
...extra,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A plain `fetch` with no caching of our own, deliberately.
|
|
52
|
+
*
|
|
53
|
+
* Every framework caches differently and owning it here means reimplementing
|
|
54
|
+
* all of them badly. Returning plain data from a plain fetch lets Next's fetch
|
|
55
|
+
* cache, React Router loaders and everything else work natively. What the host
|
|
56
|
+
* CANNOT know is which surrogate keys a response belongs to for CDN purging —
|
|
57
|
+
* Capa emits those, so `listContent`/`getContent` surface them and the host
|
|
58
|
+
* decides what to do with them.
|
|
59
|
+
*/
|
|
60
|
+
async function getJson(config, path, query = {}, auth = "key") {
|
|
61
|
+
const res = await config.fetchImpl(buildUrl(config, path, query), {
|
|
62
|
+
method: "GET",
|
|
63
|
+
headers: headers(config, { Accept: "application/json" }, auth),
|
|
64
|
+
});
|
|
65
|
+
const text = await res.text();
|
|
66
|
+
if (!res.ok)
|
|
67
|
+
throw new CapaError(res.status, path, text);
|
|
68
|
+
let body;
|
|
69
|
+
try {
|
|
70
|
+
body = JSON.parse(text);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
throw new CapaError(res.status, path, `response was not JSON: ${text.slice(0, 200)}`);
|
|
74
|
+
}
|
|
75
|
+
const tags = res.headers?.get?.("surrogate-key") ?? "";
|
|
76
|
+
return { body, cacheTags: tags ? tags.split(/\s+/).filter(Boolean) : [] };
|
|
77
|
+
}
|
|
78
|
+
/** A conditional GET. Returns `null` when the server answers 304. */
|
|
79
|
+
async function getJsonConditional(config, path, etag, query = {}) {
|
|
80
|
+
const extra = { Accept: "application/json" };
|
|
81
|
+
if (etag)
|
|
82
|
+
extra["If-None-Match"] = etag;
|
|
83
|
+
const res = await config.fetchImpl(buildUrl(config, path, query), {
|
|
84
|
+
method: "GET",
|
|
85
|
+
headers: headers(config, extra),
|
|
86
|
+
});
|
|
87
|
+
if (res.status === 304)
|
|
88
|
+
return null;
|
|
89
|
+
const text = await res.text();
|
|
90
|
+
if (!res.ok)
|
|
91
|
+
throw new CapaError(res.status, path, text);
|
|
92
|
+
return { body: JSON.parse(text), etag: res.headers?.get?.("etag") ?? null };
|
|
93
|
+
}
|
|
94
|
+
/** GET an endpoint that answers with text rather than JSON (e.g. /v2/schema/types). */
|
|
95
|
+
async function getText(config, path, query = {}) {
|
|
96
|
+
const res = await config.fetchImpl(buildUrl(config, path, query), {
|
|
97
|
+
method: "GET",
|
|
98
|
+
headers: headers(config, { Accept: "text/plain, */*" }),
|
|
99
|
+
});
|
|
100
|
+
const text = await res.text();
|
|
101
|
+
if (!res.ok)
|
|
102
|
+
throw new CapaError(res.status, path, text);
|
|
103
|
+
return text;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* A write verb — POST, PUT, PATCH or DELETE.
|
|
107
|
+
*
|
|
108
|
+
* The SDK was read-only because the surface was: `verifyApiKey` guarded five
|
|
109
|
+
* route files carrying 15 GETs and no write verb at all. The agent surface
|
|
110
|
+
* (#383) and ADMIN_UI_OVERHAUL 0h.4b changed that, and
|
|
111
|
+
* `tenant_api_keys.permission` is now enforced (#4646), so a `read` key gets a
|
|
112
|
+
* 403 here and a `write` key does not. `CapaError` carries the API's own body,
|
|
113
|
+
* so a caller can tell the two apart without parsing a message this file made
|
|
114
|
+
* up.
|
|
115
|
+
*
|
|
116
|
+
* Content writes are still NOT offered. `/v2/agent/model-instances` exists, but
|
|
117
|
+
* an SDK that can silently publish a draft needs a deliberate surface of its
|
|
118
|
+
* own rather than a second use of this function.
|
|
119
|
+
*/
|
|
120
|
+
async function writeJson(config, method, path, body, auth = "key") {
|
|
121
|
+
// A DELETE with no body sends no body and no Content-Type. Fastify routes
|
|
122
|
+
// without a body schema answer 400 to an empty JSON object on DELETE, and
|
|
123
|
+
// `webhooks.endpoints.delete` has nothing to say anyway.
|
|
124
|
+
const sendsBody = !(method === "DELETE" && body === undefined);
|
|
125
|
+
const extra = { Accept: "application/json" };
|
|
126
|
+
if (sendsBody)
|
|
127
|
+
extra["Content-Type"] = "application/json";
|
|
128
|
+
const res = await config.fetchImpl(buildUrl(config, path, {}), {
|
|
129
|
+
method,
|
|
130
|
+
headers: headers(config, extra, auth),
|
|
131
|
+
body: sendsBody ? JSON.stringify(body ?? {}) : undefined,
|
|
132
|
+
});
|
|
133
|
+
const text = await res.text();
|
|
134
|
+
if (!res.ok)
|
|
135
|
+
throw new CapaError(res.status, path, text);
|
|
136
|
+
if (!text)
|
|
137
|
+
return null;
|
|
138
|
+
try {
|
|
139
|
+
return JSON.parse(text);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
throw new CapaError(res.status, path, `response was not JSON: ${text.slice(0, 200)}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { createClient, instanceIdOf } from "./client";
|
|
2
|
+
export type { CapaClient, CreateScheduledActionsInput, CreateScheduledActionsResult, LayoutCard, LayoutDisplay, LayoutField, LayoutInline, LayoutWidth, ListOptions, ListScheduledActionsOptions, ModelLayout, ModelLayoutInfo, ModelsResource, Page, ResolvedScheduleTime, ScheduledAction, ScheduledActionsPage, ScheduledActionsResource, ScheduledActionStatus, ScheduledActionTarget, SearchHit, WorkspaceApplyResult, WorkspaceChanges, WorkspaceDocNode, WorkspaceDocument, WorkspaceSummary, WorkspaceTemplate, WorkspaceTreeDocument, WorkspacesResource, } from "./client";
|
|
3
|
+
export type { CapaConfig } from "./config";
|
|
4
|
+
export { CapaError } from "./http";
|
|
5
|
+
export { WEBHOOKS_NEED_ACCESS_TOKEN } from "./webhooks";
|
|
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
|
+
export { DEFAULT_WEBHOOK_TOLERANCE_SECONDS, parseWebhookSignatureHeader, signWebhookPayload, verifyWebhookSignature, WEBHOOK_SIGNATURE_HEADER, } from "./webhook-signature";
|
|
8
|
+
export type { ParsedWebhookSignature, VerifyWebhookSignatureInput, } from "./webhook-signature";
|
|
9
|
+
export { generate, normalizeTypes, readStampedChecksum, typeNames } from "./codegen";
|
|
10
|
+
export type { CodegenResult } from "./codegen";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
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;
|
|
4
|
+
var client_1 = require("./client");
|
|
5
|
+
Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return client_1.createClient; } });
|
|
6
|
+
Object.defineProperty(exports, "instanceIdOf", { enumerable: true, get: function () { return client_1.instanceIdOf; } });
|
|
7
|
+
var http_1 = require("./http");
|
|
8
|
+
Object.defineProperty(exports, "CapaError", { enumerable: true, get: function () { return http_1.CapaError; } });
|
|
9
|
+
var webhooks_1 = require("./webhooks");
|
|
10
|
+
Object.defineProperty(exports, "WEBHOOKS_NEED_ACCESS_TOKEN", { enumerable: true, get: function () { return webhooks_1.WEBHOOKS_NEED_ACCESS_TOKEN; } });
|
|
11
|
+
var webhook_signature_1 = require("./webhook-signature");
|
|
12
|
+
Object.defineProperty(exports, "DEFAULT_WEBHOOK_TOLERANCE_SECONDS", { enumerable: true, get: function () { return webhook_signature_1.DEFAULT_WEBHOOK_TOLERANCE_SECONDS; } });
|
|
13
|
+
Object.defineProperty(exports, "parseWebhookSignatureHeader", { enumerable: true, get: function () { return webhook_signature_1.parseWebhookSignatureHeader; } });
|
|
14
|
+
Object.defineProperty(exports, "signWebhookPayload", { enumerable: true, get: function () { return webhook_signature_1.signWebhookPayload; } });
|
|
15
|
+
Object.defineProperty(exports, "verifyWebhookSignature", { enumerable: true, get: function () { return webhook_signature_1.verifyWebhookSignature; } });
|
|
16
|
+
Object.defineProperty(exports, "WEBHOOK_SIGNATURE_HEADER", { enumerable: true, get: function () { return webhook_signature_1.WEBHOOK_SIGNATURE_HEADER; } });
|
|
17
|
+
var codegen_1 = require("./codegen");
|
|
18
|
+
Object.defineProperty(exports, "generate", { enumerable: true, get: function () { return codegen_1.generate; } });
|
|
19
|
+
Object.defineProperty(exports, "normalizeTypes", { enumerable: true, get: function () { return codegen_1.normalizeTypes; } });
|
|
20
|
+
Object.defineProperty(exports, "readStampedChecksum", { enumerable: true, get: function () { return codegen_1.readStampedChecksum; } });
|
|
21
|
+
Object.defineProperty(exports, "typeNames", { enumerable: true, get: function () { return codegen_1.typeNames; } });
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import type { Select } from "./select-types";
|
|
2
|
+
export interface CapaNextConfig {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
version: string;
|
|
6
|
+
contract?: 1;
|
|
7
|
+
/**
|
|
8
|
+
* The page these reads are for, sent as `Capa-Page`: `/blog/[slug]` for a
|
|
9
|
+
* route, or `/blog/hello` for the concrete path being rendered.
|
|
10
|
+
*
|
|
11
|
+
* Optional, and telemetry only. It does not change a single byte of any
|
|
12
|
+
* response, it is not part of the cache key, and Capa ignores a value it
|
|
13
|
+
* cannot store. What it buys is the answer to "which pages read this entry",
|
|
14
|
+
* which nothing else in Capa can work out.
|
|
15
|
+
*
|
|
16
|
+
* Usually set per call, from the route being rendered, rather than here. Set
|
|
17
|
+
* it here when a client serves exactly one page.
|
|
18
|
+
*/
|
|
19
|
+
page?: string;
|
|
20
|
+
/**
|
|
21
|
+
* The checksum of the schema this code was generated from, sent as
|
|
22
|
+
* `Capa-Schema`. Pass `CAPA_SCHEMA_CHECKSUM` from your generated types:
|
|
23
|
+
*
|
|
24
|
+
* import { CAPA_SCHEMA_CHECKSUM } from "./capa-types";
|
|
25
|
+
* createClient({ ..., schemaChecksum: CAPA_SCHEMA_CHECKSUM });
|
|
26
|
+
*
|
|
27
|
+
* Optional, and telemetry only. It does not change a single byte of any
|
|
28
|
+
* response and is not part of the cache key. What it buys is the one thing
|
|
29
|
+
* Capa cannot work out on its own: whether your deployed site was built
|
|
30
|
+
* against the models the project has NOW, or against an older set. A site
|
|
31
|
+
* that has never been rebuilt still sends perfectly valid requests, so
|
|
32
|
+
* without this stamp a stale build is invisible.
|
|
33
|
+
*
|
|
34
|
+
* Config only, never per call: one build has one schema.
|
|
35
|
+
*/
|
|
36
|
+
schemaChecksum?: string;
|
|
37
|
+
/** Injected for tests, non-standard runtimes, and framework fetch wrappers. */
|
|
38
|
+
fetch?: typeof fetch;
|
|
39
|
+
}
|
|
40
|
+
interface ResolvedNextConfig extends CapaNextConfig {
|
|
41
|
+
baseUrl: string;
|
|
42
|
+
fetchImpl: typeof fetch;
|
|
43
|
+
}
|
|
44
|
+
export interface CallOptions {
|
|
45
|
+
signal?: AbortSignal;
|
|
46
|
+
/**
|
|
47
|
+
* The page THIS read is for. Overrides `page` on the config, because a client
|
|
48
|
+
* is usually built once and reused across routes.
|
|
49
|
+
*
|
|
50
|
+
* See `CapaNextConfig.page`. A malformed value throws a `TypeError` here
|
|
51
|
+
* rather than being dropped in silence: the API ignores a header it cannot
|
|
52
|
+
* store, which is right for a running site and wrong for the moment you are
|
|
53
|
+
* writing the code.
|
|
54
|
+
*/
|
|
55
|
+
page?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface Entry<T = Record<string, unknown>> {
|
|
58
|
+
id: string;
|
|
59
|
+
model: string;
|
|
60
|
+
status: "published" | "draft" | "changed" | string;
|
|
61
|
+
createdAt?: string;
|
|
62
|
+
updatedAt?: string;
|
|
63
|
+
publishedAt?: string | null;
|
|
64
|
+
version?: number;
|
|
65
|
+
folder?: unknown;
|
|
66
|
+
tags?: string[];
|
|
67
|
+
fields: T;
|
|
68
|
+
[key: string]: unknown;
|
|
69
|
+
}
|
|
70
|
+
export interface ResponseMeta {
|
|
71
|
+
version: string;
|
|
72
|
+
contract?: number;
|
|
73
|
+
environment?: string;
|
|
74
|
+
requestId: string;
|
|
75
|
+
[key: string]: unknown;
|
|
76
|
+
}
|
|
77
|
+
export interface PageInfo {
|
|
78
|
+
limit: number;
|
|
79
|
+
hasNext: boolean;
|
|
80
|
+
next: string | null;
|
|
81
|
+
hasPrev: boolean;
|
|
82
|
+
prev: string | null;
|
|
83
|
+
total?: number;
|
|
84
|
+
}
|
|
85
|
+
export interface Page<T> {
|
|
86
|
+
data: T[];
|
|
87
|
+
page: PageInfo;
|
|
88
|
+
meta: ResponseMeta;
|
|
89
|
+
cacheTags: string[];
|
|
90
|
+
}
|
|
91
|
+
export interface Single<T> {
|
|
92
|
+
data: T;
|
|
93
|
+
meta: ResponseMeta;
|
|
94
|
+
cacheTags: string[];
|
|
95
|
+
}
|
|
96
|
+
export type FilterOperator = "eq" | "ne" | "in" | "nin" | "lt" | "lte" | "gt" | "gte" | "contains" | "startsWith" | "endsWith" | "has" | "hasAny" | "hasAll" | "exists" | "null";
|
|
97
|
+
export type FilterScalar = string | number | boolean | null;
|
|
98
|
+
export type FilterValue = FilterScalar | readonly FilterScalar[];
|
|
99
|
+
export type Filter = Record<string, Partial<Record<FilterOperator, FilterValue>>>;
|
|
100
|
+
export interface ListOptions<T = Record<string, unknown>> extends CallOptions {
|
|
101
|
+
select?: Select<T> | string;
|
|
102
|
+
filter?: Filter;
|
|
103
|
+
where?: Record<string, unknown>;
|
|
104
|
+
sort?: readonly string[];
|
|
105
|
+
limit?: number;
|
|
106
|
+
count?: boolean;
|
|
107
|
+
after?: string;
|
|
108
|
+
before?: string;
|
|
109
|
+
}
|
|
110
|
+
export interface GetOptions<T = Record<string, unknown>> extends CallOptions {
|
|
111
|
+
select?: Select<T> | string;
|
|
112
|
+
}
|
|
113
|
+
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>;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* One suggestion drawn from a page's own reads.
|
|
120
|
+
*
|
|
121
|
+
* Computed on the API, never here. Three clients want this answer (this SDK,
|
|
122
|
+
* the Capa admin and `@capa/mcp`), and a second implementation of "this page
|
|
123
|
+
* over-fetches" would drift from the first the moment a threshold moved.
|
|
124
|
+
*/
|
|
125
|
+
export interface PageInsight {
|
|
126
|
+
kind: "overfetch" | "fanout" | "cache" | "drift" | "unused";
|
|
127
|
+
severity: "info" | "warn";
|
|
128
|
+
/** One sentence, present tense. The numbers are in `evidence`. */
|
|
129
|
+
title: string;
|
|
130
|
+
/** Why it matters, in one or two sentences. */
|
|
131
|
+
detail: string;
|
|
132
|
+
/** The copyable fix, when the rule can build one. Absent when it cannot. */
|
|
133
|
+
rewrite?: {
|
|
134
|
+
select?: string;
|
|
135
|
+
/** Query parameters by their full name, ready to paste. */
|
|
136
|
+
filter?: Record<string, string>;
|
|
137
|
+
url?: string;
|
|
138
|
+
};
|
|
139
|
+
/** Every number the rule used, so the claim can be checked rather than trusted. */
|
|
140
|
+
evidence: Record<string, number | string | string[]>;
|
|
141
|
+
modelId?: string;
|
|
142
|
+
/** The `queries[]` row this is about: `${modelId}:${selectText ?? ""}`. */
|
|
143
|
+
queryKey?: string;
|
|
144
|
+
[key: string]: unknown;
|
|
145
|
+
}
|
|
146
|
+
/** One row of `GET /api/pages`. */
|
|
147
|
+
export interface PageSummary {
|
|
148
|
+
/** The page string. It is the identity, so `id` and `pattern` are equal. */
|
|
149
|
+
id: string;
|
|
150
|
+
pattern: string;
|
|
151
|
+
kind: "declared" | "observed" | "both";
|
|
152
|
+
models: Array<{
|
|
153
|
+
id: string;
|
|
154
|
+
namespace: string;
|
|
155
|
+
modelName: string;
|
|
156
|
+
}>;
|
|
157
|
+
reads30d: number;
|
|
158
|
+
/**
|
|
159
|
+
* The newest read of this page in the window, or null when nothing has read
|
|
160
|
+
* it. The same instant on the list and on the detail: a folded row knows the
|
|
161
|
+
* day and not the instant, so it claims the day's end, which is the latest
|
|
162
|
+
* moment the read could have happened and never a future one.
|
|
163
|
+
*/
|
|
164
|
+
lastReadAt: string | null;
|
|
165
|
+
slugField: string | null;
|
|
166
|
+
/** Present only on a list narrowed with `entry`. */
|
|
167
|
+
entryReads?: number;
|
|
168
|
+
[key: string]: unknown;
|
|
169
|
+
}
|
|
170
|
+
/** `GET /api/pages/{page}`: one page, its entries and its queries. */
|
|
171
|
+
export interface PageDetail extends PageSummary {
|
|
172
|
+
page: string;
|
|
173
|
+
readsByDay: Array<{
|
|
174
|
+
day: string;
|
|
175
|
+
reads: number;
|
|
176
|
+
}>;
|
|
177
|
+
entries: Array<{
|
|
178
|
+
id: string;
|
|
179
|
+
modelId: string;
|
|
180
|
+
namespace: string;
|
|
181
|
+
title: string | null;
|
|
182
|
+
reads: number;
|
|
183
|
+
}>;
|
|
184
|
+
queries: Array<{
|
|
185
|
+
modelId: string;
|
|
186
|
+
namespace: string | null;
|
|
187
|
+
selectText: string | null;
|
|
188
|
+
reads: number;
|
|
189
|
+
lastAt: string | null;
|
|
190
|
+
keyIds: string[];
|
|
191
|
+
/**
|
|
192
|
+
* The request this group actually made: the most frequent recorded URL for
|
|
193
|
+
* it in the window, with its filters, sort and limit, not just its select.
|
|
194
|
+
* Null when every read in the group has been folded into the daily table,
|
|
195
|
+
* which keeps counts rather than requests.
|
|
196
|
+
*/
|
|
197
|
+
url: string | null;
|
|
198
|
+
/**
|
|
199
|
+
* The Selection IR this query's `select` parses to, against the model's
|
|
200
|
+
* CURRENT schema, or null when it no longer parses. Parsed on the API so
|
|
201
|
+
* every client reads one tree. Exactly one of these two is set.
|
|
202
|
+
*
|
|
203
|
+
* A failure is usually not a bug: a select recorded last week naming a
|
|
204
|
+
* field that has since been removed is the drift this screen exists to
|
|
205
|
+
* show.
|
|
206
|
+
*/
|
|
207
|
+
selection: unknown | null;
|
|
208
|
+
selectionError: {
|
|
209
|
+
code: string;
|
|
210
|
+
message: string;
|
|
211
|
+
} | null;
|
|
212
|
+
}>;
|
|
213
|
+
since: string;
|
|
214
|
+
/** Sorted warn first, then by kind. */
|
|
215
|
+
insights: PageInsight[];
|
|
216
|
+
}
|
|
217
|
+
export interface PagesListOptions extends CallOptions {
|
|
218
|
+
/**
|
|
219
|
+
* Narrow the list to the pages that read one entry, and add `entryReads` to
|
|
220
|
+
* every row. This is "appears on 4 pages" beside an entry.
|
|
221
|
+
*/
|
|
222
|
+
entry?: string;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* `meta` on `GET /api/pages`, which carries the tenant-wide `unused` insights.
|
|
226
|
+
*
|
|
227
|
+
* IN `meta` AND NOT BESIDE `data` because on a list `data` IS the array, so
|
|
228
|
+
* `meta` is the one place a fact about the whole answer can go. Always present
|
|
229
|
+
* and empty when there is nothing to say, so "no insights" and "an older API"
|
|
230
|
+
* never look alike.
|
|
231
|
+
*/
|
|
232
|
+
export interface PagesListMeta extends ResponseMeta {
|
|
233
|
+
insights: PageInsight[];
|
|
234
|
+
}
|
|
235
|
+
export interface PagesResource {
|
|
236
|
+
list(options?: PagesListOptions): Promise<{
|
|
237
|
+
data: PageSummary[];
|
|
238
|
+
meta: PagesListMeta;
|
|
239
|
+
}>;
|
|
240
|
+
get(page: string, options?: CallOptions): Promise<{
|
|
241
|
+
data: PageDetail;
|
|
242
|
+
meta: ResponseMeta;
|
|
243
|
+
} | null>;
|
|
244
|
+
}
|
|
245
|
+
/** What `GET /api/preview` says a preview token names. */
|
|
246
|
+
export interface PreviewClaim {
|
|
247
|
+
entryId: string;
|
|
248
|
+
modelId: string;
|
|
249
|
+
/** Null when the model has since lost its route. */
|
|
250
|
+
namespace: string | null;
|
|
251
|
+
/** Null when the path can no longer be resolved. Falls back to your routing. */
|
|
252
|
+
path: string | null;
|
|
253
|
+
expiresAt: string;
|
|
254
|
+
}
|
|
255
|
+
export interface CapaNextClient {
|
|
256
|
+
entries: EntriesResource;
|
|
257
|
+
pages: PagesResource;
|
|
258
|
+
/**
|
|
259
|
+
* Verify a preview token minted by the Capa admin.
|
|
260
|
+
*
|
|
261
|
+
* Returns the claim, or NULL when the token is invalid or expired, because
|
|
262
|
+
* both mean the same thing to a preview route: do not enable draft mode.
|
|
263
|
+
* Every other failure throws, so a Capa outage does not look like a bad link.
|
|
264
|
+
*/
|
|
265
|
+
preview(token: string, options?: CallOptions): Promise<PreviewClaim | null>;
|
|
266
|
+
me<T = Record<string, unknown>>(options?: CallOptions): Promise<T>;
|
|
267
|
+
versions<T = Record<string, unknown>>(options?: CallOptions): Promise<T>;
|
|
268
|
+
}
|
|
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
|
+
export declare function resolveNextConfig(config: CapaNextConfig): ResolvedNextConfig;
|
|
290
|
+
type SelectInput = string | ReadonlyArray<unknown>;
|
|
291
|
+
/** Serialize the SDK object form into the canonical `/api/entries` grammar. */
|
|
292
|
+
export declare function serializeSelect(select: SelectInput): string;
|
|
293
|
+
export declare function createClient(config: CapaNextConfig): CapaNextClient;
|
|
294
|
+
export {};
|