Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Grafserv overhaul: now compatible with envelop #2046

Merged
merged 30 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
316b647
Only build hooks once
benjie May 2, 2024
f808742
Configuration of parseAndValidate cache size
benjie May 2, 2024
b35079f
Don't create LRU if cache size is 0
benjie May 2, 2024
ece1394
Overhaul grafserv startup and fix server shutdown hang issue
benjie May 2, 2024
d7b2e3a
Abstract execution stuff ready for envelop integration
benjie May 2, 2024
b77cd99
Integrate new getExecutionStuff in GraphQL handler
benjie May 3, 2024
a7f9d50
Heavy caching of execution stuff
benjie May 3, 2024
5871735
Tweak cache line to avoid an additional tick
benjie May 3, 2024
e2bc829
Clearer logic
benjie May 3, 2024
a184e27
Kick off the process earlier
benjie May 3, 2024
bf32525
Enable hooking execution stuff via init hook
benjie May 3, 2024
ffc14c1
Rename ExecutionStuff to ExecutionConfig (thanks ChatGPT)
benjie May 3, 2024
cea2315
Add version file to grafserv
benjie May 3, 2024
b20cc3b
Envelop plugin
benjie May 3, 2024
d4712a1
Lint fixes
benjie May 3, 2024
ae2440d
Preset disables builtin error masking
benjie May 3, 2024
8cb3c91
Move envelop import to the top
benjie May 3, 2024
90b81af
Add missing forwards
benjie May 3, 2024
1ce0898
docs(changeset): `resolvedPreset` and `outputDataAsString` can now be…
benjie May 3, 2024
17bccba
Bump peerDep
benjie May 3, 2024
52c5550
Upgrade all the envelop stuff
benjie May 3, 2024
9662035
docs(changeset): Envelop peer dependency upgraded to V5
benjie May 3, 2024
a5a0816
docs(changeset): Massive refactor of Grafserv internals.
benjie May 3, 2024
08ae810
Once the handler error has triggered, auto-fail all following requests
benjie May 3, 2024
24567c7
Handlers for perma-failed graphql/graphiql
benjie May 3, 2024
48ebd68
Prevent concurrent setPreset
benjie May 3, 2024
de8dd95
Since setPreset can throw now... we better handle that.
benjie May 3, 2024
c5b0b36
Mark functions as private
benjie May 3, 2024
10dce88
Don't set rootStep value... Not needed and confusing
benjie May 6, 2024
e73f072
Add more details to error
benjie May 6, 2024
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
8 changes: 8 additions & 0 deletions .changeset/famous-falcons-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"grafast": patch
---

`resolvedPreset` and `outputDataAsString` can now be specified directly via
ExecutionArgs - no need to pass additional params to `execute()` and
`subscribe()` to enable these. Previous signatures are now deprecated (but still
supported, for a few betas at least).
5 changes: 5 additions & 0 deletions .changeset/odd-wasps-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"grafserv": patch
---

Massive refactor of Grafserv internals.
6 changes: 6 additions & 0 deletions .changeset/perfect-timers-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"postgraphile": patch
"grafast": patch
---

Envelop peer dependency upgraded to V5
2 changes: 1 addition & 1 deletion grafast/grafast/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"tslib": "^2.6.2"
},
"peerDependencies": {
"@envelop/core": "^3.0.4",
"@envelop/core": "^5.0.0",
"graphile-config": "workspace:^",
"graphql": "^16.1.0-experimental-stream-defer.6",
"tamedevil": "workspace:^"
Expand Down
32 changes: 28 additions & 4 deletions grafast/grafast/src/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import type { PromiseOrValue } from "graphql/jsutils/PromiseOrValue";
import { NULL_PRESET } from "./config.js";
import { isDev } from "./dev.js";
import { inspect } from "./inspect.js";
import type { ExecutionEventEmitter, ExecutionEventMap } from "./interfaces.js";
import type {
ExecutionEventEmitter,
ExecutionEventMap,
GrafastExecutionArgs,
} from "./interfaces.js";
import { $$eventEmitter, $$extensions } from "./interfaces.js";
import { grafastPrepare } from "./prepare.js";
import { isPromiseLike } from "./utils.js";
Expand Down Expand Up @@ -89,16 +93,36 @@ export function withGrafastArgs(
}
}

/**
* @deprecated Second and third parameters should be passed as part of args,
* specifically `resolvedPreset` and `outputDataAsString`.
*/
export function execute(
args: GrafastExecutionArgs,
resolvedPreset: GraphileConfig.ResolvedPreset | undefined,
outputDataAsString?: boolean,
): PromiseOrValue<
ExecutionResult | AsyncGenerator<AsyncExecutionResult, void, undefined>
>;
/**
* Use this instead of GraphQL.js' execute method and we'll automatically
* run grafastPrepare for you and handle the result.
*/
export function execute(
args: ExecutionArgs,
resolvedPreset: GraphileConfig.ResolvedPreset = NULL_PRESET,
outputDataAsString = false,
): PromiseOrValue<
ExecutionResult | AsyncGenerator<AsyncExecutionResult, void, undefined>
>;
export function execute(
args: GrafastExecutionArgs,
resolvedPreset?: GraphileConfig.ResolvedPreset,
outputDataAsString?: boolean,
): PromiseOrValue<
ExecutionResult | AsyncGenerator<AsyncExecutionResult, void, undefined>
> {
return withGrafastArgs(args, resolvedPreset, outputDataAsString);
return withGrafastArgs(
args,
args.resolvedPreset ?? resolvedPreset ?? NULL_PRESET,
args.outputDataAsString ?? outputDataAsString ?? false,
);
}
2 changes: 2 additions & 0 deletions grafast/grafast/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import type {
$$queryCache,
CacheByOperationEntry,
DataFromStep,
GrafastExecutionArgs,
GrafastTimeouts,
ScalarInputPlanResolver,
} from "./interfaces.js";
Expand Down Expand Up @@ -323,6 +324,7 @@ export {
getEnumValueConfig,
grafast,
GrafastArgumentConfig,
GrafastExecutionArgs,
GrafastFieldConfig,
GrafastFieldConfigArgumentMap,
grafast as grafastGraphql,
Expand Down
6 changes: 6 additions & 0 deletions grafast/grafast/src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type EventEmitter from "eventemitter3";
import type {
ASTNode,
ExecutionArgs,
FragmentDefinitionNode,
GraphQLArgs,
GraphQLArgument,
Expand Down Expand Up @@ -942,3 +943,8 @@ export const $$deepDepSkip = Symbol("deepDepSkip_experimental");

export type DataFromStep<TStep extends ExecutableStep> =
TStep extends ExecutableStep<infer TData> ? TData : never;

export interface GrafastExecutionArgs extends ExecutionArgs {
resolvedPreset?: GraphileConfig.ResolvedPreset;
outputDataAsString?: boolean;
}
31 changes: 27 additions & 4 deletions grafast/grafast/src/subscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,41 @@ import type { PromiseOrValue } from "graphql/jsutils/PromiseOrValue";

import { NULL_PRESET } from "./config.js";
import { withGrafastArgs } from "./execute.js";
import type { GrafastExecutionArgs } from "./index.js";

/**
* @deprecated Second and third parameters should be passed as part of args,
* specifically `resolvedPreset` and `outputDataAsString`.
*/
export function subscribe(
args: ExecutionArgs,
resolvedPreset: GraphileConfig.ResolvedPreset | undefined,
outputDataAsString?: boolean,
): PromiseOrValue<
| AsyncGenerator<ExecutionResult | AsyncExecutionResult, void, void>
| ExecutionResult
>;
/**
* Use this instead of GraphQL.js' subscribe method and we'll automatically
* run grafastPrepare for you and handle the result.
*/
export function subscribe(
args: ExecutionArgs,
resolvedPreset: GraphileConfig.ResolvedPreset = NULL_PRESET,
outputDataAsString = false,
args: GrafastExecutionArgs,
): PromiseOrValue<
| AsyncGenerator<ExecutionResult | AsyncExecutionResult, void, void>
| ExecutionResult
>;
export function subscribe(
args: GrafastExecutionArgs,
resolvedPreset?: GraphileConfig.ResolvedPreset,
outputDataAsString?: boolean,
): PromiseOrValue<
| AsyncGenerator<ExecutionResult | AsyncExecutionResult, void, void>
| ExecutionResult
> {
return withGrafastArgs(args, resolvedPreset, outputDataAsString);
return withGrafastArgs(
args,
args.resolvedPreset ?? resolvedPreset ?? NULL_PRESET,
args.outputDataAsString ?? outputDataAsString ?? false,
);
}
77 changes: 77 additions & 0 deletions grafast/grafserv/__tests__/envelop.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { envelop, useLogger, useMaskedErrors } from "@envelop/core";
import { fetch } from "@whatwg-node/fetch";

import { GrafservEnvelopPreset } from "../src/envelop/index.js";
import { makeExampleServer } from "./exampleServer.js";

let server: Awaited<ReturnType<typeof makeExampleServer>> | null = null;

afterEach(() => {
server?.release();
});

const sharedConfig: GraphileConfig.Preset = {
grafserv: {
graphqlOverGET: true,
graphqlPath: "/graphql",
dangerouslyAllowAllCORSRequests: true,
},
grafast: {
context: () => {
return {
fromGrafastContext: true,
};
},
},
};

test("envelop plugin is used", async () => {
const logFn = jest.fn((_eventName, _args) => {});
const getEnveloped = envelop({
plugins: [useLogger({ logFn }), useMaskedErrors()],
});
server = await makeExampleServer({
extends: [sharedConfig, GrafservEnvelopPreset],
grafserv: {
getEnveloped,
},
});
const res = await fetch(server.url, {
method: "POST",
headers: {
"content-type": "application/json",
accept: "application/graphql-response+json",
},
body: JSON.stringify({ query: "{ __typename }" }),
});
const responseBody = await res.json();
expect(responseBody.data).toEqual({
__typename: "Query",
});
expect(logFn).toHaveBeenCalledTimes(2);
expect(logFn.mock.calls[0][0]).toEqual("execute-start");
expect(logFn.mock.calls[1][0]).toEqual("execute-end");
});

test("envelop masked errors works", async () => {
const getEnveloped = envelop({
plugins: [useMaskedErrors()],
});
server = await makeExampleServer({
extends: [sharedConfig, GrafservEnvelopPreset],
grafserv: {
getEnveloped,
},
});
const res = await fetch(server.url, {
method: "POST",
headers: {
"content-type": "application/json",
accept: "application/graphql-response+json",
},
body: JSON.stringify({ query: "{ throwAnError }" }),
});
const responseBody = await res.json();
expect(responseBody.errors).toBeTruthy();
expect(responseBody.errors[0].message).toEqual("Unexpected error.");
});
2 changes: 1 addition & 1 deletion grafast/grafserv/__tests__/errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { makeExampleServer } from "./exampleServer.js";

let server: Awaited<ReturnType<typeof makeExampleServer>> | null = null;

afterAll(() => {
afterEach(() => {
server?.release();
});

Expand Down
19 changes: 15 additions & 4 deletions grafast/grafserv/__tests__/exampleServer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { createServer } from "node:http";
import type { AddressInfo } from "node:net";

import { constant, makeGrafastSchema } from "grafast";
import { constant, error, makeGrafastSchema } from "grafast";
import { resolvePresets } from "graphile-config";

import { grafserv } from "../src/servers/node/index.js";

Expand All @@ -14,23 +15,29 @@ export async function makeExampleServer(
},
},
) {
const resolvedPreset = resolvePresets([preset]);
const schema = makeGrafastSchema({
typeDefs: /* GraphQL */ `
type Query {
hello: String!
throwAnError: String
}
`,
plans: {
Query: {
hello() {
return constant("world");
},
throwAnError() {
return error(new Error("You asked for an error... Here it is."));
},
},
},
});

const serv = grafserv({ schema, preset });
const server = createServer(serv.createHandler());
const server = createServer();
serv.addTo(server);
const promise = new Promise<void>((resolve, reject) => {
server.on("listening", () => {
server.off("error", reject);
Expand All @@ -45,9 +52,13 @@ export async function makeExampleServer(
info.family === "IPv6"
? `[${info.address === "::" ? "::1" : info.address}]`
: info.address
}:${info.port}${preset.grafserv.graphqlPath}`;
}:${info.port}${resolvedPreset.grafserv!.graphqlPath}`;

const release = () => server.close();
const release = () => {
serv.release();
server.close();
server.closeAllConnections();
};
return { url, release };
}

Expand Down
9 changes: 9 additions & 0 deletions grafast/grafserv/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./envelop": {
"types": "./dist/envelop/index.d.ts",
"default": "./dist/envelop/index.js"
},
"./node": {
"types": "./dist/servers/node/index.d.ts",
"default": "./dist/servers/node/index.js"
Expand Down Expand Up @@ -85,13 +89,17 @@
"node": ">=16.10"
},
"peerDependencies": {
"@envelop/core": "^5.0.0",
"grafast": "workspace:^",
"graphile-config": "workspace:^",
"graphql": "^16.1.0-experimental-stream-defer.6",
"h3": "^1.7.1",
"ws": "^8.12.1"
},
"peerDependenciesMeta": {
"@envelop/core": {
"optional": true
},
"h3": {
"optional": true
},
Expand All @@ -100,6 +108,7 @@
}
},
"devDependencies": {
"@envelop/core": "^5.0.0",
"@fastify/websocket": "^8.2.0",
"@types/aws-lambda": "^8.10.123",
"@types/express": "^4.17.17",
Expand Down
Loading
Loading