From 022bc683cef1bcdac43c05c9e3b9810ea8024075 Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Tue, 20 Oct 2026 14:35:56 +0100 Subject: [PATCH 1/3] docs(delete-user-data): say functions deploy to us-east1 unless a region is set The README said the kit's functions deploy to us-central1 when no region is set. The Firebase CLI deploys them to us-east1. Document how to set the region with setGlobalOptions in a file imported before the kit, and add a test that the kit sets no region and follows global options. --- kits/delete-user-data/CHANGELOG.md | 2 + kits/delete-user-data/README.md | 48 ++++++++++++++++++++-- kits/delete-user-data/tests/region.test.ts | 44 ++++++++++++++++++++ 3 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 kits/delete-user-data/tests/region.test.ts diff --git a/kits/delete-user-data/CHANGELOG.md b/kits/delete-user-data/CHANGELOG.md index 0e40c6a6f..93340f89b 100644 --- a/kits/delete-user-data/CHANGELOG.md +++ b/kits/delete-user-data/CHANGELOG.md @@ -1,3 +1,5 @@ +- docs: the README said the functions deploy to `us-central1` when no region is set. The Firebase CLI deploys them to `us-east1`. The README now says so, recommends keeping the functions in the same region as your Firestore database, and shows how to set the region with `setGlobalOptions({ region })` in a file imported before the kit. + - fix: an Eventarc publish failure no longer fails the function. A channel that was deleted or is unreachable answers `PERMISSION_DENIED`, and the publish was awaited after the work, so the deletion completed but the invocation still failed. The publish failure is now logged as a warning and the invocation continues. Deploys also declare `eventarcpublishing.googleapis.com`, so the API is enabled on your project whether or not you use events. The failed invocation used to be redelivered, re-running a deletion that had already succeeded; it no longer is. - fix: `ENABLE_AUTO_DISCOVERY` accepts the extension's `yes` / `no` values again. The kit had declared it as a boolean param, which only counts the literal `true` as enabled, so a `.env` copied from an installed extension instance carried `yes` and silently left auto-discovery switched off. The param is now the extension's labeled `Yes` / `No` select and is read the same way the extension read it. diff --git a/kits/delete-user-data/README.md b/kits/delete-user-data/README.md index 465d7a7cd..1fa5064ab 100644 --- a/kits/delete-user-data/README.md +++ b/kits/delete-user-data/README.md @@ -58,6 +58,9 @@ and configure with a `.env` (or `.env.`). Importing the package without exporting its functions deploys nothing — the CLI only deploys what your entry file exports. +The functions deploy to `us-east1` unless you set a region. See +[Functions deploy to `us-east1` unless you set a region](#functions-deploy-to-us-east1-unless-you-set-a-region). + ## Deploy Add a `kit` stanza to your project's `firebase.json` (Firebase CLI 15.27.0 or @@ -187,11 +190,48 @@ You can also override both names with `DISCOVERY_TOPIC_NAME` and `DELETION_TOPIC_NAME`, which the extension did not allow. Change them together, since one function publishes to a topic the other is triggered by. -### Functions deploy to your default region +### Functions deploy to `us-east1` unless you set a region + +The extension deployed to the location you picked at install time +(`us-central1` by default). This kit sets no region, and with no region set +the Firebase CLI deploys its Auth- and Pub/Sub-triggered functions to +`us-east1`, not `us-central1`. + +Keep the functions in the same region as your Firestore database (for a `nam5` +database, `us-central1`), so user data is not read and deleted from another +region. + +To choose the region, call `setGlobalOptions({ region })` from +`firebase-functions/v2` before the kit's functions are defined. Put the call in +its own file and import that file before the kit: + +```ts +// functions/src/options.ts +import { setGlobalOptions } from "firebase-functions/v2"; + +setGlobalOptions({ region: "us-central1" }); +``` + +```ts +// functions/src/index.ts +import "./options.js"; + +export { + clearData, + handleSearch, + handleDeletion, +} from "@firebase-function-kits/delete-user-data"; +``` + +A `setGlobalOptions` call in `index.ts` itself can run too late. In an ES +module, `import` and `export … from` statements load the kit before any other +code in the file runs, so the kit's functions are defined before the call. A +separate file imported first works for both ES modules and CommonJS. The +options apply to every function in the codebase. -The extension deployed to the location you picked at install time. This kit -sets no region, so its functions deploy to your codebase's default -(`us-central1` unless you have changed it). +`firebase functions:kits:install` and `firebase ext:migrate` generate an entry +file that does this for you, setting `region` from a `FUNCTION_DEFAULT_REGION` +parameter. ### All functions are 2nd gen diff --git a/kits/delete-user-data/tests/region.test.ts b/kits/delete-user-data/tests/region.test.ts new file mode 100644 index 000000000..5c55f1593 --- /dev/null +++ b/kits/delete-user-data/tests/region.test.ts @@ -0,0 +1,44 @@ +/** + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, test, vi } from "vitest"; +import { setGlobalOptions } from "firebase-functions/v2"; + +const FUNCTIONS = ["clearData", "handleSearch", "handleDeletion"] as const; + +async function loadKit() { + vi.resetModules(); + return (await import("../src/index")) as Record; +} + +// The README tells users to pick a region with setGlobalOptions({ region }) +// before the kit loads, so the kit must not pin a region of its own. +describe("function region", () => { + test("is not set by the kit, so the CLI default applies", async () => { + const kit = await loadKit(); + for (const name of FUNCTIONS) { + expect(kit[name].__endpoint.region).toBeUndefined(); + } + }); + + test("follows setGlobalOptions called before the kit loads", async () => { + setGlobalOptions({ region: "europe-west1" }); + const kit = await loadKit(); + for (const name of FUNCTIONS) { + expect(kit[name].__endpoint.region).toEqual(["europe-west1"]); + } + }); +}); From 40c914d8c7941b7d7449ef162fa686a10c887346 Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Tue, 27 Oct 2026 10:15:17 +0000 Subject: [PATCH 2/3] docs(delete-user-data): read the region from FUNCTION_DEFAULT_REGION --- kits/delete-user-data/README.md | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/kits/delete-user-data/README.md b/kits/delete-user-data/README.md index 1fa5064ab..6f895b7bf 100644 --- a/kits/delete-user-data/README.md +++ b/kits/delete-user-data/README.md @@ -42,10 +42,14 @@ service account for this codebase — it conflicts with that automatic setup. ## Usage -Export the functions from your functions codebase entry: +Export the functions from your functions codebase entry, importing your +region options file first (see +[Functions deploy to `us-east1` unless you set a region](#functions-deploy-to-us-east1-unless-you-set-a-region)): ```ts // functions/src/index.ts +import "./options.js"; + export { clearData, handleSearch, @@ -58,9 +62,6 @@ and configure with a `.env` (or `.env.`). Importing the package without exporting its functions deploys nothing — the CLI only deploys what your entry file exports. -The functions deploy to `us-east1` unless you set a region. See -[Functions deploy to `us-east1` unless you set a region](#functions-deploy-to-us-east1-unless-you-set-a-region). - ## Deploy Add a `kit` stanza to your project's `firebase.json` (Firebase CLI 15.27.0 or @@ -202,14 +203,23 @@ database, `us-central1`), so user data is not read and deleted from another region. To choose the region, call `setGlobalOptions({ region })` from -`firebase-functions/v2` before the kit's functions are defined. Put the call in -its own file and import that file before the kit: +`firebase-functions/v2` before the kit's functions are defined, reading the +region from a `FUNCTION_DEFAULT_REGION` parameter as the generated entry file +does. Put the call in its own file and import that file before the kit: ```ts // functions/src/options.ts import { setGlobalOptions } from "firebase-functions/v2"; +import { defineString } from "firebase-functions/params"; -setGlobalOptions({ region: "us-central1" }); +setGlobalOptions({ + region: defineString("FUNCTION_DEFAULT_REGION", { default: "us-central1" }), +}); +``` + +```sh +# .env (or .env.) +FUNCTION_DEFAULT_REGION=us-central1 ``` ```ts @@ -229,9 +239,11 @@ code in the file runs, so the kit's functions are defined before the call. A separate file imported first works for both ES modules and CommonJS. The options apply to every function in the codebase. -`firebase functions:kits:install` and `firebase ext:migrate` generate an entry -file that does this for you, setting `region` from a `FUNCTION_DEFAULT_REGION` -parameter. +Each instance reads `FUNCTION_DEFAULT_REGION` from its own `.env`, so instances +can deploy to different regions. `firebase ext:migrate` writes it to `.env` +with the region the extension ran in, and `firebase functions:kits:install` +and `firebase ext:migrate` generate an entry file that does all of this for +you. ### All functions are 2nd gen From 14f9a122e81b2fead1cd9bb0f1e7cb4e19891701 Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Tue, 27 Oct 2026 12:46:50 +0000 Subject: [PATCH 3/3] fix(delete-user-data): default the functions to us-central1 via FUNCTION_DEFAULT_REGION --- kits/delete-user-data/CHANGELOG.md | 2 +- kits/delete-user-data/README.md | 61 +++++----------------- kits/delete-user-data/src/config.ts | 10 ++++ kits/delete-user-data/src/index.ts | 24 +++++---- kits/delete-user-data/tests/region.test.ts | 19 +++---- 5 files changed, 44 insertions(+), 72 deletions(-) diff --git a/kits/delete-user-data/CHANGELOG.md b/kits/delete-user-data/CHANGELOG.md index 93340f89b..e68e30e0e 100644 --- a/kits/delete-user-data/CHANGELOG.md +++ b/kits/delete-user-data/CHANGELOG.md @@ -1,4 +1,4 @@ -- docs: the README said the functions deploy to `us-central1` when no region is set. The Firebase CLI deploys them to `us-east1`. The README now says so, recommends keeping the functions in the same region as your Firestore database, and shows how to set the region with `setGlobalOptions({ region })` in a file imported before the kit. +- fix: the functions deploy to `us-central1` by default again, as the extension did. The kit set no region, so the Firebase CLI deployed them to `us-east1`. The region is now read from `FUNCTION_DEFAULT_REGION` (default `us-central1`), the name `firebase ext:migrate` writes to `.env`, so each instance can deploy to its own region. `setGlobalOptions({ region })` no longer moves the functions; set `FUNCTION_DEFAULT_REGION` instead. - fix: an Eventarc publish failure no longer fails the function. A channel that was deleted or is unreachable answers `PERMISSION_DENIED`, and the publish was awaited after the work, so the deletion completed but the invocation still failed. The publish failure is now logged as a warning and the invocation continues. Deploys also declare `eventarcpublishing.googleapis.com`, so the API is enabled on your project whether or not you use events. The failed invocation used to be redelivered, re-running a deletion that had already succeeded; it no longer is. diff --git a/kits/delete-user-data/README.md b/kits/delete-user-data/README.md index 6f895b7bf..9d7d64dda 100644 --- a/kits/delete-user-data/README.md +++ b/kits/delete-user-data/README.md @@ -42,14 +42,10 @@ service account for this codebase — it conflicts with that automatic setup. ## Usage -Export the functions from your functions codebase entry, importing your -region options file first (see -[Functions deploy to `us-east1` unless you set a region](#functions-deploy-to-us-east1-unless-you-set-a-region)): +Export the functions from your functions codebase entry: ```ts // functions/src/index.ts -import "./options.js"; - export { clearData, handleSearch, @@ -119,6 +115,7 @@ overridden there. | `searchDepth` | `AUTO_DISCOVERY_SEARCH_DEPTH` | no | `3` | Discovery depth | | `searchFields` | `AUTO_DISCOVERY_SEARCH_FIELDS` | no | `id,uid,userId` | Fields treated as user ids | | `searchFunction` | `SEARCH_FUNCTION` | no | (empty) | URL of a custom search function that returns paths to delete | +| — | `FUNCTION_DEFAULT_REGION` | no | `us-central1` | Region the functions deploy to | | `discoveryTopicName` | `DISCOVERY_TOPIC_NAME` | no | `kit--discovery` | Pub/Sub discovery topic | | `deletionTopicName` | `DELETION_TOPIC_NAME` | no | `kit--deletion` | Pub/Sub deletion topic | @@ -191,59 +188,25 @@ You can also override both names with `DISCOVERY_TOPIC_NAME` and `DELETION_TOPIC_NAME`, which the extension did not allow. Change them together, since one function publishes to a topic the other is triggered by. -### Functions deploy to `us-east1` unless you set a region +### Functions deploy to `FUNCTION_DEFAULT_REGION` The extension deployed to the location you picked at install time -(`us-central1` by default). This kit sets no region, and with no region set -the Firebase CLI deploys its Auth- and Pub/Sub-triggered functions to -`us-east1`, not `us-central1`. - -Keep the functions in the same region as your Firestore database (for a `nam5` -database, `us-central1`), so user data is not read and deleted from another -region. - -To choose the region, call `setGlobalOptions({ region })` from -`firebase-functions/v2` before the kit's functions are defined, reading the -region from a `FUNCTION_DEFAULT_REGION` parameter as the generated entry file -does. Put the call in its own file and import that file before the kit: - -```ts -// functions/src/options.ts -import { setGlobalOptions } from "firebase-functions/v2"; -import { defineString } from "firebase-functions/params"; - -setGlobalOptions({ - region: defineString("FUNCTION_DEFAULT_REGION", { default: "us-central1" }), -}); -``` +(`us-central1` by default). The kit reads the region from +`FUNCTION_DEFAULT_REGION` in each instance's `.env`, also defaulting to +`us-central1`, so instances can deploy to different regions. +`firebase ext:migrate` writes it with the region the extension ran in. ```sh # .env (or .env.) FUNCTION_DEFAULT_REGION=us-central1 ``` -```ts -// functions/src/index.ts -import "./options.js"; - -export { - clearData, - handleSearch, - handleDeletion, -} from "@firebase-function-kits/delete-user-data"; -``` +Keep the functions in the same region as your Firestore database (for a `nam5` +database, `us-central1`), so user data is not read and deleted from another +region. -A `setGlobalOptions` call in `index.ts` itself can run too late. In an ES -module, `import` and `export … from` statements load the kit before any other -code in the file runs, so the kit's functions are defined before the call. A -separate file imported first works for both ES modules and CommonJS. The -options apply to every function in the codebase. - -Each instance reads `FUNCTION_DEFAULT_REGION` from its own `.env`, so instances -can deploy to different regions. `firebase ext:migrate` writes it to `.env` -with the region the extension ran in, and `firebase functions:kits:install` -and `firebase ext:migrate` generate an entry file that does all of this for -you. +The kit sets the region on each function, so `setGlobalOptions({ region })` +does not move them. Set `FUNCTION_DEFAULT_REGION` instead. ### All functions are 2nd gen diff --git a/kits/delete-user-data/src/config.ts b/kits/delete-user-data/src/config.ts index e172ae79e..dd6a5ff1d 100644 --- a/kits/delete-user-data/src/config.ts +++ b/kits/delete-user-data/src/config.ts @@ -163,6 +163,15 @@ const params = { }, }, }), + // The extension's install-time location, under the name the CLI's generated + // entry file and `ext:migrate` use. Defaulting here keeps the extension's + // `us-central1`; with no region set the CLI would deploy to `us-east1`. + functionRegion: defineString("FUNCTION_DEFAULT_REGION", { + label: "Cloud Functions location", + description: + "Where do you want to deploy the functions created for this kit? You usually want a location close to your database.", + default: "us-central1", + }), // Non-empty defaults so Pub/Sub trigger bindings resolve during deploy // discovery without freezing an empty topic name into the manifest. discoveryTopicName: defineString("DISCOVERY_TOPIC_NAME", { @@ -174,6 +183,7 @@ const params = { }; export const CONFIG_EXPRESSIONS = { + functionRegion: params.functionRegion, discoveryTopicName: params.discoveryTopicName, deletionTopicName: params.deletionTopicName, } as const; diff --git a/kits/delete-user-data/src/index.ts b/kits/delete-user-data/src/index.ts index cca4a43d0..ab566d447 100644 --- a/kits/delete-user-data/src/index.ts +++ b/kits/delete-user-data/src/index.ts @@ -109,19 +109,24 @@ function getContext(): HandlerContext { return ctx; } -export const clearData = onUserDeleted((event) => { - // The Auth event delivers no user record when the payload envelope is empty, - // so bail before getContext() rather than initialising the SDKs for nothing. - const uid = event.data?.uid; - if (!uid) { - logs.deletionEventMissingUid(event.id); - return; +export const clearData = onUserDeleted( + { region: CONFIG_EXPRESSIONS.functionRegion }, + (event) => { + // The Auth event delivers no user record when the payload envelope is + // empty, so bail before getContext() rather than initialising the SDKs for + // nothing. + const uid = event.data?.uid; + if (!uid) { + logs.deletionEventMissingUid(event.id); + return; + } + return handleClear(uid, getContext()); } - return handleClear(uid, getContext()); -}); +); export const handleSearch = onMessagePublished( { + region: CONFIG_EXPRESSIONS.functionRegion, topic: CONFIG_EXPRESSIONS.discoveryTopicName, }, (event) => runSearch(event.data.message.json, getContext()) @@ -129,6 +134,7 @@ export const handleSearch = onMessagePublished( export const handleDeletion = onMessagePublished( { + region: CONFIG_EXPRESSIONS.functionRegion, topic: CONFIG_EXPRESSIONS.deletionTopicName, }, (event) => runDeletion(event.data.message.json, getContext()) diff --git a/kits/delete-user-data/tests/region.test.ts b/kits/delete-user-data/tests/region.test.ts index 5c55f1593..3cf0e63a1 100644 --- a/kits/delete-user-data/tests/region.test.ts +++ b/kits/delete-user-data/tests/region.test.ts @@ -15,7 +15,6 @@ */ import { describe, expect, test, vi } from "vitest"; -import { setGlobalOptions } from "firebase-functions/v2"; const FUNCTIONS = ["clearData", "handleSearch", "handleDeletion"] as const; @@ -24,21 +23,15 @@ async function loadKit() { return (await import("../src/index")) as Record; } -// The README tells users to pick a region with setGlobalOptions({ region }) -// before the kit loads, so the kit must not pin a region of its own. +// The extension deployed to its install-time location, `us-central1` by +// default. With no region set the CLI would pick `us-east1` instead. describe("function region", () => { - test("is not set by the kit, so the CLI default applies", async () => { + test("is read from FUNCTION_DEFAULT_REGION, defaulting to us-central1", async () => { const kit = await loadKit(); for (const name of FUNCTIONS) { - expect(kit[name].__endpoint.region).toBeUndefined(); - } - }); - - test("follows setGlobalOptions called before the kit loads", async () => { - setGlobalOptions({ region: "europe-west1" }); - const kit = await loadKit(); - for (const name of FUNCTIONS) { - expect(kit[name].__endpoint.region).toEqual(["europe-west1"]); + const region = kit[name].__endpoint.region; + expect(region.name).toBe("FUNCTION_DEFAULT_REGION"); + expect(region.options.default).toBe("us-central1"); } }); });