@roottale/cms-client 0.66.0 → 0.67.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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @roottale/cms-client
2
2
 
3
+ ## 0.67.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 30d26ac: 공통 메타데이터 해석기·고유 경로 조회·Next SSR 빌더와 캐시 태그를 추가합니다. 기존 canonical과 강제 robots 정책을 보존하고 공유 이미지 대체 텍스트를 OG·X에 동일하게 적용합니다. 분류의 공유 전용 설정과 메타데이터 조회·수정 MCP 도구, Pro 하향 후 공개 적용 유지 계약을 문서와 예제에 반영합니다. 분류 색인 전환을 선언한 사이트가 공개 분류 응답의 `category_index_activated`를 확인한 뒤에만 분류 검색 제외를 적용하도록 안내합니다.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [30d26ac]
12
+ - @roottale/cms-core@0.67.0
13
+
3
14
  ## 0.66.0
4
15
 
5
16
  ### Minor Changes
package/dist/server.d.ts CHANGED
@@ -2,6 +2,8 @@ import { InquiryFormDefinition, InquiryAnswer } from '@roottale/inquiry-forms';
2
2
  export { InquiryAnswer, InquiryFormDefinition, InquiryFormField, InquiryKind } from '@roottale/inquiry-forms';
3
3
  import { InquiryAttribution } from './attribution.js';
4
4
  export { parseAttributionJson } from './attribution.js';
5
+ import { PublicRouteMetadata } from '@roottale/cms-core';
6
+ export { MetadataDefaultRules, MetadataDefaultRulesSchema, MetadataDefaultsDeclaration, MetadataDefaultsInput, MetadataField, MetadataPatch, MetadataResolutionInput, MetadataRobotsPolicy, MetadataTextRule, MetadataValues, PublicRouteMetadata, formatMetadataDefaults, metadataContentImageUrl, readMetadataValues, resolveMetadata } from '@roottale/cms-core';
5
7
 
6
8
  /**
7
9
  * `@roottale/cms-client/server` — RootTale CMS Public API server-side fetch client.
@@ -754,6 +756,10 @@ interface CmsCategoryCount {
754
756
  description: string | null;
755
757
  seoTitle: string | null;
756
758
  seoDescription: string | null;
759
+ /** 공유 전용 설정. 본문 분류 이미지와 별도로 관리한다. */
760
+ seoOgImage?: string | null;
761
+ seoOgImageAlt?: string | null;
762
+ seoNofollow?: boolean | null;
757
763
  imageMediaId?: string | null;
758
764
  imageUrl?: string | null;
759
765
  imageAlt?: string | null;
@@ -1345,6 +1351,34 @@ declare function sitePatternPresentationAttributes(presentation: CmsSitePatternP
1345
1351
  */
1346
1352
  declare function selectSitePatternForSlot(patternSlots: Readonly<Record<string, string | null>> | null | undefined, patterns: readonly CmsSitePattern[], slot?: string): CmsSitePattern | null;
1347
1353
 
1354
+ declare const METADATA_CACHE_TAG = "rt-metadata";
1355
+ declare const SITE_METADATA_DEFAULTS_CACHE_TAG = "rt-metadata-defaults";
1356
+ /** locale를 생략한 요청은 사이트 기본 locale을 조회한다. */
1357
+ declare function routeMetadataCacheTag(routeKey: string, locale?: string): string;
1358
+ interface FetchRouteMetadataOptions {
1359
+ apiKey: string;
1360
+ baseUrl?: string;
1361
+ siteId?: string;
1362
+ routeKey: string;
1363
+ locale?: string;
1364
+ signal?: AbortSignal;
1365
+ /** 기본 300초. 웹훅 유실 시 재조회하며, 0이면 항상 최신값을 읽는다. */
1366
+ revalidate?: number;
1367
+ }
1368
+ /**
1369
+ * 고유 route key 직접 조회. 등록되지 않은 경로만 null이며, 연결된 빈 설정은
1370
+ * overrides:{}인 객체다. HTTP/네트워크/응답 오류는 throw하여 빈 설정으로 캐시하지 않는다.
1371
+ * 공개 읽기는 구독 등급을 검사하지 않아 요금제 하향 후에도 기존 설정을 유지한다.
1372
+ */
1373
+ declare function fetchRouteMetadata(options: FetchRouteMetadataOptions): Promise<PublicRouteMetadata | null>;
1374
+
1375
+ /**
1376
+ * 배포 도구가 정규화해 등록한 manifest의 SHA-256(64자리 hex).
1377
+ * 이 함수는 스키마 검사·기본값 채우기를 하지 않는다. 원본 JSON을 그대로 해시하면
1378
+ * 등록 API가 정규화한 계약과 달라질 수 있으므로 배포 시 정규화한 manifest를 사용한다.
1379
+ */
1380
+ declare function metadataContractChecksum(normalizedContract: unknown): Promise<string>;
1381
+
1348
1382
  /**
1349
1383
  * 설정류 fetch 캐시 tag 한 벌 (ADR-0096 Phase C).
1350
1384
  *
@@ -1657,4 +1691,4 @@ declare function formatKoreanPhoneNumber(input: string): string;
1657
1691
  */
1658
1692
  declare function submitInquiry(options: SubmitInquiryOptions): Promise<SubmitInquiryResult>;
1659
1693
 
1660
- export { type ActionDockAction, type ActionDockKind, type ActionDockPosition, type ActionDockSettings, type AnalyticsTagConfig, type AnalyticsTagProvider, type AnnouncementItem, type AnnouncementSettings, type AnnouncementTone, type AnnouncementVariant, type ArticleSchemaInput, BLOG_SETTINGS_CACHE_TAG, BUSINESS_CACHE_TAG, type BusinessDayCode, type BusinessOpeningHours, type BusinessType, CMS_CLIENT_VERSION, COLLECTIONS_CACHE_TAG, CmsApiError, type CmsArchiveRelatedGroup, type CmsArchiveRelatedPost, type CmsCategoryCount, type CmsCategoryCounts, type CmsContentModel, type CmsExposureCampaign, type CmsExposureDecision, type CmsFieldMetaEntry, type CmsPostContent, type CmsPostContentPage, type CmsPostPreviewContent, type CmsPostPreviewInfo, type CmsPostTranslationRef, type CmsPostType, type CmsRouteCollection, type CmsSearchHit, type CmsSitePattern, type CmsSitePatternPresentation, type CmsTaxonomyKind, type CmsTermRef, DEFAULT_BLOG_SETTINGS, DEFAULT_SITE_PATTERN_PRESENTATION, EXPOSURE_CACHE_TAG, type FetchAnalyticsOptions, type FetchAuthorsOptions, type FetchBlogSettingsOptions, type FetchBusinessProfileOptions, type FetchCategoryCountsOptions, type FetchCollectionsOptions, type FetchContentModelsOptions, type FetchExposureDecisionOptions, type FetchExposureOptions, type FetchInquiryFormOptions, type FetchMenuOptions, type FetchMenusOptions, type FetchPageBySlugOptions, type FetchPostOptions, type FetchPostPreviewOptions, type FetchPostsOptions, type FetchRedirectsOptions, type FetchSiteInquiriesOptions, type FetchSiteInquiryOptions, type FetchSitePatternsOptions, type FetchThemeOptions, type FormInquiryReceipt, INQUIRY_ERROR_MESSAGES, InquiryAttribution, type InquiryBoardSettings, type InquiryVertical, LOCAL_BUSINESS_MAX_SERVICES, LOCAL_BUSINESS_SERVICE_MAX_LENGTH, type LocalBusinessProfileInput, MENUS_CACHE_TAG, POST_FOOTER_PATTERN_SLOT, type PublishedInquiryForm, type ResolvedPostDisplay, type RootTaleAuthor, type RootTaleBlogPostCta, type RootTaleBlogSettings, type RootTaleBusinessProfile, type RootTaleMenu, type RootTaleMenuItem, type RootTaleRedirect, type RootTaleSiteProfile, type RootTaleSitemapSettings, type RootTaleTheme, type RssFeedInput, type RssItemInput, SETTINGS_CACHE_TAGS, SITE_PATTERNS_CACHE_TAG, type SearchPostsOptions, type SiteAnalyticsTags, type SiteInquiryDetail, type SiteInquiryListItem, type SiteInquiryListPage, type SiteInquiryStatus, type SiteNavColumn, type SiteNavCta, type SiteNavGroup, type SiteNavLink, type SiteNavSettings, type SiteVerificationMetadata, type SiteVerificationSettings, type SubmitFormInquiryFields, type SubmitFormInquiryOptions, type SubmitFormInquiryResult, type SubmitInquiryFields, type SubmitInquiryOptions, type SubmitInquiryResult, type SubmitSiteInquiryFields, type SubmitSiteInquiryOptions, type SubmitSiteInquiryResult, THEME_CACHE_TAG, articleSchema, breadcrumbSchema, businessProfileFromWire, collectionPageSchema, contentIdMeta, faqSchema, fetchAnalyticsConfig, fetchAuthors, fetchBlogSettings, fetchBusinessProfile, fetchCategoryCounts, fetchCollections, fetchCollectionsSafeDefault, fetchContentModels, fetchExposure, fetchExposureDecision, fetchInquiryForm, fetchMenu, fetchMenus, fetchPageBySlug, fetchPost, fetchPostPreview, fetchPosts, fetchRedirects, fetchSiteInquiries, fetchSiteInquiry, fetchSitePatterns, fetchTheme, formatKoreanPhoneNumber, generateRssXml, inquiryErrorMessage, isPreviewExpiredError, jsonLdJson, jsonLdScript, localBusinessSchema, organizationSchema, parseInquiryRedirect, profilePageSchema, resolvePostDisplay, resolveSearchHitPath, rssItemsFromPosts, searchPosts, selectRelatedPosts, selectSitePatternForSlot, sitePatternPresentationAttributes, siteVerificationMetadata, submitFormInquiry, submitInquiry, submitSiteInquiry, websiteSchema };
1694
+ export { type ActionDockAction, type ActionDockKind, type ActionDockPosition, type ActionDockSettings, type AnalyticsTagConfig, type AnalyticsTagProvider, type AnnouncementItem, type AnnouncementSettings, type AnnouncementTone, type AnnouncementVariant, type ArticleSchemaInput, BLOG_SETTINGS_CACHE_TAG, BUSINESS_CACHE_TAG, type BusinessDayCode, type BusinessOpeningHours, type BusinessType, CMS_CLIENT_VERSION, COLLECTIONS_CACHE_TAG, CmsApiError, type CmsArchiveRelatedGroup, type CmsArchiveRelatedPost, type CmsCategoryCount, type CmsCategoryCounts, type CmsContentModel, type CmsExposureCampaign, type CmsExposureDecision, type CmsFieldMetaEntry, type CmsPostContent, type CmsPostContentPage, type CmsPostPreviewContent, type CmsPostPreviewInfo, type CmsPostTranslationRef, type CmsPostType, type CmsRouteCollection, type CmsSearchHit, type CmsSitePattern, type CmsSitePatternPresentation, type CmsTaxonomyKind, type CmsTermRef, DEFAULT_BLOG_SETTINGS, DEFAULT_SITE_PATTERN_PRESENTATION, EXPOSURE_CACHE_TAG, type FetchAnalyticsOptions, type FetchAuthorsOptions, type FetchBlogSettingsOptions, type FetchBusinessProfileOptions, type FetchCategoryCountsOptions, type FetchCollectionsOptions, type FetchContentModelsOptions, type FetchExposureDecisionOptions, type FetchExposureOptions, type FetchInquiryFormOptions, type FetchMenuOptions, type FetchMenusOptions, type FetchPageBySlugOptions, type FetchPostOptions, type FetchPostPreviewOptions, type FetchPostsOptions, type FetchRedirectsOptions, type FetchRouteMetadataOptions, type FetchSiteInquiriesOptions, type FetchSiteInquiryOptions, type FetchSitePatternsOptions, type FetchThemeOptions, type FormInquiryReceipt, INQUIRY_ERROR_MESSAGES, InquiryAttribution, type InquiryBoardSettings, type InquiryVertical, LOCAL_BUSINESS_MAX_SERVICES, LOCAL_BUSINESS_SERVICE_MAX_LENGTH, type LocalBusinessProfileInput, MENUS_CACHE_TAG, METADATA_CACHE_TAG, POST_FOOTER_PATTERN_SLOT, type PublishedInquiryForm, type ResolvedPostDisplay, type RootTaleAuthor, type RootTaleBlogPostCta, type RootTaleBlogSettings, type RootTaleBusinessProfile, type RootTaleMenu, type RootTaleMenuItem, type RootTaleRedirect, type RootTaleSiteProfile, type RootTaleSitemapSettings, type RootTaleTheme, type RssFeedInput, type RssItemInput, SETTINGS_CACHE_TAGS, SITE_METADATA_DEFAULTS_CACHE_TAG, SITE_PATTERNS_CACHE_TAG, type SearchPostsOptions, type SiteAnalyticsTags, type SiteInquiryDetail, type SiteInquiryListItem, type SiteInquiryListPage, type SiteInquiryStatus, type SiteNavColumn, type SiteNavCta, type SiteNavGroup, type SiteNavLink, type SiteNavSettings, type SiteVerificationMetadata, type SiteVerificationSettings, type SubmitFormInquiryFields, type SubmitFormInquiryOptions, type SubmitFormInquiryResult, type SubmitInquiryFields, type SubmitInquiryOptions, type SubmitInquiryResult, type SubmitSiteInquiryFields, type SubmitSiteInquiryOptions, type SubmitSiteInquiryResult, THEME_CACHE_TAG, articleSchema, breadcrumbSchema, businessProfileFromWire, collectionPageSchema, contentIdMeta, faqSchema, fetchAnalyticsConfig, fetchAuthors, fetchBlogSettings, fetchBusinessProfile, fetchCategoryCounts, fetchCollections, fetchCollectionsSafeDefault, fetchContentModels, fetchExposure, fetchExposureDecision, fetchInquiryForm, fetchMenu, fetchMenus, fetchPageBySlug, fetchPost, fetchPostPreview, fetchPosts, fetchRedirects, fetchRouteMetadata, fetchSiteInquiries, fetchSiteInquiry, fetchSitePatterns, fetchTheme, formatKoreanPhoneNumber, generateRssXml, inquiryErrorMessage, isPreviewExpiredError, jsonLdJson, jsonLdScript, localBusinessSchema, metadataContractChecksum, organizationSchema, parseInquiryRedirect, profilePageSchema, resolvePostDisplay, resolveSearchHitPath, routeMetadataCacheTag, rssItemsFromPosts, searchPosts, selectRelatedPosts, selectSitePatternForSlot, sitePatternPresentationAttributes, siteVerificationMetadata, submitFormInquiry, submitInquiry, submitSiteInquiry, websiteSchema };
package/dist/server.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
 
5
5
  // src/server-base.ts
6
6
  var DEFAULT_BASE_URL = "https://api.roottale.com";
7
- var CLIENT_VERSION = true ? "0.66.0" : "dev";
7
+ var CLIENT_VERSION = true ? "0.67.0" : "dev";
8
8
  var CMS_CLIENT_VERSION = CLIENT_VERSION;
9
9
  function authHeaders(apiKey) {
10
10
  return {
@@ -559,6 +559,9 @@ async function fetchCategoryCounts(options) {
559
559
  description: typeof o.description === "string" ? o.description : null,
560
560
  seoTitle: typeof o.seo_title === "string" ? o.seo_title : null,
561
561
  seoDescription: typeof o.seo_description === "string" ? o.seo_description : null,
562
+ seoOgImage: typeof o.seo_og_image === "string" ? o.seo_og_image : null,
563
+ seoOgImageAlt: typeof o.seo_og_image_alt === "string" ? o.seo_og_image_alt : null,
564
+ seoNofollow: typeof o.seo_nofollow === "boolean" ? o.seo_nofollow : null,
562
565
  imageMediaId: typeof o.image_media_id === "string" ? o.image_media_id : null,
563
566
  imageUrl: typeof o.image_url === "string" ? o.image_url : null,
564
567
  imageAlt: typeof o.image_alt === "string" ? o.image_alt : null,
@@ -1196,6 +1199,63 @@ function selectSitePatternForSlot(patternSlots, patterns, slot = POST_FOOTER_PAT
1196
1199
  return patterns.find((pattern) => pattern.key === key) ?? null;
1197
1200
  }
1198
1201
 
1202
+ // src/server-metadata.ts
1203
+ import { MetadataRobotsPolicySchema, MetadataValuesSchema } from "@roottale/cms-core";
1204
+ import { readMetadataValues, resolveMetadata, formatMetadataDefaults, metadataContentImageUrl, MetadataDefaultRulesSchema } from "@roottale/cms-core";
1205
+ var METADATA_CACHE_TAG = "rt-metadata";
1206
+ var SITE_METADATA_DEFAULTS_CACHE_TAG = "rt-metadata-defaults";
1207
+ function routeMetadataCacheTag(routeKey, locale) {
1208
+ if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(routeKey) || routeKey.length > 160) throw new Error("Invalid metadata route key");
1209
+ const language = locale?.trim().toLowerCase() || "default";
1210
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(language) || language.length > 40) {
1211
+ throw new Error("Invalid metadata locale");
1212
+ }
1213
+ return `${METADATA_CACHE_TAG}:${routeKey}:${language}`;
1214
+ }
1215
+ async function fetchRouteMetadata(options) {
1216
+ assertServer();
1217
+ const routeTag = routeMetadataCacheTag(options.routeKey, options.locale);
1218
+ const params = new URLSearchParams({ route_key: options.routeKey });
1219
+ if (options.locale) params.set("locale", options.locale);
1220
+ if (options.siteId) params.set("site_id", options.siteId);
1221
+ const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
1222
+ const response = await fetch(`${baseUrl}/v1/cms/public/metadata?${params}`, buildGetInit(options.apiKey, {
1223
+ signal: options.signal,
1224
+ revalidate: options.revalidate ?? 300,
1225
+ tags: [METADATA_CACHE_TAG, SITE_METADATA_DEFAULTS_CACHE_TAG, routeTag]
1226
+ }));
1227
+ if (!response.ok) throw await toApiError(response);
1228
+ const json = await response.json();
1229
+ if (!json || typeof json !== "object" || !("metadata" in json)) throw unexpectedResponse(response.status);
1230
+ if (json.metadata === null) return null;
1231
+ if (!isRouteMetadata(json.metadata) || json.metadata.routeKey !== options.routeKey) throw unexpectedResponse(response.status);
1232
+ return json.metadata;
1233
+ }
1234
+ function isValues(value) {
1235
+ return MetadataValuesSchema.safeParse(value).success;
1236
+ }
1237
+ function isRouteMetadata(value) {
1238
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1239
+ const row = value;
1240
+ return typeof row.routeKey === "string" && typeof row.locale === "string" && typeof row.path === "string" && row.path.startsWith("/") && !row.path.startsWith("//") && typeof row.version === "string" && isValues(row.overrides) && isValues(row.defaults) && isValues(row.effective) && (row.fallback === void 0 || isValues(row.fallback)) && (row.robots === void 0 || MetadataRobotsPolicySchema.safeParse(row.robots).success) && Array.isArray(row.supportedFields) && row.supportedFields.every((field) => ["title", "description", "ogImage", "ogImageAlt", "noindex", "nofollow"].includes(field));
1241
+ }
1242
+ function unexpectedResponse(status) {
1243
+ return new CmsApiError("RootTale CMS API returned invalid route metadata", status, "unexpected_response");
1244
+ }
1245
+
1246
+ // src/metadata-contract-checksum.ts
1247
+ function canonicalize(value) {
1248
+ if (Array.isArray(value)) return value.map(canonicalize);
1249
+ if (!value || typeof value !== "object") return value;
1250
+ return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, canonicalize(child)]));
1251
+ }
1252
+ async function metadataContractChecksum(normalizedContract) {
1253
+ const json = JSON.stringify(canonicalize(normalizedContract));
1254
+ if (json === void 0) throw new Error("Metadata contract must be a normalized JSON value");
1255
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(json));
1256
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
1257
+ }
1258
+
1199
1259
  // src/server-cache-tags.ts
1200
1260
  var SETTINGS_CACHE_TAGS = [
1201
1261
  THEME_CACHE_TAG,
@@ -1204,7 +1264,9 @@ var SETTINGS_CACHE_TAGS = [
1204
1264
  BLOG_SETTINGS_CACHE_TAG,
1205
1265
  COLLECTIONS_CACHE_TAG,
1206
1266
  EXPOSURE_CACHE_TAG,
1207
- SITE_PATTERNS_CACHE_TAG
1267
+ SITE_PATTERNS_CACHE_TAG,
1268
+ METADATA_CACHE_TAG,
1269
+ SITE_METADATA_DEFAULTS_CACHE_TAG
1208
1270
  ];
1209
1271
 
1210
1272
  // src/seo.ts
@@ -1651,8 +1713,11 @@ export {
1651
1713
  LOCAL_BUSINESS_MAX_SERVICES,
1652
1714
  LOCAL_BUSINESS_SERVICE_MAX_LENGTH,
1653
1715
  MENUS_CACHE_TAG,
1716
+ METADATA_CACHE_TAG,
1717
+ MetadataDefaultRulesSchema,
1654
1718
  POST_FOOTER_PATTERN_SLOT,
1655
1719
  SETTINGS_CACHE_TAGS,
1720
+ SITE_METADATA_DEFAULTS_CACHE_TAG,
1656
1721
  SITE_PATTERNS_CACHE_TAG,
1657
1722
  THEME_CACHE_TAG,
1658
1723
  articleSchema,
@@ -1679,23 +1744,30 @@ export {
1679
1744
  fetchPostPreview,
1680
1745
  fetchPosts,
1681
1746
  fetchRedirects,
1747
+ fetchRouteMetadata,
1682
1748
  fetchSiteInquiries,
1683
1749
  fetchSiteInquiry,
1684
1750
  fetchSitePatterns,
1685
1751
  fetchTheme,
1686
1752
  formatKoreanPhoneNumber,
1753
+ formatMetadataDefaults,
1687
1754
  generateRssXml,
1688
1755
  inquiryErrorMessage,
1689
1756
  isPreviewExpiredError,
1690
1757
  jsonLdJson,
1691
1758
  jsonLdScript,
1692
1759
  localBusinessSchema,
1760
+ metadataContentImageUrl,
1761
+ metadataContractChecksum,
1693
1762
  organizationSchema,
1694
1763
  parseAttributionJson,
1695
1764
  parseInquiryRedirect,
1696
1765
  profilePageSchema,
1766
+ readMetadataValues,
1767
+ resolveMetadata,
1697
1768
  resolvePostDisplay,
1698
1769
  resolveSearchHitPath,
1770
+ routeMetadataCacheTag,
1699
1771
  rssItemsFromPosts,
1700
1772
  searchPosts,
1701
1773
  selectRelatedPosts,