Jelajahi Sumber

initial commit

Billy Barrow 1 Minggu lalu
melakukan
4777763a08

+ 521 - 0
examples/CounterComponent.vala

@@ -0,0 +1,521 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+using Inversion;
+using Spry;
+
+/**
+ * CounterComponent Example
+ * 
+ * Demonstrates using the Spry.Component class to build dynamic HTML pages
+ * with outlets for component composition. Shows how to:
+ *   - Define a Component subclass with embedded HTML markup
+ *   - Use spry-outlet elements for dynamic content injection
+ *   - Use add_outlet_child/set_outlet_child methods to populate outlets
+ *   - Handle form POST to update state
+ * 
+ * This example mirrors the Astralis DocumentBuilderTemplate example but
+ * uses the Component class abstraction for better code organization.
+ * 
+ * Usage: counter-component [port]
+ */
+
+// Application state - a simple counter
+class AppState : Object {
+    public int counter { get; set; }
+    public int total_changes { get; set; }
+    public string last_action { get; set; }
+    public DateTime last_update { get; set; }
+    
+    public AppState() {
+        counter = 0;
+        total_changes = 0;
+        last_action = "Initialized";
+        last_update = new DateTime.now_local();
+    }
+    
+    public void increment() {
+        counter++;
+        total_changes++;
+        last_action = "Incremented";
+        last_update = new DateTime.now_local();
+    }
+    
+    public void decrement() {
+        counter--;
+        total_changes++;
+        last_action = "Decremented";
+        last_update = new DateTime.now_local();
+    }
+    
+    public void reset() {
+        counter = 0;
+        total_changes++;
+        last_action = "Reset";
+        last_update = new DateTime.now_local();
+    }
+}
+
+/**
+ * CSS content for the counter page.
+ * Served as a FastResource for optimal performance with pre-compression.
+ */
+private const string COUNTER_CSS = """
+body {
+    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+    max-width: 700px;
+    margin: 0 auto;
+    padding: 20px;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    min-height: 100vh;
+}
+.card {
+    background: white;
+    border-radius: 12px;
+    padding: 25px;
+    margin: 15px 0;
+    box-shadow: 0 10px 40px rgba(0,0,0,0.2);
+}
+h1 {
+    color: #333;
+    margin-top: 0;
+}
+.counter-display {
+    text-align: center;
+    padding: 30px;
+    background: #f8f9fa;
+    border-radius: 8px;
+    margin: 20px 0;
+}
+.counter-value {
+    font-size: 72px;
+    font-weight: bold;
+    color: #667eea;
+    line-height: 1;
+}
+.counter-label {
+    color: #666;
+    font-size: 14px;
+    text-transform: uppercase;
+    letter-spacing: 2px;
+}
+.button-group {
+    display: flex;
+    gap: 10px;
+    justify-content: center;
+    margin: 20px 0;
+}
+button {
+    padding: 12px 24px;
+    border: none;
+    border-radius: 6px;
+    cursor: pointer;
+    font-size: 16px;
+    font-weight: 600;
+    transition: all 0.2s;
+}
+button:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+}
+.btn-primary {
+    background: #667eea;
+    color: white;
+}
+.btn-primary:hover {
+    background: #5a6fd6;
+}
+.btn-danger {
+    background: #e74c3c;
+    color: white;
+}
+.btn-danger:hover {
+    background: #c0392b;
+}
+.btn-success {
+    background: #2ecc71;
+    color: white;
+}
+.btn-success:hover {
+    background: #27ae60;
+}
+.info-grid {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    gap: 15px;
+    margin: 20px 0;
+}
+.info-item {
+    background: #f8f9fa;
+    padding: 15px;
+    border-radius: 6px;
+    text-align: center;
+}
+.info-label {
+    font-size: 12px;
+    color: #666;
+    text-transform: uppercase;
+    letter-spacing: 1px;
+}
+.info-value {
+    font-size: 18px;
+    font-weight: 600;
+    color: #333;
+    margin-top: 5px;
+}
+.status-positive {
+    color: #2ecc71 !important;
+}
+.status-negative {
+    color: #e74c3c !important;
+}
+.status-zero {
+    color: #666 !important;
+}
+code {
+    background: #e8e8e8;
+    padding: 2px 6px;
+    border-radius: 4px;
+    font-size: 14px;
+}
+pre {
+    background: #263238;
+    color: #aed581;
+    padding: 15px;
+    border-radius: 6px;
+    overflow-x: auto;
+    font-size: 13px;
+}
+.feature-list {
+    margin: 0;
+    padding-left: 20px;
+}
+.feature-list li {
+    margin: 8px 0;
+    color: #555;
+}
+a {
+    color: #667eea;
+    text-decoration: none;
+}
+a:hover {
+    text-decoration: underline;
+}
+""";
+
+/**
+ * InfoItemComponent - A single info grid item.
+ */
+class InfoItemComponent : Component {
+    public override string markup { get {
+        return """
+        <div class="info-item">
+            <div class="info-label" id="label"></div>
+            <div class="info-value" id="value"></div>
+        </div>
+        """;
+    }}
+
+    public string label { set {
+        instance.get_element_by_id("label").text_content = value;
+    }}
+
+    public string info_value { set {
+        instance.get_element_by_id("value").text_content = value;
+    }}
+
+    public string? status_class { set {
+        var el = instance.get_element_by_id("value");
+        if (value != null) {
+            el.add_class(value);
+        }
+    }}
+}
+
+/**
+ * InfoGridComponent - Container for info items.
+ */
+class InfoGridComponent : Component {
+    
+    public void set_items(Enumerable<Component> items) {
+        set_outlet_children("items", items);
+    }
+    
+    public override string markup { get {
+        return """
+        <div class="info-grid">
+            <spry-outlet id="items"/>
+        </div>
+        """;
+    }}
+}
+
+/**
+ * CounterDisplayComponent - Shows the counter value with buttons.
+ */
+class CounterDisplayComponent : Component {
+    private InfoGridComponent _info_grid;
+    
+    public CounterDisplayComponent() {
+        _info_grid = new InfoGridComponent();
+    }
+    
+    public void set_info_items(Enumerable<Component> items) {
+        _info_grid.set_items(items);
+        set_outlet_child("info-grid", _info_grid);
+    }
+    
+    public override string markup { get {
+        return """
+        <div class="card" id="counter-card">
+            <div class="counter-display">
+                <div class="counter-label">Current Value</div>
+                <div class="counter-value" id="counter-value">0</div>
+            </div>
+            <div class="button-group">
+                <form method="POST" action="/decrement" style="display: inline;">
+                    <button type="submit" class="btn-danger">- Decrease</button>
+                </form>
+                <form method="POST" action="/reset" style="display: inline;">
+                    <button type="submit" class="btn-primary">Reset</button>
+                </form>
+                <form method="POST" action="/increment" style="display: inline;">
+                    <button type="submit" class="btn-success">+ Increase</button>
+                </form>
+            </div>
+            <spry-outlet id="info-grid"/>
+        </div>
+        """;
+    }}
+    
+    public int counter { set {
+        var counter_el = instance.get_element_by_id("counter-value");
+        counter_el.text_content = value.to_string();
+        
+        // Update status class
+        counter_el.remove_class("status-positive");
+        counter_el.remove_class("status-negative");
+        counter_el.remove_class("status-zero");
+        
+        if (value > 0) {
+            counter_el.add_class("status-positive");
+        } else if (value < 0) {
+            counter_el.add_class("status-negative");
+        } else {
+            counter_el.add_class("status-zero");
+        }
+    }}
+}
+
+/**
+ * CounterPageComponent - The main page component.
+ */
+class CounterPageComponent : Component {
+    
+    public void set_counter_section(Component component) {
+        set_outlet_child("counter-section", component);
+    }
+    
+    public override string markup { get {
+        return """
+        <!DOCTYPE html>
+        <html lang="en">
+        <head>
+            <meta charset="UTF-8"/>
+            <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+            <title>Component Counter Example</title>
+            <link rel="stylesheet" href="/styles.css"/>
+        </head>
+        <body>
+            <div class="card" id="main-card">
+                <h1 id="page-title">Component Counter Example</h1>
+                <p id="description">This page demonstrates using Spry.Component with outlets for dynamic content.</p>
+            </div>
+            <spry-outlet id="counter-section"/>
+            <div class="card" id="code-card">
+                <h2>How It Works</h2>
+                <p>The page uses Component with spry-outlet elements for dynamic content:</p>
+                <pre>component.set_outlet_child("content", child);</pre>
+                <h3>Component Features Used:</h3>
+                <ul class="feature-list">
+                    <li><code>spry-outlet</code> - Placeholder for child components</li>
+                    <li><code>set_outlet_child()</code> - Insert single component</li>
+                    <li><code>to_result()</code> - Render as HttpResult</li>
+                </ul>
+            </div>
+            <div class="card" id="footer-card">
+                <p style="text-align: center; color: #666; margin: 0;">
+                    Built with <code>Spry.Component</code> |
+                    <a href="/raw">View Raw HTML</a>
+                </p>
+            </div>
+        </body>
+        </html>
+        """;
+    }}
+}
+
+/**
+ * NoticeComponent - A simple notice for the raw view.
+ */
+class NoticeComponent : Component {
+    public override string markup { get {
+        return """
+        <div class="card" style="background: #fff3cd; color: #856404; border: 1px solid #ffc107;">
+            <p>This is the raw template without dynamic modifications.</p>
+            <p><a href="/">Back to dynamic version</a></p>
+        </div>
+        """;
+    }}
+}
+
+// Global app state
+AppState app_state;
+
+// Home page endpoint - builds the component tree
+class HomePageEndpoint : Object, Endpoint {
+    public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
+        // Determine status
+        string status_text;
+        string status_class;
+        if (app_state.counter > 0) {
+            status_text = "Positive";
+            status_class = "status-positive";
+        } else if (app_state.counter < 0) {
+            status_text = "Negative";
+            status_class = "status-negative";
+        } else {
+            status_text = "Zero";
+            status_class = "status-zero";
+        }
+        
+        // Create info items
+        var items = new Series<Component>();
+        
+        var status_item = new InfoItemComponent();
+        status_item.label = "Status";
+        status_item.info_value = status_text;
+        status_item.status_class = status_class;
+        items.add(status_item);
+        
+        var action_item = new InfoItemComponent();
+        action_item.label = "Last Action";
+        action_item.info_value = app_state.last_action;
+        items.add(action_item);
+        
+        var time_item = new InfoItemComponent();
+        time_item.label = "Last Update";
+        time_item.info_value = app_state.last_update.format("%H:%M:%S");
+        items.add(time_item);
+        
+        var changes_item = new InfoItemComponent();
+        changes_item.label = "Total Changes";
+        changes_item.info_value = app_state.total_changes.to_string();
+        items.add(changes_item);
+        
+        // Create the counter display component and set its info items
+        var counter_display = new CounterDisplayComponent();
+        counter_display.counter = app_state.counter;
+        counter_display.set_info_items(items);
+        
+        // Create the main page component and set the counter section
+        var page = new CounterPageComponent();
+        page.set_counter_section(counter_display);
+        
+        // to_result() handles all outlet replacement
+        return page.to_result();
+    }
+}
+
+// Increment endpoint
+class IncrementEndpoint : Object, Endpoint {
+    public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
+        app_state.increment();
+        return new HttpStringResult("", (StatusCode)302)
+            .set_header("Location", "/");
+    }
+}
+
+// Decrement endpoint
+class DecrementEndpoint : Object, Endpoint {
+    public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
+        app_state.decrement();
+        return new HttpStringResult("", (StatusCode)302)
+            .set_header("Location", "/");
+    }
+}
+
+// Reset endpoint
+class ResetEndpoint : Object, Endpoint {
+    public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
+        app_state.reset();
+        return new HttpStringResult("", (StatusCode)302)
+            .set_header("Location", "/");
+    }
+}
+
+// Raw HTML endpoint - shows the unmodified template
+class RawHtmlEndpoint : Object, Endpoint {
+    public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
+        // Create a raw page without modifications
+        var page = new CounterPageComponent();
+        
+        // Add a notice component
+        page.set_counter_section(new NoticeComponent());
+        
+        return page.to_result();
+    }
+}
+
+void main(string[] args) {
+    int port = args.length > 1 ? int.parse(args[1]) : 8080;
+    
+    // Initialize app state
+    app_state = new AppState();
+    
+    print("═══════════════════════════════════════════════════════════════\n");
+    print("            Spry CounterComponent Example\n");
+    print("═══════════════════════════════════════════════════════════════\n");
+    print("  Port: %d\n", port);
+    print("═══════════════════════════════════════════════════════════════\n");
+    print("  Endpoints:\n");
+    print("    /           - Counter page (Component-based)\n");
+    print("    /styles.css - CSS stylesheet (FastResource)\n");
+    print("    /increment  - Increase counter (POST)\n");
+    print("    /decrement  - Decrease counter (POST)\n");
+    print("    /reset      - Reset counter (POST)\n");
+    print("    /raw        - Raw template (no modifications)\n");
+    print("═══════════════════════════════════════════════════════════════\n");
+    print("\nPress Ctrl+C to stop the server\n\n");
+    
+    try {
+        var application = new WebApplication(port);
+        
+        // Register compression components
+        application.use_compression();
+        
+        // Register CSS as a FastResource
+        application.add_startup_endpoint<FastResource>(new EndpointRoute("/styles.css"), () => {
+            try {
+                return new FastResource.from_string(COUNTER_CSS)
+                    .with_content_type("text/css; charset=utf-8")
+                    .with_default_compressors();
+            } catch (Error e) {
+                error("Failed to create CSS resource: %s", e.message);
+            }
+        });
+        
+        // Register endpoints
+        application.add_endpoint<HomePageEndpoint>(new EndpointRoute("/"));
+        application.add_endpoint<IncrementEndpoint>(new EndpointRoute("/increment"));
+        application.add_endpoint<DecrementEndpoint>(new EndpointRoute("/decrement"));
+        application.add_endpoint<ResetEndpoint>(new EndpointRoute("/reset"));
+        application.add_endpoint<RawHtmlEndpoint>(new EndpointRoute("/raw"));
+        
+        application.run();
+        
+    } catch (Error e) {
+        printerr("Error: %s\n", e.message);
+        Process.exit(1);
+    }
+}

+ 98 - 0
examples/SimpleExample.vala

@@ -0,0 +1,98 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+using Inversion;
+using Spry;
+
+
+class TestComponent : Component {
+    public override string markup { get {
+        return """
+        <!DOCTYPE html>
+        <html>
+        <body>
+        <h1>Hello, World!</h1>
+        <spry-outlet id="content" />
+        </body>
+        </html>
+        """;
+    }}
+
+    public void set_content(Component component) {
+        set_outlet_child("content", component);
+    }
+
+}
+
+class ContentComponent : Component {
+    public override string markup { get {
+        return """
+        <p>Quia assumenda sunt qui. Voluptatibus magni exercitationem tenetur sit itaque laborum aut. Sint voluptates fugit consectetur.</p>
+        <p>Optio totam doloremque minus impedit est eum. Nisi a rerum natus. Impedit est autem culpa ullam reprehenderit sunt. Et harum odio cumque iure.</p>
+        <p>Architecto non ipsum quibusdam aut. Deleniti enim magnam aperiam voluptatibus similique dolor. Porro et aliquam et eum magnam neque praesentium.</p>
+        <p>Quod at nemo rem fugit quia nemo facere et. Fugit sed labore voluptates recusandae dolores sed. Consequatur autem sit minima sunt excepturi et fugiat voluptates. Repellat aut vero doloribus quis laborum repellat dolorem et.</p>
+        <p>Porro at id optio sit voluptatibus nulla quam. Dignissimos praesentium est dolore architecto voluptatem adipisci nostrum. In dolorum et qui distinctio. Aperiam et eligendi quod eaque.</p>
+        """;
+    }}
+
+}
+
+class UserContentComponent : Component {
+    public override string markup { get {
+        return """
+        <p>You said: <span id="message"></span></p>
+        """;
+    }}
+
+    public string message { set {
+        instance.get_element_by_id("message").text_content = value;
+    }}
+
+}
+
+class HomePageEndpoint : Object, Endpoint {
+
+    public async Astralis.HttpResult handle_request(Astralis.HttpContext http_context, Astralis.RouteContext route_context) throws Error {
+        var component = new TestComponent();
+        component.set_content(new ContentComponent());
+        return component.to_result();
+    }
+
+
+}
+
+class MessageEndpoint : Object, Endpoint {
+
+    public async Astralis.HttpResult handle_request(Astralis.HttpContext http_context, Astralis.RouteContext route_context) throws Error {
+        var component = new TestComponent();
+        var content = new UserContentComponent();
+        content.message = http_context.request.query_params.get_any_or_default("message") ?? "No message provided!";
+        component.set_content(content);
+        return component.to_result();
+    }
+
+
+}
+
+
+void main(string[] args) {
+    int port = args.length > 1 ? int.parse(args[1]) : 8080;
+
+    
+    try {
+        var application = new WebApplication(port);
+        
+        // Register compression components
+        application.use_compression();
+        
+        // Register endpoints
+        application.add_endpoint<HomePageEndpoint>(new EndpointRoute("/"));
+        application.add_endpoint<MessageEndpoint>(new EndpointRoute("/message"));
+        
+        application.run();
+        
+    } catch (Error e) {
+        printerr("Error: %s\n", e.message);
+        Process.exit(1);
+    }
+}

+ 485 - 0
examples/TodoComponent.vala

@@ -0,0 +1,485 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+using Inversion;
+using Spry;
+
+/**
+ * TodoComponent Example
+ * 
+ * Demonstrates using the Spry.Component class to build a todo list
+ * application with component composition. Shows how to:
+ *   - Build a complete CRUD interface with Components
+ *   - Use set_outlet_child/set_outlet_children for component composition
+ *   - Handle form submissions
+ * 
+ * This example mirrors the Astralis DocumentBuilder example but
+ * uses the Component class abstraction for better code organization.
+ * 
+ * Usage: todo-component [port]
+ */
+
+// Simple task model for our todo list
+class TodoItem : Object {
+    public int id { get; set; }
+    public string title { get; set; }
+    public bool completed { get; set; }
+    
+    public TodoItem(int id, string title, bool completed = false) {
+        this.id = id;
+        this.title = title;
+        this.completed = completed;
+    }
+}
+
+// In-memory todo store
+class TodoStore : Object {
+    private Series<TodoItem> items = new Series<TodoItem>();
+    private int next_id = 1;
+    
+    public TodoStore() {
+        // Add some initial items
+        add("Learn Spry.Component");
+        add("Build dynamic HTML pages");
+        add("Handle form submissions");
+    }
+    
+    public void add(string title) {
+        items.add(new TodoItem(next_id++, title));
+    }
+    
+    public void toggle(int id) {
+        items.to_immutable_buffer().iterate((item) => {
+            if (item.id == id) {
+                item.completed = !item.completed;
+            }
+        });
+    }
+    
+    public void remove(int id) {
+        var new_items = items.to_immutable_buffer()
+            .where(item => item.id != id)
+            .to_series();
+        items = new_items;
+    }
+    
+    public ImmutableBuffer<TodoItem> all() {
+        return items.to_immutable_buffer();
+    }
+    
+    public int count() {
+        return (int)items.to_immutable_buffer().count();
+    }
+    
+    public int completed_count() {
+        return (int)items.to_immutable_buffer()
+            .count(item => item.completed);
+    }
+}
+
+// Main application with todo store
+TodoStore todo_store;
+
+/**
+ * EmptyListComponent - Shown when no items exist.
+ */
+class EmptyListComponent : Component {
+    public override string markup { get {
+        return """
+        <div class="empty">
+            No tasks yet! Add one below.
+        </div>
+        """;
+    }}
+}
+
+/**
+ * TodoItemComponent - A single todo item.
+ */
+class TodoItemComponent : Component {
+    public override string markup { get {
+        return """
+        <div class="todo-item">
+            <form method="POST" action="/toggle" style="display: inline;">
+                <input type="hidden" name="id" id="toggle-id"/>
+                <button type="submit" class="btn-toggle" id="toggle-btn"></button>
+            </form>
+            <span class="title" id="title"></span>
+            <form method="POST" action="/delete" style="display: inline;">
+                <input type="hidden" name="id" id="delete-id"/>
+                <button type="submit" class="btn-delete">Delete</button>
+            </form>
+        </div>
+        """;
+    }}
+    
+    public int item_id { set {
+        instance.get_element_by_id("toggle-id").set_attribute("value", value.to_string());
+        instance.get_element_by_id("delete-id").set_attribute("value", value.to_string());
+    }}
+    
+    public string title { set {
+        instance.get_element_by_id("title").text_content = value;
+    }}
+    
+    public bool completed { set {
+        var btn = instance.get_element_by_id("toggle-btn");
+        btn.text_content = value ? "Undo" : "Done";
+    }}
+}
+
+/**
+ * TodoListComponent - Container for todo items.
+ */
+class TodoListComponent : Component {
+    
+    public void set_items(Enumerable<Component> items) {
+        set_outlet_children("items", items);
+    }
+    
+    public override string markup { get {
+        return """
+        <div class="card">
+            <h2>Todo List</h2>
+            <spry-outlet id="items"/>
+        </div>
+        """;
+    }}
+}
+
+/**
+ * HeaderComponent - Page header with stats.
+ */
+class HeaderComponent : Component {
+    public override string markup { get {
+        return """
+        <div class="card">
+            <h1>Todo Component Example</h1>
+            <p>This page demonstrates <code>Spry.Component</code> for building dynamic pages.</p>
+            <div class="stats">
+                <div class="stat">
+                    <div class="stat-value" id="total"></div>
+                    <div class="stat-label">Total Tasks</div>
+                </div>
+                <div class="stat">
+                    <div class="stat-value" id="completed"></div>
+                    <div class="stat-label">Completed</div>
+                </div>
+            </div>
+        </div>
+        """;
+    }}
+    
+    public int total_tasks { set {
+        instance.get_element_by_id("total").text_content = value.to_string();
+    }}
+    
+    public int completed_tasks { set {
+        instance.get_element_by_id("completed").text_content = value.to_string();
+    }}
+}
+
+/**
+ * AddFormComponent - Form to add new todos.
+ */
+class AddFormComponent : Component {
+    public override string markup { get {
+        return """
+        <div class="card">
+            <h2>Add New Task</h2>
+            <form method="POST" action="/add" style="display: flex; gap: 10px;">
+                <input type="text" name="title" placeholder="Enter a new task..." required="required"/>
+                <button type="submit" class="btn-primary">Add Task</button>
+            </form>
+        </div>
+        """;
+    }}
+}
+
+/**
+ * FeaturesComponent - Shows Component features used.
+ */
+class FeaturesComponent : Component {
+    public override string markup { get {
+        return """
+        <div class="card">
+            <h2>Component Features Used</h2>
+            <div class="feature">
+                <strong>Defining Components:</strong>
+                <code>class MyComponent : Component { public override string markup { get { return "..."; } } }</code>
+            </div>
+            <div class="feature">
+                <strong>Using Outlets:</strong>
+                <code><spry-outlet id="content"/></code>
+            </div>
+            <div class="feature">
+                <strong>Setting Outlet Content:</strong>
+                <code>set_outlet_child("content", child);</code>
+            </div>
+            <div class="feature">
+                <strong>Returning Results:</strong>
+                <code>return component.to_result();</code>
+            </div>
+        </div>
+        """;
+    }}
+}
+
+/**
+ * FooterComponent - Page footer.
+ */
+class FooterComponent : Component {
+    public override string markup { get {
+        return """
+        <div class="card" style="text-align: center; color: #666;">
+            <p>Built with <code>Spry.Component</code> |
+                <a href="/api/todos">View JSON API</a>
+            </p>
+        </div>
+        """;
+    }}
+}
+
+/**
+ * PageLayoutComponent - The main page structure.
+ */
+class PageLayoutComponent : Component {
+    
+    public void set_header(Component component) {
+        set_outlet_child("header", component);
+    }
+    
+    public void set_todo_list(Component component) {
+        set_outlet_child("todo-list", component);
+    }
+    
+    public void set_add_form(Component component) {
+        set_outlet_child("add-form", component);
+    }
+    
+    public void set_features(Component component) {
+        set_outlet_child("features", component);
+    }
+    
+    public void set_footer(Component component) {
+        set_outlet_child("footer", component);
+    }
+    
+    public override string markup { get {
+        return """
+        <!DOCTYPE html>
+        <html>
+        <head>
+            <meta charset="UTF-8"/>
+            <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+            <title>Todo Component Example</title>
+            <style>
+                body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background: #f5f5f5; }
+                .card { background: white; border-radius: 8px; padding: 20px; margin: 10px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
+                h1 { color: #333; margin-top: 0; }
+                h2 { color: #555; border-bottom: 2px solid #4CAF50; padding-bottom: 10px; }
+                .todo-item { display: flex; align-items: center; padding: 10px; margin: 5px 0; background: #fafafa; border-radius: 4px; border-left: 4px solid #4CAF50; }
+                .todo-item.completed { border-left-color: #ccc; opacity: 0.7; }
+                .todo-item.completed .title { text-decoration: line-through; color: #888; }
+                .todo-item .title { flex: 1; margin: 0 10px; }
+                .todo-item form { margin: 0; }
+                .stats { display: flex; gap: 20px; margin: 10px 0; }
+                .stat { background: #e8f5e9; padding: 10px 20px; border-radius: 4px; }
+                .stat-value { font-size: 24px; font-weight: bold; color: #4CAF50; }
+                .stat-label { font-size: 12px; color: #666; }
+                input[type="text"] { padding: 10px; border: 1px solid #ddd; border-radius: 4px; flex: 1; }
+                button { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
+                .btn-primary { background: #4CAF50; color: white; }
+                .btn-primary:hover { background: #45a049; }
+                .btn-toggle { background: #2196F3; color: white; padding: 5px 10px; font-size: 12px; }
+                .btn-delete { background: #f44336; color: white; padding: 5px 10px; font-size: 12px; }
+                code { background: #e8e8e8; padding: 2px 6px; border-radius: 4px; font-size: 14px; }
+                pre { background: #263238; color: #aed581; padding: 15px; border-radius: 4px; overflow-x: auto; }
+                .feature { margin: 15px 0; }
+                .feature code { display: block; background: #f5f5f5; padding: 10px; margin: 5px 0; }
+                a { color: #2196F3; text-decoration: none; }
+                a:hover { text-decoration: underline; }
+                .empty { color: #999; font-style: italic; padding: 20px; text-align: center; }
+            </style>
+        </head>
+        <body>
+            <spry-outlet id="header"/>
+            <spry-outlet id="todo-list"/>
+            <spry-outlet id="add-form"/>
+            <spry-outlet id="features"/>
+            <spry-outlet id="footer"/>
+        </body>
+        </html>
+        """;
+    }}
+}
+
+// Home page endpoint - builds the component tree
+class HomePageEndpoint : Object, Endpoint {
+    public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
+        // Create the page layout
+        var page = new PageLayoutComponent();
+        
+        // Create header with stats
+        var header = new HeaderComponent();
+        header.total_tasks = todo_store.count();
+        header.completed_tasks = todo_store.completed_count();
+        page.set_header(header);
+        
+        // Create todo list
+        var todo_list = new TodoListComponent();
+        
+        var count = todo_store.count();
+        if (count == 0) {
+            todo_list.set_items(Iterate.single<Component>(new EmptyListComponent()));
+        } else {
+            var items = new Series<Component>();
+            todo_store.all().iterate((item) => {
+                var component = new TodoItemComponent();
+                component.item_id = item.id;
+                component.title = item.title;
+                component.completed = item.completed;
+                items.add(component);
+            });
+            todo_list.set_items(items);
+        }
+        page.set_todo_list(todo_list);
+        
+        // Add form
+        page.set_add_form(new AddFormComponent());
+        
+        // Features
+        page.set_features(new FeaturesComponent());
+        
+        // Footer
+        page.set_footer(new FooterComponent());
+        
+        // to_result() handles all outlet replacement automatically
+        return page.to_result();
+    }
+}
+
+// Add todo endpoint
+class AddTodoEndpoint : Object, Endpoint {
+    public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
+        // Parse form data
+        FormData form_data = yield FormDataParser.parse(
+            context.request.request_body,
+            context.request.content_type
+        );
+        
+        var title = form_data.get_field("title");
+        
+        if (title != null && title.strip() != "") {
+            todo_store.add(title.strip());
+        }
+        
+        // Redirect back to home (302 Found)
+        return new HttpStringResult("", (StatusCode)302)
+            .set_header("Location", "/");
+    }
+}
+
+// Toggle todo endpoint
+class ToggleTodoEndpoint : Object, Endpoint {
+    public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
+        FormData form_data = yield FormDataParser.parse(
+            context.request.request_body,
+            context.request.content_type
+        );
+        
+        var id_str = form_data.get_field("id");
+        if (id_str != null) {
+            var id = int.parse(id_str);
+            todo_store.toggle(id);
+        }
+        
+        return new HttpStringResult("", (StatusCode)302)
+            .set_header("Location", "/");
+    }
+}
+
+// Delete todo endpoint
+class DeleteTodoEndpoint : Object, Endpoint {
+    public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
+        FormData form_data = yield FormDataParser.parse(
+            context.request.request_body,
+            context.request.content_type
+        );
+        
+        var id_str = form_data.get_field("id");
+        if (id_str != null) {
+            var id = int.parse(id_str);
+            todo_store.remove(id);
+        }
+        
+        return new HttpStringResult("", (StatusCode)302)
+            .set_header("Location", "/");
+    }
+}
+
+// API endpoint that returns JSON representation of todos
+class TodoJsonEndpoint : Object, Endpoint {
+    public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
+        var json_parts = new Series<string>();
+        json_parts.add("{\"todos\": [");
+        
+        bool first = true;
+        todo_store.all().iterate((item) => {
+            if (!first) json_parts.add(",");
+            var completed_str = item.completed ? "true" : "false";
+            var escaped_title = item.title.replace("\"", "\\\"");
+            json_parts.add(@"{\"id\":$(item.id),\"title\":\"$escaped_title\",\"completed\":$completed_str}");
+            first = false;
+        });
+        
+        json_parts.add("]}");
+        
+        var json = json_parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new HttpStringResult(json)
+            .set_header("Content-Type", "application/json");
+    }
+}
+
+void main(string[] args) {
+    int port = args.length > 1 ? int.parse(args[1]) : 8080;
+    
+    // Initialize the todo store
+    todo_store = new TodoStore();
+    
+    print("═══════════════════════════════════════════════════════════════\n");
+    print("             Spry TodoComponent Example\n");
+    print("═══════════════════════════════════════════════════════════════\n");
+    print("  Port: %d\n", port);
+    print("═══════════════════════════════════════════════════════════════\n");
+    print("  Endpoints:\n");
+    print("    /           - Todo list (Component-based)\n");
+    print("    /add        - Add a todo item (POST)\n");
+    print("    /toggle     - Toggle todo completion (POST)\n");
+    print("    /delete     - Delete a todo item (POST)\n");
+    print("    /api/todos  - JSON API for todos\n");
+    print("═══════════════════════════════════════════════════════════════\n");
+    print("\nPress Ctrl+C to stop the server\n\n");
+    
+    try {
+        var application = new WebApplication(port);
+        
+        // Register compression components (optional, for better performance)
+        application.use_compression();
+        
+        // Register endpoints
+        application.add_endpoint<HomePageEndpoint>(new EndpointRoute("/"));
+        application.add_endpoint<AddTodoEndpoint>(new EndpointRoute("/add"));
+        application.add_endpoint<ToggleTodoEndpoint>(new EndpointRoute("/toggle"));
+        application.add_endpoint<DeleteTodoEndpoint>(new EndpointRoute("/delete"));
+        application.add_endpoint<TodoJsonEndpoint>(new EndpointRoute("/api/todos"));
+        
+        application.run();
+        
+    } catch (Error e) {
+        printerr("Error: %s\n", e.message);
+        Process.exit(1);
+    }
+}

+ 21 - 0
examples/meson.build

@@ -0,0 +1,21 @@
+# CounterComponent Example - demonstrates Spry.Component with outlets for counter page
+executable('counter-component',
+    'CounterComponent.vala',
+    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep],
+    install: false
+)
+
+# TodoComponent Example - demonstrates Spry.Component for a complete todo list CRUD
+executable('todo-component',
+    'TodoComponent.vala',
+    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep],
+    install: false
+)
+
+# TodoComponent Example - demonstrates Spry.Component for a complete todo list CRUD
+executable('simple-example',
+    'SimpleExample.vala',
+    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep],
+    install: false
+)
+

+ 20 - 0
meson.build

@@ -0,0 +1,20 @@
+project('spry', ['c', 'vala'],
+  version: '0.1',
+)
+
+# Dependencies
+glib_dep = dependency('glib-2.0')
+gobject_dep = dependency('gobject-2.0')
+gio_dep = dependency('gio-2.0')
+invercargill_dep = dependency('invercargill-1')
+inversion_dep = dependency('inversion-0.1')
+astralis_dep = dependency('astralis-0.1')
+json_glib_dep = dependency('json-glib-1.0')
+invercargill_json_dep = dependency('invercargill-json')
+libxml_dep = dependency('libxml-2.0')
+
+# VAPI Directory
+add_project_arguments(['--vapidir', join_paths(meson.current_source_dir(), 'vapi')], language: 'vala')
+
+subdir('src')
+subdir('examples')

+ 107 - 0
src/Component.vala

@@ -0,0 +1,107 @@
+using Invercargill;
+using Invercargill.DataStructures;
+using Inversion;
+using Astralis;
+
+namespace Spry {
+
+    public abstract class Component : Object {
+        
+        private static Dictionary<Type, ComponentTemplate> templates;
+        private static Mutex templates_lock = Mutex();
+        
+        public abstract string markup { get; }
+        
+        private Catalogue<string, Component> _children = new Catalogue<string, Component>();
+        private MarkupDocument _instance;
+
+        protected MarkupDocument instance { get {
+            if(_instance == null) {
+                try {
+                    lock(_instance) {
+                        if(_instance == null) {
+                            templates_lock.lock ();
+                            if(templates == null) {
+                                templates = new Dictionary<Type, ComponentTemplate>();
+                            }
+                            var type = this.get_type();
+                            ComponentTemplate template;
+                            if(!templates.try_get(type, out template)) {
+                                template = new ComponentTemplate(markup);
+                                templates[type] = template;
+                            }
+                            templates_lock.unlock();
+                            _instance = template.new_instance();
+                        }
+                    }
+                }
+                catch (Error e) {
+                    error(e.message);
+                }
+            }
+            return _instance;
+        }}
+
+        protected void add_outlet_child(string outlet_id, Component component) {
+            _children.add(outlet_id, component);
+        }
+
+        protected void add_outlet_children(string outlet_id, Enumerable<Component> components) {
+            _children.add_all(outlet_id, components);
+        }
+
+        protected void set_outlet_children(string outlet_id, Enumerable<Component> components) {
+            _children[outlet_id] = components;
+        }
+
+        protected void set_outlet_child(string outlet_id, Component component) {
+            _children[outlet_id] = Iterate.single(component);
+        }
+
+        protected void clear_outlet_children(string outlet_id) {
+            _children.clear_key(outlet_id);
+        }
+
+        public MarkupDocument to_document() throws Error {
+            var final_instance = instance.copy();
+
+            // Replace outlets
+            var outlets = final_instance.select("//spry-outlet");
+            foreach (var outlet in outlets) {
+                print(@"loop $(outlet.id)\n");
+                var nodes = _children.get_or_empty(outlet.id)
+                    .attempt_select<MarkupDocument>(c => c.to_document())
+                    .to_series() // To series first so we don't delete the document (which deletes the children internally)
+                    .select_many<MarkupNode>(d => d.body.children);
+
+                print("replace\n");
+                outlet.replace_with_nodes(nodes);
+            }
+
+            // Remove hidden blocks
+            final_instance.select("//*[@spry-hidden]")
+                .iterate(n => n.remove());
+
+            // Replace control blocks with their children
+            final_instance.select("//spry-control")
+                .iterate(n => n.replace_with_nodes(n.children));
+
+            return final_instance;
+        }
+
+        public HttpResult to_result(StatusCode status = StatusCode.OK) throws Error {
+            return to_document().to_result(status);
+        }
+
+        private class ComponentTemplate : MarkupTemplate {
+            private string _markup;
+            protected override string markup { get { return _markup; } }
+
+            public ComponentTemplate(string markup) {
+                this._markup = markup;
+            }
+        }
+
+    }
+
+}

+ 13 - 0
src/ComponentEndpoint.vala

@@ -0,0 +1,13 @@
+
+using Invercargill;
+using Invercargill.DataStructures;
+using Inversion;
+using Astralis;
+
+namespace Spry {
+
+    //  public abstract class ComponentEndpoint : Object, Endpoint {
+        
+    //  }
+
+}

+ 12 - 0
src/Context.vala

@@ -0,0 +1,12 @@
+using Astralis;
+using Invercargill.DataStructures;
+namespace Spry {
+
+    //  public class SpryContext {
+
+    //      public MarkupDocument document { get; private set; }
+    //      public bool is_fragment { get; private set; }
+
+    //  }
+
+}

+ 28 - 0
src/StyleProvider.vala

@@ -0,0 +1,28 @@
+using Invercargill;
+namespace Spry {
+
+    public interface StyleProvider : Object {
+
+        public abstract string? get_css();
+
+    }
+
+    public interface CssClassProvider : Object {
+
+        public abstract string fg_primary { get; }
+        public abstract string fg_secondary { get; }
+        public abstract string fg_tertiary { get; }
+        public abstract string fg_success { get; }
+        public abstract string fg_caution { get; }
+        public abstract string fg_danger { get; }
+
+        public abstract string bg_primary { get; }
+        public abstract string bg_secondary { get; }
+        public abstract string bg_tertiary { get; }
+        public abstract string bg_success { get; }
+        public abstract string bg_caution { get; }
+        public abstract string bg_danger { get; }
+
+    }
+
+}

+ 36 - 0
src/meson.build

@@ -0,0 +1,36 @@
+sources = files(
+    'Component.vala',
+    'ComponentEndpoint.vala',
+    'Context.vala',
+    'StyleProvider.vala',
+)
+
+
+library_version = meson.project_version()
+libspry = shared_library('spry-@0@'.format(library_version),
+    sources,
+    dependencies: [glib_dep, gobject_dep, gio_dep, invercargill_dep, invercargill_json_dep, json_glib_dep, inversion_dep, libxml_dep, astralis_dep],
+    install: true,
+    vala_gir: 'spry-@0@.gir'.format(library_version),
+    install_dir: [true, true, true, true]
+)
+
+
+pkg = import('pkgconfig')
+pkg.generate(libspry,
+    version : library_version,
+    name : 'spry-@0@'.format(library_version))
+    
+g_ir_compiler = find_program('g-ir-compiler')
+custom_target('spry typelib', command: [g_ir_compiler, '--shared-library=libspry-@0@.so'.format(library_version), '--output', '@OUTPUT@', meson.current_build_dir() / 'spry-@0@.gir'.format(library_version)],
+              output: 'libspry-@0@.typelib'.format(library_version),
+              depends: libspry,
+              install: true,
+              install_dir: get_option('libdir') / 'girepository-1.0')
+
+
+spry_dep = declare_dependency(
+    link_with: libspry,
+    include_directories: include_directories('.'),
+    dependencies: [glib_dep, gobject_dep, gio_dep, invercargill_dep, invercargill_json_dep, json_glib_dep, inversion_dep, libxml_dep]
+)

+ 135 - 0
vapi/libbrotlienc.vapi

@@ -0,0 +1,135 @@
+/* libbrotlienc Vala bindings
+ * 
+ * Bindings for the Brotli compression library (encoder only)
+ * Based on brotli encode.h and types.h
+ */
+
+[CCode (cprefix = "BROTLI_", lower_case_cprefix = "brotli_", cheader_filename = "brotli/encode.h,brotli/types.h")]
+namespace Brotli {
+
+    /* Boolean type */
+    [CCode (cname = "BROTLI_BOOL", has_type_id = false, default_value = "BROTLI_FALSE")]
+    public struct Bool : int {
+    }
+    
+    [CCode (cname = "BROTLI_TRUE")]
+    public const Bool TRUE;
+    [CCode (cname = "BROTLI_FALSE")]
+    public const Bool FALSE;
+    
+    [CCode (cname = "TO_BROTLI_BOOL")]
+    public Bool to_bool(int x);
+
+    /* Memory allocation callbacks */
+    [CCode (cname = "brotli_alloc_func", has_target = false)]
+    public delegate void* AllocFunc(void* opaque, size_t size);
+    
+    [CCode (cname = "brotli_free_func", has_target = false)]
+    public delegate void FreeFunc(void* opaque, void* address);
+
+    /* Constants from encode.h */
+    [CCode (cname = "BROTLI_MIN_WINDOW_BITS")]
+    public const int MIN_WINDOW_BITS;
+    [CCode (cname = "BROTLI_MAX_WINDOW_BITS")]
+    public const int MAX_WINDOW_BITS;
+    [CCode (cname = "BROTLI_LARGE_MAX_WINDOW_BITS")]
+    public const int LARGE_MAX_WINDOW_BITS;
+    [CCode (cname = "BROTLI_MIN_INPUT_BLOCK_BITS")]
+    public const int MIN_INPUT_BLOCK_BITS;
+    [CCode (cname = "BROTLI_MAX_INPUT_BLOCK_BITS")]
+    public const int MAX_INPUT_BLOCK_BITS;
+    [CCode (cname = "BROTLI_MIN_QUALITY")]
+    public const int MIN_QUALITY;
+    [CCode (cname = "BROTLI_MAX_QUALITY")]
+    public const int MAX_QUALITY;
+    [CCode (cname = "BROTLI_DEFAULT_QUALITY")]
+    public const int DEFAULT_QUALITY;
+    [CCode (cname = "BROTLI_DEFAULT_WINDOW")]
+    public const int DEFAULT_WINDOW;
+
+    /* Encoder mode */
+    [CCode (cname = "BrotliEncoderMode", has_type_id = false)]
+    public enum EncoderMode {
+        [CCode (cname = "BROTLI_MODE_GENERIC")]
+        GENERIC,
+        [CCode (cname = "BROTLI_MODE_TEXT")]
+        TEXT,
+        [CCode (cname = "BROTLI_MODE_FONT")]
+        FONT
+    }
+
+    /* Encoder operation for streaming */
+    [CCode (cname = "BrotliEncoderOperation", has_type_id = false)]
+    public enum EncoderOperation {
+        [CCode (cname = "BROTLI_OPERATION_PROCESS")]
+        PROCESS,
+        [CCode (cname = "BROTLI_OPERATION_FLUSH")]
+        FLUSH,
+        [CCode (cname = "BROTLI_OPERATION_FINISH")]
+        FINISH,
+        [CCode (cname = "BROTLI_OPERATION_EMIT_METADATA")]
+        EMIT_METADATA
+    }
+
+    /* Encoder parameters */
+    [CCode (cname = "BrotliEncoderParameter", has_type_id = false)]
+    public enum EncoderParameter {
+        [CCode (cname = "BROTLI_PARAM_MODE")]
+        MODE,
+        [CCode (cname = "BROTLI_PARAM_QUALITY")]
+        QUALITY,
+        [CCode (cname = "BROTLI_PARAM_LGWIN")]
+        LGWIN,
+        [CCode (cname = "BROTLI_PARAM_LGBLOCK")]
+        LGBLOCK,
+        [CCode (cname = "BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING")]
+        DISABLE_LITERAL_CONTEXT_MODELING,
+        [CCode (cname = "BROTLI_PARAM_SIZE_HINT")]
+        SIZE_HINT,
+        [CCode (cname = "BROTLI_PARAM_LARGE_WINDOW")]
+        LARGE_WINDOW,
+        [CCode (cname = "BROTLI_PARAM_NPOSTFIX")]
+        NPOSTFIX,
+        [CCode (cname = "BROTLI_PARAM_NDIRECT")]
+        NDIRECT,
+        [CCode (cname = "BROTLI_PARAM_STREAM_OFFSET")]
+        STREAM_OFFSET
+    }
+
+    /* Encoder state - opaque structure */
+    [CCode (cname = "BrotliEncoderState", free_function = "BrotliEncoderDestroyInstance")]
+    [Compact]
+    public class EncoderState {
+        [CCode (cname = "BrotliEncoderCreateInstance")]
+        public EncoderState(AllocFunc? alloc_func, FreeFunc? free_func, void* opaque);
+        
+        [CCode (cname = "BrotliEncoderSetParameter")]
+        public Bool set_parameter(EncoderParameter param, uint32 value);
+        
+        [CCode (cname = "BrotliEncoderCompressStream")]
+        public Bool compress_stream(EncoderOperation op, ref size_t available_in, 
+            ref uint8* next_in, ref size_t available_out, ref uint8* next_out, 
+            out size_t total_out);
+        
+        [CCode (cname = "BrotliEncoderIsFinished")]
+        public Bool is_finished();
+        
+        [CCode (cname = "BrotliEncoderHasMoreOutput")]
+        public Bool has_more_output();
+        
+        [CCode (cname = "BrotliEncoderTakeOutput")]
+        public unowned uint8* take_output(ref size_t size);
+    }
+
+    /* Encoder one-shot functions */
+    [CCode (cname = "BrotliEncoderMaxCompressedSize")]
+    public size_t encoder_max_compressed_size(size_t input_size);
+    
+    [CCode (cname = "BrotliEncoderCompress")]
+    public Bool encoder_compress(int quality, int lgwin, EncoderMode mode, 
+        size_t input_size, uint8* input_buffer, 
+        ref size_t encoded_size, uint8* encoded_buffer);
+    
+    [CCode (cname = "BrotliEncoderVersion")]
+    public uint32 encoder_version();
+}

+ 159 - 0
vapi/libmicrohttpd.vapi

@@ -0,0 +1,159 @@
+[CCode (cheader_filename = "microhttpd.h")]
+namespace MHD {
+    [CCode (cname = "enum MHD_ValueKind", cprefix = "MHD_")]
+    public enum ValueKind {
+        RESPONSE_HEADER_KIND,
+        HEADER_KIND,
+        COOKIE_KIND,
+        POSTDATA_KIND,
+        GET_ARGUMENT_KIND,
+        FOOTER_KIND
+    }
+
+    [CCode (cname = "enum MHD_RequestTerminationCode", cprefix = "MHD_REQUEST_TERMINATED_")]
+    public enum RequestTerminationCode {
+        COMPLETED_OK,
+        WITH_ERROR,
+        TIMEOUT_REACHED,
+        DAEMON_SHUTDOWN,
+        READ_ERROR,
+        CLIENT_ABORT
+    }
+
+    [CCode (cname = "enum MHD_ResponseMemoryMode", cprefix = "MHD_")]
+    public enum ResponseMemoryMode {
+        RESPMEM_PERSISTENT,
+        RESPMEM_MUST_FREE,
+        RESPMEM_MUST_COPY
+    }
+
+    [CCode (cname = "MHD_CONTENT_READER_END_OF_STREAM")]
+    public const ssize_t CONTENT_READER_END_OF_STREAM;
+    [CCode (cname = "MHD_CONTENT_READER_END_WITH_ERROR")]
+    public const ssize_t CONTENT_READER_END_WITH_ERROR;
+
+    [CCode (cname = "MHD_USE_SELECT_INTERNALLY")]
+    public const uint USE_SELECT_INTERNALLY;
+    [CCode (cname = "MHD_USE_THREAD_PER_CONNECTION")]
+    public const uint USE_THREAD_PER_CONNECTION;
+    [CCode (cname = "MHD_USE_DEBUG")]
+    public const uint USE_DEBUG;
+    [CCode (cname = "MHD_ALLOW_SUSPEND_RESUME")]
+    public const uint ALLOW_SUSPEND_RESUME;
+
+    [CCode (cname = "MHD_OPTION_END")]
+    public const int OPTION_END;
+
+    [SimpleType]
+    [CCode (cname = "struct MHD_Connection*")]
+    public struct Connection {}
+
+    [CCode (cname = "int", cprefix = "MHD_")]
+    public enum Result {
+        YES = 1,
+        NO = 0
+    }
+
+    [Compact]
+    [CCode (cname = "struct MHD_Response", free_function = "MHD_destroy_response")]
+    public class Response {
+        [CCode (cname = "MHD_create_response_from_buffer")]
+        public Response.from_buffer (size_t size, [CCode(array_length=false)] uint8[] buffer, ResponseMemoryMode mode);
+
+        [CCode (cname = "MHD_create_response_from_callback")]
+        public Response.from_callback (uint64 size, size_t block_size, ContentReaderCallback crc, void* crc_cls, ContentReaderFreeCallback? crfc);
+
+        [CCode (cname = "MHD_add_response_header")]
+        public int add_header (string header, string content);
+    }
+
+    [Compact]
+    [CCode (cname = "struct MHD_Daemon", free_function = "MHD_stop_daemon")]
+    public class Daemon {
+        [CCode (cname = "MHD_start_daemon")]
+        public static Daemon start (uint flags, uint16 port, 
+            AcceptPolicyCallback? apc, 
+            AccessHandlerCallback dh, 
+            ...);
+    }
+
+    [CCode (instance_pos = 0)]
+    public delegate int AcceptPolicyCallback (void* cls, void* addr, uint addrlen);
+
+    [CCode (instance_pos = 0)]
+    public delegate int AccessHandlerCallback (Connection connection, 
+        string? url, string? method, string? version, 
+        string? upload_data, [CCode(array_length=false)] size_t* upload_data_size, 
+        void** con_cls);
+
+    [CCode (has_target = false)]
+    public delegate ssize_t ContentReaderCallback (void* cls, uint64 pos, char* buf, size_t max);
+
+    [CCode (has_target = false, cname = "MHD_ContentReaderFreeCallback")]
+    public delegate void ContentReaderFreeCallback (void* cls);
+    
+    [CCode (cname = "MHD_queue_response")]
+    public int queue_response (Connection connection, uint status_code, Response response);
+
+    [CCode (cname = "MHD_suspend_connection")]
+    public int suspend_connection (Connection connection);
+
+    [CCode (cname = "MHD_resume_connection")]
+    public int resume_connection (Connection connection);
+
+    [CCode (cname = "MHD_lookup_connection_value")]
+    public unowned string? lookup_connection_value (Connection connection, ValueKind kind, string key);
+
+    [CCode (has_target = false)]
+    public delegate Result KeyValueIterator (void* cls, ValueKind kind, string key, string? value);
+
+    [CCode (cname = "MHD_get_connection_values")]
+    public int get_connection_values (Connection connection, ValueKind kind, KeyValueIterator? iterator, void* iterator_cls = null);
+
+    [CCode (cname = "enum MHD_ConnectionInfoType", cprefix = "MHD_CONNECTION_INFO_")]
+    public enum ConnectionInfoType {
+        CIPHER_ALGO,
+        PROTOCOL,
+        CLIENT_ADDRESS,
+        GNUTLS_SESSION,
+        GNUTLS_CLIENT_CERT,
+        DAEMON,
+        CONNECTION_FD,
+        SOCKET_CONTEXT,
+        CONNECTION_SUSPENDED,
+        CONNECTION_TIMEOUT,
+        REQUEST_HEADER_SIZE,
+        HTTP_STATUS
+    }
+
+    [CCode (cname = "union MHD_ConnectionInfo", has_type_id = false)]
+    public struct ConnectionInfo {
+        [CCode (cname = "cipher_algorithm")]
+        public int cipher_algorithm;
+        [CCode (cname = "protocol")]
+        public int protocol;
+        [CCode (cname = "client_cert")]
+        public void* client_cert;
+        [CCode (cname = "tls_session")]
+        public void* tls_session;
+        [CCode (cname = "daemon")]
+        public Daemon* daemon;
+        [CCode (cname = "connect_fd")]
+        public int connect_fd;
+        [CCode (cname = "socket_context")]
+        public void* socket_context;
+        [CCode (cname = "suspended")]
+        public int suspended;
+        [CCode (cname = "connection_timeout")]
+        public uint connection_timeout;
+        [CCode (cname = "client_addr")]
+        public void* client_addr;
+        [CCode (cname = "header_size")]
+        public size_t header_size;
+        [CCode (cname = "http_status")]
+        public uint http_status;
+    }
+
+    [CCode (cname = "MHD_get_connection_info")]
+    public unowned ConnectionInfo? get_connection_info (Connection connection, ConnectionInfoType info_type);
+}

+ 286 - 0
vapi/libzstd.vapi

@@ -0,0 +1,286 @@
+/* libzstd.vapi - Vala bindings for Zstandard compression library
+ * 
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * Vala bindings generated for use with the spry framework.
+ * 
+ * Based on zstd.h from libzstd 1.5.7
+ */
+
+[CCode (cprefix = "ZSTD_", lower_case_cprefix = "ZSTD_", cheader_filename = "zstd.h,zstd_errors.h")]
+namespace Zstd {
+    
+    /* Version */
+    [CCode (cname = "ZSTD_VERSION_MAJOR")]
+    public const int VERSION_MAJOR;
+    [CCode (cname = "ZSTD_VERSION_MINOR")]
+    public const int VERSION_MINOR;
+    [CCode (cname = "ZSTD_VERSION_RELEASE")]
+    public const int VERSION_RELEASE;
+    [CCode (cname = "ZSTD_VERSION_NUMBER")]
+    public const int VERSION_NUMBER;
+    
+    [CCode (cname = "ZSTD_versionNumber")]
+    public uint version_number();
+    [CCode (cname = "ZSTD_versionString")]
+    public unowned string version_string();
+    
+    /* Default compression level */
+    [CCode (cname = "ZSTD_CLEVEL_DEFAULT")]
+    public const int CLEVEL_DEFAULT;
+    
+    /* Block size constants */
+    [CCode (cname = "ZSTD_BLOCKSIZELOG_MAX")]
+    public const int BLOCKSIZELOG_MAX;
+    [CCode (cname = "ZSTD_BLOCKSIZE_MAX")]
+    public const int BLOCKSIZE_MAX;
+    
+    /* Content size constants */
+    [CCode (cname = "ZSTD_CONTENTSIZE_UNKNOWN")]
+    public const uint64 CONTENTSIZE_UNKNOWN;
+    [CCode (cname = "ZSTD_CONTENTSIZE_ERROR")]
+    public const uint64 CONTENTSIZE_ERROR;
+    
+    /* Compression strategies */
+    [CCode (cname = "ZSTD_strategy", cprefix = "ZSTD_", has_type_id = false)]
+    public enum Strategy {
+        fast = 1,
+        dfast = 2,
+        greedy = 3,
+        lazy = 4,
+        lazy2 = 5,
+        btlazy2 = 6,
+        btopt = 7,
+        btultra = 8,
+        btultra2 = 9
+    }
+    
+    /* End directive for streaming */
+    [CCode (cname = "ZSTD_EndDirective", cprefix = "ZSTD_e_", has_type_id = false)]
+    public enum EndDirective {
+        continue = 0,
+        flush = 1,
+        end = 2
+    }
+    
+    /* Reset directive */
+    [CCode (cname = "ZSTD_ResetDirective", cprefix = "ZSTD_reset_", has_type_id = false)]
+    public enum ResetDirective {
+        session_only = 1,
+        parameters = 2,
+        session_and_parameters = 3
+    }
+    
+    /* Error codes */
+    [CCode (cname = "ZSTD_ErrorCode", cprefix = "ZSTD_error_", has_type_id = false)]
+    public enum ErrorCode {
+        no_error = 0,
+        GENERIC = 1,
+        prefix_unknown = 10,
+        version_unsupported = 12,
+        frameParameter_unsupported = 14,
+        frameParameter_windowTooLarge = 16,
+        corruption_detected = 20,
+        checksum_wrong = 22,
+        literals_headerWrong = 24,
+        dictionary_corrupted = 30,
+        dictionary_wrong = 32,
+        dictionaryCreation_failed = 34,
+        parameter_unsupported = 40,
+        parameter_combination_unsupported = 41,
+        parameter_outOfBound = 42,
+        tableLog_tooLarge = 44,
+        maxSymbolValue_tooLarge = 46,
+        maxSymbolValue_tooSmall = 48,
+        cannotProduce_uncompressedBlock = 49,
+        stabilityCondition_notRespected = 50,
+        stage_wrong = 60,
+        init_missing = 62,
+        memory_allocation = 64,
+        workSpace_tooSmall = 66,
+        dstSize_tooSmall = 70,
+        srcSize_wrong = 72,
+        dstBuffer_null = 74,
+        noForwardProgress_destFull = 80,
+        noForwardProgress_inputEmpty = 82,
+        frameIndex_tooLarge = 100,
+        seekableIO = 102,
+        dstBuffer_wrong = 104,
+        srcBuffer_wrong = 105,
+        sequenceProducer_failed = 106,
+        externalSequences_invalid = 107,
+        maxCode = 120
+    }
+    
+    /* Bounds structure */
+    [CCode (cname = "ZSTD_bounds", has_type_id = false)]
+    public struct Bounds {
+        public size_t error;
+        public int lowerBound;
+        public int upperBound;
+    }
+    
+    /* Input buffer for streaming */
+    [CCode (cname = "ZSTD_inBuffer", has_type_id = false)]
+    public struct InBuffer {
+        public uint8* src;
+        public size_t size;
+        public size_t pos;
+    }
+    
+    /* Output buffer for streaming */
+    [CCode (cname = "ZSTD_outBuffer", has_type_id = false)]
+    public struct OutBuffer {
+        public uint8* dst;
+        public size_t size;
+        public size_t pos;
+    }
+    
+    /* Compression context */
+    [CCode (cname = "ZSTD_CCtx", free_function = "ZSTD_freeCCtx", has_type_id = false)]
+    [Compact]
+    public class CCtx {
+        [CCode (cname = "ZSTD_createCCtx")]
+        public CCtx();
+        
+        [CCode (cname = "ZSTD_compressCCtx")]
+        public size_t compress(uint8* dst, size_t dstCapacity, uint8* src, size_t srcSize, int compressionLevel);
+        
+        [CCode (cname = "ZSTD_compress2")]
+        public size_t compress2(uint8* dst, size_t dstCapacity, uint8* src, size_t srcSize);
+        
+        [CCode (cname = "ZSTD_CCtx_setParameter")]
+        public size_t set_parameter(CParameter param, int value);
+        
+        [CCode (cname = "ZSTD_CCtx_setPledgedSrcSize")]
+        public size_t set_pledged_src_size(uint64 pledgedSrcSize);
+        
+        [CCode (cname = "ZSTD_CCtx_reset")]
+        public size_t reset(ResetDirective reset);
+        
+        [CCode (cname = "ZSTD_compressStream2")]
+        public size_t compress_stream2(OutBuffer* output, InBuffer* input, EndDirective endOp);
+        
+        [CCode (cname = "ZSTD_initCStream")]
+        public size_t init_stream(int compressionLevel);
+        
+        [CCode (cname = "ZSTD_compressStream")]
+        public size_t compress_stream(OutBuffer* output, InBuffer* input);
+        
+        [CCode (cname = "ZSTD_flushStream")]
+        public size_t flush_stream(OutBuffer* output);
+        
+        [CCode (cname = "ZSTD_endStream")]
+        public size_t end_stream(OutBuffer* output);
+    }
+    
+    /* Decompression context */
+    [CCode (cname = "ZSTD_DCtx", free_function = "ZSTD_freeDCtx", has_type_id = false)]
+    [Compact]
+    public class DCtx {
+        [CCode (cname = "ZSTD_createDCtx")]
+        public DCtx();
+        
+        [CCode (cname = "ZSTD_decompressDCtx")]
+        public size_t decompress(uint8* dst, size_t dstCapacity, uint8* src, size_t srcSize);
+        
+        [CCode (cname = "ZSTD_DCtx_setParameter")]
+        public size_t set_parameter(DParameter param, int value);
+        
+        [CCode (cname = "ZSTD_DCtx_reset")]
+        public size_t reset(ResetDirective reset);
+    }
+    
+    /* Compression stream (alias for CCtx) */
+    [CCode (cname = "ZSTD_CStream", free_function = "ZSTD_freeCStream", has_type_id = false)]
+    [Compact]
+    public class CStream {
+        [CCode (cname = "ZSTD_createCStream")]
+        public CStream();
+    }
+    
+    /* Compression parameters */
+    [CCode (cname = "ZSTD_cParameter", cprefix = "ZSTD_c_", has_type_id = false)]
+    public enum CParameter {
+        compressionLevel = 100,
+        windowLog = 101,
+        hashLog = 102,
+        chainLog = 103,
+        searchLog = 104,
+        minMatch = 105,
+        targetLength = 106,
+        strategy = 107,
+        targetCBlockSize = 130,
+        enableLongDistanceMatching = 160,
+        ldmHashLog = 161,
+        ldmMinMatch = 162,
+        ldmBucketSizeLog = 163,
+        ldmHashRateLog = 164,
+        contentSizeFlag = 200,
+        checksumFlag = 201,
+        dictIDFlag = 202,
+        nbWorkers = 400,
+        jobSize = 401,
+        overlapLog = 402
+    }
+    
+    /* Decompression parameters */
+    [CCode (cname = "ZSTD_dParameter", cprefix = "ZSTD_d_", has_type_id = false)]
+    public enum DParameter {
+        windowLogMax = 100
+    }
+    
+    /* Simple API - One-shot compression */
+    [CCode (cname = "ZSTD_compress")]
+    public size_t compress(uint8* dst, size_t dstCapacity, uint8* src, size_t srcSize, int compressionLevel);
+    
+    /* Simple API - One-shot decompression */
+    [CCode (cname = "ZSTD_decompress")]
+    public size_t decompress(uint8* dst, size_t dstCapacity, uint8* src, size_t compressedSize);
+    
+    /* Helper functions */
+    [CCode (cname = "ZSTD_compressBound")]
+    public size_t compress_bound(size_t srcSize);
+    
+    [CCode (cname = "ZSTD_isError")]
+    public uint is_error(size_t result);
+    
+    [CCode (cname = "ZSTD_getErrorCode")]
+    public ErrorCode get_error_code(size_t functionResult);
+    
+    [CCode (cname = "ZSTD_getErrorName")]
+    public unowned string get_error_name(size_t result);
+    
+    [CCode (cname = "ZSTD_minCLevel")]
+    public int min_c_level();
+    
+    [CCode (cname = "ZSTD_maxCLevel")]
+    public int max_c_level();
+    
+    [CCode (cname = "ZSTD_defaultCLevel")]
+    public int default_c_level();
+    
+    /* Content size functions */
+    [CCode (cname = "ZSTD_getFrameContentSize")]
+    public uint64 get_frame_content_size(uint8* src, size_t srcSize);
+    
+    [CCode (cname = "ZSTD_findFrameCompressedSize")]
+    public size_t find_frame_compressed_size(uint8* src, size_t srcSize);
+    
+    /* Streaming buffer size recommendations */
+    [CCode (cname = "ZSTD_CStreamInSize")]
+    public size_t cstream_in_size();
+    
+    [CCode (cname = "ZSTD_CStreamOutSize")]
+    public size_t cstream_out_size();
+    
+    /* Error string from error code */
+    [CCode (cname = "ZSTD_getErrorString")]
+    public unowned string get_error_string(ErrorCode code);
+    
+    /* Parameter bounds */
+    [CCode (cname = "ZSTD_cParam_getBounds")]
+    public Bounds cparam_get_bounds(CParameter cParam);
+    
+    [CCode (cname = "ZSTD_dParam_getBounds")]
+    public Bounds dparam_get_bounds(DParameter dParam);
+}