@sayren/store-sdk 0.41.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { A as auditLogPageSchema, C as approvalPageSchema, D as auditActorTypeSchema, E as auditActorSchema, M as auditLogSourceSchema, N as writeDryRunResultSchema, O as auditLogListQuerySchema, P as writePreviewSchema, S as approvalListQuerySchema, T as approvalStatusSchema, _ as resolveToken, a as WEBHOOK_HEADERS, b as agentPolicyDecisionSchema, c as APPROVAL_ALREADY_DECIDED_CODE, d as ApprovalPendingError, f as SAYREN_API_BASE_URL, g as path, h as createHttp, i as WEBHOOK_EVENT_TYPE_HEADER, j as auditLogSchema, k as auditLogOutcomeSchema, l as APPROVAL_REQUIRED_CODE, m as buildQuery, n as WEBHOOK_DELIVERY_ID_HEADER, o as WEBHOOK_SIGNATURE_HEADER, p as SAYREN_API_ORIGIN, r as WEBHOOK_EVENT_ID_HEADER, s as WEBHOOK_USER_AGENT, t as WEBHOOK_DEFAULT_TOLERANCE_SEC, u as ApiError, v as toApiError, w as approvalRequestSchema, x as approvalDecisionRequestSchema, y as unwrapData } from "./webhook-headers-D974dE4T.mjs";
1
+ import { A as auditLogPageSchema, C as approvalPageSchema, D as auditActorTypeSchema, E as auditActorSchema, F as strictRequest, M as auditLogSourceSchema, N as writeDryRunResultSchema, O as auditLogListQuerySchema, P as writePreviewSchema, S as approvalListQuerySchema, T as approvalStatusSchema, _ as resolveToken, a as WEBHOOK_HEADERS, b as agentPolicyDecisionSchema, c as APPROVAL_ALREADY_DECIDED_CODE, d as ApprovalPendingError, f as SAYREN_API_BASE_URL, g as path, h as createHttp, i as WEBHOOK_EVENT_TYPE_HEADER, j as auditLogSchema, k as auditLogOutcomeSchema, l as APPROVAL_REQUIRED_CODE, m as buildQuery, n as WEBHOOK_DELIVERY_ID_HEADER, o as WEBHOOK_SIGNATURE_HEADER, p as SAYREN_API_ORIGIN, r as WEBHOOK_EVENT_ID_HEADER, s as WEBHOOK_USER_AGENT, t as WEBHOOK_DEFAULT_TOLERANCE_SEC, u as ApiError, v as toApiError, w as approvalRequestSchema, x as approvalDecisionRequestSchema, y as unwrapData } from "./webhook-headers-D0E3wPpS.mjs";
2
2
  import { z } from "zod";
3
3
  //#region src/schemas/auth.ts
4
4
  const storeRoleSchema = z.enum([
@@ -1361,7 +1361,7 @@ const businessInfoResponseShape = {
1361
1361
  const autoDeliverDaysDescription = "발송 후 자동 배송완료까지의 일수(0~30, 기본 7). 발송(`DISPATCHED`)·배송중(`DELIVERING`) 주문상품을 이 기간이 지나면 배송완료(`DELIVERED`)로 바꾼다. `0`이면 자동 배송완료를 하지 않는다";
1362
1362
  const autoConfirmDaysDescription = "배송완료 후 자동 구매확정까지의 일수(0~30, 기본 7). 배송완료 시점에 구매확정 예정일(`autoDecisionDate`)을 이 값으로 잡고, 그 날짜가 지나면 구매확정(`PURCHASE_DECIDED`)으로 바꾼다. 진행 중인 클레임이 있으면 건너뛴다. `0`이면 자동 구매확정을 하지 않는다";
1363
1363
  /** `PATCH /v1/store` 본문 — 바꿀 필드만 보낸다 */
1364
- const updateStoreRequestSchema = z.object({
1364
+ const updateStoreRequestSchema = strictRequest(z.object({
1365
1365
  name: z.string().min(2).max(50).optional(),
1366
1366
  logoUrl: z.url().optional(),
1367
1367
  customerCenterPhone: z.string().nullable().optional().describe("고객센터 전화번호. `null`을 주면 지운다"),
@@ -1380,7 +1380,7 @@ const updateStoreRequestSchema = z.object({
1380
1380
  termsUrl: policyDocumentUrlSchema.optional().describe("이용약관 문서 주소(https). 동의 시점의 값이 동의 이력에 복사된다. `null`을 주면 지운다"),
1381
1381
  privacyUrl: policyDocumentUrlSchema.optional().describe("개인정보 수집이용 동의 문서 주소(https). `null`을 주면 지운다"),
1382
1382
  defaultLocale: localeCodeSchema.optional().describe("상점 기본 언어(이슈 #134). 상품·카테고리 필드가 이 언어 값이고 번역이 없는 칸은 이 값으로 보인다. 바꿔도 저장된 값은 그대로다")
1383
- }).refine((value) => Object.values(value).some((v) => v !== void 0), { message: "최소 1개 필드가 필요합니다" });
1383
+ }).refine((value) => Object.values(value).some((v) => v !== void 0), { message: "최소 1개 필드가 필요합니다" }));
1384
1384
  const storeSchema = z.object({
1385
1385
  storeId: z.string(),
1386
1386
  name: z.string(),
@@ -1457,12 +1457,12 @@ const invitationSchema = z.object({
1457
1457
  acceptedAt: z.string().nullable().default(null)
1458
1458
  });
1459
1459
  /** `POST /v1/store/invitations` 요청 — ADMIN 초대는 OWNER만 할 수 있다 */
1460
- const inviteMemberRequestSchema = z.object({
1460
+ const inviteMemberRequestSchema = strictRequest(z.object({
1461
1461
  email: z.email(),
1462
1462
  role: z.enum(["ADMIN", "STAFF"])
1463
- });
1463
+ }));
1464
1464
  /** `PATCH /v1/store/members/{userId}` 요청 */
1465
- const updateMemberRoleRequestSchema = z.object({ role: z.enum(["ADMIN", "STAFF"]) });
1465
+ const updateMemberRoleRequestSchema = strictRequest(z.object({ role: z.enum(["ADMIN", "STAFF"]) }));
1466
1466
  /** `GET /v1/store/invitations` 쿼리 — OpenAPI 문서용 형태 */
1467
1467
  const invitationListQuerySchema = z.object({ status: invitationSchema.shape.status.optional().describe("초대 상태 필터") });
1468
1468
  /** `PATCH /v1/store/members/{userId}` 응답 — 바뀐 역할 */
@@ -1489,7 +1489,7 @@ const sellerAddressSchema = z.object({
1489
1489
  inventoryLocationId: z.string().nullable().optional().describe("이 주소의 재고 위치 ID — 출고지면 배송 주문을 이 위치 재고로 팔고, 반품지면 반품을 이 위치로 들인다. 위치를 하나만 쓰는 상점은 기본 위치다"),
1490
1490
  createdAt: z.string()
1491
1491
  });
1492
- const sellerAddressRequestSchema = z.object({
1492
+ const sellerAddressRequestSchema = strictRequest(z.object({
1493
1493
  type: addressTypeSchema,
1494
1494
  name: z.string().min(1).max(50),
1495
1495
  contactName: z.string().min(1),
@@ -1498,7 +1498,7 @@ const sellerAddressRequestSchema = z.object({
1498
1498
  address1: z.string().min(1),
1499
1499
  address2: z.string().optional(),
1500
1500
  inventoryLocationId: z.string().min(1).optional().describe("재고 위치 ID. 등록에서 생략하면 기본 위치, 수정에서 생략하면 지금 연결을 유지한다")
1501
- });
1501
+ }));
1502
1502
  //#endregion
1503
1503
  //#region src/schemas/storefront-hosting.ts
1504
1504
  /**
@@ -1662,11 +1662,11 @@ const TEMPLATE_STORE_LOCKED_BUSINESS_FIELDS = [
1662
1662
  "customerCenterPhone",
1663
1663
  "customerCenterEmail"
1664
1664
  ];
1665
- const updateStoreTemplateRequestSchema = z.object({
1665
+ const updateStoreTemplateRequestSchema = strictRequest(z.object({
1666
1666
  name: z.string().trim().min(1).max(40).describe("템플릿 이름(1~40자)"),
1667
1667
  description: z.string().trim().max(200).describe("템플릿 소개(200자까지)"),
1668
1668
  thumbnailUrl: z.url({ protocol: /^https$/ }).max(2048).nullable().describe("썸네일 이미지 주소(https). 이미지 업로드(`POST /v1/uploads`)로 받은 주소를 쓴다. 없으면 null")
1669
- });
1669
+ }));
1670
1670
  /**
1671
1671
  * 버전 상태 — 템플릿 버전(`source: TEMPLATE`)은 템플릿 산출물을 그대로 쓰므로 만들 때 곧바로 `READY`다. 편집 버전
1672
1672
  * (`source: EDIT`, M2b)은 작업 트리 스냅샷을 빌드하는 동안 `BUILDING`이고, 빌드·규칙 검증을 통과하면 `READY`, 실패하면
@@ -1827,11 +1827,11 @@ const storefrontDeploymentSchema = z.object({
1827
1827
  attempts: z.number().int().describe("연속 실패 횟수"),
1828
1828
  nextAttemptAt: z.string().nullable().describe("다음 자동 시도 시각 (ISO 8601)")
1829
1829
  });
1830
- const createStorefrontSiteRequestSchema = z.object({
1830
+ const createStorefrontSiteRequestSchema = strictRequest(z.object({
1831
1831
  templateId: z.string().min(1).max(64).describe("템플릿 id"),
1832
1832
  slug: storefrontSlugSchema.optional().describe("주소 슬러그. 생략하면 상점 코드"),
1833
1833
  copyContent: z.boolean().optional().describe("템플릿 상점의 템플릿일 때만 — true면 템플릿 상점의 콘텐츠(레이아웃, 상품·카테고리·컬렉션·주문 흐름·배송·환불 정책)를 이 상점으로 복사한다. 쿠폰·적립금·리뷰는 복사하지 않는다. 생략하거나 false면 레이아웃 콘텐츠를 비우고 빌드한다")
1834
- });
1834
+ }));
1835
1835
  /** @deprecated 템플릿 변경은 지원하지 않는다(항상 409 `TEMPLATE_CHANGE_NOT_SUPPORTED`). 호환을 위해 스키마만 남겼다 */
1836
1836
  const changeStorefrontTemplateRequestSchema = z.object({
1837
1837
  templateId: z.string().min(1).max(64).describe("바꿀 템플릿 id"),
@@ -1843,28 +1843,28 @@ const changeStorefrontTemplateRequestSchema = z.object({
1843
1843
  * 지금 발행 버전이 그 값일 때만 바꾼다(화면을 본 뒤 다른 사람이 바꿨으면 409 `STOREFRONT_SITE_CHANGED`).
1844
1844
  * 이미 그 버전이 발행돼 있으면 아무것도 바꾸지 않고 200이다(같은 요청의 재실행이 안전하다).
1845
1845
  */
1846
- const publishStorefrontVersionRequestSchema = z.object({ expectedPublishedVersionId: z.string().nullable().optional().describe("화면에서 본 발행 버전 id (발행 전이었으면 null). 생략하면 확인하지 않는다") });
1847
- const rollbackStorefrontRequestSchema = z.object({
1846
+ const publishStorefrontVersionRequestSchema = strictRequest(z.object({ expectedPublishedVersionId: z.string().nullable().optional().describe("화면에서 본 발행 버전 id (발행 전이었으면 null). 생략하면 확인하지 않는다") }));
1847
+ const rollbackStorefrontRequestSchema = strictRequest(z.object({
1848
1848
  versionId: z.string().optional().describe("되돌릴 버전. 생략하면 지금 버전 직전에 발행했던 버전"),
1849
1849
  expectedPublishedVersionId: z.string().nullable().optional()
1850
- });
1851
- const changeStorefrontSlugRequestSchema = z.object({ slug: storefrontSlugSchema });
1852
- const setStorefrontVisibilityRequestSchema = z.object({
1850
+ }));
1851
+ const changeStorefrontSlugRequestSchema = strictRequest(z.object({ slug: storefrontSlugSchema }));
1852
+ const setStorefrontVisibilityRequestSchema = strictRequest(z.object({
1853
1853
  visibility: storefrontVisibilitySchema,
1854
1854
  expectedVisibility: storefrontVisibilitySchema.optional().describe("화면에서 본 공개 상태. 주면 지금 값이 같을 때만 바꾼다")
1855
- });
1855
+ }));
1856
1856
  /** 호스팅 끄기 — 사이트를 닫고(`CLOSED`) 슬러그를 보류한다. 확인 입력은 받지 않는다(2026-10-22) */
1857
- const disableStorefrontHostingRequestSchema = z.object({ confirmSlug: z.string().max(200).optional().describe("쓰지 않는다 — 보내도 무시한다(옛 확인 입력)") });
1857
+ const disableStorefrontHostingRequestSchema = strictRequest(z.object({ confirmSlug: z.string().max(200).optional().describe("쓰지 않는다 — 보내도 무시한다(옛 확인 입력)") }));
1858
1858
  const storefrontSlugAvailabilityQuerySchema = z.object({ slug: z.string().describe("확인할 슬러그") });
1859
1859
  /**
1860
1860
  * 버전 미리보기 링크 — 상점 플랫폼에서 로그인한 OWNER·ADMIN만 받는다(PAT·에이전트 불가). 버전마다 미리보기 전용 주소가 있고
1861
1861
  * (오픈 준비 중이어도 실제 화면), 링크를 5분 안에 열면 그 브라우저에 그 주소 전용 12시간 쿠키가 생긴다. 사이트 주소는 오픈 전이면
1862
1862
  * 누구에게나 「오픈 준비 중」이다. 미리보기 주소에 쿠키 없이 들어오면 상점 플랫폼 로그인과 OWNER·ADMIN 확인을 거친다.
1863
1863
  */
1864
- const createStorefrontPreviewLinkRequestSchema = z.object({
1864
+ const createStorefrontPreviewLinkRequestSchema = strictRequest(z.object({
1865
1865
  path: z.string().max(512).optional().describe("열 경로 (`/`로 시작). 생략하면 `/`"),
1866
1866
  versionId: z.string().max(64).optional().describe("볼 버전. 생략하면 발행 버전이다. 보관 기간이 지난 버전(`previewable: false`)은 409 `STOREFRONT_VERSION_NOT_PREVIEWABLE`")
1867
- });
1867
+ }));
1868
1868
  const storefrontPreviewLinkSchema = z.object({
1869
1869
  url: z.string().describe("미리보기 링크 — 5분 안에 연다"),
1870
1870
  editorUrl: z.string().optional().describe("편집 미리보기만: AI 편집 패널이 있는 미리보기 화면 링크(5분). 사이트는 그 안에 따로 뜨고 AI 자격은 사이트 코드에 닿지 않는다"),
@@ -2050,7 +2050,7 @@ const storefrontSiteDomainsSchema = z.object({
2050
2050
  target: z.string().describe("CNAME 대상 (예: `sites.sayren.co`)"),
2051
2051
  domains: z.array(storefrontSiteDomainSchema).describe("대표 주소 먼저, 그다음 추가한 순서")
2052
2052
  });
2053
- const addStorefrontDomainRequestSchema = z.object({ hostname: z.string().min(1).max(300).describe("연결할 도메인 (예: `shop.brand.com`). 서버가 소문자·punycode로 정규화한다") });
2053
+ const addStorefrontDomainRequestSchema = strictRequest(z.object({ hostname: z.string().min(1).max(300).describe("연결할 도메인 (예: `shop.brand.com`). 서버가 소문자·punycode로 정규화한다") }));
2054
2054
  /** 플랫폼 전체 동시 편집 환경 상한 */
2055
2055
  const STOREFRONT_EDIT_CAPACITY = 20;
2056
2056
  /** 활동이 없으면 편집 환경이 잠드는 시간(분). 작업 트리는 보존된다 */
@@ -2143,11 +2143,11 @@ const storefrontEditSessionSchema = z.object({
2143
2143
  * 편집 환경이 켜져 있지 않으면 409 `EDIT_SESSION_REQUIRED`
2144
2144
  */
2145
2145
  /** `POST /v1/storefront/site/editing/git/merge` — 켜진 편집 환경 작업 트리에 운영 브랜치 최신 커밋을 합친다(#138, Git 원천 사이트) */
2146
- const storefrontGitMergeRequestSchema = z.object({ action: z.enum([
2146
+ const storefrontGitMergeRequestSchema = strictRequest(z.object({ action: z.enum([
2147
2147
  "merge",
2148
2148
  "continue",
2149
2149
  "abort"
2150
- ]).default("merge").describe("`merge` 최신 운영 브랜치를 합친다 · `continue` 충돌 표시를 고친 뒤 합치기를 끝낸다 · `abort` 합치기를 취소하고 합치기 전으로 돌린다") });
2150
+ ]).default("merge").describe("`merge` 최신 운영 브랜치를 합친다 · `continue` 충돌 표시를 고친 뒤 합치기를 끝낸다 · `abort` 합치기를 취소하고 합치기 전으로 돌린다") }));
2151
2151
  const storefrontGitMergeSchema = z.object({
2152
2152
  status: z.enum([
2153
2153
  "MERGED",
@@ -2222,12 +2222,12 @@ const storefrontFileContentSchema = z.object({
2222
2222
  * 해시가 같아야 한다. 조건을 **모두** 확인한 뒤 쓰고, 하나라도 어긋나면 아무것도 쓰지 않는다(409 `FILE_CHANGED`,
2223
2223
  * `error.details.conflicts`).
2224
2224
  */
2225
- const writeStorefrontFilesRequestSchema = z.object({ files: z.array(z.object({
2225
+ const writeStorefrontFilesRequestSchema = strictRequest(z.object({ files: z.array(z.object({
2226
2226
  path: storefrontFilePathSchema,
2227
2227
  content: z.string().describe("내용 (`encoding`에 따라 텍스트 또는 base64)"),
2228
2228
  encoding: storefrontFileEncodingSchema.optional().describe("기본 `utf8`"),
2229
2229
  expectedHash: storefrontFileHashSchema.nullable().optional().describe("쓰기 조건. 생략 = 조건 없음 · null = 새 파일 · 값 = 지금 해시가 같을 때만")
2230
- })).min(1).max(50).refine((files) => new Set(files.map((file) => file.path)).size === files.length, { message: "같은 경로를 두 번 실을 수 없습니다" }) });
2230
+ })).min(1).max(50).refine((files) => new Set(files.map((file) => file.path)).size === files.length, { message: "같은 경로를 두 번 실을 수 없습니다" }) }));
2231
2231
  const storefrontFileWriteResultSchema = z.object({ files: z.array(storefrontFileEntrySchema).describe("쓴 파일의 새 해시·크기 (요청 순서)") });
2232
2232
  /** 409 `FILE_CHANGED`의 `error.details` */
2233
2233
  const storefrontFileConflictDetailsSchema = z.object({ conflicts: z.array(z.object({
@@ -2235,21 +2235,21 @@ const storefrontFileConflictDetailsSchema = z.object({ conflicts: z.array(z.obje
2235
2235
  currentHash: storefrontFileHashSchema.nullable().describe("지금 해시. 파일이 없으면 null")
2236
2236
  })) });
2237
2237
  /** `POST /v1/storefront/site/files/delete` — 없는 파일이면 `deleted: false`(조건을 주면 409 `FILE_CHANGED`) */
2238
- const deleteStorefrontFileRequestSchema = z.object({
2238
+ const deleteStorefrontFileRequestSchema = strictRequest(z.object({
2239
2239
  path: storefrontFilePathSchema,
2240
2240
  expectedHash: storefrontFileHashSchema.optional().describe("주면 지금 해시가 같을 때만 지운다")
2241
- });
2241
+ }));
2242
2242
  const storefrontFileDeleteResultSchema = z.object({
2243
2243
  path: z.string(),
2244
2244
  deleted: z.boolean()
2245
2245
  });
2246
2246
  /** `POST /v1/storefront/site/files/move` — 이름 바꾸기·옮기기 */
2247
- const moveStorefrontFileRequestSchema = z.object({
2247
+ const moveStorefrontFileRequestSchema = strictRequest(z.object({
2248
2248
  from: storefrontFilePathSchema,
2249
2249
  to: storefrontFilePathSchema,
2250
2250
  expectedHash: storefrontFileHashSchema.optional().describe("주면 `from`의 지금 해시가 같을 때만"),
2251
2251
  overwrite: z.boolean().optional().describe("`to`에 파일이 있으면 덮는가 (기본 false → 409 `FILE_EXISTS`)")
2252
- });
2252
+ }));
2253
2253
  const storefrontFileMoveResultSchema = z.object({
2254
2254
  from: z.string(),
2255
2255
  to: z.string(),
@@ -2259,7 +2259,7 @@ const storefrontFileMoveResultSchema = z.object({
2259
2259
  /** `POST /v1/storefront/site/files/search` — 텍스트 파일 내용 검색(편집 환경이 없어도 된다) */
2260
2260
  /** 정규식 검색의 패턴 길이 상한 — 편집 Sandbox가 실행 비용을 막으려고 더 좁게 받는다(sandbox-api §4.6) */
2261
2261
  const STOREFRONT_SEARCH_REGEX_MAX_LENGTH = 100;
2262
- const searchStorefrontFilesRequestSchema = z.object({
2262
+ const searchStorefrontFilesRequestSchema = strictRequest(z.object({
2263
2263
  query: z.string().min(1).max(200),
2264
2264
  regex: z.boolean().optional().describe("`query`를 정규식으로 본다 (기본 false = 글자 그대로). 정규식은 100자까지이고 중첩 수량자(`(a+)+`)·반복되는 선택(`(a|b)+`)·역참조·뒤 보기, 가변 수량자(`*`·`+`·`?`·`{n,m}`, 넓은 범위는 2로 센다) 차수 4 초과는 400 `INVALID_REQUEST`다. 차수가 클수록 긴 줄은 건너뛰고 비용이 크면 `truncated`로 끊는다"),
2265
2265
  caseSensitive: z.boolean().optional().describe("대소문자를 가리는가 (기본 false)"),
@@ -2268,7 +2268,7 @@ const searchStorefrontFilesRequestSchema = z.object({
2268
2268
  }).refine((body) => !body.regex || body.query.length <= 100, {
2269
2269
  message: `정규식은 100자까지입니다`,
2270
2270
  path: ["query"]
2271
- });
2271
+ }));
2272
2272
  const storefrontFileSearchResultSchema = z.object({
2273
2273
  matches: z.array(z.object({
2274
2274
  path: z.string(),
@@ -2284,11 +2284,11 @@ const storefrontFileSearchResultSchema = z.object({
2284
2284
  * `GET /v1/storefront/site/checks/{checkId}`로 조회한다(권장 2~3초 간격). 사이트당 끝나지 않은 검사는 하나다 — 같은 종류가 돌고
2285
2285
  * 있으면 그 검사를 다시 돌려주고, 다른 종류면 409 `CHECK_RUNNING`(`error.details.checkId`)이다. 출력은 꼬리만 준다.
2286
2286
  */
2287
- const runStorefrontCheckRequestSchema = z.object({ check: z.enum([
2287
+ const runStorefrontCheckRequestSchema = strictRequest(z.object({ check: z.enum([
2288
2288
  "typecheck",
2289
2289
  "lint",
2290
2290
  "test"
2291
- ]) });
2291
+ ]) }));
2292
2292
  const storefrontCheckStatusSchema = z.enum([
2293
2293
  "QUEUED",
2294
2294
  "RUNNING",
@@ -2436,10 +2436,10 @@ const storefrontBuildListQuerySchema = z.object({ limit: z.coerce.number().int()
2436
2436
  * `POST /v1/storefront/site/versions` — 지금 작업 트리를 고정해 버전을 만든다(빌드 시작). 사이트당 끝나지 않은 빌드는
2437
2437
  * 하나다(409 `STOREFRONT_BUILD_IN_PROGRESS`). 편집 환경이 없어도 된다.
2438
2438
  */
2439
- const createStorefrontVersionRequestSchema = z.object({
2439
+ const createStorefrontVersionRequestSchema = strictRequest(z.object({
2440
2440
  message: z.string().max(200).optional().describe("버전 메모 (에이전트는 바꾼 내용 요약)"),
2441
2441
  expectedSourceRef: z.string().max(200).optional().describe("고정할 작업 트리 (`PUT /v1/storefront/site/source`의 `sourceRef`). 주면 지금 작업 트리가 이 값일 때만 버전을 만든다(다르면 409 `SOURCE_CHANGED`)")
2442
- });
2442
+ }));
2443
2443
  /** 버전 만들기 응답 (201) — 버전은 `BUILDING`, 빌드는 `QUEUED`로 시작한다. 결과는 `GET builds/{buildId}`로 본다 */
2444
2444
  const storefrontVersionCreatedSchema = z.object({
2445
2445
  version: storefrontVersionSchema,
@@ -2450,7 +2450,7 @@ const storefrontVersionCreatedSchema = z.object({
2450
2450
  * 받는다(PAT·에이전트 불가). 5분 안에 열면 그 브라우저에 미리보기 호스트 전용 쿠키(12시간)가 생긴다. 편집 환경이
2451
2451
  * 없으면 409 `EDIT_SESSION_REQUIRED`다(편집 환경이 `STARTING`이면 링크는 주고, 미리보기 화면이 기동 대기를 보여 준다).
2452
2452
  */
2453
- const createStorefrontEditPreviewLinkRequestSchema = z.object({ path: z.string().max(512).optional().describe("열 경로 (`/`로 시작). 생략하면 `/`") });
2453
+ const createStorefrontEditPreviewLinkRequestSchema = strictRequest(z.object({ path: z.string().max(512).optional().describe("열 경로 (`/`로 시작). 생략하면 `/`") }));
2454
2454
  /** 압축 파일 조각 하나의 최대 크기(바이트) */
2455
2455
  const STOREFRONT_SOURCE_PART_BYTES = 4194304;
2456
2456
  /** 내용 올리기 한 번의 최대 파일 수 */
@@ -2492,10 +2492,10 @@ const storefrontSourceArchivePartSchema = z.object({
2492
2492
  content: z.string().describe("조각 내용 (base64)")
2493
2493
  });
2494
2494
  /** `POST /v1/storefront/site/source/blobs` — 파일 내용 올리기(해시 키, 다시 올려도 안전하다). 작업 트리는 바뀌지 않는다 */
2495
- const uploadStorefrontSourceBlobsRequestSchema = z.object({ blobs: z.array(z.object({
2495
+ const uploadStorefrontSourceBlobsRequestSchema = strictRequest(z.object({ blobs: z.array(z.object({
2496
2496
  hash: storefrontFileHashSchema,
2497
2497
  content: z.string().describe("파일 내용 (base64, 1MiB까지)")
2498
- })).min(1).max(200) });
2498
+ })).min(1).max(200) }));
2499
2499
  const storefrontSourceBlobsResultSchema = z.object({
2500
2500
  stored: z.number().int().min(0).describe("새로 올린 수"),
2501
2501
  existing: z.number().int().min(0).describe("이미 있던 수")
@@ -2504,7 +2504,7 @@ const storefrontSourceBlobsResultSchema = z.object({
2504
2504
  * `PUT /v1/storefront/site/source` — 작업 트리 전체를 이 목록으로 바꾼다(없는 경로는 지운다). 전부 아니면 전무다.
2505
2505
  * `package.json`·`pnpm-lock.yaml`도 바꿀 수 있고 의존성은 허용 목록 안이어야 한다. `.env*`·작업 트리 밖 경로는 받지 않는다.
2506
2506
  */
2507
- const replaceStorefrontSourceRequestSchema = z.object({
2507
+ const replaceStorefrontSourceRequestSchema = strictRequest(z.object({
2508
2508
  expectedBaseRef: storefrontSourceRefSchema.optional().describe("내려받은 작업 트리. 그 뒤 작업 트리가 바뀌었으면 이 트리를 기준으로 3-way 병합하고, 같은 곳을 고쳤으면 409 `SOURCE_CHANGED`다. 생략하면 조건 없이 덮어쓴다"),
2509
2509
  files: z.array(z.object({
2510
2510
  path: storefrontFilePathSchema,
@@ -2514,7 +2514,7 @@ const replaceStorefrontSourceRequestSchema = z.object({
2514
2514
  endEditSession: z.boolean().optional().describe("열린 편집 환경을 끄고 진행한다 (기본 false — 열려 있으면 409 `EDIT_SESSION_OPEN`)"),
2515
2515
  packageJson: z.string().max(STOREFRONT_FILE_MAX_BYTES).optional().describe("`files`에 `package.json`이 있으면 그 내용(utf8). 서버가 의존성을 판정한다. 해시가 목록과 다르면 400 `DEPENDENCY_FILES_MISMATCH`"),
2516
2516
  lockfile: z.string().max(STOREFRONT_FILE_MAX_BYTES).optional().describe("`files`에 `pnpm-lock.yaml`이 있으면 그 내용(utf8)")
2517
- });
2517
+ }));
2518
2518
  const storefrontSourceReplacedSchema = z.object({
2519
2519
  sourceRef: storefrontSourceRefSchema.describe("새 작업 트리(버전 만들기의 `expectedSourceRef`). 다음 올리기의 기준은 `uploadedRef`다"),
2520
2520
  previousRef: z.string().describe("바꾸기 전 작업 트리"),
@@ -2556,25 +2556,25 @@ const storefrontGitRepositorySchema = z.object({
2556
2556
  defaultBranch: z.string()
2557
2557
  });
2558
2558
  /** `POST /v1/storefront/site/git/installations` — GitHub App 설치 화면에서 돌아온 값(`installation_id`·`code`) */
2559
- const linkStorefrontGitInstallationRequestSchema = z.object({
2559
+ const linkStorefrontGitInstallationRequestSchema = strictRequest(z.object({
2560
2560
  installationId: z.number().int().positive(),
2561
2561
  code: z.string().min(1).max(200).describe("설치 화면이 돌려준 GitHub 사용자 인증 코드(한 번만 쓴다)"),
2562
2562
  state: z.string().min(1).max(200).describe("설치 화면으로 보내기 전에 받은 연결 state(`POST …/git/installations/state`, 한 번만 쓴다)")
2563
- });
2563
+ }));
2564
2564
  /** `POST /v1/storefront/site/git/installations/state` — GitHub 설치 화면에 싣는 1회용 state(이 상점·사용자, 10분) */
2565
2565
  const storefrontGitConnectStateSchema = z.object({
2566
2566
  state: z.string(),
2567
2567
  expiresAt: z.string()
2568
2568
  });
2569
2569
  /** `POST /v1/storefront/site/git/connect` */
2570
- const connectStorefrontGitRequestSchema = z.object({
2570
+ const connectStorefrontGitRequestSchema = strictRequest(z.object({
2571
2571
  installationId: z.number().int().positive(),
2572
2572
  repoId: z.number().int().positive(),
2573
2573
  productionBranch: z.string().min(1).max(200).regex(/^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/).optional().describe("운영 배포 브랜치(생략하면 저장소 기본 브랜치)"),
2574
2574
  rootDirectory: z.string().max(200).regex(/^$|^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/).refine((v) => !v.split("/").some((seg) => seg === "." || seg === ".."), "상대 경로만").optional().describe("저장소 안 사이트 루트(생략하면 저장소 루트)")
2575
- });
2575
+ }));
2576
2576
  /** `PUT /v1/storefront/site/deploy-approval` — 승인 없이 배포(#138) */
2577
- const setStorefrontDeployApprovalRequestSchema = z.object({ deployWithoutApproval: z.boolean() });
2577
+ const setStorefrontDeployApprovalRequestSchema = strictRequest(z.object({ deployWithoutApproval: z.boolean() }));
2578
2578
  //#endregion
2579
2579
  //#region src/schemas/ai.ts
2580
2580
  /**
@@ -2945,18 +2945,18 @@ const storeSetupBriefSchema = z.object({
2945
2945
  heroProduct: z.string().trim().max(500).describe("첫 화면에 크게 걸 상품 (비우면 AI가 정한다)"),
2946
2946
  slug: storefrontSlugSchema.optional().describe("바라는 스토어프론트 주소")
2947
2947
  });
2948
- const createAiConversationRequestSchema = z.object({
2948
+ const createAiConversationRequestSchema = strictRequest(z.object({
2949
2949
  /** 웹사이트에서 남긴 초안(#124 M2) — 연결한 사용자만 쓴다. 주면 첫 메시지로 실행을 시작한다 */
2950
2950
  draftId: z.string().max(64).optional(),
2951
2951
  /** 질문 화면의 답 — `draftId`와 함께만 쓴다. 초안 앞에 붙어 첫 메시지가 된다 */
2952
2952
  brief: storeSetupBriefSchema.optional()
2953
- });
2953
+ }));
2954
2954
  const createAiConversationResponseSchema = z.object({
2955
2955
  conversation: aiConversationSummarySchema,
2956
2956
  /** `draftId`로 시작했으면 첫 실행 */
2957
2957
  run: aiStreamAccessSchema.nullable()
2958
2958
  });
2959
- const sendAiMessageRequestSchema = z.object({
2959
+ const sendAiMessageRequestSchema = strictRequest(z.object({
2960
2960
  text: z.string().trim().max(AI_MESSAGE_MAX_CHARS),
2961
2961
  attachments: z.array(aiAttachmentSchema).max(5).default([]),
2962
2962
  /** 선택지 버튼으로 보낸 답이면 그 값(화면 표시는 `text`) */
@@ -2965,7 +2965,7 @@ const sendAiMessageRequestSchema = z.object({
2965
2965
  selection: aiPreviewSelectionSchema.optional(),
2966
2966
  /** 디자인 탭에서 고른 아트보드(2026-10-14) — 모델은 그 아트보드를 기준으로 고친다 */
2967
2967
  designSelection: aiDesignSelectionSchema.optional()
2968
- }).refine((body) => body.text.length > 0 || body.attachments.length > 0, { message: "내용이나 첨부가 있어야 합니다" });
2968
+ }).refine((body) => body.text.length > 0 || body.attachments.length > 0, { message: "내용이나 첨부가 있어야 합니다" }));
2969
2969
  const sendAiMessageResponseSchema = aiStreamAccessSchema.extend({ messageId: z.string() });
2970
2970
  /**
2971
2971
  * AI가 바꾸는 화면 영역 — 쓰기 도구가 시작되면 이벤트에 싣고, 패널이 그 화면으로 옮겨 반영된 데이터를 다시 읽는다(셀러가 AI가 하는 일을 같이 본다).
@@ -3150,13 +3150,13 @@ const STORE_DRAFT_MAX_ATTACHMENTS = 5;
3150
3150
  const STORE_DRAFT_ATTACHMENT_MAX_BYTES = 10485760;
3151
3151
  /** 연결하지 않은 초안을 지우기까지 */
3152
3152
  const STORE_DRAFT_RETENTION_DAYS = 7;
3153
- const createStoreDraftRequestSchema = z.object({
3153
+ const createStoreDraftRequestSchema = strictRequest(z.object({
3154
3154
  text: z.string().trim().min(1).max(AI_MESSAGE_MAX_CHARS),
3155
3155
  attachments: z.array(z.object({
3156
3156
  filename: z.string().trim().min(1).max(200),
3157
3157
  size: z.number().int().positive().max(STORE_DRAFT_ATTACHMENT_MAX_BYTES)
3158
3158
  })).max(5).default([])
3159
- });
3159
+ }));
3160
3160
  const createStoreDraftResponseSchema = z.object({
3161
3161
  draftId: z.string(),
3162
3162
  /** 첨부마다 브라우저가 파일을 그대로 PUT할 자리(요청 순서와 같다) */
@@ -3841,7 +3841,7 @@ const claimHoldResultSchema = z.object({
3841
3841
  claimId: z.string(),
3842
3842
  holdExpiresAt: z.string()
3843
3843
  });
3844
- const rejectClaimRequestSchema = z.object({
3844
+ const rejectClaimRequestSchema = strictRequest(z.object({
3845
3845
  reason: z.enum([
3846
3846
  "USED_PRODUCT",
3847
3847
  "MISSING_COMPONENT",
@@ -3851,7 +3851,7 @@ const rejectClaimRequestSchema = z.object({
3851
3851
  ]),
3852
3852
  detail: z.string().min(10).max(1e3),
3853
3853
  evidenceImages: z.array(z.url()).max(5).optional()
3854
- });
3854
+ }));
3855
3855
  const approveClaimResultSchema = z.object({
3856
3856
  claimId: z.string(),
3857
3857
  refundAmount: z.number().int().describe("실제 환불한 금액 — 수량 비율 상품 실결제 금액(쿠폰 배분을 뺀 금액) + 배송비 환불 − 반품 배송비 − 조건부 무료배송 미달 차감 − 쿠폰 조건 미달 차감(`couponChargeAmount`) + 되돌린 쿠폰 차감(`couponChargeRefundAmount`) − 추가 차감(`deductAmount`)"),
@@ -4034,12 +4034,12 @@ const storePointPolicySchema = z.object({
4034
4034
  * `PUT /v1/store/point-policy` 본문 — 준 항목만 바꾼다(생략 = 저장된 값 유지). 리뷰 적립·구매 적립 중 하나라도 켜면 유효 기간이 있어야 한다
4035
4035
  * (본문이나 저장된 값, 없으면 400 `POINT_EXPIRY_REQUIRED`).
4036
4036
  */
4037
- const updatePointPolicyRequestSchema = z.object({
4037
+ const updatePointPolicyRequestSchema = strictRequest(z.object({
4038
4038
  reviewReward: reviewRewardPolicySchema.optional(),
4039
4039
  purchaseEarn: purchaseEarnPolicySchema.optional(),
4040
4040
  use: pointUsePolicySchema.optional(),
4041
4041
  expiryDays: expiryDays.optional()
4042
- });
4042
+ }));
4043
4043
  /** 응답의 원장 유형 — 값이 추가될 수 있어 문자열로 받는다 */
4044
4044
  const responsePointEntryTypeSchema = z.union([z.enum(POINT_ENTRY_TYPES), z.string()]).describe("유형. `EARN_REVIEW` 리뷰 적립 · `REVOKE_REVIEW` 리뷰 삭제 회수 · `EARN_PURCHASE` 구매 적립 · `EXPIRE` 소멸 · `ADJUST` 셀러 지급·차감 · `OPENING` 기존 잔액 이전 · `USE_RESERVE` 결제 사용(결제 시작 때 빠짐) · `USE_RELEASE` 결제가 실패·만료되어 되돌림 · `REFUND_RESTORE` 취소·반품 환불로 반환. 값이 추가될 수 있어 문자열로 받는다");
4045
4045
  /** 원장 항목 — 셀러 조회용(사유·행위자 포함) */
@@ -4086,14 +4086,14 @@ const POINT_ADJUSTMENT_MAX = 1e7;
4086
4086
  * 지급의 소멸 시각은 `expiresAt`(생략하면 정책 유효 기간, 정책이 없으면 400 `POINT_EXPIRY_REQUIRED`), 차감은 소멸이 이른 적립금부터 뺀다
4087
4087
  * (잔액보다 많으면 409 `POINT_BALANCE_INSUFFICIENT`).
4088
4088
  */
4089
- const pointAdjustmentRequestSchema = z.object({
4089
+ const pointAdjustmentRequestSchema = strictRequest(z.object({
4090
4090
  amount: z.number().int().min(-1e7).max(POINT_ADJUSTMENT_MAX).refine((value) => value !== 0, "0원은 조정할 수 없습니다").describe("지급은 양수, 차감은 음수(원)"),
4091
4091
  reason: z.string().trim().min(1).max(200).describe("사유(필수). 구매자에게는 보이지 않는다"),
4092
4092
  expiresAt: z.iso.datetime({ offset: true }).optional().describe("지급분의 소멸 시각. 생략하면 정책 유효 기간(`expiryDays`)으로 정한다. **정책 유효 기간이 없으면 지급에 필수다**(없으면 400 `POINT_EXPIRY_REQUIRED`). 차감에는 주지 않는다")
4093
4093
  }).refine((value) => !(value.amount < 0 && value.expiresAt), {
4094
4094
  message: "차감에는 소멸 시각을 줄 수 없습니다",
4095
4095
  path: ["expiresAt"]
4096
- });
4096
+ }));
4097
4097
  const pointAdjustmentResultSchema = z.object({
4098
4098
  entry: pointEntrySchema,
4099
4099
  balance: z.number().int().describe("조정 뒤 잔액")
@@ -4184,7 +4184,7 @@ const productNoticeSchema = z.object({
4184
4184
  values: z.record(z.string(), z.string()).describe("칸 키 → 셀러가 적은 값. 비운 칸은 없다")
4185
4185
  });
4186
4186
  const MAX_NOTICE_VALUE_LENGTH = 1500;
4187
- const productNoticeRequestSchema = z.object({
4187
+ const productNoticeRequestSchema = z.strictObject({
4188
4188
  schemaId: z.string().trim().min(1).max(50).describe("품목군 코드(`GET /v1/info-schemas`)"),
4189
4189
  version: z.number().int().positive().optional().describe("품목군 버전. 생략하면 현재 버전"),
4190
4190
  values: z.record(z.string().max(50), z.string().max(MAX_NOTICE_VALUE_LENGTH)).default({}).describe("칸 키 → 값. 출처가 `legal`인 칸은 받지 않는다(400). 빈 문자열은 비운 칸으로 저장하지 않는다. 칸 길이는 스키마 `maxLength`")
@@ -4208,13 +4208,13 @@ const productCertificationsSchema = z.object({
4208
4208
  number: z.string().describe("인증 번호")
4209
4209
  })).describe("인증서. 배열 순서가 표시 순서다")
4210
4210
  });
4211
- const productCertificationsRequestSchema = z.object({
4212
- subjects: z.array(z.object({
4211
+ const productCertificationsRequestSchema = z.strictObject({
4212
+ subjects: z.array(z.strictObject({
4213
4213
  subject: z.string().trim().min(1).max(50),
4214
4214
  status: z.enum(CERTIFICATION_STATUSES),
4215
4215
  exemption: z.string().trim().min(1).max(50).nullish()
4216
4216
  })).max(20).default([]).describe("인증 대상 여부. `REQUIRED`인데 그 대상의 인증 종류(`attrs.kinds`) 인증서가 `items`에 없으면 400 `CERTIFICATION_REQUIRED`, `EXEMPT`인데 사유가 없거나 대상이 허용하지 않는 사유면 400 `CERTIFICATION_EXEMPTION_INVALID`"),
4217
- items: z.array(z.object({
4217
+ items: z.array(z.strictObject({
4218
4218
  kind: z.string().trim().min(1).max(50),
4219
4219
  agency: z.string().trim().max(100).nullish(),
4220
4220
  number: z.string().trim().min(1).max(100)
@@ -4236,11 +4236,11 @@ const infoSectionSchema = z.object({
4236
4236
  createdAt: z.string(),
4237
4237
  updatedAt: z.string()
4238
4238
  });
4239
- const infoSectionRequestSchema = z.object({
4239
+ const infoSectionRequestSchema = strictRequest(z.object({
4240
4240
  title: z.string().trim().min(1).max(100),
4241
4241
  body: z.string().default(""),
4242
4242
  bodyJson: z.string().nullable().optional()
4243
- });
4243
+ }));
4244
4244
  /**
4245
4245
  * 안내 섹션 번역 (이슈 #134) — 언어 하나의 제목·본문. 섹션 필드는 상점 기본 언어 값이다. 비운 칸(null)은 스토어프론트가 기본 언어 값으로 대체한다.
4246
4246
  * 섹션을 연결한 모든 상품이 같은 번역을 보인다.
@@ -4313,14 +4313,14 @@ const calendarQuerySchema = z.object({
4313
4313
  * 달력 설정 — 기간의 날짜마다 재고·가격을 덮어쓴다(없는 날은 만든다). `stock`·`price`는 준 것만 바꾸고, `price: null`은 조합 가격으로
4314
4314
  * 되돌린다. `daysOfWeek`(0=일~6=토)를 주면 그 요일만 바꾼다
4315
4315
  */
4316
- const setCalendarRequestSchema = z.object({
4316
+ const setCalendarRequestSchema = strictRequest(z.object({
4317
4317
  variantIds: z.array(z.string()).min(1).max(50),
4318
4318
  from: calendarDateSchema,
4319
4319
  to: calendarDateSchema,
4320
4320
  stock: z.number().int().min(0).max(1e5).optional(),
4321
4321
  price: z.number().int().min(0).multipleOf(10).nullable().optional(),
4322
4322
  daysOfWeek: z.array(z.number().int().min(0).max(6)).min(1).max(7).optional()
4323
- }).refine((value) => value.stock !== void 0 || value.price !== void 0, { message: "stock이나 price 중 하나는 있어야 합니다" });
4323
+ }).refine((value) => value.stock !== void 0 || value.price !== void 0, { message: "stock이나 price 중 하나는 있어야 합니다" }));
4324
4324
  //#endregion
4325
4325
  //#region src/schemas/upload.ts
4326
4326
  /**
@@ -4426,11 +4426,11 @@ function uploadRejection(purpose, filename, size) {
4426
4426
  * 끝나면 `url`이 공개 주소가 된다. 서버가 객체 키를 정하므로 요청은 파일명만 보낸다
4427
4427
  * (확장자만 쓰이고 이름 자체는 저장되지 않는다).
4428
4428
  */
4429
- const uploadRequestSchema = z.object({
4429
+ const uploadRequestSchema = strictRequest(z.object({
4430
4430
  filename: z.string().min(1).max(255),
4431
4431
  purpose: uploadPurposeSchema.default("description").describe("업로드 용도. 허용 확장자와 크기 상한이 용도별로 다르다"),
4432
4432
  size: z.number().int().positive().optional().describe("파일 바이트 크기. 주면 용도별 상한을 발급 전에 검사한다")
4433
- });
4433
+ }));
4434
4434
  const uploadTicketSchema = z.object({
4435
4435
  /** 업로드 완료 후 공개될 주소 */
4436
4436
  url: z.url(),
@@ -4611,7 +4611,7 @@ function customInputLabelForbiddenWord(label) {
4611
4611
  }
4612
4612
  /** 옵션명·값 이름 — 앞뒤 공백을 지우고 빈 값은 받지 않는다 */
4613
4613
  const optionNameSchema = z.string().trim().min(1).max(50);
4614
- const addonValueRequestSchema = z.object({
4614
+ const addonValueRequestSchema = z.strictObject({
4615
4615
  /** 전달 시 기존 값 행을 유지(UPDATE). 생략하면 신규 발급 */
4616
4616
  valueId: z.string().optional(),
4617
4617
  name: optionNameSchema,
@@ -4619,14 +4619,14 @@ const addonValueRequestSchema = z.object({
4619
4619
  prices: additionalCurrencyPricesSchema.optional(),
4620
4620
  usable: z.boolean().default(true)
4621
4621
  });
4622
- const addonGroupRequestSchema = z.object({
4622
+ const addonGroupRequestSchema = z.strictObject({
4623
4623
  /** 전달 시 기존 옵션명 행을 유지(UPDATE). 조합 옵션명의 id는 줄 수 없다(방식을 바꾸려면 새 옵션명으로 보낸다) */
4624
4624
  groupId: z.string().optional(),
4625
4625
  name: optionNameSchema,
4626
4626
  required: z.boolean().default(false),
4627
4627
  values: z.array(addonValueRequestSchema).min(1).max(100)
4628
4628
  });
4629
- const customInputRequestSchema = z.object({
4629
+ const customInputRequestSchema = z.strictObject({
4630
4630
  /** 전달 시 기존 항목 행을 유지(UPDATE). 생략하면 신규 발급 */
4631
4631
  inputId: z.string().optional(),
4632
4632
  label: z.string().trim().min(1).max(30),
@@ -4754,18 +4754,18 @@ function productImageRejection(url) {
4754
4754
  * (`WHERE ... is not null`)의 판정이 갈려 409 대신 500이 나간다.
4755
4755
  */
4756
4756
  const codeSchema = z.string().trim().min(1).max(64);
4757
- const optionValueRequestSchema = z.object({
4757
+ const optionValueRequestSchema = z.strictObject({
4758
4758
  /** 전달 시 기존 값 행을 유지(UPDATE). 생략하면 신규 발급 */
4759
4759
  valueId: z.string().optional(),
4760
4760
  name: z.string().min(1).max(50)
4761
4761
  });
4762
- const optionGroupRequestSchema = z.object({
4762
+ const optionGroupRequestSchema = z.strictObject({
4763
4763
  /** 전달 시 기존 축 행을 유지(UPDATE). 생략하면 신규 발급 */
4764
4764
  groupId: z.string().optional(),
4765
4765
  name: z.string().min(1).max(50),
4766
4766
  values: z.array(optionValueRequestSchema).min(1).max(100)
4767
4767
  });
4768
- const productVariantRequestSchema = z.object({
4768
+ const productVariantRequestSchema = z.strictObject({
4769
4769
  /** 기존 variant 유지 시 전달. 생략하면 신규 생성 */
4770
4770
  variantId: z.string().optional(),
4771
4771
  /** optionGroups 순서대로 각 축에서 고른 값 이름. 서버가 이름 → valueId를 해석한다 */
@@ -4787,7 +4787,7 @@ const productVariantRequestSchema = z.object({
4787
4787
  attributes: z.record(z.string().regex(/^[a-z][a-zA-Z0-9_]{0,39}$/), z.string().max(100)).refine((value) => Object.keys(value).length <= 10, { message: "조합 속성은 10개까지입니다" }).optional().describe("조합 속성(이슈 #115) — 키 → 값 문자열(예: 회차 일시 `showAt`). 플로우가 `item.option.<키>`로 읽고 타입(날짜·숫자 등)은 플로우가 선언한다. 생략하면 지금 값 유지(새 조합은 없음), `{}`면 비운다")
4788
4788
  });
4789
4789
  /** 축 × 값 → variant 전체 교체 요청 (PUT /products/{productId}/options) */
4790
- const optionModelRequestSchema = z.object({
4790
+ const optionModelRequestSchema = z.strictObject({
4791
4791
  optionGroups: z.array(optionGroupRequestSchema).max(3).default([]),
4792
4792
  variants: z.array(productVariantRequestSchema).min(1).max(500),
4793
4793
  addonGroups: z.array(addonGroupRequestSchema).max(5).optional().describe("추가 선택 옵션명 전체(이슈 #59). 주면 통째로 바꾸고, 생략하면 등록은 없음·수정은 현재 값을 그대로 둔다. 끄려면 빈 배열을 보낸다"),
@@ -4847,7 +4847,7 @@ const productWriteShape = {
4847
4847
  */
4848
4848
  descriptionJson: z.string().nullable().optional(),
4849
4849
  images: productImagesRequestSchema.optional().describe("상품 이미지 주소 목록. 첫 장이 대표 이미지(썸네일)다"),
4850
- attributes: z.array(z.object({
4850
+ attributes: z.array(z.strictObject({
4851
4851
  attributeId: z.string(),
4852
4852
  value: z.string()
4853
4853
  })).optional(),
@@ -4898,12 +4898,12 @@ const productListQuerySchema = z.object({
4898
4898
  sort: productSortSchema.optional().describe("정렬 (기본 createdAt.desc)"),
4899
4899
  ...pageQueryShape()
4900
4900
  });
4901
- const discountRequestSchema = z.object({
4901
+ const discountRequestSchema = strictRequest(z.object({
4902
4902
  discountType: z.enum(["RATE", "AMOUNT"]),
4903
4903
  discountValue: z.number().int().min(1),
4904
4904
  startAt: z.string().optional(),
4905
4905
  endAt: z.string().optional()
4906
- });
4906
+ }));
4907
4907
  //#endregion
4908
4908
  //#region src/schemas/webhook-event.ts
4909
4909
  const envelopeFields = {
@@ -5896,7 +5896,7 @@ const webhookDeliveryListQuerySchema = z.object({
5896
5896
  });
5897
5897
  /** 실패 건 일괄 재전송 응답 — `POST /webhooks/{id}/deliveries/redeliver-failed` */
5898
5898
  const webhookRedeliverFailedResultSchema = z.object({ queued: z.number().int().min(0).describe("재전송 대기열에 올린 건수. 최근 72시간의 `FAILED` 중 아직 재전송되지 않은 건이 대상이며 최대 500건이다") });
5899
- const webhookTestRequestSchema = z.object({ eventType: eventTypeSchema.describe("샘플 페이로드로 보낼 이벤트 타입") });
5899
+ const webhookTestRequestSchema = strictRequest(z.object({ eventType: eventTypeSchema.describe("샘플 페이로드로 보낼 이벤트 타입") }));
5900
5900
  /** 테스트 이벤트 발송 결과 (동기 반환). 재시도·정지 판정·전송 기록이 없다 */
5901
5901
  const webhookTestResultSchema = z.object({
5902
5902
  responseStatusCode: z.number().int().nullable().describe("수신 서버의 HTTP 응답 코드. 연결 실패·타임아웃·차단이면 null"),
@@ -6112,10 +6112,10 @@ const appInstallationActivitySchema = z.object({
6112
6112
  * - `{ result }`: accounts 설치 동의 결과(1회용 JWT). 개발자 포털 [내 상점에 설치]가 이 길로 온다
6113
6113
  * - `{ appId, settings }`: 상점 플랫폼 앱 목록 › 설치 화면에서 동의하고 설정까지 채워 바로 설치한다
6114
6114
  */
6115
- const appInstallRequestSchema = z.union([z.object({ result: z.string().min(1).describe("accounts가 발급한 설치 결과 JWT(`typ: sayren-install+jwt`)") }), z.object({
6115
+ const appInstallRequestSchema = strictRequest(z.union([z.object({ result: z.string().min(1).describe("accounts가 발급한 설치 결과 JWT(`typ: sayren-install+jwt`)") }), z.object({
6116
6116
  appId: z.string().min(1),
6117
6117
  settings: z.record(z.string(), settingsValueSchema).default({}).describe("설치와 함께 저장할 설정. 비밀 필드도 여기서 넣는다")
6118
- })]);
6118
+ })]));
6119
6119
  /** 앱 목록 정보 — 현재 버전이 올린 아이콘·README(없으면 매니페스트 `iconUrl`) */
6120
6120
  const appListingSchema = z.object({
6121
6121
  description: z.string().nullable(),
@@ -6479,7 +6479,7 @@ const categoryAttributeSchema = z.object({
6479
6479
  unit: z.string().nullable(),
6480
6480
  allowedValues: z.array(z.string()).optional()
6481
6481
  });
6482
- const categoryCreateRequestSchema = z.object({
6482
+ const categoryCreateRequestSchema = strictRequest(z.object({
6483
6483
  name: z.string().min(1).max(100),
6484
6484
  parentId: z.string().nullable().optional(),
6485
6485
  sortOrder: z.number().int().optional(),
@@ -6490,8 +6490,8 @@ const categoryCreateRequestSchema = z.object({
6490
6490
  metaTitle: z.string().max(100).nullable().optional(),
6491
6491
  metaDescription: z.string().max(300).nullable().optional(),
6492
6492
  metaKeywords: z.string().max(200).nullable().optional()
6493
- });
6494
- const categoryPatchRequestSchema = z.object({
6493
+ }));
6494
+ const categoryPatchRequestSchema = strictRequest(z.object({
6495
6495
  name: z.string().min(1).max(100).optional(),
6496
6496
  parentId: z.string().nullable().optional(),
6497
6497
  sortOrder: z.number().int().optional(),
@@ -6503,7 +6503,7 @@ const categoryPatchRequestSchema = z.object({
6503
6503
  metaDescription: z.string().max(300).nullable().optional(),
6504
6504
  metaKeywords: z.string().max(200).nullable().optional(),
6505
6505
  flowId: z.string().nullable().optional().describe("주문 흐름(이슈 #115) — 게시한 흐름 id, null이면 기본 흐름. 맨 아래 카테고리에만 지정한다(400 `CATEGORY_NOT_LEAF`). 게시하지 않은 흐름은 409 `FLOW_NOT_PUBLISHED`")
6506
- });
6506
+ }));
6507
6507
  const categoryDeleteStrategySchema = z.enum(["reparent", "cascade"]);
6508
6508
  /**
6509
6509
  * `GET /v1/categories` 쿼리 — OpenAPI 문서용 형태(값은 쿼리 문자열). 응답 형태가 쿼리로 갈린다:
@@ -6826,7 +6826,7 @@ const periodRefine = (body, ctx) => {
6826
6826
  * 직접 선택(`MANUAL`)은 `productIds`가 처음 담을 상품과 순서다. 조건 자동(`AUTOMATIC`)은 `rules`가 있어야 하고 `productIds`를 받지 않는다.
6827
6827
  * `productSort`를 생략하면 직접 선택은 `MANUAL`, 조건 자동은 `LATEST`(판매량 상위 조건이 있으면 `BEST_SELLING`)다. 조건 자동은 `MANUAL`을 쓸 수 없다.
6828
6828
  */
6829
- const collectionCreateRequestSchema = z.object({
6829
+ const collectionCreateRequestSchema = strictRequest(z.object({
6830
6830
  title: z.string().trim().min(1).max(60).describe("제목"),
6831
6831
  slug: slugField.optional(),
6832
6832
  type: z.enum(COLLECTION_TYPES).default("MANUAL").describe("유형. `MANUAL`(직접 선택)·`AUTOMATIC`(조건 자동). 만든 뒤 바꿀 수 없다"),
@@ -6841,12 +6841,12 @@ const collectionCreateRequestSchema = z.object({
6841
6841
  metaTitle: commonFields.metaTitle.optional(),
6842
6842
  metaDescription: commonFields.metaDescription.optional(),
6843
6843
  productIds: productIdsField.optional()
6844
- }).superRefine(periodRefine);
6844
+ }).superRefine(periodRefine));
6845
6845
  /**
6846
6846
  * `PATCH /v1/collections/{collectionId}` — 준 항목만 바뀐다. `type`을 주면 400 `COLLECTION_TYPE_IMMUTABLE`이다.
6847
6847
  * `revision`을 주면 그 번호일 때만 바뀌고, 그 사이 다른 수정이 있었으면 409 `COLLECTION_CHANGED`다.
6848
6848
  */
6849
- const collectionPatchRequestSchema = z.object({
6849
+ const collectionPatchRequestSchema = strictRequest(z.object({
6850
6850
  title: z.string().trim().min(1).max(60).optional(),
6851
6851
  slug: slugField.optional(),
6852
6852
  type: z.string().optional().describe("바꿀 수 없다. 주면 400 `COLLECTION_TYPE_IMMUTABLE`"),
@@ -6861,14 +6861,14 @@ const collectionPatchRequestSchema = z.object({
6861
6861
  metaTitle: commonFields.metaTitle.optional(),
6862
6862
  metaDescription: commonFields.metaDescription.optional(),
6863
6863
  revision: z.number().int().min(0).optional().describe("읽은 수정 번호")
6864
- }).superRefine(periodRefine);
6864
+ }).superRefine(periodRefine));
6865
6865
  /** `PUT /v1/collections/{collectionId}/products` — 담긴 상품을 통째로 바꾼다(순서 포함) */
6866
- const collectionProductsReplaceRequestSchema = z.object({
6866
+ const collectionProductsReplaceRequestSchema = strictRequest(z.object({
6867
6867
  productIds: productIdsField,
6868
6868
  revision: z.number().int().min(0).optional().describe("읽은 수정 번호")
6869
- });
6869
+ }));
6870
6870
  /** `POST /v1/collections/{collectionId}/products` — 뒤에 붙인다. 이미 담긴 상품은 그 자리에 둔다 */
6871
- const collectionProductsAddRequestSchema = z.object({ productIds: z.array(z.string().min(1)).min(1).max(500) });
6871
+ const collectionProductsAddRequestSchema = strictRequest(z.object({ productIds: z.array(z.string().min(1)).min(1).max(500) }));
6872
6872
  /** 컬렉션에 담긴 상품 한 줄 */
6873
6873
  const collectionProductSchema = z.object({
6874
6874
  productId: z.string(),
@@ -6905,10 +6905,10 @@ const collectionProductListQuerySchema = z.object({ ...pageQueryShape({
6905
6905
  maxSize: MAX_COLLECTION_PRODUCTS
6906
6906
  }) });
6907
6907
  /** `POST /v1/collections/preview` — 저장하지 않고 조건으로 나오는 상품을 본다 */
6908
- const collectionPreviewRequestSchema = z.object({
6908
+ const collectionPreviewRequestSchema = strictRequest(z.object({
6909
6909
  rules: collectionRulesSchema,
6910
6910
  productSort: collectionProductSortRequestSchema.optional().describe("미리보기 순서. 생략하면 `LATEST`(판매량 상위 조건이 있으면 `BEST_SELLING`)이고, `MANUAL`이면 `LATEST`다")
6911
- });
6911
+ }));
6912
6912
  /** 미리보기 응답 — 조건을 만족하는 판매 중 상품 수와 앞 20개 */
6913
6913
  const collectionPreviewSchema = z.object({
6914
6914
  count: z.number().int().describe("조건을 만족하는 판매 중 상품 수(조회 시각 기준)"),
@@ -7039,10 +7039,10 @@ const orderItemFlowSchema = z.object({
7039
7039
  branches: z.array(flowStateSchema).optional().describe("활성 위치가 둘 이상(병렬 가지·하위 흐름)이면 각 위치의 상태. `flowState`는 그 가운데 가장 앞 단계다")
7040
7040
  });
7041
7041
  /** 상태 직접 이동(#115) — 연결선을 지나지 않고 다른 상태로 옮긴다. 블록(재고·환불·송장)은 실행하지 않는다 */
7042
- const moveFlowStateRequestSchema = z.object({
7042
+ const moveFlowStateRequestSchema = strictRequest(z.object({
7043
7043
  to: z.string().min(1).describe("옮길 상태 키(`moveTargets[].key`)"),
7044
7044
  reason: z.string().trim().max(200).optional().describe("옮긴 사유(실행 기록에 남는다)")
7045
- });
7045
+ }));
7046
7046
  const flowRunStepSchema = z.object({
7047
7047
  step: z.number().int(),
7048
7048
  block: z.string().describe("실행한 블록 종류"),
@@ -7138,7 +7138,7 @@ const commerceFlowSchema = commerceFlowSummarySchema.extend({
7138
7138
  }))
7139
7139
  });
7140
7140
  /** 진행 중 주문을 활성 버전으로 옮기기(#115) — 한 번에 `limit`개까지. 남으면 다시 부른다 */
7141
- const migrateFlowVersionRequestSchema = z.object({ limit: z.number().int().min(1).max(500).optional().describe("한 번에 옮길 흐름 수(기본 100)") });
7141
+ const migrateFlowVersionRequestSchema = strictRequest(z.object({ limit: z.number().int().min(1).max(500).optional().describe("한 번에 옮길 흐름 수(기본 100)") }));
7142
7142
  const migrateFlowVersionResultSchema = z.object({
7143
7143
  fromVersion: z.number().int(),
7144
7144
  toVersion: z.number().int(),
@@ -7150,19 +7150,19 @@ const migrateFlowVersionResultSchema = z.object({
7150
7150
  })).describe("이번에 옮기지 못한 흐름(그 버전에 남는다)"),
7151
7151
  remaining: z.number().int().describe("아직 이 버전에 남은 진행 중 흐름 수(건너뛴 것 포함)")
7152
7152
  });
7153
- const createCommerceFlowRequestSchema = z.object({
7153
+ const createCommerceFlowRequestSchema = strictRequest(z.object({
7154
7154
  key: z.string().regex(/^[a-z][a-z0-9-]{1,48}$/).describe("상점 안 유일한 키(소문자·숫자·하이픈)"),
7155
7155
  name: z.string().min(1).max(60).optional(),
7156
7156
  presetKey: z.string().optional().describe("출발한 프리셋(출처로 남는다). 없으면 빈 플로우"),
7157
7157
  graph: flowDocumentSchema.optional().describe("편집기에서 고친 그래프(#115). 없으면 프리셋 그래프 그대로, 프리셋도 없으면 빈 흐름(결제 완료 노드 하나)이다"),
7158
7158
  definition: flowDocumentSchema.optional().describe("흐름 설정 `requires`(progression·groupClaim·fulfillmentMethods). 없으면 프리셋의 설정"),
7159
7159
  variables: z.record(z.string(), z.unknown()).optional().describe("쓰지 않는다(무시). 값은 그래프의 노드 설정 안에 있다(#115)")
7160
- });
7161
- const saveFlowDraftRequestSchema = z.object({
7160
+ }));
7161
+ const saveFlowDraftRequestSchema = strictRequest(z.object({
7162
7162
  graph: flowDocumentSchema,
7163
7163
  definition: flowDocumentSchema.optional().describe("흐름 설정 `requires`(progression·groupClaim·fulfillmentMethods)"),
7164
7164
  variables: z.record(z.string(), z.unknown()).optional().describe("쓰지 않는다(무시). 값은 그래프의 노드 설정 안에 있다(#115)")
7165
- });
7165
+ }));
7166
7166
  const flowIssueSchema = z.object({
7167
7167
  code: z.string(),
7168
7168
  message: z.string(),
@@ -7251,7 +7251,7 @@ const flowDraftOperationSchema = z.discriminatedUnion("op", [
7251
7251
  ])).optional()
7252
7252
  }).describe("흐름 설정(진행 단위·묶음 클레임·이행 방식) 바꾸기")
7253
7253
  ]);
7254
- const applyFlowOperationsRequestSchema = z.object({ operations: z.array(flowDraftOperationSchema).min(1).describe("차례대로 적용한다. 묶음은 원자적이다 — 하나라도 형식이 틀리면 아무것도 바꾸지 않는다(400)") });
7254
+ const applyFlowOperationsRequestSchema = strictRequest(z.object({ operations: z.array(flowDraftOperationSchema).min(1).describe("차례대로 적용한다. 묶음은 원자적이다 — 하나라도 형식이 틀리면 아무것도 바꾸지 않는다(400)") }));
7255
7255
  const flowOperationsResultSchema = z.object({
7256
7256
  revision: z.number().int().describe("저장 뒤 초안 revision — 다음 조작의 If-Match"),
7257
7257
  changed: z.object({
@@ -7282,10 +7282,10 @@ const flowSecretSchema = z.object({
7282
7282
  headerNames: z.array(z.string()).describe("저장한 헤더 이름(값은 보이지 않는다)"),
7283
7283
  updatedAt: z.string()
7284
7284
  });
7285
- const saveFlowSecretRequestSchema = z.object({
7285
+ const saveFlowSecretRequestSchema = strictRequest(z.object({
7286
7286
  url: z.url().refine((value) => value.startsWith("https://"), { message: "https 주소만 받습니다" }).describe("요청 주소. https만, 사설·루프백 주소는 400 `FLOW_SECRET_URL_INVALID`"),
7287
7287
  headers: z.record(z.string().regex(/^[A-Za-z0-9-]{1,64}$/), z.string().max(2e3)).refine((value) => Object.keys(value).length <= 10, { message: "헤더는 10개까지입니다" }).default({}).describe("함께 보낼 헤더(인증 등). 값은 암호화해 두고 다시 보여 주지 않는다")
7288
- });
7288
+ }));
7289
7289
  /**
7290
7290
  * 주문 전체 행동 결과(#115) — 같은 행동을 주문의 주문상품마다 실행한다. 한 주문상품의 실패가 다른 주문상품을 되돌리지 않는다(부분 실행).
7291
7291
  * 묶음 진행이면 한 번 실행이 묶음의 주문상품을 함께 움직인다(COMPLETED로 함께 나온다). 그 행동이 없는 주문상품은 SKIPPED다
@@ -7314,11 +7314,11 @@ const simulationEventSchema = z.object({
7314
7314
  ]).describe("start 결제 승인 · seller.action 셀러 버튼 · buyer.action 구매자 버튼 · event 사건 · timer 타이머 키"),
7315
7315
  key: z.string().optional().describe("행동·사건·타이머 키(start는 없음)")
7316
7316
  });
7317
- const simulateFlowRequestSchema = z.object({
7317
+ const simulateFlowRequestSchema = strictRequest(z.object({
7318
7318
  startAt: z.iso.datetime({ offset: true }).optional().describe("가상 시계 시작(기본: 지금)"),
7319
7319
  context: z.record(z.string(), z.unknown()).optional().describe("식이 읽을 값(item·order·buyer·product). 비우면 견본 주문 값"),
7320
7320
  steps: z.array(z.union([z.object({ event: simulationEventSchema }), z.object({ advance: z.string().regex(/^P/).describe("시간 경과(ISO 8601 기간)") })])).min(1).max(100)
7321
- });
7321
+ }));
7322
7322
  const simulationStepSchema = z.object({
7323
7323
  step: z.number().int(),
7324
7324
  input: z.record(z.string(), z.unknown()).describe("넣은 사건(`event`) 또는 시간 경과(`advance`)"),
@@ -7344,7 +7344,7 @@ const simulateFlowResultSchema = z.object({
7344
7344
  errors: z.array(flowIssueSchema),
7345
7345
  trace: z.array(simulationStepSchema)
7346
7346
  });
7347
- const replayFlowRequestSchema = z.object({ orderItemId: z.string().describe("재생할 주문상품 — 그 흐름의 실행 기록을 초안으로 다시 돌린다") });
7347
+ const replayFlowRequestSchema = strictRequest(z.object({ orderItemId: z.string().describe("재생할 주문상품 — 그 흐름의 실행 기록을 초안으로 다시 돌린다") }));
7348
7348
  const replayFlowResultSchema = z.object({
7349
7349
  orderItemId: z.string(),
7350
7350
  compiled: z.boolean(),
@@ -7492,7 +7492,7 @@ const flowEditSessionViewSchema = z.object({
7492
7492
  streamToken: z.string()
7493
7493
  }).nullable().describe("흐름 채널 구독(SSE, `Authorization: Bearer streamToken`) — session·ops·saved·released 이벤트. 중계가 없는 환경이면 null")
7494
7494
  });
7495
- const claimFlowEditSessionRequestSchema = z.object({ takeover: z.boolean().optional().describe("다른 주체가 잡고 있어도 넘겨받는다. 앞 주체의 저장하지 않은 수정은 사라지고, AI 도우미면 그 실행이 멈춘다") });
7495
+ const claimFlowEditSessionRequestSchema = strictRequest(z.object({ takeover: z.boolean().optional().describe("다른 주체가 잡고 있어도 넘겨받는다. 앞 주체의 저장하지 않은 수정은 사라지고, AI 도우미면 그 실행이 멈춘다") }));
7496
7496
  //#endregion
7497
7497
  //#region src/schemas/promotion.ts
7498
7498
  /**
@@ -8171,7 +8171,7 @@ const couponTargetInputSchema = z.strictObject({
8171
8171
  });
8172
8172
  const couponTimeField = (description) => z.iso.datetime({ offset: true }).nullable().optional().describe(description);
8173
8173
  /** `POST /v1/coupons` */
8174
- const createCouponRequestSchema = z.strictObject({
8174
+ const createCouponRequestSchema = strictRequest(z.strictObject({
8175
8175
  name: z.string().trim().min(1).max(60).describe(descriptions.name),
8176
8176
  description: z.string().max(300).nullable().optional().describe(descriptions.description),
8177
8177
  kind: couponKindSchema,
@@ -8202,12 +8202,12 @@ const createCouponRequestSchema = z.strictObject({
8202
8202
  if (body.issueMethod !== "CODE" && body.perMemberUseLimit != null) issue("perMemberUseLimit", "발급형 쿠폰은 발급 한 장이 한 번 쓰입니다. 1인 발급 수 한도를 사용하십시오");
8203
8203
  if (!(body.conditions?.memberOnly ?? COUPON_CONDITIONS_DEFAULTS.memberOnly) && (body.issueMethod !== "CODE" || body.perMemberUseLimit != null)) issue("conditions", "비회원에게 여는 쿠폰은 코드 입력형이면서 1인 사용 한도가 없어야 합니다");
8204
8204
  if (body.startsAt && body.endsAt && Date.parse(body.endsAt) <= Date.parse(body.startsAt)) issue("endsAt", "종료 시각은 시작 시각보다 뒤여야 합니다");
8205
- });
8205
+ }));
8206
8206
  /**
8207
8207
  * `PATCH /v1/coupons/{couponId}` — 준 항목만 바뀐다. 발급·사용이 시작된 쿠폰(`locked`)은 이름·설명·종료일 연장·
8208
8208
  * 한도 상향만 받고, 그 밖의 항목이 현재 값과 다르면 409 `COUPON_LOCKED`다. 종류·발급 방식은 바꿀 수 없다.
8209
8209
  */
8210
- const updateCouponRequestSchema = z.strictObject({
8210
+ const updateCouponRequestSchema = strictRequest(z.strictObject({
8211
8211
  name: z.string().trim().min(1).max(60).optional().describe(descriptions.name),
8212
8212
  description: z.string().max(300).nullable().optional().describe(descriptions.description),
8213
8213
  benefit: couponBenefitSchema.optional(),
@@ -8221,7 +8221,7 @@ const updateCouponRequestSchema = z.strictObject({
8221
8221
  totalUseLimit: limitField(descriptions.totalUseLimit),
8222
8222
  perMemberIssueLimit: limitField(descriptions.perMemberIssueLimit),
8223
8223
  perMemberUseLimit: limitField(descriptions.perMemberUseLimit)
8224
- });
8224
+ }));
8225
8225
  /** 쿠폰 응답 */
8226
8226
  const couponSchema = z.object({
8227
8227
  couponId: z.string(),
@@ -8489,7 +8489,7 @@ const customerOrderSchema = z.object({
8489
8489
  }))
8490
8490
  });
8491
8491
  /** `POST /v1/customers/{id}/withdraw` 본문 */
8492
- const withdrawCustomerRequestSchema = z.object({
8492
+ const withdrawCustomerRequestSchema = strictRequest(z.object({
8493
8493
  reason: z.enum([
8494
8494
  "BUYER_REQUEST",
8495
8495
  "DORMANT",
@@ -8497,7 +8497,7 @@ const withdrawCustomerRequestSchema = z.object({
8497
8497
  "OTHER"
8498
8498
  ]).describe("처리 사유 — 구매자 요청·장기 미이용·부정 이용·기타. 감사 로그에 남는다"),
8499
8499
  memo: z.string().max(200).optional().describe("사유 보충(선택)")
8500
- });
8500
+ }));
8501
8501
  /** `WITHDRAWAL_BLOCKED`의 `error.details` — 탈퇴를 막는 항목 수(스토어프론트 탈퇴와 같다) */
8502
8502
  const customerWithdrawalBlockedDetailsSchema = z.object({
8503
8503
  inProgressOrderItems: z.number().int(),
@@ -8638,15 +8638,15 @@ const inventoryOperationResultSchema = z.object({
8638
8638
  createdAt: z.string()
8639
8639
  });
8640
8640
  /** 수거 완료의 재입고 지정(Q3) — 생략하면 조합은 할당 순서(교환 → 판매), 위치는 반품지에 연결된 위치 → 할당 위치다 */
8641
- const claimCollectCompleteRequestSchema = z.object({ restock: z.object({
8641
+ const claimCollectCompleteRequestSchema = strictRequest(z.object({ restock: z.object({
8642
8642
  optionId: z.string().min(1).optional().describe("돌아온 조합 — 그 주문상품의 할당 조합이어야 한다(아니면 400 `INVALID_RESTOCK_OPTION`)"),
8643
8643
  inventoryLocationId: z.string().min(1).optional().describe("재입고할 재고 위치")
8644
- }).optional() });
8645
- const claimRedeliveryRequestSchema = z.object({
8644
+ }).optional() }));
8645
+ const claimRedeliveryRequestSchema = strictRequest(z.object({
8646
8646
  carrierCode: z.string().min(1),
8647
8647
  trackingNumber: z.string().min(1),
8648
8648
  inventoryLocationId: z.string().min(1).optional().describe("교환품을 보낼 재고 위치. 생략하면 그 상품의 기준 위치")
8649
- });
8649
+ }));
8650
8650
  //#endregion
8651
8651
  //#region src/schemas/login.ts
8652
8652
  /** 소셜 로그인 공급자 */
@@ -8679,14 +8679,14 @@ const loginSettingsSchema = z.object({
8679
8679
  providers: z.array(loginProviderStatusSchema)
8680
8680
  });
8681
8681
  /** `PATCH /v1/store/login-settings` */
8682
- const updateLoginSettingsRequestSchema = z.object({ passwordLoginEnabled: z.boolean() });
8682
+ const updateLoginSettingsRequestSchema = strictRequest(z.object({ passwordLoginEnabled: z.boolean() }));
8683
8683
  /** `PUT /v1/store/auth-providers/{provider}` — 저장 전에 연결 테스트를 한다 */
8684
- const saveLoginProviderRequestSchema = z.object({
8684
+ const saveLoginProviderRequestSchema = strictRequest(z.object({
8685
8685
  clientId: z.string().trim().min(1).max(200),
8686
8686
  clientSecret: z.string().trim().min(1).max(500)
8687
- });
8687
+ }));
8688
8688
  /** `PATCH /v1/store/auth-providers/{provider}` */
8689
- const updateLoginProviderRequestSchema = z.object({ enabled: z.boolean() });
8689
+ const updateLoginProviderRequestSchema = strictRequest(z.object({ enabled: z.boolean() }));
8690
8690
  /** 연결 테스트 결과 */
8691
8691
  const loginProviderTestSchema = z.object({
8692
8692
  ok: z.boolean(),
@@ -8876,36 +8876,36 @@ const payPalCredentialsInputSchema = z.object({
8876
8876
  clientSecret: secretInput().optional().describe("Secret. 비밀 값. 최초 연결 시 필수, 수정 시 생략하면 기존 값을 유지한다")
8877
8877
  });
8878
8878
  const upsertEnvironment = () => pgEnvironmentSchema.describe("저장할 자격 증명 세트. PG마다 `TEST`(샌드박스)·`LIVE`(실결제) 세트를 따로 둔다. 어느 세트로 결제할지는 이 값이 아니라 샌드박스 스위치(`sandboxMode`)가 정한다");
8879
- const upsertTossPaymentsProviderRequestSchema = z.object({
8879
+ const upsertTossPaymentsProviderRequestSchema = strictRequest(z.object({
8880
8880
  environment: upsertEnvironment(),
8881
8881
  credentials: tossPaymentsCredentialsInputSchema
8882
- });
8883
- const upsertPortOneProviderRequestSchema = z.object({
8882
+ }));
8883
+ const upsertPortOneProviderRequestSchema = strictRequest(z.object({
8884
8884
  environment: upsertEnvironment(),
8885
8885
  credentials: portOneCredentialsInputSchema
8886
- });
8887
- const upsertKakaoPayProviderRequestSchema = z.object({
8886
+ }));
8887
+ const upsertKakaoPayProviderRequestSchema = strictRequest(z.object({
8888
8888
  environment: upsertEnvironment(),
8889
8889
  credentials: kakaoPayCredentialsInputSchema
8890
- });
8891
- const upsertNaverPayProviderRequestSchema = z.object({
8890
+ }));
8891
+ const upsertNaverPayProviderRequestSchema = strictRequest(z.object({
8892
8892
  environment: upsertEnvironment(),
8893
8893
  credentials: naverPayCredentialsInputSchema
8894
- });
8895
- const upsertPayPalProviderRequestSchema = z.object({
8894
+ }));
8895
+ const upsertPayPalProviderRequestSchema = strictRequest(z.object({
8896
8896
  environment: upsertEnvironment(),
8897
8897
  credentials: payPalCredentialsInputSchema
8898
- });
8899
- const updatePaymentProviderRequestSchema = z.object({
8898
+ }));
8899
+ const updatePaymentProviderRequestSchema = strictRequest(z.object({
8900
8900
  enabled: z.boolean().optional().describe("사용 여부. 켜려면 활성 환경(샌드박스면 TEST, 아니면 LIVE) 자격 증명이 모두 저장돼 있어야 한다(`PAYMENT_PROVIDER_INCOMPLETE`)"),
8901
8901
  sandboxMode: z.boolean().optional().describe("샌드박스 스위치. `true`면 새 결제에 TEST 세트를 쓴다(항상 허용). `false`(라이브 전환)는 라이브 개방 조건을 모두 충족해야 한다(`LIVE_GATE_NOT_MET`)"),
8902
8902
  easyPayProviders: z.array(easyPayProviderSchema).max(easyPayProviderSchema.options.length).optional().describe("이 PG로 받는 간편결제사 — 통째로 바꾼다. PG 계약(포트원은 채널)에 추가한 간편결제만 켠다. 켠 간편결제사마다 스토어프론트 결제 옵션이 하나씩 생긴다(포트원은 간편결제를 받는 채널이 있어야 한다)")
8903
- }).refine((body) => body.enabled !== void 0 || body.sandboxMode !== void 0 || body.easyPayProviders !== void 0, { message: "enabled·sandboxMode·easyPayProviders 중 하나는 보내야 합니다" });
8904
- const verifyPaymentProviderRequestSchema = z.object({ environment: pgEnvironmentSchema.optional().describe("연결 테스트할 자격 증명 세트. 생략하면 활성 환경(샌드박스면 TEST, 아니면 LIVE)") });
8905
- const updatePaymentSettingsRequestSchema = z.object({
8903
+ }).refine((body) => body.enabled !== void 0 || body.sandboxMode !== void 0 || body.easyPayProviders !== void 0, { message: "enabled·sandboxMode·easyPayProviders 중 하나는 보내야 합니다" }));
8904
+ const verifyPaymentProviderRequestSchema = strictRequest(z.object({ environment: pgEnvironmentSchema.optional().describe("연결 테스트할 자격 증명 세트. 생략하면 활성 환경(샌드박스면 TEST, 아니면 LIVE)") }));
8905
+ const updatePaymentSettingsRequestSchema = strictRequest(z.object({
8906
8906
  priority: z.array(pgProviderSchema).min(1).max(50).optional().describe("PG 우선순위(앞이 1순위) — 스토어프론트 결제 옵션 목록의 PG 순서다. 연결된 PG 수만큼 **모두 한 번씩** 나열한다(길이 상한 50은 안전장치다). 빠지거나 중복되거나 연결되지 않은 PG가 있으면 `INVALID_PRIORITY`"),
8907
8907
  checkoutOrigins: z.array(z.string().max(200)).max(20).optional().describe("결제 도메인 — 통째로 바꾼다. 결제를 마친 구매자가 돌아올 스토어프론트 origin(`https://shop.example.com`, https·경로 없음). 결제 시작의 `returnUrl`은 이 목록 안이어야 한다. 비어 있으면 실결제를 시작할 수 없다(테스트 결제는 localhost 허용). 같은 도메인을 PG 가맹 정보에도 등록한다. 형식이 틀리면 `INVALID_CHECKOUT_ORIGIN`")
8908
- }).refine((body) => body.priority !== void 0 || body.checkoutOrigins !== void 0, { message: "priority 또는 checkoutOrigins 중 하나는 보내야 합니다" });
8908
+ }).refine((body) => body.priority !== void 0 || body.checkoutOrigins !== void 0, { message: "priority 또는 checkoutOrigins 중 하나는 보내야 합니다" }));
8909
8909
  const maskedSecretSchema = z.object({
8910
8910
  field: z.string().describe("비밀 필드 이름 (`secretKey`·`apiSecret`·`webhookSecret`)"),
8911
8911
  configured: z.boolean().describe("값이 저장돼 있는가"),
@@ -9201,7 +9201,7 @@ const paymentListQuerySchema = z.object({
9201
9201
  size: z.string().optional().describe("페이지 크기 (기본 20, 최대 100)")
9202
9202
  });
9203
9203
  /** 테스트모드 키 활성화 요청(2026-10-14) — `PUT /v1/store/payment-providers/{provider}/platform-test-keys` */
9204
- const setPlatformTestKeysRequestSchema = z.object({ enabled: z.boolean().describe("`true`면 sayren 지정 테스트 키를 TEST 세트로 저장하고 PG를 켠다. `false`면 고정만 푼다(TEST 세트는 그대로)") });
9204
+ const setPlatformTestKeysRequestSchema = strictRequest(z.object({ enabled: z.boolean().describe("`true`면 sayren 지정 테스트 키를 TEST 세트로 저장하고 PG를 켠다. `false`면 고정만 푼다(TEST 세트는 그대로)") }));
9205
9205
  /** 결제 상태 PG 한 줄 — 비밀 값·공개 키 없이 연결·사용·환경·테스트모드 키만 */
9206
9206
  const paymentStatusProviderSchema = z.object({
9207
9207
  provider: pgProviderResponseSchema,
@@ -9375,7 +9375,7 @@ const deliveryMethodSchema = z.enum([
9375
9375
  "QUICK",
9376
9376
  "NOTHING"
9377
9377
  ]);
9378
- const dispatchRequestSchema = z.object({
9378
+ const dispatchRequestSchema = strictRequest(z.object({
9379
9379
  deliveryMethod: deliveryMethodSchema,
9380
9380
  carrierCode: z.string().optional(),
9381
9381
  trackingNumber: z.string().optional(),
@@ -9387,7 +9387,7 @@ const dispatchRequestSchema = z.object({
9387
9387
  * 잡 전체를 멈출 수 있어 입력에서 좁혔다. 검증을 통과한 값도 저장 전에 UTC ISO로 정규화한다.
9388
9388
  */
9389
9389
  dispatchedAt: z.iso.datetime({ offset: true }).optional()
9390
- }).refine((value) => value.deliveryMethod === "DIRECT" || value.deliveryMethod === "NOTHING" ? true : !!value.carrierCode && !!value.trackingNumber, { message: "COURIER/QUICK 발송은 carrierCode와 trackingNumber가 필수입니다" });
9390
+ }).refine((value) => value.deliveryMethod === "DIRECT" || value.deliveryMethod === "NOTHING" ? true : !!value.carrierCode && !!value.trackingNumber, { message: "COURIER/QUICK 발송은 carrierCode와 trackingNumber가 필수입니다" }));
9391
9391
  const deliveryInfoSchema = z.object({
9392
9392
  orderItemId: z.string(),
9393
9393
  deliveryMethod: deliveryMethodSchema,
@@ -9434,7 +9434,7 @@ const carrierSchema = z.object({
9434
9434
  name: z.string(),
9435
9435
  trackingUrlTemplate: z.string()
9436
9436
  });
9437
- const holdOrderItemRequestSchema = z.object({
9437
+ const holdOrderItemRequestSchema = strictRequest(z.object({
9438
9438
  reason: z.enum([
9439
9439
  "OUT_OF_STOCK",
9440
9440
  "CUSTOMER_REQUEST",
@@ -9443,8 +9443,8 @@ const holdOrderItemRequestSchema = z.object({
9443
9443
  ]),
9444
9444
  detail: z.string().max(500).optional(),
9445
9445
  expectedDispatchDate: z.string()
9446
- });
9447
- const cancelBySellerRequestSchema = z.object({
9446
+ }));
9447
+ const cancelBySellerRequestSchema = strictRequest(z.object({
9448
9448
  reason: z.enum([
9449
9449
  "OUT_OF_STOCK",
9450
9450
  "PRICE_ERROR",
@@ -9454,17 +9454,17 @@ const cancelBySellerRequestSchema = z.object({
9454
9454
  detail: z.string().max(500).optional(),
9455
9455
  quantity: z.number().int().min(1).optional().describe("취소할 수량. 생략하면 남은 수량 전체다. 남은 수량보다 크면 400 `INVALID_QUANTITY`. 일부만 취소하면 주문상품은 상태를 유지한 채 `canceledQuantity`가 늘고, 전 수량이 소진되면 `CANCELED`가 된다"),
9456
9456
  holdRefund: z.boolean().optional().describe("true면 주문만 취소하고 결제 환불은 대기로 남긴다(#115). 정산·재고·적립금 반환은 그대로 하고, PG 환불은 결제 상세의 `POST /v1/payments/refunds/{refundId}/process`로 따로 한다. 생략하면 취소와 함께 환불한다")
9457
- });
9457
+ }));
9458
9458
  /**
9459
9459
  * 제공 처리 요청 (이슈 #48) — `POST /v1/order-items/{orderItemId}/fulfill`. 배송 없는 주문상품(`MANUAL`)을
9460
9460
  * 제공 완료(`DELIVERED`)로 바꾼다.
9461
9461
  */
9462
- const fulfillRequestSchema = z.object({ note: z.string().trim().max(1e3).optional().describe("구매자에게 보일 이용 안내(예: 예약 방법, 수령 장소). 주문 상세에 실린다") });
9462
+ const fulfillRequestSchema = strictRequest(z.object({ note: z.string().trim().max(1e3).optional().describe("구매자에게 보일 이용 안내(예: 예약 방법, 수령 장소). 주문 상세에 실린다") }));
9463
9463
  /** 제공 처리 일괄 요청 — `POST /v1/order-items/fulfill` */
9464
- const fulfillBulkRequestSchema = z.object({
9464
+ const fulfillBulkRequestSchema = strictRequest(z.object({
9465
9465
  orderItemIds: z.array(z.string()).min(1).max(100),
9466
9466
  note: fulfillRequestSchema.shape.note
9467
- });
9467
+ }));
9468
9468
  /** `POST /v1/order-items/{orderItemId}/cancel` 응답 — 셀러 직권취소 환불 결과 */
9469
9469
  const cancelBySellerResultSchema = z.object({
9470
9470
  orderItemId: z.string(),
@@ -9822,17 +9822,17 @@ const savedOptionSetSchema = z.object({
9822
9822
  createdAt: z.string(),
9823
9823
  updatedAt: z.string()
9824
9824
  });
9825
- const savedOptionValueRequestSchema = z.object({
9825
+ const savedOptionValueRequestSchema = z.strictObject({
9826
9826
  name: nameSchema,
9827
9827
  additionalPrice: z.number().int().min(0).default(0)
9828
9828
  });
9829
- const savedOptionGroupRequestSchema = z.object({
9829
+ const savedOptionGroupRequestSchema = z.strictObject({
9830
9830
  name: nameSchema,
9831
9831
  kind: optionGroupKindSchema,
9832
9832
  required: z.boolean().default(false).describe("추가 선택만 쓴다. 조합 옵션명에 true를 주면 400이다"),
9833
9833
  values: z.array(savedOptionValueRequestSchema).min(1).max(100)
9834
9834
  });
9835
- const savedCustomInputRequestSchema = z.object({
9835
+ const savedCustomInputRequestSchema = z.strictObject({
9836
9836
  label: z.string().trim().min(1).max(30),
9837
9837
  placeholder: z.string().trim().max(100).nullish(),
9838
9838
  maxLength: z.number().int().min(1).max(200),
@@ -9846,7 +9846,7 @@ const savedCustomInputRequestSchema = z.object({
9846
9846
  * 저장 옵션 만들기·수정 본문. 조합 옵션명은 최대 `MAX_OPTION_GROUPS`개, 추가 선택은 최대 `MAX_ADDON_GROUPS`개이고
9847
9847
  * 옵션명과 직접 입력 항목이 모두 비면 400이다. 수정(PUT)은 통째로 바꾼다.
9848
9848
  */
9849
- const savedOptionSetRequestSchema = z.object({
9849
+ const savedOptionSetRequestSchema = z.strictObject({
9850
9850
  name: z.string().trim().min(1).max(50),
9851
9851
  optionSort: optionSortSchema.default("REGISTERED"),
9852
9852
  groups: z.array(savedOptionGroupRequestSchema).max(8).default([]),
@@ -9858,7 +9858,7 @@ const savedOptionSetRequestSchema = z.object({
9858
9858
  * 조합 추가금이 값별 추가금의 합으로 되돌려지면 값별로 저장하고, 조합마다 제각각이면 추가금을 빼고
9859
9859
  * 응답 `additionalPriceDropped: true`로 알린다. `replaceOptionSetId`를 주면 그 저장 옵션을 덮어쓴다(이름 중복의 덮어쓰기).
9860
9860
  */
9861
- const savedOptionSetFromProductRequestSchema = z.object({
9861
+ const savedOptionSetFromProductRequestSchema = z.strictObject({
9862
9862
  productId: z.string(),
9863
9863
  name: z.string().trim().min(1).max(50),
9864
9864
  includeOptions: z.boolean().default(true).describe("옵션명(조합·추가 선택)을 저장하는가"),
@@ -9976,14 +9976,14 @@ const brandBriefSchema = z.object({
9976
9976
  notes: z.string().trim().max(4e3).nullable().describe("인터뷰 메모 (타깃 고객, 판매 방식, 셀러가 강조한 요구)")
9977
9977
  });
9978
9978
  /** `PUT /v1/storefront/brand-brief` — 브리프 전체를 바꾼다. 생략한 항목은 비운다 */
9979
- const putBrandBriefRequestSchema = z.object({
9979
+ const putBrandBriefRequestSchema = strictRequest(z.object({
9980
9980
  industry: storeIndustrySchema.nullable().default(null),
9981
9981
  mood: z.string().trim().max(200).nullable().default(null),
9982
9982
  pointColor: z.string().regex(/^#[0-9a-fA-F]{6}$/, "#RRGGBB 형식이어야 합니다").nullable().default(null),
9983
9983
  references: z.array(brandBriefReferenceSchema).max(5).default([]),
9984
9984
  productSource: brandBriefProductSourceSchema.nullable().default(null),
9985
9985
  notes: z.string().trim().max(4e3).nullable().default(null)
9986
- });
9986
+ }));
9987
9987
  /** `GET·PUT /v1/storefront/brand-brief` 응답 — 저장한 적이 없으면 `brief`·`updatedAt`이 null이다 */
9988
9988
  const brandBriefViewSchema = z.object({
9989
9989
  brief: brandBriefSchema.nullable(),
@@ -11185,7 +11185,7 @@ async function refreshUserToken(options) {
11185
11185
  ...options.resource ? { resource: options.resource } : {}
11186
11186
  }, options.fetch);
11187
11187
  }
11188
- const customerDelegationRequestSchema = z.object({
11188
+ const customerDelegationRequestSchema = strictRequest(z.object({
11189
11189
  uid: z.string().regex(/^[A-Za-z0-9._:@|-]{1,128}$/).optional().describe("셀러 자체 인증의 사용자 id(1~128자, 영문·숫자·`._:@|-`). 처음 보는 id면 구매자를 만든다"),
11190
11190
  email: z.email().max(254).optional().describe("uid가 없으면 이메일로 지목한다. uid와 함께 주면 처음 만들 때의 이메일이다"),
11191
11191
  name: z.string().max(50).optional().describe("처음 만들 때의 이름"),
@@ -11193,7 +11193,7 @@ const customerDelegationRequestSchema = z.object({
11193
11193
  }).refine((value) => Boolean(value.uid || value.email), {
11194
11194
  message: "uid 또는 email이 필요합니다",
11195
11195
  path: ["uid"]
11196
- }).describe("구매자 위임 요청");
11196
+ }).describe("구매자 위임 요청"));
11197
11197
  const customerDelegationSchema = z.object({
11198
11198
  accessToken: z.string().describe("구매자 액세스 토큰(스토어프론트 API Bearer)"),
11199
11199
  tokenType: z.literal("Bearer"),