Home > Enterprise >  `setDoc` write to Firestore from TypeScript Functions messes up directory structure on `npm run buil
`setDoc` write to Firestore from TypeScript Functions messes up directory structure on `npm run buil

Time:12-19

Transpiling TypeScript creates a new, incorrect directory structure when a Firebase Function writes to Firestore with setDoc or updateDoc.

This code transpiles and runs correctly:

index.ts

import * as functions from "firebase-functions";
export const MakeUppercase = functions.firestore.document('Messages/{docId}').onCreate((snap, context) => {
    try {
      const original = snap.data().original;
      const uppercase = original.toUpperCase();
      return snap.ref.set({ uppercase }, { merge: true }); // writes to the same document
    } catch (error) {
      console.error(error); // emulator always throws an "unhandled error": "Your function timed out after ~60s."
      return 0;
    }
  });

The first time the function is transpiled in a new directory the transpiler makes this directory structure:

myproject
├── environments
│   └── environment.ts
├── functions
│   ├── lib
│   │   ├── index.js
│   │   ├── index.js.map
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json
│   ├── src
│   │   ├── index.ts
│   └── tsconfig.json

That directory structure is correct. The main in package.json is

lib/index.js

Let's change the code a little and use setDoc to write to a different directory in Firestore:

index.ts

import * as functions from "firebase-functions";
import { initializeApp } from "firebase/app";
import { getFirestore, setDoc, doc } from "firebase/firestore";
import { environment } from '../../environments/environment';
const firebaseApp = initializeApp(environment.firebase);
const firestore = getFirestore(firebaseApp);

export const MakeUppercase = functions.firestore.document('Messages/{docId}').onCreate((snap, context) => {
    try {
      const original = snap.data().original;
      const uppercase = original.toUpperCase();

      return setDoc(
        doc(firestore, 'AnotherCollection', context.params.docId),
        { uppercase }, { merge: true }
      );

    } catch (error) {
      console.error(error); // emulator always throws an "unhandled error": "Your function timed out after ~60s."
      return 0;
    }
  });

I transpile the function with npm run build in the functions directory. The directory structure changes to:

myproject
├── environments
│   └── environment.ts
├── functions
│   ├── lib
│   │   ├── environments
│   │   │   ├── environment.js
│   │   │   └── environment.js.map
│   │   ├── functions
│   │   │   └── src
│   │   │       ├── index.js
│   │   │       ├── index.js.map
│   │   ├── index.js
│   │   ├── index.js.map
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json
│   ├── src
│   │   ├── index.ts
│   └── tsconfig.json

The transpiler added three new directories and four new files. It's exposing my apiKey and other credentials. To make the new function run I changed main to

lib/functions/src/index.js

Now the new code runs but it won't write to Firestore. I'm getting PERMISSION_DENIED: Missing or insufficient permissions. errors. Here's my rules:

firestore-rules

rules_version = "2";
service cloud.firestore {
  match /databases/{database}/documents {
  
    match /{document=**} {
      allow read, write;
    }
  }
}

Don't worry, those rules are for the emulator, not for production.

A couple more weird things. Visual Studio Code shows a single directory functions/src, not two, nested directories. (MacOS shows two nested directories.)

I can deploy the new function to Cloud Functions with firebase deploy but gcloud functions deploy won't deploy the function. It throws an error that it can't find index.js:

lib/functions/src/index.js does not exist;

What is going on here??? To test this I spun up four new Firebase Functions directories. I tried different functions. It happens every time I use setDoc or updateDoc, and doesn't happen if the code doesn't include setDoc or updateDoc.

Here's my tsconfig.json:

tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "outDir": "lib",
    "sourceMap": true,
    "strict": true,
    "target": "es2017"
  },
  "compileOnSave": true,
  "include": [
    "src"
  ]
}

CodePudding user response:

Thanks, @Doug Stevenson! What I'm reading in your comment is that Firebase Cloud Functions can't use the Firebase 9 syntax setDoc and updateDoc for Firestore, and uploadString etc. for Storage. We have to use the old school Firebase syntax, like this:

index.js

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

export const MakeUppercase = functions.firestore.document('Messages/{docId}').onCreate(async (snap: any, context: any) => {
    try {
        const original = snap.data().original;
        const uppercase = original.toUpperCase();
        await admin.firestore().collection('AnotherCollection').add({uppercase: uppercase});
        return uppercase;
    } catch (error) {
        console.error(error); // emulator always throws an "unhandled error": "Your function timed out after ~60s."
        return 0;
    }
});

That transpiled without creating new directories or files, and ran without a problem.

  • Related