Skip to content
This repository has been archived by the owner on Jul 20, 2023. It is now read-only.

Commit

Permalink
Implement Selector and selector APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
timkendall committed Sep 13, 2021
1 parent fbb6313 commit d5fb12f
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 4 deletions.
46 changes: 46 additions & 0 deletions src/Query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ISelector } from "./Selector";

// export const operation = <S extends ISelector<any>>(
// type: "query" | "mutation" | "subscription",
// selector: S
// ) => <T extends Array<Selection>>(
// name: string,
// select: (t: S) => T
// ): Operation<SelectionSet<T>> =>
// new Operation(name, type, new SelectionSet(select(selector)));

// ```
// // or new Query({ ... })
// const operation = query({
// name: 'Example',
// variables: {
// id: t.string,
// },
// selection: (t, v) => [
// t.user({ id: v.id }, t => [
// t.id,
// t.name,
// t.friends(t => [
// t.id,
// t.firstName,
// ])
// ])
// ]
// extensions: []
// })
// ```

export interface QueryConfig<TQuery> {
name?: string;
variables?: Record<string, unknown /* @todo */>;
selection: ISelector<TQuery>;
// extensions?: Array<Extension>
}

export class Query<Q> {
// ast?: DocumentNode or OperationDefinitionNode

constructor(config: QueryConfig<Q>) {}

// toString()
}
61 changes: 61 additions & 0 deletions src/Selector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
Field,
SelectionSet,
Selection,
Argument,
Primitive,
} from "./Operation";

export type ISelector<T> = {
// @todo use Paramaters<T> to support variables
[F in keyof T]: T[F] extends Primitive
? (variables?: Record<string, any>) => Field<F extends string ? F : never>
: <S extends Array<Selection>>(
arg0?: ((selector: ISelector<T[F]>) => S) | Record<string, any> | never,
arg1?: ((selector: ISelector<T[F]>) => S) | never
) => Field<F extends string ? F : never, never, SelectionSet<S>>;
};

export class Selector<T> {
constructor(public readonly select: (t: ISelector<T>) => Array<Selection>) {}

toSelectionSet() {
return new SelectionSet(this.select(selector()));
}
}

export const selector = <T>(): ISelector<T> =>
new Proxy(Object.create(null), {
get(target, field) /*: FieldFn*/ {
return function fieldFn(...args: any[]) {
if (args.length === 0) {
return new Field<any, any, any>(field);
} else if (typeof args[0] === "function") {
return new Field<any, any, any>(
field,
undefined,
new SelectionSet(args[0](selector()))
);
} else if (typeof args[0] === "object") {
if (typeof args[1] === "function") {
return new Field<any, any, any>(
field,
Object.entries(args[0]).map(
([name, value]) => new Argument(name, value)
),
new SelectionSet(args[1](selector()))
);
} else {
return new Field<any, any, any>(
field,
Object.entries(args[0]).map(
([name, value]) => new Argument(name, value)
)
);
}
}

throw new Error(`Unable to derive field function from arguments.`);
};
},
});
86 changes: 86 additions & 0 deletions src/__tests__/Selector.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { print } from "graphql";

import { selector, Selector } from "../Selector";
import { SelectionSet } from "../Operation";
import { Result } from "../Result";

describe("Selector", () => {
describe("type-saftey", () => {
it("supports selecting against an interface", () => {
interface Query {
foo: string;
bar: number;
baz: {
id: string;
};
}

const { foo, bar, baz } = selector<Query>();

const selection = [foo(), bar(), baz((t) => [t.id()])];
type ExampleResult = Result<Query, SelectionSet<typeof selection>>;

const query = print(new SelectionSet(selection).ast);

expect(query).toMatchInlineSnapshot(`
"{
foo
bar
baz {
id
}
}"
`);
});
});

describe("class API", () => {
it("supports arbitrarily deep seletions", () => {
const selector = new Selector<any>((t) => [
t.user({ id: "foo" }, (t) => [t.id()]),
]);

const query = print(selector.toSelectionSet().ast);

expect(query).toMatchInlineSnapshot(`
"{
user(id: \\"foo\\") {
id
}
}"
`);
});
});

describe("function API", () => {
it("supports arbitrarily deep selections", () => {
const { authors, user } = selector<any>();

const selection = [
authors((t) => [t.name(), t.address((t) => [t.country()])]),

user({ id: "foo" }, (t) => [t.foo((t) => [t.bar((t) => [t.baz()])])]),
];

const query = print(new SelectionSet(selection).ast);

expect(query).toMatchInlineSnapshot(`
"{
authors {
name
address {
country
}
}
user(id: \\"foo\\") {
foo {
bar {
baz
}
}
}
}"
`);
});
});
});
6 changes: 2 additions & 4 deletions starwars.example.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Executor } from "./src";
import { Starwars } from "./starwars.api";
(async () => {
const starwars = new Starwars(new Executor(""));
const starwars = new Starwars(new Executor({ uri: "" }));

const character = await starwars.query.character({ id: "frf" }, (t) => [
t.__typename(),
Expand All @@ -11,9 +11,7 @@ import { Starwars } from "./starwars.api";
t.friends((t) => [t.id()]),
]),

// t.on('Droid', t => [
// t.primaryFunction(),
// ])
t.on("Droid", (t) => [t.primaryFunction()]),
]);

character.data.character.__typename;
Expand Down

0 comments on commit d5fb12f

Please sign in to comment.