Skip to content

Conversation

@djgrant
Copy link
Collaborator

@djgrant djgrant commented Dec 13, 2023

Currently, infrastructure modules can safely import function modules (treating them as resources). This PR makes it possible for function modules to import infrastructure modules.

Why is this necessary? As discussed in discussion #2, there are use cases where a function will need to be configured based upon some infrastructure concerns. For example, a lambda that calls out to DynamoDB needs to have a policy that grants access to the table. The best place to define this is in the lambda, where the developer can see what access is actually required. Even better would be to put type constraints on the Dynamo DB client based on the access that has been granted e.g. can only call putItem if putItem access has been granted to the lambda role.

To achieve this, some code in the function module will need to be evaluated when generating the orchestration graph. To make this safe, we can eliminate everything from the module, except that which is definitely safe to evaluate.

// this is safe to evaluate because it comes from an infra module
import { userTable } from "infra/tables.ts"; 

// runtime stuff, should not be evaluated at compile time
import { handle, json } from "@notation/aws/lambda.fn"; 

// definitely not safe!
fetch("https://example.com"); 

// safe – only contains references to an infra object
const userTableClient = userTable.createClient({
  allow: ["getItem", "putItem"],
});

// not safe
const userTableClient = userTable.client({
  allow: ["getItem", "putItem"],
  cheeky: handle,
});

// not safe
export const writeUser = handle.apiRequest(() => {
  const user = await userTableClient.get({
    id: "user-1",
  });
  return user;
});

The compiler does three things:

  1. Checks if an import is coming from an infra module (some updates to the project structure will be required to enable this)
  2. Marks those imports as safe references
  3. Only keeps statements that contain identifiers that are definitely safe references

Because the heuristic is to opt-in safe code, and I have not covered every possible case, the resulting parser is quite aggressive. It will cull statements that are definitely safe. That's ok for now - the use cases will be limited.

This is a rough solution. It is not efficient, particularly as I didn't refactor the existing compiler transform, but it provides a reasonable foundation to build some interesting features.

Copy link
Contributor

@Happy0 Happy0 left a comment

Choose a reason for hiding this comment

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

Impressive times :D

@djgrant
Copy link
Collaborator Author

djgrant commented Dec 13, 2023

Afterthought. There are some cases that could lead to unexpected behaviour:

safeRef = unsafeRef /// this statement gets removed
safeRef() /// this statement stays, but won't do what's expected

Cases like this should cause compiler errors. Something to pickup when this code is revisited.

@djgrant djgrant merged commit 45c9fd1 into main Dec 13, 2023
@djgrant djgrant deleted the infra-in-runtime branch December 13, 2023 14:19
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.

3 participants