busabase-cms-sdk 0.1.3 → 0.1.5

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.
@@ -0,0 +1,45 @@
1
+ //#region src/fallback.d.ts
2
+ /**
3
+ * The "read from Busabase, fall back to bundled content on any failure" wrapper — every app
4
+ * consuming busabase-cms re-implemented this same try/catch/console.warn a couple of times
5
+ * (once for list-shaped reads, once for single-record reads). One generic version, one place
6
+ * to change the log format.
7
+ */
8
+ declare const readCmsOrFallback: <T>(operation: (() => Promise<T>) | null | undefined, fallback: T, label: string) => Promise<T>;
9
+ /**
10
+ * The answer a read got, when the caller needs to act on it.
11
+ *
12
+ * `readCmsOrFallback` deliberately folds "unreachable" into "empty", which is the
13
+ * right default for rendering — a marketing page should not 500 because the
14
+ * workspace behind it blinked. It is the wrong answer the moment a page turns it
15
+ * into a claim: an unreachable CMS then renders as a 404, and whatever caching
16
+ * sits in front of it persists that as "this content was deleted".
17
+ *
18
+ * Reads whose result decides a `notFound()` should go through `readCmsStatus`
19
+ * instead and be told which answer they actually received.
20
+ */
21
+ type CmsRead<T> = {
22
+ status: "ok";
23
+ data: T;
24
+ } | {
25
+ status: "unavailable";
26
+ };
27
+ declare const readCmsStatus: <T>(operation: (() => Promise<T>) | null | undefined,
28
+ /**
29
+ * What "nothing here" looks like for this read.
30
+ *
31
+ * Returned as a SUCCESSFUL read when the integration is simply not configured:
32
+ * an app with no CMS wired up is not broken, it just has no CMS content, and
33
+ * turning that into an error breaks first-run and CMS-off deployments.
34
+ */
35
+ whenUnconfigured: T, label: string) => Promise<CmsRead<T>>;
36
+ /**
37
+ * Unwrap a read, or fail loudly.
38
+ *
39
+ * Throwing beats rendering a 404: an error response is not cached, so the page
40
+ * recovers on the next request, whereas a cached 404 tells crawlers the content
41
+ * is gone.
42
+ */
43
+ declare const requireCmsRead: <T>(read: CmsRead<T>, what: string) => T;
44
+ //#endregion
45
+ export { requireCmsRead as i, readCmsOrFallback as n, readCmsStatus as r, CmsRead as t };
package/dist/index.d.ts CHANGED
@@ -1,14 +1,6 @@
1
1
  import { C as taxonomyFieldsDTOSchema, S as tagVOSchema, _ as pageVOSchema, a as PageFieldsDTO, b as relationFieldsDTOSchema, c as PostVO, d as TagVO, f as attachmentFieldsDTOSchema, g as pageFieldsDTOSchema, h as categoryVOSchema, i as CategoryVO, l as RelationFieldsDTO, m as categoryFieldsDTOSchema, n as AttachmentVO, o as PageVO, p as attachmentVOSchema, r as CategoryFieldsDTO, s as PostFieldsDTO, t as AttachmentFieldsDTO, u as TagFieldsDTO, v as postFieldsDTOSchema, x as tagFieldsDTOSchema, y as postVOSchema } from "./types-QKFW2jvT.js";
2
2
  import { $ as BusabaseCmsSetupError, A as BUSABASE_CMS_SCHEMA_PROFILES, B as i18nName, C as createBusabaseCms, D as mapPublishedPostRecord, E as mapPublishedPageRecord, F as BusabaseCmsFieldDefinition, G as BusabaseCmsFieldOptions, H as BusabaseCmsBase, I as BusabaseCmsFieldsOverride, J as BusabaseCmsSource, K as BusabaseCmsNode, L as BusabaseCmsFolderMetadata, M as BusabaseCmsBaseDefinition, N as BusabaseCmsBaseIds, O as BUSABASE_CMS_METADATA_KEY, P as BusabaseCmsBaseRole, Q as BusabaseCmsSchemaDriftError, R as BusabaseCmsSchemaProfile, S as InvalidCmsRecordIssue, T as mapActiveTagRecord, U as BusabaseCmsClient, V as replaceField, W as BusabaseCmsField, X as createBusabaseCmsSourceFromConfig, Y as createBusabaseCmsSource, Z as BusabaseCmsError, _ as CmsRecordKind, a as buildCmsCanonicalPath, b as DEFAULT_POSTS_BASE_SLUG, c as filterCmsPostsByTaxonomy, d as normalizeCmsPath, f as parseCmsCanonicalPath, g as BusabaseCmsTaxonomyCollection, h as BusabaseCmsPathCollection, i as CmsTaxonomyKind, j as BUSABASE_CMS_SCHEMA_VERSION, k as BUSABASE_CMS_ROLES, l as isCmsBlogPostPath, m as BusabaseCmsOptions, n as CmsCanonicalPathOptions, o as buildCmsTaxonomyArchivePath, p as BusabaseCms, q as BusabaseCmsRecord, r as CmsPathHelpers, s as createCmsPathHelpers, t as CmsCanonicalPath, u as isCmsContentForLocale, v as DEFAULT_CATEGORIES_BASE_SLUG, w as mapActiveCategoryRecord, x as DEFAULT_TAGS_BASE_SLUG, y as DEFAULT_PAGES_BASE_SLUG, z as getBusabaseCmsBaseDefinition } from "./routing-VU56uuJ0.js";
3
- //#region src/fallback.d.ts
4
- /**
5
- * The "read from Busabase, fall back to bundled content on any failure" wrapper — every app
6
- * consuming busabase-cms re-implemented this same try/catch/console.warn a couple of times
7
- * (once for list-shaped reads, once for single-record reads). One generic version, one place
8
- * to change the log format.
9
- */
10
- declare const readCmsOrFallback: <T>(operation: (() => Promise<T>) | null | undefined, fallback: T, label: string) => Promise<T>;
11
- //#endregion
3
+ import { i as requireCmsRead, n as readCmsOrFallback, r as readCmsStatus, t as CmsRead } from "./fallback-5I_dTsOb.js";
12
4
  //#region src/links.d.ts
13
5
  /** Keep rendered CMS attachments on protocols browsers can navigate safely. */
14
6
  declare const getSafeCmsExternalUrl: (value: string) => string | null;
@@ -30,4 +22,4 @@ interface BusabaseCmsSchemaConfig {
30
22
  }>;
31
23
  }
32
24
  //#endregion
33
- export { AttachmentFieldsDTO, AttachmentVO, BUSABASE_CMS_METADATA_KEY, BUSABASE_CMS_ROLES, BUSABASE_CMS_SCHEMA_PROFILES, BUSABASE_CMS_SCHEMA_VERSION, type BusabaseCms, type BusabaseCmsBase, type BusabaseCmsBaseDefinition, type BusabaseCmsBaseIds, type BusabaseCmsBaseRole, type BusabaseCmsClient, BusabaseCmsError, type BusabaseCmsField, type BusabaseCmsFieldDefinition, type BusabaseCmsFieldOptions, type BusabaseCmsFieldsOverride, type BusabaseCmsFolderMetadata, type BusabaseCmsNode, type BusabaseCmsOptions, type BusabaseCmsPathCollection, type BusabaseCmsRecord, type BusabaseCmsSchemaConfig, BusabaseCmsSchemaDriftError, type BusabaseCmsSchemaProfile, BusabaseCmsSetupError, type BusabaseCmsSource, type BusabaseCmsTaxonomyCollection, CategoryFieldsDTO, CategoryVO, CmsCanonicalPath, CmsCanonicalPathOptions, CmsPathHelpers, type CmsRecordKind, CmsTaxonomyKind, DEFAULT_CATEGORIES_BASE_SLUG, DEFAULT_PAGES_BASE_SLUG, DEFAULT_POSTS_BASE_SLUG, DEFAULT_TAGS_BASE_SLUG, type InvalidCmsRecordIssue, PageFieldsDTO, PageVO, PostFieldsDTO, PostVO, RelationFieldsDTO, TagFieldsDTO, TagVO, attachmentFieldsDTOSchema, attachmentVOSchema, buildCmsCanonicalPath, buildCmsTaxonomyArchivePath, i18nName as busabaseCmsFieldI18nName, categoryFieldsDTOSchema, categoryVOSchema, createBusabaseCms, createBusabaseCmsSource, createBusabaseCmsSourceFromConfig, createCmsPathHelpers, filterCmsPostsByTaxonomy, getBusabaseCmsBaseDefinition, getSafeCmsExternalUrl, isCmsBlogPostPath, isCmsContentForLocale, mapActiveCategoryRecord, mapActiveTagRecord, mapPublishedPageRecord, mapPublishedPostRecord, normalizeCmsPath, pageFieldsDTOSchema, pageVOSchema, parseCmsCanonicalPath, postFieldsDTOSchema, postVOSchema, readCmsOrFallback, relationFieldsDTOSchema, replaceField, tagFieldsDTOSchema, tagVOSchema, taxonomyFieldsDTOSchema };
25
+ export { AttachmentFieldsDTO, AttachmentVO, BUSABASE_CMS_METADATA_KEY, BUSABASE_CMS_ROLES, BUSABASE_CMS_SCHEMA_PROFILES, BUSABASE_CMS_SCHEMA_VERSION, type BusabaseCms, type BusabaseCmsBase, type BusabaseCmsBaseDefinition, type BusabaseCmsBaseIds, type BusabaseCmsBaseRole, type BusabaseCmsClient, BusabaseCmsError, type BusabaseCmsField, type BusabaseCmsFieldDefinition, type BusabaseCmsFieldOptions, type BusabaseCmsFieldsOverride, type BusabaseCmsFolderMetadata, type BusabaseCmsNode, type BusabaseCmsOptions, type BusabaseCmsPathCollection, type BusabaseCmsRecord, type BusabaseCmsSchemaConfig, BusabaseCmsSchemaDriftError, type BusabaseCmsSchemaProfile, BusabaseCmsSetupError, type BusabaseCmsSource, type BusabaseCmsTaxonomyCollection, CategoryFieldsDTO, CategoryVO, CmsCanonicalPath, CmsCanonicalPathOptions, CmsPathHelpers, CmsRead, type CmsRecordKind, CmsTaxonomyKind, DEFAULT_CATEGORIES_BASE_SLUG, DEFAULT_PAGES_BASE_SLUG, DEFAULT_POSTS_BASE_SLUG, DEFAULT_TAGS_BASE_SLUG, type InvalidCmsRecordIssue, PageFieldsDTO, PageVO, PostFieldsDTO, PostVO, RelationFieldsDTO, TagFieldsDTO, TagVO, attachmentFieldsDTOSchema, attachmentVOSchema, buildCmsCanonicalPath, buildCmsTaxonomyArchivePath, i18nName as busabaseCmsFieldI18nName, categoryFieldsDTOSchema, categoryVOSchema, createBusabaseCms, createBusabaseCmsSource, createBusabaseCmsSourceFromConfig, createCmsPathHelpers, filterCmsPostsByTaxonomy, getBusabaseCmsBaseDefinition, getSafeCmsExternalUrl, isCmsBlogPostPath, isCmsContentForLocale, mapActiveCategoryRecord, mapActiveTagRecord, mapPublishedPageRecord, mapPublishedPostRecord, normalizeCmsPath, pageFieldsDTOSchema, pageVOSchema, parseCmsCanonicalPath, postFieldsDTOSchema, postVOSchema, readCmsOrFallback, readCmsStatus, relationFieldsDTOSchema, replaceField, requireCmsRead, tagFieldsDTOSchema, tagVOSchema, taxonomyFieldsDTOSchema };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { A as replaceField, C as createBusabaseCmsSourceFromConfig, D as BUSABASE_CMS_SCHEMA_VERSION, E as BUSABASE_CMS_SCHEMA_PROFILES, M as BusabaseCmsSchemaDriftError, N as BusabaseCmsSetupError, O as getBusabaseCmsBaseDefinition, S as createBusabaseCmsSource, T as BUSABASE_CMS_ROLES, _ as postVOSchema, a as createBusabaseCms, b as tagVOSchema, c as mapPublishedPageRecord, d as attachmentVOSchema, f as categoryFieldsDTOSchema, g as postFieldsDTOSchema, h as pageVOSchema, i as DEFAULT_TAGS_BASE_SLUG, j as BusabaseCmsError, k as i18nName, l as mapPublishedPostRecord, m as pageFieldsDTOSchema, n as DEFAULT_PAGES_BASE_SLUG, o as mapActiveCategoryRecord, p as categoryVOSchema, r as DEFAULT_POSTS_BASE_SLUG, s as mapActiveTagRecord, t as DEFAULT_CATEGORIES_BASE_SLUG, u as attachmentFieldsDTOSchema, v as relationFieldsDTOSchema, w as BUSABASE_CMS_METADATA_KEY, x as taxonomyFieldsDTOSchema, y as tagFieldsDTOSchema } from "./content-BXgsWQjQ.js";
2
- import { a as isCmsBlogPostPath, c as parseCmsCanonicalPath, i as filterCmsPostsByTaxonomy, l as readCmsOrFallback, n as buildCmsTaxonomyArchivePath, o as isCmsContentForLocale, r as createCmsPathHelpers, s as normalizeCmsPath, t as buildCmsCanonicalPath } from "./routing-C3lkVAZ8.js";
2
+ import { a as isCmsBlogPostPath, c as parseCmsCanonicalPath, d as requireCmsRead, i as filterCmsPostsByTaxonomy, l as readCmsOrFallback, n as buildCmsTaxonomyArchivePath, o as isCmsContentForLocale, r as createCmsPathHelpers, s as normalizeCmsPath, t as buildCmsCanonicalPath, u as readCmsStatus } from "./routing-Cf35nomf.js";
3
3
  //#region src/links.ts
4
4
  /** Keep rendered CMS attachments on protocols browsers can navigate safely. */
5
5
  const getSafeCmsExternalUrl = (value) => {
@@ -11,4 +11,4 @@ const getSafeCmsExternalUrl = (value) => {
11
11
  }
12
12
  };
13
13
  //#endregion
14
- export { BUSABASE_CMS_METADATA_KEY, BUSABASE_CMS_ROLES, BUSABASE_CMS_SCHEMA_PROFILES, BUSABASE_CMS_SCHEMA_VERSION, BusabaseCmsError, BusabaseCmsSchemaDriftError, BusabaseCmsSetupError, DEFAULT_CATEGORIES_BASE_SLUG, DEFAULT_PAGES_BASE_SLUG, DEFAULT_POSTS_BASE_SLUG, DEFAULT_TAGS_BASE_SLUG, attachmentFieldsDTOSchema, attachmentVOSchema, buildCmsCanonicalPath, buildCmsTaxonomyArchivePath, i18nName as busabaseCmsFieldI18nName, categoryFieldsDTOSchema, categoryVOSchema, createBusabaseCms, createBusabaseCmsSource, createBusabaseCmsSourceFromConfig, createCmsPathHelpers, filterCmsPostsByTaxonomy, getBusabaseCmsBaseDefinition, getSafeCmsExternalUrl, isCmsBlogPostPath, isCmsContentForLocale, mapActiveCategoryRecord, mapActiveTagRecord, mapPublishedPageRecord, mapPublishedPostRecord, normalizeCmsPath, pageFieldsDTOSchema, pageVOSchema, parseCmsCanonicalPath, postFieldsDTOSchema, postVOSchema, readCmsOrFallback, relationFieldsDTOSchema, replaceField, tagFieldsDTOSchema, tagVOSchema, taxonomyFieldsDTOSchema };
14
+ export { BUSABASE_CMS_METADATA_KEY, BUSABASE_CMS_ROLES, BUSABASE_CMS_SCHEMA_PROFILES, BUSABASE_CMS_SCHEMA_VERSION, BusabaseCmsError, BusabaseCmsSchemaDriftError, BusabaseCmsSetupError, DEFAULT_CATEGORIES_BASE_SLUG, DEFAULT_PAGES_BASE_SLUG, DEFAULT_POSTS_BASE_SLUG, DEFAULT_TAGS_BASE_SLUG, attachmentFieldsDTOSchema, attachmentVOSchema, buildCmsCanonicalPath, buildCmsTaxonomyArchivePath, i18nName as busabaseCmsFieldI18nName, categoryFieldsDTOSchema, categoryVOSchema, createBusabaseCms, createBusabaseCmsSource, createBusabaseCmsSourceFromConfig, createCmsPathHelpers, filterCmsPostsByTaxonomy, getBusabaseCmsBaseDefinition, getSafeCmsExternalUrl, isCmsBlogPostPath, isCmsContentForLocale, mapActiveCategoryRecord, mapActiveTagRecord, mapPublishedPageRecord, mapPublishedPostRecord, normalizeCmsPath, pageFieldsDTOSchema, pageVOSchema, parseCmsCanonicalPath, postFieldsDTOSchema, postVOSchema, readCmsOrFallback, readCmsStatus, relationFieldsDTOSchema, replaceField, requireCmsRead, tagFieldsDTOSchema, tagVOSchema, taxonomyFieldsDTOSchema };
@@ -1,5 +1,6 @@
1
1
  import { c as PostVO, d as TagVO, i as CategoryVO, o as PageVO } from "../types-QKFW2jvT.js";
2
2
  import { S as InvalidCmsRecordIssue, i as CmsTaxonomyKind, p as BusabaseCms, r as CmsPathHelpers, t as CmsCanonicalPath } from "../routing-VU56uuJ0.js";
3
+ import { t as CmsRead } from "../fallback-5I_dTsOb.js";
3
4
  import "server-only";
4
5
  //#region src/integration/config.d.ts
5
6
  /** Next data-cache `revalidate` used when an app does not override it. */
@@ -113,6 +114,8 @@ interface CmsPageReads {
113
114
  getBusabaseLandingPageByPath: (path: string) => Promise<PageVO | null>;
114
115
  listBusabaseLandingPagesOrFallback: () => Promise<PageVO[]>;
115
116
  getBusabaseLandingPageByPathOrFallback: (path: string) => Promise<PageVO | null>;
117
+ /** Status-aware variant, for callers that turn a missing page into a 404. */
118
+ readBusabaseLandingPageByPath: (path: string) => Promise<CmsRead<PageVO | null>>;
116
119
  }
117
120
  declare const createCmsPageReads: ({ getCms, requireCms }: CmsClientProvider, appLabel: string) => CmsPageReads;
118
121
  /** The argument object an app's own `generatePageMetadata` helper accepts. */
@@ -123,29 +126,73 @@ interface CmsPageMetadataOptions {
123
126
  lang: string;
124
127
  type: "website" | "article";
125
128
  }
129
+ /**
130
+ * All the locale resolver actually reads off a Page: its canonical path and its locale. Kept
131
+ * deliberately narrower than `PageVO` so an app whose own Page VO omits SDK fields it does not
132
+ * store (Buda drops `hero`/`features`/`faqs`) still flows through these helpers with its own
133
+ * type intact, instead of being silently widened back to `PageVO`.
134
+ */
135
+ type CmsPageIdentity = Pick<PageVO, "path" | "locale">;
136
+ /**
137
+ * A Page resolved for one request, carrying the locale that actually supplied the content.
138
+ * `isLocaleFallback` is what a route reads to decide whether to render a "not translated yet,
139
+ * showing English" notice. Structurally mirrors `ResolvedCmsPostPage` on the Post side.
140
+ */
141
+ interface ResolvedCmsPage<TPage extends CmsPageIdentity = PageVO> {
142
+ page: TPage;
143
+ requestedLocale: string;
144
+ contentLocale: string;
145
+ isLocaleFallback: boolean;
146
+ }
126
147
  /** The slice of a `CmsIntegration` the Page helpers depend on. */
127
- interface CmsPageHelpersIntegration {
148
+ interface CmsPageHelpersIntegration<TPage extends CmsPageIdentity = PageVO> {
128
149
  buildCmsPath: (locale: string, path: string | readonly string[]) => string | null;
129
150
  parseCmsPath: (path: string) => CmsCanonicalPath | null;
130
151
  isCmsContentForLocale: (item: {
131
152
  locale: string;
132
153
  path: string;
133
154
  }, locale: string) => boolean;
134
- getBusabaseLandingPageByPathOrFallback: (path: string) => Promise<PageVO | null>;
155
+ getBusabaseLandingPageByPathOrFallback: (path: string) => Promise<TPage | null>;
156
+ /** Status-aware variant, for callers that turn a missing page into a 404. */
157
+ readBusabaseLandingPageByPath: (path: string) => Promise<CmsRead<TPage | null>>;
135
158
  }
136
- interface CmsPageHelpersOptions<TMetadata> {
137
- integration: CmsPageHelpersIntegration;
159
+ interface CmsPageHelpersOptions<TMetadata, TPage extends CmsPageIdentity = PageVO> {
160
+ integration: CmsPageHelpersIntegration<TPage>;
138
161
  /**
139
162
  * The app's own metadata helper. Injected and typed structurally so the SDK does not depend
140
163
  * on any app's site config.
141
164
  */
142
165
  generatePageMetadata: (options: CmsPageMetadataOptions) => TMetadata;
143
166
  }
144
- interface CmsPageHelpers<TMetadata> {
145
- getCmsPageForRequest: (lang: string, path: string | readonly string[]) => Promise<PageVO | null>;
167
+ interface CmsPageHelpers<TMetadata, TPage extends CmsPageIdentity = PageVO> {
168
+ /**
169
+ * STRICT per-locale lookup: no English fallback, `null` when this exact locale has no Page.
170
+ * Callers that run their own cross-locale cascade (Buda's use-case resolver cascades CMS →
171
+ * bundled ICP → editorial before falling back) must use this — handing them the fallback
172
+ * variant makes every locale look translated, which silently suppresses the "showing English"
173
+ * notice and mislabels the content locale.
174
+ */
175
+ getCmsPageForLocale: (locale: string, path: string | readonly string[]) => Promise<TPage | null>;
176
+ /**
177
+ * The resolved Page only. Falls back to English when the requested locale has no translation,
178
+ * so an untranslated Page renders in English instead of 404ing — the same rule the Post
179
+ * resolver applies. Use `resolveCmsPageForRequest` when the route needs to know it fell back.
180
+ */
181
+ getCmsPageForRequest: (lang: string, path: string | readonly string[]) => Promise<TPage | null>;
182
+ /** Same resolution as `getCmsPageForRequest`, plus which locale supplied the content. */
183
+ resolveCmsPageForRequest: (lang: string, path: string | readonly string[]) => Promise<ResolvedCmsPage<TPage> | null>;
184
+ /**
185
+ * Status-aware variant. A CMS Page has no local-MDX equivalent, so a catch-all
186
+ * route cannot otherwise tell "no page at this path" (a correct, cacheable
187
+ * 404) from "the CMS is unreachable" (a 404 that would be cached as though the
188
+ * page had been deleted).
189
+ */
190
+ readCmsPageForRequest: (lang: string, path: string | readonly string[]) => Promise<CmsRead<TPage | null>>;
191
+ /** Status-aware variant of `resolveCmsPageForRequest`. */
192
+ readResolvedCmsPageForRequest: (lang: string, path: string | readonly string[]) => Promise<CmsRead<ResolvedCmsPage<TPage> | null>>;
146
193
  generateCmsPageMetadata: (page: PageVO, lang: string) => TMetadata | Record<string, never>;
147
194
  }
148
- declare const createCmsPageHelpers: <TMetadata>({ integration, generatePageMetadata }: CmsPageHelpersOptions<TMetadata>) => CmsPageHelpers<TMetadata>;
195
+ declare const createCmsPageHelpers: <TMetadata, TPage extends CmsPageIdentity = PageVO>({ integration, generatePageMetadata }: CmsPageHelpersOptions<TMetadata, TPage>) => CmsPageHelpers<TMetadata, TPage>;
149
196
  //#endregion
150
197
  //#region src/integration/posts.d.ts
151
198
  interface CmsPostReads {
@@ -222,6 +269,13 @@ interface CmsTaxonomyReads {
222
269
  listBusabaseCategoriesOrFallback: () => Promise<CategoryVO[]>;
223
270
  listBusabaseTags: () => Promise<TagVO[]>;
224
271
  listBusabaseTagsOrFallback: () => Promise<TagVO[]>;
272
+ /**
273
+ * Status-aware variants, for callers that turn the result into a 404.
274
+ * A Category or Tag exists only in the CMS — there is no local fallback — so
275
+ * "the CMS is unreachable" and "no such taxonomy" are otherwise identical here.
276
+ */
277
+ readBusabaseCategories: () => Promise<CmsRead<CategoryVO[]>>;
278
+ readBusabaseTags: () => Promise<CmsRead<TagVO[]>>;
225
279
  }
226
280
  declare const createCmsTaxonomyReads: ({ getCms, requireCms }: CmsClientProvider, appLabel: string) => CmsTaxonomyReads;
227
281
  //#endregion
@@ -248,4 +302,4 @@ interface CmsIntegration extends CmsPostReads, CmsPageReads, CmsTaxonomyReads {
248
302
  }
249
303
  declare const createCmsIntegration: (config: CmsIntegrationConfig) => CmsIntegration;
250
304
  //#endregion
251
- export { type BlogCardContent, type CmsClientProvider, CmsIntegration, type CmsIntegrationBaseSlugs, type CmsIntegrationCacheConfig, type CmsIntegrationCacheTags, type CmsIntegrationConfig, type CmsPageHelpers, type CmsPageHelpersIntegration, type CmsPageHelpersOptions, type CmsPageMetadataOptions, type CmsPageReads, type CmsPostReads, type CmsPostResolver, type CmsPostResolverDependencies, type CmsPostResolverIntegration, type CmsPostResolverOptions, type CmsTaxonomyReads, DEFAULT_CMS_REVALIDATE_SECONDS, type LocalPostSourceLike, type ResolvedCmsConfig, type ResolvedCmsPostPage, buildCmsCacheKeyPrefix, createCmsClientProvider, createCmsIntegration, createCmsPageHelpers, createCmsPageReads, createCmsPostReads, createCmsPostResolver, createCmsTaxonomyReads, mergeBlogCardsByPath, readCmsEnvConfig, resolveCmsCacheTags };
305
+ export { type BlogCardContent, type CmsClientProvider, CmsIntegration, type CmsIntegrationBaseSlugs, type CmsIntegrationCacheConfig, type CmsIntegrationCacheTags, type CmsIntegrationConfig, type CmsPageHelpers, type CmsPageHelpersIntegration, type CmsPageHelpersOptions, type CmsPageIdentity, type CmsPageMetadataOptions, type CmsPageReads, type CmsPostReads, type CmsPostResolver, type CmsPostResolverDependencies, type CmsPostResolverIntegration, type CmsPostResolverOptions, type CmsTaxonomyReads, DEFAULT_CMS_REVALIDATE_SECONDS, type LocalPostSourceLike, type ResolvedCmsConfig, type ResolvedCmsPage, type ResolvedCmsPostPage, buildCmsCacheKeyPrefix, createCmsClientProvider, createCmsIntegration, createCmsPageHelpers, createCmsPageReads, createCmsPostReads, createCmsPostResolver, createCmsTaxonomyReads, mergeBlogCardsByPath, readCmsEnvConfig, resolveCmsCacheTags };
@@ -1,4 +1,4 @@
1
- import { l as readCmsOrFallback, r as createCmsPathHelpers, s as normalizeCmsPath } from "../routing-C3lkVAZ8.js";
1
+ import { l as readCmsOrFallback, r as createCmsPathHelpers, s as normalizeCmsPath, u as readCmsStatus } from "../routing-Cf35nomf.js";
2
2
  import { createCachedBusabaseCms } from "../next.js";
3
3
  import "server-only";
4
4
  import { cache } from "react";
@@ -118,6 +118,10 @@ const createCmsClientProvider = (config) => {
118
118
  const createCmsPageReads = ({ getCms, requireCms }, appLabel) => ({
119
119
  listBusabaseLandingPages: async () => requireCms().pages.list(),
120
120
  getBusabaseLandingPageByPath: async (path) => requireCms().pages.getByPath(path),
121
+ readBusabaseLandingPageByPath: async (path) => {
122
+ const cms = getCms();
123
+ return readCmsStatus(cms ? () => cms.pages.getByPath(path) : null, null, `get ${appLabel} page`);
124
+ },
121
125
  listBusabaseLandingPagesOrFallback: async () => {
122
126
  const cms = getCms();
123
127
  return readCmsOrFallback(cms ? () => cms.pages.list() : null, [], `list ${appLabel} landing pages`);
@@ -127,27 +131,95 @@ const createCmsPageReads = ({ getCms, requireCms }, appLabel) => ({
127
131
  return readCmsOrFallback(cms ? () => cms.pages.getByPath(path) : null, null, `get ${appLabel} landing page ${path}`);
128
132
  }
129
133
  });
134
+ /**
135
+ * The locale every other locale falls back to when it has no translation of a Page.
136
+ * Mirrors the Post resolver in ./posts.ts — see `resolvePostPageWithDependencies`.
137
+ */
138
+ const FALLBACK_LOCALE = "en";
130
139
  const createCmsPageHelpers = ({ integration, generatePageMetadata }) => {
131
- const { buildCmsPath, getBusabaseLandingPageByPathOrFallback, isCmsContentForLocale, parseCmsPath } = integration;
132
- const getCmsPageForRequest = async (lang, path) => {
133
- const canonicalPath = buildCmsPath(lang, path);
140
+ const { buildCmsPath, getBusabaseLandingPageByPathOrFallback, isCmsContentForLocale, parseCmsPath, readBusabaseLandingPageByPath } = integration;
141
+ const getForLocale = async (locale, path) => {
142
+ const canonicalPath = buildCmsPath(locale, path);
134
143
  if (!canonicalPath) return null;
135
144
  const page = await getBusabaseLandingPageByPathOrFallback(canonicalPath);
136
- return page && isCmsContentForLocale(page, lang) ? page : null;
145
+ return page && isCmsContentForLocale(page, locale) ? page : null;
146
+ };
147
+ const readForLocale = async (locale, path) => {
148
+ const canonicalPath = buildCmsPath(locale, path);
149
+ if (!canonicalPath) return {
150
+ status: "ok",
151
+ data: null
152
+ };
153
+ const read = await readBusabaseLandingPageByPath(canonicalPath);
154
+ if (read.status !== "ok") return read;
155
+ const page = read.data;
156
+ return {
157
+ status: "ok",
158
+ data: page && isCmsContentForLocale(page, locale) ? page : null
159
+ };
160
+ };
161
+ const resolved = (page, requestedLocale, contentLocale) => ({
162
+ page,
163
+ requestedLocale,
164
+ contentLocale,
165
+ isLocaleFallback: contentLocale !== requestedLocale
166
+ });
167
+ const resolveCmsPageForRequest = async (lang, path) => {
168
+ if (!buildCmsPath(lang, path)) return null;
169
+ const requested = await getForLocale(lang, path);
170
+ if (requested) return resolved(requested, lang, lang);
171
+ if (lang === FALLBACK_LOCALE) return null;
172
+ const fallback = await getForLocale(FALLBACK_LOCALE, path);
173
+ return fallback ? resolved(fallback, lang, FALLBACK_LOCALE) : null;
174
+ };
175
+ const getCmsPageForRequest = async (lang, path) => (await resolveCmsPageForRequest(lang, path))?.page ?? null;
176
+ const readResolvedCmsPageForRequest = async (lang, path) => {
177
+ if (!buildCmsPath(lang, path)) return {
178
+ status: "ok",
179
+ data: null
180
+ };
181
+ const requested = await readForLocale(lang, path);
182
+ if (requested.status !== "ok") return requested;
183
+ if (requested.data) return {
184
+ status: "ok",
185
+ data: resolved(requested.data, lang, lang)
186
+ };
187
+ if (lang === FALLBACK_LOCALE) return {
188
+ status: "ok",
189
+ data: null
190
+ };
191
+ const fallback = await readForLocale(FALLBACK_LOCALE, path);
192
+ if (fallback.status !== "ok") return fallback;
193
+ return {
194
+ status: "ok",
195
+ data: fallback.data ? resolved(fallback.data, lang, FALLBACK_LOCALE) : null
196
+ };
197
+ };
198
+ const readCmsPageForRequest = async (lang, path) => {
199
+ const read = await readResolvedCmsPageForRequest(lang, path);
200
+ return read.status === "ok" ? {
201
+ status: "ok",
202
+ data: read.data?.page ?? null
203
+ } : read;
137
204
  };
138
205
  const generateCmsPageMetadata = (page, lang) => {
139
206
  const parsed = parseCmsPath(page.path);
140
- if (!parsed || parsed.locale !== lang) return {};
207
+ if (!parsed) return {};
208
+ const contentLocale = isCmsContentForLocale(page, lang) ? lang : parsed.locale;
141
209
  return generatePageMetadata({
142
210
  title: page.seoTitle ?? page.title,
143
211
  description: page.seoDescription ?? "",
144
212
  path: parsed.pathWithoutLocale,
145
- lang,
213
+ lang: contentLocale,
146
214
  type: "website"
147
215
  });
148
216
  };
149
217
  return {
218
+ getCmsPageForLocale: getForLocale,
150
219
  getCmsPageForRequest,
220
+ resolveCmsPageForRequest,
221
+ readCmsPageForRequest,
222
+ readResolvedCmsPageForRequest,
151
223
  generateCmsPageMetadata
152
224
  };
153
225
  };
@@ -238,6 +310,14 @@ const createCmsTaxonomyReads = ({ getCms, requireCms }, appLabel) => ({
238
310
  return readCmsOrFallback(cms ? () => cms.categories.list() : null, [], `list ${appLabel} categories`);
239
311
  },
240
312
  listBusabaseTags: async () => requireCms().tags.list(),
313
+ readBusabaseCategories: async () => {
314
+ const cms = getCms();
315
+ return readCmsStatus(cms ? () => cms.categories.list() : null, [], `list ${appLabel} categories`);
316
+ },
317
+ readBusabaseTags: async () => {
318
+ const cms = getCms();
319
+ return readCmsStatus(cms ? () => cms.tags.list() : null, [], `list ${appLabel} tags`);
320
+ },
241
321
  listBusabaseTagsOrFallback: async () => {
242
322
  const cms = getCms();
243
323
  return readCmsOrFallback(cms ? () => cms.tags.list() : null, [], `list ${appLabel} tags`);
@@ -14,6 +14,32 @@ const readCmsOrFallback = async (operation, fallback, label) => {
14
14
  return fallback;
15
15
  }
16
16
  };
17
+ const readCmsStatus = async (operation, whenUnconfigured, label) => {
18
+ if (!operation) return {
19
+ status: "ok",
20
+ data: whenUnconfigured
21
+ };
22
+ try {
23
+ return {
24
+ status: "ok",
25
+ data: await operation()
26
+ };
27
+ } catch (error) {
28
+ console.warn(`[busabase-cms] ${label} failed`, error);
29
+ return { status: "unavailable" };
30
+ }
31
+ };
32
+ /**
33
+ * Unwrap a read, or fail loudly.
34
+ *
35
+ * Throwing beats rendering a 404: an error response is not cached, so the page
36
+ * recovers on the next request, whereas a cached 404 tells crawlers the content
37
+ * is gone.
38
+ */
39
+ const requireCmsRead = (read, what) => {
40
+ if (read.status === "unavailable") throw new Error(`Busabase CMS unreachable while rendering ${what}`);
41
+ return read.data;
42
+ };
17
43
  //#endregion
18
44
  //#region src/routing.ts
19
45
  const normalizeSegment = (segment) => {
@@ -88,4 +114,4 @@ const filterCmsPostsByTaxonomy = (posts, kind, taxonomy) => {
88
114
  return posts.filter((post) => post.locale === taxonomy.locale && post[relationKey].includes(taxonomy.id));
89
115
  };
90
116
  //#endregion
91
- export { isCmsBlogPostPath as a, parseCmsCanonicalPath as c, filterCmsPostsByTaxonomy as i, readCmsOrFallback as l, buildCmsTaxonomyArchivePath as n, isCmsContentForLocale as o, createCmsPathHelpers as r, normalizeCmsPath as s, buildCmsCanonicalPath as t };
117
+ export { isCmsBlogPostPath as a, parseCmsCanonicalPath as c, requireCmsRead as d, filterCmsPostsByTaxonomy as i, readCmsOrFallback as l, buildCmsTaxonomyArchivePath as n, isCmsContentForLocale as o, createCmsPathHelpers as r, normalizeCmsPath as s, buildCmsCanonicalPath as t, readCmsStatus as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "busabase-cms-sdk",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Typed Posts, Pages, Categories, and Tags for Busabase CMS, Next.js, and Fumadocs.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/busabase/busabase/tree/main/packages/busabase-cms-sdk",
@@ -41,13 +41,13 @@
41
41
  ],
42
42
  "sideEffects": false,
43
43
  "dependencies": {
44
- "busabase-sdk": "^0.30.1",
45
44
  "fumadocs-core": "16.14.5",
46
45
  "rehype-sanitize": "^6.0.0",
47
46
  "remark-gfm": "^4.0.1",
48
47
  "sanitize-html": "^2.17.6",
49
48
  "server-only": "^0.0.1",
50
- "zod": "^4.3.6"
49
+ "zod": "^4.3.6",
50
+ "busabase-sdk": "^0.53.0"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "next": ">=15 <17",
@@ -62,7 +62,7 @@
62
62
  }
63
63
  },
64
64
  "devDependencies": {
65
- "@types/node": "^24.13.2",
65
+ "@types/node": "^26.3.0",
66
66
  "@types/react": "^19.2.17",
67
67
  "@types/sanitize-html": "^2.16.1",
68
68
  "next": "16.3.3",