redux-starter-kit
Advanced tools
Comparing version 0.5.1 to 0.6.0
@@ -12,10 +12,16 @@ import { Action } from 'redux'; | ||
} | ||
export declare type Diff<T, U> = T extends U ? never : T; | ||
/** | ||
* An action creator that produces actions with a `payload` attribute. | ||
*/ | ||
export interface PayloadActionCreator<P = any, T extends string = string> { | ||
(): Action<T>; | ||
(payload: P): PayloadAction<P, T>; | ||
export declare type PayloadActionCreator<P = any, T extends string = string> = { | ||
type: T; | ||
} | ||
} & ([undefined] extends [P] ? { | ||
(payload?: undefined): PayloadAction<undefined, T>; | ||
<PT extends Diff<P, undefined>>(payload?: PT): PayloadAction<PT, T>; | ||
} : [void] extends [P] ? { | ||
(): PayloadAction<undefined, T>; | ||
} : { | ||
<PT extends P>(payload: PT): PayloadAction<PT, T>; | ||
}); | ||
/** | ||
@@ -22,0 +28,0 @@ * A utility function to create an action creator for the given action type |
import { Reducer } from 'redux'; | ||
import { PayloadAction } from './createAction'; | ||
import { PayloadAction, PayloadActionCreator } from './createAction'; | ||
import { CaseReducers } from './createReducer'; | ||
/** | ||
* An action creator atttached to a slice. | ||
* | ||
* @deprecated please use PayloadActionCreator directly | ||
*/ | ||
export declare type SliceActionCreator<P> = P extends void ? () => PayloadAction<void> : (payload: P) => PayloadAction<P>; | ||
export declare type SliceActionCreator<P> = PayloadActionCreator<P>; | ||
export interface Slice<S = any, AP extends { | ||
@@ -26,3 +28,3 @@ [key: string]: any; | ||
actions: { | ||
[type in keyof AP]: SliceActionCreator<AP[type]>; | ||
[type in keyof AP]: PayloadActionCreator<AP[type]>; | ||
}; | ||
@@ -29,0 +31,0 @@ /** |
@@ -61,2 +61,6 @@ 'use strict'; | ||
function _slicedToArray(arr, i) { | ||
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); | ||
} | ||
function _toConsumableArray(arr) { | ||
@@ -74,2 +78,6 @@ return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); | ||
function _arrayWithHoles(arr) { | ||
if (Array.isArray(arr)) return arr; | ||
} | ||
function _iterableToArray(iter) { | ||
@@ -79,2 +87,28 @@ if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); | ||
function _iterableToArrayLimit(arr, i) { | ||
var _arr = []; | ||
var _n = true; | ||
var _d = false; | ||
var _e = undefined; | ||
try { | ||
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { | ||
_arr.push(_s.value); | ||
if (i && _arr.length === i) break; | ||
} | ||
} catch (err) { | ||
_d = true; | ||
_e = err; | ||
} finally { | ||
try { | ||
if (!_n && _i["return"] != null) _i["return"](); | ||
} finally { | ||
if (_d) throw _e; | ||
} | ||
} | ||
return _arr; | ||
} | ||
function _nonIterableSpread() { | ||
@@ -84,2 +118,6 @@ throw new TypeError("Invalid attempt to spread non-iterable instance"); | ||
function _nonIterableRest() { | ||
throw new TypeError("Invalid attempt to destructure non-iterable instance"); | ||
} | ||
/** | ||
@@ -119,2 +157,3 @@ * Returns true if the passed value is "plain" object, i.e. an object whose | ||
var isSerializable = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : isPlain; | ||
var getEntries = arguments.length > 3 ? arguments[3] : undefined; | ||
var foundNestedSerializable; | ||
@@ -133,21 +172,43 @@ | ||
for (var _i = 0, _Object$keys = Object.keys(value); _i < _Object$keys.length; _i++) { | ||
var property = _Object$keys[_i]; | ||
var nestedPath = path.concat(property); | ||
var nestedValue = value[property]; | ||
var entries = getEntries != null ? getEntries(value) : Object.entries(value); | ||
var _iteratorNormalCompletion = true; | ||
var _didIteratorError = false; | ||
var _iteratorError = undefined; | ||
if (!isSerializable(nestedValue)) { | ||
return { | ||
keyPath: nestedPath.join('.'), | ||
value: nestedValue | ||
}; | ||
} | ||
try { | ||
for (var _iterator = entries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var _step$value = _slicedToArray(_step.value, 2), | ||
property = _step$value[0], | ||
nestedValue = _step$value[1]; | ||
if (_typeof(nestedValue) === 'object') { | ||
foundNestedSerializable = findNonSerializableValue(nestedValue, nestedPath, isSerializable); | ||
var nestedPath = path.concat(property); | ||
if (foundNestedSerializable) { | ||
return foundNestedSerializable; | ||
if (!isSerializable(nestedValue)) { | ||
return { | ||
keyPath: nestedPath.join('.'), | ||
value: nestedValue | ||
}; | ||
} | ||
if (_typeof(nestedValue) === 'object') { | ||
foundNestedSerializable = findNonSerializableValue(nestedValue, nestedPath, isSerializable, getEntries); | ||
if (foundNestedSerializable) { | ||
return foundNestedSerializable; | ||
} | ||
} | ||
} | ||
} catch (err) { | ||
_didIteratorError = true; | ||
_iteratorError = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion && _iterator["return"] != null) { | ||
_iterator["return"](); | ||
} | ||
} finally { | ||
if (_didIteratorError) { | ||
throw _iteratorError; | ||
} | ||
} | ||
} | ||
@@ -171,7 +232,8 @@ | ||
var _options$isSerializab = options.isSerializable, | ||
isSerializable = _options$isSerializab === void 0 ? isPlain : _options$isSerializab; | ||
isSerializable = _options$isSerializab === void 0 ? isPlain : _options$isSerializab, | ||
getEntries = options.getEntries; | ||
return function (storeAPI) { | ||
return function (next) { | ||
return function (action) { | ||
var foundActionNonSerializableValue = findNonSerializableValue(action, [], isSerializable); | ||
var foundActionNonSerializableValue = findNonSerializableValue(action, [], isSerializable, getEntries); | ||
@@ -186,3 +248,3 @@ if (foundActionNonSerializableValue) { | ||
var state = storeAPI.getState(); | ||
var foundStateNonSerializableValue = findNonSerializableValue(state); | ||
var foundStateNonSerializableValue = findNonSerializableValue(state, [], isSerializable, getEntries); | ||
@@ -375,2 +437,4 @@ if (foundStateNonSerializableValue) { | ||
* An action creator atttached to a slice. | ||
* | ||
* @deprecated please use PayloadActionCreator directly | ||
*/ | ||
@@ -377,0 +441,0 @@ |
@@ -57,2 +57,6 @@ import { combineReducers, applyMiddleware, createStore, compose } from 'redux'; | ||
function _slicedToArray(arr, i) { | ||
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); | ||
} | ||
function _toConsumableArray(arr) { | ||
@@ -70,2 +74,6 @@ return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); | ||
function _arrayWithHoles(arr) { | ||
if (Array.isArray(arr)) return arr; | ||
} | ||
function _iterableToArray(iter) { | ||
@@ -75,2 +83,28 @@ if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); | ||
function _iterableToArrayLimit(arr, i) { | ||
var _arr = []; | ||
var _n = true; | ||
var _d = false; | ||
var _e = undefined; | ||
try { | ||
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { | ||
_arr.push(_s.value); | ||
if (i && _arr.length === i) break; | ||
} | ||
} catch (err) { | ||
_d = true; | ||
_e = err; | ||
} finally { | ||
try { | ||
if (!_n && _i["return"] != null) _i["return"](); | ||
} finally { | ||
if (_d) throw _e; | ||
} | ||
} | ||
return _arr; | ||
} | ||
function _nonIterableSpread() { | ||
@@ -80,2 +114,6 @@ throw new TypeError("Invalid attempt to spread non-iterable instance"); | ||
function _nonIterableRest() { | ||
throw new TypeError("Invalid attempt to destructure non-iterable instance"); | ||
} | ||
/** | ||
@@ -115,2 +153,3 @@ * Returns true if the passed value is "plain" object, i.e. an object whose | ||
var isSerializable = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : isPlain; | ||
var getEntries = arguments.length > 3 ? arguments[3] : undefined; | ||
var foundNestedSerializable; | ||
@@ -129,21 +168,43 @@ | ||
for (var _i = 0, _Object$keys = Object.keys(value); _i < _Object$keys.length; _i++) { | ||
var property = _Object$keys[_i]; | ||
var nestedPath = path.concat(property); | ||
var nestedValue = value[property]; | ||
var entries = getEntries != null ? getEntries(value) : Object.entries(value); | ||
var _iteratorNormalCompletion = true; | ||
var _didIteratorError = false; | ||
var _iteratorError = undefined; | ||
if (!isSerializable(nestedValue)) { | ||
return { | ||
keyPath: nestedPath.join('.'), | ||
value: nestedValue | ||
}; | ||
} | ||
try { | ||
for (var _iterator = entries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var _step$value = _slicedToArray(_step.value, 2), | ||
property = _step$value[0], | ||
nestedValue = _step$value[1]; | ||
if (_typeof(nestedValue) === 'object') { | ||
foundNestedSerializable = findNonSerializableValue(nestedValue, nestedPath, isSerializable); | ||
var nestedPath = path.concat(property); | ||
if (foundNestedSerializable) { | ||
return foundNestedSerializable; | ||
if (!isSerializable(nestedValue)) { | ||
return { | ||
keyPath: nestedPath.join('.'), | ||
value: nestedValue | ||
}; | ||
} | ||
if (_typeof(nestedValue) === 'object') { | ||
foundNestedSerializable = findNonSerializableValue(nestedValue, nestedPath, isSerializable, getEntries); | ||
if (foundNestedSerializable) { | ||
return foundNestedSerializable; | ||
} | ||
} | ||
} | ||
} catch (err) { | ||
_didIteratorError = true; | ||
_iteratorError = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion && _iterator["return"] != null) { | ||
_iterator["return"](); | ||
} | ||
} finally { | ||
if (_didIteratorError) { | ||
throw _iteratorError; | ||
} | ||
} | ||
} | ||
@@ -167,7 +228,8 @@ | ||
var _options$isSerializab = options.isSerializable, | ||
isSerializable = _options$isSerializab === void 0 ? isPlain : _options$isSerializab; | ||
isSerializable = _options$isSerializab === void 0 ? isPlain : _options$isSerializab, | ||
getEntries = options.getEntries; | ||
return function (storeAPI) { | ||
return function (next) { | ||
return function (action) { | ||
var foundActionNonSerializableValue = findNonSerializableValue(action, [], isSerializable); | ||
var foundActionNonSerializableValue = findNonSerializableValue(action, [], isSerializable, getEntries); | ||
@@ -182,3 +244,3 @@ if (foundActionNonSerializableValue) { | ||
var state = storeAPI.getState(); | ||
var foundStateNonSerializableValue = findNonSerializableValue(state); | ||
var foundStateNonSerializableValue = findNonSerializableValue(state, [], isSerializable, getEntries); | ||
@@ -371,2 +433,4 @@ if (foundStateNonSerializableValue) { | ||
* An action creator atttached to a slice. | ||
* | ||
* @deprecated please use PayloadActionCreator directly | ||
*/ | ||
@@ -373,0 +437,0 @@ |
@@ -14,3 +14,3 @@ import { Middleware } from 'redux'; | ||
} | ||
export declare function findNonSerializableValue(value: unknown, path?: ReadonlyArray<string>, isSerializable?: (value: unknown) => boolean): NonSerializableValue | false; | ||
export declare function findNonSerializableValue(value: unknown, path?: ReadonlyArray<string>, isSerializable?: (value: unknown) => boolean, getEntries?: (value: unknown) => [string, any][]): NonSerializableValue | false; | ||
/** | ||
@@ -26,2 +26,8 @@ * Options for `createSerializableStateInvariantMiddleware()`. | ||
isSerializable?: (value: any) => boolean; | ||
/** | ||
* The function that will be used to retrieve entries from each | ||
* value. If unspecified, `Object.entries` will be used. Defaults | ||
* to `undefined`. | ||
*/ | ||
getEntries?: (value: any) => [string, any][]; | ||
} | ||
@@ -28,0 +34,0 @@ /** |
{ | ||
"name": "redux-starter-kit", | ||
"version": "0.5.1", | ||
"version": "0.6.0", | ||
"description": "A simple set of tools to make using Redux easier", | ||
@@ -5,0 +5,0 @@ "repository": "https://github.com/markerikson/redux-starter-kit", |
@@ -15,10 +15,24 @@ import { Action } from 'redux' | ||
export type Diff<T, U> = T extends U ? never : T; | ||
/** | ||
* An action creator that produces actions with a `payload` attribute. | ||
*/ | ||
export interface PayloadActionCreator<P = any, T extends string = string> { | ||
(): Action<T> | ||
(payload: P): PayloadAction<P, T> | ||
type: T | ||
} | ||
export type PayloadActionCreator<P = any, T extends string = string> = { type: T } & ( | ||
/* | ||
* The `P` generic is wrapped with a single-element tuple to prevent the | ||
* conditional from being checked distributively, thus preserving unions | ||
* of contra-variant types. | ||
*/ | ||
[undefined] extends [P] ? { | ||
(payload?: undefined): PayloadAction<undefined, T> | ||
<PT extends Diff<P, undefined>>(payload?: PT): PayloadAction<PT, T> | ||
} | ||
: [void] extends [P] ? { | ||
(): PayloadAction<undefined, T> | ||
} | ||
: { | ||
<PT extends P>(payload: PT): PayloadAction<PT, T> | ||
} | ||
); | ||
@@ -37,5 +51,3 @@ /** | ||
): PayloadActionCreator<P, T> { | ||
function actionCreator(): Action<T> | ||
function actionCreator(payload: P): PayloadAction<P, T> | ||
function actionCreator(payload?: P): Action<T> | PayloadAction<P, T> { | ||
function actionCreator(payload?: P): PayloadAction<undefined | P, T> { | ||
return { type, payload } | ||
@@ -48,3 +60,3 @@ } | ||
return actionCreator | ||
return actionCreator as any | ||
} | ||
@@ -51,0 +63,0 @@ |
import { Reducer } from 'redux' | ||
import { createAction, PayloadAction } from './createAction' | ||
import { createAction, PayloadAction, PayloadActionCreator } from './createAction' | ||
import { createReducer, CaseReducers } from './createReducer' | ||
@@ -8,6 +8,6 @@ import { createSliceSelector, createSelectorName } from './sliceSelector' | ||
* An action creator atttached to a slice. | ||
* | ||
* @deprecated please use PayloadActionCreator directly | ||
*/ | ||
export type SliceActionCreator<P> = P extends void | ||
? () => PayloadAction<void> | ||
: (payload: P) => PayloadAction<P> | ||
export type SliceActionCreator<P> = PayloadActionCreator<P> | ||
@@ -17,3 +17,3 @@ export interface Slice< | ||
AP extends { [key: string]: any } = { [key: string]: any } | ||
> { | ||
> { | ||
/** | ||
@@ -33,3 +33,3 @@ * The slice name. | ||
*/ | ||
actions: { [type in keyof AP]: SliceActionCreator<AP[type]> } | ||
actions: { [type in keyof AP]: PayloadActionCreator<AP[type]> } | ||
@@ -51,3 +51,3 @@ /** | ||
CR extends CaseReducers<S, any> = CaseReducers<S, any> | ||
> { | ||
> { | ||
/** | ||
@@ -81,6 +81,6 @@ * The slice's name. Used to namespace the generated action types and to | ||
[T in keyof CR]: CR[T] extends (state: any) => any | ||
? void | ||
: (CR[T] extends (state: any, action: PayloadAction<infer P>) => any | ||
? P | ||
: void) | ||
? void | ||
: (CR[T] extends (state: any, action: PayloadAction<infer P>) => any | ||
? P | ||
: void) | ||
} | ||
@@ -87,0 +87,0 @@ |
@@ -6,3 +6,4 @@ import { Reducer } from 'redux' | ||
createSerializableStateInvariantMiddleware, | ||
findNonSerializableValue | ||
findNonSerializableValue, | ||
isPlain, | ||
} from './serializableStateInvariantMiddleware' | ||
@@ -162,2 +163,161 @@ | ||
}) | ||
describe('consumer tolerated structures', () => { | ||
const nonSerializableValue = new Map(); | ||
const nestedSerializableObjectWithBadValue = { | ||
isSerializable: true, | ||
entries: (): [string, any][] => | ||
[ | ||
['good-string', 'Good!'], | ||
['good-number', 1337], | ||
['bad-map-instance', nonSerializableValue], | ||
], | ||
}; | ||
const serializableObject = { | ||
isSerializable: true, | ||
entries: (): [string, any][] => | ||
[ | ||
['first', 1], | ||
['second', 'B!'], | ||
['third', nestedSerializableObjectWithBadValue] | ||
], | ||
}; | ||
it('Should log an error when a non-serializable value is nested in state', () => { | ||
const ACTION_TYPE = 'TEST_ACTION' | ||
const initialState = { | ||
a: 0 | ||
} | ||
const reducer: Reducer = (state = initialState, action) => { | ||
switch (action.type) { | ||
case ACTION_TYPE: { | ||
return { | ||
a: serializableObject | ||
} | ||
} | ||
default: | ||
return state | ||
} | ||
} | ||
// use default options | ||
const serializableStateInvariantMiddleware = createSerializableStateInvariantMiddleware() | ||
const store = configureStore({ | ||
reducer: { | ||
testSlice: reducer | ||
}, | ||
middleware: [serializableStateInvariantMiddleware] | ||
}) | ||
store.dispatch({ type: ACTION_TYPE }) | ||
expect(console.error).toHaveBeenCalled() | ||
const [ | ||
message, | ||
keyPath, | ||
value, | ||
actionType | ||
] = (console.error as jest.Mock).mock.calls[0] | ||
// since default options are used, the `entries` function in `serializableObject` will cause the error | ||
expect(message).toContain('detected in the state, in the path: `%s`') | ||
expect(keyPath).toBe('testSlice.a.entries') | ||
expect(value).toBe(serializableObject.entries) | ||
expect(actionType).toBe(ACTION_TYPE) | ||
}) | ||
it('Should use consumer supplied isSerializable and getEntries options to tolerate certain structures', () => { | ||
const ACTION_TYPE = 'TEST_ACTION' | ||
const initialState = { | ||
a: 0 | ||
} | ||
const isSerializable = (val: any): boolean => val.isSerializable || isPlain(val); | ||
const getEntries = (val: any): [string, any][] => val.isSerializable ? val.entries() : Object.entries(val); | ||
const reducer: Reducer = (state = initialState, action) => { | ||
switch (action.type) { | ||
case ACTION_TYPE: { | ||
return { | ||
a: serializableObject | ||
} | ||
} | ||
default: | ||
return state | ||
} | ||
} | ||
const serializableStateInvariantMiddleware = createSerializableStateInvariantMiddleware({ isSerializable, getEntries }) | ||
const store = configureStore({ | ||
reducer: { | ||
testSlice: reducer | ||
}, | ||
middleware: [serializableStateInvariantMiddleware] | ||
}) | ||
store.dispatch({ type: ACTION_TYPE }) | ||
expect(console.error).toHaveBeenCalled() | ||
const [ | ||
message, | ||
keyPath, | ||
value, | ||
actionType | ||
] = (console.error as jest.Mock).mock.calls[0] | ||
// error reported is from a nested class instance, rather than the `entries` function `serializableObject` | ||
expect(message).toContain('detected in the state, in the path: `%s`') | ||
expect(keyPath).toBe('testSlice.a.third.bad-map-instance') | ||
expect(value).toBe(nonSerializableValue) | ||
expect(actionType).toBe(ACTION_TYPE) | ||
}) | ||
}); | ||
it('Should use the supplied isSerializable function to determine serializability', () => { | ||
const ACTION_TYPE = 'TEST_ACTION' | ||
const initialState = { | ||
a: 0 | ||
} | ||
const badValue = new Map() | ||
const reducer: Reducer = (state = initialState, action) => { | ||
switch (action.type) { | ||
case ACTION_TYPE: { | ||
return { | ||
a: badValue | ||
} | ||
} | ||
default: | ||
return state | ||
} | ||
} | ||
const serializableStateInvariantMiddleware = createSerializableStateInvariantMiddleware({ | ||
isSerializable: (value: any) => true | ||
}) | ||
const store = configureStore({ | ||
reducer: { | ||
testSlice: reducer | ||
}, | ||
middleware: [serializableStateInvariantMiddleware] | ||
}) | ||
store.dispatch({ type: ACTION_TYPE }) | ||
// Supplied 'isSerializable' considers all values serializable, hence | ||
// no error logging is expected: | ||
expect(console.error).not.toHaveBeenCalled() | ||
}) | ||
}) |
@@ -43,3 +43,4 @@ import isPlainObject from './isPlainObject' | ||
path: ReadonlyArray<string> = [], | ||
isSerializable: (value: unknown) => boolean = isPlain | ||
isSerializable: (value: unknown) => boolean = isPlain, | ||
getEntries?: (value: unknown) => [string, any][] | ||
): NonSerializableValue | false { | ||
@@ -59,5 +60,6 @@ let foundNestedSerializable: NonSerializableValue | false | ||
for (const property of Object.keys(value)) { | ||
const entries = getEntries != null ? getEntries(value) : Object.entries(value); | ||
for (const [property, nestedValue] of entries) { | ||
const nestedPath = path.concat(property) | ||
const nestedValue: unknown = (value as any)[property] | ||
@@ -75,3 +77,4 @@ if (!isSerializable(nestedValue)) { | ||
nestedPath, | ||
isSerializable | ||
isSerializable, | ||
getEntries | ||
) | ||
@@ -97,3 +100,9 @@ | ||
*/ | ||
isSerializable?: (value: any) => boolean | ||
isSerializable?: (value: any) => boolean, | ||
/** | ||
* The function that will be used to retrieve entries from each | ||
* value. If unspecified, `Object.entries` will be used. Defaults | ||
* to `undefined`. | ||
*/ | ||
getEntries?: (value: any) => [string, any][], | ||
} | ||
@@ -111,3 +120,3 @@ | ||
): Middleware { | ||
const { isSerializable = isPlain } = options | ||
const { isSerializable = isPlain, getEntries } = options | ||
@@ -118,3 +127,4 @@ return storeAPI => next => action => { | ||
[], | ||
isSerializable | ||
isSerializable, | ||
getEntries, | ||
) | ||
@@ -132,3 +142,8 @@ | ||
const foundStateNonSerializableValue = findNonSerializableValue(state) | ||
const foundStateNonSerializableValue = findNonSerializableValue( | ||
state, | ||
[], | ||
isSerializable, | ||
getEntries, | ||
) | ||
@@ -135,0 +150,0 @@ if (foundStateNonSerializableValue) { |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
224003
5952