@bettercms-ai/sdk 1.8.1 → 1.9.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/index.d.cts CHANGED
@@ -850,6 +850,60 @@ declare function decodeStega(value: string): {
850
850
  /** Remove any stega payload, returning the clean string. */
851
851
  declare function stripStega(value: string): string;
852
852
 
853
+ /**
854
+ * Reading a rich-text field, for any site.
855
+ *
856
+ * A `text` field delivers a bare string. A `richtext` field delivers the canonical envelope
857
+ * `{ format, value, html }`. Those are different shapes, and switching a field's type in the
858
+ * CMS switches which one your site receives — the Visual Editor offers exactly that switch,
859
+ * so it happens on live projects. A template that interpolates the value directly then
860
+ * renders `[object Object]` (or, in React, throws `Objects are not valid as a React child`).
861
+ *
862
+ * No payload can be both structured and safe to interpolate, which is why every headless CMS
863
+ * requires a renderer for rich text. These are ours:
864
+ *
865
+ * plain(v) → text, for <title>, meta descriptions, JSON-LD, alt text, aria-label
866
+ * rich(v) → HTML, for `set:html` / `dangerouslySetInnerHTML`
867
+ *
868
+ * Both accept a bare string, so a field can be switched in the CMS without touching the site.
869
+ *
870
+ * <h1 set:html={rich(page.heroTitle)} /> // Astro
871
+ * <h1 dangerouslySetInnerHTML={{ __html: rich(f.heroTitle) }}/> // React
872
+ * seo({ title: plain(page.heroTitle) })
873
+ */
874
+ /** The canonical rich-text envelope returned by the Delivery API. */
875
+ interface RichTextValue {
876
+ readonly format?: string;
877
+ readonly value?: unknown;
878
+ /** Server-rendered, sanitized HTML. Computed on write. */
879
+ readonly html?: string;
880
+ }
881
+ /**
882
+ * A field that may arrive as either shape. Type author-editable text fields with this and the
883
+ * compiler routes you through `plain()`/`rich()`, so a type switch in the CMS can never reach
884
+ * production as `[object Object]`.
885
+ */
886
+ type TextOrRich = string | RichTextValue | null | undefined;
887
+ /** True when the value is a rich-text envelope rather than a bare string. */
888
+ declare function isRichText(value: unknown): value is RichTextValue;
889
+ /**
890
+ * Plain text for an attribute context — `<title>`, meta description, JSON-LD, `alt`.
891
+ * Strips tags and decodes the entities that survive stripping, so the result is text the
892
+ * consumer can escape once without double-escaping.
893
+ */
894
+ declare function plain(value: TextOrRich): string;
895
+ /**
896
+ * Renderable HTML. Rich text keeps its inline marks (the server sanitizes `html` on write);
897
+ * a bare string is escaped, so a plain field can never inject markup.
898
+ *
899
+ * A lone wrapping block is unwrapped: a field switched from `text` stores its value as
900
+ * `<p>…</p>`, but it renders inside the element that already is the block — and
901
+ * `<h1><p>…</p></h1>` is invalid, the parser closes the heading at the `<p>` and the text
902
+ * falls out of it. Values with real block structure (several paragraphs, a list) are left
903
+ * alone, so a body field still renders as a document.
904
+ */
905
+ declare function rich(value: TextOrRich, fallback?: string): string;
906
+
853
907
  /**
854
908
  * resolveSeo — framework-agnostic page-over-site SEO merge for headless consumers.
855
909
  *
@@ -1121,4 +1175,4 @@ declare function inviteMember(client: BetterCMSAdminClient, workspaceId: string,
1121
1175
  declare function updateMemberRole(client: BetterCMSAdminClient, workspaceId: string, memberId: string, role: MemberRole): Promise<Member>;
1122
1176
  declare function removeMember(client: BetterCMSAdminClient, workspaceId: string, memberId: string): Promise<void>;
1123
1177
 
1124
- export { type AddPageFieldsInput, type ApiKeyUsage, type ApiKeyWithRaw, type AuthResult, BetterCMSError as BCMSClientError, ErrorCodes as BCMSErrorCodes, BetterCMS, BetterCMSAdminClient, type BetterCMSAdminOptions, BetterCMSDeliveryClient, BetterCMSError, BetterCMSManagementClient, type BetterCMSManagementOptions, type BetterCMSReadClient, type BetterCMSSiteOptions, type CreateApiKeyInput, type CreateClientOptions, type CreateEntryInput, type CreateFormInput, type CreateManagedPageInput, type CreateModelInput, type CreatePageInput, type CreateWorkspaceInput, type DeliveryForm, type DeliveryFormField, type DeliveryFormFieldType, ErrorCodes, type FormFieldInput, type FormListOptions, type FormSubmitError, type FormValue, type FormValues, type GetEntryOptions, type ListContentAllOptions, type ListContentOptions, type ListEntriesFilter, type ListEntriesOptions, type ListPagesOptions, type ManagedComponent, type ManagedComponentInput, type ManagedContentEntry, type ManagedContentModel, type ManagedForm, type ManagedFormInput, type ManagedPage, type ManagementClient, type MediaListOptions, type MediaMetadata, type Member, type MemberRole, type PageListOptions, type PendingInvite, type ResolvedSeo, type SearchHit, type SearchOptions, type SeoInput, type SeoMeta, type SeoMetaInput, type SetPageContentInput, type StegaPayload, type SubmissionListOptions, type SubmitFormOptions, type SubmitFormResult, type UpdateApiKeyInput, type UpdateEntryInput, type UpdateFormInput, type UpdateModelInput, type UpdatePageInput, type UpdateWorkspaceInput, type UploadAssetInput, type UploadedAsset, type WriteContentInput, addModelFields, addPageFields, createApiKey, createClient, createEntry, createForm, createManagedPage, createModel, createPage, createWorkspace, decodeStega, deleteForm, deleteMedia, deletePage, deleteSubmission, deleteWorkspace, encodeStega, formInitialValues, getApiKeyUsage, getEntry, getForm, getManagedPage, getMedia, getMember, getModel, getPage, getSubmission, getWorkspace, inviteMember, listApiKeys, listEntries, listForms, listManagedPages, listMedia, listMembers, listModels, listPages, listSubmissions, listWorkspaces, publishPage, regenerateApiKey, removeMember, resolveSeo, revokeApiKey, search, setPageContent, shouldShowField, signIn, signInWithGithub, signInWithGoogle, signUp, stripStega, submitForm, updateApiKey, updateEntry, updateForm, updateMemberRole, updateModel, updatePage, updateWorkspace, uploadMedia };
1178
+ export { type AddPageFieldsInput, type ApiKeyUsage, type ApiKeyWithRaw, type AuthResult, BetterCMSError as BCMSClientError, ErrorCodes as BCMSErrorCodes, BetterCMS, BetterCMSAdminClient, type BetterCMSAdminOptions, BetterCMSDeliveryClient, BetterCMSError, BetterCMSManagementClient, type BetterCMSManagementOptions, type BetterCMSReadClient, type BetterCMSSiteOptions, type CreateApiKeyInput, type CreateClientOptions, type CreateEntryInput, type CreateFormInput, type CreateManagedPageInput, type CreateModelInput, type CreatePageInput, type CreateWorkspaceInput, type DeliveryForm, type DeliveryFormField, type DeliveryFormFieldType, ErrorCodes, type FormFieldInput, type FormListOptions, type FormSubmitError, type FormValue, type FormValues, type GetEntryOptions, type ListContentAllOptions, type ListContentOptions, type ListEntriesFilter, type ListEntriesOptions, type ListPagesOptions, type ManagedComponent, type ManagedComponentInput, type ManagedContentEntry, type ManagedContentModel, type ManagedForm, type ManagedFormInput, type ManagedPage, type ManagementClient, type MediaListOptions, type MediaMetadata, type Member, type MemberRole, type PageListOptions, type PendingInvite, type ResolvedSeo, type RichTextValue, type SearchHit, type SearchOptions, type SeoInput, type SeoMeta, type SeoMetaInput, type SetPageContentInput, type StegaPayload, type SubmissionListOptions, type SubmitFormOptions, type SubmitFormResult, type TextOrRich, type UpdateApiKeyInput, type UpdateEntryInput, type UpdateFormInput, type UpdateModelInput, type UpdatePageInput, type UpdateWorkspaceInput, type UploadAssetInput, type UploadedAsset, type WriteContentInput, addModelFields, addPageFields, createApiKey, createClient, createEntry, createForm, createManagedPage, createModel, createPage, createWorkspace, decodeStega, deleteForm, deleteMedia, deletePage, deleteSubmission, deleteWorkspace, encodeStega, formInitialValues, getApiKeyUsage, getEntry, getForm, getManagedPage, getMedia, getMember, getModel, getPage, getSubmission, getWorkspace, inviteMember, isRichText, listApiKeys, listEntries, listForms, listManagedPages, listMedia, listMembers, listModels, listPages, listSubmissions, listWorkspaces, plain, publishPage, regenerateApiKey, removeMember, resolveSeo, revokeApiKey, rich, search, setPageContent, shouldShowField, signIn, signInWithGithub, signInWithGoogle, signUp, stripStega, submitForm, updateApiKey, updateEntry, updateForm, updateMemberRole, updateModel, updatePage, updateWorkspace, uploadMedia };
package/dist/index.d.ts CHANGED
@@ -850,6 +850,60 @@ declare function decodeStega(value: string): {
850
850
  /** Remove any stega payload, returning the clean string. */
851
851
  declare function stripStega(value: string): string;
852
852
 
853
+ /**
854
+ * Reading a rich-text field, for any site.
855
+ *
856
+ * A `text` field delivers a bare string. A `richtext` field delivers the canonical envelope
857
+ * `{ format, value, html }`. Those are different shapes, and switching a field's type in the
858
+ * CMS switches which one your site receives — the Visual Editor offers exactly that switch,
859
+ * so it happens on live projects. A template that interpolates the value directly then
860
+ * renders `[object Object]` (or, in React, throws `Objects are not valid as a React child`).
861
+ *
862
+ * No payload can be both structured and safe to interpolate, which is why every headless CMS
863
+ * requires a renderer for rich text. These are ours:
864
+ *
865
+ * plain(v) → text, for <title>, meta descriptions, JSON-LD, alt text, aria-label
866
+ * rich(v) → HTML, for `set:html` / `dangerouslySetInnerHTML`
867
+ *
868
+ * Both accept a bare string, so a field can be switched in the CMS without touching the site.
869
+ *
870
+ * <h1 set:html={rich(page.heroTitle)} /> // Astro
871
+ * <h1 dangerouslySetInnerHTML={{ __html: rich(f.heroTitle) }}/> // React
872
+ * seo({ title: plain(page.heroTitle) })
873
+ */
874
+ /** The canonical rich-text envelope returned by the Delivery API. */
875
+ interface RichTextValue {
876
+ readonly format?: string;
877
+ readonly value?: unknown;
878
+ /** Server-rendered, sanitized HTML. Computed on write. */
879
+ readonly html?: string;
880
+ }
881
+ /**
882
+ * A field that may arrive as either shape. Type author-editable text fields with this and the
883
+ * compiler routes you through `plain()`/`rich()`, so a type switch in the CMS can never reach
884
+ * production as `[object Object]`.
885
+ */
886
+ type TextOrRich = string | RichTextValue | null | undefined;
887
+ /** True when the value is a rich-text envelope rather than a bare string. */
888
+ declare function isRichText(value: unknown): value is RichTextValue;
889
+ /**
890
+ * Plain text for an attribute context — `<title>`, meta description, JSON-LD, `alt`.
891
+ * Strips tags and decodes the entities that survive stripping, so the result is text the
892
+ * consumer can escape once without double-escaping.
893
+ */
894
+ declare function plain(value: TextOrRich): string;
895
+ /**
896
+ * Renderable HTML. Rich text keeps its inline marks (the server sanitizes `html` on write);
897
+ * a bare string is escaped, so a plain field can never inject markup.
898
+ *
899
+ * A lone wrapping block is unwrapped: a field switched from `text` stores its value as
900
+ * `<p>…</p>`, but it renders inside the element that already is the block — and
901
+ * `<h1><p>…</p></h1>` is invalid, the parser closes the heading at the `<p>` and the text
902
+ * falls out of it. Values with real block structure (several paragraphs, a list) are left
903
+ * alone, so a body field still renders as a document.
904
+ */
905
+ declare function rich(value: TextOrRich, fallback?: string): string;
906
+
853
907
  /**
854
908
  * resolveSeo — framework-agnostic page-over-site SEO merge for headless consumers.
855
909
  *
@@ -1121,4 +1175,4 @@ declare function inviteMember(client: BetterCMSAdminClient, workspaceId: string,
1121
1175
  declare function updateMemberRole(client: BetterCMSAdminClient, workspaceId: string, memberId: string, role: MemberRole): Promise<Member>;
1122
1176
  declare function removeMember(client: BetterCMSAdminClient, workspaceId: string, memberId: string): Promise<void>;
1123
1177
 
1124
- export { type AddPageFieldsInput, type ApiKeyUsage, type ApiKeyWithRaw, type AuthResult, BetterCMSError as BCMSClientError, ErrorCodes as BCMSErrorCodes, BetterCMS, BetterCMSAdminClient, type BetterCMSAdminOptions, BetterCMSDeliveryClient, BetterCMSError, BetterCMSManagementClient, type BetterCMSManagementOptions, type BetterCMSReadClient, type BetterCMSSiteOptions, type CreateApiKeyInput, type CreateClientOptions, type CreateEntryInput, type CreateFormInput, type CreateManagedPageInput, type CreateModelInput, type CreatePageInput, type CreateWorkspaceInput, type DeliveryForm, type DeliveryFormField, type DeliveryFormFieldType, ErrorCodes, type FormFieldInput, type FormListOptions, type FormSubmitError, type FormValue, type FormValues, type GetEntryOptions, type ListContentAllOptions, type ListContentOptions, type ListEntriesFilter, type ListEntriesOptions, type ListPagesOptions, type ManagedComponent, type ManagedComponentInput, type ManagedContentEntry, type ManagedContentModel, type ManagedForm, type ManagedFormInput, type ManagedPage, type ManagementClient, type MediaListOptions, type MediaMetadata, type Member, type MemberRole, type PageListOptions, type PendingInvite, type ResolvedSeo, type SearchHit, type SearchOptions, type SeoInput, type SeoMeta, type SeoMetaInput, type SetPageContentInput, type StegaPayload, type SubmissionListOptions, type SubmitFormOptions, type SubmitFormResult, type UpdateApiKeyInput, type UpdateEntryInput, type UpdateFormInput, type UpdateModelInput, type UpdatePageInput, type UpdateWorkspaceInput, type UploadAssetInput, type UploadedAsset, type WriteContentInput, addModelFields, addPageFields, createApiKey, createClient, createEntry, createForm, createManagedPage, createModel, createPage, createWorkspace, decodeStega, deleteForm, deleteMedia, deletePage, deleteSubmission, deleteWorkspace, encodeStega, formInitialValues, getApiKeyUsage, getEntry, getForm, getManagedPage, getMedia, getMember, getModel, getPage, getSubmission, getWorkspace, inviteMember, listApiKeys, listEntries, listForms, listManagedPages, listMedia, listMembers, listModels, listPages, listSubmissions, listWorkspaces, publishPage, regenerateApiKey, removeMember, resolveSeo, revokeApiKey, search, setPageContent, shouldShowField, signIn, signInWithGithub, signInWithGoogle, signUp, stripStega, submitForm, updateApiKey, updateEntry, updateForm, updateMemberRole, updateModel, updatePage, updateWorkspace, uploadMedia };
1178
+ export { type AddPageFieldsInput, type ApiKeyUsage, type ApiKeyWithRaw, type AuthResult, BetterCMSError as BCMSClientError, ErrorCodes as BCMSErrorCodes, BetterCMS, BetterCMSAdminClient, type BetterCMSAdminOptions, BetterCMSDeliveryClient, BetterCMSError, BetterCMSManagementClient, type BetterCMSManagementOptions, type BetterCMSReadClient, type BetterCMSSiteOptions, type CreateApiKeyInput, type CreateClientOptions, type CreateEntryInput, type CreateFormInput, type CreateManagedPageInput, type CreateModelInput, type CreatePageInput, type CreateWorkspaceInput, type DeliveryForm, type DeliveryFormField, type DeliveryFormFieldType, ErrorCodes, type FormFieldInput, type FormListOptions, type FormSubmitError, type FormValue, type FormValues, type GetEntryOptions, type ListContentAllOptions, type ListContentOptions, type ListEntriesFilter, type ListEntriesOptions, type ListPagesOptions, type ManagedComponent, type ManagedComponentInput, type ManagedContentEntry, type ManagedContentModel, type ManagedForm, type ManagedFormInput, type ManagedPage, type ManagementClient, type MediaListOptions, type MediaMetadata, type Member, type MemberRole, type PageListOptions, type PendingInvite, type ResolvedSeo, type RichTextValue, type SearchHit, type SearchOptions, type SeoInput, type SeoMeta, type SeoMetaInput, type SetPageContentInput, type StegaPayload, type SubmissionListOptions, type SubmitFormOptions, type SubmitFormResult, type TextOrRich, type UpdateApiKeyInput, type UpdateEntryInput, type UpdateFormInput, type UpdateModelInput, type UpdatePageInput, type UpdateWorkspaceInput, type UploadAssetInput, type UploadedAsset, type WriteContentInput, addModelFields, addPageFields, createApiKey, createClient, createEntry, createForm, createManagedPage, createModel, createPage, createWorkspace, decodeStega, deleteForm, deleteMedia, deletePage, deleteSubmission, deleteWorkspace, encodeStega, formInitialValues, getApiKeyUsage, getEntry, getForm, getManagedPage, getMedia, getMember, getModel, getPage, getSubmission, getWorkspace, inviteMember, isRichText, listApiKeys, listEntries, listForms, listManagedPages, listMedia, listMembers, listModels, listPages, listSubmissions, listWorkspaces, plain, publishPage, regenerateApiKey, removeMember, resolveSeo, revokeApiKey, rich, search, setPageContent, shouldShowField, signIn, signInWithGithub, signInWithGoogle, signUp, stripStega, submitForm, updateApiKey, updateEntry, updateForm, updateMemberRole, updateModel, updatePage, updateWorkspace, uploadMedia };
package/dist/index.js CHANGED
@@ -1150,6 +1150,30 @@ function stripStega(value) {
1150
1150
  return value.replace(FRAME, "");
1151
1151
  }
1152
1152
 
1153
+ // src/richtext.ts
1154
+ function isRichText(value) {
1155
+ return typeof value === "object" && value !== null && !Array.isArray(value) && ("html" in value || "format" in value);
1156
+ }
1157
+ function plain(value) {
1158
+ if (typeof value === "string") return value;
1159
+ if (!isRichText(value) || typeof value.html !== "string") return "";
1160
+ return decodeEntities(value.html.replace(/<[^>]+>/g, "")).trim();
1161
+ }
1162
+ function rich(value, fallback = "") {
1163
+ const html = (typeof value === "string" ? escapeHtml(value) : isRichText(value) ? value.html ?? "" : "").trim();
1164
+ return html ? unwrapLoneBlock(html) : escapeHtml(fallback);
1165
+ }
1166
+ function unwrapLoneBlock(html) {
1167
+ const m = html.match(/^<(p|div|h[1-6])(?:\s[^>]*)?>([\s\S]*)<\/\1>$/i);
1168
+ return m && !new RegExp(`</${m[1]}>`, "i").test(m[2]) ? m[2] : html;
1169
+ }
1170
+ function escapeHtml(s) {
1171
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1172
+ }
1173
+ function decodeEntities(s) {
1174
+ return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#0?39;/g, "'").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&");
1175
+ }
1176
+
1153
1177
  // src/seo.ts
1154
1178
  function jsonLdNodes(schema) {
1155
1179
  if (!schema) return [];
@@ -1522,6 +1546,7 @@ export {
1522
1546
  default2 as imageUrlBuilder,
1523
1547
  inviteMember,
1524
1548
  isBlock,
1549
+ isRichText,
1525
1550
  listApiKeys,
1526
1551
  listEntries,
1527
1552
  listForms2 as listForms,
@@ -1532,11 +1557,13 @@ export {
1532
1557
  listPages,
1533
1558
  listSubmissions,
1534
1559
  listWorkspaces,
1560
+ plain,
1535
1561
  publishPage,
1536
1562
  regenerateApiKey,
1537
1563
  removeMember,
1538
1564
  resolveSeo,
1539
1565
  revokeApiKey,
1566
+ rich,
1540
1567
  search,
1541
1568
  setPageContent,
1542
1569
  shouldShowField,