Best API to build a "IGraph SearchAround Result DirectEdge V1" programatically

I have this kind of Vertex to display A → B → C/D.
I have to do that programatically in TypeScript.

I wonder if something like:
Bs = Object.search().A().searchAroundB();
Cs = Bs.searchAroundC()
Ds = Bs.searchAroundD()

would be a better API to use, rather than nesting calls to .allAsync() into a callback hell of async functions.

(which is a mess at the end, when you need to aggregate all yours asynchronous A, B, C, D
into a IGraphSearchAroundResultDirectEdgeV1)

Is there an example of the most efficient way to traverse such a graph and return [a Promise resolving to] a IGraphSearchAroundResultDirectEdgeV1 ?

1 Like

Hi @lolive. I understand it can get a little messy to generate the correct graph structure.

The problem with just using (e.g.) Cs = Bs.searchAroundC() is that you’ll now have a set of B objects, and a set of C objects, but you won’t know which object(s) in B is linked to which object(s) in C, so we wont be able to correctly render the edges in the graph. We need to know precisely which links you want to display as edges between the objects in the graph.

We could potentially offer a simpler form of Search Around function that allows you to return a set of objects / multiple sets, and just discover all links (of a given type) between all objects, but this would be less flexible.

The best example, to my knowledge, is in the Graphically explore relationships example. You can install this to your Foundry instance for more details. I’ve copied relevant function below, and you can see how it uses Promise.all to merge together many asynchronous calls, then process them afterwards. If you have many hops to search through, you can of course split your function into individual asynchronous helpers, and merge the results across them at the end.

I hope this helps!

@Function()
    public async connectingAirports(startAirport: ExampleAirport, endAirport: ExampleAirport): Promise<IGraphSearchAroundResultV1> {
        // Fetch start and end routes
        const [startRoutes, endRoutes] = await Promise.all([startAirport.departingRoutes.allAsync(), endAirport.arrivingRoutes.allAsync()]);

        // Fetch connecting airports for start and end routes
        const asyncStartConnectingAirports =  Promise.all(startRoutes.map(route => route.destinationAirport.getAsync()));
        const asyncEndConnectionAirports =  Promise.all(endRoutes.map(route => route.departureAirport.getAsync()));
        const [startConnectingAirports, endConnectingAirports] = await Promise.all([asyncStartConnectingAirports, asyncEndConnectionAirports]);

        // Create a map of end routes by connecting airport ID
        const endRoutesByConnectingAirportId = new Map(endConnectingAirports
            .map((airport, idx): [string | undefined, ExampleRoute] =>  [airport?.airportId, endRoutes[idx]])
            .filter(idRoutePair => idRoutePair[0] !== undefined)
        );

        // Initialize intermediate edges array
        const intermediateEdges: IGraphSearchAroundResultIntermediateEdgeV1[] = [];

        // Iterate through start connecting airports
        startConnectingAirports.forEach((startConnectingAirport, idx) => {
            const startRoute = startRoutes[idx];
            if (startConnectingAirport !== undefined && endRoutesByConnectingAirportId.has(startConnectingAirport.airportId)) {
                // Add intermediate edge for start route
                intermediateEdges.push({
                    sourceObjectRid: startAirport.rid!,
                    intermediateObjectRid: startRoute.rid!,
                    targetObjectRid: startConnectingAirport.rid!,
                    label: `${startAirport.airport}-${startConnectingAirport.airport}`
                });

                // Add intermediate edge for end route
                const endRoute = endRoutesByConnectingAirportId.get(startConnectingAirport.airportId);
                if (endRoute !== undefined) {
                    intermediateEdges.push({
                        sourceObjectRid: startConnectingAirport.rid!,
                        intermediateObjectRid: endRoute.rid!,
                        targetObjectRid: endAirport.rid!,
                        label: `${startConnectingAirport.airport}-${endAirport.airport}`
                    });
                }
            }
        });

        // Return intermediate edges
        return { intermediateEdges };
    }
1 Like

We have a bit more aggressive version of this code, where each atomic promise resolution triggers immediately the DB request to the next step of the traversal.
And we have a massive Promise.all at the end of our code, to wait for aaaall those promises to resolve.
Not sure it is better or worse, than waiting for each step to fully resolve before going to the next.

Anyway, both options sound to me like a “n+1 select” strategy (as stated in https://stackoverflow.com/a/97253), and this strategy is known to be very inefficient .

So any graph traversal API that may reduce the number of roundtrip would be a better option, IF it returns the proper information of nodes and links traversed during this API call.

#foodForThought

1 Like