-
Notifications
You must be signed in to change notification settings - Fork 27
Description
This is a proposal for a breaking change to Satchel — this would be Satchel v5. The goal here is to get rid of the two great evils in the existing implementation: global objects and side effects.
Motivation
Today Satchel's internal state — including stores, subscriptions, middleware, etc. — is stored in a global object hanging off the window. Mutators and orchestrators are registered by applying the mutator and orchestrator "decorators" to the handlers. (These APIs aren't really decorators — typically a decorator would just wrap the function in a higher order function — because they aren't wrapping the function but rather registering the function as a callback in the Satchel dispatcher.) This makes for very convenient coding patterns, but it presents some problems:
- If there are multiple instances of Satchel on a page — for instance if some imported library or component also uses Satchel — then there may be conflicts within the store or configuration.
- Because mutators and orchestrators are registered as side effects (the registration happens as part of code that runs at module scope) tools like Webpack have trouble applying optimizations like tree shaking to Satchel projects.
Proposal
Create satchel instances with createSatchel
Instead of assuming there's a global store available, consumers will create an instance of a satchel. There can be multiple satchels, configured separately and completely isolated in terms of data and subscribers — but in practice a codebase should only ever create one.
import { createSatchel } from 'satcheljs';
const satchel = createSatchel(options);The options would include the middleware, replacing the applyMiddleware API. (I don't think there are any other options at this point.)
Explicitly register subscribers
Subscribers (mutators and orchestrators) must be explicitly registered on the satchel. Now mutator and orchestrator will be true decorators — they'll just wrap the handler in an object to be registered separately.
// myMutator.ts
import { mutator } from 'satcheljs';
import { someAction } from './someAction';
export const myMutator = mutator(someAction, message => {
// Mutate stuff here
});// initializeMutators.ts
import { mySatchel } from './mySatchel';
import { myMutator } from './myMutator';
export function initializeMutators() {
mySatchel.register(myMutator);
}The mutator and orchestrator APIs will simply return an object with some properties. (If we care to keep the properties private we might encapsulate them in a closure and return that.) The properties will include:
- The action to subscribe to
- The callback
- Whether it is a mutator or orchestrator (or, alternately, we could have separate
registerMutatorandregisterOrchestratorAPIs on the satchel, but I think justregisteris cleaner. We could also call itsubscribe— thoughts?) - A flag indicating whether it has already been registered. Registering the same subscriber subsequent times is no-op. (We don't want the same subscriber get registered multiple times.)
Other APIs
Now that satchel is instanced, various other APIs will need to be changed:
mutatorActionwill need to take in the satchel as an agument so that it knows where to register the mutatordispatchwill become a method on the satchelactionwill become a method on a satchel so that it knows where to dispatch the actiongetRootStorewill become a method on the satchel
Add a satchel.hasSubscribers(action) API
This will allow consumers to check if there are any subscribers for an action. While not strictly necessary, this will be useful for middleware that wants to know if the dispatched action is handled by anything.