@spacemolt/lib 4.6.0
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/AGENTS.md +118 -0
- package/COMMANDS.md +386 -0
- package/LICENSE +21 -0
- package/README.md +298 -0
- package/dist/account.d.ts +389 -0
- package/dist/auth/clerk.d.ts +58 -0
- package/dist/auth/credentials.d.ts +52 -0
- package/dist/auth/file-store.d.ts +20 -0
- package/dist/client.d.ts +305 -0
- package/dist/data/catalog.d.ts +39 -0
- package/dist/data/map.d.ts +30 -0
- package/dist/errors.d.ts +52 -0
- package/dist/events/emitter.d.ts +58 -0
- package/dist/generated/actions.gen.d.ts +25 -0
- package/dist/generated/commands.gen.d.ts +1790 -0
- package/dist/generated/notifications.gen.d.ts +41 -0
- package/dist/generated/openapi/index.d.ts +1 -0
- package/dist/generated/openapi/types.gen.d.ts +19080 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +4638 -0
- package/dist/node.d.ts +8 -0
- package/dist/node.js +49 -0
- package/dist/protocol.d.ts +178 -0
- package/dist/state/cache.d.ts +43 -0
- package/dist/state/market.d.ts +30 -0
- package/dist/state/observation.d.ts +38 -0
- package/dist/transport/correlator.d.ts +54 -0
- package/dist/transport/socket.d.ts +60 -0
- package/llms.txt +23 -0
- package/package.json +49 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-account client.
|
|
3
|
+
*
|
|
4
|
+
* Manages N authenticated `Account` connections, persisting their credentials
|
|
5
|
+
* through a pluggable `CredentialStore`. Connecting many accounts is staggered
|
|
6
|
+
* to stay under the server's per-IP login/connection rate limits — the server
|
|
7
|
+
* itself advises "reuse one session per bot for its lifetime", so the intended
|
|
8
|
+
* pattern is: add accounts once, `connectAll()` at startup, keep them open.
|
|
9
|
+
*/
|
|
10
|
+
import { Account } from './account.js';
|
|
11
|
+
import { type AuthCredentials, type CredentialStore, type StoredAccount } from './auth/credentials.js';
|
|
12
|
+
import type { WebSocketFactory } from './transport/socket.js';
|
|
13
|
+
import type { ReconnectOptions, RegisterParams, RegisterResult } from './account.js';
|
|
14
|
+
import { CatalogCache } from './data/catalog.js';
|
|
15
|
+
import { MapCache } from './data/map.js';
|
|
16
|
+
import { type ClerkPlayer } from './auth/clerk.js';
|
|
17
|
+
import { type ConnectionClosedError } from './errors.js';
|
|
18
|
+
export interface SpacemoltClientOptions {
|
|
19
|
+
/** WebSocket URL of the v2 endpoint. */
|
|
20
|
+
url?: string;
|
|
21
|
+
/** Where credentials live. Defaults to an in-memory store. */
|
|
22
|
+
store?: CredentialStore;
|
|
23
|
+
/** Inject a WebSocket implementation (tests, custom runtimes). */
|
|
24
|
+
webSocketFactory?: WebSocketFactory;
|
|
25
|
+
/** Seed each account's state cache after auth (see Account). Default true. */
|
|
26
|
+
seedState?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Delay between connecting accounts. Default is derived from
|
|
29
|
+
* `connectBatchSize` (`60_000 / connectBatchSize` — 600ms for the default
|
|
30
|
+
* batch size of 100), guaranteeing a batch can't finish in under a minute
|
|
31
|
+
* regardless of how fast each connect() actually completes — a flat
|
|
32
|
+
* 250ms doesn't guarantee that (see `connectIds`). Set explicitly to
|
|
33
|
+
* override that computed default with your own value (trusted as-is, not
|
|
34
|
+
* a floor) — e.g. for a smaller test fleet where finishing fast is fine.
|
|
35
|
+
*/
|
|
36
|
+
connectStaggerMs?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Max accounts to connect before pausing for `connectBatchWaitMs`, so a
|
|
39
|
+
* fleet never actually trips the server's per-IP WS-connection rate limit
|
|
40
|
+
* (hitting it risks an IP-level timeout/ban on repeat offense — better to
|
|
41
|
+
* never ask). Matches the server's default connection cap (100/min), so a
|
|
42
|
+
* fleet at or under this size behaves exactly like a single stagger pass,
|
|
43
|
+
* same as before; a larger fleet connects in batches of this size instead.
|
|
44
|
+
* Default 100.
|
|
45
|
+
*/
|
|
46
|
+
connectBatchSize?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Pause between batches once `connectBatchSize` is exceeded, letting the
|
|
49
|
+
* server's per-IP rate-limit window (1 minute by default) fully roll over
|
|
50
|
+
* before the next batch starts. Default 65000 (65s — a margin over the
|
|
51
|
+
* server's 1-minute window).
|
|
52
|
+
*/
|
|
53
|
+
connectBatchWaitMs?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Reconnect an account after an unexpected disconnect (a dropped
|
|
56
|
+
* WebSocket after it was already connected — see `connectRetry` for the
|
|
57
|
+
* initial connect instead). Default `true`. Driven by the client itself
|
|
58
|
+
* (not each `Account`'s own standalone reconnect logic), reconnecting the
|
|
59
|
+
* *same* `Account` instance in place (`Account.reconnectOnce` — fresh
|
|
60
|
+
* socket, re-authenticated, subscriptions restored) rather than replacing
|
|
61
|
+
* it, so anything holding a direct reference (e.g. a running loop) keeps
|
|
62
|
+
* working transparently. Paced through the same rate-limited queue used
|
|
63
|
+
* for the initial connect — see `handleAccountDisconnected` — so a mass
|
|
64
|
+
* disconnect (e.g. every account dropped at once by a game-server restart)
|
|
65
|
+
* reconnects the fleet through the same `connectBatchSize`/
|
|
66
|
+
* `connectStaggerMs`/`connectBatchWaitMs` pacing, instead of every account
|
|
67
|
+
* racing an independent timer. Pass `false` to leave a dropped account
|
|
68
|
+
* disconnected instead (see `onAccountDisconnected`). If you pass a
|
|
69
|
+
* `ReconnectOptions` object, only its truthiness matters — reconnect
|
|
70
|
+
* pacing comes from `connectRetry` (the same backoff config used for the
|
|
71
|
+
* initial connect), not this option's own `maxRetries`/`baseDelayMs`/
|
|
72
|
+
* `maxDelayMs`. Never reconnects after a deliberate `close()`/`remove()`,
|
|
73
|
+
* a `session_replaced`, or an `auth_timeout`. Token-only accounts can't
|
|
74
|
+
* reconnect (the token is single-use).
|
|
75
|
+
*/
|
|
76
|
+
reconnect?: boolean | ReconnectOptions;
|
|
77
|
+
/** HTTP origin for bulk data fetches. Defaults to the origin of `url`. */
|
|
78
|
+
httpBaseUrl?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Clerk API key for `connectOwned()` / `listOwnedPlayers()` — connect every
|
|
81
|
+
* game account the Clerk user owns without storing per-account passwords.
|
|
82
|
+
* Generate one from the website; keep it secret.
|
|
83
|
+
*/
|
|
84
|
+
clerkApiKey?: string;
|
|
85
|
+
/** Inject a `fetch` implementation (tests, custom runtimes). */
|
|
86
|
+
fetchImpl?: typeof fetch;
|
|
87
|
+
/**
|
|
88
|
+
* How long to wait for the server's `welcome` frame (post-WS-upgrade) and
|
|
89
|
+
* for a `logged_in`/error response to an auth attempt, before giving up.
|
|
90
|
+
* Without this, a connection the server silently never completes (accepts
|
|
91
|
+
* the WS upgrade but never responds) hangs forever with no error and no
|
|
92
|
+
* retry — this can't be told apart from "still connecting" from the
|
|
93
|
+
* outside. Default 15000 (15s — generous over the ~150-300ms a healthy
|
|
94
|
+
* connect actually takes, per live measurement).
|
|
95
|
+
*/
|
|
96
|
+
connectTimeoutMs?: number;
|
|
97
|
+
/**
|
|
98
|
+
* How long to wait for a query's response before giving up. Unlike a
|
|
99
|
+
* mutation (which can legitimately take minutes, e.g. a travel/jump's
|
|
100
|
+
* transit time), a query has no legitimate reason to take long — bounding
|
|
101
|
+
* it turns a silently-dropped response into a clean error instead of a
|
|
102
|
+
* permanent hang. Default 15000 (15s).
|
|
103
|
+
*/
|
|
104
|
+
queryTimeoutMs?: number;
|
|
105
|
+
/**
|
|
106
|
+
* How long to wait, after a mutation's pending ack arrives, for its final
|
|
107
|
+
* `action_result`/`action_error` outcome before giving up. Generous by
|
|
108
|
+
* design — some mutations legitimately take many ticks (a jump/travel's
|
|
109
|
+
* transit time is distance-based and can run minutes) — but not infinite:
|
|
110
|
+
* without this, a single silently-dropped outcome frame hangs forever AND
|
|
111
|
+
* wedges every subsequent mutation on that account behind it (mutations
|
|
112
|
+
* are serialized per account). Default 600000 (10 minutes).
|
|
113
|
+
*/
|
|
114
|
+
mutationTimeoutMs?: number;
|
|
115
|
+
/**
|
|
116
|
+
* Fallback safety net: retry a failed `connect()` (the raw WebSocket
|
|
117
|
+
* handshake, before any auth frame) with backoff instead of dropping the
|
|
118
|
+
* account. `connectBatchSize`/`connectBatchWaitMs` above are the primary
|
|
119
|
+
* defense — they're sized to avoid ever tripping the server's per-IP
|
|
120
|
+
* WS-connection rate limit in the first place — but this covers the
|
|
121
|
+
* unexpected case anyway (e.g. other traffic sharing the IP eating into
|
|
122
|
+
* the budget), since a 429 on the handshake surfaces as a plain
|
|
123
|
+
* `ConnectionClosedError` — not a `SpacemoltError` with `code:
|
|
124
|
+
* 'rate_limited'` — so it can't be told apart from a genuine connection
|
|
125
|
+
* failure and isn't covered by `Account`'s targeted rate-limit retry.
|
|
126
|
+
* Default `true` (enabled). Pass `false` to disable and fail fast instead.
|
|
127
|
+
*/
|
|
128
|
+
connectRetry?: boolean | ReconnectOptions;
|
|
129
|
+
}
|
|
130
|
+
export declare class SpacemoltClient {
|
|
131
|
+
private readonly opts;
|
|
132
|
+
private readonly store;
|
|
133
|
+
private readonly connected;
|
|
134
|
+
private catalogCache?;
|
|
135
|
+
private mapCache?;
|
|
136
|
+
private clerkSource?;
|
|
137
|
+
private readonly accountConnectedListeners;
|
|
138
|
+
private readonly accountReconnectedListeners;
|
|
139
|
+
private readonly accountDisconnectedListeners;
|
|
140
|
+
/** Paced by connectBatchSize/connectStaggerMs/connectBatchWaitMs — the same limiter drains both the initial fleet connect and later reconnects, see `enqueueRateLimited`. */
|
|
141
|
+
private readonly rateLimitedQueue;
|
|
142
|
+
private rateLimitedQueueDraining;
|
|
143
|
+
/**
|
|
144
|
+
* Ids currently mid-reconnect. Guards against a socket dropping again
|
|
145
|
+
* *during* `reconnectAccountInPlace` (e.g. another rate-limit close before
|
|
146
|
+
* auth completes) re-firing `handleAccountDisconnected` and enqueuing a
|
|
147
|
+
* second, parallel reconnect chain for the same id — the drop instead
|
|
148
|
+
* surfaces as a failure inside the one already-running `withConnectRetry`
|
|
149
|
+
* loop, which retries it itself.
|
|
150
|
+
*/
|
|
151
|
+
private readonly reconnectingIds;
|
|
152
|
+
constructor(opts?: SpacemoltClientOptions);
|
|
153
|
+
/**
|
|
154
|
+
* Fires the first time an id becomes a connected+authenticated `Account`
|
|
155
|
+
* instance — the initial connect (`connect`/`connectAll`/`connectOwned`/
|
|
156
|
+
* `register`). Does NOT fire again for a later reconnect after an
|
|
157
|
+
* unexpected disconnect — that reuses the same instance in place (see
|
|
158
|
+
* `Account.reconnectOnce`) rather than creating a new one, so there's
|
|
159
|
+
* nothing new to index/re-wire; see `onAccountReconnected` for that event
|
|
160
|
+
* instead. Unlike `connectAll`'s/`connectOwned`'s per-call `onConnect`
|
|
161
|
+
* option (scoped to that one call), this is a persistent subscription.
|
|
162
|
+
* Returns an unsubscribe function.
|
|
163
|
+
*/
|
|
164
|
+
onAccountConnected(listener: (account: Account) => void): () => void;
|
|
165
|
+
/**
|
|
166
|
+
* Fires after an already-connected account's connection is restored
|
|
167
|
+
* following an unexpected disconnect — the same `Account` instance
|
|
168
|
+
* throughout, reconnected in place. Purely informational (e.g. logging);
|
|
169
|
+
* nothing needs re-indexing since the instance never changed. Returns an
|
|
170
|
+
* unsubscribe function.
|
|
171
|
+
*/
|
|
172
|
+
onAccountReconnected(listener: (account: Account) => void): () => void;
|
|
173
|
+
/**
|
|
174
|
+
* Fires when an account is dropped for good: a terminal close (session
|
|
175
|
+
* replaced by another connection, or an auth timeout) that this client
|
|
176
|
+
* deliberately does not reconnect after, or a reconnect attempt that
|
|
177
|
+
* exhausted its retries. Silent otherwise today — this is the only
|
|
178
|
+
* visibility a caller has into that. Returns an unsubscribe function.
|
|
179
|
+
*/
|
|
180
|
+
onAccountDisconnected(listener: (id: string, err: ConnectionClosedError) => void): () => void;
|
|
181
|
+
private notifyAccountConnected;
|
|
182
|
+
private notifyAccountReconnected;
|
|
183
|
+
private notifyAccountDisconnected;
|
|
184
|
+
/** HTTP origin used for bulk data fetches (catalog, map). */
|
|
185
|
+
get httpBaseUrl(): string;
|
|
186
|
+
/** The bulk catalog, fetched once and cached. Pass `force` to refetch. */
|
|
187
|
+
catalog(force?: boolean): Promise<CatalogCache>;
|
|
188
|
+
/** The static galaxy map, fetched once and cached. Pass `force` to refetch. */
|
|
189
|
+
map(force?: boolean): Promise<MapCache>;
|
|
190
|
+
/** The credential store backing this client. */
|
|
191
|
+
get credentialStore(): CredentialStore;
|
|
192
|
+
/** Persist username/password credentials. Returns the account id (username). */
|
|
193
|
+
addLogin(username: string, password: string): Promise<string>;
|
|
194
|
+
/** Persist a login-token credential under an explicit id. */
|
|
195
|
+
addToken(id: string, token: string): Promise<string>;
|
|
196
|
+
/**
|
|
197
|
+
* Register a brand-new account: connect, register, and persist the generated
|
|
198
|
+
* password as login credentials (keyed by username) for future sessions.
|
|
199
|
+
*/
|
|
200
|
+
register(params: RegisterParams): Promise<{
|
|
201
|
+
account: Account;
|
|
202
|
+
result: RegisterResult;
|
|
203
|
+
}>;
|
|
204
|
+
/** Resolves `connectRetry` into a concrete backoff config. */
|
|
205
|
+
private connectRetryConfig;
|
|
206
|
+
/**
|
|
207
|
+
* Retry `attempt` with the shared `connectRetry` backoff, honoring a 4003
|
|
208
|
+
* `connection_rate_limited` close's `retry_after` hint over the
|
|
209
|
+
* exponential fallback. Shared by the initial connect (`connect`) and an
|
|
210
|
+
* in-place reconnect (`reconnectAccountInPlace`) so both back off
|
|
211
|
+
* identically.
|
|
212
|
+
*/
|
|
213
|
+
private withConnectRetry;
|
|
214
|
+
/**
|
|
215
|
+
* Connect and authenticate one stored account. Idempotent per id.
|
|
216
|
+
*
|
|
217
|
+
* Retries the whole connect+authenticate sequence with backoff on failure
|
|
218
|
+
* (see `connectRetry`) — a fleet large enough to exceed the per-IP
|
|
219
|
+
* WS-connection rate limit sees some accounts rejected at the handshake,
|
|
220
|
+
* which self-heals once the server's rate-limit window rolls over.
|
|
221
|
+
*/
|
|
222
|
+
connect(id: string): Promise<Account>;
|
|
223
|
+
/** Connect every stored account, staggered to respect rate limits. */
|
|
224
|
+
connectAll(opts?: {
|
|
225
|
+
onConnect?: (account: Account) => void;
|
|
226
|
+
}): Promise<Account[]>;
|
|
227
|
+
/** List the player accounts the Clerk user owns. Requires `clerkApiKey`. */
|
|
228
|
+
listOwnedPlayers(): Promise<ClerkPlayer[]>;
|
|
229
|
+
/**
|
|
230
|
+
* Connect every account the Clerk user owns (optionally filtered), staggered
|
|
231
|
+
* to respect rate limits. Stores a `clerk` credential per player — each
|
|
232
|
+
* account mints a fresh single-use WS token on connect and reconnect, so no
|
|
233
|
+
* passwords are persisted. Requires `clerkApiKey`.
|
|
234
|
+
*
|
|
235
|
+
* A fleet-wide call can legitimately take minutes (see `connectIds`'s
|
|
236
|
+
* pacing) — `onConnect` fires as each account finishes, so a caller can
|
|
237
|
+
* index/use accounts incrementally instead of only after the whole batch
|
|
238
|
+
* settles (the returned promise still doesn't resolve until every account
|
|
239
|
+
* has been attempted).
|
|
240
|
+
*/
|
|
241
|
+
connectOwned(opts?: {
|
|
242
|
+
filter?: (player: ClerkPlayer) => boolean;
|
|
243
|
+
onConnect?: (account: Account) => void;
|
|
244
|
+
}): Promise<Account[]>;
|
|
245
|
+
private requireClerkSource;
|
|
246
|
+
/**
|
|
247
|
+
* Connect the given stored ids, staggered to respect rate limits. At or
|
|
248
|
+
* under `connectBatchSize` accounts this is a single stagger pass — the
|
|
249
|
+
* same behavior as before batching existed. Past that, it pauses for
|
|
250
|
+
* `connectBatchWaitMs` between batches so the fleet never actually asks
|
|
251
|
+
* for more connections than the server's per-IP window allows. A single
|
|
252
|
+
* account failing to connect must not abort the rest of the batch — it's
|
|
253
|
+
* logged and skipped so the caller still gets every account that succeeded.
|
|
254
|
+
*
|
|
255
|
+
* `onConnect` fires synchronously as each id finishes (success only) so a
|
|
256
|
+
* caller managing many accounts (e.g. indexing them for lookup) doesn't
|
|
257
|
+
* have to wait for the entire — potentially minutes-long — batch before
|
|
258
|
+
* any single account becomes usable.
|
|
259
|
+
*
|
|
260
|
+
* Pacing itself is `enqueueRateLimited` — the same queue a later
|
|
261
|
+
* reconnect (see `handleAccountDisconnected`) competes for a slot in, so a
|
|
262
|
+
* reconnect landing mid-batch is paced fairly alongside the rest of the
|
|
263
|
+
* fleet instead of racing it on a separate timer.
|
|
264
|
+
*/
|
|
265
|
+
private connectIds;
|
|
266
|
+
/**
|
|
267
|
+
* Enqueues `task`, paced by `connectBatchSize`/`connectStaggerMs`/
|
|
268
|
+
* `connectBatchWaitMs` — the single rate limiter shared by the initial
|
|
269
|
+
* fleet connect (`connectIds`) and every later reconnect
|
|
270
|
+
* (`handleAccountDisconnected`), so the server's per-IP WS-connection cap
|
|
271
|
+
* is respected by one mechanism, not a parallel one for each case. Returns
|
|
272
|
+
* a promise settling with `task`'s own outcome once its turn comes up.
|
|
273
|
+
*/
|
|
274
|
+
private enqueueRateLimited;
|
|
275
|
+
private drainRateLimitedQueue;
|
|
276
|
+
/** The connected account for an id, if connected. */
|
|
277
|
+
account(id: string): Account | undefined;
|
|
278
|
+
/** All currently-connected accounts. */
|
|
279
|
+
accounts(): Account[];
|
|
280
|
+
/** Ids of all currently-connected accounts. */
|
|
281
|
+
ids(): string[];
|
|
282
|
+
/** Close and forget an account, and remove its stored credentials. */
|
|
283
|
+
remove(id: string): Promise<void>;
|
|
284
|
+
/** Close every connection (credentials remain in the store). */
|
|
285
|
+
closeAll(): void;
|
|
286
|
+
/**
|
|
287
|
+
* Reconnection for a client-managed account is driven here, not by the
|
|
288
|
+
* `Account`'s own (disabled — see `createAccount`) reconnect logic: an
|
|
289
|
+
* unexpected disconnect closes every affected account at once (e.g. a
|
|
290
|
+
* game-server restart), and reconnecting through the same rate-limited
|
|
291
|
+
* queue used for the initial connect — instead of each account racing its
|
|
292
|
+
* own independent timer — is what keeps a mass reconnect from re-tripping
|
|
293
|
+
* the server's per-IP WS-connection rate limit right as it recovers.
|
|
294
|
+
*
|
|
295
|
+
* Reconnects in place (`Account.reconnectOnce`, same instance) rather than
|
|
296
|
+
* replacing the account with a new one: anything holding a direct
|
|
297
|
+
* reference to this `Account` (e.g. a running loop, which captures it once
|
|
298
|
+
* at start) keeps working transparently once the connection is restored,
|
|
299
|
+
* instead of being welded to a now-dead instance nobody is driving.
|
|
300
|
+
*/
|
|
301
|
+
private handleAccountDisconnected;
|
|
302
|
+
private createAccount;
|
|
303
|
+
private capturePlayerId;
|
|
304
|
+
}
|
|
305
|
+
export type { AuthCredentials, CredentialStore, StoredAccount };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local copy of the bulk game catalog (`GET /api/catalog.json`).
|
|
3
|
+
*
|
|
4
|
+
* Reference data — ships, items, recipes, skills, facilities — that changes
|
|
5
|
+
* only on a server release. Fetched once over HTTP and cached; the entry
|
|
6
|
+
* shapes aren't in the v2 OpenAPI spec, so they're typed loosely with `id`-keyed
|
|
7
|
+
* lookups. Public, ETag-cached, ~1h max-age on the server.
|
|
8
|
+
*/
|
|
9
|
+
export interface CatalogEntry {
|
|
10
|
+
id?: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface Catalog {
|
|
14
|
+
version?: string;
|
|
15
|
+
ships: CatalogEntry[];
|
|
16
|
+
items: CatalogEntry[];
|
|
17
|
+
recipes: CatalogEntry[];
|
|
18
|
+
skills: CatalogEntry[];
|
|
19
|
+
facilities: CatalogEntry[];
|
|
20
|
+
}
|
|
21
|
+
export declare function fetchCatalog(httpBaseUrl: string): Promise<Catalog>;
|
|
22
|
+
export declare class CatalogCache {
|
|
23
|
+
readonly catalog: Catalog;
|
|
24
|
+
private readonly indexes;
|
|
25
|
+
constructor(catalog: Catalog);
|
|
26
|
+
/** Fetch the catalog and wrap it in a cache. */
|
|
27
|
+
static load(httpBaseUrl: string): Promise<CatalogCache>;
|
|
28
|
+
get version(): string | undefined;
|
|
29
|
+
ship(id: string): CatalogEntry | undefined;
|
|
30
|
+
item(id: string): CatalogEntry | undefined;
|
|
31
|
+
recipe(id: string): CatalogEntry | undefined;
|
|
32
|
+
skill(id: string): CatalogEntry | undefined;
|
|
33
|
+
facility(id: string): CatalogEntry | undefined;
|
|
34
|
+
get ships(): readonly CatalogEntry[];
|
|
35
|
+
get items(): readonly CatalogEntry[];
|
|
36
|
+
get recipes(): readonly CatalogEntry[];
|
|
37
|
+
get skills(): readonly CatalogEntry[];
|
|
38
|
+
get facilities(): readonly CatalogEntry[];
|
|
39
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local copy of the static galaxy map (`GET /api/map`).
|
|
3
|
+
*
|
|
4
|
+
* Returns `{ systems, empires }` where `empires` maps an empire id to a display
|
|
5
|
+
* colour. System shapes aren't in the v2 spec, so they're typed loosely with
|
|
6
|
+
* `id`-keyed lookups. Static per release; the separate `/api/map/activity`
|
|
7
|
+
* overlay (online counts, battles) changes frequently and is not cached here.
|
|
8
|
+
*/
|
|
9
|
+
export interface MapSystem {
|
|
10
|
+
id?: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface GalaxyMap {
|
|
14
|
+
systems: MapSystem[];
|
|
15
|
+
empires: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
export declare function fetchMap(httpBaseUrl: string): Promise<GalaxyMap>;
|
|
18
|
+
export declare class MapCache {
|
|
19
|
+
readonly map: GalaxyMap;
|
|
20
|
+
private readonly bySystemId;
|
|
21
|
+
constructor(map: GalaxyMap);
|
|
22
|
+
/** Fetch the galaxy map and wrap it in a cache. */
|
|
23
|
+
static load(httpBaseUrl: string): Promise<MapCache>;
|
|
24
|
+
system(id: string): MapSystem | undefined;
|
|
25
|
+
get systems(): readonly MapSystem[];
|
|
26
|
+
/** Empire id -> display colour. */
|
|
27
|
+
get empires(): Record<string, string>;
|
|
28
|
+
}
|
|
29
|
+
/** Derive the HTTP origin from a WebSocket URL (wss://host/ws/v2 -> https://host). */
|
|
30
|
+
export declare function httpBaseFromWs(wsUrl: string): string;
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Error types surfaced by the library. */
|
|
2
|
+
import type { ActionErrorFrame, ErrorFrame } from './protocol.js';
|
|
3
|
+
/**
|
|
4
|
+
* A server-reported error. Carries the machine-readable `code` from the
|
|
5
|
+
* server's error/action_error frame plus any structured details.
|
|
6
|
+
*/
|
|
7
|
+
export declare class SpacemoltError extends Error {
|
|
8
|
+
readonly code: string;
|
|
9
|
+
readonly details?: Record<string, unknown>;
|
|
10
|
+
readonly requestId?: string;
|
|
11
|
+
/** Set when the failure came from a two-phase mutation outcome. */
|
|
12
|
+
readonly command?: string;
|
|
13
|
+
/** Game tick of the failure, for action_error outcomes. */
|
|
14
|
+
readonly tick?: number;
|
|
15
|
+
/** On `action_pending` errors: the mutation already queued. */
|
|
16
|
+
readonly pendingCommand?: string;
|
|
17
|
+
constructor(code: string, message: string, opts?: {
|
|
18
|
+
details?: Record<string, unknown>;
|
|
19
|
+
requestId?: string;
|
|
20
|
+
command?: string;
|
|
21
|
+
tick?: number;
|
|
22
|
+
pendingCommand?: string;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export declare function errorFromFrame(frame: ErrorFrame): SpacemoltError;
|
|
26
|
+
export declare function errorFromActionFrame(frame: ActionErrorFrame): SpacemoltError;
|
|
27
|
+
/** Raised against every in-flight request when the socket closes. */
|
|
28
|
+
export declare class ConnectionClosedError extends Error {
|
|
29
|
+
readonly code?: number;
|
|
30
|
+
readonly reason?: string;
|
|
31
|
+
constructor(message?: string, code?: number, reason?: string);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Custom WS close codes the server documents (as of gameserver v0.471.4):
|
|
35
|
+
* `4001` session_replaced (another connection took your slot — don't
|
|
36
|
+
* reconnect, you'd just fight it), `4002` auth_timeout (connected but didn't
|
|
37
|
+
* authenticate in time), `4003` connection_rate_limited (exceeded the
|
|
38
|
+
* 100/min-per-IP WS-connection cap — the close reason carries a
|
|
39
|
+
* `retry_after=<seconds>` hint; honor it before reconnecting).
|
|
40
|
+
*/
|
|
41
|
+
export declare const CLOSE_CODE: {
|
|
42
|
+
readonly SESSION_REPLACED: 4001;
|
|
43
|
+
readonly AUTH_TIMEOUT: 4002;
|
|
44
|
+
readonly CONNECTION_RATE_LIMITED: 4003;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Parses the `retry_after=<seconds>` hint the server includes on a `4003`
|
|
48
|
+
* (connection_rate_limited) close reason. Returns the wait time in
|
|
49
|
+
* milliseconds, or `undefined` if `err` isn't a rate-limited close or carries
|
|
50
|
+
* no parseable hint.
|
|
51
|
+
*/
|
|
52
|
+
export declare function retryAfterMsFromClose(err: unknown): number | undefined;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed event dispatch for server push frames.
|
|
3
|
+
*
|
|
4
|
+
* Callbacks (`on`/`onAny`) and async iterators (`stream`/`anyStream`) are both
|
|
5
|
+
* supported. The Account facade adds the per-msg_type payload typing on top of
|
|
6
|
+
* this (loosely-typed) core.
|
|
7
|
+
*/
|
|
8
|
+
import type { RawFrame } from '../protocol.js';
|
|
9
|
+
type PayloadHandler = (payload: unknown) => void;
|
|
10
|
+
type FrameHandler = (frame: RawFrame) => void;
|
|
11
|
+
/**
|
|
12
|
+
* An async iterator over events of one kind, with internal buffering so a slow
|
|
13
|
+
* consumer doesn't drop frames. Breaking out of a `for await` calls `return()`,
|
|
14
|
+
* which unsubscribes via the `onClose` callback.
|
|
15
|
+
*/
|
|
16
|
+
export declare class EventStream<T> implements AsyncIterableIterator<T> {
|
|
17
|
+
private readonly onClose;
|
|
18
|
+
private readonly buffer;
|
|
19
|
+
private readonly waiting;
|
|
20
|
+
private ended;
|
|
21
|
+
constructor(onClose: () => void);
|
|
22
|
+
/** Internal: deliver a value to the consumer or buffer it. */
|
|
23
|
+
push(value: T): void;
|
|
24
|
+
/** Internal: end the stream (e.g. on disconnect). */
|
|
25
|
+
end(): void;
|
|
26
|
+
next(): Promise<IteratorResult<T>>;
|
|
27
|
+
return(): Promise<IteratorResult<T>>;
|
|
28
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<T>;
|
|
29
|
+
}
|
|
30
|
+
export declare class TypedEmitter {
|
|
31
|
+
private readonly handlers;
|
|
32
|
+
private readonly anyHandlers;
|
|
33
|
+
private readonly streams;
|
|
34
|
+
private readonly anyStreams;
|
|
35
|
+
/** Listen for one msg_type. Returns an unsubscribe function. */
|
|
36
|
+
on(type: string, handler: PayloadHandler): () => void;
|
|
37
|
+
/** Listen for every push frame. Returns an unsubscribe function. */
|
|
38
|
+
onAny(handler: FrameHandler): () => void;
|
|
39
|
+
/** Async-iterate one msg_type's payloads. */
|
|
40
|
+
stream(type: string): EventStream<unknown>;
|
|
41
|
+
/** Async-iterate every push frame. */
|
|
42
|
+
anyStream(): EventStream<RawFrame>;
|
|
43
|
+
/**
|
|
44
|
+
* Dispatch a push frame to all matching listeners and streams. Each
|
|
45
|
+
* callback is isolated with try/catch — this is the single funnel every
|
|
46
|
+
* `account.on(type, ...)`/`onAny` consumer runs through, invoked directly
|
|
47
|
+
* from `routeFrame`'s `default:` case with nothing above it to contain a
|
|
48
|
+
* throw. Without isolation, one listener throwing would skip every other
|
|
49
|
+
* listener/stream for this frame and propagate out through `routeFrame`
|
|
50
|
+
* into the WebSocket's `message` event handler, which has no try/catch of
|
|
51
|
+
* its own either.
|
|
52
|
+
*/
|
|
53
|
+
emit(frame: RawFrame): void;
|
|
54
|
+
private safeCall;
|
|
55
|
+
/** End every open stream (on disconnect). Callback listeners are kept. */
|
|
56
|
+
closeStreams(): void;
|
|
57
|
+
}
|
|
58
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** The gameserver spec version this command surface was generated from. */
|
|
2
|
+
export declare const GENERATED_SPEC_VERSION = "v0.485.0";
|
|
3
|
+
export type ToolName = "spacemolt" | "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_social" | "spacemolt_storage" | "spacemolt_transfer";
|
|
4
|
+
export type ActionName = "abandon_mission" | "accept" | "accept_ally" | "accept_invite" | "accept_mission" | "accept_peace" | "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_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" | "chat" | "claim" | "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" | "deploy" | "deploy_outpost" | "deposit" | "disband" | "disembark" | "dismantle" | "distress_signal" | "dock" | "edit" | "edit_role" | "engage" | "espionage" | "estimate_purchase" | "facility_set_description" | "faction_build" | "faction_dismantle" | "faction_list" | "faction_owned" | "faction_upgrade" | "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" | "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" | "login" | "login_token" | "logout" | "loot" | "mine" | "modify_order" | "mute_notifications" | "name" | "owned" | "personal_build" | "personal_decorate" | "personal_visit" | "petition" | "place_ship_buy_order" | "policies" | "post_mission" | "prepay_tax" | "promote" | "propose_ally" | "propose_peace" | "query_intel" | "query_trade_intel" | "quote" | "read_note" | "recall" | "recycle" | "refit_ship" | "refuel" | "register" | "release" | "reload" | "remove_ally" | "remove_enemy" | "remove_faction" | "remove_player" | "rename_ship" | "renounce" | "repair" | "repair_module" | "retreat" | "rooms" | "scan" | "scan_poi" | "scrap" | "scrap_ship" | "search_systems" | "self_destruct" | "sell" | "sell_ship" | "sell_ship_to_order" | "set_access" | "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" | "supply_commission" | "survey_system" | "switch_ship" | "target" | "tax_estimate" | "tow" | "trade_accept" | "trade_cancel" | "trade_decline" | "trade_intel_status" | "trade_offer" | "transfer" | "travel" | "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
|
+
export interface ActionParam {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly type: string;
|
|
8
|
+
readonly required: boolean;
|
|
9
|
+
readonly description?: string;
|
|
10
|
+
readonly enumValues?: readonly string[];
|
|
11
|
+
readonly positional?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ActionDef {
|
|
14
|
+
readonly tool: ToolName;
|
|
15
|
+
readonly action: ActionName;
|
|
16
|
+
readonly kind: 'query' | 'mutation';
|
|
17
|
+
readonly summary?: string;
|
|
18
|
+
readonly params: readonly ActionParam[];
|
|
19
|
+
/** TS type name of the query's structuredContent response (queries with a published response schema). */
|
|
20
|
+
readonly responseType?: string;
|
|
21
|
+
/** TS type name of the mutation's delta.details response (mutations with a published details schema). */
|
|
22
|
+
readonly detailsType?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Every v2 command, keyed by "tool/action". */
|
|
25
|
+
export declare const ACTIONS: Record<string, ActionDef>;
|