Longterm Wiki
Updated 2026-03-13HistoryData
Page StatusDocumentation
Edited today3.5k wordsPoint-in-time
15ImportancePeripheral20ResearchMinimal
Content1/13
LLM summaryScheduleEntityEdit historyOverview
Tables10/ ~14Diagrams0/ ~1Int. links0/ ~28Ext. links21/ ~17Footnotes0/ ~10References0/ ~10Quotes0Accuracy0

Server Communication Investigation

Server Communication Investigation

Date: 2026-02-24 Status: Investigation / Proposal Context: PRs #947-#956 wired many frontend features to the wiki-server API. This document evaluates the current patterns and recommends improvements.

Current Architecture

Three consumers talk to the wiki-server (Hono + PostgreSQL):

ConsumerTransportClient codeSize
Next.js app (apps/web/)fetchDetailed() / fetchFromWikiServer() / raw fetch()wiki-server.ts (138 lines) + 13 fetch locations across 11 files9 hand-written Api*Entry interfaces
Crux CLI (crux/)apiRequest() / batchedRequest()crux/lib/wiki-server/client.ts + 17 domain modules107 exported interfaces & types
Build scriptsDirect fetchInline in build-data.mjs etc.Minor

The wiki-server exposes 18 route modules with 109 endpoints and 778 lines in apps/wiki-server/src/api-types.ts (53+ Zod input schemas on lines 1-619, 14 response interfaces on lines 620-778).

What Works Well

  1. withApiFallback pattern -- Clean abstraction. Dashboards get API data when available, local fallback when not, plus a DataSourceBanner that shows users where data came from and why.

  2. FetchResult<T> discriminated union -- Precise error classification (not-configured / connection-error / server-error) enables actionable UI messages. Better than T | null.

  3. Shared Zod schemas for inputs -- api-types.ts is imported by both the server (runtime validation) and crux CLI (TypeScript type inference). Input types are not duplicated. Additionally, 14 response interfaces are already exported (covering sessions, facts, and links endpoints), which serve as the model for expanding coverage.

  4. Crux's ApiResult<T> pattern -- Consistent error handling across all 17 domain modules. Well-designed with apiOk() / apiErr() helpers.

  5. Crux's two-tier architecture -- Core HTTP primitives (apiRequest() with 5s timeout, batchedRequest() with 30s timeout) in client.ts are cleanly separated from 17 domain-specific wrapper modules. Each domain module handles batch splitting with data-type-optimized sizes (200 entities, 500 facts, 2,000 links, 100 risk snapshots).

Pain Points

1. Response types are mostly duplicated (~120 hand-written interfaces across consumers)

Of 109 server endpoints, only 14 have response types exported from api-types.ts (sessions: 4, facts: 5, links+related: 5). The remaining 95 endpoints return untyped JSON -- response shapes exist only implicitly in c.json({...}) calls inside route handlers.

Each consumer re-declares response shapes for these untyped endpoints:

  • Crux CLI: 107 exported interfaces & types across 17 domain modules in crux/lib/wiki-server/. Some are re-exports of the 14 shared server types (e.g., SessionApiEntry = z.input<typeof CreateSessionSchema>), but most are hand-written result wrappers (SaveArtifactsResult, JobStatsResult, etc.)
  • Next.js app: 9 Api*Entry interfaces scattered across dashboard pages (ApiRunEntry, ApiNewsItem, ApiAgentSession, ApiJobEntry, ApiArtifactEntry, etc.) plus ~15 display-only Row types

When a server route changes, the hand-written types drift silently -- no compile-time check that they match. The 14 shared types (sessions, facts, links) prove the pattern works; the other 95 endpoints just haven't been covered yet.

2. Four separate fetch strategies across 13 locations

The Next.js app has 13 locations that call the wiki-server, using 4 different strategies:

StrategyFunctionError modelAuthLocations
Error-awarefetchDetailed<T>()FetchResult<T> (3 error variants)Bearer8 dashboards (auto-update-runs, citation-accuracy, citation-content, auto-update-news, jobs, improve-runs, agent-sessions)
Legacy null-basedfetchFromWikiServer<T>()T | nullBearer1 file (citation-data.ts)
Custom paginationgetWikiServerConfig() + manual loopFetchResult<T> (manual)Bearer1 file (hallucination-risk/page.tsx, 70-line pagination loop)
Raw fetchfetch()variesvaries2 files (search route: Bearer + 3s timeout; data page: x-api-key -- wrong!)

Crux has its own two-tier system:

LayerFunctionError modelTimeout
CoreapiRequest<T>()ApiResult<T> (4 error variants)5s default
CorebatchedRequest<T>()ApiResult<T>30s

The data page at apps/web/src/app/wiki/[id]/data/page.tsx:54 uses x-api-key instead of Authorization: Bearer -- this is a latent bug (currently works because the server allows unauthenticated access when the API key env var is unset, but will break when auth is enforced).

3. Only 13% of endpoints have shared response types

The api-types.ts file defines 53+ input schemas (lines 1-619) but only 14 response interfaces (lines 620-778) covering 3 of 18 route modules (sessions, facts, links/related). The remaining 95 endpoints return untyped JSON via inline c.json({...}) calls.

The server-side response construction uses 4 inconsistent patterns:

  1. Inline untyped objects (majority) -- return c.json({ results: rows, total }) with no type annotation
  2. Database row casting to any (pages.ts, citations.ts) -- raw SQL returns any, field mapping is unvalidated
  3. Mapped helper functions (sessions.ts) -- mapSessionRow() documents shape implicitly, response type exported
  4. Complex aggregations (citations.ts /accuracy-dashboard, 177 lines) -- entirely untyped, impossible for clients to infer

Error responses follow a consistent format ({ error: "<code>", message: string }) but this shape is not exported as a type or schema.

4. Inconsistent timeout, caching, and error handling

Timeouts:

  • Search route: explicit 3-second AbortSignal.timeout (exemplary)
  • Crux: 5-second default, 30-second batch timeout (via AbortController)
  • All 8 dashboard pages using fetchDetailed: no timeout (relies on Next.js defaults)
  • Hallucination risk pagination loop: no timeout on individual requests

ISR revalidation:

  • Dashboard summaries: 60-300 seconds
  • Citation data: 600 seconds (10 min)
  • Page-level data: 300 seconds
  • Search proxy: no caching (always fresh)

Error handling:

  • 8 dashboards use fetchDetailedwithApiFallbackDataSourceBanner (good)
  • citation-data.ts uses fetchFromWikiServer → silent null fallback → empty array (loses error details)
  • Hallucination risk page: builds its own FetchResult<T> manually (correct but duplicates logic)

5. Growing boilerplate per dashboard page

Each new internal dashboard repeats the same ~40-line pattern:

define ApiXyz interface → loadFromApi() using fetchDetailed → loadFromLocal() → withApiFallback → render + DataSourceBanner

This is functional but tedious. Adding a new dashboard that reads from 2 API endpoints requires writing ~60-80 lines of type definitions and fetch logic before any UI code.

Options Evaluated

Option A: Hono RPC (built-in)

Hono has a built-in RPC layer: export typeof app from the server, use hc<AppType>() on the client for fully typed calls with zero code generation.

Pros:

  • Zero new dependencies (already using Hono)
  • Eliminates all hand-written response types
  • Compile-time type checking of paths, methods, and response shapes

Cons:

  • IDE performance degrades at scale -- community reports multi-second autocomplete lag at 100+ endpoints. Would need per-module client splitting.
  • Requires refactoring all 18 route files to use method chaining (currently uses separate statement pattern)
  • Next.js ISR integration is awkward -- hc client doesn't pass next: { revalidate } options; you'd extract the URL and call fetch() directly, negating some benefit
  • Error types don't flow through global error handlers
  • Query param coercion broken -- z.coerce.number() in query schemas doesn't flow correctly to client types

Migration effort: High. Every route file and every client file must change. Mechanical but large.

Community research (Feb 2026): Hono RPC works well below ~50 endpoints. Hono's creator Yusuke Wada confirmed that 300+ endpoints makes hc "impossible" due to TypeScript instantiation depth limits, and acknowledged IDE degradation is a known limitation. Practical ceiling is ~100 endpoints with module splitting. The hc client is intentionally thin -- no batching, no React Query integration, no retry logic. The 9M weekly npm downloads are for Hono overall, not the RPC feature specifically. Monorepo setups are fragile: AppType export requires precise tsconfig alignment across packages or types break silently. For our 100+ endpoints, Hono RPC would require aggressive module splitting from day one.

Option B: ts-rest

Define a "contract" object that wraps existing Zod schemas with path, method, and response schema. Server and client both consume the contract for type safety.

Pros:

  • Keeps REST structure (paths, methods stay the same)
  • Incremental migration (one route module at a time)
  • Existing Zod schemas slot directly into contracts
  • CLI client works naturally (@ts-rest/core has no React dependency)
  • Standard fetch() underneath, so Next.js ISR works

Cons:

  • Hono adapter is community-maintained (ts-rest-hono), not official
  • Large upfront contract definition -- 100+ endpoints need contract entries
  • Some community concern about project trajectory (GitHub #797), though still actively maintained
  • Requires response schemas -- currently response shapes are implicit; you'd need to define Zod schemas for outputs too (this is arguably a benefit, but it's work)

Migration effort: Medium. Mechanical but incremental. Contract definitions are boilerplate-heavy.

Community research (Feb 2026): ts-rest's trajectory is the biggest concern. Two part-time volunteer maintainers, both with full-time jobs elsewhere. The v3.53.0 RC (Standard Schema / Zod 4 support) has sat unreleased for 9 months. GitHub issue #797 ("Future of ts-rest") was opened by a developer who'd already been burned by Zodios's abandonment -- maintainers reassured the community in May 2025, then the issue was reopened in September after the reassurances weren't sustained. 105 open issues, 32 open PRs. The ts-rest-hono adapter is effectively dead (54 stars, last release November 2023, single maintainer). At 100+ endpoints, TypeScript performance degrades to 40+ second builds and 10+ second IDE autocompletion (Issue #764); mitigation requires splitting contracts from day one. Production user Inkitt ($59M raised) documented positive outcomes, but no major enterprise adoption is publicly known. ~115K weekly npm downloads, ~3,300 GitHub stars. The library is functional but the community explicitly invokes the "Zodios parallel" -- beloved, feature-complete, thin maintenance, eventual abandonment risk.

Option C: oRPC (newer alternative)

TypeScript-first RPC framework with an official Hono adapter, built-in OpenAPI generation.

Pros:

  • Official Hono adapter (unlike ts-rest)
  • 1.6x faster type checking than tRPC (claims 2.8x runtime speed, 2.6x smaller bundle)
  • Built-in OpenAPI spec generation
  • Designed to address tRPC's limitations
  • Optional contract-first mode (like ts-rest) OR procedure-first (like tRPC)
  • Native file upload/download and Server Actions support

Cons:

  • v1.0 released December 2025 -- very young, small community
  • Solo maintainer (unnoq / Hung Viet Pham) -- bus factor of 1, biggest risk
  • Limited production battle-testing
  • API surface may still change

Migration effort: Medium-high. Similar to ts-rest but less ecosystem support.

Community research (Feb 2026): oRPC is the most interesting newcomer but carries real risk. Created by a developer who used tRPC extensively, tried ts-rest but found it "missing features I relied on from tRPC, like flexible middleware," and built oRPC to combine both. ThoughtWorks Technology Radar placed it in the "Assess" ring. ~245K weekly npm downloads (growing). The commercial SaaS boilerplate supastarter chose oRPC over tRPC for their v3 rewrite. InfoQ covered the v1.0 launch. Val Town evaluated oRPC but hesitated specifically on the solo-maintainer risk. The "bus factor 1" concern is the most frequently cited reservation across Reddit, HN, and blog posts. If the single maintainer loses interest, the project could stall like ts-rest but faster. However, the technical design is strong: incremental tRPC migration path, first-class OpenAPI from Zod schemas, and the official Hono adapter means no community-maintained shim layer.

Option D: tRPC

Not recommended. Requires abandoning REST structure (all endpoints become RPC procedures behind /trpc), fights Next.js ISR caching, has a known bug where discriminated unions lose required properties (#5215). Would be a full rewrite, not a migration.

Option E: OpenAPI codegen (openapi-typescript + openapi-fetch)

Generate types from an OpenAPI spec. Use @hono/zod-openapi to produce the spec from Hono routes.

Pros:

  • Industry-standard OpenAPI spec as source of truth
  • Works with any consumer (not just TypeScript)
  • Tiny client (~6 KB)

Cons:

  • Code generation step in build pipeline
  • @hono/zod-openapi requires significant route refactoring (different API than plain Hono)
  • Generated types can be verbose

Option F: Pragmatic improvements without a framework

Improve the existing patterns without adopting a new library.

Pros:

  • Zero migration risk
  • Can be done incrementally today
  • Addresses the most painful issues without framework lock-in

Cons:

  • Doesn't eliminate the fundamental "response types drift" problem
  • Manual discipline required to keep things consistent

Recommendation

Phase 1 (now): Pragmatic foundations -- Option F

Fix the concrete issues that cause daily friction. This work is valuable regardless of which framework we pilot later, because it establishes the shared response types that any framework integration would need anyway.

  1. Expand response type exports in api-types.ts. The 14 existing response interfaces (sessions, facts, links) prove the pattern works. Add response types for the remaining high-traffic modules: citations (18 endpoints, 0 types), jobs (10 endpoints, 0 types), entities (5 endpoints, 0 types). Target: cover the ~40 most-used endpoints. Both crux and Next.js import from the same source.

  2. Fix the x-api-key bug in apps/web/src/app/wiki/[id]/data/page.tsx:54 -- use fetchDetailed instead of raw fetch with wrong auth header. This will break when auth is enforced.

  3. Consolidate the Next.js fetch layer. Make fetchDetailed the single entry point. Migrate citation-data.ts from fetchFromWikiServer (silent null) to fetchDetailed (structured errors). Extract the pagination helper from hallucination-risk/page.tsx for reuse. Add optional timeout support to fetchDetailed.

  4. Create typed fetch helpers per domain in apps/web/src/lib/wiki-api/ (e.g., sessions.ts, citations.ts). These import shared response types from api-types.ts. Dashboard pages call getSessions() instead of writing ~60-80 lines of inline fetch + type definitions.

Phase 2 (soon): Framework pilot -- head-to-head comparison

Option F doesn't scale forever. At 109 endpoints growing toward 200+, manually maintaining response types is a losing game. The framework pilot should happen within the next few weeks, not "if the API keeps growing."

Pilot design: Pick one route module and implement it with two frameworks side by side. The facts module is ideal: 5 endpoints, well-defined Zod inputs, used by both crux and Next.js, has existing response types to compare against.

Framework A: oRPC -- strongest technical fit (official Hono adapter, contract-first + procedure-first modes, built-in OpenAPI). Solo-maintainer risk is real but bounded: if the pilot succeeds and the project later stalls, we'd have one module to port, not 18.

Framework B: Hono RPC -- zero new dependencies, type safety from typeof app. The IDE performance concern is theoretical at our scale (109 endpoints vs the "impossible" threshold of 300+). Per-module splitting may work fine.

Measure:

  • TypeScript compilation time delta
  • IDE autocomplete latency (is it perceptibly slower?)
  • Lines of code eliminated vs added
  • Developer ergonomics for adding a new endpoint
  • Whether Next.js ISR (next: { revalidate }) integrates cleanly

Decision criteria: If either framework eliminates the response type duplication problem without degrading IDE performance, adopt it incrementally (one module at a time). If both degrade DX, stick with Option F's manual types as the long-term approach.

Phase 3 (if pilot succeeds): Incremental migration

Migrate remaining 17 route modules one at a time, prioritizing by endpoint count (citations: 18, jobs: 10, resources: 8). Each module migration is independent and safe to ship separately.

Framework comparison after community research

DimensionHono RPCts-restoRPC
Maintenance healthPart of Hono (healthy)Concerning (9-month gap, RC unreleased)Active but solo maintainer
Hono integrationNative (built-in)Dead community adapterOfficial adapter
Scale at 100+ endpointsCreator says "impossible" at 300+40s builds, 10s+ IDE lagClaims better perf; unproven at scale
Bus factorHono team (healthy)2 part-time volunteers1 person (biggest risk)
Community size9M weekly (Hono overall)≈115K weekly, 3.3K stars≈245K weekly, growing
Production battle-testingModerateModerate (Inkitt)Minimal
OpenAPI generationNoVia contractBuilt-in, first-class
Server ActionsNoNoYes
Migration approachAll-or-nothing per moduleIncrementalIncremental

Pilot candidates (for Phase 2 head-to-head):

  • oRPC is the strongest technical fit for our architecture (Hono server, multiple consumers, 100+ endpoints, need for both procedure and contract modes). The solo-maintainer risk is real but bounded by the pilot approach: if the project stalls, we lose one module's worth of investment.

  • Hono RPC is the zero-dependency alternative. At 109 endpoints we're below the "impossible" threshold (300+), so the IDE performance concern may not materialize in practice. The pilot will answer this empirically.

Not recommended for pilot:

  • ts-rest -- maintenance trajectory (9-month unreleased RC), dead Hono adapter, and community anxiety about the project's future make it a poor bet for a project that will depend on it for years.
  • tRPC -- wrong architecture (requires abandoning REST structure).
  • Zodios -- abandoned.
  • Full OpenAPI codegen -- too much ceremony for current scale.

Sizing

Phase 1: Pragmatic foundations

TaskScopeEffort
Add response types for top ≈40 endpoints to api-types.ts≈30 new interfaces1 session
Fix x-api-key bug1 fileTrivial
Consolidate Next.js fetch layer (migrate legacy, extract pagination helper, add timeouts)≈5 files1 session
Create typed domain helpers in apps/web/src/lib/wiki-api/≈8-10 new files1-2 sessions
Update crux clients to import shared response types (replacing hand-written duplicates)17 files1 session

Total: ~4-5 sessions.

Phase 2: Framework pilot

TaskScopeEffort
Implement facts module with oRPC (server + crux client + Next.js client)3-4 files1 session
Implement facts module with Hono RPC (server + crux client + Next.js client)3-4 files1 session
Measure & document performance comparisonBenchmarks + writeup0.5 sessions

Total: ~2-3 sessions for the head-to-head comparison.

Phase 3: Incremental migration (if pilot succeeds)

TaskScopeEffort
Migrate remaining 17 route modules≈18 route files + ~17 client modules1-2 sessions per module, ~8-10 sessions total

Total: ~8-10 sessions spread over time.

Appendix A: Numbers

  • Wiki-server routes: 18 modules, 109 endpoints
  • Shared schemas in api-types.ts: 778 lines (53+ input Zod schemas, 14 response TypeScript interfaces)
  • Response type coverage: 14 of 109 endpoints (13%) have shared response types — sessions (4), facts (5), links+related (5)
  • Crux client code: 17 domain modules + 1 core client in crux/lib/wiki-server/, 107 exported interfaces & types (mix of server re-exports and hand-written wrappers)
  • Next.js integration points: 13 fetch locations across 11 files, 9 hand-written Api*Entry interfaces + ~15 display types
  • Fetch strategies in Next.js: 8 use fetchDetailed, 1 uses legacy fetchFromWikiServer, 1 uses custom pagination, 2 use raw fetch

Endpoint inventory by route module

ModuleEndpointsResponse types exported?
citations.ts18No
jobs.ts10No
resources.ts8No
edit-logs.ts6No
hallucination-risk.ts6No
sessions.ts6Yes (4 interfaces)
artifacts.ts5No
auto-update-news.ts5No
entities.ts5No
facts.ts5Yes (5 interfaces)
links.ts5Yes (2 interfaces)
pages.ts5No
summaries.ts5No
auto-update-runs.ts4No
agent-sessions.ts4No
ids.ts4No
health.ts1No

Next.js fetch locations

FileStrategyAuthStatus
internal/auto-update-runs/page.tsxfetchDetailedBearerOK
internal/citation-accuracy/page.tsxfetchDetailedBearerOK
internal/citation-content/page.tsx (2 calls)fetchDetailedBearerOK
internal/auto-update-news/page.tsxfetchDetailedBearerOK
internal/jobs/page.tsxfetchDetailedBearerOK
internal/improve-runs/page.tsxfetchDetailedBearerOK
internal/agent-sessions/page.tsx (2 calls)fetchDetailedBearerOK
internal/hallucination-risk/page.tsxCustom paginationBearerOK (but not reusable)
lib/citation-data.tsfetchFromWikiServerBearerLegacy (silent null)
api/search/route.tsRaw fetchBearerOK (3s timeout)
wiki/[id]/data/page.tsxRaw fetchx-api-keyBUG

Crux batch sizes by data type

ModuleBatch sizeTimeout
entities.ts20030s
facts.ts50030s
links.ts2,00030s
risk.ts10030s
auto-update.ts50030s

Appendix B: Community Research Sources (Feb 2026)

Hono RPC

ts-rest

oRPC

Cross-framework comparisons