Compare commits

..

No commits in common. "dev-v4" and "typescript" have entirely different histories.

154 changed files with 3743 additions and 21010 deletions

5
.gitignore vendored
View File

@ -9,8 +9,3 @@ pnpm-debug.log
yarn-error.log
GPUCache/
updux-2.0.0.tgz
pnpm-lock.yaml
updux-5.0.0.tgz
.envrc
.task
releases

View File

@ -1,33 +1,18 @@
dist/**/*.test.js
docs
.eslint*
GPUCache/
node_modules/
.nyc_output/
**/*.orig
out/
package-lock.json
pnpm-debug.log
pnpm-lock.yaml
.pre-commit*
.prettier*
.prettierignore
src/
Taskfile.yaml
*.test.*
test.*
TODO
tools/gen_sidebar.pl
.travis.yml
tsconfig.json
docs
node_modules/
tsconfig.tsbuildinfo
updux-2.0.0.tgz
updux-*.tgz
vitest.config.js
yarn-error.log
**/*.orig
dist
package-lock.json
yarn.lock
docs*
.envrc
contrib/*
typedoc.json
dist/tutorial/*
.nyc_output/
pnpm-debug.log
pnpm-lock.yaml
yarn-error.log
GPUCache/
updux-2.0.0.tgz
.travis.yml
.prettierignore
tools/gen_sidebar.pl

View File

@ -2,12 +2,6 @@
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
View File

@ -1,9 +1,2 @@
* documentation action + mutation <<<
* createAction
* addMutation
addMutation with signature
.addMutation( actionCreator, (payload,action) => state => newState )
* then check that the mutation is in the new type
- documentation generator (mkdocs + jsdoc-to-markdown)
- createStore

View File

@ -1,58 +1,8 @@
# 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:
@ -94,21 +44,3 @@ 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

View File

@ -1,17 +0,0 @@
#!/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});

View File

@ -1,226 +0,0 @@
# 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
}
});
```

View File

@ -1,3 +0,0 @@
- [Home](/)
- [ Tutorial ](tutorial.md)
- [ Recipes ](recipes.md)

View File

@ -1 +0,0 @@
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.

View File

@ -1,228 +0,0 @@
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
}
});
```

View File

@ -1,435 +0,0 @@
[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` |

View File

@ -1,129 +0,0 @@
[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` |

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,55 +0,0 @@
(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);
}());

View File

@ -1 +0,0 @@
!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)}();

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,28 +0,0 @@
(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);
}());

View File

@ -1 +0,0 @@
!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)}();

View File

@ -1,505 +0,0 @@
(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);
}());

File diff suppressed because one or more lines are too long

View File

@ -1,43 +0,0 @@
(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);
}());

View File

@ -1 +0,0 @@
!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)}();

View File

@ -1,27 +0,0 @@
(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);
}());

View File

@ -1 +0,0 @@
$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);

View File

@ -1,42 +0,0 @@
(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);
}());

View File

@ -1 +0,0 @@
!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)}();

View File

@ -1,540 +0,0 @@
(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(/&quot;/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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
};
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);
}());

File diff suppressed because one or more lines are too long

View File

@ -1,668 +0,0 @@
(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);
}());

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,115 +0,0 @@
```
```
--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
View File

@ -1,21 +0,0 @@
# 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

View File

@ -1,4 +0,0 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

View File

@ -1,11 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

View File

@ -1,55 +1,226 @@
# Starlight Starter Kit: Basics
# What's Updux?
[![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build)
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
```
npm create astro@latest -- --template starlight
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);
```
[![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)
# Description
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
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)
## 🚀 Project Structure
## Exporting upduxes
Inside of your Astro + Starlight project, you'll see the following folders and files:
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:
```
.
├── public/
├── src/
│ ├── assets/
│ ├── content/
│ │ ├── docs/
│ │ └── config.ts
│ └── env.d.ts
├── astro.config.mjs
├── package.json
└── tsconfig.json
import Updux from 'updux';
const updux = new Updux({ ... });
export default updux;
```
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.
Then you can use them as subduxes like this:
Images can be added to `src/assets/` and embedded in Markdown with a relative link.
```
import Updux from 'updux';
import foo from './foo'; // foo is an Updux
import bar from './bar'; // bar is an Updux as well
Static assets, like favicons, can be placed in the `public/` directory.
const updux = new Updux({
subduxes: {
foo, bar
}
});
```
## 🧞 Commands
Or if you want to use it:
All commands are run from the root of the project, from a terminal:
```
import updux from './myUpdux';
| 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 |
const {
reducer,
actions: { doTheThing },
createStore,
middleware,
} = updux;
```
## 👀 Want to learn more?
## Mapping a mutation to all values of a state
Check out [Starlights docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat).
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/_sidebar.md Normal file
View File

@ -0,0 +1,3 @@
* [Home](/)
* [ Tutorial ](tutorial.md)
* [ Recipes ](recipes.md)

View File

@ -1,54 +0,0 @@
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' },
// },
],
}),
],
});

View 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="docsify/themes/vue.css">
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/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/docsify.js"></script>
<script src="docsify.js"></script>
</body>
</html>

View File

@ -1,19 +0,0 @@
{
"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"
}
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 583 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 583 KiB

View File

@ -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,8 +86,11 @@ 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
}
});
```

54
docs/recipes.test.js Normal file
View File

@ -0,0 +1,54 @@
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
]);
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

View File

@ -1,6 +0,0 @@
import { defineCollection } from 'astro:content';
import { docsSchema } from '@astrojs/starlight/schema';
export const collections = {
docs: defineCollection({ schema: docsSchema() }),
};

View File

@ -1 +0,0 @@
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.

View File

@ -1,153 +0,0 @@
---
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
}
});
```

View File

@ -1,355 +0,0 @@
---
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`

View File

@ -1,129 +0,0 @@
---
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` |

View File

@ -1,47 +0,0 @@
---
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/

View File

@ -1,32 +0,0 @@
---
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
});
```

View File

@ -1,43 +0,0 @@
---
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);
```

View File

@ -1,96 +0,0 @@
---
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') );
```

View File

@ -1,86 +0,0 @@
---
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 );
```

View File

@ -1,47 +0,0 @@
---
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 );
``````

View File

@ -1,28 +0,0 @@
---
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.

View File

@ -1,242 +0,0 @@
---
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
}
});
```

View File

@ -1,11 +0,0 @@
---
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

View File

@ -1,74 +0,0 @@
---
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" />

View File

@ -1 +0,0 @@
../../../../../src/tutorial

View File

@ -1,14 +0,0 @@
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");
}

View File

@ -1,20 +0,0 @@
---
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" />

View File

@ -1,23 +0,0 @@
---
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/

View File

@ -1,24 +0,0 @@
---
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
View File

@ -1,2 +0,0 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

View File

@ -1,10 +0,0 @@
{
"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/*"]
}
}
}

View File

@ -0,0 +1,69 @@
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' ]);
} )

View File

@ -0,0 +1,40 @@
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);
});

View File

@ -0,0 +1,27 @@
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);
} );

View File

@ -0,0 +1,91 @@
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 Normal file
View File

@ -0,0 +1,407 @@
# 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

26
docs/tutorial.test.js Normal file
View File

@ -0,0 +1,26 @@
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'
})
});

View File

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

View File

@ -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 */

View File

@ -1827,4 +1827,4 @@
<hkern u1="&#x201e;" u2="G" k="102" />
<hkern u1="&#x201e;" u2="C" k="102" />
</font>
</defs></svg>
</defs></svg>

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

View File

@ -1827,4 +1827,4 @@
<hkern u1="&#x201e;" u2="G" k="102" />
<hkern u1="&#x201e;" u2="C" k="102" />
</font>
</defs></svg>
</defs></svg>

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

View File

@ -1827,4 +1827,4 @@
<hkern u1="&#x201e;" u2="G" k="102" />
<hkern u1="&#x201e;" u2="C" k="102" />
</font>
</defs></svg>
</defs></svg>

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

View File

@ -1828,4 +1828,4 @@
<hkern u1="&#x201e;" u2="G" k="102" />
<hkern u1="&#x201e;" u2="C" k="102" />
</font>
</defs></svg>
</defs></svg>

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

View File

@ -1832,4 +1832,4 @@
<hkern g1="uniFB00" u2="&#x27;" k="-123" />
<hkern g1="uniFB00" u2="&#x22;" k="-123" />
</font>
</defs></svg>
</defs></svg>

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

View File

@ -1828,4 +1828,4 @@
<hkern u1="&#x201e;" u2="G" k="102" />
<hkern u1="&#x201e;" u2="C" k="102" />
</font>
</defs></svg>
</defs></svg>

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

View File

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

View File

@ -1,28 +1,20 @@
{
"version": "4.0.0-alpha.5",
"type": "module",
"dependencies": {
"@mobily/ts-belt": "4.0.0-rc.5",
"@yanick/updeep-remeda": "^2.3.1",
"immer": "^9.0.15",
"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",
"module": "dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js"
}
},
"main": "src/index.js",
"name": "updux",
"description": "Updeep-friendly Redux helper framework",
"scripts": {},
"scripts": {
"docsify:serve": "docsify serve docs"
},
"version": "4.0.0",
"repository": {
"type": "git",
"url": "git+https://github.com/yanick/updux.git"
@ -38,16 +30,12 @@
"homepage": "https://github.com/yanick/updux#readme",
"devDependencies": {
"@vitest/browser": "^0.23.1",
"@vitest/ui": "^2.0.5",
"@vitest/ui": "^0.23.1",
"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",
"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"
"vitest": "0.23.1"
}
}

2197
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -35,7 +35,7 @@ export class Updux {
this.#middlewareWrapper = config.middlewareWrapper;
this.#localInitial = config.initialState;
this.#localInitial = config.initial;
this.#subduxes = config.subduxes ?? {};
this.#actions = R.mapValues(config.actions ?? {}, (arg, name) =>
@ -89,7 +89,7 @@ export class Updux {
return this.#actions;
}
get initialState() {
get initial() {
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, ({ initialState }) => initialState),
R.mapValues(this.#subduxes, ({ initial }) => initial),
);
}
@ -167,14 +167,14 @@ export class Updux {
);
}
createStore(initialState = undefined, enhancerGenerator = undefined) {
createStore(initial = undefined, enhancerGenerator = undefined) {
const enhancer = (enhancerGenerator ?? applyMiddleware)(
this.middleware,
);
const store = reduxCreateStore(
this.reducer,
initialState ?? this.initialState,
initial ?? this.initial,
enhancer,
);
@ -247,16 +247,13 @@ export class Updux {
return (state, previousState, unsubscribe) => {
const gone = { ...cache };
const mappedState = Array.isArray(state)? Object.fromEntries(
state.map( s => [ mapper(s), s ] )
) : state;
for (const key in mappedState) {
// TODO assuming object here
for (const key in state) {
if (cache[key]) {
delete gone[key];
} else {
const dux = new Updux({
initialState: null,
initial: null,
actions: { update: null },
mutations: {
update: (payload) => () => payload,

View File

@ -1,457 +0,0 @@
/* 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);
}
}

View File

@ -1,374 +0,0 @@
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;
}
}

View File

@ -1,265 +0,0 @@
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);
}
}

View File

@ -1,94 +0,0 @@
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>
// >
// >;
}
}

26
src/actions.js Normal file
View File

@ -0,0 +1,26 @@
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;
}

86
src/actions.test.js Normal file
View File

@ -0,0 +1,86 @@
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' },
});
});

View File

@ -1,124 +0,0 @@
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',
});
});

Some files were not shown because too many files have changed in this diff Show More