@@ -33,9 +33,8 @@ import {
33
33
import * as logs from "./logs" ;
34
34
import * as events from "./events" ;
35
35
import { getChangeType , getDocumentId } from "./util" ;
36
- import { DocumentSnapshot } from "firebase-admin/firestore" ;
37
36
38
- // Configuration for the Firestore Event History Tracker.
37
+ // Configuration for the Firestore Event History Tracker
39
38
const eventTrackerConfig = {
40
39
firestoreInstanceId : config . databaseId ,
41
40
tableId : config . tableId ,
@@ -67,27 +66,27 @@ const eventTrackerConfig = {
67
66
logLevel : config . logLevel ,
68
67
} ;
69
68
70
- // Initialize the Firestore Event History Tracker with the given configuration.
71
- const eventTracker : FirestoreBigQueryEventHistoryTracker =
72
- new FirestoreBigQueryEventHistoryTracker ( eventTrackerConfig ) ;
69
+ const eventTracker = new FirestoreBigQueryEventHistoryTracker (
70
+ eventTrackerConfig
71
+ ) ;
73
72
74
- // Initialize logging.
75
73
logs . logger . setLogLevel ( config . logLevel ) ;
76
74
logs . init ( ) ;
77
75
78
- /** Initialize Firebase Admin SDK if not already initialized */
79
76
if ( admin . apps . length === 0 ) {
80
77
admin . initializeApp ( ) ;
81
78
}
82
79
83
- // Setup the event channel for EventArc.
84
80
events . setupEventChannel ( ) ;
85
81
86
- // Define a type for task data to ensure consistency
82
+ /**
83
+ * Task data structure for BigQuery synchronization
84
+ */
87
85
interface SyncBigQueryTaskData {
88
86
timestamp : string ;
89
87
eventId : string ;
90
- documentPath : string ;
88
+ relativePath : string ;
89
+ fullResourceName : string ;
91
90
changeType : ChangeType ;
92
91
documentId : string ;
93
92
params : Record < string , any > | null ;
@@ -96,39 +95,38 @@ interface SyncBigQueryTaskData {
96
95
}
97
96
98
97
/**
99
- * Cloud Function to handle enqueued tasks to synchronize Firestore changes to BigQuery.
98
+ * Handles enqueued tasks for syncing Firestore changes to BigQuery
100
99
*/
101
100
export const syncBigQuery = functions . tasks
102
101
. taskQueue ( )
103
102
. onDispatch ( async ( taskData : SyncBigQueryTaskData , ctx ) => {
104
- const documentName = taskData . documentPath ;
103
+ const fullResourceName = taskData . fullResourceName ;
105
104
const eventId = taskData . eventId ;
106
105
const operation = taskData . changeType ;
107
106
108
107
logs . logEventAction (
109
108
"Firestore event received by onDispatch trigger" ,
110
- documentName ,
109
+ fullResourceName ,
111
110
eventId ,
112
111
operation
113
112
) ;
114
113
115
114
try {
116
- // Use the shared function to write the event to BigQuery
117
115
await recordEventToBigQuery (
118
116
taskData . changeType ,
119
117
taskData . documentId ,
118
+ taskData . fullResourceName ,
120
119
taskData . data ,
121
120
taskData . oldData ,
122
121
taskData
123
122
) ;
124
123
125
- // Record a success event in EventArc, if configured
126
124
await events . recordSuccessEvent ( {
127
125
subject : taskData . documentId ,
128
126
data : {
129
127
timestamp : taskData . timestamp ,
130
128
operation : taskData . changeType ,
131
- documentName : taskData . documentPath ,
129
+ documentName : taskData . fullResourceName ,
132
130
documentId : taskData . documentId ,
133
131
pathParams : taskData . params ,
134
132
eventId : taskData . eventId ,
@@ -137,13 +135,11 @@ export const syncBigQuery = functions.tasks
137
135
} ,
138
136
} ) ;
139
137
140
- // Log completion of the task.
141
138
logs . complete ( ) ;
142
139
} catch ( err ) {
143
- // Log error and throw it to handle in the calling function.
144
140
logs . logFailedEventAction (
145
141
"Failed to write event to BigQuery from onDispatch handler" ,
146
- documentName ,
142
+ fullResourceName ,
147
143
eventId ,
148
144
operation ,
149
145
err as Error
@@ -153,35 +149,34 @@ export const syncBigQuery = functions.tasks
153
149
}
154
150
} ) ;
155
151
152
+ /**
153
+ * Main Cloud Function that triggers on Firestore document changes
154
+ * and sends the data to BigQuery
155
+ */
156
156
export const fsexportbigquery = onDocumentWritten (
157
157
`${ config . collectionPath } /{documentId}` ,
158
158
async ( event ) => {
159
159
const { data, ...context } = event ;
160
-
161
- // Start logging the function execution.
162
160
logs . start ( ) ;
163
161
164
- // Determine the type of change (CREATE, UPDATE, DELETE) from the new event data.
165
162
const changeType = getChangeType ( data ) ;
166
163
const documentId = getDocumentId ( data ) ;
167
-
168
- // Check if the document is newly created or deleted.
169
164
const isCreated = changeType === ChangeType . CREATE ;
170
165
const isDeleted = changeType === ChangeType . DELETE ;
171
166
172
- // Get the new and old data from the snapshot.
173
167
const newData = isDeleted ? undefined : data . after . data ( ) ;
174
168
const oldData =
175
169
isCreated || config . excludeOldData ? undefined : data . before . data ( ) ;
176
170
177
- // check this is the full doc name
178
- const documentName = context . document ;
171
+ const relativeName = context . document ;
172
+ const projectId = config . projectId ;
173
+ const fullResourceName = `projects/${ projectId } /databases/${ config . databaseId } /documents/${ relativeName } ` ;
179
174
const eventId = context . id ;
180
175
const operation = changeType ;
181
176
182
177
logs . logEventAction (
183
178
"Firestore event received by onDocumentWritten trigger" ,
184
- documentName ,
179
+ fullResourceName ,
185
180
eventId ,
186
181
operation
187
182
) ;
@@ -190,13 +185,12 @@ export const fsexportbigquery = onDocumentWritten(
190
185
let serializedOldData : any ;
191
186
192
187
try {
193
- // Serialize the data before processing.
194
188
serializedData = eventTracker . serializeData ( newData ) ;
195
189
serializedOldData = eventTracker . serializeData ( oldData ) ;
196
190
} catch ( err ) {
197
191
logs . logFailedEventAction (
198
192
"Failed to serialize data" ,
199
- documentName ,
193
+ fullResourceName ,
200
194
eventId ,
201
195
operation ,
202
196
err as Error
@@ -205,7 +199,6 @@ export const fsexportbigquery = onDocumentWritten(
205
199
}
206
200
207
201
try {
208
- // Record the start event in EventArc, if configured.
209
202
await events . recordStartEvent ( {
210
203
documentId,
211
204
changeType,
@@ -219,16 +212,17 @@ export const fsexportbigquery = onDocumentWritten(
219
212
}
220
213
221
214
try {
222
- // Write the change event to BigQuery.
223
215
await recordEventToBigQuery (
224
216
changeType ,
225
217
documentId ,
218
+ fullResourceName ,
226
219
serializedData ,
227
220
serializedOldData ,
228
221
{
229
222
timestamp : context . time ,
230
223
eventId : context . id ,
231
- documentPath : context . document ,
224
+ relativePath : context . document ,
225
+ fullResourceName,
232
226
changeType,
233
227
documentId,
234
228
params : config . wildcardIds ? context . params : null ,
@@ -238,11 +232,12 @@ export const fsexportbigquery = onDocumentWritten(
238
232
) ;
239
233
} catch ( err ) {
240
234
logs . failedToWriteToBigQueryImmediately ( err as Error ) ;
241
- // Handle enqueue errors with retries and backup to GCS.
235
+
242
236
await attemptToEnqueue ( err , {
243
237
timestamp : context . time ,
244
238
eventId : context . id ,
245
- documentPath : context . document ,
239
+ relativePath : context . document ,
240
+ fullResourceName : fullResourceName ,
246
241
changeType,
247
242
documentId,
248
243
params : config . wildcardIds ? context . params : null ,
@@ -251,49 +246,49 @@ export const fsexportbigquery = onDocumentWritten(
251
246
} ) ;
252
247
}
253
248
254
- // Log the successful completion of the function.
255
249
logs . complete ( ) ;
256
250
}
257
251
) ;
258
252
259
253
/**
260
- * Record the event to the Firestore Event History Tracker and BigQuery.
254
+ * Records a Firestore document change event to BigQuery
261
255
*
262
- * @param changeType - The type of change (CREATE, UPDATE, DELETE).
263
- * @param documentId - The ID of the Firestore document.
264
- * @param serializedData - The serialized new data of the document.
265
- * @param serializedOldData - The serialized old data of the document.
266
- * @param taskData - The task data containing event information.
256
+ * @param changeType - The type of change (CREATE, UPDATE, DELETE)
257
+ * @param documentId - The ID of the Firestore document
258
+ * @param fullResourceName - Fully-qualified Firestore document path
259
+ * @param serializedData - The serialized new data
260
+ * @param serializedOldData - The serialized old data
261
+ * @param taskData - Task metadata containing event information
267
262
*/
268
263
async function recordEventToBigQuery (
269
264
changeType : ChangeType ,
270
265
documentId : string ,
266
+ fullResourceName : string ,
271
267
serializedData : any ,
272
268
serializedOldData : any ,
273
269
taskData : SyncBigQueryTaskData
274
270
) {
275
271
const event : FirestoreDocumentChangeEvent = {
276
- timestamp : taskData . timestamp , // Cloud Firestore commit timestamp
277
- operation : changeType , // The type of operation performed
278
- documentName : taskData . documentPath , // The document name
279
- documentId, // The document ID
272
+ timestamp : taskData . timestamp ,
273
+ operation : changeType ,
274
+ documentName : fullResourceName ,
275
+ documentId,
280
276
pathParams : taskData . params as
281
277
| FirestoreDocumentChangeEvent [ "pathParams" ]
282
- | null , // Path parameters, if any
283
- eventId : taskData . eventId , // The event ID from Firestore
284
- data : serializedData , // Serialized new data
285
- oldData : serializedOldData , // Serialized old data
278
+ | null ,
279
+ eventId : taskData . eventId ,
280
+ data : serializedData ,
281
+ oldData : serializedOldData ,
286
282
} ;
287
283
288
- // Record the event in the Firestore Event History Tracker and BigQuery.
289
284
await eventTracker . record ( [ event ] ) ;
290
285
}
291
286
292
287
/**
293
- * Handle errors when enqueueing tasks to sync BigQuery.
288
+ * Handles task enqueueing with retry logic when BigQuery sync fails
294
289
*
295
- * @param err - The error object.
296
- * @param taskData - The task data to be enqueued.
290
+ * @param err - The error that occurred
291
+ * @param taskData - The task data to enqueue
297
292
*/
298
293
async function attemptToEnqueue ( _err : Error , taskData : SyncBigQueryTaskData ) {
299
294
try {
@@ -303,36 +298,31 @@ async function attemptToEnqueue(_err: Error, taskData: SyncBigQueryTaskData) {
303
298
) ;
304
299
305
300
let attempts = 0 ;
306
- const jitter = Math . random ( ) * 100 ; // Adding jitter to avoid collision
307
-
308
- // Exponential backoff formula with a maximum of 5 + jitter seconds
301
+ const jitter = Math . random ( ) * 100 ;
309
302
const backoff = ( attempt : number ) =>
310
303
Math . min ( Math . pow ( 2 , attempt ) * 100 , 5000 ) + jitter ;
311
304
312
305
while ( attempts < config . maxEnqueueAttempts ) {
313
306
if ( attempts > 0 ) {
314
- // Wait before retrying to enqueue the task.
315
307
await new Promise ( ( resolve ) => setTimeout ( resolve , backoff ( attempts ) ) ) ;
316
308
}
317
309
318
310
attempts ++ ;
319
311
try {
320
312
await queue . enqueue ( taskData ) ;
321
- break ; // Break the loop if enqueuing is successful.
313
+ break ;
322
314
} catch ( enqueueErr ) {
323
- // Throw the error if max attempts are reached.
324
315
if ( attempts === config . maxEnqueueAttempts ) {
325
316
throw enqueueErr ;
326
317
}
327
318
}
328
319
}
329
320
} catch ( enqueueErr ) {
330
- // Record the error event.
331
321
await events . recordErrorEvent ( enqueueErr as Error ) ;
332
322
333
323
logs . logFailedEventAction (
334
324
"Failed to enqueue event to Cloud Tasks from onWrite handler" ,
335
- taskData . documentPath ,
325
+ taskData . fullResourceName ,
336
326
taskData . eventId ,
337
327
taskData . changeType ,
338
328
enqueueErr as Error
@@ -341,37 +331,27 @@ async function attemptToEnqueue(_err: Error, taskData: SyncBigQueryTaskData) {
341
331
}
342
332
343
333
/**
344
- * Cloud Function to set up BigQuery sync by initializing the event tracker.
334
+ * Sets up BigQuery synchronization by initializing the event tracker
345
335
*/
346
336
export const setupBigQuerySync = functions . tasks
347
337
. taskQueue ( )
348
338
. onDispatch ( async ( ) => {
349
- /** Setup runtime environment */
350
339
const runtime = getExtensions ( ) . runtime ( ) ;
351
-
352
- // Initialize the BigQuery sync.
353
340
await eventTracker . initialize ( ) ;
354
-
355
- // Update the processing state.
356
341
await runtime . setProcessingState (
357
342
"PROCESSING_COMPLETE" ,
358
343
"Sync setup completed"
359
344
) ;
360
345
} ) ;
361
346
362
347
/**
363
- * Cloud Function to initialize BigQuery sync.
348
+ * Initializes BigQuery synchronization
364
349
*/
365
350
export const initBigQuerySync = functions . tasks
366
351
. taskQueue ( )
367
352
. onDispatch ( async ( ) => {
368
- /** Setup runtime environment */
369
353
const runtime = getExtensions ( ) . runtime ( ) ;
370
-
371
- // Initialize the BigQuery sync.
372
354
await eventTracker . initialize ( ) ;
373
-
374
- // Update the processing state.
375
355
await runtime . setProcessingState (
376
356
"PROCESSING_COMPLETE" ,
377
357
"Sync setup completed"
0 commit comments