busabase-cms-sdk 0.1.4 → 0.1.6
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/integration/index.d.ts +45 -10
- package/dist/integration/index.js +61 -8
- package/package.json +2 -2
|
@@ -126,38 +126,73 @@ interface CmsPageMetadataOptions {
|
|
|
126
126
|
lang: string;
|
|
127
127
|
type: "website" | "article";
|
|
128
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
|
+
}
|
|
129
147
|
/** The slice of a `CmsIntegration` the Page helpers depend on. */
|
|
130
|
-
interface CmsPageHelpersIntegration {
|
|
148
|
+
interface CmsPageHelpersIntegration<TPage extends CmsPageIdentity = PageVO> {
|
|
131
149
|
buildCmsPath: (locale: string, path: string | readonly string[]) => string | null;
|
|
132
150
|
parseCmsPath: (path: string) => CmsCanonicalPath | null;
|
|
133
151
|
isCmsContentForLocale: (item: {
|
|
134
152
|
locale: string;
|
|
135
153
|
path: string;
|
|
136
154
|
}, locale: string) => boolean;
|
|
137
|
-
getBusabaseLandingPageByPathOrFallback: (path: string) => Promise<
|
|
155
|
+
getBusabaseLandingPageByPathOrFallback: (path: string) => Promise<TPage | null>;
|
|
138
156
|
/** Status-aware variant, for callers that turn a missing page into a 404. */
|
|
139
|
-
readBusabaseLandingPageByPath: (path: string) => Promise<CmsRead<
|
|
157
|
+
readBusabaseLandingPageByPath: (path: string) => Promise<CmsRead<TPage | null>>;
|
|
140
158
|
}
|
|
141
|
-
interface CmsPageHelpersOptions<TMetadata> {
|
|
142
|
-
integration: CmsPageHelpersIntegration
|
|
159
|
+
interface CmsPageHelpersOptions<TMetadata, TPage extends CmsPageIdentity = PageVO> {
|
|
160
|
+
integration: CmsPageHelpersIntegration<TPage>;
|
|
143
161
|
/**
|
|
144
162
|
* The app's own metadata helper. Injected and typed structurally so the SDK does not depend
|
|
145
163
|
* on any app's site config.
|
|
146
164
|
*/
|
|
147
165
|
generatePageMetadata: (options: CmsPageMetadataOptions) => TMetadata;
|
|
148
166
|
}
|
|
149
|
-
interface CmsPageHelpers<TMetadata> {
|
|
150
|
-
|
|
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>;
|
|
151
184
|
/**
|
|
152
185
|
* Status-aware variant. A CMS Page has no local-MDX equivalent, so a catch-all
|
|
153
186
|
* route cannot otherwise tell "no page at this path" (a correct, cacheable
|
|
154
187
|
* 404) from "the CMS is unreachable" (a 404 that would be cached as though the
|
|
155
188
|
* page had been deleted).
|
|
156
189
|
*/
|
|
157
|
-
readCmsPageForRequest: (lang: string, path: string | readonly string[]) => Promise<CmsRead<
|
|
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>>;
|
|
158
193
|
generateCmsPageMetadata: (page: PageVO, lang: string) => TMetadata | Record<string, never>;
|
|
159
194
|
}
|
|
160
|
-
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>;
|
|
161
196
|
//#endregion
|
|
162
197
|
//#region src/integration/posts.d.ts
|
|
163
198
|
interface CmsPostReads {
|
|
@@ -267,4 +302,4 @@ interface CmsIntegration extends CmsPostReads, CmsPageReads, CmsTaxonomyReads {
|
|
|
267
302
|
}
|
|
268
303
|
declare const createCmsIntegration: (config: CmsIntegrationConfig) => CmsIntegration;
|
|
269
304
|
//#endregion
|
|
270
|
-
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 };
|
|
@@ -131,16 +131,21 @@ const createCmsPageReads = ({ getCms, requireCms }, appLabel) => ({
|
|
|
131
131
|
return readCmsOrFallback(cms ? () => cms.pages.getByPath(path) : null, null, `get ${appLabel} landing page ${path}`);
|
|
132
132
|
}
|
|
133
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";
|
|
134
139
|
const createCmsPageHelpers = ({ integration, generatePageMetadata }) => {
|
|
135
140
|
const { buildCmsPath, getBusabaseLandingPageByPathOrFallback, isCmsContentForLocale, parseCmsPath, readBusabaseLandingPageByPath } = integration;
|
|
136
|
-
const
|
|
137
|
-
const canonicalPath = buildCmsPath(
|
|
141
|
+
const getForLocale = async (locale, path) => {
|
|
142
|
+
const canonicalPath = buildCmsPath(locale, path);
|
|
138
143
|
if (!canonicalPath) return null;
|
|
139
144
|
const page = await getBusabaseLandingPageByPathOrFallback(canonicalPath);
|
|
140
|
-
return page && isCmsContentForLocale(page,
|
|
145
|
+
return page && isCmsContentForLocale(page, locale) ? page : null;
|
|
141
146
|
};
|
|
142
|
-
const
|
|
143
|
-
const canonicalPath = buildCmsPath(
|
|
147
|
+
const readForLocale = async (locale, path) => {
|
|
148
|
+
const canonicalPath = buildCmsPath(locale, path);
|
|
144
149
|
if (!canonicalPath) return {
|
|
145
150
|
status: "ok",
|
|
146
151
|
data: null
|
|
@@ -150,23 +155,71 @@ const createCmsPageHelpers = ({ integration, generatePageMetadata }) => {
|
|
|
150
155
|
const page = read.data;
|
|
151
156
|
return {
|
|
152
157
|
status: "ok",
|
|
153
|
-
data: page && isCmsContentForLocale(page,
|
|
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
|
|
154
196
|
};
|
|
155
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;
|
|
204
|
+
};
|
|
156
205
|
const generateCmsPageMetadata = (page, lang) => {
|
|
157
206
|
const parsed = parseCmsPath(page.path);
|
|
158
|
-
if (!parsed
|
|
207
|
+
if (!parsed) return {};
|
|
208
|
+
const contentLocale = isCmsContentForLocale(page, lang) ? lang : parsed.locale;
|
|
159
209
|
return generatePageMetadata({
|
|
160
210
|
title: page.seoTitle ?? page.title,
|
|
161
211
|
description: page.seoDescription ?? "",
|
|
162
212
|
path: parsed.pathWithoutLocale,
|
|
163
|
-
lang,
|
|
213
|
+
lang: contentLocale,
|
|
164
214
|
type: "website"
|
|
165
215
|
});
|
|
166
216
|
};
|
|
167
217
|
return {
|
|
218
|
+
getCmsPageForLocale: getForLocale,
|
|
168
219
|
getCmsPageForRequest,
|
|
220
|
+
resolveCmsPageForRequest,
|
|
169
221
|
readCmsPageForRequest,
|
|
222
|
+
readResolvedCmsPageForRequest,
|
|
170
223
|
generateCmsPageMetadata
|
|
171
224
|
};
|
|
172
225
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "busabase-cms-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"sanitize-html": "^2.17.6",
|
|
48
48
|
"server-only": "^0.0.1",
|
|
49
49
|
"zod": "^4.3.6",
|
|
50
|
-
"busabase-sdk": "^0.
|
|
50
|
+
"busabase-sdk": "^0.60.0"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"next": ">=15 <17",
|