Skip to content

Instantly share code, notes, and snippets.

@loucou
Last active April 6, 2022 23:18
Show Gist options
  • Save loucou/878ec2d8c0c0b1ec33ed9a4926e6970f to your computer and use it in GitHub Desktop.
Save loucou/878ec2d8c0c0b1ec33ed9a4926e6970f to your computer and use it in GitHub Desktop.
function countListener(onCountChange: (count: number) => void) {
const query = firebase.firestore().collection("fruits");
let count = 0;
return query.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if (change.type === "added") onCountChange(++count);
if (change.type === "removed") onCountChange(--count);
});
});
}
const db = admin.firestore();
const fruitTrigger = functions.firestore.document("fruits/{id}");
export const onCreate = fruitTrigger.onCreate((doc, context) =>
updateCount(context.eventId, +1)
);
export const onDelete = fruitTrigger.onDelete((doc, context) =>
updateCount(context.eventId, -1)
);
async function updateCount(eventId: string, delta: number) {
try {
// create will throw ALREADY_EXISTS if the event has already been processed
await db
.doc(`deduped_fruits_counter/counter/events/${eventId}`)
.create({ createdAt: firestore.FieldValue.serverTimestamp() });
await db
.doc("deduped_fruits_counter/counter")
.set({ count: firestore.FieldValue.increment(delta) }, { merge: true });
if (Math.random() < 1.0 / 300) {
await cleanup();
}
} catch (error) {
if (error.code === GrpcStatus.ALREADY_EXISTS) {
functions.logger.debug("Duplicated event trigger!");
} else {
throw error;
}
}
}
async function cleanup() {
const limitDate = new Date(Date.now() - 1000 * 60 * 10);
const batch = db.batch();
const pastEvents = await db
.collection("deduped_fruits_counter/counter/events")
.orderBy("createdAt", "asc")
.where("createdAt", "<", limitDate)
.limit(400)
.get();
pastEvents.forEach(event => batch.delete(event.ref));
await batch.commit();
}
const fruitTrigger = functions.firestore.document("fruits/{id}");
export const onCreate = fruitTrigger.onCreate(() => updateCount(+1));
export const onDelete = fruitTrigger.onDelete(() => updateCount(-1));
async function updateCount(delta: number) {
await admin
.firestore()
.doc("simple_fruits_counter/counter")
.set({ count: firestore.FieldValue.increment(delta) }, { merge: true });
}
async function paginatedCount() {
const query = admin
.firestore()
.collection("fruits")
.orderBy(firestore.FieldPath.documentId(), "asc");
let lastDocId = "";
let count = 0;
while (true) {
const offsetQuery = lastDocId ? query.startAfter(lastDocId) : query;
const snapshot = await offsetQuery.limit(2).get();
const size = snapshot.size;
if (size === 0) break;
count += size;
lastDocId = snapshot.docs[size - 1].id;
}
return count;
}
export const onSchedule = functions.pubsub.schedule("every 15 minutes").onRun(async () => {
const count = await streamedCount();
await admin
.firestore()
.doc("refreshed_fruits_counter/counter")
.set({ count, updatedAt: firestore.FieldValue.serverTimestamp() });
});
const db = admin.firestore();
const fruitTrigger = functions.firestore.document("fruits/{id}");
export const onCreate = fruitTrigger.onCreate((snapshot, context) =>
updateCount(snapshot.id, +1)
);
export const onDelete = fruitTrigger.onDelete((snapshot, context) =>
updateCount(snapshot.id, -1)
);
let pool: any = null;
async function updateCount(fruitId: string, action: number) {
pool = pool || (await createPool());
if (action === 1) {
await pool.query("INSERT IGNORE INTO fruits VALUES (?)", [fruitId]);
} else {
await pool.query("DELETE FROM fruits WHERE id = ?", [fruitId]);
}
const res = await pool.query("SELECT COUNT(*) AS count FROM fruits");
await db.doc("sql_fruits_counter/counter").set({ count: res[0].count });
}
async function createPool() {
// ...
// see https://cloud.google.com/sql/docs/mysql/connect-functions#public-ip-default_1
}
function streamedCount() {
const query = admin.firestore().collection("fruits");
return new Promise<number>(resolve => {
let count = 0;
const stream = query.stream();
stream.on("data", _ => ++count);
stream.on("end", () => resolve(count));
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment