×

Prerequisites

TypeScript function template structure

When you create a TypeScript function using the Knative (kn) CLI, the project directory looks like a typical TypeScript project. The only exception is the additional func.yaml file, which is used for configuring the function.

Both http and event trigger functions have the same template structure:

Template structure
.
├── func.yaml (1)
├── package.json (2)
├── package-lock.json
├── README.md
├── src
│   └── index.ts (3)
├── test (4)
│   ├── integration.ts
│   └── unit.ts
└── tsconfig.json
1 The func.yaml configuration file is used to determine the image name and registry.
2 You are not restricted to the dependencies provided in the template package.json file. You can add additional dependencies as you would in any other TypeScript project.
Example of adding npm dependencies
npm install --save opossum

When the project is built for deployment, these dependencies are included in the created runtime container image.

3 Your project must contain an src/index.js file which exports a function named handle.
4 Integration and unit test scripts are provided as part of the function template.

About invoking TypeScript functions

When using the Knative (kn) CLI to create a function project, you can generate a project that responds to CloudEvents or one that responds to simple HTTP requests. CloudEvents in Knative are transported over HTTP as a POST request, so both function types listen for and respond to incoming HTTP events.

TypeScript functions can be invoked with a simple HTTP request. When an incoming request is received, functions are invoked with a context object as the first parameter.

TypeScript context objects

To invoke a function, you provide a context object as the first parameter. Accessing properties of the context object can provide information about the incoming HTTP request.

Example context object
function handle(context:Context): string

This information includes the HTTP request method, any query strings or headers sent with the request, the HTTP version, and the request body. Incoming requests that contain a CloudEvent attach the incoming instance of the CloudEvent to the context object so that it can be accessed by using context.cloudevent.

Context object methods

The context object has a single method, cloudEventResponse(), that accepts a data value and returns a CloudEvent.

In a Knative system, if a function deployed as a service is invoked by an event broker sending a CloudEvent, the broker examines the response. If the response is a CloudEvent, this event is handled by the broker.

Example context object method
// Expects to receive a CloudEvent with customer data
export function handle(context: Context, cloudevent?: CloudEvent): CloudEvent {
  // process the customer
  const customer = cloudevent.data;
  const processed = processCustomer(customer);
  return context.cloudEventResponse(customer)
    .source('/customer/process')
    .type('customer.processed')
    .response();
}

Context types

The TypeScript type definition files export the following types for use in your functions.

Exported type definitions
// Invokable is the expeted Function signature for user functions
export interface Invokable {
    (context: Context, cloudevent?: CloudEvent): any
}

// Logger can be used for structural logging to the console
export interface Logger {
  debug: (msg: any) => void,
  info:  (msg: any) => void,
  warn:  (msg: any) => void,
  error: (msg: any) => void,
  fatal: (msg: any) => void,
  trace: (msg: any) => void,
}

// Context represents the function invocation context, and provides
// access to the event itself as well as raw HTTP objects.
export interface Context {
    log: Logger;
    req: IncomingMessage;
    query?: Record<string, any>;
    body?: Record<string, any>|string;
    method: string;
    headers: IncomingHttpHeaders;
    httpVersion: string;
    httpVersionMajor: number;
    httpVersionMinor: number;
    cloudevent: CloudEvent;
    cloudEventResponse(data: string|object): CloudEventResponse;
}

// CloudEventResponse is a convenience class used to create
// CloudEvents on function returns
export interface CloudEventResponse {
    id(id: string): CloudEventResponse;
    source(source: string): CloudEventResponse;
    type(type: string): CloudEventResponse;
    version(version: string): CloudEventResponse;
    response(): CloudEvent;
}

CloudEvent data

If the incoming request is a CloudEvent, any data associated with the CloudEvent is extracted from the event and provided as a second parameter. For example, if a CloudEvent is received that contains a JSON string in its data property that is similar to the following:

{
  "customerId": "0123456",
  "productId": "6543210"
}

When invoked, the second parameter to the function, after the context object, will be a JavaScript object that has customerId and productId properties.

Example signature
function handle(context: Context, cloudevent?: CloudEvent): CloudEvent

The cloudevent parameter in this example is a JavaScript object that contains the customerId and productId properties.

TypeScript function return values

Functions can return any valid JavaScript type or can have no return value. When a function has no return value specified, and no failure is indicated, the caller receives a 204 No Content response.

Functions can also return a CloudEvent or a Message object in order to push events into the Knative Eventing system. In this case, the developer is not required to understand or implement the CloudEvent messaging specification. Headers and other relevant information from the returned values are extracted and sent with the response.

Example
export const handle: Invokable = function (
  context: Context,
  cloudevent?: CloudEvent
): Message {
  // process customer and return a new CloudEvent
  const customer = cloudevent.data;
  return HTTP.binary(
    new CloudEvent({
      source: 'customer.processor',
      type: 'customer.processed'
    })
  );
};

Returning headers

You can set a response header by adding a headers property to the return object. These headers are extracted and sent with the response to the caller.

Example response header
export function handle(context: Context, cloudevent?: CloudEvent): Record<string, any> {
  // process customer and return custom headers
  const customer = cloudevent.data as Record<string, any>;
  return { headers: { 'customer-id': customer.id } };
}

Returning status codes

You can set a status code that is returned to the caller by adding a statusCode property to the return object:

Example status code
export function handle(context: Context, cloudevent?: CloudEvent): Record<string, any> {
  // process customer
  const customer = cloudevent.data as Record<string, any>;
  if (customer.restricted) {
    return {
      statusCode: 451
    }
  }
  // business logic, then
  return {
    statusCode: 240
  }
}

Status codes can also be set for errors that are created and thrown by the function:

Example error status code
export function handle(context: Context, cloudevent?: CloudEvent): Record<string, string> {
  // process customer
  const customer = cloudevent.data as Record<string, any>;
  if (customer.restricted) {
    const err = new Error(Unavailable for legal reasons);
    err.statusCode = 451;
    throw err;
  }
}

Testing TypeScript functions

TypeScript functions can be tested locally on your computer. In the default project that is created when you create a function using kn func create, there is a test folder that contains some simple unit and integration tests.

Prerequisites
  • The OpenShift Serverless Operator and Knative Serving are installed on the cluster.

  • You have installed the Knative (kn) CLI.

  • You have created a function by using kn func create.

Procedure
  1. If you have not previously run tests, install the dependencies first:

    $ npm install
  2. Navigate to the test folder for your function.

  3. Run the tests:

    $ npm test

Overriding liveness and readiness probe values

You can override liveness and readiness probe values for your TypeScript functions. This allows you to configure health checks performed on the function.

Prerequisites
  • The OpenShift Serverless Operator and Knative Serving are installed on the cluster.

  • You have installed the Knative (kn) CLI.

  • You have created a function by using kn func create.

Procedure
  1. In your function code, create the Function object, which implements the following interface:

    export interface Function {
      init?: () => any; (1)
    
      shutdown?: () => any; (2)
    
      liveness?: HealthCheck; (3)
    
      readiness?: HealthCheck; (4)
    
      logLevel?: LogLevel;
    
      handle: CloudEventFunction | HTTPFunction; (5)
    }
    1 The initialization function, called before the server is started. This function is optional and should be synchronous.
    2 The shutdown function, called after the server is stopped. This function is optional and should be synchronous.
    3 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.
    4 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.
    5 The function to handle HTTP requests.

    For example, add the following code to the index.js file:

    const Function = {
    
      handle: (context, body) => {
        // The function logic goes here
        return 'function called'
      },
    
      liveness: () => {
        process.stdout.write('In liveness\n');
        return 'ok, alive';
      }, (1)
    
      readiness: () => {
        process.stdout.write('In readiness\n');
        return 'ok, ready';
      } (2)
    };
    
    Function.liveness.path = '/alive'; (3)
    Function.readiness.path = '/ready'; (4)
    
    module.exports = Function;
    1 Custom liveness function.
    2 Custom readiness function.
    3 Custom liveness endpoint.
    4 Custom readiness endpoint.

    As an alternative to Function.liveness.path and Function.readiness.path, you can specify custom endpoints using the LIVENESS_URL and READINESS_URL environment variables:

    run:
      envs:
      - name: LIVENESS_URL
        value: /alive (1)
      - name: READINESS_URL
        value: /ready (2)
    1 The liveness path, set to /alive here.
    2 The readiness path, set to /ready here.
  2. Add the new endpoints to the func.yaml file, so that they are properly bound to the container for the Knative service:

    deploy:
      healthEndpoints:
        liveness: /alive
        readiness: /ready

TypeScript context object reference

The context object has several properties that can be accessed by the function developer. Accessing these properties can provide information about incoming HTTP requests and write output to the cluster logs.

log

Provides a logging object that can be used to write output to the cluster logs. The log adheres to the Pino logging API.

Example log
export function handle(context: Context): string {
    // log the incoming request body's 'hello' parameter
    if (context.body) {
      context.log.info((context.body as Record<string, string>).hello);
    } else {
      context.log.info('No data received');
    }
    return 'OK';
}

You can access the function by using the kn func invoke command:

Example command
$ kn func invoke --target 'http://example.function.com'
Example output
{"level":30,"time":1604511655265,"pid":3430203,"hostname":"localhost.localdomain","reqId":1,"msg":"Processing customer"}

You can change the log level to one of fatal, error, warn, info, debug, trace, or silent. To do that, change the value of logLevel by assigning one of these values to the environment variable FUNC_LOG_LEVEL using the config command.

query

Returns the query string for the request, if any, as key-value pairs. These attributes are also found on the context object itself.

Example query
export function handle(context: Context): string {
      // log the 'name' query parameter
    if (context.query) {
      context.log.info((context.query as Record<string, string>).name);
    } else {
      context.log.info('No data received');
    }
    return 'OK';
}

You can access the function by using the kn func invoke command:

Example command
$ kn func invoke --target 'http://example.function.com' --data '{"name": "tiger"}'
Example output
{"level":30,"time":1604511655265,"pid":3430203,"hostname":"localhost.localdomain","reqId":1,"msg":"tiger"}
{"level":30,"time":1604511655265,"pid":3430203,"hostname":"localhost.localdomain","reqId":1,"msg":"tiger"}

body

Returns the request body, if any. If the request body contains JSON code, this will be parsed so that the attributes are directly available.

Example body
export function handle(context: Context): string {
    // log the incoming request body's 'hello' parameter
    if (context.body) {
      context.log.info((context.body as Record<string, string>).hello);
    } else {
      context.log.info('No data received');
    }
    return 'OK';
}

You can access the function by using the kn func invoke command:

Example command
$ kn func invoke --target 'http://example.function.com' --data '{"hello": "world"}'
Example output
{"level":30,"time":1604511655265,"pid":3430203,"hostname":"localhost.localdomain","reqId":1,"msg":"world"}

headers

Returns the HTTP request headers as an object.

Example header
export function handle(context: Context): string {
    // log the incoming request body's 'hello' parameter
    if (context.body) {
      context.log.info((context.headers as Record<string, string>)['custom-header']);
    } else {
      context.log.info('No data received');
    }
    return 'OK';
}

You can access the function by using the curl command to invoke it:

Example command
$ curl -H'x-custom-header: some-value’' http://example.function.com
Example output
{"level":30,"time":1604511655265,"pid":3430203,"hostname":"localhost.localdomain","reqId":1,"msg":"some-value"}

HTTP requests

method

Returns the HTTP request method as a string.

httpVersion

Returns the HTTP version as a string.

httpVersionMajor

Returns the HTTP major version number as a string.

httpVersionMinor

Returns the HTTP minor version number as a string.

Next steps