diff --git a/packages/repluggable-core/src/API.ts b/packages/repluggable-core/src/API.ts index 7113f197..19c88323 100644 --- a/packages/repluggable-core/src/API.ts +++ b/packages/repluggable-core/src/API.ts @@ -1,7 +1,7 @@ import * as Redux from 'redux' -import { ThrottledStore } from './throttledStore' import { INTERNAL_DONT_USE_SHELL_GET_APP_HOST } from './__internal' import { CustomCreateExtensionSlot } from './extensionSlot' +import { ThrottledStore } from './throttledStore' export interface AnySlotKey { readonly name: string @@ -77,6 +77,11 @@ export interface EntryPoint { * @return {SlotKey[]} API keys to wait for implementation */ getDependencyAPIs?(): SlotKey[] + /** + * Define which API keys (a.k.a. contracts) are required for implementation but optional for this entry point to be executed + * @return {SlotKey[]} API keys that may be used but don't block loading + */ + getColdDependencyAPIs?(): 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 @@ -331,7 +336,7 @@ interface AppHostPlugins { } } -export type {CustomCreateExtensionSlot} +export type { CustomCreateExtensionSlot } export interface AppHostOptions { readonly logger?: HostLogger @@ -406,6 +411,15 @@ export interface Shell extends Pick} key API Key for the cold dependency + * @return {Lazy} Lazy wrapper that resolves the API on access + */ + getColdAPI(key: SlotKey): Lazy /** * Is store ready to be requested * @@ -525,6 +539,7 @@ export interface Shell extends Pick(v: T[] | T[][]): v is T[][] { return _.every(v, _.isArray) @@ -427,11 +427,49 @@ miss: ${memoizedWithMissHit.miss} } } + function buildApiToEntryPointMap(entryPoints: EntryPoint[]): Map { + const map = new Map() + for (const ep of entryPoints) { + for (const api of declaredAPIs(ep)) { + map.set(slotKeyToName(api), ep) + } + } + return map + } + + function getEffectiveBlockingDeps( + entryPoint: EntryPoint, + apiToEntryPoint: Map, + visited = new Set() + ): AnySlotKey[] { + if (visited.has(entryPoint.name)) { + return [] + } + visited.add(entryPoint.name) + + const regularDeps = dependentAPIs(entryPoint) + const result = [...regularDeps] + + for (const dep of regularDeps) { + const provider = apiToEntryPoint.get(slotKeyToName(dep)) + if (provider) { + result.push(...coldDependentAPIs(provider)) + result.push(...getEffectiveBlockingDeps(provider, apiToEntryPoint, visited)) + } + } + return result + } + function executeInstallShell(entryPoints: EntryPoint[]): void { + const allEntryPoints = [...addedShells.values()] + .map(s => s.entryPoint) + .concat(unReadyEntryPointsStore.get(), entryPoints) + const apiToEntryPoint = buildApiToEntryPointMap(allEntryPoints) + const [readyEntryPoints, currentUnReadyEntryPoints] = _.partition(entryPoints, entryPoint => { - const dependencies = entryPoint.getDependencyAPIs && entryPoint.getDependencyAPIs() + const effectiveDeps = getEffectiveBlockingDeps(entryPoint, apiToEntryPoint) return _.every( - dependencies, + effectiveDeps, k => readyAPIs.has(getOwnSlotKey(k)) || (options.experimentalCyclicMode && isAllAPIDependenciesAreReadyOrPending(k, entryPoints)) @@ -455,8 +493,11 @@ miss: ${memoizedWithMissHit.miss} invokeEntryPointPhase( 'getDependencyAPIs', shells, - f => f.entryPoint.getDependencyAPIs && f.setDependencyAPIs(f.entryPoint.getDependencyAPIs()), - f => !!f.entryPoint.getDependencyAPIs + f => { + f.entryPoint.getDependencyAPIs && f.setDependencyAPIs(f.entryPoint.getDependencyAPIs()) + f.entryPoint.getColdDependencyAPIs && f.setColdDependencyAPIs(f.entryPoint.getColdDependencyAPIs()) + }, + f => !!f.entryPoint.getDependencyAPIs || !!f.entryPoint.getColdDependencyAPIs ) invokeEntryPointPhase( @@ -932,6 +973,7 @@ miss: ${memoizedWithMissHit.miss} let APIsEnabled = false let wasInitCompleted = false let dependencyAPIs: Set = new Set() + let coldDependencyAPIs: Set = new Set() let nextObservableId = 1 const boundaryAspects: ShellBoundaryAspect[] = [] @@ -991,6 +1033,10 @@ miss: ${memoizedWithMissHit.miss} dependencyAPIs = new Set(APIs) }, + setColdDependencyAPIs(APIs: AnySlotKey[]): void { + coldDependencyAPIs = new Set(APIs) + }, + canUseAPIs(): boolean { return APIsEnabled }, @@ -1037,6 +1083,12 @@ miss: ${memoizedWithMissHit.miss} if (dependencyAPIs.has(key) || isOwnContributedAPI(key)) { return host.getAPI(key) } + if (coldDependencyAPIs.has(key)) { + if (!wasInitCompleted) { + throw new Error(`API '${slotKeyToName(key)}' is a cold dependency and cannot be accessed during initialization`) + } + return host.getAPI(key) + } throw new Error( `API '${slotKeyToName(key)}' is not declared as dependency by entry point '${ entryPoint.name @@ -1048,6 +1100,10 @@ miss: ${memoizedWithMissHit.miss} return (dependencyAPIs.has(key) || isOwnContributedAPI(key)) && host.hasAPI(key) }, + getColdAPI(key: SlotKey): Lazy { + return shell.lazyEvaluator(() => shell.getAPI(key)) + }, + contributeAPI(key: SlotKey, factory: () => TAPI, apiOptions?: ContributeAPIOptions): TAPI { host.log.log('verbose', `Contributing API ${slotKeyToName(key)}.`) diff --git a/packages/repluggable-core/src/appHostUtils.ts b/packages/repluggable-core/src/appHostUtils.ts index 44d70e6a..cb93918b 100644 --- a/packages/repluggable-core/src/appHostUtils.ts +++ b/packages/repluggable-core/src/appHostUtils.ts @@ -5,6 +5,9 @@ import _ from 'lodash' export const dependentAPIs = (entryPoint: AnyEntryPoint): AnySlotKey[] => { return _.chain(entryPoint).invoke('getDependencyAPIs').defaultTo([]).value() } +export const coldDependentAPIs = (entryPoint: AnyEntryPoint): AnySlotKey[] => { + return _.chain(entryPoint).invoke('getColdDependencyAPIs').defaultTo([]).value() +} export const declaredAPIs = (entryPoint: AnyEntryPoint): AnySlotKey[] => { return _.chain(entryPoint).invoke('declareAPIs').defaultTo([]).value() diff --git a/packages/repluggable-core/test/appHost.spec.ts b/packages/repluggable-core/test/appHost.spec.ts index 6de0bfa9..ba3e7dad 100644 --- a/packages/repluggable-core/test/appHost.spec.ts +++ b/packages/repluggable-core/test/appHost.spec.ts @@ -31,6 +31,7 @@ import { import { AppHostAPI, AppHostServicesEntryPointName, AppHostServicesProvider } from '../src/appHostServices' import { ConsoleHostLogger } from '../src/loggers' +import { mockPackageWithColdDependency } from '../testKit/mockPackage' import { createCircularEntryPoints, createDirectCircularEntryPoints } from './appHost.mock' import { createSignalItemsDataStructure } from './createSignalItemsDataStructure' @@ -1875,4 +1876,509 @@ If the API is intended to be public, it should be declared as "public: true" in expect(itemsSpy).toBeCalledTimes(4) }) }) + describe('Cold Dependencies', () => { + it('should allow entry point to load without waiting for cold dependencies', async () => { + const host = createAppHost([], testHostOptions) + host.addShells([mockPackageWithColdDependency]) + expect(host.hasShell(mockPackageWithColdDependency.name)).toBe(true) + }) + it('should return false for hasAPI when cold dependency is not ready', async () => { + const host = createAppHost([], testHostOptions) + host.addShells([mockPackageWithColdDependency]) + expect(host.hasAPI(MockAPI)).toBe(false) + }) + it('should throw when accessing cold dependency that is not ready', async () => { + const host = createAppHost([], testHostOptions) + host.addShells([mockPackageWithColdDependency]) + expect(() => host.getAPI(MockAPI)).toThrow() + }) + it('should throw when accessing cold dependency during entry point attach', async () => { + const coldDependencyEntryPoint: EntryPoint = { + name: 'COLD_DEPENDENCY_ENTRY_POINT', + getColdDependencyAPIs() { + return [MockAPI] + }, + declareAPIs() { + return [MockPublicAPI] + }, + attach(shell: Shell) { + shell.contributeAPI(MockPublicAPI, () => { + const mockAPI = shell.getAPI(MockAPI) + return { + stubTrue: () => mockAPI.stubTrue() + } + }) + } + } + const host = createAppHost([], testHostOptions) + expect(() => host.addShells([coldDependencyEntryPoint])).toThrow() + }) + it('should throw when accessing cold dependency during entry point extend', async () => { + const coldDependencyEntryPoint: EntryPoint = { + name: 'COLD_DEPENDENCY_ENTRY_POINT', + getColdDependencyAPIs() { + return [MockAPI] + }, + extend(shell: Shell) { + shell.getAPI(MockAPI).stubTrue() + } + } + const host = createAppHost([], testHostOptions) + expect(() => host.addShells([coldDependencyEntryPoint])).toThrow() + }) + it('should allow accessing cold dependency via getAPI after initialization completes', async () => { + const host = createAppHost([mockPackage], testHostOptions) + const shell = addMockShell(host, { + getColdDependencyAPIs: () => [MockAPI] + }) + + expect(() => shell.getAPI(MockAPI)).not.toThrow() + expect(shell.getAPI(MockAPI).stubTrue()).toBe(true) + }) + describe('getColdAPI', () => { + it('should allow using getColdAPI inside contributeAPI factory', async () => { + // Problem: You can't call getAPI(coldDep) inside contributeAPI factory because init hasn't completed + // Solution: getColdAPI returns a lazy wrapper that defers resolution until the method is actually called + const host = createAppHost([mockPackage], testHostOptions) + + addMockShell(host, { + getColdDependencyAPIs: () => [MockAPI], + declareAPIs: () => [MockPublicAPI], + attach(shell) { + const mockAPI = shell.getColdAPI(MockAPI) + + shell.contributeAPI(MockPublicAPI, () => ({ + stubTrue: () => mockAPI.get().stubTrue() + })) + } + }) + + expect(host.getAPI(MockPublicAPI).stubTrue()).toBe(true) + }) + it('should resolve cold dependency when get() is called after initialization', async () => { + const host = createAppHost([mockPackage], testHostOptions) + const shell = addMockShell(host, { + getColdDependencyAPIs: () => [MockAPI] + }) + + const lazyAPI = shell.getColdAPI(MockAPI) + expect(lazyAPI.get().stubTrue()).toBe(true) + }) + it('should throw when get() is called and cold dependency is not ready', async () => { + const host = createAppHost([], testHostOptions) + const shell = addMockShell(host, { + getColdDependencyAPIs: () => [MockAPI] + }) + + const lazyAPI = shell.getColdAPI(MockAPI) + expect(() => lazyAPI.get()).toThrow() + }) + it('should cache the API after first get() call', async () => { + const host = createAppHost([mockPackage], testHostOptions) + const shell = addMockShell(host, { + getColdDependencyAPIs: () => [MockAPI] + }) + + const lazyAPI = shell.getColdAPI(MockAPI) + const first = lazyAPI.get() + const second = lazyAPI.get() + expect(first).toBe(second) + }) + }) + describe('Cyclic Cold Dependencies', () => { + it('should load entry point with cyclic cold dependencies', async () => { + /** + * (A)───cold───>(B) + * ^ │ + * │ │ + * └────cold─────┘ + * + * A declares APIA, has cold dep on APIB + * B declares APIB, has cold dep on APIA + */ + const APIA: SlotKey<{}> = { name: 'API_A' } + const APIB: SlotKey<{}> = { name: 'API_B' } + const declaringAWithColdDepOnB: EntryPoint = { + name: 'ENTRY_POINT_A', + getColdDependencyAPIs() { + return [APIB] + }, + declareAPIs() { + return [APIA] + }, + attach(shell: Shell) { + shell.contributeAPI(APIA, () => ({ + stubTrue: () => true + })) + } + } + const declaringBWithColdDepOnA: EntryPoint = { + name: 'ENTRY_POINT_B', + getColdDependencyAPIs() { + return [APIA] + }, + declareAPIs() { + return [APIB] + }, + attach(shell: Shell) { + shell.contributeAPI(APIB, () => ({ + stubTrue: () => true + })) + } + } + const host = createAppHost([declaringAWithColdDepOnB, declaringBWithColdDepOnA], testHostOptions) + expect(host.hasShell(declaringAWithColdDepOnB.name)).toBe(true) + expect(host.hasShell(declaringBWithColdDepOnA.name)).toBe(true) + }) + it('should load entry points with cyclic cold and regular dependencies', async () => { + /** + * (A)───cold───>(B)───cold───>(C) + * ^ │ + * │ │ + * └─────────regular────────────┘ + * + * A declares APIA, has cold dep on APIB + * B declares APIB, has cold dep on APIC + * C declares APIC, has regular dep on APIA + */ + const APIA: SlotKey<{}> = { name: 'API_A' } + const APIB: SlotKey<{}> = { name: 'API_B' } + const APIC: SlotKey<{}> = { name: 'API_C' } + const declaringA: EntryPoint = { + name: 'ENTRY_POINT_A', + getColdDependencyAPIs() { + return [APIB] + }, + declareAPIs() { + return [APIA] + }, + attach(shell: Shell) { + shell.contributeAPI(APIA, () => ({ + stubTrue: () => true + })) + } + } + const declaringB: EntryPoint = { + name: 'ENTRY_POINT_B', + getColdDependencyAPIs() { + return [APIC] + }, + declareAPIs() { + return [APIB] + }, + attach(shell: Shell) { + shell.contributeAPI(APIB, () => ({ + stubTrue: () => true + })) + } + } + const declaringC: EntryPoint = { + name: 'ENTRY_POINT_C', + getDependencyAPIs() { + return [APIA] + }, + declareAPIs() { + return [APIC] + }, + attach(shell: Shell) { + shell.contributeAPI(APIC, () => ({ + stubTrue: () => true + })) + } + } + const host = createAppHost([declaringA, declaringB, declaringC], testHostOptions) + expect(host.hasShell(declaringA.name)).toBe(true) + expect(host.hasShell(declaringB.name)).toBe(true) + expect(host.hasShell(declaringC.name)).toBe(true) + }) + }) + describe('Cold dependency promotion', () => { + it('should promote transitive cold dependencies to regular dependencies when there is a real direct dependency', async () => { + const APIA: SlotKey<{}> = { name: 'API_A' } + const APIB: SlotKey<{}> = { name: 'API_B' } + const APIC: SlotKey<{}> = { name: 'API_C' } + const declaringA: EntryPoint = { + name: 'ENTRY_POINT_A', + declareAPIs() { + return [APIA] + }, + attach(shell: Shell) { + shell.contributeAPI(APIA, () => ({ + stubTrue: () => true + })) + } + } + const declaringBWithColdDepOnA: EntryPoint = { + name: 'ENTRY_POINT_B', + getColdDependencyAPIs() { + return [APIA] + }, + declareAPIs() { + return [APIB] + }, + attach(shell: Shell) { + shell.contributeAPI(APIB, () => ({ + stubTrue: () => true + })) + } + } + const declaringCWithRegularDepOnB: EntryPoint = { + name: 'ENTRY_POINT_C', + getDependencyAPIs() { + return [APIB] + }, + declareAPIs() { + return [APIC] + }, + attach(shell: Shell) { + shell.contributeAPI(APIC, () => ({ + stubTrue: () => true + })) + } + } + // no APIA in the host + const host = createAppHost([declaringBWithColdDepOnA, declaringCWithRegularDepOnB], testHostOptions) + expect(host.hasShell(declaringBWithColdDepOnA.name)).toBe(true) + expect(host.hasShell(declaringCWithRegularDepOnB.name)).toBe(false) + await host.addShells([declaringA]) + expect(host.hasShell(declaringCWithRegularDepOnB.name)).toBe(true) + }) + it('should promote cold dependencies from unready entry points added in previous batches', async () => { + const APIA: SlotKey<{}> = { name: 'API_A' } + const APIB: SlotKey<{}> = { name: 'API_B' } + const APIC: SlotKey<{}> = { name: 'API_C' } + const APIX: SlotKey<{}> = { name: 'API_X' } + + // B declares APIB, depends on APIX (keeps B unready), has cold dep on APIA + const declaringB: EntryPoint = { + name: 'ENTRY_POINT_B', + getDependencyAPIs() { + return [APIX] + }, + getColdDependencyAPIs() { + return [APIA] + }, + declareAPIs() { + return [APIB] + }, + attach(shell: Shell) { + shell.contributeAPI(APIB, () => ({ stubTrue: () => true })) + } + } + + // C depends on APIB (from unready B) + const declaringC: EntryPoint = { + name: 'ENTRY_POINT_C', + getDependencyAPIs() { + return [APIB] + }, + declareAPIs() { + return [APIC] + }, + attach(shell: Shell) { + shell.contributeAPI(APIC, () => ({ stubTrue: () => true })) + } + } + + const declaringA: EntryPoint = { + name: 'ENTRY_POINT_A', + declareAPIs() { + return [APIA] + }, + attach(shell: Shell) { + shell.contributeAPI(APIA, () => ({ stubTrue: () => true })) + } + } + + const declaringX: EntryPoint = { + name: 'ENTRY_POINT_X', + declareAPIs() { + return [APIX] + }, + attach(shell: Shell) { + shell.contributeAPI(APIX, () => ({ stubTrue: () => true })) + } + } + + const host = createAppHost([], testHostOptions) + + // Batch 1: Add B (stays unready - needs APIX) + await host.addShells([declaringB]) + expect(host.hasShell(declaringB.name)).toBe(false) + + // Batch 2: Add C (depends on APIB from unready B) + // Even though B is unready, C's effective deps should include B's cold dep on APIA + await host.addShells([declaringC]) + expect(host.hasShell(declaringC.name)).toBe(false) + + // Add X only (provides APIX for B) + // B can now load and contribute APIB + // But C should still wait because B's cold dep (APIA) is promoted to C + await host.addShells([declaringX]) + expect(host.hasShell(declaringB.name)).toBe(true) + expect(host.hasShell(declaringC.name)).toBe(false) + + // Add A (provides APIA) + await host.addShells([declaringA]) + expect(host.hasShell(declaringC.name)).toBe(true) + }) + it('should promote cold dependencies through deep regular dependency chains', async () => { + /** + * (A)───regular───>(B)───regular───>(C)───regular───>(D) + * │ + * cold + * │ + * v + * (E) + * + * A depends on B, B depends on C, C depends on D, D has cold dep on E + * A should wait for E (cold dep promoted through entire chain) + */ + const APIA: SlotKey<{}> = { name: 'API_A' } + const APIB: SlotKey<{}> = { name: 'API_B' } + const APIC: SlotKey<{}> = { name: 'API_C' } + const APID: SlotKey<{}> = { name: 'API_D' } + const APIE: SlotKey<{}> = { name: 'API_E' } + + const declaringE: EntryPoint = { + name: 'ENTRY_POINT_E', + declareAPIs() { + return [APIE] + }, + attach(shell: Shell) { + shell.contributeAPI(APIE, () => ({ stubTrue: () => true })) + } + } + + const declaringD: EntryPoint = { + name: 'ENTRY_POINT_D', + getColdDependencyAPIs() { + return [APIE] + }, + declareAPIs() { + return [APID] + }, + attach(shell: Shell) { + shell.contributeAPI(APID, () => ({ stubTrue: () => true })) + } + } + + const declaringC: EntryPoint = { + name: 'ENTRY_POINT_C', + getDependencyAPIs() { + return [APID] + }, + declareAPIs() { + return [APIC] + }, + attach(shell: Shell) { + shell.contributeAPI(APIC, () => ({ stubTrue: () => true })) + } + } + + const declaringB: EntryPoint = { + name: 'ENTRY_POINT_B', + getDependencyAPIs() { + return [APIC] + }, + declareAPIs() { + return [APIB] + }, + attach(shell: Shell) { + shell.contributeAPI(APIB, () => ({ stubTrue: () => true })) + } + } + + const declaringA: EntryPoint = { + name: 'ENTRY_POINT_A', + getDependencyAPIs() { + return [APIB] + }, + declareAPIs() { + return [APIA] + }, + attach(shell: Shell) { + shell.contributeAPI(APIA, () => ({ stubTrue: () => true })) + } + } + + // Without E, D loads (cold dep doesn't block), but A, B, C should wait + const host = createAppHost([declaringA, declaringB, declaringC, declaringD], testHostOptions) + expect(host.hasShell(declaringD.name)).toBe(true) + expect(host.hasShell(declaringC.name)).toBe(false) + expect(host.hasShell(declaringB.name)).toBe(false) + expect(host.hasShell(declaringA.name)).toBe(false) + + // Add E, now everyone can load + await host.addShells([declaringE]) + expect(host.hasShell(declaringC.name)).toBe(true) + expect(host.hasShell(declaringB.name)).toBe(true) + expect(host.hasShell(declaringA.name)).toBe(true) + }) + }) + it('should not promote transitive cold dependencies to regular dependencies when there is no real direct dependency', async () => { + const APIA: SlotKey<{}> = { name: 'API_A' } + const APIB: SlotKey<{}> = { name: 'API_B' } + const APIC: SlotKey<{}> = { name: 'API_C' } + const APID: SlotKey<{}> = { name: 'API_D' } + const declaringA: EntryPoint = { + name: 'ENTRY_POINT_A', + declareAPIs() { + return [APIA] + }, + attach(shell: Shell) { + shell.contributeAPI(APIA, () => ({ + stubTrue: () => true + })) + } + } + const declaringBWithColdDepOnA: EntryPoint = { + name: 'ENTRY_POINT_B', + getColdDependencyAPIs() { + return [APIA] + }, + declareAPIs() { + return [APIB] + }, + attach(shell: Shell) { + shell.contributeAPI(APIB, () => ({ + stubTrue: () => true + })) + } + } + const declaringCWithRegularDepOnB: EntryPoint = { + name: 'ENTRY_POINT_C', + getDependencyAPIs() { + return [APIB] + }, + declareAPIs() { + return [APIC] + }, + attach(shell: Shell) { + shell.contributeAPI(APIC, () => ({ + stubTrue: () => true + })) + } + } + const declaringDWithColdDepOnC: EntryPoint = { + name: 'ENTRY_POINT_D', + getColdDependencyAPIs() { + return [APIC] + }, + declareAPIs() { + return [APID] + }, + attach(shell: Shell) { + shell.contributeAPI(APID, () => ({ + stubTrue: () => true + })) + } + } + const host = createAppHost([declaringBWithColdDepOnA, declaringCWithRegularDepOnB, declaringDWithColdDepOnC], testHostOptions) + expect(host.hasShell(declaringDWithColdDepOnC.name)).toBe(true) + expect(host.hasShell(declaringCWithRegularDepOnB.name)).toBe(false) + await host.addShells([declaringA]) + expect(host.hasShell(declaringCWithRegularDepOnB.name)).toBe(true) + }) + }) }) diff --git a/packages/repluggable-core/testKit/mockPackage.ts b/packages/repluggable-core/testKit/mockPackage.ts index b58d544d..99e9d186 100644 --- a/packages/repluggable-core/testKit/mockPackage.ts +++ b/packages/repluggable-core/testKit/mockPackage.ts @@ -108,3 +108,18 @@ export const mockPackageWithSlot: EntryPoint = { shell.declareSlot(MockSlot) } } + +export const mockPackageWithColdDependency: EntryPoint = { + name: 'MOCK_PACKAGE_WITH_COLD_DEPENDENCY', + getColdDependencyAPIs() { + return [MockAPI] + }, + declareAPIs() { + return [MockPublicAPI] + }, + attach(shell: Shell) { + shell.contributeAPI(MockPublicAPI, () => ({ + stubTrue: () => true + })) + } +} diff --git a/packages/repluggable/src/interceptEntryPoints.ts b/packages/repluggable/src/interceptEntryPoints.ts index bdc6536c..0dadc7a7 100644 --- a/packages/repluggable/src/interceptEntryPoints.ts +++ b/packages/repluggable/src/interceptEntryPoints.ts @@ -17,6 +17,9 @@ function applyInterceptor(inner: EntryPoint, interceptor: EntryPointInterceptor) getDependencyAPIs: interceptor.interceptGetDependencyAPIs ? interceptor.interceptGetDependencyAPIs(inner.getDependencyAPIs) : inner.getDependencyAPIs, + getColdDependencyAPIs: interceptor.interceptGetColdDependencyAPIs + ? interceptor.interceptGetColdDependencyAPIs(inner.getColdDependencyAPIs) + : inner.getColdDependencyAPIs, declareAPIs: interceptor.interceptDeclareAPIs ? interceptor.interceptDeclareAPIs(inner.declareAPIs) : inner.declareAPIs, attach: interceptor.interceptAttach ? interceptor.interceptAttach(inner.attach) : inner.attach, detach: interceptor.interceptDetach ? interceptor.interceptDetach(inner.detach) : inner.detach, diff --git a/packages/repluggable/testKit/index.tsx b/packages/repluggable/testKit/index.tsx index df6f95d4..66afcb11 100644 --- a/packages/repluggable/testKit/index.tsx +++ b/packages/repluggable/testKit/index.tsx @@ -128,6 +128,9 @@ function createShell(host: AppHost): PrivateShell { getHostOptions: () => host.options, log: createShellLogger(host, entryPoint), lazyEvaluator: func => ({ get: func }), + getColdAPI(key) { + return { get: () => host.getAPI(key) } + }, [INTERNAL_DONT_USE_SHELL_GET_APP_HOST]: () => { return host }