# Spectral 2.x Upgrade Guide

Several syntactical changes were made between Spectral 1.x and Spectral 2.x to improve the developer experience and catch common errors at compile time rather than at runtime. Let's examine those changes and the upgrade process from version 1.x to 2.x. To see an example of upgrading a component from Spectral 1.x to 2.x, [this commit](https://github.com/prismatic-io/examples/commit/411598bac4163c8a25b492a9275590b3fd7855a9) upgrades the "Format Name" example component from the [writing custom components](https://prismatic.io/docs/custom-connectors.md) article.

To start, update `@prismatic-io/spectral` in your `package.json` file and then run `npm install` or `yarn install`:

```
{
  "dependencies": {
    "@prismatic-io/spectral": "^2.0.0"
  }
}
```

## Input keys have moved[​](#input-keys-have-moved "Direct link to Input keys have moved")

**Motivation**: In Spectral 1.x, each `input` had a `key` attribute to uniquely identify it. This caused problems, however, as the value of the `input`'s `key` had to exactly match the `action`'s perform function's `params.keyName` to reference inputs correctly. Mismatched input keys and parameter keys would yield `unknown` values for inputs. TypeScript generics were used in 2.x to ensure that your editor and compiler catch mismatched keys prior to deployment.

First, remove the `key:` property from each input:

```
const myFirstInput = input({
  /* key: "first", */ // Removed in spectral 2.x
  label: "My Input Field",
  placeholder: "Some example input",
  type: "string",
  required: true,
});
const mySecondInput = input({
  /* key: "second", */
  label: "My Input Field",
  placeholder: "Some example input",
  type: "string",
  required: true,
});
```

Inputs on actions are now an **object** (key-value pairs) instead of an **array**. Adjust your actions accordingly:

```
/* Spectral 1.x */
const myAction = action({
  perform: async (context, { first, second }) => {
    /*...*/
  },
  inputs: [myFirstInput, mySecondInput],
});

/* Spectral 2.x */
const myAction = action({
  perform: async (context, { first, second }) => {
    /*...*/
  },
  inputs: {
    first: myFirstInput,
    second: mySecondInput,
  },
});
```

Ensure that your destructured `params` parameter (in this case, `{ first, second }`) has keys that match the keys you provide in the `inputs` object. If they do not match, your compiler will throw an error.

## Action keys have moved[​](#action-keys-have-moved "Direct link to Action keys have moved")

Similar to inputs, action keys have also moved. This addresses an issue in Spectral 1.x where if an action's `key` property did not match the name of the variable representing the `action`, a component would compile but the component's action code would not be found at runtime.

Action's unique keys are now declared as part of the `component` declaration, and the `key` property has been removed from the `action` declaration:

```
/* Spectral 1.x */
const myAction = action({
  key: "myAction", // Remove this
  /*...*/
});
export default component({
  /*...*/
  actions: {
    // Remove the spread operators
    ...myAction,
    ...myOtherAction,
  },
});

/* Spectral 2.x */
const myAction = action({
  /*...*/
});

export default component({
  /*...*/
  actions: {
    myAction: myAction,
    myOtherAction: myOtherAction,
  },
});
```

Note: you can use JavaScript [shorthand property names](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer) to write `actions: { myAction, myOtherAction }` instead.

## Component version is deprecated[​](#component-version-is-deprecated "Direct link to Component version is deprecated")

[Component versioning](https://prismatic.io/docs/custom-connectors/publishing.md#component-versioning) is handled by the platform, so there's no need to pass in a `version` property in your `component` declaration. You can remove it.

```
export default component({
  key: "my-component",
  /* version: "123", */
});
```

## Input types are more strict[​](#input-types-are-more-strict "Direct link to Input types are more strict")

In Spectral 1.x, all inputs had an `any` type. So, if a helper function expected `(string, number)` inputs, this type of invocation would be acceptable by TypeScript with Spectral 1.x:

```
const helper = (foo: string, bar: number) =>
  `Hi, ${foo}, adding 3 gives you ${bar + 3}`;

const myAction = action({
  perform: async (context, { myInput1, myInput2 }) => {
    helper(myInput1, myInput2);
  },
});
```

This caused runtime issues. What if a component's user entered the string `"2"` for `myInput2`? That would cause problems, since `"2" + 3` equals `"23"` in JavaScript. That's probably not the desired result.

In Spectral 2.x, we changed all inputs to have an `unknown` type, so you need to explicitly convert inputs into the type expected by your helper functions and third-party libraries. We created a series of utility functions to help convert inputs to types your helper functions and third-party libraries require. The above code could be written using `util.types` functions:

```
const helper = (foo: string, bar: number) =>
  `Hi, ${foo}, adding 3 gives you ${bar + 3}`;

const myAction = action({
  perform: async (context, { myInput1, myInput2 }) => {
    helper(util.types.toString(myInput1), util.types.toNumber(myInput2));
  },
});
```

If a string `"2"` is input into `myInput2` using Spectral 2.x now, that string is converted to a number and then passed into `helper`, and the result would be `5` instead of `"23"`.

## Unit testing[​](#unit-testing "Direct link to Unit testing")

In Spectral 2.x, it is assumed that you want a `PerformDataReturn` object when you run an `invoke` function in a unit test. Passing the return object type to the invoke function is no longer required.

```
/* Spectral 1.x */
await invoke<PerformDataReturn>(myAction, { someInput });

/* Spectral 2.x */
await invoke(myAction, { someInput });
```
