@spacemolt/lib 12.2.0 → 12.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/dist/account.d.ts CHANGED
@@ -160,6 +160,8 @@ export declare class Account {
160
160
  private subscribedMarketBaseId;
161
161
  private observationSubscribedState;
162
162
  private observationActiveScanState;
163
+ /** Invalidates an in-flight reconciliation whenever a newer cache update arrives. */
164
+ private stateRevision;
163
165
  private subscribedObservationPoiId;
164
166
  private readonly reconnectedListeners;
165
167
  private readonly reconnectingListeners;
@@ -374,6 +376,22 @@ export declare class Account {
374
376
  * after every state update that could carry a `location` change.
375
377
  */
376
378
  private checkSubscriptionsAgainstLocation;
379
+ /**
380
+ * Fleet followers do not submit the movement mutation themselves, so their
381
+ * arrival is delivered as a plain `ok` push rather than an `action_result`
382
+ * carrying a state delta. Bridge the location data present on those pushes
383
+ * into the cache. Fleet docking is the one exception: its push only contains
384
+ * the station's display name, not the canonical base ID used by `docked_at`,
385
+ * so refresh the canonical snapshot instead of caching the wrong identifier.
386
+ */
387
+ private applyFleetMovementPush;
388
+ /**
389
+ * Fleet movement pushes omit follower fuel and skill deltas, while docking
390
+ * omits the canonical base ID. Reconcile those fields from one authoritative
391
+ * snapshot. A newer movement push invalidates an older in-flight query so a
392
+ * delayed dock/arrival snapshot cannot move the cache backwards.
393
+ */
394
+ private reconcileFleetState;
377
395
  /** Close the connection deliberately. Suppresses auto-reconnect. */
378
396
  close(): void;
379
397
  /** Fired after a successful reconnect + re-auth. Returns an unsubscribe function. */
package/dist/index.js CHANGED
@@ -4027,6 +4027,7 @@ class Account {
4027
4027
  subscribedMarketBaseId;
4028
4028
  observationSubscribedState = false;
4029
4029
  observationActiveScanState = false;
4030
+ stateRevision = 0;
4030
4031
  subscribedObservationPoiId;
4031
4032
  reconnectedListeners = new Set;
4032
4033
  reconnectingListeners = new Set;
@@ -4195,6 +4196,8 @@ class Account {
4195
4196
  const result = await this.commands.spacemolt.get_status();
4196
4197
  const snapshot = requireStructuredContent(result, "spacemolt/get_status");
4197
4198
  const changed = this.cache.seed(snapshot);
4199
+ if (changed.length)
4200
+ this.stateRevision++;
4198
4201
  if (changed.includes("location"))
4199
4202
  this.checkSubscriptionsAgainstLocation();
4200
4203
  if (changed.length)
@@ -4331,6 +4334,8 @@ class Account {
4331
4334
  nearby_players: nearbyPlayers,
4332
4335
  nearby_player_count: view.nearby.size
4333
4336
  });
4337
+ if (changed.length)
4338
+ this.stateRevision++;
4334
4339
  if (changed.length)
4335
4340
  this.emitStateChange(changed);
4336
4341
  }
@@ -4349,6 +4354,123 @@ class Account {
4349
4354
  this.subscribedObservationPoiId = undefined;
4350
4355
  }
4351
4356
  }
4357
+ applyFleetMovementPush(frame) {
4358
+ if (frame.type !== "ok" || !isRecord(frame.payload) || typeof frame.payload.action !== "string")
4359
+ return;
4360
+ const payload = frame.payload;
4361
+ let changed = [];
4362
+ let reconcile = false;
4363
+ const currentLocation = this.cache.location;
4364
+ const currentSystem = {
4365
+ connections: currentLocation?.connections,
4366
+ empire: currentLocation?.empire,
4367
+ security_status: currentLocation?.security_status,
4368
+ system_id: currentLocation?.system_id,
4369
+ system_name: currentLocation?.system_name
4370
+ };
4371
+ switch (payload.action) {
4372
+ case "fleet_jump": {
4373
+ if (typeof payload.destination !== "string" || typeof payload.arrival_tick !== "number") {
4374
+ this.warnMalformedFrame(frame);
4375
+ return;
4376
+ }
4377
+ changed = this.cache.applyDelta({
4378
+ location: {
4379
+ in_transit: true,
4380
+ transit_arrival_tick: payload.arrival_tick,
4381
+ transit_dest_system_id: payload.destination,
4382
+ transit_type: "jump"
4383
+ }
4384
+ });
4385
+ reconcile = true;
4386
+ break;
4387
+ }
4388
+ case "jumped":
4389
+ case "pathfinder_arrival": {
4390
+ if (typeof payload.system_id !== "string" || typeof payload.system !== "string") {
4391
+ this.warnMalformedFrame(frame);
4392
+ return;
4393
+ }
4394
+ changed = this.cache.applyDelta({
4395
+ location: {
4396
+ in_transit: false,
4397
+ poi_id: typeof payload.poi === "string" ? payload.poi : undefined,
4398
+ system_id: payload.system_id,
4399
+ system_name: payload.system
4400
+ }
4401
+ });
4402
+ reconcile = true;
4403
+ break;
4404
+ }
4405
+ case "fleet_travel": {
4406
+ if (typeof payload.destination !== "string" || typeof payload.arrival_tick !== "number") {
4407
+ this.warnMalformedFrame(frame);
4408
+ return;
4409
+ }
4410
+ changed = this.cache.applyDelta({
4411
+ location: {
4412
+ ...currentSystem,
4413
+ in_transit: true,
4414
+ transit_arrival_tick: payload.arrival_tick,
4415
+ transit_dest_poi_id: payload.destination,
4416
+ transit_type: "travel"
4417
+ }
4418
+ });
4419
+ reconcile = true;
4420
+ break;
4421
+ }
4422
+ case "arrived": {
4423
+ if (typeof payload.poi_id !== "string" || typeof payload.poi !== "string") {
4424
+ this.warnMalformedFrame(frame);
4425
+ return;
4426
+ }
4427
+ changed = this.cache.applyDelta({
4428
+ location: {
4429
+ ...currentSystem,
4430
+ in_transit: false,
4431
+ poi_id: payload.poi_id,
4432
+ poi_name: payload.poi
4433
+ }
4434
+ });
4435
+ reconcile = true;
4436
+ break;
4437
+ }
4438
+ case "fleet_undock":
4439
+ changed = currentLocation ? this.cache.patchSection("location", { docked_at: undefined }) : this.cache.applyDelta({ location: {} });
4440
+ break;
4441
+ case "fleet_dock":
4442
+ reconcile = true;
4443
+ break;
4444
+ default:
4445
+ return;
4446
+ }
4447
+ const revision = ++this.stateRevision;
4448
+ if (changed.includes("location"))
4449
+ this.checkSubscriptionsAgainstLocation();
4450
+ if (changed.length)
4451
+ this.emitStateChange(changed);
4452
+ if (reconcile)
4453
+ this.reconcileFleetState(revision, payload.action);
4454
+ }
4455
+ async reconcileFleetState(revision, action) {
4456
+ try {
4457
+ const result = await this.commands.spacemolt.get_status();
4458
+ const snapshot = requireStructuredContent(result, "spacemolt/get_status");
4459
+ if (revision !== this.stateRevision)
4460
+ return;
4461
+ const changed = this.cache.seed(snapshot);
4462
+ if (changed.length)
4463
+ this.stateRevision++;
4464
+ if (changed.includes("location"))
4465
+ this.checkSubscriptionsAgainstLocation();
4466
+ if (changed.length)
4467
+ this.emitStateChange(changed);
4468
+ } catch (err) {
4469
+ if (revision === this.stateRevision) {
4470
+ console.warn(`[spacemolt] failed to reconcile state after ${action}`, err);
4471
+ }
4472
+ }
4473
+ }
4352
4474
  close() {
4353
4475
  this.userClosing = true;
4354
4476
  this.socket.close();
@@ -4465,6 +4587,8 @@ class Account {
4465
4587
  const delta = frame.payload.result;
4466
4588
  if (delta) {
4467
4589
  const changed = this.cache.applyDelta(delta);
4590
+ if (changed.length)
4591
+ this.stateRevision++;
4468
4592
  if (changed.includes("location"))
4469
4593
  this.checkSubscriptionsAgainstLocation();
4470
4594
  if (changed.length)
@@ -4499,6 +4623,7 @@ class Account {
4499
4623
  return;
4500
4624
  }
4501
4625
  default:
4626
+ this.applyFleetMovementPush(frame);
4502
4627
  this.emitter.emit(frame);
4503
4628
  }
4504
4629
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spacemolt/lib",
3
- "version": "12.2.0",
3
+ "version": "12.2.1",
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",