@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.
- package/CHANGELOG.md +345 -0
- package/README.md +1069 -186
- package/bin/capa-codegen.js +192 -5
- package/bin/capa.js +208 -0
- package/bin/graphql-project.js +142 -0
- package/bin/project-env.js +58 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.js +17 -0
- package/dist/codegen.d.ts +55 -0
- package/dist/codegen.js +320 -39
- package/dist/config.d.ts +5 -36
- package/dist/config.js +47 -1
- package/dist/graphql-codegen.d.ts +117 -0
- package/dist/graphql-codegen.js +705 -0
- package/dist/http.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -1
- package/dist/next/attrs.d.ts +51 -12
- package/dist/next/attrs.js +74 -20
- package/dist/next/client.d.ts +112 -38
- package/dist/next/client.js +131 -83
- package/dist/next/entry-fields.d.ts +162 -0
- package/dist/next/entry-fields.js +2 -0
- package/dist/next/errors.d.ts +136 -0
- package/dist/next/errors.js +214 -0
- package/dist/next/field-names.d.ts +37 -0
- package/dist/next/field-names.js +145 -0
- package/dist/next/graphql/build.d.ts +27 -0
- package/dist/next/graphql/build.js +98 -0
- package/dist/next/graphql/documents.d.ts +67 -0
- package/dist/next/graphql/documents.js +35 -0
- package/dist/next/graphql/edit-mode.d.ts +16 -0
- package/dist/next/graphql/edit-mode.js +93 -0
- package/dist/next/graphql/filter-values.d.ts +34 -0
- package/dist/next/graphql/filter-values.js +96 -0
- package/dist/next/graphql/introspection.d.ts +89 -0
- package/dist/next/graphql/introspection.js +102 -0
- package/dist/next/graphql/plan.d.ts +115 -0
- package/dist/next/graphql/plan.js +531 -0
- package/dist/next/graphql/request.d.ts +228 -0
- package/dist/next/graphql/request.js +283 -0
- package/dist/next/graphql/rest.d.ts +66 -0
- package/dist/next/graphql/rest.js +502 -0
- package/dist/next/graphql/selection.d.ts +55 -0
- package/dist/next/graphql/selection.js +212 -0
- package/dist/next/graphql/sha256.d.ts +13 -0
- package/dist/next/graphql/sha256.js +86 -0
- package/dist/next/graphql/summary.d.ts +83 -0
- package/dist/next/graphql/summary.js +151 -0
- package/dist/next/graphql/tree-layout.d.ts +36 -0
- package/dist/next/graphql/tree-layout.js +20 -0
- package/dist/next/graphql/tree.d.ts +171 -0
- package/dist/next/graphql/tree.js +249 -0
- package/dist/next/graphql/typed.d.ts +261 -0
- package/dist/next/graphql/typed.js +146 -0
- package/dist/next/index.d.ts +28 -5
- package/dist/next/index.js +25 -1
- package/dist/next/inflate.d.ts +25 -7
- package/dist/next/inflate.js +46 -32
- package/dist/next/key-family.d.ts +34 -0
- package/dist/next/key-family.js +74 -0
- package/dist/next/select-types.d.ts +44 -8
- package/dist/next/system-keys.d.ts +27 -0
- package/dist/next/system-keys.js +42 -0
- package/dist/nextjs/index.d.ts +174 -12
- package/dist/nextjs/index.js +270 -23
- package/dist/nextjs/overlay.d.ts +5 -0
- package/dist/nextjs/overlay.js +35 -0
- package/package.json +31 -13
package/dist/next/inflate.js
CHANGED
|
@@ -34,39 +34,49 @@ exports.inflate = inflate;
|
|
|
34
34
|
* cannot loop. A reference the select did not expand stays a reference.
|
|
35
35
|
*/
|
|
36
36
|
const attrs_1 = require("./attrs");
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
"id",
|
|
40
|
-
"model",
|
|
41
|
-
"status",
|
|
42
|
-
"createdAt",
|
|
43
|
-
"updatedAt",
|
|
44
|
-
"publishedAt",
|
|
45
|
-
"version",
|
|
46
|
-
"folder",
|
|
47
|
-
"tags",
|
|
48
|
-
];
|
|
37
|
+
const system_keys_1 = require("./system-keys");
|
|
38
|
+
const field_names_1 = require("./field-names");
|
|
49
39
|
const ALWAYS_KEYS = new Set(["id", "model", "status"]);
|
|
50
|
-
const SYSTEM_KEY_SET = new Set(SYSTEM_KEYS);
|
|
51
40
|
/**
|
|
52
|
-
* The select grammar, read only as far as `inflate` needs it: names,
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
41
|
+
* The select grammar, read only as far as `inflate` needs it: names, bare or
|
|
42
|
+
* quoted (`"price.usd"`, spec 17 amendment 121), nesting and `*`. Modifiers
|
|
43
|
+
* (`limit:`, `sort:`, `after:`) decide which rows the API returned, which the
|
|
44
|
+
* response already reflects, so they are skipped. The API validated the
|
|
45
|
+
* select before answering, so this parser trusts its shape and throws a
|
|
46
|
+
* `TypeError` only on text it cannot split at all.
|
|
57
47
|
*/
|
|
58
48
|
function parseSelectLevels(select) {
|
|
59
49
|
let pos = 0;
|
|
60
50
|
const fail = () => {
|
|
61
51
|
throw new TypeError(`@capacms/sdk/next: inflate could not read the select ${JSON.stringify(select)}.`);
|
|
62
52
|
};
|
|
53
|
+
/** The quoted name at `pos`, read back, with `pos` moved past it. */
|
|
54
|
+
const quotedAt = () => {
|
|
55
|
+
const read = (0, field_names_1.readQuoted)(select, pos);
|
|
56
|
+
if (!read)
|
|
57
|
+
return fail();
|
|
58
|
+
pos = read.end;
|
|
59
|
+
return read.name;
|
|
60
|
+
};
|
|
63
61
|
const level = () => {
|
|
64
62
|
const out = { star: false, items: [] };
|
|
65
63
|
for (;;) {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
const quoted = select[pos] === field_names_1.NAME_QUOTE;
|
|
65
|
+
let token = "";
|
|
66
|
+
if (quoted) {
|
|
67
|
+
token = quotedAt();
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const start = pos;
|
|
71
|
+
// A modifier's value may quote a name too: `sort:-"zip.code"`.
|
|
72
|
+
while (pos < select.length && !",()".includes(select[pos])) {
|
|
73
|
+
if (select[pos] === field_names_1.NAME_QUOTE)
|
|
74
|
+
quotedAt();
|
|
75
|
+
else
|
|
76
|
+
pos += 1;
|
|
77
|
+
}
|
|
78
|
+
token = select.slice(start, pos);
|
|
79
|
+
}
|
|
70
80
|
if (select[pos] === "(") {
|
|
71
81
|
if (token === "")
|
|
72
82
|
fail();
|
|
@@ -75,16 +85,16 @@ function parseSelectLevels(select) {
|
|
|
75
85
|
if (select[pos] !== ")")
|
|
76
86
|
fail();
|
|
77
87
|
pos += 1;
|
|
78
|
-
out.items.push({ name: token, expand: inner });
|
|
88
|
+
out.items.push({ name: token, expand: inner, ...(quoted ? { quoted } : {}) });
|
|
79
89
|
}
|
|
80
|
-
else if (token === "*") {
|
|
90
|
+
else if (!quoted && token === "*") {
|
|
81
91
|
out.star = true;
|
|
82
92
|
}
|
|
83
|
-
else if (token.includes(":")) {
|
|
93
|
+
else if (!quoted && token.includes(":")) {
|
|
84
94
|
// A modifier: `limit:2`, `sort:-name`, `after:<cursor>`.
|
|
85
95
|
}
|
|
86
|
-
else if (token !== "") {
|
|
87
|
-
out.items.push({ name: token, expand: null });
|
|
96
|
+
else if (quoted || token !== "") {
|
|
97
|
+
out.items.push({ name: token, expand: null, ...(quoted ? { quoted } : {}) });
|
|
88
98
|
}
|
|
89
99
|
else {
|
|
90
100
|
fail();
|
|
@@ -133,18 +143,22 @@ function resolve(index, ref) {
|
|
|
133
143
|
function project(entry, level, index) {
|
|
134
144
|
const fields = (entry.fields ?? {});
|
|
135
145
|
const all = level === null || level.star;
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
146
|
+
// `$tags` is always the system key (spec 17, amendment 29). A plain name is
|
|
147
|
+
// a FIELD when the entry has a field of that name, otherwise a system key:
|
|
148
|
+
// a model field shadows a system key of the same name (spec 3.3), and a
|
|
149
|
+
// path that selected the field is exactly what put it here.
|
|
139
150
|
const wanted = new Set(ALWAYS_KEYS);
|
|
140
151
|
if (level) {
|
|
141
152
|
for (const item of level.items) {
|
|
142
|
-
|
|
153
|
+
const system = (0, system_keys_1.sigilSystemKey)(item.name);
|
|
154
|
+
if (system !== null)
|
|
155
|
+
wanted.add(system);
|
|
156
|
+
else if (!item.quoted && !(item.name in fields) && (0, system_keys_1.isSystemKey)(item.name))
|
|
143
157
|
wanted.add(item.name);
|
|
144
158
|
}
|
|
145
159
|
}
|
|
146
160
|
const out = {};
|
|
147
|
-
for (const key of SYSTEM_KEYS) {
|
|
161
|
+
for (const key of system_keys_1.SYSTEM_KEYS) {
|
|
148
162
|
if (!(key in entry))
|
|
149
163
|
continue;
|
|
150
164
|
if (all || wanted.has(key))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* key-family.ts — which keys `@capacms/sdk/next` takes, and for what.
|
|
3
|
+
*
|
|
4
|
+
* A `cap_` key is the `/api/` key: scoped, hashed at rest, and the only kind
|
|
5
|
+
* a draft preview takes, both to verify a preview token and to read drafts.
|
|
6
|
+
* Every other key is legacy, which is the API's rule
|
|
7
|
+
* (`apps/api/src/api-next/auth/key-format.ts`): the `pk_` and `sk_` keys, and
|
|
8
|
+
* the unprefixed keys older tenants were minted, all reach `/api/` with the
|
|
9
|
+
* grants their permission gives. So every read call takes them, and the first
|
|
10
|
+
* client built with one warns once per process, naming the key to mint.
|
|
11
|
+
*
|
|
12
|
+
* The `cap_` prefix is the whole rule, case-sensitive, as on the API:
|
|
13
|
+
* `CAP_live_…` is not a key Capa mints, so it is looked up as a legacy key.
|
|
14
|
+
* `@capa/mcp` applies the same rule, and both packages are tested against one
|
|
15
|
+
* vector file, `test/fixtures/key-family.json`.
|
|
16
|
+
*/
|
|
17
|
+
export type KeyFamily = "cap" | "legacy";
|
|
18
|
+
/** The family of `apiKey`, or null for a value that is no key at all: not a string, or empty. */
|
|
19
|
+
export declare function keyFamily(apiKey: unknown): KeyFamily | null;
|
|
20
|
+
/** Warn about a legacy key once per process: a site builds a client per request. */
|
|
21
|
+
export declare function warnLegacyKeyOnce(apiKey: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* Throws for a legacy key: verifying a preview token takes a `cap_` key. A
|
|
24
|
+
* value that is no key at all is `resolveNextConfig`'s to refuse, by name.
|
|
25
|
+
*/
|
|
26
|
+
export declare function requirePreviewKey(apiKey: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Throws for a legacy key: a draft read takes a `cap_` key. `holder` names
|
|
29
|
+
* where the key came from (`CAPA_DRAFT_KEY`, `the draft config`), so the
|
|
30
|
+
* message says what to change.
|
|
31
|
+
*/
|
|
32
|
+
export declare function requireDraftKey(apiKey: string, holder: string): void;
|
|
33
|
+
/** Forget that the warning was printed. For tests. */
|
|
34
|
+
export declare function __resetKeyWarningForTests(): void;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* key-family.ts — which keys `@capacms/sdk/next` takes, and for what.
|
|
4
|
+
*
|
|
5
|
+
* A `cap_` key is the `/api/` key: scoped, hashed at rest, and the only kind
|
|
6
|
+
* a draft preview takes, both to verify a preview token and to read drafts.
|
|
7
|
+
* Every other key is legacy, which is the API's rule
|
|
8
|
+
* (`apps/api/src/api-next/auth/key-format.ts`): the `pk_` and `sk_` keys, and
|
|
9
|
+
* the unprefixed keys older tenants were minted, all reach `/api/` with the
|
|
10
|
+
* grants their permission gives. So every read call takes them, and the first
|
|
11
|
+
* client built with one warns once per process, naming the key to mint.
|
|
12
|
+
*
|
|
13
|
+
* The `cap_` prefix is the whole rule, case-sensitive, as on the API:
|
|
14
|
+
* `CAP_live_…` is not a key Capa mints, so it is looked up as a legacy key.
|
|
15
|
+
* `@capa/mcp` applies the same rule, and both packages are tested against one
|
|
16
|
+
* vector file, `test/fixtures/key-family.json`.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.keyFamily = keyFamily;
|
|
20
|
+
exports.warnLegacyKeyOnce = warnLegacyKeyOnce;
|
|
21
|
+
exports.requirePreviewKey = requirePreviewKey;
|
|
22
|
+
exports.requireDraftKey = requireDraftKey;
|
|
23
|
+
exports.__resetKeyWarningForTests = __resetKeyWarningForTests;
|
|
24
|
+
const LEGACY_PREFIX = /^(pk|sk)_/;
|
|
25
|
+
/** The family of `apiKey`, or null for a value that is no key at all: not a string, or empty. */
|
|
26
|
+
function keyFamily(apiKey) {
|
|
27
|
+
if (typeof apiKey !== "string" || apiKey === "")
|
|
28
|
+
return null;
|
|
29
|
+
return apiKey.startsWith("cap_") ? "cap" : "legacy";
|
|
30
|
+
}
|
|
31
|
+
function isLegacy(apiKey) {
|
|
32
|
+
return keyFamily(apiKey) === "legacy";
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* How a message names a legacy key: by its `pk_` or `sk_` prefix, and an
|
|
36
|
+
* unprefixed key by that fact alone, since every other character is the secret.
|
|
37
|
+
*/
|
|
38
|
+
function legacyKind(apiKey) {
|
|
39
|
+
const prefix = LEGACY_PREFIX.exec(apiKey)?.[0];
|
|
40
|
+
return prefix ? `legacy ${prefix} key` : "legacy key with no pk_ or sk_ prefix";
|
|
41
|
+
}
|
|
42
|
+
const MINT = "in the Capa admin under Developers > Keys.";
|
|
43
|
+
let warned = false;
|
|
44
|
+
/** Warn about a legacy key once per process: a site builds a client per request. */
|
|
45
|
+
function warnLegacyKeyOnce(apiKey) {
|
|
46
|
+
if (warned)
|
|
47
|
+
return;
|
|
48
|
+
warned = true;
|
|
49
|
+
console.warn(`@capacms/sdk/next: this client reads with a ${legacyKind(apiKey)}. Reads work as they do with a cap_ key. ` +
|
|
50
|
+
`Draft previews need a cap_ key: mint one ${MINT}`);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Throws for a legacy key: verifying a preview token takes a `cap_` key. A
|
|
54
|
+
* value that is no key at all is `resolveNextConfig`'s to refuse, by name.
|
|
55
|
+
*/
|
|
56
|
+
function requirePreviewKey(apiKey) {
|
|
57
|
+
if (!isLegacy(apiKey))
|
|
58
|
+
return;
|
|
59
|
+
throw new TypeError(`@capacms/sdk/next: preview needs a cap_ key, and this client holds a ${legacyKind(apiKey)}. Mint a cap_ key ${MINT}`);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Throws for a legacy key: a draft read takes a `cap_` key. `holder` names
|
|
63
|
+
* where the key came from (`CAPA_DRAFT_KEY`, `the draft config`), so the
|
|
64
|
+
* message says what to change.
|
|
65
|
+
*/
|
|
66
|
+
function requireDraftKey(apiKey, holder) {
|
|
67
|
+
if (!isLegacy(apiKey))
|
|
68
|
+
return;
|
|
69
|
+
throw new TypeError(`@capacms/sdk/next: draft reads need a cap_ key, and ${holder} holds a ${legacyKind(apiKey)}. Mint a development cap_ key ${MINT}`);
|
|
70
|
+
}
|
|
71
|
+
/** Forget that the warning was printed. For tests. */
|
|
72
|
+
function __resetKeyWarningForTests() {
|
|
73
|
+
warned = false;
|
|
74
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SortableSystemKeyName, SystemKeyName } from "./system-keys";
|
|
1
2
|
/** A single relation field emitted by `capa-codegen`. */
|
|
2
3
|
export type CapaRelation<T> = T & {
|
|
3
4
|
readonly __capaRelation: "one";
|
|
@@ -13,14 +14,38 @@ type RelationValue = {
|
|
|
13
14
|
readonly __capaRelation: "one" | "many";
|
|
14
15
|
readonly __capaRelationTarget: unknown;
|
|
15
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* A field type that says nothing about the field: `unknown`, as every field
|
|
19
|
+
* of an untyped client's `Record<string, unknown>` is, or `any`, as a type
|
|
20
|
+
* inferred from a select's names is. Such a field may be a relation or not.
|
|
21
|
+
*/
|
|
22
|
+
type Untyped<V> = 0 extends 1 & V ? true : unknown extends V ? true : false;
|
|
23
|
+
type UntypedKeys<T> = {
|
|
24
|
+
[K in StringKey<T>]-?: Untyped<T[K]> extends true ? K : never;
|
|
25
|
+
}[StringKey<T>];
|
|
16
26
|
type RelationKeys<T> = {
|
|
17
|
-
[K in StringKey<T>]-?: NonNullable<T[K]> extends RelationValue ? K : never;
|
|
27
|
+
[K in StringKey<T>]-?: Untyped<T[K]> extends true ? never : NonNullable<T[K]> extends RelationValue ? K : never;
|
|
18
28
|
}[StringKey<T>];
|
|
19
29
|
type ScalarKeys<T> = Exclude<StringKey<T>, RelationKeys<T>>;
|
|
20
30
|
type RelationTarget<T> = NonNullable<T> extends {
|
|
21
31
|
readonly __capaRelationTarget: infer R;
|
|
22
32
|
} ? R : never;
|
|
23
|
-
|
|
33
|
+
/**
|
|
34
|
+
* A system key by its `$` name. The API reads a plain name as the model's
|
|
35
|
+
* field first, so `$tags` is how a select or sort asks for the entry's own
|
|
36
|
+
* tags beside a field called `tags` (spec 17, amendment 29).
|
|
37
|
+
*/
|
|
38
|
+
export type SystemKey = `$${SystemKeyName}`;
|
|
39
|
+
/** A system key the API sorts by: `$id`, `$createdAt`, `$updatedAt`, `$publishedAt`. */
|
|
40
|
+
export type SortableSystemKey = `$${SortableSystemKeyName}`;
|
|
41
|
+
/**
|
|
42
|
+
* A field the grammar can name: every one but a namespace starting with `$`,
|
|
43
|
+
* which is a system key's spelling (spec 17, amendment 121). `select: "*"`
|
|
44
|
+
* still returns such a field. A namespace holding `.` or `,` is named as it
|
|
45
|
+
* is (`"price.usd"`), and the client writes it quoted.
|
|
46
|
+
*/
|
|
47
|
+
type Nameable<K extends string> = Exclude<K, `$${string}`>;
|
|
48
|
+
export type SelectSort<T> = Nameable<ScalarKeys<T>> | `-${Nameable<ScalarKeys<T>>}` | SortableSystemKey | `-${SortableSystemKey}`;
|
|
24
49
|
export interface RelationSelectOptions<T> {
|
|
25
50
|
select: Select<T> | "*";
|
|
26
51
|
limit?: number;
|
|
@@ -31,16 +56,26 @@ export interface RelationSelectOptions<T> {
|
|
|
31
56
|
type RelationSelect<T, K extends RelationKeys<T>> = {
|
|
32
57
|
[P in K]: Select<RelationTarget<T[P]>> | "*" | RelationSelectOptions<RelationTarget<T[P]>>;
|
|
33
58
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
59
|
+
/** An expansion of a field whose type says nothing (`Untyped`): its target is read untyped too. */
|
|
60
|
+
type UntypedRelationSelect<K extends string> = {
|
|
61
|
+
[P in K]: Select<Record<string, unknown>> | "*" | RelationSelectOptions<Record<string, unknown>>;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* One item of a select: `*`, a field by name (a relation named alone is read
|
|
65
|
+
* as a reference, `{ id, model }`), a system key by its `$` name, or a
|
|
66
|
+
* relation expanded into the fields its own select names. A field whose
|
|
67
|
+
* namespace starts with `$` has no name (`Nameable`).
|
|
68
|
+
*/
|
|
69
|
+
export type SelectItem<T> = "*" | Nameable<ScalarKeys<T>> | Nameable<RelationKeys<T>> | SystemKey | {
|
|
70
|
+
[K in Nameable<RelationKeys<T>>]: RelationSelect<T, K>;
|
|
71
|
+
}[Nameable<RelationKeys<T>>] | ([UntypedKeys<T>] extends [never] ? never : UntypedRelationSelect<UntypedKeys<T>>);
|
|
37
72
|
/** A typed form of the `/api/entries` `select` grammar. */
|
|
38
73
|
export type Select<T> = ReadonlyArray<SelectItem<T>>;
|
|
39
74
|
type Depth = [never, 0, 1, 2, 3, 4];
|
|
40
75
|
/** The target types one select item expands, and everything below them. */
|
|
41
76
|
type ItemTargets<T, I, D extends number> = [D] extends [never] ? never : I extends string ? never : {
|
|
42
77
|
[K in Extract<keyof I, RelationKeys<T>>]: RelationTarget<T[K]> | ValueTargets<RelationTarget<T[K]>, I[K], Depth[D]>;
|
|
43
|
-
}[Extract<keyof I, RelationKeys<T>>];
|
|
78
|
+
}[Extract<keyof I, RelationKeys<T>>] | ([Extract<keyof I, UntypedKeys<T>>] extends [never] ? never : Record<string, unknown>);
|
|
44
79
|
type ValueTargets<R, V, D extends number> = V extends "*" ? never : V extends {
|
|
45
80
|
select: infer S;
|
|
46
81
|
} ? ExpandedTargetsAt<R, S, D> : ExpandedTargetsAt<R, V, D>;
|
|
@@ -49,8 +84,9 @@ type ExpandedTargetsAt<T, S, D extends number> = S extends ReadonlyArray<infer I
|
|
|
49
84
|
* Every entry type a select EXPANDS, at any depth: what `included` can hold
|
|
50
85
|
* for a `shape: "flat"` read. `[{ author: ["name"] }]` on an article is
|
|
51
86
|
* `Author`; a select given as a string cannot be read by the type system and is
|
|
52
|
-
* `Record<string, unknown>`. Bounded
|
|
53
|
-
* relates to itself does not
|
|
87
|
+
* `Record<string, unknown>`. Bounded one level past the 4 relations the API
|
|
88
|
+
* reads below the root entry, so a model that relates to itself does not
|
|
89
|
+
* recurse forever.
|
|
54
90
|
*/
|
|
55
91
|
export type ExpandedTargets<T, S> = S extends string ? Record<string, unknown> : [ExpandedTargetsAt<T, S, 4>] extends [never] ? never : ExpandedTargetsAt<T, S, 4>;
|
|
56
92
|
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* system-keys.ts — the entry's own keys, as REST names them.
|
|
3
|
+
*
|
|
4
|
+
* Every entry REST returns carries these beside `fields`, in this order. A
|
|
5
|
+
* request names one plainly (`tags`) only where the model has no field of that
|
|
6
|
+
* name, because REST reads a plain name as the field first; `$tags` always
|
|
7
|
+
* means the system key, in `select`, `where` and `sort`, and after a hop as
|
|
8
|
+
* `author.$id` (spec 17, amendment 29). REST names no field whose namespace
|
|
9
|
+
* starts with `$` (field-names.ts), so no field is ever spelled like one.
|
|
10
|
+
*/
|
|
11
|
+
/** The system keys, in the order the API prints them on an entry. */
|
|
12
|
+
export declare const SYSTEM_KEYS: readonly ["id", "model", "status", "createdAt", "updatedAt", "publishedAt", "version", "folder", "tags"];
|
|
13
|
+
export type SystemKeyName = (typeof SYSTEM_KEYS)[number];
|
|
14
|
+
/**
|
|
15
|
+
* The system keys the API sorts by, as `packages/shared`'s `SORTABLE_SYSTEM_KEYS`
|
|
16
|
+
* lists them: `$tags` is a list and `$version`, `$model`, `$status` and
|
|
17
|
+
* `$folder` are refused as sort keys.
|
|
18
|
+
*/
|
|
19
|
+
export declare const SORTABLE_SYSTEM_KEYS: readonly ["id", "createdAt", "updatedAt", "publishedAt"];
|
|
20
|
+
export type SortableSystemKeyName = (typeof SORTABLE_SYSTEM_KEYS)[number];
|
|
21
|
+
export declare function isSortableSystemKey(name: string): name is SortableSystemKeyName;
|
|
22
|
+
export declare const SYSTEM_KEY_SIGIL = "$";
|
|
23
|
+
export declare function isSystemKey(name: string): name is SystemKeyName;
|
|
24
|
+
/** Every system key by its `$` name, for a message that lists them. */
|
|
25
|
+
export declare const SYSTEM_KEY_LIST: string;
|
|
26
|
+
/** The system key a `$` name spells (`$tags` is `tags`), or null for any other name. */
|
|
27
|
+
export declare function sigilSystemKey(name: string): SystemKeyName | null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* system-keys.ts — the entry's own keys, as REST names them.
|
|
4
|
+
*
|
|
5
|
+
* Every entry REST returns carries these beside `fields`, in this order. A
|
|
6
|
+
* request names one plainly (`tags`) only where the model has no field of that
|
|
7
|
+
* name, because REST reads a plain name as the field first; `$tags` always
|
|
8
|
+
* means the system key, in `select`, `where` and `sort`, and after a hop as
|
|
9
|
+
* `author.$id` (spec 17, amendment 29). REST names no field whose namespace
|
|
10
|
+
* starts with `$` (field-names.ts), so no field is ever spelled like one.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.SYSTEM_KEY_LIST = exports.SYSTEM_KEY_SIGIL = exports.SORTABLE_SYSTEM_KEYS = exports.SYSTEM_KEYS = void 0;
|
|
14
|
+
exports.isSortableSystemKey = isSortableSystemKey;
|
|
15
|
+
exports.isSystemKey = isSystemKey;
|
|
16
|
+
exports.sigilSystemKey = sigilSystemKey;
|
|
17
|
+
/** The system keys, in the order the API prints them on an entry. */
|
|
18
|
+
exports.SYSTEM_KEYS = ["id", "model", "status", "createdAt", "updatedAt", "publishedAt", "version", "folder", "tags"];
|
|
19
|
+
/**
|
|
20
|
+
* The system keys the API sorts by, as `packages/shared`'s `SORTABLE_SYSTEM_KEYS`
|
|
21
|
+
* lists them: `$tags` is a list and `$version`, `$model`, `$status` and
|
|
22
|
+
* `$folder` are refused as sort keys.
|
|
23
|
+
*/
|
|
24
|
+
exports.SORTABLE_SYSTEM_KEYS = ["id", "createdAt", "updatedAt", "publishedAt"];
|
|
25
|
+
const SORTABLE_SYSTEM_KEY_SET = new Set(exports.SORTABLE_SYSTEM_KEYS);
|
|
26
|
+
function isSortableSystemKey(name) {
|
|
27
|
+
return SORTABLE_SYSTEM_KEY_SET.has(name);
|
|
28
|
+
}
|
|
29
|
+
exports.SYSTEM_KEY_SIGIL = "$";
|
|
30
|
+
const SYSTEM_KEY_SET = new Set(exports.SYSTEM_KEYS);
|
|
31
|
+
function isSystemKey(name) {
|
|
32
|
+
return SYSTEM_KEY_SET.has(name);
|
|
33
|
+
}
|
|
34
|
+
/** Every system key by its `$` name, for a message that lists them. */
|
|
35
|
+
exports.SYSTEM_KEY_LIST = exports.SYSTEM_KEYS.map((key) => `${exports.SYSTEM_KEY_SIGIL}${key}`).join(", ");
|
|
36
|
+
/** The system key a `$` name spells (`$tags` is `tags`), or null for any other name. */
|
|
37
|
+
function sigilSystemKey(name) {
|
|
38
|
+
if (!name.startsWith(exports.SYSTEM_KEY_SIGIL))
|
|
39
|
+
return null;
|
|
40
|
+
const key = name.slice(exports.SYSTEM_KEY_SIGIL.length);
|
|
41
|
+
return isSystemKey(key) ? key : null;
|
|
42
|
+
}
|
package/dist/nextjs/index.d.ts
CHANGED
|
@@ -1,20 +1,55 @@
|
|
|
1
|
-
import { LAYOUT_PAGE, type CapaNextClient, type CapaNextConfig, type PagesResource, type PreviewClaim } from "../next";
|
|
1
|
+
import { gql, LAYOUT_PAGE, type CapaDocumentResult, type CapaDocuments, type CapaDocumentVariables, type CapaNextClient, type CapaNextConfig, type GraphQLCallOptions, type GraphQLResult, type NotARecordedDocument, type PagesResource, type PreviewClaim, type TypedDocument, type UntypedQuery, type VariablesThenOptions } from "../next";
|
|
2
2
|
export interface CacheOptions {
|
|
3
3
|
tags?: string[];
|
|
4
4
|
revalidate?: number | false;
|
|
5
5
|
}
|
|
6
6
|
/** Add Next.js fetch-cache options without importing `next/*`. */
|
|
7
7
|
export declare function withCache(fetchImpl: typeof fetch, options: CacheOptions): typeof fetch;
|
|
8
|
+
/**
|
|
9
|
+
* The Next.js cache tag for every read of a model, by its namespace. A GraphQL
|
|
10
|
+
* query names its models by namespace (`articles`), not by id, so this is the
|
|
11
|
+
* tag a GraphQL read is cached under, and `revalidateFromWebhook` revalidates
|
|
12
|
+
* it when an entry of that model changes.
|
|
13
|
+
*/
|
|
14
|
+
export declare function modelTag(namespace: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* The tag `graphql()` keeps a read under when it gives a `revalidate` and no
|
|
17
|
+
* `tags`, and that `revalidateFromWebhook` revalidates on every content
|
|
18
|
+
* change: such a page is never stale after a publish, at the price of
|
|
19
|
+
* refreshing on any publish. Name the models it reads with
|
|
20
|
+
* `tagsFor({ namespace })` to refresh it only when one of those changes. A
|
|
21
|
+
* read with neither `tags` nor `revalidate` is not kept at all.
|
|
22
|
+
*/
|
|
23
|
+
export declare const GRAPHQL_TAG = "capa:graphql";
|
|
24
|
+
/**
|
|
25
|
+
* The tag `tagsFor({ namespace })` adds beside its model tags, and that
|
|
26
|
+
* `revalidateFromWebhook` revalidates on a media event: a GraphQL read shows
|
|
27
|
+
* a file's URL and alt text whichever models it names, so editing a file in
|
|
28
|
+
* the media library refreshes it.
|
|
29
|
+
*/
|
|
30
|
+
export declare const MEDIA_TAG = "capa:media";
|
|
31
|
+
/**
|
|
32
|
+
* Next.js cache tags for a read. `model`, `entry`, `key` and `tenant` are the
|
|
33
|
+
* API's surrogate keys (`m:`, `e:`, `k:`, `t:`), by id; `namespace` is one
|
|
34
|
+
* `capa:model:<namespace>` tag per model a GraphQL query reads, which
|
|
35
|
+
* `capa-codegen --graphql` lists as `<Name>Models`, and `MEDIA_TAG`, since
|
|
36
|
+
* the query may show a file from the media library. Each pairs with
|
|
37
|
+
* `revalidateFromWebhook`.
|
|
38
|
+
*/
|
|
8
39
|
export declare function tagsFor(input: {
|
|
9
40
|
model?: string;
|
|
10
41
|
entry?: string;
|
|
11
42
|
key?: string;
|
|
12
43
|
tenant?: string;
|
|
44
|
+
namespace?: string | readonly string[];
|
|
13
45
|
}): string[];
|
|
14
46
|
export interface WebhookPayload {
|
|
47
|
+
type?: unknown;
|
|
15
48
|
data?: {
|
|
16
49
|
instanceId?: unknown;
|
|
17
50
|
modelId?: unknown;
|
|
51
|
+
modelNamespace?: unknown;
|
|
52
|
+
namespace?: unknown;
|
|
18
53
|
[key: string]: unknown;
|
|
19
54
|
};
|
|
20
55
|
instanceId?: unknown;
|
|
@@ -22,17 +57,34 @@ export interface WebhookPayload {
|
|
|
22
57
|
modelId?: unknown;
|
|
23
58
|
[key: string]: unknown;
|
|
24
59
|
}
|
|
25
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Revalidate every tag a webhook's entry and model can be cached under: the
|
|
62
|
+
* entry (`e:`), the model by id (`m:`) and by namespace (`capa:model:`, the
|
|
63
|
+
* tag a GraphQL read uses). The namespace is `data.modelNamespace` on
|
|
64
|
+
* `instance.published`, `instance.unpublished` and `model.published`, and
|
|
65
|
+
* `data.namespace` on the other events (docs/WEBHOOKS.md). Any of those, and
|
|
66
|
+
* a `media.` event, also revalidates `GRAPHQL_TAG`, the tag of a `graphql()`
|
|
67
|
+
* read that named no tags. A `media.` event (a file's alt text, name or
|
|
68
|
+
* visibility changed, or the file went) revalidates the file's own key
|
|
69
|
+
* (`f:<fileId>`, as REST's `Surrogate-Key` names it) and `MEDIA_TAG`, which
|
|
70
|
+
* every read tagged by namespace carries, since it may show that file.
|
|
71
|
+
*/
|
|
26
72
|
export declare function revalidateFromWebhook(input: {
|
|
27
73
|
payload: WebhookPayload;
|
|
28
74
|
revalidateTag: (tag: string) => void | Promise<void>;
|
|
29
75
|
}): Promise<string[]>;
|
|
30
|
-
/**
|
|
31
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Select a client using server-only draft state supplied by the caller. Pass
|
|
78
|
+
* codegen's `CapaQuery` to type the builder, as with `createClient`:
|
|
79
|
+
* `draftClient<CapaQuery>({ ... })`. `production` may hold the legacy key a
|
|
80
|
+
* site already has; `draft` needs a `cap_` key, and a legacy one throws a
|
|
81
|
+
* `TypeError` when draft mode selects it.
|
|
82
|
+
*/
|
|
83
|
+
export declare function draftClient<Q = UntypedQuery>(input: {
|
|
32
84
|
production: CapaNextConfig;
|
|
33
85
|
draft: CapaNextConfig;
|
|
34
86
|
isDraft: () => boolean | Promise<boolean>;
|
|
35
|
-
}): Promise<CapaNextClient
|
|
87
|
+
}): Promise<CapaNextClient<Q>>;
|
|
36
88
|
export declare function routeOf(file: string): string;
|
|
37
89
|
/**
|
|
38
90
|
* Verify a preview token from a Next route handler.
|
|
@@ -50,7 +102,7 @@ export declare function preview(token: string, client: CapaNextClient): Promise<
|
|
|
50
102
|
* this and never hold the whole client.
|
|
51
103
|
*/
|
|
52
104
|
export declare function pagesFor(client: CapaNextClient): PagesResource;
|
|
53
|
-
export { LAYOUT_PAGE };
|
|
105
|
+
export { gql, LAYOUT_PAGE };
|
|
54
106
|
export type { PreviewClaim };
|
|
55
107
|
/**
|
|
56
108
|
* The query parameter that turns edit mode on for one request without draft
|
|
@@ -128,13 +180,19 @@ export declare function resolveEditRequest(request: {
|
|
|
128
180
|
has(name: string): boolean;
|
|
129
181
|
};
|
|
130
182
|
}, client: Pick<CapaNextClient, "preview">): Promise<EditRequest>;
|
|
131
|
-
/**
|
|
183
|
+
/**
|
|
184
|
+
* Where the env-driven helpers read their settings (M6): the one pair of
|
|
185
|
+
* names the whole product uses (the MCP server, `capa-codegen`, `capa
|
|
186
|
+
* persist`, every curl example in the API docs).
|
|
187
|
+
*/
|
|
132
188
|
export declare const CAPA_ENV: {
|
|
133
189
|
readonly baseUrl: "CAPA_API_URL";
|
|
134
190
|
readonly apiKey: "CAPA_KEY";
|
|
135
191
|
readonly draftKey: "CAPA_DRAFT_KEY";
|
|
136
192
|
readonly version: "CAPA_API_VERSION";
|
|
137
193
|
};
|
|
194
|
+
/** Older names that still work, read only when the name above is unset. */
|
|
195
|
+
export declare const CAPA_ENV_ALIASES: Readonly<Record<string, string>>;
|
|
138
196
|
export declare const DEFAULT_API_VERSION = "2026-10-01";
|
|
139
197
|
type DraftModeFn = () => {
|
|
140
198
|
isEnabled: boolean;
|
|
@@ -145,20 +203,124 @@ type DraftModeFn = () => {
|
|
|
145
203
|
enable?: () => void;
|
|
146
204
|
disable?: () => void;
|
|
147
205
|
}>;
|
|
148
|
-
/**
|
|
149
|
-
|
|
206
|
+
/**
|
|
207
|
+
* The published-key client from env: what verifying a token needs. `overrides`
|
|
208
|
+
* win over env, and pass codegen's `CapaQuery` to type the builder:
|
|
209
|
+
* `getPublishedClient<CapaQuery>()`.
|
|
210
|
+
*/
|
|
211
|
+
export declare function getPublishedClient<Q = UntypedQuery>(overrides?: Partial<CapaNextConfig>): CapaNextClient<Q>;
|
|
150
212
|
/**
|
|
151
213
|
* The client for this request, from env: the draft key under draft mode,
|
|
152
214
|
* otherwise the published key, and `editMode` worked out for you.
|
|
153
215
|
*
|
|
154
216
|
* import { draftMode, headers } from "next/headers";
|
|
155
|
-
* const capa = await getCapaClient({ draftMode, headers });
|
|
217
|
+
* const capa = await getCapaClient<CapaQuery>({ draftMode, headers });
|
|
218
|
+
* const { data } = await capa.graphql.query(selection, { tags: tagsFor({ namespace: "articles" }) });
|
|
219
|
+
*
|
|
220
|
+
* `CapaQuery` (from `capa-codegen --graphql`) types the builder, as with
|
|
221
|
+
* `createClient`; leave it out for an untyped client. `config` wins over env.
|
|
222
|
+
*
|
|
223
|
+
* Its REST reads are sent as `createClient` sends them, which Next does not
|
|
224
|
+
* keep. Its GraphQL reads, a document or the builder, are sent the same way
|
|
225
|
+
* unless a call gives `tags` or `revalidate`, so a publish shows up on both
|
|
226
|
+
* alike. Given either, a read is kept in Next's data cache exactly as
|
|
227
|
+
* `graphql()` keeps it: a published read with no errors, never a draft or an
|
|
228
|
+
* edit-mode page.
|
|
156
229
|
*/
|
|
157
|
-
export declare function getCapaClient(input: {
|
|
230
|
+
export declare function getCapaClient<Q = UntypedQuery>(input: {
|
|
158
231
|
draftMode: DraftModeFn;
|
|
159
232
|
headers: () => HeaderReader | Promise<HeaderReader>;
|
|
160
233
|
config?: Partial<CapaNextConfig>;
|
|
161
|
-
|
|
234
|
+
/** Next's `unstable_cache`, as `graphql()` takes it. Leave it out: Next's own is loaded. */
|
|
235
|
+
unstable_cache?: UnstableCache;
|
|
236
|
+
}): Promise<CapaNextClient<Q, NextCacheOptions>>;
|
|
237
|
+
/** Where a GraphQL read in Next keeps its answer: Next's data cache, as `graphql()` and `getCapaClient` keep it. */
|
|
238
|
+
export interface NextCacheOptions extends GraphQLCallOptions {
|
|
239
|
+
/**
|
|
240
|
+
* Next.js cache tags to keep this read under. `tagsFor({ namespace:
|
|
241
|
+
* <Name>Models })`, with the list `capa-codegen --graphql` writes beside
|
|
242
|
+
* each document, tags it with every model the query reads, which
|
|
243
|
+
* `revalidateFromWebhook` revalidates on a publish. Given a `revalidate`
|
|
244
|
+
* and no tags, the read is kept under `GRAPHQL_TAG`, which every publish
|
|
245
|
+
* revalidates. Given neither, the read is not kept: it is sent as a REST
|
|
246
|
+
* read is, on every render.
|
|
247
|
+
*/
|
|
248
|
+
tags?: string[];
|
|
249
|
+
/** Seconds to cache, `false` to cache until a tag is revalidated, or 0 not to cache. */
|
|
250
|
+
revalidate?: number | false;
|
|
251
|
+
}
|
|
252
|
+
export interface NextGraphQLOptions extends NextCacheOptions {
|
|
253
|
+
/**
|
|
254
|
+
* Next's `draftMode` and `headers`, to work out draft and edit mode as
|
|
255
|
+
* `getCapaClient` does: drafts under draft mode, and in edit mode every
|
|
256
|
+
* entry the query reads marked for `capaAttrs`.
|
|
257
|
+
*/
|
|
258
|
+
draftMode?: DraftModeFn;
|
|
259
|
+
headers?: () => HeaderReader | Promise<HeaderReader>;
|
|
260
|
+
/**
|
|
261
|
+
* Read drafts with `CAPA_DRAFT_KEY`, uncached. Worked out from `draftMode`
|
|
262
|
+
* when that is passed; set it to decide yourself.
|
|
263
|
+
*/
|
|
264
|
+
draft?: boolean;
|
|
265
|
+
/** Overrides for the env-derived client (base URL, key, version, edit mode, fetch). Each one given is not read from env. */
|
|
266
|
+
config?: Partial<CapaNextConfig>;
|
|
267
|
+
/**
|
|
268
|
+
* Next's `unstable_cache`, from `next/cache`. Leave it out: `graphql()`
|
|
269
|
+
* loads Next's own. A published read given `tags` or a `revalidate` is kept
|
|
270
|
+
* in Next's data cache only when it answered with no `errors`, under `tags`
|
|
271
|
+
* for `revalidate` seconds, or with no `revalidate` until one of its tags is
|
|
272
|
+
* revalidated. Next's fetch
|
|
273
|
+
* cache is not used for it: that cache keeps any 200, and a GraphQL error
|
|
274
|
+
* is a 200 (a root field that timed out), and in Next 15 it keeps nothing
|
|
275
|
+
* without a `revalidate`, tags or not. Pass it only to supply another.
|
|
276
|
+
*/
|
|
277
|
+
unstable_cache?: UnstableCache;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Next's `unstable_cache` (`import { unstable_cache } from "next/cache"`),
|
|
281
|
+
* typed here so this package never imports `next/*`.
|
|
282
|
+
*/
|
|
283
|
+
export type UnstableCache = <T extends (...args: any[]) => Promise<any>>(cb: T, keyParts?: string[], options?: {
|
|
284
|
+
revalidate?: number | false;
|
|
285
|
+
tags?: string[];
|
|
286
|
+
}) => T;
|
|
287
|
+
/**
|
|
288
|
+
* GraphQL in a server component, one line, from env:
|
|
289
|
+
*
|
|
290
|
+
* import { draftMode, headers } from "next/headers";
|
|
291
|
+
* import { graphql, tagsFor } from "@capacms/sdk/nextjs";
|
|
292
|
+
* import { LatestModels } from "./capa-graphql";
|
|
293
|
+
* const LATEST = `#graphql
|
|
294
|
+
* query Latest($first: Int) { articles(first: $first) { nodes { id model title } } }
|
|
295
|
+
* `;
|
|
296
|
+
* const { data } = await graphql(LATEST, { first: 5 }, { draftMode, headers, tags: tagsFor({ namespace: LatestModels }) });
|
|
297
|
+
*
|
|
298
|
+
* Once `capa-codegen --graphql` has seen the literal, `data` and the
|
|
299
|
+
* variables are typed from it, as they are for a `<Name>Document` it writes,
|
|
300
|
+
* and `LatestModels` lists the models it reads.
|
|
301
|
+
*
|
|
302
|
+
* With no `tags` and no `revalidate` a read is sent as the REST reads are,
|
|
303
|
+
* and Next keeps nothing: the page shows a publish on its next render, as a
|
|
304
|
+
* page reading by REST does. Given either, a published read is kept in Next's
|
|
305
|
+
* data cache (`unstable_cache`) when it answered with no errors: under `tags`,
|
|
306
|
+
* for `revalidate` seconds, or with no `revalidate` until
|
|
307
|
+
* `revalidateFromWebhook` revalidates one of its tags on a publish. It goes
|
|
308
|
+
* as a GET, which the CDN and the API also cache for the published key; a
|
|
309
|
+
* document too long for a GET URL goes as a POST, which only Next's data
|
|
310
|
+
* cache keeps. Under draft mode the read uses `CAPA_DRAFT_KEY`, a
|
|
311
|
+
* `cap_` key, and bypasses the cache (the API answers a development key's GET
|
|
312
|
+
* `no-store`), the same split `getCapaClient` makes for REST, and in edit mode
|
|
313
|
+
* each entry that selects `id` and `model` is marked for `capaAttrs`, uncached.
|
|
314
|
+
* Outside a Next request (a script, a test) the read is simply sent.
|
|
315
|
+
*
|
|
316
|
+
* `persisted: true` sends only the hash, and pays off once the documents are
|
|
317
|
+
* stored with `capa persist`, which takes a development key: a production
|
|
318
|
+
* key never stores one. Until then a
|
|
319
|
+
* hash misses, and the client remembers the miss for five minutes and sends
|
|
320
|
+
* the document by POST meanwhile, so it costs one extra GET per five minutes.
|
|
321
|
+
*/
|
|
322
|
+
export declare function graphql<D extends keyof CapaDocuments>(document: D, ...rest: VariablesThenOptions<CapaDocumentVariables<D>, NextGraphQLOptions>): Promise<GraphQLResult<CapaDocumentResult<D>>>;
|
|
323
|
+
export declare function graphql<TData = Record<string, unknown>, TVariables = Record<string, unknown>, D extends string = string>(document: NotARecordedDocument<D, TypedDocument<TData, TVariables> | D>, ...rest: VariablesThenOptions<TVariables, NextGraphQLOptions>): Promise<GraphQLResult<TData>>;
|
|
162
324
|
/** Only a path on this site: never `//elsewhere.example` or a full URL. */
|
|
163
325
|
export declare function safeSitePath(value: string | null | undefined): string;
|
|
164
326
|
/**
|