repluggable-core 1.302.76 → 1.302.78
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.
- package/dist/debug/src/API.d.ts +8 -1
- package/dist/debug/src/extensionSlot.d.ts +13 -2
- package/dist/es/src/API.d.ts +8 -1
- package/dist/es/src/API.d.ts.map +1 -1
- package/dist/es/src/IterableWeakMap.d.ts.map +1 -1
- package/dist/es/src/IterableWeakMap.js.map +1 -1
- package/dist/es/src/appHost.d.ts.map +1 -1
- package/dist/es/src/appHost.js +11 -2
- package/dist/es/src/appHost.js.map +1 -1
- package/dist/es/src/extensionSlot.d.ts +13 -2
- package/dist/es/src/extensionSlot.d.ts.map +1 -1
- package/dist/es/src/extensionSlot.js +21 -9
- package/dist/es/src/extensionSlot.js.map +1 -1
- package/dist/es/src/index.d.ts.map +1 -1
- package/dist/es/src/index.js.map +1 -1
- package/dist/es/src/throttledStore.d.ts.map +1 -1
- package/dist/es/src/throttledStore.js.map +1 -1
- package/dist/es/test/appHost.spec.js +71 -0
- package/dist/es/test/appHost.spec.js.map +1 -1
- package/dist/es/test/createSignalItemsDataStructure.d.ts +16 -0
- package/dist/es/test/createSignalItemsDataStructure.d.ts.map +1 -0
- package/dist/es/test/createSignalItemsDataStructure.js +54 -0
- package/dist/es/test/createSignalItemsDataStructure.js.map +1 -0
- package/dist/es/test/extensionSlot.spec.d.ts +2 -0
- package/dist/es/test/extensionSlot.spec.d.ts.map +1 -0
- package/dist/es/test/extensionSlot.spec.js +136 -0
- package/dist/es/test/extensionSlot.spec.js.map +1 -0
- package/dist/es/testKit/index.d.ts +1 -0
- package/dist/es/testKit/index.d.ts.map +1 -1
- package/dist/es/testKit/index.js +1 -0
- package/dist/es/testKit/index.js.map +1 -1
- package/dist/src/API.d.ts +8 -1
- package/dist/src/API.d.ts.map +1 -1
- package/dist/src/IterableWeakMap.d.ts.map +1 -1
- package/dist/src/IterableWeakMap.js.map +1 -1
- package/dist/src/appHost.d.ts.map +1 -1
- package/dist/src/appHost.js +11 -2
- package/dist/src/appHost.js.map +1 -1
- package/dist/src/extensionSlot.d.ts +13 -2
- package/dist/src/extensionSlot.d.ts.map +1 -1
- package/dist/src/extensionSlot.js +21 -9
- package/dist/src/extensionSlot.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/throttledStore.d.ts.map +1 -1
- package/dist/src/throttledStore.js.map +1 -1
- package/dist/test/appHost.spec.js +71 -0
- package/dist/test/appHost.spec.js.map +1 -1
- package/dist/test/createSignalItemsDataStructure.d.ts +16 -0
- package/dist/test/createSignalItemsDataStructure.d.ts.map +1 -0
- package/dist/test/createSignalItemsDataStructure.js +58 -0
- package/dist/test/createSignalItemsDataStructure.js.map +1 -0
- package/dist/test/extensionSlot.spec.d.ts +2 -0
- package/dist/test/extensionSlot.spec.d.ts.map +1 -0
- package/dist/test/extensionSlot.spec.js +138 -0
- package/dist/test/extensionSlot.spec.js.map +1 -0
- package/dist/testKit/index.d.ts +1 -0
- package/dist/testKit/index.d.ts.map +1 -1
- package/dist/testKit/index.js +3 -1
- package/dist/testKit/index.js.map +1 -1
- package/package.json +3 -2
- package/src/API.ts +10 -4
- package/src/IterableWeakMap.ts +3 -5
- package/src/__internal.ts +1 -1
- package/src/appHost.ts +10 -2
- package/src/extensionSlot.ts +36 -10
- package/src/index.ts +2 -5
- package/src/throttledStore.ts +9 -15
- package/test/appHost.spec.ts +89 -0
- package/test/createSignalItemsDataStructure.ts +65 -0
- package/test/extensionSlot.spec.ts +176 -0
- package/testKit/index.ts +7 -7
package/src/API.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as Redux from 'redux'
|
|
2
2
|
import { ThrottledStore } from './throttledStore'
|
|
3
3
|
import { INTERNAL_DONT_USE_SHELL_GET_APP_HOST } from './__internal'
|
|
4
|
-
|
|
4
|
+
import { CustomCreateExtensionSlot } from './extensionSlot'
|
|
5
5
|
|
|
6
6
|
export interface AnySlotKey {
|
|
7
7
|
readonly name: string
|
|
@@ -333,6 +333,12 @@ export interface APILayer {
|
|
|
333
333
|
name: string
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
+
interface AppHostPlugins {
|
|
337
|
+
extensionSlot?: {
|
|
338
|
+
customCreateExtensionSlot: CustomCreateExtensionSlot
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
336
342
|
export interface AppHostOptions {
|
|
337
343
|
readonly logger?: HostLogger
|
|
338
344
|
readonly monitoring: MonitoringOptions
|
|
@@ -343,6 +349,7 @@ export interface AppHostOptions {
|
|
|
343
349
|
readonly enableReduxDevtoolsExtension?: boolean
|
|
344
350
|
readonly experimentalCyclicMode?: boolean
|
|
345
351
|
readonly shouldScopeReducers?: boolean
|
|
352
|
+
readonly plugins?: AppHostPlugins
|
|
346
353
|
}
|
|
347
354
|
|
|
348
355
|
export interface MemoizeMissHit {
|
|
@@ -526,9 +533,8 @@ export interface PrivateShell extends Shell {
|
|
|
526
533
|
setDependencyAPIs(APIs: AnySlotKey[]): void
|
|
527
534
|
setLifecycleState(enableStore: boolean, enableAPIs: boolean, initCompleted: boolean): void
|
|
528
535
|
getBoundaryAspects(): ShellBoundaryAspect[]
|
|
529
|
-
getHostOptions(): AppHostOptions
|
|
530
|
-
|
|
531
|
-
|
|
536
|
+
getHostOptions(): AppHostOptions
|
|
537
|
+
[INTERNAL_DONT_USE_SHELL_GET_APP_HOST](): AppHost
|
|
532
538
|
}
|
|
533
539
|
|
|
534
540
|
export interface EntryPointsInfo {
|
package/src/IterableWeakMap.ts
CHANGED
|
@@ -81,7 +81,7 @@ export class IterableWeakMap<K extends object = object, V = any> implements Map<
|
|
|
81
81
|
|
|
82
82
|
forEach(callbackfn: (value: V, key: K, map: this) => void, thisArg?: any): void {
|
|
83
83
|
for (const [key, value] of this) {
|
|
84
|
-
callbackfn(value, key, this
|
|
84
|
+
callbackfn(value, key, this)
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -107,17 +107,15 @@ export class IterableWeakMap<K extends object = object, V = any> implements Map<
|
|
|
107
107
|
return this[Symbol.iterator]() as unknown as ReturnType<Map<K, V>['entries']>
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
*keys(): ReturnType<Map<K, V>['keys']>
|
|
110
|
+
*keys(): ReturnType<Map<K, V>['keys']> {
|
|
111
111
|
for (const [key] of this) {
|
|
112
112
|
yield key
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
*values():
|
|
116
|
+
*values(): ReturnType<Map<K, V>['values']> {
|
|
117
117
|
for (const [_, value] of this) {
|
|
118
118
|
yield value
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
-
|
|
123
|
-
|
package/src/__internal.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* This symbol is only for internal use of repluggable mono-repo.
|
|
3
3
|
*/
|
|
4
|
-
export const INTERNAL_DONT_USE_SHELL_GET_APP_HOST: unique symbol = Symbol.for('SHELL_GET_APP_HOST')
|
|
4
|
+
export const INTERNAL_DONT_USE_SHELL_GET_APP_HOST: unique symbol = Symbol.for('SHELL_GET_APP_HOST')
|
package/src/appHost.ts
CHANGED
|
@@ -251,7 +251,10 @@ export function createAppHost(initialEntryPointsOrPackages: EntryPointOrPackage[
|
|
|
251
251
|
const memoized = _.memoize(func, resolver)
|
|
252
252
|
|
|
253
253
|
if (options.monitoring.debugMemoization) {
|
|
254
|
-
Object.defineProperty(memoized, 'name', {
|
|
254
|
+
Object.defineProperty(memoized, 'name', {
|
|
255
|
+
value: `${func.name}_memoized`,
|
|
256
|
+
writable: false
|
|
257
|
+
})
|
|
255
258
|
}
|
|
256
259
|
|
|
257
260
|
if (options.monitoring.disableMonitoring) {
|
|
@@ -560,7 +563,12 @@ miss: ${memoizedWithMissHit.miss}
|
|
|
560
563
|
}
|
|
561
564
|
|
|
562
565
|
function declareSlot<TItem>(key: SlotKey<TItem>, declaringShell?: Shell): ExtensionSlot<TItem> {
|
|
563
|
-
const newSlot = registerSlotOrThrow(key, () =>
|
|
566
|
+
const newSlot = registerSlotOrThrow(key, () =>
|
|
567
|
+
createExtensionSlot(key, host, {
|
|
568
|
+
declaringShell,
|
|
569
|
+
customCreateExtensionSlot: options.plugins?.extensionSlot?.customCreateExtensionSlot
|
|
570
|
+
})
|
|
571
|
+
)
|
|
564
572
|
return newSlot
|
|
565
573
|
}
|
|
566
574
|
|
package/src/extensionSlot.ts
CHANGED
|
@@ -21,18 +21,44 @@ const alwaysTrue = () => true
|
|
|
21
21
|
|
|
22
22
|
type Unsubscribe = () => void
|
|
23
23
|
|
|
24
|
+
interface ItemsDataStructure<T> {
|
|
25
|
+
get(): ExtensionItem<T>[]
|
|
26
|
+
add(item: ExtensionItem<T>): void
|
|
27
|
+
discardBy(predicate: (item: ExtensionItem<T>) => boolean): void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const itemsDataStructure = <T>(): ItemsDataStructure<T> => {
|
|
31
|
+
let items: ExtensionItem<T>[] = []
|
|
32
|
+
return {
|
|
33
|
+
get: () => items,
|
|
34
|
+
add: (item: ExtensionItem<T>) => {
|
|
35
|
+
items.push(item)
|
|
36
|
+
},
|
|
37
|
+
discardBy: (predicate: (item: ExtensionItem<T>) => boolean) => {
|
|
38
|
+
items = items.filter(predicate)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type CustomCreateExtensionSlot = <T>() => ItemsDataStructure<T>
|
|
44
|
+
|
|
45
|
+
export interface CreateExtensionSlotOptions {
|
|
46
|
+
declaringShell?: Shell
|
|
47
|
+
customCreateExtensionSlot?: CustomCreateExtensionSlot
|
|
48
|
+
}
|
|
49
|
+
|
|
24
50
|
export function createExtensionSlot<T>(
|
|
25
51
|
key: SlotKey<T>,
|
|
26
52
|
host: PrivateAppHost,
|
|
27
|
-
|
|
53
|
+
options?: CreateExtensionSlotOptions
|
|
28
54
|
): PrivateExtensionSlot<T> & AnyExtensionSlot {
|
|
29
|
-
|
|
55
|
+
const items = options?.customCreateExtensionSlot ? options.customCreateExtensionSlot<T>() : itemsDataStructure<T>()
|
|
30
56
|
let subscribers: (() => void)[] = []
|
|
31
57
|
const slotUniqueId = _.uniqueId()
|
|
32
58
|
|
|
33
59
|
return {
|
|
34
60
|
host,
|
|
35
|
-
declaringShell,
|
|
61
|
+
declaringShell: options?.declaringShell,
|
|
36
62
|
name: key.name,
|
|
37
63
|
contribute,
|
|
38
64
|
getItems,
|
|
@@ -43,7 +69,7 @@ export function createExtensionSlot<T>(
|
|
|
43
69
|
}
|
|
44
70
|
|
|
45
71
|
function contribute(fromShell: Shell, item: T, condition?: ContributionPredicate): void {
|
|
46
|
-
items.
|
|
72
|
+
items.add({
|
|
47
73
|
shell: fromShell,
|
|
48
74
|
contribution: item,
|
|
49
75
|
condition: condition || alwaysTrue,
|
|
@@ -53,21 +79,21 @@ export function createExtensionSlot<T>(
|
|
|
53
79
|
}
|
|
54
80
|
|
|
55
81
|
function getItems(forceAll: boolean = false): ExtensionItem<T>[] {
|
|
56
|
-
return forceAll ? items : items.filter(item => item.condition())
|
|
82
|
+
return forceAll ? items.get() : items.get().filter(item => item.condition())
|
|
57
83
|
}
|
|
58
84
|
|
|
59
85
|
function getSingleItem(): ExtensionItem<T> | undefined {
|
|
60
|
-
return items.find(item => item.condition())
|
|
86
|
+
return items.get().find(item => item.condition())
|
|
61
87
|
}
|
|
62
88
|
|
|
63
89
|
function getItemByName(name: string): ExtensionItem<T> | undefined {
|
|
64
|
-
return items.find(item => item.name === name && item.condition())
|
|
90
|
+
return items.get().find(item => item.name === name && item.condition())
|
|
65
91
|
}
|
|
66
92
|
|
|
67
93
|
function discardBy(predicate: ExtensionItemFilter<T>) {
|
|
68
|
-
const originalContributionCount = items.length
|
|
69
|
-
items
|
|
70
|
-
if (items.length !== originalContributionCount) {
|
|
94
|
+
const originalContributionCount = items.get().length
|
|
95
|
+
items.discardBy(v => !predicate(v))
|
|
96
|
+
if (items.get().length !== originalContributionCount) {
|
|
71
97
|
subscribers.forEach(func => func())
|
|
72
98
|
}
|
|
73
99
|
}
|
package/src/index.ts
CHANGED
|
@@ -26,13 +26,11 @@ export {
|
|
|
26
26
|
ScopedStore
|
|
27
27
|
} from './API'
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
29
|
export { ConsoleHostLogger } from './loggers'
|
|
32
30
|
|
|
33
31
|
export { createAppHost, makeLazyEntryPoint, mainViewSlotKey, stateSlotKey } from './appHost'
|
|
34
32
|
|
|
35
|
-
export {AppHostServicesProvider} from './appHostServices'
|
|
33
|
+
export { AppHostServicesProvider } from './appHostServices'
|
|
36
34
|
|
|
37
35
|
export { InstalledShellsSelectors, ShellToggleSet } from './installedShellsState'
|
|
38
36
|
|
|
@@ -42,5 +40,4 @@ export { interceptAnyObject } from './interceptAnyObject'
|
|
|
42
40
|
export { monitorAPI } from './monitorAPI'
|
|
43
41
|
export { hot } from './hot'
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
export {INTERNAL_DONT_USE_SHELL_GET_APP_HOST} from './__internal'
|
|
43
|
+
export { INTERNAL_DONT_USE_SHELL_GET_APP_HOST } from './__internal'
|
package/src/throttledStore.ts
CHANGED
|
@@ -16,18 +16,17 @@ interface AnyShellAction extends AnyAction {
|
|
|
16
16
|
__shellName?: string
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let id : undefined | NodeJS.Timeout = undefined
|
|
19
|
+
function createTimeOutPublisher(notify: () => void) {
|
|
20
|
+
let id: undefined | NodeJS.Timeout = undefined
|
|
22
21
|
return () => {
|
|
23
22
|
if (id === undefined) {
|
|
24
23
|
id = setTimeout(() => {
|
|
25
24
|
id = undefined
|
|
26
25
|
notify()
|
|
27
|
-
},0)
|
|
26
|
+
}, 0)
|
|
28
27
|
}
|
|
29
28
|
return () => {
|
|
30
|
-
if(id === undefined) {
|
|
29
|
+
if (id === undefined) {
|
|
31
30
|
return
|
|
32
31
|
}
|
|
33
32
|
clearTimeout(id)
|
|
@@ -36,8 +35,8 @@ function createTimeOutPublisher ( notify: () => void) {
|
|
|
36
35
|
}
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
function createAnimationFramePublisher
|
|
40
|
-
let id
|
|
38
|
+
function createAnimationFramePublisher(notify: () => void) {
|
|
39
|
+
let id: undefined | number = undefined
|
|
41
40
|
return () => {
|
|
42
41
|
if (id === undefined) {
|
|
43
42
|
id = requestAnimationFrame(() => {
|
|
@@ -46,7 +45,7 @@ function createAnimationFramePublisher ( notify: () => void) {
|
|
|
46
45
|
})
|
|
47
46
|
}
|
|
48
47
|
return () => {
|
|
49
|
-
if(id === undefined) {
|
|
48
|
+
if (id === undefined) {
|
|
50
49
|
return
|
|
51
50
|
}
|
|
52
51
|
cancelAnimationFrame(id)
|
|
@@ -55,9 +54,6 @@ function createAnimationFramePublisher ( notify: () => void) {
|
|
|
55
54
|
}
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
57
|
type Subscriber = () => void
|
|
62
58
|
|
|
63
59
|
export interface StateContribution<TState = {}, TAction extends AnyAction = AnyAction> {
|
|
@@ -252,10 +248,8 @@ export const createThrottledStore = (
|
|
|
252
248
|
notifyAll()
|
|
253
249
|
}
|
|
254
250
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const notifyAllOnPublish = typeof window === 'undefined' ? createTimeOutPublisher(scheduledNotifyAll) : createAnimationFramePublisher(scheduledNotifyAll)
|
|
251
|
+
const notifyAllOnPublish =
|
|
252
|
+
typeof window === 'undefined' ? createTimeOutPublisher(scheduledNotifyAll) : createAnimationFramePublisher(scheduledNotifyAll)
|
|
259
253
|
|
|
260
254
|
let cancelRender = _.noop
|
|
261
255
|
|
package/test/appHost.spec.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
import { AppHostAPI, AppHostServicesEntryPointName, AppHostServicesProvider } from '../src/appHostServices'
|
|
33
33
|
import { ConsoleHostLogger } from '../src/loggers'
|
|
34
34
|
import { createCircularEntryPoints, createDirectCircularEntryPoints } from './appHost.mock'
|
|
35
|
+
import { createSignalItemsDataStructure } from './createSignalItemsDataStructure'
|
|
35
36
|
|
|
36
37
|
const testHostOptions: AppHostOptions = {
|
|
37
38
|
monitoring: { disableMonitoring: true }
|
|
@@ -1792,4 +1793,92 @@ If the API is intended to be public, it should be declared as "public: true" in
|
|
|
1792
1793
|
)
|
|
1793
1794
|
})
|
|
1794
1795
|
})
|
|
1796
|
+
|
|
1797
|
+
describe('Custom Items Data Structure via AppHost Options (plugins)', () => {
|
|
1798
|
+
it('should use customCreateExtensionSlot from appHostOptions when contributing and removing items', async () => {
|
|
1799
|
+
interface SlotItem {
|
|
1800
|
+
value: string
|
|
1801
|
+
}
|
|
1802
|
+
const slotKey: SlotKey<SlotItem> = { name: 'host_options_signal_slot' }
|
|
1803
|
+
|
|
1804
|
+
interface SlotContributionAPI {
|
|
1805
|
+
contributeItem(fromShell: Shell, item: SlotItem): void
|
|
1806
|
+
getItems(): SlotItem[]
|
|
1807
|
+
}
|
|
1808
|
+
const SlotContributionAPIKey: SlotKey<SlotContributionAPI> = {
|
|
1809
|
+
name: 'SLOT_CONTRIBUTION_API'
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
const itemsSpy = jest.fn()
|
|
1813
|
+
|
|
1814
|
+
const { createDataStructure, effect } = createSignalItemsDataStructure()
|
|
1815
|
+
|
|
1816
|
+
const slotOwnerEntryPoint: EntryPoint = {
|
|
1817
|
+
name: 'SLOT_OWNER_ENTRY_POINT',
|
|
1818
|
+
declareAPIs() {
|
|
1819
|
+
return [SlotContributionAPIKey]
|
|
1820
|
+
},
|
|
1821
|
+
attach(shell: Shell) {
|
|
1822
|
+
const slot = shell.declareSlot(slotKey)
|
|
1823
|
+
shell.contributeAPI(SlotContributionAPIKey, () => ({
|
|
1824
|
+
contributeItem(fromShell: Shell, item: SlotItem) {
|
|
1825
|
+
slot.contribute(fromShell, item)
|
|
1826
|
+
},
|
|
1827
|
+
getItems() {
|
|
1828
|
+
return slot.getItems().map(item => item.contribution)
|
|
1829
|
+
}
|
|
1830
|
+
}))
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
const ContributorEntryPoint: EntryPoint = {
|
|
1835
|
+
name: 'CONTRIBUTOR_ENTRY_POINT',
|
|
1836
|
+
getDependencyAPIs() {
|
|
1837
|
+
return [SlotContributionAPIKey, ListenerAPI]
|
|
1838
|
+
},
|
|
1839
|
+
extend(shell: Shell) {
|
|
1840
|
+
const api = shell.getAPI(SlotContributionAPIKey)
|
|
1841
|
+
api.contributeItem(shell, { value: 'item1' })
|
|
1842
|
+
api.contributeItem(shell, { value: 'item2' })
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
const ListenerAPI: SlotKey<{}> = { name: 'LISTENER_API' }
|
|
1847
|
+
|
|
1848
|
+
const ListenerEntryPoint: EntryPoint = {
|
|
1849
|
+
name: 'LISTENER_ENTRY_POINT',
|
|
1850
|
+
declareAPIs() {
|
|
1851
|
+
return [ListenerAPI]
|
|
1852
|
+
},
|
|
1853
|
+
getDependencyAPIs() {
|
|
1854
|
+
return [SlotContributionAPIKey]
|
|
1855
|
+
},
|
|
1856
|
+
// move to attach to make sure
|
|
1857
|
+
attach(shell: Shell) {
|
|
1858
|
+
const slotContributionAPI = shell.getAPI(SlotContributionAPIKey)
|
|
1859
|
+
effect(() => {
|
|
1860
|
+
slotContributionAPI.getItems()
|
|
1861
|
+
itemsSpy()
|
|
1862
|
+
})
|
|
1863
|
+
// contribute an api to make sure that the contribution depends on it and happens after.
|
|
1864
|
+
shell.contributeAPI(ListenerAPI, () => ({}))
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
const host = createAppHost([slotOwnerEntryPoint, ContributorEntryPoint, ListenerEntryPoint], {
|
|
1869
|
+
monitoring: {},
|
|
1870
|
+
plugins: {
|
|
1871
|
+
extensionSlot: {
|
|
1872
|
+
customCreateExtensionSlot: createDataStructure
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
})
|
|
1876
|
+
|
|
1877
|
+
// first was created and then two time
|
|
1878
|
+
expect(itemsSpy).toBeCalledTimes(3)
|
|
1879
|
+
|
|
1880
|
+
await host.removeShells([ContributorEntryPoint.name])
|
|
1881
|
+
expect(itemsSpy).toBeCalledTimes(4)
|
|
1882
|
+
})
|
|
1883
|
+
})
|
|
1795
1884
|
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ExtensionItem } from '../src/API'
|
|
2
|
+
|
|
3
|
+
// Naive signals implementation
|
|
4
|
+
interface Signal<T> {
|
|
5
|
+
get(): T
|
|
6
|
+
set(value: T): void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let currentEffect: (() => void) | null = null
|
|
10
|
+
|
|
11
|
+
const signal = <T>(initialValue: T): Signal<T> => {
|
|
12
|
+
let value = initialValue
|
|
13
|
+
const subscribers = new Set<() => void>()
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
get: () => {
|
|
17
|
+
if (currentEffect) {
|
|
18
|
+
subscribers.add(currentEffect)
|
|
19
|
+
}
|
|
20
|
+
return value
|
|
21
|
+
},
|
|
22
|
+
set: (newValue: T) => {
|
|
23
|
+
value = newValue
|
|
24
|
+
subscribers.forEach(fn => fn())
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const effect = (fn: () => void): (() => void) => {
|
|
30
|
+
const execute = () => {
|
|
31
|
+
currentEffect = execute
|
|
32
|
+
fn()
|
|
33
|
+
currentEffect = null
|
|
34
|
+
}
|
|
35
|
+
execute()
|
|
36
|
+
return () => {
|
|
37
|
+
currentEffect = null
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const createSignalItemsDataStructure = () => {
|
|
42
|
+
let signalToTrack: Signal<ExtensionItem<any>[]> | null = null
|
|
43
|
+
return {
|
|
44
|
+
createDataStructure: <U>() => {
|
|
45
|
+
const itemsSignal = signal<ExtensionItem<U>[]>([])
|
|
46
|
+
signalToTrack = itemsSignal
|
|
47
|
+
return {
|
|
48
|
+
get: () => itemsSignal.get(),
|
|
49
|
+
add: (item: ExtensionItem<U>) => {
|
|
50
|
+
itemsSignal.set([...itemsSignal.get(), item])
|
|
51
|
+
},
|
|
52
|
+
discardBy: (predicate: (item: ExtensionItem<U>) => boolean) => {
|
|
53
|
+
itemsSignal.set(itemsSignal.get().filter(predicate))
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
getSignalToTrack: () => {
|
|
58
|
+
if (!signalToTrack) {
|
|
59
|
+
throw new Error('Signal to track is not set')
|
|
60
|
+
}
|
|
61
|
+
return signalToTrack
|
|
62
|
+
},
|
|
63
|
+
effect
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { createAppHost, SlotKey } from '../src'
|
|
2
|
+
import { EntryPoint, ExtensionItem, PrivateAppHost, PrivateExtensionSlot, Shell } from '../src/API'
|
|
3
|
+
import { createExtensionSlot } from '../src/extensionSlot'
|
|
4
|
+
import { addMockShell } from '../testKit'
|
|
5
|
+
import { createSignalItemsDataStructure } from './createSignalItemsDataStructure'
|
|
6
|
+
|
|
7
|
+
describe('ExtensionSlot', () => {
|
|
8
|
+
describe('Subscribe', () => {
|
|
9
|
+
it('should be invoked on contribution', () => {
|
|
10
|
+
const slotKey: SlotKey<{}> = {
|
|
11
|
+
name: 'mock_key'
|
|
12
|
+
}
|
|
13
|
+
const host = createAppHost([])
|
|
14
|
+
const shell = addMockShell(host)
|
|
15
|
+
const spy = jest.fn()
|
|
16
|
+
const slot = shell.declareSlot(slotKey) as PrivateExtensionSlot<{}>
|
|
17
|
+
|
|
18
|
+
slot.subscribe(spy)
|
|
19
|
+
|
|
20
|
+
slot.contribute(shell, () => ({}))
|
|
21
|
+
|
|
22
|
+
expect(spy).toBeCalledTimes(1)
|
|
23
|
+
|
|
24
|
+
slot.contribute(shell, () => ({}))
|
|
25
|
+
|
|
26
|
+
expect(spy).toBeCalledTimes(2)
|
|
27
|
+
})
|
|
28
|
+
it('should be invoked once when contribution is during installation phase', () => {
|
|
29
|
+
interface SlotItem {
|
|
30
|
+
name: string
|
|
31
|
+
}
|
|
32
|
+
const slotKey: SlotKey<SlotItem> = {
|
|
33
|
+
name: 'MOCK_SLOT'
|
|
34
|
+
}
|
|
35
|
+
interface MockContributionAPI {
|
|
36
|
+
contributeItem(fromShell: Shell, item: SlotItem): void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ContributionAPI: SlotKey<MockContributionAPI> = {
|
|
40
|
+
name: 'CONTRIBUTION_API'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const spy = jest.fn()
|
|
44
|
+
|
|
45
|
+
const contributionEntryPoint: EntryPoint = {
|
|
46
|
+
name: 'PRIVATE CONTRIBUTION ENTRY POINT',
|
|
47
|
+
declareAPIs() {
|
|
48
|
+
return [ContributionAPI]
|
|
49
|
+
},
|
|
50
|
+
attach(shell) {
|
|
51
|
+
const slot = shell.declareSlot(slotKey) as PrivateExtensionSlot<SlotItem>
|
|
52
|
+
slot.subscribe(spy)
|
|
53
|
+
shell.contributeAPI(ContributionAPI, () => ({
|
|
54
|
+
contributeItem(fromShell, item) {
|
|
55
|
+
shell.getSlot(slotKey).contribute(fromShell, item)
|
|
56
|
+
}
|
|
57
|
+
}))
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const API_A: SlotKey<{}> = { name: 'API_A' }
|
|
62
|
+
|
|
63
|
+
const itemContributionEntryPointA: EntryPoint = {
|
|
64
|
+
name: 'ITEM CONTRIBUTION ENTRY POINT A',
|
|
65
|
+
getDependencyAPIs() {
|
|
66
|
+
return [ContributionAPI]
|
|
67
|
+
},
|
|
68
|
+
declareAPIs(): SlotKey<{}>[] {
|
|
69
|
+
return [API_A]
|
|
70
|
+
},
|
|
71
|
+
attach(shell: Shell) {
|
|
72
|
+
shell.contributeAPI(API_A, () => ({}))
|
|
73
|
+
},
|
|
74
|
+
extend(shell: Shell) {
|
|
75
|
+
shell.getAPI(ContributionAPI).contributeItem(shell, { name: 'A.1' })
|
|
76
|
+
shell.getAPI(ContributionAPI).contributeItem(shell, { name: 'A.2' })
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const itemContributionEntryPointB: EntryPoint = {
|
|
81
|
+
name: 'ITEM CONTRIBUTION ENTRY POINT B',
|
|
82
|
+
getDependencyAPIs() {
|
|
83
|
+
return [ContributionAPI, API_A]
|
|
84
|
+
},
|
|
85
|
+
extend(shell: Shell) {
|
|
86
|
+
shell.getAPI(ContributionAPI).contributeItem(shell, { name: 'B.1' })
|
|
87
|
+
shell.getAPI(ContributionAPI).contributeItem(shell, { name: 'B.2' })
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
createAppHost([contributionEntryPoint, itemContributionEntryPointA, itemContributionEntryPointB])
|
|
92
|
+
|
|
93
|
+
expect(spy).toBeCalledTimes(1)
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe('Custom Items Data Structure with Signals', () => {
|
|
98
|
+
it('should notify effect when items are added', () => {
|
|
99
|
+
const slotKey: SlotKey<{ value: string }> = { name: 'signal_slot' }
|
|
100
|
+
const host = createAppHost([]) as PrivateAppHost
|
|
101
|
+
const shell = addMockShell(host)
|
|
102
|
+
|
|
103
|
+
const { createDataStructure: dataStructure, getSignalToTrack, effect } = createSignalItemsDataStructure()
|
|
104
|
+
const effectSpy = jest.fn()
|
|
105
|
+
|
|
106
|
+
const slot = createExtensionSlot(slotKey, host, {
|
|
107
|
+
customCreateExtensionSlot: dataStructure
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
effect(() => {
|
|
111
|
+
getSignalToTrack().get()
|
|
112
|
+
effectSpy()
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
expect(effectSpy).toHaveBeenCalledTimes(1)
|
|
116
|
+
|
|
117
|
+
slot.contribute(shell, { value: 'item1' })
|
|
118
|
+
expect(effectSpy).toHaveBeenCalledTimes(2)
|
|
119
|
+
|
|
120
|
+
slot.contribute(shell, { value: 'item2' })
|
|
121
|
+
expect(effectSpy).toHaveBeenCalledTimes(3)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('should notify effect when items are filtered/removed', () => {
|
|
125
|
+
const slotKey: SlotKey<{ id: number }> = { name: 'filter_signal_slot' }
|
|
126
|
+
const host = createAppHost([]) as PrivateAppHost
|
|
127
|
+
const shell = addMockShell(host)
|
|
128
|
+
|
|
129
|
+
const { createDataStructure: dataStructure, getSignalToTrack, effect } = createSignalItemsDataStructure()
|
|
130
|
+
const effectSpy = jest.fn()
|
|
131
|
+
|
|
132
|
+
const slot = createExtensionSlot(slotKey, host, {
|
|
133
|
+
customCreateExtensionSlot: dataStructure
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
effect(() => {
|
|
137
|
+
getSignalToTrack().get()
|
|
138
|
+
effectSpy()
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
expect(effectSpy).toHaveBeenCalledTimes(1)
|
|
142
|
+
|
|
143
|
+
slot.contribute(shell, { id: 1 })
|
|
144
|
+
slot.contribute(shell, { id: 2 })
|
|
145
|
+
expect(effectSpy).toHaveBeenCalledTimes(3)
|
|
146
|
+
|
|
147
|
+
slot.discardBy((item: ExtensionItem<{ id: number }>) => item.contribution.id === 1)
|
|
148
|
+
expect(effectSpy).toHaveBeenCalledTimes(4)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('should track signal value correctly', () => {
|
|
152
|
+
const slotKey: SlotKey<{ name: string }> = { name: 'track_slot' }
|
|
153
|
+
const host = createAppHost([]) as PrivateAppHost
|
|
154
|
+
const shell = addMockShell(host)
|
|
155
|
+
|
|
156
|
+
const { createDataStructure: dataStructure, getSignalToTrack, effect } = createSignalItemsDataStructure()
|
|
157
|
+
let capturedItems: ExtensionItem<{ name: string }>[] = []
|
|
158
|
+
|
|
159
|
+
const slot = createExtensionSlot(slotKey, host, {
|
|
160
|
+
customCreateExtensionSlot: dataStructure
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
effect(() => {
|
|
164
|
+
capturedItems = getSignalToTrack().get()
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
slot.contribute(shell, { name: 'first' })
|
|
168
|
+
expect(capturedItems).toHaveLength(1)
|
|
169
|
+
expect(capturedItems[0].contribution.name).toBe('first')
|
|
170
|
+
|
|
171
|
+
slot.contribute(shell, { name: 'second' })
|
|
172
|
+
expect(capturedItems).toHaveLength(2)
|
|
173
|
+
expect(capturedItems[1].contribution.name).toBe('second')
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
})
|
package/testKit/index.ts
CHANGED
|
@@ -4,13 +4,14 @@ import { AnySlotKey, AppHost, createAppHost as _createAppHost, EntryPointOrPacka
|
|
|
4
4
|
import { createShellLogger } from '../src/loggers'
|
|
5
5
|
import { emptyLoggerOptions } from './emptyLoggerOptions'
|
|
6
6
|
|
|
7
|
-
export {createShellLogger}
|
|
7
|
+
export { createShellLogger }
|
|
8
8
|
export { emptyLoggerOptions }
|
|
9
9
|
export { AppHost } from '../src/index'
|
|
10
10
|
|
|
11
11
|
export { withConsoleErrors } from './withConsoleErrors'
|
|
12
12
|
export { withThrowOnError } from './withThrowOnError'
|
|
13
13
|
export * from './mockPackage'
|
|
14
|
+
export { createSignalItemsDataStructure } from '../test/createSignalItemsDataStructure'
|
|
14
15
|
|
|
15
16
|
export const createAppHost: typeof _createAppHost = (packages, options = emptyLoggerOptions) => {
|
|
16
17
|
return _createAppHost(packages, options)
|
|
@@ -74,7 +75,10 @@ export function createAppHostWithPacts(packages: EntryPointOrPackage[], pacts: P
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
return createAppHost([...packages, pactsEntryPoint], {
|
|
78
|
+
return createAppHost([...packages, pactsEntryPoint], {
|
|
79
|
+
...emptyLoggerOptions,
|
|
80
|
+
disableLayersValidation: true
|
|
81
|
+
})
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
export async function createAppHostAndWaitForLoading(packages: EntryPointOrPackage[], pacts: PactAPIBase[]): Promise<AppHost> {
|
|
@@ -88,7 +92,7 @@ export async function createAppHostAndWaitForLoading(packages: EntryPointOrPacka
|
|
|
88
92
|
setTimeout(() => {
|
|
89
93
|
const readyAPIs = Array.from(globalThis.repluggableAppDebug.readyAPIs)
|
|
90
94
|
const unreadyAPIs = declaredAPIs.filter(api => !readyAPIs.some(readyAPI => readyAPI.name === api.name))
|
|
91
|
-
const rootUnreadyAPI = globalThis.repluggableAppDebug.utils.getRootUnreadyAPI()
|
|
95
|
+
const rootUnreadyAPI = globalThis.repluggableAppDebug.utils.getRootUnreadyAPI()
|
|
92
96
|
|
|
93
97
|
reject(
|
|
94
98
|
new Error(
|
|
@@ -118,7 +122,6 @@ in addition here's the full list of declared APIs that have not been contributed
|
|
|
118
122
|
return Promise.race([timeoutPromise, loadingPromise]).then(() => appHost)
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
|
|
122
125
|
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
|
|
123
126
|
interface EntryPointOverrides extends Omit<EntryPoint, 'name'> {
|
|
124
127
|
name?: EntryPoint['name']
|
|
@@ -159,8 +162,6 @@ export const addMockShell = (host: AppHost, entryPointOverrides: EntryPointOverr
|
|
|
159
162
|
return shell
|
|
160
163
|
}
|
|
161
164
|
|
|
162
|
-
|
|
163
|
-
|
|
164
165
|
export function mockObservable<T>(value: T): ObservableState<T> {
|
|
165
166
|
return {
|
|
166
167
|
subscribe: () => {
|
|
@@ -171,4 +172,3 @@ export function mockObservable<T>(value: T): ObservableState<T> {
|
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
}
|
|
174
|
-
|