@sayren/store-sdk 0.23.0 → 0.28.1
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 +636 -482
- package/dist/index.mjs +370 -347
- package/dist/server.d.mts +19 -28
- package/dist/server.mjs +29 -42
- package/package.json +1 -1
- package/src/client.ts +55 -153
- package/src/http.ts +1 -1
- package/src/index.ts +3 -2
- package/src/member-delegation.ts +51 -77
- package/src/oauth.ts +28 -33
- package/src/schemas/api-log.ts +7 -5
- package/src/schemas/audit.ts +5 -5
- package/src/schemas/auth.ts +0 -64
- package/src/schemas/claim.ts +4 -4
- package/src/schemas/coupon.ts +2 -2
- package/src/schemas/customer-delegation.ts +48 -0
- package/src/schemas/customer.ts +3 -3
- package/src/schemas/fulfillment.ts +9 -11
- package/src/schemas/order.ts +1 -1
- package/src/schemas/payment.ts +7 -5
- package/src/schemas/product.ts +12 -5
- package/src/schemas/promotion.ts +4 -4
- package/src/schemas/refund-policy.ts +12 -12
- package/src/schemas/saved-option-set.ts +1 -1
- package/src/schemas/scope.ts +3 -4
- package/src/schemas/shipping-policy.ts +18 -18
- package/src/schemas/store.ts +9 -9
- package/src/schemas/storefront-hosting.ts +62 -38
- package/src/schemas/subscription.ts +222 -0
- package/src/schemas/telemetry.ts +2 -2
- package/src/schemas/upload.ts +1 -1
- package/src/schemas/webhook-event.ts +1 -1
- package/src/server.ts +1 -2
- package/src/session.ts +46 -29
- package/src/schemas/api-client.ts +0 -121
|
@@ -4,7 +4,7 @@ import { RESERVED_STORE_CODES } from "./store";
|
|
|
4
4
|
/**
|
|
5
5
|
* 스토어프론트 호스팅 (이슈 #53, M1: 템플릿 발행) — 관리 API `/v1/storefront/**`의 계약이다.
|
|
6
6
|
*
|
|
7
|
-
* 셀러는 템플릿을 골라 사이트를 만들고 `{slug}.{호스팅 도메인}` 주소를 받는다.
|
|
7
|
+
* 셀러는 템플릿을 골라 사이트를 만들고 `{slug}.{호스팅 도메인}` 주소를 받는다. 상점을 만들어도 사이트는 생기지 않고
|
|
8
8
|
* `POST /v1/storefront/site`가 유일한 생성 경로다. 템플릿은 만들 때만 고르고 바꾸지 않는다. 발행은 버전 단위이고,
|
|
9
9
|
* 되돌리기는 이전 버전을 다시 발행하는 것이다(재빌드 없음). 상태의 원천은 api DB이고 Cloudflare 쪽 반영
|
|
10
10
|
* (라우팅 표·사이트 Worker)은 커밋 뒤 비동기다 — 반영 상태는 `provisioning`으로 본다.
|
|
@@ -15,15 +15,16 @@ import { RESERVED_STORE_CODES } from "./store";
|
|
|
15
15
|
* 오류 코드:
|
|
16
16
|
* - 403 `STOREFRONT_HOSTING_DISABLED` — 플랫폼 스위치가 꺼져 새 사이트를 만들 수 없다
|
|
17
17
|
* - 404 `STOREFRONT_SITE_NOT_FOUND` · `STOREFRONT_TEMPLATE_NOT_FOUND` · `STOREFRONT_VERSION_NOT_FOUND`
|
|
18
|
-
* - 409 `STOREFRONT_SITE_EXISTS` — 이미 사이트가 있다(
|
|
18
|
+
* - 409 `STOREFRONT_SITE_EXISTS` — 이미 사이트가 있다(상점당 1개)
|
|
19
19
|
* - 409 `STOREFRONT_SLUG_TAKEN` — 다른 사이트가 쓰거나 보류 중인 슬러그다
|
|
20
|
-
* - 409 `STOREFRONT_SLUG_CHANGE_LIMIT` — 슬러그는
|
|
21
|
-
* - 409 `STOREFRONT_SITE_CREATE_LIMIT` — 사이트는
|
|
20
|
+
* - 409 `STOREFRONT_SLUG_CHANGE_LIMIT` — 슬러그는 상점당 30일에 3번까지 바꾼다
|
|
21
|
+
* - 409 `STOREFRONT_SITE_CREATE_LIMIT` — 사이트는 상점당 30일에 3번까지 만든다
|
|
22
22
|
* - 409 `STOREFRONT_SITE_CHANGED` — 읽은 상태(`expected*`)와 현재 상태가 다르다. 다시 읽고 시도한다
|
|
23
23
|
* - 409 `STOREFRONT_SITE_NOT_ACTIVE` — 정지(`SUSPENDED`)된 사이트는 바꾸거나 끌 수 없다
|
|
24
|
-
* - 409 `STORE_SUSPENDED` — 정지된
|
|
24
|
+
* - 409 `STORE_SUSPENDED` — 정지된 상점에는 사이트를 만들 수 없다
|
|
25
25
|
* - 409 `STOREFRONT_VERSION_NOT_READY` — 빌드가 끝나지 않았거나 실패한 버전은 발행할 수 없다
|
|
26
26
|
* - 409 `STOREFRONT_NO_ROLLBACK_TARGET` — 되돌릴 이전 발행 버전이 없다
|
|
27
|
+
* - 409 `STOREFRONT_NOT_READY` — 공개 전환: 판매 중인 상품(샘플 제외)이 없다(이슈 #70). 상품을 등록한 뒤 공개한다
|
|
27
28
|
* - 409 `TEMPLATE_CHANGE_NOT_SUPPORTED` — 템플릿 변경(`POST /v1/storefront/site/template`, 폐기)은 항상 이 오류다
|
|
28
29
|
* - 400 `STOREFRONT_SLUG_CONFIRM_MISMATCH` — 호스팅 끄기 확인 입력이 현재 슬러그와 다르다
|
|
29
30
|
* - 503 `STOREFRONT_PREVIEW_NOT_CONFIGURED` — 미리보기 서명 키가 설정되지 않았다
|
|
@@ -46,7 +47,7 @@ import { RESERVED_STORE_CODES } from "./store";
|
|
|
46
47
|
* - 502 `STOREFRONT_SANDBOX_UNAVAILABLE` — 편집 Sandbox 호출이 실패했다. 잠시 뒤 다시 한다
|
|
47
48
|
*
|
|
48
49
|
* 소스 내려받기·올리기(#60, `/v1/storefront/site/source/**`, `sayren` CLI):
|
|
49
|
-
* - 409 `SOURCE_CHANGED` — 작업 트리가 내려받은 뒤(`expectedBaseRef`) 바뀌었다(
|
|
50
|
+
* - 409 `SOURCE_CHANGED` — 작업 트리가 내려받은 뒤(`expectedBaseRef`) 바뀌었다(상점 플랫폼·AI 편집). `error.details.currentRef`
|
|
50
51
|
* - 409 `EDIT_SESSION_OPEN` — 편집 환경이 열려 있다. `endEditSession: true`로 끄고 진행할 수 있다
|
|
51
52
|
* - 409 `SOURCE_BLOBS_MISSING` — 올리지 않은 파일 내용이 있다(`error.details.hashes`). `POST source/blobs`로 올리고 다시 한다
|
|
52
53
|
* - 409 `DEPENDENCY_REJECTED` — 쓸 수 없는 npm 의존성이다(`error.details.rejected: [{ name, version, reason, detail }]`)
|
|
@@ -59,12 +60,12 @@ import { RESERVED_STORE_CODES } from "./store";
|
|
|
59
60
|
// ─── 슬러그 ───────────────────────────────────────────────
|
|
60
61
|
|
|
61
62
|
/**
|
|
62
|
-
* 슬러그 형식 —
|
|
63
|
+
* 슬러그 형식 — 상점 코드와 같은 DNS 라벨 규칙(소문자·숫자·하이픈 3~30자, 하이픈으로 시작·끝 불가)에
|
|
63
64
|
* 연속 하이픈 금지를 더한다. `--`는 버전 미리보기 주소(`v3--{slug}`)와 punycode(`xn--`)가 쓰는 구분자다.
|
|
64
65
|
*/
|
|
65
66
|
export const STOREFRONT_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,28}[a-z0-9])$/;
|
|
66
67
|
|
|
67
|
-
/** 호스팅 도메인에서 플랫폼이 쓰거나 쓸 이름 —
|
|
68
|
+
/** 호스팅 도메인에서 플랫폼이 쓰거나 쓸 이름 — 상점 코드 예약어에 호스팅 전용 이름을 더한다 */
|
|
68
69
|
export const RESERVED_STOREFRONT_SLUGS: readonly string[] = [
|
|
69
70
|
...RESERVED_STORE_CODES,
|
|
70
71
|
"mcp",
|
|
@@ -91,6 +92,16 @@ export const RESERVED_STOREFRONT_SLUGS: readonly string[] = [
|
|
|
91
92
|
*/
|
|
92
93
|
export const STOREFRONT_EDIT_PREVIEW_SUFFIX = "-preview";
|
|
93
94
|
|
|
95
|
+
/**
|
|
96
|
+
* 공식 쇼케이스 슬러그 접두사(이슈 #72) — `demo-{템플릿 id}`는 템플릿 미리보기용 쇼케이스 사이트 주소다. 쇼케이스로 지정된
|
|
97
|
+
* 상점만 쓸 수 있다(서버가 상점 용도로 판정한다). 공식 미리보기를 흉내 내는 주소를 막는다
|
|
98
|
+
*/
|
|
99
|
+
export const STOREFRONT_SHOWCASE_SLUG_PREFIX = "demo-";
|
|
100
|
+
|
|
101
|
+
export function isShowcaseStorefrontSlug(slug: string): boolean {
|
|
102
|
+
return slug.toLowerCase().startsWith(STOREFRONT_SHOWCASE_SLUG_PREFIX);
|
|
103
|
+
}
|
|
104
|
+
|
|
94
105
|
export function isReservedStorefrontSlug(slug: string): boolean {
|
|
95
106
|
const lower = slug.toLowerCase();
|
|
96
107
|
return (
|
|
@@ -121,9 +132,9 @@ export const storefrontSlugSchema = z
|
|
|
121
132
|
export const STOREFRONT_SLUG_HOLD_DAYS = 90;
|
|
122
133
|
/** 슬러그 변경 한도 — 이 기간(일) 안에 */
|
|
123
134
|
export const STOREFRONT_SLUG_CHANGE_WINDOW_DAYS = 30;
|
|
124
|
-
/** 슬러그 변경 한도 — 횟수(
|
|
135
|
+
/** 슬러그 변경 한도 — 횟수(상점 단위. 끄고 다시 만들어도 이어서 센다) */
|
|
125
136
|
export const STOREFRONT_SLUG_CHANGE_LIMIT = 3;
|
|
126
|
-
/** 사이트 만들기 한도 — 같은 창(30일) 안의 횟수(
|
|
137
|
+
/** 사이트 만들기 한도 — 같은 창(30일) 안의 횟수(상점 단위). 끄고 만들기를 되풀이해 이름을 묶어 두지 못하게 한다 */
|
|
127
138
|
export const STOREFRONT_SITE_CREATE_LIMIT = 3;
|
|
128
139
|
|
|
129
140
|
// ─── 템플릿 ───────────────────────────────────────────────
|
|
@@ -135,7 +146,24 @@ export const storefrontTemplateSchema = z.object({
|
|
|
135
146
|
description: z.string().describe("템플릿 설명"),
|
|
136
147
|
thumbnailUrl: z.string().nullable().describe("썸네일 이미지 주소. 없으면 null"),
|
|
137
148
|
version: z.string().describe("현재 템플릿 버전. 새 사이트는 이 버전으로 만든다"),
|
|
138
|
-
isDefault: z.boolean().describe("기본 템플릿인가 (
|
|
149
|
+
isDefault: z.boolean().describe("기본 템플릿인가 (상점 생성 시 자동 생성에 쓴다)"),
|
|
150
|
+
tags: z
|
|
151
|
+
.array(z.string())
|
|
152
|
+
.default([])
|
|
153
|
+
.describe(
|
|
154
|
+
"템플릿 특징 태그 (예: `후기 강조`). 목록 카드에 보인다. 옛 서버 응답은 빈 배열로 읽는다",
|
|
155
|
+
),
|
|
156
|
+
supportsCheckout: z
|
|
157
|
+
.boolean()
|
|
158
|
+
.default(true)
|
|
159
|
+
.describe(
|
|
160
|
+
"주문을 받는 템플릿인가. false는 가격·문의 중심의 카탈로그형이다. 옛 서버 응답은 true로 읽는다",
|
|
161
|
+
),
|
|
162
|
+
previewUrl: z
|
|
163
|
+
.string()
|
|
164
|
+
.nullable()
|
|
165
|
+
.default(null)
|
|
166
|
+
.describe("공식 쇼케이스 사이트 주소. 연결된 쇼케이스가 공개 중일 때만 싣고 그 밖에는 null"),
|
|
139
167
|
});
|
|
140
168
|
export type StorefrontTemplate = z.infer<typeof storefrontTemplateSchema>;
|
|
141
169
|
|
|
@@ -179,7 +207,7 @@ export const storefrontVersionSchema = z.object({
|
|
|
179
207
|
.nullable()
|
|
180
208
|
.optional()
|
|
181
209
|
.describe(
|
|
182
|
-
"만든 경로 (`CONSOLE`
|
|
210
|
+
"만든 경로 (`CONSOLE` 상점 플랫폼 · `AI` AI 에이전트(MCP) · `CLI` `sayren` CLI · `API` 관리 API · `SYSTEM` 자동). 늘어날 수 있다",
|
|
183
211
|
),
|
|
184
212
|
createdAt: z.string().describe("만든 시각 (ISO 8601)"),
|
|
185
213
|
});
|
|
@@ -189,7 +217,7 @@ export type StorefrontVersion = z.infer<typeof storefrontVersionSchema>;
|
|
|
189
217
|
|
|
190
218
|
export const storefrontSiteStatusSchema = z
|
|
191
219
|
.enum(["ACTIVE", "SUSPENDED", "CLOSED"])
|
|
192
|
-
.describe("`ACTIVE` 운영 중 · `SUSPENDED` 운영자 정지 · `CLOSED` 호스팅
|
|
220
|
+
.describe("`ACTIVE` 운영 중 · `SUSPENDED` 운영자 정지 · `CLOSED` 호스팅 끔·상점 폐쇄");
|
|
193
221
|
export type StorefrontSiteStatus = z.infer<typeof storefrontSiteStatusSchema>;
|
|
194
222
|
|
|
195
223
|
export const storefrontVisibilitySchema = z
|
|
@@ -231,7 +259,7 @@ export type StorefrontProvisioning = z.infer<typeof storefrontProvisioningSchema
|
|
|
231
259
|
|
|
232
260
|
/**
|
|
233
261
|
* 편집 미리보기 주소(M2b) — 사이트마다 `{사이트 id 정규화}-preview.{호스팅 도메인}` 호스트 하나다. 호스팅 도메인의 와일드카드
|
|
234
|
-
* 인증서가 덮으므로 따로 준비할 것이 없다. 이 주소는 편집 미리보기 링크(
|
|
262
|
+
* 인증서가 덮으므로 따로 준비할 것이 없다. 이 주소는 편집 미리보기 링크(상점 플랫폼 전용)로 받은 쿠키가 있어야 열린다.
|
|
235
263
|
*/
|
|
236
264
|
export const storefrontEditPreviewSchema = z.object({
|
|
237
265
|
hostname: z
|
|
@@ -304,7 +332,7 @@ export const storefrontSlugAvailabilitySchema = z.object({
|
|
|
304
332
|
export type StorefrontSlugAvailability = z.infer<typeof storefrontSlugAvailabilitySchema>;
|
|
305
333
|
|
|
306
334
|
/**
|
|
307
|
-
* 배포 진행 — 사이트의 지금 발행 상태가 공개 주소에 반영되기까지의 단계와 기록이다(
|
|
335
|
+
* 배포 진행 — 사이트의 지금 발행 상태가 공개 주소에 반영되기까지의 단계와 기록이다(상점 플랫폼의 스토어프론트 만들기 3단계가 폴링한다).
|
|
308
336
|
* 만들기(`POST /v1/storefront/site`)는 곧바로 201로 답하고 반영은 그 뒤에 비동기로 하므로, 끝났는지는 이 조회로 본다.
|
|
309
337
|
* 템플릿 사이트는 미리 빌드한 템플릿 산출물을 올리므로 빌드 단계가 없다. 기록은 사이트 상태에서 만든 요약이다.
|
|
310
338
|
*/
|
|
@@ -351,7 +379,7 @@ export type StorefrontDeployment = z.infer<typeof storefrontDeploymentSchema>;
|
|
|
351
379
|
|
|
352
380
|
export const createStorefrontSiteRequestSchema = z.object({
|
|
353
381
|
templateId: z.string().min(1).max(64).describe("템플릿 id"),
|
|
354
|
-
slug: storefrontSlugSchema.optional().describe("주소 슬러그. 생략하면
|
|
382
|
+
slug: storefrontSlugSchema.optional().describe("주소 슬러그. 생략하면 상점 코드"),
|
|
355
383
|
});
|
|
356
384
|
export type CreateStorefrontSiteRequest = z.infer<typeof createStorefrontSiteRequestSchema>;
|
|
357
385
|
|
|
@@ -411,7 +439,7 @@ export const storefrontSlugAvailabilityQuerySchema = z.object({
|
|
|
411
439
|
});
|
|
412
440
|
|
|
413
441
|
/**
|
|
414
|
-
* 오픈 준비 중 미리보기 링크 —
|
|
442
|
+
* 오픈 준비 중 미리보기 링크 — 상점 플랫폼에서 로그인한 OWNER·ADMIN만 받는다(PAT·에이전트 불가). 링크는 5분 안에 한 번 열면
|
|
415
443
|
* 그 브라우저에 12시간짜리 미리보기 쿠키가 생기고 오픈 준비 중인 사이트의 실제 화면을 본다. 링크를 남에게 넘기면 그 사람도
|
|
416
444
|
* 볼 수 있으므로 공유하지 않는다.
|
|
417
445
|
*/
|
|
@@ -431,14 +459,14 @@ export type StorefrontPreviewLink = z.infer<typeof storefrontPreviewLinkSchema>;
|
|
|
431
459
|
// ─── 내 도메인 (M3) ───────────────────────────────────────
|
|
432
460
|
//
|
|
433
461
|
// 셀러가 가진 도메인(예: `shop.brand.com`)을 사이트 주소로 연결한다. 흐름은 추가(`PENDING_DNS`) → 셀러가 DNS에 TXT(소유 확인)·
|
|
434
|
-
// CNAME(연결)을 넣음 → 서버가 TXT를 확인하면 도메인을 이
|
|
462
|
+
// CNAME(연결)을 넣음 → 서버가 TXT를 확인하면 도메인을 이 상점에 잡고 인증서 발급을 시작한다(`PENDING_VALIDATION`) → 인증서가
|
|
435
463
|
// 발급되면 `ACTIVE`다. 확인은 서버가 주기적으로 하고 `verify`는 곧바로 한 번 더 본다. 연결된 도메인은 대표 주소로 지정할 수 있고,
|
|
436
464
|
// 대표가 아닌 주소(기본 주소 포함)는 대표 주소로 301한다.
|
|
437
465
|
//
|
|
438
|
-
// - TXT 소유 확인은 도메인마다 새 값이다. CNAME만으로는 연결하지 않는다 — 연결을 끊은 뒤 CNAME이 남은 도메인을 다른
|
|
466
|
+
// - TXT 소유 확인은 도메인마다 새 값이다. CNAME만으로는 연결하지 않는다 — 연결을 끊은 뒤 CNAME이 남은 도메인을 다른 상점이
|
|
439
467
|
// 가져가지 못하게 한다.
|
|
440
|
-
// - 소유 확인 전(`PENDING_DNS`)에는 도메인을 잡지 않는다. 같은 도메인을 여러
|
|
441
|
-
//
|
|
468
|
+
// - 소유 확인 전(`PENDING_DNS`)에는 도메인을 잡지 않는다. 같은 도메인을 여러 상점이 추가해 둘 수 있고, 먼저 TXT 확인을 통과한
|
|
469
|
+
// 상점이 잡는다. 이미 다른 상점이 잡은 도메인은 409 `STOREFRONT_DOMAIN_TAKEN`이다.
|
|
442
470
|
// - 해제하면 곧바로 연결을 지운다. 연결된 뒤 DNS가 7일 넘게 이 서비스를 가리키지 않으면 서버가 연결을 해제한다.
|
|
443
471
|
// - 결제 복귀·방문 분석·소셜 로그인 허용 목록은 연결된(`ACTIVE`) 도메인을 자동으로 더한다.
|
|
444
472
|
//
|
|
@@ -446,11 +474,11 @@ export type StorefrontPreviewLink = z.infer<typeof storefrontPreviewLinkSchema>;
|
|
|
446
474
|
// - 403 `STOREFRONT_CUSTOM_DOMAINS_DISABLED` — 플랫폼 스위치가 꺼져 도메인을 추가할 수 없다
|
|
447
475
|
// - 400 `STOREFRONT_DOMAIN_INVALID` — 형식이 틀렸거나 연결할 수 없는 도메인이다(`error.details.reason`)
|
|
448
476
|
// - 404 `STOREFRONT_DOMAIN_NOT_FOUND`
|
|
449
|
-
// - 409 `STOREFRONT_DOMAIN_EXISTS` — 이
|
|
450
|
-
// - 409 `STOREFRONT_DOMAIN_TAKEN` — 다른
|
|
451
|
-
// - 409 `STOREFRONT_DOMAIN_LIMIT` —
|
|
477
|
+
// - 409 `STOREFRONT_DOMAIN_EXISTS` — 이 상점에 이미 추가한 도메인이다
|
|
478
|
+
// - 409 `STOREFRONT_DOMAIN_TAKEN` — 다른 상점이 연결했거나 연결 중인 도메인이다
|
|
479
|
+
// - 409 `STOREFRONT_DOMAIN_LIMIT` — 상점당 도메인 수 상한(`error.details.limit`)
|
|
452
480
|
// - 409 `STOREFRONT_DOMAIN_NOT_ACTIVE` — 연결되지 않았거나 경고(`failureCode`)가 있는 도메인은 대표 주소로 지정할 수 없다
|
|
453
|
-
// - 429 `TOO_MANY_REQUESTS` — 재확인은
|
|
481
|
+
// - 429 `TOO_MANY_REQUESTS` — 재확인은 상점당 분당 20번까지다(`error.details.retryAfterSeconds`)
|
|
454
482
|
// - 400 `STOREFRONT_DOMAIN_NOT_REMOVABLE` — 기본 주소는 해제할 수 없다
|
|
455
483
|
|
|
456
484
|
/**
|
|
@@ -547,7 +575,7 @@ export const STOREFRONT_PUBLIC_SUFFIXES: readonly string[] = [
|
|
|
547
575
|
"imweb.me",
|
|
548
576
|
];
|
|
549
577
|
|
|
550
|
-
/**
|
|
578
|
+
/** 상점당 연결할 수 있는 도메인 수 기본값 — 요금제(#44) 전의 상한이다. 실제 값은 `GET …/domains`의 `limit` */
|
|
551
579
|
export const STOREFRONT_CUSTOM_DOMAIN_LIMIT_DEFAULT = 2;
|
|
552
580
|
/** DNS를 넣을 때까지 기다리는 기간(일) — 지나면 `FAILED`(`DNS_NOT_CONFIGURED`) */
|
|
553
581
|
export const STOREFRONT_DOMAIN_DNS_WAIT_DAYS = 7;
|
|
@@ -655,7 +683,7 @@ export const storefrontSiteDomainSchema = z.object({
|
|
|
655
683
|
.string()
|
|
656
684
|
.nullable()
|
|
657
685
|
.describe(
|
|
658
|
-
"실패·경고 코드. `DNS_NOT_CONFIGURED` 기한 안에 TXT를 찾지 못함 · `VALIDATION_TIMEOUT` 인증서 발급 기한 지남 · `CERTIFICATE_FAILED` 인증서 발급 실패 · `DNS_NOT_POINTING` 연결된 뒤 DNS가 이 서비스를 가리키지 않음(기한이 지나면 해제) · `DOMAIN_TAKEN` 다른
|
|
686
|
+
"실패·경고 코드. `DNS_NOT_CONFIGURED` 기한 안에 TXT를 찾지 못함 · `VALIDATION_TIMEOUT` 인증서 발급 기한 지남 · `CERTIFICATE_FAILED` 인증서 발급 실패 · `DNS_NOT_POINTING` 연결된 뒤 DNS가 이 서비스를 가리키지 않음(기한이 지나면 해제) · `DOMAIN_TAKEN` 다른 상점이 먼저 소유를 확인함 · `HOSTNAME_BLOCKED` 연결이 막힘 · `HOSTNAME_CONFLICT` 연결 준비 중 충돌(운영 확인). 늘어날 수 있다",
|
|
659
687
|
),
|
|
660
688
|
verifiedAt: z.string().nullable().describe("소유를 확인한 시각 (ISO 8601)"),
|
|
661
689
|
activatedAt: z.string().nullable().describe("연결된 시각 (ISO 8601)"),
|
|
@@ -667,11 +695,7 @@ export type StorefrontSiteDomain = z.infer<typeof storefrontSiteDomainSchema>;
|
|
|
667
695
|
/** `GET /v1/storefront/site/domains` — 사이트의 주소 목록(기본 주소 + 내 도메인)과 연결 설정 */
|
|
668
696
|
export const storefrontSiteDomainsSchema = z.object({
|
|
669
697
|
enabled: z.boolean().describe("내 도메인을 추가할 수 있는가 (플랫폼 스위치)"),
|
|
670
|
-
limit: z
|
|
671
|
-
.number()
|
|
672
|
-
.int()
|
|
673
|
-
.min(0)
|
|
674
|
-
.describe("스토어당 내 도메인 수 상한 (해제한 도메인은 세지 않는다)"),
|
|
698
|
+
limit: z.number().int().min(0).describe("상점당 내 도메인 수 상한 (해제한 도메인은 세지 않는다)"),
|
|
675
699
|
target: z.string().describe("CNAME 대상 (예: `sites.sayren.co`)"),
|
|
676
700
|
domains: z.array(storefrontSiteDomainSchema).describe("대표 주소 먼저, 그다음 추가한 순서"),
|
|
677
701
|
});
|
|
@@ -688,11 +712,11 @@ export type AddStorefrontDomainRequest = z.infer<typeof addStorefrontDomainReque
|
|
|
688
712
|
|
|
689
713
|
// ─── 편집 (M2b) ───────────────────────────────────────────
|
|
690
714
|
//
|
|
691
|
-
// 셀러(
|
|
715
|
+
// 셀러(상점 플랫폼)와 에이전트(MCP)가 사이트의 작업 트리를 고치고 버전을 만든다. 작업 트리는 사이트당 하나이고 편집 Sandbox
|
|
692
716
|
// (격리된 컨테이너)가 dev 서버로 띄운다. 편집 환경은 플랫폼 전체 동시 상한이 있고 15분 동안 활동이 없으면 잠든다(작업
|
|
693
717
|
// 트리는 보존된다). 버전 만들기는 그 시점의 작업 트리를 고정해 일회용 빌드 환경에서 규칙 검증 → 타입 검사 → 빌드를
|
|
694
718
|
// 하고, 통과하면 발행할 수 있는 `READY` 버전이 된다. 에이전트의 파일 쓰기·검사·버전 만들기는 승인 없이 되고, 발행·
|
|
695
|
-
// 되돌리기·공개 전환은 기존대로 승인이다. 편집 미리보기 링크는
|
|
719
|
+
// 되돌리기·공개 전환은 기존대로 승인이다. 편집 미리보기 링크는 상점 플랫폼 OWNER·ADMIN만 받는다(에이전트에게 주지 않는다).
|
|
696
720
|
|
|
697
721
|
/** 플랫폼 전체 동시 편집 환경 상한 */
|
|
698
722
|
export const STOREFRONT_EDIT_CAPACITY = 20;
|
|
@@ -785,7 +809,7 @@ export const storefrontEditSessionStatusSchema = z
|
|
|
785
809
|
.enum(["STARTING", "RUNNING"])
|
|
786
810
|
.describe("`STARTING` 띄우는 중 · `RUNNING` 사용 중");
|
|
787
811
|
|
|
788
|
-
/** 편집 환경을 띄우는 단계 —
|
|
812
|
+
/** 편집 환경을 띄우는 단계 — 상점 플랫폼 진행 표시용이다. 값은 늘어날 수 있다 */
|
|
789
813
|
export const STOREFRONT_EDIT_START_STAGES = ["CONTAINER", "RESTORE", "DEV_SERVER"] as const;
|
|
790
814
|
|
|
791
815
|
export const storefrontEditSessionSchema = z.object({
|
|
@@ -1224,7 +1248,7 @@ export const storefrontVersionCreatedSchema = z.object({
|
|
|
1224
1248
|
export type StorefrontVersionCreated = z.infer<typeof storefrontVersionCreatedSchema>;
|
|
1225
1249
|
|
|
1226
1250
|
/**
|
|
1227
|
-
* `POST /v1/storefront/site/editing/preview-link` — 편집 미리보기(dev 서버) 링크.
|
|
1251
|
+
* `POST /v1/storefront/site/editing/preview-link` — 편집 미리보기(dev 서버) 링크. 상점 플랫폼에서 로그인한 OWNER·ADMIN만
|
|
1228
1252
|
* 받는다(PAT·에이전트 불가). 5분 안에 열면 그 브라우저에 미리보기 호스트 전용 쿠키(12시간)가 생긴다. 편집 환경이
|
|
1229
1253
|
* 없으면 409 `EDIT_SESSION_REQUIRED`다(편집 환경이 `STARTING`이면 링크는 주고, 미리보기 화면이 기동 대기를 보여 준다).
|
|
1230
1254
|
*/
|
|
@@ -1234,7 +1258,7 @@ export type CreateStorefrontEditPreviewLinkRequest = CreateStorefrontPreviewLink
|
|
|
1234
1258
|
|
|
1235
1259
|
// ─── 소스 내려받기·올리기 (#60, `sayren` CLI) ─────────────────────────────
|
|
1236
1260
|
//
|
|
1237
|
-
// 셀러가 사이트 작업 트리를 로컬로 받아(`npx sayren pull`) 고치고 되돌려 놓는다(`npx sayren deploy`).
|
|
1261
|
+
// 셀러가 사이트 작업 트리를 로컬로 받아(`npx sayren pull`) 고치고 되돌려 놓는다(`npx sayren deploy`). 상점 플랫폼·AI 편집과 같은
|
|
1238
1262
|
// 작업 트리다. 올리는 것은 소스뿐이고 빌드는 서버가 한다(버전 만들기). 흐름:
|
|
1239
1263
|
// 1. `GET source` — 지금 작업 트리를 고정하고(`sourceRef`) 압축 파일 크기를 알린다
|
|
1240
1264
|
// 2. `GET source/archive?sourceRef=&part=` — 압축 파일(tar.gz)을 4MiB씩 받는다
|
|
@@ -1258,7 +1282,7 @@ export const storefrontSourceRefSchema = z
|
|
|
1258
1282
|
export const storefrontSourceSchema = z.object({
|
|
1259
1283
|
siteId: z.string(),
|
|
1260
1284
|
storeId: z.string(),
|
|
1261
|
-
storeCode: z.string().describe("
|
|
1285
|
+
storeCode: z.string().describe("상점 코드 — 로컬 개발 서버의 `SAYREN_STORE_CODE`"),
|
|
1262
1286
|
sourceRef: storefrontSourceRefSchema,
|
|
1263
1287
|
treeSha256: z.string().describe("트리 지문 (경로 오름차순 `{path}\\t{sha256}\\n`의 SHA-256)"),
|
|
1264
1288
|
files: z.number().int().min(0).describe("파일 수"),
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 요금제·구독 — `GET /v1/store/subscription`(스코프 `store:r`). 구독 단위는 상점이다.
|
|
5
|
+
* 결제 수단·청구 금액은 싣지 않는다. 요금제 변경은 상점 플랫폼에서만 한다.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** 요금제 코드 — 이름(`plan.name`)이 바뀌어도 코드는 바뀌지 않는다. 값은 추가만 한다 */
|
|
9
|
+
export const planCodeSchema = z
|
|
10
|
+
.enum(["BETA", "FREE", "BASIC", "PRO", "BUSINESS", "ENTERPRISE", "PREMIUM"])
|
|
11
|
+
.describe(
|
|
12
|
+
"요금제 코드. `FREE`=Free, `PRO`=Pro, `PREMIUM`=프리미엄, `ENTERPRISE`=엔터프라이즈, `BETA`=베타(운영 오픈 기간). `BASIC`·`BUSINESS`는 2026-09-30 이전 요금제(더 쓰지 않는다)",
|
|
13
|
+
);
|
|
14
|
+
export type PlanCode = z.infer<typeof planCodeSchema>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 응답의 요금제 코드 — 아는 코드(`PlanCode`)거나 이 SDK가 모르는 새 코드(문자열)다. 요금제 코드는 추가될 수 있어 응답 파싱을 깨지 않게
|
|
18
|
+
* 넓게 받는다(옛 SDK는 새 코드를 모르면 파싱에 실패했다, #44 리뷰 M3). 요청에는 `planCodeSchema`를 쓴다
|
|
19
|
+
*/
|
|
20
|
+
export const responsePlanCodeSchema = z
|
|
21
|
+
.union([planCodeSchema, z.string()])
|
|
22
|
+
.describe(planCodeSchema.description ?? "요금제 코드");
|
|
23
|
+
export type ResponsePlanCode = PlanCode | (string & {});
|
|
24
|
+
|
|
25
|
+
export const subscriptionStatusSchema = z
|
|
26
|
+
.enum(["ACTIVE", "PAST_DUE", "CANCELED", "INCOMPLETE"])
|
|
27
|
+
.describe(
|
|
28
|
+
"구독 상태. `ACTIVE`=이용 중, `PAST_DUE`=결제 실패 후 유예 중, `CANCELED`=해지됨, `INCOMPLETE`=첫 결제 대기",
|
|
29
|
+
);
|
|
30
|
+
export type SubscriptionStatus = z.infer<typeof subscriptionStatusSchema>;
|
|
31
|
+
|
|
32
|
+
const limit = (description: string) =>
|
|
33
|
+
z.number().int().nullable().describe(`${description}. null이면 한도 없음`);
|
|
34
|
+
|
|
35
|
+
export const subscriptionLimitsSchema = z.object({
|
|
36
|
+
liveOrdersMonthly: limit("더 쓰지 않는다 — 라이브 주문 수로 요금제를 나누지 않아 항상 null"),
|
|
37
|
+
seatsIncluded: limit("구성원 석 — 구성원은 모든 요금제에서 무료·무제한이라 null"),
|
|
38
|
+
aiActionsMonthly: limit("MCP(에이전트) 쓰기(AI 작업) 월 한도"),
|
|
39
|
+
aiReadsMonthly: limit("MCP(에이전트) 읽기 월 한도"),
|
|
40
|
+
customDomains: limit("내 도메인 수"),
|
|
41
|
+
webhooks: limit("웹훅 구독 수(0이면 쓸 수 없다)"),
|
|
42
|
+
socialProviders: limit("더 쓰지 않는다 — 소셜 로그인 공급자 수로 요금제를 나누지 않아 항상 null"),
|
|
43
|
+
emailsMonthly: limit("더 쓰지 않는다 — 메일 한도가 없어 항상 null"),
|
|
44
|
+
storefrontRequestsMonthly: limit("스토어프론트 월 트래픽(요청 수, 넘어도 막지 않고 안내만)"),
|
|
45
|
+
apiLogRetentionDays: limit("API 로그 보관 일수(0이면 API 로그 없음)"),
|
|
46
|
+
auditLogRetentionDays: limit("감사 로그 보관 일수"),
|
|
47
|
+
storefrontPreviewHoursMonthly: limit("스토어프론트 편집 미리보기 월 시간"),
|
|
48
|
+
storefrontBuildMinutesMonthly: limit("스토어프론트 빌드 월 분"),
|
|
49
|
+
adminApiRequestsMonthly: limit("관리 API 요청 월 포함량(넘으면 속도만 낮춘다, 주문 처리 제외)"),
|
|
50
|
+
});
|
|
51
|
+
export type SubscriptionLimits = z.infer<typeof subscriptionLimitsSchema>;
|
|
52
|
+
|
|
53
|
+
export const subscriptionUsageSchema = z.object({
|
|
54
|
+
liveOrders: z.number().int().describe("이번 기간 라이브 주문 수(테스트 결제 주문 제외)"),
|
|
55
|
+
aiActions: z.number().int().describe("이번 기간 MCP(에이전트) 쓰기(AI 작업) 수"),
|
|
56
|
+
aiReads: z.number().int().describe("이번 기간 MCP(에이전트) 읽기 수"),
|
|
57
|
+
storefrontRequests: z.number().int().describe("이번 기간 스토어프론트 요청 수"),
|
|
58
|
+
adminApiRequests: z
|
|
59
|
+
.number()
|
|
60
|
+
.int()
|
|
61
|
+
.describe("이번 기간 관리 API 요청 수(API 토큰·앱·에이전트 호출, 상점 플랫폼 제외)"),
|
|
62
|
+
emails: z.number().int().describe("이번 기간 초대·양도 알림 메일 수(한도 없음, 표시만)"),
|
|
63
|
+
storefrontPreviewMinutes: z.number().int().describe("이번 기간 편집 미리보기 분(끝난 세션)"),
|
|
64
|
+
storefrontBuildMinutes: z.number().int().describe("이번 기간 빌드 분(끝난 빌드)"),
|
|
65
|
+
seats: z.number().int().describe("현재 석 수 — 구성원(OWNER 포함) + 대기 중 초대"),
|
|
66
|
+
customDomains: z.number().int().describe("현재 내 도메인 수(해제한 도메인 제외)"),
|
|
67
|
+
webhooks: z.number().int().describe("현재 웹훅 구독 수"),
|
|
68
|
+
});
|
|
69
|
+
export type SubscriptionUsage = z.infer<typeof subscriptionUsageSchema>;
|
|
70
|
+
|
|
71
|
+
export const storeSubscriptionSchema = z.object({
|
|
72
|
+
plan: z.object({
|
|
73
|
+
code: responsePlanCodeSchema,
|
|
74
|
+
name: z.string().describe("요금제 표시 이름(예: 프로)"),
|
|
75
|
+
}),
|
|
76
|
+
status: subscriptionStatusSchema,
|
|
77
|
+
interval: z
|
|
78
|
+
.enum(["MONTH"])
|
|
79
|
+
.nullable()
|
|
80
|
+
.describe("결제 주기. `MONTH`=월간. 요금을 내지 않는 요금제(베타·무료)는 null"),
|
|
81
|
+
cancelAtPeriodEnd: z.boolean().describe("기간 끝에 해지 예정인가"),
|
|
82
|
+
period: z
|
|
83
|
+
.object({
|
|
84
|
+
start: z.string().describe("사용량 기간 시작(ISO 8601)"),
|
|
85
|
+
end: z.string().describe("사용량 기간 끝(ISO 8601, 이 시각에 사용량이 초기화된다)"),
|
|
86
|
+
})
|
|
87
|
+
.describe("사용량을 세는 기간. 요금을 내지 않는 요금제는 한국 시간 달력 월이다"),
|
|
88
|
+
limits: subscriptionLimitsSchema,
|
|
89
|
+
features: z.object({
|
|
90
|
+
adminApi: z.boolean().describe("관리 API(개인 액세스 토큰·외부 앱 토큰 호출)를 쓸 수 있다"),
|
|
91
|
+
analytics: z.boolean().describe("애널리틱스 조회를 쓸 수 있다"),
|
|
92
|
+
storeClone: z.boolean().describe("더 쓰지 않는다 — 상점 복제 기능을 없애 항상 false"),
|
|
93
|
+
removeBadge: z.boolean().describe("더 쓰지 않는다 — 배지 키를 없애 항상 false"),
|
|
94
|
+
}),
|
|
95
|
+
usage: subscriptionUsageSchema,
|
|
96
|
+
aiActions: z
|
|
97
|
+
.object({
|
|
98
|
+
limit: z.number().int().nullable(),
|
|
99
|
+
used: z.number().int(),
|
|
100
|
+
resetsAt: z.string().describe("한도가 초기화되는 시각(ISO 8601)"),
|
|
101
|
+
})
|
|
102
|
+
.describe("AI 작업(MCP 쓰기) 한도 요약 — 에이전트가 남은 작업 수를 판단할 때 쓴다"),
|
|
103
|
+
aiReads: z
|
|
104
|
+
.object({
|
|
105
|
+
limit: z.number().int().nullable(),
|
|
106
|
+
used: z.number().int(),
|
|
107
|
+
resetsAt: z.string().describe("한도가 초기화되는 시각(ISO 8601)"),
|
|
108
|
+
})
|
|
109
|
+
.describe("MCP 읽기 한도 요약"),
|
|
110
|
+
salesFeePercent: z.literal(0).describe("판매 수수료(%). 모든 요금제 0이다"),
|
|
111
|
+
});
|
|
112
|
+
export type StoreSubscription = z.infer<typeof storeSubscriptionSchema>;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 요금제 한도 오류의 `error.details` — 기존 오류 코드(`STORE_LIMIT_EXCEEDED`·`STOREFRONT_DOMAIN_LIMIT`·`WEBHOOK_LIMIT_EXCEEDED` 등)와
|
|
116
|
+
* 요금제 오류(`PLAN_LIMIT_EXCEEDED`·`PLAN_AI_ACTIONS_EXCEEDED`·`PLAN_AI_READS_EXCEEDED`·`PLAN_FEATURE_REQUIRED`·`PLAN_API_QUOTA_EXCEEDED`)가 같은 형식을 싣는다.
|
|
117
|
+
* 모든 필드가 선택이다(기존 코드는 요금제 판정 전 필드만 실을 수 있다).
|
|
118
|
+
*/
|
|
119
|
+
export const planErrorDetailsSchema = z
|
|
120
|
+
.object({
|
|
121
|
+
feature: z.string().optional().describe("한도에 닿은 기능 키(예: `limits.ai_actions_monthly`)"),
|
|
122
|
+
limit: z.number().nullable().optional(),
|
|
123
|
+
used: z.number().optional(),
|
|
124
|
+
plan: responsePlanCodeSchema.optional(),
|
|
125
|
+
resetsAt: z.string().optional().describe("월 한도가 초기화되는 시각(ISO 8601)"),
|
|
126
|
+
upgradeUrl: z.string().optional().describe("요금제 화면 주소(상점 플랫폼)"),
|
|
127
|
+
reason: z
|
|
128
|
+
.string()
|
|
129
|
+
.optional()
|
|
130
|
+
.describe(
|
|
131
|
+
"상점 수 한도의 이유 — `OWNED_STORES`(소유 상점 수). `FREE_STORES`는 더 쓰지 않는다",
|
|
132
|
+
),
|
|
133
|
+
})
|
|
134
|
+
.loose();
|
|
135
|
+
export type PlanErrorDetails = z.infer<typeof planErrorDetailsSchema>;
|
|
136
|
+
|
|
137
|
+
const limitOrContract = (description: string) =>
|
|
138
|
+
z.number().int().nullable().describe(`${description}. null이면 계약(엔터프라이즈)·한도 없음`);
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 요금제 기능 키 값 — 요금제 비교표·상점 플랫폼 요금제 화면이 쓴다. 키는 서버 판정의 기능 키와 같다(추가만 한다). 상위 요금제는 하위 요금제
|
|
142
|
+
* 기능을 모두 포함한다
|
|
143
|
+
*/
|
|
144
|
+
export const planEntitlementsSchema = z
|
|
145
|
+
.object({
|
|
146
|
+
"features.admin_api": z
|
|
147
|
+
.boolean()
|
|
148
|
+
.describe(
|
|
149
|
+
"관리 API(개인 액세스 토큰·외부 앱 토큰 호출). 상점 플랫폼·CLI·MCP는 모든 요금제에서 쓴다",
|
|
150
|
+
),
|
|
151
|
+
"features.analytics": z.boolean().describe("애널리틱스 화면·조회 API"),
|
|
152
|
+
"limits.ai_reads_monthly": limitOrContract("MCP 읽기 월 한도"),
|
|
153
|
+
"limits.ai_actions_monthly": limitOrContract(
|
|
154
|
+
"MCP 쓰기(AI 작업) 월 한도 — 주문 처리는 막지 않는다",
|
|
155
|
+
),
|
|
156
|
+
"limits.custom_domains": limitOrContract("내 도메인 수"),
|
|
157
|
+
"limits.webhooks": limitOrContract("웹훅 구독 수(0이면 쓸 수 없다)"),
|
|
158
|
+
"limits.storefront_preview_hours_monthly": limitOrContract("편집 환경 시간(월, 시간)"),
|
|
159
|
+
"limits.storefront_build_minutes_monthly": limitOrContract("빌드 시간(월, 분)"),
|
|
160
|
+
"limits.storefront_requests_monthly": limitOrContract(
|
|
161
|
+
"스토어프론트 월 트래픽(요청 수) — 넘어도 막지 않고 안내만 한다",
|
|
162
|
+
),
|
|
163
|
+
"limits.admin_api_requests_monthly": limitOrContract(
|
|
164
|
+
"관리 API 월 요청 — 넘으면 속도만 낮춘다(주문 처리 제외)",
|
|
165
|
+
),
|
|
166
|
+
"rate_limit.admin_api": z
|
|
167
|
+
.number()
|
|
168
|
+
.nullable()
|
|
169
|
+
.describe("관리 API 분당 속도 배수(기본 분당 600 × 배수)"),
|
|
170
|
+
"storefront.cpu_ms": z.number().int().describe("스토어프론트 사이트 요청당 CPU 상한(ms)"),
|
|
171
|
+
"storefront.subrequests": z
|
|
172
|
+
.number()
|
|
173
|
+
.int()
|
|
174
|
+
.describe("스토어프론트 사이트 요청당 하위 요청 상한"),
|
|
175
|
+
"storefront.rate_limit_per_minute": z
|
|
176
|
+
.number()
|
|
177
|
+
.int()
|
|
178
|
+
.describe("스토어프론트 남용 방지 요청 속도 상한(사이트당 분당)"),
|
|
179
|
+
"seats.included": limitOrContract(
|
|
180
|
+
"포함 구성원 석 — 구성원은 모든 요금제에서 무료·무제한이라 null",
|
|
181
|
+
),
|
|
182
|
+
"retention.analytics_days": z.number().int().describe("애널리틱스 보관 일수"),
|
|
183
|
+
"retention.api_logs_days": limitOrContract("API 로그 보관 일수(0이면 API 로그 없음)"),
|
|
184
|
+
"retention.audit_days": limitOrContract("감사 로그 보관 일수"),
|
|
185
|
+
"support.tier": z
|
|
186
|
+
.union([z.enum(["COMMUNITY", "EMAIL", "EMAIL_PRIORITY", "DEDICATED", "SLA"]), z.string()])
|
|
187
|
+
.describe(
|
|
188
|
+
"지원 등급(`COMMUNITY`·`EMAIL`·`EMAIL_PRIORITY`·`DEDICATED`·`SLA`, 새 등급이 더해질 수 있다)",
|
|
189
|
+
),
|
|
190
|
+
})
|
|
191
|
+
.describe("요금제 기능 키 값");
|
|
192
|
+
export type PlanEntitlementValues = z.infer<typeof planEntitlementsSchema>;
|
|
193
|
+
|
|
194
|
+
/** 공개 요금제 한 줄 — `GET /v1/public/plans` */
|
|
195
|
+
export const publicPlanSchema = z.object({
|
|
196
|
+
code: responsePlanCodeSchema,
|
|
197
|
+
name: z.string().describe("표시 이름(Free·Pro·프리미엄·엔터프라이즈)"),
|
|
198
|
+
version: z.number().int().describe("요금제 버전 — 값이 바뀌면 새 버전이 게시된다"),
|
|
199
|
+
sort: z.number().int(),
|
|
200
|
+
purchasable: z
|
|
201
|
+
.boolean()
|
|
202
|
+
.describe("셀프 결제로 고를 수 있는가(Free·엔터프라이즈는 아니다 — 엔터프라이즈는 계약)"),
|
|
203
|
+
prices: z.object({
|
|
204
|
+
currency: z.literal("KRW"),
|
|
205
|
+
monthly: z.number().int().nullable().describe("월 가격(공급가, 부가세 별도)"),
|
|
206
|
+
startingAt: z.boolean().describe("「부터」 가격(엔터프라이즈 계약)"),
|
|
207
|
+
vatIncluded: z
|
|
208
|
+
.object({
|
|
209
|
+
monthly: z.number().int().nullable(),
|
|
210
|
+
})
|
|
211
|
+
.describe("표시 가격(부가세 포함) — 요금제 비교표는 이 값을 보인다"),
|
|
212
|
+
}),
|
|
213
|
+
entitlements: planEntitlementsSchema,
|
|
214
|
+
});
|
|
215
|
+
export type PublicPlan = z.infer<typeof publicPlanSchema>;
|
|
216
|
+
|
|
217
|
+
/** 요금제 비교표 — `GET /v1/public/plans`(인증 없음, 캐시 가능). 값은 서버가 관리하고 언제든 바뀔 수 있다 */
|
|
218
|
+
export const publicPlansSchema = z.object({
|
|
219
|
+
vatPercent: z.number().int().describe("부가세율(%)"),
|
|
220
|
+
plans: z.array(publicPlanSchema).describe("공개 요금제의 지금 버전 — 정렬 순"),
|
|
221
|
+
});
|
|
222
|
+
export type PublicPlans = z.infer<typeof publicPlansSchema>;
|
package/src/schemas/telemetry.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { z } from "zod";
|
|
|
8
8
|
* 세는 것이다. 그 숫자가 규칙과 안내를 고치는 근거가 된다.
|
|
9
9
|
*
|
|
10
10
|
* **보내지 않는 것**: 파일 경로·소스 코드·위반 상세(`detail`)·토큰·사람 식별자. 규칙 id와 개수뿐이다.
|
|
11
|
-
* 테넌트는 요청 토큰이 정한다(본문에
|
|
11
|
+
* 테넌트는 요청 토큰이 정한다(본문에 상점 id가 없다). 옵트아웃은 클라이언트의 `SAYREN_TELEMETRY=0`이다.
|
|
12
12
|
*
|
|
13
13
|
* 알 수 없는 키는 저장하지 않고 버린다(`.strict()`를 쓰지 않는다) — 새 버전 클라이언트가 필드를
|
|
14
14
|
* 하나 더 실었다고 계측 전체가 400으로 날아가면 그 버전의 숫자가 통째로 빈다.
|
|
@@ -64,7 +64,7 @@ export const mcpToolEventSchema = z.object({
|
|
|
64
64
|
results: z
|
|
65
65
|
.array(mcpRuleResultSchema)
|
|
66
66
|
.max(MCP_TELEMETRY_MAX_RESULTS)
|
|
67
|
-
// 규칙 하나당 한 줄이다. 같은 id를 여러 줄로 실으면 집계가 한
|
|
67
|
+
// 규칙 하나당 한 줄이다. 같은 id를 여러 줄로 실으면 집계가 한 상점의 한 규칙을 몇 번 세야
|
|
68
68
|
// 하는지 정할 수 없고, 상한만큼 같은 규칙을 반복해 보내는 것이 저장을 부풀리는 길이 된다.
|
|
69
69
|
.refine((results) => new Set(results.map((result) => result.ruleId)).size === results.length, {
|
|
70
70
|
message: "같은 규칙 id를 두 번 실을 수 없습니다",
|
package/src/schemas/upload.ts
CHANGED
|
@@ -28,7 +28,7 @@ export {
|
|
|
28
28
|
const envelopeFields = {
|
|
29
29
|
id: z.string().describe("이벤트 ID (`evt_…`, 테스트 전송은 `evt_test_…`). 중복 제거 키"),
|
|
30
30
|
createdAt: z.string().describe("이벤트 발생 일시 (ISO 8601)"),
|
|
31
|
-
storeId: z.string().describe("이벤트가 발생한
|
|
31
|
+
storeId: z.string().describe("이벤트가 발생한 상점 ID"),
|
|
32
32
|
sequence: z
|
|
33
33
|
.number()
|
|
34
34
|
.int()
|