Compare commits
104 Commits
typescript
...
dev-v4
Author | SHA1 | Date | |
---|---|---|---|
40b58a41a6 | |||
93582bcb70 | |||
eec442ff68 | |||
760263b555 | |||
9b23a6995b | |||
39aaf231ac | |||
5fe858ef16 | |||
91e6bdd4f0 | |||
2f687cfda3 | |||
90e90779e1 | |||
ac53f1d24b | |||
55e65bd5d8 | |||
fb4290c983 | |||
9420e7af91 | |||
590aeef770 | |||
55376db1c2 | |||
33a10d6916 | |||
dd7b7159b1 | |||
66c2b162db | |||
edb6716c9c | |||
88e1565fa5 | |||
b821f3669f | |||
38949d9e0f | |||
b4c1b357e4 | |||
2e4b0900ea | |||
af5f2d0a8f | |||
e3e9c9a24b | |||
c438847051 | |||
7280381ca8 | |||
a22a121018 | |||
e4083645bc | |||
a6b6a54c72 | |||
8b88bcc0b2 | |||
8e16d9fef7 | |||
d443441048 | |||
7e8c2ef171 | |||
76311d31e9 | |||
bc12cf6dcd | |||
b65a032c7c | |||
787028995a | |||
6bae57b69a | |||
d104ce002f | |||
71141e0f01 | |||
3680f2b289 | |||
b022c1aedb | |||
e8fa1cc6c5 | |||
342d1b6f3a | |||
2e78e27bf6 | |||
a04ebfcdfe | |||
06b4bf62c4 | |||
55f0d5cd13 | |||
9fe6fc952e | |||
d405c90a0d | |||
bb0bc14873 | |||
5ff07b2e2f | |||
a9cb408521 | |||
bde9cac053 | |||
0e894b7db1 | |||
56625c5083 | |||
5297aecba6 | |||
f322691906 | |||
347d90267e | |||
17cd3bec46 | |||
5edbc688be | |||
daa8421251 | |||
95768706fa | |||
7a5733425e | |||
c660b7be1d | |||
3e9049ce93 | |||
1df6ac5329 | |||
769b70dfce | |||
1555e4d289 | |||
e55bf7a771 | |||
e1cb50100f | |||
76ccd0d14a | |||
2e03a51e15 | |||
4c28a9ad05 | |||
93533a739f | |||
b3088334a4 | |||
736377effd | |||
042f5b8f13 | |||
e9778f98e8 | |||
9ac3032a7f | |||
1a2b2df9ac | |||
88808507ad | |||
000ca9871a | |||
d1ed23de2c | |||
13eeb86e05 | |||
f5cdc4207a | |||
9bac1de62c | |||
5e39c2b162 | |||
d304fc0fcc | |||
bf152d6f05 | |||
a27865d365 | |||
80d18703c4 | |||
29d3d9a2aa | |||
ea724ab73b | |||
90a2171cc7 | |||
ad20f908a7 | |||
0cb445be3a | |||
455002453b | |||
7bb988aa54 | |||
ad88599dd9 | |||
3273690fe2 |
5
.gitignore
vendored
@ -9,3 +9,8 @@ pnpm-debug.log
|
||||
yarn-error.log
|
||||
GPUCache/
|
||||
updux-2.0.0.tgz
|
||||
pnpm-lock.yaml
|
||||
updux-5.0.0.tgz
|
||||
.envrc
|
||||
.task
|
||||
releases
|
||||
|
37
.npmignore
@ -1,18 +1,33 @@
|
||||
*.test.*
|
||||
test.*
|
||||
dist/**/*.test.js
|
||||
docs
|
||||
.eslint*
|
||||
GPUCache/
|
||||
node_modules/
|
||||
tsconfig.tsbuildinfo
|
||||
**/*.orig
|
||||
dist
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
.nyc_output/
|
||||
**/*.orig
|
||||
out/
|
||||
package-lock.json
|
||||
pnpm-debug.log
|
||||
pnpm-lock.yaml
|
||||
yarn-error.log
|
||||
GPUCache/
|
||||
updux-2.0.0.tgz
|
||||
.travis.yml
|
||||
.pre-commit*
|
||||
.prettier*
|
||||
.prettierignore
|
||||
src/
|
||||
Taskfile.yaml
|
||||
*.test.*
|
||||
test.*
|
||||
TODO
|
||||
tools/gen_sidebar.pl
|
||||
.travis.yml
|
||||
tsconfig.json
|
||||
tsconfig.tsbuildinfo
|
||||
updux-2.0.0.tgz
|
||||
updux-*.tgz
|
||||
vitest.config.js
|
||||
yarn-error.log
|
||||
yarn.lock
|
||||
docs*
|
||||
.envrc
|
||||
contrib/*
|
||||
typedoc.json
|
||||
dist/tutorial/*
|
||||
|
@ -2,6 +2,12 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
## 5.0.1 (2023-05-04)
|
||||
|
||||
- Upgrade @reduxjs/toolkit for cjs/ems fun
|
||||
|
||||
## 5.0.0 (2023-05-04)
|
||||
|
||||
## 4.0.0 (2022-08-30)
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
11
TODO
@ -1,2 +1,9 @@
|
||||
- documentation generator (mkdocs + jsdoc-to-markdown)
|
||||
- createStore
|
||||
* documentation action + mutation <<<
|
||||
* createAction
|
||||
* addMutation
|
||||
|
||||
addMutation with signature
|
||||
|
||||
.addMutation( actionCreator, (payload,action) => state => newState )
|
||||
|
||||
* then check that the mutation is in the new type
|
||||
|
@ -1,8 +1,58 @@
|
||||
# https://taskfile.dev
|
||||
|
||||
# tea releases c --asset releases/updux-4.0.0-alpha.2.tgz -p --title v4.0.0-alpha.2 --tag v4.0.0-alpha.2
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
PARENT_BRANCH: main
|
||||
|
||||
tasks:
|
||||
release:gitea:
|
||||
cmds:
|
||||
- tea releases create -asset releases/updux-{{.VERSION}}.tgz -p --title {{.VERSION}} --tag {{.VERSION}}
|
||||
vars:
|
||||
VERSION: { sh: 'npm version --json | jq -r .updux' }
|
||||
prerelease:
|
||||
desc: deploy a prerelease
|
||||
deps:
|
||||
- build
|
||||
- checks
|
||||
cmds:
|
||||
- npm version prerelease
|
||||
- git push --tags
|
||||
- npm pack --pack-destination releases
|
||||
- { task: 'release:gitea' }
|
||||
|
||||
pack:
|
||||
desc: bundle the distro
|
||||
deps: [build, checks]
|
||||
cmds:
|
||||
- npm pack --pack-destination releases
|
||||
|
||||
build:
|
||||
sources:
|
||||
- src/**/*.ts
|
||||
generates:
|
||||
- dist/**/*.js
|
||||
cmds:
|
||||
- tsc
|
||||
|
||||
checks:
|
||||
deps: [test, build]
|
||||
|
||||
integrate:
|
||||
deps: [checks]
|
||||
cmds:
|
||||
- git is-clean
|
||||
# did we had tests?
|
||||
- git diff-ls {{.PARENT_BRANCH}} | grep test
|
||||
- git checkout {{.PARENT_BRANCH}}
|
||||
- git weld -
|
||||
|
||||
test: vitest run src
|
||||
|
||||
test:dev: vitest src
|
||||
|
||||
lint:fix:delta:
|
||||
vars:
|
||||
FILES:
|
||||
@ -44,3 +94,21 @@ tasks:
|
||||
lint:eslint:
|
||||
cmds:
|
||||
- npx eslint {{.FILES | default "src/**" }}
|
||||
|
||||
docs:api:
|
||||
sources: [src/**.ts]
|
||||
generates: [docs/src/content/docs/api/**.md]
|
||||
cmds:
|
||||
- typedoc
|
||||
- contrib/api_grooming.pl docs/src/content/docs/api
|
||||
docs:serve:
|
||||
cmds:
|
||||
- mkdocs serve
|
||||
|
||||
typedoc:
|
||||
cmds:
|
||||
- typedoc
|
||||
|
||||
docsify-install:
|
||||
cmds:
|
||||
- cp -r node_modules/docsify/lib docs/docsify
|
||||
|
17
contrib/api_grooming.pl
Executable file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use 5.34.0;
|
||||
|
||||
use Path::Tiny;
|
||||
|
||||
my $dir = path(shift);
|
||||
|
||||
$dir->visit(sub{
|
||||
my( $path ) = @_;
|
||||
return unless $path =~ /\.md$/;
|
||||
|
||||
$path->edit(sub{
|
||||
s/.*^\# (.*?)$/---\ntitle: "@{[ $1 =~ s(\\)()gr]}"\n---/sm;
|
||||
});
|
||||
},{ recurse => 1});
|
||||
|
226
docs-docsidy/README.md
Normal file
@ -0,0 +1,226 @@
|
||||
# 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
|
||||
|
||||
Full documentation can be [found here](https://yanick.github.io/updux/).
|
||||
Right now the best way to understand the whole thing is to go
|
||||
through the [tutorial](https://yanick.github.io/updux/#/tutorial)
|
||||
|
||||
## 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
|
||||
}
|
||||
});
|
||||
|
||||
```
|
3
docs-docsidy/_sidebar.md
Normal file
@ -0,0 +1,3 @@
|
||||
- [Home](/)
|
||||
- [ Tutorial ](tutorial.md)
|
||||
- [ Recipes ](recipes.md)
|
1
docs-docsidy/api/.nojekyll
Normal file
@ -0,0 +1 @@
|
||||
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.
|
228
docs-docsidy/api/README.md
Normal file
@ -0,0 +1,228 @@
|
||||
updux / [Exports](modules.md)
|
||||
|
||||
# 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
|
||||
|
||||
Full documentation can be [found here](https://yanick.github.io/updux/).
|
||||
Right now the best way to understand the whole thing is to go
|
||||
through the [tutorial](https://yanick.github.io/updux/#/tutorial)
|
||||
|
||||
## 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
|
||||
}
|
||||
});
|
||||
|
||||
```
|
435
docs-docsidy/api/classes/default.md
Normal file
@ -0,0 +1,435 @@
|
||||
[updux](../README.md) / [Exports](../modules.md) / default
|
||||
|
||||
# Class: default\<D\>
|
||||
|
||||
## Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `D` | extends `DuxConfig` |
|
||||
|
||||
## Constructors
|
||||
|
||||
### constructor
|
||||
|
||||
• **new default**\<`D`\>(`duxConfig`): [`default`](default.md)\<`D`\>
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `D` | extends `Partial`\<\{ `actions`: `Record`\<`string`, `any`\> ; `initialState`: `any` ; `reducer`: `unknown` ; `schema`: `Record`\<`string`, `any`\> ; `selectors`: `Record`\<`string`, `any`\> ; `subduxes`: `Record`\<`string`, Partial\<\{ initialState: any; schema: Record\<string, any\>; actions: Record\<string, any\>; subduxes: Record\<string, Partial\<...\>\>; reducer: unknown; selectors: Record\<string, any\>; }\>\> }\> |
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `duxConfig` | `D` |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D`\>
|
||||
|
||||
## Properties
|
||||
|
||||
### #defaultMutation
|
||||
|
||||
• `Private` **#defaultMutation**: `any`
|
||||
|
||||
___
|
||||
|
||||
### #effects
|
||||
|
||||
• `Private` **#effects**: `any`[] = `[]`
|
||||
|
||||
___
|
||||
|
||||
### #mutations
|
||||
|
||||
• `Private` **#mutations**: `any`[] = `[]`
|
||||
|
||||
___
|
||||
|
||||
### #reactions
|
||||
|
||||
• `Private` **#reactions**: `any`[] = `[]`
|
||||
|
||||
___
|
||||
|
||||
### duxConfig
|
||||
|
||||
• `Private` `Readonly` **duxConfig**: `D`
|
||||
|
||||
___
|
||||
|
||||
### memoBuildActions
|
||||
|
||||
• **memoBuildActions**: `Moized`\<(`localActions`: {}, `subduxes`: {}) => `any`, `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<(`localActions`: {}, `subduxes`: {}) => `any`\> ; `onCacheChange`: `OnCacheOperation`\<(`localActions`: {}, `subduxes`: {}) => `any`\> ; `onCacheHit`: `OnCacheOperation`\<(`localActions`: {}, `subduxes`: {}) => `any`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheChange`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheHit`: `OnCacheOperation`\<`Moizeable`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & \{ `maxSize`: `number` = 1 }\>
|
||||
|
||||
___
|
||||
|
||||
### memoBuildEffects
|
||||
|
||||
• **memoBuildEffects**: `Moized`\<(`localEffects`: `any`, `subduxes`: {}) => `any`[], `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<(`localEffects`: `any`, `subduxes`: {}) => `any`[]\> ; `onCacheChange`: `OnCacheOperation`\<(`localEffects`: `any`, `subduxes`: {}) => `any`[]\> ; `onCacheHit`: `OnCacheOperation`\<(`localEffects`: `any`, `subduxes`: {}) => `any`[]\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheChange`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheHit`: `OnCacheOperation`\<`Moizeable`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & \{ `maxSize`: `number` = 1 }\>
|
||||
|
||||
___
|
||||
|
||||
### memoBuildReducer
|
||||
|
||||
• **memoBuildReducer**: `Moized`\<(`initialStateState`: `unknown`, `mutations`: `MutationCase`[], `defaultMutation?`: `Omit`\<`MutationCase`, ``"matcher"``\>, `subduxes`: `Record`\<`string`, `Partial`\<\{ `actions`: `Record`\<`string`, `any`\> ; `initialState`: `any` ; `reducer`: `unknown` ; `schema`: `Record`\<`string`, `any`\> ; `selectors`: `Record`\<`string`, `any`\> ; `subduxes`: Record\<string, Partial\<\{ initialState: any; schema: Record\<string, any\>; actions: Record\<string, any\>; subduxes: Record\<string, Partial\<...\>\>; reducer: unknown; selectors: Record\<...\>; }\>\> }\>\>) => (`state`: `unknown`, `action`: `Action`\<`any`\>) => `unknown`, `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<(`initialStateState`: `unknown`, `mutations`: `MutationCase`[], `defaultMutation?`: `Omit`\<`MutationCase`, ``"matcher"``\>, `subduxes`: `Record`\<`string`, `Partial`\<\{ `actions`: `Record`\<`string`, `any`\> ; `initialState`: `any` ; `reducer`: `unknown` ; `schema`: `Record`\<`string`, `any`\> ; `selectors`: `Record`\<`string`, `any`\> ; `subduxes`: Record\<string, Partial\<\{ initialState: any; schema: Record\<string, any\>; actions: Record\<string, any\>; subduxes: Record\<string, Partial\<...\>\>; reducer: unknown; selectors: Record\<...\>; }\>\> }\>\>) => (`state`: `unknown`, `action`: `Action`\<`any`\>) => `unknown`\> ; `onCacheChange`: `OnCacheOperation`\<(`initialStateState`: `unknown`, `mutations`: `MutationCase`[], `defaultMutation?`: `Omit`\<`MutationCase`, ``"matcher"``\>, `subduxes`: `Record`\<`string`, `Partial`\<\{ `actions`: `Record`\<`string`, `any`\> ; `initialState`: `any` ; `reducer`: `unknown` ; `schema`: `Record`\<`string`, `any`\> ; `selectors`: `Record`\<`string`, `any`\> ; `subduxes`: Record\<string, Partial\<\{ initialState: any; schema: Record\<string, any\>; actions: Record\<string, any\>; subduxes: Record\<string, Partial\<...\>\>; reducer: unknown; selectors: Record\<...\>; }\>\> }\>\>) => (`state`: `unknown`, `action`: `Action`\<`any`\>) => `unknown`\> ; `onCacheHit`: `OnCacheOperation`\<(`initialStateState`: `unknown`, `mutations`: `MutationCase`[], `defaultMutation?`: `Omit`\<`MutationCase`, ``"matcher"``\>, `subduxes`: `Record`\<`string`, `Partial`\<\{ `actions`: `Record`\<`string`, `any`\> ; `initialState`: `any` ; `reducer`: `unknown` ; `schema`: `Record`\<`string`, `any`\> ; `selectors`: `Record`\<`string`, `any`\> ; `subduxes`: Record\<string, Partial\<\{ initialState: any; schema: Record\<string, any\>; actions: Record\<string, any\>; subduxes: Record\<string, Partial\<...\>\>; reducer: unknown; selectors: Record\<...\>; }\>\> }\>\>) => (`state`: `unknown`, `action`: `Action`\<`any`\>) => `unknown`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheChange`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheHit`: `OnCacheOperation`\<`Moizeable`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & \{ `maxSize`: `number` = 1 }\>
|
||||
|
||||
___
|
||||
|
||||
### memoBuildSchema
|
||||
|
||||
• **memoBuildSchema**: `Moized`\<(`schema`: `any`, `initialState`: `any`, `subduxes`: {}) => `any`, `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<(`schema`: `any`, `initialState`: `any`, `subduxes`: {}) => `any`\> ; `onCacheChange`: `OnCacheOperation`\<(`schema`: `any`, `initialState`: `any`, `subduxes`: {}) => `any`\> ; `onCacheHit`: `OnCacheOperation`\<(`schema`: `any`, `initialState`: `any`, `subduxes`: {}) => `any`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheChange`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheHit`: `OnCacheOperation`\<`Moizeable`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & \{ `maxSize`: `number` = 1 }\>
|
||||
|
||||
___
|
||||
|
||||
### memoBuildSelectors
|
||||
|
||||
• **memoBuildSelectors**: `Moized`\<(`localSelectors`: {}, `subduxes`: {}) => `object`, `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<(`localSelectors`: {}, `subduxes`: {}) => `object`\> ; `onCacheChange`: `OnCacheOperation`\<(`localSelectors`: {}, `subduxes`: {}) => `object`\> ; `onCacheHit`: `OnCacheOperation`\<(`localSelectors`: {}, `subduxes`: {}) => `object`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheChange`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheHit`: `OnCacheOperation`\<`Moizeable`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & \{ `maxSize`: `number` = 1 }\>
|
||||
|
||||
___
|
||||
|
||||
### memoInitialState
|
||||
|
||||
• **memoInitialState**: `Moized`\<(`localInitialState`: `any`, `subduxes`: `any`) => `any`, `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<(`localInitialState`: `any`, `subduxes`: `any`) => `any`\> ; `onCacheChange`: `OnCacheOperation`\<(`localInitialState`: `any`, `subduxes`: `any`) => `any`\> ; `onCacheHit`: `OnCacheOperation`\<(`localInitialState`: `any`, `subduxes`: `any`) => `any`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheChange`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheHit`: `OnCacheOperation`\<`Moizeable`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & \{ `maxSize`: `number` = 1 }\>
|
||||
|
||||
## Accessors
|
||||
|
||||
### actions
|
||||
|
||||
• `get` **actions**(): `DuxActions`\<`D`\>
|
||||
|
||||
#### Returns
|
||||
|
||||
`DuxActions`\<`D`\>
|
||||
|
||||
___
|
||||
|
||||
### asDux
|
||||
|
||||
• `get` **asDux**(): `Object`
|
||||
|
||||
#### Returns
|
||||
|
||||
`Object`
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `actions` | `DuxActions`\<`D`\> |
|
||||
| `effects` | `any` |
|
||||
| `initialState` | `DuxState`\<`D`\> |
|
||||
| `reactions` | `any`[] |
|
||||
| `reducer` | (`state`: `unknown`, `action`: `Action`\<`any`\>) => `unknown` |
|
||||
| `schema` | `any` |
|
||||
| `selectors` | `DuxSelectors`\<`D`\> |
|
||||
| `upreducer` | (`action`: `any`) => (`state`: `any`) => `unknown` |
|
||||
|
||||
___
|
||||
|
||||
### effects
|
||||
|
||||
• `get` **effects**(): `any`
|
||||
|
||||
#### Returns
|
||||
|
||||
`any`
|
||||
|
||||
___
|
||||
|
||||
### foo
|
||||
|
||||
• `get` **foo**(): `DuxActions`\<`D`\>
|
||||
|
||||
#### Returns
|
||||
|
||||
`DuxActions`\<`D`\>
|
||||
|
||||
___
|
||||
|
||||
### initialState
|
||||
|
||||
• `get` **initialState**(): `DuxState`\<`D`\>
|
||||
|
||||
#### Returns
|
||||
|
||||
`DuxState`\<`D`\>
|
||||
|
||||
___
|
||||
|
||||
### reactions
|
||||
|
||||
• `get` **reactions**(): `any`[]
|
||||
|
||||
#### Returns
|
||||
|
||||
`any`[]
|
||||
|
||||
___
|
||||
|
||||
### reducer
|
||||
|
||||
• `get` **reducer**(): (`state`: `unknown`, `action`: `Action`\<`any`\>) => `unknown`
|
||||
|
||||
#### Returns
|
||||
|
||||
`fn`
|
||||
|
||||
▸ (`state?`, `action`): `unknown`
|
||||
|
||||
##### Parameters
|
||||
|
||||
| Name | Type | Default value |
|
||||
| :------ | :------ | :------ |
|
||||
| `state` | `unknown` | `initialStateState` |
|
||||
| `action` | `Action`\<`any`\> | `undefined` |
|
||||
|
||||
##### Returns
|
||||
|
||||
`unknown`
|
||||
|
||||
___
|
||||
|
||||
### schema
|
||||
|
||||
• `get` **schema**(): `any`
|
||||
|
||||
#### Returns
|
||||
|
||||
`any`
|
||||
|
||||
___
|
||||
|
||||
### selectors
|
||||
|
||||
• `get` **selectors**(): `DuxSelectors`\<`D`\>
|
||||
|
||||
#### Returns
|
||||
|
||||
`DuxSelectors`\<`D`\>
|
||||
|
||||
___
|
||||
|
||||
### upreducer
|
||||
|
||||
• `get` **upreducer**(): (`action`: `any`) => (`state`: `any`) => `unknown`
|
||||
|
||||
#### Returns
|
||||
|
||||
`fn`
|
||||
|
||||
▸ (`action`): (`state`: `any`) => `unknown`
|
||||
|
||||
##### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `action` | `any` |
|
||||
|
||||
##### Returns
|
||||
|
||||
`fn`
|
||||
|
||||
▸ (`state`): `unknown`
|
||||
|
||||
##### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `state` | `any` |
|
||||
|
||||
##### Returns
|
||||
|
||||
`unknown`
|
||||
|
||||
## Methods
|
||||
|
||||
### addDefaultMutation
|
||||
|
||||
▸ **addDefaultMutation**(`mutation`, `terminal?`): `any`
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `mutation` | `Mutation`\<`any`, `DuxState`\<`D`\>\> |
|
||||
| `terminal?` | `boolean` |
|
||||
|
||||
#### Returns
|
||||
|
||||
`any`
|
||||
|
||||
___
|
||||
|
||||
### addEffect
|
||||
|
||||
▸ **addEffect**(`actionType`, `effect`): [`default`](default.md)\<`D`\>
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `actionType` | keyof `D` extends \{ `actions`: `A` } ? \{ [key in string \| number \| symbol]: key extends string ? ExpandedAction\<A[key], key\> : never } : {} \| keyof `UnionToIntersection`\<`D` extends \{ `subduxes`: `S` } ? `DuxActions`\<`S`[keyof `S`]\> : {}\> |
|
||||
| `effect` | `EffectMiddleware`\<`D`\> |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D`\>
|
||||
|
||||
▸ **addEffect**(`actionCreator`, `effect`): [`default`](default.md)\<`D`\>
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `actionCreator` | `Object` |
|
||||
| `actionCreator.match` | (`action`: `any`) => `boolean` |
|
||||
| `effect` | `EffectMiddleware`\<`D`\> |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D`\>
|
||||
|
||||
▸ **addEffect**(`guardFunc`, `effect`): [`default`](default.md)\<`D`\>
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `guardFunc` | (`action`: `AnyAction`) => `boolean` |
|
||||
| `effect` | `EffectMiddleware`\<`D`\> |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D`\>
|
||||
|
||||
▸ **addEffect**(`effect`): [`default`](default.md)\<`D`\>
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `effect` | `EffectMiddleware`\<`D`\> |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D`\>
|
||||
|
||||
___
|
||||
|
||||
### addMutation
|
||||
|
||||
▸ **addMutation**\<`A`\>(`matcher`, `mutation`, `terminal?`): [`default`](default.md)\<`D`\>
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `A` | extends `string` \| `number` \| `symbol` |
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `matcher` | `A` |
|
||||
| `mutation` | `Mutation`\<`DuxActions`\<`D`\>[`A`] extends (...`args`: `any`) => `P` ? `P` : `never`, `DuxState`\<`D`\>\> |
|
||||
| `terminal?` | `boolean` |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D`\>
|
||||
|
||||
▸ **addMutation**\<`A`\>(`matcher`, `mutation`, `terminal?`): [`default`](default.md)\<`D`\>
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `A` | extends `Action`\<`any`, `A`\> |
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `matcher` | (`action`: `A`) => `boolean` |
|
||||
| `mutation` | `Mutation`\<`A`, `DuxState`\<`D`\>\> |
|
||||
| `terminal?` | `boolean` |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D`\>
|
||||
|
||||
▸ **addMutation**\<`A`\>(`actionCreator`, `mutation`, `terminal?`): [`default`](default.md)\<`D`\>
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `A` | extends `ActionCreator`\<`any`, `any`[], `A`\> |
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `actionCreator` | `A` |
|
||||
| `mutation` | `Mutation`\<`ReturnType`\<`A`\>, `DuxState`\<`D`\>\> |
|
||||
| `terminal?` | `boolean` |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D`\>
|
||||
|
||||
___
|
||||
|
||||
### addReaction
|
||||
|
||||
▸ **addReaction**(`reaction`): `void`
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `reaction` | `any` |
|
||||
|
||||
#### Returns
|
||||
|
||||
`void`
|
||||
|
||||
___
|
||||
|
||||
### createStore
|
||||
|
||||
▸ **createStore**(`options?`): `ToolkitStore`\<`D` extends \{ `initialState`: `INITIAL_STATE` } ? `INITIAL_STATE` : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown` & {}, `AnyAction`, `Middlewares`\<`DuxState`\<`D`\>\>\> & `MiddlewareAPI`\<`Dispatch`\<`AnyAction`\>, `DuxState`\<`D`\>\> & \{ `actions`: `DuxActions`\<`D`\> ; `dispatch`: `DuxActions`\<`D`\> ; `getState`: `CurriedSelectors`\<`DuxSelectors`\<`D`\>\> ; `selectors`: `DuxSelectors`\<`D`\> }
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `options` | `Partial`\<\{ `buildMiddleware`: (`middleware`: `any`[]) => `any` ; `preloadedState`: `DuxState`\<`D`\> ; `validate`: `boolean` }\> |
|
||||
|
||||
#### Returns
|
||||
|
||||
`ToolkitStore`\<`D` extends \{ `initialState`: `INITIAL_STATE` } ? `INITIAL_STATE` : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown` & {}, `AnyAction`, `Middlewares`\<`DuxState`\<`D`\>\>\> & `MiddlewareAPI`\<`Dispatch`\<`AnyAction`\>, `DuxState`\<`D`\>\> & \{ `actions`: `DuxActions`\<`D`\> ; `dispatch`: `DuxActions`\<`D`\> ; `getState`: `CurriedSelectors`\<`DuxSelectors`\<`D`\>\> ; `selectors`: `DuxSelectors`\<`D`\> }
|
||||
|
||||
___
|
||||
|
||||
### toDux
|
||||
|
||||
▸ **toDux**(): `Object`
|
||||
|
||||
#### Returns
|
||||
|
||||
`Object`
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `actions` | `DuxActions`\<`D`\> |
|
||||
| `effects` | `any` |
|
||||
| `initialState` | `DuxState`\<`D`\> |
|
||||
| `reactions` | `any`[] |
|
||||
| `reducer` | (`state`: `unknown`, `action`: `Action`\<`any`\>) => `unknown` |
|
||||
| `schema` | `any` |
|
||||
| `selectors` | `DuxSelectors`\<`D`\> |
|
||||
| `upreducer` | (`action`: `any`) => (`state`: `any`) => `unknown` |
|
129
docs-docsidy/api/modules.md
Normal file
@ -0,0 +1,129 @@
|
||||
[updux](README.md) / Exports
|
||||
|
||||
# updux
|
||||
|
||||
## Classes
|
||||
|
||||
- [default](classes/default.md)
|
||||
|
||||
## Functions
|
||||
|
||||
### createAction
|
||||
|
||||
▸ **createAction**\<`P`, `T`\>(`type`): `PayloadActionCreator`\<`P`, `T`\>
|
||||
|
||||
A utility function to create an action creator for the given action type
|
||||
string. The action creator accepts a single argument, which will be included
|
||||
in the action object as a field called payload. The action creator function
|
||||
will also have its toString() overridden so that it returns the action type,
|
||||
allowing it to be used in reducer logic that is looking for that action type.
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `P` | `void` |
|
||||
| `T` | extends `string` = `string` |
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `type` | `T` | The action type to use for created actions. |
|
||||
|
||||
#### Returns
|
||||
|
||||
`PayloadActionCreator`\<`P`, `T`\>
|
||||
|
||||
▸ **createAction**\<`PA`, `T`\>(`type`, `prepareAction`): `PayloadActionCreator`\<`ReturnType`\<`PA`\>[``"payload"``], `T`, `PA`\>
|
||||
|
||||
A utility function to create an action creator for the given action type
|
||||
string. The action creator accepts a single argument, which will be included
|
||||
in the action object as a field called payload. The action creator function
|
||||
will also have its toString() overridden so that it returns the action type,
|
||||
allowing it to be used in reducer logic that is looking for that action type.
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `PA` | extends `PrepareAction`\<`any`\> |
|
||||
| `T` | extends `string` = `string` |
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `type` | `T` | The action type to use for created actions. |
|
||||
| `prepareAction` | `PA` | - |
|
||||
|
||||
#### Returns
|
||||
|
||||
`PayloadActionCreator`\<`ReturnType`\<`PA`\>[``"payload"``], `T`, `PA`\>
|
||||
|
||||
___
|
||||
|
||||
### withPayload
|
||||
|
||||
▸ **withPayload**\<`P`\>(): (`input`: `P`) => \{ `payload`: `P` }
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name |
|
||||
| :------ |
|
||||
| `P` |
|
||||
|
||||
#### Returns
|
||||
|
||||
`fn`
|
||||
|
||||
▸ (`input`): `Object`
|
||||
|
||||
##### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `input` | `P` |
|
||||
|
||||
##### Returns
|
||||
|
||||
`Object`
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `payload` | `P` |
|
||||
|
||||
▸ **withPayload**\<`P`, `A`\>(`prepare`): (...`input`: `A`) => \{ `payload`: `P` }
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `P` | `P` |
|
||||
| `A` | extends `any`[] |
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `prepare` | (...`args`: `A`) => `P` |
|
||||
|
||||
#### Returns
|
||||
|
||||
`fn`
|
||||
|
||||
▸ (`...input`): `Object`
|
||||
|
||||
##### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `...input` | `A` |
|
||||
|
||||
##### Returns
|
||||
|
||||
`Object`
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `payload` | `P` |
|
10233
docs-docsidy/docsify/docsify.js
Normal file
1
docs-docsidy/docsify/docsify.min.js
vendored
Normal file
55
docs-docsidy/docsify/plugins/disqus.js
Normal file
@ -0,0 +1,55 @@
|
||||
(function () {
|
||||
/* eslint-disable no-unused-vars */
|
||||
var fixedPath = location.href.replace('/-/', '/#/');
|
||||
if (fixedPath !== location.href) {
|
||||
location.href = fixedPath;
|
||||
}
|
||||
|
||||
function install(hook, vm) {
|
||||
var dom = Docsify.dom;
|
||||
var disqus = vm.config.disqus;
|
||||
if (!disqus) {
|
||||
throw Error('$docsify.disqus is required');
|
||||
}
|
||||
|
||||
hook.init(function (_) {
|
||||
var script = dom.create('script');
|
||||
|
||||
script.async = true;
|
||||
script.src = "https://" + disqus + ".disqus.com/embed.js";
|
||||
script.setAttribute('data-timestamp', Number(new Date()));
|
||||
dom.appendTo(dom.body, script);
|
||||
});
|
||||
|
||||
hook.mounted(function (_) {
|
||||
var div = dom.create('div');
|
||||
div.id = 'disqus_thread';
|
||||
var main = dom.getNode('#main');
|
||||
div.style = "width: " + (main.clientWidth) + "px; margin: 0 auto 20px;";
|
||||
dom.appendTo(dom.find('.content'), div);
|
||||
|
||||
// eslint-disable-next-line
|
||||
window.disqus_config = function() {
|
||||
this.page.url = location.origin + '/-' + vm.route.path;
|
||||
this.page.identifier = vm.route.path;
|
||||
this.page.title = document.title;
|
||||
};
|
||||
});
|
||||
|
||||
hook.doneEach(function (_) {
|
||||
if (typeof window.DISQUS !== 'undefined') {
|
||||
window.DISQUS.reset({
|
||||
reload: true,
|
||||
config: function () {
|
||||
this.page.url = location.origin + '/-' + vm.route.path;
|
||||
this.page.identifier = vm.route.path;
|
||||
this.page.title = document.title;
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$docsify.plugins = [].concat(install, $docsify.plugins);
|
||||
|
||||
}());
|
1
docs-docsidy/docsify/plugins/disqus.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
!function(){var i=location.href.replace("/-/","/#/");i!==location.href&&(location.href=i),$docsify.plugins=[].concat(function(i,o){var n=Docsify.dom,e=o.config.disqus;if(!e)throw Error("$docsify.disqus is required");i.init(function(i){var t=n.create("script");t.async=!0,t.src="https://"+e+".disqus.com/embed.js",t.setAttribute("data-timestamp",Number(new Date)),n.appendTo(n.body,t)}),i.mounted(function(i){var t=n.create("div");t.id="disqus_thread";var e=n.getNode("#main");t.style="width: "+e.clientWidth+"px; margin: 0 auto 20px;",n.appendTo(n.find(".content"),t),window.disqus_config=function(){this.page.url=location.origin+"/-"+o.route.path,this.page.identifier=o.route.path,this.page.title=document.title}}),i.doneEach(function(i){void 0!==window.DISQUS&&window.DISQUS.reset({reload:!0,config:function(){this.page.url=location.origin+"/-"+o.route.path,this.page.identifier=o.route.path,this.page.title=document.title}})})},$docsify.plugins)}();
|
1903
docs-docsidy/docsify/plugins/emoji.js
Normal file
1
docs-docsidy/docsify/plugins/emoji.min.js
vendored
Normal file
28
docs-docsidy/docsify/plugins/external-script.js
Normal file
@ -0,0 +1,28 @@
|
||||
(function () {
|
||||
function handleExternalScript() {
|
||||
var container = Docsify.dom.getNode('#main');
|
||||
var scripts = Docsify.dom.findAll(container, 'script');
|
||||
|
||||
for (var i = scripts.length; i--; ) {
|
||||
var script = scripts[i];
|
||||
|
||||
if (script && script.src) {
|
||||
var newScript = document.createElement('script');
|
||||
|
||||
Array.prototype.slice.call(script.attributes).forEach(function (attribute) {
|
||||
newScript[attribute.name] = attribute.value;
|
||||
});
|
||||
|
||||
script.parentNode.insertBefore(newScript, script);
|
||||
script.parentNode.removeChild(script);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var install = function (hook) {
|
||||
hook.doneEach(handleExternalScript);
|
||||
};
|
||||
|
||||
window.$docsify.plugins = [].concat(install, window.$docsify.plugins);
|
||||
|
||||
}());
|
1
docs-docsidy/docsify/plugins/external-script.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
!function(){function e(){for(var o=Docsify.dom.getNode("#main"),e=Docsify.dom.findAll(o,"script"),n=e.length;n--;){var i,t=e[n];t&&t.src&&(i=document.createElement("script"),Array.prototype.slice.call(t.attributes).forEach(function(o){i[o.name]=o.value}),t.parentNode.insertBefore(i,t),t.parentNode.removeChild(t))}}window.$docsify.plugins=[].concat(function(o){o.doneEach(e)},window.$docsify.plugins)}();
|
505
docs-docsidy/docsify/plugins/front-matter.js
Normal file
@ -0,0 +1,505 @@
|
||||
(function () {
|
||||
/**
|
||||
* Fork https://github.com/egoist/docute/blob/master/src/utils/yaml.js
|
||||
*/
|
||||
/* eslint-disable */
|
||||
/*
|
||||
YAML parser for Javascript
|
||||
Author: Diogo Costa
|
||||
This program is released under the MIT License as follows:
|
||||
Copyright (c) 2011 Diogo Costa (costa.h4evr@gmail.com)
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @name YAML
|
||||
* @namespace
|
||||
*/
|
||||
|
||||
var errors = [],
|
||||
reference_blocks = [],
|
||||
processing_time = 0,
|
||||
regex = {
|
||||
regLevel: new RegExp('^([\\s\\-]+)'),
|
||||
invalidLine: new RegExp('^\\-\\-\\-|^\\.\\.\\.|^\\s*#.*|^\\s*$'),
|
||||
dashesString: new RegExp('^\\s*\\"([^\\"]*)\\"\\s*$'),
|
||||
quotesString: new RegExp("^\\s*\\'([^\\']*)\\'\\s*$"),
|
||||
float: new RegExp('^[+-]?[0-9]+\\.[0-9]+(e[+-]?[0-9]+(\\.[0-9]+)?)?$'),
|
||||
integer: new RegExp('^[+-]?[0-9]+$'),
|
||||
array: new RegExp('\\[\\s*(.*)\\s*\\]'),
|
||||
map: new RegExp('\\{\\s*(.*)\\s*\\}'),
|
||||
key_value: new RegExp('([a-z0-9_-][ a-z0-9_-]*):( .+)', 'i'),
|
||||
single_key_value: new RegExp('^([a-z0-9_-][ a-z0-9_-]*):( .+?)$', 'i'),
|
||||
key: new RegExp('([a-z0-9_-][ a-z0-9_-]+):( .+)?', 'i'),
|
||||
item: new RegExp('^-\\s+'),
|
||||
trim: new RegExp('^\\s+|\\s+$'),
|
||||
comment: new RegExp('([^\\\'\\"#]+([\\\'\\"][^\\\'\\"]*[\\\'\\"])*)*(#.*)?')
|
||||
};
|
||||
|
||||
/**
|
||||
* @class A block of lines of a given level.
|
||||
* @param {int} lvl The block's level.
|
||||
* @private
|
||||
*/
|
||||
function Block(lvl) {
|
||||
return {
|
||||
/* The block's parent */
|
||||
parent: null,
|
||||
/* Number of children */
|
||||
length: 0,
|
||||
/* Block's level */
|
||||
level: lvl,
|
||||
/* Lines of code to process */
|
||||
lines: [],
|
||||
/* Blocks with greater level */
|
||||
children: [],
|
||||
/* Add a block to the children collection */
|
||||
addChild: function(obj) {
|
||||
this.children.push(obj);
|
||||
obj.parent = this;
|
||||
++this.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parser(str) {
|
||||
var regLevel = regex['regLevel'];
|
||||
var invalidLine = regex['invalidLine'];
|
||||
var lines = str.split('\n');
|
||||
var m;
|
||||
var level = 0,
|
||||
curLevel = 0;
|
||||
|
||||
var blocks = [];
|
||||
|
||||
var result = new Block(-1);
|
||||
var currentBlock = new Block(0);
|
||||
result.addChild(currentBlock);
|
||||
var levels = [];
|
||||
var line = '';
|
||||
|
||||
blocks.push(currentBlock);
|
||||
levels.push(level);
|
||||
|
||||
for (var i = 0, len = lines.length; i < len; ++i) {
|
||||
line = lines[i];
|
||||
|
||||
if (line.match(invalidLine)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ((m = regLevel.exec(line))) {
|
||||
level = m[1].length;
|
||||
} else { level = 0; }
|
||||
|
||||
if (level > curLevel) {
|
||||
var oldBlock = currentBlock;
|
||||
currentBlock = new Block(level);
|
||||
oldBlock.addChild(currentBlock);
|
||||
blocks.push(currentBlock);
|
||||
levels.push(level);
|
||||
} else if (level < curLevel) {
|
||||
var added = false;
|
||||
|
||||
var k = levels.length - 1;
|
||||
for (; k >= 0; --k) {
|
||||
if (levels[k] == level) {
|
||||
currentBlock = new Block(level);
|
||||
blocks.push(currentBlock);
|
||||
levels.push(level);
|
||||
if (blocks[k].parent != null) { blocks[k].parent.addChild(currentBlock); }
|
||||
added = true;
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!added) {
|
||||
errors.push('Error: Invalid indentation at line ' + i + ': ' + line);
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
currentBlock.lines.push(line.replace(regex['trim'], ''));
|
||||
curLevel = level;
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function processValue(val) {
|
||||
val = val.replace(regex['trim'], '');
|
||||
var m = null;
|
||||
|
||||
if (val == 'true') {
|
||||
return true
|
||||
} else if (val == 'false') {
|
||||
return false
|
||||
} else if (val == '.NaN') {
|
||||
return Number.NaN
|
||||
} else if (val == 'null') {
|
||||
return null
|
||||
} else if (val == '.inf') {
|
||||
return Number.POSITIVE_INFINITY
|
||||
} else if (val == '-.inf') {
|
||||
return Number.NEGATIVE_INFINITY
|
||||
} else if ((m = val.match(regex['dashesString']))) {
|
||||
return m[1]
|
||||
} else if ((m = val.match(regex['quotesString']))) {
|
||||
return m[1]
|
||||
} else if ((m = val.match(regex['float']))) {
|
||||
return parseFloat(m[0])
|
||||
} else if ((m = val.match(regex['integer']))) {
|
||||
return parseInt(m[0])
|
||||
} else if (!isNaN((m = Date.parse(val)))) {
|
||||
return new Date(m)
|
||||
} else if ((m = val.match(regex['single_key_value']))) {
|
||||
var res = {};
|
||||
res[m[1]] = processValue(m[2]);
|
||||
return res
|
||||
} else if ((m = val.match(regex['array']))) {
|
||||
var count = 0,
|
||||
c = ' ';
|
||||
var res = [];
|
||||
var content = '';
|
||||
var str = false;
|
||||
for (var j = 0, lenJ = m[1].length; j < lenJ; ++j) {
|
||||
c = m[1][j];
|
||||
if (c == "'" || c == '"') {
|
||||
if (str === false) {
|
||||
str = c;
|
||||
content += c;
|
||||
continue
|
||||
} else if ((c == "'" && str == "'") || (c == '"' && str == '"')) {
|
||||
str = false;
|
||||
content += c;
|
||||
continue
|
||||
}
|
||||
} else if (str === false && (c == '[' || c == '{')) {
|
||||
++count;
|
||||
} else if (str === false && (c == ']' || c == '}')) {
|
||||
--count;
|
||||
} else if (str === false && count == 0 && c == ',') {
|
||||
res.push(processValue(content));
|
||||
content = '';
|
||||
continue
|
||||
}
|
||||
|
||||
content += c;
|
||||
}
|
||||
|
||||
if (content.length > 0) { res.push(processValue(content)); }
|
||||
return res
|
||||
} else if ((m = val.match(regex['map']))) {
|
||||
var count = 0,
|
||||
c = ' ';
|
||||
var res = [];
|
||||
var content = '';
|
||||
var str = false;
|
||||
for (var j = 0, lenJ = m[1].length; j < lenJ; ++j) {
|
||||
c = m[1][j];
|
||||
if (c == "'" || c == '"') {
|
||||
if (str === false) {
|
||||
str = c;
|
||||
content += c;
|
||||
continue
|
||||
} else if ((c == "'" && str == "'") || (c == '"' && str == '"')) {
|
||||
str = false;
|
||||
content += c;
|
||||
continue
|
||||
}
|
||||
} else if (str === false && (c == '[' || c == '{')) {
|
||||
++count;
|
||||
} else if (str === false && (c == ']' || c == '}')) {
|
||||
--count;
|
||||
} else if (str === false && count == 0 && c == ',') {
|
||||
res.push(content);
|
||||
content = '';
|
||||
continue
|
||||
}
|
||||
|
||||
content += c;
|
||||
}
|
||||
|
||||
if (content.length > 0) { res.push(content); }
|
||||
|
||||
var newRes = {};
|
||||
for (var j = 0, lenJ = res.length; j < lenJ; ++j) {
|
||||
if ((m = res[j].match(regex['key_value']))) {
|
||||
newRes[m[1]] = processValue(m[2]);
|
||||
}
|
||||
}
|
||||
|
||||
return newRes
|
||||
} else { return val }
|
||||
}
|
||||
|
||||
function processFoldedBlock(block) {
|
||||
var lines = block.lines;
|
||||
var children = block.children;
|
||||
var str = lines.join(' ');
|
||||
var chunks = [str];
|
||||
for (var i = 0, len = children.length; i < len; ++i) {
|
||||
chunks.push(processFoldedBlock(children[i]));
|
||||
}
|
||||
return chunks.join('\n')
|
||||
}
|
||||
|
||||
function processLiteralBlock(block) {
|
||||
var lines = block.lines;
|
||||
var children = block.children;
|
||||
var str = lines.join('\n');
|
||||
for (var i = 0, len = children.length; i < len; ++i) {
|
||||
str += processLiteralBlock(children[i]);
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
function processBlock(blocks) {
|
||||
var m = null;
|
||||
var res = {};
|
||||
var lines = null;
|
||||
var children = null;
|
||||
var currentObj = null;
|
||||
|
||||
var level = -1;
|
||||
|
||||
var processedBlocks = [];
|
||||
|
||||
var isMap = true;
|
||||
|
||||
for (var j = 0, lenJ = blocks.length; j < lenJ; ++j) {
|
||||
if (level != -1 && level != blocks[j].level) { continue }
|
||||
|
||||
processedBlocks.push(j);
|
||||
|
||||
level = blocks[j].level;
|
||||
lines = blocks[j].lines;
|
||||
children = blocks[j].children;
|
||||
currentObj = null;
|
||||
|
||||
for (var i = 0, len = lines.length; i < len; ++i) {
|
||||
var line = lines[i];
|
||||
|
||||
if ((m = line.match(regex['key']))) {
|
||||
var key = m[1];
|
||||
|
||||
if (key[0] == '-') {
|
||||
key = key.replace(regex['item'], '');
|
||||
if (isMap) {
|
||||
isMap = false;
|
||||
if (typeof res.length === 'undefined') {
|
||||
res = [];
|
||||
}
|
||||
}
|
||||
if (currentObj != null) { res.push(currentObj); }
|
||||
currentObj = {};
|
||||
isMap = true;
|
||||
}
|
||||
|
||||
if (typeof m[2] != 'undefined') {
|
||||
var value = m[2].replace(regex['trim'], '');
|
||||
if (value[0] == '&') {
|
||||
var nb = processBlock(children);
|
||||
if (currentObj != null) { currentObj[key] = nb; }
|
||||
else { res[key] = nb; }
|
||||
reference_blocks[value.substr(1)] = nb;
|
||||
} else if (value[0] == '|') {
|
||||
if (currentObj != null)
|
||||
{ currentObj[key] = processLiteralBlock(children.shift()); }
|
||||
else { res[key] = processLiteralBlock(children.shift()); }
|
||||
} else if (value[0] == '*') {
|
||||
var v = value.substr(1);
|
||||
var no = {};
|
||||
|
||||
if (typeof reference_blocks[v] == 'undefined') {
|
||||
errors.push("Reference '" + v + "' not found!");
|
||||
} else {
|
||||
for (var k in reference_blocks[v]) {
|
||||
no[k] = reference_blocks[v][k];
|
||||
}
|
||||
|
||||
if (currentObj != null) { currentObj[key] = no; }
|
||||
else { res[key] = no; }
|
||||
}
|
||||
} else if (value[0] == '>') {
|
||||
if (currentObj != null)
|
||||
{ currentObj[key] = processFoldedBlock(children.shift()); }
|
||||
else { res[key] = processFoldedBlock(children.shift()); }
|
||||
} else {
|
||||
if (currentObj != null) { currentObj[key] = processValue(value); }
|
||||
else { res[key] = processValue(value); }
|
||||
}
|
||||
} else {
|
||||
if (currentObj != null) { currentObj[key] = processBlock(children); }
|
||||
else { res[key] = processBlock(children); }
|
||||
}
|
||||
} else if (line.match(/^-\s*$/)) {
|
||||
if (isMap) {
|
||||
isMap = false;
|
||||
if (typeof res.length === 'undefined') {
|
||||
res = [];
|
||||
}
|
||||
}
|
||||
if (currentObj != null) { res.push(currentObj); }
|
||||
currentObj = {};
|
||||
isMap = true;
|
||||
continue
|
||||
} else if ((m = line.match(/^-\s*(.*)/))) {
|
||||
if (currentObj != null) { currentObj.push(processValue(m[1])); }
|
||||
else {
|
||||
if (isMap) {
|
||||
isMap = false;
|
||||
if (typeof res.length === 'undefined') {
|
||||
res = [];
|
||||
}
|
||||
}
|
||||
res.push(processValue(m[1]));
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (currentObj != null) {
|
||||
if (isMap) {
|
||||
isMap = false;
|
||||
if (typeof res.length === 'undefined') {
|
||||
res = [];
|
||||
}
|
||||
}
|
||||
res.push(currentObj);
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = processedBlocks.length - 1; j >= 0; --j) {
|
||||
blocks.splice.call(blocks, processedBlocks[j], 1);
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
function semanticAnalysis(blocks) {
|
||||
var res = processBlock(blocks.children);
|
||||
return res
|
||||
}
|
||||
|
||||
function preProcess(src) {
|
||||
var m;
|
||||
var lines = src.split('\n');
|
||||
|
||||
var r = regex['comment'];
|
||||
|
||||
for (var i in lines) {
|
||||
if ((m = lines[i].match(r))) {
|
||||
/* var cmt = "";
|
||||
if(typeof m[3] != "undefined")
|
||||
lines[i] = m[1];
|
||||
else if(typeof m[3] != "undefined")
|
||||
lines[i] = m[3];
|
||||
else
|
||||
lines[i] = "";
|
||||
*/
|
||||
if (typeof m[3] !== 'undefined') {
|
||||
lines[i] = m[0].substr(0, m[0].length - m[3].length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function load(str) {
|
||||
errors = [];
|
||||
reference_blocks = [];
|
||||
processing_time = new Date().getTime();
|
||||
var pre = preProcess(str);
|
||||
var doc = parser(pre);
|
||||
var res = semanticAnalysis(doc);
|
||||
processing_time = new Date().getTime() - processing_time;
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* Fork https://github.com/egoist/docute/blob/master/src/utils/front-matter.js
|
||||
*/
|
||||
|
||||
var optionalByteOrderMark = '\\ufeff?';
|
||||
var pattern =
|
||||
'^(' +
|
||||
optionalByteOrderMark +
|
||||
'(= yaml =|---)' +
|
||||
'$([\\s\\S]*?)' +
|
||||
'(?:\\2|\\.\\.\\.)' +
|
||||
'$' +
|
||||
'' +
|
||||
'(?:\\n)?)';
|
||||
// NOTE: If this pattern uses the 'g' flag the `regex` variable definition will
|
||||
// need to be moved down into the functions that use it.
|
||||
var regex$1 = new RegExp(pattern, 'm');
|
||||
|
||||
function extractor(string) {
|
||||
string = string || '';
|
||||
|
||||
var lines = string.split(/(\r?\n)/);
|
||||
if (lines[0] && /= yaml =|---/.test(lines[0])) {
|
||||
return parse(string)
|
||||
} else {
|
||||
return { attributes: {}, body: string }
|
||||
}
|
||||
}
|
||||
|
||||
function parse(string) {
|
||||
var match = regex$1.exec(string);
|
||||
|
||||
if (!match) {
|
||||
return {
|
||||
attributes: {},
|
||||
body: string
|
||||
}
|
||||
}
|
||||
|
||||
var yaml = match[match.length - 1].replace(/^\s+|\s+$/g, '');
|
||||
var attributes = load(yaml) || {};
|
||||
var body = string.replace(match[0], '');
|
||||
|
||||
return { attributes: attributes, body: body, frontmatter: yaml }
|
||||
}
|
||||
|
||||
var install = function (hook, vm) {
|
||||
// Used to remove front matter from embedded pages if installed.
|
||||
vm.config.frontMatter = {};
|
||||
vm.config.frontMatter.installed = true;
|
||||
vm.config.frontMatter.parseMarkdown = function (content) {
|
||||
var ref = extractor(content);
|
||||
var body = ref.body;
|
||||
return body;
|
||||
};
|
||||
|
||||
hook.beforeEach(function (content) {
|
||||
var ref = extractor(content);
|
||||
var attributes = ref.attributes;
|
||||
var body = ref.body;
|
||||
|
||||
vm.frontmatter = attributes;
|
||||
|
||||
return body;
|
||||
});
|
||||
};
|
||||
|
||||
$docsify.plugins = [].concat(install, $docsify.plugins);
|
||||
|
||||
}());
|
1
docs-docsidy/docsify/plugins/front-matter.min.js
vendored
Normal file
43
docs-docsidy/docsify/plugins/ga.js
Normal file
@ -0,0 +1,43 @@
|
||||
(function () {
|
||||
/* eslint-disable no-console */
|
||||
// From https://github.com/egoist/vue-ga/blob/master/src/index.js
|
||||
function appendScript() {
|
||||
var script = document.createElement('script');
|
||||
script.async = true;
|
||||
script.src = 'https://www.google-analytics.com/analytics.js';
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
|
||||
function init(id) {
|
||||
appendScript();
|
||||
window.ga =
|
||||
window.ga ||
|
||||
function () {
|
||||
(window.ga.q = window.ga.q || []).push(arguments);
|
||||
};
|
||||
|
||||
window.ga.l = Number(new Date());
|
||||
window.ga('create', id, 'auto');
|
||||
}
|
||||
|
||||
function collect() {
|
||||
if (!window.ga) {
|
||||
init($docsify.ga);
|
||||
}
|
||||
|
||||
window.ga('set', 'page', location.hash);
|
||||
window.ga('send', 'pageview');
|
||||
}
|
||||
|
||||
var install = function (hook) {
|
||||
if (!$docsify.ga) {
|
||||
console.error('[Docsify] ga is required.');
|
||||
return;
|
||||
}
|
||||
|
||||
hook.beforeEach(collect);
|
||||
};
|
||||
|
||||
$docsify.plugins = [].concat(install, $docsify.plugins);
|
||||
|
||||
}());
|
1
docs-docsidy/docsify/plugins/ga.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
!function(){function n(n){var o;(o=document.createElement("script")).async=!0,o.src="https://www.google-analytics.com/analytics.js",document.body.appendChild(o),window.ga=window.ga||function(){(window.ga.q=window.ga.q||[]).push(arguments)},window.ga.l=Number(new Date),window.ga("create",n,"auto")}function o(){window.ga||n($docsify.ga),window.ga("set","page",location.hash),window.ga("send","pageview")}$docsify.plugins=[].concat(function(n){$docsify.ga?n.beforeEach(o):console.error("[Docsify] ga is required.")},$docsify.plugins)}();
|
27
docs-docsidy/docsify/plugins/gitalk.js
Normal file
@ -0,0 +1,27 @@
|
||||
(function () {
|
||||
/* eslint-disable no-unused-vars */
|
||||
function install(hook) {
|
||||
var dom = Docsify.dom;
|
||||
|
||||
hook.mounted(function (_) {
|
||||
var div = dom.create('div');
|
||||
div.id = 'gitalk-container';
|
||||
var main = dom.getNode('#main');
|
||||
div.style = "width: " + (main.clientWidth) + "px; margin: 0 auto 20px;";
|
||||
dom.appendTo(dom.find('.content'), div);
|
||||
});
|
||||
|
||||
hook.doneEach(function (_) {
|
||||
var el = document.getElementById('gitalk-container');
|
||||
while (el.hasChildNodes()) {
|
||||
el.removeChild(el.firstChild);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
gitalk.render('gitalk-container');
|
||||
});
|
||||
}
|
||||
|
||||
$docsify.plugins = [].concat(install, $docsify.plugins);
|
||||
|
||||
}());
|
1
docs-docsidy/docsify/plugins/gitalk.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
$docsify.plugins=[].concat(function(i){var e=Docsify.dom;i.mounted(function(i){var n=e.create("div");n.id="gitalk-container";var t=e.getNode("#main");n.style="width: "+t.clientWidth+"px; margin: 0 auto 20px;",e.appendTo(e.find(".content"),n)}),i.doneEach(function(i){for(var n=document.getElementById("gitalk-container");n.hasChildNodes();)n.removeChild(n.firstChild);gitalk.render("gitalk-container")})},$docsify.plugins);
|
42
docs-docsidy/docsify/plugins/matomo.js
Normal file
@ -0,0 +1,42 @@
|
||||
(function () {
|
||||
function appendScript(options) {
|
||||
var script = document.createElement('script');
|
||||
script.async = true;
|
||||
script.src = options.host + '/matomo.js';
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
|
||||
function init(options) {
|
||||
window._paq = window._paq || [];
|
||||
window._paq.push(['trackPageView']);
|
||||
window._paq.push(['enableLinkTracking']);
|
||||
setTimeout(function () {
|
||||
appendScript(options);
|
||||
window._paq.push(['setTrackerUrl', options.host + '/matomo.php']);
|
||||
window._paq.push(['setSiteId', String(options.id)]);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function collect() {
|
||||
if (!window._paq) {
|
||||
init($docsify.matomo);
|
||||
}
|
||||
|
||||
window._paq.push(['setCustomUrl', window.location.hash.substr(1)]);
|
||||
window._paq.push(['setDocumentTitle', document.title]);
|
||||
window._paq.push(['trackPageView']);
|
||||
}
|
||||
|
||||
var install = function (hook) {
|
||||
if (!$docsify.matomo) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('[Docsify] matomo is required.');
|
||||
return;
|
||||
}
|
||||
|
||||
hook.beforeEach(collect);
|
||||
};
|
||||
|
||||
$docsify.plugins = [].concat(install, $docsify.plugins);
|
||||
|
||||
}());
|
1
docs-docsidy/docsify/plugins/matomo.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
!function(){function o(n){window._paq=window._paq||[],window._paq.push(["trackPageView"]),window._paq.push(["enableLinkTracking"]),setTimeout(function(){var o,i;o=n,(i=document.createElement("script")).async=!0,i.src=o.host+"/matomo.js",document.body.appendChild(i),window._paq.push(["setTrackerUrl",n.host+"/matomo.php"]),window._paq.push(["setSiteId",String(n.id)])},0)}function i(){window._paq||o($docsify.matomo),window._paq.push(["setCustomUrl",window.location.hash.substr(1)]),window._paq.push(["setDocumentTitle",document.title]),window._paq.push(["trackPageView"])}$docsify.plugins=[].concat(function(o){$docsify.matomo?o.beforeEach(i):console.error("[Docsify] matomo is required.")},$docsify.plugins)}();
|
540
docs-docsidy/docsify/plugins/search.js
Normal file
@ -0,0 +1,540 @@
|
||||
(function () {
|
||||
/**
|
||||
* Converts a colon formatted string to a object with properties.
|
||||
*
|
||||
* This is process a provided string and look for any tokens in the format
|
||||
* of `:name[=value]` and then convert it to a object and return.
|
||||
* An example of this is ':include :type=code :fragment=demo' is taken and
|
||||
* then converted to:
|
||||
*
|
||||
* ```
|
||||
* {
|
||||
* include: '',
|
||||
* type: 'code',
|
||||
* fragment: 'demo'
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param {string} str The string to parse.
|
||||
*
|
||||
* @return {object} The original string and parsed object, { str, config }.
|
||||
*/
|
||||
function getAndRemoveConfig(str) {
|
||||
if ( str === void 0 ) str = '';
|
||||
|
||||
var config = {};
|
||||
|
||||
if (str) {
|
||||
str = str
|
||||
.replace(/^('|")/, '')
|
||||
.replace(/('|")$/, '')
|
||||
.replace(/(?:^|\s):([\w-]+:?)=?([\w-%]+)?/g, function (m, key, value) {
|
||||
if (key.indexOf(':') === -1) {
|
||||
config[key] = (value && value.replace(/"/g, '')) || true;
|
||||
return '';
|
||||
}
|
||||
|
||||
return m;
|
||||
})
|
||||
.trim();
|
||||
}
|
||||
|
||||
return { str: str, config: config };
|
||||
}
|
||||
|
||||
function removeDocsifyIgnoreTag(str) {
|
||||
return str
|
||||
.replace(/<!-- {docsify-ignore} -->/, '')
|
||||
.replace(/{docsify-ignore}/, '')
|
||||
.replace(/<!-- {docsify-ignore-all} -->/, '')
|
||||
.replace(/{docsify-ignore-all}/, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
var INDEXS = {};
|
||||
|
||||
var LOCAL_STORAGE = {
|
||||
EXPIRE_KEY: 'docsify.search.expires',
|
||||
INDEX_KEY: 'docsify.search.index',
|
||||
};
|
||||
|
||||
function resolveExpireKey(namespace) {
|
||||
return namespace
|
||||
? ((LOCAL_STORAGE.EXPIRE_KEY) + "/" + namespace)
|
||||
: LOCAL_STORAGE.EXPIRE_KEY;
|
||||
}
|
||||
|
||||
function resolveIndexKey(namespace) {
|
||||
return namespace
|
||||
? ((LOCAL_STORAGE.INDEX_KEY) + "/" + namespace)
|
||||
: LOCAL_STORAGE.INDEX_KEY;
|
||||
}
|
||||
|
||||
function escapeHtml(string) {
|
||||
var entityMap = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
};
|
||||
|
||||
return String(string).replace(/[&<>"']/g, function (s) { return entityMap[s]; });
|
||||
}
|
||||
|
||||
function getAllPaths(router) {
|
||||
var paths = [];
|
||||
|
||||
Docsify.dom
|
||||
.findAll('.sidebar-nav a:not(.section-link):not([data-nosearch])')
|
||||
.forEach(function (node) {
|
||||
var href = node.href;
|
||||
var originHref = node.getAttribute('href');
|
||||
var path = router.parse(href).path;
|
||||
|
||||
if (
|
||||
path &&
|
||||
paths.indexOf(path) === -1 &&
|
||||
!Docsify.util.isAbsolutePath(originHref)
|
||||
) {
|
||||
paths.push(path);
|
||||
}
|
||||
});
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
function getTableData(token) {
|
||||
if (!token.text && token.type === 'table') {
|
||||
token.cells.unshift(token.header);
|
||||
token.text = token.cells
|
||||
.map(function (rows) {
|
||||
return rows.join(' | ');
|
||||
})
|
||||
.join(' |\n ');
|
||||
}
|
||||
return token.text;
|
||||
}
|
||||
|
||||
function getListData(token) {
|
||||
if (!token.text && token.type === 'list') {
|
||||
token.text = token.raw;
|
||||
}
|
||||
return token.text;
|
||||
}
|
||||
|
||||
function saveData(maxAge, expireKey, indexKey) {
|
||||
localStorage.setItem(expireKey, Date.now() + maxAge);
|
||||
localStorage.setItem(indexKey, JSON.stringify(INDEXS));
|
||||
}
|
||||
|
||||
function genIndex(path, content, router, depth) {
|
||||
if ( content === void 0 ) content = '';
|
||||
|
||||
var tokens = window.marked.lexer(content);
|
||||
var slugify = window.Docsify.slugify;
|
||||
var index = {};
|
||||
var slug;
|
||||
var title = '';
|
||||
|
||||
tokens.forEach(function (token, tokenIndex) {
|
||||
if (token.type === 'heading' && token.depth <= depth) {
|
||||
var ref = getAndRemoveConfig(token.text);
|
||||
var str = ref.str;
|
||||
var config = ref.config;
|
||||
|
||||
var text = removeDocsifyIgnoreTag(token.text);
|
||||
|
||||
if (config.id) {
|
||||
slug = router.toURL(path, { id: slugify(config.id) });
|
||||
} else {
|
||||
slug = router.toURL(path, { id: slugify(escapeHtml(text)) });
|
||||
}
|
||||
|
||||
if (str) {
|
||||
title = removeDocsifyIgnoreTag(str);
|
||||
}
|
||||
|
||||
index[slug] = { slug: slug, title: title, body: '' };
|
||||
} else {
|
||||
if (tokenIndex === 0) {
|
||||
slug = router.toURL(path);
|
||||
index[slug] = {
|
||||
slug: slug,
|
||||
title: path !== '/' ? path.slice(1) : 'Home Page',
|
||||
body: token.text || '',
|
||||
};
|
||||
}
|
||||
|
||||
if (!slug) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!index[slug]) {
|
||||
index[slug] = { slug: slug, title: '', body: '' };
|
||||
} else if (index[slug].body) {
|
||||
token.text = getTableData(token);
|
||||
token.text = getListData(token);
|
||||
|
||||
index[slug].body += '\n' + (token.text || '');
|
||||
} else {
|
||||
token.text = getTableData(token);
|
||||
token.text = getListData(token);
|
||||
|
||||
index[slug].body = token.text || '';
|
||||
}
|
||||
}
|
||||
});
|
||||
slugify.clear();
|
||||
return index;
|
||||
}
|
||||
|
||||
function ignoreDiacriticalMarks(keyword) {
|
||||
if (keyword && keyword.normalize) {
|
||||
return keyword.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
||||
}
|
||||
return keyword;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} query Search query
|
||||
* @returns {Array} Array of results
|
||||
*/
|
||||
function search(query) {
|
||||
var matchingResults = [];
|
||||
var data = [];
|
||||
Object.keys(INDEXS).forEach(function (key) {
|
||||
data = data.concat(Object.keys(INDEXS[key]).map(function (page) { return INDEXS[key][page]; }));
|
||||
});
|
||||
|
||||
query = query.trim();
|
||||
var keywords = query.split(/[\s\-,\\/]+/);
|
||||
if (keywords.length !== 1) {
|
||||
keywords = [].concat(query, keywords);
|
||||
}
|
||||
|
||||
var loop = function ( i ) {
|
||||
var post = data[i];
|
||||
var matchesScore = 0;
|
||||
var resultStr = '';
|
||||
var handlePostTitle = '';
|
||||
var handlePostContent = '';
|
||||
var postTitle = post.title && post.title.trim();
|
||||
var postContent = post.body && post.body.trim();
|
||||
var postUrl = post.slug || '';
|
||||
|
||||
if (postTitle) {
|
||||
keywords.forEach(function (keyword) {
|
||||
// From https://github.com/sindresorhus/escape-string-regexp
|
||||
var regEx = new RegExp(
|
||||
escapeHtml(ignoreDiacriticalMarks(keyword)).replace(
|
||||
/[|\\{}()[\]^$+*?.]/g,
|
||||
'\\$&'
|
||||
),
|
||||
'gi'
|
||||
);
|
||||
var indexTitle = -1;
|
||||
var indexContent = -1;
|
||||
handlePostTitle = postTitle
|
||||
? escapeHtml(ignoreDiacriticalMarks(postTitle))
|
||||
: postTitle;
|
||||
handlePostContent = postContent
|
||||
? escapeHtml(ignoreDiacriticalMarks(postContent))
|
||||
: postContent;
|
||||
|
||||
indexTitle = postTitle ? handlePostTitle.search(regEx) : -1;
|
||||
indexContent = postContent ? handlePostContent.search(regEx) : -1;
|
||||
|
||||
if (indexTitle >= 0 || indexContent >= 0) {
|
||||
matchesScore += indexTitle >= 0 ? 3 : indexContent >= 0 ? 2 : 0;
|
||||
if (indexContent < 0) {
|
||||
indexContent = 0;
|
||||
}
|
||||
|
||||
var start = 0;
|
||||
var end = 0;
|
||||
|
||||
start = indexContent < 11 ? 0 : indexContent - 10;
|
||||
end = start === 0 ? 70 : indexContent + keyword.length + 60;
|
||||
|
||||
if (postContent && end > postContent.length) {
|
||||
end = postContent.length;
|
||||
}
|
||||
|
||||
var matchContent =
|
||||
handlePostContent &&
|
||||
'...' +
|
||||
handlePostContent
|
||||
.substring(start, end)
|
||||
.replace(
|
||||
regEx,
|
||||
function (word) { return ("<em class=\"search-keyword\">" + word + "</em>"); }
|
||||
) +
|
||||
'...';
|
||||
|
||||
resultStr += matchContent;
|
||||
}
|
||||
});
|
||||
|
||||
if (matchesScore > 0) {
|
||||
var matchingPost = {
|
||||
title: handlePostTitle,
|
||||
content: postContent ? resultStr : '',
|
||||
url: postUrl,
|
||||
score: matchesScore,
|
||||
};
|
||||
|
||||
matchingResults.push(matchingPost);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0; i < data.length; i++) loop( i );
|
||||
|
||||
return matchingResults.sort(function (r1, r2) { return r2.score - r1.score; });
|
||||
}
|
||||
|
||||
function init(config, vm) {
|
||||
var isAuto = config.paths === 'auto';
|
||||
var paths = isAuto ? getAllPaths(vm.router) : config.paths;
|
||||
|
||||
var namespaceSuffix = '';
|
||||
|
||||
// only in auto mode
|
||||
if (paths.length && isAuto && config.pathNamespaces) {
|
||||
var path = paths[0];
|
||||
|
||||
if (Array.isArray(config.pathNamespaces)) {
|
||||
namespaceSuffix =
|
||||
config.pathNamespaces.filter(
|
||||
function (prefix) { return path.slice(0, prefix.length) === prefix; }
|
||||
)[0] || namespaceSuffix;
|
||||
} else if (config.pathNamespaces instanceof RegExp) {
|
||||
var matches = path.match(config.pathNamespaces);
|
||||
|
||||
if (matches) {
|
||||
namespaceSuffix = matches[0];
|
||||
}
|
||||
}
|
||||
var isExistHome = paths.indexOf(namespaceSuffix + '/') === -1;
|
||||
var isExistReadme = paths.indexOf(namespaceSuffix + '/README') === -1;
|
||||
if (isExistHome && isExistReadme) {
|
||||
paths.unshift(namespaceSuffix + '/');
|
||||
}
|
||||
} else if (paths.indexOf('/') === -1 && paths.indexOf('/README') === -1) {
|
||||
paths.unshift('/');
|
||||
}
|
||||
|
||||
var expireKey = resolveExpireKey(config.namespace) + namespaceSuffix;
|
||||
var indexKey = resolveIndexKey(config.namespace) + namespaceSuffix;
|
||||
|
||||
var isExpired = localStorage.getItem(expireKey) < Date.now();
|
||||
|
||||
INDEXS = JSON.parse(localStorage.getItem(indexKey));
|
||||
|
||||
if (isExpired) {
|
||||
INDEXS = {};
|
||||
} else if (!isAuto) {
|
||||
return;
|
||||
}
|
||||
|
||||
var len = paths.length;
|
||||
var count = 0;
|
||||
|
||||
paths.forEach(function (path) {
|
||||
if (INDEXS[path]) {
|
||||
return count++;
|
||||
}
|
||||
|
||||
Docsify.get(vm.router.getFile(path), false, vm.config.requestHeaders).then(
|
||||
function (result) {
|
||||
INDEXS[path] = genIndex(path, result, vm.router, config.depth);
|
||||
len === ++count && saveData(config.maxAge, expireKey, indexKey);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
var NO_DATA_TEXT = '';
|
||||
var options;
|
||||
|
||||
function style() {
|
||||
var code = "\n.sidebar {\n padding-top: 0;\n}\n\n.search {\n margin-bottom: 20px;\n padding: 6px;\n border-bottom: 1px solid #eee;\n}\n\n.search .input-wrap {\n display: flex;\n align-items: center;\n}\n\n.search .results-panel {\n display: none;\n}\n\n.search .results-panel.show {\n display: block;\n}\n\n.search input {\n outline: none;\n border: none;\n width: 100%;\n padding: 0.6em 7px;\n font-size: inherit;\n border: 1px solid transparent;\n}\n\n.search input:focus {\n box-shadow: 0 0 5px var(--theme-color, #42b983);\n border: 1px solid var(--theme-color, #42b983);\n}\n\n.search input::-webkit-search-decoration,\n.search input::-webkit-search-cancel-button,\n.search input {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\n.search input::-ms-clear {\n display: none;\n height: 0;\n width: 0;\n}\n\n.search .clear-button {\n cursor: pointer;\n width: 36px;\n text-align: right;\n display: none;\n}\n\n.search .clear-button.show {\n display: block;\n}\n\n.search .clear-button svg {\n transform: scale(.5);\n}\n\n.search h2 {\n font-size: 17px;\n margin: 10px 0;\n}\n\n.search a {\n text-decoration: none;\n color: inherit;\n}\n\n.search .matching-post {\n border-bottom: 1px solid #eee;\n}\n\n.search .matching-post:last-child {\n border-bottom: 0;\n}\n\n.search p {\n font-size: 14px;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n.search p.empty {\n text-align: center;\n}\n\n.app-name.hide, .sidebar-nav.hide {\n display: none;\n}";
|
||||
|
||||
Docsify.dom.style(code);
|
||||
}
|
||||
|
||||
function tpl(defaultValue) {
|
||||
if ( defaultValue === void 0 ) defaultValue = '';
|
||||
|
||||
var html = "<div class=\"input-wrap\">\n <input type=\"search\" value=\"" + defaultValue + "\" aria-label=\"Search text\" />\n <div class=\"clear-button\">\n <svg width=\"26\" height=\"24\">\n <circle cx=\"12\" cy=\"12\" r=\"11\" fill=\"#ccc\" />\n <path stroke=\"white\" stroke-width=\"2\" d=\"M8.25,8.25,15.75,15.75\" />\n <path stroke=\"white\" stroke-width=\"2\"d=\"M8.25,15.75,15.75,8.25\" />\n </svg>\n </div>\n </div>\n <div class=\"results-panel\"></div>\n </div>";
|
||||
var el = Docsify.dom.create('div', html);
|
||||
var aside = Docsify.dom.find('aside');
|
||||
|
||||
Docsify.dom.toggleClass(el, 'search');
|
||||
Docsify.dom.before(aside, el);
|
||||
}
|
||||
|
||||
function doSearch(value) {
|
||||
var $search = Docsify.dom.find('div.search');
|
||||
var $panel = Docsify.dom.find($search, '.results-panel');
|
||||
var $clearBtn = Docsify.dom.find($search, '.clear-button');
|
||||
var $sidebarNav = Docsify.dom.find('.sidebar-nav');
|
||||
var $appName = Docsify.dom.find('.app-name');
|
||||
|
||||
if (!value) {
|
||||
$panel.classList.remove('show');
|
||||
$clearBtn.classList.remove('show');
|
||||
$panel.innerHTML = '';
|
||||
|
||||
if (options.hideOtherSidebarContent) {
|
||||
$sidebarNav && $sidebarNav.classList.remove('hide');
|
||||
$appName && $appName.classList.remove('hide');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var matchs = search(value);
|
||||
|
||||
var html = '';
|
||||
matchs.forEach(function (post) {
|
||||
html += "<div class=\"matching-post\">\n<a href=\"" + (post.url) + "\">\n<h2>" + (post.title) + "</h2>\n<p>" + (post.content) + "</p>\n</a>\n</div>";
|
||||
});
|
||||
|
||||
$panel.classList.add('show');
|
||||
$clearBtn.classList.add('show');
|
||||
$panel.innerHTML = html || ("<p class=\"empty\">" + NO_DATA_TEXT + "</p>");
|
||||
if (options.hideOtherSidebarContent) {
|
||||
$sidebarNav && $sidebarNav.classList.add('hide');
|
||||
$appName && $appName.classList.add('hide');
|
||||
}
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
var $search = Docsify.dom.find('div.search');
|
||||
var $input = Docsify.dom.find($search, 'input');
|
||||
var $inputWrap = Docsify.dom.find($search, '.input-wrap');
|
||||
|
||||
var timeId;
|
||||
|
||||
/**
|
||||
Prevent to Fold sidebar.
|
||||
|
||||
When searching on the mobile end,
|
||||
the sidebar is collapsed when you click the INPUT box,
|
||||
making it impossible to search.
|
||||
*/
|
||||
Docsify.dom.on(
|
||||
$search,
|
||||
'click',
|
||||
function (e) { return ['A', 'H2', 'P', 'EM'].indexOf(e.target.tagName) === -1 &&
|
||||
e.stopPropagation(); }
|
||||
);
|
||||
Docsify.dom.on($input, 'input', function (e) {
|
||||
clearTimeout(timeId);
|
||||
timeId = setTimeout(function (_) { return doSearch(e.target.value.trim()); }, 100);
|
||||
});
|
||||
Docsify.dom.on($inputWrap, 'click', function (e) {
|
||||
// Click input outside
|
||||
if (e.target.tagName !== 'INPUT') {
|
||||
$input.value = '';
|
||||
doSearch();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updatePlaceholder(text, path) {
|
||||
var $input = Docsify.dom.getNode('.search input[type="search"]');
|
||||
|
||||
if (!$input) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof text === 'string') {
|
||||
$input.placeholder = text;
|
||||
} else {
|
||||
var match = Object.keys(text).filter(function (key) { return path.indexOf(key) > -1; })[0];
|
||||
$input.placeholder = text[match];
|
||||
}
|
||||
}
|
||||
|
||||
function updateNoData(text, path) {
|
||||
if (typeof text === 'string') {
|
||||
NO_DATA_TEXT = text;
|
||||
} else {
|
||||
var match = Object.keys(text).filter(function (key) { return path.indexOf(key) > -1; })[0];
|
||||
NO_DATA_TEXT = text[match];
|
||||
}
|
||||
}
|
||||
|
||||
function updateOptions(opts) {
|
||||
options = opts;
|
||||
}
|
||||
|
||||
function init$1(opts, vm) {
|
||||
var keywords = vm.router.parse().query.s;
|
||||
|
||||
updateOptions(opts);
|
||||
style();
|
||||
tpl(keywords);
|
||||
bindEvents();
|
||||
keywords && setTimeout(function (_) { return doSearch(keywords); }, 500);
|
||||
}
|
||||
|
||||
function update(opts, vm) {
|
||||
updateOptions(opts);
|
||||
updatePlaceholder(opts.placeholder, vm.route.path);
|
||||
updateNoData(opts.noData, vm.route.path);
|
||||
}
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
var CONFIG = {
|
||||
placeholder: 'Type to search',
|
||||
noData: 'No Results!',
|
||||
paths: 'auto',
|
||||
depth: 2,
|
||||
maxAge: 86400000, // 1 day
|
||||
hideOtherSidebarContent: false,
|
||||
namespace: undefined,
|
||||
pathNamespaces: undefined,
|
||||
};
|
||||
|
||||
var install = function (hook, vm) {
|
||||
var util = Docsify.util;
|
||||
var opts = vm.config.search || CONFIG;
|
||||
|
||||
if (Array.isArray(opts)) {
|
||||
CONFIG.paths = opts;
|
||||
} else if (typeof opts === 'object') {
|
||||
CONFIG.paths = Array.isArray(opts.paths) ? opts.paths : 'auto';
|
||||
CONFIG.maxAge = util.isPrimitive(opts.maxAge) ? opts.maxAge : CONFIG.maxAge;
|
||||
CONFIG.placeholder = opts.placeholder || CONFIG.placeholder;
|
||||
CONFIG.noData = opts.noData || CONFIG.noData;
|
||||
CONFIG.depth = opts.depth || CONFIG.depth;
|
||||
CONFIG.hideOtherSidebarContent =
|
||||
opts.hideOtherSidebarContent || CONFIG.hideOtherSidebarContent;
|
||||
CONFIG.namespace = opts.namespace || CONFIG.namespace;
|
||||
CONFIG.pathNamespaces = opts.pathNamespaces || CONFIG.pathNamespaces;
|
||||
}
|
||||
|
||||
var isAuto = CONFIG.paths === 'auto';
|
||||
|
||||
hook.mounted(function (_) {
|
||||
init$1(CONFIG, vm);
|
||||
!isAuto && init(CONFIG, vm);
|
||||
});
|
||||
hook.doneEach(function (_) {
|
||||
update(CONFIG, vm);
|
||||
isAuto && init(CONFIG, vm);
|
||||
});
|
||||
};
|
||||
|
||||
$docsify.plugins = [].concat(install, $docsify.plugins);
|
||||
|
||||
}());
|
1
docs-docsidy/docsify/plugins/search.min.js
vendored
Normal file
668
docs-docsidy/docsify/plugins/zoom-image.js
Normal file
@ -0,0 +1,668 @@
|
||||
(function () {
|
||||
/*! medium-zoom 1.0.8 | MIT License | https://github.com/francoischalifour/medium-zoom */
|
||||
var _extends = Object.assign || function (target) {
|
||||
var arguments$1 = arguments;
|
||||
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var source = arguments$1[i];
|
||||
|
||||
for (var key in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
};
|
||||
|
||||
var isSupported = function isSupported(node) {
|
||||
return node.tagName === 'IMG';
|
||||
};
|
||||
|
||||
/* eslint-disable-next-line no-prototype-builtins */
|
||||
var isNodeList = function isNodeList(selector) {
|
||||
return NodeList.prototype.isPrototypeOf(selector);
|
||||
};
|
||||
|
||||
var isNode = function isNode(selector) {
|
||||
return selector && selector.nodeType === 1;
|
||||
};
|
||||
|
||||
var isSvg = function isSvg(image) {
|
||||
var source = image.currentSrc || image.src;
|
||||
return source.substr(-4).toLowerCase() === '.svg';
|
||||
};
|
||||
|
||||
var getImagesFromSelector = function getImagesFromSelector(selector) {
|
||||
try {
|
||||
if (Array.isArray(selector)) {
|
||||
return selector.filter(isSupported);
|
||||
}
|
||||
|
||||
if (isNodeList(selector)) {
|
||||
// Do not use spread operator or Array.from() for IE support
|
||||
return [].slice.call(selector).filter(isSupported);
|
||||
}
|
||||
|
||||
if (isNode(selector)) {
|
||||
return [selector].filter(isSupported);
|
||||
}
|
||||
|
||||
if (typeof selector === 'string') {
|
||||
// Do not use spread operator or Array.from() for IE support
|
||||
return [].slice.call(document.querySelectorAll(selector)).filter(isSupported);
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (err) {
|
||||
throw new TypeError('The provided selector is invalid.\n' + 'Expects a CSS selector, a Node element, a NodeList or an array.\n' + 'See: https://github.com/francoischalifour/medium-zoom');
|
||||
}
|
||||
};
|
||||
|
||||
var createOverlay = function createOverlay(background) {
|
||||
var overlay = document.createElement('div');
|
||||
overlay.classList.add('medium-zoom-overlay');
|
||||
overlay.style.background = background;
|
||||
|
||||
return overlay;
|
||||
};
|
||||
|
||||
var cloneTarget = function cloneTarget(template) {
|
||||
var _template$getBounding = template.getBoundingClientRect(),
|
||||
top = _template$getBounding.top,
|
||||
left = _template$getBounding.left,
|
||||
width = _template$getBounding.width,
|
||||
height = _template$getBounding.height;
|
||||
|
||||
var clone = template.cloneNode();
|
||||
var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
|
||||
var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
|
||||
|
||||
clone.removeAttribute('id');
|
||||
clone.style.position = 'absolute';
|
||||
clone.style.top = top + scrollTop + 'px';
|
||||
clone.style.left = left + scrollLeft + 'px';
|
||||
clone.style.width = width + 'px';
|
||||
clone.style.height = height + 'px';
|
||||
clone.style.transform = '';
|
||||
|
||||
return clone;
|
||||
};
|
||||
|
||||
var createCustomEvent = function createCustomEvent(type, params) {
|
||||
var eventParams = _extends({
|
||||
bubbles: false,
|
||||
cancelable: false,
|
||||
detail: undefined
|
||||
}, params);
|
||||
|
||||
if (typeof window.CustomEvent === 'function') {
|
||||
return new CustomEvent(type, eventParams);
|
||||
}
|
||||
|
||||
var customEvent = document.createEvent('CustomEvent');
|
||||
customEvent.initCustomEvent(type, eventParams.bubbles, eventParams.cancelable, eventParams.detail);
|
||||
|
||||
return customEvent;
|
||||
};
|
||||
|
||||
var mediumZoom = function mediumZoom(selector) {
|
||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
|
||||
/**
|
||||
* Ensure the compatibility with IE11 if no Promise polyfill are used.
|
||||
*/
|
||||
var Promise = window.Promise || function Promise(fn) {
|
||||
function noop() {}
|
||||
fn(noop, noop);
|
||||
};
|
||||
|
||||
var _handleClick = function _handleClick(event) {
|
||||
var target = event.target;
|
||||
|
||||
|
||||
if (target === overlay) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (images.indexOf(target) === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
toggle({ target: target });
|
||||
};
|
||||
|
||||
var _handleScroll = function _handleScroll() {
|
||||
if (isAnimating || !active.original) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentScroll = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
|
||||
|
||||
if (Math.abs(scrollTop - currentScroll) > zoomOptions.scrollOffset) {
|
||||
setTimeout(close, 150);
|
||||
}
|
||||
};
|
||||
|
||||
var _handleKeyUp = function _handleKeyUp(event) {
|
||||
var key = event.key || event.keyCode;
|
||||
|
||||
// Close if escape key is pressed
|
||||
if (key === 'Escape' || key === 'Esc' || key === 27) {
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
var update = function update() {
|
||||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||||
|
||||
var newOptions = options;
|
||||
|
||||
if (options.background) {
|
||||
overlay.style.background = options.background;
|
||||
}
|
||||
|
||||
if (options.container && options.container instanceof Object) {
|
||||
newOptions.container = _extends({}, zoomOptions.container, options.container);
|
||||
}
|
||||
|
||||
if (options.template) {
|
||||
var template = isNode(options.template) ? options.template : document.querySelector(options.template);
|
||||
|
||||
newOptions.template = template;
|
||||
}
|
||||
|
||||
zoomOptions = _extends({}, zoomOptions, newOptions);
|
||||
|
||||
images.forEach(function (image) {
|
||||
image.dispatchEvent(createCustomEvent('medium-zoom:update', {
|
||||
detail: { zoom: zoom }
|
||||
}));
|
||||
});
|
||||
|
||||
return zoom;
|
||||
};
|
||||
|
||||
var clone = function clone() {
|
||||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||||
return mediumZoom(_extends({}, zoomOptions, options));
|
||||
};
|
||||
|
||||
var attach = function attach() {
|
||||
var arguments$1 = arguments;
|
||||
|
||||
for (var _len = arguments.length, selectors = Array(_len), _key = 0; _key < _len; _key++) {
|
||||
selectors[_key] = arguments$1[_key];
|
||||
}
|
||||
|
||||
var newImages = selectors.reduce(function (imagesAccumulator, currentSelector) {
|
||||
return [].concat(imagesAccumulator, getImagesFromSelector(currentSelector));
|
||||
}, []);
|
||||
|
||||
newImages.filter(function (newImage) {
|
||||
return images.indexOf(newImage) === -1;
|
||||
}).forEach(function (newImage) {
|
||||
images.push(newImage);
|
||||
newImage.classList.add('medium-zoom-image');
|
||||
});
|
||||
|
||||
eventListeners.forEach(function (_ref) {
|
||||
var type = _ref.type,
|
||||
listener = _ref.listener,
|
||||
options = _ref.options;
|
||||
|
||||
newImages.forEach(function (image) {
|
||||
image.addEventListener(type, listener, options);
|
||||
});
|
||||
});
|
||||
|
||||
return zoom;
|
||||
};
|
||||
|
||||
var detach = function detach() {
|
||||
var arguments$1 = arguments;
|
||||
|
||||
for (var _len2 = arguments.length, selectors = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
|
||||
selectors[_key2] = arguments$1[_key2];
|
||||
}
|
||||
|
||||
if (active.zoomed) {
|
||||
close();
|
||||
}
|
||||
|
||||
var imagesToDetach = selectors.length > 0 ? selectors.reduce(function (imagesAccumulator, currentSelector) {
|
||||
return [].concat(imagesAccumulator, getImagesFromSelector(currentSelector));
|
||||
}, []) : images;
|
||||
|
||||
imagesToDetach.forEach(function (image) {
|
||||
image.classList.remove('medium-zoom-image');
|
||||
image.dispatchEvent(createCustomEvent('medium-zoom:detach', {
|
||||
detail: { zoom: zoom }
|
||||
}));
|
||||
});
|
||||
|
||||
images = images.filter(function (image) {
|
||||
return imagesToDetach.indexOf(image) === -1;
|
||||
});
|
||||
|
||||
return zoom;
|
||||
};
|
||||
|
||||
var on = function on(type, listener) {
|
||||
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
||||
|
||||
images.forEach(function (image) {
|
||||
image.addEventListener('medium-zoom:' + type, listener, options);
|
||||
});
|
||||
|
||||
eventListeners.push({ type: 'medium-zoom:' + type, listener: listener, options: options });
|
||||
|
||||
return zoom;
|
||||
};
|
||||
|
||||
var off = function off(type, listener) {
|
||||
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
||||
|
||||
images.forEach(function (image) {
|
||||
image.removeEventListener('medium-zoom:' + type, listener, options);
|
||||
});
|
||||
|
||||
eventListeners = eventListeners.filter(function (eventListener) {
|
||||
return !(eventListener.type === 'medium-zoom:' + type && eventListener.listener.toString() === listener.toString());
|
||||
});
|
||||
|
||||
return zoom;
|
||||
};
|
||||
|
||||
var open = function open() {
|
||||
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
||||
target = _ref2.target;
|
||||
|
||||
var _animate = function _animate() {
|
||||
var container = {
|
||||
width: document.documentElement.clientWidth,
|
||||
height: document.documentElement.clientHeight,
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0
|
||||
};
|
||||
var viewportWidth = void 0;
|
||||
var viewportHeight = void 0;
|
||||
|
||||
if (zoomOptions.container) {
|
||||
if (zoomOptions.container instanceof Object) {
|
||||
// The container is given as an object with properties like width, height, left, top
|
||||
container = _extends({}, container, zoomOptions.container);
|
||||
|
||||
// We need to adjust custom options like container.right or container.bottom
|
||||
viewportWidth = container.width - container.left - container.right - zoomOptions.margin * 2;
|
||||
viewportHeight = container.height - container.top - container.bottom - zoomOptions.margin * 2;
|
||||
} else {
|
||||
// The container is given as an element
|
||||
var zoomContainer = isNode(zoomOptions.container) ? zoomOptions.container : document.querySelector(zoomOptions.container);
|
||||
|
||||
var _zoomContainer$getBou = zoomContainer.getBoundingClientRect(),
|
||||
_width = _zoomContainer$getBou.width,
|
||||
_height = _zoomContainer$getBou.height,
|
||||
_left = _zoomContainer$getBou.left,
|
||||
_top = _zoomContainer$getBou.top;
|
||||
|
||||
container = _extends({}, container, {
|
||||
width: _width,
|
||||
height: _height,
|
||||
left: _left,
|
||||
top: _top
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
viewportWidth = viewportWidth || container.width - zoomOptions.margin * 2;
|
||||
viewportHeight = viewportHeight || container.height - zoomOptions.margin * 2;
|
||||
|
||||
var zoomTarget = active.zoomedHd || active.original;
|
||||
var naturalWidth = isSvg(zoomTarget) ? viewportWidth : zoomTarget.naturalWidth || viewportWidth;
|
||||
var naturalHeight = isSvg(zoomTarget) ? viewportHeight : zoomTarget.naturalHeight || viewportHeight;
|
||||
|
||||
var _zoomTarget$getBoundi = zoomTarget.getBoundingClientRect(),
|
||||
top = _zoomTarget$getBoundi.top,
|
||||
left = _zoomTarget$getBoundi.left,
|
||||
width = _zoomTarget$getBoundi.width,
|
||||
height = _zoomTarget$getBoundi.height;
|
||||
|
||||
var scaleX = Math.min(Math.max(width, naturalWidth), viewportWidth) / width;
|
||||
var scaleY = Math.min(Math.max(height, naturalHeight), viewportHeight) / height;
|
||||
var scale = Math.min(scaleX, scaleY);
|
||||
var translateX = (-left + (viewportWidth - width) / 2 + zoomOptions.margin + container.left) / scale;
|
||||
var translateY = (-top + (viewportHeight - height) / 2 + zoomOptions.margin + container.top) / scale;
|
||||
var transform = 'scale(' + scale + ') translate3d(' + translateX + 'px, ' + translateY + 'px, 0)';
|
||||
|
||||
active.zoomed.style.transform = transform;
|
||||
|
||||
if (active.zoomedHd) {
|
||||
active.zoomedHd.style.transform = transform;
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise(function (resolve) {
|
||||
if (target && images.indexOf(target) === -1) {
|
||||
resolve(zoom);
|
||||
return;
|
||||
}
|
||||
|
||||
var _handleOpenEnd = function _handleOpenEnd() {
|
||||
isAnimating = false;
|
||||
active.zoomed.removeEventListener('transitionend', _handleOpenEnd);
|
||||
active.original.dispatchEvent(createCustomEvent('medium-zoom:opened', {
|
||||
detail: { zoom: zoom }
|
||||
}));
|
||||
|
||||
resolve(zoom);
|
||||
};
|
||||
|
||||
if (active.zoomed) {
|
||||
resolve(zoom);
|
||||
return;
|
||||
}
|
||||
|
||||
if (target) {
|
||||
// The zoom was triggered manually via a click
|
||||
active.original = target;
|
||||
} else if (images.length > 0) {
|
||||
var _images = images;
|
||||
active.original = _images[0];
|
||||
} else {
|
||||
resolve(zoom);
|
||||
return;
|
||||
}
|
||||
|
||||
active.original.dispatchEvent(createCustomEvent('medium-zoom:open', {
|
||||
detail: { zoom: zoom }
|
||||
}));
|
||||
|
||||
scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
|
||||
isAnimating = true;
|
||||
active.zoomed = cloneTarget(active.original);
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
if (zoomOptions.template) {
|
||||
var template = isNode(zoomOptions.template) ? zoomOptions.template : document.querySelector(zoomOptions.template);
|
||||
active.template = document.createElement('div');
|
||||
active.template.appendChild(template.content.cloneNode(true));
|
||||
|
||||
document.body.appendChild(active.template);
|
||||
}
|
||||
|
||||
// If the selected <img> tag is inside a <picture> tag, set the
|
||||
// currently-applied source as the cloned `src=` attribute.
|
||||
// (as these might differ, or src= might be unset in some cases)
|
||||
if (active.original.parentElement && active.original.parentElement.tagName === 'PICTURE' && active.original.currentSrc) {
|
||||
active.zoomed.src = active.original.currentSrc;
|
||||
}
|
||||
|
||||
document.body.appendChild(active.zoomed);
|
||||
|
||||
window.requestAnimationFrame(function () {
|
||||
document.body.classList.add('medium-zoom--opened');
|
||||
});
|
||||
|
||||
active.original.classList.add('medium-zoom-image--hidden');
|
||||
active.zoomed.classList.add('medium-zoom-image--opened');
|
||||
|
||||
active.zoomed.addEventListener('click', close);
|
||||
active.zoomed.addEventListener('transitionend', _handleOpenEnd);
|
||||
|
||||
if (active.original.getAttribute('data-zoom-src')) {
|
||||
active.zoomedHd = active.zoomed.cloneNode();
|
||||
|
||||
// Reset the `scrset` property or the HD image won't load.
|
||||
active.zoomedHd.removeAttribute('srcset');
|
||||
active.zoomedHd.removeAttribute('sizes');
|
||||
// Remove loading attribute so the browser can load the image normally
|
||||
active.zoomedHd.removeAttribute('loading');
|
||||
|
||||
active.zoomedHd.src = active.zoomed.getAttribute('data-zoom-src');
|
||||
|
||||
active.zoomedHd.onerror = function () {
|
||||
clearInterval(getZoomTargetSize);
|
||||
console.warn('Unable to reach the zoom image target ' + active.zoomedHd.src);
|
||||
active.zoomedHd = null;
|
||||
_animate();
|
||||
};
|
||||
|
||||
// We need to access the natural size of the full HD
|
||||
// target as fast as possible to compute the animation.
|
||||
var getZoomTargetSize = setInterval(function () {
|
||||
if ( active.zoomedHd.complete) {
|
||||
clearInterval(getZoomTargetSize);
|
||||
active.zoomedHd.classList.add('medium-zoom-image--opened');
|
||||
active.zoomedHd.addEventListener('click', close);
|
||||
document.body.appendChild(active.zoomedHd);
|
||||
_animate();
|
||||
}
|
||||
}, 10);
|
||||
} else if (active.original.hasAttribute('srcset')) {
|
||||
// If an image has a `srcset` attribuet, we don't know the dimensions of the
|
||||
// zoomed (HD) image (like when `data-zoom-src` is specified).
|
||||
// Therefore the approach is quite similar.
|
||||
active.zoomedHd = active.zoomed.cloneNode();
|
||||
|
||||
// Resetting the sizes attribute tells the browser to load the
|
||||
// image best fitting the current viewport size, respecting the `srcset`.
|
||||
active.zoomedHd.removeAttribute('sizes');
|
||||
|
||||
// In Firefox, the `loading` attribute needs to be set to `eager` (default
|
||||
// value) for the load event to be fired.
|
||||
active.zoomedHd.removeAttribute('loading');
|
||||
|
||||
// Wait for the load event of the hd image. This will fire if the image
|
||||
// is already cached.
|
||||
var loadEventListener = active.zoomedHd.addEventListener('load', function () {
|
||||
active.zoomedHd.removeEventListener('load', loadEventListener);
|
||||
active.zoomedHd.classList.add('medium-zoom-image--opened');
|
||||
active.zoomedHd.addEventListener('click', close);
|
||||
document.body.appendChild(active.zoomedHd);
|
||||
_animate();
|
||||
});
|
||||
} else {
|
||||
_animate();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var close = function close() {
|
||||
return new Promise(function (resolve) {
|
||||
if (isAnimating || !active.original) {
|
||||
resolve(zoom);
|
||||
return;
|
||||
}
|
||||
|
||||
var _handleCloseEnd = function _handleCloseEnd() {
|
||||
active.original.classList.remove('medium-zoom-image--hidden');
|
||||
document.body.removeChild(active.zoomed);
|
||||
if (active.zoomedHd) {
|
||||
document.body.removeChild(active.zoomedHd);
|
||||
}
|
||||
document.body.removeChild(overlay);
|
||||
active.zoomed.classList.remove('medium-zoom-image--opened');
|
||||
if (active.template) {
|
||||
document.body.removeChild(active.template);
|
||||
}
|
||||
|
||||
isAnimating = false;
|
||||
active.zoomed.removeEventListener('transitionend', _handleCloseEnd);
|
||||
|
||||
active.original.dispatchEvent(createCustomEvent('medium-zoom:closed', {
|
||||
detail: { zoom: zoom }
|
||||
}));
|
||||
|
||||
active.original = null;
|
||||
active.zoomed = null;
|
||||
active.zoomedHd = null;
|
||||
active.template = null;
|
||||
|
||||
resolve(zoom);
|
||||
};
|
||||
|
||||
isAnimating = true;
|
||||
document.body.classList.remove('medium-zoom--opened');
|
||||
active.zoomed.style.transform = '';
|
||||
|
||||
if (active.zoomedHd) {
|
||||
active.zoomedHd.style.transform = '';
|
||||
}
|
||||
|
||||
// Fade out the template so it's not too abrupt
|
||||
if (active.template) {
|
||||
active.template.style.transition = 'opacity 150ms';
|
||||
active.template.style.opacity = 0;
|
||||
}
|
||||
|
||||
active.original.dispatchEvent(createCustomEvent('medium-zoom:close', {
|
||||
detail: { zoom: zoom }
|
||||
}));
|
||||
|
||||
active.zoomed.addEventListener('transitionend', _handleCloseEnd);
|
||||
});
|
||||
};
|
||||
|
||||
var toggle = function toggle() {
|
||||
var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
||||
target = _ref3.target;
|
||||
|
||||
if (active.original) {
|
||||
return close();
|
||||
}
|
||||
|
||||
return open({ target: target });
|
||||
};
|
||||
|
||||
var getOptions = function getOptions() {
|
||||
return zoomOptions;
|
||||
};
|
||||
|
||||
var getImages = function getImages() {
|
||||
return images;
|
||||
};
|
||||
|
||||
var getZoomedImage = function getZoomedImage() {
|
||||
return active.original;
|
||||
};
|
||||
|
||||
var images = [];
|
||||
var eventListeners = [];
|
||||
var isAnimating = false;
|
||||
var scrollTop = 0;
|
||||
var zoomOptions = options;
|
||||
var active = {
|
||||
original: null,
|
||||
zoomed: null,
|
||||
zoomedHd: null,
|
||||
template: null
|
||||
|
||||
// If the selector is omitted, it's replaced by the options
|
||||
};if (Object.prototype.toString.call(selector) === '[object Object]') {
|
||||
zoomOptions = selector;
|
||||
} else if (selector || typeof selector === 'string' // to process empty string as a selector
|
||||
) {
|
||||
attach(selector);
|
||||
}
|
||||
|
||||
// Apply the default option values
|
||||
zoomOptions = _extends({
|
||||
margin: 0,
|
||||
background: '#fff',
|
||||
scrollOffset: 40,
|
||||
container: null,
|
||||
template: null
|
||||
}, zoomOptions);
|
||||
|
||||
var overlay = createOverlay(zoomOptions.background);
|
||||
|
||||
document.addEventListener('click', _handleClick);
|
||||
document.addEventListener('keyup', _handleKeyUp);
|
||||
document.addEventListener('scroll', _handleScroll);
|
||||
window.addEventListener('resize', close);
|
||||
|
||||
var zoom = {
|
||||
open: open,
|
||||
close: close,
|
||||
toggle: toggle,
|
||||
update: update,
|
||||
clone: clone,
|
||||
attach: attach,
|
||||
detach: detach,
|
||||
on: on,
|
||||
off: off,
|
||||
getOptions: getOptions,
|
||||
getImages: getImages,
|
||||
getZoomedImage: getZoomedImage
|
||||
};
|
||||
|
||||
return zoom;
|
||||
};
|
||||
|
||||
function styleInject(css, ref) {
|
||||
if ( ref === void 0 ) { ref = {}; }
|
||||
var insertAt = ref.insertAt;
|
||||
|
||||
if (!css || typeof document === 'undefined') { return; }
|
||||
|
||||
var head = document.head || document.getElementsByTagName('head')[0];
|
||||
var style = document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
|
||||
if (insertAt === 'top') {
|
||||
if (head.firstChild) {
|
||||
head.insertBefore(style, head.firstChild);
|
||||
} else {
|
||||
head.appendChild(style);
|
||||
}
|
||||
} else {
|
||||
head.appendChild(style);
|
||||
}
|
||||
|
||||
if (style.styleSheet) {
|
||||
style.styleSheet.cssText = css;
|
||||
} else {
|
||||
style.appendChild(document.createTextNode(css));
|
||||
}
|
||||
}
|
||||
|
||||
var css = ".medium-zoom-overlay{position:fixed;top:0;right:0;bottom:0;left:0;opacity:0;transition:opacity .3s;will-change:opacity}.medium-zoom--opened .medium-zoom-overlay{cursor:pointer;cursor:zoom-out;opacity:1}.medium-zoom-image{cursor:pointer;cursor:zoom-in;transition:transform .3s cubic-bezier(.2,0,.2,1)!important}.medium-zoom-image--hidden{visibility:hidden}.medium-zoom-image--opened{position:relative;cursor:pointer;cursor:zoom-out;will-change:transform}";
|
||||
styleInject(css);
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
var matchesSelector =
|
||||
Element.prototype.matches ||
|
||||
Element.prototype.webkitMatchesSelector ||
|
||||
Element.prototype.msMatchesSelector;
|
||||
|
||||
function install(hook) {
|
||||
var zoom;
|
||||
|
||||
hook.doneEach(function (_) {
|
||||
var elms = Array.apply(
|
||||
null,
|
||||
document.querySelectorAll(
|
||||
'.markdown-section img:not(.emoji):not([data-no-zoom])'
|
||||
)
|
||||
);
|
||||
|
||||
elms = elms.filter(function (elm) { return matchesSelector.call(elm, 'a img') === false; });
|
||||
|
||||
if (zoom) {
|
||||
zoom.detach();
|
||||
}
|
||||
|
||||
zoom = mediumZoom(elms);
|
||||
});
|
||||
}
|
||||
|
||||
$docsify.plugins = [].concat(install, $docsify.plugins);
|
||||
|
||||
}());
|
1
docs-docsidy/docsify/plugins/zoom-image.min.js
vendored
Normal file
1
docs-docsidy/docsify/themes/buble.css
Normal file
1
docs-docsidy/docsify/themes/dark.css
Normal file
1
docs-docsidy/docsify/themes/dolphin.css
Normal file
1
docs-docsidy/docsify/themes/pure.css
Normal file
1
docs-docsidy/docsify/themes/vue.css
Normal file
@ -6,19 +6,19 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="description" content="Description">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css">
|
||||
<link rel="stylesheet" href="docsify/themes/vue.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
window.$docsify = {
|
||||
loadSidebar: true,
|
||||
loadSidebar: true,
|
||||
name: 'updux',
|
||||
repo: '',
|
||||
subMaxLevel: 3,
|
||||
}
|
||||
</script>
|
||||
<!-- Docsify v4 -->
|
||||
<script src="docsify.js"></script>
|
||||
<script src="docsify/docsify.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -4,7 +4,7 @@
|
||||
|
||||
Say you have a `todos` state that is an array of `todo` sub-states, with some
|
||||
actions that should percolate to all todos, and some that should only
|
||||
percolate to one. One way to model this is via updux's splat subduxes
|
||||
percolate to one. One way to model this is via updux's splat subduxes
|
||||
(backed by `updeep`'s own '*'-key behavior).
|
||||
|
||||
```
|
||||
@ -55,7 +55,7 @@ const updux = new Updux({
|
||||
add: (inc=1) => state => ({ counter: state.counter + inc })
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
```
|
||||
|
||||
Converting it to Immer would look like:
|
||||
@ -68,10 +68,10 @@ import { produce } from 'immer';
|
||||
const updux = new Updux({
|
||||
initial: { counter: 0 },
|
||||
mutations: {
|
||||
add: (inc=1) => produce( draft => draft.counter += inc ) }
|
||||
add: (inc=1) => produce( draft => draft.counter += inc ) }
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
```
|
||||
|
||||
But since typing `produce` over and over is no fun, `groomMutations`
|
||||
@ -86,11 +86,8 @@ const updux = new Updux({
|
||||
initial: { counter: 0 },
|
||||
groomMutations: mutation => (...args) => produce( mutation(...args) ),
|
||||
mutations: {
|
||||
add: (inc=1) => draft => draft.counter += inc
|
||||
add: (inc=1) => draft => draft.counter += inc
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
115
docs-docsidy/tutorial.md
Normal file
@ -0,0 +1,115 @@
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
--8<- "docs/tutorial-effects.test.ts:effects-1"
|
||||
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Subduxes
|
||||
|
||||
Now that we have all the building blocks, we can embark on the last and
|
||||
funkiest part of Updux: its recursive nature.
|
||||
|
||||
### Recap: the Todos dux, undivided
|
||||
|
||||
Upduxes can be divided into sub-upduxes that deal with the various parts of
|
||||
the global state. This is better understood by working out an example, so
|
||||
let's recap on the Todos dux we have so far:
|
||||
|
||||
```title="todos-monolith.ts"
|
||||
--8<- "docs/tutorial-monolith.test.ts:mono"
|
||||
```
|
||||
|
||||
This store has two main components: the `nextId`, and the `todos` collection.
|
||||
The `todos` collection is itself composed of the individual `todo`s. Let's
|
||||
create upduxes for each of those.
|
||||
|
||||
### NextId dux
|
||||
|
||||
```js title="nextId.ts"
|
||||
--8<- "docs/nextId.ts:dux"
|
||||
```
|
||||
|
||||
### Todo updux
|
||||
|
||||
```title="todo.ts"
|
||||
--8<- "docs/todo.ts"
|
||||
```
|
||||
|
||||
### Todos updux
|
||||
|
||||
```title="todos.ts"
|
||||
--8<- "docs/todos.ts"
|
||||
```
|
||||
|
||||
### Main store
|
||||
|
||||
```title="todoList.ts"
|
||||
--8<- "docs/todoList.ts"
|
||||
```
|
||||
|
||||
Tadah! We had to define the `addTodo` effect at the top level as it needs to
|
||||
access the `getNextId` selector from `nextId` and the `addTodoWithId`
|
||||
action from the `todos`.
|
||||
|
||||
Note that the `getNextId` selector still gets the
|
||||
right value; when aggregating subduxes selectors Updux auto-wraps them to
|
||||
access the right slice of the top object.
|
||||
|
||||
## Reactions
|
||||
|
||||
Reactions -- aka Redux's subscriptions -- can be added to a updux store via the initial config
|
||||
or the method `addSubscription`. The signature of a reaction is:
|
||||
|
||||
```
|
||||
(storeApi) => (state, previousState, unsubscribe) => {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Subscriptions registered for an updux and its subduxes are automatically
|
||||
subscribed to the store when calling `createStore`.
|
||||
|
||||
The `state` passed to the subscriptions of the subduxes is the local state.
|
||||
|
||||
Also, all subscriptions are wrapped such that they are called only if the
|
||||
local `state` changed since their last invocation.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
const todos = new Updux({
|
||||
initial: [],
|
||||
actions: {
|
||||
setNbrTodos: null,
|
||||
addTodo: null,
|
||||
},
|
||||
mutations: {
|
||||
addTodo: todo => todos => [ ...todos, todo ],
|
||||
},
|
||||
reactions: [
|
||||
({dispatch}) => todos => dispatch.setNbrTodos(todos.length)
|
||||
],
|
||||
});
|
||||
|
||||
const myDux = new Updux({
|
||||
initial: {
|
||||
nbrTodos: 0
|
||||
},
|
||||
subduxes: {
|
||||
todos,
|
||||
},
|
||||
mutations: {
|
||||
setNbrTodos: nbrTodos => u({ nbrTodos })
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
[immer]: https://www.npmjs.com/package/immer
|
||||
[lodash]: https://www.npmjs.com/package/lodash
|
||||
[ts-action]: https://www.npmjs.com/package/ts-action
|
||||
[remeda]: remedajs.com/
|
21
docs/.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# build output
|
||||
dist/
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
4
docs/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
11
docs/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
243
docs/README.md
@ -1,226 +1,55 @@
|
||||
# What's Updux?
|
||||
# Starlight Starter Kit: Basics
|
||||
|
||||
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
|
||||
[![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build)
|
||||
|
||||
```
|
||||
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);
|
||||
npm create astro@latest -- --template starlight
|
||||
```
|
||||
|
||||
# Description
|
||||
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/starlight/tree/main/examples/basics)
|
||||
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/starlight/tree/main/examples/basics)
|
||||
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/withastro/starlight&create_from_path=examples/basics)
|
||||
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fwithastro%2Fstarlight%2Ftree%2Fmain%2Fexamples%2Fbasics&project-name=my-starlight-docs&repository-name=my-starlight-docs)
|
||||
|
||||
Full documentation can be [found here](https://yanick.github.io/updux/).
|
||||
Right now the best way to understand the whole thing is to go
|
||||
through the [tutorial](https://yanick.github.io/updux/#/tutorial)
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
## Exporting upduxes
|
||||
## 🚀 Project Structure
|
||||
|
||||
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:
|
||||
Inside of your Astro + Starlight project, you'll see the following folders and files:
|
||||
|
||||
```
|
||||
import Updux from 'updux';
|
||||
|
||||
const updux = new Updux({ ... });
|
||||
|
||||
export default updux;
|
||||
.
|
||||
├── public/
|
||||
├── src/
|
||||
│ ├── assets/
|
||||
│ ├── content/
|
||||
│ │ ├── docs/
|
||||
│ │ └── config.ts
|
||||
│ └── env.d.ts
|
||||
├── astro.config.mjs
|
||||
├── package.json
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
Then you can use them as subduxes like this:
|
||||
Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. Each file is exposed as a route based on its file name.
|
||||
|
||||
```
|
||||
import Updux from 'updux';
|
||||
import foo from './foo'; // foo is an Updux
|
||||
import bar from './bar'; // bar is an Updux as well
|
||||
Images can be added to `src/assets/` and embedded in Markdown with a relative link.
|
||||
|
||||
const updux = new Updux({
|
||||
subduxes: {
|
||||
foo, bar
|
||||
}
|
||||
});
|
||||
```
|
||||
Static assets, like favicons, can be placed in the `public/` directory.
|
||||
|
||||
Or if you want to use it:
|
||||
## 🧞 Commands
|
||||
|
||||
```
|
||||
import updux from './myUpdux';
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
const {
|
||||
reducer,
|
||||
actions: { doTheThing },
|
||||
createStore,
|
||||
middleware,
|
||||
} = updux;
|
||||
```
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## Mapping a mutation to all values of a state
|
||||
## 👀 Want to learn more?
|
||||
|
||||
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
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
Check out [Starlight’s docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat).
|
||||
|
@ -1,3 +0,0 @@
|
||||
* [Home](/)
|
||||
* [ Tutorial ](tutorial.md)
|
||||
* [ Recipes ](recipes.md)
|
54
docs/astro.config.mjs
Normal file
@ -0,0 +1,54 @@
|
||||
import { defineConfig, squooshImageService } from 'astro/config';
|
||||
import starlight from '@astrojs/starlight';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
image: { service: squooshImageService() },
|
||||
integrations: [
|
||||
starlight({
|
||||
title: 'Updux',
|
||||
pagefind: false,
|
||||
social: {
|
||||
github: 'https://github.com/withastro/starlight',
|
||||
},
|
||||
sidebar: [
|
||||
{
|
||||
label: 'Home',
|
||||
slug: '',
|
||||
},
|
||||
{
|
||||
label: 'Tutorial',
|
||||
autogenerate: {
|
||||
directory: 'tutorial',
|
||||
}
|
||||
// items: [
|
||||
// { label: 'Introduction', slug: 'tutorial/intro'}
|
||||
// { label: 'Introduction', slug: 'tutorial/intro'}
|
||||
// ]
|
||||
},
|
||||
{
|
||||
label: 'API',
|
||||
items: [
|
||||
{ label: 'updux', slug: 'api/modules' },
|
||||
{ label: 'defaults', slug: 'api/classes/default' },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Guide',
|
||||
autogenerate: {
|
||||
directory: 'guides',
|
||||
}
|
||||
},
|
||||
// items: [
|
||||
// // Each item here is one entry in the navigation menu.
|
||||
// { label: 'Example Guide', slug: 'guides/example' },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// label: 'Reference',
|
||||
// autogenerate: { directory: 'reference' },
|
||||
// },
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
19
docs/package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "starlight-docs",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/starlight": "^0.25.3",
|
||||
"astro": "^4.10.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^2.0.4"
|
||||
}
|
||||
}
|
177
docs/public/favicon.svg
Normal file
After Width: | Height: | Size: 583 KiB |
177
docs/public/updux-logo.svg
Normal file
After Width: | Height: | Size: 583 KiB |
@ -1,54 +0,0 @@
|
||||
import { test, expect } from 'vitest';
|
||||
|
||||
import u from 'updeep';
|
||||
import { Updux } from '../src/index.js';
|
||||
|
||||
const done = () => (state) => ({...state, done: true});
|
||||
|
||||
const todo = new Updux({
|
||||
initial: { id: 0, done: false },
|
||||
actions: {
|
||||
done: null,
|
||||
doneAll: null,
|
||||
},
|
||||
mutations: {
|
||||
done,
|
||||
doneAll: done,
|
||||
},
|
||||
});
|
||||
|
||||
const todos = new Updux({
|
||||
initial: [],
|
||||
subduxes: { '*': todo },
|
||||
actions: { addTodo: null },
|
||||
mutations: {
|
||||
addTodo: text => state => [ ...state, { text } ]
|
||||
}
|
||||
});
|
||||
|
||||
todos.setMutation(
|
||||
todo.actions.done,
|
||||
(text,action) => u.map(u.if(u.is('text',text), todo.upreducer(action))),
|
||||
true // prevents the subduxes mutations to run automatically
|
||||
);
|
||||
|
||||
test( "tutorial", async () => {
|
||||
const store = todos.createStore();
|
||||
|
||||
store.dispatch.addTodo('one');
|
||||
store.dispatch.addTodo('two');
|
||||
store.dispatch.addTodo('three');
|
||||
|
||||
store.dispatch.done( 'two' );
|
||||
|
||||
expect( store.getState()[1].done ).toBeTruthy();
|
||||
expect( store.getState()[2].done ).toBeFalsy();
|
||||
|
||||
store.dispatch.doneAll();
|
||||
|
||||
expect( store.getState().map( ({done}) => done ) ).toEqual([
|
||||
true, true, true
|
||||
]);
|
||||
|
||||
});
|
||||
|
BIN
docs/src/assets/houston.webp
Normal file
After Width: | Height: | Size: 96 KiB |
6
docs/src/content/config.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { defineCollection } from 'astro:content';
|
||||
import { docsSchema } from '@astrojs/starlight/schema';
|
||||
|
||||
export const collections = {
|
||||
docs: defineCollection({ schema: docsSchema() }),
|
||||
};
|
1
docs/src/content/docs/api/.nojekyll
Normal file
@ -0,0 +1 @@
|
||||
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.
|
153
docs/src/content/docs/api/README.md
Normal file
@ -0,0 +1,153 @@
|
||||
---
|
||||
title: "Description"
|
||||
---
|
||||
|
||||
Full documentation can be [found here](https://yanick.github.io/updux/).
|
||||
Right now the best way to understand the whole thing is to go
|
||||
through the [tutorial](https://yanick.github.io/updux/#/tutorial)
|
||||
|
||||
## 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
|
||||
}
|
||||
});
|
||||
|
||||
```
|
355
docs/src/content/docs/api/classes/default.md
Normal file
@ -0,0 +1,355 @@
|
||||
---
|
||||
title: "Class: default<D>"
|
||||
---
|
||||
|
||||
**`Description`**
|
||||
|
||||
main Updux class
|
||||
|
||||
## Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `D` | extends `DuxConfig` |
|
||||
|
||||
## Constructors
|
||||
|
||||
### constructor
|
||||
|
||||
• **new default**\<`D`\>(`duxConfig`): [`default`](default.md)\<`D`\>
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `D` | extends `Partial`\<\{ `actions`: `Record`\<`string`, `Function` \| `ActionCreator`\<`string`, `any`[]\>\> ; `initialState`: `any` ; `selectors`: `Record`\<`string`, `Function`\> ; `subduxes`: `Record`\<`string`, Partial\<\{ initialState: any; subduxes: Record\<string, Partial\<...\>\>; actions: Record\<string, Function \| ActionCreator\<string, any[]\>\>; selectors: Record\<...\>; }\>\> }\> |
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `duxConfig` | `D` |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D`\>
|
||||
|
||||
## Accessors
|
||||
|
||||
### actions
|
||||
|
||||
• `get` **actions**(): `ResolveActions`\<`D` extends \{ `actions`: `any` } ? `D`[``"actions"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesActions`\<`D`\> : `unknown`\>
|
||||
|
||||
#### Returns
|
||||
|
||||
`ResolveActions`\<`D` extends \{ `actions`: `any` } ? `D`[``"actions"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesActions`\<`D`\> : `unknown`\>
|
||||
|
||||
___
|
||||
|
||||
### effects
|
||||
|
||||
• `get` **effects**(): `any`
|
||||
|
||||
#### Returns
|
||||
|
||||
`any`
|
||||
|
||||
___
|
||||
|
||||
### initialState
|
||||
|
||||
• `get` **initialState**(): `ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\>
|
||||
|
||||
#### Returns
|
||||
|
||||
`ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\>
|
||||
|
||||
**`Description`**
|
||||
|
||||
Initial state of the dux.
|
||||
|
||||
___
|
||||
|
||||
### reactions
|
||||
|
||||
• `get` **reactions**(): `any`
|
||||
|
||||
#### Returns
|
||||
|
||||
`any`
|
||||
|
||||
___
|
||||
|
||||
### reducer
|
||||
|
||||
• `get` **reducer**(): `any`
|
||||
|
||||
#### Returns
|
||||
|
||||
`any`
|
||||
|
||||
___
|
||||
|
||||
### selectors
|
||||
|
||||
• `get` **selectors**(): `ForceResolveObject`\<`D` extends \{ `selectors`: `S` } ? `S` : {} & `D` extends \{ `subduxes`: `SUB` } ? `UnionToIntersection`\<`Values`\<\{ [key in string \| number \| symbol]: RebaseSelectors\<key, SUB[key]\> }\>\> : {}\>
|
||||
|
||||
#### Returns
|
||||
|
||||
`ForceResolveObject`\<`D` extends \{ `selectors`: `S` } ? `S` : {} & `D` extends \{ `subduxes`: `SUB` } ? `UnionToIntersection`\<`Values`\<\{ [key in string \| number \| symbol]: RebaseSelectors\<key, SUB[key]\> }\>\> : {}\>
|
||||
|
||||
___
|
||||
|
||||
### subduxes
|
||||
|
||||
• `get` **subduxes**(): `Object`
|
||||
|
||||
#### Returns
|
||||
|
||||
`Object`
|
||||
|
||||
___
|
||||
|
||||
### upreducer
|
||||
|
||||
• `get` **upreducer**(): (`action`: `AnyAction`) => (`state?`: `ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\>) => `any`
|
||||
|
||||
#### Returns
|
||||
|
||||
`fn`
|
||||
|
||||
▸ (`action`): (`state?`: `ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\>) => `any`
|
||||
|
||||
##### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `action` | `AnyAction` |
|
||||
|
||||
##### Returns
|
||||
|
||||
`fn`
|
||||
|
||||
▸ (`state?`): `any`
|
||||
|
||||
##### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `state?` | `ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\> |
|
||||
|
||||
##### Returns
|
||||
|
||||
`any`
|
||||
|
||||
## Methods
|
||||
|
||||
### addAction
|
||||
|
||||
▸ **addAction**(`action`): `void`
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `action` | `ActionCreator`\<`any`, `any`, `any`\> |
|
||||
|
||||
#### Returns
|
||||
|
||||
`void`
|
||||
|
||||
___
|
||||
|
||||
### addEffect
|
||||
|
||||
▸ **addEffect**\<`A`\>(`actionType`, `effect`): [`default`](default.md)\<`D`\>
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `A` | extends `string` \| `number` \| `symbol` |
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `actionType` | `A` |
|
||||
| `effect` | `EffectMiddleware`\<`D`, `ResolveActions`\<`D` extends \{ `actions`: `any` } ? `D`[``"actions"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesActions`\<`D`\> : `unknown`\>[`A`] extends (...`args`: `any`[]) => `R` ? `R` : `AnyAction`\> |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D`\>
|
||||
|
||||
▸ **addEffect**\<`AC`\>(`actionCreator`, `effect`): [`default`](default.md)\<`D`\>
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `AC` | extends `ActionCreatorWithPreparedPayload`\<`any`, `any`, `string`, `never`, `never`, `AC`\> |
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `actionCreator` | `AC` |
|
||||
| `effect` | `EffectMiddleware`\<`D`, `ReturnType`\<`AC`\>\> |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D`\>
|
||||
|
||||
▸ **addEffect**(`guardFunc`, `effect`): [`default`](default.md)\<`D`\>
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `guardFunc` | (`action`: `AnyAction`) => `boolean` |
|
||||
| `effect` | `EffectMiddleware`\<`D`, `AnyAction`\> |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D`\>
|
||||
|
||||
▸ **addEffect**(`effect`): [`default`](default.md)\<`D`\>
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `effect` | `EffectMiddleware`\<`D`, `AnyAction`\> |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D`\>
|
||||
|
||||
___
|
||||
|
||||
### addMutation
|
||||
|
||||
▸ **addMutation**\<`A`\>(`actionType`, `mutation`, `terminal?`): [`default`](default.md)\<`D`\>
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `A` | extends `string` \| `number` \| `symbol` |
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `actionType` | `A` |
|
||||
| `mutation` | `Mutation`\<`ResolveActions`\<`D` extends \{ `actions`: `any` } ? `D`[``"actions"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesActions`\<`D`\> : `unknown`\>[`A`] extends (...`args`: `any`[]) => `R` ? `R` : `AnyAction`, `ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\>\> |
|
||||
| `terminal?` | `boolean` |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D`\>
|
||||
|
||||
▸ **addMutation**\<`AC`\>(`actionCreator`, `mutation`, `terminal?`): [`default`](default.md)\<`D` & \{ `actions`: `Record`\<`AC` extends \{ `type`: `T` } ? `T` : `never`, `AC`\> }\>
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `AC` | extends `ActionCreatorWithPreparedPayload`\<`any`, `any`, `string`, `never`, `never`, `AC`\> |
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `actionCreator` | `AC` |
|
||||
| `mutation` | `Mutation`\<`ReturnType`\<`AC`\>, `ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\>\> |
|
||||
| `terminal?` | `boolean` |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D` & \{ `actions`: `Record`\<`AC` extends \{ `type`: `T` } ? `T` : `never`, `AC`\> }\>
|
||||
|
||||
▸ **addMutation**\<`A`, `P`, `X`\>(`actionCreator`, `mutation`, `terminal?`): [`default`](default.md)\<`D` & \{ `actions`: `Record`\<`A`, `ActionCreator`\<`A`, `P`, `X`\>\> }\>
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `A` | extends `string` |
|
||||
| `P` | `P` |
|
||||
| `X` | extends `any`[] |
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `actionCreator` | `ActionCreator`\<`A`, `P`, `X`\> |
|
||||
| `mutation` | `Mutation`\<\{ `payload`: `P` ; `type`: `A` }, `ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\>\> |
|
||||
| `terminal?` | `boolean` |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D` & \{ `actions`: `Record`\<`A`, `ActionCreator`\<`A`, `P`, `X`\>\> }\>
|
||||
|
||||
▸ **addMutation**(`matcher`, `mutation`, `terminal?`): [`default`](default.md)\<`D`\>
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `matcher` | (`action`: `AnyAction`) => `boolean` |
|
||||
| `mutation` | `Mutation`\<`AnyAction`, `ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\>\> |
|
||||
| `terminal?` | `boolean` |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D`\>
|
||||
|
||||
___
|
||||
|
||||
### addReaction
|
||||
|
||||
▸ **addReaction**(`reaction`): [`default`](default.md)\<`D`\>
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `reaction` | `DuxReaction`\<`D`\> |
|
||||
|
||||
#### Returns
|
||||
|
||||
[`default`](default.md)\<`D`\>
|
||||
|
||||
___
|
||||
|
||||
### createStore
|
||||
|
||||
▸ **createStore**(`options?`): `ToolkitStore`\<`ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\> & {}, `AnyAction`, `Middlewares`\<`ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\>\>\> & `MiddlewareAPI`\<`Dispatch`\<`AnyAction`\>, `ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\>\> & \{ `actions`: `ResolveActions`\<`D` extends \{ `actions`: `any` } ? `D`[``"actions"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesActions`\<`D`\> : `unknown`\> ; `dispatch`: `ResolveActions`\<`D` extends \{ `actions`: `any` } ? `D`[``"actions"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesActions`\<`D`\> : `unknown`\> ; `getState`: `CurriedSelectors`\<`ForceResolveObject`\<`D` extends \{ `selectors`: `S` } ? `S` : {} & `D` extends \{ `subduxes`: `SUB` } ? `UnionToIntersection`\<`Values`\<\{ [key in string \| number \| symbol]: RebaseSelectors\<key, (...)[(...)]\> }\>\> : {}\>\> ; `selectors`: `ForceResolveObject`\<`D` extends \{ `selectors`: `S` } ? `S` : {} & `D` extends \{ `subduxes`: `SUB` } ? `UnionToIntersection`\<`Values`\<\{ [key in string \| number \| symbol]: RebaseSelectors\<key, SUB[key]\> }\>\> : {}\> }
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `options` | `Partial`\<\{ `preloadedState`: `ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\> }\> |
|
||||
|
||||
#### Returns
|
||||
|
||||
`ToolkitStore`\<`ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\> & {}, `AnyAction`, `Middlewares`\<`ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\>\>\> & `MiddlewareAPI`\<`Dispatch`\<`AnyAction`\>, `ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\>\> & \{ `actions`: `ResolveActions`\<`D` extends \{ `actions`: `any` } ? `D`[``"actions"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesActions`\<`D`\> : `unknown`\> ; `dispatch`: `ResolveActions`\<`D` extends \{ `actions`: `any` } ? `D`[``"actions"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesActions`\<`D`\> : `unknown`\> ; `getState`: `CurriedSelectors`\<`ForceResolveObject`\<`D` extends \{ `selectors`: `S` } ? `S` : {} & `D` extends \{ `subduxes`: `SUB` } ? `UnionToIntersection`\<`Values`\<\{ [key in string \| number \| symbol]: RebaseSelectors\<key, (...)[(...)]\> }\>\> : {}\>\> ; `selectors`: `ForceResolveObject`\<`D` extends \{ `selectors`: `S` } ? `S` : {} & `D` extends \{ `subduxes`: `SUB` } ? `UnionToIntersection`\<`Values`\<\{ [key in string \| number \| symbol]: RebaseSelectors\<key, SUB[key]\> }\>\> : {}\> }
|
||||
|
||||
___
|
||||
|
||||
### setDefaultMutation
|
||||
|
||||
▸ **setDefaultMutation**(`mutation`, `terminal?`): `any`
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `mutation` | `Mutation`\<`any`, `ForceResolveObject`\<`D` extends \{ `initialState`: `any` } ? `D`[``"initialState"``] : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown`\>\> |
|
||||
| `terminal?` | `boolean` |
|
||||
|
||||
#### Returns
|
||||
|
||||
`any`
|
129
docs/src/content/docs/api/modules.md
Normal file
@ -0,0 +1,129 @@
|
||||
---
|
||||
title: "updux"
|
||||
---
|
||||
|
||||
## Classes
|
||||
|
||||
- [default](classes/default.md)
|
||||
|
||||
## Functions
|
||||
|
||||
### createAction
|
||||
|
||||
▸ **createAction**\<`P`, `T`\>(`type`): `PayloadActionCreator`\<`P`, `T`\>
|
||||
|
||||
A utility function to create an action creator for the given action type
|
||||
string. The action creator accepts a single argument, which will be included
|
||||
in the action object as a field called payload. The action creator function
|
||||
will also have its toString() overridden so that it returns the action type,
|
||||
allowing it to be used in reducer logic that is looking for that action type.
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `P` | `void` |
|
||||
| `T` | extends `string` = `string` |
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `type` | `T` | The action type to use for created actions. |
|
||||
|
||||
#### Returns
|
||||
|
||||
`PayloadActionCreator`\<`P`, `T`\>
|
||||
|
||||
▸ **createAction**\<`PA`, `T`\>(`type`, `prepareAction`): `PayloadActionCreator`\<`ReturnType`\<`PA`\>[``"payload"``], `T`, `PA`\>
|
||||
|
||||
A utility function to create an action creator for the given action type
|
||||
string. The action creator accepts a single argument, which will be included
|
||||
in the action object as a field called payload. The action creator function
|
||||
will also have its toString() overridden so that it returns the action type,
|
||||
allowing it to be used in reducer logic that is looking for that action type.
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `PA` | extends `PrepareAction`\<`any`\> |
|
||||
| `T` | extends `string` = `string` |
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `type` | `T` | The action type to use for created actions. |
|
||||
| `prepareAction` | `PA` | - |
|
||||
|
||||
#### Returns
|
||||
|
||||
`PayloadActionCreator`\<`ReturnType`\<`PA`\>[``"payload"``], `T`, `PA`\>
|
||||
|
||||
___
|
||||
|
||||
### withPayload
|
||||
|
||||
▸ **withPayload**\<`P`\>(): (`input`: `P`) => \{ `payload`: `P` }
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name |
|
||||
| :------ |
|
||||
| `P` |
|
||||
|
||||
#### Returns
|
||||
|
||||
`fn`
|
||||
|
||||
▸ (`input`): `Object`
|
||||
|
||||
##### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `input` | `P` |
|
||||
|
||||
##### Returns
|
||||
|
||||
`Object`
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `payload` | `P` |
|
||||
|
||||
▸ **withPayload**\<`P`, `A`\>(`prepare`): (...`input`: `A`) => \{ `payload`: `P` }
|
||||
|
||||
#### Type parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `P` | `P` |
|
||||
| `A` | extends `any`[] |
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `prepare` | (...`args`: `A`) => `P` |
|
||||
|
||||
#### Returns
|
||||
|
||||
`fn`
|
||||
|
||||
▸ (`...input`): `Object`
|
||||
|
||||
##### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `...input` | `A` |
|
||||
|
||||
##### Returns
|
||||
|
||||
`Object`
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `payload` | `P` |
|
47
docs/src/content/docs/guides/001-introduction.mdx
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Introduction
|
||||
---
|
||||
|
||||
Updux is a class that collects together all the stuff pertaining to a Redux
|
||||
reducer -- actions, middleware, subscriptions, selectors -- in a way that
|
||||
is as modular and as TypeScript-friendly as possible.
|
||||
|
||||
While it has originally been created to play well with [updeep][], it also
|
||||
work wells with plain JavaScript, and
|
||||
interfaces very neatly with other immutability/deep merging libraries
|
||||
like
|
||||
[Mutative], [immer][], [updeep][],
|
||||
[remeda][],
|
||||
[lodash][], etc.
|
||||
|
||||
## Updux terminology
|
||||
|
||||
<dl>
|
||||
<dt>Updux</dt>
|
||||
<dd>Object encapsulating the information pertinent for a Redux reducer.
|
||||
Named thus because it has been designed to work well with [updeep][],
|
||||
and follows my spin on
|
||||
the [Ducks pattern](https://github.com/erikras/ducks-modular-redux).</dd>
|
||||
<dt>Mutation</dt>
|
||||
<dd>Reducing function, mostly associated with an action. All mutations of
|
||||
an updux object are combined to form its reducer.</dd>
|
||||
<dt>Subdux</dt>
|
||||
<dd>Updux objects associated with sub-states of a main updux. The main
|
||||
updux will inherit all of its subduxes actions, mutations, reactions,
|
||||
etc.</dd>
|
||||
<dt>Effect</dt>
|
||||
<dd>A Redux middleware, augmented with a few goodies.</dd>
|
||||
<dt>Reaction</dt>
|
||||
<dd>Subscription to a updux. Unlike regular Redux subscriptions, don't
|
||||
trigger if the state of the updux isn't changed by the reducing.</dd>
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
[updeep]: https://www.npmjs.com/package/@yanick/updeep-remeda
|
||||
[immer]: https://www.npmjs.com/package/immer
|
||||
[lodash]: https://www.npmjs.com/package/lodash
|
||||
[ts-action]: https://www.npmjs.com/package/ts-action
|
||||
[remeda]: remedajs.com/
|
||||
[Mutative]: https://mutative.js.org/
|
32
docs/src/content/docs/guides/002-state-definition.mdx
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
title: State definition
|
||||
---
|
||||
|
||||
The state of a Updux store is automatically interpolated from the constructor
|
||||
argument `initialState` (which also provides the reducer's default initial state)..
|
||||
|
||||
```js
|
||||
const myDux = new Updux({
|
||||
initialState: {
|
||||
counter: 1,
|
||||
name: 'foo',
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
If `initialState` is not provided, it defaults to an empty object.
|
||||
|
||||
## Manually providing the store state
|
||||
|
||||
Sometimes -- mostly when arrays are involved -- the initial state is not
|
||||
providing the whole state type. In those cases specifying manually the type of
|
||||
`initialState` will do the trick.
|
||||
|
||||
```js
|
||||
type Todo = { label: string; done: boolean };
|
||||
const initialState : Todo[] = [];
|
||||
|
||||
const myDux = new Updux({
|
||||
initialState
|
||||
});
|
||||
```
|
43
docs/src/content/docs/guides/003-selectors.md
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
title: Selectors
|
||||
---
|
||||
|
||||
Selectors can be defined to get data derived from the state.
|
||||
The selectors can be accessed as-is via the accessor `selectors`.
|
||||
More interestingly, the `getState` method of a dux store will be augmented
|
||||
with its selectors, with the first call for the state already
|
||||
curried for you.
|
||||
|
||||
|
||||
```js
|
||||
type Todo = { label: string; id: number; done: boolean };
|
||||
|
||||
const dux = new Updux({
|
||||
initialState: [] as Todo[],
|
||||
selectors: {
|
||||
getDone: (state) => state.filter(({ done }) => done),
|
||||
getById: (state) => (id) => state.find((todo) => todo.id === id),
|
||||
},
|
||||
});
|
||||
|
||||
const done = dux.selectors.getDone([ ... ]);
|
||||
const myTodo = dux.selectors.getDone([...])(12);
|
||||
|
||||
|
||||
```
|
||||
|
||||
### Store selectors
|
||||
|
||||
The store created by an updux will have the updux selectors available from a
|
||||
`selectors` property. In addition, the store `getState` function will also have
|
||||
the actions as properties.
|
||||
|
||||
```js
|
||||
const store = dux.createStore();
|
||||
|
||||
const done = store.selectors.getDone([ ... ]);
|
||||
const myTodo = store.selectors.getDone([...])(12);
|
||||
|
||||
const done = store.getState.getDone();
|
||||
const myTodo = store.getState.getDone(12);
|
||||
```
|
96
docs/src/content/docs/guides/004-actions.mdx
Normal file
@ -0,0 +1,96 @@
|
||||
---
|
||||
title: Actions
|
||||
---
|
||||
|
||||
Updux actions are just the usual Redux actions. Updux exports two
|
||||
utility functions: `createAction`, which is a convenience re-export of the
|
||||
function provided by
|
||||
[@reduxjs/toolkit](https://redux-toolkit.js.org/), and
|
||||
`withPayload`, an utility function that makes defining the
|
||||
action payload just a little more concise.
|
||||
|
||||
```js
|
||||
const foo = createAction('foo', withPayload<string>() );
|
||||
// equivalent to
|
||||
const foo = createAction('foo', (payload: string) => ({ payload }) ) );
|
||||
|
||||
const createAction('foo', withPayload( (level:number) => ({ level }) );
|
||||
// equivalent to
|
||||
const createAction('foo', (level: number) => ({ payload: { level } }) ) );
|
||||
```
|
||||
|
||||
### Adding actions to an Updux object
|
||||
|
||||
Actions can be tied to an Updux object both at construction time
|
||||
|
||||
```js
|
||||
const addTodo = createAction('addTodo', withPayload<string>());
|
||||
const todoDone = createAction('todoDone', withPayload<number>());
|
||||
|
||||
const myDux = new Updux({
|
||||
actions: {
|
||||
addTodo,
|
||||
todoDone,
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
or they will be automatically added when used in methods like `addMutation`.
|
||||
|
||||
```js
|
||||
|
||||
const myDux = new Updux({})
|
||||
.addMutation( addTodo, (label) => todos => todos.concat({ label });
|
||||
```
|
||||
|
||||
:::danger
|
||||
|
||||
Note the chained invocation of `addMutation`, which is needed if you want
|
||||
the type of myDux to include the `myAction` action.
|
||||
|
||||
```js
|
||||
|
||||
// myDux will have the right type for myDux.actions.myAction
|
||||
myDux = new Updux({})
|
||||
.addMutation( myAction, (payload) => state => state + payload );
|
||||
|
||||
// myDux type will not know about myAction.
|
||||
myDux = new Updux({});
|
||||
myDux.addMutation( myAction, (payload) => state => state + payload );
|
||||
|
||||
``````
|
||||
:::
|
||||
|
||||
### Accessing Updux actions
|
||||
|
||||
Once an action is defined, its creator is accessible via the `actions` accessor.
|
||||
|
||||
```js
|
||||
myDux.actions.addTodo('write tutorial');
|
||||
// => { type: 'addtodo', payload: 'write tutorial' }
|
||||
```
|
||||
|
||||
### Store actions
|
||||
|
||||
The store created by an updux will have the updux actions available from a
|
||||
`actions` property. In addition, the store `dispatch` function will also have
|
||||
the actions as properties, which can be used to create the actions and
|
||||
dispatch them immediately.
|
||||
|
||||
```js
|
||||
const myDux = new Updux({
|
||||
actions: {
|
||||
foo: (x: string) => x,
|
||||
}
|
||||
});
|
||||
|
||||
const store = myDux.createStore();
|
||||
|
||||
const a = store.actions.foo('bar'); // { type: 'foo', payload: 'bar' }
|
||||
|
||||
store.dispatch.foo('bar');
|
||||
// equivalent to
|
||||
// store.dispatch( store.actions.foo('bar') );
|
||||
|
||||
|
||||
```
|
86
docs/src/content/docs/guides/005-mutations.md
Normal file
@ -0,0 +1,86 @@
|
||||
---
|
||||
title: Mutations
|
||||
---
|
||||
|
||||
Mutations are what tie actions to a reducing function.
|
||||
Their signature is a smidge different than the typical
|
||||
Redux reducing functions:
|
||||
|
||||
```js
|
||||
( actionPayload, action ) => state => newState
|
||||
```
|
||||
|
||||
## Defining a mutation
|
||||
|
||||
|
||||
They are tied to actions in an updux via the method `addMutation`.
|
||||
|
||||
```js
|
||||
todosDux.addMutation(addTodo, (description) => ({ todos, nextId }) => ({
|
||||
nextId: 1 + nextId,
|
||||
todos: todos.concat({ description, id: nextId, done: false }),
|
||||
}));
|
||||
|
||||
todosDux.addMutation(todoDone, (id) => ({ todos, nextId }) => ({
|
||||
nextId: 1 + nextId,
|
||||
todos: todos.map((todo) => {
|
||||
if (todo.id !== id) return todo;
|
||||
return {
|
||||
...todo,
|
||||
done: true,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
```
|
||||
|
||||
The first argument can be an action creator (the action will be added to the
|
||||
actions known to the updux), an action type (must be the type of an action
|
||||
already known to the updux), a function taking in an action and returning a
|
||||
boolean.
|
||||
|
||||
```
|
||||
const addTodo = createAction( 'addTodo', withPayload<string>() );
|
||||
const todoDone = createAction( 'todoDone', withPayload<number>() )
|
||||
|
||||
const myDux = new Updux({
|
||||
actions: {
|
||||
addTodo
|
||||
}
|
||||
});
|
||||
|
||||
// valid
|
||||
myDux.addMutation( addTodo, ... );
|
||||
|
||||
// valid
|
||||
myDux.addMutation( 'addTodo', ... );
|
||||
|
||||
// invalid! todoDone is not known to myDux
|
||||
myDux.addMutation( 'todoDone', ... )
|
||||
|
||||
// valid
|
||||
myDux.addMutation( todoDone, ... )
|
||||
|
||||
// valid
|
||||
myDux.addMutation(
|
||||
(action) => action.type.startsWidth('todo'),
|
||||
...
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
The first argument can also be omitted to have a mutation that
|
||||
is applied for all incoming actions.
|
||||
|
||||
```js
|
||||
myDux.addMutation( (payload,action) => state => state+1 );
|
||||
```
|
||||
|
||||
|
||||
## Default mutation
|
||||
|
||||
It's possible to set a mutation that will be triggered if an action doesn't match
|
||||
any of the other updux mutations (excluding subdux mutations).
|
||||
|
||||
```js
|
||||
myDux.setDefaultMutation( (payload,action) => state => state+1 );
|
||||
```
|
47
docs/src/content/docs/guides/010-effects.md
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Effects
|
||||
---
|
||||
|
||||
In addition to mutations, Updux also provides action-specific middleware, here
|
||||
called effects.
|
||||
|
||||
Effects use the usual Redux middleware signature, plus a few goodies.
|
||||
The `getState` and `dispatch` functions are augmented with the dux selectors
|
||||
and actions, respectively. The selectors and actions are also available
|
||||
from the api object.
|
||||
|
||||
```js
|
||||
const myEffect = ({ getState, dispatch, actions, selectors })
|
||||
=> (next) => (action) => {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
Assigning effects to an updux is done via `addEffect`, and follows the same
|
||||
pattern as `addMutation`. The filter can be the type of an action,
|
||||
an action creator, a guard function, or nothing at all.
|
||||
|
||||
```js
|
||||
const myEffect = ({getState,dispatch}) => (next) => (action) => {
|
||||
|
||||
next(action);
|
||||
|
||||
if( getState.getSpecialThing() )
|
||||
dispatch.doTheOtherThing();
|
||||
};
|
||||
|
||||
// valid
|
||||
myDux.addEffect( addTodo, myEffect );
|
||||
|
||||
// valid
|
||||
myDux.addEffect( 'addTodo', myEffect );
|
||||
|
||||
// valid
|
||||
myDux.addEffect(
|
||||
(action) => action.type.startsWidth('todo'),
|
||||
myEffect
|
||||
);
|
||||
|
||||
// valid
|
||||
myDux.addEffect( myEffect );
|
||||
``````
|
28
docs/src/content/docs/guides/020-subduxes.md
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Subduxes
|
||||
---
|
||||
|
||||
Subduxes are how Updux goes recursive. They are upduxes (or updux
|
||||
configurations) themselves and are provided at contruction time.
|
||||
|
||||
|
||||
```js
|
||||
const bar = ;
|
||||
|
||||
const mainDux = new Updux({
|
||||
subduxes: {
|
||||
bar: new Updux({
|
||||
initialState: {
|
||||
a: 1
|
||||
}
|
||||
}),
|
||||
baz: {
|
||||
initialState: {
|
||||
b: 'two'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
All properties of the subduxes will be incorporated in the main updux.
|
242
docs/src/content/docs/index.mdx
Normal file
@ -0,0 +1,242 @@
|
||||
---
|
||||
title: What's Updux?
|
||||
description: Updux manual
|
||||
# template: splash
|
||||
hero:
|
||||
# tagline: Congrats on setting up a new Starlight project!
|
||||
image:
|
||||
file: ../../../public/updux-logo.svg
|
||||
# actions:
|
||||
# - text: Example Guide
|
||||
# link: /guides/example/
|
||||
# icon: right-arrow
|
||||
# variant: primary
|
||||
# - text: Read the Starlight docs
|
||||
# link: https://starlight.astro.build
|
||||
# icon: external
|
||||
---
|
||||
|
||||
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
|
||||
|
||||
Full documentation can be [found here](https://yanick.github.io/updux/).
|
||||
Right now the best way to understand the whole thing is to go
|
||||
through the [tutorial](https://yanick.github.io/updux/#/tutorial)
|
||||
|
||||
## 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
|
||||
}
|
||||
});
|
||||
|
||||
```
|
11
docs/src/content/docs/reference/example.md
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Example Reference
|
||||
description: A reference page in my new Starlight docs site.
|
||||
---
|
||||
|
||||
Reference pages are ideal for outlining how things work in terse and clear terms.
|
||||
Less concerned with telling a story or addressing a specific use case, they should give a comprehensive outline of what you're documenting.
|
||||
|
||||
## Further reading
|
||||
|
||||
- Read [about reference](https://diataxis.fr/reference/) in the Diátaxis framework
|
74
docs/src/content/docs/tutorial/actions.mdx
Normal file
@ -0,0 +1,74 @@
|
||||
---
|
||||
title: Actions
|
||||
sidebar:
|
||||
order: 3
|
||||
---
|
||||
|
||||
import { Code } from '@astrojs/starlight/components';
|
||||
import snippet from './code/actions.test.ts?raw';
|
||||
import extractSnippet from './extractSnippet.js';
|
||||
|
||||
Having a state is good and all of that, but it's also very static.
|
||||
So let's introduce some reducing. First step: let's create some actions.
|
||||
|
||||
<Code code={extractSnippet('actions1')(snippet)} lang="js" />
|
||||
|
||||
`createAction` is actually provided by
|
||||
[@reduxjs/toolkit](https://redux-toolkit.js.org/), we only re-export it
|
||||
for convenience. `withPayload` is an utility function that makes defining the
|
||||
action payload just a little more concise.
|
||||
|
||||
```js
|
||||
createAction('foo', withPayload<string>() );
|
||||
// equivalent to
|
||||
createAction('foo', (payload: string) => ({ payload }) ) );
|
||||
|
||||
createAction('foo', withPayload( (level:number) => ({ level }) );
|
||||
// equivalent to
|
||||
createAction('foo', (level: number) => ({ payload: { level } }) ) );
|
||||
```
|
||||
|
||||
### Adding actions to an Updux object
|
||||
|
||||
Actions can be tied to an Updux object both at construction time
|
||||
|
||||
```js
|
||||
const myAction = createAction('myAction');
|
||||
const myOtherAction = createAction('myOtherAction');
|
||||
|
||||
const myDux = new Updux({
|
||||
actions: {
|
||||
myAction,
|
||||
myOtherAction,
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
or they will be automatically added when used in methods like `addMutation`.
|
||||
|
||||
```js
|
||||
|
||||
const myDux = new Updux({})
|
||||
.addMutation( myAction, (payload) => state => state + payload );
|
||||
```
|
||||
|
||||
Note the chained invocation of `addMutation`, which is needed if you want
|
||||
the type of myDux to include the `myAction` action.
|
||||
|
||||
```js
|
||||
|
||||
// myDux will have the right type for myDux.actions.myAction
|
||||
myDux = new Updux({})
|
||||
.addMutation( myAction, (payload) => state => state + payload );
|
||||
|
||||
// myDux type will not know about myAction.
|
||||
myDux = new Updux({});
|
||||
myDux.addMutation( myAction, (payload) => state => state + payload );
|
||||
|
||||
```
|
||||
|
||||
### Accessing Updux actions
|
||||
|
||||
Once an action is defined, its creator is accessible via the `actions` accessor.
|
||||
|
||||
<Code code={extractSnippet('actions2')(snippet)} lang="js" />
|
1
docs/src/content/docs/tutorial/code
Symbolic link
@ -0,0 +1 @@
|
||||
../../../../../src/tutorial
|
14
docs/src/content/docs/tutorial/extractSnippet.js
Normal file
@ -0,0 +1,14 @@
|
||||
export default (fence) => (snippet) => {
|
||||
snippet = snippet.split("\n");
|
||||
snippet = snippet.slice(snippet.findIndex(l => l.includes(fence)) + 1);
|
||||
snippet = snippet.slice(0, snippet.findIndex(l => l.includes(fence)));
|
||||
|
||||
const indent = Math.min(
|
||||
...snippet.map(l => l === '' ? 999 : l.match(/^ */)[0].length)
|
||||
);
|
||||
|
||||
if (indent)
|
||||
snippet = snippet.map(l => l.slice(indent));
|
||||
|
||||
return snippet.join("\n");
|
||||
}
|
20
docs/src/content/docs/tutorial/initialState.mdx
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
title: State definition
|
||||
sidebar:
|
||||
order: 2
|
||||
---
|
||||
|
||||
import { Code } from '@astrojs/starlight/components';
|
||||
import snippet from './code/initialState.test.ts?raw';
|
||||
import extractSnippet from './extractSnippet.js';
|
||||
|
||||
Let's start slow with an Updux document that has nothing but an initial
|
||||
state.
|
||||
|
||||
<Code code={extractSnippet('tut1')(snippet)} lang="js" />
|
||||
|
||||
Congrats! You have written your first Updux object. It
|
||||
doesn't do a lot, but you can already create a store out of it, and its
|
||||
initial state will be automatically set:
|
||||
|
||||
<Code code={extractSnippet('tut2')(snippet)} lang="js" />
|
23
docs/src/content/docs/tutorial/intro.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
title: Introduction
|
||||
description: Introducing the tutorial
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
This tutorial walks you through the features of `Updux` using the
|
||||
time-honored example of the implementation of Todo list store.
|
||||
|
||||
For simplicity sake, we'll write our reducer code using plain JavaScript.
|
||||
But for real-life applications where we want some help with
|
||||
immutability and deep merging, Updux plays extremely well with libraries and
|
||||
tools like [Mutative], [immer][], [updeep][],
|
||||
[remeda][],
|
||||
[lodash][], etc.
|
||||
|
||||
[updeep]: https://www.npmjs.com/package/@yanick/updeep-remeda
|
||||
[immer]: https://www.npmjs.com/package/immer
|
||||
[lodash]: https://www.npmjs.com/package/lodash
|
||||
[ts-action]: https://www.npmjs.com/package/ts-action
|
||||
[remeda]: remedajs.com/
|
||||
[Mutative]: https://mutative.js.org/
|
24
docs/src/content/docs/tutorial/mutations.mdx
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
title: Mutations
|
||||
sidebar:
|
||||
order: 4
|
||||
---
|
||||
|
||||
import { Code } from '@astrojs/starlight/components';
|
||||
import snippet from './code/actions.test.ts?raw';
|
||||
import extractSnippet from './extractSnippet.js';
|
||||
|
||||
Mutations are what tie actions to a reducing function.
|
||||
|
||||
## Defining a mutation
|
||||
|
||||
The signature of a mutation is just a smidge different than the typical
|
||||
Redux reducing functions:
|
||||
|
||||
```js
|
||||
( actionPayload, action ) => state => newState
|
||||
```
|
||||
|
||||
They are tied to actions in an updux via the method `addMutation`.
|
||||
|
||||
<Code code={extractSnippet('addMutation-1')(snippet)} lang="js" />
|
2
docs/src/env.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/// <reference path="../.astro/types.d.ts" />
|
||||
/// <reference types="astro/client" />
|
10
docs/tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "nodenext" /* Specify how TypeScript looks up a file from a given module specifier. */,
|
||||
"paths": {
|
||||
"updux": ["../src/index.ts"],
|
||||
"@tutorial/*": [ "../src/tutorial/*"]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
import { test, expect } from 'vitest';
|
||||
|
||||
import u from 'updeep';
|
||||
import { action, Updux, dux } from '../src/index.js';
|
||||
|
||||
const addTodoWithId = action('addTodoWithId');
|
||||
const incNextId = action('incNextId');
|
||||
const addTodo = action('addTodo');
|
||||
|
||||
const addTodoEffect = ({ getState, dispatch }) => next => action => {
|
||||
const id = getState.nextId();
|
||||
|
||||
dispatch.incNextId();
|
||||
|
||||
next(action);
|
||||
|
||||
dispatch.addTodoWithId({ description: action.payload, id });
|
||||
}
|
||||
|
||||
const todosDux = new Updux({
|
||||
initial: { nextId: 1, todos: [] },
|
||||
actions: { addTodo, incNextId, addTodoWithId },
|
||||
selectors: {
|
||||
nextId: ({nextId}) => nextId,
|
||||
},
|
||||
mutations: {
|
||||
addTodoWithId: (todo) => u({ todos: (todos) => [...todos, todo] }),
|
||||
incNextId: () => u({ nextId: id => id+1 }),
|
||||
},
|
||||
effects: {
|
||||
'addTodo': addTodoEffect
|
||||
}
|
||||
});
|
||||
|
||||
const store = todosDux.createStore();
|
||||
|
||||
test( "tutorial example", async () => {
|
||||
store.dispatch.addTodo('Do the thing');
|
||||
|
||||
expect( store.getState() ).toMatchObject({
|
||||
nextId:2, todos: [ { description: 'Do the thing', id: 1 } ]
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
test( "catch-all effect", () => {
|
||||
|
||||
let seen = [];
|
||||
|
||||
const foo = new Updux({
|
||||
actions: {
|
||||
one: null,
|
||||
two: null,
|
||||
},
|
||||
effects: {
|
||||
'*': (api) => next => action => {
|
||||
seen.push(action.type);
|
||||
next(action);
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
const store = foo.createStore();
|
||||
|
||||
store.dispatch.one();
|
||||
store.dispatch.two();
|
||||
|
||||
expect(seen).toEqual([ 'one', 'two' ]);
|
||||
} )
|
@ -1,40 +0,0 @@
|
||||
import { test, expect } from 'vitest';
|
||||
import u from 'updeep';
|
||||
|
||||
import { Updux } from '../src/index.js';
|
||||
|
||||
const todos = new Updux({
|
||||
initial: [],
|
||||
actions: {
|
||||
setNbrTodos: null,
|
||||
addTodo: null,
|
||||
},
|
||||
mutations: {
|
||||
addTodo: todo => todos => [ ...todos, todo ],
|
||||
},
|
||||
reactions: [
|
||||
({dispatch}) => todos => dispatch.setNbrTodos(todos.length)
|
||||
],
|
||||
});
|
||||
|
||||
const myDux = new Updux({
|
||||
initial: {
|
||||
nbrTodos: 0
|
||||
},
|
||||
subduxes: {
|
||||
todos,
|
||||
},
|
||||
mutations: {
|
||||
setNbrTodos: nbrTodos => u({ nbrTodos })
|
||||
}
|
||||
});
|
||||
|
||||
test( "basic tests", async () => {
|
||||
const store = myDux.createStore();
|
||||
|
||||
store.dispatch.addTodo('one');
|
||||
store.dispatch.addTodo('two');
|
||||
|
||||
expect(store.getState().nbrTodos).toEqual(2);
|
||||
});
|
||||
|
@ -1,27 +0,0 @@
|
||||
import { test, expect } from 'vitest';
|
||||
|
||||
import { Updux } from '../src/index.js';
|
||||
|
||||
test( 'selectors', () => {
|
||||
const dux = new Updux({
|
||||
initial: { a: 1, b: 2 },
|
||||
selectors: {
|
||||
getA: ({a}) => a,
|
||||
getBPlus: ({b}) => addition => b + addition,
|
||||
},
|
||||
subduxes: {
|
||||
subbie: new Updux({
|
||||
initial: { d: 3 },
|
||||
selectors: {
|
||||
getD: ({d}) => d
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const store = dux.createStore();
|
||||
|
||||
expect( store.getState.getA() ).toEqual(1);
|
||||
expect( store.getState.getBPlus(7) ).toEqual(9);
|
||||
expect( store.getState.getD() ).toEqual(3);
|
||||
} );
|
@ -1,91 +0,0 @@
|
||||
import { test, expect } from 'vitest';
|
||||
|
||||
import u from 'updeep';
|
||||
import R from 'remeda';
|
||||
|
||||
import { Updux } from '../src/index.js';
|
||||
|
||||
const nextIdDux = new Updux({
|
||||
initial: 1,
|
||||
actions: {
|
||||
incrementNextId: null,
|
||||
},
|
||||
selectors: {
|
||||
getNextId: state => state
|
||||
},
|
||||
mutations: {
|
||||
incrementNextId: () => state => state + 1,
|
||||
}
|
||||
});
|
||||
|
||||
const matches = conditions => target => Object.entries(conditions).every(
|
||||
([key,value]) => typeof value === 'function' ? value(target[key]) : target[key] === value
|
||||
);
|
||||
|
||||
const todoDux = new Updux({
|
||||
initial: {
|
||||
id: 0,
|
||||
description: "",
|
||||
done: false,
|
||||
},
|
||||
actions: {
|
||||
todoDone: null,
|
||||
},
|
||||
mutations: {
|
||||
todoDone: id => u.if( matches({id}), { done: true })
|
||||
},
|
||||
selectors: {
|
||||
desc: R.prop('description'),
|
||||
}
|
||||
});
|
||||
|
||||
const todosDux = new Updux({
|
||||
initial: [],
|
||||
subduxes: {
|
||||
'*': todoDux
|
||||
},
|
||||
actions: {
|
||||
addTodoWithId: (description, id) => ({description, id} )
|
||||
},
|
||||
findSelectors: {
|
||||
getTodoById: state => id => state.find(matches({id}))
|
||||
},
|
||||
mutations: {
|
||||
addTodoWithId: todo => todos => [...todos, todo]
|
||||
}
|
||||
});
|
||||
|
||||
const mainDux = new Updux({
|
||||
subduxes: {
|
||||
nextId: nextIdDux,
|
||||
todos: todosDux,
|
||||
},
|
||||
actions: {
|
||||
addTodo: null
|
||||
},
|
||||
effects: {
|
||||
addTodo: ({ getState, dispatch }) => next => action => {
|
||||
const id = getState.getNextId();
|
||||
|
||||
dispatch.incrementNextId()
|
||||
|
||||
next(action);
|
||||
|
||||
dispatch.addTodoWithId( action.payload, id );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const store = mainDux.createStore();
|
||||
|
||||
|
||||
test( "basic tests", () => {
|
||||
|
||||
const myDesc = 'do the thing';
|
||||
store.dispatch.addTodo(myDesc);
|
||||
|
||||
expect( store.getState.getTodoById(1).desc() ).toEqual(myDesc);
|
||||
|
||||
|
||||
|
||||
});
|
407
docs/tutorial.md
@ -1,407 +0,0 @@
|
||||
# Tutorial
|
||||
|
||||
This tutorial walks you through the features of `Updux` using the
|
||||
time-honored example of the implementation of Todo list store.
|
||||
|
||||
We'll be using
|
||||
[updeep](https://www.npmjs.com/package/updeep) to
|
||||
help with immutability and deep merging,
|
||||
but that's totally optional. If `updeep` is not your bag,
|
||||
it can easily be substitued with, say, [immer][], [lodash][], or even
|
||||
plain JavaScript.
|
||||
|
||||
## Definition of the state
|
||||
|
||||
To begin with, let's define that has nothing but an initial state.
|
||||
|
||||
```js
|
||||
import { Updux } from 'updux';
|
||||
|
||||
const todosDux = new Updux({
|
||||
initial: {
|
||||
next_id: 1,
|
||||
todos: [],
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Congrats! You have written your first Updux object. It
|
||||
doesn't do a lot, but you can already create a store out of it, and its
|
||||
initial state will be automatically set:
|
||||
|
||||
```js
|
||||
const store = todosDux.createStore();
|
||||
|
||||
console.log(store.getState()); // prints { next_id: 1, todos: [] }
|
||||
```
|
||||
|
||||
## Add actions
|
||||
|
||||
This is all good, but a little static. Let's add actions!
|
||||
|
||||
```js
|
||||
const todosDux = new Updux({
|
||||
initial: {
|
||||
next_id: 1,
|
||||
todos: [],
|
||||
},
|
||||
{
|
||||
addTodo: null,
|
||||
todoDone: null,
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Accessing actions
|
||||
|
||||
Once an action is defined, its creator is accessible via the `actions` accessor.
|
||||
|
||||
```js
|
||||
console.log( todosDux.actions.addTodo('write tutorial') );
|
||||
// prints { type: 'addTodo', payload: 'write tutorial' }
|
||||
```
|
||||
### Adding a mutation
|
||||
|
||||
Mutations are the reducing functions associated to actions. They
|
||||
are defined via the `setMutation` method:
|
||||
|
||||
|
||||
```js
|
||||
todosDux.setMutation( 'addTodo', description => ({next_id: id, todos}) => ({
|
||||
next_id: 1 + id,
|
||||
todos: [...todos, { description, id, done: false }]
|
||||
}));
|
||||
```
|
||||
## Effects
|
||||
|
||||
In addition to mutations, Updux also provides action-specific middleware, here
|
||||
called effects.
|
||||
|
||||
Effects use the usual Redux middleware signature, plus a few goodies.
|
||||
The `getState` and `dispatch` functions are augmented with the dux selectors,
|
||||
and actions, respectively. The selectors and actions are also available
|
||||
from the api object.
|
||||
|
||||
```js
|
||||
import u from 'updeep';
|
||||
import { action, Updux } from 'updux';
|
||||
|
||||
// we want to decouple the increment of next_id and the creation of
|
||||
// a new todo. So let's use a new version of the action 'addTodo'.
|
||||
|
||||
const addTodoWithId = action('addTodoWithId');
|
||||
const incNextId = action('incNextId');
|
||||
const addTodo = action('addTodo');
|
||||
|
||||
const addTodoEffect = ({ getState, dispatch }) => next => action => {
|
||||
const id = getState.nextId();
|
||||
|
||||
dispatch.incNextId();
|
||||
|
||||
next(action);
|
||||
|
||||
dispatch.addTodoWithId({ description: action.payload, id });
|
||||
}
|
||||
|
||||
const todosDux = new Updux({
|
||||
initial: { nextId: 1, todos: [] },
|
||||
actions: { addTodo, incNextId, addTodoWithId },
|
||||
selectors: {
|
||||
nextId: ({nextId}) => nextId,
|
||||
},
|
||||
mutations: {
|
||||
addTodoWithId: (todo) => u({ todos: (todos) => [...todos, todo] }),
|
||||
incNextId: () => u({ nextId: id => id+1 }),
|
||||
},
|
||||
effects: {
|
||||
'addTodo': addTodoEffect
|
||||
}
|
||||
});
|
||||
|
||||
const store = todosDux.createStore();
|
||||
|
||||
store.dispatch.addTodo('Do the thing');
|
||||
```
|
||||
|
||||
### Catch-all effect
|
||||
|
||||
It is possible to have an effect match all actions via the special `*` token.
|
||||
|
||||
```
|
||||
todosUpdux.addEffect('*', () => next => action => {
|
||||
console.log( 'seeing action fly by:', action );
|
||||
next(action);
|
||||
});
|
||||
```
|
||||
|
||||
## Adding selectors
|
||||
|
||||
Selectors can be defined to get data derived from the state.
|
||||
From now you should know the drill: selectors can be defined at construction
|
||||
time or via `setSelector`.
|
||||
|
||||
```
|
||||
const getTodoById = ({todos}) => targetId => todos.find(({id}) => id === targetId);
|
||||
|
||||
const todosUpdux = new Updux({
|
||||
selectors: {
|
||||
getTodoById
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
todosDux.setSelector('getTodoById', getTodoById);
|
||||
```
|
||||
|
||||
### Accessing selectors
|
||||
|
||||
The `getState` method of a dux store is augmented
|
||||
with its selectors, with the first call for the state already
|
||||
called in for you.
|
||||
|
||||
```js
|
||||
const store = todosDux.createStore();
|
||||
|
||||
console.log(
|
||||
todosUpdux.getState.getTodoById(1)
|
||||
);
|
||||
```
|
||||
|
||||
## Subduxes
|
||||
|
||||
Now that we have all the building blocks, we can embark on the last and
|
||||
funkiest part of Updux: its recursive nature.
|
||||
|
||||
### Recap: the Todos dux, undivided
|
||||
|
||||
Upduxes can be divided into sub-upduxes that deal with the various parts of
|
||||
the global state. This is better understood by working out an example, so
|
||||
let's recap on the Todos dux we have so far:
|
||||
|
||||
```js
|
||||
import Updux from 'updux';
|
||||
import u from 'updeep';
|
||||
import fp from 'lodash/fp';
|
||||
|
||||
const todosDux = new Updux({
|
||||
initial: {
|
||||
nextId: 1,
|
||||
todos: [],
|
||||
},
|
||||
actions: {
|
||||
addTodo: null,
|
||||
addTodoWithId: (description, id) => ({description, id, done: false}),
|
||||
todoDone: null,
|
||||
incNextId: null,
|
||||
},
|
||||
selectors: {
|
||||
getTodoById: ({todos}) => id => fp.find({id},todos)
|
||||
},
|
||||
mutations: {
|
||||
addTodoWithId: todo =>
|
||||
u.updateIn( 'todos', todos => [ ...todos, todo] ),
|
||||
incrementNextId: () => u({ nextId: fp.add(1) }),
|
||||
todoDone: (id) => u.updateIn('todos',
|
||||
u.map( u.if( fp.matches({id}), todo => u({done: true}, todo) ) )
|
||||
),
|
||||
},
|
||||
effects: {
|
||||
addTodo: ({ getState, dispatch }) => next => action => {
|
||||
const { nextId: id } = getState();
|
||||
|
||||
dispatch.incNextId();
|
||||
|
||||
next(action);
|
||||
|
||||
dispatch.addTodoWithId(action.payload, id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
This store has two main components: the `nextId`, and the `todos` collection.
|
||||
The `todos` collection is itself composed of the individual `todo`s. Let's
|
||||
create upduxes for each of those.
|
||||
|
||||
### NextId dux
|
||||
|
||||
```
|
||||
// dux/nextId.js
|
||||
|
||||
import { Updux } from 'updux';
|
||||
import u from 'updeep';
|
||||
|
||||
export default new Updux({
|
||||
initial: 1,
|
||||
actions: {
|
||||
incrementNextId: null,
|
||||
},
|
||||
selectors: {
|
||||
getNextId: state => state
|
||||
},
|
||||
mutations: {
|
||||
incrementNextId: () => state => state + 1,
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
### Todo updux
|
||||
|
||||
```
|
||||
// dux/todos/todo/index.ts
|
||||
|
||||
import { Updux } from 'updux';
|
||||
import u from 'updeep';
|
||||
import fp from 'lodash/fp';
|
||||
|
||||
export default new Updux({
|
||||
initial: {
|
||||
id: 0,
|
||||
description: "",
|
||||
done: false,
|
||||
},
|
||||
actions: {
|
||||
todoDone: null,
|
||||
},
|
||||
mutations: {
|
||||
todoDone: id => u.if( fp.matches({id}), { done: true }) )
|
||||
},
|
||||
selectors: {
|
||||
desc: ({description}) => description,
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
### Todos updux
|
||||
|
||||
```
|
||||
// dux/todos/index.js
|
||||
|
||||
import { Updux } from 'updux';
|
||||
import u from 'updeep';
|
||||
import fp from 'lodash/fp';
|
||||
|
||||
import todo from './todo/index.js';
|
||||
|
||||
export default new Updux({
|
||||
initial: [],
|
||||
subduxes: {
|
||||
'*': todoDux
|
||||
},
|
||||
actions: {
|
||||
addTodoWithId: (description, id) => ({description, id} )
|
||||
},
|
||||
findSelectors: {
|
||||
getTodoById: state => id => fp.find({id},state)
|
||||
},
|
||||
mutations: {
|
||||
addTodoWithId: todo =>
|
||||
todos => [ ...todos, todo ]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Note the special '\*' subdux key used here. This
|
||||
allows the updux to map every item present in its
|
||||
state to a `todo` updux. See [this recipe](/recipes?id=mapping-a-mutation-to-all-values-of-a-state) for details.
|
||||
|
||||
### Main store
|
||||
|
||||
```
|
||||
// dux/index.js
|
||||
|
||||
import Updux from 'updux';
|
||||
|
||||
import todos from './todos';
|
||||
import nextId from './next_id';
|
||||
|
||||
export new Updux({
|
||||
subduxes: {
|
||||
nextId,
|
||||
todos,
|
||||
},
|
||||
actions: {
|
||||
addTodo: null
|
||||
},
|
||||
effects: {
|
||||
addTodo: ({ getState, dispatch }) => next => action => {
|
||||
const id = getState.getNextId();
|
||||
|
||||
dispatch.incrementNextId()
|
||||
|
||||
next(action);
|
||||
|
||||
dispatch.addTodoWithId( action.payload, id );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
Tadah! We had to define the `addTodo` effect at the top level as it needs to
|
||||
access the `getNextId` selector from `nextId` and the `addTodoWithId`
|
||||
action from the `todos`.
|
||||
|
||||
Note that the `getNextId` selector still gets the
|
||||
right value; when aggregating subduxes selectors Updux auto-wraps them to
|
||||
access the right slice of the top object. ```
|
||||
|
||||
## Reactions
|
||||
|
||||
Reactions -- aka Redux's subscriptions -- can be added to a updux store via the initial config
|
||||
or the method `addSubscription`. The signature of a reaction is:
|
||||
|
||||
```
|
||||
(storeApi) => (state, previousState, unsubscribe) => {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Subscriptions registered for an updux and its subduxes are automatically
|
||||
subscribed to the store when calling `createStore`.
|
||||
|
||||
The `state` passed to the subscriptions of the subduxes is the local state.
|
||||
|
||||
Also, all subscriptions are wrapped such that they are called only if the
|
||||
local `state` changed since their last invocation.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
const todos = new Updux({
|
||||
initial: [],
|
||||
actions: {
|
||||
setNbrTodos: null,
|
||||
addTodo: null,
|
||||
},
|
||||
mutations: {
|
||||
addTodo: todo => todos => [ ...todos, todo ],
|
||||
},
|
||||
reactions: [
|
||||
({dispatch}) => todos => dispatch.setNbrTodos(todos.length)
|
||||
],
|
||||
});
|
||||
|
||||
const myDux = new Updux({
|
||||
initial: {
|
||||
nbrTodos: 0
|
||||
},
|
||||
subduxes: {
|
||||
todos,
|
||||
},
|
||||
mutations: {
|
||||
setNbrTodos: nbrTodos => u({ nbrTodos })
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
[immer]: https://www.npmjs.com/package/immer
|
||||
[lodash]: https://www.npmjs.com/package/lodash
|
||||
[ts-action]: https://www.npmjs.com/package/ts-action
|
||||
|
@ -1,26 +0,0 @@
|
||||
import { test, expect } from 'vitest';
|
||||
|
||||
import { Updux } from '../src/index.js';
|
||||
|
||||
test( "basic checks", async () => {
|
||||
|
||||
|
||||
const todosDux = new Updux({
|
||||
initial: {
|
||||
next_id: 1,
|
||||
todos: [],
|
||||
},
|
||||
actions: {
|
||||
addTodo: null,
|
||||
todoDone: null,
|
||||
}
|
||||
});
|
||||
|
||||
const store = todosDux.createStore();
|
||||
|
||||
expect(store.getState()).toEqual({ next_id: 1, todos: [] });
|
||||
|
||||
expect(store.actions.addTodo("learn updux")).toMatchObject({
|
||||
type: 'addTodo', payload: 'learn updux'
|
||||
})
|
||||
});
|
160
out/Updux.html
@ -19,7 +19,7 @@
|
||||
|
||||
<h1 class="page-title">Class: Updux</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -27,24 +27,24 @@
|
||||
<section>
|
||||
|
||||
<header>
|
||||
|
||||
|
||||
<h2><span class="attribs"><span class="type-signature"></span></span>Updux<span class="signature">()</span><span class="type-signature"></span></h2>
|
||||
|
||||
|
||||
|
||||
|
||||
</header>
|
||||
|
||||
<article>
|
||||
<div class="container-overview">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="Updux"><span class="type-signature"></span>new Updux<span class="signature">()</span><span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -62,42 +62,42 @@
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="Updux.js.html">Updux.js</a>, <a href="Updux.js.html#line24">line 24</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
@ -119,33 +119,33 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h3 class="subsection-title">Classes</h3>
|
||||
|
||||
<dl>
|
||||
<dt><a href="Updux.html">Updux</a></dt>
|
||||
<dd></dd>
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h3 class="subsection-title">Members</h3>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="actions"><span class="type-signature"></span>actions<span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
@ -159,42 +159,42 @@
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="Updux.js.html">Updux.js</a>, <a href="Updux.js.html#line111">line 111</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
@ -202,8 +202,8 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="initial"><span class="type-signature"></span>initial<span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
@ -217,42 +217,42 @@
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="Updux.js.html">Updux.js</a>, <a href="Updux.js.html#line104">line 104</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
@ -260,14 +260,14 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</article>
|
||||
|
||||
</section>
|
||||
@ -290,4 +290,4 @@
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -19,11 +19,11 @@
|
||||
|
||||
<h1 class="page-title">Source: Updux.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/* TODO change * for leftovers to +, change subscriptions to reactions */
|
||||
|
@ -1827,4 +1827,4 @@
|
||||
<hkern u1="„" u2="G" k="102" />
|
||||
<hkern u1="„" u2="C" k="102" />
|
||||
</font>
|
||||
</defs></svg>
|
||||
</defs></svg>
|
||||
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
@ -1827,4 +1827,4 @@
|
||||
<hkern u1="„" u2="G" k="102" />
|
||||
<hkern u1="„" u2="C" k="102" />
|
||||
</font>
|
||||
</defs></svg>
|
||||
</defs></svg>
|
||||
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
@ -1827,4 +1827,4 @@
|
||||
<hkern u1="„" u2="G" k="102" />
|
||||
<hkern u1="„" u2="C" k="102" />
|
||||
</font>
|
||||
</defs></svg>
|
||||
</defs></svg>
|
||||
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
@ -1828,4 +1828,4 @@
|
||||
<hkern u1="„" u2="G" k="102" />
|
||||
<hkern u1="„" u2="C" k="102" />
|
||||
</font>
|
||||
</defs></svg>
|
||||
</defs></svg>
|
||||
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
@ -1832,4 +1832,4 @@
|
||||
<hkern g1="uniFB00" u2="'" k="-123" />
|
||||
<hkern g1="uniFB00" u2=""" k="-123" />
|
||||
</font>
|
||||
</defs></svg>
|
||||
</defs></svg>
|
||||
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
@ -1828,4 +1828,4 @@
|
||||
<hkern u1="„" u2="G" k="102" />
|
||||
<hkern u1="„" u2="C" k="102" />
|
||||
</font>
|
||||
</defs></svg>
|
||||
</defs></svg>
|
||||
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
@ -19,11 +19,11 @@
|
||||
|
||||
<h1 class="page-title">Home</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h3> </h3>
|
||||
@ -37,7 +37,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -62,4 +62,4 @@
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
30
package.json
@ -1,20 +1,28 @@
|
||||
{
|
||||
"version": "4.0.0-alpha.5",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"immer": "^9.0.15",
|
||||
"@mobily/ts-belt": "4.0.0-rc.5",
|
||||
"@yanick/updeep-remeda": "^2.3.1",
|
||||
"json-schema-shorthand": "^2.0.0",
|
||||
"json-schema-to-ts": "^2.9.2",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"moize": "^6.1.6",
|
||||
"redux": "^4.2.0",
|
||||
"remeda": "^1.0.1",
|
||||
"updeep": "^1.2.1"
|
||||
},
|
||||
"license": "MIT",
|
||||
"main": "src/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"name": "updux",
|
||||
"description": "Updeep-friendly Redux helper framework",
|
||||
"scripts": {
|
||||
"docsify:serve": "docsify serve docs"
|
||||
},
|
||||
"version": "4.0.0",
|
||||
"scripts": {},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/yanick/updux.git"
|
||||
@ -30,12 +38,16 @@
|
||||
"homepage": "https://github.com/yanick/updux#readme",
|
||||
"devDependencies": {
|
||||
"@vitest/browser": "^0.23.1",
|
||||
"@vitest/ui": "^0.23.1",
|
||||
"@vitest/ui": "^2.0.5",
|
||||
"eslint": "^8.22.0",
|
||||
"eslint-plugin-no-only-tests": "^3.0.0",
|
||||
"eslint-plugin-todo-plz": "^1.2.1",
|
||||
"jsdoc-to-markdown": "^7.1.1",
|
||||
"prettier": "^2.7.1",
|
||||
"vitest": "0.23.1"
|
||||
"redux-toolkit": "^1.1.2",
|
||||
"typedoc": "^0.25.9",
|
||||
"typedoc-plugin-markdown": "^3.17.1",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^4.2.1",
|
||||
"vitest": "2.0.4"
|
||||
}
|
||||
}
|
||||
|
2197
pnpm-lock.yaml
457
src/Updux.original
Normal file
@ -0,0 +1,457 @@
|
||||
/* TODO change * for leftovers to +, change subscriptions to reactions */
|
||||
import moize from 'moize';
|
||||
import u from 'updeep';
|
||||
import { createStore as reduxCreateStore, applyMiddleware } from 'redux';
|
||||
import { get, map, mapValues, merge, difference } from 'lodash-es';
|
||||
|
||||
import { buildInitial } from './buildInitial/index.js';
|
||||
import { buildActions } from './buildActions/index.js';
|
||||
import { buildSelectors } from './buildSelectors/index.js';
|
||||
import { action } from './actions.js';
|
||||
import { buildUpreducer } from './buildUpreducer.js';
|
||||
import {
|
||||
buildMiddleware,
|
||||
augmentMiddlewareApi,
|
||||
effectToMiddleware,
|
||||
} from './buildMiddleware/index.js';
|
||||
|
||||
import {
|
||||
AggregateDuxActions,
|
||||
AggregateDuxState,
|
||||
Dict,
|
||||
ItemsOf,
|
||||
Reducer,
|
||||
Upreducer,
|
||||
} from './types.js';
|
||||
|
||||
type Mutation<TState,TAction extends { payload?: any }> = (payload:TAction['payload'], action:TAction) => (state: TState) => TState;
|
||||
|
||||
/**
|
||||
* Configuration object typically passed to the constructor of the class Updux.
|
||||
*/
|
||||
export interface UpduxConfig<
|
||||
TState = any,
|
||||
TActions = {},
|
||||
TSelectors = {},
|
||||
TSubduxes = {}
|
||||
> {
|
||||
/**
|
||||
* Local initialState state.
|
||||
* @default {}
|
||||
*/
|
||||
initialState?: TState;
|
||||
|
||||
/**
|
||||
* Subduxes to be merged to this dux.
|
||||
*/
|
||||
subduxes?: TSubduxes;
|
||||
|
||||
/**
|
||||
* Local actions.
|
||||
*/
|
||||
actions?: TActions;
|
||||
|
||||
/**
|
||||
* Local selectors.
|
||||
*/
|
||||
selectors?: Record<string, Function>;
|
||||
|
||||
/**
|
||||
* Local mutations
|
||||
*/
|
||||
mutations?: Record<string, Function>;
|
||||
|
||||
/**
|
||||
* Selectors to apply to the mapped subduxes. Only
|
||||
* applicable if the dux is a mapping dux.
|
||||
*/
|
||||
mappedSelectors?: Record<string, Function>;
|
||||
|
||||
/**
|
||||
* Local effects.
|
||||
*/
|
||||
effects?: Record<string, Function>;
|
||||
|
||||
/**
|
||||
* Local reactions.
|
||||
*/
|
||||
reactions?: Function[];
|
||||
|
||||
/**
|
||||
* If true, enables mapped reactions. Additionally, it can be
|
||||
* a reaction function, which will treated as a regular
|
||||
* reaction for the mapped dux.
|
||||
*/
|
||||
mappedReaction?: Function | boolean;
|
||||
|
||||
/**
|
||||
* Wrapping function for the upreducer to provides full customization.
|
||||
* @example
|
||||
* // if an action has the 'dontDoIt' meta flag, don't do anything
|
||||
* const dux = new Updux({
|
||||
* ...,
|
||||
* upreducerWrapper: (upreducer) => action => {
|
||||
* if( action?.meta?.dontDoIt ) return state => state;
|
||||
* return upreducer(action);
|
||||
* }
|
||||
* })
|
||||
*/
|
||||
upreducerWrapper?: (
|
||||
upreducer: Upreducer<
|
||||
AggregateDuxState<TState, TSubduxes>,
|
||||
ItemsOf<AggregateDuxActions<TActions, TSubduxes>>
|
||||
>
|
||||
) => Upreducer<
|
||||
AggregateDuxState<TState, TSubduxes>,
|
||||
ItemsOf<AggregateDuxActions<TActions, TSubduxes>>
|
||||
>;
|
||||
|
||||
middlewareWrapper?: Function;
|
||||
}
|
||||
|
||||
export class Updux<
|
||||
TState extends any = {},
|
||||
TActions extends object = {},
|
||||
TSelectors = {},
|
||||
TSubduxes extends object = {}
|
||||
> {
|
||||
/** @type { unknown } */
|
||||
#initialState = {};
|
||||
#subduxes = {};
|
||||
|
||||
/** @type Record<string,Function> */
|
||||
#actions = {};
|
||||
#selectors = {};
|
||||
#mutations = {};
|
||||
#effects = [];
|
||||
#reactions = [];
|
||||
#mappedSelectors = undefined;
|
||||
#mappedReaction = undefined;
|
||||
#upreducerWrapper = undefined;
|
||||
|
||||
#middlewareWrapper = undefined;
|
||||
|
||||
constructor(
|
||||
config: UpduxConfig<TState, TActions, TSelectors, TSubduxes>
|
||||
) {
|
||||
this.#initialState = config.initialState ?? {};
|
||||
this.#subduxes = config.subduxes ?? {};
|
||||
|
||||
if (config.subduxes) {
|
||||
this.#subduxes = mapValues(config.subduxes, (sub) =>
|
||||
sub instanceof Updux ? sub : new Updux(sub)
|
||||
);
|
||||
}
|
||||
|
||||
if (config.actions) {
|
||||
for (const [type, actionArg] of Object.entries(config.actions)) {
|
||||
if (typeof actionArg === 'function' && actionArg.type) {
|
||||
this.#actions[type] = actionArg;
|
||||
} else {
|
||||
const args = Array.isArray(actionArg)
|
||||
? actionArg
|
||||
: [actionArg];
|
||||
this.#actions[type] = action(type, ...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.#selectors = config.selectors ?? {};
|
||||
this.#mappedSelectors = config.mappedSelectors;
|
||||
|
||||
this.#mutations = config.mutations ?? {};
|
||||
|
||||
Object.keys(this.#mutations)
|
||||
.filter((action) => action !== '+')
|
||||
.filter((action) => !this.actions.hasOwnProperty(action))
|
||||
.forEach((action) => {
|
||||
throw new Error(`action '${action}' is not defined`);
|
||||
});
|
||||
|
||||
if (config.effects) {
|
||||
this.#effects = Object.entries(config.effects);
|
||||
}
|
||||
|
||||
this.#reactions = config.reactions ?? [];
|
||||
|
||||
this.#mappedReaction = config.mappedReaction;
|
||||
|
||||
this.#upreducerWrapper = config.upreducerWrapper;
|
||||
|
||||
this.#middlewareWrapper = config.middlewareWrapper;
|
||||
}
|
||||
|
||||
#memoInitial = moize(buildInitial);
|
||||
#memoActions = moize(buildActions);
|
||||
#memoSelectors = moize(buildSelectors);
|
||||
#memoUpreducer = moize(buildUpreducer);
|
||||
#memoMiddleware = moize(buildMiddleware);
|
||||
|
||||
setMappedSelector(name, f) {
|
||||
this.#mappedSelectors = {
|
||||
...this.#mappedSelectors,
|
||||
[name]: f,
|
||||
};
|
||||
}
|
||||
|
||||
get middleware() {
|
||||
return this.#memoMiddleware(
|
||||
this.#effects,
|
||||
this.actions,
|
||||
this.selectors,
|
||||
this.#subduxes,
|
||||
this.#middlewareWrapper,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
setMiddlewareWrapper(wrapper: Function) {
|
||||
this.#middlewareWrapper = wrapper;
|
||||
}
|
||||
|
||||
/** @member { unknown } */
|
||||
get initialState(): AggregateDuxState<TState, TSubduxes> {
|
||||
return this.#memoInitial(this.#initialState, this.#subduxes);
|
||||
}
|
||||
|
||||
get actions(): AggregateDuxActions<TActions, TSubduxes> {
|
||||
return this.#memoActions(this.#actions, this.#subduxes) as any;
|
||||
}
|
||||
|
||||
get selectors() {
|
||||
return this.#memoSelectors(
|
||||
this.#selectors,
|
||||
this.#mappedSelectors,
|
||||
this.#subduxes
|
||||
);
|
||||
}
|
||||
|
||||
get subduxes() { return this.#subduxes }
|
||||
|
||||
get upreducer(): Upreducer<
|
||||
AggregateDuxState<TState, TSubduxes>,
|
||||
ItemsOf<AggregateDuxActions<TActions, TSubduxes>>
|
||||
> {
|
||||
return this.#memoUpreducer(
|
||||
this.initialState,
|
||||
this.#mutations,
|
||||
this.#subduxes,
|
||||
this.#upreducerWrapper
|
||||
);
|
||||
}
|
||||
|
||||
get reducer(): Reducer<
|
||||
AggregateDuxState<TState, TSubduxes>,
|
||||
ItemsOf<AggregateDuxActions<TActions, TSubduxes>>
|
||||
> {
|
||||
return (state, action) => this.upreducer(action)(state);
|
||||
}
|
||||
|
||||
addSubscription(subscription) {
|
||||
this.#reactions = [...this.#reactions, subscription];
|
||||
}
|
||||
|
||||
addReaction(reaction) {
|
||||
this.#reactions = [...this.#reactions, reaction];
|
||||
}
|
||||
|
||||
|
||||
setAction(type, payloadFunc?: (...args: any) => any) {
|
||||
const theAction = action(type, payloadFunc);
|
||||
|
||||
this.#actions = { ...this.#actions, [type]: theAction };
|
||||
|
||||
return theAction;
|
||||
}
|
||||
|
||||
setSelector(name, func) {
|
||||
// TODO selector already exists? Complain!
|
||||
this.#selectors = {
|
||||
...this.#selectors,
|
||||
[name]: func,
|
||||
};
|
||||
return func;
|
||||
}
|
||||
|
||||
setMutation<TAction extends keyof AggregateDuxActions<TActions,TSubduxes>>(name: TAction, mutation: Mutation<AggregateDuxState<TState, TSubduxes>,
|
||||
ReturnType<AggregateDuxActions<TActions,TSubduxes>[TAction]>>) {
|
||||
if (typeof name === 'function') name = name.type;
|
||||
|
||||
this.#mutations = {
|
||||
...this.#mutations,
|
||||
[name]: mutation,
|
||||
};
|
||||
|
||||
return mutation;
|
||||
}
|
||||
|
||||
addEffect<TType, E>(action: TType, effect: E): E {
|
||||
this.#effects = [...this.#effects, [action, effect]];
|
||||
return effect;
|
||||
}
|
||||
|
||||
augmentMiddlewareApi(api) {
|
||||
return augmentMiddlewareApi(api, this.actions, this.selectors);
|
||||
}
|
||||
|
||||
splatSubscriber(store, inner, splatReaction) {
|
||||
const cache = {};
|
||||
|
||||
return () => (state, previous, unsub) => {
|
||||
const cacheKeys = Object.keys(cache);
|
||||
|
||||
const newKeys = difference(Object.keys(state), cacheKeys);
|
||||
|
||||
for (const slice of newKeys) {
|
||||
let localStore = {
|
||||
...store,
|
||||
getState: () => store.getState()[slice],
|
||||
};
|
||||
|
||||
cache[slice] = [];
|
||||
|
||||
if (typeof splatReaction === 'function') {
|
||||
localStore = {
|
||||
...localStore,
|
||||
...splatReaction(localStore, slice),
|
||||
};
|
||||
}
|
||||
|
||||
const { unsub, subscriber, subscriberRaw } =
|
||||
inner.subscribeAll(localStore);
|
||||
|
||||
cache[slice].push({ unsub, subscriber, subscriberRaw });
|
||||
subscriber();
|
||||
}
|
||||
|
||||
const deletedKeys = difference(cacheKeys, Object.keys(state));
|
||||
|
||||
for (const deleted of deletedKeys) {
|
||||
for (const inner of cache[deleted]) {
|
||||
inner.subscriber();
|
||||
inner.unsub();
|
||||
}
|
||||
delete cache[deleted];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
subscribeTo(store, subscription, setupArgs = []) {
|
||||
const localStore = augmentMiddlewareApi(
|
||||
{
|
||||
...store,
|
||||
subscribe: (subscriber) =>
|
||||
this.subscribeTo(store, () => subscriber),
|
||||
},
|
||||
this.actions,
|
||||
this.selectors
|
||||
);
|
||||
|
||||
const subscriber = subscription(localStore, ...setupArgs);
|
||||
|
||||
let previous;
|
||||
|
||||
const memoSub = () => {
|
||||
const state = store.getState();
|
||||
if (state === previous) return;
|
||||
let p = previous;
|
||||
previous = state;
|
||||
subscriber(state, p, unsub);
|
||||
};
|
||||
|
||||
let ret = store.subscribe(memoSub);
|
||||
const unsub = typeof ret === 'function' ? ret : ret.unsub;
|
||||
return {
|
||||
unsub,
|
||||
subscriber: memoSub,
|
||||
subscriberRaw: subscriber,
|
||||
};
|
||||
}
|
||||
|
||||
subscribeAll(store) {
|
||||
let results = this.#reactions.map((sub) =>
|
||||
this.subscribeTo(store, sub)
|
||||
);
|
||||
|
||||
for (const subdux in this.#subduxes) {
|
||||
if (subdux !== '*') {
|
||||
const localStore = {
|
||||
...store,
|
||||
getState: () => get(store.getState(), subdux),
|
||||
};
|
||||
results.push(this.#subduxes[subdux].subscribeAll(localStore));
|
||||
}
|
||||
}
|
||||
|
||||
if (this.#mappedReaction) {
|
||||
results.push(
|
||||
this.subscribeTo(
|
||||
store,
|
||||
this.splatSubscriber(
|
||||
store,
|
||||
this.#subduxes['*'],
|
||||
this.#mappedReaction
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
unsub: () => results.forEach(({ unsub }) => unsub()),
|
||||
subscriber: () =>
|
||||
results.forEach(({ subscriber }) => subscriber()),
|
||||
subscriberRaw: (...args) =>
|
||||
results.forEach(({ subscriberRaw }) =>
|
||||
subscriberRaw(...args)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
createStore(initialState?: unknown, enhancerGenerator?: Function) {
|
||||
const enhancer = (enhancerGenerator ?? applyMiddleware)(
|
||||
this.middleware
|
||||
);
|
||||
|
||||
const store: {
|
||||
getState: Function & Record<string, Function>;
|
||||
dispatch: Function & Record<string, Function>;
|
||||
selectors: Record<string, Function>;
|
||||
actions: AggregateDuxActions<TActions, TSubduxes>;
|
||||
} = reduxCreateStore(
|
||||
this.reducer as any,
|
||||
initialState ?? this.initialState,
|
||||
enhancer
|
||||
) as any;
|
||||
|
||||
store.actions = this.actions;
|
||||
|
||||
store.selectors = this.selectors;
|
||||
|
||||
merge(
|
||||
store.getState,
|
||||
mapValues(this.selectors, (selector) => {
|
||||
return (...args) => {
|
||||
let result = selector(store.getState());
|
||||
|
||||
if (typeof result === 'function') return result(...args);
|
||||
|
||||
return result;
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
for (const action in this.actions) {
|
||||
store.dispatch[action] = (...args) => {
|
||||
return store.dispatch(this.actions[action](...(args as any)));
|
||||
};
|
||||
}
|
||||
|
||||
this.subscribeAll(store);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
effectToMiddleware(effect) {
|
||||
return effectToMiddleware(effect, this.actions, this.selectors);
|
||||
}
|
||||
}
|
@ -35,7 +35,7 @@ export class Updux {
|
||||
|
||||
this.#middlewareWrapper = config.middlewareWrapper;
|
||||
|
||||
this.#localInitial = config.initial;
|
||||
this.#localInitial = config.initialState;
|
||||
this.#subduxes = config.subduxes ?? {};
|
||||
|
||||
this.#actions = R.mapValues(config.actions ?? {}, (arg, name) =>
|
||||
@ -89,7 +89,7 @@ export class Updux {
|
||||
return this.#actions;
|
||||
}
|
||||
|
||||
get initial() {
|
||||
get initialState() {
|
||||
if (Object.keys(this.#subduxes).length === 0)
|
||||
return this.#localInitial ?? {};
|
||||
|
||||
@ -101,7 +101,7 @@ export class Updux {
|
||||
return Object.assign(
|
||||
{},
|
||||
this.#localInitial ?? {},
|
||||
R.mapValues(this.#subduxes, ({ initial }) => initial),
|
||||
R.mapValues(this.#subduxes, ({ initialState }) => initialState),
|
||||
);
|
||||
}
|
||||
|
||||
@ -167,14 +167,14 @@ export class Updux {
|
||||
);
|
||||
}
|
||||
|
||||
createStore(initial = undefined, enhancerGenerator = undefined) {
|
||||
createStore(initialState = undefined, enhancerGenerator = undefined) {
|
||||
const enhancer = (enhancerGenerator ?? applyMiddleware)(
|
||||
this.middleware,
|
||||
);
|
||||
|
||||
const store = reduxCreateStore(
|
||||
this.reducer,
|
||||
initial ?? this.initial,
|
||||
initialState ?? this.initialState,
|
||||
enhancer,
|
||||
);
|
||||
|
||||
@ -247,13 +247,16 @@ export class Updux {
|
||||
return (state, previousState, unsubscribe) => {
|
||||
const gone = { ...cache };
|
||||
|
||||
// TODO assuming object here
|
||||
for (const key in state) {
|
||||
const mappedState = Array.isArray(state)? Object.fromEntries(
|
||||
state.map( s => [ mapper(s), s ] )
|
||||
) : state;
|
||||
|
||||
for (const key in mappedState) {
|
||||
if (cache[key]) {
|
||||
delete gone[key];
|
||||
} else {
|
||||
const dux = new Updux({
|
||||
initial: null,
|
||||
initialState: null,
|
||||
actions: { update: null },
|
||||
mutations: {
|
||||
update: (payload) => () => payload,
|
374
src/Updux.ts
Normal file
@ -0,0 +1,374 @@
|
||||
import * as rtk from '@reduxjs/toolkit';
|
||||
const {
|
||||
configureStore,
|
||||
} = rtk;
|
||||
import { Action, AnyAction, EnhancedStore, PayloadAction } from '@reduxjs/toolkit';
|
||||
import {
|
||||
AugmentedMiddlewareAPI,
|
||||
DuxActions,
|
||||
DuxConfig,
|
||||
DuxReaction,
|
||||
DuxSelectors,
|
||||
DuxState,
|
||||
Mutation,
|
||||
MutationEntry,
|
||||
} from './types.js';
|
||||
import baseMoize from 'moize/mjs/index.mjs';
|
||||
import { buildInitialState } from './initialState.js';
|
||||
import { buildActions } from './actions.js';
|
||||
import { D } from '@mobily/ts-belt';
|
||||
import { buildReducer } from './reducer.js';
|
||||
import { buildSelectors } from './selectors.js';
|
||||
import { buildEffectsMiddleware, buildEffects, EffectMiddleware, augmentMiddlewareApi } from './effects.js';
|
||||
import { augmentGetState, augmentDispatch } from './createStore.js';
|
||||
import { buildReactions } from './reactions.js';
|
||||
|
||||
type CreateStoreOptions<D> = Partial<{
|
||||
preloadedState: DuxState<D>;
|
||||
}>;
|
||||
|
||||
interface ActionCreator<T extends string, P, A extends Array<any>> {
|
||||
type: T;
|
||||
match: (
|
||||
action: Action<string>,
|
||||
) => action is PayloadAction<P, T, never, never>;
|
||||
(...args: A): PayloadAction<P, T, never, never>;
|
||||
}
|
||||
|
||||
const moize = (func) => baseMoize(func, { maxSize: 1 });
|
||||
|
||||
|
||||
/**
|
||||
* @description main Updux class
|
||||
*/
|
||||
export default class Updux<D extends DuxConfig> {
|
||||
/** @internal */
|
||||
#subduxes: {};
|
||||
|
||||
/** @internal */
|
||||
#memoInitialState = moize(buildInitialState);
|
||||
|
||||
/** @internal */
|
||||
#memoBuildReducer = moize(buildReducer);
|
||||
|
||||
/** @internal */
|
||||
#memoBuildSelectors = moize(buildSelectors);
|
||||
|
||||
/** @internal */
|
||||
#memoBuildEffects = moize(buildEffects);
|
||||
|
||||
/** @internal */
|
||||
#memoBuildReactions = moize(buildReactions);
|
||||
|
||||
/** @internal */
|
||||
#actions: Record<string, ActionCreator<string, any, any>> = {};
|
||||
|
||||
/** @internal */
|
||||
#defaultMutation: {
|
||||
terminal: boolean;
|
||||
mutation: Mutation<any, DuxState<D>>;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
#effects: EffectMiddleware<D>[] = [];
|
||||
|
||||
/** @internal */
|
||||
#reactions: DuxReaction<D>[] = [];
|
||||
|
||||
/** @internal */
|
||||
#mutations: any[] = [];
|
||||
|
||||
/** @internal */
|
||||
#inheritedReducer: (state: DuxState<D> | undefined, action: AnyAction) => DuxState<D>;
|
||||
|
||||
/** @internal */
|
||||
#selectors = {};
|
||||
|
||||
constructor(private readonly duxConfig: D) {
|
||||
if (duxConfig.subduxes)
|
||||
this.#subduxes = D.map(duxConfig.subduxes, (s) =>
|
||||
s instanceof Updux ? s : new Updux(s),
|
||||
);
|
||||
|
||||
this.#inheritedReducer = duxConfig.reducer;
|
||||
|
||||
this.#effects = duxConfig.effects ?? [];
|
||||
|
||||
this.#reactions = (duxConfig.reactions as any) ?? [];
|
||||
|
||||
this.#actions = buildActions(duxConfig.actions, this.#subduxes) as any;
|
||||
|
||||
this.#selectors = duxConfig.selectors ?? {};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description Initial state of the dux.
|
||||
*/
|
||||
get initialState(): DuxState<D> {
|
||||
return this.#memoInitialState(
|
||||
this.duxConfig.initialState,
|
||||
this.#subduxes,
|
||||
);
|
||||
}
|
||||
|
||||
get subduxes() {
|
||||
return this.#subduxes;
|
||||
}
|
||||
|
||||
|
||||
get actions(): DuxActions<D> {
|
||||
return this.#actions as any;
|
||||
}
|
||||
|
||||
createStore(
|
||||
options: CreateStoreOptions<D> = {},
|
||||
): EnhancedStore<DuxState<D>> & AugmentedMiddlewareAPI<D> {
|
||||
const preloadedState = options.preloadedState;
|
||||
|
||||
const effects = buildEffectsMiddleware(
|
||||
this.effects,
|
||||
this.actions,
|
||||
this.selectors,
|
||||
);
|
||||
|
||||
let middleware = (gdm) => gdm().concat(effects);
|
||||
|
||||
const store: any = configureStore({
|
||||
preloadedState,
|
||||
reducer: this.reducer,
|
||||
middleware,
|
||||
});
|
||||
|
||||
store.dispatch = augmentDispatch(store.dispatch, this.actions);
|
||||
|
||||
store.getState = augmentGetState(store.getState, this.selectors);
|
||||
|
||||
store.actions = this.actions;
|
||||
store.selectors = this.selectors;
|
||||
|
||||
for (const reaction of this.reactions) {
|
||||
let unsub;
|
||||
const r = reaction(store);
|
||||
|
||||
unsub = store.subscribe(() => r(unsub));
|
||||
}
|
||||
|
||||
|
||||
return store as any;
|
||||
}
|
||||
|
||||
addAction(action: ActionCreator<any, any, any>) {
|
||||
const { type } = action as any;
|
||||
if (!this.#actions[type]) {
|
||||
this.#actions = {
|
||||
...this.#actions,
|
||||
[type]: action,
|
||||
};
|
||||
return;
|
||||
}
|
||||
if (this.#actions[type] !== action)
|
||||
throw new Error(`redefining action ${type}`);
|
||||
}
|
||||
|
||||
addSelector<R, N extends string>(name: N, selector: (state: DuxState<D>) => R) {
|
||||
this.#selectors = {
|
||||
...this.#selectors,
|
||||
[name]: selector,
|
||||
}
|
||||
|
||||
return this as any as Updux<D & {
|
||||
selectors: Record<N, (state: DuxState<D>) => R>
|
||||
}>;
|
||||
}
|
||||
|
||||
addMutation<A extends keyof DuxActions<D>>(
|
||||
actionType: A,
|
||||
mutation: Mutation<
|
||||
DuxActions<D>[A] extends (...args: any[]) => infer R ? R : AnyAction,
|
||||
DuxState<D>
|
||||
>,
|
||||
terminal?: boolean,
|
||||
): Updux<D>;
|
||||
addMutation<AC extends rtk.ActionCreatorWithPreparedPayload<any, any, string, never, never>>(
|
||||
actionCreator: AC,
|
||||
mutation: Mutation<ReturnType<AC>, DuxState<D>>,
|
||||
terminal?: boolean
|
||||
): Updux<D & {
|
||||
actions: Record<
|
||||
AC extends { type: infer T } ? T : never
|
||||
, AC
|
||||
>
|
||||
}
|
||||
>;
|
||||
addMutation<A extends string, P, X extends Array<any>>(
|
||||
actionCreator: ActionCreator<A, P, X>,
|
||||
mutation: Mutation<ReturnType<ActionCreator<A, P, X>>, DuxState<D>>,
|
||||
terminal?: boolean,
|
||||
): Updux<D & {
|
||||
actions: Record<A, ActionCreator<A, P, X>>;
|
||||
}
|
||||
>;
|
||||
addMutation(
|
||||
matcher: (action: AnyAction) => boolean,
|
||||
mutation: Mutation<AnyAction, DuxState<D>>,
|
||||
terminal?: boolean,
|
||||
): Updux<D>;
|
||||
addMutation(actionCreator, mutation, terminal = false) {
|
||||
|
||||
if (typeof actionCreator === 'string') {
|
||||
if (!this.actions[actionCreator]) throw new Error(`action ${actionCreator} not found`);
|
||||
actionCreator = this.actions[actionCreator];
|
||||
}
|
||||
else {
|
||||
this.addAction(actionCreator);
|
||||
}
|
||||
|
||||
this.#mutations = this.#mutations.concat({
|
||||
terminal,
|
||||
matcher: (actionCreator as any).match ?? actionCreator,
|
||||
mutation,
|
||||
});
|
||||
|
||||
return this as any;
|
||||
}
|
||||
|
||||
|
||||
setDefaultMutation(
|
||||
mutation: Mutation<any, DuxState<D>>,
|
||||
terminal?: boolean,
|
||||
): Updux<D>;
|
||||
setDefaultMutation(mutation, terminal = false) {
|
||||
this.#defaultMutation = { terminal, mutation };
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
get reducer() {
|
||||
return this.#memoBuildReducer(
|
||||
this.initialState,
|
||||
this.#mutations,
|
||||
this.#defaultMutation,
|
||||
this.#subduxes,
|
||||
this.#inheritedReducer,
|
||||
);
|
||||
}
|
||||
|
||||
get selectors(): DuxSelectors<D> {
|
||||
return this.#memoBuildSelectors(
|
||||
this.#selectors,
|
||||
this.#subduxes,
|
||||
) as any;
|
||||
}
|
||||
|
||||
addEffect<A extends keyof DuxActions<D>>(
|
||||
actionType: A,
|
||||
effect: EffectMiddleware<D,
|
||||
DuxActions<D>[A] extends (...args: any[]) => infer R ? R : AnyAction
|
||||
>,
|
||||
): Updux<D>;
|
||||
addEffect<AC extends rtk.ActionCreatorWithPreparedPayload<any, any, string, never, never>>(
|
||||
actionCreator: AC,
|
||||
effect: EffectMiddleware<D, ReturnType<AC>>,
|
||||
): Updux<D>;
|
||||
addEffect(
|
||||
guardFunc: (action: AnyAction) => boolean,
|
||||
effect: EffectMiddleware<D>,
|
||||
): Updux<D>;
|
||||
addEffect(effect: EffectMiddleware<D>): Updux<D>;
|
||||
addEffect(...args) {
|
||||
let effect;
|
||||
if (args.length === 1) {
|
||||
effect = args[0];
|
||||
} else {
|
||||
let [actionCreator, originalEffect] = args;
|
||||
|
||||
if (typeof actionCreator === 'string') {
|
||||
if (this.actions[actionCreator]) {
|
||||
actionCreator = this.actions[actionCreator];
|
||||
}
|
||||
else {
|
||||
throw new Error(`action '${actionCreator}' is unknown`);
|
||||
}
|
||||
}
|
||||
|
||||
const test = actionCreator.hasOwnProperty('match')
|
||||
? actionCreator.match
|
||||
: actionCreator;
|
||||
|
||||
effect = (api) => (next) => {
|
||||
const e = originalEffect(api)(next);
|
||||
return (action) => {
|
||||
const func = test(action) ? e : next;
|
||||
return func(action);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
this.#effects = this.#effects.concat(effect);
|
||||
|
||||
return this as any;
|
||||
}
|
||||
|
||||
get effects(): any[] {
|
||||
return this.#memoBuildEffects(this.#effects, this.#subduxes);
|
||||
}
|
||||
|
||||
get upreducer(): (action: AnyAction) => (state?: DuxState<D>) => DuxState<D> {
|
||||
return (action: AnyAction) => (state?: DuxState<D>) => this.reducer(state, action);
|
||||
}
|
||||
|
||||
addReaction(
|
||||
reaction: DuxReaction<D>
|
||||
) {
|
||||
let previous: any;
|
||||
|
||||
const memoized = (api: any) => {
|
||||
api = augmentMiddlewareApi(api, this.actions, this.selectors);
|
||||
const r = reaction(api);
|
||||
|
||||
const rMemoized = (localState, unsub) => {
|
||||
let p = previous;
|
||||
previous = localState;
|
||||
r(localState, p, unsub);
|
||||
};
|
||||
|
||||
return (unsub: () => void) => rMemoized(api.getState(), unsub);
|
||||
};
|
||||
|
||||
this.#reactions =
|
||||
this.#reactions.concat(memoized as any);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
get reactions() {
|
||||
return this.#memoBuildReactions(this.#reactions, this.#subduxes);
|
||||
}
|
||||
|
||||
get defaultMutation() {
|
||||
return this.#defaultMutation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns an object holding the Updux reducer and all its
|
||||
* paraphernalia.
|
||||
*/
|
||||
get asDux(): {
|
||||
initialState: DuxState<D>,
|
||||
actions: DuxActions<D>,
|
||||
reducer: (state: DuxState<D>, action: AnyAction) => DuxState<D>,
|
||||
effects: EffectMiddleware<D>[],
|
||||
reactions: DuxReaction<D>[],
|
||||
} {
|
||||
return {
|
||||
initialState: this.initialState,
|
||||
actions: this.actions,
|
||||
effects: this.effects,
|
||||
reactions: this.reactions,
|
||||
reducer: this.reducer,
|
||||
} as any;
|
||||
}
|
||||
|
||||
|
||||
}
|
265
src/Updux.ts.2023-08-18
Normal file
@ -0,0 +1,265 @@
|
||||
import * as R from 'remeda';
|
||||
import {
|
||||
createStore as reduxCreateStore,
|
||||
applyMiddleware,
|
||||
DeepPartial,
|
||||
Action,
|
||||
MiddlewareAPI,
|
||||
AnyAction,
|
||||
Middleware,
|
||||
Dispatch,
|
||||
} from 'redux';
|
||||
import {
|
||||
configureStore,
|
||||
Reducer,
|
||||
ActionCreator,
|
||||
ActionCreatorWithoutPayload,
|
||||
ActionCreatorWithPreparedPayload,
|
||||
} from '@reduxjs/toolkit';
|
||||
import { AggregateActions, AggregateSelectors, Dux } from './types.js';
|
||||
import { buildActions } from './buildActions.js';
|
||||
import { buildInitial, AggregateState } from './initial.js';
|
||||
import { buildReducer, MutationCase } from './reducer.js';
|
||||
import {
|
||||
augmentGetState,
|
||||
augmentMiddlewareApi,
|
||||
buildEffectsMiddleware,
|
||||
EffectMiddleware,
|
||||
} from './effects.js';
|
||||
import { ToolkitStore } from '@reduxjs/toolkit/dist/configureStore.js';
|
||||
import { produce } from 'immer';
|
||||
|
||||
type MyActionCreator = { type: string } & ((...args: any) => any);
|
||||
|
||||
|
||||
type ResolveAction<
|
||||
ActionType extends string,
|
||||
ActionArg extends any,
|
||||
> = ActionArg extends MyActionCreator
|
||||
? ActionArg
|
||||
: ActionArg extends (...args: any) => any
|
||||
? ActionCreatorWithPreparedPayload<
|
||||
Parameters<ActionArg>,
|
||||
ReturnType<ActionArg>,
|
||||
ActionType
|
||||
>
|
||||
: ActionCreatorWithoutPayload<ActionType>;
|
||||
|
||||
type ResolveActions<
|
||||
A extends {
|
||||
[key: string]: any;
|
||||
},
|
||||
> = {
|
||||
[ActionType in keyof A]: ActionType extends string
|
||||
? ResolveAction<ActionType, A[ActionType]>
|
||||
: never;
|
||||
};
|
||||
|
||||
type Reaction<S = any, M extends MiddlewareAPI = MiddlewareAPI> = (
|
||||
api: M,
|
||||
) => (state: S, previousState: S, unsubscribe: () => void) => any;
|
||||
|
||||
|
||||
|
||||
type SelectorForState<S> = (state: S) => unknown;
|
||||
type SelectorsForState<S> = {
|
||||
[key: string]: SelectorForState<S>;
|
||||
};
|
||||
|
||||
export default class Updux<
|
||||
T_LocalState = Record<any, any>,
|
||||
T_LocalActions extends {
|
||||
[actionType: string]: any;
|
||||
} = {},
|
||||
T_Subduxes extends Record<string, Dux> = {},
|
||||
T_LocalSelectors extends SelectorsForState<
|
||||
AggregateState<T_LocalState, T_Subduxes>
|
||||
> = {},
|
||||
> {
|
||||
#localInitial: T_LocalState;
|
||||
#localActions: T_LocalActions;
|
||||
#localMutations: MutationCase[] = [];
|
||||
#defaultMutation: Omit<MutationCase, 'matcher'>;
|
||||
#subduxes: T_Subduxes;
|
||||
|
||||
#name: string;
|
||||
|
||||
#actions: AggregateActions<ResolveActions<T_LocalActions>, T_Subduxes>;
|
||||
|
||||
#initialState: AggregateState<T_LocalState, T_Subduxes>;
|
||||
|
||||
#localSelectors: Record<
|
||||
string,
|
||||
(state: AggregateState<T_LocalState, T_Subduxes>) => any
|
||||
>;
|
||||
#selectors: any;
|
||||
|
||||
#localEffects: Middleware[] = [];
|
||||
|
||||
constructor(
|
||||
config: Partial<{
|
||||
initialState: T_LocalState;
|
||||
actions: T_LocalActions;
|
||||
subduxes: T_Subduxes;
|
||||
selectors: T_LocalSelectors;
|
||||
}>,
|
||||
) {
|
||||
// TODO check that we can't alter the initialState after the fact
|
||||
this.#localInitial = config.initialState ?? ({} as T_LocalState);
|
||||
this.#localActions = config.actions ?? ({} as T_LocalActions);
|
||||
this.#subduxes = config.subduxes ?? ({} as T_Subduxes);
|
||||
|
||||
this.#actions = buildActions(this.#localActions, this.#subduxes);
|
||||
|
||||
this.#initialState = buildInitial(this.#localInitial, this.#subduxes);
|
||||
this.#localSelectors = config.selectors;
|
||||
|
||||
const basedSelectors = R.mergeAll(
|
||||
Object.entries(this.#subduxes)
|
||||
.filter(([slice, { selectors }]) => selectors)
|
||||
.map(([slice, { selectors }]) =>
|
||||
R.mapValues(selectors, (s) => (state = {}) => {
|
||||
return s(state?.[slice]);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
this.#selectors = R.merge(basedSelectors, this.#localSelectors);
|
||||
}
|
||||
|
||||
get actions() {
|
||||
return this.#actions;
|
||||
}
|
||||
|
||||
// TODO memoize?
|
||||
get initialState() {
|
||||
return this.#initialState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
createStore(
|
||||
options: Partial<{
|
||||
preloadedState: T_LocalState;
|
||||
}> = {},
|
||||
) {
|
||||
const preloadedState: any = options.preloadedState;
|
||||
|
||||
const effects = buildEffectsMiddleware(
|
||||
this.effects,
|
||||
this.actions,
|
||||
this.selectors,
|
||||
);
|
||||
|
||||
const store = configureStore({
|
||||
reducer: this.reducer as Reducer<
|
||||
AggregateState<T_LocalState, T_Subduxes>,
|
||||
AnyAction
|
||||
>,
|
||||
preloadedState,
|
||||
middleware: [effects],
|
||||
});
|
||||
|
||||
const dispatch: any = store.dispatch;
|
||||
for (const a in this.actions) {
|
||||
dispatch[a] = (...args) => {
|
||||
const action = (this.actions as any)[a](...args);
|
||||
dispatch(action);
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
store.getState = augmentGetState(store.getState, this.selectors);
|
||||
|
||||
for (const reaction of this.reactions) {
|
||||
let unsub;
|
||||
const r = reaction(store);
|
||||
|
||||
unsub = store.subscribe(() => r(unsub));
|
||||
}
|
||||
|
||||
(store as any).actions = this.actions;
|
||||
(store as any).selectors = this.selectors;
|
||||
|
||||
return store as ToolkitStore<AggregateState<T_LocalState, T_Subduxes>> &
|
||||
AugmentedMiddlewareAPI<
|
||||
AggregateState<T_LocalState, T_Subduxes>,
|
||||
AggregateActions<ResolveActions<T_LocalActions>, T_Subduxes>,
|
||||
AggregateSelectors<
|
||||
T_LocalSelectors,
|
||||
T_Subduxes,
|
||||
AggregateState<T_LocalState, T_Subduxes>
|
||||
>
|
||||
>;
|
||||
}
|
||||
|
||||
get selectors(): AggregateSelectors<
|
||||
T_LocalSelectors,
|
||||
T_Subduxes,
|
||||
AggregateState<T_LocalState, T_Subduxes>
|
||||
> {
|
||||
return this.#selectors as any;
|
||||
}
|
||||
|
||||
// TODO memoize this sucker
|
||||
get reducer() {
|
||||
return buildReducer(
|
||||
this.initialState,
|
||||
this.#localMutations,
|
||||
this.#defaultMutation,
|
||||
this.#subduxes,
|
||||
) as any as (
|
||||
state: undefined | typeof this.initialState,
|
||||
action: Action,
|
||||
) => typeof this.initialState;
|
||||
}
|
||||
|
||||
|
||||
addDefaultMutation(
|
||||
mutation: Mutation<
|
||||
Action<any>,
|
||||
AggregateState<T_LocalState, T_Subduxes>
|
||||
>,
|
||||
terminal = false,
|
||||
) {
|
||||
this.#defaultMutation = { mutation, terminal };
|
||||
}
|
||||
|
||||
|
||||
get effectsMiddleware() {
|
||||
return buildEffectsMiddleware(
|
||||
this.effects,
|
||||
this.actions,
|
||||
this.selectors,
|
||||
);
|
||||
}
|
||||
|
||||
#localReactions: any[] = [];
|
||||
|
||||
// internal method REMOVE
|
||||
subscribeTo(store, subscription) {
|
||||
const localStore = augmentMiddlewareApi(
|
||||
{
|
||||
...store,
|
||||
subscribe: (subscriber) => this.subscribeTo(store, subscriber), // TODO not sure
|
||||
},
|
||||
this.actions,
|
||||
this.selectors,
|
||||
);
|
||||
|
||||
const subscriber = subscription(localStore);
|
||||
|
||||
let previous;
|
||||
let unsub;
|
||||
|
||||
const memoSub = () => {
|
||||
const state = store.getState();
|
||||
if (state === previous) return;
|
||||
let p = previous;
|
||||
previous = state;
|
||||
subscriber(state, p, unsub);
|
||||
};
|
||||
|
||||
return store.subscribe(memoSub);
|
||||
}
|
||||
}
|
94
src/Updux.ts.2024-07-29
Normal file
@ -0,0 +1,94 @@
|
||||
import type { DuxConfig, DuxState } from './types.js';
|
||||
import u from '@yanick/updeep-remeda';
|
||||
import moize from 'moize/mjs/index.mjs';
|
||||
import * as R from 'remeda';
|
||||
import { expandAction, buildActions, DuxActions } from './actions.js';
|
||||
import {
|
||||
Action,
|
||||
ActionCreator,
|
||||
AnyAction,
|
||||
configureStore,
|
||||
EnhancedStore,
|
||||
} from '@reduxjs/toolkit';
|
||||
import { produce } from 'immer';
|
||||
import { buildReducer } from './reducer.js';
|
||||
import { buildInitialState } from './initialState.js';
|
||||
import { buildSelectors, DuxSelectors } from './selectors.js';
|
||||
import { AugmentedMiddlewareAPI, augmentGetState } from './createStore.js';
|
||||
import {
|
||||
augmentMiddlewareApi,
|
||||
buildEffects,
|
||||
buildEffectsMiddleware,
|
||||
EffectMiddleware,
|
||||
} from './effects.js';
|
||||
import buildSchema from './schema.js';
|
||||
import Ajv from 'ajv';
|
||||
|
||||
|
||||
|
||||
function buildValidateMiddleware(schema) {
|
||||
// @ts-ignore
|
||||
const ajv = new Ajv();
|
||||
const validate = ajv.compile(schema);
|
||||
return (api) => next => action => {
|
||||
next(action);
|
||||
const valid = validate(api.getState());
|
||||
|
||||
if (!valid) throw new Error("validation failed after action " + JSON.stringify(action) + "\n" + JSON.stringify(validate.errors));
|
||||
}
|
||||
}
|
||||
|
||||
export default class Updux<D extends DuxConfig> {
|
||||
#mutations = [];
|
||||
|
||||
get schema() {
|
||||
return this.memoBuildSchema(
|
||||
this.duxConfig.schema,
|
||||
this.initialState,
|
||||
this.duxConfig.subduxes,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
createStore(
|
||||
options: Partial<{
|
||||
preloadedState: DuxState<D>;
|
||||
validate: boolean;
|
||||
buildMiddleware: (middleware: any[]) => any
|
||||
}> = {},
|
||||
): EnhancedStore<DuxState<D>> & AugmentedMiddlewareAPI<D> {
|
||||
const preloadedState = options.preloadedState;
|
||||
|
||||
|
||||
if (options.validate) {
|
||||
middleware.unshift(
|
||||
buildValidateMiddleware(this.schema)
|
||||
)
|
||||
}
|
||||
|
||||
if (options.buildMiddleware)
|
||||
middleware = options.buildMiddleware(middleware);
|
||||
|
||||
const store = configureStore({
|
||||
reducer: this.reducer,
|
||||
preloadedState,
|
||||
middleware,
|
||||
});
|
||||
|
||||
|
||||
return store as any;
|
||||
|
||||
// return store as ToolkitStore<AggregateState<T_LocalState, T_Subduxes>> &
|
||||
// AugmentedMiddlewareAPI<
|
||||
// AggregateState<T_LocalState, T_Subduxes>,
|
||||
// AggregateActions<ResolveActions<T_LocalActions>, T_Subduxes>,
|
||||
// AggregateSelectors<
|
||||
// T_LocalSelectors,
|
||||
// T_Subduxes,
|
||||
// AggregateState<T_LocalState, T_Subduxes>
|
||||
// >
|
||||
// >;
|
||||
}
|
||||
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
export function isActionGen(action) {
|
||||
return typeof action === 'function' && action.type;
|
||||
}
|
||||
|
||||
export function action(type, payloadFunction, transformer) {
|
||||
let generator = function (...payloadArg) {
|
||||
const result = { type };
|
||||
|
||||
if (payloadFunction) {
|
||||
result.payload = payloadFunction(...payloadArg);
|
||||
} else {
|
||||
if (payloadArg[0] !== undefined) result.payload = payloadArg[0];
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
if (transformer) {
|
||||
const orig = generator;
|
||||
generator = (...args) => transformer(orig(...args), args);
|
||||
}
|
||||
|
||||
generator.type = type;
|
||||
|
||||
return generator;
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
import { test, expect } from 'vitest';
|
||||
|
||||
import { action } from './actions.js';
|
||||
|
||||
import { Updux } from './Updux.js';
|
||||
|
||||
test('basic action', () => {
|
||||
const foo = action('foo', (thing) => ({ thing }));
|
||||
|
||||
expect(foo('bar')).toEqual({
|
||||
type: 'foo',
|
||||
payload: {
|
||||
thing: 'bar',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Updux config accepts actions', () => {
|
||||
const foo = new Updux({
|
||||
actions: {
|
||||
one: action('one', (x) => ({ x })),
|
||||
two: action('two', (x) => x),
|
||||
},
|
||||
});
|
||||
|
||||
expect(Object.keys(foo.actions)).toHaveLength(2);
|
||||
|
||||
expect(foo.actions.one).toBeTypeOf('function');
|
||||
expect(foo.actions.one('potato')).toEqual({
|
||||
type: 'one',
|
||||
payload: {
|
||||
x: 'potato',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('subduxes actions', () => {
|
||||
const foo = new Updux({
|
||||
actions: {
|
||||
foo: null,
|
||||
},
|
||||
subduxes: {
|
||||
beta: {
|
||||
actions: {
|
||||
bar: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(foo.actions).toHaveProperty('foo');
|
||||
expect(foo.actions).toHaveProperty('bar');
|
||||
});
|
||||
|
||||
test('throw if double action', () => {
|
||||
expect(
|
||||
() =>
|
||||
new Updux({
|
||||
actions: {
|
||||
foo: action('foo'),
|
||||
},
|
||||
subduxes: {
|
||||
beta: {
|
||||
actions: {
|
||||
foo: action('foo'),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toThrow(/action 'foo' already defined/);
|
||||
});
|
||||
|
||||
test('action definition shortcut', () => {
|
||||
const foo = new Updux({
|
||||
actions: {
|
||||
foo: null,
|
||||
bar: (x) => ({ x }),
|
||||
},
|
||||
});
|
||||
|
||||
expect(foo.actions.foo('hello')).toEqual({ type: 'foo', payload: 'hello' });
|
||||
expect(foo.actions.bar('hello')).toEqual({
|
||||
type: 'bar',
|
||||
payload: { x: 'hello' },
|
||||
});
|
||||
});
|
124
src/actions.test.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import {
|
||||
ActionCreator,
|
||||
ActionCreatorWithPreparedPayload,
|
||||
} from '@reduxjs/toolkit';
|
||||
import { buildActions, createAction, withPayload } from './actions.js';
|
||||
import Updux from './index.js';
|
||||
|
||||
test('basic action', () => {
|
||||
const foo = createAction(
|
||||
'foo',
|
||||
withPayload((thing: string) => ({ thing })),
|
||||
);
|
||||
|
||||
expect(foo('bar')).toEqual({
|
||||
type: 'foo',
|
||||
payload: {
|
||||
thing: 'bar',
|
||||
},
|
||||
});
|
||||
|
||||
expectTypeOf(foo).parameters.toMatchTypeOf<[string]>();
|
||||
|
||||
expectTypeOf(foo).returns.toMatchTypeOf<{
|
||||
type: 'foo';
|
||||
payload: { thing: string };
|
||||
}>();
|
||||
});
|
||||
|
||||
test('withPayload, just the type', () => {
|
||||
const foo = createAction('foo', withPayload<string>());
|
||||
|
||||
expect(foo('bar')).toEqual({
|
||||
type: 'foo',
|
||||
payload: 'bar',
|
||||
});
|
||||
|
||||
expectTypeOf(foo).parameters.toMatchTypeOf<[string]>();
|
||||
|
||||
expectTypeOf(foo).returns.toMatchTypeOf<{
|
||||
type: 'foo';
|
||||
payload: string;
|
||||
}>();
|
||||
});
|
||||
|
||||
test('buildActions', () => {
|
||||
const actions = buildActions(
|
||||
{
|
||||
one: createAction('one'),
|
||||
two: (x: string) => x,
|
||||
withoutValue: null,
|
||||
},
|
||||
{ a: { actions: buildActions({ three: () => 3 }) } },
|
||||
);
|
||||
|
||||
expect(actions.one()).toEqual({
|
||||
type: 'one',
|
||||
});
|
||||
|
||||
expectTypeOf(actions.one()).toMatchTypeOf<{
|
||||
type: 'one';
|
||||
}>();
|
||||
|
||||
expect(actions.two('potato')).toEqual({ type: 'two', payload: 'potato' });
|
||||
|
||||
expectTypeOf(actions.two('potato')).toMatchTypeOf<{
|
||||
type: 'two';
|
||||
payload: string;
|
||||
}>();
|
||||
|
||||
expect(actions.three()).toEqual({ type: 'three', payload: 3 });
|
||||
|
||||
expectTypeOf(actions.three()).toMatchTypeOf<{
|
||||
type: 'three';
|
||||
payload: number;
|
||||
}>();
|
||||
|
||||
expect(actions.withoutValue()).toEqual({ type: 'withoutValue' });
|
||||
|
||||
expectTypeOf(actions.withoutValue()).toMatchTypeOf<{
|
||||
type: 'withoutValue';
|
||||
}>();
|
||||
});
|
||||
|
||||
describe('Updux interactions', () => {
|
||||
const dux = new Updux({
|
||||
initialState: { a: 3 },
|
||||
actions: {
|
||||
add: (x: number) => x,
|
||||
withNull: null,
|
||||
},
|
||||
subduxes: {
|
||||
subdux1: new Updux({
|
||||
actions: {
|
||||
fromSubdux: (x: string) => x,
|
||||
},
|
||||
}),
|
||||
subdux2: {
|
||||
actions: {
|
||||
ohmy: () => { },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
test('actions getter', () => {
|
||||
expect(dux.actions.add).toBeTruthy();
|
||||
});
|
||||
|
||||
expectTypeOf(dux.actions.withNull).toMatchTypeOf<
|
||||
ActionCreator<'withNull'>
|
||||
>();
|
||||
|
||||
expect(dux.actions.withNull()).toMatchObject({
|
||||
type: 'withNull',
|
||||
});
|
||||
|
||||
expectTypeOf(dux.actions).toMatchTypeOf<Record<string, any>>();
|
||||
expectTypeOf(dux.actions?.add).not.toEqualTypeOf<any>();
|
||||
|
||||
expect(dux.actions.fromSubdux('payload')).toEqual({
|
||||
type: 'fromSubdux',
|
||||
payload: 'payload',
|
||||
});
|
||||
});
|