Share Typescript Interfaces inside a Firebase project

Viewed 825

I am working on a Firebase project where the frontend is written in Angular. I am using the @angular/fire library.

I defined several interfaces where I use firebase and firestore types.

e.g

   export interface SchoolAddress {
     street: string;
     city: string;
     state: string;
     region: string;
     zip: string;
     note: string;
     position?: firestore.GeoPoint;
  }

Now I would like to share this interface between the frontend in Angular and a backend Cloud Function written on top of the firebase SDK.

The problem is so far I was unable to import types like "GeoPoint"in a way that works with both the two projects

import { firestore } from 'firebase';    

Works on the Angular app but not on the backend, the compilation fails.

Angular app package.json

...
"dependencies": {
    "@angular/animations": "~10.1.6",
    "@angular/cdk": "^10.2.5",
    "@angular/common": "~10.1.6",
    "@angular/compiler": "~10.1.6",
    "@angular/core": "~10.1.6",
    "@angular/fire": "^6.0.3",
    "@angular/forms": "~10.1.6",
    "@angular/localize": "^10.1.6",
    "@angular/material": "^10.2.5",
    "@angular/platform-browser": "~10.1.6",
    "@angular/platform-browser-dynamic": "~10.1.6",
    "@angular/router": "~10.1.6",
    "firebase": "^7.13.1",
    "ngx-wig": "^10.0.1",
    "rxjs": "~6.5.4",
    "tslib": "^2.0.0",
    "zone.js": "~0.10.2"
  },
...

Backend pakage.json

...
"dependencies": {
    "firebase-admin": "^8.10.0",
    "firebase-functions": "^3.6.1"
  },
...

I do not want to import the full firebase package in the BE if not needed only to have one type definition. What is the best way to do it?

2 Answers

The APIs and interfaces are different between both the web client and backend SDKs. They don't share common definitions for Firestore data types, so there's nothing you can say in your interface that would compile and work for both, short of having both definitions imported in all of your code, and using a union type to accept both of them.

To be specific, the firebase module for web clients exports firebase.firestore.GeoPoint, whereas firebase-admin re-exports the Google Cloud SDK's firestore.GeoPoint. They simply aren't the same object, and you can't coerce them to actually be the same. The same is true for other types such as FieldValue and Timestamp - they are simply different types between different libraries.

Probably the best you can do here is create your own abstraction layer that can be implemented by both SDKs. So, you would introduce your own GeoPoint interface, and provide objects behind the scenes that implement that interface no matter which SDK generated the document. It's really a lot of work just to share some interfaces between front and backend, but that's how it'll have to go if you're unwilling to import the SDKs on both sides.

I just got an Idea, not sure if that helps. What if we use generic to decide GeoPoint

export interface SchoolAddress<T> {
     street: string;
     city: string;
     state: string;
     region: string;
     zip: string;
     note: string;
     position?: T;
  }

project having firebase admin will use this interface as

import * as admin from "firebase-admin";
const schoolAddress:SchoolAddress<admin.firestore.GeoPoint>

project having normal firestore will use as

import { firestore } from 'firebase';  
const schoolAddress:SchoolAddress<firestore.GeoPoint>
Related