ducks
Version:
🦆🦆🦆 Ducks is a Reducer Bundles Manager that Implementing the Redux Ducks Modular Proposal with Great Convenience.
198 lines (164 loc) • 5.62 kB
text/typescript
/**
* Ducks - https://github.com/huan/ducks
*
* @copyright 2020 Huan LI (李卓桓) <https://github.com/huan>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import type {
Store,
} from 'redux'
import {
VERSION,
} from './config.js'
import type {
Duck,
OperationsMapObject,
SelectorsMapObject,
} from './duck.js'
/**
* Map types from a Map Object
* https://github.com/microsoft/TypeScript/issues/24220#issuecomment-390063153
*/
type BundleOperations <O extends OperationsMapObject> = {
[key in keyof O]: ReturnType<O[key]>
}
type DucksifyOperations <D extends Duck> = BundleOperations <D extends { operations: any } ? D['operations'] : {}>
type BundleSelectors <S extends SelectorsMapObject> = {
[key in keyof S]: ReturnType<S[key]>
}
type DucksifySelectors <D extends Duck> = BundleSelectors <D extends { selectors: any } ? D['selectors'] : {}>
class Bundle <D extends Duck = any> {
static VERSION = VERSION
get store () {
if (!this._store) {
throw new Error('Bundle store has not been set yet: Bundle can only be used after its store has been initialized.')
}
return this._store!
}
private _store?: Store
namespaces: string[]
get reducer (): D['default'] { return this.duck.default }
get actions () : D['actions'] { return this.duck.actions }
get types () : D['types'] { return this.duck.types }
// get epics () : A['epics'] { return this.duck.epics }
// get sagas () : A['sagas'] { return this.duck.sagas }
get operations () {
return this.ducksifiedOperations
}
get selectors () {
return this.ducksifiedSelectors
}
protected ducksifiedSelectors : DucksifySelectors<D>
protected ducksifiedOperations : DucksifyOperations<D>
protected get state () : ReturnType<D['default']> {
// console.info('[duck] state:', this.store.getState())
// console.info('[duck] namespaces:', this.namespaces)
// console.info('[duck] state[namespaces[0]]:', this.store.getState()[this.namespaces[0]])
// console.info('[duck] state[namespaces[1]]:', this.store.getState()[this.namespaces[1]])
const duckStateReducer = (duckState: any, namespace: string, idx: number) => {
if (namespace in duckState) {
return duckState[namespace]
}
throw new Error('duckStateReducer() can not get state from namespace: ' + this.namespaces[idx] + ' with index: ' + idx)
}
const duckState = this.namespaces.reduce(
duckStateReducer,
this.store.getState(),
)
return duckState
}
constructor (
public duck: D,
) {
this.namespaces = []
/**
* We provide a convenience way to call `selectors` and `operations`
* by encapsulating the `store` and `state`(includes the `namespace`)
* and take care of them for users
*
* Huan(202005): I'd like to call this: ducksify
*/
this.ducksifiedOperations = this.ducksifyOperations(this.duck)
this.ducksifiedSelectors = this.ducksifySelectors(this.duck)
}
public setStore (store: Store): void {
if (this._store) {
throw new Error('A store has already been set, and it can not be set twice.')
}
this._store = store
}
public setNamespaces (...namespaces: string[]): void {
if (this.namespaces.length > 0) {
throw new Error('Namespaces has already been set, and it can not be set twice.')
}
this.namespaces = namespaces
}
protected ducksifyOperations (
duck: D,
): DucksifyOperations<D> {
let ducksifiedOperations: BundleOperations<any> = {}
const operations = duck.operations
if (!operations) {
return ducksifiedOperations
}
const that = this
Object.keys(operations).forEach(operation => {
/**
* Inferred function names
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name
*/
ducksifiedOperations = {
...ducksifiedOperations,
[operation]: function (...args: any[]) {
return operations[operation]!(
/**
* We have to make sure `store` has been initialized before the following code has been ran
*/
that.store.dispatch
)(...args)
},
}
})
return ducksifiedOperations
}
protected ducksifySelectors (
duck: D,
): DucksifySelectors<D> {
let ducksifiedSelectors: BundleSelectors<any> = {}
const selectors = duck.selectors
if (!selectors) {
return ducksifiedSelectors
}
const that = this
Object.keys(selectors).forEach(selector => {
/**
* Inferred function names
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name
*/
ducksifiedSelectors = {
...ducksifiedSelectors,
[selector]: function (...args: any[]) {
return selectors[selector]!(
that.state,
)(...args)
},
}
})
return ducksifiedSelectors
}
}
export {
Bundle,
}