@roottale/cms-client 0.31.0 → 0.35.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,31 @@
1
1
  # @roottale/cms-client
2
2
 
3
+ ## 0.35.0
4
+
5
+ ### Minor Changes
6
+
7
+ - cfddfc7: 커스텀 리다이렉트 (WP RankMath Redirections 패리티) — 운영자가 어드민 "설정 >
8
+ 주소 이동"에서 글이 아닌 임의 경로 이동 규칙(`/old-event → /promo`, 301/302)을
9
+ 정의하고, 사이트 미들웨어가 적용한다.
10
+ - `@roottale/cms-client`: `fetchRedirects()` 추가 — `GET /v1/cms/public/redirects`
11
+ 의 활성 규칙 목록을 가져온다(404 → 빈 배열 fail-soft).
12
+ - `@roottale/cms-renderer-next`: `createRedirectMiddleware()` 추가(+ 순수 함수
13
+ `matchRedirect`/`normalizeRedirectPath`), `/routes` 서브패스로 노출. 규칙을
14
+ TTL 캐시하고 API 실패 시 트래픽을 막지 않는다.
15
+ - `@roottale/cms-mcp`: `custom-redirects.md` 연동 가이드 + api-reference 의
16
+ `/redirects` 항목 + nextjs `middleware.ts` 예시.
17
+
18
+ ## 0.33.0
19
+
20
+ ### Minor Changes
21
+
22
+ - 2e6cbd0: ADR-0060 Amendment 1 (Option A) — 글의 섹션을 명시 필드로
23
+
24
+ `resolvePostCollection` 이 글의 명시 `collectionKey` 를 카테고리 slug 파생보다
25
+ 우선 사용한다. `RoutablePost`·`CmsPostContent` 에 `collectionKey` 추가, 공개 API
26
+ post 페이로드에 `collection_key` 포함. 미설정/구 서버면 기존 카테고리 파생으로
27
+ fallback(완전 하위호환). sitemap/feed/가드/아카이브가 명시 섹션을 따른다.
28
+
3
29
  ## 0.31.0
4
30
 
5
31
  ### Minor Changes
package/dist/server.d.ts CHANGED
@@ -1,212 +1,6 @@
1
1
  import { InquiryAttribution } from './attribution.js';
2
2
  export { parseAttributionJson } from './attribution.js';
3
3
 
4
- interface RssItemInput {
5
- title: string;
6
- /** 글 절대 URL. */
7
- link: string;
8
- /** 고유 식별자. 미지정 시 link 사용. */
9
- guid?: string;
10
- description?: string | null;
11
- /** 발행 시각. */
12
- pubDate?: string | Date | null;
13
- }
14
- interface RssFeedInput {
15
- /** 사이트/피드 제목. */
16
- title: string;
17
- /** 사이트 홈 URL. */
18
- link: string;
19
- /** 이 피드 자체의 URL (atom:self). */
20
- feedUrl: string;
21
- description?: string;
22
- /** 기본 "ko-KR". */
23
- language?: string;
24
- items: RssItemInput[];
25
- }
26
- /** RSS 2.0 XML 문자열 생성. */
27
- declare function generateRssXml(input: RssFeedInput): string;
28
- /** CmsPostContent[] → RSS item[]. postUrl 로 글 절대 URL 생성. */
29
- declare function rssItemsFromPosts(posts: CmsPostContent[], opts: {
30
- postUrl: (post: CmsPostContent) => string;
31
- }): RssItemInput[];
32
- type JsonLd = Record<string, unknown>;
33
- interface ArticleSchemaInput {
34
- headline: string;
35
- url: string;
36
- description?: string | null;
37
- image?: string | null;
38
- datePublished?: string | Date | null;
39
- dateModified?: string | Date | null;
40
- authorName?: string | null;
41
- publisherName?: string;
42
- publisherLogo?: string;
43
- }
44
- declare function articleSchema(input: ArticleSchemaInput): JsonLd;
45
- declare function breadcrumbSchema(items: {
46
- name: string;
47
- url: string;
48
- }[]): JsonLd;
49
- declare function organizationSchema(input: {
50
- name: string;
51
- url: string;
52
- logo?: string;
53
- sameAs?: string[];
54
- }): JsonLd;
55
- /**
56
- * `localBusinessSchema` 입력 — `fetchBusinessProfile` 의 반환값
57
- * (RootTaleBusinessProfile) 을 그대로 받도록 느슨하게 정의 (null/undefined 모두
58
- * 허용). 수동 구성도 가능.
59
- */
60
- interface LocalBusinessProfileInput {
61
- name: string;
62
- legalName?: string | null;
63
- /** Schema.org LocalBusiness 하위 타입. 미지정 시 "LocalBusiness". */
64
- businessType?: string | null;
65
- telephone?: string | null;
66
- email?: string | null;
67
- address?: {
68
- streetAddress: string;
69
- addressLocality: string;
70
- addressRegion?: string | null;
71
- postalCode?: string | null;
72
- } | null;
73
- geo?: {
74
- latitude: number;
75
- longitude: number;
76
- } | null;
77
- openingHours?: {
78
- days: string[];
79
- opens: string;
80
- closes: string;
81
- }[] | null;
82
- priceRange?: string | null;
83
- areaServed?: string[] | null;
84
- /** 외부 프로필 URL — 값들이 sameAs 로 들어간다 (네이버플레이스·구글 등). */
85
- profiles?: Record<string, string | null | undefined> | null;
86
- }
87
- /**
88
- * LocalBusiness JSON-LD — admin "운영 > 비즈니스 프로필" 의
89
- * `fetchBusinessProfile` 결과로 layout 에 1회 렌더한다 (로컬 SEO).
90
- *
91
- * - `@type` 은 `[businessType, "LocalBusiness"]` 중복 제거 배열.
92
- * - `profiles` 의 URL 들은 `sameAs` 로 — 검색엔진이 네이버플레이스/구글
93
- * 비즈니스 프로필과 동일 사업장임을 연결한다.
94
- */
95
- declare function localBusinessSchema(profile: LocalBusinessProfileInput | RootTaleBusinessProfile, opts: {
96
- url: string;
97
- image?: string | null;
98
- }): JsonLd;
99
- declare function websiteSchema(input: {
100
- name: string;
101
- url: string;
102
- searchUrl?: string;
103
- }): JsonLd;
104
- declare function faqSchema(qa: {
105
- question: string;
106
- answer: string;
107
- }[]): JsonLd;
108
- /** JSON-LD 객체 → `<script type="application/ld+json">` HTML 문자열 (XSS-safe). */
109
- declare function jsonLdScript(schema: JsonLd | JsonLd[]): string;
110
-
111
- /**
112
- * `@roottale/cms-client` — 리드(문의) 접수 server-side 헬퍼.
113
- *
114
- * 외부 고객사 사이트가 *블로그 조회(`fetchPosts`)와 동일한 API key 하나* 로 리드도
115
- * 접수하기 위한 SSR-only 클라이언트. 브라우저가 아니라 사이트 서버(Server Action /
116
- * Route Handler)에서 호출해야 한다 — API key 는 절대 브라우저로 노출 금지
117
- * (ADR-0023 §5.1 #15). 키가 tenant 를 권위 식별하므로 `tenant_slug` 불필요.
118
- *
119
- * 사용 예 (Next.js server action):
120
- * ```ts
121
- * "use server";
122
- * import { submitInquiry } from "@roottale/cms-client/server";
123
- *
124
- * const result = await submitInquiry({
125
- * apiKey: process.env.ROOTTALE_API_KEY!, // fetchPosts 와 같은 키
126
- * fields: {
127
- * vertical: "tax",
128
- * contactName: name,
129
- * businessName: company,
130
- * email,
131
- * phone,
132
- * message,
133
- * privacyConsent: true,
134
- * },
135
- * });
136
- * if (result.ok) showSuccess(); // 그 키의 tenant /crm 에 도착
137
- * else showError(result.message);
138
- * ```
139
- *
140
- * intake API 는 302 `?ok=1` / `?err=*` 로 응답 — 본 헬퍼가 파싱해 구조화 결과 반환.
141
- */
142
-
143
- type InquiryVertical = "consulting" | "medical" | "tax" | "legal";
144
- /** intake API 의 `err` 코드 → 한국어 사용자 메시지. */
145
- declare const INQUIRY_ERROR_MESSAGES: Record<string, string>;
146
- interface SubmitInquiryFields {
147
- vertical: InquiryVertical;
148
- contactName: string;
149
- businessName: string;
150
- email: string;
151
- phone: string;
152
- /** 자유 문의 내용 (선택). */
153
- message?: string;
154
- /** 상담 분야 라벨 등 부가 메타 (선택). */
155
- consultationField?: string;
156
- /** 현재 사이트 URL (선택). */
157
- currentSiteUrl?: string;
158
- /** 개인정보 수집·이용 동의 (필수 true). */
159
- privacyConsent: boolean;
160
- /** 의료 vertical 의 국외이전 동의 (medical 일 때 필수). */
161
- overseasTransferConsent?: boolean;
162
- /**
163
- * "patient"(고객사 방문자 문의, 기본) | "sales"(RootTale 자체 B2B 영업 리드).
164
- * sales 는 medical 국외이전 동의 게이트 면제.
165
- */
166
- leadKind?: "patient" | "sales";
167
- /**
168
- * 표준 필드 외에 폼이 받은 임의 항목(key→value)을 있는 그대로 전달. intake 가
169
- * 암호화 보관하고 CRM 상세에서 노출한다. 예약 키(표준 필드)·`_` 접두 키는 무시.
170
- * 주의: 개인정보가 담길 수 있으므로 폼의 동의 고지에 수집 항목을 반영할 것.
171
- */
172
- extras?: Record<string, string>;
173
- /**
174
- * 유입 first-touch 어트리뷰션 (ADR-0048 §2, 선택). 브라우저에서
175
- * `readAttribution()`(`@roottale/cms-client/attribution`)으로 읽어 Server
176
- * Action 인자로 넘긴 값을 그대로 전달하면 된다. CRM 에 "이 문의가 어느
177
- * 글/링크/QR 에서 왔는지"가 표시된다. null/undefined 면 미동반 제출.
178
- */
179
- attribution?: InquiryAttribution | null;
180
- }
181
- interface SubmitInquiryOptions {
182
- /** `rtlk_cust_*` API key — `fetchPosts` 와 동일한 키. Required. */
183
- apiKey: string;
184
- /** API base URL 오버라이드. 기본 `https://api.roottale.com`. */
185
- baseUrl?: string;
186
- fields: SubmitInquiryFields;
187
- signal?: AbortSignal;
188
- }
189
- type SubmitInquiryResult = {
190
- ok: true;
191
- } | {
192
- ok: false;
193
- code: string | null;
194
- message: string;
195
- };
196
- /** `err` 코드를 사용자 메시지로 (미지의 코드는 generic). */
197
- declare function inquiryErrorMessage(code: string | null | undefined): string;
198
- /**
199
- * intake API 의 302 Location (`?ok=1` / `?err=*`) 을 구조화 결과로 파싱.
200
- * 외부 사이트가 ERROR_MESSAGES·파싱을 각자 재구현하지 않도록 공유.
201
- */
202
- declare function parseInquiryRedirect(location: string | null, base?: string): SubmitInquiryResult;
203
- declare function formatKoreanPhoneNumber(input: string): string;
204
- /**
205
- * 리드를 RootTale intake API 로 제출 (server-to-server, Bearer 인증).
206
- * 키의 tenant 로 라우팅되어 admin(mysite.roottale.com) /crm 에서 관리된다.
207
- */
208
- declare function submitInquiry(options: SubmitInquiryOptions): Promise<SubmitInquiryResult>;
209
-
210
4
  /**
211
5
  * `@roottale/cms-client/server` — RootTale CMS Public API server-side fetch client.
212
6
  *
@@ -243,6 +37,11 @@ interface CmsPostContent {
243
37
  siteId: string;
244
38
  /** WP post type. */
245
39
  type: CmsPostType;
40
+ /**
41
+ * ADR-0060 Amendment 1 — 글의 명시 섹션(content collection) key. 라우팅/스트림
42
+ * 판정에 카테고리 파생보다 우선. 구 서버/미설정이면 null.
43
+ */
44
+ collectionKey: string | null;
246
45
  /** Lowercase kebab-case slug (1-120 chars). */
247
46
  slug: string;
248
47
  title: string;
@@ -318,6 +117,12 @@ interface FetchPostOptions {
318
117
  siteId?: string;
319
118
  signal?: AbortSignal;
320
119
  }
120
+ declare class CmsApiError extends Error {
121
+ readonly status: number;
122
+ readonly code: string;
123
+ constructor(message: string, status: number, code: string);
124
+ }
125
+
321
126
  /**
322
127
  * 외부 사이트가 cms-renderer 에 주입할 RootTale 디자인 토큰.
323
128
  * admin /design (mysite.roottale.com) 에서 저장한 값과 동일한 shape.
@@ -390,6 +195,9 @@ interface FetchAnalyticsOptions {
390
195
  * 동의는 항상 fail-closed(런타임이 처리)이므로 키 노출 위험 없음(public id).
391
196
  */
392
197
  declare function fetchAnalyticsConfig(options: FetchAnalyticsOptions): Promise<SiteAnalyticsTags>;
198
+ declare function fetchPosts(options: FetchPostsOptions): Promise<CmsPostContentPage>;
199
+ declare function fetchPost(options: FetchPostOptions): Promise<CmsPostContent | null>;
200
+
393
201
  /**
394
202
  * 블로그 표시 설정 — admin /design 의 "블로그 표시" 에서 저장한 사이트 기본값.
395
203
  * renderer 가 prop 미지정 시 이 값을 기본으로 적용 (글별 override 는 post 의
@@ -466,6 +274,33 @@ declare function fetchCollections(options: FetchCollectionsOptions): Promise<Cms
466
274
  declare const fetchCollectionsSafeDefault: CmsRouteCollection[];
467
275
  /** fetch 실패/미설정 시 사용할 안전 기본값 (기존 렌더러 동작). */
468
276
  declare const DEFAULT_BLOG_SETTINGS: RootTaleBlogSettings;
277
+ interface ResolvedPostDisplay {
278
+ showTableOfContents: boolean;
279
+ showAuthor: boolean;
280
+ showDate: boolean;
281
+ showAuthorCard: boolean;
282
+ tocTitle: string;
283
+ authorCardEyebrow: string;
284
+ authorCardDescription: string | null;
285
+ authorProfileName: string | null;
286
+ authorProfileBio: string | null;
287
+ authorProfileImageUrl: string | null;
288
+ authorProfileImageRadius: "circle" | "rounded" | "square";
289
+ authorProfileImagePositionX: number;
290
+ authorProfileImagePositionY: number;
291
+ }
292
+ /**
293
+ * 글 1건의 실제 표시 여부 결정. 우선순위:
294
+ * 명시 prop > 글별 override(post.metaJson.display) > 사이트 기본값(settings).
295
+ * tocTitle: 명시 prop > settings.tocTitle > "목차".
296
+ */
297
+ declare function resolvePostDisplay(post: {
298
+ metaJson?: Record<string, unknown> | null;
299
+ }, settings: RootTaleBlogSettings, explicit?: {
300
+ showTableOfContents?: boolean;
301
+ tableOfContentsTitle?: string;
302
+ }): ResolvedPostDisplay;
303
+
469
304
  /** Schema.org LocalBusiness 하위 타입 (admin "운영 > 비즈니스 프로필" 의 업종). */
470
305
  type BusinessType = "LocalBusiness" | "ProfessionalService" | "AccountingService" | "LegalService" | "MedicalClinic" | "Dentist" | "RealEstateAgent" | "Restaurant" | "BeautySalon";
471
306
  /** Schema.org openingHours 요일 약어. */
@@ -560,39 +395,7 @@ declare function businessProfileFromWire(json: BusinessProfileWire): RootTaleBus
560
395
  * `.catch(() => null)` 등으로 fallback.
561
396
  */
562
397
  declare function fetchBusinessProfile(options: FetchBusinessProfileOptions): Promise<RootTaleBusinessProfile | null>;
563
- interface ResolvedPostDisplay {
564
- showTableOfContents: boolean;
565
- showAuthor: boolean;
566
- showDate: boolean;
567
- showAuthorCard: boolean;
568
- tocTitle: string;
569
- authorCardEyebrow: string;
570
- authorCardDescription: string | null;
571
- authorProfileName: string | null;
572
- authorProfileBio: string | null;
573
- authorProfileImageUrl: string | null;
574
- authorProfileImageRadius: "circle" | "rounded" | "square";
575
- authorProfileImagePositionX: number;
576
- authorProfileImagePositionY: number;
577
- }
578
- /**
579
- * 글 1건의 실제 표시 여부 결정. 우선순위:
580
- * 명시 prop > 글별 override(post.metaJson.display) > 사이트 기본값(settings).
581
- * tocTitle: 명시 prop > settings.tocTitle > "목차".
582
- */
583
- declare function resolvePostDisplay(post: {
584
- metaJson?: Record<string, unknown> | null;
585
- }, settings: RootTaleBlogSettings, explicit?: {
586
- showTableOfContents?: boolean;
587
- tableOfContentsTitle?: string;
588
- }): ResolvedPostDisplay;
589
- declare class CmsApiError extends Error {
590
- readonly status: number;
591
- readonly code: string;
592
- constructor(message: string, status: number, code: string);
593
- }
594
- declare function fetchPosts(options: FetchPostsOptions): Promise<CmsPostContentPage>;
595
- declare function fetchPost(options: FetchPostOptions): Promise<CmsPostContent | null>;
398
+
596
399
  interface SearchPostsOptions {
597
400
  apiKey: string;
598
401
  baseUrl?: string;
@@ -662,5 +465,232 @@ interface FetchMenusOptions {
662
465
  declare function fetchMenu(options: FetchMenuOptions): Promise<RootTaleMenu | null>;
663
466
  /** 사이트의 네비게이션 메뉴 전체. 구 서버(라우트 미배포)는 404 — 빈 배열. */
664
467
  declare function fetchMenus(options: FetchMenusOptions): Promise<RootTaleMenu[]>;
468
+ interface RootTaleRedirect {
469
+ id: string;
470
+ /** 정규화된 출발 경로 (앞 슬래시, 쿼리 제외). 예: `/old-event`. */
471
+ fromPath: string;
472
+ /** 도착지 — 사이트 내부 경로(`/promo`) 또는 절대 URL. */
473
+ toTarget: string;
474
+ /** 301 영구 / 302 임시. */
475
+ statusCode: 301 | 302;
476
+ }
477
+ interface FetchRedirectsOptions {
478
+ apiKey: string;
479
+ baseUrl?: string;
480
+ siteId?: string;
481
+ signal?: AbortSignal;
482
+ }
483
+ /**
484
+ * 사이트의 활성 커스텀 리다이렉트 규칙 전체. 고객 사이트 middleware
485
+ * (`createRedirectMiddleware`)가 요청 경로 매칭에 사용한다. 구 서버(라우트
486
+ * 미배포)는 404 — 빈 배열로 fail-soft (트래픽을 막지 않음).
487
+ */
488
+ declare function fetchRedirects(options: FetchRedirectsOptions): Promise<RootTaleRedirect[]>;
489
+
490
+ interface RssItemInput {
491
+ title: string;
492
+ /** 글 절대 URL. */
493
+ link: string;
494
+ /** 고유 식별자. 미지정 시 link 사용. */
495
+ guid?: string;
496
+ description?: string | null;
497
+ /** 발행 시각. */
498
+ pubDate?: string | Date | null;
499
+ }
500
+ interface RssFeedInput {
501
+ /** 사이트/피드 제목. */
502
+ title: string;
503
+ /** 사이트 홈 URL. */
504
+ link: string;
505
+ /** 이 피드 자체의 URL (atom:self). */
506
+ feedUrl: string;
507
+ description?: string;
508
+ /** 기본 "ko-KR". */
509
+ language?: string;
510
+ items: RssItemInput[];
511
+ }
512
+ /** RSS 2.0 XML 문자열 생성. */
513
+ declare function generateRssXml(input: RssFeedInput): string;
514
+ /** CmsPostContent[] → RSS item[]. postUrl 로 글 절대 URL 생성. */
515
+ declare function rssItemsFromPosts(posts: CmsPostContent[], opts: {
516
+ postUrl: (post: CmsPostContent) => string;
517
+ }): RssItemInput[];
518
+ type JsonLd = Record<string, unknown>;
519
+ interface ArticleSchemaInput {
520
+ headline: string;
521
+ url: string;
522
+ description?: string | null;
523
+ image?: string | null;
524
+ datePublished?: string | Date | null;
525
+ dateModified?: string | Date | null;
526
+ authorName?: string | null;
527
+ publisherName?: string;
528
+ publisherLogo?: string;
529
+ }
530
+ declare function articleSchema(input: ArticleSchemaInput): JsonLd;
531
+ declare function breadcrumbSchema(items: {
532
+ name: string;
533
+ url: string;
534
+ }[]): JsonLd;
535
+ declare function organizationSchema(input: {
536
+ name: string;
537
+ url: string;
538
+ logo?: string;
539
+ sameAs?: string[];
540
+ }): JsonLd;
541
+ /**
542
+ * `localBusinessSchema` 입력 — `fetchBusinessProfile` 의 반환값
543
+ * (RootTaleBusinessProfile) 을 그대로 받도록 느슨하게 정의 (null/undefined 모두
544
+ * 허용). 수동 구성도 가능.
545
+ */
546
+ interface LocalBusinessProfileInput {
547
+ name: string;
548
+ legalName?: string | null;
549
+ /** Schema.org LocalBusiness 하위 타입. 미지정 시 "LocalBusiness". */
550
+ businessType?: string | null;
551
+ telephone?: string | null;
552
+ email?: string | null;
553
+ address?: {
554
+ streetAddress: string;
555
+ addressLocality: string;
556
+ addressRegion?: string | null;
557
+ postalCode?: string | null;
558
+ } | null;
559
+ geo?: {
560
+ latitude: number;
561
+ longitude: number;
562
+ } | null;
563
+ openingHours?: {
564
+ days: string[];
565
+ opens: string;
566
+ closes: string;
567
+ }[] | null;
568
+ priceRange?: string | null;
569
+ areaServed?: string[] | null;
570
+ /** 외부 프로필 URL — 값들이 sameAs 로 들어간다 (네이버플레이스·구글 등). */
571
+ profiles?: Record<string, string | null | undefined> | null;
572
+ }
573
+ /**
574
+ * LocalBusiness JSON-LD — admin "운영 > 비즈니스 프로필" 의
575
+ * `fetchBusinessProfile` 결과로 layout 에 1회 렌더한다 (로컬 SEO).
576
+ *
577
+ * - `@type` 은 `[businessType, "LocalBusiness"]` 중복 제거 배열.
578
+ * - `profiles` 의 URL 들은 `sameAs` 로 — 검색엔진이 네이버플레이스/구글
579
+ * 비즈니스 프로필과 동일 사업장임을 연결한다.
580
+ */
581
+ declare function localBusinessSchema(profile: LocalBusinessProfileInput | RootTaleBusinessProfile, opts: {
582
+ url: string;
583
+ image?: string | null;
584
+ }): JsonLd;
585
+ declare function websiteSchema(input: {
586
+ name: string;
587
+ url: string;
588
+ searchUrl?: string;
589
+ }): JsonLd;
590
+ declare function faqSchema(qa: {
591
+ question: string;
592
+ answer: string;
593
+ }[]): JsonLd;
594
+ /** JSON-LD 객체 → `<script type="application/ld+json">` HTML 문자열 (XSS-safe). */
595
+ declare function jsonLdScript(schema: JsonLd | JsonLd[]): string;
596
+
597
+ /**
598
+ * `@roottale/cms-client` — 리드(문의) 접수 server-side 헬퍼.
599
+ *
600
+ * 외부 고객사 사이트가 *블로그 조회(`fetchPosts`)와 동일한 API key 하나* 로 리드도
601
+ * 접수하기 위한 SSR-only 클라이언트. 브라우저가 아니라 사이트 서버(Server Action /
602
+ * Route Handler)에서 호출해야 한다 — API key 는 절대 브라우저로 노출 금지
603
+ * (ADR-0023 §5.1 #15). 키가 tenant 를 권위 식별하므로 `tenant_slug` 불필요.
604
+ *
605
+ * 사용 예 (Next.js server action):
606
+ * ```ts
607
+ * "use server";
608
+ * import { submitInquiry } from "@roottale/cms-client/server";
609
+ *
610
+ * const result = await submitInquiry({
611
+ * apiKey: process.env.ROOTTALE_API_KEY!, // fetchPosts 와 같은 키
612
+ * fields: {
613
+ * vertical: "tax",
614
+ * contactName: name,
615
+ * businessName: company,
616
+ * email,
617
+ * phone,
618
+ * message,
619
+ * privacyConsent: true,
620
+ * },
621
+ * });
622
+ * if (result.ok) showSuccess(); // 그 키의 tenant /crm 에 도착
623
+ * else showError(result.message);
624
+ * ```
625
+ *
626
+ * intake API 는 302 `?ok=1` / `?err=*` 로 응답 — 본 헬퍼가 파싱해 구조화 결과 반환.
627
+ */
628
+
629
+ type InquiryVertical = "consulting" | "medical" | "tax" | "legal";
630
+ /** intake API 의 `err` 코드 → 한국어 사용자 메시지. */
631
+ declare const INQUIRY_ERROR_MESSAGES: Record<string, string>;
632
+ interface SubmitInquiryFields {
633
+ vertical: InquiryVertical;
634
+ contactName: string;
635
+ businessName: string;
636
+ email: string;
637
+ phone: string;
638
+ /** 자유 문의 내용 (선택). */
639
+ message?: string;
640
+ /** 상담 분야 라벨 등 부가 메타 (선택). */
641
+ consultationField?: string;
642
+ /** 현재 사이트 URL (선택). */
643
+ currentSiteUrl?: string;
644
+ /** 개인정보 수집·이용 동의 (필수 true). */
645
+ privacyConsent: boolean;
646
+ /** 의료 vertical 의 국외이전 동의 (medical 일 때 필수). */
647
+ overseasTransferConsent?: boolean;
648
+ /**
649
+ * "patient"(고객사 방문자 문의, 기본) | "sales"(RootTale 자체 B2B 영업 리드).
650
+ * sales 는 medical 국외이전 동의 게이트 면제.
651
+ */
652
+ leadKind?: "patient" | "sales";
653
+ /**
654
+ * 표준 필드 외에 폼이 받은 임의 항목(key→value)을 있는 그대로 전달. intake 가
655
+ * 암호화 보관하고 CRM 상세에서 노출한다. 예약 키(표준 필드)·`_` 접두 키는 무시.
656
+ * 주의: 개인정보가 담길 수 있으므로 폼의 동의 고지에 수집 항목을 반영할 것.
657
+ */
658
+ extras?: Record<string, string>;
659
+ /**
660
+ * 유입 first-touch 어트리뷰션 (ADR-0048 §2, 선택). 브라우저에서
661
+ * `readAttribution()`(`@roottale/cms-client/attribution`)으로 읽어 Server
662
+ * Action 인자로 넘긴 값을 그대로 전달하면 된다. CRM 에 "이 문의가 어느
663
+ * 글/링크/QR 에서 왔는지"가 표시된다. null/undefined 면 미동반 제출.
664
+ */
665
+ attribution?: InquiryAttribution | null;
666
+ }
667
+ interface SubmitInquiryOptions {
668
+ /** `rtlk_cust_*` API key — `fetchPosts` 와 동일한 키. Required. */
669
+ apiKey: string;
670
+ /** API base URL 오버라이드. 기본 `https://api.roottale.com`. */
671
+ baseUrl?: string;
672
+ fields: SubmitInquiryFields;
673
+ signal?: AbortSignal;
674
+ }
675
+ type SubmitInquiryResult = {
676
+ ok: true;
677
+ } | {
678
+ ok: false;
679
+ code: string | null;
680
+ message: string;
681
+ };
682
+ /** `err` 코드를 사용자 메시지로 (미지의 코드는 generic). */
683
+ declare function inquiryErrorMessage(code: string | null | undefined): string;
684
+ /**
685
+ * intake API 의 302 Location (`?ok=1` / `?err=*`) 을 구조화 결과로 파싱.
686
+ * 외부 사이트가 ERROR_MESSAGES·파싱을 각자 재구현하지 않도록 공유.
687
+ */
688
+ declare function parseInquiryRedirect(location: string | null, base?: string): SubmitInquiryResult;
689
+ declare function formatKoreanPhoneNumber(input: string): string;
690
+ /**
691
+ * 리드를 RootTale intake API 로 제출 (server-to-server, Bearer 인증).
692
+ * 키의 tenant 로 라우팅되어 admin(mysite.roottale.com) /crm 에서 관리된다.
693
+ */
694
+ declare function submitInquiry(options: SubmitInquiryOptions): Promise<SubmitInquiryResult>;
665
695
 
666
- export { type AnalyticsTagConfig, type AnalyticsTagProvider, type ArticleSchemaInput, type BusinessDayCode, type BusinessOpeningHours, type BusinessType, CMS_CLIENT_VERSION, CmsApiError, type CmsPostContent, type CmsPostContentPage, type CmsPostType, type CmsRouteCollection, type CmsSearchHit, type CmsTaxonomyKind, type CmsTermRef, DEFAULT_BLOG_SETTINGS, type FetchAnalyticsOptions, type FetchBlogSettingsOptions, type FetchBusinessProfileOptions, type FetchCollectionsOptions, type FetchMenuOptions, type FetchMenusOptions, type FetchPostOptions, type FetchPostsOptions, type FetchThemeOptions, INQUIRY_ERROR_MESSAGES, InquiryAttribution, type InquiryVertical, type LocalBusinessProfileInput, type ResolvedPostDisplay, type RootTaleBlogSettings, type RootTaleBusinessProfile, type RootTaleMenu, type RootTaleMenuItem, type RootTaleTheme, type RssFeedInput, type RssItemInput, type SearchPostsOptions, type SiteAnalyticsTags, type SubmitInquiryFields, type SubmitInquiryOptions, type SubmitInquiryResult, articleSchema, breadcrumbSchema, businessProfileFromWire, faqSchema, fetchAnalyticsConfig, fetchBlogSettings, fetchBusinessProfile, fetchCollections, fetchCollectionsSafeDefault, fetchMenu, fetchMenus, fetchPost, fetchPosts, fetchTheme, formatKoreanPhoneNumber, generateRssXml, inquiryErrorMessage, jsonLdScript, localBusinessSchema, organizationSchema, parseInquiryRedirect, resolvePostDisplay, rssItemsFromPosts, searchPosts, submitInquiry, websiteSchema };
696
+ export { type AnalyticsTagConfig, type AnalyticsTagProvider, type ArticleSchemaInput, type BusinessDayCode, type BusinessOpeningHours, type BusinessType, CMS_CLIENT_VERSION, CmsApiError, type CmsPostContent, type CmsPostContentPage, type CmsPostType, type CmsRouteCollection, type CmsSearchHit, type CmsTaxonomyKind, type CmsTermRef, DEFAULT_BLOG_SETTINGS, type FetchAnalyticsOptions, type FetchBlogSettingsOptions, type FetchBusinessProfileOptions, type FetchCollectionsOptions, type FetchMenuOptions, type FetchMenusOptions, type FetchPostOptions, type FetchPostsOptions, type FetchRedirectsOptions, type FetchThemeOptions, INQUIRY_ERROR_MESSAGES, InquiryAttribution, type InquiryVertical, type LocalBusinessProfileInput, type ResolvedPostDisplay, type RootTaleBlogSettings, type RootTaleBusinessProfile, type RootTaleMenu, type RootTaleMenuItem, type RootTaleRedirect, type RootTaleTheme, type RssFeedInput, type RssItemInput, type SearchPostsOptions, type SiteAnalyticsTags, type SubmitInquiryFields, type SubmitInquiryOptions, type SubmitInquiryResult, articleSchema, breadcrumbSchema, businessProfileFromWire, faqSchema, fetchAnalyticsConfig, fetchBlogSettings, fetchBusinessProfile, fetchCollections, fetchCollectionsSafeDefault, fetchMenu, fetchMenus, fetchPost, fetchPosts, fetchRedirects, fetchTheme, formatKoreanPhoneNumber, generateRssXml, inquiryErrorMessage, jsonLdScript, localBusinessSchema, organizationSchema, parseInquiryRedirect, resolvePostDisplay, rssItemsFromPosts, searchPosts, submitInquiry, websiteSchema };