@sayren/store-sdk 0.35.0 → 0.37.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/dist/index.d.mts +690 -221
- package/dist/index.mjs +378 -168
- package/package.json +1 -1
- package/src/client.ts +120 -1
- package/src/index.ts +1 -0
- package/src/schemas/collection.ts +25 -0
- package/src/schemas/plans.ts +104 -0
- package/src/schemas/product-info.ts +26 -0
- package/src/schemas/product-localization.ts +46 -1
- package/src/schemas/product.ts +13 -1
- package/src/schemas/saved-option-set.ts +62 -0
- package/src/schemas/storefront-hosting.ts +141 -4
- package/src/schemas/subscription.ts +34 -87
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -87,11 +87,14 @@ import {
|
|
|
87
87
|
import {
|
|
88
88
|
type CollectionCreateRequestInput,
|
|
89
89
|
type CollectionListParams,
|
|
90
|
+
type CollectionLocalizationRequest,
|
|
90
91
|
type CollectionPatchRequest,
|
|
91
92
|
type CollectionPreviewRequest,
|
|
92
93
|
type CollectionProductsAddRequest,
|
|
93
94
|
type CollectionProductsReplaceRequest,
|
|
94
95
|
collectionCreateRequestSchema,
|
|
96
|
+
collectionLocalizationRequestSchema,
|
|
97
|
+
collectionLocalizationSchema,
|
|
95
98
|
collectionPageSchema,
|
|
96
99
|
collectionPatchRequestSchema,
|
|
97
100
|
collectionPreviewRequestSchema,
|
|
@@ -277,6 +280,7 @@ import {
|
|
|
277
280
|
type VerifyPaymentProviderRequest,
|
|
278
281
|
verifyPaymentProviderRequestSchema,
|
|
279
282
|
} from "./schemas/payment";
|
|
283
|
+
import { publicPlansSchema } from "./schemas/plans";
|
|
280
284
|
import {
|
|
281
285
|
customerPointsSchema,
|
|
282
286
|
type PointAdjustmentRequest,
|
|
@@ -311,8 +315,11 @@ import {
|
|
|
311
315
|
variantStockSchema,
|
|
312
316
|
} from "./schemas/product";
|
|
313
317
|
import {
|
|
318
|
+
type InfoSectionLocalizationRequest,
|
|
314
319
|
type InfoSectionRequestInput,
|
|
315
320
|
infoSchemaSchema,
|
|
321
|
+
infoSectionLocalizationRequestSchema,
|
|
322
|
+
infoSectionLocalizationSchema,
|
|
316
323
|
infoSectionRequestSchema,
|
|
317
324
|
infoSectionSchema,
|
|
318
325
|
platformCodeSchema,
|
|
@@ -325,9 +332,12 @@ import {
|
|
|
325
332
|
import {
|
|
326
333
|
type SavedOptionSetFromProductRequestInput,
|
|
327
334
|
type SavedOptionSetListParams,
|
|
335
|
+
type SavedOptionSetLocalizationRequest,
|
|
328
336
|
type SavedOptionSetRequestInput,
|
|
329
337
|
savedOptionSetFromProductRequestSchema,
|
|
330
338
|
savedOptionSetFromProductResultSchema,
|
|
339
|
+
savedOptionSetLocalizationRequestSchema,
|
|
340
|
+
savedOptionSetLocalizationSchema,
|
|
331
341
|
savedOptionSetRequestSchema,
|
|
332
342
|
savedOptionSetSchema,
|
|
333
343
|
} from "./schemas/saved-option-set";
|
|
@@ -360,12 +370,14 @@ import {
|
|
|
360
370
|
addStorefrontDomainRequestSchema,
|
|
361
371
|
type ChangeStorefrontSlugRequest,
|
|
362
372
|
type ChangeStorefrontTemplateRequest,
|
|
373
|
+
type ConnectStorefrontGitRequest,
|
|
363
374
|
type CreateStorefrontEditPreviewLinkRequest,
|
|
364
375
|
type CreateStorefrontPreviewLinkRequest,
|
|
365
376
|
type CreateStorefrontSiteRequest,
|
|
366
377
|
type CreateStorefrontVersionRequest,
|
|
367
378
|
changeStorefrontSlugRequestSchema,
|
|
368
379
|
changeStorefrontTemplateRequestSchema,
|
|
380
|
+
connectStorefrontGitRequestSchema,
|
|
369
381
|
createStorefrontEditPreviewLinkRequestSchema,
|
|
370
382
|
createStorefrontPreviewLinkRequestSchema,
|
|
371
383
|
createStorefrontSiteRequestSchema,
|
|
@@ -374,6 +386,8 @@ import {
|
|
|
374
386
|
type DisableStorefrontHostingRequest,
|
|
375
387
|
deleteStorefrontFileRequestSchema,
|
|
376
388
|
disableStorefrontHostingRequestSchema,
|
|
389
|
+
type LinkStorefrontGitInstallationRequest,
|
|
390
|
+
linkStorefrontGitInstallationRequestSchema,
|
|
377
391
|
type MoveStorefrontFileRequest,
|
|
378
392
|
moveStorefrontFileRequestSchema,
|
|
379
393
|
type PublishStorefrontVersionRequest,
|
|
@@ -385,8 +399,10 @@ import {
|
|
|
385
399
|
rollbackStorefrontRequestSchema,
|
|
386
400
|
runStorefrontCheckRequestSchema,
|
|
387
401
|
type SearchStorefrontFilesRequest,
|
|
402
|
+
type SetStorefrontDeployApprovalRequest,
|
|
388
403
|
type SetStorefrontVisibilityRequest,
|
|
389
404
|
searchStorefrontFilesRequestSchema,
|
|
405
|
+
setStorefrontDeployApprovalRequestSchema,
|
|
390
406
|
setStorefrontVisibilityRequestSchema,
|
|
391
407
|
storefrontBuildSchema,
|
|
392
408
|
storefrontCheckSchema,
|
|
@@ -398,6 +414,9 @@ import {
|
|
|
398
414
|
storefrontFileMoveResultSchema,
|
|
399
415
|
storefrontFileSearchResultSchema,
|
|
400
416
|
storefrontFileWriteResultSchema,
|
|
417
|
+
storefrontGitConnectStateSchema,
|
|
418
|
+
storefrontGitRepositorySchema,
|
|
419
|
+
storefrontGitStatusSchema,
|
|
401
420
|
storefrontHostingSchema,
|
|
402
421
|
storefrontPreviewLinkSchema,
|
|
403
422
|
storefrontSiteDomainSchema,
|
|
@@ -421,7 +440,7 @@ import {
|
|
|
421
440
|
type WriteStorefrontFilesRequest,
|
|
422
441
|
writeStorefrontFilesRequestSchema,
|
|
423
442
|
} from "./schemas/storefront-hosting";
|
|
424
|
-
import {
|
|
443
|
+
import { storeSubscriptionSchema } from "./schemas/subscription";
|
|
425
444
|
import {
|
|
426
445
|
type UploadPurpose,
|
|
427
446
|
type UploadRequest,
|
|
@@ -701,6 +720,27 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
701
720
|
savedOptionSetFromProductResultSchema,
|
|
702
721
|
{ body: savedOptionSetFromProductRequestSchema.parse(body) },
|
|
703
722
|
),
|
|
723
|
+
/** 번역(이슈 #134) — 기본 언어 이름이 키다. 불러올 때 상품 번역으로 복사한다 */
|
|
724
|
+
localizations: {
|
|
725
|
+
list: (optionSetId: string) =>
|
|
726
|
+
storeHttp.request(
|
|
727
|
+
"GET",
|
|
728
|
+
path`/saved-option-sets/${optionSetId}/localizations`,
|
|
729
|
+
z.array(savedOptionSetLocalizationSchema),
|
|
730
|
+
),
|
|
731
|
+
put: (optionSetId: string, locale: string, body: SavedOptionSetLocalizationRequest) =>
|
|
732
|
+
storeHttp.request(
|
|
733
|
+
"PUT",
|
|
734
|
+
path`/saved-option-sets/${optionSetId}/localizations/${locale}`,
|
|
735
|
+
savedOptionSetLocalizationSchema,
|
|
736
|
+
{ body: savedOptionSetLocalizationRequestSchema.parse(body) },
|
|
737
|
+
),
|
|
738
|
+
remove: (optionSetId: string, locale: string) =>
|
|
739
|
+
storeHttp.requestVoid(
|
|
740
|
+
"DELETE",
|
|
741
|
+
path`/saved-option-sets/${optionSetId}/localizations/${locale}`,
|
|
742
|
+
),
|
|
743
|
+
},
|
|
704
744
|
},
|
|
705
745
|
|
|
706
746
|
/**
|
|
@@ -737,6 +777,27 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
737
777
|
}),
|
|
738
778
|
remove: (infoSectionId: string) =>
|
|
739
779
|
storeHttp.requestVoid("DELETE", path`/info-sections/${infoSectionId}`),
|
|
780
|
+
/** 번역(이슈 #134) — 언어별 제목·본문 */
|
|
781
|
+
localizations: {
|
|
782
|
+
list: (infoSectionId: string) =>
|
|
783
|
+
storeHttp.request(
|
|
784
|
+
"GET",
|
|
785
|
+
path`/info-sections/${infoSectionId}/localizations`,
|
|
786
|
+
z.array(infoSectionLocalizationSchema),
|
|
787
|
+
),
|
|
788
|
+
put: (infoSectionId: string, locale: string, body: InfoSectionLocalizationRequest) =>
|
|
789
|
+
storeHttp.request(
|
|
790
|
+
"PUT",
|
|
791
|
+
path`/info-sections/${infoSectionId}/localizations/${locale}`,
|
|
792
|
+
infoSectionLocalizationSchema,
|
|
793
|
+
{ body: infoSectionLocalizationRequestSchema.parse(body) },
|
|
794
|
+
),
|
|
795
|
+
remove: (infoSectionId: string, locale: string) =>
|
|
796
|
+
storeHttp.requestVoid(
|
|
797
|
+
"DELETE",
|
|
798
|
+
path`/info-sections/${infoSectionId}/localizations/${locale}`,
|
|
799
|
+
),
|
|
800
|
+
},
|
|
740
801
|
},
|
|
741
802
|
|
|
742
803
|
/**
|
|
@@ -786,6 +847,27 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
786
847
|
storeHttp.request("POST", "/collections/preview", collectionPreviewSchema, {
|
|
787
848
|
body: collectionPreviewRequestSchema.parse(body),
|
|
788
849
|
}),
|
|
850
|
+
/** 번역(이슈 #134) — 언어별 제목·설명·SEO */
|
|
851
|
+
localizations: {
|
|
852
|
+
list: (collectionId: string) =>
|
|
853
|
+
storeHttp.request(
|
|
854
|
+
"GET",
|
|
855
|
+
path`/collections/${collectionId}/localizations`,
|
|
856
|
+
z.array(collectionLocalizationSchema),
|
|
857
|
+
),
|
|
858
|
+
put: (collectionId: string, locale: string, body: CollectionLocalizationRequest) =>
|
|
859
|
+
storeHttp.request(
|
|
860
|
+
"PUT",
|
|
861
|
+
path`/collections/${collectionId}/localizations/${locale}`,
|
|
862
|
+
collectionLocalizationSchema,
|
|
863
|
+
{ body: collectionLocalizationRequestSchema.parse(body) },
|
|
864
|
+
),
|
|
865
|
+
remove: (collectionId: string, locale: string) =>
|
|
866
|
+
storeHttp.requestVoid(
|
|
867
|
+
"DELETE",
|
|
868
|
+
path`/collections/${collectionId}/localizations/${locale}`,
|
|
869
|
+
),
|
|
870
|
+
},
|
|
789
871
|
},
|
|
790
872
|
|
|
791
873
|
categories: {
|
|
@@ -2255,6 +2337,43 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
2255
2337
|
storeHttp.request("PUT", "/storefront/site/source", storefrontSourceReplacedSchema, {
|
|
2256
2338
|
body: replaceStorefrontSourceRequestSchema.parse(body),
|
|
2257
2339
|
}),
|
|
2340
|
+
/** `PUT /v1/storefront/site/deploy-approval` — 승인 없이 배포(상점 플랫폼 OWNER·ADMIN 전용) */
|
|
2341
|
+
setDeployApproval: (body: SetStorefrontDeployApprovalRequest) =>
|
|
2342
|
+
storeHttp.request("PUT", "/storefront/site/deploy-approval", storefrontSiteSchema, {
|
|
2343
|
+
body: setStorefrontDeployApprovalRequestSchema.parse(body),
|
|
2344
|
+
}),
|
|
2345
|
+
/** `GET /v1/storefront/site/git` — Git 원천(발행 소스를 둔 저장소)과 상태(#138) */
|
|
2346
|
+
getGit: () => storeHttp.request("GET", "/storefront/site/git", storefrontGitStatusSchema),
|
|
2347
|
+
/** `POST /v1/storefront/site/git/installations/state` — GitHub 설치 화면에 싣는 1회용 state(상점 플랫폼 전용) */
|
|
2348
|
+
createGitConnectState: () =>
|
|
2349
|
+
storeHttp.request(
|
|
2350
|
+
"POST",
|
|
2351
|
+
"/storefront/site/git/installations/state",
|
|
2352
|
+
storefrontGitConnectStateSchema,
|
|
2353
|
+
),
|
|
2354
|
+
/** `POST /v1/storefront/site/git/installations` — GitHub App 설치 확인(상점 플랫폼 전용) → 그 설치의 저장소 */
|
|
2355
|
+
linkGitInstallation: (body: LinkStorefrontGitInstallationRequest) =>
|
|
2356
|
+
storeHttp.request(
|
|
2357
|
+
"POST",
|
|
2358
|
+
"/storefront/site/git/installations",
|
|
2359
|
+
z.array(storefrontGitRepositorySchema),
|
|
2360
|
+
{ body: linkStorefrontGitInstallationRequestSchema.parse(body) },
|
|
2361
|
+
),
|
|
2362
|
+
/** `GET /v1/storefront/site/git/installations/{installationId}/repositories` — 확인한 설치의 저장소(상점 플랫폼 전용) */
|
|
2363
|
+
listGitRepositories: (installationId: number) =>
|
|
2364
|
+
storeHttp.request(
|
|
2365
|
+
"GET",
|
|
2366
|
+
`/storefront/site/git/installations/${encodeURIComponent(String(installationId))}/repositories`,
|
|
2367
|
+
z.array(storefrontGitRepositorySchema),
|
|
2368
|
+
),
|
|
2369
|
+
/** `POST /v1/storefront/site/git/connect` — 셀러 GitHub 저장소 연결(상점 플랫폼 전용) */
|
|
2370
|
+
connectGit: (body: ConnectStorefrontGitRequest) =>
|
|
2371
|
+
storeHttp.request("POST", "/storefront/site/git/connect", storefrontGitStatusSchema, {
|
|
2372
|
+
body: connectStorefrontGitRequestSchema.parse(body),
|
|
2373
|
+
}),
|
|
2374
|
+
/** `POST /v1/storefront/site/git/disconnect` — 연결 해제(sayren 저장소로 되돌림, 상점 플랫폼 전용) */
|
|
2375
|
+
disconnectGit: () =>
|
|
2376
|
+
storeHttp.request("POST", "/storefront/site/git/disconnect", storefrontGitStatusSchema),
|
|
2258
2377
|
},
|
|
2259
2378
|
|
|
2260
2379
|
subscription: {
|
package/src/index.ts
CHANGED
|
@@ -42,6 +42,7 @@ export * from "./schemas/market";
|
|
|
42
42
|
export * from "./schemas/misc";
|
|
43
43
|
export * from "./schemas/order";
|
|
44
44
|
export * from "./schemas/payment";
|
|
45
|
+
export * from "./schemas/plans";
|
|
45
46
|
export * from "./schemas/points";
|
|
46
47
|
export * from "./schemas/product";
|
|
47
48
|
export * from "./schemas/product-info";
|
|
@@ -587,3 +587,28 @@ export const collectionPreviewSchema = z.object({
|
|
|
587
587
|
});
|
|
588
588
|
|
|
589
589
|
export type CollectionPreview = z.infer<typeof collectionPreviewSchema>;
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* 컬렉션 번역 (이슈 #134) — 언어 하나의 제목·설명·SEO. 컬렉션 필드는 상점 기본 언어 값이고 번역 레코드가 언어별 값이다.
|
|
593
|
+
* 비운 칸(null)은 스토어프론트가 기본 언어 값으로 대체한다(칸 단위 대체).
|
|
594
|
+
*/
|
|
595
|
+
export const collectionLocalizationSchema = z.object({
|
|
596
|
+
locale: z.string().describe("언어(BCP 47)"),
|
|
597
|
+
title: z.string().nullable().describe("제목. null이면 기본 언어 제목"),
|
|
598
|
+
description: z.string().nullable(),
|
|
599
|
+
metaTitle: z.string().nullable(),
|
|
600
|
+
metaDescription: z.string().nullable(),
|
|
601
|
+
updatedAt: z.string(),
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
export type CollectionLocalization = z.infer<typeof collectionLocalizationSchema>;
|
|
605
|
+
|
|
606
|
+
/** `PUT /v1/collections/{collectionId}/localizations/{locale}` 본문 — 레코드 전체를 바꾼다. 빠진 칸은 null(기본 언어 값으로 대체)이다 */
|
|
607
|
+
export const collectionLocalizationRequestSchema = z.strictObject({
|
|
608
|
+
title: z.string().trim().min(1).max(MAX_COLLECTION_TITLE).nullable().optional(),
|
|
609
|
+
description: z.string().trim().max(MAX_COLLECTION_DESCRIPTION).nullable().optional(),
|
|
610
|
+
metaTitle: z.string().trim().max(60).nullable().optional(),
|
|
611
|
+
metaDescription: z.string().trim().max(160).nullable().optional(),
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
export type CollectionLocalizationRequest = z.infer<typeof collectionLocalizationRequestSchema>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { responsePlanCodeSchema } from "./subscription";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 요금제 비교표 — `GET /v1/public/plans`(인증 없음). 요금제마다 가격과 기능 키 값을 준다. 값은 서버가 관리하고(사내 어드민 게시) 언제든
|
|
6
|
+
* 바뀔 수 있으니 코드에 요금제 수치를 두지 않는다. 상점 하나의 구독·사용량은 `subscription.ts`다
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const limitOrContract = (description: string) =>
|
|
10
|
+
z.number().int().nullable().describe(`${description}. null이면 계약(엔터프라이즈)·한도 없음`);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 요금제 기능 키 값 — 요금제 비교표·상점 플랫폼 요금제 화면이 쓴다. 키는 서버 판정의 기능 키와 같다(추가만 한다). 상위 요금제는 하위 요금제
|
|
14
|
+
* 기능을 모두 포함한다
|
|
15
|
+
*/
|
|
16
|
+
export const planEntitlementsSchema = z
|
|
17
|
+
.object({
|
|
18
|
+
"features.members": z
|
|
19
|
+
.boolean()
|
|
20
|
+
.describe("소유자 외 구성원(관리자·직원)의 초대와 상점 접근. false면 소유자만 쓴다"),
|
|
21
|
+
"features.admin_api": z
|
|
22
|
+
.boolean()
|
|
23
|
+
.describe(
|
|
24
|
+
"관리 API(개인 액세스 토큰·외부 앱 토큰 호출). 상점 플랫폼·CLI·MCP는 모든 요금제에서 쓴다",
|
|
25
|
+
),
|
|
26
|
+
"features.analytics": z.boolean().describe("애널리틱스 화면·조회 API"),
|
|
27
|
+
"limits.ai_reads_monthly": limitOrContract("MCP 읽기 월 한도"),
|
|
28
|
+
"limits.ai_actions_monthly": limitOrContract(
|
|
29
|
+
"MCP 쓰기(AI 작업) 월 한도 — 주문 처리는 막지 않는다",
|
|
30
|
+
),
|
|
31
|
+
"limits.custom_domains": limitOrContract("내 도메인 수"),
|
|
32
|
+
"limits.webhooks": limitOrContract("웹훅 구독 수(0이면 쓸 수 없다)"),
|
|
33
|
+
"limits.public_apps": limitOrContract("공개 앱(앱 스토어 게시 앱) 설치 수"),
|
|
34
|
+
"limits.private_apps": limitOrContract(
|
|
35
|
+
"비공개 앱(이 상점 전용 앱) 설치 수(0이면 설치할 수 없다)",
|
|
36
|
+
),
|
|
37
|
+
"limits.ai_credits_monthly": limitOrContract(
|
|
38
|
+
"AI 상점 도우미 월 크레딧(1 크레딧 = 모델 원가 $0.01, 이월 없음)",
|
|
39
|
+
),
|
|
40
|
+
"limits.storefront_preview_hours_monthly": limitOrContract("편집 환경 시간(월, 시간)"),
|
|
41
|
+
"limits.storefront_build_minutes_monthly": limitOrContract("빌드 시간(월, 분)"),
|
|
42
|
+
"limits.storefront_requests_monthly": limitOrContract(
|
|
43
|
+
"스토어프론트 월 트래픽(요청 수) — 넘어도 막지 않고 안내만 한다",
|
|
44
|
+
),
|
|
45
|
+
"limits.admin_api_requests_monthly": limitOrContract(
|
|
46
|
+
"관리 API 월 요청 — 넘으면 속도만 낮춘다(주문 처리 제외)",
|
|
47
|
+
),
|
|
48
|
+
"rate_limit.admin_api": z
|
|
49
|
+
.number()
|
|
50
|
+
.nullable()
|
|
51
|
+
.describe("관리 API 분당 속도 배수(기본 분당 600 × 배수)"),
|
|
52
|
+
"storefront.cpu_ms": z.number().int().describe("스토어프론트 사이트 요청당 CPU 상한(ms)"),
|
|
53
|
+
"storefront.subrequests": z
|
|
54
|
+
.number()
|
|
55
|
+
.int()
|
|
56
|
+
.describe("스토어프론트 사이트 요청당 하위 요청 상한"),
|
|
57
|
+
"storefront.rate_limit_per_minute": z
|
|
58
|
+
.number()
|
|
59
|
+
.int()
|
|
60
|
+
.describe("스토어프론트 남용 방지 요청 속도 상한(사이트당 분당)"),
|
|
61
|
+
"seats.included": limitOrContract(
|
|
62
|
+
"포함 구성원 석 — 구성원은 모든 요금제에서 무료·무제한이라 null",
|
|
63
|
+
),
|
|
64
|
+
"retention.analytics_days": z.number().int().describe("애널리틱스 보관 일수"),
|
|
65
|
+
"retention.api_logs_days": limitOrContract("API 로그 보관 일수(0이면 API 로그 없음)"),
|
|
66
|
+
"retention.audit_days": limitOrContract("감사 로그 보관 일수"),
|
|
67
|
+
"support.tier": z
|
|
68
|
+
.union([z.enum(["COMMUNITY", "EMAIL", "EMAIL_PRIORITY", "DEDICATED", "SLA"]), z.string()])
|
|
69
|
+
.describe(
|
|
70
|
+
"지원 등급(`COMMUNITY`·`EMAIL`·`EMAIL_PRIORITY`·`DEDICATED`·`SLA`, 새 등급이 더해질 수 있다)",
|
|
71
|
+
),
|
|
72
|
+
})
|
|
73
|
+
.describe("요금제 기능 키 값");
|
|
74
|
+
export type PlanEntitlementValues = z.infer<typeof planEntitlementsSchema>;
|
|
75
|
+
|
|
76
|
+
/** 공개 요금제 한 줄 — `GET /v1/public/plans` */
|
|
77
|
+
export const publicPlanSchema = z.object({
|
|
78
|
+
code: responsePlanCodeSchema,
|
|
79
|
+
name: z.string().describe("표시 이름(Free·Pro·프리미엄·엔터프라이즈)"),
|
|
80
|
+
version: z.number().int().describe("요금제 버전 — 값이 바뀌면 새 버전이 게시된다"),
|
|
81
|
+
sort: z.number().int(),
|
|
82
|
+
purchasable: z
|
|
83
|
+
.boolean()
|
|
84
|
+
.describe("셀프 결제로 고를 수 있는가(Free·엔터프라이즈는 아니다 — 엔터프라이즈는 계약)"),
|
|
85
|
+
prices: z.object({
|
|
86
|
+
currency: z.literal("KRW"),
|
|
87
|
+
monthly: z.number().int().nullable().describe("월 가격(공급가, 부가세 별도)"),
|
|
88
|
+
startingAt: z.boolean().describe("「부터」 가격(엔터프라이즈 계약)"),
|
|
89
|
+
vatIncluded: z
|
|
90
|
+
.object({
|
|
91
|
+
monthly: z.number().int().nullable(),
|
|
92
|
+
})
|
|
93
|
+
.describe("표시 가격(부가세 포함) — 요금제 비교표는 이 값을 보인다"),
|
|
94
|
+
}),
|
|
95
|
+
entitlements: planEntitlementsSchema,
|
|
96
|
+
});
|
|
97
|
+
export type PublicPlan = z.infer<typeof publicPlanSchema>;
|
|
98
|
+
|
|
99
|
+
/** 요금제 비교표 — `GET /v1/public/plans`(인증 없음, 캐시 가능). 값은 서버가 관리하고 언제든 바뀔 수 있다 */
|
|
100
|
+
export const publicPlansSchema = z.object({
|
|
101
|
+
vatPercent: z.number().int().describe("부가세율(%)"),
|
|
102
|
+
plans: z.array(publicPlanSchema).describe("공개 요금제의 지금 버전 — 정렬 순"),
|
|
103
|
+
});
|
|
104
|
+
export type PublicPlans = z.infer<typeof publicPlansSchema>;
|
|
@@ -188,3 +188,29 @@ export const infoSectionRequestSchema = z.object({
|
|
|
188
188
|
|
|
189
189
|
export type InfoSectionRequest = z.infer<typeof infoSectionRequestSchema>;
|
|
190
190
|
export type InfoSectionRequestInput = z.input<typeof infoSectionRequestSchema>;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 안내 섹션 번역 (이슈 #134) — 언어 하나의 제목·본문. 섹션 필드는 상점 기본 언어 값이다. 비운 칸(null)은 스토어프론트가 기본 언어 값으로 대체한다.
|
|
194
|
+
* 섹션을 연결한 모든 상품이 같은 번역을 보인다.
|
|
195
|
+
*/
|
|
196
|
+
export const infoSectionLocalizationSchema = z.object({
|
|
197
|
+
locale: z.string().describe("언어(BCP 47)"),
|
|
198
|
+
title: z.string().nullable(),
|
|
199
|
+
body: z
|
|
200
|
+
.string()
|
|
201
|
+
.nullable()
|
|
202
|
+
.describe("본문 HTML(상세설명과 같은 정화 규칙). null이면 기본 언어 본문"),
|
|
203
|
+
bodyJson: z.string().nullable(),
|
|
204
|
+
updatedAt: z.string(),
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
export type InfoSectionLocalization = z.infer<typeof infoSectionLocalizationSchema>;
|
|
208
|
+
|
|
209
|
+
/** `PUT /v1/info-sections/{infoSectionId}/localizations/{locale}` 본문 — 레코드 전체를 바꾼다. 빠진 칸은 null이다 */
|
|
210
|
+
export const infoSectionLocalizationRequestSchema = z.strictObject({
|
|
211
|
+
title: z.string().trim().min(1).max(MAX_INFO_SECTION_TITLE).nullable().optional(),
|
|
212
|
+
body: z.string().nullable().optional(),
|
|
213
|
+
bodyJson: z.string().nullable().optional(),
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
export type InfoSectionLocalizationRequest = z.infer<typeof infoSectionLocalizationRequestSchema>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { MAX_PRODUCT_IMAGES, productImageUrlSchema } from "./product";
|
|
3
|
+
import { MAX_NOTICE_VALUE_LENGTH } from "./product-info";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* 상품 번역 (이슈 #134) — 언어 하나의 상품명·상세설명·이미지·옵션 이름. 상품 필드는 상점 기본 언어 값이고 번역 레코드가 언어별 값이다.
|
|
@@ -24,8 +25,14 @@ const translatedCustomInputSchema = z.object({
|
|
|
24
25
|
placeholder: z.string().nullable().describe("입력 안내. null이면 기본 언어 안내"),
|
|
25
26
|
});
|
|
26
27
|
|
|
28
|
+
const translatedNoticeValueSchema = z.object({
|
|
29
|
+
key: z.string().describe("고시 칸 키"),
|
|
30
|
+
label: z.string().describe("칸 이름(원문)"),
|
|
31
|
+
value: z.string().nullable().describe("셀러 값 번역. null이면 기본 언어 값"),
|
|
32
|
+
});
|
|
33
|
+
|
|
27
34
|
/**
|
|
28
|
-
* 조회 응답 — 지금 상품의 옵션명·값·직접 입력
|
|
35
|
+
* 조회 응답 — 지금 상품의 옵션명·값·직접 입력 항목, 셀러가 적은 고시 칸을 모두 싣고 번역하지 않은 칸은 null이다.
|
|
29
36
|
* `optionGroups`는 조합 옵션, `addonGroups`는 추가 선택이다(상품 응답과 같은 구분)
|
|
30
37
|
*/
|
|
31
38
|
export const productLocalizationSchema = z.object({
|
|
@@ -37,6 +44,13 @@ export const productLocalizationSchema = z.object({
|
|
|
37
44
|
optionGroups: z.array(translatedOptionGroupSchema),
|
|
38
45
|
addonGroups: z.array(translatedOptionGroupSchema),
|
|
39
46
|
customInputs: z.array(translatedCustomInputSchema),
|
|
47
|
+
notice: z
|
|
48
|
+
.array(translatedNoticeValueSchema)
|
|
49
|
+
.optional()
|
|
50
|
+
.default([])
|
|
51
|
+
.describe(
|
|
52
|
+
"셀러가 적은 상품정보제공 고시 칸의 값 번역(이슈 #134). 출처·법정 문구 칸은 싣지 않는다",
|
|
53
|
+
),
|
|
40
54
|
updatedAt: z.string(),
|
|
41
55
|
});
|
|
42
56
|
|
|
@@ -71,6 +85,37 @@ export const productLocalizationRequestSchema = z.strictObject({
|
|
|
71
85
|
}),
|
|
72
86
|
)
|
|
73
87
|
.optional(),
|
|
88
|
+
notice: z
|
|
89
|
+
.array(
|
|
90
|
+
z.strictObject({
|
|
91
|
+
key: z.string(),
|
|
92
|
+
value: z.string().trim().min(1).max(MAX_NOTICE_VALUE_LENGTH).nullable().optional(),
|
|
93
|
+
}),
|
|
94
|
+
)
|
|
95
|
+
.optional()
|
|
96
|
+
.describe(
|
|
97
|
+
"고시 칸 값 번역. 상품에 셀러 값이 없는 칸 키는 400 `PRODUCT_LOCALIZATION_TARGET_NOT_FOUND`",
|
|
98
|
+
),
|
|
74
99
|
});
|
|
75
100
|
|
|
76
101
|
export type ProductLocalizationRequest = z.infer<typeof productLocalizationRequestSchema>;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 번역할 칸이 비었는가(이슈 #134, 2026-10-18 결정: 완성도 대신 「번역 필요」 표시만) — 상품명, 기본 언어에 본문이 있는 상세설명,
|
|
105
|
+
* 옵션명·값·직접 입력 항목명, 셀러가 적은 고시 칸. 이미지·입력 안내는 기본 언어 값을 그대로 써도 되는 칸이라 세지 않는다.
|
|
106
|
+
* 상점 플랫폼(상품 수정·목록)과 관리 목록 응답이 같은 함수다
|
|
107
|
+
*/
|
|
108
|
+
export function productLocalizationNeedsTranslation(
|
|
109
|
+
localization: Pick<
|
|
110
|
+
ProductLocalization,
|
|
111
|
+
"name" | "description" | "optionGroups" | "addonGroups" | "customInputs"
|
|
112
|
+
> & { notice?: ProductLocalization["notice"] },
|
|
113
|
+
base: { description?: string | null },
|
|
114
|
+
): boolean {
|
|
115
|
+
if (!localization.name) return true;
|
|
116
|
+
if ((base.description ?? "").trim() && !localization.description) return true;
|
|
117
|
+
const groups = [...localization.optionGroups, ...localization.addonGroups];
|
|
118
|
+
if (groups.some((group) => !group.name || group.values.some((value) => !value.name))) return true;
|
|
119
|
+
if (localization.customInputs.some((item) => !item.label)) return true;
|
|
120
|
+
return (localization.notice ?? []).some((entry) => !entry.value);
|
|
121
|
+
}
|
package/src/schemas/product.ts
CHANGED
|
@@ -67,6 +67,18 @@ export const productSummarySchema = z.object({
|
|
|
67
67
|
.describe(
|
|
68
68
|
"상점을 만들 때 넣은 샘플 상품인가(이슈 #70). 상점 플랫폼이 「샘플」 배지를 단다. 스토어프론트에는 싣지 않는다. 서버는 항상 싣는다",
|
|
69
69
|
),
|
|
70
|
+
localizations: z
|
|
71
|
+
.array(
|
|
72
|
+
z.object({
|
|
73
|
+
locale: z.string().describe("언어(BCP 47)"),
|
|
74
|
+
needsTranslation: z
|
|
75
|
+
.boolean()
|
|
76
|
+
.describe("번역할 칸이 비었는가(`productLocalizationNeedsTranslation`과 같은 규칙)"),
|
|
77
|
+
}),
|
|
78
|
+
)
|
|
79
|
+
.optional()
|
|
80
|
+
.default([])
|
|
81
|
+
.describe("번역 레코드가 있는 언어(이슈 #134). 상점 기본 언어는 싣지 않는다"),
|
|
70
82
|
});
|
|
71
83
|
|
|
72
84
|
export type ProductSummary = z.infer<typeof productSummarySchema>;
|
|
@@ -372,7 +384,7 @@ export const discountSchema = z.object({
|
|
|
372
384
|
|
|
373
385
|
export type Discount = z.infer<typeof discountSchema>;
|
|
374
386
|
|
|
375
|
-
export const productSchema = productSummarySchema.extend({
|
|
387
|
+
export const productSchema = productSummarySchema.omit({ localizations: true }).extend({
|
|
376
388
|
prices: z
|
|
377
389
|
.array(storedProductCurrencyPriceSchema)
|
|
378
390
|
.optional()
|
|
@@ -174,3 +174,65 @@ export const savedOptionSetListQuerySchema = z.object({
|
|
|
174
174
|
sort: savedOptionSetSortSchema.optional().describe("정렬 (기본 updatedAt.desc)"),
|
|
175
175
|
...pageQueryShape({ defaultSize: 20, maxSize: 200 }),
|
|
176
176
|
});
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 저장 옵션 번역 (이슈 #134) — 저장 옵션에는 id가 없어 **기본 언어 이름이 키**다(`baseName`·`baseLabel`).
|
|
180
|
+
* 상품이 불러올 때 상품 번역으로 함께 복사하고(상품 저장 트랜잭션), 그 뒤로는 상품 번역이 원천이다 — 저장 옵션 번역을 고쳐도
|
|
181
|
+
* 이미 불러온 상품은 그대로다. 기본 언어 이름을 바꾸면 그 이름의 번역은 읽지 않는다.
|
|
182
|
+
*/
|
|
183
|
+
export const savedOptionSetLocalizationSchema = z.object({
|
|
184
|
+
locale: z.string().describe("언어(BCP 47)"),
|
|
185
|
+
groups: z.array(
|
|
186
|
+
z.object({
|
|
187
|
+
baseName: z.string().describe("기본 언어 옵션명"),
|
|
188
|
+
name: z.string().nullable().describe("번역 옵션명. null이면 기본 언어 이름"),
|
|
189
|
+
values: z.array(
|
|
190
|
+
z.object({
|
|
191
|
+
baseName: z.string(),
|
|
192
|
+
name: z.string().nullable(),
|
|
193
|
+
}),
|
|
194
|
+
),
|
|
195
|
+
}),
|
|
196
|
+
),
|
|
197
|
+
customInputs: z.array(
|
|
198
|
+
z.object({
|
|
199
|
+
baseLabel: z.string().describe("기본 언어 항목명"),
|
|
200
|
+
label: z.string().nullable(),
|
|
201
|
+
placeholder: z.string().nullable(),
|
|
202
|
+
}),
|
|
203
|
+
),
|
|
204
|
+
updatedAt: z.string(),
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
export type SavedOptionSetLocalization = z.infer<typeof savedOptionSetLocalizationSchema>;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* `PUT /v1/saved-option-sets/{optionSetId}/localizations/{locale}` 본문 — 레코드 전체를 바꾼다. 번역할 이름만 보낸다.
|
|
211
|
+
* 저장 옵션에 없는 기본 언어 이름은 400 `OPTION_SET_LOCALIZATION_TARGET_NOT_FOUND`, 번역 이름 중복은 상품과 같은 규칙으로 400 `INVALID_OPTION_MODEL`이다
|
|
212
|
+
*/
|
|
213
|
+
export const savedOptionSetLocalizationRequestSchema = z.strictObject({
|
|
214
|
+
groups: z
|
|
215
|
+
.array(
|
|
216
|
+
z.strictObject({
|
|
217
|
+
baseName: z.string(),
|
|
218
|
+
name: nameSchema.nullable().optional(),
|
|
219
|
+
values: z
|
|
220
|
+
.array(z.strictObject({ baseName: z.string(), name: nameSchema.nullable().optional() }))
|
|
221
|
+
.optional(),
|
|
222
|
+
}),
|
|
223
|
+
)
|
|
224
|
+
.optional(),
|
|
225
|
+
customInputs: z
|
|
226
|
+
.array(
|
|
227
|
+
z.strictObject({
|
|
228
|
+
baseLabel: z.string(),
|
|
229
|
+
label: z.string().trim().min(1).max(30).nullable().optional(),
|
|
230
|
+
placeholder: z.string().trim().min(1).max(100).nullable().optional(),
|
|
231
|
+
}),
|
|
232
|
+
)
|
|
233
|
+
.optional(),
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
export type SavedOptionSetLocalizationRequest = z.infer<
|
|
237
|
+
typeof savedOptionSetLocalizationRequestSchema
|
|
238
|
+
>;
|