Socket
Socket
Sign inDemoInstall

faas-js-runtime

Package Overview
Dependencies
Maintainers
1
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 0.9.1 to 0.9.3

lib/types.d.ts

14

index.d.ts
import { Server } from 'http';
import { CloudEvent } from 'cloudevents';
import { Context } from './lib/context';
import { CloudEventFunction, HTTPFunction } from './lib/types';
// Invokable describes the function signature for a function that can be invoked by the server.
export type Invokable = CloudEventFunction | HTTPFunction;
// start starts the server for the given function.
export declare const start: {

@@ -9,2 +12,3 @@ (func: Invokable, options?: InvokerOptions): Promise<Server>

// InvokerOptions allow the user to configure the server.
export type InvokerOptions = {

@@ -20,7 +24,3 @@ 'logLevel'?: LogLevel,

export interface Invokable {
(context: Context, cloudevent?: CloudEvent<any>): any
}
// re-export
export { Context, Logger, CloudEventResponse } from './lib/context';
export * from './lib/types';

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

});
server.addContentTypeParser('*', { parseAs: 'buffer' }, function(req, body, done) {

@@ -52,3 +52,3 @@ try {

} catch (err) {
err.statusCode = 400;
err.statusCode = 500;
done(err, undefined);

@@ -62,3 +62,3 @@ }

server.addHook('preHandler', (req, reply, done) => {
req.fcontext = new Context(req, reply);
req.fcontext = new Context(req);
done();

@@ -76,3 +76,7 @@ });

return new Promise((resolve, reject) => {
server.listen(port, '0.0.0.0', err => {
server.listen({
port,
host: '::'
},
err => { // callback function
if (err) return reject(err);

@@ -87,3 +91,3 @@ resolve(server.server);

if (!fileOrDirPath) fileOrDirPath = './';
let baseDir;

@@ -90,0 +94,0 @@ let maybeDir = fs.statSync(fileOrDirPath);

@@ -5,4 +5,4 @@ const { CloudEvent } = require('cloudevents');

constructor(request) {
this.body = request.body;
this.query = { ...request.query };
this.body = { ...request.body };
this.headers = { ...request.headers };

@@ -13,2 +13,3 @@ this.method = request.raw.method;

this.httpVersionMinor = request.raw.httpVersionMinor;
this.contentType = request.headers['content-type'];

@@ -22,3 +23,2 @@ Object.assign(this, request.query);

}
}

@@ -25,0 +25,0 @@

@@ -22,4 +22,4 @@ const invoker = require('./invoker');

function sendReply(reply, payload) {
const contentType = payload.headers['content-type'];
if (contentType.startsWith('text/plain') && (typeof payload.response !== 'string')) {
const contentType = payload.headers?.['content-type'];
if (contentType?.startsWith('text/plain') && (typeof payload.response !== 'string')) {
payload.response = JSON.stringify(payload.response);

@@ -26,0 +26,0 @@ }

@@ -1,2 +0,4 @@

'use strict';
/**
* The invoker module is responsible for invoking the user function.
*/
const { CloudEvent, HTTP } = require('cloudevents');

@@ -18,58 +20,89 @@

let fnReturn;
const scope = Object.freeze({});
try {
if (context.cloudevent) {
// If there is a cloud event, provide the data
// as the first parameter
payload.response = await func.bind(scope)(context, context.cloudevent);
// Invoke the function with the context and the CloudEvent
fnReturn = await func.bind(scope)(context, context.cloudevent);
// If the response is a CloudEvent, we need to convert it
// to a Message first and respond with the headers/body
if (fnReturn instanceof CloudEvent) {
try {
const message = HTTP.binary(fnReturn);
payload.headers = {...payload.headers, ...message.headers};
payload.response = message.body;
// In this case, where the function is invoked with a CloudEvent
// and returns a CloudEvent we don't need to continue processing the
// response. Just return it using the HTTP.binary format.
return payload;
} catch (err) {
return handleError(err, log);
}
}
} else {
// Invoke with context
// TODO: Should this actually just get the Node.js request object?
payload.response = await func.bind(scope)(context);
// It's an HTTP function - extract the request body
let body = context.body;
if (context.contentType === 'application/json' && typeof body === 'string') {
try {
body = JSON.parse(body);
} catch (err) {
console.error('Error parsing JSON body', err);
}
}
// Invoke with context and the raw body
fnReturn = await func.bind(scope)(context, body);
}
} catch (err) {
payload.response = handleError(err, log);
return handleError(err, log);
}
// Raw HTTP functions, and CloudEvent functions that return something
// other than a CloudEvent, will end up here.
// Return 204 No Content if the function returns
// null, undefined or empty string
if (!payload.response) {
if (!fnReturn) {
payload.headers['content-type'] = 'text/plain';
payload.code = 204;
payload.response = '';
return payload;
}
// Check for user defined headers
if (typeof payload.response.headers === 'object') {
// If the function returns a string, set the content type to text/plain
// and return it as the response
if (typeof fnReturn === 'string') {
payload.headers['content-type'] = 'text/plain; charset=utf8';
payload.response = fnReturn;
return payload;
}
// The function returned an object or an array, check for
// user defined headers or datacontenttype
if (typeof fnReturn?.headers === 'object') {
const headers = {};
// normalize the headers as lowercase
for (const header in payload.response.headers) {
headers[header.toLocaleLowerCase()] = payload.response.headers[header];
for (const header in fnReturn.headers) {
headers[header.toLocaleLowerCase()] = fnReturn.headers[header];
}
payload.headers = { ...payload.headers, ...headers };
delete payload.response.headers;
}
// If the response is a CloudEvent, we need to convert it
// to a Message first and respond with the headers/body
if (payload.response instanceof CloudEvent) {
try {
const message = HTTP.binary(payload.response);
payload.headers = {...payload.headers, ...message.headers};
payload.response = message.body;
} catch (err) {
payload.response = handleError(err, log);
return payload;
}
}
// Check for user defined status code
if (payload.response.statusCode) {
payload.code = payload.response.statusCode;
delete payload.response.statusCode;
if (fnReturn.statusCode) {
payload.code = fnReturn.statusCode;
}
// Check for user supplied body
if (payload.response.body !== undefined) {
payload.response = payload.response.body;
delete payload.response.body;
if (fnReturn.body !== undefined) {
if (typeof fnReturn.body === 'string') {
payload.headers['content-type'] = 'text/plain; charset=utf8';
} else if (typeof fnReturn.body === 'object') {
payload.headers['content-type'] = 'application/json; charset=utf8';
}
payload.response = fnReturn.body;
} else if (typeof fnReturn === 'object' && !fnReturn?.body) {
// Finally, the user may have supplied a simple object response
payload.headers['content-type'] = 'application/json; charset=utf8';
payload.response = fnReturn;
}

@@ -81,7 +114,7 @@ return payload;

function handleError(err, log) {
log.error(err);
log.error('Error processing user function', err);
return {
statusCode: err.code ? err.code : 500,
statusMessage: err.message
code: err.code ? err.code : 500,
response: err.message
};
}
{
"name": "faas-js-runtime",
"version": "0.9.1",
"version": "0.9.3",
"repository": {

@@ -10,6 +10,9 @@ "type": "git",

"license": "Apache-2.0",
"engines": {
"node": "^18 || ^16 || ^14"
},
"scripts": {
"lint": "eslint --ignore-path .gitignore .",
"test": "npm run test:source && npm run test:types",
"test:source": "nyc tape test/test*.js | colortape",
"test:source": "nyc --reporter=lcovonly tape test/test*.js | colortape",
"test:types": "tsd",

@@ -25,15 +28,17 @@ "pretest": "npm run lint"

],
"bugs": {
"url": "https://github.com/boson-project/faas-js-runtime/issues"
},
"types": "index.d.ts",
"bin": "./bin/cli.js",
"dependencies": {
"chalk": "^4.1.2",
"cloudevents": "^5.3.1",
"commander": "^8.1.0",
"commander": "^9.4.0",
"death": "^1.1.0",
"fastify": "^3.20.1",
"fastify": "^4.9.2",
"js-yaml": "^4.1.0",
"node-os-utils": "^1.3.5",
"overload-protection": "^1.2.0",
"prom-client": "^14.0.0",
"qs": "^6.10.1"
"overload-protection": "^1.2.3",
"prom-client": "^14.1.0",
"qs": "^6.11.0"
},

@@ -49,3 +54,3 @@ "devDependencies": {

"nyc": "^15.1.0",
"supertest": "^6.1.4",
"supertest": "^6.3.1",
"tape": "^5.3.1",

@@ -55,7 +60,2 @@ "tsd": "^0.17.0",

},
"standardx": {
"ignore": [
"lib/context.js"
]
},
"tsd": {

@@ -62,0 +62,0 @@ "directory": "test/types",

@@ -1,24 +0,76 @@

## FaaS Node.js Runtime Framework
# Node.js Function Framework
[![Node.js CI](https://github.com/boson-project/faas-js-runtime/workflows/Node.js%20CI/badge.svg)](https://github.com/boson-project/faas-js-runtime/actions?query=workflow%3A%22Node.js+CI%22+branch%3Amaster)
[![codecov](https://codecov.io/gh/boson-project/faas-js-runtime/branch/main/graph/badge.svg?token=Z72LKANFJI)](https://codecov.io/gh/boson-project/faas-js-runtime)
This module provides a Node.js framework for executing a function that
exists in a user provided directory path as an `index.js` file. The
exists in a user-provided directory path as an `index.js` file. The
directory may also contain an optional `package.json` file which can
be used to declare runtime dependencies for the function.
be used to declare runtime dependencies for the function. You can also
provide a path to an arbitrary JavaScript file instead of a directory
path, allowing you to execute a single file as a function.
The function is loaded, and then invoked for incoming HTTP requests
The function is loaded and then invoked for incoming HTTP requests
at `localhost:8080`. The incoming request may be a
[Cloud Event](https://github.com/cloudevents/sdk-javascript#readme.) or
just a simple HTTP GET request. In either case, the function will receive
a `Context` object instance that has a `cloudevent` property. For a raw HTTP
request, the incoming request is converted to a Cloud Event.
just a simple HTTP GET/POST request. The invoked user function can be
`async` but that is not required.
The invoked user function can be `async` but that is not required.
## Function Signatures
### CLI
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.
### HTTP Functions
The HTTP function signature is the simplest. It is invoked for every HTTP request that does not contain a CloudEvent.
```typescript
interface HTTPFunction {
(context: Context, body?: IncomingBody): HTTPFunctionReturn;
}
```
Where the `IncomingBody` is either a string, a Buffer, a JavaScript object, or undefined, depending on what was supplied in the HTTP POST message body. The `HTTTPFunctionReturn` type is defined as:
```typescript
type HTTPFunctionReturn = Promise<StructuredReturn> | StructuredReturn | ResponseBody | void;
```
Where the `StructuredReturn` is a JavaScript object with the following properties:
```typescript
interface StructuredReturn {
statusCode?: number;
headers?: Record<string, string>;
body?: ResponseBody;
}
```
If the function returns a `StructuredReturn` object, then the `statusCode` and `headers` properties are used to construct the HTTP response. If the `body` property is present, it is used as the response body. If the function returns `void` or `undefined`, then the response body is empty.
The `ResponseBody` is either a string, a JavaScript object, or a Buffer. JavaScript objects will be serialized as JSON. Buffers will be sent as binary data.
### CloudEvent Functions
CloudEvent functions are used in environments where the incoming HTTP request is a CloudEvent. The function signature is:
```typescript
interface CloudEventFunction {
(context: Context, event: CloudEvent): CloudEventFunctionReturn;
}
```
Where the return type is defined as:
```typescript
type CloudEventFunctionReturn = Promise<CloudEvent> | CloudEvent | HTTPFunctionReturn;
```
The function return type can be anything that a simple HTTP function can return or a CloudEvent. Whatever is returned, it will be sent back to the caller as a response.
## CLI
The easiest way to get started is to use the CLI. You can call it
with the path to any JavaScript file which has a default export that
is a function. For example,
is a function. For example,

@@ -39,2 +91,5 @@ ```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`

@@ -47,5 +102,5 @@ with the CLI.

### Usage
## Usage as a Module
In my current working directory, I have an `index.js` file like this.
In the current working directory, there is an `index.js` file like this.

@@ -61,6 +116,7 @@ ```js

// My function directory is in ./function-dir
// The function directory is in ./function-dir
start(require(`${__dirname}/function-dir/`), server => {
// The server is now listening on localhost:8080
// and the function will be invoked for each HTTP
// and the function defined in `function-dir/index.js`
// will be invoked for each HTTP
// request to this endpoint.

@@ -79,3 +135,3 @@ console.log('Server listening');

module.exports = async function myFunction(context) {
const ret = 'This is a test function for Node.js FaaS. Success.';
const ret = 'This is a test for Node.js functions. Success.';
return new Promise((resolve, reject) => {

@@ -110,3 +166,3 @@ setTimeout(_ => {

-H'Ce-type: dev.knative.example' \
-H'Ce-specversion: 0.2' \
-H'Ce-specversion: 1.0' \
http://localhost:8080

@@ -117,7 +173,2 @@ ```

You can see this in action, executing the function at `test/fixtures/async`
by running `node hack/run.js`.
### Tests
Just run `npm test`.
You can see this in action by running `node hack/run.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