@bettercms-ai/sdk 1.34.0 → 1.35.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/dist/draft.cjs +32 -0
- package/dist/draft.cjs.map +1 -1
- package/dist/draft.d.cts +15 -1
- package/dist/draft.d.ts +15 -1
- package/dist/draft.js +29 -0
- package/dist/draft.js.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/{read-ndf2I3D4.d.cts → read-client-tNsCHy0p.d.cts} +1 -37
- package/dist/{read-ndf2I3D4.d.ts → read-client-tNsCHy0p.d.ts} +1 -37
- package/dist/read.d.cts +39 -1
- package/dist/read.d.ts +39 -1
- package/dist/sections.cjs +516 -0
- package/dist/sections.cjs.map +1 -0
- package/dist/sections.d.cts +49 -0
- package/dist/sections.d.ts +49 -0
- package/dist/sections.js +486 -0
- package/dist/sections.js.map +1 -0
- package/package.json +6 -1
package/dist/draft.cjs
CHANGED
|
@@ -22,6 +22,9 @@ var draft_exports = {};
|
|
|
22
22
|
__export(draft_exports, {
|
|
23
23
|
DRAFT_COOKIE: () => DRAFT_COOKIE,
|
|
24
24
|
decodeTokenSlug: () => decodeTokenSlug,
|
|
25
|
+
draftDisableResponse: () => draftDisableResponse,
|
|
26
|
+
draftEnableResponse: () => draftEnableResponse,
|
|
27
|
+
draftTokenFrom: () => draftTokenFrom,
|
|
25
28
|
previewTokenValid: () => previewTokenValid,
|
|
26
29
|
safeRedirectPath: () => safeRedirectPath,
|
|
27
30
|
signValue: () => signValue,
|
|
@@ -74,10 +77,39 @@ async function previewTokenValid(apiUrl, token, fetchImpl = fetch) {
|
|
|
74
77
|
});
|
|
75
78
|
return res.ok;
|
|
76
79
|
}
|
|
80
|
+
async function draftTokenFrom(cookieHeader, secret) {
|
|
81
|
+
if (!cookieHeader || !secret) return null;
|
|
82
|
+
const m = new RegExp(`(?:^|;\\s*)${DRAFT_COOKIE}=([^;]+)`).exec(cookieHeader);
|
|
83
|
+
if (!m) return null;
|
|
84
|
+
try {
|
|
85
|
+
return await verifyValue(decodeURIComponent(m[1]), secret);
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function draftEnableResponse(request, opts) {
|
|
91
|
+
const url = new URL(request.url);
|
|
92
|
+
const token = url.searchParams.get("token");
|
|
93
|
+
if (!opts.secret) return new Response("BCMS_DRAFT_SECRET is not configured", { status: 500 });
|
|
94
|
+
if (!token) return new Response("Missing token", { status: 400 });
|
|
95
|
+
if (!await previewTokenValid(opts.apiUrl, token, opts.fetchImpl)) return new Response("Invalid or expired token", { status: 401 });
|
|
96
|
+
const cookie = `${DRAFT_COOKIE}=${encodeURIComponent(await signValue(token, opts.secret))}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400${url.protocol === "https:" ? "; Secure" : ""}`;
|
|
97
|
+
return new Response(null, { status: 302, headers: { Location: safeRedirectPath(url.searchParams.get("redirect") ?? "/"), "Set-Cookie": cookie } });
|
|
98
|
+
}
|
|
99
|
+
function draftDisableResponse(request) {
|
|
100
|
+
const url = new URL(request.url);
|
|
101
|
+
return new Response(null, {
|
|
102
|
+
status: 302,
|
|
103
|
+
headers: { Location: safeRedirectPath(url.searchParams.get("redirect") ?? "/"), "Set-Cookie": `${DRAFT_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0` }
|
|
104
|
+
});
|
|
105
|
+
}
|
|
77
106
|
// Annotate the CommonJS export names for ESM import in node:
|
|
78
107
|
0 && (module.exports = {
|
|
79
108
|
DRAFT_COOKIE,
|
|
80
109
|
decodeTokenSlug,
|
|
110
|
+
draftDisableResponse,
|
|
111
|
+
draftEnableResponse,
|
|
112
|
+
draftTokenFrom,
|
|
81
113
|
previewTokenValid,
|
|
82
114
|
safeRedirectPath,
|
|
83
115
|
signValue,
|
package/dist/draft.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/draft.ts"],"sourcesContent":["/**\n * Draft mode, shared by every framework adapter: the signed cookie that says \"this visitor is\n * previewing\", the preview token's slug, and the redirect a draft route may follow.\n *\n * Web Crypto and `atob` only, so it runs in Node, edge runtimes and workers alike. Each adapter\n * mounts its own routes around these; the rules live here once.\n */\n\n/** The draft cookie every adapter sets: one name, so an editor link works on any framework. */\nexport const DRAFT_COOKIE = \"bcms-draft\";\n\nconst enc = new TextEncoder();\n\nfunction base64url(bytes: ArrayBuffer): string {\n const b = btoa(String.fromCharCode(...new Uint8Array(bytes)));\n return b.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nasync function hmac(value: string, secret: string): Promise<string> {\n const key = await crypto.subtle.importKey(\"raw\", enc.encode(secret), { name: \"HMAC\", hash: \"SHA-256\" }, false, [\"sign\"]);\n return base64url(await crypto.subtle.sign(\"HMAC\", key, enc.encode(value)));\n}\n\n/** Sign a value as `value.sig` for tamper-evident cookie storage (HMAC-SHA256, base64url). */\nexport async function signValue(value: string, secret: string): Promise<string> {\n return `${value}.${await hmac(value, secret)}`;\n}\n\n/** The original value of a signed one, or null when it was tampered with. */\nexport async function verifyValue(signed: string, secret: string): Promise<string | null> {\n const idx = signed.lastIndexOf(\".\");\n if (idx < 0) return null;\n const value = signed.slice(0, idx);\n const sig = signed.slice(idx + 1);\n const expected = await hmac(value, secret);\n if (sig.length !== expected.length) return null;\n let diff = 0;\n for (let i = 0; i < sig.length; i++) diff |= sig.charCodeAt(i) ^ expected.charCodeAt(i);\n return diff === 0 ? value : null;\n}\n\n/** Only same-origin relative paths: blocks \"//evil.com\", \"https://evil.com\" and \"/\\\\evil.com\". */\nexport function safeRedirectPath(to: string): string {\n return to.startsWith(\"/\") && !to.startsWith(\"//\") && !to.startsWith(\"/\\\\\") ? to : \"/\";\n}\n\n/** A preview token's `entrySlug`, read from its payload UNVERIFIED (the backend verifies it). */\nexport function decodeTokenSlug(token: string): string | null {\n try {\n const part = token.split(\".\")[1];\n if (!part) return null;\n // atob gives bytes as Latin-1: decoded as UTF-8, a slug like `über-uns` survives.\n const bytes = Uint8Array.from(atob(part.replace(/-/g, \"+\").replace(/_/g, \"/\")), (c) => c.charCodeAt(0));\n const json = JSON.parse(new TextDecoder().decode(bytes)) as { entrySlug?: string };\n return json.entrySlug ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Ask the backend whether a preview token is good: the backend is the signature authority, so a\n * draft route enables draft mode only on its yes.\n */\nexport async function previewTokenValid(apiUrl: string, token: string, fetchImpl: typeof fetch = fetch): Promise<boolean> {\n const slug = decodeTokenSlug(token);\n if (!slug) return false;\n const res = await fetchImpl(`${apiUrl.replace(/\\/+$/, \"\")}/api/v1/preview/${encodeURIComponent(slug)}?token=${encodeURIComponent(token)}`);\n await res.body?.cancel().catch(() => {});\n return res.ok;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASO,IAAM,eAAe;AAE5B,IAAM,MAAM,IAAI,YAAY;AAE5B,SAAS,UAAU,OAA4B;AAC7C,QAAM,IAAI,KAAK,OAAO,aAAa,GAAG,IAAI,WAAW,KAAK,CAAC,CAAC;AAC5D,SAAO,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AACpE;AAEA,eAAe,KAAK,OAAe,QAAiC;AAClE,QAAM,MAAM,MAAM,OAAO,OAAO,UAAU,OAAO,IAAI,OAAO,MAAM,GAAG,EAAE,MAAM,QAAQ,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;AACvH,SAAO,UAAU,MAAM,OAAO,OAAO,KAAK,QAAQ,KAAK,IAAI,OAAO,KAAK,CAAC,CAAC;AAC3E;AAGA,eAAsB,UAAU,OAAe,QAAiC;AAC9E,SAAO,GAAG,KAAK,IAAI,MAAM,KAAK,OAAO,MAAM,CAAC;AAC9C;AAGA,eAAsB,YAAY,QAAgB,QAAwC;AACxF,QAAM,MAAM,OAAO,YAAY,GAAG;AAClC,MAAI,MAAM,EAAG,QAAO;AACpB,QAAM,QAAQ,OAAO,MAAM,GAAG,GAAG;AACjC,QAAM,MAAM,OAAO,MAAM,MAAM,CAAC;AAChC,QAAM,WAAW,MAAM,KAAK,OAAO,MAAM;AACzC,MAAI,IAAI,WAAW,SAAS,OAAQ,QAAO;AAC3C,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,IAAK,SAAQ,IAAI,WAAW,CAAC,IAAI,SAAS,WAAW,CAAC;AACtF,SAAO,SAAS,IAAI,QAAQ;AAC9B;AAGO,SAAS,iBAAiB,IAAoB;AACnD,SAAO,GAAG,WAAW,GAAG,KAAK,CAAC,GAAG,WAAW,IAAI,KAAK,CAAC,GAAG,WAAW,KAAK,IAAI,KAAK;AACpF;AAGO,SAAS,gBAAgB,OAA8B;AAC5D,MAAI;AACF,UAAM,OAAO,MAAM,MAAM,GAAG,EAAE,CAAC;AAC/B,QAAI,CAAC,KAAM,QAAO;AAElB,UAAM,QAAQ,WAAW,KAAK,KAAK,KAAK,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AACtG,UAAM,OAAO,KAAK,MAAM,IAAI,YAAY,EAAE,OAAO,KAAK,CAAC;AACvD,WAAO,KAAK,aAAa;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,kBAAkB,QAAgB,OAAe,YAA0B,OAAyB;AACxH,QAAM,OAAO,gBAAgB,KAAK;AAClC,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,MAAM,MAAM,UAAU,GAAG,OAAO,QAAQ,QAAQ,EAAE,CAAC,mBAAmB,mBAAmB,IAAI,CAAC,UAAU,mBAAmB,KAAK,CAAC,EAAE;AACzI,QAAM,IAAI,MAAM,OAAO,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AACvC,SAAO,IAAI;AACb;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/draft.ts"],"sourcesContent":["/**\n * Draft mode, shared by every framework adapter: the signed cookie that says \"this visitor is\n * previewing\", the preview token's slug, and the redirect a draft route may follow.\n *\n * Web Crypto and `atob` only, so it runs in Node, edge runtimes and workers alike. Each adapter\n * mounts its own routes around these; the rules live here once.\n */\n\n/** The draft cookie every adapter sets: one name, so an editor link works on any framework. */\nexport const DRAFT_COOKIE = \"bcms-draft\";\n\nconst enc = new TextEncoder();\n\nfunction base64url(bytes: ArrayBuffer): string {\n const b = btoa(String.fromCharCode(...new Uint8Array(bytes)));\n return b.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nasync function hmac(value: string, secret: string): Promise<string> {\n const key = await crypto.subtle.importKey(\"raw\", enc.encode(secret), { name: \"HMAC\", hash: \"SHA-256\" }, false, [\"sign\"]);\n return base64url(await crypto.subtle.sign(\"HMAC\", key, enc.encode(value)));\n}\n\n/** Sign a value as `value.sig` for tamper-evident cookie storage (HMAC-SHA256, base64url). */\nexport async function signValue(value: string, secret: string): Promise<string> {\n return `${value}.${await hmac(value, secret)}`;\n}\n\n/** The original value of a signed one, or null when it was tampered with. */\nexport async function verifyValue(signed: string, secret: string): Promise<string | null> {\n const idx = signed.lastIndexOf(\".\");\n if (idx < 0) return null;\n const value = signed.slice(0, idx);\n const sig = signed.slice(idx + 1);\n const expected = await hmac(value, secret);\n if (sig.length !== expected.length) return null;\n let diff = 0;\n for (let i = 0; i < sig.length; i++) diff |= sig.charCodeAt(i) ^ expected.charCodeAt(i);\n return diff === 0 ? value : null;\n}\n\n/** Only same-origin relative paths: blocks \"//evil.com\", \"https://evil.com\" and \"/\\\\evil.com\". */\nexport function safeRedirectPath(to: string): string {\n return to.startsWith(\"/\") && !to.startsWith(\"//\") && !to.startsWith(\"/\\\\\") ? to : \"/\";\n}\n\n/** A preview token's `entrySlug`, read from its payload UNVERIFIED (the backend verifies it). */\nexport function decodeTokenSlug(token: string): string | null {\n try {\n const part = token.split(\".\")[1];\n if (!part) return null;\n // atob gives bytes as Latin-1: decoded as UTF-8, a slug like `über-uns` survives.\n const bytes = Uint8Array.from(atob(part.replace(/-/g, \"+\").replace(/_/g, \"/\")), (c) => c.charCodeAt(0));\n const json = JSON.parse(new TextDecoder().decode(bytes)) as { entrySlug?: string };\n return json.entrySlug ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Ask the backend whether a preview token is good: the backend is the signature authority, so a\n * draft route enables draft mode only on its yes.\n */\nexport async function previewTokenValid(apiUrl: string, token: string, fetchImpl: typeof fetch = fetch): Promise<boolean> {\n const slug = decodeTokenSlug(token);\n if (!slug) return false;\n const res = await fetchImpl(`${apiUrl.replace(/\\/+$/, \"\")}/api/v1/preview/${encodeURIComponent(slug)}?token=${encodeURIComponent(token)}`);\n await res.body?.cancel().catch(() => {});\n return res.ok;\n}\n\n/** The draft cookie's preview token from a `Cookie` header, or null when absent or tampered with. */\nexport async function draftTokenFrom(cookieHeader: string | null | undefined, secret: string | undefined): Promise<string | null> {\n if (!cookieHeader || !secret) return null;\n const m = new RegExp(`(?:^|;\\\\s*)${DRAFT_COOKIE}=([^;]+)`).exec(cookieHeader);\n if (!m) return null;\n try {\n return await verifyValue(decodeURIComponent(m[1]!), secret);\n } catch {\n return null;\n }\n}\n\n/**\n * The draft-enable route for any server that speaks Web `Request`/`Response` (SvelteKit `+server.ts`,\n * Nuxt via `toWebRequest`, Remix, Hono): `GET ?token=…&redirect=/x` checks the token with the\n * backend, sets the signed draft cookie and redirects. 401 on a refused token; 500 without a secret.\n */\nexport async function draftEnableResponse(request: Request, opts: { apiUrl: string; secret: string | undefined; fetchImpl?: typeof fetch }): Promise<Response> {\n const url = new URL(request.url);\n const token = url.searchParams.get(\"token\");\n if (!opts.secret) return new Response(\"BCMS_DRAFT_SECRET is not configured\", { status: 500 });\n if (!token) return new Response(\"Missing token\", { status: 400 });\n if (!(await previewTokenValid(opts.apiUrl, token, opts.fetchImpl))) return new Response(\"Invalid or expired token\", { status: 401 });\n const cookie = `${DRAFT_COOKIE}=${encodeURIComponent(await signValue(token, opts.secret))}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400${url.protocol === \"https:\" ? \"; Secure\" : \"\"}`;\n return new Response(null, { status: 302, headers: { Location: safeRedirectPath(url.searchParams.get(\"redirect\") ?? \"/\"), \"Set-Cookie\": cookie } });\n}\n\n/** Leave draft mode: clear the cookie and redirect (`?redirect=/x`, same-origin only). */\nexport function draftDisableResponse(request: Request): Response {\n const url = new URL(request.url);\n return new Response(null, {\n status: 302,\n headers: { Location: safeRedirectPath(url.searchParams.get(\"redirect\") ?? \"/\"), \"Set-Cookie\": `${DRAFT_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0` },\n });\n}\n"],"mappings":"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","names":[]}
|
package/dist/draft.d.cts
CHANGED
|
@@ -20,5 +20,19 @@ declare function decodeTokenSlug(token: string): string | null;
|
|
|
20
20
|
* draft route enables draft mode only on its yes.
|
|
21
21
|
*/
|
|
22
22
|
declare function previewTokenValid(apiUrl: string, token: string, fetchImpl?: typeof fetch): Promise<boolean>;
|
|
23
|
+
/** The draft cookie's preview token from a `Cookie` header, or null when absent or tampered with. */
|
|
24
|
+
declare function draftTokenFrom(cookieHeader: string | null | undefined, secret: string | undefined): Promise<string | null>;
|
|
25
|
+
/**
|
|
26
|
+
* The draft-enable route for any server that speaks Web `Request`/`Response` (SvelteKit `+server.ts`,
|
|
27
|
+
* Nuxt via `toWebRequest`, Remix, Hono): `GET ?token=…&redirect=/x` checks the token with the
|
|
28
|
+
* backend, sets the signed draft cookie and redirects. 401 on a refused token; 500 without a secret.
|
|
29
|
+
*/
|
|
30
|
+
declare function draftEnableResponse(request: Request, opts: {
|
|
31
|
+
apiUrl: string;
|
|
32
|
+
secret: string | undefined;
|
|
33
|
+
fetchImpl?: typeof fetch;
|
|
34
|
+
}): Promise<Response>;
|
|
35
|
+
/** Leave draft mode: clear the cookie and redirect (`?redirect=/x`, same-origin only). */
|
|
36
|
+
declare function draftDisableResponse(request: Request): Response;
|
|
23
37
|
|
|
24
|
-
export { DRAFT_COOKIE, decodeTokenSlug, previewTokenValid, safeRedirectPath, signValue, verifyValue };
|
|
38
|
+
export { DRAFT_COOKIE, decodeTokenSlug, draftDisableResponse, draftEnableResponse, draftTokenFrom, previewTokenValid, safeRedirectPath, signValue, verifyValue };
|
package/dist/draft.d.ts
CHANGED
|
@@ -20,5 +20,19 @@ declare function decodeTokenSlug(token: string): string | null;
|
|
|
20
20
|
* draft route enables draft mode only on its yes.
|
|
21
21
|
*/
|
|
22
22
|
declare function previewTokenValid(apiUrl: string, token: string, fetchImpl?: typeof fetch): Promise<boolean>;
|
|
23
|
+
/** The draft cookie's preview token from a `Cookie` header, or null when absent or tampered with. */
|
|
24
|
+
declare function draftTokenFrom(cookieHeader: string | null | undefined, secret: string | undefined): Promise<string | null>;
|
|
25
|
+
/**
|
|
26
|
+
* The draft-enable route for any server that speaks Web `Request`/`Response` (SvelteKit `+server.ts`,
|
|
27
|
+
* Nuxt via `toWebRequest`, Remix, Hono): `GET ?token=…&redirect=/x` checks the token with the
|
|
28
|
+
* backend, sets the signed draft cookie and redirects. 401 on a refused token; 500 without a secret.
|
|
29
|
+
*/
|
|
30
|
+
declare function draftEnableResponse(request: Request, opts: {
|
|
31
|
+
apiUrl: string;
|
|
32
|
+
secret: string | undefined;
|
|
33
|
+
fetchImpl?: typeof fetch;
|
|
34
|
+
}): Promise<Response>;
|
|
35
|
+
/** Leave draft mode: clear the cookie and redirect (`?redirect=/x`, same-origin only). */
|
|
36
|
+
declare function draftDisableResponse(request: Request): Response;
|
|
23
37
|
|
|
24
|
-
export { DRAFT_COOKIE, decodeTokenSlug, previewTokenValid, safeRedirectPath, signValue, verifyValue };
|
|
38
|
+
export { DRAFT_COOKIE, decodeTokenSlug, draftDisableResponse, draftEnableResponse, draftTokenFrom, previewTokenValid, safeRedirectPath, signValue, verifyValue };
|
package/dist/draft.js
CHANGED
|
@@ -45,9 +45,38 @@ async function previewTokenValid(apiUrl, token, fetchImpl = fetch) {
|
|
|
45
45
|
});
|
|
46
46
|
return res.ok;
|
|
47
47
|
}
|
|
48
|
+
async function draftTokenFrom(cookieHeader, secret) {
|
|
49
|
+
if (!cookieHeader || !secret) return null;
|
|
50
|
+
const m = new RegExp(`(?:^|;\\s*)${DRAFT_COOKIE}=([^;]+)`).exec(cookieHeader);
|
|
51
|
+
if (!m) return null;
|
|
52
|
+
try {
|
|
53
|
+
return await verifyValue(decodeURIComponent(m[1]), secret);
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function draftEnableResponse(request, opts) {
|
|
59
|
+
const url = new URL(request.url);
|
|
60
|
+
const token = url.searchParams.get("token");
|
|
61
|
+
if (!opts.secret) return new Response("BCMS_DRAFT_SECRET is not configured", { status: 500 });
|
|
62
|
+
if (!token) return new Response("Missing token", { status: 400 });
|
|
63
|
+
if (!await previewTokenValid(opts.apiUrl, token, opts.fetchImpl)) return new Response("Invalid or expired token", { status: 401 });
|
|
64
|
+
const cookie = `${DRAFT_COOKIE}=${encodeURIComponent(await signValue(token, opts.secret))}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400${url.protocol === "https:" ? "; Secure" : ""}`;
|
|
65
|
+
return new Response(null, { status: 302, headers: { Location: safeRedirectPath(url.searchParams.get("redirect") ?? "/"), "Set-Cookie": cookie } });
|
|
66
|
+
}
|
|
67
|
+
function draftDisableResponse(request) {
|
|
68
|
+
const url = new URL(request.url);
|
|
69
|
+
return new Response(null, {
|
|
70
|
+
status: 302,
|
|
71
|
+
headers: { Location: safeRedirectPath(url.searchParams.get("redirect") ?? "/"), "Set-Cookie": `${DRAFT_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0` }
|
|
72
|
+
});
|
|
73
|
+
}
|
|
48
74
|
export {
|
|
49
75
|
DRAFT_COOKIE,
|
|
50
76
|
decodeTokenSlug,
|
|
77
|
+
draftDisableResponse,
|
|
78
|
+
draftEnableResponse,
|
|
79
|
+
draftTokenFrom,
|
|
51
80
|
previewTokenValid,
|
|
52
81
|
safeRedirectPath,
|
|
53
82
|
signValue,
|
package/dist/draft.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/draft.ts"],"sourcesContent":["/**\n * Draft mode, shared by every framework adapter: the signed cookie that says \"this visitor is\n * previewing\", the preview token's slug, and the redirect a draft route may follow.\n *\n * Web Crypto and `atob` only, so it runs in Node, edge runtimes and workers alike. Each adapter\n * mounts its own routes around these; the rules live here once.\n */\n\n/** The draft cookie every adapter sets: one name, so an editor link works on any framework. */\nexport const DRAFT_COOKIE = \"bcms-draft\";\n\nconst enc = new TextEncoder();\n\nfunction base64url(bytes: ArrayBuffer): string {\n const b = btoa(String.fromCharCode(...new Uint8Array(bytes)));\n return b.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nasync function hmac(value: string, secret: string): Promise<string> {\n const key = await crypto.subtle.importKey(\"raw\", enc.encode(secret), { name: \"HMAC\", hash: \"SHA-256\" }, false, [\"sign\"]);\n return base64url(await crypto.subtle.sign(\"HMAC\", key, enc.encode(value)));\n}\n\n/** Sign a value as `value.sig` for tamper-evident cookie storage (HMAC-SHA256, base64url). */\nexport async function signValue(value: string, secret: string): Promise<string> {\n return `${value}.${await hmac(value, secret)}`;\n}\n\n/** The original value of a signed one, or null when it was tampered with. */\nexport async function verifyValue(signed: string, secret: string): Promise<string | null> {\n const idx = signed.lastIndexOf(\".\");\n if (idx < 0) return null;\n const value = signed.slice(0, idx);\n const sig = signed.slice(idx + 1);\n const expected = await hmac(value, secret);\n if (sig.length !== expected.length) return null;\n let diff = 0;\n for (let i = 0; i < sig.length; i++) diff |= sig.charCodeAt(i) ^ expected.charCodeAt(i);\n return diff === 0 ? value : null;\n}\n\n/** Only same-origin relative paths: blocks \"//evil.com\", \"https://evil.com\" and \"/\\\\evil.com\". */\nexport function safeRedirectPath(to: string): string {\n return to.startsWith(\"/\") && !to.startsWith(\"//\") && !to.startsWith(\"/\\\\\") ? to : \"/\";\n}\n\n/** A preview token's `entrySlug`, read from its payload UNVERIFIED (the backend verifies it). */\nexport function decodeTokenSlug(token: string): string | null {\n try {\n const part = token.split(\".\")[1];\n if (!part) return null;\n // atob gives bytes as Latin-1: decoded as UTF-8, a slug like `über-uns` survives.\n const bytes = Uint8Array.from(atob(part.replace(/-/g, \"+\").replace(/_/g, \"/\")), (c) => c.charCodeAt(0));\n const json = JSON.parse(new TextDecoder().decode(bytes)) as { entrySlug?: string };\n return json.entrySlug ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Ask the backend whether a preview token is good: the backend is the signature authority, so a\n * draft route enables draft mode only on its yes.\n */\nexport async function previewTokenValid(apiUrl: string, token: string, fetchImpl: typeof fetch = fetch): Promise<boolean> {\n const slug = decodeTokenSlug(token);\n if (!slug) return false;\n const res = await fetchImpl(`${apiUrl.replace(/\\/+$/, \"\")}/api/v1/preview/${encodeURIComponent(slug)}?token=${encodeURIComponent(token)}`);\n await res.body?.cancel().catch(() => {});\n return res.ok;\n}\n"],"mappings":";AASO,IAAM,eAAe;AAE5B,IAAM,MAAM,IAAI,YAAY;AAE5B,SAAS,UAAU,OAA4B;AAC7C,QAAM,IAAI,KAAK,OAAO,aAAa,GAAG,IAAI,WAAW,KAAK,CAAC,CAAC;AAC5D,SAAO,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AACpE;AAEA,eAAe,KAAK,OAAe,QAAiC;AAClE,QAAM,MAAM,MAAM,OAAO,OAAO,UAAU,OAAO,IAAI,OAAO,MAAM,GAAG,EAAE,MAAM,QAAQ,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;AACvH,SAAO,UAAU,MAAM,OAAO,OAAO,KAAK,QAAQ,KAAK,IAAI,OAAO,KAAK,CAAC,CAAC;AAC3E;AAGA,eAAsB,UAAU,OAAe,QAAiC;AAC9E,SAAO,GAAG,KAAK,IAAI,MAAM,KAAK,OAAO,MAAM,CAAC;AAC9C;AAGA,eAAsB,YAAY,QAAgB,QAAwC;AACxF,QAAM,MAAM,OAAO,YAAY,GAAG;AAClC,MAAI,MAAM,EAAG,QAAO;AACpB,QAAM,QAAQ,OAAO,MAAM,GAAG,GAAG;AACjC,QAAM,MAAM,OAAO,MAAM,MAAM,CAAC;AAChC,QAAM,WAAW,MAAM,KAAK,OAAO,MAAM;AACzC,MAAI,IAAI,WAAW,SAAS,OAAQ,QAAO;AAC3C,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,IAAK,SAAQ,IAAI,WAAW,CAAC,IAAI,SAAS,WAAW,CAAC;AACtF,SAAO,SAAS,IAAI,QAAQ;AAC9B;AAGO,SAAS,iBAAiB,IAAoB;AACnD,SAAO,GAAG,WAAW,GAAG,KAAK,CAAC,GAAG,WAAW,IAAI,KAAK,CAAC,GAAG,WAAW,KAAK,IAAI,KAAK;AACpF;AAGO,SAAS,gBAAgB,OAA8B;AAC5D,MAAI;AACF,UAAM,OAAO,MAAM,MAAM,GAAG,EAAE,CAAC;AAC/B,QAAI,CAAC,KAAM,QAAO;AAElB,UAAM,QAAQ,WAAW,KAAK,KAAK,KAAK,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AACtG,UAAM,OAAO,KAAK,MAAM,IAAI,YAAY,EAAE,OAAO,KAAK,CAAC;AACvD,WAAO,KAAK,aAAa;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,kBAAkB,QAAgB,OAAe,YAA0B,OAAyB;AACxH,QAAM,OAAO,gBAAgB,KAAK;AAClC,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,MAAM,MAAM,UAAU,GAAG,OAAO,QAAQ,QAAQ,EAAE,CAAC,mBAAmB,mBAAmB,IAAI,CAAC,UAAU,mBAAmB,KAAK,CAAC,EAAE;AACzI,QAAM,IAAI,MAAM,OAAO,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AACvC,SAAO,IAAI;AACb;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/draft.ts"],"sourcesContent":["/**\n * Draft mode, shared by every framework adapter: the signed cookie that says \"this visitor is\n * previewing\", the preview token's slug, and the redirect a draft route may follow.\n *\n * Web Crypto and `atob` only, so it runs in Node, edge runtimes and workers alike. Each adapter\n * mounts its own routes around these; the rules live here once.\n */\n\n/** The draft cookie every adapter sets: one name, so an editor link works on any framework. */\nexport const DRAFT_COOKIE = \"bcms-draft\";\n\nconst enc = new TextEncoder();\n\nfunction base64url(bytes: ArrayBuffer): string {\n const b = btoa(String.fromCharCode(...new Uint8Array(bytes)));\n return b.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nasync function hmac(value: string, secret: string): Promise<string> {\n const key = await crypto.subtle.importKey(\"raw\", enc.encode(secret), { name: \"HMAC\", hash: \"SHA-256\" }, false, [\"sign\"]);\n return base64url(await crypto.subtle.sign(\"HMAC\", key, enc.encode(value)));\n}\n\n/** Sign a value as `value.sig` for tamper-evident cookie storage (HMAC-SHA256, base64url). */\nexport async function signValue(value: string, secret: string): Promise<string> {\n return `${value}.${await hmac(value, secret)}`;\n}\n\n/** The original value of a signed one, or null when it was tampered with. */\nexport async function verifyValue(signed: string, secret: string): Promise<string | null> {\n const idx = signed.lastIndexOf(\".\");\n if (idx < 0) return null;\n const value = signed.slice(0, idx);\n const sig = signed.slice(idx + 1);\n const expected = await hmac(value, secret);\n if (sig.length !== expected.length) return null;\n let diff = 0;\n for (let i = 0; i < sig.length; i++) diff |= sig.charCodeAt(i) ^ expected.charCodeAt(i);\n return diff === 0 ? value : null;\n}\n\n/** Only same-origin relative paths: blocks \"//evil.com\", \"https://evil.com\" and \"/\\\\evil.com\". */\nexport function safeRedirectPath(to: string): string {\n return to.startsWith(\"/\") && !to.startsWith(\"//\") && !to.startsWith(\"/\\\\\") ? to : \"/\";\n}\n\n/** A preview token's `entrySlug`, read from its payload UNVERIFIED (the backend verifies it). */\nexport function decodeTokenSlug(token: string): string | null {\n try {\n const part = token.split(\".\")[1];\n if (!part) return null;\n // atob gives bytes as Latin-1: decoded as UTF-8, a slug like `über-uns` survives.\n const bytes = Uint8Array.from(atob(part.replace(/-/g, \"+\").replace(/_/g, \"/\")), (c) => c.charCodeAt(0));\n const json = JSON.parse(new TextDecoder().decode(bytes)) as { entrySlug?: string };\n return json.entrySlug ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Ask the backend whether a preview token is good: the backend is the signature authority, so a\n * draft route enables draft mode only on its yes.\n */\nexport async function previewTokenValid(apiUrl: string, token: string, fetchImpl: typeof fetch = fetch): Promise<boolean> {\n const slug = decodeTokenSlug(token);\n if (!slug) return false;\n const res = await fetchImpl(`${apiUrl.replace(/\\/+$/, \"\")}/api/v1/preview/${encodeURIComponent(slug)}?token=${encodeURIComponent(token)}`);\n await res.body?.cancel().catch(() => {});\n return res.ok;\n}\n\n/** The draft cookie's preview token from a `Cookie` header, or null when absent or tampered with. */\nexport async function draftTokenFrom(cookieHeader: string | null | undefined, secret: string | undefined): Promise<string | null> {\n if (!cookieHeader || !secret) return null;\n const m = new RegExp(`(?:^|;\\\\s*)${DRAFT_COOKIE}=([^;]+)`).exec(cookieHeader);\n if (!m) return null;\n try {\n return await verifyValue(decodeURIComponent(m[1]!), secret);\n } catch {\n return null;\n }\n}\n\n/**\n * The draft-enable route for any server that speaks Web `Request`/`Response` (SvelteKit `+server.ts`,\n * Nuxt via `toWebRequest`, Remix, Hono): `GET ?token=…&redirect=/x` checks the token with the\n * backend, sets the signed draft cookie and redirects. 401 on a refused token; 500 without a secret.\n */\nexport async function draftEnableResponse(request: Request, opts: { apiUrl: string; secret: string | undefined; fetchImpl?: typeof fetch }): Promise<Response> {\n const url = new URL(request.url);\n const token = url.searchParams.get(\"token\");\n if (!opts.secret) return new Response(\"BCMS_DRAFT_SECRET is not configured\", { status: 500 });\n if (!token) return new Response(\"Missing token\", { status: 400 });\n if (!(await previewTokenValid(opts.apiUrl, token, opts.fetchImpl))) return new Response(\"Invalid or expired token\", { status: 401 });\n const cookie = `${DRAFT_COOKIE}=${encodeURIComponent(await signValue(token, opts.secret))}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400${url.protocol === \"https:\" ? \"; Secure\" : \"\"}`;\n return new Response(null, { status: 302, headers: { Location: safeRedirectPath(url.searchParams.get(\"redirect\") ?? \"/\"), \"Set-Cookie\": cookie } });\n}\n\n/** Leave draft mode: clear the cookie and redirect (`?redirect=/x`, same-origin only). */\nexport function draftDisableResponse(request: Request): Response {\n const url = new URL(request.url);\n return new Response(null, {\n status: 302,\n headers: { Location: safeRedirectPath(url.searchParams.get(\"redirect\") ?? \"/\"), \"Set-Cookie\": `${DRAFT_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0` },\n });\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as _bettercms_ai_types from '@bettercms-ai/types';
|
|
2
2
|
import { ContentPageResult, Content, DeliveredLayout, ContentModelField, Fieldset, ComponentPreview, LayoutStructureDocument, LayoutDataDocument, PageLayoutOverrideDocument, ManagementLayoutCommand, AuthUser, AuthSession, SignInInput, SignUpInput, Page, MediaAsset, FormField, Form, FormSubmission, Workspace, ApiKey, ApiKeyPermission, ApiKeyTokenType } from '@bettercms-ai/types';
|
|
3
3
|
export { ApiError, ApiKey, ApiKeyPermission, ApiKeyTokenType, AssertEqual, AuthSession, AuthUser, BetterCMSErrorCode, BlockType, ButtonBlock, ButtonProps, ColumnsBlock, ColumnsProps, Content, ContentBlock, ContentEntry, ContentModel, ContentModelField, ContentPageResult, ContentResponse, DeepReadonly, DeliveryComponent, DeliveryEntry, DeliveryList, DeliveryPage, Form, FormBlock, FormField, FormProps, FormSubmission, HeadingBlock, HeadingProps, ImageBlock, ImageProps, MediaAsset, Page, PageMetaJson, PaginatedResult, Perspective, Redirect, SignInInput, SignUpInput, SiteSeoDefaults, SpacerBlock, SpacerProps, TextBlock, TextProps, VideoBlock, VideoProps, Workspace, getBlockType, isBlock } from '@bettercms-ai/types';
|
|
4
|
-
import { D as DeliveryFormField } from './read-
|
|
5
|
-
export {
|
|
4
|
+
import { D as DeliveryFormField } from './read-client-tNsCHy0p.cjs';
|
|
5
|
+
export { a as BCMSClientError, E as BCMSErrorCodes, a as BetterCMSError, B as BetterCMSReadClient, C as CreateClientOptions, b as DeliveryForm, c as DeliveryFormFieldType, E as ErrorCodes, F as FormSubmitError, d as FormValue, e as FormValues, G as GetEntryOptions, L as ListEntriesOptions, f as ListPagesOptions, S as SubmitFormOptions, g as SubmitFormResult, h as asStringArray, i as createClient, j as formInitialValues, k as isMultiValueField, s as shouldShowField, l as submitForm, t as toggleOption } from './read-client-tNsCHy0p.cjs';
|
|
6
6
|
export { ResolvedSeo, SeoInput, SeoLocation, entrySeoInput, resolveSeo } from './seo.cjs';
|
|
7
|
+
export { SearchHit, SearchOptions, search } from './read.cjs';
|
|
7
8
|
export { ImageFit, ImageFormat, ImageSource, ImageUrlBuilder, ImageUrlOptions, imageUrl, default as imageUrlBuilder, imageValueObjectPosition, imageValueUrl } from '@bettercms-ai/image-url';
|
|
8
9
|
|
|
9
10
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as _bettercms_ai_types from '@bettercms-ai/types';
|
|
2
2
|
import { ContentPageResult, Content, DeliveredLayout, ContentModelField, Fieldset, ComponentPreview, LayoutStructureDocument, LayoutDataDocument, PageLayoutOverrideDocument, ManagementLayoutCommand, AuthUser, AuthSession, SignInInput, SignUpInput, Page, MediaAsset, FormField, Form, FormSubmission, Workspace, ApiKey, ApiKeyPermission, ApiKeyTokenType } from '@bettercms-ai/types';
|
|
3
3
|
export { ApiError, ApiKey, ApiKeyPermission, ApiKeyTokenType, AssertEqual, AuthSession, AuthUser, BetterCMSErrorCode, BlockType, ButtonBlock, ButtonProps, ColumnsBlock, ColumnsProps, Content, ContentBlock, ContentEntry, ContentModel, ContentModelField, ContentPageResult, ContentResponse, DeepReadonly, DeliveryComponent, DeliveryEntry, DeliveryList, DeliveryPage, Form, FormBlock, FormField, FormProps, FormSubmission, HeadingBlock, HeadingProps, ImageBlock, ImageProps, MediaAsset, Page, PageMetaJson, PaginatedResult, Perspective, Redirect, SignInInput, SignUpInput, SiteSeoDefaults, SpacerBlock, SpacerProps, TextBlock, TextProps, VideoBlock, VideoProps, Workspace, getBlockType, isBlock } from '@bettercms-ai/types';
|
|
4
|
-
import { D as DeliveryFormField } from './read-
|
|
5
|
-
export {
|
|
4
|
+
import { D as DeliveryFormField } from './read-client-tNsCHy0p.js';
|
|
5
|
+
export { a as BCMSClientError, E as BCMSErrorCodes, a as BetterCMSError, B as BetterCMSReadClient, C as CreateClientOptions, b as DeliveryForm, c as DeliveryFormFieldType, E as ErrorCodes, F as FormSubmitError, d as FormValue, e as FormValues, G as GetEntryOptions, L as ListEntriesOptions, f as ListPagesOptions, S as SubmitFormOptions, g as SubmitFormResult, h as asStringArray, i as createClient, j as formInitialValues, k as isMultiValueField, s as shouldShowField, l as submitForm, t as toggleOption } from './read-client-tNsCHy0p.js';
|
|
6
6
|
export { ResolvedSeo, SeoInput, SeoLocation, entrySeoInput, resolveSeo } from './seo.js';
|
|
7
|
+
export { SearchHit, SearchOptions, search } from './read.js';
|
|
7
8
|
export { ImageFit, ImageFormat, ImageSource, ImageUrlBuilder, ImageUrlOptions, imageUrl, default as imageUrlBuilder, imageValueObjectPosition, imageValueUrl } from '@bettercms-ai/image-url';
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -354,40 +354,4 @@ interface BetterCMSReadClient {
|
|
|
354
354
|
}
|
|
355
355
|
declare function createClient(options: CreateClientOptions): BetterCMSReadClient;
|
|
356
356
|
|
|
357
|
-
|
|
358
|
-
* Public site search — the headless surface for the visitor-facing search box.
|
|
359
|
-
*
|
|
360
|
-
* Queries `GET /api/v1/delivery/search?project=…&q=…`, the same key-less public
|
|
361
|
-
* endpoint the embeddable widget uses. Powers <BcmsSearch> in @bettercms-ai/next
|
|
362
|
-
* and @bettercms-ai/astro, so the component and any custom UI never drift.
|
|
363
|
-
*
|
|
364
|
-
* `snippet` is HTML-safe: the server escapes the field content and keeps only the
|
|
365
|
-
* <mark> highlight wrappers, so it can be rendered with dangerouslySetInnerHTML.
|
|
366
|
-
*/
|
|
367
|
-
interface SearchHit {
|
|
368
|
-
title: string;
|
|
369
|
-
slug: string;
|
|
370
|
-
type: "page" | "entry";
|
|
371
|
-
/** HTML-safe snippet with <mark> highlights around matched terms. */
|
|
372
|
-
snippet: string;
|
|
373
|
-
/** Site-relative URL, e.g. "/about". */
|
|
374
|
-
url: string;
|
|
375
|
-
}
|
|
376
|
-
interface SearchOptions {
|
|
377
|
-
/** The project id (from the dashboard's Site Search settings). */
|
|
378
|
-
project: string;
|
|
379
|
-
/** The query string. Calls with fewer than 1 char resolve to []. */
|
|
380
|
-
q: string;
|
|
381
|
-
/** Max hits, 1–20 (default 8). */
|
|
382
|
-
limit?: number;
|
|
383
|
-
/** API origin. Defaults to https://api.bettercms.ai. */
|
|
384
|
-
baseUrl?: string;
|
|
385
|
-
/** Injectable fetch for tests / non-browser runtimes. */
|
|
386
|
-
fetchImpl?: typeof fetch;
|
|
387
|
-
/** AbortSignal so callers can cancel a stale in-flight search. */
|
|
388
|
-
signal?: AbortSignal;
|
|
389
|
-
}
|
|
390
|
-
/** Search a published site. Resolves with hits, or [] on an empty query / network error. */
|
|
391
|
-
declare function search(opts: SearchOptions): Promise<SearchHit[]>;
|
|
392
|
-
|
|
393
|
-
export { BetterCMSError as B, type CreateClientOptions as C, type DeliveryFormField as D, ErrorCodes as E, type FormSubmitError as F, type GetEntryOptions as G, type ListEntriesOptions as L, type SearchHit as S, type BetterCMSReadClient as a, type DeliveryForm as b, type DeliveryFormFieldType as c, type FormValue as d, type FormValues as e, type ListPagesOptions as f, type SearchOptions as g, type SubmitFormOptions as h, type SubmitFormResult as i, asStringArray as j, createClient as k, formInitialValues as l, isMultiValueField as m, shouldShowField as n, submitForm as o, search as s, toggleOption as t };
|
|
357
|
+
export { type BetterCMSReadClient as B, type CreateClientOptions as C, type DeliveryFormField as D, ErrorCodes as E, type FormSubmitError as F, type GetEntryOptions as G, type ListEntriesOptions as L, type SubmitFormOptions as S, BetterCMSError as a, type DeliveryForm as b, type DeliveryFormFieldType as c, type FormValue as d, type FormValues as e, type ListPagesOptions as f, type SubmitFormResult as g, asStringArray as h, createClient as i, formInitialValues as j, isMultiValueField as k, submitForm as l, shouldShowField as s, toggleOption as t };
|
|
@@ -354,40 +354,4 @@ interface BetterCMSReadClient {
|
|
|
354
354
|
}
|
|
355
355
|
declare function createClient(options: CreateClientOptions): BetterCMSReadClient;
|
|
356
356
|
|
|
357
|
-
|
|
358
|
-
* Public site search — the headless surface for the visitor-facing search box.
|
|
359
|
-
*
|
|
360
|
-
* Queries `GET /api/v1/delivery/search?project=…&q=…`, the same key-less public
|
|
361
|
-
* endpoint the embeddable widget uses. Powers <BcmsSearch> in @bettercms-ai/next
|
|
362
|
-
* and @bettercms-ai/astro, so the component and any custom UI never drift.
|
|
363
|
-
*
|
|
364
|
-
* `snippet` is HTML-safe: the server escapes the field content and keeps only the
|
|
365
|
-
* <mark> highlight wrappers, so it can be rendered with dangerouslySetInnerHTML.
|
|
366
|
-
*/
|
|
367
|
-
interface SearchHit {
|
|
368
|
-
title: string;
|
|
369
|
-
slug: string;
|
|
370
|
-
type: "page" | "entry";
|
|
371
|
-
/** HTML-safe snippet with <mark> highlights around matched terms. */
|
|
372
|
-
snippet: string;
|
|
373
|
-
/** Site-relative URL, e.g. "/about". */
|
|
374
|
-
url: string;
|
|
375
|
-
}
|
|
376
|
-
interface SearchOptions {
|
|
377
|
-
/** The project id (from the dashboard's Site Search settings). */
|
|
378
|
-
project: string;
|
|
379
|
-
/** The query string. Calls with fewer than 1 char resolve to []. */
|
|
380
|
-
q: string;
|
|
381
|
-
/** Max hits, 1–20 (default 8). */
|
|
382
|
-
limit?: number;
|
|
383
|
-
/** API origin. Defaults to https://api.bettercms.ai. */
|
|
384
|
-
baseUrl?: string;
|
|
385
|
-
/** Injectable fetch for tests / non-browser runtimes. */
|
|
386
|
-
fetchImpl?: typeof fetch;
|
|
387
|
-
/** AbortSignal so callers can cancel a stale in-flight search. */
|
|
388
|
-
signal?: AbortSignal;
|
|
389
|
-
}
|
|
390
|
-
/** Search a published site. Resolves with hits, or [] on an empty query / network error. */
|
|
391
|
-
declare function search(opts: SearchOptions): Promise<SearchHit[]>;
|
|
392
|
-
|
|
393
|
-
export { BetterCMSError as B, type CreateClientOptions as C, type DeliveryFormField as D, ErrorCodes as E, type FormSubmitError as F, type GetEntryOptions as G, type ListEntriesOptions as L, type SearchHit as S, type BetterCMSReadClient as a, type DeliveryForm as b, type DeliveryFormFieldType as c, type FormValue as d, type FormValues as e, type ListPagesOptions as f, type SearchOptions as g, type SubmitFormOptions as h, type SubmitFormResult as i, asStringArray as j, createClient as k, formInitialValues as l, isMultiValueField as m, shouldShowField as n, submitForm as o, search as s, toggleOption as t };
|
|
357
|
+
export { type BetterCMSReadClient as B, type CreateClientOptions as C, type DeliveryFormField as D, ErrorCodes as E, type FormSubmitError as F, type GetEntryOptions as G, type ListEntriesOptions as L, type SubmitFormOptions as S, BetterCMSError as a, type DeliveryForm as b, type DeliveryFormFieldType as c, type FormValue as d, type FormValues as e, type ListPagesOptions as f, type SubmitFormResult as g, asStringArray as h, createClient as i, formInitialValues as j, isMultiValueField as k, submitForm as l, shouldShowField as s, toggleOption as t };
|
package/dist/read.d.cts
CHANGED
|
@@ -1,2 +1,40 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { B as BetterCMSReadClient, C as CreateClientOptions, b as DeliveryForm, D as DeliveryFormField, c as DeliveryFormFieldType, F as FormSubmitError, d as FormValue, e as FormValues, S as SubmitFormOptions, g as SubmitFormResult, h as asStringArray, i as createClient, j as formInitialValues, k as isMultiValueField, s as shouldShowField, l as submitForm, t as toggleOption } from './read-client-tNsCHy0p.cjs';
|
|
2
2
|
import '@bettercms-ai/types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Public site search — the headless surface for the visitor-facing search box.
|
|
6
|
+
*
|
|
7
|
+
* Queries `GET /api/v1/delivery/search?project=…&q=…`, the same key-less public
|
|
8
|
+
* endpoint the embeddable widget uses. Powers <BcmsSearch> in @bettercms-ai/next
|
|
9
|
+
* and @bettercms-ai/astro, so the component and any custom UI never drift.
|
|
10
|
+
*
|
|
11
|
+
* `snippet` is HTML-safe: the server escapes the field content and keeps only the
|
|
12
|
+
* <mark> highlight wrappers, so it can be rendered with dangerouslySetInnerHTML.
|
|
13
|
+
*/
|
|
14
|
+
interface SearchHit {
|
|
15
|
+
title: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
type: "page" | "entry";
|
|
18
|
+
/** HTML-safe snippet with <mark> highlights around matched terms. */
|
|
19
|
+
snippet: string;
|
|
20
|
+
/** Site-relative URL, e.g. "/about". */
|
|
21
|
+
url: string;
|
|
22
|
+
}
|
|
23
|
+
interface SearchOptions {
|
|
24
|
+
/** The project id (from the dashboard's Site Search settings). */
|
|
25
|
+
project: string;
|
|
26
|
+
/** The query string. Calls with fewer than 1 char resolve to []. */
|
|
27
|
+
q: string;
|
|
28
|
+
/** Max hits, 1–20 (default 8). */
|
|
29
|
+
limit?: number;
|
|
30
|
+
/** API origin. Defaults to https://api.bettercms.ai. */
|
|
31
|
+
baseUrl?: string;
|
|
32
|
+
/** Injectable fetch for tests / non-browser runtimes. */
|
|
33
|
+
fetchImpl?: typeof fetch;
|
|
34
|
+
/** AbortSignal so callers can cancel a stale in-flight search. */
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
}
|
|
37
|
+
/** Search a published site. Resolves with hits, or [] on an empty query / network error. */
|
|
38
|
+
declare function search(opts: SearchOptions): Promise<SearchHit[]>;
|
|
39
|
+
|
|
40
|
+
export { type SearchHit, type SearchOptions, search };
|
package/dist/read.d.ts
CHANGED
|
@@ -1,2 +1,40 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { B as BetterCMSReadClient, C as CreateClientOptions, b as DeliveryForm, D as DeliveryFormField, c as DeliveryFormFieldType, F as FormSubmitError, d as FormValue, e as FormValues, S as SubmitFormOptions, g as SubmitFormResult, h as asStringArray, i as createClient, j as formInitialValues, k as isMultiValueField, s as shouldShowField, l as submitForm, t as toggleOption } from './read-client-tNsCHy0p.js';
|
|
2
2
|
import '@bettercms-ai/types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Public site search — the headless surface for the visitor-facing search box.
|
|
6
|
+
*
|
|
7
|
+
* Queries `GET /api/v1/delivery/search?project=…&q=…`, the same key-less public
|
|
8
|
+
* endpoint the embeddable widget uses. Powers <BcmsSearch> in @bettercms-ai/next
|
|
9
|
+
* and @bettercms-ai/astro, so the component and any custom UI never drift.
|
|
10
|
+
*
|
|
11
|
+
* `snippet` is HTML-safe: the server escapes the field content and keeps only the
|
|
12
|
+
* <mark> highlight wrappers, so it can be rendered with dangerouslySetInnerHTML.
|
|
13
|
+
*/
|
|
14
|
+
interface SearchHit {
|
|
15
|
+
title: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
type: "page" | "entry";
|
|
18
|
+
/** HTML-safe snippet with <mark> highlights around matched terms. */
|
|
19
|
+
snippet: string;
|
|
20
|
+
/** Site-relative URL, e.g. "/about". */
|
|
21
|
+
url: string;
|
|
22
|
+
}
|
|
23
|
+
interface SearchOptions {
|
|
24
|
+
/** The project id (from the dashboard's Site Search settings). */
|
|
25
|
+
project: string;
|
|
26
|
+
/** The query string. Calls with fewer than 1 char resolve to []. */
|
|
27
|
+
q: string;
|
|
28
|
+
/** Max hits, 1–20 (default 8). */
|
|
29
|
+
limit?: number;
|
|
30
|
+
/** API origin. Defaults to https://api.bettercms.ai. */
|
|
31
|
+
baseUrl?: string;
|
|
32
|
+
/** Injectable fetch for tests / non-browser runtimes. */
|
|
33
|
+
fetchImpl?: typeof fetch;
|
|
34
|
+
/** AbortSignal so callers can cancel a stale in-flight search. */
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
}
|
|
37
|
+
/** Search a published site. Resolves with hits, or [] on an empty query / network error. */
|
|
38
|
+
declare function search(opts: SearchOptions): Promise<SearchHit[]>;
|
|
39
|
+
|
|
40
|
+
export { type SearchHit, type SearchOptions, search };
|