Add concurrency caps, the two-pane web UI, AGENTS.md, and plan status
This commit is contained in:
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
export class ApiError extends Error {
|
||||
status;
|
||||
constructor(status, message) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
export async function request(method, path, opts = {}) {
|
||||
const pathParams = (opts.path ?? {});
|
||||
let url = path.replace(/\{(\w+)\}/g, (_, key) => {
|
||||
const value = pathParams[key];
|
||||
if (value === undefined) {
|
||||
throw new Error(`missing path parameter ${key} for ${path}`);
|
||||
}
|
||||
return encodeURIComponent(String(value));
|
||||
});
|
||||
const query = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries((opts.query ?? {}))) {
|
||||
if (value !== undefined) {
|
||||
query.set(key, String(value));
|
||||
}
|
||||
}
|
||||
if (query.size > 0) {
|
||||
url += `?${query}`;
|
||||
}
|
||||
const init = { method: method.toUpperCase() };
|
||||
if (opts.body !== undefined) {
|
||||
init.headers = { "Content-Type": "application/json" };
|
||||
init.body = JSON.stringify(opts.body);
|
||||
}
|
||||
const res = await fetch(url, init);
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
throw new ApiError(res.status, data.message);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
export const get = (path, opts) => request("get", path, opts);
|
||||
export const post = (path, opts) => request("post", path, opts);
|
||||
export const put = (path, opts) => request("put", path, opts);
|
||||
export function subscribe(after, onEvent, onState) {
|
||||
const source = new EventSource(`/api/events?after=${after}`);
|
||||
source.onopen = () => onState(true);
|
||||
source.onerror = () => onState(false);
|
||||
source.onmessage = (msg) => onEvent(JSON.parse(msg.data));
|
||||
return source;
|
||||
}
|
||||
//# sourceMappingURL=api.js.map
|
||||
Reference in New Issue
Block a user