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

Create Astro resource #4075

Closed
skyrpex opened this issue Sep 5, 2023 · 5 comments
Closed

Create Astro resource #4075

skyrpex opened this issue Sep 5, 2023 · 5 comments
Labels

Comments

@skyrpex
Copy link
Contributor

skyrpex commented Sep 5, 2023

Create a Wing resource that enables working with Astro projects.

  • Astro generates MPAs, so a server renders the HTML
  • In order to pass data and clients from Wing to the code in Astro, a virtual module can be used
  • Astro can generate one single JS entry for all of the routes, or one JS file for each route. This can be used from Wing to either generate one cloud.Function or multiple cloud.Functions

Some in-depth details:

Passing Data and Clients to the Astro environment

Currently, there's no way to pass inflight clients from Wing source code to the runtime environment in Astro. I thought this interface would be ideal to enable a good DX:

// main.w
bring cloud;
bring ex;

let users = new ex.Table();
let api = new cloud.Api();

let astro = new ex.Astro(
    configFile: "../website/astro.config.mjs",
);

// The inflight here will become a virtual npm module that can be imported from the Astro project. The `apiURL` value is a simple string, and the `users` object is the inflight API for the table.
astro.defineVirtualModule("virtual:wing", inflight () => {
    return {
        users: users,
        apiURL: api.url,
    };
});

// Alternative syntax. Defining virtual modules is quite low level. Maybe we could just:
astro.expose("users", users); // how to resolve permissions?
astro.expose("apiURL", api.url);
---
// ../website/src/pages/index.astro
import { users, apiURL } from "virtual:wing";

const user = await users.get("user_1");
---

<html>
  <body>
    <h1>Hello, ${ user.name }!</h1>
    <p>This is the API URL: ${ apiURL }</p>
  </body>
</html>

TypeScript type definitions for the Virtual Modules can be generated from Wing and consumed in the Astro project. The type definition needed for the example above would be:

// ../website/.wing/virtual.d.ts
declare module "virtual:wing" {
  export const users: {
    insert(key: string, row: JsonSerializable): Promise<void>;
    update(key: str, row: JsonSerializable): Promise<void>;
    get(key: str): Promise<JsonSerializable>;
    list(): Promise<JsonSerializable[]>;
  };

  export const apiURL: string;
}

Possible problems:

  • Can Wing obtain the (let's say) API URL during build time? If not, is there a workaround?
  • Can we run async tasks in preflight?
  • Can we generate type definitions based on Wing structs or classes?

References:

Building one function per route or one for all

By building the Astro project with adapterFeatures: { functionPerRoute: true }, we can generate either one function per route, or one function for all routes. This implies that we need to build the Astro project before creating the cloud.Function resources. It would be interesting to enable async tasks for resources, but in the meantime we can resort to use the Astro CLI with exec or spawn.

References:

Creating Type Definitions for the Astro Virtual Modules

In Astro projects there's an astro.config.mjs file where you can customize the project. There you can enable integrations and adapters, which are similar to Vite plugins. I propose having a @winglang/astro-adapter package that can be used like this:

import wing from "@winglang/astro-adapter";
import { defineConfig } from "astro/config";

export default defineConfig({
  output: "server",
  adapter: wing({
    // (optional) Where Wing will put generated files (ie, virtual module type definitions).
    wingDir: ".wing/",
  }),
});

The purpose of this adapter is to:

  • Allow the users to customize invasive Wing features such as where it's going to generate type definitions
  • Give the control of the Astro project to Wing. Since it will likely depend on Wing for dependencies such as tables, APIs, etc, the project can't really be started without wing it
  • Allow Wing to customize the build process so it can generate the aproppiate cloud functions and stuff
@skyrpex skyrpex added this to Wing Sep 5, 2023
@skyrpex skyrpex converted this from a draft issue Sep 5, 2023
@skyrpex skyrpex changed the title Create Astro resource in Wing Create Astro resource Sep 5, 2023
@Chriscbr
Copy link
Contributor

Chriscbr commented Sep 5, 2023

I have limited experience with MPA frameworks but it would be great to get this working. Some initial ideas and thoughts:

I thought this interface would be ideal to enable a good DX:
(code example)

The API makes sense to me - the only possible issue in the short term is I'm not sure if we'll be able to support passing entire resource clients (like users in your first code example) to other frameworks. The reason is that when an entire resource is passed, Wing can't infer which methods will get called on the class, so it won't know which permissions to give. (Longer term something like #76 might offer a workaround for this). But it should be OK to pass other values like strings (like api.url), or Json, or inflight functions.

Can Wing obtain the (let's say) API URL during build time? If not, is there a workaround?

It's not possible to get the URL during the synthesis of the construct tree since the API resource may not be deployed yet (and the URL may be dynamically generated by a cloud provider). But, it is possible to have Wing create a Terraform (or CloudFormation) template in a way so that the URL is injected into a resource in one way or another during deployment time.

In cloud.Website, this is solved by injecting the URL into a config file -- the Terraform config ends up looking like this:
Screenshot 2023-09-05 at 2 16 48 PM

This "content" field references the property, and it's only read once the API has been deployed. This is just one way to do it, environment variables is another common solution, but I think we can also come up with more custom or granular solutions.

Some way to create a custom resource might also solve this (not sure): #3270

Can we run async tasks in preflight?

In the normal sense, not really (right now all preflight methods have to be synchronous). It might be a good idea to create a separate issue if we want to add support for that.

But it is possible to create a resource that performs an asynchronous task during deployment. For example, cloud.OnDeploy lets you do that - in theory it can run any async code, and you can condition other resources to only deploy once cloud.OnDeploy has finished. That said, there are some caveats:

  1. There's no way for cloud.OnDeploy to specify outputs or return values for other resources to use.
  2. It runs your inflight code in the cloud, instead of on your machine.

I think it should be possible to lift these restrictions...

Can we generate type definitions based on Wing structs or classes?

Sounds feasible. We don't have any reflection APIs in Wing yet but I know some folks have brought it up before as a potentially useful tool.

@skyrpex
Copy link
Contributor Author

skyrpex commented Sep 5, 2023

I have limited experience with MPA frameworks but it would be great to get this working.

To summarize, in an MPA framework there is a server that renders the HTML back to the user (a la old school PHP server). The difference today is that you can still code your app with your framework of choice (ie, react) as if it was a client-only app... you almost don't need to care about it being an MPA.

I thought this interface would be ideal to enable a good DX:
(code example)

The API makes sense to me - the only possible issue in the short term is I'm not sure if we'll be able to support passing entire resource clients (like users in your first code example) to other frameworks. The reason is that when an entire resource is passed, Wing can't infer which methods will get called on the class, so it won't know which permissions to give. (Longer term something like #76 might offer a workaround for this). But it should be OK to pass other values like strings (like api.url), or Json, or inflight functions.

Yeah, inferring is out of the question here. As a workaround, we can "trigger" the permission binding as follows:

bring cloud;
bring ex;

let users = new ex.Table();
let astro = new ex.Astro();
astro.expose("users", inflight () => {
  // Bind permissions
  users.put;
  users.get;

  return users;
});

Can Wing obtain the (let's say) API URL during build time? If not, is there a workaround?

It's not possible to get the URL during the synthesis of the construct tree since the API resource may not be deployed yet (and the URL may be dynamically generated by a cloud provider). But, it is possible to have Wing create a Terraform (or CloudFormation) template in a way so that the URL is injected into a resource in one way or another during deployment time.

In cloud.Website, this is solved by injecting the URL into a config file -- the Terraform config ends up looking like this: Screenshot 2023-09-05 at 2 16 48 PM

This "content" field references the property, and it's only read once the API has been deployed. This is just one way to do it, environment variables is another common solution, but I think we can also come up with more custom or granular solutions.

Some way to create a custom resource might also solve this (not sure): #3270

That's great. I think environment variables should be enough (and the most optimal way since it doesn't require fetching other resources). The astro server will be a cloud.Function so it can technically rely on process.env.*.

Can we run async tasks in preflight?

In the normal sense, not really (right now all preflight methods have to be synchronous). It might be a good idea to create a separate issue if we want to add support for that.

But it is possible to create a resource that performs an asynchronous task during deployment. For example, cloud.OnDeploy lets you do that - in theory it can run any async code, and you can condition other resources to only deploy once cloud.OnDeploy has finished. That said, there are some caveats:

  1. There's no way for cloud.OnDeploy to specify outputs or return values for other resources to use.
  2. It runs your inflight code in the cloud, instead of on your machine.

I think it should be possible to lift these restrictions...

I think we need async in preflight initializers... It's a very common pattern that we'll encounter for so many resources we want to integrate with. I understand this needs a whole separate discussion, but I picture doing the following in the Astro initializer:

struct AstroProps {
  configFile: str;
}

bring astro;

class Astro {
  init(props: AstroProps) {
    const routes = await astro.build(configFile: props.configFile);

    let api = new cloud.Api();

    // Create an API route for each astro route
    for route in routes {
      api.addRoute(route.method, inflight (event) => { require(route.entry)(event); });
    }
  }
}

Can we generate type definitions based on Wing structs or classes?

Sounds feasible. We don't have any reflection APIs in Wing yet but I know some folks have brought it up before as a potentially useful tool.

Good :)

@skorfmann
Copy link
Contributor

Just wanna sum up what I was trying to say in the meeting regarding the "SDK" - What I meant was SDK in a sense of an inflight client consumed in another context, which the following types seems to represent to me.

// Alternative syntax. Defining virtual modules is quite low level. Maybe we could just:
astro.expose("users", users); // how to resolve permissions?
astro.expose("apiURL", api.url);

... 

// ../website/.wing/virtual.d.ts
declare module "virtual:wing" {
  export const users: {
    insert(key: string, row: JsonSerializable): Promise<void>;
    update(key: str, row: JsonSerializable): Promise<void>;
    get(key: str): Promise<JsonSerializable>;
    list(): Promise<JsonSerializable[]>;
  };

  export const apiURL: string;
}

Isn't this just an implicit inflight SDK for resources defined in Wing to be consumed in another context - which happens to be Astro / JS in this case. However, conceptually this could be (generated) Python, Java or some other language code. So, could a similar thing be useful for a Laravel, Rails or Flask app?

@ainvoner ainvoner modified the milestone: Wing Cloud: infrastructure Sep 6, 2023
Copy link

github-actions bot commented Nov 6, 2023

Hi,

This issue hasn't seen activity in 60 days. Therefore, we are marking this issue as stale for now. It will be closed after 7 days.
Feel free to re-open this issue when there's an update or relevant information to be added.
Thanks!

@github-actions github-actions bot added the Stale label Nov 6, 2023
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Nov 14, 2023
@github-project-automation github-project-automation bot moved this from 🆕 New - not properly defined to ✅ Done in Wing Nov 14, 2023
@skyrpex
Copy link
Contributor Author

skyrpex commented Nov 17, 2023

However, conceptually this could be (generated) Python, Java or some other language code. So, could a similar thing be useful for a Laravel, Rails or Flask app?

Yes, if resources are able to reflect metadata from inflight APIs during preflight, it should be possible to generate source files for different languages (I believe).

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

Successfully merging a pull request may close this issue.

4 participants