async function watchServices (app, opts) {
  const { services, refreshTimeout } = opts.composer
  if (refreshTimeout < 1) {
    return
  }

  const watching = services.filter(isFetchable)
  if (watching.length < 1) {
    return
  }

  if (!globalThis[Symbol.for('plt.runtime.id')]) {
    app.log.warn('Watching services is only supported when running within a Platformatic Runtime.')
    return
  }

  const { fetchOpenApiSchema } = await import('./lib/openapi-fetch-schemas.mjs')

  app.log.info({ services: watching }, 'start watching services')

  const timer = setInterval(async () => {
    try {
      if (await detectServicesUpdate({ app, services: watching, fetchOpenApiSchema, fetchGraphqlSubgraphs })) {
        clearInterval(timer)
        app.log.info('detected services changes, restarting ...')

        globalThis[Symbol.for('plt.runtime.itc')].notify('changed')
      }
    } catch (error) {
      app.log.error(
        {
          err: {
            message: error.message,
            stack: error.stack
          }
        },
        'failed to get services info'
      )
    }
  }, refreshTimeout).unref()

  app.addHook('onClose', async () => {
    clearInterval(timer)
  })
}
poll services to detect changes, every `opts.composer.refreshTimeout`
polling is disabled on refreshTimeout = 0
or there are no network openapi nor graphql remote services (the services are from file or they don't have a schema/graph to fetch)