The node function invoker provides a host for functions consisting of a single NodeJS module. It adheres to riff streaming protocol and invokes functions accordingly.
Non-streaming functions, more specifically "request-reply" functions, such as:
module.exports = (x) => x ** 2;
will be automatically promoted to streaming functions via the equivalent of the map
operator.
Request-reply functions can also be asynchronous:
module.exports = async (x) => x ** 2;
or return a Promise:
module.exports = (x) => Promise.resolve(x ** 2);
Finally, note that the interaction model can be explicitly advertised, albeit this is not necessary:
module.exports = (x) => x ** 2;
module.exports.$interactionModel = 'request-reply';
Streaming functions must comply to the following signature:
module.exports = (inputStreams, outputStreams) => {
const { numbers, letters } = inputStreams;
const { repetitions } = outputStreams;
// do something
};
module.exports.$interactionModel = 'node-streams';
Please note that streaming functions must always declare the corresponding interaction mode.
Streams can also be looked up by index:
module.exports = (inputStreams, outputStreams) => {
const firstInputStream = inputStreams.$order[0];
const firstOutputStream = outputStreams.$order[0];
const secondOutputStream = outputStreams.$order[1];
// do something
};
module.exports.$interactionModel = 'node-streams';
Input streams are Readable streams.
Output streams are Writable streams.
The function must end the output streams when it is done emitting data or when an error occurs
(if the output streams are pipe
'd from
input streams, then this is automatically managed).
A message is an object that contains both headers and a payload. Message headers are a map with case-insensitive keys and multiple string values.
Since JavaScript and Node have no built-in type for messages or headers, riff uses the @projectriff/message npm module.
By default, request-reply functions accept and produce payloads. They can be configured instead to receive either the entire message or the headers only.
Streaming functions can only receive messages. Configuring them with
$argumentType
will trigger an error. However, they can produce either messages or payloads, just like request-reply functions.
// a request-reply function that accepts a message, which is an instance of Message
module.exports = message => {
const authorization = message.headers.getValue('Authorization');
// [...]
};
// tell the invoker the function wants to receive messages
module.exports.$argumentType = 'message';
To produce messages, functions should install the @projectriff/message
package:
npm install --save @projectriff/message
const { Message } = require('@projectriff/message');
const instanceId = Math.round(Math.random() * 10000);
let invocationCount = 0;
// a request-reply function that produces a Message
module.exports = name => {
return Message.builder()
.addHeader('X-Riff-Instance', instanceId)
.addHeader('X-Riff-Count', invocationCount++)
.payload(`Hello ${name}!`)
.build();
};
Functions that communicate with external services, like a database, can use the $init
and $destroy
lifecycle hooks
on the function.
These methods are called once per process.
The $init
method is guaranteed to finish before the main function is invoked for the first time.
The $destroy
method is guaranteed to be invoked after all of the main functions are finished, before the process shuts down.
let client;
// function
module.exports = async ({key, amount}) => {
return await client.incrby(key, amount);
};
// setup
module.exports.$init = async () => {
const Redis = require('redis-promise');
client = new Redis();
await client.connect();
};
// cleanup
module.exports.$destroy = async () => {
await client.quit();
};
The lifecycle methods are optional, and should only be implemented when needed. Note that the lifecycle hooks must be fields on the exported function. The hooks may be either synchronous or async functions. Lifecycle functions have up to 10 seconds to complete their work, or the function invoker will abort.
This invoker supports only streaming, and complies to riff streaming protocol. However, it is possible to send HTTP requests and receive HTTP responses if you combine this invoker with the streaming HTTP adapter available here.
- Node version required: 10 (LTS), 12 (LTS) or 13.
- Make sure to install the EditorConfig plugin in your editing environment.
- Install dependencies by running
npm ci
. - Run the tests with
npm test
Execute the following:
$ cd /path/to/node-function-invoker
$ FUNCTION_URI="/absolute/path/to/function.js" NODE_DEBUG='riff' node server.js
If you just want to test request-reply functions, clone the Streaming HTTP adapter and run:
$ cd /path/to/streaming-http-adapter
$ make
$ FUNCTION_URI="/absolute/path/to/function.js" NODE_DEBUG='riff' ./streaming-http-adapter node /path/to/node-function-invoker/server.js
You can then send HTTP POST requests to http://localhost:8080
and interact with the function.
We use prettier style checks to enforce code consistency. Many editors can automatically reformat source to match this style. To manually request formatted source run npm run format
.