sqlite-vec/site/build-ref.mjs
2024-07-16 22:28:15 -07:00

194 lines
4.4 KiB
JavaScript

import Database from "better-sqlite3";
import { load } from "js-yaml";
import { fileURLToPath } from "node:url";
import { resolve, dirname } from "node:path";
import { readFileSync, writeFileSync } from "node:fs";
import * as v from "valibot";
import { table } from "table";
const REF_PATH = resolve(
dirname(fileURLToPath(import.meta.url)),
"../reference.yaml"
);
const EXT_PATH = resolve(
dirname(fileURLToPath(import.meta.url)),
"../dist/vec0"
);
const DocSchema = v.object({
sections: v.record(
v.string(),
v.object({
title: v.string(),
desc: v.string(),
})
),
functions: v.record(
v.string(),
v.object({
params: v.array(v.string()),
desc: v.string(),
section: v.string(),
example: v.union([v.string(), v.array(v.string())]),
})
),
/*table_functions: v.record(
v.string(),
v.object({
params: v.array(v.string()),
desc: v.string(),
example: v.union([v.string(), v.array(v.string())]),
})
),*/
});
const tableConfig = {
border: {
topBody: ``,
topJoin: ``,
topLeft: ``,
topRight: ``,
bottomBody: ``,
bottomJoin: ``,
bottomLeft: ``,
bottomRight: ``,
bodyLeft: ``,
bodyRight: ``,
bodyJoin: ``,
joinBody: ``,
joinLeft: ``,
joinRight: ``,
joinJoin: ``,
},
};
function formatSingleValue(value) {
if (typeof value === "string") {
const s = `'${value.replace(/'/g, "''")}'`;
if (s.split("\n").length > 1) {
return `/*\n${s}\n*/`;
}
return `-- ${s}`;
}
if (typeof value === "number") return `-- ${value.toString()}`;
if (value === null) return "-- NULL";
if (value instanceof Uint8Array) {
let s = "X'";
for (const v of value) {
s += v.toString(16).toUpperCase();
}
s += "'";
return `-- ${s}`;
}
if (typeof value === "object" || Array.isArray(value))
return "-- " + JSON.stringify(value, null, 2);
}
function formatValue(value) {
if (typeof value === "string" || typeof value === "number") return value;
if (value === null) return "NULL";
if (value instanceof Uint8Array) {
let s = "X'";
for (const v of value) {
s += v.toString(16);
}
s += "'";
return s;
}
if (typeof value === "object" || Array.isArray(value))
return JSON.stringify(value, null, 2);
}
function tableize(stmt, results) {
const columnNames = stmt.columns().map((c) => c.name);
const rows = results.map((row) =>
row.map((value) => {
return formatValue(value);
})
);
return table([columnNames, ...rows], tableConfig);
}
function renderExamples(db, name, example) {
let md = "```sql\n";
const examples = Array.isArray(example) ? example : [example];
for (const example of examples) {
const sql = example
/* Strip any '```sql' markdown at the beginning */
.replace(/^\w*```sql/, "")
/* Strip any '```' markdown at the end */
.replace(/```\w*$/m, "")
.trim();
let stmt, results, error;
results = null;
try {
stmt = db.prepare(sql);
stmt.raw(true);
} catch (error) {
console.error(`Error preparing statement for ${name}:`);
console.error(error);
throw Error();
}
try {
results = stmt.all();
} catch (e) {
error = e.message;
}
md += sql + "\n";
if (!results) {
md += `-- ❌ ${error}\n\n`;
continue;
}
const result =
results.length > 1 || stmt.columns().length > 1
? `/*\n${tableize(stmt, results)}\n*/\n`
: formatSingleValue(results[0][0]);
md += result + "\n\n";
}
md += "\n```\n\n";
return md;
}
let md = `# API Reference
::: warning
sqlite-vec is pre-v1, so expect breaking changes.
:::
[[toc]]
`;
const doc = v.parse(DocSchema, load(readFileSync(REF_PATH, "utf8")));
const db = new Database();
db.loadExtension(EXT_PATH);
let lastSection = null;
for (const [name, { params, desc, example, section }] of Object.entries(
doc.functions
)) {
const headerText = `\`${name}(${(params ?? []).join(", ")})\` {#${name}}`;
if (lastSection != section) {
md += `## ${doc.sections[section].title} {#${section}} \n\n`;
md += doc.sections[section].desc;
md += "\n\n";
lastSection = section;
}
md += "### " + headerText + "\n\n";
md += desc + "\n\n";
md += renderExamples(db, name, example);
}
writeFileSync("api-reference.md", md, "utf8");
console.log("done");