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

Added lambda adapter #1777

Merged
merged 4 commits into from
Oct 5, 2023
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
5 changes: 5 additions & 0 deletions .changeset/twelve-apes-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"grafserv": patch
---

Added AWS lambda adapter for grafserv
11 changes: 11 additions & 0 deletions grafast/grafserv/examples/example-lambda.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { grafserv } from "grafserv/lambda/v1";

import preset from "./graphile.config.mjs";
import schema from "./schema.mjs";

// Create a Grafserv instance
const serv = grafserv({ schema, preset });

// Let lambda call into its handler
export const handler = serv.createHandler()

1 change: 1 addition & 0 deletions grafast/grafserv/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
}
},
"devDependencies": {
"@types/aws-lambda": "^8.10.123",
"@types/express": "^4.17.17",
"@types/koa": "^2.13.8",
"@types/koa-bodyparser": "^4.3.10",
Expand Down
130 changes: 130 additions & 0 deletions grafast/grafserv/src/servers/lambda/v1/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import type {
APIGatewayEvent,
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context as LambdaContext,
} from "aws-lambda";

import { GrafservBase } from "../../../core/base.js";
import type {
GrafservConfig,
RequestDigest,
Result,
} from "../../../interfaces.js";
import { processHeaders } from "../../../utils.js";

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Grafast {
interface RequestContext {
lambdav1: { event: APIGatewayProxyEvent; context: LambdaContext };
}
}
}

/** @experimental */
export class LambdaGrafserv extends GrafservBase {
benjie marked this conversation as resolved.
Show resolved Hide resolved
protected lambdaRequestToGrafserv(
event: APIGatewayEvent,
context: LambdaContext,
): RequestDigest {
const version = event.requestContext.protocol.match(
/^HTTP\/(?<major>[0-9]+)\.(?<minor>[0-9]+)$/,
);

return {
httpVersionMajor: parseInt(version?.groups?.major ?? "1"),
httpVersionMinor: parseInt(version?.groups?.minor ?? "0"),
isSecure: false, // Because we don't trust X-Forwarded-Proto
method: event.httpMethod,
path: event.requestContext.path,
headers: processHeaders(event.multiValueHeaders),
getQueryParams() {
return Object.fromEntries(
Object.entries(event.queryStringParameters ?? {}).filter(
([_k, v]) => v !== undefined,
),
) as Record<string, string>;
},
getBody() {
return {
type: "text",
text: event.body ?? "",
};
},
requestContext: {
lambdav1: { event, context },
},
preferJSON: true,
};
}

protected grafservResponseToLambda(response: Result | null) {
if (response === null) {
return {
statusCode: 404,
body: "¯\\_(ツ)_/¯",
};
}

switch (response.type) {
case "error": {
const { statusCode, headers, error } = response;
return {
statusCode,
headers: { ...headers, "Content-Type": "text/plain" },
body: error.message,
};
}

case "buffer": {
const { statusCode, headers, buffer } = response;
return { statusCode, headers, body: buffer.toString("utf8") };
}

case "json": {
const { statusCode, headers, json } = response;
return { statusCode, headers, body: JSON.stringify(json) };
}

default: {
console.log("Unhandled:");
console.dir(response);
return {
statusCode: 501,
headers: { "Content-Type": "text/plain" },
body: "Server hasn't implemented this yet",
};
}
}
}

createHandler() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
return async (
event: APIGatewayEvent,
context: LambdaContext,
): Promise<APIGatewayProxyResult> => {
return this.grafservResponseToLambda(
await this.processLambdaRequest(
event,
context,
this.lambdaRequestToGrafserv(event, context),
),
);
};
}

protected processLambdaRequest(
_event: APIGatewayEvent,
_context: LambdaContext,
request: RequestDigest,
) {
return this.processRequest(request);
}
}

/** @experimental */
export function grafserv(config: GrafservConfig) {
benjie marked this conversation as resolved.
Show resolved Hide resolved
return new LambdaGrafserv(config);
}
10 changes: 10 additions & 0 deletions grafast/grafserv/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,13 @@ export function parseGraphQLJSONBody(
extensions,
};
}

export async function concatBufferIterator(
bufferIterator: AsyncGenerator<Buffer>,
) {
const buffers = [];
for await (const buffer of bufferIterator) {
buffers.push(buffer);
}
return Buffer.concat(buffers);
}
17 changes: 14 additions & 3 deletions grafast/website/grafserv/servers/lambda.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
# Lambda

**TODO: actually implement this!**
**THIS INTEGRATION IS EXPERIMENTAL**. PRs improving it are welcome.

Grafserv supports the following AWS lambda configurations:
benjie marked this conversation as resolved.
Show resolved Hide resolved

## AWS API Gateway v2

To deploy Grafserv in API Gateway v2:

- Create an Node 18.x lambda following the instructions at https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html
- Add `grafserv` as a dependency using your node package manager of choice
- Replace your lambda's handler implementation with the code below
- Deploy your lambda as a zip package following the instructions at https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html#nodejs-package-create-dependencies

```js
import { grafserv } from "grafserv/lambda";
import { grafserv } from "grafserv/lambda/v1";
import preset from "./graphile.config.mjs";
import schema from "./schema.mjs";

// Create a Grafserv instance
const serv = grafserv({ schema, preset });

// Export a lambda handler for GraphQL
export const handler = serv.createGraphQLHandler();
export const handler = serv.createHandler();
```
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5105,6 +5105,13 @@ __metadata:
languageName: node
linkType: hard

"@types/aws-lambda@npm:^8.10.123":
version: 8.10.123
resolution: "@types/aws-lambda@npm:8.10.123"
checksum: 3336788a2b0fbdca0ce1b643ab5c35219e939ef0e0fa474029efe7cd618e5e8c3dc68180bd8ac3eaeb73b234d4cd01c2a30b7601e6eb0ecfc863b5142966c0b0
languageName: node
linkType: hard

"@types/babel__core@npm:^7.1.14":
version: 7.20.1
resolution: "@types/babel__core@npm:7.20.1"
Expand Down Expand Up @@ -11789,6 +11796,7 @@ __metadata:
resolution: "grafserv@workspace:grafast/grafserv"
dependencies:
"@graphile/lru": "workspace:^"
"@types/aws-lambda": "npm:^8.10.123"
"@types/express": "npm:^4.17.17"
"@types/koa": "npm:^2.13.8"
"@types/koa-bodyparser": "npm:^4.3.10"
Expand Down