mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-07 07:55:13 +02:00
* docs: rewrite Semantic Querying concept with imperative-vs-declarative diagram
Reframe semantic-layer-internals.mdx around the contract the semantic
layer offers an agent: declare what you want (a Semantic Query), KTX
figures out how to compute it. Replaces the old "Context-Aware SQL"
framing with a clear imperative-vs-declarative narrative.
Adds a React Flow component (semantic-layer-flow.tsx) that contrasts a
buggy 4-table agent-authored SQL (chasm trap, LEFT-JOIN-in-WHERE,
hardcoded DATE_TRUNC) against the chasm-safe per-fact CTE SQL the
planner actually emits, including the outer GROUP BY over the requested
dimensions. Both lanes converge into a shared warehouse node and each
SQL card now has parallel bullet notes (failures on the left, KTX
behavior on the right).
Side fixes bundled in:
- include the /ktx basePath in the favicon metadata so the icon resolves
under the production prefix
- migrate docs-site/middleware.ts to docs-site/proxy.ts (Next 16 rename)
- redirect / to /ktx/docs/getting-started/introduction so the apex docs
URL works
- add tests covering the apex redirect, the favicon basePath, and the
middleware-to-proxy rename
- propagate the Semantic Query terminology across the ktx-sl CLI
reference, the context-layer concept page, and the agent-clients /
primary-sources integration pages
* Fix CI dead-code failures
* docs-site: polish semantic-layer-internals code blocks and flow diagram
- Make CodeBlock a server component so children traverse synchronously
under React 19 RSC streaming; previously extractText returned "" in
dev SSR, leaving code blocks empty.
- Add custom JSON/YAML/SQL/code-like tokenizers with theme-aware token
classes; drop the colored file-glyph dot and gradient tab-head.
- Tighten tab-head: subtle grey background, smaller monospace filename
in muted grey, smaller rectangular language pill placed to the left
of the filename.
- Polish the React Flow semantic-layer diagram (controls, fit-view
padding, edge types).
* docs-site: annotate imperative SQL, add section anchor, drop ClickHouse
- Wire numbered red badges to each problematic span in the "Without KTX"
SQL with hover sync between SQL gutter, lines, and the notes list.
- Add #imperative-vs-declarative anchor on the flow section header so
the eyebrow link is shareable; reveals a # glyph on hover/focus.
- Align the compiled-SQL note dots to the first-line midpoint
(mt-[6px] instead of mt-1) so 4px dots sit at y=8 in a 16px line.
- Remove all ClickHouse references from docs-site (primary-sources,
quickstart, ktx-setup, contributing, agents-setup, mechanics test,
warehouse drivers in the flow diagram).
* test: drop ClickHouse contributing-docs assertion
Align the workspace-package mirror test with the ClickHouse removal
from docs-site (75907eb). The connector-clickhouse package still
exists in packages/, but contributing.mdx no longer lists it, so the
test that mirrored docs against the workspace was failing.
143 lines
3.8 KiB
JavaScript
143 lines
3.8 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { spawn } from "node:child_process";
|
|
import { once } from "node:events";
|
|
import { readFile, writeFile } from "node:fs/promises";
|
|
import { dirname, join } from "node:path";
|
|
import { createServer } from "node:net";
|
|
import { after, before, test } from "node:test";
|
|
import { setTimeout as delay } from "node:timers/promises";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const configuredDocsSiteUrl = process.env.DOCS_SITE_URL;
|
|
const docsBasePath = "/ktx";
|
|
let docsSiteUrl = configuredDocsSiteUrl;
|
|
let docsServer;
|
|
let docsServerOutput = "";
|
|
let nextEnvPath;
|
|
let nextEnvContents;
|
|
|
|
async function getAvailablePort() {
|
|
const server = createServer();
|
|
server.listen(0, "127.0.0.1");
|
|
await once(server, "listening");
|
|
|
|
const address = server.address();
|
|
await new Promise((resolve, reject) => {
|
|
server.close((error) => {
|
|
if (error) reject(error);
|
|
else resolve();
|
|
});
|
|
});
|
|
|
|
assert.equal(typeof address, "object");
|
|
assert.notEqual(address, null);
|
|
return address.port;
|
|
}
|
|
|
|
function appendDocsServerOutput(chunk) {
|
|
docsServerOutput = `${docsServerOutput}${chunk.toString()}`.slice(-4000);
|
|
}
|
|
|
|
async function waitForDocsServer() {
|
|
for (let attempt = 0; attempt < 150; attempt += 1) {
|
|
if (docsServer?.exitCode !== null) {
|
|
throw new Error(
|
|
`Docs server exited before it was ready.\n${docsServerOutput}`,
|
|
);
|
|
}
|
|
|
|
try {
|
|
await fetch(`${docsSiteUrl}${docsBasePath}/docs`, { redirect: "manual" });
|
|
return;
|
|
} catch {
|
|
await delay(200);
|
|
}
|
|
}
|
|
|
|
throw new Error(`Timed out waiting for docs server.\n${docsServerOutput}`);
|
|
}
|
|
|
|
before(async () => {
|
|
if (configuredDocsSiteUrl) {
|
|
return;
|
|
}
|
|
|
|
const docsSiteDir = join(
|
|
dirname(fileURLToPath(import.meta.url)),
|
|
"..",
|
|
);
|
|
nextEnvPath = join(docsSiteDir, "next-env.d.ts");
|
|
nextEnvContents = await readFile(nextEnvPath, "utf8");
|
|
|
|
const port = await getAvailablePort();
|
|
docsSiteUrl = `http://127.0.0.1:${port}`;
|
|
docsServer = spawn(
|
|
"pnpm",
|
|
["exec", "next", "dev", "--hostname", "127.0.0.1", "--port", `${port}`],
|
|
{
|
|
cwd: docsSiteDir,
|
|
env: { ...process.env, NEXT_TELEMETRY_DISABLED: "1" },
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
},
|
|
);
|
|
docsServer.stdout.on("data", appendDocsServerOutput);
|
|
docsServer.stderr.on("data", appendDocsServerOutput);
|
|
|
|
await waitForDocsServer();
|
|
});
|
|
|
|
after(async () => {
|
|
if (docsServer && docsServer.exitCode === null) {
|
|
docsServer.kill("SIGTERM");
|
|
await Promise.race([
|
|
once(docsServer, "exit"),
|
|
delay(5000).then(() => docsServer?.kill("SIGKILL")),
|
|
]);
|
|
}
|
|
|
|
if (nextEnvPath && nextEnvContents !== undefined) {
|
|
await writeFile(nextEnvPath, nextEnvContents);
|
|
}
|
|
});
|
|
|
|
test("/ktx/docs redirects to the docs introduction", async () => {
|
|
const response = await fetch(`${docsSiteUrl}${docsBasePath}/docs`, {
|
|
redirect: "manual",
|
|
});
|
|
|
|
assert.equal(response.status, 307);
|
|
assert.equal(
|
|
response.headers.get("location"),
|
|
`${docsBasePath}/docs/getting-started/introduction`,
|
|
);
|
|
});
|
|
|
|
test("/ redirects into the /ktx docs site", async () => {
|
|
const response = await fetch(`${docsSiteUrl}/`, {
|
|
redirect: "manual",
|
|
});
|
|
|
|
assert.equal(response.status, 307);
|
|
assert.equal(
|
|
response.headers.get("location"),
|
|
`${docsBasePath}/docs/getting-started/introduction`,
|
|
);
|
|
});
|
|
|
|
test("/ktx/api/search returns docs search results", async () => {
|
|
const response = await fetch(
|
|
`${docsSiteUrl}${docsBasePath}/api/search?query=setup`,
|
|
);
|
|
|
|
assert.equal(response.status, 200);
|
|
|
|
const results = await response.json();
|
|
assert.ok(Array.isArray(results), "search response should be an array");
|
|
assert.ok(
|
|
results.some(
|
|
(result) =>
|
|
typeof result.url === "string" && result.url.startsWith("/docs/"),
|
|
),
|
|
"search should return at least one docs result",
|
|
);
|
|
});
|