going toward v2
This commit is contained in:
parent
e3c5aad399
commit
f0653442f3
32
.eslintrc.js
Normal file
32
.eslintrc.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// @format
|
||||||
|
|
||||||
|
// https://dev.to/robertcoopercode/using-eslint-and-prettier-in-a-typescript-project-53jb
|
||||||
|
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const ts_nope = [
|
||||||
|
'no-explicit-any',
|
||||||
|
'explicit-function-return-type',
|
||||||
|
'no-object-literal-type-assertion',
|
||||||
|
'camelcase',
|
||||||
|
'member-delimiter-style',
|
||||||
|
'prefer-interface',
|
||||||
|
'indent',
|
||||||
|
].map(r => '@typescript-eslint/' + r);
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
|
||||||
|
extends: [
|
||||||
|
'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
|
||||||
|
],
|
||||||
|
plugins: ['import'],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
|
||||||
|
sourceType: 'module', // Allows for the use of imports
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
..._.fromPairs(ts_nope.map(r => [r, 'off'])),
|
||||||
|
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
|
||||||
|
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
|
||||||
|
},
|
||||||
|
};
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -4,3 +4,8 @@ tsconfig.tsbuildinfo
|
|||||||
dist
|
dist
|
||||||
package-lock.json
|
package-lock.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
.nyc_output/
|
||||||
|
pnpm-debug.log
|
||||||
|
pnpm-lock.yaml
|
||||||
|
yarn-error.log
|
||||||
|
GPUCache/
|
||||||
|
32
Promake
Executable file
32
Promake
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const Promake = require('promake');
|
||||||
|
const glob = require('glob').sync;
|
||||||
|
|
||||||
|
const {
|
||||||
|
task, cli, rule, exec
|
||||||
|
} = new Promake();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const compile = task( 'compile', rule( 'tsconfig.tsbuildinfo', glob('src/**.ts'),
|
||||||
|
async () => {
|
||||||
|
return exec( 'tsc' );
|
||||||
|
}) );
|
||||||
|
|
||||||
|
const docs = task( 'docs', [ rule(
|
||||||
|
glob('docs/4-API/*.md'),
|
||||||
|
[ ...glob('./src/**.ts'), compile ], async() => {
|
||||||
|
await exec( "typedoc src" );
|
||||||
|
}
|
||||||
|
)]);
|
||||||
|
|
||||||
|
const sidebar = task(
|
||||||
|
'sidebar',
|
||||||
|
rule('./docs/_sidebar.md', [...glob('./docs/4-API/**.md'), docs], async () => {
|
||||||
|
return exec('docsify-auto-sidebar -d docs');
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
cli();
|
369
api-extractor.json
Normal file
369
api-extractor.json
Normal file
@ -0,0 +1,369 @@
|
|||||||
|
/**
|
||||||
|
* Config file for API Extractor. For more info, please visit: https://api-extractor.com
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optionally specifies another JSON config file that this file extends from. This provides a way for
|
||||||
|
* standard settings to be shared across multiple projects.
|
||||||
|
*
|
||||||
|
* If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains
|
||||||
|
* the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be
|
||||||
|
* resolved using NodeJS require().
|
||||||
|
*
|
||||||
|
* SUPPORTED TOKENS: none
|
||||||
|
* DEFAULT VALUE: ""
|
||||||
|
*/
|
||||||
|
// "extends": "./shared/api-extractor-base.json"
|
||||||
|
// "extends": "my-package/include/api-extractor-base.json"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the "<projectFolder>" token that can be used with other config file settings. The project folder
|
||||||
|
* typically contains the tsconfig.json and package.json config files, but the path is user-defined.
|
||||||
|
*
|
||||||
|
* The path is resolved relative to the folder of the config file that contains the setting.
|
||||||
|
*
|
||||||
|
* The default value for "projectFolder" is the token "<lookup>", which means the folder is determined by traversing
|
||||||
|
* parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder
|
||||||
|
* that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error
|
||||||
|
* will be reported.
|
||||||
|
*
|
||||||
|
* SUPPORTED TOKENS: <lookup>
|
||||||
|
* DEFAULT VALUE: "<lookup>"
|
||||||
|
*/
|
||||||
|
// "projectFolder": "..",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor
|
||||||
|
* analyzes the symbols exported by this module.
|
||||||
|
*
|
||||||
|
* The file extension must be ".d.ts" and not ".ts".
|
||||||
|
*
|
||||||
|
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||||
|
* prepend a folder token such as "<projectFolder>".
|
||||||
|
*
|
||||||
|
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||||
|
*/
|
||||||
|
"mainEntryPointFilePath": "./dist/index.d.ts",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of NPM package names whose exports should be treated as part of this package.
|
||||||
|
*
|
||||||
|
* For example, suppose that Webpack is used to generate a distributed bundle for the project "library1",
|
||||||
|
* and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part
|
||||||
|
* of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly
|
||||||
|
* imports library2. To avoid this, we can specify:
|
||||||
|
*
|
||||||
|
* "bundledPackages": [ "library2" ],
|
||||||
|
*
|
||||||
|
* This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been
|
||||||
|
* local files for library1.
|
||||||
|
*/
|
||||||
|
"bundledPackages": [ ],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines how the TypeScript compiler engine will be invoked by API Extractor.
|
||||||
|
*/
|
||||||
|
"compiler": {
|
||||||
|
/**
|
||||||
|
* Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project.
|
||||||
|
*
|
||||||
|
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||||
|
* prepend a folder token such as "<projectFolder>".
|
||||||
|
*
|
||||||
|
* Note: This setting will be ignored if "overrideTsconfig" is used.
|
||||||
|
*
|
||||||
|
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||||
|
* DEFAULT VALUE: "<projectFolder>/tsconfig.json"
|
||||||
|
*/
|
||||||
|
// "tsconfigFilePath": "<projectFolder>/tsconfig.json",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk.
|
||||||
|
* The object must conform to the TypeScript tsconfig schema:
|
||||||
|
*
|
||||||
|
* http://json.schemastore.org/tsconfig
|
||||||
|
*
|
||||||
|
* If omitted, then the tsconfig.json file will be read from the "projectFolder".
|
||||||
|
*
|
||||||
|
* DEFAULT VALUE: no overrideTsconfig section
|
||||||
|
*/
|
||||||
|
// "overrideTsconfig": {
|
||||||
|
// . . .
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended
|
||||||
|
* and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when
|
||||||
|
* dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses
|
||||||
|
* for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck.
|
||||||
|
*
|
||||||
|
* DEFAULT VALUE: false
|
||||||
|
*/
|
||||||
|
// "skipLibCheck": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures how the API report file (*.api.md) will be generated.
|
||||||
|
*/
|
||||||
|
"apiReport": {
|
||||||
|
/**
|
||||||
|
* (REQUIRED) Whether to generate an API report.
|
||||||
|
*/
|
||||||
|
"enabled": true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce
|
||||||
|
* a full file path.
|
||||||
|
*
|
||||||
|
* The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/".
|
||||||
|
*
|
||||||
|
* SUPPORTED TOKENS: <packageName>, <unscopedPackageName>
|
||||||
|
* DEFAULT VALUE: "<unscopedPackageName>.api.md"
|
||||||
|
*/
|
||||||
|
// "reportFileName": "<unscopedPackageName>.api.md",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the folder where the API report file is written. The file name portion is determined by
|
||||||
|
* the "reportFileName" setting.
|
||||||
|
*
|
||||||
|
* The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy,
|
||||||
|
* e.g. for an API review.
|
||||||
|
*
|
||||||
|
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||||
|
* prepend a folder token such as "<projectFolder>".
|
||||||
|
*
|
||||||
|
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||||
|
* DEFAULT VALUE: "<projectFolder>/etc/"
|
||||||
|
*/
|
||||||
|
"reportFolder": "<projectFolder>/temp/",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the folder where the temporary report file is written. The file name portion is determined by
|
||||||
|
* the "reportFileName" setting.
|
||||||
|
*
|
||||||
|
* After the temporary file is written to disk, it is compared with the file in the "reportFolder".
|
||||||
|
* If they are different, a production build will fail.
|
||||||
|
*
|
||||||
|
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||||
|
* prepend a folder token such as "<projectFolder>".
|
||||||
|
*
|
||||||
|
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||||
|
* DEFAULT VALUE: "<projectFolder>/temp/"
|
||||||
|
*/
|
||||||
|
"reportTempFolder": "<projectFolder>/temp/"
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures how the doc model file (*.api.json) will be generated.
|
||||||
|
*/
|
||||||
|
"docModel": {
|
||||||
|
/**
|
||||||
|
* (REQUIRED) Whether to generate a doc model file.
|
||||||
|
*/
|
||||||
|
"enabled": true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The output path for the doc model file. The file extension should be ".api.json".
|
||||||
|
*
|
||||||
|
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||||
|
* prepend a folder token such as "<projectFolder>".
|
||||||
|
*
|
||||||
|
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||||
|
* DEFAULT VALUE: "<projectFolder>/temp/<unscopedPackageName>.api.json"
|
||||||
|
*/
|
||||||
|
// "apiJsonFilePath": "<projectFolder>/temp/<unscopedPackageName>.api.json"
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures how the .d.ts rollup file will be generated.
|
||||||
|
*/
|
||||||
|
"dtsRollup": {
|
||||||
|
/**
|
||||||
|
* (REQUIRED) Whether to generate the .d.ts rollup file.
|
||||||
|
*/
|
||||||
|
"enabled": true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the output path for a .d.ts rollup file to be generated without any trimming.
|
||||||
|
* This file will include all declarations that are exported by the main entry point.
|
||||||
|
*
|
||||||
|
* If the path is an empty string, then this file will not be written.
|
||||||
|
*
|
||||||
|
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||||
|
* prepend a folder token such as "<projectFolder>".
|
||||||
|
*
|
||||||
|
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||||
|
* DEFAULT VALUE: "<projectFolder>/dist/<unscopedPackageName>.d.ts"
|
||||||
|
*/
|
||||||
|
// "untrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>.d.ts",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release.
|
||||||
|
* This file will include only declarations that are marked as "@public" or "@beta".
|
||||||
|
*
|
||||||
|
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||||
|
* prepend a folder token such as "<projectFolder>".
|
||||||
|
*
|
||||||
|
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||||
|
* DEFAULT VALUE: ""
|
||||||
|
*/
|
||||||
|
// "betaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-beta.d.ts",
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release.
|
||||||
|
* This file will include only declarations that are marked as "@public".
|
||||||
|
*
|
||||||
|
* If the path is an empty string, then this file will not be written.
|
||||||
|
*
|
||||||
|
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||||
|
* prepend a folder token such as "<projectFolder>".
|
||||||
|
*
|
||||||
|
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||||
|
* DEFAULT VALUE: ""
|
||||||
|
*/
|
||||||
|
// "publicTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-public.d.ts",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a declaration is trimmed, by default it will be replaced by a code comment such as
|
||||||
|
* "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the
|
||||||
|
* declaration completely.
|
||||||
|
*
|
||||||
|
* DEFAULT VALUE: false
|
||||||
|
*/
|
||||||
|
// "omitTrimmingComments": true
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures how the tsdoc-metadata.json file will be generated.
|
||||||
|
*/
|
||||||
|
"tsdocMetadata": {
|
||||||
|
/**
|
||||||
|
* Whether to generate the tsdoc-metadata.json file.
|
||||||
|
*
|
||||||
|
* DEFAULT VALUE: true
|
||||||
|
*/
|
||||||
|
// "enabled": true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies where the TSDoc metadata file should be written.
|
||||||
|
*
|
||||||
|
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||||
|
* prepend a folder token such as "<projectFolder>".
|
||||||
|
*
|
||||||
|
* The default value is "<lookup>", which causes the path to be automatically inferred from the "tsdocMetadata",
|
||||||
|
* "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup
|
||||||
|
* falls back to "tsdoc-metadata.json" in the package folder.
|
||||||
|
*
|
||||||
|
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||||
|
* DEFAULT VALUE: "<lookup>"
|
||||||
|
*/
|
||||||
|
// "tsdocMetadataFilePath": "<projectFolder>/dist/tsdoc-metadata.json"
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies what type of newlines API Extractor should use when writing output files. By default, the output files
|
||||||
|
* will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead.
|
||||||
|
* To use the OS's default newline kind, specify "os".
|
||||||
|
*
|
||||||
|
* DEFAULT VALUE: "crlf"
|
||||||
|
*/
|
||||||
|
// "newlineKind": "crlf",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures how API Extractor reports error and warning messages produced during analysis.
|
||||||
|
*
|
||||||
|
* There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages.
|
||||||
|
*/
|
||||||
|
"messages": {
|
||||||
|
/**
|
||||||
|
* Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing
|
||||||
|
* the input .d.ts files.
|
||||||
|
*
|
||||||
|
* TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551"
|
||||||
|
*
|
||||||
|
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
|
||||||
|
*/
|
||||||
|
"compilerMessageReporting": {
|
||||||
|
/**
|
||||||
|
* Configures the default routing for messages that don't match an explicit rule in this table.
|
||||||
|
*/
|
||||||
|
"default": {
|
||||||
|
/**
|
||||||
|
* Specifies whether the message should be written to the the tool's output log. Note that
|
||||||
|
* the "addToApiReportFile" property may supersede this option.
|
||||||
|
*
|
||||||
|
* Possible values: "error", "warning", "none"
|
||||||
|
*
|
||||||
|
* Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail
|
||||||
|
* and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes
|
||||||
|
* the "--local" option), the warning is displayed but the build will not fail.
|
||||||
|
*
|
||||||
|
* DEFAULT VALUE: "warning"
|
||||||
|
*/
|
||||||
|
"logLevel": "warning",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md),
|
||||||
|
* then the message will be written inside that file; otherwise, the message is instead logged according to
|
||||||
|
* the "logLevel" option.
|
||||||
|
*
|
||||||
|
* DEFAULT VALUE: false
|
||||||
|
*/
|
||||||
|
// "addToApiReportFile": false
|
||||||
|
},
|
||||||
|
|
||||||
|
// "TS2551": {
|
||||||
|
// "logLevel": "warning",
|
||||||
|
// "addToApiReportFile": true
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// . . .
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures handling of messages reported by API Extractor during its analysis.
|
||||||
|
*
|
||||||
|
* API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag"
|
||||||
|
*
|
||||||
|
* DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings
|
||||||
|
*/
|
||||||
|
"extractorMessageReporting": {
|
||||||
|
"default": {
|
||||||
|
"logLevel": "warning",
|
||||||
|
// "addToApiReportFile": false
|
||||||
|
},
|
||||||
|
|
||||||
|
// "ae-extra-release-tag": {
|
||||||
|
// "logLevel": "warning",
|
||||||
|
// "addToApiReportFile": true
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// . . .
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures handling of messages reported by the TSDoc parser when analyzing code comments.
|
||||||
|
*
|
||||||
|
* TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text"
|
||||||
|
*
|
||||||
|
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
|
||||||
|
*/
|
||||||
|
"tsdocMessageReporting": {
|
||||||
|
"default": {
|
||||||
|
"logLevel": "warning",
|
||||||
|
// "addToApiReportFile": false
|
||||||
|
}
|
||||||
|
|
||||||
|
// "tsdoc-link-tag-unescaped-text": {
|
||||||
|
// "logLevel": "warning",
|
||||||
|
// "addToApiReportFile": true
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// . . .
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
228
docs/API/README.md
Normal file
228
docs/API/README.md
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
[updux - v1.2.0](README.md) › [Globals](globals.md)
|
||||||
|
|
||||||
|
# updux - v1.2.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/docs/).
|
||||||
|
|
||||||
|
## 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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
491
docs/API/classes/updux.md
Normal file
491
docs/API/classes/updux.md
Normal file
@ -0,0 +1,491 @@
|
|||||||
|
[updux - v1.2.0](../README.md) › [Globals](../globals.md) › [Updux](updux.md)
|
||||||
|
|
||||||
|
# Class: Updux <**S, A, X, C**>
|
||||||
|
|
||||||
|
## Type parameters
|
||||||
|
|
||||||
|
▪ **S**
|
||||||
|
|
||||||
|
▪ **A**
|
||||||
|
|
||||||
|
▪ **X**
|
||||||
|
|
||||||
|
▪ **C**: *[UpduxConfig](../globals.md#upduxconfig)*
|
||||||
|
|
||||||
|
## Hierarchy
|
||||||
|
|
||||||
|
* **Updux**
|
||||||
|
|
||||||
|
## Index
|
||||||
|
|
||||||
|
### Constructors
|
||||||
|
|
||||||
|
* [constructor](updux.md#constructor)
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
* [coduxes](updux.md#coduxes)
|
||||||
|
* [groomMutations](updux.md#groommutations)
|
||||||
|
* [subduxes](updux.md#subduxes)
|
||||||
|
|
||||||
|
### Accessors
|
||||||
|
|
||||||
|
* [_middlewareEntries](updux.md#_middlewareentries)
|
||||||
|
* [actions](updux.md#actions)
|
||||||
|
* [asDux](updux.md#asdux)
|
||||||
|
* [createStore](updux.md#createstore)
|
||||||
|
* [initial](updux.md#initial)
|
||||||
|
* [middleware](updux.md#middleware)
|
||||||
|
* [mutations](updux.md#mutations)
|
||||||
|
* [reducer](updux.md#reducer)
|
||||||
|
* [selectors](updux.md#selectors)
|
||||||
|
* [subduxUpreducer](updux.md#subduxupreducer)
|
||||||
|
* [upreducer](updux.md#upreducer)
|
||||||
|
|
||||||
|
### Methods
|
||||||
|
|
||||||
|
* [addAction](updux.md#addaction)
|
||||||
|
* [addEffect](updux.md#addeffect)
|
||||||
|
* [addMutation](updux.md#addmutation)
|
||||||
|
* [addSelector](updux.md#addselector)
|
||||||
|
|
||||||
|
## Constructors
|
||||||
|
|
||||||
|
### constructor
|
||||||
|
|
||||||
|
\+ **new Updux**(`config`: C): *[Updux](updux.md)*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type | Default | Description |
|
||||||
|
------ | ------ | ------ | ------ |
|
||||||
|
`config` | C | {} as C | an [UpduxConfig](../globals.md#upduxconfig) plain object |
|
||||||
|
|
||||||
|
**Returns:** *[Updux](updux.md)*
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### coduxes
|
||||||
|
|
||||||
|
• **coduxes**: *[Dux](../globals.md#dux)[]*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### groomMutations
|
||||||
|
|
||||||
|
• **groomMutations**: *function*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
▸ (`mutation`: [Mutation](../globals.md#mutation)‹S›): *[Mutation](../globals.md#mutation)‹S›*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`mutation` | [Mutation](../globals.md#mutation)‹S› |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### subduxes
|
||||||
|
|
||||||
|
• **subduxes**: *[Dictionary](../globals.md#dictionary)‹[Dux](../globals.md#dux)›*
|
||||||
|
|
||||||
|
## Accessors
|
||||||
|
|
||||||
|
### _middlewareEntries
|
||||||
|
|
||||||
|
• **get _middlewareEntries**(): *any[]*
|
||||||
|
|
||||||
|
**Returns:** *any[]*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### actions
|
||||||
|
|
||||||
|
• **get actions**(): *[DuxActions](../globals.md#duxactions)‹A, C›*
|
||||||
|
|
||||||
|
Action creators for all actions defined or used in the actions, mutations, effects and subduxes
|
||||||
|
of the updux config.
|
||||||
|
|
||||||
|
Non-custom action creators defined in `actions` have the signature `(payload={},meta={}) => ({type,
|
||||||
|
payload,meta})` (with the extra sugar that if `meta` or `payload` are not
|
||||||
|
specified, that key won't be present in the produced action).
|
||||||
|
|
||||||
|
The same action creator can be included
|
||||||
|
in multiple subduxes. However, if two different creators
|
||||||
|
are included for the same action, an error will be thrown.
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
|
||||||
|
```
|
||||||
|
const actions = updux.actions;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** *[DuxActions](../globals.md#duxactions)‹A, C›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### asDux
|
||||||
|
|
||||||
|
• **get asDux**(): *object*
|
||||||
|
|
||||||
|
Returns a <a href="https://github.com/erikras/ducks-modular-redux">ducks</a>-like
|
||||||
|
plain object holding the reducer from the Updux object and all
|
||||||
|
its trimmings.
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
|
||||||
|
```
|
||||||
|
const {
|
||||||
|
createStore,
|
||||||
|
upreducer,
|
||||||
|
subduxes,
|
||||||
|
coduxes,
|
||||||
|
middleware,
|
||||||
|
actions,
|
||||||
|
reducer,
|
||||||
|
mutations,
|
||||||
|
initial,
|
||||||
|
selectors,
|
||||||
|
} = myUpdux.asDux;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** *object*
|
||||||
|
|
||||||
|
* **actions**: = this.actions
|
||||||
|
|
||||||
|
* **coduxes**: *object[]* = this.coduxes
|
||||||
|
|
||||||
|
* **createStore**(): *function*
|
||||||
|
|
||||||
|
* (`initial?`: S, `injectEnhancer?`: Function): *Store‹S› & object*
|
||||||
|
|
||||||
|
* **initial**: = this.initial
|
||||||
|
|
||||||
|
* **middleware**(): *function*
|
||||||
|
|
||||||
|
* (`api`: UpduxMiddlewareAPI‹S, X›): *function*
|
||||||
|
|
||||||
|
* (`next`: Function): *function*
|
||||||
|
|
||||||
|
* (`action`: A): *any*
|
||||||
|
|
||||||
|
* **mutations**(): *object*
|
||||||
|
|
||||||
|
* **reducer**(): *function*
|
||||||
|
|
||||||
|
* (`state`: S | undefined, `action`: [Action](../globals.md#action)): *S*
|
||||||
|
|
||||||
|
* **selectors**: = this.selectors
|
||||||
|
|
||||||
|
* **subduxes**(): *object*
|
||||||
|
|
||||||
|
* **upreducer**(): *function*
|
||||||
|
|
||||||
|
* (`action`: [Action](../globals.md#action)): *function*
|
||||||
|
|
||||||
|
* (`state`: S): *S*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### createStore
|
||||||
|
|
||||||
|
• **get createStore**(): *function*
|
||||||
|
|
||||||
|
Returns a `createStore` function that takes two argument:
|
||||||
|
`initial` and `injectEnhancer`. `initial` is a custom
|
||||||
|
initial state for the store, and `injectEnhancer` is a function
|
||||||
|
taking in the middleware built by the updux object and allowing
|
||||||
|
you to wrap it in any enhancer you want.
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
|
||||||
|
```
|
||||||
|
const createStore = updux.createStore;
|
||||||
|
|
||||||
|
const store = createStore(initial);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** *function*
|
||||||
|
|
||||||
|
▸ (`initial?`: S, `injectEnhancer?`: Function): *Store‹S› & object*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`initial?` | S |
|
||||||
|
`injectEnhancer?` | Function |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### initial
|
||||||
|
|
||||||
|
• **get initial**(): *AggDuxState‹S, C›*
|
||||||
|
|
||||||
|
**Returns:** *AggDuxState‹S, C›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### middleware
|
||||||
|
|
||||||
|
• **get middleware**(): *[UpduxMiddleware](../globals.md#upduxmiddleware)‹AggDuxState‹S, C›, [DuxSelectors](../globals.md#duxselectors)‹AggDuxState‹S, C›, X, C››*
|
||||||
|
|
||||||
|
Array of middlewares aggregating all the effects defined in the
|
||||||
|
updux and its subduxes. Effects of the updux itself are
|
||||||
|
done before the subduxes effects.
|
||||||
|
Note that `getState` will always return the state of the
|
||||||
|
local updux.
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
|
||||||
|
```
|
||||||
|
const middleware = updux.middleware;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** *[UpduxMiddleware](../globals.md#upduxmiddleware)‹AggDuxState‹S, C›, [DuxSelectors](../globals.md#duxselectors)‹AggDuxState‹S, C›, X, C››*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### mutations
|
||||||
|
|
||||||
|
• **get mutations**(): *[Dictionary](../globals.md#dictionary)‹[Mutation](../globals.md#mutation)‹S››*
|
||||||
|
|
||||||
|
Merge of the updux and subduxes mutations. If an action triggers
|
||||||
|
mutations in both the main updux and its subduxes, the subduxes
|
||||||
|
mutations will be performed first.
|
||||||
|
|
||||||
|
**Returns:** *[Dictionary](../globals.md#dictionary)‹[Mutation](../globals.md#mutation)‹S››*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### reducer
|
||||||
|
|
||||||
|
• **get reducer**(): *function*
|
||||||
|
|
||||||
|
A Redux reducer generated using the computed initial state and
|
||||||
|
mutations.
|
||||||
|
|
||||||
|
**Returns:** *function*
|
||||||
|
|
||||||
|
▸ (`state`: S | undefined, `action`: [Action](../globals.md#action)): *S*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`state` | S | undefined |
|
||||||
|
`action` | [Action](../globals.md#action) |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### selectors
|
||||||
|
|
||||||
|
• **get selectors**(): *[DuxSelectors](../globals.md#duxselectors)‹AggDuxState‹S, C›, X, C›*
|
||||||
|
|
||||||
|
A dictionary of the updux's selectors. Subduxes'
|
||||||
|
selectors are included as well (with the mapping to the
|
||||||
|
sub-state already taken care of you).
|
||||||
|
|
||||||
|
**Returns:** *[DuxSelectors](../globals.md#duxselectors)‹AggDuxState‹S, C›, X, C›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### subduxUpreducer
|
||||||
|
|
||||||
|
• **get subduxUpreducer**(): *function*
|
||||||
|
|
||||||
|
Returns the upreducer made of the merge of all sudbuxes reducers, without
|
||||||
|
the local mutations. Useful, for example, for sink mutations.
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
|
||||||
|
```
|
||||||
|
import todo from './todo'; // updux for a single todo
|
||||||
|
import Updux from 'updux';
|
||||||
|
import u from 'updeep';
|
||||||
|
|
||||||
|
const todos = new Updux({ initial: [], subduxes: { '*': todo } });
|
||||||
|
todos.addMutation(
|
||||||
|
todo.actions.done,
|
||||||
|
({todo_id},action) => u.map( u.if( u.is('id',todo_id) ), todos.subduxUpreducer(action) )
|
||||||
|
true
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** *function*
|
||||||
|
|
||||||
|
▸ (`action`: [Action](../globals.md#action)): *function*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`action` | [Action](../globals.md#action) |
|
||||||
|
|
||||||
|
▸ (`state`: S): *S*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`state` | S |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### upreducer
|
||||||
|
|
||||||
|
• **get upreducer**(): *[Upreducer](../globals.md#upreducer)‹S›*
|
||||||
|
|
||||||
|
**Returns:** *[Upreducer](../globals.md#upreducer)‹S›*
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### addAction
|
||||||
|
|
||||||
|
▸ **addAction**(`theaction`: string, `transform?`: any): *ActionCreator‹string, any›*
|
||||||
|
|
||||||
|
Adds an action to the updux. It can take an already defined action
|
||||||
|
creator, or any arguments that can be passed to `actionCreator`.
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
```
|
||||||
|
const action = updux.addAction( name, ...creatorArgs );
|
||||||
|
const action = updux.addAction( otherActionCreator );
|
||||||
|
```
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
```
|
||||||
|
import {actionCreator, Updux} from 'updux';
|
||||||
|
|
||||||
|
const updux = new Updux();
|
||||||
|
|
||||||
|
const foo = updux.addAction('foo');
|
||||||
|
const bar = updux.addAction( 'bar', (x) => ({stuff: x+1}) );
|
||||||
|
|
||||||
|
const baz = actionCreator( 'baz' );
|
||||||
|
|
||||||
|
foo({ a: 1}); // => { type: 'foo', payload: { a: 1 } }
|
||||||
|
bar(2); // => { type: 'bar', payload: { stuff: 3 } }
|
||||||
|
baz(); // => { type: 'baz', payload: undefined }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`theaction` | string |
|
||||||
|
`transform?` | any |
|
||||||
|
|
||||||
|
**Returns:** *ActionCreator‹string, any›*
|
||||||
|
|
||||||
|
▸ **addAction**(`theaction`: string | ActionCreator‹any›, `transform?`: undefined): *ActionCreator‹string, any›*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`theaction` | string | ActionCreator‹any› |
|
||||||
|
`transform?` | undefined |
|
||||||
|
|
||||||
|
**Returns:** *ActionCreator‹string, any›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### addEffect
|
||||||
|
|
||||||
|
▸ **addEffect**<**AC**>(`creator`: AC, `middleware`: [UpduxMiddleware](../globals.md#upduxmiddleware)‹AggDuxState‹S, C›, [DuxSelectors](../globals.md#duxselectors)‹AggDuxState‹S, C›, X, C›, ReturnType‹AC››, `isGenerator?`: undefined | false | true): *any*
|
||||||
|
|
||||||
|
**Type parameters:**
|
||||||
|
|
||||||
|
▪ **AC**: *ActionCreator*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`creator` | AC |
|
||||||
|
`middleware` | [UpduxMiddleware](../globals.md#upduxmiddleware)‹AggDuxState‹S, C›, [DuxSelectors](../globals.md#duxselectors)‹AggDuxState‹S, C›, X, C›, ReturnType‹AC›› |
|
||||||
|
`isGenerator?` | undefined | false | true |
|
||||||
|
|
||||||
|
**Returns:** *any*
|
||||||
|
|
||||||
|
▸ **addEffect**(`creator`: string, `middleware`: [UpduxMiddleware](../globals.md#upduxmiddleware)‹AggDuxState‹S, C›, [DuxSelectors](../globals.md#duxselectors)‹AggDuxState‹S, C›, X, C››, `isGenerator?`: undefined | false | true): *any*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`creator` | string |
|
||||||
|
`middleware` | [UpduxMiddleware](../globals.md#upduxmiddleware)‹AggDuxState‹S, C›, [DuxSelectors](../globals.md#duxselectors)‹AggDuxState‹S, C›, X, C›› |
|
||||||
|
`isGenerator?` | undefined | false | true |
|
||||||
|
|
||||||
|
**Returns:** *any*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### addMutation
|
||||||
|
|
||||||
|
▸ **addMutation**<**A**>(`creator`: A, `mutation`: [Mutation](../globals.md#mutation)‹S, ActionType‹A››, `isSink?`: undefined | false | true): *any*
|
||||||
|
|
||||||
|
Adds a mutation and its associated action to the updux.
|
||||||
|
|
||||||
|
**`remarks`**
|
||||||
|
|
||||||
|
If a local mutation was already associated to the action,
|
||||||
|
it will be replaced by the new one.
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
|
||||||
|
```js
|
||||||
|
updux.addMutation(
|
||||||
|
action('ADD', payload<int>() ),
|
||||||
|
inc => state => state + in
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Type parameters:**
|
||||||
|
|
||||||
|
▪ **A**: *ActionCreator*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type | Description |
|
||||||
|
------ | ------ | ------ |
|
||||||
|
`creator` | A | - |
|
||||||
|
`mutation` | [Mutation](../globals.md#mutation)‹S, ActionType‹A›› | - |
|
||||||
|
`isSink?` | undefined | false | true | If `true`, disables the subduxes mutations for this action. To conditionally run the subduxes mutations, check out [subduxUpreducer](updux.md#subduxupreducer). Defaults to `false`. |
|
||||||
|
|
||||||
|
**Returns:** *any*
|
||||||
|
|
||||||
|
▸ **addMutation**<**A**>(`creator`: string, `mutation`: [Mutation](../globals.md#mutation)‹S, any›, `isSink?`: undefined | false | true): *any*
|
||||||
|
|
||||||
|
**Type parameters:**
|
||||||
|
|
||||||
|
▪ **A**: *ActionCreator*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`creator` | string |
|
||||||
|
`mutation` | [Mutation](../globals.md#mutation)‹S, any› |
|
||||||
|
`isSink?` | undefined | false | true |
|
||||||
|
|
||||||
|
**Returns:** *any*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### addSelector
|
||||||
|
|
||||||
|
▸ **addSelector**(`name`: string, `selector`: [Selector](../globals.md#selector)): *void*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`name` | string |
|
||||||
|
`selector` | [Selector](../globals.md#selector) |
|
||||||
|
|
||||||
|
**Returns:** *void*
|
980
docs/API/globals.md
Normal file
980
docs/API/globals.md
Normal file
@ -0,0 +1,980 @@
|
|||||||
|
[updux - v1.2.0](README.md) › [Globals](globals.md)
|
||||||
|
|
||||||
|
# updux - v1.2.0
|
||||||
|
|
||||||
|
## Index
|
||||||
|
|
||||||
|
### Classes
|
||||||
|
|
||||||
|
* [Updux](classes/updux.md)
|
||||||
|
|
||||||
|
### Type aliases
|
||||||
|
|
||||||
|
* [Action](globals.md#action)
|
||||||
|
* [ActionPair](globals.md#actionpair)
|
||||||
|
* [ActionPayloadGenerator](globals.md#actionpayloadgenerator)
|
||||||
|
* [ActionsOf](globals.md#actionsof)
|
||||||
|
* [CoduxesOf](globals.md#coduxesof)
|
||||||
|
* [Dictionary](globals.md#dictionary)
|
||||||
|
* [Dux](globals.md#dux)
|
||||||
|
* [DuxActions](globals.md#duxactions)
|
||||||
|
* [DuxActionsCoduxes](globals.md#duxactionscoduxes)
|
||||||
|
* [DuxActionsSubduxes](globals.md#duxactionssubduxes)
|
||||||
|
* [DuxSelectors](globals.md#duxselectors)
|
||||||
|
* [DuxState](globals.md#duxstate)
|
||||||
|
* [DuxStateCoduxes](globals.md#duxstatecoduxes)
|
||||||
|
* [DuxStateGlobSub](globals.md#duxstateglobsub)
|
||||||
|
* [DuxStateSubduxes](globals.md#duxstatesubduxes)
|
||||||
|
* [Effect](globals.md#effect)
|
||||||
|
* [GenericActions](globals.md#genericactions)
|
||||||
|
* [ItemsOf](globals.md#itemsof)
|
||||||
|
* [LocalDuxState](globals.md#localduxstate)
|
||||||
|
* [MaybePayload](globals.md#maybepayload)
|
||||||
|
* [MaybeReturnType](globals.md#maybereturntype)
|
||||||
|
* [Merge](globals.md#merge)
|
||||||
|
* [Mutation](globals.md#mutation)
|
||||||
|
* [MutationEntry](globals.md#mutationentry)
|
||||||
|
* [MwGen](globals.md#mwgen)
|
||||||
|
* [Next](globals.md#next)
|
||||||
|
* [RebaseSelector](globals.md#rebaseselector)
|
||||||
|
* [Selector](globals.md#selector)
|
||||||
|
* [SelectorsOf](globals.md#selectorsof)
|
||||||
|
* [StateOf](globals.md#stateof)
|
||||||
|
* [StoreWithDispatchActions](globals.md#storewithdispatchactions)
|
||||||
|
* [SubMutations](globals.md#submutations)
|
||||||
|
* [Submws](globals.md#submws)
|
||||||
|
* [UnionToIntersection](globals.md#uniontointersection)
|
||||||
|
* [UpduxActions](globals.md#upduxactions)
|
||||||
|
* [UpduxConfig](globals.md#upduxconfig)
|
||||||
|
* [UpduxLocalActions](globals.md#upduxlocalactions)
|
||||||
|
* [UpduxMiddleware](globals.md#upduxmiddleware)
|
||||||
|
* [Upreducer](globals.md#upreducer)
|
||||||
|
|
||||||
|
### Variables
|
||||||
|
|
||||||
|
* [subEffects](globals.md#const-subeffects)
|
||||||
|
* [updux](globals.md#const-updux)
|
||||||
|
|
||||||
|
### Functions
|
||||||
|
|
||||||
|
* [MiddlewareFor](globals.md#const-middlewarefor)
|
||||||
|
* [buildActions](globals.md#buildactions)
|
||||||
|
* [buildCreateStore](globals.md#buildcreatestore)
|
||||||
|
* [buildInitial](globals.md#buildinitial)
|
||||||
|
* [buildMiddleware](globals.md#buildmiddleware)
|
||||||
|
* [buildMutations](globals.md#buildmutations)
|
||||||
|
* [buildSelectors](globals.md#buildselectors)
|
||||||
|
* [buildUpreducer](globals.md#buildupreducer)
|
||||||
|
* [coduxes](globals.md#const-coduxes)
|
||||||
|
* [composeMutations](globals.md#const-composemutations)
|
||||||
|
* [composeMw](globals.md#const-composemw)
|
||||||
|
* [dux](globals.md#const-dux)
|
||||||
|
* [effectToMw](globals.md#const-effecttomw)
|
||||||
|
* [sliceMw](globals.md#slicemw)
|
||||||
|
* [subMiddleware](globals.md#const-submiddleware)
|
||||||
|
* [subSelectors](globals.md#subselectors)
|
||||||
|
|
||||||
|
## Type aliases
|
||||||
|
|
||||||
|
### Action
|
||||||
|
|
||||||
|
Ƭ **Action**: *object & [MaybePayload](globals.md#maybepayload)‹P›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### ActionPair
|
||||||
|
|
||||||
|
Ƭ **ActionPair**: *[string, ActionCreator]*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### ActionPayloadGenerator
|
||||||
|
|
||||||
|
Ƭ **ActionPayloadGenerator**: *function*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
▸ (...`args`: any[]): *any*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`...args` | any[] |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### ActionsOf
|
||||||
|
|
||||||
|
Ƭ **ActionsOf**: *U extends Updux ? U["actions"] : object*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### CoduxesOf
|
||||||
|
|
||||||
|
Ƭ **CoduxesOf**: *U extends Updux<any, any, any, infer S> ? S : []*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Dictionary
|
||||||
|
|
||||||
|
Ƭ **Dictionary**: *object*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
* \[ **key**: *string*\]: T
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Dux
|
||||||
|
|
||||||
|
Ƭ **Dux**: *object*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
* **actions**: *A*
|
||||||
|
|
||||||
|
* **coduxes**: *[Dux](globals.md#dux)[]*
|
||||||
|
|
||||||
|
* **initial**: *AggDuxState‹S, C›*
|
||||||
|
|
||||||
|
* **subduxes**: *[Dictionary](globals.md#dictionary)‹[Dux](globals.md#dux)›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### DuxActions
|
||||||
|
|
||||||
|
Ƭ **DuxActions**:
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### DuxActionsCoduxes
|
||||||
|
|
||||||
|
Ƭ **DuxActionsCoduxes**: *C extends Array<infer I> ? UnionToIntersection<ActionsOf<I>> : object*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### DuxActionsSubduxes
|
||||||
|
|
||||||
|
Ƭ **DuxActionsSubduxes**: *C extends object ? ActionsOf<C[keyof C]> : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### DuxSelectors
|
||||||
|
|
||||||
|
Ƭ **DuxSelectors**: *unknown extends X ? object : X*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### DuxState
|
||||||
|
|
||||||
|
Ƭ **DuxState**: *D extends object ? S : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### DuxStateCoduxes
|
||||||
|
|
||||||
|
Ƭ **DuxStateCoduxes**: *C extends Array<infer U> ? UnionToIntersection<StateOf<U>> : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### DuxStateGlobSub
|
||||||
|
|
||||||
|
Ƭ **DuxStateGlobSub**: *S extends object ? StateOf<I> : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### DuxStateSubduxes
|
||||||
|
|
||||||
|
Ƭ **DuxStateSubduxes**: *C extends object ? object : C extends object ? object : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Effect
|
||||||
|
|
||||||
|
Ƭ **Effect**: *[string, [UpduxMiddleware](globals.md#upduxmiddleware) | [MwGen](globals.md#mwgen), undefined | false | true]*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### GenericActions
|
||||||
|
|
||||||
|
Ƭ **GenericActions**: *[Dictionary](globals.md#dictionary)‹ActionCreator‹string, function››*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### ItemsOf
|
||||||
|
|
||||||
|
Ƭ **ItemsOf**: *C extends object ? C[keyof C] : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### LocalDuxState
|
||||||
|
|
||||||
|
Ƭ **LocalDuxState**: *S extends never[] ? unknown[] : S*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### MaybePayload
|
||||||
|
|
||||||
|
Ƭ **MaybePayload**: *P extends object | string | boolean | number ? object : object*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### MaybeReturnType
|
||||||
|
|
||||||
|
Ƭ **MaybeReturnType**: *X extends function ? ReturnType<X> : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Merge
|
||||||
|
|
||||||
|
Ƭ **Merge**: *[UnionToIntersection](globals.md#uniontointersection)‹T[keyof T]›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Mutation
|
||||||
|
|
||||||
|
Ƭ **Mutation**: *function*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
▸ (`payload`: A["payload"], `action`: A): *function*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`payload` | A["payload"] |
|
||||||
|
`action` | A |
|
||||||
|
|
||||||
|
▸ (`state`: S): *S*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`state` | S |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### MutationEntry
|
||||||
|
|
||||||
|
Ƭ **MutationEntry**: *[ActionCreator | string, [Mutation](globals.md#mutation)‹any, [Action](globals.md#action)‹string, any››, undefined | false | true]*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### MwGen
|
||||||
|
|
||||||
|
Ƭ **MwGen**: *function*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
▸ (): *[UpduxMiddleware](globals.md#upduxmiddleware)*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Next
|
||||||
|
|
||||||
|
Ƭ **Next**: *function*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
▸ (`action`: [Action](globals.md#action)): *any*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`action` | [Action](globals.md#action) |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### RebaseSelector
|
||||||
|
|
||||||
|
Ƭ **RebaseSelector**: *object*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Selector
|
||||||
|
|
||||||
|
Ƭ **Selector**: *function*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
▸ (`state`: S): *unknown*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`state` | S |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### SelectorsOf
|
||||||
|
|
||||||
|
Ƭ **SelectorsOf**: *C extends object ? S : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### StateOf
|
||||||
|
|
||||||
|
Ƭ **StateOf**: *D extends object ? I : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### StoreWithDispatchActions
|
||||||
|
|
||||||
|
Ƭ **StoreWithDispatchActions**: *Store‹S› & object*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### SubMutations
|
||||||
|
|
||||||
|
Ƭ **SubMutations**: *object*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
* \[ **slice**: *string*\]: [Dictionary](globals.md#dictionary)‹[Mutation](globals.md#mutation)›
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Submws
|
||||||
|
|
||||||
|
Ƭ **Submws**: *[Dictionary](globals.md#dictionary)‹[UpduxMiddleware](globals.md#upduxmiddleware)›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### UnionToIntersection
|
||||||
|
|
||||||
|
Ƭ **UnionToIntersection**: *U extends any ? function : never extends function ? I : never*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### UpduxActions
|
||||||
|
|
||||||
|
Ƭ **UpduxActions**: *U extends Updux ? UnionToIntersection<UpduxLocalActions<U> | ActionsOf<CoduxesOf<U>[keyof CoduxesOf<U>]>> : object*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### UpduxConfig
|
||||||
|
|
||||||
|
Ƭ **UpduxConfig**: *Partial‹object›*
|
||||||
|
|
||||||
|
Configuration object given to Updux's constructor.
|
||||||
|
|
||||||
|
#### arguments
|
||||||
|
|
||||||
|
##### initial
|
||||||
|
|
||||||
|
Default initial state of the reducer. If applicable, is merged with
|
||||||
|
the subduxes initial states, with the parent having precedence.
|
||||||
|
|
||||||
|
If not provided, defaults to an empty object.
|
||||||
|
|
||||||
|
##### actions
|
||||||
|
|
||||||
|
[Actions](/concepts/Actions) used by the updux.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { dux } from 'updux';
|
||||||
|
import { action, payload } from 'ts-action';
|
||||||
|
|
||||||
|
const bar = action('BAR', payload<int>());
|
||||||
|
const foo = action('FOO');
|
||||||
|
|
||||||
|
const myDux = dux({
|
||||||
|
actions: {
|
||||||
|
bar
|
||||||
|
},
|
||||||
|
mutations: [
|
||||||
|
[ foo, () => state => state ]
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
myDux.actions.foo({ x: 1, y: 2 }); // => { type: foo, x:1, y:2 }
|
||||||
|
myDux.actions.bar(2); // => { type: bar, payload: 2 }
|
||||||
|
```
|
||||||
|
|
||||||
|
New actions used directly in mutations and effects will be added to the
|
||||||
|
dux actions -- that is, they will be accessible via `dux.actions` -- but will
|
||||||
|
not appear as part of its Typescript type.
|
||||||
|
|
||||||
|
##### selectors
|
||||||
|
|
||||||
|
Dictionary of selectors for the current updux. The updux also
|
||||||
|
inherit its subduxes' selectors.
|
||||||
|
|
||||||
|
The selectors are available via the class' getter.
|
||||||
|
|
||||||
|
##### mutations
|
||||||
|
|
||||||
|
mutations: [
|
||||||
|
[ action, mutation, isSink ],
|
||||||
|
...
|
||||||
|
]
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
mutations: {
|
||||||
|
action: mutation,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
List of mutations for assign to the dux. If you want Typescript goodness, you
|
||||||
|
probably want to use `addMutation()` instead.
|
||||||
|
|
||||||
|
In its generic array-of-array form,
|
||||||
|
each mutation tuple contains: the action, the mutation,
|
||||||
|
and boolean indicating if this is a sink mutation.
|
||||||
|
|
||||||
|
The action can be an action creator function or a string. If it's a string, it's considered to be the
|
||||||
|
action type and a generic `action( actionName, payload() )` creator will be
|
||||||
|
generated for it. If an action is not already defined in the `actions`
|
||||||
|
parameter, it'll be automatically added.
|
||||||
|
|
||||||
|
The pseudo-action type `*` can be used to match any action not explicitly matched by other mutations.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const todosUpdux = updux({
|
||||||
|
mutations: {
|
||||||
|
add: todo => state => [ ...state, todo ],
|
||||||
|
done: done_id => u.map( u.if( ({id} => id === done_id), {done: true} ) ),
|
||||||
|
'*' (payload,action) => state => {
|
||||||
|
console.warn( "unexpected action ", action.type );
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The signature of the mutations is `(payload,action) => state => newState`.
|
||||||
|
It is designed to play well with `Updeep` (and [Immer](https://immerjs.github.io/immer/docs/introduction)). This way, instead of doing
|
||||||
|
|
||||||
|
```js
|
||||||
|
mutation: {
|
||||||
|
renameTodo: newName => state => { ...state, name: newName }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
we can do
|
||||||
|
|
||||||
|
```js
|
||||||
|
mutation: {
|
||||||
|
renameTodo: newName => u({ name: newName })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The final argument is the optional boolean `isSink`. If it is true, it'll
|
||||||
|
prevent subduxes' mutations on the same action. It defaults to `false`.
|
||||||
|
|
||||||
|
The object version of the argument can be used as a shortcut when all actions
|
||||||
|
are strings. In that case, `isSink` is `false` for all mutations.
|
||||||
|
|
||||||
|
##### groomMutations
|
||||||
|
|
||||||
|
Function that can be provided to alter all local mutations of the updux
|
||||||
|
(the mutations of subduxes are left untouched).
|
||||||
|
|
||||||
|
Can be used, for example, for Immer integration:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import Updux from 'updux';
|
||||||
|
import { produce } from 'Immer';
|
||||||
|
|
||||||
|
const updux = new Updux({
|
||||||
|
initial: { counter: 0 },
|
||||||
|
groomMutations: mutation => (...args) => produce( mutation(...args) ),
|
||||||
|
mutations: {
|
||||||
|
add: (inc=1) => draft => draft.counter += inc
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Or perhaps for debugging:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import Updux from 'updux';
|
||||||
|
|
||||||
|
const updux = new Updux({
|
||||||
|
initial: { counter: 0 },
|
||||||
|
groomMutations: mutation => (...args) => state => {
|
||||||
|
console.log( "got action ", args[1] );
|
||||||
|
return mutation(...args)(state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
##### subduxes
|
||||||
|
|
||||||
|
Object mapping slices of the state to sub-upduxes. In addition to creating
|
||||||
|
sub-reducers for those slices, it'll make the parend updux inherit all the
|
||||||
|
actions and middleware from its subduxes.
|
||||||
|
|
||||||
|
For example, if in plain Redux you would do
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { combineReducers } from 'redux';
|
||||||
|
import todosReducer from './todos';
|
||||||
|
import statisticsReducer from './statistics';
|
||||||
|
|
||||||
|
const rootReducer = combineReducers({
|
||||||
|
todos: todosReducer,
|
||||||
|
stats: statisticsReducer,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
then with Updux you'd do
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { updux } from 'updux';
|
||||||
|
import todos from './todos';
|
||||||
|
import statistics from './statistics';
|
||||||
|
|
||||||
|
const rootUpdux = updux({
|
||||||
|
subduxes: {
|
||||||
|
todos,
|
||||||
|
statistics
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
##### effects
|
||||||
|
|
||||||
|
Array of arrays or plain object defining asynchronous actions and side-effects triggered by actions.
|
||||||
|
The effects themselves are Redux middleware, with the `dispatch`
|
||||||
|
property of the first argument augmented with all the available actions.
|
||||||
|
|
||||||
|
```
|
||||||
|
updux({
|
||||||
|
effects: {
|
||||||
|
fetch: ({dispatch}) => next => async (action) => {
|
||||||
|
next(action);
|
||||||
|
|
||||||
|
let result = await fetch(action.payload.url).then( result => result.json() );
|
||||||
|
dispatch.fetchSuccess(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
|
||||||
|
```
|
||||||
|
import Updux from 'updux';
|
||||||
|
import { actions, payload } from 'ts-action';
|
||||||
|
import u from 'updeep';
|
||||||
|
|
||||||
|
const todoUpdux = new Updux({
|
||||||
|
initial: {
|
||||||
|
done: false,
|
||||||
|
note: "",
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
finish: action('FINISH', payload()),
|
||||||
|
edit: action('EDIT', payload()),
|
||||||
|
},
|
||||||
|
mutations: [
|
||||||
|
[ edit, note => u({note}) ]
|
||||||
|
],
|
||||||
|
selectors: {
|
||||||
|
getNote: state => state.note
|
||||||
|
},
|
||||||
|
groomMutations: mutation => transform(mutation),
|
||||||
|
subduxes: {
|
||||||
|
foo
|
||||||
|
},
|
||||||
|
effects: {
|
||||||
|
finish: () => next => action => {
|
||||||
|
console.log( "Woo! one more bites the dust" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### UpduxLocalActions
|
||||||
|
|
||||||
|
Ƭ **UpduxLocalActions**: *S extends Updux<any, null> ? object : S extends Updux<any, infer A> ? A : object*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### UpduxMiddleware
|
||||||
|
|
||||||
|
Ƭ **UpduxMiddleware**: *function*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
▸ (`api`: UpduxMiddlewareAPI‹S, X›): *function*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`api` | UpduxMiddlewareAPI‹S, X› |
|
||||||
|
|
||||||
|
▸ (`next`: Function): *function*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`next` | Function |
|
||||||
|
|
||||||
|
▸ (`action`: A): *any*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`action` | A |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Upreducer
|
||||||
|
|
||||||
|
Ƭ **Upreducer**: *function*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
▸ (`action`: [Action](globals.md#action)): *function*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`action` | [Action](globals.md#action) |
|
||||||
|
|
||||||
|
▸ (`state`: S): *S*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`state` | S |
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
### `Const` subEffects
|
||||||
|
|
||||||
|
• **subEffects**: *[Effect](globals.md#effect)* = [ '*', subMiddleware ] as any
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### `Const` updux
|
||||||
|
|
||||||
|
• **updux**: *[Updux](classes/updux.md)‹unknown, null, unknown, object›* = new Updux({
|
||||||
|
subduxes: {
|
||||||
|
foo: dux({ initial: "banana" })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
### `Const` MiddlewareFor
|
||||||
|
|
||||||
|
▸ **MiddlewareFor**(`type`: any, `mw`: Middleware): *Middleware*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`type` | any |
|
||||||
|
`mw` | Middleware |
|
||||||
|
|
||||||
|
**Returns:** *Middleware*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### buildActions
|
||||||
|
|
||||||
|
▸ **buildActions**(`actions`: [ActionPair](globals.md#actionpair)[]): *[Dictionary](globals.md#dictionary)‹ActionCreator‹string, function››*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type | Default |
|
||||||
|
------ | ------ | ------ |
|
||||||
|
`actions` | [ActionPair](globals.md#actionpair)[] | [] |
|
||||||
|
|
||||||
|
**Returns:** *[Dictionary](globals.md#dictionary)‹ActionCreator‹string, function››*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### buildCreateStore
|
||||||
|
|
||||||
|
▸ **buildCreateStore**<**S**, **A**>(`reducer`: Reducer‹S›, `middleware`: Middleware, `actions`: A): *function*
|
||||||
|
|
||||||
|
**Type parameters:**
|
||||||
|
|
||||||
|
▪ **S**
|
||||||
|
|
||||||
|
▪ **A**
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type | Default |
|
||||||
|
------ | ------ | ------ |
|
||||||
|
`reducer` | Reducer‹S› | - |
|
||||||
|
`middleware` | Middleware | - |
|
||||||
|
`actions` | A | {} as A |
|
||||||
|
|
||||||
|
**Returns:** *function*
|
||||||
|
|
||||||
|
▸ (`initial?`: S, `injectEnhancer?`: Function): *Store‹S› & object*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`initial?` | S |
|
||||||
|
`injectEnhancer?` | Function |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### buildInitial
|
||||||
|
|
||||||
|
▸ **buildInitial**(`initial`: any, `coduxes`: any, `subduxes`: any): *any*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type | Default |
|
||||||
|
------ | ------ | ------ |
|
||||||
|
`initial` | any | - |
|
||||||
|
`coduxes` | any | [] |
|
||||||
|
`subduxes` | any | {} |
|
||||||
|
|
||||||
|
**Returns:** *any*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### buildMiddleware
|
||||||
|
|
||||||
|
▸ **buildMiddleware**<**S**>(`local`: [UpduxMiddleware](globals.md#upduxmiddleware)[], `co`: [UpduxMiddleware](globals.md#upduxmiddleware)[], `sub`: [Submws](globals.md#submws)): *[UpduxMiddleware](globals.md#upduxmiddleware)‹S›*
|
||||||
|
|
||||||
|
**Type parameters:**
|
||||||
|
|
||||||
|
▪ **S**
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type | Default |
|
||||||
|
------ | ------ | ------ |
|
||||||
|
`local` | [UpduxMiddleware](globals.md#upduxmiddleware)[] | [] |
|
||||||
|
`co` | [UpduxMiddleware](globals.md#upduxmiddleware)[] | [] |
|
||||||
|
`sub` | [Submws](globals.md#submws) | {} |
|
||||||
|
|
||||||
|
**Returns:** *[UpduxMiddleware](globals.md#upduxmiddleware)‹S›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### buildMutations
|
||||||
|
|
||||||
|
▸ **buildMutations**(`mutations`: [Dictionary](globals.md#dictionary)‹[Mutation](globals.md#mutation) | [[Mutation](globals.md#mutation), boolean | undefined]›, `subduxes`: object, `coduxes`: [Upreducer](globals.md#upreducer)[]): *object*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type | Default |
|
||||||
|
------ | ------ | ------ |
|
||||||
|
`mutations` | [Dictionary](globals.md#dictionary)‹[Mutation](globals.md#mutation) | [[Mutation](globals.md#mutation), boolean | undefined]› | {} |
|
||||||
|
`subduxes` | object | {} |
|
||||||
|
`coduxes` | [Upreducer](globals.md#upreducer)[] | [] |
|
||||||
|
|
||||||
|
**Returns:** *object*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### buildSelectors
|
||||||
|
|
||||||
|
▸ **buildSelectors**(`localSelectors`: [Dictionary](globals.md#dictionary)‹[Selector](globals.md#selector)›, `coduxes`: [Dictionary](globals.md#dictionary)‹[Selector](globals.md#selector)›[], `subduxes`: [Dictionary](globals.md#dictionary)‹[Selector](globals.md#selector)›): *object*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type | Default |
|
||||||
|
------ | ------ | ------ |
|
||||||
|
`localSelectors` | [Dictionary](globals.md#dictionary)‹[Selector](globals.md#selector)› | {} |
|
||||||
|
`coduxes` | [Dictionary](globals.md#dictionary)‹[Selector](globals.md#selector)›[] | [] |
|
||||||
|
`subduxes` | [Dictionary](globals.md#dictionary)‹[Selector](globals.md#selector)› | {} |
|
||||||
|
|
||||||
|
**Returns:** *object*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### buildUpreducer
|
||||||
|
|
||||||
|
▸ **buildUpreducer**<**S**>(`initial`: S, `mutations`: [Dictionary](globals.md#dictionary)‹[Mutation](globals.md#mutation)‹S››): *[Upreducer](globals.md#upreducer)‹S›*
|
||||||
|
|
||||||
|
**Type parameters:**
|
||||||
|
|
||||||
|
▪ **S**
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`initial` | S |
|
||||||
|
`mutations` | [Dictionary](globals.md#dictionary)‹[Mutation](globals.md#mutation)‹S›› |
|
||||||
|
|
||||||
|
**Returns:** *[Upreducer](globals.md#upreducer)‹S›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### `Const` coduxes
|
||||||
|
|
||||||
|
▸ **coduxes**<**C**, **U**>(...`coduxes`: U): *object*
|
||||||
|
|
||||||
|
**Type parameters:**
|
||||||
|
|
||||||
|
▪ **C**: *[Dux](globals.md#dux)*
|
||||||
|
|
||||||
|
▪ **U**: *[C]*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`...coduxes` | U |
|
||||||
|
|
||||||
|
**Returns:** *object*
|
||||||
|
|
||||||
|
* **coduxes**: *U*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### `Const` composeMutations
|
||||||
|
|
||||||
|
▸ **composeMutations**(`mutations`: [Mutation](globals.md#mutation)[]): *function | (Anonymous function)*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`mutations` | [Mutation](globals.md#mutation)[] |
|
||||||
|
|
||||||
|
**Returns:** *function | (Anonymous function)*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### `Const` composeMw
|
||||||
|
|
||||||
|
▸ **composeMw**(`mws`: [UpduxMiddleware](globals.md#upduxmiddleware)[]): *(Anonymous function)*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`mws` | [UpduxMiddleware](globals.md#upduxmiddleware)[] |
|
||||||
|
|
||||||
|
**Returns:** *(Anonymous function)*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### `Const` dux
|
||||||
|
|
||||||
|
▸ **dux**<**S**, **A**, **X**, **C**>(`config`: C): *object*
|
||||||
|
|
||||||
|
**Type parameters:**
|
||||||
|
|
||||||
|
▪ **S**
|
||||||
|
|
||||||
|
▪ **A**
|
||||||
|
|
||||||
|
▪ **X**
|
||||||
|
|
||||||
|
▪ **C**: *[UpduxConfig](globals.md#upduxconfig)*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`config` | C |
|
||||||
|
|
||||||
|
**Returns:** *object*
|
||||||
|
|
||||||
|
* **actions**: = this.actions
|
||||||
|
|
||||||
|
* **coduxes**: *object[]* = this.coduxes
|
||||||
|
|
||||||
|
* **createStore**(): *function*
|
||||||
|
|
||||||
|
* (`initial?`: S, `injectEnhancer?`: Function): *Store‹S› & object*
|
||||||
|
|
||||||
|
* **initial**: = this.initial
|
||||||
|
|
||||||
|
* **middleware**(): *function*
|
||||||
|
|
||||||
|
* (`api`: UpduxMiddlewareAPI‹S, X›): *function*
|
||||||
|
|
||||||
|
* (`next`: Function): *function*
|
||||||
|
|
||||||
|
* (`action`: A): *any*
|
||||||
|
|
||||||
|
* **mutations**(): *object*
|
||||||
|
|
||||||
|
* **reducer**(): *function*
|
||||||
|
|
||||||
|
* (`state`: S | undefined, `action`: [Action](globals.md#action)): *S*
|
||||||
|
|
||||||
|
* **selectors**: = this.selectors
|
||||||
|
|
||||||
|
* **subduxes**(): *object*
|
||||||
|
|
||||||
|
* **upreducer**(): *function*
|
||||||
|
|
||||||
|
* (`action`: [Action](globals.md#action)): *function*
|
||||||
|
|
||||||
|
* (`state`: S): *S*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### `Const` effectToMw
|
||||||
|
|
||||||
|
▸ **effectToMw**(`effect`: [Effect](globals.md#effect), `actions`: [Dictionary](globals.md#dictionary)‹ActionCreator›, `selectors`: [Dictionary](globals.md#dictionary)‹[Selector](globals.md#selector)›): *subMiddleware | augmented*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`effect` | [Effect](globals.md#effect) |
|
||||||
|
`actions` | [Dictionary](globals.md#dictionary)‹ActionCreator› |
|
||||||
|
`selectors` | [Dictionary](globals.md#dictionary)‹[Selector](globals.md#selector)› |
|
||||||
|
|
||||||
|
**Returns:** *subMiddleware | augmented*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### sliceMw
|
||||||
|
|
||||||
|
▸ **sliceMw**(`slice`: string, `mw`: [UpduxMiddleware](globals.md#upduxmiddleware)): *[UpduxMiddleware](globals.md#upduxmiddleware)*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`slice` | string |
|
||||||
|
`mw` | [UpduxMiddleware](globals.md#upduxmiddleware) |
|
||||||
|
|
||||||
|
**Returns:** *[UpduxMiddleware](globals.md#upduxmiddleware)*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### `Const` subMiddleware
|
||||||
|
|
||||||
|
▸ **subMiddleware**(): *(Anonymous function)*
|
||||||
|
|
||||||
|
**Returns:** *(Anonymous function)*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### subSelectors
|
||||||
|
|
||||||
|
▸ **subSelectors**(`__namedParameters`: [string, Function]): *[string, [Selector](globals.md#selector)][]*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`__namedParameters` | [string, Function] |
|
||||||
|
|
||||||
|
**Returns:** *[string, [Selector](globals.md#selector)][]*
|
202
docs/README.md
202
docs/README.md
@ -1,11 +1,12 @@
|
|||||||
|
|
||||||
# What's Updux?
|
# What's Updux?
|
||||||
|
|
||||||
So, I'm a fan of [Redux](https://redux.js.org). Two days ago I discovered
|
So, I'm a fan of [Redux](https://redux.js.org).
|
||||||
[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
|
As I was looking into tools to help cut on its boilerplate,
|
||||||
boilerplate. Keeping mutations and asynchronous effects close to the
|
I came across [rematch](https://rematch.github.io/rematch).
|
||||||
|
It has a few pretty darn good ideas.
|
||||||
|
Keeping mutations and asynchronous effects close to the
|
||||||
reducer definition? Nice. Automatically infering the
|
reducer definition? Nice. Automatically infering the
|
||||||
actions from the said mutations and effects? Genius!
|
actions from the said mutations and effects? Genius!
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ But it also enforces a flat hierarchy of reducers -- where
|
|||||||
is the fun in that? And I'm also having a strong love for
|
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.
|
[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
|
Hence: `Updux`. Heavily inspired by `rematch`, but twisted
|
||||||
to work with `updeep` and to fit my peculiar needs. It offers features such as
|
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
|
* Mimic the way VueX has mutations (reducer reactions to specific actions) and
|
||||||
@ -24,55 +25,49 @@ to work with `updeep` and to fit my peculiar needs. It offers features such as
|
|||||||
and makes then accessible as attributes to the `dispatch` object of the
|
and makes then accessible as attributes to the `dispatch` object of the
|
||||||
store.
|
store.
|
||||||
* Mutations have a signature that is friendly to Updux and Immer.
|
* Mutations have a signature that is friendly to Updux and Immer.
|
||||||
* Also, the mutation signature auto-unwrap the payload of the actions for you.
|
* Mutations auto-unwrapping the payload of actions for you.
|
||||||
* TypeScript types.
|
* TypeScript types.
|
||||||
|
* Leverage [ts-action](https://www.npmjs.com/package/ts-action) for action
|
||||||
|
creation.
|
||||||
|
|
||||||
|
**Fair warning**: this package is still very new, likely to go through
|
||||||
Fair warning: this package is still very new, probably very buggy,
|
big changes before I find the perfect balance between ease of use and sanity.
|
||||||
definitively very badly documented, and very subject to changes. Caveat
|
Caveat Emptor.
|
||||||
Maxima Emptor.
|
|
||||||
|
|
||||||
# Synopsis
|
# Synopsis
|
||||||
|
|
||||||
```
|
```
|
||||||
import updux from 'updux';
|
import Updux from 'updux';
|
||||||
|
import { action, payload } from 'ts-action';
|
||||||
|
|
||||||
import otherUpdux from './otherUpdux';
|
import otherDux from './otherUpdux';
|
||||||
|
|
||||||
const {
|
const inc = action( 'INC', payload<int>() );
|
||||||
initial,
|
|
||||||
reducer,
|
const updux = new Updux({
|
||||||
actions,
|
|
||||||
middleware,
|
|
||||||
createStore,
|
|
||||||
} = new Updux({
|
|
||||||
initial: {
|
initial: {
|
||||||
counter: 0,
|
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: {
|
actions: {
|
||||||
customAction: ( someArg ) => ({
|
inc
|
||||||
type: "custom",
|
|
||||||
payload: { someProp: someArg }
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
|
subduxes: {
|
||||||
|
otherDux,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const store = createStore();
|
updux.addMutation( inc, increment => u({counter: s => s + increment }));
|
||||||
|
|
||||||
store.dispatch.inc(3);
|
updux.addEffect( '*', api => next => action => {
|
||||||
|
console.log( "hey, look, an action zoomed by!", action );
|
||||||
|
next(action);
|
||||||
|
} );
|
||||||
|
|
||||||
|
const myDux = updux.asDux;
|
||||||
|
|
||||||
|
const store = myDux.createStore();
|
||||||
|
|
||||||
|
store.dispatch( myDux.actions.inc(3) );
|
||||||
```
|
```
|
||||||
|
|
||||||
# Description
|
# Description
|
||||||
@ -85,23 +80,22 @@ types can be found over [here](https://yanick.github.io/updux/docs/classes/updux
|
|||||||
If you are creating upduxes that will be used as subduxes
|
If you are creating upduxes that will be used as subduxes
|
||||||
by other upduxes, or as
|
by other upduxes, or as
|
||||||
[ducks](https://github.com/erikras/ducks-modular-redux)-like containers, I
|
[ducks](https://github.com/erikras/ducks-modular-redux)-like containers, I
|
||||||
recommend that you export the Updux instance as the default export:
|
recommend that you export the "compiled" (as in, no more editable and with all its properties resolved) output of the Updux instance via its `asDux()` getter:
|
||||||
|
|
||||||
```
|
```
|
||||||
import Updux from 'updux';
|
import Updux from 'updux';
|
||||||
|
|
||||||
const updux = new Updux({ ... });
|
const updux = new Updux({ ... });
|
||||||
|
|
||||||
export default updux;
|
export default updux.asDux;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Then you can use them as subduxes like this:
|
Then you can use them as subduxes like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
import Updux from 'updux';
|
import Updux from 'updux';
|
||||||
import foo from './foo'; // foo is an Updux
|
import foo from './foo'; // foo is a dux
|
||||||
import bar from './bar'; // bar is an Updux as well
|
import bar from './bar'; // bar is a dux as well
|
||||||
|
|
||||||
const updux = new Updux({
|
const updux = new Updux({
|
||||||
subduxes: {
|
subduxes: {
|
||||||
@ -109,125 +103,3 @@ const updux = new Updux({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<!-- docs/_sidebar.md -->
|
- [README](/README.md)
|
||||||
|
- [Tutorial](/tutorial.md)
|
||||||
* [Home](/)
|
- [Concepts](/concepts.md)
|
||||||
* [Concepts](concepts.md)
|
- [Recipes](/recipes.md)
|
||||||
* API Reference
|
- API
|
||||||
* [Updux](updux.md)
|
- [Top-level exports](/exports.md)
|
||||||
|
- [main index](/API/globals.md)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
## actions
|
## actions
|
||||||
|
|
||||||
Updux internally uses the package `ts-action` to create action creator
|
Updux internally uses the package `ts-action` to create action creator
|
||||||
functions. Even if you don't use typescript, I recommend that you use it,
|
functions. Even if you don't use Typescript, I recommend that you use it,
|
||||||
as it does what it does very well. But if you don't want to, no big deal.
|
as it does what it does very well. But if you don't want to, no big deal.
|
||||||
Updux will recognize a function as an action creator if it has a `type`
|
Updux will recognize a function as an action creator if it has a `type`
|
||||||
property. So a homegrown creator could be as simple as:
|
property. So a homegrown creator could be as simple as:
|
||||||
@ -14,10 +14,9 @@ function action(type) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## effects
|
## effects
|
||||||
|
|
||||||
Updux effects are a superset of redux middleware. I kept that format, and the
|
Updux effects are redux middlewares. I kept that format, and the
|
||||||
use of `next` mostly because I wanted to give myself a way to alter
|
use of `next` mostly because I wanted to give myself a way to alter
|
||||||
actions before they hit the reducer, something that `redux-saga` and
|
actions before they hit the reducer, something that `redux-saga` and
|
||||||
`rematch` don't allow.
|
`rematch` don't allow.
|
||||||
@ -25,21 +24,5 @@ actions before they hit the reducer, something that `redux-saga` and
|
|||||||
An effect has the signature
|
An effect has the signature
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const effect = ({ getState, dispatch, getRootState, selectors})
|
const effect = ({ getState, dispatch }) => next => action => { ... }
|
||||||
=> next => action => { ... }
|
|
||||||
```
|
|
||||||
|
|
||||||
The first argument is like the usual redux middlewareApi, except
|
|
||||||
for the availability of selectors and of the root updux's state.
|
|
||||||
|
|
||||||
Also, the function `dispatch` is augmented to be able to be called
|
|
||||||
with the allowed actions as props. For example, assuming that the action
|
|
||||||
`complete_todo` has been declared somewhere, then it's possible to do:
|
|
||||||
|
|
||||||
```js
|
|
||||||
updux.addEffect( 'todo_bankrupcy',
|
|
||||||
({ getState, dispatch }) => next => action => {
|
|
||||||
getState.forEach( todo => dispatch.complete_todo( todo.id ) );
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
```
|
||||||
|
12
docs/exports.md
Normal file
12
docs/exports.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Top-level exports
|
||||||
|
|
||||||
|
## Default export
|
||||||
|
|
||||||
|
* [Updux](/API/classes/updux)
|
||||||
|
|
||||||
|
## Exports
|
||||||
|
|
||||||
|
* [Updux](/API/classes/updux)
|
||||||
|
* [dux](/API/globals?id=const-dux)
|
||||||
|
* [coduxes](/API/globals?id=const-coduxes)
|
||||||
|
* [subEffects](/API/globals?id=const-subeffects)
|
@ -16,6 +16,7 @@
|
|||||||
repo: 'https://github.com/yanick/updux',
|
repo: 'https://github.com/yanick/updux',
|
||||||
loadSidebar: true,
|
loadSidebar: true,
|
||||||
subMaxLevel: 4,
|
subMaxLevel: 4,
|
||||||
|
relativePath: true,
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
|
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
|
||||||
|
107
docs/recipes.md
Normal file
107
docs/recipes.md
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
# Recipes
|
||||||
|
|
||||||
|
## 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({
|
||||||
|
actions: {
|
||||||
|
review: action('REVIEW'),
|
||||||
|
done: action('DONE',payload<int>()),
|
||||||
|
},
|
||||||
|
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 can be
|
||||||
|
rewritten as:
|
||||||
|
|
||||||
|
```
|
||||||
|
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 not
|
||||||
|
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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
596
docs/tutorial.md
Normal file
596
docs/tutorial.md
Normal file
@ -0,0 +1,596 @@
|
|||||||
|
# Tutorial
|
||||||
|
|
||||||
|
This tutorial walks you through the features of `Updux` using the
|
||||||
|
time-honored example of the implementation of Todo list store.
|
||||||
|
|
||||||
|
This tutorial assumes that our project is written in TypeScript, and
|
||||||
|
that we are using [updeep](https://www.npmjs.com/package/updeep) to
|
||||||
|
help with immutability and deep merging and [ts-action][] to manage our
|
||||||
|
actions. This is the recommended setup, but
|
||||||
|
none of those two architecture
|
||||||
|
decisions are mandatory; Updux is equally usable in a pure-JavaScript setting,
|
||||||
|
and `updeep` can easily be substitued by, say, [immer][], [lodash][], or even
|
||||||
|
just plain JavaScript. Eventually, I plan to write a version of this tutorial
|
||||||
|
with all those different configurations.
|
||||||
|
|
||||||
|
Also, the code used here is also available in the project repository, in the
|
||||||
|
`src/tutorial` directory.
|
||||||
|
|
||||||
|
## Definition of the state
|
||||||
|
|
||||||
|
First thing first: let's define the type of our store:
|
||||||
|
|
||||||
|
```
|
||||||
|
type Todo = {
|
||||||
|
id: number;
|
||||||
|
description: string;
|
||||||
|
done: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TodoStore = {
|
||||||
|
next_id: number;
|
||||||
|
todos: Todo[];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
With that, let's create our very first Updux:
|
||||||
|
|
||||||
|
```
|
||||||
|
import Updux from 'updux';
|
||||||
|
|
||||||
|
const todosUpdux = new Updux({
|
||||||
|
initial: {
|
||||||
|
next_id: 1,
|
||||||
|
todos: [],
|
||||||
|
} as TodoStore
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that we explicitly cast the initial state as `as TodoStore`. This lets
|
||||||
|
Updux know what is the store's state.
|
||||||
|
|
||||||
|
This being said, 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:
|
||||||
|
|
||||||
|
```
|
||||||
|
const store = todosUpdux.createStore();
|
||||||
|
|
||||||
|
console.log(store.getState());
|
||||||
|
// { next_id: 1, todos: [] }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add actions
|
||||||
|
|
||||||
|
This is all good, but a little static. Let's add actions!
|
||||||
|
|
||||||
|
```
|
||||||
|
import { action, payload } from 'ts-action';
|
||||||
|
|
||||||
|
const add_todo = action('add_todo', payload<string>() );
|
||||||
|
const todo_done = action('todo_done', payload<number>() );
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, there is a lot of ways to add actions to a Updux object.
|
||||||
|
|
||||||
|
It can be defined when the object is created:
|
||||||
|
|
||||||
|
```
|
||||||
|
const todosUpdux = new Updux({
|
||||||
|
actions: {
|
||||||
|
add_todo,
|
||||||
|
todo_done,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
It can be done via the method `addAction`:
|
||||||
|
|
||||||
|
```
|
||||||
|
todosUpdux.addAction(add_todo);
|
||||||
|
```
|
||||||
|
|
||||||
|
Or it can be directly used in the definition of a mutation or effect, and will
|
||||||
|
be automatically added to the Updux.
|
||||||
|
|
||||||
|
```
|
||||||
|
todosUpdux.addMutation( add_todo, todoMutation );
|
||||||
|
```
|
||||||
|
|
||||||
|
For TypeScript projects I recommend declaring the actions as part of the
|
||||||
|
configuration passed to the constructors, as it makes them accessible to the class
|
||||||
|
at compile-time, and allow Updux to auto-add them to its aggregated `actions` type.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
const todosUpdux = new Updux({
|
||||||
|
actions: {
|
||||||
|
add_todo,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
todosUpdux.addAction(todo_done);
|
||||||
|
|
||||||
|
// only `add_todo` is visible to the type
|
||||||
|
type MyActions = typeof todosUpdux.actions;
|
||||||
|
// { add_todo: Function }
|
||||||
|
|
||||||
|
// but both actions are accessible at runtime
|
||||||
|
const myAction = ( todosUpdux.actions as any).todo_done(1);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accessing actions
|
||||||
|
|
||||||
|
Once an action is defined, its creator is accessible via the `actions` accessor.
|
||||||
|
|
||||||
|
```
|
||||||
|
console.log( todosUpdux.actions.add_todo('write tutorial') );
|
||||||
|
// { type: 'add_todo', payload: 'write tutorial' }
|
||||||
|
```
|
||||||
|
|
||||||
|
### What is an action?
|
||||||
|
|
||||||
|
In this tutorial we use `ts-action` for all the work, but under the hood Updux defines actions via
|
||||||
|
their creators, which are expected to be:
|
||||||
|
|
||||||
|
1. Functions,
|
||||||
|
2. returning a plain object of the format `{ type: string; payload?: unknown }`.
|
||||||
|
3. with an additional property `type`, which is also the action type.
|
||||||
|
|
||||||
|
For example, this is a perfectly cromulent action:
|
||||||
|
|
||||||
|
```
|
||||||
|
const add_todo = description => ({ type: 'add_todo', payload: description});
|
||||||
|
add_todo.type = 'add_todo';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mutations
|
||||||
|
|
||||||
|
Actions that don't do anything are not fun. The transformations typically
|
||||||
|
done by a Redux's reducer are called 'mutations' in Updux. A mutation is a
|
||||||
|
function with the following signature:
|
||||||
|
|
||||||
|
```
|
||||||
|
( payload, action ) => state => {
|
||||||
|
// ... stuff done here
|
||||||
|
return new_state;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The inversion and chaining of parameters from the usual Redux reducer's
|
||||||
|
signature is there to work with `updeep`'s curried nature. The expansion of
|
||||||
|
the usual `action` into `(payload, action)` is present because in most cases
|
||||||
|
`payload` is what we're interested in. So why not make it easily available?
|
||||||
|
|
||||||
|
### Adding a mutation
|
||||||
|
|
||||||
|
As for the actions, a mutation can be defined as part of the Updux
|
||||||
|
init arguments:
|
||||||
|
|
||||||
|
```
|
||||||
|
const add_todo_mutation = description => ({next_id: id, todos}) => {
|
||||||
|
return {
|
||||||
|
next_id: 1 + id,
|
||||||
|
todos: [...todos, { description, id, done: false }]
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const todosUpdux = new Updux({
|
||||||
|
actions: { add_todo },
|
||||||
|
mutations: [
|
||||||
|
[ add_todo, add_todo_mutation ]
|
||||||
|
]
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
or via the method `addMutation`:
|
||||||
|
|
||||||
|
```
|
||||||
|
todos.addMutation( add_todo, description => ({next_id: id, todos}) => {
|
||||||
|
return {
|
||||||
|
next_id: 1 + id,
|
||||||
|
todos: [...todos, { description, id, done: false }]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
This time around, if the project is using TypeScript then the addition of
|
||||||
|
mutations via `addMutation` is encouraged, as the method signature
|
||||||
|
has visibility of the types of the action and state.
|
||||||
|
|
||||||
|
### Leftover mutation
|
||||||
|
|
||||||
|
A mutation with the special action `*` will match any action that haven't been
|
||||||
|
explicitly dealt with with any other defined mutation.
|
||||||
|
|
||||||
|
```
|
||||||
|
todosUpdux.addMutation( '*', (payload,action) => state => {
|
||||||
|
console.log("hey, action has no mutation! ", action.type);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Effects
|
||||||
|
|
||||||
|
In addition of mutations, Updux also provide action-specific middleware, here
|
||||||
|
called effects.
|
||||||
|
|
||||||
|
Effects use the usual Redux middleware signature:
|
||||||
|
|
||||||
|
```
|
||||||
|
import u from 'updeep';
|
||||||
|
|
||||||
|
// 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 'add_todo'.
|
||||||
|
|
||||||
|
const add_todo_with_id = action('add_todo_with_id', payload<{description: string; id?: number}>() );
|
||||||
|
const inc_next_id = action('inc_next_id');
|
||||||
|
|
||||||
|
const populate_next_id = ({ getState, dispatch }) => next => action => {
|
||||||
|
const { next_id: id } = getState();
|
||||||
|
|
||||||
|
dispatch(inc_next_id());
|
||||||
|
next(action);
|
||||||
|
dispatch( add_todo_with_id({ description: action.payload, id }) );
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And just like mutations, they can be defined as part of the init
|
||||||
|
configuration, or after via the method `addEffect`:
|
||||||
|
|
||||||
|
```
|
||||||
|
const todosUpdux = new Updux({
|
||||||
|
actions: { add_todo, inc_next_id },
|
||||||
|
effects: [
|
||||||
|
[ add_todo, populate_next_id ]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```
|
||||||
|
const todosUpdux = new Updux({
|
||||||
|
actions: { add_todo, inc_next_id },
|
||||||
|
});
|
||||||
|
|
||||||
|
todosUpdux.addEffect( add_todo, populate_next_id );
|
||||||
|
```
|
||||||
|
|
||||||
|
As for the mutations, for TypeScript projects
|
||||||
|
the use of `addEffect` is prefered, as the method gives visibility of the
|
||||||
|
action and state types.
|
||||||
|
|
||||||
|
### 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);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Selectors
|
||||||
|
|
||||||
|
Selectors can be defined to get data derived from the state.
|
||||||
|
|
||||||
|
### Adding selectors
|
||||||
|
|
||||||
|
From now you should know the drill: selectors can be defined at construction
|
||||||
|
time or via `addSelector`.
|
||||||
|
|
||||||
|
```
|
||||||
|
import fp from 'lodash/fp';
|
||||||
|
|
||||||
|
const getTodoById = ({todos}) => id => fp.find({id},todos);
|
||||||
|
|
||||||
|
const todosUpdux = new Updux({
|
||||||
|
selectors: {
|
||||||
|
getTodoById
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```
|
||||||
|
todosUpdux.addSelector('getTodoById', ({todos}) => id => fp.find({id},todos));
|
||||||
|
```
|
||||||
|
|
||||||
|
Here the declaration as part of the constructor configuration is prefered.
|
||||||
|
Whereas the `addSelector` will provides the state's type as part of its
|
||||||
|
signature, declaring the selectors via the constructors will make them visible
|
||||||
|
via the type of the accessors `selectors`.
|
||||||
|
|
||||||
|
### Accessing selectors
|
||||||
|
|
||||||
|
Selectors are available via the accessor `selectors`.
|
||||||
|
|
||||||
|
```
|
||||||
|
const store = todosUpdux.createStore();
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
todosUpdux.selectors.getTodoById( store.getState() )(1)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Subduxes
|
||||||
|
|
||||||
|
Now that we have all the building blocks, we can embark on the last, and best,
|
||||||
|
part of Updux: its recursive nature.
|
||||||
|
|
||||||
|
### Recap: the Todos updux, 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 Updux we have so far:
|
||||||
|
|
||||||
|
```
|
||||||
|
import Updux from 'updux';
|
||||||
|
import { action, payload } from 'ts-action';
|
||||||
|
import u from 'updeep';
|
||||||
|
import fp from 'lodash/fp';
|
||||||
|
|
||||||
|
type Todo = {
|
||||||
|
id: number;
|
||||||
|
description: string;
|
||||||
|
done: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TodoStore = {
|
||||||
|
next_id: number;
|
||||||
|
todos: Todo[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const add_todo = action('add_todo', payload<string>() );
|
||||||
|
const add_todo_with_id = action('add_todo_with_id',
|
||||||
|
payload<{ description: string; id: number }>() );
|
||||||
|
const todo_done = action('todo_done', payload<number>() );
|
||||||
|
const inc_next_id = action('inc_next_id');
|
||||||
|
|
||||||
|
const todosUpdux = new Updux({
|
||||||
|
initial: {
|
||||||
|
next_id: 1,
|
||||||
|
todos: [],
|
||||||
|
} as TodoStore,
|
||||||
|
actions: {
|
||||||
|
add_todo,
|
||||||
|
add_todo_with_id,
|
||||||
|
todo_done,
|
||||||
|
inc_next_id,
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
getTodoById: ({todos}) => id => fp.find({id},todos)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
todosUpdux.addMutation( add_todo_with_id, payload =>
|
||||||
|
u.updateIn( 'todos', todos => [ ...todos, { ...payload, done: false }] )
|
||||||
|
);
|
||||||
|
|
||||||
|
todosUpdux.addMutation( inc_next_id, () => u({ next_id: i => i + 1 }) );
|
||||||
|
|
||||||
|
todosUpdux.addMutation( todo_done, id => u.updateIn(
|
||||||
|
'todos', u.map( u.if( fp.matches({id}), todo => u({done: true}, todo) ) )
|
||||||
|
) );
|
||||||
|
|
||||||
|
todosUpdux.addEffect( add_todo, ({ getState, dispatch }) => next => action => {
|
||||||
|
const { next_id: id } = getState();
|
||||||
|
|
||||||
|
dispatch(inc_next_id());
|
||||||
|
|
||||||
|
next(u.updateIn('payload', {id}, action))
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
This store has two main components: the `next_id`, and the `todos` collection.
|
||||||
|
The `todos` collection is itself composed of the individual `todo`s. So let's
|
||||||
|
create upduxes for each of those.
|
||||||
|
|
||||||
|
### Next_id updux
|
||||||
|
|
||||||
|
```
|
||||||
|
// dux/next_id.ts
|
||||||
|
|
||||||
|
import Updux from 'updux';
|
||||||
|
import { action, payload } from 'ts-action';
|
||||||
|
import u from 'updeep';
|
||||||
|
import fp from 'lodash/fp';
|
||||||
|
|
||||||
|
const inc_next_id = action('inc_next_id');
|
||||||
|
|
||||||
|
const updux = new Updux({
|
||||||
|
initial: 1,
|
||||||
|
actions: {
|
||||||
|
inc_next_id,
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
getNextId: state => state
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updux.addMutation( inc_next_id, () => fp.add(1) );
|
||||||
|
|
||||||
|
export default updux.asDux;
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice that here we didn't have to specify what is the type of `initial`;
|
||||||
|
TypeScript figures by itself that it's a number.
|
||||||
|
|
||||||
|
Also, note that we're exporting the output of the accessor `asDux` instead of
|
||||||
|
the updux object itself. See the upcoming section 'Exporting upduxes' for the rationale.
|
||||||
|
|
||||||
|
### Todo updux
|
||||||
|
|
||||||
|
```
|
||||||
|
// dux/todos/todo/index.ts
|
||||||
|
|
||||||
|
import Updux from 'updux';
|
||||||
|
import { action, payload } from 'ts-action';
|
||||||
|
import u from 'updeep';
|
||||||
|
import fp from 'lodash/fp';
|
||||||
|
|
||||||
|
type Todo = {
|
||||||
|
id: number;
|
||||||
|
description: string;
|
||||||
|
done: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const todo_done = action('todo_done', payload<number>() );
|
||||||
|
|
||||||
|
const updux = new Updux({
|
||||||
|
initial: {
|
||||||
|
next_id: 0,
|
||||||
|
description: "",
|
||||||
|
done: false,
|
||||||
|
} as Todo,
|
||||||
|
actions: {
|
||||||
|
todo_done
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updux.addMutation( todo_done, id => u.if( fp.matches({id}), { done: true }) );
|
||||||
|
|
||||||
|
export default updux.asDux;
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Todos updux
|
||||||
|
|
||||||
|
```
|
||||||
|
// dux/todos/index.ts
|
||||||
|
|
||||||
|
import Updux, { DuxState } from 'updux';
|
||||||
|
import { action, payload } from 'ts-action';
|
||||||
|
import u from 'updeep';
|
||||||
|
import fp from 'lodash/fp';
|
||||||
|
|
||||||
|
import todo from './todo';
|
||||||
|
|
||||||
|
type TodoState = DuxState<typeof todo>;
|
||||||
|
|
||||||
|
const add_todo_with_id = action('add_todo_with_id',
|
||||||
|
payload<{ description: string; id: number }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
const updux = new Updux({
|
||||||
|
initial: [] as Todo[],
|
||||||
|
subduxes: {
|
||||||
|
'*': todo.upreducer
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
add_todo_with_id,
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
getTodoById: state => id => fp.find({id},state)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
todosUpdux.addMutation( add_todo_with_id, payload =>
|
||||||
|
todos => [ ...todos, { ...payload, done: false }]
|
||||||
|
);
|
||||||
|
|
||||||
|
export default updux.asDux;
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
We could also have written the updux as:
|
||||||
|
|
||||||
|
```
|
||||||
|
const updux = new Updux({
|
||||||
|
initial: [] as Todo[],
|
||||||
|
actions: {
|
||||||
|
add_todo_with_id,
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
getTodoById: state => id => fp.find({id},state)
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
'*': (payload,action) => state => u.map( todo.reducer(state, action) )
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Note how we are using the `upreducer` accessor in the first case (which yields
|
||||||
|
a reducer for the dux using the signature `(payload,action) => state =>
|
||||||
|
new_state`) and `reducer` in the second case (which yield an equivalent
|
||||||
|
reducer using the classic signature `(state,action) => new_state`).
|
||||||
|
|
||||||
|
|
||||||
|
### Main store
|
||||||
|
|
||||||
|
```
|
||||||
|
// dux/index.ts
|
||||||
|
|
||||||
|
import Updux from 'updux';
|
||||||
|
|
||||||
|
import todos from './todos';
|
||||||
|
import next_id from './next_id';
|
||||||
|
|
||||||
|
const add_todo = action('add_todo', payload<string>() );
|
||||||
|
|
||||||
|
const updux = new Updux({
|
||||||
|
subduxes: {
|
||||||
|
next_id,
|
||||||
|
todos,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
add_todo
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
todos.addEffect( add_todo, ({ getState, dispatch }) => next => action => {
|
||||||
|
const id = updux.selectors.getNextId( getState() );
|
||||||
|
|
||||||
|
dispatch(updux.actions.inc_next_id());
|
||||||
|
|
||||||
|
next(action);
|
||||||
|
|
||||||
|
dispatch( updux.actions.add_todo_with_id({ description: action.payload, id }) );
|
||||||
|
});
|
||||||
|
|
||||||
|
export default updux.asDux;
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Tadah! We had to define the `add_todo` effect at the top level as it needs to
|
||||||
|
access the `getNextId` selector from `next_id` and the `add_todo_with_id`
|
||||||
|
action from the `todos`.
|
||||||
|
|
||||||
|
Note that the `getNextId` selector still get the
|
||||||
|
rigth value; when aggregating subduxes selectors Updux auto-wrap them to
|
||||||
|
access the right slice of the top object. I.e., the `getNextId` selector
|
||||||
|
at the main level is actually defined as:
|
||||||
|
|
||||||
|
```
|
||||||
|
const getNextId = state => next_id.selectors.getNextId(state.next_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Exporting upduxes
|
||||||
|
|
||||||
|
As a general rule, don't directly export your upduxes, but rather use the accessor `asDux`.
|
||||||
|
|
||||||
|
```
|
||||||
|
const updux = new Updux({ ... });
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
export default updux.asDux;
|
||||||
|
```
|
||||||
|
|
||||||
|
`asDux` returns an immutable copy of the attributes of the updux. Exporting
|
||||||
|
this instead of the updux itself prevents unexpected modifications done
|
||||||
|
outside of the updux declaration file. More importantly, the output of
|
||||||
|
`asDux` has more precise typing, which in result results in better typing of
|
||||||
|
parent upduxes using the dux as one of its subduxes.
|
||||||
|
|
||||||
|
[immer]: https://www.npmjs.com/package/immer
|
||||||
|
[lodash]: https://www.npmjs.com/package/lodash
|
||||||
|
[ts-action]: https://www.npmjs.com/package/ts-action
|
228
docs/typedoc/README.md
Normal file
228
docs/typedoc/README.md
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
[updux - v1.2.0](README.md) › [Globals](globals.md)
|
||||||
|
|
||||||
|
# updux - v1.2.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/docs/).
|
||||||
|
|
||||||
|
## 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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
491
docs/typedoc/classes/updux.md
Normal file
491
docs/typedoc/classes/updux.md
Normal file
@ -0,0 +1,491 @@
|
|||||||
|
[updux - v1.2.0](../README.md) › [Globals](../globals.md) › [Updux](updux.md)
|
||||||
|
|
||||||
|
# Class: Updux <**S, A, X, C**>
|
||||||
|
|
||||||
|
## Type parameters
|
||||||
|
|
||||||
|
▪ **S**
|
||||||
|
|
||||||
|
▪ **A**
|
||||||
|
|
||||||
|
▪ **X**
|
||||||
|
|
||||||
|
▪ **C**: *[UpduxConfig](../globals.md#upduxconfig)*
|
||||||
|
|
||||||
|
## Hierarchy
|
||||||
|
|
||||||
|
* **Updux**
|
||||||
|
|
||||||
|
## Index
|
||||||
|
|
||||||
|
### Constructors
|
||||||
|
|
||||||
|
* [constructor](updux.md#constructor)
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
* [coduxes](updux.md#coduxes)
|
||||||
|
* [groomMutations](updux.md#groommutations)
|
||||||
|
* [subduxes](updux.md#subduxes)
|
||||||
|
|
||||||
|
### Accessors
|
||||||
|
|
||||||
|
* [_middlewareEntries](updux.md#_middlewareentries)
|
||||||
|
* [actions](updux.md#actions)
|
||||||
|
* [asDux](updux.md#asdux)
|
||||||
|
* [createStore](updux.md#createstore)
|
||||||
|
* [initial](updux.md#initial)
|
||||||
|
* [middleware](updux.md#middleware)
|
||||||
|
* [mutations](updux.md#mutations)
|
||||||
|
* [reducer](updux.md#reducer)
|
||||||
|
* [selectors](updux.md#selectors)
|
||||||
|
* [subduxUpreducer](updux.md#subduxupreducer)
|
||||||
|
* [upreducer](updux.md#upreducer)
|
||||||
|
|
||||||
|
### Methods
|
||||||
|
|
||||||
|
* [addAction](updux.md#addaction)
|
||||||
|
* [addEffect](updux.md#addeffect)
|
||||||
|
* [addMutation](updux.md#addmutation)
|
||||||
|
* [addSelector](updux.md#addselector)
|
||||||
|
|
||||||
|
## Constructors
|
||||||
|
|
||||||
|
### constructor
|
||||||
|
|
||||||
|
\+ **new Updux**(`config`: C): *[Updux](updux.md)*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type | Default | Description |
|
||||||
|
------ | ------ | ------ | ------ |
|
||||||
|
`config` | C | {} as C | an [UpduxConfig](../globals.md#upduxconfig) plain object |
|
||||||
|
|
||||||
|
**Returns:** *[Updux](updux.md)*
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### coduxes
|
||||||
|
|
||||||
|
• **coduxes**: *[Dux](../globals.md#dux)[]*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### groomMutations
|
||||||
|
|
||||||
|
• **groomMutations**: *function*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
▸ (`mutation`: [Mutation](../globals.md#mutation)‹S›): *[Mutation](../globals.md#mutation)‹S›*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`mutation` | [Mutation](../globals.md#mutation)‹S› |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### subduxes
|
||||||
|
|
||||||
|
• **subduxes**: *[Dictionary](../globals.md#dictionary)‹[Dux](../globals.md#dux)›*
|
||||||
|
|
||||||
|
## Accessors
|
||||||
|
|
||||||
|
### _middlewareEntries
|
||||||
|
|
||||||
|
• **get _middlewareEntries**(): *any[]*
|
||||||
|
|
||||||
|
**Returns:** *any[]*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### actions
|
||||||
|
|
||||||
|
• **get actions**(): *[DuxActions](../globals.md#duxactions)‹A, C›*
|
||||||
|
|
||||||
|
Action creators for all actions defined or used in the actions, mutations, effects and subduxes
|
||||||
|
of the updux config.
|
||||||
|
|
||||||
|
Non-custom action creators defined in `actions` have the signature `(payload={},meta={}) => ({type,
|
||||||
|
payload,meta})` (with the extra sugar that if `meta` or `payload` are not
|
||||||
|
specified, that key won't be present in the produced action).
|
||||||
|
|
||||||
|
The same action creator can be included
|
||||||
|
in multiple subduxes. However, if two different creators
|
||||||
|
are included for the same action, an error will be thrown.
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
|
||||||
|
```
|
||||||
|
const actions = updux.actions;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** *[DuxActions](../globals.md#duxactions)‹A, C›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### asDux
|
||||||
|
|
||||||
|
• **get asDux**(): *object*
|
||||||
|
|
||||||
|
Returns a <a href="https://github.com/erikras/ducks-modular-redux">ducks</a>-like
|
||||||
|
plain object holding the reducer from the Updux object and all
|
||||||
|
its trimmings.
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
|
||||||
|
```
|
||||||
|
const {
|
||||||
|
createStore,
|
||||||
|
upreducer,
|
||||||
|
subduxes,
|
||||||
|
coduxes,
|
||||||
|
middleware,
|
||||||
|
actions,
|
||||||
|
reducer,
|
||||||
|
mutations,
|
||||||
|
initial,
|
||||||
|
selectors,
|
||||||
|
} = myUpdux.asDux;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** *object*
|
||||||
|
|
||||||
|
* **actions**: = this.actions
|
||||||
|
|
||||||
|
* **coduxes**: *object[]* = this.coduxes
|
||||||
|
|
||||||
|
* **createStore**(): *function*
|
||||||
|
|
||||||
|
* (`initial?`: S, `injectEnhancer?`: Function): *Store‹S› & object*
|
||||||
|
|
||||||
|
* **initial**: = this.initial
|
||||||
|
|
||||||
|
* **middleware**(): *function*
|
||||||
|
|
||||||
|
* (`api`: UpduxMiddlewareAPI‹S, X›): *function*
|
||||||
|
|
||||||
|
* (`next`: Function): *function*
|
||||||
|
|
||||||
|
* (`action`: A): *any*
|
||||||
|
|
||||||
|
* **mutations**(): *object*
|
||||||
|
|
||||||
|
* **reducer**(): *function*
|
||||||
|
|
||||||
|
* (`state`: S | undefined, `action`: [Action](../globals.md#action)): *S*
|
||||||
|
|
||||||
|
* **selectors**: = this.selectors
|
||||||
|
|
||||||
|
* **subduxes**(): *object*
|
||||||
|
|
||||||
|
* **upreducer**(): *function*
|
||||||
|
|
||||||
|
* (`action`: [Action](../globals.md#action)): *function*
|
||||||
|
|
||||||
|
* (`state`: S): *S*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### createStore
|
||||||
|
|
||||||
|
• **get createStore**(): *function*
|
||||||
|
|
||||||
|
Returns a `createStore` function that takes two argument:
|
||||||
|
`initial` and `injectEnhancer`. `initial` is a custom
|
||||||
|
initial state for the store, and `injectEnhancer` is a function
|
||||||
|
taking in the middleware built by the updux object and allowing
|
||||||
|
you to wrap it in any enhancer you want.
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
|
||||||
|
```
|
||||||
|
const createStore = updux.createStore;
|
||||||
|
|
||||||
|
const store = createStore(initial);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** *function*
|
||||||
|
|
||||||
|
▸ (`initial?`: S, `injectEnhancer?`: Function): *Store‹S› & object*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`initial?` | S |
|
||||||
|
`injectEnhancer?` | Function |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### initial
|
||||||
|
|
||||||
|
• **get initial**(): *AggDuxState‹S, C›*
|
||||||
|
|
||||||
|
**Returns:** *AggDuxState‹S, C›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### middleware
|
||||||
|
|
||||||
|
• **get middleware**(): *[UpduxMiddleware](../globals.md#upduxmiddleware)‹AggDuxState‹S, C›, [DuxSelectors](../globals.md#duxselectors)‹AggDuxState‹S, C›, X, C››*
|
||||||
|
|
||||||
|
Array of middlewares aggregating all the effects defined in the
|
||||||
|
updux and its subduxes. Effects of the updux itself are
|
||||||
|
done before the subduxes effects.
|
||||||
|
Note that `getState` will always return the state of the
|
||||||
|
local updux.
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
|
||||||
|
```
|
||||||
|
const middleware = updux.middleware;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** *[UpduxMiddleware](../globals.md#upduxmiddleware)‹AggDuxState‹S, C›, [DuxSelectors](../globals.md#duxselectors)‹AggDuxState‹S, C›, X, C››*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### mutations
|
||||||
|
|
||||||
|
• **get mutations**(): *[Dictionary](../globals.md#dictionary)‹[Mutation](../globals.md#mutation)‹S››*
|
||||||
|
|
||||||
|
Merge of the updux and subduxes mutations. If an action triggers
|
||||||
|
mutations in both the main updux and its subduxes, the subduxes
|
||||||
|
mutations will be performed first.
|
||||||
|
|
||||||
|
**Returns:** *[Dictionary](../globals.md#dictionary)‹[Mutation](../globals.md#mutation)‹S››*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### reducer
|
||||||
|
|
||||||
|
• **get reducer**(): *function*
|
||||||
|
|
||||||
|
A Redux reducer generated using the computed initial state and
|
||||||
|
mutations.
|
||||||
|
|
||||||
|
**Returns:** *function*
|
||||||
|
|
||||||
|
▸ (`state`: S | undefined, `action`: [Action](../globals.md#action)): *S*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`state` | S | undefined |
|
||||||
|
`action` | [Action](../globals.md#action) |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### selectors
|
||||||
|
|
||||||
|
• **get selectors**(): *[DuxSelectors](../globals.md#duxselectors)‹AggDuxState‹S, C›, X, C›*
|
||||||
|
|
||||||
|
A dictionary of the updux's selectors. Subduxes'
|
||||||
|
selectors are included as well (with the mapping to the
|
||||||
|
sub-state already taken care of you).
|
||||||
|
|
||||||
|
**Returns:** *[DuxSelectors](../globals.md#duxselectors)‹AggDuxState‹S, C›, X, C›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### subduxUpreducer
|
||||||
|
|
||||||
|
• **get subduxUpreducer**(): *function*
|
||||||
|
|
||||||
|
Returns the upreducer made of the merge of all sudbuxes reducers, without
|
||||||
|
the local mutations. Useful, for example, for sink mutations.
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
|
||||||
|
```
|
||||||
|
import todo from './todo'; // updux for a single todo
|
||||||
|
import Updux from 'updux';
|
||||||
|
import u from 'updeep';
|
||||||
|
|
||||||
|
const todos = new Updux({ initial: [], subduxes: { '*': todo } });
|
||||||
|
todos.addMutation(
|
||||||
|
todo.actions.done,
|
||||||
|
({todo_id},action) => u.map( u.if( u.is('id',todo_id) ), todos.subduxUpreducer(action) )
|
||||||
|
true
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** *function*
|
||||||
|
|
||||||
|
▸ (`action`: [Action](../globals.md#action)): *function*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`action` | [Action](../globals.md#action) |
|
||||||
|
|
||||||
|
▸ (`state`: S): *S*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`state` | S |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### upreducer
|
||||||
|
|
||||||
|
• **get upreducer**(): *[Upreducer](../globals.md#upreducer)‹S›*
|
||||||
|
|
||||||
|
**Returns:** *[Upreducer](../globals.md#upreducer)‹S›*
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### addAction
|
||||||
|
|
||||||
|
▸ **addAction**(`theaction`: string, `transform?`: any): *ActionCreator‹string, any›*
|
||||||
|
|
||||||
|
Adds an action to the updux. It can take an already defined action
|
||||||
|
creator, or any arguments that can be passed to `actionCreator`.
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
```
|
||||||
|
const action = updux.addAction( name, ...creatorArgs );
|
||||||
|
const action = updux.addAction( otherActionCreator );
|
||||||
|
```
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
```
|
||||||
|
import {actionCreator, Updux} from 'updux';
|
||||||
|
|
||||||
|
const updux = new Updux();
|
||||||
|
|
||||||
|
const foo = updux.addAction('foo');
|
||||||
|
const bar = updux.addAction( 'bar', (x) => ({stuff: x+1}) );
|
||||||
|
|
||||||
|
const baz = actionCreator( 'baz' );
|
||||||
|
|
||||||
|
foo({ a: 1}); // => { type: 'foo', payload: { a: 1 } }
|
||||||
|
bar(2); // => { type: 'bar', payload: { stuff: 3 } }
|
||||||
|
baz(); // => { type: 'baz', payload: undefined }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`theaction` | string |
|
||||||
|
`transform?` | any |
|
||||||
|
|
||||||
|
**Returns:** *ActionCreator‹string, any›*
|
||||||
|
|
||||||
|
▸ **addAction**(`theaction`: string | ActionCreator‹any›, `transform?`: undefined): *ActionCreator‹string, any›*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`theaction` | string | ActionCreator‹any› |
|
||||||
|
`transform?` | undefined |
|
||||||
|
|
||||||
|
**Returns:** *ActionCreator‹string, any›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### addEffect
|
||||||
|
|
||||||
|
▸ **addEffect**<**AC**>(`creator`: AC, `middleware`: [UpduxMiddleware](../globals.md#upduxmiddleware)‹AggDuxState‹S, C›, [DuxSelectors](../globals.md#duxselectors)‹AggDuxState‹S, C›, X, C›, ReturnType‹AC››, `isGenerator?`: undefined | false | true): *any*
|
||||||
|
|
||||||
|
**Type parameters:**
|
||||||
|
|
||||||
|
▪ **AC**: *ActionCreator*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`creator` | AC |
|
||||||
|
`middleware` | [UpduxMiddleware](../globals.md#upduxmiddleware)‹AggDuxState‹S, C›, [DuxSelectors](../globals.md#duxselectors)‹AggDuxState‹S, C›, X, C›, ReturnType‹AC›› |
|
||||||
|
`isGenerator?` | undefined | false | true |
|
||||||
|
|
||||||
|
**Returns:** *any*
|
||||||
|
|
||||||
|
▸ **addEffect**(`creator`: string, `middleware`: [UpduxMiddleware](../globals.md#upduxmiddleware)‹AggDuxState‹S, C›, [DuxSelectors](../globals.md#duxselectors)‹AggDuxState‹S, C›, X, C››, `isGenerator?`: undefined | false | true): *any*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`creator` | string |
|
||||||
|
`middleware` | [UpduxMiddleware](../globals.md#upduxmiddleware)‹AggDuxState‹S, C›, [DuxSelectors](../globals.md#duxselectors)‹AggDuxState‹S, C›, X, C›› |
|
||||||
|
`isGenerator?` | undefined | false | true |
|
||||||
|
|
||||||
|
**Returns:** *any*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### addMutation
|
||||||
|
|
||||||
|
▸ **addMutation**<**A**>(`creator`: A, `mutation`: [Mutation](../globals.md#mutation)‹S, ActionType‹A››, `isSink?`: undefined | false | true): *any*
|
||||||
|
|
||||||
|
Adds a mutation and its associated action to the updux.
|
||||||
|
|
||||||
|
**`remarks`**
|
||||||
|
|
||||||
|
If a local mutation was already associated to the action,
|
||||||
|
it will be replaced by the new one.
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
|
||||||
|
```js
|
||||||
|
updux.addMutation(
|
||||||
|
action('ADD', payload<int>() ),
|
||||||
|
inc => state => state + in
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Type parameters:**
|
||||||
|
|
||||||
|
▪ **A**: *ActionCreator*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type | Description |
|
||||||
|
------ | ------ | ------ |
|
||||||
|
`creator` | A | - |
|
||||||
|
`mutation` | [Mutation](../globals.md#mutation)‹S, ActionType‹A›› | - |
|
||||||
|
`isSink?` | undefined | false | true | If `true`, disables the subduxes mutations for this action. To conditionally run the subduxes mutations, check out [subduxUpreducer](updux.md#subduxupreducer). Defaults to `false`. |
|
||||||
|
|
||||||
|
**Returns:** *any*
|
||||||
|
|
||||||
|
▸ **addMutation**<**A**>(`creator`: string, `mutation`: [Mutation](../globals.md#mutation)‹S, any›, `isSink?`: undefined | false | true): *any*
|
||||||
|
|
||||||
|
**Type parameters:**
|
||||||
|
|
||||||
|
▪ **A**: *ActionCreator*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`creator` | string |
|
||||||
|
`mutation` | [Mutation](../globals.md#mutation)‹S, any› |
|
||||||
|
`isSink?` | undefined | false | true |
|
||||||
|
|
||||||
|
**Returns:** *any*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### addSelector
|
||||||
|
|
||||||
|
▸ **addSelector**(`name`: string, `selector`: [Selector](../globals.md#selector)): *void*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`name` | string |
|
||||||
|
`selector` | [Selector](../globals.md#selector) |
|
||||||
|
|
||||||
|
**Returns:** *void*
|
980
docs/typedoc/globals.md
Normal file
980
docs/typedoc/globals.md
Normal file
@ -0,0 +1,980 @@
|
|||||||
|
[updux - v1.2.0](README.md) › [Globals](globals.md)
|
||||||
|
|
||||||
|
# updux - v1.2.0
|
||||||
|
|
||||||
|
## Index
|
||||||
|
|
||||||
|
### Classes
|
||||||
|
|
||||||
|
* [Updux](classes/updux.md)
|
||||||
|
|
||||||
|
### Type aliases
|
||||||
|
|
||||||
|
* [Action](globals.md#action)
|
||||||
|
* [ActionPair](globals.md#actionpair)
|
||||||
|
* [ActionPayloadGenerator](globals.md#actionpayloadgenerator)
|
||||||
|
* [ActionsOf](globals.md#actionsof)
|
||||||
|
* [CoduxesOf](globals.md#coduxesof)
|
||||||
|
* [Dictionary](globals.md#dictionary)
|
||||||
|
* [Dux](globals.md#dux)
|
||||||
|
* [DuxActions](globals.md#duxactions)
|
||||||
|
* [DuxActionsCoduxes](globals.md#duxactionscoduxes)
|
||||||
|
* [DuxActionsSubduxes](globals.md#duxactionssubduxes)
|
||||||
|
* [DuxSelectors](globals.md#duxselectors)
|
||||||
|
* [DuxState](globals.md#duxstate)
|
||||||
|
* [DuxStateCoduxes](globals.md#duxstatecoduxes)
|
||||||
|
* [DuxStateGlobSub](globals.md#duxstateglobsub)
|
||||||
|
* [DuxStateSubduxes](globals.md#duxstatesubduxes)
|
||||||
|
* [Effect](globals.md#effect)
|
||||||
|
* [GenericActions](globals.md#genericactions)
|
||||||
|
* [ItemsOf](globals.md#itemsof)
|
||||||
|
* [LocalDuxState](globals.md#localduxstate)
|
||||||
|
* [MaybePayload](globals.md#maybepayload)
|
||||||
|
* [MaybeReturnType](globals.md#maybereturntype)
|
||||||
|
* [Merge](globals.md#merge)
|
||||||
|
* [Mutation](globals.md#mutation)
|
||||||
|
* [MutationEntry](globals.md#mutationentry)
|
||||||
|
* [MwGen](globals.md#mwgen)
|
||||||
|
* [Next](globals.md#next)
|
||||||
|
* [RebaseSelector](globals.md#rebaseselector)
|
||||||
|
* [Selector](globals.md#selector)
|
||||||
|
* [SelectorsOf](globals.md#selectorsof)
|
||||||
|
* [StateOf](globals.md#stateof)
|
||||||
|
* [StoreWithDispatchActions](globals.md#storewithdispatchactions)
|
||||||
|
* [SubMutations](globals.md#submutations)
|
||||||
|
* [Submws](globals.md#submws)
|
||||||
|
* [UnionToIntersection](globals.md#uniontointersection)
|
||||||
|
* [UpduxActions](globals.md#upduxactions)
|
||||||
|
* [UpduxConfig](globals.md#upduxconfig)
|
||||||
|
* [UpduxLocalActions](globals.md#upduxlocalactions)
|
||||||
|
* [UpduxMiddleware](globals.md#upduxmiddleware)
|
||||||
|
* [Upreducer](globals.md#upreducer)
|
||||||
|
|
||||||
|
### Variables
|
||||||
|
|
||||||
|
* [subEffects](globals.md#const-subeffects)
|
||||||
|
* [updux](globals.md#const-updux)
|
||||||
|
|
||||||
|
### Functions
|
||||||
|
|
||||||
|
* [MiddlewareFor](globals.md#const-middlewarefor)
|
||||||
|
* [buildActions](globals.md#buildactions)
|
||||||
|
* [buildCreateStore](globals.md#buildcreatestore)
|
||||||
|
* [buildInitial](globals.md#buildinitial)
|
||||||
|
* [buildMiddleware](globals.md#buildmiddleware)
|
||||||
|
* [buildMutations](globals.md#buildmutations)
|
||||||
|
* [buildSelectors](globals.md#buildselectors)
|
||||||
|
* [buildUpreducer](globals.md#buildupreducer)
|
||||||
|
* [coduxes](globals.md#const-coduxes)
|
||||||
|
* [composeMutations](globals.md#const-composemutations)
|
||||||
|
* [composeMw](globals.md#const-composemw)
|
||||||
|
* [dux](globals.md#const-dux)
|
||||||
|
* [effectToMw](globals.md#const-effecttomw)
|
||||||
|
* [sliceMw](globals.md#slicemw)
|
||||||
|
* [subMiddleware](globals.md#const-submiddleware)
|
||||||
|
* [subSelectors](globals.md#subselectors)
|
||||||
|
|
||||||
|
## Type aliases
|
||||||
|
|
||||||
|
### Action
|
||||||
|
|
||||||
|
Ƭ **Action**: *object & [MaybePayload](globals.md#maybepayload)‹P›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### ActionPair
|
||||||
|
|
||||||
|
Ƭ **ActionPair**: *[string, ActionCreator]*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### ActionPayloadGenerator
|
||||||
|
|
||||||
|
Ƭ **ActionPayloadGenerator**: *function*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
▸ (...`args`: any[]): *any*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`...args` | any[] |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### ActionsOf
|
||||||
|
|
||||||
|
Ƭ **ActionsOf**: *U extends Updux ? U["actions"] : object*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### CoduxesOf
|
||||||
|
|
||||||
|
Ƭ **CoduxesOf**: *U extends Updux<any, any, any, infer S> ? S : []*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Dictionary
|
||||||
|
|
||||||
|
Ƭ **Dictionary**: *object*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
* \[ **key**: *string*\]: T
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Dux
|
||||||
|
|
||||||
|
Ƭ **Dux**: *object*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
* **actions**: *A*
|
||||||
|
|
||||||
|
* **coduxes**: *[Dux](globals.md#dux)[]*
|
||||||
|
|
||||||
|
* **initial**: *AggDuxState‹S, C›*
|
||||||
|
|
||||||
|
* **subduxes**: *[Dictionary](globals.md#dictionary)‹[Dux](globals.md#dux)›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### DuxActions
|
||||||
|
|
||||||
|
Ƭ **DuxActions**:
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### DuxActionsCoduxes
|
||||||
|
|
||||||
|
Ƭ **DuxActionsCoduxes**: *C extends Array<infer I> ? UnionToIntersection<ActionsOf<I>> : object*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### DuxActionsSubduxes
|
||||||
|
|
||||||
|
Ƭ **DuxActionsSubduxes**: *C extends object ? ActionsOf<C[keyof C]> : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### DuxSelectors
|
||||||
|
|
||||||
|
Ƭ **DuxSelectors**: *unknown extends X ? object : X*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### DuxState
|
||||||
|
|
||||||
|
Ƭ **DuxState**: *D extends object ? S : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### DuxStateCoduxes
|
||||||
|
|
||||||
|
Ƭ **DuxStateCoduxes**: *C extends Array<infer U> ? UnionToIntersection<StateOf<U>> : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### DuxStateGlobSub
|
||||||
|
|
||||||
|
Ƭ **DuxStateGlobSub**: *S extends object ? StateOf<I> : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### DuxStateSubduxes
|
||||||
|
|
||||||
|
Ƭ **DuxStateSubduxes**: *C extends object ? object : C extends object ? object : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Effect
|
||||||
|
|
||||||
|
Ƭ **Effect**: *[string, [UpduxMiddleware](globals.md#upduxmiddleware) | [MwGen](globals.md#mwgen), undefined | false | true]*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### GenericActions
|
||||||
|
|
||||||
|
Ƭ **GenericActions**: *[Dictionary](globals.md#dictionary)‹ActionCreator‹string, function››*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### ItemsOf
|
||||||
|
|
||||||
|
Ƭ **ItemsOf**: *C extends object ? C[keyof C] : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### LocalDuxState
|
||||||
|
|
||||||
|
Ƭ **LocalDuxState**: *S extends never[] ? unknown[] : S*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### MaybePayload
|
||||||
|
|
||||||
|
Ƭ **MaybePayload**: *P extends object | string | boolean | number ? object : object*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### MaybeReturnType
|
||||||
|
|
||||||
|
Ƭ **MaybeReturnType**: *X extends function ? ReturnType<X> : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Merge
|
||||||
|
|
||||||
|
Ƭ **Merge**: *[UnionToIntersection](globals.md#uniontointersection)‹T[keyof T]›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Mutation
|
||||||
|
|
||||||
|
Ƭ **Mutation**: *function*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
▸ (`payload`: A["payload"], `action`: A): *function*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`payload` | A["payload"] |
|
||||||
|
`action` | A |
|
||||||
|
|
||||||
|
▸ (`state`: S): *S*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`state` | S |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### MutationEntry
|
||||||
|
|
||||||
|
Ƭ **MutationEntry**: *[ActionCreator | string, [Mutation](globals.md#mutation)‹any, [Action](globals.md#action)‹string, any››, undefined | false | true]*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### MwGen
|
||||||
|
|
||||||
|
Ƭ **MwGen**: *function*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
▸ (): *[UpduxMiddleware](globals.md#upduxmiddleware)*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Next
|
||||||
|
|
||||||
|
Ƭ **Next**: *function*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
▸ (`action`: [Action](globals.md#action)): *any*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`action` | [Action](globals.md#action) |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### RebaseSelector
|
||||||
|
|
||||||
|
Ƭ **RebaseSelector**: *object*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Selector
|
||||||
|
|
||||||
|
Ƭ **Selector**: *function*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
▸ (`state`: S): *unknown*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`state` | S |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### SelectorsOf
|
||||||
|
|
||||||
|
Ƭ **SelectorsOf**: *C extends object ? S : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### StateOf
|
||||||
|
|
||||||
|
Ƭ **StateOf**: *D extends object ? I : unknown*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### StoreWithDispatchActions
|
||||||
|
|
||||||
|
Ƭ **StoreWithDispatchActions**: *Store‹S› & object*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### SubMutations
|
||||||
|
|
||||||
|
Ƭ **SubMutations**: *object*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
* \[ **slice**: *string*\]: [Dictionary](globals.md#dictionary)‹[Mutation](globals.md#mutation)›
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Submws
|
||||||
|
|
||||||
|
Ƭ **Submws**: *[Dictionary](globals.md#dictionary)‹[UpduxMiddleware](globals.md#upduxmiddleware)›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### UnionToIntersection
|
||||||
|
|
||||||
|
Ƭ **UnionToIntersection**: *U extends any ? function : never extends function ? I : never*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### UpduxActions
|
||||||
|
|
||||||
|
Ƭ **UpduxActions**: *U extends Updux ? UnionToIntersection<UpduxLocalActions<U> | ActionsOf<CoduxesOf<U>[keyof CoduxesOf<U>]>> : object*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### UpduxConfig
|
||||||
|
|
||||||
|
Ƭ **UpduxConfig**: *Partial‹object›*
|
||||||
|
|
||||||
|
Configuration object given to Updux's constructor.
|
||||||
|
|
||||||
|
#### arguments
|
||||||
|
|
||||||
|
##### initial
|
||||||
|
|
||||||
|
Default initial state of the reducer. If applicable, is merged with
|
||||||
|
the subduxes initial states, with the parent having precedence.
|
||||||
|
|
||||||
|
If not provided, defaults to an empty object.
|
||||||
|
|
||||||
|
##### actions
|
||||||
|
|
||||||
|
[Actions](/concepts/Actions) used by the updux.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { dux } from 'updux';
|
||||||
|
import { action, payload } from 'ts-action';
|
||||||
|
|
||||||
|
const bar = action('BAR', payload<int>());
|
||||||
|
const foo = action('FOO');
|
||||||
|
|
||||||
|
const myDux = dux({
|
||||||
|
actions: {
|
||||||
|
bar
|
||||||
|
},
|
||||||
|
mutations: [
|
||||||
|
[ foo, () => state => state ]
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
myDux.actions.foo({ x: 1, y: 2 }); // => { type: foo, x:1, y:2 }
|
||||||
|
myDux.actions.bar(2); // => { type: bar, payload: 2 }
|
||||||
|
```
|
||||||
|
|
||||||
|
New actions used directly in mutations and effects will be added to the
|
||||||
|
dux actions -- that is, they will be accessible via `dux.actions` -- but will
|
||||||
|
not appear as part of its Typescript type.
|
||||||
|
|
||||||
|
##### selectors
|
||||||
|
|
||||||
|
Dictionary of selectors for the current updux. The updux also
|
||||||
|
inherit its subduxes' selectors.
|
||||||
|
|
||||||
|
The selectors are available via the class' getter.
|
||||||
|
|
||||||
|
##### mutations
|
||||||
|
|
||||||
|
mutations: [
|
||||||
|
[ action, mutation, isSink ],
|
||||||
|
...
|
||||||
|
]
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
mutations: {
|
||||||
|
action: mutation,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
List of mutations for assign to the dux. If you want Typescript goodness, you
|
||||||
|
probably want to use `addMutation()` instead.
|
||||||
|
|
||||||
|
In its generic array-of-array form,
|
||||||
|
each mutation tuple contains: the action, the mutation,
|
||||||
|
and boolean indicating if this is a sink mutation.
|
||||||
|
|
||||||
|
The action can be an action creator function or a string. If it's a string, it's considered to be the
|
||||||
|
action type and a generic `action( actionName, payload() )` creator will be
|
||||||
|
generated for it. If an action is not already defined in the `actions`
|
||||||
|
parameter, it'll be automatically added.
|
||||||
|
|
||||||
|
The pseudo-action type `*` can be used to match any action not explicitly matched by other mutations.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const todosUpdux = updux({
|
||||||
|
mutations: {
|
||||||
|
add: todo => state => [ ...state, todo ],
|
||||||
|
done: done_id => u.map( u.if( ({id} => id === done_id), {done: true} ) ),
|
||||||
|
'*' (payload,action) => state => {
|
||||||
|
console.warn( "unexpected action ", action.type );
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The signature of the mutations is `(payload,action) => state => newState`.
|
||||||
|
It is designed to play well with `Updeep` (and [Immer](https://immerjs.github.io/immer/docs/introduction)). This way, instead of doing
|
||||||
|
|
||||||
|
```js
|
||||||
|
mutation: {
|
||||||
|
renameTodo: newName => state => { ...state, name: newName }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
we can do
|
||||||
|
|
||||||
|
```js
|
||||||
|
mutation: {
|
||||||
|
renameTodo: newName => u({ name: newName })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The final argument is the optional boolean `isSink`. If it is true, it'll
|
||||||
|
prevent subduxes' mutations on the same action. It defaults to `false`.
|
||||||
|
|
||||||
|
The object version of the argument can be used as a shortcut when all actions
|
||||||
|
are strings. In that case, `isSink` is `false` for all mutations.
|
||||||
|
|
||||||
|
##### groomMutations
|
||||||
|
|
||||||
|
Function that can be provided to alter all local mutations of the updux
|
||||||
|
(the mutations of subduxes are left untouched).
|
||||||
|
|
||||||
|
Can be used, for example, for Immer integration:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import Updux from 'updux';
|
||||||
|
import { produce } from 'Immer';
|
||||||
|
|
||||||
|
const updux = new Updux({
|
||||||
|
initial: { counter: 0 },
|
||||||
|
groomMutations: mutation => (...args) => produce( mutation(...args) ),
|
||||||
|
mutations: {
|
||||||
|
add: (inc=1) => draft => draft.counter += inc
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Or perhaps for debugging:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import Updux from 'updux';
|
||||||
|
|
||||||
|
const updux = new Updux({
|
||||||
|
initial: { counter: 0 },
|
||||||
|
groomMutations: mutation => (...args) => state => {
|
||||||
|
console.log( "got action ", args[1] );
|
||||||
|
return mutation(...args)(state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
##### subduxes
|
||||||
|
|
||||||
|
Object mapping slices of the state to sub-upduxes. In addition to creating
|
||||||
|
sub-reducers for those slices, it'll make the parend updux inherit all the
|
||||||
|
actions and middleware from its subduxes.
|
||||||
|
|
||||||
|
For example, if in plain Redux you would do
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { combineReducers } from 'redux';
|
||||||
|
import todosReducer from './todos';
|
||||||
|
import statisticsReducer from './statistics';
|
||||||
|
|
||||||
|
const rootReducer = combineReducers({
|
||||||
|
todos: todosReducer,
|
||||||
|
stats: statisticsReducer,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
then with Updux you'd do
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { updux } from 'updux';
|
||||||
|
import todos from './todos';
|
||||||
|
import statistics from './statistics';
|
||||||
|
|
||||||
|
const rootUpdux = updux({
|
||||||
|
subduxes: {
|
||||||
|
todos,
|
||||||
|
statistics
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
##### effects
|
||||||
|
|
||||||
|
Array of arrays or plain object defining asynchronous actions and side-effects triggered by actions.
|
||||||
|
The effects themselves are Redux middleware, with the `dispatch`
|
||||||
|
property of the first argument augmented with all the available actions.
|
||||||
|
|
||||||
|
```
|
||||||
|
updux({
|
||||||
|
effects: {
|
||||||
|
fetch: ({dispatch}) => next => async (action) => {
|
||||||
|
next(action);
|
||||||
|
|
||||||
|
let result = await fetch(action.payload.url).then( result => result.json() );
|
||||||
|
dispatch.fetchSuccess(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**`example`**
|
||||||
|
|
||||||
|
```
|
||||||
|
import Updux from 'updux';
|
||||||
|
import { actions, payload } from 'ts-action';
|
||||||
|
import u from 'updeep';
|
||||||
|
|
||||||
|
const todoUpdux = new Updux({
|
||||||
|
initial: {
|
||||||
|
done: false,
|
||||||
|
note: "",
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
finish: action('FINISH', payload()),
|
||||||
|
edit: action('EDIT', payload()),
|
||||||
|
},
|
||||||
|
mutations: [
|
||||||
|
[ edit, note => u({note}) ]
|
||||||
|
],
|
||||||
|
selectors: {
|
||||||
|
getNote: state => state.note
|
||||||
|
},
|
||||||
|
groomMutations: mutation => transform(mutation),
|
||||||
|
subduxes: {
|
||||||
|
foo
|
||||||
|
},
|
||||||
|
effects: {
|
||||||
|
finish: () => next => action => {
|
||||||
|
console.log( "Woo! one more bites the dust" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### UpduxLocalActions
|
||||||
|
|
||||||
|
Ƭ **UpduxLocalActions**: *S extends Updux<any, null> ? object : S extends Updux<any, infer A> ? A : object*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### UpduxMiddleware
|
||||||
|
|
||||||
|
Ƭ **UpduxMiddleware**: *function*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
▸ (`api`: UpduxMiddlewareAPI‹S, X›): *function*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`api` | UpduxMiddlewareAPI‹S, X› |
|
||||||
|
|
||||||
|
▸ (`next`: Function): *function*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`next` | Function |
|
||||||
|
|
||||||
|
▸ (`action`: A): *any*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`action` | A |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### Upreducer
|
||||||
|
|
||||||
|
Ƭ **Upreducer**: *function*
|
||||||
|
|
||||||
|
#### Type declaration:
|
||||||
|
|
||||||
|
▸ (`action`: [Action](globals.md#action)): *function*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`action` | [Action](globals.md#action) |
|
||||||
|
|
||||||
|
▸ (`state`: S): *S*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`state` | S |
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
### `Const` subEffects
|
||||||
|
|
||||||
|
• **subEffects**: *[Effect](globals.md#effect)* = [ '*', subMiddleware ] as any
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### `Const` updux
|
||||||
|
|
||||||
|
• **updux**: *[Updux](classes/updux.md)‹unknown, null, unknown, object›* = new Updux({
|
||||||
|
subduxes: {
|
||||||
|
foo: dux({ initial: "banana" })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
### `Const` MiddlewareFor
|
||||||
|
|
||||||
|
▸ **MiddlewareFor**(`type`: any, `mw`: Middleware): *Middleware*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`type` | any |
|
||||||
|
`mw` | Middleware |
|
||||||
|
|
||||||
|
**Returns:** *Middleware*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### buildActions
|
||||||
|
|
||||||
|
▸ **buildActions**(`actions`: [ActionPair](globals.md#actionpair)[]): *[Dictionary](globals.md#dictionary)‹ActionCreator‹string, function››*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type | Default |
|
||||||
|
------ | ------ | ------ |
|
||||||
|
`actions` | [ActionPair](globals.md#actionpair)[] | [] |
|
||||||
|
|
||||||
|
**Returns:** *[Dictionary](globals.md#dictionary)‹ActionCreator‹string, function››*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### buildCreateStore
|
||||||
|
|
||||||
|
▸ **buildCreateStore**<**S**, **A**>(`reducer`: Reducer‹S›, `middleware`: Middleware, `actions`: A): *function*
|
||||||
|
|
||||||
|
**Type parameters:**
|
||||||
|
|
||||||
|
▪ **S**
|
||||||
|
|
||||||
|
▪ **A**
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type | Default |
|
||||||
|
------ | ------ | ------ |
|
||||||
|
`reducer` | Reducer‹S› | - |
|
||||||
|
`middleware` | Middleware | - |
|
||||||
|
`actions` | A | {} as A |
|
||||||
|
|
||||||
|
**Returns:** *function*
|
||||||
|
|
||||||
|
▸ (`initial?`: S, `injectEnhancer?`: Function): *Store‹S› & object*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`initial?` | S |
|
||||||
|
`injectEnhancer?` | Function |
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### buildInitial
|
||||||
|
|
||||||
|
▸ **buildInitial**(`initial`: any, `coduxes`: any, `subduxes`: any): *any*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type | Default |
|
||||||
|
------ | ------ | ------ |
|
||||||
|
`initial` | any | - |
|
||||||
|
`coduxes` | any | [] |
|
||||||
|
`subduxes` | any | {} |
|
||||||
|
|
||||||
|
**Returns:** *any*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### buildMiddleware
|
||||||
|
|
||||||
|
▸ **buildMiddleware**<**S**>(`local`: [UpduxMiddleware](globals.md#upduxmiddleware)[], `co`: [UpduxMiddleware](globals.md#upduxmiddleware)[], `sub`: [Submws](globals.md#submws)): *[UpduxMiddleware](globals.md#upduxmiddleware)‹S›*
|
||||||
|
|
||||||
|
**Type parameters:**
|
||||||
|
|
||||||
|
▪ **S**
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type | Default |
|
||||||
|
------ | ------ | ------ |
|
||||||
|
`local` | [UpduxMiddleware](globals.md#upduxmiddleware)[] | [] |
|
||||||
|
`co` | [UpduxMiddleware](globals.md#upduxmiddleware)[] | [] |
|
||||||
|
`sub` | [Submws](globals.md#submws) | {} |
|
||||||
|
|
||||||
|
**Returns:** *[UpduxMiddleware](globals.md#upduxmiddleware)‹S›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### buildMutations
|
||||||
|
|
||||||
|
▸ **buildMutations**(`mutations`: [Dictionary](globals.md#dictionary)‹[Mutation](globals.md#mutation) | [[Mutation](globals.md#mutation), boolean | undefined]›, `subduxes`: object, `coduxes`: [Upreducer](globals.md#upreducer)[]): *object*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type | Default |
|
||||||
|
------ | ------ | ------ |
|
||||||
|
`mutations` | [Dictionary](globals.md#dictionary)‹[Mutation](globals.md#mutation) | [[Mutation](globals.md#mutation), boolean | undefined]› | {} |
|
||||||
|
`subduxes` | object | {} |
|
||||||
|
`coduxes` | [Upreducer](globals.md#upreducer)[] | [] |
|
||||||
|
|
||||||
|
**Returns:** *object*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### buildSelectors
|
||||||
|
|
||||||
|
▸ **buildSelectors**(`localSelectors`: [Dictionary](globals.md#dictionary)‹[Selector](globals.md#selector)›, `coduxes`: [Dictionary](globals.md#dictionary)‹[Selector](globals.md#selector)›[], `subduxes`: [Dictionary](globals.md#dictionary)‹[Selector](globals.md#selector)›): *object*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type | Default |
|
||||||
|
------ | ------ | ------ |
|
||||||
|
`localSelectors` | [Dictionary](globals.md#dictionary)‹[Selector](globals.md#selector)› | {} |
|
||||||
|
`coduxes` | [Dictionary](globals.md#dictionary)‹[Selector](globals.md#selector)›[] | [] |
|
||||||
|
`subduxes` | [Dictionary](globals.md#dictionary)‹[Selector](globals.md#selector)› | {} |
|
||||||
|
|
||||||
|
**Returns:** *object*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### buildUpreducer
|
||||||
|
|
||||||
|
▸ **buildUpreducer**<**S**>(`initial`: S, `mutations`: [Dictionary](globals.md#dictionary)‹[Mutation](globals.md#mutation)‹S››): *[Upreducer](globals.md#upreducer)‹S›*
|
||||||
|
|
||||||
|
**Type parameters:**
|
||||||
|
|
||||||
|
▪ **S**
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`initial` | S |
|
||||||
|
`mutations` | [Dictionary](globals.md#dictionary)‹[Mutation](globals.md#mutation)‹S›› |
|
||||||
|
|
||||||
|
**Returns:** *[Upreducer](globals.md#upreducer)‹S›*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### `Const` coduxes
|
||||||
|
|
||||||
|
▸ **coduxes**<**C**, **U**>(...`coduxes`: U): *object*
|
||||||
|
|
||||||
|
**Type parameters:**
|
||||||
|
|
||||||
|
▪ **C**: *[Dux](globals.md#dux)*
|
||||||
|
|
||||||
|
▪ **U**: *[C]*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`...coduxes` | U |
|
||||||
|
|
||||||
|
**Returns:** *object*
|
||||||
|
|
||||||
|
* **coduxes**: *U*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### `Const` composeMutations
|
||||||
|
|
||||||
|
▸ **composeMutations**(`mutations`: [Mutation](globals.md#mutation)[]): *function | (Anonymous function)*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`mutations` | [Mutation](globals.md#mutation)[] |
|
||||||
|
|
||||||
|
**Returns:** *function | (Anonymous function)*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### `Const` composeMw
|
||||||
|
|
||||||
|
▸ **composeMw**(`mws`: [UpduxMiddleware](globals.md#upduxmiddleware)[]): *(Anonymous function)*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`mws` | [UpduxMiddleware](globals.md#upduxmiddleware)[] |
|
||||||
|
|
||||||
|
**Returns:** *(Anonymous function)*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### `Const` dux
|
||||||
|
|
||||||
|
▸ **dux**<**S**, **A**, **X**, **C**>(`config`: C): *object*
|
||||||
|
|
||||||
|
**Type parameters:**
|
||||||
|
|
||||||
|
▪ **S**
|
||||||
|
|
||||||
|
▪ **A**
|
||||||
|
|
||||||
|
▪ **X**
|
||||||
|
|
||||||
|
▪ **C**: *[UpduxConfig](globals.md#upduxconfig)*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`config` | C |
|
||||||
|
|
||||||
|
**Returns:** *object*
|
||||||
|
|
||||||
|
* **actions**: = this.actions
|
||||||
|
|
||||||
|
* **coduxes**: *object[]* = this.coduxes
|
||||||
|
|
||||||
|
* **createStore**(): *function*
|
||||||
|
|
||||||
|
* (`initial?`: S, `injectEnhancer?`: Function): *Store‹S› & object*
|
||||||
|
|
||||||
|
* **initial**: = this.initial
|
||||||
|
|
||||||
|
* **middleware**(): *function*
|
||||||
|
|
||||||
|
* (`api`: UpduxMiddlewareAPI‹S, X›): *function*
|
||||||
|
|
||||||
|
* (`next`: Function): *function*
|
||||||
|
|
||||||
|
* (`action`: A): *any*
|
||||||
|
|
||||||
|
* **mutations**(): *object*
|
||||||
|
|
||||||
|
* **reducer**(): *function*
|
||||||
|
|
||||||
|
* (`state`: S | undefined, `action`: [Action](globals.md#action)): *S*
|
||||||
|
|
||||||
|
* **selectors**: = this.selectors
|
||||||
|
|
||||||
|
* **subduxes**(): *object*
|
||||||
|
|
||||||
|
* **upreducer**(): *function*
|
||||||
|
|
||||||
|
* (`action`: [Action](globals.md#action)): *function*
|
||||||
|
|
||||||
|
* (`state`: S): *S*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### `Const` effectToMw
|
||||||
|
|
||||||
|
▸ **effectToMw**(`effect`: [Effect](globals.md#effect), `actions`: [Dictionary](globals.md#dictionary)‹ActionCreator›, `selectors`: [Dictionary](globals.md#dictionary)‹[Selector](globals.md#selector)›): *subMiddleware | augmented*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`effect` | [Effect](globals.md#effect) |
|
||||||
|
`actions` | [Dictionary](globals.md#dictionary)‹ActionCreator› |
|
||||||
|
`selectors` | [Dictionary](globals.md#dictionary)‹[Selector](globals.md#selector)› |
|
||||||
|
|
||||||
|
**Returns:** *subMiddleware | augmented*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### sliceMw
|
||||||
|
|
||||||
|
▸ **sliceMw**(`slice`: string, `mw`: [UpduxMiddleware](globals.md#upduxmiddleware)): *[UpduxMiddleware](globals.md#upduxmiddleware)*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`slice` | string |
|
||||||
|
`mw` | [UpduxMiddleware](globals.md#upduxmiddleware) |
|
||||||
|
|
||||||
|
**Returns:** *[UpduxMiddleware](globals.md#upduxmiddleware)*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### `Const` subMiddleware
|
||||||
|
|
||||||
|
▸ **subMiddleware**(): *(Anonymous function)*
|
||||||
|
|
||||||
|
**Returns:** *(Anonymous function)*
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
### subSelectors
|
||||||
|
|
||||||
|
▸ **subSelectors**(`__namedParameters`: [string, Function]): *[string, [Selector](globals.md#selector)][]*
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
Name | Type |
|
||||||
|
------ | ------ |
|
||||||
|
`__namedParameters` | [string, Function] |
|
||||||
|
|
||||||
|
**Returns:** *[string, [Selector](globals.md#selector)][]*
|
359
docs/updux.md
359
docs/updux.md
@ -1,359 +0,0 @@
|
|||||||
# Updux
|
|
||||||
|
|
||||||
`Updux` is a way to minimize and simplify the boilerplate associated with the
|
|
||||||
creation of a `Redux` store. It takes a shorthand configuration
|
|
||||||
object, and generates the appropriate reducer, actions, middleware, etc.
|
|
||||||
In true `Redux`-like fashion, upduxes can be made of sub-upduxes (`subduxes` for short) for different slices of the root state.
|
|
||||||
|
|
||||||
## Constructor
|
|
||||||
|
|
||||||
const updux = new Updux({ ...buildArgs })
|
|
||||||
|
|
||||||
### arguments
|
|
||||||
|
|
||||||
#### initial
|
|
||||||
|
|
||||||
Default initial state of the reducer. If applicable, is merged with
|
|
||||||
the subduxes initial states, with the parent having precedence.
|
|
||||||
|
|
||||||
If not provided, defaults to an empty object.
|
|
||||||
|
|
||||||
#### actions
|
|
||||||
|
|
||||||
Generic action creations are automatically created from the mutations and effects, but you can
|
|
||||||
also define custom action creator here. The object's values are function that
|
|
||||||
transform the arguments of the creator to the action's payload.
|
|
||||||
|
|
||||||
```js
|
|
||||||
const { actions } = updux({
|
|
||||||
actions: {
|
|
||||||
bar: (x,y) => ({x,y})
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
foo: () => state => state,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
actions.foo({ x: 1, y: 2 }); // => { type: foo, payload: { x:1, y:2 } }
|
|
||||||
actions.bar(1,2); // => { type: bar, payload: { x:1, y:2 } }
|
|
||||||
|
|
||||||
|
|
||||||
#### selectors
|
|
||||||
|
|
||||||
Dictionary of selectors for the current updux. The updux also
|
|
||||||
inherit its dubduxes' selectors.
|
|
||||||
|
|
||||||
The selectors are available via the class' getter and, for
|
|
||||||
middlewares, the middlewareApi.
|
|
||||||
|
|
||||||
```js
|
|
||||||
const todoUpdux = new Updux({
|
|
||||||
selectors: {
|
|
||||||
done: state => state.filter( ({done}) => done ),
|
|
||||||
byId: state => targetId => state.find( ({id}) => id === targetId ),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### mutations
|
|
||||||
|
|
||||||
Object mapping actions to the associated state mutation.
|
|
||||||
|
|
||||||
For example, in `Redux` you'd do
|
|
||||||
|
|
||||||
```js
|
|
||||||
function todosReducer(state=[],action) {
|
|
||||||
|
|
||||||
switch(action.type) {
|
|
||||||
case 'ADD': return [ ...state, action.payload ];
|
|
||||||
|
|
||||||
case 'DONE': return state.map( todo => todo.id === action.payload
|
|
||||||
? { ...todo, done: true } : todo )
|
|
||||||
|
|
||||||
default: return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
With Updux:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const todosUpdux = updux({
|
|
||||||
mutations: {
|
|
||||||
add: todo => state => [ ...state, todo ],
|
|
||||||
done: done_id => u.map( u.if( ({id} => id === done_id), {done: true} ) )
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
The signature of the mutations is `(payload,action) => state => newState`.
|
|
||||||
It is designed to play well with `Updeep` (and [Immer](https://immerjs.github.io/immer/docs/introduction)). This way, instead of doing
|
|
||||||
|
|
||||||
```js
|
|
||||||
mutation: {
|
|
||||||
renameTodo: newName => state => { ...state, name: newName }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
we can do
|
|
||||||
|
|
||||||
```js
|
|
||||||
mutation: {
|
|
||||||
renameTodo: newName => u({ name: newName })
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Also, the special key `*` can be used to match any
|
|
||||||
action not explicitly matched by other mutations.
|
|
||||||
|
|
||||||
```js
|
|
||||||
const todosUpdux = updux({
|
|
||||||
mutations: {
|
|
||||||
add: todo => state => [ ...state, todo ],
|
|
||||||
done: done_id => u.map( u.if( ({id} => id === done_id), {done: true} ) ),
|
|
||||||
'*' (payload,action) => state => {
|
|
||||||
console.warn( "unexpected action ", action.type );
|
|
||||||
return state;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### groomMutations
|
|
||||||
|
|
||||||
|
|
||||||
Function that can be provided to alter all local mutations of the updux
|
|
||||||
(the mutations of subduxes are left untouched).
|
|
||||||
|
|
||||||
Can be used, for example, for Immer integration:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import Updux from 'updux';
|
|
||||||
import { produce } from 'Immer';
|
|
||||||
|
|
||||||
const updux = new Updux({
|
|
||||||
initial: { counter: 0 },
|
|
||||||
groomMutations: mutation => (...args) => produce( mutation(...args) ),
|
|
||||||
mutations: {
|
|
||||||
add: (inc=1) => draft => draft.counter += inc
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Or perhaps for debugging:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import Updux from 'updux';
|
|
||||||
|
|
||||||
const updux = new Updux({
|
|
||||||
initial: { counter: 0 },
|
|
||||||
groomMutations: mutation => (...args) => state => {
|
|
||||||
console.log( "got action ", args[1] );
|
|
||||||
return mutation(...args)(state);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### subduxes
|
|
||||||
|
|
||||||
Object mapping slices of the state to sub-upduxes. In addition to creating
|
|
||||||
sub-reducers for those slices, it'll make the parend updux inherit all the
|
|
||||||
actions and middleware from its subduxes.
|
|
||||||
|
|
||||||
For example, if in plain Redux you would do
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { combineReducers } from 'redux';
|
|
||||||
import todosReducer from './todos';
|
|
||||||
import statisticsReducer from './statistics';
|
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
|
||||||
todos: todosReducer,
|
|
||||||
stats: statisticsReducer,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
then with Updux you'd do
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { updux } from 'updux';
|
|
||||||
import todos from './todos';
|
|
||||||
import statistics from './statistics';
|
|
||||||
|
|
||||||
const rootUpdux = updux({
|
|
||||||
subduxes: {
|
|
||||||
todos,
|
|
||||||
statistics
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### effects
|
|
||||||
|
|
||||||
Plain object defining asynchronous actions and side-effects triggered by actions.
|
|
||||||
The effects themselves are Redux middleware, with the `dispatch`
|
|
||||||
property of the first argument augmented with all the available actions.
|
|
||||||
|
|
||||||
```
|
|
||||||
updux({
|
|
||||||
effects: {
|
|
||||||
fetch: ({dispatch}) => next => async (action) => {
|
|
||||||
next(action);
|
|
||||||
|
|
||||||
let result = await fetch(action.payload.url).then( result => result.json() );
|
|
||||||
dispatch.fetchSuccess(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### middleware
|
|
||||||
|
|
||||||
## Getters
|
|
||||||
|
|
||||||
### actions
|
|
||||||
|
|
||||||
Action creators for all actions defined or used in the actions, mutations, effects and subduxes
|
|
||||||
of the updux config.
|
|
||||||
|
|
||||||
Non-custom action creators defined in `actions` have the signature `(payload={},meta={}) => ({type,
|
|
||||||
payload,meta})` (with the extra sugar that if `meta` or `payload` are not
|
|
||||||
specified, the key is not present in the produced action).
|
|
||||||
|
|
||||||
If the same action appears in multiple locations, the precedence order
|
|
||||||
determining which one will prevail is
|
|
||||||
|
|
||||||
actions generated from mutations/effects < non-custom subduxes actions <
|
|
||||||
custom subduxes actions < custom actions
|
|
||||||
|
|
||||||
### middleware
|
|
||||||
|
|
||||||
const middleware = updux.middleware;
|
|
||||||
|
|
||||||
Array of middlewares aggregating all the effects defined in the
|
|
||||||
updux and its subduxes. Effects of the updux itself are
|
|
||||||
done before the subduxes effects.
|
|
||||||
Note that `getState` will always return the state of the
|
|
||||||
local updux. The function `getRootState` is provided
|
|
||||||
alongside `getState` to get the root state.
|
|
||||||
|
|
||||||
|
|
||||||
#### reducer
|
|
||||||
|
|
||||||
A Redux reducer generated using the computed initial state and
|
|
||||||
mutations.
|
|
||||||
|
|
||||||
|
|
||||||
#### mutations
|
|
||||||
|
|
||||||
Merge of the updux and subduxes mutations. If an action triggers
|
|
||||||
mutations in both the main updux and its subduxes, the subduxes
|
|
||||||
mutations will be performed first.
|
|
||||||
|
|
||||||
#### subduxUpreducer
|
|
||||||
|
|
||||||
|
|
||||||
Returns the upreducer made of the merge of all sudbuxes reducers, without
|
|
||||||
the local mutations. Useful, for example, for sink mutations.
|
|
||||||
|
|
||||||
```js
|
|
||||||
import todo from './todo'; // updux for a single todo
|
|
||||||
import Updux from 'updux';
|
|
||||||
import u from 'updeep';
|
|
||||||
|
|
||||||
const todos = new Updux({ initial: [], subduxes: { '*': todo } });
|
|
||||||
todos.addMutation(
|
|
||||||
todo.actions.done,
|
|
||||||
({todo_id},action) => u.map( u.if( u.is('id',todo_id) ), todos.subduxUpreducer(action) )
|
|
||||||
true
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### createStore
|
|
||||||
|
|
||||||
|
|
||||||
Same as doing
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { createStore, applyMiddleware } from 'redux';
|
|
||||||
|
|
||||||
const { initial, reducer, middleware, actions } = updox(...);
|
|
||||||
|
|
||||||
const store = createStore( initial, reducer, applyMiddleware(middleware) );
|
|
||||||
|
|
||||||
for ( let type in actions ) {
|
|
||||||
store.dispatch[type] = (...args) => {
|
|
||||||
store.dispatch(actions[type](...args))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
So that later on you can do
|
|
||||||
|
|
||||||
```js
|
|
||||||
store.dispatch.addTodo(...);
|
|
||||||
|
|
||||||
// still work
|
|
||||||
store.dispatch( actions.addTodo(...) );
|
|
||||||
```
|
|
||||||
|
|
||||||
## Methods
|
|
||||||
|
|
||||||
### asDux
|
|
||||||
|
|
||||||
|
|
||||||
Returns a [ducks](https://github.com/erikras/ducks-modular-redux)-like
|
|
||||||
plain object holding the reducer from the Updux object and all
|
|
||||||
its trimmings.
|
|
||||||
|
|
||||||
|
|
||||||
### addMutation
|
|
||||||
|
|
||||||
Adds a mutation and its associated action to the updux.
|
|
||||||
If a local mutation was already associated to the action,
|
|
||||||
it will be replaced by the new one.
|
|
||||||
@param isSink
|
|
||||||
If `true`, disables the subduxes mutations for this action. To
|
|
||||||
conditionally run the subduxes mutations, check out [[subduxUpreducer]].
|
|
||||||
|
|
||||||
```js
|
|
||||||
updux.addMutation( add, inc => state => state + inc );
|
|
||||||
```
|
|
||||||
|
|
||||||
### addAction
|
|
||||||
|
|
||||||
```js
|
|
||||||
const action = updux.addAction( name, ...creatorArgs );
|
|
||||||
const action = updux.addAction( otherActionCreator );
|
|
||||||
```
|
|
||||||
|
|
||||||
Adds an action to the updux. It can take an already defined action creator,
|
|
||||||
or any arguments that can be passed to `actionCreator`.
|
|
||||||
|
|
||||||
```js
|
|
||||||
import {actionCreator, Updux} from 'updux';
|
|
||||||
|
|
||||||
const updux = new Updux();
|
|
||||||
|
|
||||||
const foo = updux.addAction('foo');
|
|
||||||
const bar = updux.addAction( 'bar', (x) => ({stuff: x+1}) );
|
|
||||||
|
|
||||||
const baz = actionCreator( 'baz' );
|
|
||||||
|
|
||||||
foo({ a: 1}); // => { type: 'foo', payload: { a: 1 } }
|
|
||||||
bar(2); // => { type: 'bar', payload: { stuff: 3 } }
|
|
||||||
baz(); // => { type: 'baz', payload: undefined }
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### selectors
|
|
||||||
|
|
||||||
Returns a dictionary of the
|
|
||||||
updux's selectors. Subduxes' selectors
|
|
||||||
are included as well (with the mapping to the sub-state already
|
|
||||||
taken care of you).
|
|
||||||
|
|
||||||
|
|
114
package.json
114
package.json
@ -1,51 +1,67 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"redux": "^4.0.4",
|
"redux": "^4.0.5",
|
||||||
"ts-action": "^11.0.0",
|
"ts-action": "^11.0.0",
|
||||||
"updeep": "^1.2.0"
|
"ts-node": "^8.6.2",
|
||||||
},
|
"updeep": "^1.2.0"
|
||||||
"devDependencies": {
|
},
|
||||||
"@babel/cli": "^7.6.4",
|
"devDependencies": {
|
||||||
"@babel/core": "^7.6.4",
|
"tap": "^14.10.6",
|
||||||
"@babel/preset-env": "^7.6.3",
|
"typedoc": "0.17.7",
|
||||||
"@types/jest": "^24.0.19",
|
"typedoc-plugin-markdown": "^2.2.17",
|
||||||
"@types/lodash": "^4.14.144",
|
"sinon": "^9.0.1",
|
||||||
"babel-jest": "^24.9.0",
|
"promake": "^3.1.3",
|
||||||
"docsify": "^4.10.2",
|
"dtslint": "^3.3.0",
|
||||||
"docsify-cli": "^4.4.0",
|
"glob": "^7.1.6",
|
||||||
"jest": "^24.9.0",
|
"@types/sinon": "^7.5.2",
|
||||||
"ts-jest": "^24.1.0",
|
"docsify-tools": "^1.0.20",
|
||||||
"tsd": "^0.10.0",
|
"@babel/cli": "^7.8.4",
|
||||||
"typescript": "^3.6.4"
|
"@babel/core": "^7.8.7",
|
||||||
},
|
"@babel/preset-env": "^7.8.7",
|
||||||
"license": "MIT",
|
"@types/jest": "^25.1.4",
|
||||||
"main": "dist/index.js",
|
"@types/lodash": "^4.14.149",
|
||||||
"name": "updux",
|
"@typescript-eslint/eslint-plugin": "^2.23.0",
|
||||||
"description": "Updeep-friendly Redux helper framework",
|
"@typescript-eslint/parser": "^2.23.0",
|
||||||
"scripts": {
|
"babel-jest": "^25.1.0",
|
||||||
"docsify:serve": "docsify serve docs",
|
"docsify": "^4.11.2",
|
||||||
"build": "tsc",
|
"docsify-cli": "^4.4.0",
|
||||||
"test": "jest"
|
"eslint": "^6.8.0",
|
||||||
},
|
"eslint-config-prettier": "^6.10.0",
|
||||||
"version": "1.2.0",
|
"eslint-plugin-import": "^2.20.1",
|
||||||
"repository": {
|
"eslint-plugin-prettier": "^3.1.2",
|
||||||
"type": "git",
|
"jest": "^25.1.0",
|
||||||
"url": "git+https://github.com/yanick/updux.git"
|
"ts-jest": "^25.2.1",
|
||||||
},
|
"tsd": "^0.11.0",
|
||||||
"keywords": [
|
"typescript": "^3.8.3"
|
||||||
"redux",
|
},
|
||||||
"updeep"
|
"license": "MIT",
|
||||||
],
|
"main": "dist/index.js",
|
||||||
"author": "Yanick Champoux <yanick@babyl.ca> (http://techblog.babyl.ca)",
|
"name": "updux",
|
||||||
"bugs": {
|
"description": "Updeep-friendly Redux helper framework",
|
||||||
"url": "https://github.com/yanick/updux/issues"
|
"scripts": {
|
||||||
},
|
"docsify:serve": "docsify serve docs",
|
||||||
"homepage": "https://github.com/yanick/updux#readme",
|
"build": "tsc",
|
||||||
"types": "./dist/index.d.ts",
|
"test": "tap src/**test.ts"
|
||||||
"prettier": {
|
},
|
||||||
"tabWidth": 4,
|
"version": "1.2.0",
|
||||||
"singleQuote": true,
|
"repository": {
|
||||||
"trailingComma": "es5"
|
"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",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"prettier": {
|
||||||
|
"tabWidth": 4,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
121
src/actions-params.test.ts
Normal file
121
src/actions-params.test.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import Updux, { dux, coduxes } from '.';
|
||||||
|
import { action, payload } from 'ts-action';
|
||||||
|
import { test } from 'tap';
|
||||||
|
import { expectAssignable } from 'tsd';
|
||||||
|
import { DuxActionsCoduxes } from './types';
|
||||||
|
|
||||||
|
const foo_actions = {
|
||||||
|
aaa: action('aaa', payload<number>()),
|
||||||
|
};
|
||||||
|
|
||||||
|
const fooDux = dux({
|
||||||
|
actions: foo_actions,
|
||||||
|
});
|
||||||
|
|
||||||
|
const bar_actions = {
|
||||||
|
bbb: action('bbb', (x: string) => ({ payload: '1' + x })),
|
||||||
|
};
|
||||||
|
|
||||||
|
test('actions are present', t => {
|
||||||
|
const barDux = dux({
|
||||||
|
subduxes: { foo: fooDux },
|
||||||
|
actions: bar_actions,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = Object.keys(barDux.actions);
|
||||||
|
result.sort();
|
||||||
|
t.same(result, ['aaa', 'bbb']);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
const a = action('a');
|
||||||
|
const b = action('b');
|
||||||
|
|
||||||
|
test('typing', t => {
|
||||||
|
t.test('nothing at all', t => {
|
||||||
|
const foo = new Updux();
|
||||||
|
t.same(foo.actions, {});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test('with two coduxes', t => {
|
||||||
|
const myDux = new Updux({
|
||||||
|
...coduxes(dux({ actions: { a } }), dux({ actions: { b } })),
|
||||||
|
});
|
||||||
|
|
||||||
|
t.ok(myDux.actions.a);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test('empty dux', t => {
|
||||||
|
const empty = dux({});
|
||||||
|
|
||||||
|
expectAssignable<object>(empty.actions);
|
||||||
|
|
||||||
|
t.same(empty.actions, {}, 'no actions there');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test('coduxes actions', t => {
|
||||||
|
const typeOf = <C>(x: C) => (x as any) as DuxActionsCoduxes<C>;
|
||||||
|
|
||||||
|
expectAssignable<{ a: any }>(typeOf([dux({ actions: { a } })]));
|
||||||
|
expectAssignable<{}>(typeOf([dux({})]));
|
||||||
|
|
||||||
|
expectAssignable<{ a: any; b: any }>(
|
||||||
|
typeOf([dux({ actions: { a } }), dux({ actions: { b } })])
|
||||||
|
);
|
||||||
|
|
||||||
|
const co = coduxes(dux({ actions: { a } }), dux({})).coduxes;
|
||||||
|
|
||||||
|
expectAssignable<{ a: any }>(typeOf(co));
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test('with empty coduxes', t => {
|
||||||
|
const emptyDux = dux({});
|
||||||
|
|
||||||
|
const myDux = dux({
|
||||||
|
...coduxes(dux({ actions: { a } }), emptyDux),
|
||||||
|
});
|
||||||
|
|
||||||
|
t.ok(myDux.actions.a);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test('with three coduxes', t => {
|
||||||
|
const emptyDux = new Updux();
|
||||||
|
emptyDux.actions;
|
||||||
|
|
||||||
|
const dux = new Updux({
|
||||||
|
coduxes: [
|
||||||
|
emptyDux,
|
||||||
|
new Updux({ actions: { a } }),
|
||||||
|
new Updux({ actions: { b } }),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
t.ok(dux.actions.b);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test('with grandchild', t => {
|
||||||
|
const dux = new Updux({
|
||||||
|
subduxes: {
|
||||||
|
bar: new Updux({
|
||||||
|
subduxes: {
|
||||||
|
baz: new Updux({
|
||||||
|
actions: { a },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
t.ok(dux.actions.a);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
@ -1,58 +1,78 @@
|
|||||||
import { action, payload } from 'ts-action';
|
import { action, payload } from 'ts-action';
|
||||||
import u from 'updeep';
|
|
||||||
|
|
||||||
import Updux from '.';
|
import Updux, { dux } from '.';
|
||||||
|
import { test } from 'tap';
|
||||||
|
import { expectAssignable } from 'tsd';
|
||||||
|
|
||||||
const noopEffect = () => () => () => {};
|
const noopEffect = () => () => () => null;
|
||||||
|
|
||||||
test('actions defined in effects and mutations, multi-level', () => {
|
test(
|
||||||
const bar = action('bar',(payload,meta) => ({payload,meta}) );
|
'actions defined in effects and mutations, multi-level',
|
||||||
const foo = action('foo',(limit:number) => ({payload:{ limit} }) );
|
{ autoend: true },
|
||||||
|
t => {
|
||||||
|
const bar = action('bar', (payload, meta) => ({ payload, meta }));
|
||||||
|
const foo = action('foo', (limit: number) => ({
|
||||||
|
payload: { limit },
|
||||||
|
}));
|
||||||
|
|
||||||
const {actions} = new Updux({
|
const { actions }: any = dux({
|
||||||
effects: [ [ foo, noopEffect ] ],
|
effects: [[foo, noopEffect] as any],
|
||||||
mutations: [ [ bar, () => () => null ] ],
|
mutations: [[bar, () => () => null]],
|
||||||
subduxes: {
|
subduxes: {
|
||||||
mysub: {
|
mysub: dux({
|
||||||
effects: {baz: noopEffect},
|
effects: { baz: noopEffect },
|
||||||
mutations: {quux: () => () => null},
|
mutations: { quux: () => () => null },
|
||||||
actions: {
|
actions: {
|
||||||
foo
|
foo,
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
myothersub: {
|
myothersub: dux({
|
||||||
effects: [ [foo, noopEffect] ],
|
effects: [[foo, noopEffect]],
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const types = Object.keys(actions);
|
const types = Object.keys(actions);
|
||||||
types.sort();
|
types.sort();
|
||||||
|
|
||||||
expect(types).toEqual(['bar', 'baz', 'foo', 'quux']);
|
t.match(types, ['bar', 'baz', 'foo', 'quux']);
|
||||||
|
|
||||||
expect(actions.bar()).toEqual({type: 'bar'});
|
t.match(actions.bar(), { type: 'bar' });
|
||||||
expect(actions.bar('xxx')).toEqual({type: 'bar', payload: 'xxx'});
|
t.match(actions.bar('xxx'), { type: 'bar', payload: 'xxx' });
|
||||||
expect(actions.bar(undefined, 'yyy')).toEqual({type: 'bar', payload: undefined, meta: 'yyy'});
|
t.match(actions.bar(undefined, 'yyy'), {
|
||||||
|
type: 'bar',
|
||||||
|
payload: undefined,
|
||||||
|
meta: 'yyy',
|
||||||
|
});
|
||||||
|
|
||||||
expect(actions.foo(12)).toEqual({type: 'foo', payload: {limit: 12}});
|
t.same(actions.foo(12), { type: 'foo', payload: { limit: 12 } });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test('different calls to addAction', t => {
|
||||||
|
const updux = new Updux<any,any>();
|
||||||
|
|
||||||
|
updux.addAction(action('foo', payload()));
|
||||||
|
t.match(updux.actions.foo('yo'), {
|
||||||
|
type: 'foo',
|
||||||
|
payload: 'yo',
|
||||||
|
});
|
||||||
|
|
||||||
|
updux.addAction('baz', x => ({ x }));
|
||||||
|
t.match(updux.actions.baz(3), {
|
||||||
|
type: 'baz',
|
||||||
|
payload: { x: 3 },
|
||||||
|
});
|
||||||
|
|
||||||
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('different calls to addAction', () => {
|
test('types', t => {
|
||||||
const updux = new Updux();
|
const {actions} = dux({ actions: {
|
||||||
|
foo: action('foo')
|
||||||
|
}});
|
||||||
|
|
||||||
test('string', () => {
|
expectAssignable<object>( actions );
|
||||||
updux.addAction( action('foo', payload() ));
|
|
||||||
expect(updux.actions.foo('yo')).toMatchObject({
|
|
||||||
type: 'foo',
|
|
||||||
payload: 'yo',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('actionCreator inlined', () => {
|
t.end();
|
||||||
updux.addAction( 'baz', (x) => ({payload: {x}}));
|
|
||||||
expect(updux.actions.baz(3)).toMatchObject({
|
|
||||||
type: 'baz', payload: { x: 3 }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { action } from 'ts-action';
|
import { action } from 'ts-action';
|
||||||
|
import tap from 'tap';
|
||||||
|
|
||||||
import Updux from "./updux";
|
import Updux from "./updux";
|
||||||
|
|
||||||
@ -6,17 +7,19 @@ type MyState = {
|
|||||||
sum: number;
|
sum: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
test("added mutation is present", () => {
|
tap.test("added mutation is present", t => {
|
||||||
const updux = new Updux<MyState>({
|
const updux = new Updux<MyState>({
|
||||||
initial: { sum: 0 }
|
initial: { sum: 0 }
|
||||||
});
|
});
|
||||||
|
|
||||||
const add = action("add", (n: number) => ({ payload: { n } }));
|
const add = action("add", (n: number) => ({ payload: { n } }));
|
||||||
|
|
||||||
updux.addMutation(add, ({ n }, action) => ({ sum }) => ({ sum: sum + n }));
|
updux.addMutation(add, ({ n }) => ({ sum }) => ({ sum: sum + n }));
|
||||||
|
|
||||||
const store = updux.createStore();
|
const store = updux.createStore();
|
||||||
store.dispatch.add(3);
|
store.dispatch(add(3));
|
||||||
|
|
||||||
expect(store.getState()).toEqual({ sum: 3 });
|
t.same(store.getState(), {sum: 3});
|
||||||
|
|
||||||
|
t.end();
|
||||||
});
|
});
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
import fp from 'lodash/fp';
|
import fp from 'lodash/fp';
|
||||||
import {
|
import {
|
||||||
ActionCreator,
|
ActionCreator
|
||||||
|
} from 'ts-action';
|
||||||
|
|
||||||
|
import {
|
||||||
Dictionary,
|
Dictionary,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
type ActionPair = [string, ActionCreator];
|
type ActionPair = [string, ActionCreator];
|
||||||
|
|
||||||
function buildActions(actions: ActionPair[] = []): Dictionary<ActionCreator> {
|
function buildActions(actions: ActionPair[] = []): Dictionary<ActionCreator<string,(...args: any) => {type: string} >>{
|
||||||
// priority => generics => generic subs => craft subs => creators
|
// priority => generics => generic subs => craft subs => creators
|
||||||
|
|
||||||
const [crafted, generic] = fp.partition(([type, f]) => !f._genericAction)(
|
const [crafted, generic] = fp.partition(([type, f]) => !f._genericAction)(
|
||||||
|
@ -3,29 +3,28 @@ import {
|
|||||||
applyMiddleware,
|
applyMiddleware,
|
||||||
Middleware,
|
Middleware,
|
||||||
Reducer,
|
Reducer,
|
||||||
PreloadedState
|
PreloadedState,
|
||||||
|
Store,
|
||||||
} from 'redux';
|
} from 'redux';
|
||||||
import { ActionCreator, Dictionary } from '../types';
|
|
||||||
|
|
||||||
function buildCreateStore<S>(
|
function buildCreateStore<S,A = {}>(
|
||||||
reducer: Reducer<S>,
|
reducer: Reducer<S>,
|
||||||
initial: PreloadedState<S>,
|
|
||||||
middleware: Middleware,
|
middleware: Middleware,
|
||||||
actions: Dictionary<ActionCreator>,
|
actions: A = {} as A,
|
||||||
) {
|
): (initial?: S, injectEnhancer?: Function) => Store<S> & { actions: A } {
|
||||||
return () => {
|
return function createStore(initial?: S, injectEnhancer?: Function ): Store<S> & { actions: A } {
|
||||||
|
|
||||||
|
let enhancer = injectEnhancer ? injectEnhancer(middleware) : applyMiddleware(middleware);
|
||||||
|
|
||||||
const store = reduxCreateStore(
|
const store = reduxCreateStore(
|
||||||
reducer,
|
reducer,
|
||||||
initial,
|
initial as PreloadedState<S>,
|
||||||
applyMiddleware(middleware),
|
enhancer
|
||||||
);
|
);
|
||||||
for (let a in actions) {
|
|
||||||
( store.dispatch as any)[a] = (...args: any[]) => {
|
|
||||||
store.dispatch(actions[a](...args));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return store;
|
(store as any).actions = actions;
|
||||||
|
|
||||||
|
return store as any;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
28
src/buildCreateStore/test.ts
Normal file
28
src/buildCreateStore/test.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import tap from 'tap';
|
||||||
|
import { expectType } from 'tsd';
|
||||||
|
|
||||||
|
import buildCreateStore from '.';
|
||||||
|
import { action, ActionCreator } from 'ts-action';
|
||||||
|
import {Reducer} from 'redux';
|
||||||
|
|
||||||
|
const foo = action('foo');
|
||||||
|
const bar = action('bar');
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
x: number,
|
||||||
|
y: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = buildCreateStore(
|
||||||
|
((state: State|undefined) => state ?? {x: 1} ) as Reducer<State>,
|
||||||
|
() => () => () => {return},
|
||||||
|
{ foo, bar }
|
||||||
|
)();
|
||||||
|
|
||||||
|
expectType<State>( store.getState() );
|
||||||
|
expectType<number>( store.getState().x );
|
||||||
|
|
||||||
|
expectType<{ foo: ActionCreator }>( store.actions );
|
||||||
|
|
||||||
|
tap.pass();
|
||||||
|
|
@ -1,13 +1,16 @@
|
|||||||
import fp from 'lodash/fp';
|
import fp from 'lodash/fp';
|
||||||
import { Dictionary } from '../types';
|
import u from 'updeep';
|
||||||
|
|
||||||
function buildInitial<S extends number|string|boolean>( initial: S, subduxes?: Dictionary<undefined> ): S;
|
function buildInitial(initial: any, coduxes: any = [], subduxes: any = {}) {
|
||||||
function buildInitial<S extends object>( initial?: Partial<S>, subduxes?: Partial<S> ): S extends object ? S : never;
|
if (!fp.isPlainObject(initial)) return initial;
|
||||||
function buildInitial(
|
|
||||||
initial : any = {},
|
return fp.flow(
|
||||||
subduxes : any = {} ,
|
[
|
||||||
) {
|
u(fp.omit(['*'], subduxes)),
|
||||||
return fp.isPlainObject(initial) ? fp.mergeAll([subduxes, initial]) : initial;
|
coduxes.map(i => u(i)),
|
||||||
|
u(initial),
|
||||||
|
].flat()
|
||||||
|
)({});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default buildInitial;
|
export default buildInitial;
|
||||||
|
@ -1,61 +1,89 @@
|
|||||||
import fp from "lodash/fp";
|
import fp from 'lodash/fp';
|
||||||
|
import { ActionCreator } from 'ts-action';
|
||||||
|
|
||||||
import { Middleware, MiddlewareAPI, Dispatch } from "redux";
|
import { Middleware, MiddlewareAPI, Dispatch } from 'redux';
|
||||||
import {
|
import {
|
||||||
Dictionary,
|
Dictionary,
|
||||||
ActionCreator,
|
Action,
|
||||||
Action,
|
UpduxMiddleware,
|
||||||
UpduxDispatch,
|
UpduxMiddlewareAPI,
|
||||||
UpduxMiddleware,
|
Selector,
|
||||||
UpduxMiddlewareAPI,
|
} from '../types';
|
||||||
EffectEntry
|
import Updux from '..';
|
||||||
} from "../types";
|
|
||||||
import Updux from "..";
|
|
||||||
|
|
||||||
const MiddlewareFor = (
|
const MiddlewareFor = (
|
||||||
type: any,
|
type: any,
|
||||||
mw: Middleware
|
mw: Middleware
|
||||||
): Middleware => api => next => action => {
|
): Middleware => api => next => action => {
|
||||||
if (!["*", "^", "$"].includes(type) && action.type !== type)
|
if (!type.includes('*') && action.type !== type) return next(action);
|
||||||
return next(action);
|
|
||||||
|
|
||||||
return mw(api)(next)(action);
|
return mw(api)(next)(action);
|
||||||
};
|
};
|
||||||
|
|
||||||
type Next = (action: Action) => any;
|
type Next = (action: Action) => any;
|
||||||
|
|
||||||
function sliceMw(slice: string, mw: Middleware, updux: Updux): Middleware {
|
function sliceMw(slice: string, mw: UpduxMiddleware): UpduxMiddleware {
|
||||||
return api => {
|
return api => {
|
||||||
const getSliceState =
|
const getSliceState = () => fp.get(slice, api.getState());
|
||||||
slice.length > 0 ? () => fp.get(slice, api.getState()) : api.getState;
|
return mw({ ...api, getState: getSliceState } as any);
|
||||||
const getRootState = (api as any).getRootState || api.getState;
|
};
|
||||||
return mw({ ...api, getState: getSliceState, getRootState,
|
|
||||||
selectors: updux.selectors } as any);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildMiddleware<S = any>(
|
type Submws = Dictionary<UpduxMiddleware>;
|
||||||
middlewareEntries: any[] = [],
|
|
||||||
actions: Dictionary<ActionCreator> = {}
|
|
||||||
): UpduxMiddleware<S> {
|
|
||||||
let mws = middlewareEntries
|
|
||||||
.map(([updux, slice, actionType, mw, isGen]: any) =>
|
|
||||||
isGen ? [updux, slice, actionType, mw()] : [updux, slice, actionType, mw]
|
|
||||||
)
|
|
||||||
.map(([updux, slice, actionType, mw]) =>
|
|
||||||
MiddlewareFor(actionType, sliceMw(slice, mw, updux))
|
|
||||||
);
|
|
||||||
|
|
||||||
return (api: UpduxMiddlewareAPI<S>) => {
|
type MwGen = () => UpduxMiddleware;
|
||||||
for (let type in actions) {
|
export type Effect = [string, UpduxMiddleware|MwGen, boolean? ];
|
||||||
const ac = actions[type];
|
|
||||||
api.dispatch[type] = (...args: any[]) => api.dispatch(ac(...args));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (original_next: Next) => {
|
export const subMiddleware = () => next => action => next(action);
|
||||||
return mws.reduceRight((next, mw) => mw(api)(next), original_next);
|
export const subEffects : Effect = [ '*', subMiddleware ] as any;
|
||||||
|
|
||||||
|
export const effectToMw = (
|
||||||
|
effect: Effect,
|
||||||
|
actions: Dictionary<ActionCreator>,
|
||||||
|
selectors: Dictionary<Selector>,
|
||||||
|
) => {
|
||||||
|
let [type, mw, gen]: any = effect;
|
||||||
|
|
||||||
|
if ( mw === subMiddleware ) return subMiddleware;
|
||||||
|
|
||||||
|
if (gen) mw = mw();
|
||||||
|
|
||||||
|
const augmented = api => mw({ ...api, actions, selectors });
|
||||||
|
|
||||||
|
if (type === '*') return augmented;
|
||||||
|
|
||||||
|
return api => next => action => {
|
||||||
|
if (action.type !== type) return next(action);
|
||||||
|
|
||||||
|
return augmented(api)(next)(action);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const composeMw = (mws: UpduxMiddleware[]) => (
|
||||||
|
api: UpduxMiddlewareAPI<any>
|
||||||
|
) => (original_next: Next) =>
|
||||||
|
mws.reduceRight((next, mw) => mw(api)(next), original_next);
|
||||||
|
|
||||||
|
export function buildMiddleware<S = unknown>(
|
||||||
|
local: UpduxMiddleware[] = [],
|
||||||
|
co: UpduxMiddleware[] = [],
|
||||||
|
sub: Submws = {}
|
||||||
|
): UpduxMiddleware<S> {
|
||||||
|
let inner = [
|
||||||
|
...co,
|
||||||
|
...Object.entries(sub).map(([slice, mw]) => sliceMw(slice, mw)),
|
||||||
|
];
|
||||||
|
|
||||||
|
let found = false;
|
||||||
|
let mws = local.flatMap(e => {
|
||||||
|
if (e !== subMiddleware) return e;
|
||||||
|
found = true;
|
||||||
|
return inner;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!found) mws = [...mws, ...inner];
|
||||||
|
|
||||||
|
return composeMw(mws) as UpduxMiddleware<S>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default buildMiddleware;
|
export default buildMiddleware;
|
||||||
|
67
src/buildMiddleware/test.ts
Normal file
67
src/buildMiddleware/test.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { buildMiddleware, subMiddleware } from '.';
|
||||||
|
import tap from 'tap';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
const myMiddleware = (tag: string) => api => next => action => {
|
||||||
|
next({
|
||||||
|
...action,
|
||||||
|
payload: [...action.payload, tag],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const local1 = myMiddleware('local1');
|
||||||
|
const local2 = myMiddleware('local2');
|
||||||
|
const co = myMiddleware('co');
|
||||||
|
const sub = myMiddleware('sub');
|
||||||
|
|
||||||
|
tap.test('basic', t => {
|
||||||
|
const next = sinon.fake();
|
||||||
|
|
||||||
|
const mw = buildMiddleware([local1, local2], [co], { sub });
|
||||||
|
|
||||||
|
mw({} as any)(next)({ type: 'foo', payload: [] });
|
||||||
|
|
||||||
|
t.match(next.firstCall.args, [
|
||||||
|
{
|
||||||
|
type: 'foo',
|
||||||
|
payload: ['local1', 'local2', 'co', 'sub'],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('inner in the middle', t => {
|
||||||
|
const next = sinon.fake();
|
||||||
|
|
||||||
|
const mw = buildMiddleware([local1, subMiddleware, local2], [co], {
|
||||||
|
sub,
|
||||||
|
});
|
||||||
|
|
||||||
|
mw({} as any)(next)({ type: 'foo', payload: [] });
|
||||||
|
|
||||||
|
t.match(next.firstCall.lastArg, {
|
||||||
|
type: 'foo',
|
||||||
|
payload: ['local1', 'co', 'sub', 'local2'],
|
||||||
|
});
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('sub-mw get their store sliced', t => {
|
||||||
|
const next = sinon.fake();
|
||||||
|
|
||||||
|
const sub = ({ getState }) => next => action => next(getState());
|
||||||
|
|
||||||
|
const mw = buildMiddleware([], [], { sub });
|
||||||
|
|
||||||
|
mw({
|
||||||
|
getState() {
|
||||||
|
return { foo: 1, sub: 2 };
|
||||||
|
},
|
||||||
|
} as any)(next)({ type: 'noop' });
|
||||||
|
|
||||||
|
t.same( next.firstCall.lastArg, 2 );
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
@ -1,71 +1,57 @@
|
|||||||
import fp from "lodash/fp";
|
import fp from 'lodash/fp';
|
||||||
import u from "updeep";
|
import u from 'updeep';
|
||||||
import { Mutation, Action, Dictionary, MutationEntry } from "../types";
|
import {
|
||||||
|
Mutation,
|
||||||
|
Action,
|
||||||
|
Dictionary,
|
||||||
|
MutationEntry,
|
||||||
|
Upreducer,
|
||||||
|
} from '../types';
|
||||||
|
import Updux from '..';
|
||||||
|
|
||||||
const composeMutations = (mutations: Mutation[]) =>
|
const composeMutations = (mutations: Mutation[]) => {
|
||||||
mutations.reduce((m1, m2) => (payload: any = null, action: Action) => state =>
|
if (mutations.length == 0) return () => state => state;
|
||||||
m2(payload, action)(m1(payload, action)(state))
|
|
||||||
);
|
return mutations.reduce(
|
||||||
|
(m1, m2) => (payload: any = null, action: Action) => state =>
|
||||||
|
m2(payload, action)(m1(payload, action)(state))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
type SubMutations = {
|
type SubMutations = {
|
||||||
[slice: string]: Dictionary<Mutation>;
|
[slice: string]: Dictionary<Mutation>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function buildMutations(
|
function buildMutations(
|
||||||
mutations: Dictionary<Mutation | [Mutation, boolean | undefined]> = {},
|
mutations: Dictionary<Mutation | [Mutation, boolean | undefined]> = {},
|
||||||
subduxes = {}
|
subduxes = {},
|
||||||
|
coduxes: Upreducer[] = []
|
||||||
) {
|
) {
|
||||||
// we have to differentiate the subduxes with '*' than those
|
const submuts = Object.entries(subduxes).map(
|
||||||
// without, as the root '*' is not the same as any sub-'*'
|
([slice, upreducer]: [string, any]) =>
|
||||||
|
<Mutation>(
|
||||||
|
((payload, action: Action) =>
|
||||||
|
(u.updateIn as any)(slice, upreducer(action)))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const actions = fp.uniq(
|
const comuts = coduxes.map(c => (payload, action: Action) => c(action));
|
||||||
Object.keys(mutations).concat(
|
|
||||||
...Object.values(subduxes).map(({ mutations = {} }: any) =>
|
|
||||||
Object.keys(mutations)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let mergedMutations: Dictionary<Mutation[]> = {};
|
const subreducer = composeMutations([...submuts, ...comuts]);
|
||||||
|
|
||||||
let [globby, nonGlobby] = fp.partition(
|
let merged = {};
|
||||||
([_, { mutations = {} }]: any) => mutations["*"],
|
|
||||||
Object.entries(subduxes)
|
|
||||||
);
|
|
||||||
|
|
||||||
globby = fp.flow([
|
|
||||||
fp.fromPairs,
|
|
||||||
fp.mapValues(({ reducer }) => (_: any, action: Action) => (state: any) =>
|
|
||||||
reducer(state, action)
|
|
||||||
)
|
|
||||||
])(globby);
|
|
||||||
|
|
||||||
const globbyMutation = (payload: any, action: Action) =>
|
|
||||||
u(fp.mapValues((mut: any) => mut(payload, action))(globby));
|
|
||||||
|
|
||||||
actions.forEach(action => {
|
|
||||||
mergedMutations[action] = [globbyMutation];
|
|
||||||
});
|
|
||||||
|
|
||||||
nonGlobby.forEach(([slice, { mutations = {}, reducer = {} }]: any[]) => {
|
|
||||||
Object.entries(mutations).forEach(([type, mutation]) => {
|
Object.entries(mutations).forEach(([type, mutation]) => {
|
||||||
const localized = (payload = null, action: Action) => {
|
const [m, sink] = Array.isArray(mutation)
|
||||||
return u.updateIn(slice)((mutation as Mutation)(payload, action));
|
? mutation
|
||||||
};
|
: [mutation, false];
|
||||||
|
|
||||||
mergedMutations[type].push(localized);
|
merged[type] = sink ? m : composeMutations([subreducer, m]);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
Object.entries(mutations).forEach(([type, mutation]) => {
|
if (!merged['*']) merged['*'] = subreducer;
|
||||||
if (Array.isArray(mutation)) {
|
|
||||||
if (mutation[1]) {
|
|
||||||
mergedMutations[type] = [mutation[0]];
|
|
||||||
} else mergedMutations[type].push(mutation[0]);
|
|
||||||
} else mergedMutations[type].push(mutation);
|
|
||||||
});
|
|
||||||
|
|
||||||
return fp.mapValues(composeMutations)(mergedMutations);
|
return merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default buildMutations;
|
export default buildMutations;
|
||||||
|
@ -2,24 +2,25 @@ import fp from 'lodash/fp';
|
|||||||
import Updux from '..';
|
import Updux from '..';
|
||||||
import { Dictionary, Selector } from '../types';
|
import { Dictionary, Selector } from '../types';
|
||||||
|
|
||||||
function subSelectors([slice, subdux]: [string, Updux]): [string, Selector][] {
|
function subSelectors([slice, selectors]: [string, Function]): [string, Selector][] {
|
||||||
const selectors = subdux.selectors;
|
|
||||||
if (!selectors) return [];
|
if (!selectors) return [];
|
||||||
|
|
||||||
return Object.entries(
|
return Object.entries(
|
||||||
fp.mapValues(selector => (state: any) =>
|
fp.mapValues(selector => (state: any) =>
|
||||||
(selector as any)(state[slice])
|
(selector as any)(state[slice])
|
||||||
)(selectors)
|
)(selectors as any)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function buildSelectors(
|
export default function buildSelectors(
|
||||||
localSelectors: Dictionary<Selector> = {},
|
localSelectors: Dictionary<Selector> = {},
|
||||||
subduxes: Dictionary<Updux> = {}
|
coduxes: Dictionary<Selector>[] = [],
|
||||||
|
subduxes: Dictionary<Selector> = {}
|
||||||
) {
|
) {
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
[
|
[
|
||||||
Object.entries(subduxes).flatMap(subSelectors),
|
Object.entries(subduxes).flatMap(subSelectors),
|
||||||
|
Object.entries(coduxes),
|
||||||
Object.entries(localSelectors),
|
Object.entries(localSelectors),
|
||||||
].flat()
|
].flat()
|
||||||
);
|
);
|
||||||
|
36
src/coduxes.test.ts
Normal file
36
src/coduxes.test.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import Updux from '.';
|
||||||
|
import { action } from 'ts-action';
|
||||||
|
import { Effect } from './buildMiddleware';
|
||||||
|
import tap from 'tap';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
const myEffect: Effect = [
|
||||||
|
'*',
|
||||||
|
() => next => action => next({ ...action, hello: true }),
|
||||||
|
];
|
||||||
|
|
||||||
|
const codux = new Updux({
|
||||||
|
actions: {
|
||||||
|
foo: action('foo'),
|
||||||
|
},
|
||||||
|
effects: [myEffect],
|
||||||
|
});
|
||||||
|
|
||||||
|
const dux = new Updux({
|
||||||
|
coduxes: [codux],
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('actions', t => {
|
||||||
|
t.ok(dux.actions.foo);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('effects', t => {
|
||||||
|
const next = sinon.fake();
|
||||||
|
|
||||||
|
dux.middleware({} as any)(next)({ type: 'foo' });
|
||||||
|
|
||||||
|
t.same(next.lastCall.lastArg, { type: 'foo', hello: true });
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
14
src/duxstate.test.ts
Normal file
14
src/duxstate.test.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import tap from 'tap';
|
||||||
|
import { expectType } from 'tsd';
|
||||||
|
|
||||||
|
import { dux, DuxState } from '.';
|
||||||
|
|
||||||
|
const myDux = dux({
|
||||||
|
initial: { a: 1, b: "potato" }
|
||||||
|
});
|
||||||
|
|
||||||
|
type State = DuxState<typeof myDux>;
|
||||||
|
|
||||||
|
expectType<State>({ a: 12, b: "something" });
|
||||||
|
|
||||||
|
tap.pass();
|
14
src/effects-action.test.ts
Normal file
14
src/effects-action.test.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import tap from 'tap';
|
||||||
|
import Updux from '.';
|
||||||
|
import { action, payload } from 'ts-action';
|
||||||
|
import { expectType } from 'tsd';
|
||||||
|
|
||||||
|
const dux = new Updux({ });
|
||||||
|
|
||||||
|
const myAction = action('mine',payload<string>() );
|
||||||
|
|
||||||
|
dux.addEffect( myAction, () => () => action => {
|
||||||
|
expectType<{payload: string; type: "mine"}>(action);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.pass("pure type checking");
|
18
src/index.ts
18
src/index.ts
@ -1,6 +1,22 @@
|
|||||||
import Updux from "./updux";
|
import Updux from "./updux";
|
||||||
|
import { UpduxConfig, Dux, Dictionary, Selector, Mutation, AggDuxState, Action,
|
||||||
|
Upreducer, UpduxMiddleware, DuxActions, DuxSelectors } from "./types";
|
||||||
|
|
||||||
|
import {Creator} from 'ts-action';
|
||||||
|
import { AnyAction, Store } from 'redux';
|
||||||
|
|
||||||
export { default as Updux } from "./updux";
|
export { default as Updux } from "./updux";
|
||||||
export { UpduxConfig } from "./types";
|
export { UpduxConfig, DuxState } from "./types";
|
||||||
|
export { subEffects } from './buildMiddleware';
|
||||||
|
export * from './types';
|
||||||
|
|
||||||
export default Updux;
|
export default Updux;
|
||||||
|
|
||||||
|
export const coduxes = <C extends Dux, U extends [C,...C[]]>(...coduxes: U): { coduxes: U } => ({
|
||||||
|
coduxes });
|
||||||
|
|
||||||
|
export const dux = <S=unknown,A=unknown,X=unknown,C extends UpduxConfig= {}>(config: C ) => {
|
||||||
|
/// : Dux<S,A,X,C> => {
|
||||||
|
return ( new Updux<S,A,X,C>(config) ).asDux;
|
||||||
|
}
|
||||||
|
|
||||||
|
15
src/initial.test.ts
Normal file
15
src/initial.test.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { dux } from '.';
|
||||||
|
import tap from 'tap';
|
||||||
|
|
||||||
|
const foo = dux({
|
||||||
|
initial: { root: 'abc' },
|
||||||
|
coduxes: [
|
||||||
|
dux({ initial: { co: 'works' } }),
|
||||||
|
dux({ initial: { co2: 'works' } }),
|
||||||
|
],
|
||||||
|
subduxes: {
|
||||||
|
bar: dux({ initial: 123 }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.same(foo.initial, { root: 'abc', co: 'works', co2: 'works', bar: 123 });
|
@ -1,28 +1,31 @@
|
|||||||
import Updux from './updux';
|
import Updux from './updux';
|
||||||
import u from 'updeep';
|
import u from 'updeep';
|
||||||
|
import tap from 'tap';
|
||||||
|
|
||||||
const todo = new Updux({
|
const todo: any = new Updux<any>({
|
||||||
mutations: {
|
mutations: {
|
||||||
review: () => u({ reviewed: true}),
|
review: () => u({ reviewed: true }),
|
||||||
done: () => u({done: true}),
|
done: () => u({ done: true }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const todos = new Updux({
|
const todos: any = new Updux({
|
||||||
subduxes: { '*': todo },
|
subduxes: { '*': todo },
|
||||||
});
|
});
|
||||||
|
|
||||||
todos.addMutation(
|
todos.addMutation(
|
||||||
todo.actions.done, (id,action) => u.map(u.if(u.is('id',id), todo.upreducer(action))), true
|
todo.actions.done,
|
||||||
|
(id, action) => u.map(u.if(u.is('id', id), todo.upreducer(action))),
|
||||||
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
test( '* for mapping works', () => {
|
tap.test('* for mapping works', async t => {
|
||||||
const reducer = todos.reducer;
|
const reducer = todos.reducer;
|
||||||
let state = [ { id: 0 }, {id: 1 } ];
|
let state = [{ id: 0 }, { id: 1 }];
|
||||||
state = reducer( state, todos.actions.review() );
|
state = reducer(state, todos.actions.review());
|
||||||
state = reducer( state, todos.actions.done(1) );
|
state = reducer(state, todos.actions.done(1));
|
||||||
|
|
||||||
expect(state).toEqual([
|
t.same(state, [
|
||||||
{ id: 0, reviewed: true },
|
{ id: 0, reviewed: true },
|
||||||
{ id: 1, reviewed: true, done: true },
|
{ id: 1, reviewed: true, done: true },
|
||||||
]);
|
]);
|
||||||
|
@ -1,36 +1,38 @@
|
|||||||
import u from 'updeep';
|
import u from 'updeep';
|
||||||
import { action, payload } from 'ts-action';
|
import { action } from 'ts-action';
|
||||||
|
import tap from 'tap';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
import Updux from '.';
|
import Updux, { dux, subEffects } from '.';
|
||||||
import mwUpdux from './middleware_aux';
|
import mwUpdux from './middleware_aux';
|
||||||
|
|
||||||
test('simple effect', () => {
|
tap.test('simple effect', async t => {
|
||||||
const tracer = jest.fn();
|
const tracer = sinon.fake();
|
||||||
|
|
||||||
const store = new Updux({
|
const store = new Updux({
|
||||||
effects: {
|
effects: {
|
||||||
foo: (api: any) => (next: any) => (action: any) => {
|
foo: () => (next: any) => (action: any) => {
|
||||||
tracer();
|
tracer();
|
||||||
next(action);
|
next(action);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).createStore();
|
}).createStore();
|
||||||
|
|
||||||
expect(tracer).not.toHaveBeenCalled();
|
t.ok(!tracer.called);
|
||||||
|
|
||||||
store.dispatch({ type: 'bar' });
|
store.dispatch({ type: 'bar' });
|
||||||
|
|
||||||
expect(tracer).not.toHaveBeenCalled();
|
t.ok(!tracer.called);
|
||||||
|
|
||||||
store.dispatch.foo();
|
store.dispatch( (store as any).actions.foo() );
|
||||||
|
|
||||||
expect(tracer).toHaveBeenCalled();
|
t.ok(tracer.called);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('effect and sub-effect', () => {
|
tap.test('effect and sub-effect', async t => {
|
||||||
const tracer = jest.fn();
|
const tracer = sinon.fake();
|
||||||
|
|
||||||
const tracerEffect = (signature: string) => (api: any) => (next: any) => (
|
const tracerEffect = (signature: string) => () => (next: any) => (
|
||||||
action: any
|
action: any
|
||||||
) => {
|
) => {
|
||||||
tracer(signature);
|
tracer(signature);
|
||||||
@ -42,87 +44,86 @@ test('effect and sub-effect', () => {
|
|||||||
foo: tracerEffect('root'),
|
foo: tracerEffect('root'),
|
||||||
},
|
},
|
||||||
subduxes: {
|
subduxes: {
|
||||||
zzz: {
|
zzz: dux({
|
||||||
effects: {
|
effects: {
|
||||||
foo: tracerEffect('child'),
|
foo: tracerEffect('child'),
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
}).createStore();
|
}).createStore();
|
||||||
|
|
||||||
expect(tracer).not.toHaveBeenCalled();
|
t.ok(!tracer.called);
|
||||||
|
|
||||||
store.dispatch({ type: 'bar' });
|
store.dispatch({ type: 'bar' });
|
||||||
|
|
||||||
expect(tracer).not.toHaveBeenCalled();
|
t.ok(!tracer.called);
|
||||||
|
|
||||||
store.dispatch.foo();
|
store.dispatch( (store.actions as any).foo() );
|
||||||
|
|
||||||
expect(tracer).toHaveBeenNthCalledWith(1, 'root');
|
t.is( tracer.firstCall.lastArg, 'root' );
|
||||||
expect(tracer).toHaveBeenNthCalledWith(2, 'child');
|
t.is( tracer.secondCall.lastArg, 'child' );
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('"*" effect', () => {
|
tap.test('"*" effect', async t => {
|
||||||
test('from the constructor', () => {
|
t.test('from the constructor', async t => {
|
||||||
const tracer = jest.fn();
|
const tracer = sinon.fake();
|
||||||
|
|
||||||
const store = new Updux({
|
const store = new Updux({
|
||||||
effects: {
|
effects: {
|
||||||
'*': api => next => action => {
|
'*': () => next => action => {
|
||||||
tracer();
|
tracer();
|
||||||
next(action);
|
next(action);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).createStore();
|
}).createStore();
|
||||||
|
|
||||||
expect(tracer).not.toHaveBeenCalled();
|
t.ok(!tracer.called);
|
||||||
|
|
||||||
store.dispatch({ type: 'bar' });
|
store.dispatch({ type: 'bar' });
|
||||||
|
t.ok(tracer.called);
|
||||||
|
|
||||||
expect(tracer).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('from addEffect', () => {
|
t.test('from addEffect', async t => {
|
||||||
const tracer = jest.fn();
|
const tracer = sinon.fake();
|
||||||
|
|
||||||
const updux = new Updux({});
|
const updux = new Updux({});
|
||||||
|
|
||||||
updux.addEffect('*', api => next => action => {
|
updux.addEffect('*', () => next => action => {
|
||||||
tracer();
|
tracer();
|
||||||
next(action);
|
next(action);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tracer).not.toHaveBeenCalled();
|
t.ok(!tracer.called);
|
||||||
|
|
||||||
updux.createStore().dispatch({ type: 'bar' });
|
updux.createStore().dispatch({ type: 'bar' });
|
||||||
|
|
||||||
expect(tracer).toHaveBeenCalled();
|
t.ok(tracer.called);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('action can be modified', () => {
|
t.test('action can be modified', async t => {
|
||||||
|
|
||||||
const mw = mwUpdux.middleware;
|
const mw = mwUpdux.middleware;
|
||||||
|
|
||||||
const next = jest.fn();
|
const next = sinon.fake();
|
||||||
|
|
||||||
mw({dispatch:{}} as any)(next as any)({type: 'bar'});
|
mw({dispatch:{}} as any)(next as any)({type: 'bar'});
|
||||||
|
|
||||||
expect(next).toHaveBeenCalled();
|
t.ok(next.called);
|
||||||
|
t.match( next.firstCall.args[0], {meta: 'gotcha' } );
|
||||||
expect(next.mock.calls[0][0]).toMatchObject({meta: 'gotcha'});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('async effect', async () => {
|
tap.test('async effect', async t => {
|
||||||
function timeout(ms: number) {
|
function timeout(ms: number) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
const tracer = jest.fn();
|
const tracer = sinon.fake();
|
||||||
|
|
||||||
const store = new Updux({
|
const store = new Updux({
|
||||||
effects: {
|
effects: {
|
||||||
foo: api => next => async action => {
|
foo: () => next => async action => {
|
||||||
next(action);
|
next(action);
|
||||||
await timeout(1000);
|
await timeout(1000);
|
||||||
tracer();
|
tracer();
|
||||||
@ -130,28 +131,26 @@ test('async effect', async () => {
|
|||||||
},
|
},
|
||||||
}).createStore();
|
}).createStore();
|
||||||
|
|
||||||
expect(tracer).not.toHaveBeenCalled();
|
t.ok(!tracer.called);
|
||||||
|
|
||||||
store.dispatch.foo();
|
store.dispatch( (store.actions as any).foo() );
|
||||||
|
|
||||||
expect(tracer).not.toHaveBeenCalled();
|
t.ok(!tracer.called);
|
||||||
|
|
||||||
await timeout(1000);
|
await timeout(1000);
|
||||||
|
|
||||||
expect(tracer).toHaveBeenCalled();
|
t.ok(tracer.called);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getState is local', () => {
|
tap.test('getState is local', async t => {
|
||||||
let childState;
|
let childState;
|
||||||
let rootState;
|
let rootState;
|
||||||
let rootFromChild;
|
|
||||||
|
|
||||||
const child = new Updux({
|
const child = new Updux({
|
||||||
initial: { alpha: 12 },
|
initial: { alpha: 12 },
|
||||||
effects: {
|
effects: {
|
||||||
doIt: ({ getState, getRootState }) => next => action => {
|
doIt: ({ getState }) => next => action => {
|
||||||
childState = getState();
|
childState = getState();
|
||||||
rootFromChild = getRootState();
|
|
||||||
next(action);
|
next(action);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -159,7 +158,7 @@ test('getState is local', () => {
|
|||||||
|
|
||||||
const root = new Updux({
|
const root = new Updux({
|
||||||
initial: { beta: 24 },
|
initial: { beta: 24 },
|
||||||
subduxes: { child },
|
subduxes: { child: child.asDux },
|
||||||
effects: {
|
effects: {
|
||||||
doIt: ({ getState }) => next => action => {
|
doIt: ({ getState }) => next => action => {
|
||||||
rootState = getState();
|
rootState = getState();
|
||||||
@ -169,17 +168,13 @@ test('getState is local', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const store = root.createStore();
|
const store = root.createStore();
|
||||||
store.dispatch.doIt();
|
store.dispatch( (store.actions as any).doIt() );
|
||||||
|
|
||||||
expect(rootState).toEqual({ beta: 24, child: { alpha: 12 } });
|
t.match(rootState,{ beta: 24, child: { alpha: 12 } });
|
||||||
expect(rootFromChild).toEqual({ beta: 24, child: { alpha: 12 } });
|
t.match(childState,{ alpha: 12 });
|
||||||
expect(childState).toEqual({ alpha: 12 });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('middleware as map', () => {
|
tap.test('middleware as map', async t => {
|
||||||
let childState;
|
|
||||||
let rootState;
|
|
||||||
let rootFromChild;
|
|
||||||
|
|
||||||
const doIt = action('doIt', () => ({payload: ''}));
|
const doIt = action('doIt', () => ({payload: ''}));
|
||||||
|
|
||||||
@ -200,12 +195,13 @@ test('middleware as map', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const root = new Updux({
|
const root = new Updux({
|
||||||
initial: { message: '' },
|
initial: { message: '' },
|
||||||
subduxes: { child },
|
subduxes: { child: child.asDux },
|
||||||
effects: [
|
effects: [
|
||||||
[
|
[
|
||||||
'^',
|
'*',
|
||||||
() => next => action => {
|
() => next => action => {
|
||||||
next(
|
next(
|
||||||
u({ payload: (p: string) => p + 'Pre' }, action) as any
|
u({ payload: (p: string) => p + 'Pre' }, action) as any
|
||||||
@ -231,8 +227,9 @@ test('middleware as map', () => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
subEffects,
|
||||||
[
|
[
|
||||||
'$',
|
'*',
|
||||||
() => next => action => {
|
() => next => action => {
|
||||||
next(
|
next(
|
||||||
u({ payload: (p: string) => p + 'End' }, action) as any
|
u({ payload: (p: string) => p + 'End' }, action) as any
|
||||||
@ -244,12 +241,13 @@ test('middleware as map', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const store = root.createStore();
|
const store = root.createStore();
|
||||||
store.dispatch.doIt('');
|
const actions: any = store.actions;
|
||||||
|
store.dispatch( actions.doIt('') );
|
||||||
|
|
||||||
expect(store.getState()).toEqual({ message: 'PreRootAfterChildEnd' });
|
t.same(store.getState(),{ message: 'PreRootAfterChildEnd' });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generator', () => {
|
tap.test('generator', async t => {
|
||||||
const updux = new Updux({
|
const updux = new Updux({
|
||||||
initial: 0,
|
initial: 0,
|
||||||
mutations: [['doIt', payload => () => payload]],
|
mutations: [['doIt', payload => () => payload]],
|
||||||
@ -267,13 +265,14 @@ test('generator', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const store1 = updux.createStore();
|
const store1 = updux.createStore();
|
||||||
store1.dispatch.doIt();
|
store1.dispatch( (store1 as any).actions.doIt() );
|
||||||
expect(store1.getState()).toEqual(1);
|
|
||||||
store1.dispatch.doIt();
|
t.is(store1.getState(),1);
|
||||||
expect(store1.getState()).toEqual(2);
|
store1.dispatch( (store1 as any).actions.doIt() );
|
||||||
updux.actions;
|
|
||||||
|
t.is(store1.getState(),2);
|
||||||
|
|
||||||
const store2 = updux.createStore();
|
const store2 = updux.createStore();
|
||||||
store2.dispatch.doIt();
|
store2.dispatch( (store2 as any).actions.doIt() );
|
||||||
expect(store2.getState()).toEqual(1);
|
t.is(store2.getState(),1);
|
||||||
});
|
});
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import Updux from '.';
|
import Updux, {dux} from '.';
|
||||||
|
|
||||||
const updux = new Updux({
|
const updux = new Updux({
|
||||||
subduxes: {
|
subduxes: {
|
||||||
foo: { initial: "banana" }
|
foo: dux({ initial: "banana" })
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
updux.addEffect('*', api => next => action => {
|
updux.addEffect('*', () => next => action => {
|
||||||
next({...action, meta: "gotcha" });
|
next({...action, meta: "gotcha" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,27 +1,88 @@
|
|||||||
import { action } from 'ts-action';
|
import { action, empty } from 'ts-action';
|
||||||
|
import Updux, { dux } from '.';
|
||||||
|
import tap from 'tap';
|
||||||
|
|
||||||
import Updux from "./updux";
|
import u from 'updeep';
|
||||||
|
|
||||||
describe("as array of arrays", () => {
|
tap.test('as array of arrays', async t => {
|
||||||
const doIt = action("doIt");
|
const doIt = action('doIt');
|
||||||
|
|
||||||
const updux = new Updux({
|
const updux = new Updux({
|
||||||
initial: "",
|
initial: '',
|
||||||
mutations: [
|
actions: { doIt },
|
||||||
[doIt, () => () => "bingo"],
|
mutations: [
|
||||||
["thisToo", () => () => "straight type"]
|
[doIt, () => () => 'bingo'],
|
||||||
]
|
['thisToo', () => () => 'straight type'],
|
||||||
});
|
],
|
||||||
|
});
|
||||||
|
|
||||||
const store = updux.createStore();
|
const store = updux.createStore();
|
||||||
|
|
||||||
test("doIt", () => {
|
t.test('doIt', async t => {
|
||||||
store.dispatch.doIt();
|
store.dispatch( store.actions.doIt() );
|
||||||
expect(store.getState()).toEqual("bingo");
|
t.is(store.getState(),'bingo');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("straight type", () => {
|
t.test('straight type', async t => {
|
||||||
store.dispatch.thisToo();
|
store.dispatch( (store.actions as any).thisToo() );
|
||||||
expect(store.getState()).toEqual("straight type");
|
t.is(store.getState(),'straight type');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('override', async t => {
|
||||||
|
const d = new Updux<any>({
|
||||||
|
initial: { alpha: [] },
|
||||||
|
mutations: {
|
||||||
|
'*': (payload, action) => state => ({
|
||||||
|
...state,
|
||||||
|
alpha: [...state.alpha, action.type],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
subduxes: {
|
||||||
|
subbie: dux({
|
||||||
|
initial: 0,
|
||||||
|
mutations: {
|
||||||
|
foo: () => state => state + 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = d.createStore();
|
||||||
|
store.dispatch({ type: 'foo' });
|
||||||
|
store.dispatch({ type: 'bar' });
|
||||||
|
|
||||||
|
t.pass();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('coduxes and subduxes', async t => {
|
||||||
|
const foo = action('foo',empty());
|
||||||
|
|
||||||
|
const d = new Updux({
|
||||||
|
initial: {
|
||||||
|
x: '',
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
foo
|
||||||
|
},
|
||||||
|
mutations: [[foo, () => (u.updateIn as any)('x', x => x + 'm')]],
|
||||||
|
subduxes: {
|
||||||
|
x: dux({
|
||||||
|
mutations: [[foo, () => x => x + 's']],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
coduxes: [
|
||||||
|
dux({
|
||||||
|
mutations: [[foo, () => (u.updateIn as any)('x', x => x + 'c')]],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = d.createStore();
|
||||||
|
|
||||||
|
store.dispatch(d.actions.foo());
|
||||||
|
|
||||||
|
t.same(store.getState(),{
|
||||||
|
x: 'scm',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
import Updux from '.';
|
import { test } from 'tap';
|
||||||
|
import Updux, { dux, coduxes, DuxState } from '.';
|
||||||
|
import { expectType } from 'tsd';
|
||||||
|
|
||||||
test('basic selectors', () => {
|
test('basic selectors', async t => {
|
||||||
const updux = new Updux({
|
const updux = dux({
|
||||||
subduxes: {
|
subduxes: {
|
||||||
bogeys: {
|
bogeys: dux({
|
||||||
selectors: {
|
selectors: {
|
||||||
bogey: (bogeys: any) => (id: string) => bogeys[id],
|
bogey: (bogeys: any) => (id: string) => bogeys[id],
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
selectors: {
|
selectors: {
|
||||||
bogeys: ({ bogeys }: any) => bogeys,
|
bogeys: ({ bogeys }) => bogeys,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -21,19 +23,19 @@ test('basic selectors', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(updux.selectors.bogeys(state)).toEqual({ foo: 1, bar: 2 });
|
t.same(updux.selectors.bogeys(state), { foo: 1, bar: 2 });
|
||||||
expect((updux.selectors.bogey(state) as any)('foo')).toEqual(1);
|
t.equal(updux.selectors.bogey(state)('foo'), 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('available in the middleware', () => {
|
test('available in the middleware', async t => {
|
||||||
const updux = new Updux({
|
const updux = dux<any, any>({
|
||||||
subduxes: {
|
subduxes: {
|
||||||
bogeys: {
|
bogeys: dux({
|
||||||
initial: { enkidu: 'foo' },
|
initial: { enkidu: 'foo' },
|
||||||
selectors: {
|
selectors: {
|
||||||
bogey: (bogeys: any) => (id: string) => bogeys[id],
|
bogey: (bogeys: any) => (id: string) => bogeys[id],
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
effects: {
|
effects: {
|
||||||
doIt: ({ selectors: { bogey }, getState }) => next => action => {
|
doIt: ({ selectors: { bogey }, getState }) => next => action => {
|
||||||
@ -49,7 +51,85 @@ test('available in the middleware', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const store = updux.createStore();
|
const store = updux.createStore();
|
||||||
store.dispatch.doIt();
|
store.dispatch(updux.actions.doIt());
|
||||||
|
|
||||||
expect(store.getState()).toMatchObject({ payload: 'foo' });
|
t.match(store.getState(), { payload: 'foo' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('selector typescript', async t => {
|
||||||
|
const bar = dux({
|
||||||
|
initial: { baz: 1 } as { baz: number },
|
||||||
|
selectors: {
|
||||||
|
getBaz: (state: { baz: number }) => state.baz,
|
||||||
|
getStringBaz: state => `${state.baz}`,
|
||||||
|
getMultBaz: state => (mult: number) => state.baz * mult,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expectType<{
|
||||||
|
getBaz: Function;
|
||||||
|
getStringBaz: Function;
|
||||||
|
}>(bar.selectors);
|
||||||
|
|
||||||
|
t.same(bar.selectors.getBaz(bar.initial), 1);
|
||||||
|
t.same(bar.selectors.getMultBaz({ baz: 3 })(2), 6);
|
||||||
|
|
||||||
|
test('subduxes', async t => {
|
||||||
|
const foo = dux({
|
||||||
|
subduxes: { bar },
|
||||||
|
...coduxes( dux({}) ),
|
||||||
|
selectors: {
|
||||||
|
getRoot: () => 'root'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expectType<{
|
||||||
|
({ bar: { baz: number } }): number;
|
||||||
|
}>(foo.selectors.getBaz);
|
||||||
|
|
||||||
|
t.same(foo.selectors.getBaz(foo.initial), 1);
|
||||||
|
t.same(foo.selectors.getMultBaz({ bar: { baz: 3 } })(2), 6);
|
||||||
|
|
||||||
|
t.ok( foo.selectors.getRoot );
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no root selector', async t => {
|
||||||
|
const foo = dux({
|
||||||
|
subduxes: {
|
||||||
|
quux: dux({}),
|
||||||
|
bar: dux({
|
||||||
|
selectors: {
|
||||||
|
getBaz: () => 'baz'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
t.ok(foo.selectors.getBaz);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('selector in mw', async () => {
|
||||||
|
const myDux = new Updux(
|
||||||
|
{
|
||||||
|
initial: { stuff: 12 },
|
||||||
|
subduxes: {
|
||||||
|
bar: dux({
|
||||||
|
initial: 'potato',
|
||||||
|
selectors: { getBar: () => 'meh' }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
// TODO here we should auto-populate the state
|
||||||
|
getStuff: (state: {stuff: number}) => state.stuff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
myDux.addEffect( '*', ({
|
||||||
|
selectors, getState
|
||||||
|
}) => () => () => {
|
||||||
|
expectType<DuxState<typeof myDux>>( getState() );
|
||||||
|
expectType<(...args:any[]) => number>(selectors.getStuff);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,56 +1,57 @@
|
|||||||
import Updux from './updux';
|
import Updux, { dux } from '.';
|
||||||
|
import tap from 'tap';
|
||||||
|
import { action } from 'ts-action';
|
||||||
|
|
||||||
const foo = new Updux<number>({
|
const foo = dux({
|
||||||
initial: 0,
|
initial: 0,
|
||||||
mutations: {
|
actions: {
|
||||||
doIt: () => (state: number) => {
|
doIt: action('doIt'),
|
||||||
return state + 1;
|
doTheThing: action('doTheThing'),
|
||||||
},
|
},
|
||||||
doTheThing: () => (state: number) => {
|
mutations: {
|
||||||
return state + 3;
|
doIt: () => (state: number) => {
|
||||||
|
return state + 1;
|
||||||
|
},
|
||||||
|
doTheThing: () => (state: number) => {
|
||||||
|
return state + 3;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const bar = new Updux<{foo: number}>({
|
const bar: any = new Updux<{ foo: number }>({
|
||||||
subduxes: {foo},
|
subduxes: { foo },
|
||||||
});
|
});
|
||||||
|
|
||||||
bar.addMutation(
|
bar.addMutation(
|
||||||
foo.actions.doTheThing,
|
foo.actions.doTheThing,
|
||||||
(_, action) => state => {
|
(_, action) => state => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
baz: bar.subduxUpreducer(action)(state),
|
baz: foo.upreducer(action)(state.foo),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
true,
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
bar.addMutation(
|
bar.addMutation(
|
||||||
foo.actions.doIt,
|
foo.actions.doIt,
|
||||||
() => (state: any) => ({...state, bar: 'yay'}),
|
() => (state: any) => ({ ...state, bar: 'yay' }),
|
||||||
true,
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
test('initial', () => {
|
tap.same(bar.initial, { foo: 0 });
|
||||||
expect(bar.initial).toEqual({foo: 0});
|
|
||||||
|
tap.test('foo alone', t => {
|
||||||
|
t.is(foo.reducer(undefined, foo.actions.doIt()), 1);
|
||||||
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('foo alone', () => {
|
tap.test('sink mutations', t => {
|
||||||
expect(foo.reducer(undefined, foo.actions.doIt())).toEqual(1);
|
t.same(
|
||||||
});
|
bar.reducer(undefined, bar.actions.doIt()), {
|
||||||
|
foo: 0,
|
||||||
|
bar: 'yay',
|
||||||
|
});
|
||||||
|
|
||||||
test('sink mutations', () => {
|
t.end();
|
||||||
expect(bar.reducer(undefined, bar.actions.doIt())).toEqual({
|
|
||||||
foo: 0,
|
|
||||||
bar: 'yay',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('sink mutation and subduxUpreducer', () => {
|
|
||||||
expect(bar.reducer(undefined, bar.actions.doTheThing())).toEqual({
|
|
||||||
foo: 0,
|
|
||||||
baz: {foo: 3},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -1,63 +1,113 @@
|
|||||||
import Updux from '.';
|
import tap from 'tap';
|
||||||
|
import Updux, {dux} from '.';
|
||||||
import u from 'updeep';
|
import u from 'updeep';
|
||||||
|
import { expectType } from 'tsd';
|
||||||
|
|
||||||
const tracer = (chr:string) => u({ tracer: (s='') => s + chr });
|
const tracer = (chr: string) => u({ tracer: (s = '') => s + chr });
|
||||||
|
|
||||||
test( 'mutations, simple', () => {
|
tap.test('mutations, simple', t => {
|
||||||
const dux = new Updux({
|
const dux : any = new Updux({
|
||||||
mutations: {
|
mutations: {
|
||||||
foo: () => tracer('a'),
|
foo: () => tracer('a'),
|
||||||
'*': () => tracer('b'),
|
'*': () => tracer('b'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const store = dux.createStore();
|
const store = dux.createStore();
|
||||||
|
|
||||||
expect(store.getState()).toEqual({ tracer: 'b'});
|
t.same(store.getState(),{ tracer: 'b' });
|
||||||
|
|
||||||
store.dispatch.foo();
|
store.dispatch( store.actions.foo() );
|
||||||
|
|
||||||
expect(store.getState()).toEqual({ tracer: 'ba', });
|
t.same(store.getState(),{ tracer: 'ba' });
|
||||||
|
|
||||||
store.dispatch({ type: 'bar' });
|
store.dispatch({ type: 'bar' });
|
||||||
|
|
||||||
expect(store.getState()).toEqual({ tracer: 'bab', });
|
t.same(store.getState(),{ tracer: 'bab' });
|
||||||
|
|
||||||
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
test( 'with subduxes', () => {
|
tap.test('with subduxes', t => {
|
||||||
const dux = new Updux({
|
const d = dux({
|
||||||
mutations: {
|
mutations: {
|
||||||
foo: () => tracer('a'),
|
foo: () => tracer('a'),
|
||||||
'*': () => tracer('b'),
|
'*': () => tracer('b'),
|
||||||
bar: () => ({bar}:any) => ({ bar, tracer: bar.tracer })
|
bar: () => ({ bar }: any) => ({ bar, tracer: bar.tracer }),
|
||||||
},
|
},
|
||||||
subduxes: {
|
subduxes: {
|
||||||
bar: {
|
bar: dux({
|
||||||
mutations: {
|
mutations: {
|
||||||
foo: () => tracer('d'),
|
foo: () => tracer('d'),
|
||||||
'*': () => tracer('e'),
|
'*': () => tracer('e'),
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const store = dux.createStore();
|
const store = d.createStore();
|
||||||
|
|
||||||
expect(store.getState()).toEqual({
|
t.same(store.getState(),{
|
||||||
tracer: 'b',
|
tracer: 'b',
|
||||||
bar: { tracer: 'e' } });
|
bar: { tracer: 'e' },
|
||||||
|
});
|
||||||
|
|
||||||
store.dispatch.foo();
|
store.dispatch( (store.actions as any).foo() );
|
||||||
|
|
||||||
expect(store.getState()).toEqual({
|
t.same(store.getState(),{
|
||||||
tracer: 'ba',
|
tracer: 'ba',
|
||||||
bar: { tracer: 'ed' } });
|
bar: { tracer: 'ed' },
|
||||||
|
});
|
||||||
|
|
||||||
store.dispatch({type: 'bar'});
|
store.dispatch({ type: 'bar' });
|
||||||
|
|
||||||
expect(store.getState()).toEqual({
|
t.same(store.getState(),{
|
||||||
tracer: 'ede',
|
tracer: 'ede',
|
||||||
bar: { tracer: 'ede' } });
|
bar: { tracer: 'ede' },
|
||||||
|
});
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test( 'splat and state', async t => {
|
||||||
|
|
||||||
|
const inner = dux({ initial: 3 });
|
||||||
|
inner.initial;
|
||||||
|
|
||||||
|
const arrayDux = dux({
|
||||||
|
initial: [],
|
||||||
|
subduxes: {
|
||||||
|
'*': inner
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expectType<number[]>(arrayDux.initial)
|
||||||
|
t.same(arrayDux.initial,[]);
|
||||||
|
|
||||||
|
const objDux = dux({
|
||||||
|
initial: {},
|
||||||
|
subduxes: {
|
||||||
|
'*': dux({initial: 3})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expectType<{
|
||||||
|
[key: string]: number
|
||||||
|
}>(objDux.initial);
|
||||||
|
|
||||||
|
t.same(objDux.initial,{});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test( 'multi-splat', async t => {
|
||||||
|
|
||||||
|
dux({
|
||||||
|
subduxes: {
|
||||||
|
foo: dux({ mutations: { '*': () => s => s }}),
|
||||||
|
bar: dux({ mutations: { '*': () => s => s }}),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
t.pass();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
115
src/test.ts
115
src/test.ts
@ -1,23 +1,27 @@
|
|||||||
import Updux from '.';
|
import Updux, {dux} from '.';
|
||||||
|
import u from 'updeep';
|
||||||
|
import { action, payload } from 'ts-action';
|
||||||
|
import tap from 'tap';
|
||||||
|
|
||||||
test('actions from mutations', () => {
|
tap.test('actions from mutations', async t => {
|
||||||
const {
|
const {
|
||||||
actions: {foo, bar},
|
actions: {foo, bar},
|
||||||
} = new Updux({
|
} : any = new Updux({
|
||||||
mutations: {
|
mutations: {
|
||||||
foo: () => (x:any) => x,
|
foo: () => (x:any) => x,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(foo()).toEqual({type: 'foo'});
|
t.match( foo(), { type: 'foo' } );
|
||||||
|
t.same( foo(true), {type: 'foo', payload: true});
|
||||||
expect(foo(true)).toEqual({type: 'foo', payload: true});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('reducer', () => {
|
tap.test('reducer', async t => {
|
||||||
const {actions, reducer} = new Updux({
|
const inc = action('inc');
|
||||||
|
const {actions, reducer} = dux({
|
||||||
initial: {counter: 1},
|
initial: {counter: 1},
|
||||||
|
actions: { inc },
|
||||||
mutations: {
|
mutations: {
|
||||||
inc: () => ({counter}:{counter:number}) => ({counter: counter + 1}),
|
inc: () => ({counter}:{counter:number}) => ({counter: counter + 1}),
|
||||||
},
|
},
|
||||||
@ -25,24 +29,28 @@ test('reducer', () => {
|
|||||||
|
|
||||||
let state = reducer(undefined, {type:'noop'});
|
let state = reducer(undefined, {type:'noop'});
|
||||||
|
|
||||||
expect(state).toEqual({counter: 1});
|
t.same(state,{counter: 1});
|
||||||
|
|
||||||
state = reducer(state, actions.inc());
|
state = reducer(state, actions.inc());
|
||||||
|
|
||||||
expect(state).toEqual({counter: 2});
|
t.same(state,{counter: 2});
|
||||||
});
|
});
|
||||||
|
|
||||||
test( 'sub reducers', () => {
|
tap.test( 'sub reducers', async t => {
|
||||||
const foo = new Updux({
|
const doAll = action('doAll', payload<number>() );
|
||||||
|
|
||||||
|
const foo = dux({
|
||||||
initial: 1,
|
initial: 1,
|
||||||
|
actions: { doAll },
|
||||||
mutations: {
|
mutations: {
|
||||||
doFoo: () => (x:number) => x + 1,
|
doFoo: () => (x:number) => x + 1,
|
||||||
doAll: () => (x:number) => x + 10,
|
doAll: () => (x:number) => x + 10,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const bar = new Updux({
|
const bar = dux({
|
||||||
initial: 'a',
|
initial: 'a',
|
||||||
|
actions: { doAll },
|
||||||
mutations: {
|
mutations: {
|
||||||
doBar: () => (x:string) => x + 'a',
|
doBar: () => (x:string) => x + 'a',
|
||||||
doAll: () => (x:string) => x + 'b',
|
doAll: () => (x:string) => x + 'b',
|
||||||
@ -55,34 +63,34 @@ test( 'sub reducers', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(initial).toEqual({ foo: 1, bar: 'a' });
|
t.same(initial,{ foo: 1, bar: 'a' });
|
||||||
|
|
||||||
expect(Object.keys(actions)).toHaveLength(3);
|
t.is(Object.keys(actions).length,3);
|
||||||
|
|
||||||
let state = reducer(undefined,{type:'noop'});
|
let state = reducer(undefined,{type:'noop'});
|
||||||
|
|
||||||
expect(state).toEqual({ foo: 1, bar: 'a' });
|
t.same(state,{ foo: 1, bar: 'a' });
|
||||||
|
|
||||||
state = reducer(state, actions.doFoo() );
|
state = reducer(state, (actions as any).doFoo() );
|
||||||
|
|
||||||
expect(state).toEqual({ foo: 2, bar: 'a' });
|
t.same(state,{ foo: 2, bar: 'a' });
|
||||||
|
|
||||||
state = reducer(state, actions.doBar() );
|
state = reducer(state, (actions as any).doBar() );
|
||||||
|
|
||||||
expect(state).toEqual({ foo: 2, bar: 'aa' });
|
t.same(state,{ foo: 2, bar: 'aa' });
|
||||||
|
|
||||||
state = reducer(state, actions.doAll() );
|
state = reducer(state, (actions as any).doAll() );
|
||||||
|
|
||||||
expect(state).toEqual({ foo: 12, bar: 'aab' });
|
t.same(state,{ foo: 12, bar: 'aab' });
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('precedence between root and sub-reducers', () => {
|
tap.test('precedence between root and sub-reducers', async t => {
|
||||||
const {
|
const {
|
||||||
initial,
|
initial,
|
||||||
reducer,
|
reducer,
|
||||||
actions,
|
actions,
|
||||||
} = new Updux({
|
} = dux({
|
||||||
initial: {
|
initial: {
|
||||||
foo: { bar: 4 },
|
foo: { bar: 4 },
|
||||||
},
|
},
|
||||||
@ -95,7 +103,7 @@ test('precedence between root and sub-reducers', () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
subduxes: {
|
subduxes: {
|
||||||
foo: {
|
foo: dux({
|
||||||
initial: {
|
initial: {
|
||||||
bar: 2,
|
bar: 2,
|
||||||
quux: 3,
|
quux: 3,
|
||||||
@ -103,15 +111,16 @@ test('precedence between root and sub-reducers', () => {
|
|||||||
mutations: {
|
mutations: {
|
||||||
inc: () => (state:any) => ({...state, bar: state.bar + 1 })
|
inc: () => (state:any) => ({...state, bar: state.bar + 1 })
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(initial).toEqual({
|
// quick fix until https://github.com/facebook/jest/issues/9531
|
||||||
|
t.same(initial,{
|
||||||
foo: { bar: 4, quux: 3 }
|
foo: { bar: 4, quux: 3 }
|
||||||
});
|
});
|
||||||
|
|
||||||
expect( reducer(undefined,actions.inc() ) ).toEqual({
|
t.same( reducer(undefined,(actions as any).inc() ) ,{
|
||||||
foo: { bar: 5, quux: 3 }, surprise: 5
|
foo: { bar: 5, quux: 3 }, surprise: 5
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -121,67 +130,73 @@ function timeout(ms:number) {
|
|||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
test( 'middleware', async () => {
|
tap.test( 'middleware', async t => {
|
||||||
const {
|
const {
|
||||||
middleware,
|
middleware,
|
||||||
createStore
|
createStore,
|
||||||
} = new Updux({
|
actions,
|
||||||
initial: "",
|
} = dux({
|
||||||
|
initial: { result: [] },
|
||||||
mutations: {
|
mutations: {
|
||||||
inc: (addition:number) => (state:number) => state + addition,
|
inc: (addition:number) => u({ result: r => [ ...r, addition ]}),
|
||||||
doEeet: () => (state:number) => {
|
doEeet: () => u({ result: r => [ ...r, 'Z' ]}),
|
||||||
return state + 'Z';
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
effects: {
|
effects: {
|
||||||
doEeet: api => next => async action => {
|
doEeet: api => next => async action => {
|
||||||
api.dispatch.inc('a');
|
api.dispatch( api.actions.inc('a') );
|
||||||
next(action);
|
next(action);
|
||||||
await timeout(1000);
|
await timeout(1000);
|
||||||
api.dispatch.inc('c');
|
api.dispatch( api.actions.inc('c') );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
subduxes: {
|
subduxes: {
|
||||||
foo: {
|
foo: dux({
|
||||||
effects: {
|
effects: {
|
||||||
doEeet: (api:any) => ( next:any ) => ( action: any ) => {
|
doEeet: (api:any) => ( next:any ) => ( action: any ) => {
|
||||||
api.dispatch({ type: 'inc', payload: 'b'});
|
api.dispatch({ type: 'inc', payload: 'b'});
|
||||||
next(action);
|
next(action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const store = createStore();
|
const store :any = createStore();
|
||||||
|
|
||||||
store.dispatch.doEeet();
|
store.dispatch( (actions as any).doEeet() );
|
||||||
|
|
||||||
expect(store.getState()).toEqual( 'abZ' );
|
t.is(store.getState().result.join(''),'abZ' );
|
||||||
|
|
||||||
await timeout(1000);
|
await timeout(1000);
|
||||||
|
|
||||||
expect(store.getState()).toEqual( 'abZc' );
|
t.is(store.getState().result.join(''), 'abZc' );
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test( "subduxes and mutations", () => {
|
tap.test( "subduxes and mutations", async t => {
|
||||||
const foo = new Updux({ mutations: {
|
const quux = action('quux');
|
||||||
|
|
||||||
|
const foo = dux({
|
||||||
|
actions: { quux },
|
||||||
|
mutations: {
|
||||||
quux: () => () => 'x',
|
quux: () => () => 'x',
|
||||||
blart: () => () => 'a',
|
blart: () => () => 'a',
|
||||||
}});
|
}});
|
||||||
const bar = new Updux({ mutations: {
|
const bar = dux({
|
||||||
|
actions: { quux },
|
||||||
|
mutations: {
|
||||||
quux: () => () => 'y'
|
quux: () => () => 'y'
|
||||||
}});
|
}});
|
||||||
const baz = new Updux({
|
const baz = dux({
|
||||||
|
actions: { quux },
|
||||||
mutations: {
|
mutations: {
|
||||||
quux: () => (state:any) => ({...state, "baz": "z" })
|
quux: () => (state:any) => ({...state, "baz": "z" })
|
||||||
}, subduxes: { foo, bar } });
|
}, subduxes: { foo, bar } });
|
||||||
|
|
||||||
let state = baz.reducer(undefined, baz.actions.quux() );
|
let state = baz.reducer(undefined, (baz.actions as any).quux() );
|
||||||
|
|
||||||
expect(state).toEqual({
|
t.same(state,{
|
||||||
foo: "x",
|
foo: "x",
|
||||||
bar: "y",
|
bar: "y",
|
||||||
baz: "z",
|
baz: "z",
|
||||||
|
34
src/tutorial/todos.test.ts
Normal file
34
src/tutorial/todos.test.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import tap from 'tap';
|
||||||
|
|
||||||
|
import Updux from '..';
|
||||||
|
import {action, payload} from 'ts-action';
|
||||||
|
|
||||||
|
type Todo = {
|
||||||
|
id: number;
|
||||||
|
description: string;
|
||||||
|
done: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TodoStore = {
|
||||||
|
next_id: number;
|
||||||
|
todos: Todo[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const todosUpdux = new Updux({
|
||||||
|
initial: {
|
||||||
|
next_id: 1,
|
||||||
|
todos: [],
|
||||||
|
} as TodoStore
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
tap.test('initial state', async t => {
|
||||||
|
const store = todosUpdux.createStore();
|
||||||
|
|
||||||
|
t.like(store.getState(), {
|
||||||
|
next_id: 1,
|
||||||
|
todos: [],
|
||||||
|
}, 'initial state'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
521
src/types.ts
521
src/types.ts
@ -1,209 +1,362 @@
|
|||||||
import { Dispatch, Middleware } from "redux";
|
import { ActionCreator } from 'ts-action';
|
||||||
|
|
||||||
type MaybePayload<P> = P extends object | string | boolean | number
|
type MaybePayload<P> = P extends object | string | boolean | number
|
||||||
? {
|
? {
|
||||||
payload: P;
|
payload: P;
|
||||||
}
|
}
|
||||||
: { payload?: P };
|
: { payload?: P };
|
||||||
|
|
||||||
export type Action<T extends string = string, P = any> = {
|
export type Action<T extends string = string, P = any> = {
|
||||||
type: T;
|
type: T;
|
||||||
} & MaybePayload<P>;
|
} & MaybePayload<P>;
|
||||||
|
|
||||||
export type Dictionary<T> = { [key: string]: T };
|
export type Dictionary<T> = { [key: string]: T };
|
||||||
|
|
||||||
export type Mutation<S = any, A extends Action = Action> = (
|
export type Mutation<S = any, A extends Action = Action> = (
|
||||||
payload: A["payload"],
|
payload: A['payload'],
|
||||||
action: A
|
action: A
|
||||||
) => (state: S) => S;
|
) => (state: S) => S;
|
||||||
|
|
||||||
export type ActionPayloadGenerator = (...args: any[]) => any;
|
export type ActionPayloadGenerator = (...args: any[]) => any;
|
||||||
|
|
||||||
export type MutationEntry = [
|
export type MutationEntry = [
|
||||||
ActionCreator | string,
|
ActionCreator | string,
|
||||||
Mutation<any, Action<string, any>>,
|
Mutation<any, Action<string, any>>,
|
||||||
boolean?
|
boolean?
|
||||||
];
|
];
|
||||||
|
|
||||||
export type ActionCreator<T extends string = string, P = any> = {
|
export type GenericActions = Dictionary<
|
||||||
type: T;
|
ActionCreator<string, (...args: any) => { type: string }>
|
||||||
_genericAction?: boolean;
|
>;
|
||||||
} & ((...args: any[]) => Action<T, P>);
|
|
||||||
|
|
||||||
export type UpduxDispatch = Dispatch & Dictionary<Function>;
|
export type UnionToIntersection<U> = (U extends any
|
||||||
|
? (k: U) => void
|
||||||
|
: never) extends (k: infer I) => void
|
||||||
|
? I
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type StateOf<D> = D extends { initial: infer I } ? I : unknown;
|
||||||
|
|
||||||
|
export type DuxStateCoduxes<C> = C extends Array<infer U> ? UnionToIntersection<StateOf<U>>: unknown
|
||||||
|
export type DuxStateSubduxes<C> =
|
||||||
|
C extends { '*': infer I } ? {
|
||||||
|
[ key: string ]: StateOf<I>,
|
||||||
|
[ index: number ]: StateOf<I>,
|
||||||
|
} :
|
||||||
|
C extends object ? { [ K in keyof C]: StateOf<C[K]>}: unknown;
|
||||||
|
|
||||||
|
type DuxStateGlobSub<S> = S extends { '*': infer I } ? StateOf<I> : unknown;
|
||||||
|
|
||||||
|
type LocalDuxState<S> = S extends never[] ? unknown[] : S;
|
||||||
|
|
||||||
|
/** @ignore */
|
||||||
|
type AggDuxState2<L,S,C> = (
|
||||||
|
L extends never[] ? Array<DuxStateGlobSub<S>> : L & DuxStateSubduxes<S> ) & DuxStateCoduxes<C>;
|
||||||
|
|
||||||
|
/** @ignore */
|
||||||
|
export type AggDuxState<O,S extends UpduxConfig> = unknown extends O ?
|
||||||
|
AggDuxState2<S['initial'],S['subduxes'],S['coduxes']> : O
|
||||||
|
|
||||||
|
|
||||||
|
type SelectorsOf<C> = C extends { selectors: infer S } ? S : unknown;
|
||||||
|
|
||||||
|
/** @ignore */
|
||||||
|
export type DuxSelectorsSubduxes<C> = C extends object ? UnionToIntersection<SelectorsOf<C[keyof C]>> : unknown;
|
||||||
|
|
||||||
|
/** @ignore */
|
||||||
|
export type DuxSelectorsCoduxes<C> = C extends Array<infer U> ? UnionToIntersection<SelectorsOf<U>> : unknown;
|
||||||
|
|
||||||
|
type MaybeReturnType<X> = X extends (...args: any) => any ? ReturnType<X> : unknown;
|
||||||
|
|
||||||
|
type RebaseSelector<S,X> = {
|
||||||
|
[ K in keyof X]: (state: S) => MaybeReturnType< X[K] >
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionsOf<C> = C extends { actions: infer A } ? A : {};
|
||||||
|
|
||||||
|
type DuxActionsSubduxes<C> = C extends object ? ActionsOf<C[keyof C]> : unknown;
|
||||||
|
export type DuxActionsCoduxes<C> = C extends Array<infer I> ? UnionToIntersection<ActionsOf<I>> : {};
|
||||||
|
|
||||||
|
type ItemsOf<C> = C extends object? C[keyof C] : unknown
|
||||||
|
|
||||||
|
export type DuxActions<A,C extends UpduxConfig> = A extends object ? A: (
|
||||||
|
UnionToIntersection<ActionsOf<C|ItemsOf<C['subduxes']>|ItemsOf<C['coduxes']>>>
|
||||||
|
);
|
||||||
|
|
||||||
|
export type DuxSelectors<S,X,C extends UpduxConfig> = unknown extends X ? (
|
||||||
|
RebaseSelector<S,
|
||||||
|
C['selectors'] & DuxSelectorsCoduxes<C['coduxes']> &
|
||||||
|
DuxSelectorsSubduxes<C['subduxes']> >
|
||||||
|
): X
|
||||||
|
|
||||||
|
export type Dux<
|
||||||
|
S = unknown,
|
||||||
|
A = unknown,
|
||||||
|
X = unknown,
|
||||||
|
C = unknown,
|
||||||
|
> = {
|
||||||
|
subduxes: Dictionary<Dux>,
|
||||||
|
coduxes: Dux[],
|
||||||
|
initial: AggDuxState<S,C>,
|
||||||
|
actions: A,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration object given to Updux's constructor.
|
* Configuration object given to Updux's constructor.
|
||||||
* @typeparam S Type of the Updux's state. Defaults to `any`.
|
*
|
||||||
*/
|
* #### arguments
|
||||||
export type UpduxConfig<S = any> = {
|
*
|
||||||
/**
|
* ##### initial
|
||||||
* The default initial state of the reducer. Can be anything your
|
*
|
||||||
* heart desires.
|
* Default initial state of the reducer. If applicable, is merged with
|
||||||
*/
|
* the subduxes initial states, with the parent having precedence.
|
||||||
initial?: S;
|
*
|
||||||
|
* If not provided, defaults to an empty object.
|
||||||
|
*
|
||||||
|
* ##### actions
|
||||||
|
*
|
||||||
|
* [Actions](/concepts/Actions) used by the updux.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* import { dux } from 'updux';
|
||||||
|
* import { action, payload } from 'ts-action';
|
||||||
|
*
|
||||||
|
* const bar = action('BAR', payload<int>());
|
||||||
|
* const foo = action('FOO');
|
||||||
|
*
|
||||||
|
* const myDux = dux({
|
||||||
|
* actions: {
|
||||||
|
* bar
|
||||||
|
* },
|
||||||
|
* mutations: [
|
||||||
|
* [ foo, () => state => state ]
|
||||||
|
* ]
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* myDux.actions.foo({ x: 1, y: 2 }); // => { type: foo, x:1, y:2 }
|
||||||
|
* myDux.actions.bar(2); // => { type: bar, payload: 2 }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* New actions used directly in mutations and effects will be added to the
|
||||||
|
* dux actions -- that is, they will be accessible via `dux.actions` -- but will
|
||||||
|
* not appear as part of its Typescript type.
|
||||||
|
*
|
||||||
|
* ##### selectors
|
||||||
|
*
|
||||||
|
* Dictionary of selectors for the current updux. The updux also
|
||||||
|
* inherit its subduxes' selectors.
|
||||||
|
*
|
||||||
|
* The selectors are available via the class' getter.
|
||||||
|
*
|
||||||
|
* ##### mutations
|
||||||
|
*
|
||||||
|
* mutations: [
|
||||||
|
* [ action, mutation, isSink ],
|
||||||
|
* ...
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* or
|
||||||
|
*
|
||||||
|
* mutations: {
|
||||||
|
* action: mutation,
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* List of mutations for assign to the dux. If you want Typescript goodness, you
|
||||||
|
* probably want to use `addMutation()` instead.
|
||||||
|
*
|
||||||
|
* In its generic array-of-array form,
|
||||||
|
* each mutation tuple contains: the action, the mutation,
|
||||||
|
* and boolean indicating if this is a sink mutation.
|
||||||
|
*
|
||||||
|
* The action can be an action creator function or a string. If it's a string, it's considered to be the
|
||||||
|
* action type and a generic `action( actionName, payload() )` creator will be
|
||||||
|
* generated for it. If an action is not already defined in the `actions`
|
||||||
|
* parameter, it'll be automatically added.
|
||||||
|
*
|
||||||
|
* The pseudo-action type `*` can be used to match any action not explicitly matched by other mutations.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* const todosUpdux = updux({
|
||||||
|
* mutations: {
|
||||||
|
* add: todo => state => [ ...state, todo ],
|
||||||
|
* done: done_id => u.map( u.if( ({id} => id === done_id), {done: true} ) ),
|
||||||
|
* '*' (payload,action) => state => {
|
||||||
|
* console.warn( "unexpected action ", action.type );
|
||||||
|
* return state;
|
||||||
|
* },
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The signature of the mutations is `(payload,action) => state => newState`.
|
||||||
|
* It is designed to play well with `Updeep` (and [Immer](https://immerjs.github.io/immer/docs/introduction)). This way, instead of doing
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* mutation: {
|
||||||
|
* renameTodo: newName => state => { ...state, name: newName }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* we can do
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* mutation: {
|
||||||
|
* renameTodo: newName => u({ name: newName })
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The final argument is the optional boolean `isSink`. If it is true, it'll
|
||||||
|
* prevent subduxes' mutations on the same action. It defaults to `false`.
|
||||||
|
*
|
||||||
|
* The object version of the argument can be used as a shortcut when all actions
|
||||||
|
* are strings. In that case, `isSink` is `false` for all mutations.
|
||||||
|
*
|
||||||
|
* ##### groomMutations
|
||||||
|
*
|
||||||
|
* Function that can be provided to alter all local mutations of the updux
|
||||||
|
* (the mutations of subduxes are left untouched).
|
||||||
|
*
|
||||||
|
* Can be used, for example, for Immer integration:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* import Updux from 'updux';
|
||||||
|
* import { produce } from 'Immer';
|
||||||
|
*
|
||||||
|
* const updux = new Updux({
|
||||||
|
* initial: { counter: 0 },
|
||||||
|
* groomMutations: mutation => (...args) => produce( mutation(...args) ),
|
||||||
|
* mutations: {
|
||||||
|
* add: (inc=1) => draft => draft.counter += inc
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Or perhaps for debugging:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* import Updux from 'updux';
|
||||||
|
*
|
||||||
|
* const updux = new Updux({
|
||||||
|
* initial: { counter: 0 },
|
||||||
|
* groomMutations: mutation => (...args) => state => {
|
||||||
|
* console.log( "got action ", args[1] );
|
||||||
|
* return mutation(...args)(state);
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* ##### subduxes
|
||||||
|
*
|
||||||
|
* Object mapping slices of the state to sub-upduxes. In addition to creating
|
||||||
|
* sub-reducers for those slices, it'll make the parend updux inherit all the
|
||||||
|
* actions and middleware from its subduxes.
|
||||||
|
*
|
||||||
|
* For example, if in plain Redux you would do
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* import { combineReducers } from 'redux';
|
||||||
|
* import todosReducer from './todos';
|
||||||
|
* import statisticsReducer from './statistics';
|
||||||
|
*
|
||||||
|
* const rootReducer = combineReducers({
|
||||||
|
* todos: todosReducer,
|
||||||
|
* stats: statisticsReducer,
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* then with Updux you'd do
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* import { updux } from 'updux';
|
||||||
|
* import todos from './todos';
|
||||||
|
* import statistics from './statistics';
|
||||||
|
*
|
||||||
|
* const rootUpdux = updux({
|
||||||
|
* subduxes: {
|
||||||
|
* todos,
|
||||||
|
* statistics
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ##### effects
|
||||||
|
*
|
||||||
|
* Array of arrays or plain object defining asynchronous actions and side-effects triggered by actions.
|
||||||
|
* The effects themselves are Redux middleware, with the `dispatch`
|
||||||
|
* property of the first argument augmented with all the available actions.
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* updux({
|
||||||
|
* effects: {
|
||||||
|
* fetch: ({dispatch}) => next => async (action) => {
|
||||||
|
* next(action);
|
||||||
|
*
|
||||||
|
* let result = await fetch(action.payload.url).then( result => result.json() );
|
||||||
|
* dispatch.fetchSuccess(result);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* import Updux from 'updux';
|
||||||
|
* import { actions, payload } from 'ts-action';
|
||||||
|
* import u from 'updeep';
|
||||||
|
*
|
||||||
|
* const todoUpdux = new Updux({
|
||||||
|
* initial: {
|
||||||
|
* done: false,
|
||||||
|
* note: "",
|
||||||
|
* },
|
||||||
|
* actions: {
|
||||||
|
* finish: action('FINISH', payload()),
|
||||||
|
* edit: action('EDIT', payload()),
|
||||||
|
* },
|
||||||
|
* mutations: [
|
||||||
|
* [ edit, note => u({note}) ]
|
||||||
|
* ],
|
||||||
|
* selectors: {
|
||||||
|
* getNote: state => state.note
|
||||||
|
* },
|
||||||
|
* groomMutations: mutation => transform(mutation),
|
||||||
|
* subduxes: {
|
||||||
|
* foo
|
||||||
|
* },
|
||||||
|
* effects: {
|
||||||
|
* finish: () => next => action => {
|
||||||
|
* console.log( "Woo! one more bites the dust" );
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export type UpduxConfig = Partial<{
|
||||||
|
initial: unknown, /** foo */
|
||||||
|
subduxes: Dictionary<Dux>,
|
||||||
|
coduxes: Dux[],
|
||||||
|
actions: Dictionary<ActionCreator>,
|
||||||
|
selectors: Dictionary<Selector>,
|
||||||
|
mutations: any,
|
||||||
|
groomMutations: (m: Mutation) => Mutation,
|
||||||
|
effects: any,
|
||||||
|
}>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Object mapping slices of the state to sub-upduxes.
|
|
||||||
*
|
|
||||||
* For example, if in plain Redux you would do
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* import { combineReducers } from 'redux';
|
|
||||||
* import todosReducer from './todos';
|
|
||||||
* import statisticsReducer from './statistics';
|
|
||||||
*
|
|
||||||
* const rootReducer = combineReducers({
|
|
||||||
* todos: todosReducer,
|
|
||||||
* stats: statisticsReducer,
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* then with Updux you'd do
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* import { updux } from 'updux';
|
|
||||||
* import todos from './todos';
|
|
||||||
* import statistics from './statistics';
|
|
||||||
*
|
|
||||||
* const rootUpdux = updux({
|
|
||||||
* subduxes: {
|
|
||||||
* todos, statistics
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
subduxes?: {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic action creations are automatically created from the mutations and effects, but you can
|
|
||||||
* also define custom action creator here. The object's values are function that
|
|
||||||
* transform the arguments of the creator to the action's payload.
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* const { actions } = updox({
|
|
||||||
* mutations: {
|
|
||||||
* foo: () => state => state,
|
|
||||||
* }
|
|
||||||
* actions: {
|
|
||||||
* bar: (x,y) => ({x,y})
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* actions.foo({ x: 1, y: 2 }); // => { type: foo, payload: { x:1, y:2 } }
|
|
||||||
* actions.bar(1,2); // => { type: bar, payload: { x:1, y:2 } }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
|
|
||||||
actions?: {
|
|
||||||
[type: string]: ActionCreator;
|
|
||||||
};
|
|
||||||
|
|
||||||
selectors?: Dictionary<Selector>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object mapping actions to the associated state mutation.
|
|
||||||
*
|
|
||||||
* For example, in `Redux` you'd do
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* function todosReducer(state=[],action) {
|
|
||||||
*
|
|
||||||
* switch(action.type) {
|
|
||||||
* case 'ADD': return [ ...state, action.payload ];
|
|
||||||
*
|
|
||||||
* case 'DONE': return state.map( todo => todo.id === action.payload
|
|
||||||
* ? { ...todo, done: true } : todo )
|
|
||||||
*
|
|
||||||
* default: return state;
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* With Updux:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* const todosUpdux = updux({
|
|
||||||
* mutations: {
|
|
||||||
* add: todo => state => [ ...state, todo ],
|
|
||||||
* done: done_id => u.map( u.if( ({id} => id === done_id), {done: true} ) )
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* The signature of the mutations is `(payload,action) => state => newState`.
|
|
||||||
* It is designed to play well with `Updeep` (and [Immer](https://immerjs.github.io/immer/docs/introduction)). This way, instead of doing
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* mutation: {
|
|
||||||
* renameTodo: newName => state => { ...state, name: newName }
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* we can do
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* mutation: {
|
|
||||||
* renameTodo: newName => u({ name: newName })
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Also, the special key `*` can be used to match any
|
|
||||||
* action not explicitly matched by other mutations.
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* const todosUpdux = updux({
|
|
||||||
* mutations: {
|
|
||||||
* add: todo => state => [ ...state, todo ],
|
|
||||||
* done: done_id => u.map( u.if( ({id} => id === done_id), {done: true} ) ),
|
|
||||||
* '*' (payload,action) => state => {
|
|
||||||
* console.warn( "unexpected action ", action.type );
|
|
||||||
* return state;
|
|
||||||
* },
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
mutations?: { [actionType: string]: Mutation<S> } | MutationEntry[];
|
|
||||||
|
|
||||||
groomMutations?: (m: Mutation<S>) => Mutation<S>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plain object defining asynchronous actions and side-effects triggered by actions.
|
|
||||||
* The effects themselves are Redux middleware, expect with the `dispatch`
|
|
||||||
* property of the first argument augmented with all the available actions.
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* updux({
|
|
||||||
* effects: {
|
|
||||||
* fetch: ({dispatch}) => next => async (action) => {
|
|
||||||
* next(action);
|
|
||||||
*
|
|
||||||
* let result = await fetch(action.payload.url).then( result => result.json() );
|
|
||||||
* dispatch.fetchSuccess(result);
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
effects?: Dictionary<UpduxMiddleware<S>> | EffectEntry<S>[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type EffectEntry<S> = [
|
|
||||||
ActionCreator | string,
|
|
||||||
UpduxMiddleware<S>,
|
|
||||||
boolean?
|
|
||||||
];
|
|
||||||
|
|
||||||
export type Upreducer<S = any> = (action: Action) => (state: S) => S;
|
export type Upreducer<S = any> = (action: Action) => (state: S) => S;
|
||||||
|
|
||||||
export interface UpduxMiddlewareAPI<S> {
|
/** @ignore */
|
||||||
dispatch: UpduxDispatch;
|
export interface UpduxMiddlewareAPI<S=any,X = Dictionary<Selector>> {
|
||||||
getState(): any;
|
dispatch: Function;
|
||||||
getRootState(): S;
|
getState(): S;
|
||||||
selectors: Dictionary<Selector>;
|
selectors: X;
|
||||||
|
actions: Dictionary<ActionCreator>;
|
||||||
}
|
}
|
||||||
export type UpduxMiddleware<S = any> = (
|
export type UpduxMiddleware<S=any,X = Dictionary<Selector>,A = Action> = (
|
||||||
api: UpduxMiddlewareAPI<S>
|
api: UpduxMiddlewareAPI<S,X>
|
||||||
) => (next: UpduxDispatch) => (action: Action) => any;
|
) => (next: Function) => (action: A) => any;
|
||||||
|
|
||||||
export type Selector<S = any> = (state:S) => any;
|
export type Selector<S = any> = (state: S) => unknown;
|
||||||
|
|
||||||
|
export type DuxState<D> = D extends { initial: infer S } ? S : unknown;
|
||||||
|
685
src/updux.ts
685
src/updux.ts
@ -1,259 +1,522 @@
|
|||||||
import fp from "lodash/fp";
|
import fp from 'lodash/fp';
|
||||||
import u from "updeep";
|
import { action, payload, ActionCreator, ActionType } from 'ts-action';
|
||||||
import { action, payload } from 'ts-action';
|
import {AnyAction} from 'redux';
|
||||||
|
|
||||||
import buildActions from "./buildActions";
|
import buildInitial from './buildInitial';
|
||||||
import buildInitial from "./buildInitial";
|
import buildMutations from './buildMutations';
|
||||||
import buildMutations from "./buildMutations";
|
|
||||||
|
|
||||||
import buildCreateStore from "./buildCreateStore";
|
import buildCreateStore from './buildCreateStore';
|
||||||
import buildMiddleware from "./buildMiddleware";
|
import buildMiddleware, { effectToMw, Effect } from './buildMiddleware';
|
||||||
import buildUpreducer from "./buildUpreducer";
|
import buildUpreducer from './buildUpreducer';
|
||||||
import {
|
import {
|
||||||
UpduxConfig,
|
UpduxConfig,
|
||||||
Dictionary,
|
Dictionary,
|
||||||
Action,
|
Action,
|
||||||
ActionCreator,
|
Mutation,
|
||||||
Mutation,
|
Upreducer,
|
||||||
Upreducer,
|
UpduxMiddleware,
|
||||||
UpduxDispatch,
|
Selector,
|
||||||
UpduxMiddleware,
|
Dux,
|
||||||
MutationEntry,
|
UnionToIntersection,
|
||||||
EffectEntry,
|
AggDuxState,
|
||||||
Selector
|
DuxSelectors,
|
||||||
} from "./types";
|
DuxActions,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
import { Middleware, Store, PreloadedState } from "redux";
|
import { Store, PreloadedState } from 'redux';
|
||||||
import buildSelectors from "./buildSelectors";
|
import buildSelectors from './buildSelectors';
|
||||||
|
|
||||||
|
type Merge<T> = UnionToIntersection<T[keyof T]>;
|
||||||
|
|
||||||
|
type ActionsOf<U> = U extends Updux ? U['actions'] : {};
|
||||||
|
|
||||||
|
//| ActionsOf<SubduxesOf<U>[keyof SubduxesOf<U>]>
|
||||||
|
export type UpduxActions<U> = U extends Updux
|
||||||
|
? UnionToIntersection<
|
||||||
|
UpduxLocalActions<U> | ActionsOf<CoduxesOf<U>[keyof CoduxesOf<U>]>
|
||||||
|
>
|
||||||
|
: {};
|
||||||
|
|
||||||
|
export type UpduxLocalActions<S> = S extends Updux<any, null>
|
||||||
|
? {}
|
||||||
|
: S extends Updux<any, infer A>
|
||||||
|
? A
|
||||||
|
: {};
|
||||||
|
export type CoduxesOf<U> = U extends Updux<any, any, any, infer S> ? S : [];
|
||||||
|
|
||||||
type StoreWithDispatchActions<
|
type StoreWithDispatchActions<
|
||||||
S = any,
|
S = any,
|
||||||
Actions = { [action: string]: (...args: any) => Action }
|
Actions = { [action: string]: (...args: any) => Action }
|
||||||
> = Store<S> & {
|
> = Store<S> & {
|
||||||
dispatch: { [type in keyof Actions]: (...args: any) => void };
|
dispatch: { [type in keyof Actions]: (...args: any) => void };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Dux<S> = Pick<
|
/**
|
||||||
Updux<S>,
|
* @public
|
||||||
| "subduxes"
|
* `Updux` is a way to minimize and simplify the boilerplate associated with the
|
||||||
| "actions"
|
* creation of a `Redux` store. It takes a shorthand configuration
|
||||||
| "initial"
|
* object, and generates the appropriate reducer, actions, middleware, etc.
|
||||||
| "mutations"
|
* In true `Redux`-like fashion, upduxes can be made of sub-upduxes (`subduxes` for short) for different slices of the root state.
|
||||||
| "reducer"
|
*/
|
||||||
| "middleware"
|
export class Updux<
|
||||||
| "createStore"
|
S = unknown,
|
||||||
| "upreducer"
|
A = null,
|
||||||
>;
|
X = unknown,
|
||||||
|
C extends UpduxConfig = {}
|
||||||
|
> {
|
||||||
|
subduxes: Dictionary<Dux>;
|
||||||
|
coduxes: Dux[];
|
||||||
|
|
||||||
export class Updux<S = any> {
|
private localSelectors: Dictionary<Selector> = {};
|
||||||
subduxes: Dictionary<Updux> = {};
|
|
||||||
|
|
||||||
private local_selectors: Dictionary<Selector<S>> = {};
|
private localInitial: unknown;
|
||||||
|
|
||||||
initial: S;
|
groomMutations: (mutation: Mutation<S>) => Mutation<S>;
|
||||||
|
|
||||||
groomMutations: (mutation: Mutation<S>) => Mutation<S>;
|
private localEffects: Effect[] = [];
|
||||||
|
|
||||||
|
private localActions: Dictionary<ActionCreator> = {};
|
||||||
|
|
||||||
private localEffects: EffectEntry<S>[] = [];
|
private localMutations: Dictionary<
|
||||||
|
Mutation<S> | [Mutation<S>, boolean | undefined]
|
||||||
|
> = {};
|
||||||
|
|
||||||
private localActions: Dictionary<ActionCreator> = {};
|
get initial(): AggDuxState<S, C> {
|
||||||
|
return buildInitial(
|
||||||
|
this.localInitial,
|
||||||
|
this.coduxes.map(({ initial }) => initial),
|
||||||
|
fp.mapValues('initial', this.subduxes)
|
||||||
|
) as any;
|
||||||
|
}
|
||||||
|
|
||||||
private localMutations: Dictionary<
|
/**
|
||||||
Mutation<S> | [Mutation<S>, boolean | undefined]
|
* @param config an [[UpduxConfig]] plain object
|
||||||
> = {};
|
*
|
||||||
|
*/
|
||||||
|
constructor(config: C = {} as C) {
|
||||||
|
this.localInitial = config.initial ?? {};
|
||||||
|
this.localSelectors = config.selectors ?? {};
|
||||||
|
this.coduxes = config.coduxes ?? [];
|
||||||
|
this.subduxes = config.subduxes ?? {};
|
||||||
|
|
||||||
constructor(config: UpduxConfig = {}) {
|
Object.entries(config.actions ?? {}).forEach((args) =>
|
||||||
this.groomMutations = config.groomMutations || ((x: Mutation<S>) => x);
|
(this.addAction as any)(...args)
|
||||||
|
);
|
||||||
|
|
||||||
const selectors = fp.getOr( {}, 'selectors', config ) as Dictionary<Selector>;
|
this.coduxes.forEach((c: any) =>
|
||||||
Object.entries(selectors).forEach( ([name,sel]: [string,Function]) => this.addSelector(name,sel as Selector) );
|
Object.entries(c.actions).forEach((args) =>
|
||||||
|
(this.addAction as any)(...args)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Object.values(this.subduxes).forEach((c: any) => {
|
||||||
|
Object.entries(c.actions).forEach((args) => {
|
||||||
|
(this.addAction as any)(...args);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
Object.entries( fp.mapValues((value: UpduxConfig | Updux) =>
|
this.groomMutations =
|
||||||
fp.isPlainObject(value) ? new Updux(value as any) : value
|
config.groomMutations ?? ((x: Mutation<S>) => x);
|
||||||
)(fp.getOr({}, "subduxes", config))).forEach(
|
|
||||||
([slice,sub]) => this.subduxes[slice] = sub as any
|
|
||||||
);
|
|
||||||
|
|
||||||
const actions = fp.getOr({}, "actions", config);
|
let effects = config.effects ?? [];
|
||||||
Object.entries(actions).forEach(([type, p]: [string, any]): any =>
|
|
||||||
this.addAction(
|
|
||||||
(p as any).type ? p : action(type, p)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let effects = fp.getOr([], "effects", config);
|
if (!Array.isArray(effects)) {
|
||||||
if (!Array.isArray(effects)) {
|
effects = (Object.entries(effects) as unknown) as Effect[];
|
||||||
effects = Object.entries(effects);
|
}
|
||||||
}
|
effects.forEach((effect) => (this.addEffect as any)(...effect));
|
||||||
effects.forEach(effect => this.addEffect(...effect));
|
|
||||||
|
|
||||||
this.initial = buildInitial<any>(
|
let mutations = config.mutations ?? [];
|
||||||
config.initial,
|
|
||||||
fp.mapValues(({ initial }) => initial)(this.subduxes)
|
|
||||||
);
|
|
||||||
|
|
||||||
let mutations = fp.getOr([], "mutations", config);
|
if (!Array.isArray(mutations)) {
|
||||||
if (!Array.isArray(mutations)) {
|
mutations = fp.toPairs(mutations);
|
||||||
mutations = fp.toPairs(mutations);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
mutations.forEach(args => (this.addMutation as any)(...args));
|
mutations.forEach((args) => (this.addMutation as any)(...args));
|
||||||
}
|
|
||||||
|
|
||||||
get middleware(): UpduxMiddleware<S> {
|
/*
|
||||||
return buildMiddleware(this._middlewareEntries, this.actions);
|
|
||||||
}
|
|
||||||
|
|
||||||
get actions(): Dictionary<ActionCreator> {
|
Object.entries(selectors).forEach(([name, sel]: [string, Function]) =>
|
||||||
return buildActions([
|
this.addSelector(name, sel as Selector)
|
||||||
...(Object.entries(this.localActions) as any),
|
);
|
||||||
...(fp.flatten(
|
|
||||||
Object.values(this.subduxes).map(({ actions }: Updux) =>
|
|
||||||
Object.entries(actions)
|
|
||||||
)
|
|
||||||
) as any),
|
|
||||||
,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
get upreducer(): Upreducer<S> {
|
Object.entries(
|
||||||
return buildUpreducer(this.initial, this.mutations);
|
fp.mapValues((value: UpduxConfig | Updux) =>
|
||||||
}
|
fp.isPlainObject(value) ? new Updux(value as any) : value
|
||||||
|
)(fp.getOr({}, 'subduxes', config))
|
||||||
|
).forEach(([slice, sub]) => (this.subduxes[slice] = sub as any));
|
||||||
|
|
||||||
get reducer(): (state: S | undefined, action: Action) => S {
|
const actions = fp.getOr({}, 'actions', config);
|
||||||
return (state, action) => this.upreducer(action)(state as S);
|
Object.entries(actions as any).forEach(([type, p]: [string, any]): any =>
|
||||||
}
|
this.addAction((p as any).type ? p : action(type, p))
|
||||||
|
);
|
||||||
|
|
||||||
get mutations(): Dictionary<Mutation<S>> {
|
*/
|
||||||
return buildMutations(this.localMutations, this.subduxes);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
get subduxUpreducer() {
|
/**
|
||||||
return buildUpreducer(this.initial, buildMutations({}, this.subduxes));
|
* Array of middlewares aggregating all the effects defined in the
|
||||||
}
|
* updux and its subduxes. Effects of the updux itself are
|
||||||
|
* done before the subduxes effects.
|
||||||
|
* Note that `getState` will always return the state of the
|
||||||
|
* local updux.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* const middleware = updux.middleware;
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get middleware(): UpduxMiddleware<
|
||||||
|
AggDuxState<S, C>,
|
||||||
|
DuxSelectors<AggDuxState<S, C>, X, C>
|
||||||
|
> {
|
||||||
|
const selectors = this.selectors;
|
||||||
|
const actions = this.actions;
|
||||||
|
return buildMiddleware(
|
||||||
|
this.localEffects.map((effect) =>
|
||||||
|
effectToMw(effect, actions as any, selectors as any)
|
||||||
|
),
|
||||||
|
(this.coduxes as any).map(fp.get('middleware')) as any,
|
||||||
|
fp.mapValues('middleware', this.subduxes)
|
||||||
|
) as any;
|
||||||
|
}
|
||||||
|
|
||||||
get createStore(): () => StoreWithDispatchActions<S> {
|
/**
|
||||||
const actions = this.actions;
|
* Action creators for all actions defined or used in the actions, mutations, effects and subduxes
|
||||||
|
* of the updux config.
|
||||||
|
*
|
||||||
|
* Non-custom action creators defined in `actions` have the signature `(payload={},meta={}) => ({type,
|
||||||
|
* payload,meta})` (with the extra sugar that if `meta` or `payload` are not
|
||||||
|
* specified, that key won't be present in the produced action).
|
||||||
|
*
|
||||||
|
* The same action creator can be included
|
||||||
|
* in multiple subduxes. However, if two different creators
|
||||||
|
* are included for the same action, an error will be thrown.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* const actions = updux.actions;
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get actions(): DuxActions<A, C> {
|
||||||
|
// UpduxActions<Updux<S,A,SUB,CO>> {
|
||||||
|
return this.localActions as any;
|
||||||
|
}
|
||||||
|
|
||||||
return buildCreateStore<S>(
|
get upreducer(): Upreducer<S> {
|
||||||
this.reducer,
|
return buildUpreducer(
|
||||||
this.initial as PreloadedState<S>,
|
this.initial,
|
||||||
this.middleware as any,
|
this.mutations as any
|
||||||
actions
|
) as any;
|
||||||
) as () => StoreWithDispatchActions<S, typeof actions>;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
get asDux(): Dux<S> {
|
/**
|
||||||
return {
|
* A Redux reducer generated using the computed initial state and
|
||||||
createStore: this.createStore,
|
* mutations.
|
||||||
upreducer: this.upreducer,
|
*/
|
||||||
subduxes: this.subduxes,
|
get reducer(): (state: S | undefined, action: Action) => S {
|
||||||
middleware: this.middleware,
|
return (state, action) => this.upreducer(action)(state as S);
|
||||||
actions: this.actions,
|
}
|
||||||
reducer: this.reducer,
|
|
||||||
mutations: this.mutations,
|
|
||||||
initial: this.initial
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
addMutation<A extends ActionCreator=any>(
|
/**
|
||||||
creator: A,
|
* Merge of the updux and subduxes mutations. If an action triggers
|
||||||
mutation: Mutation<S, A extends (...args: any[]) => infer R ? R : never>,
|
* mutations in both the main updux and its subduxes, the subduxes
|
||||||
isSink?: boolean
|
* mutations will be performed first.
|
||||||
)
|
*/
|
||||||
addMutation<A extends ActionCreator=any>(
|
get mutations(): Dictionary<Mutation<S>> {
|
||||||
creator: string,
|
return buildMutations(
|
||||||
mutation: Mutation<S, any>,
|
this.localMutations,
|
||||||
isSink?: boolean
|
fp.mapValues('upreducer', this.subduxes as any),
|
||||||
)
|
fp.map('upreducer', this.coduxes as any)
|
||||||
addMutation<A extends ActionCreator=any>(
|
);
|
||||||
creator,
|
}
|
||||||
mutation,
|
|
||||||
isSink
|
|
||||||
)
|
|
||||||
{
|
|
||||||
let c = this.addAction(creator);
|
|
||||||
|
|
||||||
this.localMutations[c.type] = [
|
/**
|
||||||
this.groomMutations(mutation as any) as Mutation<S>,
|
* Returns the upreducer made of the merge of all sudbuxes reducers, without
|
||||||
isSink
|
* the local mutations. Useful, for example, for sink mutations.
|
||||||
];
|
*
|
||||||
}
|
* @example
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* import todo from './todo'; // updux for a single todo
|
||||||
|
* import Updux from 'updux';
|
||||||
|
* import u from 'updeep';
|
||||||
|
*
|
||||||
|
* const todos = new Updux({ initial: [], subduxes: { '*': todo } });
|
||||||
|
* todos.addMutation(
|
||||||
|
* todo.actions.done,
|
||||||
|
* ({todo_id},action) => u.map( u.if( u.is('id',todo_id) ), todos.subduxUpreducer(action) )
|
||||||
|
* true
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
get subduxUpreducer() {
|
||||||
|
return buildUpreducer(
|
||||||
|
this.initial,
|
||||||
|
buildMutations({}, this.subduxes)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
addEffect(
|
/**
|
||||||
creator: ActionCreator | string,
|
* Returns a `createStore` function that takes two argument:
|
||||||
middleware: UpduxMiddleware<S>,
|
* `initial` and `injectEnhancer`. `initial` is a custom
|
||||||
isGenerator: boolean = false
|
* initial state for the store, and `injectEnhancer` is a function
|
||||||
) {
|
* taking in the middleware built by the updux object and allowing
|
||||||
const c = this.addAction(creator);
|
* you to wrap it in any enhancer you want.
|
||||||
this.localEffects.push([c.type, middleware, isGenerator]);
|
*
|
||||||
}
|
* @example
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* const createStore = updux.createStore;
|
||||||
|
*
|
||||||
|
* const store = createStore(initial);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
get createStore() {
|
||||||
|
return buildCreateStore<AggDuxState<S, C>, DuxActions<A, C>>(
|
||||||
|
this.reducer as any,
|
||||||
|
this.middleware as any,
|
||||||
|
this.actions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// can be
|
/**
|
||||||
//addAction( actionCreator )
|
* Returns a <a href="https://github.com/erikras/ducks-modular-redux">ducks</a>-like
|
||||||
// addAction( 'foo', transform )
|
* plain object holding the reducer from the Updux object and all
|
||||||
addAction(theaction: string, transform?: any): ActionCreator<string,any>
|
* its trimmings.
|
||||||
addAction(theaction: string|ActionCreator<any>, transform?: never): ActionCreator<string,any>
|
*
|
||||||
addAction(theaction: any,transform:any) {
|
* @example
|
||||||
if (typeof theaction === "string") {
|
*
|
||||||
if(transform !== undefined ) {
|
* ```
|
||||||
theaction = action(theaction,transform);
|
* const {
|
||||||
}
|
* createStore,
|
||||||
else {
|
* upreducer,
|
||||||
theaction = this.actions[theaction] || action(theaction,payload())
|
* subduxes,
|
||||||
}
|
* coduxes,
|
||||||
}
|
* middleware,
|
||||||
|
* actions,
|
||||||
|
* reducer,
|
||||||
|
* mutations,
|
||||||
|
* initial,
|
||||||
|
* selectors,
|
||||||
|
* } = myUpdux.asDux;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
get asDux() {
|
||||||
|
return {
|
||||||
|
createStore: this.createStore,
|
||||||
|
upreducer: this.upreducer,
|
||||||
|
subduxes: this.subduxes,
|
||||||
|
coduxes: this.coduxes,
|
||||||
|
middleware: this.middleware,
|
||||||
|
actions: this.actions,
|
||||||
|
reducer: this.reducer,
|
||||||
|
mutations: this.mutations,
|
||||||
|
initial: this.initial,
|
||||||
|
selectors: this.selectors,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const already = this.actions[theaction.type];
|
/**
|
||||||
if( already ) {
|
* Adds a mutation and its associated action to the updux.
|
||||||
if ( already !== theaction ) {
|
*
|
||||||
throw new Error(`action ${theaction.type} already exists`)
|
* @param isSink - If `true`, disables the subduxes mutations for this action. To
|
||||||
}
|
* conditionally run the subduxes mutations, check out [[subduxUpreducer]]. Defaults to `false`.
|
||||||
return already;
|
*
|
||||||
}
|
* @remarks
|
||||||
|
*
|
||||||
|
* If a local mutation was already associated to the action,
|
||||||
|
* it will be replaced by the new one.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* updux.addMutation(
|
||||||
|
* action('ADD', payload<int>() ),
|
||||||
|
* inc => state => state + in
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
addMutation<A extends ActionCreator>(
|
||||||
|
creator: A,
|
||||||
|
mutation: Mutation<S, ActionType<A>>,
|
||||||
|
isSink?: boolean
|
||||||
|
);
|
||||||
|
addMutation<A extends ActionCreator = any>(
|
||||||
|
creator: string,
|
||||||
|
mutation: Mutation<S, any>,
|
||||||
|
isSink?: boolean
|
||||||
|
);
|
||||||
|
addMutation<A extends ActionCreator = any>(
|
||||||
|
creator,
|
||||||
|
mutation,
|
||||||
|
isSink
|
||||||
|
) {
|
||||||
|
const c = this.addAction(creator);
|
||||||
|
|
||||||
return this.localActions[theaction.type] = theaction;
|
this.localMutations[c.type] = [
|
||||||
}
|
this.groomMutations(mutation as any) as Mutation<S>,
|
||||||
|
isSink,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
get _middlewareEntries() {
|
addEffect<AC extends ActionCreator>(
|
||||||
const groupByOrder = (mws: any) =>
|
creator: AC,
|
||||||
fp.groupBy(
|
middleware: UpduxMiddleware<
|
||||||
([a,b, actionType]: any) =>
|
AggDuxState<S, C>,
|
||||||
["^", "$"].includes(actionType) ? actionType : "middle",
|
DuxSelectors<AggDuxState<S, C>, X, C>,
|
||||||
mws
|
ReturnType<AC>
|
||||||
);
|
>,
|
||||||
|
isGenerator?: boolean
|
||||||
|
);
|
||||||
|
addEffect(
|
||||||
|
creator: string,
|
||||||
|
middleware: UpduxMiddleware<
|
||||||
|
AggDuxState<S, C>,
|
||||||
|
DuxSelectors<AggDuxState<S, C>, X, C>
|
||||||
|
>,
|
||||||
|
isGenerator?: boolean
|
||||||
|
);
|
||||||
|
addEffect(creator, middleware, isGenerator = false) {
|
||||||
|
const c = this.addAction(creator);
|
||||||
|
this.localEffects.push([c.type, middleware, isGenerator] as any);
|
||||||
|
}
|
||||||
|
|
||||||
let subs = fp.flow([
|
// can be
|
||||||
fp.toPairs,
|
//addAction( actionCreator )
|
||||||
fp.map(([slice, updux]) =>
|
// addAction( 'foo', transform )
|
||||||
updux._middlewareEntries.map(([u, ps, ...args]: any) => [u,[slice, ...ps], ...args])
|
/**
|
||||||
),
|
* Adds an action to the updux. It can take an already defined action
|
||||||
fp.flatten,
|
* creator, or any arguments that can be passed to `actionCreator`.
|
||||||
groupByOrder
|
* @example
|
||||||
])(this.subduxes);
|
* ```
|
||||||
|
* const action = updux.addAction( name, ...creatorArgs );
|
||||||
|
* const action = updux.addAction( otherActionCreator );
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* import {actionCreator, Updux} from 'updux';
|
||||||
|
*
|
||||||
|
* const updux = new Updux();
|
||||||
|
*
|
||||||
|
* const foo = updux.addAction('foo');
|
||||||
|
* const bar = updux.addAction( 'bar', (x) => ({stuff: x+1}) );
|
||||||
|
*
|
||||||
|
* const baz = actionCreator( 'baz' );
|
||||||
|
*
|
||||||
|
* foo({ a: 1}); // => { type: 'foo', payload: { a: 1 } }
|
||||||
|
* bar(2); // => { type: 'bar', payload: { stuff: 3 } }
|
||||||
|
* baz(); // => { type: 'baz', payload: undefined }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
addAction(
|
||||||
|
theaction: string,
|
||||||
|
transform?: any
|
||||||
|
): ActionCreator<string, any>;
|
||||||
|
addAction(
|
||||||
|
theaction: string | ActionCreator<any>,
|
||||||
|
transform?: never
|
||||||
|
): ActionCreator<string, any>;
|
||||||
|
addAction(actionIn: any, transform: any) {
|
||||||
|
let name: string;
|
||||||
|
let creator: ActionCreator;
|
||||||
|
|
||||||
let local = groupByOrder(this.localEffects.map(x => [this,[], ...x]));
|
if (typeof actionIn === 'string') {
|
||||||
|
name = actionIn;
|
||||||
|
|
||||||
return fp.flatten(
|
if (transform) {
|
||||||
[
|
creator = transform.type
|
||||||
local["^"],
|
? transform
|
||||||
subs["^"],
|
: action(name, (...args: any) => ({
|
||||||
local.middle,
|
payload: transform(...args),
|
||||||
subs.middle,
|
}));
|
||||||
subs["$"],
|
} else {
|
||||||
local["$"]
|
creator =
|
||||||
].filter(x => x)
|
this.localActions[name] ?? action(name, payload());
|
||||||
);
|
}
|
||||||
}
|
} else {
|
||||||
|
name = actionIn.type;
|
||||||
|
creator = actionIn;
|
||||||
|
}
|
||||||
|
|
||||||
addSelector( name: string, selector: Selector) {
|
const already = this.localActions[name];
|
||||||
this.local_selectors[name] = selector;
|
|
||||||
}
|
|
||||||
|
|
||||||
get selectors() {
|
if (!already)
|
||||||
return buildSelectors(this.local_selectors, this.subduxes);
|
return ((this.localActions as any)[name] = creator) as any;
|
||||||
}
|
|
||||||
}
|
if (already !== creator && already.type !== '*') {
|
||||||
|
throw new Error(`action ${name} already exists`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return already;
|
||||||
|
}
|
||||||
|
|
||||||
|
get _middlewareEntries() {
|
||||||
|
const groupByOrder = (mws: any) =>
|
||||||
|
fp.groupBy(
|
||||||
|
([, , actionType]: any) =>
|
||||||
|
['^', '$'].includes(actionType)
|
||||||
|
? actionType
|
||||||
|
: 'middle',
|
||||||
|
mws
|
||||||
|
);
|
||||||
|
|
||||||
|
const subs = fp.flow([
|
||||||
|
fp.toPairs,
|
||||||
|
fp.map(([slice, updux]) =>
|
||||||
|
updux._middlewareEntries.map(([u, ps, ...args]: any) => [
|
||||||
|
u,
|
||||||
|
[slice, ...ps],
|
||||||
|
...args,
|
||||||
|
])
|
||||||
|
),
|
||||||
|
fp.flatten,
|
||||||
|
groupByOrder,
|
||||||
|
])(this.subduxes);
|
||||||
|
|
||||||
|
const local = groupByOrder(
|
||||||
|
this.localEffects.map((x) => [this, [], ...x])
|
||||||
|
);
|
||||||
|
|
||||||
|
return fp.flatten(
|
||||||
|
[
|
||||||
|
local['^'],
|
||||||
|
subs['^'],
|
||||||
|
local.middle,
|
||||||
|
subs.middle,
|
||||||
|
subs['$'],
|
||||||
|
local['$'],
|
||||||
|
].filter((x) => x)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addSelector(name: string, selector: Selector) {
|
||||||
|
this.localSelectors[name] = selector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
A dictionary of the updux's selectors. Subduxes'
|
||||||
|
selectors are included as well (with the mapping to the
|
||||||
|
sub-state already taken care of you).
|
||||||
|
*/
|
||||||
|
get selectors(): DuxSelectors<AggDuxState<S, C>, X, C> {
|
||||||
|
return buildSelectors(
|
||||||
|
this.localSelectors,
|
||||||
|
fp.map('selectors', this.coduxes),
|
||||||
|
fp.mapValues('selectors', this.subduxes)
|
||||||
|
) as any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default Updux;
|
export default Updux;
|
||||||
|
22
src/updux/createStore.test.ts
Normal file
22
src/updux/createStore.test.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import tap from 'tap';
|
||||||
|
import { expectType } from 'tsd';
|
||||||
|
|
||||||
|
import { dux } from '..';
|
||||||
|
|
||||||
|
import { action } from 'ts-action';
|
||||||
|
|
||||||
|
const d = dux({
|
||||||
|
initial: 123,
|
||||||
|
actions: {
|
||||||
|
foo: action('foo'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expectType<number>( d.createStore().getState() );
|
||||||
|
|
||||||
|
tap.equal(d.createStore().getState(), 123, 'default initial state');
|
||||||
|
|
||||||
|
tap.equal(d.createStore(456).getState(), 456, 'given initial state');
|
||||||
|
|
||||||
|
expectType<{ foo: Function }>( d.createStore().actions );
|
||||||
|
tap.pass('we have actions');
|
75
tools/gen_sidebar.pl
Executable file
75
tools/gen_sidebar.pl
Executable file
@ -0,0 +1,75 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
|
use 5.30.0;
|
||||||
|
use warnings;
|
||||||
|
use experimental qw/
|
||||||
|
signatures
|
||||||
|
postderef
|
||||||
|
/;
|
||||||
|
|
||||||
|
|
||||||
|
use Path::Tiny;
|
||||||
|
use Path::Tiny::Glob pathglob => { all => 1};
|
||||||
|
use File::Serialize;
|
||||||
|
use List::AllUtils qw/ before_incl /;
|
||||||
|
use List::UtilsBy qw/ partition_by /;
|
||||||
|
|
||||||
|
my $api = deserialize_file './temp/updux.api.json';
|
||||||
|
|
||||||
|
my @lines = generate_index([$api]);
|
||||||
|
|
||||||
|
my $sidebar = path('docs/_sidebar.md');
|
||||||
|
|
||||||
|
$sidebar->spew(
|
||||||
|
( before_incl { /!-- API/ } $sidebar->lines ),
|
||||||
|
map { "$_\n" } @lines
|
||||||
|
);
|
||||||
|
|
||||||
|
sub generate_index($entries,$indent = 2) {
|
||||||
|
my @lines;
|
||||||
|
|
||||||
|
my %sections = partition_by {
|
||||||
|
$_->{kind}
|
||||||
|
} @$entries;
|
||||||
|
|
||||||
|
|
||||||
|
for my $type ( sort keys %sections ) {
|
||||||
|
my $entries = $sections{$type};
|
||||||
|
|
||||||
|
if ( $type eq 'Constructor' ) {
|
||||||
|
push @lines, sprintf "%s- [%s](%s)",
|
||||||
|
" " x $indent, 'Constructor', ref2link(@$entries);
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
push @lines, join '', " " x $indent, '- ', $type unless $type eq 'EntryPoint';
|
||||||
|
|
||||||
|
for my $entry ( @$entries ) {
|
||||||
|
my $i = $indent;
|
||||||
|
|
||||||
|
if( $entry->{name} ){
|
||||||
|
my $link = ref2link($entry);
|
||||||
|
|
||||||
|
push @lines, sprintf "%s- [%s](%s)",
|
||||||
|
" " x (++$i), $entry->{name}, $link;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $members = $entry->{members} or next;
|
||||||
|
|
||||||
|
push @lines, generate_index($members, $i+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return @lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ref2link($entry) {
|
||||||
|
return "/4-API/".
|
||||||
|
lc( $entry->{canonicalReference} =~ s/!|#/./gr
|
||||||
|
=~ s/:constructor/._constructor_/r
|
||||||
|
=~ s/:\w+//r
|
||||||
|
=~ s/\)//r
|
||||||
|
=~ s/\((\d+)/'_' . ($1-1)/er
|
||||||
|
=~ s/_0//r ). '.md'
|
||||||
|
;
|
||||||
|
}
|
@ -19,7 +19,7 @@
|
|||||||
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
//"composite": true, /* Enable project compilation */
|
//"composite": true, /* Enable project compilation */
|
||||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
"removeComments": true, /* Do not emit comments to output. */
|
"removeComments": false, /* Do not emit comments to output. */
|
||||||
// "noEmit": true, /* Do not emit outputs. */
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
11
typedoc.json
11
typedoc.json
@ -1,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"theme": "default",
|
"theme": "markdown",
|
||||||
"tsBuildInfoFile": true,
|
"out": "docs/API",
|
||||||
"out": "docs",
|
"mode": "modules",
|
||||||
"mode": "file",
|
|
||||||
"excludePrivate": true,
|
"excludePrivate": true,
|
||||||
"excludeNotExported": false,
|
"excludeNotExported": true,
|
||||||
|
"hideSources": true,
|
||||||
|
"includeVersion": true,
|
||||||
"exclude": [ "src/**/*test.ts" ]
|
"exclude": [ "src/**/*test.ts" ]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user