Skip to content

Commit

Permalink
Allow async edge connections (#1829)
Browse files Browse the repository at this point in the history
  • Loading branch information
Swahvay authored Sep 5, 2024
1 parent 9bc78f9 commit 44e783c
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 33 deletions.
42 changes: 24 additions & 18 deletions ts/src/graphql/query/edge_connection.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EdgeQuery, PaginationInfo } from "../../core/query/query";
import { Data, Ent, ID, Viewer } from "../../core/base";
import { EdgeQuery, PaginationInfo } from "../../core/query/query";

// TODO getCursor...
export interface GraphQLEdge<T extends Data> {
Expand All @@ -8,20 +8,22 @@ export interface GraphQLEdge<T extends Data> {
cursor: string;
}

type MaybePromise<T> = T | Promise<T>;

interface edgeQueryCtr<
T extends Ent,
TEdge extends Data,
TViewer extends Viewer,
> {
(v: TViewer, src: T): EdgeQuery<T, Ent, TEdge>;
(v: TViewer, src: T): MaybePromise<EdgeQuery<T, Ent, TEdge>>;
}

interface edgeQueryCtr2<
T extends Ent,
TEdge extends Data,
TViewer extends Viewer,
> {
(v: TViewer): EdgeQuery<T, Ent, TEdge>;
(v: TViewer): MaybePromise<EdgeQuery<T, Ent, TEdge>>;
}

// TODO probably need to template Ent. maybe 2 ents?
Expand All @@ -30,7 +32,7 @@ export class GraphQLEdgeConnection<
TEdge extends Data,
TViewer extends Viewer = Viewer,
> {
query: EdgeQuery<TSource, Ent, TEdge>;
query: Promise<EdgeQuery<TSource, Ent, TEdge>>;
private results: GraphQLEdge<TEdge>[] = [];
private viewer: TViewer;
private source?: TSource;
Expand All @@ -55,12 +57,12 @@ export class GraphQLEdgeConnection<
) {
this.viewer = viewer;
if (typeof arg2 === "function") {
this.query = arg2(this.viewer);
this.query = Promise.resolve(arg2(this.viewer));
} else {
this.source = arg2;
}
if (typeof arg3 === "function") {
this.query = arg3(this.viewer, this.source!);
this.query = Promise.resolve(arg3(this.viewer, this.source!));
} else {
this.args = arg3;
}
Expand All @@ -74,23 +76,26 @@ export class GraphQLEdgeConnection<
if (this.args.before && !this.args.before) {
throw new Error("cannot process before without last");
}
const argFirst = this.args.first;
const argLast = this.args.last;
const argCursor = this.args.cursor;
if (this.args.first) {
this.query = this.query.first(this.args.first, this.args.after);
this.query = this.query.then((query) => query.first(argFirst, argLast));
}
if (this.args.last) {
this.query = this.query.last(this.args.last, this.args.cursor);
this.query = this.query.then((query) => query.last(argLast, argCursor));
}
// TODO custom args
// how to proceed
}
}

first(limit: number, cursor?: string) {
this.query = this.query.first(limit, cursor);
this.query = this.query.then((query) => query.first(limit, cursor));
}

last(limit: number, cursor?: string) {
this.query = this.query.last(limit, cursor);
this.query = this.query.then((query) => query.last(limit, cursor));
}

// any custom filters can be applied here...
Expand All @@ -99,11 +104,11 @@ export class GraphQLEdgeConnection<
query: EdgeQuery<TSource, Ent, TEdge>,
) => EdgeQuery<TSource, Ent, TEdge>,
) {
this.query = fn(this.query);
this.query = this.query.then((query) => fn(query));
}

async queryTotalCount() {
return this.query.queryRawCount();
return (await this.query).queryRawCount();
}

async queryEdges() {
Expand All @@ -116,7 +121,7 @@ export class GraphQLEdgeConnection<
// if nodes queried just return ents
// unlikely to query nodes and pageInfo so we just load this separately for now
async queryNodes() {
return this.query.queryEnts();
return (await this.query).queryEnts();
}

private defaultPageInfo() {
Expand All @@ -130,7 +135,7 @@ export class GraphQLEdgeConnection<

async queryPageInfo(): Promise<PaginationInfo> {
await this.queryData();
const paginationInfo = this.query.paginationInfo();
const paginationInfo = (await this.query).paginationInfo();
if (this.source !== undefined) {
return paginationInfo.get(this.source.id) || this.defaultPageInfo();
}
Expand All @@ -144,26 +149,27 @@ export class GraphQLEdgeConnection<
}

private async queryData() {
const query = await this.query;
const [edges, ents] = await Promise.all([
// TODO need a test that this will only fetch edges once
// and then fetch ents afterward
this.query.queryEdges(),
this.query.queryEnts(),
query.queryEdges(),
query.queryEnts(),
]);

let entsMap = new Map<ID, Ent>();
ents.forEach((ent) => entsMap.set(ent.id, ent));

let results: GraphQLEdge<TEdge>[] = [];
for (const edge of edges) {
const node = entsMap.get(this.query.dataToID(edge));
const node = entsMap.get(query.dataToID(edge));
if (!node) {
continue;
}
results.push({
edge,
node,
cursor: this.query.getCursor(edge),
cursor: query.getCursor(edge),
});
}
this.results = results;
Expand Down
46 changes: 31 additions & 15 deletions ts/src/graphql/query/shared_edge_connection.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { IDViewer } from "../../core/viewer";
import { Viewer, Data, Ent } from "../../core/base";
import { Data, Ent, Viewer } from "../../core/base";
import { cursorOptions, getCursor } from "../../core/ent";
import { GraphQLEdgeConnection } from "./edge_connection";
import { EdgeQuery } from "../../core/query/query";
import { IDViewer } from "../../core/viewer";
import {
FakeUser,
FakeContact,
FakeUser,
UserToContactsFkeyQuery,
UserToContactsFkeyQueryDeprecated,
UserToContactsFkeyQueryAsc,
UserToContactsFkeyQueryDeletedAt,
UserToContactsFkeyQueryDeletedAtAsc,
UserToContactsFkeyQueryDeprecated,
} from "../../testutils/fake_data/index";
import {
inputs,
createAllContacts,
inputs,
} from "../../testutils/fake_data/test_helpers";
import { EdgeQuery } from "../../core/query/query";
import { GraphQLEdgeConnection } from "./edge_connection";

class TestConnection<TEdge extends Data> {
private user: FakeUser;
Expand All @@ -33,7 +33,7 @@ class TestConnection<TEdge extends Data> {
conn: GraphQLEdgeConnection<Ent, TEdge>,
user: FakeUser,
contacts: FakeContact[],
) => void,
) => Promise<void>,
) {}

async beforeEach() {
Expand All @@ -45,11 +45,27 @@ class TestConnection<TEdge extends Data> {
(v, user: FakeUser) => this.getQuery(v, user),
);
if (this.filter) {
this.filter(this.conn, this.user, this.allContacts);
await this.filter(this.conn, this.user, this.allContacts);
}
this.filteredContacts = this.ents(this.allContacts);
}

async testAsyncConn() {
const asyncConn = new GraphQLEdgeConnection<Ent, TEdge>(
new IDViewer(this.user.id),
this.user,
(v, user: FakeUser) =>
new Promise((resolve) => {
setTimeout(() => resolve(this.getQuery(v, user)), 0);
}),
);
if (this.filter) {
await this.filter(asyncConn, this.user, this.allContacts);
}
const count = await asyncConn.queryTotalCount();
expect(count).toBe(inputs.length);
}

async testTotalCount() {
const count = await this.conn.queryTotalCount();
expect(count).toBe(inputs.length);
Expand All @@ -69,7 +85,7 @@ class TestConnection<TEdge extends Data> {
for (let i = 0; i < this.filteredContacts.length; i++) {
const edge = edges[i];
expect(edge.node.id).toBe(this.filteredContacts[i].id);
expect(this.conn.query.dataToID(edge.edge)).toBe(
expect((await this.conn.query).dataToID(edge.edge)).toBe(
this.filteredContacts[i].id,
);
}
Expand Down Expand Up @@ -152,7 +168,7 @@ export const commonTests = <TEdge extends Data>(
const filter = new TestConnection(
(v, user: FakeUser) => opts.getQuery(v, user),
(contacts) => contacts.slice(0, 2),
(conn: GraphQLEdgeConnection<Ent, TEdge>) => {
async (conn: GraphQLEdgeConnection<Ent, TEdge>) => {
conn.first(2);
},
);
Expand Down Expand Up @@ -193,12 +209,12 @@ export const commonTests = <TEdge extends Data>(
(v, user: FakeUser) => opts.getQuery(v, user),
// get the next 2
(contacts) => contacts.slice(idx + 1, idx + N),
(
async (
conn: GraphQLEdgeConnection<Ent, TEdge>,
user: FakeUser,
contacts: FakeContact[],
) => {
const cursor = getCursorFrom(conn.query, contacts, idx);
const cursor = getCursorFrom(await conn.query, contacts, idx);
conn.first(2, cursor);
},
);
Expand Down Expand Up @@ -236,13 +252,13 @@ export const commonTests = <TEdge extends Data>(
const filter = new TestConnection(
(v, user: FakeUser) => opts.getQuery(v, user),
(contacts) => contacts.slice(2, 4).reverse(),
(
async (
conn: GraphQLEdgeConnection<Ent, TEdge>,
user: FakeUser,
contacts: FakeContact[],
) => {
// get the 2 before it
const cursor = getCursorFrom(conn.query, contacts, 4);
const cursor = getCursorFrom(await conn.query, contacts, 4);

conn.last(2, cursor);
},
Expand Down

0 comments on commit 44e783c

Please sign in to comment.