JSPM

  • Created
  • Published
  • Downloads 2
  • Score
    100M100P100Q30871F
  • License MIT

Create Firebase Cloud Functions from functions marked with decorators

Package Exports

  • firefuncs

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (firefuncs) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

firefuncs

Create Firebase cloud functions from functions marked with decorators.

npm i firefuncs

To demonstrate the use of firefuncs, let's create Firebase cloud functions with and without firefuncs.

Creating Firebase cloud functions without firefuncs

For a start you may have all your cloud functions in one file.

index.ts

import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';

exports.helloWorld = functions.https.onRequest((request, response) => {
 response.send('Hello from Firebase!\n\n');
});

exports.initializeApp = functions.https.onRequest(async (request, response) => {
 admin.initializeApp(functions.config().firebase);
});

Over time that file grows and that necessitates breaking it into smaller files. You may do so as shown below.

hello.functions.ts

import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';

export const helloWorld = (request, response) => {
 response.send('Hello from Firebase!\n\n');
};

export const initializeApp = async (request, response) => {
 admin.initializeApp(functions.config().firebase);
};

index.ts

// import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
import { helloWorld, initializeApp } from './hello.functions';

exports.helloWorld = functions.https.onRequest(helloWorld);

exports.initializeApp = functions.https.onRequest(initializeApp);

While this is a lot better than the first example, it still requires that index.ts be modified every time new functions are added or existing ones removed.

To get a solution where index.ts never needs to change even as functions are added or removed, we need a way of specifying what a function is meant for; we need a way of marking or decorating a function with its purpose. Enter decorators!

Creating Firebase cloud functions using firefuncs

firefuncs makes use of decorators, an experimental TypeScript feature. Ensure you enable experimentalDecorators and emitDecoratorMetadata options in tsconfig.json.

{
  "compilerOptions": {
    /* Other Options */

    /* Experimental Options */
    "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
    "emitDecoratorMetadata": true         /* Enables experimental support for emitting type metadata for decorators. */
  }
}

Install firefuncs

npm i firefuncs

Move your functions into classes and decorate them with the appropriate decorators. In the example below, we want our functions to handle HTTP requests, so we decorate them with the onRequest decorator.

functions/hello.functions.ts

import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
import { onRequest } from 'firefuncs';

export class Hello {
    @onRequest()
    public helloWorld(request, response) {
        response.send('Hello from Firebase!\n\n');
    }
    @onRequest()
    public initializeApp(request, response) {
        admin.initializeApp(functions.config().firebase);
    }
}

To create cloud functions, use the getFunctions function from firefuncs, supplying a glob pattern that matches all the files containing JavaScript functions that should be converted to Firebase cloud functions.

index.ts

import { getFunctions } from 'firefuncs';

// search the 'functions' directory and all its subdirectories
// for JavaScript or TypeScript files
const funcs = getFunctions(__dirname + '/functions/**/*.{js,ts}');

// just writing 'exports = funcs;' won't work
// you have to loop through the keys in 'funcs'
// and assign their values to matching keys in 'exports'
Object.keys(funcs).forEach(key => {
    exports[key] = funcs[key];
});

Now you can add more functions in the /functions directory and never need to edit index.ts for the added functions to be discovered and converted to Firebase cloud functions.

Firefuncs decorators

onRequest

onRequest(path: string = '/', options?: RequestOptions, ...regions: Region[])

The onRequest decorator specifies that a function should handle an HTTP request.

parameters

  • path
    This is a string parameter that represents an Express-style route path, like /users/:id.

  • options
    This is an optional RequestOptions parameter for passing in extra data to the decorator.

class RequestOptions {
    method?: RequestMethodType;
    middleware?: typeof RequestMiddleware[];
}

class RequestMiddleware {
    middleware(req, res, next) {}
}

type RequestMethodType = "delete" | "get" | "options" | "post" | "put"
  • regions
    This is an optional list of regions where the function should be deployed to.

example

import { onRequest } from 'firefuncs';

export class Records {
    @onRequest()
    public async hello(req, res) {
        res.send('Hello World!');
    }

    @onRequest('/', {
        method: 'post',
        middleware: [InitMiddleware, AuthMiddleware]
    }, 'europe-west1')
    public async save(req, res) {
        try {
            const db = req.firestore.db;
            await db.collection('records').add({ ...req.body });
            res.send('Added records successfully');
        } catch (error) {
            res.send({ error })
        }
    }
}

class AuthClientMiddleware {
    async middleware(req, res, next) {
        console.log('Authenticate the request here');
    }
}

class InitMiddleware {
    async middleware(req, res, next) {
        console.log('Initialize the Firebase app here');
    }
}

Note: A middleware is any class that has a method named middleware with the same signature as an Express middleware.

onFirestoreCreate, onFirestoreDelete, onFirestoreUpdate, onFirestoreWrite

onFirestoreCreate(path: string, ...regions: Region[])
onFirestoreDelete(path: string, ...regions: Region[])
onFirestoreUpdate(path: string, ...regions: Region[])
onFirestoreWrite(path: string, ...regions: Region[])

These decorators specify that a function should handle events in Cloud Firestore.

parameters

  • path
    This is a string parameter that represents the path to the document that is being listened to.

  • regions
    This is an optional list of regions where the function should be deployed to.

example

import { onFirestoreCreate, onFirestoreDelete, onFirestoreUpdate, onFirestoreWrite } from 'firefuncs';

export class FirestoreFunctions {
    @onFirestoreCreate('users/{userId}')
    public async listenForWhenUserIsCreated(snapshot, context) {
        console.log(`New data at users/${context.params.userId} is:`);
        console.log(snapshot.data());
    }
    @onFirestoreDelete('users/{userId}')
    public async listenForWhenUserIsDeleted(snapshot, context) {
        console.log(`Deleted data at users/${context.params.userId} is:`);
        console.log(snapshot.data());
    }
    @onFirestoreUpdate('users/{userId}')
    public async listenForWhenUserIsUpdated(change, context) {
        console.log(`previous data at users/${context.params.userId} is:`);
        console.log(change.before.data());
        console.log(`New data at users/${context.params.userId} is:`);
        console.log(change.after.data());
    }
    @onFirestoreWrite('users/marie')
    public async listenForWhenUserMarieIsWritten(change, context) {
        console.log('previous data at users/marie is:');
        console.log(change.before.data());
        // a write operation could be a create, update or delete
        // check to see if the document still exists
        console.log('New data at users/marie is:');
        console.log(change.after.exists ? change.after.data() : undefined);
    }
    @onFirestoreWrite('users/{userId}')
    public async listenForWhenUserIsWritten(change, context) {
        console.log(`previous data at users/${context.params.userId} is:`);
        console.log(change.before.data());
        // a write operation could be a create, update or delete
        // check to see if the document still exists
        console.log(`New data at users/${context.params.userId} is:`);
        console.log(change.after.exists ? change.after.data() : undefined);
    }
}

onSchedule

onSchedule(schedule: string, options?: ScheduleOptions, ...regions: Region[])

The onSchedule decorator specifies that a function should run at specified times.

parameters

  • schedule
    This is a string parameter that represents when you want the function to run. Both Unix Crontab and AppEngine syntax are supported.

  • options
    This is an optional ScheduleOptions parameter for passing in extra data to the decorator.

class ScheduleOptions {
    timeZone?: string;
}
  • regions
    This is an optional list of regions where the function should be deployed to.

example

import { onSchedule } from 'firefuncs';

export class Schedules {
    @onSchedule('every 5 minutes')
    public async scheduledFunction(context) {
        console.log('This will be run every 5 minutes!');
        return null;
    }
    @onSchedule('5 11 * * *', {
        timeZone: 'America/New_York'
    }, 'europe-west1')
    public async scheduledFunctionCrontab(context) {
        console.log('This will be run every day at 11:05 AM Eastern!');
        return null;
    }
}

onCall

onCall(...regions: Region[])

The onCall decorator specifies that a function should be callable directly from a Firebase app.

parameters

  • regions
    This is an optional list of regions where the function should be deployed to.