يوضّح لك هذا الدليل كيفية نقل الإضافات من بيئة Firebase Extensions المتوقّفة نهائيًا إلى دالة يثبّتها المستخدمون وينشرونها في قاعدة الرموز البرمجية الخاصة بهم في Cloud Functions لـ Firebase (الجيل الثاني).
هذا هو مسار نقل البيانات المقترَح. ستحتفظ Firebase بقائمة من الإضافات التي لها مكافئات npm رسمية، ويشرح لك هذا الدليل كيفية إنشاء إضافتك.
في هذا الدليل، يتم استخدام إضافة بث Cloud Firestore إلى
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، وسيتم الردّ برسالة إلكترونية لطلب الانضمام. يجب الردّ على هذه الرسالة الإلكترونية، وليس النقر على الزر "الانضمام إلى هذه المجموعة".
قبل البدء
لإكمال عملية النقل هذه كما هو موضّح، ستستخدم الميزات التالية في Cloud Functions:
Parameterized Configuration. تصبح كل مَعلمة يتم تعريفها في
extension.yamlمَعلمة محدّدة في رمز الحزمة.أدوار "إدارة الهوية وإمكانية الوصول" التعريفية وواجهات برمجة التطبيقات المطلوبة: يصبح كل دور تعلنه في
extension.yamlعبارة عن طلبrequiresRole(...)، وتصبح كل واجهة برمجة تطبيقات عبارة عن طلبrequiresAPI(...)في رمز الحزمة. في وقت النشر، تمنح واجهة سطر الأوامر Firebase الأدوار المُعلَن عنها لحساب خدمة وقت تشغيل مُدار وتفعّل واجهات برمجة التطبيقات المُعلَن عنها نيابةً عنك.أحداث يتم في مراحل النشاط لقواعد الرموز البرمجية في Cloud Functions تتيح قواعد الرموز البرمجية في Cloud Functions الآن استخدام الأحداث التي تتم في مراحل النشاط بشكل مشابه لـ Firebase Extensions. يمكنك تحديد الإعدادات التي يتم تطبيقها عند التثبيت وعند التحديث باستخدام دالّتَي مراحل النشاط
afterFirstDeploy(...)وafterRedeploy(...). وتحلّ هذه محلّlifecycleEventsالتي تعلن عنها فيextension.yaml.
نقل مصدر Firebase Extensions إلى دالة من الجيل الثاني
(اختياري) نقل البيانات تلقائيًا باستخدام مهارة وكيل Firebase
يمكنك تنفيذ الخطوات من 1 إلى 8 بشكل آلي (جرد الموارد، وتفعيل
الترقيات، وتحويل المَعلمات والبيانات السرية، وإدارة الهوية وإمكانية الوصول التعريفية، وخطافات مراحل النشاط،
وإنشاء ملف README للحزمة) باستخدام مهارة وكيل الذكاء الاصطناعي
extension-to-functions-codebase
الرسمية.
تثبيت المهارة
إذا لم يسبق لك أو لمساعد البرمجة بالذكاء الاصطناعي (Gemini في Firebase أو Cursor أو Claude Code أو GitHub Copilot) تثبيت المهارة، يمكنك تشغيل الأمر التالي باستخدام واجهة سطر الأوامر (CLI) للمهارات:
npx skills add firebase/agent-skills --skill extension-to-functions-codebase
بعد تثبيت المهارة في مشروعك، يتّبع مساعد البرمجة بالذكاء الاصطناعي قواعد نقل البيانات وخطوات التحويل تلقائيًا. يمكنك استخدام الطلب التالي:
"يُرجى نقل إضافة Firebase هذه إلى حزمة أدوات دالة قابلة للنشر من الجيل الثاني باتّباع التعليمات الواردة في مهارة extension-to-functions-codebase".
1. إدراج الإضافة في المستودع
ابدأ بإجراء جرد للإضافة: قائمة كاملة بكل ما تعلنه الإضافة وتشحنه وتوثّقه، بحيث يكون لكل سلوك وجهة محدّدة في دالة الجيل الثاني ولا يتم فقدان أي شيء في عملية النقل.
راجِع كل مما يلي، ودوِّن ما تجده:
extension.yaml، الذي يُعلن عن المَعلمات والدوال والأحداث وأدوار "إدارة الهوية وإمكانية الوصول" وواجهات برمجة التطبيقات المطلوبة والرموز السرية وخطافات مراحل النشاط.
functions/، الذي يحتوي على رمز الدالة والتبعيات وإعدادات التصميم والمشغّلات ودوال قائمة انتظار المهام.
README.mdوPREINSTALL.mdوPOSTINSTALL.mdالتي تحتوي على خطوات الإعداد والتحذيرات وملاحظات الفوترةscripts/، الذي يحتوي على أي أدوات استيراد أو تعبئة أو إدارة الهوية وإمكانية الوصول أو إصلاح أو نقل، وأي أدوات أخرى يتم شحنها مع الإضافة.
بعد ذلك، لكل عنصر في
extension.yaml، حدِّد مكانه
في حزمة npm:
حوِّل إعدادات المستخدم إلى مَعلمات Cloud Functions (الخطوة 4).
حوِّل الرموز السرية إلى رموز سرية في Cloud Functions (الخطوة 4).
حوِّل أدوار "إدارة الهوية وإمكانية الوصول" إلى بيانات
requiresRole(...)(الخطوة 6).تحويل واجهات برمجة تطبيقات Google المطلوبة إلى تعريفات
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 |
25 (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، لذا لا يوجد
أي شيء يمكن نقله للبيانات السرية في الخطوة 4. أما مشغّل الحدث، فهو من الجيل الثاني، ووظائف قائمة انتظار المهام فقط هي التي لا تزال من الجيل الأول (ذات صلة بالخطوة 3).
2. تعديل ملف 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 الخاص بالإضافة خاصًا، ويحدّد معرّف الإضافة، ويعلن عن 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"
}
}
3- ترقية الدوال من الجيل الأول إلى الجيل الثاني
إذا كانت الإضافة لا تزال تصدّر دوال الجيل الأول، يمكنك تحويل كل عامل تشغيل إلى ما يعادله من الجيل الثاني. استورِد من وحدات 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 التي يتم تشغيلها
بناءً على الأحداث.
4. تحويل مَعلمات الإضافات والبيانات السرية
المَعلمات
تصبح كل مَعلمة تعلن عنها في 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() لقراءة السلسلة داخل معالج، واستخدِم
collectionPath مباشرةً في المكان الذي يُتوقّع فيه استخدام عنصر نائب، مثل مسار
مشغّل دالة.
تكتشف واجهة سطر الأوامر (CLI) في 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، لذا لن يتمكن المستخدمون من
ضبطها أو إلغائها هناك. لا تظهر القيم التي تُدخلها واجهة سطر الأوامر لنظام المَعلمات. اقرأها مباشرة من البيئة:
// Before
const instanceId = process.env.EXT_INSTANCE_ID;
// After
const instanceId = process.env.FIREBASE_KIT_INSTANCE_ID;
لن يتم ضبط المتغيّر إلا عند نشر الحزمة كمجموعة. إذا تم نشر الرمز البرمجي أيضًا كقاعدة رموز برمجية مستقلة (راجِع الخطوة 9)، يمكنك التعامل معه كرمز اختياري أو إيقاف العملية بسرعة مع عرض رسالة واضحة عند عدم توفّره. إذا كانت الإضافة تعرض رقم تعريف المثيل كمَعلمة موجّهة للمستخدمين، يجب إزالة هذه المَعلمة، لأنّ واجهة سطر الأوامر في Firebase تملك القيمة الآن.
مثال عملي: حذف بيانات المستخدمين
(لا تقرأ إضافة "نقل البيانات من Stream Cloud Firestore إلى BigQuery" رقم تعريف المثيل، لذا لا حاجة إلى نقل أي بيانات هناك. تستخدم إضافة "حذف بيانات المستخدم" هذا المعرّف لتسمية مواضيع Pub/Sub).
قبل: يمكنك قراءته كمتغيّر بيئة أولي في config.ts باستخدام البادئة ext- التي تستخدمها الإضافات لمواردها:
// functions/src/config.ts
discoveryTopic: `ext-${process.env.EXT_INSTANCE_ID}-discovery`,
deletionTopic: `ext-${process.env.EXT_INSTANCE_ID}-deletion`,
بعد ذلك A plain process.env read of FIREBASE_KIT_INSTANCE_ID used for
two ordinary params so that users can override the topic names:
// 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`,
}),
يجب ألا تكون القيم التلقائية فارغة لأنّه يتم حلّ عمليات ربط المشغّلات في
وقت الاكتشاف. سيتم كتابة قيمة تلقائية فارغة في بيان النشر كاسم الموضوع. توفّر المجموعة أيضًا حماية ضد التشغيل خارج سياق
المجموعة. إذا كان المتغيّر غير متوفّر، سيتم تقييم القيمة التلقائية على مستوى الوحدة على أنّها
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-*. لرفض عملية النشر نفسها، يمكنك إجراء عملية التحقّق على مستوى الوحدة ليتم تنفيذها أثناء عملية الاستكشاف. بما أنّ واجهة سطر الأوامر تستمد رقم تعريف المثيل من firebase.json، لا يتوفّر INSTANCE_ID قابل للضبط، ولا يمكن مزامنة أي شيء على مستوى مثيلات متعددة.
المفاتيح السرّية
في extension.yaml، يمكنك الإعلان عن الأسرار باستخدام type: secret. تخزّن بيئة تشغيل الإضافات هذه القيم وتربطها، ليتمكّن رمز الإضافة من قراءة process.env.PARAM_NAME مباشرةً. في قاعدة رموز برمجية نموذجية في Cloud Functions،
يتم الإعلان عن كل مفتاح سرّي وربطه بشكل صريح:
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();
// ...
}
);
5- نقل طلبات قائمة انتظار المهام الداخلية
تضع بعض الإضافات العمل في قائمة انتظار المهام الخاصة بها من داخل رمز الدالة، باستخدام Firebase Admin SDK. ويختلف ذلك عن تلقّي مهمة تم إرسالها (موضّحة في قسمَي ترقية الدوال وتحويل دوال دورة الحياة). في هذه الحالة، يكون الرمز البرمجي هو المنتِج الذي يستدعي
queue.enqueue(...).
كانت الإصدارات السابقة من Admin SDK تتطلّب من الإضافات تمرير رقم تعريف مثيل الإضافة الخاص بها كمَعلمة ثانية لاستهداف دالة "قائمة انتظار المهام" في الإضافة نفسها. اعتبارًا من الإصدار 14.2.0 من "firebase-admin"، لم يعُد هذا الإجراء مطلوبًا أو يُنصح به. تستهدف واجهة Task Queue API الآن قوائم انتظار المهام في السياق نفسه (على سبيل المثال، إضافة أو حزمة) بشكل تلقائي. من الآمن إزالة هذه المَعلمة في الرمز البرمجي، سواء كإضافة أو كدوال مستقلة، وننصحك بذلك.
وتضمن إزالة هذه المَعلمة إمكانية النقل والتوافق مع الإصدارات المستقبلية.
يظل كل شيء آخر في طلب الإدراج في قائمة الانتظار كما هو، بما في ذلك مسار المورد 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);
إذا كان طلب الإضافة إلى قائمة الانتظار يستهدف قاعدة رموز برمجية تحتوي على بادئة، فإنّ اسم الدالة المكتشفة
يحتوي على بادئة أيضًا (على سبيل المثال، orders-syncBigQuery). يُرجى الاطّلاع على
مراجعة وتثبيت نسخة حزمة الدوال البديلة
والاختبار كحزمة دوال.
6. تحديد واجهات برمجة التطبيقات وأدوار "إدارة الهوية وإمكانية الوصول" المطلوبة
انقل متطلبات "إدارة الهوية وإمكانية الوصول" وواجهة برمجة التطبيقات الخاصة بإضافتك من 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 حساب خدمة مُدارًا لوقت التشغيل لقاعدة الرموز البرمجية أو تعدّله، وتمنحه مجموعة من جميع الأدوار المُعلَن عنها. يجب أن توضّح للمستخدمين أنّ جميع الدوال في قاعدة الرموز البرمجية تعمل باستخدام هذه الأدوار، ما لم تكن واجهة برمجة التطبيقات النهائية تتيح نموذجًا أضيق.
مثال عملي: بث Cloud Firestore إلى BigQuery
قبل تم الإعلان عنها في extension.yaml، حيث أتاحت بيئة تشغيل الإضافات واجهة برمجة التطبيقات ومنحت الأدوار لحساب مُدار:
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، عليك أيضًا ضبط الأدوار وواجهات برمجة التطبيقات المطلوبة لنشر الأحداث. كانت الإضافات تعالج هذا الأمر سابقًا بدون الحاجة إلى إجراء أي تغييرات في 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."
);
}
7. تحويل دوال مراحل النشاط
إذا كانت الإضافة تستدعي 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"، من خلال وقت تشغيل الإضافات:
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.
8. إعداد المستندات للمستخدمين
اكتب README للحزمة يشرح على الأقل ما يلي:
- قيم
.envالتي تتطلّبها الحزمة - الأسرار التي تتطلّبها الحزمة، وكيفية نقل قيم الأسرار الحالية
- أدوار "إدارة الهوية وإمكانية الوصول" التي تعلن عنها الحزمة باستخدام
requiresRole(...). - واجهات Google APIs التي تفعّلها الحزمة أو تتطلّبها
- The lifecycle hooks that the package declares, and how to rerun them manually.
- ملاحظات الفوترة
- التغييرات التي تم إجراؤها مقارنةً بالإضافة الأصلية
- كيفية حصول الحزمة على رقم تعريف المثيل (
FIREBASE_KIT_INSTANCE_IDالذي تم ضبطه من خلال واجهة سطر الأوامر) وأنّ جميع دوال المثيل يتم نشرها باستخدام بادئةkit-<instanceId>-.
مثال عملي: بث Cloud Firestore إلى BigQuery
تتضمّن الحزمة README جدولاً ملموسًا بعنوان "ما الذي تغيّر":
| Concern | كالإضافة | باسم @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 |
9. اختبار دالة الجيل الثاني
من المفترض أن يكون لديك الآن دالة من الجيل الثاني تعمل، عند نشرها، بشكل مماثل لعملية تثبيت جديدة لإضافتك. الخطوة التالية هي التحقّق من أي مشاكل تم إدخالها عن طريق الخطأ أثناء العملية وإصلاحها.
لاستدعاء setGlobalOptions
لضبط الخيارات العامة، مثل المنطقة التلقائية أو وحدة المعالجة المركزية، يجب إجراء ذلك فقط عند
نشر حزمة SDK كدالة مستقلة من الجيل الثاني. عند تثبيت حزمتك كحزمة 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 بشكل تام بين الأطراف:
- في صفحة Cloud Firestore ضمن وحدة تحكّم Firebase، أنشئ المجموعة
التي حدّدتها على أنّها
COLLECTION_PATH(users) إذا لم تكن موجودة من قبل. - أنشئ مستندًا باسم
bigquery-mirror-testيحتوي على أي حقول بأي قيم. في صفحة BigQuery ضمن وحدة تحكّم Google Cloud، يمكنك الاستعلام عن جدول سجلّ التغييرات الأوّلي. يجب أن يحتوي على صف واحد يسجل إنشاء المستند:
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`يمكنك الاستعلام عن أحدث طريقة عرض، والتي من المفترض أن تعرض أحدث حدث تغيير للمستند الوحيد الحالي (
bigquery-mirror-test):SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`حذف مستند "
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. يمكنك أيضًا إجراء اختبار وحدة للرمز باستخدام حزمة تطوير البرامج (SDK) لـfirebase-functions-testكما هو موضّح في اختبار وحدة Cloud Functions. - لم يعُد توفير الموارد يعتمد على وقت تشغيل الإضافات. إذا لم يظهر جدول سجلّ التغييرات بعد النشر، أعِد تشغيل مهمة الإعداد يدويًا:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. تكون المهمة متكررة، لذا فإن إعادة تشغيلها تؤدي إلى تسوية مجموعة البيانات والجدول وطرق العرض. - تأتي قيم المَعلمات من
.envبدلاً من نموذج التثبيت، لذا فإنّ عمليات إعادة تشغيلfirebase deployتكون غير تفاعلية بعد اكتمال.env.
10. نشر حزمة الدوال على npm
بعد التحقّق من صحة الإحالة الناجحة من الإضافة إلى دالة الجيل الثاني، يمكنك نشر إصدار محتمل على npm لإجراء اختبار شامل باستخدام أحد الأدلة التالية:
عند نشر حزمتك على npm، يمكن تثبيتها باستخدام
firebase functions:kits:install وإدراجها كبديل
رسمي لإضافتك.
ننصحك بشدة بنشر إصدار محتمل أولاً. يتم تثبيت الحِزم حسب اسم الحزمة وإصدارها، لذا يتيح لك الإصدار التجريبي اختبار عملية التثبيت الحقيقية في السجلّ بدون عرض حزمة غير مكتملة للمستخدمين الذين يثبّتون الحزمة باستخدام علامة latest التلقائية.
قبل النشر:
- اختَر اسم الحزمة. يمكن استخدام الأسماء ذات النطاق المحدّد وغير المحدّد (يُرجى الاطّلاع على الأدلة أعلاه). يُرجى العِلم أنّ الحزم المحدودة النطاق تكون خاصة تلقائيًا، لذا يجب تمرير
--access public. - أنشئ منتجاتك وتحقَّق من جودتها قبل شحنها. يشير
mainوtypesإلى الناتج المجمّع (lib/في المثال الذي عملنا عليه)، لذا يجب تضمين هذا الدليل في ملف tar المنشور. استخدِم.npmignoreأو قائمة مسموح بها فيfiles، وافحص النتيجة باستخدام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" } عدم استبدال latest بـ npm
publish عادي.
إنشاء إصدار محتمل:
على سبيل المثال، لزيادة الإصدار محليًا من 0.0.2-rc.3 إلى 0.0.2-rc.4
(يتم تنفيذ ذلك ووضع علامات في Git إذا كان 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
بعد إكمال الخطوة 11 والخطوة 12 من هذا الدليل، يمكنك ترقية الحزمة إلى إصدار ثابت:
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" }
}
يُرجى العِلم أنّه في هذه الحالة، تكون الحزمة في مستودع واحد، لذا من المهم تضمين repository.directory لكي يشير رابط سجلّ npm إلى المجلد الصحيح. يحتوي الحقل CHANGELOG.md على ملاحظات حول الإصدار في انتظار المراجعة.
11. الاختبار كحزمة وظائف
بعد نشر الحزمة، ننصحك باختبارها باستخدام npm.
تأكَّد من استخدام الإصدار >= 15.32.0 من
firebase-tools
وثبِّت الحزمة:
firebase functions:kits:install --package <your-package-name>@<your-prerelease-version>
يؤدي هذا إلى تنزيل الحزمة من npm، وإعدادها داخل دليل ملفات مصدر جديد للحزمة، وإرشادك خلال عملية ضبط المثيل الأول على غرار مسار تثبيت الإضافات. بعد تثبيت الحزمة وإعدادها محليًا، يمكنك نشرها لإنشاء الموارد في مشروعك على Google Cloud:
firebase deploy --only functions:<your-kit-instance-id>
بعد التثبيت، تطبع واجهة سطر الأوامر في Firebase أمر نشر مشابهًا يتضمّن معرّف المثيل الدقيق الذي اخترته أثناء التثبيت.
تحقَّق من صحة المجموعة مرة أخرى باستخدام التعليمات الواردة في
الخطوة 9. اختبِر دالة الجيل الثاني. بما أنّك تنشر الآن باستخدام المجموعات، يتم وضع بادئة لدوالك وتسميتها باسم kit-<instance-id>-<method-name>. يتيح ذلك للمجموعات أن تتضمّن نُسخًا متعددة،
ما يسمح بنشر الدالة نفسها عدة مرات في مشروع واحد، ولكل نسخة اسم
فريد.
12. اختبار استبدال نقل البيانات
يمكنك إعداد نسخة من الإضافة تعمل بشكل صحيح، ثم استخدام
دليل نقل بيانات المستخدمين
(باستخدام firebase ext:migrate --package
أو أوامر واجهة سطر الأوامر لمجموعات الدوال)
لإنهاء اختبار مجموعة الدوال كبديل لعملية نقل البيانات.
13. إبلاغ المستخدمين وGoogle بالإضافة الرسمية البديلة
بعد أن تصبح حزمة الدوال البديلة جاهزة ومتاحة كحزمة npm يجب أن ينتقل إليها المستخدمون، عليك إبلاغ كل من المستخدمين وGoogle بهذا البديل الرسمي. عدِّل ملف 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.
تفحص Google ملفات 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.