async function http(method, path, body, opts = {}) { const res = await fetch(path, { method, credentials: 'same-origin', headers: body ? { 'Content-Type': 'application/json' } : {}, body: body ? JSON.stringify(body) : undefined, signal: opts.signal }); if (res.status === 204) return null; const ct = res.headers.get('content-type') || ''; const data = ct.includes('json') ? await res.json() : await res.text(); if (!res.ok) throw Object.assign(new Error(data?.error || res.statusText), { status: res.status, data }); return data; } // Returns true for AbortError thrown by `fetch(... { signal })` so callers can // silently ignore cancelled requests instead of surfacing them as errors. export function isAbort(err) { return err && (err.name === 'AbortError' || err.code === 20); } export const api = { get: (p, opts) => http('GET', p, null, opts), post: (p, b, opts) => http('POST', p, b, opts), put: (p, b, opts) => http('PUT', p, b, opts), patch: (p, b, opts) => http('PATCH', p, b, opts), del: (p, opts) => http('DELETE', p, null, opts) };