@sayren/store-sdk 0.56.1 → 0.58.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.
@@ -3,10 +3,10 @@ import { RESERVED_STORE_CODES } from "./store";
3
3
  import { strictRequest } from "./strict";
4
4
 
5
5
  /**
6
- * 스토어프론트 호스팅 (이슈 #53, M1: 템플릿 발행) — 관리 API `/v1/storefront/**`의 계약이다.
6
+ * 스토어프론트 호스팅 (이슈 #53) — 관리 API `/v1/storefront/**`의 계약이다.
7
7
  *
8
- * 셀러는 템플릿을 골라 사이트를 만들고 `{slug}.{호스팅 도메인}` 주소를 받는다. 상점을 만들어도 사이트는 생기지 않고
9
- * `POST /v1/storefront/site`가 유일한 생성 경로다. 템플릿은 만들 때만 고르고 바꾸지 않는다. 발행은 버전 단위이고,
8
+ * 셀러는 사이트를 만들고 `{slug}.{호스팅 도메인}` 주소를 받는다. 상점을 만들어도 사이트는 생기지 않고
9
+ * `POST /v1/storefront/site`가 유일한 생성 경로다. 첫 소스는 `initialSource`(빈 작업 트리·시작 코드)다. 발행은 버전 단위이고,
10
10
  * 되돌리기는 이전 버전을 다시 발행하는 것이다(재빌드 없음). 상태의 원천은 api DB이고 Cloudflare 쪽 반영
11
11
  * (라우팅 표·사이트 Worker)은 커밋 뒤 비동기다 — 반영 상태는 `provisioning`으로 본다.
12
12
  *
@@ -14,7 +14,7 @@ import { strictRequest } from "./strict";
14
14
  * 공개 전환·슬러그 변경·호스팅 끄기는 승인(`APPROVAL_REQUIRED`)이다 — 구매자가 보는 화면을 바꾸는 쓰기다.
15
15
  *
16
16
  * 오류 코드:
17
- * - 404 `STOREFRONT_SITE_NOT_FOUND` · `STOREFRONT_TEMPLATE_NOT_FOUND` · `STOREFRONT_VERSION_NOT_FOUND`
17
+ * - 404 `STOREFRONT_SITE_NOT_FOUND` · `STOREFRONT_VERSION_NOT_FOUND`
18
18
  * - 409 `STOREFRONT_SITE_EXISTS` — 이미 사이트가 있다(상점당 1개)
19
19
  * - 409 `STOREFRONT_SLUG_TAKEN` — 다른 사이트가 쓰거나 보류 중인 슬러그다
20
20
  * - 409 `STOREFRONT_SLUG_CHANGE_LIMIT` — 슬러그는 상점당 30일에 3번까지 바꾼다
@@ -25,11 +25,11 @@ import { strictRequest } from "./strict";
25
25
  * - 409 `STOREFRONT_VERSION_NOT_READY` — 빌드가 끝나지 않았거나 실패한 버전은 발행할 수 없다
26
26
  * - 409 `STOREFRONT_NO_ROLLBACK_TARGET` — 되돌릴 이전 발행 버전이 없다
27
27
  * - 409 `STOREFRONT_NOT_READY` — 공개 전환: 판매 중인 상품(샘플 제외)이 없다(이슈 #70). 상품을 등록한 뒤 공개한다
28
- * - 409 `TEMPLATE_CHANGE_NOT_SUPPORTED` — 템플릿 변경(`POST /v1/storefront/site/template`, 폐기)은 항상 이 오류다
29
28
  * - 503 `STOREFRONT_PREVIEW_NOT_CONFIGURED` — 미리보기 서명 키가 설정되지 않았다
29
+ * - 503 `STOREFRONT_STARTER_UNAVAILABLE` — 시작 코드(`initialSource: "STARTER"`)를 아직 쓸 수 없다. 사이트를 만들지 않았다(한도도 세지 않는다). 잠시 뒤 다시 한다
30
30
  *
31
31
  * 편집(M2b, `/v1/storefront/site/editing`·`files/**`·`checks/**`·`verify`·`builds/**`, 버전 만들기):
32
- * - 403 `STOREFRONT_EDITING_DISABLED` — 편집을 쓸 수 없어 셀러 템플릿으로 사이트를 만들 수 없다(사이트 만들기)
32
+ * - 403 `STOREFRONT_EDITING_DISABLED` — 편집을 쓸 수 없어 사이트를 만들 수 없다(사이트 만들기)
33
33
  * - 503 `STOREFRONT_EDITING_NOT_CONFIGURED` — 편집 Sandbox가 설정되지 않았다
34
34
  * - 409 `EDIT_CAPACITY_FULL` — 플랫폼 전체 동시 편집 상한에 걸렸다. `error.details.retryAfterSeconds` 뒤에 다시 한다
35
35
  * - 409 `EDIT_SESSION_REQUIRED` — 편집 미리보기 링크: 편집 환경이 없다
@@ -38,8 +38,9 @@ import { strictRequest } from "./strict";
38
38
  * - 409 `FILE_CHANGED` — 해시 조건이 어긋났다. 아무것도 쓰지 않았다(`error.details.conflicts: [{ path, currentHash }]`)
39
39
  * - 409 `FILE_EXISTS` — 옮길 자리에 파일이 있다(`overwrite: true`로 덮는다)
40
40
  * - 404 `FILE_NOT_FOUND` — 파일이 없다
41
- * - 400 `INVALID_FILE_PATH` — 경로 형식이 틀렸거나 편집할 수 없는 경로다(`node_modules`·`dist`·`.env*` 등). 쓸 수 있는 곳은
42
- * `src/**`·`public/**`와 루트 설정 파일 몇 개이고, `package.json`·`pnpm-lock.yaml`은 읽기만 된다
41
+ * - 400 `INVALID_FILE_PATH` — 경로 형식이 틀렸거나 편집할 수 없는 경로다(`node_modules`·`dist`·`.env*`·`.dev.vars`·`.npmrc`·`.sayren/` 등).
42
+ * 그 밖은 폴더 구조와 무관하게 쓸 수 있고 `package.json`·잠금 파일도 바꾼다(바꾸면 열린 편집 환경이 의존성을 다시 검사해 다시 뜬다).
43
+ * `CLAUDE.md`·`.mcp.json`·`.claude/**`는 읽기만 된다
43
44
  * - 413 `FILE_TOO_LARGE`·`WORKSPACE_TOO_LARGE` — 파일 하나 1MiB, 작업 트리 전체 20MiB·2,000개를 넘는다
44
45
  * - 409 `STOREFRONT_BUILD_IN_PROGRESS` — 이 사이트에 끝나지 않은 빌드가 있다(사이트당 하나)
45
46
  * - 404 `STOREFRONT_BUILD_NOT_FOUND`
@@ -92,16 +93,6 @@ export const RESERVED_STOREFRONT_SLUGS: readonly string[] = [
92
93
  */
93
94
  export const STOREFRONT_EDIT_PREVIEW_SUFFIX = "-preview";
94
95
 
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
-
105
96
  export function isReservedStorefrontSlug(slug: string): boolean {
106
97
  const lower = slug.toLowerCase();
107
98
  return (
@@ -147,94 +138,6 @@ export const STOREFRONT_SLUG_CHANGE_LIMIT = 3;
147
138
  /** 사이트 만들기 한도 — 같은 창(30일) 안의 횟수(상점 단위). 끄고 만들기를 되풀이해 이름을 묶어 두지 못하게 한다 */
148
139
  export const STOREFRONT_SITE_CREATE_LIMIT = 3;
149
140
 
150
- // ─── 템플릿 ───────────────────────────────────────────────
151
-
152
- export const storefrontTemplateSchema = z.object({
153
- templateId: z.string().describe("템플릿 id (예: `basic`)"),
154
- name: z.string().describe("템플릿 이름"),
155
- industry: z.string().describe("업종 (예: `general`·`fashion`·`food`). 목록 필터에 쓴다"),
156
- description: z.string().describe("템플릿 설명"),
157
- thumbnailUrl: z.string().nullable().describe("썸네일 이미지 주소. 없으면 null"),
158
- version: z.string().describe("현재 템플릿 버전. 새 사이트는 이 버전으로 만든다"),
159
- isDefault: z.boolean().describe("기본 템플릿인가 (상점 생성 시 자동 생성에 쓴다)"),
160
- tags: z
161
- .array(z.string())
162
- .default([])
163
- .describe(
164
- "템플릿 특징 태그 (예: `후기 강조`). 목록 카드에 보인다. 옛 서버 응답은 빈 배열로 읽는다",
165
- ),
166
- supportsCheckout: z
167
- .boolean()
168
- .default(true)
169
- .describe(
170
- "주문을 받는 템플릿인가. false는 가격·문의 중심의 카탈로그형이다. 옛 서버 응답은 true로 읽는다",
171
- ),
172
- previewUrl: z
173
- .string()
174
- .nullable()
175
- .default(null)
176
- .describe(
177
- "미리보기 사이트 주소. 공식 템플릿은 연결된 쇼케이스, 템플릿 상점의 템플릿은 그 상점의 사이트가 공개 중일 때만 싣고 그 밖에는 null",
178
- ),
179
- source: z
180
- .enum(["CATALOG", "STORE"])
181
- .default("CATALOG")
182
- .describe(
183
- "`CATALOG` 공식 템플릿 · `STORE` 템플릿 상점의 템플릿(사이트를 만들 때 `copyContent`로 콘텐츠를 복사할 수 있다). 옛 서버 응답은 `CATALOG`로 읽는다",
184
- ),
185
- });
186
- export type StorefrontTemplate = z.infer<typeof storefrontTemplateSchema>;
187
-
188
- // ─── 템플릿 상점 (#123) ────────────────────────────────────
189
-
190
- /**
191
- * 템플릿 상점의 템플릿 정보 — 상점을 만들 때 템플릿 상점으로 고른 상점만 있다. 발행한 스토어프론트가 템플릿이 되고, 이 이름·소개·
192
- * 썸네일로 모든 셀러의 템플릿 목록에 보인다
193
- */
194
- export const storeTemplateSchema = z.object({
195
- templateId: z.string().describe("템플릿 id. 다른 상점이 사이트를 만들 때 이 값을 쓴다"),
196
- name: z.string().describe("템플릿 이름"),
197
- description: z.string().describe("템플릿 소개"),
198
- thumbnailUrl: z.string().nullable().describe("썸네일 이미지 주소. 없으면 null"),
199
- status: z
200
- .enum(["DRAFT", "ACTIVE", "RETIRED"])
201
- .describe(
202
- "`DRAFT` 아직 편집한 스토어프론트를 발행하지 않아 목록에 보이지 않음 · `ACTIVE` 목록에 보임 · `RETIRED` 상점을 닫아 내려감",
203
- ),
204
- version: z
205
- .string()
206
- .nullable()
207
- .describe("목록에 보이는 템플릿 버전. 발행한 스토어프론트 버전에서 정해진다. `DRAFT`면 null"),
208
- });
209
- export type StoreTemplate = z.infer<typeof storeTemplateSchema>;
210
-
211
- /**
212
- * 템플릿 상점의 고정 사업자 정보 필드 — 상점 수정에서 다른 값을 보내면 409 `TEMPLATE_STORE_BUSINESS_LOCKED`다(값은 플랫폼이 정한다)
213
- */
214
- export const TEMPLATE_STORE_LOCKED_BUSINESS_FIELDS = [
215
- "businessName",
216
- "representativeName",
217
- "businessNumber",
218
- "mailOrderSalesNumber",
219
- "customerCenterPhone",
220
- "customerCenterEmail",
221
- ] as const;
222
-
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
- );
236
- export type UpdateStoreTemplateRequest = z.infer<typeof updateStoreTemplateRequestSchema>;
237
-
238
141
  // ─── 버전 ─────────────────────────────────────────────────
239
142
 
240
143
  /**
@@ -251,8 +154,6 @@ export type StorefrontVersionStatus = z.infer<typeof storefrontVersionStatusSche
251
154
  export const storefrontVersionSchema = z.object({
252
155
  versionId: z.string(),
253
156
  number: z.number().int().min(1).describe("사이트 안에서 1부터 늘어나는 번호 (v1, v2…)"),
254
- templateId: z.string().describe("이 버전의 기준 템플릿"),
255
- templateVersion: z.string().describe("이 버전의 기준 템플릿 버전"),
256
157
  status: storefrontVersionStatusSchema,
257
158
  published: z.boolean().describe("지금 발행된 버전인가"),
258
159
  publishedAt: z
@@ -328,10 +229,6 @@ export const storefrontVisibilitySchema = z
328
229
  .describe("`COMING_SOON` 오픈 준비 중(방문자에게 준비 중 페이지) · `PUBLIC` 공개");
329
230
  export type StorefrontVisibility = z.infer<typeof storefrontVisibilitySchema>;
330
231
 
331
- export const storefrontSourceModeSchema = z
332
- .enum(["MANAGED", "CUSTOM"])
333
- .describe("`MANAGED` 템플릿 그대로 · `CUSTOM` 한 번이라도 편집함");
334
-
335
232
  export const storefrontDomainSchema = z.object({
336
233
  hostname: z.string().describe("호스트 이름 (예: `mystore.sayren.co`)"),
337
234
  kind: z.enum(["PLATFORM", "CUSTOM"]).describe("`PLATFORM` 슬러그 주소 · `CUSTOM` 내 도메인"),
@@ -404,8 +301,6 @@ export const storefrontSiteSchema = z.object({
404
301
  hostname: z.string().describe("대표 호스트 이름"),
405
302
  url: z.string().describe("대표 주소 (`https://{hostname}`)"),
406
303
  domains: z.array(storefrontDomainSchema).describe("사용 중·보류 중인 호스트 이름"),
407
- templateId: z.string().describe("지금 발행된 버전의 템플릿 (발행 전이면 만든 템플릿)"),
408
- sourceMode: storefrontSourceModeSchema,
409
304
  status: storefrontSiteStatusSchema,
410
305
  visibility: storefrontVisibilitySchema,
411
306
  publishedVersion: storefrontVersionSchema.nullable().describe("지금 발행된 버전"),
@@ -470,7 +365,7 @@ export const storefrontDeploymentLogSchema = z.object({
470
365
  code: z
471
366
  .string()
472
367
  .describe(
473
- "기록 종류 (`SITE_CREATED`·`ADDRESS_RESERVED`·`WAITING`·`SYNCING`·`VERSION_UPLOADED`·`SYNC_FAILED`·`RETRY_SCHEDULED`·`PUBLISHED`, 템플릿 상점의 템플릿은 `BUILDING`·`BUILD_FAILED`·`CONTENT_COPYING`·`CONTENT_COPIED`·`CONTENT_COPY_FAILED`)",
368
+ "기록 종류 (`SITE_CREATED`·`ADDRESS_RESERVED`·`WAITING`·`SYNCING`·`VERSION_UPLOADED`·`SYNC_FAILED`·`RETRY_SCHEDULED`·`PUBLISHED`, 템플릿 없이 만든 사이트는 `BUILDING`·`BUILD_FAILED`, 템플릿 없이 빈 트리로 만든 사이트의 첫 업로드 전은 `WAITING_SOURCE`)",
474
369
  ),
475
370
  message: z.string().describe("사람이 읽는 문장"),
476
371
  });
@@ -496,32 +391,22 @@ export type StorefrontDeployment = z.infer<typeof storefrontDeploymentSchema>;
496
391
 
497
392
  // ─── 요청 ─────────────────────────────────────────────────
498
393
 
394
+ /** 사이트의 첫 소스(#315) — `NONE` 빈 작업 트리(CLI link) · `STARTER` sayren 시작 코드(웹 시작) */
395
+ export const storefrontInitialSourceSchema = z.enum(["NONE", "STARTER"]);
396
+ export type StorefrontInitialSource = z.infer<typeof storefrontInitialSourceSchema>;
397
+
499
398
  export const createStorefrontSiteRequestSchema = strictRequest(
500
399
  z.object({
501
- templateId: z.string().min(1).max(64).describe("템플릿 id"),
502
- slug: storefrontSlugSchema.optional().describe("주소 슬러그. 생략하면 상점 코드"),
503
- copyContent: z
504
- .boolean()
400
+ initialSource: storefrontInitialSourceSchema
505
401
  .optional()
506
402
  .describe(
507
- "템플릿 상점의 템플릿일 때만 — true면 템플릿 상점의 콘텐츠(레이아웃, 상품·카테고리·컬렉션·주문 흐름·배송·환불 정책)를 이 상점으로 복사한다. 쿠폰·적립금·리뷰는 복사하지 않는다. 생략하거나 false면 레이아웃 콘텐츠를 비우고 빌드한다",
403
+ "첫 소스 (기본 `NONE`). `NONE`은 빈 작업 트리로 시작하고 첫 소스 업로드가 첫 소스다. `STARTER`는 sayren 시작 코드의 최신 게시 버전으로 첫 작업 트리를 만들고 빌드한다",
508
404
  ),
405
+ slug: storefrontSlugSchema.optional().describe("주소 슬러그. 생략하면 상점 코드"),
509
406
  }),
510
407
  );
511
408
  export type CreateStorefrontSiteRequest = z.infer<typeof createStorefrontSiteRequestSchema>;
512
409
 
513
- /** @deprecated 템플릿 변경은 지원하지 않는다(항상 409 `TEMPLATE_CHANGE_NOT_SUPPORTED`). 호환을 위해 스키마만 남겼다 */
514
- export const changeStorefrontTemplateRequestSchema = z.object({
515
- templateId: z.string().min(1).max(64).describe("바꿀 템플릿 id"),
516
- publish: z
517
- .boolean()
518
- .optional()
519
- .describe("새 버전을 곧바로 발행하는가 (기본 true). false면 버전만 만든다"),
520
- message: z.string().max(200).optional().describe("버전 메모"),
521
- });
522
- /** @deprecated 템플릿 변경은 지원하지 않는다 */
523
- export type ChangeStorefrontTemplateRequest = z.infer<typeof changeStorefrontTemplateRequestSchema>;
524
-
525
410
  /**
526
411
  * 발행·되돌리기 — 발행은 사이트의 발행 포인터를 바꾸는 조건부 쓰기다. `expectedPublishedVersionId`를 주면
527
412
  * 지금 발행 버전이 그 값일 때만 바꾼다(화면을 본 뒤 다른 사람이 바꿨으면 409 `STOREFRONT_SITE_CHANGED`).
@@ -925,7 +810,7 @@ export function normalizeStorefrontPath(path: string): string {
925
810
  * 작업 트리 상대 경로인가(정규화한 값 기준) — `/`로 구분하고 `/`로 시작하지 않는다. `.`·`..`·빈 조각, 조각 문자 규칙
926
811
  * (`STOREFRONT_PATH_SEGMENT_PATTERN`, 공백·역슬래시·제어·제로폭·bidi 문자 불가) 밖, 설치물·산출물 폴더
927
812
  * (`STOREFRONT_BLOCKED_PATH_SEGMENTS`), `.env`로 시작하는 조각(파일·폴더)은 받지 않는다. 최대 256자·깊이 16.
928
- * 어디에 쓸 수 있는지(`src/**`·`public/**`·루트 설정 파일)는 편집 Sandbox가 판정한다(400 `INVALID_FILE_PATH`).
813
+ * 그 밖에 추적하지 않는 이름(`.sayren/`·`.npmrc`·`.dev.vars` 등)과 읽기만 되는 에이전트 안내는 편집 Sandbox가 판정한다(400 `INVALID_FILE_PATH`).
929
814
  * 편집 Sandbox Worker가 같은 규칙의 복제본을 들고 있다(api `test/storefront-hosting-parity.spec.ts`가 대조한다).
930
815
  */
931
816
  export function isEditableStorefrontPath(path: string): boolean {
@@ -1099,9 +984,7 @@ export const storefrontEditingSchema = z.object({
1099
984
  "편집 미리보기 주소 (사이트의 `editPreview`와 같다)",
1100
985
  ),
1101
986
  workingTree: z.object({
1102
- modified: z
1103
- .boolean()
1104
- .describe("템플릿에서 한 번이라도 고쳤는가 (사이트 `sourceMode`가 `CUSTOM`)"),
987
+ modified: z.boolean().describe("작업 트리를 한 번이라도 고쳤는가"),
1105
988
  lastEditedAt: z.string().nullable().describe("마지막으로 파일을 바꾼 시각"),
1106
989
  }),
1107
990
  // 옛 api 응답에는 없다 — 새 SDK가 배포 전 api와 함께 쓰여도 깨지지 않게 선택 필드다
@@ -1411,7 +1294,8 @@ export const STOREFRONT_BUILD_STAGES = [
1411
1294
  * 같은 모양이다. 대상 패키지와 사유. 셀러가 고칠 수 있게 버전·권고 id·거절 사유를 싣는다.
1412
1295
  * `reason`: `DENYLISTED`(거절 목록) · `ADVISORY`(보안 권고) · `MALWARE`(악성 코드 권고, 패키지 전체) · `INSTALL_SCRIPT`(루트
1413
1296
  * package.json의 설치 훅·의존성의 설치 스크립트, 경고) · `INSTALL_CONFIG`(설치 방식 필드, 경고) · `UNSUPPORTED_SOURCE`(레지스트리 밖 설치 경로) · `BUNDLED_DEPENDENCIES`(묶여
1414
- * 오는 의존성, 경고) · `INVALID_MANIFEST`(package.json·잠금 파일 형식), 늘어날 수 있다.
1297
+ * 오는 의존성, 경고) · `INVALID_MANIFEST`(package.json·잠금 파일 형식) · `UNSUPPORTED_PACKAGE_MANAGER`(pnpm·npm 밖의 패키지 관리자) ·
1298
+ * `RESOLVE_FAILED`(잠금 파일 없는 트리의 의존성 해석 실패), 늘어날 수 있다.
1415
1299
  */
1416
1300
  export const storefrontDependencyFindingSchema = z.object({
1417
1301
  name: z.string().describe("패키지 이름. 파일 전체의 문제면 `package.json`·`pnpm-lock.yaml`"),
@@ -1662,7 +1546,12 @@ export const storefrontSourceReplacedSchema = z.object({
1662
1546
  sourceRef: storefrontSourceRefSchema.describe(
1663
1547
  "새 작업 트리(버전 만들기의 `expectedSourceRef`). 다음 올리기의 기준은 `uploadedRef`다",
1664
1548
  ),
1665
- previousRef: z.string().describe("바꾸기 전 작업 트리"),
1549
+ previousRef: z
1550
+ .string()
1551
+ .nullable()
1552
+ .describe(
1553
+ "바꾸기 전 작업 트리. 소스가 없던 사이트(템플릿 없이 만든 사이트)의 첫 업로드는 null",
1554
+ ),
1666
1555
  treeSha256: z.string(),
1667
1556
  files: z.number().int().min(0),
1668
1557
  bytes: z.number().int().min(0),