Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ const bundle = config => ({
export default [
bundle({
plugins: [
nodeResolve(),
babel(),
nodeResolve(),
babel(),
terser(),
typescript({
target: "es5"
}),
}),
],
output: [
{
Expand Down
11 changes: 8 additions & 3 deletions src/builder/AbstractField.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { HigherKindType, FieldDescendantStore } from './hkt';
import type { InlineFragment } from './InlineFragment';
import { Field } from './Field';

// Importing InlineFragment as class here causes severe circular dependency issues
// Prefer importing as type and using this helper function
Expand Down Expand Up @@ -191,6 +190,12 @@ export abstract class AbstractField<
}
}

export default AbstractField;
// Declaring Field here to prevent circular dependency issues

// Importing assets here prevents circular dependency issues
export class Field<
Name extends string,
FieldReturnType,
IsArray extends boolean = false
> extends AbstractField<Name, FieldReturnType, IsArray> {
readonly tag = 'Field';
}
8 changes: 3 additions & 5 deletions src/builder/CombinedField.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Mutation from './Mutation';
import Query from './Query';
import { Mutation } from './Mutation';
import { Query } from './Query';
import { GraphQlRequestType } from '../client/prepare-document';
import AbstractField from './AbstractField';
import { AbstractField } from './AbstractField';

export class CombinedField<ReturnType> {
type?: GraphQlRequestType;
Expand Down Expand Up @@ -29,5 +29,3 @@ export class CombinedField<ReturnType> {

getFields = () => this.fields;
}

export default CombinedField;
11 changes: 0 additions & 11 deletions src/builder/Field.ts

This file was deleted.

4 changes: 1 addition & 3 deletions src/builder/InlineFragment.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AbstractField from './AbstractField';
import { AbstractField } from './AbstractField';

export class InlineFragment<
N extends string,
Expand All @@ -10,5 +10,3 @@ export class InlineFragment<
super(`... on ${name}` as N);
}
}

export default InlineFragment;
6 changes: 2 additions & 4 deletions src/builder/Mutation.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import AbstractField from "./AbstractField";
import { AbstractField } from "./AbstractField";
import { GraphQlRequestType } from "../client/prepare-document";
import { IRequestable } from "./interface/IRequestable";

class Mutation<
export class Mutation<
Name extends string,
FieldReturnType,
IsArray extends boolean = false
Expand All @@ -11,5 +11,3 @@ class Mutation<

readonly type = GraphQlRequestType.Mutation;
}

export default Mutation;
6 changes: 2 additions & 4 deletions src/builder/Query.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import AbstractField from "./AbstractField";
import { AbstractField } from "./AbstractField";
import { GraphQlRequestType } from "../client/prepare-document";
import { IRequestable } from "./interface/IRequestable";

class Query<
export class Query<
Name extends string,
FieldReturnType,
IsArray extends boolean = false
Expand All @@ -11,5 +11,3 @@ class Query<

readonly type = GraphQlRequestType.Query;
}

export default Query;
9 changes: 4 additions & 5 deletions src/builder/hkt.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { Field } from './Field';
import type Query from './Query'
import type Mutation from './Mutation'
import type InlineFragment from './InlineFragment';
import type AbstractField from './AbstractField';
import type { Query } from './Query'
import type { Mutation } from './Mutation'
import type { InlineFragment } from './InlineFragment';
import type { AbstractField, Field } from './AbstractField';

export interface FieldDescendantStore<
N extends string,
Expand Down
7 changes: 7 additions & 0 deletions src/builder/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export * from './AbstractField'
export * from './CombinedField'
export * from './InlineFragment'
export * from './Mutation'
export * from './Query'
export * from './hkt'
export * from './interface'
2 changes: 0 additions & 2 deletions src/builder/interface/IRequestable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,3 @@ import { GraphQlRequestType } from "../../client/prepare-document";
export interface IRequestable {
readonly type: GraphQlRequestType
};

export default IRequestable;
1 change: 1 addition & 0 deletions src/builder/interface/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './IRequestable'
129 changes: 129 additions & 0 deletions src/client/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { CombinedField } from '../builder/CombinedField';
import { prepareRequest } from './prepare-document';
import { parseResponse } from './parse-response';
import { executePost } from './post';
import { Mutation } from '../builder/Mutation';
import { Query } from '../builder/Query';
import { AbstractField } from '../builder/AbstractField';
import { deepApply } from '../util/deep-apply';
import { DataType } from '../util/data-type';

export interface GraphQlResponse {
errors: string | Error | Error[],
data: unknown
}

export type Middleware = (response: GraphQlResponse) => unknown;

export type RequestOptions = {
endpoint: string,
headers?: Record<string, string>
} & Omit<RequestInit, 'method' | 'body' | 'headers'>;

export const defaultOptions: RequestOptions = {
endpoint: process.env.GRAPHQL_ENDPOINT || '/graphql'
};

export class Client {
protected options: RequestOptions = defaultOptions;

setEndpoint = (endpoint: string): void => {
this.options.endpoint = endpoint;
};

setHeaders = (headers: Record<string, string>): void => {
this.options.headers = headers;
};

getOptions = (): RequestOptions => this.options;

async post<N extends string, RT, A extends boolean>(
rawField: Query<N, RT, A> | Mutation<N, RT, A>,
overrideOptions?: Partial<RequestOptions>
): Promise<DataType<typeof rawField>>;

async post<RT>(
rawField: CombinedField<RT>,
overrideOptions?: Partial<RequestOptions>
): Promise<DataType<typeof rawField>>;

async post(
rawField: any,
overrideOptions?: Partial<RequestOptions>
) {
const fieldArray = rawField instanceof CombinedField ? rawField.getFields() : [rawField];

if (!fieldArray.length) {
throw new Error('Attempting to post empty field!');
}

const response = await executePost(
prepareRequest(fieldArray, rawField.type!),
// TODO deep merge
{
...this.options,
...(overrideOptions || {})
}
);

const parsedResponse = parseResponse(await response.json());

if (rawField instanceof CombinedField) {
for (const field of rawField.getFields()) {
await this.process(field, parsedResponse[field.name], parsedResponse)
}
} else {
await this.process(rawField, parsedResponse[rawField.name], parsedResponse);
}

deepApply(Object.freeze, parsedResponse);

return parsedResponse;
};

/**
* Handles calculating and transforming fields on result
*/
protected async process(field: AbstractField<any, any, any>, result: any, parentResult: any) {
// Prevent calculating for non-object fields from the result
if (!field.children.length) {
return;
}

// If array - process each separately
if (Array.isArray(result)) {
for (const item of result) {
await this.process(field, item, parentResult);
}
} else {
// If has children - process children first
for (const child of field.children) {
if (child.tag === 'InlineFragment') {
for (const fragmentChild of child.children) {
if (!Object.hasOwnProperty.call(result, fragmentChild.name)) {
continue;
}

await this.process(fragmentChild, result[fragmentChild.name], result);
}
} else {
await this.process(child, result[child.name], result);
}
}

// POSTVISIT - calculate the actual fields
for (const [fieldName, calculator] of Object.entries(field.calculators)) {
result[fieldName] = await calculator(result);
}

// Prevent adding new properties from now on
deepApply(Object.seal, result);

if (field.transformer) {
parentResult[field.name] = await field.transformer(result);
}

// TODO in dev mode we can compare own props to prevent extending in improper ways
}
}
}
Loading