@genkit-ai/firebase
Version:
Genkit AI framework plugin for Firebase including Firestore trace/state store and deployment helpers for Cloud Functions for Firebase.
115 lines • 3.07 kB
JavaScript
import { randomUUID } from "crypto";
import {
FieldValue,
getFirestore
} from "firebase-admin/firestore";
import {
GenkitError,
StreamNotFoundError
} from "genkit/beta";
class FirestoreActionStream {
streamDoc;
constructor(streamDoc) {
this.streamDoc = streamDoc;
}
async update(data) {
await this.streamDoc.update({
...data,
updatedAt: FieldValue.serverTimestamp()
});
}
async write(chunk) {
await this.update({
// We add a random ID to the chunk to prevent Firestore from deduplicating chunks
// that have the same content.
stream: FieldValue.arrayUnion({
type: "chunk",
chunk,
uuid: randomUUID()
})
});
}
async done(output) {
await this.update({
stream: FieldValue.arrayUnion({ type: "done", output })
});
}
async error(err) {
const serializableError = {
message: err.message,
stack: err.stack,
...err
};
await this.update({
stream: FieldValue.arrayUnion({ type: "error", err: serializableError })
});
}
}
class FirestoreStreamManager {
db;
collection;
timeout;
constructor(opts) {
this.collection = opts.collection;
this.db = opts.db ?? (opts.firebaseApp ? getFirestore(opts.firebaseApp) : getFirestore());
this.timeout = opts.timeout ?? 6e4;
}
async open(streamId) {
const streamDoc = this.db.collection(this.collection).doc(streamId);
await streamDoc.set({
createdAt: FieldValue.serverTimestamp(),
stream: []
});
return new FirestoreActionStream(streamDoc);
}
async subscribe(streamId, callbacks) {
const streamDoc = this.db.collection(this.collection).doc(streamId);
const snapshot = await streamDoc.get();
if (!snapshot.exists) {
throw new StreamNotFoundError(`Stream ${streamId} not found.`);
}
let lastIndex = -1;
let timeoutId;
const resetTimeout = () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
callbacks.onError?.(
new GenkitError({
status: "DEADLINE_EXCEEDED",
message: "Stream timed out."
})
);
unsubscribe();
}, this.timeout);
};
const unsubscribe = streamDoc.onSnapshot((snapshot2) => {
resetTimeout();
const data = snapshot2.data();
if (!data) {
return;
}
const stream = data.stream || [];
for (let i = lastIndex + 1; i < stream.length; i++) {
const event = stream[i];
if (event.type === "chunk") {
callbacks.onChunk?.(event.chunk);
} else if (event.type === "done") {
clearTimeout(timeoutId);
callbacks.onDone?.(event.output);
unsubscribe();
} else if (event.type === "error") {
clearTimeout(timeoutId);
callbacks.onError?.(event.err);
unsubscribe();
}
}
lastIndex = stream.length - 1;
});
resetTimeout();
return { unsubscribe };
}
}
export {
FirestoreStreamManager
};
//# sourceMappingURL=firestore.mjs.map