@sayren/store-sdk 0.29.7 → 0.30.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.d.mts +1803 -279
- package/dist/index.mjs +552 -30
- package/package.json +1 -1
- package/src/client.ts +143 -0
- package/src/index.ts +2 -0
- package/src/schemas/category.ts +14 -0
- package/src/schemas/commerce-flow.ts +230 -0
- package/src/schemas/fulfillment.ts +177 -9
- package/src/schemas/misc.ts +2 -0
- package/src/schemas/order.ts +4 -0
- package/src/schemas/product.ts +92 -0
- package/src/schemas/saved-option-set.ts +8 -0
- package/src/schemas/scope.ts +5 -0
- package/src/schemas/shipping-policy.ts +4 -1
- package/src/schemas/stay.ts +90 -0
- package/src/schemas/upload.ts +6 -1
- package/src/schemas/webhook-event.ts +57 -0
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -69,6 +69,23 @@ import {
|
|
|
69
69
|
collectionProductsReplaceRequestSchema,
|
|
70
70
|
collectionSchema,
|
|
71
71
|
} from "./schemas/collection";
|
|
72
|
+
import {
|
|
73
|
+
type CreateCommerceFlowRequest,
|
|
74
|
+
commerceFlowCheckSchema,
|
|
75
|
+
commerceFlowSchema,
|
|
76
|
+
commerceFlowSummarySchema,
|
|
77
|
+
createCommerceFlowRequestSchema,
|
|
78
|
+
flowActionRequestSchema,
|
|
79
|
+
flowActionResultSchema,
|
|
80
|
+
flowPresetSummarySchema,
|
|
81
|
+
flowSecretSchema,
|
|
82
|
+
orderItemFlowSchema,
|
|
83
|
+
resolvedFlowSchema,
|
|
84
|
+
type SaveFlowDraftRequest,
|
|
85
|
+
type SaveFlowSecretRequest,
|
|
86
|
+
saveFlowDraftRequestSchema,
|
|
87
|
+
saveFlowSecretRequestSchema,
|
|
88
|
+
} from "./schemas/commerce-flow";
|
|
72
89
|
import { bulkResultSchema, type PageParams, pageSchema } from "./schemas/common";
|
|
73
90
|
import {
|
|
74
91
|
type CreateCouponRequest,
|
|
@@ -95,6 +112,11 @@ import {
|
|
|
95
112
|
type WithdrawCustomerRequest,
|
|
96
113
|
withdrawCustomerRequestSchema,
|
|
97
114
|
} from "./schemas/customer";
|
|
115
|
+
import {
|
|
116
|
+
type PickupLocationRequestInput,
|
|
117
|
+
pickupLocationRequestSchema,
|
|
118
|
+
pickupLocationSchema,
|
|
119
|
+
} from "./schemas/fulfillment";
|
|
98
120
|
import {
|
|
99
121
|
type LoginProvider,
|
|
100
122
|
loginProviderTestSchema,
|
|
@@ -215,6 +237,12 @@ import {
|
|
|
215
237
|
savedOptionSetRequestSchema,
|
|
216
238
|
savedOptionSetSchema,
|
|
217
239
|
} from "./schemas/saved-option-set";
|
|
240
|
+
import {
|
|
241
|
+
calendarDaySchema,
|
|
242
|
+
calendarQuerySchema,
|
|
243
|
+
type SetCalendarRequest,
|
|
244
|
+
setCalendarRequestSchema,
|
|
245
|
+
} from "./schemas/stay";
|
|
218
246
|
import {
|
|
219
247
|
type AddressType,
|
|
220
248
|
type Invitation,
|
|
@@ -506,6 +534,24 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
506
534
|
}),
|
|
507
535
|
removeDiscount: (productId: string) =>
|
|
508
536
|
storeHttp.requestVoid("DELETE", path`/products/${productId}/discount`),
|
|
537
|
+
/** 날짜별 재고 상품(`inventoryMode: "DATED"`, 이슈 #118)의 기간(포함) 달력. 없는 날은 싣지 않는다 */
|
|
538
|
+
getCalendar: (productId: string, query: z.input<typeof calendarQuerySchema>) =>
|
|
539
|
+
storeHttp.request(
|
|
540
|
+
"GET",
|
|
541
|
+
path`/products/${productId}/calendar`,
|
|
542
|
+
z.array(calendarDaySchema),
|
|
543
|
+
{
|
|
544
|
+
query: calendarQuerySchema.parse(query) as Query,
|
|
545
|
+
},
|
|
546
|
+
),
|
|
547
|
+
/** 기간의 날짜마다 조합별 재고·가격을 덮어쓴다. `price: null`은 조합 가격으로 되돌린다 */
|
|
548
|
+
setCalendar: (productId: string, body: SetCalendarRequest) =>
|
|
549
|
+
storeHttp.request(
|
|
550
|
+
"PUT",
|
|
551
|
+
path`/products/${productId}/calendar`,
|
|
552
|
+
z.object({ updated: z.number().int() }),
|
|
553
|
+
{ body: setCalendarRequestSchema.parse(body) },
|
|
554
|
+
),
|
|
509
555
|
},
|
|
510
556
|
|
|
511
557
|
/**
|
|
@@ -730,6 +776,88 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
730
776
|
}),
|
|
731
777
|
getDelivery: (orderItemId: string) =>
|
|
732
778
|
storeHttp.request("GET", path`/order-items/${orderItemId}/delivery`, deliveryStatusSchema),
|
|
779
|
+
/** `GET /order-items/{id}/flow` — 커머스 플로우의 지금 상태·할 수 있는 행동·타이머(#115). 스코프 `order:r` */
|
|
780
|
+
getFlow: (orderItemId: string) =>
|
|
781
|
+
storeHttp.request("GET", path`/order-items/${orderItemId}/flow`, orderItemFlowSchema),
|
|
782
|
+
/**
|
|
783
|
+
* `POST /order-items/{id}/actions/{key}` — `getFlow().actions`에 켜진 행동을 실행한다(#115). 묶음 진행이면
|
|
784
|
+
* `affectedOrderItemIds`에 함께 움직인 주문상품이 온다. 에러: 409 `ACTION_NOT_AVAILABLE`
|
|
785
|
+
*/
|
|
786
|
+
act: (
|
|
787
|
+
orderItemId: string,
|
|
788
|
+
key: string,
|
|
789
|
+
input: Record<string, unknown> = {},
|
|
790
|
+
idempotencyKey?: string,
|
|
791
|
+
) =>
|
|
792
|
+
storeHttp.request(
|
|
793
|
+
"POST",
|
|
794
|
+
path`/order-items/${orderItemId}/actions/${key}`,
|
|
795
|
+
flowActionResultSchema,
|
|
796
|
+
{ body: flowActionRequestSchema.parse({ input }), headers: idempotency(idempotencyKey) },
|
|
797
|
+
),
|
|
798
|
+
/**
|
|
799
|
+
* `POST /order-items/{id}/events/{key}` — 현장 단말·연동의 상점 사건(`custom.<이름>`)을 플로우에 넣는다(#115).
|
|
800
|
+
* 에러: 400 `FLOW_EVENT_KEY_INVALID`, 409 `FLOW_EVENT_NOT_ACCEPTED`
|
|
801
|
+
*/
|
|
802
|
+
emitEvent: (
|
|
803
|
+
orderItemId: string,
|
|
804
|
+
key: `custom.${string}`,
|
|
805
|
+
input: Record<string, unknown> = {},
|
|
806
|
+
idempotencyKey?: string,
|
|
807
|
+
) =>
|
|
808
|
+
storeHttp.request(
|
|
809
|
+
"POST",
|
|
810
|
+
path`/order-items/${orderItemId}/events/${key}`,
|
|
811
|
+
flowActionResultSchema,
|
|
812
|
+
{ body: flowActionRequestSchema.parse({ input }), headers: idempotency(idempotencyKey) },
|
|
813
|
+
),
|
|
814
|
+
},
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* 커머스 플로우 (이슈 #115) — 프리셋 설치·초안 저장·검사·게시와 상품 할당 규칙. 조회 `flow:r`, 쓰기 `flow:rw`.
|
|
818
|
+
* 게시한 버전은 불변이고, 진행 중 주문은 결제 때 붙은 버전으로 끝까지 간다.
|
|
819
|
+
*/
|
|
820
|
+
commerceFlows: {
|
|
821
|
+
presets: () => storeHttp.request("GET", "/flow-presets", z.array(flowPresetSummarySchema)),
|
|
822
|
+
list: () => storeHttp.request("GET", "/commerce-flows", z.array(commerceFlowSummarySchema)),
|
|
823
|
+
get: (flowId: string) =>
|
|
824
|
+
storeHttp.request("GET", path`/commerce-flows/${flowId}`, commerceFlowSchema),
|
|
825
|
+
/** 프리셋 설치 또는 빈 플로우. 에러: 409 `FLOW_KEY_TAKEN`·`FLOW_PRESET_UNAVAILABLE` */
|
|
826
|
+
create: (body: CreateCommerceFlowRequest) =>
|
|
827
|
+
storeHttp.request("POST", "/commerce-flows", commerceFlowSchema, {
|
|
828
|
+
body: createCommerceFlowRequestSchema.parse(body),
|
|
829
|
+
}),
|
|
830
|
+
/** 초안 저장. `revision`을 주면 그 초안일 때만 저장한다(409 `FLOW_REVISION_CONFLICT`) */
|
|
831
|
+
saveDraft: (flowId: string, body: SaveFlowDraftRequest, revision?: number) =>
|
|
832
|
+
storeHttp.request("PUT", path`/commerce-flows/${flowId}/draft`, commerceFlowSchema, {
|
|
833
|
+
body: saveFlowDraftRequestSchema.parse(body),
|
|
834
|
+
headers: revision === undefined ? undefined : { "if-match": String(revision) },
|
|
835
|
+
}),
|
|
836
|
+
check: (flowId: string) =>
|
|
837
|
+
storeHttp.request(
|
|
838
|
+
"POST",
|
|
839
|
+
path`/commerce-flows/${flowId}/draft/check`,
|
|
840
|
+
commerceFlowCheckSchema,
|
|
841
|
+
),
|
|
842
|
+
/** 게시. 차단 오류가 있으면 409 `PUBLISH_BLOCKED`(`details.blocking`) */
|
|
843
|
+
publish: (flowId: string) =>
|
|
844
|
+
storeHttp.request(
|
|
845
|
+
"POST",
|
|
846
|
+
path`/commerce-flows/${flowId}/draft/publish`,
|
|
847
|
+
commerceFlowSchema,
|
|
848
|
+
),
|
|
849
|
+
/** 이 상품에 지금 붙을 플로우 */
|
|
850
|
+
forProduct: (productId: string) =>
|
|
851
|
+
storeHttp.request("GET", path`/products/${productId}/flow`, resolvedFlowSchema),
|
|
852
|
+
/** HTTP 노드 시크릿 목록(헤더 값 제외) */
|
|
853
|
+
secrets: () => storeHttp.request("GET", "/commerce-flows/secrets", z.array(flowSecretSchema)),
|
|
854
|
+
/** 시크릿 저장(같은 이름이면 덮어쓴다). 상점 플랫폼 OWNER·ADMIN만. 에러: 400 `FLOW_SECRET_URL_INVALID` */
|
|
855
|
+
saveSecret: (name: string, body: SaveFlowSecretRequest) =>
|
|
856
|
+
storeHttp.request("PUT", path`/commerce-flows/secrets/${name}`, flowSecretSchema, {
|
|
857
|
+
body: saveFlowSecretRequestSchema.parse(body),
|
|
858
|
+
}),
|
|
859
|
+
removeSecret: (name: string) =>
|
|
860
|
+
storeHttp.requestVoid("DELETE", path`/commerce-flows/secrets/${name}`),
|
|
733
861
|
},
|
|
734
862
|
|
|
735
863
|
carriers: {
|
|
@@ -1335,6 +1463,21 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1335
1463
|
storeHttp.requestVoid("POST", path`/seller-addresses/${addressId}/default`),
|
|
1336
1464
|
},
|
|
1337
1465
|
|
|
1466
|
+
/** 픽업 장소 (이슈 #115) — 방문 수령 주문의 수령 장소. 조회 `store:r`, 쓰기 `store:rw`(상점 플랫폼 OWNER·ADMIN) */
|
|
1467
|
+
pickupLocations: {
|
|
1468
|
+
list: () => storeHttp.request("GET", "/pickup-locations", z.array(pickupLocationSchema)),
|
|
1469
|
+
create: (body: PickupLocationRequestInput) =>
|
|
1470
|
+
storeHttp.request("POST", "/pickup-locations", pickupLocationSchema, {
|
|
1471
|
+
body: pickupLocationRequestSchema.parse(body),
|
|
1472
|
+
}),
|
|
1473
|
+
update: (locationId: string, body: PickupLocationRequestInput) =>
|
|
1474
|
+
storeHttp.request("PUT", path`/pickup-locations/${locationId}`, pickupLocationSchema, {
|
|
1475
|
+
body: pickupLocationRequestSchema.parse(body),
|
|
1476
|
+
}),
|
|
1477
|
+
remove: (locationId: string) =>
|
|
1478
|
+
storeHttp.requestVoid("DELETE", path`/pickup-locations/${locationId}`),
|
|
1479
|
+
},
|
|
1480
|
+
|
|
1338
1481
|
/**
|
|
1339
1482
|
* 스토어프론트 호스팅 (이슈 #53) — 조회 `storefront:r`, 쓰기 `storefront:rw`(OWNER·ADMIN).
|
|
1340
1483
|
* 쓰기 응답은 바뀐 사이트다. Cloudflare 반영은 커밋 뒤라 `provisioning.status`가 잠시 `PENDING`일 수 있다.
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ export * from "./schemas/auth";
|
|
|
23
23
|
export * from "./schemas/category";
|
|
24
24
|
export * from "./schemas/claim";
|
|
25
25
|
export * from "./schemas/collection";
|
|
26
|
+
export * from "./schemas/commerce-flow";
|
|
26
27
|
export * from "./schemas/commerce-number";
|
|
27
28
|
export * from "./schemas/common";
|
|
28
29
|
export * from "./schemas/coupon";
|
|
@@ -40,6 +41,7 @@ export * from "./schemas/refund-policy";
|
|
|
40
41
|
export * from "./schemas/saved-option-set";
|
|
41
42
|
export * from "./schemas/scope";
|
|
42
43
|
export * from "./schemas/shipping-policy";
|
|
44
|
+
export * from "./schemas/stay";
|
|
43
45
|
export * from "./schemas/store";
|
|
44
46
|
export * from "./schemas/storefront-hosting";
|
|
45
47
|
export * from "./schemas/subscription";
|
package/src/schemas/category.ts
CHANGED
|
@@ -26,6 +26,13 @@ export const categorySchema = z.object({
|
|
|
26
26
|
metaTitle: z.string().nullable(),
|
|
27
27
|
metaDescription: z.string().nullable(),
|
|
28
28
|
metaKeywords: z.string().nullable(),
|
|
29
|
+
flowId: z
|
|
30
|
+
.string()
|
|
31
|
+
.nullable()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe(
|
|
34
|
+
"주문 흐름(이슈 #115) — 맨 아래 카테고리에만 있다. 흐름을 지정하지 않은 상품이 쓴다. null이면 기본 흐름",
|
|
35
|
+
),
|
|
29
36
|
createdAt: z.string(),
|
|
30
37
|
updatedAt: z.string(),
|
|
31
38
|
});
|
|
@@ -89,6 +96,13 @@ export const categoryPatchRequestSchema = z.object({
|
|
|
89
96
|
metaTitle: z.string().max(100).nullable().optional(),
|
|
90
97
|
metaDescription: z.string().max(300).nullable().optional(),
|
|
91
98
|
metaKeywords: z.string().max(200).nullable().optional(),
|
|
99
|
+
flowId: z
|
|
100
|
+
.string()
|
|
101
|
+
.nullable()
|
|
102
|
+
.optional()
|
|
103
|
+
.describe(
|
|
104
|
+
"주문 흐름(이슈 #115) — 게시한 흐름 id, null이면 기본 흐름. 맨 아래 카테고리에만 지정한다(400 `CATEGORY_NOT_LEAF`). 게시하지 않은 흐름은 409 `FLOW_NOT_PUBLISHED`",
|
|
105
|
+
),
|
|
92
106
|
});
|
|
93
107
|
|
|
94
108
|
export type CategoryPatchRequest = z.infer<typeof categoryPatchRequestSchema>;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { claimStatusSchema } from "./claim";
|
|
3
|
+
import { orderItemStatusSchema } from "./common";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 커머스 플로우(이슈 #115) — 주문상품이 따르는 플로우의 지금 상태와 할 수 있는 행동. 공개 계약은 필드 추가로만 자란다.
|
|
7
|
+
* 행동 키·필드 키·꺼진 이유는 상점·앱이 늘리는 **열린 문자열**이다(모르는 값도 파싱한다). 공개 상태(`coreStatus`)만 닫힌 8개 값이다.
|
|
8
|
+
* 화면은 이 값만 그리고 상태 이름으로 분기하지 않는다 — 판정은 서버가 한다.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const flowInputFieldSchema = z.object({
|
|
12
|
+
key: z.string(),
|
|
13
|
+
type: z.string().describe("text · number · select · date · datetime · image (늘어날 수 있다)"),
|
|
14
|
+
required: z.boolean(),
|
|
15
|
+
label: z.string(),
|
|
16
|
+
maxLength: z.number().int().optional(),
|
|
17
|
+
maxFiles: z.number().int().optional(),
|
|
18
|
+
options: z.array(z.union([z.string(), z.number()])).optional(),
|
|
19
|
+
});
|
|
20
|
+
export type FlowInputField = z.infer<typeof flowInputFieldSchema>;
|
|
21
|
+
|
|
22
|
+
export const flowActionSchema = z.object({
|
|
23
|
+
key: z.string().describe("행동 키. 플랫폼 키(예: CONFIRM_ORDER)와 상점·앱이 정한 키가 섞인다"),
|
|
24
|
+
label: z.string().describe("보여 줄 이름"),
|
|
25
|
+
mode: z
|
|
26
|
+
.enum(["INSTANT", "REQUEST", "VIEW"])
|
|
27
|
+
.describe("INSTANT 바로 실행 · REQUEST 승인 요청(클레임) · VIEW 보기"),
|
|
28
|
+
enabled: z.boolean(),
|
|
29
|
+
disabled: z
|
|
30
|
+
.object({
|
|
31
|
+
reason: z.string().describe("EXPIRED · CONDITION (늘어날 수 있다)"),
|
|
32
|
+
message: z.string().optional(),
|
|
33
|
+
})
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("꺼진 이유. 켜져 있으면 없다"),
|
|
36
|
+
availableUntil: z.string().optional().describe("이 시각까지만 할 수 있다(ISO 8601)"),
|
|
37
|
+
subflow: z.string().optional().describe("이 행동이 시작하는 하위 플로우(클레임)"),
|
|
38
|
+
input: z.array(flowInputFieldSchema).optional().describe("받는 입력 폼"),
|
|
39
|
+
partialQuantity: z.union([z.boolean(), z.literal("UNIT")]).optional(),
|
|
40
|
+
});
|
|
41
|
+
export type FlowAction = z.infer<typeof flowActionSchema>;
|
|
42
|
+
|
|
43
|
+
/** 커스텀 필드 값 — 문자열(날짜·일시는 ISO 8601)·숫자·참거짓·null, 수량 단위 필드는 문자열 배열 */
|
|
44
|
+
export const flowFieldValueSchema = z.union([
|
|
45
|
+
z.string(),
|
|
46
|
+
z.number(),
|
|
47
|
+
z.boolean(),
|
|
48
|
+
z.null(),
|
|
49
|
+
z.array(z.string()),
|
|
50
|
+
]);
|
|
51
|
+
export type FlowFieldValue = z.infer<typeof flowFieldValueSchema>;
|
|
52
|
+
|
|
53
|
+
export const flowStateSchema = z.object({
|
|
54
|
+
key: z.string().describe("플로우 안 상태 키(상점이 정한다). 화면 분기에 쓰지 않는다"),
|
|
55
|
+
label: z.string().describe("보는 사람(셀러·구매자)의 상태 이름"),
|
|
56
|
+
coreStatus: orderItemStatusSchema.optional().describe("공개 상태(8개)"),
|
|
57
|
+
claimStatus: claimStatusSchema.optional(),
|
|
58
|
+
message: z.string().optional().describe("보는 사람에게 보여 줄 안내문"),
|
|
59
|
+
enteredAt: z.string().describe("이 상태에 들어온 시각(ISO 8601)"),
|
|
60
|
+
deadlineAt: z.string().optional().describe("가장 가까운 기한(ISO 8601)"),
|
|
61
|
+
attachments: z
|
|
62
|
+
.array(z.string())
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("가장 최근 행동이 붙인 첨부 주소(시안 등, #115). 없으면 생략"),
|
|
65
|
+
progress: z.boolean().optional().describe("진행 단계 표시에 넣는 상태인가"),
|
|
66
|
+
fields: z.record(z.string(), flowFieldValueSchema).describe("보는 사람 공개 범위의 커스텀 필드"),
|
|
67
|
+
});
|
|
68
|
+
export type FlowState = z.infer<typeof flowStateSchema>;
|
|
69
|
+
|
|
70
|
+
export const orderItemFlowSchema = z.object({
|
|
71
|
+
orderItemId: z.string(),
|
|
72
|
+
flow: z.object({ flowId: z.string(), version: z.number().int() }),
|
|
73
|
+
flowState: flowStateSchema,
|
|
74
|
+
actions: z.array(flowActionSchema),
|
|
75
|
+
timers: z
|
|
76
|
+
.array(z.object({ key: z.string(), dueAt: z.string(), paused: z.boolean() }))
|
|
77
|
+
.describe("걸린 타이머(셀러 보기)"),
|
|
78
|
+
ended: z.boolean(),
|
|
79
|
+
});
|
|
80
|
+
export type OrderItemFlow = z.infer<typeof orderItemFlowSchema>;
|
|
81
|
+
|
|
82
|
+
export const flowActionRequestSchema = z.object({
|
|
83
|
+
input: z.record(z.string(), z.unknown()).optional().describe("행동 입력 — 행동의 `input` 폼 키"),
|
|
84
|
+
});
|
|
85
|
+
export type FlowActionRequest = z.infer<typeof flowActionRequestSchema>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 행동 실행 결과. 지금은 동기 완료(COMPLETED)뿐이다. 외부 호출이 든 전이는 202와 함께 ACCEPTED(runId)가 온다(HTTP·앱 노드 단계에서 더한다).
|
|
89
|
+
* 동기 거절은 409(ACTION_NOT_AVAILABLE)다.
|
|
90
|
+
*/
|
|
91
|
+
export const flowActionResultSchema = z.object({
|
|
92
|
+
status: z.literal("COMPLETED"),
|
|
93
|
+
flowState: flowStateSchema,
|
|
94
|
+
actions: z.array(flowActionSchema),
|
|
95
|
+
/** 이 행동으로 바뀐 주문상품(주문 묶음 진행이면 여럿) */
|
|
96
|
+
affectedOrderItemIds: z.array(z.string()),
|
|
97
|
+
});
|
|
98
|
+
export type FlowActionResult = z.infer<typeof flowActionResultSchema>;
|
|
99
|
+
|
|
100
|
+
// ── 플로우 정의 관리(이슈 #115 D3a) — `/v1/commerce-flows/**`, 스코프 `flow:r|rw` ─────────────────────
|
|
101
|
+
|
|
102
|
+
/** 플로우 작성 그래프와 정의는 형태가 크고 노드 종류가 늘어나므로 여기서는 열린 객체로 싣는다(검증은 서버 `…/draft/check`) */
|
|
103
|
+
const flowDocumentSchema = z.record(z.string(), z.unknown());
|
|
104
|
+
|
|
105
|
+
export const flowPresetSummarySchema = z.object({
|
|
106
|
+
key: z.string(),
|
|
107
|
+
presetVersion: z.number().int(),
|
|
108
|
+
name: z.string(),
|
|
109
|
+
description: z.string(),
|
|
110
|
+
availability: z
|
|
111
|
+
.enum(["AVAILABLE", "BLOCKED"])
|
|
112
|
+
.describe("BLOCKED면 의존 이슈(dependsOn)가 끝나야 설치할 수 있다"),
|
|
113
|
+
dependsOn: z.array(z.string()),
|
|
114
|
+
requires: flowDocumentSchema.describe(
|
|
115
|
+
"프리셋이 쓰는 Core 기능(진행 단위·이행 방식·주문 입력 등)",
|
|
116
|
+
),
|
|
117
|
+
variables: z.array(flowDocumentSchema).describe("셀러가 값만 입력하는 변수 정의"),
|
|
118
|
+
});
|
|
119
|
+
export type FlowPresetSummary = z.infer<typeof flowPresetSummarySchema>;
|
|
120
|
+
|
|
121
|
+
export const commerceFlowSummarySchema = z.object({
|
|
122
|
+
flowId: z.string(),
|
|
123
|
+
key: z.string().describe("상점 안 유일한 키"),
|
|
124
|
+
name: z.string(),
|
|
125
|
+
progression: z.enum(["ITEM", "ORDER_GROUP"]),
|
|
126
|
+
fulfillmentMethods: z.array(z.string()),
|
|
127
|
+
presetKey: z.string().nullable().describe("설치한 프리셋(설치 뒤 연결되지 않는다)"),
|
|
128
|
+
presetVersion: z.number().int().nullable(),
|
|
129
|
+
activeVersion: z.number().int().nullable().describe("게시된 버전. 새 주문이 따른다"),
|
|
130
|
+
archivedAt: z.string().nullable(),
|
|
131
|
+
createdAt: z.string(),
|
|
132
|
+
updatedAt: z.string(),
|
|
133
|
+
});
|
|
134
|
+
export type CommerceFlowSummary = z.infer<typeof commerceFlowSummarySchema>;
|
|
135
|
+
|
|
136
|
+
export const commerceFlowVersionViewSchema = z.object({
|
|
137
|
+
version: z.number().int(),
|
|
138
|
+
status: z.enum(["DRAFT", "ACTIVE", "LEGACY"]),
|
|
139
|
+
revision: z.number().int().describe("초안 저장의 If-Match 값"),
|
|
140
|
+
graph: flowDocumentSchema,
|
|
141
|
+
definition: flowDocumentSchema,
|
|
142
|
+
variables: z.record(z.string(), z.unknown()),
|
|
143
|
+
publishedAt: z.string().nullable(),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
export const commerceFlowSchema = commerceFlowSummarySchema.extend({
|
|
147
|
+
draft: commerceFlowVersionViewSchema.nullable(),
|
|
148
|
+
active: commerceFlowVersionViewSchema.nullable(),
|
|
149
|
+
versions: z.array(
|
|
150
|
+
z.object({
|
|
151
|
+
version: z.number().int(),
|
|
152
|
+
status: z.enum(["DRAFT", "ACTIVE", "LEGACY"]),
|
|
153
|
+
publishedAt: z.string().nullable(),
|
|
154
|
+
}),
|
|
155
|
+
),
|
|
156
|
+
});
|
|
157
|
+
export type CommerceFlow = z.infer<typeof commerceFlowSchema>;
|
|
158
|
+
|
|
159
|
+
export const createCommerceFlowRequestSchema = z.object({
|
|
160
|
+
key: z
|
|
161
|
+
.string()
|
|
162
|
+
.regex(/^[a-z][a-z0-9-]{1,48}$/)
|
|
163
|
+
.describe("상점 안 유일한 키(소문자·숫자·하이픈)"),
|
|
164
|
+
name: z.string().min(1).max(60).optional(),
|
|
165
|
+
presetKey: z.string().optional().describe("설치할 프리셋. 없으면 빈 플로우"),
|
|
166
|
+
variables: z.record(z.string(), z.unknown()).optional().describe("프리셋 변수 값"),
|
|
167
|
+
});
|
|
168
|
+
export type CreateCommerceFlowRequest = z.infer<typeof createCommerceFlowRequestSchema>;
|
|
169
|
+
|
|
170
|
+
export const saveFlowDraftRequestSchema = z.object({
|
|
171
|
+
graph: flowDocumentSchema,
|
|
172
|
+
definition: flowDocumentSchema.optional(),
|
|
173
|
+
variables: z.record(z.string(), z.unknown()).optional(),
|
|
174
|
+
});
|
|
175
|
+
export type SaveFlowDraftRequest = z.infer<typeof saveFlowDraftRequestSchema>;
|
|
176
|
+
|
|
177
|
+
const flowIssueSchema = z.object({
|
|
178
|
+
code: z.string(),
|
|
179
|
+
message: z.string(),
|
|
180
|
+
nodeId: z.string().optional(),
|
|
181
|
+
edgeId: z.string().optional(),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
export const commerceFlowCheckSchema = z.object({
|
|
185
|
+
publishable: z.boolean().describe("게시를 막는 오류(실행 불가·공개 계약·한도)가 없다"),
|
|
186
|
+
blocking: z.array(flowIssueSchema),
|
|
187
|
+
warnings: z.array(flowIssueSchema).describe("업무 논리 경고 — 게시를 막지 않는다"),
|
|
188
|
+
});
|
|
189
|
+
export type CommerceFlowCheck = z.infer<typeof commerceFlowCheckSchema>;
|
|
190
|
+
|
|
191
|
+
// ── 흐름 해석(이슈 #115) — 상품의 흐름 → 카테고리(맨 아래)의 흐름 → 기본 흐름. 할당 규칙은 없앴다 ─────────────────
|
|
192
|
+
|
|
193
|
+
export const resolvedFlowSchema = z.object({
|
|
194
|
+
flowId: z.string(),
|
|
195
|
+
version: z.number().int(),
|
|
196
|
+
source: z
|
|
197
|
+
.enum(["PRODUCT", "CATEGORY", "PLATFORM"])
|
|
198
|
+
.describe("PRODUCT 상품에 지정한 흐름 · CATEGORY 카테고리의 흐름 · PLATFORM 기본 흐름"),
|
|
199
|
+
});
|
|
200
|
+
export type ResolvedFlow = z.infer<typeof resolvedFlowSchema>;
|
|
201
|
+
|
|
202
|
+
// ─── 플로우 시크릿(이슈 #115 E5b) ─────────────────────────────
|
|
203
|
+
|
|
204
|
+
/** 시크릿 이름 — 플로우 변수 `secretRef`의 값이다 */
|
|
205
|
+
export const flowSecretNameSchema = z
|
|
206
|
+
.string()
|
|
207
|
+
.regex(/^[a-z][a-z0-9-]{1,39}$/)
|
|
208
|
+
.describe("시크릿 이름(소문자·숫자·하이픈 2~40자). 플로우 변수(시크릿)의 값으로 쓴다");
|
|
209
|
+
|
|
210
|
+
/** HTTP 노드가 부를 셀러 API — 헤더 값은 응답에 싣지 않는다 */
|
|
211
|
+
export const flowSecretSchema = z.object({
|
|
212
|
+
name: z.string(),
|
|
213
|
+
url: z.string().describe("요청 주소(https)"),
|
|
214
|
+
headerNames: z.array(z.string()).describe("저장한 헤더 이름(값은 보이지 않는다)"),
|
|
215
|
+
updatedAt: z.string(),
|
|
216
|
+
});
|
|
217
|
+
export type FlowSecret = z.infer<typeof flowSecretSchema>;
|
|
218
|
+
|
|
219
|
+
export const saveFlowSecretRequestSchema = z.object({
|
|
220
|
+
url: z
|
|
221
|
+
.url()
|
|
222
|
+
.refine((value) => value.startsWith("https://"), { message: "https 주소만 받습니다" })
|
|
223
|
+
.describe("요청 주소. https만, 사설·루프백 주소는 400 `FLOW_SECRET_URL_INVALID`"),
|
|
224
|
+
headers: z
|
|
225
|
+
.record(z.string().regex(/^[A-Za-z0-9-]{1,64}$/), z.string().max(2000))
|
|
226
|
+
.refine((value) => Object.keys(value).length <= 10, { message: "헤더는 10개까지입니다" })
|
|
227
|
+
.default({})
|
|
228
|
+
.describe("함께 보낼 헤더(인증 등). 값은 암호화해 두고 다시 보여 주지 않는다"),
|
|
229
|
+
});
|
|
230
|
+
export type SaveFlowSecretRequest = z.input<typeof saveFlowSecretRequestSchema>;
|