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

(JS) Expose handler function type to allow manual exporting of Lambda handler #248

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

hugo-vrijswijk
Copy link

@hugo-vrijswijk hugo-vrijswijk commented Jul 23, 2022

Related to #246

Needs some more docs, maybe some further (manual) testing, and possibly an entirely different implementation. But I wanted to open this PR to have an example and get the discussion flowing.

With this, you can implement and export your lambda as follows:

object Lambda extends IOLambda[String, String] {

  override def handler: Resource[IO, LambdaEnv[IO, String] => IO[Option[String]]] = ???

  @JSExportTopLevel("handler")
  def impl: HandlerFn = handlerFn
}

Copy link
Member

@armanbilge armanbilge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for starting this!! If you're really brave I guess ideally we would have some integration tests against the actual AWS JS runtime that verify it works for both CommonJS and ESModules.

Comment on lines 40 to 41
final def main(args: Array[String]): Unit =
js.Dynamic.global.exports.updateDynamic(handlerName)(handlerFn)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we can remove this now :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can remove it if you want, but it would be a breaking change for any existing users. It might be nice to still have the old implementation for CommonJS users. Maybe we can also add a warning or clear error when this is called inside ESModule

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to think about this. I think I dislike the idea that there are two distinct ways to define JS Lambdas, and "dead" code depending on which you choose (such as handlerName for ESModules). I think this would be very confusing. Since your approach words for both, I'd rather buy-in all the way.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, I suppose a breaking change like this is better to do early in the life of a library. If I have your permission, I'll remove the related code

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. Please go ahead with the change, although maybe we should make this PR the beginning of an v0.2.x series since this is a significant change (ignoring the fact that 0.1.x hasn't materialized a stable release 😅). You can bump the tlBaseVersion in the build to signify this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I don't think I've missed anything, but you are welcome to double-check me :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I'm afraid the example project will also need updating :) To keep it cross-compiling with JVM I think we can add a @JSExport stub.
https://github.com/scala-js/scala-js-stubs/

Comment on lines 60 to 61
// This would be exported with `@JSExportTopLevel("handler")`
def impl: HandlerFn = handlerFn
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're not exporting it, what is this actually testing?

To verify the export works, our test should be able to call the exported method without knowing about the CountingIOLambda type.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I suppose this only tests that impl calls handlerFn which calls handler. Which verifies that the handler works when implemented by the class. I don't think you can explicitly call an export inside a unit test. If you have any ideas about how to approach I'd be happy to hear it

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've now added a new sbt scripted test to replace this one. It's similar to the existing one, except it tests the exported handler

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you can explicitly call an export inside a unit test. If you have any ideas about how to approach I'd be happy to hear it

For the record, I'm pretty sure you can :) conceptually an "exported" function is equivalent to a function defined in JavaScript land. So we can write a facade for it, and call it, without knowing that it is in fact defined in Scala!

README.md Outdated Show resolved Hide resolved
@hugo-vrijswijk
Copy link
Author

If you're really brave I guess ideally we would have some integration tests against the actual AWS JS runtime that verify it works for both CommonJS and ESModules.

I have no idea how I'd even start on this 😅

@armanbilge
Copy link
Member

I have no idea how I'd even start on this

If you want to do this 😁 start by making a new project with feral and figure out how to run it against SAM locally. Then, write those commands into an sbt task. Then, we make that into a scripted test.

Some of my old work here might be helpful:
https://github.com/ChristopherDavenport/js-test/tree/serverless

Anyway just driving by, sorry, will come back later with another review pass :) thanks for all your work!

@armanbilge
Copy link
Member

I wonder if instead of dealing with SAM it would be easier to work with the Node.js runtime directly.
https://www.npmjs.com/package/aws-lambda-ric
https://github.com/aws/aws-lambda-nodejs-runtime-interface-client

@armanbilge
Copy link
Member

Cool, I did some investigation. It seems the trick is to install aws-lambda-ric and and aws-lambda-rie.

Then we start the lambda like this:

$ aws-lambda-rie aws-lambda-ric app.handler

where app.js contains

exports.handler = async (event, context) => {
  return 'Hello World!';
}

which can be invoked like:

$ curl -XPOST "http://localhost:8080/2015-03-31/functions/function/invocations" -d '{}'
"Hello World!"

No pressure to take this on :) but given my previous hiccups with exports I would like to add this integration test just to be sure that everything works.

@armanbilge
Copy link
Member

@armanbilge
Copy link
Member

Ah, instead of installing the ric and rie seems we can directly invoke the docker image. https://hub.docker.com/r/amazon/aws-lambda-nodejs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants