Skip to content

Commit

Permalink
feat(typescript-effect): export a basic GraphQLClient layer from with…
Browse files Browse the repository at this point in the history
…in the generated SDK
  • Loading branch information
vecerek committed Mar 20, 2024
1 parent 185ccb8 commit 673fdfd
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 293 deletions.
92 changes: 62 additions & 30 deletions packages/plugins/typescript/effect/src/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,71 @@ import {

export interface EffectPluginConfig extends ClientSideBasePluginConfig {}

const additionalStaticContent = (documentMode: DocumentMode) => `
export type GraphQLSuccessResponse<A> = {
body: ExecutionResult & { data: A };
headers: Record<string, string>;
const clientCode = `export type GraphQLSuccessResponse<A = any> = Pick<
Http.response.ClientResponse,
'status' | 'headers'
> & {
readonly body: ExecutionResult & { readonly data: A };
};
export type GraphQLErrorResponse = {
body: Omit<ExecutionResult, 'data'>;
headers: Record<string, string>;
export type GraphQLErrorResponse = Pick<Http.response.ClientResponse, 'status' | 'headers'> & {
readonly body: Pick<ExecutionResult, 'errors' | 'extensions'>;
};
export class MissingDataGraphQLResponseError extends Data.TaggedError(
'MissingDataGraphQLResponseError',
)<GraphQLErrorResponse> {}
const headers = {
// https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#legacy-watershed
Accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8',
'Content-Type': 'application/json',
};
export class GraphQLClient extends Context.Tag('GraphQLClient')<
GraphQLClient,
Http.client.Client<
never,
Http.error.HttpClientError | MissingDataGraphQLResponseError,
GraphQLSuccessResponse
>
>() {
static fromDefaultClient(client: Http.client.Client.Default): Layer.Layer<GraphQLClient> {
return Layer.succeed(
GraphQLClient,
client.pipe(
Http.client.mapRequest(Http.request.setHeaders(headers)),
Http.client.filterStatusOk,
Http.client.mapEffectScoped(res =>
Effect.flatMap(res.json, _ => {
const body = _ as ExecutionResult;
return body.data
? Effect.succeed({ ...res, body: { ...body, data: body.data } })
: Effect.fail(new MissingDataGraphQLResponseError({ ...res, body }));
}),
),
),
);
}
static Live: Layer.Layer<GraphQLClient, never, Http.client.Client.Default> = Layer.unwrapEffect(
Effect.map(Http.client.Client, GraphQLClient.fromDefaultClient),
);
static fromEndpoint(
endpoint: string,
): Layer.Layer<GraphQLClient, never, Http.client.Client.Default> {
return Layer.unwrapEffect(
Effect.map(Http.client.Client, client =>
GraphQLClient.fromDefaultClient(
Http.client.mapRequest(client, Http.request.prependUrl(endpoint)),
),
),
);
}
}`;

const additionalStaticContent = (documentMode: DocumentMode) => `
export type GraphQLOperationOptions = {
preferredOpName?: string;
};
Expand All @@ -34,43 +84,23 @@ type GraphQLOperationArgs = {
fallbackOperationName: string;
};
// https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#legacy-watershed
const headers = {
Accept:
"application/graphql-response+json; charset=utf-8, application/json; charset=utf-8",
"Content-Type": "application/json",
};
const makeGraphQLClient = <Data>() =>
Effect.map(Http.client.Client, client =>
Http.client.mapRequest(client, Http.request.setHeaders(headers)).pipe(
Http.client.filterStatusOk,
Http.client.mapEffectScoped(res =>
Effect.flatMap(res.json, _ => {
const result = { body: _ as ExecutionResult<Data>, headers: res.headers };
return result.body.data
? Effect.succeed(result as GraphQLSuccessResponse<Data>)
: Effect.fail(new MissingDataGraphQLResponseError(result));
}),
),
),
);
${clientCode}
const makeGraphQLOperation =
<Vars, Data>({ document, fallbackOperationName }: GraphQLOperationArgs) =>
(variables: Vars, opts?: GraphQLOperationOptions) => {
const operationName = opts?.preferredOpName ?? fallbackOperationName;
const query = ${documentMode === DocumentMode.string ? 'document' : 'print(document)'};
return Effect.flatMap(makeGraphQLClient<Data>(), client =>
return Effect.flatMap(GraphQLClient, client =>
Http.request.post('').pipe(
Http.request.jsonBody({
query,
operationName,
variables,
}),
Effect.flatMap(client),
Effect.map(_ => _ as GraphQLSuccessResponse<Data>),
),
);
};
Expand Down Expand Up @@ -113,8 +143,10 @@ export class EffectVisitor extends ClientSideBaseVisitor<
[
createNamedImport(
[
['Context', 'value'],
['Data', 'value'],
['Effect', 'value'],
['Layer', 'value'],
],
'effect',
),
Expand Down
Loading

0 comments on commit 673fdfd

Please sign in to comment.