@sayren/store-sdk 0.20.0 → 0.22.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.mjs CHANGED
@@ -1258,8 +1258,9 @@ const couponRejectReasonSchema = z.enum([
1258
1258
  "NO_ELIGIBLE_ITEMS",
1259
1259
  "MIN_ORDER_AMOUNT",
1260
1260
  "LINE_NOT_ELIGIBLE",
1261
+ "LINE_AMBIGUOUS",
1261
1262
  "NO_DISCOUNT"
1262
- ]).describe("쿠폰을 적용하지 못한 사유. `DUPLICATE_COUPON`=같은 쿠폰을 두 번 넣음, `STACK_LIMIT`=같은 종류의 쿠폰이 이미 적용됨(주문 쿠폰·배송비 쿠폰은 주문당 1장, 상품 쿠폰은 주문상품 한 줄에 1장), `NOT_STARTED`=사용 기간 전, `EXPIRED`=사용 기간 지남, `MEMBER_ONLY`=회원 전용, `FIRST_ORDER_ONLY`=첫 구매 전용, `NO_ELIGIBLE_ITEMS`=대상 상품이 주문에 없음, `MIN_ORDER_AMOUNT`=최소 주문 금액 미달, `LINE_NOT_ELIGIBLE`=지정한 주문상품이 대상이 아니거나 이미 상품 쿠폰이 있음, `NO_DISCOUNT`=할인할 금액이 없음");
1263
+ ]).describe("쿠폰을 적용하지 못한 사유. `DUPLICATE_COUPON`=같은 쿠폰을 두 번 넣음, `STACK_LIMIT`=같은 종류의 쿠폰이 이미 적용됨(주문 쿠폰·배송비 쿠폰은 주문당 1장, 상품 쿠폰은 주문상품 한 줄에 1장), `NOT_STARTED`=사용 기간 전, `EXPIRED`=사용 기간 지남, `MEMBER_ONLY`=회원 전용, `FIRST_ORDER_ONLY`=첫 구매 전용, `NO_ELIGIBLE_ITEMS`=대상 상품이 주문에 없음, `MIN_ORDER_AMOUNT`=최소 주문 금액 미달, `LINE_NOT_ELIGIBLE`=지정한 주문상품이 대상이 아니거나 이미 상품 쿠폰이 있음, `LINE_AMBIGUOUS`=지정한 `optionId`의 줄이 여럿이라 줄을 정할 수 없음(`lineId`로 지목한다), `NO_DISCOUNT`=할인할 금액이 없음");
1263
1264
  /** 쿠폰 적용 뒤 결제 금액의 하한 — 0원 결제 경로가 없어 이보다 작으면 결제를 시작하지 않는다(400 `PAYMENT_AMOUNT_TOO_LOW`) */
1264
1265
  const MIN_PAYMENT_AMOUNT = 100;
1265
1266
  /**
@@ -2887,6 +2888,496 @@ const paymentListQuerySchema = z.object({
2887
2888
  size: z.string().optional().describe("페이지 크기 (기본 20, 최대 100)")
2888
2889
  });
2889
2890
  //#endregion
2891
+ //#region src/schemas/upload.ts
2892
+ /**
2893
+ * 업로드 용도. 허용 확장자와 크기 상한이 용도별로 다르다.
2894
+ *
2895
+ * - `product-image`: 상품 목록·상세의 상품 이미지. 목록 썸네일로 쓰이므로 정지 이미지만 받는다.
2896
+ * - `description`: 상세설명 본문에 삽입하는 미디어. 동영상과 svg도 받는다.
2897
+ */
2898
+ const uploadPurposeSchema = z.enum(["product-image", "description"]);
2899
+ const MB = 1048576;
2900
+ /**
2901
+ * 용도별 업로드 제한 — 서버(`POST /v1/uploads`)와 클라이언트가 공유하는 단일 원천이다.
2902
+ *
2903
+ * 크기는 **요청이 스스로 신고한 값**으로만 검사한다. 바이트는 api를 거치지 않고 CDN으로
2904
+ * 직접 PUT되므로 이 검사는 셀러가 올리기 전에 알게 하는 장치이고, 실제 상한은 CDN이 강제한다.
2905
+ */
2906
+ const UPLOAD_PURPOSE_LIMITS = {
2907
+ "product-image": {
2908
+ extensions: [
2909
+ "jpg",
2910
+ "jpeg",
2911
+ "png",
2912
+ "webp",
2913
+ "avif",
2914
+ "gif"
2915
+ ],
2916
+ maxBytes: 10 * MB
2917
+ },
2918
+ description: {
2919
+ extensions: [
2920
+ "jpg",
2921
+ "jpeg",
2922
+ "png",
2923
+ "gif",
2924
+ "webp",
2925
+ "avif",
2926
+ "svg",
2927
+ "mp4",
2928
+ "webm",
2929
+ "mov"
2930
+ ],
2931
+ maxBytes: 50 * MB
2932
+ }
2933
+ };
2934
+ /** 파일명의 소문자 확장자. 확장자가 없으면 `""` */
2935
+ function uploadExtensionOf(filename) {
2936
+ const dot = filename.lastIndexOf(".");
2937
+ if (dot < 0 || dot === filename.length - 1) return "";
2938
+ return filename.slice(dot + 1).toLowerCase();
2939
+ }
2940
+ /**
2941
+ * 용도 제한 위반 여부. 통과하면 `null`.
2942
+ * 서버가 던지는 오류와 콘솔이 먼저 띄우는 안내가 갈리지 않도록 한 함수로 판정한다.
2943
+ */
2944
+ function uploadRejection(purpose, filename, size) {
2945
+ const limit = UPLOAD_PURPOSE_LIMITS[purpose];
2946
+ if (!limit.extensions.includes(uploadExtensionOf(filename))) return {
2947
+ status: 400,
2948
+ code: "UNSUPPORTED_FILE_TYPE",
2949
+ message: `지원하지 않는 파일 형식입니다. ${limit.extensions.join(", ")}만 올릴 수 있습니다`
2950
+ };
2951
+ if (size !== void 0 && size > limit.maxBytes) return {
2952
+ status: 413,
2953
+ code: "FILE_TOO_LARGE",
2954
+ message: `파일이 너무 큽니다. ${Math.floor(limit.maxBytes / MB)}MB까지 올릴 수 있습니다`
2955
+ };
2956
+ return null;
2957
+ }
2958
+ /**
2959
+ * 미디어 업로드 자리 발급 — `POST /v1/uploads`.
2960
+ *
2961
+ * 파일 바이트는 api를 거치지 않는다. 응답의 `uploadUrl`로 클라이언트가 직접 PUT하고,
2962
+ * 끝나면 `url`이 공개 주소가 된다. 서버가 객체 키를 정하므로 요청은 파일명만 보낸다
2963
+ * (확장자만 쓰이고 이름 자체는 저장되지 않는다).
2964
+ */
2965
+ const uploadRequestSchema = z.object({
2966
+ filename: z.string().min(1).max(255),
2967
+ purpose: uploadPurposeSchema.default("description").describe("업로드 용도. 허용 확장자와 크기 상한이 용도별로 다르다"),
2968
+ size: z.number().int().positive().optional().describe("파일 바이트 크기. 주면 용도별 상한을 발급 전에 검사한다")
2969
+ });
2970
+ const uploadTicketSchema = z.object({
2971
+ /** 업로드 완료 후 공개될 주소 */
2972
+ url: z.url(),
2973
+ /** 파일 바이트를 PUT할 presigned URL. 1시간 유효 */
2974
+ uploadUrl: z.url()
2975
+ });
2976
+ //#endregion
2977
+ //#region src/schemas/product.ts
2978
+ const productStatusSchema = z.enum([
2979
+ "SALE",
2980
+ "SUSPENDED",
2981
+ "OUT_OF_STOCK",
2982
+ "CLOSED"
2983
+ ]);
2984
+ const productSummarySchema = z.object({
2985
+ productId: z.string(),
2986
+ name: z.string(),
2987
+ status: productStatusSchema,
2988
+ salePrice: z.number().int().describe("판매가. 원(KRW) 단위 정수"),
2989
+ discountedPrice: z.number().int().nullable().describe("즉시 할인 적용가. 원 단위. 할인이 없으면 null"),
2990
+ /** usable variant 재고 합계 (서버 계산) */
2991
+ stockQuantity: z.number().int().describe("판매 가능한 옵션 재고의 합계. 서버가 계산한다"),
2992
+ categoryId: z.string().nullable(),
2993
+ thumbnailUrl: z.url().optional(),
2994
+ /** optionGroups.length > 0 */
2995
+ hasOptions: z.boolean(),
2996
+ fulfillment: z.object({ type: fulfillmentTypeResponseSchema }).describe("상품 이행의 유형. 유형별 속성은 단건 조회(`GET /v1/products/{productId}`)의 `fulfillment`에 있다"),
2997
+ updatedAt: z.string()
2998
+ });
2999
+ /** 옵션 축(그룹)이 가지는 값 하나 — 예: 축 "색상"의 값 "블랙" */
3000
+ const optionValueSchema = z.object({
3001
+ valueId: z.string(),
3002
+ name: z.string(),
3003
+ sortOrder: z.number().int()
3004
+ });
3005
+ /** 옵션 축(그룹) — 예: "색상", "사이즈" */
3006
+ const optionGroupSchema = z.object({
3007
+ groupId: z.string(),
3008
+ name: z.string(),
3009
+ sortOrder: z.number().int(),
3010
+ values: z.array(optionValueSchema)
3011
+ });
3012
+ /**
3013
+ * 판매 단위(SKU). variantId는 기존 optionId 값 공간을 승계하므로
3014
+ * 카트·주문·리뷰·클레임의 optionId가 곧 variantId다.
3015
+ */
3016
+ const productVariantSchema = z.object({
3017
+ variantId: z.string(),
3018
+ /** 축 sortOrder 순서와 1:1 대응. 옵션 없는 상품은 [] */
3019
+ valueIds: z.array(z.string()),
3020
+ /** 값 이름을 " / "로 이은 파생 표시명. 옵션 없으면 "" */
3021
+ name: z.string(),
3022
+ additionalPrice: z.number().int(),
3023
+ stockQuantity: z.number().int(),
3024
+ sku: z.string().nullable(),
3025
+ barcode: z.string().nullable(),
3026
+ costPrice: z.number().int().nullable(),
3027
+ weightGram: z.number().int().nullable(),
3028
+ sellerCode: z.string().nullable(),
3029
+ usable: z.boolean(),
3030
+ hasOrders: z.boolean().optional().describe("주문에 담긴 적이 있는 조합인가(이슈 #59). true인 조합은 옵션 교체에서 뺄 수 없고(409 `OPTION_HAS_ORDERS`) `usable: false`로 판매를 끈다. 서버는 항상 싣는다. 요청에는 쓰지 않는다")
3031
+ });
3032
+ /** 옵션 축(그룹) 최대 개수 — 스키마 상한의 단일 원천 */
3033
+ const MAX_OPTION_GROUPS = 3;
3034
+ /** 축 하나가 가질 수 있는 값의 최대 개수 */
3035
+ const MAX_VALUES_PER_GROUP = 100;
3036
+ /** 상품 하나가 가질 수 있는 variant(SKU) 최대 개수 */
3037
+ const MAX_VARIANTS = 500;
3038
+ /** 추가 선택 옵션명 최대 개수 */
3039
+ const MAX_ADDON_GROUPS = 5;
3040
+ /** 직접 입력 항목 최대 개수 */
3041
+ const MAX_CUSTOM_INPUTS = 5;
3042
+ /** 직접 입력 항목 최대 글자 수의 상한 */
3043
+ const MAX_CUSTOM_INPUT_LENGTH = 200;
3044
+ /** 조합 수가 이 값 이상이면 콘솔이 경고한다(상한은 `MAX_VARIANTS`) */
3045
+ const VARIANT_COUNT_WARNING = 100;
3046
+ /**
3047
+ * 옵션 값 정렬 순서 — 등록순·가나다순·낮은 추가금순·높은 추가금순. 서버가 저장할 때 이 순서로 값의 `sortOrder`를 매긴다.
3048
+ * 추가금순은 추가 선택 값이면 값의 추가금, 조합 값이면 그 값이 든 조합 추가금의 최솟값으로 비교하고 같으면 등록순이다.
3049
+ */
3050
+ const optionSortSchema = z.enum([
3051
+ "REGISTERED",
3052
+ "NAME",
3053
+ "PRICE_ASC",
3054
+ "PRICE_DESC"
3055
+ ]);
3056
+ /** 추가 선택 값 — 예: 옵션명 "선물 포장"의 값 "포장함"(+2,000원) */
3057
+ const addonValueSchema = z.object({
3058
+ valueId: z.string(),
3059
+ name: z.string(),
3060
+ sortOrder: z.number().int(),
3061
+ additionalPrice: z.number().int().describe("이 값을 고르면 단가에 더하는 금액(원, 0 이상)"),
3062
+ usable: z.boolean().describe("사용 여부. false면 구매자에게 보이지 않고 고를 수 없다")
3063
+ });
3064
+ /** 추가 선택 옵션명 */
3065
+ const addonGroupSchema = z.object({
3066
+ groupId: z.string(),
3067
+ name: z.string(),
3068
+ sortOrder: z.number().int(),
3069
+ required: z.boolean().describe("구매자가 꼭 골라야 하는가. false면 고르지 않아도 된다"),
3070
+ values: z.array(addonValueSchema)
3071
+ });
3072
+ /** 직접 입력 항목 — 예: 각인 문구 */
3073
+ const customInputSchema = z.object({
3074
+ inputId: z.string(),
3075
+ label: z.string().describe("항목명"),
3076
+ placeholder: z.string().nullable().describe("입력 안내(구매자 입력 칸의 안내 문구). 없으면 null"),
3077
+ maxLength: z.number().int().describe("최대 글자 수"),
3078
+ required: z.boolean(),
3079
+ usable: z.boolean().describe("사용 여부. false면 구매자에게 보이지 않는다"),
3080
+ sortOrder: z.number().int()
3081
+ });
3082
+ /**
3083
+ * 직접 입력 항목명에 쓸 수 없는 개인정보성 단어 — 콘솔 검사와 서버 400(`CUSTOM_INPUT_LABEL_FORBIDDEN`)이 이 목록 하나를 쓴다.
3084
+ * 구매자 개인정보는 주문 정보로 받는다. 직접 입력 항목으로 받으면 보관·파기 규칙 밖에 남는다.
3085
+ */
3086
+ const CUSTOM_INPUT_FORBIDDEN_WORDS = [
3087
+ "연락처",
3088
+ "전화",
3089
+ "휴대폰",
3090
+ "핸드폰",
3091
+ "폰번호",
3092
+ "메일",
3093
+ "주소",
3094
+ "주민",
3095
+ "생년월일",
3096
+ "생일",
3097
+ "실명",
3098
+ "계좌",
3099
+ "카드번호",
3100
+ "비밀번호",
3101
+ "여권",
3102
+ "통관",
3103
+ "phone",
3104
+ "mobile",
3105
+ "mail",
3106
+ "address"
3107
+ ];
3108
+ /** 금지어 비교용 정규화 — NFKC(전각·호환 문자를 보통 문자로)·소문자 뒤 문자·숫자만 남긴다(공백·기호·제로폭 문자로 쪼갠 우회를 막는다) */
3109
+ function forbiddenWordKeyOf(text) {
3110
+ return text.normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}]/gu, "");
3111
+ }
3112
+ /**
3113
+ * 직접 입력 항목명·입력 안내에 든 개인정보성 금지어. 없으면 null. 공백·기호·대소문자·전각을 무시한다.
3114
+ * 서버는 항목명과 입력 안내 둘 다 이 함수로 검사한다(안내에 「연락처를 적어 주십시오」라고 쓰는 우회를 막는다).
3115
+ */
3116
+ function customInputLabelForbiddenWord(label) {
3117
+ const normalized = forbiddenWordKeyOf(label);
3118
+ return CUSTOM_INPUT_FORBIDDEN_WORDS.find((word) => normalized.includes(forbiddenWordKeyOf(word))) ?? null;
3119
+ }
3120
+ /** 옵션명·값 이름 — 앞뒤 공백을 지우고 빈 값은 받지 않는다 */
3121
+ const optionNameSchema = z.string().trim().min(1).max(50);
3122
+ const addonValueRequestSchema = z.object({
3123
+ /** 전달 시 기존 값 행을 유지(UPDATE). 생략하면 신규 발급 */
3124
+ valueId: z.string().optional(),
3125
+ name: optionNameSchema,
3126
+ additionalPrice: z.number().int().min(0).default(0).describe("추가금(원, 0 이상)"),
3127
+ usable: z.boolean().default(true)
3128
+ });
3129
+ const addonGroupRequestSchema = z.object({
3130
+ /** 전달 시 기존 옵션명 행을 유지(UPDATE). 조합 옵션명의 id는 줄 수 없다(방식을 바꾸려면 새 옵션명으로 보낸다) */
3131
+ groupId: z.string().optional(),
3132
+ name: optionNameSchema,
3133
+ required: z.boolean().default(false),
3134
+ values: z.array(addonValueRequestSchema).min(1).max(100)
3135
+ });
3136
+ const customInputRequestSchema = z.object({
3137
+ /** 전달 시 기존 항목 행을 유지(UPDATE). 생략하면 신규 발급 */
3138
+ inputId: z.string().optional(),
3139
+ label: z.string().trim().min(1).max(30),
3140
+ placeholder: z.string().trim().max(100).nullish(),
3141
+ maxLength: z.number().int().min(1).max(200),
3142
+ required: z.boolean().default(false),
3143
+ usable: z.boolean().default(true)
3144
+ });
3145
+ /**
3146
+ * 주문상품·장바구니·주문서 줄의 옵션 선택 스냅샷 한 칸 — 주문 시점 이름이다(상품을 고쳐도 바뀌지 않는다).
3147
+ * 조합 옵션은 옵션명마다 한 칸이고 추가금은 조합 단위라 0이다(조합 추가금은 단가에 들어 있다). 추가 선택은 값의 추가금이다.
3148
+ */
3149
+ const optionSelectionSchema = z.object({
3150
+ kind: z.string().describe("옵션명 방식. 현재 값은 `combination`·`addon`이고 값이 추가될 수 있어 문자열로 받는다"),
3151
+ groupName: z.string(),
3152
+ valueName: z.string(),
3153
+ additionalPrice: z.number().int()
3154
+ });
3155
+ /** 구매자가 적은 직접 입력값 — 주문 시점 항목명과 값 */
3156
+ const customInputValueSchema = z.object({
3157
+ label: z.string(),
3158
+ value: z.string()
3159
+ });
3160
+ const discountSchema = z.object({
3161
+ productId: z.string(),
3162
+ discountType: z.enum(["RATE", "AMOUNT"]),
3163
+ discountValue: z.number().int(),
3164
+ discountedPrice: z.number().int(),
3165
+ startAt: z.string().nullable(),
3166
+ endAt: z.string().nullable()
3167
+ });
3168
+ const productSchema = productSummarySchema.extend({
3169
+ description: z.string().optional(),
3170
+ /**
3171
+ * 상세설명 에디터(@avarlabs/editor)의 직렬화 상태. 재편집 때 이 값으로 문서를 복원한다.
3172
+ * 표시는 `description`(HTML)이 맡는다 — 소비자는 이 필드를 몰라도 된다.
3173
+ */
3174
+ descriptionJson: z.string().nullable().optional(),
3175
+ originalPrice: z.number().int().nullable().optional(),
3176
+ images: z.array(z.url()).optional(),
3177
+ attributes: z.array(z.object({
3178
+ attributeId: z.string(),
3179
+ value: z.string()
3180
+ })).optional(),
3181
+ optionGroups: z.array(optionGroupSchema),
3182
+ /** 모든 상품이 최소 1개 variant를 갖는다 (옵션 없으면 default variant 1개) */
3183
+ variants: z.array(productVariantSchema),
3184
+ addonGroups: z.array(addonGroupSchema).optional().default([]).describe("추가 선택 옵션명(이슈 #59). 없으면 빈 배열. 서버는 항상 싣는다"),
3185
+ customInputs: z.array(customInputSchema).optional().default([]).describe("직접 입력 항목(이슈 #59). 사용하지 않는 항목도 싣는다. 없으면 빈 배열. 서버는 항상 싣는다"),
3186
+ optionSort: optionSortSchema.optional().default("REGISTERED").describe("옵션 값 정렬 순서(이슈 #59). 서버는 항상 싣는다"),
3187
+ fulfillment: productFulfillmentResponseSchema,
3188
+ fulfillmentTypeLocked: z.boolean().describe("판매 이력(주문상품)이 있어 상품 유형(`fulfillment.type`)을 바꿀 수 없는가. true면 다른 유형으로 수정하는 요청이 409 `FULFILLMENT_TYPE_LOCKED`다. 같은 유형 안의 속성은 바꿀 수 있다"),
3189
+ refundPolicy: productRefundPolicyPatchSchema.nullable().describe("상품별 환불 정책 덮어쓰기(상품이 정한 항목만). null이면 스토어 기본 환불 정책을 쓴다. 반품·교환 배송비(`returnDeliveryFee`·`exchangeDeliveryFee`)는 배송 상품에만 있다"),
3190
+ /**
3191
+ * 설정된 즉시할인. `GET /products/{productId}/discount`의 응답과 같은 형태다(이슈 #51).
3192
+ * 할인이 없으면 null이다. 이 필드를 싣기 전 서버의 응답에는 없으므로 optional이다.
3193
+ */
3194
+ discount: discountSchema.nullable().optional().describe("설정된 즉시할인. GET /v1/products/{productId}/discount 응답과 같은 형태다. 할인이 없으면 null"),
3195
+ createdAt: z.string().optional()
3196
+ });
3197
+ /** 상품 하나에 등록할 수 있는 이미지 최대 장수 — 첫 장이 대표(썸네일)다 */
3198
+ const MAX_PRODUCT_IMAGES = 10;
3199
+ /**
3200
+ * 상품 이미지 주소의 와이어 형식 — URL이고 2048자 이하다.
3201
+ *
3202
+ * https·확장자 규칙은 여기서 강제하지 않고 `productImageRejection`이 판정한다. 서버가
3203
+ * **새로 추가된 주소에만** 그 규칙을 적용해야 하기 때문이다(규칙이 생기기 전에 저장된
3204
+ * 주소 하나로 상품 수정 전체가 막히면 셀러가 빠져나갈 길이 없다).
3205
+ *
3206
+ * 응답 스키마(`productSchema.images`)도 넓게 둔다. 과거에 저장된 주소를 파싱 오류로
3207
+ * 만들면 상품 조회 자체가 막힌다.
3208
+ */
3209
+ const productImageUrlSchema = z.url().max(2048).describe("상품 이미지 주소. https만 허용하고, 확장자가 있으면 이미지 형식이어야 한다");
3210
+ /**
3211
+ * 상품에 **새로 붙이는** 이미지 주소의 규칙. 통과하면 `null`.
3212
+ *
3213
+ * - https만 받는다. http는 https 스토어프론트에서 혼합 콘텐츠로 차단되고 `data:`·`javascript:`는
3214
+ * 상세 화면에 박히는 주입 경로가 된다.
3215
+ * - 호스트는 제한하지 않는다 — 셀러가 이미 쓰는 이미지 호스트를 그대로 붙일 수 있어야 한다.
3216
+ * - 경로에 확장자가 있으면 상품 이미지 형식이어야 한다(svg·동영상 거부). **확장자가 없으면 통과시킨다**
3217
+ * — 외부 이미지 CDN은 확장자 없는 키를 쓰는 경우가 흔하고, 주소만으로는 실제 형식을 알 수 없다.
3218
+ */
3219
+ function productImageRejection(url) {
3220
+ if (url.length > 2048) return {
3221
+ code: "INVALID_IMAGE_URL",
3222
+ message: "이미지 주소가 너무 깁니다"
3223
+ };
3224
+ let parsed;
3225
+ try {
3226
+ parsed = new URL(url);
3227
+ } catch {
3228
+ return {
3229
+ code: "INVALID_IMAGE_URL",
3230
+ message: "이미지 주소 형식이 아닙니다"
3231
+ };
3232
+ }
3233
+ if (parsed.protocol !== "https:") return {
3234
+ code: "INVALID_IMAGE_URL",
3235
+ message: "이미지 주소는 https로 시작해야 합니다"
3236
+ };
3237
+ const extension = uploadExtensionOf(parsed.pathname);
3238
+ const allowed = UPLOAD_PURPOSE_LIMITS["product-image"].extensions;
3239
+ if (extension && !allowed.includes(extension)) return {
3240
+ code: "UNSUPPORTED_FILE_TYPE",
3241
+ message: `상품 이미지는 ${allowed.join(", ")}만 쓸 수 있습니다`
3242
+ };
3243
+ return null;
3244
+ }
3245
+ /**
3246
+ * 식별 코드 — 빈 문자열·공백만 있는 값은 거부한다.
3247
+ * `""`를 통과시키면 서버 중복 사전 검사(falsy skip)와 DB partial unique
3248
+ * (`WHERE ... is not null`)의 판정이 갈려 409 대신 500이 나간다.
3249
+ */
3250
+ const codeSchema = z.string().trim().min(1).max(64);
3251
+ const optionValueRequestSchema = z.object({
3252
+ /** 전달 시 기존 값 행을 유지(UPDATE). 생략하면 신규 발급 */
3253
+ valueId: z.string().optional(),
3254
+ name: z.string().min(1).max(50)
3255
+ });
3256
+ const optionGroupRequestSchema = z.object({
3257
+ /** 전달 시 기존 축 행을 유지(UPDATE). 생략하면 신규 발급 */
3258
+ groupId: z.string().optional(),
3259
+ name: z.string().min(1).max(50),
3260
+ values: z.array(optionValueRequestSchema).min(1).max(100)
3261
+ });
3262
+ const productVariantRequestSchema = z.object({
3263
+ /** 기존 variant 유지 시 전달. 생략하면 신규 생성 */
3264
+ variantId: z.string().optional(),
3265
+ /** optionGroups 순서대로 각 축에서 고른 값 이름. 서버가 이름 → valueId를 해석한다 */
3266
+ valueNames: z.array(z.string()).default([]),
3267
+ additionalPrice: z.number().int().default(0),
3268
+ /**
3269
+ * 생략 = 기존 재고 유지 (신규 variant는 0).
3270
+ * 구조 교체(PUT)가 폼 로드 시점의 재고를 되쓰지 않게 하는 옵트인 필드다 —
3271
+ * 재고만 바꿀 때는 PATCH /products/{id}/variants/{variantId}/stock을 쓴다.
3272
+ */
3273
+ stockQuantity: z.number().int().min(0).optional(),
3274
+ sku: codeSchema.optional(),
3275
+ barcode: codeSchema.optional(),
3276
+ costPrice: z.number().int().min(0).optional(),
3277
+ weightGram: z.number().int().min(0).optional(),
3278
+ sellerCode: codeSchema.optional(),
3279
+ usable: z.boolean().default(true)
3280
+ });
3281
+ /** 축 × 값 → variant 전체 교체 요청 (PUT /products/{productId}/options) */
3282
+ const optionModelRequestSchema = z.object({
3283
+ optionGroups: z.array(optionGroupRequestSchema).max(3).default([]),
3284
+ variants: z.array(productVariantRequestSchema).min(1).max(500),
3285
+ addonGroups: z.array(addonGroupRequestSchema).max(5).optional().describe("추가 선택 옵션명 전체(이슈 #59). 주면 통째로 바꾸고, 생략하면 등록은 없음·수정은 현재 값을 그대로 둔다. 끄려면 빈 배열을 보낸다. 기능이 열리기 전에는 비어 있지 않은 값이 409 `OPTION_ADDONS_UNAVAILABLE`이다"),
3286
+ customInputs: z.array(customInputRequestSchema).max(5).optional().describe("직접 입력 항목 전체(이슈 #59). 배열 순서가 표시 순서다. 주면 통째로 바꾸고, 생략하면 등록은 없음·수정은 현재 값을 그대로 둔다. 항목명에 개인정보성 단어(`CUSTOM_INPUT_FORBIDDEN_WORDS`)가 있으면 400 `CUSTOM_INPUT_LABEL_FORBIDDEN`, 기능이 열리기 전에는 비어 있지 않은 값이 409 `OPTION_ADDONS_UNAVAILABLE`이다"),
3287
+ optionSort: optionSortSchema.optional().describe("옵션 값 정렬 순서(이슈 #59). 서버가 이 순서로 값의 `sortOrder`를 다시 매긴다. 생략하면 등록은 `REGISTERED`, 수정은 현재 값이다")
3288
+ });
3289
+ /** GET /products/{productId}/options 응답 */
3290
+ const optionModelSchema = z.object({
3291
+ optionGroups: z.array(optionGroupSchema),
3292
+ variants: z.array(productVariantSchema),
3293
+ addonGroups: z.array(addonGroupSchema).optional().default([]).describe("추가 선택 옵션명(이슈 #59). 없으면 빈 배열. 서버는 항상 싣는다"),
3294
+ customInputs: z.array(customInputSchema).optional().default([]).describe("직접 입력 항목(이슈 #59). 없으면 빈 배열. 서버는 항상 싣는다"),
3295
+ optionSort: optionSortSchema.optional().default("REGISTERED").describe("옵션 값 정렬 순서(이슈 #59). 서버는 항상 싣는다")
3296
+ });
3297
+ /**
3298
+ * 재고 변경 택일 위반 문구 — SDK `.refine`과 서버 DomainException이 공유하는 단일 원천.
3299
+ * 경로에 따라 셀러가 다른 문구를 받지 않게 한다.
3300
+ */
3301
+ const VARIANT_STOCK_EXCLUSIVE_MESSAGE = "stockQuantity 또는 adjustment 중 하나만 전달해야 합니다";
3302
+ /**
3303
+ * variant 재고 변경 요청 — 절대값(`stockQuantity`) 또는 증감(`adjustment`) 택일.
3304
+ * 서버도 같은 규칙을 400 VALIDATION_FAILED로 검증한다(이중 방어).
3305
+ */
3306
+ const variantStockRequestSchema = z.object({
3307
+ stockQuantity: z.number().int().min(0).optional(),
3308
+ adjustment: z.number().int().optional()
3309
+ }).refine((v) => v.stockQuantity !== void 0 !== (v.adjustment !== void 0), { message: VARIANT_STOCK_EXCLUSIVE_MESSAGE });
3310
+ /** PATCH /products/{productId}/variants/{variantId}/stock 응답 */
3311
+ const variantStockSchema = z.object({
3312
+ variantId: z.string(),
3313
+ stockQuantity: z.number().int()
3314
+ });
3315
+ /**
3316
+ * 상품 쓰기 요청의 공통 필드 — 등록(POST)과 전체 수정(PUT)이 같다. 이행(`fulfillment`)만 두 요청의 스키마가 다르다.
3317
+ *
3318
+ * 최상위가 `strictObject`다. 모르는 필드는 400이다 — 이슈 #57 이전의 평면 필드(`deliveryFee`·`fulfillmentType`·`returnFee` 등)를
3319
+ * 보낸 요청이 조용히 무시되지 않게 한다. 옛 필드는 `fulfillment.{유형}.…`과 `refundPolicy`로 옮겼다.
3320
+ */
3321
+ const productWriteShape = {
3322
+ name: z.string().min(1).max(100),
3323
+ categoryId: z.string().min(1, "카테고리를 선택해주십시오"),
3324
+ salePrice: z.number().int().min(10).multipleOf(10, "판매가는 10원 단위여야 합니다 (INVALID_PRICE_UNIT)").describe("판매가. 원(KRW) 단위 정수, 최소 10원, 10원 단위"),
3325
+ originalPrice: z.number().int().optional().describe("정가(할인 전 표시 가격). 원 단위"),
3326
+ description: z.string().optional(),
3327
+ /**
3328
+ * 상세설명 에디터(@avarlabs/editor)의 직렬화 상태. 재편집 때 이 값으로 문서를 복원한다.
3329
+ * 표시는 `description`(HTML)이 맡는다 — 소비자는 이 필드를 몰라도 된다.
3330
+ */
3331
+ descriptionJson: z.string().nullable().optional(),
3332
+ images: z.array(productImageUrlSchema).min(1).max(10).optional().describe("상품 이미지 주소 목록. 첫 장이 대표 이미지(썸네일)다"),
3333
+ attributes: z.array(z.object({
3334
+ attributeId: z.string(),
3335
+ value: z.string()
3336
+ })).optional(),
3337
+ refundPolicy: productRefundPolicyPatchSchema.nullish().describe("상품별 환불 정책 — 준 항목만 스토어 기본을 덮어쓴다. `null`이나 생략은 스토어 기본을 그대로 쓴다는 뜻이다. 반품·교환 배송비(`returnDeliveryFee`·`exchangeDeliveryFee`)는 배송 상품에만 줄 수 있다(다른 유형은 400)"),
3338
+ ...optionModelRequestSchema.shape,
3339
+ sourceOptionSetIds: z.array(z.string()).max(10).optional().describe("쓰기 전용(이슈 #59) — 이 저장으로 옵션을 불러온 저장 옵션 id. 상품 저장과 같은 트랜잭션에서 불러오기 기록을 남겨 저장 옵션의 `importedProductCount`를 센다. 없는 id는 무시한다")
3340
+ };
3341
+ const productCreateRequestSchema = z.strictObject({
3342
+ ...productWriteShape,
3343
+ fulfillment: productFulfillmentCreateSchema.optional().describe("상품 이행. `type`이 유형이고 같은 이름의 키가 유형별 속성이다. 생략하면 배송 상품(기본값)이다. 배송 상품이 아닌 유형은 판매가 열린 유형만 등록할 수 있다(409 `FULFILLMENT_TYPE_NOT_AVAILABLE`)")
3344
+ });
3345
+ const fulfillmentUpdateDescription = "상품 이행. 생략하면 바꾸지 않는다. 주면 유형 안 필드를 모두 담아 통째로 바꾼다(현재 값과 병합하지 않는다). 판매 이력이 있으면 유형을 바꿀 수 없다(409 `FULFILLMENT_TYPE_LOCKED`)";
3346
+ /** 전체 수정(PUT) — 공통 필드는 등록과 같고, 이행은 생략하면 현재 값 유지, 주면 필드가 모두 필수다 */
3347
+ const productReplaceRequestSchema = z.strictObject({
3348
+ ...productWriteShape,
3349
+ fulfillment: productFulfillmentUpdateSchema.optional().describe(fulfillmentUpdateDescription)
3350
+ });
3351
+ const productPatchRequestSchema = z.strictObject({
3352
+ name: z.string().max(100).optional(),
3353
+ salePrice: z.number().int().multipleOf(10).optional(),
3354
+ status: productStatusSchema.optional(),
3355
+ fulfillment: productFulfillmentUpdateSchema.optional().describe(fulfillmentUpdateDescription),
3356
+ refundPolicy: productRefundPolicyPatchSchema.nullable().optional().describe("상품별 환불 정책. 주면 통째로 바꾼다(준 항목만 스토어 기본을 덮어쓴다). `null`은 덮어쓰기를 지워 스토어 기본을 따른다. 생략하면 바꾸지 않는다")
3357
+ }).refine((value) => Object.values(value).some((v) => v !== void 0), { message: "최소 1개 필드가 필요합니다" });
3358
+ const productSortSchema = z.enum([
3359
+ "createdAt.desc",
3360
+ "createdAt.asc",
3361
+ "salePrice.asc",
3362
+ "salePrice.desc",
3363
+ "updatedAt.desc"
3364
+ ]);
3365
+ /** `GET /v1/products` 쿼리 — OpenAPI 문서용 형태(값은 쿼리 문자열) */
3366
+ const productListQuerySchema = z.object({
3367
+ status: productStatusSchema.optional().describe("판매 상태 필터"),
3368
+ categoryId: z.string().optional().describe("카테고리 필터 — 하위 카테고리의 상품도 포함"),
3369
+ keyword: z.string().optional().describe("상품명 검색 (포함)"),
3370
+ sellerCode: z.string().optional().describe("판매자 상품코드 — 이 코드가 붙은 variant를 가진 상품"),
3371
+ sort: productSortSchema.optional().describe("정렬 (기본 createdAt.desc)"),
3372
+ ...pageQueryShape()
3373
+ });
3374
+ const discountRequestSchema = z.object({
3375
+ discountType: z.enum(["RATE", "AMOUNT"]),
3376
+ discountValue: z.number().int().min(1),
3377
+ startAt: z.string().optional(),
3378
+ endAt: z.string().optional()
3379
+ });
3380
+ //#endregion
2890
3381
  //#region src/schemas/order.ts
2891
3382
  const orderSummarySchema = z.object({
2892
3383
  orderId: z.string(),
@@ -2926,6 +3417,8 @@ const orderItemSchema = z.object({
2926
3417
  productName: z.string(),
2927
3418
  optionId: z.string().nullable(),
2928
3419
  optionName: z.string().nullable(),
3420
+ optionSelections: z.array(optionSelectionSchema).optional().default([]).describe("주문 시점 옵션 선택(이슈 #59) — 조합 옵션(옵션명마다 한 칸, 추가금 0)과 추가 선택(값의 추가금). 옵션이 없거나 이 필드 이전 주문은 빈 배열. 서버는 항상 싣는다"),
3421
+ customInputs: z.array(customInputValueSchema).optional().default([]).describe("구매자가 적은 직접 입력값(주문 시점 항목명과 값, 이슈 #59). 없으면 빈 배열. 서버는 항상 싣는다"),
2929
3422
  sellerCode: z.string().nullable(),
2930
3423
  fulfillmentSnapshot: orderItemFulfillmentSnapshotResponseSchema.describe("주문 시점 이행 스냅샷(이슈 #57). `type`이 주문 시점 상품 유형이다. 배송 상품이 아니면 발송·배송완료 대신 제공 처리를 하고, `DELIVERED`는 제공 완료다. 상품을 나중에 고쳐도 바뀌지 않는다. 제공 기록은 `fulfillment`에 있다"),
2931
3424
  quantity: z.number().int().describe("주문 수량"),
@@ -3115,397 +3608,145 @@ const fulfillRequestSchema = z.object({ note: z.string().trim().max(1e3).optiona
3115
3608
  /** 제공 처리 일괄 요청 — `POST /v1/order-items/fulfill` */
3116
3609
  const fulfillBulkRequestSchema = z.object({
3117
3610
  orderItemIds: z.array(z.string()).min(1).max(100),
3118
- note: fulfillRequestSchema.shape.note
3119
- });
3120
- /** `POST /v1/order-items/{orderItemId}/cancel` 응답 — 셀러 직권취소 환불 결과 */
3121
- const cancelBySellerResultSchema = z.object({
3122
- orderItemId: z.string(),
3123
- refundAmount: z.number().int().describe("실제 환불한 금액 — 수량 비율 상품 실결제 금액(쿠폰 배분을 뺀 금액) + 배송비 환불 − 조건부 무료배송 미달 차감 + 되돌린 쿠폰 차감(`couponChargeRefundAmount`)"),
3124
- quantity: z.number().int().describe("취소한 수량"),
3125
- canceledQuantity: z.number().int().describe("취소 후 누적 취소 수량"),
3126
- deliveryFeeRefundAmount: z.number().int().describe("환불에 포함된 배송비. 배송 묶음(같은 상품)이 모두 취소됐고 환불 정책이 켜져 있을 때만 0보다 크다"),
3127
- deliveryFeeChargeAmount: z.number().int().describe("조건부 무료배송 기준 미달로 환불액에서 뺀 배송비. 정책이 꺼져 있으면 항상 0이다"),
3128
- couponChargeAmount: z.number().int().optional().default(0).describe("쿠폰 조건 미달 차감. 판매자 취소는 차감하지 않으므로 늘 0이다. 서버는 항상 싣는다"),
3129
- couponChargeRefundAmount: z.number().int().optional().default(0).describe("환불액에 더한, 앞선 환불에서 뺀 쿠폰 차감을 되돌린 몫. 서버는 항상 싣는다"),
3130
- refundedAt: z.string()
3131
- });
3132
- /** `PUT /v1/order-items/{orderItemId}/memo` 응답 — 빈 문자열로 저장하면 메모가 지워져 `null`이다 */
3133
- const orderItemMemoResultSchema = z.object({
3134
- orderItemId: z.string(),
3135
- sellerMemo: z.string().nullable()
3136
- });
3137
- /** `POST /v1/order-items/{orderItemId}/purchase-decision/request` 응답 */
3138
- const purchaseDecisionRequestResultSchema = z.object({
3139
- orderItemId: z.string(),
3140
- requestedAt: z.string()
3141
- });
3142
- /** `POST /v1/order-items/{orderItemId}/purchase-decision/extend` 응답 — 연장은 최대 2회 */
3143
- const purchaseDecisionExtendResultSchema = z.object({
3144
- orderItemId: z.string(),
3145
- autoDecisionDate: z.string(),
3146
- extendCount: z.number().int().max(2)
3147
- });
3148
- /**
3149
- * `GET /v1/orders/{orderId}/address-change-requests` 응답 — 배송지 변경 요청 플로우는 아직 제공하지 않아
3150
- * 현재는 항상 빈 배열이다
3151
- */
3152
- const addressChangeRequestListSchema = z.array(z.record(z.string(), z.unknown())).describe("배송지 변경 요청 목록 (현재 항상 빈 배열)");
3153
- //#endregion
3154
- //#region src/schemas/upload.ts
3155
- /**
3156
- * 업로드 용도. 허용 확장자와 크기 상한이 용도별로 다르다.
3157
- *
3158
- * - `product-image`: 상품 목록·상세의 상품 이미지. 목록 썸네일로 쓰이므로 정지 이미지만 받는다.
3159
- * - `description`: 상세설명 본문에 삽입하는 미디어. 동영상과 svg도 받는다.
3160
- */
3161
- const uploadPurposeSchema = z.enum(["product-image", "description"]);
3162
- const MB = 1048576;
3163
- /**
3164
- * 용도별 업로드 제한 — 서버(`POST /v1/uploads`)와 클라이언트가 공유하는 단일 원천이다.
3165
- *
3166
- * 크기는 **요청이 스스로 신고한 값**으로만 검사한다. 바이트는 api를 거치지 않고 CDN으로
3167
- * 직접 PUT되므로 이 검사는 셀러가 올리기 전에 알게 하는 장치이고, 실제 상한은 CDN이 강제한다.
3168
- */
3169
- const UPLOAD_PURPOSE_LIMITS = {
3170
- "product-image": {
3171
- extensions: [
3172
- "jpg",
3173
- "jpeg",
3174
- "png",
3175
- "webp",
3176
- "avif",
3177
- "gif"
3178
- ],
3179
- maxBytes: 10 * MB
3180
- },
3181
- description: {
3182
- extensions: [
3183
- "jpg",
3184
- "jpeg",
3185
- "png",
3186
- "gif",
3187
- "webp",
3188
- "avif",
3189
- "svg",
3190
- "mp4",
3191
- "webm",
3192
- "mov"
3193
- ],
3194
- maxBytes: 50 * MB
3195
- }
3196
- };
3197
- /** 파일명의 소문자 확장자. 확장자가 없으면 `""` */
3198
- function uploadExtensionOf(filename) {
3199
- const dot = filename.lastIndexOf(".");
3200
- if (dot < 0 || dot === filename.length - 1) return "";
3201
- return filename.slice(dot + 1).toLowerCase();
3202
- }
3203
- /**
3204
- * 용도 제한 위반 여부. 통과하면 `null`.
3205
- * 서버가 던지는 오류와 콘솔이 먼저 띄우는 안내가 갈리지 않도록 한 함수로 판정한다.
3206
- */
3207
- function uploadRejection(purpose, filename, size) {
3208
- const limit = UPLOAD_PURPOSE_LIMITS[purpose];
3209
- if (!limit.extensions.includes(uploadExtensionOf(filename))) return {
3210
- status: 400,
3211
- code: "UNSUPPORTED_FILE_TYPE",
3212
- message: `지원하지 않는 파일 형식입니다. ${limit.extensions.join(", ")}만 올릴 수 있습니다`
3213
- };
3214
- if (size !== void 0 && size > limit.maxBytes) return {
3215
- status: 413,
3216
- code: "FILE_TOO_LARGE",
3217
- message: `파일이 너무 큽니다. ${Math.floor(limit.maxBytes / MB)}MB까지 올릴 수 있습니다`
3218
- };
3219
- return null;
3220
- }
3221
- /**
3222
- * 미디어 업로드 자리 발급 — `POST /v1/uploads`.
3223
- *
3224
- * 파일 바이트는 api를 거치지 않는다. 응답의 `uploadUrl`로 클라이언트가 직접 PUT하고,
3225
- * 끝나면 `url`이 공개 주소가 된다. 서버가 객체 키를 정하므로 요청은 파일명만 보낸다
3226
- * (확장자만 쓰이고 이름 자체는 저장되지 않는다).
3227
- */
3228
- const uploadRequestSchema = z.object({
3229
- filename: z.string().min(1).max(255),
3230
- purpose: uploadPurposeSchema.default("description").describe("업로드 용도. 허용 확장자와 크기 상한이 용도별로 다르다"),
3231
- size: z.number().int().positive().optional().describe("파일 바이트 크기. 주면 용도별 상한을 발급 전에 검사한다")
3232
- });
3233
- const uploadTicketSchema = z.object({
3234
- /** 업로드 완료 후 공개될 주소 */
3235
- url: z.url(),
3236
- /** 파일 바이트를 PUT할 presigned URL. 1시간 유효 */
3237
- uploadUrl: z.url()
3238
- });
3239
- //#endregion
3240
- //#region src/schemas/product.ts
3241
- const productStatusSchema = z.enum([
3242
- "SALE",
3243
- "SUSPENDED",
3244
- "OUT_OF_STOCK",
3245
- "CLOSED"
3246
- ]);
3247
- const productSummarySchema = z.object({
3248
- productId: z.string(),
3249
- name: z.string(),
3250
- status: productStatusSchema,
3251
- salePrice: z.number().int().describe("판매가. 원(KRW) 단위 정수"),
3252
- discountedPrice: z.number().int().nullable().describe("즉시 할인 적용가. 원 단위. 할인이 없으면 null"),
3253
- /** usable variant 재고 합계 (서버 계산) */
3254
- stockQuantity: z.number().int().describe("판매 가능한 옵션 재고의 합계. 서버가 계산한다"),
3255
- categoryId: z.string().nullable(),
3256
- thumbnailUrl: z.url().optional(),
3257
- /** optionGroups.length > 0 */
3258
- hasOptions: z.boolean(),
3259
- fulfillment: z.object({ type: fulfillmentTypeResponseSchema }).describe("상품 이행의 유형. 유형별 속성은 단건 조회(`GET /v1/products/{productId}`)의 `fulfillment`에 있다"),
3260
- updatedAt: z.string()
3261
- });
3262
- /** 옵션 축(그룹)이 가지는 값 하나 — 예: 축 "색상"의 값 "블랙" */
3263
- const optionValueSchema = z.object({
3264
- valueId: z.string(),
3265
- name: z.string(),
3266
- sortOrder: z.number().int()
3267
- });
3268
- /** 옵션 축(그룹) — 예: "색상", "사이즈" */
3269
- const optionGroupSchema = z.object({
3270
- groupId: z.string(),
3271
- name: z.string(),
3272
- sortOrder: z.number().int(),
3273
- values: z.array(optionValueSchema)
3274
- });
3275
- /**
3276
- * 판매 단위(SKU). variantId는 기존 optionId 값 공간을 승계하므로
3277
- * 카트·주문·리뷰·클레임의 optionId가 곧 variantId다.
3278
- */
3279
- const productVariantSchema = z.object({
3280
- variantId: z.string(),
3281
- /** 축 sortOrder 순서와 1:1 대응. 옵션 없는 상품은 [] */
3282
- valueIds: z.array(z.string()),
3283
- /** 값 이름을 " / "로 이은 파생 표시명. 옵션 없으면 "" */
3284
- name: z.string(),
3285
- additionalPrice: z.number().int(),
3286
- stockQuantity: z.number().int(),
3287
- sku: z.string().nullable(),
3288
- barcode: z.string().nullable(),
3289
- costPrice: z.number().int().nullable(),
3290
- weightGram: z.number().int().nullable(),
3291
- sellerCode: z.string().nullable(),
3292
- usable: z.boolean()
3293
- });
3294
- const discountSchema = z.object({
3295
- productId: z.string(),
3296
- discountType: z.enum(["RATE", "AMOUNT"]),
3297
- discountValue: z.number().int(),
3298
- discountedPrice: z.number().int(),
3299
- startAt: z.string().nullable(),
3300
- endAt: z.string().nullable()
3301
- });
3302
- const productSchema = productSummarySchema.extend({
3303
- description: z.string().optional(),
3304
- /**
3305
- * 상세설명 에디터(@avarlabs/editor)의 직렬화 상태. 재편집 때 이 값으로 문서를 복원한다.
3306
- * 표시는 `description`(HTML)이 맡는다 — 소비자는 이 필드를 몰라도 된다.
3307
- */
3308
- descriptionJson: z.string().nullable().optional(),
3309
- originalPrice: z.number().int().nullable().optional(),
3310
- images: z.array(z.url()).optional(),
3311
- attributes: z.array(z.object({
3312
- attributeId: z.string(),
3313
- value: z.string()
3314
- })).optional(),
3315
- optionGroups: z.array(optionGroupSchema),
3316
- /** 모든 상품이 최소 1개 variant를 갖는다 (옵션 없으면 default variant 1개) */
3317
- variants: z.array(productVariantSchema),
3318
- fulfillment: productFulfillmentResponseSchema,
3319
- fulfillmentTypeLocked: z.boolean().describe("판매 이력(주문상품)이 있어 상품 유형(`fulfillment.type`)을 바꿀 수 없는가. true면 다른 유형으로 수정하는 요청이 409 `FULFILLMENT_TYPE_LOCKED`다. 같은 유형 안의 속성은 바꿀 수 있다"),
3320
- refundPolicy: productRefundPolicyPatchSchema.nullable().describe("상품별 환불 정책 덮어쓰기(상품이 정한 항목만). null이면 스토어 기본 환불 정책을 쓴다. 반품·교환 배송비(`returnDeliveryFee`·`exchangeDeliveryFee`)는 배송 상품에만 있다"),
3321
- /**
3322
- * 설정된 즉시할인. `GET /products/{productId}/discount`의 응답과 같은 형태다(이슈 #51).
3323
- * 할인이 없으면 null이다. 이 필드를 싣기 전 서버의 응답에는 없으므로 optional이다.
3324
- */
3325
- discount: discountSchema.nullable().optional().describe("설정된 즉시할인. GET /v1/products/{productId}/discount 응답과 같은 형태다. 할인이 없으면 null"),
3326
- createdAt: z.string().optional()
3611
+ note: fulfillRequestSchema.shape.note
3612
+ });
3613
+ /** `POST /v1/order-items/{orderItemId}/cancel` 응답 — 셀러 직권취소 환불 결과 */
3614
+ const cancelBySellerResultSchema = z.object({
3615
+ orderItemId: z.string(),
3616
+ refundAmount: z.number().int().describe("실제 환불한 금액 — 수량 비율 상품 실결제 금액(쿠폰 배분을 뺀 금액) + 배송비 환불 − 조건부 무료배송 미달 차감 + 되돌린 쿠폰 차감(`couponChargeRefundAmount`)"),
3617
+ quantity: z.number().int().describe("취소한 수량"),
3618
+ canceledQuantity: z.number().int().describe("취소 후 누적 취소 수량"),
3619
+ deliveryFeeRefundAmount: z.number().int().describe("환불에 포함된 배송비. 배송 묶음(같은 상품)이 모두 취소됐고 환불 정책이 켜져 있을 때만 0보다 크다"),
3620
+ deliveryFeeChargeAmount: z.number().int().describe("조건부 무료배송 기준 미달로 환불액에서 뺀 배송비. 정책이 꺼져 있으면 항상 0이다"),
3621
+ couponChargeAmount: z.number().int().optional().default(0).describe("쿠폰 조건 미달 차감. 판매자 취소는 차감하지 않으므로 늘 0이다. 서버는 항상 싣는다"),
3622
+ couponChargeRefundAmount: z.number().int().optional().default(0).describe("환불액에 더한, 앞선 환불에서 뺀 쿠폰 차감을 되돌린 몫. 서버는 항상 싣는다"),
3623
+ refundedAt: z.string()
3624
+ });
3625
+ /** `PUT /v1/order-items/{orderItemId}/memo` 응답 — 빈 문자열로 저장하면 메모가 지워져 `null`이다 */
3626
+ const orderItemMemoResultSchema = z.object({
3627
+ orderItemId: z.string(),
3628
+ sellerMemo: z.string().nullable()
3629
+ });
3630
+ /** `POST /v1/order-items/{orderItemId}/purchase-decision/request` 응답 */
3631
+ const purchaseDecisionRequestResultSchema = z.object({
3632
+ orderItemId: z.string(),
3633
+ requestedAt: z.string()
3634
+ });
3635
+ /** `POST /v1/order-items/{orderItemId}/purchase-decision/extend` 응답 — 연장은 최대 2회 */
3636
+ const purchaseDecisionExtendResultSchema = z.object({
3637
+ orderItemId: z.string(),
3638
+ autoDecisionDate: z.string(),
3639
+ extendCount: z.number().int().max(2)
3327
3640
  });
3328
- /** 상품 하나에 등록할 수 있는 이미지 최대 장수 — 첫 장이 대표(썸네일)다 */
3329
- const MAX_PRODUCT_IMAGES = 10;
3330
3641
  /**
3331
- * 상품 이미지 주소의 와이어 형식 — URL이고 2048자 이하다.
3332
- *
3333
- * https·확장자 규칙은 여기서 강제하지 않고 `productImageRejection`이 판정한다. 서버가
3334
- * **새로 추가된 주소에만** 그 규칙을 적용해야 하기 때문이다(규칙이 생기기 전에 저장된
3335
- * 주소 하나로 상품 수정 전체가 막히면 셀러가 빠져나갈 길이 없다).
3336
- *
3337
- * 응답 스키마(`productSchema.images`)도 넓게 둔다. 과거에 저장된 주소를 파싱 오류로
3338
- * 만들면 상품 조회 자체가 막힌다.
3642
+ * `GET /v1/orders/{orderId}/address-change-requests` 응답 — 배송지 변경 요청 플로우는 아직 제공하지 않아
3643
+ * 현재는 항상 빈 배열이다
3339
3644
  */
3340
- const productImageUrlSchema = z.url().max(2048).describe("상품 이미지 주소. https만 허용하고, 확장자가 있으면 이미지 형식이어야 한다");
3645
+ const addressChangeRequestListSchema = z.array(z.record(z.string(), z.unknown())).describe("배송지 변경 요청 목록 (현재 항상 빈 배열)");
3646
+ //#endregion
3647
+ //#region src/schemas/saved-option-set.ts
3341
3648
  /**
3342
- * 상품에 **새로 붙이는** 이미지 주소의 규칙. 통과하면 `null`.
3649
+ * 저장 옵션 (이슈 #59) — 자주 쓰는 옵션 구성을 이름 붙여 두고 상품 등록에서 불러온다.
3343
3650
  *
3344
- * - https만 받는다. http는 https 스토어프론트에서 혼합 콘텐츠로 차단되고 `data:`·`javascript:`는
3345
- * 상세 화면에 박히는 주입 경로가 된다.
3346
- * - 호스트는 제한하지 않는다 — 셀러가 이미 쓰는 이미지 호스트를 그대로 붙일 수 있어야 한다.
3347
- * - 경로에 확장자가 있으면 상품 이미지 형식이어야 한다(svg·동영상 거부). **확장자가 없으면 통과시킨다**
3348
- * — 외부 이미지 CDN은 확장자 없는 키를 쓰는 경우가 흔하고, 주소만으로는 실제 형식을 알 수 없다.
3349
- */
3350
- function productImageRejection(url) {
3351
- if (url.length > 2048) return {
3352
- code: "INVALID_IMAGE_URL",
3353
- message: "이미지 주소가 너무 깁니다"
3354
- };
3355
- let parsed;
3356
- try {
3357
- parsed = new URL(url);
3358
- } catch {
3359
- return {
3360
- code: "INVALID_IMAGE_URL",
3361
- message: "이미지 주소 형식이 아닙니다"
3362
- };
3363
- }
3364
- if (parsed.protocol !== "https:") return {
3365
- code: "INVALID_IMAGE_URL",
3366
- message: "이미지 주소는 https로 시작해야 합니다"
3367
- };
3368
- const extension = uploadExtensionOf(parsed.pathname);
3369
- const allowed = UPLOAD_PURPOSE_LIMITS["product-image"].extensions;
3370
- if (extension && !allowed.includes(extension)) return {
3371
- code: "UNSUPPORTED_FILE_TYPE",
3372
- message: `상품 이미지는 ${allowed.join(", ")}만 쓸 수 있습니다`
3373
- };
3374
- return null;
3375
- }
3376
- /** 옵션 축(그룹) 최대 개수 — 스키마 상한의 단일 원천 */
3377
- const MAX_OPTION_GROUPS = 3;
3378
- /** 축 하나가 가질 수 있는 값의 최대 개수 */
3379
- const MAX_VALUES_PER_GROUP = 100;
3380
- /** 상품 하나가 가질 수 있는 variant(SKU) 최대 개수 */
3381
- const MAX_VARIANTS = 500;
3382
- /**
3383
- * 식별 코드 — 빈 문자열·공백만 있는 값은 거부한다.
3384
- * `""`를 통과시키면 서버 중복 사전 검사(falsy skip)와 DB partial unique
3385
- * (`WHERE ... is not null`)의 판정이 갈려 409 대신 500이 나간다.
3651
+ * **불러오기는 복사다.** 저장 옵션을 고치거나 지워도 이미 불러온 상품의 옵션은 그대로다. 재고·SKU·사용 여부는 상품마다
3652
+ * 달라 저장하지 않는다. 추가금은 조합별이 아니라 **값별**로 둔다 — 조합 옵션으로 불러오면 조합 추가금은 고른 값 추가금의 합이다.
3386
3653
  */
3387
- const codeSchema = z.string().trim().min(1).max(64);
3388
- const optionValueRequestSchema = z.object({
3389
- /** 전달 시 기존 값 행을 유지(UPDATE). 생략하면 신규 발급 */
3390
- valueId: z.string().optional(),
3391
- name: z.string().min(1).max(50)
3654
+ /** 스토어당 저장 옵션 최대 개수 */
3655
+ const MAX_SAVED_OPTION_SETS = 200;
3656
+ /** 저장 옵션 이름 최대 글자 수 */
3657
+ const MAX_SAVED_OPTION_SET_NAME = 50;
3658
+ /** 옵션명 방식 — `combination`(조합: 값의 조합이 판매 단위가 된다)·`addon`(추가 선택: 재고 없이 추가금만) */
3659
+ const optionGroupKindSchema = z.enum(["combination", "addon"]);
3660
+ const nameSchema = z.string().trim().min(1).max(50);
3661
+ const savedOptionValueSchema = z.object({
3662
+ name: z.string(),
3663
+ additionalPrice: z.number().int().describe("값별 추가금(원, 0 이상)")
3392
3664
  });
3393
- const optionGroupRequestSchema = z.object({
3394
- /** 전달 시 기존 축 행을 유지(UPDATE). 생략하면 신규 발급 */
3395
- groupId: z.string().optional(),
3396
- name: z.string().min(1).max(50),
3397
- values: z.array(optionValueRequestSchema).min(1).max(100)
3665
+ const savedOptionGroupSchema = z.object({
3666
+ name: z.string(),
3667
+ kind: z.string().describe("옵션명 방식. 현재 값은 `combination`·`addon`이고 값이 추가될 수 있어 문자열로 받는다"),
3668
+ required: z.boolean().describe("추가 선택을 구매자가 꼭 고르는가. 조합 옵션명은 항상 false다"),
3669
+ values: z.array(savedOptionValueSchema)
3670
+ });
3671
+ const savedCustomInputSchema = z.object({
3672
+ label: z.string(),
3673
+ placeholder: z.string().nullable(),
3674
+ maxLength: z.number().int(),
3675
+ required: z.boolean()
3676
+ });
3677
+ const savedOptionSetSchema = z.object({
3678
+ optionSetId: z.string(),
3679
+ name: z.string(),
3680
+ optionSort: optionSortSchema,
3681
+ groups: z.array(savedOptionGroupSchema).describe("옵션명. 배열 순서가 입력 순서다"),
3682
+ customInputs: z.array(savedCustomInputSchema),
3683
+ importedProductCount: z.number().int().describe("이 저장 옵션을 불러온 상품 수(중복 없이, 지운 상품 제외). 불러오기 기록으로 센다"),
3684
+ createdAt: z.string(),
3685
+ updatedAt: z.string()
3398
3686
  });
3399
- const productVariantRequestSchema = z.object({
3400
- /** 기존 variant 유지 시 전달. 생략하면 신규 생성 */
3401
- variantId: z.string().optional(),
3402
- /** optionGroups 순서대로 각 축에서 고른 값 이름. 서버가 이름 → valueId를 해석한다 */
3403
- valueNames: z.array(z.string()).default([]),
3404
- additionalPrice: z.number().int().default(0),
3405
- /**
3406
- * 생략 = 기존 재고 유지 (신규 variant는 0).
3407
- * 구조 교체(PUT)가 폼 로드 시점의 재고를 되쓰지 않게 하는 옵트인 필드다 —
3408
- * 재고만 바꿀 때는 PATCH /products/{id}/variants/{variantId}/stock을 쓴다.
3409
- */
3410
- stockQuantity: z.number().int().min(0).optional(),
3411
- sku: codeSchema.optional(),
3412
- barcode: codeSchema.optional(),
3413
- costPrice: z.number().int().min(0).optional(),
3414
- weightGram: z.number().int().min(0).optional(),
3415
- sellerCode: codeSchema.optional(),
3416
- usable: z.boolean().default(true)
3687
+ const savedOptionValueRequestSchema = z.object({
3688
+ name: nameSchema,
3689
+ additionalPrice: z.number().int().min(0).default(0)
3417
3690
  });
3418
- /** 축 × 값 → variant 전체 교체 요청 (PUT /products/{productId}/options) */
3419
- const optionModelRequestSchema = z.object({
3420
- optionGroups: z.array(optionGroupRequestSchema).max(3).default([]),
3421
- variants: z.array(productVariantRequestSchema).min(1).max(500)
3691
+ const savedOptionGroupRequestSchema = z.object({
3692
+ name: nameSchema,
3693
+ kind: optionGroupKindSchema,
3694
+ required: z.boolean().default(false).describe("추가 선택만 쓴다. 조합 옵션명에 true를 주면 400이다"),
3695
+ values: z.array(savedOptionValueRequestSchema).min(1).max(100)
3422
3696
  });
3423
- /** GET /products/{productId}/options 응답 */
3424
- const optionModelSchema = z.object({
3425
- optionGroups: z.array(optionGroupSchema),
3426
- variants: z.array(productVariantSchema)
3697
+ const savedCustomInputRequestSchema = z.object({
3698
+ label: z.string().trim().min(1).max(30),
3699
+ placeholder: z.string().trim().max(100).nullish(),
3700
+ maxLength: z.number().int().min(1).max(200),
3701
+ required: z.boolean().default(false)
3427
3702
  });
3428
3703
  /**
3429
- * 재고 변경 택일 위반 문구 — SDK `.refine`과 서버 DomainException이 공유하는 단일 원천.
3430
- * 경로에 따라 셀러가 다른 문구를 받지 않게 한다.
3431
- */
3432
- const VARIANT_STOCK_EXCLUSIVE_MESSAGE = "stockQuantity 또는 adjustment 중 하나만 전달해야 합니다";
3433
- /**
3434
- * variant 재고 변경 요청 — 절대값(`stockQuantity`) 또는 증감(`adjustment`) 택일.
3435
- * 서버도 같은 규칙을 400 VALIDATION_FAILED로 검증한다(이중 방어).
3704
+ * 저장 옵션 만들기·수정 본문. 조합 옵션명은 최대 `MAX_OPTION_GROUPS`개, 추가 선택은 최대 `MAX_ADDON_GROUPS`개이고
3705
+ * 옵션명과 직접 입력 항목이 모두 비면 400이다. 수정(PUT)은 통째로 바꾼다.
3436
3706
  */
3437
- const variantStockRequestSchema = z.object({
3438
- stockQuantity: z.number().int().min(0).optional(),
3439
- adjustment: z.number().int().optional()
3440
- }).refine((v) => v.stockQuantity !== void 0 !== (v.adjustment !== void 0), { message: VARIANT_STOCK_EXCLUSIVE_MESSAGE });
3441
- /** PATCH /products/{productId}/variants/{variantId}/stock 응답 */
3442
- const variantStockSchema = z.object({
3443
- variantId: z.string(),
3444
- stockQuantity: z.number().int()
3707
+ const savedOptionSetRequestSchema = z.object({
3708
+ name: z.string().trim().min(1).max(50),
3709
+ optionSort: optionSortSchema.default("REGISTERED"),
3710
+ groups: z.array(savedOptionGroupRequestSchema).max(8).default([]),
3711
+ customInputs: z.array(savedCustomInputRequestSchema).max(5).default([])
3445
3712
  });
3446
3713
  /**
3447
- * 상품 쓰기 요청의 공통 필드 — 등록(POST)과 전체 수정(PUT)이 같다. 이행(`fulfillment`)만 두 요청의 스키마가 다르다.
3714
+ * 상품에서 저장 (`POST /v1/saved-option-sets/from-product`) — 상품의 현재 옵션을 저장 옵션으로 만든다.
3448
3715
  *
3449
- * 최상위가 `strictObject`다. 모르는 필드는 400이다 — 이슈 #57 이전의 평면 필드(`deliveryFee`·`fulfillmentType`·`returnFee` 등)를
3450
- * 보낸 요청이 조용히 무시되지 않게 한다. 옛 필드는 `fulfillment.{유형}.…`과 `refundPolicy`로 옮겼다.
3716
+ * 조합 추가금이 값별 추가금의 합으로 되돌려지면 값별로 저장하고, 조합마다 제각각이면 추가금을 빼고
3717
+ * 응답 `additionalPriceDropped: true`로 알린다. `replaceOptionSetId`를 주면 그 저장 옵션을 덮어쓴다(이름 중복의 덮어쓰기).
3451
3718
  */
3452
- const productWriteShape = {
3453
- name: z.string().min(1).max(100),
3454
- categoryId: z.string().min(1, "카테고리를 선택해주십시오"),
3455
- salePrice: z.number().int().min(10).multipleOf(10, "판매가는 10원 단위여야 합니다 (INVALID_PRICE_UNIT)").describe("판매가. 원(KRW) 단위 정수, 최소 10원, 10원 단위"),
3456
- originalPrice: z.number().int().optional().describe("정가(할인 전 표시 가격). 원 단위"),
3457
- description: z.string().optional(),
3458
- /**
3459
- * 상세설명 에디터(@avarlabs/editor)의 직렬화 상태. 재편집 때 이 값으로 문서를 복원한다.
3460
- * 표시는 `description`(HTML)이 맡는다 — 소비자는 이 필드를 몰라도 된다.
3461
- */
3462
- descriptionJson: z.string().nullable().optional(),
3463
- images: z.array(productImageUrlSchema).min(1).max(10).optional().describe("상품 이미지 주소 목록. 첫 장이 대표 이미지(썸네일)다"),
3464
- attributes: z.array(z.object({
3465
- attributeId: z.string(),
3466
- value: z.string()
3467
- })).optional(),
3468
- refundPolicy: productRefundPolicyPatchSchema.nullish().describe("상품별 환불 정책 — 준 항목만 스토어 기본을 덮어쓴다. `null`이나 생략은 스토어 기본을 그대로 쓴다는 뜻이다. 반품·교환 배송비(`returnDeliveryFee`·`exchangeDeliveryFee`)는 배송 상품에만 줄 수 있다(다른 유형은 400)"),
3469
- ...optionModelRequestSchema.shape
3470
- };
3471
- const productCreateRequestSchema = z.strictObject({
3472
- ...productWriteShape,
3473
- fulfillment: productFulfillmentCreateSchema.optional().describe("상품 이행. `type`이 유형이고 같은 이름의 키가 유형별 속성이다. 생략하면 배송 상품(기본값)이다. 배송 상품이 아닌 유형은 판매가 열린 유형만 등록할 수 있다(409 `FULFILLMENT_TYPE_NOT_AVAILABLE`)")
3474
- });
3475
- const fulfillmentUpdateDescription = "상품 이행. 생략하면 바꾸지 않는다. 주면 유형 안 필드를 모두 담아 통째로 바꾼다(현재 값과 병합하지 않는다). 판매 이력이 있으면 유형을 바꿀 수 없다(409 `FULFILLMENT_TYPE_LOCKED`)";
3476
- /** 전체 수정(PUT) — 공통 필드는 등록과 같고, 이행은 생략하면 현재 값 유지, 주면 필드가 모두 필수다 */
3477
- const productReplaceRequestSchema = z.strictObject({
3478
- ...productWriteShape,
3479
- fulfillment: productFulfillmentUpdateSchema.optional().describe(fulfillmentUpdateDescription)
3480
- });
3481
- const productPatchRequestSchema = z.strictObject({
3482
- name: z.string().max(100).optional(),
3483
- salePrice: z.number().int().multipleOf(10).optional(),
3484
- status: productStatusSchema.optional(),
3485
- fulfillment: productFulfillmentUpdateSchema.optional().describe(fulfillmentUpdateDescription),
3486
- refundPolicy: productRefundPolicyPatchSchema.nullable().optional().describe("상품별 환불 정책. 주면 통째로 바꾼다(준 항목만 스토어 기본을 덮어쓴다). `null`은 덮어쓰기를 지워 스토어 기본을 따른다. 생략하면 바꾸지 않는다")
3487
- }).refine((value) => Object.values(value).some((v) => v !== void 0), { message: "최소 1개 필드가 필요합니다" });
3488
- const productSortSchema = z.enum([
3489
- "createdAt.desc",
3490
- "createdAt.asc",
3491
- "salePrice.asc",
3492
- "salePrice.desc",
3493
- "updatedAt.desc"
3719
+ const savedOptionSetFromProductRequestSchema = z.object({
3720
+ productId: z.string(),
3721
+ name: z.string().trim().min(1).max(50),
3722
+ includeOptions: z.boolean().default(true).describe("옵션명(조합·추가 선택)을 저장하는가"),
3723
+ includeCustomInputs: z.boolean().default(true).describe("직접 입력 항목을 저장하는가"),
3724
+ replaceOptionSetId: z.string().optional().describe("덮어쓸 저장 옵션 id. 생략하면 새로 만든다")
3725
+ });
3726
+ const savedOptionSetFromProductResultSchema = z.object({
3727
+ optionSet: savedOptionSetSchema,
3728
+ additionalPriceDropped: z.boolean().describe("조합 추가금을 값별 추가금으로 되돌릴 수 없어 추가금을 빼고 저장했는가")
3729
+ });
3730
+ /** 구성 필터 — 조합 옵션명·추가 선택 옵션명·직접 입력 항목을 가진 저장 옵션 */
3731
+ const savedOptionSetCompositionSchema = z.enum([
3732
+ "combination",
3733
+ "addon",
3734
+ "customInput"
3494
3735
  ]);
3495
- /** `GET /v1/products` 쿼리 — OpenAPI 문서용 형태(값은 쿼리 문자열) */
3496
- const productListQuerySchema = z.object({
3497
- status: productStatusSchema.optional().describe("판매 상태 필터"),
3498
- categoryId: z.string().optional().describe("카테고리 필터 — 하위 카테고리의 상품도 포함"),
3499
- keyword: z.string().optional().describe("상품명 검색 (포함)"),
3500
- sellerCode: z.string().optional().describe("판매자 상품코드 — 이 코드가 붙은 variant를 가진 상품"),
3501
- sort: productSortSchema.optional().describe("정렬 (기본 createdAt.desc)"),
3502
- ...pageQueryShape()
3503
- });
3504
- const discountRequestSchema = z.object({
3505
- discountType: z.enum(["RATE", "AMOUNT"]),
3506
- discountValue: z.number().int().min(1),
3507
- startAt: z.string().optional(),
3508
- endAt: z.string().optional()
3736
+ const savedOptionSetSortSchema = z.enum([
3737
+ "updatedAt.desc",
3738
+ "name.asc",
3739
+ "importedProductCount.desc"
3740
+ ]);
3741
+ /** `GET /v1/saved-option-sets` 쿼리 — OpenAPI 문서용 형태(값은 쿼리 문자열) */
3742
+ const savedOptionSetListQuerySchema = z.object({
3743
+ keyword: z.string().optional().describe("이름 검색 (포함, 대소문자 무시)"),
3744
+ composition: savedOptionSetCompositionSchema.optional().describe("구성 필터"),
3745
+ sort: savedOptionSetSortSchema.optional().describe("정렬 (기본 updatedAt.desc)"),
3746
+ ...pageQueryShape({
3747
+ defaultSize: 20,
3748
+ maxSize: 200
3749
+ })
3509
3750
  });
3510
3751
  //#endregion
3511
3752
  //#region src/schemas/shipping-policy.ts
@@ -4188,6 +4429,7 @@ const sellerAddressRequestSchema = z.object({
4188
4429
  * - 409 `STOREFRONT_SITE_CREATE_LIMIT` — 사이트는 스토어당 30일에 3번까지 만든다
4189
4430
  * - 409 `STOREFRONT_SITE_CHANGED` — 읽은 상태(`expected*`)와 현재 상태가 다르다. 다시 읽고 시도한다
4190
4431
  * - 409 `STOREFRONT_SITE_NOT_ACTIVE` — 정지(`SUSPENDED`)된 사이트는 바꾸거나 끌 수 없다
4432
+ * - 409 `STORE_SUSPENDED` — 정지된 스토어에는 사이트를 만들 수 없다
4191
4433
  * - 409 `STOREFRONT_VERSION_NOT_READY` — 빌드가 끝나지 않았거나 실패한 버전은 발행할 수 없다
4192
4434
  * - 409 `STOREFRONT_NO_ROLLBACK_TARGET` — 되돌릴 이전 발행 버전이 없다
4193
4435
  * - 400 `STOREFRONT_SLUG_CONFIRM_MISMATCH` — 호스팅 끄기 확인 입력이 현재 슬러그와 다르다
@@ -4209,6 +4451,16 @@ const sellerAddressRequestSchema = z.object({
4209
4451
  * - 409 `STOREFRONT_BUILD_IN_PROGRESS` — 이 사이트에 끝나지 않은 빌드가 있다(사이트당 하나)
4210
4452
  * - 404 `STOREFRONT_BUILD_NOT_FOUND`
4211
4453
  * - 502 `STOREFRONT_SANDBOX_UNAVAILABLE` — 편집 Sandbox 호출이 실패했다. 잠시 뒤 다시 한다
4454
+ *
4455
+ * 소스 내려받기·올리기(#60, `/v1/storefront/site/source/**`, `sayren` CLI):
4456
+ * - 409 `SOURCE_CHANGED` — 작업 트리가 내려받은 뒤(`expectedBaseRef`) 바뀌었다(콘솔·AI 편집). `error.details.currentRef`
4457
+ * - 409 `EDIT_SESSION_OPEN` — 편집 환경이 열려 있다. `endEditSession: true`로 끄고 진행할 수 있다
4458
+ * - 409 `SOURCE_BLOBS_MISSING` — 올리지 않은 파일 내용이 있다(`error.details.hashes`). `POST source/blobs`로 올리고 다시 한다
4459
+ * - 409 `DEPENDENCY_REJECTED` — 쓸 수 없는 npm 의존성이다(`error.details.rejected: [{ name, version, reason, detail }]`)
4460
+ * - 413 `SOURCE_TOO_MANY_NEW_FILES` — 한 번에 새 내용이 800개를 넘는다(`error.details.max`). 나눠서 바꾼다
4461
+ * - 400 `DEPENDENCY_FILES_MISMATCH` — `packageJson`·`lockfile` 내용이 목록의 해시와 다르거나 빠졌다
4462
+ * - 400 `INVALID_SOURCE_REF` · `BLOB_HASH_MISMATCH` · `INVALID_FILE_PATH`(`.env*`·작업 트리 밖 경로)
4463
+ * - 404 `STOREFRONT_SOURCE_NOT_FOUND` — 그 스냅샷이 없다
4212
4464
  */
4213
4465
  /**
4214
4466
  * 슬러그 형식 — 스토어 코드와 같은 DNS 라벨 규칙(소문자·숫자·하이픈 3~30자, 하이픈으로 시작·끝 불가)에
@@ -4231,7 +4483,8 @@ const RESERVED_STOREFRONT_SLUGS = [
4231
4483
  "suspended",
4232
4484
  "dispatch",
4233
4485
  "control",
4234
- "outbound"
4486
+ "outbound",
4487
+ "sites"
4235
4488
  ];
4236
4489
  /**
4237
4490
  * 편집 미리보기 호스트 접미사 — 편집 미리보기 주소는 `{사이트 id 정규화}-preview.{호스팅 도메인}`이다(M2b). 사이트 주소와 같은
@@ -4291,6 +4544,7 @@ const storefrontVersionSchema = z.object({
4291
4544
  source: z.enum(["TEMPLATE", "EDIT"]).describe("`TEMPLATE` 템플릿 산출물 그대로 · `EDIT` 편집 작업 트리를 빌드함"),
4292
4545
  buildId: z.string().nullable().describe("편집 버전의 빌드 (`GET /v1/storefront/site/builds/{buildId}`). 템플릿 버전은 null"),
4293
4546
  createdByType: z.string().describe("만든 행위자 유형 (`USER`·`AGENT`·`APP`·`TOKEN`·`SYSTEM`). 늘어날 수 있다"),
4547
+ createdVia: z.string().nullable().optional().describe("만든 경로 (`CONSOLE` 콘솔 · `AI` AI 에이전트(MCP) · `CLI` `sayren` CLI · `API` 관리 API · `SYSTEM` 자동). 늘어날 수 있다"),
4294
4548
  createdAt: z.string().describe("만든 시각 (ISO 8601)")
4295
4549
  });
4296
4550
  const storefrontSiteStatusSchema = z.enum([
@@ -4303,7 +4557,7 @@ const storefrontSourceModeSchema = z.enum(["MANAGED", "CUSTOM"]).describe("`MANA
4303
4557
  const storefrontDomainSchema = z.object({
4304
4558
  hostname: z.string().describe("호스트 이름 (예: `mystore.sayren.co`)"),
4305
4559
  kind: z.enum(["PLATFORM", "CUSTOM"]).describe("`PLATFORM` 슬러그 주소 · `CUSTOM` 내 도메인"),
4306
- status: z.enum(["ACTIVE", "HELD"]).describe("`ACTIVE` 사용 중 · `HELD` 슬러그를 바꿔 보류 중(새 주소로 301)"),
4560
+ status: z.enum(["ACTIVE", "HELD"]).describe("`ACTIVE` 사용 중 · `HELD` 슬러그를 바꿔 보류 중(새 주소로 301). 연결 중인 내 도메인은 싣지 않는다(`GET /v1/storefront/site/domains`)"),
4307
4561
  isPrimary: z.boolean().describe("대표 주소인가 (`Store.storefrontUrl`)"),
4308
4562
  holdUntil: z.string().nullable().describe("보류가 끝나는 시각 (ISO 8601). 보류가 아니면 null")
4309
4563
  });
@@ -4325,6 +4579,14 @@ const storefrontProvisioningSchema = z.object({
4325
4579
  * 인증서가 덮으므로 따로 준비할 것이 없다. 이 주소는 편집 미리보기 링크(콘솔 전용)로 받은 쿠키가 있어야 열린다.
4326
4580
  */
4327
4581
  const storefrontEditPreviewSchema = z.object({ hostname: z.string().nullable().describe("편집 미리보기 호스트 (예: `sfs-ab12cd34-preview.sayren.co`). 편집을 쓸 수 없으면 null") });
4582
+ /**
4583
+ * 사이트 정지 — 운영 정책에 따라 플랫폼 운영자가 사이트를 정지한다. 정지 중에는 방문자에게 정지 안내가 보이고 발행·되돌리기·템플릿 변경·
4584
+ * 공개 전환·슬러그 변경·끄기·편집이 409 `STOREFRONT_SITE_NOT_ACTIVE`다. 셀러가 풀 수 없다(고객센터로 문의한다).
4585
+ */
4586
+ const storefrontSuspensionSchema = z.object({
4587
+ reason: z.string().nullable().describe("정지 사유. 운영자가 적은 안내이고 없으면 null"),
4588
+ suspendedAt: z.string().nullable().describe("정지한 시각 (ISO 8601)")
4589
+ });
4328
4590
  const storefrontSiteSchema = z.object({
4329
4591
  siteId: z.string(),
4330
4592
  slug: z.string().describe("주소 슬러그"),
@@ -4339,6 +4601,7 @@ const storefrontSiteSchema = z.object({
4339
4601
  provisioning: storefrontProvisioningSchema,
4340
4602
  editPreview: storefrontEditPreviewSchema,
4341
4603
  slugChangesRemaining: z.number().int().min(0).describe(`최근 30일 기준 남은 슬러그 변경 횟수`),
4604
+ suspension: storefrontSuspensionSchema.nullable().optional().describe("정지 정보. `status`가 `SUSPENDED`일 때만 있고 그 밖은 null"),
4342
4605
  createdAt: z.string(),
4343
4606
  updatedAt: z.string()
4344
4607
  });
@@ -4396,6 +4659,186 @@ const storefrontPreviewLinkSchema = z.object({
4396
4659
  url: z.string().describe("미리보기 링크 — 5분 안에 연다"),
4397
4660
  expiresAt: z.string().describe("링크 만료 시각 (ISO 8601)")
4398
4661
  });
4662
+ /**
4663
+ * 연결할 수 없는 도메인 — 플랫폼이 쓰는 도메인과 그 하위(기본 주소는 슬러그로 바꾼다). 호스팅 도메인은 서버 설정이라
4664
+ * `storefrontCustomHostnameProblem`의 인자로 따로 받는다.
4665
+ */
4666
+ const STOREFRONT_PLATFORM_DOMAINS = [
4667
+ "sayren.co",
4668
+ "sayren.app",
4669
+ "sayren.shop",
4670
+ "avarlabs.com"
4671
+ ];
4672
+ /** 공개 인터넷에서 쓸 수 없는 최상위 도메인(RFC 2606·6761·6762 등) */
4673
+ const NON_PUBLIC_TLDS = [
4674
+ "example",
4675
+ "localhost",
4676
+ "local",
4677
+ "test",
4678
+ "invalid",
4679
+ "onion",
4680
+ "internal",
4681
+ "arpa",
4682
+ "home",
4683
+ "lan",
4684
+ "corp",
4685
+ "intranet",
4686
+ "alt"
4687
+ ];
4688
+ /**
4689
+ * 공용 접미사 — 누구나 그 아래 이름을 받는 도메인이라 접미사 자체는 연결할 수 없다. Public Suffix List 전체가 아니라 자주 틀리는
4690
+ * 것만 둔다(여기 없는 접미사도 TXT 소유 확인을 통과할 수 없어 연결되지 않는다 — 이 목록은 추가 단계에서 빨리 알려 주는 안내다).
4691
+ */
4692
+ const STOREFRONT_PUBLIC_SUFFIXES = [
4693
+ "co.kr",
4694
+ "or.kr",
4695
+ "ne.kr",
4696
+ "go.kr",
4697
+ "ac.kr",
4698
+ "re.kr",
4699
+ "pe.kr",
4700
+ "mil.kr",
4701
+ "hs.kr",
4702
+ "ms.kr",
4703
+ "es.kr",
4704
+ "sc.kr",
4705
+ "kg.kr",
4706
+ "seoul.kr",
4707
+ "busan.kr",
4708
+ "incheon.kr",
4709
+ "daegu.kr",
4710
+ "gwangju.kr",
4711
+ "daejeon.kr",
4712
+ "ulsan.kr",
4713
+ "gyeonggi.kr",
4714
+ "gangwon.kr",
4715
+ "chungbuk.kr",
4716
+ "chungnam.kr",
4717
+ "jeonbuk.kr",
4718
+ "jeonnam.kr",
4719
+ "gyeongbuk.kr",
4720
+ "gyeongnam.kr",
4721
+ "jeju.kr",
4722
+ "co.jp",
4723
+ "ne.jp",
4724
+ "or.jp",
4725
+ "com.cn",
4726
+ "net.cn",
4727
+ "com.tw",
4728
+ "com.hk",
4729
+ "co.uk",
4730
+ "org.uk",
4731
+ "com.au",
4732
+ "net.au",
4733
+ "com.sg",
4734
+ "co.nz",
4735
+ "com.br",
4736
+ "github.io",
4737
+ "gitlab.io",
4738
+ "pages.dev",
4739
+ "workers.dev",
4740
+ "vercel.app",
4741
+ "netlify.app",
4742
+ "herokuapp.com",
4743
+ "web.app",
4744
+ "firebaseapp.com",
4745
+ "blogspot.com",
4746
+ "cloudfront.net",
4747
+ "azurewebsites.net",
4748
+ "myshopify.com",
4749
+ "wixsite.com",
4750
+ "cafe24.com",
4751
+ "imweb.me"
4752
+ ];
4753
+ /** 스토어당 연결할 수 있는 도메인 수 기본값 — 요금제(#44) 전의 상한이다. 실제 값은 `GET …/domains`의 `limit` */
4754
+ const STOREFRONT_CUSTOM_DOMAIN_LIMIT_DEFAULT = 2;
4755
+ /** DNS를 넣을 때까지 기다리는 기간(일) — 지나면 `FAILED`(`DNS_NOT_CONFIGURED`) */
4756
+ const STOREFRONT_DOMAIN_DNS_WAIT_DAYS = 7;
4757
+ /** 연결된 도메인의 DNS가 이 서비스를 가리키지 않아도 연결을 유지하는 기간(일) — 지나면 서버가 해제한다 */
4758
+ const STOREFRONT_DOMAIN_DNS_GRACE_DAYS = 7;
4759
+ /** 소유 확인 TXT 레코드의 이름 앞부분 — `_sayren-verify.{도메인}` */
4760
+ const STOREFRONT_DOMAIN_TXT_LABEL = "_sayren-verify";
4761
+ const LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
4762
+ const PUNYCODE_TLD = /^xn--[a-z0-9-]{1,59}$/;
4763
+ const ALPHA_TLD = /^[a-z]{2,63}$/;
4764
+ /**
4765
+ * 도메인 정규화 — 앞뒤 공백·끝 점을 떼고 소문자로 바꾸며 국제화 도메인은 punycode(`xn--`)로 바꾼다. 스킴·경로·포트가 붙은 입력
4766
+ * (`https://www.a.com/`)은 호스트만 남기지 않고 그대로 두어 형식 검사에서 떨어진다(서버는 사용자가 적은 도메인만 받는다).
4767
+ * 변환할 수 없으면 입력을 소문자로만 바꿔 돌려준다(형식 검사가 거부한다).
4768
+ */
4769
+ function normalizeStorefrontHostname(input) {
4770
+ const trimmed = input.trim().replace(/\.+$/, "");
4771
+ if (!trimmed || /[\s/:?#@\\[\]%]/.test(trimmed)) return trimmed.toLowerCase();
4772
+ if (/^[\x21-\x7e]+$/.test(trimmed)) return trimmed.toLowerCase();
4773
+ try {
4774
+ return new URL(`http://${trimmed}`).hostname.toLowerCase();
4775
+ } catch {
4776
+ return trimmed.toLowerCase();
4777
+ }
4778
+ }
4779
+ /**
4780
+ * 연결할 수 있는 도메인인가(정규화한 값 기준). 문제가 없으면 null이다. 규칙: 253자 이하, 라벨 2개 이상, 라벨은 소문자·숫자·하이픈
4781
+ * 63자 이하(하이픈으로 시작·끝 불가), 최상위 도메인은 영문자이거나 punycode, IP 주소·와일드카드·공개 인터넷 밖 최상위 도메인·
4782
+ * 플랫폼 도메인과 그 하위·공용 접미사 자체는 안 된다. 제어·디스패치 Worker가 형식 규칙의 복제본을 들고 있다(api
4783
+ * `test/storefront-hosting-parity.spec.ts`가 대조한다).
4784
+ */
4785
+ function storefrontCustomHostnameProblem(hostname, hostingDomain = "sayren.co") {
4786
+ if (hostname.startsWith("*.")) return "WILDCARD";
4787
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname) || /^\[?[0-9a-f:.]*:[0-9a-f:.]*\]?$/.test(hostname)) return "IP_ADDRESS";
4788
+ if (hostname.length === 0 || hostname.length > 253) return "FORMAT";
4789
+ const labels = hostname.split(".");
4790
+ if (labels.length < 2) return "SINGLE_LABEL";
4791
+ if (!labels.every((label) => LABEL.test(label))) return "FORMAT";
4792
+ const tld = labels[labels.length - 1] ?? "";
4793
+ if (!ALPHA_TLD.test(tld) && !PUNYCODE_TLD.test(tld)) return "FORMAT";
4794
+ if (NON_PUBLIC_TLDS.includes(tld)) return "NON_PUBLIC_TLD";
4795
+ const platform = /* @__PURE__ */ new Set([...STOREFRONT_PLATFORM_DOMAINS, hostingDomain.toLowerCase()]);
4796
+ for (const domain of platform) if (hostname === domain || hostname.endsWith(`.${domain}`)) return "PLATFORM_DOMAIN";
4797
+ if (STOREFRONT_PUBLIC_SUFFIXES.includes(hostname)) return "PUBLIC_SUFFIX";
4798
+ return null;
4799
+ }
4800
+ /** 소유 확인 TXT 레코드의 전체 이름 */
4801
+ function storefrontDomainTxtNameOf(hostname) {
4802
+ return `${STOREFRONT_DOMAIN_TXT_LABEL}.${hostname}`;
4803
+ }
4804
+ const storefrontCustomDomainStatusSchema = z.enum([
4805
+ "PENDING_DNS",
4806
+ "PENDING_VALIDATION",
4807
+ "ACTIVE",
4808
+ "FAILED"
4809
+ ]).describe("`PENDING_DNS` DNS 설정 대기(소유 확인 전) · `PENDING_VALIDATION` 소유 확인됨, 인증서 발급 중 · `ACTIVE` 연결됨 · `FAILED` 연결 실패(`failureCode`)");
4810
+ const storefrontDnsRecordSchema = z.object({
4811
+ type: z.enum(["CNAME", "TXT"]).describe("레코드 유형"),
4812
+ name: z.string().describe("레코드 이름 (전체 이름, 예: `_sayren-verify.shop.brand.com`)"),
4813
+ value: z.string().describe("레코드 값"),
4814
+ purpose: z.enum(["ROUTING", "OWNERSHIP"]).describe("`ROUTING` 연결 · `OWNERSHIP` 소유 확인")
4815
+ });
4816
+ const storefrontSiteDomainSchema = z.object({
4817
+ domainId: z.string(),
4818
+ hostname: z.string().describe("호스트 이름 (punycode, 소문자)"),
4819
+ kind: z.enum(["PLATFORM", "CUSTOM"]).describe("`PLATFORM` 기본 주소(슬러그) · `CUSTOM` 내 도메인"),
4820
+ status: storefrontCustomDomainStatusSchema.describe("기본 주소는 늘 `ACTIVE`다. 내 도메인은 연결 단계"),
4821
+ isPrimary: z.boolean().describe("대표 주소인가 (`Store.storefrontUrl`). 대표가 아닌 주소는 대표로 301한다"),
4822
+ dnsRecords: z.array(storefrontDnsRecordSchema).describe("DNS에 넣을 레코드. 기본 주소는 빈 배열"),
4823
+ certificateStatus: z.enum([
4824
+ "PENDING",
4825
+ "ACTIVE",
4826
+ "FAILED"
4827
+ ]).nullable().describe("인증서 상태. 소유 확인 전·기본 주소는 null"),
4828
+ failureCode: z.string().nullable().describe("실패·경고 코드. `DNS_NOT_CONFIGURED` 기한 안에 TXT를 찾지 못함 · `VALIDATION_TIMEOUT` 인증서 발급 기한 지남 · `CERTIFICATE_FAILED` 인증서 발급 실패 · `DNS_NOT_POINTING` 연결된 뒤 DNS가 이 서비스를 가리키지 않음(기한이 지나면 해제) · `DOMAIN_TAKEN` 다른 스토어가 먼저 소유를 확인함 · `HOSTNAME_BLOCKED` 연결이 막힘 · `HOSTNAME_CONFLICT` 연결 준비 중 충돌(운영 확인). 늘어날 수 있다"),
4829
+ verifiedAt: z.string().nullable().describe("소유를 확인한 시각 (ISO 8601)"),
4830
+ activatedAt: z.string().nullable().describe("연결된 시각 (ISO 8601)"),
4831
+ checkedAt: z.string().nullable().describe("마지막으로 확인한 시각 (ISO 8601)"),
4832
+ createdAt: z.string()
4833
+ });
4834
+ /** `GET /v1/storefront/site/domains` — 사이트의 주소 목록(기본 주소 + 내 도메인)과 연결 설정 */
4835
+ const storefrontSiteDomainsSchema = z.object({
4836
+ enabled: z.boolean().describe("내 도메인을 추가할 수 있는가 (플랫폼 스위치)"),
4837
+ limit: z.number().int().min(0).describe("스토어당 내 도메인 수 상한 (해제한 도메인은 세지 않는다)"),
4838
+ target: z.string().describe("CNAME 대상 (예: `sites.sayren.co`)"),
4839
+ domains: z.array(storefrontSiteDomainSchema).describe("대표 주소 먼저, 그다음 추가한 순서")
4840
+ });
4841
+ const addStorefrontDomainRequestSchema = z.object({ hostname: z.string().min(1).max(300).describe("연결할 도메인 (예: `shop.brand.com`). 서버가 소문자·punycode로 정규화한다") });
4399
4842
  /** 플랫폼 전체 동시 편집 환경 상한 */
4400
4843
  const STOREFRONT_EDIT_CAPACITY = 20;
4401
4844
  /** 활동이 없으면 편집 환경이 잠드는 시간(분). 작업 트리는 보존된다 */
@@ -4672,9 +5115,11 @@ const STOREFRONT_BUILD_TERMINAL_STATUSES = [
4672
5115
  ];
4673
5116
  /**
4674
5117
  * 빌드 단계 — 이 순서로 돈다. 첫 실패에서 멈추지만 규칙 검증(`VERIFY`)은 실패해도 타입 검사까지 이어서 해 한 번에 알려 준다.
4675
- * `UPLOAD`는 사이트 버전 Worker 업로드다(성공하면 발행할 수 있다). 값은 늘어날 수 있다.
5118
+ * `UPLOAD`는 사이트 버전 Worker 업로드다(성공하면 발행할 수 있다). `DEPENDENCIES`는 빌드 시작 전 의존성 검사(거절 목록·보안 권고)다.
5119
+ * 값은 늘어날 수 있다.
4676
5120
  */
4677
5121
  const STOREFRONT_BUILD_STAGES = [
5122
+ "DEPENDENCIES",
4678
5123
  "VERIFY",
4679
5124
  "RESTORE",
4680
5125
  "INSTALL",
@@ -4684,13 +5129,37 @@ const STOREFRONT_BUILD_STAGES = [
4684
5129
  "PACK",
4685
5130
  "UPLOAD"
4686
5131
  ];
5132
+ /**
5133
+ * 의존성 검사 결과 한 줄(#61) — 거절(`DEPENDENCY_REJECTED`의 `details.rejected`)·경고(빌드 `dependencies`, 소스 교체 `dependencyWarnings`)가
5134
+ * 같은 모양이다. 대상 패키지와 사유. 셀러가 고칠 수 있게 버전·권고 id·거절 사유를 싣는다.
5135
+ * `reason`: `DENYLISTED`(거절 목록) · `ADVISORY`(보안 권고) · `MALWARE`(악성 코드 권고, 패키지 전체) · `INSTALL_SCRIPT`(루트
5136
+ * package.json의 설치 훅은 거절, 의존성의 설치 스크립트는 경고) · `UNSUPPORTED_SOURCE`(레지스트리 밖 설치 경로) · `BUNDLED_DEPENDENCIES`(묶여
5137
+ * 오는 의존성, 경고) · `INVALID_MANIFEST`(package.json·잠금 파일 형식), 늘어날 수 있다.
5138
+ */
5139
+ const storefrontDependencyFindingSchema = z.object({
5140
+ name: z.string().describe("패키지 이름. 파일 전체의 문제면 `package.json`·`pnpm-lock.yaml`"),
5141
+ version: z.string().nullable().describe("해당 버전. 버전과 무관하면 null"),
5142
+ reason: z.string().describe("사유 코드"),
5143
+ detail: z.string().describe("셀러용 설명"),
5144
+ advisory: z.object({
5145
+ id: z.string(),
5146
+ url: z.string().nullable(),
5147
+ severity: z.string(),
5148
+ title: z.string()
5149
+ }).nullable().optional().describe("보안 권고(사유가 `ADVISORY`·`MALWARE`일 때)")
5150
+ });
5151
+ const storefrontBuildDependenciesSchema = z.object({
5152
+ checkedPackages: z.number().int().min(0).describe("검사한 설치 트리의 패키지(이름·버전) 수"),
5153
+ rejected: z.array(storefrontDependencyFindingSchema).describe("빌드를 막은 의존성"),
5154
+ warnings: z.array(storefrontDependencyFindingSchema).describe("빌드는 막지 않는 경고(중간 이하 심각도 권고)")
5155
+ });
4687
5156
  const storefrontBuildSchema = z.object({
4688
5157
  buildId: z.string(),
4689
5158
  versionId: z.string(),
4690
5159
  versionNumber: z.number().int().min(1),
4691
5160
  status: storefrontBuildStatusSchema,
4692
- failureStage: z.string().nullable().describe("실패한 단계 (`VERIFY`·`RESTORE`·`INSTALL`·`TYPECHECK`·`BUILD`·`BUNDLE`·`PACK`·`UPLOAD`, 늘어날 수 있다). 실패가 아니면 null"),
4693
- errorCode: z.string().nullable().describe("실패 코드 (`RULES_VIOLATED`·`INSTALL_FAILED`·`TYPECHECK_FAILED`·`BUILD_FAILED`·`BUNDLE_FAILED`·`ARTIFACT_INVALID`·`UPLOAD_FAILED`·`SOURCE_NOT_FOUND`·`CAPACITY_EXHAUSTED`·`INTERNAL`, 늘어날 수 있다). 실패가 아니면 null"),
5161
+ failureStage: z.string().nullable().describe("실패한 단계 (`DEPENDENCIES`·`VERIFY`·`RESTORE`·`INSTALL`·`TYPECHECK`·`BUILD`·`BUNDLE`·`PACK`·`UPLOAD`, 늘어날 수 있다). 실패가 아니면 null"),
5162
+ errorCode: z.string().nullable().describe("실패 코드 (`DEPENDENCY_REJECTED`·`DEPENDENCY_CHECK_UNAVAILABLE`·`DEPENDENCIES_CHANGED`·`RULES_VIOLATED`·`INSTALL_FAILED`·`INSTALL_TOO_LARGE`·`TYPECHECK_FAILED`·`BUILD_FAILED`·`BUILD_TIMEOUT`·`BUNDLE_FAILED`·`ARTIFACT_TOO_LARGE`·`ARTIFACT_INVALID`·`UPLOAD_FAILED`·`SOURCE_NOT_FOUND`·`CAPACITY_EXHAUSTED`·`INTERNAL`, 늘어날 수 있다). 실패가 아니면 null"),
4694
5163
  logTail: z.string().nullable().describe("실패 단계 출력 꼬리 (최대 8KB). 없으면 null"),
4695
5164
  stages: z.array(z.object({
4696
5165
  stage: z.string(),
@@ -4698,6 +5167,7 @@ const storefrontBuildSchema = z.object({
4698
5167
  durationMs: z.number().int().min(0)
4699
5168
  })).describe("끝난 단계 (도는 순서)"),
4700
5169
  verify: storefrontVerifyReportSchema.nullable().describe("규칙 검증 보고서(위반 상세는 50건까지). 그 단계까지 가지 못했으면 null"),
5170
+ dependencies: storefrontBuildDependenciesSchema.nullable().optional().describe("의존성 검사 결과(거절 목록·보안 권고). 빌드 시작 전에 돈다. 거절이 있으면 `failureStage: DEPENDENCIES`, `errorCode: DEPENDENCY_REJECTED`다. 아직 검사 전이면 null(옛 서버는 싣지 않는다)"),
4701
5171
  queuedAt: z.string(),
4702
5172
  startedAt: z.string().nullable(),
4703
5173
  finishedAt: z.string().nullable(),
@@ -4708,7 +5178,10 @@ const storefrontBuildListQuerySchema = z.object({ limit: z.coerce.number().int()
4708
5178
  * `POST /v1/storefront/site/versions` — 지금 작업 트리를 고정해 버전을 만든다(빌드 시작). 사이트당 끝나지 않은 빌드는
4709
5179
  * 하나다(409 `STOREFRONT_BUILD_IN_PROGRESS`). 편집 환경이 없어도 된다.
4710
5180
  */
4711
- const createStorefrontVersionRequestSchema = z.object({ message: z.string().max(200).optional().describe("버전 메모 (에이전트는 바꾼 내용 요약)") });
5181
+ const createStorefrontVersionRequestSchema = z.object({
5182
+ message: z.string().max(200).optional().describe("버전 메모 (에이전트는 바꾼 내용 요약)"),
5183
+ expectedSourceRef: z.string().max(200).optional().describe("고정할 작업 트리 (`PUT /v1/storefront/site/source`의 `sourceRef`). 주면 지금 작업 트리가 이 값일 때만 버전을 만든다(다르면 409 `SOURCE_CHANGED`)")
5184
+ });
4712
5185
  /** 버전 만들기 응답 (201) — 버전은 `BUILDING`, 빌드는 `QUEUED`로 시작한다. 결과는 `GET builds/{buildId}`로 본다 */
4713
5186
  const storefrontVersionCreatedSchema = z.object({
4714
5187
  version: storefrontVersionSchema,
@@ -4720,6 +5193,80 @@ const storefrontVersionCreatedSchema = z.object({
4720
5193
  * 없으면 409 `EDIT_SESSION_REQUIRED`다(편집 환경이 `STARTING`이면 링크는 주고, 미리보기 화면이 기동 대기를 보여 준다).
4721
5194
  */
4722
5195
  const createStorefrontEditPreviewLinkRequestSchema = createStorefrontPreviewLinkRequestSchema;
5196
+ /** 압축 파일 조각 하나의 최대 크기(바이트) */
5197
+ const STOREFRONT_SOURCE_PART_BYTES = 4194304;
5198
+ /** 내용 올리기 한 번의 최대 파일 수 */
5199
+ const STOREFRONT_SOURCE_BLOB_BATCH_MAX = 200;
5200
+ /** 작업 트리 파일 수 상한 */
5201
+ const STOREFRONT_WORKSPACE_MAX_FILES = 2e3;
5202
+ /** 작업 트리 스냅샷 키 — `sources/{siteId}/{sha256}.tgz` */
5203
+ const storefrontSourceRefSchema = z.string().regex(/^sources\/sfs_[a-z0-9]{8,40}\/[0-9a-f]{64}\.tgz$/, "작업 트리 스냅샷 키 형식이 아닙니다").describe("작업 트리 스냅샷 (`sources/{siteId}/{sha256}.tgz`, 내용 주소)");
5204
+ /** `GET /v1/storefront/site/source` — 지금 작업 트리(편집 환경이 떠 있으면 지금 내용을 고정한다) */
5205
+ const storefrontSourceSchema = z.object({
5206
+ siteId: z.string(),
5207
+ storeId: z.string(),
5208
+ storeCode: z.string().describe("스토어 코드 — 로컬 개발 서버의 `SAYREN_STORE_CODE`"),
5209
+ sourceRef: storefrontSourceRefSchema,
5210
+ treeSha256: z.string().describe("트리 지문 (경로 오름차순 `{path}\\t{sha256}\\n`의 SHA-256)"),
5211
+ files: z.number().int().min(0).describe("파일 수"),
5212
+ bytes: z.number().int().min(0).describe("파일 크기 합"),
5213
+ archive: z.object({
5214
+ format: z.literal("tar+gzip"),
5215
+ bytes: z.number().int().min(0).describe("압축 파일 크기"),
5216
+ sha256: z.string().describe("압축 파일의 SHA-256 (`sourceRef`의 해시와 같다)"),
5217
+ parts: z.number().int().min(0).describe("`GET source/archive`로 받을 조각 수"),
5218
+ partBytes: z.number().int().describe("조각 하나의 최대 크기")
5219
+ }),
5220
+ editSessionOpen: z.boolean().describe("편집 환경이 열려 있는가 (올리기 전에 끄거나 `endEditSession`)")
5221
+ });
5222
+ const storefrontSourceArchiveQuerySchema = z.object({
5223
+ sourceRef: storefrontSourceRefSchema,
5224
+ part: z.coerce.number().int().min(0).max(100).optional().describe("조각 번호 (0부터, 기본 0)")
5225
+ });
5226
+ /** `GET /v1/storefront/site/source/archive` — 압축 파일 조각 */
5227
+ const storefrontSourceArchivePartSchema = z.object({
5228
+ sourceRef: storefrontSourceRefSchema,
5229
+ part: z.number().int().min(0),
5230
+ parts: z.number().int().min(0),
5231
+ offset: z.number().int().min(0),
5232
+ bytes: z.number().int().min(0).describe("이 조각의 크기"),
5233
+ totalBytes: z.number().int().min(0),
5234
+ content: z.string().describe("조각 내용 (base64)")
5235
+ });
5236
+ /** `POST /v1/storefront/site/source/blobs` — 파일 내용 올리기(해시 키, 다시 올려도 안전하다). 작업 트리는 바뀌지 않는다 */
5237
+ const uploadStorefrontSourceBlobsRequestSchema = z.object({ blobs: z.array(z.object({
5238
+ hash: storefrontFileHashSchema,
5239
+ content: z.string().describe("파일 내용 (base64, 1MiB까지)")
5240
+ })).min(1).max(200) });
5241
+ const storefrontSourceBlobsResultSchema = z.object({
5242
+ stored: z.number().int().min(0).describe("새로 올린 수"),
5243
+ existing: z.number().int().min(0).describe("이미 있던 수")
5244
+ });
5245
+ /**
5246
+ * `PUT /v1/storefront/site/source` — 작업 트리 전체를 이 목록으로 바꾼다(없는 경로는 지운다). 전부 아니면 전무다.
5247
+ * `package.json`·`pnpm-lock.yaml`도 바꿀 수 있고 의존성은 허용 목록 안이어야 한다. `.env*`·작업 트리 밖 경로는 받지 않는다.
5248
+ */
5249
+ const replaceStorefrontSourceRequestSchema = z.object({
5250
+ expectedBaseRef: storefrontSourceRefSchema.optional().describe("내려받은 작업 트리. 지금 작업 트리가 이 값일 때만 바꾼다. 생략하면 조건 없이 덮어쓴다"),
5251
+ files: z.array(z.object({
5252
+ path: storefrontFilePathSchema,
5253
+ hash: storefrontFileHashSchema,
5254
+ size: z.number().int().min(0).max(STOREFRONT_FILE_MAX_BYTES)
5255
+ })).min(1).max(STOREFRONT_WORKSPACE_MAX_FILES).refine((files) => new Set(files.map((file) => file.path)).size === files.length, { message: "같은 경로를 두 번 실을 수 없습니다" }),
5256
+ endEditSession: z.boolean().optional().describe("열린 편집 환경을 끄고 진행한다 (기본 false — 열려 있으면 409 `EDIT_SESSION_OPEN`)"),
5257
+ packageJson: z.string().max(STOREFRONT_FILE_MAX_BYTES).optional().describe("`files`에 `package.json`이 있으면 그 내용(utf8). 서버가 의존성을 판정한다. 해시가 목록과 다르면 400 `DEPENDENCY_FILES_MISMATCH`"),
5258
+ lockfile: z.string().max(STOREFRONT_FILE_MAX_BYTES).optional().describe("`files`에 `pnpm-lock.yaml`이 있으면 그 내용(utf8)")
5259
+ });
5260
+ const storefrontSourceReplacedSchema = z.object({
5261
+ sourceRef: storefrontSourceRefSchema.describe("새 작업 트리 — 다음 올리기의 `expectedBaseRef`"),
5262
+ previousRef: z.string().describe("바꾸기 전 작업 트리"),
5263
+ treeSha256: z.string(),
5264
+ files: z.number().int().min(0),
5265
+ bytes: z.number().int().min(0),
5266
+ changed: z.boolean().describe("내용이 바뀌었는가 (같은 트리를 다시 올리면 false)"),
5267
+ endedEditSession: z.boolean().describe("열린 편집 환경을 끄고 진행했는가"),
5268
+ dependencyWarnings: z.array(storefrontDependencyFindingSchema).describe("배포를 막지 않는 의존성 경고 (보안 권고 등)")
5269
+ });
4723
5270
  //#endregion
4724
5271
  //#region src/client.ts
4725
5272
  function formBody(fields) {
@@ -4907,6 +5454,21 @@ function createStoreClient(options) {
4907
5454
  setDiscount: (productId, body) => storeHttp.request("PUT", path`/products/${productId}/discount`, discountSchema, { body: discountRequestSchema.parse(body) }),
4908
5455
  removeDiscount: (productId) => storeHttp.requestVoid("DELETE", path`/products/${productId}/discount`)
4909
5456
  },
5457
+ /**
5458
+ * 저장 옵션 (이슈 #59) — 자주 쓰는 옵션 구성. 불러오기는 복사라 저장 옵션을 고쳐도 불러온 상품은 바뀌지 않는다.
5459
+ * 권한은 상품과 같다(`product:r`·`product:rw`).
5460
+ */
5461
+ savedOptionSets: {
5462
+ list: (params) => storeHttp.request("GET", "/saved-option-sets", pageSchema(savedOptionSetSchema), { query: params }),
5463
+ get: (optionSetId) => storeHttp.request("GET", path`/saved-option-sets/${optionSetId}`, savedOptionSetSchema),
5464
+ /** 이름이 겹치면 409 `OPTION_SET_NAME_TAKEN`, 스토어당 200개를 넘으면 409 `OPTION_SET_LIMIT_EXCEEDED` */
5465
+ create: (body) => storeHttp.request("POST", "/saved-option-sets", savedOptionSetSchema, { body: savedOptionSetRequestSchema.parse(body) }),
5466
+ /** 통째로 바꾼다. 불러온 상품은 바뀌지 않는다 */
5467
+ update: (optionSetId, body) => storeHttp.request("PUT", path`/saved-option-sets/${optionSetId}`, savedOptionSetSchema, { body: savedOptionSetRequestSchema.parse(body) }),
5468
+ remove: (optionSetId) => storeHttp.requestVoid("DELETE", path`/saved-option-sets/${optionSetId}`),
5469
+ /** 상품의 현재 옵션을 저장 옵션으로 만든다(또는 `replaceOptionSetId`를 덮어쓴다) */
5470
+ saveFromProduct: (body) => storeHttp.request("POST", "/saved-option-sets/from-product", savedOptionSetFromProductResultSchema, { body: savedOptionSetFromProductRequestSchema.parse(body) })
5471
+ },
4910
5472
  categories: {
4911
5473
  list: (params) => storeHttp.request("GET", "/categories", z.array(categorySchema), { query: params }),
4912
5474
  tree: () => storeHttp.request("GET", "/categories", z.array(categoryTreeNodeSchema), { query: { tree: true } }),
@@ -5317,6 +5879,19 @@ list: (params) => storeHttp.request("GET", "/audit-logs", auditLogPageSchema, {
5317
5879
  * 콘솔에서 로그인한 OWNER·ADMIN만 받는다
5318
5880
  */
5319
5881
  createPreviewLink: (body = {}) => storeHttp.request("POST", "/storefront/site/preview-link", storefrontPreviewLinkSchema, { body: createStorefrontPreviewLinkRequestSchema.parse(body) }),
5882
+ /** `GET /v1/storefront/site/domains` — 주소 목록(기본 주소 + 내 도메인)과 DNS 안내 */
5883
+ listDomains: () => storeHttp.request("GET", "/storefront/site/domains", storefrontSiteDomainsSchema),
5884
+ /**
5885
+ * `POST /v1/storefront/site/domains` (201) — 내 도메인을 추가한다. 응답의 `dnsRecords`를 DNS에 넣으면 서버가 소유를 확인하고
5886
+ * 인증서를 발급한다
5887
+ */
5888
+ addDomain: (body) => storeHttp.request("POST", "/storefront/site/domains", storefrontSiteDomainSchema, { body: addStorefrontDomainRequestSchema.parse(body) }),
5889
+ /** `POST /v1/storefront/site/domains/{domainId}/verify` — DNS를 곧바로 다시 확인한다(실패한 도메인은 처음부터 다시) */
5890
+ verifyDomain: (domainId) => storeHttp.request("POST", path`/storefront/site/domains/${domainId}/verify`, storefrontSiteDomainSchema),
5891
+ /** `POST /v1/storefront/site/domains/{domainId}/primary` — 대표 주소로 지정한다. 다른 주소는 대표 주소로 301한다 */
5892
+ setPrimaryDomain: (domainId) => storeHttp.request("POST", path`/storefront/site/domains/${domainId}/primary`, storefrontSiteDomainsSchema),
5893
+ /** `DELETE /v1/storefront/site/domains/{domainId}` — 내 도메인 연결을 해제한다(곧바로 끊긴다) */
5894
+ removeDomain: (domainId) => storeHttp.requestVoid("DELETE", path`/storefront/site/domains/${domainId}`),
5320
5895
  /** `POST /v1/storefront/site/disable` — 호스팅을 끈다(사이트 `CLOSED`, 슬러그 90일 보류). 다시 만들 수 있다 */
5321
5896
  disable: (body) => storeHttp.request("POST", "/storefront/site/disable", storefrontSiteSchema, { body: disableStorefrontHostingRequestSchema.parse(body) }),
5322
5897
  /** `GET /v1/storefront/site/editing` — 편집 환경 상태(열린 환경·동시 상한·미리보기 인증서) */
@@ -5365,7 +5940,18 @@ list: (params) => storeHttp.request("GET", "/audit-logs", auditLogPageSchema, {
5365
5940
  /** `GET /v1/storefront/site/builds?limit=` — 빌드 목록(최신 먼저) */
5366
5941
  listBuilds: (params = {}) => storeHttp.request("GET", "/storefront/site/builds", z.array(storefrontBuildSchema), { query: params }),
5367
5942
  /** `GET /v1/storefront/site/builds/{buildId}` — 빌드 상태·실패 단계·로그 꼬리·규칙 결과 */
5368
- getBuild: (buildId) => storeHttp.request("GET", path`/storefront/site/builds/${buildId}`, storefrontBuildSchema)
5943
+ getBuild: (buildId) => storeHttp.request("GET", path`/storefront/site/builds/${buildId}`, storefrontBuildSchema),
5944
+ /** `GET /v1/storefront/site/source` — 지금 작업 트리를 고정하고 압축 파일(tar.gz) 크기·조각 수를 알린다 */
5945
+ getSource: () => storeHttp.request("GET", "/storefront/site/source", storefrontSourceSchema),
5946
+ /** `GET /v1/storefront/site/source/archive?sourceRef=&part=` — 압축 파일 조각(base64, 4MiB까지) */
5947
+ getSourceArchive: (params) => storeHttp.request("GET", "/storefront/site/source/archive", storefrontSourceArchivePartSchema, { query: storefrontSourceArchiveQuerySchema.parse(params) }),
5948
+ /** `POST /v1/storefront/site/source/blobs` — 파일 내용 올리기(해시 키). 작업 트리는 바뀌지 않는다 */
5949
+ uploadSourceBlobs: (body) => storeHttp.request("POST", "/storefront/site/source/blobs", storefrontSourceBlobsResultSchema, { body: uploadStorefrontSourceBlobsRequestSchema.parse(body) }),
5950
+ /**
5951
+ * `PUT /v1/storefront/site/source` — 작업 트리 전체를 바꾼다. 409 `SOURCE_CHANGED`(내려받은 뒤 바뀜)·`EDIT_SESSION_OPEN`·
5952
+ * `SOURCE_BLOBS_MISSING`(`details.hashes`를 올리고 다시)
5953
+ */
5954
+ replaceSource: (body) => storeHttp.request("PUT", "/storefront/site/source", storefrontSourceReplacedSchema, { body: replaceStorefrontSourceRequestSchema.parse(body) })
5369
5955
  },
5370
5956
  store: {
5371
5957
  get: () => storeHttp.request("GET", "/store", storeSchema),
@@ -5607,7 +6193,12 @@ const orderWebhookEventDataSchema = z.object({
5607
6193
  testPayment: z.boolean().optional().default(false).describe("테스트 결제(샌드박스 결제) 주문인가. true면 실제로 돈이 오가지 않았다. 이 필드가 없는 이전 이벤트는 false로 읽는다"),
5608
6194
  couponDiscountAmount: z.number().int().optional().default(0).describe("주문이 받은 상품·주문 쿠폰 할인 합계 (원). 즉시할인(`discountAmount`)과 별개다. 쿠폰이 없거나 이 필드가 없는 이전 이벤트는 0으로 읽는다"),
5609
6195
  orderItemId: z.string().optional().describe("이벤트를 만든 주문상품 ID. 배송 없는 상품 제공으로 보내는 `ORDER.DELIVERED`에만 실린다. 택배 배송완료와 그 밖의 주문 이벤트에는 없다"),
5610
- fulfillmentSnapshot: orderItemFulfillmentSnapshotResponseSchema.optional().describe("이벤트를 만든 주문상품의 주문 시점 이행 스냅샷(주문상품 응답의 `fulfillmentSnapshot`과 같다, 이슈 #57). 배송 없는 상품의 제공 완료는 `ORDER.DELIVERED`로도 오고 `type`이 `MANUAL`이다. `orderItemId`가 있는 이벤트에만 실린다")
6196
+ fulfillmentSnapshot: orderItemFulfillmentSnapshotResponseSchema.optional().describe("이벤트를 만든 주문상품의 주문 시점 이행 스냅샷(주문상품 응답의 `fulfillmentSnapshot`과 같다, 이슈 #57). 배송 없는 상품의 제공 완료는 `ORDER.DELIVERED`로도 오고 `type`이 `MANUAL`이다. `orderItemId`가 있는 이벤트에만 실린다"),
6197
+ itemOptions: z.array(z.object({
6198
+ orderItemId: z.string(),
6199
+ optionSelections: z.array(optionSelectionSchema),
6200
+ customInputs: z.array(customInputValueSchema)
6201
+ })).optional().describe("추가 선택·직접 입력이 있는 주문상품의 주문 시점 선택값(이슈 #59, 주문상품 응답의 `optionSelections`·`customInputs`와 같다). `ORDER.PAID`에만 실리고 그런 주문상품이 없으면 없다")
5611
6202
  });
5612
6203
  /**
5613
6204
  * `ORDER.FULFILLED` — 배송 없는 주문상품(`MANUAL`)을 제공했다(이슈 #48). 셀러의 제공 처리
@@ -5877,4 +6468,4 @@ function createStoreSession(options) {
5877
6468
  };
5878
6469
  }
5879
6470
  //#endregion
5880
- export { APPROVAL_ALREADY_DECIDED_CODE, APPROVAL_REQUIRED_CODE, ApiError, ApprovalPendingError, CASH_RECEIPT_STATUS_VALUES, COUPON_CODE_PATTERN, COUPON_CONDITIONS_DEFAULTS, COUPON_TARGET_ALL, COUPON_TARGET_LIST_MAX, FULFILLMENT_RULES, MAX_ANALYTICS_ALLOWED_ORIGINS, MAX_AUTO_TRANSITION_DAYS, MAX_OPTION_GROUPS, MAX_PRODUCT_IMAGES, MAX_VALUES_PER_GROUP, MAX_VARIANTS, MCP_TELEMETRY_MAX_RESULTS, MIN_PAYMENT_AMOUNT, NO_COUPON_SETTLEMENT, NO_SHIPPING_BUNDLE_KEY, PAYMENT_ROUTING_EXCLUSION_CODES, PORTONE_CHANNEL_NAME_MAX_LENGTH, PORTONE_MAX_CHANNELS, PORTONE_MIN_CHANNELS, REFUND_POLICY_DEFAULTS, REFUND_POLICY_DELIVERY_FEE_KEYS, REMOTE_AREA_ZIP_RANGES, RESERVED_STOREFRONT_SLUGS, RESERVED_STORE_CODES, SHIPPING_POLICY_DEFAULTS, STOREFRONT_BLOCKED_PATH_SEGMENTS, STOREFRONT_BUILD_STAGES, STOREFRONT_BUILD_TERMINAL_STATUSES, STOREFRONT_CHECK_TERMINAL_STATUSES, STOREFRONT_EDIT_ACTIVITY_TIMEOUT_MINUTES, STOREFRONT_EDIT_CAPACITY, STOREFRONT_EDIT_IDLE_MINUTES, STOREFRONT_EDIT_PREVIEW_SUFFIX, STOREFRONT_EDIT_START_STAGES, STOREFRONT_FILE_BATCH_MAX, STOREFRONT_FILE_MAX_BYTES, STOREFRONT_PATH_SEGMENT_PATTERN, STOREFRONT_SEARCH_REGEX_MAX_LENGTH, STOREFRONT_SITE_CREATE_LIMIT, STOREFRONT_SLUG_CHANGE_LIMIT, STOREFRONT_SLUG_CHANGE_WINDOW_DAYS, STOREFRONT_SLUG_HOLD_DAYS, STOREFRONT_SLUG_PATTERN, STOREFRONT_WORKSPACE_MAX_BYTES, STORE_CODE_PATTERN, STORE_ROLE_SCOPES, STORE_SCOPE_RESOURCES, STORE_SESSION_KEYS, TOSS_WIDGET_CLIENT_KEY_PATTERN, TOSS_WIDGET_KEY_MESSAGE, TOSS_WIDGET_SECRET_KEY_PATTERN, UPLOAD_PURPOSE_LIMITS, VARIANT_STOCK_EXCLUSIVE_MESSAGE, WEBHOOK_DEFAULT_TOLERANCE_SEC, WEBHOOK_DELIVERY_ID_HEADER, WEBHOOK_EVENT_ID_HEADER, WEBHOOK_EVENT_TYPE_HEADER, WEBHOOK_FAILURE_REASONS, WEBHOOK_HEADERS, WEBHOOK_PAYLOAD_EXCERPT_MAX_BYTES, WEBHOOK_RESOURCE_TYPES, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_USER_AGENT, addressChangeRequestListSchema, addressTypeSchema, agentPolicyDecisionSchema, allocateByWeight, analyticsChannelSchema, analyticsCheckoutOutcomeSchema, analyticsCheckoutSchema, analyticsDeviceRowSchema, analyticsDevicesSchema, analyticsFunnelStepSchema, analyticsOriginSchema, analyticsOverviewSchema, analyticsPagesQuerySchema, analyticsPagesSchema, analyticsProductRowSchema, analyticsProductSortSchema, analyticsProductsQuerySchema, analyticsProductsSchema, analyticsRangeQuerySchema, analyticsRealtimeSchema, analyticsSearchQuerySchema, analyticsSearchSchema, analyticsSourceGroupBySchema, analyticsSourceRowSchema, analyticsSourcesQuerySchema, analyticsSourcesSchema, analyticsTotalsSchema, apiClientListQuerySchema, apiClientSchema, apiClientSecretRotatedSchema, apiClientStatusSchema, apiClientTypeSchema, apiLogErrorSchema, apiLogListQuerySchema, apiLogPageSchema, apiLogPrincipalSchema, apiLogPrincipalTypeSchema, apiLogSchema, apiLogStatusFilterSchema, apiLogSummarySchema, apiTokenCreateRequestSchema, apiTokenCreatedSchema, apiTokenSchema, approvalDecisionRequestSchema, approvalListQuerySchema, approvalPageSchema, approvalRequestSchema, approvalStatusSchema, approveClaimResultSchema, auditActorSchema, auditActorTypeSchema, auditLogListQuerySchema, auditLogOutcomeSchema, auditLogPageSchema, auditLogSchema, auditLogSourceSchema, buildAuthorizeUrl, buildQuery, bulkResultSchema, cancelBySellerRequestSchema, cancelBySellerResultSchema, carrierSchema, cashReceiptSchema, categoryAttributeSchema, categoryCreateRequestSchema, categoryDeleteQuerySchema, categoryDeleteStrategySchema, categoryListQuerySchema, categoryPatchRequestSchema, categoryProductSortSchema, categorySchema, categorySearchResultSchema, categoryTreeNodeSchema, categoryWithPathSchema, changeStorefrontSlugRequestSchema, changeStorefrontTemplateRequestSchema, changeTypeSchema, claimDetailSchema, claimHoldResultSchema, claimListQuerySchema, claimReasonSchema, claimReturnAddressSchema, claimSchema, claimStatusSchema, claimTypeSchema, claimWebhookEventDataSchema, claimWebhookEventSchema, clientCredentialsTokenResponseSchema, consentActionSchema, consentKindSchema, consentSourceSchema, couponBenefitSchema, couponConditionsInputSchema, couponConditionsSchema, couponDiscountOf, couponIssueListQuerySchema, couponIssueMethodSchema, couponIssueSchema, couponIssueStatusSchema, couponKindSchema, couponListQuerySchema, couponRedemptionListQuerySchema, couponRedemptionSchema, couponRejectReasonSchema, couponRuleSchema, couponSchema, couponStatsSchema, couponStatusSchema, couponTargetInputSchema, couponTargetSchema, createCouponRequestSchema, createPkcePair, createStoreClient, createStoreSession, createStorefrontEditPreviewLinkRequestSchema, createStorefrontPreviewLinkRequestSchema, createStorefrontSiteRequestSchema, createStorefrontVersionRequestSchema, customerAddressSchema, customerConsentListQuerySchema, customerConsentSchema, customerConsentStateSchema, customerConsentsSchema, customerCouponSchema, customerDetailSchema, customerInquiryCategorySchema, customerInquiryListQuerySchema, customerListQuerySchema, customerOrderSchema, customerSortSchema, customerStatsSchema, customerSummarySchema, customerWithdrawalBlockedDetailsSchema, deleteStorefrontFileRequestSchema, deliveryBundleKeyOf, deliveryFeeWaiverSchema, deliveryInfoSchema, deliveryMethodSchema, deliveryStatusSchema, deliveryTypeSchema, disableStorefrontHostingRequestSchema, discountRequestSchema, discountSchema, dispatchRequestSchema, easyPayProviderResponseSchema, easyPayProviderSchema, eventTypeSchema, exchangeAuthorizationCode, exchangeStoreTokenViaOAuth, fulfillBulkRequestSchema, fulfillRequestSchema, fulfillmentRuleOf, fulfillmentTypeOf, fulfillmentTypeResponseSchema, fulfillmentTypeSchema, holdOrderItemRequestSchema, inquiryListQuerySchema, inquirySchema, inquiryWebhookEventDataSchema, inquiryWebhookEventSchema, invitationDeliveryStatusSchema, invitationListQuerySchema, invitationSchema, inviteMemberRequestSchema, isCouponTargetLine, isEditableStorefrontPath, isReservedStoreCode, isReservedStorefrontSlug, isStoreScope, isWebOrigin, issueCouponRequestSchema, issueCouponResultSchema, liveGateConditionCodeSchema, liveGateConditionSchema, liveGateErrorDetailsSchema, liveGateSchema, loginProviderSchema, loginProviderStatusSchema, loginProviderTestSchema, loginSettingsSchema, maskedSecretSchema, mcpRuleResultSchema, mcpToolEventSchema, moveStorefrontFileRequestSchema, myAccountSchema, myStoreSchema, normalizeCouponCode, normalizeStorefrontPath, oauthClientRegistrationRequestSchema, oauthClientRegistrationResponseSchema, oauthTokenResponseSchema, openFulfillmentUnion, optionGroupRequestSchema, optionGroupSchema, optionModelRequestSchema, optionModelSchema, optionValueRequestSchema, optionValueSchema, orderCanceledWebhookEventDataSchema, orderCanceledWebhookEventSchema, orderDiscountSchema, orderFulfilledWebhookEventDataSchema, orderFulfilledWebhookEventSchema, orderItemFulfillmentSchema, orderItemFulfillmentSnapshotResponseSchema, orderItemFulfillmentSnapshotSchema, orderItemListQuerySchema, orderItemMemoResultSchema, orderItemPageSchema, orderItemSchema, orderItemStatusSchema, orderListQuerySchema, orderSchema, orderSummaryPageSchema, orderSummarySchema, orderWebhookEventDataSchema, orderWebhookEventSchema, pageInfoSchema, pageQueryShape, pageSchema, paymentAttemptRecordSchema, paymentAttemptStatusSchema, paymentCredentialSetSchema, paymentListQuerySchema, paymentProviderSchema, paymentProviderVerificationSchema, paymentRecordDetailSchema, paymentRecordPageSchema, paymentRecordSchema, paymentRecordStatusSchema, paymentRefundRecordSchema, paymentSettingsSchema, pgEnvironmentSchema, pgPaymentMethodSchema, pgProviderSchema, pgVerificationResultSchema, policyDocumentUrlSchema, policyVersionSchema, portOneChannelSchema, portOneCredentialsInputSchema, productCreateRequestSchema, productFulfillmentCreateSchema, productFulfillmentResponseSchema, productFulfillmentSchema, productFulfillmentUpdateSchema, productImageRejection, productImageUrlSchema, productListQuerySchema, productOutOfStockWebhookEventDataSchema, productOutOfStockWebhookEventSchema, productPatchRequestSchema, productRefundPolicyOverride, productRefundPolicyPatchSchema, productReplaceRequestSchema, productSchema, productSortSchema, productStatusSchema, productSummarySchema, productVariantRequestSchema, productVariantSchema, proratedPaidRefundBase, proratedRefundBase, publishStorefrontVersionRequestSchema, purchaseDecisionExtendResultSchema, purchaseDecisionRequestResultSchema, quoteDelivery, quotePricing, refreshUserToken, refundPolicyPatchSchema, refundPolicySchema, rejectClaimRequestSchema, remoteAreaLabelOf, remoteAreaOf, remoteAreaSchema, remoteSurchargeOf, requiresCollection, requiresShipping, resolveRefundPolicy, resolveShippingPolicy, reviewListQuerySchema, reviewReportResultSchema, reviewSchema, reviewSortSchema, reviewWebhookEventDataSchema, reviewWebhookEventSchema, rollbackStorefrontRequestSchema, runStorefrontCheckRequestSchema, salesReportGroupBySchema, salesReportQuerySchema, salesReportRowSchema, salesReportSchema, salesTotalsSchema, saveLoginProviderRequestSchema, scopeAccessSchema, searchStorefrontFilesRequestSchema, sellerAddressListQuerySchema, sellerAddressRequestSchema, sellerAddressSchema, sellerCustomerInquirySchema, setStorefrontVisibilityRequestSchema, settleCouponShortfall, settlementDetailQuerySchema, settlementDetailSchema, settlementListQuerySchema, settlementStatusSchema, settlementSummarySchema, settlementWithDetailsSchema, shippingAddressSchema, shippingPolicyPatchSchema, shippingPolicySchema, storeCodeSchema, storeMemberRoleUpdatedSchema, storeMemberSchema, storeRoleSchema, storeSchema, storeScopeResourceSchema, storeTokenResponseSchema, storefrontBuildListQuerySchema, storefrontBuildSchema, storefrontBuildStatusSchema, storefrontCheckSchema, storefrontCheckStatusSchema, storefrontDomainSchema, storefrontEditPreviewLabelOf, storefrontEditPreviewSchema, storefrontEditSessionSchema, storefrontEditSessionStatusSchema, storefrontEditingSchema, storefrontFileConflictDetailsSchema, storefrontFileContentQuerySchema, storefrontFileContentSchema, storefrontFileDeleteResultSchema, storefrontFileEncodingSchema, storefrontFileEntrySchema, storefrontFileHashSchema, storefrontFileListQuerySchema, storefrontFileListSchema, storefrontFileMoveResultSchema, storefrontFilePathSchema, storefrontFileSearchResultSchema, storefrontFileWriteResultSchema, storefrontHostingSchema, storefrontPreviewLinkSchema, storefrontProvisioningSchema, storefrontRuleResultSchema, storefrontRuleSeveritySchema, storefrontSiteSchema, storefrontSiteStatusSchema, storefrontSlugAvailabilityQuerySchema, storefrontSlugAvailabilitySchema, storefrontSlugSchema, storefrontSourceModeSchema, storefrontTemplateSchema, storefrontVerifyReportSchema, storefrontVersionCreatedSchema, storefrontVersionSchema, storefrontVersionStatusSchema, storefrontVisibilitySchema, tossPaymentsCredentialsInputSchema, trackingStageResponseSchema, trackingUpdateResultSchema, updateCouponRequestSchema, updateLoginProviderRequestSchema, updateLoginSettingsRequestSchema, updateMemberRoleRequestSchema, updatePaymentProviderRequestSchema, updatePaymentSettingsRequestSchema, updateStoreRequestSchema, uploadExtensionOf, uploadPurposeSchema, uploadRejection, uploadRequestSchema, uploadTicketSchema, upsertPortOneProviderRequestSchema, upsertTossPaymentsProviderRequestSchema, userTokenResponseSchema, variantStockRequestSchema, variantStockSchema, verifyPaymentProviderRequestSchema, webhookCreatedSchema, webhookDeliveryDetailSchema, webhookDeliveryErrorCodeSchema, webhookDeliveryListQuerySchema, webhookDeliveryResourceSchema, webhookDeliverySchema, webhookDeliveryStatusSchema, webhookEventSchema, webhookFailureReasonSchema, webhookRedeliverFailedResultSchema, webhookRequestHeaderSchema, webhookRequestSchema, webhookResourceTypeSchema, webhookSchema, webhookSecretRotatedSchema, webhookStatusSchema, webhookTestRequestSchema, webhookTestResultSchema, withdrawCustomerRequestSchema, writeDryRunResultSchema, writePreviewSchema, writeStorefrontFilesRequestSchema };
6471
+ export { APPROVAL_ALREADY_DECIDED_CODE, APPROVAL_REQUIRED_CODE, ApiError, ApprovalPendingError, CASH_RECEIPT_STATUS_VALUES, COUPON_CODE_PATTERN, COUPON_CONDITIONS_DEFAULTS, COUPON_TARGET_ALL, COUPON_TARGET_LIST_MAX, CUSTOM_INPUT_FORBIDDEN_WORDS, FULFILLMENT_RULES, MAX_ADDON_GROUPS, MAX_ANALYTICS_ALLOWED_ORIGINS, MAX_AUTO_TRANSITION_DAYS, MAX_CUSTOM_INPUTS, MAX_CUSTOM_INPUT_LENGTH, MAX_OPTION_GROUPS, MAX_PRODUCT_IMAGES, MAX_SAVED_OPTION_SETS, MAX_SAVED_OPTION_SET_NAME, MAX_VALUES_PER_GROUP, MAX_VARIANTS, MCP_TELEMETRY_MAX_RESULTS, MIN_PAYMENT_AMOUNT, NO_COUPON_SETTLEMENT, NO_SHIPPING_BUNDLE_KEY, PAYMENT_ROUTING_EXCLUSION_CODES, PORTONE_CHANNEL_NAME_MAX_LENGTH, PORTONE_MAX_CHANNELS, PORTONE_MIN_CHANNELS, REFUND_POLICY_DEFAULTS, REFUND_POLICY_DELIVERY_FEE_KEYS, REMOTE_AREA_ZIP_RANGES, RESERVED_STOREFRONT_SLUGS, RESERVED_STORE_CODES, SHIPPING_POLICY_DEFAULTS, STOREFRONT_BLOCKED_PATH_SEGMENTS, STOREFRONT_BUILD_STAGES, STOREFRONT_BUILD_TERMINAL_STATUSES, STOREFRONT_CHECK_TERMINAL_STATUSES, STOREFRONT_CUSTOM_DOMAIN_LIMIT_DEFAULT, STOREFRONT_DOMAIN_DNS_GRACE_DAYS, STOREFRONT_DOMAIN_DNS_WAIT_DAYS, STOREFRONT_DOMAIN_TXT_LABEL, STOREFRONT_EDIT_ACTIVITY_TIMEOUT_MINUTES, STOREFRONT_EDIT_CAPACITY, STOREFRONT_EDIT_IDLE_MINUTES, STOREFRONT_EDIT_PREVIEW_SUFFIX, STOREFRONT_EDIT_START_STAGES, STOREFRONT_FILE_BATCH_MAX, STOREFRONT_FILE_MAX_BYTES, STOREFRONT_PATH_SEGMENT_PATTERN, STOREFRONT_PLATFORM_DOMAINS, STOREFRONT_PUBLIC_SUFFIXES, STOREFRONT_SEARCH_REGEX_MAX_LENGTH, STOREFRONT_SITE_CREATE_LIMIT, STOREFRONT_SLUG_CHANGE_LIMIT, STOREFRONT_SLUG_CHANGE_WINDOW_DAYS, STOREFRONT_SLUG_HOLD_DAYS, STOREFRONT_SLUG_PATTERN, STOREFRONT_SOURCE_BLOB_BATCH_MAX, STOREFRONT_SOURCE_PART_BYTES, STOREFRONT_WORKSPACE_MAX_BYTES, STOREFRONT_WORKSPACE_MAX_FILES, STORE_CODE_PATTERN, STORE_ROLE_SCOPES, STORE_SCOPE_RESOURCES, STORE_SESSION_KEYS, TOSS_WIDGET_CLIENT_KEY_PATTERN, TOSS_WIDGET_KEY_MESSAGE, TOSS_WIDGET_SECRET_KEY_PATTERN, UPLOAD_PURPOSE_LIMITS, VARIANT_COUNT_WARNING, VARIANT_STOCK_EXCLUSIVE_MESSAGE, WEBHOOK_DEFAULT_TOLERANCE_SEC, WEBHOOK_DELIVERY_ID_HEADER, WEBHOOK_EVENT_ID_HEADER, WEBHOOK_EVENT_TYPE_HEADER, WEBHOOK_FAILURE_REASONS, WEBHOOK_HEADERS, WEBHOOK_PAYLOAD_EXCERPT_MAX_BYTES, WEBHOOK_RESOURCE_TYPES, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_USER_AGENT, addStorefrontDomainRequestSchema, addonGroupRequestSchema, addonGroupSchema, addonValueRequestSchema, addonValueSchema, addressChangeRequestListSchema, addressTypeSchema, agentPolicyDecisionSchema, allocateByWeight, analyticsChannelSchema, analyticsCheckoutOutcomeSchema, analyticsCheckoutSchema, analyticsDeviceRowSchema, analyticsDevicesSchema, analyticsFunnelStepSchema, analyticsOriginSchema, analyticsOverviewSchema, analyticsPagesQuerySchema, analyticsPagesSchema, analyticsProductRowSchema, analyticsProductSortSchema, analyticsProductsQuerySchema, analyticsProductsSchema, analyticsRangeQuerySchema, analyticsRealtimeSchema, analyticsSearchQuerySchema, analyticsSearchSchema, analyticsSourceGroupBySchema, analyticsSourceRowSchema, analyticsSourcesQuerySchema, analyticsSourcesSchema, analyticsTotalsSchema, apiClientListQuerySchema, apiClientSchema, apiClientSecretRotatedSchema, apiClientStatusSchema, apiClientTypeSchema, apiLogErrorSchema, apiLogListQuerySchema, apiLogPageSchema, apiLogPrincipalSchema, apiLogPrincipalTypeSchema, apiLogSchema, apiLogStatusFilterSchema, apiLogSummarySchema, apiTokenCreateRequestSchema, apiTokenCreatedSchema, apiTokenSchema, approvalDecisionRequestSchema, approvalListQuerySchema, approvalPageSchema, approvalRequestSchema, approvalStatusSchema, approveClaimResultSchema, auditActorSchema, auditActorTypeSchema, auditLogListQuerySchema, auditLogOutcomeSchema, auditLogPageSchema, auditLogSchema, auditLogSourceSchema, buildAuthorizeUrl, buildQuery, bulkResultSchema, cancelBySellerRequestSchema, cancelBySellerResultSchema, carrierSchema, cashReceiptSchema, categoryAttributeSchema, categoryCreateRequestSchema, categoryDeleteQuerySchema, categoryDeleteStrategySchema, categoryListQuerySchema, categoryPatchRequestSchema, categoryProductSortSchema, categorySchema, categorySearchResultSchema, categoryTreeNodeSchema, categoryWithPathSchema, changeStorefrontSlugRequestSchema, changeStorefrontTemplateRequestSchema, changeTypeSchema, claimDetailSchema, claimHoldResultSchema, claimListQuerySchema, claimReasonSchema, claimReturnAddressSchema, claimSchema, claimStatusSchema, claimTypeSchema, claimWebhookEventDataSchema, claimWebhookEventSchema, clientCredentialsTokenResponseSchema, consentActionSchema, consentKindSchema, consentSourceSchema, couponBenefitSchema, couponConditionsInputSchema, couponConditionsSchema, couponDiscountOf, couponIssueListQuerySchema, couponIssueMethodSchema, couponIssueSchema, couponIssueStatusSchema, couponKindSchema, couponListQuerySchema, couponRedemptionListQuerySchema, couponRedemptionSchema, couponRejectReasonSchema, couponRuleSchema, couponSchema, couponStatsSchema, couponStatusSchema, couponTargetInputSchema, couponTargetSchema, createCouponRequestSchema, createPkcePair, createStoreClient, createStoreSession, createStorefrontEditPreviewLinkRequestSchema, createStorefrontPreviewLinkRequestSchema, createStorefrontSiteRequestSchema, createStorefrontVersionRequestSchema, customInputLabelForbiddenWord, customInputRequestSchema, customInputSchema, customInputValueSchema, customerAddressSchema, customerConsentListQuerySchema, customerConsentSchema, customerConsentStateSchema, customerConsentsSchema, customerCouponSchema, customerDetailSchema, customerInquiryCategorySchema, customerInquiryListQuerySchema, customerListQuerySchema, customerOrderSchema, customerSortSchema, customerStatsSchema, customerSummarySchema, customerWithdrawalBlockedDetailsSchema, deleteStorefrontFileRequestSchema, deliveryBundleKeyOf, deliveryFeeWaiverSchema, deliveryInfoSchema, deliveryMethodSchema, deliveryStatusSchema, deliveryTypeSchema, disableStorefrontHostingRequestSchema, discountRequestSchema, discountSchema, dispatchRequestSchema, easyPayProviderResponseSchema, easyPayProviderSchema, eventTypeSchema, exchangeAuthorizationCode, exchangeStoreTokenViaOAuth, fulfillBulkRequestSchema, fulfillRequestSchema, fulfillmentRuleOf, fulfillmentTypeOf, fulfillmentTypeResponseSchema, fulfillmentTypeSchema, holdOrderItemRequestSchema, inquiryListQuerySchema, inquirySchema, inquiryWebhookEventDataSchema, inquiryWebhookEventSchema, invitationDeliveryStatusSchema, invitationListQuerySchema, invitationSchema, inviteMemberRequestSchema, isCouponTargetLine, isEditableStorefrontPath, isReservedStoreCode, isReservedStorefrontSlug, isStoreScope, isWebOrigin, issueCouponRequestSchema, issueCouponResultSchema, liveGateConditionCodeSchema, liveGateConditionSchema, liveGateErrorDetailsSchema, liveGateSchema, loginProviderSchema, loginProviderStatusSchema, loginProviderTestSchema, loginSettingsSchema, maskedSecretSchema, mcpRuleResultSchema, mcpToolEventSchema, moveStorefrontFileRequestSchema, myAccountSchema, myStoreSchema, normalizeCouponCode, normalizeStorefrontHostname, normalizeStorefrontPath, oauthClientRegistrationRequestSchema, oauthClientRegistrationResponseSchema, oauthTokenResponseSchema, openFulfillmentUnion, optionGroupKindSchema, optionGroupRequestSchema, optionGroupSchema, optionModelRequestSchema, optionModelSchema, optionSelectionSchema, optionSortSchema, optionValueRequestSchema, optionValueSchema, orderCanceledWebhookEventDataSchema, orderCanceledWebhookEventSchema, orderDiscountSchema, orderFulfilledWebhookEventDataSchema, orderFulfilledWebhookEventSchema, orderItemFulfillmentSchema, orderItemFulfillmentSnapshotResponseSchema, orderItemFulfillmentSnapshotSchema, orderItemListQuerySchema, orderItemMemoResultSchema, orderItemPageSchema, orderItemSchema, orderItemStatusSchema, orderListQuerySchema, orderSchema, orderSummaryPageSchema, orderSummarySchema, orderWebhookEventDataSchema, orderWebhookEventSchema, pageInfoSchema, pageQueryShape, pageSchema, paymentAttemptRecordSchema, paymentAttemptStatusSchema, paymentCredentialSetSchema, paymentListQuerySchema, paymentProviderSchema, paymentProviderVerificationSchema, paymentRecordDetailSchema, paymentRecordPageSchema, paymentRecordSchema, paymentRecordStatusSchema, paymentRefundRecordSchema, paymentSettingsSchema, pgEnvironmentSchema, pgPaymentMethodSchema, pgProviderSchema, pgVerificationResultSchema, policyDocumentUrlSchema, policyVersionSchema, portOneChannelSchema, portOneCredentialsInputSchema, productCreateRequestSchema, productFulfillmentCreateSchema, productFulfillmentResponseSchema, productFulfillmentSchema, productFulfillmentUpdateSchema, productImageRejection, productImageUrlSchema, productListQuerySchema, productOutOfStockWebhookEventDataSchema, productOutOfStockWebhookEventSchema, productPatchRequestSchema, productRefundPolicyOverride, productRefundPolicyPatchSchema, productReplaceRequestSchema, productSchema, productSortSchema, productStatusSchema, productSummarySchema, productVariantRequestSchema, productVariantSchema, proratedPaidRefundBase, proratedRefundBase, publishStorefrontVersionRequestSchema, purchaseDecisionExtendResultSchema, purchaseDecisionRequestResultSchema, quoteDelivery, quotePricing, refreshUserToken, refundPolicyPatchSchema, refundPolicySchema, rejectClaimRequestSchema, remoteAreaLabelOf, remoteAreaOf, remoteAreaSchema, remoteSurchargeOf, replaceStorefrontSourceRequestSchema, requiresCollection, requiresShipping, resolveRefundPolicy, resolveShippingPolicy, reviewListQuerySchema, reviewReportResultSchema, reviewSchema, reviewSortSchema, reviewWebhookEventDataSchema, reviewWebhookEventSchema, rollbackStorefrontRequestSchema, runStorefrontCheckRequestSchema, salesReportGroupBySchema, salesReportQuerySchema, salesReportRowSchema, salesReportSchema, salesTotalsSchema, saveLoginProviderRequestSchema, savedCustomInputRequestSchema, savedCustomInputSchema, savedOptionGroupRequestSchema, savedOptionGroupSchema, savedOptionSetCompositionSchema, savedOptionSetFromProductRequestSchema, savedOptionSetFromProductResultSchema, savedOptionSetListQuerySchema, savedOptionSetRequestSchema, savedOptionSetSchema, savedOptionSetSortSchema, savedOptionValueRequestSchema, savedOptionValueSchema, scopeAccessSchema, searchStorefrontFilesRequestSchema, sellerAddressListQuerySchema, sellerAddressRequestSchema, sellerAddressSchema, sellerCustomerInquirySchema, setStorefrontVisibilityRequestSchema, settleCouponShortfall, settlementDetailQuerySchema, settlementDetailSchema, settlementListQuerySchema, settlementStatusSchema, settlementSummarySchema, settlementWithDetailsSchema, shippingAddressSchema, shippingPolicyPatchSchema, shippingPolicySchema, storeCodeSchema, storeMemberRoleUpdatedSchema, storeMemberSchema, storeRoleSchema, storeSchema, storeScopeResourceSchema, storeTokenResponseSchema, storefrontBuildDependenciesSchema, storefrontBuildListQuerySchema, storefrontBuildSchema, storefrontBuildStatusSchema, storefrontCheckSchema, storefrontCheckStatusSchema, storefrontCustomDomainStatusSchema, storefrontCustomHostnameProblem, storefrontDependencyFindingSchema, storefrontDnsRecordSchema, storefrontDomainSchema, storefrontDomainTxtNameOf, storefrontEditPreviewLabelOf, storefrontEditPreviewSchema, storefrontEditSessionSchema, storefrontEditSessionStatusSchema, storefrontEditingSchema, storefrontFileConflictDetailsSchema, storefrontFileContentQuerySchema, storefrontFileContentSchema, storefrontFileDeleteResultSchema, storefrontFileEncodingSchema, storefrontFileEntrySchema, storefrontFileHashSchema, storefrontFileListQuerySchema, storefrontFileListSchema, storefrontFileMoveResultSchema, storefrontFilePathSchema, storefrontFileSearchResultSchema, storefrontFileWriteResultSchema, storefrontHostingSchema, storefrontPreviewLinkSchema, storefrontProvisioningSchema, storefrontRuleResultSchema, storefrontRuleSeveritySchema, storefrontSiteDomainSchema, storefrontSiteDomainsSchema, storefrontSiteSchema, storefrontSiteStatusSchema, storefrontSlugAvailabilityQuerySchema, storefrontSlugAvailabilitySchema, storefrontSlugSchema, storefrontSourceArchivePartSchema, storefrontSourceArchiveQuerySchema, storefrontSourceBlobsResultSchema, storefrontSourceModeSchema, storefrontSourceRefSchema, storefrontSourceReplacedSchema, storefrontSourceSchema, storefrontSuspensionSchema, storefrontTemplateSchema, storefrontVerifyReportSchema, storefrontVersionCreatedSchema, storefrontVersionSchema, storefrontVersionStatusSchema, storefrontVisibilitySchema, tossPaymentsCredentialsInputSchema, trackingStageResponseSchema, trackingUpdateResultSchema, updateCouponRequestSchema, updateLoginProviderRequestSchema, updateLoginSettingsRequestSchema, updateMemberRoleRequestSchema, updatePaymentProviderRequestSchema, updatePaymentSettingsRequestSchema, updateStoreRequestSchema, uploadExtensionOf, uploadPurposeSchema, uploadRejection, uploadRequestSchema, uploadStorefrontSourceBlobsRequestSchema, uploadTicketSchema, upsertPortOneProviderRequestSchema, upsertTossPaymentsProviderRequestSchema, userTokenResponseSchema, variantStockRequestSchema, variantStockSchema, verifyPaymentProviderRequestSchema, webhookCreatedSchema, webhookDeliveryDetailSchema, webhookDeliveryErrorCodeSchema, webhookDeliveryListQuerySchema, webhookDeliveryResourceSchema, webhookDeliverySchema, webhookDeliveryStatusSchema, webhookEventSchema, webhookFailureReasonSchema, webhookRedeliverFailedResultSchema, webhookRequestHeaderSchema, webhookRequestSchema, webhookResourceTypeSchema, webhookSchema, webhookSecretRotatedSchema, webhookStatusSchema, webhookTestRequestSchema, webhookTestResultSchema, withdrawCustomerRequestSchema, writeDryRunResultSchema, writePreviewSchema, writeStorefrontFilesRequestSchema };