@sayren/store-sdk 0.40.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { RESERVED_STORE_CODES } from "./store";
3
+ import { strictRequest } from "./strict";
3
4
 
4
5
  /**
5
6
  * 스토어프론트 호스팅 (이슈 #53, M1: 템플릿 발행) — 관리 API `/v1/storefront/**`의 계약이다.
@@ -219,17 +220,19 @@ export const TEMPLATE_STORE_LOCKED_BUSINESS_FIELDS = [
219
220
  "customerCenterEmail",
220
221
  ] as const;
221
222
 
222
- export const updateStoreTemplateRequestSchema = z.object({
223
- name: z.string().trim().min(1).max(40).describe("템플릿 이름(1~40자)"),
224
- description: z.string().trim().max(200).describe("템플릿 소개(200자까지)"),
225
- thumbnailUrl: z
226
- .url({ protocol: /^https$/ })
227
- .max(2048)
228
- .nullable()
229
- .describe(
230
- "썸네일 이미지 주소(https). 이미지 업로드(`POST /v1/uploads`)로 받은 주소를 쓴다. 없으면 null",
231
- ),
232
- });
223
+ export const updateStoreTemplateRequestSchema = strictRequest(
224
+ z.object({
225
+ name: z.string().trim().min(1).max(40).describe("템플릿 이름(1~40자)"),
226
+ description: z.string().trim().max(200).describe("템플릿 소개(200자까지)"),
227
+ thumbnailUrl: z
228
+ .url({ protocol: /^https$/ })
229
+ .max(2048)
230
+ .nullable()
231
+ .describe(
232
+ "썸네일 이미지 주소(https). 이미지 업로드(`POST /v1/uploads`)로 받은 주소를 쓴다. 없으면 null",
233
+ ),
234
+ }),
235
+ );
233
236
  export type UpdateStoreTemplateRequest = z.infer<typeof updateStoreTemplateRequestSchema>;
234
237
 
235
238
  // ─── 버전 ─────────────────────────────────────────────────
@@ -493,16 +496,18 @@ export type StorefrontDeployment = z.infer<typeof storefrontDeploymentSchema>;
493
496
 
494
497
  // ─── 요청 ─────────────────────────────────────────────────
495
498
 
496
- export const createStorefrontSiteRequestSchema = z.object({
497
- templateId: z.string().min(1).max(64).describe("템플릿 id"),
498
- slug: storefrontSlugSchema.optional().describe("주소 슬러그. 생략하면 상점 코드"),
499
- copyContent: z
500
- .boolean()
501
- .optional()
502
- .describe(
503
- "템플릿 상점의 템플릿일 때만 — true면 템플릿 상점의 콘텐츠(레이아웃, 상품·카테고리·컬렉션·주문 흐름·배송·환불 정책)를 이 상점으로 복사한다. 쿠폰·적립금·리뷰는 복사하지 않는다. 생략하거나 false면 레이아웃 콘텐츠를 비우고 빌드한다",
504
- ),
505
- });
499
+ export const createStorefrontSiteRequestSchema = strictRequest(
500
+ z.object({
501
+ templateId: z.string().min(1).max(64).describe("템플릿 id"),
502
+ slug: storefrontSlugSchema.optional().describe("주소 슬러그. 생략하면 상점 코드"),
503
+ copyContent: z
504
+ .boolean()
505
+ .optional()
506
+ .describe(
507
+ "템플릿 상점의 템플릿일 때만 — true면 템플릿 상점의 콘텐츠(레이아웃, 상품·카테고리·컬렉션·주문 흐름·배송·환불 정책)를 이 상점으로 복사한다. 쿠폰·적립금·리뷰는 복사하지 않는다. 생략하거나 false면 레이아웃 콘텐츠를 비우고 빌드한다",
508
+ ),
509
+ }),
510
+ );
506
511
  export type CreateStorefrontSiteRequest = z.infer<typeof createStorefrontSiteRequestSchema>;
507
512
 
508
513
  /** @deprecated 템플릿 변경은 지원하지 않는다(항상 409 `TEMPLATE_CHANGE_NOT_SUPPORTED`). 호환을 위해 스키마만 남겼다 */
@@ -522,42 +527,55 @@ export type ChangeStorefrontTemplateRequest = z.infer<typeof changeStorefrontTem
522
527
  * 지금 발행 버전이 그 값일 때만 바꾼다(화면을 본 뒤 다른 사람이 바꿨으면 409 `STOREFRONT_SITE_CHANGED`).
523
528
  * 이미 그 버전이 발행돼 있으면 아무것도 바꾸지 않고 200이다(같은 요청의 재실행이 안전하다).
524
529
  */
525
- export const publishStorefrontVersionRequestSchema = z.object({
526
- expectedPublishedVersionId: z
527
- .string()
528
- .nullable()
529
- .optional()
530
- .describe("화면에서 본 발행 버전 id (발행 전이었으면 null). 생략하면 확인하지 않는다"),
531
- });
530
+ export const publishStorefrontVersionRequestSchema = strictRequest(
531
+ z.object({
532
+ expectedPublishedVersionId: z
533
+ .string()
534
+ .nullable()
535
+ .optional()
536
+ .describe("화면에서 본 발행 버전 id (발행 전이었으면 null). 생략하면 확인하지 않는다"),
537
+ }),
538
+ );
532
539
  export type PublishStorefrontVersionRequest = z.infer<typeof publishStorefrontVersionRequestSchema>;
533
540
 
534
- export const rollbackStorefrontRequestSchema = z.object({
535
- versionId: z.string().optional().describe("되돌릴 버전. 생략하면 지금 버전 직전에 발행했던 버전"),
536
- expectedPublishedVersionId: z.string().nullable().optional(),
537
- });
541
+ export const rollbackStorefrontRequestSchema = strictRequest(
542
+ z.object({
543
+ versionId: z
544
+ .string()
545
+ .optional()
546
+ .describe("되돌릴 버전. 생략하면 지금 버전 직전에 발행했던 버전"),
547
+ expectedPublishedVersionId: z.string().nullable().optional(),
548
+ }),
549
+ );
538
550
  export type RollbackStorefrontRequest = z.infer<typeof rollbackStorefrontRequestSchema>;
539
551
 
540
- export const changeStorefrontSlugRequestSchema = z.object({
541
- slug: storefrontSlugSchema,
542
- });
552
+ export const changeStorefrontSlugRequestSchema = strictRequest(
553
+ z.object({
554
+ slug: storefrontSlugSchema,
555
+ }),
556
+ );
543
557
  export type ChangeStorefrontSlugRequest = z.infer<typeof changeStorefrontSlugRequestSchema>;
544
558
 
545
- export const setStorefrontVisibilityRequestSchema = z.object({
546
- visibility: storefrontVisibilitySchema,
547
- expectedVisibility: storefrontVisibilitySchema
548
- .optional()
549
- .describe("화면에서 본 공개 상태. 주면 지금 값이 같을 때만 바꾼다"),
550
- });
559
+ export const setStorefrontVisibilityRequestSchema = strictRequest(
560
+ z.object({
561
+ visibility: storefrontVisibilitySchema,
562
+ expectedVisibility: storefrontVisibilitySchema
563
+ .optional()
564
+ .describe("화면에서 본 공개 상태. 주면 지금 값이 같을 때만 바꾼다"),
565
+ }),
566
+ );
551
567
  export type SetStorefrontVisibilityRequest = z.infer<typeof setStorefrontVisibilityRequestSchema>;
552
568
 
553
569
  /** 호스팅 끄기 — 사이트를 닫고(`CLOSED`) 슬러그를 보류한다. 확인 입력은 받지 않는다(2026-10-22) */
554
- export const disableStorefrontHostingRequestSchema = z.object({
555
- confirmSlug: z
556
- .string()
557
- .max(200)
558
- .optional()
559
- .describe("쓰지 않는다 — 보내도 무시한다(옛 확인 입력)"),
560
- });
570
+ export const disableStorefrontHostingRequestSchema = strictRequest(
571
+ z.object({
572
+ confirmSlug: z
573
+ .string()
574
+ .max(200)
575
+ .optional()
576
+ .describe("쓰지 않는다 — 보내도 무시한다(옛 확인 입력)"),
577
+ }),
578
+ );
561
579
  export type DisableStorefrontHostingRequest = z.infer<typeof disableStorefrontHostingRequestSchema>;
562
580
 
563
581
  export const storefrontSlugAvailabilityQuerySchema = z.object({
@@ -569,16 +587,18 @@ export const storefrontSlugAvailabilityQuerySchema = z.object({
569
587
  * (오픈 준비 중이어도 실제 화면), 링크를 5분 안에 열면 그 브라우저에 그 주소 전용 12시간 쿠키가 생긴다. 사이트 주소는 오픈 전이면
570
588
  * 누구에게나 「오픈 준비 중」이다. 미리보기 주소에 쿠키 없이 들어오면 상점 플랫폼 로그인과 OWNER·ADMIN 확인을 거친다.
571
589
  */
572
- export const createStorefrontPreviewLinkRequestSchema = z.object({
573
- path: z.string().max(512).optional().describe("열 경로 (`/`로 시작). 생략하면 `/`"),
574
- versionId: z
575
- .string()
576
- .max(64)
577
- .optional()
578
- .describe(
579
- "볼 버전. 생략하면 발행 버전이다. 보관 기간이 지난 버전(`previewable: false`)은 409 `STOREFRONT_VERSION_NOT_PREVIEWABLE`",
580
- ),
581
- });
590
+ export const createStorefrontPreviewLinkRequestSchema = strictRequest(
591
+ z.object({
592
+ path: z.string().max(512).optional().describe("열 경로 (`/`로 시작). 생략하면 `/`"),
593
+ versionId: z
594
+ .string()
595
+ .max(64)
596
+ .optional()
597
+ .describe(
598
+ "볼 버전. 생략하면 발행 버전이다. 보관 기간이 지난 버전(`previewable: false`)은 409 `STOREFRONT_VERSION_NOT_PREVIEWABLE`",
599
+ ),
600
+ }),
601
+ );
582
602
  export type CreateStorefrontPreviewLinkRequest = z.infer<
583
603
  typeof createStorefrontPreviewLinkRequestSchema
584
604
  >;
@@ -840,13 +860,15 @@ export const storefrontSiteDomainsSchema = z.object({
840
860
  });
841
861
  export type StorefrontSiteDomains = z.infer<typeof storefrontSiteDomainsSchema>;
842
862
 
843
- export const addStorefrontDomainRequestSchema = z.object({
844
- hostname: z
845
- .string()
846
- .min(1)
847
- .max(300)
848
- .describe("연결할 도메인 (예: `shop.brand.com`). 서버가 소문자·punycode로 정규화한다"),
849
- });
863
+ export const addStorefrontDomainRequestSchema = strictRequest(
864
+ z.object({
865
+ hostname: z
866
+ .string()
867
+ .min(1)
868
+ .max(300)
869
+ .describe("연결할 도메인 (예: `shop.brand.com`). 서버가 소문자·punycode로 정규화한다"),
870
+ }),
871
+ );
850
872
  export type AddStorefrontDomainRequest = z.infer<typeof addStorefrontDomainRequestSchema>;
851
873
 
852
874
  // ─── 편집 (M2b) ───────────────────────────────────────────
@@ -990,14 +1012,16 @@ export type StorefrontEditSession = z.infer<typeof storefrontEditSessionSchema>;
990
1012
  * 편집 환경이 켜져 있지 않으면 409 `EDIT_SESSION_REQUIRED`
991
1013
  */
992
1014
  /** `POST /v1/storefront/site/editing/git/merge` — 켜진 편집 환경 작업 트리에 운영 브랜치 최신 커밋을 합친다(#138, Git 원천 사이트) */
993
- export const storefrontGitMergeRequestSchema = z.object({
994
- action: z
995
- .enum(["merge", "continue", "abort"])
996
- .default("merge")
997
- .describe(
998
- "`merge` 최신 운영 브랜치를 합친다 · `continue` 충돌 표시를 고친 뒤 합치기를 끝낸다 · `abort` 합치기를 취소하고 합치기 전으로 돌린다",
999
- ),
1000
- });
1015
+ export const storefrontGitMergeRequestSchema = strictRequest(
1016
+ z.object({
1017
+ action: z
1018
+ .enum(["merge", "continue", "abort"])
1019
+ .default("merge")
1020
+ .describe(
1021
+ "`merge` 최신 운영 브랜치를 합친다 · `continue` 충돌 표시를 고친 뒤 합치기를 끝낸다 · `abort` 합치기를 취소하고 합치기 전으로 돌린다",
1022
+ ),
1023
+ }),
1024
+ );
1001
1025
  export type StorefrontGitMergeRequest = z.input<typeof storefrontGitMergeRequestSchema>;
1002
1026
 
1003
1027
  export const storefrontGitMergeSchema = z.object({
@@ -1134,25 +1158,27 @@ export type StorefrontFileContent = z.infer<typeof storefrontFileContentSchema>;
1134
1158
  * 해시가 같아야 한다. 조건을 **모두** 확인한 뒤 쓰고, 하나라도 어긋나면 아무것도 쓰지 않는다(409 `FILE_CHANGED`,
1135
1159
  * `error.details.conflicts`).
1136
1160
  */
1137
- export const writeStorefrontFilesRequestSchema = z.object({
1138
- files: z
1139
- .array(
1140
- z.object({
1141
- path: storefrontFilePathSchema,
1142
- content: z.string().describe("내용 (`encoding`에 따라 텍스트 또는 base64)"),
1143
- encoding: storefrontFileEncodingSchema.optional().describe("기본 `utf8`"),
1144
- expectedHash: storefrontFileHashSchema
1145
- .nullable()
1146
- .optional()
1147
- .describe("쓰기 조건. 생략 = 조건 없음 · null = 새 파일 · 값 = 지금 해시가 같을 때만"),
1161
+ export const writeStorefrontFilesRequestSchema = strictRequest(
1162
+ z.object({
1163
+ files: z
1164
+ .array(
1165
+ z.object({
1166
+ path: storefrontFilePathSchema,
1167
+ content: z.string().describe("내용 (`encoding`에 따라 텍스트 또는 base64)"),
1168
+ encoding: storefrontFileEncodingSchema.optional().describe("기본 `utf8`"),
1169
+ expectedHash: storefrontFileHashSchema
1170
+ .nullable()
1171
+ .optional()
1172
+ .describe("쓰기 조건. 생략 = 조건 없음 · null = 새 파일 · 값 = 지금 해시가 같을 때만"),
1173
+ }),
1174
+ )
1175
+ .min(1)
1176
+ .max(STOREFRONT_FILE_BATCH_MAX)
1177
+ .refine((files) => new Set(files.map((file) => file.path)).size === files.length, {
1178
+ message: "같은 경로를 두 번 실을 수 없습니다",
1148
1179
  }),
1149
- )
1150
- .min(1)
1151
- .max(STOREFRONT_FILE_BATCH_MAX)
1152
- .refine((files) => new Set(files.map((file) => file.path)).size === files.length, {
1153
- message: "같은 경로를 두 번 실을 수 없습니다",
1154
- }),
1155
- });
1180
+ }),
1181
+ );
1156
1182
  export type WriteStorefrontFilesRequest = z.infer<typeof writeStorefrontFilesRequestSchema>;
1157
1183
 
1158
1184
  export const storefrontFileWriteResultSchema = z.object({
@@ -1172,10 +1198,12 @@ export const storefrontFileConflictDetailsSchema = z.object({
1172
1198
  export type StorefrontFileConflictDetails = z.infer<typeof storefrontFileConflictDetailsSchema>;
1173
1199
 
1174
1200
  /** `POST /v1/storefront/site/files/delete` — 없는 파일이면 `deleted: false`(조건을 주면 409 `FILE_CHANGED`) */
1175
- export const deleteStorefrontFileRequestSchema = z.object({
1176
- path: storefrontFilePathSchema,
1177
- expectedHash: storefrontFileHashSchema.optional().describe("주면 지금 해시가 같을 때만 지운다"),
1178
- });
1201
+ export const deleteStorefrontFileRequestSchema = strictRequest(
1202
+ z.object({
1203
+ path: storefrontFilePathSchema,
1204
+ expectedHash: storefrontFileHashSchema.optional().describe("주면 지금 해시가 같을 때만 지운다"),
1205
+ }),
1206
+ );
1179
1207
  export type DeleteStorefrontFileRequest = z.infer<typeof deleteStorefrontFileRequestSchema>;
1180
1208
 
1181
1209
  export const storefrontFileDeleteResultSchema = z.object({
@@ -1185,15 +1213,19 @@ export const storefrontFileDeleteResultSchema = z.object({
1185
1213
  export type StorefrontFileDeleteResult = z.infer<typeof storefrontFileDeleteResultSchema>;
1186
1214
 
1187
1215
  /** `POST /v1/storefront/site/files/move` — 이름 바꾸기·옮기기 */
1188
- export const moveStorefrontFileRequestSchema = z.object({
1189
- from: storefrontFilePathSchema,
1190
- to: storefrontFilePathSchema,
1191
- expectedHash: storefrontFileHashSchema.optional().describe("주면 `from`의 지금 해시가 같을 때만"),
1192
- overwrite: z
1193
- .boolean()
1194
- .optional()
1195
- .describe("`to`에 파일이 있으면 덮는가 (기본 false → 409 `FILE_EXISTS`)"),
1196
- });
1216
+ export const moveStorefrontFileRequestSchema = strictRequest(
1217
+ z.object({
1218
+ from: storefrontFilePathSchema,
1219
+ to: storefrontFilePathSchema,
1220
+ expectedHash: storefrontFileHashSchema
1221
+ .optional()
1222
+ .describe("주면 `from`의 지금 해시가 같을 때만"),
1223
+ overwrite: z
1224
+ .boolean()
1225
+ .optional()
1226
+ .describe("`to`에 파일이 있으면 덮는가 (기본 false → 409 `FILE_EXISTS`)"),
1227
+ }),
1228
+ );
1197
1229
  export type MoveStorefrontFileRequest = z.infer<typeof moveStorefrontFileRequestSchema>;
1198
1230
 
1199
1231
  export const storefrontFileMoveResultSchema = z.object({
@@ -1208,23 +1240,25 @@ export type StorefrontFileMoveResult = z.infer<typeof storefrontFileMoveResultSc
1208
1240
  /** 정규식 검색의 패턴 길이 상한 — 편집 Sandbox가 실행 비용을 막으려고 더 좁게 받는다(sandbox-api §4.6) */
1209
1241
  export const STOREFRONT_SEARCH_REGEX_MAX_LENGTH = 100;
1210
1242
 
1211
- export const searchStorefrontFilesRequestSchema = z
1212
- .object({
1213
- query: z.string().min(1).max(200),
1214
- regex: z
1215
- .boolean()
1216
- .optional()
1217
- .describe(
1218
- "`query`를 정규식으로 본다 (기본 false = 글자 그대로). 정규식은 100자까지이고 중첩 수량자(`(a+)+`)·반복되는 선택(`(a|b)+`)·역참조·뒤 보기, 가변 수량자(`*`·`+`·`?`·`{n,m}`, 넓은 범위는 2로 센다) 차수 4 초과는 400 `INVALID_REQUEST`다. 차수가 클수록 긴 줄은 건너뛰고 비용이 크면 `truncated`로 끊는다",
1219
- ),
1220
- caseSensitive: z.boolean().optional().describe("대소문자를 가리는가 (기본 false)"),
1221
- pathPrefix: z.string().max(256).optional(),
1222
- maxResults: z.number().int().min(1).max(200).optional().describe("기본 50"),
1223
- })
1224
- .refine((body) => !body.regex || body.query.length <= STOREFRONT_SEARCH_REGEX_MAX_LENGTH, {
1225
- message: `정규식은 ${STOREFRONT_SEARCH_REGEX_MAX_LENGTH}자까지입니다`,
1226
- path: ["query"],
1227
- });
1243
+ export const searchStorefrontFilesRequestSchema = strictRequest(
1244
+ z
1245
+ .object({
1246
+ query: z.string().min(1).max(200),
1247
+ regex: z
1248
+ .boolean()
1249
+ .optional()
1250
+ .describe(
1251
+ "`query`를 정규식으로 본다 (기본 false = 글자 그대로). 정규식은 100자까지이고 중첩 수량자(`(a+)+`)·반복되는 선택(`(a|b)+`)·역참조·뒤 보기, 가변 수량자(`*`·`+`·`?`·`{n,m}`, 넓은 범위는 2로 센다) 차수 4 초과는 400 `INVALID_REQUEST`다. 차수가 클수록 긴 줄은 건너뛰고 비용이 크면 `truncated`로 끊는다",
1252
+ ),
1253
+ caseSensitive: z.boolean().optional().describe("대소문자를 가리는가 (기본 false)"),
1254
+ pathPrefix: z.string().max(256).optional(),
1255
+ maxResults: z.number().int().min(1).max(200).optional().describe("기본 50"),
1256
+ })
1257
+ .refine((body) => !body.regex || body.query.length <= STOREFRONT_SEARCH_REGEX_MAX_LENGTH, {
1258
+ message: `정규식은 ${STOREFRONT_SEARCH_REGEX_MAX_LENGTH}자까지입니다`,
1259
+ path: ["query"],
1260
+ }),
1261
+ );
1228
1262
  export type SearchStorefrontFilesRequest = z.infer<typeof searchStorefrontFilesRequestSchema>;
1229
1263
 
1230
1264
  export const storefrontFileSearchResultSchema = z.object({
@@ -1248,9 +1282,11 @@ export type StorefrontFileSearchResult = z.infer<typeof storefrontFileSearchResu
1248
1282
  * `GET /v1/storefront/site/checks/{checkId}`로 조회한다(권장 2~3초 간격). 사이트당 끝나지 않은 검사는 하나다 — 같은 종류가 돌고
1249
1283
  * 있으면 그 검사를 다시 돌려주고, 다른 종류면 409 `CHECK_RUNNING`(`error.details.checkId`)이다. 출력은 꼬리만 준다.
1250
1284
  */
1251
- export const runStorefrontCheckRequestSchema = z.object({
1252
- check: z.enum(["typecheck", "lint", "test"]),
1253
- });
1285
+ export const runStorefrontCheckRequestSchema = strictRequest(
1286
+ z.object({
1287
+ check: z.enum(["typecheck", "lint", "test"]),
1288
+ }),
1289
+ );
1254
1290
  export type RunStorefrontCheckRequest = z.infer<typeof runStorefrontCheckRequestSchema>;
1255
1291
 
1256
1292
  export const storefrontCheckStatusSchema = z
@@ -1456,16 +1492,18 @@ export const storefrontBuildListQuerySchema = z.object({
1456
1492
  * `POST /v1/storefront/site/versions` — 지금 작업 트리를 고정해 버전을 만든다(빌드 시작). 사이트당 끝나지 않은 빌드는
1457
1493
  * 하나다(409 `STOREFRONT_BUILD_IN_PROGRESS`). 편집 환경이 없어도 된다.
1458
1494
  */
1459
- export const createStorefrontVersionRequestSchema = z.object({
1460
- message: z.string().max(200).optional().describe("버전 메모 (에이전트는 바꾼 내용 요약)"),
1461
- expectedSourceRef: z
1462
- .string()
1463
- .max(200)
1464
- .optional()
1465
- .describe(
1466
- "고정할 작업 트리 (`PUT /v1/storefront/site/source`의 `sourceRef`). 주면 지금 작업 트리가 이 값일 때만 버전을 만든다(다르면 409 `SOURCE_CHANGED`)",
1467
- ),
1468
- });
1495
+ export const createStorefrontVersionRequestSchema = strictRequest(
1496
+ z.object({
1497
+ message: z.string().max(200).optional().describe("버전 메모 (에이전트는 바꾼 내용 요약)"),
1498
+ expectedSourceRef: z
1499
+ .string()
1500
+ .max(200)
1501
+ .optional()
1502
+ .describe(
1503
+ "고정할 작업 트리 (`PUT /v1/storefront/site/source`의 `sourceRef`). 주면 지금 작업 트리가 이 값일 때만 버전을 만든다(다르면 409 `SOURCE_CHANGED`)",
1504
+ ),
1505
+ }),
1506
+ );
1469
1507
  export type CreateStorefrontVersionRequest = z.infer<typeof createStorefrontVersionRequestSchema>;
1470
1508
 
1471
1509
  /** 버전 만들기 응답 (201) — 버전은 `BUILDING`, 빌드는 `QUEUED`로 시작한다. 결과는 `GET builds/{buildId}`로 본다 */
@@ -1480,9 +1518,11 @@ export type StorefrontVersionCreated = z.infer<typeof storefrontVersionCreatedSc
1480
1518
  * 받는다(PAT·에이전트 불가). 5분 안에 열면 그 브라우저에 미리보기 호스트 전용 쿠키(12시간)가 생긴다. 편집 환경이
1481
1519
  * 없으면 409 `EDIT_SESSION_REQUIRED`다(편집 환경이 `STARTING`이면 링크는 주고, 미리보기 화면이 기동 대기를 보여 준다).
1482
1520
  */
1483
- export const createStorefrontEditPreviewLinkRequestSchema = z.object({
1484
- path: z.string().max(512).optional().describe("열 경로 (`/`로 시작). 생략하면 `/`"),
1485
- });
1521
+ export const createStorefrontEditPreviewLinkRequestSchema = strictRequest(
1522
+ z.object({
1523
+ path: z.string().max(512).optional().describe("열 경로 (`/`로 시작). 생략하면 `/`"),
1524
+ }),
1525
+ );
1486
1526
  export type CreateStorefrontEditPreviewLinkRequest = z.infer<
1487
1527
  typeof createStorefrontEditPreviewLinkRequestSchema
1488
1528
  >;
@@ -1549,17 +1589,19 @@ export const storefrontSourceArchivePartSchema = z.object({
1549
1589
  export type StorefrontSourceArchivePart = z.infer<typeof storefrontSourceArchivePartSchema>;
1550
1590
 
1551
1591
  /** `POST /v1/storefront/site/source/blobs` — 파일 내용 올리기(해시 키, 다시 올려도 안전하다). 작업 트리는 바뀌지 않는다 */
1552
- export const uploadStorefrontSourceBlobsRequestSchema = z.object({
1553
- blobs: z
1554
- .array(
1555
- z.object({
1556
- hash: storefrontFileHashSchema,
1557
- content: z.string().describe("파일 내용 (base64, 1MiB까지)"),
1558
- }),
1559
- )
1560
- .min(1)
1561
- .max(STOREFRONT_SOURCE_BLOB_BATCH_MAX),
1562
- });
1592
+ export const uploadStorefrontSourceBlobsRequestSchema = strictRequest(
1593
+ z.object({
1594
+ blobs: z
1595
+ .array(
1596
+ z.object({
1597
+ hash: storefrontFileHashSchema,
1598
+ content: z.string().describe("파일 내용 (base64, 1MiB까지)"),
1599
+ }),
1600
+ )
1601
+ .min(1)
1602
+ .max(STOREFRONT_SOURCE_BLOB_BATCH_MAX),
1603
+ }),
1604
+ );
1563
1605
  export type UploadStorefrontSourceBlobsRequest = z.infer<
1564
1606
  typeof uploadStorefrontSourceBlobsRequestSchema
1565
1607
  >;
@@ -1574,42 +1616,46 @@ export type StorefrontSourceBlobsResult = z.infer<typeof storefrontSourceBlobsRe
1574
1616
  * `PUT /v1/storefront/site/source` — 작업 트리 전체를 이 목록으로 바꾼다(없는 경로는 지운다). 전부 아니면 전무다.
1575
1617
  * `package.json`·`pnpm-lock.yaml`도 바꿀 수 있고 의존성은 허용 목록 안이어야 한다. `.env*`·작업 트리 밖 경로는 받지 않는다.
1576
1618
  */
1577
- export const replaceStorefrontSourceRequestSchema = z.object({
1578
- expectedBaseRef: storefrontSourceRefSchema
1579
- .optional()
1580
- .describe(
1581
- "내려받은 작업 트리. 그 뒤 작업 트리가 바뀌었으면 이 트리를 기준으로 3-way 병합하고, 같은 곳을 고쳤으면 409 `SOURCE_CHANGED`다. 생략하면 조건 없이 덮어쓴다",
1582
- ),
1583
- files: z
1584
- .array(
1585
- z.object({
1586
- path: storefrontFilePathSchema,
1587
- hash: storefrontFileHashSchema,
1588
- size: z.number().int().min(0).max(STOREFRONT_FILE_MAX_BYTES),
1619
+ export const replaceStorefrontSourceRequestSchema = strictRequest(
1620
+ z.object({
1621
+ expectedBaseRef: storefrontSourceRefSchema
1622
+ .optional()
1623
+ .describe(
1624
+ "내려받은 작업 트리. 그 뒤 작업 트리가 바뀌었으면 이 트리를 기준으로 3-way 병합하고, 같은 곳을 고쳤으면 409 `SOURCE_CHANGED`다. 생략하면 조건 없이 덮어쓴다",
1625
+ ),
1626
+ files: z
1627
+ .array(
1628
+ z.object({
1629
+ path: storefrontFilePathSchema,
1630
+ hash: storefrontFileHashSchema,
1631
+ size: z.number().int().min(0).max(STOREFRONT_FILE_MAX_BYTES),
1632
+ }),
1633
+ )
1634
+ .min(1)
1635
+ .max(STOREFRONT_WORKSPACE_MAX_FILES)
1636
+ .refine((files) => new Set(files.map((file) => file.path)).size === files.length, {
1637
+ message: "같은 경로를 두 번 실을 수 없습니다",
1589
1638
  }),
1590
- )
1591
- .min(1)
1592
- .max(STOREFRONT_WORKSPACE_MAX_FILES)
1593
- .refine((files) => new Set(files.map((file) => file.path)).size === files.length, {
1594
- message: "같은 경로를 두 번 실을 수 없습니다",
1595
- }),
1596
- endEditSession: z
1597
- .boolean()
1598
- .optional()
1599
- .describe("열린 편집 환경을 끄고 진행한다 (기본 false — 열려 있으면 409 `EDIT_SESSION_OPEN`)"),
1600
- packageJson: z
1601
- .string()
1602
- .max(STOREFRONT_FILE_MAX_BYTES)
1603
- .optional()
1604
- .describe(
1605
- "`files`에 `package.json`이 있으면 그 내용(utf8). 서버가 의존성을 판정한다. 해시가 목록과 다르면 400 `DEPENDENCY_FILES_MISMATCH`",
1606
- ),
1607
- lockfile: z
1608
- .string()
1609
- .max(STOREFRONT_FILE_MAX_BYTES)
1610
- .optional()
1611
- .describe("`files`에 `pnpm-lock.yaml`이 있으면 그 내용(utf8)"),
1612
- });
1639
+ endEditSession: z
1640
+ .boolean()
1641
+ .optional()
1642
+ .describe(
1643
+ "열린 편집 환경을 끄고 진행한다 (기본 false — 열려 있으면 409 `EDIT_SESSION_OPEN`)",
1644
+ ),
1645
+ packageJson: z
1646
+ .string()
1647
+ .max(STOREFRONT_FILE_MAX_BYTES)
1648
+ .optional()
1649
+ .describe(
1650
+ "`files`에 `package.json`이 있으면 그 내용(utf8). 서버가 의존성을 판정한다. 해시가 목록과 다르면 400 `DEPENDENCY_FILES_MISMATCH`",
1651
+ ),
1652
+ lockfile: z
1653
+ .string()
1654
+ .max(STOREFRONT_FILE_MAX_BYTES)
1655
+ .optional()
1656
+ .describe("`files`에 `pnpm-lock.yaml`이 있으면 그 내용(utf8)"),
1657
+ }),
1658
+ );
1613
1659
  export type ReplaceStorefrontSourceRequest = z.infer<typeof replaceStorefrontSourceRequestSchema>;
1614
1660
 
1615
1661
  export const storefrontSourceReplacedSchema = z.object({
@@ -1708,21 +1754,23 @@ export const storefrontGitRepositorySchema = z.object({
1708
1754
  export type StorefrontGitRepository = z.infer<typeof storefrontGitRepositorySchema>;
1709
1755
 
1710
1756
  /** `POST /v1/storefront/site/git/installations` — GitHub App 설치 화면에서 돌아온 값(`installation_id`·`code`) */
1711
- export const linkStorefrontGitInstallationRequestSchema = z.object({
1712
- installationId: z.number().int().positive(),
1713
- code: z
1714
- .string()
1715
- .min(1)
1716
- .max(200)
1717
- .describe("설치 화면이 돌려준 GitHub 사용자 인증 코드(한 번만 쓴다)"),
1718
- state: z
1719
- .string()
1720
- .min(1)
1721
- .max(200)
1722
- .describe(
1723
- "설치 화면으로 보내기 전에 받은 연결 state(`POST …/git/installations/state`, 한 번만 쓴다)",
1724
- ),
1725
- });
1757
+ export const linkStorefrontGitInstallationRequestSchema = strictRequest(
1758
+ z.object({
1759
+ installationId: z.number().int().positive(),
1760
+ code: z
1761
+ .string()
1762
+ .min(1)
1763
+ .max(200)
1764
+ .describe("설치 화면이 돌려준 GitHub 사용자 인증 코드(한 번만 쓴다)"),
1765
+ state: z
1766
+ .string()
1767
+ .min(1)
1768
+ .max(200)
1769
+ .describe(
1770
+ "설치 화면으로 보내기 전에 받은 연결 state(`POST …/git/installations/state`, 한 번만 쓴다)",
1771
+ ),
1772
+ }),
1773
+ );
1726
1774
 
1727
1775
  /** `POST /v1/storefront/site/git/installations/state` — GitHub 설치 화면에 싣는 1회용 state(이 상점·사용자, 10분) */
1728
1776
  export const storefrontGitConnectStateSchema = z.object({
@@ -1735,30 +1783,34 @@ export type LinkStorefrontGitInstallationRequest = z.infer<
1735
1783
  >;
1736
1784
 
1737
1785
  /** `POST /v1/storefront/site/git/connect` */
1738
- export const connectStorefrontGitRequestSchema = z.object({
1739
- installationId: z.number().int().positive(),
1740
- repoId: z.number().int().positive(),
1741
- productionBranch: z
1742
- .string()
1743
- .min(1)
1744
- .max(200)
1745
- .regex(/^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/)
1746
- .optional()
1747
- .describe("운영 배포 브랜치(생략하면 저장소 기본 브랜치)"),
1748
- rootDirectory: z
1749
- .string()
1750
- .max(200)
1751
- .regex(/^$|^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/)
1752
- .refine((v) => !v.split("/").some((seg) => seg === "." || seg === ".."), "상대 경로만")
1753
- .optional()
1754
- .describe("저장소 안 사이트 루트(생략하면 저장소 루트)"),
1755
- });
1786
+ export const connectStorefrontGitRequestSchema = strictRequest(
1787
+ z.object({
1788
+ installationId: z.number().int().positive(),
1789
+ repoId: z.number().int().positive(),
1790
+ productionBranch: z
1791
+ .string()
1792
+ .min(1)
1793
+ .max(200)
1794
+ .regex(/^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/)
1795
+ .optional()
1796
+ .describe("운영 배포 브랜치(생략하면 저장소 기본 브랜치)"),
1797
+ rootDirectory: z
1798
+ .string()
1799
+ .max(200)
1800
+ .regex(/^$|^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/)
1801
+ .refine((v) => !v.split("/").some((seg) => seg === "." || seg === ".."), "상대 경로만")
1802
+ .optional()
1803
+ .describe("저장소 안 사이트 루트(생략하면 저장소 루트)"),
1804
+ }),
1805
+ );
1756
1806
  export type ConnectStorefrontGitRequest = z.infer<typeof connectStorefrontGitRequestSchema>;
1757
1807
 
1758
1808
  /** `PUT /v1/storefront/site/deploy-approval` — 승인 없이 배포(#138) */
1759
- export const setStorefrontDeployApprovalRequestSchema = z.object({
1760
- deployWithoutApproval: z.boolean(),
1761
- });
1809
+ export const setStorefrontDeployApprovalRequestSchema = strictRequest(
1810
+ z.object({
1811
+ deployWithoutApproval: z.boolean(),
1812
+ }),
1813
+ );
1762
1814
  export type SetStorefrontDeployApprovalRequest = z.infer<
1763
1815
  typeof setStorefrontDeployApprovalRequestSchema
1764
1816
  >;