@genkit-ai/google-cloud
Version:
Genkit AI framework plugin for Google Cloud Platform including Firestore trace/state store and deployment helpers for Cloud Functions for Firebase.
1 lines • 43.6 kB
Source Map (JSON)
{"version":3,"sources":["../../src/session-store/firestore.ts"],"sourcesContent":["/**\n * Copyright 2026 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 {\n Firestore,\n type CollectionReference,\n type DocumentReference,\n type DocumentSnapshot,\n type Transaction,\n} from '@google-cloud/firestore';\nimport {\n GenkitError,\n applyPatch,\n diff,\n type JsonPatch,\n type SessionSnapshot,\n type SessionState,\n type SessionStore,\n type SessionStoreOptions,\n type SnapshotMutator,\n} from 'genkit/beta';\nimport { logger } from 'genkit/logging';\n\n/**\n * Default number of turns between full-state checkpoints.\n *\n * Chosen to favor the common chat / `useChat` workload, where per-turn state is\n * small and read cost dominates. Per-save reconstruction reads grow ~linearly\n * with the interval while checkpoint write/storage cost shrinks with it, so the\n * op-cost optimum is roughly `sqrt(6 * checkpointShardCount)` (≈small for tiny\n * state); 25 sits near that optimum for chat while staying conservative for\n * larger states. Raise it (e.g. 50-100) for large per-turn state retained\n * long-term; lower it (e.g. 10) for small-state, read-heavy sessions.\n */\nconst DEFAULT_CHECKPOINT_INTERVAL = 25;\n\n/**\n * Default maximum size (in bytes) of a single shard / diff document. Kept well\n * under Firestore's 1 MiB per-document limit so that no individual write can be\n * rejected for being too large.\n */\nconst DEFAULT_SHARD_SIZE = 512 * 1024;\n\n/**\n * Fallback prefix used when no {@link FirestoreSessionStoreOptions.snapshotPathPrefix}\n * is configured.\n */\nconst DEFAULT_PREFIX = 'global';\n\n/**\n * Options for {@link FirestoreSessionStore}.\n */\nexport interface FirestoreSessionStoreOptions {\n /**\n * An explicit Firestore instance. Defaults to a new {@link Firestore}\n * instance (which picks up Application Default Credentials and the\n * `FIRESTORE_EMULATOR_HOST` environment variable).\n */\n db?: Firestore;\n /**\n * The collection where snapshot documents are stored (keyed by\n * `snapshotId`). Defaults to `\"genkit-sessions\"`. Two companion collections\n * are derived from it: `\"<collection>-pointers\"` holds one pointer document\n * per session and `\"<collection>-shards\"` holds the sharded checkpoint\n * state.\n */\n collection?: string;\n /**\n * Number of turns between full-state checkpoints. A larger value stores\n * fewer (but reconstructs over more) diffs; a smaller value reconstructs\n * faster at the cost of more frequent full-state writes. Defaults to\n * {@link DEFAULT_CHECKPOINT_INTERVAL}.\n *\n * Cost tuning: per-save reconstruction reads grow ~linearly with this value\n * (so per-interval read work is ~quadratic in it), while checkpoint write and\n * storage cost shrink with it. Lower it (e.g. 10) for small-state, read-heavy\n * sessions; raise it (e.g. 50-100) for large per-turn state retained for a\n * long time, where checkpoint write/storage amplification dominates.\n */\n checkpointInterval?: number;\n\n /**\n * Maximum size in bytes of a single shard / diff document. Checkpoint state\n * is split into chunks of this size, and any diff exceeding it is promoted to\n * a (sharded) checkpoint so that no document approaches Firestore's 1 MiB\n * limit. Defaults to {@link DEFAULT_SHARD_SIZE}.\n */\n shardSize?: number;\n\n /**\n * Returns a per-tenant prefix derived from the call's\n * {@link SessionStoreOptions} (e.g. the authenticated user id from\n * `options.context`). When set, all snapshot, pointer and shard documents are\n * nested under a tenant-scoped subcollection keyed by this prefix, so reads\n * and writes are isolated per tenant: one tenant can never see (or even\n * address) another's snapshots, even if they get hold of a `snapshotId` -\n * resolving it still requires the matching, auth-derived prefix. Defaults to\n * `\"global\"`.\n *\n * The prefix is used only to build document paths; the stored ids\n * (`snapshotId`, `parentId`, `checkpointId`, ...) remain prefix-agnostic.\n */\n snapshotPathPrefix?: (options?: SessionStoreOptions) => string;\n}\n\n/**\n * The persisted shape of a snapshot document.\n *\n * A session's history is stored as a chain of per-turn documents. To keep\n * reads and document sizes bounded regardless of how long a session grows,\n * documents come in two `kind`s:\n *\n * - `checkpoint` - a full materialization of the session state at that turn.\n * The state itself is stored *out of band*, sharded across the shards\n * collection (see {@link ShardDoc}), so a checkpoint never approaches the\n * 1 MiB document limit. Written for the session root, every\n * `checkpointInterval` turns, and whenever a single turn's diff would be too\n * large.\n * - `diff` - only the {@link JsonPatch} (`statePatch`) that transforms its\n * parent's state into its own.\n *\n * Every document carries the metadata needed to reconstruct it with a single\n * batched, strongly-consistent `getAll` (no queries / secondary indexes):\n * `checkpointId` (the nearest checkpoint ancestor), `checkpointShardCount`, and\n * `segmentPath` (the ordered diff IDs from that checkpoint down to this\n * document). Because `segmentPath` resets at every checkpoint, the *number of\n * diff documents* read per reconstruction is bounded by `checkpointInterval`,\n * not by total session length. (The number of shard documents still scales\n * with the state's size - i.e. with session length - since each checkpoint\n * stores the full accumulated state.)\n */\ninterface SnapshotDoc {\n snapshotId: string;\n sessionId: string;\n parentId?: string;\n createdAt: string;\n updatedAt?: string;\n status?: SessionSnapshot['status'];\n /** Heartbeat timestamp (RFC 3339) for an in-flight detached turn. */\n heartbeatAt?: SessionSnapshot['heartbeatAt'];\n finishReason?: SessionSnapshot['finishReason'];\n error?: SessionSnapshot['error'];\n /** `checkpoint` stores full state in shards; `diff` stores `statePatch`. */\n kind: 'diff' | 'checkpoint';\n /** Nearest checkpoint ancestor (equals `snapshotId` when a checkpoint). */\n checkpointId: string;\n /** Shard count of the checkpoint identified by `checkpointId`. */\n checkpointShardCount: number;\n /**\n * Ordered diff IDs from the checkpoint (exclusive) to this document\n * (inclusive). Empty for a checkpoint. Applying these patches in order onto\n * the checkpoint's state materializes this document's state.\n */\n segmentPath: string[];\n /** RFC 6902 patch from the parent's state. Only set for `kind: 'diff'`. */\n statePatch?: JsonPatch;\n}\n\n/**\n * One shard of a checkpoint's materialized state. The full state is\n * JSON-serialized to UTF-8 and split into byte-bounded chunks stored at\n * `<checkpointId>_<index>`; concatenating the chunks and parsing the result\n * yields the original state.\n */\ninterface ShardDoc {\n chunk: Buffer;\n}\n\n/**\n * The per-session pointer document. Tracks the current leaf snapshot and the\n * metadata needed to reconstruct it (its checkpoint, shard count and segment\n * path) so the common `sessionId` lookup is a single pointer read followed by\n * one batched `getAll`. It deliberately does *not* cache the full state, so it\n * can never approach the 1 MiB document limit no matter how long the session\n * grows.\n */\ninterface PointerDoc {\n currentSnapshotId: string;\n checkpointId: string;\n checkpointShardCount: number;\n segmentPath: string[];\n updatedAt: string;\n}\n\n/**\n * Chain metadata about a parent snapshot needed to extend the chain - the\n * nearest checkpoint, its shard count and the diff segment leading to the\n * parent. Deliberately excludes the parent's (potentially large) state so it\n * can be resolved without a full reconstruction.\n */\ninterface ParentChainMeta {\n checkpointId: string;\n checkpointShardCount: number;\n segmentPath: string[];\n}\n\n/**\n * A minimal batched read interface so reconstruction can run identically\n * against a transaction or the bare Firestore instance. Both `get` and\n * `getAll` are document-ID lookups, which Firestore serves with strong\n * consistency (unlike queries), keeping reconstruction deterministic.\n */\ninterface Reader {\n get(ref: DocumentReference): Promise<DocumentSnapshot>;\n getAll(refs: DocumentReference[]): Promise<DocumentSnapshot[]>;\n}\n\n/**\n * Strips `undefined` members (Firestore rejects them) while preserving JSON\n * semantics - matching how snapshot state is diffed and reconstructed.\n */\nfunction sanitize<T>(value: T): T {\n return JSON.parse(JSON.stringify(value ?? null));\n}\n\n/**\n * A Firestore-backed {@link SessionStore} that persists session snapshots as\n * incremental JSON Patch diffs anchored to periodic, sharded full-state\n * checkpoints.\n *\n * Storage layout (the `<prefix>` segment is the per-tenant prefix returned by\n * {@link FirestoreSessionStoreOptions.snapshotPathPrefix}, or `\"global\"` when\n * none is configured):\n *\n * - `<collection>/<prefix>/snapshots/<snapshotId>` - one document per snapshot.\n * A `diff` document holds the patch from its parent (`statePatch`); a\n * `checkpoint` document holds a full-state materialization (sharded out of\n * band).\n * - `<collection>-shards/<prefix>/shards/<checkpointId>_<index>` - the sharded\n * full state for a checkpoint.\n * - `<collection>-pointers/<prefix>/pointers/<sessionId>` - one document per\n * session pointing at the latest leaf snapshot and the metadata needed to\n * reconstruct it.\n *\n * Reconstruction uses only document-ID lookups (`getAll`), so it needs no\n * secondary indexes and is strongly consistent. No single document approaches\n * the 1 MiB limit (state is sharded by `shardSize`), and the number of *diff*\n * documents touched per read/write is bounded by `checkpointInterval` rather\n * than total session length - so the store scales to arbitrarily long sessions\n * (e.g. coding agents, long-lived chatbots). Note that checkpoints still store\n * the full accumulated state, so checkpoint shard count (and the bytes written\n * per checkpoint) grow with the state's size; tune `checkpointInterval` to\n * trade per-save diff reads against checkpoint write amplification.\n */\nexport class FirestoreSessionStore<S = unknown> implements SessionStore<S> {\n protected db: Firestore;\n protected collection: string;\n protected checkpointInterval: number;\n protected shardSize: number;\n protected snapshotPathPrefix?: (options?: SessionStoreOptions) => string;\n\n constructor(opts?: FirestoreSessionStoreOptions) {\n this.db = opts?.db ?? new Firestore();\n this.collection = opts?.collection ?? 'genkit-sessions';\n this.checkpointInterval =\n opts?.checkpointInterval ?? DEFAULT_CHECKPOINT_INTERVAL;\n this.shardSize = opts?.shardSize ?? DEFAULT_SHARD_SIZE;\n this.snapshotPathPrefix = opts?.snapshotPathPrefix;\n }\n\n /** Resolves the (per-tenant) prefix for the given call options. */\n private prefixFor(options?: SessionStoreOptions): string {\n return this.snapshotPathPrefix\n ? this.snapshotPathPrefix(options)\n : DEFAULT_PREFIX;\n }\n\n /** The (per-tenant) snapshots subcollection. */\n private snapshotsCol(options?: SessionStoreOptions): CollectionReference {\n return this.db\n .collection(this.collection)\n .doc(this.prefixFor(options))\n .collection('snapshots');\n }\n\n /** The (per-tenant) pointers subcollection. */\n private pointersCol(options?: SessionStoreOptions): CollectionReference {\n return this.db\n .collection(`${this.collection}-pointers`)\n .doc(this.prefixFor(options))\n .collection('pointers');\n }\n\n /** The (per-tenant) shards subcollection. */\n private shardsCol(options?: SessionStoreOptions): CollectionReference {\n return this.db\n .collection(`${this.collection}-shards`)\n .doc(this.prefixFor(options))\n .collection('shards');\n }\n\n async getSnapshot(opts: {\n snapshotId?: string;\n sessionId?: string;\n context?: SessionStoreOptions['context'];\n }): Promise<SessionSnapshot<S> | undefined> {\n const { snapshotId, sessionId } = this.normalize(opts);\n const options: SessionStoreOptions = { context: opts.context };\n\n // Reconstruct inside a read-only transaction so the pointer read and the\n // batched shard/diff reads all observe a single, consistent point in time.\n // Without this, a concurrent checkpoint write - which overwrites a\n // checkpoint's shards in place and may delete now-stale trailing shards\n // (see `writeShards`) - could let a reader stitch together a mix of old and\n // new chunks, yielding a `DATA_LOSS` (missing shard) error or a corrupt\n // `JSON.parse`. A read-only transaction also avoids the contention/retries\n // of a read-write one.\n return this.db.runTransaction(\n async (tx) => {\n const reader = this.reader(tx);\n\n if (sessionId) {\n const pointerSnap = await tx.get(\n this.pointersCol(options).doc(sessionId)\n );\n if (!pointerSnap.exists) return undefined;\n const pointer = pointerSnap.data() as PointerDoc;\n // Reconstruct straight from the pointer's checkpoint metadata - one\n // batched round-trip, no extra read of the leaf document.\n const reconstructed = await this.reconstructFrom(\n reader,\n pointer.checkpointId,\n pointer.checkpointShardCount,\n pointer.segmentPath,\n pointer.currentSnapshotId,\n options\n );\n if (!reconstructed) return undefined;\n return this.toSnapshot(reconstructed.doc, reconstructed.state);\n }\n\n const reconstructed = await this.reconstruct(\n reader,\n snapshotId!,\n options\n );\n if (!reconstructed) return undefined;\n return this.toSnapshot(reconstructed.doc, reconstructed.state);\n },\n { readOnly: true }\n );\n }\n\n async saveSnapshot(\n snapshotId: string | undefined,\n mutator: SnapshotMutator<S>,\n options?: SessionStoreOptions\n ): Promise<string | null> {\n return this.db.runTransaction(async (tx) => {\n const reader = this.reader(tx);\n\n // Reads phase 1: load the existing snapshot (if any) so the mutator can\n // inspect the current full state.\n let existing: { doc: SnapshotDoc; state: SessionState<S> } | undefined;\n if (snapshotId) {\n existing = await this.reconstruct(reader, snapshotId, options);\n }\n const current = existing\n ? this.toSnapshot(existing.doc, existing.state)\n : undefined;\n\n const result = mutator(current);\n if (result === null) return null;\n\n const id =\n snapshotId || result.snapshotId || globalThis.crypto.randomUUID();\n // Prefer the snapshot's top-level `sessionId`; fall back to the id carried\n // in its state for rows written before snapshot-level ids existed.\n const sessionId = result.sessionId ?? result.state?.sessionId;\n if (!sessionId) {\n throw new GenkitError({\n status: 'INVALID_ARGUMENT',\n message: `FirestoreSessionStore requires 'sessionId' to be set on the snapshot.`,\n });\n }\n const newState = (result.state ?? {}) as SessionState<S>;\n\n // Reads phase 2: the per-session pointer (current leaf metadata).\n const pointerRef = this.pointersCol(options).doc(sessionId);\n const pointerSnap = await tx.get(pointerRef);\n const pointer = pointerSnap.exists\n ? (pointerSnap.data() as PointerDoc)\n : undefined;\n\n let kind: 'diff' | 'checkpoint';\n let checkpointId: string;\n let checkpointShardCount: number;\n let segmentPath: string[];\n let statePatch: JsonPatch | undefined;\n\n if (existing) {\n // Upsert: preserve the document's role and chain position; only the\n // state/metadata change. Callers must only upsert the *leaf* -\n // rewriting a non-leaf snapshot's state would invalidate its\n // descendants' diffs.\n if (existing.doc.kind === 'checkpoint') {\n ({\n kind,\n checkpointId,\n checkpointShardCount,\n segmentPath,\n statePatch,\n } = this.writeCheckpoint(\n tx,\n id,\n newState,\n options,\n existing.doc.checkpointShardCount\n ));\n } else {\n // Reads phase 3 (diff upsert): resolve parent state for the patch.\n const parentState = existing.doc.parentId\n ? (await this.reconstruct(reader, existing.doc.parentId, options))\n ?.state\n : undefined;\n const candidatePatch = diff(parentState, newState);\n // Promote an oversized diff to a (sharded) checkpoint so even an\n // in-place leaf rewrite can never push the document past the 1 MiB\n // limit. Safe because callers only upsert the leaf, which has no\n // descendants depending on its chain position.\n if (this.byteLength(candidatePatch) > this.shardSize) {\n ({\n kind,\n checkpointId,\n checkpointShardCount,\n segmentPath,\n statePatch,\n } = this.writeCheckpoint(tx, id, newState, options));\n } else {\n kind = 'diff';\n checkpointId = existing.doc.checkpointId;\n checkpointShardCount = existing.doc.checkpointShardCount;\n segmentPath = existing.doc.segmentPath;\n statePatch = candidatePatch;\n }\n }\n } else {\n // New snapshot: resolve the parent's *chain metadata* (no state) to\n // decide diff vs checkpoint. Materializing the parent's full state is\n // deferred until we know we actually need a diff - so the expensive\n // reconstruction is skipped on every checkpoint-boundary turn (which\n // would rewrite the whole state regardless).\n let parentMeta: ParentChainMeta | undefined;\n if (result.parentId) {\n parentMeta = await this.loadParentChainMeta(\n reader,\n result.parentId,\n pointer,\n options\n );\n }\n\n if (\n !result.parentId ||\n !parentMeta ||\n parentMeta.segmentPath.length + 1 >= this.checkpointInterval\n ) {\n // Write a full checkpoint without ever reconstructing the parent's\n // state, for any of: a session root, an orphaned parent, or reaching\n // the checkpoint interval (whose final segment is exactly the longest,\n // costliest one we'd otherwise pay to reconstruct here).\n ({\n kind,\n checkpointId,\n checkpointShardCount,\n segmentPath,\n statePatch,\n } = this.writeCheckpoint(tx, id, newState, options));\n } else {\n // Diff candidate: now we must materialize the parent's state to\n // compute the patch.\n const parentState = (\n await this.reconstructFrom(\n reader,\n parentMeta.checkpointId,\n parentMeta.checkpointShardCount,\n parentMeta.segmentPath,\n result.parentId,\n options\n )\n )?.state;\n const candidatePatch = diff(parentState, newState);\n // Promote oversized diffs to checkpoints so a single large turn is\n // sharded rather than rejected by the 1 MiB limit.\n if (this.byteLength(candidatePatch) > this.shardSize) {\n ({\n kind,\n checkpointId,\n checkpointShardCount,\n segmentPath,\n statePatch,\n } = this.writeCheckpoint(tx, id, newState, options));\n } else {\n kind = 'diff';\n checkpointId = parentMeta.checkpointId;\n checkpointShardCount = parentMeta.checkpointShardCount;\n segmentPath = [...parentMeta.segmentPath, id];\n statePatch = candidatePatch;\n }\n }\n }\n\n // Writes phase.\n const doc: SnapshotDoc = {\n snapshotId: id,\n sessionId,\n parentId: result.parentId,\n createdAt: result.createdAt,\n updatedAt: result.updatedAt ?? result.createdAt,\n status: result.status,\n heartbeatAt: result.heartbeatAt,\n finishReason: result.finishReason,\n error: result.error,\n kind,\n checkpointId,\n checkpointShardCount,\n segmentPath,\n statePatch,\n };\n tx.set(this.snapshotsCol(options).doc(id), sanitize(doc));\n\n // Advance the pointer when this is a new leaf, or refresh it when we just\n // rewrote the current leaf. Upserts of older, non-leaf snapshots leave\n // the pointer untouched.\n const isNew = !existing;\n if (isNew || !pointer || pointer.currentSnapshotId === id) {\n tx.set(\n pointerRef,\n sanitize<PointerDoc>({\n currentSnapshotId:\n isNew || !pointer ? id : pointer.currentSnapshotId,\n checkpointId,\n checkpointShardCount,\n segmentPath,\n updatedAt: new Date().toISOString(),\n })\n );\n }\n\n return id;\n });\n }\n\n onSnapshotStateChange(\n snapshotId: string,\n callback: (snapshot: SessionSnapshot<S>) => void,\n options?: SessionStoreOptions\n ): void | (() => void) {\n const ref = this.snapshotsCol(options).doc(snapshotId);\n return ref.onSnapshot(async (docSnap) => {\n if (!docSnap.exists) return;\n try {\n const snapshot = await this.getSnapshot({\n snapshotId,\n context: options?.context,\n });\n if (snapshot) callback(snapshot);\n } catch (err) {\n // Swallow errors so a transient read failure (network / permission)\n // doesn't surface as an unhandled promise rejection and crash the\n // process. The next snapshot event will retry.\n logger.error(\n `FirestoreSessionStore.watch failed to load snapshot ${snapshotId}`,\n err\n );\n }\n });\n }\n\n /**\n * Validates that exactly one of `snapshotId` / `sessionId` is provided, and\n * that the provided one is a non-blank string. Blank / whitespace-only ids\n * are rejected up front (rather than silently treated as \"absent\") so callers\n * get a clear `INVALID_ARGUMENT` instead of an unusable document key.\n */\n private normalize(opts: { snapshotId?: string; sessionId?: string }): {\n snapshotId?: string;\n sessionId?: string;\n } {\n const snapshotId = opts.snapshotId?.trim() ? opts.snapshotId : undefined;\n const sessionId = opts.sessionId?.trim() ? opts.sessionId : undefined;\n if (!!snapshotId === !!sessionId) {\n throw new GenkitError({\n status: 'INVALID_ARGUMENT',\n message:\n `getSnapshot requires exactly one non-empty 'snapshotId' or ` +\n `'sessionId' (got ${snapshotId ? 'snapshotId' : opts.snapshotId !== undefined ? 'blank snapshotId' : 'neither'}` +\n `${sessionId ? ' and sessionId' : opts.sessionId !== undefined ? ' and blank sessionId' : ''}).`,\n });\n }\n return { snapshotId, sessionId };\n }\n\n /** Builds a {@link Reader} bound to a transaction or the bare instance. */\n private reader(tx?: Transaction): Reader {\n if (tx) {\n return {\n get: (ref) => tx.get(ref),\n getAll: (refs) =>\n refs.length ? tx.getAll(...refs) : Promise.resolve([]),\n };\n }\n return {\n get: (ref) => ref.get(),\n getAll: (refs) =>\n refs.length ? this.db.getAll(...refs) : Promise.resolve([]),\n };\n }\n\n /**\n * Resolves a parent's chain metadata (nearest checkpoint, shard count and\n * segment path) *without* materializing its - potentially large - state.\n *\n * In the common linear case the parent is the session's current leaf, so the\n * metadata is read straight off the pointer and this performs *zero*\n * document reads. Otherwise it reads the single parent document. Crucially,\n * resolving only the metadata lets `saveSnapshot` decide diff-vs-checkpoint\n * (which only needs `segmentPath.length`) before paying for a full state\n * reconstruction - so checkpoint-boundary turns, which would rewrite the\n * whole state anyway, skip reconstruction entirely.\n */\n private async loadParentChainMeta(\n reader: Reader,\n parentId: string,\n pointer: PointerDoc | undefined,\n options?: SessionStoreOptions\n ): Promise<ParentChainMeta | undefined> {\n if (pointer && pointer.currentSnapshotId === parentId) {\n return {\n checkpointId: pointer.checkpointId,\n checkpointShardCount: pointer.checkpointShardCount,\n segmentPath: pointer.segmentPath,\n };\n }\n const snap = await reader.get(this.snapshotsCol(options).doc(parentId));\n if (!snap.exists) return undefined;\n const d = snap.data() as SnapshotDoc;\n return {\n checkpointId: d.checkpointId,\n checkpointShardCount: d.checkpointShardCount,\n segmentPath: d.segmentPath,\n };\n }\n\n /**\n * Reconstructs the state of `id` by reading its document to learn its\n * checkpoint and segment path, then materializing from that checkpoint.\n * Returns `undefined` when the snapshot does not exist.\n */\n private async reconstruct(\n reader: Reader,\n id: string,\n options?: SessionStoreOptions\n ): Promise<{ doc: SnapshotDoc; state: SessionState<S> } | undefined> {\n const snap = await reader.get(this.snapshotsCol(options).doc(id));\n if (!snap.exists) return undefined;\n const d = snap.data() as SnapshotDoc;\n return this.reconstructFrom(\n reader,\n d.checkpointId,\n d.checkpointShardCount,\n d.segmentPath,\n id,\n options\n );\n }\n\n /**\n * Materializes the state of `targetId` from a known checkpoint using a single\n * batched, strongly-consistent `getAll`: the checkpoint's shards, the\n * (bounded) segment of diff documents along `segmentPath`, and - only when\n * the target *is* the checkpoint - the checkpoint document itself. The diffs\n * are then applied in order onto the checkpoint's state. Cost is bounded by\n * `checkpointInterval` + shard count, independent of total session length.\n *\n * Note: when `segmentPath` is non-empty the state comes entirely from the\n * shards and the target's metadata from the last segment document, so the\n * checkpoint *document* is not read - saving one read on the common path.\n */\n private async reconstructFrom(\n reader: Reader,\n checkpointId: string,\n shardCount: number,\n segmentPath: string[],\n targetId: string,\n options?: SessionStoreOptions\n ): Promise<{ doc: SnapshotDoc; state: SessionState<S> } | undefined> {\n const targetIsCheckpoint = segmentPath.length === 0;\n const snapshotsCol = this.snapshotsCol(options);\n const shardsCol = this.shardsCol(options);\n const checkpointRef = snapshotsCol.doc(checkpointId);\n const shardRefs = Array.from({ length: shardCount }, (_, i) =>\n shardsCol.doc(`${checkpointId}_${i}`)\n );\n const segRefs = segmentPath.map((sid) => snapshotsCol.doc(sid));\n\n const snaps = await reader.getAll([\n // The checkpoint document is only needed when it is itself the target;\n // otherwise the last segment document carries the target metadata.\n ...(targetIsCheckpoint ? [checkpointRef] : []),\n ...shardRefs,\n ...segRefs,\n ]);\n\n // `getAll` does not guarantee result order matches request order, so index\n // the snapshots by their (fully-qualified) path and look each up explicitly.\n const byPath = new Map<string, DocumentSnapshot>();\n for (const s of snaps) byPath.set(s.ref.path, s);\n\n const shardSnaps = shardRefs.map((ref) => byPath.get(ref.path)!);\n let state = this.stitch(shardSnaps) as SessionState<S> | undefined;\n\n if (targetIsCheckpoint) {\n const checkpointSnap = byPath.get(checkpointRef.path);\n if (!checkpointSnap?.exists) return undefined;\n const checkpointDoc = checkpointSnap.data() as SnapshotDoc;\n if (checkpointDoc.snapshotId !== targetId) return undefined;\n return { doc: checkpointDoc, state: (state ?? {}) as SessionState<S> };\n }\n\n let targetDoc: SnapshotDoc | undefined;\n for (const ref of segRefs) {\n const segSnap = byPath.get(ref.path);\n if (!segSnap?.exists) return undefined; // Missing diff: corrupt chain.\n const segDoc = segSnap.data() as SnapshotDoc;\n state = applyPatch(state, segDoc.statePatch ?? []);\n targetDoc = segDoc;\n }\n\n if (!targetDoc || targetDoc.snapshotId !== targetId) return undefined;\n return { doc: targetDoc, state: (state ?? {}) as SessionState<S> };\n }\n\n /**\n * Serializes `state` to UTF-8, splits it into `shardSize`-byte chunks, and\n * writes them at `<checkpointId>_<index>`. When `oldShardCount` exceeds the\n * new count (a shrinking re-checkpoint), the now-stale trailing shards are\n * deleted. Returns the number of shards written.\n */\n private writeShards(\n tx: Transaction,\n checkpointId: string,\n state: SessionState<S>,\n options?: SessionStoreOptions,\n oldShardCount = 0\n ): number {\n const shardsCol = this.shardsCol(options);\n // `JSON.stringify` already drops `undefined` members, so it produces the\n // same bytes as `sanitize(state)` without the extra parse+stringify round\n // trip - a meaningful saving when checkpointing large states.\n const buf = Buffer.from(JSON.stringify(state ?? null), 'utf8');\n const count = Math.max(1, Math.ceil(buf.length / this.shardSize));\n for (let i = 0; i < count; i++) {\n // Copy the slice into its own buffer. `subarray` returns a view sharing\n // the parent's underlying ArrayBuffer, which the Firestore serializer can\n // persist in full rather than just the sliced range.\n const chunk = Buffer.from(\n buf.subarray(i * this.shardSize, (i + 1) * this.shardSize)\n );\n tx.set(shardsCol.doc(`${checkpointId}_${i}`), {\n chunk,\n } satisfies ShardDoc);\n }\n for (let i = count; i < oldShardCount; i++) {\n tx.delete(shardsCol.doc(`${checkpointId}_${i}`));\n }\n return count;\n }\n\n /**\n * Writes a full-state checkpoint at `id` (sharding the state via\n * {@link writeShards}) and returns the snapshot metadata describing it: a\n * checkpoint anchors itself (`checkpointId === id`), has an empty\n * `segmentPath`, and carries no `statePatch`.\n *\n * This is the shared promotion path used whenever a snapshot must be a\n * checkpoint rather than a diff - the session root, an orphaned parent, a\n * checkpoint-interval boundary, an in-place checkpoint rewrite, and the\n * promotion of an oversized diff (whether new turn or leaf upsert) so that no\n * single document approaches Firestore's 1 MiB limit. Pass `oldShardCount`\n * when re-checkpointing an existing checkpoint so stale trailing shards are\n * pruned.\n */\n private writeCheckpoint(\n tx: Transaction,\n id: string,\n state: SessionState<S>,\n options?: SessionStoreOptions,\n oldShardCount = 0\n ): {\n kind: 'checkpoint';\n checkpointId: string;\n checkpointShardCount: number;\n segmentPath: string[];\n statePatch: undefined;\n } {\n return {\n kind: 'checkpoint',\n checkpointId: id,\n checkpointShardCount: this.writeShards(\n tx,\n id,\n state,\n options,\n oldShardCount\n ),\n segmentPath: [],\n statePatch: undefined,\n };\n }\n\n /** Concatenates ordered shard documents and parses the materialized state. */\n private stitch(shardSnaps: DocumentSnapshot[]): unknown {\n if (shardSnaps.length === 0) return undefined;\n const buffers: Buffer[] = [];\n for (const s of shardSnaps) {\n if (!s.exists) {\n throw new GenkitError({\n status: 'DATA_LOSS',\n message: `FirestoreSessionStore: missing checkpoint shard '${s.id}'.`,\n });\n }\n buffers.push((s.data() as ShardDoc).chunk);\n }\n return JSON.parse(Buffer.concat(buffers).toString('utf8'));\n }\n\n /** UTF-8 byte length of a JSON-serializable value. */\n private byteLength(value: unknown): number {\n return Buffer.byteLength(JSON.stringify(value ?? null), 'utf8');\n }\n\n /** Assembles a {@link SessionSnapshot} from a document and its state. */\n private toSnapshot(\n doc: SnapshotDoc,\n state: SessionState<S>\n ): SessionSnapshot<S> {\n const snapshot: SessionSnapshot<S> = {\n snapshotId: doc.snapshotId,\n sessionId: doc.sessionId,\n createdAt: doc.createdAt,\n // Normalize to plain objects: values reconstructed from Firestore\n // documents (e.g. patch operands) can carry non-plain prototypes.\n state: sanitize(state),\n };\n\n if (doc.parentId !== undefined) snapshot.parentId = doc.parentId;\n if (doc.updatedAt !== undefined) snapshot.updatedAt = doc.updatedAt;\n if (doc.status !== undefined) snapshot.status = doc.status;\n if (doc.heartbeatAt !== undefined) snapshot.heartbeatAt = doc.heartbeatAt;\n if (doc.finishReason !== undefined)\n snapshot.finishReason = doc.finishReason;\n if (doc.error !== undefined) snapshot.error = doc.error;\n return snapshot;\n }\n}\n"],"mappings":"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","names":["reconstructed"]}