firebase-functions
firebase-functions copied to clipboard
`functions.region` seem to do a global update
[REQUIRED] Describe your environment
- Operating System version: Linux
- Browser version: n/a
- Firebase SDK version: v9
- Firebase Product: functions
[REQUIRED] Describe the problem
Steps to reproduce:
- we create a util called
localized-functions.jswhich exports a version of the "functions" module with the default region like this:
const DEFAULT_FUNCTIONS_REGION = "europe-west3";
import "firebase-functions/lib/logger/compat";
import * as _functions from "firebase-functions";
export const functions = _functions.region(DEFAULT_FUNCTIONS_REGION);
- we use it like this in most methods:
# file1.js
import { functions } from "./localized-functions";
export const myTrigger = functions.https.onCall(...)
- but if we do something like this somewhere:
# file2.js
import { functions } from "./localized-functions";
export const myOtherTrigger = functions.region("europe-west1").https.onCall(...)
this seems to impact globally the "functions" module, and sometimes when deploying cloud functions it indicates that the function in file1 is to be deployed in europe-west1 (rather than europe-west3). More precisely, it tells us that the function myTrigger (europe-west3) exists in the cloud functions from a previous deployment, but no longer exists locally, and asks me whether I want to delete it
I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.
Transferring this issue to the correct repo.
Same for me, upgraded to 4.0.1 and this started happening.
My syntax:
export const DEFAULT_FUNCTIONS_REGION = "europe-west1";
export const functions = _functions
.runWith(RUNTIME_OPTS)
.region(DEFAULT_FUNCTIONS_REGION);
export const onCreateFirebaseUser = functions.auth
.user()
.onCreate(async (user) => {
...;
});
This caught me out too, but I think I understand why this happens, now. The best example to use here is @arnoutvandervorst's one - I think the original one at the top of the thread has been reduced a bit too much. In that example, when this part of the call happens:
export const functions = _functions
.runWith(RUNTIME_OPTS)
the RUNTIME_OPTS object becomes the options object attached to the FunctionBuilder. This is because the FunctionBuilder's runWith(opts) takes the user-supplied options object by reference, here, rather than cloning it when it's passed in e.g. with a spread operator:
https://github.com/firebase/firebase-functions/blob/a64fd48fac12cd6293885ec8402a334037470c44/src/v1/function-builder.ts#L283-L290
This means that, later, when the .region("some-region1") call happens, that RUNTIME_OPTS object is internally mutated by the FunctionBuilder! All other instances of FunctionBuilder that were passed the same object are seeing the same exact object instance, and that mutation will affect them all. (This is the unexpected "global update" referred to by the issue title.)
This doesn't usually happen with the methods called in the opposite order (i.e. functions.region("some-region-1").runWith({ ... })), because that version of runWith (the method) does copy the values:
https://github.com/firebase/firebase-functions/blob/a64fd48fac12cd6293885ec8402a334037470c44/src/v1/function-builder.ts#L326-L329
Hope that helps explain what the problem is here.
I'd strongly recommend a change to lines 285, 290 and 304, to assign a new cloned options object, rather than allow mutation of a function parameter - that will make this issue disappear. Changing the type slightly (maybe DeploymentOptions to Readonly<DeploymentOptions>) may also have helped to enforce that the builder was always assigning a new options object rather than mutating one the user passed in.
A full example is:
export const defaultOptions = {
maxInstances: 3,
region: "us-west2"
};
export const onCreateFirebaseUser = functions
.runWith(defaultOptions)
.region("europe-west1")
.auth
.user()
.onCreate(async (user) => {
// ...
});
console.log(defaultOptions); // Prints: { maxInstances: 3, region: "europe-west1" }