@spacemolt/lib 15.1.0 → 15.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/COMMANDS.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <!-- AUTO-GENERATED by scripts/generate.ts from openapi.json. DO NOT EDIT. -->
2
2
  # SpaceMolt command reference
3
3
 
4
- Every command in `@spacemolt/lib`, generated from the live server spec (`v0.612.0`). **296 commands across 19 tools.**
4
+ Every command in `@spacemolt/lib`, generated from the live server spec (`v0.613.4`). **296 commands across 19 tools.**
5
5
 
6
6
  ## How to call a command
7
7
 
package/README.md CHANGED
@@ -136,6 +136,23 @@ account.onAny((frame) => console.log('push:', frame.type));
136
136
  for await (const hit of account.events('battle_damage')) { /* ... */ }
137
137
  ```
138
138
 
139
+ `onAny` sees only unsolicited pushes. To log every frame on the wire, including
140
+ your own query/mutation replies, pass `onSend`/`onReceive` (the client variants
141
+ also receive the account id). Credentials are redacted in both directions:
142
+ `onSend` sees a `spacemolt_auth` frame with its `password`, `token`,
143
+ `registration_code`, or `device_code` (from `login_link_poll`) replaced by
144
+ `'[redacted]'`, and `onReceive` sees a `registered` push (which carries the
145
+ account's generated password) with that password redacted, and a `result`
146
+ frame carrying a `device_code` (`login_link`'s response) with that code
147
+ redacted too — the frame actually sent/routed is unaffected either way:
148
+
149
+ ```ts
150
+ new SpacemoltClient({
151
+ onSend: (id, frame) => console.log(id, '→', frame.tool, frame.action, frame.request_id),
152
+ onReceive: (id, frame) => console.log(id, '←', frame.type, frame.request_id),
153
+ });
154
+ ```
155
+
139
156
  ### Subscriptions
140
157
 
141
158
  Subscribe to a station's order book or to presence at your location; the
@@ -303,6 +320,40 @@ const { stations } = await fetchStations('https://game.spacemolt.com');
303
320
  const { system } = await fetchMobileBase('https://game.spacemolt.com'); // the moving capital
304
321
  ```
305
322
 
323
+ Every one of these fetches is bounded by a timeout (30s by default, covering the
324
+ response body, not just the headers; pass `{ timeoutMs }`, or `httpTimeoutMs` on
325
+ the client, to change it — the catalog is several MB), retries a transient `429`/`503` a few times
326
+ (honoring `Retry-After`, jittered), and throws a typed `HttpError`
327
+ (`status`/`url`/`attempts`/`retryAfterMs`) on a retry-exhausted or otherwise
328
+ non-ok response. A timeout or network error throws the native error
329
+ (`DOMException`/`TypeError`) instead — it's not an `HttpError`. The 60s
330
+ retry-wait cap is on the *total* base wait summed across every attempt, not
331
+ each attempt checked in isolation — so three retries of a near-the-cap
332
+ `Retry-After` can't add up to several times the cap.
333
+
334
+ With a client, the same data is `client.catalog()`, `client.map()`,
335
+ `client.stations()` and `client.mobileBase()`. To observe HTTP traffic
336
+ (status/bytes/timing/retries), set `fetchImpl` once on the client — every
337
+ HTTP call it makes, including Clerk, goes through it:
338
+
339
+ ```ts
340
+ const client = new SpacemoltClient({
341
+ fetchImpl: async (input, init) => {
342
+ const started = Date.now();
343
+ const res = await fetch(input, init);
344
+ console.log(res.status, String(input), `${Date.now() - started}ms`);
345
+ return res;
346
+ },
347
+ });
348
+ ```
349
+
350
+ The per-call timeout is delivered through `init.signal` (an `AbortSignal`), not
351
+ a separate argument — a wrapper must pass `init` through to the real `fetch`
352
+ unchanged, as above, or it silently loses the timeout.
353
+
354
+ The standalone functions above take the same option as a trailing
355
+ `{ fetchImpl }` for use without a client.
356
+
306
357
  ## Examples
307
358
 
308
359
  Runnable scripts in [`examples/`](./examples), run with `bun run examples/<name>.ts`:
package/dist/account.d.ts CHANGED
@@ -18,7 +18,7 @@ import type { FleetPush, OkPush } from './push-frames.js';
18
18
  import { ConnectionClosedError } from './errors.js';
19
19
  import { type MarketBook } from './state/market.js';
20
20
  import { type ObservationView } from './state/observation.js';
21
- import type { GameState, MutationAck, MutationResult, QueryResult, RawFrame, StateSection, WelcomeFrame } from './protocol.js';
21
+ import type { GameState, MutationAck, MutationResult, QueryResult, InboundFrame, RawFrame, StateSection, WelcomeFrame } from './protocol.js';
22
22
  import { type WebSocketFactory } from './transport/socket.js';
23
23
  /**
24
24
  * The `logged_in` frame payload, typed from the server's published
@@ -43,11 +43,36 @@ export interface ReconnectOptions {
43
43
  /** Backoff ceiling in ms. Default 30000. */
44
44
  maxDelayMs?: number;
45
45
  }
46
+ /** Info reported to an `onRateLimited` listener — see `Account.onRateLimited`. */
47
+ export interface RateLimitedInfo {
48
+ /** The call being retried: `tool.action` for `query`/`mutate`, `'authenticate'` for auth. */
49
+ command: string;
50
+ /** 1-based retry attempt number. */
51
+ attempt: number;
52
+ /** How long this retry is waiting before resending, including jitter. */
53
+ delayMs: number;
54
+ }
46
55
  export interface AccountOptions {
47
56
  /** WebSocket URL of the v2 endpoint. Defaults to the production server. */
48
57
  url?: string;
49
58
  /** Inject a WebSocket implementation (tests, custom runtimes). */
50
59
  webSocketFactory?: WebSocketFactory;
60
+ /**
61
+ * Observe every outbound frame (logging/review). Fires before it is sent.
62
+ * A `spacemolt_auth` frame's `password`, `token`, `registration_code`, or
63
+ * `device_code` (from `login_link_poll`) is replaced with `'[redacted]'`
64
+ * before this fires — the frame actually sent to the server is unaffected.
65
+ */
66
+ onSend?: (frame: InboundFrame) => void;
67
+ /**
68
+ * Observe every parsed inbound frame (logging/review). Fires before
69
+ * routing. A `registered` push's `password` (the account's generated
70
+ * credential), and a `result` frame's `device_code` (`login_link`'s
71
+ * response), are replaced with `'[redacted]'` before this fires — the
72
+ * frame that reaches `register()`'s/the correlator's own resolution is
73
+ * unaffected.
74
+ */
75
+ onReceive?: (frame: RawFrame) => void;
51
76
  /**
52
77
  * After authenticating, issue a `get_status` query to seed the local state
53
78
  * cache with the canonical full state. Default `true`. Disable to avoid the
@@ -136,6 +161,8 @@ export declare class Account {
136
161
  private readonly seedState;
137
162
  private readonly url;
138
163
  private readonly webSocketFactory?;
164
+ private readonly onSend?;
165
+ private readonly onReceive?;
139
166
  private readonly reconnectConfig;
140
167
  private readonly credentialsProvider?;
141
168
  private readonly fetchImpl?;
@@ -148,6 +175,8 @@ export declare class Account {
148
175
  private requestSeq;
149
176
  private _welcome;
150
177
  private _authenticated;
178
+ /** How many `reconnectOnce` calls are mid-swap; a count, so overlapping calls can't lift each other's guard. */
179
+ private reconnectsPending;
151
180
  private _loginPayload;
152
181
  private _currentTick;
153
182
  private _commands;
@@ -169,6 +198,7 @@ export declare class Account {
169
198
  private readonly reconnectedListeners;
170
199
  private readonly reconnectingListeners;
171
200
  private readonly disconnectedListeners;
201
+ private readonly rateLimitedListeners;
172
202
  constructor(opts?: AccountOptions);
173
203
  private makeSocket;
174
204
  /** Live view of the cached game state. Treat as read-only. */
@@ -446,6 +476,13 @@ export declare class Account {
446
476
  onReconnected(listener: () => void): () => void;
447
477
  /** Fired at the start of each reconnect attempt (1-based). Returns an unsubscribe function. */
448
478
  onReconnecting(listener: (attempt: number) => void): () => void;
479
+ /**
480
+ * Fired before each rate-limit retry sleep (`query`/`mutate`/`authenticate`
481
+ * auto-retrying a `rate_limited` error) — the delay includes jitter, so
482
+ * accounts rate-limited together don't retry in lockstep; this is what a
483
+ * caller sees per attempt. Returns an unsubscribe function.
484
+ */
485
+ onRateLimited(listener: (info: RateLimitedInfo) => void): () => void;
449
486
  /**
450
487
  * Fired when the connection is gone for good (non-reconnectable or retries
451
488
  * exhausted). Returns an unsubscribe function.
@@ -454,8 +491,15 @@ export declare class Account {
454
491
  private authExchange;
455
492
  /** Best-effort seed of the state cache after auth; failures are non-fatal. */
456
493
  private maybeSeedState;
494
+ /** Returns whether the auth exchange was accepted — a caller must not send its frame when it wasn't. */
457
495
  private beginAuth;
458
496
  private sendFrame;
497
+ /**
498
+ * Calls an optional single-callback hook (`onSend`/`onReceive`), swallowing
499
+ * a throw so a broken observer can't stop the frame being sent or routed —
500
+ * same handling as `notifyListeners` for a `Set` of listeners.
501
+ */
502
+ private callHook;
459
503
  private nextRequestId;
460
504
  /**
461
505
  * Pick the `request_id` for one frame. A caller-supplied id is echoed by the
@@ -471,6 +515,16 @@ export declare class Account {
471
515
  private claimRequestId;
472
516
  private routeFrame;
473
517
  private warnMalformedFrame;
518
+ /**
519
+ * Fails everything waiting on the current socket: in-flight correlator
520
+ * entries (query/mutate), a pending `welcome` wait, and a pending auth
521
+ * exchange (`login`/`register`/`loginToken`). Deliberately excludes
522
+ * `emitter.closeStreams()`: `handleClose` (a real close) still ends the
523
+ * player's event async iterators itself, but a `reconnectOnce` forced on a
524
+ * live socket leaves them running. Shared by `handleClose` and
525
+ * `reconnectOnce`, which both strand work on a socket that's going away.
526
+ */
527
+ private failPendingWork;
474
528
  private handleClose;
475
529
  private shouldReconnect;
476
530
  private reconnectLoop;
@@ -490,6 +544,13 @@ export declare class Account {
490
544
  */
491
545
  reconnectOnce(): Promise<void>;
492
546
  private resubscribe;
547
+ /**
548
+ * Runs `fn`, retrying on a `rate_limited` `SpacemoltError` up to
549
+ * `maxRateLimitRetries` times. The wait is jittered (see `jitteredDelayMs`)
550
+ * so accounts rate-limited together don't all retry in lockstep, and fires
551
+ * `onRateLimited` before each sleep. `command` identifies the call for that
552
+ * listener (`tool.action`, or `'authenticate'`).
553
+ */
493
554
  private withRateLimitRetry;
494
555
  /** Serialize mutations: each runs only after the previous settles. */
495
556
  private enqueueMutation;
@@ -39,13 +39,14 @@ export interface ClerkRegistration {
39
39
  registrationCode: string;
40
40
  players: ClerkPlayer[];
41
41
  }
42
+ type HeadersCtorInit = NonNullable<ConstructorParameters<typeof Headers>[0]>;
42
43
  /**
43
44
  * Headers that authorize calls to the Clerk-gated gameserver endpoints — an
44
45
  * alternative to `apiKey` for callers whose credential isn't a static key.
45
46
  * A factory is resolved fresh on every request, so short-lived tokens (e.g. a
46
47
  * browser Clerk session's `getToken()` JWT) stay valid across reconnects.
47
48
  */
48
- export type ClerkAuthHeaders = HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
49
+ export type ClerkAuthHeaders = HeadersCtorInit | (() => HeadersCtorInit | Promise<HeadersCtorInit>);
49
50
  /** How Clerk-gated requests are authorized. Provide `apiKey`, `headers`, or both. */
50
51
  export interface ClerkAuth {
51
52
  /** Clerk API key, sent as `Authorization: Bearer`. Keep it secret. */
@@ -83,3 +84,4 @@ export declare class ClerkSource {
83
84
  /** Mint a single-use WS login token for one owned player. */
84
85
  mintWsToken(playerId: string): Promise<string>;
85
86
  }
87
+ export {};
package/dist/client.d.ts CHANGED
@@ -10,9 +10,12 @@
10
10
  import { Account } from './account.js';
11
11
  import { type AuthCredentials, type CredentialStore, type StoredAccount } from './auth/credentials.js';
12
12
  import type { WebSocketFactory } from './transport/socket.js';
13
+ import type { InboundFrame, RawFrame } from './protocol.js';
13
14
  import type { ReconnectOptions, RegisterParams, RegisterResult } from './account.js';
14
15
  import { CatalogCache } from './data/catalog.js';
15
16
  import { MapCache } from './data/map.js';
17
+ import { type MobileBaseLocation } from './data/mobile-base.js';
18
+ import { type StationList } from './data/stations.js';
16
19
  import { type ClerkPlayer } from './auth/clerk.js';
17
20
  import { type ConnectionClosedError } from './errors.js';
18
21
  export interface SpacemoltClientOptions {
@@ -22,6 +25,10 @@ export interface SpacemoltClientOptions {
22
25
  store?: CredentialStore;
23
26
  /** Inject a WebSocket implementation (tests, custom runtimes). */
24
27
  webSocketFactory?: WebSocketFactory;
28
+ /** Observe every outbound frame on every managed account (see Account). */
29
+ onSend?: (accountId: string, frame: InboundFrame) => void;
30
+ /** Observe every inbound frame on every managed account (see Account). */
31
+ onReceive?: (accountId: string, frame: RawFrame) => void;
25
32
  /** Seed each account's state cache after auth (see Account). Default true. */
26
33
  seedState?: boolean;
27
34
  /**
@@ -94,8 +101,19 @@ export interface SpacemoltClientOptions {
94
101
  * Generate one from the website; keep it secret.
95
102
  */
96
103
  clerkApiKey?: string;
97
- /** Inject a `fetch` implementation (tests, custom runtimes). */
104
+ /**
105
+ * Inject a `fetch` implementation (tests, custom runtimes). The per-call
106
+ * timeout is delivered via `init.signal` (an `AbortSignal`), not a
107
+ * separate argument — a wrapper must pass `init` through to the real
108
+ * `fetch` unchanged or it silently loses the timeout.
109
+ */
98
110
  fetchImpl?: typeof fetch;
111
+ /**
112
+ * Abort each HTTP data fetch (catalog, map, stations, mobile base) after this
113
+ * many ms, body included. Default 30000. Raise it on a slow link: the catalog
114
+ * is several MB.
115
+ */
116
+ httpTimeoutMs?: number;
99
117
  /**
100
118
  * How long to wait for the server's `welcome` frame (post-WS-upgrade) and
101
119
  * for a `logged_in`/error response to an auth attempt, before giving up.
@@ -196,6 +214,7 @@ export declare class SpacemoltClient {
196
214
  private notifyAccountDisconnected;
197
215
  /** HTTP origin used for bulk data fetches (catalog, map). */
198
216
  get httpBaseUrl(): string;
217
+ private get httpOptions();
199
218
  /**
200
219
  * The bulk catalog. Fetched on first use and cached, then revalidated against
201
220
  * the server once the cache is older than `catalogMaxAgeMs` (default 1h) so a
@@ -207,6 +226,10 @@ export declare class SpacemoltClient {
207
226
  catalog(force?: boolean): Promise<CatalogCache>;
208
227
  /** The static galaxy map, fetched once and cached. Pass `force` to refetch. */
209
228
  map(force?: boolean): Promise<MapCache>;
229
+ /** The live station directory. Not cached — it changes as the game runs. */
230
+ stations(): Promise<StationList>;
231
+ /** The mobile base's current system. Not cached — it moves. */
232
+ mobileBase(): Promise<MobileBaseLocation>;
210
233
  /** The credential store backing this client. */
211
234
  get credentialStore(): CredentialStore;
212
235
  /** Persist username/password credentials. Returns the account id (username). */
@@ -1,4 +1,5 @@
1
1
  import type { CatalogAchievement, CatalogDump, FacilityDefinition, Item, Module, Recipe, ShipClass, SkillDefinition } from '../generated/openapi/types.gen.js';
2
+ import { type FetchOptions } from './http.js';
2
3
  /**
3
4
  * Local copy of the bulk game catalog (`GET /api/catalog.json`).
4
5
  *
@@ -52,10 +53,14 @@ export interface CatalogFetchResult {
52
53
  * `etag` is passed and the server confirms the catalog is unchanged, this
53
54
  * returns `{ notModified: true }` after a ~0-byte `304` — no multi-MB download
54
55
  * or re-parse. Otherwise it returns the fresh catalog and its new `ETag`.
56
+ *
57
+ * Bounded by a request timeout and retried on a transient `429`/`503`; a
58
+ * failure that isn't a 304 throws `HttpError`. Pass `fetchImpl` to inject a
59
+ * `fetch` (tests, or an observability wrapper — see `httpGet`).
55
60
  */
56
- export declare function fetchCatalogConditional(httpBaseUrl: string, etag?: string): Promise<CatalogFetchResult>;
57
- /** Unconditionally fetch and normalize the catalog. */
58
- export declare function fetchCatalog(httpBaseUrl: string): Promise<Catalog>;
61
+ export declare function fetchCatalogConditional(httpBaseUrl: string, etag?: string, opts?: FetchOptions): Promise<CatalogFetchResult>;
62
+ /** Unconditionally fetch and normalize the catalog. See `fetchCatalogConditional` for `fetchImpl`. */
63
+ export declare function fetchCatalog(httpBaseUrl: string, opts?: FetchOptions): Promise<Catalog>;
59
64
  export declare class CatalogCache {
60
65
  readonly catalog: Catalog;
61
66
  /** The server `ETag` this catalog was fetched with, for conditional reloads. */
@@ -70,13 +75,13 @@ export declare class CatalogCache {
70
75
  /** The server `ETag` this catalog was fetched with, for conditional reloads. */
71
76
  etag?: string | undefined);
72
77
  /** Fetch the catalog and wrap it in a cache. */
73
- static load(httpBaseUrl: string): Promise<CatalogCache>;
78
+ static load(httpBaseUrl: string, opts?: FetchOptions): Promise<CatalogCache>;
74
79
  /**
75
80
  * Conditionally refresh via `If-None-Match`. Returns `this` unchanged when the
76
81
  * server confirms the catalog is still current (a cheap `304`), or a new
77
82
  * `CatalogCache` when the catalog has changed.
78
83
  */
79
- revalidate(httpBaseUrl: string): Promise<CatalogCache>;
84
+ revalidate(httpBaseUrl: string, opts?: FetchOptions): Promise<CatalogCache>;
80
85
  /** Server release this catalog was built for; `''` if the response omitted it. */
81
86
  get version(): string;
82
87
  ship(id: string): CatalogShip | undefined;
@@ -0,0 +1,36 @@
1
+ /** Options every public data fetcher accepts. */
2
+ export interface FetchOptions {
3
+ /** Inject a `fetch` implementation (tests, observability). Defaults to global `fetch`. */
4
+ fetchImpl?: typeof fetch;
5
+ /** Abort each request after this many ms, body included. Default 30000. */
6
+ timeoutMs?: number;
7
+ }
8
+ export interface HttpGetOptions {
9
+ headers?: Record<string, string>;
10
+ /** Abort the request after this many ms (`AbortSignal.timeout`). Default 30000. */
11
+ timeoutMs?: number;
12
+ /**
13
+ * Inject a `fetch` implementation — tests, custom runtimes, or an
14
+ * observability wrapper (status/bytes/timing/retries) a consumer supplies
15
+ * via `SpacemoltClientOptions.fetchImpl` / the per-call `fetchImpl` option.
16
+ * Defaults to global `fetch`.
17
+ */
18
+ fetchImpl?: typeof fetch;
19
+ /** Response statuses besides 2xx to return rather than treat as a failure (e.g. 304). */
20
+ okStatuses?: readonly number[];
21
+ /**
22
+ * Total (un-jittered) base retry wait allowed across every attempt before
23
+ * giving up — not re-checked per attempt in isolation. Default
24
+ * `MAX_RETRY_WAIT_MS` (60000). Mainly for tests that need a small cap to
25
+ * exercise this without waiting real seconds.
26
+ */
27
+ maxRetryWaitMs?: number;
28
+ }
29
+ /**
30
+ * `GET url`, bounded by `timeoutMs` (headers and body both) and retried up to
31
+ * `MAX_RETRIES` times on `429`/`503` (waiting `Retry-After` when present,
32
+ * else a short backoff — either way, jittered). A timeout or network error is
33
+ * not retried — it throws the native error as-is, not an `HttpError`. Any
34
+ * other non-ok, non-`okStatuses` response throws an `HttpError`.
35
+ */
36
+ export declare function httpGet(url: string, opts?: HttpGetOptions): Promise<Response>;
@@ -1,4 +1,5 @@
1
1
  import type { MapData, MapDataSystem } from '../generated/openapi/types.gen.js';
2
+ import { type FetchOptions } from './http.js';
2
3
  /**
3
4
  * Local copy of the static galaxy map (`GET /api/map`).
4
5
  *
@@ -15,13 +16,13 @@ import type { MapData, MapDataSystem } from '../generated/openapi/types.gen.js';
15
16
  /** A system entry from `GET /api/map`. */
16
17
  export type MapSystem = MapDataSystem;
17
18
  export type GalaxyMap = MapData;
18
- export declare function fetchMap(httpBaseUrl: string): Promise<GalaxyMap>;
19
+ export declare function fetchMap(httpBaseUrl: string, opts?: FetchOptions): Promise<GalaxyMap>;
19
20
  export declare class MapCache {
20
21
  readonly map: GalaxyMap;
21
22
  private readonly bySystemId;
22
23
  constructor(map: GalaxyMap);
23
24
  /** Fetch the galaxy map and wrap it in a cache. */
24
- static load(httpBaseUrl: string): Promise<MapCache>;
25
+ static load(httpBaseUrl: string, opts?: FetchOptions): Promise<MapCache>;
25
26
  system(id: string): MapSystem | undefined;
26
27
  get systems(): readonly MapSystem[];
27
28
  /** Empire id -> display colour. */
@@ -1,4 +1,5 @@
1
1
  import type { MobileBaseLocation } from '../generated/openapi/types.gen.js';
2
+ import { type FetchOptions } from './http.js';
2
3
  /**
3
4
  * Where the galaxy's single moving capital currently is
4
5
  * (`GET /wheres-mobile-base`).
@@ -8,6 +9,6 @@ import type { MobileBaseLocation } from '../generated/openapi/types.gen.js';
8
9
  * is the spec's published `MobileBaseLocation` (`{ system }`), a system id that
9
10
  * indexes into the galaxy map.
10
11
  */
11
- /** Fetch the mobile base's current system id. */
12
- export declare function fetchMobileBase(httpBaseUrl: string): Promise<MobileBaseLocation>;
12
+ /** Fetch the mobile base's current system id. Pass `fetchImpl` to inject a `fetch` (tests, observability). */
13
+ export declare function fetchMobileBase(httpBaseUrl: string, opts?: FetchOptions): Promise<MobileBaseLocation>;
13
14
  export type { MobileBaseLocation };
@@ -1,3 +1,4 @@
1
+ import { type FetchOptions } from './http.js';
1
2
  /**
2
3
  * Station directory (`GET /api/stations`).
3
4
  *
@@ -53,5 +54,5 @@ export interface StationList {
53
54
  stations: StationSummary[];
54
55
  empires: StationEmpire[];
55
56
  }
56
- /** Fetch the public station directory. */
57
- export declare function fetchStations(httpBaseUrl: string): Promise<StationList>;
57
+ /** Fetch the public station directory. Pass `fetchImpl` to inject a `fetch` (tests, observability). */
58
+ export declare function fetchStations(httpBaseUrl: string, opts?: FetchOptions): Promise<StationList>;
package/dist/errors.d.ts CHANGED
@@ -24,6 +24,25 @@ export declare class SpacemoltError extends Error {
24
24
  }
25
25
  export declare function errorFromFrame(frame: ErrorFrame): SpacemoltError;
26
26
  export declare function errorFromActionFrame(frame: ActionErrorFrame): SpacemoltError;
27
+ /**
28
+ * Raised by a bulk/live HTTP data fetch (catalog, map, stations, mobile-base)
29
+ * that didn't come back ok — after retries, for a retryable status. Message
30
+ * text matches the old bare-`Error` format (`GET <url> -> <status> <text>`).
31
+ */
32
+ export declare class HttpError extends Error {
33
+ readonly status: number;
34
+ readonly url: string;
35
+ /** How many requests were made in total, including the one that finally failed. */
36
+ readonly attempts: number;
37
+ /** The server's `Retry-After` on the failing response, parsed to ms, if any. */
38
+ readonly retryAfterMs?: number;
39
+ constructor(message: string, opts: {
40
+ status: number;
41
+ url: string;
42
+ attempts: number;
43
+ retryAfterMs?: number;
44
+ });
45
+ }
27
46
  /** Raised against every in-flight request when the socket closes. */
28
47
  export declare class ConnectionClosedError extends Error {
29
48
  readonly code?: number;
@@ -1,5 +1,5 @@
1
1
  /** The gameserver spec version this command surface was generated from. */
2
- export declare const GENERATED_SPEC_VERSION = "v0.612.0";
2
+ export declare const GENERATED_SPEC_VERSION = "v0.613.4";
3
3
  export type ToolName = "spacemolt" | "spacemolt_arena" | "spacemolt_auth" | "spacemolt_battle" | "spacemolt_citizenship" | "spacemolt_drone" | "spacemolt_facility" | "spacemolt_faction" | "spacemolt_faction_admin" | "spacemolt_faction_commerce" | "spacemolt_fleet" | "spacemolt_intel" | "spacemolt_market" | "spacemolt_salvage" | "spacemolt_ship" | "spacemolt_shipping" | "spacemolt_social" | "spacemolt_storage" | "spacemolt_transfer";
4
4
  export type ActionName = "abandon_mission" | "accept" | "accept_ally" | "accept_invite" | "accept_mission" | "accept_peace" | "active" | "advance" | "allow_faction" | "allow_player" | "analyze_market" | "apply" | "attack" | "ban" | "base_cost" | "board" | "browse_for_sale" | "browse_ships" | "build" | "buy" | "buy_listed_ship" | "buy_listing" | "buy_ship_license" | "cancel" | "cancel_commission" | "cancel_listing" | "cancel_mission" | "cancel_order" | "cancel_ship_buy_order" | "cancel_ship_listing" | "captains_log_add" | "captains_log_delete" | "captains_log_get" | "captains_log_list" | "challenge" | "challenges" | "chat" | "claim" | "claim_prize" | "cloak" | "commission_quote" | "commission_ship" | "commission_status" | "complete_mission" | "completed_missions" | "craft" | "create" | "create_buy_order" | "create_note" | "create_role" | "create_sell_order" | "declare_war" | "decline" | "decline_invite" | "decline_mission" | "delete_note" | "delete_role" | "delete_room" | "deliver" | "deploy" | "deploy_outpost" | "deposit" | "disband" | "disembark" | "dismantle" | "dismantle_outpost" | "distress_signal" | "dock" | "edit" | "edit_role" | "engage" | "espionage" | "estimate_purchase" | "facility_set_description" | "faction_build" | "faction_dismantle" | "faction_list" | "faction_owned" | "faction_personnel" | "faction_upgrade" | "fight" | "find_route" | "forum_create_thread" | "forum_delete_reply" | "forum_delete_thread" | "forum_get_thread" | "forum_list" | "forum_reply" | "forum_upvote" | "found_station" | "garages" | "get" | "get_achievements" | "get_action_log" | "get_active_missions" | "get_base" | "get_cargo" | "get_chat_history" | "get_commands" | "get_empire_info" | "get_faction_achievements" | "get_guide" | "get_invites" | "get_location" | "get_map" | "get_missions" | "get_nearby" | "get_notes" | "get_notification_settings" | "get_notifications" | "get_player" | "get_poi" | "get_queue" | "get_ship" | "get_skills" | "get_state" | "get_status" | "get_system" | "get_system_agents" | "get_tax_estimate" | "get_trades" | "get_version" | "hunt" | "info" | "inspect" | "install_mod" | "insure" | "intel_status" | "invite" | "jettison" | "job_add" | "job_cancel" | "job_list" | "job_reorder" | "join" | "jump" | "kick" | "leave" | "list" | "list_for_sale" | "list_missions" | "list_passengers" | "list_ship_for_sale" | "list_ships" | "list_station_passengers" | "load" | "load_passenger" | "log" | "login" | "login_link" | "login_link_poll" | "login_token" | "logout" | "loot" | "mine" | "modify_order" | "mute_notifications" | "name" | "owned" | "pay_bounty" | "pay_debt" | "personal_build" | "personal_decorate" | "personal_visit" | "petition" | "place_ship_buy_order" | "policies" | "post" | "post_mission" | "prepay_tax" | "profile" | "promote" | "propose_ally" | "propose_peace" | "query_intel" | "query_trade_intel" | "quote" | "ranch_set_cull" | "ranch_status" | "read_note" | "recall" | "recruit_personnel" | "recycle" | "refit_ship" | "refuel" | "register" | "release" | "reload" | "remove_ally" | "remove_enemy" | "remove_faction" | "remove_player" | "rename_ship" | "renounce" | "repair" | "retreat" | "return" | "rooms" | "scan" | "scan_poi" | "scrap" | "scrap_ship" | "search_systems" | "self_destruct" | "sell" | "sell_ship_to_order" | "service_prize" | "set_access" | "set_auto_buy_fuel" | "set_build_policy" | "set_colors" | "set_description" | "set_enemy" | "set_home" | "set_market_fee" | "set_name" | "set_output_price" | "set_public" | "set_refuel_price" | "set_repair_price" | "set_service_access" | "set_status" | "stance" | "station_info" | "station_set_name" | "status" | "submit_intel" | "submit_trade_intel" | "subscribe_market" | "subscribe_observation" | "summary" | "supply_commission" | "survey_system" | "switch_ship" | "target" | "tax_estimate" | "tow" | "track" | "trade_accept" | "trade_cancel" | "trade_decline" | "trade_intel_status" | "trade_offer" | "transfer" | "transfer_personnel" | "travel" | "treat_personnel" | "types" | "unban" | "undock" | "uninstall_mod" | "unload" | "unload_passenger" | "unmute_notifications" | "unsubscribe_market" | "unsubscribe_observation" | "upgrade" | "upgrades" | "upload" | "use_item" | "view" | "view_completed_mission" | "view_market" | "view_orders" | "view_ship_buy_orders" | "visit_room" | "withdraw" | "withdraw_invite" | "wrecks" | "write_note" | "write_room";
5
5
  export interface ActionParam {
@@ -2877,7 +2877,7 @@ export type DockResponse = {
2877
2877
  commissions_active?: Array<CommissionActive>;
2878
2878
  facility_note?: string;
2879
2879
  fuel_warning?: string;
2880
- gifts?: Array<GiftNotification>;
2880
+ gifts?: Array<StorageGift>;
2881
2881
  gifts_count?: number;
2882
2882
  gifts_note?: string;
2883
2883
  gifts_truncated?: boolean;
@@ -5069,14 +5069,6 @@ export type GetWrecksResponse = {
5069
5069
  count: number;
5070
5070
  wrecks: Array<EnrichedWreck>;
5071
5071
  };
5072
- export type GiftNotification = {
5073
- credits?: number;
5074
- items?: Array<ItemQuantity>;
5075
- message?: string;
5076
- sender: string;
5077
- sender_id: string;
5078
- timestamp: string;
5079
- };
5080
5072
  export type GiftShipResponse = {
5081
5073
  action: 'gift_ship';
5082
5074
  /**
@@ -5166,6 +5158,9 @@ export type InspectPackageShipment = {
5166
5158
  };
5167
5159
  export type InspectResponse = {
5168
5160
  base?: GetBaseResponse;
5161
+ /**
5162
+ * Present only when source is catalog. kind item or module → type items, entries are Item or Module. kind ship_class → type ships, entries are ShipClass. Other catalog types are not returned here.
5163
+ */
5169
5164
  catalog?: {
5170
5165
  analysis?: RecipeAnalysis;
5171
5166
  estimated_material_cost?: number;
@@ -5704,6 +5699,10 @@ export type MarketInsight = {
5704
5699
  };
5705
5700
  export type MarketItemBook = {
5706
5701
  buy_orders: Array<MarketLevel>;
5702
+ /**
5703
+ * Item category of this item_id such as ore; refined; component; or module for a ship module. It is the category view_market reports for the same item_id. Omitted when the definition is no longer known.
5704
+ */
5705
+ category?: string;
5707
5706
  item_id: string;
5708
5707
  item_name?: string;
5709
5708
  sell_orders: Array<MarketLevel>;
@@ -10908,7 +10907,7 @@ export type V2Response = {
10908
10907
  */
10909
10908
  error?: {
10910
10909
  /**
10911
- * Machine-readable error code. Session/auth codes: session_required (no X-Session-Id header), session_invalid (session not found or expired — create a new one with POST /api/v2/session then login), not_authenticated (session exists but not logged in). Other codes: rate_limited, command_error, invalid_params, invalid_json, payload_too_large, method_not_allowed, missing_action, unknown_command.
10910
+ * Machine-readable error code. Session/auth codes: session_required (no X-Session-Id header), session_invalid (session not found or expired — create a new one with POST /api/v2/session then login), not_authenticated (session exists but not logged in). Other codes: rate_limited, command_error, invalid_params, invalid_json, payload_too_large, method_not_allowed, missing_action, unknown_command. ip_timed_out is WebSocket-only: over HTTP an IP block returns a flat 429 body without this envelope: {"error": "rate_limited", "message", "retry_after", "limit": "ip_timeout", "scope": "ip"} plus a Retry-After header.
10912
10911
  */
10913
10912
  code?: string;
10914
10913
  /**
@@ -10922,7 +10921,7 @@ export type V2Response = {
10922
10921
  */
10923
10922
  message?: string;
10924
10923
  /**
10925
- * Seconds to wait before retrying. Present on rate_limited errors.
10924
+ * Seconds to wait before retrying. Present on rate_limited errors from a rate-limit bucket (failed-login and bug-report rate_limited errors omit it), with the same value in the Retry-After header. details then carries limit (the bucket), scope (what the bucket is keyed on) and, for capped buckets, limit_per_min and current.
10926
10925
  */
10927
10926
  retry_after?: number;
10928
10927
  };
package/dist/index.d.ts CHANGED
@@ -10,17 +10,18 @@
10
10
  * subsequent milestones.
11
11
  */
12
12
  export * from './protocol.js';
13
- export { Account, type AccountOptions, type ReconnectOptions, type RegisterParams, type RegisterResult, type LoggedInPayload, } from './account.js';
13
+ export { Account, type AccountOptions, type ReconnectOptions, type RegisterParams, type RegisterResult, type LoggedInPayload, type RateLimitedInfo, } from './account.js';
14
14
  export { SpacemoltClient, type SpacemoltClientOptions } from './client.js';
15
15
  export { ClerkSource, mintWsToken, type ClerkAuth, type ClerkAuthHeaders, type ClerkPlayer, type ClerkRegistration, type ClerkSourceOptions, } from './auth/clerk.js';
16
16
  export { type CredentialStore, type StoredAccount, type AuthCredentials, MemoryCredentialStore, } from './auth/credentials.js';
17
- export { SpacemoltError, ConnectionClosedError, CLOSE_CODE, retryAfterMsFromClose } from './errors.js';
17
+ export { SpacemoltError, ConnectionClosedError, HttpError, CLOSE_CODE, retryAfterMsFromClose } from './errors.js';
18
18
  export { StateCache } from './state/cache.js';
19
19
  export { MarketCache, type MarketBook, type MarketItem } from './state/market.js';
20
20
  export { ObservationCache, type ObservationView, type ObservedPlayer, type CloakedContact, } from './state/observation.js';
21
21
  export { TypedEmitter, EventStream } from './events/emitter.js';
22
22
  export { type Commands, type CommandDispatch, buildCommands } from './generated/commands.gen.js';
23
23
  export { CatalogCache, fetchCatalog, fetchCatalogConditional, type Catalog, type CatalogAchievement, type CatalogFacility, type CatalogFetchResult, type CatalogItem, type CatalogRecipe, type CatalogShip, type CatalogSkill, } from './data/catalog.js';
24
+ export type { FetchOptions } from './data/http.js';
24
25
  export { MapCache, fetchMap, httpBaseFromWs, type GalaxyMap, type MapSystem } from './data/map.js';
25
26
  export { fetchStations, type StationEmpire, type StationList, type StationSummary } from './data/stations.js';
26
27
  export { fetchMobileBase, type MobileBaseLocation } from './data/mobile-base.js';