Skip to content

Add grafserv/lambda/v2 #2899

@nick-kang

Description

@nick-kang

Feature description

The current lambda implementation is 3 years old and only supports api gateway v1. Api gateway v2 is the standard now. Adding Api gateway v2 also unlocks Lambda Function URL support as they use the same handler type signature.

Breaking changes

Not a breaking change.

Supporting development

Here is the implementation based on the v1.

// grafserv/lambda/v2/index.ts
import type {
  APIGatewayProxyEventV2,
  APIGatewayProxyHandlerV2,
  APIGatewayProxyResultV2,
  Context as LambdaContext,
} from "aws-lambda";
import type { GrafservConfig, RequestDigest, Result } from "grafserv";
import { GrafservBase } from "grafserv";

declare global {
  namespace Grafast {
    interface RequestContext {
      lambdav2: { event: APIGatewayProxyEventV2; context: LambdaContext };
    }
  }
}

/** @experimental */
export class LambdaV2Grafserv extends GrafservBase {
  protected lambdaRequestToGrafserv(
    event: APIGatewayProxyEventV2,
    context: LambdaContext,
  ): RequestDigest {
    const version = event.requestContext.http.protocol.match(
      /^HTTP\/(?<major>[0-9]+)\.(?<minor>[0-9]+)$/,
    );

    return {
      httpVersionMajor: parseInt(version?.groups?.major ?? "1", 10),
      httpVersionMinor: parseInt(version?.groups?.minor ?? "0", 10),
      isSecure: false, // Because we don't trust X-Forwarded-Proto
      method: event.requestContext.http.method,
      path: event.rawPath,
      headers: event.headers as Record<string, string>,
      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: {
        lambdav2: { event, context },
      },
      preferJSON: true,
    };
  }

  protected grafservResponseToLambda(
    response: Result | null,
  ): APIGatewayProxyResultV2 {
    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(): APIGatewayProxyHandlerV2<APIGatewayProxyResultV2> {
    return async (
      event: APIGatewayProxyEventV2,
      context: LambdaContext,
    ): Promise<APIGatewayProxyResultV2> => {
      return this.grafservResponseToLambda(
        await this.processLambdaRequest(
          event,
          context,
          this.lambdaRequestToGrafserv(event, context),
        ),
      );
    };
  }

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

/** @experimental */
export function grafserv(config: GrafservConfig) {
  return new LambdaV2Grafserv(config);
}
  • am interested in building this feature myself
  • am interested in collaborating on building this feature
  • am willing to help testing this feature before it's released
  • am willing to write a test-driven test suite for this feature (before it exists)
  • am a Graphile sponsor ❤️
  • have an active support or consultancy contract with Graphile

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    🌳 Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions