secret-stack-lifecycle
v0.2.0
Published
provides a lifecycle to secret-stack using readyables
Downloads
8
Maintainers
Readme
secret-stack-lifecycle
provides a lifecycle to secret-stack using readyables
Installation
npm install secret-stack-lifecycle
Usage
While secret-stack does provide a way to create and close an application, it doesn't provide a lifecycle for plugins to organize around. It has become convention for plugins to use setImmediate()
to wait for other plugins to be defined, for example when one plugin relies on the functionality of another during startup; it's also typical to use app.close.hook()
to perform teardown tasks. The result is that the plugin registration ordering takes on a major significance, and each plugin re-invents tracking state around setup and teardown.
The purpose of secret-stack-lifecycle is to provide an application lifecycle and a means for plugins to flexibly organize around each other. It manages all the state that comes along with that, and deemphasizes the importance of plugin registration order.
The lifecycle consists of listening
, ready
, closing
, and close
. These always occur in order, barring errors that may occur along the way, which stops the lifecycle. Each lifecycle step takes the form of a readyable, which allows it to be extended and branched off into plugin-defined lifecycle steps that may happen during any point in the application's lifeycle. Plugins can become very granular with this— for example, a specific function provided by a plugin may have its own setup and teardown, and other plugins can be aware of this and organize around it. One upside is that it becomes safe to use a function provided by a plugin before the entire application is ready. Asynchronous functions provided by plugins can even be called before they are ready, and they will wait to become ready before running.
There's more than that, though! Please check out the example and API documentation below to get started. The library itself is also relatively short, and the tests are comprehensive in showing many different situations and usages.
Example
'use strict'
const SecretStack = require('secret-stack')
const { withLifecycle } = require('secret-stack-lifecycle')
const mathPlugin = withLifecycle({
name: 'math',
init (app) {
const { during, ready, handle, fn } = app.lifecycle
const addReady = during(ready)
let baseline = NaN
handle(addReady, (cb) => {
setTimeout(() => {
baseline = 143 // An arbitrary number for example purposes
cb()
}, 300)
})
return {
// Other plugins can use the app.math.addReady readyable
// to check if this function is ready to be used, and a
// means to wait until it is ready.
add: fn(addReady, (x) => {
return baseline + x
})
}
}
})
const create = SecretStack().use(mathPlugin)
const app = create()
const { run } = app.lifecycle
try {
app.math.add(1) // This would throw here, as the function is not ready yet
} catch {}
run(app.lifecycle.ready, (err) => {
if (err) throw err
// app.math.add() is guaranteed to be ready here
const result = app.math.add(1) // 143 + 1 = 144
})
API
lifecycle(app)
Given a secret-stack application app
, returns a lifecycle object. This may be called on the same app
many times, and you'll always receive the exact same lifecycle object. Note that this does not decorate app.lifecycle
onto app
, although withLifecycle()
does.
withLifecycle(plugin)
Given a secret-stack plugin
, ensures that the plugin's init(app, options)
is always passed an application with the app.lifecycle
lifecycle object defined. Additionally, this ensures that each function provided by the plugin that has been wrapped using fn()
or asyncFn()
has a corresponding readyable (name suffixed with -Ready
) for other plugins to interoperate with. For example:
const mathPlugin = withLifecycle({
name: 'math',
init (app) {
const { fn, listening } = app.lifecycle
return {
// Other plugins can use the app.math.addReady readyable
// to check if this function is ready to be used, and a
// means to wait until it is ready.
add: fn(listening, (a, b) => {
// This function will throw if called before the app is listening.
return a + b
})
}
}
})
app.lifecycle
A lifecycle object for a secret-stack application app
provided using withLifecycle(plugin)
or by lifecycle(app)
. This object contains the following:
listening
A readyable that begins before setup and relies on app
's 'multiserver:listening'
event to fire to complete.
ready
A readyable that begins after setup and depends on listening
to complete.
closing
A readyable that begins once app.close()
is called and depends on app.close()
to finish to complete.
closed
A readyable that begins once app.close()
is finished and depends on closing
to complete.
run(readyable, cb)
Runs the readyable
until it ends, then calls cb
. You may also pass a stream or array of streams for readyable
, and they will be rolled into a single readyable which waits for all of them to complete.
status()
Returns one of several values indicating the status of the app's lifecycle:
'uninitialized'
- before setup, i.e. during plugin registration.'initializing'
- during setup.'initialized'
- setup has completed.'ready'
- theready
lifecycle step has completed and the application is actively listening.'closing'
-app.close()
was called and is in progress.'closed'
-app.close()
and theclosed
lifecycle step have completed.'failed'
- an error occurred at any point during the processing of the application lifecycle.
setup(cb)
This may be called during plugin registration to schedule cb()
to run during app initialization. Initialization of all plugins will occur synchronized with each other using a single setImmediate()
. During this time the implementation of cb
may reference functions, etc. provided by other plugins registered to app
. It is also safe to call dependOn()
during setup, so you can reference readyables exposed by other plugins.
fn(readyable, func)
Returns a new function wrapping func
that will throw if it is called before readyable
is ready. You may also pass a stream or array of streams for readyable
, and they will be rolled into a single readyable which waits for all of them to complete.
asyncFn(readyable, func)
Returns a new function wrapping callback-receiving func
that will wait for readyable
to become ready before continuing processing. You may also pass a stream or array of streams for readyable
, and they will be rolled into a single readyable which waits for all of them to complete.
during(readyable, [{ dependOn }])
Returns a new readyable that is automatically started when readyable
begins. When dependOn
is true
, which is the default, this new readyable is also marked as a dependency of readyable
. See also the pull-readyable docs.
dependOn(readyable, dependency)
Sets readyable dependency
as a dependency of readyable
(i.e. a lifecycle step such as ready
): it will not run its handlers or complete until dependency
ends. You may also pass a stream or array of streams and they will be combined. Returns readyable
. See also the pull-readyable docs.
handle(readyable, cb)
Sets callback cb
as a handler of readyable
(i.e. a lifecycle step such as ready
): it will not complete until cb
is called. The cb
callback may be called with an error, which will be propagated to readyable
. Returns readyable
. See also the pull-readyable docs.