Socket
Socket
Sign inDemoInstall

faas-js-runtime

Package Overview
Dependencies
Maintainers
9
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

faas-js-runtime - npm Package Compare versions

Comparing version 1.0.0 to 1.1.0

16

bin/cli.js

@@ -7,4 +7,2 @@ #!/usr/bin/env node

const pkg = require('../package.json');
const ON_DEATH = require('death')({ uncaughtException: true });
const { Command } = require('commander');

@@ -27,3 +25,2 @@

try {
let server;
let options = {

@@ -37,6 +34,7 @@ logLevel: process.env.FUNC_LOG_LEVEL || programOpts['logLevel'] || defaults.LOG_LEVEL,

if (typeof code === 'function') {
server = await start(code, options);
} else if (typeof code.handle === 'function') {
server = await start(code.handle, options);
// The module will extract `handle` and other lifecycle functions
// from `code` if it is an object. If it's just a function, it will
// be used directly.
if (typeof code === 'function' || typeof code === 'object') {
return start(code, options);
} else {

@@ -46,6 +44,2 @@ console.error(code);

}
ON_DEATH(_ => {
server.close();
process.exit(0);
});
} catch (error) {

@@ -52,0 +46,0 @@ console.error(`⛔ ${error}`);

import { Server } from 'http';
import { CloudEventFunction, HTTPFunction } from './lib/types';
import { CloudEventFunction, HTTPFunction, InvokerOptions } from './lib/types';

@@ -9,17 +9,7 @@ // Invokable describes the function signature for a function that can be invoked by the server.

export declare const start: {
(func: Invokable, options?: InvokerOptions): Promise<Server>
}
// eslint-disable-next-line no-unused-vars
(func: Invokable | Function, options?: InvokerOptions): Promise<Server>
};
// InvokerOptions allow the user to configure the server.
export type InvokerOptions = {
'logLevel'?: LogLevel,
'port'?: Number,
'path'?: String
}
export enum LogLevel {
'trace', 'debug', 'info', 'warn', 'error', 'fatal'
}
// re-export
export * from './lib/types';

@@ -8,2 +8,3 @@ const qs = require('qs');

const Context = require('./lib/context');
const shutdown = require('death')({ uncaughtException: true });

@@ -13,22 +14,106 @@ // HTTP framework

let LOG_LEVEL = 'warn';
// Default log level
const LOG_LEVEL = 'warn';
// Default port
const PORT = 8080;
// Invoker
function start(func, options) {
/**
* Starts the provided Function. If the function is a module, it will be
* inspected for init, shutdown, liveness, and readiness functions and those
* will be used to configure the server. If it's a function, it will be used
* directly.
*
* @param {Object | function} func The function to start (see the Function type)
* @param {*} options Options to configure the server
* @param {string} options.logLevel The log level to use
* @param {number} options.port The port to listen on
* @returns {Promise<http.Server>} The server that was started
*/
async function start(func, options) {
options = options || {};
if (typeof func === 'function') {
return __start(func, options);
}
if (typeof func.handle !== 'function') {
throw new TypeError('Function must export a handle function');
}
if (typeof func.init === 'function') {
func.init();
}
if (typeof func.shutdown === 'function') {
options.shutdown = func.shutdown;
}
if (typeof func.liveness === 'function') {
options.liveness = func.liveness;
}
if (typeof func.readiness === 'function') {
options.readiness = func.readiness;
}
return __start(func.handle, options);
}
/**
* Internal function to start the server. This is used by the start function.
*
* @param {function} func - The function to start
* @param {*} options - Options to configure the server
* @param {string} options.logLevel - The log level to use
* @param {number} options.port - The port to listen on
* @returns {Promise<http.Server>} The server that was started
*/
async function __start(func, options) {
// Load a func.yaml file if it exists
const funcConfig = loadFuncYaml(options.config) || {};
const config = loadConfig(options);
// Set the log level
if (['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent']
.find(l => funcConfig.logLevel === l)) {
LOG_LEVEL = funcConfig.logLevel;
// Create and configure the server for the default behavior
const server = initializeServer(config);
// Configures the server to handle incoming requests to the function itself,
// and also to other endpoints such as telemetry and liveness/readiness
requestHandler(server, { func, funcConfig: config });
// Start the server
try {
await server.listen({
port: config.port,
host: '::'
});
return server.server;
} catch(err) {
console.error('Error starting server', err);
process.exit(1);
}
}
// Create the server
const { logLevel = LOG_LEVEL, port = PORT } = { ...options };
const server = fastify({ logger: { level: logLevel } });
/**
* Creates and configures the HTTP server to handle incoming CloudEvents,
* and initializes the Context object.
* @param {object} config - The configuration object for port and logLevel
* @returns {FastifyInstance} The Fastify server that was created
*/
function initializeServer(config) {
const server = fastify({
logger: {
level: config.logLevel,
formatters: {
bindings: bindings => ({
pid: bindings.pid,
hostname: bindings.hostname,
node_version: process.version
})
}
}
});
// Give the Function an opportunity to clean up before the process exits
shutdown(_ => {
if (typeof config.shutdown === 'function') {
config.shutdown();
}
server.close();
process.exit(0);
});
// Add a parser for application/x-www-form-urlencoded
server.addContentTypeParser('application/x-www-form-urlencoded',

@@ -49,2 +134,4 @@ function(_, payload, done) {

// Add a parser for everything else - parse it as a buffer and
// let this framework's router handle it
server.addContentTypeParser('*', { parseAs: 'buffer' }, function(req, body, done) {

@@ -71,20 +158,27 @@ try {

// Configures the server to handle incoming requests to the function itself,
// and also to other endpoints such as telemetry and liveness/readiness
requestHandler(server, { func, funcConfig });
return server;
}
return new Promise((resolve, reject) => {
server.listen({
port,
host: '::'
},
err => { // callback function
if (err) return reject(err);
resolve(server.server);
});
});
/**
* loadConfig() loads a func.yaml file if it exists, allowing it to take precedence over the default options
*
* @param {Object} options Server options
* @param {String} options.config Path to a func.yaml file
* @param {String} options.logLevel Log level - one of 'fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent'
* @param {number} options.port Port to listen on
* @returns {Object} Configuration object
*/
function loadConfig(options) {
const opts = { ...options, ...readFuncYaml(options.config) };
opts.logLevel = opts.logLevel || LOG_LEVEL;
opts.port = opts.port || PORT;
return opts;
}
// reads a func.yaml file at path and returns it as a JS object
function loadFuncYaml(fileOrDirPath) {
/**
* Reads a func.yaml file at path and returns it as a JS object
* @param {string} fileOrDirPath - the path to the func.yaml file or the directory containing it
* @returns {object} the parsed func.yaml file
*/
function readFuncYaml(fileOrDirPath) {
if (!fileOrDirPath) fileOrDirPath = './';

@@ -91,0 +185,0 @@

@@ -26,6 +26,11 @@ 'use strict';

module.exports = function use(fastify, _, done) {
fastify.get(readinessURL, { logLevel: 'warn' }, callProtect);
fastify.get(livenessURL, { logLevel: 'warn' }, callProtect);
done();
module.exports = function healthCheck(opts) {
return (fastify, _, done) => {
// Handle health checks
fastify.get(opts?.readiness?.path || readinessURL,
{ logLevel: opts?.logLevel || 'warn' }, opts.readiness || callProtect);
fastify.get(opts?.liveness?.path || livenessURL,
{ logLevel: opts?.logLevel || 'warn' }, opts.liveness || callProtect);
done();
};
};

@@ -33,3 +33,3 @@ 'use strict';

server.register(function healthCheckContext(s, _, done) {
s.register(healthCheck);
s.register(healthCheck(opts.funcConfig));
done();

@@ -36,0 +36,0 @@ });

/* eslint-disable no-unused-vars */
import { IncomingHttpHeaders, IncomingMessage } from 'http';
import { CloudEvent } from 'cloudevents';
import { Http2ServerRequest, Http2ServerResponse } from 'http2';
/**
* The Function interface describes a Function project, including the handler function
* as well as the initialization and shutdown functions, and health checks.
*/
export interface Function {
// The initialization function, called before the server is started
// This function is optional and should be synchronous.
init?: () => any;
// The shutdown function, called after the server is stopped
// This function is optional and should be synchronous.
shutdown?: () => any;
// The liveness function, called to check if the server is alive
// This function is optional and should return 200/OK if the server is alive.
liveness?: HealthCheck;
// The readiness function, called to check if the server is ready to accept requests
// This function is optional and should return 200/OK if the server is ready.
readiness?: HealthCheck;
logLevel?: LogLevel;
// The function to handle HTTP requests
handle: CloudEventFunction | HTTPFunction;
}
/**
* The HealthCheck interface describes a health check function,
* including the optional path to which it should be bound.
*/
export interface HealthCheck {
(request: Http2ServerRequest, reply: Http2ServerResponse): any;
path?: string;
}
// InvokerOptions allow the user to configure the server.
export type InvokerOptions = {
'logLevel'?: LogLevel,
'port'?: Number,
'path'?: String
}
/**
* Log level options for the server.
*/
export enum LogLevel {
'trace', 'debug', 'info', 'warn', 'error', 'fatal'
}
/**
* CloudEventFunction describes the function signature for a function that accepts CloudEvents.

@@ -7,0 +58,0 @@ */

{
"name": "faas-js-runtime",
"version": "1.0.0",
"version": "1.1.0",
"repository": {

@@ -34,6 +34,6 @@ "type": "git",

"dependencies": {
"cloudevents": "^6.0.3",
"cloudevents": "^6.0.4",
"commander": "^10.0.0",
"death": "^1.1.0",
"fastify": "^4.12.0",
"fastify": "^4.15.0",
"js-yaml": "^4.1.0",

@@ -43,15 +43,15 @@ "node-os-utils": "^1.3.7",

"prom-client": "^14.1.1",
"qs": "^6.11.0"
"qs": "^6.11.1"
},
"devDependencies": {
"@types/node": "^18.11.18",
"@types/node": "^18.14.6",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.49.0",
"@typescript-eslint/parser": "^5.54.0",
"colortape": "^0.1.2",
"eslint": "^8.33.0",
"eslint-config-prettier": "^8.6.0",
"eslint": "^8.36.0",
"eslint-config-prettier": "^8.7.0",
"nyc": "^15.1.0",
"supertest": "^6.3.1",
"tape": "^5.6.3",
"tsd": "^0.25.0",
"tsd": "^0.26.0",
"typescript": "^4.9.5"

@@ -58,0 +58,0 @@ },

@@ -25,4 +25,38 @@ # Node.js Function Framework

## Function Signatures
## The Function Interface
The function file that is loaded may export a single function or a `Function`
object. The `Function` object allows developers to add lifecycle hooks for
initialization and shutdown, as well as providing a way to implement custom
health checks.
The `Function` interface is defined as:
```typescript
export interface Function {
// The initialization function, called before the server is started
// This function is optional and should be synchronous.
init?: () => any;
// The shutdown function, called after the server is stopped
// This function is optional and should be synchronous.
shutdown?: () => any;
// The liveness function, called to check if the server is alive
// This function is optional and should return 200/OK if the server is alive.
liveness?: HealthCheck;
// The readiness function, called to check if the server is ready to accept requests
// This function is optional and should return 200/OK if the server is ready.
readiness?: HealthCheck;
logLevel?: LogLevel;
// The function to handle HTTP requests
handle: CloudEventFunction | HTTPFunction;
}
```
## Handle Signature
This module supports two different function signatures: HTTP or CloudEvents. In the type definitions below, we use TypeScript to express interfaces and types, but this module is usable from JavaScript as well.

@@ -78,2 +112,21 @@

### Health Checks
The `Function` interface also allows for the addition of a `liveness` and `readiness` function. These functions are used to implement health checks for the function. The `liveness` function is called to check if the function is alive. The `readiness` function is called to check if the function is ready to accept requests. If either of these functions return a non-200 status code, then the function is considered unhealthy.
A health check function is defined as:
```typescript
/**
* The HealthCheck interface describes a health check function,
* including the optional path to which it should be bound.
*/
export interface HealthCheck {
(request: Http2ServerRequest, reply: Http2ServerResponse): any;
path?: string;
}
```
By default, the health checks are bound to the `/health/liveness` and `/health/readiness` paths. You can override this by setting the `path` property on the `HealthCheck` object, or by setting the `LIVENESS_URL` and `READINESS_URL` environment variables.
## CLI

@@ -83,3 +136,3 @@

with the path to any JavaScript file which has a default export that
is a function. For example,
is a function, or that exports a `Function` type. For example,

@@ -100,5 +153,2 @@ ```js

Additionally, if your JavaScript file exports more than a single function,
an exported `handle` function will be invoked.
You can expose this function as an HTTP endpoint at `localhost:8080`

@@ -111,5 +161,19 @@ with the CLI.

## Debugging Locally
During local development, it is often necessary to set breakpoints in your code for debugging. Since functions are just javascript files, using any current debugging(VS Code, Chrome Dev Tools) method will work. The linked blog post shows how to setup and use VS Code for development debugging.
https://developers.redhat.com/articles/2021/07/13/nodejs-serverless-functions-red-hat-openshift-part-2-debugging-locally
## Debugging Remotely
It is also possible to debug your function while it is running on a remote cluster. The linked blog posts shows how to setup and use the Chrome Dev Tools inspector for debugging on a cluster.
https://developers.redhat.com/articles/2021/12/08/nodejs-serverless-functions-red-hat-openshift-part-3-debugging-cluster
### Functions as ES Modules
Functions can be written and imported as ES modules with either the `.mjs` file extenstion or by adding the `type` property to the functions package.json and setting it to `module`.
Functions can be written and imported as ES modules with either the `.mjs` file extension or by adding the `type` property to the functions package.json and setting it to `module`.

@@ -201,2 +265,2 @@ ```js

You can see this in action by running `node hack/run.js`.
You can see this in action by running `node bin/cli.js sample/index.js`.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc