Skip to content

feat(kits): add the firestore-bigquery-export kit - #3313

Open
CorieW wants to merge 475 commits into
nextfrom
fbe-kit
Open

CorieW wants to merge 475 commits into
nextfrom
fbe-kit

Conversation

@CorieW

@CorieW CorieW commented Nov 4, 2026

Copy link
Copy Markdown
Member

Brings the firestore-bigquery-export kit from kits into next, without the other kits.

  • Branched from the latest kits (2a760638), then removed every kit under kits/ except firestore-bigquery-export.
  • Dropped the removed kits from the Release Kit workflow's options and from kits/README.md (the README's firebase.json example now uses firestore-bigquery-export).
  • Regenerated pnpm-lock.yaml. The old lockfile was out of date for this kit (firebase-admin ^14.2.0 / firebase-functions ^7.3.3-rc.2 vs ^14.4.0 / ^7.4.0 in its package.json), so that's fixed here too.

IzaakGough and others added 30 commits September 2, 2026 12:32
…tension treated as terminal

The extension reported a missing linked transfer config and a rejected
partitioning-field removal as a completed install and returned, so Cloud Tasks
never retried them. The kit throws on both, which retries five times and then
dead-letters. Log them and return instead.

Kits have no status channel, so the messages go to Cloud Logging at error level
and carry the remediation.
…nts from the real module in tests

Both sides of the handler's prefix match came from copies in the test file, so
the suite stayed green when the thrown message no longer started with the
prefix. Take both constants from the module under test.

Also scope the comment on the swallow to what the guard does, since #2985 shows
the API does accept clearing the field.
…model

TEMPERATURE, TOP_P, TOP_K and CANDIDATE_COUNT were accepted as config in the
extension but never forwarded on the generation request. The kit forwards all
four, and decides whether to write the candidates field from the count actually
sent rather than from the deploy-time value. Record it as a deliberate
divergence in the kit's differences section.
…tZodError (#3040)

The kit's formatZodError dropped the invalid_string branch the legacy
extension has, so string-validation failures (e.g. a failed .email()
check) fell through to zod's raw default message instead of the
extension's "Field 'x' must be a valid email address" / "Field 'x' is
invalid" shape. This restores the branch; the kit pins zod ^3.25.76
(same major as the extension's ^3.24.4), so the issue code and its
validation field carry over unchanged. formatZodError is now exported so
the new vitest tests can drive it with real zod errors; note the kit's
current schemas do not yet use string validators, so this path is only
reachable if a schema adds one, matching the extension's defensive
intent. Full kit suite, tsc, and prettier pass; no live/e2e run.

Part of #3036.
…om embeddings endpoint (#3042)

The legacy extension (firestore-vector-search v0.1.3) checked the custom
embeddings endpoint's content-type header and rejected non-JSON
responses with a clear error; the kit dropped that check, so a non-JSON
response (for example an HTML error page) surfaced as a raw `Unexpected
token` parse error. This restores the check in
`CustomEndpointClient.getEmbeddings`, throwing "Error getting embeddings
from custom endpoint: response is not JSON" before parsing, matching the
legacy wording. Following review, it also restores two more legacy
behaviors in the same method: the schema parse is wrapped so valid JSON
of the wrong shape throws "Error getting embeddings from custom
endpoint: response does not match expected schema" instead of a raw
ZodError, and the length-mismatch message reverts to legacy's exact
"response does not contain embeddings" wording. Adds four vitest cases
(JSON success with charset parameter, non-JSON content-type, wrong-shape
JSON, missing embeddings) - each failing case was written and verified
red before its fix. Full kit suite passes (53 tests) and `tsc -b` is
clean; note `npx prettier --check src` already fails on origin/kits
because prettier 2.7.1 cannot parse `satisfies` in `src/config.ts`
(pre-existing, untouched). Part of #3036.
…STALENESS doc example (#3045)

Restores two parity items from the #2974 ledger. The
bigquery-firestore-export kit gets back the extension's
`logs.writeRunResultsToFirestore(runId)` helper, called after the
BigQuery results are read and before the row writes, with a test pinning
the call and its ordering on both sides (mutation-checked: hoisting the
call above the read fails); the kit's full suite, tsc, and prettier
pass. The other two dropped helpers, `logs.start()` and
`logs.invalidResourceName()`, are deliberately skipped: neither has a
call site in the upstream extension (both are defined and never called),
and the kit already has its own `start()`/`init(config)` logging, so
restoring them would add dead code. The MAX_STALENESS doc example is
restored to the extension's `INTERVAL "8:0:0" HOUR TO SECOND`; the
ledger and #3036 mislabeled which kit owns this param - it actually
lives in firestore-bigquery-export, so that is where the fix lands, and
it is a JSDoc comment only, so no tests were added for it. Checked
#2983, #2980, #2960, #2963 for collisions: none add or rename
`writeRunResultsToFirestore` in logs.ts, though #2963 also extends
helper.test.ts and may need a trivial merge. Part of #3036.
…ccess and onCompletion (#3071)

The kit README's Events section claimed the function publishes
`onStart`, `onError`, `onSuccess`, and `onCompletion`. The code
publishes `onStart` and `onError` only: `src/handlers.ts` calls
`recordStartEvent` and `recordErrorEvent`, and nothing calls
`recordSuccessEvent` or `recordCompletionEvent`. The extension emitted
`onSuccess` from its `syncBigQuery` task queue handler, which the kit
deliberately removed, and the README's differences section already
documents that. So the README was the wrong side; this corrects the
Events section to list `onStart` and `onError` and point at the
differences entry. Re-adding `onSuccess` emission (parity with the
extension) is a separate decision, not attempted here.

Doc-only change, so no tests. Note #3066 also edits this README; its
hunks (config table, provisioning, region sections) do not overlap this
one.

Fixes #3018
…path (#3073)

The `.txt` transcription output URI retained `replace("tmp/", "")` from
the legacy extension, where the transcoded `.wav` was uploaded under an
object name derived from its local `/tmp` path. The kit uploads the
`.wav` bucket-relative, so the remnant only mangled object paths that
legitimately contain `tmp/` (e.g. `audio/tmp/clip.mp3` produced a
transcript at `audio/clip.mp3.wav_transcription.txt`). The transform is
removed; the `.txt` now lands at `<transcoded object>_transcription.txt`
exactly, as the README states.

Added a unit test on `transcribeAndUpload` pinning the `gcsUri` for a
`tmp/`-containing path; it was red against the remnant and is green
after. Full vitest suite, `tsc -b` and prettier pass. Live transcription
was not run.

The open decision on where the transcoded `.wav` lands (#2974 Migration
section) is out of scope and unchanged.

Fixes #3026
…gurable

The kit renamed the notification topic from ext-<instance id>-processMessages
to kit-<INSTANCE_ID>-processMessages, so the update path, which reconciles
notification_pubsub_topic to whatever the config names, repointed every adopted
transfer config on first deploy. Anything else subscribed to the old topic,
including an extension instance still installed, stopped receiving run
notifications.

PUB_SUB_TOPIC now names the topic, defaulting to the kit name. Migrating users
can set the extension's topic and keep the transfer config untouched. A linked
TRANSFER_CONFIG_NAME config is still never repointed, so a topic mismatch there
now logs a warning instead of silently dropping every run.
…ig logs (#3077)

The kit logs the full config object via `logs.init` at startup and
`logs.start` on every invocation, and the config can carry
`googleAiApiKey`, so the secret lands in Cloud Logging in cleartext per
event.

The fix masks secret-shaped config values as `<omitted>` inside the
`start`/`init` message builders, following the firestore-send-email /
bigquery-firestore-export convention. Redaction sits at the logging site
rather than at the config merge, so it is order-independent with #3075
(still open), which changes where the config picks up the secret inside
`getContext()`: whichever PR lands second cannot reintroduce the leak.
The handlers test that previously asserted the merged secret appears in
`logger.log` now observes merge precedence through the `googleAI` plugin
mock instead.

Repro-first tests cover both `start` and `init` paths (red on the old
code, green now); full vitest suite (95 tests) and `tsc -b` pass.

Fixes #3076
…ums and restore the PROJECT_ID error text (#3046)

The kit typed safety settings as a local `{ category: string; threshold:
string }`, dropping the enum-level typing the extension got from the SDK
`SafetySetting` types. `SafetySetting` is now typed with
template-literal unions over both pinned SDKs' enums
(`@google/generative-ai` and `@google/genai` `HarmCategory` /
`HarmBlockThreshold`), so misspelled values fail to compile while plain
string literals and enum members both stay valid for lib consumers. An
earlier revision also rejected non-enum values at runtime; that was
dropped after review, because the extension never validated safety
values at runtime (it casts raw env strings) and the pinned
`@google/generative-ai` enums lag the live API (no `OFF`, no
`HARM_CATEGORY_CIVIC_INTEGRITY`), so the rejection broke
previously-working configs. `resolveConfig` now only checks safety
settings structurally, and rejects an unknown provider with the
extension's `Invalid Provider: X` wording. `getProjectId` now throws the
extension's `Missing required environment variables: PROJECT_ID` wording
instead of `FIREBASE_CONFIG is not set`, including when
`FIREBASE_CONFIG` is present without a projectId. Covered by new
resolveConfig, provider and getProjectId tests; full suite (29 tests),
tsc and prettier are clean, and the test files were typechecked
explicitly since tsconfig excludes them. Not verified against a live
deploy. Part of #3036.
…3057)

The `firestore-send-email` kit shipped with 14 tests against roughly
1,900 lines of source, while the legacy extension it was ported from
carries around 2,000 lines of unit tests. This PR ports the four pure
unit suites that transfer without an emulator: `validatePayload` (new
`tests/validation.test.ts`), `setSmtpCredentials` / `isSendGrid` (new
`tests/helpers.test.ts`), `SendGridTransport` (new
`tests/nodemailer-sendgrid.test.ts`), and template merging appended to
the existing `tests/prepare-payload.test.ts`. Jest goes to vitest, `it`
to `test`, and the legacy `setDependencies` mocking is replaced by the
kit's `PreparePayloadDependencies` seam. The kit test count goes from 14
to 115, all green, `tsc -b` clean, prettier clean. Only test files
change; no source or manifest is touched.

Three tests are new rather than ported, each mutation-verified by
breaking the branch it covers and confirming it was the only failure.
Two pin guards the kit's transport refactor added and the legacy
transport lacked: an empty `watchHtml` emits no content entry, an empty
`messageId` emits no header. The third covers the template merge falling
back to the message's own attachments when the render supplies none.

Deliberately not ported: everything gated on the `TESTING` env flag (the
kit has no `process.env.TESTING`), the `describe.skip`'d SMTP e2e, and
the `E2E_SENDGRID` live suite. Also dropped are the legacy helper tests'
`regex.test(...)` assertions, which only exercised a regex literal
declared inside the test file rather than the kit's own
`SMTP_CONNECTION_URI` `validationRegex`.

Two notes. #3040 also adds `tests/validation.test.ts` with
`formatZodError` tests; the two files do not overlap in coverage but
will need a trivial merge, whichever lands second. Its `invalid_string`
branch is also the one real difference between the kit's `validation.ts`
and the legacy file, which are otherwise logic-identical on every
reachable path. And no test here touches the SendGrid OAuth2 secret
gating in #3009, so nothing pins that bug.

Part of #2974.
…e log

Dropping it from logs.ts broke the build, since helper.ts still calls it.
The run-results logs mock never listed it, so restoring the export alone
leaves those tests failing.
The param takes a bare topic ID, but nothing enforced it, and a full
projects/<project>/topics/<topic> value produced a double-prefixed
notification topic. A validation regex rejects it when prompted, and
notificationTopicName passes a resource name through for dotenv values,
which the CLI does not validate.
…llback (#3090)

The kit's `checkImageContent` threw `FUNCTION_REGION is required for
Vertex AI filtering.` when no region was passed; the extension fell back
to `us-central1` (master,
storage-resize-images/functions/src/content-filter.ts:121). This
restores the fallback at the same point of use: the Vertex AI plugin now
receives `location ?? "us-central1"` and the throw is gone. `config.ts`
and `export-config.ts` are untouched, so there is no overlap with #3002
and the resolved config still reports `FUNCTION_REGION` faithfully.

Normal CLI deploys never hit the throw: firebase-tools injects
`FUNCTION_REGION` into every gen2 function's env (verified in 15.26.0,
lib/gcp/cloudfunctionsv2.js:197). It was reachable for library consumers
of `checkImageContent`, emulator runs, and hand-rolled envs.

The throw test is replaced by a fallback test (verified to fail against
the old code) plus an explicit-location passthrough test; README,
CHANGELOG, and a stale test comment are updated. 182 tests and `tsc
--noEmit` pass; the live Vertex filtering path was not exercised. This
instantiates a "FUNCTION_REGION if set, else us-central1" rule relevant
to the open decision in #3028.

Fixes #3023
fix(bigquery-firestore-export): stop retrying the two failures the extension treated as terminal
**What was wrong**

The `Use Java 21 for the emulator` step sets `JAVA_HOME`, but
firebase-tools never reads it: `checkJavaMajorVersion` spawns a bare
`java` (confirmed in firebase-tools 15.28.2,
`lib/emulator/commandUtils.js`, where `JAVA_HOME` appears nowhere in the
package). The runner's default Java 17 stayed in front on PATH, so every
emulator-gated kit suite failed with "firebase-tools no longer supports
Java version before 21".

**What changed**

The step now also appends `$JAVA_HOME_21_X64/bin` to `GITHUB_PATH`, so
the `java` firebase-tools resolves is 21. `JAVA_HOME` is kept for
anything else that honours it.

**How it was verified**

Read against the published firebase-tools 15 source to confirm the
version check reads PATH only, and traced the failure to that step in
the run for #3087. `prettier --check` passes and the workflow still
parses as YAML. Java is referenced in no other live workflow, and the
two steps after this one (Firebase CLI install, emulator tests) are the
only consumers in the job, so nothing else moves off 17.

CI on this PR does not exercise the fix, since no kit changes here and
the emulator job only runs for changed kits. Retargeting a kit PR at
this branch, or merging and re-running #3087, is what proves it.
…tion-options

docs(kits): note that genai chatbot generation options now reach the model
…the merge

The merge into this branch left two vi.mock calls for src/dts and stranded
two logs mock keys inside one of them, so the file no longer parsed.
…e path (#3065)

On the queryOnWrite path, `handleQueryOnWrite` cast `data.prefilters as
Prefilter[]` without validation, so malformed prefilters on a watched
query document (a non-array, or non-object entries) either failed
downstream inside the Firestore query or silently misbehaved. The legacy
extension (firestore-vector-search v0.1.3) zod-parses prefilters with
`z.array(prefilterSchema)` on this path (parity ledger #2974, item 7b).

This reuses the kit's existing `prefilterSchema` via a new
`parsePrefilters` helper and replaces the cast at the point of use,
throwing `Invalid prefilters: ...` before any embedding work. The legacy
processor additionally caught the error and wrote an ERROR status to the
document; the kit throws out of this handler today, and status parity is
deferred to ledger item 7c.

Tests: a new `handleQueryOnWrite prefilters validation` describe block,
confirmed red before the fix; full suite, `tsc -b`, and prettier pass.
Note #3038 touches the head of the same function and appends its own
onWrite tests; whichever lands second rebases trivially.

Fixes #3013

Following review (050ce5d): an explicit `prefilters: null` is treated
as no prefilters, preserving the `?? []` behavior the parse replaced,
and validation errors include each issue's path so a mixed array names
the index of the bad entry. Note the parse checks the top-level shape
only (an array of objects), matching legacy's `z.record(z.any())`
prefilterSchema; a typo'd key inside a prefilter is not caught here and
still surfaces from the Firestore query downstream.
…ion-topic

fix(bigquery-firestore-export): make the DTS notification topic configurable
…per process (#3075)

The kit's `handleDocumentWrite` called `createTranslationService` on
every invocation, so each event built a new `TranslationService` and a
fresh translation client. The legacy extension constructs the client
once at module scope.

Following the kit family's lazy-context pattern (delete-user-data's
`getContext`), the service now lives on the memoized `HandlerContext`
built once per process in `getContext()`, which also merges the runtime
Google AI API key secret into the config there. The `./lib` entry stays
side-effect free.

New `tests/context.test.ts` drives the real trigger path twice with a
mocked `v2.Translate` constructor and pins a single construction; it
failed with two constructions before the fix. Full suite (90 tests) and
`tsc -b` pass.

Fixes #3021
…3038)

handleQueryOnWrite ran a vector query and wrote the result back onto the
same document it watches, re-firing itself in a self-sustaining loop,
billed one embedding call plus one vector query per pass (parity ledger
#2974, firestore-vector-search section 7).

An earlier revision guarded on the event's before/after snapshots, but
that races: if a slow run overwrites a newer query's result, the echo
sees unchanged inputs plus a result and skips, stranding query B's
inputs with query A's result permanently. The guard now uses stored
state instead: the result write also stores the request (query, limit,
prefilters) that produced it in the status field, and a trigger skips
only when the document's current inputs deep-match that stored record. A
stale overwrite mismatches, re-runs on the current inputs, and the
corrective echo matches and stops. This is in the spirit of the legacy
extension's FirestoreOnWriteProcessor (which the kit cannot consume,
being firebase-functions v1-tied) but deliberately stronger: the legacy
processor never re-ran a completed query document at all, even when the
query changed, and did not track prefilters. The kit re-runs on any
input edit, matching its embed-path rule; the README documents the new
query-doc status shape and this divergence.

Vitest pins the guard, the re-run paths, a legacy doc without a record,
and the full race sequence; guard mutations fail 5-11 tests depending on
the mutation, including an asymmetric write-path mutation that would
loop forever. Kit suite: 65 passed. Unit tests only, not verified
against live Firestore.

Fixes #3010
…with a multi-region mapping (#3101)

This partially reverts #3066, by agreement with the firebase-tools team.
#3066 removed the DATABASE_REGION param because its raw value was used
as the function region, which hard-failed deploys for multi-region
databases (#3017). But the CLI's region inference the kit fell back to
is unreliable for param-declared databases: region resolution runs
before param substitution and silently defaults to us-central1
(firebase/firebase-tools#11020). Since the extension-to-kit migration
exports DATABASE_REGION into the user's .env, the decision is to keep
using it, with the mapping that makes it safe where the pre-#3066 code
failed.

The restored param now places all three functions: Firestore
multi-region locations map to a Cloud Run region (nam5/nam7 to
us-central1, eur3 to europe-west1, mirroring the CLI's own
FIRESTORE_DUAL_REGION_TO_REGION_MAPPING); regional locations pass
through; unset keeps today's no-region behavior. The region is read from
process.env at module load, which requires firebase-tools >= 15.28.0
(firebase/firebase-tools#10936) to be present during discovery; on older
CLIs it degrades to the no-region fallback rather than failing. Tests
pin all mapping cases and the unset case, and breaking the mapping fails
them. No live multi-region deploy was run this time; #3066's no-region
behavior was live-verified on nam5.
IzaakGough and others added 24 commits October 14, 2026 12:57
…-bugs

docs(kits): add bug reporting instructions
…ncurrency

fix(storage-resize-images): match the extension's Gen1 concurrency and CPU
…nd OUTPUT_OPTIONS at deploy

The CLI applies a param's validationRegex only at the prompt, so a bad value in .env deployed and then failed every upload. Check these values at module load so discovery fails naming the param, and document quoting JSON values in .env.
…OR severity

An error thrown before the resize, such as an invalid SHARP_OPTIONS, reached the logs only as unstructured stderr. Log it with logger.error and rethrow, and note in the README that 2nd gen failures should be alerted on from logs.
…ploy

europe-north2 and northamerica-south1 have no Cloud Functions location, and the CLI only lets a function listen to a regional bucket from the same region, so neither option can deploy.
…tions

Reject multi-line SHARP_OPTIONS and OUTPUT_OPTIONS as the extension does, give OUTPUT_OPTIONS an error that matches what is checked, and say to single-quote JSON values in .env.
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Matches the extension's ERROR entry so Error Reporting groups each failure once. The README now says that failed events are always recorded as HTTP 200 and that timeouts are never logged at ERROR.
…ress (#3276)

Pins the extension's `concurrency: 1`, `cpu: "gcf_gen1"` and
`ingressSettings: "ALLOW_INTERNAL_ONLY"` on all three kit functions, so
auto-discovery no longer runs `handleSearch` out of memory and silently
skips deletions. Fixes #3250.

Follows #3209 (`firestore-bigquery-export`) and #3233
(`firestore-send-email`). #3206 sets concurrency and ingress for this
kit but not `cpu`, and is open with conflicts, so this lands the
`delete-user-data` part on its own; #3206 can drop this kit.

## Changes

- `clearData`, `handleSearch` and `handleDeletion` declare `concurrency:
1`, `cpu: "gcf_gen1"` and `ingressSettings: "ALLOW_INTERNAL_ONLY"`.
- README: notes the matching runtime settings, and the remaining
max-instances difference with how to raise it (`setGlobalOptions({
maxInstances })` before loading the kit).
- CHANGELOG entry.
- Test that each function's `__endpoint` has the three options.

## Testing

Deployed side by side on a test project, each with its own Firestore
database: extension `firebase/delete-user-data@0.1.30`, the kit from
`kits` @ fcdd555, and the kit from this branch. Same config for all:
auto-discovery on, depth 1, default search fields, shallow mode, no
paths. Each database has 20 root collections × 2,500 docs (~2 KB each)
plus one doc per collection owned by the test user. I then created and
deleted the user and counted the user's docs about 3 minutes later. Two
runs:

| | Extension | Kit before | Kit after |
|---|---|---|---|
| Concurrency | 1 | 80 | 1 |
| CPU | 1st gen, 256MB | 1 | 0.1666 |
| Ingress | `ALLOW_INTERNAL_ONLY` | `ALLOW_ALL` | `ALLOW_INTERNAL_ONLY`
|
| Max instances | 3000 | 100 (default) | 100 (default) |
| User docs deleted, run 1 | 20/20 | 0/20 | 20/20 |
| User docs deleted, run 2 | 20/20 | 1/20 | 20/20 |
| `handleSearch` failures | none | 1 × `Memory limit of 256 MiB
exceeded`, 18–19 × "The request failed because ... connection to the
instance had an error" per run | none |

- With internal-only ingress, the kit still receives the Auth deletion
event (`clearData`) and both Pub/Sub topics (20 `handleSearch` and 20
`handleDeletion` requests per run).
- The 100-instance cap didn't matter here (20 searches per deletion), so
it is left at the default and documented.
- `vitest run`: 122 tests pass. Prettier passes on the changed files.
`tsc` passes once `@types/node` is available; the kit's tsconfig lists
it, but it isn't installed on `kits`, so `pnpm build` fails there with
or without this change.
…rted-regions

fix(storage-resize-images): drop BUCKET_REGION options that cannot deploy
…e-env-params

fix(storage-resize-images): reject invalid IMG_SIZES, SHARP_OPTIONS and OUTPUT_OPTIONS at deploy
…dler-errors

fix(storage-resize-images): log errors that escape the trigger at ERROR severity
…t deploy (#3278)

Rejects the `.env` values the extension refuses at install time, so the
kit's deploy fails instead of deploying and quietly deleting less data.
Values the extension accepts, its option labels included, still deploy
and run as they did in the extension. Fixes #3258 (part of #3249).

## Changes

- `assertValidParams()` in `src/config.ts`. It reads each param's
declared `input` (no copied lists): a select value must be one of the
option values or labels, and a text value must match its
`validationRegex`. The error names the param, the bad value and the
allowed values, and lists every bad param at once.
- Unset and empty values are skipped, so optional params can stay blank
as in the extension. Blank required params are still handled by
`assertRequiredParams()` (see #3256).
- `assertParams()` runs both checks at module load and throws once, so a
blank required param and an invalid value are reported in the same
deploy. `assertRequiredParams()` itself is unchanged, as the other kits
share it.
- Option labels (`Recursive`, `Shallow`, `Yes`, `No`, `United States`,
`Belgium`, `Singapore`) are accepted, as the extension's installer
accepted them. They run as they did in the extension: `Recursive`
deletes shallowly, `Yes` leaves auto-discovery off, and a location label
gives a database URL that doesn't exist. The README warns about this and
says to use the values.
- Tests in `tests/valid-params.test.ts` and `tests/index.test.ts`.
README (Configuration) and CHANGELOG updated.
- Issue edit: the "Precedent: #3124" line now points at the existing
`assertRequiredParams()` / `validatePathListsFromEnv()` pattern. #3124
was closed; #3169 (open, into `next`) proposes the `IMAGE_TYPE` checks.

Example error:

```
Error: Invalid parameter values: FIRESTORE_DELETE_MODE="RECURSIVE" must be one of: recursive, shallow. Fix them in this instance's .env or .env.<projectId> file, or remove the entries to use their defaults.
```

## Testing

Deployed on a test project: the extension
`firebase/delete-user-data@0.1.30`, the kit from `kits` and the fixed
kit, each with its own Firestore database. Each had
`FIRESTORE_PATHS=users/{UID}` and was seeded with `users/<uid>`,
`users/<uid>/sub/s1`, `things/t-<uid> {uid}` and a control doc; then a
test user was deleted.

Bad values (extension checked with the instances API
`validateOnly=true`; kits with a real `firebase deploy`):

| `.env` value | Extension | Kit before | Kit after |
|---|---|---|---|
| `FIRESTORE_DELETE_MODE=RECURSIVE` / `recursivex` | rejected | deploys
| deploy fails |
| `FIRESTORE_DELETE_MODE=Recursive` (label) | accepted | deploys |
deploys¹ |
| `ENABLE_AUTO_DISCOVERY=true` / `YES` | rejected | deploys | deploy
fails |
| `SELECTED_DATABASE_LOCATION=us-east1` / `US-CENTRAL1` | rejected |
deploys | deploy fails |
| `SELECTED_DATABASE_INSTANCE=https://x.firebaseio.com` | rejected |
deploys | deploy fails |
| `CLOUD_STORAGE_BUCKET=gs://x` / `Upper_Case` | rejected | deploys |
deploy fails |
| blank `SELECTED_DATABASE_INSTANCE` / `SELECTED_DATABASE_LOCATION` |
accepted | deploys | deploys |

¹ The deploy run rejected labels; accepting them came later (32a478f)
and is covered by unit tests, not a fresh deploy.

The kit before deployed with all five bad values in one `.env`; the
runtime env held them as written.

User deletion (extension and fixed kit with `recursive` / `yes`, kit
before with `RECURSIVE` / `true`):

| Doc | Extension | Kit before | Kit after |
|---|---|---|---|
| `users/<uid>` | deleted | deleted | deleted |
| `users/<uid>/sub/s1` | deleted | **left behind** | deleted |
| `things/t-<uid>` (`uid` field, auto-discovery) | deleted | **left
behind** | deleted |
| control doc | kept | kept | kept |

The kit before logged `firestoreDeleteMode: "RECURSIVE"`,
`enableAutoDiscovery: false` and a non-recursive delete.

Unit tests: `vitest run` passes (16 files, 165 tests), including every
option label being accepted and a blank required param plus an invalid
value being reported in one error. Prettier passes on the changed files.
`tsc` passes, but only with a temporarily available `@types/node`: on
`kits`, `pnpm build` fails with `TS2688: Cannot find type definition
file for 'node'` because `@types/node` isn't installed, which this
change doesn't cause.

Other kits read `.env` the same way and probably have the same gap.
…NT_FILTER_LEVEL

An invalid SHARP_OPTIONS is now rejected when the module loads, so it no
longer reaches the handler's error logging.
…error-test

test(storage-resize-images): fix the config-load error test
…ION_DEFAULT_REGION (#3277)

Sets the region on the kit's three functions from
`FUNCTION_DEFAULT_REGION` (default `us-central1`), so they deploy where
the extension did. Before, the kit set no region and the Firebase CLI
put them in `us-east1`, while the README said `us-central1`. Fixes #3252
(part of #3249).

| | Extension | Kit before | Kit after |
|---|---|---|---|
| Region with nothing set | `us-central1` | `us-east1` | `us-central1` |
| Region per instance | yes, chosen at install | only with a
`setGlobalOptions({ region })` entry file, like the one `kits:install`
generates | yes, `FUNCTION_DEFAULT_REGION` in each instance's `.env` |
| `setGlobalOptions({ region })` moves the functions | n/a | yes | no |

## Changes

- `src/config.ts`: a new `FUNCTION_DEFAULT_REGION` string param ("Cloud
Functions location", default `us-central1`). It's the name `firebase
functions:kits:install` and `ext:migrate` already use, so a migrated
instance keeps its extension's region, and each instance can have its
own region.
- `src/index.ts`: `clearData`, `handleSearch` and `handleDeletion` take
their `region` from it. Because the region is set on each function,
`setGlobalOptions({ region })` no longer moves them.
- README: a row in the config table, and the "Functions deploy to …"
section now describes `FUNCTION_DEFAULT_REGION`, says to keep the
functions in the Firestore database's region, and says
`setGlobalOptions({ region })` doesn't apply.
- Tests: `tests/region.test.ts` checks all three functions read
`FUNCTION_DEFAULT_REGION` with default `us-central1`;
`tests/index.test.ts` now expects the kit's region over a global one.
- CHANGELOG entry.

## Testing

Deployed this branch (4433882) to a test project with firebase-tools
15.31.0, using the README's setup (an entry file that re-exports the
three functions) unless noted. Each instance had
`FIRESTORE_PATHS=r77-<instance>/{UID}`. For the deletion check, I wrote
a doc at each instance's path, deleted an Auth user, and waited for the
docs to go.

| Setup | `FUNCTION_DEFAULT_REGION` | Deployed to | User data deleted |
|---|---|---|---|
| New instance | `us-central1` | `us-central1` | yes (10s) |
| New instance | `europe-west2` | `europe-west2` | yes (6s) |
| New instance | not in `.env` | deploy fails (non-interactive) | — |
| `kits:install`-style entry file (its own
`defineString("FUNCTION_DEFAULT_REGION")` + `setGlobalOptions({ region
})`) | `europe-west2` | `europe-west2` | yes (6s) |
| Kit from `kits`, for comparison | — | `us-east1` | yes (7s) |

`vitest run`: 17 files, 166 tests pass. `tsc` still reports `TS2688:
Cannot find type definition file for 'node'` on `kits` because
`@types/node` isn't installed. This change doesn't cause that, and the
output is emitted.
…1st gen limit (#3312)

Defaults the kit's three functions to `maxInstances: 3000`, the
extension's 1st gen limit, instead of the 2nd gen default of 100. Fixes
#3311 (part of #3249).

| | Extension | Kit before | Kit after |
|---|---|---|---|
| Max instances per function | 3000 | 100 | 3000 |
| `setGlobalOptions({ maxInstances })` changes it | n/a | yes | yes |
| `setGlobalOptions` changes concurrency, CPU or ingress | n/a | no | no
|

Function-level options win over `setGlobalOptions`, so the kit sets
`maxInstances` only when no global value is set. Concurrency, CPU and
ingress stay fixed.

## Changes

- `src/index.ts`: defaults `maxInstances` to 3000 unless
`setGlobalOptions` sets it. `getGlobalOptions` isn't in the `.d.ts`, so
the kit reads it from the SDK's `globalThis` symbol.
- README, tests and CHANGELOG updated.

## Testing

- A 2nd gen function with the kit's defaults deployed to a test project
with `maxInstances` 3000.
- `vitest run`: 167 tests pass, including a global `maxInstances`
overriding the default while a global `concurrency: 80` doesn't.
- Not tested: a full kit deploy, or a burst of more than 100 events.
@CorieW
CorieW requested a review from a team as a code owner November 4, 2026 23:54

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces Firebase Function Kits, specifically packaging the firestore-bigquery-export extension as a deployable npm package under the new kits/ directory, along with corresponding configuration updates, documentation, and comprehensive unit tests. The code review feedback is highly constructive and identifies several key improvements: mapping the missing asia3 multi-region to prevent deployment failures, recalculating the task retry jitter inside the loop to ensure proper randomized backoff, explicitly typing the wildcard resolution accumulator to prevent strict TypeScript compilation errors, and removing unused lodash dependencies from package.json.

@cabljac cabljac left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The kit source and tests match the kits branch; the only package differences are removal of the unused lodash dependencies.

Two things before merging:

  • The build check fails during README generation with pnpm: not found. Can we install pnpm in that workflow or keep that step using npm?
  • We need to check: do releases now come from next? The release workflow still checks out and pushes version commits to kits, so changes merged here won’t be what it publishes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants