@spacemolt/lib 14.0.1 → 14.2.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 +24 -0
- package/README.md +12 -2
- package/dist/account.d.ts +42 -16
- package/dist/index.d.ts +8 -0
- package/dist/index.js +100 -16
- package/dist/push-frames.d.ts +291 -0
- package/dist/state/observation.d.ts +18 -3
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -84,6 +84,30 @@ account.credits, account.ship, account.location, account.cargo, account.skills;
|
|
|
84
84
|
account.on('mining_yield', (y) => console.log(y.quantity, y.resource_id));
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
Push payloads are typed. Most come from the server's published schema; the `ok`
|
|
88
|
+
and `fleet` families are hand-written unions, because the server publishes no
|
|
89
|
+
schema for those two. `ok` splits its discriminator: most variants key on
|
|
90
|
+
`action`, seven key on `type`. A bare `p.action` therefore does not compile on
|
|
91
|
+
`OkPush` — guard with `'action' in p`, or narrow to the exported `OkActionPush`
|
|
92
|
+
/ `OkTypePush` halves. `FleetPush` has one key and needs no guard:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
account.on('ok', (p) => {
|
|
96
|
+
if ('action' in p && p.action === 'fleet_dock') p.base_id; // canonical base id
|
|
97
|
+
if ('type' in p && p.type === 'emergency_warp_stabilizer_activated') p.hull;
|
|
98
|
+
});
|
|
99
|
+
account.on('fleet', (p) => {
|
|
100
|
+
if (p.action === 'fleet_invite') p.fleet_id;
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Both unions are closed and were derived from a specific server build. A frame
|
|
105
|
+
the server added since still arrives at your `on('ok')` handler — nothing
|
|
106
|
+
validates the payload against these types — as a value matching no member, so
|
|
107
|
+
give your `switch` a real default rather than a `never` assertion.
|
|
108
|
+
`OK_PUSH_ACTIONS`, `OK_PUSH_TYPES` and `FLEET_PUSH_ACTIONS` are exported with
|
|
109
|
+
their derived literal types if you want to enumerate.
|
|
110
|
+
|
|
87
111
|
## Gotchas worth knowing up front
|
|
88
112
|
|
|
89
113
|
- **Preconditions are real.** A new account starts docked; `mine` needs an
|
package/README.md
CHANGED
|
@@ -138,7 +138,7 @@ for await (const hit of account.events('battle_damage')) { /* ... */ }
|
|
|
138
138
|
|
|
139
139
|
### Subscriptions
|
|
140
140
|
|
|
141
|
-
Subscribe to a station's order book or to
|
|
141
|
+
Subscribe to a station's order book or to presence at your location; the
|
|
142
142
|
baseline snapshot seeds a local cache that the push stream keeps current:
|
|
143
143
|
|
|
144
144
|
```ts
|
|
@@ -146,9 +146,19 @@ await account.subscribeMarket(); // current docked station
|
|
|
146
146
|
account.market(baseId); // cached order book, kept live by `market_update`
|
|
147
147
|
|
|
148
148
|
await account.subscribeObservation();
|
|
149
|
-
account.observation();
|
|
149
|
+
const view = account.observation(); // kept live by `observation_update`
|
|
150
|
+
view?.nearby; // players at your POI, keyed by player_id
|
|
151
|
+
view?.system; // players system-wide
|
|
152
|
+
view?.pirates; // pirate NPCs, keyed by pirate_id
|
|
153
|
+
view?.empireNpcs; // empire NPCs, keyed by npc_id
|
|
154
|
+
view?.creatures; // wildlife, keyed by creature_id
|
|
155
|
+
view?.prizes; // intact captured ships, keyed by prize_id
|
|
150
156
|
```
|
|
151
157
|
|
|
158
|
+
The watch is not players only. It covers five of `get_nearby`'s six classes —
|
|
159
|
+
arena NPCs are the one it does not carry — and every class except wildlife is
|
|
160
|
+
also mirrored into `account.state.location`.
|
|
161
|
+
|
|
152
162
|
## Multi-account
|
|
153
163
|
|
|
154
164
|
`SpacemoltClient` manages many accounts, staggers connects to respect login
|
package/dist/account.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { type Commands } from './generated/commands.gen.js';
|
|
|
14
14
|
import type { AuthCredentials } from './auth/credentials.js';
|
|
15
15
|
import type { LoggedInPayload as LoggedInPayloadSchema, SubscribeMarketResponse, SubscribeObservationResponse } from './generated/openapi/types.gen.js';
|
|
16
16
|
import type { NotificationPayloads, TypedNotificationType } from './generated/notifications.gen.js';
|
|
17
|
+
import type { FleetPush, OkPush } from './push-frames.js';
|
|
17
18
|
import { type ConnectionClosedError } from './errors.js';
|
|
18
19
|
import { type MarketBook } from './state/market.js';
|
|
19
20
|
import { type ObservationView } from './state/observation.js';
|
|
@@ -300,10 +301,14 @@ export declare class Account {
|
|
|
300
301
|
send(tool: string, action: string, payload?: Record<string, unknown>, requestId?: string): Promise<QueryResult | MutationResult>;
|
|
301
302
|
/**
|
|
302
303
|
* Listen for a specific server push by msg_type. The handler receives the
|
|
303
|
-
* typed payload for published notification types,
|
|
304
|
-
*
|
|
304
|
+
* typed payload for published notification types, and for the `ok` and
|
|
305
|
+
* `fleet` families from the hand-written unions in `push-frames.ts` (the
|
|
306
|
+
* server publishes no schema for those two). Anything else is a
|
|
307
|
+
* loosely-typed object. Returns an unsubscribe function.
|
|
305
308
|
*/
|
|
306
309
|
on<K extends TypedNotificationType>(type: K, handler: (payload: NotificationPayloads[K]) => void): () => void;
|
|
310
|
+
on(type: 'ok', handler: (payload: OkPush) => void): () => void;
|
|
311
|
+
on(type: 'fleet', handler: (payload: FleetPush) => void): () => void;
|
|
307
312
|
on(type: string, handler: (payload: Record<string, unknown>) => void): () => void;
|
|
308
313
|
/** Listen for every server push frame. Returns an unsubscribe function. */
|
|
309
314
|
onAny(handler: (frame: RawFrame) => void): () => void;
|
|
@@ -313,6 +318,8 @@ export declare class Account {
|
|
|
313
318
|
* Buffered, so a slow consumer won't drop frames; `break` unsubscribes.
|
|
314
319
|
*/
|
|
315
320
|
events<K extends TypedNotificationType>(type: K): AsyncIterableIterator<NotificationPayloads[K]>;
|
|
321
|
+
events(type: 'ok'): AsyncIterableIterator<OkPush>;
|
|
322
|
+
events(type: 'fleet'): AsyncIterableIterator<FleetPush>;
|
|
316
323
|
events(type: string): AsyncIterableIterator<Record<string, unknown>>;
|
|
317
324
|
/** Async-iterate every push frame. */
|
|
318
325
|
anyEvents(): AsyncIterableIterator<RawFrame>;
|
|
@@ -336,14 +343,17 @@ export declare class Account {
|
|
|
336
343
|
/** The cached order book for a base, if subscribed. */
|
|
337
344
|
market(baseId: string): MarketBook | undefined;
|
|
338
345
|
/**
|
|
339
|
-
* Subscribe to a change-feed of
|
|
346
|
+
* Subscribe to a change-feed of presence at your current POI/system: players
|
|
347
|
+
* (POI and system-wide), plus the pirates, empire NPCs, wildlife and intact
|
|
348
|
+
* prizes at the POI — not players only. That is five of `get_nearby`'s six
|
|
349
|
+
* classes; arena NPCs are not in the feed, so an arena match still needs
|
|
350
|
+
* `get_nearby` to see its opponents.
|
|
340
351
|
* Returns the baseline and seeds the observation cache; `observation_update`
|
|
341
352
|
* pushes are merged automatically. Read with `account.observation()`. Also
|
|
342
|
-
* bridges POI-scoped
|
|
343
|
-
* `
|
|
344
|
-
* `
|
|
345
|
-
*
|
|
346
|
-
* `get_status`/mutation delta.
|
|
353
|
+
* bridges the POI-scoped classes into their matching `location` fields in the
|
|
354
|
+
* base state cache (see `bridgeObservationToLocation`), so
|
|
355
|
+
* `account.state.location` reflects live data too once subscribed, instead of
|
|
356
|
+
* only refreshing on the next `get_status`/mutation delta.
|
|
347
357
|
*
|
|
348
358
|
* Scoped to your current POI the same way `subscribeMarket` is scoped to
|
|
349
359
|
* your current dock — see the docked_at note there; the same silent
|
|
@@ -355,14 +365,30 @@ export declare class Account {
|
|
|
355
365
|
/** Unsubscribe from the observation watch and clear its cache. */
|
|
356
366
|
unsubscribeObservation(): Promise<void>;
|
|
357
367
|
/**
|
|
358
|
-
* Mirrors the observation watch's POI-scoped
|
|
359
|
-
* `location
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
368
|
+
* Mirrors the observation watch's POI-scoped presence into the matching
|
|
369
|
+
* `location` fields in the base state cache, so code reading
|
|
370
|
+
* `account.state.location` (rather than `account.observation()`) sees live
|
|
371
|
+
* data once subscribed: players, pirates, empire NPCs, intact prizes, their
|
|
372
|
+
* counts, and the cloaked-signature hint. Wildlife has no `location` field —
|
|
373
|
+
* read creatures from `account.observation()`.
|
|
374
|
+
*
|
|
375
|
+
* Three ways the bridged values differ from what `get_status` would return
|
|
376
|
+
* for the same POI, none of them fixable from here:
|
|
377
|
+
* - The arrays are uncapped. `get_status` truncates them (50 players, 20
|
|
378
|
+
* each of the rest) while reporting the pre-cap total in the count, so a
|
|
379
|
+
* bridged array can be longer than the server would ever send. The counts
|
|
380
|
+
* mean the same thing either way.
|
|
381
|
+
* - `offline_collapsed` is a `get_status` response cap the feed does not
|
|
382
|
+
* report, so it is left untouched at whatever the server last said. Above
|
|
383
|
+
* the crowding threshold `get_status` drops offline players from
|
|
384
|
+
* `nearby_players` and counts them there instead; the feed carries them in
|
|
385
|
+
* full, so once bridged the two no longer sum — read the array, not the sum.
|
|
386
|
+
* - `ship_name` on players and empire NPCs is the ship's display name here
|
|
387
|
+
* (its custom name, else the class name) where `get_status` sends the
|
|
388
|
+
* custom name alone. Present does not imply custom-named while subscribed.
|
|
389
|
+
*
|
|
390
|
+
* `unknown_signature` is written even when false, where `get_status` omits
|
|
391
|
+
* the key; the bridge has to write it to clear a signature that has gone.
|
|
366
392
|
* No-ops if `location` hasn't been seeded yet or nothing is subscribed.
|
|
367
393
|
*/
|
|
368
394
|
private bridgeObservationToLocation;
|
package/dist/index.d.ts
CHANGED
|
@@ -30,3 +30,11 @@ export type { ToolName, ActionName, ActionDef, ActionParam } from './generated/a
|
|
|
30
30
|
export type * from './generated/openapi/types.gen.js';
|
|
31
31
|
export { TYPED_NOTIFICATION_TYPES } from './generated/notifications.gen.js';
|
|
32
32
|
export type { NotificationPayloads, TypedNotificationType, } from './generated/notifications.gen.js';
|
|
33
|
+
/**
|
|
34
|
+
* Payload types for the `ok` and `fleet` push families, plus the discriminator
|
|
35
|
+
* lists. Hand-written rather than generated: the server publishes no
|
|
36
|
+
* `Notification_ok` or `Notification_fleet` schema, so codegen has nothing to
|
|
37
|
+
* derive them from. Re-exported wholesale so a variant added to the unions does
|
|
38
|
+
* not also have to be remembered here.
|
|
39
|
+
*/
|
|
40
|
+
export * from './push-frames.js';
|
package/dist/index.js
CHANGED
|
@@ -3832,6 +3832,15 @@ function indexBy(items, key) {
|
|
|
3832
3832
|
}
|
|
3833
3833
|
return map;
|
|
3834
3834
|
}
|
|
3835
|
+
function merge(map, changed, departed, key) {
|
|
3836
|
+
for (const item of changed ?? []) {
|
|
3837
|
+
const k = key(item);
|
|
3838
|
+
if (k)
|
|
3839
|
+
map.set(k, item);
|
|
3840
|
+
}
|
|
3841
|
+
for (const id of departed ?? [])
|
|
3842
|
+
map.delete(id);
|
|
3843
|
+
}
|
|
3835
3844
|
|
|
3836
3845
|
class ObservationCache {
|
|
3837
3846
|
view = null;
|
|
@@ -3842,6 +3851,10 @@ class ObservationCache {
|
|
|
3842
3851
|
tick: 0,
|
|
3843
3852
|
nearby: indexBy(snapshot.nearby, (p) => p.player_id),
|
|
3844
3853
|
system: indexBy(snapshot.system_agents, (p) => p.player_id),
|
|
3854
|
+
pirates: indexBy(snapshot.pirates, (p) => p.pirate_id),
|
|
3855
|
+
empireNpcs: indexBy(snapshot.empire_npcs, (n) => n.npc_id),
|
|
3856
|
+
creatures: indexBy(snapshot.creatures, (c) => c.creature_id),
|
|
3857
|
+
prizes: indexBy(snapshot.prizes, (p) => p.prize_id),
|
|
3845
3858
|
cloaked: indexBy(snapshot.cloaked_contacts, (c) => c.target_id),
|
|
3846
3859
|
unknownSignature: snapshot.unknown_signature ?? false,
|
|
3847
3860
|
activeScan: snapshot.active_scan ?? false
|
|
@@ -3856,6 +3869,10 @@ class ObservationCache {
|
|
|
3856
3869
|
tick: update.tick,
|
|
3857
3870
|
nearby: new Map,
|
|
3858
3871
|
system: new Map,
|
|
3872
|
+
pirates: new Map,
|
|
3873
|
+
empireNpcs: new Map,
|
|
3874
|
+
creatures: new Map,
|
|
3875
|
+
prizes: new Map,
|
|
3859
3876
|
cloaked: new Map,
|
|
3860
3877
|
unknownSignature: false,
|
|
3861
3878
|
activeScan: false
|
|
@@ -3863,21 +3880,13 @@ class ObservationCache {
|
|
|
3863
3880
|
}
|
|
3864
3881
|
const v = this.view;
|
|
3865
3882
|
v.tick = update.tick;
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
v.system.set(p.player_id, p);
|
|
3874
|
-
for (const id of update.system_departed ?? [])
|
|
3875
|
-
v.system.delete(id);
|
|
3876
|
-
for (const c of update.cloaked_resolved ?? [])
|
|
3877
|
-
if (c.target_id)
|
|
3878
|
-
v.cloaked.set(c.target_id, c);
|
|
3879
|
-
for (const id of update.cloaked_lost ?? [])
|
|
3880
|
-
v.cloaked.delete(id);
|
|
3883
|
+
merge(v.nearby, update.nearby_changed, update.nearby_departed, (p) => p.player_id);
|
|
3884
|
+
merge(v.system, update.system_changed, update.system_departed, (p) => p.player_id);
|
|
3885
|
+
merge(v.pirates, update.pirates_changed, update.pirates_departed, (p) => p.pirate_id);
|
|
3886
|
+
merge(v.empireNpcs, update.empire_npcs_changed, update.empire_npcs_departed, (n) => n.npc_id);
|
|
3887
|
+
merge(v.creatures, update.creatures_changed, update.creatures_departed, (c) => c.creature_id);
|
|
3888
|
+
merge(v.prizes, update.prizes_changed, update.prizes_departed, (p) => p.prize_id);
|
|
3889
|
+
merge(v.cloaked, update.cloaked_resolved, update.cloaked_lost, (c) => c.target_id);
|
|
3881
3890
|
if (update.unknown_signature !== undefined)
|
|
3882
3891
|
v.unknownSignature = update.unknown_signature;
|
|
3883
3892
|
if (update.active_scan !== undefined)
|
|
@@ -4579,9 +4588,39 @@ class Account {
|
|
|
4579
4588
|
...p.ship_name !== undefined && { ship_name: p.ship_name },
|
|
4580
4589
|
...p.username !== undefined && { username: p.username }
|
|
4581
4590
|
}));
|
|
4591
|
+
const nearbyPirates = [...view.pirates.values()].map((p) => ({
|
|
4592
|
+
pirate_id: p.pirate_id,
|
|
4593
|
+
name: p.name,
|
|
4594
|
+
tier: p.tier,
|
|
4595
|
+
is_boss: p.is_boss,
|
|
4596
|
+
status: p.status,
|
|
4597
|
+
hull: p.hull ?? 0,
|
|
4598
|
+
max_hull: p.max_hull ?? 0,
|
|
4599
|
+
shield: p.shield ?? 0,
|
|
4600
|
+
max_shield: p.max_shield ?? 0,
|
|
4601
|
+
...p.faction !== undefined && { faction: p.faction },
|
|
4602
|
+
...p.faction_name !== undefined && { faction_name: p.faction_name }
|
|
4603
|
+
}));
|
|
4604
|
+
const nearbyEmpireNpcs = [...view.empireNpcs.values()].map((n) => ({
|
|
4605
|
+
npc_id: n.npc_id,
|
|
4606
|
+
name: n.name,
|
|
4607
|
+
role: n.role,
|
|
4608
|
+
empire: n.empire,
|
|
4609
|
+
in_combat: n.in_combat,
|
|
4610
|
+
...n.fleet_name !== undefined && { fleet_name: n.fleet_name },
|
|
4611
|
+
...n.ship_class !== undefined && { ship_class: n.ship_class },
|
|
4612
|
+
...n.ship_name !== undefined && { ship_name: n.ship_name }
|
|
4613
|
+
}));
|
|
4582
4614
|
const changed = this.cache.patchSection("location", {
|
|
4583
4615
|
nearby_players: nearbyPlayers,
|
|
4584
|
-
nearby_player_count: view.nearby.size
|
|
4616
|
+
nearby_player_count: view.nearby.size,
|
|
4617
|
+
nearby_pirates: nearbyPirates,
|
|
4618
|
+
nearby_pirate_count: view.pirates.size,
|
|
4619
|
+
nearby_empire_npcs: nearbyEmpireNpcs,
|
|
4620
|
+
nearby_empire_npc_count: view.empireNpcs.size,
|
|
4621
|
+
nearby_prizes: [...view.prizes.values()],
|
|
4622
|
+
nearby_prize_count: view.prizes.size,
|
|
4623
|
+
unknown_signature: view.unknownSignature
|
|
4585
4624
|
});
|
|
4586
4625
|
if (changed.length)
|
|
4587
4626
|
this.stateRevision++;
|
|
@@ -5533,6 +5572,48 @@ var TYPED_NOTIFICATION_TYPES = [
|
|
|
5533
5572
|
"trade_declined",
|
|
5534
5573
|
"trade_offer_received"
|
|
5535
5574
|
];
|
|
5575
|
+
// src/push-frames.ts
|
|
5576
|
+
var OK_PUSH_ACTIONS = [
|
|
5577
|
+
"travel",
|
|
5578
|
+
"jump",
|
|
5579
|
+
"fleet_travel",
|
|
5580
|
+
"fleet_jump",
|
|
5581
|
+
"fleet_dock",
|
|
5582
|
+
"fleet_undock",
|
|
5583
|
+
"passenger_stranded",
|
|
5584
|
+
"mobile_capital_transit",
|
|
5585
|
+
"espionage",
|
|
5586
|
+
"arrived",
|
|
5587
|
+
"jumped",
|
|
5588
|
+
"pathfinder_arrival",
|
|
5589
|
+
"dock",
|
|
5590
|
+
"attack",
|
|
5591
|
+
"pathfinder_jump",
|
|
5592
|
+
"pathfinder_redirect"
|
|
5593
|
+
];
|
|
5594
|
+
var OK_PUSH_TYPES = [
|
|
5595
|
+
"auto_dock",
|
|
5596
|
+
"auto_undock",
|
|
5597
|
+
"emergency_warp_stabilizer_activated",
|
|
5598
|
+
"passenger_arrivals",
|
|
5599
|
+
"passengers_transferred",
|
|
5600
|
+
"ship_processing",
|
|
5601
|
+
"transit_lounge_departures",
|
|
5602
|
+
"shipment_overdue",
|
|
5603
|
+
"new_forum_post"
|
|
5604
|
+
];
|
|
5605
|
+
var FLEET_PUSH_ACTIONS = [
|
|
5606
|
+
"fleet_invite",
|
|
5607
|
+
"fleet_member_joined",
|
|
5608
|
+
"fleet_member_left",
|
|
5609
|
+
"fleet_member_kicked",
|
|
5610
|
+
"fleet_kicked",
|
|
5611
|
+
"fleet_disbanded",
|
|
5612
|
+
"fleet_leader_promoted",
|
|
5613
|
+
"fleet_member_died",
|
|
5614
|
+
"fleet_passenger_boarded",
|
|
5615
|
+
"fleet_passenger_disembarked"
|
|
5616
|
+
];
|
|
5536
5617
|
export {
|
|
5537
5618
|
ACTIONS,
|
|
5538
5619
|
Account,
|
|
@@ -5541,10 +5622,13 @@ export {
|
|
|
5541
5622
|
ClerkSource,
|
|
5542
5623
|
ConnectionClosedError,
|
|
5543
5624
|
EventStream,
|
|
5625
|
+
FLEET_PUSH_ACTIONS,
|
|
5544
5626
|
GENERATED_SPEC_VERSION,
|
|
5545
5627
|
MapCache,
|
|
5546
5628
|
MarketCache,
|
|
5547
5629
|
MemoryCredentialStore,
|
|
5630
|
+
OK_PUSH_ACTIONS,
|
|
5631
|
+
OK_PUSH_TYPES,
|
|
5548
5632
|
ObservationCache,
|
|
5549
5633
|
STATE_SECTIONS,
|
|
5550
5634
|
Socket,
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-written payload types for the `ok` and `fleet` push frames.
|
|
3
|
+
*
|
|
4
|
+
* Every other push frame is typed from the server's published
|
|
5
|
+
* `Notification_<msg_type>` schema, and the generated `NotificationPayloads`
|
|
6
|
+
* map carries it. These two families publish no schema at all — neither
|
|
7
|
+
* appears in the spec's notification union — so without this file they reach a
|
|
8
|
+
* consumer as `Record<string, unknown>`.
|
|
9
|
+
*
|
|
10
|
+
* They are hand-written rather than published because splitting them into
|
|
11
|
+
* distinct `msg_type`s is the fix, and that breaks any v1 client switching on
|
|
12
|
+
* `type === "ok"`. Until v1 is retired the shapes live here.
|
|
13
|
+
* `tests/push-frames.test.ts` fails the moment the server does publish either
|
|
14
|
+
* schema, which is the signal to delete this file and let codegen take over.
|
|
15
|
+
*
|
|
16
|
+
* **Two discriminators.** Most `ok` variants key on `action`; seven key on
|
|
17
|
+
* `type` instead. That split is the server's, not ours, so a bare `p.action`
|
|
18
|
+
* does not compile on `OkPush` — guard with `'action' in p` first, or narrow to
|
|
19
|
+
* `OkActionPush` / `OkTypePush`. `FleetPush` has a single key and needs no guard.
|
|
20
|
+
*
|
|
21
|
+
* **The unions are closed**, derived from gameserver v0.596.2. A member typed
|
|
22
|
+
* `{ action: string }` to absorb later frames would join every literal's
|
|
23
|
+
* narrowing and make all of them useless. The cost is that a variant the server
|
|
24
|
+
* adds after that build still arrives at your `on('ok')` handler — nothing
|
|
25
|
+
* validates the payload against these types — as a value matching no member. An
|
|
26
|
+
* exhaustive `switch` with a `never` default therefore compiles but is unsafe at
|
|
27
|
+
* runtime; give it a real default branch.
|
|
28
|
+
*/
|
|
29
|
+
import type { AttackNpcResponse, AttackPlayerResponse, DockResponse, EspionageResponse, JumpResponse, PathfinderJumpResponse, TravelResponse } from './generated/openapi/types.gen.js';
|
|
30
|
+
/** Human-readable summary, carried by the hand-written variants below. */
|
|
31
|
+
interface PushBase {
|
|
32
|
+
message?: string;
|
|
33
|
+
}
|
|
34
|
+
/** Your own transit started. `applyMovementPush` applies this to the cache. */
|
|
35
|
+
export interface OkTravelStarted extends PushBase {
|
|
36
|
+
action: 'travel';
|
|
37
|
+
/** POI ID being travelled to. */
|
|
38
|
+
destination: string;
|
|
39
|
+
/** Engine tick the arrival lands on. */
|
|
40
|
+
arrival_tick: number;
|
|
41
|
+
}
|
|
42
|
+
/** Your own jump started. `applyMovementPush` applies this to the cache. */
|
|
43
|
+
export interface OkJumpStarted extends PushBase {
|
|
44
|
+
action: 'jump';
|
|
45
|
+
/** System ID being jumped to. */
|
|
46
|
+
destination: string;
|
|
47
|
+
arrival_tick: number;
|
|
48
|
+
/** True when the jump routes through a wormhole rather than a lane. */
|
|
49
|
+
is_wormhole: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** Your fleet leader started an in-system travel and pulled you along. */
|
|
52
|
+
export interface OkFleetTravelStarted extends PushBase {
|
|
53
|
+
action: 'fleet_travel';
|
|
54
|
+
destination: string;
|
|
55
|
+
arrival_tick: number;
|
|
56
|
+
}
|
|
57
|
+
/** Your fleet leader started a jump and pulled you along. */
|
|
58
|
+
export interface OkFleetJumpStarted extends PushBase {
|
|
59
|
+
action: 'fleet_jump';
|
|
60
|
+
destination: string;
|
|
61
|
+
arrival_tick: number;
|
|
62
|
+
}
|
|
63
|
+
/** Your fleet leader docked and took the fleet with them. */
|
|
64
|
+
export interface OkFleetDocked extends PushBase {
|
|
65
|
+
action: 'fleet_dock';
|
|
66
|
+
/** Station display name. */
|
|
67
|
+
base: string;
|
|
68
|
+
/** Canonical base ID — this is what `location.docked_at` holds, not `base`. */
|
|
69
|
+
base_id: string;
|
|
70
|
+
}
|
|
71
|
+
/** Your fleet leader undocked and took the fleet with them. */
|
|
72
|
+
export interface OkFleetUndocked extends PushBase {
|
|
73
|
+
action: 'fleet_undock';
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The ship you were riding was destroyed or captured. You are at your home
|
|
77
|
+
* station with no ship. A state delta follows on a v2 socket; `riding` is not a
|
|
78
|
+
* state section, so clear any cached value yourself.
|
|
79
|
+
*/
|
|
80
|
+
export interface OkPassengerStranded extends PushBase {
|
|
81
|
+
action: 'passenger_stranded';
|
|
82
|
+
}
|
|
83
|
+
/** The Mobile Capital jumped while you were docked at it, taking you along. */
|
|
84
|
+
export interface OkMobileCapitalTransit extends PushBase {
|
|
85
|
+
action: 'mobile_capital_transit';
|
|
86
|
+
/** System ID the station arrived in. */
|
|
87
|
+
system: string;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* An espionage operation you launched is under way (start) or resolved
|
|
91
|
+
* (outcome). The two shapes differ: the start carries `arrival_tick`, the
|
|
92
|
+
* resolution carries `outcome` and `story`. Only the start reaches a v2 socket
|
|
93
|
+
* — the resolution ships as a delta-carrying `action_result` — but both are
|
|
94
|
+
* here because a v1 transport sees both.
|
|
95
|
+
*/
|
|
96
|
+
export type OkEspionage = (PushBase & {
|
|
97
|
+
action: 'espionage';
|
|
98
|
+
arrival_tick: number;
|
|
99
|
+
}) | (EspionageResponse & {
|
|
100
|
+
action: 'espionage';
|
|
101
|
+
});
|
|
102
|
+
/**
|
|
103
|
+
* In-system travel completed.
|
|
104
|
+
*
|
|
105
|
+
* v1 transports only. Over `/ws/v2` an arrival is a delta-carrying
|
|
106
|
+
* `action_result`; this plain broadcast is the fallback taken when no delta
|
|
107
|
+
* sections are set. A v2 consumer's `arrived` handler never fires — read
|
|
108
|
+
* `account.location` instead.
|
|
109
|
+
*/
|
|
110
|
+
export type OkArrived = TravelResponse & {
|
|
111
|
+
action: 'arrived';
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* A jump or Pathfinder drift completed. `pathfinder_arrival` is the off-network
|
|
115
|
+
* variant. v1 transports only, for the same reason as `OkArrived`.
|
|
116
|
+
*/
|
|
117
|
+
export type OkJumped = JumpResponse;
|
|
118
|
+
/** Docking completed. */
|
|
119
|
+
export type OkDocked = DockResponse & {
|
|
120
|
+
action: 'dock';
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* An attack resolved. Two shapes share the action and are separated by `kind`:
|
|
124
|
+
* `npc` covers pirates, wildlife, stations and prize hulls; `player` is PvP.
|
|
125
|
+
*/
|
|
126
|
+
export type OkAttack = AttackNpcResponse | AttackPlayerResponse;
|
|
127
|
+
/** A Pathfinder off-network jump was plotted, or an in-flight drift replotted. */
|
|
128
|
+
export type OkPathfinderPlot = PathfinderJumpResponse;
|
|
129
|
+
/** The server docked you so a dock-required command could run. */
|
|
130
|
+
export interface OkAutoDock extends PushBase {
|
|
131
|
+
type: 'auto_dock';
|
|
132
|
+
}
|
|
133
|
+
/** The server undocked you so a command needing open space could run. */
|
|
134
|
+
export interface OkAutoUndock extends PushBase {
|
|
135
|
+
type: 'auto_undock';
|
|
136
|
+
}
|
|
137
|
+
/** An Emergency Warp Stabilizer fired at critical hull and warped you home. */
|
|
138
|
+
export interface OkEmergencyWarpStabilizer extends PushBase {
|
|
139
|
+
type: 'emergency_warp_stabilizer_activated';
|
|
140
|
+
/** Home station display name — not a base ID. */
|
|
141
|
+
home_base: string;
|
|
142
|
+
hull: number;
|
|
143
|
+
max_hull: number;
|
|
144
|
+
}
|
|
145
|
+
/** Passengers you were carrying reached their destination. */
|
|
146
|
+
export interface OkPassengerArrivals extends PushBase {
|
|
147
|
+
type: 'passenger_arrivals';
|
|
148
|
+
station: string;
|
|
149
|
+
delivered: number;
|
|
150
|
+
stranded: number;
|
|
151
|
+
fare_collected: number;
|
|
152
|
+
/**
|
|
153
|
+
* Reputation change per counterparty, keyed by empire or stronghold ID. The
|
|
154
|
+
* key is always present, but the value is `null` when the delivery moved no
|
|
155
|
+
* reputation — the common case. Check before iterating.
|
|
156
|
+
*/
|
|
157
|
+
reputation_changes: Record<string, number> | null;
|
|
158
|
+
}
|
|
159
|
+
/** Another player moved connecting passengers onto one of your ships. */
|
|
160
|
+
export interface OkPassengersTransferred extends PushBase {
|
|
161
|
+
type: 'passengers_transferred';
|
|
162
|
+
/** ID of your ship the passengers were moved onto. */
|
|
163
|
+
ship_id: string;
|
|
164
|
+
count: number;
|
|
165
|
+
/** Station display name. */
|
|
166
|
+
station: string;
|
|
167
|
+
}
|
|
168
|
+
/** A ship-processing job at a station finished. */
|
|
169
|
+
export interface OkShipProcessing extends PushBase {
|
|
170
|
+
type: 'ship_processing';
|
|
171
|
+
}
|
|
172
|
+
/** Passengers are waiting in a transit lounge you can pick up from. */
|
|
173
|
+
export interface OkTransitLoungeDepartures extends PushBase {
|
|
174
|
+
type: 'transit_lounge_departures';
|
|
175
|
+
station: string;
|
|
176
|
+
base_id: string;
|
|
177
|
+
passengers: number;
|
|
178
|
+
/** Ticks until the soonest departure. */
|
|
179
|
+
soonest_ticks: number;
|
|
180
|
+
}
|
|
181
|
+
/** A shipment you contracted for is past its delivery window. */
|
|
182
|
+
export interface OkShipmentOverdue extends PushBase {
|
|
183
|
+
type: 'shipment_overdue';
|
|
184
|
+
shipment_id: string;
|
|
185
|
+
package_id: string;
|
|
186
|
+
/** Base ID the shipment is bound for. */
|
|
187
|
+
destination: string;
|
|
188
|
+
/** Ticks left before the shipment is recovered. */
|
|
189
|
+
recovery_ticks: number;
|
|
190
|
+
late_fee: number;
|
|
191
|
+
}
|
|
192
|
+
/** A new forum thread was posted. Broadcast to every connected player. */
|
|
193
|
+
export interface OkNewForumPost extends PushBase {
|
|
194
|
+
type: 'new_forum_post';
|
|
195
|
+
thread_id: string;
|
|
196
|
+
title: string;
|
|
197
|
+
author: string;
|
|
198
|
+
category: string;
|
|
199
|
+
/** RFC3339 timestamp. */
|
|
200
|
+
timestamp: string;
|
|
201
|
+
is_dev_team: boolean;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* A wildlife kill notice, sent when a hunt lands. Carries neither `action` nor
|
|
205
|
+
* `type` — the optional-undefined keys are what let the other members stay
|
|
206
|
+
* narrowable alongside it.
|
|
207
|
+
*/
|
|
208
|
+
export interface OkMessageOnly {
|
|
209
|
+
action?: undefined;
|
|
210
|
+
type?: undefined;
|
|
211
|
+
message: string;
|
|
212
|
+
}
|
|
213
|
+
/** You were invited to a fleet. */
|
|
214
|
+
export interface FleetInvite extends PushBase {
|
|
215
|
+
action: 'fleet_invite';
|
|
216
|
+
fleet_id: string;
|
|
217
|
+
leader_name: string;
|
|
218
|
+
leader_id: string;
|
|
219
|
+
/** True for a fleet auto-created to carry an arena challenge. */
|
|
220
|
+
arena_only: boolean;
|
|
221
|
+
}
|
|
222
|
+
/** Someone joined the fleet. Sent to every other member. */
|
|
223
|
+
export interface FleetMemberJoined {
|
|
224
|
+
action: 'fleet_member_joined';
|
|
225
|
+
player_id: string;
|
|
226
|
+
player_name: string;
|
|
227
|
+
}
|
|
228
|
+
/** Someone left the fleet. Sent to every other member. */
|
|
229
|
+
export interface FleetMemberLeft {
|
|
230
|
+
action: 'fleet_member_left';
|
|
231
|
+
player_id: string;
|
|
232
|
+
player_name: string;
|
|
233
|
+
}
|
|
234
|
+
/** The leader removed someone. Sent to the remaining members, not the target. */
|
|
235
|
+
export interface FleetMemberKicked {
|
|
236
|
+
action: 'fleet_member_kicked';
|
|
237
|
+
player_id: string;
|
|
238
|
+
player_name: string;
|
|
239
|
+
}
|
|
240
|
+
/** The leader removed *you* from the fleet. */
|
|
241
|
+
export interface FleetKicked extends PushBase {
|
|
242
|
+
action: 'fleet_kicked';
|
|
243
|
+
}
|
|
244
|
+
/** The fleet was disbanded. */
|
|
245
|
+
export interface FleetDisbanded extends PushBase {
|
|
246
|
+
action: 'fleet_disbanded';
|
|
247
|
+
}
|
|
248
|
+
/** Fleet leadership passed to another member. */
|
|
249
|
+
export interface FleetLeaderPromoted extends PushBase {
|
|
250
|
+
action: 'fleet_leader_promoted';
|
|
251
|
+
new_leader: string;
|
|
252
|
+
}
|
|
253
|
+
/** A fleet member died. */
|
|
254
|
+
export interface FleetMemberDied extends PushBase {
|
|
255
|
+
action: 'fleet_member_died';
|
|
256
|
+
player_name: string;
|
|
257
|
+
}
|
|
258
|
+
/** A passenger boarded a fleet ship. */
|
|
259
|
+
export interface FleetPassengerBoarded extends PushBase {
|
|
260
|
+
action: 'fleet_passenger_boarded';
|
|
261
|
+
player_id: string;
|
|
262
|
+
player_name: string;
|
|
263
|
+
}
|
|
264
|
+
/** A passenger stopped riding and left the fleet. */
|
|
265
|
+
export interface FleetPassengerDisembarked extends PushBase {
|
|
266
|
+
action: 'fleet_passenger_disembarked';
|
|
267
|
+
player_id: string;
|
|
268
|
+
player_name: string;
|
|
269
|
+
}
|
|
270
|
+
/** Every `ok` push known as of gameserver v0.596.2. */
|
|
271
|
+
export type OkPush = OkTravelStarted | OkJumpStarted | OkFleetTravelStarted | OkFleetJumpStarted | OkFleetDocked | OkFleetUndocked | OkPassengerStranded | OkMobileCapitalTransit | OkEspionage | OkArrived | OkJumped | OkDocked | OkAttack | OkPathfinderPlot | OkAutoDock | OkAutoUndock | OkEmergencyWarpStabilizer | OkPassengerArrivals | OkPassengersTransferred | OkShipProcessing | OkTransitLoungeDepartures | OkShipmentOverdue | OkNewForumPost | OkMessageOnly;
|
|
272
|
+
/** The `ok` variants keyed on `action`. `Extract`ed for an exhaustive switch. */
|
|
273
|
+
export type OkActionPush = Extract<OkPush, {
|
|
274
|
+
action: string;
|
|
275
|
+
}>;
|
|
276
|
+
/** The `ok` variants keyed on `type`. `Extract`ed for an exhaustive switch. */
|
|
277
|
+
export type OkTypePush = Extract<OkPush, {
|
|
278
|
+
type: string;
|
|
279
|
+
}>;
|
|
280
|
+
/** Every `fleet` push known as of gameserver v0.596.2, all keyed on `action`. */
|
|
281
|
+
export type FleetPush = FleetInvite | FleetMemberJoined | FleetMemberLeft | FleetMemberKicked | FleetKicked | FleetDisbanded | FleetLeaderPromoted | FleetMemberDied | FleetPassengerBoarded | FleetPassengerDisembarked;
|
|
282
|
+
/** `action` values carried by an `ok` push. */
|
|
283
|
+
export declare const OK_PUSH_ACTIONS: readonly ["travel", "jump", "fleet_travel", "fleet_jump", "fleet_dock", "fleet_undock", "passenger_stranded", "mobile_capital_transit", "espionage", "arrived", "jumped", "pathfinder_arrival", "dock", "attack", "pathfinder_jump", "pathfinder_redirect"];
|
|
284
|
+
export type OkPushAction = (typeof OK_PUSH_ACTIONS)[number];
|
|
285
|
+
/** `type` values carried by an `ok` push, for the frames that use that key. */
|
|
286
|
+
export declare const OK_PUSH_TYPES: readonly ["auto_dock", "auto_undock", "emergency_warp_stabilizer_activated", "passenger_arrivals", "passengers_transferred", "ship_processing", "transit_lounge_departures", "shipment_overdue", "new_forum_post"];
|
|
287
|
+
export type OkPushType = (typeof OK_PUSH_TYPES)[number];
|
|
288
|
+
/** `action` values carried by a `fleet` push. */
|
|
289
|
+
export declare const FLEET_PUSH_ACTIONS: readonly ["fleet_invite", "fleet_member_joined", "fleet_member_left", "fleet_member_kicked", "fleet_kicked", "fleet_disbanded", "fleet_leader_promoted", "fleet_member_died", "fleet_passenger_boarded", "fleet_passenger_disembarked"];
|
|
290
|
+
export type FleetPushAction = (typeof FLEET_PUSH_ACTIONS)[number];
|
|
291
|
+
export {};
|
|
@@ -2,11 +2,18 @@
|
|
|
2
2
|
* Local cache of the subscribed observation watch (current POI + system).
|
|
3
3
|
*
|
|
4
4
|
* Seeded from the `subscribe_observation` baseline and kept current by merging
|
|
5
|
-
* `observation_update` pushes: `*_changed` arrays upsert
|
|
6
|
-
*
|
|
5
|
+
* `observation_update` pushes: `*_changed` arrays upsert entities (keyed by
|
|
6
|
+
* their own id), `*_departed` arrays (id strings) remove them, and the
|
|
7
7
|
* cloaked-contact / signature hints are tracked alongside.
|
|
8
|
+
*
|
|
9
|
+
* Not a players-only feed: the baseline and every update also carry the
|
|
10
|
+
* pirate, empire-NPC, wildlife and intact-prize presence at the watched POI,
|
|
11
|
+
* each with the same changed/departed delta shape as players. It covers five
|
|
12
|
+
* of `get_nearby`'s six classes — arena NPCs are the one it does not carry, so
|
|
13
|
+
* a player in an arena match still has to poll `get_nearby` for their
|
|
14
|
+
* opponents. `offline_collapsed` has no equivalent here either.
|
|
8
15
|
*/
|
|
9
|
-
import type { NotificationObservationUpdate, SubscribeObservationResponse } from '../generated/openapi/types.gen.js';
|
|
16
|
+
import type { CreatureInfo, EmpireNpcInfo, NotificationObservationUpdate, PirateInfo, PrizeInfo, SubscribeObservationResponse } from '../generated/openapi/types.gen.js';
|
|
10
17
|
export type ObservedPlayer = NonNullable<NotificationObservationUpdate['nearby_changed']>[number];
|
|
11
18
|
export type CloakedContact = NonNullable<NotificationObservationUpdate['cloaked_resolved']>[number];
|
|
12
19
|
export interface ObservationView {
|
|
@@ -18,6 +25,14 @@ export interface ObservationView {
|
|
|
18
25
|
nearby: Map<string, ObservedPlayer>;
|
|
19
26
|
/** Uncloaked players system-wide, keyed by player_id. */
|
|
20
27
|
system: Map<string, ObservedPlayer>;
|
|
28
|
+
/** Pirate NPCs at the watched POI, keyed by pirate_id. */
|
|
29
|
+
pirates: Map<string, PirateInfo>;
|
|
30
|
+
/** Empire NPCs at the watched POI, keyed by npc_id. */
|
|
31
|
+
empireNpcs: Map<string, EmpireNpcInfo>;
|
|
32
|
+
/** Wildlife at the watched POI, keyed by creature_id. */
|
|
33
|
+
creatures: Map<string, CreatureInfo>;
|
|
34
|
+
/** Intact captured ships at the watched POI, keyed by prize_id. */
|
|
35
|
+
prizes: Map<string, PrizeInfo>;
|
|
21
36
|
/** Cloaked contacts resolved by an active sensor sweep, keyed by target_id. */
|
|
22
37
|
cloaked: Map<string, CloakedContact>;
|
|
23
38
|
/** A faint cloaked-ship signature is present at the watched POI. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spacemolt/lib",
|
|
3
|
-
"version": "14.0
|
|
3
|
+
"version": "14.2.0",
|
|
4
4
|
"description": "The definitive TypeScript library for SpaceMolt. WebSocket-v2-first, multi-account, with local state caches. Internals are regenerated from the server's OpenAPI spec.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/index.js",
|