Conversation
WorkaroundIn conversations v2, anything returned from
So, the idea is to persist the entire conversation state as a single string field (
This avoids invalid nested entities and CodeAdapter const { stringify, parse } = require("flatted");
const BIGINT_PREFIX = "bigint:";
const TIMESTAMP_PREFIX = "timestamp:";
function replacer(key, value) {
if (typeof value === "function" || typeof value === "symbol" || value === undefined) {
return undefined;
}
if (typeof value === "bigint") {
return BIGINT_PREFIX + value.toString();
}
if (value instanceof Date) {
return TIMESTAMP_PREFIX + value.getTime();
}
return value;
}
function reviver(key, value) {
if (typeof value === "string" && value.startsWith(BIGINT_PREFIX)) {
try {
return BigInt(value.slice(BIGINT_PREFIX.length));
}
catch { }
}
if (typeof value === "string" && value.startsWith(TIMESTAMP_PREFIX)) {
try {
return new Date(Number(value.slice(TIMESTAMP_PREFIX.length)));
}
catch { }
}
return value;
}
function firestoreAdapter(collection) {
return {
async read(key) {
const snapshot = await collection.doc(key).get();
try {
return parse(snapshot.data()?.state, reviver);
}
catch {
return undefined;
}
},
async write(key, value) {
const state = stringify(value, replacer);
await collection.doc(key).set({ state }, { merge: false });
},
async delete(key) {
await collection.doc(key).delete();
},
};
}
module.exports = {
firestoreAdapter
};Wiring const { Composer, session } = require("grammy");
const { conversations, createConversation } = require("@grammyjs/conversations");
const { hydrate } = require("@grammyjs/hydrate");
const { getFirestore } = require("firebase-admin/firestore");
const admin = require("firebase-admin");
const { firestoreAdapter } = require("../utils/adapter");
const conversation = require("./conversations/conversation");
admin.initializeApp();
const composer = new Composer();
composer.use(session({
initial: () => ({ user: null })
}));
composer.use(hydrate());
composer.use(conversations({
storage: firestoreAdapter(getFirestore().collection("conversations")),
plugins: [hydrate()]
}));
composer.use(createConversation(conversation, "conversation"));ExampleHere’s what the serialized conversation state looks like in Firestore (5.87 KB): Notes
Status / Help wantedI’d really appreciate reviews, test cases, and fixes from anyone interested. Happy to iterate on this and adjust the approach if maintainers prefer a different direction. Also, big thanks to @KnorpelSenf for the initial idea to use |
Suggested in https://t.me/grammyjs/320305