Skip to content

Commit 31ac4c8

Browse files
committed
Determine type of sidebar store automatically
Avoid the need to manually create the `SidebarStore` type by inferring it automatically. This works as follows: 1. The `modules` argument to `createStore` has been converted to a tuple type (`[ModuleA, ModuleB, ...]`) 2. The type of a module that would result from merging all the individual modules (`ModuleA & ModuleB ...`) is computed using a `TupleToIntersection` utility type 3. `StoreFromModule` is used to compute the type of the store that the merged module would produce
1 parent 1326d1a commit 31ac4c8

2 files changed

Lines changed: 28 additions & 33 deletions

File tree

src/sidebar/store/create-store.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,24 @@ function assignOnce(target, source) {
149149
return Object.assign(target, source);
150150
}
151151

152+
/**
153+
* @template T
154+
* @typedef {{[K in keyof T]: (x: T[K]) => void }} MapContravariant
155+
*/
156+
157+
/**
158+
* Utility that turns a tuple type `[A, B, C]` into an intersection `A & B & C`.
159+
*
160+
* The implementation is magic adapted from
161+
* https://github.com/microsoft/TypeScript/issues/28323. Roughly speaking it
162+
* works by computing a type that could be assigned to any position in the
163+
* tuple, which must be the intersection of all the tuple element types.
164+
*
165+
* @template T
166+
* @template {Record<number, unknown>} [Temp=MapContravariant<T>]
167+
* @typedef {Temp[number] extends (x: infer U) => unknown ? U : never} TupleToIntersection
168+
*/
169+
152170
/**
153171
* Create a Redux store from a set of _modules_.
154172
*
@@ -173,10 +191,11 @@ function assignOnce(target, source) {
173191
* `use-store.js`. This returns a proxy which enables UI components to observe
174192
* what store state a component depends upon and re-render when it changes.
175193
*
176-
* @param {Module<any,any,any,any>[]} modules
194+
* @template {readonly Module<any,any,any,any>[]} Modules
195+
* @param {Modules} modules
177196
* @param {any[]} [initArgs] - Arguments to pass to each state module's `initialState` function
178197
* @param {any[]} [middleware] - List of additional Redux middlewares to use
179-
* @return Store<any,any,any>
198+
* @return {StoreFromModule<TupleToIntersection<Modules>>}
180199
*/
181200
export function createStore(modules, initArgs = [], middleware = []) {
182201
/** @type {Record<string, unknown>} */
@@ -241,7 +260,7 @@ export function createStore(modules, initArgs = [], middleware = []) {
241260
}
242261
Object.assign(store, selectorMethods);
243262

244-
return store;
263+
return /** @type {any} */ (store);
245264
}
246265

247266
/**

src/sidebar/store/index.js

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,7 @@ import { sidebarPanelsModule } from './modules/sidebar-panels';
1717
import { toastMessagesModule } from './modules/toast-messages';
1818
import { viewerModule } from './modules/viewer';
1919

20-
/**
21-
* @template M
22-
* @typedef {import('./create-store').StoreFromModule<M>} StoreFromModule
23-
*/
24-
25-
/**
26-
* @typedef {StoreFromModule<activityModule> &
27-
* StoreFromModule<annotationsModule> &
28-
* StoreFromModule<defaultsModule> &
29-
* StoreFromModule<directLinkedModule> &
30-
* StoreFromModule<draftsModule> &
31-
* StoreFromModule<filtersModule> &
32-
* StoreFromModule<framesModule> &
33-
* StoreFromModule<groupsModule> &
34-
* StoreFromModule<linksModule> &
35-
* StoreFromModule<realTimeUpdatesModule> &
36-
* StoreFromModule<routeModule> &
37-
* StoreFromModule<selectionModule> &
38-
* StoreFromModule<sessionModule> &
39-
* StoreFromModule<sidebarPanelsModule> &
40-
* StoreFromModule<toastMessagesModule> &
41-
* StoreFromModule<viewerModule>
42-
* } SidebarStore
43-
*/
20+
/** @typedef {ReturnType<createSidebarStore>} SidebarStore */
4421

4522
/**
4623
* Create the central state store for the sidebar application.
@@ -52,13 +29,14 @@ import { viewerModule } from './modules/viewer';
5229
* [1] https://redux.js.org
5330
*
5431
* @param {import('../../types/config').SidebarSettings} settings
55-
* @return {SidebarStore}
5632
* @inject
5733
*/
5834
export function createSidebarStore(settings) {
5935
const middleware = [debugMiddleware];
6036

61-
const modules = [
37+
// `const` type gives `modules` a tuple type, which allows `createStore`
38+
// to infer properties (eg. action and selector methods) of returned store.
39+
const modules = /** @type {const} */ ([
6240
activityModule,
6341
annotationsModule,
6442
defaultsModule,
@@ -75,8 +53,6 @@ export function createSidebarStore(settings) {
7553
sidebarPanelsModule,
7654
toastMessagesModule,
7755
viewerModule,
78-
];
79-
return /** @type {SidebarStore} */ (
80-
createStore(modules, [settings], middleware)
81-
);
56+
]);
57+
return createStore(modules, [settings], middleware);
8258
}

0 commit comments

Comments
 (0)