Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions skills/firebase-v1-v2-migration/SKILL.md
Original file line number Diff line number Diff line change
@@ -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.
Comment thread
shettyvarun268 marked this conversation as resolved.
Outdated

### 🛡️ 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 })`.
Comment thread
shettyvarun268 marked this conversation as resolved.
Outdated

# 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).
45 changes: 45 additions & 0 deletions skills/firebase-v1-v2-migration/references/destructuring-shim.md
Original file line number Diff line number Diff line change
@@ -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!
27 changes: 27 additions & 0 deletions skills/firebase-v1-v2-migration/references/signature-mapping.md
Original file line number Diff line number Diff line change
@@ -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`). |
Comment thread
shettyvarun268 marked this conversation as resolved.
Outdated
| **Tasks** | `tasks.taskQueue().onDispatch()` | `tasks.onTaskDispatched()` | **Available** | |
Loading