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.