Actionhero is a realtime multi-transport nodejs API Server with integrated cluster capabilities and delayed tasks
APACHE-2.0 License
Bot releases are hidden (Show)
Published by evantahler almost 4 years ago
Published by evantahler almost 4 years ago
Published by evantahler almost 4 years ago
@types/ioredis
is a required dependency. It was improperly moved to a devDependency
in https://github.com/actionhero/actionhero/pull/1672
Published by evantahler almost 4 years ago
Migration Guide: https://www.actionherojs.com/tutorials/upgrade-path
Actionhero can now be run with the ioredis-mock
package. In this way, we can allow users of Actionhero to use every feature of the framework, including cache, chat, and tasks, without a Redis server running locally.
BREAKING CHANGE
This is a breaking change as it removes the enabled
option on config.redis
, as Redis is now always enabled, but possibly mocked. See what a configuration (/src/config/redis.ts
) from using this redis mock looks like on the master branch.
Of course, no data will be persisted or shared between nodes unless you are using a "real" Redis server. The default configuration of Actionhero (when a new project is generated with npx actionhero generate
) is to assume that a real Redis server can be reached at localhost
. However, we make it easy to change by providing a ioredis-mock
-based configuration commented out in config/redis.ts
.
Using ioredis-mock
may also be beneficial in your tests.
This PR greatly simplifies the running and generation of Actionhero CLI commands.
➜ actionhero ./node_modules/.bin/actionhero help
Usage: actionhero [options] [command]
Options:
-V, --version output the version number
-h, --help display help for command
Commands:
actions List the actions defined on this server
console Start an interactive REPL session with the api object in-scope
generate Generate a new Actionhero Project in an empty directory
generate-action [options] Generate a new Action
generate-cli [options] Generate a new cli command
generate-initializer [options] Generate a new Initializer
generate-plugin Generate the structure of a new actionhero plugin in an empty directory
generate-server [options] Generate a new Server
generate-task [options] Generate a new Task
task-enqueue [options] Enqueue a defined Task into your actionhero cluster
help [command] display help for command
➜ actionhero ./node_modules/.bin/actionhero generate-action -h
Usage: actionhero generate-action [options]
Generate a new Action
Options:
--name <name>
--description <description> (default: "an actionhero action")
-h, --help display help for command
Example:
actionhero generate action --name=[name] --description=[description]
commander
to be the main "runner" for our CLI commands
auto-generated
help and version
commandsactionhero
)Downsides:
npm run build
) your commands before using them. You can use the tsc --watch
workflow to speed things upBREAKING CHANGES:
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
, etc.This addresses bug #1509, consistency displaying the error message sent to web clients in the console/logs. In addition, for actions handled by a web server, any thrown errors will result in the response HTTP status code of 500 (indicating a server error) instead of 400 (usually reserved for client errors).
BREAKING CHANGE
This change adds a new option to the web server config: config.servers.web.defaultErrorStatusCode
which is defaulted to 500
. This option is only effective if the status code has not been set by the action.
(Typescript) This PR updates the specHelper
such that you can provide the Action or Task run method type to it so that the response type returned will defend by the Action.
import { Process, specHelper } from "actionhero";
import { RandomNumber } from "../../src/actions/randomNumber";
const RunMethod = RandomNumber.prototype.run;
const actionhero = new Process();
describe("Action", () => {
describe("randomNumber", () => {
beforeAll(async () => {
await actionhero.start();
});
afterAll(async () => {
await actionhero.stop();
});
test("generates random numbers", async () => {
// Now, the response type of `specHelper.runAction` will be known ahead of time!
const { randomNumber } = await specHelper.runAction<typeof RunMethod>(
"randomNumber"
);
expect(randomNumber).toBeGreaterThan(0);
expect(randomNumber).toBeLessThan(1);
});
});
});
BREAKING CHANGES
Replaces the old process.env.ACTIONHERO_TEST_FILE_EXTENSION
with process.env.ACTIONHERO_TYPESCRIPT_MODE
to more declaratively state if we should load typescript files or not. New values are process.env.ACTIONHERO_TYPESCRIPT_MODE="true"
or process.env.ACTIONHERO_TYPESCRIPT_MODE="false"
Published by evantahler almost 4 years ago
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
, etc.Published by evantahler almost 4 years ago
Published by evantahler almost 4 years ago
ACTIONHERO_TEST_FILE_EXTENSION
with ACTIONHERO_TYPESCRIPT_MODE
(boolean) #1668
process.env.ACTIONHERO_TEST_FILE_EXTENSION
with process.env.ACTIONHERO_TYPESCRIPT_MODE
to more declaratively state if we should load typescript files or not.Published by evantahler almost 4 years ago
throws
is now 500
, configured by a new config setting, config.servers.web.defaultErrorStatusCode
(default 500). This option is only effective if the status code has not been set by the action.Published by evantahler almost 4 years ago
Pre-Release: v25.0.0-alpha.1
config.redis.enabled
, as Redis is now always enabled, but possibly mocked. Update your config/redis.ts
to the version on master to see the new configuration options to use ioredis-mock
Install pre-releases with npm install actionhero@next
Published by evantahler almost 4 years ago
Published by evantahler almost 4 years ago
Adds an option to task.enqueueAt()
and task.enqueueIn()
to suppress errors if there is a duplicate task at the same time with the same arguments. We use this new setting when (re)enqueueing periodic tasks.
/**
* Enqueue a task to be performed in the background, at a certain time in the future.
* Will throw an error if redis cannot be reached.
*
* Inputs:
* * taskName: The name of the task.
* * inputs: inputs to pass to the task.
* * queue: (Optional) Which queue/priority to run this instance of the task on.
* * suppressDuplicateTaskError: (optional) Suppress errors when the same task with the same arguments are double-enqueued for the same time
*/
export async function enqueueAt(
timestamp: number,
taskName: string,
inputs: TaskInputs,
queue: string = api.tasks.tasks[taskName].queue,
suppressDuplicateTaskError = false
) {
await validateInput(taskName, inputs);
return api.resque.queue.enqueueAt(
timestamp,
queue,
taskName,
[inputs],
suppressDuplicateTaskError
);
}
/**
* Enqueue a task to be performed in the background, at a certain number of ms from now.
* Will throw an error if redis cannot be reached.
*
* Inputs:
* * timestamp: At what time the task is able to be run. Does not guarantee that the task will be run at this time. (in ms)
* * taskName: The name of the task.
* * inputs: inputs to pass to the task.
* * queue: (Optional) Which queue/priority to run this instance of the task on.
* * suppressDuplicateTaskError: (optional) Suppress errors when the same task with the same arguments are double-enqueued for the same time
*/
export async function enqueueIn(
time: number,
taskName: string,
inputs: TaskInputs,
queue: string = api.tasks.tasks[taskName].queue,
suppressDuplicateTaskError = false
) {
await validateInput(taskName, inputs);
return api.resque.queue.enqueueIn(
time,
queue,
taskName,
[inputs],
suppressDuplicateTaskError
);
}
Merges in the changes from Node Resque:
Published by evantahler almost 4 years ago
Published by evantahler almost 4 years ago
end
command when disconnecting from redis #1635With ioredis
"quit" will try to reconnect now. We want to "end" the connections when shutting down.
If you are seeing timeouts when completing your jest tests or errors like Jest did not exit one second after the test run has completed.
this may be the culprit
This PR introduces action.run()
which allows Actions to be run arbitrarily from elsewhere in the codebase. With this, you can have one Action call another, have a Task run Action, etc. Middleware for the the Action will be run. action.run()
creates a new proxy connection with only the arguments you provide to the method.
import { action } from "actionhero"
const nameOfAction = 'myAction'
const actionVersion = 'v1' // or leave null to use the latest version
const params = {key: 'value'} // the params which would be parsed from the client
const connectionProperties = {} // special properties on the connection which may be expected by the action or middleware. Perhaps "session.id" or "authenticated = true" depending on your middleware
const response = await action.run(nameOfAction, actionVersion, params, connectionProperties);
So, if you wanted an Action which combines the responses of 2 other Actions:
import { Action, action } from "actionhero";
export class RecursiveAction extends Action {
constructor() {
super();
this.name = "recursiveAction";
this.description = "I am an action that runs 2 other actions";
this.outputExample = {};
}
async run() {
const localResponse = { local: true };
const firstActionResponse = await action.run("otherAction");
const secondActionResponse = await action.run("anotherAction");
return Object.assign(firstActionResponse, secondActionResponse, localResponse);
}
}
Future work:
action.run()
according the run method's return types.Published by evantahler about 4 years ago
A new major release with lots of new features!
This new version of node-resque did a lot of work to make your tasks more idempotent. It's now harder to loose jobs if your application crashes. Be sure to set config.tasks.retryStuckJobs
(boolean) to enable the automatic-retrying of stuck tasks by the resque scheduler.
From https://github.com/actionhero/node-resque/pull/453
This PR moves to use a Lua script to atomically (within the redis srerver) both pop the job from the list and write it to the Worker's metadata.
Due to the sematics of how ioredis loads and uses lua commands, this is a breaking change as this may break users who do not use the ioredis package
Redis version >= 2.6.0 is now required
Per the discussion in https://github.com/actionhero/actionhero/discussions/1579, Actionhero is removing the options config.servers.web.simpleRouting
and config.servers.web.queryRouting
. These options were confusing, and generally conflicted with the config/routes.ts
file. We want to be clear to new users that the "right" way to define the routes for your Actionhero APIs is in /config/routes.ts
.
That said, there are times when you may need to quickly build routes automatically from your actions, like when testing or deploying this core Actionhero project directly 😁. For those cases, this PR adds a new option, config.servers.web.automaticRoutes
. This new option is similar-ish to the old automaticRoutes
option, but differs in a few key ways:
config.servers.web.automaticRoutes = ['get']
config.servers.web.automaticRoutes = ['get', 'post', 'head']
For newly generated Actionhero Projects, we will start with your config/routes.ts
file including a get
entry for the Status, Swagger, and CreateChatRoom actions, so the examples continue to work
The preferred way to describe an Action's run method is now by returning an object which you want to send to your consumers. By doing this, you are building up a Typescript type which can then be used in your API to type-check API responses!
For example:
// src/actions/randomNumber.ts
import { Action } from "actionhero";
export class RandomNumber extends Action {
constructor() {
super();
this.name = "randomNumber";
this.description = "I am an API method which will generate a random number";
this.outputExample = { randomNumber: 0.123 };
}
async run({ connection }) {
const randomNumber = Math.random();
const stringRandomNumber: string = connection.localize([
"Your random number is {{randomNumber}}",
{ randomNumber },
]);
return { randomNumber, stringRandomNumber };
}
}
Now, you can load in the action and inspect the run method's return type:
const { RandomNumber } = await import("../../src/actions/randomNumber");
type ResponseType = UnwrapPromise<typeof RandomNumber.prototype.run>;
// now that we know the types, we can enforce that new objects match the type
// this is OK!
const responsePayload: ResponseType = {
randomNumber: 1,
stringRandomNumber: "some string",
};
// this fails compilation (missing stringRandomNumber):
const responsePayload: ResponseType = {
randomNumber: 1,
};
The type information is also available to your IDE
Setting data.response
in the action is still possible, but now it will be discouraged by the class Action, which expects the run method to return an object or null.
Actionhero now includes the helper types <UnwrapPromise>
and <AssertEqualType>
for these types of use cases.
cache.keys
can be provided an optionalScopePrefix (#1612)require
with await import
where possible (#1614)New Config Options which should be added:
config.servers.web.automaticRoutes
= []config.tasks.retryStuckJobs
= falseConfig Options which should be removed:
config.servers.web.simpleRouting
(spiritually replaced with config.servers.web.automaticRoutes
)config.servers.web.queryRouting
(depreciated)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.
Published by evantahler about 4 years ago
Support retryStuckJobs option for Resque Scheduler (#1605)
Published by evantahler about 4 years ago
Published by evantahler about 4 years ago
Published by evantahler about 4 years ago
Published by evantahler about 4 years ago