@sayren/store-sdk 0.39.0 → 0.41.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 +498 -401
- package/dist/index.mjs +62 -356
- package/dist/server.d.mts +2 -2
- package/dist/server.mjs +2 -2
- package/dist/webhook-headers-D974dE4T.mjs +359 -0
- package/package.json +1 -1
- package/src/client.ts +17 -7
- package/src/http.ts +8 -1
- package/src/index.ts +2 -0
- package/src/member-delegation.ts +21 -17
- package/src/schemas/ai.ts +62 -10
- package/src/schemas/payment.ts +1 -1
- package/src/schemas/storefront-hosting.ts +6 -3
- package/src/session.ts +8 -5
- package/dist/webhook-headers-ap_exsb9.mjs +0 -22
package/src/schemas/ai.ts
CHANGED
|
@@ -96,9 +96,15 @@ export const aiActivitySchema = z.enum([
|
|
|
96
96
|
]);
|
|
97
97
|
export type AiActivity = z.infer<typeof aiActivitySchema>;
|
|
98
98
|
|
|
99
|
+
/** 바뀐 줄 미리보기 한 줄의 최대 길이 — 서버가 이 길이로 자르고 화면은 이보다 긴 줄을 앞부분으로 비교한다 */
|
|
100
|
+
export const AI_TOOL_CHANGE_LINE_MAX = 300;
|
|
101
|
+
/** 바뀐 줄 범위(`lines`) 개수 상한 */
|
|
102
|
+
export const AI_TOOL_CHANGE_RANGES_MAX = 200;
|
|
103
|
+
|
|
99
104
|
/**
|
|
100
105
|
* 도구가 바꾼 파일 하나(2026-10-13, AI 상점 도우미 작업 공간의 수정 중인 코드) — 서버가 도구 입력으로 계산한다.
|
|
101
|
-
* `edit`는 바꾼 부분의 줄 비교, `write`는 새로 쓴 내용(이전 내용을 모르므로 모두 추가로 센다). `preview`는 바뀐 줄 앞부분 12
|
|
106
|
+
* `edit`는 바꾼 부분의 줄 비교, `write`는 새로 쓴 내용(이전 내용을 모르므로 모두 추가로 센다). `preview`는 바뀐 줄 앞부분 12줄까지다.
|
|
107
|
+
* `lines`는 바꾼 뒤 파일에서 추가된 줄의 범위(1부터, 양끝 포함)다 — `edit`가 성공한 뒤에만 실행기가 채운다(옛 기록·`write`에는 없다)
|
|
102
108
|
*/
|
|
103
109
|
export const aiToolChangeSchema = z.object({
|
|
104
110
|
path: z.string().max(300),
|
|
@@ -106,8 +112,17 @@ export const aiToolChangeSchema = z.object({
|
|
|
106
112
|
added: z.number().int().min(0),
|
|
107
113
|
removed: z.number().int().min(0),
|
|
108
114
|
preview: z
|
|
109
|
-
.array(
|
|
115
|
+
.array(
|
|
116
|
+
z.object({
|
|
117
|
+
kind: z.enum(["added", "removed"]),
|
|
118
|
+
text: z.string().max(AI_TOOL_CHANGE_LINE_MAX),
|
|
119
|
+
}),
|
|
120
|
+
)
|
|
110
121
|
.max(12),
|
|
122
|
+
lines: z
|
|
123
|
+
.array(z.tuple([z.number().int().min(1), z.number().int().min(1)]))
|
|
124
|
+
.max(AI_TOOL_CHANGE_RANGES_MAX)
|
|
125
|
+
.optional(),
|
|
111
126
|
});
|
|
112
127
|
export type AiToolChange = z.infer<typeof aiToolChangeSchema>;
|
|
113
128
|
|
|
@@ -127,28 +142,49 @@ export const aiAgentPhaseSchema = z.enum(AI_AGENT_PHASES);
|
|
|
127
142
|
export type AiAgentPhase = z.infer<typeof aiAgentPhaseSchema>;
|
|
128
143
|
|
|
129
144
|
/**
|
|
130
|
-
* AI 상점 도우미의 작업 메모 폴더(2026-10-14) — 회차마다 읽는 숨은 규칙(`rules.md`)과 디자인 계획(`design.md`).
|
|
131
|
-
*
|
|
145
|
+
* AI 상점 도우미의 작업 메모 폴더(2026-10-14) — 회차마다 읽는 숨은 규칙(`rules.md`)과 디자인 계획(`design.md`). 편집 Sandbox가 점(.)으로 시작하는
|
|
146
|
+
* 경로를 받지 않아 작업 트리의 `src` 아래에 둔다(앱이 import하지 않는 문서라 빌드에 들어가지 않는다). 숨은 규칙은 셀러 화면(타임라인·코드 탭·변경 기록)에
|
|
147
|
+
* 보이지 않는다. 디자인 계획은 2026-10-24부터 셀러 문서(작업 공간 문서 탭의 「디자인 가이드라인」)라 숨기지 않는다(`AI_DESIGN_GUIDE_PATH`)
|
|
132
148
|
*/
|
|
133
149
|
export const AI_AGENT_NOTES_DIR = "src/site/agent";
|
|
150
|
+
/** 디자인 가이드라인(디자인 계획) — 작업 메모 폴더에 있지만 셀러가 읽는 문서다 */
|
|
151
|
+
export const AI_DESIGN_GUIDE_PATH = `${AI_AGENT_NOTES_DIR}/design.md`;
|
|
152
|
+
/** 사이트맵 — 에이전트가 쓰는 화면 구성 문서(`{version:1,pages:[…]}`) */
|
|
153
|
+
export const AI_SITEMAP_PATH = "src/site/sitemap.json";
|
|
154
|
+
|
|
155
|
+
/** 작업 공간 문서 탭의 문서 파일인가(사이트맵·디자인 가이드라인) — 앱 코드가 아니라서 코드 탭·미리보기 점검·코드 변경 판정에서 뺀다 */
|
|
156
|
+
export function isAiWorkspaceDocPath(path: string): boolean {
|
|
157
|
+
return path === AI_SITEMAP_PATH || path === AI_DESIGN_GUIDE_PATH;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** 「이어서 진행」 버튼(실패한 실행 잇기)이 보내는 선택 값 — 선택지 답처럼 같은 할 일 목록을 잇는다 */
|
|
161
|
+
export const AI_RUN_RESUME_CHOICE = "run:resume";
|
|
134
162
|
|
|
135
163
|
/**
|
|
136
164
|
* 에이전트의 할 일 목록(2026-10-14, Claude Code의 TaskCreate·TaskUpdate·TaskList와 같다) — 에이전트가 패널 도구로 할 일을 만들고 상태를 바꾼다.
|
|
137
|
-
* 이벤트·조각은 그 시점의 목록 전체이고 작업 공간
|
|
165
|
+
* 이벤트·조각은 그 시점의 목록 전체이고 작업 공간 로드맵 탭이 마지막 목록을 칸반으로 그린다. 진행중은 한 번에 하나다.
|
|
166
|
+
* 목록은 셀러 요청 하나(사이클)의 일이다 — 새 요청이면 빈 목록에서 시작하고 선택지 답이면 잇는다(2026-10-24). 개수 상한은 없다
|
|
167
|
+
* (옛 30개 상한은 대화 전체에 할 일이 쌓여 넘쳤다 — 크기는 사이클 범위가, 폭주는 턴 예산이 막는다).
|
|
168
|
+
* `blocked`는 같은 문제를 거듭 고쳐도 남아 멈춘 일이고 `note`가 그 사유다(이어 가기 리마인더가 남은 일로 세지 않는다)
|
|
138
169
|
*/
|
|
139
|
-
export const AI_TODO_STATUSES = ["pending", "in_progress", "completed"] as const;
|
|
170
|
+
export const AI_TODO_STATUSES = ["pending", "in_progress", "completed", "blocked"] as const;
|
|
140
171
|
export const aiTodoSchema = z.object({
|
|
141
|
-
/** 할 일 번호(
|
|
172
|
+
/** 할 일 번호(사이클 안에서 1부터) — 옛 조각에는 없다 */
|
|
142
173
|
id: z.string().max(10).optional(),
|
|
143
174
|
content: z.string().min(1).max(200),
|
|
144
175
|
status: z.enum(AI_TODO_STATUSES),
|
|
145
176
|
/** 이 할 일이 속한 작업 단계 — 진행중이 되면 실행기가 단계를 알린다(2026-10-14, 모델이 단계 알림을 빠뜨렸다) */
|
|
146
177
|
phase: aiAgentPhaseSchema.optional(),
|
|
178
|
+
/** 막힌 사유(`blocked`일 때만) */
|
|
179
|
+
note: z.string().max(300).optional(),
|
|
147
180
|
});
|
|
148
181
|
export type AiTodo = z.infer<typeof aiTodoSchema>;
|
|
182
|
+
/** @deprecated 할 일 개수 상한은 없앴다(2026-10-24). 공개 export라 값만 남긴다 */
|
|
149
183
|
export const AI_TODOS_MAX = 30;
|
|
150
184
|
|
|
185
|
+
/** 셀러에게 보이지 않는 작업 메모인가 — 작업 메모 폴더에서 디자인 가이드라인을 뺀 나머지다 */
|
|
151
186
|
export function isAiAgentNotesPath(path: string): boolean {
|
|
187
|
+
if (path === AI_DESIGN_GUIDE_PATH) return false;
|
|
152
188
|
return path === AI_AGENT_NOTES_DIR || path.startsWith(`${AI_AGENT_NOTES_DIR}/`);
|
|
153
189
|
}
|
|
154
190
|
|
|
@@ -260,7 +296,12 @@ export const AI_DESIGN_CONFIRM_APPROVE = "design:approve";
|
|
|
260
296
|
* - `todos`: 에이전트의 할 일 목록 전체(작업 공간의 체크리스트)
|
|
261
297
|
*/
|
|
262
298
|
export const aiMessagePartSchema = z.discriminatedUnion("type", [
|
|
263
|
-
z.object({
|
|
299
|
+
z.object({
|
|
300
|
+
type: z.literal("text"),
|
|
301
|
+
text: z.string(),
|
|
302
|
+
/** 셀러가 선택지 버튼으로 보낸 답의 값(사용자 메시지만) — 할 일 목록(사이클)을 잇는 기준(`isAiChoiceAnswer`) */
|
|
303
|
+
choice: z.string().max(200).optional(),
|
|
304
|
+
}),
|
|
264
305
|
z.object({
|
|
265
306
|
type: z.literal("tool"),
|
|
266
307
|
toolCallId: z.string(),
|
|
@@ -302,7 +343,7 @@ export const aiMessagePartSchema = z.discriminatedUnion("type", [
|
|
|
302
343
|
z.object({ type: z.literal("reasoning"), text: z.string() }),
|
|
303
344
|
z.object({ type: z.literal("phase"), phase: aiAgentPhaseSchema }),
|
|
304
345
|
/** 에이전트의 할 일 목록 — 그 시점의 목록 전체다(가장 최근 것이 지금 목록) */
|
|
305
|
-
z.object({ type: z.literal("todos"), todos: z.array(aiTodoSchema)
|
|
346
|
+
z.object({ type: z.literal("todos"), todos: z.array(aiTodoSchema) }),
|
|
306
347
|
]);
|
|
307
348
|
export type AiMessagePart = z.infer<typeof aiMessagePartSchema>;
|
|
308
349
|
|
|
@@ -506,7 +547,7 @@ export const aiStreamEventSchema = z.discriminatedUnion("type", [
|
|
|
506
547
|
z.object({
|
|
507
548
|
type: z.literal("todos"),
|
|
508
549
|
messageId: z.string(),
|
|
509
|
-
todos: z.array(aiTodoSchema)
|
|
550
|
+
todos: z.array(aiTodoSchema),
|
|
510
551
|
}),
|
|
511
552
|
z.object({
|
|
512
553
|
type: z.literal("tool_start"),
|
|
@@ -527,6 +568,8 @@ export const aiStreamEventSchema = z.discriminatedUnion("type", [
|
|
|
527
568
|
toolCallId: z.string(),
|
|
528
569
|
status: z.enum(["ok", "error", "unknown"]),
|
|
529
570
|
summary: z.string().nullable(),
|
|
571
|
+
/** 결과로 확정한 변경(바뀐 줄 범위 포함) — 있으면 `tool_start`의 `changes`를 바꾼다 */
|
|
572
|
+
changes: z.array(aiToolChangeSchema).max(10).optional(),
|
|
530
573
|
}),
|
|
531
574
|
z.object({
|
|
532
575
|
type: z.literal("choices"),
|
|
@@ -622,3 +665,12 @@ export const storeDraftPreviewSchema = z.object({
|
|
|
622
665
|
used: z.boolean(),
|
|
623
666
|
});
|
|
624
667
|
export type StoreDraftPreview = z.infer<typeof storeDraftPreviewSchema>;
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* 셀러 메시지가 선택지에서 고른 답인가(조각에 `choice`가 있다) — 할 일 목록은 셀러 요청 하나(사이클)의 일이고, 고른 답(선택지·만들기 확인·계획 승인·발행 확인·
|
|
671
|
+
* 「이어서 진행」)은 같은 사이클을 잇고 새로 쓴 글은 새 사이클이다(2026-10-24). api(`currentCycleRows`)와 상점 플랫폼(로드맵)이 이 판정 하나를 쓴다.
|
|
672
|
+
* 직전 답에 선택지가 있었는지로 가르면 선택지를 무시하고 새 요청을 쓴 경우도 옛 사이클에 묶였다
|
|
673
|
+
*/
|
|
674
|
+
export function isAiChoiceAnswer(parts: readonly { type: string; choice?: string }[]): boolean {
|
|
675
|
+
return parts.some((part) => part.type === "text" && Boolean(part.choice));
|
|
676
|
+
}
|
package/src/schemas/payment.ts
CHANGED
|
@@ -513,7 +513,7 @@ export const paymentProviderSchema = z.object({
|
|
|
513
513
|
"지금 새 결제의 후보가 아닌 사유. 후보면 null. 판정 원천은 서버 라우팅이다(상점 플랫폼이 다시 계산하지 않는다). " +
|
|
514
514
|
"후보가 아닌 PG에는 항상 사유 코드를 붙인다(연결 안 된 PG 포함) — 모든 PG는 `candidates`에 있거나 이 값이 null이 아니다. 현재 값: " +
|
|
515
515
|
"`NOT_CONNECTED`(연결 안 됨) · `DISABLED`(사용 중지) · `CREDENTIAL_MISSING`(활성 환경 자격 증명 없음) · " +
|
|
516
|
-
"`SANDBOX_IN_LIVE_STORE`(
|
|
516
|
+
"`SANDBOX_IN_LIVE_STORE`(더는 반환하지 않음 — PG는 각자 쓰인다) · " +
|
|
517
517
|
"`PLATFORM_BLOCKED`(플랫폼이 실결제를 막음) · `MODE_MISMATCH`(라이브를 연 서버 모드와 현재 모드가 다름). " +
|
|
518
518
|
"값이 추가될 수 있어 문자열로 받는다 — 모르는 값은 후보 아님으로 다룬다." +
|
|
519
519
|
" 서버는 항상 싣는다. 이 필드가 없는 이전 서버 응답은 기본값(null)으로 읽는다",
|
|
@@ -25,7 +25,6 @@ import { RESERVED_STORE_CODES } from "./store";
|
|
|
25
25
|
* - 409 `STOREFRONT_NO_ROLLBACK_TARGET` — 되돌릴 이전 발행 버전이 없다
|
|
26
26
|
* - 409 `STOREFRONT_NOT_READY` — 공개 전환: 판매 중인 상품(샘플 제외)이 없다(이슈 #70). 상품을 등록한 뒤 공개한다
|
|
27
27
|
* - 409 `TEMPLATE_CHANGE_NOT_SUPPORTED` — 템플릿 변경(`POST /v1/storefront/site/template`, 폐기)은 항상 이 오류다
|
|
28
|
-
* - 400 `STOREFRONT_SLUG_CONFIRM_MISMATCH` — 호스팅 끄기 확인 입력이 현재 슬러그와 다르다
|
|
29
28
|
* - 503 `STOREFRONT_PREVIEW_NOT_CONFIGURED` — 미리보기 서명 키가 설정되지 않았다
|
|
30
29
|
*
|
|
31
30
|
* 편집(M2b, `/v1/storefront/site/editing`·`files/**`·`checks/**`·`verify`·`builds/**`, 버전 만들기):
|
|
@@ -551,9 +550,13 @@ export const setStorefrontVisibilityRequestSchema = z.object({
|
|
|
551
550
|
});
|
|
552
551
|
export type SetStorefrontVisibilityRequest = z.infer<typeof setStorefrontVisibilityRequestSchema>;
|
|
553
552
|
|
|
554
|
-
/** 호스팅 끄기 — 사이트를 닫고(`CLOSED`) 슬러그를 보류한다. 확인 입력은
|
|
553
|
+
/** 호스팅 끄기 — 사이트를 닫고(`CLOSED`) 슬러그를 보류한다. 확인 입력은 받지 않는다(2026-10-22) */
|
|
555
554
|
export const disableStorefrontHostingRequestSchema = z.object({
|
|
556
|
-
confirmSlug: z
|
|
555
|
+
confirmSlug: z
|
|
556
|
+
.string()
|
|
557
|
+
.max(200)
|
|
558
|
+
.optional()
|
|
559
|
+
.describe("쓰지 않는다 — 보내도 무시한다(옛 확인 입력)"),
|
|
557
560
|
});
|
|
558
561
|
export type DisableStorefrontHostingRequest = z.infer<typeof disableStorefrontHostingRequestSchema>;
|
|
559
562
|
|
package/src/session.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { base64UrlFromBytes } from "./base64";
|
|
2
|
-
import { toApiError, unwrapData } from "./http";
|
|
2
|
+
import { SAYREN_API_BASE_URL, toApiError, unwrapData } from "./http";
|
|
3
3
|
import {
|
|
4
4
|
buildAuthorizeUrl,
|
|
5
5
|
createPkcePair,
|
|
@@ -50,8 +50,8 @@ export interface StoreAuthServer {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export interface StoreSessionOptions {
|
|
53
|
-
/** api 주소(
|
|
54
|
-
baseUrl
|
|
53
|
+
/** api 주소(버전 경로 포함) — `userinfo()`의 `GET /me`에 쓴다. 생략하면 운영 주소(`https://api.sayren.io/v1`) */
|
|
54
|
+
baseUrl?: string;
|
|
55
55
|
/** 인가 서버(accounts) 주소들 */
|
|
56
56
|
authServer: StoreAuthServer;
|
|
57
57
|
/** OAuth public client id (예: avarlabs) */
|
|
@@ -114,9 +114,12 @@ export interface StoreSession {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
export function createStoreSession(options: StoreSessionOptions): StoreSession {
|
|
117
|
-
const {
|
|
117
|
+
const { clientId, storage, authServer } = options;
|
|
118
|
+
const baseUrl = options.baseUrl ?? SAYREN_API_BASE_URL;
|
|
118
119
|
const { revokeUrl, tokenUrl, resource } = authServer;
|
|
119
|
-
|
|
120
|
+
// 주입이 없으면 부를 때마다 지금의 전역 fetch를 쓴다(http.ts와 같다)
|
|
121
|
+
const fetchFn: typeof globalThis.fetch =
|
|
122
|
+
options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
120
123
|
const listeners = new Set<() => void>();
|
|
121
124
|
|
|
122
125
|
function notify(): void {
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
//#region src/webhook-headers.ts
|
|
2
|
-
/** 웹훅 발송 요청 헤더 상수 (공개 계약). zod 의존 없이 서버 엔트리포인트에서도 가볍게 쓴다 */
|
|
3
|
-
/** 서명 헤더. 형식: `t=<unix초>,v1=<hex>[,v1=<hex>]` */
|
|
4
|
-
const WEBHOOK_SIGNATURE_HEADER = "Sayren-Signature";
|
|
5
|
-
/** 이벤트 ID 헤더. 본문 `id`와 같다. 중복 제거 키 */
|
|
6
|
-
const WEBHOOK_EVENT_ID_HEADER = "Sayren-Event-Id";
|
|
7
|
-
/** 이벤트 타입 헤더. 본문 `type`과 같다 */
|
|
8
|
-
const WEBHOOK_EVENT_TYPE_HEADER = "Sayren-Event-Type";
|
|
9
|
-
/** 전송 기록 ID 헤더. 전송 기록의 `deliveryId`와 같다 */
|
|
10
|
-
const WEBHOOK_DELIVERY_ID_HEADER = "Sayren-Delivery-Id";
|
|
11
|
-
/** 발송 요청의 `User-Agent` 값 */
|
|
12
|
-
const WEBHOOK_USER_AGENT = "Sayren-Webhooks/1.0";
|
|
13
|
-
/** 수신 측 권장 타임스탬프 허용 오차(초) */
|
|
14
|
-
const WEBHOOK_DEFAULT_TOLERANCE_SEC = 300;
|
|
15
|
-
const WEBHOOK_HEADERS = {
|
|
16
|
-
signature: WEBHOOK_SIGNATURE_HEADER,
|
|
17
|
-
eventId: WEBHOOK_EVENT_ID_HEADER,
|
|
18
|
-
eventType: WEBHOOK_EVENT_TYPE_HEADER,
|
|
19
|
-
deliveryId: WEBHOOK_DELIVERY_ID_HEADER
|
|
20
|
-
};
|
|
21
|
-
//#endregion
|
|
22
|
-
export { WEBHOOK_HEADERS as a, WEBHOOK_EVENT_TYPE_HEADER as i, WEBHOOK_DELIVERY_ID_HEADER as n, WEBHOOK_SIGNATURE_HEADER as o, WEBHOOK_EVENT_ID_HEADER as r, WEBHOOK_USER_AGENT as s, WEBHOOK_DEFAULT_TOLERANCE_SEC as t };
|