انتقال افزونه‌های فایربیس به توابع ابری

این راهنما به شما نشان می‌دهد که چگونه افزونه‌های خود را از محیط منسوخ‌شده‌ی Firebase Extensions به تابعی که کاربران شما نصب و در پایگاه کد Cloud Functions for Firebase (نسل دوم) خود مستقر می‌کنند، منتقل کنید.

این مسیر مهاجرت توصیه شده است. Firebase فهرستی از افزونه‌ها با معادل‌های رسمی npm را نگهداری می‌کند؛ این راهنما شما را در ایجاد افزونه‌ی خودتان راهنمایی می‌کند.

در سراسر این راهنما، افزونه‌ی Stream Cloud Firestore to BigQuery ( firestore-bigquery-export ) به عنوان یک مثال اجرایی استفاده شده است. هر بخش با یک مثال عملی به پایان می‌رسد که نشان می‌دهد آن افزونه قبل از مهاجرت چگونه بوده و پس از آن، به عنوان بسته‌ی @firebase-function-kits/firestore-bigquery-export چگونه به نظر می‌رسد.

برای دریافت اطلاعات بیشتر و کمک در مورد مهاجرت افزونه‌ها، ثبت نام کنید

اگر در مورد نحوه مهاجرت از Firebase Extensions سؤالی دارید، می‌توانید از طریق firebase-extensions-migrator-support-external@google.com با ما در ارتباط باشید. ما همچنین با به‌روزرسانی راهنما و ارائه اطلاعات بیشتر در مورد بسته‌بندی، آزمایش و توزیع توابع نسل دوم شما، به این گروه ایمیل خواهیم زد.

برای پیوستن به این گروه، پیامی به firebase-extensions-migrator-support-external+subscribe@google.com ارسال کنید، که با یک ایمیل درخواست عضویت پاسخ داده خواهد شد. شما باید به آن ایمیل پاسخ دهید، نه اینکه روی دکمه "Join This Group" کلیک کنید.

قبل از اینکه شروع کنی

برای تکمیل این مهاجرت به صورت نوشته شده، از ویژگی‌های زیر در Cloud Functions استفاده خواهید کرد:

  • پیکربندی پارامتری . هر پارامتری که در extension.yaml تعریف می‌کنید، به یک پارامتر تعریف‌شده در کد بسته شما تبدیل می‌شود.

  • نقش‌های IAM اعلانی و APIهای مورد نیاز. هر نقشی که در extension.yaml اعلان می‌کنید، به یک فراخوانی requiresRole(...) تبدیل می‌شود و هر API در کد بسته شما به یک فراخوانی requiresAPI(...) تبدیل می‌شود. در زمان استقرار، Firebase CLI نقش‌های اعلان‌شده را به یک حساب سرویس زمان اجرای مدیریت‌شده اعطا می‌کند و APIهای اعلان‌شده را از طرف شما فعال می‌کند.

  • رویدادهای چرخه عمر برای پایگاه‌های کد Cloud Functions . پایگاه‌های کد Cloud Functions اکنون از رویدادهای چرخه عمر مشابه Firebase Extensions پشتیبانی می‌کنند. تنظیمات زمان نصب و زمان به‌روزرسانی را با قلاب‌های چرخه عمر afterFirstDeploy(...) و afterRedeploy(...) اعلام کنید. این‌ها جایگزین lifecycleEvents می‌شوند که شما در extension.yaml اعلام می‌کنید.

منبع Firebase Extensions به یک تابع نسل دوم منتقل کنید

(اختیاری) مهاجرت خودکار با مهارت عامل Firebase

شما می‌توانید مراحل ۱ تا ۸ (فهرست‌بندی منابع، ارتقاء تریگرها، تبدیل‌های پارامتر و مخفی، IAM اعلانی، قلاب‌های چرخه عمر و تولید README بسته) را با استفاده از مهارت رسمی عامل هوش مصنوعیِ extension-to-functions-codebase خودکارسازی کنید.

مهارت را نصب کنید

اگر شما یا دستیار کدنویسی هوش مصنوعی شما (Gemini در Firebase ، Cursor، Claude Code، GitHub Copilot) هنوز این مهارت را نصب نکرده‌اید، دستور زیر را با استفاده از خط فرمان مهارت‌ها اجرا کنید:

npx skills add firebase/agent-skills --skill extension-to-functions-codebase

پس از نصب مهارت در پروژه شما، دستیار کدنویسی هوش مصنوعی شما به طور خودکار قوانین مهاجرت و مراحل تبدیل آن را دنبال می‌کند. می‌توانید از دستورالعمل زیر استفاده کنید:

«لطفاً این افزونه‌ی فایربیس را طبق دستورالعمل‌های موجود در مهارت extension-to-functions-codebase » به یک بسته‌ی کیت تابع نسل دوم قابل انتشار منتقل کنید.»

۱. موجودی افزونه را بررسی کنید

با تهیه فهرستی از افزونه خود شروع کنید: یک لیست کامل از هر چیزی که افزونه اعلام می‌کند، ارسال می‌کند و مستند می‌کند، به طوری که هر رفتار در تابع نسل دوم یک مقصد مشخص داشته باشد و هیچ چیز در مهاجرت از دست نرود.

هر یک از موارد زیر را بررسی کنید و آنچه را که پیدا می‌کنید یادداشت کنید:

  • extension.yaml که پارامترها، توابع، رویدادها، نقش‌های IAM، APIهای مورد نیاز، رمزها و هوک‌های چرخه عمر شما را تعریف می‌کند.

  • functions/ که شامل کد تابع، وابستگی‌ها، پیکربندی ساخت، تریگرها و توابع صف وظایف شما می‌شود.

  • README.md ، PREINSTALL.md و POSTINSTALL.md که شامل مراحل راه‌اندازی، هشدارها و یادداشت‌های صورتحساب هستند.

  • scripts/ که شامل هرگونه ابزار import، backfill، IAM، repair یا migration و هر ابزار دیگری است که شما در کنار افزونه ارائه می‌دهید.

سپس، برای هر آیتم در extension.yaml ، تصمیم بگیرید که در کجای پکیج npm قرار گیرد:

  • پیکربندی کاربر را به پارامترهای Cloud Functions تبدیل کنید ( مرحله 4 ).

  • تبدیل اسرار به اسرار Cloud Functions ( مرحله 4 ).

  • نقش‌های IAM را به اعلان‌های requiresRole(...) تبدیل کنید ( مرحله 6 ).

  • در صورت لزوم، APIهای مورد نیاز گوگل را به اعلان‌های requiresAPI(...) تبدیل کنید ( مرحله 6 ).

  • قلاب‌های نصب و به‌روزرسانی را به اعلان‌های afterFirstDeploy(...) و afterRedeploy(...) تبدیل کنید ( مرحله 7 ).

  • شناسه‌های نمونه را از EXT_INSTANCE_ID به FIREBASE_KIT_INSTANCE_ID تبدیل کنید ( مرحله 4 ).

مثال کار شده: انتقال Cloud Firestore به BigQuery

خواندن firestore-bigquery-export/extension.yaml و functions/ این فهرست را تولید می‌کند:

در extension.yaml تعداد / مقدار کجا می رود؟
params ۲۵ ( COLLECTION_PATH ، DATASET_ID ، TABLE_ID ، DATASET_LOCATION ، VIEW_TYPE ، …) پارامترهای Cloud Functions ( مرحله 4 )
apis bigquery.googleapis.com requiresAPI(...) ( مرحله 6 )
roles bigquery.dataEditor ، datastore.user ، bigquery.user requiresRole(...) ( مرحله 6 )
resources ۱ ماشه رویداد ( fsexportbigquery ) + توابع صف وظایف ( initBigQuerySync ، setupBigQuerySync ) توابع بسته اکسپورت شده ( مرحله 3 )
lifecycleEvents onInstall → initBigQuerySync ; onUpdate / onConfigure → setupBigQuerySync afterFirstDeploy / afterRedeploy ( مرحله 7 )
شناسه نمونه استفاده نشده ( EXT_INSTANCE_ID خوانده نمی‌شود) چیزی برای مهاجرت نیست
scripts/ import/ (پر کردن مجدد)، gen-schema-view/ به عنوان اسکریپت نگهداری می‌شود (خارج از محدوده اینجا)

تحلیل. افزونه هیچ type: secret را اعلام نمی‌کند، بنابراین چیزی برای انتقال برای secrets در مرحله ۴ وجود ندارد. تریگر رویداد از قبل نسل دوم است؛ فقط توابع صف وظایف هنوز نسل اول هستند (مربوط به مرحله ۳ ).

۲. به‌روزرسانی package.json

فایل package.json افزونه خود را به‌روزرسانی کنید. اگر در حال انتقال یک افزونه هستید، این می‌تواند فایل ریشه package.json باشد. اگر در حال انتقال چندین افزونه در یک مخزن هستید، به هر افزونه یک پکیج جداگانه اختصاص دهید.

حداقل نسخه‌های SDK: firebase-functions >= 7.4.0 و firebase-admin >= 14.2.0 را به عنوان وابستگی اعلام کنید. نسخه firebase-functions خود را نیز به عنوان یک وابستگی همتا اعلام کنید، به طوری که پروژه Cloud Functions کاربران شما همان نسخه SDK را داشته باشد که کتابخانه شما با آن نوشته شده است.

{
  "name": "<package-name>",
  "version": "1.0.0",
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
  "exports": {
    ".": {
      "types": "./lib/index.d.ts",
      "default": "./lib/index.js"
    }
  },
  "engines": {
    "node": "22"
  },
  "peerDependencies": {
    "firebase-functions": "^7.4.0"
  },
  "dependencies": {
    "firebase-functions": "^7.4.0",
    "firebase-admin": "^14.2.0"
  }
}

مثال کار شده: انتقال Cloud Firestore به BigQuery

قبل از. فایل functions/package.json افزونه private است، شناسه افزونه را نامگذاری می‌کند و firebase-functions به عنوان یک وابستگی مستقیم اعلام می‌کند:

{
  "name": "firestore-bigquery-export",
  "main": "lib/index.js",
  "private": true,
  "dependencies": {
    "@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
    "firebase-admin": "^14.2.0",
    "firebase-functions": "^6.3.2"
  }
}

بعد از آن. یک بسته قابل انتشار: نام محدود، یک نقشه exports و firebase-functions به peerDependencies منتقل شدند:

{
  "name": "@firebase-function-kits/firestore-bigquery-export",
  "version": "0.1.0",
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
  "exports": {
    ".": {
      "types": "./lib/index.d.ts",
      "default": "./lib/index.js"
    }
  },
  "engines": {
    "node": "22"
  },
  "peerDependencies": {
    "firebase-functions": "^7.4.0"
  },
  "dependencies": {
    "@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
    "firebase-admin": "^14.2.0",
    "firebase-functions": "^7.4.0"
  }
}

۳. ارتقا عملکردها از نسل اول به نسل دوم

اگر افزونه شما هنوز توابع نسل اول را صادر می‌کند، هر تریگر را به معادل نسل دوم آن تبدیل کنید. از ماژول‌های firebase-functions/... وارد کنید و تنظیمات زمان اجرا را در گزینه‌های تریگر وارد کنید.

به راهنمای ارتقاء نسل دوم Cloud Functions مراجعه کنید. نکته قابل توجه این است که می‌توانید با استفاده از تجزیه رویداد وصله‌بندی‌شده نسل دوم، تلاش‌های بازنویسی را به حداقل برسانید و از بازنویسی منطق تابع خود جلوگیری کنید، زیرا SDK نسل دوم پارامترهای نسخه ۱ را به عنوان فیلدهایی در شیء رویداد نمایش می‌دهد و به شما امکان می‌دهد از پارامترهای تجزیه‌شده/نام‌گذاری‌شده استفاده کنید و منطق کسب‌وکار خود را بدون تغییر نگه دارید.

قبل از. نسل اول:

import * as functions from "firebase-functions/v1";

export const sync = functions.firestore
  .document("{collectionId}/{documentId}")
  .onWrite(async (change, context) => {
    await handleWrite(change.before, change.after, context.params);
  });

بعد از نسل دوم:

import { onDocumentWritten } from "firebase-functions/firestore";

export const syncV2 = onDocumentWritten(
  { document: "{collectionId}/{documentId}" },
  async ({ change, context }) =>
    await handleWrite(change.before, change.after, context.params)
);

برای مشاهده فهرست جامعی از تفاوت‌های بین توابع نسل اول و نسل دوم ، به مقایسه نسخه‌های Cloud Functions مراجعه کنید.

یک تفاوت قابل توجه بین توابع نسل اول و دوم این است که توابع نسل دوم باید با منابع محرک خود در یک مکان قرار گیرند. هنگامی که کاربران از یک افزونه با استفاده از توابع نسل اول به یک کیت با استفاده از توابع نسل دوم مهاجرت می‌کنند، ممکن است برای برآورده کردن این نیاز نیاز به تغییر مکان توابع داشته باشند.

هر مکان Cloud Functions که کاربران شما انتخاب کرده‌اند، ممکن است در طول مهاجرت خراب شود، زیرا مکان موجود حفظ می‌شود. اگر توابع فعال‌شده توسط رویداد افزونه شما به مکان توابع ارائه‌شده توسط کاربر متکی بودند، پیشنهاد می‌کنیم یک پارامتر جدید به افزونه خود اضافه کنید تا مکان فعال‌شدن رویداد را جمع‌آوری کرده و آن را به مکان تابع جدید نگاشت کند.

مثال کار شده: انتقال Cloud Firestore به BigQuery

defineString("DATABASE_REGION", {
  label: "Firestore Instance Location",
  description:
    "Where is the Firestore database located? You can check your current database location at https://console.cloud.google.com/firestore/databases. The functions in this kit deploy to the Cloud Run region closest to this location.",
  input: select({
    "Multi-region (Europe - Belgium and Netherlands)": "eur3",
    "Multi-region (United States)": "nam5",
    "Multi-region (Iowa, North Virginia, and Oklahoma)": "nam7",
    "Iowa (us-central1)": "us-central1",
    // More locations...
  })
});

// Firestore multi-region locations are not Cloud Run regions; deploying a
// function to one hard-fails, so they map to a region inside the multi-region.
const MULTI_REGION_TO_FUNCTION_REGION: Record<string, string> = {
  nam5: "us-central1",
  nam7: "us-central1",
  eur3: "europe-west1",
};

/**
 * Maps a Firestore database location to the Cloud Run region the functions
 * should deploy to. The lookup is case-insensitive and ignores surrounding
 * whitespace, as the CLI's own region handling is. Regional locations pass
 * through lowercased; an unset or blank location returns `undefined`, meaning
 * the functions declare no region.
 */
export function firestoreLocationToFunctionRegion(
  location: string | undefined
): string | undefined {
  const normalized = location?.trim().toLowerCase();
  if (!normalized) {
    return undefined;
  }
  return MULTI_REGION_TO_FUNCTION_REGION[normalized] ?? normalized;
}

const functionRegion = firestoreLocationToFunctionRegion(
  process.env.DATABASE_REGION
);

export const fsexportbigquery = onDocumentWritten(
  {
    region: functionRegion,
    // Other configuration
  },
  (event) => handleDocumentWrite(event, getHandlerContext())
);

در اولین استقرار کیت، از کاربران خواسته می‌شود که DATABASE_REGION خود را وارد کنند و توابع در functionRegion مربوطه در بالا مستقر می‌شوند و هرگونه مشکل مکانی را برای توابع فعال شده توسط رویداد gen2 آنها برطرف می‌کنند.

۴. تبدیل پارامترها و رمزهای افزونه

پارامز

هر پارامتری که در extension.yaml تعریف می‌کنید، به یک پارامتر Cloud Functions تبدیل می‌شود.

تبدیل مستقیم محیط خوانده شده:

const collectionPath = process.env.COLLECTION_PATH;

پارامترهای Cloud Functions :

import { defineString } from "firebase-functions/params";
import { onDocumentWritten } from "firebase-functions/firestore";

const collectionPath = defineString("COLLECTION_PATH");

// Pass the param directly when used as a placeholder (e.g. trigger path)
export const sync = onDocumentWritten(
  { document: collectionPath },
  async (event) => {
    // Call .value() to read the string inside a handler
    const path = collectionPath.value();
    await handleWrite(path, event);
  }
);

از collectionPath.value() برای خواندن رشته درون یک handler استفاده کنید؛ collectionPath مستقیماً در جایی که انتظار می‌رود یک placeholder وجود داشته باشد، مانند مسیر تریگر تابع، استفاده کنید.

رابط خط فرمان Firebase پارامترهای شما را کشف می‌کند و مقادیر آنها را از .env ، .env.<projectId> می‌خواند، یا در حین استقرار از کاربران شما درخواست می‌کند. نام پارامترها را یکسان نگه دارید تا مقادیر از یک نصب موجود به نصب بعدی منتقل شوند.

مهم است که به هیچ وجه نام پارامترهای اعلام شده در کد خود را تغییر ندهید . مهاجرت افزونه، مقادیر پارامترهای موجود کاربر نهایی را به طور خودکار حفظ می‌کند، اما فقط زمانی که نام‌ها بدون تغییر باشند.

مثال کار شده: انتقال Cloud Firestore به BigQuery

قبل از آن. یک پارامتر که در extension.yaml تعریف شده بود، به عنوان یک متغیر محیطی خام در config.ts خوانده می‌شد:

# extension.yaml
- param: COLLECTION_PATH
  label: Collection path
  type: string
  required: true
// functions/src/config.ts
collectionPath: process.env.COLLECTION_PATH,

بعد از آن. یک defineString ; رابط خط فرمان آن را کشف می‌کند و از .env می‌خواند:

// src/config.ts
import { defineString } from "firebase-functions/params";

collectionPath: defineString("COLLECTION_PATH", {
  label: "Collection path",
  // We now support "nonEmpty: true" to ensure a value other than the empty string
  // is entered, analogous to "required: true" in extension.yaml
  input: { text: { nonEmpty: true } }
}),

نام پارامتر بدون تغییر باقی می‌ماند، بنابراین یک .env موجود به کار خود ادامه می‌دهد.

شناسه نمونه

افزونه‌ها شناسه نمونه خود را از EXT_INSTANCE_ID که توسط زمان اجرای افزونه‌ها تزریق می‌شود، می‌خوانند. کیت‌های تابع، شناسه نمونه خود را از FIREBASE_KIT_INSTANCE_ID می‌خوانند، که رابط خط فرمان Firebase برای هر نمونه کیت، آن را روی کلید نمونه در نقشه instances در firebase.json تنظیم می‌کند. رابط خط فرمان آن را در طول کشف زمان استقرار، در شبیه‌ساز و برای توابع مستقر فراهم می‌کند.

شناسه نمونه یک پارامتر نیست، بنابراین آن را با defineString تعریف نکنید. در واقع، FIREBASE_... یک پیشوند رزرو شده در فایل‌های .env است، بنابراین کاربران نمی‌توانند آن را در آنجا تنظیم یا لغو کنند. مقادیری که CLI تزریق می‌کند برای سیستم params قابل مشاهده نیستند. آن را مستقیماً از محیط بخوانید:

// Before
const instanceId = process.env.EXT_INSTANCE_ID;

// After
const instanceId = process.env.FIREBASE_KIT_INSTANCE_ID;

این متغیر فقط زمانی تنظیم می‌شود که بسته شما به عنوان یک کیت مستقر شود. اگر کد شما به عنوان یک پایگاه کد مستقل نیز مستقر شده است (به مرحله 9 مراجعه کنید)، یا آن را به عنوان اختیاری در نظر بگیرید یا در صورت عدم وجود آن، با یک پیام واضح، سریعاً از کار بیفتید. اگر افزونه شما شناسه نمونه را به عنوان یک پارامتر کاربرپسند نمایش داده است، باید آن پارامتر را حذف کنید، زیرا اکنون Firebase CLI مالک این مقدار است.

مثال حل شده: حذف داده‌های کاربر

(افزونه Stream Cloud Firestore to BigQuery شناسه نمونه خود را نمی‌خواند، بنابراین چیزی برای انتقال به آنجا وجود ندارد. افزونه Delete User Data از آن برای نامگذاری موضوعات Pub/Sub خود استفاده می‌کند.)

قبل از. به عنوان یک متغیر محیطی خام در config.ts با پیشوند ext- که Extensions برای منابع خود استفاده می‌کرد، خوانده می‌شود:

// functions/src/config.ts
discoveryTopic: `ext-${process.env.EXT_INSTANCE_ID}-discovery`,
deletionTopic: `ext-${process.env.EXT_INSTANCE_ID}-deletion`,

بعد از آن. یک process.env ساده، FIREBASE_KIT_INSTANCE_ID را می‌خواند که برای دو پارامتر معمولی استفاده می‌شود تا کاربران بتوانند نام تاپیک‌ها را تغییر دهند:

// src/config.ts
const instanceId = process.env.FIREBASE_KIT_INSTANCE_ID;

// Non-empty defaults so Pub/Sub trigger bindings resolve during deploy
// discovery without freezing an empty topic name into the manifest.
discoveryTopicName: defineString("DISCOVERY_TOPIC_NAME", {
  default: `kit-${instanceId}-discovery`,
}),
deletionTopicName: defineString("DELETION_TOPIC_NAME", {
  default: `kit-${instanceId}-deletion`,
}),

مقادیر پیش‌فرض نباید خالی باشند زیرا اتصالات تریگر در زمان کشف (discovery) حل می‌شوند. یک مقدار پیش‌فرض خالی در مانیفست استقرار به عنوان نام موضوع (topic name) نوشته می‌شود. این کیت همچنین در برابر اجرا در خارج از چارچوب یک کیت، دفاعی عمل می‌کند. اگر متغیر وجود نداشته باشد، مقدار پیش‌فرض سطح ماژول به صورت kit-undefined-discovery ارزیابی می‌شود، بنابراین بارگذاری پیکربندی با یک خطای توضیحی مواجه می‌شود:

// ...
const instanceId = process.env.FIREBASE_KIT_INSTANCE_ID;
if (!instanceId) {
  throw new Error(
    "FIREBASE_KIT_INSTANCE_ID is not set. It is provided automatically to " +
      "kit instances by firebase-tools >= 15.32.0; deploy or emulate this " +
      "kit with a supported CLI version."
  );
}
// ...

این بررسی زمانی اجرا می‌شود که یک کنترل‌کننده ابتدا پیکربندی خود را حل می‌کند، بنابراین یک متغیر گمشده یک خطای زمان اجرا واضح ایجاد می‌کند تا توابعی که به طور خاموش به موضوعات kit-undefined-* متصل هستند. برای رد کردن خودِ استقرار، بررسی را در محدوده ماژول انجام دهید تا در حین کشف اجرا شود. از آنجا که CLI شناسه نمونه را از firebase.json می‌گیرد، هیچ INSTANCE_ID قابل تنظیمی وجود ندارد و چیزی برای همگام‌سازی در چندین نمونه وجود ندارد.

اسرار

در extension.yaml ، شما secretها را با type: secret تعریف می‌کنید. زمان اجرای Extensionها آنها را ذخیره و bind می‌کند، بنابراین کد extension شما می‌تواند process.env.PARAM_NAME مستقیماً بخواند. در یک پایگاه کد معمولی Cloud Functions ، شما هر secret را به صراحت تعریف و bind می‌کنید:

import { defineSecret } from "firebase-functions/params";
import { onRequest } from "firebase-functions/https";

const apiKey = defineSecret("API_KEY");
export const fn = onRequest({ secrets: [apiKey] }, handler);

پس از انتقال افزونه شما به یک بسته/کیت npm، ارجاعات محرمانه در فایل .env کاربر نهایی مدیریت می‌شوند. مهم است که به هیچ وجه نام‌های محرمانه اعلام شده در کد خود را تغییر ندهید . در طول انتقال، اطلاعات محرمانه کاربر نهایی نیز به همین ترتیب منتقل می‌شوند.

مثال کار شده: فعال کردن ایمیل از Cloud Firestore

قبلاً. MAIL_COLLECTION و SMTP_PASSWORD به عنوان متغیرهای محیطی خام در config.ts خوانده می‌شدند:

# extension.yaml
- param: MAIL_COLLECTION
  label: Email documents collection
  type: string
  default: mail
  required: true

- param: SMTP_PASSWORD
  label: SMTP password
  type: secret
// functions/src/config.ts
mailCollection: process.env.MAIL_COLLECTION,
smtpPassword: process.env.SMTP_PASSWORD,

بعد از آن. یکی defineString و یکی defineSecret ؛ رابط خط فرمان هر دو را کشف کرده و از .env می‌خواند:

import { defineString, defineSecret } from "firebase-functions/params";
import { onDocumentWritten } from "firebase-functions/firestore";

const mailCollection = defineString("MAIL_COLLECTION", {
  label: "Email documents collection",
  default: "mail"
});

const smtpPassword = defineSecret("SMTP_PASSWORD", { label: "SMTP password" });

export const processQueue = onDocumentWritten(
  { document: `${mailCollection}/{documentId}`, secrets: [smtpPassword] },
  async (event) => {
    const collection = mailCollection.value();
    const password = smtpPassword.value();
    // ...
  }
);

۵. انتقال فراخوانی‌های صف وظایف داخلی

برخی از افزونه‌ها با استفاده از Firebase Admin SDK ، از داخل کد تابع خود، روی صف‌های وظیفه خود کار می‌کنند. این با دریافت یک وظیفه ارسال شده (که در بخش‌های توابع ارتقا و قلاب‌های چرخه عمر تبدیل پوشش داده شده است) متفاوت است. در اینجا کد شما تولیدکننده‌ای است که queue.enqueue(...) را فراخوانی می‌کند.

نسخه‌های قبلی Admin SDK از افزونه‌ها می‌خواستند که شناسه نمونه افزونه خود را به عنوان پارامتر دوم برای هدف قرار دادن یک تابع Task Queue در همان افزونه ارسال کنند. از firebase-admin 14.2.0، این کار نه الزامی است و نه توصیه می‌شود. API Task Queue اکنون به طور پیش‌فرض صف‌های وظیفه را در همان زمینه (مثلاً یک افزونه یا کیت) هدف قرار می‌دهد. حذف این پارامتر در کد شما، چه به عنوان یک افزونه و چه به عنوان توابع مستقل، ایمن و توصیه می‌شود. حذف این پارامتر، قابلیت حمل و سازگاری رو به جلو را تضمین می‌کند.

هر چیز دیگری در مورد فراخوانی enqueue - مسیر منبع locations/<region>/functions/<name> ، بار وظیفه و منطق تلاش مجدد شما - بدون تغییر باقی می‌ماند.

برای جزئیات بیشتر در مورد در صف قرار دادن توابع با Cloud Tasks، به «در صف قرار دادن توابع با Cloud Tasks مراجعه کنید.

قبل از. پسوند نسل اول:

import { getFunctions } from "firebase-admin/functions";

const queue = getFunctions().taskQueue(
  `locations/${config.location}/functions/syncBigQuery`,
  process.env.EXT_INSTANCE_ID, // extension instance ID, injected by the runtime
);
await queue.enqueue(taskData);

بعد از آن. پسوند نسل دوم:

import { getFunctions } from "firebase-admin/functions";

const queue = getFunctions().taskQueue(
  `locations/${process.env.FUNCTION_REGION}/functions/syncBigQuery`
);
await queue.enqueue(taskData);

اگر فراخوانی enqueue شما یک پایگاه کد از پیش تعیین‌شده را هدف قرار دهد، نام تابع کشف‌شده نیز از پیش تعیین‌شده است (برای مثال، orders-syncBigQuery )؛ به بخش «مرور و نصب نمونه کیت تابع جایگزین» و «تست به عنوان کیت تابع» مراجعه کنید.

۶. APIها و نقش‌های IAM مورد نیاز را اعلام کنید

الزامات IAM و API افزونه خود را از extension.yaml خارج کرده و به کد منتقل کنید:

import { requiresAPI, requiresRole } from "firebase-functions";

requiresAPI("bigquery.googleapis.com", "Needed to write changelog rows");
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/bigquery.user");

با امنیت اعلانی، رابط خط فرمان Firebase یک حساب سرویس زمان اجرای مدیریت‌شده برای کدبیس ایجاد یا به‌روزرسانی می‌کند و به آن اجازه می‌دهد تا تمام نقش‌های اعلان‌شده را در یکجا داشته باشد. برای کاربران خود مستند کنید که تمام توابع موجود در کدبیس با آن نقش‌ها اجرا می‌شوند، مگر اینکه API نهایی از مدل محدودتری پشتیبانی کند.

مثال کار شده: انتقال Cloud Firestore به BigQuery

قبلاً. در extension.yaml تعریف شده بود؛ زمان اجرای Extensions، API را فعال کرده و نقش‌ها را به یک حساب مدیریت‌شده اعطا می‌کرد:

apis:
  - apiName: bigquery.googleapis.com
roles:
  - role: bigquery.dataEditor
  - role: datastore.user
  - role: bigquery.user

بعد از آن. در کد با requiresAPI و requiresRole تعریف شده است:

import { requiresAPI, requiresRole } from "firebase-functions";

requiresAPI(
  "bigquery.googleapis.com",
  "Needed to write changelog rows and views"
);
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/datastore.user");
requiresRole("roles/bigquery.user");

اگر افزونه شما رویدادهای Eventarc را منتشر می‌کند ، باید نقش‌ها و APIهای مورد نیاز مناسب را نیز برای انتشار رویدادها تنظیم کنید. این کار قبلاً توسط افزونه‌ها و بدون نیاز به هیچ تغییری در extensions.yaml شما انجام می‌شد. می‌توانید این کار را به صورت شرطی در کد خود انجام دهید تا کیت فقط زمانی که از یک کانال Eventarc سفارشی استفاده می‌شود، این مجوزها را درخواست کند.

if (!!process.env.EVENTARC_CHANNEL) {
  requiresRole("roles/eventarc.publisher");
  requiresAPI(
    "eventarcpublishing.googleapis.com",
    "Publishes the extension's custom events to its Eventarc channel."
  );
}

۷. تبدیل هوک‌های چرخه عمر

اگر افزونه شما تابع getExtensions().runtime() را فراخوانی می‌کند (برای مثال، setProcessingState یا setFatalError )، آن فراخوانی‌ها را حذف کنید، زیرا اگر از یک تابع نسل دوم که به طور معمول مستقر شده است فراخوانی شوند، خطا می‌دهند. وضعیت چرخه حیات اکنون توسط afterFirstDeploy و afterRedeploy هدایت می‌شود، که در آن‌ها از این ردیابی وضعیت استفاده نمی‌شود.

Firebase Extensions می‌توانند هنگام نصب، به‌روزرسانی یا پیکربندی مجدد یک افزونه توسط کاربر، تنظیمات را اجرا کنند. در بسته npm خود، اکشن‌های چرخه عمر معادل را در کد تعریف کنید.

برای تنظیم یک‌باره:

import { afterFirstDeploy } from "firebase-functions/lifecycle";
import { onTaskDispatched } from "firebase-functions/tasks";

export const runInitialSetup = onTaskDispatched(async (request) => {
  await initializeResources(request.data);
});

afterFirstDeploy({
  task: {
    function: "runInitialSetup",
    body: {}
  }
});

برای پیکربندی یا به‌روزرسانی کد:

import { afterRedeploy } from "firebase-functions/lifecycle";

afterRedeploy({
  task: {
    function: "runInitialSetup",
    body: { reconcile: true }
  }
});

اقدامات چرخه عمر خود را بی‌اثر کنید. در صورت عدم موفقیت در ارسال یا اجرا، ممکن است کاربران شما نیاز به اجرای مجدد دستی آنها داشته باشند:

firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME
firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME

مثال کار شده: انتقال Cloud Firestore به BigQuery

قبل از lifecycleEvents در extension.yaml ، که توسط زمان اجرای Extensions هدایت می‌شود:

lifecycleEvents:
  onInstall:
    function: initBigQuerySync
    processingMessage: Configuring BigQuery Sync.
  onUpdate:
    function: setupBigQuerySync
    processingMessage: Configuring BigQuery Sync
  onConfigure:
    function: setupBigQuerySync
    processingMessage: Configuring BigQuery Sync

بعد از آن. در کد اعلام شده است؛ وظایف BigQuery در اولین استقرار به شرح زیر است:

import { afterFirstDeploy, afterRedeploy } from "firebase-functions/lifecycle";

afterFirstDeploy({ task: { function: "initBigQuerySync" } });
afterRedeploy({ task: { function: "setupBigQuerySync" } });

تأمین منابع به صورت خودگردان انجام می‌شود، بنابراین یک اجرای مجدد، مجموعه داده‌ها، جدول و نماها را با هم تطبیق می‌دهد. کاربران می‌توانند به صورت دستی با firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME اجرا مجدد کنند.

۸. تنظیم سند برای کاربران شما

یک README برای بسته بنویسید که حداقل موارد زیر را توضیح دهد:

  • مقادیر .env که بسته به آنها نیاز دارد.
  • اسراری که بسته به آنها نیاز دارد و نحوه انتقال مقادیر مخفی موجود.
  • نقش‌های IAM که پکیج با requiresRole(...) اعلام می‌کند.
  • APIهای گوگل که بسته فعال می‌کند یا به آنها نیاز دارد.
  • هوک‌های چرخه عمری که پکیج اعلام می‌کند، و نحوه‌ی اجرای مجدد آنها به صورت دستی.
  • یادداشت‌های صورتحساب.
  • چه چیزی در مقایسه با افزونه اصلی تغییر کرده است.
  • نحوه دریافت شناسه نمونه ( FIREBASE_KIT_INSTANCE_ID که توسط CLI تنظیم شده است) توسط بسته و اینکه تمام توابع نمونه با پیشوند kit-<instanceId>- مستقر می‌شوند.

مثال کار شده: انتقال Cloud Firestore به BigQuery

فایل README پکیج، یک جدول «چه چیزهایی تغییر کرده» ارائه می‌دهد:

نگرانی به عنوان پسوند به عنوان @firebase-function-kits/firestore-bigquery-export
پیکربندی پارامترهای افزونه پارامترهای Cloud Functions از طریق .env
آی ام اعطا شده توسط افزونه‌ها requiresRole(...) ، در زمان استقرار اعمال می‌شود.
تأمین وظیفه چرخه عمر توسط افزونه‌ها وظیفه afterFirstDeploy / afterRedeploy
نام توابع ext-<instanceId>-fsexportbigquery fsexportbigquery (پیشوند اختیاری)
شناسه نمونه تزریق EXT_INSTANCE_ID توسط افزونه‌ها FIREBASE_KIT_INSTANCE_ID ، که توسط رابط خط فرمان (CLI) از firebase.json تنظیم شده است.

۹. عملکرد نسل دوم خود را آزمایش کنید

اکنون باید یک تابع نسل دوم داشته باشید که هنگام استقرار، دقیقاً مانند نصب جدید افزونه شما رفتار می‌کند. مرحله بعدی، تأیید و رفع هرگونه مشکلی است که به طور تصادفی در طول مسیر ایجاد شده است.

برای فراخوانی setGlobalOptions برای تنظیم گزینه‌های سراسری مانند منطقه یا CPU پیش‌فرض، باید این کار را فقط هنگام استقرار کیت خود به عنوان یک تابع مستقل نسل دوم انجام دهید. وقتی کیت شما به عنوان یک بسته npm نصب می‌شود، کاربران شما setGlobalOptions در کد بسته‌بندی خود برای پیکربندی این پارامترها فراخوانی می‌کنند و اگر این اتفاق دو بار رخ دهد، هشدار دریافت می‌کنند. می‌توانید با بررسی متغیر محیطی FIREBASE_KIT_INSTANCE_ID از این فراخوانی محافظت کنید:

import { setGlobalOptions } from "firebase-functions";

if (!process.env.FIREBASE_KIT_INSTANCE_ID) {
  setGlobalOptions({
    region: "us-east1",
    maxInstances: 10,
  });
}

مطمئن شوید که از firebase-tools >= 15.32.0 استفاده می‌کنید و تابع تبدیل‌شده‌ی نسل دوم خود را در یک پروژه‌ی آزمایشی با منابع مناسب مستقر کنید تا رفتار آن را آزمایش کنید. اگر از قبل یک پروژه‌ی آزمایشی از آزمایش افزونه‌ی خود راه‌اندازی کرده‌اید، دستور زیر را اجرا کنید:

firebase deploy --only functions

همانطور که فرم نصب افزونه را در کنسول Firebase پر می‌کردید، ویزارد نمایش داده شده که از شما مقادیر پارامترها را درخواست می‌کند را پر کنید.

مثال کار شده: انتقال Cloud Firestore به BigQuery

ما همگام‌سازی Cloud Firestore با BigQuery را از ابتدا تا انتها تأیید می‌کنیم:

  1. در صفحه Cloud Firestore کنسول Firebase ، اگر مجموعه‌ای که به عنوان COLLECTION_PATH ( users ) تنظیم کرده‌اید از قبل وجود ندارد، آن را ایجاد کنید.
  2. یک سند با نام bigquery-mirror-test ایجاد کنید که شامل هر فیلدی با هر مقداری باشد.
  3. در صفحه BigQuery کنسول Google Cloud ، جدول خام گزارش تغییرات را جستجو کنید. این جدول باید شامل یک ردیف باشد که ایجاد سند را ثبت می‌کند:

    SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
    
  4. آخرین نمای (view) را کوئری کنید، که باید آخرین رویداد تغییر را برای تنها سند موجود برگرداند ( bigquery-mirror-test ):

    SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
    
  5. سند bigquery-mirror-test را در Cloud Firestore حذف کنید. این سند از آخرین نمای نمایش داده شده ناپدید می‌شود و یک رویداد DELETE به جدول خام گزارش تغییرات اضافه می‌شود.

    شما می‌توانید تاریخچه کامل یک سند را با استفاده از موارد زیر بررسی کنید:

    SELECT *
       FROM `PROJECT_ID.analytics.users_raw_changelog`
       WHERE document_name = "bigquery-mirror-test"
       ORDER BY timestamp ASC
    

تفاوت‌های حاصل از آزمایش افزونه:

  • تریگر به صورت fsexportbigquery (بدون پیشوند هنگام استقرار به عنوان یک تابع معمولی نسل دوم و نه یک کیت) مستقر می‌شود، نه ext-<instanceId>-fsexportbigquery . آن نام را در داشبورد و گزارش‌های Cloud Functions جستجو کنید.
  • اکنون کد شما در Firebase Local Emulator Suite به عنوان توابع معمولی اجرا می‌شود. می‌توانید مقدار پارامترهایی را که در شبیه‌ساز استفاده می‌شوند با .env.local تنظیم کنید. همچنین می‌توانید کد خود را با استفاده از firebase-functions-test SDK همانطور که در بخش تست واحد Cloud Functions توضیح داده شده است، تست واحد کنید.
  • تأمین منابع دیگر توسط زمان اجرای Extensions هدایت نمی‌شود. اگر جدول گزارش تغییرات پس از استقرار از دست رفته باشد، وظیفه راه‌اندازی را به صورت دستی دوباره اجرا کنید: firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME . این وظیفه خودتوان است، بنابراین اجرای مجدد آن، مجموعه داده‌ها، جدول و نماها را تطبیق می‌دهد.
  • مقادیر پارامتر به جای فرم نصب، از .env ‎ می‌آیند، بنابراین اجرای مجدد firebase deploy پس از تکمیل فایل .env ‎ غیرتعاملی خواهد بود.

۱۰. کیت تابع خود را در npm منتشر کنید

پس از تأیید تبدیل خود از افزونه به تابع نسل دوم، می‌توانید با استفاده از یکی از راهنماهای زیر، یک نسخه کاندید را برای آزمایش سرتاسری در npm منتشر کنید:

وقتی کیت شما در npm منتشر شد، می‌تواند با استفاده از firebase functions:kits:install نصب شود و به عنوان جایگزین رسمی افزونه شما در نظر گرفته شود.

ما اکیداً توصیه می‌کنیم ابتدا یک نسخه کاندید منتشر کنید. کیت‌ها بر اساس نام و نسخه بسته نصب می‌شوند، بنابراین یک نسخه پیش‌انتشار به شما امکان می‌دهد جریان نصب واقعی را در رجیستری آزمایش کنید بدون اینکه یک بسته ناتمام را در معرض دید کاربرانی که با latest برچسب پیش‌فرض نصب می‌کنند، قرار دهید.

قبل از انتشار:

  1. یک نام بسته انتخاب کنید. هر دو نام scoped و unscoped کار می‌کنند (به راهنماهای بالا مراجعه کنید). توجه داشته باشید که بسته‌های scoped به طور پیش‌فرض private هستند، بنابراین --access public را به آن اختصاص دهید.
  2. فایل را بسازید و بررسی کنید که چه چیزهایی را در فایل اصلی و انواع فایل‌ها ( main و types ) به خروجی کامپایل شده شما (در مثال حل شده ما lib/ ) اشاره می‌کنند، بنابراین آن دایرکتوری باید در فایل tar منتشر شده گنجانده شود. .npmignore یا یک files allow-list استفاده کنید و نتیجه را با npm pack --dry-run بررسی کنید. یک اسکریپت prepublishOnly که فایل ساخته شده شما را اجرا می‌کند، از انتشار خروجی‌های قدیمی جلوگیری می‌کند.

افزونه‌های package.json که انتشار را به طور پیش‌فرض ایمن می‌کنند:

{
  "files": ["lib", "README.md", "CHANGELOG.md"],
  "publishConfig": { "access": "public", "tag": "next" },
  "scripts": {
    "build": "tsc -b",
    "prepublishOnly": "npm run build && npm test"
  }
}

فیلد "publishConfig": { "tag": "next" } تضمین می‌کند که یک npm publish هرگز latest بازنویسی نمی‌کند.

حذف یک کاندیدای انتشار:

برای مثال، برای افزایش نسخه به صورت محلی از 0.0.2-rc.3 به 0.0.2-rc.4 (اگر package.json در ریشه مخزن باشد، این دستور در گیت کامیت و تگ‌گذاری می‌شود):

npm version prerelease --preid rc

برای انتشار نسخه کاندید و ثبت @your-org/your-kit@0.0.2-rc.4 در npm تحت تگ next :

npm publish

نمایش نسخه جدید توسط وب‌سایت npm ممکن است چند دقیقه طول بکشد؛ npm view مستقیماً رجیستری را می‌خواند:

npm view @your-org/your-kit versions dist-tags

پس از تکمیل مراحل ۱۱ و ۱۲ این راهنما، می‌توانید بسته را به نسخه پایدار ارتقا دهید:

npm version 0.0.2
npm publish --tag latest
npm dist-tag add @your-org/your-kit@0.0.2 next

نکته‌ای در مورد npm-shrinkwrap.json : اکیداً توصیه می‌کنیم فایل npm-shrinkwrap.json را به بسته خود اضافه کنید؛ در صورت عدم انجام این کار، رابط خط فرمان (CLI) در هنگام نصب به کاربران هشدار می‌دهد. این امر تضمین می‌کند که کاربران از وابستگی‌های دقیقی که شما در برابر آنها آزمایش کرده‌اید استفاده کنند و به محافظت در برابر حملات زنجیره تأمین کمک می‌کند. با این حال، shrinkwrap به طور کامل در پروژه‌های کاربران شما اعمال می‌شود، از جمله در طول ساخت Cloud Functions ( npm ci )، جایی که ورودی‌های فقط توسعه‌یافته می‌توانند با EBADPLATFORM با شکست مواجه شوند. ممکن است لازم باشد "dev": true و devDependencies را از نسخه shrinkwrap منتشر شده حذف کنید.

مثال کار شده: انتقال Cloud Firestore به BigQuery

package.json کیت در زمان انتشار چهارمین نسخه کاندید:

{
  "name": "@firebase-function-kits/firestore-bigquery-export",
  "version": "0.0.2-rc.4",
  "repository": {
    "type": "git",
    "url": "https://github.com/firebase/extensions.git",
    "directory": "kits/firestore-bigquery-export"
  },
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
  "engines": { "node": "22" },
  "scripts": { "build": "tsc -b" }
}

توجه داشته باشید که در این مورد، کیت در یک monorepo قرار دارد، بنابراین مهم است که repository.directory را برای لینک رجیستری npm که به پوشه صحیح اشاره می‌کند، اضافه کنید. CHANGELOG.md آن یادداشت‌هایی را برای نسخه در انتظار انتشار نگه می‌دارد.

۱۱. تست به عنوان یک کیت تابع

پس از انتشار کیت خود، توصیه می‌کنیم کیت خود را با استفاده از npm آزمایش کنید.

مطمئن شوید که از نسخه firebase-tools >= 15.32.0 استفاده می‌کنید و کیت را نصب کنید:

firebase functions:kits:install --package <your-package-name>@<your-prerelease-version>

این دستور، بسته شما را از npm دانلود می‌کند، آن را در یک دایرکتوری منبع جدید برای کیت شما تنظیم می‌کند و شما را در پیکربندی اولین نمونه، مشابه جریان نصب افزونه‌ها، راهنمایی می‌کند. پس از نصب و راه‌اندازی بسته خود به صورت محلی، یک deploy اجرا کنید تا منابع در پروژه Google Cloud شما ایجاد شوند:

firebase deploy --only functions:<your-kit-instance-id>

پس از نصب، رابط خط فرمان Firebase CLI) یک دستور استقرار مشابه با شناسه نمونه دقیقی که هنگام نصب انتخاب کرده‌اید، چاپ می‌کند.

کیت خود را دوباره با استفاده از دستورالعمل‌های مرحله ۹ اعتبارسنجی کنید. تابع نسل دوم خود را آزمایش کنید . اکنون که با استفاده از کیت‌ها مستقر می‌شوید، توابع شما پیشوند دارند و به صورت kit-<instance-id>-<method-name> نامگذاری شده‌اند. این به کیت‌ها اجازه می‌دهد تا چندین نمونه داشته باشند و یک تابع را چندین بار در یک پروژه مستقر کنند، هر کدام با یک نام منحصر به فرد.

۱۲. جایگزینی مهاجرت آزمایشی

شما می‌توانید یک نمونه افزونه فعال راه‌اندازی کنید و سپس از راهنمای مهاجرت کاربر (با استفاده از firebase ext:migrate --package یا دستورات رابط خط فرمان function kits ) برای تکمیل آزمایش function kit خود به عنوان جایگزین مهاجرت استفاده کنید.

۱۳. کاربران و گوگل را از جایگزین رسمی افزونه خود مطلع کنید.

زمانی که جایگزین کیت تابع شما آماده و به عنوان یک بسته npm در دسترس قرار گرفت که کاربران باید به آن مهاجرت کنند، هم کاربران خود و هم گوگل را از این جایگزین رسمی مطلع کنید. فایل README.md را در مخزن GitHub که افزونه شما را میزبانی می‌کند، با اطلاعات زیر به‌روزرسانی کنید:

<!-- FIREBASE_EXTENSION_REPLACEMENT: extension="<your-extesion-id>" package="<your-npm-package-name>" -->
> [!WARNING]
> **Deprecation Notice:** The Firebase Extension `<your-extension>` is deprecated. Migrate to the [<your-npm-package-name>](<link-to-your-npm-package>) package.

گوگل فایل‌های README افزونه‌های شناخته‌شده را برای یافتن کامنت‌هایی مانند <!-- FIREBASE_EXTENSION_REPLACEMENT: extension="firebase/firestore-bigquery-export" package="@firebase-function-kits/firestore-bigquery-export" --> اسکن می‌کند و از این برای پر کردن رجیستری رسمی جایگزین‌های ذخیره‌شده در مخزن firebase-tools با نام replacements.json استفاده می‌کند. همچنین می‌توانید replacements.json بررسی کنید تا ببینید کدام README.md برای افزونه شما اسکن خواهد شد. فهرست رسمی جایگزین‌ها هفتگی به‌روزرسانی می‌شود.

مثال کار شده: انتقال Cloud Firestore به BigQuery

افزونه‌ی firestore-bigquery-export به README.md شامل موارد زیر است:

<!-- FIREBASE_EXTENSION_REPLACEMENT: extension="firebase/firestore-bigquery-export" package="@firebase-function-kits/firestore-bigquery-export" -->
> [!WARNING]
> **Deprecation Notice:** The Firebase Extension `firebase/firestore-bigquery-export` is deprecated. Please migrate to the [`@firebase-function-kits/firestore-bigquery-export`](https://www.npmjs.com/package/@firebase-function-kits/firestore-bigquery-export) package.