@sayren/store-sdk 0.40.0 → 0.42.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 +178 -80
- package/dist/index.mjs +214 -507
- package/dist/server.d.mts +2 -2
- package/dist/server.mjs +2 -2
- package/dist/webhook-headers-D0E3wPpS.mjs +437 -0
- package/package.json +1 -1
- package/src/client.ts +16 -6
- package/src/http.ts +8 -1
- package/src/index.ts +2 -0
- package/src/member-delegation.ts +21 -17
- package/src/schemas/ai.ts +101 -42
- package/src/schemas/apps.ts +18 -15
- package/src/schemas/audit.ts +6 -3
- package/src/schemas/category.ts +36 -31
- package/src/schemas/claim.ts +14 -11
- package/src/schemas/collection.ts +75 -64
- package/src/schemas/commerce-flow.ts +106 -85
- package/src/schemas/coupon.ts +87 -79
- package/src/schemas/customer-delegation.ts +30 -27
- package/src/schemas/customer.ts +9 -6
- package/src/schemas/inventory.ts +28 -23
- package/src/schemas/login.ts +17 -10
- package/src/schemas/misc.ts +6 -3
- package/src/schemas/order.ts +69 -58
- package/src/schemas/payment.ts +108 -89
- package/src/schemas/points.ts +32 -27
- package/src/schemas/product-info.ts +12 -9
- package/src/schemas/product.ts +17 -14
- package/src/schemas/saved-option-set.ts +5 -5
- package/src/schemas/starter.ts +15 -12
- package/src/schemas/stay.ts +15 -12
- package/src/schemas/store.ts +122 -111
- package/src/schemas/storefront-hosting.ts +271 -219
- package/src/schemas/strict.ts +84 -0
- package/src/schemas/upload.ts +15 -12
- package/src/session.ts +8 -5
- package/dist/webhook-headers-ap_exsb9.mjs +0 -22
package/src/schemas/ai.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { storefrontSlugSchema } from "./storefront-hosting";
|
|
3
|
+
import { strictRequest } from "./strict";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* 상점 플랫폼 AI 채팅(이슈 #124) — 대화·메시지·실행과 실행 이벤트 스트림의 계약.
|
|
@@ -96,9 +97,15 @@ export const aiActivitySchema = z.enum([
|
|
|
96
97
|
]);
|
|
97
98
|
export type AiActivity = z.infer<typeof aiActivitySchema>;
|
|
98
99
|
|
|
100
|
+
/** 바뀐 줄 미리보기 한 줄의 최대 길이 — 서버가 이 길이로 자르고 화면은 이보다 긴 줄을 앞부분으로 비교한다 */
|
|
101
|
+
export const AI_TOOL_CHANGE_LINE_MAX = 300;
|
|
102
|
+
/** 바뀐 줄 범위(`lines`) 개수 상한 */
|
|
103
|
+
export const AI_TOOL_CHANGE_RANGES_MAX = 200;
|
|
104
|
+
|
|
99
105
|
/**
|
|
100
106
|
* 도구가 바꾼 파일 하나(2026-10-13, AI 상점 도우미 작업 공간의 수정 중인 코드) — 서버가 도구 입력으로 계산한다.
|
|
101
|
-
* `edit`는 바꾼 부분의 줄 비교, `write`는 새로 쓴 내용(이전 내용을 모르므로 모두 추가로 센다). `preview`는 바뀐 줄 앞부분 12
|
|
107
|
+
* `edit`는 바꾼 부분의 줄 비교, `write`는 새로 쓴 내용(이전 내용을 모르므로 모두 추가로 센다). `preview`는 바뀐 줄 앞부분 12줄까지다.
|
|
108
|
+
* `lines`는 바꾼 뒤 파일에서 추가된 줄의 범위(1부터, 양끝 포함)다 — `edit`가 성공한 뒤에만 실행기가 채운다(옛 기록·`write`에는 없다)
|
|
102
109
|
*/
|
|
103
110
|
export const aiToolChangeSchema = z.object({
|
|
104
111
|
path: z.string().max(300),
|
|
@@ -106,8 +113,17 @@ export const aiToolChangeSchema = z.object({
|
|
|
106
113
|
added: z.number().int().min(0),
|
|
107
114
|
removed: z.number().int().min(0),
|
|
108
115
|
preview: z
|
|
109
|
-
.array(
|
|
116
|
+
.array(
|
|
117
|
+
z.object({
|
|
118
|
+
kind: z.enum(["added", "removed"]),
|
|
119
|
+
text: z.string().max(AI_TOOL_CHANGE_LINE_MAX),
|
|
120
|
+
}),
|
|
121
|
+
)
|
|
110
122
|
.max(12),
|
|
123
|
+
lines: z
|
|
124
|
+
.array(z.tuple([z.number().int().min(1), z.number().int().min(1)]))
|
|
125
|
+
.max(AI_TOOL_CHANGE_RANGES_MAX)
|
|
126
|
+
.optional(),
|
|
111
127
|
});
|
|
112
128
|
export type AiToolChange = z.infer<typeof aiToolChangeSchema>;
|
|
113
129
|
|
|
@@ -127,28 +143,49 @@ export const aiAgentPhaseSchema = z.enum(AI_AGENT_PHASES);
|
|
|
127
143
|
export type AiAgentPhase = z.infer<typeof aiAgentPhaseSchema>;
|
|
128
144
|
|
|
129
145
|
/**
|
|
130
|
-
* AI 상점 도우미의 작업 메모 폴더(2026-10-14) — 회차마다 읽는 숨은 규칙(`rules.md`)과 디자인 계획(`design.md`).
|
|
131
|
-
*
|
|
146
|
+
* AI 상점 도우미의 작업 메모 폴더(2026-10-14) — 회차마다 읽는 숨은 규칙(`rules.md`)과 디자인 계획(`design.md`). 편집 Sandbox가 점(.)으로 시작하는
|
|
147
|
+
* 경로를 받지 않아 작업 트리의 `src` 아래에 둔다(앱이 import하지 않는 문서라 빌드에 들어가지 않는다). 숨은 규칙은 셀러 화면(타임라인·코드 탭·변경 기록)에
|
|
148
|
+
* 보이지 않는다. 디자인 계획은 2026-10-24부터 셀러 문서(작업 공간 문서 탭의 「디자인 가이드라인」)라 숨기지 않는다(`AI_DESIGN_GUIDE_PATH`)
|
|
132
149
|
*/
|
|
133
150
|
export const AI_AGENT_NOTES_DIR = "src/site/agent";
|
|
151
|
+
/** 디자인 가이드라인(디자인 계획) — 작업 메모 폴더에 있지만 셀러가 읽는 문서다 */
|
|
152
|
+
export const AI_DESIGN_GUIDE_PATH = `${AI_AGENT_NOTES_DIR}/design.md`;
|
|
153
|
+
/** 사이트맵 — 에이전트가 쓰는 화면 구성 문서(`{version:1,pages:[…]}`) */
|
|
154
|
+
export const AI_SITEMAP_PATH = "src/site/sitemap.json";
|
|
155
|
+
|
|
156
|
+
/** 작업 공간 문서 탭의 문서 파일인가(사이트맵·디자인 가이드라인) — 앱 코드가 아니라서 코드 탭·미리보기 점검·코드 변경 판정에서 뺀다 */
|
|
157
|
+
export function isAiWorkspaceDocPath(path: string): boolean {
|
|
158
|
+
return path === AI_SITEMAP_PATH || path === AI_DESIGN_GUIDE_PATH;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** 「이어서 진행」 버튼(실패한 실행 잇기)이 보내는 선택 값 — 선택지 답처럼 같은 할 일 목록을 잇는다 */
|
|
162
|
+
export const AI_RUN_RESUME_CHOICE = "run:resume";
|
|
134
163
|
|
|
135
164
|
/**
|
|
136
165
|
* 에이전트의 할 일 목록(2026-10-14, Claude Code의 TaskCreate·TaskUpdate·TaskList와 같다) — 에이전트가 패널 도구로 할 일을 만들고 상태를 바꾼다.
|
|
137
|
-
* 이벤트·조각은 그 시점의 목록 전체이고 작업 공간
|
|
166
|
+
* 이벤트·조각은 그 시점의 목록 전체이고 작업 공간 로드맵 탭이 마지막 목록을 칸반으로 그린다. 진행중은 한 번에 하나다.
|
|
167
|
+
* 목록은 셀러 요청 하나(사이클)의 일이다 — 새 요청이면 빈 목록에서 시작하고 선택지 답이면 잇는다(2026-10-24). 개수 상한은 없다
|
|
168
|
+
* (옛 30개 상한은 대화 전체에 할 일이 쌓여 넘쳤다 — 크기는 사이클 범위가, 폭주는 턴 예산이 막는다).
|
|
169
|
+
* `blocked`는 같은 문제를 거듭 고쳐도 남아 멈춘 일이고 `note`가 그 사유다(이어 가기 리마인더가 남은 일로 세지 않는다)
|
|
138
170
|
*/
|
|
139
|
-
export const AI_TODO_STATUSES = ["pending", "in_progress", "completed"] as const;
|
|
171
|
+
export const AI_TODO_STATUSES = ["pending", "in_progress", "completed", "blocked"] as const;
|
|
140
172
|
export const aiTodoSchema = z.object({
|
|
141
|
-
/** 할 일 번호(
|
|
173
|
+
/** 할 일 번호(사이클 안에서 1부터) — 옛 조각에는 없다 */
|
|
142
174
|
id: z.string().max(10).optional(),
|
|
143
175
|
content: z.string().min(1).max(200),
|
|
144
176
|
status: z.enum(AI_TODO_STATUSES),
|
|
145
177
|
/** 이 할 일이 속한 작업 단계 — 진행중이 되면 실행기가 단계를 알린다(2026-10-14, 모델이 단계 알림을 빠뜨렸다) */
|
|
146
178
|
phase: aiAgentPhaseSchema.optional(),
|
|
179
|
+
/** 막힌 사유(`blocked`일 때만) */
|
|
180
|
+
note: z.string().max(300).optional(),
|
|
147
181
|
});
|
|
148
182
|
export type AiTodo = z.infer<typeof aiTodoSchema>;
|
|
183
|
+
/** @deprecated 할 일 개수 상한은 없앴다(2026-10-24). 공개 export라 값만 남긴다 */
|
|
149
184
|
export const AI_TODOS_MAX = 30;
|
|
150
185
|
|
|
186
|
+
/** 셀러에게 보이지 않는 작업 메모인가 — 작업 메모 폴더에서 디자인 가이드라인을 뺀 나머지다 */
|
|
151
187
|
export function isAiAgentNotesPath(path: string): boolean {
|
|
188
|
+
if (path === AI_DESIGN_GUIDE_PATH) return false;
|
|
152
189
|
return path === AI_AGENT_NOTES_DIR || path.startsWith(`${AI_AGENT_NOTES_DIR}/`);
|
|
153
190
|
}
|
|
154
191
|
|
|
@@ -260,7 +297,12 @@ export const AI_DESIGN_CONFIRM_APPROVE = "design:approve";
|
|
|
260
297
|
* - `todos`: 에이전트의 할 일 목록 전체(작업 공간의 체크리스트)
|
|
261
298
|
*/
|
|
262
299
|
export const aiMessagePartSchema = z.discriminatedUnion("type", [
|
|
263
|
-
z.object({
|
|
300
|
+
z.object({
|
|
301
|
+
type: z.literal("text"),
|
|
302
|
+
text: z.string(),
|
|
303
|
+
/** 셀러가 선택지 버튼으로 보낸 답의 값(사용자 메시지만) — 할 일 목록(사이클)을 잇는 기준(`isAiChoiceAnswer`) */
|
|
304
|
+
choice: z.string().max(200).optional(),
|
|
305
|
+
}),
|
|
264
306
|
z.object({
|
|
265
307
|
type: z.literal("tool"),
|
|
266
308
|
toolCallId: z.string(),
|
|
@@ -302,7 +344,7 @@ export const aiMessagePartSchema = z.discriminatedUnion("type", [
|
|
|
302
344
|
z.object({ type: z.literal("reasoning"), text: z.string() }),
|
|
303
345
|
z.object({ type: z.literal("phase"), phase: aiAgentPhaseSchema }),
|
|
304
346
|
/** 에이전트의 할 일 목록 — 그 시점의 목록 전체다(가장 최근 것이 지금 목록) */
|
|
305
|
-
z.object({ type: z.literal("todos"), todos: z.array(aiTodoSchema)
|
|
347
|
+
z.object({ type: z.literal("todos"), todos: z.array(aiTodoSchema) }),
|
|
306
348
|
]);
|
|
307
349
|
export type AiMessagePart = z.infer<typeof aiMessagePartSchema>;
|
|
308
350
|
|
|
@@ -347,12 +389,14 @@ export const storeSetupBriefSchema = z.object({
|
|
|
347
389
|
});
|
|
348
390
|
export type StoreSetupBrief = z.infer<typeof storeSetupBriefSchema>;
|
|
349
391
|
|
|
350
|
-
export const createAiConversationRequestSchema =
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
392
|
+
export const createAiConversationRequestSchema = strictRequest(
|
|
393
|
+
z.object({
|
|
394
|
+
/** 웹사이트에서 남긴 초안(#124 M2) — 연결한 사용자만 쓴다. 주면 첫 메시지로 실행을 시작한다 */
|
|
395
|
+
draftId: z.string().max(64).optional(),
|
|
396
|
+
/** 질문 화면의 답 — `draftId`와 함께만 쓴다. 초안 앞에 붙어 첫 메시지가 된다 */
|
|
397
|
+
brief: storeSetupBriefSchema.optional(),
|
|
398
|
+
}),
|
|
399
|
+
);
|
|
356
400
|
export type CreateAiConversationRequest = z.infer<typeof createAiConversationRequestSchema>;
|
|
357
401
|
|
|
358
402
|
export const createAiConversationResponseSchema = z.object({
|
|
@@ -362,20 +406,22 @@ export const createAiConversationResponseSchema = z.object({
|
|
|
362
406
|
});
|
|
363
407
|
export type CreateAiConversationResponse = z.infer<typeof createAiConversationResponseSchema>;
|
|
364
408
|
|
|
365
|
-
export const sendAiMessageRequestSchema =
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
409
|
+
export const sendAiMessageRequestSchema = strictRequest(
|
|
410
|
+
z
|
|
411
|
+
.object({
|
|
412
|
+
text: z.string().trim().max(AI_MESSAGE_MAX_CHARS),
|
|
413
|
+
attachments: z.array(aiAttachmentSchema).max(AI_MESSAGE_MAX_ATTACHMENTS).default([]),
|
|
414
|
+
/** 선택지 버튼으로 보낸 답이면 그 값(화면 표시는 `text`) */
|
|
415
|
+
choice: z.string().max(200).optional(),
|
|
416
|
+
/** 편집 미리보기에서 고른 요소(#65) — 모델은 이 요소를 기준으로 고친다 */
|
|
417
|
+
selection: aiPreviewSelectionSchema.optional(),
|
|
418
|
+
/** 디자인 탭에서 고른 아트보드(2026-10-14) — 모델은 그 아트보드를 기준으로 고친다 */
|
|
419
|
+
designSelection: aiDesignSelectionSchema.optional(),
|
|
420
|
+
})
|
|
421
|
+
.refine((body) => body.text.length > 0 || body.attachments.length > 0, {
|
|
422
|
+
message: "내용이나 첨부가 있어야 합니다",
|
|
423
|
+
}),
|
|
424
|
+
);
|
|
379
425
|
export type SendAiMessageRequest = z.input<typeof sendAiMessageRequestSchema>;
|
|
380
426
|
|
|
381
427
|
export const sendAiMessageResponseSchema = aiStreamAccessSchema.extend({
|
|
@@ -506,7 +552,7 @@ export const aiStreamEventSchema = z.discriminatedUnion("type", [
|
|
|
506
552
|
z.object({
|
|
507
553
|
type: z.literal("todos"),
|
|
508
554
|
messageId: z.string(),
|
|
509
|
-
todos: z.array(aiTodoSchema)
|
|
555
|
+
todos: z.array(aiTodoSchema),
|
|
510
556
|
}),
|
|
511
557
|
z.object({
|
|
512
558
|
type: z.literal("tool_start"),
|
|
@@ -527,6 +573,8 @@ export const aiStreamEventSchema = z.discriminatedUnion("type", [
|
|
|
527
573
|
toolCallId: z.string(),
|
|
528
574
|
status: z.enum(["ok", "error", "unknown"]),
|
|
529
575
|
summary: z.string().nullable(),
|
|
576
|
+
/** 결과로 확정한 변경(바뀐 줄 범위 포함) — 있으면 `tool_start`의 `changes`를 바꾼다 */
|
|
577
|
+
changes: z.array(aiToolChangeSchema).max(10).optional(),
|
|
530
578
|
}),
|
|
531
579
|
z.object({
|
|
532
580
|
type: z.literal("choices"),
|
|
@@ -579,18 +627,20 @@ export const STORE_DRAFT_ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
|
|
|
579
627
|
/** 연결하지 않은 초안을 지우기까지 */
|
|
580
628
|
export const STORE_DRAFT_RETENTION_DAYS = 7;
|
|
581
629
|
|
|
582
|
-
export const createStoreDraftRequestSchema =
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
630
|
+
export const createStoreDraftRequestSchema = strictRequest(
|
|
631
|
+
z.object({
|
|
632
|
+
text: z.string().trim().min(1).max(AI_MESSAGE_MAX_CHARS),
|
|
633
|
+
attachments: z
|
|
634
|
+
.array(
|
|
635
|
+
z.object({
|
|
636
|
+
filename: z.string().trim().min(1).max(200),
|
|
637
|
+
size: z.number().int().positive().max(STORE_DRAFT_ATTACHMENT_MAX_BYTES),
|
|
638
|
+
}),
|
|
639
|
+
)
|
|
640
|
+
.max(STORE_DRAFT_MAX_ATTACHMENTS)
|
|
641
|
+
.default([]),
|
|
642
|
+
}),
|
|
643
|
+
);
|
|
594
644
|
export type CreateStoreDraftRequest = z.input<typeof createStoreDraftRequestSchema>;
|
|
595
645
|
|
|
596
646
|
export const createStoreDraftResponseSchema = z.object({
|
|
@@ -622,3 +672,12 @@ export const storeDraftPreviewSchema = z.object({
|
|
|
622
672
|
used: z.boolean(),
|
|
623
673
|
});
|
|
624
674
|
export type StoreDraftPreview = z.infer<typeof storeDraftPreviewSchema>;
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* 셀러 메시지가 선택지에서 고른 답인가(조각에 `choice`가 있다) — 할 일 목록은 셀러 요청 하나(사이클)의 일이고, 고른 답(선택지·만들기 확인·계획 승인·발행 확인·
|
|
678
|
+
* 「이어서 진행」)은 같은 사이클을 잇고 새로 쓴 글은 새 사이클이다(2026-10-24). api(`currentCycleRows`)와 상점 플랫폼(로드맵)이 이 판정 하나를 쓴다.
|
|
679
|
+
* 직전 답에 선택지가 있었는지로 가르면 선택지를 무시하고 새 요청을 쓴 경우도 옛 사이클에 묶였다
|
|
680
|
+
*/
|
|
681
|
+
export function isAiChoiceAnswer(parts: readonly { type: string; choice?: string }[]): boolean {
|
|
682
|
+
return parts.some((part) => part.type === "text" && Boolean(part.choice));
|
|
683
|
+
}
|
package/src/schemas/apps.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
appSettingFieldSchema,
|
|
9
9
|
} from "./app-manifest";
|
|
10
10
|
import { webhookDeliveryErrorCodeSchema, webhookDeliveryStatusSchema } from "./misc";
|
|
11
|
+
import { strictRequest } from "./strict";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* 설치 앱 관리 API 계약 (이슈 #22) — `/v1/apps/installations/**`(상점이 설치한 앱), `/v1/app/installation`(설치 토큰 자기 조회).
|
|
@@ -183,21 +184,23 @@ export type AppInstallationActivity = z.infer<typeof appInstallationActivitySche
|
|
|
183
184
|
* - `{ result }`: accounts 설치 동의 결과(1회용 JWT). 개발자 포털 [내 상점에 설치]가 이 길로 온다
|
|
184
185
|
* - `{ appId, settings }`: 상점 플랫폼 앱 목록 › 설치 화면에서 동의하고 설정까지 채워 바로 설치한다
|
|
185
186
|
*/
|
|
186
|
-
export const appInstallRequestSchema =
|
|
187
|
-
z.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
187
|
+
export const appInstallRequestSchema = strictRequest(
|
|
188
|
+
z.union([
|
|
189
|
+
z.object({
|
|
190
|
+
result: z
|
|
191
|
+
.string()
|
|
192
|
+
.min(1)
|
|
193
|
+
.describe("accounts가 발급한 설치 결과 JWT(`typ: sayren-install+jwt`)"),
|
|
194
|
+
}),
|
|
195
|
+
z.object({
|
|
196
|
+
appId: z.string().min(1),
|
|
197
|
+
settings: z
|
|
198
|
+
.record(z.string(), settingsValueSchema)
|
|
199
|
+
.default({})
|
|
200
|
+
.describe("설치와 함께 저장할 설정. 비밀 필드도 여기서 넣는다"),
|
|
201
|
+
}),
|
|
202
|
+
]),
|
|
203
|
+
);
|
|
201
204
|
export type AppInstallRequest = z.infer<typeof appInstallRequestSchema>;
|
|
202
205
|
|
|
203
206
|
/** 앱 목록 정보 — 현재 버전이 올린 아이콘·README(없으면 매니페스트 `iconUrl`) */
|
package/src/schemas/audit.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { strictRequest } from "./strict";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* 감사 로그와 승인 요청 — `/v1/audit-logs`, `/v1/approval-requests` (이슈 #36).
|
|
@@ -261,7 +262,9 @@ export const approvalPageSchema = z.object({
|
|
|
261
262
|
});
|
|
262
263
|
export type ApprovalPage = z.infer<typeof approvalPageSchema>;
|
|
263
264
|
|
|
264
|
-
export const approvalDecisionRequestSchema =
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
export const approvalDecisionRequestSchema = strictRequest(
|
|
266
|
+
z.object({
|
|
267
|
+
note: z.string().trim().max(500).optional().describe("승인·거절 메모 (최대 500자)"),
|
|
268
|
+
}),
|
|
269
|
+
);
|
|
267
270
|
export type ApprovalDecisionRequest = z.infer<typeof approvalDecisionRequestSchema>;
|
package/src/schemas/category.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { strictRequest } from "./strict";
|
|
2
3
|
|
|
3
4
|
export const categoryProductSortSchema = z.enum([
|
|
4
5
|
"RECENT",
|
|
@@ -70,40 +71,44 @@ export const categoryAttributeSchema = z.object({
|
|
|
70
71
|
|
|
71
72
|
export type CategoryAttribute = z.infer<typeof categoryAttributeSchema>;
|
|
72
73
|
|
|
73
|
-
export const categoryCreateRequestSchema =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
74
|
+
export const categoryCreateRequestSchema = strictRequest(
|
|
75
|
+
z.object({
|
|
76
|
+
name: z.string().min(1).max(100),
|
|
77
|
+
parentId: z.string().nullable().optional(),
|
|
78
|
+
sortOrder: z.number().int().optional(),
|
|
79
|
+
visible: z.boolean().optional(),
|
|
80
|
+
showPc: z.boolean().optional(),
|
|
81
|
+
showMobile: z.boolean().optional(),
|
|
82
|
+
productSort: categoryProductSortSchema.optional(),
|
|
83
|
+
metaTitle: z.string().max(100).nullable().optional(),
|
|
84
|
+
metaDescription: z.string().max(300).nullable().optional(),
|
|
85
|
+
metaKeywords: z.string().max(200).nullable().optional(),
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
85
88
|
|
|
86
89
|
export type CategoryCreateRequest = z.infer<typeof categoryCreateRequestSchema>;
|
|
87
90
|
|
|
88
|
-
export const categoryPatchRequestSchema =
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
91
|
+
export const categoryPatchRequestSchema = strictRequest(
|
|
92
|
+
z.object({
|
|
93
|
+
name: z.string().min(1).max(100).optional(),
|
|
94
|
+
parentId: z.string().nullable().optional(),
|
|
95
|
+
sortOrder: z.number().int().optional(),
|
|
96
|
+
visible: z.boolean().optional(),
|
|
97
|
+
showPc: z.boolean().optional(),
|
|
98
|
+
showMobile: z.boolean().optional(),
|
|
99
|
+
productSort: categoryProductSortSchema.optional(),
|
|
100
|
+
metaTitle: z.string().max(100).nullable().optional(),
|
|
101
|
+
metaDescription: z.string().max(300).nullable().optional(),
|
|
102
|
+
metaKeywords: z.string().max(200).nullable().optional(),
|
|
103
|
+
flowId: z
|
|
104
|
+
.string()
|
|
105
|
+
.nullable()
|
|
106
|
+
.optional()
|
|
107
|
+
.describe(
|
|
108
|
+
"주문 흐름(이슈 #115) — 게시한 흐름 id, null이면 기본 흐름. 맨 아래 카테고리에만 지정한다(400 `CATEGORY_NOT_LEAF`). 게시하지 않은 흐름은 409 `FLOW_NOT_PUBLISHED`",
|
|
109
|
+
),
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
107
112
|
|
|
108
113
|
export type CategoryPatchRequest = z.infer<typeof categoryPatchRequestSchema>;
|
|
109
114
|
|
package/src/schemas/claim.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { commerceNumberField } from "./commerce-number";
|
|
3
3
|
import { type PageParams, pageQueryShape } from "./common";
|
|
4
|
+
import { strictRequest } from "./strict";
|
|
4
5
|
|
|
5
6
|
export const claimTypeSchema = z.enum(["CANCEL", "RETURN", "EXCHANGE"]);
|
|
6
7
|
|
|
@@ -164,17 +165,19 @@ export const claimHoldResultSchema = z.object({ claimId: z.string(), holdExpires
|
|
|
164
165
|
|
|
165
166
|
export type ClaimHoldResult = z.infer<typeof claimHoldResultSchema>;
|
|
166
167
|
|
|
167
|
-
export const rejectClaimRequestSchema =
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
168
|
+
export const rejectClaimRequestSchema = strictRequest(
|
|
169
|
+
z.object({
|
|
170
|
+
reason: z.enum([
|
|
171
|
+
"USED_PRODUCT",
|
|
172
|
+
"MISSING_COMPONENT",
|
|
173
|
+
"PERIOD_EXPIRED",
|
|
174
|
+
"BUYER_FAULT_DAMAGE",
|
|
175
|
+
"ETC",
|
|
176
|
+
]),
|
|
177
|
+
detail: z.string().min(10).max(1000),
|
|
178
|
+
evidenceImages: z.array(z.url()).max(5).optional(),
|
|
179
|
+
}),
|
|
180
|
+
);
|
|
178
181
|
|
|
179
182
|
export type RejectClaimRequest = z.infer<typeof rejectClaimRequestSchema>;
|
|
180
183
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { type PageParams, pageQueryShape, pageSchema } from "./common";
|
|
3
3
|
import { productImageUrlSchema } from "./product";
|
|
4
|
+
import { strictRequest } from "./strict";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* 컬렉션 (이슈 #83) — 상품을 어디에 보여 줄지 정하는 묶음이다. 카테고리는 상품이 무엇인지를 정하고 상품당 하나지만, 컬렉션은
|
|
@@ -432,33 +433,35 @@ const periodRefine = (
|
|
|
432
433
|
* 직접 선택(`MANUAL`)은 `productIds`가 처음 담을 상품과 순서다. 조건 자동(`AUTOMATIC`)은 `rules`가 있어야 하고 `productIds`를 받지 않는다.
|
|
433
434
|
* `productSort`를 생략하면 직접 선택은 `MANUAL`, 조건 자동은 `LATEST`(판매량 상위 조건이 있으면 `BEST_SELLING`)다. 조건 자동은 `MANUAL`을 쓸 수 없다.
|
|
434
435
|
*/
|
|
435
|
-
export const collectionCreateRequestSchema =
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
.
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
436
|
+
export const collectionCreateRequestSchema = strictRequest(
|
|
437
|
+
z
|
|
438
|
+
.object({
|
|
439
|
+
title: z.string().trim().min(1).max(MAX_COLLECTION_TITLE).describe("제목"),
|
|
440
|
+
slug: slugField.optional(),
|
|
441
|
+
type: z
|
|
442
|
+
.enum(COLLECTION_TYPES)
|
|
443
|
+
.default("MANUAL")
|
|
444
|
+
.describe("유형. `MANUAL`(직접 선택)·`AUTOMATIC`(조건 자동). 만든 뒤 바꿀 수 없다"),
|
|
445
|
+
rules: collectionRulesSchema
|
|
446
|
+
.optional()
|
|
447
|
+
.describe("조건 자동 컬렉션의 조건(AND). 조건 자동이면 필수, 직접 선택이면 줄 수 없다"),
|
|
448
|
+
description: commonFields.description.default(""),
|
|
449
|
+
imageUrl: commonFields.imageUrl.optional(),
|
|
450
|
+
productSort: commonFields.productSort
|
|
451
|
+
.optional()
|
|
452
|
+
.describe(
|
|
453
|
+
"스토어프론트 상품 기본 정렬. 생략하면 직접 선택은 `MANUAL`, 조건 자동은 `LATEST`(판매량 상위 조건이 있으면 `BEST_SELLING`)다. 조건 자동은 `MANUAL`을 쓸 수 없다",
|
|
454
|
+
),
|
|
455
|
+
visible: commonFields.visible.default(true),
|
|
456
|
+
startsAt: commonFields.startsAt,
|
|
457
|
+
endsAt: commonFields.endsAt,
|
|
458
|
+
sortOrder: commonFields.sortOrder.optional().describe("목록 순서. 생략하면 맨 뒤"),
|
|
459
|
+
metaTitle: commonFields.metaTitle.optional(),
|
|
460
|
+
metaDescription: commonFields.metaDescription.optional(),
|
|
461
|
+
productIds: productIdsField.optional(),
|
|
462
|
+
})
|
|
463
|
+
.superRefine(periodRefine),
|
|
464
|
+
);
|
|
462
465
|
|
|
463
466
|
export type CollectionCreateRequest = z.infer<typeof collectionCreateRequestSchema>;
|
|
464
467
|
export type CollectionCreateRequestInput = z.input<typeof collectionCreateRequestSchema>;
|
|
@@ -467,45 +470,51 @@ export type CollectionCreateRequestInput = z.input<typeof collectionCreateReques
|
|
|
467
470
|
* `PATCH /v1/collections/{collectionId}` — 준 항목만 바뀐다. `type`을 주면 400 `COLLECTION_TYPE_IMMUTABLE`이다.
|
|
468
471
|
* `revision`을 주면 그 번호일 때만 바뀌고, 그 사이 다른 수정이 있었으면 409 `COLLECTION_CHANGED`다.
|
|
469
472
|
*/
|
|
470
|
-
export const collectionPatchRequestSchema =
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
473
|
+
export const collectionPatchRequestSchema = strictRequest(
|
|
474
|
+
z
|
|
475
|
+
.object({
|
|
476
|
+
title: z.string().trim().min(1).max(MAX_COLLECTION_TITLE).optional(),
|
|
477
|
+
slug: slugField.optional(),
|
|
478
|
+
type: z.string().optional().describe("바꿀 수 없다. 주면 400 `COLLECTION_TYPE_IMMUTABLE`"),
|
|
479
|
+
rules: collectionRulesSchema
|
|
480
|
+
.optional()
|
|
481
|
+
.describe(
|
|
482
|
+
"조건 자동 컬렉션의 조건을 통째로 바꾼다. 직접 선택 컬렉션에 주면 400 `COLLECTION_RULES_NOT_ALLOWED`",
|
|
483
|
+
),
|
|
484
|
+
description: commonFields.description.optional(),
|
|
485
|
+
imageUrl: commonFields.imageUrl.optional(),
|
|
486
|
+
productSort: commonFields.productSort.optional(),
|
|
487
|
+
visible: commonFields.visible.optional(),
|
|
488
|
+
startsAt: commonFields.startsAt,
|
|
489
|
+
endsAt: commonFields.endsAt,
|
|
490
|
+
sortOrder: commonFields.sortOrder.optional(),
|
|
491
|
+
metaTitle: commonFields.metaTitle.optional(),
|
|
492
|
+
metaDescription: commonFields.metaDescription.optional(),
|
|
493
|
+
revision: z.number().int().min(0).optional().describe("읽은 수정 번호"),
|
|
494
|
+
})
|
|
495
|
+
.superRefine(periodRefine),
|
|
496
|
+
);
|
|
492
497
|
|
|
493
498
|
export type CollectionPatchRequest = z.infer<typeof collectionPatchRequestSchema>;
|
|
494
499
|
|
|
495
500
|
/** `PUT /v1/collections/{collectionId}/products` — 담긴 상품을 통째로 바꾼다(순서 포함) */
|
|
496
|
-
export const collectionProductsReplaceRequestSchema =
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
501
|
+
export const collectionProductsReplaceRequestSchema = strictRequest(
|
|
502
|
+
z.object({
|
|
503
|
+
productIds: productIdsField,
|
|
504
|
+
revision: z.number().int().min(0).optional().describe("읽은 수정 번호"),
|
|
505
|
+
}),
|
|
506
|
+
);
|
|
500
507
|
|
|
501
508
|
export type CollectionProductsReplaceRequest = z.infer<
|
|
502
509
|
typeof collectionProductsReplaceRequestSchema
|
|
503
510
|
>;
|
|
504
511
|
|
|
505
512
|
/** `POST /v1/collections/{collectionId}/products` — 뒤에 붙인다. 이미 담긴 상품은 그 자리에 둔다 */
|
|
506
|
-
export const collectionProductsAddRequestSchema =
|
|
507
|
-
|
|
508
|
-
|
|
513
|
+
export const collectionProductsAddRequestSchema = strictRequest(
|
|
514
|
+
z.object({
|
|
515
|
+
productIds: z.array(z.string().min(1)).min(1).max(MAX_COLLECTION_PRODUCTS_PER_ADD),
|
|
516
|
+
}),
|
|
517
|
+
);
|
|
509
518
|
|
|
510
519
|
export type CollectionProductsAddRequest = z.infer<typeof collectionProductsAddRequestSchema>;
|
|
511
520
|
|
|
@@ -562,14 +571,16 @@ export const collectionProductListQuerySchema = z.object({
|
|
|
562
571
|
});
|
|
563
572
|
|
|
564
573
|
/** `POST /v1/collections/preview` — 저장하지 않고 조건으로 나오는 상품을 본다 */
|
|
565
|
-
export const collectionPreviewRequestSchema =
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
574
|
+
export const collectionPreviewRequestSchema = strictRequest(
|
|
575
|
+
z.object({
|
|
576
|
+
rules: collectionRulesSchema,
|
|
577
|
+
productSort: collectionProductSortRequestSchema
|
|
578
|
+
.optional()
|
|
579
|
+
.describe(
|
|
580
|
+
"미리보기 순서. 생략하면 `LATEST`(판매량 상위 조건이 있으면 `BEST_SELLING`)이고, `MANUAL`이면 `LATEST`다",
|
|
581
|
+
),
|
|
582
|
+
}),
|
|
583
|
+
);
|
|
573
584
|
|
|
574
585
|
export type CollectionPreviewRequest = z.infer<typeof collectionPreviewRequestSchema>;
|
|
575
586
|
|