@sayren/store-sdk 0.37.2 → 0.39.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.
@@ -357,12 +357,22 @@ export const saveFlowDraftRequestSchema = z.object({
357
357
  });
358
358
  export type SaveFlowDraftRequest = z.infer<typeof saveFlowDraftRequestSchema>;
359
359
 
360
- const flowIssueSchema = z.object({
360
+ export const flowIssueSchema = z.object({
361
361
  code: z.string(),
362
362
  message: z.string(),
363
363
  nodeId: z.string().optional(),
364
364
  edgeId: z.string().optional(),
365
+ pointer: z
366
+ .string()
367
+ .optional()
368
+ .describe(
369
+ "위치(JSON Pointer) — 그래프 문서 안(`/nodes/3/config/buttons/0/key`). 조작 오류(`OP_*`)는 그 조작 안 위치",
370
+ ),
371
+ opIndex: z.number().int().optional().describe("조작 묶음에서 몇 번째 조작(0부터)에서 났는가"),
372
+ hint: z.string().optional().describe("고치는 법·후보(가능한 값·비슷한 이름)"),
373
+ expected: z.unknown().optional().describe("그 위치의 설정 형식(JSON Schema 조각)"),
365
374
  });
375
+ export type FlowIssue = z.infer<typeof flowIssueSchema>;
366
376
 
367
377
  export const commerceFlowCheckSchema = z.object({
368
378
  publishable: z.boolean().describe("게시를 막는 오류(실행 불가·공개 계약·한도)가 없다"),
@@ -371,6 +381,130 @@ export const commerceFlowCheckSchema = z.object({
371
381
  });
372
382
  export type CommerceFlowCheck = z.infer<typeof commerceFlowCheckSchema>;
373
383
 
384
+ // ── 흐름 조작 묶음(#120) — 노드·연결선 단위로 고친다. `POST /commerce-flows/{flowId}/draft/operations`, `If-Match` 필수 ─────────────
385
+
386
+ /** JSON Pointer(`/`로 시작) */
387
+ const flowPointerSchema = z.string().regex(/^\//, "JSON Pointer(/로 시작)여야 합니다");
388
+
389
+ const flowPointerSetSchema = z
390
+ .record(flowPointerSchema, z.unknown())
391
+ .describe(
392
+ "JSON Pointer → 값. 그 위치의 값을 통째로 바꾼다(합치지 않는다). 배열(버튼 등)은 배열 전체를 넣는다",
393
+ );
394
+ const flowPointerUnsetSchema = z.array(flowPointerSchema).describe("지울 위치(JSON Pointer) 목록");
395
+
396
+ /** 흐름 노드 id — 영문으로 시작, 영문·숫자·_·- */
397
+ export const flowNodeIdSchema = z
398
+ .string()
399
+ .regex(/^[A-Za-z][A-Za-z0-9_-]*$/, "노드 id는 영문으로 시작하고 영문·숫자·_·-만 씁니다");
400
+
401
+ /**
402
+ * 조작 하나(#120) — 원천은 여기 하나다(api 본문 검증과 flow-spec `applyFlowOps`가 같은 스키마를 쓴다). 모르는 키는 400이다
403
+ * (지우고 성공하면 잘못 쓴 조작이 아무 일도 하지 않은 채 통과한다)
404
+ */
405
+ export const flowDraftOperationSchema = z.discriminatedUnion("op", [
406
+ z
407
+ .strictObject({
408
+ op: z.literal("addNode"),
409
+ id: flowNodeIdSchema.describe("새 노드 id(영문으로 시작, 영문·숫자·_·-)"),
410
+ type: z.string().min(1).describe("노드 종류(`GET /flow-node-types/{type}`)"),
411
+ config: z.record(z.string(), z.unknown()).optional().describe("노드 설정"),
412
+ position: z
413
+ .strictObject({ x: z.number(), y: z.number() })
414
+ .optional()
415
+ .describe("없으면 서버가 앞 노드 오른쪽에 둔다"),
416
+ })
417
+ .describe("노드 더하기 — version·outputs는 서버가 채운다"),
418
+ z
419
+ .strictObject({
420
+ op: z.literal("updateNode"),
421
+ id: z.string().min(1),
422
+ set: flowPointerSetSchema.optional().describe("`/config/…`·`/position` 아래만"),
423
+ unset: flowPointerUnsetSchema.optional(),
424
+ })
425
+ .describe("노드 고치기"),
426
+ z
427
+ .strictObject({ op: z.literal("removeNode"), id: z.string().min(1) })
428
+ .describe("노드 지우기 — 드나드는 연결선도 지운다. 시작 노드는 400(먼저 setStart)"),
429
+ z
430
+ .strictObject({
431
+ op: z.literal("connect"),
432
+ id: z
433
+ .string()
434
+ .min(1)
435
+ .optional()
436
+ .describe(
437
+ "연결선 id. 주면 같은 묶음의 updateEdge·disconnect가 가리킬 수 있다. 없으면 서버가 만든다",
438
+ ),
439
+ from: z.string().min(1).describe("`노드` 또는 `노드:출력`"),
440
+ to: z.string().min(1),
441
+ on: z
442
+ .record(z.string(), z.unknown())
443
+ .optional()
444
+ .describe("조건(트리거). 출력 연결선·즉시 노드에서는 두지 않는다"),
445
+ guard: z.string().optional().describe("조건식(CEL)"),
446
+ do: z.array(z.record(z.string(), z.unknown())).optional().describe("지날 때 실행할 블록"),
447
+ })
448
+ .describe("연결선 잇기"),
449
+ z
450
+ .strictObject({
451
+ op: z.literal("updateEdge"),
452
+ id: z.string().min(1),
453
+ set: flowPointerSetSchema.optional().describe("`/from`·`/to`·`/on`·`/guard`·`/do` 아래만"),
454
+ unset: flowPointerUnsetSchema.optional(),
455
+ })
456
+ .describe("연결선 고치기"),
457
+ z.strictObject({ op: z.literal("disconnect"), id: z.string().min(1) }).describe("연결선 지우기"),
458
+ z.strictObject({ op: z.literal("setStart"), id: z.string().min(1) }).describe("시작 노드 바꾸기"),
459
+ z
460
+ .strictObject({
461
+ op: z.literal("setDefinition"),
462
+ set: z
463
+ .strictObject({
464
+ progression: z.enum(["ITEM", "ORDER_GROUP"]).optional(),
465
+ groupClaim: z.enum(["CONTINUE", "PAUSE"]).optional(),
466
+ fulfillmentMethods: z
467
+ .array(z.enum(["CARRIER", "DIRECT", "PICKUP", "PROVIDED"]))
468
+ .min(1)
469
+ .optional(),
470
+ })
471
+ .optional(),
472
+ unset: z.array(z.enum(["progression", "groupClaim", "fulfillmentMethods"])).optional(),
473
+ })
474
+ .describe("흐름 설정(진행 단위·묶음 클레임·이행 방식) 바꾸기"),
475
+ ]);
476
+ export type FlowDraftOperation = z.infer<typeof flowDraftOperationSchema>;
477
+
478
+ export const applyFlowOperationsRequestSchema = z.object({
479
+ operations: z
480
+ .array(flowDraftOperationSchema)
481
+ .min(1)
482
+ .describe(
483
+ "차례대로 적용한다. 묶음은 원자적이다 — 하나라도 형식이 틀리면 아무것도 바꾸지 않는다(400)",
484
+ ),
485
+ });
486
+ export type ApplyFlowOperationsRequest = z.infer<typeof applyFlowOperationsRequestSchema>;
487
+
488
+ export const flowOperationsResultSchema = z.object({
489
+ revision: z.number().int().describe("저장 뒤 초안 revision — 다음 조작의 If-Match"),
490
+ changed: z.object({
491
+ nodes: z.array(z.string()).describe("더하거나 바꾼 노드"),
492
+ edges: z.array(z.string()).describe("더하거나 바꾼 연결선"),
493
+ removedNodes: z.array(z.string()),
494
+ removedEdges: z.array(z.string()).describe("지운 연결선(노드와 함께 지워진 것 포함)"),
495
+ removedActions: z
496
+ .array(z.string())
497
+ .describe(
498
+ "그래프에서 사라진 셀러·구매자 행동(`seller.action:CONFIRM_ORDER`). 셀러에게 확인받는다",
499
+ ),
500
+ }),
501
+ issues: z
502
+ .array(flowIssueSchema)
503
+ .describe("게시를 막는 문제. 저장은 됐다(초안은 미완성일 수 있다)"),
504
+ warnings: z.array(flowIssueSchema).describe("업무 논리 경고 — 게시를 막지 않는다"),
505
+ });
506
+ export type FlowOperationsResult = z.infer<typeof flowOperationsResultSchema>;
507
+
374
508
  // ── 흐름 해석(이슈 #115) — 상품의 흐름 → 카테고리(맨 아래)의 흐름 → 기본 흐름. 할당 규칙은 없앴다 ─────────────────
375
509
 
376
510
  export const resolvedFlowSchema = z.object({
@@ -527,6 +661,75 @@ export const replayFlowResultSchema = z.object({
527
661
  });
528
662
  export type ReplayFlowResult = z.infer<typeof replayFlowResultSchema>;
529
663
 
664
+ // ── 요소 단위 읽기(#120) — 흐름 전체 대신 노드·연결선을 따로 읽는다. 응답 크기가 흐름이 아니라 요소에 비례한다 ─────────────
665
+
666
+ const flowDraftRevisionSchema = z
667
+ .number()
668
+ .int()
669
+ .describe(
670
+ "초안 revision — 조작의 If-Match 값. 초안이 없으면 게시 버전을 0으로 보여 준다(첫 쓰기가 초안을 연다)",
671
+ );
672
+
673
+ export const flowDraftNodesSchema = z.object({
674
+ revision: flowDraftRevisionSchema,
675
+ start: z.string().describe("시작 노드 id"),
676
+ nodes: z.array(
677
+ z.object({
678
+ id: z.string(),
679
+ type: z.string(),
680
+ label: z.string().nullable().describe("셀러용 이름(`config.label.seller`). 없으면 null"),
681
+ kind: z
682
+ .string()
683
+ .nullable()
684
+ .describe(
685
+ "start · state · wait · end · transient · call · fork · join (늘어날 수 있다). 모르는 종류면 null",
686
+ ),
687
+ outputs: z
688
+ .array(z.string())
689
+ .describe("출력 이름 — 연결선은 `노드:출력`으로 가리킨다. 없으면 `노드`로 가리킨다"),
690
+ }),
691
+ ),
692
+ });
693
+ export type FlowDraftNodes = z.infer<typeof flowDraftNodesSchema>;
694
+
695
+ export const flowDraftNodeSchema = z.object({
696
+ revision: flowDraftRevisionSchema,
697
+ node: z
698
+ .object({
699
+ id: z.string(),
700
+ type: z.string(),
701
+ version: z.number().int(),
702
+ config: z.record(z.string(), z.unknown()),
703
+ outputs: z.array(z.string()).optional(),
704
+ position: z.object({ x: z.number(), y: z.number() }).optional(),
705
+ })
706
+ .describe("노드 하나(설정 전체)"),
707
+ kind: z.string().nullable(),
708
+ outputs: z.array(z.string()).describe("설정에서 계산한 출력"),
709
+ start: z.boolean().describe("시작 노드인가"),
710
+ incoming: z.array(z.string()).describe("들어오는 연결선 id"),
711
+ outgoing: z.array(z.string()).describe("나가는 연결선 id"),
712
+ });
713
+ export type FlowDraftNode = z.infer<typeof flowDraftNodeSchema>;
714
+
715
+ export const flowDraftEdgesSchema = z.object({
716
+ revision: flowDraftRevisionSchema,
717
+ edges: z.array(
718
+ z.object({
719
+ id: z.string(),
720
+ from: z.string().describe("`노드` 또는 `노드:출력`"),
721
+ to: z.string(),
722
+ on: z
723
+ .record(z.string(), z.unknown())
724
+ .optional()
725
+ .describe("조건 — `type`: seller.action · buyer.action · event · timer · immediate"),
726
+ guard: z.string().optional().describe("조건식(CEL)"),
727
+ do: z.array(z.record(z.string(), z.unknown())).optional().describe("지날 때 실행할 블록"),
728
+ }),
729
+ ),
730
+ });
731
+ export type FlowDraftEdges = z.infer<typeof flowDraftEdgesSchema>;
732
+
530
733
  /** 노드·블록 종류 목록(#115) — 흐름을 API·AI로 짤 때 쓸 수 있는 종류와 설정 형식(JSON Schema) */
531
734
  export const flowNodeTypesSchema = z.object({
532
735
  nodes: z.array(
@@ -571,3 +774,99 @@ export const flowNodeTypesSchema = z.object({
571
774
  .describe("노드 권한 표에 켤 수 있는 플랫폼 행동(#115) — 편집기는 이 목록으로 행을 그린다"),
572
775
  });
573
776
  export type FlowNodeTypes = z.infer<typeof flowNodeTypesSchema>;
777
+
778
+ // ── 작성 안내(#120) — 흐름을 노드·연결선 단위로 짤 때 읽는다. 종류 하나의 설정 형식은 따로 읽는다 ─────────────
779
+
780
+ export const flowAuthoringGuideSchema = z.object({
781
+ nodes: z
782
+ .array(
783
+ z.object({
784
+ type: z.string(),
785
+ name: z.string().describe("표시 이름"),
786
+ kind: z
787
+ .string()
788
+ .describe("start · state · wait · end · transient · call · fork · join (늘어날 수 있다)"),
789
+ outputs: z.array(z.string()).nullable().describe("고정 출력. 설정에서 나오면 null"),
790
+ description: z.string(),
791
+ }),
792
+ )
793
+ .describe("노드 종류 — 설정 형식은 `GET /flow-node-types/{type}`"),
794
+ blocks: z.array(
795
+ z.object({
796
+ type: z.string(),
797
+ effect: z.string().describe("local · afterCommit · external"),
798
+ description: z.string(),
799
+ }),
800
+ ),
801
+ triggers: z
802
+ .array(z.object({ type: z.string(), format: z.string(), description: z.string() }))
803
+ .describe("연결선 조건(on) 형식"),
804
+ context: z
805
+ .record(z.string(), z.string())
806
+ .describe("식·안내문 틀이 읽을 수 있는 값 — 점 경로 → 타입(int·string·bool·timestamp·dyn …)"),
807
+ functions: z
808
+ .array(z.object({ name: z.string(), signature: z.string(), doc: z.string() }))
809
+ .describe("식 함수"),
810
+ templateFilters: z.array(z.string()).describe("안내문 틀 필터(`{{ 경로 | 필터 }}`)"),
811
+ rules: z.array(z.string()).describe("그래프 규칙"),
812
+ expressionExamples: z.array(z.object({ expr: z.string(), description: z.string() })),
813
+ operationExample: z
814
+ .array(z.record(z.string(), z.unknown()))
815
+ .describe("조작 묶음 예시(`POST /commerce-flows/{flowId}/draft/operations`의 operations)"),
816
+ });
817
+ export type FlowAuthoringGuide = z.infer<typeof flowAuthoringGuideSchema>;
818
+
819
+ export const flowNodeTypeDetailSchema = z.object({
820
+ type: z.string(),
821
+ category: z.string().describe("node · block"),
822
+ version: z.number().int().describe("최신 버전 — 조작이 노드를 더할 때 이 버전을 쓴다"),
823
+ kind: z.string().nullable().describe("노드 성격(블록은 null)"),
824
+ effect: z.string().nullable().describe("블록 실행 성격(노드는 null)"),
825
+ outputs: z.array(z.string()).nullable().describe("고정 출력. 설정에서 나오면 null"),
826
+ name: z.string(),
827
+ description: z.string(),
828
+ configSchema: z
829
+ .record(z.string(), z.unknown())
830
+ .nullable()
831
+ .describe(
832
+ "설정 형식(JSON Schema). 공통 설정(이름·권한·입력 칸·식 참조)은 `$defs`. 옮길 수 없으면 null",
833
+ ),
834
+ example: z.record(z.string(), z.unknown()).describe("검사를 통과하는 예시 설정"),
835
+ });
836
+ export type FlowNodeTypeDetail = z.infer<typeof flowNodeTypeDetailSchema>;
837
+
838
+ // ── 편집권(#120) — 흐름 초안은 한 번에 한 주체(사람·AI 도우미·외부 에이전트)만 고친다 ─────────────
839
+
840
+ export const flowEditSessionViewSchema = z.object({
841
+ revision: z.number().int().describe("지금 초안 revision(초안이 없으면 0)"),
842
+ session: z
843
+ .object({
844
+ holder: z.object({
845
+ kind: z
846
+ .string()
847
+ .describe("USER 사람 · AI_ASSISTANT AI 도우미 · AGENT 외부 에이전트 (늘어날 수 있다)"),
848
+ name: z.string().nullable(),
849
+ }),
850
+ expiresAt: z.string().describe("이때까지 연장하지 않으면 풀린다(ISO 8601)"),
851
+ mine: z.boolean().describe("요청한 주체의 편집권인가"),
852
+ })
853
+ .nullable()
854
+ .describe("지금 편집권. 비어 있으면 null — 누구든 첫 쓰기가 잡는다"),
855
+ channel: z
856
+ .object({ streamId: z.string(), streamUrl: z.string(), streamToken: z.string() })
857
+ .nullable()
858
+ .describe(
859
+ "흐름 채널 구독(SSE, `Authorization: Bearer streamToken`) — session·ops·saved·released 이벤트. 중계가 없는 환경이면 null",
860
+ ),
861
+ });
862
+ export type FlowEditSessionView = z.infer<typeof flowEditSessionViewSchema>;
863
+
864
+ export const claimFlowEditSessionRequestSchema = z.object({
865
+ takeover: z
866
+ .boolean()
867
+ .optional()
868
+ .describe(
869
+ "다른 주체가 잡고 있어도 넘겨받는다. 앞 주체의 저장하지 않은 수정은 사라지고, AI 도우미면 그 실행이 멈춘다",
870
+ ),
871
+ });
872
+ export type ClaimFlowEditSessionRequest = z.infer<typeof claimFlowEditSessionRequestSchema>;
@@ -60,12 +60,6 @@ export const orderItemFulfillmentSchema = z.object({
60
60
  ),
61
61
  fulfilledAt: z.string().describe("제공 시각 (ISO 8601)"),
62
62
  note: z.string().nullable().describe("셀러가 제공 처리 때 남긴 이용 안내. 없으면 null"),
63
- mode: z
64
- .string()
65
- .optional()
66
- .describe(
67
- "**Deprecated** — 항상 `MANUAL`이다. 자동 제공을 없앴고(이슈 #48) store-sdk 0.19 이하 호환으로만 싣는다. 게시된 연동이 store-sdk 0.20 이상으로 넘어간 뒤 다음 릴리스에서 뺀다",
68
- ),
69
63
  });
70
64
 
71
65
  export type OrderItemFulfillment = z.infer<typeof orderItemFulfillmentSchema>;
@@ -180,7 +180,7 @@ export type RemoteArea = z.infer<typeof remoteAreaSchema>;
180
180
  * 도로명주소·건물관리번호 단위 판정은 후속 과제다.
181
181
  *
182
182
  * 범위는 **닫힌 구간**(`from` ≤ zip ≤ `to`)이고 겹치지 않는다. 앞에서부터 처음 맞는 항목을 쓴다.
183
- * 순서는 상점 플랫폼 「추가 배송비 지역」 표의 행 순서이기도 하다(`remoteAreaGroups`). 제주를 먼저 두고
183
+ * 순서는 docs `guides/shipping-fees.mdx` 「제주·도서산간 추가 배송비」 표의 행 순서이기도 하다. 제주를 먼저 두고
184
184
  * 도서산간은 우편번호 오름차순으로 둔다.
185
185
  */
186
186
  export const REMOTE_AREA_ZIP_RANGES: ReadonlyArray<{
@@ -285,6 +285,13 @@ export const storefrontVersionSchema = z.object({
285
285
  .nullable()
286
286
  .optional()
287
287
  .describe("이 버전 소스를 담은 저장소 커밋(Git 원천 사이트). 저장소에 없는 버전은 null"),
288
+ excludedPaths: z
289
+ .array(z.string())
290
+ .nullable()
291
+ .optional()
292
+ .describe(
293
+ "저장소 push로 만든 버전에서 경로 규칙 밖이라 빼고 배포한 파일(`.env`·서버 코드 등, 앞 20개). README·`.github/` 같은 저장소 관리 파일은 싣지 않는다. 없으면 null",
294
+ ),
288
295
  mainChanged: z
289
296
  .boolean()
290
297
  .optional()
@@ -939,7 +946,12 @@ export const storefrontEditSessionStatusSchema = z
939
946
  .describe("`STARTING` 띄우는 중 · `RUNNING` 사용 중");
940
947
 
941
948
  /** 편집 환경을 띄우는 단계 — 상점 플랫폼 진행 표시용이다. 값은 늘어날 수 있다 */
942
- export const STOREFRONT_EDIT_START_STAGES = ["CONTAINER", "RESTORE", "DEV_SERVER"] as const;
949
+ export const STOREFRONT_EDIT_START_STAGES = [
950
+ "CONTAINER",
951
+ "RESTORE",
952
+ "INSTALL",
953
+ "DEV_SERVER",
954
+ ] as const;
943
955
 
944
956
  export const storefrontEditSessionSchema = z.object({
945
957
  sessionId: z.string(),
@@ -948,7 +960,7 @@ export const storefrontEditSessionSchema = z.object({
948
960
  .string()
949
961
  .nullable()
950
962
  .describe(
951
- "`STARTING`일 때 지금 단계 (`CONTAINER` 컨테이너 기동 · `RESTORE` 작업 트리 복원 · `DEV_SERVER` 개발 서버 기동). 늘어날 수 있다. `RUNNING`이면 null",
963
+ "`STARTING`일 때 지금 단계 (`CONTAINER` 컨테이너 기동 · `RESTORE` 작업 트리 복원 · `INSTALL` 의존성 설치(`pnpm-lock.yaml`이 템플릿과 다를 때만) · `DEV_SERVER` 개발 서버 기동). 늘어날 수 있다. `RUNNING`이면 null",
952
964
  ),
953
965
  startedAt: z.string().describe("연 시각 (ISO 8601)"),
954
966
  lastEditedAt: z
@@ -974,6 +986,39 @@ export type StorefrontEditSession = z.infer<typeof storefrontEditSessionSchema>;
974
986
  * `GET /v1/storefront/site/editing/logs?lines=` — 편집 환경 dev 서버 로그의 끝부분(2026-10-14). 컴파일 오류·HMR 실패를 바로 읽는다.
975
987
  * 편집 환경이 켜져 있지 않으면 409 `EDIT_SESSION_REQUIRED`
976
988
  */
989
+ /** `POST /v1/storefront/site/editing/git/merge` — 켜진 편집 환경 작업 트리에 운영 브랜치 최신 커밋을 합친다(#138, Git 원천 사이트) */
990
+ export const storefrontGitMergeRequestSchema = z.object({
991
+ action: z
992
+ .enum(["merge", "continue", "abort"])
993
+ .default("merge")
994
+ .describe(
995
+ "`merge` 최신 운영 브랜치를 합친다 · `continue` 충돌 표시를 고친 뒤 합치기를 끝낸다 · `abort` 합치기를 취소하고 합치기 전으로 돌린다",
996
+ ),
997
+ });
998
+ export type StorefrontGitMergeRequest = z.input<typeof storefrontGitMergeRequestSchema>;
999
+
1000
+ export const storefrontGitMergeSchema = z.object({
1001
+ status: z
1002
+ .enum(["MERGED", "UP_TO_DATE", "CONFLICT", "COMMITTED", "ABORTED"])
1003
+ .describe(
1004
+ "`MERGED` 합쳤다 · `UP_TO_DATE` 이미 최신이다 · `CONFLICT` 충돌 — `conflicts`의 파일에서 충돌 표시(`<<<<<<<`·`>>>>>>>`)를 고친 뒤 `continue` · `COMMITTED` 충돌을 고쳐 합치기를 끝냈다 · `ABORTED` 취소했다",
1005
+ ),
1006
+ conflicts: z.array(z.string()).describe("충돌한 파일(`CONFLICT`일 때). 그 밖은 빈 배열"),
1007
+ mainChanged: z
1008
+ .boolean()
1009
+ .describe(
1010
+ "합친 뒤에도 발행 전 작업이 지금 운영 브랜치 위가 아니다(충돌·취소면 true). false면 버전을 만들어 발행할 수 있다",
1011
+ ),
1012
+ // 옛 api 응답에는 없다
1013
+ dependenciesChanged: z
1014
+ .boolean()
1015
+ .optional()
1016
+ .describe(
1017
+ "합친 운영 브랜치가 `package.json`·`pnpm-lock.yaml`을 바꿔 의존성을 다시 검사하고 편집 환경을 다시 띄웠다(`STARTING`, 설치가 실패하면 편집 상태 `lastFailure`)",
1018
+ ),
1019
+ });
1020
+ export type StorefrontGitMerge = z.infer<typeof storefrontGitMergeSchema>;
1021
+
977
1022
  export const storefrontDevLogsSchema = z.object({
978
1023
  stdout: z.string(),
979
1024
  stderr: z.string(),
@@ -995,6 +1040,25 @@ export const storefrontDevLogsSchema = z.object({
995
1040
  });
996
1041
  export type StorefrontDevLogs = z.infer<typeof storefrontDevLogsSchema>;
997
1042
 
1043
+ /**
1044
+ * 편집 환경 시작 실패(#125) — 작업 트리 `pnpm-lock.yaml`이 템플릿과 다르면 편집 환경은 빌드와 같은 의존성 정책으로 설치한 뒤 개발 서버를
1045
+ * 띄운다. 설치가 실패하면 미리보기 대신 이 원인이 남는다
1046
+ */
1047
+ export const storefrontEditFailureSchema = z.object({
1048
+ code: z
1049
+ .string()
1050
+ .describe(
1051
+ "실패 코드 (`INSTALL_FAILED` 설치 실패 · `INSTALL_TOO_LARGE` 설치 크기 상한 초과 · `DEPENDENCIES_CHANGED` 검사 뒤 의존성 파일이 바뀜 · `DEPENDENCIES_UNCHECKED` 의존성 검사 없음 · `START_TIMEOUT`·`CAPACITY_EXHAUSTED`·`CONTAINER_ERROR`·`RESTART_FAILED` 등, 늘어날 수 있다)",
1052
+ ),
1053
+ message: z.string().describe("사용자용 안내"),
1054
+ logTail: z
1055
+ .string()
1056
+ .nullable()
1057
+ .describe("설치 출력 끝부분(레지스트리가 거절한 요청 포함). 설치 단계 실패가 아니면 null"),
1058
+ failedAt: z.string().nullable().describe("닫힌 시각 (ISO 8601)"),
1059
+ });
1060
+ export type StorefrontEditFailure = z.infer<typeof storefrontEditFailureSchema>;
1061
+
998
1062
  export const storefrontEditingSchema = z.object({
999
1063
  enabled: z.boolean().describe("편집을 쓸 수 있는가 (Sandbox 설정)"),
1000
1064
  session: storefrontEditSessionSchema
@@ -1013,6 +1077,13 @@ export const storefrontEditingSchema = z.object({
1013
1077
  .describe("템플릿에서 한 번이라도 고쳤는가 (사이트 `sourceMode`가 `CUSTOM`)"),
1014
1078
  lastEditedAt: z.string().nullable().describe("마지막으로 파일을 바꾼 시각"),
1015
1079
  }),
1080
+ // 옛 api 응답에는 없다 — 새 SDK가 배포 전 api와 함께 쓰여도 깨지지 않게 선택 필드다
1081
+ lastFailure: storefrontEditFailureSchema
1082
+ .nullable()
1083
+ .optional()
1084
+ .describe(
1085
+ "편집 환경이 꺼져 있고 마지막 편집 환경이 시작하지 못하고(또는 다시 띄우지 못하고) 닫혔으면 그 원인. 다시 켜면 사라진다. 없으면 null",
1086
+ ),
1016
1087
  });
1017
1088
  export type StorefrontEditing = z.infer<typeof storefrontEditingSchema>;
1018
1089
 
@@ -1606,6 +1677,23 @@ export const storefrontGitStatusSchema = z.object({
1606
1677
  .string()
1607
1678
  .nullable()
1608
1679
  .describe("셀러 저장소를 연결할 때 보관한 발행 전 작업 트리(연결 전 편집). 없으면 null"),
1680
+ lastDisconnect: z
1681
+ .object({
1682
+ reason: z
1683
+ .string()
1684
+ .describe(
1685
+ "`APP_UNINSTALLED` GitHub App 설치 삭제·정지 · `REPO_ACCESS_REMOVED` 설치에서 저장소 제외 · `STORE_TRANSFERRED` 상점 양도",
1686
+ ),
1687
+ repository: z
1688
+ .string()
1689
+ .nullable()
1690
+ .describe("연결이 끊긴 셀러 저장소(`owner/name`). 상점 양도면 이전 소유자의 저장소라 null"),
1691
+ at: z.string().describe("끊긴 시각 (ISO 8601)"),
1692
+ })
1693
+ .nullish()
1694
+ .describe(
1695
+ "셀러 저장소 연결이 셀러의 해제 없이 끊겨 sayren 저장소로 돌아왔으면 그 까닭. 다시 연결하면 null",
1696
+ ),
1609
1697
  });
1610
1698
  export type StorefrontGitStatus = z.infer<typeof storefrontGitStatusSchema>;
1611
1699
 
@@ -128,12 +128,6 @@ export const orderFulfilledWebhookEventDataSchema = z.object({
128
128
  fulfillmentSnapshot: orderItemFulfillmentSnapshotResponseSchema.describe(
129
129
  "제공한 주문상품의 주문 시점 이행 스냅샷(주문상품 응답의 `fulfillmentSnapshot`과 같다, 이슈 #57)",
130
130
  ),
131
- fulfillment: z
132
- .object({ mode: z.string() })
133
- .optional()
134
- .describe(
135
- '**Deprecated** — 항상 `{ mode: "MANUAL" }`이다. 자동 제공을 없앴고(이슈 #48) store-sdk 0.19 이하 호환으로만 싣는다. 게시된 연동이 store-sdk 0.20 이상으로 넘어간 뒤 다음 릴리스에서 뺀다',
136
- ),
137
131
  fulfilledAt: z.string().describe("제공 시각 (ISO 8601). 주문상품의 `deliveredAt`과 같다"),
138
132
  currency: z
139
133
  .string()