firebase_functions_interop 1.0.0+1

  • README.md
  • CHANGELOG.md
  • Example
  • Installing
  • Versions
  • 88

Build Status Pub Gitter

Write Firebase Cloud functions in Dart, run in Node.js. This is an early development preview, open-source project.

Using 1.0.0-dev.* version? See UPGRADING.md for details on breaking changes and upgrade instructions.

What is this?

firebase_functions_interop provides interoperability layer for Firebase Functions Node.js SDK. Firebase functions written in Dart using this library must be compiled to JavaScript and run in Node.js. Luckily, a lot of interoperability details are handled by this library and a collections of tools from Dart SDK.

Here is a minimalistic "Hello world" example of a HTTPS cloud function:

import 'package:firebase_functions_interop/firebase_functions_interop.dart';

void main() {
  functions['helloWorld'] = functions.https.onRequest(helloWorld);
}

void helloWorld(ExpressHttpRequest request) {
  request.response.writeln('Hello world');
  request.response.close();
}

Status

Version 1.0.0 is considered stable though not feature complete. Below is status report of already implemented functionality by namespace:

  • [x] functions
  • [x] functions.config
  • [ ] functions.analytics
  • [x] functions.auth
  • [x] functions.firestore 🔥
  • [x] functions.database
  • [x] functions.https
  • [x] functions.pubsub
  • [x] functions.storage
  • [ ] functions.remoteConfig

Usage

Make sure you have Firebase CLI installed as well as a Firebase account and a test app.

See Getting started for more details.

1. Create Initial Project

$ mkdir myproject
$ cd myproject
$ firebase init functions

This creates functions subdirectory in your project's root which contains standard Node.js package structure with package.json and index.js files.

2. Initialize Dart Project

Go to functions sub-folder and add a pubspec.yaml with following contents:

name: myproject_functions
description: My project functions
version: 0.0.1

environment:
  sdk: '>=2.0.0-dev <3.0.0'

dependencies:
  # Firebase Functions bindings
  firebase_functions_interop: ^1.0.0

dev_dependencies:
  # Needed to compile Dart to valid Node.js module.
  build_runner: ^1.0.0
  build_node_compilers: ^0.2.0

Then run pub get to install dependencies.

3 Write a Basic Function

Create functions/node/index.dart and type in something like this:

import 'package:firebase_functions_interop/firebase_functions_interop.dart';

void main() {
  functions['helloWorld'] = functions.https.onRequest(helloWorld);
}

void helloWorld(ExpressHttpRequest request) {
  request.response.writeln('Hello world');
  request.response.close();
}

Copy-pasting also works.

4. Build your Function(s)

Version 1.0.0 of this library depends on Dart 2 and the new build_runner package. Integration with dart2js and DDC compilers is provided by build_node_compilers package which should already be in dev_dependencies in pubspec.yaml (see step 2).

Create functions/build.yaml file with following contents:

targets:
  $default:
    sources:
      - "node/**"
      - "lib/**"
    builders:
      build_node_compilers|entrypoint:
        generate_for:
        - node/**
        options:
          compiler: dart2js
          # List any dart2js specific args here, or omit it.
          dart2js_args:
          - --minify

By default build_runner compiles with DDC which is not supported by this library at this point. Above configuration makes it compile Dart with dart2js.

To build run following:

$ cd functions
$ pub run build_runner build --output=build

5. Deploy

The result of pub run is located in functions/build/node/index.dart.js.

In your functions/package.json, set the main field to point to this file:

{
    "...": "...",
    "main": "build/node/index.dart.js"
}

Alternatively, you can replace the default index.js with the built version:

$ cp functions/build/node/index.dart.js functions/index.js

Deploy using Firebase CLI:

$ firebase deploy --only functions

6. Test it

You can navigate to the new HTTPS function's URL printed out by the deploy command. For the Realtime Database function, login to the Firebase Console and try changing values under /messages/{randomValue}/original.

7. Scripts (optional)

You can use NPM scripts to simplify the work-flow of serving and deploying functions.

Update your functions/package.json to be like so:

{
	"...": "...",
    "scripts": {
         "build": "pub run build_runner build --output=build",
         "watch": "pub run build_runner watch --output=build",

        "preserve": "npm run build",
        "serve": "firebase serve --only functions",

        "predeploy": "npm run build",
        "deploy": "firebase deploy --only functions",

        "preshell": "npm run build",
        "shell": "firebase experimental:functions:shell",

        "...": "..."
    }
}

Examples

HTTPS Function

import 'package:firebase_functions_interop/firebase_functions_interop.dart';

void main() {
  functions['helloWorld'] = functions.https.onRequest(helloWorld);
}

void helloWorld(ExpressHttpRequest request) {
  request.response.writeln('Hello world');
  request.response.close();
}

Realtime Database Function

void main() {
  functions['makeUppercase'] = functions.database
      .ref('/messages/{messageId}/original')
      .onWrite(makeUppercase);
}

FutureOr<void> makeUppercase(
    Change<DataSnapshot<String>> change, EventContext context) {
  final DataSnapshot<String> snapshot = change.after;
  var original = snapshot.val();
  var pushId = context.params['testId'];
  print('Uppercasing $original');
  var uppercase = pushId.toString() + ': ' + original.toUpperCase();
  return snapshot.ref.parent.child('uppercase').setValue(uppercase);
}

Firestore Function

void main() {
  functions['makeNamesUppercase'] = functions.firestore
      .document('/users/{userId}').onWrite(makeNamesUppercase)
}

FutureOr<void> makeNamesUppercase(Change<DocumentSnapshot> change, EventContext context) {
  // Since this is an update of the same document we must guard against
  // infinite cycle of this function writing, reading and writing again.
  final snapshot = change.after;
  if (snapshot.data.getString("uppercasedName") == null) {
    var original = snapshot.data.getString("name");
    print('Uppercasing $original');

    UpdateData newData = new UpdateData();
    newData.setString("uppercasedName", original.toUpperCase());

    return snapshot.reference.updateData(newData);
  }
  return null;
}

Pubsub Functions

void main() {
  functions['logPubsub'] = functions.pubsub.topic('my-topic').onPublish(logPubsub);
}

void logPubsub(Message message, EventContext context) {
  print(message.json["name"]);
}

Storage Functions

void main() {
  functions['logStorage'] = functions.storage.object().onChange(logStorage);
}

void logStorage(ObjectMetadata data, EventContext context) {
  print(data.name);
}

Auth Functions

void main() {
  functions['logAuth'] = functions.auth.user().onCreate(logAuth);
}

void logAuth(UserRecord data, EventContext context) {
  print(data.email);
}

Configuration

Firebase SDK provides a way to set and access environment variables from your Firebase functions.

Environment variables are set using Firebase CLI, e.g.:

firebase functions:config:set some_service.api_key="secret" some_service.url="https://api.example.com"

For more details see https://firebase.google.com/docs/functions/config-env.

To read these values in a Firebase function use functions.config.

Below example also uses node_http package which provides a HTTP client powered by Node.js I/O.

import 'package:firebase_functions_interop/firebase_functions_interop.dart';
import 'package:node_http/node_http.dart' as http;

void main() {
  functions['helloWorld'] = functions.https.onRequest(helloWorld);
}

void helloWorld(ExpressHttpRequest request) async {
  /// fetch env configuration
  final config = functions.config;
  final String serviceKey = config.get('someservice.key');
  final String serviceUrl = config.get('someservice.url');
  /// `http.get()` function is exposed by the `node_http` package.
  var response = await http.get("$serviceUrl?apiKey=$serviceKey");
  // do something with the response, e.g. forward response body to the client:
  request.response.write(response.body);
  request.response.close();
}

HTTPS Functions Details

Firebase uses the Express.js web framework for HTTPS functions with body-parser middleware enabled by default (documentation).

The ExpressHttpRequest exposed by this library extends standard dart:io HttpRequest interface, which means it is also a stream of bytes. However if body-parser middleware already decoded request body then listening for data on the request would hang since it's already been consumed. Use ExpressHttpRequest.body field to get decoded request body in this case.

Features and Bugs

Please file feature requests and bugs at the issue tracker.

See the development file for instructions on running the test suite.

1.0.0+1

  • Readme fixes.

1.0.0

This is the first stable release. It includes some breaking changes described below and in UPGRADING.md.

Breaking change: all static fields on FirebaseFunctions class were converted to regular instance fields. This change was required to introduce new region and runWith methods on FirebaseFunctions.

To migrate your existing code simply replace FirebaseFunctions with functions. E.g.

void main() {
  // Before
  functions['myfunc'] = FirebaseFunctions.https.onRequest(myHandler);
  // After
  functions['myfunc'] = functions.https.onRequest(myHandler);
}
  • Added: FirebaseFunctions.region and FirebaseFunctions.runWith methods.
  • Removed: Deprecated firebaseFunctions library field, use functions instead.

1.0.0-dev.11.0

  • Log console error when callable response cannot be jsify-ed, with details about the data (simplifies debugging).

1.0.0-dev.10.0

  • Breaking change: upgraded to JS module firebase-functions v2.0.5 which introduced breaking changes in DocumentSnapshot.createTime, DocumentSnapshot.updateTime
  • Upgraded to firebase_admin_interop v1.0.0-dev.20.0 which also introduced breaking changes around Timestamps. See changelog and readme in firebase_admin_interop for more details.

1.0.0-dev.9.0

  • Fixed: EventContext.resource type changed from String to object.
  • Misc: removed strong-mode analyzer option from test functions.

1.0.0-dev.8.0

  • Fixed analysis warnings and declared support for Dart 2 stable.

1.0.0-dev.7.0

  • Internal: upgraded example functions to use latest build_runner.
  • Internal: strong mode fix for tests setup script.

1.0.0-dev.6.0

  • Fixed: strong mode issue in Firestore EventContext after deploying tests with --preview-dart-2 instead of --checked.
  • Fixed: strong mode issue in Database event handlers instantiating DataSnapshot with proper generic type argument.

1.0.0-dev.5.0

  • Added: FirebaseFunctions.https.onCall as well as HttpsError and CallableContext.

1.0.0-dev.4.0

  • Breaking: Upgraded to Functions SDK 1.0.1 and Admin SDK 5.12.0 Official migration guide is located here: https://firebase.google.com/docs/functions/beta-v1-diff Changes in this library are identical and slightly adapted to Dart semantics:
    • admin.config().firebase field has been removed
    • Background functions (that is everything except HTTPS) now expect two arguments data and context instead of single event argument.
    • See UPGRADING.md for more details and instructions. Also check updated examples in example/folder.

1.0.0-dev.3.0

  • Added: Pubsub (#10), Storage (#12) and Auth (#17) triggers support.

1.0.0-dev.2.0

  • Fixed: expose Firestore functions namespace in FirebaseFunctions.firestore (#8).
  • Other: update readme and development docs (#8).

1.0.0-dev.1.0

  • Depends on Dart SDK >= 2.0.0-dev.19.0.
  • Depends on firebase_admin_interop >= 1.0.0-dev.1.0 (as well as node_* packages).
  • Breaking: Removed built_value support.
  • Added Firestore triggers support.
  • Deprecated firebaseFunctions, to be replaced with shorter functions.
  • Breaking: firebaseFunctions.https is now static const. Use FirebaseFunctions.https.
  • Breaking: firebaseFunctions.database is now static const. Use FirebaseFunctions.database.

0.1.0-beta.3

  • Fixed dependency constraints

0.1.0-beta.2

  • Fixed: RefBuilder onCreate, onUpdate and onDelete were subscribing to JS onWrite (see #5)

0.1.0-beta.1

This version marks first attempt to stabilize API layer provided by this library, which (ironically) means there are some breaking changes.

  • Updated for node_interop: 0.1.0-beta.
  • Updated for firebase_admin_interop: 0.1.0-beta.
  • Removed stringify from bindings, use jsonStringify from node_interop instead (available since 0.1.0-beta.6).
  • Reorganized bindings in single file.
  • Finalized bindings for HTTPS and Realtime Database functions.
  • Added support for built_value serializers in Realtime Database functions
  • HTTPS functions onRequest method now accepts handler function with single parameter of type HttpRequest (from node_interop/http). This request is fully compatible with "dart:io" and acts mostly as a proxy to JS native request and response objects. This should also make it easier to build integrations with Dart server-side web frameworks.
  • Gitter: https://gitter.im/pulyaevskiy/firebase-functions-interop

0.0.4

  • Added <0.1.0 constraint on node_interop dependency.

0.0.3

  • Added toJson to DeltaSnapshot.
  • Added top-level firebaseFunctions getter.
  • Deprecated FirebaseFunctions constructor. Use firebaseFunctions instead.
  • Implemented firebaseFunctions.config().
  • Added DEVELOPMENT.md docs.

0.0.2

  • Added generics to Event class (#3)
  • Added basic integration testing infrastructure
  • Minor dartdoc updates

0.0.1

  • Initial version

example/main.dart

// Copyright (c) 2017, Anatoly Pulyaevskiy. All rights reserved. Use of this source code
// is governed by a BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'package:firebase_functions_interop/firebase_functions_interop.dart';

void main() {
  /// You can export a function by setting a key on global [functions]
  /// object.
  ///
  /// For HTTPS functions the key is also a URL path prefix, so in below
  /// example `helloWorld` function will be available at `/helloWorld`
  /// URL path and it will also handle all paths under this prefix, e.g.
  /// `/helloWorld/any/number/of/sections`.
  functions['helloWorld'] = functions.https.onRequest(helloWorld);
  functions['makeUppercase'] =
      functions.database.ref('/tests/{testId}/original').onWrite(makeUppercase);
  functions['makeNamesUppercase'] = functions.firestore
      .document('/users/{userId}')
      .onWrite(makeNamesUppercase);
  functions['logPubsub'] =
      functions.pubsub.topic('my-topic').onPublish(logPubsub);
  functions['logStorage'] = functions.storage.object().onFinalize(logStorage);
  functions['logAuth'] = functions.auth.user().onCreate(logAuth);
}

/// Example Realtime Database function.
FutureOr<void> makeUppercase(
    Change<DataSnapshot<String>> change, EventContext context) {
  final DataSnapshot<String> snapshot = change.after;
  var original = snapshot.val();
  var pushId = context.params['testId'];
  print('Uppercasing $original');
  var uppercase = pushId.toString() + ': ' + original.toUpperCase();
  return snapshot.ref.parent.child('uppercase').setValue(uppercase);
}

/// Example Firestore
FutureOr<void> makeNamesUppercase(
    Change<DocumentSnapshot> change, EventContext context) {
  // Since this is an update of the same document we must guard against
  // infinite cycle of this function writing, reading and writing again.
  final snapshot = change.after;
  if (snapshot.data.getString("uppercasedName") == null) {
    var original = snapshot.data.getString("name");
    print('Uppercasing $original');

    UpdateData newData = new UpdateData();
    newData.setString("uppercasedName", original.toUpperCase());

    return snapshot.reference.updateData(newData);
  }
  return null;
}

/// Example Pubsub
void logPubsub(Message message, EventContext context) {
  print(message.json["name"]);
}

/// Example Storage
void logStorage(ObjectMetadata data, EventContext context) {
  print(data.name);
}

/// Example Auth
void logAuth(UserRecord data, EventContext context) {
  print(data.email);
}

/// Example HTTPS function.
Future<void> helloWorld(ExpressHttpRequest request) async {
  try {
    /// If there are any config parameters we can access them as follows:
    var config = functions.config;
    var serviceKey = config.get('someservice.key');
    var serviceUrl = config.get('someservice.url');
    // Don't do this on a real project:
    print('Service key: $serviceKey, service URL: $serviceUrl');

    /// The provided [request] is fully compatible with "dart:io" `HttpRequest`
    /// including the fact that it's a valid Dart `Stream`.
    /// Note though that Firebase uses body-parser expressjs middleware which
    /// decodes request body for some common content-types (json included).
    /// In such cases use `request.body` which contains decoded body.
    String name = request.requestedUri.queryParameters['name'];
    if (name != null) {
      // We can also write to Realtime Database right here:
      var admin = FirebaseAdmin.instance;
      var app = admin.initializeApp();
      var database = app.database();
      await database.ref('/tests/some-path').setValue(name);
    }
    request.response.writeln('Hello world');
  } finally {
    request.response.close();
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  firebase_functions_interop: ^1.0.0+1

2. Install it

You can install packages from the command line:

with pub:


$ pub get

Alternatively, your editor might support pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:firebase_functions_interop/firebase_functions_interop.dart';
  
Version Uploaded Documentation Archive
1.0.0+1 Dec 6, 2018 Go to the documentation of firebase_functions_interop 1.0.0+1 Download firebase_functions_interop 1.0.0+1 archive
1.0.0 Dec 6, 2018 Go to the documentation of firebase_functions_interop 1.0.0 Download firebase_functions_interop 1.0.0 archive
0.0.4+1 Oct 9, 2017 Go to the documentation of firebase_functions_interop 0.0.4+1 Download firebase_functions_interop 0.0.4+1 archive
0.0.4 Oct 8, 2017 Go to the documentation of firebase_functions_interop 0.0.4 Download firebase_functions_interop 0.0.4 archive
0.0.3 Sep 30, 2017 Go to the documentation of firebase_functions_interop 0.0.3 Download firebase_functions_interop 0.0.3 archive
0.0.2 Aug 1, 2017 Go to the documentation of firebase_functions_interop 0.0.2 Download firebase_functions_interop 0.0.2 archive
0.0.1 Jun 18, 2017 Go to the documentation of firebase_functions_interop 0.0.1 Download firebase_functions_interop 0.0.1 archive
1.0.0-dev.11.0 Oct 22, 2018 Go to the documentation of firebase_functions_interop 1.0.0-dev.11.0 Download firebase_functions_interop 1.0.0-dev.11.0 archive
1.0.0-dev.10.0 Oct 5, 2018 Go to the documentation of firebase_functions_interop 1.0.0-dev.10.0 Download firebase_functions_interop 1.0.0-dev.10.0 archive
1.0.0-dev.9.0 Aug 22, 2018 Go to the documentation of firebase_functions_interop 1.0.0-dev.9.0 Download firebase_functions_interop 1.0.0-dev.9.0 archive

All 22 versions...

Popularity:
Describes how popular the package is relative to other packages. [more]
77
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
88
Learn more about scoring.

We analyzed this package on Dec 6, 2018, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.1.0
  • pana: 0.12.7

Platforms

Detected platforms: web, other

Primary library: package:firebase_functions_interop/firebase_functions_interop.dart with components: js, io.

Health suggestions

Format lib/src/bindings.dart.

Run dartfmt to format lib/src/bindings.dart.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.0.0-dev <3.0.0
firebase_admin_interop ^1.0.0 1.1.0
js ^0.6.1 0.6.1+1
meta ^1.1.0 1.1.6
node_http ^1.0.0-dev 1.0.0-dev.10.0
node_interop ^1.0.0 1.0.0
node_io ^1.0.0-dev 1.0.0-dev.10.0
Transitive dependencies
async 2.0.8
charcode 1.1.2
collection 1.14.11
http 0.11.3+17 0.12.0
http_parser 3.1.3
path 1.6.2
quiver_hashcode 2.0.0
source_span 1.4.1
string_scanner 1.0.4
typed_data 1.1.6
Dev dependencies
test ^1.0.0