@dooboostore/simple-web-component 1.0.49 → 1.0.51

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 (251) hide show
  1. package/README.md +790 -337
  2. package/dist/cjs/SwcAppEngine.js.map +2 -2
  3. package/dist/cjs/decorators/addEventListener.js +1925 -22
  4. package/dist/cjs/decorators/addEventListener.js.map +2 -2
  5. package/dist/cjs/decorators/applyAttribute.js +41 -47
  6. package/dist/cjs/decorators/applyAttribute.js.map +2 -2
  7. package/dist/cjs/decorators/applyClass.js +14 -11
  8. package/dist/cjs/decorators/applyClass.js.map +2 -2
  9. package/dist/cjs/decorators/applyNode.js +19 -21
  10. package/dist/cjs/decorators/applyNode.js.map +2 -2
  11. package/dist/cjs/decorators/applyProperty.js +24 -48
  12. package/dist/cjs/decorators/applyProperty.js.map +2 -2
  13. package/dist/cjs/decorators/applySlot.js +9 -8
  14. package/dist/cjs/decorators/applySlot.js.map +2 -2
  15. package/dist/cjs/decorators/applyStyle.js +14 -11
  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 +61 -24
  20. package/dist/cjs/decorators/changedAttribute.js.map +2 -2
  21. package/dist/cjs/decorators/elementDefine.js +240 -196
  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 +112 -0
  26. package/dist/cjs/decorators/eventMedia.js.map +7 -0
  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 +8 -2
  30. package/dist/cjs/decorators/index.js.map +2 -2
  31. package/dist/cjs/decorators/intersectionObserver.js +76 -65
  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 +97 -90
  36. package/dist/cjs/decorators/mutationObserver.js.map +3 -3
  37. package/dist/cjs/decorators/parameter.js +156 -0
  38. package/dist/cjs/decorators/parameter.js.map +7 -0
  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 +115 -0
  44. package/dist/cjs/decorators/requestAnimationFrame.js.map +7 -0
  45. package/dist/cjs/decorators/resizeObserver.js +84 -69
  46. package/dist/cjs/decorators/resizeObserver.js.map +3 -3
  47. package/dist/cjs/decorators/setInterval.js +65 -22
  48. package/dist/cjs/decorators/setInterval.js.map +2 -2
  49. package/dist/cjs/decorators/setTimeout.js +68 -22
  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 +256 -67
  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/types/window.d.js.map +1 -1
  66. package/dist/cjs/utils/Utils.js +36 -15
  67. package/dist/cjs/utils/Utils.js.map +2 -2
  68. package/dist/esm/SwcAppEngine.js.map +2 -2
  69. package/dist/esm/decorators/addEventListener.js +1925 -22
  70. package/dist/esm/decorators/addEventListener.js.map +2 -2
  71. package/dist/esm/decorators/applyAttribute.js +41 -47
  72. package/dist/esm/decorators/applyAttribute.js.map +2 -2
  73. package/dist/esm/decorators/applyClass.js +14 -11
  74. package/dist/esm/decorators/applyClass.js.map +2 -2
  75. package/dist/esm/decorators/applyNode.js +20 -22
  76. package/dist/esm/decorators/applyNode.js.map +2 -2
  77. package/dist/esm/decorators/applyProperty.js +24 -48
  78. package/dist/esm/decorators/applyProperty.js.map +2 -2
  79. package/dist/esm/decorators/applySlot.js +9 -8
  80. package/dist/esm/decorators/applySlot.js.map +2 -2
  81. package/dist/esm/decorators/applyStyle.js +14 -11
  82. package/dist/esm/decorators/applyStyle.js.map +2 -2
  83. package/dist/esm/decorators/around.js +105 -0
  84. package/dist/esm/decorators/around.js.map +7 -0
  85. package/dist/esm/decorators/changedAttribute.js +61 -24
  86. package/dist/esm/decorators/changedAttribute.js.map +2 -2
  87. package/dist/esm/decorators/elementDefine.js +238 -194
  88. package/dist/esm/decorators/elementDefine.js.map +3 -3
  89. package/dist/esm/decorators/emitCustomEvent.js +15 -13
  90. package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
  91. package/dist/esm/decorators/eventMedia.js +94 -0
  92. package/dist/esm/decorators/eventMedia.js.map +7 -0
  93. package/dist/esm/decorators/fetch.js +259 -0
  94. package/dist/esm/decorators/fetch.js.map +7 -0
  95. package/dist/esm/decorators/index.js +8 -2
  96. package/dist/esm/decorators/index.js.map +2 -2
  97. package/dist/esm/decorators/intersectionObserver.js +76 -65
  98. package/dist/esm/decorators/intersectionObserver.js.map +2 -2
  99. package/dist/esm/decorators/lifecycles.js +11 -11
  100. package/dist/esm/decorators/lifecycles.js.map +2 -2
  101. package/dist/esm/decorators/mutationObserver.js +97 -90
  102. package/dist/esm/decorators/mutationObserver.js.map +3 -3
  103. package/dist/esm/decorators/parameter.js +137 -0
  104. package/dist/esm/decorators/parameter.js.map +7 -0
  105. package/dist/esm/decorators/publishSwcAppMessage.js +2 -2
  106. package/dist/esm/decorators/publishSwcAppMessage.js.map +2 -2
  107. package/dist/esm/decorators/query.js +11 -5
  108. package/dist/esm/decorators/query.js.map +2 -2
  109. package/dist/esm/decorators/requestAnimationFrame.js +96 -0
  110. package/dist/esm/decorators/requestAnimationFrame.js.map +7 -0
  111. package/dist/esm/decorators/resizeObserver.js +84 -69
  112. package/dist/esm/decorators/resizeObserver.js.map +2 -2
  113. package/dist/esm/decorators/setInterval.js +65 -23
  114. package/dist/esm/decorators/setInterval.js.map +2 -2
  115. package/dist/esm/decorators/setTimeout.js +68 -23
  116. package/dist/esm/decorators/setTimeout.js.map +2 -2
  117. package/dist/esm/decorators/state.js +13 -10
  118. package/dist/esm/decorators/state.js.map +2 -2
  119. package/dist/esm/decorators/subscribeSwcAppMessage.js +53 -0
  120. package/dist/esm/decorators/subscribeSwcAppMessage.js.map +7 -0
  121. package/dist/esm/decorators/{subscribeSwcAppRouteChangeWhileConnected.js → subscribeSwcAppRouteChange.js} +20 -16
  122. package/dist/esm/decorators/subscribeSwcAppRouteChange.js.map +7 -0
  123. package/dist/esm/decorators/swcAppRoute.js +130 -0
  124. package/dist/esm/decorators/swcAppRoute.js.map +7 -0
  125. package/dist/esm/elements/SwcApp.js +70 -0
  126. package/dist/esm/elements/SwcApp.js.map +2 -2
  127. package/dist/esm/elements/SwcAppMixin.js +258 -69
  128. package/dist/esm/elements/SwcAppMixin.js.map +3 -3
  129. package/dist/esm/types/index.js +5 -1
  130. package/dist/esm/types/index.js.map +2 -2
  131. package/dist/esm/utils/Utils.js +36 -15
  132. package/dist/esm/utils/Utils.js.map +2 -2
  133. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +4395 -1266
  134. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  135. package/dist/types/SwcAppEngine.d.ts +1 -0
  136. package/dist/types/SwcAppEngine.d.ts.map +1 -1
  137. package/dist/types/decorators/addEventListener.d.ts +936 -11
  138. package/dist/types/decorators/addEventListener.d.ts.map +1 -1
  139. package/dist/types/decorators/applyAttribute.d.ts +27 -37
  140. package/dist/types/decorators/applyAttribute.d.ts.map +1 -1
  141. package/dist/types/decorators/applyClass.d.ts +2 -2
  142. package/dist/types/decorators/applyClass.d.ts.map +1 -1
  143. package/dist/types/decorators/applyNode.d.ts +16 -16
  144. package/dist/types/decorators/applyNode.d.ts.map +1 -1
  145. package/dist/types/decorators/applyProperty.d.ts +28 -44
  146. package/dist/types/decorators/applyProperty.d.ts.map +1 -1
  147. package/dist/types/decorators/applySlot.d.ts.map +1 -1
  148. package/dist/types/decorators/applyStyle.d.ts +2 -2
  149. package/dist/types/decorators/applyStyle.d.ts.map +1 -1
  150. package/dist/types/decorators/around.d.ts +87 -0
  151. package/dist/types/decorators/around.d.ts.map +1 -0
  152. package/dist/types/decorators/changedAttribute.d.ts +22 -4
  153. package/dist/types/decorators/changedAttribute.d.ts.map +1 -1
  154. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  155. package/dist/types/decorators/emitCustomEvent.d.ts +0 -1
  156. package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
  157. package/dist/types/decorators/eventMedia.d.ts +48 -0
  158. package/dist/types/decorators/eventMedia.d.ts.map +1 -0
  159. package/dist/types/decorators/fetch.d.ts +139 -0
  160. package/dist/types/decorators/fetch.d.ts.map +1 -0
  161. package/dist/types/decorators/index.d.ts +8 -2
  162. package/dist/types/decorators/index.d.ts.map +1 -1
  163. package/dist/types/decorators/intersectionObserver.d.ts +32 -10
  164. package/dist/types/decorators/intersectionObserver.d.ts.map +1 -1
  165. package/dist/types/decorators/lifecycles.d.ts +8 -7
  166. package/dist/types/decorators/lifecycles.d.ts.map +1 -1
  167. package/dist/types/decorators/mutationObserver.d.ts +34 -12
  168. package/dist/types/decorators/mutationObserver.d.ts.map +1 -1
  169. package/dist/types/decorators/parameter.d.ts +113 -0
  170. package/dist/types/decorators/parameter.d.ts.map +1 -0
  171. package/dist/types/decorators/publishSwcAppMessage.d.ts +0 -1
  172. package/dist/types/decorators/publishSwcAppMessage.d.ts.map +1 -1
  173. package/dist/types/decorators/query.d.ts +7 -7
  174. package/dist/types/decorators/query.d.ts.map +1 -1
  175. package/dist/types/decorators/requestAnimationFrame.d.ts +47 -0
  176. package/dist/types/decorators/requestAnimationFrame.d.ts.map +1 -0
  177. package/dist/types/decorators/resizeObserver.d.ts +34 -12
  178. package/dist/types/decorators/resizeObserver.d.ts.map +1 -1
  179. package/dist/types/decorators/setInterval.d.ts +27 -5
  180. package/dist/types/decorators/setInterval.d.ts.map +1 -1
  181. package/dist/types/decorators/setTimeout.d.ts +27 -5
  182. package/dist/types/decorators/setTimeout.d.ts.map +1 -1
  183. package/dist/types/decorators/state.d.ts.map +1 -1
  184. package/dist/types/decorators/subscribeSwcAppMessage.d.ts +44 -0
  185. package/dist/types/decorators/subscribeSwcAppMessage.d.ts.map +1 -0
  186. package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts +60 -0
  187. package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts.map +1 -0
  188. package/dist/types/decorators/swcAppRoute.d.ts +94 -0
  189. package/dist/types/decorators/swcAppRoute.d.ts.map +1 -0
  190. package/dist/types/elements/SwcApp.d.ts.map +1 -1
  191. package/dist/types/elements/SwcAppMixin.d.ts +423 -388
  192. package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
  193. package/dist/types/types/index.d.ts +43 -4
  194. package/dist/types/types/index.d.ts.map +1 -1
  195. package/dist/types/utils/Utils.d.ts +15 -5
  196. package/dist/types/utils/Utils.d.ts.map +1 -1
  197. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +4395 -1266
  198. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  199. package/package.json +8 -6
  200. package/src/SwcAppEngine.ts +1 -0
  201. package/src/decorators/addEventListener.ts +1086 -28
  202. package/src/decorators/applyAttribute.ts +82 -134
  203. package/src/decorators/applyClass.ts +14 -10
  204. package/src/decorators/applyNode.ts +39 -37
  205. package/src/decorators/applyProperty.ts +72 -100
  206. package/src/decorators/applySlot.ts +9 -8
  207. package/src/decorators/applyStyle.ts +14 -10
  208. package/src/decorators/around.ts +196 -0
  209. package/src/decorators/changedAttribute.ts +65 -26
  210. package/src/decorators/elementDefine.ts +298 -220
  211. package/src/decorators/emitCustomEvent.ts +17 -13
  212. package/src/decorators/eventMedia.ts +119 -0
  213. package/src/decorators/fetch.ts +454 -0
  214. package/src/decorators/index.ts +8 -2
  215. package/src/decorators/intersectionObserver.ts +108 -78
  216. package/src/decorators/lifecycles.ts +24 -22
  217. package/src/decorators/mutationObserver.ts +140 -104
  218. package/src/decorators/parameter.ts +212 -0
  219. package/src/decorators/publishSwcAppMessage.ts +3 -4
  220. package/src/decorators/query.ts +22 -14
  221. package/src/decorators/requestAnimationFrame.ts +149 -0
  222. package/src/decorators/resizeObserver.ts +119 -82
  223. package/src/decorators/setInterval.ts +90 -23
  224. package/src/decorators/setTimeout.ts +93 -23
  225. package/src/decorators/state.ts +19 -13
  226. package/src/decorators/subscribeSwcAppMessage.ts +106 -0
  227. package/src/decorators/subscribeSwcAppRouteChange.ts +131 -0
  228. package/src/decorators/swcAppRoute.ts +231 -0
  229. package/src/elements/SwcApp.ts +40 -10
  230. package/src/elements/SwcAppMixin.ts +329 -85
  231. package/src/types/index.ts +38 -5
  232. package/src/types/window.d.ts +2 -18
  233. package/src/utils/Utils.ts +50 -18
  234. package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js.map +0 -7
  235. package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +0 -7
  236. package/dist/cjs/examples/RxJSOperatorsExample.js +0 -285
  237. package/dist/cjs/examples/RxJSOperatorsExample.js.map +0 -7
  238. package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js +0 -54
  239. package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js.map +0 -7
  240. package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +0 -7
  241. package/dist/esm/examples/RxJSOperatorsExample.js +0 -266
  242. package/dist/esm/examples/RxJSOperatorsExample.js.map +0 -7
  243. package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts +0 -23
  244. package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts.map +0 -1
  245. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts +0 -31
  246. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts.map +0 -1
  247. package/dist/types/examples/RxJSOperatorsExample.d.ts +0 -94
  248. package/dist/types/examples/RxJSOperatorsExample.d.ts.map +0 -1
  249. package/src/decorators/subscribeSwcAppMessageWhileConnected.ts +0 -91
  250. package/src/decorators/subscribeSwcAppRouteChangeWhileConnected.ts +0 -107
  251. package/src/examples/RxJSOperatorsExample.ts +0 -279
@@ -1,10 +1,54 @@
1
- import { SwcAppInterface, SwcAppMessage } from '../types';
2
- import { APPLY_NODE_METADATA_KEY, findAllLifecycleMetadata, getSubscribeSwcAppMessageWhileConnectedMetadata, getSubscribeSwcAppRouteChangeWhileConnectedMetadata, ON_CONNECTED_SWC_APP_METADATA_KEY, SUBSCRIBE_SWC_APP_ROUTE_CHANGE_WHILE_CONNECTED_METADATA_KEY } from '../decorators';
1
+ import { SwcAppInterface, SwcAppMessage, SwcAppMessageObserveOptions, SwcElement } from '../types';
2
+ import { APPLY_NODE_METADATA_KEY, findAllLifecycleMetadata, getSubscribeSwcAppMessageMetadata, getSubscribeSwcAppRouteChangeMetadata, ON_CONNECTED_SWC_APP_METADATA_KEY, SUBSCRIBE_SWC_APP_ROUTE_CHANGE_METADATA_KEY, buildSwcParameterArgs } from '../decorators';
3
3
  import { SwcAppEngine, SwcAttributeConfigType, SwcConfigType } from '../SwcAppEngine';
4
- import { debounceTimeIntervalLock, FunctionUtils, Subscription } from '@dooboostore/core';
4
+ import { debounceTimeIntervalLock, FunctionUtils, Subscription, Subject, Observable } from '@dooboostore/core';
5
5
  import {RouterEventType, ValidUtils} from '@dooboostore/core-web';
6
6
  import { SwcUtils } from '../utils/Utils';
7
7
 
8
+ type RouteSubscriberState = { matched: boolean; signature: string; pathData?: { [k: string]: string }; pathDataAll?: { [k: string]: string[] } };
9
+ const routeStateOf = (instance: any): Map<string | symbol, RouteSubscriberState> => (instance.__swc_routeState ??= new Map());
10
+
11
+ /** 라우트 파라미터 데코레이터 값 (kind 이름 = 데코레이터 이름). 키를 받는 것은 (key) => 값 */
12
+ const routeParamValues = (sp: URLSearchParams, pathDataAll: { [k: string]: string[] }) => {
13
+ const queryAll: { [k: string]: string[] } = Object.fromEntries([...new Set(sp.keys())].map(k => [k, sp.getAll(k)]));
14
+ const pick = (all: { [k: string]: string[] }, at: 'first' | 'last') =>
15
+ Object.fromEntries(Object.entries(all).map(([k, v]) => [k, at === 'first' ? v[0] : v[v.length - 1]]));
16
+ const lastOf = (v: string[] | undefined) => (v && v.length ? v[v.length - 1] : undefined);
17
+ const firstQuery = (k: string) => sp.get(k);
18
+ const firstPath = (k: string) => pathDataAll[k]?.[0];
19
+ return {
20
+ swcAppRouteQueryParam: firstQuery,
21
+ swcAppRouteFirstQueryParam: firstQuery,
22
+ swcAppRouteLastQueryParam: (k: string) => lastOf(sp.getAll(k)) ?? null,
23
+ swcAppRouteQueryParams: (k: string) => sp.getAll(k),
24
+ swcAppRouteQueryParamObject: pick(queryAll, 'first'),
25
+ swcAppRouteFirstQueryParamObject: pick(queryAll, 'first'),
26
+ swcAppRouteLastQueryParamObject: pick(queryAll, 'last'),
27
+ swcAppRouteQueryParamsObject: queryAll,
28
+ swcAppRouteURLSearchParams: sp,
29
+ swcAppRoutePathVariable: firstPath,
30
+ swcAppRouteFirstPathVariable: firstPath,
31
+ swcAppRouteLastPathVariable: (k: string) => lastOf(pathDataAll[k]),
32
+ swcAppRoutePathVariables: (k: string) => pathDataAll[k] ?? [],
33
+ swcAppRoutePathVariableObject: pick(pathDataAll, 'first'),
34
+ swcAppRouteFirstPathVariableObject: pick(pathDataAll, 'first'),
35
+ swcAppRouteLastPathVariableObject: pick(pathDataAll, 'last'),
36
+ swcAppRoutePathVariablesObject: pathDataAll
37
+ };
38
+ };
39
+
40
+ /** 구독자의 path(문자열/배열/함수/없음)를 route 에 매칭. 안 맞으면 null */
41
+ const matchRoute = (pathOption: any, instance: any, re: { path?: string }): { pathData: { [k: string]: string }; pathDataAll: { [k: string]: string[] } } | null => {
42
+ let pattern = typeof pathOption === 'function' ? pathOption(instance) : pathOption;
43
+ if (!pattern) return { pathData: {}, pathDataAll: {} };
44
+ const path = re.path || '/';
45
+ for (const p of Array.isArray(pattern) ? pattern : [pattern]) {
46
+ const pathData = SwcUtils.parsePathPattern(p, path);
47
+ if (pathData !== null) return { pathData, pathDataAll: SwcUtils.parsePathPatternAll(p, path) ?? {} };
48
+ }
49
+ return null;
50
+ };
51
+
8
52
  export const isSSR = (i: HTMLElement) => {
9
53
  return i.hasAttribute('swc-use-ssr');
10
54
  };
@@ -25,11 +69,23 @@ export const removeSSRAttribute = (i: HTMLElement) => {
25
69
  * }
26
70
  */
27
71
  export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Base: T) {
28
- return class SwcAppMixinClass extends Base implements SwcAppInterface {
72
+ abstract class SwcAppMixinClass extends Base implements SwcAppInterface {
29
73
  __swc_engine = new SwcAppEngine(this as any);
74
+ _swcId!: string;
30
75
  _swc_connected_instance = new Set<any>();
31
76
  _routerSubscription?: Subscription;
32
77
  _lastRouterEvent?: RouterEventType;
78
+ // replay 옵션으로 발행된 타입별 마지막 메시지 1개 보관
79
+ _replayedMessages = new Map<string, SwcAppMessage[]>();
80
+ // 코드 구독(observeMessage)용 live 스트림. 데코레이터 구독과 같은 publishMessage에서 흘러나옴.
81
+ _messageSubject = new Subject<SwcAppMessage>();
82
+ // ── 호스트 라이프사이클 훅 (abstract): 상속한 클래스가 반드시 구현. @inject 파라미터가 있으면 DI로 자동 해결됨 ──
83
+ // connectedCallback 시점 (DOM에 붙을 때마다). connect() 전이라 DI 컨테이너가 아직 없을 수 있음.
84
+ abstract onConnected(...args: any[]): void | Promise<void>;
85
+ // disconnectedCallback 시점 (DOM에서 떨어질 때마다). onConnected에서 만든 구독 등을 여기서 해제.
86
+ abstract onDisconnected(...args: any[]): void | Promise<void>;
87
+ // connect() 완료 후 (DI 컨테이너·라우터 준비됨). 서비스 호출은 여기서.
88
+ abstract onSwcAppConnected(...args: any[]): void | Promise<void>;
33
89
  _routeChangeInvocations = 0;
34
90
  _connectedInvocations = 0;
35
91
 
@@ -42,11 +98,8 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
42
98
  * 부모가 자식들을 강제로 등록해주는 형태로 처리
43
99
  */
44
100
  _connected_safari_and_standby = [];
45
-
46
- constructor(...args: any[]) {
47
- super(...args);
48
- console.log('[SWC-APP-MIXIN] constructor');
49
- }
101
+ // connect() 전에 붙은 자식들이 기다리는 곳 — connect() 가 엔진을 띄운 뒤 깨운다
102
+ _appReadyWaiters: Array<() => void> = [];
50
103
 
51
104
  get simpleApplication() {
52
105
  return this.__swc_engine.simpleApplication;
@@ -60,7 +113,8 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
60
113
  return this.__swc_engine?.router;
61
114
  }
62
115
 
63
- async _invokeRouteChangeSubscribers(instance: HTMLElement, re: RouterEventType) {
116
+ // phase: 'live'(기본, 전체 방송) | 'connected' | 'connectedDone' (replay, 해당 trigger만)
117
+ async _invokeRouteChangeSubscribers(instance: HTMLElement, re: RouterEventType, phase: 'live' | 'connected' | 'connectedDone' = 'live') {
64
118
  if (instance.isConnected === false) return;
65
119
 
66
120
  this._routeChangeInvocations++;
@@ -80,59 +134,70 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
80
134
  );
81
135
 
82
136
  try {
83
- const routeChangeSubscribers = getSubscribeSwcAppRouteChangeWhileConnectedMetadata(instance);
137
+ const routeChangeSubscribers = getSubscribeSwcAppRouteChangeMetadata(instance);
84
138
 
85
139
  if (routeChangeSubscribers.length > 0) {
86
- console.log('[SWC-MIXIN] Route change subscribers found');
87
-
88
- // Subscribers are already sorted by order from getSubscribeSwcAppRouteChangeWhileConnectedMetadata
89
- // Execute subscribers in order, stop if one returns a value
140
+ // Subscribers are already sorted by order from getSubscribeSwcAppRouteChangeMetadata
141
+ // Execute subscribers in order, stop if one returns a value.
142
+ // live는 전부 실행, replay는 해당 trigger 구독자만 실행.
143
+ // on(enter/update/leave) 판정을 위해 구독자별 직전 매칭 상태를 기억한다 (멈춘 뒤에도 상태는 계속 갱신).
144
+ const state = routeStateOf(instance);
145
+ let stopped = false;
90
146
  for (const metadata of routeChangeSubscribers) {
147
+ if (phase !== 'live' && (metadata.options?.trigger ?? 'connected') !== phase) continue;
148
+ const on = metadata.options?.on ?? 'match';
149
+ if (on === 'beforeLeave') continue; // 라우터 가드로 처리 (_registerLeaveGuards)
91
150
  const methodName = metadata.propertyKey;
92
- let pathPattern = metadata.options?.path as any;
93
- const filter = metadata.options?.filter;
94
151
  const extractValue = (v: any) => {
95
- const keyToUse = metadata.options?.valueKey ?? SUBSCRIBE_SWC_APP_ROUTE_CHANGE_WHILE_CONNECTED_METADATA_KEY;
152
+ const keyToUse = metadata.options?.valueKey ?? SUBSCRIBE_SWC_APP_ROUTE_CHANGE_METADATA_KEY;
96
153
  if (v && typeof v === 'object' && keyToUse in v) {
97
154
  return v[keyToUse];
98
155
  }
99
156
  return v;
100
157
  };
101
158
 
102
- if (typeof pathPattern === 'function') {
103
- pathPattern = (pathPattern as Function)(instance);
104
- }
159
+ const match = matchRoute(metadata.options?.path, instance, re);
160
+ const prev = state.get(methodName);
161
+ // update 판정 기준: 경로 + query (path 변수는 경로에서 나오므로 포함됨. 패턴 없는 bare 구독도 경로 변경을 감지)
162
+ const signature = match ? JSON.stringify([re.path ?? '', re.search ?? '']) : '';
163
+ state.set(methodName, match ? { matched: true, signature, pathData: match.pathData, pathDataAll: match.pathDataAll } : { matched: false, signature: '' });
105
164
 
106
- let pathMatched = false;
107
- let pathData: any = null;
108
-
109
- if (!pathPattern) {
110
- pathMatched = true;
111
- pathData = {};
112
- } else if (Array.isArray(pathPattern)) {
113
- for (const pattern of pathPattern) {
114
- const data = SwcUtils.parsePathPattern(pattern, re.path || '/');
115
- if (data !== null) {
116
- pathMatched = true;
117
- pathData = data;
118
- break;
119
- }
120
- }
121
- } else {
122
- pathData = SwcUtils.parsePathPattern(pathPattern, re.path || '/');
123
- pathMatched = pathData !== null;
124
- }
165
+ const shouldRun =
166
+ on === 'match' ? !!match
167
+ : on === 'enter' ? !!match && !prev?.matched
168
+ : on === 'update' ? !!match && !!prev?.matched && prev.signature !== signature
169
+ : on === 'leave' ? !match && !!prev?.matched
170
+ : false;
171
+ if (stopped || !shouldRun || !instance[methodName]) continue;
125
172
 
126
- const hostSet = SwcUtils.getHelperAndHostSet(this.config.window, this);
127
- const filterPassed = !filter || filter(this.router!, { helper: hostSet, currentThis: instance });
173
+ // leave 는 지금 경로가 안 맞으므로 직전 매칭의 pathData 로 준다
174
+ const pathData = match ? match.pathData : prev?.pathData ?? {};
175
+ const pathDataAll = match ? match.pathDataAll : prev?.pathDataAll ?? {};
128
176
 
129
- if (pathMatched && filterPassed && instance[methodName]) {
130
- const result = extractValue(await instance[methodName]({ ...re, pathData: pathData }));
131
- // if (typeof result === 'object'
132
- // If handler returns a value, stop propagation to next handlers
133
- if (result !== undefined && result !== null) {
134
- break;
135
- }
177
+ const filter = metadata.options?.filter;
178
+ const instanceHelperHostSet = SwcUtils.getHelperAndHostSet(instance, this.config.window);
179
+ const filterPassed = !filter || (await filter(this.router!, { helper: instanceHelperHostSet, currentThis: instance }));
180
+ if (!filterPassed) continue;
181
+
182
+ const before = metadata.options?.before;
183
+ const finaly = metadata.options?.finally;
184
+ // before를 먼저 돌려 리턴값을 @routeChangeBeforeReturn 으로 주입할 수 있게 캡처.
185
+ const beforeReturn = before ? await before(this.router!, { helper: instanceHelperHostSet, currentThis: instance }) : undefined;
186
+ const routeEventValue = { ...re, pathData };
187
+ const args = this._buildRouteArgs(instance, methodName, routeEventValue, pathDataAll, beforeReturn);
188
+ let rawResult: any, error: any;
189
+ try {
190
+ rawResult = await instance[methodName](...args);
191
+ } catch (e) {
192
+ error = e;
193
+ } finally {
194
+ if (finaly) await finaly(this.router!, { helper: instanceHelperHostSet, currentThis: instance }, { args, result: rawResult, error });
195
+ }
196
+ if (error) throw error;
197
+ const result = extractValue(rawResult);
198
+ // If handler returns a value, stop propagation to next handlers
199
+ if (result !== undefined && result !== null) {
200
+ stopped = true;
136
201
  }
137
202
  }
138
203
  }
@@ -148,11 +213,70 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
148
213
  }
149
214
  }
150
215
 
216
+ /** 라우트 핸들러 인자: @swcAppRouterEvent / host 계열 / @routeChangeBeforeReturn / 라우트 파라미터(query·path 변수) */
217
+ _buildRouteArgs(instance: any, methodName: string | symbol, routeEventValue: any, pathDataAll: { [k: string]: string[] }, beforeReturn: any) {
218
+ const instanceHelperHostSet = SwcUtils.getHelperAndHostSet(instance, this.config.window);
219
+ const sp: URLSearchParams = routeEventValue.searchParams ?? new URLSearchParams(routeEventValue.search ?? '');
220
+ return buildSwcParameterArgs(
221
+ instance,
222
+ methodName,
223
+ {
224
+ swcAppRouterEvent: routeEventValue,
225
+ hostSet: instanceHelperHostSet,
226
+ helperHostSet: instanceHelperHostSet,
227
+ helperSet: SwcUtils.getHelperSet(this.config.window),
228
+ routeChangeBeforeReturn: beforeReturn,
229
+ ...routeParamValues(sp, pathDataAll)
230
+ },
231
+ [routeEventValue]
232
+ );
233
+ }
234
+
235
+ /**
236
+ * on: 'beforeLeave' 구독을 라우터 가드로 등록. 지금 경로는 맞고 다음 경로는 안 맞을 때 핸들러를 부르고,
237
+ * false 를 리턴하면 이동을 취소한다. 요소가 떨어지면 _disconnected 에서 해제.
238
+ */
239
+ _registerLeaveGuards(instance: any) {
240
+ if (!this.router?.addLeaveGuard) return;
241
+ instance.__swc_leaveGuardOffs?.forEach((off: () => void) => off());
242
+ instance.__swc_leaveGuardOffs = getSubscribeSwcAppRouteChangeMetadata(instance)
243
+ .filter(m => m.options?.on === 'beforeLeave')
244
+ .map(metadata =>
245
+ this.router!.addLeaveGuard(async (to, from) => {
246
+ if (instance.isConnected === false || !instance[metadata.propertyKey]) return true;
247
+ const fromMatch = matchRoute(metadata.options?.path, instance, from);
248
+ if (!fromMatch || matchRoute(metadata.options?.path, instance, to)) return true; // 이 라우트를 떠나는 경우만
249
+ const filter = metadata.options?.filter;
250
+ const helper = SwcUtils.getHelperAndHostSet(instance, this.config.window);
251
+ if (filter && !(await filter(this.router!, { helper, currentThis: instance }))) return true;
252
+ const beforeReturn = metadata.options?.before ? await metadata.options.before(this.router!, { helper, currentThis: instance }) : undefined;
253
+ const routeEventValue = { ...from, pathData: fromMatch.pathData, to };
254
+ const args = this._buildRouteArgs(instance, metadata.propertyKey, routeEventValue, fromMatch.pathDataAll, beforeReturn);
255
+ let result: any, error: any;
256
+ try {
257
+ result = await instance[metadata.propertyKey](...args);
258
+ } catch (e) {
259
+ error = e;
260
+ } finally {
261
+ if (metadata.options?.finally) await metadata.options.finally(this.router!, { helper, currentThis: instance }, { args, result, error });
262
+ }
263
+ if (error) throw error;
264
+ return result !== false;
265
+ })
266
+ );
267
+ }
268
+
151
269
  async _connected(instance: HTMLElement, option?: { noIncrements: boolean }) {
152
270
  if (instance) {
153
271
  if (!this.simpleApplication && !this.config) {
154
- this._connected_safari_and_standby.push(instance);
155
- return;
272
+ // 자기 자신은 connect() 가 나중에 재생한다
273
+ if (instance === (this as any)) {
274
+ this._connected_safari_and_standby.push(instance);
275
+ return;
276
+ }
277
+ // 앱이 아직 connect 전: 준비될 때까지 기다린다. 안 기다리면 이미 정의된 자식이 DI·라우터 없이
278
+ // 라이프사이클(@inject 등)을 먼저 돌려 undefined 를 받는다 (elementDefine 이 이 await 뒤에 렌더한다).
279
+ await new Promise<void>(resolve => this._appReadyWaiters.push(resolve));
156
280
  }
157
281
 
158
282
  this.config.onConnectedChildBefore?.(instance);
@@ -160,10 +284,14 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
160
284
  this._connectedInvocations++;
161
285
  }
162
286
  this._swc_connected_instance.add(instance);
287
+ // await 사이에 요소가 떨어지거나 다시 붙으면(elementDefine 의 차례 번호가 바뀌면) 나머지(가드 등록·재생)는 건너뛴다
288
+ const gen = (instance as any).__swc_connectGen;
289
+ const stale = () => (instance as any).__swc_connectGen !== gen;
163
290
 
164
291
  const connectedSwcAppCallBacks = findAllLifecycleMetadata(instance, ON_CONNECTED_SWC_APP_METADATA_KEY);
165
292
  for (let connectedSwcAppCallBack of connectedSwcAppCallBacks) {
166
293
  await (instance as any)?._invokeLifecycleMethod?.(connectedSwcAppCallBack.propertyKey);
294
+ if (stale()) return this;
167
295
  }
168
296
 
169
297
  if (this._childrenConnectedDoneInterval) {
@@ -183,15 +311,20 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
183
311
  if (this.simpleApplication) {
184
312
  try {
185
313
  if (this.router && this._lastRouterEvent) {
186
- await this._invokeRouteChangeSubscribers(instance, this._lastRouterEvent!);
314
+ await this._invokeRouteChangeSubscribers(instance, this._lastRouterEvent!, 'connected');
187
315
  }
188
316
  } catch (e) {
189
317
  throw e;
190
318
  }
191
319
  }
320
+ if (stale()) return this;
321
+
322
+ if (this.simpleApplication) this._registerLeaveGuards(instance);
323
+
324
+ // replay 버퍼가 있으면 신규 인스턴스에만 재생 (fire-and-forget, 연결 블로킹 안 함)
325
+ this._replayMessagesTo(instance);
192
326
 
193
327
  const isBrowser = ValidUtils.isBrowser();
194
- console.log('------>', isBrowser, this.config?.ssr)
195
328
  if (!isBrowser && this.config?.ssr) {
196
329
  setSSRAttribute(instance);
197
330
  } else {
@@ -203,9 +336,20 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
203
336
  return this;
204
337
  }
205
338
 
206
- async _connectedDone(instance: any) {
339
+ async _connectedDone(instance: any, option?: { aborted?: boolean }) {
207
340
  try {
208
- // 내부 연결 처리 로직
341
+ if (option?.aborted) return; // 중간에 멈춘 연결: 재생 없이 연결 수만 맞춘다
342
+ // trigger 'connectedDone' 구독에만 버퍼 재생 (render 완료 후라 DOM 타겟 안전)
343
+ this._replayMessagesTo(instance, 'connectedDone');
344
+ if (this.simpleApplication) {
345
+ try {
346
+ if (this.router && this._lastRouterEvent) {
347
+ await this._invokeRouteChangeSubscribers(instance, this._lastRouterEvent!, 'connectedDone');
348
+ }
349
+ } catch (e) {
350
+ throw e;
351
+ }
352
+ }
209
353
  } finally {
210
354
  this._connectedInvocations = Math.max(0, this._connectedInvocations - 1);
211
355
  }
@@ -214,6 +358,10 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
214
358
  _disconnected(instance: any) {
215
359
  if (instance) {
216
360
  this._swc_connected_instance.delete(instance);
361
+ // beforeLeave 가드 해제 + on 판정 상태 초기화 (다시 붙으면 enter 부터)
362
+ instance.__swc_leaveGuardOffs?.forEach((off: () => void) => off());
363
+ instance.__swc_leaveGuardOffs = undefined;
364
+ instance.__swc_routeState = undefined;
217
365
  this.config?.onDisconnectedChildAfter?.(instance);
218
366
  }
219
367
  }
@@ -226,7 +374,6 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
226
374
  }
227
375
 
228
376
  async connect(config?: SwcAttributeConfigType) {
229
- console.log('[SWC-MIXIN] connect');
230
377
  const swcConfig = {
231
378
  routeType: 'element',
232
379
  window: config?.window,
@@ -234,6 +381,7 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
234
381
  } as SwcConfigType;
235
382
 
236
383
  await this.__swc_engine.connect(swcConfig);
384
+ this._appReadyWaiters.splice(0).forEach(resolve => resolve());
237
385
 
238
386
  // Safari is 속성 처리
239
387
  this._connected_safari_and_standby.forEach((instance: any) => {
@@ -242,19 +390,15 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
242
390
 
243
391
  // 라우터 변경 구독
244
392
  if (!this._routerSubscription && this.router) {
245
- const config = this.__swc_engine.config;
246
-
247
393
  this._routerSubscription = this.router.observable.subscribe(async (route: RouterEventType) => {
248
- console.log('[SWC-MIXIN] Router event received:', route.triggerPoint);
249
394
  if (route.triggerPoint === 'end') {
250
395
  await this._handleRouteChange(route);
251
396
  }
252
397
  });
253
-
254
- if (config?.path) {
255
- console.log('[DEBUG] Navigating to initial path:', config.path);
256
- }
257
398
  }
399
+
400
+ // onSwcAppConnected 훅 (@inject 파라미터 있으면 DI 해결 — _invokeLifecycleMethod 경로)
401
+ await this._invokeHook('onSwcAppConnected');
258
402
  }
259
403
 
260
404
  connectedCallback() {
@@ -269,7 +413,7 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
269
413
  const userConfig = FunctionUtils.executeReturn({
270
414
  script: configStr,
271
415
  context: this,
272
- args: SwcUtils.getHelperAndHostSet(win, this)
416
+ args: SwcUtils.getHelperAndHostSet(this, win)
273
417
  });
274
418
  if (userConfig instanceof Promise) {
275
419
  userConfig
@@ -290,6 +434,7 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
290
434
  this._connected(this);
291
435
  this.__swc_engine?.config?.onConnected?.(this);
292
436
  this._connectedDone(this);
437
+ void this._invokeHook('onConnected').catch(e => console.error('[SWC-MIXIN] onConnected error:', e));
293
438
  }
294
439
 
295
440
  disconnectedCallback() {
@@ -303,6 +448,7 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
303
448
  this.__swc_engine.disconnect();
304
449
  this.__swc_engine?.config?.onDisconnected?.(this);
305
450
  this._disconnected(this);
451
+ void this._invokeHook('onDisconnected').catch(e => console.error('[SWC-MIXIN] onDisconnected error:', e));
306
452
  }
307
453
 
308
454
  async routing(path: string) {
@@ -321,37 +467,135 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
321
467
  this.router?.reload();
322
468
  }
323
469
 
324
- publishMessage(message: SwcAppMessage): void {
470
+ publishMessage(message: SwcAppMessage): void { // 버퍼 먼저 (핸들러가 동기 재발행해도 순서 보장)
471
+ // 전 타입 과거 메시지 보관 (늦게 연결될 인스턴스에 재생). 개수는 앱 설정 1곳.
472
+ if (message.type) {
473
+ const buf = [...(this._replayedMessages.get(message.type) ?? []), message];
474
+ this._replayedMessages.set(message.type, buf.slice(-Math.max(1, this.config?.messageReplayBufferSize ?? 10)));
475
+ }
325
476
  this._swc_connected_instance.forEach((instance: any) => {
326
477
  this._invokeMessageSubscribers(instance, message);
327
478
  });
479
+ this._messageSubject.next(message);
480
+ }
481
+
482
+ connectedElements(): Array<HTMLElement & SwcElement> {
483
+ return Array.from(this._swc_connected_instance);
484
+ }
485
+
486
+ /**
487
+ * 데코레이터 없이 코드로 메시지를 구독한다 (RxJS 스타일). 서비스·동적 구독 등 데코레이터를 못 붙이는 곳용.
488
+ * host.observeMessage<Me>(AUTH_CHANGED).subscribe(msg => ...) → Subscription (unsubscribe 필수)
489
+ * type 생략 시 전 타입 live. subject 옵션은 @subscribeSwcAppMessage 와 같은 규칙(type 지정 시에만 재생):
490
+ * 'behavior' = 버퍼 마지막 1개 먼저 / 'replay' = 버퍼 전체 시간순 먼저 / 미지정·'subject' = live만.
491
+ * 호출 형태:
492
+ * observeMessage() 전 타입 live
493
+ * observeMessage('auth') auth live
494
+ * observeMessage('auth', { subject: 'behavior' }) auth 마지막 1개 + live
495
+ * observeMessage({ type: 'auth', subject: 'replay' }) 옵션 객체 하나로
496
+ */
497
+ observeMessage<T = any>(): Observable<SwcAppMessage<T>>;
498
+ observeMessage<T = any>(type: string): Observable<SwcAppMessage<T>>;
499
+ observeMessage<T = any>(type: string, options: Omit<SwcAppMessageObserveOptions, 'type'>): Observable<SwcAppMessage<T>>;
500
+ observeMessage<T = any>(options: SwcAppMessageObserveOptions): Observable<SwcAppMessage<T>>;
501
+ observeMessage<T = any>(typeOrOptions?: string | SwcAppMessageObserveOptions, options?: Omit<SwcAppMessageObserveOptions, 'type'>): Observable<SwcAppMessage<T>> {
502
+ const { type, subject } = typeof typeOrOptions === 'string' ? { ...options, type: typeOrOptions } : { ...typeOrOptions };
503
+ return new Observable<SwcAppMessage<T>>(subscriber => {
504
+ if (type && (subject === 'behavior' || subject === 'replay')) {
505
+ const buf = this._replayedMessages.get(type) ?? [];
506
+ (subject === 'behavior' ? buf.slice(-1) : buf).forEach(m => subscriber.next(m as SwcAppMessage<T>));
507
+ }
508
+ const sub = this._messageSubject.subscribe(m => {
509
+ if (!type || m.type === type) subscriber.next(m as SwcAppMessage<T>);
510
+ });
511
+ return () => sub.unsubscribe();
512
+ });
513
+ }
514
+
515
+ // hook 실행 + @inject 파라미터 DI 해결 (host 본체 포함.
516
+ // _invokeLifecycleMethod는 host/hostSet 상황 분기가 달라 호스트 본체에서 주입이 안 됨)
517
+ async _invokeHook(methodName: string | symbol) {
518
+ const fn = (this as any)[methodName];
519
+ if (typeof fn !== 'function') return;
520
+ const manager = (this as any).simpleApplication?.simstanceManager;
521
+ if (!manager) {
522
+ return fn.call(this);
523
+ }
524
+ return manager.executeBindParameterSimPromise({ target: this, targetKey: methodName });
525
+ }
526
+
527
+ // 신규 연결 인스턴스 1곳에만 버퍼 재생. 구독자별 subject 선언 기준:
528
+ // - 'behavior': 마지막 1개 / 'replay': 버퍼 전체 시간순 / 미지정·'subject': live만 (재생 안 함)
529
+ // - trigger 'connectedDone'인 구독은 _connectedDone(render 완료 후)에서 재생. phase로 구분.
530
+ // 이미 연결된 인스턴스는 publishMessage 때 live로 받으므로 여기 오지 않음.
531
+ _replayMessagesTo(instance: any, phase: 'connected' | 'connectedDone' = 'connected') {
532
+ const subs = getSubscribeSwcAppMessageMetadata(instance);
533
+ if (!subs || !Array.isArray(subs)) return;
534
+ const seen = new Set<string>();
535
+ subs.forEach(meta => {
536
+ const type = meta.messageType as string | undefined;
537
+ if (!type || seen.has(type)) return;
538
+ seen.add(type);
539
+ if ((meta.trigger ?? 'connected') !== phase) return;
540
+ const buf = this._replayedMessages.get(type) ?? [];
541
+ if (meta.subject === 'behavior') {
542
+ const last = buf[buf.length - 1];
543
+ if (last) this._invokeMessageSubscribers(instance, last);
544
+ } else if (meta.subject === 'replay') {
545
+ buf.forEach(msg => this._invokeMessageSubscribers(instance, msg));
546
+ }
547
+ });
328
548
  }
329
549
 
330
550
  _invokeMessageSubscribers(instance: any, message: SwcAppMessage) {
331
- const messageSubscribers = getSubscribeSwcAppMessageWhileConnectedMetadata(instance);
551
+ const messageSubscribers = getSubscribeSwcAppMessageMetadata(instance);
332
552
  if (messageSubscribers && Array.isArray(messageSubscribers)) {
553
+ // 메시지 구독은 fire-and-forget — async filter/before/finally를 위해 구독자마다 비동기 러너로 돌리되
554
+ // 호출측(publishMessage)은 논블로킹 유지. 미처리 rejection은 로깅만.
333
555
  messageSubscribers.forEach(metadata => {
334
- const methodName = metadata.propertyKey;
335
- const messageType = metadata.messageType;
336
- const filter = metadata.filter;
337
-
338
- let typeMatched = false;
339
- if (!messageType) {
340
- typeMatched = true;
341
- } else if (message.type === messageType) {
342
- typeMatched = true;
343
- }
556
+ void this._runMessageSubscriber(instance, message, metadata).catch(e => console.error('[SWC] subscribeSwcAppMessage error:', e));
557
+ });
558
+ }
559
+ }
344
560
 
345
- let filterMatched = true;
346
- if (filter && typeMatched) {
347
- filterMatched = filter(message, instance);
348
- }
561
+ async _runMessageSubscriber(instance: any, message: SwcAppMessage, metadata: any) {
562
+ const methodName = metadata.propertyKey;
563
+ const messageType = metadata.messageType;
564
+ const { filter, before, finally: finaly } = metadata;
565
+
566
+ const typeMatched = !messageType || message.type === messageType;
567
+ if (!typeMatched) return;
568
+ if (filter && !(await filter(message, instance))) return;
569
+ if (typeof instance[methodName] !== 'function') return;
570
+
571
+ const instanceHelperHostSet = SwcUtils.getHelperAndHostSet(instance, this.config.window);
572
+ const instanceHelperSet = SwcUtils.getHelperSet(this.config.window);
573
+ // before를 먼저 돌려 리턴값을 @appMessageBeforeReturn 으로 주입할 수 있게 캡처.
574
+ const beforeReturn = before ? await before(message, instance) : undefined;
575
+ const args = buildSwcParameterArgs(
576
+ instance,
577
+ methodName,
578
+ {
579
+ appMessage: message,
580
+ hostSet: instanceHelperHostSet,
581
+ helperHostSet: instanceHelperHostSet,
582
+ helperSet: instanceHelperSet,
583
+ appMessageBeforeReturn: beforeReturn
584
+ },
585
+ [message]
586
+ );
349
587
 
350
- if (typeMatched && filterMatched && typeof instance[methodName] === 'function') {
351
- instance[methodName](message);
352
- }
353
- });
588
+ let result: any, error: any;
589
+ try {
590
+ result = await instance[methodName](...args);
591
+ } catch (e) {
592
+ error = e;
593
+ } finally {
594
+ if (finaly) await finaly(message, instance, { args, result, error });
354
595
  }
596
+ if (error) throw error;
355
597
  }
356
- };
598
+ }
599
+
600
+ return SwcAppMixinClass;
357
601
  }