@bettercms-ai/sdk 1.12.0 → 1.13.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
@@ -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;
@@ -1081,7 +1095,11 @@ interface SeoInput {
1081
1095
  interface ResolvedSeo {
1082
1096
  title: string;
1083
1097
  description: string;
1084
- /** Canonical URL — page's own `canonical`, else "". Consumers may supply their own base. */
1098
+ /**
1099
+ * Canonical URL — the page's own `canonical`, else `siteUrl` + `path` when both are given,
1100
+ * else "". Empty means the consumer must omit the tag: a RELATIVE canonical is worse than
1101
+ * none at all.
1102
+ */
1085
1103
  canonical: string;
1086
1104
  og: {
1087
1105
  title: string;
@@ -1100,11 +1118,24 @@ interface ResolvedSeo {
1100
1118
  /** JSON-LD nodes (site schema first, then the page's), already flattened + de-empted. */
1101
1119
  jsonLd: Array<Record<string, unknown>>;
1102
1120
  }
1121
+ /** Where this page lives, so a canonical can be derived when the page sets none. */
1122
+ interface SeoLocation {
1123
+ /** Absolute site origin, e.g. `https://acme.com`. Trailing slash optional. */
1124
+ siteUrl?: string;
1125
+ /** This page's path, e.g. `/about` or `/`. */
1126
+ path?: string;
1127
+ }
1103
1128
  /**
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.
1129
+ * Merge a page's own SEO over the site defaults: page meta wins, then site defaults, then
1130
+ * sensible fallbacks.
1131
+ *
1132
+ * The CANONICAL is the one field that cannot be resolved from content alone, and getting this
1133
+ * wrong is silent. The hosted renderer (`mergeSeoMeta` in the backend) falls back to a canonical
1134
+ * it derives from the request's own origin and path — headless consumers have no request, so
1135
+ * they must pass `location`. Without it this returns "" and the caller ships no
1136
+ * `<link rel="canonical">` and no `og:url`, exactly as it did before `location` existed.
1106
1137
  */
1107
- declare function resolveSeo(page: SeoInput, defaults?: SiteSeoDefaults): ResolvedSeo;
1138
+ declare function resolveSeo(page: SeoInput, defaults?: SiteSeoDefaults, location?: SeoLocation): ResolvedSeo;
1108
1139
 
1109
1140
  /**
1110
1141
  * Public site search — the headless surface for the visitor-facing search box.
@@ -1326,4 +1357,4 @@ declare function inviteMember(client: BetterCMSAdminClient, workspaceId: string,
1326
1357
  declare function updateMemberRole(client: BetterCMSAdminClient, workspaceId: string, memberId: string, role: MemberRole): Promise<Member>;
1327
1358
  declare function removeMember(client: BetterCMSAdminClient, workspaceId: string, memberId: string): Promise<void>;
1328
1359
 
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 };
1360
+ 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;
@@ -1081,7 +1095,11 @@ interface SeoInput {
1081
1095
  interface ResolvedSeo {
1082
1096
  title: string;
1083
1097
  description: string;
1084
- /** Canonical URL — page's own `canonical`, else "". Consumers may supply their own base. */
1098
+ /**
1099
+ * Canonical URL — the page's own `canonical`, else `siteUrl` + `path` when both are given,
1100
+ * else "". Empty means the consumer must omit the tag: a RELATIVE canonical is worse than
1101
+ * none at all.
1102
+ */
1085
1103
  canonical: string;
1086
1104
  og: {
1087
1105
  title: string;
@@ -1100,11 +1118,24 @@ interface ResolvedSeo {
1100
1118
  /** JSON-LD nodes (site schema first, then the page's), already flattened + de-empted. */
1101
1119
  jsonLd: Array<Record<string, unknown>>;
1102
1120
  }
1121
+ /** Where this page lives, so a canonical can be derived when the page sets none. */
1122
+ interface SeoLocation {
1123
+ /** Absolute site origin, e.g. `https://acme.com`. Trailing slash optional. */
1124
+ siteUrl?: string;
1125
+ /** This page's path, e.g. `/about` or `/`. */
1126
+ path?: string;
1127
+ }
1103
1128
  /**
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.
1129
+ * Merge a page's own SEO over the site defaults: page meta wins, then site defaults, then
1130
+ * sensible fallbacks.
1131
+ *
1132
+ * The CANONICAL is the one field that cannot be resolved from content alone, and getting this
1133
+ * wrong is silent. The hosted renderer (`mergeSeoMeta` in the backend) falls back to a canonical
1134
+ * it derives from the request's own origin and path — headless consumers have no request, so
1135
+ * they must pass `location`. Without it this returns "" and the caller ships no
1136
+ * `<link rel="canonical">` and no `og:url`, exactly as it did before `location` existed.
1106
1137
  */
1107
- declare function resolveSeo(page: SeoInput, defaults?: SiteSeoDefaults): ResolvedSeo;
1138
+ declare function resolveSeo(page: SeoInput, defaults?: SiteSeoDefaults, location?: SeoLocation): ResolvedSeo;
1108
1139
 
1109
1140
  /**
1110
1141
  * Public site search — the headless surface for the visitor-facing search box.
@@ -1326,4 +1357,4 @@ declare function inviteMember(client: BetterCMSAdminClient, workspaceId: string,
1326
1357
  declare function updateMemberRole(client: BetterCMSAdminClient, workspaceId: string, memberId: string, role: MemberRole): Promise<Member>;
1327
1358
  declare function removeMember(client: BetterCMSAdminClient, workspaceId: string, memberId: string): Promise<void>;
1328
1359
 
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 };
1360
+ 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
@@ -1247,12 +1247,20 @@ function decodeEntities(s) {
1247
1247
  }
1248
1248
 
1249
1249
  // src/seo.ts
1250
+ function derivedCanonical({ siteUrl, path }) {
1251
+ if (!siteUrl || !path) return "";
1252
+ try {
1253
+ return new URL(path, siteUrl.endsWith("/") ? siteUrl : `${siteUrl}/`).toString();
1254
+ } catch {
1255
+ return "";
1256
+ }
1257
+ }
1250
1258
  function jsonLdNodes(schema) {
1251
1259
  if (!schema) return [];
1252
1260
  const arr = Array.isArray(schema) ? schema : [schema];
1253
1261
  return arr.filter((node) => node && Object.keys(node).length > 0);
1254
1262
  }
1255
- function resolveSeo(page, defaults = {}) {
1263
+ function resolveSeo(page, defaults = {}, location = {}) {
1256
1264
  const meta = page.metaJson ?? {};
1257
1265
  const title = page.metaTitle || page.title || "";
1258
1266
  const description = page.metaDescription || defaults.metaDescription || "";
@@ -1260,7 +1268,7 @@ function resolveSeo(page, defaults = {}) {
1260
1268
  const ogTitle = meta.og?.title || title;
1261
1269
  const ogDescription = meta.og?.description || description;
1262
1270
  const twImage = meta.twitter?.image || ogImage;
1263
- const canonical = meta.canonical || "";
1271
+ const canonical = meta.canonical || derivedCanonical(location);
1264
1272
  return {
1265
1273
  title,
1266
1274
  description,