feat: moving documentation to docsify

This commit is contained in:
Yanick Champoux 2020-01-31 18:26:58 -05:00
commit fa55762efc
10 changed files with 3566 additions and 600 deletions

0
docs/.nojekyll Normal file
View File

233
docs/README.md Normal file
View 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
View File

@ -0,0 +1,6 @@
<!-- docs/_sidebar.md -->
* [Home](/)
* API Reference
* [Updux](updux.md)
* [Types](types.md)

View File

@ -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&#39;s Updux?</h1>
</a>
<p>So, I&#39;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&#39;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&#39;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>) =&gt;</span> u({<span class="hljs-attr">counter</span>: <span class="hljs-function"><span class="hljs-params">s</span> =&gt;</span> s + increment })
},
<span class="hljs-attr">effects</span>: {
<span class="hljs-string">'*'</span> =&gt; <span class="hljs-function"><span class="hljs-params">api</span> =&gt;</span> <span class="hljs-function"><span class="hljs-params">next</span> =&gt;</span> <span class="hljs-function"><span class="hljs-params">action</span> =&gt;</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>) =&gt;</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&#39;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> =&gt;</span> u({ reviewed: <span class="hljs-literal">true</span>}),
done: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</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> =&gt;</span> state =&gt; state.map( todo.upreducer(action) )
);
todos.addMutation(
todo.actions.done,
<span class="hljs-function"><span class="hljs-params">(id,action)</span> =&gt;</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> =&gt;</span> u({ reviewed: <span class="hljs-literal">true</span>}),
done: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</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> =&gt;</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&#39;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>) =&gt;</span> <span class="hljs-function"><span class="hljs-params">state</span> =&gt;</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>) =&gt;</span> produce( <span class="hljs-function"><span class="hljs-params">draft</span> =&gt;</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> =&gt;</span> <span class="hljs-function">(<span class="hljs-params">...args</span>) =&gt;</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>) =&gt;</span> <span class="hljs-function"><span class="hljs-params">draft</span> =&gt;</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
View 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

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
} }

View File

@ -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;

View File

@ -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>,
) { ) {

View File

@ -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>,