feat: core grammar, discovery and renderer; Postgres dialect; conformance suite

This commit is contained in:
2026-11-02 01:11:38 +01:00
commit 4b276f21e3
54 changed files with 4103 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
import { readFileSync } from 'node:fs';
/**
* Every ```http block followed by a ```json block in a docs page is one
* conformance case: the request, then the exact body it must answer. A body
* with an `error` key must come with a 4xx or 5xx status.
*/
export function examples(file) {
const text = readFileSync(file, 'utf8');
const out = [];
const re = /```http\n([\s\S]*?)```\s*\n+```json\n([\s\S]*?)```/g;
for (let m; (m = re.exec(text));) {
const line = text.slice(0, m.index).split('\n').length;
const request = m[1].split('\n').map((l) => l.trim()).filter(Boolean).join('');
const space = request.indexOf(' ');
const target = new URL(request.slice(space + 1), 'http://example');
out.push({
name: `${file.split('/').pop()}:${line} ${request}`,
method: request.slice(0, space),
path: target.pathname,
query: target.searchParams,
expected: JSON.parse(m[2]),
});
}
return out;
}
export async function run(rest, example, assert) {
const res = await rest.handle({ method: example.method, path: example.path, query: example.query, headers: {}, context: undefined });
assert.deepEqual(res.body, example.expected, `status ${res.status}`);
if (example.expected && typeof example.expected === 'object' && 'error' in example.expected) assert.ok(res.status >= 400, `status ${res.status}`);
else assert.ok(res.status < 300, `status ${res.status}`);
}
+60
View File
@@ -0,0 +1,60 @@
DROP SCHEMA IF EXISTS demo CASCADE;
CREATE SCHEMA demo;
CREATE TABLE demo.users (
id integer PRIMARY KEY,
name text NOT NULL,
email text,
password_hash text,
"limit" integer,
manager_id integer REFERENCES demo.users (id)
);
CREATE TABLE demo.assets (
id integer PRIMARY KEY,
name text NOT NULL,
status text NOT NULL,
owner_id integer REFERENCES demo.users (id),
cost numeric(10, 2),
active boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL
);
CREATE TABLE demo.tags (
id integer PRIMARY KEY,
name text NOT NULL UNIQUE
);
CREATE TABLE demo.asset_tags (
asset_id integer NOT NULL REFERENCES demo.assets (id),
tag_id integer NOT NULL REFERENCES demo.tags (id),
PRIMARY KEY (asset_id, tag_id)
);
CREATE TABLE demo.incidents (
id integer PRIMARY KEY,
title text NOT NULL,
asset_id integer NOT NULL REFERENCES demo.assets (id),
severity integer NOT NULL,
opened_on date NOT NULL
);
INSERT INTO demo.users (id, name, email, password_hash, "limit", manager_id) VALUES
(1, 'Ada', 'ada@example.com', 'x', 5, NULL),
(2, 'Grace', 'grace@example.com', 'x', NULL, 1),
(3, 'Linus', NULL, 'x', 2, 1);
INSERT INTO demo.assets (id, name, status, owner_id, cost, active, created_at) VALUES
(1, 'vpn gateway', 'open', 1, 1200.50, true, '2026-01-02T10:00:00Z'),
(2, 'mail relay', 'open', 2, 300.00, true, '2026-01-05T09:30:00Z'),
(3, 'old wiki', 'retired', NULL, NULL, false, '2025-06-01T00:00:00Z'),
(4, 'payroll db', 'open', 3, 4000.00, true, '2026-02-11T16:45:00Z');
INSERT INTO demo.tags (id, name) VALUES (1, 'pci'), (2, 'gdpr'), (3, 'internal');
INSERT INTO demo.asset_tags (asset_id, tag_id) VALUES (1, 1), (1, 3), (2, 2), (4, 1), (4, 2);
INSERT INTO demo.incidents (id, title, asset_id, severity, opened_on) VALUES
(1, 'VPN outage', 1, 3, '2026-01-10'),
(2, 'Spoofed mail', 2, 2, '2026-02-01'),
(3, 'Brute force', 1, 4, '2026-03-05');
+29
View File
@@ -0,0 +1,29 @@
import { test, before, after } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { createREST } from '../packages/core/dist/index.js';
import { postgres } from '../packages/postgres/dist/index.js';
import { examples, run } from './examples.mjs';
const require = createRequire(new URL('../packages/postgres/package.json', import.meta.url));
const { Pool } = require('pg');
const url = process.env.RESTLOOM_PG_URL;
const docs = new URL('../docs/', import.meta.url).pathname;
const pages = ['query-grammar.md'];
let pool;
let rest;
before(async () => {
if (!url) return;
pool = new Pool({ connectionString: url });
await pool.query(readFileSync(new URL('./fixtures/postgres.sql', import.meta.url), 'utf8'));
rest = createREST({ db: postgres(pool), expose: { schemas: ['demo'] }, resources: { asset_tags: { expose: false } } });
});
after(async () => { await pool?.end(); });
for (const page of pages) {
for (const example of examples(docs + page)) {
test(`postgres ${example.name}`, { skip: !url && 'RESTLOOM_PG_URL is not set' }, () => run(rest, example, assert));
}
}