Merge branch 'typedoc'

This commit is contained in:
Yanick Champoux 2024-02-26 14:30:50 -05:00
commit edb6716c9c
15 changed files with 936 additions and 87 deletions

View File

@ -65,6 +65,14 @@ tasks:
cmds: cmds:
- npx eslint {{.FILES | default "src/**" }} - npx eslint {{.FILES | default "src/**" }}
docs:serve:
cmds:
- mkdocs serve
typedoc:
cmds:
- typedoc
docsify-install: docsify-install:
cmds: cmds:
- cp -r node_modules/docsify/lib docs/docsify - cp -r node_modules/docsify/lib docs/docsify

1
docs/api/.nojekyll Normal file
View File

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

228
docs/api/README.md Normal file
View File

@ -0,0 +1,228 @@
updux / [Exports](modules.md)
# What's Updux?
So, I'm a fan of [Redux](https://redux.js.org). Two days ago I discovered
[rematch](https://rematch.github.io/rematch) alonside a few other frameworks built atop Redux.
It has a couple of pretty good ideas that removes some of the
boilerplate. Keeping mutations and asynchronous effects close to the
reducer definition? Nice. Automatically infering the
actions from the said mutations and effects? Genius!
But it also enforces a flat hierarchy of reducers -- where
is the fun in that? And I'm also having a strong love for
[Updeep](https://github.com/substantial/updeep), so I want reducer state updates to leverage the heck out of it.
All that to say, say hello to `Updux`. Heavily inspired by `rematch`, but twisted
to work with `updeep` and to fit my peculiar needs. It offers features such as
- Mimic the way VueX has mutations (reducer reactions to specific actions) and
effects (middleware reacting to actions that can be asynchronous and/or
have side-effects), so everything pertaining to a store are all defined
in the space place.
- Automatically gather all actions used by the updux's effects and mutations,
and makes then accessible as attributes to the `dispatch` object of the
store.
- Mutations have a signature that is friendly to Updux and Immer.
- Also, the mutation signature auto-unwrap the payload of the actions for you.
- TypeScript types.
Fair warning: this package is still very new, probably very buggy,
definitively very badly documented, and very subject to changes. Caveat
Maxima Emptor.
# Synopsis
```
import updux from 'updux';
import otherUpdux from './otherUpdux';
const {
initial,
reducer,
actions,
middleware,
createStore,
} = new Updux({
initial: {
counter: 0,
},
subduxes: {
otherUpdux,
},
mutations: {
inc: ( increment = 1 ) => u({counter: s => s + increment })
},
effects: {
'*' => api => next => action => {
console.log( "hey, look, an action zoomed by!", action );
next(action);
};
},
actions: {
customAction: ( someArg ) => ({
type: "custom",
payload: { someProp: someArg }
}),
},
});
const store = createStore();
store.dispatch.inc(3);
```
# Description
Full documentation can be [found here](https://yanick.github.io/updux/).
Right now the best way to understand the whole thing is to go
through the [tutorial](https://yanick.github.io/updux/#/tutorial)
## Exporting upduxes
If you are creating upduxes that will be used as subduxes
by other upduxes, or as
[ducks](https://github.com/erikras/ducks-modular-redux)-like containers, I
recommend that you export the Updux instance as the default export:
```
import Updux from 'updux';
const updux = new Updux({ ... });
export default updux;
```
Then you can use them as subduxes like this:
```
import Updux from 'updux';
import foo from './foo'; // foo is an Updux
import bar from './bar'; // bar is an Updux as well
const updux = new Updux({
subduxes: {
foo, bar
}
});
```
Or if you want to use it:
```
import updux from './myUpdux';
const {
reducer,
actions: { doTheThing },
createStore,
middleware,
} = updux;
```
## Mapping a mutation to all values of a state
Say you have a `todos` state that is an array of `todo` sub-states. It's easy
enough to have the main reducer maps away all items to the sub-reducer:
```
const todo = new Updux({
mutations: {
review: () => u({ reviewed: true}),
done: () => u({done: true}),
},
});
const todos = new Updux({ initial: [] });
todos.addMutation(
todo.actions.review,
(_,action) => state => state.map( todo.upreducer(action) )
);
todos.addMutation(
todo.actions.done,
(id,action) => u.map(u.if(u.is('id',id), todo.upreducer(action))),
);
```
But `updeep` can iterate through all the items of an array (or the values of
an object) via the special key `*`. So the todos updux above could also be
written:
```
const todo = new Updux({
mutations: {
review: () => u({ reviewed: true}),
done: () => u({done: true}),
},
});
const todos = new Updux({
subduxes: { '*': todo },
});
todos.addMutation(
todo.actions.done,
(id,action) => u.map(u.if(u.is('id',id), todo.upreducer(action))),
true
);
```
The advantages being that the actions/mutations/effects of the subdux will be
imported by the root updux as usual, and all actions that aren't being
overridden by a sink mutation will trickle down automatically.
## Usage with Immer
While Updux was created with Updeep in mind, it also plays very
well with [Immer](https://immerjs.github.io/immer/docs/introduction).
For example, taking this basic updux:
```
import Updux from 'updux';
const updux = new Updux({
initial: { counter: 0 },
mutations: {
add: (inc=1) => state => { counter: counter + inc }
}
});
```
Converting it to Immer would look like:
```
import Updux from 'updux';
import { produce } from 'Immer';
const updux = new Updux({
initial: { counter: 0 },
mutations: {
add: (inc=1) => produce( draft => draft.counter += inc ) }
}
});
```
But since typing `produce` over and over is no fun, `groomMutations`
can be used to wrap all mutations with it:
```
import Updux from 'updux';
import { produce } from 'Immer';
const updux = new Updux({
initial: { counter: 0 },
groomMutations: mutation => (...args) => produce( mutation(...args) ),
mutations: {
add: (inc=1) => draft => draft.counter += inc
}
});
```

435
docs/api/classes/default.md Normal file
View File

@ -0,0 +1,435 @@
[updux](../README.md) / [Exports](../modules.md) / default
# Class: default\<D\>
## Type parameters
| Name | Type |
| :------ | :------ |
| `D` | extends `DuxConfig` |
## Constructors
### constructor
**new default**\<`D`\>(`duxConfig`): [`default`](default.md)\<`D`\>
#### Type parameters
| Name | Type |
| :------ | :------ |
| `D` | extends `Partial`\<\{ `actions`: `Record`\<`string`, `any`\> ; `initialState`: `any` ; `reducer`: `unknown` ; `schema`: `Record`\<`string`, `any`\> ; `selectors`: `Record`\<`string`, `any`\> ; `subduxes`: `Record`\<`string`, Partial\<\{ initialState: any; schema: Record\<string, any\>; actions: Record\<string, any\>; subduxes: Record\<string, Partial\<...\>\>; reducer: unknown; selectors: Record\<string, any\>; }\>\> }\> |
#### Parameters
| Name | Type |
| :------ | :------ |
| `duxConfig` | `D` |
#### Returns
[`default`](default.md)\<`D`\>
## Properties
### #defaultMutation
`Private` **#defaultMutation**: `any`
___
### #effects
`Private` **#effects**: `any`[] = `[]`
___
### #mutations
`Private` **#mutations**: `any`[] = `[]`
___
### #reactions
`Private` **#reactions**: `any`[] = `[]`
___
### duxConfig
`Private` `Readonly` **duxConfig**: `D`
___
### memoBuildActions
**memoBuildActions**: `Moized`\<(`localActions`: {}, `subduxes`: {}) => `any`, `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<(`localActions`: {}, `subduxes`: {}) => `any`\> ; `onCacheChange`: `OnCacheOperation`\<(`localActions`: {}, `subduxes`: {}) => `any`\> ; `onCacheHit`: `OnCacheOperation`\<(`localActions`: {}, `subduxes`: {}) => `any`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheChange`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheHit`: `OnCacheOperation`\<`Moizeable`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & \{ `maxSize`: `number` = 1 }\>
___
### memoBuildEffects
**memoBuildEffects**: `Moized`\<(`localEffects`: `any`, `subduxes`: {}) => `any`[], `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<(`localEffects`: `any`, `subduxes`: {}) => `any`[]\> ; `onCacheChange`: `OnCacheOperation`\<(`localEffects`: `any`, `subduxes`: {}) => `any`[]\> ; `onCacheHit`: `OnCacheOperation`\<(`localEffects`: `any`, `subduxes`: {}) => `any`[]\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheChange`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheHit`: `OnCacheOperation`\<`Moizeable`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & \{ `maxSize`: `number` = 1 }\>
___
### memoBuildReducer
**memoBuildReducer**: `Moized`\<(`initialStateState`: `unknown`, `mutations`: `MutationCase`[], `defaultMutation?`: `Omit`\<`MutationCase`, ``"matcher"``\>, `subduxes`: `Record`\<`string`, `Partial`\<\{ `actions`: `Record`\<`string`, `any`\> ; `initialState`: `any` ; `reducer`: `unknown` ; `schema`: `Record`\<`string`, `any`\> ; `selectors`: `Record`\<`string`, `any`\> ; `subduxes`: Record\<string, Partial\<\{ initialState: any; schema: Record\<string, any\>; actions: Record\<string, any\>; subduxes: Record\<string, Partial\<...\>\>; reducer: unknown; selectors: Record\<...\>; }\>\> }\>\>) => (`state`: `unknown`, `action`: `Action`\<`any`\>) => `unknown`, `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<(`initialStateState`: `unknown`, `mutations`: `MutationCase`[], `defaultMutation?`: `Omit`\<`MutationCase`, ``"matcher"``\>, `subduxes`: `Record`\<`string`, `Partial`\<\{ `actions`: `Record`\<`string`, `any`\> ; `initialState`: `any` ; `reducer`: `unknown` ; `schema`: `Record`\<`string`, `any`\> ; `selectors`: `Record`\<`string`, `any`\> ; `subduxes`: Record\<string, Partial\<\{ initialState: any; schema: Record\<string, any\>; actions: Record\<string, any\>; subduxes: Record\<string, Partial\<...\>\>; reducer: unknown; selectors: Record\<...\>; }\>\> }\>\>) => (`state`: `unknown`, `action`: `Action`\<`any`\>) => `unknown`\> ; `onCacheChange`: `OnCacheOperation`\<(`initialStateState`: `unknown`, `mutations`: `MutationCase`[], `defaultMutation?`: `Omit`\<`MutationCase`, ``"matcher"``\>, `subduxes`: `Record`\<`string`, `Partial`\<\{ `actions`: `Record`\<`string`, `any`\> ; `initialState`: `any` ; `reducer`: `unknown` ; `schema`: `Record`\<`string`, `any`\> ; `selectors`: `Record`\<`string`, `any`\> ; `subduxes`: Record\<string, Partial\<\{ initialState: any; schema: Record\<string, any\>; actions: Record\<string, any\>; subduxes: Record\<string, Partial\<...\>\>; reducer: unknown; selectors: Record\<...\>; }\>\> }\>\>) => (`state`: `unknown`, `action`: `Action`\<`any`\>) => `unknown`\> ; `onCacheHit`: `OnCacheOperation`\<(`initialStateState`: `unknown`, `mutations`: `MutationCase`[], `defaultMutation?`: `Omit`\<`MutationCase`, ``"matcher"``\>, `subduxes`: `Record`\<`string`, `Partial`\<\{ `actions`: `Record`\<`string`, `any`\> ; `initialState`: `any` ; `reducer`: `unknown` ; `schema`: `Record`\<`string`, `any`\> ; `selectors`: `Record`\<`string`, `any`\> ; `subduxes`: Record\<string, Partial\<\{ initialState: any; schema: Record\<string, any\>; actions: Record\<string, any\>; subduxes: Record\<string, Partial\<...\>\>; reducer: unknown; selectors: Record\<...\>; }\>\> }\>\>) => (`state`: `unknown`, `action`: `Action`\<`any`\>) => `unknown`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheChange`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheHit`: `OnCacheOperation`\<`Moizeable`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & \{ `maxSize`: `number` = 1 }\>
___
### memoBuildSchema
**memoBuildSchema**: `Moized`\<(`schema`: `any`, `initialState`: `any`, `subduxes`: {}) => `any`, `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<(`schema`: `any`, `initialState`: `any`, `subduxes`: {}) => `any`\> ; `onCacheChange`: `OnCacheOperation`\<(`schema`: `any`, `initialState`: `any`, `subduxes`: {}) => `any`\> ; `onCacheHit`: `OnCacheOperation`\<(`schema`: `any`, `initialState`: `any`, `subduxes`: {}) => `any`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheChange`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheHit`: `OnCacheOperation`\<`Moizeable`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & \{ `maxSize`: `number` = 1 }\>
___
### memoBuildSelectors
**memoBuildSelectors**: `Moized`\<(`localSelectors`: {}, `subduxes`: {}) => `object`, `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<(`localSelectors`: {}, `subduxes`: {}) => `object`\> ; `onCacheChange`: `OnCacheOperation`\<(`localSelectors`: {}, `subduxes`: {}) => `object`\> ; `onCacheHit`: `OnCacheOperation`\<(`localSelectors`: {}, `subduxes`: {}) => `object`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheChange`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheHit`: `OnCacheOperation`\<`Moizeable`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & \{ `maxSize`: `number` = 1 }\>
___
### memoInitialState
**memoInitialState**: `Moized`\<(`localInitialState`: `any`, `subduxes`: `any`) => `any`, `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<(`localInitialState`: `any`, `subduxes`: `any`) => `any`\> ; `onCacheChange`: `OnCacheOperation`\<(`localInitialState`: `any`, `subduxes`: `any`) => `any`\> ; `onCacheHit`: `OnCacheOperation`\<(`localInitialState`: `any`, `subduxes`: `any`) => `any`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & `Partial`\<\{ `isDeepEqual`: `boolean` ; `isPromise`: `boolean` ; `isReact`: `boolean` ; `isSerialized`: `boolean` ; `isShallowEqual`: `boolean` ; `matchesArg`: `IsEqual` ; `matchesKey`: `IsMatchingKey` ; `maxAge`: `number` ; `maxArgs`: `number` ; `maxSize`: `number` ; `onCacheAdd`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheChange`: `OnCacheOperation`\<`Moizeable`\> ; `onCacheHit`: `OnCacheOperation`\<`Moizeable`\> ; `onExpire`: `OnExpire` ; `profileName`: `string` ; `serializer`: `Serialize` ; `transformArgs`: `TransformKey` ; `updateCacheForKey`: `UpdateCacheForKey` ; `updateExpire`: `boolean` }\> & \{ `maxSize`: `number` = 1 }\>
## Accessors
### actions
`get` **actions**(): `DuxActions`\<`D`\>
#### Returns
`DuxActions`\<`D`\>
___
### asDux
`get` **asDux**(): `Object`
#### Returns
`Object`
| Name | Type |
| :------ | :------ |
| `actions` | `DuxActions`\<`D`\> |
| `effects` | `any` |
| `initialState` | `DuxState`\<`D`\> |
| `reactions` | `any`[] |
| `reducer` | (`state`: `unknown`, `action`: `Action`\<`any`\>) => `unknown` |
| `schema` | `any` |
| `selectors` | `DuxSelectors`\<`D`\> |
| `upreducer` | (`action`: `any`) => (`state`: `any`) => `unknown` |
___
### effects
`get` **effects**(): `any`
#### Returns
`any`
___
### foo
`get` **foo**(): `DuxActions`\<`D`\>
#### Returns
`DuxActions`\<`D`\>
___
### initialState
`get` **initialState**(): `DuxState`\<`D`\>
#### Returns
`DuxState`\<`D`\>
___
### reactions
`get` **reactions**(): `any`[]
#### Returns
`any`[]
___
### reducer
`get` **reducer**(): (`state`: `unknown`, `action`: `Action`\<`any`\>) => `unknown`
#### Returns
`fn`
▸ (`state?`, `action`): `unknown`
##### Parameters
| Name | Type | Default value |
| :------ | :------ | :------ |
| `state` | `unknown` | `initialStateState` |
| `action` | `Action`\<`any`\> | `undefined` |
##### Returns
`unknown`
___
### schema
`get` **schema**(): `any`
#### Returns
`any`
___
### selectors
`get` **selectors**(): `DuxSelectors`\<`D`\>
#### Returns
`DuxSelectors`\<`D`\>
___
### upreducer
`get` **upreducer**(): (`action`: `any`) => (`state`: `any`) => `unknown`
#### Returns
`fn`
▸ (`action`): (`state`: `any`) => `unknown`
##### Parameters
| Name | Type |
| :------ | :------ |
| `action` | `any` |
##### Returns
`fn`
▸ (`state`): `unknown`
##### Parameters
| Name | Type |
| :------ | :------ |
| `state` | `any` |
##### Returns
`unknown`
## Methods
### addDefaultMutation
**addDefaultMutation**(`mutation`, `terminal?`): `any`
#### Parameters
| Name | Type |
| :------ | :------ |
| `mutation` | `Mutation`\<`any`, `DuxState`\<`D`\>\> |
| `terminal?` | `boolean` |
#### Returns
`any`
___
### addEffect
**addEffect**(`actionType`, `effect`): [`default`](default.md)\<`D`\>
#### Parameters
| Name | Type |
| :------ | :------ |
| `actionType` | keyof `D` extends \{ `actions`: `A` } ? \{ [key in string \| number \| symbol]: key extends string ? ExpandedAction\<A[key], key\> : never } : {} \| keyof `UnionToIntersection`\<`D` extends \{ `subduxes`: `S` } ? `DuxActions`\<`S`[keyof `S`]\> : {}\> |
| `effect` | `EffectMiddleware`\<`D`\> |
#### Returns
[`default`](default.md)\<`D`\>
**addEffect**(`actionCreator`, `effect`): [`default`](default.md)\<`D`\>
#### Parameters
| Name | Type |
| :------ | :------ |
| `actionCreator` | `Object` |
| `actionCreator.match` | (`action`: `any`) => `boolean` |
| `effect` | `EffectMiddleware`\<`D`\> |
#### Returns
[`default`](default.md)\<`D`\>
**addEffect**(`guardFunc`, `effect`): [`default`](default.md)\<`D`\>
#### Parameters
| Name | Type |
| :------ | :------ |
| `guardFunc` | (`action`: `AnyAction`) => `boolean` |
| `effect` | `EffectMiddleware`\<`D`\> |
#### Returns
[`default`](default.md)\<`D`\>
**addEffect**(`effect`): [`default`](default.md)\<`D`\>
#### Parameters
| Name | Type |
| :------ | :------ |
| `effect` | `EffectMiddleware`\<`D`\> |
#### Returns
[`default`](default.md)\<`D`\>
___
### addMutation
**addMutation**\<`A`\>(`matcher`, `mutation`, `terminal?`): [`default`](default.md)\<`D`\>
#### Type parameters
| Name | Type |
| :------ | :------ |
| `A` | extends `string` \| `number` \| `symbol` |
#### Parameters
| Name | Type |
| :------ | :------ |
| `matcher` | `A` |
| `mutation` | `Mutation`\<`DuxActions`\<`D`\>[`A`] extends (...`args`: `any`) => `P` ? `P` : `never`, `DuxState`\<`D`\>\> |
| `terminal?` | `boolean` |
#### Returns
[`default`](default.md)\<`D`\>
**addMutation**\<`A`\>(`matcher`, `mutation`, `terminal?`): [`default`](default.md)\<`D`\>
#### Type parameters
| Name | Type |
| :------ | :------ |
| `A` | extends `Action`\<`any`, `A`\> |
#### Parameters
| Name | Type |
| :------ | :------ |
| `matcher` | (`action`: `A`) => `boolean` |
| `mutation` | `Mutation`\<`A`, `DuxState`\<`D`\>\> |
| `terminal?` | `boolean` |
#### Returns
[`default`](default.md)\<`D`\>
**addMutation**\<`A`\>(`actionCreator`, `mutation`, `terminal?`): [`default`](default.md)\<`D`\>
#### Type parameters
| Name | Type |
| :------ | :------ |
| `A` | extends `ActionCreator`\<`any`, `any`[], `A`\> |
#### Parameters
| Name | Type |
| :------ | :------ |
| `actionCreator` | `A` |
| `mutation` | `Mutation`\<`ReturnType`\<`A`\>, `DuxState`\<`D`\>\> |
| `terminal?` | `boolean` |
#### Returns
[`default`](default.md)\<`D`\>
___
### addReaction
**addReaction**(`reaction`): `void`
#### Parameters
| Name | Type |
| :------ | :------ |
| `reaction` | `any` |
#### Returns
`void`
___
### createStore
**createStore**(`options?`): `ToolkitStore`\<`D` extends \{ `initialState`: `INITIAL_STATE` } ? `INITIAL_STATE` : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown` & {}, `AnyAction`, `Middlewares`\<`DuxState`\<`D`\>\>\> & `MiddlewareAPI`\<`Dispatch`\<`AnyAction`\>, `DuxState`\<`D`\>\> & \{ `actions`: `DuxActions`\<`D`\> ; `dispatch`: `DuxActions`\<`D`\> ; `getState`: `CurriedSelectors`\<`DuxSelectors`\<`D`\>\> ; `selectors`: `DuxSelectors`\<`D`\> }
#### Parameters
| Name | Type |
| :------ | :------ |
| `options` | `Partial`\<\{ `buildMiddleware`: (`middleware`: `any`[]) => `any` ; `preloadedState`: `DuxState`\<`D`\> ; `validate`: `boolean` }\> |
#### Returns
`ToolkitStore`\<`D` extends \{ `initialState`: `INITIAL_STATE` } ? `INITIAL_STATE` : {} & `D` extends \{ `subduxes`: `any` } ? `SubduxesState`\<`D`\> : `unknown` & {}, `AnyAction`, `Middlewares`\<`DuxState`\<`D`\>\>\> & `MiddlewareAPI`\<`Dispatch`\<`AnyAction`\>, `DuxState`\<`D`\>\> & \{ `actions`: `DuxActions`\<`D`\> ; `dispatch`: `DuxActions`\<`D`\> ; `getState`: `CurriedSelectors`\<`DuxSelectors`\<`D`\>\> ; `selectors`: `DuxSelectors`\<`D`\> }
___
### toDux
**toDux**(): `Object`
#### Returns
`Object`
| Name | Type |
| :------ | :------ |
| `actions` | `DuxActions`\<`D`\> |
| `effects` | `any` |
| `initialState` | `DuxState`\<`D`\> |
| `reactions` | `any`[] |
| `reducer` | (`state`: `unknown`, `action`: `Action`\<`any`\>) => `unknown` |
| `schema` | `any` |
| `selectors` | `DuxSelectors`\<`D`\> |
| `upreducer` | (`action`: `any`) => (`state`: `any`) => `unknown` |

129
docs/api/modules.md Normal file
View File

@ -0,0 +1,129 @@
[updux](README.md) / Exports
# updux
## Classes
- [default](classes/default.md)
## Functions
### createAction
**createAction**\<`P`, `T`\>(`type`): `PayloadActionCreator`\<`P`, `T`\>
A utility function to create an action creator for the given action type
string. The action creator accepts a single argument, which will be included
in the action object as a field called payload. The action creator function
will also have its toString() overridden so that it returns the action type,
allowing it to be used in reducer logic that is looking for that action type.
#### Type parameters
| Name | Type |
| :------ | :------ |
| `P` | `void` |
| `T` | extends `string` = `string` |
#### Parameters
| Name | Type | Description |
| :------ | :------ | :------ |
| `type` | `T` | The action type to use for created actions. |
#### Returns
`PayloadActionCreator`\<`P`, `T`\>
**createAction**\<`PA`, `T`\>(`type`, `prepareAction`): `PayloadActionCreator`\<`ReturnType`\<`PA`\>[``"payload"``], `T`, `PA`\>
A utility function to create an action creator for the given action type
string. The action creator accepts a single argument, which will be included
in the action object as a field called payload. The action creator function
will also have its toString() overridden so that it returns the action type,
allowing it to be used in reducer logic that is looking for that action type.
#### Type parameters
| Name | Type |
| :------ | :------ |
| `PA` | extends `PrepareAction`\<`any`\> |
| `T` | extends `string` = `string` |
#### Parameters
| Name | Type | Description |
| :------ | :------ | :------ |
| `type` | `T` | The action type to use for created actions. |
| `prepareAction` | `PA` | - |
#### Returns
`PayloadActionCreator`\<`ReturnType`\<`PA`\>[``"payload"``], `T`, `PA`\>
___
### withPayload
**withPayload**\<`P`\>(): (`input`: `P`) => \{ `payload`: `P` }
#### Type parameters
| Name |
| :------ |
| `P` |
#### Returns
`fn`
▸ (`input`): `Object`
##### Parameters
| Name | Type |
| :------ | :------ |
| `input` | `P` |
##### Returns
`Object`
| Name | Type |
| :------ | :------ |
| `payload` | `P` |
**withPayload**\<`P`, `A`\>(`prepare`): (...`input`: `A`) => \{ `payload`: `P` }
#### Type parameters
| Name | Type |
| :------ | :------ |
| `P` | `P` |
| `A` | extends `any`[] |
#### Parameters
| Name | Type |
| :------ | :------ |
| `prepare` | (...`args`: `A`) => `P` |
#### Returns
`fn`
▸ (`...input`): `Object`
##### Parameters
| Name | Type |
| :------ | :------ |
| `...input` | `A` |
##### Returns
`Object`
| Name | Type |
| :------ | :------ |
| `payload` | `P` |

View File

@ -1,4 +1,4 @@
/// [dux] /// --8<-- [start:dux]
import Updux from '../src/index.js'; import Updux from '../src/index.js';
import u from '@yanick/updeep-remeda'; import u from '@yanick/updeep-remeda';
@ -16,4 +16,4 @@ nextIdDux.addMutation('incNextId', () => id => id + 1)
export default nextIdDux.asDux; export default nextIdDux.asDux;
/// [dux] /// --8<-- [end:dux]

View File

@ -1,6 +1,6 @@
import { test, expect } from 'vitest'; import { test, expect } from 'vitest';
/// [tut1] /// --8<-- [start:tut1]
import Updux from 'updux'; import Updux from 'updux';
const todosDux = new Updux({ const todosDux = new Updux({
@ -10,13 +10,13 @@ const todosDux = new Updux({
} }
}); });
/// [tut1] /// ---8<-- [end:tut1]
/// [tut2] /// ---8<-- [start:tut2]
const store = todosDux.createStore(); const store = todosDux.createStore();
store.getState(); // { nextId: 1, todos: [] } store.getState(); // { nextId: 1, todos: [] }
/// [tut2] /// ---8<-- [end:tut2]
test("basic", () => { test("basic", () => {
expect(store.getState()).toEqual({ expect(store.getState()).toEqual({

View File

@ -1,6 +1,6 @@
import { test, expect } from 'vitest'; import { test, expect } from 'vitest';
/// [actions1] /// --8<-- [start:actions1]
import Updux, { createAction, withPayload } from 'updux'; import Updux, { createAction, withPayload } from 'updux';
type TodoId = number; type TodoId = number;
@ -18,12 +18,12 @@ const todosDux = new Updux({
todoDone, todoDone,
} }
}); });
/// [actions1] /// --8<-- [end:actions1]
/// [actions2] /// --8<-- [start:actions2]
todosDux.actions.addTodo('write tutorial'); todosDux.actions.addTodo('write tutorial');
// { type: 'addTodo', payload: 'write tutorial' } // { type: 'addTodo', payload: 'write tutorial' }
/// [actions2] /// --8<-- [end:actions2]
test("basic", () => { test("basic", () => {
expect(todosDux.actions.addTodo('write tutorial')).toEqual({ expect(todosDux.actions.addTodo('write tutorial')).toEqual({
@ -31,7 +31,7 @@ test("basic", () => {
}) })
}); });
/// [addMutation] /// --8<-- [start:addMutation]
todosDux.addMutation(addTodo, (description) => ({ todos, nextId }) => { todosDux.addMutation(addTodo, (description) => ({ todos, nextId }) => {
return { return {
todos: todos.concat({ description, id: nextId, done: false }), todos: todos.concat({ description, id: nextId, done: false }),
@ -55,7 +55,7 @@ const state = store.getState();
// ] // ]
// } // }
/// [addMutation] /// --8<-- [end:addMutation]
test("addMutation", () => { test("addMutation", () => {
expect(state).toEqual({ expect(state).toEqual({

View File

@ -2,7 +2,7 @@ import { test, expect } from 'vitest';
//process.env.UPDEEP_MODE = "dangerously_never_freeze"; //process.env.UPDEEP_MODE = "dangerously_never_freeze";
/// [effects-1] /// --8<-- [start:effects-1]
import u from '@yanick/updeep-remeda'; import u from '@yanick/updeep-remeda';
import * as R from 'remeda'; import * as R from 'remeda';
@ -53,7 +53,7 @@ todosDux.addEffect("addTodo", ({ getState, dispatch }) => next => action => {
const store = todosDux.createStore(); const store = todosDux.createStore();
store.dispatch.addTodo('write tutorial'); store.dispatch.addTodo('write tutorial');
/// [effects-1] /// --8<-- [end:effects-1]
test('basic', () => { test('basic', () => {
expect(store.getState()).toMatchObject({ expect(store.getState()).toMatchObject({

View File

@ -1,6 +1,6 @@
import { test, expect } from 'vitest'; import { test, expect } from 'vitest';
/// [mono] /// --8<-- [start:mono]
import Updux from '../src/index.js'; import Updux from '../src/index.js';
import u from '@yanick/updeep-remeda'; import u from '@yanick/updeep-remeda';
@ -51,7 +51,7 @@ todosDux.addEffect(
} }
); );
/// [mono] /// --8<-- [end:mono]
test('basic', () => { test('basic', () => {
const store = todosDux.createStore(); const store = todosDux.createStore();

View File

@ -1,6 +1,6 @@
import { test, expect } from 'vitest'; import { test, expect } from 'vitest';
/// [sel1] /// --8<-- [start:sel1]
import Updux from 'updux'; import Updux from 'updux';
const dux = new Updux({ const dux = new Updux({
@ -24,7 +24,7 @@ store.selectors.getById(state)(2); // = { id: 2, done: false }
store.getState.getDone(); // = [ { id: 1, done: true } ] store.getState.getDone(); // = [ { id: 1, done: true } ]
store.getState.getById(2); // = { id: 2, done: false } store.getState.getById(2); // = { id: 2, done: false }
/// [sel1] /// --8<-- [end:sel1]
test('selectors', () => { test('selectors', () => {
expect(dux.selectors.getDone(state)).toMatchObject([{ id: 1, done: true }]); expect(dux.selectors.getDone(state)).toMatchObject([{ id: 1, done: true }]);

View File

@ -16,20 +16,25 @@ plain JavaScript.
To begin with, let's define that has nothing but an initial state. To begin with, let's define that has nothing but an initial state.
[filename](tutorial-1.test.ts ':include :type=code :fragment=tut1') ```
--8<- "docs/tutorial-1.test.ts:tut1"
```
Congrats! You have written your first Updux object. It 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 doesn't do a lot, but you can already create a store out of it, and its
initial state will be automatically set: initial state will be automatically set:
[filename](tutorial-1.test.ts ':include :type=code :fragment=tut2') ```
--8<- "docs/tutorial-1.test.ts:tut2"
```
## Add actions ## Add actions
This is all good, but very static. Let's add some actions! This is all good, but very static. Let's add some actions!
```
[filename](tutorial-actions.test.ts ':include :type=code :fragment=actions1') --8<- "docs/tutorial-actions.test.ts:actions1"
```
### Accessing actions ### Accessing actions
@ -37,14 +42,18 @@ Once an action is defined, its creator is accessible via the `actions` accessor.
This is not yet terribly exciting, but it'll get more interesting once we begin using This is not yet terribly exciting, but it'll get more interesting once we begin using
subduxes. subduxes.
[filename](tutorial-actions.test.ts ':include :type=code :fragment=actions2') ```
--8<- "docs/tutorial-actions.test.ts:actions2"
```
### Adding a mutation ### Adding a mutation
Mutations are the reducing functions associated to actions. They Mutations are the reducing functions associated to actions. They
are defined via the `addMutation` method. are defined via the `addMutation` method.
[filename](tutorial-actions.test.ts ':include :type=code :fragment=addMutation') ```
--8<- "docs/tutorial-actions.test.ts:addMutation"
```
Note that in the mutation we take the liberty of changing the state directly. Note that in the mutation we take the liberty of changing the state directly.
@ -61,7 +70,9 @@ The `getState` and `dispatch` functions are augmented with the dux selectors,
and actions, respectively. The selectors and actions are also available and actions, respectively. The selectors and actions are also available
from the api object. from the api object.
[filename](tutorial-effects.test.ts ':include :type=code :fragment=effects-1') ```
--8<- "docs/tutorial-effects.test.ts:effects-1"
```
## Selectors ## Selectors
@ -70,7 +81,9 @@ The `getState` method of a dux store will be augmented
with its selectors, with the first call for the state already with its selectors, with the first call for the state already
curried for you. curried for you.
[filename](tutorial-selectors.test.ts ':include :type=code :fragment=sel1') ```
--8<- "docs/tutorial-selectors.test.ts:sel1"
```
## Subduxes ## Subduxes
@ -83,7 +96,9 @@ 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 the global state. This is better understood by working out an example, so
let's recap on the Todos dux we have so far: let's recap on the Todos dux we have so far:
[filename](tutorial-monolith.test.ts ':include :type=code :fragment=mono') ``` title="todos-monolith.ts"
--8<- "docs/tutorial-monolith.test.ts:mono"
```
This store has two main components: the `nextId`, and the `todos` collection. 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 The `todos` collection is itself composed of the individual `todo`s. Let's
@ -91,19 +106,27 @@ create upduxes for each of those.
### NextId dux ### NextId dux
[filename](nextId.ts ':include :type=code :fragment=dux') ``` js title="nextId.ts"
--8<- "docs/nextId.ts:dux"
```
### Todo updux ### Todo updux
[filename](todo.ts ':include :type=code') ``` title="todo.ts"
--8<- "docs/todo.ts"
```
### Todos updux ### Todos updux
[filename](todos.ts ':include :type=code') ``` title="todos.ts"
--8<- "docs/todos.ts"
```
### Main store ### Main store
[filename](todoList.ts ':include :type=code') ``` title="todoList.ts"
--8<- "docs/todoList.ts"
```
Tadah! We had to define the `addTodo` effect at the top level as it needs to 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` access the `getNextId` selector from `nextId` and the `addTodoWithId`

16
mkdocs.yml Normal file
View File

@ -0,0 +1,16 @@
site_name: Updux
theme:
name: material
markdown_extensions:
- pymdownx.snippets
- pymdownx.inlinehilite
- pymdownx.superfences
- pymdownx.highlight:
default_lang: js
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
nav:
- Home: index.md
- Tutorial: tutorial.md
- API: api/modules.md

View File

@ -1,59 +1,61 @@
{ {
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@yanick/updeep-remeda": "^2.2.0", "@yanick/updeep-remeda": "^2.2.0",
"ajv": "^8.12.0", "ajv": "^8.12.0",
"expect-type": "^0.16.0", "expect-type": "^0.16.0",
"immer": "^9.0.15", "immer": "^9.0.15",
"json-schema-shorthand": "^2.0.0", "json-schema-shorthand": "^2.0.0",
"json-schema-to-ts": "^2.9.2", "json-schema-to-ts": "^2.9.2",
"memoize-one": "^6.0.0", "memoize-one": "^6.0.0",
"moize": "^6.1.6", "moize": "^6.1.6",
"redux": "^4.2.0", "redux": "^4.2.0",
"remeda": "^1.0.1", "remeda": "^1.0.1",
"updeep": "^1.2.1" "updeep": "^1.2.1"
}, },
"license": "MIT", "license": "MIT",
"module": "dist/index.js", "module": "dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"exports": { "exports": {
".": { ".": {
"import": "./dist/index.js" "import": "./dist/index.js"
}
},
"name": "updux",
"description": "Updeep-friendly Redux helper framework",
"scripts": {
"docsify:serve": "docsify serve docs"
},
"version": "5.1.0",
"repository": {
"type": "git",
"url": "git+https://github.com/yanick/updux.git"
},
"keywords": [
"redux",
"updeep"
],
"author": "Yanick Champoux <yanick@babyl.ca> (http://techblog.babyl.ca)",
"bugs": {
"url": "https://github.com/yanick/updux/issues"
},
"homepage": "https://github.com/yanick/updux#readme",
"devDependencies": {
"@reduxjs/toolkit": "==2.0.0-alpha.5 ",
"@vitest/browser": "^0.23.1",
"@vitest/ui": "^0.23.1",
"docsify": "^4.13.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",
"tsdoc-markdown": "^0.0.4",
"typedoc": "^0.25.9",
"typedoc-plugin-markdown": "^3.17.1",
"typescript": "^4.9.5",
"vite": "^4.2.1",
"vitest": "0.23.1"
} }
},
"name": "updux",
"description": "Updeep-friendly Redux helper framework",
"scripts": {
"docsify:serve": "docsify serve docs"
},
"version": "5.1.0",
"repository": {
"type": "git",
"url": "git+https://github.com/yanick/updux.git"
},
"keywords": [
"redux",
"updeep"
],
"author": "Yanick Champoux <yanick@babyl.ca> (http://techblog.babyl.ca)",
"bugs": {
"url": "https://github.com/yanick/updux/issues"
},
"homepage": "https://github.com/yanick/updux#readme",
"devDependencies": {
"@reduxjs/toolkit": "==2.0.0-alpha.5 ",
"@vitest/browser": "^0.23.1",
"@vitest/ui": "^0.23.1",
"docsify": "^4.13.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",
"tsdoc-markdown": "^0.0.4",
"typescript": "^4.9.5",
"vite": "^4.2.1",
"vitest": "0.23.1"
}
} }

7
typedoc.json Normal file
View File

@ -0,0 +1,7 @@
{
"entryPoints": "src/index.ts",
"out": "docs/api",
"hideInPageTOC": true,
"disableSources": true,
"plugin": [ "typedoc-plugin-markdown" ]
}