Herbert Vojčík 8 years ago
parent
commit
35bf847019
3 changed files with 556 additions and 1 deletions
  1. 190 0
      README.md
  2. 279 1
      src/Sedux.js
  3. 87 0
      src/Sedux.st

+ 190 - 0
README.md

@@ -1,2 +1,192 @@
 # sedux
 Redux-inspired framework for (Amber) Smalltalk.
+
+## Mappings of Redux parts to Smalltalk
+
+While original JS types are in flux notation, Smalltalk types are just pseudo-schema. 
+
+###State
+
+    type State = any
+
+State (also called the state tree) is a broad term, but in the Redux API it usually refers to the single state value that is managed by the store and returned by getState(). It represents the entire state of a Redux application, which is often a deeply nested object.
+
+By convention, the top-level state is an object or some other key-value collection like a Map, but technically it can be any type. Still, you should do your best to keep the state serializable. Don’t put anything inside it that you can’t easily turn into JSON.
+
+In Smalltalk:
+
+    type State = >> perform: <ActionType> withArguments: <Payload>
+
+###Action
+
+    type Action = Object
+
+An action is a plain object that represents an intention to change the state. Actions are the only way to get data into the store. Any data, whether from UI events, network callbacks, or other sources such as WebSockets needs to eventually be dispatched as actions.
+
+Actions must have a type field that indicates the type of action being performed. Types can be defined as constants and imported from another module. It’s better to use strings for type than Symbols because strings are serializable.
+
+Other than type, the structure of an action object is really up to you. If you’re interested, check out Flux Standard Action for recommendations on how actions should be constructed.
+
+See also async action below.
+
+In Smalltalk:
+
+    type Action = >> sendTo: <Dispatch> => <self>, >> selector => <ActionType> [default: Message]
+    type ActionType = Symbol | any
+    type Payload = Array | any
+    Sedux class >> dispatch: anAction with: aReceiver fallback: aBlock ^ (aReceiver respondsTo: anAction selector) ifTrue: [ anAction sendTo: aReceiver ] ifFalse: aBlock
+
+###Reducer
+
+    type Reducer<S, A> = (state: S, action: A) => S
+
+A reducer (also called a reducing function) is a function that accepts an accumulation and a value and returns a new accumulation. They are used to reduce a collection of values down to a single value.
+
+Reducers are not unique to Redux—they are a fundamental concept in functional programming. Even most non-functional languages, like JavaScript, have a built-in API for reducing. In JavaScript, it's Array.prototype.reduce().
+
+In Redux, the accumulated value is the state object, and the values being accumulated are actions. Reducers calculate a new state given the previous state and an action. They must be pure functions—functions that return the exact same output for given inputs. They should also be free of side-effects. This is what enables exciting features like hot reloading and time travel.
+
+Reducers are the most important concept in Redux.
+
+Do not put API calls into reducers.
+
+In Smalltalk:
+
+    type Reducer<S, A> = >> value:<S>value:<A> => <S | self> [trivial: BlockClosure; nontrivial: Reducer class subclass]
+    Reducer class >> value: aState value: anAction ^ Sedux dispatch: anAction with: (self on: aState) fallback: [ self ]
+    Reducer class >> on: aState ^ self new seduxState: aState; yourself
+    Reducer >> seduxState: aState state := aState
+
+###Dispatching Function
+
+    type BaseDispatch = (a: Action) => Action
+    type Dispatch = (a: Action | AsyncAction) => any
+
+A dispatching function (or simply dispatch function) is a function that accepts an action or an async action; it then may or may not dispatch one or more actions to the store.
+
+We must distinguish between dispatching functions in general and the base dispatch function provided by the store instance without any middleware.
+
+The base dispatch function always synchronously sends an action to the store’s reducer, along with the previous state returned by the store, to calculate a new state. It expects actions to be plain objects ready to be consumed by the reducer.
+
+Middleware wraps the base dispatch function. It allows the dispatch function to handle async actions in addition to actions. Middleware may transform, delay, ignore, or otherwise interpret actions or async actions before passing them to the next middleware. See below for more information.
+
+In Smalltalk: is not a function, but an object that is sent the Action (eg. a message). There is no BaseDispatch.
+
+    type Dispatch = >> respondsTo: <ActionType>, >> perform: <ActionType> withArguments: <Payload>
+
+###Action Creator
+
+    type ActionCreator = (...args: any) => Action | AsyncAction
+
+An action creator is, quite simply, a function that creates an action. Do not confuse the two terms—again, an action is a payload of information, and an action creator is a factory that creates an action.
+
+Calling an action creator only produces an action, but does not dispatch it. You need to call the store’s dispatch function to actually cause the mutation. Sometimes we say bound action creators to mean functions that call an action creator and immediately dispatch its result to a specific store instance.
+
+If an action creator needs to read the current state, perform an API call, or cause a side effect, like a routing transition, it should return an async action instead of an action.
+
+In Smalltalk: there is no action creator. There is AutoDispatch that catches the message (that is, an Action) in `doesNotUnderstand:` and that is how Action is created and dispatched at the same time. If you want to create Action to save and dispatch later, create `Message` by hand, and dispatch it by `action sendTo: dispatch`.
+
+    type AutoDispatch = SeduxAutoDispatch
+    ProtoObject subclass: #SeduxAutoDispatch.
+    SeduxAutoDispatch >> doesNotUnderstand: aMessage "check if selector is safe (does not start with 'sedux')" ^ store dispatch: aMessage
+
+###Async Action
+
+    type AsyncAction = any
+
+An async action is a value that is sent to a dispatching function, but is not yet ready for consumption by the reducer. It will be transformed by middleware into an action (or a series of actions) before being sent to the base dispatch() function. Async actions may have different types, depending on the middleware you use. They are often asynchronous primitives, like a Promise or a thunk, which are not passed to the reducer immediately, but trigger action dispatches once an operation has completed.
+
+In Smalltalk: there is no specific Async Action. `Action` represents any action; it is properly decorated Dispatch that can take care of non-synchronous Action.
+
+###Middleware
+
+    type MiddlewareAPI = { dispatch: Dispatch, getState: () => State }
+    type Middleware = (api: MiddlewareAPI) => (next: Dispatch) => Dispatch
+
+A middleware is a higher-order function that composes a dispatch function to return a new dispatch function. It often turns async actions into actions.
+
+Middleware is composable using function composition. It is useful for logging actions, performing side effects like routing, or turning an asynchronous API call into a series of synchronous actions.
+
+See applyMiddleware(...middlewares) for a detailed look at middleware.
+
+In Smalltalk: There is none. Just use Store Enhancer.
+
+###Store
+
+    type Store = {
+      dispatch: Dispatch
+      getState: () => State
+      subscribe: (listener: () => void) => () => void
+      replaceReducer: (reducer: Reducer) => void
+    }
+
+A store is an object that holds the application’s state tree.
+There should only be a single store in a Redux app, as the composition happens on the reducer level.
+
+dispatch(action) is the base dispatch function described above.
+getState() returns the current state of the store.
+subscribe(listener) registers a function to be called on state changes.
+replaceReducer(nextReducer) can be used to implement hot reloading and code splitting. Most likely you won’t use it.
+See the complete store API reference for more details.
+
+In Smalltalk:
+
+    type Store = >> dispatch => AutoDispatch, >> dispatch: <Action>, >> state => <State>
+
+Base store is `SeduxStore`, decorated ones are `SeduxDecoratedStore`.
+
+###Store creator
+
+    type StoreCreator = (reducer: Reducer, initialState: ?State) => Store
+
+A store creator is a function that creates a Redux store. Like with dispatching function, we must distinguish the base store creator, createStore(reducer, initialState) exported from the Redux package, from store creators that are returned from the store enhancers.
+
+In Smalltalk: not a function, but an object that is sent `inject:into:`. It can be a class which thereby creates a new instance, or an instance of some higher order creator. Both should subclass `StoreCreator` as it has the convenience `inject:` without into: part as well as `*` wrapper with StoreEnhancers.
+
+    SeduxStoreCreator class >> inject: anObject ^ self inject: anObject into: nil
+    SeduxStoreCreator >> inject: anObject ^ self inject: anObject into: nil
+    SeduxStoreCreator class >> << aStoreEnhancer ^ aStoreEnhancer next: self
+    SeduxStoreCreator >> << aStoreEnhancer ^ aStoreEnhancer next: self
+    type StoreCreator = SeduxStoreCreator subclass: | SeduxStoreCreator class subclass:, >> inject: <Reducer> into: <State> => Store, >> << <StoreEnhancer> => StoreCreator
+    StoreCreator subclass: #Sedux.
+    Sedux class >> inject: aReducer into: anObject "Creates a store, dispatches initiating message" 
+
+###Store enhancer
+
+    type StoreEnhancer = (next: StoreCreator) => StoreCreator
+
+A store enhancer is a higher-order function that composes a store creator to return a new, enhanced store creator. This is similar to middleware in that it allows you to alter the store interface in a composable way.
+
+Store enhancers are much the same concept as higher-order components in React, which are also occasionally called “component enhancers”.
+
+Because a store is not an instance, but rather a plain-object collection of functions, copies can be easily created and modified without mutating the original store. There is an example in compose documentation demonstrating that.
+
+Most likely you’ll never write a store enhancer, but you may use the one provided by the developer tools. It is what makes time travel possible without the app being aware it is happening. Amusingly, the Redux middleware implementation is itself a store enhancer.
+
+In Smalltalk: not a function, but an object.
+
+    type StoreEnhancer = >> next: <StoreCreator> => StoreCreator [default: SeduxDecorator class subclass]
+
+###Smalltalk solutions
+
+As Smalltalk does not have higher order functions easily and is class-based, there is `SeduxDecorator` which plays three roles: Dispatch (instance side), StoreCreator (class side) and StoreEnhancer (class side, by returning `self`). The latter is, of course, not reusable - you can only use a class once as a StoreEnhancer - but the class-based solution, if you want to reuse, is to create dedicated subclasses just for the purpose of using them as distinct StoreEnhancers.
+
+    SeduxDecorator class >> next: aStoreCreator next := aStoreCreator. ^self
+    SeduxDecorator class >> inject: aReducer into: anObject ^ SeduxDecoratedStore dispatch: self new next: (self nextInject: aReducer into: anObject)
+    SeduxDecorator class >> nextInject: aReducer into: anObject ^ next inject: aReducer into: anObject
+
+The `SeduxDecoratedStore` wraps another Store and forwards all sedux messages to it, except `dispatch:`.
+ 
+     SeduxDecoratedStore >> dispatch: anAction ^ Sedux dispatch: anAction with: dispatch fallback: [ ^ next dispatch: anAction ]
+
+So, a store is created as
+
+    store := (Sedux << SomeEnhancer << SomeOtherEnhancer << ....) inject: reducer
+
+and dispatcher can be obtained by
+
+    dispatch := store dispatch
+
+Then, dispatching actions is done via
+
+    dispatch keyword: 'foo' another: 'bar'

+ 279 - 1
src/Sedux.js

@@ -1,6 +1,284 @@
-define("sedux/Sedux", ["amber/boot"], function($boot){"use strict";
+define("sedux/Sedux", ["amber/boot", "amber_core/Kernel-Objects"], function($boot){"use strict";
 var $core=$boot.api,nil=$boot.nil,$recv=$boot.asReceiver,$globals=$boot.globals;
 $core.addPackage('Sedux');
 $core.packages["Sedux"].innerEval = function (expr) { return eval(expr); };
 $core.packages["Sedux"].transport = {"type":"amd","amdNamespace":"sedux"};
+
+$core.addClass('SeduxCreatorClass', $globals.Object, [], 'Sedux');
+
+$core.addMethod(
+$core.method({
+selector: "<<",
+protocol: 'as yet unclassified',
+fn: function (aStoreEnhancer){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+return $recv(aStoreEnhancer)._next_(self);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"<<",{aStoreEnhancer:aStoreEnhancer},$globals.SeduxCreatorClass.klass)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aStoreEnhancer"],
+source: "<< aStoreEnhancer\x0a\x09^ aStoreEnhancer next: self",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["next:"]
+}),
+$globals.SeduxCreatorClass.klass);
+
+$core.addMethod(
+$core.method({
+selector: "inject:",
+protocol: 'as yet unclassified',
+fn: function (anObject){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+return self._inject_into_(anObject,nil);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"inject:",{anObject:anObject},$globals.SeduxCreatorClass.klass)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anObject"],
+source: "inject: anObject\x0a\x09^ self inject: anObject into: nil",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["inject:into:"]
+}),
+$globals.SeduxCreatorClass.klass);
+
+
+$core.addClass('Sedux', $globals.SeduxCreatorClass, [], 'Sedux');
+
+$core.addMethod(
+$core.method({
+selector: "dispatch:with:fallback:",
+protocol: 'as yet unclassified',
+fn: function (anAction,aReceiver,aBlock){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+$1=$recv(aReceiver)._respondsTo_($recv(anAction)._selector());
+return $recv($1)._ifTrue_ifFalse_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $recv(anAction)._sendTo_(aReceiver);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)});
+//>>excludeEnd("ctx");
+}),aBlock);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"dispatch:with:fallback:",{anAction:anAction,aReceiver:aReceiver,aBlock:aBlock},$globals.Sedux.klass)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anAction", "aReceiver", "aBlock"],
+source: "dispatch: anAction with: aReceiver fallback: aBlock\x0a\x09^ (aReceiver respondsTo: anAction selector)\x0a\x09\x09ifTrue: [ anAction sendTo: aReceiver ]\x0a\x09\x09ifFalse: aBlock",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["ifTrue:ifFalse:", "respondsTo:", "selector", "sendTo:"]
+}),
+$globals.Sedux.klass);
+
+$core.addMethod(
+$core.method({
+selector: "inject:into:",
+protocol: 'as yet unclassified',
+fn: function (aReducer,anObject){
+var self=this;
+return self;
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aReducer", "anObject"],
+source: "inject: aReducer into: anObject\x0a\x09\x22Creates a store, dispatches initiating message\x22",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.Sedux.klass);
+
+
+$core.addClass('SeduxDecorator', $globals.SeduxCreatorClass, ['next'], 'Sedux');
+$core.addMethod(
+$core.method({
+selector: "next:",
+protocol: 'accessing',
+fn: function (anObject){
+var self=this;
+self["@next"]=anObject;
+return self;
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anObject"],
+source: "next: anObject\x0a\x09next := anObject",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.SeduxDecorator);
+
+
+$globals.SeduxDecorator.klass.iVarNames = ['next'];
+$core.addMethod(
+$core.method({
+selector: "inject:into:",
+protocol: 'as yet unclassified',
+fn: function (aReducer,anObject){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+return $recv($globals.SeduxDecoratedStore)._dispatch_next_(self._new(),self._nextInject_into_(aReducer,anObject));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"inject:into:",{aReducer:aReducer,anObject:anObject},$globals.SeduxDecorator.klass)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aReducer", "anObject"],
+source: "inject: aReducer into: anObject\x0a\x09^ SeduxDecoratedStore dispatch: self new next: (self nextInject: aReducer into: anObject)",
+referencedClasses: ["SeduxDecoratedStore"],
+//>>excludeEnd("ide");
+messageSends: ["dispatch:next:", "new", "nextInject:into:"]
+}),
+$globals.SeduxDecorator.klass);
+
+$core.addMethod(
+$core.method({
+selector: "next:",
+protocol: 'as yet unclassified',
+fn: function (aStoreCreator){
+var self=this;
+self["@next"]=aStoreCreator;
+return self;
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aStoreCreator"],
+source: "next: aStoreCreator\x0a\x09next := aStoreCreator.\x0a\x09^self",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.SeduxDecorator.klass);
+
+$core.addMethod(
+$core.method({
+selector: "nextInject:into:",
+protocol: 'as yet unclassified',
+fn: function (aReducer,anObject){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+return $recv(self["@next"])._inject_into_(aReducer,anObject);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"nextInject:into:",{aReducer:aReducer,anObject:anObject},$globals.SeduxDecorator.klass)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aReducer", "anObject"],
+source: "nextInject: aReducer into: anObject\x0a\x09^ next inject: aReducer into: anObject",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["inject:into:"]
+}),
+$globals.SeduxDecorator.klass);
+
+
+$core.addClass('SeduxFoo', $globals.SeduxDecorator, [], 'Sedux');
+
+
+$core.addClass('SeduxDecoratedStore', $globals.Object, ['next', 'dispatch'], 'Sedux');
+$core.addMethod(
+$core.method({
+selector: "dispatch:",
+protocol: 'accessing',
+fn: function (anAction){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $early={};
+try {
+return $recv($globals.Sedux)._dispatch_with_fallback_(anAction,self["@dispatch"],(function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+throw $early=[$recv(self["@next"])._dispatch_(anAction)];
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"dispatch:",{anAction:anAction},$globals.SeduxDecoratedStore)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anAction"],
+source: "dispatch: anAction\x0a\x09^ Sedux\x0a\x09\x09dispatch: anAction\x0a\x09\x09with: dispatch\x0a\x09\x09fallback: [ ^ next dispatch: anAction ]",
+referencedClasses: ["Sedux"],
+//>>excludeEnd("ide");
+messageSends: ["dispatch:with:fallback:", "dispatch:"]
+}),
+$globals.SeduxDecoratedStore);
+
+$core.addMethod(
+$core.method({
+selector: "dispatch:next:",
+protocol: 'accessing',
+fn: function (aDispatcher,aStore){
+var self=this;
+self["@dispatch"]=aDispatcher;
+self["@next"]=aStore;
+return self;
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aDispatcher", "aStore"],
+source: "dispatch: aDispatcher next: aStore\x0a\x09dispatch := aDispatcher.\x0a\x09next := aStore",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.SeduxDecoratedStore);
+
+
+$core.addMethod(
+$core.method({
+selector: "dispatch:next:",
+protocol: 'instance creation',
+fn: function (aDispatcher,aStore){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+$1=self._new();
+$recv($1)._dispatch_next_(aDispatcher,aStore);
+return $recv($1)._yourself();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"dispatch:next:",{aDispatcher:aDispatcher,aStore:aStore},$globals.SeduxDecoratedStore.klass)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aDispatcher", "aStore"],
+source: "dispatch: aDispatcher next: aStore\x0a\x09^ self new\x0a\x09\x09dispatch: aDispatcher next: aStore;\x0a\x09\x09yourself",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["dispatch:next:", "new", "yourself"]
+}),
+$globals.SeduxDecoratedStore.klass);
+
 });

+ 87 - 0
src/Sedux.st

@@ -1 +1,88 @@
 Smalltalk createPackage: 'Sedux'!
+Object subclass: #SeduxCreatorClass
+	instanceVariableNames: ''
+	package: 'Sedux'!
+
+!SeduxCreatorClass class methodsFor: 'as yet unclassified'!
+
+<< aStoreEnhancer
+	^ aStoreEnhancer next: self
+!
+
+inject: anObject
+	^ self inject: anObject into: nil
+! !
+
+SeduxCreatorClass subclass: #Sedux
+	instanceVariableNames: ''
+	package: 'Sedux'!
+
+!Sedux class methodsFor: 'as yet unclassified'!
+
+dispatch: anAction with: aReceiver fallback: aBlock
+	^ (aReceiver respondsTo: anAction selector)
+		ifTrue: [ anAction sendTo: aReceiver ]
+		ifFalse: aBlock
+!
+
+inject: aReducer into: anObject
+	"Creates a store, dispatches initiating message"
+! !
+
+SeduxCreatorClass subclass: #SeduxDecorator
+	instanceVariableNames: 'next'
+	package: 'Sedux'!
+
+!SeduxDecorator methodsFor: 'accessing'!
+
+next: anObject
+	next := anObject
+! !
+
+SeduxDecorator class instanceVariableNames: 'next'!
+
+!SeduxDecorator class methodsFor: 'as yet unclassified'!
+
+inject: aReducer into: anObject
+	^ SeduxDecoratedStore dispatch: self new next: (self nextInject: aReducer into: anObject)
+!
+
+next: aStoreCreator
+	next := aStoreCreator.
+	^self
+!
+
+nextInject: aReducer into: anObject
+	^ next inject: aReducer into: anObject
+! !
+
+SeduxDecorator subclass: #SeduxFoo
+	instanceVariableNames: ''
+	package: 'Sedux'!
+
+Object subclass: #SeduxDecoratedStore
+	instanceVariableNames: 'next dispatch'
+	package: 'Sedux'!
+
+!SeduxDecoratedStore methodsFor: 'accessing'!
+
+dispatch: anAction
+	^ Sedux
+		dispatch: anAction
+		with: dispatch
+		fallback: [ ^ next dispatch: anAction ]
+!
+
+dispatch: aDispatcher next: aStore
+	dispatch := aDispatcher.
+	next := aStore
+! !
+
+!SeduxDecoratedStore class methodsFor: 'instance creation'!
+
+dispatch: aDispatcher next: aStore
+	^ self new
+		dispatch: aDispatcher next: aStore;
+		yourself
+! !
+