Merge branch 'generics' into return-to-ts
This commit is contained in:
commit
8314ff94ca
@ -1,2 +1,7 @@
|
|||||||
|
dist
|
||||||
|
out
|
||||||
|
pnpm-lock.yaml
|
||||||
|
types
|
||||||
|
docs
|
||||||
Changes
|
Changes
|
||||||
.prettierignore
|
.prettierignore
|
||||||
|
28
CHANGELOG.md
28
CHANGELOG.md
@ -6,54 +6,58 @@ All notable changes to this project will be documented in this file. See [standa
|
|||||||
|
|
||||||
### ⚠ BREAKING CHANGES
|
### ⚠ BREAKING CHANGES
|
||||||
|
|
||||||
* Upgrade to Typescript 4.
|
- Upgrade to Typescript 4.
|
||||||
* Switch from 'updeep' to '@yanick/updeep'.
|
- Switch from 'updeep' to '@yanick/updeep'.
|
||||||
|
|
||||||
|
|
||||||
## [2.1.0](https://github.com/yanick/updux/compare/v2.0.0...v2.1.0) (2020-06-19)
|
## [2.1.0](https://github.com/yanick/updux/compare/v2.0.0...v2.1.0) (2020-06-19)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* add support for subscriptions ([9c45ee7](https://github.com/yanick/updux/commit/9c45ee7efcb623defb9da5d01165fbad0e4424f9))
|
- add support for subscriptions ([9c45ee7](https://github.com/yanick/updux/commit/9c45ee7efcb623defb9da5d01165fbad0e4424f9))
|
||||||
|
|
||||||
## [2.0.0](https://github.com/yanick/updux/compare/v1.2.0...v2.0.0) (2020-06-13)
|
## [2.0.0](https://github.com/yanick/updux/compare/v1.2.0...v2.0.0) (2020-06-13)
|
||||||
|
|
||||||
### ⚠ BREAKING CHANGES
|
### ⚠ BREAKING CHANGES
|
||||||
|
|
||||||
* use ts-action for action creation
|
- use ts-action for action creation
|
||||||
* middleware support refined
|
- middleware support refined
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* allow adding actionCreators via addAction() ([27ae46d](https://github.com/yanick/updux/commit/27ae46dbab289b27ea99aca149aaa3b7c90ee7d0))
|
- allow adding actionCreators via addAction() ([27ae46d](https://github.com/yanick/updux/commit/27ae46dbab289b27ea99aca149aaa3b7c90ee7d0))
|
||||||
* middleware support refined ([d90d721](https://github.com/yanick/updux/commit/d90d72148c2d4ba186a19650d961c64df5791c55))
|
- middleware support refined ([d90d721](https://github.com/yanick/updux/commit/d90d72148c2d4ba186a19650d961c64df5791c55))
|
||||||
* moving documentation to docsify ([fa55762](https://github.com/yanick/updux/commit/fa55762efcbd4db356150f6022fd62750adc27a9))
|
- moving documentation to docsify ([fa55762](https://github.com/yanick/updux/commit/fa55762efcbd4db356150f6022fd62750adc27a9))
|
||||||
* use ts-action for action creation ([6349d72](https://github.com/yanick/updux/commit/6349d720b8aba4b443a7225d6a377c5c929a3021))
|
- use ts-action for action creation ([6349d72](https://github.com/yanick/updux/commit/6349d720b8aba4b443a7225d6a377c5c929a3021))
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* state is a PreloadedState<S> ([93bebc5](https://github.com/yanick/updux/commit/93bebc5acf193752aa6b4857507f05d52b1b7665))
|
- state is a PreloadedState<S> ([93bebc5](https://github.com/yanick/updux/commit/93bebc5acf193752aa6b4857507f05d52b1b7665))
|
||||||
|
|
||||||
## 1.2.0 2019-11-06
|
## 1.2.0 2019-11-06
|
||||||
|
|
||||||
- The middleware's 'getState' returns the local state of its updux,
|
- The middleware's 'getState' returns the local state of its updux,
|
||||||
instead of the root state. Plus we add `getRootState` to get
|
instead of the root state. Plus we add `getRootState` to get
|
||||||
the root state.
|
the root state.
|
||||||
|
|
||||||
## 1.1.0 2019-11-05
|
## 1.1.0 2019-11-05
|
||||||
|
|
||||||
- Document mapping behavior of the '*' subdux.
|
- Document mapping behavior of the '*' subdux.
|
||||||
- add subduxUpreducer.
|
- add subduxUpreducer.
|
||||||
- add sink mutations.
|
- add sink mutations.
|
||||||
|
|
||||||
## 1.0.0 2019-11-04
|
## 1.0.0 2019-11-04
|
||||||
|
|
||||||
- Pretty big rework.
|
- Pretty big rework.
|
||||||
- Better documentation.
|
- Better documentation.
|
||||||
|
|
||||||
## 0.2.0 2019-10-24
|
## 0.2.0 2019-10-24
|
||||||
|
|
||||||
- Converted everything to Typescript.
|
- Converted everything to Typescript.
|
||||||
|
|
||||||
## 0.1.0 2019-10-22
|
## 0.1.0 2019-10-22
|
||||||
|
|
||||||
- Add 'actions' in the config.
|
- Add 'actions' in the config.
|
||||||
|
|
||||||
## 0.0.1 2019-10-22
|
## 0.0.1 2019-10-22
|
||||||
|
|
||||||
- Initial release.
|
- Initial release.
|
||||||
|
@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
|
|||||||
Examples of behavior that contributes to a positive environment for our
|
Examples of behavior that contributes to a positive environment for our
|
||||||
community include:
|
community include:
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
- Demonstrating empathy and kindness toward other people
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
- Being respectful of differing opinions, viewpoints, and experiences
|
||||||
* Giving and gracefully accepting constructive feedback
|
- Giving and gracefully accepting constructive feedback
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
and learning from the experience
|
and learning from the experience
|
||||||
* Focusing on what is best not just for us as individuals, but for the
|
- Focusing on what is best not just for us as individuals, but for the
|
||||||
overall community
|
overall community
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or
|
- The use of sexualized language or imagery, and sexual attention or
|
||||||
advances of any kind
|
advances of any kind
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
- Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or email
|
- Publishing others' private information, such as a physical or email
|
||||||
address, without their explicit permission
|
address, without their explicit permission
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
- Other conduct which could reasonably be considered inappropriate in a
|
||||||
professional setting
|
professional setting
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
## Enforcement Responsibilities
|
||||||
@ -126,4 +126,3 @@ enforcement ladder](https://github.com/mozilla/diversity).
|
|||||||
For answers to common questions about this code of conduct, see the FAQ at
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
https://www.contributor-covenant.org/faq. Translations are available at
|
https://www.contributor-covenant.org/faq. Translations are available at
|
||||||
https://www.contributor-covenant.org/translations.
|
https://www.contributor-covenant.org/translations.
|
||||||
|
|
||||||
|
14
Taskfile.yml
14
Taskfile.yml
@ -6,6 +6,20 @@ vars:
|
|||||||
GREETING: Hello, World!
|
GREETING: Hello, World!
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
tsc:
|
||||||
|
cmds:
|
||||||
|
- tsc
|
||||||
|
- echo 🙌 typescript compilation successful
|
||||||
|
|
||||||
|
test: jest
|
||||||
|
|
||||||
|
'check:prettier': prettier --check .
|
||||||
|
|
||||||
|
'check:prettier:fix': prettier --write .
|
||||||
|
|
||||||
|
check:
|
||||||
|
deps: [tsc, test, 'check:prettier']
|
||||||
|
|
||||||
'test:types': tsd
|
'test:types': tsd
|
||||||
docs:
|
docs:
|
||||||
cmds:
|
cmds:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
presets: [
|
presets: [
|
||||||
['@babel/preset-env', {targets: {node: 'current'}}],
|
['@babel/preset-env', { targets: { node: 'current' } }],
|
||||||
'@babel/preset-typescript',
|
'@babel/preset-typescript',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export default {
|
export default {
|
||||||
roots: [ './src' ]
|
roots: ['./src'],
|
||||||
}
|
};
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"tags": {
|
"tags": {
|
||||||
"allowUnknownTags": true,
|
"allowUnknownTags": true,
|
||||||
"dictionaries": ["jsdoc","closure"]
|
"dictionaries": ["jsdoc", "closure"]
|
||||||
},
|
},
|
||||||
"templates": {
|
"templates": {
|
||||||
"cleverLinks": false,
|
"cleverLinks": false,
|
||||||
@ -19,4 +19,3 @@
|
|||||||
"tutorials": "./tutorials"
|
"tutorials": "./tutorials"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
src/Updux.d.ts
vendored
16
src/Updux.d.ts
vendored
@ -1,19 +1,17 @@
|
|||||||
type UpduxConfig<TState> = Partial<{
|
type UpduxConfig<TState> = Partial<{
|
||||||
initial: TState;
|
initial: TState;
|
||||||
subduxes: Record<string,any>;
|
subduxes: Record<string, any>;
|
||||||
actions: Record<string, any>;
|
actions: Record<string, any>;
|
||||||
selectors: Record<string, Function>;
|
selectors: Record<string, Function>;
|
||||||
mutations: Record<string, Function>;
|
mutations: Record<string, Function>;
|
||||||
mappedSelectors: Record<string,Function>;
|
mappedSelectors: Record<string, Function>;
|
||||||
effects: Record<string,Function>;
|
effects: Record<string, Function>;
|
||||||
reactions: Record<string,Function>;
|
reactions: Record<string, Function>;
|
||||||
mappedReaction: Function;
|
mappedReaction: Function;
|
||||||
}>
|
}>;
|
||||||
|
|
||||||
|
export class Updux<TState = unknown> {
|
||||||
export class Updux<TState=unknown> {
|
constructor(config: UpduxConfig<TState>);
|
||||||
|
|
||||||
constructor( config: UpduxConfig<TState> );
|
|
||||||
|
|
||||||
get initial(): TState;
|
get initial(): TState;
|
||||||
get selectors(): unknown;
|
get selectors(): unknown;
|
||||||
|
50
src/Updux.ts
50
src/Updux.ts
@ -9,17 +9,19 @@ import { buildActions } from './buildActions';
|
|||||||
import { buildSelectors } from './buildSelectors';
|
import { buildSelectors } from './buildSelectors';
|
||||||
import { action } from './actions';
|
import { action } from './actions';
|
||||||
import { buildUpreducer } from './buildUpreducer';
|
import { buildUpreducer } from './buildUpreducer';
|
||||||
import {
|
import { buildMiddleware, augmentMiddlewareApi } from './buildMiddleware';
|
||||||
buildMiddleware,
|
|
||||||
augmentMiddlewareApi,
|
|
||||||
} from './buildMiddleware';
|
|
||||||
|
|
||||||
import { Dict } from './types';
|
import { AggregateDuxActions, AggregateDuxState, Dict } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration object typically passed to the constructor of the class Updux.
|
* Configuration object typically passed to the constructor of the class Updux.
|
||||||
*/
|
*/
|
||||||
export interface UpduxConfig<TState = unknown> {
|
export interface UpduxConfig<
|
||||||
|
TState = any,
|
||||||
|
TActions = {},
|
||||||
|
TSelectors = {},
|
||||||
|
TSubduxes = {}
|
||||||
|
> {
|
||||||
/**
|
/**
|
||||||
* Local initial state.
|
* Local initial state.
|
||||||
* @default {}
|
* @default {}
|
||||||
@ -29,12 +31,12 @@ export interface UpduxConfig<TState = unknown> {
|
|||||||
/**
|
/**
|
||||||
* Subduxes to be merged to this dux.
|
* Subduxes to be merged to this dux.
|
||||||
*/
|
*/
|
||||||
subduxes?: Dict<Updux | UpduxConfig>;
|
subduxes?: TSubduxes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Local actions.
|
* Local actions.
|
||||||
*/
|
*/
|
||||||
actions?: Record<string, any>;
|
actions?: TActions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Local selectors.
|
* Local selectors.
|
||||||
@ -70,7 +72,12 @@ export interface UpduxConfig<TState = unknown> {
|
|||||||
mappedReaction?: Function | boolean;
|
mappedReaction?: Function | boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Updux {
|
export class Updux<
|
||||||
|
TState extends any = {},
|
||||||
|
TActions extends object = {},
|
||||||
|
TSelectors = {},
|
||||||
|
TSubduxes extends object = {}
|
||||||
|
> {
|
||||||
/** @type { unknown } */
|
/** @type { unknown } */
|
||||||
#initial = {};
|
#initial = {};
|
||||||
#subduxes = {};
|
#subduxes = {};
|
||||||
@ -84,7 +91,7 @@ export class Updux {
|
|||||||
#mappedSelectors = undefined;
|
#mappedSelectors = undefined;
|
||||||
#mappedReaction = undefined;
|
#mappedReaction = undefined;
|
||||||
|
|
||||||
constructor(config: UpduxConfig) {
|
constructor(config: UpduxConfig<TState, TActions, TSelectors, TSubduxes>) {
|
||||||
this.#initial = config.initial ?? {};
|
this.#initial = config.initial ?? {};
|
||||||
this.#subduxes = config.subduxes ?? {};
|
this.#subduxes = config.subduxes ?? {};
|
||||||
|
|
||||||
@ -135,7 +142,7 @@ export class Updux {
|
|||||||
this.#mappedSelectors = {
|
this.#mappedSelectors = {
|
||||||
...this.#mappedSelectors,
|
...this.#mappedSelectors,
|
||||||
[name]: f,
|
[name]: f,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get middleware() {
|
get middleware() {
|
||||||
@ -148,12 +155,12 @@ export class Updux {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @member { unknown } */
|
/** @member { unknown } */
|
||||||
get initial() {
|
get initial(): AggregateDuxState<TState, TSubduxes> {
|
||||||
return this.#memoInitial(this.#initial, this.#subduxes);
|
return this.#memoInitial(this.#initial, this.#subduxes);
|
||||||
}
|
}
|
||||||
|
|
||||||
get actions(): Record<string, Function> {
|
get actions(): AggregateDuxActions<TActions, TSubduxes> {
|
||||||
return this.#memoActions(this.#actions, this.#subduxes);
|
return this.#memoActions(this.#actions, this.#subduxes) as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
get selectors() {
|
get selectors() {
|
||||||
@ -323,14 +330,15 @@ export class Updux {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createStore(initial?: unknown, enhancerGenerator?: Function) {
|
createStore(initial?: unknown, enhancerGenerator?: Function) {
|
||||||
|
const enhancer = (enhancerGenerator ?? applyMiddleware)(
|
||||||
|
this.middleware
|
||||||
|
);
|
||||||
|
|
||||||
const enhancer = (enhancerGenerator ?? applyMiddleware)(this.middleware);
|
const store: {
|
||||||
|
getState: Function & Record<string, Function>;
|
||||||
const store : {
|
dispatch: Function & Record<string, Function>;
|
||||||
getState: Function & Record<string,Function>,
|
selectors: Record<string, Function>;
|
||||||
dispatch: Function & Record<string,Function>,
|
actions: AggregateDuxActions<TActions, TSubduxes>;
|
||||||
selectors: Record<string,Function>,
|
|
||||||
actions: Record<string,Function>,
|
|
||||||
} = reduxCreateStore(
|
} = reduxCreateStore(
|
||||||
this.reducer,
|
this.reducer,
|
||||||
initial ?? this.initial,
|
initial ?? this.initial,
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { action } from './actions.js';
|
import { action } from './actions';
|
||||||
|
|
||||||
test('action generators', () => {
|
test('action generators', () => {
|
||||||
const foo = action('foo');
|
const foo = action('foo');
|
||||||
|
|
||||||
expect(foo.type).toEqual( 'foo');
|
expect(foo.type).toEqual('foo');
|
||||||
expect(foo()).toMatchObject( { type: 'foo' });
|
expect(foo()).toMatchObject({ type: 'foo' });
|
||||||
|
|
||||||
const bar = action('bar');
|
const bar = action('bar');
|
||||||
|
|
||||||
expect(bar.type).toEqual( 'bar');
|
expect(bar.type).toEqual('bar');
|
||||||
expect(bar()).toMatchObject( { type: 'bar' });
|
expect(bar()).toMatchObject({ type: 'bar' });
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
export type Action<T extends string=string,TPayload=unknown> = {
|
export type Action<T extends string = string, TPayload = unknown> = {
|
||||||
type: T; meta?: Record<string,unknown>; } & (
|
type: T;
|
||||||
{ payload?: TPayload }
|
meta?: Record<string, unknown>;
|
||||||
)
|
} & {
|
||||||
|
payload?: TPayload;
|
||||||
|
};
|
||||||
|
|
||||||
export type ActionGenerator<TType extends string = string, TPayloadGen = undefined> = {
|
export type ActionGenerator<
|
||||||
|
TType extends string = string,
|
||||||
|
TPayloadGen = undefined
|
||||||
|
> = {
|
||||||
type: TType;
|
type: TType;
|
||||||
} & (TPayloadGen extends (...args:any) => any
|
} & (TPayloadGen extends (...args: any) => any
|
||||||
? (...args: Parameters<TPayloadGen>) => {
|
? (...args: Parameters<TPayloadGen>) => {
|
||||||
type: TType;
|
type: TType;
|
||||||
payload: ReturnType<TPayloadGen>;
|
payload: ReturnType<TPayloadGen>;
|
||||||
@ -22,7 +27,7 @@ export type ActionGenerator<TType extends string = string, TPayloadGen = undefin
|
|||||||
|
|
||||||
export function action(type, payloadFunction = null) {
|
export function action(type, payloadFunction = null) {
|
||||||
const generator = function (...payloadArg) {
|
const generator = function (...payloadArg) {
|
||||||
const result :Action = { type };
|
const result: Action = { type };
|
||||||
|
|
||||||
if (payloadFunction) {
|
if (payloadFunction) {
|
||||||
result.payload = payloadFunction(...payloadArg);
|
result.payload = payloadFunction(...payloadArg);
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import { buildInitial } from '.';
|
import { buildInitial } from '.';
|
||||||
|
|
||||||
test('basic', () => {
|
test('basic', () => {
|
||||||
expect(
|
expect(buildInitial({ a: 1 }, { b: { initial: { c: 2 } } })).toMatchObject({
|
||||||
buildInitial({ a: 1 }, { b: { initial: { c: 2 } } })
|
|
||||||
).toMatchObject({
|
|
||||||
a: 1,
|
a: 1,
|
||||||
b: { c: 2 },
|
b: { c: 2 },
|
||||||
});
|
});
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
import { map, mapValues, merge } from 'lodash';
|
import { map, mapValues, merge } from 'lodash';
|
||||||
|
|
||||||
export function buildSelectors(localSelectors, splatSelector = {}, subduxes ={}) {
|
export function buildSelectors(
|
||||||
|
localSelectors,
|
||||||
|
splatSelector = {},
|
||||||
|
subduxes = {}
|
||||||
|
) {
|
||||||
const subSelectors = map(subduxes, ({ selectors }, slice) => {
|
const subSelectors = map(subduxes, ({ selectors }, slice) => {
|
||||||
if (!selectors) return {};
|
if (!selectors) return {};
|
||||||
if (slice === '*') return {};
|
if (slice === '*') return {};
|
||||||
|
|
||||||
return mapValues(selectors, (func: Function) => (state) => func(state[slice]));
|
return mapValues(
|
||||||
|
selectors,
|
||||||
|
(func: Function) => (state) => func(state[slice])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let splat = {};
|
let splat = {};
|
||||||
|
|
||||||
for ( const name in splatSelector ) {
|
for (const name in splatSelector) {
|
||||||
splat[name] =
|
splat[name] =
|
||||||
(state) =>
|
(state) =>
|
||||||
(...args) => {
|
(...args) => {
|
||||||
|
@ -8,7 +8,8 @@ export function buildUpreducer(initial, mutations, subduxes = {}) {
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (action) => (state) => {
|
return (action) => (state) => {
|
||||||
if( !action?.type ) throw new Error("upreducer called with a bad action");
|
if (!action?.type)
|
||||||
|
throw new Error('upreducer called with a bad action');
|
||||||
|
|
||||||
let newState = state ?? initial;
|
let newState = state ?? initial;
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ test('README.md', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('tutorial', () => {
|
test('tutorial', () => {
|
||||||
const todosDux = new Updux({
|
const todosDux = new Updux<any, any>({
|
||||||
initial: {
|
initial: {
|
||||||
next_id: 1,
|
next_id: 1,
|
||||||
todos: [],
|
todos: [],
|
||||||
|
@ -14,9 +14,11 @@ test('basic', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(dux.reducer(undefined, dux.actions.doIt())).toEqual( 'bingo');
|
expect(dux.reducer(undefined, dux.actions.doIt())).toEqual('bingo');
|
||||||
|
|
||||||
expect(dux.reducer(undefined, dux.actions.thisToo())).toEqual( 'straight type');
|
expect(dux.reducer(undefined, dux.actions.thisToo())).toEqual(
|
||||||
|
'straight type'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('override', () => {
|
test('override', () => {
|
||||||
@ -48,8 +50,7 @@ test('override', () => {
|
|||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(state).toMatchObject(
|
expect(state).toMatchObject({
|
||||||
{
|
|
||||||
alpha: ['foo', 'bar'],
|
alpha: ['foo', 'bar'],
|
||||||
subbie: 1,
|
subbie: 1,
|
||||||
});
|
});
|
||||||
@ -77,8 +78,9 @@ test('order of processing', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(dux.reducer(undefined, foo()))
|
expect(dux.reducer(undefined, foo())).toMatchObject({
|
||||||
.toMatchObject({ x: ['subdux', 'main'] });
|
x: ['subdux', 'main'],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('setMutation', () => {
|
test('setMutation', () => {
|
||||||
@ -89,12 +91,11 @@ test('setMutation', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// noop
|
// noop
|
||||||
expect(dux.reducer(undefined, foo())).toEqual( '');
|
expect(dux.reducer(undefined, foo())).toEqual('');
|
||||||
|
|
||||||
dux.setMutation('foo', () => () => 'foo');
|
dux.setMutation('foo', () => () => 'foo');
|
||||||
|
|
||||||
expect(dux.reducer(undefined, foo())).toEqual( 'foo');
|
expect(dux.reducer(undefined, foo())).toEqual('foo');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('setMutation, name as function', () => {
|
test('setMutation, name as function', () => {
|
||||||
@ -105,5 +106,5 @@ test('setMutation, name as function', () => {
|
|||||||
});
|
});
|
||||||
dux.setMutation(bar, () => () => 'bar');
|
dux.setMutation(bar, () => () => 'bar');
|
||||||
|
|
||||||
expect(dux.reducer(undefined, bar())).toEqual( 'bar');
|
expect(dux.reducer(undefined, bar())).toEqual('bar');
|
||||||
});
|
});
|
||||||
|
@ -7,7 +7,7 @@ test('basic reducer', () => {
|
|||||||
|
|
||||||
expect(typeof dux.reducer).toBe('function');
|
expect(typeof dux.reducer).toBe('function');
|
||||||
|
|
||||||
expect(dux.reducer({ a: 1 }, { type: 'foo' })).toMatchObject({a:1}); // noop
|
expect(dux.reducer({ a: 1 }, { type: 'foo' })).toMatchObject({ a: 1 }); // noop
|
||||||
});
|
});
|
||||||
|
|
||||||
test('basic upreducer', () => {
|
test('basic upreducer', () => {
|
||||||
@ -15,7 +15,7 @@ test('basic upreducer', () => {
|
|||||||
|
|
||||||
expect(typeof dux.upreducer).toBe('function');
|
expect(typeof dux.upreducer).toBe('function');
|
||||||
|
|
||||||
expect(dux.upreducer({type:'foo'})({ a: 1 })).toMatchObject({a:1}); // noop
|
expect(dux.upreducer({ type: 'foo' })({ a: 1 })).toMatchObject({ a: 1 }); // noop
|
||||||
});
|
});
|
||||||
|
|
||||||
test('reducer with action', () => {
|
test('reducer with action', () => {
|
||||||
@ -28,5 +28,5 @@ test('reducer with action', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(dux.reducer({ a: 1 }, { type: 'inc' })).toMatchObject({a:2});
|
expect(dux.reducer({ a: 1 }, { type: 'inc' })).toMatchObject({ a: 2 });
|
||||||
});
|
});
|
||||||
|
@ -135,8 +135,7 @@ test('splat subscriptions, more', () => {
|
|||||||
initial: { a: 1 },
|
initial: { a: 1 },
|
||||||
actions: { foo: null, incAll: null },
|
actions: { foo: null, incAll: null },
|
||||||
mutations: {
|
mutations: {
|
||||||
foo: (id) => (state) =>
|
foo: (id) => (state) => state.a === id ? { ...state, b: 1 } : state,
|
||||||
state.a === id ? { ...state, b: 1 } : state,
|
|
||||||
incAll: () => (state) => ({ ...state, a: state.a + 1 }),
|
incAll: () => (state) => ({ ...state, a: state.a + 1 }),
|
||||||
},
|
},
|
||||||
reactions: [() => snitch],
|
reactions: [() => snitch],
|
||||||
@ -163,16 +162,8 @@ test('splat subscriptions, more', () => {
|
|||||||
|
|
||||||
expect(snitch).toHaveBeenCalledTimes(2);
|
expect(snitch).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
expect(snitch).toHaveBeenCalledWith(
|
expect(snitch).toHaveBeenCalledWith({ a: 1 }, undefined, expect.anything());
|
||||||
{ a: 1 },
|
expect(snitch).toHaveBeenCalledWith({ a: 2 }, undefined, expect.anything());
|
||||||
undefined,
|
|
||||||
expect.anything()
|
|
||||||
);
|
|
||||||
expect(snitch).toHaveBeenCalledWith(
|
|
||||||
{ a: 2 },
|
|
||||||
undefined,
|
|
||||||
expect.anything()
|
|
||||||
);
|
|
||||||
|
|
||||||
snitch.mockReset();
|
snitch.mockReset();
|
||||||
|
|
||||||
@ -194,11 +185,7 @@ test('splat subscriptions, more', () => {
|
|||||||
|
|
||||||
expect(snitch).toHaveBeenCalledTimes(1);
|
expect(snitch).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
expect(snitch).toHaveBeenCalledWith(
|
expect(snitch).toHaveBeenCalledWith(undefined, { a: 1 }, expect.anything());
|
||||||
undefined,
|
|
||||||
{ a: 1 },
|
|
||||||
expect.anything()
|
|
||||||
);
|
|
||||||
|
|
||||||
// only one subscriber left
|
// only one subscriber left
|
||||||
snitch.mockReset();
|
snitch.mockReset();
|
||||||
@ -246,9 +233,7 @@ test('many levels down', () => {
|
|||||||
mutations: {
|
mutations: {
|
||||||
add: () => (x) => x + 1,
|
add: () => (x) => x + 1,
|
||||||
},
|
},
|
||||||
reactions: [
|
reactions: [(store) => (state) => snitch(state, store)],
|
||||||
(store) => (state) => snitch(state, store),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -109,8 +109,7 @@ test('subscription within subduxes', () => {
|
|||||||
let innerState = jest.fn(() => null);
|
let innerState = jest.fn(() => null);
|
||||||
let outerState = jest.fn(() => null);
|
let outerState = jest.fn(() => null);
|
||||||
|
|
||||||
const resetMocks = () =>
|
const resetMocks = () => [innerState, outerState].map((f) => f.mockReset());
|
||||||
[innerState, outerState].map((f) => f.mockReset());
|
|
||||||
|
|
||||||
const inner = new Updux({
|
const inner = new Updux({
|
||||||
initial: 1,
|
initial: 1,
|
||||||
|
37
src/types.ts
37
src/types.ts
@ -1,2 +1,37 @@
|
|||||||
|
|
||||||
export type Dict<T> = Record<string, T>;
|
export type Dict<T> = Record<string, T>;
|
||||||
|
|
||||||
|
export type UnionToIntersection<U> = (
|
||||||
|
U extends any ? (k: U) => void : never
|
||||||
|
) extends (k: infer I) => void
|
||||||
|
? I
|
||||||
|
: never;
|
||||||
|
|
||||||
|
type Subdux<TState = any> = {
|
||||||
|
initial: TState;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateOf<D> = D extends { initial: infer I } ? I : unknown;
|
||||||
|
type ActionsOf<C> = C extends { actions: infer A }
|
||||||
|
? {
|
||||||
|
[K in keyof A]: Function;
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
type Subduxes = Record<string, Subdux>;
|
||||||
|
|
||||||
|
export type DuxStateSubduxes<C> = C extends { '*': infer I }
|
||||||
|
? {
|
||||||
|
[key: string]: StateOf<I>;
|
||||||
|
[index: number]: StateOf<I>;
|
||||||
|
}
|
||||||
|
: { [K in keyof C]: StateOf<C[K]> };
|
||||||
|
|
||||||
|
export type AggregateDuxState<TState, TSubduxes> = TState &
|
||||||
|
DuxStateSubduxes<TSubduxes>;
|
||||||
|
|
||||||
|
type DuxActionsSubduxes<C> = C extends object ? ActionsOf<C[keyof C]> : unknown;
|
||||||
|
|
||||||
|
type ItemsOf<C> = C extends object ? C[keyof C] : unknown;
|
||||||
|
|
||||||
|
export type AggregateDuxActions<TActions, TSubduxes> = TActions &
|
||||||
|
UnionToIntersection<ActionsOf<ItemsOf<TSubduxes>>>;
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
{
|
{
|
||||||
"include": [ "./src" ],
|
"include": ["./src"],
|
||||||
"exclude": [ "./docs", "./dist" ],
|
"exclude": ["./docs", "./dist"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"target": "es2020",
|
"target": "es2020",
|
||||||
"lib": [
|
"lib": ["es2020"],
|
||||||
"es2020"
|
|
||||||
],
|
|
||||||
"module": "ES2020",
|
"module": "ES2020",
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"strict": false,
|
"strict": false,
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Updux",
|
"name": "Updux",
|
||||||
"entryPoints": [
|
"entryPoints": ["./types/index.d.ts"],
|
||||||
"./types/index.d.ts"
|
|
||||||
],
|
|
||||||
"out": "docs/API",
|
"out": "docs/API",
|
||||||
"excludeExternals": true,
|
"excludeExternals": true,
|
||||||
"excludePrivate": true,
|
"excludePrivate": true,
|
||||||
|
Loading…
Reference in New Issue
Block a user