Upgrading big Actionhero projects to a new major might require some effort. Every Actionhero version has it's own specific project files which you generate using Actionhero generate
command.
One of the ways to upgrade your project is to generate a new project using the latest Actionhero framework (npx Actionhero generate
). Using that as your starting point you can then carefully copy all your configs
, initializers
, servers
, tasks
, actions
, and other custom code from your old project, making sure that you are at the same working state as before. It's a good practice to make tests for your actions (or any other component) before you plan to upgrade your Actionhero project.
Actionhero follows semantic versioning. This means that a minor change is a right-most number. A new feature added is the middle number, and a breaking change is the left number. You should expect something in your application to need to be changed if you upgrade a major version.
For most users, this breaking change will not be noticeable. In https://github.com/actionhero/actionhero/pull/2541, Actionhero changed the internal representation fo cache
objects to remove readAt
and expireTimestamp
. We now rely on redis itself for object expiration. Error responses when objects have expired will be changed as well.
Strongly Typed Config
The biggest change in Actionhero v28 is that Actionhero's codebase now has no implicit any
Typescript types. This means that all of Actionhero is strongly Typed! The largest change was how we now type ./config
files so that they can extend the type of the global config object using modules.
To upgrade from v27 to v28 the fastest way forward may be to delete your ./src/config
directory and re-initialize your actionhero project with npx actionhero generate .
, and then re-apply your changes. Otherwise, you should copy and paste the config files from Actionhero's source keeping the following in mind:
..
with "actionhero"
./src/config/servers/web.ts
and ./src/config/servers/websocket.ts
have been moved to ./src/config/web.ts
and ./src/config/websocket.ts
respectively to match their new namespace config locations. There is no more config.servers.web
- it's config.web
now.An example of the required changes can be seen here.
Spec Helper Type changes
It's now much easier to get the types of your response from specHelper.runAction<Action>()
and specHelper.runTask<Task>()
!
Just provide your Action or Task Class!
// In `__tests__/actions/randomNumber.ts`
import { Process, specHelper } from "actionhero";
import { RandomNumber } from "../../src/actions/randomNumber";
describe("Action: randomNumber", () => {
const actionhero = new Process();
beforeAll(async () => await actionhero.start());
afterAll(async () => await actionhero.stop());
test("generates random numbers", async () => {
// now "randomNumber" is typed properly as a number
const { randomNumber } = await specHelper.runAction<RandomNumber>(
"randomNumber"
);
expect(randomNumber).toBeGreaterThan(0);
expect(randomNumber).toBeLessThan(1);
});
});
Version 27 also removed i18n
and uglify
from Actionhero
Localization Removal
/locales/*
files you have, and move that text content into your Actions and Tasksconnection.localize()
in your code - this method is removedConfiguration
src/config/api.ts
:config.general.welcomeMessage = 'Welcome to the Actionhero API!'
or similar messageconfig.general.paths.locale
src/config/errors.ts
:data.connection.localize
and use regular JS stringsMinified Websocket Client Library Removed
ActionheroWebsocketClient.min.js
will no longer be generated in your Actionhero projects. Most users include /public/javascript/ActionheroWebsocketClient.js
in their build and it is compiled into their react or angular project... or cached and minified by their CDN. Minifiying this client-side javascript is now outside of the scope of Actionhero.
There are no major changes in this version's code, but as of v26, Actionhero requires Node.js v12+ Support for Node.js v10 has been dropped.
Configuration
ioredis-mock
instead. See what a configuration from using this redis mock looks like on the main branch. See PR #1653 for more information.Logging and HTTP Server
config.servers.web.defaultErrorStatusCode
(default 500). This option is only effective if the status code has not been set by the action. See PR #1661 for more information.CLI Commands
initialize
and start
options, so you can opt-into initializing or starting your server as needed for your CLI command. The default is to initialize (initialize=true
) but not start (start=false
)-
. IE: actionhero generate action
is now actionhero generate-action
.New Config Options which should be added:
config.servers.web.automaticRoutes
= []config.tasks.retryStuckJobs
= falseConfig Options which need to be removed:
config.servers.web.simpleRouting
(spiritually replaced with config.servers.web.automaticRoutes
)config.servers.web.queryRouting
(removed)And if you want to use the new Typescript features, change your Actions
to return the response you want to send rather than using data.response
. data.response
will be removed in a future version of Actionhero.
This release is breaking for 2 reasons:
Documentation
initializer and related showDocumentation
action has been removed in favor of the new swagger action & middleware Pull Request.If you use automated log ingestion (i.e.: Splunk or a Winston logger) this PR should be helpful, as the error and stack trace will now all be on the same line... but you will need to update your log tools.
If you had been using the Documentation
initializer, you can re-build it yourself from api.actions.actions
. If you want upgrade your Actionhero project to use the new Swagger documentation tooling, you need to copy in 2 files:
There are also new logging options to add to src/config/api.ts
:
config.general.enableResponseLogging
(bool) toggles this option on (off by default)config.general. filteredResponse
(string[]) allow you to filter out certain parts of the response payload from the logs, hiding sensitive data.Assuming that you have already migrated your project to Typescript, the only change is create your server.ts
and change your package.json
scripts to use it. Support for boot.js
has also been removed, and you should move that logic into your new server.ts
// in ./src/server.ts
import { Process } from "actionhero";
// load any custom code, configure the env, as needed
async function main() {
// create a new actionhero process
const app = new Process();
// handle unix signals and uncaught exceptions & rejections
app.registerProcessSignals();
// start the app!
// you can pass custom configuration to the process as needed
await app.start();
}
main();
Your package json should now contain:
"scripts": {
"postinstall": "npm run build",
"dev": "ts-node-dev --no-deps --transpile-only ./src/server.ts",
"start": "node ./dist/server.js",
"test": "jest",
"pretest": "npm run build && npm run lint",
"build": "tsc --declaration",
"lint": "prettier --check src/*/** __test__/*/**",
"pretty": "prettier --write src/*/** __test__/*/**"
},
Also be sure that your packate.json
contains the @types/ioreids
devDependency. You can install it with npm install --save-dev @types/ioredis
Tasks can now use input validation.
Full Release Notes: GitHub
The recommended upgrade to v21 of Actionhero is to move your project to Typescript. Detailed notes can be found on the Typescript Tutorial.
Full Release Notes: GitHub
Full Release Notes: GitHub
The only change to take note of is that you must now ensure that the working directory (CWD/PWD) in use when you start your Actionhero project is the root. (where /config, /actions, etc) are visible.
Full Release Notes: GitHub
Configuration
config/tasks.js
add config.tasks.stuckWorkerTimeout = 3600000
. This will be a 1 hour timeout for stuck/crashed worker processesconfig/servers/websocket.js
add config.servers.websocket.client.cookieKey = config.servers.web.fingerprintOptions.cookieKey
. This will instruct the Actionhero Websocket Clients to share the same cookie as the web server to share a fingerprint, which can be used to share session information.process.env.JEST_WORKER_ID
. Please view config/api.ts
, config/redis.ts
, config/servers/websocket.ts
, and config/servers/web.ts
for more informationFull Release Notes: GitHub
config/api.js
add config.general.paths.test = [path.join(__dirname, '/../__tests__')]
so that when you generate a new action or task, the related test file can be generated as well.cd
into the root directory of the project before starting the server.Full Release Notes: GitHub
config/tasks.js
add config.tasks.stuckWorkerTimeout = 3600000
. This will be a 1 hour timeout for stuck/crashed worker processesconfig/servers/websocket.js
add config.servers.websocket.client.cookieKey = config.servers.web.fingerprintOptions.cookieKey
. This will instruct the Actionhero Websocket Clients to share the same cookie as the web server to share a fingerprint, which can be used to share session information.process.env.JEST_WORKER_ID
. Please view config/api.ts
, config/redis.ts
, config/servers/websocket.ts
, and config/servers/web.ts
for more informationFull Release Notes: GitHub
Breaking Changes and How to Overcome Them:
There are many changes to the APIs Actionhero exposes. You can read up on the new syntax on our new documentation website, docs.actionherojs.com
Node.js version
Actions
require('Actionhero').Action
.run
method only has one argument now, data
and becomes a async
method. api
can be required globally to your file.const { Action, api } = require("actionhero");
module.exports = class MyAction extends Action {
constructor() {
super();
this.name = "randomNumber";
this.description = "I am an API method which will generate a random number";
this.outputExample = { randomNumber: 0.1234 };
}
async run(data) {
data.response.randomNumber = Math.random();
}
};
require('Actionhero').Task
.run
method only has one argument now, data
and becomes a async
method. api
can be required globally to your file.const { api, Task } = require("actionhero");
module.exports = class SendWelcomeMessage extends Task {
constructor() {
super();
this.name = "SendWelcomeEmail";
this.description = "I send the welcome email to new users";
this.frequency = 0;
this.queue = "high";
this.middleware = [];
}
async run(data) {
await api.sendWelcomeEmail({ address: data.email });
return true;
}
};
require('actionhero').Initializer
.initialize
, start
, and stop
methods now have no arguments and become a async
methods. api
can be required globally to your file.const { Actionhero, api } = require("actionhero");
module.exports = class StuffInit extends Actionhero.Initializer {
constructor() {
super();
this.name = "StuffInit";
this.loadPriority = 1000;
this.startPriority = 1000;
this.stopPriority = 1000;
}
async initialize() {
api.StuffInit = {};
api.StuffInit.doAThing = async () => {};
api.StuffInit.stopStuff = async () => {};
api.log("I initialized", "debug", this.name);
}
async start() {
await api.StuffInit.startStuff();
api.log("I started", "debug", this.name);
}
async stop() {
await api.StuffInit.stopStuff();
api.log("I stopped", "debug", this.name);
}
};
require('Actionhero').Server
.initialize
, start
, and stop
methods now have no arguments and become a async
methods. api
can be required globally to your file.const Actionhero = require("Actionhero");
module.exports = class MyServer extends Actionhero.Server {
constructor() {
super();
this.type = "%%name%%";
this.attributes = {
canChat: false,
logConnections: true,
logExits: true,
sendWelcomeMessage: false,
verbs: [],
};
// this.config will be set to equal config.servers[this.type]
}
initialize() {
this.on("connection", (connection) => {});
this.on("actionComplete", (data) => {});
}
start() {
// this.buildConnection (data)
// this.processAction (connection)
// this.processFile (connection)
}
stop() {}
sendMessage(connection, message, messageId) {}
sendFile(connection, error, fileStream, mime, length, lastModified) {}
goodbye(connection) {}
};
require('Actionhero').CLI
.run
method now has one argument, data
and becomes a async
method. api
can be required globally to your file.const {api, CLI} = require('Actionhero')
module.exports = class RedisKeys extends CLI {
constructor () {
super()
this.name = 'redis keys'
this.description = 'I list all the keys in redis'
this.example = 'Actionhero keys --prefix Actionhero'
}
inputs () {
return {
prefix: {
required: true,
default: 'Actionhero',
note: 'the redis prefix for searching keys'
}
}
}
async run ({params}) => {
let keys = await api.redis.clients.client.keys(params.prefix)
api.log('Found ' + keys.length + 'keys:')
keys.forEach((k) => { api.log(k) })
}
}
Cache
async
methods which, when await
ed, return a result and throw
errorsTasks
async
methods which, when await
ed, return a result and throw
errorsChat
async
methods which, when await
ed, return a result and throw
errorsSpecHelper
async
methods which, when await
ed, return a result and throw
errorsconst chai = require("chai");
const dirtyChai = require("dirty-chai");
const expect = chai.expect;
chai.use(dirtyChai);
const path = require("path");
const Actionhero = require("Actionhero");
const Actionhero = new Actionhero.Process();
let api;
describe("Action: RandomNumber", () => {
before(async () => {
api = await Actionhero.start();
});
after(async () => {
await Actionhero.stop();
});
let firstNumber = null;
it("generates random numbers", async () => {
let { randomNumber } = await api.specHelper.runAction("randomNumber");
expect(randomNumber).to.be.at.least(0);
expect(randomNumber).to.be.at.most(1);
firstNumber = randomNumber;
});
it("is unique / random", async () => {
let { randomNumber } = await api.specHelper.runAction("randomNumber");
expect(randomNumber).to.be.at.least(0);
expect(randomNumber).to.be.at.most(1);
expect(randomNumber).not.to.equal(firstNumber);
});
});
Utils
api.utils.recursiveDirectoryGlob
has been removed in favor of the glob package. Use this instead.async
methods which, when await
ed, return a result and throw
errorsPlugins
plugins
directory in your actions, tasks, config, or public folders, delete them../config/plugins.js
config file. You should create one per the exampleActionhero link
and Actionhero unlink
per the above.Actionhero generate plugin
, a helper which you can use in an empty directory which will create a template plugin projectClients
ActionheroClient
(the included client library for browser websocket clients) as been named a more clear ActionheroWebsocketClient
to avoid ambiguity.Actionhero-node-client
to help clear up any confusion.Full Release Notes: GitHub
Breaking Changes and How to Overcome Them:
Localization (i18n)
In ./config/i18n.js
be sure to enable objectNotation
, or else the new locale file will be gibberish to Actionhero
As of this release, Actionhero no longer localizes its log messages. This is done to simplify and speed up the logger methods. There is not mitigation path here without overwriting the api.log()
method.
Any use of %
interpolation should be removed from your logger strings. Favor native JS string templates.
Actionhero now ships with locale files by default.
You will need to acquire the default locale file and copy it into ./locales/en.json
within your project.
The error reporters have all been changed to use these new locale file and mustache-style syntax. Update your from the default errors file
The welcomeMessage
and goodbyeMessage
are removed from the config files and Actionhero now references the locale files for these strings. Update yours accordingly.
utils
api.utils.recursiveDirectoryGlob
has been removed in favor of the glob package. Use this instead.
Full Release Notes: GitHub
Breaking Changes and How to Overcome Them:
The only breaking changes are related to the capilization of internal methods:
api.Connection()
rather than api.connection()
api.GenericServer()
rather than api.genericServer()
api.ActionProcessor()
rather than api.actionProcessor()
require('Actionhero')
not require('Actionhero').actionheroPrototype
should you be using Actionhero programmatically.Full Release Notes: GitHub
Breaking Changes and How to Overcome Them:
\`Actionhero generateAction --name=[name]\` -> \`Actionhero generate action --name=[name]\`
\`Actionhero generateInitializer --name=[name]\` -> \`Actionhero generate initializer --name=[name]\`
\`Actionhero generateServer --name=[name]\` -> \`Actionhero generate server --name=[name]\`
\`Actionhero generateTask --name=[name]\` -> \`Actionhero generate task --name=[name]\`
Full Release Notes: GitHub
Breaking Changes and How to Overcome Them:
config/redis.js
is to take if from the main branch directly and re-apply your configuration.config.redis.channel
to config.general.channel
config.redis. rpcTimeout
to config.general.rpcTimeout
config.redis.client
rather than api.redis.client
Full Release Notes: GitHub
Breaking Changes and How to Overcome Them:
config/plugins.js
is removed. Delete yours.Actionhero link --name=NameOfPlugin
to link your plugins in the new method.config/i18n.js
is to take if from the main branch.config.i18n.updateFiles
is true
so that your locale files can be generated for the first time.config/errors.js
has been completely redone to take advantage of connection.localize
. The easiest way to upgrade your config/errors.js
is to take if from the main branch.package
is a reserved keyword in JavaScript. We now use the key pkg
in the redis config.Full Release Notes: GitHub
Breaking Changes and How to Overcome Them:
redis
npm package to ioredis
. Change this in your package.json.ioredis
handles passwords slightly differently. Read the ioredis documentation to learn more.api.stats
subsection has been removed from ActionheroFull Release Notes: GitHub
Breaking Changes and How to Overcome Them:
run: function(api, data, next){ data.response.randomNumber = Math.random(); next(error); }
data = { connection: connection, action: 'randomNumber', toProcess: true, toRender: true, messageId: 123, params: { action: 'randomNumber', apiVersion: 1 }, actionStartTime: 123, response: {}, }
data.connection
rather than connection
directly.data.response
rather than connection.response
directly.data
pattern. You will need to change your middleware accordingly.connection._originalConnection
.connection.params = {}
. If you rely on the old behavior, you will need to change your client code.The Actionhero server is open source, under the Apache-2 license
Actionhero runs on Linux, OS X, and Windows
You always have access to the Actionhero team via Slack and Github
We provide support for corporate & nonprofit customers starting at a flat rate of $200/hr. Our services include:
We have packages appropriate for all company sizes. Contact us to learn more.
We provide support for corporate & nonprofit customers starting at a flat rate of $200/hr. Our services include:
We have packages appropriate for all company sizes. Contact us to learn more.
For larger customers in need of a support contract, we offer an enterprise plan including everything in the Premium plan plus: