@zalkera/client 0.14.0 → 0.16.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.cts CHANGED
@@ -949,7 +949,7 @@ declare class ZalkeraError extends Error {
949
949
  * 이 패키지는 그 방언을 읽는 헬퍼([asHandle]·[asHandleArray]·[assetPath]·[readConfig])를 실어 나른다.
950
950
  * 그래서 아래 `SECTION_CONTRACT` 리터럴은 rev 3 과 **바이트 동일**하다(동기 스크립트가 확인한다).
951
951
  */
952
- declare const SECTION_CONTRACT_REV = 4;
952
+ declare const SECTION_CONTRACT_REV = 5;
953
953
  /** 업종 분류 — 콘솔 섹션 픽커의 그룹핑에 쓴다(테넌트 업종 필드는 아직 없다·memo102 §5). */
954
954
  type SectionVertical = "BEAUTY" | "GENERAL";
955
955
  interface SectionSpec {
@@ -972,6 +972,17 @@ interface SectionSpec {
972
972
  * **필수성의 집행 지점은 팩/시드뿐이다** — 런타임은 그대로 관용이다(렌더러 스킵·콘솔 raw 편집 허용).
973
973
  */
974
974
  readonly requiredRefs: readonly string[];
975
+ /**
976
+ * **참조 그룹**(rev 5) — 각 그룹에서 **하나 이상**이 채워져야 한다.
977
+ *
978
+ * rev 3 은 필수성을 키 하나(`productIds`)에 걸었는데, rev 5 에서 갈래 참조(`categorySlug`)가 같은
979
+ * 역할을 하게 되면서 필수의 단위가 **"참조 존재"**로 옮겨갔다. 막으려는 것은 그대로다 — 아무것도
980
+ * 안 가리킨 채 시드에 들어와 개시 직후 조용히 사라지는 섹션.
981
+ *
982
+ * `requiredRefs`(무조건 필수)와 **함께** 쓴다: 그룹으로 표현되는 섹션은 `requiredRefs` 가 빈 배열이고,
983
+ * 종전처럼 단일 키가 무조건 필수인 섹션은 이 필드가 빈 배열이다. 둘 다 빈 배열이면 참조 요구가 없다.
984
+ */
985
+ readonly requiredRefsAnyOf: readonly (readonly string[])[];
975
986
  }
976
987
  /**
977
988
  * 아는 섹션 전량. **순서는 콘솔 픽커의 노출 순서**(관행 아크: 주목→가치→신뢰→행동).
@@ -985,62 +996,74 @@ declare const SECTION_CONTRACT: readonly [{
985
996
  readonly type: "SERVICE_MENU";
986
997
  readonly vertical: "BEAUTY";
987
998
  readonly jsonLd: "ItemList";
988
- readonly requiredRefs: readonly ["productIds"];
999
+ readonly requiredRefs: readonly [];
1000
+ readonly requiredRefsAnyOf: readonly [readonly ["productIds", "categorySlug"]];
989
1001
  }, {
990
1002
  readonly type: "BEFORE_AFTER_GALLERY";
991
1003
  readonly vertical: "BEAUTY";
992
1004
  readonly jsonLd: null;
993
1005
  readonly requiredRefs: readonly [];
1006
+ readonly requiredRefsAnyOf: readonly [];
994
1007
  }, {
995
1008
  readonly type: "BOOKING_CTA";
996
1009
  readonly vertical: "BEAUTY";
997
1010
  readonly jsonLd: null;
998
- readonly requiredRefs: readonly ["productId"];
1011
+ readonly requiredRefs: readonly [];
1012
+ readonly requiredRefsAnyOf: readonly [readonly ["productId", "categorySlug"]];
999
1013
  }, {
1000
1014
  readonly type: "DOCTOR_INTRO";
1001
1015
  readonly vertical: "BEAUTY";
1002
1016
  readonly jsonLd: null;
1003
1017
  readonly requiredRefs: readonly [];
1018
+ readonly requiredRefsAnyOf: readonly [];
1004
1019
  }, {
1005
1020
  readonly type: "HERO";
1006
1021
  readonly vertical: "GENERAL";
1007
1022
  readonly jsonLd: null;
1008
1023
  readonly requiredRefs: readonly [];
1024
+ readonly requiredRefsAnyOf: readonly [];
1009
1025
  }, {
1010
1026
  readonly type: "FEATURE_GRID";
1011
1027
  readonly vertical: "GENERAL";
1012
1028
  readonly jsonLd: null;
1013
1029
  readonly requiredRefs: readonly [];
1030
+ readonly requiredRefsAnyOf: readonly [];
1014
1031
  }, {
1015
1032
  readonly type: "TEXT_MEDIA";
1016
1033
  readonly vertical: "GENERAL";
1017
1034
  readonly jsonLd: null;
1018
1035
  readonly requiredRefs: readonly [];
1036
+ readonly requiredRefsAnyOf: readonly [];
1019
1037
  }, {
1020
1038
  readonly type: "LOGO_WALL";
1021
1039
  readonly vertical: "GENERAL";
1022
1040
  readonly jsonLd: null;
1023
1041
  readonly requiredRefs: readonly [];
1042
+ readonly requiredRefsAnyOf: readonly [];
1024
1043
  }, {
1025
1044
  readonly type: "STATS_BAND";
1026
1045
  readonly vertical: "GENERAL";
1027
1046
  readonly jsonLd: null;
1028
1047
  readonly requiredRefs: readonly [];
1048
+ readonly requiredRefsAnyOf: readonly [];
1029
1049
  }, {
1030
1050
  readonly type: "TESTIMONIALS";
1031
1051
  readonly vertical: "GENERAL";
1032
1052
  readonly jsonLd: null;
1033
1053
  readonly requiredRefs: readonly [];
1054
+ readonly requiredRefsAnyOf: readonly [];
1034
1055
  }, {
1035
1056
  readonly type: "FAQ_LIST";
1036
1057
  readonly vertical: "GENERAL";
1037
1058
  readonly jsonLd: "FAQPage";
1038
1059
  readonly requiredRefs: readonly [];
1060
+ readonly requiredRefsAnyOf: readonly [];
1039
1061
  }, {
1040
1062
  readonly type: "LEAD_CTA";
1041
1063
  readonly vertical: "GENERAL";
1042
1064
  readonly jsonLd: null;
1043
1065
  readonly requiredRefs: readonly [];
1066
+ readonly requiredRefsAnyOf: readonly [];
1044
1067
  }];
1045
1068
  /** 지금 렌더러가 아는 섹션 타입. 이 밖의 값이 와도 정상이다(스킵). */
1046
1069
  type KnownSectionType = (typeof SECTION_CONTRACT)[number]["type"];
package/dist/index.d.ts CHANGED
@@ -949,7 +949,7 @@ declare class ZalkeraError extends Error {
949
949
  * 이 패키지는 그 방언을 읽는 헬퍼([asHandle]·[asHandleArray]·[assetPath]·[readConfig])를 실어 나른다.
950
950
  * 그래서 아래 `SECTION_CONTRACT` 리터럴은 rev 3 과 **바이트 동일**하다(동기 스크립트가 확인한다).
951
951
  */
952
- declare const SECTION_CONTRACT_REV = 4;
952
+ declare const SECTION_CONTRACT_REV = 5;
953
953
  /** 업종 분류 — 콘솔 섹션 픽커의 그룹핑에 쓴다(테넌트 업종 필드는 아직 없다·memo102 §5). */
954
954
  type SectionVertical = "BEAUTY" | "GENERAL";
955
955
  interface SectionSpec {
@@ -972,6 +972,17 @@ interface SectionSpec {
972
972
  * **필수성의 집행 지점은 팩/시드뿐이다** — 런타임은 그대로 관용이다(렌더러 스킵·콘솔 raw 편집 허용).
973
973
  */
974
974
  readonly requiredRefs: readonly string[];
975
+ /**
976
+ * **참조 그룹**(rev 5) — 각 그룹에서 **하나 이상**이 채워져야 한다.
977
+ *
978
+ * rev 3 은 필수성을 키 하나(`productIds`)에 걸었는데, rev 5 에서 갈래 참조(`categorySlug`)가 같은
979
+ * 역할을 하게 되면서 필수의 단위가 **"참조 존재"**로 옮겨갔다. 막으려는 것은 그대로다 — 아무것도
980
+ * 안 가리킨 채 시드에 들어와 개시 직후 조용히 사라지는 섹션.
981
+ *
982
+ * `requiredRefs`(무조건 필수)와 **함께** 쓴다: 그룹으로 표현되는 섹션은 `requiredRefs` 가 빈 배열이고,
983
+ * 종전처럼 단일 키가 무조건 필수인 섹션은 이 필드가 빈 배열이다. 둘 다 빈 배열이면 참조 요구가 없다.
984
+ */
985
+ readonly requiredRefsAnyOf: readonly (readonly string[])[];
975
986
  }
976
987
  /**
977
988
  * 아는 섹션 전량. **순서는 콘솔 픽커의 노출 순서**(관행 아크: 주목→가치→신뢰→행동).
@@ -985,62 +996,74 @@ declare const SECTION_CONTRACT: readonly [{
985
996
  readonly type: "SERVICE_MENU";
986
997
  readonly vertical: "BEAUTY";
987
998
  readonly jsonLd: "ItemList";
988
- readonly requiredRefs: readonly ["productIds"];
999
+ readonly requiredRefs: readonly [];
1000
+ readonly requiredRefsAnyOf: readonly [readonly ["productIds", "categorySlug"]];
989
1001
  }, {
990
1002
  readonly type: "BEFORE_AFTER_GALLERY";
991
1003
  readonly vertical: "BEAUTY";
992
1004
  readonly jsonLd: null;
993
1005
  readonly requiredRefs: readonly [];
1006
+ readonly requiredRefsAnyOf: readonly [];
994
1007
  }, {
995
1008
  readonly type: "BOOKING_CTA";
996
1009
  readonly vertical: "BEAUTY";
997
1010
  readonly jsonLd: null;
998
- readonly requiredRefs: readonly ["productId"];
1011
+ readonly requiredRefs: readonly [];
1012
+ readonly requiredRefsAnyOf: readonly [readonly ["productId", "categorySlug"]];
999
1013
  }, {
1000
1014
  readonly type: "DOCTOR_INTRO";
1001
1015
  readonly vertical: "BEAUTY";
1002
1016
  readonly jsonLd: null;
1003
1017
  readonly requiredRefs: readonly [];
1018
+ readonly requiredRefsAnyOf: readonly [];
1004
1019
  }, {
1005
1020
  readonly type: "HERO";
1006
1021
  readonly vertical: "GENERAL";
1007
1022
  readonly jsonLd: null;
1008
1023
  readonly requiredRefs: readonly [];
1024
+ readonly requiredRefsAnyOf: readonly [];
1009
1025
  }, {
1010
1026
  readonly type: "FEATURE_GRID";
1011
1027
  readonly vertical: "GENERAL";
1012
1028
  readonly jsonLd: null;
1013
1029
  readonly requiredRefs: readonly [];
1030
+ readonly requiredRefsAnyOf: readonly [];
1014
1031
  }, {
1015
1032
  readonly type: "TEXT_MEDIA";
1016
1033
  readonly vertical: "GENERAL";
1017
1034
  readonly jsonLd: null;
1018
1035
  readonly requiredRefs: readonly [];
1036
+ readonly requiredRefsAnyOf: readonly [];
1019
1037
  }, {
1020
1038
  readonly type: "LOGO_WALL";
1021
1039
  readonly vertical: "GENERAL";
1022
1040
  readonly jsonLd: null;
1023
1041
  readonly requiredRefs: readonly [];
1042
+ readonly requiredRefsAnyOf: readonly [];
1024
1043
  }, {
1025
1044
  readonly type: "STATS_BAND";
1026
1045
  readonly vertical: "GENERAL";
1027
1046
  readonly jsonLd: null;
1028
1047
  readonly requiredRefs: readonly [];
1048
+ readonly requiredRefsAnyOf: readonly [];
1029
1049
  }, {
1030
1050
  readonly type: "TESTIMONIALS";
1031
1051
  readonly vertical: "GENERAL";
1032
1052
  readonly jsonLd: null;
1033
1053
  readonly requiredRefs: readonly [];
1054
+ readonly requiredRefsAnyOf: readonly [];
1034
1055
  }, {
1035
1056
  readonly type: "FAQ_LIST";
1036
1057
  readonly vertical: "GENERAL";
1037
1058
  readonly jsonLd: "FAQPage";
1038
1059
  readonly requiredRefs: readonly [];
1060
+ readonly requiredRefsAnyOf: readonly [];
1039
1061
  }, {
1040
1062
  readonly type: "LEAD_CTA";
1041
1063
  readonly vertical: "GENERAL";
1042
1064
  readonly jsonLd: null;
1043
1065
  readonly requiredRefs: readonly [];
1066
+ readonly requiredRefsAnyOf: readonly [];
1044
1067
  }];
1045
1068
  /** 지금 렌더러가 아는 섹션 타입. 이 밖의 값이 와도 정상이다(스킵). */
1046
1069
  type KnownSectionType = (typeof SECTION_CONTRACT)[number]["type"];
package/dist/index.js CHANGED
@@ -104,15 +104,25 @@ function createZalkeraClient(options) {
104
104
  if (init?.bearer) headers["Authorization"] = `Bearer ${init.bearer}`;
105
105
  if (init?.cartSession) headers["X-Cart-Session"] = init.cartSession;
106
106
  if (init?.idempotencyKey) headers["Idempotency-Key"] = init.idempotencyKey;
107
+ const method = init?.method ?? "GET";
108
+ const isRead = method === "GET";
107
109
  const controller = new AbortController();
108
110
  const timer = setTimeout(() => controller.abort(), timeoutMs);
111
+ let timedOut = false;
112
+ const deadline = new Promise((_, reject) => {
113
+ setTimeout(() => {
114
+ timedOut = true;
115
+ reject(new Error("AbortError"));
116
+ }, timeoutMs).unref?.();
117
+ });
109
118
  let response;
110
119
  try {
111
120
  const fetchInit = {
112
- method: init?.method ?? "GET",
121
+ method,
113
122
  headers,
114
123
  body: init?.body != null ? JSON.stringify(init.body) : void 0,
115
- signal: controller.signal,
124
+ // 읽기는 위 이유로 signal 을 뺀다(메모이제이션 보존).
125
+ ...isRead ? {} : { signal: controller.signal },
116
126
  // 리다이렉트를 따라가지 않는다 — **보안 불변식이라 스위치를 두지 않는다**(옵션으로 열면
117
127
  // 그 옵션이 곧 사고 경로가 된다). 기본값 `follow` 로 크로스오리진 3xx 를 따라가면
118
128
  // `X-Storefront-Key`(테넌트 시크릿)·`X-Tenant`·`X-Cart-Session`·`Idempotency-Key` 가
@@ -122,9 +132,10 @@ function createZalkeraClient(options) {
122
132
  redirect: "manual"
123
133
  };
124
134
  if (init?.next) fetchInit.next = init.next;
125
- response = await fetchImpl(url.toString(), fetchInit);
135
+ const call = fetchImpl(url.toString(), fetchInit);
136
+ response = isRead ? await Promise.race([call, deadline]) : await call;
126
137
  } catch (cause) {
127
- const aborted = cause instanceof Error && cause.name === "AbortError";
138
+ const aborted = timedOut || cause instanceof Error && cause.name === "AbortError";
128
139
  throw new ZalkeraError(
129
140
  aborted ? `\uC694\uCCAD \uC2DC\uAC04\uC774 \uCD08\uACFC\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (${timeoutMs}ms)` : "\uB124\uD2B8\uC6CC\uD06C \uC694\uCCAD\uC774 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.",
130
141
  { status: 0, cause }
@@ -290,7 +301,7 @@ function safeJsonParse(text) {
290
301
  }
291
302
 
292
303
  // src/sections.ts
293
- var SECTION_CONTRACT_REV = 4;
304
+ var SECTION_CONTRACT_REV = 5;
294
305
  var SECTION_CONTRACT = [
295
306
  // 뷰티(memo47) — append-only 계약상 불변
296
307
  // SERVICE_MENU 의 ItemList 는 rev 2 에서 올라왔다(memo119 T6-ⓒ): 쇼핑몰 유형엔 상품 목록 표면을
@@ -298,19 +309,19 @@ var SECTION_CONTRACT = [
298
309
  // 정위치는 별도 라우트가 아니라 홈의 이 섹션이라, FAQ_LIST→FAQPage 와 같은 형태로 섹션이 직접 낸다.
299
310
  // rev 3 에서 productIds 의 `?` 가 떨어졌다 — 목록을 내겠다고 선언한 섹션이 목록을 안 가리키는 상태가
300
311
  // 계약상 성립하지 않게 됐다(BOOKING_CTA.productId 는 rev 1 부터 필수였다·비대칭 해소).
301
- { type: "SERVICE_MENU", vertical: "BEAUTY", jsonLd: "ItemList", requiredRefs: ["productIds"] },
302
- { type: "BEFORE_AFTER_GALLERY", vertical: "BEAUTY", jsonLd: null, requiredRefs: [] },
303
- { type: "BOOKING_CTA", vertical: "BEAUTY", jsonLd: null, requiredRefs: ["productId"] },
304
- { type: "DOCTOR_INTRO", vertical: "BEAUTY", jsonLd: null, requiredRefs: [] },
312
+ { type: "SERVICE_MENU", vertical: "BEAUTY", jsonLd: "ItemList", requiredRefs: [], requiredRefsAnyOf: [["productIds", "categorySlug"]] },
313
+ { type: "BEFORE_AFTER_GALLERY", vertical: "BEAUTY", jsonLd: null, requiredRefs: [], requiredRefsAnyOf: [] },
314
+ { type: "BOOKING_CTA", vertical: "BEAUTY", jsonLd: null, requiredRefs: [], requiredRefsAnyOf: [["productId", "categorySlug"]] },
315
+ { type: "DOCTOR_INTRO", vertical: "BEAUTY", jsonLd: null, requiredRefs: [], requiredRefsAnyOf: [] },
305
316
  // 기업 마케팅(memo102 §2)
306
- { type: "HERO", vertical: "GENERAL", jsonLd: null, requiredRefs: [] },
307
- { type: "FEATURE_GRID", vertical: "GENERAL", jsonLd: null, requiredRefs: [] },
308
- { type: "TEXT_MEDIA", vertical: "GENERAL", jsonLd: null, requiredRefs: [] },
309
- { type: "LOGO_WALL", vertical: "GENERAL", jsonLd: null, requiredRefs: [] },
310
- { type: "STATS_BAND", vertical: "GENERAL", jsonLd: null, requiredRefs: [] },
311
- { type: "TESTIMONIALS", vertical: "GENERAL", jsonLd: null, requiredRefs: [] },
312
- { type: "FAQ_LIST", vertical: "GENERAL", jsonLd: "FAQPage", requiredRefs: [] },
313
- { type: "LEAD_CTA", vertical: "GENERAL", jsonLd: null, requiredRefs: [] }
317
+ { type: "HERO", vertical: "GENERAL", jsonLd: null, requiredRefs: [], requiredRefsAnyOf: [] },
318
+ { type: "FEATURE_GRID", vertical: "GENERAL", jsonLd: null, requiredRefs: [], requiredRefsAnyOf: [] },
319
+ { type: "TEXT_MEDIA", vertical: "GENERAL", jsonLd: null, requiredRefs: [], requiredRefsAnyOf: [] },
320
+ { type: "LOGO_WALL", vertical: "GENERAL", jsonLd: null, requiredRefs: [], requiredRefsAnyOf: [] },
321
+ { type: "STATS_BAND", vertical: "GENERAL", jsonLd: null, requiredRefs: [], requiredRefsAnyOf: [] },
322
+ { type: "TESTIMONIALS", vertical: "GENERAL", jsonLd: null, requiredRefs: [], requiredRefsAnyOf: [] },
323
+ { type: "FAQ_LIST", vertical: "GENERAL", jsonLd: "FAQPage", requiredRefs: [], requiredRefsAnyOf: [] },
324
+ { type: "LEAD_CTA", vertical: "GENERAL", jsonLd: null, requiredRefs: [], requiredRefsAnyOf: [] }
314
325
  ];
315
326
  function sectionsOfVertical(vertical) {
316
327
  return SECTION_CONTRACT.filter((s) => s.vertical === vertical);