Manuale di riferimento MALDA™

Il linguaggio di programmazione AI-First - Versione 1.0.11

29. Sviluppo full-stack con MALDA

Questo capitolo mostra come usare MALDA come piattaforma full-stack completa. L'obiettivo è collegare concetti già documentati in capitoli dedicati in un'unica architettura end-to-end. Per starter eseguibili usa malda new webapi, malda new fullstack, malda new game --fullstack (client canvas + API punteggi) e gli esempi in Examples/Web/ (per esempio rest_api_server.malda e ui_controls_showcase_minimal.malda).

29.1 Cosa significa "full-stack" in MALDA

In MALDA un'app full-stack combina in genere:

Questi pezzi corrispondono direttamente a capitoli già presenti nel manuale: generazione Web UI, Server REST API, Client REST Web, Supporto database e Orchestrazione di agenti.

29.1.1 Confine di runtime: MALDA vs JavaScript

Il livello UI può quindi essere renderizzato sul server da MALDA, renderizzato nel client in JavaScript, oppure un ibrido dei due.

29.1.2 Modelli di distribuzione

Sia lo stack nativo MALDA sia quello ibrido sono full-stack, perché includono i livelli UI + API + dati da un capo all'altro.

29.1.3 Decoratori di target a sorgente unica

MALDA supporta ora i decoratori di target, così un unico sorgente .malda può contenere codice solo-server, solo-client e condiviso in modo controllato:

I decoratori di route esistenti (@GET, @POST, @PUT, @DELETE, @PATCH, @OPTIONS, @PAGE, @AIPAGE, @ACTION, @COMPONENT, @LIVE) restano decoratori runtime/web e continuano a implicare la partecipazione del server.

Regole di validazione: combinare @client() con i decoratori di route non è valido, e @shared() dovrebbe usare solo helper sicuri su entrambi i target. Le operazioni specifiche del target, come API dirette su filesystem/database/server oppure helper DOM/local storage del browser, devono restare in dichiarazioni esplicite server/client.

Workflow Desktop IDE: per i file full-stack a sorgente unica, il Desktop IDE può mostrare un .malda fisico come più tab virtuali quando il sorgente lo richiede con separatori espliciti // @malda-section Name. Il salvataggio scrive comunque un unico file fisico, e i file include regolari continuano ad aprirsi e modificarsi come documenti fisici normali. F5 avvia il dual debug: l'interprete sulla partizione host e Web Preview sul client (vedi 2.6.6). Ctrl+F5 offre ancora il dialogo Server / Client preview / Full stack.

29.2 Scenario di esempio: app semplice di item

Un'app di esempio tipica consente di:

Modello dati minimo:

{
  "id": 101,
  "ownerName": "Alice",
  "email": "alice@example.com",
  "title": "Cannot login",
  "description": "Password reset link expired",
  "status": "open",
  "priority": "high",
  "createdAt": "2026-02-14T09:15:00Z"
}

29.3 Struttura di progetto consigliata

/app
  /api
    items-api.malda
  /ui
    dashboard.malda
  /shared
    item-model.malda
  /agents
    triage-agent.malda

Tieni la logica di business in funzioni/classi riutilizzabili, poi esponila sia tramite handler API sia tramite handler UI. Così eviti di duplicare la logica tra i flussi front-end e back-end.

Quando usi i decoratori di target a sorgente unica, puoi anche scegliere una struttura più piatta (per esempio un unico entry app.malda) continuando a separare le responsabilità marcando le dichiarazioni con @server(), @client() e @shared().

29.4 Livello API (CRUD degli item + workflow)

Definisci le route REST per le operazioni sugli item:

function ok(data) {
    return {"status": 200, "data": data};
}

@GET("/api/items")
function getItems(status) {
    // status can come from query string ?status=open
    var items = dbQuery("SELECT * FROM Items WHERE (@status IS NULL OR status = @status)", {"status": status});
    return ok(items);
}

@POST("/api/items")
function createItem(body) {
    var now = dateNow();
    var insert = dbExecute(
        "INSERT INTO Items (ownerName, email, title, description, status, priority, createdAt) VALUES (@ownerName, @email, @title, @description, 'open', 'normal', @createdAt)",
        {
            "ownerName": body.ownerName,
            "email": body.email,
            "title": body.title,
            "description": body.description,
            "createdAt": now
        }
    );
    return {"status": 201, "data": {"id": insert.lastInsertId, "createdAt": now}};
}

@PATCH("/api/items/{id}/status")
function updateItemStatus(id, body) {
    dbExecute("UPDATE Items SET status = @status WHERE id = @id", {"id": id, "status": body.status});
    return ok({"id": id, "status": body.status});
}

Raccomandazione per la produzione: aggiungi validazione, middleware di auth e payload di errore coerenti. Vedi 27. Server REST API.

29.5 Livello UI (quale tecnologia usare)

Ci sono due approcci comuni:

ModelloQuando sceglierlo
Full stack nativo MALDAConsegna rapida, meno parti in movimento, le pagine guidate dal server bastano
Full stack ibridoUX SPA ricca, team frontend numerosi, requisiti forti sull'ecosistema JS

Le opzioni di framework per la UI nel browser includono JavaScript vanilla, React, Vue, Angular, Svelte, Next.js (modalità frontend/consumatore di API), Nuxt, Solid e opzioni leggere come HTMX o Alpine.js. MALDA è agnostico rispetto al backend, purché il frontend possa chiamare API HTTP.

29.5.0 Pattern a componenti nativo MALDA

Per il full stack nativo MALDA, questo fornisce una baseline pragmatica a componenti server:

Come scegliere il trasporto: usa fragment HTTP come default per le azioni avviate dall'utente (form/pulsanti). Aggiungi LIVE/SSE quando la UI deve aggiornarsi senza un'azione dell'utente (notifiche, contatori, code).

Nota: gli endpoint LIVE supportano le sottoscrizioni a canale (per esempio /items/live?channel=items) e componentLiveEmit("items", ...) pubblica solo su quel canale. Lo scaffold malda new fullstack include anche un componente di esempio in /components/TicketBoard per una demo locale rapida.

29.5.0.1 Piccolo esempio di item

component ItemBoard() {
    var items = componentStateGet("board", "items", []);
    return "<h1>Open: " + items.length + "</h1>";
}

@ACTION("/items/add")
function addItem(body) {
    var items = componentStateGet("board", "items", []);
    items.append({"title": body.title, "status": "open"});
    componentStateSet("board", "items", items);
    componentLiveEmit("items", {"count": items.length}, "item-added");
    return componentFragment("item-list", "<ul><li>" + body.title + "</li></ul>");
}

@LIVE("/items/live")
function itemsLive() {
    return {"sse": true};
}

29.5.1 Handler di pagina lato server MALDA

@PAGE("/items")
function itemsPage() {
    return "<html><body><h1>Items</h1></body></html>";
}

29.5.2 JavaScript nel browser che chiama l'API MALDA

async function loadItems() {
  const r = await fetch("/api/items?status=open");
  const json = await r.json();
  return json.data;
}

async function closeItem(id) {
  await fetch(`/api/items/${id}/status`, {
    method: "PATCH",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ status: "closed" })
  });
}

Questa separazione tiene le preoccupazioni della UI concentrate su rendering e interazione, mentre MALDA resta la fonte di verità per la logica di business e l'accesso ai dati.

29.5.3 Percorso di migrazione da @PAGE ai componenti

  1. Tieni stabili gli endpoint @PAGE esistenti (non è richiesta una migrazione breaking).
  2. Estrai la logica di vista in funzioni component.
  3. Aggiungi handler @ACTION per gli aggiornamenti interattivi e restituisci componentFragment(...).
  4. Aggiungi un endpoint @LIVE + componentLiveEmit(...) per le notifiche push.
  5. Adotta il percorso in modo incrementale, pagina per pagina, invece di riscrivere l'intera UI.

Suggerimento di migrazione incrementale: parti da un unico workflow di form (per esempio "create item"), verifica che aggiornamenti dei fragment e gestione dello stato siano chiari al team, poi estendi alle altre pagine.

29.5.4 Esempio UI end-to-end

Per un flusso completo elenco/dettaglio con controlli in stile CRUD, parti da Examples/Web/ui_controls_showcase_minimal.malda, Examples/Web/ui_form_workflow.malda e Examples/Web/rest_api_server.malda, poi componi le pagine con ui.crudControls, @PAGE e @POST. Pattern chiave:

function crudPage(req, schema, pageTitle, lookups) {
    var queryParams = req.query == null ? {} : req.query;
    var sessionId = ui.sessionId(req, schema.sessionDefault);
    var controlsHtml = ui.crudControls(schema, sessionId, queryParams, lookups);
    return controlsHtml;
}

@PAGE("/web/items")
function itemsPage(req) { return crudPage(req, itemSchema, "Items", {}); }
@POST("/web/items/add")
function addItem(req, body, res) {
    var errors = [];
    if (body.title == null || body.title == "") errors.append("Title required");
    if (errors.length > 0) {
        return {"status": 400, "body": "<ul class='error-list'><li>" + errors[0] + "</li></ul>"};
    }
    var id = dbExecute("INSERT INTO items (...) VALUES (...)", body).lastInsertId;
    return RedirectTo("/web/items?added=" + id);
}

Per la struttura completa dello schema e i path dei template, vedi 24. Componenti server Web UI (24.3.1.3 riferimento allo schema CRUD).

29.6 Pattern di integrazione database

Un approccio pratico è usare:

Per setup del provider e dettagli di connessione, vedi 16. Supporto database.

29.7 Workflow di triage AI (opzionale ma potente)

Usa un agente per classificare l'urgenza degli item in arrivo e proporre l'azione successiva:

var client = new OpenRouterClient();
var triageAgent = new Agent(
    "TriageAgent",
    "item-triage",
    "Classify items by priority and suggest next action.",
    client
);

function triageItem(item) {
    var prompt = "Classify this item and return JSON with priority and queue: " + toJSON(item);
    var result = triageAgent.think(prompt);
    return parseJSON(result.content);
}

Pattern di integrazione comune: chiama il triage subito dopo la creazione dell'item, poi persisti i campi priority e queue prima di restituire la risposta.

29.8 Flusso della richiesta end-to-end

  1. L'utente invia il form dell'item nella Web UI
  2. La UI invia POST /api/items
  3. L'API valida e persiste l'item iniziale
  4. L'API chiama opzionalmente l'agente di triage
  5. L'API aggiorna priorità/coda e restituisce JSON
  6. La UI aggiorna l'elenco via GET /api/items

29.9 Configurazione e segreti

Tieni credenziali e valori specifici dell'ambiente fuori dal codice sorgente:

I built-in rilevanti sono documentati in 13. Funzioni built-in.

29.10 Checklist pratica per nuove app full-stack

29.10.1 Testing e troubleshooting (Web UI + API)

Quando integri la Web UI con le REST API, usa questi controlli pratici:

Per il debugging specifico della Web UI (divergenza dell'albero, event handler, cache dei template), vedi 24. Componenti server Web UI (24.3.15 Insidie comuni e debugging, 24.6 Testing e troubleshooting).

Vedi anche