⚙️ Réorganise l'API publique de la lib

pull/925/head
Maxime Quandalle 2020-03-10 11:52:53 +01:00
parent c9325b8eed
commit c65a04cf86
16 changed files with 313 additions and 286 deletions

View File

@ -121,7 +121,8 @@
"@types/color-convert": "^1.9.0",
"@types/iframe-resizer": "^3.5.7",
"@types/js-yaml": "^3.12.2",
"@types/ramda": "^0.26.33",
"@types/nearley": "^2.11.1",
"@types/ramda": "^0.26.43",
"@types/raven-for-redux": "^1.1.1",
"@types/react": "^16.9.11",
"@types/react-addons-css-transition-group": "^15.0.5",

View File

@ -2,7 +2,7 @@ import { coerceArray } from '../utils'
export function syntaxError(
rules: string[] | string,
message: string,
originalError: Error
originalError?: Error
) {
throw new Error(
`\n[ Erreur syntaxique ]

View File

@ -1,5 +1,6 @@
import { bonus, evaluateNode, mergeMissing } from 'Engine/evaluation'
import { map, mergeAll, pick, pipe } from 'ramda'
import { Rule } from 'Types/rule'
import { typeWarning } from './error'
import { convertNodeToUnit } from './nodeUnits'
import { anyNull, undefOrTruthy, val } from './traverse-common-functions'
@ -9,12 +10,14 @@ export const evaluateApplicability = (
cache,
situationGate,
parsedRules,
node
node: Rule
) => {
let evaluatedAttributes = pipe(
pick(['non applicable si', 'applicable si', 'rendu non applicable']),
pick(['non applicable si', 'applicable si', 'rendu non applicable']) as (
x: any
) => any,
map(value => evaluateNode(cache, situationGate, parsedRules, value))
)(node),
)(node) as any,
{
'non applicable si': notApplicable,
'applicable si': applicable,
@ -51,39 +54,43 @@ export const evaluateApplicability = (
export default (cache, situationGate, parsedRules, node) => {
cache._meta.contextRule.push(node.dottedName)
let applicabilityEvaluation = evaluateApplicability(
cache,
situationGate,
parsedRules,
node
),
{
missingVariables: condMissing,
nodeValue: isApplicable
} = applicabilityEvaluation,
evaluateFormula = () =>
node.formule
? evaluateNode(cache, situationGate, parsedRules, node.formule)
: {},
// evaluate the formula lazily, only if the applicability is known and true
evaluatedFormula = isApplicable
? evaluateFormula()
: isApplicable === false
? {
...node.formule,
missingVariables: {},
nodeValue: 0
}
: {
...node.formule,
missingVariables: {},
nodeValue: null
},
{ missingVariables: formulaMissingVariables, nodeValue } = evaluatedFormula,
missingVariables = mergeMissing(
bonus(condMissing, !!Object.keys(condMissing).length),
formulaMissingVariables
)
const applicabilityEvaluation = evaluateApplicability(
cache,
situationGate,
parsedRules,
node
)
const {
missingVariables: condMissing,
nodeValue: isApplicable
} = applicabilityEvaluation
const evaluateFormula = () =>
node.formule
? evaluateNode(cache, situationGate, parsedRules, node.formule)
: {}
// evaluate the formula lazily, only if the applicability is known and true
const evaluatedFormula = isApplicable
? evaluateFormula()
: isApplicable === false
? {
...node.formule,
missingVariables: {},
nodeValue: 0
}
: {
...node.formule,
missingVariables: {},
nodeValue: null
}
let {
missingVariables: formulaMissingVariables,
nodeValue
} = evaluatedFormula
const missingVariables = mergeMissing(
bonus(condMissing, !!Object.keys(condMissing).length),
formulaMissingVariables
)
const unit =
node.unit ||
(node.defaultUnit &&

View File

@ -1,22 +0,0 @@
L'objectif dans un premier temps est de faire une démonstration d'interface de saisie pour calculer les obligations du CDD. Le CDD est un contrat d'exception, ce qui le rend assez complexe.
Les Variables du système social comportent des entrées particulières (des cléfs de l'objet) que l'on nommera "mécanismes". Elles diffèrent des entrées simples (telle la description d'une variable) car elles sont susceptibles de comporter des variables, et la description de calculs.
Voici une liste des mécanismes qui sont à traverser pour notamment :
- d'extraire les variables utilisées et donc que l'utilisateur devra saisir
- d'attribuer une note à ces variables pour déterminer la prochaine saisie (ou "question")
- de court-circuiter les branches rendues inutiles par la situation courante saisie par l'utilisateur, ce qui influence la note.
Dans un premier temps, l'idée est de créer un formulaire à questions unitaires optimisées pour que la saisie de données inutiles soit évitée.
- Il faut résoudre le calcul des variables.
- ça introduit l'assiette 2300€
- ça nous donne son intervalle en % vu que l'assiette est constante, et donc un avancement du formulaire
- ça nous donne des résultats !!!!!! :))
- Il faut donner des explications à TOUTES les variables. Aucune n'est triviale !
- Il faut ajouter la notion d'entité.
- Ce qui permettra de regrouper les questions par thème. Exemple, demander "Quel type de CDD" et ordonner les réponses possibles par influence (e.g. si c'est un CDD d'usage )
-----------------------------------------

View File

@ -1,69 +0,0 @@
// This file exports the functions of the public computing library
import { safeLoad } from 'js-yaml'
import { collectDefaults, enrichRule, rulesFr } from './rules'
import { analyseMany, parseAll } from './traverse'
// The public evaluation function takes a nested object of input values
let inputToStateSelector = rules => input => dottedName =>
({
...collectDefaults(rules),
...input
}[dottedName])
let enrichRules = input => {
const rules = typeof input === 'string' ? safeLoad(input) : input
const rulesList = Array.isArray(rules)
? rules
: Object.entries(rules).map(([dottedName, rule]) => ({
dottedName,
...rule
}))
return rulesList.map(enrichRule)
}
class Engine {
situation = {}
parsedRules
constructor(rules = rulesFr) {
this.parsedRules = parseAll(rules)
this.defaultValues = collectDefaults(rules)
}
evaluate(targets, { defaultUnits, situation, withDefaultValues = true }) {
this.evaluation = analyseMany(
this.parsedRules,
targets,
defaultUnits
)(
dottedName =>
situation[dottedName] ||
(withDefaultValues && this.defaultValues[dottedName])
)
return this.evaluation.targets.map(({ nodeValue }) => nodeValue)
}
getLastEvaluationExplanations() {
return this.evaluation
}
}
export default {
evaluate: (targetInput, input, config, defaultUnits = []) => {
let rules = config
? [
...(config.base ? enrichRules(config.base) : rulesFr),
...(config.extra ? enrichRules(config.extra) : [])
]
: rulesFr
let evaluation = analyseMany(
parseAll(rules),
Array.isArray(targetInput) ? targetInput : [targetInput],
defaultUnits
)(inputToStateSelector(rules)(input))
if (config?.debug) return evaluation
let values = evaluation.targets.map(t => t.nodeValue)
return Array.isArray(targetInput) ? values : values[0]
},
Engine
}

86
source/engine/index.ts Normal file
View File

@ -0,0 +1,86 @@
import { safeLoad } from 'js-yaml'
import { Simulation } from 'Reducers/rootReducer'
import { DottedName, Rule } from 'Types/rule'
import { evaluateNode } from './evaluation'
import { collectDefaults, enrichRule, rulesFr } from './rules'
import { parseAll } from './traverse'
import { parseUnit } from './units'
const emptyCache = {
_meta: { contextRule: [], defaultUnits: [] }
}
type EngineConfig = {
rules?: string | Array<any> | object
extra?: string | Array<any> | object
}
let enrichRules = input => {
const rules = typeof input === 'string' ? safeLoad(input) : input
const rulesList = Array.isArray(rules)
? rules
: Object.entries(rules).map(([dottedName, rule]) => ({
dottedName,
...(rule as any)
}))
return rulesList.map(enrichRule)
}
export default class Engine {
parsedRules: Record<DottedName, Rule>
defaultValues: Simulation['situation']
situation: Simulation['situation'] = {}
cache = { ...emptyCache }
constructor(config: EngineConfig = {}) {
const rules = config
? [
...(config.rules ? enrichRules(config.rules) : rulesFr),
...(config.extra ? enrichRules(config.extra) : [])
]
: rulesFr
this.parsedRules = parseAll(rules) as any
this.defaultValues = collectDefaults(rules)
}
private resetCache() {
this.cache = { ...emptyCache }
}
setSituation(situation: Simulation['situation'] = {}) {
this.situation = situation
this.resetCache()
}
setDefaultUnits(defaultUnits = []) {
this.cache._meta.defaultUnits = defaultUnits.map(unit =>
parseUnit(unit)
) as any
}
evaluate(expression: string | Array<string>) {
const results = (Array.isArray(expression) ? expression : [expression]).map(
expr =>
this.cache[expr] ||
(this.parsedRules[expr]
? evaluateNode(
this.cache,
this.situationGate,
this.parsedRules,
this.parsedRules[expr]
// TODO: To support expressions (with operations, unit conversion,
// etc.) it should be enough to replace the above line with :
// parse(this.parsedRules, { dottedName: '' }, this.parsedRules)(expr)
// But currently there are small side effects (null values converted
// to 0), so we need to modify a little bit the engine before enabling
// publicode expressions in the UI.
)
: null)
)
return Array.isArray(expression) ? results : results[0]
}
situationGate = (dottedName: string) =>
this.situation[dottedName] || this.defaultValues[dottedName]
}

View File

@ -25,7 +25,7 @@ import {
subtract
} from 'ramda'
import React from 'react'
import { syntaxError } from './error.ts'
import { syntaxError } from './error'
import grammar from './grammar.ne'
import {
mecanismAllOf,
@ -69,7 +69,7 @@ Utilisez leur contrepartie française : 'oui' / 'non'`
const compiledGrammar = Grammar.fromCompiled(grammar)
const parseExpression = (rule, rawNode) => {
export const parseExpression = (rule, rawNode) => {
/* Strings correspond to infix expressions.
* Indeed, a subset of expressions like simple arithmetic operations `3 + (quantity * 2)` or like `salary [month]` are more explicit that their prefixed counterparts.
* This function makes them prefixed operations. */

View File

@ -1,13 +1,13 @@
import { ShowValuesConsumer } from 'Components/rule/ShowValuesContext'
import RuleLink from 'Components/RuleLink'
import evaluate from 'Engine/evaluateRule'
import { parse } from 'Engine/parse'
import { evolve, map } from 'ramda'
import React from 'react'
import { Trans } from 'react-i18next'
import { coerceArray } from '../utils'
import evaluate from './evaluateRule'
import { evaluateNode, makeJsx, mergeAllMissing } from './evaluation'
import { Node } from './mecanismViews/common'
import { parse } from './parse'
import { disambiguateRuleReference, findParentDependencies } from './rules'
export default (rules, rule, parsedRules) => {

37
source/engine/react.tsx Normal file
View File

@ -0,0 +1,37 @@
import Value from 'Components/Value'
import React, { createContext, useContext, useMemo } from 'react'
import Engine from '.'
const EngineContext = createContext(new Engine())
type InputProps = {
rules?: any
situation?: any
children: React.ReactNode
}
export function Provider({ rules, situation, children }: InputProps) {
const engine = useMemo(() => new Engine({ rules }), [rules])
if (!Object.is(situation, engine.situation)) {
engine.setSituation(situation)
}
return (
<EngineContext.Provider value={engine}>{children}</EngineContext.Provider>
)
}
export function useEvaluation(expression: string) {
const engine = useContext(EngineContext)
return engine.evaluate(expression)
}
export function Evaluation({ expression }) {
const value = useEvaluation(expression)
return <Value {...value} />
}
export default {
Provider,
useEvaluation,
Evaluation
}

View File

@ -1,11 +1,9 @@
import baremeIr from '!!raw-loader!./exemples/bareme-ir.yaml'
import douche from '!!raw-loader!./exemples/douche.yaml'
import { ControlledEditor } from '@monaco-editor/react'
import { formatValue } from 'Engine/format'
import Engine from 'Engine/index'
import { buildFlatRules } from 'Engine/rules'
import Engine from 'Engine/react'
import { safeLoad } from 'js-yaml'
import React, { useRef, useState } from 'react'
import React, { useState } from 'react'
import emoji from 'react-easy-emoji'
import { useLocation } from 'react-router'
import styled from 'styled-components'
@ -63,75 +61,69 @@ export function Studio() {
)
const [targets, setTargets] = useState<string[]>([])
const [currentTarget, setCurrentTarget] = useState('')
const [analysis, setAnalysis] = useState()
const engine = useRef<any>(null)
const [rules, setRules] = useState(editorValue)
try {
setTargets(Object.keys(safeLoad(editorValue) ?? {}))
} catch {}
function updateResult() {
engine.current = new Engine.Engine(buildFlatRules(safeLoad(editorValue)))
engine.current.evaluate(
[targets.includes(currentTarget) ? currentTarget : targets[0]],
{ defaultUnits: [], situation: {} }
)
setAnalysis(engine.current.getLastEvaluationExplanations()?.targets?.[0])
}
return (
<Layout>
<section>
<ControlledEditor
<Engine.Provider rules={rules}>
<Layout>
<section>
<ControlledEditor
css={`
height: 100%;
`}
language="yaml"
value={editorValue}
onChange={(ev, newValue) => setEditorValue(newValue ?? '')}
options={{ minimap: { enabled: false } }}
/>
</section>
<section
css={`
height: 100%;
`}
language="yaml"
value={editorValue}
onChange={(ev, newValue) => setEditorValue(newValue ?? '')}
options={{ minimap: { enabled: false } }}
/>
</section>
<section
css={`
padding: 30px 20px;
`}
>
<div
css={`
background: var(--lighterColor);
padding: 20px;
border-radius: 5px;
padding: 30px 20px;
`}
>
<label htmlFor="objectif">Que voulez-vous calculer ? </label>
<select
id="objectif"
onChange={e => {
setCurrentTarget(e.target.value)
}}
<div
css={`
padding: 5px;
background: var(--lighterColor);
padding: 20px;
border-radius: 5px;
`}
>
{targets.map(target => (
<option key={target} value={target}>
{target}
</option>
))}
</select>
<br />
<button
css="display: block; margin-top: 1rem"
className="ui__ button small"
onClick={() => updateResult()}
>
{emoji('▶️')} Calculer
</button>
</div>
<Results analysis={analysis} />
</section>
</Layout>
<label htmlFor="objectif">Que voulez-vous calculer ? </label>
<select
id="objectif"
onChange={e => {
setCurrentTarget(e.target.value)
}}
css={`
padding: 5px;
`}
>
{targets.map(target => (
<option key={target} value={target}>
{target}
</option>
))}
</select>
<br />
<br />
<button
className="ui__ button small"
onClick={() => setRules(editorValue)}
>
{emoji('▶️')} Recalculer
</button>
</div>
<Results
rule={targets.includes(currentTarget) ? currentTarget : targets[0]}
/>
</section>
</Layout>
</Engine.Provider>
)
}
@ -153,18 +145,15 @@ const Layout = styled.div`
}
`
export const Results = ({ analysis }) => {
export const Results = ({ rule }) => {
const analysis = Engine.useEvaluation(rule)
return analysis ? (
<div>
<h2>Résultats</h2>
{analysis.isApplicable === false ? (
<> Cette règle n'est pas applicable</>
<>{emoji('❌')} Cette règle n'est pas applicable</>
) : (
formatValue({
language: 'fr',
value: analysis.nodeValue,
unit: analysis.unit
})
<Engine.Evaluation expression={rule} />
)}
</div>
) : null

View File

@ -0,0 +1,4 @@
declare module '*.ne' {
const content: any
export default content
}

View File

@ -10,6 +10,7 @@ export type Rule = {
summary?: string
title?: string
defaultValue: any
parentDependencies: Array<Rule>
icons: string
formule: any
}

View File

@ -1,32 +1,33 @@
import { expect } from 'chai'
import Lib from '../source/engine/index'
import Engine from '../source/engine/index'
import co2 from './rules/co2.yaml'
import sasuRules from './rules/sasu.yaml'
describe('library', function() {
it('should evaluate one target with no input data', function() {
let target = 'contrat salarié . rémunération . net'
let value = Lib.evaluate(target, {
let engine = new Engine()
engine.setSituation({
'contrat salarié . rémunération . brut de base': 2300
})
expect(value).to.be.within(1798, 1800)
expect(engine.evaluate(target).nodeValue).to.be.within(1798, 1800)
})
it('should let the user replace the default rules', function() {
let rules = `
- nom: yo
yo:
formule: 200
- nom: ya
ya:
formule: yo + 1
- nom: yi
yi:
formule: yo + 2
`
let engine = new Engine({ rules })
let values = Lib.evaluate(['ya', 'yi'], {}, { base: rules })
expect(values[0]).to.equal(201)
expect(values[1]).to.equal(202)
expect(engine.evaluate('ya').nodeValue).to.equal(201)
expect(engine.evaluate('yi').nodeValue).to.equal(202)
})
it('should let the user add rules to the default ones', function() {
let rules = `
yo:
@ -34,60 +35,54 @@ yo:
ya:
formule: contrat salarié . rémunération . net + yo
`
let value = Lib.evaluate(
'ya',
{
'contrat salarié . rémunération . brut de base': 2300
},
{ extra: rules }
)
expect(value).to.be.closeTo(1799, 1)
let engine = new Engine({ extra: rules })
engine.setSituation({
'contrat salarié . rémunération . brut de base': 2300
})
expect(engine.evaluate('ya').nodeValue).to.be.closeTo(1799, 1)
})
it('should let the user extend the rules constellation in a serious manner', function() {
let CA = 550 * 16
let salaireTotal = Lib.evaluate(
'salaire total',
{
'chiffre affaires': CA
},
{ extra: sasuRules }
)
let engine = new Engine({ extra: sasuRules })
engine.setSituation({
'chiffre affaires': CA
})
let salaireTotal = engine.evaluate('salaire total').nodeValue
let salaireNetAprèsImpôt = Lib.evaluate(
engine.setSituation({
'contrat salarié . prix du travail': salaireTotal
})
let salaireNetAprèsImpôt = engine.evaluate(
'contrat salarié . rémunération . net après impôt'
).nodeValue
engine.setSituation({
'contrat salarié . rémunération . net après impôt': salaireNetAprèsImpôt,
'chiffre affaires': CA
})
let [revenuDisponible, dividendes] = engine.evaluate([
'contrat salarié . rémunération . net après impôt',
{
'contrat salarié . prix du travail': salaireTotal
}
)
let [revenuDisponible, dividendes] = Lib.evaluate(
['contrat salarié . rémunération . net après impôt', 'dividendes . net'],
{
'contrat salarié . rémunération . net après impôt': salaireNetAprèsImpôt,
'chiffre affaires': CA
},
{ extra: sasuRules }
)
'dividendes . net'
])
expect(revenuDisponible).to.be.closeTo(2324, 1)
expect(dividendes).to.be.closeTo(2507, 1)
expect(revenuDisponible.nodeValue).to.be.closeTo(2324, 1)
expect(dividendes.nodeValue).to.be.closeTo(2507, 1)
}).timeout(5000)
it('should let the user define a simplified revenue tax system', function() {
let règles = `
- nom: revenu imposable
let rules = `
revenu imposable:
question: Quel est votre revenu imposable ?
unité:
- nom: revenu abattu
revenu abattu:
formule:
allègement:
assiette: revenu imposable
abattement: 10%
- nom: impôt sur le revenu
impôt sur le revenu:
formule:
barème:
assiette: revenu abattu
@ -102,8 +97,7 @@ ya:
plafond: 153783
- taux: 45%
- nom: impôt sur le revenu à payer
impôt sur le revenu à payer:
formule:
allègement:
assiette: impôt sur le revenu
@ -112,28 +106,22 @@ ya:
plafond: 1177
`
let target = 'impôt sur le revenu à payer'
let value = Lib.evaluate(
target,
{ 'revenu imposable': '48000' },
{ base: règles }
)
expect(value).to.equal(7253.26)
let engine = new Engine({ rules })
engine.setSituation({
'revenu imposable': '48000'
})
let value = engine.evaluate('impôt sur le revenu à payer')
expect(value.nodeValue).to.equal(7253.26)
})
it('should let let user define a rule base on a completely different subject', function() {
let targets = 'impact'
let value = Lib.evaluate(
targets,
{
'nombre de douches': 30,
'chauffage . type': 'gaz',
'durée de la douche': 10
},
{ base: co2, debug: false }
)
//console.log(JSON.stringify(value.targets[0], null, 4))
expect(value).to.be.within(20, 21)
it('should let the user define a rule base on a completely different subject', function() {
let engine = new Engine({ rules: co2 })
engine.setSituation({
'nombre de douches': 30,
'chauffage . type': 'gaz',
'durée de la douche': 10
})
let value = engine.evaluate('douche . impact')
expect(value.nodeValue).to.be.within(20, 21)
})
})

View File

@ -11,7 +11,7 @@ import autoentrepreneurConfig from '../../source/components/simulationConfigs/au
import independantConfig from '../../source/components/simulationConfigs/indépendant.yaml'
import remunerationDirigeantConfig from '../../source/components/simulationConfigs/rémunération-dirigeant.yaml'
import employeeConfig from '../../source/components/simulationConfigs/salarié.yaml'
import Lib from '../../source/engine/index'
import Engine from '../../source/engine'
import artisteAuteurSituations from './simulations-artiste-auteur.yaml'
import autoEntrepreneurSituations from './simulations-auto-entrepreneur.yaml'
import independentSituations from './simulations-indépendant.yaml'
@ -19,7 +19,7 @@ import remunerationDirigeantSituations from './simulations-rémunération-dirige
import employeeSituations from './simulations-salarié.yaml'
const roundResult = arr => arr.map(x => Math.round(x))
const engine = new Lib.Engine()
const engine = new Engine()
const runSimulations = (
situations,
targets,
@ -29,10 +29,9 @@ const runSimulations = (
) =>
Object.entries(situations).map(([name, situations]) =>
situations.forEach(situation => {
const res = engine.evaluate(targets, {
situation: { ...baseSituation, ...situation },
defaultUnits
})
engine.setSituation({ ...baseSituation, ...situation })
engine.setDefaultUnits(defaultUnits)
const res = engine.evaluate(targets).map(node => node.nodeValue)
// Stringify is not required, but allows the result to be displayed in a single
// line in the snapshot, which considerably reduce the number of lines of this snapshot
// and improve its readability.

View File

@ -28,6 +28,7 @@
"Components": ["components"],
"Components/*": ["components/*"],
"Ui/*": ["components/ui/*"],
"Engine": ["engine"],
"Engine/*": ["engine/*"],
"Images/*": ["images/*"],
"Reducers/*": ["reducers/*"],

View File

@ -1200,6 +1200,11 @@
resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.0.tgz#9ca52cda363f699c69466c2a6ccdaad913ea7a73"
integrity sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM=
"@types/nearley@^2.11.1":
version "2.11.1"
resolved "https://registry.yarnpkg.com/@types/nearley/-/nearley-2.11.1.tgz#6ac3f57c00ca28071a1774ec72d2e45750f21420"
integrity sha512-oaAg5gn74VFpPYs6Ou2pjDao3WJxnlnH29q9rLOxSGb0PTw2QtBQcTAN9xs1OAHrtI9En5kIXKM96stf7//c9w==
"@types/node@*":
version "13.1.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.1.tgz#6d11a8c2d58405b3db9388ab740106cbfa64c3c9"
@ -1215,12 +1220,12 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
"@types/ramda@^0.26.33":
version "0.26.39"
resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.26.39.tgz#bb392cdb4d431cf6884b3ef11bef635c5d20274f"
integrity sha512-3bu32X02VpjJhsYPUWkdOQGoBXjb/UveZgGg4IYMm+SPAXio96BOYrRhVELfM4AoP00sxoi/f2tqrXdwtR4jjg==
"@types/ramda@^0.26.43":
version "0.26.43"
resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.26.43.tgz#62e235ea17133b8629bc891a26851cf0ea2b4204"
integrity sha512-VK2EaHR/fpeMNPDboGSPAmH+a6HN1pflWqRt67Jii2n8vGpYt6vxIBc1ZdoYrA/jQXRaGGpJKRiSPcHALRD3/A==
dependencies:
ts-toolbelt "^4.12.0"
ts-toolbelt "^6.3.3"
"@types/raven-for-redux@^1.1.1":
version "1.1.1"
@ -11030,10 +11035,10 @@ trough@^1.0.0:
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.4.tgz#3b52b1f13924f460c3fbfd0df69b587dbcbc762e"
integrity sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q==
ts-toolbelt@^4.12.0:
version "4.14.6"
resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-4.14.6.tgz#9a232f62276caeee4fa9e81e0c4bffa047de0765"
integrity sha512-SONcnRd93+LuYGfn/CZg5A5qhCODohZslAVZKHHu5bnwUxoXLqd2k2VIdwRUXYfKnY+UCeNbI2pTPz+Dno6Mpg==
ts-toolbelt@^6.3.3:
version "6.3.5"
resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.3.5.tgz#5cb4e0454ab954faa9b6e4d5bce366fdb262e364"
integrity sha512-Xvh/gvBBCRU1qGeholaN8kgiwBH4neyun6VIDDsJf/jNwz4PXyR8ZY/5qdpB1DuMBrWMG2oTT1oWcOzGPOnluQ==
tslib@^1.9.0:
version "1.10.0"