From be27c671c812fad91a3b2a3298992cd1e420ab4e Mon Sep 17 00:00:00 2001 From: Varun Shetty Date: Tue, 31 Mar 2026 22:07:51 +0000 Subject: [PATCH 1/7] Initial Pass --- skills/firebase-v1-v2-migration/SKILL.md | 53 +++++++++++++++++++ .../references/destructuring-shim.md | 45 ++++++++++++++++ .../references/signature-mapping.md | 27 ++++++++++ 3 files changed, 125 insertions(+) create mode 100644 skills/firebase-v1-v2-migration/SKILL.md create mode 100644 skills/firebase-v1-v2-migration/references/destructuring-shim.md create mode 100644 skills/firebase-v1-v2-migration/references/signature-mapping.md diff --git a/skills/firebase-v1-v2-migration/SKILL.md b/skills/firebase-v1-v2-migration/SKILL.md new file mode 100644 index 00000000..48ed3441 --- /dev/null +++ b/skills/firebase-v1-v2-migration/SKILL.md @@ -0,0 +1,53 @@ +--- +name: firebase-v1-v2-migration +description: Use this skill when a user wants to upgrade their legacy Firebase Functions from V1 (GCF 1st Gen) to V2 (GCF 2nd Gen) safely without rewriting their internal business logic. This skill relies on the Destructuring Compatibility Shim. +--- +# Prerequisites +Please ensure the workspace is ready for V2 before attempting a code migration: +1. **Configuration Check**: Ensure the workspace has transitioned away from functions.config() to Parameterized Configuration or standard environment variables. +2. **Dependencies**: The project must be using firebase-functions version that supports V2 (>= 4.0.0). + +# 🔍 Pre-Migration Checklist +Before modifying any code, the agent should run a quick scan: +1. **Scan for legacy configs**: Run a `grep` or text search for usages of `functions.config()`. + - **Action**: If found, **stop and warn the user** that these configs will evaluate to `undefined` in V2 unless they migrate to Parameterized Configuration or standard `.env` variables first. + +# Principles of Safe Migration +Always follow these principles to ensure zero-touch logic migration: +1. **Use Context-Aware Editing over Global Regex**: Never use naive find-and-replace. Rely on syntax-aware editing (such as an AI agent reading the file context and making precise edits, or tools like ts-morph/ast parsers) to ensure context isolation. +2. **Signature Modernization with Destructuring**: Do NOT rewrite the internal variable usages of context or params inside the function body. Instead, use JavaScript's native object destructuring in the new V2 signature parameters. + +### 🛡️ Example Transformation + +#### Before (V1 Legacy) +```typescript +import * as functions from "firebase-functions"; +export const processOrder = functions.pubsub.topic("orders").onPublish((message, context) => { + const orderId = message.json.id; + console.log(`Processing order ${orderId} at ${context.timestamp}`); +}); +``` + +#### After (V2 Target - Safe Migration) +```typescript +import { onMessagePublished } from "firebase-functions/v2/pubsub"; +// Using direct object destructuring in the signature! +export const processOrder = onMessagePublished("orders", ({ message, context }) => { + const orderId = message.json.id; // Legacy logic remains untouched! + console.log(`Processing order ${orderId} at ${context.timestamp}`); +}); +``` + +# Verification +After making any migration edits, immediately run the following verification steps: +1. Run `npm run build` to ensure the TypeScript compiler is happy with the types and parameters. +2. Run `npm test` to verify no regressions occurred in existing unit tests. + +> [!WARNING] +> **Test Signature Mismatch**: The destructuring shim changes the function signature from two arguments `(data, context)` to a single destructured object `({ message, context })`. +> +> Existing V1 unit tests that invoke the function with two parameters separately (e.g., `myFn(mockData, mockContext)`) **will fail** because the function treats `mockData` as the entire event object. You will need to update test calls to pass a single object: `myFn({ message: mockData, context: mockContext })`. + +# References +- **Deep Dive into Shims**: See the architectural choices for the shim in [destructuring-shim.md](references/destructuring-shim.md). +- **Function Name Mapping**: See the V1 vs V2 function signature mapping table in [signature-mapping.md](references/signature-mapping.md). diff --git a/skills/firebase-v1-v2-migration/references/destructuring-shim.md b/skills/firebase-v1-v2-migration/references/destructuring-shim.md new file mode 100644 index 00000000..b2beab0e --- /dev/null +++ b/skills/firebase-v1-v2-migration/references/destructuring-shim.md @@ -0,0 +1,45 @@ +# Architectural Deep Dive: Destructuring Compatibility Shim + +The Destructuring Compatibility Shim is a **Zero-Touch Logic Migration** pattern. It allows you to upgrade a function's infrastructure to V2 (and take advantage of GCF 2nd Gen runtimes) without rewriting any of your internal business logic. + +--- + +## 🛠️ How it Works + +When you migrate a V1 function to V2, the signature changes from two parameters `(data, context)` to a single `CloudEvent` object. + +Instead of manually rewriting all usages of `context.params` or `message.json` inside the function, you use JavaScript's **Object Destructuring** in the signature. + +### Example Transformation + +#### Step 1: Legacy V1 + +```typescript +export const processOrder = functions.pubsub.topic("orders").onPublish((message, context) => { + const orderId = message.json.id; + console.log(`Processing order ${orderId} at ${context.timestamp}`); +}); +``` + +#### Step 2: Modern V2 + Shim + +We change the trigger to `onMessagePublished`, and instead of accepting `event`, we destructure `{ message, context }` directly: + +```typescript +export const processOrder = onMessagePublished("orders", ({ message, context }) => { + const orderId = message.json.id; // Legacy logic remains untouched! + console.log(`Processing order ${orderId} at ${context.timestamp}`); +}); +``` + +### 🧠 Why This Works + +The Firebase Functions SDK has been updated to provide **Lazy Getters** on the `CloudEvent` object for standard event triggers. When you attempt to destructure `{ message, context }` from the event, the SDK transparently maps the V2 event properties back into V1-compatible objects on the fly! + +--- + +## 🛡️ Best Practices for AI Agents + +1. **Do NOT attempt to rewrite variable access inside the body.** It is error-prone and time-consuming. +2. **Rely on the shim by default.** Only attempt a pure V2 rewrite if the user explicitly asks for "clean-room" modernization. +3. **Always type-check (`tsc`) after the rewrite.** If the types are wrong, the shim might not be fully supported for that specific provider yet! diff --git a/skills/firebase-v1-v2-migration/references/signature-mapping.md b/skills/firebase-v1-v2-migration/references/signature-mapping.md new file mode 100644 index 00000000..0476015d --- /dev/null +++ b/skills/firebase-v1-v2-migration/references/signature-mapping.md @@ -0,0 +1,27 @@ +# Firebase Functions V1 vs V2 Function Mapping + +This reference table maps legacy V1 functions to their modern V2 equivalents. Use this table to find the correct V2 function names when migrating. + +| Category | V1 Function | V2 Function | Status | Notes | +| :--- | :--- | :--- | :--- | :--- | +| **Auth** | `auth.user().beforeSignIn()` | `identity.beforeUserSignedIn()` | **Available** | Renamed to `beforeUserSignedIn`. | +| **Auth** | `auth.user().beforeCreate()` | `identity.beforeUserCreated()` | **Available** | | +| **Auth** | `auth.user().beforeEmail()` | `identity.beforeEmailSent()` | **Available** | Renamed to `beforeEmailSent`. | +| **Auth** | `auth.user().beforeSms()` | `identity.beforeSmsSent()` | **Available** | | +| **Database** | `database.ref().onWrite()` | `database.onValueWritten()` | **Available** | | +| **Database** | `database.ref().onCreate()` | `database.onValueCreated()` | **Available** | | +| **Database** | `database.ref().onUpdate()` | `database.onValueUpdated()` | **Available** | | +| **Database** | `database.ref().onDelete()` | `database.onValueDeleted()` | **Available** | | +| **Firestore** | `firestore.document().onWrite()` | `firestore.onDocumentWritten()` | **Available** | | +| **Firestore** | `firestore.document().onCreate()` | `firestore.onDocumentCreated()` | **Available** | | +| **Firestore** | `firestore.document().onUpdate()` | `firestore.onDocumentUpdated()` | **Available** | | +| **Firestore** | `firestore.document().onDelete()` | `firestore.onDocumentDeleted()` | **Available** | | +| **Pub/Sub** | `pubsub.topic().onPublish()` | `pubsub.onMessagePublished()` | **Available** | | +| **Pub/Sub** | `pubsub.schedule().onRun()` | `scheduler.onSchedule()` | **Available** | Moved to `scheduler` namespace. | +| **Storage** | `storage.object().onArchive()` | `storage.onObjectArchived()` | **Available** | | +| **Storage** | `storage.object().onDelete()` | `storage.onObjectDeleted()` | **Available** | | +| **Storage** | `storage.object().onFinalize()` | `storage.onObjectFinalized()` | **Available** | | +| **Storage** | `storage.object().onMetadataUpdate()` | `storage.onObjectMetadataUpdated()` | **Available** | | +| **HTTPS** | `https.onRequest()` | `https.onRequest()` | **Both** | Same name, different module. | +| **HTTPS** | `https.onCall()` | `https.onCall()` | **Both** | Different parameter type (`CallableRequest`). | +| **Tasks** | `tasks.taskQueue().onDispatch()` | `tasks.onTaskDispatched()` | **Available** | | From 06fffc68b64123300154ce09aa88c8a7be1fcca1 Mon Sep 17 00:00:00 2001 From: Varun Shetty Date: Wed, 1 Apr 2026 17:27:45 +0000 Subject: [PATCH 2/7] improving reference documents --- skills/firebase-v1-v2-migration/SKILL.md | 8 +- .../references/destructuring-shim.md | 47 ++++++++ .../references/signature-mapping.md | 108 +++++++++++++----- 3 files changed, 134 insertions(+), 29 deletions(-) diff --git a/skills/firebase-v1-v2-migration/SKILL.md b/skills/firebase-v1-v2-migration/SKILL.md index 48ed3441..305fb643 100644 --- a/skills/firebase-v1-v2-migration/SKILL.md +++ b/skills/firebase-v1-v2-migration/SKILL.md @@ -15,7 +15,7 @@ Before modifying any code, the agent should run a quick scan: # Principles of Safe Migration Always follow these principles to ensure zero-touch logic migration: 1. **Use Context-Aware Editing over Global Regex**: Never use naive find-and-replace. Rely on syntax-aware editing (such as an AI agent reading the file context and making precise edits, or tools like ts-morph/ast parsers) to ensure context isolation. -2. **Signature Modernization with Destructuring**: Do NOT rewrite the internal variable usages of context or params inside the function body. Instead, use JavaScript's native object destructuring in the new V2 signature parameters. +2. **Signature Modernization with Destructuring**: Do NOT rewrite the internal variable usages of context or params inside the function body. Instead, use JavaScript's native object destructuring in the new V2 signature parameters. (Note: For `https.onCall`, the context shim is not available; you should destructure `auth` and `data` directly from the request object instead of expecting a `.context` property). ### 🛡️ Example Transformation @@ -46,7 +46,11 @@ After making any migration edits, immediately run the following verification ste > [!WARNING] > **Test Signature Mismatch**: The destructuring shim changes the function signature from two arguments `(data, context)` to a single destructured object `({ message, context })`. > -> Existing V1 unit tests that invoke the function with two parameters separately (e.g., `myFn(mockData, mockContext)`) **will fail** because the function treats `mockData` as the entire event object. You will need to update test calls to pass a single object: `myFn({ message: mockData, context: mockContext })`. +> Existing V1 unit tests that invoke the function with two parameters separately (e.g., `myFn(mockData, mockContext)`) **will fail** because the function treats `mockData` as the entire event object. You will need to update test calls to pass a single object. +> +> **Crucial**: The key name in the test mock must match the specific **Shimmed Key** for that trigger (e.g., `change` for `onDocumentWritten`, `snapshot` for `onDocumentCreated`, `message` for PubSub, or `object` for Storage). +> +> Example: `myFn({ change: mockChange, context: mockContext })` or `myFn({ message: mockMessage, context: mockContext })`. See [signature-mapping.md](references/signature-mapping.md) for the exact keys. # References - **Deep Dive into Shims**: See the architectural choices for the shim in [destructuring-shim.md](references/destructuring-shim.md). diff --git a/skills/firebase-v1-v2-migration/references/destructuring-shim.md b/skills/firebase-v1-v2-migration/references/destructuring-shim.md index b2beab0e..ebfe25e8 100644 --- a/skills/firebase-v1-v2-migration/references/destructuring-shim.md +++ b/skills/firebase-v1-v2-migration/references/destructuring-shim.md @@ -38,8 +38,55 @@ The Firebase Functions SDK has been updated to provide **Lazy Getters** on the ` --- +## 📖 Provider Mapping Examples + +Here are the exact destructuring patterns for every supported V2 provider: + +### 1. Cloud Firestore + +* **Created / Deleted** triggers: + ```typescript + // V2: onDocumentCreated, onDocumentDeleted + export const processDoc = onDocumentCreated("users/{id}", ({ snapshot, context }) => { ... }); + ``` +* **Updated / Written** triggers: + ```typescript + // V2: onDocumentUpdated, onDocumentWritten + export const processDoc = onDocumentUpdated("users/{id}", ({ change, context }) => { ... }); + ``` + +### 2. Cloud Storage + +* **All** triggers (`onObjectFinalized`, `onObjectDeleted`, `onObjectArchived`, `onObjectMetadataUpdated`): + ```typescript + export const processFile = onObjectFinalized(({ object, context }) => { ... }); + ``` + +### 3. Realtime Database + +* **Created / Deleted** triggers: + ```typescript + export const processData = onValueCreated("/users/{id}", ({ snapshot, context }) => { ... }); + ``` +* **Updated / Written** triggers: + ```typescript + export const processData = onValueWritten("/users/{id}", ({ change, context }) => { ... }); + ``` + +### 4. Remote Config + +* **Updated** triggers: + ```typescript + export const processConfig = onConfigUpdated(({ version, context }) => { ... }); + ``` + +--- + ## 🛡️ Best Practices for AI Agents 1. **Do NOT attempt to rewrite variable access inside the body.** It is error-prone and time-consuming. 2. **Rely on the shim by default.** Only attempt a pure V2 rewrite if the user explicitly asks for "clean-room" modernization. 3. **Always type-check (`tsc`) after the rewrite.** If the types are wrong, the shim might not be fully supported for that specific provider yet! +4. **HTTPS Callables (Flattened Context)**: Unlike event triggers, Callables do **not** use `V1Compat` or a `context` object. Instead, all context properties are flattened onto the request object. + * **V1 Priority**: `(data, context) => { ... }` + * **V2 Equivalent**: `({ data, auth, app }) => { ... }` diff --git a/skills/firebase-v1-v2-migration/references/signature-mapping.md b/skills/firebase-v1-v2-migration/references/signature-mapping.md index 0476015d..c4d2e332 100644 --- a/skills/firebase-v1-v2-migration/references/signature-mapping.md +++ b/skills/firebase-v1-v2-migration/references/signature-mapping.md @@ -1,27 +1,81 @@ -# Firebase Functions V1 vs V2 Function Mapping - -This reference table maps legacy V1 functions to their modern V2 equivalents. Use this table to find the correct V2 function names when migrating. - -| Category | V1 Function | V2 Function | Status | Notes | -| :--- | :--- | :--- | :--- | :--- | -| **Auth** | `auth.user().beforeSignIn()` | `identity.beforeUserSignedIn()` | **Available** | Renamed to `beforeUserSignedIn`. | -| **Auth** | `auth.user().beforeCreate()` | `identity.beforeUserCreated()` | **Available** | | -| **Auth** | `auth.user().beforeEmail()` | `identity.beforeEmailSent()` | **Available** | Renamed to `beforeEmailSent`. | -| **Auth** | `auth.user().beforeSms()` | `identity.beforeSmsSent()` | **Available** | | -| **Database** | `database.ref().onWrite()` | `database.onValueWritten()` | **Available** | | -| **Database** | `database.ref().onCreate()` | `database.onValueCreated()` | **Available** | | -| **Database** | `database.ref().onUpdate()` | `database.onValueUpdated()` | **Available** | | -| **Database** | `database.ref().onDelete()` | `database.onValueDeleted()` | **Available** | | -| **Firestore** | `firestore.document().onWrite()` | `firestore.onDocumentWritten()` | **Available** | | -| **Firestore** | `firestore.document().onCreate()` | `firestore.onDocumentCreated()` | **Available** | | -| **Firestore** | `firestore.document().onUpdate()` | `firestore.onDocumentUpdated()` | **Available** | | -| **Firestore** | `firestore.document().onDelete()` | `firestore.onDocumentDeleted()` | **Available** | | -| **Pub/Sub** | `pubsub.topic().onPublish()` | `pubsub.onMessagePublished()` | **Available** | | -| **Pub/Sub** | `pubsub.schedule().onRun()` | `scheduler.onSchedule()` | **Available** | Moved to `scheduler` namespace. | -| **Storage** | `storage.object().onArchive()` | `storage.onObjectArchived()` | **Available** | | -| **Storage** | `storage.object().onDelete()` | `storage.onObjectDeleted()` | **Available** | | -| **Storage** | `storage.object().onFinalize()` | `storage.onObjectFinalized()` | **Available** | | -| **Storage** | `storage.object().onMetadataUpdate()` | `storage.onObjectMetadataUpdated()` | **Available** | | -| **HTTPS** | `https.onRequest()` | `https.onRequest()` | **Both** | Same name, different module. | -| **HTTPS** | `https.onCall()` | `https.onCall()` | **Both** | Different parameter type (`CallableRequest`). | -| **Tasks** | `tasks.taskQueue().onDispatch()` | `tasks.onTaskDispatched()` | **Available** | | +# Firebase Functions V1 vs V2 Signature Mapping + +This reference maps legacy V1 functions to their modern V2 equivalents. It includes the **Shimmed Parameter Key** you should use when destructuring the V2 event object to preserve V1 business logic. + +--- + +## 🔥 Cloud Firestore + +| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | +| :--- | :--- | :--- | :--- | +| `firestore.document().onWrite()` | `onDocumentWritten()` | `change` | `({ change, context })` | +| `firestore.document().onCreate()` | `onDocumentCreated()` | `snapshot` | `({ snapshot, context })` | +| `firestore.document().onUpdate()` | `onDocumentUpdated()` | `change` | `({ change, context })` | +| `firestore.document().onDelete()` | `onDocumentDeleted()` | `snapshot` | `({ snapshot, context })` | + +--- + +## 📨 Cloud Pub/Sub + +| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | +| :--- | :--- | :--- | :--- | +| `pubsub.topic().onPublish()` | `onMessagePublished()` | `message` | `({ message, context })` | +| `pubsub.schedule().onRun()` | `scheduler.onSchedule()` | **N/A** | Access `event` directly | + +> [!NOTE] +> Scheduled functions moved from the `pubsub` namespace to the `scheduler` namespace in V2. + +--- + +## 💾 Realtime Database + +| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | +| :--- | :--- | :--- | :--- | +| `database.ref().onWrite()` | `onValueWritten()` | `change` | `({ change, context })` | +| `database.ref().onCreate()` | `onValueCreated()` | `snapshot` | `({ snapshot, context })` | +| `database.ref().onUpdate()` | `onValueUpdated()` | `change` | `({ change, context })` | +| `database.ref().onDelete()` | `onValueDeleted()` | `snapshot` | `({ snapshot, context })` | + +--- + +## 🗄️ Cloud Storage + +| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | +| :--- | :--- | :--- | :--- | +| `storage.object().onArchive()` | `onObjectArchived()` | `object` | `({ object, context })` | +| `storage.object().onDelete()` | `onObjectDeleted()` | `object` | `({ object, context })` | +| `storage.object().onFinalize()` | `onObjectFinalized()` | `object` | `({ object, context })` | +| `storage.object().onMetadataUpdate()` | `onObjectMetadataUpdated()` | `object` | `({ object, context })` | + +--- + +## 🌐 HTTP / Callables + +| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | +| :--- | :--- | :--- | :--- | +| `https.onRequest()` | `https.onRequest()` | **N/A** | Standard Express `(req, res)` | +| `https.onCall()` | `https.onCall()` | **N/A** | Destructure `({ data, auth })` | + +> [!IMPORTANT] +> **HTTP Callables do NOT use the Destructuring Shim.** +> In V2, the handler receives a single `CallableRequest` object (not a `CloudEvent`). You should destructure properties like `data`, `auth`, and `app` directly from it. The traditional `context` object is **unavailable**. + +--- + +## 🔑 Auth (Blocking) + +| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | +| :--- | :--- | :--- | :--- | +| `auth.user().beforeSignIn()` | `identity.beforeUserSignedIn()` | **N/A** | Access `event` directly | +| `auth.user().beforeCreate()` | `identity.beforeUserCreated()` | **N/A** | Access `event` directly | + +> [!NOTE] +> Auth Blocking triggers moved to the `identity` namespace in V2. + +--- + +## ⏰ Cloud Tasks + +| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | +| :--- | :--- | :--- | :--- | +| `tasks.taskQueue().onDispatch()` | `tasks.onTaskDispatched()` | **N/A** | Access `event` directly | From 00a1b540fd25e888624c5eda83daed429c29668c Mon Sep 17 00:00:00 2001 From: Varun Shetty Date: Thu, 2 Apr 2026 17:14:47 +0000 Subject: [PATCH 3/7] Addressing review comments --- skills/firebase-v1-v2-migration/SKILL.md | 20 +++++ .../references/configuration-migration.md | 88 +++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 skills/firebase-v1-v2-migration/references/configuration-migration.md diff --git a/skills/firebase-v1-v2-migration/SKILL.md b/skills/firebase-v1-v2-migration/SKILL.md index 305fb643..003431d8 100644 --- a/skills/firebase-v1-v2-migration/SKILL.md +++ b/skills/firebase-v1-v2-migration/SKILL.md @@ -2,6 +2,17 @@ name: firebase-v1-v2-migration description: Use this skill when a user wants to upgrade their legacy Firebase Functions from V1 (GCF 1st Gen) to V2 (GCF 2nd Gen) safely without rewriting their internal business logic. This skill relies on the Destructuring Compatibility Shim. --- +# 🚀 Supported Migration Strategies + +This skill currently supports the **In-Place Migration** strategy. + +### 🚜 In-Place Migration (Standard) +The agent modifies the existing V1 code file directly to use V2 syntax and overwrites the deployment slot. +* **Pros**: Fast, simple, clean repository history. +* **Cons**: No safety net during deployment. If the V2 deployment fails, you must rollback using Git. + +*Note: For complex or zero-downtime migrations (e.g. Side-by-Side deployment), refer to external orchestration skills.* + # Prerequisites Please ensure the workspace is ready for V2 before attempting a code migration: 1. **Configuration Check**: Ensure the workspace has transitioned away from functions.config() to Parameterized Configuration or standard environment variables. @@ -52,6 +63,15 @@ After making any migration edits, immediately run the following verification ste > > Example: `myFn({ change: mockChange, context: mockContext })` or `myFn({ message: mockMessage, context: mockContext })`. See [signature-mapping.md](references/signature-mapping.md) for the exact keys. +# 💸 Performance & Cost Considerations + +In Firebase Functions V2, you can handle multiple requests concurrently per instance (up to 1,000 requests, default 80 if CPU >= 1). However, enabling concurrency requires assigning at least 1 full CPU. + +* **V1 Cost Parity**: If you want to keep V1 fractional CPU pricing (and disable concurrency), you must explicitly set `cpu: "gcf_gen1"`. +* **Modernization**: If you want to take advantage of Concurrency, you must assign at least 1 CPU. + +See [configuration-migration.md](references/configuration-migration.md) for how to set these options. + # References - **Deep Dive into Shims**: See the architectural choices for the shim in [destructuring-shim.md](references/destructuring-shim.md). - **Function Name Mapping**: See the V1 vs V2 function signature mapping table in [signature-mapping.md](references/signature-mapping.md). diff --git a/skills/firebase-v1-v2-migration/references/configuration-migration.md b/skills/firebase-v1-v2-migration/references/configuration-migration.md new file mode 100644 index 00000000..d7f32f39 --- /dev/null +++ b/skills/firebase-v1-v2-migration/references/configuration-migration.md @@ -0,0 +1,88 @@ +# Migrating Runtime Configurations (runWith) + +In Firebase Functions V1, you configured runtime settings like memory, timeout, and service accounts using `.runWith()`. In V2, `.runWith()` is removed and replaced by a more flexible options system. + +You can configure V2 functions in two ways: **Globally** (for all functions in a file) or **Per-Function**. + +--- + +## 🌍 1. Global Configuration (`setGlobalOptions`) + +Use `setGlobalOptions` at the top of your file to set defaults for all functions defined after it. + +### V1 Legacy + +```typescript +import * as functions from "firebase-functions"; + +export const myFn = functions + .runWith({ + memory: "1GB", + timeoutSeconds: 120, + serviceAccount: "custom-sa@my-project.iam.gserviceaccount.com", + }) + .https.onRequest((req, res) => { ... }); +``` + +### V2 Modern Equivalent + +```typescript +import { setGlobalOptions } from "firebase-functions/v2"; +import { onRequest } from "firebase-functions/v2/https"; + +// Set global defaults for this file +setGlobalOptions({ + memory: "1GiB", // Note: GiB instead of GB is preferred in V2 types + timeoutSeconds: 120, + serviceAccount: "custom-sa@my-project.iam.gserviceaccount.com", +}); + +export const myFn = onRequest((req, res) => { ... }); +``` + +--- + +## 🎯 2. Per-Function Configuration + +Pass the configuration object as the **first argument** to the V2 trigger function. + +### V1 Legacy + +```typescript +export const processOrder = functions + .runWith({ memory: "2GB" }) + .pubsub.topic("orders") + .onPublish((message, context) => { ... }); +``` + +### V2 Modern Equivalent + +```typescript +import { onMessagePublished } from "firebase-functions/v2/pubsub"; + +export const processOrder = onMessagePublished( + { + topic: "orders", + memory: "2GiB", // Options passed as the first argument! + }, + ({ message, context }) => { ... } // Destructuring shim pattern +); +``` + +> [!TIP] +> **Memory Unit Caveat**: V1 accepted `"1GB"`. V2 types strongly prefer IEC units like `"1GiB"`, `"2GiB"`, etc. + +--- + +## ⚠️ Common Property Translations + +| V1 Property | V2 Property | Notes | +| :--- | :--- | :--- | +| `memory` | `memory` | Use `"1GiB"` instead of `"1GB"`. | +| `timeoutSeconds` | `timeoutSeconds` | Same. | +| `ingressSettings` | `ingressSettings` | Same. | +| `vpcConnector` | `vpcConnector` | Same. | +| `vpcConnectorEgressSettings` | `vpcConnectorEgressSettings` | Same. | +| `serviceAccount` | `serviceAccount` | Same. | +| `secrets` | `secrets` | Same. | +| `failurePolicy` | `retry` | Renamed to boolean `retry: true/false` in V2 Eventarc triggers. | From 2793d38d44f237dc638a7614de3a683ae9a970f2 Mon Sep 17 00:00:00 2001 From: Varun Shetty Date: Tue, 26 May 2026 19:58:14 +0000 Subject: [PATCH 4/7] Update destructuring shim documentation --- .../references/destructuring-shim.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/skills/firebase-v1-v2-migration/references/destructuring-shim.md b/skills/firebase-v1-v2-migration/references/destructuring-shim.md index ebfe25e8..c1210fdf 100644 --- a/skills/firebase-v1-v2-migration/references/destructuring-shim.md +++ b/skills/firebase-v1-v2-migration/references/destructuring-shim.md @@ -34,7 +34,7 @@ export const processOrder = onMessagePublished("orders", ({ message, context }) ### 🧠 Why This Works -The Firebase Functions SDK has been updated to provide **Lazy Getters** on the `CloudEvent` object for standard event triggers. When you attempt to destructure `{ message, context }` from the event, the SDK transparently maps the V2 event properties back into V1-compatible objects on the fly! +The Firebase Functions SDK uses a utility called `addV1Compat` to attach these properties via **Lazy Getters** on the `CloudEvent` object for standard event triggers. When you attempt to destructure `{ message, context }` from the event, the SDK transparently maps the V2 event properties back into V1-compatible objects on the fly! This feature is available in modern V2 environments supported by the SDK. --- @@ -90,3 +90,11 @@ Here are the exact destructuring patterns for every supported V2 provider: 4. **HTTPS Callables (Flattened Context)**: Unlike event triggers, Callables do **not** use `V1Compat` or a `context` object. Instead, all context properties are flattened onto the request object. * **V1 Priority**: `(data, context) => { ... }` * **V2 Equivalent**: `({ data, auth, app }) => { ... }` + +--- + +## 🔗 Related Migrations + +Migrating event signatures is only one part of moving from V1 to V2. If your functions use `functions.config()`, you should also migrate to the new parameterized configuration system (`defineString`, `defineSecret`, etc.). + +For more details on how to migrate configuration parameters, refer to the documentation in PR #67 or the `firebase-functions-params-refactor` skill. From 2836df2bf2092008e7a81815fe7daf8a788ad90a Mon Sep 17 00:00:00 2001 From: Varun Shetty Date: Tue, 26 May 2026 20:13:32 +0000 Subject: [PATCH 5/7] improvements for functions.config() migration --- .../references/configuration-migration.md | 38 +++++++++++++++++++ .../references/destructuring-shim.md | 23 ++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/skills/firebase-v1-v2-migration/references/configuration-migration.md b/skills/firebase-v1-v2-migration/references/configuration-migration.md index d7f32f39..4fae5700 100644 --- a/skills/firebase-v1-v2-migration/references/configuration-migration.md +++ b/skills/firebase-v1-v2-migration/references/configuration-migration.md @@ -86,3 +86,41 @@ export const processOrder = onMessagePublished( | `serviceAccount` | `serviceAccount` | Same. | | `secrets` | `secrets` | Same. | | `failurePolicy` | `retry` | Renamed to boolean `retry: true/false` in V2 Eventarc triggers. | + +--- + +## 🔐 3. Migrating Environment Configurations (`functions.config()`) + +In V1, you used `functions.config()` to access environment configuration. In V2, this is replaced by **Parameterized Configuration**. + +### Deterministic Rules for Migration + +Follow these rules to ensure a deterministic and safe migration: + +#### Typing +* **Numbers**: If the value is used as a number, use `defineNumber`. +* **Secrets**: If the key contains "KEY", "SECRET", "TOKEN", or "PASSWORD", use `defineSecret()`. + * *Note*: Secrets MUST be explicitly bound to the function that uses them in the options object (e.g., `{ secrets: [MY_SECRET] }`). +* **Lists**: Use `defineList` for comma-separated lists. +* **JSON**: Use `defineJSON` for JSON strings. +* **Buckets**: If the param is a storage bucket, set `input: 'BUCKET_PICKER'`. + +#### Initialization & Scope +* **Global Initialization**: If a variable was initialized globally in V1 (e.g., `const client = new Client(functions.config().key)`), you must split it to have declaration at global scope and initialization inside `onInit`: + ```typescript + import { onInit } from "firebase-functions/v2"; + + const myKey = defineSecret("MY_KEY"); + let client: Client; + + onInit(() => { + client = new Client(myKey.value()); + }); + ``` + +#### Advanced Interpolation & Logic +* **String Interpolation**: Use the `expr` tagged template literal from `firebase-functions/params` (e.g., `expr`every ${period} days``) instead of standard template literals when constructing dynamic strings with parameters. Do NOT call `.value()` inside `expr`. +* **Logic Operators**: Use expressions like `projectID.equals('prod').thenElse(1, 0)` for logical operations instead of ternary operators on `.value()`. + +#### Built-ins +* Prefer built-in variables like `databaseURL`, `projectID`, `gcloudProject`, `storageBucket` rather than defining new params for these values. diff --git a/skills/firebase-v1-v2-migration/references/destructuring-shim.md b/skills/firebase-v1-v2-migration/references/destructuring-shim.md index c1210fdf..fe4e0426 100644 --- a/skills/firebase-v1-v2-migration/references/destructuring-shim.md +++ b/skills/firebase-v1-v2-migration/references/destructuring-shim.md @@ -95,6 +95,25 @@ Here are the exact destructuring patterns for every supported V2 provider: ## 🔗 Related Migrations -Migrating event signatures is only one part of moving from V1 to V2. If your functions use `functions.config()`, you should also migrate to the new parameterized configuration system (`defineString`, `defineSecret`, etc.). +Migrating event signatures is only one part of moving from V1 to V2. Another critical area is configuration management. -For more details on how to migrate configuration parameters, refer to the documentation in PR #67 or the `firebase-functions-params-refactor` skill. +### Parameterized Configuration +If your functions use `functions.config()`, you should migrate to the new **Parameterized Configuration** system in V2. The destructuring shim handles event signatures, but it does not shim `functions.config()`. + +#### How to Migrate: +1. **Identify Usages**: Search for `functions.config().path.to.value`. +2. **Define Parameters**: At the top of your file, define the parameter using the appropriate primitive from `firebase-functions/params` (available types include `defineString`, `defineSecret`, `defineInt`, `defineBoolean`, `defineList`, and `defineJSON`): + ```typescript + import { defineString, defineSecret } from "firebase-functions/params"; + + const stripeKey = defineSecret("STRIPE_KEY"); + const apiDomain = defineString("API_DOMAIN"); + ``` +3. **Access Values**: Replace the V1 call with the `.value()` method of the defined parameter: + * **V1**: `const key = functions.config().stripe.key;` + * **V2**: `const key = stripeKey.value();` + +> [!NOTE] +> When you migrate a function to V2 (even with the destructuring shim), `functions.config()` will return `undefined` unless you have explicitly set up environment variables or are running in a specific emulation mode. Parameterized configuration is the standard and recommended way to handle this in V2. + +For a complete guide and deterministic rules on how to migrate configurations, refer to [configuration-migration.md](configuration-migration.md). From 6fc753f7b9a9a41b2dac4e097d610f9aba3bc36d Mon Sep 17 00:00:00 2001 From: Varun Shetty Date: Thu, 9 Jul 2026 19:32:28 +0000 Subject: [PATCH 6/7] Update firebase-v1-v2-migration skill with V2 SDK features, import consistency, and formatting fixes --- skills/firebase-v1-v2-migration/SKILL.md | 49 +++++++--- .../references/configuration-migration.md | 74 +++++++------- .../references/destructuring-shim.md | 96 ++++++++++--------- .../references/signature-mapping.md | 87 +++++++++-------- 4 files changed, 168 insertions(+), 138 deletions(-) diff --git a/skills/firebase-v1-v2-migration/SKILL.md b/skills/firebase-v1-v2-migration/SKILL.md index 003431d8..7c6c1fe5 100644 --- a/skills/firebase-v1-v2-migration/SKILL.md +++ b/skills/firebase-v1-v2-migration/SKILL.md @@ -2,35 +2,45 @@ name: firebase-v1-v2-migration description: Use this skill when a user wants to upgrade their legacy Firebase Functions from V1 (GCF 1st Gen) to V2 (GCF 2nd Gen) safely without rewriting their internal business logic. This skill relies on the Destructuring Compatibility Shim. --- + # 🚀 Supported Migration Strategies This skill currently supports the **In-Place Migration** strategy. ### 🚜 In-Place Migration (Standard) + The agent modifies the existing V1 code file directly to use V2 syntax and overwrites the deployment slot. -* **Pros**: Fast, simple, clean repository history. -* **Cons**: No safety net during deployment. If the V2 deployment fails, you must rollback using Git. + +- **Pros**: Fast, simple, clean repository history. +- **Cons**: No safety net during deployment. If the V2 deployment fails, you must rollback using Git. *Note: For complex or zero-downtime migrations (e.g. Side-by-Side deployment), refer to external orchestration skills.* # Prerequisites + Please ensure the workspace is ready for V2 before attempting a code migration: -1. **Configuration Check**: Ensure the workspace has transitioned away from functions.config() to Parameterized Configuration or standard environment variables. -2. **Dependencies**: The project must be using firebase-functions version that supports V2 (>= 4.0.0). + +1. **Configuration Check**: Ensure the workspace has transitioned away from functions.config() to Parameterized Configuration or standard environment variables. +1. **Dependencies**: The project must be using firebase-functions version that supports V2 (>= 4.0.0). # 🔍 Pre-Migration Checklist + Before modifying any code, the agent should run a quick scan: -1. **Scan for legacy configs**: Run a `grep` or text search for usages of `functions.config()`. - - **Action**: If found, **stop and warn the user** that these configs will evaluate to `undefined` in V2 unless they migrate to Parameterized Configuration or standard `.env` variables first. + +1. **Scan for legacy configs**: Run a `grep` or text search for usages of `functions.config()`. + - **Action**: If found, **stop and warn the user** that these configs will evaluate to `undefined` in V2 unless they migrate to Parameterized Configuration or standard `.env` variables first. # Principles of Safe Migration + Always follow these principles to ensure zero-touch logic migration: -1. **Use Context-Aware Editing over Global Regex**: Never use naive find-and-replace. Rely on syntax-aware editing (such as an AI agent reading the file context and making precise edits, or tools like ts-morph/ast parsers) to ensure context isolation. -2. **Signature Modernization with Destructuring**: Do NOT rewrite the internal variable usages of context or params inside the function body. Instead, use JavaScript's native object destructuring in the new V2 signature parameters. (Note: For `https.onCall`, the context shim is not available; you should destructure `auth` and `data` directly from the request object instead of expecting a `.context` property). + +1. **Use Context-Aware Editing over Global Regex**: Never use naive find-and-replace. Rely on syntax-aware editing (such as an AI agent reading the file context and making precise edits, or tools like ts-morph/ast parsers) to ensure context isolation. +1. **Signature Modernization with Destructuring**: Do NOT rewrite the internal variable usages of context or params inside the function body. Instead, use JavaScript's native object destructuring in the new V2 signature parameters. (Note: For `https.onCall`, the context shim is not available; you should destructure `auth` and `data` directly from the request object instead of expecting a `.context` property). ### 🛡️ Example Transformation #### Before (V1 Legacy) + ```typescript import * as functions from "firebase-functions"; export const processOrder = functions.pubsub.topic("orders").onPublish((message, context) => { @@ -40,6 +50,7 @@ export const processOrder = functions.pubsub.topic("orders").onPublish((message, ``` #### After (V2 Target - Safe Migration) + ```typescript import { onMessagePublished } from "firebase-functions/v2/pubsub"; // Using direct object destructuring in the signature! @@ -50,16 +61,18 @@ export const processOrder = onMessagePublished("orders", ({ message, context }) ``` # Verification + After making any migration edits, immediately run the following verification steps: + 1. Run `npm run build` to ensure the TypeScript compiler is happy with the types and parameters. -2. Run `npm test` to verify no regressions occurred in existing unit tests. +1. Run `npm test` to verify no regressions occurred in existing unit tests. > [!WARNING] -> **Test Signature Mismatch**: The destructuring shim changes the function signature from two arguments `(data, context)` to a single destructured object `({ message, context })`. +> **Test Signature Mismatch**: The destructuring shim changes the function signature from two arguments `(data, context)` to a single destructured object `({ message, context })`. > -> Existing V1 unit tests that invoke the function with two parameters separately (e.g., `myFn(mockData, mockContext)`) **will fail** because the function treats `mockData` as the entire event object. You will need to update test calls to pass a single object. +> Existing V1 unit tests that invoke the function with two parameters separately (e.g., `myFn(mockData, mockContext)`) **will fail** because the function treats `mockData` as the entire event object. You will need to update test calls to pass a single object. > -> **Crucial**: The key name in the test mock must match the specific **Shimmed Key** for that trigger (e.g., `change` for `onDocumentWritten`, `snapshot` for `onDocumentCreated`, `message` for PubSub, or `object` for Storage). +> **Crucial**: The key name in the test mock must match the specific **Shimmed Key** for that trigger (e.g., `change` for `onDocumentWritten`, `snapshot` for `onDocumentCreated`, `message` for PubSub, or `object` for Storage). > > Example: `myFn({ change: mockChange, context: mockContext })` or `myFn({ message: mockMessage, context: mockContext })`. See [signature-mapping.md](references/signature-mapping.md) for the exact keys. @@ -67,11 +80,19 @@ After making any migration edits, immediately run the following verification ste In Firebase Functions V2, you can handle multiple requests concurrently per instance (up to 1,000 requests, default 80 if CPU >= 1). However, enabling concurrency requires assigning at least 1 full CPU. -* **V1 Cost Parity**: If you want to keep V1 fractional CPU pricing (and disable concurrency), you must explicitly set `cpu: "gcf_gen1"`. -* **Modernization**: If you want to take advantage of Concurrency, you must assign at least 1 CPU. +- **V1 Cost Parity**: If you want to keep V1 fractional CPU pricing (and disable concurrency), you must explicitly set `cpu: "gcf_gen1"`. +- **Modernization**: If you want to take advantage of Concurrency, you must assign at least 1 CPU. See [configuration-migration.md](references/configuration-migration.md) for how to set these options. +# 🛠️ Additional V2 SDK Features + +When upgrading to V2, take advantage of modern V2 SDK features where applicable: + +- **Declarative Security (IAM Roles & APIs)**: Use `requiresRole("roles/...")` and `requiresAPI("service.googleapis.com", "reason")` from `firebase-functions/v2` so IAM role and Google Cloud API dependencies are declared directly in code instead of instructing users to run manual `gcloud` commands. +- **Lifecycle Hooks**: Use post-deployment lifecycle hooks (`afterFirstDeploy`, `afterRedeploy`) from `firebase-functions/v2` for automated post-deployment initialization tasks. + # References + - **Deep Dive into Shims**: See the architectural choices for the shim in [destructuring-shim.md](references/destructuring-shim.md). - **Function Name Mapping**: See the V1 vs V2 function signature mapping table in [signature-mapping.md](references/signature-mapping.md). diff --git a/skills/firebase-v1-v2-migration/references/configuration-migration.md b/skills/firebase-v1-v2-migration/references/configuration-migration.md index 4fae5700..05cdea77 100644 --- a/skills/firebase-v1-v2-migration/references/configuration-migration.md +++ b/skills/firebase-v1-v2-migration/references/configuration-migration.md @@ -4,7 +4,7 @@ In Firebase Functions V1, you configured runtime settings like memory, timeout, You can configure V2 functions in two ways: **Globally** (for all functions in a file) or **Per-Function**. ---- +______________________________________________________________________ ## 🌍 1. Global Configuration (`setGlobalOptions`) @@ -40,7 +40,7 @@ setGlobalOptions({ export const myFn = onRequest((req, res) => { ... }); ``` ---- +______________________________________________________________________ ## 🎯 2. Per-Function Configuration @@ -70,24 +70,24 @@ export const processOrder = onMessagePublished( ``` > [!TIP] -> **Memory Unit Caveat**: V1 accepted `"1GB"`. V2 types strongly prefer IEC units like `"1GiB"`, `"2GiB"`, etc. +> **Memory Unit Recommendation**: While V1 accepted non-IEC units like `"1GB"` or `"2GB"`, V2 options strongly prefer IEC units like `"1GiB"`, `"2GiB"`, etc., to align with Cloud Run types and prevent TypeScript type errors. ---- +______________________________________________________________________ ## ⚠️ Common Property Translations -| V1 Property | V2 Property | Notes | -| :--- | :--- | :--- | -| `memory` | `memory` | Use `"1GiB"` instead of `"1GB"`. | -| `timeoutSeconds` | `timeoutSeconds` | Same. | -| `ingressSettings` | `ingressSettings` | Same. | -| `vpcConnector` | `vpcConnector` | Same. | -| `vpcConnectorEgressSettings` | `vpcConnectorEgressSettings` | Same. | -| `serviceAccount` | `serviceAccount` | Same. | -| `secrets` | `secrets` | Same. | -| `failurePolicy` | `retry` | Renamed to boolean `retry: true/false` in V2 Eventarc triggers. | +| V1 Property | V2 Property | Notes | +| :--------------------------- | :--------------------------- | :--------------------------------------------------------------------- | +| `memory` | `memory` | Prefer IEC units (`"1GiB"`) instead of (`"1GB"`). | +| `timeoutSeconds` | `timeoutSeconds` | Same. | +| `ingressSettings` | `ingressSettings` | Same. | +| `vpcConnector` | `vpcConnector` | Same. | +| `vpcConnectorEgressSettings` | `vpcConnectorEgressSettings` | Same. | +| `serviceAccount` | `serviceAccount` | Same. | +| `secrets` | `secrets` | Pass array of secret parameter variables, e.g. `{ secrets: [myKey] }`. | +| `failurePolicy` | `retry` | Renamed to boolean `retry: true/false` in V2 Eventarc triggers. | ---- +______________________________________________________________________ ## 🔐 3. Migrating Environment Configurations (`functions.config()`) @@ -98,29 +98,33 @@ In V1, you used `functions.config()` to access environment configuration. In V2, Follow these rules to ensure a deterministic and safe migration: #### Typing -* **Numbers**: If the value is used as a number, use `defineNumber`. -* **Secrets**: If the key contains "KEY", "SECRET", "TOKEN", or "PASSWORD", use `defineSecret()`. - * *Note*: Secrets MUST be explicitly bound to the function that uses them in the options object (e.g., `{ secrets: [MY_SECRET] }`). -* **Lists**: Use `defineList` for comma-separated lists. -* **JSON**: Use `defineJSON` for JSON strings. -* **Buckets**: If the param is a storage bucket, set `input: 'BUCKET_PICKER'`. + +- **Numbers**: If the value is used as a number, use `defineNumber`. +- **Secrets**: If the key contains "KEY", "SECRET", "TOKEN", or "PASSWORD", use `defineSecret()`. + - *Note*: Secrets MUST be explicitly bound to the function that uses them in the options object using the secret parameter variable (e.g., `{ secrets: [myKey] }`, where `const myKey = defineSecret("MY_KEY")`). Passing an unreferenced uppercase identifier will cause a TypeScript reference error. +- **Lists**: Use `defineList` for comma-separated lists. +- **JSON**: Use `defineJSON` for JSON strings. +- **Buckets**: If the param is a storage bucket, set `input: 'BUCKET_PICKER'`. #### Initialization & Scope -* **Global Initialization**: If a variable was initialized globally in V1 (e.g., `const client = new Client(functions.config().key)`), you must split it to have declaration at global scope and initialization inside `onInit`: - ```typescript - import { onInit } from "firebase-functions/v2"; - - const myKey = defineSecret("MY_KEY"); - let client: Client; - - onInit(() => { - client = new Client(myKey.value()); - }); - ``` + +- **Global Initialization**: If a variable was initialized globally in V1 (e.g., `const client = new Client(functions.config().key)`), you must split it to have declaration at global scope and initialization inside `onInit`: + ```typescript + import { onInit } from "firebase-functions/v2"; + + const myKey = defineSecret("MY_KEY"); + let client: Client; + + onInit(() => { + client = new Client(myKey.value()); + }); + ``` #### Advanced Interpolation & Logic -* **String Interpolation**: Use the `expr` tagged template literal from `firebase-functions/params` (e.g., `expr`every ${period} days``) instead of standard template literals when constructing dynamic strings with parameters. Do NOT call `.value()` inside `expr`. -* **Logic Operators**: Use expressions like `projectID.equals('prod').thenElse(1, 0)` for logical operations instead of ternary operators on `.value()`. + +- **String Interpolation**: Use the `expr` tagged template literal from `firebase-functions/params` (e.g., `expr`every ${period} days\`\`) instead of standard template literals when constructing dynamic strings with parameters. Do NOT call `.value()` inside `expr`. +- **Logic Operators**: Use expressions like `projectID.equals('prod').thenElse(1, 0)` for logical operations instead of ternary operators on `.value()`. #### Built-ins -* Prefer built-in variables like `databaseURL`, `projectID`, `gcloudProject`, `storageBucket` rather than defining new params for these values. + +- Prefer built-in variables like `databaseURL`, `projectID`, `gcloudProject`, `storageBucket` rather than defining new params for these values. diff --git a/skills/firebase-v1-v2-migration/references/destructuring-shim.md b/skills/firebase-v1-v2-migration/references/destructuring-shim.md index fe4e0426..7fc15f50 100644 --- a/skills/firebase-v1-v2-migration/references/destructuring-shim.md +++ b/skills/firebase-v1-v2-migration/references/destructuring-shim.md @@ -2,7 +2,7 @@ The Destructuring Compatibility Shim is a **Zero-Touch Logic Migration** pattern. It allows you to upgrade a function's infrastructure to V2 (and take advantage of GCF 2nd Gen runtimes) without rewriting any of your internal business logic. ---- +______________________________________________________________________ ## 🛠️ How it Works @@ -36,7 +36,7 @@ export const processOrder = onMessagePublished("orders", ({ message, context }) The Firebase Functions SDK uses a utility called `addV1Compat` to attach these properties via **Lazy Getters** on the `CloudEvent` object for standard event triggers. When you attempt to destructure `{ message, context }` from the event, the SDK transparently maps the V2 event properties back into V1-compatible objects on the fly! This feature is available in modern V2 environments supported by the SDK. ---- +______________________________________________________________________ ## 📖 Provider Mapping Examples @@ -44,74 +44,76 @@ Here are the exact destructuring patterns for every supported V2 provider: ### 1. Cloud Firestore -* **Created / Deleted** triggers: - ```typescript - // V2: onDocumentCreated, onDocumentDeleted - export const processDoc = onDocumentCreated("users/{id}", ({ snapshot, context }) => { ... }); - ``` -* **Updated / Written** triggers: - ```typescript - // V2: onDocumentUpdated, onDocumentWritten - export const processDoc = onDocumentUpdated("users/{id}", ({ change, context }) => { ... }); - ``` +- **Created / Deleted** triggers: + ```typescript + // V2: onDocumentCreated, onDocumentDeleted + export const processDoc = onDocumentCreated("users/{id}", ({ snapshot, context }) => { ... }); + ``` +- **Updated / Written** triggers: + ```typescript + // V2: onDocumentUpdated, onDocumentWritten + export const processDoc = onDocumentUpdated("users/{id}", ({ change, context }) => { ... }); + ``` ### 2. Cloud Storage -* **All** triggers (`onObjectFinalized`, `onObjectDeleted`, `onObjectArchived`, `onObjectMetadataUpdated`): - ```typescript - export const processFile = onObjectFinalized(({ object, context }) => { ... }); - ``` +- **All** triggers (`onObjectFinalized`, `onObjectDeleted`, `onObjectArchived`, `onObjectMetadataUpdated`): + ```typescript + export const processFile = onObjectFinalized(({ object, context }) => { ... }); + ``` ### 3. Realtime Database -* **Created / Deleted** triggers: - ```typescript - export const processData = onValueCreated("/users/{id}", ({ snapshot, context }) => { ... }); - ``` -* **Updated / Written** triggers: - ```typescript - export const processData = onValueWritten("/users/{id}", ({ change, context }) => { ... }); - ``` +- **Created / Deleted** triggers: + ```typescript + export const processData = onValueCreated("/users/{id}", ({ snapshot, context }) => { ... }); + ``` +- **Updated / Written** triggers: + ```typescript + export const processData = onValueWritten("/users/{id}", ({ change, context }) => { ... }); + ``` ### 4. Remote Config -* **Updated** triggers: - ```typescript - export const processConfig = onConfigUpdated(({ version, context }) => { ... }); - ``` +- **Updated** triggers: + ```typescript + export const processConfig = onConfigUpdated(({ version, context }) => { ... }); + ``` ---- +______________________________________________________________________ ## 🛡️ Best Practices for AI Agents -1. **Do NOT attempt to rewrite variable access inside the body.** It is error-prone and time-consuming. -2. **Rely on the shim by default.** Only attempt a pure V2 rewrite if the user explicitly asks for "clean-room" modernization. -3. **Always type-check (`tsc`) after the rewrite.** If the types are wrong, the shim might not be fully supported for that specific provider yet! -4. **HTTPS Callables (Flattened Context)**: Unlike event triggers, Callables do **not** use `V1Compat` or a `context` object. Instead, all context properties are flattened onto the request object. - * **V1 Priority**: `(data, context) => { ... }` - * **V2 Equivalent**: `({ data, auth, app }) => { ... }` +1. **Do NOT attempt to rewrite variable access inside the body.** It is error-prone and time-consuming. +1. **Rely on the shim by default.** Only attempt a pure V2 rewrite if the user explicitly asks for "clean-room" modernization. +1. **Always type-check (`tsc`) after the rewrite.** If the types are wrong, the shim might not be fully supported for that specific provider yet! +1. **HTTPS Callables (Flattened Context)**: Unlike event triggers, Callables do **not** use `V1Compat` or a `context` object. Instead, all context properties are flattened onto the request object. + - **V1 Priority**: `(data, context) => { ... }` + - **V2 Equivalent**: `({ data, auth, app }) => { ... }` ---- +______________________________________________________________________ ## 🔗 Related Migrations Migrating event signatures is only one part of moving from V1 to V2. Another critical area is configuration management. ### Parameterized Configuration + If your functions use `functions.config()`, you should migrate to the new **Parameterized Configuration** system in V2. The destructuring shim handles event signatures, but it does not shim `functions.config()`. #### How to Migrate: -1. **Identify Usages**: Search for `functions.config().path.to.value`. -2. **Define Parameters**: At the top of your file, define the parameter using the appropriate primitive from `firebase-functions/params` (available types include `defineString`, `defineSecret`, `defineInt`, `defineBoolean`, `defineList`, and `defineJSON`): - ```typescript - import { defineString, defineSecret } from "firebase-functions/params"; - - const stripeKey = defineSecret("STRIPE_KEY"); - const apiDomain = defineString("API_DOMAIN"); - ``` -3. **Access Values**: Replace the V1 call with the `.value()` method of the defined parameter: - * **V1**: `const key = functions.config().stripe.key;` - * **V2**: `const key = stripeKey.value();` + +1. **Identify Usages**: Search for `functions.config().path.to.value`. +1. **Define Parameters**: At the top of your file, define the parameter using the appropriate primitive from `firebase-functions/params` (available types include `defineString`, `defineSecret`, `defineInt`, `defineBoolean`, `defineList`, and `defineJSON`): + ```typescript + import { defineString, defineSecret } from "firebase-functions/params"; + + const stripeKey = defineSecret("STRIPE_KEY"); + const apiDomain = defineString("API_DOMAIN"); + ``` +1. **Access Values**: Replace the V1 call with the `.value()` method of the defined parameter: + - **V1**: `const key = functions.config().stripe.key;` + - **V2**: `const key = stripeKey.value();` > [!NOTE] > When you migrate a function to V2 (even with the destructuring shim), `functions.config()` will return `undefined` unless you have explicitly set up environment variables or are running in a specific emulation mode. Parameterized configuration is the standard and recommended way to handle this in V2. diff --git a/skills/firebase-v1-v2-migration/references/signature-mapping.md b/skills/firebase-v1-v2-migration/references/signature-mapping.md index c4d2e332..0d1694e8 100644 --- a/skills/firebase-v1-v2-migration/references/signature-mapping.md +++ b/skills/firebase-v1-v2-migration/references/signature-mapping.md @@ -2,80 +2,83 @@ This reference maps legacy V1 functions to their modern V2 equivalents. It includes the **Shimmed Parameter Key** you should use when destructuring the V2 event object to preserve V1 business logic. ---- +______________________________________________________________________ ## 🔥 Cloud Firestore -| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | -| :--- | :--- | :--- | :--- | -| `firestore.document().onWrite()` | `onDocumentWritten()` | `change` | `({ change, context })` | -| `firestore.document().onCreate()` | `onDocumentCreated()` | `snapshot` | `({ snapshot, context })` | -| `firestore.document().onUpdate()` | `onDocumentUpdated()` | `change` | `({ change, context })` | -| `firestore.document().onDelete()` | `onDocumentDeleted()` | `snapshot` | `({ snapshot, context })` | +| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | +| :-------------------------------- | :-------------------- | :---------- | :------------------------ | +| `firestore.document().onWrite()` | `onDocumentWritten()` | `change` | `({ change, context })` | +| `firestore.document().onCreate()` | `onDocumentCreated()` | `snapshot` | `({ snapshot, context })` | +| `firestore.document().onUpdate()` | `onDocumentUpdated()` | `change` | `({ change, context })` | +| `firestore.document().onDelete()` | `onDocumentDeleted()` | `snapshot` | `({ snapshot, context })` | ---- +______________________________________________________________________ ## 📨 Cloud Pub/Sub -| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | -| :--- | :--- | :--- | :--- | -| `pubsub.topic().onPublish()` | `onMessagePublished()` | `message` | `({ message, context })` | -| `pubsub.schedule().onRun()` | `scheduler.onSchedule()` | **N/A** | Access `event` directly | +| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | +| :--------------------------- | :--------------------- | :---------- | :----------------------- | +| `pubsub.topic().onPublish()` | `onMessagePublished()` | `message` | `({ message, context })` | +| `pubsub.schedule().onRun()` | `onSchedule()` | **N/A** | Access `event` directly | > [!NOTE] -> Scheduled functions moved from the `pubsub` namespace to the `scheduler` namespace in V2. +> Scheduled functions moved from the `pubsub` namespace to `firebase-functions/v2/scheduler` (`import { onSchedule } from "firebase-functions/v2/scheduler"`). ---- +______________________________________________________________________ ## 💾 Realtime Database -| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | -| :--- | :--- | :--- | :--- | -| `database.ref().onWrite()` | `onValueWritten()` | `change` | `({ change, context })` | -| `database.ref().onCreate()` | `onValueCreated()` | `snapshot` | `({ snapshot, context })` | -| `database.ref().onUpdate()` | `onValueUpdated()` | `change` | `({ change, context })` | -| `database.ref().onDelete()` | `onValueDeleted()` | `snapshot` | `({ snapshot, context })` | +| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | +| :-------------------------- | :----------------- | :---------- | :------------------------ | +| `database.ref().onWrite()` | `onValueWritten()` | `change` | `({ change, context })` | +| `database.ref().onCreate()` | `onValueCreated()` | `snapshot` | `({ snapshot, context })` | +| `database.ref().onUpdate()` | `onValueUpdated()` | `change` | `({ change, context })` | +| `database.ref().onDelete()` | `onValueDeleted()` | `snapshot` | `({ snapshot, context })` | ---- +______________________________________________________________________ ## 🗄️ Cloud Storage -| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | -| :--- | :--- | :--- | :--- | -| `storage.object().onArchive()` | `onObjectArchived()` | `object` | `({ object, context })` | -| `storage.object().onDelete()` | `onObjectDeleted()` | `object` | `({ object, context })` | -| `storage.object().onFinalize()` | `onObjectFinalized()` | `object` | `({ object, context })` | -| `storage.object().onMetadataUpdate()` | `onObjectMetadataUpdated()` | `object` | `({ object, context })` | +| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | +| :------------------------------------ | :-------------------------- | :---------- | :---------------------- | +| `storage.object().onArchive()` | `onObjectArchived()` | `object` | `({ object, context })` | +| `storage.object().onDelete()` | `onObjectDeleted()` | `object` | `({ object, context })` | +| `storage.object().onFinalize()` | `onObjectFinalized()` | `object` | `({ object, context })` | +| `storage.object().onMetadataUpdate()` | `onObjectMetadataUpdated()` | `object` | `({ object, context })` | ---- +______________________________________________________________________ ## 🌐 HTTP / Callables -| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | -| :--- | :--- | :--- | :--- | -| `https.onRequest()` | `https.onRequest()` | **N/A** | Standard Express `(req, res)` | -| `https.onCall()` | `https.onCall()` | **N/A** | Destructure `({ data, auth })` | +| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | +| :------------------ | :------------------ | :---------- | :----------------------------- | +| `https.onRequest()` | `https.onRequest()` | **N/A** | Standard Express `(req, res)` | +| `https.onCall()` | `https.onCall()` | **N/A** | Destructure `({ data, auth })` | > [!IMPORTANT] -> **HTTP Callables do NOT use the Destructuring Shim.** +> **HTTP Callables do NOT use the Destructuring Shim.** > In V2, the handler receives a single `CallableRequest` object (not a `CloudEvent`). You should destructure properties like `data`, `auth`, and `app` directly from it. The traditional `context` object is **unavailable**. ---- +______________________________________________________________________ ## 🔑 Auth (Blocking) -| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | -| :--- | :--- | :--- | :--- | -| `auth.user().beforeSignIn()` | `identity.beforeUserSignedIn()` | **N/A** | Access `event` directly | -| `auth.user().beforeCreate()` | `identity.beforeUserCreated()` | **N/A** | Access `event` directly | +| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | +| :--------------------------- | :------------------------------ | :---------- | :---------------------- | +| `auth.user().beforeSignIn()` | `identity.beforeUserSignedIn()` | **N/A** | Access `event` directly | +| `auth.user().beforeCreate()` | `identity.beforeUserCreated()` | **N/A** | Access `event` directly | > [!NOTE] > Auth Blocking triggers moved to the `identity` namespace in V2. ---- +______________________________________________________________________ ## ⏰ Cloud Tasks -| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | -| :--- | :--- | :--- | :--- | -| `tasks.taskQueue().onDispatch()` | `tasks.onTaskDispatched()` | **N/A** | Access `event` directly | +| V1 Trigger | V2 Equivalent | Shimmed Key | Destructuring Pattern | +| :------------------------------- | :------------------- | :---------- | :---------------------- | +| `tasks.taskQueue().onDispatch()` | `onTaskDispatched()` | **N/A** | Access `event` directly | + +> [!NOTE] +> Task queue triggers moved from the `tasks` namespace to `firebase-functions/v2/tasks` (`import { onTaskDispatched } from "firebase-functions/v2/tasks"`). From aa375f6283d994d920b1375d0ed092a7bc91d2b8 Mon Sep 17 00:00:00 2001 From: Varun Shetty Date: Thu, 9 Jul 2026 19:57:03 +0000 Subject: [PATCH 7/7] Format markdown files with mdformat --wrap 80 --- skills/firebase-v1-v2-migration/SKILL.md | 88 ++++++++++++++----- .../references/configuration-migration.md | 47 +++++++--- .../references/destructuring-shim.md | 63 +++++++++---- .../references/signature-mapping.md | 24 ++--- 4 files changed, 158 insertions(+), 64 deletions(-) diff --git a/skills/firebase-v1-v2-migration/SKILL.md b/skills/firebase-v1-v2-migration/SKILL.md index 7c6c1fe5..2497415b 100644 --- a/skills/firebase-v1-v2-migration/SKILL.md +++ b/skills/firebase-v1-v2-migration/SKILL.md @@ -9,33 +9,50 @@ This skill currently supports the **In-Place Migration** strategy. ### 🚜 In-Place Migration (Standard) -The agent modifies the existing V1 code file directly to use V2 syntax and overwrites the deployment slot. +The agent modifies the existing V1 code file directly to use V2 syntax and +overwrites the deployment slot. - **Pros**: Fast, simple, clean repository history. -- **Cons**: No safety net during deployment. If the V2 deployment fails, you must rollback using Git. +- **Cons**: No safety net during deployment. If the V2 deployment fails, you + must rollback using Git. -*Note: For complex or zero-downtime migrations (e.g. Side-by-Side deployment), refer to external orchestration skills.* +*Note: For complex or zero-downtime migrations (e.g. Side-by-Side deployment), +refer to external orchestration skills.* # Prerequisites Please ensure the workspace is ready for V2 before attempting a code migration: -1. **Configuration Check**: Ensure the workspace has transitioned away from functions.config() to Parameterized Configuration or standard environment variables. -1. **Dependencies**: The project must be using firebase-functions version that supports V2 (>= 4.0.0). +1. **Configuration Check**: Ensure the workspace has transitioned away from + functions.config() to Parameterized Configuration or standard environment + variables. +1. **Dependencies**: The project must be using firebase-functions version that + supports V2 (>= 4.0.0). # 🔍 Pre-Migration Checklist Before modifying any code, the agent should run a quick scan: -1. **Scan for legacy configs**: Run a `grep` or text search for usages of `functions.config()`. - - **Action**: If found, **stop and warn the user** that these configs will evaluate to `undefined` in V2 unless they migrate to Parameterized Configuration or standard `.env` variables first. +1. **Scan for legacy configs**: Run a `grep` or text search for usages of + `functions.config()`. + - **Action**: If found, **stop and warn the user** that these configs will + evaluate to `undefined` in V2 unless they migrate to Parameterized + Configuration or standard `.env` variables first. # Principles of Safe Migration Always follow these principles to ensure zero-touch logic migration: -1. **Use Context-Aware Editing over Global Regex**: Never use naive find-and-replace. Rely on syntax-aware editing (such as an AI agent reading the file context and making precise edits, or tools like ts-morph/ast parsers) to ensure context isolation. -1. **Signature Modernization with Destructuring**: Do NOT rewrite the internal variable usages of context or params inside the function body. Instead, use JavaScript's native object destructuring in the new V2 signature parameters. (Note: For `https.onCall`, the context shim is not available; you should destructure `auth` and `data` directly from the request object instead of expecting a `.context` property). +1. **Use Context-Aware Editing over Global Regex**: Never use naive + find-and-replace. Rely on syntax-aware editing (such as an AI agent reading + the file context and making precise edits, or tools like ts-morph/ast + parsers) to ensure context isolation. +1. **Signature Modernization with Destructuring**: Do NOT rewrite the internal + variable usages of context or params inside the function body. Instead, use + JavaScript's native object destructuring in the new V2 signature parameters. + (Note: For `https.onCall`, the context shim is not available; you should + destructure `auth` and `data` directly from the request object instead of + expecting a `.context` property). ### 🛡️ Example Transformation @@ -62,37 +79,60 @@ export const processOrder = onMessagePublished("orders", ({ message, context }) # Verification -After making any migration edits, immediately run the following verification steps: +After making any migration edits, immediately run the following verification +steps: -1. Run `npm run build` to ensure the TypeScript compiler is happy with the types and parameters. +1. Run `npm run build` to ensure the TypeScript compiler is happy with the types + and parameters. 1. Run `npm test` to verify no regressions occurred in existing unit tests. -> [!WARNING] -> **Test Signature Mismatch**: The destructuring shim changes the function signature from two arguments `(data, context)` to a single destructured object `({ message, context })`. +> [!WARNING] **Test Signature Mismatch**: The destructuring shim changes the +> function signature from two arguments `(data, context)` to a single +> destructured object `({ message, context })`. > -> Existing V1 unit tests that invoke the function with two parameters separately (e.g., `myFn(mockData, mockContext)`) **will fail** because the function treats `mockData` as the entire event object. You will need to update test calls to pass a single object. +> Existing V1 unit tests that invoke the function with two parameters separately +> (e.g., `myFn(mockData, mockContext)`) **will fail** because the function +> treats `mockData` as the entire event object. You will need to update test +> calls to pass a single object. > -> **Crucial**: The key name in the test mock must match the specific **Shimmed Key** for that trigger (e.g., `change` for `onDocumentWritten`, `snapshot` for `onDocumentCreated`, `message` for PubSub, or `object` for Storage). +> **Crucial**: The key name in the test mock must match the specific **Shimmed +> Key** for that trigger (e.g., `change` for `onDocumentWritten`, `snapshot` for +> `onDocumentCreated`, `message` for PubSub, or `object` for Storage). > -> Example: `myFn({ change: mockChange, context: mockContext })` or `myFn({ message: mockMessage, context: mockContext })`. See [signature-mapping.md](references/signature-mapping.md) for the exact keys. +> Example: `myFn({ change: mockChange, context: mockContext })` or +> `myFn({ message: mockMessage, context: mockContext })`. See +> [signature-mapping.md](references/signature-mapping.md) for the exact keys. # 💸 Performance & Cost Considerations -In Firebase Functions V2, you can handle multiple requests concurrently per instance (up to 1,000 requests, default 80 if CPU >= 1). However, enabling concurrency requires assigning at least 1 full CPU. +In Firebase Functions V2, you can handle multiple requests concurrently per +instance (up to 1,000 requests, default 80 if CPU >= 1). However, enabling +concurrency requires assigning at least 1 full CPU. -- **V1 Cost Parity**: If you want to keep V1 fractional CPU pricing (and disable concurrency), you must explicitly set `cpu: "gcf_gen1"`. -- **Modernization**: If you want to take advantage of Concurrency, you must assign at least 1 CPU. +- **V1 Cost Parity**: If you want to keep V1 fractional CPU pricing (and disable + concurrency), you must explicitly set `cpu: "gcf_gen1"`. +- **Modernization**: If you want to take advantage of Concurrency, you must + assign at least 1 CPU. -See [configuration-migration.md](references/configuration-migration.md) for how to set these options. +See [configuration-migration.md](references/configuration-migration.md) for how +to set these options. # 🛠️ Additional V2 SDK Features When upgrading to V2, take advantage of modern V2 SDK features where applicable: -- **Declarative Security (IAM Roles & APIs)**: Use `requiresRole("roles/...")` and `requiresAPI("service.googleapis.com", "reason")` from `firebase-functions/v2` so IAM role and Google Cloud API dependencies are declared directly in code instead of instructing users to run manual `gcloud` commands. -- **Lifecycle Hooks**: Use post-deployment lifecycle hooks (`afterFirstDeploy`, `afterRedeploy`) from `firebase-functions/v2` for automated post-deployment initialization tasks. +- **Declarative Security (IAM Roles & APIs)**: Use `requiresRole("roles/...")` + and `requiresAPI("service.googleapis.com", "reason")` from + `firebase-functions/v2` so IAM role and Google Cloud API dependencies are + declared directly in code instead of instructing users to run manual `gcloud` + commands. +- **Lifecycle Hooks**: Use post-deployment lifecycle hooks (`afterFirstDeploy`, + `afterRedeploy`) from `firebase-functions/v2` for automated post-deployment + initialization tasks. # References -- **Deep Dive into Shims**: See the architectural choices for the shim in [destructuring-shim.md](references/destructuring-shim.md). -- **Function Name Mapping**: See the V1 vs V2 function signature mapping table in [signature-mapping.md](references/signature-mapping.md). +- **Deep Dive into Shims**: See the architectural choices for the shim in + [destructuring-shim.md](references/destructuring-shim.md). +- **Function Name Mapping**: See the V1 vs V2 function signature mapping table + in [signature-mapping.md](references/signature-mapping.md). diff --git a/skills/firebase-v1-v2-migration/references/configuration-migration.md b/skills/firebase-v1-v2-migration/references/configuration-migration.md index 05cdea77..17625e34 100644 --- a/skills/firebase-v1-v2-migration/references/configuration-migration.md +++ b/skills/firebase-v1-v2-migration/references/configuration-migration.md @@ -1,14 +1,18 @@ # Migrating Runtime Configurations (runWith) -In Firebase Functions V1, you configured runtime settings like memory, timeout, and service accounts using `.runWith()`. In V2, `.runWith()` is removed and replaced by a more flexible options system. +In Firebase Functions V1, you configured runtime settings like memory, timeout, +and service accounts using `.runWith()`. In V2, `.runWith()` is removed and +replaced by a more flexible options system. -You can configure V2 functions in two ways: **Globally** (for all functions in a file) or **Per-Function**. +You can configure V2 functions in two ways: **Globally** (for all functions in a +file) or **Per-Function**. ______________________________________________________________________ ## 🌍 1. Global Configuration (`setGlobalOptions`) -Use `setGlobalOptions` at the top of your file to set defaults for all functions defined after it. +Use `setGlobalOptions` at the top of your file to set defaults for all functions +defined after it. ### V1 Legacy @@ -44,7 +48,8 @@ ______________________________________________________________________ ## 🎯 2. Per-Function Configuration -Pass the configuration object as the **first argument** to the V2 trigger function. +Pass the configuration object as the **first argument** to the V2 trigger +function. ### V1 Legacy @@ -69,8 +74,10 @@ export const processOrder = onMessagePublished( ); ``` -> [!TIP] -> **Memory Unit Recommendation**: While V1 accepted non-IEC units like `"1GB"` or `"2GB"`, V2 options strongly prefer IEC units like `"1GiB"`, `"2GiB"`, etc., to align with Cloud Run types and prevent TypeScript type errors. +> [!TIP] **Memory Unit Recommendation**: While V1 accepted non-IEC units like +> `"1GB"` or `"2GB"`, V2 options strongly prefer IEC units like `"1GiB"`, +> `"2GiB"`, etc., to align with Cloud Run types and prevent TypeScript type +> errors. ______________________________________________________________________ @@ -91,7 +98,8 @@ ______________________________________________________________________ ## 🔐 3. Migrating Environment Configurations (`functions.config()`) -In V1, you used `functions.config()` to access environment configuration. In V2, this is replaced by **Parameterized Configuration**. +In V1, you used `functions.config()` to access environment configuration. In V2, +this is replaced by **Parameterized Configuration**. ### Deterministic Rules for Migration @@ -100,15 +108,22 @@ Follow these rules to ensure a deterministic and safe migration: #### Typing - **Numbers**: If the value is used as a number, use `defineNumber`. -- **Secrets**: If the key contains "KEY", "SECRET", "TOKEN", or "PASSWORD", use `defineSecret()`. - - *Note*: Secrets MUST be explicitly bound to the function that uses them in the options object using the secret parameter variable (e.g., `{ secrets: [myKey] }`, where `const myKey = defineSecret("MY_KEY")`). Passing an unreferenced uppercase identifier will cause a TypeScript reference error. +- **Secrets**: If the key contains "KEY", "SECRET", "TOKEN", or "PASSWORD", use + `defineSecret()`. + - *Note*: Secrets MUST be explicitly bound to the function that uses them in + the options object using the secret parameter variable (e.g., + `{ secrets: [myKey] }`, where `const myKey = defineSecret("MY_KEY")`). + Passing an unreferenced uppercase identifier will cause a TypeScript + reference error. - **Lists**: Use `defineList` for comma-separated lists. - **JSON**: Use `defineJSON` for JSON strings. - **Buckets**: If the param is a storage bucket, set `input: 'BUCKET_PICKER'`. #### Initialization & Scope -- **Global Initialization**: If a variable was initialized globally in V1 (e.g., `const client = new Client(functions.config().key)`), you must split it to have declaration at global scope and initialization inside `onInit`: +- **Global Initialization**: If a variable was initialized globally in V1 (e.g., + `const client = new Client(functions.config().key)`), you must split it to + have declaration at global scope and initialization inside `onInit`: ```typescript import { onInit } from "firebase-functions/v2"; @@ -122,9 +137,15 @@ Follow these rules to ensure a deterministic and safe migration: #### Advanced Interpolation & Logic -- **String Interpolation**: Use the `expr` tagged template literal from `firebase-functions/params` (e.g., `expr`every ${period} days\`\`) instead of standard template literals when constructing dynamic strings with parameters. Do NOT call `.value()` inside `expr`. -- **Logic Operators**: Use expressions like `projectID.equals('prod').thenElse(1, 0)` for logical operations instead of ternary operators on `.value()`. +- **String Interpolation**: Use the `expr` tagged template literal from + `firebase-functions/params` (e.g., `expr`every ${period} days\`\`) instead of + standard template literals when constructing dynamic strings with parameters. + Do NOT call `.value()` inside `expr`. +- **Logic Operators**: Use expressions like + `projectID.equals('prod').thenElse(1, 0)` for logical operations instead of + ternary operators on `.value()`. #### Built-ins -- Prefer built-in variables like `databaseURL`, `projectID`, `gcloudProject`, `storageBucket` rather than defining new params for these values. +- Prefer built-in variables like `databaseURL`, `projectID`, `gcloudProject`, + `storageBucket` rather than defining new params for these values. diff --git a/skills/firebase-v1-v2-migration/references/destructuring-shim.md b/skills/firebase-v1-v2-migration/references/destructuring-shim.md index 7fc15f50..6cbeb741 100644 --- a/skills/firebase-v1-v2-migration/references/destructuring-shim.md +++ b/skills/firebase-v1-v2-migration/references/destructuring-shim.md @@ -1,14 +1,20 @@ # Architectural Deep Dive: Destructuring Compatibility Shim -The Destructuring Compatibility Shim is a **Zero-Touch Logic Migration** pattern. It allows you to upgrade a function's infrastructure to V2 (and take advantage of GCF 2nd Gen runtimes) without rewriting any of your internal business logic. +The Destructuring Compatibility Shim is a **Zero-Touch Logic Migration** +pattern. It allows you to upgrade a function's infrastructure to V2 (and take +advantage of GCF 2nd Gen runtimes) without rewriting any of your internal +business logic. ______________________________________________________________________ ## 🛠️ How it Works -When you migrate a V1 function to V2, the signature changes from two parameters `(data, context)` to a single `CloudEvent` object. +When you migrate a V1 function to V2, the signature changes from two parameters +`(data, context)` to a single `CloudEvent` object. -Instead of manually rewriting all usages of `context.params` or `message.json` inside the function, you use JavaScript's **Object Destructuring** in the signature. +Instead of manually rewriting all usages of `context.params` or `message.json` +inside the function, you use JavaScript's **Object Destructuring** in the +signature. ### Example Transformation @@ -23,7 +29,8 @@ export const processOrder = functions.pubsub.topic("orders").onPublish((message, #### Step 2: Modern V2 + Shim -We change the trigger to `onMessagePublished`, and instead of accepting `event`, we destructure `{ message, context }` directly: +We change the trigger to `onMessagePublished`, and instead of accepting `event`, +we destructure `{ message, context }` directly: ```typescript export const processOrder = onMessagePublished("orders", ({ message, context }) => { @@ -34,7 +41,12 @@ export const processOrder = onMessagePublished("orders", ({ message, context }) ### 🧠 Why This Works -The Firebase Functions SDK uses a utility called `addV1Compat` to attach these properties via **Lazy Getters** on the `CloudEvent` object for standard event triggers. When you attempt to destructure `{ message, context }` from the event, the SDK transparently maps the V2 event properties back into V1-compatible objects on the fly! This feature is available in modern V2 environments supported by the SDK. +The Firebase Functions SDK uses a utility called `addV1Compat` to attach these +properties via **Lazy Getters** on the `CloudEvent` object for standard event +triggers. When you attempt to destructure `{ message, context }` from the event, +the SDK transparently maps the V2 event properties back into V1-compatible +objects on the fly! This feature is available in modern V2 environments +supported by the SDK. ______________________________________________________________________ @@ -57,7 +69,8 @@ Here are the exact destructuring patterns for every supported V2 provider: ### 2. Cloud Storage -- **All** triggers (`onObjectFinalized`, `onObjectDeleted`, `onObjectArchived`, `onObjectMetadataUpdated`): +- **All** triggers (`onObjectFinalized`, `onObjectDeleted`, `onObjectArchived`, + `onObjectMetadataUpdated`): ```typescript export const processFile = onObjectFinalized(({ object, context }) => { ... }); ``` @@ -84,10 +97,15 @@ ______________________________________________________________________ ## 🛡️ Best Practices for AI Agents -1. **Do NOT attempt to rewrite variable access inside the body.** It is error-prone and time-consuming. -1. **Rely on the shim by default.** Only attempt a pure V2 rewrite if the user explicitly asks for "clean-room" modernization. -1. **Always type-check (`tsc`) after the rewrite.** If the types are wrong, the shim might not be fully supported for that specific provider yet! -1. **HTTPS Callables (Flattened Context)**: Unlike event triggers, Callables do **not** use `V1Compat` or a `context` object. Instead, all context properties are flattened onto the request object. +1. **Do NOT attempt to rewrite variable access inside the body.** It is + error-prone and time-consuming. +1. **Rely on the shim by default.** Only attempt a pure V2 rewrite if the user + explicitly asks for "clean-room" modernization. +1. **Always type-check (`tsc`) after the rewrite.** If the types are wrong, the + shim might not be fully supported for that specific provider yet! +1. **HTTPS Callables (Flattened Context)**: Unlike event triggers, Callables do + **not** use `V1Compat` or a `context` object. Instead, all context properties + are flattened onto the request object. - **V1 Priority**: `(data, context) => { ... }` - **V2 Equivalent**: `({ data, auth, app }) => { ... }` @@ -95,27 +113,38 @@ ______________________________________________________________________ ## 🔗 Related Migrations -Migrating event signatures is only one part of moving from V1 to V2. Another critical area is configuration management. +Migrating event signatures is only one part of moving from V1 to V2. Another +critical area is configuration management. ### Parameterized Configuration -If your functions use `functions.config()`, you should migrate to the new **Parameterized Configuration** system in V2. The destructuring shim handles event signatures, but it does not shim `functions.config()`. +If your functions use `functions.config()`, you should migrate to the new +**Parameterized Configuration** system in V2. The destructuring shim handles +event signatures, but it does not shim `functions.config()`. #### How to Migrate: 1. **Identify Usages**: Search for `functions.config().path.to.value`. -1. **Define Parameters**: At the top of your file, define the parameter using the appropriate primitive from `firebase-functions/params` (available types include `defineString`, `defineSecret`, `defineInt`, `defineBoolean`, `defineList`, and `defineJSON`): +1. **Define Parameters**: At the top of your file, define the parameter using + the appropriate primitive from `firebase-functions/params` (available types + include `defineString`, `defineSecret`, `defineInt`, `defineBoolean`, + `defineList`, and `defineJSON`): ```typescript import { defineString, defineSecret } from "firebase-functions/params"; const stripeKey = defineSecret("STRIPE_KEY"); const apiDomain = defineString("API_DOMAIN"); ``` -1. **Access Values**: Replace the V1 call with the `.value()` method of the defined parameter: +1. **Access Values**: Replace the V1 call with the `.value()` method of the + defined parameter: - **V1**: `const key = functions.config().stripe.key;` - **V2**: `const key = stripeKey.value();` -> [!NOTE] -> When you migrate a function to V2 (even with the destructuring shim), `functions.config()` will return `undefined` unless you have explicitly set up environment variables or are running in a specific emulation mode. Parameterized configuration is the standard and recommended way to handle this in V2. +> [!NOTE] When you migrate a function to V2 (even with the destructuring shim), +> `functions.config()` will return `undefined` unless you have explicitly set up +> environment variables or are running in a specific emulation mode. +> Parameterized configuration is the standard and recommended way to handle this +> in V2. -For a complete guide and deterministic rules on how to migrate configurations, refer to [configuration-migration.md](configuration-migration.md). +For a complete guide and deterministic rules on how to migrate configurations, +refer to [configuration-migration.md](configuration-migration.md). diff --git a/skills/firebase-v1-v2-migration/references/signature-mapping.md b/skills/firebase-v1-v2-migration/references/signature-mapping.md index 0d1694e8..14e8353b 100644 --- a/skills/firebase-v1-v2-migration/references/signature-mapping.md +++ b/skills/firebase-v1-v2-migration/references/signature-mapping.md @@ -1,6 +1,8 @@ # Firebase Functions V1 vs V2 Signature Mapping -This reference maps legacy V1 functions to their modern V2 equivalents. It includes the **Shimmed Parameter Key** you should use when destructuring the V2 event object to preserve V1 business logic. +This reference maps legacy V1 functions to their modern V2 equivalents. It +includes the **Shimmed Parameter Key** you should use when destructuring the V2 +event object to preserve V1 business logic. ______________________________________________________________________ @@ -22,8 +24,9 @@ ______________________________________________________________________ | `pubsub.topic().onPublish()` | `onMessagePublished()` | `message` | `({ message, context })` | | `pubsub.schedule().onRun()` | `onSchedule()` | **N/A** | Access `event` directly | -> [!NOTE] -> Scheduled functions moved from the `pubsub` namespace to `firebase-functions/v2/scheduler` (`import { onSchedule } from "firebase-functions/v2/scheduler"`). +> [!NOTE] Scheduled functions moved from the `pubsub` namespace to +> `firebase-functions/v2/scheduler` +> (`import { onSchedule } from "firebase-functions/v2/scheduler"`). ______________________________________________________________________ @@ -56,9 +59,10 @@ ______________________________________________________________________ | `https.onRequest()` | `https.onRequest()` | **N/A** | Standard Express `(req, res)` | | `https.onCall()` | `https.onCall()` | **N/A** | Destructure `({ data, auth })` | -> [!IMPORTANT] -> **HTTP Callables do NOT use the Destructuring Shim.** -> In V2, the handler receives a single `CallableRequest` object (not a `CloudEvent`). You should destructure properties like `data`, `auth`, and `app` directly from it. The traditional `context` object is **unavailable**. +> [!IMPORTANT] **HTTP Callables do NOT use the Destructuring Shim.** In V2, the +> handler receives a single `CallableRequest` object (not a `CloudEvent`). You +> should destructure properties like `data`, `auth`, and `app` directly from it. +> The traditional `context` object is **unavailable**. ______________________________________________________________________ @@ -69,8 +73,7 @@ ______________________________________________________________________ | `auth.user().beforeSignIn()` | `identity.beforeUserSignedIn()` | **N/A** | Access `event` directly | | `auth.user().beforeCreate()` | `identity.beforeUserCreated()` | **N/A** | Access `event` directly | -> [!NOTE] -> Auth Blocking triggers moved to the `identity` namespace in V2. +> [!NOTE] Auth Blocking triggers moved to the `identity` namespace in V2. ______________________________________________________________________ @@ -80,5 +83,6 @@ ______________________________________________________________________ | :------------------------------- | :------------------- | :---------- | :---------------------- | | `tasks.taskQueue().onDispatch()` | `onTaskDispatched()` | **N/A** | Access `event` directly | -> [!NOTE] -> Task queue triggers moved from the `tasks` namespace to `firebase-functions/v2/tasks` (`import { onTaskDispatched } from "firebase-functions/v2/tasks"`). +> [!NOTE] Task queue triggers moved from the `tasks` namespace to +> `firebase-functions/v2/tasks` +> (`import { onTaskDispatched } from "firebase-functions/v2/tasks"`).