@zalkera/client 0.6.3 → 0.6.4
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/README.md +17 -7
- package/llms.txt +99 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,9 +28,19 @@
|
|
|
28
28
|
| `OnequeClient` | `ZalkeraClient` |
|
|
29
29
|
| `OnequeClientOptions` | `ZalkeraClientOptions` |
|
|
30
30
|
|
|
31
|
-
**환경변수
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
**환경변수 이름도 `ZALKERA_*` 로 바뀌었다**(2026-07-26 컷오버). 관리형 서빙(잘커라가 띄우는 사이트)은
|
|
32
|
+
인프라가 새 이름으로 주입하므로 할 일이 없다. **자기 인프라로 돌리는 BYO 사이트는 `.env` 의 이름을 직접
|
|
33
|
+
바꿔야 한다** — 구 이름 폴백은 제거됐다:
|
|
34
|
+
|
|
35
|
+
| 옛 이름 | 새 이름 |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `ONEQUE_API_BASE` | `ZALKERA_API_BASE` |
|
|
38
|
+
| `ONEQUE_TENANT` | `ZALKERA_TENANT` |
|
|
39
|
+
| `ONEQUE_STOREFRONT_KEY` | `ZALKERA_STOREFRONT_KEY` |
|
|
40
|
+
| `ONEQUE_SITE_URL` | `ZALKERA_SITE_URL` |
|
|
41
|
+
|
|
42
|
+
동적 SSR 정당화 마커도 `zalkera-allow-dynamic` 이 새 이름이지만 기존 `oneque-`/`oneq-` 마커를 계속 수용한다.
|
|
43
|
+
`x-oneque-revalidate-secret` **헤더 이름은 그대로다** — 백엔드가 보내는 와이어 이름이라 별도 전환이 필요하다.
|
|
34
44
|
|
|
35
45
|
기능·API 동작은 동일하다 — 이름만 바뀌었다.
|
|
36
46
|
|
|
@@ -52,9 +62,9 @@ npm install @zalkera/client
|
|
|
52
62
|
import {createZalkeraClient} from "@zalkera/client";
|
|
53
63
|
|
|
54
64
|
export const zalkera = createZalkeraClient({
|
|
55
|
-
baseUrl: process.env.
|
|
56
|
-
tenant: process.env.
|
|
57
|
-
secretKey: process.env.
|
|
65
|
+
baseUrl: process.env.ZALKERA_API_BASE!, // 예: http://localhost:8100 — /api는 안 붙인다
|
|
66
|
+
tenant: process.env.ZALKERA_TENANT!, // 모든 요청에 X-Tenant로 실린다
|
|
67
|
+
secretKey: process.env.ZALKERA_STOREFRONT_KEY, // (선택) 서버 시크릿 — "시크릿 키" 참고
|
|
58
68
|
});
|
|
59
69
|
```
|
|
60
70
|
|
|
@@ -202,7 +212,7 @@ await zalkera.submitInquiry(input, {clientIp: ip});
|
|
|
202
212
|
|
|
203
213
|
### 🔒 비밀 취급 규칙
|
|
204
214
|
|
|
205
|
-
- ✅ **서버 `.env`에만** 둔다(예: `
|
|
215
|
+
- ✅ **서버 `.env`에만** 둔다(예: `ZALKERA_STOREFRONT_KEY`).
|
|
206
216
|
- ❌ `NEXT_PUBLIC_*` 접두사 금지 — 클라이언트 번들에 박힌다.
|
|
207
217
|
- ❌ 클라이언트 컴포넌트 import 금지.
|
|
208
218
|
- 유출 시: 콘솔에서 revoke → 재발급 → 서버 env 교체 배포.
|
package/llms.txt
CHANGED
|
@@ -22,13 +22,13 @@ npm i ./zalkera-client-0.6.0.tgz # 배포 tarball(심볼릭 링크 말고 tarb
|
|
|
22
22
|
import { createZalkeraClient } from "@zalkera/client";
|
|
23
23
|
|
|
24
24
|
export const zalkera = createZalkeraClient({
|
|
25
|
-
baseUrl: process.env.
|
|
26
|
-
tenant: process.env.
|
|
27
|
-
secretKey: process.env.
|
|
25
|
+
baseUrl: process.env.ZALKERA_API_BASE!, // 예: https://api.zalkera.com (뒤에 /api 붙이지 않는다)
|
|
26
|
+
tenant: process.env.ZALKERA_TENANT!, // 이 사이트의 테넌트 코드. 모든 요청에 X-Tenant 로 실린다
|
|
27
|
+
secretKey: process.env.ZALKERA_STOREFRONT_KEY, // (선택·oqsk_…) 서버 시크릿. 있으면 X-Storefront-Key 로 실린다
|
|
28
28
|
});
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
- `.env`: `
|
|
31
|
+
- `.env`: `ZALKERA_API_BASE`, `ZALKERA_TENANT`, (선택)`ZALKERA_STOREFRONT_KEY`. **절대 클라이언트 컴포넌트에서 import 하지 말 것** — baseUrl·시크릿 노출.
|
|
32
32
|
- `secretKey`는 진짜 비밀이다 — `NEXT_PUBLIC_*` 접두사·클라이언트 번들 금지. 서버 `.env`에만. 안 주면 종전대로 `tenant`(X-Tenant)만으로 동작(하위호환).
|
|
33
33
|
- 모든 메서드는 성공 시 데이터를, 실패 시 `ZalkeraError`(`.status`, `.code`, `.isRateLimited`, `.isStorefrontKeyError`, `.validationErrors`)를 던진다.
|
|
34
34
|
|
|
@@ -40,7 +40,7 @@ route handler 를 두고 브라우저 ↔ route handler ↔ 잘커라로 프록
|
|
|
40
40
|
|
|
41
41
|
**`secretKey`(스토어프론트 서버 시크릿)** — `X-Tenant` 무인증 신뢰를 키로 승격한 것. 있으면 전
|
|
42
42
|
요청에 `X-Storefront-Key` 로 실려 백엔드가 테넌트 신원을 증명한다(키가 정본). 취급 규칙:
|
|
43
|
-
- 오직 서버 `.env`(`
|
|
43
|
+
- 오직 서버 `.env`(`ZALKERA_STOREFRONT_KEY`)에만. `NEXT_PUBLIC_*`·클라이언트 번들 절대 금지 — 이 클라이언트가 서버 전용인 이유.
|
|
44
44
|
- 파트너 콘솔에서 발급(원문 1회 노출·분실 시 회전). 유출 시 콘솔에서 revoke·재발급.
|
|
45
45
|
- 안 주면 종전대로 `tenant`만으로 동작(하위호환). 백엔드가 키를 요구하는 설정이면 키 없이 401 `STOREFRONT_KEY_REQUIRED`.
|
|
46
46
|
|
|
@@ -299,7 +299,7 @@ if (booking.orderNo) { // status=PENDING
|
|
|
299
299
|
- ✅ **`sitemap.ts`·`robots.ts` 필수.** sitemap 엔 **실제로 존재하는 공개 라우트만**(홈·상품 상세·콘텐츠).
|
|
300
300
|
robots 는 세션·쓰기 경로(`/api/`·`/cart`·`/checkout`·`/mypage`·`/orders`·`/login`·`/auth`)만 막고
|
|
301
301
|
공개 카탈로그는 전부 연다. **AI 크롤러(GPTBot·ClaudeBot 등)를 막지 마라** — 발견 경로를 우리 손으로 닫는 것이다.
|
|
302
|
-
- ✅ **절대 URL.** JSON-LD·sitemap·robots 는 상대경로 불가. 단일 사이트는 env(`
|
|
302
|
+
- ✅ **절대 URL.** JSON-LD·sitemap·robots 는 상대경로 불가. 단일 사이트는 env(`ZALKERA_SITE_URL`),
|
|
303
303
|
멀티테넌트는 요청 호스트에서 만든다.
|
|
304
304
|
- ✅ **ISR 유지.** JSON-LD 를 넣는다고 페이지를 동적으로 만들지 마라 — 전부 프리렌더 값이다(§5 ISR-우선과 한 몸).
|
|
305
305
|
`sitemap.ts`/`robots.ts` 는 page 가 아니라 크롤러 엔드포인트라 호스트 기반 동적이 정당한 예외다.
|
|
@@ -406,3 +406,96 @@ catch (e) {
|
|
|
406
406
|
- 테넌트 색은 `getSiteConfig().themeColors` → **루트 `layout` 이 `<html>` 의 CSS 변수로 주입**한다. 이 배선
|
|
407
407
|
(layout 의 테마 주입·`src/lib/theme.ts`)을 제거·우회하지 마라. **색 변경에 코드 수정이 필요하면 설계 위반**이다 —
|
|
408
408
|
콘솔에서 색을 바꾸면 재코딩 없이 반영되는 것이 정상이다.
|
|
409
|
+
|
|
410
|
+
**UI 프리미티브 관용구 — 있는 것을 쓴다**
|
|
411
|
+
- `cn(...)` = `twMerge(clsx(...))`. 조건부 클래스와 **덮어쓰기**(`cn("px-4", props.className)` 에서 `px-6` 이 이김)를
|
|
412
|
+
둘 다 처리한다. 문자열 이어붙이기로 대체하지 마라 — 덮어쓰기가 조용히 안 먹는다.
|
|
413
|
+
- 변형(variant)은 **cva** 로 선언한다(`ui/Button.tsx` 가 본보기). 조건문으로 클래스 문자열을 조립하지 마라.
|
|
414
|
+
- `ui/Button`(버튼) · `buttonClasses(variant)`(`<Link>`·`<a>` 용 문자열) · `ui/Card`(카드) · `ui/Icon`(아이콘).
|
|
415
|
+
- 새 프리미티브가 정말 필요하면 shadcn/ui 에서 **발췌**해 온다(CLI 상시 설치 아님). 발췌물은 아래 재작성 표를
|
|
416
|
+
적용하고 `asChild`/`Slot` 을 제거해 **Radix 의존 0** 을 유지한 뒤 `src/components/ui/` 에 커밋한다.
|
|
417
|
+
|
|
418
|
+
**shadcn 발췌 재작성 표 — 남의 토큰 어휘는 반입 금지**
|
|
419
|
+
shadcn 소스는 자기 변수층(`--card`·`--muted-foreground` …)을 전제한다. 우리는 그 층을 들이지 않는다(토큰 레이어가
|
|
420
|
+
둘이 되면 테넌트 색 주입의 우선순위가 사람 머리에 얹힌다). 발췌 시 **좌변을 우변으로 기계적으로 바꾼다**.
|
|
421
|
+
CI validator **S6** 가 좌변 잔존을 error 로 막는다.
|
|
422
|
+
|
|
423
|
+
| shadcn | 우리 |
|
|
424
|
+
|---|---|
|
|
425
|
+
| `bg-card` · `bg-popover` · `bg-muted` · `bg-accent` | `bg-surface` |
|
|
426
|
+
| `text-card-foreground` · `text-popover-foreground` · `text-accent-foreground` | `text-foreground` |
|
|
427
|
+
| `text-muted-foreground` | `text-muted` |
|
|
428
|
+
| `bg-destructive` | `bg-danger` |
|
|
429
|
+
| `text-destructive` | `text-danger` |
|
|
430
|
+
| `ring-ring` · `focus-visible:ring-*` | `ring-primary` |
|
|
431
|
+
| `border-input` | `border-border` |
|
|
432
|
+
|
|
433
|
+
⚠️ 우리 `muted` 는 **글자색**이고 shadcn 의 `muted` 는 배경이다. 그래서 `bg-muted`→`bg-surface`,
|
|
434
|
+
`text-muted-foreground`→`text-muted` 로 **갈라서** 옮긴다.
|
|
435
|
+
|
|
436
|
+
## 9. 섹션 어휘 — 페이지는 데이터로 그린다
|
|
437
|
+
|
|
438
|
+
고정 페이지(`getPage(slug)`)는 본문 HTML 만 있는 게 아니라 **섹션 배열**을 갖는다. 각 섹션은 `type`(코드 enum)과
|
|
439
|
+
`config`(타입별 JSON)뿐이고, 백엔드는 config 를 **파싱하지 않는다** — 스키마는 프론트 계약이다.
|
|
440
|
+
|
|
441
|
+
```ts
|
|
442
|
+
const page = await zalkera.getPage("home");
|
|
443
|
+
page.sections // [{ id, type: "HERO", sortOrder: 0, config: "{\"title\":\"…\"}" }, …]
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**왜 이 구조인가**: 콘텐츠가 마크업에 박히지 않고 계약을 타므로, 테넌트가 콘솔에서·"말로" 고친 것이 재코딩 없이
|
|
447
|
+
반영된다("FAQ 에 배송 질문 추가해줘"). **마크업에 문구를 하드코딩하면 이 경로가 죽는다.**
|
|
448
|
+
|
|
449
|
+
**규약**
|
|
450
|
+
- `config` 는 **문자열**이다. 파싱은 반드시 실패 허용으로(스타터의 `components/sections/parse.ts` 가 본보기 —
|
|
451
|
+
절대 throw 하지 않는다). 필수 필드가 없으면 **그 섹션만 안 그린다**. 페이지 전체가 죽으면 안 된다.
|
|
452
|
+
- **미지 타입은 조용히 건너뛴다.** 어휘는 append-only 라 백엔드가 타입을 늘려도 옛 사이트가 깨지지 않아야 한다.
|
|
453
|
+
`default:` 에서 에러·경고를 내지 마라 — 그게 계약이다.
|
|
454
|
+
- 이미지는 `assetId`(숫자)다. `getMediaUrl(id)`(스타터의 `mediaSrc()`)로 프록시 경유해 그린다.
|
|
455
|
+
- **모든 href 는 `lib/safeUrl` 을 태운다.** 콘솔 입력이라 `javascript:` 가 들어올 수 있다(저장형 XSS).
|
|
456
|
+
- 자유 HTML 은 없다 — plain text + 줄바꿈뿐(`whitespace-pre-wrap`).
|
|
457
|
+
- 타입·키를 **지어내지 마라.** 계약 정본은 `SECTION_CONTRACT`(이 패키지가 export)이고, 필요한 어휘가 없으면
|
|
458
|
+
만들지 말고 **보고**한다.
|
|
459
|
+
|
|
460
|
+
**어휘 (12종)** — `vertical` 은 콘솔 픽커 그룹핑용이지 사용 제한이 아니다(GENERAL 은 뷰티 테넌트도 쓴다).
|
|
461
|
+
|
|
462
|
+
| type | vertical | config (필수는 굵게) | JSON-LD |
|
|
463
|
+
|---|---|---|---|
|
|
464
|
+
| `HERO` | GENERAL | eyebrow?, **title**, subtitle?, ctaLabel?, ctaHref?, assetId? | — |
|
|
465
|
+
| `FEATURE_GRID` | GENERAL | title?, **items**[{icon?, **title**, body?}] | — |
|
|
466
|
+
| `TEXT_MEDIA` | GENERAL | title?, body?, assetId?, mediaSide?(`"left"`\|`"right"`), ctaLabel?, ctaHref? | — |
|
|
467
|
+
| `LOGO_WALL` | GENERAL | title?, **items**[{**assetId**, name?, href?}] | — |
|
|
468
|
+
| `STATS_BAND` | GENERAL | **items**[{**value**, **label**, suffix?}] — value 는 표시 문자열("1,200") | — |
|
|
469
|
+
| `TESTIMONIALS` | GENERAL | title?, **items**[{**quote**, author?, role?, assetId?}] | **없음(의도)** |
|
|
470
|
+
| `FAQ_LIST` | GENERAL | title?, **items**[{**question**, **answer**}] | `FAQPage` |
|
|
471
|
+
| `LEAD_CTA` | GENERAL | title?, body?, interest?, quick? | — (`submitLead` 계약) |
|
|
472
|
+
| `SERVICE_MENU` | BEAUTY | productIds?, categorySlug? | — |
|
|
473
|
+
| `BEFORE_AFTER_GALLERY` | BEAUTY | **items**[{**beforeAssetId**, **afterAssetId**, caption?}] | — |
|
|
474
|
+
| `BOOKING_CTA` | BEAUTY | **productId**, label? | — |
|
|
475
|
+
| `DOCTOR_INTRO` | BEAUTY | **name**, title?, photoAssetId?, bio? | — |
|
|
476
|
+
|
|
477
|
+
**섹션별 주의**
|
|
478
|
+
- `FAQ_LIST` 는 네이티브 `<details>/<summary>` 로 그린다 — 런타임 JS 0, 접근성 내장, 그리고 **닫힌 답변도
|
|
479
|
+
SSR 마크업에 실린다**(AI 답변엔진이 읽는다). 아코디언 라이브러리를 쓰지 마라. `FAQPage` JSON-LD 를 함께 낸다.
|
|
480
|
+
- `TESTIMONIALS` 에 **`Review`·`AggregateRating` 을 내지 마라.** 자사 사이트의 자사 후기에 별점을 붙이는 것은
|
|
481
|
+
self-serving reviews 정책 위반이라 제재 대상이다. 이건 빠뜨린 게 아니라 결정이다.
|
|
482
|
+
- `LEAD_CTA` 는 스타터의 `components/LeadForm` 을 그대로 쓴다(UTM·클릭ID 추적·레이트리밋 안내 동봉).
|
|
483
|
+
전환 부품을 두 벌 만들지 마라. 이 섹션은 **`id="lead"` 앵커**를 갖는다 — 원페이지 랜딩에서 히어로 CTA 를
|
|
484
|
+
`"ctaHref": "#lead"` 로 여기에 걸 수 있다. 앵커를 지우면 그 버튼이 조용히 아무 데도 안 간다.
|
|
485
|
+
- `LOGO_WALL` 에 실존 기업 로고를 넣지 마라 — 사용 허락이 있는 것만.
|
|
486
|
+
|
|
487
|
+
**아이콘 — 큐레이션 맵의 키 문자열만**
|
|
488
|
+
`FEATURE_GRID` 의 `icon` 은 아래 32개 중 하나다. 렌더는 `ui/Icon` 의 맵 lookup 이고 **미지 이름은 아이콘 영역을
|
|
489
|
+
생략**한다(죽지 않는다·기본 글리프도 안 쓴다 — 틀린 아이콘보다 없는 아이콘이 낫다).
|
|
490
|
+
|
|
491
|
+
```
|
|
492
|
+
shield-check rocket line-chart trending-up users user-check clock calendar-check
|
|
493
|
+
phone mail map-pin building-2 award handshake heart-handshake badge-check
|
|
494
|
+
star sparkles target compass lightbulb settings wrench layers
|
|
495
|
+
package truck credit-card lock globe message-circle file-text search
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
- 목록 밖 아이콘을 요청받으면 **지어내지 말고** 근접 대체를 제안하거나 목록을 제시한다.
|
|
499
|
+
- 아이콘은 색을 갖지 않는다 — `currentColor` 를 상속한다. `color`/`fill` prop·리터럴 색 금지, 크기·색은
|
|
500
|
+
className(`size-5 text-primary`)으로만. 그래야 테넌트 색이 아이콘까지 자동으로 관통한다.
|
|
501
|
+
- 전체 lucide 를 동적 로딩하지 마라(번들·콘솔 셀렉트·"말로 고치기"가 전부 망가진다).
|