Skip to content
/ fenrir Public

A transpiler that simplifies the development of serverless functions.

Notifications You must be signed in to change notification settings

Gejsi/fenrir

Repository files navigation

Fenrir

Fenrir is a framework that enriches serverless programming by providing developers with new meta-programming constructs, named annotations. Users mark the serverless functions with annotations that signal the framework which code transformations are going to be needed and which metadata will be used for their deployment.

Fenrir transpiles TypeScript codebases written following AWS Lambda's interfaces, while the generated metadata is appended to a serverless.yml file, since the deployment is entirely delegated to the Serverless framework.

Here is a quick example that converts code written in a monolithic style into a serverless architecture:

// MONOLITH
/**
 * $Fixed
 * $HttpApi(method: "GET", path: "/orders/report")
 */
export async function processOrder(orderId) {
  // ... processing logic ...
  console.log(`Processing order ${orderId}`)
  return order
}
/** $Scheduled(rate: "2 hours") */
export async function generateReport() {
  // get the processed data and generate report
  console.log('Generating report')
}

// SERVERLESS CODE
export async function processOrder(event) {
  const orderId = event.orderId
  // ... processing logic ...
  console.log(`Processing order ${orderId}`)
  return {
    statusCode: 200,
    body: JSON.stringify(order),
  }
}
// The implementation of `generateReport`
// is omitted as it remains unchanged.

While this is the metadata generated by the previous example:

# SERVERLESS METADATA
processOrder:
  handler: output.processOrder
  events:
    - httpApi:
        method: GET
        path: /orders/report
generateReport:
  handler: output.generateReport
  events:
    - schedule:
        rate: 2 hours

Usage

⚠️ Fenrir hasn't been published to NPM yet

Install the CLI:

npm install -g fenrir-cli

Make sure both fenrir-base and serverless packages are installed.

The CLI offers a init command to get things started (it creates a fenrir.config.json):

fenrir init

However, most of the time the CLI is used with the -g flag which indicates in which directory the fenrir.config.json file is located:

fenrir -g functions

Annotations

Annotations are syntactical units, or keywords, enclosed within JSDoc comments, each associated with their respective transformer. They can be parameterized and composed to form a pipeline of transformations.

$Fixed(memorySize?: number, timeout?: number, ...)

It converts monolithic functions into fixed-size serverless functions.

  • The monolithic functions’ parameters are mapped to a single event parameter in order to adhere to AWS Lambda serverless functions’ signature.
  • The monolithic functions’ return statements change to match the shape of the response expected by the platform, by creating an object with a status code (200) and a body that contains a serialized version of the initially returned value.
  • Early return statements and throw statements are modified similarly, but the status code represents a client error (400).

It has no mandatory parameters, however, all the specified arguments will be passed as metadata for the function deployment.

Input:

/** $Fixed(timeout: 10) */
export async function foo(id) {
  if (!isValid(id)) {
    throw new Error('Something went wrong')
  }

  const data = await query()

  return data
}

Output:

/** $Fixed(timeout: 10) */
export async function foo(event) {
  const id = event.id

  if (!isValid(id)) {
    return {
      statusCode: 400,
      body: JSON.stringify({
        error: 'Something went wrong',
      }),
    }
  }

  const data = await query()

  return {
    statusCode: 200,
    body: JSON.stringify(data),
  }
}
functions:
  foo:
    handler: output/source.foo
  timeout: 10 # default is 6 seconds

$TrackMetrics(namespace: string, metricName: string, metricValue?: ts.Expression)

It generates code that monitors and logs the functions’ resource usage by also importing the necessary dependencies, i.e., for AWS Lambda it uses and injects the CloudWatch dependency.

Input:

import { query } from './local'

/**
 * $TrackMetrics(namespace: 'shop', metricName: 'sell', metricValue: size)
 */
export async function processOrder(id) {
  const order = await query(id)
  const size = order.size
  // ...more logic...
  return size
}

Output:

import { query } from './local'
import { CloudWatch } from 'aws-sdk'

/**
 * $TrackMetrics(namespace: 'shop', metricName: 'sell', metricValue: size)
 */
export async function processOrder(id) {
  const order = await query(id)
  const size = order.size
  await new CloudWatch()
    .putMetricData({
      Namespace: 'shop',
      MetricData: [
        {
          MetricName: 'sell',
          Timestamp: new Date(),
          Value: size,
        },
      ],
    })
    .promise()
  // ...more logic...
  return size
}

$HttpApi(method: string, path: string, ...)

It generates the metadata needed to make the function available as an HTTP endpoint.

$Scheduled(rate: string, ...)

It generates the metadata needed to make the function run at specific dates or periodic intervals.

Custom annotations

Fenrir endorses the creation of new annotations to fit custom requirements and usages. In order to inform Fenrir of the new annotation name and its transformer implementation, the configuration file (i.e., fernrir.config.json) must be updated:

{
  "annotations": {
    "IoT": "annotations/iot-impl.ts"
  }
}

To write a transformer, you can import useful methods from fenrir-base.

import type { CustomTransformer } from 'fenrir-base'

type IotTransfomer = CustomTransformer<'IoT', { sql: string }>

const transformer: IotTransfomer = (node, context, annotation) => {
  // ...implementation...
}

// custom transformers must be exported as `default`
export default transformer

Development

To build all the packages:

turbo build

Packages - core

Contains the transpiler and its transformers.

Run the input code for testing the transformations:

$ turbo run dev --filter core

Bundle into a dist folder:

$ turbo run build --filter core

Packages - cli

Self-explanatory. It relies on the core package.

About

A transpiler that simplifies the development of serverless functions.

Topics

Resources

Stars

Watchers

Forks