@dooboostore/simple-web-component 1.0.50 → 1.0.52

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.
Files changed (241) hide show
  1. package/README.md +735 -354
  2. package/dist/cjs/SwcAppEngine.js.map +2 -2
  3. package/dist/cjs/decorators/addEventListener.js +64 -146
  4. package/dist/cjs/decorators/addEventListener.js.map +2 -2
  5. package/dist/cjs/decorators/applyAttribute.js +36 -48
  6. package/dist/cjs/decorators/applyAttribute.js.map +2 -2
  7. package/dist/cjs/decorators/applyClass.js +9 -12
  8. package/dist/cjs/decorators/applyClass.js.map +2 -2
  9. package/dist/cjs/decorators/applyNode.js +14 -22
  10. package/dist/cjs/decorators/applyNode.js.map +2 -2
  11. package/dist/cjs/decorators/applyProperty.js +12 -42
  12. package/dist/cjs/decorators/applyProperty.js.map +2 -2
  13. package/dist/cjs/decorators/applySlot.js +3 -3
  14. package/dist/cjs/decorators/applySlot.js.map +2 -2
  15. package/dist/cjs/decorators/applyStyle.js +9 -12
  16. package/dist/cjs/decorators/applyStyle.js.map +2 -2
  17. package/dist/cjs/decorators/around.js +124 -0
  18. package/dist/cjs/decorators/around.js.map +7 -0
  19. package/dist/cjs/decorators/changedAttribute.js +40 -12
  20. package/dist/cjs/decorators/changedAttribute.js.map +2 -2
  21. package/dist/cjs/decorators/elementDefine.js +224 -195
  22. package/dist/cjs/decorators/elementDefine.js.map +3 -3
  23. package/dist/cjs/decorators/emitCustomEvent.js +14 -12
  24. package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
  25. package/dist/cjs/decorators/eventMedia.js +36 -8
  26. package/dist/cjs/decorators/eventMedia.js.map +2 -2
  27. package/dist/cjs/decorators/fetch.js +278 -0
  28. package/dist/cjs/decorators/fetch.js.map +7 -0
  29. package/dist/cjs/decorators/index.js +5 -2
  30. package/dist/cjs/decorators/index.js.map +2 -2
  31. package/dist/cjs/decorators/intersectionObserver.js +33 -13
  32. package/dist/cjs/decorators/intersectionObserver.js.map +2 -2
  33. package/dist/cjs/decorators/lifecycles.js +11 -11
  34. package/dist/cjs/decorators/lifecycles.js.map +2 -2
  35. package/dist/cjs/decorators/mutationObserver.js +55 -39
  36. package/dist/cjs/decorators/mutationObserver.js.map +3 -3
  37. package/dist/cjs/decorators/parameter.js +92 -9
  38. package/dist/cjs/decorators/parameter.js.map +2 -2
  39. package/dist/cjs/decorators/publishSwcAppMessage.js +2 -2
  40. package/dist/cjs/decorators/publishSwcAppMessage.js.map +2 -2
  41. package/dist/cjs/decorators/query.js +11 -5
  42. package/dist/cjs/decorators/query.js.map +2 -2
  43. package/dist/cjs/decorators/requestAnimationFrame.js +7 -16
  44. package/dist/cjs/decorators/requestAnimationFrame.js.map +2 -2
  45. package/dist/cjs/decorators/resizeObserver.js +41 -17
  46. package/dist/cjs/decorators/resizeObserver.js.map +3 -3
  47. package/dist/cjs/decorators/setInterval.js +36 -25
  48. package/dist/cjs/decorators/setInterval.js.map +2 -2
  49. package/dist/cjs/decorators/setTimeout.js +36 -25
  50. package/dist/cjs/decorators/setTimeout.js.map +2 -2
  51. package/dist/cjs/decorators/state.js +13 -10
  52. package/dist/cjs/decorators/state.js.map +2 -2
  53. package/dist/cjs/decorators/{subscribeSwcAppMessageWhileConnected.js → subscribeSwcAppMessage.js} +20 -21
  54. package/dist/cjs/decorators/subscribeSwcAppMessage.js.map +7 -0
  55. package/dist/cjs/decorators/{subscribeSwcAppRouteChangeWhileConnected.js → subscribeSwcAppRouteChange.js} +23 -19
  56. package/dist/cjs/decorators/subscribeSwcAppRouteChange.js.map +7 -0
  57. package/dist/cjs/decorators/swcAppRoute.js +149 -0
  58. package/dist/cjs/decorators/swcAppRoute.js.map +7 -0
  59. package/dist/cjs/elements/SwcApp.js +70 -0
  60. package/dist/cjs/elements/SwcApp.js.map +2 -2
  61. package/dist/cjs/elements/SwcAppMixin.js +262 -83
  62. package/dist/cjs/elements/SwcAppMixin.js.map +3 -3
  63. package/dist/cjs/types/index.js +5 -1
  64. package/dist/cjs/types/index.js.map +2 -2
  65. package/dist/cjs/utils/Utils.js +36 -15
  66. package/dist/cjs/utils/Utils.js.map +2 -2
  67. package/dist/esm/SwcAppEngine.js.map +2 -2
  68. package/dist/esm/decorators/addEventListener.js +64 -146
  69. package/dist/esm/decorators/addEventListener.js.map +2 -2
  70. package/dist/esm/decorators/applyAttribute.js +36 -48
  71. package/dist/esm/decorators/applyAttribute.js.map +2 -2
  72. package/dist/esm/decorators/applyClass.js +9 -12
  73. package/dist/esm/decorators/applyClass.js.map +2 -2
  74. package/dist/esm/decorators/applyNode.js +15 -23
  75. package/dist/esm/decorators/applyNode.js.map +2 -2
  76. package/dist/esm/decorators/applyProperty.js +12 -42
  77. package/dist/esm/decorators/applyProperty.js.map +2 -2
  78. package/dist/esm/decorators/applySlot.js +3 -3
  79. package/dist/esm/decorators/applySlot.js.map +2 -2
  80. package/dist/esm/decorators/applyStyle.js +9 -12
  81. package/dist/esm/decorators/applyStyle.js.map +2 -2
  82. package/dist/esm/decorators/around.js +105 -0
  83. package/dist/esm/decorators/around.js.map +7 -0
  84. package/dist/esm/decorators/changedAttribute.js +40 -12
  85. package/dist/esm/decorators/changedAttribute.js.map +2 -2
  86. package/dist/esm/decorators/elementDefine.js +222 -193
  87. package/dist/esm/decorators/elementDefine.js.map +3 -3
  88. package/dist/esm/decorators/emitCustomEvent.js +15 -13
  89. package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
  90. package/dist/esm/decorators/eventMedia.js +36 -8
  91. package/dist/esm/decorators/eventMedia.js.map +2 -2
  92. package/dist/esm/decorators/fetch.js +259 -0
  93. package/dist/esm/decorators/fetch.js.map +7 -0
  94. package/dist/esm/decorators/index.js +5 -2
  95. package/dist/esm/decorators/index.js.map +2 -2
  96. package/dist/esm/decorators/intersectionObserver.js +33 -13
  97. package/dist/esm/decorators/intersectionObserver.js.map +2 -2
  98. package/dist/esm/decorators/lifecycles.js +11 -11
  99. package/dist/esm/decorators/lifecycles.js.map +2 -2
  100. package/dist/esm/decorators/mutationObserver.js +55 -39
  101. package/dist/esm/decorators/mutationObserver.js.map +3 -3
  102. package/dist/esm/decorators/parameter.js +92 -9
  103. package/dist/esm/decorators/parameter.js.map +2 -2
  104. package/dist/esm/decorators/publishSwcAppMessage.js +2 -2
  105. package/dist/esm/decorators/publishSwcAppMessage.js.map +2 -2
  106. package/dist/esm/decorators/query.js +11 -5
  107. package/dist/esm/decorators/query.js.map +2 -2
  108. package/dist/esm/decorators/requestAnimationFrame.js +8 -17
  109. package/dist/esm/decorators/requestAnimationFrame.js.map +2 -2
  110. package/dist/esm/decorators/resizeObserver.js +41 -17
  111. package/dist/esm/decorators/resizeObserver.js.map +2 -2
  112. package/dist/esm/decorators/setInterval.js +37 -26
  113. package/dist/esm/decorators/setInterval.js.map +2 -2
  114. package/dist/esm/decorators/setTimeout.js +37 -26
  115. package/dist/esm/decorators/setTimeout.js.map +2 -2
  116. package/dist/esm/decorators/state.js +13 -10
  117. package/dist/esm/decorators/state.js.map +2 -2
  118. package/dist/esm/decorators/subscribeSwcAppMessage.js +53 -0
  119. package/dist/esm/decorators/subscribeSwcAppMessage.js.map +7 -0
  120. package/dist/esm/decorators/{subscribeSwcAppRouteChangeWhileConnected.js → subscribeSwcAppRouteChange.js} +20 -16
  121. package/dist/esm/decorators/subscribeSwcAppRouteChange.js.map +7 -0
  122. package/dist/esm/decorators/swcAppRoute.js +130 -0
  123. package/dist/esm/decorators/swcAppRoute.js.map +7 -0
  124. package/dist/esm/elements/SwcApp.js +70 -0
  125. package/dist/esm/elements/SwcApp.js.map +2 -2
  126. package/dist/esm/elements/SwcAppMixin.js +264 -85
  127. package/dist/esm/elements/SwcAppMixin.js.map +3 -3
  128. package/dist/esm/types/index.js +5 -1
  129. package/dist/esm/types/index.js.map +2 -2
  130. package/dist/esm/utils/Utils.js +36 -15
  131. package/dist/esm/utils/Utils.js.map +2 -2
  132. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +1893 -1000
  133. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  134. package/dist/types/SwcAppEngine.d.ts +1 -0
  135. package/dist/types/SwcAppEngine.d.ts.map +1 -1
  136. package/dist/types/decorators/addEventListener.d.ts +923 -960
  137. package/dist/types/decorators/addEventListener.d.ts.map +1 -1
  138. package/dist/types/decorators/applyAttribute.d.ts +26 -38
  139. package/dist/types/decorators/applyAttribute.d.ts.map +1 -1
  140. package/dist/types/decorators/applyClass.d.ts +1 -3
  141. package/dist/types/decorators/applyClass.d.ts.map +1 -1
  142. package/dist/types/decorators/applyNode.d.ts +15 -17
  143. package/dist/types/decorators/applyNode.d.ts.map +1 -1
  144. package/dist/types/decorators/applyProperty.d.ts +25 -45
  145. package/dist/types/decorators/applyProperty.d.ts.map +1 -1
  146. package/dist/types/decorators/applyStyle.d.ts +1 -3
  147. package/dist/types/decorators/applyStyle.d.ts.map +1 -1
  148. package/dist/types/decorators/around.d.ts +87 -0
  149. package/dist/types/decorators/around.d.ts.map +1 -0
  150. package/dist/types/decorators/changedAttribute.d.ts +21 -1
  151. package/dist/types/decorators/changedAttribute.d.ts.map +1 -1
  152. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  153. package/dist/types/decorators/emitCustomEvent.d.ts +0 -1
  154. package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
  155. package/dist/types/decorators/eventMedia.d.ts +24 -1
  156. package/dist/types/decorators/eventMedia.d.ts.map +1 -1
  157. package/dist/types/decorators/fetch.d.ts +139 -0
  158. package/dist/types/decorators/fetch.d.ts.map +1 -0
  159. package/dist/types/decorators/index.d.ts +5 -2
  160. package/dist/types/decorators/index.d.ts.map +1 -1
  161. package/dist/types/decorators/intersectionObserver.d.ts +24 -11
  162. package/dist/types/decorators/intersectionObserver.d.ts.map +1 -1
  163. package/dist/types/decorators/lifecycles.d.ts +8 -7
  164. package/dist/types/decorators/lifecycles.d.ts.map +1 -1
  165. package/dist/types/decorators/mutationObserver.d.ts +26 -13
  166. package/dist/types/decorators/mutationObserver.d.ts.map +1 -1
  167. package/dist/types/decorators/parameter.d.ts +84 -6
  168. package/dist/types/decorators/parameter.d.ts.map +1 -1
  169. package/dist/types/decorators/publishSwcAppMessage.d.ts +0 -1
  170. package/dist/types/decorators/publishSwcAppMessage.d.ts.map +1 -1
  171. package/dist/types/decorators/query.d.ts +7 -7
  172. package/dist/types/decorators/query.d.ts.map +1 -1
  173. package/dist/types/decorators/requestAnimationFrame.d.ts +1 -1
  174. package/dist/types/decorators/requestAnimationFrame.d.ts.map +1 -1
  175. package/dist/types/decorators/resizeObserver.d.ts +26 -13
  176. package/dist/types/decorators/resizeObserver.d.ts.map +1 -1
  177. package/dist/types/decorators/setInterval.d.ts +10 -0
  178. package/dist/types/decorators/setInterval.d.ts.map +1 -1
  179. package/dist/types/decorators/setTimeout.d.ts +10 -0
  180. package/dist/types/decorators/setTimeout.d.ts.map +1 -1
  181. package/dist/types/decorators/state.d.ts.map +1 -1
  182. package/dist/types/decorators/subscribeSwcAppMessage.d.ts +44 -0
  183. package/dist/types/decorators/subscribeSwcAppMessage.d.ts.map +1 -0
  184. package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts +60 -0
  185. package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts.map +1 -0
  186. package/dist/types/decorators/swcAppRoute.d.ts +94 -0
  187. package/dist/types/decorators/swcAppRoute.d.ts.map +1 -0
  188. package/dist/types/elements/SwcApp.d.ts.map +1 -1
  189. package/dist/types/elements/SwcAppMixin.d.ts +423 -388
  190. package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
  191. package/dist/types/types/index.d.ts +43 -4
  192. package/dist/types/types/index.d.ts.map +1 -1
  193. package/dist/types/utils/Utils.d.ts +15 -5
  194. package/dist/types/utils/Utils.d.ts.map +1 -1
  195. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +1893 -1000
  196. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  197. package/package.json +8 -6
  198. package/src/SwcAppEngine.ts +1 -0
  199. package/src/decorators/addEventListener.ts +102 -96
  200. package/src/decorators/applyAttribute.ts +76 -138
  201. package/src/decorators/applyClass.ts +8 -14
  202. package/src/decorators/applyNode.ts +33 -41
  203. package/src/decorators/applyProperty.ts +51 -99
  204. package/src/decorators/applySlot.ts +3 -3
  205. package/src/decorators/applyStyle.ts +8 -14
  206. package/src/decorators/around.ts +196 -0
  207. package/src/decorators/changedAttribute.ts +39 -15
  208. package/src/decorators/elementDefine.ts +270 -219
  209. package/src/decorators/emitCustomEvent.ts +17 -13
  210. package/src/decorators/eventMedia.ts +50 -9
  211. package/src/decorators/fetch.ts +454 -0
  212. package/src/decorators/index.ts +5 -2
  213. package/src/decorators/intersectionObserver.ts +38 -24
  214. package/src/decorators/lifecycles.ts +24 -22
  215. package/src/decorators/mutationObserver.ts +71 -52
  216. package/src/decorators/parameter.ts +145 -14
  217. package/src/decorators/publishSwcAppMessage.ts +3 -4
  218. package/src/decorators/query.ts +22 -14
  219. package/src/decorators/requestAnimationFrame.ts +10 -19
  220. package/src/decorators/resizeObserver.ts +50 -29
  221. package/src/decorators/setInterval.ts +37 -26
  222. package/src/decorators/setTimeout.ts +37 -26
  223. package/src/decorators/state.ts +19 -13
  224. package/src/decorators/subscribeSwcAppMessage.ts +106 -0
  225. package/src/decorators/subscribeSwcAppRouteChange.ts +131 -0
  226. package/src/decorators/swcAppRoute.ts +231 -0
  227. package/src/elements/SwcApp.ts +40 -10
  228. package/src/elements/SwcAppMixin.ts +340 -101
  229. package/src/types/index.ts +38 -5
  230. package/src/utils/Utils.ts +50 -18
  231. package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js.map +0 -7
  232. package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +0 -7
  233. package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js +0 -54
  234. package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js.map +0 -7
  235. package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +0 -7
  236. package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts +0 -23
  237. package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts.map +0 -1
  238. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts +0 -31
  239. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts.map +0 -1
  240. package/src/decorators/subscribeSwcAppMessageWhileConnected.ts +0 -91
  241. package/src/decorators/subscribeSwcAppRouteChangeWhileConnected.ts +0 -107
@@ -1,6 +1,6 @@
1
1
  import {ReflectUtils} from '@dooboostore/core';
2
2
  import { SpecialSelector, SwcQueryOptions, SwcFnSelector, SwcSelector, HelperHostSet } from '../types';
3
- import {ensureInit, getElementConfig} from './elementDefine';
3
+ import { ensureInit } from './elementDefine';
4
4
  import {SwcUtils} from '../utils/Utils';
5
5
 
6
6
  export type EmitCustomEventFnSelector = SwcFnSelector;
@@ -90,7 +90,7 @@ function createEmitDecorator(selector: EmitCustomEventSelector, type: string, op
90
90
  if (fullOptions.bubbles === undefined) fullOptions.bubbles = true;
91
91
  if (fullOptions.composed === undefined) fullOptions.composed = true;
92
92
 
93
- let list = ReflectUtils.getMetadata<EmitCustomEventMetadata[]>(EMIT_CUSTOM_EVENT_METADATA_KEY, constructor);
93
+ let list = ReflectUtils.getOwnMetadata(EMIT_CUSTOM_EVENT_METADATA_KEY, constructor) as EmitCustomEventMetadata[] | undefined;
94
94
  if (!list) {
95
95
  list = [];
96
96
  ReflectUtils.defineMetadata(EMIT_CUSTOM_EVENT_METADATA_KEY, list, constructor);
@@ -102,7 +102,15 @@ function createEmitDecorator(selector: EmitCustomEventSelector, type: string, op
102
102
  ensureInit(this);
103
103
  const res = original.apply(this, args);
104
104
 
105
- const handleResult = (detail: any) => {
105
+ // 리턴값이 객체이고 valueKey(기본 EMIT_CUSTOM_EVENT_METADATA_KEY) 를 가지면 그 값, 아니면 리턴값 전체를 detail 로
106
+ const extractValue = (v: any) => {
107
+ const keyToUse = fullOptions.valueKey ?? EMIT_CUSTOM_EVENT_METADATA_KEY;
108
+ if (v && typeof v === 'object' && keyToUse in v) return v[keyToUse];
109
+ return v;
110
+ };
111
+
112
+ const handleResult = (result: any) => {
113
+ const detail = extractValue(result);
106
114
  const event = new CustomEvent(type, {
107
115
  detail,
108
116
  bubbles: fullOptions.bubbles,
@@ -110,9 +118,8 @@ function createEmitDecorator(selector: EmitCustomEventSelector, type: string, op
110
118
  cancelable: fullOptions.cancelable
111
119
  });
112
120
 
113
- const conf = getElementConfig(this);
114
- const currentWin = (this as any)._resolveWindow?.(conf) || ((typeof window !== 'undefined' ? window : undefined) as Window);
115
- const hostSet = SwcUtils.getHelperAndHostSet(currentWin, this as any);
121
+ const currentWin = SwcUtils.resolveWindow(this);
122
+ const hostSet = SwcUtils.getHelperAndHostSet(this as any);
116
123
 
117
124
  const eventTargets: EventTarget[] = [];
118
125
 
@@ -126,7 +133,7 @@ function createEmitDecorator(selector: EmitCustomEventSelector, type: string, op
126
133
  if (resolvedSelector instanceof currentWin.Element) {
127
134
  eventTargets.push(resolvedSelector as EventTarget);
128
135
  } else if (resolvedSelector instanceof currentWin.NodeList) {
129
- eventTargets.push(...Array.from(resolvedSelector as EventTarget[]));
136
+ eventTargets.push(...Array.from(resolvedSelector as NodeList));
130
137
  } else if (Array.isArray(resolvedSelector)) {
131
138
  eventTargets.push(...(resolvedSelector as EventTarget[]));
132
139
  } else if (resolvedSelector === null) {
@@ -181,7 +188,7 @@ function createEmitDecorator(selector: EmitCustomEventSelector, type: string, op
181
188
  t.dispatchEvent(event);
182
189
  });
183
190
 
184
- return detail;
191
+ return result;
185
192
  };
186
193
 
187
194
  if (res instanceof Promise) {
@@ -194,8 +201,8 @@ function createEmitDecorator(selector: EmitCustomEventSelector, type: string, op
194
201
  }
195
202
 
196
203
  export const getEmitCustomEventMetadataList = (target: any): EmitCustomEventMetadata[] | undefined => {
197
- const constructor = target instanceof Function ? target : target.constructor;
198
- return ReflectUtils.getMetadata(EMIT_CUSTOM_EVENT_METADATA_KEY, constructor);
204
+ const constructor = typeof target === 'function' ? target : target.constructor;
205
+ return ReflectUtils.findAllMetadata<any[]>(EMIT_CUSTOM_EVENT_METADATA_KEY, constructor).flat();
199
206
  };
200
207
 
201
208
  // --- Aliases: emit... ---
@@ -203,9 +210,6 @@ export const emit = emitCustomEvent;
203
210
 
204
211
  // ─── 편의 헬퍼 (selector/root 생략) ───
205
212
 
206
- export function emitThis(type: string, options?: EmitCustomEventQueryOptions): MethodDecorator {
207
- return emitCustomEvent('$this', type, options);
208
- }
209
213
  export function emitAppHost(type: string, options?: EmitCustomEventQueryOptions): MethodDecorator {
210
214
  return emitCustomEvent('$appHost', type, options);
211
215
  }
@@ -1,10 +1,20 @@
1
1
  import { ReflectUtils } from '@dooboostore/core';
2
2
  import { ElementDefineLifeCycler, HelperHostSet } from '../types';
3
3
  import { SwcUtils } from '../utils/Utils';
4
+ import { buildSwcParameterArgs } from './parameter';
4
5
 
5
6
  export const EVENT_MEDIA_METADATA_KEY = Symbol.for('simple-web-component:event-media');
6
7
 
7
- export interface EventMediaMetadata {
8
+ export interface EventMediaOptions<Return = any> {
9
+ /** 미디어쿼리 변화 시 핸들러 실행 여부 게이트. Promise<boolean>도 되어 async 판정 가능. false면 스킵. */
10
+ filter?: (e: MediaQueryListEvent, meta: { currentThis: any; helper: HelperHostSet }) => boolean | Promise<boolean>;
11
+ /** filter 통과 후 핸들러 직전 훅. await되고, 리턴값은 @eventMediaBeforeReturn 으로 핸들러에 주입된다. */
12
+ before?: (e: MediaQueryListEvent, meta: { currentThis: any; helper: HelperHostSet }, args: any[]) => any | Promise<any>;
13
+ /** 핸들러가 성공/실패해도 항상 실행되는 정리 훅. ctx로 핸들러 인자/결과/에러를 받는다. 에러는 삼키지 않고(로깅됨). */
14
+ finally?: (e: MediaQueryListEvent, meta: { currentThis: any; helper: HelperHostSet }, ctx: { args: any[]; result?: Return; error?: any }) => any | Promise<any>;
15
+ }
16
+
17
+ export interface EventMediaMetadata extends EventMediaOptions {
8
18
  propertyKey: string | symbol;
9
19
  query: string;
10
20
  eventType: string;
@@ -18,7 +28,9 @@ export interface EventMediaMetadata {
18
28
  * (다른 @event* 계열처럼 베이스는 명시적, 편의 별칭이 값을 고정하는 컨벤션을 따름). 'change' 고정 버전은
19
29
  * eventMediaChange를 사용한다.
20
30
  */
21
- export function eventMedia(query: string, eventType: string): MethodDecorator {
31
+ export function eventMedia(query: string, eventType: string): MethodDecorator;
32
+ export function eventMedia<Return = any>(query: string, eventType: string, options: EventMediaOptions<NoInfer<Return>>): MethodDecorator;
33
+ export function eventMedia(query: string, eventType: string, options?: EventMediaOptions): MethodDecorator {
22
34
  return (target: Object, propertyKey: string | symbol) => {
23
35
  const constructor = target.constructor;
24
36
  let metaList = ReflectUtils.getOwnMetadata(EVENT_MEDIA_METADATA_KEY, constructor) as EventMediaMetadata[];
@@ -26,17 +38,19 @@ export function eventMedia(query: string, eventType: string): MethodDecorator {
26
38
  metaList = [];
27
39
  ReflectUtils.defineMetadata(EVENT_MEDIA_METADATA_KEY, metaList, constructor);
28
40
  }
29
- metaList.push({ propertyKey, query, eventType });
41
+ metaList.push({ propertyKey, query, eventType, filter: options?.filter, before: options?.before, finally: options?.finally });
30
42
  };
31
43
  }
32
44
 
33
45
  /** eventMedia(query, 'change')의 별칭 - MediaQueryList가 실제로 발생시키는 유일한 이벤트. */
34
- export function eventMediaChange(query: string): MethodDecorator {
35
- return eventMedia(query, 'change');
46
+ export function eventMediaChange(query: string): MethodDecorator;
47
+ export function eventMediaChange<Return = any>(query: string, options: EventMediaOptions<NoInfer<Return>>): MethodDecorator;
48
+ export function eventMediaChange(query: string, options?: EventMediaOptions): MethodDecorator {
49
+ return eventMedia(query, 'change', options as any);
36
50
  }
37
51
 
38
52
  export const findAllEventMediaMetadata = (target: any): EventMediaMetadata[] => {
39
- const constructor = target instanceof Function ? target : target.constructor;
53
+ const constructor = typeof target === 'function' ? target : target.constructor;
40
54
  return (ReflectUtils.findAllMetadata<EventMediaMetadata[]>(EVENT_MEDIA_METADATA_KEY, constructor) || []).flat();
41
55
  };
42
56
 
@@ -48,12 +62,39 @@ export class EventMediaLifeCycler implements ElementDefineLifeCycler {
48
62
  const metaList = findAllEventMediaMetadata(inst);
49
63
  if (metaList.length === 0) return;
50
64
 
65
+ const win = helperHostSet.$w;
51
66
  const entries: Array<{ mql: MediaQueryList; eventType: string; handler: (e: Event) => void }> = [];
52
67
  for (const meta of metaList) {
53
- const mql = helperHostSet.$w.matchMedia(meta.query);
54
- const handler = (e: Event) => {
68
+ const mql = win.matchMedia(meta.query);
69
+ const handler = async (e: Event) => {
55
70
  try {
56
- inst[meta.propertyKey](e, helperHostSet);
71
+ const helper = SwcUtils.getHelperAndHostSet(inst);
72
+ if (meta.filter && !(await meta.filter(e as MediaQueryListEvent, { currentThis: inst, helper }))) return;
73
+ const hostSet = SwcUtils.getHostSet(inst);
74
+ const helperSet = SwcUtils.getHelperSet(win);
75
+ const legacyArgs = [e, helperHostSet];
76
+ const buildArgs = (beforeReturn: any) => buildSwcParameterArgs(inst, meta.propertyKey, {
77
+ eventObject: e,
78
+ hostSet,
79
+ helperHostSet: helper,
80
+ helperSet,
81
+ eventMediaBeforeReturn: beforeReturn
82
+ }, legacyArgs);
83
+ // before를 먼저 돌려 그 리턴값을 @eventMediaBeforeReturn 으로 주입.
84
+ let args = buildArgs(undefined);
85
+ if (meta.before) {
86
+ const beforeReturn = await meta.before(e as MediaQueryListEvent, { currentThis: inst, helper }, args);
87
+ args = buildArgs(beforeReturn);
88
+ }
89
+ let result: any, error: any;
90
+ try {
91
+ result = await inst[meta.propertyKey](...args);
92
+ } catch (err) {
93
+ error = err;
94
+ } finally {
95
+ if (meta.finally) await meta.finally(e as MediaQueryListEvent, { currentThis: inst, helper }, { args, result, error });
96
+ }
97
+ if (error) throw error;
57
98
  } catch (err) {
58
99
  console.error('[SWC] eventMedia handler error:', err);
59
100
  }
@@ -0,0 +1,454 @@
1
+ import { ReflectUtils, HttpResponseError } from '@dooboostore/core';
2
+ import { SwcUtils } from '../utils/Utils';
3
+ import { getParameterMetadata } from './parameter';
4
+ import type { ElementDefineLifeCycler, HelperHostSet } from '../types';
5
+
6
+ export const FETCH_METADATA_KEY = Symbol.for('simple-web-component:fetch');
7
+
8
+ export type FetchUrlFactory = (
9
+ currentThis: any,
10
+ helper: HelperHostSet,
11
+ parameter: any[]
12
+ ) => RequestInfo | URL;
13
+
14
+ /**
15
+ * 수동 fetch. 요청을 직접 만들고 보낸다 (서비스 호출, 직접 쓴 fetch 등). 리턴값이 fetch 결과가 된다.
16
+ * - signal: 요소 disconnect / abortPrevious 때 abort 된다. 직접 쓰는 fetch/클라이언트에 넘기면 네트워크까지 끊긴다
17
+ * (안 넘겨도 abort 즉시 호출은 끝나고 결과는 버려진다)
18
+ * - returnValue: after 모드에서 메서드 리턴값 (valueKey 가 있으면 추출한 값). before 모드에선 undefined
19
+ */
20
+ export type FetchManualFn = (
21
+ currentThis: any,
22
+ helper: HelperHostSet,
23
+ parameter: any[],
24
+ signal: AbortSignal,
25
+ returnValue?: any
26
+ ) => any | Promise<any>;
27
+
28
+ /** 모드(선언형/수동형, before/after)와 무관한 공통 옵션 */
29
+ export type FetchType = {
30
+ /**
31
+ * 같은 메서드를 다시 부르면 진행 중인 이전 요청을 abort 한다 (검색 자동완성 등 마지막 요청만 살림).
32
+ * 끊긴 이전 호출은 조용히 undefined 로 끝난다.
33
+ */
34
+ abortPrevious?: boolean;
35
+ /** false 를 리턴하면 이번 호출을 통째로 건너뛴다 (메서드도 fetch 도 실행 안 함, undefined 리턴). */
36
+ filter?: (
37
+ currentThis: any,
38
+ helper: HelperHostSet,
39
+ parameter: any[]
40
+ ) => boolean | Promise<boolean>;
41
+ /** filter 통과 후 가장 먼저 실행 (await). 리턴값은 무시. init 조립은 request 팩토리 담당. */
42
+ before?: (
43
+ currentThis: any,
44
+ helper: HelperHostSet,
45
+ parameter: any[]
46
+ ) => void | Promise<void>;
47
+ /**
48
+ * fetch settled 후 호출 (fulfilled/rejected 모두). 호출만 하고 리턴값은 무시.
49
+ * 변환·복구가 필요하면 manual 로 직접 구현할 것.
50
+ */
51
+ after?: (
52
+ currentThis: any,
53
+ helper: HelperHostSet,
54
+ parameter: any[],
55
+ settled: PromiseSettledResult<any>
56
+ ) => void | Promise<void>;
57
+ /**
58
+ * 성공/실패 무관 항상 실행 (다른 데코레이터의 finally 와 같은 규칙). 에러는 삼키지 않고 전파.
59
+ * args: 메서드에 넘긴 인자, result: 이번 호출의 리턴값(before 모드 = 메서드 리턴, after 모드 = fetch 결과)
60
+ */
61
+ finally?: (
62
+ currentThis: any,
63
+ helper: HelperHostSet,
64
+ parameter: any[],
65
+ ctx: { args: any[]; result?: any; error?: any }
66
+ ) => any | Promise<any>;
67
+ };
68
+
69
+ /** before: 인자 기반 RequestInit 조립. 없으면 GET. */
70
+ export type RequestFactory = (
71
+ currentThis: any,
72
+ helper: HelperHostSet,
73
+ parameter: any[]
74
+ ) => RequestInit | undefined;
75
+
76
+ /** after: 인자 + 메서드 리턴값 기반 RequestInit 조립. 없으면 body 유무로 GET/POST. */
77
+ export type ReturnRequestFactory = (
78
+ currentThis: any,
79
+ helper: HelperHostSet,
80
+ parameter: any[],
81
+ returnValue: any
82
+ ) => RequestInit | undefined;
83
+
84
+ export interface FetchMetadata {
85
+ propertyKey: string | symbol;
86
+ options: FetchOptions;
87
+ }
88
+
89
+ /** 선언형: url/request/process 로 기술하면 프레임워크가 fetch 한다 */
90
+ export type FetchDeclarative<R> = {
91
+ url: RequestInfo | URL | FetchUrlFactory;
92
+ request?: R | string;
93
+ /**
94
+ * 'json': 객체 → JSON.stringify + Content-Type: application/json
95
+ * 'form': 객체 → FormData (Content-Type은 브라우저가 boundary 붙이므로 건드리지 않음)
96
+ * 'urlencoded': 객체 → URLSearchParams + Content-Type: application/x-www-form-urlencoded
97
+ * 'text': String(value) + Content-Type: text/plain
98
+ * 미지정: 그대로 통과 (가공 없음). 이미 BodyInit 인 값은 가공하지 않는다.
99
+ */
100
+ process?: 'json' | 'form' | 'urlencoded' | 'text';
101
+ manual?: never;
102
+ };
103
+
104
+ /** 수동형: manual 이 요청을 전부 담당. url/request/process 는 쓰이지 않으므로 금지 */
105
+ export type FetchManual = {
106
+ manual: FetchManualFn;
107
+ url?: never;
108
+ request?: never;
109
+ process?: never;
110
+ };
111
+
112
+ export type FetchOptions =
113
+ // before(기본): (선언형 fetch | manual) → settled를 @fetchSettled 자리에
114
+ // 주입해 메서드 실행. 메서드 결과 리턴. request 없으면 GET. valueKey 는 after 전용이라 금지.
115
+ | (FetchType & { trigger?: 'before'; valueKey?: never } & (FetchDeclarative<RequestFactory> | FetchManual))
116
+ // after: 메서드 실행 → 리턴값(valueKey 가 있으면 returnValue[valueKey])을 body(수동형이면 returnValue)로 → fetch. fetch 결과 리턴.
117
+ // (valueKey 기본값 = FETCH_METADATA_KEY: Symbol 키 우선 조회, 없으면 통째 값)
118
+ | (FetchType & { trigger: 'after'; valueKey?: string } & (FetchDeclarative<ReturnRequestFactory> | FetchManual))
119
+ // after 축약: trigger 없이 valueKey 만 줘도 after
120
+ | (FetchType & { trigger?: never; valueKey: string } & (FetchDeclarative<ReturnRequestFactory> | FetchManual));
121
+
122
+ // 인스턴스별 진행 중인 요청의 컨트롤러 (disconnect 시 FetchLifeCycler 가 전부 abort)
123
+ const getActiveControllers = (inst: any): Set<AbortController> => (inst.__swc_fetchControllers ??= new Set());
124
+ // abortPrevious: 메서드별 마지막 호출의 컨트롤러
125
+ const getLatestControllers = (inst: any): Map<string | symbol, AbortController> => (inst.__swc_fetchLatest ??= new Map());
126
+
127
+ const abortError = (message: string) => new DOMException(message, 'AbortError');
128
+
129
+ // 사용자가 request 로 준 signal 과 프레임워크 signal 을 합친다 (AbortSignal.any 미지원 브라우저 대비)
130
+ const combineSignals = (user: AbortSignal | null | undefined, own: AbortSignal): AbortSignal => {
131
+ if (!user) return own;
132
+ if (typeof (AbortSignal as any).any === 'function') return (AbortSignal as any).any([user, own]);
133
+ const merged = new AbortController();
134
+ for (const s of [user, own]) {
135
+ if (s.aborted) {
136
+ merged.abort(s.reason);
137
+ break;
138
+ }
139
+ s.addEventListener('abort', () => merged.abort(s.reason), { once: true });
140
+ }
141
+ return merged.signal;
142
+ };
143
+
144
+ function createFetch(optionsOrUrl: FetchOptions | string): MethodDecorator {
145
+ const options: FetchOptions = typeof optionsOrUrl === 'string' ? { url: optionsOrUrl } : optionsOrUrl;
146
+ return (target: Object, propertyKey: string | symbol, descriptor?: PropertyDescriptor) => {
147
+ const constructor = target.constructor;
148
+ const list = (ReflectUtils.getOwnMetadata(FETCH_METADATA_KEY, constructor) as FetchMetadata[] | undefined) ?? [];
149
+ list.push({ propertyKey, options });
150
+ ReflectUtils.defineMetadata(FETCH_METADATA_KEY, list, constructor);
151
+
152
+ if (!descriptor) return descriptor;
153
+ const originalMethod = descriptor.value;
154
+
155
+ const extractBody = (v: any) => {
156
+ const keyToUse = (options as { valueKey?: symbol | string }).valueKey ?? FETCH_METADATA_KEY;
157
+ if (v && typeof v === 'object' && keyToUse in v) {
158
+ return v[keyToUse];
159
+ }
160
+ return v;
161
+ };
162
+
163
+ const resolveUrl = (inst: any, helper: HelperHostSet, args: any[]): RequestInfo | URL => {
164
+ const u = (options as FetchDeclarative<unknown>).url;
165
+ return typeof u === 'function' ? (u as FetchUrlFactory)(inst, helper, args) : u;
166
+ };
167
+
168
+ // 이미 BodyInit 인 값은 process 로 가공하지 않고 그대로 보낸다 (Blob 을 JSON.stringify 하면 '{}' 가 됨)
169
+ const isBodyInit = (v: any): boolean => {
170
+ if (typeof v === 'string') return true;
171
+ const is = (name: string) => typeof (globalThis as any)[name] === 'function' && v instanceof (globalThis as any)[name];
172
+ return ArrayBuffer.isView(v) || v instanceof ArrayBuffer || is('Blob') || is('FormData') || is('URLSearchParams') || is('ReadableStream');
173
+ };
174
+
175
+ // 204/빈 body 는 undefined, JSON 계열(application/json, +json)은 파싱, 나머지는 text
176
+ const readBody = async (res: Response): Promise<any> => {
177
+ const text = await res.text();
178
+ if (!text) return undefined;
179
+ return (res.headers.get('Content-Type') ?? '').includes('json') ? JSON.parse(text) : text;
180
+ };
181
+
182
+ const bodyProcess = (options as FetchDeclarative<unknown>).process;
183
+ const defaultFetching = async (helper: HelperHostSet | undefined, url: RequestInfo | URL, init?: RequestInit): Promise<any> => {
184
+ const rawBody: any = init?.body;
185
+ let body: BodyInit | undefined = rawBody;
186
+ let contentType: string | undefined;
187
+ if (rawBody !== undefined && rawBody !== null && !isBodyInit(rawBody)) {
188
+ if (bodyProcess === 'json') {
189
+ body = JSON.stringify(rawBody);
190
+ contentType = 'application/json';
191
+ } else if (bodyProcess === 'form') {
192
+ const fd = new FormData();
193
+ for (const [k, v] of Object.entries(rawBody)) {
194
+ if (v !== undefined && v !== null) fd.append(k, v as any);
195
+ }
196
+ body = fd;
197
+ } else if (bodyProcess === 'urlencoded') {
198
+ body = new URLSearchParams(rawBody as Record<string, string>).toString();
199
+ contentType = 'application/x-www-form-urlencoded';
200
+ } else if (bodyProcess === 'text') {
201
+ body = String(rawBody);
202
+ contentType = 'text/plain';
203
+ }
204
+ }
205
+ // Headers 인스턴스/배열/객체 모두 받는다 (객체 spread 는 Headers 인스턴스를 {} 로 날림). 사용자가 준 Content-Type 우선.
206
+ const headers = new Headers(init?.headers);
207
+ if (contentType && !headers.has('Content-Type')) headers.set('Content-Type', contentType);
208
+ // 요소가 속한 window 의 fetch (SSR 이면 요청별 window). 없으면 globalThis.fetch
209
+ const w = helper?.$w as any;
210
+ const fetchFn: typeof globalThis.fetch = typeof w?.fetch === 'function' ? w.fetch.bind(w) : globalThis.fetch;
211
+ const res: Response = await fetchFn(url, {
212
+ ...init,
213
+ method: init?.method ?? (body !== undefined ? 'POST' : 'GET'),
214
+ headers,
215
+ body
216
+ });
217
+ if (!res.ok) {
218
+ // status/응답 body 를 코드에서 꺼낼 수 있게 core 의 HttpResponseError 로 던진다
219
+ const error = new HttpResponseError(`fetch failed: ${res.status} ${String(url)}`);
220
+ error.response = res;
221
+ error.body = await readBody(res).catch(() => undefined);
222
+ throw error;
223
+ }
224
+ return readBody(res);
225
+ };
226
+
227
+ // 호출 인자는 유지한 채, @fetchSettled 자리만 채운다
228
+ const injectSettled = (inst: any, methodName: string | symbol, args: any[], settled: PromiseSettledResult<any>): any[] => {
229
+ const saves = getParameterMetadata(inst, methodName);
230
+ if (!saves.length) return args;
231
+ const out = [...args];
232
+ for (const s of saves) {
233
+ if (s.kind === 'fetchSettled') out[s.index] = settled;
234
+ }
235
+ return out;
236
+ };
237
+
238
+ descriptor.value = async function (...args: any[]) {
239
+ const inst = this as any;
240
+ let helper: HelperHostSet | undefined;
241
+ try {
242
+ helper = SwcUtils.getHelperAndHostSet(inst);
243
+ } catch {
244
+ helper = undefined;
245
+ }
246
+
247
+ // disconnect 된 뒤에 새로 시작하는 호출(예: 떠나기 직전 걸어둔 setTimeout)도 조용히 건너뛴다.
248
+ // 한 번도 connect 안 된 요소는 해당 없음 (FetchLifeCycler 가 disconnect 때만 표시)
249
+ if (inst?.__swc_fetchDetached && inst.isConnected === false) return undefined;
250
+
251
+ // filter → before → (before 모드: fetch → 메서드 / after 모드: 메서드 → fetch) → finally
252
+ if (options.filter && !(await options.filter(inst, helper!, args))) return undefined;
253
+ if (options.before) await options.before(inst, helper!, args);
254
+
255
+ const manual = (options as FetchManual).manual;
256
+
257
+ // 프레임워크가 끊는 경우(disconnect / abortPrevious)는 에러가 아니라 "할 일 없음" → 조용히 undefined
258
+ const controller = new AbortController();
259
+ const active = getActiveControllers(inst);
260
+ active.add(controller);
261
+ if (options.abortPrevious) {
262
+ const latest = getLatestControllers(inst);
263
+ latest.get(propertyKey)?.abort(abortError('superseded by a newer call'));
264
+ latest.set(propertyKey, controller);
265
+ }
266
+ const frameworkAborted = () => controller.signal.aborted;
267
+
268
+ // 사용자 manual 이 signal 을 무시해도 abort 즉시 호출이 끝나도록 abort 와 경쟁시킨다
269
+ const aborted = new Promise<never>((_, reject) => {
270
+ if (controller.signal.aborted) reject(controller.signal.reason);
271
+ else controller.signal.addEventListener('abort', () => reject(controller.signal.reason), { once: true });
272
+ });
273
+ aborted.catch(() => {}); // race 에 안 쓰이고 끝나도 unhandled rejection 이 안 나게
274
+
275
+ // 수동형이면 manual(…, signal, returnValue), 선언형이면 url/init 으로 기본 fetch
276
+ const runFetch = async (init?: RequestInit, returnValue?: any): Promise<PromiseSettledResult<any>> => {
277
+ let settled: PromiseSettledResult<any>;
278
+ try {
279
+ const pending = Promise.resolve(manual
280
+ ? manual(inst, helper!, args, controller.signal, returnValue)
281
+ : defaultFetching(helper, resolveUrl(inst, helper!, args), { ...init, signal: combineSignals(init?.signal, controller.signal) }));
282
+ pending.catch(() => {}); // abort 로 race 에서 진 뒤 늦게 reject 돼도 unhandled rejection 이 안 나게
283
+ settled = { status: 'fulfilled', value: await Promise.race([pending, aborted]) };
284
+ } catch (reason) {
285
+ settled = { status: 'rejected', reason };
286
+ }
287
+ // 떨어진 요소의 DOM 을 건드리지 않게, 프레임워크 abort 면 after 훅은 건너뛴다
288
+ if (options.after && !frameworkAborted()) await options.after(inst, helper!, args, settled);
289
+ return settled;
290
+ };
291
+
292
+ let callArgs = args;
293
+ let result: any;
294
+ let error: any;
295
+ try {
296
+ const trigger = (options as { trigger?: string }).trigger;
297
+ const isAfter = trigger === 'after' || (trigger !== 'before' && 'valueKey' in options);
298
+ if (isAfter) {
299
+ // after: 메서드 실행 → body 추출 → request 와 합쳐 fetch → fetch 결과 리턴.
300
+ const returned = await originalMethod.apply(inst, callArgs);
301
+ const body = extractBody(returned);
302
+ let settled: PromiseSettledResult<any>;
303
+ if (manual) {
304
+ settled = await runFetch(undefined, body);
305
+ } else {
306
+ const reqOpt = (options as FetchDeclarative<ReturnRequestFactory>).request;
307
+ const reqInit: RequestInit | undefined = typeof reqOpt === 'string'
308
+ ? { method: reqOpt }
309
+ : typeof reqOpt === 'function' ? await reqOpt(inst, helper!, args, returned) : undefined;
310
+ settled = await runFetch({ ...reqInit, body: reqInit?.body ?? body });
311
+ }
312
+ if (frameworkAborted()) {
313
+ error = controller.signal.reason;
314
+ return undefined;
315
+ }
316
+ if (settled.status === 'rejected') throw settled.reason;
317
+ result = settled.value;
318
+ } else {
319
+ // before: request 조립 → fetch → settled 를 주입해 메서드 실행. 메서드 결과 리턴.
320
+ const reqOpt = manual ? undefined : (options as FetchDeclarative<RequestFactory>).request;
321
+ const init: RequestInit | undefined = typeof reqOpt === 'string' ? { method: reqOpt } : typeof reqOpt === 'function' ? await reqOpt(inst, helper!, args) : undefined;
322
+ const settled = await runFetch(init);
323
+ if (frameworkAborted()) {
324
+ // 떨어진 요소에 그리지 않게 메서드도 실행하지 않는다
325
+ error = controller.signal.reason;
326
+ return undefined;
327
+ }
328
+ callArgs = injectSettled(inst, propertyKey, args, settled);
329
+ result = await originalMethod.apply(inst, callArgs);
330
+ }
331
+ return result;
332
+ } catch (e) {
333
+ error = e;
334
+ throw e;
335
+ } finally {
336
+ active.delete(controller);
337
+ if (options.abortPrevious && getLatestControllers(inst).get(propertyKey) === controller) getLatestControllers(inst).delete(propertyKey);
338
+ // finally 는 정리용이라 프레임워크 abort 여도 실행 (ctx.error 에 AbortError)
339
+ if (options.finally) await options.finally(inst, helper!, args, { args: callArgs, result, error });
340
+ }
341
+ };
342
+
343
+ return descriptor;
344
+ };
345
+ }
346
+
347
+ export function fetch(url: string): MethodDecorator;
348
+ export function fetch(options: FetchOptions): MethodDecorator;
349
+ export function fetch(optionsOrUrl: FetchOptions | string): MethodDecorator {
350
+ return createFetch(optionsOrUrl);
351
+ }
352
+
353
+ // ─── Aliases ───
354
+
355
+ /** trigger/valueKey 조합 (before 에는 valueKey 금지) */
356
+ type FetchTriggerOptions =
357
+ | { trigger?: 'before'; valueKey?: never }
358
+ | { trigger: 'after'; valueKey?: string }
359
+ | { trigger?: never; valueKey: string };
360
+
361
+ /** fetchManual 의 나머지 옵션 (공통 훅 + trigger/valueKey) */
362
+ export type FetchManualOptions = FetchType & FetchTriggerOptions;
363
+
364
+ /**
365
+ * @fetchManual(fn, options?) = @fetch({ manual: fn, ...options })
366
+ * 서비스 호출 등 요청을 직접 만들 때. 예: @fetchManual((self) => self.myService.getProfile({}))
367
+ */
368
+ export function fetchManual(manual: FetchManualFn, options?: FetchManualOptions): MethodDecorator {
369
+ return createFetch({ ...(options ?? {}), manual } as FetchOptions);
370
+ }
371
+
372
+ /** GET/DELETE(before 모드) 별칭의 옵션 — 메서드는 고정이라 request 는 팩토리만 (method 는 덮어씀) */
373
+ export type FetchBeforeMethodOptions = FetchType & Omit<FetchDeclarative<RequestFactory>, 'url' | 'request' | 'manual'> & { request?: RequestFactory };
374
+ /** POST/PUT/PATCH(after 모드) 별칭의 옵션 — 메서드 리턴값(또는 returnValue[valueKey])이 body */
375
+ export type FetchAfterMethodOptions = FetchType & Omit<FetchDeclarative<ReturnRequestFactory>, 'url' | 'request' | 'manual'> & { request?: ReturnRequestFactory; valueKey?: string };
376
+
377
+ type FetchUrl = RequestInfo | URL | FetchUrlFactory;
378
+
379
+ const beforeMethod = (method: string) => (url: FetchUrl, options?: FetchBeforeMethodOptions): MethodDecorator => {
380
+ const { request, ...rest } = options ?? {};
381
+ return createFetch({
382
+ ...rest,
383
+ url,
384
+ trigger: 'before',
385
+ request: (c, h, p) => ({ ...(request?.(c, h, p) ?? {}), method })
386
+ } as FetchOptions);
387
+ };
388
+
389
+ const afterMethod = (method: string) => (url: FetchUrl, options?: FetchAfterMethodOptions): MethodDecorator => {
390
+ const { request, ...rest } = options ?? {};
391
+ return createFetch({
392
+ ...rest,
393
+ url,
394
+ trigger: 'after',
395
+ request: (c, h, p, returnValue) => ({ ...(request?.(c, h, p, returnValue) ?? {}), method })
396
+ } as FetchOptions);
397
+ };
398
+
399
+ /** @fetchGet(url, options?) — before 모드 GET. 결과는 @fetchSettled 으로 */
400
+ export const fetchGet = beforeMethod('GET');
401
+ /** @fetchDelete(url, options?) — before 모드 DELETE (body 없음) */
402
+ export const fetchDelete = beforeMethod('DELETE');
403
+ /** @fetchPost(url, options?) — after 모드 POST. 메서드 리턴값이 body, fetch 결과를 리턴 */
404
+ export const fetchPost = afterMethod('POST');
405
+ /** @fetchPut(url, options?) — after 모드 PUT */
406
+ export const fetchPut = afterMethod('PUT');
407
+ /** @fetchPatch(url, options?) — after 모드 PATCH */
408
+ export const fetchPatch = afterMethod('PATCH');
409
+
410
+ /**
411
+ * @fetchLatest(url | options) = @fetch({ ...options, abortPrevious: true })
412
+ * 연달아 부르면 이전 요청을 끊고 마지막 것만 살린다 (검색 자동완성 등)
413
+ */
414
+ export function fetchLatest(url: string): MethodDecorator;
415
+ export function fetchLatest(options: FetchOptions): MethodDecorator;
416
+ export function fetchLatest(optionsOrUrl: FetchOptions | string): MethodDecorator {
417
+ const options: FetchOptions = typeof optionsOrUrl === 'string' ? { url: optionsOrUrl } : optionsOrUrl;
418
+ return createFetch({ ...options, abortPrevious: true } as FetchOptions);
419
+ }
420
+
421
+ /** @fetchBefore(options) = @fetch({ ...options, trigger: 'before' }) */
422
+ export function fetchBefore(options: FetchType & (FetchDeclarative<RequestFactory> | FetchManual)): MethodDecorator {
423
+ return createFetch({ ...options, trigger: 'before' } as FetchOptions);
424
+ }
425
+
426
+ /** @fetchAfter(options) = @fetch({ ...options, trigger: 'after' }) */
427
+ export function fetchAfter(options: FetchType & { valueKey?: string } & (FetchDeclarative<ReturnRequestFactory> | FetchManual)): MethodDecorator {
428
+ return createFetch({ ...options, trigger: 'after' } as FetchOptions);
429
+ }
430
+
431
+ /** @fetch 가 붙은 메서드 목록 조회 */
432
+ export const getFetchMetadata = (target: any): FetchMetadata[] | undefined => {
433
+ const constructor = typeof target === 'function' ? target : target.constructor;
434
+ return ReflectUtils.findAllMetadata<any[]>(FETCH_METADATA_KEY, constructor).flat();
435
+ };
436
+
437
+ /**
438
+ * disconnect 시 진행 중인 @fetch 요청을 전부 abort (메서드/after 는 실행되지 않고 조용히 끝난다).
439
+ * 떨어진 동안 새로 시작하는 호출도 건너뛰도록 표시하고, 다시 connect 되면 표시를 지운다.
440
+ */
441
+ export class FetchLifeCycler implements ElementDefineLifeCycler {
442
+ onConnected(helperHostSet: HelperHostSet): void {
443
+ helperHostSet.$this.__swc_fetchDetached = false;
444
+ }
445
+
446
+ onDisconnected(helperHostSet: HelperHostSet): void {
447
+ const inst = helperHostSet.$this;
448
+ inst.__swc_fetchDetached = true;
449
+ const active: Set<AbortController> | undefined = inst.__swc_fetchControllers;
450
+ if (!active?.size) return;
451
+ for (const c of active) c.abort(abortError('element disconnected'));
452
+ active.clear();
453
+ }
454
+ }
@@ -13,8 +13,9 @@ export * from './applyClass';
13
13
  export * from './applyStyle';
14
14
  export * from './applyAttribute';
15
15
  export * from './applyProperty';
16
- export * from './subscribeSwcAppRouteChangeWhileConnected';
17
- export * from './subscribeSwcAppMessageWhileConnected';
16
+ export * from './subscribeSwcAppRouteChange';
17
+ export * from './swcAppRoute';
18
+ export * from './subscribeSwcAppMessage';
18
19
  export * from './publishSwcAppMessage';
19
20
  export * from './applySlot';
20
21
  export * from './state';
@@ -23,3 +24,5 @@ export * from './setTimeout';
23
24
  export * from './requestAnimationFrame';
24
25
  export * from './eventMedia';
25
26
  export * from './parameter';
27
+ export * from './fetch';
28
+ export * from './around';