import * as Redux from 'redux' import { ThrottledStore } from './throttledStore' import { INTERNAL_DONT_USE_SHELL_GET_APP_HOST } from './__internal' import { CustomCreateExtensionSlot } from './extensionSlot' export interface AnySlotKey { readonly name: string readonly public?: boolean // TODO: Move to new interface - APIKey } /** * A key that represents an {ExtensionSlot} of shape T that's held in the {AppHost} * Created be calling {Shell.declareSlot} * Retrieved by calling {Shell.getSlot} (scoped to specific {Shell}) * * @export * @interface SlotKey * @extends {AnySlotKey} * @template T */ export interface SlotKey extends AnySlotKey { /** * Holds no value, only triggers type-checking of T */ readonly empty?: T /** * Application layer/layers that will restrict usage of APIs contributed by this entry point. * Layers hierarchy is defined in the host options * @See {AppHostOptions.layers} */ readonly layer?: string | string[] // TODO: Move to new interface - APIKey /** * Version of the API that will be part of the API key unique identification */ readonly version?: number // TODO: Move to new interface - APIKey } export { AppHostAPI } from './appHostServices' export type ScopedStore = Pick< ThrottledStore, 'dispatch' | 'getState' | 'subscribe' | 'flush' | 'hasPendingSubscribers' | 'deferSubscriberNotifications' > export type ReactComponentContributor = (props?: TProps) => React.ReactNode export type ReducersMapObjectContributor = () => Redux.ReducersMapObject< TState, TAction > export type ContributionPredicate = () => boolean export interface EntryPointTags { [name: string]: string } export type ShellsChangedCallback = (shellNames: string[]) => void export type DeclarationsChangedCallback = () => void export type UnsubscribeFromDeclarationsChanged = () => void export type ShellBoundaryAspect = React.FunctionComponent> /** * Application part that will receive a {Shell} when loaded into the {AppHost} * @export * @interface EntryPoint */ export interface EntryPoint { /** * Unique name that will represent this entry point in the host */ readonly name: string readonly tags?: EntryPointTags /** * Application layer / layers that will restrict usage of APIs contributed by this entry point. * Layers hierarchy is defined in the host options * See {AppHostOptions.layers} */ readonly layer?: string | string[] /** * Define which API keys (a.k.a. contracts) are mandatory for this entry point to be executed * @return {SlotKey[]} API keys to wait for implementation */ getDependencyAPIs?(): SlotKey[] /** * Define which API keys (a.k.a. contracts) this entry point is going to implement and contribute * @return {SlotKey[]} API keys that will be contributed */ declareAPIs?(): SlotKey[] /** * Execute logic that is independent from other entry points * Most commonly - contribute APIs and state * @param {Shell} shell */ attach?(shell: Shell): void /** * Execute logic that is dependent on other entry points * @param {Shell} shell */ extend?(shell: Shell): void /** * Clean side effects * @param {Shell} shell */ detach?(shell: Shell): void } export type AnyEntryPoint = EntryPoint export type EntryPointOrPackage = AnyEntryPoint | AnyEntryPoint[] export interface EntryPointOrPackagesMap { [name: string]: EntryPointOrPackage } export type ExtensionItemFilter = (extensionItem: ExtensionItem) => boolean /** * A slot/container for holding any contribution of shape T * Access to the slot is scoped to the {Shell} * * @export * @interface ExtensionSlot * @template T */ export interface ExtensionSlot { /** * a unique identifier for the slot */ readonly name: string readonly host: AppHost /** * Which {Shell} owns this slot */ readonly declaringShell?: Shell /** * Add an item to the slot * * @param {Shell} shell Who owns the contributed item * @param {T} item Extension item to be added to the slot * @param {ContributionPredicate} [condition] A predicate to condition the retrieval of the item when slot items are requested with {ExtensionSlot.getItems} */ contribute(shell: Shell, item: T, condition?: ContributionPredicate): void /** * Get all items contributed to the slot * * @param {boolean} [forceAll] Ignore items' contribution predicates and get all anyway * @return {ExtensionItem[]} All items contributed to the slot */ getItems(forceAll?: boolean): ExtensionItem[] /** * Get the first item in the slot * * @return {ExtensionItem} The first item in the slot */ getSingleItem(): ExtensionItem | undefined /** * Get a specific item in the slot * * @param {string} name Extension item name * @return {ExtensionItem | undefined} Extension item */ getItemByName(name: string): ExtensionItem | undefined /** * Remove items from the slot by predicate * * @param {ExtensionItemFilter | undefined} predicate Remove all items matching this predicate */ discardBy(predicate: ExtensionItemFilter): void } export interface PrivateExtensionSlot extends ExtensionSlot { subscribe(callback: () => void): () => void } export interface CustomExtensionSlotHandler { contribute(fromShell: Shell, item: T, condition?: ContributionPredicate): void discardBy(predicate: ExtensionItemFilter): void } export interface CustomExtensionSlot extends CustomExtensionSlotHandler { readonly name: string readonly host: AppHost readonly declaringShell?: Shell } /** * Item of shape T that is contributed to a slot of shape T * * @export * @interface ExtensionItem * @template T */ export interface ExtensionItem { readonly name?: string /** * Which {Shell} owns this item */ readonly shell: Shell /** * Contribution content */ readonly contribution: T /** * Condition for the retrieval of this item by {ExtensionSlot.getItems} */ readonly condition: ContributionPredicate readonly uniqueId: string } /** * An application content container that will accept {EntryPoint} and provide registry for contracts * * @export * @interface AppHost */ export interface AppHost { /** * Get the root store of the application * * @return {ThrottledStore} */ getStore(): ThrottledStore /** * Get an implementation of API previously contributed to the {AppHost} * * @template TAPI * @param {SlotKey} key API Key * @return {*} {TAPI} */ getAPI(key: SlotKey): TAPI /** * Check if an API implementation has been contributed to the {AppHost} * * @template TAPI * @param {SlotKey} key API Key * @return {*} {boolean} */ hasAPI(key: SlotKey): boolean /** * Get an extension slot defined on the host * * @template TItem * @param {SlotKey} key * @return {ExtensionSlot} */ getSlot(key: SlotKey): ExtensionSlot /** * Check if an extension slot has been contributed to the {AppHost} * * @template TItem * @param {SlotKey} key * @return {boolean} */ hasSlot(key: SlotKey): boolean /** * Get all the extension slots defined on the host * * @return {*} {AnySlotKey[]} */ getAllSlotKeys(): AnySlotKey[] /** * Get all {EntryPoint}s addded to the {AppHost} * * @return {EntryPointsInfo[]} */ getAllEntryPoints(): EntryPointsInfo[] /** * Does the {AppHost} contain a specific {Shell} * * @param {string} name * @return {boolean} */ hasShell(name: string): boolean /** * Dynamically add {Shell}s after the host is created * * @param {EntryPointOrPackage[]} entryPointsOrPackages New packages or entry points to be added to the {AppHost} * @return {Promise} */ addShells(entryPointsOrPackages: EntryPointOrPackage[]): Promise /** * Dynamically remove {Shell}s after the host is created * * @param {string[]} names {Shell} names to be removed * @return {Promise} */ removeShells(names: string[]): Promise /** * Subscribe to changes in host declarations. This includes: * - API additions and removals * - Extension slot additions and removals * * @param {DeclarationsChangedCallback} callback Function to be called when host declarations change * @return {UnsubscribeFromDeclarationsChanged} Function to unsubscribe from host declarations changes */ onDeclarationsChanged(callback: DeclarationsChangedCallback): UnsubscribeFromDeclarationsChanged onShellsChanged(callback: ShellsChangedCallback): string removeShellsChangedCallback(callbackId: string): void /** * Verify if there are pending entry points waiting for APIs that will never be available. * This usually happens when trying to consume a private API as a public API. * * @throws {Error} If there are pending entry points with API mismatches */ verifyPendingEntryPointsAPIsMismatch(): void readonly log: HostLogger readonly options: AppHostOptions } export interface PrivateAppHost extends AppHost { executeWhenFree(identifier: string, callback: () => void): void } export interface MonitoringOptions { enablePerformance?: boolean readonly disableMonitoring?: boolean readonly disableMemoization?: boolean readonly debugMemoization?: boolean } export interface Trace { name: string duration: number startTime: number res: any args: any[] } export interface APILayer { level: number name: string } interface AppHostPlugins { extensionSlot?: { customCreateExtensionSlot: CustomCreateExtensionSlot } } export type {CustomCreateExtensionSlot} export interface AppHostOptions { readonly logger?: HostLogger readonly monitoring: MonitoringOptions readonly layers?: APILayer[] | APILayer[][] readonly disableLayersValidation?: boolean readonly disableCheckCircularDependencies?: boolean readonly enableStickyErrorBoundaries?: boolean readonly enableReduxDevtoolsExtension?: boolean readonly experimentalCyclicMode?: boolean readonly shouldScopeReducers?: boolean readonly plugins?: AppHostPlugins } export interface MemoizeMissHit { miss: number calls: number hit: number printHitMiss(): void } export type enrichedMemoizationFunction = MemoizeMissHit & AnyFunction & _.MemoizedFunction export interface StatisticsMemoization { func: enrichedMemoizationFunction name: string } export interface ContributeAPIOptions { includesNamespaces?: boolean disableMonitoring?: boolean | (keyof TAPI)[] } export type StateObserverUnsubscribe = () => void export type StateObserver = (next: TSelectorAPI) => void export interface ObservableState { subscribe(fromShell: Shell, callback: StateObserver): StateObserverUnsubscribe current(allowUnsafeReading?: boolean): TSelectorAPI } export type AnyFunction = (...args: any[]) => any export type FunctionWithSameArgs = (...args: Parameters) => any export interface Lazy { get(): T } /** * An scoped communication terminal provided for an {EntryPoint} * in order to contribute its application content to the {AppHost} * * @export * @interface Shell * @extends {(Pick>)} */ export interface Shell extends Pick> { /** * Unique name of the matching {EntryPoint} */ readonly name: string readonly log: ShellLogger /** * Get store that is scoped for this {Shell} * * @template TState * @return {ScopedStore} Scoped store for this {Shell} */ getStore(): ScopedStore /** * Are APIs ready to be requested * * @return {*} {boolean} */ canUseAPIs(): boolean /** * Is store ready to be requested * * @return {*} {boolean} */ canUseStore(): boolean /** * Did the execution of {EntryPoint}s' lifecycle phases (attach, detach) are done * * @return {*} {boolean} */ wasInitializationCompleted(): boolean runLateInitializer(initializer: () => T): T /** * Create an {ExtensionSlot} * * @template TItem * @param {SlotKey} key Key that will represent the slot (an will be used for retrieval) * @return {ExtensionSlot} Actual slot */ declareSlot(key: SlotKey): ExtensionSlot declareCustomSlot(key: SlotKey, handler: CustomExtensionSlotHandler): CustomExtensionSlot // TODO: Fix contributeAPI factory type not to resort to lowest common /** * Contribute an implementation of an API (a.k.a contract) * * @template TAPI * @param {SlotKey} key API Key that represents an interface TAPI * @param {() => TAPI} factory Create an implementation of TAPI * @param {ContributeAPIOptions} [options] Contribution options {ContributeAPIOptions} * @return {TAPI} Result of the factory execution */ contributeAPI(key: SlotKey, factory: () => TAPI, options?: ContributeAPIOptions): TAPI /** * Contribute a Redux reducer that will be added to the host store. * Use it for slowly changing state (e.g. not changing because of mouse movement) * * @template TState * @param {ReducersMapObjectContributor} contributor */ contributeState( contributor: ReducersMapObjectContributor ): void /** * Contribute a Redux reducer that will be added to the host store * Use it for rapidly changing state (e.g. changing on every mouse movement event) * Changes to this state won't trigger the usual subscribers. * In order to subscribe to changes in this state, use the observer object returned by this function. * * @template TState * @param {ReducersMapObjectContributor} contributor * @return {TAPI} Observer object for subscribing to state changes. The observer can also be passed to {connectWithShell}. */ contributeObservableState( contributor: ReducersMapObjectContributor, selectorFactory: (state: TState) => TSelector ): ObservableState /** * Contribute the main view (root) of the application * Intended to be used by a single {Shell} in an application * * @param {Shell} fromShell Who owns the main view * @param {ReactComponentContributor} contributor Create the main view component */ contributeMainView(fromShell: Shell, contributor: ReactComponentContributor): void contributeBoundaryAspect(component: ShellBoundaryAspect): void /** * Create a function with internal cache until any state change in the {AppHost} store * * @template T * @param {T} func Function to build cache for * @param {FunctionWithSameArgs} resolver Key creator to index results by * @param {() => boolean} [shouldClear] Custom clear condition (if not provided, behaves like () => true) * @return {*} {(((...args: Parameters) => ReturnType) & Partial<_.MemoizedFunction> & Partial)} */ memoizeForState( func: T, resolver: FunctionWithSameArgs, shouldClear?: () => boolean ): ((...args: Parameters) => ReturnType) & Partial<_.MemoizedFunction> & Partial /** * Manually trigger clear condition for function memoized with {Shell.memoizeForState} */ flushMemoizedForState(): void /** * Create a function with internal cache * * @template T * @param {T} func Function to build cache for * @param {FunctionWithSameArgs} resolver Key creator to index results by * @return {*} {(((...args: Parameters) => ReturnType) & Partial<_.MemoizedFunction> & Partial)} */ memoize( func: T, resolver: FunctionWithSameArgs ): ((...args: Parameters) => ReturnType) & Partial<_.MemoizedFunction> & Partial /** * Clear cache of a memoized function * * @param {(Partial<_.MemoizedFunction> & Partial)} memoizedFunction */ clearCache(memoizedFunction: Partial<_.MemoizedFunction> & Partial): void /** * Creates a lazy-evaluated value. The function `func` is only executed once when `get` is called for the first time, * and the result is cached for subsequent calls. * * @template T * @param {F} func - The function that will be lazily evaluated. It should not take any arguments. * @returns {Lazy} An object with a `get` method that returns the lazily evaluated value of type `T`. */ lazyEvaluator>(func: F): Lazy } export interface PrivateShell extends Shell { readonly entryPoint: EntryPoint setDependencyAPIs(APIs: AnySlotKey[]): void setLifecycleState(enableStore: boolean, enableAPIs: boolean, initCompleted: boolean): void getBoundaryAspects(): ShellBoundaryAspect[] getHostOptions(): AppHostOptions [INTERNAL_DONT_USE_SHELL_GET_APP_HOST](): AppHost } export interface EntryPointsInfo { readonly name: string readonly lazy: boolean readonly attached: boolean } export interface EntryPointInterceptor { interceptName?(innerName: string): string interceptTags?(innerTags?: EntryPointTags): EntryPointTags interceptGetDependencyAPIs?(innerGetDependencyAPIs?: EntryPoint['getDependencyAPIs']): EntryPoint['getDependencyAPIs'] interceptDeclareAPIs?(innerDeclareAPIs?: EntryPoint['declareAPIs']): EntryPoint['declareAPIs'] interceptAttach?(innerAttach?: EntryPoint['attach']): EntryPoint['attach'] interceptDetach?(innerDetach?: EntryPoint['detach']): EntryPoint['detach'] interceptExtend?(innerExtend?: EntryPoint['extend']): EntryPoint['extend'] } export type LogSeverity = 'verbose' | 'debug' | 'info' | 'event' | 'warning' | 'error' | 'critical' export type LogSpanFlag = 'begin' | 'end' //TODO:deprecated-kept-for-backward-compat export interface HostLogger { log(severity: LogSeverity, id: string, error?: Error, keyValuePairs?: Object): void spanChild(messageId: string, keyValuePairs?: Object): ShellLoggerSpan spanRoot(messageId: string, keyValuePairs?: Object): ShellLoggerSpan } export interface ShellLogger extends HostLogger { verbose(messageId: string, keyValuePairs?: Object): void debug(messageId: string, keyValuePairs?: Object): void info(messageId: string, keyValuePairs?: Object): void warning(messageId: string, keyValuePairs?: Object): void error(messageId: string, error?: Error, keyValuePairs?: Object): void critical(messageId: string, error?: Error, keyValuePairs?: Object): void spanChild(messageId: string, keyValuePairs?: Object): ShellLoggerSpan spanRoot(messageId: string, keyValuePairs?: Object): ShellLoggerSpan monitor(messageId: string, keyValuePairs: Object, monitoredCode: () => T): T } export interface ShellLoggerSpan { end(success: boolean, error?: Error, keyValuePairs?: Object): void }