Originally the question was two questions "clubbed" together, but after a discussion in the comments and some much needed aid from @jcalz, we've managed to brig it closer to what it looks like now.
You can find the full code examples at the end for easier copy-paste
The Problem:
You can find the type definitions and code examples below
I am trying to figure out a way on how to "compose" (as in function composition) multiple functions that are suppose to modify a single object by "extending" it with additional properties, into a single function that does all the extensions and is properly typed.
The functions in question are StoreEnhancers<Ext>
(where Ext
represents a plain object that the resulting object is extended with) and the result of their composition should also be a StoreEnhancer<ExtFinal>
where ExtFinal
should be a union of all the Ext
of every enhancer that was passed into the composition.
No matter what I try, passing an array, using a spread operator (...
) I cannot seem to be able to write a compose function that is capable extending an object with multiple StoreEnhancers
and allowing typescript to infer the final Ext
so that I can access all the properties these enhancers add.
Here are some definitions for more context:
Firstly, we can define a StoreEnhancer
as a function that takes either a StoreCreator
or an EnahncedStoreCreator
and returns an EnhancedStoreCreator
. Or in a more human readable terms, its a function that will take, as it's argument another function, used to create what we will call a "store object". A store enhancer will then "enhance" this store object, by adding more properties to it, and will return an "enhanced" version of a store object.
So let's define the types (very barebones, for simplicity sake)
type Store = {
tag: 'basestore' // used just to make Store distinct from a base {}
}
type StoreCreator = () => Store
type EnhancedStoreCreator<Ext> = () => Store & Ext
// essentially one could say that:
// StoreCreator === EnhancedStoreCreator<{}>
// which allows us to define a StoreEnhancer as such:
type StoreEnhancer<Ext> = <Prev>(createStore: EnhancedStoreCreator<Prev>) => EnhancedStoreCreator<Ext & Prev>
And an implementation might look something like this:
const createStore: StoreCreator = () => ({ tag: 'basestore' })
const enhanceWithFeatureA: StoreEnhancer<{ featureA: string }> = createStore => () => {
const prevStore = createStore()
return { ...prevStore, featureA: 'some string' }
}
const enhanceWithFeatureB: StoreEnhancer<{ featureB: number }> = createStore => () => {
const prevStore = createStore()
return { ...prevStore, featureB: 123 }
}
const createStoreWithA = enhanceWithFeatureA(createStore)
const createStoreWithAandB = enhanceWithFeatureB(createStoreWithA)
const store = storeCreatorWithFeatureAandB()
console.log(store)
// {
// tag: 'baseStore',
// featureA: 'some string'
// featureB: 123
// }
Codesandbox link with the new (updated) code is here
Codesandbox link is with the original question's code is here