@sayren/store-sdk 0.18.0 → 0.20.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
@@ -975,7 +975,7 @@ const claimSchema = z.object({
975
975
  carrierCode: z.string().nullable(),
976
976
  trackingNumber: z.string().nullable()
977
977
  }).nullable(),
978
- fulfillmentType: z.string().optional().default("SHIPPING").describe("클레임 대상 주문상품의 상품 유형(이슈 #48). 수거가 없는 비실물(`DOWNLOAD`·`CODE`·`SERVICE`) 반품은 수거 등록 없이 접수·보류에서 바로 승인한다. 값이 추가될 수 있어 문자열로 받는다. 서버는 항상 싣는다"),
978
+ fulfillmentType: z.string().optional().default("SHIPPING").describe("클레임 대상 주문상품의 상품 유형(이슈 #48). 배송 없는 상품(`MANUAL`) 반품은 수거 등록 없이 접수·보류에서 바로 승인한다. 값이 추가될 수 있어 문자열로 받는다. 서버는 항상 싣는다"),
979
979
  requestedAt: z.string(),
980
980
  completedAt: z.string().nullable(),
981
981
  /** 보류 중이면 자동 해제 시각(ISO 8601), 아니면 null */
@@ -2031,140 +2031,6 @@ const customerWithdrawalBlockedDetailsSchema = z.object({
2031
2031
  pendingPayments: z.number().int()
2032
2032
  });
2033
2033
  //#endregion
2034
- //#region src/schemas/digital-asset.ts
2035
- /**
2036
- * 파일 다운로드 상품의 판매 파일 (이슈 #48 단계 3).
2037
- *
2038
- * 파일은 CDN **비공개 객체**다. `POST /v1/uploads`(`purpose: "digital-asset"`)로 자리를 받아 응답의 `headers`와 함께 PUT하고,
2039
- * 응답의 `key`로 상품에 연결한다. 객체 키와 파일 주소는 어떤 응답에도 싣지 않는다 — 구매자는 제공된 주문상품에서
2040
- * 수명 300초의 서명 주소로만 받는다. 주문은 주문 시점 파일을 스냅샷하므로 파일을 바꿔도 이미 산 구매자는 옛 파일을 받는다.
2041
- */
2042
- const digitalAssetAttachRequestSchema = z.object({
2043
- key: z.string().min(1).max(512).describe("업로드 자리 발급 응답의 `key`. 이 스토어의 비공개 업로드 키여야 한다"),
2044
- fileName: z.string().trim().min(1).max(200).describe("구매자에게 보일 파일 이름(확장자 포함)"),
2045
- sizeBytes: z.number().int().positive().optional().describe("파일 크기(바이트). 안내용이다"),
2046
- contentType: z.string().max(200).optional().describe("파일 형식(MIME). 안내용이다"),
2047
- variantId: z.string().optional().describe("이 파일을 줄 옵션(variant). 생략하면 모든 옵션의 구매자가 받는다")
2048
- });
2049
- /** 판매 파일 — 객체 키·주소는 싣지 않는다 */
2050
- const digitalAssetSchema = z.object({
2051
- assetId: z.string(),
2052
- productId: z.string(),
2053
- variantId: z.string().nullable().describe("옵션 전용 파일이면 그 옵션, 모든 옵션 공통이면 null"),
2054
- fileName: z.string(),
2055
- contentType: z.string().nullable(),
2056
- sizeBytes: z.number().int().nullable(),
2057
- status: z.string().describe("`ACTIVE`(새 주문에 제공)·`REMOVED`(해제 — 이미 산 구매자는 계속 받는다). 값이 추가될 수 있어 문자열로 받는다"),
2058
- createdAt: z.string()
2059
- });
2060
- /** 셀러 확인용 서명 주소 — `POST /v1/products/{productId}/digital-assets/{assetId}/preview-url` */
2061
- const digitalAssetPreviewSchema = z.object({
2062
- url: z.url().describe("수명 300초의 서명 주소"),
2063
- expiresAt: z.string()
2064
- });
2065
- /** 주문상품의 다운로드 제공 정보 — 주문 응답의 `fulfillment.download` */
2066
- const orderItemDownloadSchema = z.object({
2067
- count: z.number().int().describe("지금까지 받은 횟수"),
2068
- limit: z.number().int().describe("받을 수 있는 횟수"),
2069
- expiresAt: z.string().nullable().describe("받을 수 있는 기한 (ISO 8601)"),
2070
- firstDownloadedAt: z.string().nullable().describe("처음 받은 시각 — 제공 개시 증거다"),
2071
- files: z.array(z.object({
2072
- assetId: z.string(),
2073
- fileName: z.string(),
2074
- sizeBytes: z.number().int().nullable()
2075
- }))
2076
- });
2077
- //#endregion
2078
- //#region src/schemas/fulfillment-code.ts
2079
- /**
2080
- * 코드 발급 상품의 코드 풀 (이슈 #48 단계 2) — 라이선스 키·기프트 코드처럼 결제 확정과 함께 한 개씩 배정하는 코드다.
2081
- *
2082
- * **평문 코드는 두 경로로만 나간다** — 구매자 열람(`POST /storefront/v1/me/order-items/{id}/fulfillment/reveal`)과 셀러 콘솔
2083
- * 열람(`POST /v1/fulfillment-codes/{codeId}/reveal`, 콘솔 전용). 목록·주문·웹훅·API 로그에는 가린 값(`hint`)만 싣는다.
2084
- * 코드는 결제 자격 증명과 같은 키링·AES-256-GCM으로 암호화해 저장한다.
2085
- */
2086
- /**
2087
- * 코드 상태.
2088
- * - `AVAILABLE` 판매 가능 — 재고로 센다
2089
- * - `ASSIGNED` 주문상품에 배정됨
2090
- * - `REVOKED` 환불로 회수됨(구매자 화면에서 숨긴다. 코드 자체의 무효화는 셀러 시스템 몫이다)
2091
- * - `EXPIRED` 유효기간이 지나 배정하지 않음
2092
- * - `VOID` 셀러가 폐기함
2093
- */
2094
- const fulfillmentCodeStatusSchema = z.enum([
2095
- "AVAILABLE",
2096
- "ASSIGNED",
2097
- "REVOKED",
2098
- "EXPIRED",
2099
- "VOID"
2100
- ]);
2101
- /** 한 번에 올릴 수 있는 코드 수 */
2102
- const MAX_CODES_PER_UPLOAD = 1e4;
2103
- /** 코드 한 개의 최대 길이 */
2104
- const MAX_CODE_LENGTH = 200;
2105
- /**
2106
- * 코드 일괄 업로드 요청 — `POST /v1/products/{productId}/codes`. `codes`(배열)와 `csv`(본문) 중 하나를 보낸다.
2107
- * 앞뒤 공백은 지우고 빈 줄은 건너뛴다. 같은 상품에 이미 있는 코드와 요청 안의 중복은 받지 않는다(`duplicates`).
2108
- */
2109
- const fulfillmentCodeUploadRequestSchema = z.object({
2110
- variantId: z.string().optional().describe("코드를 채울 옵션(variant) ID. 옵션 없는 상품은 생략한다(기본 옵션)"),
2111
- codes: z.array(z.string()).max(MAX_CODES_PER_UPLOAD).optional().describe(`코드 목록. 한 번에 ${MAX_CODES_PER_UPLOAD}개까지. \`csv\`와 둘 중 하나만 보낸다`),
2112
- csv: z.string().max(4e6).optional().describe(`CSV 본문 — 줄마다 첫 칸이 코드다. 첫 줄의 첫 칸이 \`code\`면 머리글로 보고 건너뛴다. 한 번에 ${MAX_CODES_PER_UPLOAD}줄까지`),
2113
- expiresAt: z.iso.datetime({ offset: true }).optional().describe("이 업로드 코드의 유효기간 (ISO 8601). 지나면 배정하지 않고 만료로 바꾼다")
2114
- }).refine((value) => value.codes === void 0 !== (value.csv === void 0), { message: "codes와 csv 중 하나만 보내야 합니다" });
2115
- /** 받지 않은 줄 */
2116
- const fulfillmentCodeRejectionSchema = z.object({
2117
- line: z.number().int().describe("요청 안의 위치 (1부터). CSV는 머리글을 포함한 줄 번호다"),
2118
- reason: z.string().describe("사유. `DUPLICATE`(이미 있는 코드이거나 요청 안에서 겹침)·`TOO_LONG`(200자 초과)·`INVALID_CHARACTER`(제어 문자). 값이 추가될 수 있어 문자열로 받는다")
2119
- });
2120
- /** 업로드 결과 — 배치 요약 */
2121
- const fulfillmentCodeBatchSchema = z.object({
2122
- batchId: z.string(),
2123
- productId: z.string(),
2124
- variantId: z.string(),
2125
- total: z.number().int().describe("요청에서 읽은 코드 수(빈 줄·머리글 제외)"),
2126
- accepted: z.number().int().describe("받은 코드 수 — 재고가 이만큼 는다"),
2127
- duplicates: z.number().int().describe("중복이라 받지 않은 코드 수"),
2128
- rejected: z.array(fulfillmentCodeRejectionSchema).describe("받지 않은 줄(중복 포함). 최대 1,000개까지 싣는다"),
2129
- expiresAt: z.string().nullable(),
2130
- createdAt: z.string()
2131
- });
2132
- /** 코드 목록의 한 줄 — **가린 값만** 싣는다 */
2133
- const fulfillmentCodeSchema = z.object({
2134
- codeId: z.string(),
2135
- productId: z.string(),
2136
- variantId: z.string(),
2137
- batchId: z.string().nullable().describe("업로드 배치. 수동 제공으로 셀러가 넘긴 코드는 null"),
2138
- hint: z.string().describe("가린 코드 — 끝 4자만 보인다(예: `****AB12`)"),
2139
- status: z.string().describe("코드 상태. `AVAILABLE`·`ASSIGNED`·`REVOKED`·`EXPIRED`·`VOID`. 값이 추가될 수 있어 문자열로 받는다"),
2140
- orderItemId: z.string().nullable().describe("배정된 주문상품. 배정 전이면 null"),
2141
- assignedAt: z.string().nullable(),
2142
- revealedAt: z.string().nullable().describe("구매자가 처음 열람한 시각 — 제공 개시 증거다"),
2143
- expiresAt: z.string().nullable(),
2144
- createdAt: z.string()
2145
- });
2146
- const fulfillmentCodePageSchema = pageSchema(fulfillmentCodeSchema).extend({ available: z.number().int().describe("판매 가능한 코드 수(필터와 무관한 상품 전체)") });
2147
- /** `GET /v1/products/{productId}/codes` 쿼리 */
2148
- const fulfillmentCodeListQuerySchema = z.object({
2149
- status: fulfillmentCodeStatusSchema.optional().describe("상태 필터"),
2150
- variantId: z.string().optional().describe("옵션 필터"),
2151
- batchId: z.string().optional().describe("업로드 배치 필터"),
2152
- orderItemId: z.string().optional().describe("주문상품 필터"),
2153
- ...pageQueryShape()
2154
- });
2155
- /** 셀러 콘솔 코드 열람 응답 — `POST /v1/fulfillment-codes/{codeId}/reveal` */
2156
- const fulfillmentCodeRevealSchema = z.object({
2157
- codeId: z.string(),
2158
- code: z.string().describe("코드 평문. 응답은 캐시하지 않는다(`Cache-Control: no-store`)")
2159
- });
2160
- /** 주문상품에 배정된 코드의 가린 값 — 주문 응답의 `fulfillment.codes` */
2161
- const orderItemCodeSchema = z.object({
2162
- codeId: z.string(),
2163
- hint: z.string().describe("가린 코드 — 끝 4자만 보인다"),
2164
- status: z.string().describe("`ASSIGNED`(유효)·`REVOKED`(환불로 회수). 값이 추가될 수 있어 문자열로 받는다"),
2165
- revealedAt: z.string().nullable().describe("구매자가 처음 열람한 시각. 열람 전이면 null")
2166
- });
2167
- //#endregion
2168
2034
  //#region src/schemas/login.ts
2169
2035
  /** 소셜 로그인 공급자 */
2170
2036
  const loginProviderSchema = z.enum([
@@ -2507,83 +2373,43 @@ const reviewReportResultSchema = z.object({
2507
2373
  //#endregion
2508
2374
  //#region src/schemas/fulfillment.ts
2509
2375
  /**
2510
- * 상품 유형과 제공 방식 (이슈 #48) — 배송이 필요 없는 상품을 팔기 위한 축이다.
2376
+ * 상품 유형 (이슈 #48·#57) — 배송이 필요한 상품과 필요 없는 상품을 가르는 축이다.
2511
2377
  *
2512
- * **상품 유형은 제공 방식 하나로 표현한다.** `fulfillmentType` 한 필드가 배송 필요 여부, 이행 흐름, 재고 의미를 정한다.
2378
+ * **상품 유형은 이행 방식 하나로 표현한다.** `fulfillment.type`이 배송 필요 여부, 이행 흐름, 클레임 규칙을 정한다.
2513
2379
  * 코드 곳곳에서 유형 이름으로 분기하지 않고 이 파일의 판정 함수(`requiresShipping`·`fulfillmentRuleOf`)를 지난다.
2514
- * 유형이 늘어나도 판정표 한 곳만 고치면 배송비 계산·결제 시작·클레임·콘솔 버튼이 같이 따라온다.
2515
2380
  *
2516
2381
  * 주문상품 상태 enum은 바꾸지 않는다. 비실물의 **제공 완료는 `DELIVERED`를 재사용한다** — 새 상태를 더하면 기존 소비자의
2517
- * 상태 분기(배송완료 뒤 구매확정 대기, 반품 가능 판정)가 새 값을 모른다. 표시는 `fulfillmentType`으로 가른다.
2382
+ * 상태 분기(배송완료 뒤 구매확정 대기, 반품 가능 판정)가 새 값을 모른다. 표시는 유형으로 가른다.
2518
2383
  */
2519
2384
  /**
2520
2385
  * 상품 유형.
2521
- * - `SHIPPING` 배송 상품(기본, 이 필드 이전의 상품 전부)
2522
- * - `DOWNLOAD` 파일 다운로드
2523
- * - `CODE` 코드 발급(라이선스 키·기프트 코드)
2524
- * - `SERVICE` 이용권·상품권·서비스(예약 없음)
2525
- * - `PICKUP` 방문 수령 실물
2386
+ * - `SHIPPING` 배송 상품. 택배로 발송하고 송장·배송 추적으로 배송완료가 된다
2387
+ * - `MANUAL` 배송 없는 상품(비실물). 셀러가 제공 처리(`POST /v1/order-items/{orderItemId}/fulfill`)로 제공 완료를 기록한다
2388
+ *
2389
+ * 비실물의 세부 구분(파일 다운로드·코드 발급·이용권·방문 수령)은 2026-09-28에 제품에서 뺐다(이슈 #48 범위 축소).
2390
+ * 이름은 이행 방식이다 — `SHIPPING`이 택배 발송이듯 `MANUAL`은 셀러가 직접 제공한다는 뜻이다.
2526
2391
  */
2527
- const fulfillmentTypeSchema = z.enum([
2528
- "SHIPPING",
2529
- "DOWNLOAD",
2530
- "CODE",
2531
- "SERVICE",
2532
- "PICKUP"
2533
- ]);
2534
- /** 제공 방식 — `AUTO`는 결제 확정과 함께 제공하고, `MANUAL`은 셀러가 제공 처리한다 */
2535
- const fulfillmentModeSchema = z.enum(["AUTO", "MANUAL"]);
2392
+ const fulfillmentTypeSchema = z.enum(["SHIPPING", "MANUAL"]);
2536
2393
  /**
2537
2394
  * 응답 스키마용 유형 — 서버가 나중에 유형을 더해도 옛 SDK가 응답 파싱에서 깨지지 않게 문자열로 받는다.
2538
2395
  * 요청은 `fulfillmentTypeSchema`(닫힌 enum)로 검증한다.
2539
2396
  */
2540
- const fulfillmentTypeResponseSchema = z.string().describe("상품 유형. 현재 값은 `SHIPPING`(배송 상품)·`DOWNLOAD`(파일 다운로드)·`CODE`(코드 발급)·`SERVICE`(이용권·서비스)·`PICKUP`(방문 수령)이고 값이 추가될 수 있어 문자열로 받는다");
2541
- /**
2542
- * 유형별 규칙표 — **유일한 원천이다.**
2543
- *
2544
- * `PICKUP`은 실물이라 반품 수거와 교환을 받지만 택배 배송은 없다. `DOWNLOAD`는 결제 즉시 권한을 발급하므로 `AUTO`만,
2545
- * `PICKUP`은 셀러가 수령 완료를 누르므로 `MANUAL`만 둔다.
2546
- */
2397
+ const fulfillmentTypeResponseSchema = z.string().describe("상품 유형. 현재 값은 `SHIPPING`(배송 상품)·`MANUAL`(배송 없는 상품, 셀러가 제공 처리)이고 값이 추가될 수 있어 문자열로 받는다");
2398
+ /** 유형별 규칙표 — **유일한 원천이다.** */
2547
2399
  const FULFILLMENT_RULES = {
2548
2400
  SHIPPING: {
2549
2401
  requiresShipping: true,
2550
- modes: [],
2551
- defaultMode: null,
2552
2402
  requiresCollection: true,
2553
2403
  exchangeSupported: true
2554
2404
  },
2555
- DOWNLOAD: {
2556
- requiresShipping: false,
2557
- modes: ["AUTO"],
2558
- defaultMode: "AUTO",
2559
- requiresCollection: false,
2560
- exchangeSupported: false
2561
- },
2562
- CODE: {
2563
- requiresShipping: false,
2564
- modes: ["AUTO", "MANUAL"],
2565
- defaultMode: "AUTO",
2566
- requiresCollection: false,
2567
- exchangeSupported: false
2568
- },
2569
- SERVICE: {
2405
+ MANUAL: {
2570
2406
  requiresShipping: false,
2571
- modes: ["MANUAL", "AUTO"],
2572
- defaultMode: "MANUAL",
2573
2407
  requiresCollection: false,
2574
2408
  exchangeSupported: false
2575
- },
2576
- PICKUP: {
2577
- requiresShipping: false,
2578
- modes: ["MANUAL"],
2579
- defaultMode: "MANUAL",
2580
- requiresCollection: true,
2581
- exchangeSupported: true
2582
2409
  }
2583
2410
  };
2584
2411
  /**
2585
- * 저장된 값 → 유형. null·모르는 값은 `SHIPPING`이다 — 이 필드 이전의 주문상품(`order_items.fulfillment_type` null)과
2586
- * 상품은 모두 배송 상품이었다.
2412
+ * 저장된 값 → 유형. null·모르는 값은 `SHIPPING`이다 — 이 필드 이전의 상품·주문상품은 모두 배송 상품이었다.
2587
2413
  */
2588
2414
  function fulfillmentTypeOf(value) {
2589
2415
  const parsed = fulfillmentTypeSchema.safeParse(value);
@@ -2602,16 +2428,6 @@ function requiresCollection(type) {
2602
2428
  return fulfillmentRuleOf(type).requiresCollection;
2603
2429
  }
2604
2430
  /**
2605
- * 유효 제공 방식 — 요청 값이 유형에 허용되면 그 값, 생략이면 유형 기본값이다. 배송 상품은 null이다.
2606
- * 허용되지 않는 값은 `null`을 돌려주지 않고 호출부가 거부하도록 `undefined`를 돌려준다.
2607
- */
2608
- function resolveFulfillmentMode(type, mode) {
2609
- const rule = FULFILLMENT_RULES[type];
2610
- if (rule.modes.length === 0) return null;
2611
- if (mode === null || mode === void 0) return rule.defaultMode;
2612
- return rule.modes.includes(mode) ? mode : void 0;
2613
- }
2614
- /**
2615
2431
  * 배송이 없는 주문상품의 배송 묶음 키 (`order_items.delivery_bundle_key`).
2616
2432
  *
2617
2433
  * null로 두면 환불 정산이 #29 이전 주문의 레거시 폴백(「묶음 = 같은 상품」, 상품의 **현재** 배송비)을 탄다.
@@ -2625,16 +2441,10 @@ const deliveryTypeSchema = z.enum([
2625
2441
  "PAID",
2626
2442
  "CONDITIONAL_FREE"
2627
2443
  ]);
2628
- /** 파일 다운로드 기본값 — 횟수 5회·기간 30일(#48 결정 8) */
2629
- const DOWNLOAD_DEFAULTS = {
2630
- limit: 5,
2631
- days: 30
2632
- };
2633
2444
  const KNOWN_FULFILLMENT_TYPES = fulfillmentTypeSchema.options;
2634
2445
  const ADDRESS_ID_DESCRIPTIONS = {
2635
2446
  shipping: "출고지 주소 id(`GET /v1/seller-addresses?type=SHIPPING`). 등록에서 생략하면 그 시점 스토어 기본 출고지를 저장한다. `null`이면 비워 둔다(스토어 기본 출고지를 따른다). 이 스토어의 `SHIPPING` 주소가 아니면 거부한다",
2636
- return: "반품지 주소 id(`GET /v1/seller-addresses?type=RETURN`). 등록에서 생략하면 그 시점 스토어 기본 반품지를 저장한다. `null`이면 비워 둔다(스토어 기본 반품지를 따른다). 이 스토어의 `RETURN` 주소가 아니면 거부한다",
2637
- pickup: "방문 수령지 주소 id(`GET /v1/seller-addresses?type=PICKUP`). 등록에서 생략하면 그 시점 스토어 기본 방문 수령지를 저장하고, 기본 방문 수령지도 없으면 400 `PICKUP_ADDRESS_REQUIRED`다. 이 스토어의 `PICKUP` 주소가 아니면 거부한다"
2447
+ return: "반품지 주소 id(`GET /v1/seller-addresses?type=RETURN`). 등록에서 생략하면 그 시점 스토어 기본 반품지를 저장한다. `null`이면 비워 둔다(스토어 기본 반품지를 따른다). 이 스토어의 `RETURN` 주소가 아니면 거부한다"
2638
2448
  };
2639
2449
  /**
2640
2450
  * 배송비 필드 사이의 일관성 — FREE의 배송비, 조건부 무료가 아닌 상품의 무료 기준을 조용히 0·null로 바꿔 저장하지 않고 400으로 돌려준다
@@ -2667,51 +2477,13 @@ const shippingCreateSchema = z.strictObject({
2667
2477
  shippingAddressId: z.string().nullable().optional().describe(ADDRESS_ID_DESCRIPTIONS.shipping),
2668
2478
  returnAddressId: z.string().nullable().optional().describe(ADDRESS_ID_DESCRIPTIONS.return)
2669
2479
  }).superRefine(refineShippingConsistency);
2670
- const downloadCreateSchema = z.strictObject({
2671
- limit: z.number().int().min(1).max(100).default(DOWNLOAD_DEFAULTS.limit).describe("다운로드 횟수 한도(수량 1개당). 생략하면 5회"),
2672
- days: z.number().int().min(1).max(3650).default(DOWNLOAD_DEFAULTS.days).describe("다운로드 기간(제공일부터 일수). 생략하면 30일"),
2673
- stockTracking: z.boolean().default(false).describe("재고를 세는가. 생략하면 false(재고와 무관하게 판다)"),
2674
- withdrawalRestricted: z.boolean().default(false).describe("청약철회 제한 대상인가. true면 주문서가 제한 고지 동의를 받고, 첫 다운로드 뒤 구매자 단순 변심 취소·반품을 받지 않는다")
2675
- });
2676
- const codeCreateSchema = z.strictObject({
2677
- mode: fulfillmentModeSchema.default("AUTO").describe("제공 방식. `AUTO`는 코드 풀에서 결제 확정과 함께 배정하고, `MANUAL`은 셀러가 제공 처리 때 코드를 넘긴다"),
2678
- withdrawalRestricted: z.boolean().default(false).describe("청약철회 제한 대상인가. true면 첫 열람 뒤 구매자 단순 변심 취소·반품을 받지 않는다")
2679
- });
2680
- const serviceCreateSchema = z.strictObject({ mode: fulfillmentModeSchema.default("MANUAL").describe("제공 방식. `AUTO`는 결제 확정과 함께 제공 완료, `MANUAL`은 셀러가 제공 처리한다") });
2681
- const pickupCreateSchema = z.strictObject({ addressId: z.string().optional().describe(ADDRESS_ID_DESCRIPTIONS.pickup) });
2682
2480
  /**
2683
- * 상품 이행 — 등록(POST). 유형 키를 생략하면 그 유형의 기본값이다(`{ "type": "DOWNLOAD" }`만으로 등록할 수 있다).
2481
+ * 상품 이행 — 등록(POST). 배송 상품의 `shipping`을 생략하면 기본값(유료 배송·스토어 기본 배송비·기본 출고지·반품지)이다.
2684
2482
  */
2685
- const productFulfillmentCreateSchema = z.discriminatedUnion("type", [
2686
- z.strictObject({
2687
- type: z.literal("SHIPPING"),
2688
- shipping: shippingCreateSchema.optional()
2689
- }),
2690
- z.strictObject({
2691
- type: z.literal("DOWNLOAD"),
2692
- download: downloadCreateSchema.default({
2693
- limit: DOWNLOAD_DEFAULTS.limit,
2694
- days: DOWNLOAD_DEFAULTS.days,
2695
- stockTracking: false,
2696
- withdrawalRestricted: false
2697
- })
2698
- }),
2699
- z.strictObject({
2700
- type: z.literal("CODE"),
2701
- code: codeCreateSchema.default({
2702
- mode: "AUTO",
2703
- withdrawalRestricted: false
2704
- })
2705
- }),
2706
- z.strictObject({
2707
- type: z.literal("SERVICE"),
2708
- service: serviceCreateSchema.default({ mode: "MANUAL" })
2709
- }),
2710
- z.strictObject({
2711
- type: z.literal("PICKUP"),
2712
- pickup: pickupCreateSchema.default({})
2713
- })
2714
- ]);
2483
+ const productFulfillmentCreateSchema = z.discriminatedUnion("type", [z.strictObject({
2484
+ type: z.literal("SHIPPING"),
2485
+ shipping: shippingCreateSchema.optional()
2486
+ }), z.strictObject({ type: z.literal("MANUAL") })]);
2715
2487
  const shippingUpdateSchema = z.strictObject({
2716
2488
  deliveryType: deliveryTypeSchema.describe(deliveryTypeDescription),
2717
2489
  deliveryFee: z.number().int().min(0).describe("배송비. 원 단위. FREE면 0"),
@@ -2723,76 +2495,24 @@ const shippingUpdateSchema = z.strictObject({
2723
2495
  * 상품 이행 — 수정(PUT·PATCH). 통째 교체이고 유형 키와 그 안의 필드가 **모두 필수**다. 서버는 현재 값과 병합하지 않는다.
2724
2496
  * `fulfillment` 자체를 생략하면 이행을 바꾸지 않는다.
2725
2497
  */
2726
- const productFulfillmentUpdateSchema = z.discriminatedUnion("type", [
2727
- z.strictObject({
2728
- type: z.literal("SHIPPING"),
2729
- shipping: shippingUpdateSchema
2730
- }),
2731
- z.strictObject({
2732
- type: z.literal("DOWNLOAD"),
2733
- download: z.strictObject({
2734
- limit: z.number().int().min(1).max(100),
2735
- days: z.number().int().min(1).max(3650),
2736
- stockTracking: z.boolean(),
2737
- withdrawalRestricted: z.boolean()
2738
- })
2739
- }),
2740
- z.strictObject({
2741
- type: z.literal("CODE"),
2742
- code: z.strictObject({
2743
- mode: fulfillmentModeSchema,
2744
- withdrawalRestricted: z.boolean()
2745
- })
2746
- }),
2747
- z.strictObject({
2748
- type: z.literal("SERVICE"),
2749
- service: z.strictObject({ mode: fulfillmentModeSchema })
2750
- }),
2751
- z.strictObject({
2752
- type: z.literal("PICKUP"),
2753
- pickup: z.strictObject({ addressId: z.string().describe(ADDRESS_ID_DESCRIPTIONS.pickup) })
2754
- })
2755
- ]);
2498
+ const productFulfillmentUpdateSchema = z.discriminatedUnion("type", [z.strictObject({
2499
+ type: z.literal("SHIPPING"),
2500
+ shipping: shippingUpdateSchema
2501
+ }), z.strictObject({ type: z.literal("MANUAL") })]);
2756
2502
  /**
2757
2503
  * 저장된 상품 이행 — 알려진 유형의 닫힌 유니온. 서버의 상품 엔티티와 응답이 이 모양을 쓴다.
2758
2504
  * 응답 파싱은 `productFulfillmentResponseSchema`(열린 유니온)를 쓴다.
2759
2505
  */
2760
- const productFulfillmentSchema = z.discriminatedUnion("type", [
2761
- z.object({
2762
- type: z.literal("SHIPPING"),
2763
- shipping: z.object({
2764
- deliveryType: deliveryTypeSchema,
2765
- deliveryFee: z.number().int().describe("배송비. 원 단위. FREE면 0"),
2766
- conditionalFreeAmount: z.number().int().nullable().describe("조건부 무료 기준 묶음 소계. CONDITIONAL_FREE가 아니면 null"),
2767
- shippingAddressId: z.string().nullable().describe("출고지 주소 id. null이면 스토어 기본 출고지를 따른다"),
2768
- returnAddressId: z.string().nullable().describe("반품지 주소 id. null이면 스토어 기본 반품지를 따른다")
2769
- })
2770
- }),
2771
- z.object({
2772
- type: z.literal("DOWNLOAD"),
2773
- download: z.object({
2774
- limit: z.number().int().describe("다운로드 횟수 한도(수량 1개당)"),
2775
- days: z.number().int().describe("다운로드 기간(제공일부터 일수)"),
2776
- stockTracking: z.boolean().describe("재고를 세는가"),
2777
- withdrawalRestricted: z.boolean().describe("청약철회 제한 대상인가")
2778
- })
2779
- }),
2780
- z.object({
2781
- type: z.literal("CODE"),
2782
- code: z.object({
2783
- mode: fulfillmentModeSchema.describe("제공 방식"),
2784
- withdrawalRestricted: z.boolean().describe("청약철회 제한 대상인가")
2785
- })
2786
- }),
2787
- z.object({
2788
- type: z.literal("SERVICE"),
2789
- service: z.object({ mode: fulfillmentModeSchema.describe("제공 방식") })
2790
- }),
2791
- z.object({
2792
- type: z.literal("PICKUP"),
2793
- pickup: z.object({ addressId: z.string().nullable().describe("방문 수령지 주소 id. null은 수령지가 지워진 상품이다(수령지를 다시 지정할 때까지 팔 수 없다)") })
2506
+ const productFulfillmentSchema = z.discriminatedUnion("type", [z.object({
2507
+ type: z.literal("SHIPPING"),
2508
+ shipping: z.object({
2509
+ deliveryType: deliveryTypeSchema,
2510
+ deliveryFee: z.number().int().describe("배송비. 원 단위. FREE면 0"),
2511
+ conditionalFreeAmount: z.number().int().nullable().describe("조건부 무료 기준 묶음 소계. CONDITIONAL_FREE가 아니면 null"),
2512
+ shippingAddressId: z.string().nullable().describe("출고지 주소 id. null이면 스토어 기본 출고지를 따른다"),
2513
+ returnAddressId: z.string().nullable().describe("반품지 주소 id. null이면 스토어 기본 반품지를 따른다")
2794
2514
  })
2795
- ]);
2515
+ }), z.object({ type: z.literal("MANUAL") })]);
2796
2516
  /**
2797
2517
  * 열린 응답 유니온 — 알려진 유형은 판별 유니온으로 검사하고, **모르는 유형만** `{ type: "OTHER", rawType }`로 읽는다.
2798
2518
  *
@@ -2806,72 +2526,18 @@ function openFulfillmentUnion(known) {
2806
2526
  }))]);
2807
2527
  }
2808
2528
  const productFulfillmentResponseSchema = openFulfillmentUnion(productFulfillmentSchema).describe("상품 이행. `type`이 유형이고 같은 이름의 키가 유형별 속성이다. 값이 추가될 수 있어 SDK는 모르는 유형을 `{ type: \"OTHER\", rawType }`로 읽는다");
2809
- /** 제공 방식 — CODE·SERVICE는 저장된 값, DOWNLOAD는 AUTO, PICKUP은 MANUAL, 배송 상품은 null */
2810
- function fulfillmentModeOf(fulfillment) {
2811
- switch (fulfillment.type) {
2812
- case "CODE": return fulfillment.code.mode;
2813
- case "SERVICE": return fulfillment.service.mode;
2814
- default: return FULFILLMENT_RULES[fulfillment.type].defaultMode;
2815
- }
2816
- }
2817
- /** 재고를 세는가 — 파일 다운로드만 끌 수 있다 */
2818
- function stockTrackingOf(fulfillment) {
2819
- return fulfillment.type === "DOWNLOAD" ? fulfillment.download.stockTracking : true;
2820
- }
2821
- /** 청약철회 제한 대상인가 — 파일 다운로드·코드 발급만 켤 수 있다(전자상거래법 제17조 제2항 제5호, 디지털 콘텐츠) */
2822
- function withdrawalRestrictedOf(fulfillment) {
2823
- if (fulfillment.type === "DOWNLOAD") return fulfillment.download.withdrawalRestricted;
2824
- if (fulfillment.type === "CODE") return fulfillment.code.withdrawalRestricted;
2825
- return false;
2826
- }
2827
- /** 코드 풀을 쓰는가 — 코드 발급 + 자동 제공. 재고가 판매 가능 코드 수다 */
2828
- function usesCodePool(fulfillment) {
2829
- return fulfillment.type === "CODE" && fulfillment.code.mode === "AUTO";
2830
- }
2831
- /** 결제 확정과 함께 제공까지 끝내는가 — 비실물 + 자동 제공 */
2832
- function autoFulfillableOf(fulfillment) {
2833
- return !FULFILLMENT_RULES[fulfillment.type].requiresShipping && fulfillmentModeOf(fulfillment) === "AUTO";
2834
- }
2835
2529
  /**
2836
2530
  * 주문상품의 주문 시점 이행 — 결제 확정이 주문서 줄에서 굳힌다. 상품의 현재 값이 아니다. 서버는 주문상품 컬럼
2837
- * (`fulfillment_type`·`delivery_bundle_key`·`stock_tracked`·`withdrawal_restricted`)과 jsonb(`fulfillment_snapshot`)를 합쳐 이 모양을 만든다.
2838
- *
2839
- * `mode`는 **주문 시점 상품의 방식**이다. 실제 제공 방식은 제공 기록(`fulfillment.mode`)에 있고 다를 수 있다(자동 제공 상품의 수동 복구).
2531
+ * (`fulfillment_type`·`delivery_bundle_key`)과 jsonb(`fulfillment_snapshot`)를 합쳐 이 모양을 만든다.
2840
2532
  */
2841
- const orderItemFulfillmentSnapshotSchema = z.discriminatedUnion("type", [
2842
- z.object({
2843
- type: z.literal("SHIPPING"),
2844
- shipping: z.object({
2845
- bundleKey: z.string().nullable().describe("배송 묶음 키. 같은 키의 주문상품이 배송비를 한 번 낸 한 묶음이다. #29 이전 주문은 null"),
2846
- returnAddressId: z.string().nullable().describe("주문 시점 반품지 주소 id. null이거나 지워진 주소면 스토어 기본 반품지로 안내한다")
2847
- })
2848
- }),
2849
- z.object({
2850
- type: z.literal("DOWNLOAD"),
2851
- download: z.object({
2852
- limit: z.number().int().describe("주문 시점 다운로드 횟수 한도(수량 1개당)"),
2853
- days: z.number().int().describe("주문 시점 다운로드 기간(제공일부터 일수)"),
2854
- stockTracked: z.boolean().describe("주문 시점에 재고를 뺐는가"),
2855
- withdrawalRestricted: z.boolean().describe("구매자가 제한 고지에 동의한 청약철회 제한 주문상품인가")
2856
- })
2857
- }),
2858
- z.object({
2859
- type: z.literal("CODE"),
2860
- code: z.object({
2861
- mode: z.string().describe("주문 시점 상품의 제공 방식. 현재 값은 `AUTO`·`MANUAL`"),
2862
- withdrawalRestricted: z.boolean().describe("구매자가 제한 고지에 동의한 청약철회 제한 주문상품인가")
2863
- })
2864
- }),
2865
- z.object({
2866
- type: z.literal("SERVICE"),
2867
- service: z.object({ mode: z.string().describe("주문 시점 상품의 제공 방식. 현재 값은 `AUTO`·`MANUAL`") })
2868
- }),
2869
- z.object({
2870
- type: z.literal("PICKUP"),
2871
- pickup: z.object({ addressId: z.string().nullable().describe("주문 시점 방문 수령지 주소 id") })
2533
+ const orderItemFulfillmentSnapshotSchema = z.discriminatedUnion("type", [z.object({
2534
+ type: z.literal("SHIPPING"),
2535
+ shipping: z.object({
2536
+ bundleKey: z.string().nullable().describe("배송 묶음 키. 같은 키의 주문상품이 배송비를 한 번 낸 한 묶음이다. #29 이전 주문은 null"),
2537
+ returnAddressId: z.string().nullable().describe("주문 시점 반품지 주소 id. null이거나 지워진 주소면 스토어 기본 반품지로 안내한다")
2872
2538
  })
2873
- ]);
2874
- const orderItemFulfillmentSnapshotResponseSchema = openFulfillmentUnion(orderItemFulfillmentSnapshotSchema).describe("주문 시점 이행 스냅샷. 상품을 나중에 고쳐도 바뀌지 않는다. 실제 제공 기록은 `fulfillment`에 있다. 값이 추가될 수 있어 SDK는 모르는 유형을 `OTHER`로 읽는다");
2539
+ }), z.object({ type: z.literal("MANUAL") })]);
2540
+ const orderItemFulfillmentSnapshotResponseSchema = openFulfillmentUnion(orderItemFulfillmentSnapshotSchema).describe("주문 시점 이행 스냅샷. 상품을 나중에 고쳐도 바뀌지 않는다. 제공 기록은 `fulfillment`에 있다. 값이 추가될 수 있어 SDK는 모르는 유형을 `OTHER`로 읽는다");
2875
2541
  //#endregion
2876
2542
  //#region src/schemas/payment.ts
2877
2543
  /**
@@ -3240,22 +2906,13 @@ const shippingAddressSchema = z.object({
3240
2906
  deliveryMemo: z.string().nullish(),
3241
2907
  entranceCode: z.string().nullish()
3242
2908
  });
3243
- /** 비실물 주문상품의 제공 정보 (이슈 #48) */
2909
+ /** 배송 없는 주문상품(`MANUAL`)의 제공 기록 (이슈 #48) — 셀러의 제공 처리가 만든다 */
3244
2910
  const orderItemFulfillmentSchema = z.object({
3245
- status: z.string().describe("제공 상태. `FULFILLED`(제공함)·`REVOKED`(환불 등으로 회수함). 값이 추가될 수 있어 문자열로 받는다"),
3246
- mode: z.string().nullable().describe("제공 방식. `AUTO`·`MANUAL`"),
2911
+ status: z.string().describe("제공 상태. `FULFILLED`(제공함)·`REVOKED`(전량 취소·환불로 회수함). 값이 추가될 수 있어 문자열로 받는다"),
3247
2912
  fulfilledAt: z.string().describe("제공 시각 (ISO 8601)"),
3248
- note: z.string().nullable().describe("셀러가 남긴 이용 안내. 없으면 null"),
3249
- codes: z.array(orderItemCodeSchema).optional().default([]).describe("코드 발급 상품에 배정한 코드의 가린 값. 평문은 싣지 않는다. 코드 상품이 아니면 빈 배열. 서버는 항상 싣는다"),
3250
- download: orderItemDownloadSchema.nullable().optional().default(null).describe("파일 다운로드 상품의 횟수·기한·파일 목록. 파일 주소는 싣지 않는다. 다른 유형은 null. 서버는 항상 싣는다"),
3251
- mail: z.object({
3252
- status: z.string().describe("제공 안내 메일 상태. 현재 값은 `PENDING`(보낼 예정)·`SENDING`·`SENT`·`FAILED`·`SKIPPED`(보내지 않음: 스토어 설정 꺼짐·받을 주소 없음)이고 값이 추가될 수 있다"),
3253
- sentAt: z.string().nullable(),
3254
- errorCode: z.string().nullable().describe("실패·건너뜀 사유 코드")
3255
- }).nullable().optional().default(null).describe("구매자에게 보낸 제공 안내 메일(이슈 #48). 기록이 없으면 null. 서버는 항상 싣는다")
3256
- });
3257
- /** 제공 안내 메일 재발송 결과 */
3258
- const fulfillmentMailResendSchema = z.object({ mailStatus: z.string().describe("재발송 뒤 메일 상태 — `SENT`·`PENDING`(곧 다시 보낸다)·`FAILED`·`SKIPPED`") });
2913
+ note: z.string().nullable().describe("셀러가 제공 처리 때 남긴 이용 안내. 없으면 null"),
2914
+ mode: z.string().optional().describe("**Deprecated** — 항상 `MANUAL`이다. 자동 제공을 없앴고(이슈 #48) store-sdk 0.19 이하 호환으로만 싣는다. 게시된 연동이 store-sdk 0.20 이상으로 넘어간 뒤 다음 릴리스에서 뺀다")
2915
+ });
3259
2916
  const orderItemSchema = z.object({
3260
2917
  orderItemId: z.string(),
3261
2918
  orderId: z.string(),
@@ -3270,7 +2927,7 @@ const orderItemSchema = z.object({
3270
2927
  optionId: z.string().nullable(),
3271
2928
  optionName: z.string().nullable(),
3272
2929
  sellerCode: z.string().nullable(),
3273
- fulfillmentSnapshot: orderItemFulfillmentSnapshotResponseSchema.describe("주문 시점 이행 스냅샷(이슈 #57). `type`이 주문 시점 상품 유형이다. 배송 상품이 아니면 발송·배송완료 대신 제공 처리를 하고, `DELIVERED`는 제공 완료(방문 수령은 수령 완료)다. 상품을 나중에 고쳐도 바뀌지 않는다. 실제 제공 기록은 `fulfillment`에 있다"),
2930
+ fulfillmentSnapshot: orderItemFulfillmentSnapshotResponseSchema.describe("주문 시점 이행 스냅샷(이슈 #57). `type`이 주문 시점 상품 유형이다. 배송 상품이 아니면 발송·배송완료 대신 제공 처리를 하고, `DELIVERED`는 제공 완료다. 상품을 나중에 고쳐도 바뀌지 않는다. 제공 기록은 `fulfillment`에 있다"),
3274
2931
  quantity: z.number().int().describe("주문 수량"),
3275
2932
  canceledQuantity: z.number().int().describe("취소·반품으로 환불된 누적 수량. 수량 단위 부분 취소가 없었으면 0이다"),
3276
2933
  activeQuantity: z.number().int().describe("살아 있는 수량 (`quantity` − `canceledQuantity`). 0이면 주문상품이 전량 취소됐다"),
@@ -3282,7 +2939,7 @@ const orderItemSchema = z.object({
3282
2939
  reason: z.string(),
3283
2940
  expectedDispatchDate: z.string()
3284
2941
  }).nullable(),
3285
- fulfillment: orderItemFulfillmentSchema.nullable().optional().default(null).describe("비실물 상품의 제공 정보(이슈 #48). 제공 전이거나 배송 상품이면 null이다. 코드 평문·파일 주소는 싣지 않는다. 서버는 항상 싣는다"),
2942
+ fulfillment: orderItemFulfillmentSchema.nullable().optional().default(null).describe("배송 없는 상품의 제공 기록(이슈 #48). 제공 전이거나 배송 상품이면 null이다. 서버는 항상 싣는다"),
3286
2943
  autoDecisionDate: z.string().nullable(),
3287
2944
  sellerMemo: z.string().nullable(),
3288
2945
  orderedAt: z.string(),
@@ -3329,7 +2986,6 @@ const orderSchema = z.object({
3329
2986
  paidAt: z.string().nullable(),
3330
2987
  cashReceipt: cashReceiptSchema.nullable().optional().default(null).describe("현금영수증. 구매자가 계좌이체·가상계좌 결제에서 신청했을 때만 있고 그 밖에는 null. 서버는 항상 싣는다")
3331
2988
  }),
3332
- withdrawalAgreedAt: z.string().nullable().optional().default(null).describe("청약철회 제한 안내에 동의한 시각(이슈 #48). 제한 상품이 없던 주문은 null. 서버는 항상 싣는다"),
3333
2989
  testPayment: z.boolean().describe("테스트 결제(샌드박스 결제) 주문인가. true면 실제로 돈이 오가지 않았습니다. 상품을 발송하지 않도록 주의하십시오"),
3334
2990
  items: z.array(orderItemSchema)
3335
2991
  });
@@ -3349,7 +3005,7 @@ const orderListQuerySchema = z.object({
3349
3005
  orderedTo: z.string().optional().describe("조회 끝 시각 (ISO 8601, 포함). 생략하면 orderedFrom부터 31일. 기간은 최대 31일"),
3350
3006
  status: orderItemStatusSchema.optional().describe("주문상품 상태 필터 — 이 상태의 주문상품이 하나라도 있는 주문"),
3351
3007
  keyword: z.string().optional().describe("주문번호(일치)·주문자명·수령인명(포함) 검색"),
3352
- fulfillmentType: z.string().optional().describe("상품 유형 필터(이슈 #48) — 이 유형(`SHIPPING`·`DOWNLOAD`·`CODE`·`SERVICE`·`PICKUP`)의 주문상품이 하나라도 있는 주문"),
3008
+ fulfillmentType: z.string().optional().describe("상품 유형 필터(이슈 #48) — 이 유형(`SHIPPING`·`MANUAL`)의 주문상품이 하나라도 있는 주문"),
3353
3009
  testPayment: testPaymentQuery,
3354
3010
  ...pageQuery,
3355
3011
  size: z.string().optional().describe("페이지 크기 (기본 20, 최대 100)")
@@ -3452,13 +3108,10 @@ const cancelBySellerRequestSchema = z.object({
3452
3108
  quantity: z.number().int().min(1).optional().describe("취소할 수량. 생략하면 남은 수량 전체다. 남은 수량보다 크면 400 `INVALID_QUANTITY`. 일부만 취소하면 주문상품은 상태를 유지한 채 `canceledQuantity`가 늘고, 전 수량이 소진되면 `CANCELED`가 된다")
3453
3109
  });
3454
3110
  /**
3455
- * 제공 처리 요청 (이슈 #48) — `POST /v1/order-items/{orderItemId}/fulfill`. 수동 제공 비실물 주문상품(이용권·방문 수령 등)을
3111
+ * 제공 처리 요청 (이슈 #48) — `POST /v1/order-items/{orderItemId}/fulfill`. 배송 없는 주문상품(`MANUAL`)을
3456
3112
  * 제공 완료(`DELIVERED`)로 바꾼다.
3457
3113
  */
3458
- const fulfillRequestSchema = z.object({
3459
- note: z.string().trim().max(1e3).optional().describe("구매자에게 보일 이용 안내(예: 예약 방법, 수령 장소). 주문 상세에 실린다"),
3460
- codes: z.array(z.string().trim().min(1).max(200)).min(1).max(100).optional().describe("수동 제공 코드 발급 상품의 코드 — 셀러 시스템이 발급한 코드를 넘긴다. 남은 수량만큼 보낸다(`CODE_COUNT_MISMATCH`). 암호화해 저장하고 구매자가 열람할 때만 평문으로 나간다. 다른 유형은 보내지 않는다")
3461
- });
3114
+ const fulfillRequestSchema = z.object({ note: z.string().trim().max(1e3).optional().describe("구매자에게 보일 이용 안내(예: 예약 방법, 수령 장소). 주문 상세에 실린다") });
3462
3115
  /** 제공 처리 일괄 요청 — `POST /v1/order-items/fulfill` */
3463
3116
  const fulfillBulkRequestSchema = z.object({
3464
3117
  orderItemIds: z.array(z.string()).min(1).max(100),
@@ -3504,14 +3157,8 @@ const addressChangeRequestListSchema = z.array(z.record(z.string(), z.unknown())
3504
3157
  *
3505
3158
  * - `product-image`: 상품 목록·상세의 상품 이미지. 목록 썸네일로 쓰이므로 정지 이미지만 받는다.
3506
3159
  * - `description`: 상세설명 본문에 삽입하는 미디어. 동영상과 svg도 받는다.
3507
- * - `digital-asset`: 파일 다운로드 상품의 판매 파일(이슈 #48). **비공개 객체**로 올라가고 공개 주소가 없다 — 구매자는 제공된
3508
- * 주문상품에서 수명 300초의 서명 주소로만 받는다.
3509
3160
  */
3510
- const uploadPurposeSchema = z.enum([
3511
- "product-image",
3512
- "description",
3513
- "digital-asset"
3514
- ]);
3161
+ const uploadPurposeSchema = z.enum(["product-image", "description"]);
3515
3162
  const MB = 1048576;
3516
3163
  /**
3517
3164
  * 용도별 업로드 제한 — 서버(`POST /v1/uploads`)와 클라이언트가 공유하는 단일 원천이다.
@@ -3545,35 +3192,8 @@ const UPLOAD_PURPOSE_LIMITS = {
3545
3192
  "mov"
3546
3193
  ],
3547
3194
  maxBytes: 50 * MB
3548
- },
3549
- "digital-asset": {
3550
- extensions: [
3551
- "pdf",
3552
- "epub",
3553
- "zip",
3554
- "mp3",
3555
- "wav",
3556
- "flac",
3557
- "m4a",
3558
- "mp4",
3559
- "mov",
3560
- "jpg",
3561
- "jpeg",
3562
- "png",
3563
- "webp",
3564
- "psd",
3565
- "ai",
3566
- "txt",
3567
- "csv",
3568
- "xlsx",
3569
- "docx",
3570
- "pptx"
3571
- ],
3572
- maxBytes: 200 * MB
3573
3195
  }
3574
3196
  };
3575
- /** 비공개 업로드(판매 파일)의 PUT 필수 헤더 — CDN이 이 값으로 공개 조회를 막는다 */
3576
- const PRIVATE_UPLOAD_HEADERS = { "x-amz-meta-visibility": "private" };
3577
3197
  /** 파일명의 소문자 확장자. 확장자가 없으면 `""` */
3578
3198
  function uploadExtensionOf(filename) {
3579
3199
  const dot = filename.lastIndexOf(".");
@@ -3611,12 +3231,10 @@ const uploadRequestSchema = z.object({
3611
3231
  size: z.number().int().positive().optional().describe("파일 바이트 크기. 주면 용도별 상한을 발급 전에 검사한다")
3612
3232
  });
3613
3233
  const uploadTicketSchema = z.object({
3614
- /** 업로드 완료 후 공개될 주소. 비공개 업로드(`digital-asset`)는 이 주소로 받을 수 없다 */
3234
+ /** 업로드 완료 후 공개될 주소 */
3615
3235
  url: z.url(),
3616
3236
  /** 파일 바이트를 PUT할 presigned URL. 1시간 유효 */
3617
- uploadUrl: z.url(),
3618
- key: z.string().optional().describe("객체 키. 비공개 업로드(`digital-asset`)는 이 값으로 상품에 파일을 연결한다(`POST /v1/products/{productId}/digital-assets`). 공개 업로드에는 없다"),
3619
- headers: z.record(z.string(), z.string()).optional().default({}).describe("PUT에 반드시 실을 헤더. 비공개 업로드는 `x-amz-meta-visibility: private`이고 빠뜨리면 공개 객체가 된다. 공개 업로드는 빈 객체다")
3237
+ uploadUrl: z.url()
3620
3238
  });
3621
3239
  //#endregion
3622
3240
  //#region src/schemas/product.ts
@@ -4427,8 +4045,6 @@ const updateStoreRequestSchema = z.object({
4427
4045
  analyticsAllowedOrigins: z.array(analyticsOriginSchema).max(20).optional().describe("@deprecated `storefrontOrigins`를 쓴다(같은 목록). 방문 분석 수집을 받을 스토어프론트 오리진(최대 20개). 비우면 모든 오리진에서 받는다. 넣으면 목록에 없는 오리진의 수집 요청은 403 `ORIGIN_NOT_ALLOWED`이고, 페이지 URL이 목록 밖인 이벤트는 버린다"),
4428
4046
  storefrontOrigins: z.array(analyticsOriginSchema).max(20).optional().describe("스토어프론트 도메인(최대 20개) — 소셜 로그인 뒤 돌아갈 주소와 방문 분석 수집을 함께 제한한다. `analyticsAllowedOrigins`와 같은 목록이다(둘 다 주면 이 값이 이긴다)"),
4429
4047
  guestCheckoutEnabled: z.boolean().optional().describe("비회원 주문 허용. false(회원만)면 로그인하지 않은 구매자의 주문서 생성·결제 요청이 403 `GUEST_CHECKOUT_DISABLED`다. 장바구니 담기와 이미 받은 비회원 주문의 조회·취소는 막지 않는다"),
4430
- fulfillmentMailEnabled: z.boolean().optional().describe("비실물 상품 제공 안내 메일(이슈 #48). 켜면(기본) 제공이 끝났을 때 구매자에게 안내 메일을 보낸다. 코드·파일 주소는 싣지 않는다"),
4431
- withdrawalNotice: z.string().trim().min(1).max(1e3).nullable().optional().describe("청약철회 제한 고지 문구(이슈 #48). 제한 상품이 있는 주문서에 보인다. null이면 플랫폼 기본 문구를 쓴다"),
4432
4048
  autoDeliverDays: z.number().int().min(0).max(30).optional().describe(autoDeliverDaysDescription),
4433
4049
  autoConfirmDays: z.number().int().min(0).max(30).optional().describe(autoConfirmDaysDescription),
4434
4050
  refundPolicy: refundPolicyPatchSchema.optional().describe("스토어 기본 환불 정책 — 준 항목만 바꾼다. 상품별 정책(`products.refundPolicy`)이 없는 상품에 적용된다"),
@@ -4454,8 +4070,6 @@ const storeSchema = z.object({
4454
4070
  analyticsAllowedOrigins: z.array(z.string()).describe("방문 분석 수집을 받는 스토어프론트 오리진. 비어 있으면 모든 오리진에서 받는다"),
4455
4071
  storefrontOrigins: z.array(z.string()).describe("스토어프론트 도메인 — 소셜 로그인 리디렉트와 방문 분석 수집이 함께 쓴다(`analyticsAllowedOrigins`와 같은 값)"),
4456
4072
  guestCheckoutEnabled: z.boolean().describe("비회원 주문 허용. false면 회원만 주문할 수 있다"),
4457
- fulfillmentMailEnabled: z.boolean().optional().default(true).describe("비실물 상품 제공 안내 메일(이슈 #48). 서버는 항상 싣는다"),
4458
- withdrawalNotice: z.string().nullable().optional().default(null).describe("청약철회 제한 고지 문구(이슈 #48). null이면 플랫폼 기본 문구다. 서버는 항상 싣는다"),
4459
4073
  autoDeliverDays: z.number().int().describe(autoDeliverDaysDescription),
4460
4074
  autoConfirmDays: z.number().int().describe(autoConfirmDaysDescription),
4461
4075
  refundPolicy: refundPolicySchema.describe("스토어 기본 환불 정책. 스토어가 정하지 않은 항목은 플랫폼 기본값으로 채워 항상 전부 싣는다"),
@@ -4527,14 +4141,9 @@ const storeMemberRoleUpdatedSchema = z.object({
4527
4141
  role: z.enum(["ADMIN", "STAFF"])
4528
4142
  });
4529
4143
  /**
4530
- * 주소 유형 — 출고지·반품지·방문 수령지(이슈 #48). 방문 수령지(`PICKUP`)는 목록에서 `type=PICKUP`으로 조회할 때만 나온다
4531
- * (이 값을 모르는 옛 SDK의 목록 파싱이 깨지지 않게).
4144
+ * 주소 유형 — 출고지·반품지.
4532
4145
  */
4533
- const addressTypeSchema = z.enum([
4534
- "SHIPPING",
4535
- "RETURN",
4536
- "PICKUP"
4537
- ]);
4146
+ const addressTypeSchema = z.enum(["SHIPPING", "RETURN"]);
4538
4147
  /** `GET /v1/seller-addresses` 쿼리 — OpenAPI 문서용 형태. 기본 주소 우선, 그다음 등록순이다 */
4539
4148
  const sellerAddressListQuerySchema = z.object({ type: addressTypeSchema.optional().describe("주소 유형 필터") });
4540
4149
  const sellerAddressSchema = z.object({
@@ -4559,6 +4168,559 @@ const sellerAddressRequestSchema = z.object({
4559
4168
  address2: z.string().optional()
4560
4169
  });
4561
4170
  //#endregion
4171
+ //#region src/schemas/storefront-hosting.ts
4172
+ /**
4173
+ * 스토어프론트 호스팅 (이슈 #53, M1: 템플릿 발행) — 관리 API `/v1/storefront/**`의 계약이다.
4174
+ *
4175
+ * 셀러는 템플릿을 골라 사이트를 만들고 `{slug}.{호스팅 도메인}` 주소를 받는다. 발행은 버전 단위이고,
4176
+ * 되돌리기는 이전 버전을 다시 발행하는 것이다(재빌드 없음). 상태의 원천은 api DB이고 Cloudflare 쪽 반영
4177
+ * (라우팅 표·사이트 Worker)은 커밋 뒤 비동기다 — 반영 상태는 `provisioning`으로 본다.
4178
+ *
4179
+ * 스코프는 조회 `storefront:r`, 쓰기 `storefront:rw`(역할 OWNER·ADMIN)다. 에이전트(MCP)의 발행·되돌리기·템플릿 변경·
4180
+ * 공개 전환·슬러그 변경·호스팅 끄기는 승인(`APPROVAL_REQUIRED`)이다 — 구매자가 보는 화면을 바꾸는 쓰기다.
4181
+ *
4182
+ * 오류 코드:
4183
+ * - 403 `STOREFRONT_HOSTING_DISABLED` — 플랫폼 스위치가 꺼져 새 사이트를 만들 수 없다
4184
+ * - 404 `STOREFRONT_SITE_NOT_FOUND` · `STOREFRONT_TEMPLATE_NOT_FOUND` · `STOREFRONT_VERSION_NOT_FOUND`
4185
+ * - 409 `STOREFRONT_SITE_EXISTS` — 이미 사이트가 있다(스토어당 1개)
4186
+ * - 409 `STOREFRONT_SLUG_TAKEN` — 다른 사이트가 쓰거나 보류 중인 슬러그다
4187
+ * - 409 `STOREFRONT_SLUG_CHANGE_LIMIT` — 슬러그는 스토어당 30일에 3번까지 바꾼다
4188
+ * - 409 `STOREFRONT_SITE_CREATE_LIMIT` — 사이트는 스토어당 30일에 3번까지 만든다
4189
+ * - 409 `STOREFRONT_SITE_CHANGED` — 읽은 상태(`expected*`)와 현재 상태가 다르다. 다시 읽고 시도한다
4190
+ * - 409 `STOREFRONT_SITE_NOT_ACTIVE` — 정지(`SUSPENDED`)된 사이트는 바꾸거나 끌 수 없다
4191
+ * - 409 `STOREFRONT_VERSION_NOT_READY` — 빌드가 끝나지 않았거나 실패한 버전은 발행할 수 없다
4192
+ * - 409 `STOREFRONT_NO_ROLLBACK_TARGET` — 되돌릴 이전 발행 버전이 없다
4193
+ * - 400 `STOREFRONT_SLUG_CONFIRM_MISMATCH` — 호스팅 끄기 확인 입력이 현재 슬러그와 다르다
4194
+ * - 503 `STOREFRONT_PREVIEW_NOT_CONFIGURED` — 미리보기 서명 키가 설정되지 않았다
4195
+ *
4196
+ * 편집(M2b, `/v1/storefront/site/editing`·`files/**`·`checks/**`·`verify`·`builds/**`, 버전 만들기):
4197
+ * - 403 `STOREFRONT_EDITING_DISABLED` — 플랫폼 편집 스위치가 꺼져 있다
4198
+ * - 503 `STOREFRONT_EDITING_NOT_CONFIGURED` — 편집 Sandbox가 설정되지 않았다
4199
+ * - 409 `EDIT_CAPACITY_FULL` — 플랫폼 전체 동시 편집 상한에 걸렸다. `error.details.retryAfterSeconds` 뒤에 다시 한다
4200
+ * - 409 `EDIT_SESSION_REQUIRED` — 편집 미리보기 링크: 편집 환경이 없다
4201
+ * - 409 `CHECK_RUNNING` — 이 사이트에서 다른 종류의 검사가 돌고 있다(`error.details.checkId`)
4202
+ * - 404 `STOREFRONT_CHECK_NOT_FOUND`
4203
+ * - 409 `FILE_CHANGED` — 해시 조건이 어긋났다. 아무것도 쓰지 않았다(`error.details.conflicts: [{ path, currentHash }]`)
4204
+ * - 409 `FILE_EXISTS` — 옮길 자리에 파일이 있다(`overwrite: true`로 덮는다)
4205
+ * - 404 `FILE_NOT_FOUND` — 파일이 없다
4206
+ * - 400 `INVALID_FILE_PATH` — 경로 형식이 틀렸거나 편집할 수 없는 경로다(`node_modules`·`dist`·`.env*` 등). 쓸 수 있는 곳은
4207
+ * `src/**`·`public/**`와 루트 설정 파일 몇 개이고, `package.json`·`pnpm-lock.yaml`은 읽기만 된다
4208
+ * - 413 `FILE_TOO_LARGE`·`WORKSPACE_TOO_LARGE` — 파일 하나 1MiB, 작업 트리 전체 20MiB·2,000개를 넘는다
4209
+ * - 409 `STOREFRONT_BUILD_IN_PROGRESS` — 이 사이트에 끝나지 않은 빌드가 있다(사이트당 하나)
4210
+ * - 404 `STOREFRONT_BUILD_NOT_FOUND`
4211
+ * - 502 `STOREFRONT_SANDBOX_UNAVAILABLE` — 편집 Sandbox 호출이 실패했다. 잠시 뒤 다시 한다
4212
+ */
4213
+ /**
4214
+ * 슬러그 형식 — 스토어 코드와 같은 DNS 라벨 규칙(소문자·숫자·하이픈 3~30자, 하이픈으로 시작·끝 불가)에
4215
+ * 연속 하이픈 금지를 더한다. `--`는 버전 미리보기 주소(`v3--{slug}`)와 punycode(`xn--`)가 쓰는 구분자다.
4216
+ */
4217
+ const STOREFRONT_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,28}[a-z0-9])$/;
4218
+ /** 호스팅 도메인에서 플랫폼이 쓰거나 쓸 이름 — 스토어 코드 예약어에 호스팅 전용 이름을 더한다 */
4219
+ const RESERVED_STOREFRONT_SLUGS = [
4220
+ ...RESERVED_STORE_CODES,
4221
+ "mcp",
4222
+ "preview",
4223
+ "previews",
4224
+ "sayren",
4225
+ "shop",
4226
+ "shops",
4227
+ "abuse",
4228
+ "security",
4229
+ "closed",
4230
+ "coming-soon",
4231
+ "suspended",
4232
+ "dispatch",
4233
+ "control",
4234
+ "outbound"
4235
+ ];
4236
+ /**
4237
+ * 편집 미리보기 호스트 접미사 — 편집 미리보기 주소는 `{사이트 id 정규화}-preview.{호스팅 도메인}`이다(M2b). 사이트 주소와 같은
4238
+ * 1단계 이름이라 슬러그가 이 모양이면 남의 편집 미리보기 주소와 겹칠 수 있어 슬러그로 쓰지 못한다.
4239
+ */
4240
+ const STOREFRONT_EDIT_PREVIEW_SUFFIX = "-preview";
4241
+ function isReservedStorefrontSlug(slug) {
4242
+ const lower = slug.toLowerCase();
4243
+ return RESERVED_STOREFRONT_SLUGS.includes(lower) || lower.endsWith("-preview");
4244
+ }
4245
+ /**
4246
+ * 편집 미리보기 호스트 이름의 라벨 — 사이트 id의 `_`를 `-`로 바꾸고 `-preview`를 붙인다(`sfs_ab12cd34` → `sfs-ab12cd34-preview`).
4247
+ * 편집 Sandbox Worker(`apps/hosting-sandbox/src/preview-host.ts`)가 같은 규칙의 복제본을 들고 있고 api
4248
+ * `test/storefront-hosting-parity.spec.ts`가 둘을 대조한다. 규칙을 바꾸면 두 곳을 같이 고친다.
4249
+ */
4250
+ function storefrontEditPreviewLabelOf(siteId) {
4251
+ return `${siteId.replaceAll("_", "-")}${STOREFRONT_EDIT_PREVIEW_SUFFIX}`;
4252
+ }
4253
+ const storefrontSlugSchema = z.string().regex(STOREFRONT_SLUG_PATTERN, "소문자·숫자·하이픈 3~30자여야 하고 하이픈으로 시작하거나 끝날 수 없습니다").refine((slug) => !slug.includes("--"), "하이픈을 연달아 쓸 수 없습니다").refine((slug) => !isReservedStorefrontSlug(slug), "플랫폼이 쓰는 이름이라 쓸 수 없습니다").describe("주소 슬러그. `{slug}.{호스팅 도메인}`이 사이트 주소가 된다");
4254
+ /** 바꾼 슬러그를 다른 사이트가 가져가지 못하게 막는 기간(일). 보류 동안 옛 주소는 새 주소로 301한다 */
4255
+ const STOREFRONT_SLUG_HOLD_DAYS = 90;
4256
+ /** 슬러그 변경 한도 — 이 기간(일) 안에 */
4257
+ const STOREFRONT_SLUG_CHANGE_WINDOW_DAYS = 30;
4258
+ /** 슬러그 변경 한도 — 횟수(스토어 단위. 끄고 다시 만들어도 이어서 센다) */
4259
+ const STOREFRONT_SLUG_CHANGE_LIMIT = 3;
4260
+ /** 사이트 만들기 한도 — 같은 창(30일) 안의 횟수(스토어 단위). 끄고 만들기를 되풀이해 이름을 묶어 두지 못하게 한다 */
4261
+ const STOREFRONT_SITE_CREATE_LIMIT = 3;
4262
+ const storefrontTemplateSchema = z.object({
4263
+ templateId: z.string().describe("템플릿 id (예: `basic`)"),
4264
+ name: z.string().describe("템플릿 이름"),
4265
+ industry: z.string().describe("업종 (예: `general`·`fashion`·`food`). 목록 필터에 쓴다"),
4266
+ description: z.string().describe("템플릿 설명"),
4267
+ thumbnailUrl: z.string().nullable().describe("썸네일 이미지 주소. 없으면 null"),
4268
+ version: z.string().describe("현재 템플릿 버전. 새 사이트·템플릿 변경은 이 버전으로 만든다"),
4269
+ isDefault: z.boolean().describe("기본 템플릿인가 (스토어 생성 시 자동 생성에 쓴다)")
4270
+ });
4271
+ /**
4272
+ * 버전 상태 — 템플릿 버전(`source: TEMPLATE`)은 템플릿 산출물을 그대로 쓰므로 만들 때 곧바로 `READY`다. 편집 버전
4273
+ * (`source: EDIT`, M2b)은 작업 트리 스냅샷을 빌드하는 동안 `BUILDING`이고, 빌드·규칙 검증을 통과하면 `READY`, 실패하면
4274
+ * `FAILED`다(실패 단계·로그 꼬리는 `buildId`의 빌드). 발행할 수 있는 것은 `READY`뿐이다. 발행 여부는 상태가 아니라 사이트의
4275
+ * 발행 포인터가 원천이다(`published`).
4276
+ */
4277
+ const storefrontVersionStatusSchema = z.enum([
4278
+ "BUILDING",
4279
+ "READY",
4280
+ "FAILED"
4281
+ ]).describe("`BUILDING` 빌드 중 · `READY` 발행 가능 · `FAILED` 빌드 실패");
4282
+ const storefrontVersionSchema = z.object({
4283
+ versionId: z.string(),
4284
+ number: z.number().int().min(1).describe("사이트 안에서 1부터 늘어나는 번호 (v1, v2…)"),
4285
+ templateId: z.string().describe("이 버전의 기준 템플릿"),
4286
+ templateVersion: z.string().describe("이 버전의 기준 템플릿 버전"),
4287
+ status: storefrontVersionStatusSchema,
4288
+ published: z.boolean().describe("지금 발행된 버전인가"),
4289
+ publishedAt: z.string().nullable().describe("마지막으로 발행한 시각 (ISO 8601). 발행한 적 없으면 null"),
4290
+ message: z.string().nullable().describe("버전 메모"),
4291
+ source: z.enum(["TEMPLATE", "EDIT"]).describe("`TEMPLATE` 템플릿 산출물 그대로 · `EDIT` 편집 작업 트리를 빌드함"),
4292
+ buildId: z.string().nullable().describe("편집 버전의 빌드 (`GET /v1/storefront/site/builds/{buildId}`). 템플릿 버전은 null"),
4293
+ createdByType: z.string().describe("만든 행위자 유형 (`USER`·`AGENT`·`APP`·`TOKEN`·`SYSTEM`). 늘어날 수 있다"),
4294
+ createdAt: z.string().describe("만든 시각 (ISO 8601)")
4295
+ });
4296
+ const storefrontSiteStatusSchema = z.enum([
4297
+ "ACTIVE",
4298
+ "SUSPENDED",
4299
+ "CLOSED"
4300
+ ]).describe("`ACTIVE` 운영 중 · `SUSPENDED` 운영자 정지 · `CLOSED` 호스팅 끔·스토어 폐쇄");
4301
+ const storefrontVisibilitySchema = z.enum(["COMING_SOON", "PUBLIC"]).describe("`COMING_SOON` 오픈 준비 중(방문자에게 준비 중 페이지) · `PUBLIC` 공개");
4302
+ const storefrontSourceModeSchema = z.enum(["MANAGED", "CUSTOM"]).describe("`MANAGED` 템플릿 그대로 · `CUSTOM` 한 번이라도 편집함");
4303
+ const storefrontDomainSchema = z.object({
4304
+ hostname: z.string().describe("호스트 이름 (예: `mystore.sayren.co`)"),
4305
+ kind: z.enum(["PLATFORM", "CUSTOM"]).describe("`PLATFORM` 슬러그 주소 · `CUSTOM` 내 도메인"),
4306
+ status: z.enum(["ACTIVE", "HELD"]).describe("`ACTIVE` 사용 중 · `HELD` 슬러그를 바꿔 보류 중(새 주소로 301)"),
4307
+ isPrimary: z.boolean().describe("대표 주소인가 (`Store.storefrontUrl`)"),
4308
+ holdUntil: z.string().nullable().describe("보류가 끝나는 시각 (ISO 8601). 보류가 아니면 null")
4309
+ });
4310
+ /**
4311
+ * 호스팅 반영 상태 — 발행·슬러그·공개 전환은 DB에 먼저 확정되고 Cloudflare 반영은 커밋 뒤에 한다.
4312
+ * `PENDING`이면 반영 중이거나 재시도 대기다(잡이 다시 한다). 반영 뒤에도 라우팅 전파에 최대 1분이 걸린다.
4313
+ */
4314
+ const storefrontProvisioningSchema = z.object({
4315
+ status: z.enum([
4316
+ "SYNCED",
4317
+ "PENDING",
4318
+ "FAILED"
4319
+ ]).describe("`SYNCED` 반영됨 · `PENDING` 반영 대기 · `FAILED` 반영이 여러 번 실패함(자동 재시도는 계속한다)"),
4320
+ lastErrorCode: z.string().nullable().describe("마지막 반영 실패 코드. 없으면 null"),
4321
+ syncedAt: z.string().nullable().describe("마지막으로 반영한 시각 (ISO 8601)")
4322
+ });
4323
+ /**
4324
+ * 편집 미리보기 주소(M2b) — 사이트마다 `{사이트 id 정규화}-preview.{호스팅 도메인}` 호스트 하나다. 호스팅 도메인의 와일드카드
4325
+ * 인증서가 덮으므로 따로 준비할 것이 없다. 이 주소는 편집 미리보기 링크(콘솔 전용)로 받은 쿠키가 있어야 열린다.
4326
+ */
4327
+ const storefrontEditPreviewSchema = z.object({ hostname: z.string().nullable().describe("편집 미리보기 호스트 (예: `sfs-ab12cd34-preview.sayren.co`). 편집을 쓸 수 없으면 null") });
4328
+ const storefrontSiteSchema = z.object({
4329
+ siteId: z.string(),
4330
+ slug: z.string().describe("주소 슬러그"),
4331
+ hostname: z.string().describe("대표 호스트 이름"),
4332
+ url: z.string().describe("대표 주소 (`https://{hostname}`)"),
4333
+ domains: z.array(storefrontDomainSchema).describe("사용 중·보류 중인 호스트 이름"),
4334
+ templateId: z.string().describe("지금 발행된 버전의 템플릿 (발행 전이면 만든 템플릿)"),
4335
+ sourceMode: storefrontSourceModeSchema,
4336
+ status: storefrontSiteStatusSchema,
4337
+ visibility: storefrontVisibilitySchema,
4338
+ publishedVersion: storefrontVersionSchema.nullable().describe("지금 발행된 버전"),
4339
+ provisioning: storefrontProvisioningSchema,
4340
+ editPreview: storefrontEditPreviewSchema,
4341
+ slugChangesRemaining: z.number().int().min(0).describe(`최근 30일 기준 남은 슬러그 변경 횟수`),
4342
+ createdAt: z.string(),
4343
+ updatedAt: z.string()
4344
+ });
4345
+ /** `GET /v1/storefront/site` — 스위치 상태와 현재 사이트(없으면 null. 끈 사이트는 싣지 않는다) */
4346
+ const storefrontHostingSchema = z.object({
4347
+ enabled: z.boolean().describe("새 사이트를 만들 수 있는가 (플랫폼 스위치)"),
4348
+ domain: z.string().describe("호스팅 도메인 (예: `sayren.co`). 사이트 주소는 `{slug}.{domain}`"),
4349
+ editingEnabled: z.boolean().describe("파일 편집·버전 만들기를 쓸 수 있는가 (플랫폼 편집 스위치와 Sandbox 설정)"),
4350
+ site: storefrontSiteSchema.nullable()
4351
+ });
4352
+ const storefrontSlugAvailabilitySchema = z.object({
4353
+ slug: z.string(),
4354
+ available: z.boolean(),
4355
+ reason: z.enum([
4356
+ "INVALID",
4357
+ "RESERVED",
4358
+ "TAKEN",
4359
+ "CURRENT"
4360
+ ]).nullable().describe("쓸 수 없는 이유. `INVALID` 형식 · `RESERVED` 예약어 · `TAKEN` 사용·보류 중 · `CURRENT` 지금 내 슬러그")
4361
+ });
4362
+ const createStorefrontSiteRequestSchema = z.object({
4363
+ templateId: z.string().min(1).max(64).describe("템플릿 id"),
4364
+ slug: storefrontSlugSchema.optional().describe("주소 슬러그. 생략하면 스토어 코드")
4365
+ });
4366
+ const changeStorefrontTemplateRequestSchema = z.object({
4367
+ templateId: z.string().min(1).max(64).describe("바꿀 템플릿 id"),
4368
+ publish: z.boolean().optional().describe("새 버전을 곧바로 발행하는가 (기본 true). false면 버전만 만든다"),
4369
+ message: z.string().max(200).optional().describe("버전 메모")
4370
+ });
4371
+ /**
4372
+ * 발행·되돌리기 — 발행은 사이트의 발행 포인터를 바꾸는 조건부 쓰기다. `expectedPublishedVersionId`를 주면
4373
+ * 지금 발행 버전이 그 값일 때만 바꾼다(화면을 본 뒤 다른 사람이 바꿨으면 409 `STOREFRONT_SITE_CHANGED`).
4374
+ * 이미 그 버전이 발행돼 있으면 아무것도 바꾸지 않고 200이다(같은 요청의 재실행이 안전하다).
4375
+ */
4376
+ const publishStorefrontVersionRequestSchema = z.object({ expectedPublishedVersionId: z.string().nullable().optional().describe("화면에서 본 발행 버전 id (발행 전이었으면 null). 생략하면 확인하지 않는다") });
4377
+ const rollbackStorefrontRequestSchema = z.object({
4378
+ versionId: z.string().optional().describe("되돌릴 버전. 생략하면 지금 버전 직전에 발행했던 버전"),
4379
+ expectedPublishedVersionId: z.string().nullable().optional()
4380
+ });
4381
+ const changeStorefrontSlugRequestSchema = z.object({ slug: storefrontSlugSchema });
4382
+ const setStorefrontVisibilityRequestSchema = z.object({
4383
+ visibility: storefrontVisibilitySchema,
4384
+ expectedVisibility: storefrontVisibilitySchema.optional().describe("화면에서 본 공개 상태. 주면 지금 값이 같을 때만 바꾼다")
4385
+ });
4386
+ /** 호스팅 끄기 — 사이트를 닫고(`CLOSED`) 슬러그를 보류한다. 확인 입력은 현재 슬러그다 */
4387
+ const disableStorefrontHostingRequestSchema = z.object({ confirmSlug: z.string().min(1).describe("확인 입력 — 현재 슬러그") });
4388
+ const storefrontSlugAvailabilityQuerySchema = z.object({ slug: z.string().describe("확인할 슬러그") });
4389
+ /**
4390
+ * 오픈 준비 중 미리보기 링크 — 콘솔에서 로그인한 OWNER·ADMIN만 받는다(PAT·에이전트 불가). 링크는 5분 안에 한 번 열면
4391
+ * 그 브라우저에 12시간짜리 미리보기 쿠키가 생기고 오픈 준비 중인 사이트의 실제 화면을 본다. 링크를 남에게 넘기면 그 사람도
4392
+ * 볼 수 있으므로 공유하지 않는다.
4393
+ */
4394
+ const createStorefrontPreviewLinkRequestSchema = z.object({ path: z.string().max(512).optional().describe("열 경로 (`/`로 시작). 생략하면 `/`") });
4395
+ const storefrontPreviewLinkSchema = z.object({
4396
+ url: z.string().describe("미리보기 링크 — 5분 안에 연다"),
4397
+ expiresAt: z.string().describe("링크 만료 시각 (ISO 8601)")
4398
+ });
4399
+ /** 플랫폼 전체 동시 편집 환경 상한 */
4400
+ const STOREFRONT_EDIT_CAPACITY = 20;
4401
+ /** 활동이 없으면 편집 환경이 잠드는 시간(분). 작업 트리는 보존된다 */
4402
+ const STOREFRONT_EDIT_IDLE_MINUTES = 15;
4403
+ /**
4404
+ * 편집 작업(파일 쓰기·검사·버전 만들기·편집 미리보기 링크 발급)이 없으면 서버가 편집 환경을 닫는 시간(분). 미리보기 탭만 열어 두면
4405
+ * 유휴 잠들기(`STOREFRONT_EDIT_IDLE_MINUTES`)가 걸리지 않으므로 이 한도가 편집 환경을 끝낸다. 작업 트리는 보존된다
4406
+ */
4407
+ const STOREFRONT_EDIT_ACTIVITY_TIMEOUT_MINUTES = 30;
4408
+ /** 파일 하나의 최대 크기(바이트) */
4409
+ const STOREFRONT_FILE_MAX_BYTES = 1048576;
4410
+ /** 작업 트리 전체 최대 크기(바이트) — 파일 수는 2,000개까지 */
4411
+ const STOREFRONT_WORKSPACE_MAX_BYTES = 20971520;
4412
+ /** 일괄 쓰기 한 번의 최대 파일 수 */
4413
+ const STOREFRONT_FILE_BATCH_MAX = 50;
4414
+ /** 편집할 수 없는 경로 조각 — 설치물·산출물 폴더 */
4415
+ const STOREFRONT_BLOCKED_PATH_SEGMENTS = [
4416
+ "node_modules",
4417
+ "dist",
4418
+ ".output",
4419
+ ".tanstack",
4420
+ ".wrangler",
4421
+ ".git",
4422
+ ".nitro",
4423
+ ".vinxi"
4424
+ ];
4425
+ /** 경로 조각에 쓸 수 있는 문자 — 편집 Sandbox Worker의 경로 규칙(sandbox-api.md §2.1)과 같다. TanStack Router 파일명(`$id.tsx`·`_layout`·`(group)`·`[.]`)이 들어간다 */
4426
+ const STOREFRONT_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9$_.()[\]@+=,-]+$/;
4427
+ /**
4428
+ * 보이지 않거나 표시 방향을 바꾸는 문자 — 제로폭(U+200B~U+200D, U+FEFF)과 bidi 제어(U+202A~U+202E, U+2066~U+2069). 비슷해 보이는
4429
+ * 경로로 규칙 검사를 피하거나 리뷰어를 속이는 데 쓰인다. 문자 규칙이 이미 막지만 이유를 따로 드러내려고 먼저 본다.
4430
+ */
4431
+ const INVISIBLE_OR_BIDI = /[\u200B-\u200D\uFEFF\u202A-\u202E\u2066-\u2069]/;
4432
+ /** 경로 정규화 — NFC. 비교·저장은 이 값으로 한다(같아 보이는 두 경로가 다른 파일이 되지 않게) */
4433
+ function normalizeStorefrontPath(path) {
4434
+ return path.normalize("NFC");
4435
+ }
4436
+ /**
4437
+ * 작업 트리 상대 경로인가(정규화한 값 기준) — `/`로 구분하고 `/`로 시작하지 않는다. `.`·`..`·빈 조각, 조각 문자 규칙
4438
+ * (`STOREFRONT_PATH_SEGMENT_PATTERN`, 공백·역슬래시·제어·제로폭·bidi 문자 불가) 밖, 설치물·산출물 폴더
4439
+ * (`STOREFRONT_BLOCKED_PATH_SEGMENTS`), `.env`로 시작하는 조각(파일·폴더)은 받지 않는다. 최대 256자·깊이 16.
4440
+ * 어디에 쓸 수 있는지(`src/**`·`public/**`·루트 설정 파일)는 편집 Sandbox가 판정한다(400 `INVALID_FILE_PATH`).
4441
+ * 편집 Sandbox Worker가 같은 규칙의 복제본을 들고 있다(api `test/storefront-hosting-parity.spec.ts`가 대조한다).
4442
+ */
4443
+ function isEditableStorefrontPath(path) {
4444
+ if (path.length === 0 || path.length > 256) return false;
4445
+ if (INVISIBLE_OR_BIDI.test(path)) return false;
4446
+ if (path !== normalizeStorefrontPath(path)) return false;
4447
+ if (path.startsWith("/")) return false;
4448
+ const segments = path.split("/");
4449
+ if (segments.length > 16) return false;
4450
+ for (const segment of segments) {
4451
+ if (segment === "" || segment === "." || segment === "..") return false;
4452
+ if (!STOREFRONT_PATH_SEGMENT_PATTERN.test(segment)) return false;
4453
+ if (STOREFRONT_BLOCKED_PATH_SEGMENTS.includes(segment)) return false;
4454
+ if (segment.startsWith(".env")) return false;
4455
+ }
4456
+ return true;
4457
+ }
4458
+ /** 경로 입력 — NFC로 정규화한 뒤 규칙을 본다 */
4459
+ const storefrontFilePathSchema = z.string().transform(normalizeStorefrontPath).refine(isEditableStorefrontPath, "편집할 수 없는 경로입니다").describe("작업 트리 상대 경로 (예: `src/routes/index.tsx`). NFC로 정규화한다");
4460
+ /** 파일 해시 — 내용 바이트의 SHA-256 hex(소문자 64자) */
4461
+ const storefrontFileHashSchema = z.string().regex(/^[0-9a-f]{64}$/, "SHA-256 hex여야 합니다").describe("내용 바이트의 SHA-256 (소문자 hex)");
4462
+ const storefrontFileEncodingSchema = z.enum(["utf8", "base64"]).describe("`utf8` 텍스트 · `base64` 바이너리(이미지 등)");
4463
+ /**
4464
+ * 편집 환경 상태 — `STARTING`은 컨테이너를 띄우고 작업 트리를 복원해 dev 서버를 올리는 중이다(보통 30초 안, 최대
4465
+ * 90초). `RUNNING`이면 미리보기와 파일 쓰기가 바로 반영된다(HMR).
4466
+ */
4467
+ const storefrontEditSessionStatusSchema = z.enum(["STARTING", "RUNNING"]).describe("`STARTING` 띄우는 중 · `RUNNING` 사용 중");
4468
+ /** 편집 환경을 띄우는 단계 — 콘솔 진행 표시용이다. 값은 늘어날 수 있다 */
4469
+ const STOREFRONT_EDIT_START_STAGES = [
4470
+ "CONTAINER",
4471
+ "RESTORE",
4472
+ "DEV_SERVER"
4473
+ ];
4474
+ const storefrontEditSessionSchema = z.object({
4475
+ sessionId: z.string(),
4476
+ status: storefrontEditSessionStatusSchema,
4477
+ startStage: z.string().nullable().describe("`STARTING`일 때 지금 단계 (`CONTAINER` 컨테이너 기동 · `RESTORE` 작업 트리 복원 · `DEV_SERVER` 개발 서버 기동). 늘어날 수 있다. `RUNNING`이면 null"),
4478
+ startedAt: z.string().describe("연 시각 (ISO 8601)"),
4479
+ lastEditedAt: z.string().nullable().describe("이 편집 환경에서 마지막으로 파일을 바꾼 시각. 없으면 null"),
4480
+ lastActiveAt: z.string().describe("마지막 활동 시각 (파일 도구·검사·미리보기)"),
4481
+ idleExpiresAt: z.string().describe(`이 시각까지 활동이 없으면 잠든다 (마지막 활동 + 15분). 잠들어도 작업 트리는 보존된다`),
4482
+ closesAt: z.string().describe(`이 시각까지 편집 작업(파일 쓰기·검사·버전 만들기·미리보기 링크 발급)이 없으면 서버가 편집 환경을 닫는다 (마지막 편집 작업 + 30분). 미리보기만 보고 있어도 닫힌다. 작업 트리는 보존된다`)
4483
+ });
4484
+ /** `GET·POST·DELETE /v1/storefront/site/editing` — 편집 환경 상태 */
4485
+ const storefrontEditingSchema = z.object({
4486
+ enabled: z.boolean().describe("편집을 쓸 수 있는가 (플랫폼 스위치와 Sandbox 설정)"),
4487
+ session: storefrontEditSessionSchema.nullable().describe("열린 편집 환경. 잠들었거나 닫았으면 null"),
4488
+ capacity: z.object({
4489
+ limit: z.number().int().describe("플랫폼 전체 동시 편집 환경 상한"),
4490
+ inUse: z.number().int().describe("지금 열린 편집 환경 수")
4491
+ }),
4492
+ preview: storefrontEditPreviewSchema.describe("편집 미리보기 주소 (사이트의 `editPreview`와 같다)"),
4493
+ workingTree: z.object({
4494
+ modified: z.boolean().describe("템플릿에서 한 번이라도 고쳤는가 (사이트 `sourceMode`가 `CUSTOM`)"),
4495
+ lastEditedAt: z.string().nullable().describe("마지막으로 파일을 바꾼 시각")
4496
+ })
4497
+ });
4498
+ const storefrontFileEntrySchema = z.object({
4499
+ path: z.string(),
4500
+ size: z.number().int().min(0).describe("바이트 수"),
4501
+ hash: storefrontFileHashSchema
4502
+ });
4503
+ /** `GET /v1/storefront/site/files?prefix=` — 작업 트리 파일 목록(편집 환경이 없어도 된다. 편집 환경을 깨우지 않는다) */
4504
+ const storefrontFileListSchema = z.object({
4505
+ files: z.array(storefrontFileEntrySchema).describe("경로 사전순"),
4506
+ totalBytes: z.number().int().min(0).describe("작업 트리 전체 바이트 수")
4507
+ });
4508
+ const storefrontFileListQuerySchema = z.object({ prefix: z.string().max(256).optional().describe("이 접두사로 시작하는 경로만 (예: `src/routes/`)") });
4509
+ const storefrontFileContentQuerySchema = z.object({ path: z.string().describe("읽을 파일 경로") });
4510
+ /** `GET /v1/storefront/site/files/content?path=` — 편집 환경이 없어도 된다 */
4511
+ const storefrontFileContentSchema = z.object({
4512
+ path: z.string(),
4513
+ content: z.string().describe("내용 (`encoding`에 따라 텍스트 또는 base64)"),
4514
+ encoding: storefrontFileEncodingSchema,
4515
+ hash: storefrontFileHashSchema,
4516
+ size: z.number().int().min(0)
4517
+ });
4518
+ /**
4519
+ * `POST /v1/storefront/site/files/batch` — 여러 파일을 한 번에 쓴다. 편집 환경이 없어도 된다(잠든 작업 트리에 바로 쓰고,
4520
+ * 편집 환경이 떠 있으면 미리보기에 곧바로 반영된다). `expectedHash`: 생략하면 조건 없이 쓴다 · `null`이면 파일이 없어야 한다(새 파일) · 값이면 지금
4521
+ * 해시가 같아야 한다. 조건을 **모두** 확인한 뒤 쓰고, 하나라도 어긋나면 아무것도 쓰지 않는다(409 `FILE_CHANGED`,
4522
+ * `error.details.conflicts`).
4523
+ */
4524
+ const writeStorefrontFilesRequestSchema = z.object({ files: z.array(z.object({
4525
+ path: storefrontFilePathSchema,
4526
+ content: z.string().describe("내용 (`encoding`에 따라 텍스트 또는 base64)"),
4527
+ encoding: storefrontFileEncodingSchema.optional().describe("기본 `utf8`"),
4528
+ expectedHash: storefrontFileHashSchema.nullable().optional().describe("쓰기 조건. 생략 = 조건 없음 · null = 새 파일 · 값 = 지금 해시가 같을 때만")
4529
+ })).min(1).max(50).refine((files) => new Set(files.map((file) => file.path)).size === files.length, { message: "같은 경로를 두 번 실을 수 없습니다" }) });
4530
+ const storefrontFileWriteResultSchema = z.object({ files: z.array(storefrontFileEntrySchema).describe("쓴 파일의 새 해시·크기 (요청 순서)") });
4531
+ /** 409 `FILE_CHANGED`의 `error.details` */
4532
+ const storefrontFileConflictDetailsSchema = z.object({ conflicts: z.array(z.object({
4533
+ path: z.string(),
4534
+ currentHash: storefrontFileHashSchema.nullable().describe("지금 해시. 파일이 없으면 null")
4535
+ })) });
4536
+ /** `POST /v1/storefront/site/files/delete` — 없는 파일이면 `deleted: false`(조건을 주면 409 `FILE_CHANGED`) */
4537
+ const deleteStorefrontFileRequestSchema = z.object({
4538
+ path: storefrontFilePathSchema,
4539
+ expectedHash: storefrontFileHashSchema.optional().describe("주면 지금 해시가 같을 때만 지운다")
4540
+ });
4541
+ const storefrontFileDeleteResultSchema = z.object({
4542
+ path: z.string(),
4543
+ deleted: z.boolean()
4544
+ });
4545
+ /** `POST /v1/storefront/site/files/move` — 이름 바꾸기·옮기기 */
4546
+ const moveStorefrontFileRequestSchema = z.object({
4547
+ from: storefrontFilePathSchema,
4548
+ to: storefrontFilePathSchema,
4549
+ expectedHash: storefrontFileHashSchema.optional().describe("주면 `from`의 지금 해시가 같을 때만"),
4550
+ overwrite: z.boolean().optional().describe("`to`에 파일이 있으면 덮는가 (기본 false → 409 `FILE_EXISTS`)")
4551
+ });
4552
+ const storefrontFileMoveResultSchema = z.object({
4553
+ from: z.string(),
4554
+ to: z.string(),
4555
+ hash: storefrontFileHashSchema,
4556
+ size: z.number().int().min(0)
4557
+ });
4558
+ /** `POST /v1/storefront/site/files/search` — 텍스트 파일 내용 검색(편집 환경이 없어도 된다) */
4559
+ /** 정규식 검색의 패턴 길이 상한 — 편집 Sandbox가 실행 비용을 막으려고 더 좁게 받는다(sandbox-api §4.6) */
4560
+ const STOREFRONT_SEARCH_REGEX_MAX_LENGTH = 100;
4561
+ const searchStorefrontFilesRequestSchema = z.object({
4562
+ query: z.string().min(1).max(200),
4563
+ regex: z.boolean().optional().describe("`query`를 정규식으로 본다 (기본 false = 글자 그대로). 정규식은 100자까지이고 중첩 수량자(`(a+)+`)·반복되는 선택(`(a|b)+`)·역참조·뒤 보기, 가변 수량자(`*`·`+`·`?`·`{n,m}`, 넓은 범위는 2로 센다) 차수 4 초과는 400 `INVALID_REQUEST`다. 차수가 클수록 긴 줄은 건너뛰고 비용이 크면 `truncated`로 끊는다"),
4564
+ caseSensitive: z.boolean().optional().describe("대소문자를 가리는가 (기본 false)"),
4565
+ pathPrefix: z.string().max(256).optional(),
4566
+ maxResults: z.number().int().min(1).max(200).optional().describe("기본 50")
4567
+ }).refine((body) => !body.regex || body.query.length <= 100, {
4568
+ message: `정규식은 100자까지입니다`,
4569
+ path: ["query"]
4570
+ });
4571
+ const storefrontFileSearchResultSchema = z.object({
4572
+ matches: z.array(z.object({
4573
+ path: z.string(),
4574
+ line: z.number().int().min(1),
4575
+ column: z.number().int().min(1).optional(),
4576
+ text: z.string().describe("일치한 줄 (일치 앞뒤 200자까지)")
4577
+ })),
4578
+ truncated: z.boolean().describe("`maxResults`에서 끊었는가")
4579
+ });
4580
+ /**
4581
+ * `POST /v1/storefront/site/checks` (202) — 작업 트리에 명령 하나를 돌린다(`typecheck` 타입 검사 · `lint` · `test`). 편집 환경이
4582
+ * 없어도 된다(일회용 검사 컨테이너에서 돈다). 검사는 30초를 넘길 수 있어 **항상 202로 검사 id만 주고** 결과는
4583
+ * `GET /v1/storefront/site/checks/{checkId}`로 조회한다(권장 2~3초 간격). 사이트당 끝나지 않은 검사는 하나다 — 같은 종류가 돌고
4584
+ * 있으면 그 검사를 다시 돌려주고, 다른 종류면 409 `CHECK_RUNNING`(`error.details.checkId`)이다. 출력은 꼬리만 준다.
4585
+ */
4586
+ const runStorefrontCheckRequestSchema = z.object({ check: z.enum([
4587
+ "typecheck",
4588
+ "lint",
4589
+ "test"
4590
+ ]) });
4591
+ const storefrontCheckStatusSchema = z.enum([
4592
+ "QUEUED",
4593
+ "RUNNING",
4594
+ "PASSED",
4595
+ "FAILED",
4596
+ "TIMED_OUT",
4597
+ "UNKNOWN"
4598
+ ]).describe("`QUEUED` 대기 · `RUNNING` 실행 중 · `PASSED` 통과 · `FAILED` 실패(명령 실패 또는 `errorCode`) · `TIMED_OUT` 마감(150초)에 걸려 끊김 · `UNKNOWN` 결과를 모른다(다시 실행하지 않는다, 새 검사를 요청한다)");
4599
+ /** 끝난 검사 상태 */
4600
+ const STOREFRONT_CHECK_TERMINAL_STATUSES = [
4601
+ "PASSED",
4602
+ "FAILED",
4603
+ "TIMED_OUT",
4604
+ "UNKNOWN"
4605
+ ];
4606
+ const storefrontCheckSchema = z.object({
4607
+ checkId: z.string(),
4608
+ check: z.string().describe("`typecheck`·`lint`·`test`"),
4609
+ status: storefrontCheckStatusSchema,
4610
+ exitCode: z.number().int().nullable().describe("명령 종료 코드. 명령이 끝나지 않았으면 null"),
4611
+ durationMs: z.number().int().min(0).nullable().describe("명령 시간. 끝나지 않았으면 null"),
4612
+ outputTail: z.string().nullable().describe("출력 꼬리 (최대 16KiB). 끝나지 않았으면 null"),
4613
+ errorCode: z.string().nullable().describe("명령 밖의 실패 이유 (`CHECK_BUSY`·`SANDBOX_UNAVAILABLE`·`RESULT_UNKNOWN`, 늘어날 수 있다). 명령 결과로 끝났으면 null"),
4614
+ message: z.string().nullable().describe("`errorCode`가 있을 때 사용자용 안내"),
4615
+ createdAt: z.string(),
4616
+ startedAt: z.string().nullable(),
4617
+ finishedAt: z.string().nullable()
4618
+ });
4619
+ /** 규칙 심각도 — `error`는 버전 발행을 막는다(빌드 `VERIFY` 실패), `warning`은 알리기만 한다. 없으면 `error`로 본다 */
4620
+ const storefrontRuleSeveritySchema = z.enum(["error", "warning"]);
4621
+ /** 규칙 하나의 검사 결과 — MCP 계측(`mcpRuleResultSchema`)과 같은 모양에 심각도를 더한다 */
4622
+ const storefrontRuleResultSchema = z.object({
4623
+ ruleId: z.string(),
4624
+ status: z.enum(["pass", "violation"]),
4625
+ count: z.number().int().min(0),
4626
+ severity: storefrontRuleSeveritySchema.optional()
4627
+ });
4628
+ /**
4629
+ * 규칙 검증 보고서 — `@sayren/mcp`의 `verify_storefront`와 같은 모양이다(원천 `apps/mcp/src/verify.ts`). 위반마다
4630
+ * 고칠 자리(`file`·`line`)와 방법(`fix`·`example`)을 싣는다. `POST /v1/storefront/site/verify`(편집 환경이 잠들어 있어도
4631
+ * 된다)와 빌드의 `verify`가 이 모양이다.
4632
+ */
4633
+ const storefrontVerifyReportSchema = z.object({
4634
+ ok: z.boolean().describe("차단(`error`) 위반이 없는가"),
4635
+ checkedFiles: z.number().int().min(0),
4636
+ summary: z.object({
4637
+ rules: z.number().int(),
4638
+ passed: z.number().int(),
4639
+ violated: z.number().int(),
4640
+ findings: z.number().int()
4641
+ }),
4642
+ results: z.array(storefrontRuleResultSchema),
4643
+ findings: z.array(z.object({
4644
+ ruleId: z.string(),
4645
+ rule: z.string(),
4646
+ file: z.string(),
4647
+ line: z.number().int().optional(),
4648
+ detail: z.string(),
4649
+ cause: z.string(),
4650
+ fix: z.string(),
4651
+ example: z.string(),
4652
+ doc: z.string(),
4653
+ docUrl: z.string(),
4654
+ templateFile: z.string().optional(),
4655
+ severity: storefrontRuleSeveritySchema.optional()
4656
+ })),
4657
+ message: z.string(),
4658
+ nextSteps: z.array(z.string())
4659
+ });
4660
+ const storefrontBuildStatusSchema = z.enum([
4661
+ "QUEUED",
4662
+ "RUNNING",
4663
+ "SUCCEEDED",
4664
+ "FAILED",
4665
+ "TIMED_OUT"
4666
+ ]).describe("`QUEUED` 대기 · `RUNNING` 빌드 중 · `SUCCEEDED` 성공(버전 `READY`) · `FAILED` 실패 · `TIMED_OUT` 시간 초과(10분)");
4667
+ /** 끝난 빌드 상태 */
4668
+ const STOREFRONT_BUILD_TERMINAL_STATUSES = [
4669
+ "SUCCEEDED",
4670
+ "FAILED",
4671
+ "TIMED_OUT"
4672
+ ];
4673
+ /**
4674
+ * 빌드 단계 — 이 순서로 돈다. 첫 실패에서 멈추지만 규칙 검증(`VERIFY`)은 실패해도 타입 검사까지 이어서 해 한 번에 알려 준다.
4675
+ * `UPLOAD`는 사이트 버전 Worker 업로드다(성공하면 발행할 수 있다). 값은 늘어날 수 있다.
4676
+ */
4677
+ const STOREFRONT_BUILD_STAGES = [
4678
+ "VERIFY",
4679
+ "RESTORE",
4680
+ "INSTALL",
4681
+ "TYPECHECK",
4682
+ "BUILD",
4683
+ "BUNDLE",
4684
+ "PACK",
4685
+ "UPLOAD"
4686
+ ];
4687
+ const storefrontBuildSchema = z.object({
4688
+ buildId: z.string(),
4689
+ versionId: z.string(),
4690
+ versionNumber: z.number().int().min(1),
4691
+ 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"),
4694
+ logTail: z.string().nullable().describe("실패 단계 출력 꼬리 (최대 8KB). 없으면 null"),
4695
+ stages: z.array(z.object({
4696
+ stage: z.string(),
4697
+ ok: z.boolean(),
4698
+ durationMs: z.number().int().min(0)
4699
+ })).describe("끝난 단계 (도는 순서)"),
4700
+ verify: storefrontVerifyReportSchema.nullable().describe("규칙 검증 보고서(위반 상세는 50건까지). 그 단계까지 가지 못했으면 null"),
4701
+ queuedAt: z.string(),
4702
+ startedAt: z.string().nullable(),
4703
+ finishedAt: z.string().nullable(),
4704
+ durationMs: z.number().int().nullable().describe("빌드 시간 (시작 → 끝)")
4705
+ });
4706
+ const storefrontBuildListQuerySchema = z.object({ limit: z.coerce.number().int().min(1).max(50).optional().describe("기본 20, 최신 먼저") });
4707
+ /**
4708
+ * `POST /v1/storefront/site/versions` — 지금 작업 트리를 고정해 버전을 만든다(빌드 시작). 사이트당 끝나지 않은 빌드는
4709
+ * 하나다(409 `STOREFRONT_BUILD_IN_PROGRESS`). 편집 환경이 없어도 된다.
4710
+ */
4711
+ const createStorefrontVersionRequestSchema = z.object({ message: z.string().max(200).optional().describe("버전 메모 (에이전트는 바꾼 내용 요약)") });
4712
+ /** 버전 만들기 응답 (201) — 버전은 `BUILDING`, 빌드는 `QUEUED`로 시작한다. 결과는 `GET builds/{buildId}`로 본다 */
4713
+ const storefrontVersionCreatedSchema = z.object({
4714
+ version: storefrontVersionSchema,
4715
+ build: storefrontBuildSchema
4716
+ });
4717
+ /**
4718
+ * `POST /v1/storefront/site/editing/preview-link` — 편집 미리보기(dev 서버) 링크. 콘솔에서 로그인한 OWNER·ADMIN만
4719
+ * 받는다(PAT·에이전트 불가). 5분 안에 열면 그 브라우저에 미리보기 호스트 전용 쿠키(12시간)가 생긴다. 편집 환경이
4720
+ * 없으면 409 `EDIT_SESSION_REQUIRED`다(편집 환경이 `STARTING`이면 링크는 주고, 미리보기 화면이 기동 대기를 보여 준다).
4721
+ */
4722
+ const createStorefrontEditPreviewLinkRequestSchema = createStorefrontPreviewLinkRequestSchema;
4723
+ //#endregion
4562
4724
  //#region src/client.ts
4563
4725
  function formBody(fields) {
4564
4726
  return new URLSearchParams(fields).toString();
@@ -4708,10 +4870,7 @@ function createStoreClient(options) {
4708
4870
  }) });
4709
4871
  const put = await fetchFn(ticket.uploadUrl, {
4710
4872
  method: "PUT",
4711
- headers: {
4712
- ...ticket.headers,
4713
- "content-type": file.type || "application/octet-stream"
4714
- },
4873
+ headers: { "content-type": file.type || "application/octet-stream" },
4715
4874
  body: file
4716
4875
  });
4717
4876
  if (!put.ok) throw new ApiError(put.status, "UPLOAD_PUT_FAILED", `업로드에 실패했습니다: ${filename}`);
@@ -4746,37 +4905,8 @@ function createStoreClient(options) {
4746
4905
  updateVariantStock: (productId, variantId, body) => storeHttp.request("PATCH", path`/products/${productId}/variants/${variantId}/stock`, variantStockSchema, { body: variantStockRequestSchema.parse(body) }),
4747
4906
  getDiscount: (productId) => storeHttp.request("GET", path`/products/${productId}/discount`, discountSchema),
4748
4907
  setDiscount: (productId, body) => storeHttp.request("PUT", path`/products/${productId}/discount`, discountSchema, { body: discountRequestSchema.parse(body) }),
4749
- removeDiscount: (productId) => storeHttp.requestVoid("DELETE", path`/products/${productId}/discount`),
4750
- /**
4751
- * 코드 일괄 업로드 (이슈 #48) — 코드 발급·자동 제공 상품의 코드 풀에 올린다. `codes`와 `csv` 중 하나만 보낸다.
4752
- * 받은 코드 수만큼 재고가 는다. 에러: 409 `NOT_CODE_PRODUCT`·`CODE_POOL_NOT_USED`·`CODE_UPLOAD_CONFLICT`, 400 `TOO_MANY_CODES`
4753
- */
4754
- uploadCodes: (productId, body, idempotencyKey) => storeHttp.request("POST", path`/products/${productId}/codes`, fulfillmentCodeBatchSchema, {
4755
- body: fulfillmentCodeUploadRequestSchema.parse(body),
4756
- headers: idempotency(idempotencyKey)
4757
- }),
4758
- /** 코드 목록 — 가린 값(`hint`)만 싣는다. `available`은 필터와 무관한 판매 가능 코드 수다 */
4759
- listCodes: (productId, params) => storeHttp.request("GET", path`/products/${productId}/codes`, fulfillmentCodePageSchema, { query: params }),
4760
- /** 판매 가능 코드 폐기 — 재고가 1 준다. 배정된 코드는 409 `CODE_NOT_AVAILABLE` */
4761
- voidCode: (productId, codeId) => storeHttp.request("POST", path`/products/${productId}/codes/${codeId}/void`, fulfillmentCodeSchema),
4762
- /**
4763
- * 판매 파일 연결 (이슈 #48) — `uploads.create({ purpose: "digital-asset" })`로 받은 `key`에 파일을 올린 뒤 연결한다.
4764
- * 이후 주문부터 이 파일을 준다. 에러: 400 `INVALID_ASSET_KEY`, 409 `NOT_DOWNLOAD_PRODUCT`·`ASSET_ALREADY_ATTACHED`
4765
- */
4766
- attachDigitalAsset: (productId, body) => storeHttp.request("POST", path`/products/${productId}/digital-assets`, digitalAssetSchema, { body: digitalAssetAttachRequestSchema.parse(body) }),
4767
- /** 판매 파일 목록 — 해제한 파일도 싣는다. 객체 키·주소는 없다 */
4768
- listDigitalAssets: (productId) => storeHttp.request("GET", path`/products/${productId}/digital-assets`, z.array(digitalAssetSchema)),
4769
- /** 판매 파일 해제 — 새 주문에 주지 않는다. 이미 산 구매자는 계속 받는다 */
4770
- removeDigitalAsset: (productId, assetId) => storeHttp.request("DELETE", path`/products/${productId}/digital-assets/${assetId}`, digitalAssetSchema),
4771
- /** 판매 파일 확인 주소 — 수명 300초의 서명 주소 */
4772
- previewDigitalAsset: (productId, assetId) => storeHttp.request("POST", path`/products/${productId}/digital-assets/${assetId}/preview-url`, digitalAssetPreviewSchema)
4908
+ removeDiscount: (productId) => storeHttp.requestVoid("DELETE", path`/products/${productId}/discount`)
4773
4909
  },
4774
- fulfillmentCodes: {
4775
- /**
4776
- * 코드 평문 열람 — 콘솔에서 로그인한 OWNER·ADMIN만 된다(403 `INSUFFICIENT_ROLE`). 열람 기록과 감사 로그가 남는다.
4777
- * 평문은 화면에 보인 뒤 저장하지 않는다
4778
- */
4779
- reveal: (codeId) => storeHttp.request("POST", path`/fulfillment-codes/${codeId}/reveal`, fulfillmentCodeRevealSchema) },
4780
4910
  categories: {
4781
4911
  list: (params) => storeHttp.request("GET", "/categories", z.array(categorySchema), { query: params }),
4782
4912
  tree: () => storeHttp.request("GET", "/categories", z.array(categoryTreeNodeSchema), { query: { tree: true } }),
@@ -4843,12 +4973,7 @@ reveal: (codeId) => storeHttp.request("POST", path`/fulfillment-codes/${codeId}/
4843
4973
  headers: idempotency(idempotencyKey)
4844
4974
  }),
4845
4975
  /**
4846
- * 제공 안내 메일 재발송 (이슈 #48) — 제공된 주문상품의 안내 메일을 다시 보낸다. 주문상품당 하루 3회(429).
4847
- * 코드·파일 주소는 메일에 없다
4848
- */
4849
- resendFulfillmentMail: (orderItemId) => storeHttp.request("POST", path`/order-items/${orderItemId}/fulfillment/mail`, fulfillmentMailResendSchema),
4850
- /**
4851
- * 제공 처리 (이슈 #48) — 수동 제공 비실물 주문상품(이용권·방문 수령)을 `PAID`·`CONFIRMED`에서 제공 완료(`DELIVERED`)로
4976
+ * 제공 처리 (이슈 #48) — 배송 없는 주문상품(`MANUAL`)을 `PAID`·`CONFIRMED`에서 제공 완료(`DELIVERED`)로
4852
4977
  * 바꾼다. `note`는 구매자 주문 상세에 이용 안내로 실린다. 배송 상품은 409 `SHIPPING_REQUIRED`
4853
4978
  */
4854
4979
  fulfill: (orderItemId, body = {}, idempotencyKey) => storeHttp.request("POST", path`/order-items/${orderItemId}/fulfill`, orderItemSchema, {
@@ -5161,6 +5286,87 @@ list: (params) => storeHttp.request("GET", "/audit-logs", auditLogPageSchema, {
5161
5286
  remove: (addressId) => storeHttp.requestVoid("DELETE", path`/seller-addresses/${addressId}`),
5162
5287
  setDefault: (addressId) => storeHttp.requestVoid("POST", path`/seller-addresses/${addressId}/default`)
5163
5288
  },
5289
+ /**
5290
+ * 스토어프론트 호스팅 (이슈 #53) — 조회 `storefront:r`, 쓰기 `storefront:rw`(OWNER·ADMIN).
5291
+ * 쓰기 응답은 바뀐 사이트다. Cloudflare 반영은 커밋 뒤라 `provisioning.status`가 잠시 `PENDING`일 수 있다.
5292
+ * 에이전트 토큰의 발행·되돌리기·템플릿 변경·공개 전환·슬러그 변경·끄기는 승인 대기(`ApprovalPendingError`)다.
5293
+ */
5294
+ storefront: {
5295
+ /** `GET /v1/storefront/templates` — 템플릿 카탈로그 */
5296
+ listTemplates: () => storeHttp.request("GET", "/storefront/templates", z.array(storefrontTemplateSchema)),
5297
+ /** `GET /v1/storefront/site` — 스위치 상태와 현재 사이트(없으면 `site: null`) */
5298
+ get: () => storeHttp.request("GET", "/storefront/site", storefrontHostingSchema),
5299
+ /** `GET /v1/storefront/slug-availability?slug=` — 슬러그를 쓸 수 있는가 */
5300
+ checkSlug: (slug) => storeHttp.request("GET", "/storefront/slug-availability", storefrontSlugAvailabilitySchema, { query: { slug } }),
5301
+ /** `POST /v1/storefront/site` (201) — 템플릿으로 사이트를 만들고 v1을 발행한다. 공개 상태는 오픈 준비 중이다 */
5302
+ create: (body) => storeHttp.request("POST", "/storefront/site", storefrontSiteSchema, { body: createStorefrontSiteRequestSchema.parse(body) }),
5303
+ /** `POST /v1/storefront/site/template` — 템플릿을 바꾼다. 새 버전을 만들고 기본으로 곧바로 발행한다 */
5304
+ changeTemplate: (body) => storeHttp.request("POST", "/storefront/site/template", storefrontSiteSchema, { body: changeStorefrontTemplateRequestSchema.parse(body) }),
5305
+ /** `GET /v1/storefront/site/versions` — 버전 목록(최신 먼저) */
5306
+ listVersions: () => storeHttp.request("GET", "/storefront/site/versions", z.array(storefrontVersionSchema)),
5307
+ /** `POST /v1/storefront/site/versions/{versionId}/publish` — 그 버전을 발행한다(이미 발행돼 있으면 그대로 200) */
5308
+ publish: (versionId, body = {}) => storeHttp.request("POST", path`/storefront/site/versions/${versionId}/publish`, storefrontSiteSchema, { body: publishStorefrontVersionRequestSchema.parse(body) }),
5309
+ /** `POST /v1/storefront/site/rollback` — 이전 발행 버전으로 되돌린다(재빌드 없음) */
5310
+ rollback: (body = {}) => storeHttp.request("POST", "/storefront/site/rollback", storefrontSiteSchema, { body: rollbackStorefrontRequestSchema.parse(body) }),
5311
+ /** `PUT /v1/storefront/site/slug` — 슬러그를 바꾼다. 옛 슬러그는 90일 보류되고 새 주소로 301한다 */
5312
+ changeSlug: (body) => storeHttp.request("PUT", "/storefront/site/slug", storefrontSiteSchema, { body: changeStorefrontSlugRequestSchema.parse(body) }),
5313
+ /** `PUT /v1/storefront/site/visibility` — 오픈 준비 중 ↔ 공개 */
5314
+ setVisibility: (body) => storeHttp.request("PUT", "/storefront/site/visibility", storefrontSiteSchema, { body: setStorefrontVisibilityRequestSchema.parse(body) }),
5315
+ /**
5316
+ * `POST /v1/storefront/site/preview-link` — 오픈 준비 중인 사이트를 구성원이 볼 수 있는 5분짜리 링크.
5317
+ * 콘솔에서 로그인한 OWNER·ADMIN만 받는다
5318
+ */
5319
+ createPreviewLink: (body = {}) => storeHttp.request("POST", "/storefront/site/preview-link", storefrontPreviewLinkSchema, { body: createStorefrontPreviewLinkRequestSchema.parse(body) }),
5320
+ /** `POST /v1/storefront/site/disable` — 호스팅을 끈다(사이트 `CLOSED`, 슬러그 90일 보류). 다시 만들 수 있다 */
5321
+ disable: (body) => storeHttp.request("POST", "/storefront/site/disable", storefrontSiteSchema, { body: disableStorefrontHostingRequestSchema.parse(body) }),
5322
+ /** `GET /v1/storefront/site/editing` — 편집 환경 상태(열린 환경·동시 상한·미리보기 인증서) */
5323
+ getEditing: () => storeHttp.request("GET", "/storefront/site/editing", storefrontEditingSchema),
5324
+ /**
5325
+ * `POST /v1/storefront/site/editing` — 편집 환경을 연다(이미 열려 있으면 그대로). 플랫폼 동시 상한에 걸리면 409
5326
+ * `EDIT_CAPACITY_FULL`(`details.retryAfterSeconds`). 띄우는 데 보통 30초가 걸린다(`session.status: STARTING`)
5327
+ */
5328
+ startEditing: () => storeHttp.request("POST", "/storefront/site/editing", storefrontEditingSchema, { body: {} }),
5329
+ /** `DELETE /v1/storefront/site/editing` — 편집 환경을 닫는다(작업 트리는 보존된다) */
5330
+ stopEditing: () => storeHttp.request("DELETE", "/storefront/site/editing", storefrontEditingSchema),
5331
+ /**
5332
+ * `POST /v1/storefront/site/editing/preview-link` — 편집 미리보기(dev 서버) 5분짜리 링크.
5333
+ * 콘솔에서 로그인한 OWNER·ADMIN만 받는다(에이전트 불가)
5334
+ */
5335
+ createEditPreviewLink: (body = {}) => storeHttp.request("POST", "/storefront/site/editing/preview-link", storefrontPreviewLinkSchema, { body: createStorefrontEditPreviewLinkRequestSchema.parse(body) }),
5336
+ /** `GET /v1/storefront/site/files?prefix=` — 작업 트리 파일 목록 */
5337
+ listFiles: (params = {}) => storeHttp.request("GET", "/storefront/site/files", storefrontFileListSchema, { query: params }),
5338
+ /** `GET /v1/storefront/site/files/content?path=` — 파일 하나 읽기 */
5339
+ readFile: (filePath) => storeHttp.request("GET", "/storefront/site/files/content", storefrontFileContentSchema, { query: { path: filePath } }),
5340
+ /**
5341
+ * `POST /v1/storefront/site/files/batch` — 여러 파일 쓰기(해시 조건). 조건이 하나라도 어긋나면 아무것도 쓰지 않고 409
5342
+ * `FILE_CHANGED`(`details.conflicts`). 편집 환경이 없으면 연다
5343
+ */
5344
+ writeFiles: (body) => storeHttp.request("POST", "/storefront/site/files/batch", storefrontFileWriteResultSchema, { body: writeStorefrontFilesRequestSchema.parse(body) }),
5345
+ /** `POST /v1/storefront/site/files/delete` */
5346
+ deleteFile: (body) => storeHttp.request("POST", "/storefront/site/files/delete", storefrontFileDeleteResultSchema, { body: deleteStorefrontFileRequestSchema.parse(body) }),
5347
+ /** `POST /v1/storefront/site/files/move` */
5348
+ moveFile: (body) => storeHttp.request("POST", "/storefront/site/files/move", storefrontFileMoveResultSchema, { body: moveStorefrontFileRequestSchema.parse(body) }),
5349
+ /** `POST /v1/storefront/site/files/search` — 내용 검색 */
5350
+ searchFiles: (body) => storeHttp.request("POST", "/storefront/site/files/search", storefrontFileSearchResultSchema, { body: searchStorefrontFilesRequestSchema.parse(body) }),
5351
+ /**
5352
+ * `POST /v1/storefront/site/checks` (202) — 타입 검사·lint·test 하나를 시작한다. 결과는 `getCheck`로 끝날 때까지 조회한다
5353
+ * (`STOREFRONT_CHECK_TERMINAL_STATUSES`)
5354
+ */
5355
+ runCheck: (body) => storeHttp.request("POST", "/storefront/site/checks", storefrontCheckSchema, { body: runStorefrontCheckRequestSchema.parse(body) }),
5356
+ /** `GET /v1/storefront/site/checks/{checkId}` — 검사 상태·종료 코드·출력 꼬리 */
5357
+ getCheck: (checkId) => storeHttp.request("GET", path`/storefront/site/checks/${checkId}`, storefrontCheckSchema),
5358
+ /** `POST /v1/storefront/site/verify` — 작업 트리 규칙 검증(`verify_storefront`와 같은 보고서) */
5359
+ verify: () => storeHttp.request("POST", "/storefront/site/verify", storefrontVerifyReportSchema, { body: {} }),
5360
+ /**
5361
+ * `POST /v1/storefront/site/versions` (201) — 지금 작업 트리로 버전을 만든다(빌드 시작). 버전은 `BUILDING`, 빌드가
5362
+ * 끝나면 `READY` 또는 `FAILED`다
5363
+ */
5364
+ createVersion: (body = {}) => storeHttp.request("POST", "/storefront/site/versions", storefrontVersionCreatedSchema, { body: createStorefrontVersionRequestSchema.parse(body) }),
5365
+ /** `GET /v1/storefront/site/builds?limit=` — 빌드 목록(최신 먼저) */
5366
+ listBuilds: (params = {}) => storeHttp.request("GET", "/storefront/site/builds", z.array(storefrontBuildSchema), { query: params }),
5367
+ /** `GET /v1/storefront/site/builds/{buildId}` — 빌드 상태·실패 단계·로그 꼬리·규칙 결과 */
5368
+ getBuild: (buildId) => storeHttp.request("GET", path`/storefront/site/builds/${buildId}`, storefrontBuildSchema)
5369
+ },
5164
5370
  store: {
5165
5371
  get: () => storeHttp.request("GET", "/store", storeSchema),
5166
5372
  /**
@@ -5289,7 +5495,8 @@ const STORE_SCOPE_RESOURCES = {
5289
5495
  member: ["r", "rw"],
5290
5496
  customer: ["r", "rw"],
5291
5497
  audit: ["r"],
5292
- promotion: ["r", "rw"]
5498
+ promotion: ["r", "rw"],
5499
+ storefront: ["r", "rw"]
5293
5500
  };
5294
5501
  const storeScopeResourceSchema = z.enum(Object.keys(STORE_SCOPE_RESOURCES)).describe("스코프 리소스");
5295
5502
  /** 카탈로그상 존재하는 스코프 문자열인가 (예: `settlement:rw`는 false) */
@@ -5318,7 +5525,8 @@ const STORE_ROLE_SCOPES = {
5318
5525
  "member:rw",
5319
5526
  "customer:rw",
5320
5527
  "audit:r",
5321
- "promotion:rw"
5528
+ "promotion:rw",
5529
+ "storefront:rw"
5322
5530
  ],
5323
5531
  ADMIN: [
5324
5532
  "product:rw",
@@ -5333,7 +5541,8 @@ const STORE_ROLE_SCOPES = {
5333
5541
  "member:rw",
5334
5542
  "customer:rw",
5335
5543
  "audit:r",
5336
- "promotion:rw"
5544
+ "promotion:rw",
5545
+ "storefront:rw"
5337
5546
  ],
5338
5547
  STAFF: [
5339
5548
  "product:rw",
@@ -5397,20 +5606,19 @@ const orderWebhookEventDataSchema = z.object({
5397
5606
  status: orderItemStatusSchema.describe("이벤트 발생 시점의 주문 대표 상태"),
5398
5607
  testPayment: z.boolean().optional().default(false).describe("테스트 결제(샌드박스 결제) 주문인가. true면 실제로 돈이 오가지 않았다. 이 필드가 없는 이전 이벤트는 false로 읽는다"),
5399
5608
  couponDiscountAmount: z.number().int().optional().default(0).describe("주문이 받은 상품·주문 쿠폰 할인 합계 (원). 즉시할인(`discountAmount`)과 별개다. 쿠폰이 없거나 이 필드가 없는 이전 이벤트는 0으로 읽는다"),
5400
- orderItemId: z.string().optional().describe("이벤트를 만든 주문상품 ID. 비실물 상품 제공으로 보내는 `ORDER.DELIVERED`에만 실린다. 택배 배송완료와 그 밖의 주문 이벤트에는 없다"),
5401
- fulfillmentSnapshot: orderItemFulfillmentSnapshotResponseSchema.optional().describe("이벤트를 만든 주문상품의 주문 시점 이행 스냅샷(주문상품 응답의 `fulfillmentSnapshot`과 같다, 이슈 #57). 비실물 상품의 제공 완료는 `ORDER.DELIVERED`로도 오고 `type`이 `SHIPPING`이 아니다. `orderItemId`가 있는 이벤트에만 실린다")
5609
+ orderItemId: z.string().optional().describe("이벤트를 만든 주문상품 ID. 배송 없는 상품 제공으로 보내는 `ORDER.DELIVERED`에만 실린다. 택배 배송완료와 그 밖의 주문 이벤트에는 없다"),
5610
+ fulfillmentSnapshot: orderItemFulfillmentSnapshotResponseSchema.optional().describe("이벤트를 만든 주문상품의 주문 시점 이행 스냅샷(주문상품 응답의 `fulfillmentSnapshot`과 같다, 이슈 #57). 배송 없는 상품의 제공 완료는 `ORDER.DELIVERED`로도 오고 `type`이 `MANUAL`이다. `orderItemId`가 있는 이벤트에만 실린다")
5402
5611
  });
5403
5612
  /**
5404
- * `ORDER.FULFILLED` — 비실물 주문상품(파일·코드·이용권·방문 수령)을 제공했다(이슈 #48). 자동 제공은 결제 확정과 함께,
5405
- * 수동 제공은 셀러의 제공 처리(`POST /v1/order-items/{orderItemId}/fulfill`)로 온다. 기존 구독자를 위해 같은 순간
5406
- * `ORDER.DELIVERED`도 함께 보낸다. **코드·파일 주소는 싣지 않는다** — 관리 API로 조회한다.
5613
+ * `ORDER.FULFILLED` — 배송 없는 주문상품(`MANUAL`)을 제공했다(이슈 #48). 셀러의 제공 처리
5614
+ * (`POST /v1/order-items/{orderItemId}/fulfill`)로 온다. 기존 구독자를 위해 같은 순간 `ORDER.DELIVERED`도 함께 보낸다.
5407
5615
  */
5408
5616
  const orderFulfilledWebhookEventDataSchema = z.object({
5409
5617
  orderId: z.string().describe("주문 ID"),
5410
5618
  orderItemId: z.string().describe("제공한 주문상품 ID"),
5411
5619
  status: orderItemStatusSchema.describe("이벤트 발생 시점의 주문 대표 상태"),
5412
5620
  fulfillmentSnapshot: orderItemFulfillmentSnapshotResponseSchema.describe("제공한 주문상품의 주문 시점 이행 스냅샷(주문상품 응답의 `fulfillmentSnapshot`과 같다, 이슈 #57)"),
5413
- fulfillment: z.object({ mode: z.string().describe("실제 제공 방식. `AUTO`(결제 확정과 함께)·`MANUAL`(셀러 제공 처리). 주문상품 제공 기록의 `mode`와 같다") }).describe("제공 기록"),
5621
+ fulfillment: z.object({ mode: z.string() }).optional().describe("**Deprecated** — 항상 `{ mode: \"MANUAL\" }`이다. 자동 제공을 없앴고(이슈 #48) store-sdk 0.19 이하 호환으로만 싣는다. 게시된 연동이 store-sdk 0.20 이상으로 넘어간 뒤 다음 릴리스에서 뺀다"),
5414
5622
  fulfilledAt: z.string().describe("제공 시각 (ISO 8601). 주문상품의 `deliveredAt`과 같다"),
5415
5623
  testPayment: z.boolean().describe("테스트 결제(샌드박스 결제) 주문인가. true면 실제로 돈이 오가지 않았다")
5416
5624
  });
@@ -5669,4 +5877,4 @@ function createStoreSession(options) {
5669
5877
  };
5670
5878
  }
5671
5879
  //#endregion
5672
- 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, DOWNLOAD_DEFAULTS, FULFILLMENT_RULES, MAX_ANALYTICS_ALLOWED_ORIGINS, MAX_AUTO_TRANSITION_DAYS, MAX_CODES_PER_UPLOAD, MAX_CODE_LENGTH, 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, PRIVATE_UPLOAD_HEADERS, REFUND_POLICY_DEFAULTS, REFUND_POLICY_DELIVERY_FEE_KEYS, REMOTE_AREA_ZIP_RANGES, RESERVED_STORE_CODES, SHIPPING_POLICY_DEFAULTS, 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, autoFulfillableOf, buildAuthorizeUrl, buildQuery, bulkResultSchema, cancelBySellerRequestSchema, cancelBySellerResultSchema, carrierSchema, cashReceiptSchema, categoryAttributeSchema, categoryCreateRequestSchema, categoryDeleteQuerySchema, categoryDeleteStrategySchema, categoryListQuerySchema, categoryPatchRequestSchema, categoryProductSortSchema, categorySchema, categorySearchResultSchema, categoryTreeNodeSchema, categoryWithPathSchema, 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, customerAddressSchema, customerConsentListQuerySchema, customerConsentSchema, customerConsentStateSchema, customerConsentsSchema, customerCouponSchema, customerDetailSchema, customerInquiryCategorySchema, customerInquiryListQuerySchema, customerListQuerySchema, customerOrderSchema, customerSortSchema, customerStatsSchema, customerSummarySchema, customerWithdrawalBlockedDetailsSchema, deliveryBundleKeyOf, deliveryFeeWaiverSchema, deliveryInfoSchema, deliveryMethodSchema, deliveryStatusSchema, deliveryTypeSchema, digitalAssetAttachRequestSchema, digitalAssetPreviewSchema, digitalAssetSchema, discountRequestSchema, discountSchema, dispatchRequestSchema, easyPayProviderResponseSchema, easyPayProviderSchema, eventTypeSchema, exchangeAuthorizationCode, exchangeStoreTokenViaOAuth, fulfillBulkRequestSchema, fulfillRequestSchema, fulfillmentCodeBatchSchema, fulfillmentCodeListQuerySchema, fulfillmentCodePageSchema, fulfillmentCodeRejectionSchema, fulfillmentCodeRevealSchema, fulfillmentCodeSchema, fulfillmentCodeStatusSchema, fulfillmentCodeUploadRequestSchema, fulfillmentMailResendSchema, fulfillmentModeOf, fulfillmentModeSchema, fulfillmentRuleOf, fulfillmentTypeOf, fulfillmentTypeResponseSchema, fulfillmentTypeSchema, holdOrderItemRequestSchema, inquiryListQuerySchema, inquirySchema, inquiryWebhookEventDataSchema, inquiryWebhookEventSchema, invitationDeliveryStatusSchema, invitationListQuerySchema, invitationSchema, inviteMemberRequestSchema, isCouponTargetLine, isReservedStoreCode, isStoreScope, isWebOrigin, issueCouponRequestSchema, issueCouponResultSchema, liveGateConditionCodeSchema, liveGateConditionSchema, liveGateErrorDetailsSchema, liveGateSchema, loginProviderSchema, loginProviderStatusSchema, loginProviderTestSchema, loginSettingsSchema, maskedSecretSchema, mcpRuleResultSchema, mcpToolEventSchema, myAccountSchema, myStoreSchema, normalizeCouponCode, oauthClientRegistrationRequestSchema, oauthClientRegistrationResponseSchema, oauthTokenResponseSchema, openFulfillmentUnion, optionGroupRequestSchema, optionGroupSchema, optionModelRequestSchema, optionModelSchema, optionValueRequestSchema, optionValueSchema, orderCanceledWebhookEventDataSchema, orderCanceledWebhookEventSchema, orderDiscountSchema, orderFulfilledWebhookEventDataSchema, orderFulfilledWebhookEventSchema, orderItemCodeSchema, orderItemDownloadSchema, 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, purchaseDecisionExtendResultSchema, purchaseDecisionRequestResultSchema, quoteDelivery, quotePricing, refreshUserToken, refundPolicyPatchSchema, refundPolicySchema, rejectClaimRequestSchema, remoteAreaLabelOf, remoteAreaOf, remoteAreaSchema, remoteSurchargeOf, requiresCollection, requiresShipping, resolveFulfillmentMode, resolveRefundPolicy, resolveShippingPolicy, reviewListQuerySchema, reviewReportResultSchema, reviewSchema, reviewSortSchema, reviewWebhookEventDataSchema, reviewWebhookEventSchema, salesReportGroupBySchema, salesReportQuerySchema, salesReportRowSchema, salesReportSchema, salesTotalsSchema, saveLoginProviderRequestSchema, scopeAccessSchema, sellerAddressListQuerySchema, sellerAddressRequestSchema, sellerAddressSchema, sellerCustomerInquirySchema, settleCouponShortfall, settlementDetailQuerySchema, settlementDetailSchema, settlementListQuerySchema, settlementStatusSchema, settlementSummarySchema, settlementWithDetailsSchema, shippingAddressSchema, shippingPolicyPatchSchema, shippingPolicySchema, stockTrackingOf, storeCodeSchema, storeMemberRoleUpdatedSchema, storeMemberSchema, storeRoleSchema, storeSchema, storeScopeResourceSchema, storeTokenResponseSchema, tossPaymentsCredentialsInputSchema, trackingStageResponseSchema, trackingUpdateResultSchema, updateCouponRequestSchema, updateLoginProviderRequestSchema, updateLoginSettingsRequestSchema, updateMemberRoleRequestSchema, updatePaymentProviderRequestSchema, updatePaymentSettingsRequestSchema, updateStoreRequestSchema, uploadExtensionOf, uploadPurposeSchema, uploadRejection, uploadRequestSchema, uploadTicketSchema, upsertPortOneProviderRequestSchema, upsertTossPaymentsProviderRequestSchema, userTokenResponseSchema, usesCodePool, variantStockRequestSchema, variantStockSchema, verifyPaymentProviderRequestSchema, webhookCreatedSchema, webhookDeliveryDetailSchema, webhookDeliveryErrorCodeSchema, webhookDeliveryListQuerySchema, webhookDeliveryResourceSchema, webhookDeliverySchema, webhookDeliveryStatusSchema, webhookEventSchema, webhookFailureReasonSchema, webhookRedeliverFailedResultSchema, webhookRequestHeaderSchema, webhookRequestSchema, webhookResourceTypeSchema, webhookSchema, webhookSecretRotatedSchema, webhookStatusSchema, webhookTestRequestSchema, webhookTestResultSchema, withdrawCustomerRequestSchema, withdrawalRestrictedOf, writeDryRunResultSchema, writePreviewSchema };
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 };