Skip to content

[Feature] Centralize HTTP client + CRUD scaffolding (kill duplication) #33

@chronoai-shining

Description

@chronoai-shining

Severity: Medium

Two independent piles of duplication that bloat the api.

Pattern 1: per-domain HTTP fetch helper

Six modules repeat the exact same fetch + AbortSignal.timeout + if !res.ok throw pattern with subtly different error message formats:

  • sisyphus-api/src/runner/aevatar-client.ts
  • sisyphus-api/src/workflows/mainnet-client.ts
  • sisyphus-api/src/workflows/ornn-client.ts
  • sisyphus-api/src/workflows/reference-resolver.ts
  • sisyphus-api/src/ingest/chrono-graph-client.ts
  • sisyphus-api/src/papers/storage-client.ts

Pattern repeated ~12 times:

const resp = await fetch(url, { signal: AbortSignal.timeout(N) });
if (!resp.ok) {
  const body = await resp.text();
  throw new Error(`...HTTP ${resp.status}${body}`);
}
const result = await resp.json() as ResultType;

Pattern 2: paginated-list + partial-update CRUD

Three controllers reimplement the same two helpers ~3 times each:

  • sisyphus-api/src/workflows/controllers/WorkflowController.ts:96-117 (list), :158-167 (partial update).
  • sisyphus-api/src/workflows/controllers/ConnectorController.ts:101-122 (list), ConnectorController.ts (partial update).
  • sisyphus-api/src/schemas/controllers/SchemaController.ts:107-128 (list), :178-186 (partial update).

Remediation

Add one sisyphus-api/src/shared/http.ts:

export interface HttpJsonOptions {
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
  headers?: Record<string, string>;
  body?: unknown;
  timeoutMs?: number;  // default 15_000
  authorization?: string;
}

export async function httpJson<T>(url: string, opts: HttpJsonOptions = {}): Promise<T> {
  const { method = 'GET', headers = {}, body, timeoutMs = 15_000, authorization } = opts;
  const finalHeaders = { 'Content-Type': 'application/json', ...headers };
  if (authorization) finalHeaders.Authorization = authorization;

  const resp = await fetch(url, {
    method,
    headers: finalHeaders,
    body: body == null ? undefined : JSON.stringify(body),
    signal: AbortSignal.timeout(timeoutMs),
  });
  if (!resp.ok) {
    const text = await resp.text().catch(() => '');
    throw new UpstreamError(resp.status, text, url);  // see #23 — opaque code, full text logged
  }
  return resp.json() as Promise<T>;
}

Add sisyphus-api/src/shared/crud.ts:

export async function paginatedList<T>(
  col: Collection<T>,
  filter: Filter<T>,
  page: number, pageSize: number,
  sort?: Record<string, 1 | -1>,
): Promise<{ items: T[]; total: number; page: number; pageSize: number }> { ... }

export function pickDefined<T>(body: Partial<T>, keys: (keyof T)[]): Partial<T> { ... }

Halves these files; centralizes timeouts, error mapping (per #23), pagination clamps (per #34).

Metadata

Metadata

Labels

dxDeveloper experienceenhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions