repluggable-core 1.302.65 → 1.302.67

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 (303) hide show
  1. package/__tests__/spec-setup.js +3 -0
  2. package/dist/es/{API.d.ts → src/API.d.ts} +46 -8
  3. package/dist/es/src/API.d.ts.map +1 -0
  4. package/dist/es/src/API.js +3 -0
  5. package/dist/es/src/API.js.map +1 -0
  6. package/dist/es/src/IterableWeakMap.d.ts +20 -0
  7. package/dist/es/src/IterableWeakMap.d.ts.map +1 -0
  8. package/dist/es/src/IterableWeakMap.js +92 -0
  9. package/dist/es/src/IterableWeakMap.js.map +1 -0
  10. package/dist/es/src/__internal.d.ts.map +1 -0
  11. package/dist/es/src/__internal.js +5 -0
  12. package/dist/es/src/__internal.js.map +1 -0
  13. package/dist/es/src/appHost.d.ts +7 -0
  14. package/dist/es/src/appHost.d.ts.map +1 -0
  15. package/dist/es/src/appHost.js +898 -0
  16. package/dist/es/src/appHost.js.map +1 -0
  17. package/dist/es/src/appHostServices.d.ts +12 -0
  18. package/dist/es/src/appHostServices.d.ts.map +1 -0
  19. package/dist/es/src/appHostServices.js +25 -0
  20. package/dist/es/src/appHostServices.js.map +1 -0
  21. package/dist/es/src/appHostUtils.d.ts +4 -0
  22. package/dist/es/src/appHostUtils.d.ts.map +1 -0
  23. package/dist/es/src/appHostUtils.js +8 -0
  24. package/dist/es/src/appHostUtils.js.map +1 -0
  25. package/dist/es/src/extensionSlot.d.ts +8 -0
  26. package/dist/es/src/extensionSlot.d.ts.map +1 -0
  27. package/dist/es/src/extensionSlot.js +59 -0
  28. package/dist/es/src/extensionSlot.js.map +1 -0
  29. package/dist/es/src/hot.d.ts +14 -0
  30. package/dist/es/src/hot.d.ts.map +1 -0
  31. package/dist/es/src/hot.js +52 -0
  32. package/dist/es/src/hot.js.map +1 -0
  33. package/dist/es/src/index.d.ts +11 -0
  34. package/dist/es/src/index.d.ts.map +1 -0
  35. package/dist/es/src/index.js +10 -0
  36. package/dist/es/src/index.js.map +1 -0
  37. package/dist/es/src/installedShellsState.d.ts +19 -0
  38. package/dist/es/src/installedShellsState.d.ts.map +1 -0
  39. package/dist/es/src/installedShellsState.js +30 -0
  40. package/dist/es/src/installedShellsState.js.map +1 -0
  41. package/dist/es/src/interceptAnyObject.d.ts +7 -0
  42. package/dist/es/src/interceptAnyObject.d.ts.map +1 -0
  43. package/dist/es/src/interceptAnyObject.js +21 -0
  44. package/dist/es/src/interceptAnyObject.js.map +1 -0
  45. package/dist/es/src/invokeSlotCallbacks.d.ts +3 -0
  46. package/dist/es/src/invokeSlotCallbacks.d.ts.map +1 -0
  47. package/dist/es/src/invokeSlotCallbacks.js +20 -0
  48. package/dist/es/src/invokeSlotCallbacks.js.map +1 -0
  49. package/dist/es/src/loggers.d.ts +4 -0
  50. package/dist/es/src/loggers.d.ts.map +1 -0
  51. package/dist/es/src/loggers.js +100 -0
  52. package/dist/es/src/loggers.js.map +1 -0
  53. package/dist/es/src/monitorAPI.d.ts +3 -0
  54. package/dist/es/src/monitorAPI.d.ts.map +1 -0
  55. package/dist/es/src/monitorAPI.js +53 -0
  56. package/dist/es/src/monitorAPI.js.map +1 -0
  57. package/dist/es/src/repluggableAppDebug/index.d.ts +2 -0
  58. package/dist/es/src/repluggableAppDebug/index.d.ts.map +1 -0
  59. package/dist/es/src/repluggableAppDebug/index.js +2 -0
  60. package/dist/es/src/repluggableAppDebug/index.js.map +1 -0
  61. package/dist/es/src/repluggableAppDebug/performanceDebugInfo.d.ts +15 -0
  62. package/dist/es/src/repluggableAppDebug/performanceDebugInfo.d.ts.map +1 -0
  63. package/dist/es/src/repluggableAppDebug/performanceDebugInfo.js +94 -0
  64. package/dist/es/src/repluggableAppDebug/performanceDebugInfo.js.map +1 -0
  65. package/dist/es/src/repluggableAppDebug/repluggableAppDebug.d.ts +24 -0
  66. package/dist/es/src/repluggableAppDebug/repluggableAppDebug.d.ts.map +1 -0
  67. package/dist/es/src/repluggableAppDebug/repluggableAppDebug.js +132 -0
  68. package/dist/es/src/repluggableAppDebug/repluggableAppDebug.js.map +1 -0
  69. package/dist/es/src/tarjanGraph.d.ts +27 -0
  70. package/dist/es/src/tarjanGraph.d.ts.map +1 -0
  71. package/dist/es/src/tarjanGraph.js +94 -0
  72. package/dist/es/src/tarjanGraph.js.map +1 -0
  73. package/dist/es/src/throttledStore.d.ts +32 -0
  74. package/dist/es/src/throttledStore.d.ts.map +1 -0
  75. package/dist/es/src/throttledStore.js +240 -0
  76. package/dist/es/src/throttledStore.js.map +1 -0
  77. package/dist/es/test/IterableWeakMap.spec.d.ts +2 -0
  78. package/dist/es/test/IterableWeakMap.spec.d.ts.map +1 -0
  79. package/dist/es/test/IterableWeakMap.spec.js +218 -0
  80. package/dist/es/test/IterableWeakMap.spec.js.map +1 -0
  81. package/dist/es/test/appHost.mock.d.ts +4 -0
  82. package/dist/es/test/appHost.mock.d.ts.map +1 -0
  83. package/dist/es/test/appHost.mock.js +54 -0
  84. package/dist/es/test/appHost.mock.js.map +1 -0
  85. package/dist/es/test/appHost.spec.d.ts +2 -0
  86. package/dist/es/test/appHost.spec.d.ts.map +1 -0
  87. package/dist/es/test/appHost.spec.js +1451 -0
  88. package/dist/es/test/appHost.spec.js.map +1 -0
  89. package/dist/es/test/installedShellsState.spec.d.ts +2 -0
  90. package/dist/es/test/installedShellsState.spec.d.ts.map +1 -0
  91. package/dist/es/test/installedShellsState.spec.js +28 -0
  92. package/dist/es/test/installedShellsState.spec.js.map +1 -0
  93. package/dist/es/test/interceptAnyObject.spec.d.ts +2 -0
  94. package/dist/es/test/interceptAnyObject.spec.d.ts.map +1 -0
  95. package/dist/es/test/interceptAnyObject.spec.js +166 -0
  96. package/dist/es/test/interceptAnyObject.spec.js.map +1 -0
  97. package/dist/es/test/loggers.spec.d.ts +2 -0
  98. package/dist/es/test/loggers.spec.d.ts.map +1 -0
  99. package/dist/es/test/loggers.spec.js +218 -0
  100. package/dist/es/test/loggers.spec.js.map +1 -0
  101. package/dist/es/test/repluggableAppDebug.spec.d.ts +2 -0
  102. package/dist/es/test/repluggableAppDebug.spec.d.ts.map +1 -0
  103. package/dist/es/test/repluggableAppDebug.spec.js +107 -0
  104. package/dist/es/test/repluggableAppDebug.spec.js.map +1 -0
  105. package/dist/es/test/testKit.spec.d.ts +2 -0
  106. package/dist/es/test/testKit.spec.d.ts.map +1 -0
  107. package/dist/es/test/testKit.spec.js +129 -0
  108. package/dist/es/test/testKit.spec.js.map +1 -0
  109. package/dist/es/testKit/emptyLoggerOptions.d.ts +3 -0
  110. package/dist/es/testKit/emptyLoggerOptions.d.ts.map +1 -0
  111. package/dist/es/testKit/emptyLoggerOptions.js +15 -0
  112. package/dist/es/testKit/emptyLoggerOptions.js.map +1 -0
  113. package/dist/es/testKit/index.d.ts +27 -0
  114. package/dist/es/testKit/index.d.ts.map +1 -0
  115. package/dist/es/testKit/index.js +132 -0
  116. package/dist/es/testKit/index.js.map +1 -0
  117. package/dist/es/testKit/mockPackage.d.ts +23 -0
  118. package/dist/es/testKit/mockPackage.d.ts.map +1 -0
  119. package/dist/es/testKit/mockPackage.js +95 -0
  120. package/dist/es/testKit/mockPackage.js.map +1 -0
  121. package/dist/es/testKit/withConsoleErrors.d.ts +2 -0
  122. package/dist/es/testKit/withConsoleErrors.d.ts.map +1 -0
  123. package/dist/es/testKit/withConsoleErrors.js +12 -0
  124. package/dist/es/testKit/withConsoleErrors.js.map +1 -0
  125. package/dist/es/testKit/withThrowOnError.d.ts +3 -0
  126. package/dist/es/testKit/withThrowOnError.d.ts.map +1 -0
  127. package/dist/es/testKit/withThrowOnError.js +27 -0
  128. package/dist/es/testKit/withThrowOnError.js.map +1 -0
  129. package/dist/src/API.d.ts +46 -8
  130. package/dist/src/API.d.ts.map +1 -1
  131. package/dist/src/API.js +4 -0
  132. package/dist/src/API.js.map +1 -1
  133. package/dist/src/IterableWeakMap.d.ts +20 -0
  134. package/dist/src/IterableWeakMap.d.ts.map +1 -0
  135. package/dist/src/IterableWeakMap.js +96 -0
  136. package/dist/src/IterableWeakMap.js.map +1 -0
  137. package/dist/src/__internal.d.ts.map +1 -1
  138. package/dist/src/__internal.js +1 -1
  139. package/dist/src/__internal.js.map +1 -1
  140. package/dist/src/appHost.d.ts +7 -0
  141. package/dist/src/appHost.d.ts.map +1 -0
  142. package/dist/src/appHost.js +906 -0
  143. package/dist/src/appHost.js.map +1 -0
  144. package/dist/src/appHostServices.d.ts +12 -0
  145. package/dist/src/appHostServices.d.ts.map +1 -0
  146. package/dist/src/appHostServices.js +29 -0
  147. package/dist/src/appHostServices.js.map +1 -0
  148. package/dist/src/appHostUtils.d.ts +4 -0
  149. package/dist/src/appHostUtils.d.ts.map +1 -0
  150. package/dist/src/appHostUtils.js +16 -0
  151. package/dist/src/appHostUtils.js.map +1 -0
  152. package/dist/src/extensionSlot.d.ts +8 -0
  153. package/dist/src/extensionSlot.d.ts.map +1 -0
  154. package/dist/src/extensionSlot.js +67 -0
  155. package/dist/src/extensionSlot.js.map +1 -0
  156. package/dist/src/hot.d.ts +14 -0
  157. package/dist/src/hot.d.ts.map +1 -0
  158. package/dist/src/hot.js +56 -0
  159. package/dist/src/hot.js.map +1 -0
  160. package/dist/src/index.d.ts +10 -2
  161. package/dist/src/index.d.ts.map +1 -1
  162. package/dist/src/index.js +22 -15
  163. package/dist/src/index.js.map +1 -1
  164. package/dist/src/installedShellsState.d.ts +19 -0
  165. package/dist/src/installedShellsState.d.ts.map +1 -0
  166. package/dist/src/installedShellsState.js +38 -0
  167. package/dist/src/installedShellsState.js.map +1 -0
  168. package/dist/src/interceptAnyObject.d.ts +7 -0
  169. package/dist/src/interceptAnyObject.d.ts.map +1 -0
  170. package/dist/src/interceptAnyObject.js +28 -0
  171. package/dist/src/interceptAnyObject.js.map +1 -0
  172. package/dist/src/invokeSlotCallbacks.d.ts +3 -0
  173. package/dist/src/invokeSlotCallbacks.d.ts.map +1 -0
  174. package/dist/src/invokeSlotCallbacks.js +24 -0
  175. package/dist/src/invokeSlotCallbacks.js.map +1 -0
  176. package/dist/src/loggers.d.ts +4 -0
  177. package/dist/src/loggers.d.ts.map +1 -0
  178. package/dist/src/loggers.js +104 -0
  179. package/dist/src/loggers.js.map +1 -0
  180. package/dist/src/monitorAPI.d.ts +3 -0
  181. package/dist/src/monitorAPI.d.ts.map +1 -0
  182. package/dist/src/monitorAPI.js +57 -0
  183. package/dist/src/monitorAPI.js.map +1 -0
  184. package/dist/src/repluggableAppDebug/index.d.ts +2 -0
  185. package/dist/src/repluggableAppDebug/index.d.ts.map +1 -0
  186. package/dist/src/repluggableAppDebug/index.js +6 -0
  187. package/dist/src/repluggableAppDebug/index.js.map +1 -0
  188. package/dist/src/repluggableAppDebug/performanceDebugInfo.d.ts +15 -0
  189. package/dist/src/repluggableAppDebug/performanceDebugInfo.d.ts.map +1 -0
  190. package/dist/src/repluggableAppDebug/performanceDebugInfo.js +101 -0
  191. package/dist/src/repluggableAppDebug/performanceDebugInfo.js.map +1 -0
  192. package/dist/src/repluggableAppDebug/repluggableAppDebug.d.ts +24 -0
  193. package/dist/src/repluggableAppDebug/repluggableAppDebug.d.ts.map +1 -0
  194. package/dist/src/repluggableAppDebug/repluggableAppDebug.js +139 -0
  195. package/dist/src/repluggableAppDebug/repluggableAppDebug.js.map +1 -0
  196. package/dist/src/tarjanGraph.d.ts +27 -0
  197. package/dist/src/tarjanGraph.d.ts.map +1 -0
  198. package/dist/src/tarjanGraph.js +100 -0
  199. package/dist/src/tarjanGraph.js.map +1 -0
  200. package/dist/src/throttledStore.d.ts +32 -0
  201. package/dist/src/throttledStore.d.ts.map +1 -0
  202. package/dist/src/throttledStore.js +249 -0
  203. package/dist/src/throttledStore.js.map +1 -0
  204. package/dist/test/IterableWeakMap.spec.d.ts +2 -0
  205. package/dist/test/IterableWeakMap.spec.d.ts.map +1 -0
  206. package/dist/test/IterableWeakMap.spec.js +220 -0
  207. package/dist/test/IterableWeakMap.spec.js.map +1 -0
  208. package/dist/test/appHost.mock.d.ts +4 -0
  209. package/dist/test/appHost.mock.d.ts.map +1 -0
  210. package/dist/test/appHost.mock.js +59 -0
  211. package/dist/test/appHost.mock.js.map +1 -0
  212. package/dist/test/appHost.spec.d.ts +2 -0
  213. package/dist/test/appHost.spec.d.ts.map +1 -0
  214. package/dist/test/appHost.spec.js +1456 -0
  215. package/dist/test/appHost.spec.js.map +1 -0
  216. package/dist/test/installedShellsState.spec.d.ts +2 -0
  217. package/dist/test/installedShellsState.spec.d.ts.map +1 -0
  218. package/dist/test/installedShellsState.spec.js +30 -0
  219. package/dist/test/installedShellsState.spec.js.map +1 -0
  220. package/dist/test/interceptAnyObject.spec.d.ts +2 -0
  221. package/dist/test/interceptAnyObject.spec.d.ts.map +1 -0
  222. package/dist/test/interceptAnyObject.spec.js +171 -0
  223. package/dist/test/interceptAnyObject.spec.js.map +1 -0
  224. package/dist/test/loggers.spec.d.ts +2 -0
  225. package/dist/test/loggers.spec.d.ts.map +1 -0
  226. package/dist/test/loggers.spec.js +220 -0
  227. package/dist/test/loggers.spec.js.map +1 -0
  228. package/dist/test/repluggableAppDebug.spec.d.ts +2 -0
  229. package/dist/test/repluggableAppDebug.spec.d.ts.map +1 -0
  230. package/dist/test/repluggableAppDebug.spec.js +109 -0
  231. package/dist/test/repluggableAppDebug.spec.js.map +1 -0
  232. package/dist/test/testKit.spec.d.ts +2 -0
  233. package/dist/test/testKit.spec.d.ts.map +1 -0
  234. package/dist/test/testKit.spec.js +134 -0
  235. package/dist/test/testKit.spec.js.map +1 -0
  236. package/dist/testKit/emptyLoggerOptions.d.ts +3 -0
  237. package/dist/testKit/emptyLoggerOptions.d.ts.map +1 -0
  238. package/dist/testKit/emptyLoggerOptions.js +18 -0
  239. package/dist/testKit/emptyLoggerOptions.js.map +1 -0
  240. package/dist/testKit/index.d.ts +27 -0
  241. package/dist/testKit/index.d.ts.map +1 -0
  242. package/dist/testKit/index.js +160 -0
  243. package/dist/testKit/index.js.map +1 -0
  244. package/dist/testKit/mockPackage.d.ts +23 -0
  245. package/dist/testKit/mockPackage.d.ts.map +1 -0
  246. package/dist/testKit/mockPackage.js +101 -0
  247. package/dist/testKit/mockPackage.js.map +1 -0
  248. package/dist/testKit/withConsoleErrors.d.ts +2 -0
  249. package/dist/testKit/withConsoleErrors.d.ts.map +1 -0
  250. package/dist/testKit/withConsoleErrors.js +16 -0
  251. package/dist/testKit/withConsoleErrors.js.map +1 -0
  252. package/dist/testKit/withThrowOnError.d.ts +3 -0
  253. package/dist/testKit/withThrowOnError.d.ts.map +1 -0
  254. package/dist/testKit/withThrowOnError.js +31 -0
  255. package/dist/testKit/withThrowOnError.js.map +1 -0
  256. package/jest.config.js +16 -0
  257. package/package.json +21 -3
  258. package/src/API.ts +47 -9
  259. package/src/IterableWeakMap.ts +123 -0
  260. package/src/__internal.ts +1 -1
  261. package/src/appHost.ts +1222 -0
  262. package/src/appHostServices.ts +42 -0
  263. package/src/appHostUtils.ts +11 -0
  264. package/src/extensionSlot.ts +96 -0
  265. package/src/hot.ts +53 -0
  266. package/src/index.ts +42 -2
  267. package/src/installedShellsState.ts +54 -0
  268. package/src/interceptAnyObject.ts +39 -0
  269. package/src/invokeSlotCallbacks.ts +20 -0
  270. package/src/loggers.ts +112 -0
  271. package/src/monitorAPI.ts +71 -0
  272. package/src/repluggableAppDebug/debug.d.ts +37 -0
  273. package/src/repluggableAppDebug/index.ts +1 -0
  274. package/src/repluggableAppDebug/performanceDebugInfo.ts +96 -0
  275. package/src/repluggableAppDebug/repluggableAppDebug.ts +192 -0
  276. package/src/tarjanGraph.ts +96 -0
  277. package/src/throttledStore.ts +355 -0
  278. package/test/IterableWeakMap.spec.ts +280 -0
  279. package/test/appHost.mock.ts +58 -0
  280. package/test/appHost.spec.ts +1765 -0
  281. package/test/installedShellsState.spec.ts +39 -0
  282. package/test/interceptAnyObject.spec.ts +217 -0
  283. package/test/loggers.spec.ts +288 -0
  284. package/test/repluggableAppDebug.spec.ts +118 -0
  285. package/test/testKit.spec.tsx +153 -0
  286. package/testKit/emptyLoggerOptions.ts +16 -0
  287. package/testKit/index.ts +170 -0
  288. package/testKit/mockPackage.ts +110 -0
  289. package/testKit/package.json +3 -0
  290. package/testKit/withConsoleErrors.ts +12 -0
  291. package/testKit/withThrowOnError.ts +34 -0
  292. package/tsconfig.json +9 -2
  293. package/dist/es/API.d.ts.map +0 -1
  294. package/dist/es/API.js +0 -2
  295. package/dist/es/API.js.map +0 -1
  296. package/dist/es/__internal.d.ts.map +0 -1
  297. package/dist/es/__internal.js +0 -5
  298. package/dist/es/__internal.js.map +0 -1
  299. package/dist/es/index.d.ts +0 -3
  300. package/dist/es/index.d.ts.map +0 -1
  301. package/dist/es/index.js +0 -2
  302. package/dist/es/index.js.map +0 -1
  303. /package/dist/es/{__internal.d.ts → src/__internal.d.ts} +0 -0
package/src/appHost.ts ADDED
@@ -0,0 +1,1222 @@
1
+
2
+ import _ from 'lodash'
3
+ import { AnyAction, Store } from 'redux'
4
+ import {
5
+ AnyEntryPoint,
6
+ AnyFunction,
7
+ AnySlotKey,
8
+ APILayer,
9
+ AppHost,
10
+ AppHostOptions,
11
+ ContributeAPIOptions,
12
+ CustomExtensionSlot,
13
+ CustomExtensionSlotHandler,
14
+ DeclarationsChangedCallback,
15
+ EntryPoint,
16
+ EntryPointOrPackage,
17
+ EntryPointsInfo,
18
+ ExtensionItem,
19
+ ExtensionSlot,
20
+ FunctionWithSameArgs,
21
+ Lazy,
22
+ LazyEntryPointDescriptor,
23
+ LazyEntryPointFactory,
24
+ MemoizeMissHit,
25
+ ObservableState,
26
+ PrivateAppHost,
27
+ PrivateShell,
28
+ ReactComponentContributor,
29
+ ReducersMapObjectContributor,
30
+ ScopedStore,
31
+ Shell,
32
+ ShellBoundaryAspect,
33
+ ShellsChangedCallback,
34
+ SlotKey,
35
+ StatisticsMemoization,
36
+ Trace,
37
+ UnsubscribeFromDeclarationsChanged
38
+ } from './API'
39
+ import { AppHostAPI, AppHostServicesProvider, createAppHostServicesEntryPoint } from './appHostServices'
40
+ import { declaredAPIs, dependentAPIs } from './appHostUtils'
41
+ import { AnyExtensionSlot, createCustomExtensionSlot, createExtensionSlot } from './extensionSlot'
42
+ import { InstalledShellsActions, InstalledShellsSelectors, ShellToggleSet } from './installedShellsState'
43
+ import { IterableWeakMap } from './IterableWeakMap'
44
+ import { ConsoleHostLogger, createShellLogger } from './loggers'
45
+ import { monitorAPI } from './monitorAPI'
46
+ import { setupDebugInfo } from './repluggableAppDebug'
47
+ import { getCycle, Graph, Tarjan } from './tarjanGraph'
48
+ import {
49
+ createObservable,
50
+ createThrottledStore,
51
+ PrivateObservableState,
52
+ PrivateThrottledStore,
53
+ StateContribution,
54
+ ThrottledStore,
55
+ updateThrottledStore
56
+ } from './throttledStore'
57
+ import { INTERNAL_DONT_USE_SHELL_GET_APP_HOST } from './__internal'
58
+
59
+
60
+ function isMultiArray<T>(v: T[] | T[][]): v is T[][] {
61
+ return _.every(v, _.isArray)
62
+ }
63
+ function castMultiArray<T>(v: T[] | T[][]): T[][] {
64
+ return isMultiArray(v) ? v : [v]
65
+ }
66
+
67
+ export const makeLazyEntryPoint = (name: string, factory: LazyEntryPointFactory): LazyEntryPointDescriptor => {
68
+ return {
69
+ name,
70
+ factory
71
+ }
72
+ }
73
+
74
+ export const mainViewSlotKey: SlotKey<ReactComponentContributor> = {
75
+ name: 'mainView'
76
+ }
77
+ export const stateSlotKey: SlotKey<StateContribution> = {
78
+ name: 'state'
79
+ }
80
+
81
+ interface MemoizedFunctionData {
82
+ f: Partial<_.MemoizedFunction>
83
+ shouldClear?(): boolean
84
+ }
85
+
86
+ const toShellToggleSet = (names: string[], isInstalled: boolean): ShellToggleSet => {
87
+ return names.reduce<ShellToggleSet>((result: ShellToggleSet, name: string) => {
88
+ result[name] = isInstalled
89
+ return result
90
+ }, {})
91
+ }
92
+
93
+ interface UnreadyEntryPointsStore {
94
+ get(): EntryPoint[]
95
+ set(entryPoints: EntryPoint[]): void
96
+ }
97
+
98
+ const createUnreadyEntryPointsStore = (): UnreadyEntryPointsStore => {
99
+ let entryPoints: EntryPoint[] = []
100
+
101
+ return {
102
+ get() {
103
+ return entryPoints
104
+ },
105
+ set(newEntryPoints: EntryPoint[]) {
106
+ entryPoints = newEntryPoints
107
+ }
108
+ }
109
+ }
110
+
111
+ interface InternalAPILayer extends APILayer {
112
+ dimension: number
113
+ }
114
+
115
+ const verifyLayersUniqueness = (layers?: APILayer[] | APILayer[][]) => {
116
+ if (!layers) {
117
+ return
118
+ }
119
+ const flatLayers = _.flatten(layers)
120
+ const nonUnique = _(flatLayers)
121
+ .countBy(({ name }) => name)
122
+ .pickBy(v => v > 1)
123
+ .keys()
124
+ .value()
125
+ if (nonUnique.length > 0) {
126
+ throw new Error(`Cannot initialize host with non unique layers: ${nonUnique}`)
127
+ }
128
+ }
129
+
130
+ export function createAppHost(initialEntryPointsOrPackages: EntryPointOrPackage[], options: AppHostOptions = { monitoring: {} }): AppHost {
131
+ let store: PrivateThrottledStore | null = null
132
+ let isInstallingEntryPoints: boolean = false
133
+ let isStoreSubscribersNotifyInProgress = false
134
+ let isObserversNotifyInProgress = false
135
+ let shouldFlushMemoizationSync = false
136
+ let isBatchingDeclarationsChangedCallbacks = false
137
+ const entryPointsInstallationEndCallbacks: Map<string, () => void> = new Map()
138
+
139
+ verifyLayersUniqueness(options.layers)
140
+
141
+ const unReadyEntryPointsStore = createUnreadyEntryPointsStore()
142
+
143
+ const layers: InternalAPILayer[][] = _.map(options.layers ? castMultiArray(options.layers) : [], (singleDimension, i) =>
144
+ _.map(singleDimension, layer => ({ ...layer, dimension: i }))
145
+ )
146
+
147
+ const trace: Trace[] = []
148
+ const memoizedArr: StatisticsMemoization[] = []
149
+
150
+ const readyAPIs = new Set<AnySlotKey>()
151
+
152
+ const createExtensionSlotsMap = (): Map<AnySlotKey, AnyExtensionSlot> => {
153
+ const originalMap = new Map<AnySlotKey, AnyExtensionSlot>()
154
+
155
+ const proxyHandler = {
156
+ get(target: Map<AnySlotKey, AnyExtensionSlot>, propertyName: string | symbol) {
157
+ if (propertyName === 'set') {
158
+ return function (key: AnySlotKey, value: AnyExtensionSlot) {
159
+ return batchDeclarationsChangedCallbacks(() => target.set(key, value))
160
+ }
161
+ }
162
+
163
+ if (propertyName === 'delete') {
164
+ return function (key: AnySlotKey) {
165
+ return batchDeclarationsChangedCallbacks(() => target.delete(key))
166
+ }
167
+ }
168
+
169
+ const originalProperty = target[propertyName as keyof Map<AnySlotKey, AnyExtensionSlot>]
170
+
171
+ if (typeof originalProperty === 'function') {
172
+ return originalProperty.bind(target)
173
+ }
174
+
175
+ return originalProperty
176
+ }
177
+ }
178
+
179
+ return new Proxy(originalMap, proxyHandler)
180
+ }
181
+
182
+ const uniqueShellNames = new Set<string>()
183
+ const extensionSlots = createExtensionSlotsMap()
184
+ const slotKeysByName = new Map<string, AnySlotKey>()
185
+ const addedShells = new Map<string, PrivateShell>()
186
+ const shellInstallers = new WeakMap<PrivateShell, string[]>()
187
+ const lazyShells = new Map<string, LazyEntryPointFactory>()
188
+ const shellsChangedCallbacks = new Map<string, ShellsChangedCallback>()
189
+ const declarationsChangedCallbacks = new Map<string, DeclarationsChangedCallback>()
190
+ const APILayers = new WeakMap<AnySlotKey, APILayer[] | undefined>()
191
+
192
+ const memoizedFunctions: IterableWeakMap<AnyFunction, MemoizedFunctionData> = new IterableWeakMap()
193
+ let shouldFlushMemoization = false
194
+
195
+ const hostAPI: AppHostAPI = {
196
+ getAllEntryPoints: () => [...addedShells.entries()].map(([, { entryPoint }]) => entryPoint),
197
+ getAppHostOptions: () => options
198
+ }
199
+ const appHostServicesEntryPoint = createAppHostServicesEntryPoint(() => hostAPI)
200
+ const host: PrivateAppHost & AppHostServicesProvider = {
201
+ getStore,
202
+ getAPI,
203
+ hasAPI,
204
+ getSlot,
205
+ hasSlot,
206
+ getAllSlotKeys,
207
+ getAllEntryPoints,
208
+ hasShell,
209
+ isLazyEntryPoint,
210
+ addShells,
211
+ removeShells,
212
+ onShellsChanged,
213
+ onDeclarationsChanged,
214
+ removeShellsChangedCallback,
215
+ getAppHostServicesShell: appHostServicesEntryPoint.getAppHostServicesShell,
216
+ log: options.logger ? options.logger : ConsoleHostLogger,
217
+ options,
218
+ executeWhenFree
219
+ }
220
+
221
+ setupDebugInfo({
222
+ host,
223
+ readyAPIs,
224
+ uniqueShellNames,
225
+ extensionSlots,
226
+ addedShells,
227
+ shellInstallers,
228
+ lazyShells,
229
+
230
+ performance: {
231
+ options,
232
+ trace,
233
+ memoizedArr
234
+ },
235
+
236
+ getUnreadyEntryPoints: unReadyEntryPointsStore.get,
237
+ getOwnSlotKey,
238
+ getAPI
239
+ })
240
+
241
+ declareSlot<ReactComponentContributor>(mainViewSlotKey)
242
+ declareSlot<StateContribution>(stateSlotKey)
243
+ addShells([appHostServicesEntryPoint])
244
+
245
+ const memoize = <T extends AnyFunction>(
246
+ func: T,
247
+ resolver: FunctionWithSameArgs<T>
248
+ ): ((...args: Parameters<T>) => ReturnType<T>) & Partial<_.MemoizedFunction> & Partial<MemoizeMissHit> => {
249
+ if (options.monitoring.disableMemoization) {
250
+ return func
251
+ }
252
+ const memoized = _.memoize(func, resolver)
253
+
254
+ if (options.monitoring.debugMemoization) {
255
+ Object.defineProperty(memoized, 'name', { value: `${func.name}_memoized`, writable: false })
256
+ }
257
+
258
+ if (options.monitoring.disableMonitoring) {
259
+ return memoized
260
+ }
261
+
262
+ const enrichedMemoization = enrichMemoization(memoized)
263
+
264
+ if (options.monitoring.debugMemoization) {
265
+ return (...args: any[]) => {
266
+ const memRes = enrichedMemoization(...args)
267
+ const res = func(...args)
268
+ if (!_.isEqual(memRes, res)) {
269
+ console.log(`Memoization Error`)
270
+ console.log(`Memoization returns:`, memRes)
271
+ console.log(`Original Func returns:`, res)
272
+ console.log(`Original Func:`, func)
273
+ }
274
+ return memRes
275
+ }
276
+ }
277
+ return enrichedMemoization
278
+ }
279
+
280
+ function lazyEvaluator<F extends AnyFunction, T extends ReturnType<F>>(fn: F): Lazy<T> {
281
+ let _value: T
282
+ let _resolved: boolean = false
283
+
284
+ return {
285
+ get: () => {
286
+ if (!_resolved) {
287
+ _value = fn()
288
+ _resolved = true
289
+ }
290
+
291
+ return _value
292
+ }
293
+ }
294
+ }
295
+
296
+ // we know that addShells completes synchronously
297
+ addShells(initialEntryPointsOrPackages)
298
+
299
+ return host
300
+
301
+ //TODO: get rid of LazyEntryPointDescriptor
302
+ function isLazyEntryPointDescriptor(value: AnyEntryPoint): value is LazyEntryPointDescriptor {
303
+ return typeof (value as LazyEntryPointDescriptor).factory === 'function'
304
+ }
305
+
306
+ function enrichMemoization<T extends _.MemoizedFunction & Partial<MemoizeMissHit>>(memoized: T): T & MemoizeMissHit {
307
+ const memoizedWithMissHit = _.assign(memoized, {
308
+ miss: 0,
309
+ calls: 0,
310
+ hit: 0,
311
+ printHitMiss: () =>
312
+ console.log(
313
+ `calls: ${memoizedWithMissHit.calls}
314
+ hit: ${memoizedWithMissHit.hit}
315
+ miss: ${memoizedWithMissHit.miss}
316
+ `
317
+ )
318
+ })
319
+
320
+ const getter = memoizedWithMissHit.cache.get.bind(memoized.cache)
321
+
322
+ memoizedWithMissHit.cache.get = (key: any) => {
323
+ memoizedWithMissHit.calls++
324
+ memoizedWithMissHit.hit++
325
+ return getter(key)
326
+ }
327
+
328
+ const setter = memoizedWithMissHit.cache.set.bind(memoizedWithMissHit.cache)
329
+
330
+ memoizedWithMissHit.cache.set = (key: any, value: any) => {
331
+ memoizedWithMissHit.calls++
332
+ memoizedWithMissHit.miss++
333
+ return setter(key, value)
334
+ }
335
+ return memoizedWithMissHit
336
+ }
337
+
338
+ function getLayerByName(layerName: string): InternalAPILayer {
339
+ const layer = _(layers).flatten().find({ name: layerName })
340
+ if (!layer) {
341
+ throw new Error(`Cannot find layer ${layerName}`)
342
+ }
343
+ return layer
344
+ }
345
+
346
+ type Dependency = { layer?: InternalAPILayer; apiKey: SlotKey<any> } | undefined
347
+ function validateEntryPointLayer(entryPoint: EntryPoint) {
348
+ if (!entryPoint.getDependencyAPIs || !entryPoint.layer || _.isEmpty(layers)) {
349
+ return
350
+ }
351
+ const highestLevelDependencies: Dependency[] = _.chain(entryPoint.getDependencyAPIs())
352
+ .flatMap<Dependency>(apiKey =>
353
+ apiKey.layer
354
+ ? _(apiKey.layer)
355
+ .castArray()
356
+ .map(l => ({
357
+ layer: getLayerByName(l),
358
+ apiKey
359
+ }))
360
+ .value()
361
+ : { apiKey }
362
+ )
363
+ .groupBy(dependency => dependency?.layer?.dimension)
364
+ .map(dimension => _.maxBy(dimension, dependency => (dependency?.layer ? dependency.layer.level : -Infinity)))
365
+ .value()
366
+
367
+ const currentLayers = _(entryPoint.layer)
368
+ .castArray()
369
+ .map(l => getLayerByName(l))
370
+ .value()
371
+
372
+ const getCurrentLayerOfSameDimension = (layer: InternalAPILayer): InternalAPILayer | undefined => {
373
+ return currentLayers.find(entryPointLayer => entryPointLayer.dimension === layer.dimension)
374
+ }
375
+
376
+ highestLevelDependencies.forEach(highestLevelDependency => {
377
+ const currentLayer = highestLevelDependency?.layer && getCurrentLayerOfSameDimension(highestLevelDependency.layer)
378
+ if (highestLevelDependency?.layer && currentLayer && currentLayer.level < highestLevelDependency.layer.level) {
379
+ throw new Error(
380
+ `Entry point ${entryPoint.name} of layer ${currentLayer.name} cannot depend on API ${slotKeyToName(
381
+ highestLevelDependency.apiKey
382
+ )} of layer ${highestLevelDependency.layer.name}`
383
+ )
384
+ }
385
+ })
386
+ }
387
+
388
+ function validateLayers(entryPoints: AnyEntryPoint[]) {
389
+ _.forEach(entryPoints, ep => validateEntryPointLayer(ep))
390
+ }
391
+
392
+ function addShells(entryPointsOrPackages: EntryPointOrPackage[]): Promise<void> {
393
+ host.log.log('debug', `Adding ${entryPointsOrPackages.length} packages.`)
394
+
395
+ const entryPoints = _.flatten(entryPointsOrPackages)
396
+ const existingEntryPoints = Object.values(addedShells).map(shell => shell.entryPoint)
397
+ const allEntryPoints = existingEntryPoints.concat(unReadyEntryPointsStore.get(), entryPoints)
398
+
399
+ if (!options.disableLayersValidation) {
400
+ validateLayers(entryPoints)
401
+ }
402
+ validateUniqueShellNames(entryPoints)
403
+ !options.disableCheckCircularDependencies && !options.experimentalCyclicMode && validateCircularDependency(allEntryPoints)
404
+
405
+ const [lazyEntryPointsList, readyEntryPointsList] = _.partition(entryPoints, isLazyEntryPointDescriptor) as [
406
+ LazyEntryPointDescriptor[],
407
+ EntryPoint[]
408
+ ]
409
+
410
+ executeInstallShell(readyEntryPointsList)
411
+ lazyEntryPointsList.forEach(registerLazyEntryPoint)
412
+
413
+ setInstalledShellNames(getInstalledShellNames().concat(_.map(lazyEntryPointsList, 'name')))
414
+ return Promise.resolve()
415
+ }
416
+
417
+ function isAllAPIDependenciesAreReadyOrPending(
418
+ checkedKey: SlotKey<any>,
419
+ pendingEntryPoints: EntryPoint[],
420
+ passed: SlotKey<any>[] = []
421
+ ): boolean {
422
+ // TODO: Avoid iterating N (cycle length) times for the same cycle
423
+ const declarers = pendingEntryPoints.flatMap(ep => (ep.declareAPIs?.() || []).map(k => [k, ep] as const))
424
+ const [, keyDeclarerEntryPoint] = declarers.find(([k, ep]) => _.isEqual(k, checkedKey)) || []
425
+ if (!keyDeclarerEntryPoint) {
426
+ return false
427
+ }
428
+
429
+ const dependencies = keyDeclarerEntryPoint.getDependencyAPIs && keyDeclarerEntryPoint.getDependencyAPIs()
430
+ const uncheckDependencies = _.differenceWith(dependencies, passed, _.isEqual)
431
+
432
+ const everyDependenciesReadyOrPending = _.every(
433
+ uncheckDependencies,
434
+ k => readyAPIs.has(getOwnSlotKey(k)) || isAllAPIDependenciesAreReadyOrPending(k, pendingEntryPoints, passed.concat(checkedKey))
435
+ )
436
+
437
+ return everyDependenciesReadyOrPending
438
+ }
439
+
440
+ function onInstallShellsEnd() {
441
+ const callbacks = entryPointsInstallationEndCallbacks.values()
442
+ try {
443
+ for (const callback of callbacks) {
444
+ callback()
445
+ }
446
+ } finally {
447
+ entryPointsInstallationEndCallbacks.clear()
448
+ }
449
+ }
450
+
451
+ function executeInstallShell(entryPoints: EntryPoint[]): void {
452
+ const [readyEntryPoints, currentUnReadyEntryPoints] = _.partition(entryPoints, entryPoint => {
453
+ const dependencies = entryPoint.getDependencyAPIs && entryPoint.getDependencyAPIs()
454
+ return _.every(
455
+ dependencies,
456
+ k =>
457
+ readyAPIs.has(getOwnSlotKey(k)) ||
458
+ (options.experimentalCyclicMode && isAllAPIDependenciesAreReadyOrPending(k, entryPoints))
459
+ )
460
+ })
461
+
462
+ unReadyEntryPointsStore.set(_.union(_.difference(unReadyEntryPointsStore.get(), readyEntryPoints), currentUnReadyEntryPoints))
463
+ if (store && _.isEmpty(readyEntryPoints)) {
464
+ onInstallShellsEnd()
465
+ return
466
+ }
467
+
468
+ const shells = readyEntryPoints.map(createShell)
469
+ executeReadyEntryPoints(shells)
470
+ }
471
+
472
+ function executeReadyEntryPoints(shells: PrivateShell[]): void {
473
+ batchDeclarationsChangedCallbacks(() => {
474
+ isInstallingEntryPoints = true
475
+ try {
476
+ invokeEntryPointPhase(
477
+ 'getDependencyAPIs',
478
+ shells,
479
+ f => f.entryPoint.getDependencyAPIs && f.setDependencyAPIs(f.entryPoint.getDependencyAPIs()),
480
+ f => !!f.entryPoint.getDependencyAPIs
481
+ )
482
+
483
+ invokeEntryPointPhase(
484
+ 'attach',
485
+ shells,
486
+ f => f.entryPoint.attach && f.entryPoint.attach(f),
487
+ f => !!f.entryPoint.attach
488
+ )
489
+
490
+ buildStore()
491
+ shells.forEach(f => f.setLifecycleState(true, true, false))
492
+
493
+ invokeEntryPointPhase(
494
+ 'extend',
495
+ shells,
496
+ f => f.entryPoint.extend && f.entryPoint.extend(f),
497
+ f => !!f.entryPoint.extend
498
+ )
499
+
500
+ shells.forEach(f => {
501
+ addedShells.set(f.entryPoint.name, f)
502
+ f.setLifecycleState(true, true, true)
503
+ })
504
+ } finally {
505
+ isInstallingEntryPoints = false
506
+ }
507
+ executeInstallShell(unReadyEntryPointsStore.get())
508
+ })
509
+ }
510
+
511
+ function executeShellsChangedCallbacks() {
512
+ shellsChangedCallbacks.forEach(f => f(_.keys(InstalledShellsSelectors.getInstalledShellsSet(getStore().getState()))))
513
+ }
514
+
515
+ function executeDeclarationsChangedCallbacks() {
516
+ declarationsChangedCallbacks.forEach(f => f())
517
+ }
518
+
519
+ function batchDeclarationsChangedCallbacks<T>(action: () => T): T {
520
+ if (isBatchingDeclarationsChangedCallbacks) {
521
+ return action()
522
+ }
523
+ try {
524
+ isBatchingDeclarationsChangedCallbacks = true
525
+ const result = action()
526
+ executeDeclarationsChangedCallbacks()
527
+ return result
528
+ } finally {
529
+ isBatchingDeclarationsChangedCallbacks = false
530
+ }
531
+ }
532
+
533
+ function setInstalledShellNames(names: string[]) {
534
+ const updates = toShellToggleSet(names, true)
535
+ getStore().dispatch(InstalledShellsActions.updateInstalledShells(updates))
536
+ executeShellsChangedCallbacks()
537
+ }
538
+
539
+ function setUninstalledShellNames(names: string[]) {
540
+ const updates = toShellToggleSet(names, false)
541
+ getStore().dispatch(InstalledShellsActions.updateInstalledShells(updates))
542
+ executeShellsChangedCallbacks()
543
+ }
544
+
545
+ function onDeclarationsChanged(callback: DeclarationsChangedCallback): UnsubscribeFromDeclarationsChanged {
546
+ const callbackId = _.uniqueId('declarations-changed-callback-')
547
+ declarationsChangedCallbacks.set(callbackId, callback)
548
+ return () => {
549
+ declarationsChangedCallbacks.delete(callbackId)
550
+ }
551
+ }
552
+
553
+ function onShellsChanged(callback: ShellsChangedCallback) {
554
+ const callbackId = _.uniqueId('shells-changed-callback-')
555
+ shellsChangedCallbacks.set(callbackId, callback)
556
+ return callbackId
557
+ }
558
+
559
+ function removeShellsChangedCallback(callbackId: string) {
560
+ shellsChangedCallbacks.delete(callbackId)
561
+ }
562
+
563
+ function declareSlot<TItem>(key: SlotKey<TItem>, declaringShell?: Shell): ExtensionSlot<TItem> {
564
+ const newSlot = registerSlotOrThrow(key, () => createExtensionSlot(key, host, declaringShell))
565
+ return newSlot
566
+ }
567
+
568
+ function declareCustomSlot<TItem>(
569
+ key: SlotKey<TItem>,
570
+ handler: CustomExtensionSlotHandler<TItem>,
571
+ declaringShell?: Shell
572
+ ): CustomExtensionSlot<TItem> {
573
+ const newSlot = registerSlotOrThrow(key, () => createCustomExtensionSlot(key, handler, host, declaringShell))
574
+ return newSlot
575
+ }
576
+
577
+ function slotKeyToName<T>(key: SlotKey<T>): string {
578
+ return key.version === undefined ? key.name : `${key.name}(v${key.version})`
579
+ }
580
+
581
+ function registerSlotOrThrow<TItem, TSlot extends AnyExtensionSlot>(key: SlotKey<TItem>, factory: () => TSlot): TSlot {
582
+ const slotName = slotKeyToName(key)
583
+ if (!extensionSlots.has(key) && !slotKeysByName.has(slotName)) {
584
+ const newSlot = factory()
585
+
586
+ extensionSlots.set(key, newSlot)
587
+ slotKeysByName.set(slotName, key)
588
+
589
+ return newSlot
590
+ }
591
+ throw new Error(`Extension slot with key '${slotName}' already exists.`)
592
+ }
593
+
594
+ function getSlot<TItem>(key: SlotKey<TItem>): ExtensionSlot<TItem> {
595
+ const ownKey = getOwnSlotKey(key)
596
+ const anySlot = extensionSlots.get(ownKey)
597
+
598
+ if (anySlot) {
599
+ return anySlot as ExtensionSlot<TItem>
600
+ }
601
+ throw new Error(`Extension slot with key '${slotKeyToName(key)}' doesn't exist.`)
602
+ }
603
+
604
+ function hasSlot<TItem>(key: SlotKey<TItem>): boolean {
605
+ const ownKey = getOwnSlotKey(key)
606
+ return extensionSlots.has(ownKey)
607
+ }
608
+
609
+ function tryGetAPI<TAPI>(key: SlotKey<TAPI>): TAPI | undefined {
610
+ if (!hasSlot(key)) {
611
+ return
612
+ }
613
+ const APISlot = getSlot<TAPI>(key)
614
+ const item = APISlot.getSingleItem()
615
+ return item?.contribution
616
+ }
617
+
618
+ function getAPI<TAPI>(key: SlotKey<TAPI>): TAPI {
619
+ const api = tryGetAPI(key)
620
+ if (!api) {
621
+ throw new Error(`API '${slotKeyToName(key)}' doesn't exist.`)
622
+ }
623
+ return api
624
+ }
625
+
626
+ function hasAPI<TAPI>(key: SlotKey<TAPI>): boolean {
627
+ return !!tryGetAPI(key)
628
+ }
629
+
630
+ function executeWhenFree(key: string, callback: () => void): void {
631
+ if (isInstallingEntryPoints) {
632
+ entryPointsInstallationEndCallbacks.set(key, callback)
633
+ } else {
634
+ callback()
635
+ }
636
+ }
637
+
638
+ function getStore(): ThrottledStore {
639
+ if (store) {
640
+ return store
641
+ }
642
+ throw new Error('Store was not yet created')
643
+ }
644
+
645
+ function getAllSlotKeys(): AnySlotKey[] {
646
+ return Array.from(extensionSlots.keys())
647
+ }
648
+
649
+ function getAllEntryPoints(): EntryPointsInfo[] {
650
+ throw new Error('not implemented')
651
+ }
652
+
653
+ function hasShell(name: string): boolean {
654
+ const installedShellsSet = InstalledShellsSelectors.getInstalledShellsSet(getStore().getState())
655
+ return installedShellsSet[name] === true
656
+ }
657
+
658
+ function isLazyEntryPoint(name: string): boolean {
659
+ return lazyShells.has(name)
660
+ }
661
+
662
+ function registerLazyEntryPoint(descriptor: LazyEntryPointDescriptor): void {
663
+ lazyShells.set(descriptor.name, descriptor.factory)
664
+ }
665
+
666
+ function getOwnSlotKey<T>(key: SlotKey<T>): SlotKey<T> {
667
+ if (key.public === true) {
668
+ const ownKey = slotKeysByName.get(slotKeyToName(key))
669
+ if (ownKey && ownKey.public) {
670
+ return ownKey as SlotKey<T>
671
+ }
672
+ }
673
+ return key
674
+ }
675
+
676
+ function validateUniqueShellNames(entryPoints: AnyEntryPoint[]): void {
677
+ entryPoints.forEach(f => validateUniqueShellName(f.name))
678
+ }
679
+
680
+ function validateUniqueShellName(name: string): void {
681
+ if (!uniqueShellNames.has(name)) {
682
+ uniqueShellNames.add(name)
683
+ } else {
684
+ throw new Error(`Shell named '${name}' already exists`)
685
+ }
686
+ }
687
+
688
+ function validateCircularDependency(entryPoints: AnyEntryPoint[]): void {
689
+ const allDeclaredAPIs = new Map<string, AnyEntryPoint>()
690
+ for (const ep of entryPoints) {
691
+ const apis = declaredAPIs(ep)
692
+ for (const api of apis) {
693
+ const existingDeclaration = allDeclaredAPIs.get(api.name)
694
+ if (existingDeclaration) {
695
+ host.log.log(
696
+ 'warning',
697
+ `The API: ${api.name} is declared in multiple endpoints: ${existingDeclaration.name} and ${ep.name}`
698
+ )
699
+ } else {
700
+ allDeclaredAPIs.set(api.name, ep)
701
+ }
702
+ }
703
+ }
704
+
705
+ const graph = new Graph()
706
+ entryPoints.forEach(ep => {
707
+ const declaredApis = declaredAPIs(ep).map(x => slotKeyToName(x))
708
+ const dependencies = dependentAPIs(ep).map(x => slotKeyToName(x))
709
+ declaredApis.forEach(d => dependencies.forEach(y => graph.addConnection(d, y)))
710
+ })
711
+
712
+ const tarjan = new Tarjan(graph)
713
+ const sccs = tarjan.run()
714
+
715
+ for (const scc of sccs) {
716
+ if (scc.length > 1) {
717
+ const dependentGraph: { [key: string]: string[] } = {}
718
+ entryPoints.forEach(ep => {
719
+ const declaredApis = declaredAPIs(ep).map(child => child.name)
720
+ const dependencies = dependentAPIs(ep).map(child => child.name)
721
+ declaredApis.forEach(d => {
722
+ dependentGraph[d] = dependencies
723
+ })
724
+ })
725
+
726
+ const circle = getCycle(dependentGraph)
727
+ if (circle) {
728
+ const reversedCircle = circle.reverse()
729
+ host.log.log('error', `Circular API dependency found: ${reversedCircle.join(' -> ')}`)
730
+ throw new Error(`Circular API dependency found`)
731
+ }
732
+ }
733
+ }
734
+ }
735
+
736
+ function buildStore(): Store {
737
+ const contributedState = getSlot(stateSlotKey)
738
+
739
+ if (store) {
740
+ updateThrottledStore(host, store, contributedState)
741
+ } else {
742
+ store = createThrottledStore(
743
+ host,
744
+ contributedState,
745
+ notifySubscribersIsRunning => {
746
+ isStoreSubscribersNotifyInProgress = notifySubscribersIsRunning
747
+ },
748
+ notifyObserversIsRunning => {
749
+ isObserversNotifyInProgress = notifyObserversIsRunning
750
+ },
751
+ updateShouldFlushMemoizationSync => {
752
+ shouldFlushMemoizationSync = updateShouldFlushMemoizationSync
753
+ }
754
+ )
755
+ store.subscribe(() => {
756
+ if (shouldFlushMemoization) {
757
+ shouldFlushMemoization = false
758
+ flushMemoizedForState()
759
+ }
760
+ })
761
+ store.syncSubscribe(() => {
762
+ shouldFlushMemoization = true
763
+ if (isStoreSubscribersNotifyInProgress || isObserversNotifyInProgress || shouldFlushMemoizationSync) {
764
+ shouldFlushMemoization = false
765
+ flushMemoizedForState()
766
+ }
767
+ })
768
+ }
769
+
770
+ return store
771
+ }
772
+
773
+ function flushMemoizedForState() {
774
+ memoizedFunctions.forEach(({ f, shouldClear }) => {
775
+ if ((shouldClear || _.stubTrue)()) {
776
+ clearCache(f)
777
+ }
778
+ })
779
+ }
780
+
781
+ function clearCache(memoizedFunction: Partial<_.MemoizedFunction> & Partial<MemoizeMissHit>) {
782
+ if (memoizedFunction.cache && memoizedFunction.cache.clear) {
783
+ memoizedFunction.cache.clear()
784
+ }
785
+ }
786
+
787
+ function invokeEntryPointPhase(
788
+ phase: keyof EntryPoint, // TODO: Exclude 'name'
789
+ shell: PrivateShell[],
790
+ action: (shell: PrivateShell) => void,
791
+ predicate?: (shell: PrivateShell) => boolean
792
+ ): void {
793
+ host.log.log('debug', `--- ${phase} phase ---`)
794
+
795
+ try {
796
+ shell.filter(f => !predicate || predicate(f)).forEach(f => invokeShell(f, action, phase))
797
+ } catch (err) {
798
+ console.error(`${phase} phase FAILED`, err)
799
+ throw err
800
+ }
801
+
802
+ host.log.log('debug', `--- End of ${phase} phase ---`)
803
+ }
804
+
805
+ function invokeShell(shell: PrivateShell, action: (shell: PrivateShell) => void, phase: string): void {
806
+ host.log.log('debug', `${phase} : ${shell.entryPoint.name}`)
807
+
808
+ try {
809
+ action(shell)
810
+ } catch (err) {
811
+ host.log.log('error', 'AppHost.shellFailed', err, {
812
+ shell: shell.name,
813
+ phase,
814
+ message: `Shell '${shell.name}' FAILED ${phase} phase`,
815
+ error: err
816
+ })
817
+ throw err
818
+ }
819
+ }
820
+
821
+ function getAPIContributor<TAPI>(key: SlotKey<TAPI>): Shell | undefined {
822
+ const ownKey = getOwnSlotKey(key)
823
+ return extensionSlots.has(ownKey) ? _.get(getSlot<TAPI>(ownKey).getSingleItem(), 'shell') : undefined
824
+ }
825
+
826
+ function doesExtensionItemBelongToShells(extensionItem: ExtensionItem<any>, shellNames: string[]) {
827
+ return (
828
+ _.includes(shellNames, extensionItem.shell.name) ||
829
+ _.some(_.invoke((extensionItem.shell as PrivateShell).entryPoint, 'getDependencyAPIs'), APIKey =>
830
+ _.includes(shellNames, _.get(getAPIContributor(APIKey), 'name'))
831
+ )
832
+ )
833
+ }
834
+
835
+ function discardSlotKey<T>(key: SlotKey<T>) {
836
+ const ownKey = getOwnSlotKey(key)
837
+ readyAPIs.delete(ownKey)
838
+ extensionSlots.delete(ownKey)
839
+ slotKeysByName.delete(slotKeyToName(ownKey))
840
+
841
+ host.log.log('debug', `-- Removed slot keys: ${slotKeyToName(ownKey)} --`)
842
+ }
843
+
844
+ function findDependantShells(entryShell: PrivateShell): PrivateShell[] {
845
+ const cache = new Map<string, PrivateShell[]>()
846
+
847
+ const _findDependantShells = (declaringShell: PrivateShell): PrivateShell[] =>
848
+ _([...addedShells.entries()])
849
+ .flatMap(([name, shell]) => {
850
+ const cachedValue = cache.get(name)
851
+ if (cachedValue) {
852
+ return cachedValue
853
+ }
854
+ const dependencyAPIs = shell.entryPoint?.getDependencyAPIs?.() || []
855
+ const isDependant = dependencyAPIs.some(key => getAPIContributor(key)?.name === declaringShell.name)
856
+ if (!isDependant) {
857
+ return []
858
+ }
859
+ const dependencies = [shell, ..._findDependantShells(shell)]
860
+ cache.set(name, dependencies)
861
+ return dependencies
862
+ })
863
+ .uniqBy('name')
864
+ .value()
865
+
866
+ return _findDependantShells(entryShell)
867
+ }
868
+
869
+ function isShellBeingDependantOnInGroup(declaringShell: PrivateShell, shells: PrivateShell[]): boolean {
870
+ return !!shells.find(dependantShell => {
871
+ const dependencyAPIs = dependantShell.entryPoint?.getDependencyAPIs?.() || []
872
+ return dependencyAPIs.find(key => getAPIContributor(key)?.name === declaringShell.name)
873
+ })
874
+ }
875
+ function executeDetachOnShellReadyForRemoval(shellsToBeDetached: PrivateShell[], originalRequestedRemovalNames: string[]) {
876
+ invokeEntryPointPhase('detach', shellsToBeDetached, f => _.invoke(f.entryPoint, 'detach', f))
877
+
878
+ const detachedShellsNames = shellsToBeDetached.map(({ name }) => name)
879
+
880
+ const slotKeysToDiscard = findContributedAPIs(detachedShellsNames).concat(findDeclaredSlotKeys(detachedShellsNames))
881
+
882
+ extensionSlots.forEach(extensionSlot =>
883
+ (extensionSlot as ExtensionSlot<any>).discardBy(extensionItem =>
884
+ doesExtensionItemBelongToShells(extensionItem, detachedShellsNames)
885
+ )
886
+ )
887
+
888
+ detachedShellsNames.forEach(name => {
889
+ const isResultOfMissingDependency = !originalRequestedRemovalNames.includes(name)
890
+ if (isResultOfMissingDependency) {
891
+ const entryPoint = addedShells.get(name)?.entryPoint
892
+ entryPoint && unReadyEntryPointsStore.get().push(entryPoint)
893
+ }
894
+ addedShells.delete(name)
895
+ uniqueShellNames.delete(name)
896
+ })
897
+
898
+ slotKeysToDiscard.forEach(discardSlotKey)
899
+
900
+ host.log.log('debug', `Done uninstalling ${detachedShellsNames}`)
901
+ }
902
+
903
+ function executeUninstallShells(names: string[]): void {
904
+ batchDeclarationsChangedCallbacks(() => {
905
+ host.log.log('debug', `-- Uninstalling ${names} --`)
906
+
907
+ const shellsCandidatesToBeDetached = _(names)
908
+ .map(name => addedShells.get(name))
909
+ .compact()
910
+ .flatMap<PrivateShell>(shell => [shell, ...findDependantShells(shell)])
911
+ .uniqBy('name')
912
+ .value()
913
+
914
+ let queue = shellsCandidatesToBeDetached
915
+ while (!_.isEmpty(queue)) {
916
+ const shellsToBeDetached = queue.filter(ep => !isShellBeingDependantOnInGroup(ep, queue))
917
+ if (_.isEmpty(shellsToBeDetached)) {
918
+ throw new Error(`Some shells could not detach: ${queue.map(({ name }) => name).join()}`)
919
+ }
920
+ executeDetachOnShellReadyForRemoval(shellsToBeDetached, names)
921
+ queue = _.differenceBy(queue, shellsToBeDetached, 'name')
922
+ }
923
+ })
924
+ }
925
+
926
+ function findContributedAPIs(shellNames: string[]) {
927
+ return [...readyAPIs].filter(APIKey => _.includes(shellNames, _.get(getAPIContributor(APIKey), 'name')))
928
+ }
929
+
930
+ function findDeclaredSlotKeys(shellNames: string[]) {
931
+ const shellNameSet = new Set<string>(shellNames)
932
+ const result: AnySlotKey[] = []
933
+ for (const entry of extensionSlots.entries()) {
934
+ const { declaringShell } = entry[1]
935
+ if (declaringShell && shellNameSet.has(declaringShell.name)) {
936
+ result.push(entry[0])
937
+ }
938
+ }
939
+ return result
940
+ }
941
+
942
+ function getInstalledShellNames(): string[] {
943
+ return [...addedShells].map(([v]) => v)
944
+ }
945
+
946
+ function removeShells(names: string[]): Promise<void> {
947
+ const shellNames = getInstalledShellNames()
948
+ executeUninstallShells(names)
949
+ setUninstalledShellNames(_.difference(shellNames, getInstalledShellNames()))
950
+
951
+ return Promise.resolve()
952
+ }
953
+
954
+ function createShell(entryPoint: EntryPoint): PrivateShell {
955
+ let storeEnabled = false
956
+ let APIsEnabled = false
957
+ let wasInitCompleted = false
958
+ let dependencyAPIs: Set<AnySlotKey> = new Set()
959
+ let nextObservableId = 1
960
+ const boundaryAspects: ShellBoundaryAspect[] = []
961
+
962
+ function isOwnContributedAPI<TAPI>(key: SlotKey<TAPI>): boolean {
963
+ return getAPIContributor(key) === shell
964
+ }
965
+
966
+ const shell: PrivateShell = {
967
+ name: entryPoint.name,
968
+ entryPoint,
969
+
970
+ getSlot<TItem>(key: SlotKey<TItem>): ExtensionSlot<TItem> {
971
+ const slot = host.getSlot(key)
972
+ const { declaringShell } = slot
973
+ if (!declaringShell || declaringShell !== shell) {
974
+ throw new Error(
975
+ `Shell '${shell.name}' is trying to get slot '${slotKeyToName(key)}' that is owned by '${
976
+ declaringShell ? declaringShell.name : 'Host'
977
+ }'`
978
+ )
979
+ }
980
+ return slot
981
+ },
982
+
983
+ hasSlot<TItem>(key: SlotKey<TItem>): boolean {
984
+ if (hasSlot(key)) {
985
+ const slot = host.getSlot(key)
986
+ const { declaringShell } = slot
987
+ return !!declaringShell && declaringShell === shell
988
+ }
989
+ return false
990
+ },
991
+
992
+ getAllSlotKeys: host.getAllSlotKeys,
993
+ getAllEntryPoints: host.getAllEntryPoints,
994
+ hasShell: host.hasShell,
995
+ isLazyEntryPoint: host.isLazyEntryPoint,
996
+ onShellsChanged: host.onShellsChanged,
997
+ onDeclarationsChanged: host.onDeclarationsChanged,
998
+ removeShellsChangedCallback: host.removeShellsChangedCallback,
999
+
1000
+ declareSlot<TItem>(key: SlotKey<TItem>): ExtensionSlot<TItem> {
1001
+ return declareSlot<TItem>(key, shell)
1002
+ },
1003
+
1004
+ declareCustomSlot<TItem>(key: SlotKey<TItem>, handler: CustomExtensionSlotHandler<TItem>): CustomExtensionSlot<TItem> {
1005
+ return declareCustomSlot<TItem>(key, handler, shell)
1006
+ },
1007
+
1008
+ setLifecycleState(enableStore: boolean, enableAPIs: boolean, initCompleted: boolean) {
1009
+ storeEnabled = enableStore
1010
+ APIsEnabled = enableAPIs
1011
+ wasInitCompleted = initCompleted
1012
+ },
1013
+
1014
+ setDependencyAPIs(APIs: AnySlotKey[]): void {
1015
+ dependencyAPIs = new Set(APIs)
1016
+ },
1017
+
1018
+ canUseAPIs(): boolean {
1019
+ return APIsEnabled
1020
+ },
1021
+
1022
+ canUseStore(): boolean {
1023
+ return storeEnabled
1024
+ },
1025
+
1026
+ wasInitializationCompleted(): boolean {
1027
+ return wasInitCompleted
1028
+ },
1029
+
1030
+ runLateInitializer<T>(initializer: () => T): T {
1031
+ const saveWasInitCompleted = wasInitCompleted
1032
+ try {
1033
+ wasInitCompleted = false
1034
+ return initializer()
1035
+ } finally {
1036
+ wasInitCompleted = saveWasInitCompleted
1037
+ }
1038
+ },
1039
+
1040
+ addShells(entryPointsOrPackages: EntryPointOrPackage[]): Promise<void> {
1041
+ const shellNamesToBeinstalled = _.flatten(entryPointsOrPackages).map(x => x.name)
1042
+ const shellNamesInstalledByCurrentEntryPoint = shellInstallers.get(shell) || []
1043
+ shellInstallers.set(shell, [...shellNamesInstalledByCurrentEntryPoint, ...shellNamesToBeinstalled])
1044
+ return host.addShells(entryPointsOrPackages)
1045
+ },
1046
+
1047
+ removeShells(names: string[]): Promise<void> {
1048
+ const namesInstalledByCurrentEntryPoint = shellInstallers.get(shell) || []
1049
+ const namesNotInstalledByCurrentEntryPoint = _.difference(names, namesInstalledByCurrentEntryPoint)
1050
+ // TODO: Allow entry point to uninstall its own shell ?
1051
+ if (!_.isEmpty(namesNotInstalledByCurrentEntryPoint)) {
1052
+ throw new Error(
1053
+ `Shell ${entryPoint.name} is trying to uninstall shells: ${names} which is are not installed by entry point ${entryPoint.name} - This is not allowed`
1054
+ )
1055
+ }
1056
+ shellInstallers.set(shell, _.without(namesInstalledByCurrentEntryPoint, ...names))
1057
+ return host.removeShells(names)
1058
+ },
1059
+
1060
+ getAPI<TAPI>(key: SlotKey<TAPI>): TAPI {
1061
+ if (dependencyAPIs.has(key) || isOwnContributedAPI(key)) {
1062
+ return host.getAPI(key)
1063
+ }
1064
+ throw new Error(
1065
+ `API '${slotKeyToName(key)}' is not declared as dependency by entry point '${
1066
+ entryPoint.name
1067
+ }' (forgot to return it from getDependencyAPIs?)`
1068
+ )
1069
+ },
1070
+
1071
+ hasAPI<TAPI>(key: SlotKey<TAPI>): boolean {
1072
+ return (dependencyAPIs.has(key) || isOwnContributedAPI(key)) && host.hasAPI(key)
1073
+ },
1074
+
1075
+ contributeAPI<TAPI>(key: SlotKey<TAPI>, factory: () => TAPI, apiOptions?: ContributeAPIOptions<TAPI>): TAPI {
1076
+ host.log.log('debug', `Contributing API ${slotKeyToName(key)}.`)
1077
+
1078
+ if (!_.includes(_.invoke(entryPoint, 'declareAPIs') || [], key)) {
1079
+ throw new Error(
1080
+ `Entry point '${entryPoint.name}' is trying to contribute API '${slotKeyToName(key)}' which it didn't declare`
1081
+ )
1082
+ }
1083
+
1084
+ const areSameLayers = (l1: string | string[] | undefined, l2: string | string[] | undefined) =>
1085
+ _.isEqual(_(l1).castArray().sort().value(), _(l2).castArray().sort().value())
1086
+
1087
+ if (!options.disableLayersValidation && (entryPoint.layer || key.layer) && !areSameLayers(entryPoint.layer, key.layer)) {
1088
+ throw new Error(
1089
+ `Cannot contribute API ${slotKeyToName(key)} of layer ${key.layer || '<BLANK>'} from entry point ${
1090
+ entryPoint.name
1091
+ } of layer ${entryPoint.layer || '<BLANK>'}`
1092
+ )
1093
+ }
1094
+
1095
+ const api = factory()
1096
+ const monitoredAPI = monitorAPI(
1097
+ shell,
1098
+ options,
1099
+ normalizeApiName(slotKeyToName(key)),
1100
+ api /*, trace, memoizedArr*/,
1101
+ apiOptions
1102
+ )
1103
+ const apiSlot = declareSlot<TAPI>(key)
1104
+
1105
+ APILayers.set(
1106
+ key,
1107
+ !options.disableLayersValidation && entryPoint.layer
1108
+ ? _(entryPoint.layer)
1109
+ .castArray()
1110
+ .map(l => getLayerByName(l))
1111
+ .value()
1112
+ : undefined
1113
+ )
1114
+ apiSlot.contribute(shell, monitoredAPI)
1115
+
1116
+ readyAPIs.add(key)
1117
+
1118
+ if (!isInstallingEntryPoints) {
1119
+ const shellNames = _.map(unReadyEntryPointsStore.get(), 'name')
1120
+ executeInstallShell(unReadyEntryPointsStore.get())
1121
+ setInstalledShellNames(_.difference(shellNames, _.map(unReadyEntryPointsStore.get(), 'name')))
1122
+ }
1123
+
1124
+ return monitoredAPI
1125
+ },
1126
+
1127
+ contributeState<TState, TAction extends AnyAction = AnyAction>(
1128
+ contributor: ReducersMapObjectContributor<TState, TAction>
1129
+ ): void {
1130
+ const contribution: StateContribution = {
1131
+ notificationScope: 'broadcasting',
1132
+ reducerFactory: contributor
1133
+ }
1134
+ getSlot(stateSlotKey).contribute(shell, contribution)
1135
+ },
1136
+
1137
+ contributeObservableState<TState, TSelectorAPI, TAction extends AnyAction = AnyAction>(
1138
+ contributor: ReducersMapObjectContributor<TState, TAction>,
1139
+ mapStateToSelectors: (state: TState) => TSelectorAPI
1140
+ ): ObservableState<TSelectorAPI> {
1141
+ const observableUniqueName = `${entryPoint.name}/observable_${nextObservableId++}`
1142
+ const observable = createObservable(shell, observableUniqueName, mapStateToSelectors)
1143
+ observable.subscribe(shell, () => {
1144
+ flushMemoizedForState()
1145
+ })
1146
+ const protectedObservable: PrivateObservableState<TState, TSelectorAPI> = {
1147
+ subscribe: observable.subscribe,
1148
+ current: (allowUnsafeReading?: boolean) => {
1149
+ if (isStoreSubscribersNotifyInProgress && !allowUnsafeReading) {
1150
+ throw new Error(
1151
+ `Observer created by ${shell.name} current() function: ` +
1152
+ 'Should not read observable value during subscribers notify. ' +
1153
+ 'If you wish to read the value, you component should be observing the value directly ' +
1154
+ '(using observeWithShell or connectWithShellAndObserve)'
1155
+ )
1156
+ }
1157
+ return observable.current()
1158
+ },
1159
+ notify: observable.notify
1160
+ }
1161
+ const contribution: StateContribution = {
1162
+ notificationScope: 'observable',
1163
+ reducerFactory: contributor,
1164
+ observable
1165
+ }
1166
+ getSlot(stateSlotKey).contribute(shell, contribution)
1167
+ return protectedObservable
1168
+ },
1169
+
1170
+ getStore<TState>(): ScopedStore<TState> {
1171
+ return {
1172
+ dispatch: (host.getStore() as PrivateThrottledStore).dispatchWithShell(shell),
1173
+ subscribe: host.getStore().subscribe,
1174
+ getState: () => {
1175
+ const entireStoreState = host.getStore().getState()
1176
+ return entireStoreState[shell.name]
1177
+ },
1178
+ flush: host.getStore().flush,
1179
+ hasPendingSubscribers: host.getStore().hasPendingSubscribers,
1180
+ deferSubscriberNotifications: host.getStore().deferSubscriberNotifications
1181
+ }
1182
+ },
1183
+
1184
+ contributeMainView(fromShell: Shell, contributor: ReactComponentContributor): void {
1185
+ getSlot(mainViewSlotKey).contribute(fromShell, contributor)
1186
+ },
1187
+
1188
+ contributeBoundaryAspect(component: ShellBoundaryAspect): void {
1189
+ boundaryAspects.push(component)
1190
+ },
1191
+
1192
+ memoizeForState(func, resolver, shouldClear?) {
1193
+ const memoized = memoize(func, resolver)
1194
+ memoizedFunctions.set(memoized, shouldClear ? { f: memoized, shouldClear } : { f: memoized })
1195
+ return memoized
1196
+ },
1197
+ flushMemoizedForState,
1198
+ memoize(func, resolver) {
1199
+ return memoize(func, resolver)
1200
+ },
1201
+ clearCache,
1202
+
1203
+ getBoundaryAspects(): ShellBoundaryAspect[] {
1204
+ return boundaryAspects
1205
+ },
1206
+
1207
+ getHostOptions: () => host.options,
1208
+
1209
+ log: createShellLogger(host, entryPoint),
1210
+
1211
+ [INTERNAL_DONT_USE_SHELL_GET_APP_HOST]: () => host,
1212
+
1213
+ lazyEvaluator
1214
+ }
1215
+
1216
+ return shell
1217
+ }
1218
+
1219
+ function normalizeApiName(name: string) {
1220
+ return name.charAt(0).toLowerCase() + name.substring(1).replace(new RegExp(' ', 'g'), '')
1221
+ }
1222
+ }