{"version":3,"sources":["../../src/stream-manager/firestore.ts"],"sourcesContent":["/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { randomUUID } from 'crypto';\nimport { App } from 'firebase-admin/app';\nimport {\n  DocumentReference,\n  FieldValue,\n  getFirestore,\n  type Firestore,\n} from 'firebase-admin/firestore';\nimport {\n  GenkitError,\n  StreamNotFoundError,\n  type ActionStreamInput,\n  type ActionStreamSubscriber,\n  type StreamManager,\n} from 'genkit/beta';\n\nexport interface FirestoreStreamManagerOptions {\n  firebaseApp?: App;\n  db?: Firestore;\n  collection: string;\n  timeout?: number;\n}\n\nclass FirestoreActionStream<S, O> implements ActionStreamInput<S, O> {\n  private streamDoc;\n\n  constructor(streamDoc: DocumentReference) {\n    this.streamDoc = streamDoc;\n  }\n\n  private async update(data: any): Promise<void> {\n    await this.streamDoc.update({\n      ...data,\n      updatedAt: FieldValue.serverTimestamp(),\n    });\n  }\n\n  async write(chunk: S): Promise<void> {\n    await this.update({\n      // We add a random ID to the chunk to prevent Firestore from deduplicating chunks\n      // that have the same content.\n      stream: FieldValue.arrayUnion({\n        type: 'chunk',\n        chunk,\n        uuid: randomUUID(),\n      }),\n    });\n  }\n\n  async done(output: O): Promise<void> {\n    await this.update({\n      stream: FieldValue.arrayUnion({ type: 'done', output }),\n    });\n  }\n\n  async error(err: any): Promise<void> {\n    const serializableError = {\n      message: err.message,\n      stack: err.stack,\n      ...err,\n    };\n    await this.update({\n      stream: FieldValue.arrayUnion({ type: 'error', err: serializableError }),\n    });\n  }\n}\n\nexport class FirestoreStreamManager implements StreamManager {\n  private db: Firestore;\n  private collection: string;\n  private timeout: number;\n\n  constructor(opts: FirestoreStreamManagerOptions) {\n    this.collection = opts.collection;\n    this.db =\n      opts.db ??\n      (opts.firebaseApp ? getFirestore(opts.firebaseApp) : getFirestore());\n    this.timeout = opts.timeout ?? 60000;\n  }\n\n  async open<S, O>(streamId: string): Promise<ActionStreamInput<S, O>> {\n    const streamDoc = this.db.collection(this.collection).doc(streamId);\n    await streamDoc.set({\n      createdAt: FieldValue.serverTimestamp(),\n      stream: [],\n    });\n    return new FirestoreActionStream(streamDoc);\n  }\n\n  async subscribe<S, O>(\n    streamId: string,\n    callbacks: ActionStreamSubscriber<S, O>\n  ): Promise<{\n    unsubscribe: () => void;\n  }> {\n    const streamDoc = this.db.collection(this.collection).doc(streamId);\n    const snapshot = await streamDoc.get();\n    if (!snapshot.exists) {\n      throw new StreamNotFoundError(`Stream ${streamId} not found.`);\n    }\n    let lastIndex = -1;\n    let timeoutId: NodeJS.Timeout;\n    const resetTimeout = () => {\n      clearTimeout(timeoutId);\n      timeoutId = setTimeout(() => {\n        callbacks.onError?.(\n          new GenkitError({\n            status: 'DEADLINE_EXCEEDED',\n            message: 'Stream timed out.',\n          })\n        );\n        unsubscribe();\n      }, this.timeout);\n    };\n    const unsubscribe = streamDoc.onSnapshot((snapshot) => {\n      resetTimeout();\n      const data = snapshot.data();\n      if (!data) {\n        return;\n      }\n      const stream = data.stream || [];\n      for (let i = lastIndex + 1; i < stream.length; i++) {\n        const event = stream[i];\n        if (event.type === 'chunk') {\n          callbacks.onChunk?.(event.chunk);\n        } else if (event.type === 'done') {\n          clearTimeout(timeoutId);\n          callbacks.onDone?.(event.output);\n          unsubscribe();\n        } else if (event.type === 'error') {\n          clearTimeout(timeoutId);\n          callbacks.onError?.(event.err);\n          unsubscribe();\n        }\n      }\n      lastIndex = stream.length - 1;\n    });\n    resetTimeout();\n    return { unsubscribe };\n  }\n}\n"],"mappings":"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","names":["snapshot"]}