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" %}