{"version":3,"file":"delta_channel_history.cjs","names":["DeltaSnapshot"],"sources":["../../src/spec/delta_channel_history.ts"],"sourcesContent":["import {\n  type BaseCheckpointSaver,\n  type Checkpoint,\n  type CheckpointMetadata,\n  DeltaSnapshot,\n  emptyCheckpoint,\n  isDeltaSnapshot,\n  uuid6,\n} from \"@langchain/langgraph-checkpoint\";\nimport { RunnableConfig } from \"@langchain/core/runnables\";\nimport { CheckpointerTestInitializer } from \"../types.js\";\n\nconst meta: CheckpointMetadata = { source: \"loop\", step: 0, parents: {} };\n\n/**\n * Conformance tests for {@link BaseCheckpointSaver.getDeltaChannelHistory} — the\n * walk that reconstructs `DeltaChannel` state from `checkpoint_writes`.\n *\n * This is the one checkpointer method with no coverage in the default spec\n * suite, and it is intentionally **opt-in**: it is not run by {@link specTest},\n * so adding it does not raise the conformance bar for third-party savers (the\n * method is Beta and inherits a default implementation from\n * `BaseCheckpointSaver`). Each in-repo saver's `.spec.ts` calls it explicitly so\n * the shared base walk is validated against every backend's real storage —\n * Postgres, SQLite, Redis, MongoDB, and the `MemorySaver` override.\n *\n * The scenarios target the per-backend behaviours the walk depends on:\n * round-tripping `DeltaSnapshot` and plain-array seed blobs, following\n * `parentConfig` up the ancestor chain, and — critically — replaying concurrent\n * same-superstep writes in the canonical (task_id, idx) order regardless of how\n * a given store happens to return `pendingWrites` (insertion order, locale\n * collation, etc.).\n */\nexport function deltaChannelHistoryTests<T extends BaseCheckpointSaver>(\n  initializer: CheckpointerTestInitializer<T>\n) {\n  describe(`${initializer.checkpointerName}#getDeltaChannelHistory`, () => {\n    let checkpointer: T;\n\n    beforeEach(async () => {\n      checkpointer = await initializer.createCheckpointer();\n    });\n\n    afterEach(async () => {\n      await initializer.destroyCheckpointer?.(checkpointer);\n    });\n\n    describe.each([\"root\", \"child\"])(\"namespace: %s\", (namespace) => {\n      const checkpoint_ns = namespace === \"root\" ? \"\" : namespace;\n\n      // Append a checkpoint as a child of `parentConfig`. Channel `values` are\n      // stored as blobs, with `newVersions` derived to match so savers that only\n      // persist changed channels (Postgres, Redis) still store them.\n      async function putCheckpoint(\n        parentConfig: RunnableConfig,\n        id: string,\n        values: Record<string, unknown>\n      ): Promise<RunnableConfig> {\n        const channel_versions = Object.fromEntries(\n          Object.keys(values).map((k) => [k, 1])\n        );\n        const checkpoint: Checkpoint = {\n          ...emptyCheckpoint(),\n          id,\n          channel_values: values,\n          channel_versions,\n        };\n        return checkpointer.put(\n          parentConfig,\n          checkpoint,\n          meta,\n          channel_versions\n        );\n      }\n\n      const rootConfig = (thread_id: string): RunnableConfig => ({\n        configurable: { thread_id, checkpoint_ns },\n      });\n\n      it(\"walks ancestors collecting writes oldest→newest with a snapshot seed\", async () => {\n        const root = rootConfig(uuid6(3));\n\n        const c0 = await putCheckpoint(root, uuid6(3), {\n          messages: new DeltaSnapshot([0]),\n        });\n        await checkpointer.putWrites(c0, [[\"messages\", [1]]], \"task0\");\n        const c1 = await putCheckpoint(c0, uuid6(3), {});\n        await checkpointer.putWrites(c1, [[\"messages\", [2]]], \"task1\");\n        const c2 = await putCheckpoint(c1, uuid6(3), {});\n\n        const hist = await checkpointer.getDeltaChannelHistory({\n          config: c2,\n          channels: [\"messages\"],\n        });\n\n        expect(isDeltaSnapshot(hist.messages.seed)).toBe(true);\n        expect((hist.messages.seed as DeltaSnapshot).value).toEqual([0]);\n        // The target's own writes are excluded; c0 + c1 deltas, oldest→newest.\n        expect(hist.messages.writes.map((w) => w[2])).toEqual([[1], [2]]);\n      });\n\n      it(\"retains a plain (migration) seed and replays boundary writes\", async () => {\n        const root = rootConfig(uuid6(3));\n\n        // A thread migrated from a pre-delta channel stores a plain array at the\n        // migration-boundary checkpoint; its writes are deltas to replay on top.\n        const c0 = await putCheckpoint(root, uuid6(3), { messages: [0, 1] });\n        await checkpointer.putWrites(c0, [[\"messages\", [2]]], \"task0\");\n        const c1 = await putCheckpoint(c0, uuid6(3), {});\n\n        const hist = await checkpointer.getDeltaChannelHistory({\n          config: c1,\n          channels: [\"messages\"],\n        });\n\n        expect(isDeltaSnapshot(hist.messages.seed)).toBe(false);\n        expect(hist.messages.seed).toEqual([0, 1]);\n        expect(hist.messages.writes.map((w) => w[2])).toEqual([[2]]);\n      });\n\n      it(\"orders concurrent same-superstep writes by task id\", async () => {\n        const root = rootConfig(uuid6(3));\n\n        const c0 = await putCheckpoint(root, uuid6(3), {\n          messages: new DeltaSnapshot([]),\n        });\n        // Persist the two tasks' writes in reverse task-id order. The walk must\n        // re-sort them to (task_id, idx) so the reconstructed value matches live\n        // execution regardless of the store's pending-writes return order.\n        await checkpointer.putWrites(c0, [[\"messages\", [\"b\"]]], \"task-b\");\n        await checkpointer.putWrites(c0, [[\"messages\", [\"a\"]]], \"task-a\");\n        const c1 = await putCheckpoint(c0, uuid6(3), {});\n\n        const hist = await checkpointer.getDeltaChannelHistory({\n          config: c1,\n          channels: [\"messages\"],\n        });\n\n        expect(hist.messages.writes.map((w) => w[0])).toEqual([\n          \"task-a\",\n          \"task-b\",\n        ]);\n        expect(hist.messages.writes.map((w) => w[2])).toEqual([[\"a\"], [\"b\"]]);\n      });\n\n      it(\"omits the seed when no ancestor stored a value\", async () => {\n        const root = rootConfig(uuid6(3));\n\n        const c0 = await putCheckpoint(root, uuid6(3), {});\n        await checkpointer.putWrites(c0, [[\"messages\", [1]]], \"task0\");\n        const c1 = await putCheckpoint(c0, uuid6(3), {});\n\n        const hist = await checkpointer.getDeltaChannelHistory({\n          config: c1,\n          channels: [\"messages\"],\n        });\n\n        // Reaching the root without a stored value => \"start empty\".\n        expect(hist.messages.seed).toBeUndefined();\n        expect(hist.messages.writes.map((w) => w[2])).toEqual([[1]]);\n      });\n    });\n  });\n}\n"],"mappings":"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"}