action-manager
| Install | |
|---|---|
npm install action-manager |
|
| Latest Version: | 0.2.1 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Oct 1, 2026 |
| Links: | npm |
action-manager
Action-Manager is a framework for defining, invoking, organizing, and integrating action handlers.
Visit Action-Manager for more information.
Install
yarn add action-manager
npm install action-manager
Usage
Create an action
const printTime: Action = {
handler: () => console.log(new Date().toISOString())
}
Invoke an action (without context)
await invoke(printTime)
Invoke an action with context and type parameters. The context, all context properties, and all type parameters are optional.
const printTime: Action<void, {
type: string
}, {
timeZone: 'EST'
}> = {
handler: () => console.log(new Date().toISOString())
}
await invoke(printTime,
{
// These are the targets of the action
targets: [],
// These are the action parameters
params: {type: 'log'},
// This is the environment
env: {timeZone: 'EST'}
}
)
Benefits
- Eliminate boilerplate code
- Increase reusability
- Streamline testing
- Access a rich ecosystem of shared libraries
- Extendable framework via plugins
Features
- Fully asynchronous: sync and async handlers are invoked the same way
- Cardinality checking on action targets
- Dynamic labels support, via
asRef/asValueand thepropnameRefproperties - Plugin based lifecycle: three hooks around every invocation
- One dependency: code-extend, which provides the hooks
Declared, but not yet honoured by invoke
The Action interface declares these, and a UI layer may read them, but invoke does not
act on them yet. They are typed so that actions can be written against them today; do not
rely on invoke enforcing them.
confirm: confirmation dialogsonError: error handling policystatus: status reportingusage: usage collectionrequirements
Action registration and lookup by ID, undo/redo, and action chaining are design goals
rather than shipped behaviour. There is no registerAction export.
Creating Actions
The only required property in the Action interface is the handler.
Handlers accept a single optional argument: the context.
const printTime: Action = {
handler: () => console.log(new Date().toISOString())
}
The power of action-manager emerges as you begin to implement the optional properties.
const printTime: Action = {
id: 'print_time',
name: 'Print Time',
description: 'Print the current time, in ISO format, to the console.',
log: true,
handler: () => console.log(new Date().toISOString())
}
The action can now be used to generate user interface items, such as buttons and menu items.
Action Type Parameters
Although it is not required to type the targets, params, and environment, it is foolish not to. 😉
Action<TARGET,PARAMS,ENVIRONMENT>
| Parameter | Information | Default |
|---|---|---|
| TARGET | The action target type | any |
| PARAMS | The action params type | any |
| ENVIRONMENT | The action environment type | any |
These parameter types can be set to a specific type, any, or void.
Invoking Actions
There are advantages to invoking an action instead of directly calling its handler.
Today invoke gives you:
- Unified sync/async: the handler may return a value or a promise, and
invokealways awaits - Cardinality checking: see
ActionCardinality - Easier debugging: set
logordebugon the action - A default environment:
targets,paramsandenvare always present on the context, and your own context properties are passed through untouched - Three lifecycle hooks: see Hooks
Planned, and not yet implemented: undo/redo, confirmation dialogs, action chaining, and error-handling policy.
Action Widgets
There are several types of action widgets. These widgets are implemented using several libraries.
- Buttons
- Menu Items
- Spreadsheets
- External API
Action Context
A context object is provided to the action handler as it's only parameter.
The context is also provided to MaybeRef properties.
The action context triages properties into three buckets.
- Targets
- Parameters
- Environment
Targets
Targets are the objects an action is to be performed on. Because actions indicate the cardinality of the targets they perform on, targets can be used not only for the handler, but for calculating user interface state. For instance, a delete action can work on 1 or more targets, while a compare action can require exactly two targets. In contrast, an open file action would not require any targets.
Parameters
Parameters are properties that indicate on how the action should go about acting on the targets for a particular invocation.
Environment
The environment are common properties that typically remain the same for multiple actions.
Action Registry
Not implemented. The ActionManager interface in types.ts sketches the intended shape
(enumerate actions, look one up by ID, group them), but no registry is exported and invoke
takes an action object, not an ID. Documented here as a design goal so the interface is not
mistaken for a shipped API.
Developer
Utility
asRef
The asRef helper function allows the developer to treat action properties
as if it were a Ref, regardless of how they are implemented. This function also takes into consideration
if the action property returns a function, and if it already returns a Ref.
By only working with Refs, the developer can treat all properties as dynamically computed values.
For example, this example will work in any combination of action.name or action.nameRef being implemented.
<template>
<button>{{asRef(action, 'name')}}</button>
</template>
To use asRef, you must initialize the system with your
framework one time, before calling the utility method.
Init example:
import {ref, unref, computed} from 'vue'
import {init} from 'action-manager'
// Init action-manager with your framework
init({ref, unref, computed})
import {asRef} from 'action-manager'
// Implement name or nameRef
action: Action = {
nameRef: ()=> new Date().toString(),
handler() {
}
}
action2: Action = {
name: 'hello',
handler() {
}
}
// Get the Refs
const name1Ref = asRef(action1, 'name')
const name2Ref = asRef(action2, 'name')
asValue
asValue is the same as asRef, but will return a non-reactive value.
This will call any computed functions and unwrap any refs.
const name = asValue(action, 'name')
asActionContext
invoke, asRef and asValue all accept an ActionContextInput (every property optional)
and normalise it internally. asActionContext performs that same normalisation for you,
returning a full ActionContext with targets, params and env always present. Your own
properties are passed through untouched.
import {asActionContext} from 'action-manager'
const context = asActionContext({params: {message: 'hello'}})
// -> {targets: [], params: {message: 'hello'}, env: {}}
asActionContext(undefined)
// -> {targets: [], params: {}, env: {}}
Use it when you take a loose context from somewhere outside your control (a component prop, an
HTML attribute, a function that may or may not return one), and want to normalise it once
and hold the result, rather than re-normalising at every call. It is also what asRefWithDefault
expects, since that function takes an ActionContext rather than an ActionContextInput.
Hooks
The invoke lifecycle includes several hooks that can be tapped. These are useful for logging or plugin development.
import {hooks} from 'action-manager'
// Called when an action is invoked, but not yet validated
hooks.actionWasInvoked.tap((action, context) => {
})
// Called when an action is validated, but not yet executed
hooks.actionWillExecute.tap((action, context) => {
})
// Called after an action is executed
hooks.actionDidExecute.tap((action, context, retValue) => {
})
TODO
- Make Vue dependency optional
- Create driver for reactivity
- Organize for various UI frameworks
- Add hooks
- Create driver for prompts
- Create driver for errors
- Create driver for logging
Testing
yarn test
Building
yarn build
Refactoring Example
Here is an example of how you might refactor your code to use action-manager.
Your existing code is hardwired to call a function:
function logMessage(message) {
console.log(message)
}
<button onclick="logMessage('hello')">Log A Message</button>
Migrating to an invokable handler
We start by wrapping the function in an action handler, and typing it.
We then call invoke on the action:
const logMessage: Action<void, {
message: string
}> = {
handler(context){
console.log(context.params.message)
}
}
<button onclick="invoke(logMessage, {params: {message:'hello'}})">Log A Message</button>
With this in place, and not really using much more code, we have enabled all sorts of additional functionality.
Adding targets to log
We start with defining an environment that will be sent into every action.
// We now make our action typed, and also allow it to log targets.
const logMessage: Action<any, {
message: string
}> = {
handler(context) {
console.log(context.params)
if (context.targets && context.targets.length > 0) {
console.log('TARGETS:')
for (let target of context.targets) {
console.log(target)
}
}
}
}
Great, now our action can be used to log not just a message, but user selected items as well.
Creating an environment
We can define an environment that will be sent into every action.
const env = {
// We could redirect to a file, use console.warn, do nothing, or whatever
log: (...args: any[]) => console.log(...args),
// A custom formatter for strings
format: (item: any) => {
if (typeof item === 'string') {
return item.toLowerCase()
} else {
return item
}
}
}
// This is a quick way to create a type from an existing object.
// You can create the type explicity if you desire.
type ENV = typeof env
// We now add the ENV type to our action, and use it
const logMessage: Action<any, {
message: string
}, ENV> = {
handler(context) {
context.env.log(context.env.format(context.params))
if (context.targets) {
for (let target of context.targets) {
context.env.log(context.env.format(target))
}
}
}
}
This environment can be used in other projects. It can also be replaced by some mock when testing.
Add UI hints
The extensibility of this design is now showing. We can add even more properties to enable all sorts of additional features.
const logMessage: Action<any, {
message: string
}> = {
id: 'logMessage',
name: 'Log Message',
description: 'Logs a message and/or action targets',
debug: true,
log: true,
confirm: true,
cardinality: 'any',
onError: ['prompt', 'log'],
status: {
disable: true,
message: 'Logging...',
messageRef: {value: 'Logging...'}
},
usage: true,
handler(context) {
// ...
}
}
confirm,onError,statusandusageare declared onActionbutinvokedoes not act on them yet, and there is noregisterActionexport. This example shows the shape an action will take, not behaviour you get today. Because we registered our action, it can now be invoked by 'ID' instead of the object. This will reduce dependencies.
Widgets such as buttons, menu items, and search results can now use the action as meta-data to generate content.
The invoker can check cardinality to ensure the action is compatible with the context.
Our runtime can log and debug information when the action is invoked.
We have instructed the runtime:
- confirm with the user before invoking the action
- to present the user with any errors that occur, and also log them
- show a status bar with a message
- collect usage data
That is the design the Action interface is built for. Of the above, invoke currently
honours log, debug and cardinality; the confirmation, error, status and usage
properties are declared but not yet acted on.
Dynamic Properties
Most action properties support a propnameRef alternative that can dynamically calculate and
reactively update the value: nameRef, name2Ref, iconRef, descriptionRef, styleRef
and isVisibleRef.
Resolve them with asRef or asValue, which handle the static property, the Ref, and the
function-returning-a-Ref forms alike. See asRef above; init must be called once
with your framework's reactivity before either is used.
Related Packages
Migration tool to automate and report on breaking changes across Ionic Framework...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.2.1 | ^3.5.13 | MIT | |
| 0.2.0 | ^3.5.13 | MIT | |
| 0.1.0 | ^3.5.13 | MIT |
Pre-releases (25)
| Version | Released | Vue | License |
|---|---|---|---|
| 0.0.1-beta.25 | |||
| 0.0.1-beta.24 | |||
| 0.0.1-beta.23 | |||
| 0.0.1-beta.22 | |||
| 0.0.1-beta.21 | ^3.4.27 | ||
| 0.0.1-beta.20 | ^3.4.27 | ||
| 0.0.1-beta.19 | ^3.4.27 | ||
| 0.0.1-beta.18 | ^3.4.27 | ||
| 0.0.1-beta.17 | ^3.4.27 | ||
| 0.0.1-beta.16 | ^3.4.27 | ||
| 0.0.1-beta.15 | ^3.4.27 | ||
| 0.0.1-beta.14 | ^3.4.27 | ||
| 0.0.1-beta.13 | ^3.4.27 | ||
| 0.0.1-beta.12 | ^3.4.26 | ||
| 0.0.1-beta.11 | ^3.4.26 | ||
| 0.0.1-beta.10 | ^3.4.26 | ||
| 0.0.1-beta.9 | ^3.4.26 | ||
| 0.0.1-beta.8 | ^3.4.25 | ||
| 0.0.1-beta.7 | |||
| 0.0.1-beta.6 | |||
| 0.0.1-beta.5 | |||
| 0.0.1-beta.4 | |||
| 0.0.1-beta.3 | |||
| 0.0.1-beta.2 | |||
| 0.0.1-beta.1 |