This commit is contained in:
277
node_modules/@reduxjs/toolkit/src/serializableStateInvariantMiddleware.ts
generated
vendored
Normal file
277
node_modules/@reduxjs/toolkit/src/serializableStateInvariantMiddleware.ts
generated
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
import isPlainObject from './isPlainObject'
|
||||
import type { Middleware } from 'redux'
|
||||
import { getTimeMeasureUtils } from './utils'
|
||||
|
||||
/**
|
||||
* Returns true if the passed value is "plain", i.e. a value that is either
|
||||
* directly JSON-serializable (boolean, number, string, array, plain object)
|
||||
* or `undefined`.
|
||||
*
|
||||
* @param val The value to check.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function isPlain(val: any) {
|
||||
const type = typeof val
|
||||
return (
|
||||
val == null ||
|
||||
type === 'string' ||
|
||||
type === 'boolean' ||
|
||||
type === 'number' ||
|
||||
Array.isArray(val) ||
|
||||
isPlainObject(val)
|
||||
)
|
||||
}
|
||||
|
||||
interface NonSerializableValue {
|
||||
keyPath: string
|
||||
value: unknown
|
||||
}
|
||||
|
||||
type IgnorePaths = readonly (string | RegExp)[]
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function findNonSerializableValue(
|
||||
value: unknown,
|
||||
path: string = '',
|
||||
isSerializable: (value: unknown) => boolean = isPlain,
|
||||
getEntries?: (value: unknown) => [string, any][],
|
||||
ignoredPaths: IgnorePaths = [],
|
||||
cache?: WeakSet<object>
|
||||
): NonSerializableValue | false {
|
||||
let foundNestedSerializable: NonSerializableValue | false
|
||||
|
||||
if (!isSerializable(value)) {
|
||||
return {
|
||||
keyPath: path || '<root>',
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof value !== 'object' || value === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (cache?.has(value)) return false
|
||||
|
||||
const entries = getEntries != null ? getEntries(value) : Object.entries(value)
|
||||
|
||||
const hasIgnoredPaths = ignoredPaths.length > 0
|
||||
|
||||
for (const [key, nestedValue] of entries) {
|
||||
const nestedPath = path ? path + '.' + key : key
|
||||
|
||||
if (hasIgnoredPaths) {
|
||||
const hasMatches = ignoredPaths.some((ignored) => {
|
||||
if (ignored instanceof RegExp) {
|
||||
return ignored.test(nestedPath)
|
||||
}
|
||||
return nestedPath === ignored
|
||||
})
|
||||
if (hasMatches) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSerializable(nestedValue)) {
|
||||
return {
|
||||
keyPath: nestedPath,
|
||||
value: nestedValue,
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof nestedValue === 'object') {
|
||||
foundNestedSerializable = findNonSerializableValue(
|
||||
nestedValue,
|
||||
nestedPath,
|
||||
isSerializable,
|
||||
getEntries,
|
||||
ignoredPaths,
|
||||
cache
|
||||
)
|
||||
|
||||
if (foundNestedSerializable) {
|
||||
return foundNestedSerializable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cache && isNestedFrozen(value)) cache.add(value)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export function isNestedFrozen(value: object) {
|
||||
if (!Object.isFrozen(value)) return false
|
||||
|
||||
for (const nestedValue of Object.values(value)) {
|
||||
if (typeof nestedValue !== 'object' || nestedValue === null) continue
|
||||
|
||||
if (!isNestedFrozen(nestedValue)) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for `createSerializableStateInvariantMiddleware()`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface SerializableStateInvariantMiddlewareOptions {
|
||||
/**
|
||||
* The function to check if a value is considered serializable. This
|
||||
* function is applied recursively to every value contained in the
|
||||
* state. Defaults to `isPlain()`.
|
||||
*/
|
||||
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][]
|
||||
|
||||
/**
|
||||
* An array of action types to ignore when checking for serializability.
|
||||
* Defaults to []
|
||||
*/
|
||||
ignoredActions?: string[]
|
||||
|
||||
/**
|
||||
* An array of dot-separated path strings or regular expressions to ignore
|
||||
* when checking for serializability, Defaults to
|
||||
* ['meta.arg', 'meta.baseQueryMeta']
|
||||
*/
|
||||
ignoredActionPaths?: (string | RegExp)[]
|
||||
|
||||
/**
|
||||
* An array of dot-separated path strings or regular expressions to ignore
|
||||
* when checking for serializability, Defaults to []
|
||||
*/
|
||||
ignoredPaths?: (string | RegExp)[]
|
||||
/**
|
||||
* Execution time warning threshold. If the middleware takes longer
|
||||
* than `warnAfter` ms, a warning will be displayed in the console.
|
||||
* Defaults to 32ms.
|
||||
*/
|
||||
warnAfter?: number
|
||||
|
||||
/**
|
||||
* Opt out of checking state. When set to `true`, other state-related params will be ignored.
|
||||
*/
|
||||
ignoreState?: boolean
|
||||
|
||||
/**
|
||||
* Opt out of checking actions. When set to `true`, other action-related params will be ignored.
|
||||
*/
|
||||
ignoreActions?: boolean
|
||||
|
||||
/**
|
||||
* Opt out of caching the results. The cache uses a WeakSet and speeds up repeated checking processes.
|
||||
* The cache is automatically disabled if no browser support for WeakSet is present.
|
||||
*/
|
||||
disableCache?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a middleware that, after every state change, checks if the new
|
||||
* state is serializable. If a non-serializable value is found within the
|
||||
* state, an error is printed to the console.
|
||||
*
|
||||
* @param options Middleware options.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createSerializableStateInvariantMiddleware(
|
||||
options: SerializableStateInvariantMiddlewareOptions = {}
|
||||
): Middleware {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return () => (next) => (action) => next(action)
|
||||
}
|
||||
const {
|
||||
isSerializable = isPlain,
|
||||
getEntries,
|
||||
ignoredActions = [],
|
||||
ignoredActionPaths = ['meta.arg', 'meta.baseQueryMeta'],
|
||||
ignoredPaths = [],
|
||||
warnAfter = 32,
|
||||
ignoreState = false,
|
||||
ignoreActions = false,
|
||||
disableCache = false,
|
||||
} = options
|
||||
|
||||
const cache: WeakSet<object> | undefined =
|
||||
!disableCache && WeakSet ? new WeakSet() : undefined
|
||||
|
||||
return (storeAPI) => (next) => (action) => {
|
||||
const result = next(action)
|
||||
|
||||
const measureUtils = getTimeMeasureUtils(
|
||||
warnAfter,
|
||||
'SerializableStateInvariantMiddleware'
|
||||
)
|
||||
|
||||
if (
|
||||
!ignoreActions &&
|
||||
!(ignoredActions.length && ignoredActions.indexOf(action.type) !== -1)
|
||||
) {
|
||||
measureUtils.measureTime(() => {
|
||||
const foundActionNonSerializableValue = findNonSerializableValue(
|
||||
action,
|
||||
'',
|
||||
isSerializable,
|
||||
getEntries,
|
||||
ignoredActionPaths,
|
||||
cache
|
||||
)
|
||||
|
||||
if (foundActionNonSerializableValue) {
|
||||
const { keyPath, value } = foundActionNonSerializableValue
|
||||
|
||||
console.error(
|
||||
`A non-serializable value was detected in an action, in the path: \`${keyPath}\`. Value:`,
|
||||
value,
|
||||
'\nTake a look at the logic that dispatched this action: ',
|
||||
action,
|
||||
'\n(See https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants)',
|
||||
'\n(To allow non-serializable values see: https://redux-toolkit.js.org/usage/usage-guide#working-with-non-serializable-data)'
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!ignoreState) {
|
||||
measureUtils.measureTime(() => {
|
||||
const state = storeAPI.getState()
|
||||
|
||||
const foundStateNonSerializableValue = findNonSerializableValue(
|
||||
state,
|
||||
'',
|
||||
isSerializable,
|
||||
getEntries,
|
||||
ignoredPaths,
|
||||
cache
|
||||
)
|
||||
|
||||
if (foundStateNonSerializableValue) {
|
||||
const { keyPath, value } = foundStateNonSerializableValue
|
||||
|
||||
console.error(
|
||||
`A non-serializable value was detected in the state, in the path: \`${keyPath}\`. Value:`,
|
||||
value,
|
||||
`
|
||||
Take a look at the reducer(s) handling this action type: ${action.type}.
|
||||
(See https://redux.js.org/faq/organizing-state#can-i-put-functions-promises-or-other-non-serializable-items-in-my-store-state)`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
measureUtils.warnIfExceeded()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user