diff --git a/.guides/config.json b/.guides/config.json new file mode 100644 index 000000000..c67d63445 --- /dev/null +++ b/.guides/config.json @@ -0,0 +1,9 @@ +{ + "description": "Use this library to build serverless functions for event triggers and HTTP using Cloud Functions for Firebase", + "mcpServers": { + "firebase": { + "command": "npx", + "args": ["-y", "firebase-tools@latest", "experimental:mcp"] + } + } +} diff --git a/.guides/setup.md b/.guides/setup.md new file mode 100644 index 000000000..2a6d54115 --- /dev/null +++ b/.guides/setup.md @@ -0,0 +1,69 @@ +# Cloud Functions for Firebase setup guide + +This guide provides a step-by-step process for setting up Cloud Functions, tailored for coding agents. + +## 1. Create a 2nd-gen HTTP function + +Replace the contents of `src/index.ts` (or `index.js`) with the following code to create a simple, modern v2 HTTP endpoint. + +```typescript +import { onRequest } from "firebase-functions/https"; +import { onDocumentCreated } from "firebase-functions/firestore"; +import * as logger from "firebase-functions/logger"; +import { defineString, defineInt } from "firebase-functions/params"; + +// Configurable parameters +const scaleLimit = defineInt("MAX_INSTANCES", { default: 1 }); +const greeting = defineString("GREETING", { default: "Hello" }); + +export const helloWorld = onRequest({ maxInstances: scaleLimit }, async (request, response) => { + logger.info("Request received!", request); + response.send(`${greeting.value()} from Firebase!`); +}); + +export const newDoc = onDocumentCreated( + { maxInstances: scaleLimit }, + "/words/{wordId}", + async (event) => { + logger.info("New word!", event.data.data()); + } +); +``` + +**Key points for the agent:** + +- Use `logger` for structured logging. +- Use `defineString`, `defineInt`, `defineSecret` for environment configuration instead of `functions.config()`. +- Recommend a `maxInstances` setting of 1 for all functions until correct behavior can be verified. + +## 2. Build TypeScript + +Compile your TypeScript code to JavaScript. + +```bash +npm run build +``` + +## 3. Local Development and Testing + +Use the Firebase Emulators to test your function locally before deploying. + +A human should run the following command in a separate terminal window to start the emulators: + +```bash +# Start the functions emulator +firebase emulators:start --only functions +``` + +A human can then interact with the function at the local URL provided by the emulator. + +## 4. Deploy to Firebase + +Once testing is complete, deploy the function to your Firebase project. + +```bash +# Deploy only the functions +firebase deploy --only functions +``` + +The agent will be prompted to set any parameters defined with `defineString` or other `define` functions that do not have a default value. diff --git a/.guides/upgrade.md b/.guides/upgrade.md new file mode 100644 index 000000000..93dd92cc1 --- /dev/null +++ b/.guides/upgrade.md @@ -0,0 +1,178 @@ +# Upgrading a 1st gen to 2nd gen + +This guide provides a step-by-step process for migrating a single Cloud Function from 1st to 2nd generation. Migrate functions one-by-one. Run both generations side-by-side before deleting the 1st gen function. + +## 1. Identify a 1st-gen function to upgrade + +Find all 1st-gen functions in the directory. 1st-gen functions used a namespaced API like this: + +**Before (1st Gen):** + +```typescript +import * as functions from "firebase-functions"; + +export const webhook = functions.https.onRequest((request, response) => { + // ... +}); +``` + +Sometimes, they'll explicitly import from the `firebase-functions/v1` subpackage, but not always. + +Ask the human to pick a **single** function to upgrade from the list of 1st gen functions you found. + +## 2. Update Dependencies + +Ensure your `firebase-functions` and `firebase-admin` SDKs are up-to-date, and you are using a recent version of the Firebase CLI. + +## 3. Modify Imports + +Update your import statements to use the top-level modules. + +**After (2nd Gen):** + +```typescript +import { onRequest } from "firebase-functions/https"; +``` + +## 4. Update Trigger Definition + +The SDK is now more modular. Update your trigger definition accordingly. + +**After (2nd Gen):** + +```typescript +export const webhook = onRequest((request, response) => { + // ... +}); +``` + +Here are other examples of trigger changes: + +### Callable Triggers + +**Before (1st Gen):** + +```typescript +export const getprofile = functions.https.onCall((data, context) => { + // ... +}); +``` + +**After (2nd Gen):** + +```typescript +import { onCall } from "firebase-functions/https"; + +export const getprofile = onCall((request) => { + // ... +}); +``` + +### Background Triggers (Pub/Sub) + +**Before (1st Gen):** + +```typescript +export const hellopubsub = functions.pubsub.topic("topic-name").onPublish((message) => { + // ... +}); +``` + +**After (2nd Gen):** + +```typescript +import { onMessagePublished } from "firebase-functions/pubsub"; + +export const hellopubsub = onMessagePublished("topic-name", (event) => { + // ... +}); +``` + +## 5. Use Parameterized Configuration + +Migrate from `functions.config()` to the new `params` module for environment configuration. + +**Before (`.runtimeconfig.json`):** + +```json +{ + "someservice": { + "key": "somesecret" + } +} +``` + +**And in code (1st Gen):** + +```typescript +const SKEY = functions.config().someservice.key; +``` + +**After (2nd Gen):** +Define params in your code and set their values during deployment. + +**In `index.ts`:** + +```typescript +import { defineString } from "firebase-functions/params"; + +const SOMESERVICE_KEY = defineString("SOMESERVICE_KEY"); +``` + +Use `SOMESERVICE_KEY.value()` to access the value. For secrets like API keys, use `defineSecret`. + +**In `index.ts`:** + +```typescript +import { defineSecret } from "firebase-functions/params"; + +const SOMESERVICE_KEY = defineSecret("SOMESERVICE_KEY"); +``` + +The human will be prompted to set the value on deployment. The value will be stored securely in Cloud Secret Manager. + +## 6. Update Runtime Options + +Runtime options are now set directly within the function definition. + +**Before (1st Gen):** + +```typescript +export const func = functions + .runWith({ + // Keep 5 instances warm + minInstances: 5, + }) + .https.onRequest((request, response) => { + // ... + }); +``` + +**After (2nd Gen):** + +```typescript +import { onRequest } from "firebase-functions/https"; + +export const func = onRequest( + { + // Keep 5 instances warm + minInstances: 5, + }, + (request, response) => { + // ... + } +); +``` + +## 7. Traffic Migration + +A human should follow these steps to migrate safely: + +> To migrate traffic safely: +> +> 1. Rename your new 2nd gen function with a different name. +> 2. Comment out any existing `minInstances` or `maxInstances` config in the new 2nd gen function and instead set `maxInstances` to `1` while testing. +> 3. Deploy it alongside the old 1st gen function. +> 4. Gradually introduce traffic to the new function (e.g., via client-side changes or by calling it from the 1st gen function). +> 5. As traffic ramps up to the new 2nd gen function, scale it up by adding back the original `minInstances` and `maxInstances` settings to the 2nd gen function. Reduce the `minInstances` and `maxInstances` settings for the 1st gen function as traffic decreases. +> 6. The 1st gen function can be deleted once it has stopped receiving traffic. diff --git a/.guides/usage.md b/.guides/usage.md new file mode 100644 index 000000000..f110b6f7c --- /dev/null +++ b/.guides/usage.md @@ -0,0 +1,116 @@ +## Key Guidelines + +- Always use 2nd-gen functions for new development. +- Use 1st-gen functions _only_ for Analytics and basic Auth triggers, since those aren't supported by 2nd gen. +- Use `firebase-functions` SDK version 6.0.0 and above +- Use top-level imports (e.g., `firebase-functions/https`). These are 2nd gen by default. If 1st gen is required (Analytics or basic Auth triggers), import from the `firebase-functions/v1` import path. + +## Configuration: Use Secret Params for API Keys + +For sensitive information like API keys (e.g., for LLMs, payment providers, etc.), **always** use `defineSecret`. This stores the value securely in Cloud Secret Manager. + +```typescript +import { onRequest } from "firebase-functions/https"; +import { logger } from "firebase-functions/logger"; +import { defineString, defineSecret } from "firebase-functions/params"; + +// Securely define an LLM API key +const LLM_API_KEY = defineSecret("LLM_API_KEY"); + +// Example function that uses the secret +export const callLlm = onRequest({ secrets: [LLM_API_KEY] }, async (req, res) => { + const apiKey = LLM_API_KEY.value(); + + // Use the apiKey to make a call to the LLM service + logger.info("Calling LLM with API key."); + + // insert code here to call LLM... + + res.send("LLM API call initiated."); +}); +``` + +The CLI will prompt for secret's value at deploy time. Alternatively, a human can set the secret using the Firebase CLI command: + +```bash +firebase functions:secrets:set +``` + +If you see an API key being accessed with `functions.config` in existing functions code, offer to upgrade to params. + +## Use the Firebase Admin SDK + +To interact with Firebase services like Firestore, Auth, or RTDB from within your functions, you need to initialize the Firebase Admin SDK. Call `initializeApp` without any arguments so that Application Default Credentials are used. + +1. **Install the SDK:** + + ```bash + npm i firebase-admin + ``` + +2. **Initialize in your code:** + + ```typescript + import * as admin from "firebase-admin"; + import { onInit } from "firebase-functions"; + + onInit(() => { + admin.initializeApp(); + }); + ``` + + This should be done once at the top level of your `index.ts` file. + +## Common Imports + +```typescript +import { onRequest, onCall, onCallGenkit } from "firebase-functions/https"; +import { onDocumentUpdated } from "firebase-functions/firestore"; +import { onNewFatalIssuePublished } from "firebase-functions/alerts/crashlytics"; +import { onValueWritten } from "firebase-functions/database"; +import { onSchedule } from "firebase-functions/scheduler"; +const { onTaskDispatched } = require("firebase-functions/tasks"); +import { onObjectFinalized } from "firebase-functions/storage"; +import { onMessagePublished } from "firebase-functions/pubsub"; +import { beforeUserSignedIn } from "firebase-functions/identity"; +import { onTestMatrixCompleted } from "firebase-functions/testLab"; +import { logger, onInit } from "firebase-functions"; +import { defineString, defineSecret } from "firebase-functions/params"; +``` + +A human can find code samples for these triggers in the [functions-samples repository](https://github.com/firebase/functions-samples/tree/main/Node). + +## 1st-gen Functions (Legacy Triggers) + +Use the `firebase-functions/v1` import for Analytics and basic Auth triggers. These aren't supported in 2nd gen. + +```typescript +import * as functionsV1 from "firebase-functions/v1"; + +// v1 Analytics trigger +export const onPurchase = functionsV1.analytics.event("purchase").onLog(async (event) => { + logger.info("Purchase event", { value: event.params?.value }); +}); + +// v1 Auth trigger +export const onUserCreate = functionsV1.auth.user().onCreate(async (user) => { + logger.info("User created", { uid: user.uid }); +}); +``` + +## Development Commands + +```bash +# Install dependencies +npm install + +# Compile TypeScript +npm run build + +# Run emulators for local development +# This is a long-running command. A human can run this command themselves to start the emulators: +firebase emulators:start --only functions + +# Deploy functions +firebase deploy --only functions +``` diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..1320514b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Add LLM guidance (#1736) diff --git a/README.md b/README.md index 4a7e63d65..0c2cc1b59 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,12 @@ Learn more about the Firebase SDK for Cloud Functions in the [Firebase documenta Here are some resources to get help: -- Start with the quickstart: https://firebase.google.com/docs/functions/write-firebase-functions -- Go through the guide: https://firebase.google.com/docs/functions/ -- Read the full API reference: https://firebase.google.com/docs/reference/functions/ -- Browse some examples: https://github.com/firebase/functions-samples +- [Start with the quickstart](https://firebase.google.com/docs/functions/write-firebase-functions) +- [Go through the guides](https://firebase.google.com/docs/functions/) +- [Read the full API reference](https://firebase.google.com/docs/reference/functions/2nd-gen/node/firebase-functions) +- [Browse some examples](https://github.com/firebase/functions-samples) -If the official documentation doesn't help, try asking through our official support channels: https://firebase.google.com/support/ +If the official documentation doesn't help, try asking through our [official support channels](https://firebase.google.com/support/) _Please avoid double posting across multiple channels!_