TypeScript client for accessing TLE Community.
client CLAUDE.md
7.1 kB
Markdown
at main

CLAUDE.md #

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this repo is #

A TypeScript client library for the JSON-RPC API of The Lacuna Expanse (community edition game server), published as @tlecommunity/client to a private Gitea npm registry.

The repo serves two purposes at once:

  1. It's the HTTP client other projects use to talk to the TLE server.
  2. Its test suite is a deliberate integration test for the TLE backend itself — tests call the real, live TLE server (no mocks) to verify responses stay correct as that backend migrates from a legacy, untyped Perl codebase to Ruby on Rails. This is why tests hit a live server instead of mocking — it's the design, not an oversight. A passing suite is evidence the backend still behaves correctly, not just that the client code is correct.

Commands #

  • npm run types — typecheck (tsc --noEmit)
  • npm test — run the full suite (jest --coverage); makes real network calls, see caveat below
  • npx jest lib/endpoints/captcha.test.ts — run a single test file
  • npx jest -t "drain" — run tests matching a name
  • npm run build — library build via vite build (also emits .d.ts via vite-plugin-dts)
  • npm run format / npm run format:fix — Prettier check/write over the whole repo (there is no ESLint in this repo)
  • npm run generate:types — regenerate lib/types/schema.ts from the @tlecommunity/api-spec devDependency
  • npm run prepare — full pre-publish pipeline (types → test → build), runs automatically on npm publish
  • Requires Node >=24, npm >=10 (.nvmrc pins 24)

Test caveat (read before touching tests) #

Tests are intentionally not mocked. lib/__utils__/get-lacuna.ts constructs a real Lacuna instance and authenticates against a live server (default serverUrl in lib/core/config.ts is a Tailscale-hosted instance). Every *.test.ts file under lib/ exercises real endpoints over the network. lib/__utils__/ itself is excluded from Jest's test discovery and from coverage collection (it's a helper, not a test).

The live server rate-limits to 200 requests/minute per empire. With one test file per endpoint method group, getLacuna() (session) and findBuilding() (building inventory) cache their live lookups on disk (os.tmpdir(), ~4 minute TTL) so the whole run shares one login and one inventory fetch instead of every file redoing both. npm test runs Jest with --runInBand for the same reason — the default parallel workers each hit the live server independently and can trip the rate limit (or race the shared cache) even with the caching in place. Prefer npm test or npx jest --runInBand <path> over a bare parallel npx jest for anything broader than a single file.

Architecture #

  • lib/index.ts is the package entry point, exporting Lacuna (default), util, and types.
  • Lacuna (lib/lacuna.ts) is the root object. Its constructor builds the core infra — Log, Config, Responses, Server, Session — and then one instance per API module (alliance, body, captcha, empire, inbox, map, stats, and one per building type, e.g. shipyard, spacePort, planetaryCommand; university has no extra methods so it's a plain Building). Lacuna#buildingFromUrl maps a building's module slug to its subclass via BUILDING_SUBCLASSES, falling back to Building. Every endpoint receives the parent Lacuna instance in its constructor and reaches back through it (this.lacuna.server, .session, .log).
  • lib/core/endpoint.ts — abstract Endpoint base class every API module extends; provides callWithSession / callWithoutSession, which delegate to Server#call.
  • lib/core/server.ts — the JSON-RPC transport. Builds the request body ({jsonrpc: '2.0', id, method, params}), injects the session id into positional (array) params when addSession: true, POSTs via fetch, and normalizes numeric strings in the response via fixNumbers (the server returns numbers as strings).
  • lib/core/responses.ts — the response bus behind Lacuna#onResponse. Every response Server produces is published to subscribers with the originating request and a retry callback. Responses carrying a blocking error code (BLOCKING_ERROR_CODES in lib/core/constants.ts — RPC limit 1010, captcha 1016, and rejected-session 1006) are dispatched awaited, and a response a handler returns replaces the one the caller gets; everything else is fire-and-forget. Every handler is called even after a blocking error is resolved (subscribers are promised all responses) — later handlers just receive the resolved response, which is what keeps them from retrying redundantly.
  • lib/core/rpc-limit-handler.ts — the opt-in handler Lacuna#enableRpcLimitHandler registers: retries 1010 with progressive backoff. The library logs nothing about errors on its own; that's what subscribers are for.
  • lib/core/session.ts — holds the session id set by Lacuna#authenticate.
  • lib/core/config.ts — holds serverUrl / apiKey / fingerprintToken.
  • lib/endpoints/*.ts — one file per top-level API module (alliance, body, captcha, empire, inbox, map, stats, building). Each is a thin class extending Endpoint whose methods just forward typed params to callWithSession/callWithoutSession, in the exact positional-argument order the server RPC expects.
  • lib/endpoints/buildings/*.ts — building-specific modules (essentia-vein, shipyard, space-port, ...) extend the generic Building endpoint (lib/endpoints/building.ts) rather than Endpoint directly, inheriting view/upgrade/downgrade/demolish.
  • lib/types/*.ts — one file per endpoint module with *Params/*Response interfaces, mirroring the lib/endpoints/ structure 1:1 (imported as import * as B from '../types/building'), plus status.ts for the shared status block. lib/types/index.ts re-exports all of them plus shared aliases (ServerDate, EmpireName, IntBool, MapLocation, LacunaConfig).
  • lib/types/schema.ts is generated from @tlecommunity/api-spec (npm run generate:types) — don't hand-edit it. The hand-written types are the public API; where they deliberately differ from the spec, the divergence is listed in README "Spec Discrepancies". After bumping the spec, diff schema.ts and carry relevant changes into the hand-written types. Tests check live responses against the spec via expectMatchesApiSchema (lib/__utils__/validate-schema.ts).

Adding a new endpoint method #

  1. Add the params/response interfaces to the matching lib/types/<module>.ts.
  2. Add a method to the matching lib/endpoints/<module>.ts that calls callWithSession/callWithoutSession with positional params in the server-expected order.
  3. Add a colocated <module>.test.ts that exercises it against the live test server via getLacuna().