{"version":3,"file":"list.cjs","names":["generateTuplePairs","putTuples","toArray","toMap","TASKS"],"sources":["../../src/spec/list.ts"],"sourcesContent":["import {\n  type Checkpoint,\n  type CheckpointTuple,\n  type BaseCheckpointSaver,\n  type PendingWrite,\n  uuid6,\n  TASKS,\n} from \"@langchain/langgraph-checkpoint\";\nimport { RunnableConfig } from \"@langchain/core/runnables\";\nimport { CheckpointerTestInitializer } from \"../types.js\";\nimport {\n  generateTuplePairs,\n  it_skipForSomeModules,\n  putTuples,\n  toArray,\n  toMap,\n} from \"../test_utils.js\";\n\ninterface ListTestCase {\n  description: string;\n  thread_id: string | undefined;\n  checkpoint_ns: string | undefined;\n  limit: number | undefined;\n  before: RunnableConfig | undefined;\n  filter: Record<string, unknown> | undefined;\n  expectedCheckpointIds: string[];\n}\n\n/**\n * Exercises the `list` method of the checkpointer.\n *\n * IMPORTANT NOTE: This test relies on the `getTuple` method of the checkpointer functioning properly. If you have\n * failures in `getTuple`, you should fix them before addressing the failures in this test.\n *\n * @param initializer the initializer for the checkpointer\n */\nexport function listTests<T extends BaseCheckpointSaver>(\n  initializer: CheckpointerTestInitializer<T>\n) {\n  const invalidThreadId = uuid6(3);\n\n  const namespaces = [\"\", \"child\"];\n\n  const generatedTuples: {\n    tuple: CheckpointTuple;\n    writes: { writes: PendingWrite[]; taskId: string }[];\n    newVersions: Record<string, number | string>;\n  }[] = Array.from(generateTuplePairs(2, namespaces));\n\n  const argumentRanges = setupArgumentRanges(\n    generatedTuples.map(({ tuple }) => tuple),\n    namespaces\n  );\n\n  const argumentCombinations: ListTestCase[] = Array.from(\n    buildArgumentCombinations(\n      argumentRanges,\n      generatedTuples.map(({ tuple }) => tuple)\n    )\n  );\n\n  describe(`${initializer.checkpointerName}#list`, () => {\n    let checkpointer: T;\n    const storedTuples: Map<string, CheckpointTuple> = new Map();\n\n    beforeAll(async () => {\n      checkpointer = await initializer.createCheckpointer();\n\n      // put all the tuples\n      for await (const tuple of putTuples(checkpointer, generatedTuples)) {\n        storedTuples.set(tuple.checkpoint.id, tuple);\n      }\n    });\n\n    afterAll(async () => {\n      await initializer.destroyCheckpointer?.(checkpointer);\n    });\n\n    // can't reference argumentCombinations directly here because it isn't built at the time this is evaluated.\n    // We do know how many entries there will be though, so we just pass the index for each entry, instead.\n    it.each(argumentCombinations)(\n      \"$description\",\n      async ({\n        thread_id,\n        checkpoint_ns,\n        limit,\n        before,\n        filter,\n        expectedCheckpointIds,\n      }: ListTestCase) => {\n        const actualTuplesArray = await toArray(\n          checkpointer.list(\n            { configurable: { thread_id, checkpoint_ns } },\n            { limit, before, filter }\n          )\n        );\n\n        const limitEnforced =\n          limit !== undefined && limit < expectedCheckpointIds.length;\n\n        const expectedCount = limitEnforced\n          ? limit\n          : expectedCheckpointIds.length;\n\n        expect(actualTuplesArray.length).toEqual(expectedCount);\n\n        const actualTuplesMap = toMap(actualTuplesArray);\n        const expectedTuples = expectedCheckpointIds.map(\n          (tupleId) => storedTuples.get(tupleId)!\n        );\n\n        const expectedTuplesMap = toMap(expectedTuples);\n\n        if (limitEnforced) {\n          for (const tuple of actualTuplesArray) {\n            expect(expectedTuplesMap.has(tuple.checkpoint.id)).toBeTruthy();\n            expect(tuple).toEqual(expectedTuplesMap.get(tuple.checkpoint.id));\n          }\n        } else {\n          expect(actualTuplesMap.size).toEqual(expectedTuplesMap.size);\n          for (const [key, value] of actualTuplesMap.entries()) {\n            expect(value).toEqual(expectedTuplesMap.get(key));\n          }\n        }\n      }\n    );\n\n    it_skipForSomeModules(initializer.checkpointerName, {\n      \"@langchain/langgraph-checkpoint-mongodb\":\n        \"MongoDBSaver never stored pending sends\",\n    })(\"should migrate pending sends\", async () => {\n      let config: RunnableConfig = {\n        configurable: { thread_id: \"thread-1\", checkpoint_ns: \"\" },\n      };\n\n      const checkpoint0: Checkpoint = {\n        v: 1,\n        id: uuid6(0),\n        ts: \"2024-04-19T17:19:07.952Z\",\n        channel_values: {},\n        channel_versions: {},\n        versions_seen: {},\n      };\n\n      config = await checkpointer.put(\n        config,\n        checkpoint0,\n        { source: \"loop\", parents: {}, step: 0 },\n        {}\n      );\n\n      await checkpointer.putWrites(\n        config,\n        [\n          [TASKS, \"send-1\"],\n          [TASKS, \"send-2\"],\n        ],\n        \"task-1\"\n      );\n      await checkpointer.putWrites(config, [[TASKS, \"send-3\"]], \"task-2\");\n\n      // check that fetching checkpount 0 doesn't attach pending sends\n      // (they should be attached to the next checkpoint)\n      const tuple0 = await checkpointer.getTuple(config);\n      expect(tuple0?.checkpoint.channel_values).toEqual({});\n      expect(tuple0?.checkpoint.channel_versions).toEqual({});\n\n      // create second checkpoint\n      const checkpoint1: Checkpoint = {\n        v: 1,\n        id: uuid6(1),\n        ts: \"2024-04-20T17:19:07.952Z\",\n        channel_values: {},\n        channel_versions: checkpoint0.channel_versions,\n        versions_seen: checkpoint0.versions_seen,\n      };\n      config = await checkpointer.put(\n        config,\n        checkpoint1,\n        { source: \"loop\", parents: {}, step: 1 },\n        {}\n      );\n\n      // check that #list properly migrates old version of checkpoints (v < 4)\n      const checkpointTupleGenerator = checkpointer.list({\n        configurable: { thread_id: \"thread-1\" },\n      });\n      const checkpointTuples: CheckpointTuple[] = [];\n      for await (const checkpoint of checkpointTupleGenerator) {\n        checkpointTuples.push(checkpoint);\n      }\n\n      expect.soft(checkpointTuples.length).toBe(2);\n      expect.soft(checkpointTuples[0].checkpoint.channel_values).toEqual({\n        [TASKS]: [\"send-1\", \"send-2\", \"send-3\"],\n      });\n      expect\n        .soft(checkpointTuples[0].checkpoint.channel_versions[TASKS])\n        .toBeDefined();\n    });\n  });\n\n  function setupArgumentRanges(\n    generatedTuples: CheckpointTuple[],\n    namespaces: string[]\n  ): {\n    thread_id: (string | undefined)[];\n    checkpoint_ns: (string | undefined)[];\n    limit: (number | undefined)[];\n    before: (RunnableConfig | undefined)[];\n    filter: (Record<string, unknown> | undefined)[];\n  } {\n    const parentTupleInDefaultNamespace = generatedTuples[0];\n    const childTupleInDefaultNamespace = generatedTuples[1];\n    const parentTupleInChildNamespace = generatedTuples[2];\n    const childTupleInChildNamespace = generatedTuples[3];\n\n    return {\n      thread_id: [\n        undefined,\n        parentTupleInDefaultNamespace.config.configurable?.thread_id,\n        childTupleInDefaultNamespace.config.configurable?.thread_id,\n        parentTupleInChildNamespace.config.configurable?.thread_id,\n        childTupleInChildNamespace.config.configurable?.thread_id,\n        invalidThreadId,\n      ],\n      checkpoint_ns: [undefined, ...namespaces],\n      limit: [undefined, 1, 2],\n      before: [\n        undefined,\n        parentTupleInDefaultNamespace.config,\n        childTupleInDefaultNamespace.config,\n      ],\n      filter:\n        // TODO: MongoDBSaver support for filter is broken and can't be fixed without a breaking change\n        // see: https://github.com/langchain-ai/langgraphjs/issues/581\n        initializer.checkpointerName ===\n        \"@langchain/langgraph-checkpoint-mongodb\"\n          ? [undefined]\n          : [undefined, {}, { source: \"input\" }, { source: \"loop\" }],\n    };\n  }\n\n  function* buildArgumentCombinations(\n    argumentRanges: ReturnType<typeof setupArgumentRanges>,\n    allTuples: CheckpointTuple[]\n  ): Generator<ListTestCase> {\n    for (const thread_id of argumentRanges.thread_id) {\n      for (const checkpoint_ns of argumentRanges.checkpoint_ns) {\n        for (const limit of argumentRanges.limit) {\n          for (const before of argumentRanges.before) {\n            for (const filter of argumentRanges.filter) {\n              const expectedCheckpointIds = allTuples\n                .filter(\n                  (tuple) =>\n                    (thread_id === undefined ||\n                      tuple.config.configurable?.thread_id === thread_id) &&\n                    (checkpoint_ns === undefined ||\n                      tuple.config.configurable?.checkpoint_ns ===\n                        checkpoint_ns) &&\n                    (before === undefined ||\n                      tuple.checkpoint.id <\n                        before.configurable?.checkpoint_id) &&\n                    (filter === undefined ||\n                      Object.entries(filter).every(\n                        ([key, value]) =>\n                          (\n                            tuple.metadata as\n                              | Record<string, unknown>\n                              | undefined\n                          )?.[key] === value\n                      ))\n                )\n                .map((tuple) => tuple.checkpoint.id);\n\n              yield {\n                description: describeArguments(\n                  argumentRanges,\n                  allTuples.length,\n                  {\n                    thread_id,\n                    checkpoint_ns,\n                    limit,\n                    before,\n                    filter,\n                    expectedCheckpointIds,\n                  }\n                ),\n                thread_id,\n                checkpoint_ns,\n                limit,\n                before,\n                filter,\n                expectedCheckpointIds,\n              };\n            }\n          }\n        }\n      }\n    }\n  }\n\n  function describeArguments(\n    argumentRanges: ReturnType<typeof setupArgumentRanges>,\n    totalTupleCount: number,\n    {\n      thread_id,\n      checkpoint_ns,\n      limit,\n      before,\n      filter,\n      expectedCheckpointIds,\n    }: Omit<ListTestCase, \"description\">\n  ) {\n    const parentTupleBeforeConfig = argumentRanges.before[1];\n    const childTupleBeforeConfig = argumentRanges.before[2];\n\n    let descriptionTupleCount: string;\n\n    if (limit !== undefined && limit < expectedCheckpointIds.length) {\n      descriptionTupleCount = `${limit} ${limit === 1 ? \"tuple\" : \"tuples\"}`;\n    } else if (expectedCheckpointIds.length === totalTupleCount) {\n      descriptionTupleCount = \"all tuples\";\n    } else if (expectedCheckpointIds.length === 0) {\n      descriptionTupleCount = \"no tuples\";\n    } else {\n      descriptionTupleCount = `${expectedCheckpointIds.length} tuples`;\n    }\n\n    const descriptionWhenParts: string[] = [];\n\n    if (\n      thread_id === undefined &&\n      checkpoint_ns === undefined &&\n      limit === undefined &&\n      before === undefined &&\n      filter === undefined\n    ) {\n      descriptionWhenParts.push(\"no config or options are specified\");\n    } else {\n      if (thread_id === undefined) {\n        descriptionWhenParts.push(\"thread_id is not specified\");\n      } else if (thread_id === invalidThreadId) {\n        descriptionWhenParts.push(\n          \"thread_id does not match pushed checkpoint(s)\"\n        );\n      } else {\n        descriptionWhenParts.push(`thread_id matches pushed checkpoint(s)`);\n      }\n\n      if (checkpoint_ns === undefined) {\n        descriptionWhenParts.push(\"checkpoint_ns is not specified\");\n      } else if (checkpoint_ns !== undefined && checkpoint_ns === \"\") {\n        descriptionWhenParts.push(\"checkpoint_ns is the default namespace\");\n      } else if (checkpoint_ns !== undefined && checkpoint_ns !== \"\") {\n        descriptionWhenParts.push(`checkpoint_ns matches '${checkpoint_ns}'`);\n      }\n\n      if (limit === undefined) {\n        descriptionWhenParts.push(\"limit is undefined\");\n      } else if (limit !== undefined) {\n        descriptionWhenParts.push(`limit is ${limit}`);\n      }\n\n      if (before === undefined) {\n        descriptionWhenParts.push(\"before is not specified\");\n      } else if (before !== undefined && before === parentTupleBeforeConfig) {\n        descriptionWhenParts.push(\"before parent checkpoint\");\n      } else if (before !== undefined && before === childTupleBeforeConfig) {\n        descriptionWhenParts.push(\"before child checkpoint\");\n      }\n\n      if (filter === undefined) {\n        descriptionWhenParts.push(\"filter is undefined\");\n      } else if (Object.keys(filter).length === 0) {\n        descriptionWhenParts.push(\"filter is an empty object\");\n      } else {\n        for (const [key, value] of Object.entries(filter)) {\n          descriptionWhenParts.push(\n            `metadata.${key} matches ${JSON.stringify(value)}`\n          );\n        }\n      }\n    }\n\n    const descriptionWhen =\n      descriptionWhenParts.length > 1\n        ? `${descriptionWhenParts.slice(0, -1).join(\", \")}, and ${\n            descriptionWhenParts[descriptionWhenParts.length - 1]\n          }`\n        : descriptionWhenParts[0];\n\n    return `should return ${descriptionTupleCount} when ${descriptionWhen}`;\n  }\n}\n"],"mappings":"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"}