feat: moving documentation to docsify
This commit is contained in:
commit
fa55762efc
0
docs/.nojekyll
Normal file
0
docs/.nojekyll
Normal file
233
docs/README.md
Normal file
233
docs/README.md
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
|
||||||
|
# What's Updux?
|
||||||
|
|
||||||
|
So, I'm a fan of [Redux](https://redux.js.org). Two days ago I discovered
|
||||||
|
[rematch](https://rematch.github.io/rematch) alonside a few other frameworks built atop Redux.
|
||||||
|
|
||||||
|
It has a couple of pretty good ideas that removes some of the
|
||||||
|
boilerplate. Keeping mutations and asynchronous effects close to the
|
||||||
|
reducer definition? Nice. Automatically infering the
|
||||||
|
actions from the said mutations and effects? Genius!
|
||||||
|
|
||||||
|
But it also enforces a flat hierarchy of reducers -- where
|
||||||
|
is the fun in that? And I'm also having a strong love for
|
||||||
|
[Updeep](https://github.com/substantial/updeep), so I want reducer state updates to leverage the heck out of it.
|
||||||
|
|
||||||
|
All that to say, say hello to `Updux`. Heavily inspired by `rematch`, but twisted
|
||||||
|
to work with `updeep` and to fit my peculiar needs. It offers features such as
|
||||||
|
|
||||||
|
* Mimic the way VueX has mutations (reducer reactions to specific actions) and
|
||||||
|
effects (middleware reacting to actions that can be asynchronous and/or
|
||||||
|
have side-effects), so everything pertaining to a store are all defined
|
||||||
|
in the space place.
|
||||||
|
* Automatically gather all actions used by the updux's effects and mutations,
|
||||||
|
and makes then accessible as attributes to the `dispatch` object of the
|
||||||
|
store.
|
||||||
|
* Mutations have a signature that is friendly to Updux and Immer.
|
||||||
|
* Also, the mutation signature auto-unwrap the payload of the actions for you.
|
||||||
|
* TypeScript types.
|
||||||
|
|
||||||
|
|
||||||
|
Fair warning: this package is still very new, probably very buggy,
|
||||||
|
definitively very badly documented, and very subject to changes. Caveat
|
||||||
|
Maxima Emptor.
|
||||||
|
|
||||||
|
# Synopsis
|
||||||
|
|
||||||
|
```
|
||||||
|
import updux from 'updux';
|
||||||
|
|
||||||
|
import otherUpdux from './otherUpdux';
|
||||||
|
|
||||||
|
const {
|
||||||
|
initial,
|
||||||
|
reducer,
|
||||||
|
actions,
|
||||||
|
middleware,
|
||||||
|
createStore,
|
||||||
|
} = new Updux({
|
||||||
|
initial: {
|
||||||
|
counter: 0,
|
||||||
|
},
|
||||||
|
subduxes: {
|
||||||
|
otherUpdux,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
inc: ( increment = 1 ) => u({counter: s => s + increment })
|
||||||
|
},
|
||||||
|
effects: {
|
||||||
|
'*' => api => next => action => {
|
||||||
|
console.log( "hey, look, an action zoomed by!", action );
|
||||||
|
next(action);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
customAction: ( someArg ) => ({
|
||||||
|
type: "custom",
|
||||||
|
payload: { someProp: someArg }
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = createStore();
|
||||||
|
|
||||||
|
store.dispatch.inc(3);
|
||||||
|
```
|
||||||
|
|
||||||
|
# Description
|
||||||
|
|
||||||
|
The formal documentation of the class Updux and its associated functions and
|
||||||
|
types can be found over [here](https://yanick.github.io/updux/docs/classes/updux.html).
|
||||||
|
|
||||||
|
## Exporting upduxes
|
||||||
|
|
||||||
|
If you are creating upduxes that will be used as subduxes
|
||||||
|
by other upduxes, or as
|
||||||
|
[ducks](https://github.com/erikras/ducks-modular-redux)-like containers, I
|
||||||
|
recommend that you export the Updux instance as the default export:
|
||||||
|
|
||||||
|
```
|
||||||
|
import Updux from 'updux';
|
||||||
|
|
||||||
|
const updux = new Updux({ ... });
|
||||||
|
|
||||||
|
export default updux;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Then you can use them as subduxes like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
import Updux from 'updux';
|
||||||
|
import foo from './foo'; // foo is an Updux
|
||||||
|
import bar from './bar'; // bar is an Updux as well
|
||||||
|
|
||||||
|
const updux = new Updux({
|
||||||
|
subduxes: {
|
||||||
|
foo, bar
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if you want to use it:
|
||||||
|
|
||||||
|
```
|
||||||
|
import updux from './myUpdux';
|
||||||
|
|
||||||
|
const {
|
||||||
|
reducer,
|
||||||
|
actions: { doTheThing },
|
||||||
|
createStore,
|
||||||
|
middleware,
|
||||||
|
} = updux;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mapping a mutation to all values of a state
|
||||||
|
|
||||||
|
Say you have a `todos` state that is an array of `todo` sub-states. It's easy
|
||||||
|
enough to have the main reducer maps away all items to the sub-reducer:
|
||||||
|
|
||||||
|
```
|
||||||
|
const todo = new Updux({
|
||||||
|
mutations: {
|
||||||
|
review: () => u({ reviewed: true}),
|
||||||
|
done: () => u({done: true}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const todos = new Updux({ initial: [] });
|
||||||
|
|
||||||
|
todos.addMutation(
|
||||||
|
todo.actions.review,
|
||||||
|
(_,action) => state => state.map( todo.upreducer(action) )
|
||||||
|
);
|
||||||
|
todos.addMutation(
|
||||||
|
todo.actions.done,
|
||||||
|
(id,action) => u.map(u.if(u.is('id',id), todo.upreducer(action))),
|
||||||
|
);
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
But `updeep` can iterate through all the items of an array (or the values of
|
||||||
|
an object) via the special key `*`. So the todos updux above could also be
|
||||||
|
written:
|
||||||
|
|
||||||
|
```
|
||||||
|
const todo = new Updux({
|
||||||
|
mutations: {
|
||||||
|
review: () => u({ reviewed: true}),
|
||||||
|
done: () => u({done: true}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const todos = new Updux({
|
||||||
|
subduxes: { '*': todo },
|
||||||
|
});
|
||||||
|
|
||||||
|
todos.addMutation(
|
||||||
|
todo.actions.done,
|
||||||
|
(id,action) => u.map(u.if(u.is('id',id), todo.upreducer(action))),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
The advantages being that the actions/mutations/effects of the subdux will be
|
||||||
|
imported by the root updux as usual, and all actions that aren't being
|
||||||
|
overridden by a sink mutation will trickle down automatically.
|
||||||
|
|
||||||
|
## Usage with Immer
|
||||||
|
|
||||||
|
While Updux was created with Updeep in mind, it also plays very
|
||||||
|
well with [Immer](https://immerjs.github.io/immer/docs/introduction).
|
||||||
|
|
||||||
|
For example, taking this basic updux:
|
||||||
|
|
||||||
|
```
|
||||||
|
import Updux from 'updux';
|
||||||
|
|
||||||
|
const updux = new Updux({
|
||||||
|
initial: { counter: 0 },
|
||||||
|
mutations: {
|
||||||
|
add: (inc=1) => state => { counter: counter + inc }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Converting it to Immer would look like:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
import Updux from 'updux';
|
||||||
|
import { produce } from 'Immer';
|
||||||
|
|
||||||
|
const updux = new Updux({
|
||||||
|
initial: { counter: 0 },
|
||||||
|
mutations: {
|
||||||
|
add: (inc=1) => produce( draft => draft.counter += inc ) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
But since typing `produce` over and over is no fun, `groomMutations`
|
||||||
|
can be used to wrap all mutations with it:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
import Updux from 'updux';
|
||||||
|
import { produce } from 'Immer';
|
||||||
|
|
||||||
|
const updux = new Updux({
|
||||||
|
initial: { counter: 0 },
|
||||||
|
groomMutations: mutation => (...args) => produce( mutation(...args) ),
|
||||||
|
mutations: {
|
||||||
|
add: (inc=1) => draft => draft.counter += inc
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
6
docs/_sidebar.md
Normal file
6
docs/_sidebar.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<!-- docs/_sidebar.md -->
|
||||||
|
|
||||||
|
* [Home](/)
|
||||||
|
* API Reference
|
||||||
|
* [Updux](updux.md)
|
||||||
|
* [Types](types.md)
|
442
docs/index.html
442
docs/index.html
@ -1,419 +1,23 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html class="default no-js">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<title>updux - Updeep-friendly Redux helper framework</title>
|
||||||
<title>updux</title>
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||||
<meta name="description" content="">
|
<meta name="description" content="Updeep-friendly Redux helper framework">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
<link rel="stylesheet" href="assets/css/main.css">
|
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<div id="app"></div>
|
||||||
<div class="tsd-page-toolbar">
|
<script>
|
||||||
<div class="container">
|
window.$docsify = {
|
||||||
<div class="table-wrap">
|
name: 'updux',
|
||||||
<div class="table-cell" id="tsd-search" data-index="assets/js/search.js" data-base=".">
|
repo: 'https://github.com/yanick/updux',
|
||||||
<div class="field">
|
loadSidebar: true,
|
||||||
<label for="tsd-search-field" class="tsd-widget search no-caption">Search</label>
|
subMaxLevel: 4,
|
||||||
<input id="tsd-search-field" type="text" />
|
}
|
||||||
</div>
|
</script>
|
||||||
<ul class="results">
|
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
|
||||||
<li class="state loading">Preparing search index...</li>
|
</body>
|
||||||
<li class="state failure">The search index is not available</li>
|
</html>
|
||||||
</ul>
|
|
||||||
<a href="index.html" class="title">updux</a>
|
|
||||||
</div>
|
|
||||||
<div class="table-cell" id="tsd-widgets">
|
|
||||||
<div id="tsd-filter">
|
|
||||||
<a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a>
|
|
||||||
<div class="tsd-filter-group">
|
|
||||||
<div class="tsd-select" id="tsd-filter-visibility">
|
|
||||||
<span class="tsd-select-label">All</span>
|
|
||||||
<ul class="tsd-select-list">
|
|
||||||
<li data-value="public">Public</li>
|
|
||||||
<li data-value="protected">Public/Protected</li>
|
|
||||||
<li data-value="private" class="selected">All</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<input type="checkbox" id="tsd-filter-inherited" checked />
|
|
||||||
<label class="tsd-widget" for="tsd-filter-inherited">Inherited</label>
|
|
||||||
<input type="checkbox" id="tsd-filter-externals" checked />
|
|
||||||
<label class="tsd-widget" for="tsd-filter-externals">Externals</label>
|
|
||||||
<input type="checkbox" id="tsd-filter-only-exported" />
|
|
||||||
<label class="tsd-widget" for="tsd-filter-only-exported">Only exported</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="tsd-page-title">
|
|
||||||
<div class="container">
|
|
||||||
<ul class="tsd-breadcrumb">
|
|
||||||
<li>
|
|
||||||
<a href="globals.html">Globals</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<h1> updux</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div class="container container-main">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-8 col-content">
|
|
||||||
<div class="tsd-panel tsd-typography">
|
|
||||||
<a href="#what39s-updux" id="what39s-updux" style="color: inherit; text-decoration: none;">
|
|
||||||
<h1>What's Updux?</h1>
|
|
||||||
</a>
|
|
||||||
<p>So, I'm a fan of <a href="https://redux.js.org">Redux</a>. Two days ago I discovered
|
|
||||||
<a href="https://rematch.github.io/rematch">rematch</a> alonside a few other frameworks built atop Redux. </p>
|
|
||||||
<p>It has a couple of pretty good ideas that removes some of the
|
|
||||||
boilerplate. Keeping mutations and asynchronous effects close to the
|
|
||||||
reducer definition? Nice. Automatically infering the
|
|
||||||
actions from the said mutations and effects? Genius!</p>
|
|
||||||
<p>But it also enforces a flat hierarchy of reducers -- where
|
|
||||||
is the fun in that? And I'm also having a strong love for
|
|
||||||
<a href="https://github.com/substantial/updeep">Updeep</a>, so I want reducer state updates to leverage the heck out of it.</p>
|
|
||||||
<p>All that to say, say hello to <code>Updux</code>. Heavily inspired by <code>rematch</code>, but twisted
|
|
||||||
to work with <code>updeep</code> and to fit my peculiar needs. It offers features such as</p>
|
|
||||||
<ul>
|
|
||||||
<li>Mimic the way VueX has mutations (reducer reactions to specific actions) and
|
|
||||||
effects (middleware reacting to actions that can be asynchronous and/or
|
|
||||||
have side-effects), so everything pertaining to a store are all defined
|
|
||||||
in the space place.</li>
|
|
||||||
<li>Automatically gather all actions used by the updux's effects and mutations,
|
|
||||||
and makes then accessible as attributes to the <code>dispatch</code> object of the
|
|
||||||
store.</li>
|
|
||||||
<li>Mutations have a signature that is friendly to Updux and Immer.</li>
|
|
||||||
<li>Also, the mutation signature auto-unwrap the payload of the actions for you.</li>
|
|
||||||
<li>TypeScript types.</li>
|
|
||||||
</ul>
|
|
||||||
<p>Fair warning: this package is still very new, probably very buggy,
|
|
||||||
definitively very badly documented, and very subject to changes. Caveat
|
|
||||||
Maxima Emptor.</p>
|
|
||||||
<a href="#synopsis" id="synopsis" style="color: inherit; text-decoration: none;">
|
|
||||||
<h1>Synopsis</h1>
|
|
||||||
</a>
|
|
||||||
<pre><code><span class="hljs-keyword">import</span> updux <span class="hljs-keyword">from</span> <span class="hljs-string">'updux'</span>;
|
|
||||||
|
|
||||||
<span class="hljs-keyword">import</span> otherUpdux <span class="hljs-keyword">from</span> <span class="hljs-string">'./otherUpdux'</span>;
|
|
||||||
|
|
||||||
<span class="hljs-keyword">const</span> {
|
|
||||||
initial,
|
|
||||||
reducer,
|
|
||||||
actions,
|
|
||||||
middleware,
|
|
||||||
createStore,
|
|
||||||
} = <span class="hljs-keyword">new</span> Updux({
|
|
||||||
<span class="hljs-attr">initial</span>: {
|
|
||||||
<span class="hljs-attr">counter</span>: <span class="hljs-number">0</span>,
|
|
||||||
},
|
|
||||||
<span class="hljs-attr">subduxes</span>: {
|
|
||||||
otherUpdux,
|
|
||||||
},
|
|
||||||
<span class="hljs-attr">mutations</span>: {
|
|
||||||
<span class="hljs-attr">inc</span>: <span class="hljs-function">(<span class="hljs-params"> increment = <span class="hljs-number">1</span> </span>) =></span> u({<span class="hljs-attr">counter</span>: <span class="hljs-function"><span class="hljs-params">s</span> =></span> s + increment })
|
|
||||||
},
|
|
||||||
<span class="hljs-attr">effects</span>: {
|
|
||||||
<span class="hljs-string">'*'</span> => <span class="hljs-function"><span class="hljs-params">api</span> =></span> <span class="hljs-function"><span class="hljs-params">next</span> =></span> <span class="hljs-function"><span class="hljs-params">action</span> =></span> {
|
|
||||||
<span class="hljs-built_in">console</span>.log( <span class="hljs-string">"hey, look, an action zoomed by!"</span>, action );
|
|
||||||
next(action);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
<span class="hljs-attr">actions</span>: {
|
|
||||||
<span class="hljs-attr">customAction</span>: <span class="hljs-function">(<span class="hljs-params"> someArg </span>) =></span> ({
|
|
||||||
<span class="hljs-attr">type</span>: <span class="hljs-string">"custom"</span>,
|
|
||||||
<span class="hljs-attr">payload</span>: { <span class="hljs-attr">someProp</span>: someArg }
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
<span class="hljs-keyword">const</span> store = createStore();
|
|
||||||
|
|
||||||
store.dispatch.inc(<span class="hljs-number">3</span>);</code></pre>
|
|
||||||
<a href="#description" id="description" style="color: inherit; text-decoration: none;">
|
|
||||||
<h1>Description</h1>
|
|
||||||
</a>
|
|
||||||
<p>The formal documentation of the class Updux and its associated functions and
|
|
||||||
types can be found over <a href="https://yanick.github.io/updux/docs/classes/updux.html">here</a>.</p>
|
|
||||||
<a href="#exporting-upduxes" id="exporting-upduxes" style="color: inherit; text-decoration: none;">
|
|
||||||
<h2>Exporting upduxes</h2>
|
|
||||||
</a>
|
|
||||||
<p>If you are creating upduxes that will be used as subduxes
|
|
||||||
by other upduxes, or as
|
|
||||||
<a href="https://github.com/erikras/ducks-modular-redux">ducks</a>-like containers, I
|
|
||||||
recommend that you export the Updux instance as the default export:</p>
|
|
||||||
<pre><code>import Updux <span class="hljs-keyword">from</span> <span class="hljs-string">'updux'</span>;
|
|
||||||
|
|
||||||
const updux = new Updux({ <span class="hljs-built_in">..</span>. });
|
|
||||||
|
|
||||||
<span class="hljs-builtin-name">export</span><span class="hljs-built_in"> default </span>updux;</code></pre><p>Then you can use them as subduxes like this:</p>
|
|
||||||
<pre><code><span class="hljs-keyword">import</span> Updux <span class="hljs-keyword">from</span> <span class="hljs-string">'updux'</span>;
|
|
||||||
<span class="hljs-keyword">import</span> foo <span class="hljs-keyword">from</span> <span class="hljs-string">'./foo'</span>; <span class="hljs-comment">// foo is an Updux</span>
|
|
||||||
<span class="hljs-keyword">import</span> bar <span class="hljs-keyword">from</span> <span class="hljs-string">'./bar'</span>; <span class="hljs-comment">// bar is an Updux as well</span>
|
|
||||||
|
|
||||||
<span class="hljs-keyword">const</span> updux = <span class="hljs-keyword">new</span> Updux({
|
|
||||||
<span class="hljs-attr">subduxes</span>: {
|
|
||||||
foo, bar
|
|
||||||
}
|
|
||||||
});</code></pre><p>Or if you want to use it:</p>
|
|
||||||
<pre><code><span class="hljs-keyword">import</span> updux <span class="hljs-keyword">from</span> <span class="hljs-string">'./myUpdux'</span>;
|
|
||||||
|
|
||||||
<span class="hljs-keyword">const</span> {
|
|
||||||
reducer,
|
|
||||||
<span class="hljs-attr">actions</span>: { doTheThing },
|
|
||||||
createStore,
|
|
||||||
middleware,
|
|
||||||
} = updux;</code></pre>
|
|
||||||
<a href="#mapping-a-mutation-to-all-values-of-a-state" id="mapping-a-mutation-to-all-values-of-a-state" style="color: inherit; text-decoration: none;">
|
|
||||||
<h2>Mapping a mutation to all values of a state</h2>
|
|
||||||
</a>
|
|
||||||
<p>Say you have a <code>todos</code> state that is an array of <code>todo</code> sub-states. It's easy
|
|
||||||
enough to have the main reducer maps away all items to the sub-reducer:</p>
|
|
||||||
<pre><code>const todo = <span class="hljs-keyword">new</span> Updux({
|
|
||||||
mutations: {
|
|
||||||
review: <span class="hljs-function"><span class="hljs-params">()</span> =></span> u({ reviewed: <span class="hljs-literal">true</span>}),
|
|
||||||
done: <span class="hljs-function"><span class="hljs-params">()</span> =></span> u({done: <span class="hljs-literal">true</span>}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const todos = <span class="hljs-keyword">new</span> Updux({ initial: [] });
|
|
||||||
|
|
||||||
todos.addMutation(
|
|
||||||
todo.actions.review,
|
|
||||||
<span class="hljs-function"><span class="hljs-params">(_,action)</span> =></span> state => state.map( todo.upreducer(action) )
|
|
||||||
);
|
|
||||||
todos.addMutation(
|
|
||||||
todo.actions.done,
|
|
||||||
<span class="hljs-function"><span class="hljs-params">(id,action)</span> =></span> u.map(u.<span class="hljs-keyword">if</span>(u.<span class="hljs-keyword">is</span>(<span class="hljs-string">'id'</span>,id), todo.upreducer(action))),
|
|
||||||
);
|
|
||||||
</code></pre><p>But <code>updeep</code> can iterate through all the items of an array (or the values of
|
|
||||||
an object) via the special key <code>*</code>. So the todos updux above could also be
|
|
||||||
written:</p>
|
|
||||||
<pre><code>const todo = <span class="hljs-keyword">new</span> Updux({
|
|
||||||
mutations: {
|
|
||||||
review: <span class="hljs-function"><span class="hljs-params">()</span> =></span> u({ reviewed: <span class="hljs-literal">true</span>}),
|
|
||||||
done: <span class="hljs-function"><span class="hljs-params">()</span> =></span> u({done: <span class="hljs-literal">true</span>}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const todos = <span class="hljs-keyword">new</span> Updux({
|
|
||||||
subduxes: { <span class="hljs-string">'*'</span>: todo },
|
|
||||||
});
|
|
||||||
|
|
||||||
todos.addMutation(
|
|
||||||
todo.actions.done,
|
|
||||||
<span class="hljs-function"><span class="hljs-params">(id,action)</span> =></span> u.map(u.<span class="hljs-keyword">if</span>(u.<span class="hljs-keyword">is</span>(<span class="hljs-string">'id'</span>,id), todo.upreducer(action))),
|
|
||||||
<span class="hljs-literal">true</span>
|
|
||||||
);</code></pre><p>The advantages being that the actions/mutations/effects of the subdux will be
|
|
||||||
imported by the root updux as usual, and all actions that aren't being
|
|
||||||
overridden by a sink mutation will trickle down automatically.</p>
|
|
||||||
<a href="#usage-with-immer" id="usage-with-immer" style="color: inherit; text-decoration: none;">
|
|
||||||
<h2>Usage with Immer</h2>
|
|
||||||
</a>
|
|
||||||
<p>While Updux was created with Updeep in mind, it also plays very
|
|
||||||
well with <a href="https://immerjs.github.io/immer/docs/introduction">Immer</a>.</p>
|
|
||||||
<p>For example, taking this basic updux:</p>
|
|
||||||
<pre><code><span class="hljs-keyword">import</span> Updux <span class="hljs-keyword">from</span> <span class="hljs-string">'updux'</span>;
|
|
||||||
|
|
||||||
<span class="hljs-keyword">const</span> updux = <span class="hljs-keyword">new</span> Updux({
|
|
||||||
<span class="hljs-attr">initial</span>: { <span class="hljs-attr">counter</span>: <span class="hljs-number">0</span> },
|
|
||||||
<span class="hljs-attr">mutations</span>: {
|
|
||||||
<span class="hljs-attr">add</span>: <span class="hljs-function">(<span class="hljs-params">inc=<span class="hljs-number">1</span></span>) =></span> <span class="hljs-function"><span class="hljs-params">state</span> =></span> { <span class="hljs-attr">counter</span>: counter + inc }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</code></pre><p>Converting it to Immer would look like:</p>
|
|
||||||
<pre><code><span class="hljs-keyword">import</span> Updux <span class="hljs-keyword">from</span> <span class="hljs-string">'updux'</span>;
|
|
||||||
<span class="hljs-keyword">import</span> { produce } <span class="hljs-keyword">from</span> <span class="hljs-string">'Immer'</span>;
|
|
||||||
|
|
||||||
<span class="hljs-keyword">const</span> updux = <span class="hljs-keyword">new</span> Updux({
|
|
||||||
<span class="hljs-attr">initial</span>: { <span class="hljs-attr">counter</span>: <span class="hljs-number">0</span> },
|
|
||||||
<span class="hljs-attr">mutations</span>: {
|
|
||||||
<span class="hljs-attr">add</span>: <span class="hljs-function">(<span class="hljs-params">inc=<span class="hljs-number">1</span></span>) =></span> produce( <span class="hljs-function"><span class="hljs-params">draft</span> =></span> draft.counter += inc ) }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</code></pre><p>But since typing <code>produce</code> over and over is no fun, <code>groomMutations</code>
|
|
||||||
can be used to wrap all mutations with it:</p>
|
|
||||||
<pre><code><span class="hljs-keyword">import</span> Updux <span class="hljs-keyword">from</span> <span class="hljs-string">'updux'</span>;
|
|
||||||
<span class="hljs-keyword">import</span> { produce } <span class="hljs-keyword">from</span> <span class="hljs-string">'Immer'</span>;
|
|
||||||
|
|
||||||
<span class="hljs-keyword">const</span> updux = <span class="hljs-keyword">new</span> Updux({
|
|
||||||
<span class="hljs-attr">initial</span>: { <span class="hljs-attr">counter</span>: <span class="hljs-number">0</span> },
|
|
||||||
<span class="hljs-attr">groomMutations</span>: <span class="hljs-function"><span class="hljs-params">mutation</span> =></span> <span class="hljs-function">(<span class="hljs-params">...args</span>) =></span> produce( mutation(...args) ),
|
|
||||||
<span class="hljs-attr">mutations</span>: {
|
|
||||||
<span class="hljs-attr">add</span>: <span class="hljs-function">(<span class="hljs-params">inc=<span class="hljs-number">1</span></span>) =></span> <span class="hljs-function"><span class="hljs-params">draft</span> =></span> draft.counter += inc
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</code></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-4 col-menu menu-sticky-wrap menu-highlight">
|
|
||||||
<nav class="tsd-navigation primary">
|
|
||||||
<ul>
|
|
||||||
<li class="globals ">
|
|
||||||
<a href="globals.html"><em>Globals</em></a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<nav class="tsd-navigation secondary menu-sticky">
|
|
||||||
<ul class="before-current">
|
|
||||||
<li class=" tsd-kind-class tsd-has-type-parameter">
|
|
||||||
<a href="classes/updux.html" class="tsd-kind-icon">Updux</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-interface tsd-has-type-parameter">
|
|
||||||
<a href="interfaces/upduxmiddlewareapi.html" class="tsd-kind-icon">Updux<wbr>MiddlewareAPI</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-type-alias tsd-has-type-parameter">
|
|
||||||
<a href="globals.html#action" class="tsd-kind-icon">Action</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-type-alias tsd-has-type-parameter">
|
|
||||||
<a href="globals.html#actioncreator" class="tsd-kind-icon">Action<wbr>Creator</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-type-alias tsd-is-not-exported">
|
|
||||||
<a href="globals.html#actionpair" class="tsd-kind-icon">Action<wbr>Pair</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-type-alias">
|
|
||||||
<a href="globals.html#actionpayloadgenerator" class="tsd-kind-icon">Action<wbr>Payload<wbr>Generator</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-type-alias tsd-has-type-parameter">
|
|
||||||
<a href="globals.html#dictionary" class="tsd-kind-icon">Dictionary</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-type-alias tsd-has-type-parameter">
|
|
||||||
<a href="globals.html#dux" class="tsd-kind-icon">Dux</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-type-alias tsd-has-type-parameter tsd-is-not-exported">
|
|
||||||
<a href="globals.html#maybepayload" class="tsd-kind-icon">Maybe<wbr>Payload</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-type-alias tsd-has-type-parameter">
|
|
||||||
<a href="globals.html#mutation" class="tsd-kind-icon">Mutation</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-type-alias tsd-is-not-exported">
|
|
||||||
<a href="globals.html#next" class="tsd-kind-icon">Next</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-type-alias tsd-has-type-parameter tsd-is-not-exported">
|
|
||||||
<a href="globals.html#storewithdispatchactions" class="tsd-kind-icon">Store<wbr>With<wbr>Dispatch<wbr>Actions</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-type-alias tsd-is-not-exported">
|
|
||||||
<a href="globals.html#submutations" class="tsd-kind-icon">Sub<wbr>Mutations</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-type-alias tsd-has-type-parameter">
|
|
||||||
<a href="globals.html#upduxconfig" class="tsd-kind-icon">Updux<wbr>Config</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-type-alias">
|
|
||||||
<a href="globals.html#upduxdispatch" class="tsd-kind-icon">Updux<wbr>Dispatch</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-type-alias tsd-has-type-parameter">
|
|
||||||
<a href="globals.html#upduxmiddleware" class="tsd-kind-icon">Updux<wbr>Middleware</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-type-alias tsd-has-type-parameter">
|
|
||||||
<a href="globals.html#upreducer" class="tsd-kind-icon">Upreducer</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-function tsd-is-not-exported">
|
|
||||||
<a href="globals.html#middlewarefor" class="tsd-kind-icon">Middleware<wbr>For</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-function tsd-has-type-parameter">
|
|
||||||
<a href="globals.html#actioncreator" class="tsd-kind-icon">action<wbr>Creator</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-function tsd-is-not-exported">
|
|
||||||
<a href="globals.html#actionfor" class="tsd-kind-icon">action<wbr>For</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-function">
|
|
||||||
<a href="globals.html#buildactions" class="tsd-kind-icon">build<wbr>Actions</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-function tsd-has-type-parameter">
|
|
||||||
<a href="globals.html#buildcreatestore" class="tsd-kind-icon">build<wbr>Create<wbr>Store</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-function tsd-has-type-parameter">
|
|
||||||
<a href="globals.html#buildinitial" class="tsd-kind-icon">build<wbr>Initial</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-function tsd-has-type-parameter">
|
|
||||||
<a href="globals.html#buildmiddleware" class="tsd-kind-icon">build<wbr>Middleware</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-function">
|
|
||||||
<a href="globals.html#buildmutations" class="tsd-kind-icon">build<wbr>Mutations</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-function tsd-has-type-parameter">
|
|
||||||
<a href="globals.html#buildupreducer" class="tsd-kind-icon">build<wbr>Upreducer</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-function tsd-is-not-exported">
|
|
||||||
<a href="globals.html#composemutations" class="tsd-kind-icon">compose<wbr>Mutations</a>
|
|
||||||
</li>
|
|
||||||
<li class=" tsd-kind-function tsd-is-not-exported">
|
|
||||||
<a href="globals.html#slicemw" class="tsd-kind-icon">slice<wbr>Mw</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<footer class="with-border-bottom">
|
|
||||||
<div class="container">
|
|
||||||
<h2>Legend</h2>
|
|
||||||
<div class="tsd-legend-group">
|
|
||||||
<ul class="tsd-legend">
|
|
||||||
<li class="tsd-kind-module"><span class="tsd-kind-icon">Module</span></li>
|
|
||||||
<li class="tsd-kind-object-literal"><span class="tsd-kind-icon">Object literal</span></li>
|
|
||||||
<li class="tsd-kind-variable"><span class="tsd-kind-icon">Variable</span></li>
|
|
||||||
<li class="tsd-kind-function"><span class="tsd-kind-icon">Function</span></li>
|
|
||||||
<li class="tsd-kind-function tsd-has-type-parameter"><span class="tsd-kind-icon">Function with type parameter</span></li>
|
|
||||||
<li class="tsd-kind-index-signature"><span class="tsd-kind-icon">Index signature</span></li>
|
|
||||||
<li class="tsd-kind-type-alias"><span class="tsd-kind-icon">Type alias</span></li>
|
|
||||||
<li class="tsd-kind-type-alias tsd-has-type-parameter"><span class="tsd-kind-icon">Type alias with type parameter</span></li>
|
|
||||||
</ul>
|
|
||||||
<ul class="tsd-legend">
|
|
||||||
<li class="tsd-kind-enum"><span class="tsd-kind-icon">Enumeration</span></li>
|
|
||||||
<li class="tsd-kind-enum-member"><span class="tsd-kind-icon">Enumeration member</span></li>
|
|
||||||
<li class="tsd-kind-property tsd-parent-kind-enum"><span class="tsd-kind-icon">Property</span></li>
|
|
||||||
<li class="tsd-kind-method tsd-parent-kind-enum"><span class="tsd-kind-icon">Method</span></li>
|
|
||||||
</ul>
|
|
||||||
<ul class="tsd-legend">
|
|
||||||
<li class="tsd-kind-interface"><span class="tsd-kind-icon">Interface</span></li>
|
|
||||||
<li class="tsd-kind-interface tsd-has-type-parameter"><span class="tsd-kind-icon">Interface with type parameter</span></li>
|
|
||||||
<li class="tsd-kind-constructor tsd-parent-kind-interface"><span class="tsd-kind-icon">Constructor</span></li>
|
|
||||||
<li class="tsd-kind-property tsd-parent-kind-interface"><span class="tsd-kind-icon">Property</span></li>
|
|
||||||
<li class="tsd-kind-method tsd-parent-kind-interface"><span class="tsd-kind-icon">Method</span></li>
|
|
||||||
<li class="tsd-kind-index-signature tsd-parent-kind-interface"><span class="tsd-kind-icon">Index signature</span></li>
|
|
||||||
</ul>
|
|
||||||
<ul class="tsd-legend">
|
|
||||||
<li class="tsd-kind-class"><span class="tsd-kind-icon">Class</span></li>
|
|
||||||
<li class="tsd-kind-class tsd-has-type-parameter"><span class="tsd-kind-icon">Class with type parameter</span></li>
|
|
||||||
<li class="tsd-kind-constructor tsd-parent-kind-class"><span class="tsd-kind-icon">Constructor</span></li>
|
|
||||||
<li class="tsd-kind-property tsd-parent-kind-class"><span class="tsd-kind-icon">Property</span></li>
|
|
||||||
<li class="tsd-kind-method tsd-parent-kind-class"><span class="tsd-kind-icon">Method</span></li>
|
|
||||||
<li class="tsd-kind-accessor tsd-parent-kind-class"><span class="tsd-kind-icon">Accessor</span></li>
|
|
||||||
<li class="tsd-kind-index-signature tsd-parent-kind-class"><span class="tsd-kind-icon">Index signature</span></li>
|
|
||||||
</ul>
|
|
||||||
<ul class="tsd-legend">
|
|
||||||
<li class="tsd-kind-constructor tsd-parent-kind-class tsd-is-inherited"><span class="tsd-kind-icon">Inherited constructor</span></li>
|
|
||||||
<li class="tsd-kind-property tsd-parent-kind-class tsd-is-inherited"><span class="tsd-kind-icon">Inherited property</span></li>
|
|
||||||
<li class="tsd-kind-method tsd-parent-kind-class tsd-is-inherited"><span class="tsd-kind-icon">Inherited method</span></li>
|
|
||||||
<li class="tsd-kind-accessor tsd-parent-kind-class tsd-is-inherited"><span class="tsd-kind-icon">Inherited accessor</span></li>
|
|
||||||
</ul>
|
|
||||||
<ul class="tsd-legend">
|
|
||||||
<li class="tsd-kind-property tsd-parent-kind-class tsd-is-protected"><span class="tsd-kind-icon">Protected property</span></li>
|
|
||||||
<li class="tsd-kind-method tsd-parent-kind-class tsd-is-protected"><span class="tsd-kind-icon">Protected method</span></li>
|
|
||||||
<li class="tsd-kind-accessor tsd-parent-kind-class tsd-is-protected"><span class="tsd-kind-icon">Protected accessor</span></li>
|
|
||||||
</ul>
|
|
||||||
<ul class="tsd-legend">
|
|
||||||
<li class="tsd-kind-property tsd-parent-kind-class tsd-is-private"><span class="tsd-kind-icon">Private property</span></li>
|
|
||||||
<li class="tsd-kind-method tsd-parent-kind-class tsd-is-private"><span class="tsd-kind-icon">Private method</span></li>
|
|
||||||
<li class="tsd-kind-accessor tsd-parent-kind-class tsd-is-private"><span class="tsd-kind-icon">Private accessor</span></li>
|
|
||||||
</ul>
|
|
||||||
<ul class="tsd-legend">
|
|
||||||
<li class="tsd-kind-property tsd-parent-kind-class tsd-is-static"><span class="tsd-kind-icon">Static property</span></li>
|
|
||||||
<li class="tsd-kind-call-signature tsd-parent-kind-class tsd-is-static"><span class="tsd-kind-icon">Static method</span></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
<div class="container tsd-generator">
|
|
||||||
<p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p>
|
|
||||||
</div>
|
|
||||||
<div class="overlay"></div>
|
|
||||||
<script src="assets/js/main.js"></script>
|
|
||||||
<script>if (location.protocol == 'file:') document.write('<script src="assets/js/search.js"><' + '/script>');</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
305
docs/updux.md
Normal file
305
docs/updux.md
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
# Updux
|
||||||
|
|
||||||
|
`Updux` is a way to minimize and simplify the boilerplate associated with the
|
||||||
|
creation of a `Redux` store. It takes a shorthand configuration
|
||||||
|
object, and generates the appropriate reducer, actions, middleware, etc.
|
||||||
|
In true `Redux`-like fashion, upduxes can be made of sub-upduxes (`subduxes` for short) for different slices of the root state.
|
||||||
|
|
||||||
|
## Constructor
|
||||||
|
|
||||||
|
const updux = new Updux({ ...buildArgs })
|
||||||
|
|
||||||
|
### arguments
|
||||||
|
|
||||||
|
#### initial
|
||||||
|
|
||||||
|
Default initial state of the reducer. If applicable, is merged with
|
||||||
|
the subduxes initial states, with the parent having precedence.
|
||||||
|
|
||||||
|
If not provided, defaults to an empty object.
|
||||||
|
|
||||||
|
#### actions
|
||||||
|
|
||||||
|
Generic action creations are automatically created from the mutations and effects, but you can
|
||||||
|
also define custom action creator here. The object's values are function that
|
||||||
|
transform the arguments of the creator to the action's payload.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { actions } = updux({
|
||||||
|
actions: {
|
||||||
|
bar: (x,y) => ({x,y})
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
foo: () => state => state,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
actions.foo({ x: 1, y: 2 }); // => { type: foo, payload: { x:1, y:2 } }
|
||||||
|
actions.bar(1,2); // => { type: bar, payload: { x:1, y:2 } }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### mutations
|
||||||
|
|
||||||
|
Object mapping actions to the associated state mutation.
|
||||||
|
|
||||||
|
For example, in `Redux` you'd do
|
||||||
|
|
||||||
|
```js
|
||||||
|
function todosReducer(state=[],action) {
|
||||||
|
|
||||||
|
switch(action.type) {
|
||||||
|
case 'ADD': return [ ...state, action.payload ];
|
||||||
|
|
||||||
|
case 'DONE': return state.map( todo => todo.id === action.payload
|
||||||
|
? { ...todo, done: true } : todo )
|
||||||
|
|
||||||
|
default: return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
With Updux:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const todosUpdux = updux({
|
||||||
|
mutations: {
|
||||||
|
add: todo => state => [ ...state, todo ],
|
||||||
|
done: done_id => u.map( u.if( ({id} => id === done_id), {done: true} ) )
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The signature of the mutations is `(payload,action) => state => newState`.
|
||||||
|
It is designed to play well with `Updeep` (and [Immer](https://immerjs.github.io/immer/docs/introduction)). This way, instead of doing
|
||||||
|
|
||||||
|
```js
|
||||||
|
mutation: {
|
||||||
|
renameTodo: newName => state => { ...state, name: newName }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
we can do
|
||||||
|
|
||||||
|
```js
|
||||||
|
mutation: {
|
||||||
|
renameTodo: newName => u({ name: newName })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Also, the special key `*` can be used to match any
|
||||||
|
action not explicitly matched by other mutations.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const todosUpdux = updux({
|
||||||
|
mutations: {
|
||||||
|
add: todo => state => [ ...state, todo ],
|
||||||
|
done: done_id => u.map( u.if( ({id} => id === done_id), {done: true} ) ),
|
||||||
|
'*' (payload,action) => state => {
|
||||||
|
console.warn( "unexpected action ", action.type );
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### groomMutations
|
||||||
|
|
||||||
|
|
||||||
|
Function that can be provided to alter all local mutations of the updux
|
||||||
|
(the mutations of subduxes are left untouched).
|
||||||
|
|
||||||
|
Can be used, for example, for Immer integration:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import Updux from 'updux';
|
||||||
|
import { produce } from 'Immer';
|
||||||
|
|
||||||
|
const updux = new Updux({
|
||||||
|
initial: { counter: 0 },
|
||||||
|
groomMutations: mutation => (...args) => produce( mutation(...args) ),
|
||||||
|
mutations: {
|
||||||
|
add: (inc=1) => draft => draft.counter += inc
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Or perhaps for debugging:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import Updux from 'updux';
|
||||||
|
|
||||||
|
const updux = new Updux({
|
||||||
|
initial: { counter: 0 },
|
||||||
|
groomMutations: mutation => (...args) => state => {
|
||||||
|
console.log( "got action ", args[1] );
|
||||||
|
return mutation(...args)(state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### subduxes
|
||||||
|
|
||||||
|
Object mapping slices of the state to sub-upduxes. In addition to creating
|
||||||
|
sub-reducers for those slices, it'll make the parend updux inherit all the
|
||||||
|
actions and middleware from its subduxes.
|
||||||
|
|
||||||
|
For example, if in plain Redux you would do
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { combineReducers } from 'redux';
|
||||||
|
import todosReducer from './todos';
|
||||||
|
import statisticsReducer from './statistics';
|
||||||
|
|
||||||
|
const rootReducer = combineReducers({
|
||||||
|
todos: todosReducer,
|
||||||
|
stats: statisticsReducer,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
then with Updux you'd do
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { updux } from 'updux';
|
||||||
|
import todos from './todos';
|
||||||
|
import statistics from './statistics';
|
||||||
|
|
||||||
|
const rootUpdux = updux({
|
||||||
|
subduxes: {
|
||||||
|
todos,
|
||||||
|
statistics
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### effects
|
||||||
|
|
||||||
|
Plain object defining asynchronous actions and side-effects triggered by actions.
|
||||||
|
The effects themselves are Redux middleware, with the `dispatch`
|
||||||
|
property of the first argument augmented with all the available actions.
|
||||||
|
|
||||||
|
```
|
||||||
|
updux({
|
||||||
|
effects: {
|
||||||
|
fetch: ({dispatch}) => next => async (action) => {
|
||||||
|
next(action);
|
||||||
|
|
||||||
|
let result = await fetch(action.payload.url).then( result => result.json() );
|
||||||
|
dispatch.fetchSuccess(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### middleware
|
||||||
|
|
||||||
|
## Getters
|
||||||
|
|
||||||
|
### actions
|
||||||
|
|
||||||
|
Action creators for all actions defined or used in the actions, mutations, effects and subduxes
|
||||||
|
of the updux config.
|
||||||
|
|
||||||
|
Non-custom action creators defined in `actions` have the signature `(payload={},meta={}) => ({type,
|
||||||
|
payload,meta})` (with the extra sugar that if `meta` or `payload` are not
|
||||||
|
specified, the key is not present in the produced action).
|
||||||
|
|
||||||
|
If the same action appears in multiple locations, the precedence order
|
||||||
|
determining which one will prevail is
|
||||||
|
|
||||||
|
actions generated from mutations/effects < non-custom subduxes actions <
|
||||||
|
custom subduxes actions < custom actions
|
||||||
|
|
||||||
|
### middleware
|
||||||
|
|
||||||
|
const middleware = updux.middleware;
|
||||||
|
|
||||||
|
Array of middlewares aggregating all the effects defined in the
|
||||||
|
updux and its subduxes. Effects of the updux itself are
|
||||||
|
done before the subduxes effects.
|
||||||
|
Note that `getState` will always return the state of the
|
||||||
|
local updux. The function `getRootState` is provided
|
||||||
|
alongside `getState` to get the root state.
|
||||||
|
|
||||||
|
|
||||||
|
#### reducer
|
||||||
|
|
||||||
|
A Redux reducer generated using the computed initial state and
|
||||||
|
mutations.
|
||||||
|
|
||||||
|
|
||||||
|
#### mutations
|
||||||
|
|
||||||
|
Merge of the updux and subduxes mutations. If an action triggers
|
||||||
|
mutations in both the main updux and its subduxes, the subduxes
|
||||||
|
mutations will be performed first.
|
||||||
|
|
||||||
|
#### subduxUpreducer
|
||||||
|
|
||||||
|
|
||||||
|
Returns the upreducer made of the merge of all sudbuxes reducers, without
|
||||||
|
the local mutations. Useful, for example, for sink mutations.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import todo from './todo'; // updux for a single todo
|
||||||
|
import Updux from 'updux';
|
||||||
|
import u from 'updeep';
|
||||||
|
|
||||||
|
const todos = new Updux({ initial: [], subduxes: { '*': todo } });
|
||||||
|
todos.addMutation(
|
||||||
|
todo.actions.done,
|
||||||
|
({todo_id},action) => u.map( u.if( u.is('id',todo_id) ), todos.subduxUpreducer(action) )
|
||||||
|
true
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### createStore
|
||||||
|
|
||||||
|
|
||||||
|
Same as doing
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { createStore, applyMiddleware } from 'redux';
|
||||||
|
|
||||||
|
const { initial, reducer, middleware, actions } = updox(...);
|
||||||
|
|
||||||
|
const store = createStore( initial, reducer, applyMiddleware(middleware) );
|
||||||
|
|
||||||
|
for ( let type in actions ) {
|
||||||
|
store.dispatch[type] = (...args) => {
|
||||||
|
store.dispatch(actions[type](...args))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
So that later on you can do
|
||||||
|
|
||||||
|
```js
|
||||||
|
store.dispatch.addTodo(...);
|
||||||
|
|
||||||
|
// still work
|
||||||
|
store.dispatch( actions.addTodo(...) );
|
||||||
|
```
|
||||||
|
|
||||||
|
## asDux
|
||||||
|
|
||||||
|
|
||||||
|
Returns a [ducks](https://github.com/erikras/ducks-modular-redux)-like
|
||||||
|
plain object holding the reducer from the Updux object and all
|
||||||
|
its trimmings.
|
||||||
|
|
||||||
|
|
||||||
|
## addMutation
|
||||||
|
|
||||||
|
Adds a mutation and its associated action to the updux.
|
||||||
|
If a local mutation was already associated to the action,
|
||||||
|
it will be replaced by the new one.
|
||||||
|
@param isSink
|
||||||
|
If `true`, disables the subduxes mutations for this action. To
|
||||||
|
conditionally run the subduxes mutations, check out [[subduxUpreducer]].
|
||||||
|
|
||||||
|
```js
|
||||||
|
updux.addMutation( add, inc => state => state + inc );
|
||||||
|
```
|
2948
package-lock.json
generated
Normal file
2948
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -5,6 +5,8 @@
|
|||||||
"redux": "^4.0.4"
|
"redux": "^4.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"docsify": "^4.10.2",
|
||||||
|
"docsify-cli": "^4.4.0",
|
||||||
"@babel/cli": "^7.6.4",
|
"@babel/cli": "^7.6.4",
|
||||||
"@babel/core": "^7.6.4",
|
"@babel/core": "^7.6.4",
|
||||||
"@babel/preset-env": "^7.6.3",
|
"@babel/preset-env": "^7.6.3",
|
||||||
@ -23,6 +25,7 @@
|
|||||||
"name": "updux",
|
"name": "updux",
|
||||||
"description": "Updeep-friendly Redux helper framework",
|
"description": "Updeep-friendly Redux helper framework",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"docsify:serve": "docsify serve docs",
|
||||||
"build": "tsc && typedoc",
|
"build": "tsc && typedoc",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
@ -40,5 +43,10 @@
|
|||||||
"url": "https://github.com/yanick/updux/issues"
|
"url": "https://github.com/yanick/updux/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/yanick/updux#readme",
|
"homepage": "https://github.com/yanick/updux#readme",
|
||||||
"types": "./dist/index.d.ts"
|
"types": "./dist/index.d.ts",
|
||||||
|
"prettier": {
|
||||||
|
"tabWidth": 4,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,57 +1,57 @@
|
|||||||
import fp from "lodash/fp";
|
import fp from 'lodash/fp';
|
||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
ActionCreator,
|
ActionCreator,
|
||||||
ActionPayloadGenerator,
|
ActionPayloadGenerator,
|
||||||
Dictionary
|
Dictionary,
|
||||||
} from "../types";
|
} from '../types';
|
||||||
|
|
||||||
export function actionCreator<T extends string, P extends any>(
|
export function actionCreator<T extends string, P extends any>(
|
||||||
type: T,
|
type: T,
|
||||||
transform: (...args: any[]) => P
|
transform: (...args: any[]) => P
|
||||||
): ActionCreator<T, P>;
|
): ActionCreator<T, P>;
|
||||||
export function actionCreator<T extends string>(
|
export function actionCreator<T extends string>(
|
||||||
type: T,
|
type: T,
|
||||||
transform: null
|
transform: null
|
||||||
): ActionCreator<T, null>;
|
): ActionCreator<T, null>;
|
||||||
export function actionCreator<T extends string>(
|
export function actionCreator<T extends string>(
|
||||||
type: T
|
type: T
|
||||||
): ActionCreator<T, undefined>;
|
): ActionCreator<T, undefined>;
|
||||||
export function actionCreator(type: any, transform?: any) {
|
export function actionCreator(type: any, transform?: any) {
|
||||||
if (transform) {
|
if (transform) {
|
||||||
return Object.assign(
|
return Object.assign(
|
||||||
(...args: any[]) => ({ type, payload: transform(...args) }),
|
(...args: any[]) => ({ type, payload: transform(...args) }),
|
||||||
{ type }
|
{ type }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transform === null) {
|
if (transform === null) {
|
||||||
return Object.assign(() => ({ type }), { type });
|
return Object.assign(() => ({ type }), { type });
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.assign((payload: unknown) => ({ type, payload }), { type });
|
return Object.assign((payload: unknown) => ({ type, payload }), { type });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function actionFor(type: string): ActionCreator {
|
export function actionFor(type: string): ActionCreator {
|
||||||
const f = (payload = undefined, meta = undefined) =>
|
const f = (payload = undefined, meta = undefined) =>
|
||||||
fp.pickBy(v => v !== undefined)({ type, payload, meta }) as Action;
|
fp.pickBy(v => v !== undefined)({ type, payload, meta }) as Action;
|
||||||
|
|
||||||
return Object.assign(f, {
|
return Object.assign(f, {
|
||||||
_genericAction: true,
|
_genericAction: true,
|
||||||
type
|
type,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActionPair = [string, ActionCreator];
|
type ActionPair = [string, ActionCreator];
|
||||||
|
|
||||||
function buildActions(actions: ActionPair[] = []): Dictionary<ActionCreator> {
|
function buildActions(actions: ActionPair[] = []): Dictionary<ActionCreator> {
|
||||||
// priority => generics => generic subs => craft subs => creators
|
// priority => generics => generic subs => craft subs => creators
|
||||||
|
|
||||||
const [crafted, generic] = fp.partition(([type, f]) => !f._genericAction)(
|
const [crafted, generic] = fp.partition(([type, f]) => !f._genericAction)(
|
||||||
fp.compact(actions)
|
fp.compact(actions)
|
||||||
);
|
);
|
||||||
|
|
||||||
return fp.fromPairs([...generic, ...crafted]);
|
return fp.fromPairs([...generic, ...crafted]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default buildActions;
|
export default buildActions;
|
||||||
|
@ -3,12 +3,13 @@ import {
|
|||||||
applyMiddleware,
|
applyMiddleware,
|
||||||
Middleware,
|
Middleware,
|
||||||
Reducer,
|
Reducer,
|
||||||
|
PreloadedState
|
||||||
} from 'redux';
|
} from 'redux';
|
||||||
import { ActionCreator, Dictionary } from '../types';
|
import { ActionCreator, Dictionary } from '../types';
|
||||||
|
|
||||||
function buildCreateStore<S>(
|
function buildCreateStore<S>(
|
||||||
reducer: Reducer<S>,
|
reducer: Reducer<S>,
|
||||||
initial: S,
|
initial: PreloadedState<S>,
|
||||||
middleware: Middleware,
|
middleware: Middleware,
|
||||||
actions: Dictionary<ActionCreator>,
|
actions: Dictionary<ActionCreator>,
|
||||||
) {
|
) {
|
||||||
|
155
src/updux.ts
155
src/updux.ts
@ -43,61 +43,12 @@ export type Dux<S> = Pick<
|
|||||||
| "upreducer"
|
| "upreducer"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/**
|
|
||||||
* `Updux` is a way to minimize and simplify the boilerplate associated with the
|
|
||||||
* creation of a `Redux` store. It takes a shorthand configuration
|
|
||||||
* object, and generates the appropriate reducer, actions, middleware, etc.
|
|
||||||
* In true `Redux`-like fashion, upduxes can be made of sub-upduxes (`subduxes` for short) for different slices of the root state.
|
|
||||||
* @typeparam S Store's state type. Defaults to `any`.
|
|
||||||
*/
|
|
||||||
export class Updux<S = any> {
|
export class Updux<S = any> {
|
||||||
subduxes: Dictionary<Updux>;
|
subduxes: Dictionary<Updux>;
|
||||||
|
|
||||||
/**
|
initial: S;
|
||||||
* Default initial state of the reducer. If applicable, merges the
|
|
||||||
* initial states of `config` and `subduxes`, with `config` having
|
|
||||||
* precedence.
|
|
||||||
*
|
|
||||||
* If nothing was provided, defaults to an empty object.
|
|
||||||
*/
|
|
||||||
initial: S;
|
|
||||||
|
|
||||||
/**
|
groomMutations: (mutation: Mutation<S>) => Mutation<S>;
|
||||||
* Function that can be provided to alter all local mutations of the updux
|
|
||||||
* (the mutations of subduxes are left untouched).
|
|
||||||
*
|
|
||||||
* Can be used, for example, for Immer integration:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* import Updux from 'updux';
|
|
||||||
* import { produce } from 'Immer';
|
|
||||||
*
|
|
||||||
* const updux = new Updux({
|
|
||||||
* initial: { counter: 0 },
|
|
||||||
* groomMutations: mutation => (...args) => produce( mutation(...args) ),
|
|
||||||
* mutations: {
|
|
||||||
* add: (inc=1) => draft => draft.counter += inc
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Or perhaps for debugging:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* import Updux from 'updux';
|
|
||||||
*
|
|
||||||
* const updux = new Updux({
|
|
||||||
* initial: { counter: 0 },
|
|
||||||
* groomMutations: mutation => (...args) => state => {
|
|
||||||
* console.log( "got action ", args[1] );
|
|
||||||
* return mutation(...args)(state);
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
groomMutations: (mutation: Mutation<S>) => Mutation<S>;
|
|
||||||
|
|
||||||
private localEffects: EffectEntry<S>[] = [];
|
private localEffects: EffectEntry<S>[] = [];
|
||||||
|
|
||||||
@ -140,33 +91,12 @@ export class Updux<S = any> {
|
|||||||
mutations.forEach(args => (this.addMutation as any)(...args));
|
mutations.forEach(args => (this.addMutation as any)(...args));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
get middleware(): UpduxMiddleware<S> {
|
||||||
* Array of middlewares aggregating all the effects defined in the
|
|
||||||
* updux and its subduxes. Effects of the updux itself are
|
|
||||||
* done before the subduxes effects.
|
|
||||||
* Note that `getState` will always return the state of the
|
|
||||||
* local updux. The function `getRootState` is provided
|
|
||||||
* alongside `getState` to get the root state.
|
|
||||||
*/
|
|
||||||
get middleware(): UpduxMiddleware<S> {
|
|
||||||
return buildMiddleware(this._middlewareEntries, this.actions);
|
return buildMiddleware(this._middlewareEntries, this.actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
get actions(): Dictionary<ActionCreator> {
|
||||||
* Action creators for all actions defined or used in the actions, mutations, effects and subduxes
|
|
||||||
* of the updux config.
|
|
||||||
*
|
|
||||||
* Non-custom action creators defined in `actions` have the signature `(payload={},meta={}) => ({type,
|
|
||||||
* payload,meta})` (with the extra sugar that if `meta` or `payload` are not
|
|
||||||
* specified, the key is not present in the produced action).
|
|
||||||
*
|
|
||||||
* If the same action appears in multiple locations, the precedence order
|
|
||||||
* determining which one will prevail is
|
|
||||||
*
|
|
||||||
* actions generated from mutations/effects < non-custom subduxes actions <
|
|
||||||
* custom subduxes actions < custom actions
|
|
||||||
*/
|
|
||||||
get actions(): Dictionary<ActionCreator> {
|
|
||||||
return buildActions([
|
return buildActions([
|
||||||
...(Object.entries(this.localActions) as any),
|
...(Object.entries(this.localActions) as any),
|
||||||
...(fp.flatten(
|
...(fp.flatten(
|
||||||
@ -182,69 +112,18 @@ export class Updux<S = any> {
|
|||||||
return buildUpreducer(this.initial, this.mutations);
|
return buildUpreducer(this.initial, this.mutations);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
get reducer(): (state: S | undefined, action: Action) => S {
|
||||||
* A Redux reducer generated using the computed initial state and
|
|
||||||
* mutations.
|
|
||||||
*/
|
|
||||||
get reducer(): (state: S | undefined, action: Action) => S {
|
|
||||||
return (state, action) => this.upreducer(action)(state as S);
|
return (state, action) => this.upreducer(action)(state as S);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
get mutations(): Dictionary<Mutation<S>> {
|
||||||
* Merge of the updux and subduxes mutations. If an action triggers
|
|
||||||
* mutations in both the main updux and its subduxes, the subduxes
|
|
||||||
* mutations will be performed first.
|
|
||||||
*/
|
|
||||||
get mutations(): Dictionary<Mutation<S>> {
|
|
||||||
return buildMutations(this.localMutations, this.subduxes);
|
return buildMutations(this.localMutations, this.subduxes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
get subduxUpreducer() {
|
||||||
* Returns the upreducer made of the merge of all sudbuxes reducers, without
|
|
||||||
* the local mutations. Useful, for example, for sink mutations.
|
|
||||||
* @example
|
|
||||||
* ```
|
|
||||||
* import todo from './todo'; // updux for a single todo
|
|
||||||
* import Updux from 'updux';
|
|
||||||
* import u from 'updeep';
|
|
||||||
*
|
|
||||||
* const todos = new Updux({ initial: [], subduxes: { '*': todo } });
|
|
||||||
* todos.addMutation(
|
|
||||||
* todo.actions.done,
|
|
||||||
* ({todo_id},action) => u.map( u.if( u.is('id',todo_id) ), todos.subduxUpreducer(action) )
|
|
||||||
* true
|
|
||||||
* );
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
get subduxUpreducer() {
|
|
||||||
return buildUpreducer(this.initial, buildMutations({}, this.subduxes));
|
return buildUpreducer(this.initial, buildMutations({}, this.subduxes));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as doing
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* import { createStore, applyMiddleware } from 'redux';
|
|
||||||
*
|
|
||||||
* const { initial, reducer, middleware, actions } = updox(...);
|
|
||||||
*
|
|
||||||
* const store = createStore( initial, reducer, applyMiddleware(middleware) );
|
|
||||||
*
|
|
||||||
* for ( let type in actions ) {
|
|
||||||
* store.dispatch[type] = (...args) => {
|
|
||||||
* store.dispatch(actions[type](...args))
|
|
||||||
* };
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* So that later on you can do
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* store.dispatch.addTodo(...);
|
|
||||||
*
|
|
||||||
* // still work
|
|
||||||
* store.dispatch( actions.addTodo(...) );
|
|
||||||
*/
|
|
||||||
get createStore(): () => StoreWithDispatchActions<S> {
|
get createStore(): () => StoreWithDispatchActions<S> {
|
||||||
const actions = this.actions;
|
const actions = this.actions;
|
||||||
|
|
||||||
@ -256,12 +135,6 @@ export class Updux<S = any> {
|
|||||||
) as () => StoreWithDispatchActions<S, typeof actions>;
|
) as () => StoreWithDispatchActions<S, typeof actions>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a
|
|
||||||
* [ducks](https://github.com/erikras/ducks-modular-redux)-like
|
|
||||||
* plain object holding the reducer from the Updux object and all
|
|
||||||
* its trimmings.
|
|
||||||
*/
|
|
||||||
get asDux(): Dux<S> {
|
get asDux(): Dux<S> {
|
||||||
return {
|
return {
|
||||||
createStore: this.createStore,
|
createStore: this.createStore,
|
||||||
@ -275,18 +148,6 @@ export class Updux<S = any> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a mutation and its associated action to the updux.
|
|
||||||
* If a local mutation was already associated to the action,
|
|
||||||
* it will be replaced by the new one.
|
|
||||||
* @param isSink
|
|
||||||
* If `true`, disables the subduxes mutations for this action. To
|
|
||||||
* conditionally run the subduxes mutations, check out [[subduxUpreducer]].
|
|
||||||
* @example
|
|
||||||
* ```
|
|
||||||
* updux.addMutation( add, inc => state => state + inc );
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
addMutation<A extends ActionCreator>(
|
addMutation<A extends ActionCreator>(
|
||||||
creator: A,
|
creator: A,
|
||||||
mutation: Mutation<S, A extends (...args: any[]) => infer R ? R : never>,
|
mutation: Mutation<S, A extends (...args: any[]) => infer R ? R : never>,
|
||||||
|
Loading…
Reference in New Issue
Block a user