Skip to main content

Spectral 2.x Upgrade Guide

Some syntactical changes were made between Spectral 1.x and Spectral 2.x in order to improve the developer experience and catch some common errors at compile time (rather than at runtime). Let's look at those changes, and how to go about upgrading a component from 1.x to 2.x. To see an example of upgrading a component from Spectral 1.x to 2.x, this commit upgrades the "Format Name" example component from the writing custom components 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

Motivation: In Spectral 1.x, each input had a key attribute to uniquely identify it. This caused problems, though, as the value of the input's key had to exactly match the action's perform function's params.keyName in order 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 of 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

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 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 to instead write actions: { myAction, myOtherAction }.

Component Version is Deprecated

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

In Spectral 1.x, all inputs had an any type. So, if a helper function expected (string, number) inputs, this sort 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 is equal to "23" in JavaScript. That's probably not the desirable result.

In Spectral 2.x we changed all inputs to have an unknown type, so you need to explicitly convert inputs into the type you expect. 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 as opposed to "23".

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 });