Skip to content

Commit 1982bf5

Browse files
committed
chore(*): resolve conflicts
2 parents c24f730 + bc6ef8e commit 1982bf5

File tree

6 files changed

+427
-5
lines changed

6 files changed

+427
-5
lines changed

README.md

Lines changed: 180 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Firebase Functions for Dart
22

33
[![Tests](https://github.com/invertase/firebase_functions/actions/workflows/test.yml/badge.svg)](https://github.com/invertase/firebase_functions/actions/workflows/test.yml)
4-
[![PR Checks](https://github.com/invertase/firebase_functions/actions/workflows/pr-checks.yml/badge.svg)](https://github.com/invertase/firebase_functions/actions/workflows/pr-checks.yml)
4+
[![PR Checks](https://github.com/invertase/firebase_functions/actions/workflows/test.yml/badge.svg)](https://github.com/invertase/firebase_functions/actions/workflows/pr-checks.yml)
55

66
Write Firebase Cloud Functions in Dart with full type safety and performance.
77

@@ -16,10 +16,29 @@ This package provides a complete Dart implementation of Firebase Cloud Functions
1616
| **Firestore** | ✅ Complete | `onDocumentCreated`, `onDocumentUpdated`, `onDocumentDeleted`, `onDocumentWritten`, `onDocumentCreatedWithAuthContext`, `onDocumentUpdatedWithAuthContext`, `onDocumentDeletedWithAuthContext`, `onDocumentWrittenWithAuthContext` |
1717
| **Realtime Database** | ✅ Complete | `onValueCreated`, `onValueUpdated`, `onValueDeleted`, `onValueWritten` |
1818
| **Storage** | ✅ Complete | `onObjectFinalized`, `onObjectArchived`, `onObjectDeleted`, `onObjectMetadataUpdated` |
19-
| **Firebase Alerts** | ✅ Complete | Crashlytics, Billing, Performance alerts |
19+
| **Scheduler** | ✅ Complete | `onSchedule` |
20+
| **Firebase Alerts** | ✅ Complete | `onInAppFeedbackPublished`, `onNewAnrIssuePublished`, `onNewFatalIssuePublished`, `onNewNonfatalIssuePublished`, `onNewTesterIosDevicePublished`, `onPlanAutomatedUpdatePublished`, `onPlanUpdatePublished`, `onRegressionAlertPublished`, `onStabilityDigestPublished`, `onThresholdAlertPublished`, `onVelocityAlertPublished` |
2021
| **Eventarc** | ✅ Complete | `onCustomEventPublished` |
2122
| **Identity Platform** | ✅ Complete | `beforeUserCreated`, `beforeUserSignedIn` (+ `beforeEmailSent`, `beforeSmsSent`*) |
2223

24+
## Table of Contents
25+
26+
- [Features](#features)
27+
- [Prerequisites](#prerequisites)
28+
- [Installation](#installation)
29+
- [Quick Start](#quick-start)
30+
- [HTTPS Functions](#https-functions)
31+
- [Pub/Sub Triggers](#pubsub-triggers)
32+
- [Firestore Triggers](#firestore-triggers)
33+
- [Realtime Database Triggers](#realtime-database-triggers)
34+
- [Storage Triggers](#storage-triggers)
35+
- [Scheduler Triggers](#scheduler-triggers)
36+
- [Firebase Alerts](#firebase-alerts)
37+
- [Identity Platform (Auth Blocking)](#identity-platform-auth-blocking)
38+
- [Parameters & Configuration](#parameters--configuration)
39+
- [Project Configuration](#project-configuration)
40+
- [Development](#development)
41+
2342
## Features
2443

2544
- **Type-safe**: Leverage Dart's strong type system with typed callable functions and CloudEvents
@@ -265,6 +284,26 @@ firebase.firestore.onDocumentWrittenWithAuthContext(
265284

266285
## Realtime Database Triggers
267286

287+
Respond to changes in Firebase Realtime Database. The `ref` parameter supports path wildcards (e.g., `{messageId}`) which are extracted into `event.params`.
288+
289+
| Function | Triggers when | Event data |
290+
|----------|---------------|------------|
291+
| `onValueCreated` | Data is created | `DataSnapshot?` |
292+
| `onValueUpdated` | Data is updated | `Change<DataSnapshot>?` (before/after) |
293+
| `onValueDeleted` | Data is deleted | `DataSnapshot?` (deleted data) |
294+
| `onValueWritten` | Any write (create/update/delete) | `Change<DataSnapshot>?` (before/after) |
295+
296+
### DataSnapshot API
297+
298+
The `DataSnapshot` class provides methods to inspect the data:
299+
300+
- `val()` — Returns the snapshot contents (Map, List, String, num, bool, or null)
301+
- `exists()` — Returns `true` if the snapshot contains data
302+
- `child(path)` — Gets a child snapshot at the given path
303+
- `hasChild(path)` / `hasChildren()` — Check for child data
304+
- `numChildren()` — Number of child properties
305+
- `key` — Last segment of the reference path
306+
268307
```dart
269308
// Value created
270309
firebase.database.onValueCreated(
@@ -277,7 +316,7 @@ firebase.database.onValueCreated(
277316
},
278317
);
279318
280-
// Value updated
319+
// Value updated — access before/after states
281320
firebase.database.onValueUpdated(
282321
ref: 'messages/{messageId}',
283322
(event) async {
@@ -297,7 +336,7 @@ firebase.database.onValueDeleted(
297336
},
298337
);
299338
300-
// All write operations
339+
// All write operations — determine operation type from before/after
301340
firebase.database.onValueWritten(
302341
ref: 'users/{userId}/status',
303342
(event) async {
@@ -310,8 +349,45 @@ firebase.database.onValueWritten(
310349
);
311350
```
312351

352+
### Database Instance Targeting
353+
354+
Use `ReferenceOptions` to target a specific database instance:
355+
356+
```dart
357+
firebase.database.onValueCreated(
358+
ref: 'messages/{messageId}',
359+
options: const ReferenceOptions(instance: 'my-project-default-rtdb'),
360+
(event) async {
361+
print('Instance: ${event.instance}');
362+
},
363+
);
364+
```
365+
313366
## Storage Triggers
314367

368+
Respond to changes in Cloud Storage objects. The `bucket` parameter specifies which storage bucket to watch.
369+
370+
| Function | Triggers when |
371+
|----------|---------------|
372+
| `onObjectFinalized` | Object is created or overwritten |
373+
| `onObjectDeleted` | Object is permanently deleted |
374+
| `onObjectArchived` | Object is archived (versioned buckets) |
375+
| `onObjectMetadataUpdated` | Object metadata is updated |
376+
377+
### StorageObjectData Properties
378+
379+
The event data provides full object metadata:
380+
381+
- `name` — Object path within the bucket
382+
- `bucket` — Bucket name
383+
- `contentType` — MIME type
384+
- `size` — Content length in bytes
385+
- `storageClass` — Storage class (STANDARD, NEARLINE, COLDLINE, etc.)
386+
- `metadata` — User-provided key-value metadata
387+
- `timeCreated` / `updated` / `timeDeleted` — Timestamps
388+
- `md5Hash` / `crc32c` — Checksums
389+
- `generation` / `metageneration` — Versioning info
390+
315391
```dart
316392
// Object finalized (created or overwritten)
317393
firebase.storage.onObjectFinalized(
@@ -324,7 +400,7 @@ firebase.storage.onObjectFinalized(
324400
},
325401
);
326402
327-
// Object archived
403+
// Object archived (versioned buckets only)
328404
firebase.storage.onObjectArchived(
329405
bucket: 'my-bucket',
330406
(event) async {
@@ -354,6 +430,72 @@ firebase.storage.onObjectMetadataUpdated(
354430
);
355431
```
356432

433+
## Scheduler Triggers
434+
435+
Run functions on a recurring schedule using Cloud Scheduler. The `schedule` parameter accepts standard Unix crontab expressions.
436+
437+
### Cron Syntax
438+
439+
```
440+
┌───────────── minute (0-59)
441+
│ ┌───────────── hour (0-23)
442+
│ │ ┌───────────── day of month (1-31)
443+
│ │ │ ┌───────────── month (1-12)
444+
│ │ │ │ ┌───────────── day of week (0-6, Sunday=0)
445+
│ │ │ │ │
446+
* * * * *
447+
```
448+
449+
Common examples: `0 0 * * *` (daily midnight), `*/5 * * * *` (every 5 min), `0 9 * * 1-5` (weekdays 9 AM).
450+
451+
### ScheduledEvent Properties
452+
453+
- `jobName` — Cloud Scheduler job name (null if manually invoked)
454+
- `scheduleTime` — Scheduled execution time (RFC 3339 string)
455+
- `scheduleDateTime` — Parsed `DateTime` convenience getter
456+
457+
```dart
458+
// Basic schedule — runs every day at midnight (UTC)
459+
firebase.scheduler.onSchedule(
460+
schedule: '0 0 * * *',
461+
(event) async {
462+
print('Job: ${event.jobName}');
463+
print('Schedule time: ${event.scheduleTime}');
464+
},
465+
);
466+
```
467+
468+
### Timezone and Retry Configuration
469+
470+
Use `ScheduleOptions` to set a timezone and configure retry behavior for failed invocations:
471+
472+
```dart
473+
firebase.scheduler.onSchedule(
474+
schedule: '0 9 * * 1-5',
475+
options: const ScheduleOptions(
476+
timeZone: TimeZone('America/New_York'),
477+
retryConfig: RetryConfig(
478+
retryCount: RetryCount(3),
479+
maxRetrySeconds: MaxRetrySeconds(60),
480+
minBackoffSeconds: MinBackoffSeconds(5),
481+
maxBackoffSeconds: MaxBackoffSeconds(30),
482+
),
483+
memory: Memory(MemoryOption.mb256),
484+
),
485+
(event) async {
486+
print('Executed at: ${event.scheduleDateTime}');
487+
},
488+
);
489+
```
490+
491+
| RetryConfig field | Description |
492+
|---|---|
493+
| `retryCount` | Number of retry attempts |
494+
| `maxRetrySeconds` | Maximum total time for retries |
495+
| `minBackoffSeconds` | Minimum wait before retry (0-3600) |
496+
| `maxBackoffSeconds` | Maximum wait before retry (0-3600) |
497+
| `maxDoublings` | Times to double backoff before going linear |
498+
357499
## Firebase Alerts
358500

359501
```dart
@@ -366,6 +508,17 @@ firebase.alerts.crashlytics.onNewFatalIssuePublished(
366508
},
367509
);
368510
511+
// Crashlytics velocity alerts
512+
firebase.alerts.crashlytics.onVelocityAlertPublished(
513+
(event) async {
514+
final payload = event.data?.payload;
515+
print('Velocity alert: ${payload?.issue.title}');
516+
print('Crash count: ${payload?.crashCount}');
517+
print('Percentage: ${payload?.crashPercentage}%');
518+
print('First version: ${payload?.firstVersion}');
519+
},
520+
);
521+
369522
// Billing plan updates
370523
firebase.alerts.billing.onPlanUpdatePublished(
371524
(event) async {
@@ -375,6 +528,16 @@ firebase.alerts.billing.onPlanUpdatePublished(
375528
},
376529
);
377530
531+
// Billing automated plan updates
532+
firebase.alerts.billing.onPlanAutomatedUpdatePublished(
533+
(event) async {
534+
final payload = event.data?.payload;
535+
print('Automated plan update:');
536+
print(' Plan: ${payload?.billingPlan}');
537+
print(' Type: ${payload?.notificationType}');
538+
},
539+
);
540+
378541
// Performance threshold alerts
379542
firebase.alerts.performance.onThresholdAlertPublished(
380543
options: const AlertOptions(appId: '1:123456789:ios:abcdef'),
@@ -385,6 +548,18 @@ firebase.alerts.performance.onThresholdAlertPublished(
385548
print('Actual: ${payload?.violationValue}');
386549
},
387550
);
551+
552+
// App Distribution in-app feedback
553+
firebase.alerts.appDistribution.onInAppFeedbackPublished(
554+
(event) async {
555+
final payload = event.data?.payload;
556+
print('In-app feedback:');
557+
print(' Tester: ${payload?.testerEmail}');
558+
print(' App version: ${payload?.appVersion}');
559+
print(' Text: ${payload?.text}');
560+
print(' Console: ${payload?.feedbackConsoleUri}');
561+
},
562+
);
388563
```
389564

390565
## Eventarc

example/alerts/bin/server.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ void main(List<String> args) async {
1212
print(' App ID: ${event.appId}');
1313
});
1414

15+
// Crashlytics velocity alert
16+
firebase.alerts.crashlytics.onVelocityAlertPublished((event) async {
17+
final payload = event.data?.payload;
18+
print('Crashlytics velocity alert:');
19+
print(' Issue: ${payload?.issue.title}');
20+
print(' Crash count: ${payload?.crashCount}');
21+
print(' Percentage: ${payload?.crashPercentage}%');
22+
print(' First version: ${payload?.firstVersion}');
23+
});
24+
1525
// Billing plan update alert
1626
firebase.alerts.billing.onPlanUpdatePublished((event) async {
1727
final payload = event.data?.payload;
@@ -21,6 +31,14 @@ void main(List<String> args) async {
2131
print(' Type: ${payload?.notificationType}');
2232
});
2333

34+
// Billing automated plan update alert
35+
firebase.alerts.billing.onPlanAutomatedUpdatePublished((event) async {
36+
final payload = event.data?.payload;
37+
print('Billing automated plan update:');
38+
print(' Plan: ${payload?.billingPlan}');
39+
print(' Type: ${payload?.notificationType}');
40+
});
41+
2442
// Performance threshold alert with app ID filter
2543
firebase.alerts.performance.onThresholdAlertPublished(
2644
options: const AlertOptions(appId: '1:123456789:ios:abcdef'),
@@ -35,5 +53,15 @@ void main(List<String> args) async {
3553
print(' Actual: ${payload?.violationValue} ${payload?.violationUnit}');
3654
},
3755
);
56+
57+
// App Distribution in-app feedback alert
58+
firebase.alerts.appDistribution.onInAppFeedbackPublished((event) async {
59+
final payload = event.data?.payload;
60+
print('In-app feedback:');
61+
print(' Tester: ${payload?.testerEmail}');
62+
print(' App version: ${payload?.appVersion}');
63+
print(' Text: ${payload?.text}');
64+
print(' Console: ${payload?.feedbackConsoleUri}');
65+
});
3866
});
3967
}

example/firestore/bin/server.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,26 @@ void main(List<String> args) async {
6161
},
6262
);
6363

64+
firebase.firestore.onDocumentUpdatedWithAuthContext(
65+
document: 'orders/{orderId}',
66+
(event) async {
67+
print('Document updated by: ${event.authType} (${event.authId})');
68+
final before = event.data?.before?.data();
69+
final after = event.data?.after?.data();
70+
print(' Before: $before');
71+
print(' After: $after');
72+
},
73+
);
74+
75+
firebase.firestore.onDocumentDeletedWithAuthContext(
76+
document: 'orders/{orderId}',
77+
(event) async {
78+
print('Document deleted by: ${event.authType} (${event.authId})');
79+
final data = event.data?.data();
80+
print(' Deleted data: $data');
81+
},
82+
);
83+
6484
firebase.firestore.onDocumentWrittenWithAuthContext(
6585
document: 'orders/{orderId}',
6686
(event) async {

example/firestore_test/bin/server.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,35 @@ void main(List<String> args) async {
104104
},
105105
);
106106

107+
// Test onDocumentUpdatedWithAuthContext
108+
firebase.firestore.onDocumentUpdatedWithAuthContext(
109+
document: 'orders/{orderId}',
110+
(event) async {
111+
print('=== ORDER UPDATED WITH AUTH CONTEXT ===');
112+
print('Order updated: ${event.document}');
113+
print('Auth type: ${event.authType}');
114+
print('Auth ID: ${event.authId}');
115+
if (event.data != null) {
116+
print('Before: ${event.data!.before?.data()}');
117+
print('After: ${event.data!.after?.data()}');
118+
}
119+
},
120+
);
121+
122+
// Test onDocumentDeletedWithAuthContext
123+
firebase.firestore.onDocumentDeletedWithAuthContext(
124+
document: 'orders/{orderId}',
125+
(event) async {
126+
print('=== ORDER DELETED WITH AUTH CONTEXT ===');
127+
print('Order deleted: ${event.document}');
128+
print('Auth type: ${event.authType}');
129+
print('Auth ID: ${event.authId}');
130+
if (event.data != null) {
131+
print('Deleted data: ${event.data!.data()}');
132+
}
133+
},
134+
);
135+
107136
// Test onDocumentWrittenWithAuthContext
108137
firebase.firestore.onDocumentWrittenWithAuthContext(
109138
document: 'orders/{orderId}',

test/e2e/e2e_test.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import 'helpers/pubsub_client.dart';
1616
import 'helpers/storage_client.dart';
1717
import 'tests/database_tests.dart';
1818
import 'tests/firestore_tests.dart';
19+
import 'tests/https_oncall_tests.dart';
1920
import 'tests/https_onrequest_tests.dart';
2021
import 'tests/identity_tests.dart';
2122
import 'tests/integration_tests.dart';
@@ -119,6 +120,7 @@ void main() {
119120

120121
// Run all test groups (pass closures to defer value access)
121122
runHttpsOnRequestTests(() => client, () => emulator);
123+
runHttpsOnCallTests(() => client);
122124
runIntegrationTests(() => examplePath);
123125
runPubSubTests(() => examplePath, () => pubsubClient, () => emulator);
124126
runFirestoreTests(() => examplePath, () => firestoreClient, () => emulator);

0 commit comments

Comments
 (0)