Skip to content

Commit

Permalink
Actions mapping between parent & child stores (#25)
Browse files Browse the repository at this point in the history
* implemented actions transformation

* implemented actions mapping prototype

* refactoring

* refactoring

* refactoring

* moved to dir

* refactoring

* minor fix

* refactoring

* Refactoring
  • Loading branch information
KazaiMazai committed Apr 13, 2024
1 parent b9e1f45 commit 1cd2e87
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 123 deletions.
24 changes: 0 additions & 24 deletions Sources/Puredux/Store/ActionsInterceptor.swift

This file was deleted.

55 changes: 55 additions & 0 deletions Sources/Puredux/Store/Core/ActionsInterceptor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// File.swift
//
//
// Created by Sergey Kazakov on 12.11.2022.
//

import Foundation

struct ScopedAction<Action> {
let storeId: StoreID
let action: Action
}

struct ActionsMapping<GlobalAction, LocalAction> {
let toGlobal: (LocalAction) -> GlobalAction
let toLocal: (GlobalAction) -> LocalAction

static func passthrough<A>() -> ActionsMapping<A, A> {
ActionsMapping<A, A>(
toGlobal: { $0 },
toLocal: { $0 }
)
}
}

struct ActionsInterceptor<Action> {
let storeId: StoreID
let handler: (Action, @escaping Dispatch<Action>) -> Void
let dispatcher: Dispatch<Action>

func interceptIfNeeded(_ action: ScopedAction<Action>) {
guard action.storeId == storeId else { return }
handler(action.action, dispatcher)
}

func localInterceptor<LocalAction>(_ localStoreId: StoreID,
actionsMapping: ActionsMapping<Action, LocalAction>,
dispatcher: @escaping Dispatch<LocalAction>
) -> ActionsInterceptor<LocalAction> {

ActionsInterceptor<LocalAction>(
storeId: localStoreId,
handler: { action, dispatcher in
handler(
actionsMapping.toGlobal(action),
{ dispatcher(actionsMapping.toLocal($0)) }
)
},
dispatcher: { action in
dispatcher(action)
}
)
}
}
9 changes: 9 additions & 0 deletions Sources/Puredux/Store/Core/ActionsMapping.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//
// File.swift
//
//
// Created by Sergey Kazakov on 12/04/2024.
//

import Foundation

31 changes: 19 additions & 12 deletions Sources/Puredux/Store/Core/StoreNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,23 @@ typealias VoidStore<Action> = CoreStore<Void, Action>

typealias RootStoreNode<State, Action> = StoreNode<VoidStore<Action>, State, State, Action>

final class StoreNode<ParentStore, LocalState, State, Action> where ParentStore: StoreProtocol,
ParentStore.Action == Action {
final class StoreNode<ParentStore, LocalState, State, Action> where ParentStore: StoreProtocol {

private let localStore: CoreStore<LocalState, Action>
private let parentStore: ParentStore

private let stateMapping: (ParentStore.State, LocalState) -> State
private let actionsMapping: ActionsMapping<ParentStore.Action, Action>
private var observers: Set<Observer<State>> = []

init(initialState: LocalState,
stateMapping: @escaping (ParentStore.State, LocalState) -> State,
actionsMapping: ActionsMapping<ParentStore.Action, Action>,
parentStore: ParentStore,
reducer: @escaping Reducer<LocalState, Action>) {

self.stateMapping = stateMapping
self.actionsMapping = actionsMapping
self.parentStore = parentStore

localStore = CoreStore(
Expand All @@ -36,15 +38,16 @@ final class StoreNode<ParentStore, LocalState, State, Action> where ParentStore:
)

if let parentInterceptor = parentStore.actionsInterceptor {
let interceptor = ActionsInterceptor(
storeId: localStore.id,
handler: parentInterceptor.handler,
dispatcher: { [weak self] action in self?.dispatch(action) }
)

let interceptor = parentInterceptor.localInterceptor(
localStore.id,
actionsMapping: actionsMapping,
dispatcher: { [weak self] action in
self?.dispatch(action)
})

localStore.setInterceptorSync(interceptor)
}

localStore.syncSubscribe(observer: localObserver, receiveCurrentState: true)
parentStore.subscribe(observer: parentObserver, receiveCurrentState: true)
}
Expand Down Expand Up @@ -82,7 +85,8 @@ extension StoreNode where LocalState == State {

RootStoreNode<State, Action>(
initialState: initialState,
stateMapping: { _, state in return state },
stateMapping: { _, state in return state },
actionsMapping: .passthrough(),
parentStore: VoidStore<Action>(
queue: DispatchQueue(label: "com.puredux.store", qos: qos),
actionsInterceptor: ActionsInterceptor(
Expand All @@ -106,12 +110,15 @@ extension StoreNode: StoreProtocol {
}

var actionsInterceptor: ActionsInterceptor<Action>? {
parentStore.actionsInterceptor
localStore.actionsInterceptor
}

func syncDispatch(scopedAction: ScopedAction<Action>) {
localStore.syncDispatch(scopedAction: scopedAction)
parentStore.syncDispatch(scopedAction: scopedAction)
parentStore.syncDispatch(scopedAction: ScopedAction(
storeId: scopedAction.storeId,
action: actionsMapping.toGlobal(scopedAction.action)
))
}

func dispatch(_ action: Action) {
Expand Down
19 changes: 18 additions & 1 deletion Sources/Puredux/Store/Core/StoreProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ extension StoreProtocol {

StoreNode<Self, LocalState, ResultState, Action>(
initialState: initialState,
stateMapping: stateMapping,
stateMapping: stateMapping,
actionsMapping: .passthrough(),
parentStore: self,
reducer: reducer
)
Expand All @@ -73,8 +74,24 @@ extension StoreProtocol {
reducer: @escaping Reducer<LocalState, Action>) -> any StoreProtocol<ResultState, Action> {

StoreNode<Self, LocalState, ResultState, Action>(
initialState: initialState,
stateMapping: stateMapping,
actionsMapping: .passthrough(),
parentStore: self,
reducer: reducer
)
}

func createChildStore<LocalState, ResultState, LocalAction>(
initialState: LocalState,
stateMapping: @escaping (Self.State, LocalState) -> ResultState,
actionsMapping: ActionsMapping<Action, LocalAction>,
reducer: @escaping Reducer<LocalState, LocalAction>) -> any StoreProtocol<ResultState, LocalAction> {

StoreNode<Self, LocalState, ResultState, LocalAction>(
initialState: initialState,
stateMapping: stateMapping,
actionsMapping: actionsMapping,
parentStore: self,
reducer: reducer
)
Expand Down
33 changes: 31 additions & 2 deletions Sources/Puredux/Store/StateStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ extension StateStore {
initialState: initialState,
interceptor: interceptor,
qos: qos,
reducer: reducer)
reducer: reducer
)
}
}

Expand All @@ -50,7 +51,8 @@ extension StateStore {
func strongStore() -> Store<State, Action> {
Store<State, Action>(
dispatcher: dispatch,
subscribe: subscribe)
subscribe: subscribe
)
}

func createChildStore<LocalState, ResultState>(initialState: LocalState,
Expand All @@ -67,6 +69,21 @@ extension StateStore {
)
)
}

func createChildStore<LocalState, ResultState, LocalAction>(initialState: LocalState,
stateMapping: @escaping (State, LocalState) -> ResultState,
actionsMapping: ActionsMapping<Action, LocalAction>,
reducer: @escaping Reducer<LocalState, LocalAction>) -> StateStore<ResultState, LocalAction> {

StateStore<ResultState, LocalAction>(
storeObject: storeObject.createChildStore(
initialState: initialState,
stateMapping: stateMapping,
actionsMapping: actionsMapping,
reducer: reducer
)
)
}

func appending<T>(_ state: T,
qos: DispatchQoS = .userInitiated,
Expand All @@ -79,4 +96,16 @@ extension StateStore {
reducer: reducer
)
}

func appending<T, A>(_ state: T,
actionsMapping: ActionsMapping<Action, A>,
reducer: @escaping Reducer<T, A>) -> StateStore<(State, T), A> {

createChildStore(
initialState: state,
stateMapping: { ($0, $1) },
actionsMapping: actionsMapping,
reducer: reducer
)
}
}
25 changes: 25 additions & 0 deletions Sources/Puredux/Store/StoreActionsTransformations.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// File.swift
//
//
// Created by Sergey Kazakov on 12/04/2024.
//

import Foundation

extension Store {

func map<A>(_ transform: @escaping (A) -> Action) -> Store<State, A> {
Store<State, A>(
dispatcher: { action in dispatch(transform(action)) },
subscribe: subscribe
)
}
}

extension StateStore {

func map<A>(_ transform: @escaping (A) -> Action) -> Store<State, A> {
strongStore().map(transform)
}
}
93 changes: 93 additions & 0 deletions Sources/Puredux/Store/StoreStateTransformations.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//
// File.swift
//
//
// Created by Sergey Kazakov on 04/04/2024.
//

import Foundation

extension Store {
func map<T>(_ transform: @escaping (State) -> T) -> Store<T, Action> {
Store<T, Action>(
dispatcher: dispatch,
subscribe: { localStateObserver in
subscribe(observer: Observer<State>(id: localStateObserver.id) { state, complete in
let localState = transform(state)
localStateObserver.send(localState, complete: complete)
})
}
)
}

func compactMap<T>(_ transform: @escaping (State) -> T?) -> Store<T, Action> {
Store<T, Action>(
dispatcher: dispatch,
subscribe: { localStateObserver in
subscribe(observer: Observer<State>(id: localStateObserver.id) { state, complete in
guard let localState = transform(state) else {
complete(.active)
return
}

localStateObserver.send(localState, complete: complete)
})
}
)
}

func flatMap<T>(_ transform: @escaping (State) -> T?) -> Store<T?, Action> {
Store<T?, Action>(
dispatcher: dispatch,
subscribe: { localStateObserver in
subscribe(observer: Observer<State>(id: localStateObserver.id) { state, complete in
let localState = transform(state)
localStateObserver.send(localState, complete: complete)
})
}
)
}
}

extension Store {
func map<T>(_ keyPath: KeyPath<State, T>) -> Store<T, Action> {
map { $0[keyPath: keyPath] }
}

func compactMap<T>(_ keyPath: KeyPath<State, T?>) -> Store<T, Action> {
compactMap { $0[keyPath: keyPath] }
}

func flatMap<T>(_ keyPath: KeyPath<State, T?>) -> Store<T?, Action> {
flatMap { $0[keyPath: keyPath] }
}
}

extension StateStore {
func map<T>(_ keyPath: KeyPath<State, T>) -> Store<T, Action> {
strongStore().map(keyPath)
}

func compactMap<T>(_ keyPath: KeyPath<State, T?>) -> Store<T, Action> {
strongStore().compactMap(keyPath)
}

func flatMap<T>(_ keyPath: KeyPath<State, T?>) -> Store<T?, Action> {
strongStore().flatMap(keyPath)
}
}

extension StateStore {
func map<T>(_ transform: @escaping (State) -> T) -> Store<T, Action> {
strongStore().map(transform)
}


func compactMap<T>(_ transform: @escaping (State) -> T?) -> Store<T, Action> {
strongStore().compactMap(transform)
}

func flatMap<T>(_ transform: @escaping (State) -> T?) -> Store<T?, Action> {
strongStore().flatMap(transform)
}
}
Loading

0 comments on commit 1cd2e87

Please sign in to comment.