Skip to content
LACE
  • v0.1 Current
  • Python
  • TypeScript Soon

lace-app-sdk

Routes & runtime UI

Expose HTTP routes and federated UI modules from your app sidecar. Routes mount under /apps/<app_id>/api/* and survive restarts; UI loads as a Module Federation remote with no shell rebuild.

Routes — AppRouteProvider

Routes are declared by a provider class with app_id, a route_metadata() list (pure data — core projects it without importing your code), and a register_routes(context) that receives the sidecar's FastAPI app and app_state.

pythonapp/routes.py
from lace_app_sdk.routes import AppRouteProvider, AppRouteContext, AppRouteMetadata
from lace_app_sdk.data import AppDataService, AppDataQueryRequest
from fastapi.responses import JSONResponse

class FieldIntakeRoutes(AppRouteProvider):
    app_id = "acme.field_intake"

    def route_metadata(self):
        return [AppRouteMetadata(app_id=self.app_id, route_id="list_tickets",
                                            kind="public", path_prefix="/tickets")]

    def register_routes(self, context: AppRouteContext):
        app = context.app
        state = context.app_state

        @app.get("/tickets")
        async def list_tickets(limit: int = 20):
            svc = AppDataService(app_state=state)
            res = svc.list_records(self.app_id, "tickets", AppDataQueryRequest(limit=limit))
            return JSONResponse({"tickets": [r.data_json for r in res.records]})
  • Mount point: /apps/<app_id>/api/<your-path>. The platform's LACE_APP_PATH_PREFIX is injected by SidecarActivator.
  • Use AppDataService(app_state=state) — not an in-memory dict — so GET /tickets survives sidecar restarts (see data collections dual-plane rule).
  • Register the collection at boot (idempotent) before serving: AppDataService(...).register_collection(TICKETS).
  • Litmus test: curl /apps/acme.field_intake/api/tickets locally via lace-app dev and again after docker restart — same rows.

Runtime UI — federated modules

The LACE shell never rebuilds for your app. Instead it loads your UI as a Module Federation remote at /ui/manifest.json (DEPLOY-007). The shell fetches lace_app_manifest.json → runtime_ui, then hydrates the remote entry.

tsxui + manifest
// ui/src/RuntimeView.tsx — federated module exposed at ./RuntimeView
export default function RuntimeView() {
  return <div>Tickets: {tickets.length}</div>
}

// lace_app_manifest.json → runtime_ui block
{
  "runtime_ui": {
    "module_id": "field_intake.ui",
    "module_version": "1.0.0",
    "entrypoints": {"session_home": "./RuntimeView"},
    "isolation_mode": "iframe"
  }
}
FieldWhat it means
module_idStable id, e.g. field_intake.ui
entrypointsAt least one of new_run / session_home / run_detail → exposed module path (e.g. "./RuntimeView")
isolation_modehost (default) or iframe
sdk_api_min / sdk_api_maxHost SDK contract range (default 1..1)
runtime_contract_versionlace.ui_runtime.v1

Local verification

  1. lace-app dev — routes mounted; UI manifest served at /ui/manifest.json.
  2. curl http://localhost:8080/ui/manifest.json | jq — expect remote_entry_url (set via LACE_APP_UI_BUNDLE_BASE_URL + LACE_APP_UI_DIGEST in prod).
  3. In prod, upload the UI bundle to s3://.../app-bundles/<app_id>/<digest>/ui; the sidecar serves the manifest and the shell loads the remote entry.

App-scoped secrets & settings

Read secrets declared via get_secret("my_key") / get_secret_optional / get_setting from lace_app_sdk.secrets. They resolve through the platform's secret backend (ECS sm_secrets in cloud, filesystem in dev). Write them with lace-app secrets set <app_id> <key> <value>.

Next: testing & proof lanes or publishing.