{"version":3,"sources":["common/appContext/appContext.ts"],"names":[],"mappings":"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","file":"../../../common/appContext/appContext.d.ts","sourcesContent":["import { Barrier, once, pmap } from '../mutexHelper.js';\nimport { passThroughProvider, Provider } from '../provider.js';\n\nexport interface Dictionary<T> {\n  [key: string]: T;\n}\n\n/**\n * Extract a subset from a larger dictionary\n *\n * @param all\n * @param keyList\n * @return subset\n */\nexport function lookup<T>(all: Dictionary<T>, keyList: string[]): Dictionary<T> {\n  return keyList.map((k) => ({ k, v: all[k] })).filter((kv) => !!kv.v).reduce(\n    (acc, { k, v }) => {\n      acc[k] = v;\n      return acc;\n    }, {},\n  );\n}\n\nexport function join<T>(kvList: { k: string, v: T }[]): Dictionary<T> {\n  return kvList.reduce(\n    (acc, { k, v }) => {\n      acc[k] = v;\n      return acc;\n    }, {},\n  );\n}\n\nexport function split<T>(dict: Dictionary<T>): { k: string, v: T }[] {\n  return Object.entries(dict).map(\n    ([k, v]) => ({ k, v }),\n  );\n}\n\n/**\n * Convert a dictionary of promises to a promise\n * for a dictionary\n */\nexport function toPromise<T>(dict: Dictionary<Promise<T>>): Promise<Dictionary<T>> {\n  return Promise.all(\n    split(dict).map(\n      (kv) => kv.v.then((v) => ({ k: kv.k, v })),\n    ),\n  ).then(\n    (kvList) => join(kvList),\n  );\n}\n\nexport type ConfigDb = Dictionary<Dictionary<any>>;\n\n/**\n * An entry in the AppContext configuration db - has\n * default dictionary and loaded overrides,\n * can do simple shallow merge with { ...defaults, ...overrides }\n */\nexport interface ConfigEntry {\n  defaults: Dictionary<any>;\n  overrides: Dictionary<any>;\n}\n\nexport interface AppContextConfig {\n  configHref: string[];\n  loadConfig: (href: string) => Promise<Dictionary<Dictionary<any>>>;\n}\n\nexport type ToolBox = Dictionary<Provider<any>>;\n\nexport function getTools(tb: ToolBox): Promise<Dictionary<any>> {\n  return toPromise(\n    join(\n      split(tb).map(({ k, v }) => ({ k, v: v.get() })),\n    ),\n  );\n}\n\nexport type ToolFactory<T> = (toolbox: ToolBox) => Provider<T> | Promise<Provider<T>>;\n\nexport interface ProviderInfo {\n  key: string;\n  toolKeys: Dictionary<string>;\n  lambda: ToolFactory<any>;\n}\n\n/**\n * Core application context database decoupled\n * from custom element configuration mechanism\n *\n * LifeCycle:\n *    singleton - put config accepted\n *    started   - lookup config accepted, no new puts\n */\nexport class AppContext {\n  public static build(config: AppContextConfig): Promise<AppContext> {\n    if (AppContext.singletonBarrier.state !== 'unresolved') {\n      throw new Error('Singleton AppContext already initialized');\n    }\n    const singleton = new AppContext(config);\n    AppContext.singletonBarrier.signal(\n      singleton.init().then(() => singleton),\n    );\n    return AppContext.singletonBarrier.wait();\n  }\n\n  public static get(): Promise<AppContext> { return AppContext.singletonBarrier.wait(); }\n\n  private static singletonBarrier: Barrier<AppContext> = new Barrier<AppContext>();\n\n  public config: AppContextConfig;\n\n  /**\n     * Trigger context launch - invoke once\n     * from main().\n     *\n     * @returns a list of tools that\n     * are required by registered providers, but\n     * unbound.\n     */\n  public start: () => Promise<string[]> = once(\n    async () => {\n      const configList: Dictionary<Dictionary<any>>[] = await pmap(\n        this.config.configHref,\n        (url) => this.config.loadConfig(url),\n      );\n      this.overrideConfigs = configList.reduce(\n        (acc, db) => Object.assign(acc, db),\n        {},\n      );\n      // register a provider for any keys not already\n      Object.keys(this.overrideConfigs).forEach(\n        (configKey) => {\n          const providerKey = `config/${configKey}`;\n          if (!this.providerDb.hasOwnProperty(providerKey)) {\n            // go ahead and register a provider that retrieves the configuration\n            const configProvider = passThroughProvider(() => this.getConfig(configKey));\n            this.providerDb[providerKey] = () => Promise.resolve(configProvider);\n          }\n        },\n      );\n      const unboundTools = [...this.allToolKeys].filter((k) => !this.providerDb.hasOwnProperty(k));\n      if (!this.startBarrier.signal(unboundTools)) {\n        throw new Error('Context perviously started');\n      }\n      return this.startBarrier.wait();\n    },\n    () => { throw new Error('Context perviously started'); },\n  );\n\n  private defaultConfigs: ConfigDb = {};\n\n  private overrideConfigs: ConfigDb = {};\n\n  // Dictionary of lazy-initialized providers.\n  // The Provider factory is not invoked until\n  // the first request for the provider.\n  private providerDb: Dictionary<() => Promise<Provider<any>>> = {};\n\n  private allToolKeys: Set<string> = new Set();\n\n  private state: Dictionary<any> = {};\n\n  private subscriptions: Dictionary<any> = {};\n\n  private startBarrier = new Barrier<string[]>();\n\n  /**\n     * Constructor takes an immutable configuration\n     */\n  constructor(config: AppContextConfig) {\n    this.config = { ...config };\n  }\n\n  /**\n     * Register a new provider.\n     * A provider's factory specifies the dependencies (toolbox)\n     * it wants injected.  A tool dependency must start with\n     * driver/, alias/, or config/ - where config/ translates\n     * into a call to context.getConfig\n     *\n     * @param keyIn automatically prepended with \"driver/\" if not already\n     * @param toolKeys map from global key (driver/, alias/, or config/)\n     *           to the internal key passed to the tool factory\n     * @param lambda\n     */\n  public putProvider(keyIn: string, toolKeys: Dictionary<string>, lambda: ToolFactory<any>) {\n    const key = keyIn.replace(/^\\/*(driver\\/+)*/, 'driver/');\n    this.putProviderOrAlias(key, toolKeys, lambda);\n  }\n\n  /**\n     * Register a lambda to run at startup\n     *\n     * @param toolKeys to inject into lambda - alias to rawKey\n     * @param lambda\n     */\n  public onStart<T>(toolKeys: Dictionary<string>, lambda: (ToolBox) => T | Promise<T>): Promise<T> {\n    return this.startBarrier.wait().then(\n      () => this.fillToolBox(toolKeys).then((toolBox) => lambda(toolBox)),\n    );\n  }\n\n  /**\n     * Register a new provider.\n     *\n     * @param alias automatically prepended with \"alias/\" if not already\n     * @param driverName automatically prepended with \"driver/\" if not already\n     */\n  public putAlias(alias: string, driverName: string) {\n    const key = alias.replace(/^\\/*(alias\\/+)*/, 'alias/');\n    const tools = { driver: driverName };\n\n    this.putProviderOrAlias(key, tools,\n      (toolBox) => toolBox.driver);\n  }\n\n  /**\n     *\n     * @param key must start with driver/, alias/, or config/\n     */\n  public getProvider<T>(key: string): Promise<Provider<T>> {\n    return this.startBarrier.wait().then(\n      () => {\n        let factory = this.providerDb[key];\n        if (!factory && key.startsWith('config/')) {\n          // go ahead and register a provider that retrieves the configuration\n          const configKey = key.replace(/^config\\//, '');\n          const configProvider = passThroughProvider(() => this.getConfig(configKey));\n          factory = () => Promise.resolve(configProvider);\n          this.providerDb[key] = factory;\n        }\n\n        if (!factory) {\n          throw new Error(`no provider registered for key ${key}`);\n        }\n        return factory();\n      },\n    );\n  }\n\n  /**\n     * Put a default configuration into the context.\n     * Note that config overrides loaded at runtime\n     * overwrite a default put into the context.\n     * This method is intended for use by modules that\n     * want to register default configuration at\n     * startup time.  Multiple calls to putDeafultConfig\n     * with the same key call Object.assign() to augment\n     * the previous value.\n     *\n     * @param contextIn\n     * @param key\n     */\n  public putDefaultConfig(key: string, value: Dictionary<any>): void {\n    const configKey = key.replace(/^config\\//, '');\n    const providerKey = `config/${configKey}`;\n    const currentConfig = this.defaultConfigs[configKey] || {};\n    this.defaultConfigs[configKey] = { ...currentConfig, ...value };\n    if (!this.providerDb.hasOwnProperty(providerKey)) {\n      // go ahead and register a provider that retrieves the configuration\n      const configProvider = passThroughProvider(() => this.getConfig(configKey));\n      this.providerDb[providerKey] = () => Promise.resolve(configProvider);\n    }\n  }\n\n  public async getConfig(key: string): Promise<ConfigEntry> {\n    return this.startBarrier.wait().then(\n      () => (\n        {\n          defaults: this.defaultConfigs[key] || {},\n          overrides: this.overrideConfigs[key] || {},\n        }\n      ),\n    );\n  }\n\n  // eslint-disable-next-line\n  public async getState(key: string, part: string): Promise<any> {\n    return null;\n  }\n\n  // eslint-disable-next-line\n  public async changeState(key: string, handler: (state: any) => Promise<any>): Promise<any> {\n    return null;\n  }\n\n  /**\n     * Load the remote configuration specified\n     * by the constructor injected properties\n     */\n  private init(): Promise<void> {\n    return Promise.resolve();\n  }\n\n  private fillToolBox(toolKeys: Dictionary<string>): Promise<ToolBox> {\n    return Promise.all(\n      split(toolKeys).map(\n        (kv) => {\n          const rawKey = kv.v;\n          const alias = kv.k;\n          const providerFactory = this.providerDb[rawKey];\n          if (!providerFactory) {\n            throw new Error(`failed to fill toolbox - missing dependency ${rawKey}`);\n          }\n          return providerFactory().then((v) => ({ k: alias, v }));\n        },\n      ),\n    ).then((kvList) => join(kvList));\n  }\n\n  private putProviderOrAlias(key: string, toolKeys: Dictionary<string>, lambda: ToolFactory<any>) {\n    if (this.providerDb.hasOwnProperty(key)) {\n      throw new Error(`provider already registered for ${key}`);\n    }\n    Object.values(toolKeys).forEach(\n      (k) => {\n        if (k.startsWith('driver/') || k.startsWith('alias/') || k.startsWith('config/')) {\n          if (k.startsWith('config/') && !this.providerDb.hasOwnProperty(k)) {\n            // go ahead and register a provider that retrieves the configuration\n            const configProvider = passThroughProvider(() => this.getConfig(k.replace(/^config\\//, '')));\n            this.providerDb[k] = () => Promise.resolve(configProvider);\n          }\n          this.allToolKeys.add(k);\n        } else {\n          throw new Error(`Provider ${key} requested invalid tool key ${k} - must start with \"driver/\", \"alias/\", or \"config/\"`);\n        }\n      },\n    );\n    this.providerDb[key] = once(\n      () => this.fillToolBox(toolKeys).then(\n        (toolBox) => lambda(toolBox),\n      ),\n    );\n  }\n}\n\nexport default AppContext;\n"]}