metalman
v3.0.2
Published
Composes configurable methods which are based on middlewares.
Downloads
3,804
Maintainers
Readme
metalman
A recursion-free middleware framework for composable methods with native promise support. Middlewares are factory functions that should return a command handler function.
Middlewares receive the command config which can be used for initialization and to register command handlers if they like to.
A parameter passed during command invocation runs through all the middlewares until a middleware stops execution using an error.
Returning undefined
in a middleware handler means it should continue with the execution of the next middleware without doing any changes on the input parameter.
Example method
const metalman = require('metalman')
// Load middlewares
// registers a `schema` handler, see the `schema` attribute in the example below
const schema = require('metalman-schema')
// registers an `action` handler, see the `action` attribute in the example below
// Basically it just proxies the `action` attribute of a command to the middleware factory
// e.g. `function action (command) { return command.action }`
const action = metalman.action
// Pass the middlewares to the command factory
const command = metalman([schema, metalman.action])
module.exports = {
doSomething: command({
async action (cmd) {
return somethingAsync(cmd)
}
}),
createUser: command({
schema: {
required: ['id', 'name'],
properties: {
id: {type: 'string', maxLength: 36},
name: {type: 'string'}
}
},
// This middleware framework only supports
// one input argument and an optional callback
// We like to keep it simple (and fast), so we can stream objects into those methods
action (cmd) {
// cmd.id is definitely a string
// cmd.name is also a string
// By returning a value, you can change what gets passed
// into the next middleware as input parameter.
// If you don't need to change the output parameter,
// and just go to the next middleware, you can return `undefined`
return {id: cmd.id, name: cmd.name}
}
})
}
// module.exports.createUser({id: 'some-id', name: 'foobar'}, console.log)
Api
metalman([middlewares])
Instatiates a new object where you can pass some middlewares.
const metalman = require('metalman')
const commands = metalman([metalman.action])
A middleware is a factory that should return a command handler. It receives the config object of a command that's passed using instance(config)
.
metalman.action
The simplest middleware there is. It executes the provided function passed as action
property on a command config.
const metalman = require('metalman')
const commands = metalman([metalman.action])
const someCommand = commands({action (param) { throw new Error(param) }})
// `someCommand()` returns a callback and therefore `awaiting` it
// will throw an error 'Hello' as we're just proxying it to the error instance
// in the example.
await someCommand('Hello')
instance(config)
Constructs a new function using the provided config which gets passed to the middlewares that conditionally can register a middleware.
The config parameter must be an object.
instance.object({...methods})
A helper to create multiple methods and return an object
e.g.
const metalman = require('metalman')
const commands = metalman([metalman.action])
// The following declaration will basically return an object
module.exports = commands.object({
ping: {
action (cmd) { return 'pong' }
},
someAsyncFunction: {
async action (cmd) { await 'something' }
}
})
// The returned methods are automatically promisified and callbackified
// Just provide the optional callback as parameter, to execute it as callback
// It will look like something similar like that:
module.exports = {
ping (cmd, [callback]) { return 'pong' },
someAsyncFunction (cmd, [callback]) { await 'something' }
}
e.g. if you want to mix in your custom functions, just use a spread operator
// The following declaration will basically return an object
module.exports = {
someOtherMethod () { return 'Hello World' },
...commands.object({
ping: {
action (cmd) { return 'pong' }
},
someAsyncFunction: {
async action (cmd) { await 'something' }
}
})
}
instance.define('name', config)
Just another helper to declare some commands. e.g.
module.exports = commands
.define('ping', {action () { return 'pong' }})
.define('someAsyncFunction', {async action () { await 'something' }})
Example middleware
The action middleware exposed as require('metalman').action
.
function actionMiddleware (config) { return config.action }
module.exports = schemaValidation
// The command config directly gets passed to the factory
function schemaValidation (commandConfig) {
// In case the command doesn't need a middleware, just return a falsy value
if (!commandConfig.schema) return
const validator = require('ajv')(commandConfig.schema)
return function validate (command) {
// returning `undefined` continues the middleware execution
if (validator(command)) return
else throw new Error(JSON.stringify(validator.errors)
}
}
Example websocket server
Check out /examples/server.js and /examples/client.js
const schema = require('metalman-schema')
const action = require('metalman-action')
const Methodman = require('methodman')
const metalman = require('metalman')
const websocket = require('websocket-stream')
const command = metalman([schema, action])
const commands = {
echo: command({
schema: {type: 'string'},
action (str) {
return str
}
})
}
function onWebsocketStream (stream) {
const methodman = Methodman(stream)
methodman.commands(commands)
}
const http = require('http')
const server = http.createServer(function (req, res) { res.end() })
websocket.createServer({server}, onWebsocketStream)
const port = process.env.PORT || 0
server.listen(port, function (err) {
if (err) return console.error(err)
console.log('Listening on http://localhost:%s', server.address().port)
})