@bettercms-ai/sdk 1.12.1 → 1.13.1

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
@@ -476,6 +476,20 @@ interface DeliveryFormField {
476
476
  field: string;
477
477
  equals: string;
478
478
  };
479
+ /**
480
+ * Per-field rules the API enforces on submit. Renderers should mirror them onto the
481
+ * native control (`min`/`max`/`pattern`) so the browser reports them before the round
482
+ * trip — the server check is the one that counts.
483
+ */
484
+ validation?: DeliveryFormFieldValidation;
485
+ }
486
+ /** @see FormFieldValidation in @bettercms-ai/types — same shape, delivered to the site. */
487
+ interface DeliveryFormFieldValidation {
488
+ emailPolicy?: "any" | "business";
489
+ min?: number;
490
+ max?: number;
491
+ phoneFormat?: "any" | "e164";
492
+ pattern?: string;
479
493
  }
480
494
  /** The pick-many type, whose value is an array rather than a scalar. */
481
495
  declare function isMultiValueField(type: DeliveryFormFieldType): boolean;
@@ -666,6 +680,7 @@ interface SeoMeta {
666
680
 
667
681
  type ManagedLayoutDocument = {
668
682
  scope: "global";
683
+ copy?: "draft" | "published";
669
684
  revision: number;
670
685
  etag: string;
671
686
  status: string;
@@ -673,6 +688,7 @@ type ManagedLayoutDocument = {
673
688
  data: LayoutDataDocument;
674
689
  } | {
675
690
  scope: "page";
691
+ copy?: "draft" | "published";
676
692
  pageId: string;
677
693
  pageSlug: string;
678
694
  revision: number;
@@ -689,6 +705,9 @@ interface ManagedLayoutClient {
689
705
  interface GetManagedLayoutOptions {
690
706
  scope?: "global" | "page";
691
707
  pageId?: string;
708
+ /** Which copy to read — default draft. A publish claim verifies ONLY against the
709
+ * published copy; check the response's `copy` echo (FLO-1188). */
710
+ copy?: "draft" | "published";
692
711
  /** Required when the management credential is workspace-scoped. */
693
712
  projectId?: string;
694
713
  }
@@ -1081,7 +1100,11 @@ interface SeoInput {
1081
1100
  interface ResolvedSeo {
1082
1101
  title: string;
1083
1102
  description: string;
1084
- /** Canonical URL — page's own `canonical`, else "". Consumers may supply their own base. */
1103
+ /**
1104
+ * Canonical URL — the page's own `canonical`, else `siteUrl` + `path` when both are given,
1105
+ * else "". Empty means the consumer must omit the tag: a RELATIVE canonical is worse than
1106
+ * none at all.
1107
+ */
1085
1108
  canonical: string;
1086
1109
  og: {
1087
1110
  title: string;
@@ -1100,11 +1123,24 @@ interface ResolvedSeo {
1100
1123
  /** JSON-LD nodes (site schema first, then the page's), already flattened + de-empted. */
1101
1124
  jsonLd: Array<Record<string, unknown>>;
1102
1125
  }
1126
+ /** Where this page lives, so a canonical can be derived when the page sets none. */
1127
+ interface SeoLocation {
1128
+ /** Absolute site origin, e.g. `https://acme.com`. Trailing slash optional. */
1129
+ siteUrl?: string;
1130
+ /** This page's path, e.g. `/about` or `/`. */
1131
+ path?: string;
1132
+ }
1103
1133
  /**
1104
- * Merge a page's own SEO over the site defaults. Identical precedence to the server's
1105
- * `mergeSeoMeta`: page meta wins, then site defaults, then sensible fallbacks.
1134
+ * Merge a page's own SEO over the site defaults: page meta wins, then site defaults, then
1135
+ * sensible fallbacks.
1136
+ *
1137
+ * The CANONICAL is the one field that cannot be resolved from content alone, and getting this
1138
+ * wrong is silent. The hosted renderer (`mergeSeoMeta` in the backend) falls back to a canonical
1139
+ * it derives from the request's own origin and path — headless consumers have no request, so
1140
+ * they must pass `location`. Without it this returns "" and the caller ships no
1141
+ * `<link rel="canonical">` and no `og:url`, exactly as it did before `location` existed.
1106
1142
  */
1107
- declare function resolveSeo(page: SeoInput, defaults?: SiteSeoDefaults): ResolvedSeo;
1143
+ declare function resolveSeo(page: SeoInput, defaults?: SiteSeoDefaults, location?: SeoLocation): ResolvedSeo;
1108
1144
 
1109
1145
  /**
1110
1146
  * Public site search — the headless surface for the visitor-facing search box.
@@ -1326,4 +1362,4 @@ declare function inviteMember(client: BetterCMSAdminClient, workspaceId: string,
1326
1362
  declare function updateMemberRole(client: BetterCMSAdminClient, workspaceId: string, memberId: string, role: MemberRole): Promise<Member>;
1327
1363
  declare function removeMember(client: BetterCMSAdminClient, workspaceId: string, memberId: string): Promise<void>;
1328
1364
 
1329
- 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 CommandManagedLayoutInput, type CreateApiKeyInput, type CreateClientOptions, type CreateEntryInput, type CreateFormInput, type CreateManagedPageInput, type CreateModelInput, type CreatePageInput, type CreateWorkspaceInput, type DeliveryForm, type DeliveryFormField, type DeliveryFormFieldType, ErrorCodes, type ExtractionCandidate, type FormFieldInput, type FormListOptions, type FormSubmitError, type FormValue, type FormValues, type GetEntryOptions, type GetManagedLayoutOptions, type ListContentAllOptions, type ListContentOptions, type ListEntriesFilter, type ListEntriesOptions, type ListPagesOptions, type ManagedComponent, type ManagedComponentInput, type ManagedContentEntry, type ManagedContentModel, type ManagedForm, type ManagedFormInput, type ManagedLayoutClient, type ManagedLayoutDocument, 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, asStringArray, commandManagedLayout, createApiKey, createClient, createEntry, createForm, createManagedPage, createModel, createPage, createWorkspace, decodeStega, deleteForm, deleteMedia, deletePage, deleteSubmission, deleteWorkspace, encodeStega, formInitialValues, getApiKeyUsage, getEntry, getForm, getManagedLayout, getManagedPage, getMedia, getMember, getModel, getPage, getSubmission, getWorkspace, inviteMember, isMultiValueField, 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, toggleOption, updateApiKey, updateEntry, updateForm, updateMemberRole, updateModel, updatePage, updateWorkspace, uploadMedia };
1365
+ 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 CommandManagedLayoutInput, type CreateApiKeyInput, type CreateClientOptions, type CreateEntryInput, type CreateFormInput, type CreateManagedPageInput, type CreateModelInput, type CreatePageInput, type CreateWorkspaceInput, type DeliveryForm, type DeliveryFormField, type DeliveryFormFieldType, ErrorCodes, type ExtractionCandidate, type FormFieldInput, type FormListOptions, type FormSubmitError, type FormValue, type FormValues, type GetEntryOptions, type GetManagedLayoutOptions, type ListContentAllOptions, type ListContentOptions, type ListEntriesFilter, type ListEntriesOptions, type ListPagesOptions, type ManagedComponent, type ManagedComponentInput, type ManagedContentEntry, type ManagedContentModel, type ManagedForm, type ManagedFormInput, type ManagedLayoutClient, type ManagedLayoutDocument, 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 SeoLocation, 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, asStringArray, commandManagedLayout, createApiKey, createClient, createEntry, createForm, createManagedPage, createModel, createPage, createWorkspace, decodeStega, deleteForm, deleteMedia, deletePage, deleteSubmission, deleteWorkspace, encodeStega, formInitialValues, getApiKeyUsage, getEntry, getForm, getManagedLayout, getManagedPage, getMedia, getMember, getModel, getPage, getSubmission, getWorkspace, inviteMember, isMultiValueField, 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, toggleOption, updateApiKey, updateEntry, updateForm, updateMemberRole, updateModel, updatePage, updateWorkspace, uploadMedia };
package/dist/index.d.ts CHANGED
@@ -476,6 +476,20 @@ interface DeliveryFormField {
476
476
  field: string;
477
477
  equals: string;
478
478
  };
479
+ /**
480
+ * Per-field rules the API enforces on submit. Renderers should mirror them onto the
481
+ * native control (`min`/`max`/`pattern`) so the browser reports them before the round
482
+ * trip — the server check is the one that counts.
483
+ */
484
+ validation?: DeliveryFormFieldValidation;
485
+ }
486
+ /** @see FormFieldValidation in @bettercms-ai/types — same shape, delivered to the site. */
487
+ interface DeliveryFormFieldValidation {
488
+ emailPolicy?: "any" | "business";
489
+ min?: number;
490
+ max?: number;
491
+ phoneFormat?: "any" | "e164";
492
+ pattern?: string;
479
493
  }
480
494
  /** The pick-many type, whose value is an array rather than a scalar. */
481
495
  declare function isMultiValueField(type: DeliveryFormFieldType): boolean;
@@ -666,6 +680,7 @@ interface SeoMeta {
666
680
 
667
681
  type ManagedLayoutDocument = {
668
682
  scope: "global";
683
+ copy?: "draft" | "published";
669
684
  revision: number;
670
685
  etag: string;
671
686
  status: string;
@@ -673,6 +688,7 @@ type ManagedLayoutDocument = {
673
688
  data: LayoutDataDocument;
674
689
  } | {
675
690
  scope: "page";
691
+ copy?: "draft" | "published";
676
692
  pageId: string;
677
693
  pageSlug: string;
678
694
  revision: number;
@@ -689,6 +705,9 @@ interface ManagedLayoutClient {
689
705
  interface GetManagedLayoutOptions {
690
706
  scope?: "global" | "page";
691
707
  pageId?: string;
708
+ /** Which copy to read — default draft. A publish claim verifies ONLY against the
709
+ * published copy; check the response's `copy` echo (FLO-1188). */
710
+ copy?: "draft" | "published";
692
711
  /** Required when the management credential is workspace-scoped. */
693
712
  projectId?: string;
694
713
  }
@@ -1081,7 +1100,11 @@ interface SeoInput {
1081
1100
  interface ResolvedSeo {
1082
1101
  title: string;
1083
1102
  description: string;
1084
- /** Canonical URL — page's own `canonical`, else "". Consumers may supply their own base. */
1103
+ /**
1104
+ * Canonical URL — the page's own `canonical`, else `siteUrl` + `path` when both are given,
1105
+ * else "". Empty means the consumer must omit the tag: a RELATIVE canonical is worse than
1106
+ * none at all.
1107
+ */
1085
1108
  canonical: string;
1086
1109
  og: {
1087
1110
  title: string;
@@ -1100,11 +1123,24 @@ interface ResolvedSeo {
1100
1123
  /** JSON-LD nodes (site schema first, then the page's), already flattened + de-empted. */
1101
1124
  jsonLd: Array<Record<string, unknown>>;
1102
1125
  }
1126
+ /** Where this page lives, so a canonical can be derived when the page sets none. */
1127
+ interface SeoLocation {
1128
+ /** Absolute site origin, e.g. `https://acme.com`. Trailing slash optional. */
1129
+ siteUrl?: string;
1130
+ /** This page's path, e.g. `/about` or `/`. */
1131
+ path?: string;
1132
+ }
1103
1133
  /**
1104
- * Merge a page's own SEO over the site defaults. Identical precedence to the server's
1105
- * `mergeSeoMeta`: page meta wins, then site defaults, then sensible fallbacks.
1134
+ * Merge a page's own SEO over the site defaults: page meta wins, then site defaults, then
1135
+ * sensible fallbacks.
1136
+ *
1137
+ * The CANONICAL is the one field that cannot be resolved from content alone, and getting this
1138
+ * wrong is silent. The hosted renderer (`mergeSeoMeta` in the backend) falls back to a canonical
1139
+ * it derives from the request's own origin and path — headless consumers have no request, so
1140
+ * they must pass `location`. Without it this returns "" and the caller ships no
1141
+ * `<link rel="canonical">` and no `og:url`, exactly as it did before `location` existed.
1106
1142
  */
1107
- declare function resolveSeo(page: SeoInput, defaults?: SiteSeoDefaults): ResolvedSeo;
1143
+ declare function resolveSeo(page: SeoInput, defaults?: SiteSeoDefaults, location?: SeoLocation): ResolvedSeo;
1108
1144
 
1109
1145
  /**
1110
1146
  * Public site search — the headless surface for the visitor-facing search box.
@@ -1326,4 +1362,4 @@ declare function inviteMember(client: BetterCMSAdminClient, workspaceId: string,
1326
1362
  declare function updateMemberRole(client: BetterCMSAdminClient, workspaceId: string, memberId: string, role: MemberRole): Promise<Member>;
1327
1363
  declare function removeMember(client: BetterCMSAdminClient, workspaceId: string, memberId: string): Promise<void>;
1328
1364
 
1329
- 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 CommandManagedLayoutInput, type CreateApiKeyInput, type CreateClientOptions, type CreateEntryInput, type CreateFormInput, type CreateManagedPageInput, type CreateModelInput, type CreatePageInput, type CreateWorkspaceInput, type DeliveryForm, type DeliveryFormField, type DeliveryFormFieldType, ErrorCodes, type ExtractionCandidate, type FormFieldInput, type FormListOptions, type FormSubmitError, type FormValue, type FormValues, type GetEntryOptions, type GetManagedLayoutOptions, type ListContentAllOptions, type ListContentOptions, type ListEntriesFilter, type ListEntriesOptions, type ListPagesOptions, type ManagedComponent, type ManagedComponentInput, type ManagedContentEntry, type ManagedContentModel, type ManagedForm, type ManagedFormInput, type ManagedLayoutClient, type ManagedLayoutDocument, 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, asStringArray, commandManagedLayout, createApiKey, createClient, createEntry, createForm, createManagedPage, createModel, createPage, createWorkspace, decodeStega, deleteForm, deleteMedia, deletePage, deleteSubmission, deleteWorkspace, encodeStega, formInitialValues, getApiKeyUsage, getEntry, getForm, getManagedLayout, getManagedPage, getMedia, getMember, getModel, getPage, getSubmission, getWorkspace, inviteMember, isMultiValueField, 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, toggleOption, updateApiKey, updateEntry, updateForm, updateMemberRole, updateModel, updatePage, updateWorkspace, uploadMedia };
1365
+ 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 CommandManagedLayoutInput, type CreateApiKeyInput, type CreateClientOptions, type CreateEntryInput, type CreateFormInput, type CreateManagedPageInput, type CreateModelInput, type CreatePageInput, type CreateWorkspaceInput, type DeliveryForm, type DeliveryFormField, type DeliveryFormFieldType, ErrorCodes, type ExtractionCandidate, type FormFieldInput, type FormListOptions, type FormSubmitError, type FormValue, type FormValues, type GetEntryOptions, type GetManagedLayoutOptions, type ListContentAllOptions, type ListContentOptions, type ListEntriesFilter, type ListEntriesOptions, type ListPagesOptions, type ManagedComponent, type ManagedComponentInput, type ManagedContentEntry, type ManagedContentModel, type ManagedForm, type ManagedFormInput, type ManagedLayoutClient, type ManagedLayoutDocument, 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 SeoLocation, 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, asStringArray, commandManagedLayout, createApiKey, createClient, createEntry, createForm, createManagedPage, createModel, createPage, createWorkspace, decodeStega, deleteForm, deleteMedia, deletePage, deleteSubmission, deleteWorkspace, encodeStega, formInitialValues, getApiKeyUsage, getEntry, getForm, getManagedLayout, getManagedPage, getMedia, getMember, getModel, getPage, getSubmission, getWorkspace, inviteMember, isMultiValueField, 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, toggleOption, updateApiKey, updateEntry, updateForm, updateMemberRole, updateModel, updatePage, updateWorkspace, uploadMedia };
package/dist/index.js CHANGED
@@ -717,6 +717,7 @@ async function getManagedLayout(client, opts) {
717
717
  const query = new URLSearchParams();
718
718
  if (opts?.scope) query.set("scope", opts.scope);
719
719
  if (opts?.pageId) query.set("pageId", opts.pageId);
720
+ if (opts?.copy) query.set("preview", opts.copy);
720
721
  const result = await client.fetchJSON(
721
722
  client.url(`/management/layout${query.size ? `?${query}` : ""}`),
722
723
  opts?.projectId ? { headers: { "X-BCMS-Project": opts.projectId } } : void 0
@@ -1247,12 +1248,20 @@ function decodeEntities(s) {
1247
1248
  }
1248
1249
 
1249
1250
  // src/seo.ts
1251
+ function derivedCanonical({ siteUrl, path }) {
1252
+ if (!siteUrl || !path) return "";
1253
+ try {
1254
+ return new URL(path, siteUrl.endsWith("/") ? siteUrl : `${siteUrl}/`).toString();
1255
+ } catch {
1256
+ return "";
1257
+ }
1258
+ }
1250
1259
  function jsonLdNodes(schema) {
1251
1260
  if (!schema) return [];
1252
1261
  const arr = Array.isArray(schema) ? schema : [schema];
1253
1262
  return arr.filter((node) => node && Object.keys(node).length > 0);
1254
1263
  }
1255
- function resolveSeo(page, defaults = {}) {
1264
+ function resolveSeo(page, defaults = {}, location = {}) {
1256
1265
  const meta = page.metaJson ?? {};
1257
1266
  const title = page.metaTitle || page.title || "";
1258
1267
  const description = page.metaDescription || defaults.metaDescription || "";
@@ -1260,7 +1269,7 @@ function resolveSeo(page, defaults = {}) {
1260
1269
  const ogTitle = meta.og?.title || title;
1261
1270
  const ogDescription = meta.og?.description || description;
1262
1271
  const twImage = meta.twitter?.image || ogImage;
1263
- const canonical = meta.canonical || "";
1272
+ const canonical = meta.canonical || derivedCanonical(location);
1264
1273
  return {
1265
1274
  title,
1266
1275
  description,