AkurAI Build
Menu

Public repository / fee5b6c2cde89fa6bf8dc51068af7336b56a88b8

AkurAI-Framework / site/frontend/client-demo.html

Latest change 1d2bfb32048e3c8b890ef369116a9fa65bac00ec - Add typed client SDK (akurai-client.js), demo page, and docs by Ólafur Búi Ólafsson

{% include "header" %}
    <main class="container">
      <header class="page-head">
        <span class="eyebrow">Live demo</span>
        <h1>Typed client SDK</h1>
        <p class="lede">Everything below is driven by <code>akurai-client.js</code> — a
        no-build, zero-dependency, <em>typed</em> JS client generated from the
        auto-API manifest at <code>/api/_meta</code>. It talks to the live
        <code>posts</code> collection through <code>client.posts.list()</code> and
        <code>client.posts.create()</code>. <a href="/docs/client-sdk">Read how it works →</a></p>
      </header>

      <section class="split rt-grid">
        <!-- Create + refresh -->
        <article class="card">
          <header class="rt-head">
            <h3>Create a post</h3>
            <span id="sdk-status" class="status">connecting…</span>
          </header>
          <p class="muted small">Submitting calls <code>client.posts.create()</code>,
          then re-lists via <code>client.posts.list()</code> — proving the SDK
          end-to-end against the real REST API.</p>
          <form id="sdk-form" class="rt-form" autocomplete="off">
            <input id="sdk-title" type="text" placeholder="Title…" aria-label="Post title" required />
            <input id="sdk-body" type="text" placeholder="Body…" aria-label="Post body" />
            <button class="btn btn-primary" type="submit" id="sdk-create">Create</button>
          </form>
          <p id="sdk-error" class="alert alert-danger" role="alert" style="display:none;margin-top:1rem"></p>
        </article>

        <!-- List -->
        <article class="card">
          <header class="rt-head">
            <h3>Posts</h3>
            <span id="sdk-count" class="status">—</span>
          </header>
          <p class="muted small">Latest records, newest first, fetched through the client.</p>
          <ul class="rt-log" id="sdk-list" aria-live="polite"></ul>
        </article>
      </section>

      <section class="container cta">
        <h2>A typed API client, with no build step.</h2>
        <div class="btn-row center">
          <a class="btn btn-secondary" href="/docs/client-sdk">Client SDK docs</a>
          <a class="btn btn-secondary" href="https://github.com/olibuijr/AkurAI-Framework">Source on GitHub</a>
        </div>
      </section>
    </main>

    <style>
      /* Scoped to this demo — keeps styles.css untouched. */
      #sdk-list li { display: flex; gap: .5rem; align-items: baseline; }
      #sdk-list .sdk-id { color: var(--muted, #888); font-variant-numeric: tabular-nums; }
      #sdk-list .sdk-title { font-weight: 600; }
      #sdk-list .sdk-body { color: var(--muted, #888); }
      #sdk-list .sdk-empty { color: var(--muted, #888); font-style: italic; }
    </style>

    <script type="module">
      import { createClient } from "/akurai-client.js";

      const statusEl = document.getElementById("sdk-status");
      const countEl = document.getElementById("sdk-count");
      const listEl = document.getElementById("sdk-list");
      const errEl = document.getElementById("sdk-error");
      const formEl = document.getElementById("sdk-form");
      const titleEl = document.getElementById("sdk-title");
      const bodyEl = document.getElementById("sdk-body");

      const showError = (msg) => {
        errEl.textContent = msg;
        errEl.style.display = "";
      };
      const clearError = () => {
        errEl.textContent = "";
        errEl.style.display = "none";
      };

      const render = (records) => {
        countEl.textContent = `${records.length} record(s)`;
        listEl.replaceChildren();
        if (records.length === 0) {
          const li = document.createElement("li");
          li.className = "sdk-empty";
          li.textContent = "No posts yet — create one.";
          listEl.append(li);
          return;
        }
        for (const r of records) {
          const li = document.createElement("li");
          const id = document.createElement("span");
          id.className = "sdk-id";
          id.textContent = `#${r.id}`;
          const title = document.createElement("span");
          title.className = "sdk-title";
          title.textContent = r.title ?? "(untitled)";
          const body = document.createElement("span");
          body.className = "sdk-body";
          body.textContent = r.body ?? "";
          li.append(id, title, body);
          listEl.append(li);
        }
      };

      async function init() {
        try {
          const client = await createClient();
          statusEl.textContent = "connected";
          statusEl.classList.add("ok");

          const refresh = async () => render(await client.posts.list({ limit: 20 }));
          await refresh();

          formEl.addEventListener("submit", async (e) => {
            e.preventDefault();
            clearError();
            const title = titleEl.value.trim();
            if (!title) return;
            try {
              await client.posts.create({ title, body: bodyEl.value.trim() });
              titleEl.value = "";
              bodyEl.value = "";
              await refresh();
            } catch (err) {
              console.error("[client-demo] create failed", err);
              showError(err.message ?? String(err));
            }
          });
        } catch (err) {
          console.error("[client-demo] connect failed", err);
          statusEl.textContent = "offline";
          showError(err.message ?? String(err));
        }
      }

      if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", init, { once: true });
      } else {
        init();
      }
    </script>
{% include "footer" %}