Current section
Files
Jump to
Current section
Files
lib/firebird/phoenix/scaffold.ex
defmodule Firebird.Phoenix.Scaffold do
@moduledoc """
Generates a complete Phoenix WASM application scaffold.
Creates a ready-to-run project that uses Firebird's WASM-accelerated
Phoenix components for HTTP request handling.
## Generated Structure
my_app/
├── lib/
│ ├── my_app/
│ │ ├── application.ex # OTP Application with Phoenix WASM supervisor
│ │ ├── router.ex # Route definitions using RouterDSL
│ │ ├── controllers/
│ │ │ ├── page_controller.ex
│ │ │ └── user_controller.ex
│ │ └── templates/
│ │ ├── layout.html.eex
│ │ └── page/
│ │ └── index.html.eex
│ └── my_app.ex # Main module
├── test/
│ ├── test_helper.exs
│ └── my_app/
│ └── router_test.exs
├── config/
│ └── config.exs
└── mix.exs
"""
@doc """
Generate a Phoenix WASM application scaffold.
## Parameters
- `app_name` - Application name (e.g., "my_app")
- `opts` - Options:
- `:path` - Output directory (default: current dir + app_name)
- `:module` - Module name (default: camelized app_name)
## Returns
- `{:ok, path}` - Scaffold generated at path
- `{:error, reason}` - Generation failed
"""
@spec generate(String.t(), keyword()) :: {:ok, String.t()} | {:error, term()}
def generate(app_name, opts \\ []) do
module_name = Keyword.get(opts, :module, Macro.camelize(app_name))
base_path = Keyword.get(opts, :path, Path.join(File.cwd!(), app_name))
files = [
{"mix.exs", mix_exs(app_name, module_name)},
{"lib/#{app_name}.ex", main_module(module_name)},
{"lib/#{app_name}/application.ex", application(module_name)},
{"lib/#{app_name}/router.ex", router(module_name)},
{"lib/#{app_name}/controllers/page_controller.ex", page_controller(module_name)},
{"lib/#{app_name}/controllers/user_controller.ex", user_controller(module_name)},
{"lib/#{app_name}/templates/layout.html.eex", layout_template()},
{"lib/#{app_name}/templates/page/index.html.eex", page_index_template()},
{"test/test_helper.exs", test_helper()},
{"test/#{app_name}/router_test.exs", router_test(module_name)},
{"config/config.exs", config(app_name)},
{"README.md", readme(app_name, module_name)}
]
Enum.each(files, fn {rel_path, content} ->
full_path = Path.join(base_path, rel_path)
File.mkdir_p!(Path.dirname(full_path))
File.write!(full_path, content)
end)
{:ok, base_path}
end
defp mix_exs(app_name, module_name) do
"""
defmodule #{module_name}.MixProject do
use Mix.Project
def project do
[
app: :#{app_name},
version: "0.1.0",
elixir: "~> 1.14",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
def application do
[
extra_applications: [:logger],
mod: {#{module_name}.Application, []}
]
end
defp deps do
[
{:firebird, "~> 0.1.0"},
{:jason, "~> 1.4"}
]
end
end
"""
end
defp main_module(module_name) do
"""
defmodule #{module_name} do
@moduledoc \"\"\"
#{module_name} - A Phoenix WASM application powered by Firebird.
\"\"\"
end
"""
end
defp application(module_name) do
"""
defmodule #{module_name}.Application do
use Application
@impl true
def start(_type, _args) do
children = [
{Firebird.Phoenix.Application, name: :phoenix_wasm}
]
opts = [strategy: :one_for_one, name: #{module_name}.Supervisor]
Supervisor.start_link(children, opts)
end
end
"""
end
defp router(module_name) do
"""
defmodule #{module_name}.Router do
use Firebird.Phoenix.RouterDSL
pipeline :browser do
plug "put_secure_headers"
plug "accept:text/html"
end
pipeline :api do
plug "accept:application/json"
plug "cors:*"
end
scope "/" do
get "/", "PageController.index"
get "/about", "PageController.about"
end
scope "/api", :api do
resources "/users", "UserController"
end
@doc \"\"\"
Define action handlers for route dispatch.
\"\"\"
def actions do
%{
"PageController.index" => {200, "text/html", "<h1>Welcome to #{module_name}</h1>"},
"PageController.about" => {200, "text/html", "<h1>About</h1>"},
"UserController.index" => {200, "application/json", "[]"},
"UserController.show" => {200, "application/json", ~s({"id":"{{id}}"})},
"UserController.create" => {201, "application/json", ~s({"status":"created"})},
"UserController.update" => {200, "application/json", ~s({"id":"{{id}}","status":"updated"})},
"UserController.delete" => {204, "text/plain", ""}
}
end
@doc \"\"\"
Handle a request using the WASM router and endpoint.
\"\"\"
def handle_request(method, path) do
Firebird.Phoenix.Application.process_request(:phoenix_wasm,
method: method,
path: path,
routes: routes(),
actions: actions()
)
end
end
"""
end
defp page_controller(module_name) do
"""
defmodule #{module_name}.PageController do
@moduledoc "Handles page requests."
def index(_params) do
{200, "text/html", "<h1>Welcome to #{module_name}!</h1>"}
end
def about(_params) do
{200, "text/html", "<h1>About #{module_name}</h1><p>Powered by Firebird WASM</p>"}
end
end
"""
end
defp user_controller(module_name) do
"""
defmodule #{module_name}.UserController do
@moduledoc "Handles user API requests."
def index(_params) do
{200, "application/json", ~s([{"id":"1","name":"Alice"},{"id":"2","name":"Bob"}])}
end
def show(%{"id" => id}) do
{200, "application/json", ~s({"id":"#{"\#{id}"}","name":"User #{"\#{id}}"}"})}
end
def create(_params) do
{201, "application/json", ~s({"status":"created"})}
end
def update(%{"id" => id}) do
{200, "application/json", ~s({"id":"#{"\#{id}"}","status":"updated"})}
end
def delete(_params) do
{204, "text/plain", ""}
end
end
"""
end
defp layout_template do
"""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><%= @title %></title>
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<main>
<%= @inner_content %>
</main>
</body>
</html>
"""
end
defp page_index_template do
"""
<h1>Welcome!</h1>
<p>This application is powered by Firebird Phoenix WASM.</p>
<p>All request processing uses WebAssembly modules for:</p>
<ul>
<li>Route matching</li>
<li>Template rendering</li>
<li>Request parsing</li>
<li>Response building</li>
</ul>
"""
end
defp test_helper do
"""
ExUnit.start()
"""
end
defp router_test(module_name) do
"""
defmodule #{module_name}.RouterTest do
use ExUnit.Case
import Firebird.Phoenix.Testing
test "routes are defined" do
routes = #{module_name}.Router.routes()
assert length(routes) > 0
end
test "pipelines are defined" do
pipelines = #{module_name}.Router.pipelines()
assert Map.has_key?(pipelines, :browser)
assert Map.has_key?(pipelines, :api)
end
test "actions are defined" do
actions = #{module_name}.Router.actions()
assert Map.has_key?(actions, "PageController.index")
assert Map.has_key?(actions, "UserController.show")
end
end
"""
end
defp config(app_name) do
"""
import Config
config :#{app_name},
phoenix_wasm: [
components: [:router, :template, :plug, :endpoint, :json]
]
config :logger, level: :info
"""
end
defp readme(_app_name, module_name) do
"""
# #{module_name}
A Phoenix WASM application powered by [Firebird](https://github.com/hdresearch/firebird).
## Getting Started
```bash
mix deps.get
mix test
```
## Architecture
This application uses Firebird's WASM-accelerated Phoenix components:
- **Router** (`#{module_name}.Router`) - Route definitions using Firebird DSL
- **Endpoint** - WASM-based request lifecycle processing
- **Controllers** - Action handlers for each route
- **Templates** - EEx templates compiled to WASM-compatible format
## API Routes
| Method | Path | Handler |
|--------|------|---------|
#{Enum.map_join(["GET /", "GET /about", "GET /api/users", "GET /api/users/:id", "POST /api/users", "PUT /api/users/:id", "DELETE /api/users/:id"], "\n", fn route ->
[method, path] = String.split(route, " ", parts: 2)
"| #{method} | #{path} | Auto-dispatched |"
end)}
## Usage
```elixir
# Handle a request
{:ok, resp} = #{module_name}.Router.handle_request("GET", "/api/users/42")
resp.status # => 200
resp.body # => {"id":"42"}
```
"""
end
end