Initial Commit

This commit is contained in:
jb
2026-09-18 23:15:05 -04:00
commit fdcc4863f5
118 changed files with 16868 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
# ESI operations
The server ESI adapter uses the fixed trusted base `https://esi.evetech.net` and pins
`X-Compatibility-Date: 2026-08-18` (`ESI_COMPATIBILITY_DATE`). It exposes only the
character asset GET and global market-price GET needed by the application; callers
cannot provide an arbitrary ESI URL.
## Asset synchronization
`EsiClient.getAssets(characterId, accessToken, authorizationKey)` fetches page one,
reads `X-Pages`, fetches all remaining pages, de-duplicates by `item_id` while
checking for duplicates, and revalidates page one after the other pages. A changed
page count, duplicate ID, malformed page, failed request, or changed first page
fails the operation. The caller must not persist a returned partial result: the
method returns only a complete `{ assets, syncedAt, expiresAt }` snapshot. Asset
IDs are retained as decimal strings, and the response is parsed with
`lossless-json` so an int64 cannot be rounded by JavaScript.
This is a stability check, not a transaction. ESI's paginated endpoint does not
promise a true multi-page snapshot while its cache expires between page requests.
A caller should stage pages and replace the persisted Convex snapshot only after
`getAssets` resolves. Convex persistence is intentionally outside this adapter.
The cache is private to an `EsiClient` and keyed by user/authorization context,
character, and scopes. `authorizationKey` should therefore identify the linked
application user and include the character/scopes when available:
```ts
{ userId, characterId, scopes }
```
A string key is supported for callers that have no scope object, but is treated as
an isolated user key. Tokens are never used as cache keys or emitted in metrics.
Responses retain their raw text, ETag, Last-Modified, and cache metadata. Fresh
responses use the server TTL; stale responses send `If-None-Match` and
`If-Modified-Since`, and a 304 reuses the cached body. `Cache-Control`, `Expires`,
`Date`, and `Age` are honored. `no-store` responses retain neither body nor
validators. Requests have one deadline covering response-body reads and bounded
retries for transient network/5xx/420/429 responses. `Retry-After` (seconds or
HTTP date) and the legacy ESI error-limit reset are respected. Exhausted new
limiter buckets are cooled down per authorization context and group; the legacy
error limiter is cooled down globally. A server delay that exceeds the remaining
request budget returns a typed rate-limit error rather than retrying early. 401
and 403 are never blindly retried.
## Market estimates
`EsiMarketEstimates` reads public `GET /markets/prices`. It uses only
`average_price`; a missing average price is omitted and `adjusted_price` is never
a fallback. The returned `PriceSnapshot.source` includes the pinned compatibility
date, `asOf` is the retrieval time (ESI supplies no source timestamp), and prices
are integer ISK cents represented as validated decimal strings. The route is a
cached global reference estimate, not a regional or structure order book.
Metrics supplied through `EsiClient`'s `observe` callback include endpoint, status,
retry/cache state, and redacted rate-limit headers. They do not include access
tokens, authorization keys, or response bodies.